|
30 | 30 |
|
31 | 31 | matRad_cfg = MatRad_Config.instance(); |
32 | 32 |
|
33 | | -matRad_cfg.dispInfo('matRad: Generating single bixel stf struct... '); |
| 33 | +matRad_cfg.dispDeprecationWarning('The function %s is not intendet to be used, check out the Single Bixel Stf Generators in the steering folder',fileparts(mfilename)); |
34 | 34 |
|
35 | | -if nargin < 4 |
36 | | - visMode = 0; |
37 | | -end |
38 | | - |
39 | | -if numel(pln.propStf.gantryAngles) ~= numel(pln.propStf.couchAngles) |
40 | | - matRad_cfg.dispError('Inconsistent number of gantry and couch angles.'); |
41 | | -end |
42 | | - |
43 | | -if pln.propStf.bixelWidth < 0 || ~isfinite(pln.propStf.bixelWidth) |
44 | | - matRad_cfg.dispError('bixel width (spot distance) needs to be a real number [mm] larger than zero.'); |
45 | | -end |
46 | | - |
47 | | -% prepare structures necessary for particles |
48 | | -fileName = [pln.radiationMode '_' pln.machine '.mat']; |
49 | | -try |
50 | | - machine = matRad_loadMachine(pln); |
51 | | -catch |
52 | | - matRad_cfg.dispError('Could not find the following machine file: %s',fileName); |
53 | | -end |
54 | | - |
55 | | -SAD = machine.meta.SAD; |
56 | | - |
57 | | -if strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon') || strcmp(pln.radiationMode,'helium') |
58 | | - |
59 | | - availableEnergies = [machine.data.energy]; |
60 | | - availablePeakPos = [machine.data.peakPos] + [machine.data.offset]; |
61 | | - |
62 | | - if sum(availablePeakPos<0)>0 |
63 | | - matRad_cfg.dispError('at least one available peak position is negative - inconsistent machine file') |
64 | | - end |
65 | | - %clear machine; |
66 | | -end |
67 | | - |
68 | | -% calculate rED or rSP from HU |
69 | | -ct = matRad_calcWaterEqD(ct, pln); |
70 | | - |
71 | | -% take only voxels inside patient |
72 | | -V = [cst{:,4}]; |
73 | | -V = unique(vertcat(V{:})); |
74 | | - |
75 | | -% ignore densities outside of contours |
76 | | -eraseCtDensMask = ones(prod(ct.cubeDim),1); |
77 | | -eraseCtDensMask(V) = 0; |
78 | | -for i = 1:ct.numOfCtScen |
79 | | - ct.cube{i}(eraseCtDensMask == 1) = 0; |
80 | | -end |
81 | | - |
82 | | -% Define steering file like struct. Prellocating for speed. |
83 | | -stf = struct; |
84 | | - |
85 | | -% find set of energyies with adequate spacing |
86 | | -if ~isfield(pln.propStf, 'longitudinalSpotSpacing') |
87 | | - longitudinalSpotSpacing = matRad_cfg.defaults.propStf.longitudinalSpotSpacing; |
| 35 | +if any(strcmp(pln.radiationMode,{'protons','helium','carbon','oxygen'})) |
| 36 | + stfGen = matRad_ParticleStfGeneratorSingleBeamlet(pln); |
| 37 | +elseif strcmp(pln.radiationMode,'photons') |
| 38 | + stfGen = matRad_PhotonStfGeneratorSingleBeamlet(pln); |
88 | 39 | else |
89 | | - longitudinalSpotSpacing = pln.propStf.longitudinalSpotSpacing; |
| 40 | + matRad_cfg.dispError('Unsupported Radiation Mode!'); |
90 | 41 | end |
91 | 42 |
|
92 | | -if ~isfield(pln.propStf,'useRangeShifter') |
93 | | - pln.propStf.useRangeShifter = false; |
94 | | -end |
95 | | - |
96 | | -%Get Isocenter voxel as target |
97 | | -ct = matRad_getWorldAxes(ct); |
98 | | - |
99 | | - |
100 | | -% loop over all angles |
101 | | -for i = 1:length(pln.propStf.gantryAngles) |
102 | | - |
103 | | - % Save meta information for treatment plan |
104 | | - stf(i).gantryAngle = pln.propStf.gantryAngles(i); |
105 | | - stf(i).couchAngle = pln.propStf.couchAngles(i); |
106 | | - stf(i).bixelWidth = pln.propStf.bixelWidth; |
107 | | - stf(i).radiationMode = pln.radiationMode; |
108 | | - stf(i).SAD = SAD; |
109 | | - stf(i).isoCenter = pln.propStf.isoCenter(i,:); |
110 | | - stf(i).numOfRays = 1; |
111 | | - stf(i).numOfBixelsPerRay = 1; |
112 | | - stf(i).totalNumOfBixels = 1; |
113 | | - stf(i).machine = pln.machine; |
114 | | - |
115 | | - %Voxel index of Isocenter |
116 | | - isoIx = matRad_world2cubeIndex(stf(i).isoCenter,ct,true); |
117 | | - isoIx(isoIx < 1) = 1; |
118 | | - isoIx(isoIx > ct.cubeDim) = ct.cubeDim(isoIx > ct.cubeDim); |
119 | | - |
120 | | - % generate voi cube for targets |
121 | | - voiTarget = zeros(ct.cubeDim); |
122 | | - voiTarget(isoIx(1),isoIx(2),isoIx(3)) = 1; |
123 | | - adds = unique(perms([1 0 0]),'rows'); |
124 | | - adds = [adds; -adds]; |
125 | | - for p = 1:size(adds,1) |
126 | | - padIx = isoIx + adds(p,:); |
127 | | - if any(padIx < 1) || any(padIx > ct.cubeDim) |
128 | | - continue; |
129 | | - end |
130 | | - voiTarget(padIx(1),padIx(2),padIx(3)) = 1; |
131 | | - end |
132 | | - |
133 | | - %voiTarget = matRad_addMargin(voiTarget,cst,ct.resolution,ct.resolution); |
134 | | - |
135 | | - % Get the (active) rotation matrix. We perform a passive/system |
136 | | - % rotation with row vector coordinates, which would introduce two |
137 | | - % inversions / transpositions of the matrix, thus no changes to the |
138 | | - % rotation matrix are necessary |
139 | | - rotMat_system_T = matRad_getRotationMatrix(pln.propStf.gantryAngles(i),pln.propStf.couchAngles(i)); |
140 | | - rotMat_vectors_T = transpose(rotMat_system_T); |
141 | | - |
142 | | - stf(i).sourcePoint_bev = [0 -SAD 0]; |
143 | | - stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T; |
144 | | - |
145 | | - stf(i).ray.rayPos_bev = [0 0 0]; |
146 | | - stf(i).ray.targetPoint_bev = [0 SAD 0]; |
147 | | - |
148 | | - |
149 | | - shiftedIsoCenter = matRad_world2cubeCoords(vertcat(stf(i).isoCenter),ct); |
150 | | - |
151 | | - stf(i).ray.rayPos = stf(i).ray.rayPos_bev * rotMat_vectors_T; |
152 | | - stf(i).ray.targetPoint = [0 SAD 0] * rotMat_vectors_T; |
153 | | - |
154 | | - |
155 | | - % find appropriate energies for particles |
156 | | - if strcmp(stf(i).radiationMode,'protons') || strcmp(stf(i).radiationMode,'carbon') || strcmp(stf(i).radiationMode,'helium') |
157 | | - stf(i).longitudinalSpotSpacing = longitudinalSpotSpacing; |
158 | | - |
159 | | - % ray tracing necessary to determine depth of the target |
160 | | - [alphas,l,rho,d12,~] = matRad_siddonRayTracer(shiftedIsoCenter, ... |
161 | | - ct.resolution, ... |
162 | | - stf(i).sourcePoint, ... |
163 | | - stf(i).ray.targetPoint, ... |
164 | | - [{ct.cube{1}} {voiTarget}]); |
165 | | - |
166 | | - %Used for generic range-shifter placement |
167 | | - ctEntryPoint = alphas(1) * d12; |
168 | | - |
169 | | - % target hit |
170 | | - if sum(rho{2}) > 0 |
171 | | - |
172 | | - % compute radiological depths |
173 | | - % http://www.ncbi.nlm.nih.gov/pubmed/4000088, eq 14 |
174 | | - radDepths = cumsum(l .* rho{1}); |
175 | | - |
176 | | - % find target entry & exit |
177 | | - diff_voi = diff([rho{2}]); |
178 | | - if rho{2}(1) > 0 |
179 | | - targetEntry = 1; |
180 | | - else |
181 | | - targetEntry = radDepths(diff_voi == 1); |
182 | | - end |
183 | | - |
184 | | - targetExit = radDepths(diff_voi == -1); |
185 | | - |
186 | | - if numel(targetEntry) ~= numel(targetExit) |
187 | | - matRad_cfg.dispError('Inconsistency during ray tracing. Please check correct assignment and overlap priorities of structure types OAR & TARGET.'); |
188 | | - end |
189 | | - |
190 | | - raShiThickness = 50; |
191 | | - |
192 | | - % Save energies in stf struct |
193 | | - bestPeakPos = mean([targetExit,targetEntry]) + raShiThickness; |
194 | | - [~,closest] = min(abs(availablePeakPos - bestPeakPos)); |
195 | | - stf(i).ray.energy = availableEnergies(closest); |
196 | | - |
197 | | - |
198 | | - % book keeping & calculate focus index |
199 | | - currentMinimumFWHM = matRad_interp1(machine.meta.LUT_bxWidthminFWHM(1,:)',... |
200 | | - machine.meta.LUT_bxWidthminFWHM(2,:)',... |
201 | | - pln.propStf.bixelWidth, ... |
202 | | - machine.meta.LUT_bxWidthminFWHM(2,end)); |
203 | | - [~, vEnergyIx] = min(abs(bsxfun(@minus,[machine.data.energy]',... |
204 | | - repmat(stf(i).ray.energy,length([machine.data]),1)))); |
205 | | - |
206 | | - % get for each spot the focus index |
207 | | - stf(i).ray.focusIx = find(machine.data(vEnergyIx).initFocus.SisFWHMAtIso > currentMinimumFWHM,1,'first'); |
208 | | - |
209 | | - if pln.propStf.useRangeShifter |
210 | | - |
211 | | - %Include range shifter data |
212 | | - stf(i).ray.rangeShifter.ID = 1; |
213 | | - stf(i).ray.rangeShifter.eqThickness = raShiThickness; |
214 | | - |
215 | | - %Place range shifter 2 times the range away from isocenter, but |
216 | | - %at least 10 cm |
217 | | - sourceRaShi = round(ctEntryPoint - 2*raShiThickness,-1); %place a little away from entry, round to cms to reduce number of unique settings; |
218 | | - stf(i).ray.rangeShifter.sourceRashiDistance = sourceRaShi; |
219 | | - else |
220 | | - stf(i).ray.rangeShifter.ID = 0; |
221 | | - stf(i).ray.rangeShifter.eqThickness = 0; |
222 | | - stf(i).ray.rangeShifter.sourceRashiDistance = 0; |
223 | | - end |
224 | | - |
225 | | - else % target not hit |
226 | | - stf(i).ray = []; |
227 | | - stf(i).numOfBixelsPerray = []; |
228 | | - end |
229 | | - |
230 | | - elseif strcmp(stf(i).radiationMode,'photons') |
231 | | - |
232 | | - % book keeping for photons |
233 | | - stf(i).ray.energy = machine.data.energy; |
| 43 | +stf = stfGen.generate(ct,cst); |
234 | 44 |
|
235 | | - rayPos = stf(i).ray.rayPos; |
236 | | - |
237 | | - stf(i).ray.beamletCornersAtIso = [rayPos + [+stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];... |
238 | | - rayPos + [-stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];... |
239 | | - rayPos + [-stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2];... |
240 | | - rayPos + [+stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2]]*rotMat_vectors_T; |
241 | | - stf(i).ray.rayCorners_SCD = (repmat([0, machine.meta.SCD - SAD, 0],4,1)+ (machine.meta.SCD/SAD) * ... |
242 | | - [rayPos + [+stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];... |
243 | | - rayPos + [-stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];... |
244 | | - rayPos + [-stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2];... |
245 | | - rayPos + [+stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2]])*rotMat_vectors_T; |
246 | | - |
247 | | - else |
248 | | - matRad_cfg.dispError('Error generating stf struct: invalid radiation modality.'); |
249 | | - end |
250 | 45 | end |
251 | 46 |
|
252 | 47 |
|
|
0 commit comments