vol_prep.m 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. function vol_prep
  2. % This series of functions take in segmented PET/CT image paths and outputs
  3. % Dose Painting (DP) maps, as well as some intermediate results.
  4. % this function calls three other functions:
  5. % - vol_prep_bin
  6. % - vol_prep_fuzzy
  7. % - vol_prep_DPmap
  8. % that are needed for expanding the initial segmentations done in the
  9. % segmentation GUI
  10. close all
  11. %% ---=== INPUT PARAMS ===---
  12. patname = 'FET_FGL005';
  13. % patname = 'FET_FGL015';
  14. % patname = 'FET_FGL022';
  15. timepoint = 'B1';
  16. paths.in = ['\\Mpufs5\data_wnx1\_Data\Glioma_aus\' patname '\' timepoint '\Processed'];
  17. paths.CT_in = [patname '_' timepoint '_CT2FET'];
  18. paths.target_bin_in = [ patname '_' timepoint '_seg_thr2.0'];
  19. paths.target_fzy_in = [ patname '_' timepoint '_seg_combined'];
  20. paths.body_bin_in = [ patname '_' timepoint '_head_crop_bin'];
  21. % paths.target_bin_in = [patname '_' timepoint '_thr2'];
  22. % paths.target_fzy_in = [patname '_' timepoint '_thr2'];
  23. % paths.body_bin_in = [patname '_' timepoint '_head'];
  24. margins.CTV = 1.0; % GTV->CTV margin, in cm
  25. margins.PTV = 1.0; % CTV->PTV margin, in cm
  26. margins.body = 7.0; % how far from tumor do we still include body, in cm
  27. dose.min = 80;
  28. dose.max = 83;
  29. %% ---=== FUNCTION CALLS ===---
  30. fprintf('Starting binary volume expansion...')
  31. vol_prep_bin(paths, margins)
  32. fprintf(' done!\n')
  33. fprintf('Starting fuzzy volume expansion...')
  34. vol_prep_fuzzy(paths, margins)
  35. fprintf(' done!\n')
  36. fprintf('Creating dose plans ...')
  37. vol_prep_DPmap(paths, dose)
  38. fprintf(' done!\n')
  39. end
  40. function vol_prep_bin(paths, margins)
  41. % this function takes in the segmentations and creates PTV/GTV/CTV binary volumes,
  42. % as well as appropriately cropped healthy tissue (body) segmentation.
  43. [CT_in, CT_meta] = nrrdread([paths.in '\' paths.CT_in '.nrrd']);
  44. [GTV_in, GTV_meta] = nrrdread([paths.in '\' paths.target_bin_in '.nrrd']);
  45. [body_in, body_meta] = nrrdread([paths.in '\' paths.body_bin_in '.nrrd']);
  46. colorwash(CT_in, GTV_in, [-500, 500], [0, 1.2]);
  47. %% identify target voxels - these are used when identifiying valid beamlets
  48. % voxels that need to be considered (above the threshold)
  49. p_threshold = 0;
  50. GTV_binary = zeros(size(GTV_in));
  51. GTV_binary(GTV_in>p_threshold) = 1;
  52. % Account for infiltration (GTV -> CTV)
  53. CTV = zeros(size(GTV_binary));
  54. bwD = bwdistsc(GTV_binary, GTV_meta.spacedirections);
  55. CTV(bwD<margins.CTV) = 1;
  56. % expand to PTV (CTV -> PTV)
  57. PTV = zeros(size(GTV_binary));
  58. bwD = bwdistsc(GTV_binary, GTV_meta.spacedirections);
  59. PTV(bwD<margins.PTV) = 1;
  60. %% identify body volume of interest
  61. body = body_in;
  62. bwD = bwdistsc(GTV_binary, GTV_meta.spacedirections);
  63. body(PTV>0) = 0;
  64. body(bwD>margins.body) = 0; % how far from the tumor are we still interested in the body
  65. %% save outputs
  66. filename = [paths.in '\RODP_files\' paths.target_bin_in '_binPTV.nrrd'];
  67. matrix = single(PTV);
  68. pixelspacing = GTV_meta.spacedirections;
  69. origin = GTV_meta.spaceorigin;
  70. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  71. filename = [paths.in '\RODP_files\' paths.body_bin_in '_crop_bin.nrrd'];
  72. matrix = single(body);
  73. pixelspacing = body_meta.spacedirections;
  74. origin = body_meta.spaceorigin;
  75. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  76. colorwash(CT_in, PTV, [-500, 500], [0, 1.2]);
  77. colorwash(CT_in, body, [-500, 500], [0, 1.2]);
  78. end
  79. function vol_prep_fuzzy(paths, margins)
  80. % this function takes in the segmentations and creates fuzzy (probabilistic)
  81. % PTV/GTV/CTV volumes
  82. infilt_range = margins.CTV; % range of infiltration, in cm. Should be value of sigma (or 1/3 decay range). Assuming normal infil.
  83. [CT_in, CT_meta] = nrrdread([paths.in '\' paths.CT_in '.nrrd']);
  84. [GTV_in, GTV_meta] = nrrdread([paths.in '\' paths.target_fzy_in '.nrrd']);
  85. % [body_in, body_meta] = nrrdread([paths.in '\' paths.body_bin_in '.nrrd']);
  86. colorwash(CT_in, GTV_in, [-500, 500], [0, 1.2]);
  87. %% Account for infiltration (GTV -> CTV)
  88. % -- get distance from central dot --
  89. ir_a = 1.5; % infiltration range factor
  90. CTV = zeros(size(GTV_in));
  91. bwD = bwdistsc(GTV_in>0.05, GTV_meta.spacedirections);
  92. CTV(bwD< (ir_a * infilt_range) ) = 1;
  93. % for each voxel in the area of interest, figure out the highest
  94. % probability based on range from tumor * p that tumor is there
  95. GTV_idx_lst = find(GTV_in >0.05);
  96. CTV_idx_lst = find(CTV >0.0);
  97. prob_CTV = zeros(size(GTV_in));
  98. f = waitbar(0,'calculating expansion');
  99. % define area of interest
  100. Ksize = 1+2*ceil((ir_a * infilt_range)/min(GTV_meta.spacedirections)); % get kernel for convolution.
  101. kernel_base = zeros(Ksize,Ksize,Ksize);
  102. kernel_base(ceil(Ksize/2),ceil(Ksize/2),ceil(Ksize/2))=1;
  103. bwD = bwdistsc(kernel_base, GTV_meta.spacedirections);
  104. kernel = zeros(size(kernel_base));
  105. kernel(bwD< (ir_a * infilt_range)) = 1;
  106. CTV_idx_lst_num = numel(CTV_idx_lst);
  107. prob_CTV_solutions = zeros(1,CTV_idx_lst_num);
  108. tic
  109. for i = 1:CTV_idx_lst_num
  110. i_idx = CTV_idx_lst(i);
  111. [y1, x1, z1] = ind2sub(size(GTV_in), i_idx);
  112. waitbar(i/numel(CTV_idx_lst),f);
  113. % get the crop of nearby voxels in tumor
  114. % tabula = zeros(size(GTV_in));
  115. % tabula(i_idx) = 1;
  116. % tabula2 = convn(tabula, kernel, 'same');
  117. % idx_nearby = find(tabula2>0);
  118. idx_nearby = f_neighbors(i_idx, size(GTV_in), ir_a, infilt_range, GTV_meta.spacedirections);
  119. % overlap
  120. idx_GTV_nearby = intersect (idx_nearby, GTV_idx_lst);
  121. if numel(idx_GTV_nearby) <1
  122. prob_CTV_solutions(i) = 0;
  123. error('this is fucky. no voxels within range')
  124. end
  125. p_list=zeros(1, numel(idx_GTV_nearby));
  126. p_list_max = 0;
  127. for j = 1:numel(idx_GTV_nearby)
  128. j_idx = idx_GTV_nearby(j);
  129. if GTV_in(j_idx) < p_list_max
  130. continue
  131. end
  132. [y2, x2, z2] = ind2sub(size(GTV_in), j_idx);
  133. d = sqrt((((y2-y1)*GTV_meta.spacedirections(1))^2 + ...
  134. ((x2-x1)*GTV_meta.spacedirections(2))^2 + ((z2-z1)*GTV_meta.spacedirections(3))^2));
  135. % get probability of invisible infiltration at given voxel, based
  136. % on single voxel from tumor
  137. p_list(j) = GTV_in(j_idx) * exp(-(d*d)/(infilt_range*infilt_range));
  138. p_list_max = max(p_list_max, p_list(j));
  139. if p_list(j) == 1
  140. prob_CTV_solutions(i) = 1;
  141. break
  142. end
  143. end
  144. prob_CTV_solutions(i) = max(p_list);
  145. end
  146. toc
  147. prob_CTV(CTV_idx_lst) = prob_CTV_solutions;
  148. close(f)
  149. colorwash(CT_in, prob_CTV, [-500, 500], [0, 1.2]);
  150. %% save outputs
  151. filename = [paths.in '\RODP_files\' paths.target_bin_in '_fuzzyCTV.nrrd'];
  152. matrix = single(prob_CTV);
  153. pixelspacing = GTV_meta.spacedirections;
  154. origin = GTV_meta.spaceorigin;
  155. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  156. end
  157. function neigh_mat_idx = f_neighbors(idx, mat_size, ir_a, infilt_range, voxsize);
  158. % this function returns indeces of the box nearby around
  159. [y, x, z] = ind2sub(mat_size, idx);
  160. y1 = max(1, floor(y - ir_a*infilt_range/(voxsize(1))));
  161. y2 = min(mat_size(1), ceil(y + ir_a*infilt_range/(voxsize(1))));
  162. x1 = max(1, floor(x - ir_a*infilt_range/(voxsize(2))));
  163. x2 = min(mat_size(1), ceil(x + ir_a*infilt_range/(voxsize(2))));
  164. z1 = max(1, floor(z - ir_a*infilt_range/(voxsize(3))));
  165. z2 = min(mat_size(1), ceil(z + ir_a*infilt_range/(voxsize(3))));
  166. tabula = zeros(mat_size);
  167. tabula(y1:y2, x1:x2, z1:z2)=1;
  168. neigh_mat_idx = find( tabula>0);
  169. end
  170. function vol_prep_DPmap(paths, dose)
  171. % this function takes in the segmentations and creates fuzzy (probabilistic)
  172. % PTV/GTV/CTV volumes
  173. [CT_in, CT_meta] = nrrdread([paths.in '\' paths.CT_in '.nrrd']);
  174. [CTV_fuzzy, CTV_meta] = nrrdread([paths.in '\RODP_files\' paths.target_bin_in '_fuzzyCTV.nrrd']);
  175. dose_min = f_dose_min(CTV_fuzzy, dose.min);
  176. dose_max = f_dose_max(CTV_fuzzy, dose.max);
  177. orthoslice(dose_min, [0, dose.max+10]) % plot min dose boundary
  178. orthoslice(dose_max, [0, dose.max+10]) % plot max dose boundary
  179. %% save outputs
  180. filename = [paths.in '\RODP_files\' paths.target_bin_in '_DP_minDose.nrrd'];
  181. % filename = [pathIn 'RODP_files\FET_FGL005_B1_DP_minDose.nrrd'];
  182. matrix = single(dose_min);
  183. pixelspacing = CTV_meta.spacedirections;
  184. origin = CTV_meta.spaceorigin;
  185. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  186. filename = [paths.in '\RODP_files\' paths.target_bin_in '_DP_maxDose.nrrd'];
  187. % filename = [pathIn 'RODP_files\FET_FGL005_B1_DP_maxDose.nrrd'];
  188. matrix = single(dose_max);
  189. pixelspacing = CTV_meta.spacedirections;
  190. origin = CTV_meta.spaceorigin;
  191. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  192. end
  193. function dose_min = f_dose_min(CTV_fuzzy, D_min)
  194. % get low boundary for ideal dose plan
  195. dose_min = D_min* min(1, 2* max(0, (CTV_fuzzy - 0.25)));
  196. % orthoslice(dose_min)
  197. end
  198. function dose_max = f_dose_max(CTV_fuzzy, D_max)
  199. % get hihg boundary for ideal dose plan
  200. dose_max = D_max* min(1, 2* max(0, (CTV_fuzzy - 0.0)));
  201. end