diff --git a/APM/MEX/matRad_APMCalcExpTermLateralFast_mex.mexw64 b/APM/MEX/matRad_APMCalcExpTermLateralFast_mex.mexw64 new file mode 100644 index 000000000..d9a655147 Binary files /dev/null and b/APM/MEX/matRad_APMCalcExpTermLateralFast_mex.mexw64 differ diff --git a/APM/MEX/matRad_APMCalcExpTermRangeFast_mex.mexw64 b/APM/MEX/matRad_APMCalcExpTermRangeFast_mex.mexw64 new file mode 100644 index 000000000..f181a1860 Binary files /dev/null and b/APM/MEX/matRad_APMCalcExpTermRangeFast_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcFourthRangeMom.m b/APM/MEX/matRad_calcFourthRangeMom.m new file mode 100644 index 000000000..2036efec9 --- /dev/null +++ b/APM/MEX/matRad_calcFourthRangeMom.m @@ -0,0 +1,126 @@ +function [fourthMom] = matRad_calcFourthRangeMom(numOfSpots,numComp,w,mMeanA,mMeanB,mMeanC,mMeanD,... + mWidthA,mWidthB,mWidthC,mWidthD,... + mWeightA,mWeightB,mWeightC,mWeightD,... + voxelPos,mSysCovRadDept) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the fourth raw moment. +% +% call +% [fourthMom] = matRad_calcFourthRangeMom(numOfSpots,numComp,w,mMeanA,mMeanB,mMeanC,mMeanD, ... +% mWidthA,mWidthB,mWidthC,mWidthD,... +% mWeightA,mWeightB,mWeightC,mWeightD,... +% voxelPos,mSysCovRadDept) +% +% input +% numOfSpots total number of spots +% numComp total number of Gaussian components e.g. 10 or 13 +% mMeanA-mMeanD: four mean vectors of spot components j o q n +% mWidthA-mWidthD: four width vectors of spot components j o q n +% mWeightA-mWeightD: four weight vectors of spot components j o q n +% voxelPos: voxel position +% mSysCovRadDept: full covariance matrix of range error +% +% output +% fourthMom: fourth central moment +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 Hans-Peter Wieser +% +% This file is part of the matRad project. It is subject 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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +assert(isa(numOfSpots,'double')); +assert(isa(numComp,'double')); +assert(isa(w,'double')); +assert(isa(mMeanA,'double')); +assert(isa(mMeanB,'double')); +assert(isa(mMeanC,'double')); +assert(isa(mMeanD,'double')); +assert(isa(mWidthA,'double')); +assert(isa(mWidthB,'double')); +assert(isa(mWidthC,'double')); +assert(isa(mWidthD,'double')); +assert(isa(mWeightA,'double')); +assert(isa(mWeightB,'double')); +assert(isa(mWeightC,'double')); +assert(isa(mWeightD,'double')); +assert(isa(voxelPos,'double')); +assert(isa(mSysCovRadDept,'double')); + +coder.varsize('numOfSpots', [1 1], [0 0]); +coder.varsize('numComp', [1 1], [0 0]); +coder.varsize('w', [60 1],[1 0]); +coder.varsize('mMeanA', [15 60],[1 1]); +coder.varsize('mMeanB', [15 60],[1 1]); +coder.varsize('mMeanC', [15 60],[1 1]); +coder.varsize('mMeanD', [15 60],[1 1]); +coder.varsize('mWidthA', [15 60],[1 1]); +coder.varsize('mWidthB', [15 60],[1 1]); +coder.varsize('mWidthD', [15 60],[1 1]); +coder.varsize('mWidthD', [15 60],[1 1]); +coder.varsize('mWeightA', [15 60],[1 1]); +coder.varsize('mWeightB', [15 60],[1 1]); +coder.varsize('mWeightC', [15 60],[1 1]); +coder.varsize('mWeightD', [15 60],[1 1]); +coder.varsize('voxelPos', [1 1],[0 0]); +coder.varsize('mSysCovRadDept', [60 60],[1 1]); + + +mPSI_joqn = zeros(numOfSpots,numOfSpots,numOfSpots,numOfSpots); + +for j = 1:numOfSpots + for o = 1:numOfSpots + for q = 1:numOfSpots + for n = 1:numOfSpots + + sigma = [mSysCovRadDept(j,j) mSysCovRadDept(j,o) mSysCovRadDept(j,q) mSysCovRadDept(j,n);... + mSysCovRadDept(o,j) mSysCovRadDept(o,o) mSysCovRadDept(o,q) mSysCovRadDept(o,n);... + mSysCovRadDept(q,j) mSysCovRadDept(q,o) mSysCovRadDept(q,q) mSysCovRadDept(q,n);... + mSysCovRadDept(n,j) mSysCovRadDept(n,o) mSysCovRadDept(n,q) mSysCovRadDept(n,n)]; + + dev_j = voxelPos - mMeanA(:,j); + dev_o = voxelPos - mMeanB(:,o); + dev_q = voxelPos - mMeanC(:,q); + dev_n = voxelPos - mMeanD(:,n); + + vW = reshape(reshape(reshape(mWeightA(:,j) * mWeightB(:,o)',[],1) * mWeightC(:,q)',[],1) * mWeightD(:,n)',[numComp,numComp,numComp,numComp]); + + Y = 0; + for J = 1:numComp + for O = 1:numComp + for Q = 1:numComp + for N = 1:numComp + + lambda = diag([mWidthA(J,j) mWidthB(O,o) mWidthC(Q,q) mWidthD(N,n)]); + + LaSi = (lambda + sigma); + InvLaSi = inv(LaSi); + + Y = Y + (vW(J,O,Q,N)/((2*pi)^(4/2)*sqrt(det(LaSi)))) * ... + exp(-0.5*([dev_j(J) dev_o(O) dev_q(Q) dev_n(N)]) * (InvLaSi) * ([dev_j(J) dev_o(O) dev_q(Q) dev_n(N)])'); + + end + end + end + end + + mPSI_joqn(j,o,q,n) = Y; + + end + end + end +end + + +fourthMom = w'* reshape(reshape(reshape(mPSI_joqn,[numOfSpots^3 numOfSpots]) * w, [numOfSpots^2 numOfSpots]) * w , [numOfSpots numOfSpots]) * w; + + +end diff --git a/APM/MEX/matRad_calcFourthRangeMom_mex.mexmaci64 b/APM/MEX/matRad_calcFourthRangeMom_mex.mexmaci64 new file mode 100644 index 000000000..f6b8cd073 Binary files /dev/null and b/APM/MEX/matRad_calcFourthRangeMom_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcFourthRangeMom_mex.mexw64 b/APM/MEX/matRad_calcFourthRangeMom_mex.mexw64 new file mode 100644 index 000000000..166013392 Binary files /dev/null and b/APM/MEX/matRad_calcFourthRangeMom_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcGeoDistsFast.m b/APM/MEX/matRad_calcGeoDistsFast.m new file mode 100644 index 000000000..d685344fe --- /dev/null +++ b/APM/MEX/matRad_calcGeoDistsFast.m @@ -0,0 +1,116 @@ +function [isoLatDistsX,isoLatDistsZ] = matRad_calcGeoDistsFast(rot_coords_bev, ... + sourcePoint_bev, ... + targetPoint_bev, ... + SAD, ... + radDepthIx, ... + lateralCutOff) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad calculation of lateral distances from central ray used for +% dose calcultion +% +% call +% [ix,x_latDists,z_latDists] = ... +% matRad_calcGeoDists(rot_coords_bev, ... +% sourcePoint_bev, ... +% targetPoint_bev, ... +% SAD, ... +% radDepthIx, ... +% lateralCutOff) +% +% input +% rot_coords_bev: coordinates in bev of the voxels with index V, +% where also ray tracing results are availabe +% sourcePoint_bev: source point in voxel coordinates in beam's eye view +% targetPoint_bev: target point in voxel coordinated in beam's eye view +% SAD: source-to-axis distance +% radDepthIx: sub set of voxels for which radiological depth +% calculations are available +% lateralCutOff: lateral cutoff specifying the neighbourhood for +% which dose calculations will actually be performed +% +% output +% ix: indices of voxels where we want to compute dose +% influence data +% isoLatDistsX: lateral x-distance to the central ray projected to +% iso center plane +% isoLatDistsZ: lateral z-distance to the central ray projected to +% iso center plane +% radialDist_sq: squared radial distance to the central ray (where the +% actual computation of the radiological depth takes place) +% +% References +% +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2015 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% ROTATE A SINGLE BEAMLET AND ALIGN WITH BEAMLET WHO PASSES THROUGH +% ISOCENTER + + +assert(isa(rot_coords_bev,'double')); +assert(isa(sourcePoint_bev,'double')); +assert(isa(targetPoint_bev,'double')); +assert(isa(SAD,'double')); +assert(isa(radDepthIx,'double')); +assert(isa(lateralCutOff,'double')); + +coder.varsize('rot_coords_bev',[1 3]); +coder.varsize('sourcePoint_bev',[1 3]); +coder.varsize('targetPoint_bev',[1 3]); +coder.varsize('SAD',1); +coder.varsize('radDepthIx',1); +coder.varsize('lateralCutOff',1); + +% Put [0 0 0] position in the source point for beamlet who passes through +% isocenter +a = -sourcePoint_bev'; + +% Normalize the vector +a = a/norm(a); + +% Put [0 0 0] position in the source point for a single beamlet +b = (targetPoint_bev - sourcePoint_bev)'; + +% Normalize the vector +b = b/norm(b); + +% Define function for obtain rotation matrix. +if all(a==b) % rotation matrix corresponds to eye matrix if the vectors are the same + rot_coords_temp = rot_coords_bev; +else + % Define rotation matrix + v = cross(a,b); + ssc = [0 -v(3) v(2); v(3) 0 -v(1); -v(2) v(1) 0]; + R = eye(3) + ssc + ssc^2*(1-dot(a,b))/(norm(cross(a,b))^2); + + % Rotate every CT voxel + rot_coords_temp = rot_coords_bev*R; +end + +% Put [0 0 0] position CT in center of the beamlet. +latDistsX = rot_coords_temp(:,1) + sourcePoint_bev(1); +latDistsZ = rot_coords_temp(:,3) + sourcePoint_bev(3); + +% check of radial distance exceeds lateral cutoff (projected to iso center) +rad_distancesSq = latDistsX.^2 + latDistsZ.^2; +subsetMask = rad_distancesSq ./ rot_coords_temp(:,2).^2 <= lateralCutOff^2 /SAD^2; + +isoLatDistsX = latDistsX(subsetMask)./rot_coords_temp(subsetMask,2)*SAD; +isoLatDistsZ = latDistsZ(subsetMask)./rot_coords_temp(subsetMask,2)*SAD; + + + diff --git a/APM/MEX/matRad_calcGeoDistsFast_mex.mexmaci64 b/APM/MEX/matRad_calcGeoDistsFast_mex.mexmaci64 new file mode 100644 index 000000000..2faa50d16 Binary files /dev/null and b/APM/MEX/matRad_calcGeoDistsFast_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcGeoDistsFast_mex.mexw64 b/APM/MEX/matRad_calcGeoDistsFast_mex.mexw64 new file mode 100644 index 000000000..defc4929d Binary files /dev/null and b/APM/MEX/matRad_calcGeoDistsFast_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcSecLatMom.m b/APM/MEX/matRad_calcSecLatMom.m new file mode 100644 index 000000000..4686a816d --- /dev/null +++ b/APM/MEX/matRad_calcSecLatMom.m @@ -0,0 +1,58 @@ +function PSI_ijlm = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12,vLaSi21,Dev_j,Dev_m) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the second raw moment. Please note that i +% and l depict voxel indices and j and m pencil beam indices. This function +% allows to calculate the the correlation between spot j and multiple +% spots m simultaniously. +% +% call +% PSI_ijlm = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12,vLaSi21,Dev_j,Dev_m) +% +% input +% vLaSi11: matRads resultGUI struct +% vLaSi22: matRad plan meta information struct +% vLaSi12: matRad dij dose influence struct +% vLaSi21: matRad steering information struct +% Dev_j: matRad critical structure struct cst +% Dev_m + +% output +% PSI_ijlm: matRad critical structure struct cst +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +assert(isa(vLaSi11,'double')); +assert(isa(vLaSi22,'double')); +assert(isa(vLaSi12,'double')); +assert(isa(vLaSi21,'double')); +assert(isa(Dev_j,'double')); +assert(isa(Dev_m,'double')); + +coder.varsize('vLaSi11',[1 1]); +coder.varsize('vLaSi22',[Inf 1],[1 0]); +coder.varsize('vLaSi12',[Inf 1],[1 0]); +coder.varsize('vLaSi21',[Inf 1],[1 0]); +coder.varsize('Dev_j',[1 1]); +coder.varsize('Dev_m',[Inf 1],[1 0]); + +Det = abs((vLaSi11 .* vLaSi22) - (vLaSi12 .* vLaSi21)); +FracDet = (1./(2*pi.*real(sqrt(Det)))); +ExpTerm = -.5 .* ((Dev_j.*(vLaSi22./Det) + Dev_m.*(-vLaSi12./Det)).* Dev_j +... + (Dev_j.*(-vLaSi21./Det) + Dev_m.*(vLaSi11./Det)).* Dev_m); +PSI_ijlm = FracDet .* exp(ExpTerm); + +end + diff --git a/APM/MEX/matRad_calcSecLatMomFast.m b/APM/MEX/matRad_calcSecLatMomFast.m new file mode 100644 index 000000000..531f79f19 --- /dev/null +++ b/APM/MEX/matRad_calcSecLatMomFast.m @@ -0,0 +1,190 @@ +function [PSI_x_corr_ijm, PSI_x_uncorr_ijm, PSI_z_corr_ijm, PSI_z_uncorr_ijm, subIx] = ... + matRad_calcSecLatMomFast(vCandidates,numBeams,numBixels,vBixelindexBeamOffset,vBixelindexBeam,... + Lx,Lz,latSqSigma,randError,sysError) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the second raw moment of the lateral dose profile. +% This function is optimized for beam wise block correlation and should not +% be used for other correlatin assumptions. This function utilizes the +% following corr assumption for setup erros: +% All spots from the same beam direction are perfectly correlated whereas +% spots from different beam directions are uncorrelated. +% +% call +% [PSI_x_corr_ijm, PSI_x_uncorr_ijm, PSI_z_corr_ijm, PSI_z_uncorr_ijm, subIx] = ... +% matRad_calcSecLatMomFast(vCandidates,numBeams,numBixels,vBixelindexBeamOffset,vBixelindexBeam,... +% Lx,Lz,latSqSigma,randError,sysError,randCovLateral,sysCovLateral)% +% input +% vCandidates: lateral correlated bixel indices +% numBeams: number of beams +% numBixels: number of bixels +% vBixelindexBeamOffset: start and end bixel indices of each beam direction +% vBixelindexBeam: bixel beam look up table +% Lx: lateral distance in x direction of voxel i to spot j +% Lz: lateral distance in z direction of voxel i to spot j +% latSqSigma: beam spread (sigma of lateral beam profile in the patient considering the inital beam width +% randError: random setup error +% sysError: systematic setup error +% randCovLateral: (for further developments to include arbritrary correlation assumptions) +% sysCovLateral: (for further developments to include arbritrary correlation assumptions) +% +% output +% PSI_x_corr_ijm: second central moment of the lateral dose in x assuming correlation (single fraction term) +% PSI_x_uncorr_ijm: second central moment of the lateral dose in x assuming no random correlation (only systematic) +% PSI_z_corr_ijm: second central moment of the lateral dose in x assuming correlation (single fraction term) +% PSI_z_uncorr_ijm: second central moment of the lateral dose in x assuming no random correlation correlation (only systematic) +% subIx linear indices +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 Wieser Hans-Peter +% +% This file is part of the matRad project. It is subject 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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +assert(isa(vCandidates,'double')); +assert(isa(numBixels,'double')); +assert(isa(vBixelindexBeamOffset,'double')); +assert(isa(vBixelindexBeam,'double')); +assert(isa(Lx,'double')); +assert(isa(Lz,'double')); +assert(isa(latSqSigma,'double')); +assert(isa(randError,'double')); +assert(isa(sysError,'double')); +assert(isa(numBeams,'double')); +% assert(isa(randCovLateral,'double')); +% assert(isa(sysCovLateral,'double')); + +maxNumBixel = 30000; +uppperBound2 = 10000; + +coder.varsize('vCandidates',[1 maxNumBixel],[0 1]); +coder.varsize('numBixels',[1 1]); +coder.varsize('numBeams',[1 1]); +coder.varsize('vBixelindexBeamOffset',[1 uppperBound2],[0 1]); +coder.varsize('vBixelindexBeam',[maxNumBixel 1],[1 0]); +coder.varsize('Lx',[1 maxNumBixel],[0 1]); +coder.varsize('Lz',[1 maxNumBixel],[0 1]); +coder.varsize('latSqSigma',[uppperBound2 1],[1 0]); +coder.varsize('randError',[1 1],[0 0]); +coder.varsize('sysError',[1 1],[0 0]); +% coder.varsize('randCovLateral',[maxNumBixel 1],[1 0]); +% coder.varsize('sysCovLateral', [maxNumBixel 1],[1 0]); + +numCandidates = numel(vCandidates); +ixHelper = (1:1:numCandidates)'; +estimatedElemLateral = round(3*((numCandidates^2)./numBeams)); +PSI_x_uncorr_ijm = zeros(estimatedElemLateral,1); +PSI_x_corr_ijm = zeros(estimatedElemLateral,1); +PSI_z_uncorr_ijm = zeros(estimatedElemLateral,1); +PSI_z_corr_ijm = zeros(estimatedElemLateral,1); + +subIx = zeros(estimatedElemLateral,1); +index = 1; cntSpot = 1; + +for i = 1:numCandidates + + j = vCandidates(1,i); + upperBeamIdx = vBixelindexBeamOffset(vBixelindexBeam(j)+1); + lowerBeamIdx = vBixelindexBeamOffset(vBixelindexBeam(j)); + + bLatSqSig = false(1,numCandidates); + ixBixelLatFull = vCandidates(cntSpot:end); + ixx = (ixBixelLatFull <= upperBeamIdx & ixBixelLatFull >= lowerBeamIdx); + ixBixelLat = ixBixelLatFull(ixx); + bLatSqSig(1,cntSpot:end) = ixx; + + if ~isempty(ixBixelLat) + + mIdx = false(numCandidates,1); + mIdx(cntSpot:end) = ixx; + mIdx = ixHelper(mIdx); + currSize = length(mIdx); + jIdx = cntSpot.*ones(currSize,1); + + LinIdx = jIdx + (mIdx-1)*numCandidates; + LinIdxInv = mIdx + (jIdx-1)*numCandidates; + + idxSpotBeamLUT = vBixelindexBeam(j) == vBixelindexBeam(ixBixelLat); + + vLaSi11 = (randError^2 + sysError^2) + latSqSigma(cntSpot); + vLaSi12 = (randError^2 + sysError^2) * idxSpotBeamLUT; + vLaSi12uncorr = (sysError^2 * idxSpotBeamLUT); + vLaSi22 = sysError^2 + randError^2 + latSqSigma(bLatSqSig); + + % x-direction + Dev_ij = Lx(cntSpot); + Dev_im = Lx(mIdx)'; + + DetCorr = abs((vLaSi11 .* vLaSi22) - (vLaSi12 .* vLaSi12)); + FracDetCorr = (1./(2*pi.*real(sqrt(DetCorr)))); + + PSI_x_corr_ijmTmp = FracDetCorr .* exp(-.5 .* ((Dev_ij.*(vLaSi22./DetCorr) + Dev_im.*(-vLaSi12./DetCorr)).* Dev_ij +... + (Dev_ij.*(-vLaSi12./DetCorr) + Dev_im.*(vLaSi11./DetCorr)).* Dev_im)); + + DetUnCorr = abs((vLaSi11 .* vLaSi22) - (vLaSi12uncorr .* vLaSi12uncorr)); + FracDetUnCorr = (1./(2*pi.*real(sqrt(DetUnCorr)))); + + PSI_x_uncorr_ijmTmp = FracDetUnCorr .* exp(-.5 .* ((Dev_ij.*(vLaSi22./DetUnCorr) + Dev_im.*(-vLaSi12uncorr./DetUnCorr)).* Dev_ij +... + (Dev_ij.*(-vLaSi12uncorr./DetUnCorr) + Dev_im.*(vLaSi11./DetUnCorr)).* Dev_im)); + + % z-direction + Dev_ij = Lz(cntSpot); + Dev_im = Lz(mIdx)'; + + ExpTerm = -.5 .* ((Dev_ij.*(vLaSi22./DetCorr) + Dev_im.*(-vLaSi12./DetCorr)).* Dev_ij +... + (Dev_ij.*(-vLaSi12./DetCorr) + Dev_im.*(vLaSi11./DetCorr)).* Dev_im); + + PSI_z_corr_ijmTmp = FracDetCorr .* exp(ExpTerm); + + + ExpTerm = -.5 .* ((Dev_ij.*(vLaSi22./DetUnCorr) + Dev_im.*(-vLaSi12uncorr./DetUnCorr)).* Dev_ij +... + (Dev_ij.*(-vLaSi12uncorr./DetUnCorr) + Dev_im.*(vLaSi11./DetUnCorr)).* Dev_im); + + PSI_z_uncorr_ijmTmp = FracDetUnCorr .* exp(ExpTerm); + + if numel(PSI_x_corr_ijmTmp) ~= 1 + offset = 2; + vRange = index:index+numel(PSI_x_corr_ijmTmp)*2-offset; + PSI_x_corr_ijm(vRange') = [PSI_x_corr_ijmTmp; PSI_x_corr_ijmTmp(offset:end)]; + PSI_x_uncorr_ijm(vRange') = [PSI_x_uncorr_ijmTmp; PSI_x_uncorr_ijmTmp(offset:end)]; + PSI_z_corr_ijm(vRange') = [PSI_z_corr_ijmTmp; PSI_z_corr_ijmTmp(offset:end)]; + PSI_z_uncorr_ijm(vRange') = [PSI_z_uncorr_ijmTmp; PSI_z_uncorr_ijmTmp(offset:end)]; + subIx(vRange) = [LinIdx; LinIdxInv(offset:end)]; + else + vRange = index; + PSI_x_corr_ijm(vRange) = PSI_x_corr_ijmTmp; + PSI_x_uncorr_ijm(vRange) = PSI_x_uncorr_ijmTmp; + PSI_z_corr_ijm(vRange) = PSI_z_corr_ijmTmp; + PSI_z_uncorr_ijm(vRange) = PSI_z_uncorr_ijmTmp; + subIx(vRange) = LinIdx; + end + + index = index + numel(vRange); + + end + + cntSpot = cntSpot + 1; + +end + +PSI_x_corr_ijm = PSI_x_corr_ijm(1:index-1,1); +PSI_x_uncorr_ijm = PSI_x_uncorr_ijm(1:index-1,1); + +PSI_z_corr_ijm = PSI_z_corr_ijm(1:index-1,1); +PSI_z_uncorr_ijm = PSI_z_uncorr_ijm(1:index-1,1); + +subIx = subIx(1:index-1,1); + +end + + + diff --git a/APM/MEX/matRad_calcSecLatMomFast_mex.mexmaci64 b/APM/MEX/matRad_calcSecLatMomFast_mex.mexmaci64 new file mode 100644 index 000000000..4b981d3e7 Binary files /dev/null and b/APM/MEX/matRad_calcSecLatMomFast_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcSecLatMomFast_mex.mexw64 b/APM/MEX/matRad_calcSecLatMomFast_mex.mexw64 new file mode 100644 index 000000000..70cec67a3 Binary files /dev/null and b/APM/MEX/matRad_calcSecLatMomFast_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcSecLatMom_mex.mexmaci64 b/APM/MEX/matRad_calcSecLatMom_mex.mexmaci64 new file mode 100644 index 000000000..2cf60da9a Binary files /dev/null and b/APM/MEX/matRad_calcSecLatMom_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcSecLatMom_mex.mexw64 b/APM/MEX/matRad_calcSecLatMom_mex.mexw64 new file mode 100644 index 000000000..2fc8b3c7c Binary files /dev/null and b/APM/MEX/matRad_calcSecLatMom_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcSecRangeMom.m b/APM/MEX/matRad_calcSecRangeMom.m new file mode 100644 index 000000000..fd8bf5c09 --- /dev/null +++ b/APM/MEX/matRad_calcSecRangeMom.m @@ -0,0 +1,71 @@ +function PSIrange = matRad_calcSecRangeMom(vLaSi11,vLaSi22,vLaSi12,vLaSi21,Dev_j,Dev_m,w_j,w_m,mW_CovBio) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the second raw moment. Please note that i +% and l depict voxel indices and j and m pencil beam indices. This function +% allows to calculate the the correlation between on spot j and multiple +% spots m simultaniously. +% +% call +% PSI_ijlm = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12,vLaSi21,Dev_j,Dev_m) +% +% input +% vLaSi11: Lambda + Sigma of spot combination j,j +% vLaSi22: Lambda + Sigma of spot combination m,m +% vLaSi12: Lambda + Sigma of spot combination j,m +% vLaSi21: Lambda + Sigma of spot combination m,j +% Dev_j: distance between radiological depth and the Gaussian means of component j +% Dev_m: distance between radiological depth and the Gaussian means of component m +% mW_CovBio: covariance of the gaussian weights +% +% output +% PSI_ijlm: matRad critical structure struct cst +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +assert(isa(vLaSi11,'double')); +assert(isa(vLaSi22,'double')); +assert(isa(vLaSi12,'double')); +assert(isa(vLaSi21,'double')); +assert(isa(Dev_j,'double')); +assert(isa(Dev_m,'double')); +assert(isa(w_j,'double')); +assert(isa(w_m,'double')); +assert(isa(mW_CovBio,'double')); + +upperBound = 20; + +coder.varsize('vLaSi11',[upperBound 1],[1 0]); +coder.varsize('vLaSi22',[upperBound 1],[1 0]); +coder.varsize('vLaSi12',[1 1]); +coder.varsize('vLaSi21',[1 1]); +coder.varsize('Dev_j',[upperBound 1],[1 0]); +coder.varsize('Dev_m',[upperBound 1],[1 0]); +coder.varsize('w_j',[upperBound 1],[1 0]); +coder.varsize('w_m',[upperBound 1],[1 0]); +coder.varsize('mW_CovBio',[upperBound upperBound],[1 1]); + + +bioOffset = mW_CovBio + (w_j * w_m'); +Det = vLaSi11*vLaSi22' - (vLaSi12*vLaSi21'); +FracDet = 1./(2*pi*sqrt(Det)); +ExpTerm = FracDet .* exp(-.5./Det.*((Dev_j.^2)*vLaSi22' - bsxfun(@times,(Dev_j*Dev_m'),(vLaSi12+vLaSi21)) + vLaSi11*(Dev_m.^2)')); +PSIrange = ones(numel(w_j),1)' * (ExpTerm .* bioOffset) * ones(numel(w_m),1); + +end + + + + + diff --git a/APM/MEX/matRad_calcSecRangeMomFast.m b/APM/MEX/matRad_calcSecRangeMomFast.m new file mode 100644 index 000000000..601d38a54 --- /dev/null +++ b/APM/MEX/matRad_calcSecRangeMomFast.m @@ -0,0 +1,291 @@ +function [PSI_y_corr_ijm, PSI_y_uncorr_ijm,LinIx,LinIxInv] = matRad_calcSecRangeMomFast(amplificationFactor, numBixels,numRays,vEnergyIx,vBeamIx,vCandidates,Z,mMean,mWeight,mWidth,mDepth,LUT,... + randError,sysError,mRandCovRadDepth, mSysCovRadDepth,mBioCov,relUCTbio, mCovSpot) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the second raw moment for depth dose profile. +% This function is optimized for ray wise block correlation and should not +% be used for other correlatin assumptions. This function utilizes the +% following corr assumption for range erros: +% All spots on the same ray are perfectly correlated whereas +% spots from different rays are uncorrelated +% +% call +% [PSI_y_corr_ijm, PSI_y_uncorr_ijm,LinIx,LinIxInv] = matRad_calcSecRangeMomFast(amplificationFactor, numBixels,numRays,vEnergyIx,vBeamIx,vCandidates,Z,mMean,mWeight,mWidth,mDepth,LUT,... +% randError,sysError,mRandCovRadDepth, mSysCovRadDepth) +% +% input +% amplificationFactor: factor to will be multiplied with the resulting second central moment (converstion factor, const RBE etc.) +% numBixels: number of bixels +% numRays: number of rays +% vEnergyIx: energy index vector for all spots +% vBeamIx: beam bixel look up vector +% vCandidates: correlated spots +% Z: radiological depth of voxel i of beam s +% mMean: base data - mean of gaussian components +% mWeight: base data - weight of gaussian components +% mWidth: base data - width of gaussian components +% mDepth: base data - depth values +% LUT: base data - look up table to use only a reduced number of gaussian components +% randError: random range error +% sysError: systematic range error +% mRandCovRadDepth: random correlation between bixels +% mSysCovRadDepth: systematic correlation between bixels +% mBioCov: correlation for bio uncertainties - usually its the same structure as for range uncertainties - ray-wise correlation +% relUCTbio relative uncertainty of weights 0.2 equals a 20 % uncertainty +% mCovSpot covariance matrix of gaussian weights +% +% +% output +% PSI_y_corr_ijm: second central moment of the depth dose in y assuming correlation (single fraction term) +% PSI_y_uncorr_ijm: second central moment of the lateral dose in y assuming no random correlation (only systematic) +% LinIx: linear indices +% LinIxInv: inverted linear indices for symmetric filling +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 Wieser Hans-Peter +% +% This file is part of the matRad project. It is subject 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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +assert(isa(numBixels,'double')); +assert(isa(numRays,'double')); +assert(isa(vEnergyIx,'double')); +assert(isa(vBeamIx,'double')); +assert(isa(vCandidates,'double')); +assert(isa(Z,'double')); +assert(isa(mMean,'double')); +assert(isa(mWeight,'double')); +assert(isa(mWidth,'double')); +assert(isa(mDepth,'double')); +assert(isa(LUT,'logical')); +assert(isa(randError,'double')); +assert(isa(sysError,'double')); +assert(isa(mRandCovRadDepth,'double')); +assert(isa(mSysCovRadDepth,'double')); +assert(isa(mBioCov,'double')); +assert(isa(amplificationFactor,'double')); +assert(isa(relUCTbio,'double')); +assert(isa(mCovSpot,'double')); + +upperBound = 25000; +lowerBound = 15; +MaxNumDepth = 550; +MaxNumEnergies = 300; + +coder.varsize('numBixels',[1 1],[0 0]); +coder.varsize('numRays',[1 1],[0 0]); +coder.varsize('vEnergyIx',[1 upperBound],[0 1]); +coder.varsize('vBeamIx',[1 upperBound],[0 1]); +coder.varsize('vCandidates',[1 upperBound],[0 1]); +coder.varsize('Z',[1 lowerBound],[0 1]); +coder.varsize('mMean',[lowerBound MaxNumEnergies],[1 1]); +coder.varsize('mWeight',[lowerBound MaxNumEnergies],[1 1]); +coder.varsize('mWidth',[lowerBound MaxNumEnergies],[1 1]); +coder.varsize('mDepth',[MaxNumDepth MaxNumEnergies],[1 1]); +coder.varsize('LUT',[15 MaxNumDepth MaxNumEnergies],[1 1 1]); +coder.varsize('randError',[1 1],[0 0]); +coder.varsize('sysError',[1 1],[0 0]); +coder.varsize('mRandCovRadDepth',[upperBound upperBound],[1 1]); +coder.varsize('mSysCovRadDepth',[upperBound upperBound],[1 1]); +coder.varsize('mBioCov',[upperBound upperBound],[1 1]); +coder.varsize('amplificationFactor',[1 upperBound],[0 1]); +coder.varsize('relUCTbio',[1 1],[0 0]); +coder.varsize('mCovSpot',[lowerBound upperBound],[1 1]); + +numComp = size(mWeight,1); + +estimatedElements = max([nnz(mRandCovRadDepth) nnz(mSysCovRadDepth) nnz(mBioCov)]); +PSI_y_corr_ijm = zeros(estimatedElements,1); +PSI_y_uncorr_ijm = zeros(estimatedElements,1); +LinIx = zeros(estimatedElements,1); +LinIxInv = zeros(estimatedElements,1); + +ixSave = 1; + +for jj = 1:numel(vCandidates) + + + + j = vCandidates(jj); + + bixelIxDepthBioPhys = find((mBioCov(jj,jj:end))) + jj - 1; + + bixelIxDepthPhys = find((mRandCovRadDepth(jj,jj:end)> 0 | ... + mSysCovRadDepth(jj,jj:end) > 0 )) + jj - 1; + + bixelIxDepthBio = bixelIxDepthBioPhys(~ismember(bixelIxDepthBioPhys,bixelIxDepthPhys)); + + bixelIxDepth = unique([bixelIxDepthPhys bixelIxDepthBioPhys]); + PSI_y_corr_ijmTmp = zeros(numel(bixelIxDepth),1); + PSI_y_uncorr_ijmTmp = zeros(numel(bixelIxDepth),1); + + + lengthBx = numel(bixelIxDepthBio); + vBeamIx_j = vBeamIx(j); + vEnergyIx_j = vEnergyIx(j); + ampliFac = amplificationFactor(bixelIxDepthBio); + + ixLut_j = find(mDepth(:,vEnergyIx_j) > Z(1,vBeamIx_j),1,'first'); % find depth index + vIxLUT_j = logical(squeeze(LUT(:,ixLut_j,vEnergyIx_j))); + + Dev_j = Z(1,vBeamIx_j) - mMean(vIxLUT_j,vEnergyIx_j); + w_j = mWeight(vIxLUT_j,vEnergyIx_j)'; + + vBeamIx_m = vBeamIx(vCandidates(bixelIxDepthBio)); + vEnergyIx_m = vEnergyIx(vCandidates(bixelIxDepthBio)); + mIxLUT_m = false(numComp,lengthBx); + + [~,ixLut_m] = min(abs(bsxfun(@minus,mDepth(:,vEnergyIx_m),Z(1,vBeamIx_m))),[],1); + + cntLUT = 1; + for p = 1:lengthBx + mIxLUT_m(:,cntLUT) = LUT(:,ixLut_m(cntLUT),vEnergyIx_m(cntLUT)); + cntLUT = cntLUT + 1; + end + + SigmaJsq = mWidth(vIxLUT_j,vEnergyIx_j) + mRandCovRadDepth(jj,jj) + mSysCovRadDepth(jj,jj); + + Z_j = exp(-((Dev_j).^2./(2.*SigmaJsq)))./(sqrt(2*pi.*SigmaJsq)); + + % calculate second raw moments only for biological uncertainties + for mm = 1:lengthBx + + energyIx_m = vEnergyIx_m(mm); + vIxLUT_m = mIxLUT_m(:,mm); + + Dev_m = Z(1,vBeamIx_m(mm)) - mMean(vIxLUT_m,energyIx_m); + w_m = mWeight(vIxLUT_m,energyIx_m); + SigmaMsq = mWidth(vIxLUT_m,energyIx_m) + mRandCovRadDepth(mm,mm) + mSysCovRadDepth(mm,mm); + + Z_m = exp(-((Dev_m).^2./(2.*SigmaMsq)))./(sqrt(2*pi.*SigmaMsq)); + Z_jm = (Z_j * Z_m'); + mW_CovAlphaDose_jm = mCovSpot(vIxLUT_j,vIxLUT_m) .* ((w_j' * relUCTbio) * (w_m' * relUCTbio)); + mBioOffset = mW_CovAlphaDose_jm + (w_j' * w_m'); + vHelpJ = ones(numel(w_j),1)'; + vHelpM = ones(numel(w_m),1); + + logIx = (bixelIxDepthBio(mm)==bixelIxDepth); + PSI_y_corr_ijmTmp(logIx) = amplificationFactor(jj) * (vHelpJ * (Z_jm .* mBioOffset) * vHelpM); + PSI_y_uncorr_ijmTmp(logIx) = PSI_y_corr_ijmTmp(logIx); + + end + + % calculate second raw moments for biological and physical uncertainties + lengthBx = numel(bixelIxDepthPhys); + vBeamIx_j = vBeamIx(j); + vEnergyIx_j = vEnergyIx(j); + ampliFac = amplificationFactor(bixelIxDepthPhys); + + ixLut_j = find(mDepth(:,vEnergyIx_j) > Z(1,vBeamIx_j),1,'first'); % find depth index + vIxLUT_j = logical(squeeze(LUT(:,ixLut_j,vEnergyIx_j))); + + Dev_j = Z(1,vBeamIx_j) - mMean(vIxLUT_j,vEnergyIx_j); + w_j = mWeight(vIxLUT_j,vEnergyIx_j)'; + LinInd_MM = (bixelIxDepthPhys + (bixelIxDepthPhys-1)*numel(vCandidates))'; + + vBeamIx_m = vBeamIx(vCandidates(bixelIxDepthPhys)); + vEnergyIx_m = vEnergyIx(vCandidates(bixelIxDepthPhys)); + mIxLUT_m = false(numComp,lengthBx); + + [~,ixLut_m] = min(abs(bsxfun(@minus,mDepth(:,vEnergyIx_m),Z(1,vBeamIx_m))),[],1); + + cntLUT = 1; + for p = 1:lengthBx + mIxLUT_m(:,cntLUT) = LUT(:,ixLut_m(cntLUT),vEnergyIx_m(cntLUT)); + cntLUT = cntLUT + 1; + end + + if randError > 0 + randCovRadDepth = mRandCovRadDepth(jj,jj); + vRandCovRadDepth = mRandCovRadDepth(jj,bixelIxDepthPhys)'; + vRandCovRadDepth22 = mRandCovRadDepth(LinInd_MM); + else + randCovRadDepth = 0; vRandCovRadDepth = 0; vRandCovRadDepth22 = 0; + end + + if sysError > 0 + sysCovRadDepth = mSysCovRadDepth(jj,jj); + vSysCovRadDepth = mSysCovRadDepth(jj,bixelIxDepthPhys)'; + vSysCovRadDepth22 = mSysCovRadDepth(LinInd_MM); + else + sysCovRadDepth = 0; vSysCovRadDepth = 0; vSysCovRadDepth22 = 0; + end + + + for mm = 1:lengthBx + + energyIx_m = vEnergyIx_m(mm); + SqSigmaRandUnCorr_jm = [0; 0; 0; 0]; + vIxLUT_m = mIxLUT_m(:,mm); + + if randError > 0 + SqSigmaRandCorr_jm = [randCovRadDepth; vRandCovRadDepth(mm); vRandCovRadDepth(mm); vRandCovRadDepth22(mm)]; + SqSigmaRandUnCorr_jm = [randCovRadDepth; 0; 0; vRandCovRadDepth22(mm)]; + else + SqSigmaRandCorr_jm = [0; 0; 0; 0]; + end + + if sysError > 0 + SqSigmaSys_jm = [sysCovRadDepth; vSysCovRadDepth(mm); vSysCovRadDepth(mm); vSysCovRadDepth22(mm)]; + else + SqSigmaSys_jm = [0; 0; 0; 0]; + end + + Dev_m = Z(1,vBeamIx_m(mm)) - mMean(vIxLUT_m,energyIx_m); + w_m = mWeight(vIxLUT_m,energyIx_m); + SigmaCorr = SqSigmaSys_jm + SqSigmaRandCorr_jm; + SigmaUnCorr = SqSigmaSys_jm + SqSigmaRandUnCorr_jm; + + vLaSi11 = mWidth(vIxLUT_j,vEnergyIx_j) + SigmaCorr(1,1); + vLaSi22 = mWidth(vIxLUT_m,energyIx_m) + SigmaCorr(4,1); + vLaSi12 = SigmaCorr(2,1); + vLaSi21 = SigmaCorr(3,1); + + mW_CovAlphaDose_jm = mCovSpot(vIxLUT_j,vIxLUT_m) .* ((w_j' * relUCTbio) * (w_m' * relUCTbio)); + mBioOffset = mW_CovAlphaDose_jm + (w_j' * w_m'); + vHelpJ = ones(numel(w_j),1)'; + vHelpM = ones(numel(w_m),1); + + Det = vLaSi11*vLaSi22' - (vLaSi12*vLaSi21'); + FracDet = 1./(2*pi*sqrt(Det)); + ExpTerm = FracDet .* exp(-.5./Det.*((Dev_j.^2)*vLaSi22' - bsxfun(@times,(Dev_j*Dev_m'),(vLaSi12+vLaSi21)) + vLaSi11*(Dev_m.^2)')); + PSI_y_corr_ijmTmp(mm) = amplificationFactor(jj) * (vHelpJ * (ExpTerm .* mBioOffset) * vHelpM); + + vLaSi12 = SigmaUnCorr(2,1); + vLaSi21 = SigmaUnCorr(3,1); + + Det = vLaSi11*vLaSi22' - (vLaSi12*vLaSi21'); + FracDet = 1./(2*pi*sqrt(Det)); + ExpTerm = FracDet .* exp(-.5./Det.*((Dev_j.^2)*vLaSi22' - bsxfun(@times,(Dev_j*Dev_m'),(vLaSi12+vLaSi21)) + vLaSi11*(Dev_m.^2)')); + PSI_y_uncorr_ijmTmp(mm) = amplificationFactor(jj) * (vHelpJ * (ExpTerm .* mBioOffset) * vHelpM); + + end + + lengthBx = numel(bixelIxDepth); + mIdx = vCandidates(bixelIxDepth)'; + jIdx = j.*ones(length(mIdx),1); + + PSI_y_corr_ijm(ixSave:ixSave+lengthBx -1) = PSI_y_corr_ijmTmp; + PSI_y_uncorr_ijm(ixSave:ixSave+lengthBx -1) = PSI_y_uncorr_ijmTmp; + LinIx(ixSave:ixSave+lengthBx -1) = jIdx + (mIdx-1)*numBixels; + LinIxInv(ixSave:ixSave+lengthBx -1) = mIdx + (jIdx-1)*numBixels; + + ixSave = ixSave + lengthBx; + +end + +PSI_y_corr_ijm = PSI_y_corr_ijm(1:ixSave -1); +PSI_y_uncorr_ijm = PSI_y_uncorr_ijm(1:ixSave -1); +LinIx = LinIx(1:ixSave -1); +LinIxInv = LinIxInv(1:ixSave -1); + + diff --git a/APM/MEX/matRad_calcSecRangeMomFast_mex.mexmaci64 b/APM/MEX/matRad_calcSecRangeMomFast_mex.mexmaci64 new file mode 100644 index 000000000..05fdd76f1 Binary files /dev/null and b/APM/MEX/matRad_calcSecRangeMomFast_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcSecRangeMomFast_mex.mexw64 b/APM/MEX/matRad_calcSecRangeMomFast_mex.mexw64 new file mode 100644 index 000000000..066af78c9 Binary files /dev/null and b/APM/MEX/matRad_calcSecRangeMomFast_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcSecRangeMom_mex.mexmaci64 b/APM/MEX/matRad_calcSecRangeMom_mex.mexmaci64 new file mode 100644 index 000000000..20e7f4b77 Binary files /dev/null and b/APM/MEX/matRad_calcSecRangeMom_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcSecRangeMom_mex.mexw64 b/APM/MEX/matRad_calcSecRangeMom_mex.mexw64 new file mode 100644 index 000000000..7036b4be8 Binary files /dev/null and b/APM/MEX/matRad_calcSecRangeMom_mex.mexw64 differ diff --git a/APM/MEX/matRad_calcThirdRangeMom.m b/APM/MEX/matRad_calcThirdRangeMom.m new file mode 100644 index 000000000..cc8497f9f --- /dev/null +++ b/APM/MEX/matRad_calcThirdRangeMom.m @@ -0,0 +1,145 @@ +function [thirdMom] = matRad_calcThirdRangeMom(numOfSpots,numComp,w,mMeanA,mMeanB,mMeanC,... + mWidthA,mWidthB,mWidthC,... + mWeightA,mWeightB,mWeightC,... + voxelPos,mCovariance) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the third raw moment. +% +% call +% [thirdMom] = matRad_calcThirdRangeMom(numOfSpots,numComp,w,mMeanA,mMeanB,mMeanC,... +% mWidthA,mWidthB,mWidthC,... +% mWeightA,mWeightB,mWeightC,... +% voxelPos,mCovariance) +% +% input +% numOfSpots total number of spots +% numComp total number of Gaussian components e.g. 10 or 13 +% mMeanA-mMeanC: four mean vectors of spot components j o q n +% mWidthA-mWidthC: four width vectors of spot components j o q n +% mWeightA-mWeightC: four weight vectors of spot components j o q n +% voxelPos: voxel position +% mSysCovRadDept: full covariance matrix of range error +% +% output +% third: third central moment +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 Hans-Peter Wieser +% +% This file is part of the matRad project. It is subject 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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +assert(isa(numOfSpots,'double')); +assert(isa(numComp,'double')); +assert(isa(w,'double')); +assert(isa(mMeanA,'double')); +assert(isa(mMeanB,'double')); +assert(isa(mMeanC,'double')); +assert(isa(mWidthA,'double')); +assert(isa(mWidthB,'double')); +assert(isa(mWidthC,'double')); +assert(isa(mWeightA,'double')); +assert(isa(mWeightB,'double')); +assert(isa(mWeightC,'double')); +assert(isa(voxelPos,'double')); +assert(isa(mCovariance,'double')); + +coder.varsize('numOfSpots', [1 1],[0 0]); +coder.varsize('numComp', [1 1],[0 0]); +coder.varsize('w', [60 1],[1 0]); +coder.varsize('mMeanA', [15 60],[1 1]); +coder.varsize('mMeanB', [15 60],[1 1]); +coder.varsize('mMeanC', [15 60],[1 1]); +coder.varsize('mWidthA', [15 60],[1 1]); +coder.varsize('mWidthB', [15 60],[1 1]); +coder.varsize('mWidthC', [15 60],[1 1]); +coder.varsize('mWeightA', [15 60],[1 1]); +coder.varsize('mWeightB', [15 60],[1 1]); +coder.varsize('mWeightC', [15 60],[1 1]); +coder.varsize('voxelPos', [1 1],[0 0]); +coder.varsize('mCovariance', [60 60],[1 1]); + + +mPSI_jop = zeros(numOfSpots,numOfSpots,numOfSpots); + +for j = 1:numOfSpots + for o = 1:numOfSpots + for q = 1:numOfSpots + + + A11_vec = mWidthA(:,j) + mCovariance(j,j); % | vA11 sA12 sA13 | vA11 sA12 sA13 + A22_vec = mWidthB(:,o) + mCovariance(o,o); % | sA21 vA22 sA23 | sA21 vA22 sA23 + A33_vec = mWidthC(:,q) + mCovariance(q,q); % | sA31 sA32 vA33 | sA31 sA32 vA33 + A12 = mCovariance(j,o); + A13 = mCovariance(j,q); + A23 = mCovariance(o,q); + + mDet = reshape(reshape(A11_vec*A22_vec',[],1) * A33_vec',[numComp numComp numComp]) + A12*A23*A13 + A12*A23*A13; + a = A13*(ones(numComp,1)); aa = a * A22_vec'; cube1 = reshape(reshape(aa,[],1) * a',[numComp numComp numComp]); + b = A23*(ones(numComp,1)); bb = A11_vec * b'; cube2 = reshape(reshape(bb,[],1) * b',[numComp numComp numComp]); + c = A12*(ones(numComp,1)); cc = c * c'; cube3 = reshape(reshape(cc,[],1) * A33_vec',[numComp numComp numComp]); + mDet = mDet -cube1 - cube2 - cube3; + + + sigma = [mCovariance(j,j) mCovariance(j,o) mCovariance(j,q);... + mCovariance(o,j) mCovariance(o,o) mCovariance(o,q);... + mCovariance(q,j) mCovariance(q,o) mCovariance(q,q) ]; + + dev_j = voxelPos - mMeanA(:,j); + dev_o = voxelPos - mMeanB(:,o); + dev_q = voxelPos - mMeanC(:,q); + + + tmp = reshape(mWeightA(:,j) * mWeightB(:,o)',[numComp^2 1]); + vW = reshape(tmp * mWeightC(:,q)',[numComp numComp numComp]); + + %vWref = reshape(reshape(mWeightA(:,j) * mWeightB(:,o)',[numComp^2 1]) * mWeightB(:,q)',[numComp numComp numComp]); + + Y = 0; %Yref = 0; + for J = 1:numComp + for O = 1:numComp + for Q = 1:numComp + + lambda = diag([mWidthA(J,j) mWidthB(O,o) mWidthC(Q,q)]); + + LaSi = (lambda + sigma); + InvLaSi = inv(LaSi); + + Y = Y + (vW(J,O,Q)/((2*pi)^(3/2)*sqrt(mDet(J,O,Q)))) * ... + exp(-0.5*([dev_j(J) dev_o(O) dev_q(Q)]) * (InvLaSi) * ([dev_j(J) dev_o(O) dev_q(Q)])'); + + + %Yref = Yref + (vWref(J,O,Q) * mvnpdf([dev_j(J) dev_o(O) dev_q(Q)],0,LaSi)); + +% mDetRef = [mWidthA(J,j) + mCovariance(j,j) A12 A13; +% A12 mWidthB(O,o) + mCovariance(o,o) A23; +% A13 A23 mWidthB(Q,q) + mCovariance(q,q)]; +% +% if ((det(mDetRef)/mDet(J,O,Q)) -1) >0.01 +% warning('det are not equal'); +% end +% +% if ~isequal(vW(J,O,Q),mWeightA(J,j) * mWeightB(O,o) * mWeightB(Q,q)) +% warning('weights are not equal'); +% end + end + end + end + + mPSI_jop(j,o,q) = Y; + + end + end +end + +thirdMom = w' * reshape(reshape(mPSI_jop,[numOfSpots^2 numOfSpots]) * w, [numOfSpots numOfSpots]) * w; + +end diff --git a/APM/MEX/matRad_calcThirdRangeMom_mex.mexmaci64 b/APM/MEX/matRad_calcThirdRangeMom_mex.mexmaci64 new file mode 100644 index 000000000..6a0ad9091 Binary files /dev/null and b/APM/MEX/matRad_calcThirdRangeMom_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_calcThirdRangeMom_mex.mexw64 b/APM/MEX/matRad_calcThirdRangeMom_mex.mexw64 new file mode 100644 index 000000000..3e0dadf74 Binary files /dev/null and b/APM/MEX/matRad_calcThirdRangeMom_mex.mexw64 differ diff --git a/APM/MEX/matRad_compileAPMmexFiles.m b/APM/MEX/matRad_compileAPMmexFiles.m new file mode 100644 index 000000000..b86d41089 --- /dev/null +++ b/APM/MEX/matRad_compileAPMmexFiles.m @@ -0,0 +1,48 @@ +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to compile mex files. All mex files will be stored in +% matRadRootDir/APM/MEX +% +% call +% matRad_compileAPMmexFiles(); +% +% input +% +% output +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 Hans-Peter Wieser +% +% This file is part of the matRad project. It is subject 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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clc,clear,close + +cfg = coder.config('mex'); +% if isunix +% cfg.PostCodeGenCommand = 'buildInfo.addLinkFlags(''-fopenmp'')'; +% end + +codegen -config cfg matRad_sumGauss + +codegen -config cfg matRad_calcGeoDistsFast -report + +codegen -config cfg matRad_calcSecLatMom -report +codegen -config cfg matRad_calcSecRangeMom -report + +codegen -config cfg matRad_calcSecLatMomFast -report +codegen -config cfg matRad_calcSecRangeMomFast -report + +codegen -config cfg matRad_calcThirdRangeMom -report +codegen -config cfg matRad_calcFourthRangeMom -report + + + + + diff --git a/APM/MEX/matRad_sumGauss.m b/APM/MEX/matRad_sumGauss.m new file mode 100644 index 000000000..2ff6efdd9 --- /dev/null +++ b/APM/MEX/matRad_sumGauss.m @@ -0,0 +1,47 @@ +function vY = matRad_sumGauss(vX,vMu,vSqSigma,vW) %#codegen +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the sum of Gaussians +% +% call +% vY = matRad_sumGauss(vX,vMu,vSqSigma) +% +% input +% vX: query points at which the sum of gaussians should be +% evaluated +% vMu: mean vector +% vSqSigma: squared sigma vector +% vW weights +% +% output +% vY: sum of gaussians at vX +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +assert(isa(vX,'double')); +assert(isa(vMu,'double')); +assert(isa(vSqSigma,'double')); +assert(isa(vW,'double')); + +NumComp = 50; +coder.varsize('vX',[Inf 1],[1 0]); +coder.varsize('vMu',[NumComp 1],[1 0]); +coder.varsize('vSqSigma',[NumComp 1],[1 0]); +coder.varsize('vW',[NumComp 1],[1 0]); + +vY = ( exp(-bsxfun(@minus,vX,vMu').^2 ./ (2* ones(numel(vX),1) * vSqSigma' ))./(sqrt(2*pi*ones(numel(vX),1) * vSqSigma'))) * vW ; + +end + diff --git a/APM/MEX/matRad_sumGauss_mex.mexmaci64 b/APM/MEX/matRad_sumGauss_mex.mexmaci64 new file mode 100644 index 000000000..1e61ac170 Binary files /dev/null and b/APM/MEX/matRad_sumGauss_mex.mexmaci64 differ diff --git a/APM/MEX/matRad_sumGauss_mex.mexw64 b/APM/MEX/matRad_sumGauss_mex.mexw64 new file mode 100644 index 000000000..8a8b7ea1e Binary files /dev/null and b/APM/MEX/matRad_sumGauss_mex.mexw64 differ diff --git a/APM/matRad_calcParticleDoseBixelAPM.m b/APM/matRad_calcParticleDoseBixelAPM.m new file mode 100644 index 000000000..1ebfa157c --- /dev/null +++ b/APM/matRad_calcParticleDoseBixelAPM.m @@ -0,0 +1,132 @@ +function [bixelDose] = matRad_calcParticleDoseBixelAPM(radDepths, latDistsX, latDistsZ, sigmaIni_sq, baseData,FlagBioOpt,vTissueIndex, sError) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to calculate the three dij components (two lateral, one depth +% component) which are needed for robust treatment planning within the APM +% framework +% +% call +% dose = matRad_calcParticleDoseBixelAPM(radDepths,latDistsX, latDistsY, baseData, gaussBaseData, uct, BixelNum) +% +% input +% radDepths: radiological depths +% latDistsX: lateral x distance in BEV from central ray +% latDistsZ: lateral z distance in BEV from central ray +% baseData: base data required for particle dose calculation (contains the weights, widhts and means of) +% BixelNum: indicates the current bixel number +% +% output +% Lx_ij: particle dose at specified locations as linear vector +% LY_ij: particle dose at specified locations as linear vector +% Z_ij: particle dose at specified locations as linear vector +% +% References +% [1] http://www.ncbi.nlm.nih.gov/pubmed/23877218 +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% function handle for calculating lateral dose +Gauss = @(x,mu,SqSigma) 1./(sqrt(2*pi.*SqSigma)).*exp(-((x - mu).^2./(2.*SqSigma))); + +% function handle for calculating depth doses +sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ... + exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w); + +% add potential offset +depths = baseData.depths + baseData.offset; + +% convert from MeV cm^2/g per primary to Gy mm^2 per 1e6 primaries +conversionFactor = 1.6021766208e-02; + +if ~isfield(baseData,'sigma') + + % interpolate depth dose, sigmas, and weights + X = matRad_interp1(depths,[baseData.sigma1 baseData.weight baseData.sigma2],radDepths); + + sigmaSq_Narr = (X(:,1)).^2 + sigmaIni_sq; + sigmaSq_Bro = (X(:,3)).^2 + sigmaIni_sq; + w = (X(:,2)); + +else + % interpolate depth dose and sigma + X = matRad_interp1(depths,baseData.sigma,radDepths); + + %compute lateral sigma + sigmaSq = X.^2 + sigmaIni_sq; +end + + +%% calculate lateral sigma in x direction %%% +latMeanOffsetX = 0; +latMeanOffsetZ = 0; +shiftErrorLat = sError.latRndSq + sError.latSysSq; + +if ~isfield(baseData,'sigma') + + SqSigmaX_Narr = sigmaSq_Narr + shiftErrorLat; + SqSigmaZ_Narr = sigmaSq_Narr + shiftErrorLat; + SqSigmaX_Bro = sigmaSq_Bro + shiftErrorLat; + SqSigmaZ_Bro = sigmaSq_Bro + shiftErrorLat; + + % only save the lateral dose of the narrow component + NarrX = sqrt(1-w) .* Gauss(latDistsX,0,SqSigmaX_Narr); + NarrZ = sqrt(1-w) .* Gauss(latDistsZ,0,SqSigmaZ_Narr); + BroX = sqrt(w) .* Gauss(latDistsX,0,SqSigmaX_Bro); + BroZ = sqrt(w) .* Gauss(latDistsZ,0,SqSigmaZ_Bro); + + bixelDose.L_ij = NarrX .* NarrZ + BroX .* BroZ; + + radDist = sqrt(latDistsX.^2 + latDistsZ.^2); + bixelDose.Lx_ij = sqrt(((1-w) .* exp((-(radDist.^2))./(2*SqSigmaX_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... + (w .* exp((-(radDist.^2))./(2*SqSigmaX_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + bixelDose.Lz_ij = sqrt(((1-w) .* exp((-(radDist.^2))./(2*SqSigmaZ_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... + (w .* exp((-(radDist.^2))./(2*SqSigmaZ_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + +else + + SqSigmaX = sigmaSq + shiftErrorLat; + SqSigmaZ = sigmaSq + shiftErrorLat; + + bixelDose.Lx_ij = Gauss(latDistsX,latMeanOffsetX,SqSigmaX); + bixelDose.Lz_ij = Gauss(latDistsZ,latMeanOffsetZ,SqSigmaZ); + bixelDose.L_ij = bixelDose.Lx_ij .* bixelDose.Lz_ij; + +end + +%% calculate sigma in range direction +SqSigmaRangeOffset = sError.rangeRndSq + sError.rangeSysSq; + + +bixelDose.Z_ij = conversionFactor * baseData.LatCutOff.CompFac * sumGauss(radDepths - baseData.offset,baseData.Z.mean,... + (baseData.Z.width).^2 + SqSigmaRangeOffset,baseData.Z.weight); + +if FlagBioOpt + + tissueClasses = unique(vTissueIndex); + bixelDose.Z_Aij = zeros(numel(radDepths),1); + bixelDose.Z_Bij = zeros(numel(radDepths),1); + + for i = 1:numel(tissueClasses) + ix = vTissueIndex == tissueClasses(i); + bixelDose.Z_Aij(ix) = conversionFactor * baseData.LatCutOff.CompFac * ... + sumGauss(radDepths(ix)-baseData.offset,baseData.alphaDose(tissueClasses(i)).mean,... + (baseData.alphaDose(tissueClasses(i)).width).^2 + SqSigmaRangeOffset,baseData.alphaDose(tissueClasses(i)).weight); + + bixelDose.Z_Bij(ix) = conversionFactor * baseData.LatCutOff.CompFac * ... + sumGauss(radDepths(ix)-baseData.offset,baseData.SqrtBetaDose(tissueClasses(i)).mean,... + (baseData.SqrtBetaDose(tissueClasses(i)).width).^2 + SqSigmaRangeOffset,baseData.SqrtBetaDose(tissueClasses(i)).weight)'; + end + +end + + + +if ~isfield(baseData,'sigma') + bixelDose.physDose = bixelDose.L_ij .* bixelDose.Z_ij; +else + bixelDose.physDose = bixelDose.Lx_ij .* bixelDose.Lz_ij .* bixelDose.Z_ij; +end + + +end + + diff --git a/APM/matRad_calcParticleVarParallel.m b/APM/matRad_calcParticleVarParallel.m new file mode 100644 index 000000000..478aa94d8 --- /dev/null +++ b/APM/matRad_calcParticleVarParallel.m @@ -0,0 +1,437 @@ +function [cst,resultGUI] = matRad_calcParticleVarParallel(ct,cst,stf,pln,dij,resultGUI,ix,param) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad's seriel variance calculation function to compute based on APM +% the second central moment +% +% call +% [cst,resultGUI] = matRad_calcParticleVarSeriel(cst,stf,pln,dij,resultGUI,ix) +% +% input +% cst: matRad critical structure struct cst +% stf: matRad steering information struct +% pln: matRad plan meta information struct +% dij: matRad dij dose influence struct +% resultGUI: matRads resultGUI struct +% ix voxel indices for which variance should be calcualted +% +% output +% cst: matRad critical structure struct cst +% resultGUI: matRads resultGUI struct +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +addpath([fileparts(mfilename('fullpath')) filesep 'MEX']); + +SGGauss = @(x,mu,SqSigma) 1./(sqrt(2*pi.*SqSigma)).*exp(-((x - mu).^2./(2.*SqSigma))); +DBGauss = @(x,mu,SqSigma1,SqSigma2,W) ((1-W).*(1./(sqrt(2*pi.*SqSigma1)).*exp(-((x - mu).^2./(2.*SqSigma1))))) + ... + ((W).*(1./(sqrt(2*pi.*SqSigma2)).*exp(-((x - mu).^2./(2.*SqSigma2))))) ; + +sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ... + exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w); + +USE_MEX_LAT = false; +USE_MEX_DEPTH = false; + +if exist(['matRad_calcSecLatMom_mex.' mexext],'file') == 3 + USE_MEX_LAT = true; +end + +if exist(['matRad_calcSecRangeMom_mex.' mexext],'file') == 3 + USE_MEX_DEPTH = true; +end + +% check which depth dose component should be used for variance calculation +if ~pln.bioParam.bioOpt + depthComp = 'Z'; +else + depthComp = 'alphaDose'; +end + +% calculate rED or rSP from HU +ct = matRad_calcWaterEqD(ct, pln, param); + +% check if robust pencil beam weights are available +robSuffix = 'Rob'; +if param.CALC_OMEGA + robSuffix = ''; +end + +% find target voxel indices +omegaVoxel = []; +if pln.probOpt.omegaTarget + for i=1:size(cst,1) + if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6}) + omegaVoxel = [omegaVoxel;vertcat(cst{i,4}{:})]; + end + end +else + omegaVoxel = ix; +end + +%% load base data set and compute reduced components +load([pln.radiationMode '_' pln.machine '.mat']); +SAD = machine.meta.SAD; +[mMean, mWeight, mWidth, mLatSigma, mLUT, mDepth, vOffset, vCutOff] = matRad_getBaseDataAPM(ct,stf,pln,dij.vTissueIndex,depthComp); + +% define biological uncertainties +if ~pln.bioParam.bioOpt + relUCTbio = 0.0; +else + relUCTbio = pln.multScen.relBioUCT; +end + +mCovSpot = pln.multScen.mCovSpot; + +doubleGauss = false; +if ~isfield(machine.data(1),'sigma') + doubleGauss = true; +end + +clear machine; + +% define some variables +stdSingleFrac = zeros(dij.dimensions); +stdTotFrac = zeros(dij.dimensions); +numBixels = dij.totalNumOfBixels; +numBeams = length([pln.propStf.gantryAngles]); +numRays = dij.totalNumOfRays; +cubeDim = size(resultGUI.(['physicalDose' robSuffix])); +numFrac = pln.numOfFractions; +isoCenter = [stf(1).isoCenter]; +CTres = [ct.resolution.x ct.resolution.y ct.resolution.z]; +w = resultGUI.(['w' robSuffix]); +conversionFactorSq = 1.6021766208e-02^2; +vBeamNum = dij.beamNum; +vSigmaIni_sq = dij.sigmaIni_sq'; +vEnergyIx = dij.energyIx'; +includeLateralUTC = (sum(strcmp(pln.probOpt.InputUCT,{'phys','biophys'})) > 0 || (strcmp(robSuffix,'Rob') && strcmp(pln.probOpt.InputUCT,{'bio'}))); + +% set overlap priorities +[cst,voiIndexCube] = matRad_setOverlapPriorities(cst,dij.dimensions); + +% loop over all affected VOIs +% check how many and which structures are affected by std calculation +numOmega = 0; +lutVOI = 0; +for i = 1:size(cst,1) + for j = 1:size(cst{i,6},1) + if ~isempty(cst{i,6}(j)) + numOmega = numOmega + 1; + lutVOI(numOmega) = i; %#ok + end + end +end + +totInfo = whos; +fprintf([' current memory consumption ' num2str((sum([totInfo.bytes]))/1e9) ' GB \n']) +dijInfo = whos('dij'); +plnInfo = whos('pln'); +fprintf(['dij: ' num2str(dijInfo.bytes/1e9) ' GB; pln: ' num2str(plnInfo.bytes/1e9) ' GB \n']);fprintf(''); + + +for k = 1:numOmega + + mOmega = sparse(numBixels,numBixels); + [boolMember,~] = ismember(ix,cst{lutVOI(k),4}{1}); + ixSub = ix(boolMember); + [~,ixMap] = ismember(ixSub,ix); + + % initialize depth dose vector + if ~pln.bioParam.bioOpt + dZ = dij.dZ{1}(ixSub,:); % depth doseh component including the the conversion factor + doseExp = resultGUI.([pln.bioParam.quantityVis 'Exp' robSuffix])(ixSub); + else + dZ = dij.dZa{1}(ixSub,:); % alpha dose depth component + dZb = dij.dZb{1}(ixSub,:); % sqrt beta dose depth component + doseExp = resultGUI.(['effectExp' robSuffix])(ixSub); + end + + SpotLUT = dij.SpotLUT{1}(ixSub,:); + radDepth = dij.radDepth{1}(ixSub,:); + stdSingleFracTmp = zeros(numel(ixMap),1); + stdTotFracTmp = zeros(numel(ixMap),1); + + RBEsq = 1; + if strcmp(pln.bioParam.model,'constRBE') + dZ = dZ * pln.bioParam.RBE; + RBEsq = pln.bioParam.RBE^2; + end + + %% display complexity + fprintf(['in total are ' num2str(length(ixSub)) ' voxels affected \n']);fprintf(''); + + % loop over all voxels and calculate the secand central moment + %splitapply https://de.mathworks.com/matlabcentral/answers/368003-inevitable-broadcast-variable-in-parfor + + % running parfor with an additional parameter NumWorker to switch + % between a serieal and parallel computation does not work. Moreover, + % parfor with 0 works takes in simplied example twice as long as a + % simple for loop + for i = 1:numel(ixSub) + + cntVoxel = i; + + tmpCandidates = round(SpotLUT(cntVoxel,:)); % for the prostate case there was one single non-integer value + vCandidates = full(tmpCandidates(tmpCandidates~=0)); % maybe directly store these values + numCandidates = length(vCandidates); + numCorr = numCandidates^2; + + % in case of no correlation jump to next voxel + if isempty(vCandidates) + cntVoxel = cntVoxel + 1; + continue + end + + % compute lateral dose in x and z + [xDist,zDist] = matRad_getLateralDistVoxelSpots(ixSub(i),cubeDim,CTres,isoCenter,stf,pln,vCandidates,SAD); + + radDepthVoxel = single(full(radDepth(cntVoxel,vBeamNum(vCandidates)))); + sqSigmaInI = vSigmaIni_sq(vCandidates); + subEnergyIx = vEnergyIx(vCandidates); + + if doubleGauss + latSqSigma = zeros(numCandidates,3); + else + latSqSigma = zeros(numCandidates,1); + end + + % calc sigmas + for jj = 1:numCandidates + if doubleGauss + latSqSigma(jj,:) = matRad_interp1(mDepth(:,subEnergyIx(jj)), mLatSigma(:,subEnergyIx(jj),:),radDepthVoxel(jj)); + + latSqSigma(jj,1) = latSqSigma(jj,1).^2 + sqSigmaInI(jj); + latSqSigma(jj,3) = latSqSigma(jj,3).^2 + sqSigmaInI(jj); + vW = latSqSigma(:,2); + else + latSqSigma(jj) = matRad_interp1(mDepth(:,subEnergyIx(jj)), mLatSigma(:,subEnergyIx(jj)),radDepthVoxel(jj)).^2 + sqSigmaInI(jj); + end + end + + % get lateral errors to calcualte the expected lateral dose + shiftErrorLatExp = pln.multScen.shiftSDrnd.^2 + pln.multScen.shiftSDsys.^2; + + % calc lateral dose + if doubleGauss + + if includeLateralUTC + SqSigmaX_Narr = latSqSigma(:,1) + shiftErrorLatExp; + SqSigmaZ_Narr = latSqSigma(:,1) + shiftErrorLatExp; + SqSigmaX_Bro = latSqSigma(:,3) + shiftErrorLatExp; + SqSigmaZ_Bro = latSqSigma(:,3) + shiftErrorLatExp; + else + SqSigmaX_Narr = latSqSigma(:,1); + SqSigmaZ_Narr = latSqSigma(:,1); + SqSigmaX_Bro = latSqSigma(:,3); + SqSigmaZ_Bro = latSqSigma(:,3); + end + + latSqSigmaError = SqSigmaX_Narr; + +% rSq = sqrt(xDist.^2 + zDist.^2); +% tmp_dLx_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaX_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... +% (vW .* exp((-(rSq.^2))./(2*SqSigmaX_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); +% tmp_dLz_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaZ_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... +% (vW .* exp((-(rSq.^2))./(2*SqSigmaZ_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + + dLx = SGGauss(xDist,0,SqSigmaX_Narr); + dLz = SGGauss(zDist,0,SqSigmaZ_Narr); + + else + if includeLateralUTC + latSqSigmaError = latSqSigma + shiftErrorLatExp; + else + latSqSigmaError = latSqSigma; + end + + dLx = SGGauss(xDist,0,latSqSigmaError); + dLz = SGGauss(zDist,0,latSqSigmaError); + end + + vdLxCorr = reshape(dLx*dLx',[numCorr 1]); % create squared lateral dose in x vector + vdLxUnCorr = vdLxCorr; + vdLzCorr = reshape(dLz*dLz',[numCorr 1]) ; % create squared lateral dose in z vector + vdLzUnCorr = vdLzCorr; + + PSI_Z_Corr = (dZ(cntVoxel,:)' * dZ(cntVoxel,:)); + PSI_Z_Uncorr = PSI_Z_Corr; + + %% start lateral correlation computation + + % get indices + vI = repmat(vCandidates',[numCandidates 1]); + vJ = zeros(numCorr,1); loopIx = 1; + LinIx = zeros(numCandidates); + + for h = 1:numCandidates + vJ(loopIx:loopIx+numCandidates-1) = vCandidates(h); + loopIx = loopIx + numCandidates; + LinIx(:,h) = vCandidates' .* ones(numCandidates,1) + (vCandidates(h)'-1)*numBixels ; + end + + linIxDiag = 1:numBixels+1:numBixels^2; + + if (pln.multScen.shiftSDrnd > 0 || pln.multScen.shiftSDsys > 0) && includeLateralUTC + if USE_MEX_LAT + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast_mex(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),... + pln.multScen.shiftSDrnd,pln.multScen.shiftSDsys); + else + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),... + pln.multScen.shiftSDrnd,pln.multScen.shiftSDsys); + end + + else + if USE_MEX_LAT + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast_mex(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),0,0); + else + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),0,0); + end + + end + + % create lateral correlation matrices for voxel i + vdLxCorr(LinIdx) = vPSI_XcorrTmp; + PSI_Lx_Corr = sparse(vI,vJ,vdLxCorr,numBixels,numBixels); + vdLxUnCorr(LinIdx) = vPSI_XuncorrTmp; + PSI_Lx_Uncorr = sparse(vI,vJ,vdLxUnCorr,numBixels,numBixels); + + + vdLzCorr(LinIdx) = vPSI_ZcorrTmp; + PSI_Lz_Corr = sparse(vI,vJ,vdLzCorr,numBixels,numBixels); + vdLzUnCorr(LinIdx) = vPSI_ZuncorrTmp; + PSI_Lz_Uncorr = sparse(vI,vJ,vdLzUnCorr,numBixels,numBixels); + + if (pln.multScen.rangeSDrnd > 0 || pln.multScen.rangeSDsys > 0) || relUCTbio > 0 + + numCandidates = numel(vCandidates); + LinIx = zeros(numCandidates); + vCompFac = zeros(numCandidates,1); + + for h = 1:numCandidates + jIdx = vCandidates'; + mIdx = vCandidates(h).*ones(length(jIdx),1); + LinIx(:,h) = jIdx + (mIdx-1)*numBixels; + vCompFac(h) = vCutOff{dij.energyIx(vCandidates(h)),dij.beamNum(vCandidates(h))}.CompFac^2; + end + + randCovRadDepth = full(pln.multScen.mCovRangeRnd(LinIx)); + sysCovRadDepth = full(pln.multScen.mCovRangeSys(LinIx)); + bioCov = full(pln.multScen.mCovBio(LinIx)); + + %% check diagonal double indices + if USE_MEX_DEPTH + [PSI_CorrTmp, PSI_UncorrTmp,LinIx,LinIxInv] = matRad_calcSecRangeMomFast_mex(conversionFactorSq * vCompFac' * RBEsq,... + numBixels,numRays,vEnergyIx',vBeamNum',vCandidates,full(radDepth(cntVoxel,:)),mMean,mWeight,mWidth... + ,mDepth,mLUT,pln.multScen.rangeSDrnd,pln.multScen.rangeSDsys,randCovRadDepth,sysCovRadDepth,bioCov,... + relUCTbio,mCovSpot); + else + [PSI_CorrTmp, PSI_UncorrTmp,LinIx,LinIxInv] = matRad_calcSecRangeMomFast(conversionFactorSq * vCompFac * RBEsq,... + numBixels,numRays,vEnergyIx',vBeamNum',vCandidates,full(radDepth(cntVoxel,:)),mMean,mWeight,mWidth... + ,mDepth,mLUT,pln.multScen.rangeSDrnd,pln.multScen.rangeSDsys,randCovRadDepth,sysCovRadDepth,bioCov,... + relUCTbio,mCovSpot); + end + + PSI_Z_Corr(LinIx) = PSI_CorrTmp; + PSI_Z_Corr(LinIxInv) = PSI_CorrTmp; + PSI_Z_Uncorr(LinIx) = PSI_UncorrTmp; + PSI_Z_Uncorr(LinIxInv) = PSI_UncorrTmp; + + end + + % PSI_Z_Corr either contains physical quantites or the alpha dose component + mPSI_Corr = (PSI_Lx_Corr .* PSI_Lz_Corr .* PSI_Z_Corr); + mPSI_UnCorr = (PSI_Lx_Uncorr .* PSI_Lz_Uncorr .* PSI_Z_Uncorr); + PSI_Corr = w' * (mPSI_Corr) * w; + PSI_UnCorr = w' * (mPSI_UnCorr) * w; + + dLxSparse = sparse(ones(numCandidates,1),vCandidates,dLx,1,numBixels); + dLzSparse = sparse(ones(numCandidates,1),vCandidates,dLz,1,numBixels); + + doseExpij = (dLxSparse .* dLzSparse .* dZ(cntVoxel,:)); + + Var_Corr = (PSI_Corr - (doseExpij*w)^2); + Var_UnCorr = (PSI_UnCorr - (doseExpij*w)^2); + Var_FracTot = (1/numFrac) .* (Var_Corr + (Var_UnCorr * (numFrac-1))); + + if sum(isnan(Var_Corr)) > 0 + matRad_dispToConsole('matRad_calcParticleVarSeriel: NaN values in std',[],'warning'); + end + + stdTotFracTmp(i) = sqrt(Var_FracTot); + if imag(stdTotFracTmp(i)) + stdTotFracTmp(i) = 0; + end + stdSingleFracTmp(i) = sqrt(Var_Corr); + if imag(stdSingleFracTmp(i)) + stdSingleFracTmp(i) = 0; + end + + % fill omega matrix + if param.CALC_OMEGA && ismember(ixSub(i),omegaVoxel) + + doseExpij2 = doseExpij' * doseExpij; + PSI = (1/numFrac) .* (mPSI_Corr + (mPSI_UnCorr * (numFrac-1))); + mOmega = mOmega + (PSI - doseExpij2); + + end + + matRad_progress(cntVoxel,numel(ixSub)); + + end % eof of voxel loop + + stdTotFrac(ix(ixMap)) = stdTotFracTmp; + stdSingleFrac(ix(ixMap)) = stdSingleFracTmp; + + cst{lutVOI(k),6}.mOmega = mOmega; + +end % THIS needs to go to the end of the voxel loop + + +if ~pln.bioParam.bioOpt + + resultGUI.([pln.bioParam.quantityVis 'StdSingleFrac' robSuffix]) = stdSingleFrac; + resultGUI.([pln.bioParam.quantityVis 'StdTotFrac' robSuffix]) = stdTotFrac; + +else + + if ~strcmp(pln.bioParam.quantityOpt,'effect') + matRad_dispToConsole('matRad_calcParticleVarSerial: not supported',[],'error') + end + + resultGUI.([pln.bioParam.quantityOpt 'StdSingleFrac' robSuffix]) = stdSingleFrac; + resultGUI.([pln.bioParam.quantityOpt 'StdTotFrac' robSuffix]) = stdTotFrac; + + [~,vStdRBExDsingleFrac] = matRad_effectToRBExDose(resultGUI.(['effectExp' robSuffix]), stdSingleFrac ,dij.alphaX,dij.betaX); + [vMeanRBExD,vStdRBExDtotFrac] = matRad_effectToRBExDose(resultGUI.(['effectExp' robSuffix]), stdTotFrac ,dij.alphaX,dij.betaX); + + resultGUI.([pln.bioParam.quantityVis 'Exp' robSuffix]) = reshape(vMeanRBExD,dij.dimensions); + resultGUI.([pln.bioParam.quantityVis 'StdSingleFrac' robSuffix]) = reshape(vStdRBExDsingleFrac,dij.dimensions); + resultGUI.([pln.bioParam.quantityVis 'StdTotFrac' robSuffix]) = reshape(vStdRBExDtotFrac,dij.dimensions); + +end + + + +end % eof function + diff --git a/APM/matRad_calcParticleVarParallelParFor.m b/APM/matRad_calcParticleVarParallelParFor.m new file mode 100644 index 000000000..1d687cc02 --- /dev/null +++ b/APM/matRad_calcParticleVarParallelParFor.m @@ -0,0 +1,464 @@ +function [cst,resultGUI] = matRad_calcParticleVarParallelParFor(ct,cst,stf,pln,dij,resultGUI,ix,param) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad's seriel variance calculation function to compute based on APM +% the second central moment +% +% call +% [cst,resultGUI] = matRad_calcParticleVarSeriel(cst,stf,pln,dij,resultGUI,ix) +% +% input +% cst: matRad critical structure struct cst +% stf: matRad steering information struct +% pln: matRad plan meta information struct +% dij: matRad dij dose influence struct +% resultGUI: matRads resultGUI struct +% ix voxel indices for which variance should be calcualted +% +% output +% cst: matRad critical structure struct cst +% resultGUI: matRads resultGUI struct +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +addpath([fileparts(mfilename('fullpath')) filesep 'MEX']); + +SGGauss = @(x,mu,SqSigma) 1./(sqrt(2*pi.*SqSigma)).*exp(-((x - mu).^2./(2.*SqSigma))); +DBGauss = @(x,mu,SqSigma1,SqSigma2,W) ((1-W).*(1./(sqrt(2*pi.*SqSigma1)).*exp(-((x - mu).^2./(2.*SqSigma1))))) + ... + ((W).*(1./(sqrt(2*pi.*SqSigma2)).*exp(-((x - mu).^2./(2.*SqSigma2))))) ; + +sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ... + exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w); + +USE_MEX_LAT = false; +USE_MEX_DEPTH = false; + +if exist(['matRad_calcSecLatMom_mex.' mexext],'file') == 3 + USE_MEX_LAT = true; +end + +if exist(['matRad_calcSecRangeMom_mex.' mexext],'file') == 3 + USE_MEX_DEPTH = true; +end + +% check which depth dose component should be used for variance calculation +if ~pln.bioParam.bioOpt + depthComp = 'Z'; +else + depthComp = 'alphaDose'; +end + +% calculate rED or rSP from HU +ct = matRad_calcWaterEqD(ct, pln, param); + +% check if robust pencil beam weights are available +robSuffix = 'Rob'; +if param.CALC_OMEGA + robSuffix = ''; +end + +% find target voxel indices +omegaVoxel = []; +if pln.probOpt.omegaTarget + for i=1:size(cst,1) + if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6}) + omegaVoxel = [omegaVoxel;vertcat(cst{i,4}{:})]; + end + end +else + omegaVoxel = ix; +end + +%% load base data set and compute reduced components +load([pln.radiationMode '_' pln.machine '.mat']); +SAD = machine.meta.SAD; +numComp = numel(machine.data(1).Z.width); +[mMean, mWeight, mWidth, mLatSigma, mLUT, mDepth, vOffset, vCutOff] = matRad_getBaseDataAPM(ct,stf,pln,dij.vTissueIndex,depthComp); + +% define biological uncertainties +if ~pln.bioParam.bioOpt + relUCTbio = 0.0; +else + relUCTbio = pln.multScen.relBioUCT; +end + +mCovSpot = pln.multScen.mCovSpot; + +doubleGauss = false; +if ~isfield(machine.data(1),'sigma') + doubleGauss = true; +end + +clear machine; + +% define some variables +stdSingleFrac = zeros(dij.dimensions); +stdTotFrac = zeros(dij.dimensions); +numBixels = dij.totalNumOfBixels; +numBeams = length([pln.propStf.gantryAngles]); +numRays = dij.totalNumOfRays; +cubeDim = size(resultGUI.(['physicalDose' robSuffix])); +numFrac = pln.numOfFractions; +isoCenter = [stf(1).isoCenter]; +CTres = [ct.resolution.x ct.resolution.y ct.resolution.z]; +w = resultGUI.(['w' robSuffix]); +conversionFactorSq = 1.6021766208e-02^2; +vBeamNum = dij.beamNum; +vSigmaIni_sq = dij.sigmaIni_sq'; +vEnergyIx = dij.energyIx'; +includeLateralUTC = (sum(strcmp(pln.probOpt.InputUCT,{'phys','biophys'})) > 0 || (strcmp(robSuffix,'Rob') && strcmp(pln.probOpt.InputUCT,{'bio'}))); + +% set overlap priorities +[cst,voiIndexCube] = matRad_setOverlapPriorities(cst,dij.dimensions); + +% loop over all affected VOIs +% check how many and which structures are affected by std calculation +numOmega = 0; +lutVOI = 0; +for i = 1:size(cst,1) + for j = 1:size(cst{i,6},1) + if ~isempty(cst{i,6}(j)) + numOmega = numOmega + 1; + lutVOI(numOmega) = i; %#ok + end + end +end + +totInfo = whos; +fprintf([' current memory consumption ' num2str((sum([totInfo.bytes]))/1e9) ' GB \n']) +dijInfo = whos('dij'); +plnInfo = whos('pln'); +fprintf(['dij: ' num2str(dijInfo.bytes/1e9) ' GB; pln: ' num2str(plnInfo.bytes/1e9) ' GB \n']);fprintf(''); + + +for k = 1:numOmega + + mOmega = sparse(numBixels,numBixels); + [boolMember,~] = ismember(ix,cst{lutVOI(k),4}{1}); + ixSub = ix(boolMember); + [~,ixMap] = ismember(ixSub,ix); + + % initialize depth dose vector + if ~pln.bioParam.bioOpt + dZ = dij.dZ{1}(ixSub,:); % depth doseh component including the the conversion factor + doseExp = resultGUI.([pln.bioParam.quantityVis 'Exp' robSuffix])(ixSub); + else + dZ = dij.dZa{1}(ixSub,:); % alpha dose depth component + dZb = dij.dZb{1}(ixSub,:); % sqrt beta dose depth component + doseExp = resultGUI.(['effectExp' robSuffix])(ixSub); + end + + SpotLUT = dij.SpotLUT{1}(ixSub,:); + radDepth = dij.radDepth{1}(ixSub,:); + stdSingleFracTmp = zeros(numel(ixMap),1); + stdTotFracTmp = zeros(numel(ixMap),1); + + RBEsq = 1; + if strcmp(pln.bioParam.model,'constRBE') + dZ = dZ * pln.bioParam.RBE; + RBEsq = pln.bioParam.RBE^2; + end + + %% display complexity + fprintf(['in total are ' num2str(length(ixSub)) ' voxels affected \n']);fprintf(''); + + %create parallel pool on cluster + if pln.probOpt.numOfWorkers > 0 + p = gcp;% If no pool, do not create new one. + else + p = []; + end + if isempty(p) + poolsize = 0; + else + poolsize = p.NumWorkers; + end + + if poolsize ~= pln.probOpt.numOfWorkers + delete(gcp); + parpool('local',pln.probOpt.numOfWorkers); + end + parfor_progress(numel(ixSub)); + + % loop over all voxels and calculate the secand central moment + %splitapply https://de.mathworks.com/matlabcentral/answers/368003-inevitable-broadcast-variable-in-parfor + + % running parfor with an additional parameter NumWorker to switch + % between a serieal and parallel computation does not work. Moreover, + % parfor with 0 works takes in simplied example twice as long as a + % simple for loop + parfor i = 1:numel(ixSub) + + cntVoxel = i; + + tmpCandidates = round(SpotLUT(cntVoxel,:)); % for the prostate case there was one single non-integer value + vCandidates = full(tmpCandidates(tmpCandidates~=0)); % maybe directly store these values + numCandidates = length(vCandidates); + numCorr = numCandidates^2; + + % in case of no correlation jump to next voxel + if isempty(vCandidates) + cntVoxel = cntVoxel + 1; + continue + end + + % compute lateral dose in x and z + [xDist,zDist] = matRad_getLateralDistVoxelSpots(ixSub(i),cubeDim,CTres,isoCenter,stf,pln,vCandidates,SAD); + + radDepthVoxel = single(full(radDepth(cntVoxel,vBeamNum(vCandidates)))); + sqSigmaInI = vSigmaIni_sq(vCandidates); + subEnergyIx = vEnergyIx(vCandidates); + + if doubleGauss + latSqSigma = zeros(numCandidates,3); + else + latSqSigma = zeros(numCandidates,1); + end + + % calc sigmas + for jj = 1:numCandidates + if doubleGauss + latSqSigma(jj,:) = matRad_interp1(mDepth(:,subEnergyIx(jj)), mLatSigma(:,subEnergyIx(jj),:),radDepthVoxel(jj)); + + latSqSigma(jj,1) = latSqSigma(jj,1).^2 + sqSigmaInI(jj); + latSqSigma(jj,3) = latSqSigma(jj,3).^2 + sqSigmaInI(jj); + vW = latSqSigma(:,2); + else + latSqSigma(jj) = matRad_interp1(mDepth(:,subEnergyIx(jj)), mLatSigma(:,subEnergyIx(jj)),radDepthVoxel(jj)).^2 + sqSigmaInI(jj); + end + end + + % get lateral errors to calcualte the expected lateral dose + shiftErrorLatExp = pln.multScen.shiftSDrnd.^2 + pln.multScen.shiftSDsys.^2; + + % calc lateral dose + if doubleGauss + + if includeLateralUTC + SqSigmaX_Narr = latSqSigma(:,1) + shiftErrorLatExp; + SqSigmaZ_Narr = latSqSigma(:,1) + shiftErrorLatExp; + SqSigmaX_Bro = latSqSigma(:,3) + shiftErrorLatExp; + SqSigmaZ_Bro = latSqSigma(:,3) + shiftErrorLatExp; + else + SqSigmaX_Narr = latSqSigma(:,1); + SqSigmaZ_Narr = latSqSigma(:,1); + SqSigmaX_Bro = latSqSigma(:,3); + SqSigmaZ_Bro = latSqSigma(:,3); + end + + latSqSigmaError = SqSigmaX_Narr; + +% rSq = sqrt(xDist.^2 + zDist.^2); +% tmp_dLx_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaX_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... +% (vW .* exp((-(rSq.^2))./(2*SqSigmaX_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); +% tmp_dLz_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaZ_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... +% (vW .* exp((-(rSq.^2))./(2*SqSigmaZ_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + + dLx = SGGauss(xDist,0,SqSigmaX_Narr); + dLz = SGGauss(zDist,0,SqSigmaZ_Narr); + + else + if includeLateralUTC + latSqSigmaError = latSqSigma + shiftErrorLatExp; + else + latSqSigmaError = latSqSigma; + end + + dLx = SGGauss(xDist,0,latSqSigmaError); + dLz = SGGauss(zDist,0,latSqSigmaError); + end + + vdLxCorr = reshape(dLx*dLx',[numCorr 1]); % create squared lateral dose in x vector + vdLxUnCorr = vdLxCorr; + vdLzCorr = reshape(dLz*dLz',[numCorr 1]) ; % create squared lateral dose in z vector + vdLzUnCorr = vdLzCorr; + + PSI_Z_Corr = (dZ(cntVoxel,:)' * dZ(cntVoxel,:)); + PSI_Z_Uncorr = PSI_Z_Corr; + + %% start lateral correlation computation + + % get indices + vI = repmat(vCandidates',[numCandidates 1]); + vJ = zeros(numCorr,1); loopIx = 1; + LinIx = zeros(numCandidates); + + for h = 1:numCandidates + vJ(loopIx:loopIx+numCandidates-1) = vCandidates(h); + loopIx = loopIx + numCandidates; + LinIx(:,h) = vCandidates' .* ones(numCandidates,1) + (vCandidates(h)'-1)*numBixels ; + end + + linIxDiag = 1:numBixels+1:numBixels^2; + + if (pln.multScen.shiftSDrnd > 0 || pln.multScen.shiftSDsys > 0) && includeLateralUTC + if USE_MEX_LAT + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast_mex(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),... + pln.multScen.shiftSDrnd,pln.multScen.shiftSDsys); + else + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),... + pln.multScen.shiftSDrnd,pln.multScen.shiftSDsys); + end + + else + if USE_MEX_LAT + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast_mex(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),0,0); + else + [vPSI_XcorrTmp, vPSI_XuncorrTmp, vPSI_ZcorrTmp, vPSI_ZuncorrTmp, LinIdx] = ... + matRad_calcSecLatMomFast(... + vCandidates,numBeams,numBixels,pln.multScen.bixelIndexBeamOffset,pln.multScen.bixelBeamLUT,xDist',zDist',latSqSigma(:,1),0,0); + end + + end + + % create lateral correlation matrices for voxel i + vdLxCorr(LinIdx) = vPSI_XcorrTmp; + PSI_Lx_Corr = sparse(vI,vJ,vdLxCorr,numBixels,numBixels); + vdLxUnCorr(LinIdx) = vPSI_XuncorrTmp; + PSI_Lx_Uncorr = sparse(vI,vJ,vdLxUnCorr,numBixels,numBixels); + + + vdLzCorr(LinIdx) = vPSI_ZcorrTmp; + PSI_Lz_Corr = sparse(vI,vJ,vdLzCorr,numBixels,numBixels); + vdLzUnCorr(LinIdx) = vPSI_ZuncorrTmp; + PSI_Lz_Uncorr = sparse(vI,vJ,vdLzUnCorr,numBixels,numBixels); + + if (pln.multScen.rangeSDrnd > 0 || pln.multScen.rangeSDsys > 0) || relUCTbio > 0 + + numCandidates = numel(vCandidates); + LinIx = zeros(numCandidates); + vCompFac = zeros(numCandidates,1); + + for h = 1:numCandidates + jIdx = vCandidates'; + mIdx = vCandidates(h).*ones(length(jIdx),1); + LinIx(:,h) = jIdx + (mIdx-1)*numBixels; + + vCompFac(h) = vCutOff{dij.energyIx(vCandidates(h)),dij.beamNum(vCandidates(h))}.CompFac^2; + end + + randCovRadDepth = full(pln.multScen.mCovRangeRnd(LinIx)); + sysCovRadDepth = full(pln.multScen.mCovRangeSys(LinIx)); + bioCov = full(pln.multScen.mCovBio(LinIx)); + + %% check diagonal double indices + if USE_MEX_DEPTH + [PSI_CorrTmp, PSI_UncorrTmp,LinIx,LinIxInv] = matRad_calcSecRangeMomFast_mex(conversionFactorSq * vCompFac' * RBEsq,... + numBixels,numRays,vEnergyIx',vBeamNum',vCandidates,full(radDepth(cntVoxel,:)),mMean,mWeight,mWidth... + ,mDepth,mLUT,pln.multScen.rangeSDrnd,pln.multScen.rangeSDsys,randCovRadDepth,sysCovRadDepth,bioCov,... + relUCTbio,mCovSpot); + else + [PSI_CorrTmp, PSI_UncorrTmp,LinIx,LinIxInv] = matRad_calcSecRangeMomFast(conversionFactorSq * vCompFac * RBEsq,... + numBixels,numRays,vEnergyIx',vBeamNum',vCandidates,full(radDepth(cntVoxel,:)),mMean,mWeight,mWidth... + ,mDepth,mLUT,pln.multScen.rangeSDrnd,pln.multScen.rangeSDsys,randCovRadDepth,sysCovRadDepth,bioCov,... + relUCTbio,mCovSpot); + end + + PSI_Z_Corr(LinIx) = PSI_CorrTmp; + PSI_Z_Corr(LinIxInv) = PSI_CorrTmp; + PSI_Z_Uncorr(LinIx) = PSI_UncorrTmp; + PSI_Z_Uncorr(LinIxInv) = PSI_UncorrTmp; + + end + + dLxSparse = sparse(ones(numCandidates,1),vCandidates,dLx,1,numBixels); + dLzSparse = sparse(ones(numCandidates,1),vCandidates,dLz,1,numBixels); + + % PSI_Z_Corr either contains physical quantites or the alpha dose component + mPSI_Corr = (PSI_Lx_Corr .* PSI_Lz_Corr .* PSI_Z_Corr); + mPSI_UnCorr = (PSI_Lx_Uncorr .* PSI_Lz_Uncorr .* PSI_Z_Uncorr); + PSI_Corr = w' * (mPSI_Corr) * w; + PSI_UnCorr = w' * (mPSI_UnCorr) * w; + + doseExpij = (dLxSparse .* dLzSparse .* dZ(cntVoxel,:)); + + Var_Corr = (PSI_Corr - (doseExpij*w)^2); + Var_UnCorr = (PSI_UnCorr - (doseExpij*w)^2); + Var_FracTot = (1/numFrac) .* (Var_Corr + (Var_UnCorr * (numFrac-1))); + + if sum(isnan(Var_Corr)) > 0 + matRad_dispToConsole('matRad_calcParticleVarSeriel: NaN values in std',[],'warning'); + end + + %stdTotFracTmp(i) = real(sqrt(Var_FracTot)); + %stdSingleFracTmp(i) = real(sqrt(Var_Corr)); + + stdTotFracTmp(i) = sqrt(Var_FracTot); + if imag(stdTotFracTmp(i)) + stdTotFracTmp(i) = 0; + end + stdSingleFracTmp(i) = sqrt(Var_Corr); + if imag(stdSingleFracTmp(i)) + stdSingleFracTmp(i) = 0; + end + + % fill omega matrix + + if param.CALC_OMEGA && ismember(ixSub(i),omegaVoxel) + + doseExpij2 = doseExpij' * doseExpij; + PSI = (1/numFrac) .* (mPSI_Corr + (mPSI_UnCorr * (numFrac-1))); + mOmega = mOmega + (PSI - doseExpij2); + + end + + parfor_progress; + + end % eof of voxel loop + + % delete matlabs pool workers + parfor_progress(0); + + stdTotFrac(ix(ixMap)) = stdTotFracTmp; + stdSingleFrac(ix(ixMap)) = stdSingleFracTmp; + + cst{lutVOI(k),6}.mOmega = mOmega; + +end % THIS needs to go to the end of the voxel loop + + +if ~pln.bioParam.bioOpt + + resultGUI.([pln.bioParam.quantityVis 'StdSingleFrac' robSuffix]) = stdSingleFrac; + resultGUI.([pln.bioParam.quantityVis 'StdTotFrac' robSuffix]) = stdTotFrac; + +else + + if ~strcmp(pln.bioParam.quantityOpt,'effect') + matRad_dispToConsole('matRad_calcParticleVarSerial: not supported',[],'error') + end + + resultGUI.([pln.bioParam.quantityOpt 'StdSingleFrac' robSuffix]) = stdSingleFrac; + resultGUI.([pln.bioParam.quantityOpt 'StdTotFrac' robSuffix]) = stdTotFrac; + + [~,vStdRBExDsingleFrac] = matRad_effectToRBExDose(resultGUI.(['effectExp' robSuffix]), stdSingleFrac ,dij.alphaX,dij.betaX); + [vMeanRBExD,vStdRBExDtotFrac] = matRad_effectToRBExDose(resultGUI.(['effectExp' robSuffix]), stdTotFrac ,dij.alphaX,dij.betaX); + + resultGUI.([pln.bioParam.quantityVis 'Exp' robSuffix]) = reshape(vMeanRBExD,dij.dimensions); + resultGUI.([pln.bioParam.quantityVis 'StdSingleFrac' robSuffix]) = reshape(vStdRBExDsingleFrac,dij.dimensions); + resultGUI.([pln.bioParam.quantityVis 'StdTotFrac' robSuffix]) = reshape(vStdRBExDtotFrac,dij.dimensions); + +end + + + +end % eof function + diff --git a/APM/matRad_calcParticleVarSerial.m b/APM/matRad_calcParticleVarSerial.m new file mode 100644 index 000000000..675091006 --- /dev/null +++ b/APM/matRad_calcParticleVarSerial.m @@ -0,0 +1,715 @@ +function [cst,resultGUI] = matRad_calcParticleVarSerial(ct,cst,stf,pln,dij,resultGUI,ix,param) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad's seriel variance calculation function to compute based on APM +% the second central moment +% +% call +% [cst,resultGUI] = matRad_calcParticleVarSeriel(cst,stf,pln,dij,resultGUI,ix) +% +% input +% cst: matRad critical structure struct cst +% stf: matRad steering information struct +% pln: matRad plan meta information struct +% dij: matRad dij dose influence struct +% resultGUI: matRads resultGUI struct +% ix voxel indices for which variance should be calcualted +% +% output +% cst: matRad critical structure struct cst +% resultGUI: matRads resultGUI struct +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +addpath([fileparts(mfilename('fullpath')) filesep 'MEX']); + +SGGauss = @(x,mu,SqSigma) 1./(sqrt(2*pi.*SqSigma)).*exp(-((x - mu).^2./(2.*SqSigma))); +DBGauss = @(x,mu,SqSigma1,SqSigma2,W) ((1-W).*(1./(sqrt(2*pi.*SqSigma1)).*exp(-((x - mu).^2./(2.*SqSigma1))))) + ... + ((W).*(1./(sqrt(2*pi.*SqSigma2)).*exp(-((x - mu).^2./(2.*SqSigma2))))) ; + +sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ... + exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w); + +USE_MEX_LAT = false; +USE_MEX_DEPTH = false; +if exist(['matRad_calcSecLatMom_mex.' mexext],'file') == 3 + USE_MEX_LAT = true; +end + +if exist(['matRad_calcSecRangeMom_mex.' mexext],'file') == 3 + USE_MEX_DEPTH = true; +end + +% check which depth dose component should be used for variance calculation +if ~pln.bioParam.bioOpt + depthComp = 'Z'; +else + depthComp = 'alphaDose'; +end + +% calculate rED or rSP from HU +ct = matRad_calcWaterEqD(ct, pln, param); + +% check if robust pencil beam weights are available +robSuffix = 'Rob'; +if param.CALC_OMEGA + robSuffix = ''; +end + +% find target voxel indices +omegaVoxel = []; +if pln.probOpt.omegaTarget + for i=1:size(cst,1) + if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6}) + omegaVoxel = [omegaVoxel;vertcat(cst{i,4}{:})]; + end + end +else + omegaVoxel = ix; +end + +%% load base data set and compute reduced components +load([pln.radiationMode '_' pln.machine '.mat']); +SAD = machine.meta.SAD; +numComp = numel(machine.data(1).Z.width); +[mMean, mWeight, mWidth, mLatSigma, mLUT, mDepth, vOffset, vCutOff] = matRad_getBaseDataAPM(ct,stf,pln,dij.vTissueIndex,depthComp); + +% define biological uncertainties +if ~pln.bioParam.bioOpt + relUCTbio = 0.0; +else + relUCTbio = pln.multScen.relBioUCT; +end + +mCovSpot = pln.multScen.mCovSpot; + +doubleGauss = false; +if ~isfield(machine.data(1),'sigma') + doubleGauss = true; +end + +%clear machine; +% define some variables +stdSingleFrac = zeros(dij.dimensions); +stdTotFrac = zeros(dij.dimensions); +numBixels = dij.totalNumOfBixels; +cubeDim = size(resultGUI.(['physicalDose' robSuffix])); +numFrac = pln.numOfFractions; +isoCenter = [stf(1).isoCenter]; +CTres = [ct.resolution.x ct.resolution.y ct.resolution.z]; +radDepth = dij.radDepth{1}(ix,:); +w = resultGUI.(['w' robSuffix]); +conversionFactor = 1.6021766208e-02; +includeLateralUTC = (sum(strcmp(pln.probOpt.InputUCT,{'phys','biophys'})) > 0 || (strcmp(robSuffix,'Rob') && strcmp(pln.probOpt.InputUCT,{'bio'}))); + +% set overlap priorities +[cst,voiIndexCube] = matRad_setOverlapPriorities(cst,dij.dimensions); + +% initialize depth dose vector +dZb = zeros(numel(ix),1); +if ~pln.bioParam.bioOpt + dZ = dij.dZ{1}(ix,:); % depth doseh component including the the conversion factor + doseExp = resultGUI.([pln.bioParam.quantityVis 'Exp' robSuffix])(ix); +else + dZ = dij.dZa{1}(ix,:); % alpha dose depth component + dZb = dij.dZb{1}(ix,:); % sqrt beta dose depth component + doseExp = resultGUI.(['effectExp' robSuffix])(ix); +end + +RBEsq = 1; +if strcmp(pln.bioParam.model,'constRBE') + dZ = dZ * pln.bioParam.RBE; + RBEsq = pln.bioParam.RBE^2; +end + +%% display complexity +totInfo = whos; +fprintf([' current memory consumption ' num2str((sum([totInfo.bytes]))/1e9) ' GB \n']) +dijInfo = whos('dij'); +plnInfo = whos('pln'); +dZInfo = whos('dZ'); +matRad_dispToConsole(['dij: ' num2str(dijInfo.bytes/1e9) ' GB; pln: ' num2str(plnInfo.bytes/1e9) ' GB;' ' dZ: ' num2str(dZInfo.bytes/1e9) ' GB \n'],param,'info') +matRad_dispToConsole(['In total are ' num2str(length(ix)) ' voxels affected \n'],param,'info'); + +cntVoxel = 1; + +% loop over all voxels and calculate the secand central moment +for i = ix' + + tmpCandidates = dij.SpotLUT{1}(i,:); + vCandidates = full(tmpCandidates(tmpCandidates~=0)); % maybe directly store these values + numCandidates = length(vCandidates); + + % in case of no correlation jump to next voxel + if isempty(vCandidates) + cntVoxel = cntVoxel + 1; + continue + end + + % compute lateral dose in x and z + [xDist,zDist] = matRad_getLateralDistVoxelSpots(i,cubeDim,CTres,isoCenter,stf,pln,vCandidates,SAD); + + radDepthVoxel = full(radDepth(cntVoxel,dij.beamNum(vCandidates))) + 0; + sqSigmaInI = dij.sigmaIni_sq(vCandidates); + subEnergyIx = dij.energyIx(vCandidates); + + if doubleGauss + latSqSigma = zeros(numCandidates,3); + else + latSqSigma = zeros(numCandidates,1); + end + + % calc sigmas + for jj = 1:numCandidates + if doubleGauss + latSqSigma(jj,:) = matRad_interp1(mDepth(:,subEnergyIx(jj)), mLatSigma(:,subEnergyIx(jj),:),radDepthVoxel(jj)); + + latSqSigma(jj,1) = latSqSigma(jj,1).^2 + sqSigmaInI(jj); + latSqSigma(jj,3) = latSqSigma(jj,3).^2 + sqSigmaInI(jj); + vW = latSqSigma(:,2); + else + latSqSigma(jj) = matRad_interp1(mDepth(:,subEnergyIx(jj)), mLatSigma(:,subEnergyIx(jj)),radDepthVoxel(jj)).^2 + sqSigmaInI(jj); + end + end + + % get lateral errors to calcualte the expected lateral dose + shiftErrorLatExp = pln.multScen.shiftSDrnd.^2 + pln.multScen.shiftSDsys.^2; + + % calc lateral dose + if doubleGauss + + if includeLateralUTC + SqSigmaX_Narr = latSqSigma(:,1) + shiftErrorLatExp; + SqSigmaZ_Narr = latSqSigma(:,1) + shiftErrorLatExp; + SqSigmaX_Bro = latSqSigma(:,3) + shiftErrorLatExp; + SqSigmaZ_Bro = latSqSigma(:,3) + shiftErrorLatExp; + else + SqSigmaX_Narr = latSqSigma(:,1); + SqSigmaZ_Narr = latSqSigma(:,1); + SqSigmaX_Bro = latSqSigma(:,3); + SqSigmaZ_Bro = latSqSigma(:,3); + end + + latSqSigmaError = SqSigmaX_Narr; + +% rSq = sqrt(xDist.^2 + zDist.^2); +% tmp_dLx_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaX_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... +% (vW .* exp((-(rSq.^2))./(2*SqSigmaX_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); +% tmp_dLz_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaZ_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) +... +% (vW .* exp((-(rSq.^2))./(2*SqSigmaZ_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + + tmp_dLx = SGGauss(xDist,0,SqSigmaX_Narr); + tmp_dLz = SGGauss(zDist,0,SqSigmaZ_Narr); + + dLx = sparse(ones(numCandidates,1),vCandidates,tmp_dLx,1,numBixels); + dLz = sparse(ones(numCandidates,1),vCandidates,tmp_dLz,1,numBixels); + + else + + if includeLateralUTC + latSqSigmaError = latSqSigma(:,1) + shiftErrorLatExp; + else + latSqSigmaError = latSqSigma(:,1); + end + + dLx = sparse(ones(numCandidates,1),vCandidates,SGGauss(xDist,0,latSqSigmaError),1,numBixels); + dLz = sparse(ones(numCandidates,1),vCandidates,SGGauss(zDist,0,latSqSigmaError),1,numBixels); + + end + + PSI_Lx_Corr = (dLx' * dLx); + PSI_Lz_Corr = (dLz' * dLz); + PSI_Z_Corr = (dZ(cntVoxel,:)' * dZ(cntVoxel,:)); + PSI_Lx_Uncorr = PSI_Lx_Corr; + PSI_Lz_Uncorr = PSI_Lz_Corr; + PSI_Z_Uncorr = PSI_Z_Corr; + + + %% consistency check + if doubleGauss + rSq = sqrt(xDist.^2 + zDist.^2); + tmp_dLx_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaX_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) + ... + (vW .* exp((-(rSq.^2))./(2*SqSigmaX_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + tmp_dLz_full = sqrt(((1-vW) .* exp((-(rSq.^2))./(2*SqSigmaZ_Narr))./(sqrt(SqSigmaX_Narr).*sqrt(SqSigmaZ_Narr).*2*pi)) + ... + (vW .* exp((-(rSq.^2))./(2*SqSigmaZ_Bro)) ./(sqrt(SqSigmaX_Bro) .*sqrt(SqSigmaZ_Bro) .*2*pi))); + dLxDG = sparse(ones(numCandidates,1),vCandidates,tmp_dLx_full,1,numBixels); + dLzDG = sparse(ones(numCandidates,1),vCandidates,tmp_dLz_full,1,numBixels); + end + + if ~pln.bioParam.bioOpt + if ~doubleGauss + if abs( (full(dLx .* dLz .* dZ(cntVoxel,:)) * w ) - doseExp(cntVoxel) ) > 1e-4 + warning('recalculated dose does not match !'); + end + else + if abs( (full(dLxDG .* dLzDG .* dZ(cntVoxel,:)) * w ) - doseExp(cntVoxel) ) > 1e-4 + warning('recalculated dose does not match !'); + end + end + else + if ~doubleGauss + if abs( full( ((dLx .* dLz .* dZ(cntVoxel,:)) * w) + ((dLx .* dLz .* dZb(cntVoxel,:)) * w)^2) - doseExp(cntVoxel)) > 1e-4 + warning('recalculated effect does not match !'); + end + else + if abs( full( ((dLxDG .* dLzDG .* dZ(cntVoxel,:)) * w) + ((dLxDG .* dLzDG .* dZb(cntVoxel,:)) * w)^2) - doseExp(cntVoxel)) > 1e-4 + warning('recalculated effect does not match !'); + end + end + end + + % define lateral cut offs - this is a bit overkill - cause the + % cut off is already defined by spots which contribute to a certain + % voxel. However for the APM calculation it is possible to set an + % additional cutoff. + if pln.probOpt.LatCutOff > 0 + lateralCutOff = sqrt(pln.probOpt.LatCutOff.*latSqSigmaError); + bLcutOffX = abs(xDist) < lateralCutOff; + bLcutOffZ = abs(zDist) < lateralCutOff; + end + + % estimate the number of elements in the PSI matrix + iniGuess = numBixels*numBixels; + cntPSIx = 1; cntPSIz = 1; cntPSIrange = 1; + vPSI_Lx_i = zeros(iniGuess,1); + vPSI_Lx_j = zeros(iniGuess,1); + vPSI_Lx_lin = zeros(iniGuess,1); + vPSI_Lx_CorrV = zeros(iniGuess,1); + vPSI_Lx_UncorrV = zeros(iniGuess,1); + + vPSI_Lz_i = zeros(iniGuess,1); + vPSI_Lz_j = zeros(iniGuess,1); + vPSI_Lz_lin = zeros(iniGuess,1); + vPSI_Lz_CorrV = zeros(iniGuess,1); + vPSI_Lz_UncorrV = zeros(iniGuess,1); + + vPSI_Z_lin = zeros(iniGuess,1); + vPSI_Z_CorrV = zeros(iniGuess,1); + vPSI_Z_UncorrV = zeros(iniGuess,1); + + cntSpot = 1; + + for j = vCandidates % only loop over bixel combinations that contribute dose to voxel i + + lowerBeamIdx = pln.multScen.bixelIndexBeamOffset(dij.beamNum(j)); + upperBeamIdx = pln.multScen.bixelIndexBeamOffset(dij.beamNum(j)+1); + + % find correlated spot indices in + bLatSqSig = false(1,numCandidates); + ixBixelLatFull = vCandidates(cntSpot:end); % loop through vCandidates in a syemmtric manner + ixx = (ixBixelLatFull < upperBeamIdx & ixBixelLatFull >= lowerBeamIdx); % check if all spots belong to the same beam direction + + ixBixelLat = ixBixelLatFull(ixx); % obtained reduced correlated spot indices + bLatSqSig(1,cntSpot:end) = ixx; % obtain corresponding logical index vector + + % apply additional cut off in x direction + ixBixelLatX = ixBixelLat'; + bLatSqSigX = bLatSqSig; + + if pln.probOpt.LatCutOff > 0 + ixCut = bLcutOffX(ixx)'; + ixBixelLatX = ixBixelLatX(ixCut); + bLatSqSigX(bLatSqSigX==true) = ixCut; + end + + if ~isempty(ixBixelLatX) + + % obtain indices + mIdx = ixBixelLatX; + jIdx = j.*ones(numel(mIdx),1); + + LinIdx = jIdx + (mIdx-1) * numBixels; + LinIdxInv = mIdx + (jIdx-1) * numBixels; + + Dev_ij = xDist(cntSpot); % lateral distance in x from voxel i to central axis of pencil beam j + Dev_im = xDist(bLatSqSigX); % lateral distance in x from voxel i to central axis of pencil beam m + + ixSpotBeamLUT = dij.beamNum(j) == dij.beamNum(mIdx); + + vLaSi11 = latSqSigma(cntSpot,1) + pln.multScen.shiftSDrnd ^2 + pln.multScen.shiftSDsys^2 ; % combines Lambda and Sigma -> LaSi + vLaSi22 = latSqSigma(bLatSqSigX,1) + pln.multScen.shiftSDrnd ^2 + pln.multScen.shiftSDsys^2 ; + vLaSi12uncorr = (pln.multScen.shiftSDsys^2 * ixSpotBeamLUT); + + if (pln.multScen.shiftSDrnd > 0 || pln.multScen.shiftSDsys > 0) && includeLateralUTC + vLaSi12 = (pln.multScen.shiftSDrnd ^2 + pln.multScen.shiftSDsys^2 )* ixSpotBeamLUT; + % vLaSi22 = full(pln.multScen.mCovLatRnd(mIdx) + latSqSigma(bLatSqSigX,1)); + else + vLaSi12 = 0 * ixSpotBeamLUT; + end + + if USE_MEX_LAT + vPSI_CorrTmp = matRad_calcSecLatMom_mex(vLaSi11,vLaSi22,vLaSi12,vLaSi12,Dev_ij,Dev_im); + vPSI_UncorrTmp = matRad_calcSecLatMom_mex(vLaSi11,vLaSi22,vLaSi12uncorr,vLaSi12uncorr,Dev_ij,Dev_im); + else + vPSI_CorrTmp = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12,vLaSi12,Dev_ij,Dev_im); + vPSI_UncorrTmp = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12uncorr,vLaSi12uncorr,Dev_ij,Dev_im); + end + + numPSI = numel(vPSI_CorrTmp); + + if numPSI ~= 1 + offset = 2; + vRangeX = (cntPSIx:cntPSIx+numPSI*2-offset)'; + vPSI_Lx_i(vRangeX) = [jIdx; mIdx(offset:end)]; % take care of diagonal element, double indices will be added up when sparse matrix is created + vPSI_Lx_j(vRangeX) = [mIdx; jIdx(offset:end)]; + vPSI_Lx_CorrV(vRangeX) = [vPSI_CorrTmp; vPSI_CorrTmp(offset:end)]; + vPSI_Lx_UncorrV(vRangeX) = [vPSI_UncorrTmp; vPSI_UncorrTmp(offset:end)]; + vPSI_Lx_lin(vRangeX) = [LinIdx; LinIdxInv(offset:end)]; + else + vRangeX = cntPSIx; + vPSI_Lx_i(vRangeX) = jIdx; + vPSI_Lx_CorrV(vRangeX) = vPSI_CorrTmp; + vPSI_Lx_UncorrV(vRangeX) = vPSI_UncorrTmp; + vPSI_Lx_lin(vRangeX) = LinIdx; + end + + cntPSIx = cntPSIx + numel(vRangeX); + + end % eof ~isempty(ixBixelLatX) + + % apply additional cut off in x direction + ixBixelLatZ = ixBixelLat'; + bLatSqSigZ = bLatSqSig; + + if pln.probOpt.LatCutOff > 0 + ixCut = bLcutOffZ(ixx)'; + ixBixelLatZ = ixBixelLatZ(ixCut); + bLatSqSigZ(bLatSqSigZ==true) = ixCut; + end + + if ~isempty(ixBixelLatZ) + + % obtain indices + mIdx = ixBixelLatZ; + jIdx = j.*ones(numel(mIdx),1); + LinIdx = jIdx + (mIdx-1)* numBixels; + LinIdxInv = mIdx + (jIdx-1)* numBixels; + + Dev_ij = zDist(cntSpot); % lateral distance in z from voxel i to central axis of pencil beam j + Dev_im = zDist(bLatSqSigZ); % lateral distance in z from voxel i to central axis of pencil beam m + + ixSpotBeamLUT = dij.beamNum(j) == dij.beamNum(mIdx); + + vLaSi11 = latSqSigma(cntSpot,1) + pln.multScen.shiftSDrnd ^2 + pln.multScen.shiftSDsys^2 ; % combines Lambda and Sigma -> LaSi + vLaSi22 = latSqSigma(bLatSqSigZ,1) + pln.multScen.shiftSDrnd ^2 + pln.multScen.shiftSDsys^2 ; + vLaSi12uncorr = (pln.multScen.shiftSDsys^2 * ixSpotBeamLUT); + + if (pln.multScen.shiftSDrnd > 0 || pln.multScen.shiftSDsys > 0) && includeLateralUTC + vLaSi12 = (pln.multScen.shiftSDrnd ^2 + pln.multScen.shiftSDsys^2 )* ixSpotBeamLUT; + % vLaSi22 = full(pln.multScen.mCovLatRnd(mIdx) + latSqSigma(bLatSqSigX,1)); + else + vLaSi12 = 0 * ixSpotBeamLUT; + end + + if USE_MEX_LAT + vPSI_CorrTmp = matRad_calcSecLatMom_mex(vLaSi11,vLaSi22,vLaSi12,vLaSi12,Dev_ij,Dev_im); + vPSI_UncorrTmp = matRad_calcSecLatMom_mex(vLaSi11,vLaSi22,vLaSi12uncorr,vLaSi12uncorr,Dev_ij,Dev_im); + else + vPSI_CorrTmp = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12,vLaSi12,Dev_ij,Dev_im); + vPSI_UncorrTmp = matRad_calcSecLatMom(vLaSi11,vLaSi22,vLaSi12uncorr,vLaSi12uncorr,Dev_ij,Dev_im); + end + + numPSI = numel(vPSI_CorrTmp); + + if numPSI ~= 1 + offset = 2; + vRangeZ = (cntPSIz:cntPSIz+numPSI*2-offset)'; + vPSI_Lz_i(vRangeZ) = [jIdx; mIdx(offset:end)]; % take care of diagonal element, double indices will be added during creation of sparse matrix + vPSI_Lz_j(vRangeZ) = [mIdx; jIdx(offset:end)]; + vPSI_Lz_CorrV(vRangeZ) = [vPSI_CorrTmp; vPSI_CorrTmp(offset:end)]; + vPSI_Lz_UncorrV(vRangeZ) = [vPSI_UncorrTmp; vPSI_UncorrTmp(offset:end)]; + vPSI_Lz_lin(vRangeZ) = [LinIdx; LinIdxInv(offset:end)]; + else + vRangeZ = cntPSIz; + vPSI_Lz_i(vRangeZ) = jIdx; + vPSI_Lz_CorrV(vRangeZ) = vPSI_CorrTmp; + vPSI_Lz_UncorrV(vRangeZ) = vPSI_UncorrTmp; + vPSI_Lz_lin(vRangeZ) = LinIdx; + end + + cntPSIz = cntPSIz + numel(vRangeZ); + + end + + % calc second raw moment of range - assuming rand and sys errors + % have the same correlation structure + bixelIdxDepth = []; + if pln.multScen.rangeSDrnd > 0 + bixelIdxDepth = unique([bixelIdxDepth find((pln.multScen.mCovRangeRnd(j,j:vCandidates(end)) > 0 )) + j - 1]); + end + + if pln.multScen.rangeSDsys > 0 + bixelIdxDepth = unique([bixelIdxDepth find((pln.multScen.mCovRangeSys(j,j:vCandidates(end)) > 0 )) + j - 1]); + end + + if relUCTbio > 0 + bixelIdxDepth = unique([bixelIdxDepth find((pln.multScen.mCovBio(j,j:vCandidates(end)) > 0 )) + j - 1]); + end + + % obtain look up table for reduced components for spot j + beamIx_j = dij.beamNum(j); + energyIx_j = dij.energyIx(j); + if pln.probOpt.useReducedComp + ixLut_j = find(mDepth(:,energyIx_j) > radDepth(cntVoxel,beamIx_j),1,'first'); % find depth index + vIxLUT_j = logical(squeeze(mLUT(:,ixLut_j,energyIx_j))); + else + vIxLUT_j = true(numComp,1); + end + + Dev_j = radDepth(cntVoxel,beamIx_j) - mMean(vIxLUT_j,energyIx_j); + SigmaSq_j = mWidth(vIxLUT_j,energyIx_j); + Weight_j = mWeight(vIxLUT_j,energyIx_j); + LinInd_MM = bixelIdxDepth' + ((bixelIdxDepth-1)*numBixels)'; + + + if pln.multScen.rangeSDrnd > 0 + randCovRadDepth = pln.multScen.mCovRangeRnd(j,j); + vRandCovRadDepth = full(pln.multScen.mCovRangeRnd(j,bixelIdxDepth))'; + vRandCovRadDepth22 = full(pln.multScen.mCovRangeRnd(LinInd_MM)); + else + vRandCovRadDepth22 = 0; + end + + + if pln.multScen.rangeSDsys > 0 + sysCovRadDepth = pln.multScen.mCovRangeSys(j,j); + vSysCovRadDepth = full(pln.multScen.mCovRangeSys(j,bixelIdxDepth))'; + vSysCovRadDepth22 = full(pln.multScen.mCovRangeSys(LinInd_MM)); + else + vSysCovRadDepth22 = 0; + end + + cntRange = 1; + + % loop over all correlated spot combinations + if ~isempty(bixelIdxDepth) + for m = bixelIdxDepth + + SqSigmaRandUnCorr_jm = 0; + + if pln.multScen.rangeSDrnd > 0 + SqSigmaRandCorr_jm = [randCovRadDepth vRandCovRadDepth(cntRange) vRandCovRadDepth(cntRange) vRandCovRadDepth22(cntRange)]; + SqSigmaRandUnCorr_jm = [randCovRadDepth 0 0 vRandCovRadDepth22(cntRange)]; + else + SqSigmaRandCorr_jm = 0; + end + + if pln.multScen.rangeSDsys > 0 + SqSigmaSys_jm = [sysCovRadDepth vSysCovRadDepth(cntRange) vSysCovRadDepth(cntRange) vSysCovRadDepth22(cntRange)]; + else + SqSigmaSys_jm = 0; + end + + % obtain look up table for reduced components for spot m + beamIx_m = dij.beamNum(m); + energyIx_m = dij.energyIx(m); + + + if pln.probOpt.useReducedComp + ixLut_m = find(mDepth(:,energyIx_m) > radDepth(cntVoxel,beamIx_m),1,'first'); %this line of code is still slow + vIxLUT_m = (mLUT(:,ixLut_m,energyIx_m)); + else + vIxLUT_m = true(numComp,1); + end + + + Weight_m = mWeight(vIxLUT_m,energyIx_m); + Dev_m = radDepth(cntVoxel,beamIx_m) - mMean(vIxLUT_m,energyIx_m); + SigmaCorr = full(SqSigmaRandCorr_jm + SqSigmaSys_jm); + SigmaUnCorr = full(SqSigmaRandUnCorr_jm + SqSigmaSys_jm); + + + compFac = vCutOff{energyIx_j,dij.beamNum(j)}.CompFac * vCutOff{energyIx_m,dij.beamNum(m)}.CompFac; + + %mW_CovAlphaDose_jm = caWeightUCT.mCovalphaDose(j*numComp-(numComp-1):j*numComp,m*numComp-(numComp-1):m*numComp); + mW_CovAlphaDose_jm = mCovSpot(vIxLUT_j,vIxLUT_m).* ((Weight_j*relUCTbio) * (Weight_m'*relUCTbio)); + + if USE_MEX_DEPTH + + PSI_CorrTmp = conversionFactor^2 * compFac * RBEsq * matRad_calcSecRangeMom_mex(SigmaSq_j + SigmaCorr(1,1),... + mWidth(vIxLUT_m,energyIx_m) + SigmaCorr(:,4),... + SigmaCorr(1,2),SigmaCorr(1,3),Dev_j,Dev_m,... + Weight_j, Weight_m, mW_CovAlphaDose_jm); + + PSI_UncorrTmp = conversionFactor^2 * compFac * RBEsq * matRad_calcSecRangeMom_mex(SigmaSq_j + SigmaUnCorr(1,1),... + mWidth(vIxLUT_m,energyIx_m) + SigmaUnCorr(:,4),... + SigmaUnCorr(1,2),SigmaUnCorr(1,3),Dev_j,Dev_m,... + Weight_j, Weight_m, mW_CovAlphaDose_jm); + else + PSI_CorrTmp = conversionFactor^2 * compFac * RBEsq * matRad_calcSecRangeMom(SigmaSq_j + SigmaCorr(1,1),... + mWidth(vIxLUT_m,energyIx_m) + SigmaCorr(:,4),... + SigmaCorr(1,2),SigmaCorr(1,3),Dev_j,Dev_m,... + Weight_j, Weight_m, mW_CovAlphaDose_jm); + + PSI_UncorrTmp = conversionFactor^2 * compFac * RBEsq * matRad_calcSecRangeMom(SigmaSq_j + SigmaUnCorr(1,1),... + mWidth(vIxLUT_m,energyIx_m) + SigmaUnCorr(:,4),... + SigmaUnCorr(1,2),SigmaUnCorr(1,3),Dev_j,Dev_m,... + Weight_j, Weight_m, mW_CovAlphaDose_jm); + + end + + + if j == m + LinIdx = j + (m-1)* numBixels; + vPSI_Z_CorrV(cntPSIrange) = PSI_CorrTmp; + vPSI_Z_UncorrV(cntPSIrange) = PSI_UncorrTmp; + vPSI_Z_lin(cntPSIrange) = LinIdx; + cntPSIrange = cntPSIrange + 1; + else + LinIdx = j + (m-1)* numBixels; + LinIdxInv = m + (j-1)* numBixels; + vRangeZ = [cntPSIrange:cntPSIrange+1]'; + vPSI_Z_CorrV(vRangeZ) = [PSI_CorrTmp; PSI_CorrTmp]; + vPSI_Z_UncorrV(vRangeZ) = [PSI_UncorrTmp; PSI_UncorrTmp]; + vPSI_Z_lin(vRangeZ) = [LinIdx; LinIdxInv]; + cntPSIrange = cntPSIrange + 2; + end + + cntRange = cntRange + 1; + + end + end + cntSpot = cntSpot + 1; + end + + % fill sparse matrix from vectors + PSI_Lx_Corr(vPSI_Lx_lin(1:cntPSIx-1)) = vPSI_Lx_CorrV(1:cntPSIx-1); + PSI_Lz_Corr(vPSI_Lz_lin(1:cntPSIz-1)) = vPSI_Lz_CorrV(1:cntPSIz-1); + PSI_Lx_Uncorr(vPSI_Lx_lin(1:cntPSIx-1)) = vPSI_Lx_UncorrV(1:cntPSIx-1); + PSI_Lz_Uncorr(vPSI_Lz_lin(1:cntPSIz-1)) = vPSI_Lz_UncorrV(1:cntPSIz-1); + PSI_Z_Corr(vPSI_Z_lin(1:cntPSIrange-1)) = vPSI_Z_CorrV(1:cntPSIrange-1); + PSI_Z_Uncorr(vPSI_Z_lin(1:cntPSIrange-1)) = vPSI_Z_UncorrV(1:cntPSIrange-1); + + % PSI_Z_Corr either contains physical quantites or the alpha dose component + mPSI_Corr = (PSI_Lx_Corr .* PSI_Lz_Corr .* PSI_Z_Corr); + mPSI_UnCorr = (PSI_Lx_Uncorr .* PSI_Lz_Uncorr .* PSI_Z_Uncorr); + PSI_Corr = w' * (mPSI_Corr) * w; + PSI_UnCorr = w' * (mPSI_UnCorr) * w; + + doseExpij = (dLx .* dLz .* dZ(cntVoxel,:)); + +% % consider alpha dose and beta dose +% if pln.bioParam.bioOpt +% % create a helper vector vC +% mII_Corr = spalloc(numBixels,numBixels,1); +% mII_UnCorr = spalloc(numBixels,numBixels,1); +% mTmpCorr = PSI_Lx_Corr .* PSI_Lz_Corr; +% mTmpUnCorr = PSI_Lx_Uncorr .* PSI_Lz_Uncorr; +% dZbTmp = dZb(cntVoxel,:); +% %res = zeros(NumBixels,1); % uncomment for alternative solution + +% % only calculate the second term in BxB dimension when omega matrix is filled +% if param.CALC_OMEGA +% +% cnt = 1; +% vC = (dLx .* dLz .* dZ(cntVoxel,:)) .* w'; % dZ is in this case dZa +% numOfBixelsContainer = ceil(numBixels/10); +% CorrCont = cell(numOfBixelsContainer,1); % correlated bixel container +% UnCorrCont = cell(numOfBixelsContainer,1); % uncorrelated bixel container +% +% for l = 1:numBixels +% +% vTmp = vC.*dZbTmp(l); +% CorrCont{mod(cnt-1,numOfBixelsContainer)+1,1} = sum((mTmpCorr(l,:) .*dZbTmp)' * vTmp,2); +% UnCorrCont{mod(cnt-1,numOfBixelsContainer)+1,1} = sum((mTmpUnCorr(l,:).*dZbTmp)' * vTmp,2); +% +% % alternative solution res(g) = w' * (sum((mTmpUnCorr(g,:).*dZbTmp)' * vTmp,2)); +% if mod(cnt,numOfBixelsContainer) == 0 || cnt == numBixels +% mII_Corr(:,(ceil(cnt/numOfBixelsContainer)-1)*numOfBixelsContainer+1:cnt) = [CorrCont{1:mod(cnt-1,numOfBixelsContainer)+1,1}]; %#ok % needed for omega matrix calculation +% mII_UnCorr(:,(ceil(cnt/numOfBixelsContainer)-1)*numOfBixelsContainer+1:cnt) = [UnCorrCont{1:mod(cnt-1,numOfBixelsContainer)+1,1}]; %#ok % needed for omega matrix calculation +% end +% cnt = cnt + 1; +% end +% II_Corr = w' * mII_Corr * w; +% II_UnCorr = w' * mII_UnCorr * w; +% else +% vC = (dLx .* dLz .* dZ(cntVoxel,:)) * w; % dZ is in this case dZa +% II_Corr = 0; II_UnCorr = 0; +% for l = 1:numBixels +% II_Corr = II_Corr + w(l) * (sum(w.*((( mTmpCorr(l,:)' * dZbTmp(l)) .* dZbTmp') * vC))); +% II_UnCorr = II_UnCorr + w(l) * (sum(w.*((( mTmpUnCorr(l,:)' * dZbTmp(l)) .* dZbTmp') * vC))); +% end +% +% end +% +% mIII_approxCorr = (PSI_Lx_Corr .* PSI_Lz_Corr .* (dZb(cntVoxel,:)'*dZb(cntVoxel,:))) ; % needed for omega matrix calculation +% mIII_approxUnCorr = (PSI_Lx_Uncorr.* PSI_Lz_Uncorr .* (dZb(cntVoxel,:)'*dZb(cntVoxel,:))) ; % needed for omega matrix calculation +% III_approxCorr = (w'* (mIII_approxCorr) * w)^2; +% III_approxUnCorr = (w'* (mIII_approxUnCorr) * w)^2; +% +% PSI_Corr = PSI_Corr + 2*II_Corr + III_approxCorr; +% PSI_UnCorr = PSI_UnCorr + 2*II_UnCorr + III_approxUnCorr; +% +% end + + Var_Corr = (PSI_Corr - (doseExpij*w)^2); + Var_UnCorr = (PSI_UnCorr - (doseExpij*w)^2); + Var_FracTot = (1/numFrac) .* (Var_Corr + (Var_UnCorr * (numFrac-1))); + + if sum(isnan(Var_Corr)) > 0 || Var_Corr < 0 + matRad_dispToConsole('matRad_calcParticleVarSeriel: NaN values in std',[],'warning'); + end + + stdSingleFrac(i) = sqrt(Var_Corr); + if imag(stdSingleFrac(i)) + stdSingleFrac(i) = 0; + end + stdTotFrac(i) = sqrt(Var_FracTot); + if imag(stdTotFrac(i)) + stdTotFrac(i) = 0; + end + + % fill omega matrix + if param.CALC_OMEGA && ismember(i,omegaVoxel) + + doseExpij2 = doseExpij' * doseExpij; + PSI = (1/numFrac) .* (mPSI_Corr + (mPSI_UnCorr * (numFrac-1))); + cst{voiIndexCube(i),6}.mOmega = cst{voiIndexCube(i),6}.mOmega + (PSI - doseExpij2); + + end + + matRad_progress(cntVoxel,length(ix)); + cntVoxel = cntVoxel + 1; + +end % eof of voxel loop + + +if ~pln.bioParam.bioOpt + + resultGUI.([pln.bioParam.quantityVis 'StdSingleFrac' robSuffix]) = stdSingleFrac; + resultGUI.([pln.bioParam.quantityVis 'StdTotFrac' robSuffix]) = stdTotFrac; + +else + + if ~strcmp(pln.bioParam.quantityOpt,'effect') + matRad_dispToConsole('matRad_calcParticleVarSerial: not supported',[],'error') + end + + resultGUI.([pln.bioParam.quantityOpt 'StdSingleFrac' robSuffix]) = stdSingleFrac; + resultGUI.([pln.bioParam.quantityOpt 'StdTotFrac' robSuffix]) = stdTotFrac; + + [~,vStdRBExDsingleFrac] = matRad_effectToRBExDose(resultGUI.(['effectExp' robSuffix]), stdSingleFrac ,dij.alphaX,dij.betaX); + [vMeanRBExD,vStdRBExDtotFrac] = matRad_effectToRBExDose(resultGUI.(['effectExp' robSuffix]), stdTotFrac ,dij.alphaX,dij.betaX); + + resultGUI.([pln.bioParam.quantityVis 'Exp' robSuffix]) = reshape(vMeanRBExD,dij.dimensions); + resultGUI.([pln.bioParam.quantityVis 'StdSingleFrac' robSuffix]) = reshape(vStdRBExDsingleFrac,dij.dimensions); + resultGUI.([pln.bioParam.quantityVis 'StdTotFrac' robSuffix]) = reshape(vStdRBExDtotFrac,dij.dimensions); + +end + + + +end % eof function + diff --git a/APM/matRad_calcVar.m b/APM/matRad_calcVar.m new file mode 100644 index 000000000..6ebc17027 --- /dev/null +++ b/APM/matRad_calcVar.m @@ -0,0 +1,137 @@ +function [cst, resultGUI] = matRad_calcVar(ct, cst, stf, pln, dij, resultGUI, param) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad variance calculation function +% +% call +% [cst, resultGUI] = matRad_calcVar(ct, cst, stf, pln, dij, resultGUI) +% +% input +% resultGUI: matRads resultGUI struct +% pln: matRad plan meta information struct +% dij: matRad dij dose influence struct +% stf: matRad steering information struct +% cst: matRad critical structure struct cst +% +% output +% cst: matRad critical structure struct cst +% resultGUI: matRads resultGUI struct +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if exist('param','var') + if ~isfield(param,'logLevel') + param.logLevel = 1; + end +else + param.logLevel = 1; +end + + +%% set covariance matrices +pln.multScen.setCovarianceMatrix(ct,cst,pln,stf); + +options = matRad_probOptions(ct,cst,pln); +pln.probOpt = options.probOpt; + +if isfield(resultGUI,'wRob') + param.CALC_OMEGA = false; + fieldName = 'physicalDoseExpRob'; + if strcmp(pln.probOpt.InputUCT,'phys') + % disable covariance matrices to model biological uncertainties + pln.multScen.mCovBio = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + + elseif strcmp(pln.probOpt.InputUCT,'bio') + % disable covariance matrices to model physical uncertainties + pln.multScen.mCovRangeSys = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + pln.multScen.mCovRangeRnd = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + pln.multScen.mCovLatSys = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + pln.multScen.mCovLatRnd = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + end + + +else + param.CALC_OMEGA = true; + fieldName = 'physicalDoseExp'; + + if strcmp(pln.probOpt.InputUCT,'phys') + % disable covariance matrices to model biological uncertainties + pln.multScen.mCovBio = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + + elseif strcmp(pln.probOpt.InputUCT,'bio') + % disable covariance matrices to model physical uncertainties + pln.multScen.mCovRangeSys = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + pln.multScen.mCovRangeRnd = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + pln.multScen.mCovLatSys = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + pln.multScen.mCovLatRnd = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + end +end + +dose = resultGUI.(fieldName)(pln.probOpt.voxelList); + +% determine voxel indices for variance calculation +if pln.probOpt.relDoseThreshold > 0 + ix = pln.probOpt.voxelList(dose > pln.probOpt.relDoseThreshold * max(resultGUI.(fieldName)(:)))'; +else + ix = pln.probOpt.voxelList'; +end + + +switch pln.multScen.TYPE + + case 'apmScen' + + if strcmp(pln.probOpt.typeVarCalc,'serial') + + [cst,resultGUI] = matRad_calcParticleVarSerial(ct,cst,stf,pln,dij,resultGUI,ix,param); + + elseif strcmp(pln.probOpt.typeVarCalc,'parallel') + + [cst,resultGUI] = matRad_calcParticleVarParallel(ct,cst,stf,pln,dij,resultGUI,ix,param); + + elseif strcmp(pln.probOpt.typeVarCalc,'parallelParFor') + + [cst,resultGUI] = matRad_calcParticleVarParallelParFor(ct,cst,stf,pln,dij,resultGUI,ix,param); + + else + + matRad_dispToConsole('matRad_calcVar: This option is not implemented!',[],'error') + + end + + case {'wcScen','impScen'} + + [cst,resultGUI] = matRad_estimateParticleVar(cst,pln,dij,resultGUI); + + otherwise + + % create placeholde +end + + + + + + +end + + + + + + + + + diff --git a/APM/matRad_effectToRBExDose.m b/APM/matRad_effectToRBExDose.m new file mode 100644 index 000000000..45f9d6e3d --- /dev/null +++ b/APM/matRad_effectToRBExDose.m @@ -0,0 +1,51 @@ +function [vMeanRBExD,vStdRBExD ] = matRad_effectToRBExDose(vMeanEffect, vStdEffect ,vAlpha_x,vBeta_x) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad function to propagate the first and second moment of the biological effect +% to first and second moment of the RBE-weighted dose +% +% call +% [vMeanRBExD,vStdRBExD ] = matRad_effectToRBExDose(vMeanEffect, vStdEffect ,vAlpha_x,vBeta_x) +% +% +% input +% vMeanEffect: first raw moment of the biological effect +% vStdEffect: second central moment of the biological effect +% vAlpha_x reference radio-sensitivity parameter of photons +% vBeta_x reference radio-sensitivity parameter of photons +% +% output +% vMeanRBExD: matRad's bioParam structure containing information +% vStdRBExD about the choosen biological model +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +vMeanRBExD = zeros(size(vMeanEffect)); +vStdRBExD = zeros(size(vMeanEffect)); + +gamma = vAlpha_x./(2*vBeta_x); +ix = gamma > 0 & ~isinf(gamma) ; +x0 = vMeanEffect; +A0 = sqrt(vBeta_x(ix).^-1 .* x0(ix) + gamma(ix).^2) - gamma(ix); +A1 = ( 1 * vBeta_x(ix).^-1 )./( 2*sqrt(vBeta_x(ix).^-1 .* x0(ix) + gamma(ix).^2)); % 1 +A2 = -( 1 * vBeta_x(ix).^-2 )./( 4*((vBeta_x(ix).^-1 .* x0(ix) + gamma(ix).^2).^(3/2))) ; % 2 +A4 = -(15 * vBeta_x(ix).^-4 )./(16*((vBeta_x(ix).^-1 .* x0(ix) + gamma(ix).^2).^(7/2))) ; % 24 + +vMeanRBExD(ix) = A0 + (1/2)*A2.*vStdEffect(ix).^2 + (1/24)*A4.*vStdEffect(ix).^4; +vStdRBExD(ix) = sqrt(A1.^2 .* vStdEffect(ix).^2 + (0.5 * A2.^2 .* vStdEffect(ix).^4)); + + +end + diff --git a/APM/matRad_estimateParticleMean.m b/APM/matRad_estimateParticleMean.m new file mode 100644 index 000000000..482594d2c --- /dev/null +++ b/APM/matRad_estimateParticleMean.m @@ -0,0 +1,55 @@ +function [dij] = matRad_estimateParticleMean(cst,pln,dij) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% based on the calculated dose scenarios in the dij structure, +% the expectation value will be estimated and stored in the dij struct +% variance in the cst. +% +% call +% [dij] = matRad_calcProbParticleDoseEstimate(cst,pln,dij) +% +% input +% +% cst: matRad cst struct +% pln: matRad plan meta information struct +% dij: matRad dij struct +% +% output +% dij: matRad dij struct +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/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 ~pln.bioParam.bioOpt + fNames = {'physicalDose'}; +else + fNames = {'physicalDose','mAlphaDose','mSqrtBetaDose'}; +end + +ixDij = find(~cellfun(@isempty, dij.physicalDose))'; + +for i = 1:numel(fNames) + % create expected ij structure + dij.([fNames{1,i} 'Exp']){1} = spalloc(prod(dij.dimensions),dij.totalNumOfBixels,1); + % add up sparse matrices - should possess almost same sparsity pattern + for j = 1:pln.multScen.totNumScen + dij.([fNames{1,i} 'Exp']){1} = dij.([fNames{1,i} 'Exp']){1} + dij.([fNames{1,i}]){ixDij(j)} .* pln.multScen.scenProb(j); + end + +end + + + +end \ No newline at end of file diff --git a/APM/matRad_estimateParticleVar.m b/APM/matRad_estimateParticleVar.m new file mode 100644 index 000000000..4e0dc12ad --- /dev/null +++ b/APM/matRad_estimateParticleVar.m @@ -0,0 +1,64 @@ +function [cst,resultGUI] = matRad_estimateParticleVar(cst,pln,dij,resultGUI) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Estimate the integral variance based on the calculated dose scenarios in the dij structure, +% +% call +% [cst] = matRad_calcProbParticleDoseEstimate(cst,pln,dij) +% +% input +% +% cst: matRad cst struct +% pln: matRad plan meta information struct +% dij: matRad dij struct +% +% output +% cst: matRad cst struct +% +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +warning('code is not thoroughly tested'); + +if ~pln.bioParam.bioOpt + fNames = {'physicalDose'}; +else + fNames = {'mAlphaDose'}; + matRad_dispToConsole('Only variance in mAlphaDose is considered',[],'warning'); +end + +%% calculate the standard deviation of the dose cube + +voiIx = find(~cellfun(@isempty, cst(:,6)))'; + +for i = 1:numel(fNames) + for j = voiIx + cst{j,6}(1).mOmega = spalloc(dij.totalNumOfBixels,dij.totalNumOfBixels,1); + + % loop over scenarios and calculate the integral variance of each + % spot combination; bio bio optimization only consider std in the + % linear part of the biological effect + for k = 1:pln.multScen.totNumScen + cst{j,6}(1).mOmega = cst{j,6}(1).mOmega + ... + ((dij.(fNames{i,1}){ixDij(k)}(cst{j,4}{1},:)' * dij.(fNames{i,1}){ixDij(k)}(cst{j,4}{1},:)) * pln.multScen.scenProb(k)); + end + cst{j,6}(1).mOmega = cst{j,6}(1).mOmega - (dij.([fNames{i,1} 'Exp']){1}(cst{j,4}{1},:)' * dij.([fNames{i,1} 'Exp']){1}(cst{j,4}{1},:)); + end +end + +%% To do: calculate variance and add it to resultGUI + + +end \ No newline at end of file diff --git a/APM/matRad_getBaseDataAPM.m b/APM/matRad_getBaseDataAPM.m new file mode 100644 index 000000000..3fc8b809c --- /dev/null +++ b/APM/matRad_getBaseDataAPM.m @@ -0,0 +1,186 @@ +function [mMean, mWeight, mWidth, mLatSigma, mLUT, mDepth, vOffset, vCutOff, mCov] = matRad_getBaseDataAPM(ct,stf,pln,vTissueIndex,depthComp) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% this function converts the machine base data file into matrices and outputs +% a reduced number of APM components to increase the performance of evaluating +% the superposition of Gaussian components. +% +% call +% [mMean, mWeight, mWidth, mLatSigma, LUT, mDepth, vOffset] = matRad_getReducesCompAPM(pln,depthComp) +% +% input +% pln: matRad plan meta information struct +% depthComp: string defining the depth component (e.g. 'Z' or 'alphaDose') +% +% +% output +% mMean: means of gaussian components for each inital beam energy +% mWeight: weight of gaussian components for each inital beam energy +% mWidth: squared sigma of gaussian components for each inital beam energy +% mLatSigma: squared sigma of beam widening in water per inital beam energy +% mLUT: look up table that see which component is active in +% which depth +% +% mDepth: depth values for each initial beam energy +% vOffset: offset to account for passive beam elements per inital +% beam energy +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +visBool = 0; + +% function handle for calculating depth doses +sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ... + exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w); + +SGGauss = @(x,mu,SqSigma) 1./(sqrt(2*pi.*SqSigma)).*exp(-((x - mu).^2./(2.*SqSigma))); + +% prepare gauss base data +load([pln.radiationMode '_' pln.machine]); +numComp = length(machine.data(1).(depthComp)(1).weight); + +% define wich tissue alphaDose should be used in case of bio opt +ixBio = unique(vTissueIndex); + +if ixBio == 0 + ixBio = 1; +end + +if numel(ixBio)>1 + matRad_dispToConsole('matRad_getReducedCompAPM: multiple tissues are not supported right now',[],'error'); +end + +if ~isfield(machine.data(1),'sigma') + doubleGauss = true; +else + doubleGauss = false; +end + +%% compute SSDs +ctScen = 1; +stf = matRad_computeSSD(stf,ct,ctScen); + +%% determine lateral cutoff +cutOffLevel = 0.99; +visBoolLateralCutOff = 0; +vCutOff = []; +for i = 1:size(stf,2) + machine = matRad_calcLateralParticleCutOff(machine,cutOffLevel,stf(i),ctScen,visBoolLateralCutOff); + for j = 1:numel(machine.data) + vCutOff{j,i} = machine.data(j).LatCutOff; + end + +end + +% loop over all entries +for i = 1:length(machine.data) + + %square the sigmas + machine.data(i).(depthComp)(ixBio).width = machine.data(i).(depthComp)(ixBio).width .^2; + + %% find reduced components + if pln.probOpt.useReducedComp + + numDepths = length(machine.data(i).depths); + mContributions = zeros(numComp,numDepths); + machine.data(i).(depthComp)(ixBio).LUT = false(numComp,numDepths); + + if visBool; figure,hold on; end + % loop over the components + + for iComp = 1:numComp + vY = machine.data(i).(depthComp)(ixBio).weight(iComp) * SGGauss(machine.data(i).depths,machine.data(i).(depthComp)(ixBio).mean(iComp),machine.data(i).(depthComp)(ixBio).width(iComp)); + mContributions(iComp,:) = vY; + machine.data(i).(depthComp)(ixBio).LUT(iComp,:) = vY > (max(vY)*pln.probOpt.reducedComprelDose ); % include 99 percent of each gauss + if visBool; plot(machine.data(i).depths,vY);end + end + + machine.data(i).(depthComp)(ixBio).numDepth = numDepths; + doseRef = sumGauss(machine.data(i).depths,machine.data(i).(depthComp)(ixBio).mean,machine.data(i).(depthComp)(ixBio).width,machine.data(i).(depthComp)(ixBio).weight); + doseReduced = zeros(numel(machine.data(i).depths),1); + + for j = 1:numel(machine.data(i).depths) + doseReduced(j) = sumGauss(machine.data(i).depths(j),machine.data(i).(depthComp)(ixBio).mean((machine.data(i).(depthComp)(ixBio).LUT(:,j))),... + machine.data(i).(depthComp)(ixBio).width(machine.data(i).(depthComp)(ixBio).LUT(:,j)),... + machine.data(i).(depthComp)(ixBio).weight(machine.data(i).(depthComp)(ixBio).LUT(:,j))); + end + + if visBool + figure,hold on, + plot(machine.data(i).depths,doseRef,'b','LineWidth',2); plot(machine.data(i).depths,doseReduced,'r:','LineWidth',2), grid on, grid minor, + legend({'org profile','reduced components'}) + end + + else + machine.data(i).(depthComp)(ixBio).LUT = []; + end + +end + + +mMean = zeros(numComp,numel(machine.data)); % mean of gaussian components +mWeight = zeros(numComp,numel(machine.data)); % weight of gaussian components +mWidth = zeros(numComp,numel(machine.data)); % squared sigma of gaussian components + +mDepth = NaN*(zeros(numel(machine.data(end).depths),numel(machine.data))); % depth values (query points) of the depth dose profiles +mLUT = logical((zeros(numel(machine.data),numel(machine.data(end).depths),numComp))); % look up table to see which components are are 'activated' and deactivated +vOffset = zeros(numel(machine.data),1); % offset vector to account for the rad depth. of passive beam line elements + +mCov = cell(length(machine.data),1); + +% create matrix for lateral beam spread in water +if doubleGauss + mLatSigma = NaN*(zeros(numel(machine.data(end).depths),numel(machine.data),3)); +else + mLatSigma = NaN*(zeros(numel(machine.data(end).depths),numel(machine.data))); +end + +% loop over all entries +for i = 1:numel(machine.data) + + mMean(:,i) = machine.data(i).(depthComp)(ixBio).mean; + mWeight(:,i) = machine.data(i).(depthComp)(ixBio).weight; + mWidth(:,i) = machine.data(i).(depthComp)(ixBio).width; % with is already squared + mDepth(1:numel(machine.data(i).depths),i) = machine.data(i).depths; + + if doubleGauss + mLatSigma(1:numel(machine.data(i).depths),i,1) = machine.data(i).sigma1; + mLatSigma(1:numel(machine.data(i).depths),i,2) = machine.data(i).weight; + mLatSigma(1:numel(machine.data(i).depths),i,3) = machine.data(i).sigma2; + + else + mLatSigma(1:numel(machine.data(i).depths),i) = machine.data(i).sigma; + end + + + % save look up table + if pln.probOpt.useReducedComp + mLUT(i,1:numel(machine.data(i).depths),:) = logical(machine.data(i).(depthComp)(ixBio).LUT)'; + else + mLUT(i,1:numel(machine.data(i).depths),:) = true; + end + + vOffset(i) = machine.data(i).offset; + +end + +mLUT = permute(mLUT,[3 2 1]); + +mDepth = bsxfun(@plus,mDepth,vOffset'); + + +end % end of function + + + diff --git a/APM/matRad_getLateralDistVoxelSpots.m b/APM/matRad_getLateralDistVoxelSpots.m new file mode 100644 index 000000000..397354fa1 --- /dev/null +++ b/APM/matRad_getLateralDistVoxelSpots.m @@ -0,0 +1,98 @@ +function [ vLatDistX,vLatDistZ ] = matRad_getLateralDistVoxelSpots(ix,cubeDim,CTres,isoCenter,stf,pln,vCandidates,SAD) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% computes the lateral distance from a given voxel to the cenetral axis of defined spots +% +% call +% [ vLatDistX,vLatDistZ ] = matRad_getLateralDistVoxelSpots(ix,cubeDim,CTres,isoCenter,stf,pln,vCandidates,SAD) +% +% input +% ix: voxel index +% cubeDim: cube dimensions +% isoCenter: iso center +% stf: matRad's stf struct +% pln: matRad's pln struct +% vCandidates; spot index vector +% SAD; source to axis distance --> machine.meta.SAD +% +% output +% vLatDistX: lateral distance in x from voxel to central ray +% vLatDistZ: lateral distance in z from voxel to central ray +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +USE_MEX = false; + +if isunix && ~ismac + USE_MEX = false; % mex files are available for mac and windows but not for linux +end + +% check if mex file is available +if exist('matRad_calcGeoDistsFast_mex','file') == 3 + USE_MEX = false; +end + +[yCoordsV_vox, xCoordsV_vox, zCoordsV_vox] = ind2sub(cubeDim,ix); +xCoordsV = xCoordsV_vox(:) * CTres(1) - isoCenter(1); +yCoordsV = yCoordsV_vox(:) * CTres(2) - isoCenter(2); +zCoordsV = zCoordsV_vox(:) * CTres(3) - isoCenter(3); +coordsV = [xCoordsV yCoordsV zCoordsV]; + +maxLateralCutoffDoseCalc = 70; + +radDepthIx = 1; +counter = 1; +numRay = pln.multScen.bixelRayLUT(vCandidates); +numBeam = pln.multScen.bixelBeamLUT(vCandidates); +vLatDistX = zeros(numel(vCandidates),1); +vLatDistZ = zeros(numel(vCandidates),1); + + +for k = 1:length(vCandidates) + + % get rotation matrix + rotMat_system_T = matRad_getRotationMatrix(pln.propStf.gantryAngles(numBeam(k)),pln.propStf.couchAngles(numBeam(k))); + % rotate coordinates (1st couch around Y axis, 2nd gantry movement) + rot_coordsV = (coordsV*rotMat_system_T); + rot_coordsV(:,1) = rot_coordsV(:,1) - stf(numBeam(k)).sourcePoint_bev(1); + rot_coordsV(:,2) = rot_coordsV(:,2) - stf(numBeam(k)).sourcePoint_bev(2); + rot_coordsV(:,3) = rot_coordsV(:,3) - stf(numBeam(k)).sourcePoint_bev(3); + + if USE_MEX + + [vLatDistX(counter),vLatDistZ(counter)] = matRad_calcGeoDistsFast_mex(rot_coordsV, ... + stf(numBeam(k)).sourcePoint_bev, ... + stf(numBeam(k)).ray(numRay(k)).targetPoint_bev, ... + SAD, ... + radDepthIx, ... + maxLateralCutoffDoseCalc); + else + [vLatDistX(counter),vLatDistZ(counter)] = matRad_calcGeoDistsFast(rot_coordsV, ... + stf(numBeam(k)).sourcePoint_bev, ... + stf(numBeam(k)).ray(numRay(k)).targetPoint_bev, ... + SAD, ... + radDepthIx, ... + maxLateralCutoffDoseCalc); + end % eof MEX + + counter = counter + 1; + +end + + + + +end % eof function + diff --git a/APM/matRad_probOptions.m b/APM/matRad_probOptions.m new file mode 100644 index 000000000..9864ff34b --- /dev/null +++ b/APM/matRad_probOptions.m @@ -0,0 +1,92 @@ +function options = matRad_probOptions(ct,cst,pln) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad APM options file +% +% This script allows to define several options for analytical probabilistic +% modeling. +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +global INPUT_UNCERTAINTY; +global OUTPUT_UNCERTAINTY; +global SHOW_PARALLELTOOLBOX_WARNING; + +SHOW_PARALLELTOOLBOX_WARNING = true; + +options.probOpt.visBool = 0; +options.probOpt.numOfWorkers = 0; + +try + [FlagParallToolBoxLicensed,msg] = license('checkout','Distrib_Computing_Toolbox'); + if ~FlagParallToolBoxLicensed + if SHOW_PARALLELTOOLBOX_WARNING + matRad_dispToConsole(['Could not check out parallel computing toolbox. \n'],[],'warning'); + SHOW_PARALLELTOOLBOX_WARNING = false; + end + end +catch + FlagParallToolBoxLicensed = false; +end + +% decide which version of variance calculation should run +options.probOpt.typeVarCalc = 'parallel'; %parallel or serial +options.probOpt.numOfWorkers = 0; + +if FlagParallToolBoxLicensed + myCluster = parcluster('local'); + options.probOpt.numOfWorkers = myCluster.NumWorkers; + options.probOpt.typeVarCalc = 'parallelParFor'; +end + +options.probOpt.omegaTarget = false; % if true only target voxels are used for omega filling + +options.probOpt.LatCutOff = 3.5^2; % [sigma units] or set it to Inf if no additional cutoff should be used + % this option is only relevant for the std calculation + +if strcmp(pln.radiationMode,'protons') + options.probOpt.relDoseThreshold = 0.01; % std is only calculated for voxels having a greater relative dose than the threshold +elseif strcmp(pln.radiationMode,'carbon') + options.probOpt.relDoseThreshold = 0.01; % std is only calculated for voxels having a greater relative dose than the threshold +end + +options.probOpt.calcFullFrac = false; % calculate std for multiple number of fractions throughout the RT course +options.probOpt.calcFrac = [1 5 10 30]; % calc std for the following fractionation scheme +options.probOpt.useReducedComp = false; % use reduced gaussian components +options.probOpt.reducedComprelDose = 0.02; % if gauss component has a lower relative dose than the threshold then omitt it + +% define which sources of uncertainty should be included +if ~isempty(INPUT_UNCERTAINTY) + options.probOpt.InputUCT = INPUT_UNCERTAINTY; +else + options.probOpt.InputUCT = 'phys'; %'phys', 'bio', 'biophys' +end + +if ~isempty(OUTPUT_UNCERTAINTY) + options.probOpt.OutputUCT = OUTPUT_UNCERTAINTY; +else + options.probOpt.OutputUCT = 'phys'; %'phys', 'bio', 'biophys' +end + +%% define voxel list for which variance should be calculated +% use all voxels in the cst +%V = [cst{:,4}]; +%options.probOpt.voxelList = unique(vertcat(V{:}))'; + +% use iso center slice voxels +options.probOpt.slice = round(pln.propStf.isoCenter(1,3)/ct.resolution.z); +options.probOpt.voxelList = double(ct.cubeDim(1) * ct.cubeDim(2) * (options.probOpt.slice-1) + (1:1:(ct.cubeDim(1) * ct.cubeDim(2)))); + + + +end diff --git a/APM/parfor_progress.m b/APM/parfor_progress.m new file mode 100644 index 000000000..71bd76786 --- /dev/null +++ b/APM/parfor_progress.m @@ -0,0 +1,81 @@ +function percent = parfor_progress(N) +%PARFOR_PROGRESS Progress monitor (progress bar) that works with parfor. +% PARFOR_PROGRESS works by creating a file called parfor_progress.txt in +% your working directory, and then keeping track of the parfor loop's +% progress within that file. This workaround is necessary because parfor +% workers cannot communicate with one another so there is no simple way +% to know which iterations have finished and which haven't. +% +% PARFOR_PROGRESS(N) initializes the progress monitor for a set of N +% upcoming calculations. +% +% PARFOR_PROGRESS updates the progress inside your parfor loop and +% displays an updated progress bar. +% +% PARFOR_PROGRESS(0) deletes parfor_progress.txt and finalizes progress +% bar. +% +% To suppress output from any of these functions, just ask for a return +% variable from the function calls, like PERCENT = PARFOR_PROGRESS which +% returns the percentage of completion. +% +% Example: +% +% N = 100; +% parfor_progress(N); +% parfor i=1:N +% pause(rand); % Replace with real code +% parfor_progress; +% end +% parfor_progress(0); +% +% See also PARFOR. + +% By Jeremy Scheff - jdscheff@gmail.com - http://www.jeremyscheff.com/ + +narginchk(0, 1); + +if nargin < 1 + N = -1; +end + +percent = 0; +w = 50; % Width of progress bar + +if N > 0 + f = fopen('parfor_progress.txt', 'w'); + if f<0 + error('Do you have write permissions for %s?', pwd); + end + fprintf(f, '%d\n', N); % Save N at the top of progress.txt + fclose(f); + + if nargout == 0 + disp([' 0%[>', repmat(' ', 1, w), ']']); + end +elseif N == 0 + delete('parfor_progress.txt'); + percent = 100; + + if nargout == 0 + disp([repmat(char(8), 1, (w+9)), char(10), '100%[', repmat('=', 1, w+1), ']']); + end +else + if ~exist('parfor_progress.txt', 'file') + error('parfor_progress.txt not found. Run PARFOR_PROGRESS(N) before PARFOR_PROGRESS to initialize parfor_progress.txt.'); + end + + f = fopen('parfor_progress.txt', 'a'); + fprintf(f, '1\n'); + fclose(f); + + f = fopen('parfor_progress.txt', 'r'); + progress = fscanf(f, '%d'); + fclose(f); + percent = (length(progress)-1)/progress(1)*100; + + if nargout == 0 + perc = sprintf('%3.0f%%', percent); % 4 characters wide, percentage + disp([repmat(char(8), 1, (w+9)), char(10), perc, '[', repmat('=', 1, round(percent*w/100)), '>', repmat(' ', 1, w - round(percent*w/100)), ']']); + end +end diff --git a/carbon_GenericAPM.mat b/carbon_GenericAPM.mat new file mode 100644 index 000000000..02357037f Binary files /dev/null and b/carbon_GenericAPM.mat differ diff --git a/examples/matRad_example9_C_APM.m b/examples/matRad_example9_C_APM.m new file mode 100644 index 000000000..a9cb82bf2 --- /dev/null +++ b/examples/matRad_example9_C_APM.m @@ -0,0 +1,273 @@ +%% Example: Generate your own phantom geometry +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% +% In this example we will show how to calculate the variance of a carbon +% ion treatment plan and how to perform probabilistic optimization by +% optimization the expectation value of a squared deviation objective +clc, clear, close all + +%% Create a CT image series +xDim = 130; +yDim = 130; +zDim = 50; + +ct.cubeDim = [xDim yDim zDim]; +ct.resolution.x = 1.7; +ct.resolution.y = 1.7; +ct.resolution.z = 2; +ct.numOfCtScen = 1; + +% create an ct image series with zeros - it will be filled later +ct.cubeHU{1} = ones(ct.cubeDim) * -1000; + +%% Create the VOI data for the phantom +% Now we define structures a contour for the phantom and a target + +ixOAR = 1; +ixPTV = 2; + +% define general VOI properties +cst{ixOAR,1} = 0; +cst{ixOAR,2} = 'contour'; +cst{ixOAR,3} = 'OAR'; + +cst{ixPTV,1} = 1; +cst{ixPTV,2} = 'target'; +cst{ixPTV,3} = 'TARGET'; + +% define optimization parameter for both VOIs +cst{ixOAR,5}.TissueClass = 1; +cst{ixOAR,5}.alphaX = 0.1000; +cst{ixOAR,5}.betaX = 0.0500; +cst{ixOAR,5}.Priority = 2; +cst{ixOAR,5}.Visible = 1; +cst{ixOAR,6}.type = 'square overdosing'; +cst{ixOAR,6}.dose = 20; +cst{ixOAR,6}.penalty = 50; +cst{ixOAR,6}.EUD = NaN; +cst{ixOAR,6}.volume = NaN; +cst{ixOAR,6}.coverage = NaN; +cst{ixOAR,6}.robustness = 'none'; + +cst{ixPTV,5}.TissueClass = 1; +cst{ixPTV,5}.alphaX = 0.1000; +cst{ixPTV,5}.betaX = 0.0500; +cst{ixPTV,5}.Priority = 1; +cst{ixPTV,5}.Visible = 1; +cst{ixPTV,6}.type = 'square deviation'; +cst{ixPTV,6}.dose = 60; +cst{ixPTV,6}.penalty = 300; +cst{ixPTV,6}.EUD = NaN; +cst{ixPTV,6}.volume = NaN; +cst{ixPTV,6}.coverage = NaN; +cst{ixPTV,6}.robustness = 'none'; + + +%% Lets create either a cubic or a spheric phantom +TYPE = 'spheric'; % either 'cubic' or 'spheric' + +% first the OAR +cubeHelper = zeros(ct.cubeDim); + +switch TYPE + + case {'cubic'} + + xLowOAR = round(xDim/2 - xDim/4); + xHighOAR = round(xDim/2 + xDim/4); + yLowOAR = round(yDim/2 - yDim/4); + yHighOAR = round(yDim/2 + yDim/4); + zLowOAR = round(zDim/2 - zDim/4); + zHighOAR = round(zDim/2 + zDim/4); + + for x = xLowOAR:1:xHighOAR + for y = yLowOAR:1:yHighOAR + for z = zLowOAR:1:zHighOAR + cubeHelper(x,y,z) = 1; + end + end + end + + case {'spheric'} + + radiusOAR = xDim/6; + + for x = 1:xDim + for y = 1:yDim + for z = 1:zDim + currPost = [x y z] - round([ct.cubeDim./2]); + if sqrt(sum(currPost.^2)) < radiusOAR + cubeHelper(x,y,z) = 1; + end + end + end + end + +end + +% extract the voxel indices and save it in the cst +cst{ixOAR,4}{1} = find(cubeHelper); + + +% second the PTV +cubeHelper = zeros(ct.cubeDim); + +switch TYPE + + case {'cubic'} + + xLowPTV = round(xDim/2 - xDim/8); + xHighPTV = round(xDim/2 + xDim/8); + yLowPTV = round(yDim/2 - yDim/8); + yHighPTV = round(yDim/2 + yDim/8); + zLowPTV = round(zDim/2 - zDim/8); + zHighPTV = round(zDim/2 + zDim/8); + + cubeHelper = zeros(ct.cubeDim); + + for x = xLowPTV:1:xHighPTV + for y = yLowPTV:1:yHighPTV + for z = zLowPTV:1:zHighPTV + cubeHelper(x,y,z) = 1; + end + end + end + + case {'spheric'} + + radiusPTV = xDim/16; + + for x = 1:xDim + for y = 1:yDim + for z = 1:zDim + currPost = [x y z] - round([ct.cubeDim./2]); + if sqrt(sum(currPost.^2)) < radiusPTV + cubeHelper(x,y,z) = 1; + end + end + end + end + +end + + + +% extract the voxel indices and save it in the cst +cst{ixPTV,4}{1} = find(cubeHelper); + +% now we have ct data and cst data for a new phantom +display(ct); +display(cst); + +%% Assign relative electron densities +vIxOAR = cst{ixOAR,4}{1}; +vIxPTV = cst{ixPTV,4}{1}; + +ct.cubeHU{1}(vIxOAR) = 1; % assign HU of water +ct.cubeHU{1}(vIxPTV) = 1; % assign HU of water + +%load(['patients' filesep 'BOXPHANTOM_TINY.mat']) +%% Treatment Plan +% The next step is to define your treatment plan labeled as 'pln'. This +% structure requires input from the treatment planner and defines the most +% important cornerstones of your treatment plan. +%% +% First of all, we need to define what kind of radiation modality we would +% like to use. Possible values are photons, protons or carbon. In this +% example we would like to use photons for treatment planning. Next, we +% need to define a treatment machine to correctly load the corresponding +% base data. matRad features generic base data in the file +% 'photons_Generic.mat'; consequently the machine has to be set to 'Generic' +pln.radiationMode = 'carbon'; +pln.machine = 'GenericAPM'; + +%% +% Define the biological optimization model for treatment planning along +% with the quantity that should be used for optimization. Possible model values +% are: +% 'none': physical optimization; +% 'constRBE': constant RBE of 1.1; +% 'MCN': McNamara-variable RBE model for protons; +% 'WED': Wedenberg-variable RBE model for protons +% 'LEM': Local Effect Model +% and possible quantityOpt are 'physicalDose', 'effect' or 'RBExD'. +modelName = 'LEM'; +quantityOpt = 'effect'; + +%% +% The remaining plan parameters are set like in the previous example files +pln.numOfFractions = 20; +pln.propStf.gantryAngles = [0 90]; +pln.propStf.couchAngles = [0 0]; +pln.propStf.bixelWidth = 5; +pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles); +pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0); +pln.propOpt.runDAO = 0; +pln.propOpt.runSequencing = 0; + +% global INPUT_UNCERTAINTY; +% INPUT_UNCERTAINTY = 'phys'; %'phys', 'bio', 'biophys' + +% retrieve bio model parameters +pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); + +scenGenType = 'apmScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen' 'apmScen' +% retrieve nominal scenario for dose calculation and optimziation +pln.multScen = matRad_multScen(ct,scenGenType); + +%% Generate Beam Geometry STF +stf = matRad_generateStf(ct,cst,pln); + +%% dose calculation +[cst,dij] = matRad_calcDose(ct,stf,pln,cst); + +%% Inverse Optimization for intensity-modulated photon therapy +% The goal of the fluence optimization is to find a set of bixel/spot +% weights which yield the best possible dose distribution according to the +% clinical objectives and constraints underlying the radiation treatment. +resultGUI = matRad_fluenceOptimization(dij,cst,pln); + + %% calculate variance +[cst,resultGUI] = matRad_calcVar(ct,cst,stf,pln,dij,resultGUI); + +%% +cst{1,6}.robustness = 'PROB'; +cst{2,6}.robustness = 'PROB'; +for i= 1:size(cst,1) + cst{i,4}{1} = cstOrg{i,4}{1}; +end +param.w = resultGUI.w; +resultGUIrob = matRad_fluenceOptimization(dij,cst,pln,param); + +%% calculate variance of robust pencil beam weights +[~,resultGUIrob] = matRad_calcVar(ct,cst,stf,pln,dij,resultGUIrob); + +%% plot everthing +plane = 3; +slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z); +doseWindowExp = [0 max([max(max(resultGUI.RBExDExp(:,:,slice))) max(max(resultGUIrob.RBExDExpRob(:,:,slice)))])]*1.05; +doseWindowStd = [0 max([max(max(resultGUI.RBExDStdSingleFrac(:,:,slice))) max(max(resultGUIrob.RBExDStdSingleFracRob(:,:,slice)))])]*1.05; + +figure,title('phantom plan') +subplot(221),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExDExp,plane,slice,[],[],colorcube,[],doseWindowExp,[]); +subplot(222),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExDStdSingleFrac,plane,slice,[],[],colorcube,[],doseWindowStd,[]); +subplot(223),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrob.RBExDExpRob,plane,slice,[],[],colorcube,[],doseWindowExp,[]); +subplot(224),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrob.RBExDStdSingleFracRob,plane,slice,[],[],colorcube,[],doseWindowStd,[]); + +%% append results and show them in GUI +resultGUI = matRad_appendResultGUI(resultGUI,resultGUIrob,0); +matRadGUI + diff --git a/examples/matRad_example9_p_APM.m b/examples/matRad_example9_p_APM.m new file mode 100644 index 000000000..2f812101e --- /dev/null +++ b/examples/matRad_example9_p_APM.m @@ -0,0 +1,272 @@ +%% Example: Generate your own phantom geometry +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% +% In this example we will show how to calculate the variance of a proton +% treatment plan and how to perform probabilistic optimization by +% optimization the expectation value of a squared deviation objective +clc, clear, close all + +%% Create a CT image series +xDim = 160; +yDim = 160; +zDim = 80; + +ct.cubeDim = [xDim yDim zDim]; +ct.resolution.x = 3; +ct.resolution.y = 3; +ct.resolution.z = 3; +ct.numOfCtScen = 1; + +% create an ct image series with zeros - it will be filled later +ct.cubeHU{1} = ones(ct.cubeDim) * -1000; + +%% Create the VOI data for the phantom +% Now we define structures a contour for the phantom and a target + +ixOAR = 1; +ixPTV = 2; + +% define general VOI properties +cst{ixOAR,1} = 0; +cst{ixOAR,2} = 'contour'; +cst{ixOAR,3} = 'OAR'; + +cst{ixPTV,1} = 1; +cst{ixPTV,2} = 'target'; +cst{ixPTV,3} = 'TARGET'; + +% define optimization parameter for both VOIs +cst{ixOAR,5}.TissueClass = 1; +cst{ixOAR,5}.alphaX = 0.1000; +cst{ixOAR,5}.betaX = 0.0500; +cst{ixOAR,5}.Priority = 2; +cst{ixOAR,5}.Visible = 1; +cst{ixOAR,6}.type = 'square overdosing'; +cst{ixOAR,6}.dose = 20; +cst{ixOAR,6}.penalty = 50; +cst{ixOAR,6}.EUD = NaN; +cst{ixOAR,6}.volume = NaN; +cst{ixOAR,6}.coverage = NaN; +cst{ixOAR,6}.robustness = 'none'; + +cst{ixPTV,5}.TissueClass = 1; +cst{ixPTV,5}.alphaX = 0.1000; +cst{ixPTV,5}.betaX = 0.0500; +cst{ixPTV,5}.Priority = 1; +cst{ixPTV,5}.Visible = 1; +cst{ixPTV,6}.type = 'square deviation'; +cst{ixPTV,6}.dose = 60; +cst{ixPTV,6}.penalty = 300; +cst{ixPTV,6}.EUD = NaN; +cst{ixPTV,6}.volume = NaN; +cst{ixPTV,6}.coverage = NaN; +cst{ixPTV,6}.robustness = 'none'; + + +%% Lets create either a cubic or a spheric phantom +TYPE = 'cubic'; % either 'cubic' or 'spheric' + +% first the OAR +cubeHelper = zeros(ct.cubeDim); + +switch TYPE + + case {'cubic'} + fac = 15; + xLowOAR = round(xDim/2 - xDim/fac); + xHighOAR = round(xDim/2 + xDim/fac); + yLowOAR = round(yDim/2 - yDim/fac); + yHighOAR = round(yDim/2 + yDim/fac); + zLowOAR = round(zDim/2 - zDim/fac); + zHighOAR = round(zDim/2 + zDim/fac); + + for x = xLowOAR:1:xHighOAR + for y = yLowOAR:1:yHighOAR + for z = zLowOAR:1:zHighOAR + cubeHelper(x,y,z) = 1; + end + end + end + + case {'spheric'} + + radiusOAR = xDim/6; + + for x = 1:xDim + for y = 1:yDim + for z = 1:zDim + currPost = [x y z] - round([ct.cubeDim./2]); + if sqrt(sum(currPost.^2)) < radiusOAR + cubeHelper(x,y,z) = 1; + end + end + end + end + +end + +% extract the voxel indices and save it in the cst +cst{ixOAR,4}{1} = find(cubeHelper); + + +% second the PTV +cubeHelper = zeros(ct.cubeDim); + +switch TYPE + + case {'cubic'} + + fac = 28; + xLowPTV = round(xDim/2 - xDim/fac); + xHighPTV = round(xDim/2 + xDim/fac); + yLowPTV = round(yDim/2 - yDim/fac); + yHighPTV = round(yDim/2 + yDim/fac); + zLowPTV = round(zDim/2 - zDim/fac); + zHighPTV = round(zDim/2 + zDim/fac); + + cubeHelper = zeros(ct.cubeDim); + + for x = xLowPTV:1:xHighPTV + for y = yLowPTV:1:yHighPTV + for z = zLowPTV:1:zHighPTV + cubeHelper(x,y,z) = 1; + end + end + end + + case {'spheric'} + + radiusPTV = xDim/16; + + for x = 1:xDim + for y = 1:yDim + for z = 1:zDim + currPost = [x y z] - round([ct.cubeDim./2]); + if sqrt(sum(currPost.^2)) < radiusPTV + cubeHelper(x,y,z) = 1; + end + end + end + end + +end + + + +% extract the voxel indices and save it in the cst +cst{ixPTV,4}{1} = find(cubeHelper); + +% now we have ct data and cst data for a new phantom +display(ct); +display(cst); + +%% Assign relative electron densities +vIxOAR = cst{ixOAR,4}{1}; +vIxPTV = cst{ixPTV,4}{1}; + +ct.cubeHU{1}(vIxOAR) = 1; % assign HU of water +ct.cubeHU{1}(vIxPTV) = 1; % assign HU of water + +cstOrg = cst; +%% Treatment Plan +% The next step is to define your treatment plan labeled as 'pln'. This +% structure requires input from the treatment planner and defines the most +% important cornerstones of your treatment plan. +%% +% First of all, we need to define what kind of radiation modality we would +% like to use. Possible values are photons, protons or carbon. In this +% example we would like to use photons for treatment planning. Next, we +% need to define a treatment machine to correctly load the corresponding +% base data. matRad features generic base data in the file +% 'photons_Generic.mat'; consequently the machine has to be set to 'Generic' +pln.radiationMode = 'protons'; +pln.machine = 'GenericAPM'; + +%% +% Define the biological optimization model for treatment planning along +% with the quantity that should be used for optimization. Possible model values +% are: +% 'none': physical optimization; +% 'constRBE': constant RBE of 1.1; +% 'MCN': McNamara-variable RBE model for protons; +% 'WED': Wedenberg-variable RBE model for protons +% 'LEM': Local Effect Model +% and possible quantityOpt are 'physicalDose', 'effect' or 'RBExD'. +modelName = 'none'; +quantityOpt = 'physicalDose'; + +%% +% The remaining plan parameters are set like in the previous example files +pln.numOfFractions = 20; +pln.propStf.gantryAngles = [0 90]; +pln.propStf.couchAngles = [0 0]; +pln.propStf.bixelWidth = 5; +pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles); +pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0); +pln.propOpt.runDAO = 0; +pln.propOpt.runSequencing = 0; + +% retrieve bio model parameters +pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); + +scenGenType = 'apmScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen' 'apmScen' +% retrieve nominal scenario for dose calculation and optimziation +pln.multScen = matRad_multScen(ct,scenGenType); + +%% Generate Beam Geometry STF +stf = matRad_generateStf(ct,cst,pln); + +%% dose calculation +[cst,dij] = matRad_calcDose(ct,stf,pln,cst); + +%% Inverse Optimization for intensity-modulated photon therapy +% The goal of the fluence optimization is to find a set of bixel/spot +% weights which yield the best possible dose distribution according to the +% clinical objectives and constraints underlying the radiation treatment. +resultGUI = matRad_fluenceOptimization(dij,cst,pln); +matRadGUI + %% calculate variance +[cst,resultGUI] = matRad_calcVar(ct,cst,stf,pln,dij,resultGUI); + +%% +cst{1,6}.robustness = 'PROB'; +cst{2,6}.robustness = 'PROB'; +for i= 1:size(cst,1) + cst{i,4}{1} = cstOrg{i,4}{1}; +end +param.w = resultGUI.w; +resultGUIrob = matRad_fluenceOptimization(dij,cst,pln,param); + +%% calculate variance of robust pencil beam weights +[~,resultGUIrob] = matRad_calcVar(ct,cst,stf,pln,dij,resultGUIrob); + +%% plot everthing +plane = 3; +slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z); +doseWindowExp = [0 max([max(max(resultGUI.physicalDoseExp(:,:,slice))) max(max(resultGUIrob.physicalDoseExpRob(:,:,slice)))])]*1.05; +doseWindowStd = [0 max([max(max(resultGUI.physicalDoseStdSingleFrac(:,:,slice))) max(max(resultGUIrob.physicalDoseStdSingleFracRob(:,:,slice)))])]*1.05; + +figure,title('phantom plan') +subplot(221),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.physicalDoseExp,plane,slice,[],[],colorcube,[],doseWindowExp,[]); +subplot(222),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.physicalDoseStdSingleFrac,plane,slice,[],[],colorcube,[],doseWindowStd,[]); +subplot(223),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrob.physicalDoseExpRob,plane,slice,[],[],colorcube,[],doseWindowExp,[]); +subplot(224),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrob.physicalDoseStdSingleFracRob,plane,slice,[],[],colorcube,[],doseWindowStd,[]); + +%% append results and show them in GUI +resultGUI = matRad_appendResultGUI(resultGUI,resultGUIrob,0); +matRadGUI + + diff --git a/matRad_addMargin.m b/matRad_addMargin.m index 63017f730..9a052e9a4 100644 --- a/matRad_addMargin.m +++ b/matRad_addMargin.m @@ -1,4 +1,4 @@ -function mVOIEnlarged = matRad_addMargin(mVOI,cst,vResolution,vMargin,bDiaElem) +function mVOIEnlarged = matRad_addMargin(mVOI,cst,vResolution,vMargin,bDiaElem,bSubstract) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % matRad add margin function % @@ -40,6 +40,10 @@ error('not enough input parameters specified for matRad_addMargin'); end +if ~exist('bSubstract','var') + bSubstract = false; +end + % generate voi cube for patient surface/patient skin voiSurface = zeros(size(mVOI)); idx = [cst{:,4}]; @@ -55,15 +59,19 @@ [xUpperLim,yUpperLim,zUpperLim]=size(mVOI); for Cnt = 1:max(voxelMargins) - - % for multiple loops consider just added margin - NewIdx = setdiff(find(mVOIEnlarged),NewIdx); - [xCoord, yCoord, zCoord] = ind2sub(size(mVOIEnlarged),NewIdx); - - % find indices on border and take out - borderIx = xCoord==1 | xCoord==xUpperLim | ... - yCoord==1 | yCoord==yUpperLim | ... - zCoord==1 | zCoord==zUpperLim; + + if ~bSubstract + % for multiple loops consider just added margin + NewIdx = setdiff(find(mVOIEnlarged),NewIdx); + else + NewIdx = (find(mVOIEnlarged)); + end + [xCoord, yCoord, zCoord] = ind2sub(size(mVOIEnlarged),NewIdx); + + % find indices on border and take out + borderIx = xCoord==1 | xCoord==xUpperLim | ... + yCoord==1 | yCoord==yUpperLim | ... + zCoord==1 | zCoord==zUpperLim; xCoord(borderIx) = []; yCoord(borderIx) = []; @@ -87,7 +95,17 @@ % check if new indices are part of voiSurfaceIdx bWithinPatient = ismember(newIx,voiSurfaceIdx); - mVOIEnlarged(newIx(bWithinPatient)) = 1; + if bSubstract + mVOIEnlargedTmp = mVOIEnlarged; + + mVOIEnlargedTmp(newIx(bWithinPatient)) = 0; + mVOIEnlargedLoop = mVOIEnlarged; + mVOIEnlarged = zeros(size(mVOI)); + mVOIEnlarged(setdiff(find(mVOIEnlargedLoop),find(mVOIEnlargedTmp))) = 1; + else + mVOIEnlarged(newIx(bWithinPatient)) = 1; + end + end end diff --git a/matRad_calcCubes.m b/matRad_calcCubes.m index efd2b1e19..fe3025a71 100644 --- a/matRad_calcCubes.m +++ b/matRad_calcCubes.m @@ -1,4 +1,4 @@ -function resultGUI = matRad_calcCubes(w,dij,cst,scenNum) +function resultGUI = matRad_calcCubes(w,dij,cst,scenNum,calcBeamDose) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % matRad computation of all cubes for the resultGUI struct which is used % as result container and for visualization in matRad's GUI @@ -19,7 +19,7 @@ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% Copyright 2015 the matRad development team. +% Copyright 2018 the matRad development team. % % This file is part of the matRad project. It is subject to the license % terms in the LICENSE file found in the top-level directory of this @@ -30,86 +30,129 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -if nargin < 4 +if ~exist('scenNum','var') || isempty(scenNum) scenNum = 1; end +if ~exist('calcBeamDose','var') || isempty(calcBeamDose) + calcBeamDose = true; +end + resultGUI.w = w; -% get bixel - beam correspondence -for i = 1:dij.numOfBeams - beamInfo(i).suffix = ['_beam', num2str(i)]; - beamInfo(i).logIx = (dij.beamNum == i); +% indicating the nominal and expected dose influence matrix +if scenNum > 1 + probQuant = {''}; +else + probQuant = {'','Exp'}; +end + +% get bixel - beam correspondence +if calcBeamDose + for i = 1:dij.numOfBeams + beamInfo(i).suffix = ['_beam', num2str(i)]; + beamInfo(i).logIx = (dij.beamNum == i); + end + beamInfo(dij.numOfBeams+1).suffix = ''; + beamInfo(dij.numOfBeams+1).logIx = true(size(w)); +else + beamInfo(1).suffix = ''; + beamInfo(1).logIx = true(size(w)); end -beamInfo(dij.numOfBeams+1).suffix = ''; -beamInfo(dij.numOfBeams+1).logIx = true(size(w)); % compute physical dose for all beams individually and together -for i = 1:length(beamInfo) - resultGUI.(['physicalDose', beamInfo(i).suffix]) = reshape(full(dij.physicalDose{scenNum} * (resultGUI.w .* beamInfo(i).logIx)),dij.dimensions); +for j = 1:length(probQuant) + for i = 1:length(beamInfo) + resultGUI.(['physicalDose' probQuant{1,j} beamInfo(i).suffix]) = reshape(full(dij.(['physicalDose' probQuant{1,j}]){scenNum} *... + (resultGUI.w .* beamInfo(i).logIx)),dij.dimensions); + end end % consider RBE for protons -if isfield(dij,'RBE') - for i = 1:length(beamInfo) - resultGUI.(['RBExD', beamInfo(i).suffix]) = resultGUI.(['physicalDose', beamInfo(i).suffix]) * dij.RBE; - end +for j = 1:length(probQuant) + if isfield(dij,'RBE') + for i = 1:length(beamInfo) + resultGUI.(['RBExD' probQuant{1,j} beamInfo(i).suffix]) = resultGUI.(['physicalDose' probQuant{1,j} beamInfo(i).suffix]) * dij.RBE; + end + end end % consider VOI priorities [cst,resultGUI.overlapCube] = matRad_setOverlapPriorities(cst,dij.dimensions); % consider LET -if isfield(dij,'mLETDose') - for i = 1:length(beamInfo) - LETDoseCube = dij.mLETDose{scenNum} * (resultGUI.w .* beamInfo(i).logIx); - resultGUI.(['LET', beamInfo(i).suffix]) = zeros(dij.dimensions); - ix = resultGUI.(['physicalDose', beamInfo(i).suffix]) > 0; - resultGUI.(['LET', beamInfo(i).suffix])(ix) = LETDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix); - end +for j = 1:length(probQuant) + if isfield(dij,['mLETDose' probQuant{1,j}]) && scenNum == 1 + for i = 1:length(beamInfo) + LETDoseCube = dij.(['mLETDose' probQuant{1,j}]){scenNum} * (resultGUI.w .* beamInfo(i).logIx); + resultGUI.(['LET' probQuant{1,j} beamInfo(i).suffix]) = zeros(dij.dimensions); + ix = resultGUI.(['physicalDose' probQuant{1,j} beamInfo(i).suffix]) > 0; + resultGUI.(['LET' probQuant{1,j} beamInfo(i).suffix])(ix) = LETDoseCube(ix)./resultGUI.(['physicalDose' probQuant{1,j} beamInfo(i).suffix])(ix); + end + end end +for j = 1:length(probQuant) + % consider biological optimization for carbon ions + if isfield(dij,'mAlphaDose') && isfield(dij,'mSqrtBetaDose') -% consider biological optimization for carbon ions -if isfield(dij,'mAlphaDose') && isfield(dij,'mSqrtBetaDose') - - a_x = zeros(dij.dimensions); - b_x = zeros(dij.dimensions); + a_x = zeros(dij.dimensions); + b_x = zeros(dij.dimensions); - for j = 1:size(cst,1) - % Only take OAR or target VOI. - if isequal(cst{j,3},'OAR') || isequal(cst{j,3},'TARGET') - a_x(cst{j,4}{1}) = cst{j,5}.alphaX; - b_x(cst{j,4}{1}) = cst{j,5}.betaX; - end - end + for l = 1:size(cst,1) + % Only take OAR or target VOI. + if isequal(cst{l,3},'OAR') || isequal(cst{l,3},'TARGET') + a_x(cst{l,4}{1}) = cst{l,5}.alphaX; + b_x(cst{l,4}{1}) = cst{l,5}.betaX; + end + end - ix = b_x~=0; + ix = b_x~=0; - for i = 1:length(beamInfo) - wBeam = (resultGUI.w .* beamInfo(i).logIx); - resultGUI.(['effect', beamInfo(i).suffix]) = full(dij.mAlphaDose{scenNum} * wBeam + (dij.mSqrtBetaDose{scenNum} * wBeam).^2); - resultGUI.(['effect', beamInfo(i).suffix]) = reshape(resultGUI.(['effect', beamInfo(i).suffix]),dij.dimensions); - - resultGUI.(['RBExD', beamInfo(i).suffix]) = zeros(size(resultGUI.(['effect', beamInfo(i).suffix]))); - resultGUI.(['RBExD', beamInfo(i).suffix])(ix) = (sqrt(a_x(ix).^2 + 4 .* b_x(ix) .* resultGUI.(['effect', beamInfo(i).suffix])(ix)) - a_x(ix))./(2.*b_x(ix)); + for i = 1:length(beamInfo) + wBeam = (resultGUI.w .* beamInfo(i).logIx); + resultGUI.(['effect' probQuant{1,j} beamInfo(i).suffix]) = full(dij.(['mAlphaDose' probQuant{1,j}]){scenNum} * wBeam ... + + (dij.(['mSqrtBetaDose' probQuant{1,j}]){scenNum} * wBeam).^2); + resultGUI.(['effect' probQuant{1,j} beamInfo(i).suffix]) = reshape(resultGUI.(['effect' probQuant{1,j} beamInfo(i).suffix]),dij.dimensions); - resultGUI.(['RBE', beamInfo(i).suffix]) = resultGUI.(['RBExD', beamInfo(i).suffix])./resultGUI.(['physicalDose', beamInfo(i).suffix]); + resultGUI.(['RBExD' probQuant{1,j} beamInfo(i).suffix]) = zeros(size(resultGUI.(['effect' probQuant{1,j} beamInfo(i).suffix]))); + resultGUI.(['RBExD' probQuant{1,j} beamInfo(i).suffix])(ix) = (sqrt(a_x(ix).^2 + 4 .* b_x(ix) .* resultGUI.(['effect' probQuant{1,j} beamInfo(i).suffix])(ix)) - a_x(ix))./(2.*b_x(ix)); - resultGUI.(['alpha', beamInfo(i).suffix]) = zeros(dij.dimensions); - resultGUI.(['beta', beamInfo(i).suffix]) = zeros(dij.dimensions); + resultGUI.(['RBE' probQuant{1,j} beamInfo(i).suffix]) = resultGUI.(['RBExD' probQuant{1,j} beamInfo(i).suffix])./resultGUI.(['physicalDose' probQuant{1,j} beamInfo(i).suffix]); - AlphaDoseCube = full(dij.mAlphaDose{scenNum} * wBeam); - resultGUI.(['alpha', beamInfo(i).suffix])(ix) = AlphaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix); + resultGUI.(['alpha', beamInfo(i).suffix]) = zeros(dij.dimensions); + resultGUI.(['beta', beamInfo(i).suffix]) = zeros(dij.dimensions); - SqrtBetaDoseCube = full(dij.mSqrtBetaDose{scenNum} * wBeam); - resultGUI.(['beta', beamInfo(i).suffix])(ix) = (SqrtBetaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix)).^2; - end -end + AlphaDoseCube = full(dij.mAlphaDose{scenNum} * wBeam); + resultGUI.(['alpha', beamInfo(i).suffix])(ix) = AlphaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix); + SqrtBetaDoseCube = full(dij.mSqrtBetaDose{scenNum} * wBeam); + resultGUI.(['beta', beamInfo(i).suffix])(ix) = (SqrtBetaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix)).^2; + end + end +end % group similar fields together resultGUI = orderfields(resultGUI); +% rename fields +robID = ''; +for i = 1:size(cst,1) + for j = 1:numel(cst{i,6}) + if ~strcmp(cst{i,6}(j).robustness,'none') + robID = 'Rob'; break; + end + end +end + +fields = fieldnames(resultGUI); +for i=1:numel(fields) + if ~strcmp(fields{i,1},'overlapCube') && ~isempty(robID) + resultGUI.([fields{i,1} robID]) = resultGUI.(fields{i,1}); + resultGUI = rmfield(resultGUI,fields{i,1}); + end +end + + end diff --git a/matRad_calcDose.m b/matRad_calcDose.m new file mode 100644 index 000000000..05f05a6c7 --- /dev/null +++ b/matRad_calcDose.m @@ -0,0 +1,149 @@ +function [cst,dijExp] = matRad_calcDose(ct,stf,pln,cst,param) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad dose influence calculation wrapper +% +% call +% dij = matRad_calcParticleDose(ct,stf,pln,cst) +% +% input +% ct: ct cube +% stf: matRad steering information struct +% pln: matRad plan meta information struct +% cst: matRad cst struct +% param: (optional) structure defining additional parameter +% param.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 +% cst: matRad cst struct +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +addpath('APM') + +if exist('param','var') + if ~isfield(param,'logLevel') + param.logLevel = 1; + end + % default: dose influence matrix computation + if ~isfield(param,'calcDoseDirect') + param.calcDoseDirect = false; + end +else + param.calcDoseDirect = false; + param.subIx = []; + param.logLevel = 1; +end + +%% set covariance matrices +pln.multScen.setCovarianceMatrix(ct,cst,pln,stf); + +%% set nominal dose calculation to true +param.nomDoseCalc = true; % determines if the nominal or the expected dose should be calcuated + +% calculate scenario doses or nominal doses +if strcmp(pln.radiationMode,'photons') + dij = matRad_calcPhotonDose(ct,stf,pln,cst,param); + %dij = matRad_calcPhotonDoseVmc(ct,stf,pln,cst); +elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'helium') || strcmp(pln.radiationMode,'carbon') + dij = matRad_calcParticleDose(ct,stf,pln,cst,param); +end + +%% start calculating the expected dose influence matrices +matRad_dispToConsole('Calculating probabilistic quantities for optimization ...\n',param,'info'); + +% get fieldnames +if ~pln.bioParam.bioOpt + fNames = {'physicalDose'}; +else + fNames = {'physicalDose','mAlphaDose','mSqrtBetaDose'}; +end + +if isfield(pln,'propDoseCalc') + if isfield(pln.propDoseCalc,'calcLET') + if pln.propDoseCalc.calcLET + fNames{1,end+1} = 'mLETDose'; + end + end +end + +% perform either analytical calculation, estimation or do nothing +switch pln.multScen.TYPE + + % perform analytical first moment calculation + case 'apmScen' + + param.nomDoseCalc = false; + options = matRad_probOptions(ct,cst,pln); + pln.probOpt = options.probOpt; + + % calcualted expected dose influence matrices + dijExp = matRad_calcParticleDose(ct,stf,pln,cst,param); + + suffix = 'Exp'; + % organize fieldnames + for i = 1:numel(fNames) + % create new Exp field and set it + [dijExp.([fNames{1,i} suffix])] = dijExp.([fNames{1,i}]); + % overide existing field with nominal dose influence data field + dijExp.([fNames{1,i}]) = dij.([fNames{1,i}]); + end + + % find VOI indicies with objective or constraint and create a field in the cst for the integral variance + cst = matRad_createOmegaPlaceHolder(cst,dij.totalNumOfBixels); + + % estimate first moment based on discrete samples + case {'wcScen','impScen'} + + [dijExp] = matRad_estimateParticleMean(cst,pln,dij); + cst = matRad_createOmegaPlaceHolder(cst,dijExp.totalNumOfBixels); + + % otherwise create empty placeholders for expected ij matrices + otherwise + + dijExp = dij; + clear 'dij'; + + if param.calcDoseDirect + numCol = dijExp.numOfBeams; + else + numCol = dijExp.totalNumOfBixels; + end + + for i = 1:numel(fNames) + dijExp.([fNames{1,i} 'Exp']){1} = spalloc(prod(dijExp.dimensions),numCol,1); + end + + % find VOI indicies with objective or constraint and create a field in the cst for the integral variance + cst = matRad_createOmegaPlaceHolder(cst,dijExp.totalNumOfBixels); + +end %eof switch + + + +end % eof function + + +function cst = matRad_createOmegaPlaceHolder(cst,numBixels) +voiIx = find(~cellfun(@isempty, cst(:,6)))'; +for i = voiIx + cst{i,6}(1).mOmega = spalloc(numBixels,numBixels,1); +end +end + + diff --git a/matRad_calcDoseDirect.m b/matRad_calcDoseDirect.m index 5bdaedf0b..10c05493d 100644 --- a/matRad_calcDoseDirect.m +++ b/matRad_calcDoseDirect.m @@ -1,7 +1,7 @@ function resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst,w,param) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % matRad dose calculation wrapper bypassing dij calculation -% +% % call % resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst) % @@ -22,21 +22,21 @@ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% Copyright 2015 the matRad development team. -% -% This file is part of the matRad project. It is subject to the license -% terms in the LICENSE file found in the top-level directory of this -% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part -% of the matRad project, including this file, may be copied, modified, -% propagated, or distributed except according to the terms contained in the +% Copyright 2015 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the % LICENSE file. % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% if exist('param','var') - if ~isfield(param,'logLevel') - param.logLevel = 1; - end + if ~isfield(param,'logLevel') + param.logLevel = 1; + end else param.subIx = []; param.logLevel = 1; @@ -46,64 +46,59 @@ % check if weight vector is available, either in function call or in stf - otherwise dose calculation not possible if ~exist('w','var') && ~isfield([stf.ray],'weight') - error('No weight vector available. Please provide w or add info to stf') + error('No weight vector available. Please provide w or add info to stf') end % copy bixel weight vector into stf struct if exist('w','var') - if sum([stf.totalNumOfBixels]) ~= numel(w) - error('weighting does not match steering information') - end - counter = 0; - for i = 1:size(stf,2) - for j = 1:stf(i).numOfRays - for k = 1:stf(i).numOfBixelsPerRay(j) - counter = counter + 1; - stf(i).ray(j).weight(k) = w(counter); - end - end - end + if sum([stf.totalNumOfBixels]) ~= numel(w) + error('weighting does not match steering information') + end + counter = 0; + for i = 1:size(stf,2) + for j = 1:stf(i).numOfRays + for k = 1:stf(i).numOfBixelsPerRay(j) + counter = counter + 1; + stf(i).ray(j).weight(k) = w(counter); + end + end + end else % weights need to be in stf! - w = NaN*ones(sum([stf.totalNumOfBixels]),1); - counter = 0; - for i = 1:size(stf,2) - for j = 1:stf(i).numOfRays - for k = 1:stf(i).numOfBixelsPerRay(j) - counter = counter + 1; - w(counter) = stf(i).ray(j).weight(k); - end - end - end + w = NaN*ones(sum([stf.totalNumOfBixels]),1); + counter = 0; + for i = 1:size(stf,2) + for j = 1:stf(i).numOfRays + for k = 1:stf(i).numOfBixelsPerRay(j) + counter = counter + 1; + w(counter) = stf(i).ray(j).weight(k); + end + end + end end % dose calculation -if strcmp(pln.radiationMode,'photons') - dij = matRad_calcPhotonDose(ct,stf,pln,cst,param); - %dij = matRad_calcPhotonDoseVmc(ct,stf,pln,cst,5000,4,calcDoseDirect); -elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon') - dij = matRad_calcParticleDose(ct,stf,pln,cst,param); -end +[cst,dij] = matRad_calcDose(ct,stf,pln,cst,param); % calc resulting dose if pln.multScen.totNumScen == 1 - % calculate cubes; use uniform weights here, weighting with actual fluence - % already performed in dij construction - resultGUI = matRad_calcCubes(ones(pln.propStf.numOfBeams,1),dij,cst); - -% calc individual scenarios -else - - Cnt = 1; - ixForOpt = find(~cellfun(@isempty, dij.physicalDose))'; - for i = ixForOpt + % calculate cubes; use uniform weights here, weighting with actual fluence + % already performed in dij construction + resultGUI = matRad_calcCubes(ones(pln.propStf.numOfBeams,1),dij,cst); + + % calc individual scenarios +else + + Cnt = 1; + ixForOpt = find(~cellfun(@isempty, dij.physicalDose))'; + for i = ixForOpt tmpResultGUI = matRad_calcCubes(ones(pln.propStf.numOfBeams,1),dij,cst,i); resultGUI.([pln.bioParam.quantityVis '_' num2str(Cnt,'%d')]) = tmpResultGUI.(pln.bioParam.quantityVis); Cnt = Cnt + 1; - end + end end % remember original fluence weights -resultGUI.w = w; +resultGUI.w = w; diff --git a/matRad_calcLateralParticleCutOff.m b/matRad_calcLateralParticleCutOff.m index 37bd82c6c..f9fcfe3ff 100644 --- a/matRad_calcLateralParticleCutOff.m +++ b/matRad_calcLateralParticleCutOff.m @@ -51,7 +51,7 @@ % integration steps r_mid = 0.5*(vX(1:end-1) + vX(2:end))'; % [mm] dr = (vX(2:end) - vX(1:end-1))'; -radialDist_sq = r_mid.^2; +radialDist = r_mid; % number of depth points for which a lateral cutoff is determined numDepthVal = 35; @@ -122,8 +122,8 @@ % get the current integrated depth dose profile if isstruct(machine.data(energyIx).Z) idd_org = sumGauss(machine.data(energyIx).depths,machine.data(energyIx).Z.mean,... - machine.data(energyIx).Z.width.^2,... - machine.data(energyIx).Z.weight) * conversionFactor; + machine.data(energyIx).Z.width.^2,... + machine.data(energyIx).Z.weight) * conversionFactor; else idd_org = machine.data(energyIx).Z * conversionFactor; end @@ -169,9 +169,11 @@ else % calculate dose - dose_r = matRad_calcParticleDoseBixel(depthValues(j) + baseData.offset, radialDist_sq, largestSigmaSq4uniqueEnergies(cnt), baseData); + dose_r = matRad_calcParticleDoseBixelWrapper(depthValues(j) + baseData.offset, radialDist', 0,... + largestSigmaSq4uniqueEnergies(cnt), baseData, false,[],[]); - cumArea = cumsum(2*pi.*r_mid.*dose_r.*dr); + cumArea = cumsum(2*pi.*r_mid.*dose_r.physDose'.*dr); + relativeTolerance = 0.5; %in [%] if abs((cumArea(end)./(idd(j)))-1)*100 > relativeTolerance warning('LateralParticleCutOff: shell integration is wrong !') @@ -190,7 +192,9 @@ machine.data(energyIx).LatCutOff.CutOff(j) = depthDoseCutOff; end - end + + end + %figure,plot(machine.data(energyIx).LatCutOff.depths,machine.data(energyIx).LatCutOff.CutOff) end %% visualization @@ -213,10 +217,9 @@ dimX = numel(vLatX); midPos = round(length(vLatX)/2); [X,Y] = meshgrid(vLatX,vLatX); - + radialDist = sqrt(X.^2 + Y.^2); + radDepths = [0:sStep:machine.data(energyIx).depths(end)] + machine.data(energyIx).offset; - radialDist_sq = (X.^2 + Y.^2); - radialDist_sq = radialDist_sq(:); mDose = zeros(dimX,dimX,numel(radDepths)); vDoseInt = zeros(numel(radDepths),1); @@ -238,20 +241,18 @@ end - mDose(:,:,kk) = reshape(matRad_calcParticleDoseBixel(radDepths(kk), radialDist_sq, sigmaIni_sq,baseData),[dimX dimX]); + bixelDose = matRad_calcParticleDoseBixelWrapper(radDepths(kk), radialDist(:), 0, sigmaIni_sq,baseData,0,[]); + mDose(:,:,kk) = reshape(bixelDose.physDose,[dimX dimX]); [~,IX] = min(abs((machine.data(energyIx).LatCutOff.depths + machine.data(energyIx).offset) - radDepths(kk))); TmpCutOff = machine.data(energyIx).LatCutOff.CutOff(IX); vXCut = vX(vX<=TmpCutOff); - % integration steps r_mid_Cut = (0.5*(vXCut(1:end-1) + vXCut(2:end)))'; % [mm] dr_Cut = (vXCut(2:end) - vXCut(1:end-1))'; - radialDist_sqCut = r_mid_Cut.^2; - - dose_r_Cut = matRad_calcParticleDoseBixel(radDepths(kk), radialDist_sqCut(:), sigmaIni_sq,baseData); + dose_r_Cut = matRad_calcParticleDoseBixelWrapper(radDepths(kk), r_mid_Cut,0, sigmaIni_sq,baseData, 0, []); - cumAreaCut = cumsum(2*pi.*r_mid_Cut.*dose_r_Cut.*dr_Cut); + cumAreaCut = cumsum(2*pi.*r_mid_Cut.*dose_r_Cut.physDose.*dr_Cut); if ~isempty(cumAreaCut) vDoseInt(kk) = cumAreaCut(end); @@ -260,17 +261,17 @@ % obtain maximum dose if isstruct(machine.data(energyIx).Z) - idd = sumGauss(depthValues,machine.data(energyIx).Z.mean,... + idd = sumGauss(depthValues,machine.data(energyIx).Z.mean,... machine.data(energyIx).Z.width.^2,... machine.data(energyIx).Z.weight) * conversionFactor; else - idd = matRad_interp1(machine.data(energyIx).depths,machine.data(energyIx).Z,depthValues) * conversionFactor; + idd = matRad_interp1(machine.data(energyIx).depths,machine.data(energyIx).Z,depthValues)* conversionFactor; end [~,peakixDepth] = max(idd); - dosePeakPos = matRad_calcParticleDoseBixel(machine.data(energyIx).depths(peakixDepth), 0, sigmaIni_sq, baseData); + dosePeakPos = matRad_calcParticleDoseBixelWrapper(machine.data(energyIx).depths(peakixDepth), 0, 0,sigmaIni_sq, baseData,0,[]); - vLevelsDose = dosePeakPos.*[0.01 0.05 0.1 0.9]; + vLevelsDose = dosePeakPos.physDose.*[0.01 0.05 0.1 0.9]; doseSlice = squeeze(mDose(midPos,:,:)); figure,set(gcf,'Color',[1 1 1]); subplot(311),h=imagesc(squeeze(mDose(midPos,:,:)));hold on; @@ -289,18 +290,20 @@ legend({'isodose 1%,5%,10% 90%','calculated cutoff'}) ,colorbar,set(gca,'FontSize',12),xlabel('z [mm]'),ylabel('x [mm]'); entry = machine.data(energyIx); + if isstruct(entry.Z) - idd = sumGauss(entry.depths,entry.Z.mean,entry.Z.width.^2,entry.Z.weight); + idd = sumGauss(entry.depths,entry.Z.mean,entry.Z.width.^2,entry.Z.weight)* conversionFactor; else - idd = machine.data(energyIx).Z; + idd = machine.data(energyIx).Z * conversionFactor; end - subplot(312),plot(machine.data(energyIx).depths,idd*conversionFactor,'k','LineWidth',2),grid on,hold on + subplot(312),plot(machine.data(energyIx).depths,idd,'k','LineWidth',2),grid on,hold on plot(radDepths - machine.data(energyIx).offset,vDoseInt,'r--','LineWidth',2),hold on, plot(radDepths - machine.data(energyIx).offset,vDoseInt * TmpCompFac,'bx','LineWidth',1),hold on, legend({'original IDD',['cut off IDD at ' num2str(cutOffLevel) '%'],'cut off IDD with compensation'},'Location','northwest'), xlabel('z [mm]'),ylabel('[MeV cm^2 /(g * primary)]'),set(gca,'FontSize',12) - totEnergy = trapz(machine.data(energyIx).depths,idd*conversionFactor) ; + + totEnergy = trapz(machine.data(energyIx).depths,idd) ; totEnergyCutOff = trapz(radDepths,vDoseInt * TmpCompFac) ; relDiff = ((totEnergy/totEnergyCutOff)-1)*100; title(['rel diff of integral dose ' num2str(relDiff) '%']); diff --git a/matRad_calcParticleDose.m b/matRad_calcParticleDose.m index 10c5bf4df..edee21d4f 100644 --- a/matRad_calcParticleDose.m +++ b/matRad_calcParticleDose.m @@ -49,7 +49,14 @@ param.subIx = []; param.logLevel = 1; end - + +if isfield(pln,'propDoseCalc') + if ~isfield(pln.propDoseCalc,'calcLET') + pln.propDoseCalc.calcLET = false; + end +else + pln.propDoseCalc.calcLET = false; +end if param.logLevel == 1 % initialize waitbar @@ -71,10 +78,10 @@ dij.totalNumOfRays = sum(dij.numOfRaysPerBeam); if param.calcDoseDirect - numOfColumnsDij = length(stf); + numOfColumnsDij = length(stf); numOfBixelsContainer = 1; else - numOfColumnsDij = dij.totalNumOfBixels; + numOfColumnsDij = dij.totalNumOfBixels; numOfBixelsContainer = ceil(dij.totalNumOfBixels/10); end @@ -127,6 +134,32 @@ end +% allocate data for analytical probabilistic modelling +if ~param.nomDoseCalc + dZTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); + SpotLUTTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); + if pln.bioParam.bioOpt + dZaTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); + dZbTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); + end + + for ctScen = 1:pln.multScen.numOfCtScen + for ShiftScen = 1:pln.multScen.totNumShiftScen + for RangeShiftScen = 1:pln.multScen.totNumRangeScen + dij.mSigma{ctScen,ShiftScen,RangeShiftScen} = spalloc(dij.numOfVoxels,numOfColumnsDij,1); % depth dose component + dij.dZ{ctScen,ShiftScen,RangeShiftScen} = spalloc(dij.numOfVoxels,numOfColumnsDij,1); % depth dose component + dij.spotLUT{ctScen,ShiftScen,RangeShiftScen} = spalloc(dij.numOfVoxels,numOfColumnsDij,1); % voxel-spot looup table + dij.radDepth{ctScen,ShiftScen,RangeShiftScen} = spalloc(dij.numOfVoxels,length(pln.propStf.gantryAngles),1); % radiological depth per per beam + if pln.bioParam.bioOpt + dij.dZa{ctScen,ShiftScen,RangeShiftScen} = spalloc(dij.numOfVoxels,numOfColumnsDij,1); % alphaDose component + dij.dZb{ctScen,ShiftScen,RangeShiftScen} = spalloc(dij.numOfVoxels,numOfColumnsDij,1); % SqrtBetaDose dose component + end + end + end + end + +end + % Only take voxels inside patient. if isfield(param,'subIx') && ~isempty(param.subIx) V = param.subIx; @@ -138,6 +171,7 @@ % ignore densities outside of contours eraseCtDensMask = ones(dij.numOfVoxels,1); eraseCtDensMask(V) = 0; + for i = 1:ct.numOfCtScen ct.cube{i}(eraseCtDensMask == 1) = 0; end @@ -155,29 +189,29 @@ if isfield(pln,'propDoseCalc') && ... - isfield(pln.propDoseCalc,'calcLET') && ... - pln.propDoseCalc.calcLET - - if isfield(machine.data,'LET') - - letDoseTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.numOfShiftScen,pln.multScen.numOfRangeShiftScen); + isfield(pln.propDoseCalc,'calcLET') && ... + pln.propDoseCalc.calcLET - for ctScen = 1:pln.multScen.numOfCtScen - for ShiftScen = 1:pln.multScen.numOfShiftScen - for RangeShiftScen = 1:pln.multScen.numOfRangeShiftScen - - if pln.multScen.scenMask(ctScen,ShiftScen,RangeShiftScen) - dij.mLETDose{ctScen,ShiftScen,RangeShiftScen} = spalloc(prod(ct.cubeDim),numOfColumnsDij,1); - end - + if isfield(machine.data,'LET') + + letDoseTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); + + for ctScen = 1:pln.multScen.numOfCtScen + for ShiftScen = 1:pln.multScen.totNumShiftScen + for RangeShiftScen = 1:pln.multScen.totNumRangeScen + + if pln.multScen.scenMask(ctScen,ShiftScen,RangeShiftScen) + dij.mLETDose{ctScen,ShiftScen,RangeShiftScen} = spalloc(prod(ct.cubeDim),numOfColumnsDij,1); + end + end - end - - end - - else - matRad_dispToConsole('LET not available in the machine data. LET will not be calculated.',param,'warning'); - end + end + + end + + else + matRad_dispToConsole('LET not available in the machine data. LET will not be calculated.',param,'warning'); + end end % book keeping - this is necessary since pln is not used in optimization or @@ -186,7 +220,11 @@ dij.RBE = pln.bioParam.RBE; end -% generates tissue class matrix for biological treatment planning and alpha_x, beta_x, vectors + +% generates tissue class matrix for biological optimization +vTissueIndex = zeros(size(V,1),1); + +% generates tissue class matrix for biological treatment planning and alpha_x, beta_x, vectors if pln.bioParam.bioOpt dij.alphaX = zeros(dij.numOfVoxels,1); @@ -219,9 +257,6 @@ % create alpha_x beta_x ratio vector dij.abX(dij.betaX>0) = dij.alphaX(dij.betaX>0)./dij.betaX(dij.betaX>0); - % generates tissue class matrix for biological optimization - vTissueIndex = zeros(size(V,1),1); - if strcmp(pln.radiationMode,'carbon') || strcmp(pln.radiationMode,'helium') for i = 1:size(cst,1) @@ -254,9 +289,12 @@ matRad_dispToConsole('done. \n',param,'info'); end - + end +dij.vTissueIndex = vTissueIndex; + + ctScen = 1; % current ct scenario matRad_dispToConsole('matRad: Particle dose calculation... \n',param,'info'); @@ -265,21 +303,21 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% for ShiftScen = 1:pln.multScen.totNumShiftScen - % manipulate isocenter - for k = 1:length(stf) - stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(ShiftScen,:); + % manipulate isocenter only if a single isocenter shift is used not + if ~strcmp(pln.multScen.TYPE,'rndScenCov') + for k = 1:length(stf) + stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(ShiftScen,:); + end end - matRad_dispToConsole(['shift scenario ' num2str(ShiftScen) ' of ' num2str(pln.multScen.totNumShiftScen) ': \n'],param,'info'); matRad_dispToConsole('matRad: Particle dose calculation... \n',param,'info'); counter = 0; - - % compute SSDs - stf = matRad_computeSSD(stf,ct,ctScen,param); for i = 1:numel(stf) % loop over all beams + isoCenterOrg = stf(i).isoCenter; + matRad_dispToConsole(['Beam ' num2str(i) ' of ' num2str(dij.numOfBeams) ': \n'],param,'info'); % remember beam and bixel number @@ -288,7 +326,15 @@ dij.rayNum(i) = i; dij.bixelNum(i) = i; end - + + % consider beamwise isocenter shift + if strcmp(pln.multScen.TYPE,'rndScenCov') + stf(i).isoCenter = stf(i).isoCenter + pln.multScen.isoShift(counter + 1,:); + end + + % compute SSDs + stf(i) = matRad_computeSSD(stf(i),ct,ctScen); + bixelsPerBeam = 0; % convert voxel indices to real coordinates using iso center of beam i @@ -329,7 +375,13 @@ visBoolLateralCutOff = 0; machine = matRad_calcLateralParticleCutOff(machine,cutOffLevel,stf(i),ctScen,visBoolLateralCutOff); matRad_dispToConsole('done. \n',param,'info'); - + + % save radiological depth for preselected APM voxels + if ~param.nomDoseCalc + [subIndexAPM,~,subIx] = intersect(pln.probOpt.voxelList,V(radDepthIx)); + dij.radDepth{1}(subIndexAPM,i) = radDepthV{1}(radDepthIx(subIx)); + end + for j = 1:stf(i).numOfRays % loop over all rays if ~isempty(stf(i).ray(j).energy) @@ -340,19 +392,17 @@ maxLateralCutoffDoseCalc = max(machine.data(energyIx).LatCutOff.CutOff); % Ray tracing for beam i and ray j - [ix,radialDist_sq] = matRad_calcGeoDists(rot_coordsV, ... - stf(i).sourcePoint_bev, ... - stf(i).ray(j).targetPoint_bev, ... - machine.meta.SAD, ... - radDepthIx, ... - maxLateralCutoffDoseCalc); - - + [ix,radialDist_sq,isoLatDistsX,isoLatDistsY] = matRad_calcGeoDists(rot_coordsV, ... + stf(i).sourcePoint_bev, ... + stf(i).ray(j).targetPoint_bev, ... + machine.meta.SAD, ... + radDepthIx, ... + maxLateralCutoffDoseCalc); + + % just use tissue classes of voxels found by ray tracer - if pln.bioParam.bioOpt - vTissueIndex_j = vTissueIndex(ix,:); - end - + vTissueIndex_j = vTissueIndex(ix,:); + for k = 1:stf(i).numOfBixelsPerRay(j) % loop over all bixels per ray counter = counter + 1; @@ -382,13 +432,14 @@ % find energy index in base data energyIx = find(round2(stf(i).ray(j).energy(k),4) == round2([machine.data.energy],4)); + baseEntry = machine.data(energyIx); + % create offset vector to account for additional offsets modelled in the base data and a potential % range shifter. In the following, we only perform dose calculation for voxels having a radiological depth % that is within the limits of the base data set (-> machine.data(i).dephts). By this means, we only allow % interpolations in matRad_calcParticleDoseBixel() and avoid extrapolations. offsetRadDepth = machine.data(energyIx).offset - stf(i).ray(j).rangeShifter(k).eqThickness; - for ctScen = 1:pln.multScen.numOfCtScen for RangeShiftScen = 1:pln.multScen.totNumRangeScen @@ -396,11 +447,18 @@ radDepths = radDepthV{ctScen}(ix); + % consider raywise correlation + if strcmp(pln.multScen.TYPE,'rndScenCov') + abRelCnt = counter; + else + abRelCnt = RangeShiftScen; + end + % manipulate radDepthCube for range scenarios if pln.multScen.relRangeShift(RangeShiftScen) ~= 0 || pln.multScen.absRangeShift(RangeShiftScen) ~= 0 radDepths = radDepths +... % original cube - radDepthV{ctScen}(ix)*pln.multScen.relRangeShift(RangeShiftScen) +... % rel range shift - pln.multScen.absRangeShift(RangeShiftScen); % absolute range shift + radDepthV{ctScen}(ix)*pln.multScen.relRangeShift(abRelCnt) +... % rel range shift + pln.multScen.absRangeShift(abRelCnt); % absolute range shift radDepths(radDepths < 0) = 0; end @@ -448,40 +506,74 @@ end + % obtain errors - only relevant for analytical expectation value calculation + if param.nomDoseCalc + error.latRndSq = 0; + error.latSysSq = 0; + error.rangeRndSq = 0; + error.rangeSysSq = 0; + else + error.latRndSq = pln.multScen.mCovLatRnd(counter,counter); + error.latSysSq = pln.multScen.mCovLatSys(counter,counter); + error.rangeRndSq = pln.multScen.mCovRangeRnd(counter,counter); + error.rangeSysSq = pln.multScen.mCovRangeSys(counter,counter); + + % save bixel information + dij.energyIx(counter) = energyIx; + dij.range(counter) = machine.data(energyIx).range; + dij.sigmaIni_sq(counter) = sigmaIni_sq; + + % save APM information only for preslected voxel indices + [subIndexAPM,~,ib] = intersect(pln.probOpt.voxelList, V(ix(currIx))); + SpotLUTTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(subIndexAPM,1,counter,dij.numOfVoxels,1); + + end + % calculate particle dose for bixel k on ray j of beam i - bixelDose = matRad_calcParticleDoseBixel(... + bixelDose = matRad_calcParticleDoseBixelWrapper(... currRadDepths, ... - radialDist_sq(currIx), ... + isoLatDistsX(currIx),isoLatDistsY(currIx),... sigmaIni_sq, ... - machine.data(energyIx)); - - + baseEntry,pln.bioParam.bioOpt,vTissueIndex_j(currIx,:),error,pln.propDoseCalc.calcLET); + % dij sampling is exluded for particles until we investigated the influence of voxel sampling for particles %relDoseThreshold = 0.02; % sample dose values beyond the relative dose %Type = 'dose'; %[currIx,bixelDose] = matRad_DijSampling(currIx,bixelDose,radDepths(currIx),radialDist_sq(currIx),Type,relDoseThreshold); % save dose for every bixel in cell array - doseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelDose,dij.numOfVoxels,1); - + doseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelDose.physDose,dij.numOfVoxels,1); + + % save helper information for APM + if ~param.nomDoseCalc + % tmp = bixelDose.SqSigma(currIx); + dZTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(subIndexAPM,1,bixelDose.Z_ij(ib),dij.numOfVoxels,1); + if pln.bioParam.bioOpt + dZaTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(subIndexAPM,1,bixelDose.Z_Aij(ib),dij.numOfVoxels,1); + dZbTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(subIndexAPM,1,bixelDose.Z_Bij(ib),dij.numOfVoxels,1); + end + end + if isfield(dij,'mLETDose') - % calculate particle LET for bixel k on ray j of beam i - depths = machine.data(energyIx).depths + machine.data(energyIx).offset; - bixelLET = matRad_interp1(depths,machine.data(energyIx).LET,radDepths(currIx)); - bixelLET(isnan(bixelLET)) = 0; - % save LET for every bixel in cell array - letDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelLET.*bixelDose,dij.numOfVoxels,1); - end + letDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelDose.LET_ij.*bixelDose.physDose,dij.numOfVoxels,1); + end % save alpha_p and beta_p radiosensititvy parameter for every bixel in cell array if pln.bioParam.bioOpt - [bixelAlpha,bixelBeta] = pln.bioParam.calcLQParameter(radDepths(currIx),machine.data(energyIx),vTissueIndex_j(currIx,:),dij.alphaX(V(ix(currIx))),... + if ~isfield(bixelDose,'Z_Aij') + [bixelAlpha,bixelBeta] = pln.bioParam.calcLQParameter(radDepths(currIx),baseEntry,vTissueIndex_j(currIx,:),dij.alphaX(V(ix(currIx))),... dij.betaX(V(ix(currIx))),... dij.abX(V(ix(currIx)))); - - alphaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelAlpha.*bixelDose,dij.numOfVoxels,1); - betaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,sqrt(bixelBeta).*bixelDose,dij.numOfVoxels,1); + bixelAlphaDose = bixelDose.physDose .* bixelAlpha; + bixelBetaDose = bixelDose.physDose .* sqrt(bixelBeta); + else + bixelAlphaDose = bixelDose.L_ij .* bixelDose.Z_Aij; + bixelBetaDose = bixelDose.L_ij .* bixelDose.Z_Bij; + end + + alphaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelAlphaDose,dij.numOfVoxels,1); + betaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen} = sparse(V(ix(currIx)),1,bixelBetaDose,dij.numOfVoxels,1); end @@ -508,7 +600,7 @@ % score physical dose dij.physicalDose{ctScen,ShiftScen,RangeShiftScen}(:,i) = dij.physicalDose{ctScen,ShiftScen,RangeShiftScen}(:,i) + stf(i).ray(j).weight(k) * doseTmpContainer{1,ctScen,ShiftScen,RangeShiftScen}; - if isfield(dij,'mLETDose') && pln.sampling + if isfield(dij,'mLETDose') % score LETxDose matrices dij.mLETDose{ctScen,ShiftScen,RangeShiftScen}(:,i) = dij.mLETDose{ctScen,ShiftScen,RangeShiftScen}(:,i) + stf(i).ray(j).weight(k) * letDoseTmpContainer{1,ctScen,ShiftScen,RangeShiftScen}; end @@ -530,7 +622,17 @@ % fill entire LETxDose influence matrix dij.mLETDose{ctScen,ShiftScen,RangeShiftScen}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [letDoseTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen}]; end - + + % fill APM helpers + if ~param.nomDoseCalc + dij.dZ{ctScen,ShiftScen,RangeShiftScen}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [dZTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}]; + dij.SpotLUT{ctScen,ShiftScen,RangeShiftScen}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [SpotLUTTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}]; + if pln.bioParam.bioOpt + dij.dZa{ctScen,ShiftScen,RangeShiftScen}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [dZaTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}]; + dij.dZb{ctScen,ShiftScen,RangeShiftScen}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [dZbTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}]; + end + end + if pln.bioParam.bioOpt % fill entire alphaxDose influence and sqrt(beta)xDose influence matrices dij.mAlphaDose{ctScen,ShiftScen,RangeShiftScen}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [alphaDoseTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,ctScen,ShiftScen,RangeShiftScen}]; @@ -549,6 +651,11 @@ end % end ray loop + % manipulate iso-center for each beam direction + if strcmp(pln.multScen.TYPE,'rndScenCov') + stf(i).isoCenter = isoCenterOrg; + end + end % end beam loop % manipulate isocenter diff --git a/matRad_calcParticleDoseBixelWrapper.m b/matRad_calcParticleDoseBixelWrapper.m new file mode 100644 index 000000000..afc99bb29 --- /dev/null +++ b/matRad_calcParticleDoseBixelWrapper.m @@ -0,0 +1,74 @@ +function [bixelDose] = matRad_calcParticleDoseBixelWrapper(radDepths,latDistsX,latDistsZ,sigmaIni_sq,baseEntry,flagBioOpt,vTissueIndex,sError,calcLET) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad particle pencil beam calculation wrapper +% +% call +% dij = matRad_calcParticleDose(ct,stf,pln,cst) +% +% input +% radDepths: radiological depths +% latDistsX: lateral distance to the central beam axis in x-direction +% latDistsZ: lateral distance to the central beam axis in z-direction +% sigmaIni_sq: initial Gaussian sigma^2 of beam at patient surface +% baseEntry: base data required for particle dose calculation +% flagBioOpt boolean determining biological or physical optimization +% sError error struct defining lateral and range errors +% +% output +% bixelDose: struct holding the deposited dose in x,y,z as well as +% the total dose +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2018 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% function handle for calculating depth doses +sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ... + exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w); + +if ~exist('calcLET','var') + calcLET = false; +end + +if isempty(sError) + sError.latRndSq = 0; + sError.latSysSq = 0; + sError.rangeRndSq = 0; + sError.rangeSysSq = 0; +end + +% perform bixel dose calculation depending on the given base data format +if ~isstruct(baseEntry.Z) + rad_distancesSq = latDistsX.^2 + latDistsZ.^2; + bixelDose.physDose = matRad_calcParticleDoseBixel(radDepths,rad_distancesSq,sigmaIni_sq,baseEntry); +else + [bixelDose] = matRad_calcParticleDoseBixelAPM(radDepths,latDistsX,latDistsZ,sigmaIni_sq,baseEntry,flagBioOpt,vTissueIndex,sError); +end + +if calcLET + depths = baseEntry.depths + baseEntry.offset; + if ~isstruct(baseEntry.LET) + bixelDose.LET_ij = matRad_interp1(depths,baseEntry.LET,radDepths); + else + SqSigmaRangeOffset = sError.rangeRndSq + sError.rangeSysSq; + bixelDose.LET_ij = sumGauss(radDepths, baseEntry.LET.mean, (baseEntry.LET.width).^2 + SqSigmaRangeOffset,baseEntry.LET.weight); + end + bixelDose.LET_ij(isnan(bixelDose.LET_ij)) = 0; +end + + + + + +end \ No newline at end of file diff --git a/matRad_fluenceOptimization.m b/matRad_fluenceOptimization.m index 64582098a..0a32653fd 100644 --- a/matRad_fluenceOptimization.m +++ b/matRad_fluenceOptimization.m @@ -16,7 +16,7 @@ % resultGUI: struct containing optimized fluence vector, dose, and (for % biological optimization) RBE-weighted dose etc. % info: struct containing information about optimization -% +% % References % - % @@ -43,6 +43,17 @@ param.logLevel = 1; end +if ismac + % Code to run on Mac plaform +elseif isunix + % Code to run on Linux plaform + param.logLevel = 2; +elseif ispc + % Code to run on Windows platform +else + disp('Platform not supported') +end + % determine if Matlab or Octave [env, ~] = matRad_getEnvironment(); @@ -93,7 +104,7 @@ % adjust objectives and constraints internally for fractionation for i = 1:size(cst,1) - for j = 1:size(cst{i,6},1) + for j = 1:size(cst{i,6},2) cst{i,6}(j).dose = cst{i,6}(j).dose/pln.numOfFractions; end end @@ -106,8 +117,8 @@ for i=1:size(cst,1) if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6}) V = [V;cst{i,4}{1}]; - doseTarget = [doseTarget cst{i,6}.dose]; - ixTarget = [ixTarget i*ones(1,length([cst{i,6}.dose]))]; + doseTarget = [doseTarget max(vertcat(cst{i,6}(:).dose))]; % what if multiple objectives are defined + ixTarget = [ixTarget i*ones(1,length([cst{i,4}{1}]))]; end end [doseTarget,i] = max(doseTarget); @@ -144,7 +155,7 @@ for j = 1:size(cst{i,6},2) % check if prescribed doses are in a valid domain - if cst{i,6}(j).dose > 5 && isequal(cst{i,3},'TARGET') + if cst{i,6}(j).dose(1) > 5 && isequal(cst{i,3},'TARGET') matRad_dispToConsole('Reference dose > 5Gy[RBE] for target. Biological optimization outside the valid domain of the base data. Reduce dose prescription or use more fractions.',param,'error'); end @@ -181,79 +192,57 @@ wInit = wOnes * bixelWeight; end -matRad_dispToConsole('Calculating probabilistic quantities for optimization ...\n',param,'info'); - -if ~pln.bioParam.bioOpt - fNames = {'physicalDose'}; -else - fNames = {'mAlphaDose','mSqrtBetaDose'}; -end %% calculate probabilistic quantities for probabilistic optimization if at least % one robust objective is defined - linIxDIJ = find(~cellfun(@isempty,dij.physicalDose))'; - -% find VOI indicies with objective or constraint -voiIx = []; -for i = 1:size(cst,1) - if ~isempty(cst{i,6}) - voiIx = [voiIx i]; - cst{i,6}(1).mOmega = 0; - end -end FLAG_CALC_PROB = false; FLAG_ROB_OPT = false; for i = 1:size(cst,1) for j = 1:size(cst{i,6},1) - if strcmp(cst{i,6}(j).robustness,'PROB') && numel(linIxDIJ) > 1 + %% allow for probabilistic optimization when a prob objective function + % and a expected dose influence matrix is found + if strcmp(cst{i,6}(j).robustness,'PROB') && sum(isfield(dij,{'physicalDoseExp','alphaDoseExp'})) > 0 FLAG_CALC_PROB = true; end + % enable robust optimziation if robust object is found and the + % number of discrete scenarios is greater than one if ~strcmp(cst{i,6}(j).robustness,'none') && numel(linIxDIJ) > 1 FLAG_ROB_OPT = true; end end end -% create placeholders for expected ij matrices -for i = 1:numel(fNames) - dij.([fNames{1,i} 'Exp']){1} = spalloc(prod(dij.dimensions),dij.totalNumOfBixels,1); +% set optimization options +if ~FLAG_ROB_OPT || FLAG_CALC_PROB % if multiple robust objectives are defined for one structure then remove FLAG_CALC_PROB from the if clause + options.ixForOpt = 1; +else + options.ixForOpt = find(~cellfun(@isempty,dij.physicalDose))'; end - +% adapt OAR dose prescription if FLAG_CALC_PROB - - ixDij = find(~cellfun(@isempty, dij.physicalDose))'; - - for i = 1:numel(fNames) - % create expected ij structure - dij.([fNames{1,i} 'Exp']){1} = spalloc(prod(dij.dimensions),dij.totalNumOfBixels,1); - % add up sparse matrices - should possess almost same sparsity pattern - for j = 1:pln.multScen.totNumScen - dij.([fNames{1,i} 'Exp']){1} = dij.([fNames{1,i} 'Exp']){1} + dij.([fNames{1,i}]){ixDij(j)} .* pln.multScen.scenProb(j); - end - end - - % loop over VOIs - for i = voiIx - % loop over scenarios and calculate the integral variance of each - % spot combination; bio bio optimization only consider std in the - % linear part of the biological effect - for j = 1:pln.multScen.totNumScen - cst{i,6}(1).mOmega = cst{i,6}(1).mOmega + ... - ((dij.(fNames{1,1}){ixDij(j)}(cst{i,4}{1},:)' * dij.(fNames{1,1}){ixDij(j)}(cst{i,4}{1},:)) * pln.multScen.scenProb(j)); + for i = 1:size(cst,1) + if ~isempty(cst{i,4}) && ~isempty(cst{i,6}) + for j = 1:size(cst{i,6},2) + if isequal(cst{i,3},'OAR') + if strcmp(cst{i,6}.type,'square overdosing') + warning('rob opt only supports squared deviation objectives for OARs'); + end + cst{i,6}.type = 'square overdosing';%'square deviation'; + cst{i,6}.dose = zeros(numel(cst{i,4}{1}),1); + elseif isequal(cst{i,3},'TARGET') + % cst{i,6}.type = 'square deviation'; + end + end end - cst{i,6}(1).mOmega = cst{i,6}(1).mOmega - (dij.([fNames{1,1} 'Exp']){1}(cst{i,4}{1},:)' * dij.([fNames{1,1} 'Exp']){1}(cst{i,4}{1},:)); - end + end end -% set optimization options -if ~FLAG_ROB_OPT || FLAG_CALC_PROB % if multiple robust objectives are defined for one structure then remove FLAG_CALC_PROB from the if clause - options.ixForOpt = 1; -else - options.ixForOpt = find(~cellfun(@isempty,dij.physicalDose))'; +if isfield(param,'w') + wInit = param.w; end options.numOfScen = numel(options.ixForOpt); @@ -284,10 +273,19 @@ % calc individual scenarios if options.numOfScen > 1 || FLAG_ROB_OPT + Cnt = 1; - for i = find(~cellfun(@isempty,dij.physicalDose))' - tmpResultGUI = matRad_calcCubes(wOpt,dij,cst,i); - resultGUI.([pln.bioParam.quantityVis '_' num2str(Cnt,'%d')]) = tmpResultGUI.(pln.bioParam.quantityVis); + + if FLAG_ROB_OPT + robID = 'Rob'; + else + robID = ''; + end + + ixScen = find(~cellfun(@isempty,dij.physicalDose)); + for i = ixScen(:)' + tmpResultGUI = matRad_calcCubes(wOpt,dij,cst,i,false); + resultGUI.([pln.bioParam.quantityVis '_' num2str(Cnt,'%d') robID]) = tmpResultGUI.([pln.bioParam.quantityVis robID]); Cnt = Cnt + 1; end end diff --git a/matRad_generateStf.m b/matRad_generateStf.m index f97358f4c..3cae8069e 100644 --- a/matRad_generateStf.m +++ b/matRad_generateStf.m @@ -74,8 +74,14 @@ % add margin addmarginBool = 1; +SigmaUnit = 2.5; + if addmarginBool - voiTarget = matRad_addMargin(voiTarget,cst,ct.resolution,ct.resolution,true); + margin.x = max([pln.propStf.bixelWidth SigmaUnit * ct.resolution.x]); + margin.y = max([pln.propStf.bixelWidth SigmaUnit * ct.resolution.y]); + margin.z = max([pln.propStf.bixelWidth SigmaUnit * ct.resolution.z]); + + voiTarget = matRad_addMargin(voiTarget,cst,ct.resolution,margin,true); V = find(voiTarget>0); end @@ -228,12 +234,19 @@ for j = stf(i).numOfRays:-1:1 for ShiftScen = 1:pln.multScen.totNumShiftScen - % ray tracing necessary to determine depth of the target - [~,l{ShiftScen},rho{ShiftScen},~,~] = matRad_siddonRayTracer(stf(i).isoCenter + pln.multScen.isoShift(ShiftScen,:), ... - ct.resolution, ... - stf(i).sourcePoint, ... - stf(i).ray(j).targetPoint, ... - [ct.cube {voiTarget}]); + + if strcmp(pln.multScen.TYPE,'apmScen') + vOffset = [0 0 0]; % already covered in margin expansion + else + vOffset = pln.multScen.isoShift(ShiftScen,:); + end + + % ray tracing necessary to determine depth of the target + [~,l{ShiftScen},rho{ShiftScen},~,~] = matRad_siddonRayTracer(stf(i).isoCenter + vOffset, ... + ct.resolution, ... + stf(i).sourcePoint, ... + stf(i).ray(j).targetPoint, ... + [ct.cube {voiTarget}]); end % find appropriate energies for particles @@ -259,19 +272,38 @@ % compute radiological depths % http://www.ncbi.nlm.nih.gov/pubmed/4000088, eq 14 radDepths = cumsum(l{ShiftScen} .* rho{ShiftScen}{CtScen}); + diff_voi = diff([rho{ShiftScen}{end}]); - if pln.multScen.relRangeShift(RangeShiftScen) ~= 0 || pln.multScen.absRangeShift(RangeShiftScen) ~= 0 - radDepths = radDepths +... % original cube - rho{ShiftScen}{CtScen}*pln.multScen.relRangeShift(RangeShiftScen) +... % rel range shift - pln.multScen.absRangeShift(RangeShiftScen); % absolute range shift + if strcmp(pln.multScen.TYPE,'apmScen') + vOffset = rho{ShiftScen}{CtScen}* ((SigmaUnit*pln.multScen.rangeSDsys)/100) +... % rel range shift + SigmaUnit* pln.multScen.rangeSDrnd; + + % absolute range shift + radDepthsUnder = radDepths + vOffset; + radDepthsOver = radDepths - vOffset; + radDepthsUnder(radDepthsUnder < 0) = 0; + radDepthsOver(radDepthsOver < 0) = 0; + + % find target entry & exit + targetEntry(Counter,1:length(radDepthsOver(diff_voi == 1))) = radDepthsOver(diff_voi == 1); + targetExit(Counter,1:length(radDepthsUnder(diff_voi == -1))) = radDepthsUnder(diff_voi == -1); + + elseif pln.multScen.relRangeShift(RangeShiftScen) ~= 0 || pln.multScen.absRangeShift(RangeShiftScen) ~= 0 + vOffset = rho{ShiftScen}{CtScen}*pln.multScen.relRangeShift(RangeShiftScen) +... % rel range shift + pln.multScen.absRangeShift(RangeShiftScen); % absolute range shift + + radDepths = radDepths + vOffset ; radDepths(radDepths < 0) = 0; + + % find target entry & exit + targetEntry(Counter,1:length(radDepths(diff_voi == 1))) = radDepths(diff_voi == 1); + targetExit(Counter,1:length(radDepths(diff_voi == -1))) = radDepths(diff_voi == -1); + else + % find target entry & exit + targetEntry(Counter,1:length(radDepths(diff_voi == 1))) = radDepths(diff_voi == 1); + targetExit(Counter,1:length(radDepths(diff_voi == -1))) = radDepths(diff_voi == -1); end - % find target entry & exit - diff_voi = diff([rho{ShiftScen}{end}]); - targetEntry(Counter,1:length(radDepths(diff_voi == 1))) = radDepths(diff_voi == 1); - targetExit(Counter,1:length(radDepths(diff_voi == -1))) = radDepths(diff_voi == -1); - end end @@ -304,11 +336,14 @@ matRad_dispToConsole('Inconsistency during ray tracing.',param,'error'); end - stf(i).ray(j).energy = []; - + stf(i).ray(j).energy = []; + stf(i).ray(j).energyIx = []; + % Save energies in stf struct for k = 1:numel(targetEntry) - stf(i).ray(j).energy = [stf(i).ray(j).energy availableEnergies(availablePeakPos>=targetEntry(k)&availablePeakPos<=targetExit(k))]; + energyIx = availablePeakPos>=targetEntry(k)&availablePeakPos<=targetExit(k); + stf(i).ray(j).energy = [stf(i).ray(j).energy availableEnergies(energyIx)]; + stf(i).ray(j).energyIx = [stf(i).ray(j).energyIx find(energyIx)]; end @@ -400,6 +435,7 @@ maskEnergy = stf(i).ray(j).energy(k) == availableEnergies; if ~useEnergyBool(maskEnergy) stf(i).ray(j).energy(k) = []; + stf(i).ray(j).energyIx(k) = []; stf(i).ray(j).focusIx(k) = []; stf(i).numOfBixelsPerRay(j) = stf(i).numOfBixelsPerRay(j) - 1; end diff --git a/matRad_multScen.m b/matRad_multScen.m index 2ea06308f..4e31cebe3 100644 --- a/matRad_multScen.m +++ b/matRad_multScen.m @@ -1,538 +1,821 @@ -classdef matRad_multScen -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% matRad_multScen -% This class creates all required biological model parameters according to -% a given radiation modatlity and a given bio model identifier string. -% -% constructor call -% pln.multScen = matRad_multScen(ct,TYPE); -% -% e.g. pln.multScen = matRad_multScen(ct,'nomScen'); -% -% input -% ct: ct cube -% TYPE: string to denote a scenario creation method -% 'nomScen' create only the nominal scenario -% 'wcScen' create worst case scenarios -% 'impScen' create important/grid scenarios -% 'rndScen' create random scenarios -% -% output -% bioParam: matRad's bioParam structure containing information -% about the choosen biological model -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +classdef matRad_multScen < handle + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % matRad_multScen + % This class creates all required biological model parameters according to + % a given radiation modatlity and a given bio model identifier string. + % + % constructor call + % pln.multScen = matRad_multScen(ct,TYPE); + % + % e.g. pln.multScen = matRad_multScen(ct,'nomScen'); + % + % input + % ct: ct cube + % TYPE: string to denote a scenario creation method + % 'nomScen' create only the nominal scenario + % 'wcScen' create worst case scenarios + % 'impScen' create important/grid scenarios + % 'rndScen' create random scenarios + % 'apmScen' create a APM scneario + % 'rndScenCov' creates random samples based on beam and + % covariances matrices + % + % output + % bioParam: matRad's bioParam structure containing information + % about the choosen biological model + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % Copyright 2017 the matRad development team. + % + % This file is part of the matRad project. It is subject to the license + % terms in the LICENSE file found in the top-level directory of this + % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part + % of the matRad project, including this file, may be copied, modified, + % propagated, or distributed except according to the terms contained in the + % LICENSE file. + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %% properties + + % public properties which can be changed outside this class + properties + + TYPE; % denotes the sampling type which cen be one of the following + % 'nomScen' create only the nominal scenario + % 'wcScen' create worst case scenarios + % 'impScen' create important/grid scenarios + % 'rndScen' create random scenarios + % 'rndScenCov' create random scenarios including ray and beam correlations + % ct scenarios + numOfCtScen; % number of imported ct scenarios + + % shift scenarios + numOfShiftScen; % number of shifts in x y and z direction + shiftSize; % 3x1 vector to define maximal shift in [mm] % (e.g. abdominal cases 5mm otherwise 3mm) + shiftGenType; % 'equidistant': equidistant shifts, 'sampled': sample shifts from normal distribution + shiftCombType; % 'individual': no combination of shift scenarios; number of shift scenarios is sum(multScen.numOfShiftScen) + % 'combined': combine shift scenarios; number of shift scenarios is numOfShiftScen + % 'permuted': create every possible shift combination; number of shift scenarios is 8,27,64 . + + % b) define range error scenarios + numOfRangeShiftScen; % number of absolute and/or relative range scnearios. + % if absolute and relative range scenarios are defined then rangeCombType defines the resulting number of range scenarios + maxAbsRangeShift; % maximum absolute over and undershoot in mm + maxRelRangeShift; % maximum relative over and undershoot in % + rangeCombType; % 'individual': no combination of absolute and relative range scenarios + % 'combined': combine absolute and relative range scenarios + rangeGenType; % 'equidistant': equidistant range shifts, 'sampled': sample range shifts from normal distribution + scenCombType; % 'individual': no combination of range and setup scenarios, + % 'combined': combine range and setup scenarios if their scenario number is consistent + % 'permuted': create every possible combination of range and setup scenarios + + includeNomScen; % boolean to determine if the nominal scenario should be included + + % these parameters will be filled according to the choosen scenario type + isoShift; + relRangeShift; + absRangeShift; + + totNumShiftScen; % total number of shift scenarios in x,y and z direction + totNumRangeScen; % total number of range and absolute range scenarios + totNumScen; % total number of samples + + scenForProb; % matrix for probability calculation - each row denotes one scenario + scenProb; % probability of each scenario stored in a vector + + scenMask; % logical index array determining the activation of scenarios + linearMask; % linear indices of scenarios obtained from find(dij) + + mCovLatRnd; % covariance matrix of random lateral erros + mCovLatSys; % covariance matrix of systematic lateral erros + mCovRangeRnd; % covariance matrix of random range erros + mCovRangeSys; % covariance matrix of systematic range erros + + vSampLatRndX; % lateral samples from random covariance matrix + vSampLatSysX; % lateral samples from systematic covariance matrix + vSampLatRndZ; % lateral samples from random covariance matrix + vSampLatSysZ; % lateral samples from systematic covariance matrix + vSampRangeRnd; % range samples from random covariance matrix + vSampRangeSys; % range samples from systematic covariance matrix + + mCovBio; % raywise beam correlation skelet for biological uncertainties - same as range error covariance matrix + mCovSpot; % component wise correlation of one individual pencil beam + + % values for APM + rangeSDsys = 4; % given in % + rangeSDrnd = 1; % given in [mm] + + shiftSDsys = 2; % absolut values given in [mm] + shiftSDrnd = 2; % absolut values given in [mm] + + end + + % private properties which can only be changed inside matRad_multScen + properties(SetAccess = private) + + DEFAULT_TYPE = 'nomScen'; + DEFAULT_probDist = 'normDist'; % 'normDist': normal probability distrubtion or 'equalProb' for uniform probability distribution + DEFAULT_TypeProp = 'probBins'; % 'probBins': cumulative prob in bin or 'pointwise' for point probability + + % helper objects for covariance creation + bixelIndexRayOffset; + bixelIndexBeamOffset; + bixelBeamLUT; + bixelRayLUT; + bixelEnergyLUT; + + end + + % constant properties which are visible outside of matRad_multScen + properties(Constant = true) + + AvailableScenCreationTYPE = {'nomScen','apmScen','wcScen','impScen','rndScen','rndScenCov'}; + -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% 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. -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - %% properties - - % public properties which can be changed outside this class - properties - - TYPE; % denotes the sampling type which cen be one of the following - % 'nomScen' create only the nominal scenario - % 'wcScen' create worst case scenarios - % 'impScen' create important/grid scenarios - % 'rndScen' create random scenarios - % ct scenarios - numOfCtScen; % number of imported ct scenarios - - % shift scenarios - numOfShiftScen; % number of shifts in x y and z direction - shiftSize; % 3x1 vector to define maximal shift in [mm] % (e.g. abdominal cases 5mm otherwise 3mm) - shiftGenType; % 'equidistant': equidistant shifts, 'sampled': sample shifts from normal distribution - shiftCombType; % 'individual': no combination of shift scenarios; number of shift scenarios is sum(multScen.numOfShiftScen) - % 'combined': combine shift scenarios; number of shift scenarios is numOfShiftScen - % 'permuted': create every possible shift combination; number of shift scenarios is 8,27,64 . - - % b) define range error scenarios - numOfRangeShiftScen; % number of absolute and/or relative range scnearios. - % if absolute and relative range scenarios are defined then rangeCombType defines the resulting number of range scenarios - maxAbsRangeShift; % maximum absolute over and undershoot in mm - maxRelRangeShift; % maximum relative over and undershoot in % - rangeCombType; % 'individual': no combination of absolute and relative range scenarios - % 'combined': combine absolute and relative range scenarios - rangeGenType; % 'equidistant': equidistant range shifts, 'sampled': sample range shifts from normal distribution - scenCombType; % 'individual': no combination of range and setup scenarios, - % 'combined': combine range and setup scenarios if their scenario number is consistent - % 'permuted': create every possible combination of range and setup scenarios - - includeNomScen; % boolean to determine if the nominal scenario should be included - - % these parameters will be filled according to the choosen scenario type - isoShift; - relRangeShift; - absRangeShift; - - totNumShiftScen; % total number of shift scenarios in x,y and z direction - totNumRangeScen; % total number of range and absolute range scenarios - totNumScen; % total number of samples - - scenForProb; % matrix for probability calculation - each row denotes one scenario - scenProb; % probability of each scenario stored in a vector - scenMask; - linearMask; - - rangeRelSD = 3.5; % given in % - rangeAbsSD = 1; % given in [mm] - shiftSD = [2.25 2.25 2.25]; % given in [mm] - - end - - % private properties which can only be changed inside matRad_multScen - properties(SetAccess = private) - - DEFAULT_TYPE = 'nomScen'; - DEFAULT_probDist = 'normDist'; % 'normDist': normal probability distrubtion or 'equalProb' for uniform probability distribution - DEFAULT_TypeProp = 'probBins'; % 'probBins': cumulative prob in bin or 'pointwise' for point probability - - end - - % constant properties which are visible outside of matRad_multScen - properties(Constant = true) - - AvailableScenCreationTYPE = {'nomScen','wcScen','impScen','rndScen'}; - end - - % constant private properties which are only visible within matRad_multScen - properties(Constant = true, Access = private) - - % default parameter for each scenario creation TYPE - - % 'nomScen' default parameters for nominal scenario - numOfShiftScen_nomScen = [0 0 0]; % number of shifts in x y and z direction - shiftSize_nomScen = [0 0 0]; % given in [mm] - shiftGenType_nomScen = 'equidistant'; % equidistant: equidistant shifts, - shiftCombType_nomScen = 'individual'; % individual: no combination of shift scenarios; - numOfRangeShiftScen_nomScen = 0 % number of absolute and/or relative range scnearios. - maxAbsRangeShift_nomScen = 0; % maximum absolute over and undershoot in mm - maxRelRangeShift_nomScen = 0; % maximum relative over and undershoot in % - rangeCombType_nomScen = 'combined'; % combine absolute and relative range scenarios - rangeGenType_nomScen = 'equidistant'; % equidistant: equidistant range shifts, - scenCombType_nomScen = 'individual'; % individual: no combination of range and setup scenarios, - includeNomScen_nomScen = true; - - % 'wcScen' default parameters for worst case scenarios - numOfShiftScen_wcScen = [2 2 2]; % number of shifts in x y and z direction - shiftSize_wcScen = [3 3 3]; % given in [mm] - shiftGenType_wcScen = 'equidistant'; % equidistant: equidistant shifts - shiftCombType_wcScen = 'individual'; % individual: no combination of shift scenarios - numOfRangeShiftScen_wcScen = 2; % number of absolute and/or relative range scnearios. - maxAbsRangeShift_wcScen = 1; % maximum absolute over and undershoot in mm - maxRelRangeShift_wcScen = 3.5; % maximum relative over and undershoot in % - rangeCombType_wcScen = 'combined'; % combine absolute and relative range scenarios - rangeGenType_wcScen = 'equidistant'; % equidistant: equidistant range shifts - scenCombType_wcScen = 'individual'; % individual: no combination of range and setup scenarios - includeNomScen_wcScen = true; % include nominal scenario - - % 'impScen' create important/grid scenarios - numOfShiftScen_impScen = [0 0 0]; % number of shifts in x y and z direction - shiftSize_impScen = [0 0 0]; % given in [mm] - shiftGenType_impScen = 'equidistant'; % equidistant: equidistant shifts - shiftCombType_impScen = 'individual'; % individual: no combination of shift scenarios - numOfRangeShiftScen_impScen = 20; % number of absolute and/or relative range scnearios - maxAbsRangeShift_impScen = 1; % maximum absolute over and undershoot in mm - maxRelRangeShift_impScen = 3.5; % maximum relative over and undershoot in % - rangeCombType_impScen = 'combined'; % combine absolute and relative range scenarios - rangeGenType_impScen = 'equidistant'; % equidistant: equidistant range shifts - scenCombType_impScen = 'individual'; % individual: no combination of range and setup scenarios, - includeNomScen_impScen = false; % exclude nominal scenario - - % 'rndScen' default parameters for random sampling - numOfShiftScen_rndScen = [25 25 25]; % number of shifts in x y and z direction - shiftSize_rndScen = [3 3 3]; % given in [mm] - shiftGenType_rndScen = 'sampled'; % sample shifts from normal distribution - shiftCombType_rndScen = 'combined'; % individual: no combination of shift scenarios; - numOfRangeShiftScen_rndScen = 25; % number of absolute and/or relative range scnearios. - maxAbsRangeShift_rndScen = 1; % maximum absolute over and undershoot in mm - maxRelRangeShift_rndScen = 3.5; % maximum relative over and undershoot in % - rangeCombType_rndScen = 'combined'; % combine absolute and relative range scenarios - rangeGenType_rndScen = 'sampled'; % sampled: sample range shifts from normal distribution - scenCombType_rndScen = 'combined'; % combine range and setup scenarios if their scenario number is consistent - includeNomScen_rndScen = false; % exclude nominal scenario - end - + corrModel = 'block'; % either correlated,uncorrelated or block + relBioUCT = 0.25; + + % values for random sampling and all other scenario generation types + shiftSD = [2.25 2.25 2.25]; % standard deviation given in [mm] + + end + + % constant private properties which are only visible within matRad_multScen + properties(Constant = true, Access = private) + + % default parameter for each scenario creation TYPE + + % 'nomScen' default parameters for nominal scenario + numOfShiftScen_nomScen = [0 0 0]; % number of shifts in x y and z direction + shiftSize_nomScen = [0 0 0]; % given in [mm] + shiftGenType_nomScen = 'equidistant'; % equidistant: equidistant shifts, + shiftCombType_nomScen = 'individual'; % individual: no combination of shift scenarios; + numOfRangeShiftScen_nomScen = 0 % number of absolute and/or relative range scnearios. + maxAbsRangeShift_nomScen = 0; % maximum absolute over and undershoot in mm + maxRelRangeShift_nomScen = 0; % maximum relative over and undershoot in % + rangeCombType_nomScen = 'combined'; % combine absolute and relative range scenarios + rangeGenType_nomScen = 'equidistant'; % equidistant: equidistant range shifts, + scenCombType_nomScen = 'individual'; % individual: no combination of range and setup scenarios, + includeNomScen_nomScen = true; + + % 'apmScen' default parameters for nominal scenario + numOfShiftScen_apmScen = [0 0 0]; % number of shifts in x y and z direction + shiftSize_apmScen = [0 0 0]; % given in [mm] + shiftGenType_apmScen = 'equidistant'; % equidistant: equidistant shifts, + shiftCombType_apmScen = 'individual'; % individual: no combination of shift scenarios; + numOfRangeShiftScen_apmScen = 0 % number of absolute and/or relative range scnearios. + maxAbsRangeShift_apmScen = 0; % maximum absolute over and undershoot in mm + maxRelRangeShift_apmScen = 0; % maximum relative over and undershoot in % + rangeCombType_apmScen = 'combined'; % combine absolute and relative range scenarios + rangeGenType_apmScen = 'equidistant'; % equidistant: equidistant range shifts, + scenCombType_apmScen = 'individual'; % individual: no combination of range and setup scenarios, + includeNomScen_apmScen = true; + + % 'wcScen' default parameters for worst case scenarios + numOfShiftScen_wcScen = [2 2 2]; % number of shifts in x y and z direction + shiftSize_wcScen = [3 3 3]; % given in [mm] + shiftGenType_wcScen = 'equidistant'; % equidistant: equidistant shifts + shiftCombType_wcScen = 'individual'; % individual: no combination of shift scenarios + numOfRangeShiftScen_wcScen = 2; % number of absolute and/or relative range scnearios. + maxAbsRangeShift_wcScen = 1; % maximum absolute over and undershoot in mm + maxRelRangeShift_wcScen = 3.5; % maximum relative over and undershoot in % + rangeCombType_wcScen = 'combined'; % combine absolute and relative range scenarios + rangeGenType_wcScen = 'equidistant'; % equidistant: equidistant range shifts + scenCombType_wcScen = 'individual'; % individual: no combination of range and setup scenarios + includeNomScen_wcScen = true; % include nominal scenario + + % 'impScen' create important/grid scenarios + numOfShiftScen_impScen = [0 0 0]; % number of shifts in x y and z direction + shiftSize_impScen = [0 0 0]; % given in [mm] + shiftGenType_impScen = 'equidistant'; % equidistant: equidistant shifts + shiftCombType_impScen = 'individual'; % individual: no combination of shift scenarios + numOfRangeShiftScen_impScen = 20; % number of absolute and/or relative range scnearios + maxAbsRangeShift_impScen = 1; % maximum absolute over and undershoot in mm + maxRelRangeShift_impScen = 3.5; % maximum relative over and undershoot in % + rangeCombType_impScen = 'combined'; % combine absolute and relative range scenarios + rangeGenType_impScen = 'equidistant'; % equidistant: equidistant range shifts + scenCombType_impScen = 'individual'; % individual: no combination of range and setup scenarios, + includeNomScen_impScen = false; % exclude nominal scenario + + % 'rndScen' create random scenarios + numOfShiftScen_rndScen = ones(1,3) * 20; % number of shifts in x y and z direction + shiftSize_rndScen = [3 3 3]; % given in [mm] + shiftGenType_rndScen = 'sampled'; % sample shifts from normal distribution + shiftCombType_rndScen = 'combined'; % individual: no combination of shift scenarios; + numOfRangeShiftScen_rndScen = 20; % number of absolute and/or relative range scnearios. + maxAbsRangeShift_rndScen = 1; % maximum absolute over and undershoot in mm + maxRelRangeShift_rndScen = 3.5; % maximum relative over and undershoot in % + rangeCombType_rndScen = 'combined'; % combine absolute and relative range scenarios + rangeGenType_rndScen = 'sampled'; % sampled: sample range shifts from normal distribution + scenCombType_rndScen = 'combined'; % combine range and setup scenarios if their scenario number is consistent + includeNomScen_rndScen = false; % exclude nominal scenario + + + % 'rndScenCov' create random scenarios + numOfShiftScen_rndScenCov = ones(1,3) * 200; % number of shifts in x y and z direction + shiftSize_rndScenCov = [3 3 3]; % given in [mm] + shiftGenType_rndScenCov = 'sampled'; % sample shifts from normal distribution + shiftCombType_rndScenCov = 'combined'; % individual: no combination of shift scenarios; + numOfRangeShiftScen_rndScenCov = 200; % number of absolute and/or relative range scnearios. + maxAbsRangeShift_rndScenCov = 1; % maximum absolute over and undershoot in mm + maxRelRangeShift_rndScenCov = 3.5; % maximum relative over and undershoot in % + rangeCombType_rndScenCov = 'combined'; % combine absolute and relative range scenarios + rangeGenType_rndScenCov = 'sampled'; % sampled: sample range shifts from normal distribution + scenCombType_rndScenCov = 'combined'; % combine range and setup scenarios if their scenario number is consistent + includeNomScen_rndScenCov = false; % exclude nominal scenario + end + %% methods % public methods go here - methods - - %Attach a listener to a TYPE + methods + + %Attach a listener to a TYPE function attachListener(obj) addlistener(obj,'TYPE','PostSet',@PropLis.propChange); end % default constructor - function this = matRad_multScen(ct,TYPE) - - if exist('TYPE','var') && ~isempty(TYPE) - if sum(strcmp(this.AvailableScenCreationTYPE,TYPE))>0 - this.TYPE = TYPE; - else - matRad_dispToConsole(['matRad_multScen: Unknown TYPE - using the nominal scenario now'],[],'warning') - this.TYPE = this.DEFAULT_TYPE; - end - else + function this = matRad_multScen(ct,scenGenType) + + TYPE = scenGenType; + + if exist('TYPE','var') && ~isempty(TYPE) + if sum(strcmp(this.AvailableScenCreationTYPE,TYPE))>0 + this.TYPE = TYPE; + else + matRad_dispToConsole(['matRad_multScen: Unknown TYPE - using the nominal scenario now'],[],'warning') + this.TYPE = this.DEFAULT_TYPE; + end + else this.TYPE = this.DEFAULT_TYPE; - end - this = getMultScenParam(ct,this); - this = setMultScen(this); - this = calcScenProb(this); + end + + this = getMultScenParam(ct,this); + this = setMultScen(this); + this = calcScenProb(this); + end % end constructor % create valid instance of an object function this = matRad_createValidInstance(this) - this = setMultScen(this); - this = calcScenProb(this); + this = setMultScen(this); + this = calcScenProb(this); end - %% setters to check for valid input - function this = set.numOfShiftScen(this,value) - - if ~isempty(value) - this.numOfShiftScen = value; - else - this.numOfShiftScen = this.numOfRangeShiftScen_nomScen; - end + + function this = getCovariancesSamples(this,ct,cst,pln,stf) + + NumSpot = sum([stf(:).totalNumOfBixels]); + + this = setCovarianceMatrix(this,ct,cst,pln,stf); + + tmp = full(this.mCovRangeRnd); + mCovFull = tmp + tmp'; + mCovFull(1:NumSpot+1:end) = diag(tmp); + [U,V,S] = eig(mCovFull); + this.vSampRangeRnd = bsxfun(@plus,0,(U *real(sqrtm(V))*S')' * randn(NumSpot,this.totNumScen,1))'; + + tmp = full(this.mCovRangeSys); + mCovFull = tmp + tmp'; + mCovFull(1:NumSpot+1:end) = diag(tmp); + [U,V,S] = eig(mCovFull); + this.vSampRangeSys = bsxfun(@plus,0,(U *real(sqrtm(V))*S')' * randn(NumSpot,this.totNumScen,1))'; + + tmp = full(this.mCovLatRnd); + mCovFull = tmp + tmp'; + mCovFull(1:NumSpot+1:end) = diag(tmp); + [U,V,S] = eig(mCovFull); + this.vSampLatRndX = bsxfun(@plus,0,(U *real(sqrtm(V))*S')' * randn(NumSpot,this.totNumScen,1))'; + this.vSampLatRndZ = bsxfun(@plus,0,(U *real(sqrtm(V))*S')' * randn(NumSpot,this.totNumScen,1))'; + + tmp = full(this.mCovLatSys); + mCovFull = tmp + tmp'; + mCovFull(1:NumSpot+1:end) = diag(tmp); + [U,V,S] = eig(mCovFull); + this.vSampLatSysX = bsxfun(@plus,0,(U *real(sqrtm(V))*S')' * randn(NumSpot,this.totNumScen,1))'; + this.vSampLatSysZ = bsxfun(@plus,0,(U *real(sqrtm(V))*S')' * randn(NumSpot,this.totNumScen,1))'; + + this.scenProb = ones(this.totNumScen,1) * 1/this.totNumScen; + end - end % end public methods - - - % public static methods go here, they can be called without creating an - % instance of this class - methods(Static) - - end % end static public methods - - - %private methods go here - methods(Access = private) - - %% - % set parameters according to the choosing scenario generation type - function this = getMultScenParam(ct,this) - - % set all parameters according to the choosen scenario creation - % type - if isempty(ct) - this.numOfCtScen = 1; - else - this.numOfCtScen = ct.numOfCtScen; - end - - this.numOfShiftScen = this.(['numOfShiftScen_' this.TYPE]); - this.shiftSize = this.(['shiftSize_' this.TYPE]); - this.shiftGenType = this.(['shiftGenType_' this.TYPE]); - this.shiftCombType = this.(['shiftCombType_' this.TYPE]); - this.numOfRangeShiftScen = this.(['numOfRangeShiftScen_' this.TYPE]); - this.maxAbsRangeShift = this.(['maxAbsRangeShift_' this.TYPE]); - - this.maxRelRangeShift = this.(['maxRelRangeShift_' this.TYPE]); - this.rangeCombType = this.(['rangeCombType_' this.TYPE]); - this.rangeGenType = this.(['rangeGenType_' this.TYPE]); - - this.scenCombType = this.(['scenCombType_' this.TYPE]); - this.includeNomScen = this.(['includeNomScen_' this.TYPE]); - end - - - %% - % creates individual treatment planning scenarios - function this = setMultScen(this) - - if this.includeNomScen - nomScen = 0; - else - nomScen = []; - end + % + function this = setCovarianceMatrix(this,ct,cst,pln,stf) + + % create APM specific error correlation matrices + if sum(strcmp(this.TYPE,{'apmScen','rndScenCov'})) > 0 - %% calc setup shift scenarios - switch this.shiftGenType - case 'equidistant' - % create grid vectors - isoShiftVec{1} = [0 linspace(-this.shiftSize(1), this.shiftSize(1), this.numOfShiftScen(1))]; - isoShiftVec{2} = [0 linspace(-this.shiftSize(2), this.shiftSize(2), this.numOfShiftScen(2))]; - isoShiftVec{3} = [0 linspace(-this.shiftSize(3), this.shiftSize(3), this.numOfShiftScen(3))]; - case 'sampled' - meanP = zeros(1,3); % mean (parameter) - rng('shuffle'); - isoShiftVec{1} = [0 this.shiftSD(1) .* randn(1, this.numOfShiftScen(1)) + meanP(1)]; - rng('shuffle'); - isoShiftVec{2} = [0 this.shiftSD(2) .* randn(1, this.numOfShiftScen(2)) + meanP(2)]; - rng('shuffle'); - isoShiftVec{3} = [0 this.shiftSD(3) .* randn(1, this.numOfShiftScen(3)) + meanP(3)]; - otherwise - matRad_dispToConsole('did not expect that','error'); + options = matRad_probOptions(ct,cst,pln); + robID = ''; + for oo = 1:size(cst,1) + for pp = 1:numel(cst{oo,6}) + if ~strcmp(cst{oo,6}(pp).robustness,'none') + robID = 'Rob'; break; + end + end end - - % create scenMaskIso for isoShifts - numIso(1) = numel(isoShiftVec{1}); - numIso(2) = numel(isoShiftVec{2}); - numIso(3) = numel(isoShiftVec{3}); - scenMaskIso = false(numIso(1), numIso(2), numIso(3)); + numComp = 10; - switch this.shiftCombType - case 'individual' - scenMaskIso(:,1,1) = true; % x shifts - scenMaskIso(1,:,1) = true; % y shifts - scenMaskIso(1,1,:) = true; % z shifts - case 'permuted' - scenMaskIso(:,:,:) = true; - case 'combined' - % determine that matrix is cubic - if isequal(numIso(1), numIso(2), numIso(3)) - for i = 1:numIso(1) - scenMaskIso(i,i,i) = true; - end - else - this.shiftCombType = 'individual'; - matRad_dispToConsole('Numnber of isoShifts in every direction has to be equal in order to perform direct combination. Performing individually instead.',[],'warning'); - % call the function itself to get a working combination - [this] = setMultScen(this); - end - otherwise - matRad_dispToConsole('Uncaught exception. Probably TYPO.','error'); + if strcmp(pln.radiationMode,'carbon') + numComp = 13; end - % create list of increasing integers with referenced scenario - [xIso, yIso, zIso] = ind2sub(size(scenMaskIso),find(scenMaskIso)); - - matchMaskIso = cell(numel(xIso),2); - for i = 1:numel(xIso) - matchMaskIso{i,1} = i; - matchMaskIso{i,2} = [xIso(i) yIso(i) zIso(i)]; - end - - % create isoShift vector based on the matrix and matching - this.isoShift = zeros(size(matchMaskIso,1),3); - if numel(isoShiftVec{1}) + numel(isoShiftVec{2}) + numel(isoShiftVec{3}) > 0 - for i = 1:size(matchMaskIso,1) - matchPos = num2cell(matchMaskIso{i,2}); - if ~isequal([isoShiftVec{1}(matchPos{1}) isoShiftVec{2}(matchPos{2}) isoShiftVec{3}(matchPos{3})],[0 0 0]) || i == 1 - this.isoShift(i,:) = [isoShiftVec{1}(matchPos{1}) isoShiftVec{2}(matchPos{2}) isoShiftVec{3}(matchPos{3})] * ... - scenMaskIso(matchPos{:}); - end + if strcmp(robID,'') && strcmp(options.probOpt.InputUCT,'phys') + this.mCovSpot= zeros(numComp); + else + for j = 1:numComp + for m = 1:numComp + this.mCovSpot(j,m) = exp(-(j-m)^2./(300)); + end end end - - if ~this.includeNomScen - if size(this.isoShift,1)>1 - this.isoShift = this.isoShift(2:end,:); % cut away the first (=nominal) scenario - else - this.isoShift = []; - end - end - this.totNumShiftScen = size(this.isoShift,1); % total number of shift scenarios in x,y and z direction + totalNumberOfBixels = sum([stf.totalNumOfBixels]); - %% calc range shift scenarios - switch this.rangeGenType - case 'equidistant' - this.relRangeShift = [nomScen linspace(-this.maxRelRangeShift, this.maxRelRangeShift, this.numOfRangeShiftScen)]; - this.absRangeShift = [nomScen linspace(-this.maxAbsRangeShift, this.maxAbsRangeShift, this.numOfRangeShiftScen)]; - case 'sampled' - % relRange - std = this.rangeRelSD; meanP = 0; - rng('shuffle'); - this.relRangeShift = [nomScen std .* randn(1, this.numOfRangeShiftScen) + meanP]; - % absRange - std = this.rangeAbsSD; meanP = 0; - rng('shuffle'); - this.absRangeShift = [nomScen std .* randn(1, this.numOfRangeShiftScen) + meanP]; - otherwise - matRad_dispToConsole('Not a valid type of generating data.','error'); + % obtain covariance structure + this = getBixelBeamMaps(this,pln,stf); + [mCovlatSkelet,mCovrangeSkelet,vRange] = getCorrMatrixWrapper(this,pln,stf); + + % define correlations matrix for random erros + if this.shiftSDrnd == 0 || (strcmp(options.probOpt.InputUCT,'bio') && strcmp(robID,'')) + this.mCovLatRnd = sparse(zeros(totalNumberOfBixels)); + else + this.mCovLatRnd = triu(mCovlatSkelet * this.shiftSDrnd^2); end - numOfRelRangeShift = numel(this.relRangeShift); - numOfAbsRangeShift = numel(this.absRangeShift); - - if ~isequal(numOfRelRangeShift,numOfAbsRangeShift) - matRad_dispToConsole('Number of relative and absolute range shifts must not differ.','error'); + if this.rangeSDrnd == 0 || (strcmp(options.probOpt.InputUCT,'bio') && strcmp(robID,'')) + this.mCovRangeRnd = sparse(zeros(totalNumberOfBixels)); else - this.totNumRangeScen = numOfRelRangeShift; + this.mCovRangeRnd = triu(mCovrangeSkelet * this.rangeSDrnd^2); + end + + % define correlations matrix for systematic erros + if this.shiftSDsys == 0 || (strcmp(options.probOpt.InputUCT,'bio') && strcmp(robID,'')) + this.mCovLatSys = sparse(zeros(totalNumberOfBixels)); + else + this.mCovLatSys = triu(mCovlatSkelet * this.shiftSDsys^2); end - this.relRangeShift = (this.relRangeShift./100); % consider [%] - - % check how absolute range error scenarios and relative range error scenarios should be combined - switch this.rangeCombType - case 'individual' - rangeShift = zeros(length(this.relRangeShift)*2 ,2); - for i = 1:length(this.relRangeShift) - rangeShift((i * 2)-1,1) = this.absRangeShift(i); - rangeShift((i * 2) ,2) = this.relRangeShift(i); + if this.rangeSDsys == 0 || (strcmp(options.probOpt.InputUCT,'bio') && strcmp(robID,'')) + this.mCovRangeSys = sparse(zeros(totalNumberOfBixels)); + else + this.mCovRangeSys = triu(mCovrangeSkelet * ((this.rangeSDsys./100)^2) .* (vRange' * vRange)); + end + + if this.relBioUCT == 0 || (strcmp(options.probOpt.InputUCT,'phys') && strcmp(robID,'')) + this.mCovBio = sparse(zeros(totalNumberOfBixels)); + else + this.mCovBio = triu(ones(totalNumberOfBixels));%mCovrangeSkelet; + end + + end + end + + + + end % end public methods + + + % public static methods go here, they can be called without creating an + % instance of this class + methods(Static) + + %create correlation matrices for APM and future random sampling) + function covMatrix = createCorrelationMatrix(stf,bixelIndexBeamOffset,bixelIndexRayOffset,errorType,corrModel) + + totalNumOfBixels = sum([stf.totalNumOfBixels]); + covMatrix = spalloc(totalNumOfBixels,totalNumOfBixels,1); + + switch corrModel + case {'correlated'} + + warning('creating correlated covariance matrixes may lead to memory overflow'); + %covMatrix = spalloc(pln.uct.totalNumOfBixels,pln.uct.totalNumOfBixels,pln.uct.totalNumOfBixels*pln.uct.totalNumOfBixels) + covMatrix = ones(totalNumOfBixels); + + case {'block'} + + if strcmp(errorType,'setup') + % bixels belonging to the same beam direction are perfectly correlated + for idx = 2:length(bixelIndexBeamOffset) + covMatrix(bixelIndexBeamOffset(idx-1):bixelIndexBeamOffset(idx),... + bixelIndexBeamOffset(idx-1):bixelIndexBeamOffset(idx)) = 1; %#ok<*SPRIX> end - - this.totNumRangeScen = (numOfRelRangeShift*2)-1; - this.absRangeShift = [this.absRangeShift zeros(1,length(this.relRangeShift(this.relRangeShift~=0)))]; - this.relRangeShift = [zeros(1,length(this.relRangeShift)) this.relRangeShift(this.relRangeShift~=0)] ; - - case 'combined' - rangeShift = zeros(length(this.relRangeShift),2); - for i = 1:1:length(this.relRangeShift) - rangeShift(i,1) = this.absRangeShift(i); - rangeShift(i,2) = this.relRangeShift(i); + + elseif strcmp(errorType,'range') + % bixels belonging to the same beam and impinging at the + % same lateral position having different energies are + % considered perfectly correlated as they penetrate the + % same tissue on their ray + for idx = 2:1:length(bixelIndexRayOffset) + + if idx == 2 + covMatrix(bixelIndexRayOffset(idx-1):bixelIndexRayOffset(idx),... + bixelIndexRayOffset(idx-1):bixelIndexRayOffset(idx)) = 1; + else + covMatrix(bixelIndexRayOffset(idx-1)+1:bixelIndexRayOffset(idx),... + bixelIndexRayOffset(idx-1)+1:bixelIndexRayOffset(idx)) = 1; + end + end - - otherwise + + end + + case {'uncorrelated'} + covMatrix = speye(totalNumOfBixels,totalNumOfBixels); + end + + end %eof createCorrelationMatrix + + + end % end static public methods + + + %private methods go here + methods(Access = private) + + %% + % set parameters according to the choosing scenario generation type + function this = getMultScenParam(ct,this) + + % set all parameters according to the choosen scenario creation + % type + if isempty(ct) + this.numOfCtScen = 1; + else + this.numOfCtScen = ct.numOfCtScen; + end + + this.numOfShiftScen = this.(['numOfShiftScen_' this.TYPE]); + this.shiftSize = this.(['shiftSize_' this.TYPE]); + this.shiftGenType = this.(['shiftGenType_' this.TYPE]); + this.shiftCombType = this.(['shiftCombType_' this.TYPE]); + this.numOfRangeShiftScen = this.(['numOfRangeShiftScen_' this.TYPE]); + this.maxAbsRangeShift = this.(['maxAbsRangeShift_' this.TYPE]); + + this.maxRelRangeShift = this.(['maxRelRangeShift_' this.TYPE]); + this.rangeCombType = this.(['rangeCombType_' this.TYPE]); + this.rangeGenType = this.(['rangeGenType_' this.TYPE]); + + this.scenCombType = this.(['scenCombType_' this.TYPE]); + this.includeNomScen = this.(['includeNomScen_' this.TYPE]); + + end + + + %% + % creates individual treatment planning scenarios + function this = setMultScen(this) + + if this.includeNomScen + nomScen = 0; + else + nomScen = []; + end + + %% calc setup shift scenarios + switch this.shiftGenType + case 'equidistant' + % create grid vectors + isoShiftVec{1} = [0 linspace(-this.shiftSize(1), this.shiftSize(1), this.numOfShiftScen(1))]; + isoShiftVec{2} = [0 linspace(-this.shiftSize(2), this.shiftSize(2), this.numOfShiftScen(2))]; + isoShiftVec{3} = [0 linspace(-this.shiftSize(3), this.shiftSize(3), this.numOfShiftScen(3))]; + case 'sampled' + meanP = zeros(1,3); % mean (parameter) + rng('shuffle'); + isoShiftVec{1} = [0 this.shiftSD(1) .* randn(1, this.numOfShiftScen(1)) + meanP(1)]; + rng('shuffle'); + isoShiftVec{2} = [0 this.shiftSD(2) .* randn(1, this.numOfShiftScen(2)) + meanP(2)]; + rng('shuffle'); + isoShiftVec{3} = [0 this.shiftSD(3) .* randn(1, this.numOfShiftScen(3)) + meanP(3)]; + otherwise + matRad_dispToConsole('did not expect that','error'); + end + + % create scenMaskIso for isoShifts + numIso(1) = numel(isoShiftVec{1}); + numIso(2) = numel(isoShiftVec{2}); + numIso(3) = numel(isoShiftVec{3}); + + scenMaskIso = false(numIso(1), numIso(2), numIso(3)); + + switch this.shiftCombType + case 'individual' + scenMaskIso(:,1,1) = true; % x shifts + scenMaskIso(1,:,1) = true; % y shifts + scenMaskIso(1,1,:) = true; % z shifts + case 'permuted' + scenMaskIso(:,:,:) = true; + case 'combined' + % determine that matrix is cubic + if isequal(numIso(1), numIso(2), numIso(3)) + for i = 1:numIso(1) + scenMaskIso(i,i,i) = true; + end + else + this.shiftCombType = 'individual'; + matRad_dispToConsole('Numnber of isoShifts in every direction has to be equal in order to perform direct combination. Performing individually instead.',[],'warning'); + % call the function itself to get a working combination + [this] = setMultScen(this); + end + otherwise + matRad_dispToConsole('Uncaught exception. Probably TYPO.','error'); + end + + % create list of increasing integers with referenced scenario + [xIso, yIso, zIso] = ind2sub(size(scenMaskIso),find(scenMaskIso)); + + matchMaskIso = cell(numel(xIso),2); + for i = 1:numel(xIso) + matchMaskIso{i,1} = i; + matchMaskIso{i,2} = [xIso(i) yIso(i) zIso(i)]; + end + + % create isoShift vector based on the matrix and matching + this.isoShift = zeros(size(matchMaskIso,1),3); + if numel(isoShiftVec{1}) + numel(isoShiftVec{2}) + numel(isoShiftVec{3}) > 0 + for i = 1:size(matchMaskIso,1) + matchPos = num2cell(matchMaskIso{i,2}); + if ~isequal([isoShiftVec{1}(matchPos{1}) isoShiftVec{2}(matchPos{2}) isoShiftVec{3}(matchPos{3})],[0 0 0]) || i == 1 + this.isoShift(i,:) = [isoShiftVec{1}(matchPos{1}) isoShiftVec{2}(matchPos{2}) isoShiftVec{3}(matchPos{3})] * ... + scenMaskIso(matchPos{:}); + end end + end + + if ~this.includeNomScen + if size(this.isoShift,1)>1 + this.isoShift = this.isoShift(2:end,:); % cut away the first (=nominal) scenario + else + this.isoShift = []; + end + end + + this.totNumShiftScen = size(this.isoShift,1); % total number of shift scenarios in x,y and z direction + + %% calc range shift scenarios + switch this.rangeGenType + case 'equidistant' + this.relRangeShift = [nomScen linspace(-this.maxRelRangeShift, this.maxRelRangeShift, this.numOfRangeShiftScen)]; + this.absRangeShift = [nomScen linspace(-this.maxAbsRangeShift, this.maxAbsRangeShift, this.numOfRangeShiftScen)]; + case 'sampled' + % relRange + std = this.rangeSDsys; meanP = 0; + rng('shuffle'); + this.relRangeShift = [nomScen std .* randn(1, this.numOfRangeShiftScen) + meanP]; + % absRange + std = this.rangeSDrnd; meanP = 0; + rng('shuffle'); + this.absRangeShift = [nomScen std .* randn(1, this.numOfRangeShiftScen) + meanP]; + otherwise + matRad_dispToConsole('Not a valid type of generating data.','error'); + end + + numOfRelRangeShift = numel(this.relRangeShift); + numOfAbsRangeShift = numel(this.absRangeShift); + + if ~isequal(numOfRelRangeShift,numOfAbsRangeShift) + matRad_dispToConsole('Number of relative and absolute range shifts must not differ.','error'); + else + this.totNumRangeScen = numOfRelRangeShift; + end + + this.relRangeShift = (this.relRangeShift./100); % consider [%] + + % check how absolute range error scenarios and relative range error scenarios should be combined + switch this.rangeCombType + case 'individual' + rangeShift = zeros(length(this.relRangeShift)*2 ,2); + for i = 1:length(this.relRangeShift) + rangeShift((i * 2)-1,1) = this.absRangeShift(i); + rangeShift((i * 2) ,2) = this.relRangeShift(i); + end + + this.totNumRangeScen = (numOfRelRangeShift*2)-1; + this.absRangeShift = [this.absRangeShift zeros(1,length(this.relRangeShift(this.relRangeShift~=0)))]; + this.relRangeShift = [zeros(1,length(this.relRangeShift)) this.relRangeShift(this.relRangeShift~=0)] ; + + case 'combined' + rangeShift = zeros(length(this.relRangeShift),2); + for i = 1:1:length(this.relRangeShift) + rangeShift(i,1) = this.absRangeShift(i); + rangeShift(i,2) = this.relRangeShift(i); + end + + otherwise + end + + % combine setup and range scenarios according to scenCombType + switch this.scenCombType - % combine setup and range scenarios according to scenCombType + case 'individual' % combine setup and range scenarios individually + + % range errors should come first + if this.includeNomScen + this.scenForProb = zeros(size(this.isoShift,1)-1 + size(rangeShift,1),5); + this.scenForProb(1:size(rangeShift,1),4:5) = rangeShift; + this.scenForProb(size(rangeShift,1)+1:end,1:3) = this.isoShift(2:end,:); + else + this.scenForProb = zeros(size(this.isoShift,1) + size(rangeShift,1),5); + this.scenForProb(1:size(rangeShift,1),4:5) = rangeShift; + this.scenForProb(size(rangeShift,1)+1:end,1:3) = this.isoShift; + end + + case 'permuted' + + this.scenForProb = zeros(size(this.isoShift,1) * size(rangeShift,1),5); + Cnt = 1; + for i = 1:size(this.isoShift,1) + for j = 1:size(rangeShift,1) + this.scenForProb(Cnt,:) = [this.isoShift(i,:) rangeShift(j,:)]; + Cnt = Cnt + 1; + end + end + + case 'combined' + + if size(this.isoShift,1) == size(rangeShift,1) && this.totNumShiftScen > 0 && this.totNumRangeScen > 0 + this.scenForProb = zeros(size(this.isoShift,1),5); + this.scenForProb(1:end,1:3) = this.isoShift; + this.scenForProb(1:end,4:5) = rangeShift; + else + matRad_dispToConsole('number of setup and range scenarios MUST be the same \n',[],'warning'); + this.scenCombType = 'individual'; + multScen = setMultScen(this); + this.scenForProb = multScen.scenForProb; + end + end + + % sanity check + UniqueRowScenForProb = unique(this.scenForProb,'rows'); + + if size(UniqueRowScenForProb,1) ~= size(this.scenForProb,1) && size(UniqueRowScenForProb,1)>1 + matRad_dispToConsole('Some scenarios seem to be defined multiple times',[],'warning'); + end + + %% setup and fill combinatorics mask + % 1st dim: ct scenarios, + % 2nd dim: shift scenarios, + % 3rd dim: range scenarios + this.scenMask = false(this.numOfCtScen, this.totNumShiftScen, this.totNumRangeScen); + this.scenMask(:,1,1) = true; % ct scenarios + + % switch between combination modes here + % only makes scence when numOfShiftScen>0 and numOfRangeShiftScen>0; + if this.totNumShiftScen > 0 && this.totNumRangeScen > 0 switch this.scenCombType - - case 'individual' % combine setup and range scenarios individually - - % range errors should come first - if this.includeNomScen - this.scenForProb = zeros(size(this.isoShift,1)-1 + size(rangeShift,1),5); - this.scenForProb(1:size(rangeShift,1),4:5) = rangeShift; - this.scenForProb(size(rangeShift,1)+1:end,1:3) = this.isoShift(2:end,:); - else - this.scenForProb = zeros(size(this.isoShift,1) + size(rangeShift,1),5); - this.scenForProb(1:size(rangeShift,1),4:5) = rangeShift; - this.scenForProb(size(rangeShift,1)+1:end,1:3) = this.isoShift; - end - - case 'permuted' - - this.scenForProb = zeros(size(this.isoShift,1) * size(rangeShift,1),5); - Cnt = 1; - for i = 1:size(this.isoShift,1) - for j = 1:size(rangeShift,1) - this.scenForProb(Cnt,:) = [this.isoShift(i,:) rangeShift(j,:)]; - Cnt = Cnt + 1; - end - end - - case 'combined' - - if size(this.isoShift,1) == size(rangeShift,1) && this.totNumShiftScen > 0 && this.totNumRangeScen > 0 - this.scenForProb = zeros(size(this.isoShift,1),5); - this.scenForProb(1:end,1:3) = this.isoShift; - this.scenForProb(1:end,4:5) = rangeShift; - else - matRad_dispToConsole('number of setup and range scenarios MUST be the same \n',[],'warning'); - this.scenCombType = 'individual'; - multScen = setMultScen(this); - this.scenForProb = multScen.scenForProb; - end + case 'individual' + % get all setup scenarios + [~,ixUnq] = unique(this.scenForProb(:,1:3),'rows','stable'); + this.scenMask = false(this.numOfCtScen, length(ixUnq), this.totNumRangeScen); + this.scenMask(1,:,1) = true; % iso shift scenarios + this.scenMask(1,1,:) = true; % range shift scenarios + case 'permuted' + this.scenMask(:,:,:) = true; + case 'combined' + % check if scenForProb is cubic (ignore ct scen) - if so fill diagonal + if isequal(this.totNumShiftScen, this.totNumRangeScen) + for i = 1:size(this.scenForProb, 1) + this.scenMask(1,i,i) = true; + end + else + this.shiftCombType = 'individual'; + matRad_dispToConsole('number of setup and range scenarios MUST be the same \n',[],'warning'); + this = setMultScen(this); + end end + end + + + % create linearalized mask where the i row points to the indexes of scenMask + [x{1}, x{2}, x{3}] = ind2sub(size(this.scenMask),find(this.scenMask)); + this.linearMask = cell2mat(x); + this.totNumScen = size(this.scenForProb,1); + + end % end of setMultScen + + + %% + % provides different ways of calculating the probability + % of occurance of individual scenarios + function this = calcScenProb(this) + + mu = [0 0 0 0 0]; + sigma = [this.shiftSD this.rangeSDrnd this.rangeSDsys/100]; + + if isequal(this.DEFAULT_probDist,'normDist') - % sanity check - UniqueRowScenForProb = unique(this.scenForProb,'rows'); - - if size(UniqueRowScenForProb,1) ~= size(this.scenForProb,1) && size(UniqueRowScenForProb,1)>1 - matRad_dispToConsole('Some scenarios seem to be defined multiple times',[],'warning'); - end - - %% setup and fill combinatorics mask - % 1st dim: ct scenarios, - % 2nd dim: shift scenarios, - % 3rd dim: range scenarios - this.scenMask = false(this.numOfCtScen, this.totNumShiftScen, this.totNumRangeScen); - this.scenMask(:,1,1) = true; % ct scenarios - - % switch between combination modes here - % only makes scence when numOfShiftScen>0 and numOfRangeShiftScen>0; - if this.totNumShiftScen > 0 && this.totNumRangeScen > 0 - switch this.scenCombType - case 'individual' - % get all setup scenarios - [~,ixUnq] = unique(this.scenForProb(:,1:3),'rows','stable'); - this.scenMask = false(this.numOfCtScen, length(ixUnq), this.totNumRangeScen); - this.scenMask(1,:,1) = true; % iso shift scenarios - this.scenMask(1,1,:) = true; % range shift scenarios - case 'permuted' - this.scenMask(:,:,:) = true; - case 'combined' - % check if scenForProb is cubic (ignore ct scen) - if so fill diagonal - if isequal(this.totNumShiftScen, this.totNumRangeScen) - for i = 1:size(this.scenForProb, 1) - this.scenMask(1,i,i) = true; - end - else - this.shiftCombType = 'individual'; - matRad_dispToConsole('number of setup and range scenarios MUST be the same \n',[],'warning'); - this = setMultScen(this); - end + this.scenProb = 1; + + if isequal(this.DEFAULT_TypeProp,'probBins') + + for i = 1:length(mu) + samplePosSorted = sort(unique(this.scenForProb(:,i))); + if numel(samplePosSorted) == 1 || sigma(i) == 0 + continue; + end + binWidth = (samplePosSorted(2) - samplePosSorted(1)); + lowerBinLevel = this.scenForProb(:,i) - 0.5*binWidth; + upperBinLevel = this.scenForProb(:,i) + 0.5*binWidth; + + this.scenProb = this.scenProb.*0.5.*(erf((upperBinLevel-mu(i))/(sqrt(2)*sigma(i)))-erf((lowerBinLevel-mu(i))/(sqrt(2)*sigma(i)))); + end + + elseif isequal(this.DEFAULT_TypeProp,'pointwise') + for i = 1:length(mu) + this.scenProb = this.scenProb .* (1/sqrt(2*pi*sigma(i)^2)*exp(-((this.scenForProb(:,i)-mu(i)).^2./(2*sigma(i)^2)))); end end - - % create linearalized mask where the i row points to the indexes of scenMask - [x{1}, x{2}, x{3}] = ind2sub(size(this.scenMask),find(this.scenMask)); - this.linearMask = cell2mat(x); - this.totNumScen = size(this.scenForProb,1); + % normalize probabilities since we use only a subset of + % the 3D grid + this.scenProb = this.scenProb./sum(this.scenProb); - end % end of setMultScen - - - %% - % provides different ways of calculating the probability - % of occurance of individual scenarios - function this = calcScenProb(this) - - mu = [0 0 0 0 0]; - sigma = [this.shiftSD this.rangeAbsSD this.rangeRelSD/100]; - - if isequal(this.DEFAULT_probDist,'normDist') - - this.scenProb = 1; - - if isequal(this.DEFAULT_TypeProp,'probBins') - - for i = 1:length(mu) - samplePosSorted = sort(unique(this.scenForProb(:,i))); - if numel(samplePosSorted) == 1 || sigma(i) == 0 - continue; - end - binWidth = (samplePosSorted(2) - samplePosSorted(1)); - lowerBinLevel = this.scenForProb(:,i) - 0.5*binWidth; - upperBinLevel = this.scenForProb(:,i) + 0.5*binWidth; - - this.scenProb = this.scenProb.*0.5.*(erf((upperBinLevel-mu(i))/(sqrt(2)*sigma(i)))-erf((lowerBinLevel-mu(i))/(sqrt(2)*sigma(i)))); - end - - elseif isequal(this.DEFAULT_TypeProp,'pointwise') - for i = 1:length(mu) - this.scenProb = this.scenProb .* (1/sqrt(2*pi*sigma(i)^2)*exp(-((this.scenForProb(:,i)-mu(i)).^2./(2*sigma(i)^2)))); - end - end - - % normalize probabilities since we use only a subset of - % the 3D grid - this.scenProb = this.scenProb./sum(this.scenProb); - - elseif isequal(probDist,'equalProb') - - numScen = size(samplePos,1); - this.scenProb = repmat(1/numScen,1,numScen); - - else - matRad_dispToConsole('Until now, only normally distributed scenarios implemented',[],'error') - end + elseif isequal(probDist,'equalProb') - - end % end of calcScenProb - - - - + numScen = size(samplePos,1); + this.scenProb = repmat(1/numScen,1,numScen); + + else + matRad_dispToConsole('Until now, only normally distributed scenarios implemented',[],'error') + end + + + end % end of calcScenProb + + + %% + % get helper objects for the creation of uncertainty covariance matrices + function this = getBixelBeamMaps(this,pln,stf) + + % calculate total number of bixels from steering file + totalNumOfBixels = sum([stf(:).totalNumOfBixels]); + + %Get for each bixel the beam index and ray index + this.bixelIndexBeamOffset = [1 cumsum([stf(:).totalNumOfBixels])]; + this.bixelIndexRayOffset = [1 cumsum([stf(:).numOfBixelsPerRay])]; + + % create spot beam and spot ray look up tables + this.bixelBeamLUT = zeros(totalNumOfBixels,1); + this.bixelRayLUT = zeros(totalNumOfBixels,1); + this.bixelEnergyLUT = zeros(totalNumOfBixels,1); + + cntBeam = 1; cntRay = 1; cntBixel = 1; + % loop over all beams + for i = 1:pln.propStf.numOfBeams + this.bixelBeamLUT(cntBeam:cntBeam+stf(i).totalNumOfBixels-1) = i; + cntBeam = cntBeam + stf(i).totalNumOfBixels; + % loop over all rays per beam + for j = 1:stf(i).numOfRays + numSpots = length(stf(i).ray(j).energy); + this.bixelRayLUT(cntBixel:cntBixel + numSpots -1 ) = cntRay; + this.bixelEnergyLUT(cntBixel:cntBixel + numSpots -1) = stf(i).ray(j).energyIx; + cntRay = cntRay + 1; + cntBixel = cntBixel + numSpots; + end + cntRay = 1; + end + + + end %eof getBixelBeamMaps + + %% + % calculate covariance matrices for setup and range error + function [mCovlatSkelet,mCovrangeSkelet,vRange] = getCorrMatrixWrapper(this,pln,stf) + + mCovlatSkelet = this.createCorrelationMatrix(stf,this.bixelIndexBeamOffset,this.bixelIndexRayOffset,'setup',this.corrModel) ; + mCovrangeSkelet = this.createCorrelationMatrix(stf,this.bixelIndexBeamOffset,this.bixelIndexRayOffset,'range',this.corrModel) ; + + % obtain ranges + % prepare structures necessary for particles + fileName = [pln.radiationMode '_' pln.machine]; + try + load([fileparts(mfilename('fullpath')) filesep fileName]); + catch + matRad_dispToConsole(['Could not find the following machine file: ' fileName ],param,'error'); + end + + vRange = []; % range of bixel in mm + for i = 1:size(stf,2) + for j = 1:stf(i).numOfRays + vRange = [vRange round([machine.data(stf(i).ray(j).energyIx).range])]; %#ok + end + end + + %figure, + %subplot(121),spy(this.mCOVlat) + %subplot(121),spy(this.mCOVrange) + + end %eof + + end % end private methods diff --git a/matRad_setOverlapPriorities.m b/matRad_setOverlapPriorities.m index b9dac2acb..5a96abd22 100644 --- a/matRad_setOverlapPriorities.m +++ b/matRad_setOverlapPriorities.m @@ -1,4 +1,4 @@ -function [cst,overlapPriorityCube] = matRad_setOverlapPriorities(cst,ctDim) +function [cst,voiIndexCube,overlapPriorityCube] = matRad_setOverlapPriorities(cst,ctDim) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % function to handle overlap priorities during fluence optimizaiton and % dose calculation. If you have overlapping volumes of interest you need to @@ -66,14 +66,22 @@ end end -%Calculate the overlap cube if requested if nargout == 2 && nargin == 2 + voiIndexCube = zeros(ctDim); + for i = 1:size(cst,1) + voiIndexCube(cst{i,4}{1}) = i; + end +end + +%Calculate the overlap cube if requested +if nargout == 3 && nargin == 2 overlapPriorityCube = zeros(ctDim); for i = 1:size(cst,1) overlapPriorityCube(cst{i,4}{1}) = cst{i,5}.Priority; end end + end diff --git a/optimization/matRad_constFuncWrapper.m b/optimization/matRad_constFuncWrapper.m index b16a827b3..d1b6518ae 100644 --- a/optimization/matRad_constFuncWrapper.m +++ b/optimization/matRad_constFuncWrapper.m @@ -35,7 +35,7 @@ % get current dose / effect / RBExDose vector -d = matRad_backProjection(w,dij,cst,options); +[d,d_exp,~] = matRad_backProjection(w,dij,cst,options); % Initializes constraints c = []; @@ -71,17 +71,23 @@ c = [c; matRad_constFunc(d_i,cst{i,6}(j),d_ref)]; % if rob opt: add constraints of all dose scenarios - elseif strcmp(cst{i,6}(j).robustness,'probabilistic') || strcmp(cst{i,6}(j).robustness,'VWWC') || strcmp(cst{i,6}(j).robustness,'COWC') - - for k = 1:options.numOfScenarios - - d_i = d{k}(cst{i,4}{1}); - - c = [c; matRad_constFunc(d_i,cst{i,6}(j),d_ref)]; - - end - - + elseif strcmp(cst{i,6}(j).robustness,'PROB') + + d_i = d_exp{1}(cst{i,4}{1}); + + c = [c; matRad_constFunc(d_i,cst{i,6}(j),d_ref)]; + + elseif strcmp(cst{i,6}(j).robustness,'VWWC') || strcmp(cst{i,6}(j).robustness,'COWC') + + for k = 1:options.numOfScenarios + + d_i = d{k}(cst{i,4}{1}); + + c = [c; matRad_constFunc(d_i,cst{i,6}(j),d_ref)]; + + end + + else error('Invalid robustness setting.'); diff --git a/optimization/matRad_getConstBoundsWrapper.m b/optimization/matRad_getConstBoundsWrapper.m index 3ed7672b3..0f7f8be27 100644 --- a/optimization/matRad_getConstBoundsWrapper.m +++ b/optimization/matRad_getConstBoundsWrapper.m @@ -54,7 +54,7 @@ param = cst{i,6}(j).dose; end - if strcmp(cst{i,6}(j).robustness,'none') || strcmp(cst{i,6}(j).robustness,'probabilistic') || strcmp(cst{i,6}(j).robustness,'VWWC') ||... + if strcmp(cst{i,6}(j).robustness,'none') || strcmp(cst{i,6}(j).robustness,'PROB') || strcmp(cst{i,6}(j).robustness,'VWWC') ||... strcmp(cst{i,6}(j).robustness,'COWC') || strcmp(cst{i,6}(j).robustness,'VWWC_CONF') diff --git a/optimization/matRad_getJacobStruct.m b/optimization/matRad_getJacobStruct.m index 8215286a7..37d25fb87 100644 --- a/optimization/matRad_getJacobStruct.m +++ b/optimization/matRad_getJacobStruct.m @@ -88,7 +88,7 @@ if ~isempty(strfind(cst{i,6}(j).type,'constraint')) % if conventional opt: just add constraints of nominal dose - if strcmp(cst{i,6}(j).robustness,'none') || strcmp(cst{i,6}(j).robustness,'probabilistic') || ... + if strcmp(cst{i,6}(j).robustness,'none') || strcmp(cst{i,6}(j).robustness,'PROB') || ... strcmp(cst{i,6}(j).robustness,'VWWC') || strcmp(cst{i,6}(j).robustness,'COWC') || strcmp(cst{i,6}(j).robustness,'VWWC_CONF') if isequal(cst{i,6}(j).type, 'max dose constraint') || ... diff --git a/optimization/matRad_gradFuncWrapper.m b/optimization/matRad_gradFuncWrapper.m index fb81995d3..e7f297037 100644 --- a/optimization/matRad_gradFuncWrapper.m +++ b/optimization/matRad_gradFuncWrapper.m @@ -72,7 +72,7 @@ if (~isequal(cst{i,6}(j).type, 'mean') && ~isequal(cst{i,6}(j).type, 'EUD')) &&... isequal(options.quantityOpt,'effect') - d_ref = cst{i,5}.alphaX*cst{i,6}(j).dose + cst{i,5}.betaX*cst{i,6}(j).dose^2; + d_ref = cst{i,5}.alphaX.*cst{i,6}(j).dose + cst{i,5}.betaX.*cst{i,6}(j).dose.^2; else d_ref = cst{i,6}(j).dose; end @@ -214,7 +214,7 @@ vBias = (delta_exp{i}' * dij.mAlphaDoseExp{1})'; quadTerm = dij.mSqrtBetaDoseExp{1} * w; mPsi = (2*(delta_exp{i}.*quadTerm)'*dij.mSqrtBetaDoseExp{1})'; - g = g + vBias + mPsi ; + g = g + vBias + mPsi + (2 * vOmega); end elseif isequal(options.quantityOpt,'RBExD') @@ -240,3 +240,21 @@ end end + +RUN_GRADIENT_CHECKER = false; + +if RUN_GRADIENT_CHECKER + f = matRad_objFuncWrapper(w,dij,cst,options); + epsilon = 1e-6; + ix = unique(randi([1 numel(w)],1,5)); + + for i = ix + wInit = w; + wInit(i) = wInit(i) + epsilon; + fDelta = matRad_objFuncWrapper(wInit,dij,cst,options); + numGrad = (fDelta-f)/epsilon; + diff = (numGrad/g(i)-1)*100; + fprintf(['Component # ' num2str(i) ' - rel diff of numerical and analytical gradient = ' num2str(diff) '\n']); + end +end + diff --git a/optimization/matRad_ipoptOptions.m b/optimization/matRad_ipoptOptions.m index f52ad6dfe..9820f3309 100644 --- a/optimization/matRad_ipoptOptions.m +++ b/optimization/matRad_ipoptOptions.m @@ -31,12 +31,12 @@ options.ipopt.constr_viol_tol = 1e-4; % (Opt3) options.ipopt.compl_inf_tol = 1e-4; % (Opt4), Optimal Solution Found if (Opt1),...,(Opt4) fullfiled -options.ipopt.acceptable_iter = 3; % (Acc1) -options.ipopt.acceptable_tol = 1e10; % (Acc2) -options.ipopt.acceptable_constr_viol_tol = 1e10; % (Acc3) -options.ipopt.acceptable_dual_inf_tol = 1e10; % (Acc4) -options.ipopt.acceptable_compl_inf_tol = 1e10; % (Acc5) -options.ipopt.acceptable_obj_change_tol = 1e-3; % (Acc6), Solved To Acceptable Level if (Acc1),...,(Acc6) fullfiled +% options.ipopt.acceptable_iter = 3; % (Acc1) +% options.ipopt.acceptable_tol = 1e10; % (Acc2) +% options.ipopt.acceptable_constr_viol_tol = 1e10; % (Acc3) +% options.ipopt.acceptable_dual_inf_tol = 1e10; % (Acc4) +% options.ipopt.acceptable_compl_inf_tol = 1e10; % (Acc5) +% options.ipopt.acceptable_obj_change_tol = 1e-3; % (Acc6), Solved To Acceptable Level if (Acc1),...,(Acc6) fullfiled options.ipopt.max_iter = 500; options.ipopt.max_cpu_time = 3000; diff --git a/optimization/matRad_jacobFuncWrapper.m b/optimization/matRad_jacobFuncWrapper.m index e5bc71751..65bca2c8b 100644 --- a/optimization/matRad_jacobFuncWrapper.m +++ b/optimization/matRad_jacobFuncWrapper.m @@ -34,7 +34,7 @@ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % get current dose / effect / RBExDose vector -d = matRad_backProjection(w,dij,cst,options); +[d,d_exp,~] = matRad_backProjection(w,dij,cst,options); % initialize jacobian jacob = sparse([]); @@ -72,16 +72,20 @@ end % if conventional opt: just add constraints of nominal dose - if strcmp(cst{i,6}(j).robustness,'none') - - d_i = d{1}(cst{i,4}{1}); - - jacobVec = matRad_jacobFunc(d_i,cst{i,6}(j),d_ref); - - scenID = [scenID;1]; - scenID2 = [scenID2;ones(numel(cst{i,4}{1}),1)]; - - if isequal(options.quantityOpt,'physicalDose') && ~isempty(jacobVec) || isequal(options.model,'constRBE') + if strcmp(cst{i,6}(j).robustness,'none') || strcmp(cst{i,6}(j).robustness,'PROB') + + if strcmp(cst{i,6}(j).robustness,'none') + d_i = d{1}(cst{i,4}{1}); + elseif strcmp(cst{i,6}(j).robustness,'PROB') + d_i = d_exp{1}(cst{i,4}{1}); + end + + jacobVec = matRad_jacobFunc(d_i,cst{i,6}(j),d_ref); + + scenID = [scenID;1]; + scenID2 = [scenID2;ones(numel(cst{i,4}{1}),1)]; + + if isequal(options.quantityOpt,'physicalDose') && ~isempty(jacobVec) || isequal(options.model,'constRBE') DoseProjection = [DoseProjection,sparse(cst{i,4}{1},1,jacobVec,dij.numOfVoxels,1)]; @@ -95,9 +99,9 @@ elseif isequal(options.quantityOpt,'RBExD') && ~isempty(jacobVec) - scaledEffect = (dij.gamma(cst{i,4}{1}) + d_i); + scaledEffect = (dij.gamma(cst{i,4}{1}) + d_i); - delta = jacobVec./(2*dij.bx(cst{i,4}{1}).*scaledEffect); + delta = jacobVec./(2*dij.betaX(cst{i,4}{1}).*scaledEffect); mAlphaDoseProjection = [mAlphaDoseProjection,sparse(cst{i,4}{1},1,delta,dij.numOfVoxels,1)]; mSqrtBetaDoseProjection = [mSqrtBetaDoseProjection,... @@ -122,7 +126,7 @@ end % Calculate jacobian with dij projections -for i = 1:dij.numOfScenarios +for i = 1:options.numOfScen % enter if statement also for protons using a constant RBE if isequal(options.quantityOpt,'physicalDose') || isequal(options.model,'constRBE') diff --git a/optimization/matRad_objFuncWrapper.m b/optimization/matRad_objFuncWrapper.m index e519882cc..a255c73ef 100644 --- a/optimization/matRad_objFuncWrapper.m +++ b/optimization/matRad_objFuncWrapper.m @@ -63,7 +63,7 @@ if (~isequal(cst{i,6}(j).type, 'mean') && ~isequal(cst{i,6}(j).type, 'EUD')) &&... isequal(options.quantityOpt,'effect') - d_ref = cst{i,5}.alphaX*cst{i,6}(j).dose + cst{i,5}.betaX*cst{i,6}(j).dose^2; + d_ref = cst{i,5}.alphaX.*cst{i,6}(j).dose + cst{i,5}.betaX.*cst{i,6}(j).dose.^2; else d_ref = cst{i,6}(j).dose; end diff --git a/protons_GenericAPM.mat b/protons_GenericAPM.mat new file mode 100644 index 000000000..4dda35288 Binary files /dev/null and b/protons_GenericAPM.mat differ