homotopy_denoising.m 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. % homotopy_denoising.m
  2. % Within-subjects:
  3. % 1. Linear trends
  4. % 2. Quadratic trends
  5. % 3. 6 rigid-body motion parameters
  6. % 4. 5 principal components of CSF signals
  7. % 5. Body temperature
  8. % 6. Isoflurane dose
  9. % 7. Global signal regression (optional)
  10. % 8. Bandpass filtering
  11. % Between-subjects:
  12. % 1. Age
  13. % 2. Mean Body Temperature
  14. % 3. Mean Isoflurane Dose
  15. % 4. ROI pair Euclidean distance
  16. % 5. ROI volume
  17. % Note: requires Tools for NIfTI and ANALYZE image
  18. % (https://www.mathworks.com/matlabcentral/fileexchange/8797-tools-for-nifti-and-analyze-image)
  19. % Use smoothed or unsmoothed data for ROI extraction?
  20. useSmoothedData = false;
  21. % Use homotopic-SC as well as homotopic-nSC?
  22. useSC = true;
  23. % Regress out global signal?
  24. useGlobalSignal = false;
  25. %% LOAD DATA
  26. % Collect file paths to normalized functional images:
  27. functPath = '';
  28. nscan = 19;
  29. timeSeries = cell(1,nscan); % 5 normals, 4 scans each (except normal #5, 3 scans)
  30. for scan = 1:nscan
  31. timeSeries{scan} = cell(1,175);
  32. for timepoint = 6:180 % skip first 5 volumes (T1 equilibration)
  33. if useSmoothedData
  34. timeSeries{scan}{timepoint-5} = fullfile(functPath,sprintf('funct_%02g',scan),...
  35. sprintf('s_w_funct3D_bird_%02g_time_%03g.nii',[scan, timepoint]));
  36. else
  37. timeSeries{scan}{timepoint-5} = fullfile(functPath,sprintf('funct_%02g',scan),...
  38. sprintf('w_funct3D_bird_%02g_time_%03g.nii',[scan, timepoint]));
  39. end
  40. end
  41. end
  42. % Load ROIs:
  43. ROI_path = '';
  44. SongSystem = fullfile(ROI_path,'SongSystemROIs.nii'); % n = 20
  45. mask = load_nii(fullfile(ROI_path,'new_temp_brain_mask.nii'));
  46. nROI = 20;
  47. % Get ROI Subscripts/Coordinates:
  48. rois = load_nii(SongSystem);
  49. roi_subscripts = cell(1,nROI);
  50. for i = 1:nROI
  51. [x, y, z] = ind2sub(size(rois.img),find(rois.img(:)==i));
  52. roi_subscripts{i} = [x,y,z];
  53. end
  54. % Get CSF Subscripts/Coordinates:
  55. CSF_mask = load_nii(fullfile(ROI_path, 'CSF_mask.nii'));
  56. [CSF_x,CSF_y,CSF_z] = ind2sub(size(CSF_mask.img), find(CSF_mask.img(:)>0));
  57. n_CSF = length(CSF_x);
  58. % Extract ROI Data & CSF Signals:
  59. ROI_dat = cell(1,nscan); CSF = cell(1,nscan); globalSignal = zeros(175,nscan); % Initialize
  60. hWait = waitbar(0,'Extracting functional data...');
  61. for scan = 1:nscan
  62. waitbar(scan/nscan, hWait);
  63. ROI_dat{scan} = zeros(175, nROI); CSF_dat = zeros(175, n_CSF); % Initialize
  64. for j = 1:175 % iterate through time (j)
  65. img = load_nii(timeSeries{scan}{j});
  66. globalSignal(j, scan) = mean(img.img(mask.img(:)>0)); % Average whole brain signal for time point j
  67. for k = 1:nROI % iterate through ROIs (k)
  68. nvox = size(roi_subscripts{k}, 1); % # voxels in ROI
  69. roi_holder = zeros(1, nvox);
  70. for l = 1:nvox % iterate through voxels of ROI
  71. roi_holder(l) = img.img(roi_subscripts{k}(l,1), roi_subscripts{k}(l,2), roi_subscripts{k}(l,3));
  72. end
  73. ROI_dat{scan}(j,k) = mean(roi_holder); % average voxels of ROI
  74. end
  75. for k = 1:n_CSF % iterate through CSF voxels (k)
  76. CSF_dat(j,k) = img.img(CSF_x(k), CSF_y(k), CSF_z(k));
  77. end
  78. end
  79. [~, PCs] = pca(CSF_dat); % pca across voxels of CSF mask
  80. CSF{scan} = PCs(:,1:5); % save PC's 1-5
  81. end; delete(hWait)
  82. % Load Motion Data:
  83. motionPath = '';
  84. motion_params = cell(1,nscan);
  85. for i = 1:nscan
  86. load(fullfile(motionPath, sprintf('motion_scan_%02g.mat',i)))
  87. motion_params{i} = motion;
  88. end
  89. % Load Physiology Data (Temperature (degrees Celsius), Isoflurane (%)):
  90. physio_path = '';
  91. physio_params = cell(1,nscan);
  92. physio_times = [60:60:540,576]; % 60 seconds to 576 seconds
  93. TR = 1.0671*3; scan_times = TR:TR:TR*175;
  94. for i = 1:nscan
  95. tbl = readtable(fullfile(physio_path,'All_Normal_Condition_Physiology_Records.xlsx'),'Sheet',i);
  96. pp_temp = spline(physio_times,tbl.Temperature); % piecewise polynomial of cubic spline
  97. pred_temp = ppval(pp_temp,scan_times); % evaluate PP at x
  98. pp_iso = spline(physio_times,tbl.Isoflurane); % piecewise polynomial of cubic spline
  99. pred_iso = ppval(pp_iso,scan_times); % evaluate PP at x
  100. physio_params{i} = [pred_temp',pred_iso'];
  101. end
  102. ages = [25, 45, 65, 89, 24, 45, 65, 90, 25, 46, 67, 89, 24, 46, 66, 90, 24, 45, 67];
  103. % Get ROI-level tSNR Data:
  104. tSNR_mat = zeros(nscan, nROI);
  105. for i = 1:nscan
  106. for j = 1:nROI
  107. mean_sig = mean(ROI_dat{i}(:,j));
  108. noise_SD = std(ROI_dat{i}(:,j));
  109. tSNR_mat(i,j) = mean_sig ./ noise_SD;
  110. end
  111. end
  112. % Get Mean Physiological Parameters:
  113. temps = zeros(19,1); iso = zeros(19,1);
  114. for i = 1:19
  115. temps(i) = mean(physio_params{i}(:,1));
  116. iso(i) = mean(physio_params{i}(:,2));
  117. end
  118. % Get Mean Volumes and Euclidean Distances:
  119. vox_dim = rois.hdr.dime.pixdim(2:4);
  120. vox_volume = prod(vox_dim);
  121. m_volumes = zeros(nROI); euclideans = zeros(nROI);
  122. % Get ROI Centroids:
  123. centroids = zeros(nROI,3);
  124. for i = 1:nROI
  125. [x,y,z] = ind2sub(size(rois.img),find(rois.img==i));
  126. centroids(i,:) = [mean(x),mean(y),mean(z)].*vox_dim;
  127. end
  128. for i = 1:nROI
  129. for j = 1:nROI
  130. m_volumes(i,j) = mean([sum(rois.img(:)==i)*vox_volume,sum(rois.img(:)==j)*vox_volume]);
  131. euclideans(i,j) = sqrt(sum((centroids(i,:)-centroids(j,:)).^2));
  132. end
  133. end
  134. %% RUN DENOISING
  135. % Labels for FC:
  136. labels = {'R Area X','L Area X','R LMAN','L LMAN','R RA','L RA',...
  137. 'R HVC','L HVC','R Aud. Forebrain','L Aud. Forebrain','R Field L',...
  138. 'L Field L','R Diencephalon','L Diencephalon','R Medulla','L Medulla',...
  139. 'R Uva','L Uva','R DM','L DM'};
  140. useScans = 1:19;
  141. if useSC
  142. nROI = 20;
  143. else
  144. nROI = 12;
  145. end
  146. % Preprocess ROI Data / Within-Subjects Nuisance Regression:
  147. Fs = .3124; TR = 1.0671*3; time = (TR:TR:TR*175)'; time_sq = time.^2; intercept = ones(175,1);
  148. hWait = waitbar(0,'Denoising...');
  149. for scan = useScans
  150. if ishandle(hWait); waitbar(scan/nscan, hWait); end
  151. % Get nuisance regression covariates:
  152. if all(physio_params{scan}(:,2)==mode(physio_params{scan}(:,2))) % iso is a constant, so don't need
  153. if useGlobalSignal
  154. nuisance = [intercept, time, time_sq, motion_params{scan}, CSF{scan}, physio_params{scan}(:,1), globalSignal(:,scan)];
  155. else
  156. nuisance = [intercept, time, time_sq, motion_params{scan}, CSF{scan}, physio_params{scan}(:,1)];
  157. end
  158. else
  159. if useGlobalSignal
  160. nuisance = [intercept, time, time_sq, motion_params{scan}, CSF{scan}, physio_params{scan}, globalSignal(:,scan)];
  161. else
  162. nuisance = [intercept, time, time_sq, motion_params{scan}, CSF{scan}, physio_params{scan}];
  163. end
  164. end
  165. % Perform nuisance regression, retain residuals:
  166. for j = 1:nROI
  167. [~, ~, ROI_dat{scan}(:,j)] = regress(ROI_dat{scan}(:,j), nuisance);
  168. end
  169. % Perform bandpass filtering:
  170. [ROI_dat{scan},~,~] = bst_bandpass_filtfilt(ROI_dat{scan}', Fs, .008, .1, 0, 'iir');
  171. ROI_dat{scan} = ROI_dat{scan}';
  172. end
  173. if ishandle(hWait); delete(hWait); end
  174. % Calculate Correlation Matrix:
  175. zmats = zeros(nROI, nROI, nscan);
  176. for scan = useScans
  177. zmats(:,:,scan) = fisherz(corr(ROI_dat{scan}(:,1:nROI)));
  178. end
  179. % Between-Subjects Nuisance Regression:
  180. nscan = length(useScans);
  181. numconn = nROI*(nROI-1)/2*nscan;
  182. birds = [1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5];
  183. scan = zeros(numconn,1); bird = zeros(numconn,1);
  184. FC = zeros(numconn,1); age = zeros(numconn,1);
  185. connection_type = zeros(numconn,1); euclidean = zeros(numconn,1);
  186. m_volume = zeros(numconn,1); mean_temp = zeros(numconn,1);
  187. mean_iso = zeros(numconn,1); conn_label = cell(numconn,1); count = 0;
  188. for i = 1:nROI-1
  189. for j = i+1:nROI
  190. FC(count+1:count+nscan) = squeeze(zmats(i,j,:));
  191. conn_label(count+1:count+nscan) = {[labels{i},'_',labels{j}]};
  192. age(count+1:count+nscan) = ages(useScans);
  193. bird(count+1:count+nscan) = birds(useScans);
  194. scan(count+1:count+nscan) = useScans;
  195. euclidean(count+1:count+nscan) = repmat(euclideans(i,j),nscan,1);
  196. m_volume(count+1:count+nscan) = repmat(m_volumes(i,j),nscan,1);
  197. if ((mod(i,2) && ~mod(j,2)) || (~mod(i,2) && mod(j,2))) && ~(mod(i,2) && j==(i+1)) % 4: heterotopic (contralateral)
  198. connection_type(count+1:count+nscan) = 4*ones(nscan,1);
  199. elseif mod(i,2) && j==(i+1)
  200. if i>12 || j>12
  201. connection_type(count+1:count+nscan) = 3*ones(nscan,1); % 3: homotopic-SC
  202. else
  203. connection_type(count+1:count+nscan) = 2*ones(nscan,1); % 2: homotopic-nSC
  204. end
  205. elseif (mod(i,2) && mod(j,2)) || (~mod(i,2) && ~mod(j,2)) % 1: ipsilateral
  206. connection_type(count+1:count+nscan) = ones(nscan,1);
  207. end
  208. mean_temp(count+1:count+nscan) = temps(useScans);
  209. mean_iso(count+1:count+nscan) = iso(useScans);
  210. count = count+nscan;
  211. end
  212. end
  213. bird = categorical(bird); scan = categorical(scan);
  214. if useSC
  215. connection_type = categorical(connection_type,1:4,{'Ipsilateral','Homotopic-nSC','Homotopic-SC','Heterotopic'});
  216. else
  217. connection_type = categorical(connection_type,1:4,{'Ipsilateral','Homotopic','Homotopic','Heterotopic'});
  218. end
  219. % Mixed Model:
  220. FC_data = table(FC, bird, scan, conn_label, connection_type, age, mean_temp, ...
  221. mean_iso, euclidean, m_volume, 'VariableNames', {'FC', 'Bird', 'Scan', ...
  222. 'Connection', 'Type', 'Age', 'Temp', 'Iso', 'Euclidean', 'Volume'});
  223. % Select Between-subjects nuisance parameters for formula:
  224. FC_formula = 'FC ~ Type*Age + Type*Iso + Temp + Euclidean + Volume + (1|Bird)';
  225. % Run LME random intercept model:
  226. lme = fitlme(FC_data, FC_formula, 'StartMethod','random', 'verbose',true, 'CheckHessian',true);
  227. lme.display
  228. % Get random effect coefficients:
  229. [B, Bnames, stats] = randomEffects(lme) %#ok
  230. % Check residuals:
  231. figure; lme.plotResiduals
  232. % Get marginal (fixed-effects only) vs. conditional (w/ random) R2
  233. lme.Rsquared
  234. resid_marginal = residuals(lme, 'Conditional',false); % fixed-effects only
  235. resid_conditional = residuals(lme, 'Conditional',true); % fixed-effects only
  236. R2_marginal = 1 - sum(resid_marginal.^2) / lme.SST
  237. R2_conditional = 1 - sum(resid_conditional.^2) / lme.SST