Skip to content

Commit 97df3ff

Browse files
committed
Cutoff method and Analytical Engine test
1 parent ff893ac commit 97df3ff

File tree

3 files changed

+104
-3
lines changed

3 files changed

+104
-3
lines changed

matRad/doseCalc/+DoseEngines/matRad_ParticleAnalyticalBortfeldEngine.m

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@
7474

7575
methods (Access = protected)
7676
function dij = initDoseCalc(this,ct,cst,stf)
77-
77+
78+
matRad_cfg = MatRad_Config.instance();
79+
7880
if this.calcLET == true
7981
matRad_cfg.dispWarning('Engine does not support LET calculation! Disabling!');
8082
this.calcLET = false;
@@ -324,6 +326,11 @@ function calcLateralParticleCutOff(this,cutOffLevel,~)
324326
calcRange = true;
325327
end
326328

329+
if strcmp(this.cutOffMethod, 'relative')
330+
warning('Relative cutoff not yet implemented for Analytical Engine. Default integral method used.')
331+
this.cutOffMethod = 'integral';
332+
end
333+
327334
for i = 1:numel(this.machine.data)
328335
this.machine.data(i).LatCutOff.CompFac = 1/cutOffLevel;
329336
this.machine.data(i).LatCutOff.numSig = sqrt(2) * sqrt(gammaincinv(0.995,1)); %For a 2D symmetric gaussian we need the inverse of the incomplete Gamma function for defining the CutOff
@@ -362,6 +369,7 @@ function calcLateralParticleCutOff(this,cutOffLevel,~)
362369
end
363370
catch
364371
msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
372+
error(msg);
365373
return;
366374
end
367375

matRad/doseCalc/+DoseEngines/matRad_ParticlePencilBeamEngineAbstract.m

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
airOffsetCorrection = true; % Corrects WEPL for SSD difference to kernel database
2727
lateralModel = 'auto'; % Lateral Model used. 'auto' uses the most accurate model available (i.e. multiple Gaussians). 'single','double','multi' try to force a singleGaussian or doubleGaussian model, if available
2828

29+
cutOffMethod = 'integral'; % or 'relative'
30+
2931
visBoolLateralCutOff = false; % Boolean switch for visualization during+ LeteralCutOff calculation
3032
end
3133

@@ -710,8 +712,19 @@ function calcLateralParticleCutOff(this,cutOffLevel,stfElement)
710712
matRad_cfg.dispWarning('LateralParticleCutOff: shell integration is wrong !')
711713
end
712714

713-
IX = find(cumArea >= idd(j) * cutOffLevel,1, 'first');
714-
this.machine.data(energyIx).LatCutOff.CompFac = cutOffLevel^-1;
715+
% Find radius at which integrated dose becomes
716+
% bigger than cutoff * IDD
717+
if strcmp(this.cutOffMethod, 'integral')
718+
IX = find(cumArea >= idd(j) * cutOffLevel,1, 'first');
719+
this.machine.data(energyIx).LatCutOff.CompFac = cutOffLevel^-1;
720+
elseif strcmp(this.cutOffMethod, 'relative')
721+
IX = find(dose_r <= (1-cutOffLevel) * max(dose_r), 1, 'first');
722+
relFac = cumArea(IX)./cumArea(end); % (or idd(j)) to find the appropriate integral of dose
723+
this.machine.data(energyIx).LatCutOff.CompFac = relFac^-1;
724+
else
725+
matRad_cfg = MatRad_Config.instance();
726+
matRad_cfg.dispError('Invalid Cutoff Method');
727+
end
715728

716729
if isempty(IX)
717730
depthDoseCutOff = Inf;

test/doseCalc/test_Analytical.m

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
function test_suite = test_Analytical
2+
3+
test_functions=localfunctions();
4+
5+
initTestSuite;
6+
7+
function test_getAnalyticalEngineFromPln
8+
% Single gaussian lateral model
9+
protonDummyPln = struct('radiationMode','protons','machine','Generic');
10+
protonDummyPln.propDoseCalc.engine = 'AnalyticalPB';
11+
engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(protonDummyPln);
12+
assertTrue(isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
13+
14+
% Double Gaussian lateral model
15+
% If you don't have my clusterDose basedata you cannot try this :P
16+
%{
17+
protonDummyPln = struct('radiationMode','protons','machine','Generic_clusterDose');
18+
protonDummyPln.propDoseCalc.engine = 'AnalyticalPB';
19+
engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(protonDummyPln);
20+
assertTrue(isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
21+
%}
22+
23+
function test_loadMachineForAnalytical
24+
possibleRadModes = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.possibleRadiationModes;
25+
for i = 1:numel(possibleRadModes)
26+
machine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.loadMachine(possibleRadModes{i},'Generic');
27+
assertTrue(isstruct(machine));
28+
assertTrue(isfield(machine, 'meta'));
29+
assertTrue(isfield(machine.meta, 'radiationMode'));
30+
assertTrue(strcmp(machine.meta.radiationMode, 'protons'));
31+
end
32+
33+
function test_calcDoseAnalytical
34+
matRad_cfg = MatRad_Config.instance();
35+
36+
protonDummyPln = struct('radiationMode','protons','machine','Generic');
37+
protonDummyPln.propDoseCalc.engine = 'AnalyticalPB';
38+
39+
load([protonDummyPln.radiationMode '_' protonDummyPln.machine]);
40+
41+
load BOXPHANTOM.mat
42+
43+
stf = matRad_generateStf(ct, cst, protonDummyPln);
44+
45+
resultGUI = matRad_calcDoseForward(ct, cst, stf, protonDummyPln, ones([1, stf(:).totalNumOfBixels]));
46+
47+
assertTrue(isfield(resultGUI, 'physicalDose'));
48+
assertTrue(isfield(resultGUI, 'w'));
49+
assertTrue(isequal(size(ct.cube{1}), size(resultGUI.physicalDose)))
50+
51+
function test_nonSupportedSettings
52+
% Radiation mode other than protons not implemented
53+
carbonDummyPln = struct('radiationMode','carbon','machine','Generic');
54+
carbonDummyPln.propDoseCalc.engine = 'AnalyticalPB';
55+
engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(carbonDummyPln);
56+
assertTrue(~isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
57+
58+
% Biological models, LET, other lateral models not implemented
59+
protonDummyPln = struct('radiationMode','protons','machine','Generic');
60+
protonDummyPln.propDoseCalc.engine = 'AnalyticalPB';
61+
protonDummyPln.propDoseCalc.calcLET = true;
62+
protonDummyPln.propDoseCalc.calcBioDose = true;
63+
protonDummyPln.propDoseCalc.lateralModel = 'double';
64+
engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(protonDummyPln);
65+
assertTrue(isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
66+
load BOXPHANTOM.mat
67+
stf = matRad_generateStf(ct, cst, protonDummyPln);
68+
resultGUI = matRad_calcDoseForward(ct, cst, stf, protonDummyPln, ones([1, stf(:).totalNumOfBixels]));
69+
assertTrue(~engine.calcLET)
70+
%assertTrue(~engine.calcBioDose) % Access protected property
71+
72+
% Invalid machine without radiation mode field
73+
matRad_cfg = MatRad_Config.instance();
74+
protonDummyPln = struct('radiationMode','protons','machine','Empty');
75+
protonDummyPln.propDoseCalc.engine = 'AnalyticalPB';
76+
machine = [];
77+
assertExceptionThrown(@() DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.isAvailable(protonDummyPln, machine));
78+
79+
80+

0 commit comments

Comments
 (0)