vol_prep.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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'];
  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 = 0.5; % CTV->PTV margin, in cm
  26. margins.body = 7.0; % how far from tumor do we still include body, in cm
  27. dose.min = 60;
  28. dose.max = 63;
  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 '_binCTV.nrrd'];
  67. matrix = single(CTV);
  68. pixelspacing = GTV_meta.spacedirections;
  69. origin = GTV_meta.spaceorigin;
  70. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  71. filename = [paths.in '\RODP_files\' paths.target_bin_in '_binPTV.nrrd'];
  72. matrix = single(PTV);
  73. pixelspacing = GTV_meta.spacedirections;
  74. origin = GTV_meta.spaceorigin;
  75. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  76. filename = [paths.in '\RODP_files\' paths.body_bin_in '_crop_bin.nrrd'];
  77. matrix = single(body);
  78. pixelspacing = body_meta.spacedirections;
  79. origin = body_meta.spaceorigin;
  80. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  81. colorwash(CT_in, PTV, [-500, 500], [0, 1.2]);
  82. colorwash(CT_in, body, [-500, 500], [0, 1.2]);
  83. end
  84. function vol_prep_fuzzy(paths, margins)
  85. % this function takes in the segmentations and creates fuzzy (probabilistic)
  86. % PTV/GTV/CTV volumes
  87. infilt_range = margins.CTV; % range of infiltration, in cm. Should be value of sigma (or 1/3 decay range). Assuming normal infil.
  88. [CT_in, CT_meta] = nrrdread([paths.in '\' paths.CT_in '.nrrd']);
  89. [GTV_in, GTV_meta] = nrrdread([paths.in '\' paths.target_fzy_in '.nrrd']);
  90. % [body_in, body_meta] = nrrdread([paths.in '\' paths.body_bin_in '.nrrd']);
  91. colorwash(CT_in, GTV_in, [-500, 500], [0, 1.2]);
  92. %% Account for infiltration (GTV -> CTV)
  93. % -- get distance from central dot --
  94. ir_a = 1.5; % infiltration range factor
  95. CTV = zeros(size(GTV_in));
  96. bwD = bwdistsc(GTV_in>0.05, GTV_meta.spacedirections);
  97. CTV(bwD< (ir_a * infilt_range) ) = 1;
  98. % for each voxel in the area of interest, figure out the highest
  99. % probability based on range from tumor * p that tumor is there
  100. GTV_idx_lst = find(GTV_in >0.05);
  101. CTV_idx_lst = find(CTV >0.0);
  102. prob_CTV = zeros(size(GTV_in));
  103. f = waitbar(0,'calculating expansion');
  104. % define area of interest
  105. Ksize = 1+2*ceil((ir_a * infilt_range)/min(GTV_meta.spacedirections)); % get kernel for convolution.
  106. kernel_base = zeros(Ksize,Ksize,Ksize);
  107. kernel_base(ceil(Ksize/2),ceil(Ksize/2),ceil(Ksize/2))=1;
  108. bwD = bwdistsc(kernel_base, GTV_meta.spacedirections);
  109. kernel = zeros(size(kernel_base));
  110. kernel(bwD< (ir_a * infilt_range)) = 1;
  111. CTV_idx_lst_num = numel(CTV_idx_lst);
  112. prob_CTV_solutions = zeros(1,CTV_idx_lst_num);
  113. tic
  114. for i = 1:CTV_idx_lst_num
  115. i_idx = CTV_idx_lst(i);
  116. [y1, x1, z1] = ind2sub(size(GTV_in), i_idx);
  117. waitbar(i/numel(CTV_idx_lst),f);
  118. % get the crop of nearby voxels in tumor
  119. % tabula = zeros(size(GTV_in));
  120. % tabula(i_idx) = 1;
  121. % tabula2 = convn(tabula, kernel, 'same');
  122. % idx_nearby = find(tabula2>0);
  123. idx_nearby = f_neighbors(i_idx, size(GTV_in), ir_a, infilt_range, GTV_meta.spacedirections);
  124. % overlap
  125. idx_GTV_nearby = intersect (idx_nearby, GTV_idx_lst);
  126. if numel(idx_GTV_nearby) <1
  127. prob_CTV_solutions(i) = 0;
  128. error('this is fucky. no voxels within range')
  129. end
  130. p_list=zeros(1, numel(idx_GTV_nearby));
  131. p_list_max = 0;
  132. for j = 1:numel(idx_GTV_nearby)
  133. j_idx = idx_GTV_nearby(j);
  134. if GTV_in(j_idx) < p_list_max
  135. continue
  136. end
  137. [y2, x2, z2] = ind2sub(size(GTV_in), j_idx);
  138. d = sqrt((((y2-y1)*GTV_meta.spacedirections(1))^2 + ...
  139. ((x2-x1)*GTV_meta.spacedirections(2))^2 + ((z2-z1)*GTV_meta.spacedirections(3))^2));
  140. % get probability of invisible infiltration at given voxel, based
  141. % on single voxel from tumor
  142. p_list(j) = GTV_in(j_idx) * exp(-(d*d)/(infilt_range*infilt_range));
  143. p_list_max = max(p_list_max, p_list(j));
  144. if p_list(j) == 1
  145. prob_CTV_solutions(i) = 1;
  146. break
  147. end
  148. end
  149. prob_CTV_solutions(i) = max(p_list);
  150. end
  151. toc
  152. prob_CTV(CTV_idx_lst) = prob_CTV_solutions;
  153. close(f)
  154. colorwash(CT_in, prob_CTV, [-500, 500], [0, 1.2]);
  155. %% save outputs
  156. filename = [paths.in '\RODP_files\' paths.target_bin_in '_fuzzyCTV.nrrd'];
  157. matrix = single(prob_CTV);
  158. pixelspacing = GTV_meta.spacedirections;
  159. origin = GTV_meta.spaceorigin;
  160. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  161. end
  162. function neigh_mat_idx = f_neighbors(idx, mat_size, ir_a, infilt_range, voxsize);
  163. % this function returns indeces of the box nearby around
  164. [y, x, z] = ind2sub(mat_size, idx);
  165. y1 = max(1, floor(y - ir_a*infilt_range/(voxsize(1))));
  166. y2 = min(mat_size(1), ceil(y + ir_a*infilt_range/(voxsize(1))));
  167. x1 = max(1, floor(x - ir_a*infilt_range/(voxsize(2))));
  168. x2 = min(mat_size(1), ceil(x + ir_a*infilt_range/(voxsize(2))));
  169. z1 = max(1, floor(z - ir_a*infilt_range/(voxsize(3))));
  170. z2 = min(mat_size(1), ceil(z + ir_a*infilt_range/(voxsize(3))));
  171. tabula = zeros(mat_size);
  172. tabula(y1:y2, x1:x2, z1:z2)=1;
  173. neigh_mat_idx = find( tabula>0);
  174. end
  175. function vol_prep_DPmap(paths, dose)
  176. % this function takes in the segmentations and creates fuzzy (probabilistic)
  177. % PTV/GTV/CTV volumes
  178. [CT_in, CT_meta] = nrrdread([paths.in '\' paths.CT_in '.nrrd']);
  179. [CTV_fuzzy, CTV_meta] = nrrdread([paths.in '\RODP_files\' paths.target_bin_in '_fuzzyCTV.nrrd']);
  180. dose_min = f_dose_min(CTV_fuzzy, dose.min);
  181. dose_max = f_dose_max(CTV_fuzzy, dose.max);
  182. orthoslice(dose_min, [0, dose.max+10]) % plot min dose boundary
  183. orthoslice(dose_max, [0, dose.max+10]) % plot max dose boundary
  184. %% save outputs
  185. filename = [paths.in '\RODP_files\' paths.target_bin_in '_DP_minDose.nrrd'];
  186. % filename = [pathIn 'RODP_files\FET_FGL005_B1_DP_minDose.nrrd'];
  187. matrix = single(dose_min);
  188. pixelspacing = CTV_meta.spacedirections;
  189. origin = CTV_meta.spaceorigin;
  190. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  191. filename = [paths.in '\RODP_files\' paths.target_bin_in '_DP_maxDose.nrrd'];
  192. % filename = [pathIn 'RODP_files\FET_FGL005_B1_DP_maxDose.nrrd'];
  193. matrix = single(dose_max);
  194. pixelspacing = CTV_meta.spacedirections;
  195. origin = CTV_meta.spaceorigin;
  196. nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
  197. end
  198. function dose_min = f_dose_min(CTV_fuzzy, D_min)
  199. % get low boundary for ideal dose plan
  200. dose_min = D_min* min(1, 2* max(0, (CTV_fuzzy - 0.25)));
  201. dose_min(CTV_fuzzy>0.98) = 1.2*D_min;
  202. % orthoslice(dose_min)
  203. end
  204. function dose_max = f_dose_max(CTV_fuzzy, D_max)
  205. % get hihg boundary for ideal dose plan
  206. dose_max = D_max* min(1, 2* max(0, (CTV_fuzzy - 0.0)));
  207. dose_max(CTV_fuzzy>0.98) = 1.2*D_max;
  208. end