NLP_optimizer_v3.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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_v3(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. [Goal_path,Goal_file,ext] = fileparts(path2goal);
  34. end
  35. dialogue_box = 'no'
  36. switch dialogue_box
  37. case 'yes'
  38. str = inputdlg({'N of iterations for initial calc', 'N of iterations for full calc', ...
  39. 'Use pre-existing NLP_result to initiate? (y/n)'}, 'input', [1,35], {'100000', '500000', 'n'});
  40. N_fcallback1 = str2double(str{1}); % 100000 is a good guesstimate
  41. N_fcallback2 = str2double(str{2}); % 500000 is a good guesstimate
  42. pre_beamWeights = str{3};
  43. case 'no'
  44. disp('dialogue box skipped')
  45. N_fcallback1 = 100000;
  46. N_fcallback2 = 500000;
  47. pre_beamWeights = 'n';
  48. end
  49. switch pre_beamWeights
  50. case 'y'
  51. [NLP_file,NLP_path,indx] = uigetfile([Pat_path '\matlab_files\*.mat'], 'Select NLP_result file');
  52. load([NLP_path, NLP_file])
  53. w_beamlets = NLP_result.weights;
  54. load([Pat_path, '\all_beams.mat'])
  55. if numel(all_beams) ~= numel(w_beamlets)
  56. error('Provided weight number does not match beamlet number!')
  57. end
  58. case 'n'
  59. disp('Initial beam weights will be calculated.')
  60. end
  61. %% PROGRAM STARTS HERE
  62. % - no tocar lo que hay debajo -
  63. fprintf('starting NLP optimization process... \n')
  64. % % -- LOAD GEOMETRY, GOALS, BEAMLETS --
  65. load(path2geometry)
  66. load(path2goal)
  67. [beamlets, numBeamlet] = get_beamlets(Geometry, Pat_path, OptGoals);
  68. % [beamlets, beamlets_joined, numBeamlet, numBeam, beam_i_list] = get_beam_lets(Geometry, Pat_path);
  69. %% -- OPTIMIZATION TARGETS --
  70. % -- make the optimization optGoal structure --
  71. for i_goal = 1:size(OptGoals.goals,1)
  72. if isfield(OptGoals.data{i_goal}, 'SupVox_num')
  73. SupVox_num = OptGoals.data{i_goal}.SupVox_num;
  74. else
  75. answer = inputdlg(['# of supervoxels for "' OptGoals.data{i_goal}.name '" with ' num2str(numel(OptGoals.data{i_goal}.ROI_idx)) ' vox: ("0" to skip)'])
  76. SupVox_num = str2double(answer{1})
  77. end
  78. optGoal{i_goal} = OptGoals.data{i_goal};
  79. optGoal{i_goal}.sss_scene_list = OptGoals.sss_scene_list;
  80. optGoal{i_goal}.maxModulation = OptGoals.maxModulation;
  81. optGoal{i_goal}.BeamSmoothMax = OptGoals.BeamSmoothMax;
  82. % modulation
  83. switch SupVox_num
  84. case 0
  85. % if not supervoxel, just select provided ROI_idx
  86. optGoal{i_goal}.beamlets_pruned = sparse(beamlets(optGoal{i_goal}.ROI_idx, :));
  87. otherwise
  88. % -- if supervoxel, merge given columns
  89. % - make supervoxel map
  90. mask = zeros(OptGoals.data{i_goal}.imgDim);
  91. mask(OptGoals.data{i_goal}.ROI_idx) = 1;
  92. % group superpixels
  93. superMask = superpix_group(mask, SupVox_num, 'no');
  94. superVoxList = unique(superMask);
  95. superVoxList = superVoxList(superVoxList>0);
  96. optGoal{i_goal}.ROI_idx_old = optGoal{i_goal}.ROI_idx; % copy old index data
  97. optGoal{i_goal}.ROI_idx = zeros(numel(superVoxList), 1);
  98. optGoal{i_goal}.opt_weight = optGoal{i_goal}.opt_weight * numel(optGoal{i_goal}.ROI_idx_old)/numel(optGoal{i_goal}.ROI_idx);
  99. if isfield(OptGoals.data{i_goal}, 'wgt_map')
  100. tabula_wgtmap = zeros(size(superMask));
  101. tabula_wgtmap(OptGoals.data{i_goal}.ROI_idx) = OptGoals.data{i_goal}.wgt_map;
  102. end
  103. h_w1 = waitbar(0, 'merging superboxels');
  104. for i_supVox = 1:numel(superVoxList)
  105. waitbar(i_supVox/numel(superVoxList), h_w1)
  106. supVox_idx = superVoxList(i_supVox);
  107. idxList = find(superMask == supVox_idx);
  108. optGoal{i_goal}.beamlets_pruned(i_supVox,:) = sparse(mean(beamlets(idxList, :),1));
  109. if isfield(OptGoals.data{i_goal}, 'wgt_map')
  110. optGoal{i_goal}.vox_wgt(i_supVox) = sum(tabula_wgtmap(idxList));
  111. end
  112. % -- make new indeces
  113. optGoal{i_goal}.ROI_idx(i_supVox) = idxList(1);
  114. end
  115. close(h_w1)
  116. end
  117. end
  118. % -- make them robust --
  119. RO_params=0;
  120. optGoal = make_robust_optGoal(optGoal, RO_params, beamlets);
  121. save([Goal_path, Goal_file '_robust.mat'], 'optGoal')
  122. % -- CALLBACK OPTIMIZATION FUNCTION --
  123. fun1 = @(x) get_penalty(x, optGoal_beam);
  124. fun2 = @(x) get_penalty(x, optGoal);
  125. % -- OPTIMIZATION PARAMETERS --
  126. % define optimization parameters
  127. A = [];
  128. b = [];
  129. Aeq = [];
  130. beq = [];
  131. lb = zeros(1, numBeamlet);
  132. % lb_beam = zeros(1, numBeam);
  133. ub = [];
  134. nonlcon = [];
  135. % define opt limits, and make it fmincon progress
  136. options = optimoptions('fmincon');
  137. options.MaxFunctionEvaluations = N_fcallback1;
  138. options.Display = 'iter';
  139. options.PlotFcn = 'optimplotfval';
  140. % options.UseParallel = true;
  141. options.UseParallel = false;
  142. % options.OptimalityTolerance = 1e-9;
  143. %% -- INITIALIZE BEAMLET WEIGHTS --
  144. switch pre_beamWeights
  145. case 'y'
  146. % should have been assigned previously.
  147. disp('Provided beamlet weights used for initial comparison')
  148. case 'n'
  149. % if initial beamlet weights are not provided, get quick estimate
  150. % fprintf('\n running initial optimizer:')
  151. % initialize beamlet weights, OR
  152. w0 = ones(numBeamlet,1);
  153. % w0 = mean(optGoal{1}.D_final(optGoal{1}.ROI_idx) ./ (optGoal{1}.beamlets_pruned*w0+0.1)) * w0; % old
  154. w0 = mean(optGoal{1}.D_final(:)) ./ mean(optGoal{1}.beamlets_pruned*w0+0.05) * w0;
  155. w_beamlets = double(w0);
  156. % -- GET BEAM WEIGHTS --
  157. % tic
  158. % w_beam = fmincon(fun1,w0_beams,A,b,Aeq,beq,lb,ub,nonlcon,options);
  159. % fprintf(' done!:')
  160. % t=toc;
  161. % disp(['Optimization time for beams = ',num2str(t)]);
  162. %
  163. % w_beamlets = ones(numBeamlet,1);
  164. % numBeam=numel(unique(beam_i_list));
  165. % for beam_i = 1:numBeam % assign weights to beamlets
  166. % % beamlets from same beam get same initial weights
  167. % w_beamlets(beam_i_list == beam_i) = w_beam(beam_i);
  168. % end
  169. end
  170. %% FULL OPTIMIZATION
  171. % -- GET FULL BEAMLET WEIGHTS --
  172. options.MaxFunctionEvaluations = N_fcallback2;
  173. tic
  174. fprintf('\n running full optimizer:')
  175. w_fin = fmincon(fun2,w_beamlets,A,b,Aeq,beq,lb,ub,nonlcon,options);
  176. fprintf(' done!:')
  177. t=toc;
  178. disp(['Optimization time for beamlets = ',num2str(t)]);
  179. %% evaluate the results
  180. D_full = reshape(beamlets * w_fin, size(Geometry.data));
  181. %% save outputs
  182. NLP_result.dose = D_full;
  183. NLP_result.weights = w_fin;
  184. NLP_result.sss_scene_list = optGoal{1}.sss_scene_list;
  185. save([Pat_path, '\matlab_files\NLP_result_' Goal_file '.mat'], 'NLP_result');
  186. plot_DVH(Geometry, D_full)
  187. colorwash(Geometry.data, D_full, [500, 1500], [0, 90]);
  188. end
  189. %% support functions
  190. % ---- PENALTY FUNCTION ----
  191. function penalty = get_penalty(x, optGoal)
  192. % this function gets called by the optimizer. It checks the penalty for
  193. % all the robust implementation and returns the worst result.
  194. NumScenarios = optGoal{1}.NbrRandScenarios * optGoal{1}.NbrSystSetUpScenarios * optGoal{1}.NbrRangeScenarios;
  195. fobj = zeros(NumScenarios,1);
  196. sc_i = 1;
  197. for nrs_i = 1:optGoal{1}.NbrRandScenarios
  198. for sss_i = 1 :optGoal{1}.NbrSystSetUpScenarios % syst. setup scenarios = sss
  199. for rgs_i = 1:optGoal{1}.NbrRangeScenarios % range scenario = rs
  200. fobj(sc_i)=eval_f(x, optGoal, nrs_i, sss_i, rgs_i);
  201. sc_i = sc_i + 1;
  202. end
  203. end
  204. end
  205. % take the worst case penalty of evaluated scenarios
  206. penalty=max(fobj);
  207. % take the median worst case (stochastic robust)
  208. % penalty=median(fobj);
  209. end
  210. % ------ supp: penalty for single scenario ------
  211. function penalty = eval_f(x, optGoal, nrs_i, sss_i, rgs_i)
  212. penalty = 0;
  213. % for each condition
  214. for goal_i = 1:numel(optGoal)
  215. switch optGoal{goal_i}.function
  216. % min, max, min_sq, max_sq, LeastSquare, min_perc_Volume, max_perc_Volume
  217. case 'min'
  218. % penalize if achieved dose is lower than target dose
  219. d_penalty = 1.0e0 * sum(max(0, ...
  220. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target) -...
  221. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)));
  222. case 'max'
  223. % penalize if achieved dose is higher than target dose
  224. d_penalty = 1.0e0 * sum(max(0, ...
  225. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  226. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target)));
  227. case 'min_sq'
  228. % penalize if achieved dose is lower than target dose
  229. temp1=min(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  230. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  231. d_penalty = 1.0e0 * sum(temp1.*temp1);
  232. case 'max_sq'
  233. % penalize if achieved dose is higher than target dose
  234. temp1=max(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  235. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  236. d_penalty = 1.0e0 * sum(temp1.*temp1);
  237. case 'min_exp'
  238. % penalize if achieved dose is lower than target dose
  239. temp1=-min(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  240. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  241. d_penalty = 1.0e0 * sum(exp(temp1));
  242. case 'max_exp'
  243. % penalize if achieved dose is higher than target dose
  244. temp1=max(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  245. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  246. d_penalty = 1.0e0 * sum(exp(temp1));
  247. case 'LeastSquare'
  248. % penalize with sum of squares any deviation from target
  249. % dose
  250. temp1 = (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x) - ...
  251. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target;
  252. d_penalty = 1.0e0* sum(temp1.^2);
  253. case 'min_perc_Volume'
  254. % penalize by amount of volume under threshold
  255. perc_vox = numel(find((optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target) -...
  256. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x) > 0)) ...
  257. / numel(optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target);
  258. d_penalty = 3.0e4 * min(perc_vox-0.05, 0)
  259. case 'max_perc_Volume'
  260. % penalize by amount of volume under threshold
  261. perc_vox = numel(find((optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target) -...
  262. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x) < 0)) ...
  263. / numel(optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target);
  264. d_penalty = 3.0e4 * min(perc_vox-0.05, 0)
  265. case 'min_sq_voxwgt'
  266. % penalize if achieved dose is lower than target dose
  267. temp1=min(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  268. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  269. d_penalty = 1.0e0 * sum(temp1.*temp1.* optGoal{goal_i}.vox_wgt');
  270. case 'max_sq_voxwgt'
  271. % penalize if achieved dose is lower than target dose
  272. temp1=min(0, (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned * x)-...
  273. (optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target));
  274. d_penalty = 1.0e0 * sum(temp1.*temp1.* optGoal{goal_i}.vox_wgt');
  275. end
  276. penalty = penalty + d_penalty * optGoal{goal_i}.opt_weight;
  277. end
  278. %% add modulation penalty
  279. if true
  280. if isfield(optGoal{goal_i}, 'maxModulation')
  281. max_modulation = optGoal{goal_i}.maxModulation;
  282. else
  283. error('no Max modulation parameter enterd!')
  284. max_modulation = 5;
  285. end
  286. mod_pen_weight = 1.0e10;
  287. % calc the penalty
  288. mod_excess = max(0, x-max_modulation*mean(x));
  289. mod_pen1 = mod_pen_weight*(sum(mod_excess) + numel(mod_excess)* any(mod_excess));
  290. penalty = penalty + mod_pen1;
  291. end
  292. %% add penlty for single off beamlets - version 1
  293. if false
  294. if isfield(optGoal{goal_i}, 'BeamSmoothMax')
  295. BeamSmoothMax = optGoal{goal_i}.BeamSmoothMax;
  296. else
  297. error('no Max beam smooth parameter enterd!')
  298. BeamSmoothMax = 1e6;
  299. end
  300. mod_pen_weight = BeamSmoothMax; %1.0e6
  301. x_down = zeros(size(x));
  302. x_down(2:end) = x(1:end-1);
  303. x_up = zeros(size(x));
  304. x_up(1:end-1) = x(2:end);
  305. mod_pen2 = mod_pen_weight*sum(((x-x_down).*(x-x_up)).^2)/(mean(x)^(2*2)*numel(x));
  306. penalty = penalty + mod_pen2;
  307. end
  308. %% add penlty for single off beamlets - version 2
  309. if true
  310. if isfield(optGoal{goal_i}, 'BeamSmoothMax')
  311. BeamSmoothMax = optGoal{goal_i}.BeamSmoothMax;
  312. else
  313. error('no Max beam smooth parameter enterd!')
  314. BeamSmoothMax = 1e6;
  315. end
  316. mod_pen_weight = BeamSmoothMax; %1.0e6
  317. x_down = zeros(size(x));
  318. x_down(2:end) = x(1:end-1);
  319. x_down2 = zeros(size(x));
  320. x_down2(3:end) = x(1:end-2);
  321. x_up = zeros(size(x));
  322. x_up(1:end-1) = x(2:end);
  323. x_up2 = zeros(size(x));
  324. x_up2(1:end-2) = x(3:end);
  325. mod_pen2 = mod_pen_weight*sum((x_down+x_up-2*x).^2 + 0.5*(x_down2+x_up2-2*x).^2)/(mean(x)^(2)*numel(x));
  326. penalty = penalty + mod_pen2;
  327. end
  328. end
  329. % ---- MAKE ROI ROBUST ----
  330. function optGoal = make_robust_optGoal(optGoal, RO_params, beamlets);
  331. % take regular optimal goal and translate it into several robust cases
  332. % RO_params - should have the information below
  333. % nrs - random scenarios
  334. % sss - system setup scenarios
  335. % rgs - random range scenarios
  336. % X - X>0 moves image right
  337. % Y - Y>0 moves image down
  338. % Z - in/out.
  339. shift_X = 2; % vox of shift
  340. shift_Y = 2; % vox of shift
  341. shift_Z = 1; % vox of shift
  342. nrs_scene_list={[0,0,0]};
  343. % ----====#### CHANGE ROBUSTNESS HERE ####====----
  344. if isfield(optGoal{1}, 'sss_scene_list')
  345. sss_scene_list = optGoal{1}.sss_scene_list;
  346. else
  347. sss_scene_list={[0,0,0], [-shift_Y,0,0], [shift_Y,0,0], [0,-shift_X,0], [0,shift_X,0], [0,0,-shift_Z], [0,0,shift_Z]};
  348. optGoal{1}.sss_scene_list = sss_scene_list;
  349. end
  350. % sss_scene_list={[0,0,0]};
  351. % ----====#### CHANGE ROBUSTNESS HERE ####====----
  352. % [targetIn, meta] = nrrdread('C:\010-work\003_localGit\WiscPlan_v2\data\archive\CDP_data\CDP5_DP_target.nrrd');
  353. % [targetIn, meta] = nrrdread('C:\010-work\003_localGit\WiscPlan_v2\data\PD_HD_dicomPhantom\Tomo_DP_target.nrrd');
  354. % [targetIn, meta] = nrrdread('C:\010-work\003_localGit\WiscPlan_v2\data\archive\CDP_data\CDP5_DP_target.nrrd');
  355. rgs_scene_list={[0,0,0]};
  356. for i = 1:numel(optGoal)
  357. optGoal{i}.NbrRandScenarios =numel(nrs_scene_list);
  358. optGoal{i}.NbrSystSetUpScenarios=numel(sss_scene_list);
  359. optGoal{i}.NbrRangeScenarios =numel(rgs_scene_list);
  360. end
  361. for goal_i = 1:numel(optGoal)
  362. % get target
  363. idx=optGoal{goal_i}.ROI_idx;
  364. targetImg1=zeros(optGoal{goal_i}.imgDim);
  365. targetImg1(idx)=1;
  366. % get beamlets
  367. for nrs_i = 1:optGoal{goal_i}.NbrRandScenarios % num. of random scenarios
  368. % modify target and beamlets
  369. targetImg2=targetImg1;
  370. % beamlets stay the same
  371. for sss_i = 1 :optGoal{goal_i}.NbrSystSetUpScenarios % syst. setup scenarios = sss
  372. % modify target and beamlets
  373. [targetImg3 idxValid]=get_RO_sss(targetImg2, sss_scene_list{sss_i});
  374. % beamlets stay the same
  375. for rgs_i = 1:optGoal{goal_i}.NbrRangeScenarios % range scenario = rgs
  376. % modify target and beamlets
  377. targetImg4=targetImg3;
  378. % beamlets stay the same
  379. %% make new target and beamlets
  380. ROI_idx=[];
  381. ROI_idx=find(targetImg4>0);
  382. target = optGoal{goal_i}.D_final(idxValid);
  383. beamlets_pruned = beamlets(ROI_idx, :);
  384. % save to optGoal output
  385. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.ROI_idx = ROI_idx;
  386. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.beamlets_pruned = beamlets_pruned;
  387. optGoal{goal_i}.nrs{nrs_i}.sss{sss_i}.rgs{rgs_i}.target = target;
  388. end
  389. end
  390. end
  391. end
  392. end
  393. %% ------ supp: RO case SSS ------
  394. function [targetImg3 ia]=get_RO_sss(targetImg2, sss_scene_shift);
  395. % translate the target image
  396. targetImg3 = imtranslate(targetImg2,sss_scene_shift);
  397. % now we need to figure out if any target voxels fell out during the
  398. % shift
  399. imgValid = imtranslate(targetImg3,-sss_scene_shift);
  400. imgInvalid = (targetImg2-imgValid);
  401. idx_1 = find(targetImg2);
  402. idx_2 = find(imgInvalid);
  403. [idxValid,ia] = setdiff(idx_1,idx_2);
  404. [C,ia, ib] = intersect(idx_1,idxValid);
  405. end