From 1d1cdf62090dc523dd3421b61d3e1d27e73d3701 Mon Sep 17 00:00:00 2001 From: Michael-Sun Date: Tue, 27 Feb 2024 12:01:27 -0500 Subject: [PATCH] Major updates to HRF_Est_Toolbox2 to allow passing in of SPM.mat files to estimate the HRF --- .../EstHRF_inAtlas/EstimateHRF_inAtlas.m | 700 +++++++++++------- .../EstHRF_inAtlas/describeHRF.m | 18 +- .../EstHRF_inAtlas/extractHRF.m | 123 +++ .../EstHRF_inAtlas/generateConditionTS.m | 8 +- .../EstHRF_inAtlas/generateHRFTable.m | 24 +- .../HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m | 164 ++++ .../HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m | 35 +- .../HRF_Est_Toolbox2/Fit_Canonical_HRF.m | 30 +- .../Fit_sFIR_epochmodulation.m | 269 +++++-- CanlabCore/HRF_Est_Toolbox2/get_parameters2.m | 2 +- .../HRF_Est_Toolbox2/hrf_fit_one_voxel.m | 57 +- 11 files changed, 1072 insertions(+), 358 deletions(-) create mode 100644 CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m create mode 100644 CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m index d6c52075..c9c92aa2 100644 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m @@ -1,4 +1,4 @@ -function [tc, HRF]=EstimateHRF_inAtlas(fmri_d, PREPROC_PARAMS, HRF_PARAMS, at, rois, outfile) +function [tc, HRF, HRF_OBJ, PARAM_OBJ]=EstimateHRF_inAtlas(fmri_d, PREPROC_PARAMS, HRF_PARAMS, at, rois, outfile, varargin) % EstimateHRF_inAtlas takes a raw 4D fmri_data object, preprocesses it, and % then outputs an estimated HRF time series for each condition of interest. % PREPROC struct needs to have TR, hpf, and Condition information @@ -6,7 +6,7 @@ % values will be supplied. % % Michael Sun, Ph.D. - % - Takes 4D fmri_data() object + % - Takes 4D fmri_data() object or cell-array of fmri_data() % - PREPROC_PARAMS: struct object that contains the fields: TR, R, hpf, and smooth % - HRF_PARAMS: struct object that contains the fields: Condition, CondNames, TR, T, FWHM, alpha, and types. % - at: atlas object @@ -22,17 +22,48 @@ % Step 0. Check if a directory exists that has estHRF .nii outputs % already. - - % Step 1. Preprocess - [preproc_dat]=canlab_connectivity_preproc(fmri_d, PREPROC_PARAMS.R, 'hpf', PREPROC_PARAMS.hpf, PREPROC_PARAMS.TR, 'average_over', 'no_plots'); - - % Step 2. Smooth - preproc_dat=preprocess(preproc_dat, 'smooth', PREPROC_PARAMS.smooth); + % Step 0. Load in an SPM.mat file if available to pass in metadata and + % such + SPM=[]; + if ~isempty(varargin) + if isstruct(varargin{1}) + SPM=varargin{1}; + else + % If pointing to an SPM filepath + load(varargin{1}) + end + % % Don't do that, spmify instead into gKWY (grand-mean-scaled, filtered (K), and whitened (W)) + % gKWY=spmify(fmri_d, SPM); + % + % for d=1:numel(gKWY) + % preproc_dat{d}=fmri_d{d}; + % preproc_dat{d}.dat=gKWY{d}; + % end + [tc, HRF, HRF_OBJ, PARAM_OBJ]=roiTS_fitHRF_SPM(SPM, HRF_PARAMS, rois, at, outfile); + + else + % Step 1. Preprocess + for d=1:numel(fmri_d) + [preproc_dat{d}]=canlab_connectivity_preproc(fmri_d, PREPROC_PARAMS.R{d}, 'hpf', PREPROC_PARAMS.hpf, PREPROC_PARAMS.TR, 'average_over', 'no_plots'); + % Step 2. Smooth + preproc_dat{d}=preprocess(preproc_dat{d}, 'smooth', PREPROC_PARAMS.smooth); + end - % Step 3. fitHRF to each ROI's worth of data + % Step 3. fitHRF to each ROI's worth of data + [tc, HRF]=roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile); + + + end + + + if numel(HRF)>1 + for i=1:numel(HRF) + HRF{i}.preproc_params=PREPROC_PARAMS; + end + else + HRF.preproc_params=PREPROC_PARAMS; + end - [tc, HRF]=roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile); - HRF.preproc_params=PREPROC_PARAMS; % Step 4. Save your Results try @@ -45,341 +76,476 @@ %% HELPER FUNCTIONS -function [HRF_OBJ, HRF]=gen_HRFimg(preproc_dat, HRF_PARAMS, rois, at, outfile) - - % This is finished but untested. Intention is to generate a directory from - % HRF images in BIDS format. +% function [HRF_OBJ, HRF]=gen_HRFimg(preproc_dat, HRF_PARAMS, rois, at, outfile) +% +% % This is finished but untested. Intention is to generate a directory from +% % HRF images in BIDS format. +% +% % Make directories for files if needed +% if ~isempty(fileparts(outfile)) +% if ~exist(fileparts(outfile), 'dir') +% mkdir(fileparts(outfile)); +% end +% end +% +% % Make sure CondNames are valid before continuing: +% HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); +% +% HRF.atlas=at; +% HRF.region=rois; +% HRF.types=HRF_PARAMS.types; +% HRF.name=preproc_dat.image_names; +% +% % Initialize the parallel pool if it's not already running +% if isempty(gcp('nocreate')) +% parpool; +% end +% +% % Write out the images for later post-analyses +% % [~, fname, ~]=fileparts(preproc_dat.image_names); +% % fname=outfile +% +% parfor t=1:numel(HRF_PARAMS.types) +% % for t=1:numel(HRF_PARAMS.types) % FOR TROUBLESHOOTING +% warning('off', 'all'); +% switch HRF_PARAMS.types{t} +% case 'IL' +% [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); +% +% case 'FIR' +% [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 1); +% +% case 'CHRF' +% [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 2); +% otherwise +% error('No valid fit-type. Choose IL, FIR, or CHRF') +% end +% +% for c=1:numel(HRF_PARAMS.Condition) +% +% HRF_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_fit.nii']); +% PARAM_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_params.nii']); +% try +% write(HRF_OBJ{c}, 'overwrite'); +% write(PARAM_OBJ{c}, 'overwrite'); +% catch +% warning('Not able to write one or more files.'); +% end +% +% end +% +% end +% end + + +% function [tc, HRF]=gen_HRFstruct_from_dir(preproc_dat, HRF_PARAMS, rois, at, outfile, estHRF_dir, HRF_OBJ) +% +% % This is unfinished. Intention is to take a generated directory from +% % gen_HRFimg and generate an HRF structure from it. +% +% % Initialize the parallel pool if it's not already running +% if isempty(gcp('nocreate')) +% parpool; +% end +% +% % Initialize 'tc' and 'temp_HRF_fit' cell arrays +% tc = cell(1, numel(HRF_PARAMS.types)); +% temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); +% +% parfor t=1:numel(HRF_PARAMS.types) +% +% HRF_local = cell(1, numel(rois)); +% tc_local = cell(1, numel(rois)); +% +% +% % Consider doing apply_parcellation instead of mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); +% % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at); +% % nps=load_image_set('npsplus'); +% % nps = get_wh_image(nps,1); +% % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at, 'pattern_expression', nps); +% % r=region(at,'unique_mask_values'); +% % wh_parcels=~all(isnan(parcel_means)) +% +% for r=1:numel(rois) +% tic +% for c=1:numel(HRF_PARAMS.CondNames) +% +% try +% tc_local{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); +% +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).model=tc_local{r}{c}; +% [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs]=detectPeaksTroughs(tc_local{r}{c}', false); +% +% [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).model_voxnormed=tc_local{r}{c}/regionVoxNum; +% [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks_voxnormed, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc_local{r}{c}'/regionVoxNum, false); +% catch +% disp(t); +% disp(c); +% disp(rois{r}); +% tc_local{r}{c} +% mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) +% {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} +% HRF_OBJ{c} +% +% end +% +% % Number of phases +% start_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.start_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.start_time]; +% end_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.end_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.end_time]; +% phases = [start_times(:), end_times(:)]; +% unique_phases = unique(phases, 'rows'); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); +% +% % Loop over phases +% for p = 1:numel(HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases) +% features = {'peaks', 'troughs'}; +% for f = 1:2 +% feat = features{f}; +% +% % Count the number of features (peaks or troughs) +% start_times = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).start_time}); +% current_phase_start = HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases{p}(1); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); +% +% if HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) > 0 +% display(['Estimating ' , num2str(t), '_', rois{r}, '_', HRF_PARAMS.CondNames{c}, '_', ' Now...!']); +% display(['Phase ' , num2str(p), 'Feature ', feat]); +% idx = find(start_times == current_phase_start); +% auc = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).AUC}); +% height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).height}); +% time_to_peak = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).time_to_peak}); +% half_height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).half_height}); +% +% feat_voxnormed = strcat(feat, '_voxnormed'); +% auc_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).AUC}); +% height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).height}); +% time_to_peak_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).time_to_peak}); +% half_height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).half_height}); +% +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc = unique(auc(idx)); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); +% +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height = height(idx); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak = time_to_peak(idx); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height = half_height(idx); +% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx); +% end +% end +% end +% end +% display(strjoin({num2str(t), ' Done in ', num2str(toc), ' seconds with ', rois{r}})); +% +% end +% % Save the results for this ROI +% display([num2str(t), ' Done!']) +% temp_HRF_fit{t} = HRF_local; +% tc{t} = tc_local; +% end +% +% % Transfer the results from the temporary cell array to the HRF structure +% HRF.fit = temp_HRF_fit; +% HRF.params=HRF_PARAMS; +% +% delete(gcp('nocreate')); +% +% +% end + + +function [tc, HRF]=roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile, varargin) % Make directories for files if needed if ~isempty(fileparts(outfile)) + disp(fileparts(outfile)); if ~exist(fileparts(outfile), 'dir') mkdir(fileparts(outfile)); end end % Make sure CondNames are valid before continuing: - HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); - HRF.atlas=at; HRF.region=rois; HRF.types=HRF_PARAMS.types; - HRF.name=preproc_dat.image_names; + + if iscell(preproc_dat) + for c=1:numel(preproc_dat) + HRF.name{c}=preproc_dat{c}.image_names; - % Initialize the parallel pool if it's not already running - if isempty(gcp('nocreate')) - parpool; + % Initialize 'tc' and 'temp_HRF_fit' cell arrays + tc{c} = cell(1, numel(HRF_PARAMS.types)); + temp_HRF_fit{c} = cell(1, numel(HRF_PARAMS.types)); + end + else + HRF.name=preproc_dat.image_names; + + % Initialize 'tc' and 'temp_HRF_fit' cell arrays + tc = cell(1, numel(HRF_PARAMS.types)); + temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); end - % Write out the images for later post-analyses - % [~, fname, ~]=fileparts(preproc_dat.image_names); - % fname=outfile + % Carve up SPM's design matrix for each image + % if ~isempty(varargin) + % if ischar(varargin{1}) || isstring(varargin{1}) + % load(varargin{1}); + % elseif isstruct(varargin{1}) + % SPM=varargin{1}; + % end - parfor t=1:numel(HRF_PARAMS.types) - % for t=1:numel(HRF_PARAMS.types) % FOR TROUBLESHOOTING - warning('off', 'all'); - switch HRF_PARAMS.types{t} - case 'IL' - [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); - - case 'FIR' - [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 1); - - case 'CHRF' - [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 2); - otherwise - error('No valid fit-type. Choose IL, FIR, or CHRF') - end - for c=1:numel(HRF_PARAMS.Condition) - - HRF_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_fit.nii']); - PARAM_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_params.nii']); - try - write(HRF_OBJ{c}, 'overwrite'); - write(PARAM_OBJ{c}, 'overwrite'); - catch - warning('Not able to write one or more files.'); - end + % DX=cell(1,numel(SPM.nscan)); + % if numel(preproc_dat) == numel(SPM.nscan) + % for d=1:numel(preproc_dat) + % % Check if the SPM file accounts for the same number of scans + % + % % HRF_PARAMS.CondNames + % % Use regexp to search for the pattern + % % hot_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_heat_start'])); + % % warm_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_warm_start'])); + % % imgcue_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_imagine_cue'])); + % % imag_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_imagine_start'])); + % + % R_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(d), '\).*R.*'])); + % constant_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(d), '\).*constant'])); + % sess_cols = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(d), '\).*'])); + % + % + % % Find indices of matches + % indices = [find(sess_cols)]; + % % indices = [find(hot_matches) find(warm_matches) find(imgcue_matches) find(imag_matches)]; + % % indices = [find(R_matches) find(constant_matches)]; + % task_regressors{d} = [find(sess_cols & (~R_matches & ~constant_matches))]; + % + % % Each design matrix needs task regressors, covariates, and + % % intercept + % DX{d}=SPM.xX.xKXs.X(SPM.Sess(d).row, indices); + % + % end + % customDX=1; + % end + % + % + % end - end - - end -end + HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); -function [tc, HRF]=gen_HRFstruct_from_dir(preproc_dat, HRF_PARAMS, rois, at, outfile, estHRF_dir, HRF_OBJ) - % This is unfinished. Intention is to take a generated directory from - % gen_HRFimg and generate an HRF structure from it. - % Initialize the parallel pool if it's not already running if isempty(gcp('nocreate')) parpool; end - % Initialize 'tc' and 'temp_HRF_fit' cell arrays - tc = cell(1, numel(HRF_PARAMS.types)); - temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); - parfor t=1:numel(HRF_PARAMS.types) + % Write out the images for later post-analyses + % [~, fname, ~]=fileparts(preproc_dat.image_names); + % fname=outfile - HRF_local = cell(1, numel(rois)); - tc_local = cell(1, numel(rois)); + HRF_OBJ=cell(1,numel(preproc_dat)); + PARAM_OBJ=cell(1,numel(preproc_dat)); + parfor d=1:numel(preproc_dat) + % for d=1:numel(preproc_dat) - % Consider doing apply_parcellation instead of mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at); - % nps=load_image_set('npsplus'); - % nps = get_wh_image(nps,1); - % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at, 'pattern_expression', nps); - % r=region(at,'unique_mask_values'); - % wh_parcels=~all(isnan(parcel_means)) - - for r=1:numel(rois) - tic - for c=1:numel(HRF_PARAMS.CondNames) + if iscell(preproc_dat) + data=preproc_dat{d}; + end - try - tc_local{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).model=tc_local{r}{c}; - [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs]=detectPeaksTroughs(tc_local{r}{c}', false); + for t=1:numel(HRF_PARAMS.types) + + + warning('off', 'all'); + switch HRF_PARAMS.types{t} + case 'IL' + [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition{d}, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); + case 'FIR' + [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition{d}, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); + case 'sFIR' + [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition{d}, HRF_PARAMS.T, HRF_PARAMS.types{t}, 1); + case 'CHRF0' + [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition{d}, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); + case 'CHRF1' + [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition{d}, HRF_PARAMS.T, HRF_PARAMS.types{t}, 1); + case 'CHRF2' + [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition{d}, HRF_PARAMS.T, HRF_PARAMS.types{t}, 2); + + otherwise + error('No valid fit-type. Choose IL, FIR/sFIR or CHRF0/CHRF1/CHRF2') + end - [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; - HRF_local{r}.(HRF_PARAMS.CondNames{c}).model_voxnormed=tc_local{r}{c}/regionVoxNum; - [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks_voxnormed, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc_local{r}{c}'/regionVoxNum, false); + + for c=1:numel(HRF_PARAMS.Condition) + + HRF_OBJ{d}{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_fit.nii']); + PARAM_OBJ{d}{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_params.nii']); + try + write(HRF_OBJ{d}{c}, 'overwrite'); + write(PARAM_OBJ{d}{c}, 'overwrite'); catch - disp(t); - disp(c); - disp(rois{r}); - tc_local{r}{c} - mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) - {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} - HRF_OBJ{c} - + warning('Not able to write one or more files.'); end - - % Number of phases - start_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.start_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.start_time]; - end_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.end_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.end_time]; - phases = [start_times(:), end_times(:)]; - unique_phases = unique(phases, 'rows'); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); - % Loop over phases - for p = 1:numel(HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases) - features = {'peaks', 'troughs'}; - for f = 1:2 - feat = features{f}; - - % Count the number of features (peaks or troughs) - start_times = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).start_time}); - current_phase_start = HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases{p}(1); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); - - if HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) > 0 - display(['Estimating ' , num2str(t), '_', rois{r}, '_', HRF_PARAMS.CondNames{c}, '_', ' Now...!']); - display(['Phase ' , num2str(p), 'Feature ', feat]); - idx = find(start_times == current_phase_start); - auc = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).AUC}); - height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).height}); - time_to_peak = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).time_to_peak}); - half_height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).half_height}); - - feat_voxnormed = strcat(feat, '_voxnormed'); - auc_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).AUC}); - height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).height}); - time_to_peak_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).time_to_peak}); - half_height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).half_height}); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc = unique(auc(idx)); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height = height(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak = time_to_peak(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height = half_height(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx); - end - end - end end - display(strjoin({num2str(t), ' Done in ', num2str(toc), ' seconds with ', rois{r}})); - + + % HRF_local{d} = cell(1, numel(rois)); + % tc_local{d} = cell(1, numel(rois)); + end - % Save the results for this ROI - display([num2str(t), ' Done!']) - temp_HRF_fit{t} = HRF_local; - tc{t} = tc_local; end - % Transfer the results from the temporary cell array to the HRF structure - HRF.fit = temp_HRF_fit; - HRF.params=HRF_PARAMS; + % Generate an HRF and tc for every datafile and concatenate them + % together. - delete(gcp('nocreate')); + for d=1:numel(HRF_OBJ) + for t=1:numel(HRF_OBJ{d}) + [HRF.fit{d,t}, tc{d,t}]=extractHRF(d{1}, [SPM.Sess(i).U.name], at, rois); + end + end + % This results in an HRF struct: HRF(d, t, r, c), tc(d,t,r,c) + + delete(gcp('nocreate')); end - -function [tc, HRF]=roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile, HRF) +function [tc, HRF, HRF_OBJ, PARAM_OBJ]=roiTS_fitHRF_SPM(SPM, HRF_PARAMS, rois, at, outfile) % Make directories for files if needed if ~isempty(fileparts(outfile)) + disp(fileparts(outfile)); if ~exist(fileparts(outfile), 'dir') mkdir(fileparts(outfile)); end end % Make sure CondNames are valid before continuing: - HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); - HRF.atlas=at; HRF.region=rois; HRF.types=HRF_PARAMS.types; - HRF.name=preproc_dat.image_names; + HRF.name=unique({SPM.xY.VY.fname}'); + + if numel(HRF.name)>1 + for c=1:numel(HRF.name) + % Initialize 'tc' and 'temp_HRF_fit' cell arrays + tc{c} = cell(1, numel(HRF_PARAMS.types)); + temp_HRF_fit{c} = cell(1, numel(HRF_PARAMS.types)); + end + else + % Initialize 'tc' and 'temp_HRF_fit' cell arrays + tc = cell(1, numel(HRF_PARAMS.types)); + temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); + end + + % Carve up SPM's design matrix for each image + % DX=cell(1,numel(SPM.nscan)); + + % HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); + + HRF_OBJ=cell(1,numel(HRF_PARAMS.types)); + PARAM_OBJ=cell(1,numel(HRF_PARAMS.types)); + info=cell(1,numel(HRF_PARAMS.types)); % Initialize the parallel pool if it's not already running if isempty(gcp('nocreate')) parpool; end - % Initialize 'tc' and 'temp_HRF_fit' cell arrays - tc = cell(1, numel(HRF_PARAMS.types)); - temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); - - % Write out the images for later post-analyses - % [~, fname, ~]=fileparts(preproc_dat.image_names); - % fname=outfile + CondNames=cell(1, numel(HRF_PARAMS.types)); parfor t=1:numel(HRF_PARAMS.types) - % for t=1:numel(HRF_PARAMS.types) % FOR TROUBLESHOOTING - warning('off', 'all'); - switch HRF_PARAMS.types{t} - case 'IL' - [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); - - case 'FIR' - [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 1); - - case 'CHRF' - [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 2); - otherwise - error('No valid fit-type. Choose IL, FIR, or CHRF') - end + % for t=1:numel(HRF_PARAMS.types) + + % First check to see if valid images have already been generated. Then + % we don't have to regenerate them. + for d = 1:numel(HRF.name) + + CondNames{t}{d} = strtrim([SPM.Sess(d).U.name]'); - for c=1:numel(HRF_PARAMS.Condition) - - HRF_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_fit.nii']); - PARAM_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_params.nii']); - try - write(HRF_OBJ{c}, 'overwrite'); - write(PARAM_OBJ{c}, 'overwrite'); - catch - warning('Not able to write one or more files.'); + for c=1:numel(CondNames{t}{d}) + HRF_OBJ_path=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_fit.nii']; + PARAM_OBJ_path=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_params.nii']; + if exist(HRF_OBJ_path, 'file') + HRF_OBJ{t}{d}{c}=fmri_data(HRF_OBJ_path); + PARAM_OBJ{t}{d}{c}=fmri_data(PARAM_OBJ_path); + files_exist=1; + disp('found file') + else + disp('did not find file') + files_exist=0; + end end + end + if ~files_exist + % Generate the files otherwise. + warning('off', 'all'); + if contains(HRF_PARAMS.types{t}, 'IL'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'IL', 0);, end + if contains(HRF_PARAMS.types{t}, 'FIR'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'FIR', 0);, end + if contains(HRF_PARAMS.types{t}, 'sFIR'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'FIR', 1);, end + if contains(HRF_PARAMS.types{t}, 'CHRF0'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'CHRF', 0);, end + if contains(HRF_PARAMS.types{t},'CHRF1'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'CHRF', 1);, end + if contains(HRF_PARAMS.types{t}, 'CHRF2'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'CHRF', 2);, end + + if ~ismember(HRF_PARAMS.types{t}, {'IL', 'FIR', 'sFIR','CHRF0','CHRF1','CHRF2'}) + error('Not a valid fit-type. Choose IL, FIR/sFIR or CHRF0/CHRF1/CHRF2') + end + + % The issue with this one is that now HRF_OBJ{t} and PARAM_OBJ{t} + % are cell arrays with cells for each data file d. + + for d = 1:numel(info{t}) + + CondNames{t}{d} = strtrim(info{t}{d}.names); + + for c=1:numel(CondNames{t}{d}) + + HRF_OBJ{t}{d}{c}.fullpath=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_fit.nii']; + PARAM_OBJ{t}{d}{c}.fullpath=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_params.nii']; + try + write(HRF_OBJ{t}{d}{c}, 'overwrite'); + write(PARAM_OBJ{t}{d}{c}, 'overwrite'); + catch + warning('Not able to write one or more files.'); + end + + end + + end end + end - HRF_local = cell(1, numel(rois)); - tc_local = cell(1, numel(rois)); + HRF.CondNames=CondNames{1}; + % This will return an HRF_OBJ that is sectioned by: + % HRF Type (t), Run(d), Region (r), Condition (c) - % Consider doing apply_parcellation instead of mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at); - % nps=load_image_set('npsplus'); - % nps = get_wh_image(nps,1); - % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at, 'pattern_expression', nps); - % r=region(at,'unique_mask_values'); - % wh_parcels=~all(isnan(parcel_means)) - - for r=1:numel(rois) - tic - for c=1:numel(HRF_PARAMS.CondNames) + % Rearrange HRF_OBJ{t, d, c} and PARAM_OBJ{t, d, c} - try - tc_local{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).model=tc_local{r}{c}; - [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs]=detectPeaksTroughs(tc_local{r}{c}', false); - [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; - HRF_local{r}.(HRF_PARAMS.CondNames{c}).model_voxnormed=tc_local{r}{c}/regionVoxNum; - [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks_voxnormed, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc_local{r}{c}'/regionVoxNum, false); - catch - disp(t); - disp(c); - disp(rois{r}); - tc_local{r}{c} - mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) - {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} - HRF_OBJ{c} - end + % Generate an HRF and tc for every HRF_OBJ datafile (type x session x Condition) and concatenate them + % together. - % Number of phases - start_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.start_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.start_time]; - end_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.end_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.end_time]; - phases = [start_times(:), end_times(:)]; - unique_phases = unique(phases, 'rows'); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); - - % Loop over phases - for p = 1:numel(HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases) - features = {'peaks', 'troughs'}; - for f = 1:2 - feat = features{f}; - - % Count the number of features (peaks or troughs) - start_times = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).start_time}); - current_phase_start = HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases{p}(1); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); - - if HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) > 0 - display(['Estimating ' , num2str(t), '_', rois{r}, '_', HRF_PARAMS.CondNames{c}, '_', ' Now...!']); - display(['Phase ' , num2str(p), 'Feature ', feat]); - idx = find(start_times == current_phase_start); - auc = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).AUC}); - height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).height}); - time_to_peak = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).time_to_peak}); - half_height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).half_height}); - - feat_voxnormed = strcat(feat, '_voxnormed'); - auc_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).AUC}); - height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).height}); - time_to_peak_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).time_to_peak}); - half_height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).half_height}); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc = unique(auc(idx)); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height = height(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak = time_to_peak(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height = half_height(idx); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx); - end - end - end - end - display(strjoin({num2str(t), ' Done in ', num2str(toc), ' seconds with ', rois{r}})); - + HRF_arr=cell(1,numel(HRF_OBJ)); + for d=1:numel(HRF.name) % Organize by Session, then type, then condition: + HRF_struct=HRF; + HRF_struct.name=HRF.name{d}; + HRF_struct.CondNames=HRF.CondNames{d}; + tic + for t=1:numel(HRF.types) + [HRF_struct.fit{t}, tc{d}{t}]=extractHRF(HRF_OBJ{t}{d}, [SPM.Sess(d).U.name], at, rois); end - % Save the results for this ROI - display([num2str(t), ' Done!']) - temp_HRF_fit{t} = HRF_local; - tc{t} = tc_local; + toc + + HRF_arr{d}=HRF_struct; end - % Transfer the results from the temporary cell array to the HRF structure - HRF.fit = temp_HRF_fit; - HRF.params=HRF_PARAMS; + HRF=HRF_arr; - delete(gcp('nocreate')); -end + % This results in an HRF struct: HRF(d, t, r, c), tc(d,t,r,c) +end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m index 63de299b..4c571588 100644 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m @@ -7,19 +7,19 @@ function describeHRF(HRF) % :: % describeHRF(HRF_structure) - for c = 1:numel(HRF.params.CondNames) + for c = 1:numel(HRF.CondNames) for t = 1:numel(HRF.types) for r = 1:numel(HRF.region) - disp(['The ', HRF.types{t}, ' fitted ', HRF.region{r}, ' features ', num2str(numel(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases)), ' phases of activity for the ', HRF.params.CondNames{c}, ' condition']); - for p = 1:numel(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases) - if HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).peaks>0 - disp(['Phase ' num2str(p), ', spanning TRs ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases{p}), ' features ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).peaks), ' peak(s), (at TR(s) ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).time_to_peak), ... - ' and features an AUC of ' num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).auc), ' (voxel-normed: ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).auc_voxnormed), ').']); + disp(['The ', HRF.types{t}, ' fitted ', HRF.region{r}, ' features ', num2str(numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases)), ' phases of activity for the ', HRF.CondNames{c}, ' condition']); + for p = 1:numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) + if HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).peaks>0 + disp(['Phase ' num2str(p), ', spanning TRs ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phases{p}), ' features ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).peaks), ' peak(s), (at TR(s) ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).time_to_peak), ... + ' and features an AUC of ' num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc), ' (voxel-normed: ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc_voxnormed), ').']); end - if HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).troughs>0 - disp(['Phase ' num2str(p), ', spanning TRs ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases{p}), ' features ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).troughs), ' trough(s), (at TR(s) ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).time_to_peak), ... - ' and features an AUC of ' num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).auc), ' (voxel-normed: ', num2str(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).auc_voxnormed), ').']); + if HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).troughs>0 + disp(['Phase ' num2str(p), ', spanning TRs ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phases{p}), ' features ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).troughs), ' trough(s), (at TR(s) ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).time_to_peak), ... + ' and features an AUC of ' num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc), ' (voxel-normed: ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc_voxnormed), ').']); end end diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m new file mode 100644 index 00000000..8fbd73e1 --- /dev/null +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m @@ -0,0 +1,123 @@ +function [HRF, tc] = extractHRF(HRF_OBJ, CondNames, at, rois) + % Passed in HRF_OBJ is a cell array of fmri_data for each condition. + % CondNames should be a cell array of charstr for each condition + + + % Initialize the parallel pool if it's not already running + if isempty(gcp('nocreate')) + parpool; + end + + % Preallocation of tc_local before the parfor loop + HRF= cell(1, numel(rois)); + tc= cell(1, numel(rois)); + + CondNames=matlab.lang.makeValidName(CondNames); + + % Now, handle the fourth dimension which varies with 'd' + numCondNames = numel(CondNames); + + % HRF_local{d} = cell(1, numel(rois)); + % tc_local{d} = cell(1, numel(rois)); + + % Consider doing apply_parcellation instead of mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); + % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at); + % nps=load_image_set('npsplus'); + % nps = get_wh_image(nps,1); + % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at, 'pattern_expression', nps); + % r=region(at,'unique_mask_values'); + % wh_parcels=~all(isnan(parcel_means)) + + for r=1:numel(rois) + HRF{r} = struct; + tc{r} = cell(1, numCondNames); + tic + for c=1:numel(CondNames) + + try + tc{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); + + HRF{r}.(CondNames{c}).model=tc{r}{c}; + [HRF{r}.(CondNames{c}).peaks, HRF{r}.(CondNames{c}).troughs]=detectPeaksTroughs(tc{r}{c}', false); + + [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; + HRF{r}.(CondNames{c}).model_voxnormed=tc{r}{c}/regionVoxNum; + [HRF{r}.(CondNames{c}).peaks_voxnormed, HRF{r}.(CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc{r}{c}'/regionVoxNum, false); + catch + % disp(t); + disp(c); + disp(rois{r}); + tc{r}{c} + mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) + {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} + HRF_OBJ + + end + + % Number of phases + start_times = [HRF{r}.(CondNames{c}).peaks.start_time, HRF{r}.(CondNames{c}).troughs.start_time]; + end_times = [HRF{r}.(CondNames{c}).peaks.end_time, HRF{r}.(CondNames{c}).troughs.end_time]; + phases = [start_times(:), end_times(:)]; + unique_phases = unique(phases, 'rows'); + HRF{r}.(CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); + + % Loop over phases + for p = 1:numel(HRF{r}.(CondNames{c}).phases) + features = {'peaks', 'troughs'}; + for f = 1:2 + feat = features{f}; + + % Count the number of features (peaks or troughs) + start_times = cell2mat({HRF{r}.(CondNames{c}).(feat).start_time}); + current_phase_start = HRF{r}.(CondNames{c}).phases{p}(1); + HRF{r}.(CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); + + if HRF{r}.(CondNames{c}).phase(p).(feat) > 0 + display(['Estimating ' , '_', rois{r}, '_', CondNames{c}, '_', ' Now...!']); + display(['Phase ' , num2str(p), 'Feature ', feat]); + idx = find(start_times == current_phase_start); + auc = cell2mat({HRF{r}.(CondNames{c}).(feat).AUC}); + height = cell2mat({HRF{r}.(CondNames{c}).(feat).height}); + time_to_peak = cell2mat({HRF{r}.(CondNames{c}).(feat).time_to_peak}); + half_height = cell2mat({HRF{r}.(CondNames{c}).(feat).half_height}); + + feat_voxnormed = strcat(feat, '_voxnormed'); + auc_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).AUC}); + height_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).height}); + time_to_peak_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).time_to_peak}); + half_height_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).half_height}); + + HRF{r}.(CondNames{c}).phase(p).auc = unique(auc(idx)); + HRF{r}.(CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); + + HRF{r}.(CondNames{c}).phase(p).height = height(idx); + HRF{r}.(CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx); + HRF{r}.(CondNames{c}).phase(p).time_to_peak = time_to_peak(idx); + HRF{r}.(CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); + HRF{r}.(CondNames{c}).phase(p).half_height = half_height(idx); + HRF{r}.(CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx); + end + end + end + end + display(strjoin({' Done in ', num2str(toc), ' seconds with ', rois{r}})); + + end + + + % temp_HRF_fit = HRF_local; + % Save the results for this ROI + % display([num2str(t), ' Done!']) + % temp_HRF_fit{t} = HRF_local; + % tc{t} = tc; + + + % Transfer the results from the temporary cell array to the HRF structure + % HRF.fit = temp_HRF_fit; + % HRF.params=HRF_PARAMS; + + delete(gcp('nocreate')); + + + +end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m index 309b3f80..16ddc00e 100644 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m @@ -1,4 +1,4 @@ -function Condition=generateConditionTS(fmri_d, conditions, onsets, durations) +function Condition=generateConditionTS(fmri_d, conditions, onsets, durations, varargin) % Helper script to create condition vectors for hrf_fit_one_voxel() % Michael Sun, Ph.D. % - Takes fmri_data() object or the number of TRs in a 4D object. @@ -22,6 +22,12 @@ SPIKES=1; SPIKETRAINS=0; end + + if ~isempty(varargin) + SPIKETRAINS=1; + else + SPIKES=1; + end if strcmp(class(fmri_d), 'fmri_data') n=size(fmri_d.dat,2); diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m index 14933770..2821271a 100644 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m @@ -11,17 +11,17 @@ tblData = cell(0, 10); % You may need to adjust the number of columns based on the data you're storing % Iterate through the nested loops as before - for c = 1:numel(HRF.params.CondNames) + for c = 1:numel(HRF.CondNames) for r = 1:numel(HRF.region) for t = 1:numel(HRF.types) - %disp(['Number of phases: ', num2str(numel(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases))]); - %for p = 1:numel(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases) + %disp(['Number of phases: ', num2str(numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases))]); + %for p = 1:numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) % Define the basic information type = HRF.types{t}; region = HRF.region{r}; - condition = HRF.params.CondNames{c}; + condition = HRF.CondNames{c}; phase_num = 0; phase_span = [0 0]; peaks = 0; @@ -31,20 +31,20 @@ auc_voxnormed = 0; - if numel(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases) > 1 - for p = 1:numel(HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases) + if numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) > 1 + for p = 1:numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) phase_num = p; - phase_span = HRF.fit{t}{r}.(HRF.params.CondNames{c}).phases{p}; + phase_span = HRF.fit{t}{r}.(HRF.CondNames{c}).phases{p}; % Check for peaks and troughs - peaks = HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).peaks; - troughs = HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).troughs; + peaks = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).peaks; + troughs = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).troughs; %disp(['Peaks: ', num2str(peaks), ', Troughs: ', num2str(troughs)]); % Extract other data - time_to_peak = HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).time_to_peak; - auc = HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).auc; - auc_voxnormed = HRF.fit{t}{r}.(HRF.params.CondNames{c}).phase(p).auc_voxnormed; + time_to_peak = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).time_to_peak; + auc = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc; + auc_voxnormed = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc_voxnormed; % Append the data to the cell array tblData = [tblData; {type, region, condition, phase_num, phase_span, peaks, troughs, time_to_peak, auc, auc_voxnormed}]; end diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m new file mode 100644 index 00000000..ee56e57c --- /dev/null +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m @@ -0,0 +1,164 @@ +function [params_obj, hrf_obj, params_obj_dat, hrf_obj_dat, info] = hrf_fit(SPM,T,method,mode) +% HRF estimation on fmri_data class object +% +% HRF estimation function for a single voxel; +% +% Implemented methods include: IL-model (Deterministic/Stochastic), FIR +% (Regular/Smooth), and HRF (Canonical/+ temporal/+ temporal & dispersion). +% With SPM.mat, TR, and experimental design are imported. +% +% :Inputs: +% +% **SPM** +% SPM.mat file +% +% **T** +% length of estimated HRF ij seconds +% +% **type** +% Model type: 'FIR', 'IL', or 'CHRF' +% +% **mode** +% Mode +% +% :Model Types: +% +% A. **Fit HRF using IL-function** +% Choose mode (deterministic/stochastic) +% - 0 - deterministic aproach +% - 1 - simulated annealing approach +% +% Please note that when using simulated annealing approach you +% may need to perform some tuning before use. +% +% B. **Fit HRF using FIR-model** +% Choose mode (FIR/sFIR) +% - 0 - FIR +% - 1 - smooth FIR +% +% C. **Fit Canonical HRF** +% Choose mode (FIR/sFIR) +% - 0 - FIR +% - 1 - smooth FIR +% +% +% .. +% Created by Michael Sun on 02/20/24 +% .. + +if isstring(SPM) || ischar(SPM) + + load(SPM); + if ~exist('SPM', 'var') + error('Passed in filepath is not an SPM.mat file.') + end + +end + + +if isstruct(SPM) + fnames=unique({SPM.xY.VY.fname})'; + + parfor i=1:numel(fnames) + + if contains(fnames{i}, '/') && ispc + % PC Path conversion from Unix + if strcmp(fnames{i}(1,1:2), '\\') + d{i}=fmri_data(strrep(fnames{i}, '/', '\')); + else + d{i}=fmri_data(['\',strrep(fnames{i}, '/', '\')]); + end + + else + d{i}=fmri_data(fnames{i}); + end + end + + % Transform timeseries data into the way SPM desires. + gkwy_d=spmify(d,SPM); + + % Extract TR + TR = SPM.xY.RT; + + if isempty(T) + disp('T is empty, generating T as 2 times the maximum duration for each task regressor.'); + for i = 1:numel(SPM.Sess) + T{i}=cellfun(@(cellArray) 2*ceil(max(cellArray)), {SPM.Sess(i).U.dur}); + end + elseif numel(SPM.Sess)>1 && ~iscell(T) + % error('SPM structures concatenating multiple runs must have time windows passed in with a matching number of cell arrays.'); + T=repmat({T}, 1, numel(SPM.Sess)); + elseif iscell(T) && numel(SPM.Sess)~=numel(T) + error('Incompatible number of runs in SPM and in cell-array T.') + end + + % %% ONE WAY + % % run hrf_fit separately for every d + for i=1:numel(d) + % Reassign data. + d{i}.dat=gkwy_d{i}; + + % Extract TR + TR = SPM.xY.RT; + Runc=generateConditionTS(numel(SPM.Sess(i).row), [SPM.Sess(i).U.name], {SPM.Sess(i).U.ons}, {SPM.Sess(i).U.dur}); + + % There's a need to pull apart the SPM design matrix for every run + % Column of regressors + covariates for each session + intercept + % X=SPM.xX.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i)]); + + % Trouble is, X is not filtered + % X=spm_filter(SPM.xX.K(i), SPM.xX.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i)])); + + % Trouble here is, task regressors may have to be re-generated for + % FIR and sFIR if the original design matrix is cHRF, so check the + % basis function + if strcmpi(method, 'FIR') && ~contains(SPM.xBF.name, 'FIR') + disp('Passed in SPM structure is not FIR. Reconstructing task-regressors for FIR fit'); + + len = numel(SPM.Sess(i).row); + + % Task regressors for each run can be found here: + numstim=numel(SPM.Sess(i).U); + + % Make the design matrix: + DX_all = cell(1, numstim); % Store DX matrices for each condition + tlen_all = zeros(1, numstim); % Store tlen for each condition + + for s=1:numstim + t = 1:TR:T{i}(s); + tlen_all(s) = length(t); + DX_all{s} = tor_make_deconv_mtx3(Runc(:,s), tlen_all(s), 1); + end + DX = horzcat(DX_all{:}); + + % due to horzcat, we will have multiple intercepts in this design matrix + % therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix + intercept_idx = find(sum(DX)==len); + copyDX = DX; + copyDX(:,intercept_idx) = []; + DX = [copyDX ones(len,1)]; + + % Covariate Design Matrix for each session (without intercept): + NX=[SPM.Sess(i).C.C]; + + % Concatenate the Task regressors with Covariates + X=[DX, NX]; + % Filter + X=spm_filter(SPM.xX.K(i), X); + + else + % Otherwise set X to be the filtered design matrix of that + % section. + X=spm_filter(SPM.xX.K(i), SPM.xX.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i)])); + end + + [params_obj{i}, hrf_obj{i}, params_obj_dat{i}, hrf_obj_dat{i}] = hrf_fit(d{i},TR,Runc,T{i},method,mode,X); + + info{i}.X=X; + info{i}.names=[SPM.Sess(i).U.name]'; + + end + +end + +end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m index 88a13a58..696d2e34 100644 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m +++ b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m @@ -2,7 +2,10 @@ % Generates a plot from an HRF Structure given a specified fit type and a region name. % Michael Sun, Ph.D. % - Takes HRF Structure object generated from EstimateHRF_inAtlas() - % - t is a cellstring for fit type e.g., 'FIR', 'IL', or 'CHRF' + % - d is for selecting which data to plot, default is to aggregate them + % all. Putting a vector plots each file separately in order e.g., [1 2 3] + % - t is a cellstring for fit type e.g., 'FIR', 'sFIR', 'IL', 'CHRF0', 'CHRF1', 'CHRF2' + % - c is a cellstring for condition names or stems to plot e.g., {'*hot*, *warm*'} % - r is a cellstring for region label from atlas.labels. e.g., 'ACC', or a cell-array of regions to compare relative to each other e.g., {'ACC', 'DLPFC'} % % *Usage: @@ -78,7 +81,7 @@ % disp(typ) % Get the list of conditions from HRF_PARAMS - conds = HRF.params.CondNames; + conds = HRF.CondNames; % % Initialize a cell array to store the model matrices % array3D = cell(1, length(conds)); @@ -246,8 +249,8 @@ function plotRegionalHrfSummaries(HRF, regs) % plot time-to-peak, height, width, start_time, and end_time of every % peaks_voxnormed and trough_voxnormed for a region - create_figure(['Regional HRF summaries for ', HRF.params.CondNames{c}]); - for c = 1:numel(HRF.params.CondNames) + create_figure(['Regional HRF summaries for ', HRF.CondNames{c}]); + for c = 1:numel(HRF.CondNames) handles = []; @@ -315,17 +318,17 @@ function plotRegionalHrfSummaries(HRF, regs) minw=[]; maxw=[]; - for p=1:numel(HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed) + for p=1:numel(HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed) % meant=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t); - meant=[meant, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).time_to_peak]; + meant=[meant, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).time_to_peak]; % standard error time-to-peak % stet=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)); % mean height % meanh=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h); - meanh=[meanh, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).height]; + meanh=[meanh, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).height]; % height standard error % steh=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)); @@ -335,22 +338,22 @@ function plotRegionalHrfSummaries(HRF, regs) % maximum width % maxw=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))-meant; - minw=[minw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).start_time]; - maxw=[maxw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).end_time]; + minw=[minw, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).start_time]; + maxw=[maxw, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).end_time]; end - for t=1:numel(HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed) + for t=1:numel(HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed) % meant=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t); - meant=[meant, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).time_to_peak]; + meant=[meant, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).time_to_peak]; % standard error time-to-peak % stet=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)); % mean height % meanh=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h); - meanh=[meanh, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).height]; + meanh=[meanh, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).height]; % height standard error % steh=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)); @@ -360,15 +363,15 @@ function plotRegionalHrfSummaries(HRF, regs) % maximum width % maxw=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))-meant; - minw=[minw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).start_time]; - maxw=[maxw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).end_time]; + minw=[minw, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).start_time]; + maxw=[maxw, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).end_time]; end % handles(end+1)=errorbar(meant, meanh, steh, steh, stet, stet, 'o', 'Color', maxDifferentColors(r,:)); [~,vox,~,~]=get_region_volumes(at); vox=vox(r); - se=barplot_get_within_ste(HRF.fit{1}{r}.(HRF.params.CondNames{c}).models); + se=barplot_get_within_ste(HRF.fit{1}{r}.(HRF.CondNames{c}).models); se=se/vox; se=repmat(se, 1, numel(meant)); @@ -413,7 +416,7 @@ function plotRegionalHrfSummaries(HRF, regs) xticklabels([0:5:45]*0.46) xlabel('Time to Peak (seconds)'); % title({[signames{i}, ' Condition: ', conds{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) - title({['Condition: ', HRF.params.CondNames{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) + title({['Condition: ', HRF.CondNames{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) end end diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m b/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m index abec42bb..be1d0549 100755 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m +++ b/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m @@ -1,4 +1,4 @@ -function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc, TR, Run, T, p) +function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc, TR, Run, T, p, varargin) % function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc,TR,Runs,T,p) % % Fits GLM using canonical hrf (with option of using time and dispersion derivatives)'; @@ -26,11 +26,17 @@ % Created by Martin Lindquist on 10/02/09 % Last edited: 05/17/13 (ML) +% Edited to allow passing in custom design matrix e.g., from SPM. Will +% assume that the first regressor columns of the design matrix pertain to +% the regressors in Run: Michael Sun, Ph.D. 02/20/2024 + + %tc = tc'; d = length(Run); len = length(Run{1}); t=1:TR:T; +% Constructing the Design Matrix X: X = zeros(len,p*d); param = zeros(3,d); @@ -40,17 +46,20 @@ v = conv(Run{i},h); X(:,(i-1)*p+1) = v(1:len); + % Computing the first derivative if (p>1) v = conv(Run{i},dh); X(:,(i-1)*p+2) = v(1:len); end + % Computing the second derivative if (p>2) v = conv(Run{i},dh2); X(:,(i-1)*p+3) = v(1:len); end end - + +% This line adds an intercept X = [(zeros(len,1)+1) X]; b = pinv(X)*tc; @@ -58,6 +67,23 @@ fit = X*b; b = reshape(b(2:end),p,d)'; + + +% Import your own design matrix +if ~isempty(varargin) + X=varargin{1}; + + b = pinv(X)*tc; + e = tc-X*b; + fit = X*b; + + % Be careful here. if p>1, make sure Run includes derivatives so there + % are p*task regressors. + b = reshape(b(1:numel(Run)),p,d)'; % Extract my own regressors + +end + + bc = zeros(d,1); for i=1:d, diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m b/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m index 3342de91..d091ae5a 100755 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m +++ b/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m @@ -1,4 +1,4 @@ -function [hrf, fit, e, param] = Fit_sFIR_epochmodulation(tc, TR, Run, T, mode) +function [hrf, fit, e, param, DXinfo] = Fit_sFIR_epochmodulation(tc, TR, Run, T, mode, varargin) % % Fits FIR and smooth FIR model % @@ -58,66 +58,243 @@ % - made adjustments accordingly to the estimated hrf variable: instead of being a vector, hrf is now a matrix with varying lengths of hrf estimation, due to varying epoch length inputted via T. % - lastly, varying lengths of hrf is concatenated into one matrix (double: longest length hrf estimation x number of conditions) -numstim = length(Run); -len = length(Run{1}); +% [2/19/2024 - MS v2.0]: Updated the code to output design-matrix information, and allow input of multiple tc, +% pass in a custom design matrix, and to pass in an SPM structure -Runs = zeros(len,numstim); -for i=1:numstim - Runs(:,i) = Run{i}; -end +% Debugging Hack, import your own Design Matrix from SPM - MS +if ~isempty(varargin) + if isstruct(varargin{1}) + % 1. Decide what are regressors and what are covariates here + % This is very difficult since I will have to pass in g, K, and W + % all from SPM.mat + + % So ultimately we will want to concatenate the FIR task regressors + % and the unfiltered covariates, and then filter the whole thing. + + SPM=varargin{1}; -DX_all = cell(1, numstim); % Store DX matrices for each condition -tlen_all = zeros(1, numstim); % Store tlen for each condition + if numel(SPM.Sess)>1 && ~iscell(tc) % If SPM has concatenated runs but there's only one time series + error('SPM structures concatenating multiple runs must have timecourses passed in with a matching number of cell arrays'); + elseif iscell(tc) && numel(SPM.Sess)~=numel(tc) + error('Incompatible number of runs in SPM and in cell-array tc') + end + if isempty(T) + for i = 1:numel(SPM.Sess) + T{i}=cellfun(@(cellArray) 2*ceil(max(cellArray)), {SPM.Sess(i).U.dur}); + end + elseif numel(SPM.Sess)>1 && ~iscell(T) + error('SPM structures concatenating multiple runs must have time windows passed in with a matching number of cell arrays.'); + elseif iscell(T) && numel(SPM.Sess)~=numel(T) + error('Incompatible number of runs in SPM and in cell-array T.') + end -for i=1:numstim - t = 1:TR:T(i); - tlen_all(i) = length(t); - DX_all{i} = tor_make_deconv_mtx3(Runs(:,i), tlen_all(i), 1); -end -DX = horzcat(DX_all{:}); + for s = 1:numel(SPM.Sess) + len = numel(SPM.Sess(s).row); -% due to horzcat, we will have multiple intercepts in this design matrix -% therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix -intercept_idx = find(sum(DX)==len); -copyDX = DX; -copyDX(:,intercept_idx) = []; -DX = [copyDX ones(len,1)]; + % Task regressors for each run can be found here: + numstim=numel(SPM.Sess(s).U); + TR = SPM.xY.RT; -if mode == 1 - - MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end - start_idx = 1; + % Make the design matrix: + DX_all = cell(1, numstim); % Store DX matrices for each condition + tlen_all = zeros(1, numstim); % Store tlen for each condition + Runs{s}=generateConditionTS(numel(SPM.Sess(s).row), [SPM.Sess(s).U.name], {SPM.Sess(s).U.ons}, {SPM.Sess(s).U.dur}); + + for i=1:numstim + t = 1:TR:T{s}(i); + tlen_all(i) = length(t); + DX_all{i} = tor_make_deconv_mtx3(Runs{s}(:,i), tlen_all(i), 1); + end + DX = horzcat(DX_all{:}); + + % due to horzcat, we will have multiple intercepts in this design matrix + % therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix + intercept_idx = find(sum(DX)==len); + copyDX = DX; + copyDX(:,intercept_idx) = []; + DX = [copyDX ones(len,1)]; + + % Covariate Design Matrix for each session (without intercept): + NX=[SPM.Sess(s).C.C]; - for i=1:numstim - tlen = tlen_all(i); % get the tlen for this stimulus + % Concatenate the Task regressors with Covariates + X=[DX, NX]; + % Filter + DX_cov{s}=spm_filter(SPM.xX.K(s), X); + end - C = (1:tlen)'*(ones(1,tlen)); - h = sqrt(1/(7/TR)); + if numel(DX_cov) > 1 + for i=1:numel(DX_cov) + [hrf{i}, fit{i}, e{i}, param{i}, DXinfo{i}]=Fit_sFIR_epochmodulation(tc{i}, TR, Runs{i}, T{i}, mode, DX_cov{i}); + end + return; + end - v = 0.1; - sig = 1; + else + % If not an SPM struct, allow a design matrix to be passed in. + DX_cov=varargin{1}; - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); + numstim = length(Run); + tlen_all = zeros(1, numstim); % Store tlen for each condition + for i=1:numstim + t = 1:TR:T(i); + tlen_all(i) = length(t); + end + end + - % Adjust the indices to account for varying tlen - end_idx = start_idx + tlen - 1; - MRI(start_idx:end_idx, start_idx:end_idx) = RI; + if mode == 1 + % pen = {toeplitz([1 .3 .1])} + % n_nuis = 20; % num of nuisance + % pen{2} = zeros(20); % for nuisance, add no penalty + % blkdiag(pen{:}) - start_idx = end_idx + 1; % update the starting index for next iteration - end + MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end + start_idx = 1; + + for i=1:numstim + tlen = tlen_all(i); % get the tlen for this stimulus + + C = (1:tlen)'*(ones(1,tlen)); + h = sqrt(1/(7/TR)); + + v = 0.1; + sig = 1; - b = inv(DX'*DX+sig^2*MRI)*DX'*tc; - fit = DX*b; - e = tc - DX*b; + R = v*exp(-h/2*(C-C').^2); + RI = inv(R); + + % Adjust the indices to account for varying tlen + end_idx = start_idx + tlen - 1; + MRI(start_idx:end_idx, start_idx:end_idx) = RI; + + start_idx = end_idx + 1; % update the starting index for next iteration + end + + % multiply a 0 penalty with NX. + cov_num = size(DX_cov,2)-size(MRI,2); + % pen = sig^2*MRI; % Regularization Penalty Matrix + % pen=pad(pad(pen, cov_num)',cov_num)'; + % pen{2} = zeros(20); % for nuisance, add no penalty + pen{1} = sig^2*MRI; % Regularization Penalty Matrix + pen{2} = zeros(cov_num); % for nuisance, add no penalty + pen=blkdiag(pen{:}); -elseif mode == 0 + % disp(pen) % Check what this looks like. + % X = [DX, NX]; % Full Design Matrix - b = pinv(DX)*tc; - fit = DX*b; - e = tc - DX*b; + b = inv(DX_cov'*DX_cov+pen)*DX_cov'*tc; + % b = DX'*tc; + fit = DX_cov*b; + e = tc - DX_cov*b; + DXinfo.DX=DX_cov'; + + elseif mode == 0 + b = pinv(DX_cov)*tc; + fit = DX_cov*b; + e = tc - DX_cov*b; + DXinfo.DX=DX_cov; + + end + +else + + % If nothing is passed in varargin: + + + numstim = length(Run); + len = length(Run{1}); + Runs = zeros(len,numstim); + for i=1:numstim + Runs(:,i) = Run{i}; + end + + DX_all = cell(1, numstim); % Store DX matrices for each condition + tlen_all = zeros(1, numstim); % Store tlen for each condition + + + for i=1:numstim + t = 1:TR:T(i); + tlen_all(i) = length(t); + DX_all{i} = tor_make_deconv_mtx3(Runs(:,i), tlen_all(i), 1); + end + DX = horzcat(DX_all{:}); + + % due to horzcat, we will have multiple intercepts in this design matrix + % therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix + intercept_idx = find(sum(DX)==len); + copyDX = DX; + copyDX(:,intercept_idx) = []; + DX = [copyDX ones(len,1)]; + + if mode == 1 + + MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end + start_idx = 1; + + for i=1:numstim + tlen = tlen_all(i); % get the tlen for this stimulus + + C = (1:tlen)'*(ones(1,tlen)); + h = sqrt(1/(7/TR)); + + v = 0.1; + sig = 1; + + R = v*exp(-h/2*(C-C').^2); + RI = inv(R); + + % Adjust the indices to account for varying tlen + end_idx = start_idx + tlen - 1; + MRI(start_idx:end_idx, start_idx:end_idx) = RI; + + start_idx = end_idx + 1; % update the starting index for next iteration + end + + % DX + % + % NX = ;% Nuisance covariates and intercepts + + % X = [DX, NX]; % Full Design Matrix + pen = sig^2*MRI; % Regularization Penalty Matrix + % disp(pen) % Check what this looks like. + cov_num = size(DX,2)-size(pen, 2); + + pen=pad(pad(pen, cov_num)',cov_num)'; + + % multiply a 0 penalty with NX. + + % gKW the DX Design Matrix + + + % b = inv(DX'*DX+sig^2*MRI)*DX'*tc; + b = inv(DX'*DX+pen)*DX'*tc; + fit = DX*b; + e = tc - DX*b; + + % Code added by Michael Sun, PhD 02/05/2024 + DXinfo.DX=inv(DX'*DX+sig^2*MRI)*DX'; % This DX is essentially regression coefficients + DXinfo.DX=DXinfo.DX'; + + % DXinfo.DX=DX; + DXinfo.sig=sig; + DXinfo.MRI=MRI; + + elseif mode == 0 + + b = pinv(DX)*tc; + fit = DX*b; + e = tc - DX*b; + + % Coded added by Michael Sun, Ph.D. + DXinfo.DX=DX; + + end + + + end numstim = length(tlen_all); @@ -136,6 +313,8 @@ end_idx = start_idx + tlen_all(i) - 1; hrf{:,i} = b(start_idx:end_idx)'; param(:,i) = get_parameters2(hrf{:,i}, (1:tlen_all(i))); + + DXinfo.tlen{i}=[start_idx:end_idx]; end % HJ: concatenate estimated hrf diff --git a/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m b/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m index 5e85894e..0ab794dd 100755 --- a/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m +++ b/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m @@ -17,7 +17,7 @@ h = hdrf(p); %if (p > t(end)*0.6*delta), warning('Late time to peak'), end; -if (p > t(end)*0.8), warning('Late time to peak'), end; +% if (p > t(end)*0.8), warning('Late time to peak'), end; if (h >0) v = (hdrf >= h/2); diff --git a/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m b/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m index 72a32939..329e18c7 100644 --- a/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m +++ b/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m @@ -1,4 +1,4 @@ -function [h, fit, e, param] = hrf_fit_one_voxel(tc,TR,Runc,T,method,mode) +function [h, fit, e, param] = hrf_fit_one_voxel(tc,TR,Runc,T,method,mode, varargin) % function [h, fit, e, param] = hrf_fit_one_voxel(tc,TR,Runc,T,type,mode) % % HRF estimation function for a single voxel; @@ -27,6 +27,29 @@ % param - estimated amplitude, height and width % % Created by Martin Lindquist on 04/11/14 +% +% Added a varargin to accept a design matrix to modulate the model fit. +% - Michael Sun, Ph.D. 02/09/2024 + + +if ~isempty(varargin) + % Load in SPM structure if passed in + % timecourse tc must now be gKWY to match SPM output. + % Filtered Design Matrix SPM.xX.xKXs.X must be passed in instead of + % allowing Fit_* to generate its own design matrix. + + % if ischar(varargin{1}) || isstring(varargin{1}) + % load(varargin{1}) + % elseif isstruct(varargin{1}) + % + % SPM=varargin{1}; + % end + % + % SPM.xX.xKXs.X + + SPM_DX=varargin{1}; + +end if (strcmp(method,'IL')), @@ -41,7 +64,13 @@ % Please note that when using simulated annealing approach you % may need to perform some tuning before use. - [h, fit, e, param] = Fit_Logit2(tc,TR,Runc,T,mode); + if exists('SPM') + disp('IL-function with custom Design Matrix not yet implemented') + [h, fit, e, param] = Fit_Logit2(tc,TR,Runc,T,mode); + else + [h, fit, e, param] = Fit_Logit2(tc,TR,Runc,T,mode); + end + param(2:3,:) = param(2:3,:).*TR; @@ -56,7 +85,17 @@ % 1 - smooth FIR % [h, fit, e, param] = Fit_sFIR(tc,TR,Runc,T,mode); - [h, fit, e, param] = Fit_sFIR_epochmodulation(tc,TR,Runc,T,mode); + + + if exist('SPM_DX', 'var') + [h, fit, e, param] = Fit_sFIR_epochmodulation(tc,TR,Runc,T,mode, SPM_DX); + + else + [h, fit, e, param] = Fit_sFIR_epochmodulation(tc,TR,Runc,T,mode); + end + + + param(2:3,:) = param(2:3,:).*TR; @@ -69,8 +108,16 @@ % 1 - HRF + temporal derivative % 2 - HRF + temporal and dispersion derivative - p = mode + 1; - [h, fit, e, param] = Fit_Canonical_HRF(tc,TR,Runc,T,p); + p = mode + 1; + + + if exist('SPM_DX', 'var') + [h, fit, e, param] = Fit_Canonical_HRF(tc,TR,Runc,T,p,SPM_DX); + else + [h, fit, e, param] = Fit_Canonical_HRF(tc,TR,Runc,T,p); + end + + param(2:3,:) = param(2:3,:).*TR; else