forked from e0404/matRad
-
Notifications
You must be signed in to change notification settings - Fork 0
/
matRad_calcPhotonDose.m
315 lines (257 loc) · 13.8 KB
/
matRad_calcPhotonDose.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
function dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect)
% matRad photon dose calculation wrapper
%
% call
% dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect)
%
% input
% ct: ct cube
% stf: matRad steering information struct
% pln: matRad plan meta information struct
% cst: matRad cst struct
% calcDoseDirect: boolian switch to bypass dose influence matrix
% computation and directly calculate dose; only makes
% sense in combination with matRad_calcDoseDirect.m
%
% output
% dij: matRad dij struct
%
% References
% [1] http://www.ncbi.nlm.nih.gov/pubmed/8497215
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Copyright 2015 the matRad development team.
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
matRad_cfg = MatRad_Config.instance();
% initialize
matRad_calcDoseInit;
[env, ~] = matRad_getEnvironment();
switch env
case 'MATLAB'
rng('default');
case 'OCTAVE'
rand('seed',0)
end
% issue warning if biological optimization not possible
if sum(strcmp(pln.propOpt.bioOptimization,{'effect','RBExD'}))>0
warndlg('Effect based and RBE optimization not available for photons - physical optimization is carried out instead.');
pln.bioOptimization = 'none';
end
% initialize waitbar
figureWait = waitbar(0,'calculate dose influence matrix for photons...');
% show busy state
set(figureWait,'pointer','watch');
% set lateral cutoff value
if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'geometricCutOff')
pln.propDoseCalc.geometricCutOff = matRad_cfg.propDoseCalc.defaultGeometricCutOff; % [mm]
end
lateralCutoff = pln.propDoseCalc.geometricCutOff;
if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'kernelCutOff')
pln.propDoseCalc.kernelCutOff = matRad_cfg.propDoseCalc.defaultKernelCutOff; % [mm]
end
% set kernel cutoff value (determines how much of the kernel is used. This
% value is separated from lateralCutOff to obtain accurate large open fields)
kernelCutoff = pln.propDoseCalc.kernelCutOff;
if kernelCutoff < lateralCutoff
matRad_cfg.dispWarning('Kernel Cut-Off ''%f mm'' cannot be smaller than geometric lateral cutoff ''%f mm''. Using ''%f mm''!',kernelCutoff,lateralCutoff,lateralCutoff);
kernelCutoff = lateralCutoff;
end
% toggle custom primary fluence on/off. if 0 we assume a homogeneous
% primary fluence, if 1 we use measured radially symmetric data
if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'useCustomPrimaryPhotonFluence')
useCustomPrimFluenceBool = matRad_cfg.propDoseCalc.defaultUseCustomPrimaryPhotonFluence;
else
useCustomPrimFluenceBool = pln.propDoseCalc.useCustomPrimaryPhotonFluence;
end
% 0 if field calc is bixel based, 1 if dose calc is field based
isFieldBasedDoseCalc = strcmp(num2str(pln.propStf.bixelWidth),'field');
%% kernel convolution
% set up convolution grid
if isFieldBasedDoseCalc
% get data from DICOM import
intConvResolution = pln.propStf.collimation.convResolution;
fieldWidth = pln.propStf.collimation.fieldWidth;
else
intConvResolution = .5; % [mm]
fieldWidth = pln.propStf.bixelWidth;
end
% calculate field size and distances
fieldLimit = ceil(fieldWidth/(2*intConvResolution));
[F_X,F_Z] = meshgrid(-fieldLimit*intConvResolution: ...
intConvResolution: ...
(fieldLimit-1)*intConvResolution);
% gaussian filter to model penumbra from (measured) machine output / see
% diploma thesis siggel 4.1.2 -> https://github.com/e0404/matRad/wiki/Dose-influence-matrix-calculation
if isfield(machine.data,'penumbraFWHMatIso')
penumbraFWHM = machine.data.penumbraFWHMatIso;
else
penumbraFWHM = 5;
matRad_cfg.dispWarning('photon machine file does not contain measured penumbra width in machine.data.penumbraFWHMatIso. Assuming 5 mm.');
end
sigmaGauss = penumbraFWHM / sqrt(8*log(2)); % [mm]
% use 5 times sigma as the limits for the gaussian convolution
gaussLimit = ceil(5*sigmaGauss/intConvResolution);
[gaussFilterX,gaussFilterZ] = meshgrid(-gaussLimit*intConvResolution: ...
intConvResolution: ...
(gaussLimit-1)*intConvResolution);
gaussFilter = 1/(2*pi*sigmaGauss^2/intConvResolution^2) * exp(-(gaussFilterX.^2+gaussFilterZ.^2)/(2*sigmaGauss^2) );
gaussConvSize = 2*(fieldLimit + gaussLimit);
if ~isFieldBasedDoseCalc
% Create fluence matrix
F = ones(floor(fieldWidth/intConvResolution));
if ~useCustomPrimFluenceBool
% gaussian convolution of field to model penumbra
F = real(ifft2(fft2(F,gaussConvSize,gaussConvSize).*fft2(gaussFilter,gaussConvSize,gaussConvSize)));
end
end
% get kernel size and distances
if kernelCutoff > machine.data.kernelPos(end)
kernelCutoff = machine.data.kernelPos(end);
end
kernelLimit = ceil(kernelCutoff/intConvResolution);
[kernelX, kernelZ] = meshgrid(-kernelLimit*intConvResolution: ...
intConvResolution: ...
(kernelLimit-1)*intConvResolution);
% precalculate convolved kernel size and distances
kernelConvLimit = fieldLimit + gaussLimit + kernelLimit;
[convMx_X, convMx_Z] = meshgrid(-kernelConvLimit*intConvResolution: ...
intConvResolution: ...
(kernelConvLimit-1)*intConvResolution);
% calculate also the total size and distance as we need this during convolution extensively
kernelConvSize = 2*kernelConvLimit;
% define an effective lateral cutoff where dose will be calculated. note
% that storage within the influence matrix may be subject to sampling
effectiveLateralCutoff = lateralCutoff + fieldWidth/sqrt(2);
counter = 0;
matRad_cfg.dispInfo('matRad: Photon dose calculation...\n');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for i = 1:dij.numOfBeams % loop over all beams
matRad_calcDoseInitBeam;
% get index of central ray or closest to the central ray
[~,center] = min(sum(reshape([stf(i).ray.rayPos_bev],3,[]).^2));
% get correct kernel for given SSD at central ray (nearest neighbor approximation)
[~,currSSDIx] = min(abs([machine.data.kernel.SSD]-stf(i).ray(center).SSD));
% Display console message.
matRad_cfg.dispInfo('\tSSD = %g mm ...\n',machine.data.kernel(currSSDIx).SSD);
kernelPos = machine.data.kernelPos;
kernel1 = machine.data.kernel(currSSDIx).kernel1;
kernel2 = machine.data.kernel(currSSDIx).kernel2;
kernel3 = machine.data.kernel(currSSDIx).kernel3;
% Evaluate kernels for all distances, interpolate between values
kernel1Mx = interp1(kernelPos,kernel1,sqrt(kernelX.^2+kernelZ.^2),'linear',0);
kernel2Mx = interp1(kernelPos,kernel2,sqrt(kernelX.^2+kernelZ.^2),'linear',0);
kernel3Mx = interp1(kernelPos,kernel3,sqrt(kernelX.^2+kernelZ.^2),'linear',0);
% convolution here if no custom primary fluence and no field based dose calc
if ~useCustomPrimFluenceBool && ~isFieldBasedDoseCalc
% Display console message.
matRad_cfg.dispInfo('\tUniform primary photon fluence -> pre-compute kernel convolution...\n');
% 2D convolution of Fluence and Kernels in fourier domain
convMx1 = real(ifft2(fft2(F,kernelConvSize,kernelConvSize).* fft2(kernel1Mx,kernelConvSize,kernelConvSize)));
convMx2 = real(ifft2(fft2(F,kernelConvSize,kernelConvSize).* fft2(kernel2Mx,kernelConvSize,kernelConvSize)));
convMx3 = real(ifft2(fft2(F,kernelConvSize,kernelConvSize).* fft2(kernel3Mx,kernelConvSize,kernelConvSize)));
% Creates an interpolant for kernes from vectors position X and Z
if strcmp(env,'MATLAB')
Interp_kernel1 = griddedInterpolant(convMx_X',convMx_Z',convMx1','linear','none');
Interp_kernel2 = griddedInterpolant(convMx_X',convMx_Z',convMx2','linear','none');
Interp_kernel3 = griddedInterpolant(convMx_X',convMx_Z',convMx3','linear','none');
else
Interp_kernel1 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx1,x,y,'linear',NaN);
Interp_kernel2 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx2,x,y,'linear',NaN);
Interp_kernel3 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx3,x,y,'linear',NaN);
end
end
for j = 1:stf(i).numOfRays % loop over all rays / for photons we only have one bixel per ray!
counter = counter + 1;
bixelsPerBeam = bixelsPerBeam + 1;
% convolution here if custom primary fluence OR field based dose calc
if useCustomPrimFluenceBool || isFieldBasedDoseCalc
% overwrite field opening if necessary
if isFieldBasedDoseCalc
F = stf(i).ray(j).shape;
end
% prepare primary fluence array
primaryFluence = machine.data.primaryFluence;
r = sqrt( (F_X-stf(i).ray(j).rayPos(1)).^2 + (F_Z-stf(i).ray(j).rayPos(3)).^2 );
Psi = interp1(primaryFluence(:,1)',primaryFluence(:,2)',r,'linear',0);
% apply the primary fluence to the field
Fx = F .* Psi;
% convolve with the gaussian
Fx = real( ifft2(fft2(Fx,gaussConvSize,gaussConvSize).* fft2(gaussFilter,gaussConvSize,gaussConvSize)) );
% 2D convolution of Fluence and Kernels in fourier domain
convMx1 = real( ifft2(fft2(Fx,kernelConvSize,kernelConvSize).* fft2(kernel1Mx,kernelConvSize,kernelConvSize)) );
convMx2 = real( ifft2(fft2(Fx,kernelConvSize,kernelConvSize).* fft2(kernel2Mx,kernelConvSize,kernelConvSize)) );
convMx3 = real( ifft2(fft2(Fx,kernelConvSize,kernelConvSize).* fft2(kernel3Mx,kernelConvSize,kernelConvSize)) );
% Creates an interpolant for kernes from vectors position X and Z
if strcmp(env,'MATLAB')
Interp_kernel1 = griddedInterpolant(convMx_X',convMx_Z',convMx1','linear','none');
Interp_kernel2 = griddedInterpolant(convMx_X',convMx_Z',convMx2','linear','none');
Interp_kernel3 = griddedInterpolant(convMx_X',convMx_Z',convMx3','linear','none');
else
Interp_kernel1 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx1,x,y,'linear',NaN);
Interp_kernel2 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx2,x,y,'linear',NaN);
Interp_kernel3 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx3,x,y,'linear',NaN);
end
end
% Display progress and update text only 200 times
if mod(bixelsPerBeam,max(1,round(stf(i).totalNumOfBixels/200))) == 0
matRad_progress(bixelsPerBeam/max(1,round(stf(i).totalNumOfBixels/200)),...
floor(stf(i).totalNumOfBixels/max(1,round(stf(i).totalNumOfBixels/200))));
end
% update waitbar only 100 times
if mod(counter,round(dij.totalNumOfBixels/100)) == 0 && ishandle(figureWait)
waitbar(counter/dij.totalNumOfBixels);
end
% remember beam and bixel number
if ~calcDoseDirect
dij.beamNum(counter) = i;
dij.rayNum(counter) = j;
dij.bixelNum(counter) = 1;
else
k = 1;
end
% Ray tracing for beam i and bixel j
[ix,rad_distancesSq,isoLatDistsX,isoLatDistsZ] = matRad_calcGeoDists(rot_coordsVdoseGrid, ...
stf(i).sourcePoint_bev, ...
stf(i).ray(j).targetPoint_bev, ...
machine.meta.SAD, ...
find(~isnan(radDepthVdoseGrid{1})), ...
effectiveLateralCutoff);
% empty bixels may happen during recalculation of error
% scenarios -> skip to next bixel
if isempty(ix)
continue;
end
% calculate photon dose for beam i and bixel j
bixelDose = matRad_calcPhotonDoseBixel(machine.meta.SAD,machine.data.m,...
machine.data.betas, ...
Interp_kernel1,...
Interp_kernel2,...
Interp_kernel3,...
radDepthVdoseGrid{1}(ix),...
geoDistVdoseGrid{1}(ix),...
isoLatDistsX,...
isoLatDistsZ);
% sample dose only for bixel based dose calculation
if ~isFieldBasedDoseCalc
r0 = 20 + stf(i).bixelWidth; % [mm] sample beyond the inner core
Type = 'radius';
[ix,bixelDose] = matRad_DijSampling(ix,bixelDose,radDepthVdoseGrid{1}(ix),rad_distancesSq,Type,r0);
end
% Save dose for every bixel in cell array
doseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,1} = sparse(VdoseGrid(ix),1,bixelDose,dij.doseGrid.numOfVoxels,1);
matRad_calcDoseFillDij;
end
end
%Close Waitbar
if ishandle(figureWait)
delete(figureWait);
end