|
@@ -0,0 +1,249 @@
|
|
|
+
|
|
|
+
|
|
|
+function vol_prep
|
|
|
+% this function calls three other functions:
|
|
|
+% - vol_prep_bin
|
|
|
+% - vol_prep_fuzzy
|
|
|
+% - vol_prep_DPmap
|
|
|
+% that are needed for expanding the initial segmentations done in the
|
|
|
+% segmentation GUI
|
|
|
+
|
|
|
+fprintf('Starting binary volume expansion...')
|
|
|
+vol_prep_bin
|
|
|
+fprintf(' done!\n')
|
|
|
+
|
|
|
+fprintf('Starting fuzzy volume expansion...')
|
|
|
+vol_prep_fuzzy
|
|
|
+fprintf(' done!\n')
|
|
|
+
|
|
|
+fprintf('Creating dose plans ...')
|
|
|
+vol_prep_DPmap
|
|
|
+fprintf(' done!\n')
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+function vol_prep_bin
|
|
|
+% this function takes in the segmentations and creates PTV/GTV/CTV binary volumes,
|
|
|
+% as well as appropriately cropped healthy tissue (body) segmentation.
|
|
|
+
|
|
|
+CTV_margin = 0.5; % GTV->CTV margin, in cm
|
|
|
+PTV_margin = 0.5; % GTV->CTV margin, in cm
|
|
|
+body_margin = 7.0; % how far from tumor do we still include body, in cm
|
|
|
+
|
|
|
+pathIn = '\\Mpufs5\data_wnx1\_Data\Glioma_aus\FET_FGL005\B1\Processed\';
|
|
|
+
|
|
|
+[CT_in, CT_meta] = nrrdread([pathIn 'FET_FGL005_B1_CT2FET.nrrd']);
|
|
|
+[GTV_in, GTV_meta] = nrrdread([pathIn 'B1_seg\FET_FGL005_B1_seg_thr2.0.nrrd']);
|
|
|
+[body_in, body_meta] = nrrdread([pathIn 'B1_seg\FET_FGL005_B1_head.nrrd']);
|
|
|
+
|
|
|
+
|
|
|
+%% identify target voxels - these are used when identifiying valid beamlets
|
|
|
+% voxels that need to be considered (above the threshold)
|
|
|
+p_threshold = 0;
|
|
|
+GTV_binary = zeros(size(GTV_in));
|
|
|
+GTV_binary(GTV_in>p_threshold) = 1;
|
|
|
+
|
|
|
+% Account for infiltration (GTV -> CTV)
|
|
|
+CTV = zeros(size(GTV_binary));
|
|
|
+bwD = bwdistsc(GTV_binary, GTV_meta.spacedirections);
|
|
|
+CTV(bwD<CTV_margin) = 1;
|
|
|
+
|
|
|
+% expand to PTV (CTV -> PTV)
|
|
|
+PTV = zeros(size(GTV_binary));
|
|
|
+bwD = bwdistsc(GTV_binary, GTV_meta.spacedirections);
|
|
|
+PTV(bwD<PTV_margin) = 1;
|
|
|
+
|
|
|
+%% identify body volume of interest
|
|
|
+body = body_in;
|
|
|
+bwD = bwdistsc(GTV_binary, GTV_meta.spacedirections);
|
|
|
+body(PTV>0) = 0;
|
|
|
+body(bwD>body_margin) = 0; % how far from the tumor are we still interested in the body
|
|
|
+
|
|
|
+
|
|
|
+%% save outputs
|
|
|
+filename = [pathIn 'B1_seg\FET_FGL005_B1_seg_thr2.0_binPTV.nrrd'];
|
|
|
+matrix = single(PTV);
|
|
|
+pixelspacing = GTV_meta.spacedirections;
|
|
|
+origin = GTV_meta.spaceorigin;
|
|
|
+nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
|
|
|
+
|
|
|
+filename = [pathIn 'B1_seg\FET_FGL005_B1_head_crop_bin.nrrd'];
|
|
|
+matrix = single(body);
|
|
|
+pixelspacing = body_meta.spacedirections;
|
|
|
+origin = body_meta.spaceorigin;
|
|
|
+nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
|
|
|
+
|
|
|
+
|
|
|
+colorwash(CT_in, PTV, [-500, 500], [0, 1.2]);
|
|
|
+colorwash(CT_in, body, [-500, 500], [0, 1.2]);
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+function vol_prep_fuzzy
|
|
|
+% this function takes in the segmentations and creates fuzzy (probabilistic)
|
|
|
+% PTV/GTV/CTV volumes
|
|
|
+
|
|
|
+infilt_range = 1.2; % range of infiltration, in cm. Should be value of sigma (or 1/3 decay range). Assuming normal infil.
|
|
|
+
|
|
|
+pathIn = '\\Mpufs5\data_wnx1\_Data\Glioma_aus\FET_FGL005\B1\Processed\';
|
|
|
+
|
|
|
+[CT_in, CT_meta] = nrrdread([pathIn 'FET_FGL005_B1_CT2FET.nrrd']);
|
|
|
+[GTV_in, GTV_meta] = nrrdread([pathIn 'B1_seg\FET_FGL005_B1_seg_combined.nrrd']);
|
|
|
+[body_in, body_meta] = nrrdread([pathIn 'B1_seg\FET_FGL005_B1_head.nrrd']);
|
|
|
+
|
|
|
+% colorwash(CT_in, GTV_in, [-500, 500], [0, 1.2]);
|
|
|
+
|
|
|
+%% Account for infiltration (GTV -> CTV)
|
|
|
+
|
|
|
+% -- get distance from central dot --
|
|
|
+ir_a = 1.5; % infiltration range factor
|
|
|
+
|
|
|
+CTV = zeros(size(GTV_in));
|
|
|
+bwD = bwdistsc(GTV_in>0.05, GTV_meta.spacedirections);
|
|
|
+CTV(bwD< (ir_a * infilt_range) ) = 1;
|
|
|
+
|
|
|
+% for each voxel in the area of interest, figure out the highest
|
|
|
+% probability based on range from tumor * p that tumor is there
|
|
|
+GTV_idx_lst = find(GTV_in >0.05);
|
|
|
+CTV_idx_lst = find(CTV >0.0);
|
|
|
+prob_CTV = zeros(size(GTV_in));
|
|
|
+
|
|
|
+f = waitbar(0,'calculating expansion');
|
|
|
+
|
|
|
+% define area of interest
|
|
|
+Ksize = 1+2*ceil((ir_a * infilt_range)/min(GTV_meta.spacedirections)); % get kernel for convolution.
|
|
|
+kernel_base = zeros(Ksize,Ksize,Ksize);
|
|
|
+kernel_base(ceil(Ksize/2),ceil(Ksize/2),ceil(Ksize/2))=1;
|
|
|
+bwD = bwdistsc(kernel_base, GTV_meta.spacedirections);
|
|
|
+kernel = zeros(size(kernel_base));
|
|
|
+kernel(bwD< (ir_a * infilt_range)) = 1;
|
|
|
+
|
|
|
+CTV_idx_lst_num = numel(CTV_idx_lst);
|
|
|
+prob_CTV_solutions = zeros(1,CTV_idx_lst_num);
|
|
|
+
|
|
|
+
|
|
|
+tic
|
|
|
+for i = 1:CTV_idx_lst_num
|
|
|
+ i_idx = CTV_idx_lst(i);
|
|
|
+ [y1, x1, z1] = ind2sub(size(GTV_in), i_idx);
|
|
|
+
|
|
|
+ waitbar(i/numel(CTV_idx_lst),f);
|
|
|
+
|
|
|
+ % get the crop of nearby voxels in tumor
|
|
|
+% tabula = zeros(size(GTV_in));
|
|
|
+% tabula(i_idx) = 1;
|
|
|
+% tabula2 = convn(tabula, kernel, 'same');
|
|
|
+% idx_nearby = find(tabula2>0);
|
|
|
+
|
|
|
+ idx_nearby = f_neighbors(i_idx, size(GTV_in), ir_a, infilt_range, GTV_meta.spacedirections);
|
|
|
+
|
|
|
+ % overlap
|
|
|
+ idx_GTV_nearby = intersect (idx_nearby, GTV_idx_lst);
|
|
|
+
|
|
|
+ if numel(idx_GTV_nearby) <1
|
|
|
+ prob_CTV_solutions(i) = 0;
|
|
|
+ error('this is fucky. no voxels within range')
|
|
|
+ end
|
|
|
+
|
|
|
+ p_list=zeros(1, numel(idx_GTV_nearby));
|
|
|
+ p_list_max = 0;
|
|
|
+
|
|
|
+ for j = 1:numel(idx_GTV_nearby)
|
|
|
+ j_idx = idx_GTV_nearby(j);
|
|
|
+ if GTV_in(j_idx) < p_list_max
|
|
|
+ continue
|
|
|
+ end
|
|
|
+
|
|
|
+ [y2, x2, z2] = ind2sub(size(GTV_in), j_idx);
|
|
|
+ d = sqrt((((y2-y1)*GTV_meta.spacedirections(1))^2 + ...
|
|
|
+ ((x2-x1)*GTV_meta.spacedirections(2))^2 + ((z2-z1)*GTV_meta.spacedirections(3))^2));
|
|
|
+ % get probability of invisible infiltration at given voxel, based
|
|
|
+ % on single voxel from tumor
|
|
|
+ p_list(j) = GTV_in(j_idx) * exp(-(d*d)/(infilt_range*infilt_range));
|
|
|
+
|
|
|
+ p_list_max = max(p_list_max, p_list(j));
|
|
|
+
|
|
|
+ if p_list(j) == 1
|
|
|
+ prob_CTV_solutions(i) = 1;
|
|
|
+ break
|
|
|
+ end
|
|
|
+ end
|
|
|
+ prob_CTV_solutions(i) = max(p_list);
|
|
|
+end
|
|
|
+toc
|
|
|
+prob_CTV(CTV_idx_lst) = prob_CTV_solutions;
|
|
|
+close(f)
|
|
|
+
|
|
|
+colorwash(CT_in, GTV_in, [-500, 500], [0, 1.2]);
|
|
|
+colorwash(CT_in, prob_CTV, [-500, 500], [0, 1.2]);
|
|
|
+
|
|
|
+
|
|
|
+%% save outputs
|
|
|
+filename = [pathIn 'RODP_files\FET_FGL005_B1_seg_fuzzyCTV.nrrd'];
|
|
|
+matrix = single(prob_CTV);
|
|
|
+pixelspacing = GTV_meta.spacedirections;
|
|
|
+origin = GTV_meta.spaceorigin;
|
|
|
+nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
|
|
|
+
|
|
|
+end
|
|
|
+function neigh_mat_idx = f_neighbors(idx, mat_size, ir_a, infilt_range, voxsize);
|
|
|
+% this function returns indeces of the box nearby around
|
|
|
+ [y, x, z] = ind2sub(mat_size, idx);
|
|
|
+
|
|
|
+ y1 = max(1, floor(y - ir_a*infilt_range/(voxsize(1))));
|
|
|
+ y2 = min(mat_size(1), ceil(y + ir_a*infilt_range/(voxsize(1))));
|
|
|
+
|
|
|
+ x1 = max(1, floor(x - ir_a*infilt_range/(voxsize(2))));
|
|
|
+ x2 = min(mat_size(1), ceil(x + ir_a*infilt_range/(voxsize(2))));
|
|
|
+
|
|
|
+ z1 = max(1, floor(z - ir_a*infilt_range/(voxsize(3))));
|
|
|
+ z2 = min(mat_size(1), ceil(z + ir_a*infilt_range/(voxsize(3))));
|
|
|
+
|
|
|
+ tabula = zeros(mat_size);
|
|
|
+ tabula(y1:y2, x1:x2, z1:z2)=1;
|
|
|
+
|
|
|
+ neigh_mat_idx = find( tabula>0);
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+function vol_prep_DPmap
|
|
|
+% this function takes in the segmentations and creates fuzzy (probabilistic)
|
|
|
+% PTV/GTV/CTV volumes
|
|
|
+
|
|
|
+D_min = 80;
|
|
|
+D_max = 83;
|
|
|
+
|
|
|
+pathIn = '\\Mpufs5\data_wnx1\_Data\Glioma_aus\FET_FGL005\B1\Processed\';
|
|
|
+
|
|
|
+[CT_in, CT_meta] = nrrdread([pathIn 'FET_FGL005_B1_CT2FET.nrrd']);
|
|
|
+[CTV_fuzzy, CTV_meta] = nrrdread([pathIn 'RODP_files\FET_FGL005_B1_seg_fuzzyCTV.nrrd']);
|
|
|
+
|
|
|
+dose_min = f_dose_min(CTV_fuzzy, D_min);
|
|
|
+dose_max = f_dose_max(CTV_fuzzy, D_max);
|
|
|
+
|
|
|
+orthoslice(dose_min, [0, D_max+10]) % plot min dose boundary
|
|
|
+orthoslice(dose_max, [0, D_max+10]) % plot max dose boundary
|
|
|
+
|
|
|
+%% save outputs
|
|
|
+filename = [pathIn 'RODP_files\FET_FGL005_B1_DP_minDose.nrrd'];
|
|
|
+matrix = single(dose_min);
|
|
|
+pixelspacing = CTV_meta.spacedirections;
|
|
|
+origin = CTV_meta.spaceorigin;
|
|
|
+nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
|
|
|
+
|
|
|
+filename = [pathIn 'RODP_files\FET_FGL005_B1_DP_maxDose.nrrd'];
|
|
|
+matrix = single(dose_max);
|
|
|
+pixelspacing = CTV_meta.spacedirections;
|
|
|
+origin = CTV_meta.spaceorigin;
|
|
|
+nrrdWriter(filename, matrix, pixelspacing, origin, 'raw');
|
|
|
+
|
|
|
+end
|
|
|
+function dose_min = f_dose_min(CTV_fuzzy, D_min)
|
|
|
+% get low boundary for ideal dose plan
|
|
|
+dose_min = D_min* min(1, 2* max(0, (CTV_fuzzy - 0.25)));
|
|
|
+% orthoslice(dose_min)
|
|
|
+end
|
|
|
+function dose_max = f_dose_max(CTV_fuzzy, D_max)
|
|
|
+% get hihg boundary for ideal dose plan
|
|
|
+dose_max = D_max* min(1, 2* max(0, (CTV_fuzzy - 0.0)));
|
|
|
+end
|