NLP_optimizer_v2.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. % paths.patient_dir
  2. % paths.Goal_dir (previously called DP_dir)
  3. % paths.patient
  4. % paths.goalsName
  5. % colorwash(Geometry.data, D_full, [500, 1500], [0,70])
  6. % orthoslice(D_full, [0,70])
  7. function [D_full, w_fin, Geometry, optGoal] = NLP_optimizer_v2(varargin)
  8. % This function performs the beamlet optimization
  9. % [D_full, w_fin, Geometry, optGoal] = NLP_beamlet_optimizer;
  10. %
  11. % Inputs:
  12. % () OR
  13. % (Pat_path, path2goal) OR
  14. % (Pat_path, path2goal, beamlet_weights)
  15. % Pat_path, path2goal = strings to patient folder and optimal goals
  16. % beamlet_weights = initial beamlet weights
  17. %
  18. % Outputs:
  19. % full dose image dose: [D_full, w_fin, Geometry, optGoal]
  20. %
  21. % Made by Peter Ferjancic 1. May 2018
  22. % Last updated: 1. April 2019
  23. if nargin<2
  24. load('WiscPlan_preferences.mat')
  25. [Pat_path] = uigetdir([WiscPlan_preferences.patientDataPath ], 'Select Patient folder');
  26. [Goal_file,Goal_path,indx] = uigetfile([Pat_path '\matlab_files\*.mat'], 'Select OptGoal file');
  27. path2geometry = [Pat_path, '\matlab_files\Geometry.mat'];
  28. path2goal = [Goal_path, Goal_file];
  29. else
  30. Pat_path = varargin{1};
  31. path2geometry = [Pat_path, '\matlab_files\Geometry.mat'];
  32. path2goal = varargin{2};
  33. end
  34. str = inputdlg({'N of iterations for initial calc', 'N of iterations for full calc', ...
  35. 'Use pre-existing NLP_result to initiate? (y/n)'}, 'input', [1,35], {'10000', '500000', 'n'});
  36. N_fcallback1 = str2double(str{1}); % 10000 is a good guesstimate
  37. N_fcallback2 = str2double(str{2}); % 500000 is a good guesstimate
  38. pre_beamWeights = str{3};
  39. switch pre_beamWeights
  40. case 'y'
  41. [NLP_file,NLP_path,indx] = uigetfile([Pat_path '\matlab_files\*.mat'], 'Select NLP_result file');
  42. load([NLP_path, NLP_file])
  43. w_beamlets = NLP_result.weights;
  44. load([Pat_path, '\all_beams.mat'])
  45. % if numel(all_beams) ~= numel(w_beamlets)
  46. % error('Provided weight number does not match beamlet number!')
  47. % end
  48. case 'n'
  49. disp('Initial beam weights will be calculated.')
  50. end
  51. %% PROGRAM STARTS HERE
  52. % - no tocar lo que hay debajo -
  53. fprintf('starting NLP optimization process... \n')
  54. % % -- LOAD GEOMETRY, GOALS, BEAMLETS --
  55. load(path2geometry)
  56. load(path2goal)
  57. [beamlets, beamlets_joined, numBeamlet, numBeam, beam_i_list] = get_beam_lets(Geometry, Pat_path);
  58. %% -- OPTIMIZATION TARGETS --
  59. % -- make the optimization optGoal structure --
  60. for i_goal = 1:size(OptGoals.goals,1)
  61. if isfield(OptGoals.data{i_goal}, 'SupVox_num')
  62. SupVox_num = OptGoals.data{i_goal}.SupVox_num;
  63. else
  64. answer = inputdlg(['# of supervoxels for "' OptGoals.data{i_goal}.name '" with ' num2str(numel(OptGoals.data{i_goal}.ROI_idx)) ' vox: ("0" to skip)'])
  65. SupVox_num = str2double(answer{1})
  66. end
  67. switch SupVox_num
  68. case 0
  69. % if not supervoxel, just select provided ROI_idx
  70. optGoal{i_goal} = OptGoals.data{i_goal};
  71. optGoal{i_goal}.beamlets_pruned = sparse(beamlets(optGoal{i_goal}.ROI_idx, :));
  72. optGoal_beam{i_goal} = OptGoals.data{i_goal};
  73. optGoal_beam{i_goal}.beamlets_pruned = sparse(beamlets_joined(optGoal{i_goal}.ROI_idx, :));
  74. otherwise
  75. % -- if supervoxel, merge given columns
  76. % - make supervoxel map
  77. mask = zeros(OptGoals.data{i_goal}.imgDim);
  78. mask(OptGoals.data{i_goal}.ROI_idx) = 1;
  79. superMask = superpix_group(mask, SupVox_num);
  80. superVoxList = unique(superMask);
  81. superVoxList = superVoxList(superVoxList>0);
  82. optGoal{i_goal} = OptGoals.data{i_goal};
  83. optGoal{i_goal}.ROI_idx_old = optGoal{i_goal}.ROI_idx; % copy old index data
  84. optGoal{i_goal}.ROI_idx = zeros(numel(superVoxList), 1);
  85. optGoal{i_goal}.opt_weight = optGoal{i_goal}.opt_weight * numel(optGoal{i_goal}.ROI_idx_old)/numel(optGoal{i_goal}.ROI_idx);
  86. optGoal_beam{i_goal} = OptGoals.data{i_goal};
  87. optGoal_beam{i_goal}.ROI_idx_old = optGoal_beam{i_goal}.ROI_idx;
  88. optGoal_beam{i_goal}.ROI_idx = zeros(numel(superVoxList), 1);
  89. optGoal_beam{i_goal}.opt_weight = optGoal_beam{i_goal}.opt_weight * numel(optGoal_beam{i_goal}.ROI_idx_old)/numel(optGoal_beam{i_goal}.ROI_idx);
  90. h_w1 = waitbar(0, 'merging superboxels');
  91. for i_supVox = 1:numel(superVoxList)
  92. supVox_idx = superVoxList(i_supVox);
  93. waitbar(i_supVox/numel(superVoxList), h_w1)
  94. idxList = find(superMask == supVox_idx);
  95. optGoal{i_goal}.beamlets_pruned(i_supVox,:) = sparse(mean(beamlets(idxList, :),1));
  96. optGoal_beam{i_goal}.beamlets_pruned(i_supVox,:) = sparse(mean(beamlets_joined(idxList, :),1));
  97. % -- make new indeces
  98. optGoal{i_goal}.ROI_idx(i_supVox) = idxList(1);
  99. optGoal_beam{i_goal}.ROI_idx(i_supVox) = idxList(1);
  100. end
  101. close(h_w1)
  102. end
  103. end
  104. % -- make them robust --
  105. RO_params=0;
  106. optGoal_beam = make_robust_optGoal(optGoal_beam, RO_params, beamlets_joined);
  107. optGoal = make_robust_optGoal(optGoal, RO_params, beamlets);
  108. % -- CALLBACK OPTIMIZATION FUNCTION --
  109. fun1 = @(x) get_penalty(x, optGoal_beam);
  110. fun2 = @(x) get_penalty(x, optGoal);
  111. % -- OPTIMIZATION PARAMETERS --
  112. % define optimization parameters
  113. A = [];
  114. b = [];
  115. Aeq = [];
  116. beq = [];
  117. lb = zeros(1, numBeamlet);
  118. lb_beam = zeros(1, numBeam);
  119. ub = [];
  120. nonlcon = [];
  121. % define opt limits, and make it fmincon progress
  122. options = optimoptions('fmincon');
  123. options.MaxFunctionEvaluations = N_fcallback1;
  124. options.Display = 'iter';
  125. options.PlotFcn = 'optimplotfval';
  126. % options.UseParallel = true;
  127. options.UseParallel = false;
  128. % options.OptimalityTolerance = 1e-9;
  129. %% -- INITIALIZE BEAMLET WEIGHTS --
  130. switch pre_beamWeights
  131. case 'y'
  132. % should have been assigned previously.
  133. disp('Provided beamlet weights used for initial comparison')
  134. case 'n'
  135. % if initial beamlet weights are not provided, get quick estimate
  136. fprintf('\n running initial optimizer:')
  137. % initialize beamlet weights, OR
  138. w0_beams = ones(numBeam,1);
  139. w0_beams = mean(optGoal_beam{1}.D_final(optGoal{1}.ROI_idx) ./ (optGoal_beam{1}.beamlets_pruned*w0_beams+0.1)) * w0_beams;
  140. w0_beams = double(w0_beams);
  141. % -- GET BEAM WEIGHTS --
  142. tic
  143. w_beam = fmincon(fun1,w0_beams,A,b,Aeq,beq,lb_beam,ub,nonlcon,options);
  144. fprintf(' done!:')
  145. t=toc;
  146. disp(['Optimization time for beams = ',num2str(t)]);
  147. w_beamlets = ones(numBeamlet,1);
  148. numBeam=numel(unique(beam_i_list));
  149. for beam_i = 1:numBeam % assign weights to beamlets
  150. % beamlets from same beam get same initial weights
  151. w_beamlets(beam_i_list == beam_i) = w_beam(beam_i);
  152. end
  153. end
  154. %% FULL OPTIMIZATION
  155. % -- GET FULL BEAMLET WEIGHTS --
  156. options.MaxFunctionEvaluations = N_fcallback2;
  157. tic
  158. fprintf('\n running full optimizer:')
  159. w_fin = fmincon(fun2,w_beamlets,A,b,Aeq,beq,lb,ub,nonlcon,options);
  160. fprintf(' done!:')
  161. t=toc;
  162. disp(['Optimization time for beamlets = ',num2str(t)]);
  163. %% evaluate the results
  164. D_full = reshape(beamlets * w_fin, size(Geometry.data));
  165. %% save outputs
  166. NLP_result.dose = D_full;
  167. NLP_result.weights = w_fin;
  168. save([Pat_path, '\matlab_files\NLP_result.mat'], 'NLP_result');
  169. plot_DVH(Geometry, D_full)
  170. colorwash(Geometry.data, D_full, [500, 1500], [0, 66]);
  171. end
  172. %% support functions
  173. % ---- PENALTY FUNCTION ----
  174. function penalty = get_penalty(x, optGoal)
  175. % this function gets called by the optimizer. It checks the penalty for
  176. % all the robust implementation and returns the worst result.
  177. NumScenarios = optGoal{1}.NbrRandScenarios * optGoal{1}.NbrSystSetUpScenarios * optGoal{1}.NbrRangeScenarios;
  178. fobj = zeros(NumScenarios,1);
  179. sc_i = 1;
  180. for nrs_i = 1:optGoal{1}.NbrRandScenarios
  181. for sss_i = 1 :optGoal{1}.NbrSystSetUpScenarios % syst. setup scenarios = sss
  182. for rgs_i = 1:optGoal{1}.NbrRangeScenarios % range scenario = rs
  183. fobj(sc_i)=eval_f(x, optGoal, nrs_i, sss_i, rgs_i);
  184. sc_i = sc_i + 1;
  185. end
  186. end
  187. end
  188. % take the worst case penalty of evaluated scenarios
  189. penalty=max(fobj);
  190. end
  191. % ------ supp: penalty for single scenario ------
  192. function penalty = eval_f(x, optGoal, nrs_i, sss_i, rgs_i)
  193. penalty = 0;
  194. % for each condition
  195. for goal_i = 1:numel(optGoal)
  196. switch optGoal{goal_i}.function
  197. % min, max, min_sq, max_sq, LeastSquare, min_perc_Volume, max_perc_Volume
  198. case 'min'
  199. % penalize if achieved dose is lower than target dose
  200. d_penalty = 1.0e0 * sum(max(0, ...
  201. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target) -...
  202. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)));
  203. case 'max'
  204. % penalize if achieved dose is higher than target dose
  205. d_penalty = 1.0e0 * sum(max(0, ...
  206. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  207. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target)));
  208. case 'min_sq'
  209. % penalize if achieved dose is lower than target dose
  210. temp1=min(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  211. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  212. d_penalty = 1.0e0 * sum(temp1.*temp1);
  213. case 'max_sq'
  214. % penalize if achieved dose is higher than target dose
  215. temp1=max(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  216. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  217. d_penalty = 1.0e0 * sum(temp1.*temp1);
  218. case 'LeastSquare'
  219. % penalize with sum of squares any deviation from target
  220. % dose
  221. temp1 = (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x) - ...
  222. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target;
  223. d_penalty = 1.0e0* sum(temp1.^2);
  224. case 'min_perc_Volume'
  225. % penalize by amount of volume under threshold
  226. perc_vox = numel(find((optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target) -...
  227. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x) > 0)) ...
  228. / numel(optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target);
  229. d_penalty = 3.0e4 * min(perc_vox-0.05, 0)
  230. case 'max_perc_Volume'
  231. % penalize by amount of volume under threshold
  232. perc_vox = numel(find((optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target) -...
  233. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x) < 0)) ...
  234. / numel(optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target);
  235. d_penalty = 3.0e4 * min(perc_vox-0.05, 0)
  236. end
  237. penalty = penalty + d_penalty * optGoal{goal_i}.opt_weight;
  238. end
  239. end
  240. % ---- MAKE ROI ROBUST ----
  241. function optGoal = make_robust_optGoal(optGoal, RO_params, beamlets);
  242. % take regular optimal goal and translate it into several robust cases
  243. % RO_params - should have the information below
  244. % nrs - random scenarios
  245. % sss - system setup scenarios
  246. % rgs - random range scenarios
  247. % X - X>0 moves image right
  248. % Y - Y>0 moves image down
  249. % Z - in/out.
  250. shift_mag = 3; % vox of shift
  251. nrs_scene_list={[0,0,0]};
  252. % ----====#### CHANGE ROBUSTNESS HERE ####====----
  253. sss_scene_list={[0,0,0]};
  254. % sss_scene_list={[0,0,0], [-shift_mag,0,0], [shift_mag,0,0], [0,-shift_mag,0], [0,shift_mag,0], [0,0,-1], [0,0,1]};
  255. % sss_scene_list={[0,0,0], [-shift_mag,0,0], [shift_mag,0,0], [0,-shift_mag,0], [0,shift_mag,0],...
  256. % [-shift_mag*2,0,0], [shift_mag*2,0,0], [0,-shift_mag*2,0], [0,shift_mag*2,0]};
  257. % ----====#### CHANGE ROBUSTNESS HERE ####====----
  258. rgs_scene_list={[0,0,0]};
  259. % [targetIn, meta] = nrrdread('C:\010-work\003_localGit\WiscPlan_v2\data\archive\CDP_data\CDP5_DP_target.nrrd');
  260. % [targetIn, meta] = nrrdread('C:\010-work\003_localGit\WiscPlan_v2\data\PD_HD_dicomPhantom\Tomo_DP_target.nrrd');
  261. % [targetIn, meta] = nrrdread('C:\010-work\003_localGit\WiscPlan_v2\data\archive\CDP_data\CDP5_DP_target.nrrd');
  262. for i = 1:numel(optGoal)
  263. optGoal{i}.NbrRandScenarios =numel(nrs_scene_list);
  264. optGoal{i}.NbrSystSetUpScenarios=numel(sss_scene_list);
  265. optGoal{i}.NbrRangeScenarios =numel(rgs_scene_list);
  266. end
  267. for goal_i = 1:numel(optGoal)
  268. % get target
  269. idx=optGoal{goal_i}.ROI_idx;
  270. targetImg1=zeros(optGoal{goal_i}.imgDim);
  271. targetImg1(idx)=1;
  272. % get beamlets
  273. for nrs_i = 1:optGoal{goal_i}.NbrRandScenarios % num. of random scenarios
  274. % modify target and beamlets
  275. targetImg2=targetImg1;
  276. % beamlets stay the same
  277. for sss_i = 1 :optGoal{goal_i}.NbrSystSetUpScenarios % syst. setup scenarios = sss
  278. % modify target and beamlets
  279. [targetImg3 idxValid]=get_RO_sss(targetImg2, sss_scene_list{sss_i});
  280. % beamlets stay the same
  281. for rgs_i = 1:optGoal{goal_i}.NbrRangeScenarios % range scenario = rgs
  282. % modify target and beamlets
  283. targetImg4=targetImg3;
  284. % beamlets stay the same
  285. %% make new target and beamlets
  286. ROI_idx=[];
  287. ROI_idx=find(targetImg4>0);
  288. target = optGoal{goal_i}.D_final(idxValid);
  289. beamlets_pruned = beamlets(ROI_idx, :);
  290. % save to optGoal output
  291. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.ROI_idx = ROI_idx;
  292. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned = beamlets_pruned;
  293. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target = target;
  294. end
  295. end
  296. end
  297. end
  298. end
  299. % ------ supp: RO case SSS ------
  300. function [targetImg3 ia]=get_RO_sss(targetImg2, sss_scene_shift);
  301. % translate the target image
  302. targetImg3 = imtranslate(targetImg2,sss_scene_shift);
  303. % now we need to figure out if any target voxels fell out during the
  304. % shift
  305. imgValid = imtranslate(targetImg3,-sss_scene_shift);
  306. imgInvalid = (targetImg2-imgValid);
  307. idx_1 = find(targetImg2);
  308. idx_2 = find(imgInvalid);
  309. [idxValid,ia] = setdiff(idx_1,idx_2);
  310. [C,ia, ib] = intersect(idx_1,idxValid);
  311. end