NLP_beamlet_optimizer.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. function [D_full, w_fin] = NLP_beamlet_optimizer
  2. % This function performs the beamlet optimization
  3. % Inputs: none. Still needs to find "Geometry" and "beamlets" from Wiscplan
  4. % Outputs: full dose image dose: D_full, optimal beamlet weights: w_fin
  5. %
  6. % Made by Peter Ferjancic 1. May 2018
  7. % Last updated: 1. May 2018
  8. % Inspired by Ana Barrigan's REGGUI optimization procedures
  9. % To-do:
  10. % - Add robusness aspect (+take worst case scenario, see REGGUI)
  11. N_fcallback1 = 10000;
  12. N_fcallback2 = 50000;
  13. patient = 'phantom';
  14. switch patient
  15. case 'phantom'
  16. patient_dir = 'C:\010-work\003_localGit\WiscPlan_v2\data\PatientData';
  17. blet_in_beam=5; % this is the number of beamlets in a beam. Called "Mxp" in helicalDosecalcSetup
  18. case 'phantom_HD'
  19. patient_dir = 'C:\010-work\003_localGit\WiscPlan_v2\data\PD_HD_dicomPhantom';
  20. blet_in_beam=20; % ???
  21. case 'doggo'
  22. patient_dir = 'C:\010-work\003_localGit\WiscPlan_v2\data\PatientData_dog5_2';
  23. blet_in_beam=5; % ???
  24. otherwise
  25. error('invalid case')
  26. end
  27. %% PROGRAM STARTS HERE
  28. % - no tocar lo que hay debajo -
  29. fprintf('starting NLP optimization process... ')
  30. % -- LOAD GEOMETRY AND BEAMLETS --
  31. load([patient_dir '\matlab_files\Geometry.mat'])
  32. % beamlet_batch_filename = [patient_dir '\beamlet_batch_files\' 'beamletbatch0.bin'];
  33. beamlet_batch_filename = [patient_dir '\' 'batch_dose.bin'];
  34. beamlet_cell_array = read_ryan_beamlets(beamlet_batch_filename, 'ryan');
  35. fprintf('\n loaded beamlets...')
  36. % -- SET INITIAL PARAMETERS --
  37. numVox = numel(Geometry.data);
  38. numBeamlet = size(beamlet_cell_array,2);
  39. %% -- BEAMLET DOSE DELIVERY --
  40. beamlets = get_beamlets(beamlet_cell_array, numVox);
  41. % show_joint_beamlets(beamlets, size(Geometry.data), 7:9)
  42. fprintf('\n beamlet array constructed...')
  43. % - merge beamlets into beams -
  44. load([patient_dir '\all_beams.mat'])
  45. beamletOrigin=[0 0 0];
  46. beam_i=0;
  47. beam_i_list=[];
  48. for beamlet_i = 1:numel(all_beams)
  49. newBeamletOrigin = all_beams{beamlet_i}.ip;
  50. if any(newBeamletOrigin ~= beamletOrigin)
  51. beam_i = beam_i+1;
  52. beamletOrigin = newBeamletOrigin;
  53. end
  54. beam_i_list=[beam_i_list, beam_i];
  55. end
  56. beamlets_joined=[];
  57. target_joined=[];
  58. wbar2 = waitbar(0, 'merging beamlets into beams');
  59. numBeam=numel(unique(beam_i_list));
  60. for beam_i = unique(beam_i_list)
  61. beam_full = sum(beamlets(:,beam_i_list == beam_i), 2);
  62. beamlets_joined(:,end+1) = beam_full;
  63. waitbar(beam_i/numBeam, wbar2)
  64. end
  65. close(wbar2)
  66. %% -- OPTIMIZATION TARGETS --
  67. switch patient
  68. case 'phantom'
  69. optGoal = make_ROI_goals(Geometry, beamlets);
  70. optGoal_beam = make_ROI_goals(Geometry, beamlets_joined);
  71. N_beamlets_in_beam = 10;
  72. case 'phantom_HD'
  73. optGoal = make_ROI_goals(Geometry, beamlets);
  74. optGoal_beam = make_ROI_goals(Geometry, beamlets_joined);
  75. case 'doggo'
  76. optGoal = make_ROI_goals_DOG(Geometry, beamlets);
  77. optGoal_beam = make_ROI_goals_DOG(Geometry, beamlets_joined);
  78. otherwise
  79. error('invalid case')
  80. end
  81. % -- make them robust --
  82. RO_params=0;
  83. optGoal_beam = make_robust_optGoal(optGoal_beam, RO_params, beamlets_joined);
  84. optGoal = make_robust_optGoal(optGoal, RO_params, beamlets);
  85. %% -- INITIALIZE BEAMLET WEIGHTS --
  86. w0_beams = ones(numBeam,1);
  87. w0_beams = mean(optGoal_beam{1}.target ./ (optGoal_beam{1}.beamlets_pruned*w0_beams+0.1)) * w0_beams;
  88. w0_beams = w0_beams + (2*rand(size(w0_beams))-1) *0.1 .*w0_beams; % random perturbation
  89. % -- CALLBACK OPTIMIZATION FUNCTION --
  90. fun1 = @(x) get_penalty(x, optGoal_beam);
  91. fun2 = @(x) get_penalty(x, optGoal);
  92. % -- OPTIMIZATION PARAMETERS --
  93. % define optimization parameters
  94. A = [];
  95. b = [];
  96. Aeq = [];
  97. beq = [];
  98. lb = zeros(1, numBeamlet);
  99. lb_beam = zeros(1, numBeamlet);
  100. ub = [];
  101. nonlcon = [];
  102. % define opt limits, and make it fmincon progress
  103. options = optimoptions('fmincon');
  104. options.MaxFunctionEvaluations = N_fcallback1;
  105. options.Display = 'iter';
  106. options.PlotFcn = 'optimplotfval';
  107. fprintf('\n running initial optimizer:')
  108. %% Run the optimization
  109. % -- GET FULL BEAM WEIGHTS --
  110. tic
  111. w_beam = fmincon(fun1,w0_beams,A,b,Aeq,beq,lb_beam,ub,nonlcon,options);
  112. t=toc;
  113. disp(['Optimization time for beams = ',num2str(t)]);
  114. w_beamlets = ones(numBeamlet,1);
  115. numBeam=numel(unique(beam_i_list));
  116. for beam_i = 1:numBeam % assign weights to beamlets
  117. % beamlets from same beam get same initial weights
  118. w_beamlets(beam_i_list == beam_i) = w_beam(beam_i);
  119. end
  120. w_beamlets = w_beamlets + (2*rand(size(w_beamlets))-1) *0.1 .*w_beamlets; % small random perturbation
  121. % -- GET FULL BEAMLET WEIGHTS --
  122. options.MaxFunctionEvaluations = N_fcallback2;
  123. tic
  124. w_fin = fmincon(fun2,w_beamlets,A,b,Aeq,beq,lb,ub,nonlcon,options);
  125. t=toc;
  126. disp(['Optimization time for beamlets = ',num2str(t)]);
  127. %% evaluate the results
  128. D_full = reshape(beamlets * w_fin, size(Geometry.data));
  129. %% save outputs
  130. NLP_result.dose = D_full;
  131. NLP_result.weights = w_fin;
  132. % save('C:\010-work\003_localGit\WiscPlan_v2\data\PatientData\matlab_files\NLP_result.mat', 'NLP_result');
  133. optGoal_idx=[1,3];
  134. plot_DVH(D_full, optGoal, optGoal_idx)
  135. % plot_DVH_robust(D_full, optGoal, optGoal_idx)
  136. end
  137. % orthoslice(D_full, [0,70])
  138. %% support functions
  139. % ---- PENALTY FUNCTION ----
  140. function penalty = get_penalty(x, optGoal)
  141. % this function gets called by the optimizer. It checks the penalty for
  142. % all the robust implementation and returns the worst result.
  143. NumScenarios = optGoal{1}.NbrRandScenarios * optGoal{1}.NbrSystSetUpScenarios * optGoal{1}.NbrRangeScenarios;
  144. fobj = zeros(NumScenarios,1);
  145. sc_i = 1;
  146. for nrs_i = 1:optGoal{1}.NbrRandScenarios
  147. for sss_i = 1 :optGoal{1}.NbrSystSetUpScenarios % syst. setup scenarios = ss
  148. for rrs_i = 1:optGoal{1}.NbrRangeScenarios % range scenario = rs
  149. fobj(sc_i)=eval_f(x, optGoal, nrs_i, sss_i, rrs_i);
  150. sc_i = sc_i + 1;
  151. end
  152. end
  153. end
  154. % take the worst case penalty of evaluated scenarios
  155. penalty=max(fobj);
  156. end
  157. % ------ supp: penalty for single scenario ------
  158. function penalty = eval_f(x, optGoal, nrs_i, sss_i, rrs_i)
  159. penalty = 0;
  160. % for each condition
  161. for goal_i = 1:numel(optGoal)
  162. switch optGoal{goal_i}.function
  163. case 'min'
  164. % penalize if achieved dose is lower than target dose
  165. d_penalty = 1.0e0 * sum(max(0, ...
  166. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target) -...
  167. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned * x)));
  168. case 'max'
  169. % penalize if achieved dose is higher than target dose
  170. d_penalty = 1.0e0 * sum(max(0, ...
  171. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned * x)-...
  172. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target)));
  173. case 'LeastSquare'
  174. % penalize with sum of squares any deviation from target
  175. % dose
  176. d_penalty = 1.0e-1* sum(((...
  177. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned * x) - ...
  178. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target).^2);
  179. end
  180. penalty = penalty + d_penalty * optGoal{goal_i}.opt_weight;
  181. end
  182. end
  183. % ---- GET BEAMLETS ----
  184. function beamlets = get_beamlets(beamlet_cell_array, numVox);
  185. wbar1 = waitbar(0, 'Creating beamlet array');
  186. numBeam = size(beamlet_cell_array,2);
  187. batchSize=100;
  188. beamlets = sparse(0, 0);
  189. for beam_i=1:numBeam
  190. % for each beam define how much dose it delivers on each voxel
  191. idx=beamlet_cell_array{1, beam_i}.non_zero_indices;
  192. % break the beamlets into multiple batches
  193. if rem(beam_i, batchSize)==1;
  194. beamlet_batch = sparse(numVox, batchSize);
  195. beam_i_temp=1;
  196. end
  197. beamlet_batch(idx, beam_i_temp) = 1000*beamlet_cell_array{1, beam_i}.non_zero_values;
  198. waitbar(beam_i/numBeam, wbar1, ['Adding beamlet array: #', num2str(beam_i)])
  199. % add the batch to full set when filled
  200. if rem(beam_i, batchSize)==0;
  201. beamlets =[beamlets, beamlet_batch];
  202. end
  203. % crop and add the batch to full set when completed
  204. if beam_i==numBeam;
  205. beamlet_batch=beamlet_batch(:, 1:beam_i_temp);
  206. beamlets =[beamlets, beamlet_batch];
  207. end
  208. beam_i_temp=beam_i_temp+1;
  209. end
  210. close(wbar1)
  211. end
  212. function show_joint_beamlets(beamlets, IMGsize, Beam_list)
  213. % this function overlays and plots multiple beamlets. The goal is to
  214. % check whether some beamlets are part of the same beam manually.
  215. beam=sum(beamlets(:, Beam_list), 2);
  216. beamImg=reshape(full(beam), IMGsize);
  217. orthoslice(beamImg)
  218. end
  219. % ---- MAKE ROI GOALS ----
  220. function optGoal = make_ROI_goals(Geometry, beamlets)
  221. optGoal={};
  222. % -- START DEFINITION OF GOAL --
  223. goal_1.name = 'Target_min';
  224. goal_1.ROI_name = Geometry.ROIS{1, 2}.name;
  225. ROI_idx = Geometry.ROIS{1, 2}.ind;
  226. goal_1.ROI_idx = ROI_idx;
  227. goal_1.imgDim = size(Geometry.data);
  228. goal_1.D_final = 60;
  229. goal_1.function = 'min';
  230. goal_1.beamlets_pruned = beamlets(ROI_idx, :);
  231. goal_1.target = ones(numel(ROI_idx), 1) * goal_1.D_final;
  232. goal_1.opt_weight = 1 / numel(ROI_idx); % normalize to volume of target area
  233. goal_1.dvh_col = [0.9, 0.2, 0.2]; % color of the final DVH plot
  234. % assign target
  235. optGoal{end+1}=goal_1;
  236. % -- END DEFINITION OF GOAL --
  237. % -- START DEFINITION OF GOAL --
  238. goal_2.name = 'Target_max';
  239. goal_2.ROI_name = Geometry.ROIS{1, 2}.name;
  240. ROI_idx = Geometry.ROIS{1, 2}.ind;
  241. goal_2.ROI_idx = ROI_idx;
  242. goal_2.imgDim = size(Geometry.data);
  243. goal_2.D_final = 65;
  244. goal_2.function = 'max';
  245. goal_2.beamlets_pruned = beamlets(ROI_idx, :);
  246. goal_2.target = ones(numel(ROI_idx), 1) * goal_2.D_final;
  247. goal_2.opt_weight = 0.8 / numel(ROI_idx); % normalize to volume of target area
  248. goal_2.dvh_col = [0.9, 0.2, 0.2]; % color of the final DVH plot
  249. % assign target
  250. optGoal{end+1}=goal_2;
  251. % -- END DEFINITION OF GOAL --
  252. % -- START DEFINITION OF GOAL --
  253. goal_3.name = 'ROI 1_max';
  254. goal_3.ROI_name = Geometry.ROIS{1, 3}.name;
  255. ROI_idx = Geometry.ROIS{1, 3}.ind;
  256. goal_3.ROI_idx = ROI_idx;
  257. goal_3.imgDim = size(Geometry.data);
  258. goal_3.D_final = 10;
  259. goal_3.function = 'max';
  260. goal_3.beamlets_pruned = beamlets(ROI_idx, :);
  261. goal_3.target = ones(numel(ROI_idx), 1) * goal_3.D_final;
  262. goal_3.opt_weight = 2 / numel(ROI_idx); % normalize to volume of target area
  263. goal_3.dvh_col = [0.2, 0.9, 0.2]; % color of the final DVH plot
  264. % assign target
  265. optGoal{end+1}=goal_3;
  266. % -- END DEFINITION OF GOAL --
  267. end
  268. function optGoal = make_ROI_goals_DOG(Geometry, beamlets)
  269. optGoal={};
  270. % -- START DEFINITION OF GOAL --
  271. goal_1.name = 'Target_min';
  272. goal_1.ROI_name = Geometry.ROIS{1, 1}.name;
  273. ROI_idx = Geometry.ROIS{1, 1}.ind;
  274. goal_1.ROI_idx = ROI_idx;
  275. goal_1.imgDim = size(Geometry.data);
  276. goal_1.D_final = 60;
  277. goal_1.function = 'min';
  278. goal_1.beamlets_pruned = beamlets(ROI_idx, :);
  279. goal_1.target = ones(numel(ROI_idx), 1) * goal_1.D_final;
  280. goal_1.opt_weight = 1 / numel(ROI_idx); % normalize to volume of target area
  281. goal_1.dvh_col = [0.9, 0.2, 0.2]; % color of the final DVH plot
  282. % assign target
  283. optGoal{end+1}=goal_1;
  284. % -- END DEFINITION OF GOAL --
  285. % -- START DEFINITION OF GOAL --
  286. goal_2.name = 'Doggo_max';
  287. goal_2.ROI_name = Geometry.ROIS{1, 4}.name;
  288. ROI_idx = Geometry.ROIS{1, 1}.ind;
  289. goal_2.ROI_idx = ROI_idx;
  290. goal_2.imgDim = size(Geometry.data);
  291. goal_2.D_final = 20;
  292. goal_2.function = 'max';
  293. goal_2.beamlets_pruned = beamlets(ROI_idx, :);
  294. goal_2.target = ones(numel(ROI_idx), 1) * goal_2.D_final;
  295. goal_2.opt_weight = 0.2 / numel(ROI_idx); % normalize to volume of target area
  296. goal_2.dvh_col = [0.9, 0.2, 0.2]; % color of the final DVH plot
  297. % assign target
  298. optGoal{end+1}=goal_2;
  299. % -- END DEFINITION OF GOAL --
  300. end
  301. % ---- MAKE ROI ROBUST ----
  302. function optGoal = make_robust_optGoal(optGoal, RO_params, beamlets);
  303. % take regular optimal goal and translate it into several robust cases
  304. % RO_params - should have the information below
  305. % nrs - random scenarios
  306. % sss - system setup scenarios
  307. % rrs - random range scenarios
  308. % X - X>0 moves image right
  309. % Y - Y>0 moves image down
  310. % Z - in/out.
  311. nrs_scene_list={[0,0,0]};
  312. sss_scene_list={[0,0,0]};
  313. rrs_scene_list={[0,0,0]};
  314. % nrs_scene_list={[0,0,0]};
  315. sss_scene_list={[0,0,0], [-15,0,0], [-10,0,0], [-5,0,0], [5,0,0], [10,0,0], [15,0,0]};
  316. % rrs_scene_list={[0,0,0]};
  317. for i = 1:numel(optGoal)
  318. optGoal{i}.NbrRandScenarios =numel(nrs_scene_list);
  319. optGoal{i}.NbrSystSetUpScenarios=numel(sss_scene_list);
  320. optGoal{i}.NbrRangeScenarios =numel(rrs_scene_list);
  321. end
  322. for goal_i = 1:numel(optGoal)
  323. % get target
  324. idx=optGoal{goal_i}.ROI_idx;
  325. targetImg1=zeros(optGoal{goal_i}.imgDim);
  326. targetImg1(idx)=1;
  327. % get beamlets
  328. for nrs_i = 1:optGoal{goal_i}.NbrRandScenarios % num. of random scenarios
  329. % modify target and beamlets
  330. targetImg2=targetImg1;
  331. % beamlets stay the same
  332. for sss_i = 1:optGoal{goal_i}.NbrSystSetUpScenarios % syst. setup scenarios = sss
  333. % modify target and beamlets
  334. targetImg3=get_RO_sss(targetImg2, sss_scene_list{sss_i});
  335. % beamlets stay the same
  336. for rrs_i = 1:optGoal{goal_i}.NbrRangeScenarios % range scenario = rrs
  337. % modify target and beamlets
  338. targetImg4=targetImg3;
  339. % beamlets stay the same
  340. %% make new target and beamlets
  341. ROI_idx=[];
  342. ROI_idx=find(targetImg4>0);
  343. target = ones(numel(ROI_idx), 1) * optGoal{goal_i}.D_final;
  344. beamlets_pruned = beamlets(ROI_idx, :);
  345. % save to optGoal output
  346. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.ROI_idx = ROI_idx;
  347. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned = beamlets_pruned;
  348. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target = target;
  349. end
  350. end
  351. end
  352. end
  353. end
  354. % ------ supp: RO case SSS ------
  355. function targetImg3=get_RO_sss(targetImg2, sss_scene_shift);
  356. % translate the target image
  357. targetImg3 = imtranslate(targetImg2,sss_scene_shift);
  358. end
  359. % ---- DVH PLOT FUNCTION ----
  360. function plot_DVH(dose, optGoal, optGoal_idx)
  361. % this function plots the DVHs of the given dose
  362. nfrac=1;
  363. figure
  364. hold on
  365. for roi_idx=optGoal_idx
  366. % plot the histogram
  367. [dvh dosebins] = get_dvh_data(dose,optGoal{roi_idx}.ROI_idx,nfrac);
  368. plot(dosebins, dvh,'Color', optGoal{roi_idx}.dvh_col,'LineStyle', '-','DisplayName', optGoal{roi_idx}.ROI_name);
  369. end
  370. hold off
  371. end
  372. function plot_DVH_robust(dose, optGoal, optGoal_idx)
  373. % this function plots the DVHs of the given dose
  374. nfrac=1;
  375. figure
  376. hold on
  377. for goal_i=optGoal_idx
  378. % for all the perturbations
  379. for nrs_i = 1:optGoal{1}.NbrRandScenarios
  380. for sss_i = 1:optGoal{1}.NbrSystSetUpScenarios % syst. setup scenarios = ss
  381. for rrs_i = 1:optGoal{1}.NbrRangeScenarios % range scenario = rs
  382. % plot the histogram
  383. ROI_idx = optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.ROI_idx;
  384. [dvh dosebins] = get_dvh_data(dose,ROI_idx,nfrac);
  385. plot(dosebins, dvh,'Color', optGoal{goal_i}.dvh_col,'LineStyle', '-','DisplayName', optGoal{goal_i}.ROI_name);
  386. end
  387. end
  388. end
  389. end
  390. hold off
  391. end
  392. function [dvh dosebins] = get_dvh_data(dose,roi_idx,nfrac);
  393. % this function calculates the data for the DVH
  394. dosevec = dose(roi_idx);
  395. [pdf dosebins] = hist(dosevec, 999);
  396. % clip negative bins
  397. pdf = pdf(dosebins >= 0);
  398. dosebins = dosebins(dosebins >= 0);
  399. dvh = fliplr(cumsum(fliplr(pdf))) / numel(dosevec) * 100;
  400. % append the last bin
  401. dosebins = [dosebins dosebins(end)+0.1];
  402. dvh = [dvh 0];
  403. end