NLP_beamlet_optimizer.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. %% Program starts here
  12. fprintf('starting NLP optimization process: ')
  13. % -- LOAD GEOMETRY AND BEAMLETS --
  14. patient_dir = 'C:\010-work\003_localGit\WiscPlan_v2\data\PatientData';
  15. load([patient_dir '\matlab_files\Geometry.mat'])
  16. beamlet_batch_filename = [patient_dir '\beamlet_batch_files\' 'beamletbatch0.bin'];
  17. beamlet_cell_array = read_ryan_beamlets(beamlet_batch_filename, 'ryan');
  18. fprintf('.')
  19. % -- SET INITIAL PARAMETERS --
  20. numVox = numel(Geometry.data);
  21. numBeam = size(beamlet_cell_array,2);
  22. % ROI_idx=Geometry.ROIS{1, 2}.ind;
  23. fprintf('.')
  24. %% create input for optimization
  25. % -- BEAMLET DOSE DELIVERY --
  26. beamlets = sparse(numVox, numBeam);
  27. fprintf('.')
  28. wbar1 = waitbar(0, 'Creating beamlet array');
  29. for beam_i=1:numBeam
  30. % for each beam define how much dose it delivers on each voxel
  31. idx=beamlet_cell_array{1, beam_i}.non_zero_indices;
  32. beamlets(idx, beam_i) = 1000*beamlet_cell_array{1, beam_i}.non_zero_values;
  33. waitbar(beam_i/numBeam)
  34. end
  35. close(wbar1)
  36. fprintf('.')
  37. % -- OPTIMIZATION TARGETS --
  38. optGoal = make_ROI_goals(Geometry, beamlets);
  39. % -- INITIALIZE BEAMLET WEIGHTS --
  40. w0 = ones(numBeam,1);
  41. w0 = mean(optGoal{1}.target ./ (optGoal{1}.beamlets_pruned*w0)) * w0;
  42. % -- MAKE IT ROBUST --
  43. RO_params=0;
  44. optGoal = make_robust_optGoal(optGoal, RO_params);
  45. % -- CALLBACK OPTIMIZATION FUNCTION --
  46. fun = @(x) get_penalty(x, optGoal);
  47. % -- OPTIMIZATION PARAMETERS --
  48. % define optimization parameters
  49. A = [];
  50. b = [];
  51. Aeq = [];
  52. beq = [];
  53. lb = zeros(1, numBeam);
  54. ub = [];
  55. nonlcon = [];
  56. % define opt limits, and make it fmincon progress
  57. options = optimoptions('fmincon');
  58. options.MaxFunctionEvaluations = 30000;
  59. options.Display = 'iter';
  60. options.PlotFcn = 'optimplotfval';
  61. fprintf('-')
  62. %% Run the optimization
  63. tic
  64. w_fin = fmincon(fun,w0,A,b,Aeq,beq,lb,ub,nonlcon,options);
  65. t=toc;
  66. disp(['Optimization run time = ',num2str(t)]);
  67. %% evaluate the results
  68. D_full = reshape(beamlets * w_fin, size(Geometry.data));
  69. %% save outputs
  70. NLP_result.dose = D_full;
  71. NLP_result.weights = w_fin;
  72. save('C:\010-work\003_localGit\WiscPlan_v2\data\PatientData\matlab_files\NLP_result.mat', 'NLP_result');
  73. % plot_DVH(D_full, Geometry, 2, 1)
  74. optGoal_idx=[1,3];
  75. plot_DVH(D_full, optGoal, optGoal_idx)
  76. end
  77. % orthoslice(D_full, [0,70])
  78. %% support functions
  79. % ---- PENALTY FUNCTION 1 ----
  80. function penalty = get_penalty(x, optGoal)
  81. % this function gets called by the optimizer. It checks the penalty for
  82. % all the robust implementation and returns the worst result.
  83. NumScenarios = optGoal{1}.NbrRandScenarios * optGoal{1}.NbrSystSetUpScenarios * optGoal{1}.NbrRangeScenarios;
  84. fobj = zeros(NumScenarios,1);
  85. sc_i = 1;
  86. for nrs_i = 1:optGoal{1}.NbrRandScenarios
  87. for sss_i = 1:optGoal{1}.NbrSystSetUpScenarios % syst. setup scenarios = ss
  88. for rrs_i = 1:optGoal{1}.NbrRangeScenarios % range scenario = rs
  89. fobj(sc_i)=eval_f(x, optGoal, nrs_i, sss_i, rrs_i);
  90. sc_i = sc_i + 1;
  91. end
  92. end
  93. end
  94. % take the worst case penalty of evaluated scenarios
  95. penalty=max(fobj);
  96. end
  97. % ---- Evaluate penalty for single scenario ----
  98. function penalty = eval_f(x, optGoal, nrs_i, sss_i, rrs_i)
  99. penalty = 0;
  100. % for each condition
  101. for goal_i = 1:numel(optGoal)
  102. switch optGoal{goal_i}.function
  103. case 'min'
  104. % penalize if achieved dose is lower than target dose
  105. d_penalty = 1.0e0 * sum(max(0, ...
  106. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target) -...
  107. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned * x)));
  108. case 'max'
  109. % penalize if achieved dose is higher than target dose
  110. d_penalty = 1.0e0 * sum(max(0, ...
  111. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned * x)-...
  112. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target)));
  113. case 'LeastSquare'
  114. % penalize with sum of squares any deviation from target
  115. % dose
  116. d_penalty = 1.0e-1* sum(((...
  117. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned * x) - ...
  118. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target).^2);
  119. end
  120. penalty = penalty + d_penalty * optGoal{goal_i}.opt_weight;
  121. end
  122. end
  123. % ---- DVH PLOT FUNCTION ----
  124. function plot_DVH(dose, optGoal, optGoal_idx)
  125. % this function plots the DVHs of the given dose
  126. nfrac=1;
  127. figure
  128. hold on
  129. for roi_idx=optGoal_idx
  130. % plot the histogram
  131. [dvh dosebins] = get_dvh_data(dose,optGoal{roi_idx}.ROI_idx,nfrac);
  132. plot(dosebins, dvh,'Color', optGoal{roi_idx}.dvh_col,'LineStyle', '-','DisplayName', optGoal{roi_idx}.ROI_name);
  133. end
  134. hold off
  135. end
  136. function [dvh dosebins] = get_dvh_data(dose,roi_idx,nfrac);
  137. % this function calculates the data for the DVH
  138. dosevec = dose(roi_idx);
  139. [pdf dosebins] = hist(dosevec, 999);
  140. % clip negative bins
  141. pdf = pdf(dosebins >= 0);
  142. dosebins = dosebins(dosebins >= 0);
  143. dvh = fliplr(cumsum(fliplr(pdf))) / numel(dosevec) * 100;
  144. % append the last bin
  145. dosebins = [dosebins dosebins(end)+0.1];
  146. dvh = [dvh 0];
  147. end
  148. % ---- MAKE ROI GOALS ----
  149. function optGoal = make_ROI_goals(Geometry, beamlets)
  150. optGoal={};
  151. % -- START DEFINITION OF GOAL --
  152. goal_1.name = 'Target_min';
  153. goal_1.ROI_name = Geometry.ROIS{1, 2}.name;
  154. ROI_idx = Geometry.ROIS{1, 2}.ind;
  155. goal_1.ROI_idx = ROI_idx;
  156. goal_1.D_final = 60;
  157. goal_1.function = 'min';
  158. goal_1.beamlets_pruned = beamlets(ROI_idx, :);
  159. goal_1.target = ones(numel(ROI_idx), 1) * goal_1.D_final;
  160. goal_1.opt_weight = 1 / numel(ROI_idx); % normalize to volume of target area
  161. goal_1.dvh_col = [0.9, 0.2, 0.2]; % color of the final DVH plot
  162. % assign target
  163. optGoal{end+1}=goal_1;
  164. % -- END DEFINITION OF GOAL --
  165. % -- START DEFINITION OF GOAL --
  166. goal_2.name = 'Target_max';
  167. goal_2.ROI_name = Geometry.ROIS{1, 2}.name;
  168. ROI_idx = Geometry.ROIS{1, 2}.ind;
  169. goal_2.ROI_idx = ROI_idx;
  170. goal_2.D_final = 65;
  171. goal_2.function = 'max';
  172. goal_2.beamlets_pruned = beamlets(ROI_idx, :);
  173. goal_2.target = ones(numel(ROI_idx), 1) * goal_2.D_final;
  174. goal_2.opt_weight = 1 / numel(ROI_idx); % normalize to volume of target area
  175. goal_2.dvh_col = [0.9, 0.2, 0.2]; % color of the final DVH plot
  176. % assign target
  177. optGoal{end+1}=goal_2;
  178. % -- END DEFINITION OF GOAL --
  179. % -- START DEFINITION OF GOAL --
  180. goal_3.name = 'ROI 1_max';
  181. goal_3.ROI_name = Geometry.ROIS{1, 3}.name;
  182. ROI_idx = Geometry.ROIS{1, 3}.ind;
  183. goal_3.ROI_idx = ROI_idx;
  184. goal_3.D_final = 0;
  185. goal_3.function = 'LeastSquare';
  186. goal_3.beamlets_pruned = beamlets(ROI_idx, :);
  187. goal_3.target = ones(numel(ROI_idx), 1) * goal_3.D_final;
  188. goal_3.opt_weight = 1 / numel(ROI_idx); % normalize to volume of target area
  189. goal_3.dvh_col = [0.2, 0.9, 0.2]; % color of the final DVH plot
  190. % assign target
  191. optGoal{end+1}=goal_3;
  192. % -- END DEFINITION OF GOAL --
  193. end
  194. % ---- MAKE ROI ROBUST ----
  195. function optGoal = make_robust_optGoal(optGoal, RO_params);
  196. % take regular optimal goal and translate it into several robust cases
  197. % nrs - random scenarios
  198. % sss - system setup scenarios
  199. % rrs - random range scenarios
  200. nrs_scene_list={[0,0,0]};
  201. sss_scene_list={[0,0,0], [5,5,5], [-5, -5, -5]};
  202. rrs_scene_list={[0,0,0]};
  203. for i = 1:numel(optGoal)
  204. optGoal{i}.NbrRandScenarios =numel(nrs_scene_list);
  205. optGoal{i}.NbrSystSetUpScenarios=numel(sss_scene_list);
  206. optGoal{i}.NbrRangeScenarios =numel(rrs_scene_list);
  207. end
  208. for goal_i = 1:numel(optGoal)
  209. out_01.beamlets_pruned = optGoal{goal_i}.beamlets_pruned;
  210. out_01.target = optGoal{goal_i}.target;
  211. for nrs_i = 1:optGoal{goal_i}.NbrRandScenarios % num. of random scenarios
  212. out_02.beamlets_pruned = out_01.beamlets_pruned;
  213. out_02.target = out_01.target;
  214. for sss_i = 1:optGoal{goal_i}.NbrSystSetUpScenarios % syst. setup scenarios = sss
  215. out_03 = get_RO_sss(out_02);
  216. for rrs_i = 1:optGoal{goal_i}.NbrRangeScenarios % range scenario = rrs
  217. out_final.beamlets_pruned = out_03.beamlets_pruned;
  218. out_final.target = out_03.target;
  219. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.beamlets_pruned = optGoal{goal_i}.beamlets_pruned;
  220. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rrs{rrs_i}.target = optGoal{goal_i}.target;
  221. end
  222. end
  223. end
  224. end
  225. end
  226. function out_03 = get_RO_sss(out_02);
  227. % this function returns a systemic perturbation of the original data
  228. %% THIS PART DOES NOT WORK WELL! NEEDS TO BE FIXED SO THAT IT ACTUALLY PERMUTES DATA
  229. % wbar1 = waitbar(0, 'Creating SSS beamlet array');
  230. % for beam_i=1:numBeam
  231. % % for each beam define how much dose it delivers on each voxel
  232. % idx=beamlet_cell_array{1, beam_i}.non_zero_indices;
  233. % beamlets(idx, beam_i) = 1000*beamlet_cell_array{1, beam_i}.non_zero_values;
  234. % waitbar(beam_i/numBeam)
  235. % end
  236. % close(wbar1)
  237. out_03= out_02;
  238. end