diff --git a/@canlab_dataset/add_var.m b/@canlab_dataset/add_var.m new file mode 100644 index 00000000..1876c743 --- /dev/null +++ b/@canlab_dataset/add_var.m @@ -0,0 +1,57 @@ +% Not complete yet. Please edit me +% +% Function for adding a variable to a dataset in a systematic way. +% - Checks IDs of subjects to make sure data is added in the correct order. +% - Values for missing data are coded as missing values, as specified in dat.Description.Missing_Values +% - Handles Subject_Level or Event_Level data + +varname = 'ValenceType'; +var_to_add = SETUP.data.X; + +% get indices +[subjn, ia, ib] = intersect(dat.Subj_Level.id, subjname2, 'stable'); + +wh_subjs = false(length(dat.Subj_Level.id)); +wh_subjs(ia) = true; + +n = length(dat.Subj_Level.id); + + +% set missing value +missingval = dat.Description.Missing_Values; +if ischar(missingval), missingval = str2num(missingval); + + +nmissing = length(dat.Subj_Level.id) - length(ia); +fprintf('Adding variable %s:\n\tSubjects with missing data: %d\n', varname, nmissing); + +nextra = length(subjname2) - length(ia); +fprintf('\tSubjects not in dataset (will not be added): %d\n', nextra); + +is_subj_level = iscell(var_to_add); + +% Subject level +% simplest case +% -------------------------------------------------------------------- +if is_subj_level + + newvar = repmat(missingval, n, 1); + + newvar(ia) = var_to_add(ib); + + return + +end + +% Event level +% -------------------------------------------------------------------- + +for i = 1:n + + + +end + + +str2num('NaN') + diff --git a/@canlab_dataset/bars.m b/@canlab_dataset/bars.m new file mode 100644 index 00000000..48fa4f20 --- /dev/null +++ b/@canlab_dataset/bars.m @@ -0,0 +1,164 @@ +function [dat, descrip, colors, h1, s1] = bars(obj, varnames, varargin) +% +% +% Takes any optional inputs to barplot_colored.m +% +% Bar plot for canlab_dataset object +% +% Usage: +% ------------------------------------------------------------------------- +% [dat, descrip, colors, h1, s1] = bars(obj, varnames, varargin) +% Takes any optional inputs to barplot_colored.m +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% obj canlab_dataset object +% varnames Cell string of variable names to plot +% +% Takes any optional inputs to barplot_colored.m +% +% Outputs: +% ------------------------------------------------------------------------- +% dat data matrix for each variable +% +% Examples: +% ------------------------------------------------------------------------- +% +% create_figure('NPS values - All subjects'); +% +% varnames = {'15' '13' '11' ' 9' '16' '14' '12' '10'}; +% xvals = [1 2 4 5 8 9 11 12]; +% colors = {[1 0 0] [0 1 0] [1 0 0] [0 1 0] [1 0 0] [0 1 0] [1 0 0] [0 1 0]}; +% bars(LevoNPS, varnames, 'x', xvals, 'colors', colors, 'XTickLabels', varnames, 'within', 'nofig'); +% +% +% See also: +% * list other functions related to this one, and alternatives* + +% Programmers' notes: +% List dates and changes here, and author of changes + +n = length(varnames); +colors = scn_standard_colors(n); + + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% optional inputs with default values +% ----------------------------------- +% - allowable_args is a cell array of argument names +% - avoid spaces, special characters, and names of existing functions +% - variables will be assigned based on these names +% i.e., if you use an arg named 'cl', a variable called cl will be +% created in the workspace + +allowable_args = {'colors' 'nofig'}; + +default_values = {colors [0]}; + +% define actions for each input +% ----------------------------------- +% - cell array with one cell for each allowable argument +% - these have special meanings in the code below +% - allowable actions for inputs in the code below are: 'assign_next_input' or 'flag_on' + +actions = {'assign_next_input' 'flag_on'}; + +% logical vector and indices of which inputs are text +textargs = cellfun(@ischar, varargin); +whtextargs = find(textargs); + +for i = 1:length(allowable_args) + + % assign default + % ------------------------------------------------------------------------- + + eval([allowable_args{i} ' = default_values{i};']); + + wh = strcmp(allowable_args{i}, varargin(textargs)); + + if any(wh) + % Optional argument has been entered + % ------------------------------------------------------------------------- + + wh = whtextargs(wh); + if length(wh) > 1, warning(['input ' allowable_args{i} ' is duplicated.']); end + + switch actions{i} + case 'assign_next_input' + eval([allowable_args{i} ' = varargin{wh(1) + 1};']); + varargin{wh(1) + 1} = []; + + case 'flag_on' + eval([allowable_args{i} ' = 1;']); + + otherwise + error(['Coding bug: Illegal action for argument ' allowable_args{i}]) + end + + end % argument is input +end + +% END DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% REST OF BAR PLOT + + +[dat, ~, ~, descrip] = get_var(obj, varnames, varargin{:}); + +descrip = cat(1, descrip{:}); + + +colors = cat(1, colors{:}); + +xvals = 1:n; + +if nofig +else + +create_figure('bars'); +end + +[h1, s1] = barplot_colored(dat, varargin{:}); +% set(h2, 'BarWidth', .9) +colormap(colors) + +fprintf('T-tests against zero\n'); +fprintf('Description\tt(%3.0f)\tp\n', size(dat, 1) - 1); + +% Print table - t-test against zero +for i = 1:n + [h, p, ci, stats] = ttest(dat(:, i)); + if p < .001, str = '***'; + elseif p < .01, str = '**'; + elseif p < .05, str = '*'; + elseif p < .10, str = '+'; + else str = ' '; + end + + fprintf('%s\t%3.2f\t%3.6f\t%s\n', descrip{i}, stats.tstat, p, str); +end + + +end + diff --git a/@canlab_dataset/canlab_dataset.m b/@canlab_dataset/canlab_dataset.m new file mode 100644 index 00000000..618f1347 --- /dev/null +++ b/@canlab_dataset/canlab_dataset.m @@ -0,0 +1,72 @@ +% D = canlab_dataset(); +% +% Constructs a new, empty instance of a canlab_dataset object. +% The fields of this object can subsequently be assigned data. +% +% The dataset (D) The dataset (D) is a way of collecting behavioral data +% and/or meta-data about fMRI (and other) studies in a standardized format. +% It has entries for experiment description, Subject-level +% variables, Event-level variables (e.g., for fMRI events or trials in a +% behavioral experiment) and other fields for sub-event level data (e.g., +% physio or continuous ratings within trials). +% +% See D.Description for some basic info. +% +% Dataset methods include: +% +% write_text -> writes a flat text file (data across all subjects) +% concatenate -> returns flat matrix of data across all subjects to the workspace +% get_var -> get event-level data from one variable and return in matrix and cell formats +% scatterplot -> standard or multi-line scatterplot of relationship between two variables +% scattermatrix -> Scatterplot matrix of pairwise event-level variables +% +% - type methods(canlab_dataset) for a list of all methods +% +% Copyright Tor Wager, 2013 + +classdef canlab_dataset + + properties + + % Build a dataset containing the key variables + + + Description = struct('Experiment_Name', [], 'Missing_Values', [], 'Subj_Level', [], 'Event_Level', []); + + Subj_Level = struct('id', cell(1), 'names', cell(1), 'type', cell(1), 'units', cell(1), 'descrip', cell(1), 'data', []); + + Event_Level = struct('names', cell(1), 'type', cell(1), 'units', cell(1), 'descrip', cell(1), 'data', cell(1)); + + Sub_Event_Level = struct('names', cell(1), 'type', cell(1), 'units', cell(1), 'descrip', cell(1), 'data', cell(1)); + + Continuous = struct('names', cell(1), 'type', cell(1), 'units', cell(1), 'descrip', cell(1), 'data', cell(1)); + + wh_keep = struct(); + + end % properties + + %% Subject-level data + + methods + + % Class constructor + function obj = canlab_dataset(varargin) + + obj.Description.Subj_Level{1, 1} = 'id: Unique text identifier for each subject (e.g., subject code)'; + + obj.Description.Subj_Level{2, 1} = 'data: cell array, one cell per subject, with rectangular data matrix.'; + obj.Description.Subj_Level{3, 1} = ' Each row is one event, each column a variable, with names in .names'; + + obj.Description.Event_Level{1, 1} = 'data: cell array, one cell per subject, with rectangular data matrix.'; + obj.Description.Event_Level{2, 1} = ' Each row is one event, each column a variable, with names in .names'; + + + end % constructor function + + end % methods + + +end % classdef + + + diff --git a/@canlab_dataset/concatenate.m b/@canlab_dataset/concatenate.m new file mode 100644 index 00000000..ffdaec17 --- /dev/null +++ b/@canlab_dataset/concatenate.m @@ -0,0 +1,63 @@ +function [names, ids, dat, descrips] = concatenate(D, varargin) +% +% Concatenates Subject-level and Event-level data across all subjects +% +% [names ids dat] = concatenate(D) +% +% INPUT: +% D: canlab_dataset +% varargin: currently accepts a wh_keep (logical array) +% +% OUTPUT: +% Names: cell array of variable names +% +% Descrip: cell array of variable descriptions +% +% ids: subject IDs matching data rows in dat +% +% dat: subjects*events x variables matrix +% +% - subject number, event number are included +% - all subject-level and event-level data are included +% - this format appropriate for, e.g., SAS/HLM +% +% Examples: +% [names, ids, flatdat] = concatenate(D); +% id_numbers = flatdat(:, 1); +% +% wh_subjs = true(size(D.Subj_Level.id)); +% wh_subjs([13 18 19]) = false; +% [names, ids, dat] = concatenate(D, wh_subjs); +% +% % Copyright Tor Wager, 2013 + +% select subjects +wh_ids = true(length(D.Subj_Level.id), 1); +if length(varargin) > 0, wh_ids = logical(varargin{1}); end +whfind = find(wh_ids); + +names = {'subj_num' D.Subj_Level.names{:} 'Event_number' D.Event_Level.names{:}}; +descrips = {'subj_num' D.Subj_Level.descrip{:} 'Event_number' D.Event_Level.descrip{:}}; + + +n = length(D.Subj_Level.id(wh_ids)); + +x = D.Event_Level.data(wh_ids); + + +for i = 1:n + + e = size(D.Event_Level.data{whfind(i)}, 1); + + ids{i} = repmat(D.Subj_Level.id{whfind(i)}, e, 1); + + % subj ID subj level data trialID event data + x{i} = [repmat(i, e, 1) repmat(D.Subj_Level.data(whfind(i), :), e, 1) (1:e)' x{i}]; + +end + +ids = strvcat(ids{:}); + +dat = cat(1, x{:}); + +end % function \ No newline at end of file diff --git a/@canlab_dataset/get_var.m b/@canlab_dataset/get_var.m new file mode 100644 index 00000000..fd5fa4f5 --- /dev/null +++ b/@canlab_dataset/get_var.m @@ -0,0 +1,193 @@ +function [dat, datcell, wh_level, descrip] = get_var(D, varname, varargin) +% +% get Subject-level or Event-level variable from dataset D and return in rect matrix and cell array. +% +% Usage: +% ---------------------------------------------------------------------------------- +% [dat, datcell, wh_level, descrip] = get_var(D, varname, [opt inputs]) +% +% Inputs: +% ---------------------------------------------------------------------------------- +% D a canlab_dataset object +% varname the name of a variable to get from dataset +% - Looks for var name at either level, returns Event level if exists at both levels +% - can be a cell array of multiple var names +% in this case, dat is a n x m matrix, where n=subjs +% and m=variables requested +% [Optional inputs:] +% +% a vector of 1/0 values to use as wh_keep +% +% 'conditional': to be followed by a cell array; the first cell is the name +% of the variable to be conditionally selected upon, the second cell +% contains the condition which must be met. Example: get_var(D, 'DeltaDon', 'conditional', {'trained' 1}) +% will get DeltaDon whenever trained==1. Currently only implemented for +% event-level data. Could be expanded to include multiple conditions. +% +% Outputs: +% ---------------------------------------------------------------------------------- +% dat: rect matrix of subjects x events +% - good for plotting individuals, means/std. errors across subjects +% +% datcell: 1 x subjects cell array, each cell containing event data for one subject +% - good for input into some stats functions, e.g., glmfit_multilevel +% and igls.m +% +% wh_level: either 'Subject' or 'Event', depending on which level the +% variable exists at. +% +% descrip: the description for this variable +% +% +% % Copyright Tor Wager, 2013 + +dat = []; +datcell = {}; + +if iscell(varname) + wh_level = get_varlevel(D, varname{1}); +else + wh_level = get_varlevel(D, varname); +end + +%varargin: wh_keep +wh_keep = true(size(D.Subj_Level.id)); +do_conditional = 0; + +for i=1:length(varargin) + if islogical(varargin{i}) + wh_keep = varargin{i}; + end + + if ischar(varargin{i}) + switch varargin{i} + case 'conditional' + do_conditional=1; + + switch wh_level + case 1 + conditionalCol = strmatch(varargin{i+1}{1}, D.Subj_Level.names, 'exact'); + case 2 + + conditionalCol = strmatch(varargin{i+1}{1}, D.Event_Level.names, 'exact'); + end + + if isempty(conditionalCol), error('Conditional variable does not exist'); end + conditionalVal = varargin{i+1}{2}; + end + end +end + + +switch wh_level + case 1 + + % Subject-level + if iscell(varname) + for i=1:length(varname) + wh = strmatch(varname{i}, D.Subj_Level.names, 'exact'); + dat(:,i) = D.Subj_Level.data(:, wh); + + if wh > length(D.Subj_Level.descrip) + descrip{i} = varname; %'No description.'; + else + descrip{i} = D.Subj_Level.descrip(wh); + end + end + else + wh = strmatch(varname, D.Subj_Level.names, 'exact'); + dat = D.Subj_Level.data(:, wh); + if wh > length(D.Subj_Level.descrip) + descrip = varname; %'No description.'; + else + descrip = D.Subj_Level.descrip{wh}; + end + end + + if do_conditional + whrows = D.Subj_Level.data(:, conditionalCol) == conditionalVal; + dat = dat(whrows, :); + wh_keep = wh_keep(whrows); + end + + case 2 + + if iscell(varname) + wh=[]; + for k=1:length(varname) + wh(end+1)= find(strcmp(varname{k}, D.Event_Level.names)); + end + else + wh = find(strcmp(varname, D.Event_Level.names)); + end + + % Event-level + if do_conditional + d=conditionalData(D.Event_Level.data, conditionalCol, conditionalVal, wh); + + + else + my_col = @(x) x(:, wh); + d = cellfun(my_col, D.Event_Level.data, 'UniformOutput', 0); + + end + + datcell = d; + + dat = cat(2, d{:}); % d is Events x Subjects + + dat = dat'; % Subj x Events + + if wh > length(D.Event_Level.descrip) + descrip = 'No description.'; + else + descrip = D.Event_Level.descrip{wh}; + end + +end + +% if isempty(dat) +% disp(['WARNING: ' varname ' is not a valid variable name because it does not exist in this dataset.']); +% end + +%wh_keep +if length(wh_keep) ~= size(dat, 1) && length(wh_keep) ~= size(datcell, 2) + error('wh_keep is the wrong length. Check input.'); +end + +dat = dat(wh_keep,:); +if ~isempty(datcell), datcell = datcell(wh_keep); end + + +end % function + + + + +function varlevel = get_varlevel(D, varname) + +[varlevel, varlevel2] = deal(0); + +varlevel = any(strcmp(D.Subj_Level.names, varname)); + +varlevel2 = 2*any(strcmp(D.Event_Level.names, varname)); + +if any(varlevel & varlevel2) + error(['Variable ' varname ' exists at both Subject and Event levels. This is not allowed.']) +end + +varlevel = varlevel + varlevel2; + +if any(~varlevel) + error(['Variable ' varname ' does not exist in this dataset. Check var input names.']) +end + +end + +function arr2 = conditionalData(arr, conditionalCol, conditionalVal, outCol) +arr2 = cell(size(arr)); +for i=1:length(arr) + arr2{i} = arr{i}(arr{i}(:,conditionalCol)==conditionalVal, outCol); +end +end + diff --git a/@canlab_dataset/glm.m b/@canlab_dataset/glm.m new file mode 100644 index 00000000..7a5542c0 --- /dev/null +++ b/@canlab_dataset/glm.m @@ -0,0 +1,58 @@ +function out = glm(D, Yvarname, Xvarnames, wh_keep) +% +% predict Y from X using GLM +% +% Usage: +% ---------------------------------------------------------------------------------- +% out = glm(D, Yvarname, Xvarnames, wh_keep) +% +% Examples: +% ---------------------------------------------------------------------------------- +% out = glm(D, 'DeltaDon_avg', prednames, wh_keep) +% +% Inputs: +% ---------------------------------------------------------------------------------- +% D a canlab_dataset object +% Yvarname the name of a variable to predict. must be subject level +% Xvarnames the name(s) of predictor variables. if multiple, put in +% cell array. must be subject_level +% wh_keep a vector of 1/0 values to use as wh_keep +% +% Outputs: +% ---------------------------------------------------------------------------------- +% same as for glmfit() +% +% % Copyright Tor Wager, 2013 + +if nargin < 4 || isempty(wh_keep) + wh_keep = true(size(D.Subj_Level.id)); %everyone +end + + +[y, ally, levelY] = get_var(D, Yvarname, wh_keep); +[X, allX, levelX] = get_var(D, Xvarnames, wh_keep); + +% Print var name(s) +fprintf('Y (outcome): %s\n', Yvarname); +fprintf('X (predictors): ') +fprintf('%s\t', Xvarnames{:}); +fprintf('\n') + +if levelY == 2 && levelX == 2 + % Multi-level + out = igls_multicond(ally, allX, 'iter', 10); + +elseif levelY ~= 1 || levelX ~= 1, error('Vars must be subject level'); + +else + [b, dev, stat] = glmfit(X, y); + glm_table(stat, Xvarnames); + + out.b = b; + out.dev = dev; + out.stat = stat; + +end + + +end diff --git a/@canlab_dataset/glm_multilevel.m b/@canlab_dataset/glm_multilevel.m new file mode 100644 index 00000000..422ab614 --- /dev/null +++ b/@canlab_dataset/glm_multilevel.m @@ -0,0 +1,64 @@ +function [b, dev, stat] = glm_multilevel(D, Yvarname, Xvarnames, wh_keep) +% +% predict Y from X using GLM +% +% Usage: +% ---------------------------------------------------------------------------------- +% [b, dev, stat] = glm(D, 'DeltaDon_avg', prednames, wh_keep) +% +% Inputs: +% ---------------------------------------------------------------------------------- +% D a canlab_dataset object +% Yvarname the name of a variable to predict. must be event level +% Xvarnames the name(s) of predictor variables. if multiple, put in +% cell array. must be event level +% wh_keep a vector of 1/0 values to use as wh_keep +% +% Outputs: +% ---------------------------------------------------------------------------------- +% +% +% % Copyright Tor Wager, 2013 + +[Y, ~, levelY] = get_var(D, Yvarname, wh_keep); +Y = Y'; + +%% MUST IMPLEMENT GET_VAR FOR MULTIPLE VARS AT AN EVENT LEVEL. RETURNS A CELL ARRAY FOR EACH PERSON. DAT BECOMES WARNING STRING, DATCELL IS OF INTEREST. + +[X, ~, levelX] = get_var(D, Xvarnames, wh_keep); + +if levelY ~= 2 || levelX ~= 2, error('Vars must be event level'); end + + + +n=size(Y,2); +X1 = cell(1,n); +X2 = mean(Y)'; % matrix of 2nd level preds + +if isstruct(varargin{1}) % the "alternative format" described above + xstruct = varargin{1}; + fields = varargin{2}; + for i = 1:n % each subject + clear myX + for j = 1:length(fields) %all the fields + myX(:, j) = xstruct.(fields{j}){i}; + X1{i} = myX; + end + getvif(X1{i}) + end + +else + for i = 1:n % each subject + clear myX + for j = 1:length(varargin) + myX(:, j) = varargin{j}{i}; + X1{i} = myX; + end + end +end + + +stats = glmfit_multilevel(Y, X1, scale(X2, 1), 'weighted', 'noplots', ... + 'names', {'Intrcpt' names{2:end}}, 'beta_names', {'Avg within-ss relationship' ['Effect of Avg. ' names{1}]}); + +end \ No newline at end of file diff --git a/@canlab_dataset/histogram.m b/@canlab_dataset/histogram.m new file mode 100644 index 00000000..e5c255a9 --- /dev/null +++ b/@canlab_dataset/histogram.m @@ -0,0 +1,66 @@ +function fig_han = scatterplot(D, v1, varargin) +% +% fig_han = scatterplot(D, varname1, varargin) +% +% Histogram of one variable in dataset +% - can be either event-level or subject-level +% - event-level data is plotted as concatenated events across subject-level +% - both variables must be valid names (case-sensitive) +% +% Optional inputs: +% - 'nofig': suppress creation of new figure +% +% Example: +% +% histogram(D, 'Anxiety'); +% +% Copyright Tor Wager, 2013 + +fig_han = []; +dofig = 1; + +if any(strcmp(varargin, 'nofig')) + dofig = 0; +end + +[dat1, dcell1, whlevel1] = get_var(D, v1, varargin{:}); + + +if isempty(dat1) + % skip + disp('No plot: Missing variables'); + return +end + +if dofig + fig_han = create_figure([v1 ' Histogram']); +else + fig_han = gcf; +end + +% Set number of bins +nbins = max(10, length(dat1(:)) ./ 100); +nbins = min(nbins, 200); +nbins = min(nbins, length(unique(dat1(:)))); + +switch whlevel1 + case 1 + hist(dat1(:), nbins); + + case 2 + + hist(dat1(:), nbins); + + otherwise + error('Illegal level variable returned by get_var(D)'); +end + + grid off +han = gca; +set(gca, 'FontSize', 24) + +xlabel(v1); + + +end % function + diff --git a/@canlab_dataset/mediation.m b/@canlab_dataset/mediation.m new file mode 100644 index 00000000..c7485647 --- /dev/null +++ b/@canlab_dataset/mediation.m @@ -0,0 +1,200 @@ +function [paths, stats] = mediation(D, xvarname, yvarname, mvarname, varargin) +% Run single or multilevel mediation analysis on a canlab_dataset object +% +% Usage: +% ---------------------------------------------------------------------------------- +% [paths, stats] = mediation(D, xvarname, yvarname, mvarname, varargin) +% +% Inputs: +% ---------------------------------------------------------------------------------- +% D is a canlab_dataset object +% xvarname, mvarname, and yvarname are valid variable names in the dataset. +% +% [Optional inputs:] +% +% Takes any optional inputs to mediation.m +% e.g., 'noverbose', 'dosave', 'names', 'M', 'L2M', 'covs', others. +% - see help mediation +% +% 'wh_keep' : followed by 1/0 vector of subjects to keep. +% - must be same length as subjects +% - subjects with value 0 will be excluded +% +% 'rankdata' : ranks all data before mediation; "Nonparametric" +% +% Outputs: +% ---------------------------------------------------------------------------------- +% [paths, stats]: mediation output variable +% - see help mediation for details +% +% Examples: +% ---------------------------------------------------------------------------------- +% [paths, stats] = mediation(D, 'Group', 'DeltaDon', 'DeltaDist', 'M2', 'DeltaTend', 'wh_keep', wh_keep); +% +% % Copyright Tor Wager, 2013 + +covstr = 'nocovs'; +covvarnames = {}; +c = []; + +m2str = 'noM2'; +m2 = []; +m2names = {}; +m2varsdescrip = {}; + +nboot = 10000; +singlelevel = 0; % force single-level; update later... + +%wh_keep = true(size(D.Subj_Level.data(:, 1))); + +% +% % Get levels of main variables +% % ---------------------------------------------- +% varlevel = [0 0 0]; +% varlevel(1) = get_varlevel(D, xvarname); +% varlevel(2) = get_varlevel(D, mvarname); +% varlevel(3) = get_varlevel(D, yvarname); + +[x, xcell, varlevel(1), xvardescrip] = get_var(D, xvarname); +[m, mcell, varlevel(2), mvardescrip] = get_var(D, mvarname); +[y, ycell, varlevel(3), yvardescrip] = get_var(D, yvarname); + + + + + +% Select level and data +% ------------------------------------------------- +if any(varlevel == 1) + % enforce single-level + singlelevel = 1; + x = nanmean(x, 2); + m = nanmean(m, 2); + y = nanmean(y, 2); +else + % Multi-level + x = xcell; + m = mcell; + y = ycell; +end + +wh = strcmp(varargin, 'wh_keep'); +if any(wh) + wh_keep = varargin{find(wh)+1}; + + if length(wh_keep) ~= length(x) || ~islogical(wh_keep); + error('wh_keep must be logical vector of which subjects to keep. wrong size or datatype.'); + end + + x = x(wh_keep); + y = y(wh_keep); + m = m(wh_keep); + +end + +% Get covariates and 2nd mediators +% ---------------------------------------------- + +wh = find(strcmp(varargin, 'covs')); +if any(wh) + covstr = 'covs'; + for i = 1:length(wh) + % WILL HANDLE ONLY ONE COVARIATE NOW... + covvarnames{i} = varargin{wh+1}; + [c, ccell, cvarlevel(i)] = get_var(D, covvarnames{i}); + + if singlelevel + c = nanmean(c, 2); % <- works for both Subject/Event level var + else + c = ccell; + end + + if exist('wh_keep', 'var') + c = c(wh_keep); + end + + end +end + +wh = find(strcmp(varargin, 'M2')); +if any(wh) + m2str = 'M2'; + for i = 1:length(wh) + m2names{i} = varargin{wh+1}; + [m2, m2cell, m2varlevel(i), m2varsdescrip] = get_var(D, m2names{i}); + + if singlelevel + m2 = nanmean(m2, 2); % <- works for both Subject/Event level var + else + m2 = m2cell; + end + + if exist('wh_keep', 'var') + m2 = m2(wh_keep); + end + end +end + +% Rank data, if requested +% ---------------------------------------------- +if any(strcmp(varargin, 'rankdata')) + + if singlelevel + + x = rankdata(x); + y = rankdata(y); + m = rankdata(m); + c = rankdata(c); + m2 = rankdata(m2); + + else + cfun = @(x) rankdata(x); + x = cellfun(cfun, x, 'UniformOutput', 0); + y = cellfun(cfun, y, 'UniformOutput', 0); + m = cellfun(cfun, m, 'UniformOutput', 0); + + if ~isempty(c) + c = cellfun(cfun, c, 'UniformOutput', 0); + end + + if ~isempty(m2) + m2 = cellfun(cfun, m2, 'UniformOutput', 0); + end + + + end + +end + + + +% Names +% ---------------------------------------------- +names = [{xvardescrip} {yvardescrip} {mvardescrip} {m2varsdescrip{:}}]; + +[paths, stats] = mediation(x, y, m, 'boot', 'plots', 'verbose', 'bootsamples', nboot, 'names', names, 'covs', c, 'M', m2, varargin{:}); + + +end % function + + +% +% function varlevel = get_varlevel(D, varname) +% +% [varlevel, varlevel2] = deal(0); +% +% varlevel = any(strcmp(D.Subj_Level.names, varname)); +% +% varlevel2 = 2*any(strcmp(D.Event_Level.names, varname)); +% +% if any(varlevel & varlevel2) +% error('Some variables are in both Subject and Event levels.'); +% end +% +% varlevel = varlevel + varlevel2; +% +% if any(~varlevel) +% error(['Variable ' varname ' does not exist in this dataset. Check var input names.']) +% end +% +% end \ No newline at end of file diff --git a/@canlab_dataset/plot_var.m b/@canlab_dataset/plot_var.m new file mode 100644 index 00000000..8a9cc3d8 --- /dev/null +++ b/@canlab_dataset/plot_var.m @@ -0,0 +1,188 @@ +function [meandat, stedat] = plot_var(D, varname, varargin) +% Plot the mean and standard error of a variable across events. +% +% plot_var(D, varname) +% +% D is a canlab_dataset object +% varname is a valid variable name in the dataset. +% +% Examples: +% plot_var(D, 'Frustration') +% plot_var(D, 'RT') +% plot_var(D, 'RT', 'eventmeans'); +% plot_var(D, 'RT', 'subjtype', 'Placebo'); +% plot_var(D, 'RT', 'eventmeans', 'subjtype', 'Placebo'); +% plot_var(D, 'RT', 'eventmeans', 'subjtype', 'Placebo', 'color', {'r' 'b'}); +% +% Plot the mean and standard error of a variable across events. +% +% Usage: +% ---------------------------------------------------------------------------------- +% [meandat, stedat] = plot_var(D, varname, [opt inputs]) +% +% Inputs: +% ---------------------------------------------------------------------------------- +% D a canlab_dataset object +% varname the name of a valid variable to get from dataset +% - Looks for var name at either level, returns Event level if exists at both levels +% +% [Optional inputs:] +% +% 'subjtype' : followed by name of grouping variable +% - must be categorical subject-level variable +% - if entered, plot lines or bars based on these categories +% - 'eventmeans' will plot bars; without, it will plot line +% plots across events with standard error shading +% - the grouping variable's description, if it exists, will +% be split along commas, and those values will be used as +% column lables +% +% 'eventmeans' : calculate and plot subject means across event-level variables +% - if entered, will plot bar plots of means by condition +% +% 'wh_keep' : followed by 1/0 vector of subjects to keep. +% - must be same length as subjects +% - subjects with value 0 will be excluded +% +% 'color' : followed by one color for all bars, or cell array with names of colors cell for each line/bar +% +% 'nofig' : don't make a new figure +% +% Outputs: +% ---------------------------------------------------------------------------------- +% Copyright Tor Wager, 2013 + +%% plot variable as a function of event number + +grouping_var_name = ''; +event_means = 0; +wh_keep = true(size(D.Subj_Level.id)); %everyone +% colors <- defined below +myfontsize = 22; +nofig=0; + +for i=1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'subjtype' + grouping_var_name = varargin{i+1}; + + case 'eventmeans' + event_means = 1; + + case 'wh_keep' + wh_keep = varargin{i+1}; + + case 'nofig' + nofig=1; + end + end +end + +if ~nofig, create_figure(varname), end + +set(gca, 'FontSize', myfontsize); + +[dat, dcell, wh_level, descripVar] = get_var(D, varname, wh_keep); + +if any(any(isnan(dat(:,:)))), warning('Some NaNs!'); end +meandat = nanmean(dat); +stedat = ste(dat); + +if ~isempty(grouping_var_name) + % We have a grouping variable + + %get a wh_keep for all the levels + [grouping_var, dum, dum, descripGrp] = D.get_var(grouping_var_name, wh_keep); + + levels = unique(grouping_var); + + % colors + colors = scn_standard_colors(length(levels)); + wh = strcmp(varargin, 'colors'); + if any(wh), colors = varargin{find(wh)+1}; end + + + for i=1:length(levels) + wh_keep_lev{i} = (D.get_var(grouping_var_name,wh_keep)==levels(i)); + + %plot each level + dat_level{i} = dat(wh_keep_lev{i},:); + + if event_means || wh_level==1 + % prep bars + ev_means{i} = nanmean(dat_level{i}, wh_level); + + else + % prep plot of events - standard error fills + h = fill_around_line(nanmean(dat_level{i}), ste(dat_level{i}), colors{i}); + lineh(i) = plot(nanmean(dat_level{i}), 'o-', 'Color', colors{i}, 'LineWidth', 3); + end + end + + + groupnames = regexp(descripGrp, ',', 'split'); + + if event_means || wh_level==1 + % Bar plot of groups + if wh_level==2 + barplot_columns(ev_means, prep_name(descripVar), [],'nofig', varargin{:}); + else + barplot_columns(dat_level, prep_name(descripVar), [],'nofig', varargin{:}); + end + + + set(gca, 'FontSize', myfontsize); + ylabel(prep_name(descripVar)); + xlabel([]); + + set(gca, 'XTick', 1:length(levels), 'XTickLabel', groupnames); + + else + set(gca, 'FontSize', myfontsize); + + legend(lineh, groupnames) + + xlabel('Event number'); + ylabel(prep_name(descripVar)); + + end + +else % don't split by subj type + + if wh_level==1 %subject level variable + barplot_columns(dat, prep_name(descripVar), [],'nofig', varargin{:}); + ylabel(descripVar); xlabel('all subjects'); set(gca, 'XTickLabel', []); + + else %event level variable + + % Average across subjects, with std. err across subjects + h = fill_around_line(meandat, stedat, 'k'); + + set(gca, 'FontSize', myfontsize); + + plot(meandat, 'k', 'LineWidth', 3); + + xlabel('Event number'); + + ylabel(descripVar); + end +end + +set(gca, 'FontSize', myfontsize); + + +end % function + + +function varname = prep_name(varname) + +if iscell(varname), varname = varname{1}; end + +wh = varname == '_'; + +varname(find(wh)) = ' '; + + +end diff --git a/@canlab_dataset/print_summary.m b/@canlab_dataset/print_summary.m new file mode 100644 index 00000000..75f33402 --- /dev/null +++ b/@canlab_dataset/print_summary.m @@ -0,0 +1,44 @@ +% print_summary(D, varargin) +% prints summaries for every variable, or specified variables +% +% input: +% - D: dataset +% 'subj': followed by a cell array of subject level var names, to only see those vars +% 'event': followed by a cell array of event level var names, to only see those vars +% +% if either varargin is unspecified, all variables will be printed +function print_summary(D, varargin) + + fprintf('\n\n --------- DATASET VARS -------- \n\n'); + + fprintf('%d subjects, %d subject-level vars, %d event-level vars\n', ... + length(D.Subj_Level.id), length(D.Subj_Level.names), length(D.Event_Level.names)); + + fprintf('\n\n --------- SUBJECT LEVEL VARS -------- \n\n'); + + subj_varnames = D.Subj_Level.names; + svars = find(strcmp('subj', varargin)); + if ~isempty(svars), subj_varnames = varargin{svars+1}; end + + + for i=1:length(subj_varnames) + vname = subj_varnames{i}; + [var,~,~,descrip] = get_var(D, vname); + fprintf('%s (%s): min:%3.2f\t max:%3.2f\t mean:%3.2f\t sd:%3.2f NaNs:%d\n', ... + vname, descrip, min(var), max(var), nanmean(var), nanstd(var), sum(isnan(var))); + end + + + fprintf('\n\n --------- EVENT LEVEL VARS -------- \n\n'); + + event_varnames = D.Event_Level.names; + evars = find(strcmp('event', varargin)); + if ~isempty(evars), event_varnames = varargin{evars+1}; end + + for i=1:length(event_varnames) + vname = event_varnames{i}; + [var,~,~,descrip] = get_var(D, vname); + fprintf('%s (%s): min:%3.2f\t max:%3.2f\t mean:%3.2f NaNs:%d\n', ... + vname, descrip, min(min(var)), max(max(var)), nanmean(nanmean(var)), sum(isnan(isnan(var)))); + end +end \ No newline at end of file diff --git a/@canlab_dataset/scattermatrix.m b/@canlab_dataset/scattermatrix.m new file mode 100644 index 00000000..d1d67a98 --- /dev/null +++ b/@canlab_dataset/scattermatrix.m @@ -0,0 +1,68 @@ +function fig_han = scattermatrix(D, wh_level, wh_vars) +% Scatterplot matrix of pairwise event-level variables +% +% fig_han = scattermatrix(D, wh_level, wh_vars) +% +% wh_level: 1 (Subject) or 2 (Event) +% +% Examples: +% fig_han = scattermatrix(D); +% +% wh = [5:9]; +% fig_han = scattermatrix(D, 2, wh); +% +% f = scattermatrix(D, 2, {'Choice' 'RT' 'Pain' 'SwitchNext' 'Frustration' 'Anxiety' 'Control'}); +% +% Copyright Tor Wager, 2013 + +switch wh_level + case 1 + names = D.Subj_Level.names; + + case 2 + names = D.Event_Level.names; + otherwise + error('Enter 1 or 2 for wh_level.'); +end + +if nargin < 3 || isempty(wh_vars) + wh_vars = 1:length(names); +end + +% convert from cell of names of needed +if iscell(names) + wh = zeros(size(names)); + for i = 1:length(wh_vars) + wh = wh + strcmp(wh_vars{i}, names); + end + wh_vars = find(wh); +end + +names = names(wh_vars); + +n = length(names); + +fig_han = create_figure('scattermatrix', n, n); + +for i = 1:n + + subplot(n, n, (i-1)*n + i) + histogram(D, names{i}, 'nofig'); + + for j = i+1:n + + subplot(n, n, (i-1)*n + j) + + scatterplot(D, names{i}, names{j}, 'nofig'); + + title(' '); + drawnow + + end + +end + +end % function + + + diff --git a/@canlab_dataset/scatterplot.m b/@canlab_dataset/scatterplot.m new file mode 100644 index 00000000..1cd5eaec --- /dev/null +++ b/@canlab_dataset/scatterplot.m @@ -0,0 +1,152 @@ +function fig_han = scatterplot(D, v1, v2, varargin) +% +% fig_han = scatterplot(D, varname1, varname2, varargin) +% +% Scatterplot of two variables in dataset +% - can be either event-level or subject-level +% - event-level data is plotted as multi-line plot, one line per subject +% - both variables must be valid names (case-sensitive) +% +% Optional inputs: +% - 'nofig': suppress creation of new figure +% - 'subjtype': group by the following variable name +% - 'wh_keep' : followed by logical +% - 'colors' : followed by colors. +% - 'dorobust': do robust corr. if enabled, colors will not work and subjtype grouping will not work well until +% the function plot_correlation_samefig is updated, at some point in the future. +% +% +% Example: +% +% scatterplot(D, 'Anxiety', 'Frustration'); +% fig_han = scatterplot(D, D.Subj_Level.names{1}, D.Subj_Level.names{2}); +% scatterplot(D, D.Event_Level.names{1}, D.Event_Level.names{2}); +% +% Copyright Tor Wager, 2013 + +fig_han = []; +dofig = 1; +grouping_var_name=[]; +wh_keep = true(size(D.Subj_Level.id)); %everyone +colors{1}='k'; +dorobust=0; + +for i=1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'subjtype' + grouping_var_name = varargin{i+1}; + case 'wh_keep' + wh_keep = varargin{i+1}; + case 'nofig' + dofig=0; + case 'dorobust' + dorobust=1; + end + end +end + +[dat1, dcell1, whlevel1] = get_var(D, v1, wh_keep, varargin{:}); +[dat2, dcell2, whlevel2] = get_var(D, v2, wh_keep, varargin{:}); +dat1_level{1}=dat1; %to support grouping +dat2_level{1}=dat2; + +if whlevel1 ~= whlevel2 + disp('No plot: Variables are not at same level of analysis.'); + return +end + +if isempty(dat1) || isempty(dat2) + % skip + disp('No plot: Missing variables'); + return +end + +if dofig + fig_han = create_figure([v1 '_vs_' v2]); +else + fig_han = gcf; +end + + +if ~isempty(grouping_var_name) % We have a grouping variable + + %get a wh_keep for all the levels + [grouping_var, dum, dum, descripGrp] = D.get_var(grouping_var_name, wh_keep); + + levels = unique(grouping_var); + + % colors + colors = {'r' 'b' 'g' 'k' 'y'}; + colors = colors(1:length(levels)); + wh = strcmp(varargin, 'colors'); + if any(wh), colors = varargin{find(wh)+1}; end + + + for i=1:length(levels) + wh_keep_lev{i} = (D.get_var(grouping_var_name,wh_keep)==levels(i)); + + dat1_level{i} = dat1(wh_keep_lev{i},:); + dat2_level{i} = dat2(wh_keep_lev{i},:); + end +end + + +for i=1:length(dat1_level) + + switch whlevel1 + case 1 + x=dat1_level{i}; y= dat2_level{i}; + + if dorobust + plot_correlation_samefig(x,y,[],[],[],1) + grid off + else + scatter(x,y,65, 'MarkerFaceColor', colors{i}, 'MarkerEdgeColor', colors{i});%, 'within') + h=refline(polyfit(x,y,1)) + set(h, 'Color', colors{i}, 'LineWidth', 2) + end + + case 2 + + han = line_plot_multisubject(dcell1, dcell2, varargin{:}); + + otherwise + error('Illegal level variable returned by get_var(D)'); +end + +set(gca, 'FontSize', 24) + +xlabel(strrep(v1, '_', ' ')); +ylabel(strrep(v2, '_', ' ')); + +rtotal = corr(dat1(:), dat2(:)); + +switch whlevel1 + case 1 + str = sprintf('r = %3.2f\n', rtotal); + disp(str) + + case 2 + for i = 1:length(dcell1) + x1{i} = scale(dcell1{i}, 1); % mean-center + x2{i} = scale(dcell2{i}, 1); + end + rwithin = corr(cat(1, x1{:}), cat(1, x2{:})); + + str = sprintf('r across all data: %3.2f\nr within subjects: %3.2f', rtotal, rwithin); + disp(str) + + otherwise ('Illegal value!'); + +end + + +xloc = mean(dat1(:)) + std(dat1(:)); +yloc = mean(dat2(:)) + std(dat2(:)); + +text(xloc, yloc, str, 'FontSize', 24); + + +end % function + diff --git a/@canlab_dataset/ttest2.m b/@canlab_dataset/ttest2.m new file mode 100644 index 00000000..bbeec6c2 --- /dev/null +++ b/@canlab_dataset/ttest2.m @@ -0,0 +1,41 @@ +function [h, p, ci, stats] = ttest2(D, varname, wh_keep1, wh_keep2, varargin) +% Two sample ttest for two samples of one subject-level variable +% +% ttest2(D, varname, wh_keep1, wh_keep2, varargin) +% +% +% Examples: +% +% +% Inputs: +% ---------------------------------------------------------------------------------- +% D a canlab_dataset object +% varname the name of a valid variable to get from dataset +% wh_keep1 subjects forming first sample +% wh_keep2 subjects forming second sample +% +% [Optional inputs:] +% +% varargin: passed directly to MATLAB's ttest2 +% 'noverbose' will suppress print out of results and bargraph +% +% Outputs: as from MATLAB's ttest2, +% ---------------------------------------------------------------------------------- +% Copyright Tor Wager, 2013 + +if any(wh_keep1 & wh_keep2), warning('YOUR SAMPLES ARE OVERLAPPING!!'); end + +verbose=1; +if any(strcmp('noverbose', varargin)) + verbose=0; + varargin(find(strcmp('noverbose', varargin))) = []; +end + +x1 = get_var(D, varname, wh_keep1); +x2 = get_var(D, varname, wh_keep2); + +[h, p, ci, stats] = ttest2(x1, x2, varargin{:}); + +if verbose, ttest2_printout(x1,x2, 1); end + +end \ No newline at end of file diff --git a/@canlab_dataset/write_text.m b/@canlab_dataset/write_text.m new file mode 100644 index 00000000..0ca4abbe --- /dev/null +++ b/@canlab_dataset/write_text.m @@ -0,0 +1,149 @@ +function [headername, dataname, fid] = write_text(D, varargin) +% function [headername, dataname, fid] = write_text(D) +% +% "Flatten" dataset and write text files with header and data +% For all Event-level and Subject-level data. Files are created in the +% current working directory. +% +% first varargin parameter is the delimiter. Comma-delimited by default +% +% % Copyright Tor Wager, 2013 + + +% flesh this out later as needed +delim = ','; +for i=1:length(varargin) + delim = varargin{1}; +end + + +% Checks +% ---------------------------------------------------------------------- +for i = 1:length(D.Subj_Level.names) + + if length(D.Subj_Level.descrip) < i + D.Subj_Level.descrip{i} = 'No description provided'; + end + +end + +for i = 1:length(D.Event_Level.names) + + if length(D.Event_Level.descrip) < i + D.Event_Level.descrip{i} = 'No description provided'; + end + +end + +if isempty(D.Event_Level.data) + D.Event_Level.data = cell(1,length(D.Subj_Level.id)); +end + +if isempty(D.Event_Level.names) + D.Event_Level.names = {}; +end + + + +% Open Files +% ---------------------------------------------------------------------- +headername = [D.Description.Experiment_Name '_info_' scn_get_datetime '.txt']; +dataname = [D.Description.Experiment_Name '_data_' scn_get_datetime '.csv']; +fid = fopen(headername, 'w'); + + +% Write Header +% ---------------------------------------------------------------------- + +u = '______________________________________________________________'; + +fprintf(fid, 'Experiment: %s\n', D.Description.Experiment_Name); + +fprintf(fid, '\n%d subjects\n', length(D.Subj_Level.id)); +fprintf(fid, 'Missing values coded with: %f\n', D.Description.Missing_Values); +fprintf(fid, '%s\n', u); + +fprintf(fid, 'Subject Level\n\n'); +fprintf(fid, 'Description:\n'); + +for i = 1:length(D.Description.Subj_Level) + fprintf(fid, '\t%s\n', D.Description.Subj_Level{i}); +end + +fprintf(fid, 'Names:\n'); +for i = 1:length(D.Subj_Level.names) + fprintf(fid, '\t%s\t%s\n', D.Subj_Level.names{i}, D.Subj_Level.descrip{i}); +end + +fprintf(fid, '%s\n', u); + +fprintf(fid, 'Event Level\n\n'); +fprintf(fid, 'Description:\n'); + +for i = 1:length(D.Description.Event_Level) + fprintf(fid, '\t%s\n', D.Description.Event_Level{i}); +end + +fprintf(fid, 'Names:\n'); +for i = 1:length(D.Event_Level.names) + fprintf(fid, '\t%s\t%s\n', D.Event_Level.names{i}, D.Event_Level.descrip{i}); +end + +fprintf(fid, '%s\n', u); + +fclose(fid); + +%% Write subj-level & event-level data +% ----------------------------------------------------------------------- + +fid = fopen(dataname, 'w'); + +if isempty(D.Event_Level.names) + names = {'id' D.Subj_Level.names{:}}; +else + names = {'id' D.Subj_Level.names{:} 'Event_number' D.Event_Level.names{:}}; +end + +n = length(D.Subj_Level.id); + +%slevels = length(D.Subj_Level.names); + +printcell = @(x) fprintf(fid, ['%s' delim], x); +cellfun(printcell, names); +fprintf(fid, '\n'); + +for i = 1:n % for each subject + + e = size(D.Event_Level.data{i}, 1); + + if e==0 % no events + fprintf(fid, ['%s' delim], D.Subj_Level.id{i}); % ID, can be char + datarow = [D.Subj_Level.data(i, :)]; + printrow(fid, datarow, delim); + else + for j = 1:e % for events within subject + fprintf(fid, ['%s' delim], D.Subj_Level.id{i}); % ID, can be char + datarow = [D.Subj_Level.data(i, :) j D.Event_Level.data{i}(j, :)]; + printrow(fid, datarow, delim); + + end % events + end +end % subjects + +fclose(fid); + + +end % function + +function printrow(fid, datarow, delim) + % Could switch by datatype in a better way here!! need datatype codes in dataset. + for k = 1:length(datarow) + if datarow(k) == round(datarow(k)) + fprintf(fid, ['%d' delim], datarow(k)); + else + fprintf(fid, ['%3.3f' delim], datarow(k)); + end + end % row + + fprintf(fid, '\n'); +end \ No newline at end of file diff --git a/@fmri_data/create.m b/@fmri_data/create.m new file mode 100644 index 00000000..14420087 --- /dev/null +++ b/@fmri_data/create.m @@ -0,0 +1,40 @@ +function obj = create(obj, varargin) +% Create an object from an empty obj structure, assigning fieldname/value +% pairs as optional arguments. +% +% obj = create(obj, varargin) +% +% Used in fmri_data.m class constructor. + +% if 'noverbose' is entered, suppress output +verbose = isempty(strmatch('noverbose', varargin(cellfun(@ischar, varargin)))); + +N = fieldnames(obj); + +for i = 1:length(varargin) + if ischar(varargin{i}) + + % Look for a field (attribute) with the input name + wh = strmatch(varargin{i}, N, 'exact'); + + if ~isempty(wh) + + obj.(varargin{i}) = varargin{i + 1}; + + % special methods for specific fields + switch varargin{i} + case 'dat' + xx = isnan(obj.(varargin{i})); + if any(xx(:)) + if verbose, fprintf('fmri_data.create: Converting %3.0f NaNs to 0s.', sum(xx(:))); end + + obj.dat(xx) = 0; + end + + end + end + + end +end + +end % function \ No newline at end of file diff --git a/@fmri_data/extract_roi_averages.m b/@fmri_data/extract_roi_averages.m new file mode 100644 index 00000000..d2fafee4 --- /dev/null +++ b/@fmri_data/extract_roi_averages.m @@ -0,0 +1,333 @@ +function [cl, varargout] = extract_roi_averages(obj, mask_image, varargin) +% [cl, varargout] = extract_roi_averages(fmri_data obj, [mask_image], [average_over]) +% +% This fmri_data method a extracts and averages data stored in an fmri_data object +% from a set of ROIs defined in a mask. +% +% If no mask_image is entered, it uses the mask defined with the fmri_data object as a default. +% +% If mask_image is a new image file name, this method: +% 1) Defines an fmri_mask_image object using create_fmri_mask +% 2) Maps to the space in fmri_data object using resample_to_image_space +% +% Regions to average over can be either regions of contiguous voxels +% bounded by voxels with values of 0 or NaN, which are considered non-data +% values, or regions defined by unique integer codes in the mask image +% (i.e., for atlas images with unique codes for each defined region.) +% +% Mask/Atlas image does NOT have to be in the same space as the images to +% extract from. It will be remapped/resliced. +% NOTE: Mask is *reloaded* from original data if space is remapped, and you +% cannot use manual thresholding of the mask. This is a feature of the +% map_to_image_space method and scn_map_image +% +% extracted data is returned in single data format. +% +% Inputs: +% 1 - char array of strings containing 4D image file names (data extracted from these) +% 2 - mask_image to extract from. +% +% Optional inputs: +% how to average: +% Default = 'unique_mask_values' to average over unique integer codes in the mask image +% bounded by voxels of 0 or NaN (non-data values) +% (i.e., for atlas images with unique codes for each defined region) +% Alt. option = 'contiguous_regions' to average over contiguous voxels +% +% 'pattern_expression': +% Use values in mask images to get weighted average within each +% region, rather than simple average. See also apply_mask with +% 'pattern_expression' option. +% +% Optional outputs (varargout): +% [cl, cl_roimean, cl_roipattern] = ... +% roimean: pattern expression is average over ROI (unit vector) +% roipattern: pattern expression is dot product of activity and mean-centered pattern weights +% +% 'nonorm' +% Turn off L1 norm in pattern expression. +% +% Example: +% imgs_to_extract_from = filenames('w*.nii','char'); +% mask_image = which('anat_lbpa_thal.img'); +% [cl, imgdat] = extract_image_data(imgs_to_extract_from, mask_image); +% +% region_obj = extract_roi_averages(data_obj, mask_char_name, 'pattern_expression', 'contiguous_regions'); +% +% Notes: +% cl(i).dat gives you the pattern expression values for cluster i. +% +% Related functions: +% For an non-object-oriented alternative, see extract_image_data.m + +% Modified June 11, 2013 by Tor +% - use resample_space instead of resample_to_image_space + +pattern_norm = 1; % for pattern expression -- default is norm pattern weights +varargout = {}; + +%space_defining_image = deblank(obj.fullpath(1, :)); +space_defining_image = obj.mask; + +% --------------------------------- +% define mask object and resample to image space +% --------------------------------- + +fprintf('fmri_data.extract_roi_averages: '); + +if nargin < 2 || isempty(mask_image) + + mask = obj.mask; + + obj.dat = obj.dat'; + +else + + fprintf('Defining mask object. '); + + switch class(mask_image) + + case 'char' + % Create a mask object + mask = fmri_mask_image(mask_image); + + case 'fmri_mask_image' + mask = mask_image; + + case {'image_vector', 'fmri_data'} + mask = replace_empty(mask_image); + + otherwise + error('fmri_data.extract_roi_averages: unknown mask input type.') + + end + + isdiff = compare_space(mask, space_defining_image); + + if isdiff == 1 || isdiff == 2 + % resample it to the image space + % * this step cannot be done recursively...could be worked on. + fprintf('Resampling mask. '); + mask = resample_space(mask, space_defining_image); + + %mask = resample_to_image_space(mask, space_defining_image); % do + %not use - requires re-loading from disk and removes any manual + %thesholding + + end + + if any(obj.volInfo.image_indx ~= mask.volInfo.image_indx) + % space is same, but voxel indices are not the same... + % different masks for each, most likely... + + % There is a simpler solution than resampling...could work on this. + mask = resample_space(mask, obj); + + + end + + % this mask will have a different set of in-mask voxels + % get only the subset in BOTH the original fmri dataset mask and + % the new one we're applying + + % -------------------------------------------- + % Redefine elements of new mask to apply + % to keep only coords in original dataset + % -------------------------------------------- + + is_inmask = obj.mask.volInfo.image_indx & mask.volInfo.image_indx; + mask.volInfo.image_indx = is_inmask; + + orig_wh_inmask = mask.volInfo.wh_inmask; % save for later - + + mask.volInfo.wh_inmask = find(mask.volInfo.image_indx); + mask.volInfo.n_inmask = length(mask.volInfo.wh_inmask); + + % need to limit coords in new mask to ONLY those in the orig mask as + % well. + wh_to_keep = is_inmask(orig_wh_inmask); + mask.volInfo.xyzlist = mask.volInfo.xyzlist(wh_to_keep, :); + mask.volInfo.cluster = mask.volInfo.cluster(wh_to_keep, :); + + + % -------------------------------------------- + % Redefine data (temporarily; not passed out) + % to keep only coords in new mask + % -------------------------------------------- + + % need to limit data in obj to ONLY voxels that are also in the new + % mask. Index in space of original fmri_data object for which to keep: + wh_to_keep = is_inmask(obj.mask.volInfo.wh_inmask); + + % eliminate out-of-mask voxels before indexing into them with new mask + obj = replace_empty(obj); + obj.dat = obj.dat(wh_to_keep, :)'; + + +end + +fprintf('\n'); + +% --------------------------------- +% define region object based on choices +% also define optional inputs +% --------------------------------- + +average_over = 'unique_mask_values'; %'contiguous_regions' or 'unique_mask_values'; + +for varg = 1:length(varargin) + if ischar(varargin{varg}) + switch varargin{varg} + + % reserved keywords + case 'contiguous_regions', average_over = 'contiguous_regions'; + case 'unique_mask_values', average_over = 'unique_mask_values'; + + case {'pattern_expression', 'donorm'} + % do nothing -- ignore and use later + + case {'nonorm'} + pattern_norm = 0; + + otherwise + disp('fmri_data.extract_roi_averages: Illegal string value for average_over.'); + fprintf('You entered ''%s''\n Valid values are %s or %s\n', varargin{varg}, '''contiguous_regions''', '''unique_mask_values'''); + error('Exiting'); + end + end +end + +cl = region(mask, average_over); +cl(1).source_images = obj.fullpath; + +if length(varargout) > 0 + [clroimean, clpattern] = deal(cl); +end + +% --------------------------------- +% Now get averages by cluster +% --------------------------------- + +maskData = mask.dat(logical(mask.volInfo.wh_inmask), :); + +if any(strcmp(varargin, 'pattern_expression')) % for pexp only + maskvals = maskData; + fprintf('Applying mask weights to get pattern expression.\n'); + + if pattern_norm + fprintf('Normalizing within contiguous region by L1 norm.\n'); + else + fprintf('No normalization of region weights.\n'); + end +else + fprintf('Averaging data. '); +end + +switch average_over + + % Define integer codes for sets of voxels to average over. + + case 'unique_mask_values' + maskData = round(maskData); + u = unique(maskData)'; u(u == 0) = []; + nregions = length(u); + fprintf('Averaging over unique mask values, assuming integer-valued mask: %3.0f regions\n', nregions); + + + case 'contiguous_regions' + u = unique(mask.volInfo.cluster); u(u == 0) = []; + maskData = mask.volInfo.cluster; + + + case 'none' + cl = []; + return + + + otherwise + error('Illegal value for average_over. See help for this function.'); +end + + +% Now get the average activity in each region for the defined regions + +nregions = length(u); + +if nregions ~= length(cl) + disp('Num of regions in mask does not equal num of clusters to extract from.') + disp('The most likely cause is an ill-formed/outdated obj.volInfo.cluster field'); + disp('in the region-defining object. Try obj = reparse_contiguous(obj);'); + disp('Stopping in debugger so you can check/debug.'); + keyboard +end + +for i = 1:nregions + imgvec = maskData == u(i); + + regiondat = obj.dat(:, imgvec); + + if ~isempty(regiondat) + + if any(strcmp(varargin, 'pattern_expression')) + w = maskvals(imgvec); + + if pattern_norm + % L2 norm of pattern, norm to length 1 -- geometric mean + %w = w ./ sum(abs(w).^2)^(1/2); + + % L1 norm + w = w ./ sum(abs(w)); + + cl(i).val_descrip = 'Mask weights, L1 normed'; + else + cl(i).val_descrip = 'Mask weights, no normalization'; + end + + regionmean = regiondat * w; + cl(i).val = w; + + if length(varargout) > 0 + % optional outputs: + % separate pexp for mean across region (unit vector) and + % mean-centered pattern + z = ones(size(w)); + w2 = [z w - mean(w)]; + partialpexp = regiondat * w2; + + clroimean(i).val = z; + clroimean(i).dat = partialpexp(:, 1); + + clpattern(i).val = w - mean(w); + clpattern(i).dat = partialpexp(:, 2); + + end + + else + % else, simple average + + if size(regiondat, 2) == 1 + regionmean = double(regiondat); + else + regionmean = double(nanmean(regiondat')'); + end + end % pattern or average... + + cl(i).all_data = single(regiondat); + + else + regionmean = NaN .* zeros(size(dat, 1), 1); + end + + cl(i).dat = regionmean; + +end + +fprintf('Done.\n'); + +if length(varargout) > 0 + varargout{1} = clroimean; + varargout{2} = clpattern; +end + + +end % function \ No newline at end of file diff --git a/@fmri_data/fmri_data.m b/@fmri_data/fmri_data.m new file mode 100644 index 00000000..b2e98f46 --- /dev/null +++ b/@fmri_data/fmri_data.m @@ -0,0 +1,299 @@ +% fmri_data: Data class for storing data matrices and information +% +% 'fmri_data' is a data class containing information about generic fmri +% datasets stored in a structure-like object. Using this has the +% advantages that the fields and methods are standardized and controlled. +% It also keeps track of the history of what was done to the dataset. +% +% Creating class instances +% ----------------------------------------------------------------------- +% You can create an empty object by using: +% fmri_dat = fmri_data +% (fmri_dat is the object) +% +% You can create an object and extract data from a mask (defining many of +% the fields in the object) like this: +% +% dat = fmri_data(imgs, maskimagename); +% +% e.g., +% dat = fmri_data(imgs, which('brainmask.nii')); +% +% Defining the space of the extracted data +% ----------------------------------------------------------------------- +% Note: There are two options for defining the space (i.e., coordinates/voxels) +% that the data is mapped to. +% By default, the mask is resliced to the same space as the first image in the +% input image name set (not coregistered; just resliced to the same voxel sizes. +% The images are assumed to be in register.) +% YOU CAN ALSO map the image data to the space of the mask, by entering +% 'sample2mask' as in input argument. +% +% Creating class instances +% ----------------------------------------------------------------------- +% The fmri_data object will store image data (.X) also outcome data (.Y) +% Try typing the name of an object (class instance) you create to see its +% properties, and a link to its methods (things you can run specifically +% with this object type). +% +% Extracting ROI data easily +% ----------------------------------------------------------------------- +% You can extract image data, and save averages within regions of +% interest, by doing something like this: +% [fmri_dat, cl] = read_image_files(image_names(3, :)); +% +% cl is the ROI data in a region object +% region is a class. It's data structure is like the older "clusters" +% structure format, with average data values stored in cl.dat +% regions can be defined by EITHER contiguous voxels or based on unique integer +% values in images. +% +% More about working with masks +% ----------------------------------------------------------------------- +% You need a mask image to define which voxels are extracted and possibly the +% space of the image data. +% If you do not yet have a mask image, but have data extracted separately, +% you can add mask information (from a mask in the same space) like this: +% +% dat = create(dat, 'mask', fmri_mask_image(maskimg)); +% +% More methods +% ----------------------------------------------------------------------- +% +% Methods include create, extract_roi_averages +% +% Create lets you add fields/data to a structure (see help +% fmri_data.create) +% +% extract_roi_averages lets you specify a new mask, and extract and average +% data from ROIs defined by the new mask (provided they were in the +% original mask from which you extracted data!) +% +% Examples: +% obj = fmri_data(image_names, maskinput) +% obj = fmri_data(image_names, [], 'noverbose') + + +classdef fmri_data < image_vector + + properties + % also inherits the properties of image_vector. + + source_notes = 'Source notes...'; + + X % legacy; temporary, so we can load old objects + + mask = fmri_mask_image; + mask_descrip = 'mask is an fmri_mask_image object that defines the mask.'; + + images_per_session + + Y = []; + Y_names; + Y_descrip = 'Behavioral or outcome data matrix.'; + + covariates; + covariate_names = {''}; + covariates_descrip = 'Nuisance covariates associated with data'; + + history_descrip = 'Cell array: names of methods applied to this data, in order'; + + additional_info = struct(''); + + end % properties + + methods + + % Class constructor + function obj = fmri_data(image_names, maskinput, varargin) + % + % [obj, cl_with_averages] = fmri_data(image_names, mask_image, varargin) + % + % Reads a set of image files and a mask image, and returns + % an fmri_data object with data for all in-mask voxels. + + % --------------------------------- + % Create empty fmri_data object, and return if no additional + % arguments + % --------------------------------- + + + obj.source_notes = 'Info about image source here'; + obj.mask = fmri_mask_image; + obj.mask_descrip = 'Volume and in-area mask info from iimg_read_img'; + + obj.X = []; % legacy; temporary, so we can load old objects + obj.Y = []; + obj.Y_names; + obj.Y_descrip = 'Behavioral or outcome data matrix.'; + obj.covariates; + obj.covariate_names = {''}; + obj.covariates_descrip = 'Nuisance covariates associated with data'; + + obj.images_per_session = []; + + obj.history = {''}; + obj.history_descrip = 'Cell array of names of methods applied to this data, in order'; + obj.additional_info = struct(''); + + if nargin == 0 + return + end + + verbose = 1; + verbosestr = 'verbose'; + sample2mask = 0; + + % SET UP OPTIONAL INPUTS + % ----------------------------------- + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'verbose', varargin{i} = []; % nothing else needed + case 'noverbose', verbose = 0; verbosestr = 'noverbose'; varargin{i} = []; + case 'sample2mask', sample2mask = 1; varargin{i} = []; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + % --------------------------------- + % Special: if existing image_vector + % map into space of fmri_data and return + % --------------------------------- + if iscell(image_names), image_names = char(image_names{:}); end + + if isa(image_names, 'image_vector') + % Map fields of input object into fmri_data structure + + obj2 = struct(image_names); + + N = fieldnames(obj); + for i = 1:length(N) + if isfield(obj2, (N{i})) + obj.(N{i}) = obj2.(N{i}); + end + end + + obj.mask.volInfo = obj2.volInfo; + return + end + + % --------------------------------- + % define mask object + % --------------------------------- + + if nargin < 2 || isempty(maskinput) + maskinput = which('brainmask.nii'); + if verbose, fprintf('Using default mask: %s\n', maskinput); end + if isempty(maskinput), error('Cannot find mask image!'); end + end + + switch class(maskinput) + case 'char' % string file name + maskobj = fmri_mask_image(maskinput); + + case 'fmri_mask_image' + maskobj = maskinput; + + otherwise + error('region class constructor: unknown mask input type.') + end + clear maskinput + + % Either extract the image data in the space of the mask, or + % vice versa + %sample2mask = strmatch('sample2mask', varargin); + + if sample2mask + % Read data in mask space; map images to mask + % ------------------------------------------------ + if verbose, fprintf('Expanding image filenames if necessary\n'); end + + for i = 1:size(image_names, 1) + + iinames{i} = expand_4d_filenames(image_names(i, :)); + end + iinames = char(iinames{:}); + imgdat = zeros(length(maskobj.volInfo.wh_inmask), size(iinames, 1), 'single'); + + if isempty(iinames) + disp('Images do not exist!'); disp(image_names); error('Exiting'); + end + + % Now extract the actual data from the mask + if verbose + fprintf('Sampling %3.0f images to mask space: %04d', size(iinames, 1), 0) + end + + for i = 1:size(iinames, 1) + + if verbose, fprintf('\b\b\b\b%04d', i); end + idat = scn_map_image(iinames(i, :), maskobj); + imgdat(:, i) = idat(maskobj.volInfo.wh_inmask); + + end + + if verbose, fprintf('\n'); end + + [dd, ff, ee] = fileparts(maskobj.volInfo.fname); + maskobj.space_defining_image_name = [ff ee]; + + obj.volInfo = maskobj.volInfo; + + else + % Read data in image space; map mask to images + % ------------------------------------------------ + + % resample to image space if necessary + space_defining_image = deblank(image_names(1, :)); + maskobj = resample_to_image_space(maskobj, space_defining_image); + + + % Now extract the actual data from the mask + switch spm('Ver') + + case {'SPM8', 'SPM5'} + imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); + + case {'SPM2', 'SPM99'} + % legacy, for old SPM + imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr); + + otherwise + error('Unknown version of SPM! Update code, check path, etc.'); + end + + imgdat = imgdat'; + + end % read data, depending on mask sampling + + % add mask object to fmri_data object + obj = create(obj, 'mask', maskobj, verbosestr); + + % append data + obj = create(obj, 'dat', imgdat, 'image_names', image_names, verbosestr); + clear imgdat + + % append description info + [dd, ff, ee] = fileparts(maskobj.volInfo.fname); + mask_image_name = [ff ee]; + %obj = create(obj, 'dat_descrip', sprintf('Data from %s: %s', mask_image_name, obj.dat_descrip)); + obj = create(obj, 'mask_descrip', mask_image_name, verbosestr); + + obj.volInfo = maskobj.volInfo; + + obj.history(end+1) = {sprintf('Sampled to space of %s', maskobj.space_defining_image_name)}; + obj.history(end+1) = {['Masked with ' mask_image_name]}; + + obj = check_image_filenames(obj, verbosestr); + + end % constructor function + + end % methods + + +end \ No newline at end of file diff --git a/@fmri_data/hrf_fit.m b/@fmri_data/hrf_fit.m new file mode 100644 index 00000000..a6c833e2 --- /dev/null +++ b/@fmri_data/hrf_fit.m @@ -0,0 +1,174 @@ +function [params_obj hrf_obj] = hrf_fit(obj,TR,Runc,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) +% +% INPUTS: +% +% obj - fMRI object +% TR - time resolution +% Runs - expermental design +% 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 HRF using FIR-model +% +% Choose mode (FIR/sFIR) +% +% 0 - FIR +% 1 - smooth FIR +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Created by Martin Lindquist on 04/11/14 +% +% EXAMPLE: SIMULATE DATA AND RUN +% ------------------------------------------------------------------------ +% % params for sim and fitting +% % ---------------------------------------------- +% TR = 2; % repetition time (sec) +% n = 200; % time points measured (for simulation) must be multiple of 10 +% T = 30; % duration of HRF to estimate (seconds) +% nconds = 2; % num conditions +% nevents = 8; % events per condition +% +% % Create fake data +% % ---------------------------------------------- +% h = spm_hrf(TR); +% y = zeros(n, 1); +% +% % onsets - indicator +% Condition = {}; +% for i = 1:nconds +% Condition{i} = zeros(n,1); +% wh = randperm(n); +% Condition{i}(wh(1:nevents)) = 1; +% +% ytmp{i} = conv(Condition{i}, h); +% ytmp{i} = ytmp{i}(1:n); +% end +% +% y = sum(cat(2, ytmp{:}), 2); +% +% dat = fmri_data('VMPFC_mask_neurosynth.img'); % AVAILABLE ON WIKI IN MASK GALLERY +% dat = threshold(dat, [5 Inf], 'raw-between'); +% +% v = size(dat.dat, 1); % voxels in mask +% dat.dat = repmat(y',v, 1) + .1 * randn(v, n); +% +% % Fit data - estimate HRFs across the brain mask +% % ---------------------------------------------- +% [params_obj hrf_obj] = hrf_fit(dat,TR, Condition, T,'FIR', 1); +% +% hrf = fmri_data('HRF_timecourse_cond0001.img'); +% hrf = remove_empty(hrf); +% create_figure('hrfs', 1, 2); +% plot(hrf.dat'); +% title('Condition 1') +% hrf = fmri_data('HRF_timecourse_cond0002.img'); +% hrf = remove_empty(hrf); +% subplot(1, 2, 2); +% plot(hrf.dat'); +% title('Condition 2') + + + +fprintf('HRF estimation\n') + +%fhan = @(tc) hrf_fit_one_voxel(tc,TR,Runc,T,method,mode); + +fhan = @(tc) hrf_fit_one_voxel_meval(tc,TR,Runc,T,method,mode); + +narg_to_request = length(Runc) * 2; +myoutargs = cell(1, narg_to_request); + +[myoutargs{:}] = matrix_eval_function(obj.dat',fhan); + +%[hrf_vals_brain param_vals_brain] = matrix_eval_function(obj.dat',fhan); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +numstim = length(Runc); % number of conditions + +shell_obj = mean(obj); % shell object + +for i=1:numstim + + hrf_obj_i = shell_obj; + params_obj_i = shell_obj; + + %%%%%%%%%%%%%%%%%%% + % Assign param data + params_obj_i.dat = myoutargs{numstim + i}; + + %%%%%%%%%%%%%%%%%%% + % Write data + params_obj_i.fullpath = sprintf('HRF_params_cond%04d.img', i); + write(params_obj_i); + + + %%%%%%%%%%%%%%%%%%%% + % Assign HRF data + hrf_obj_i.dat = myoutargs{i}; + + %%%%%%%%%%%%%%%%%%%% + % Write data + hrf_obj_i.fullpath = sprintf('HRF_timecourse_cond%04d.img', i); + write(hrf_obj_i); + +end + +hrf_obj = myoutargs(1:numstim); +params_obj = myoutargs(numstim+1 : end); + + +end + + + +function varargout = hrf_fit_one_voxel_meval(tc,TR,Runc,T,method,mode) +% parse outputs into separate row vectors (for matrix_eval_function) +% outputs must be row vectors + +[h, fit, e, param] = hrf_fit_one_voxel(tc,TR,Runc,T,method,mode); + +varargout = {}; +for i = 1:size(h, 2) + varargout{end+1} = h(:, i)'; +end + +for i = 1:size(param, 2) + varargout{end+1} = param(:, i)'; +end + +end + diff --git a/@fmri_data/plot.m b/@fmri_data/plot.m new file mode 100644 index 00000000..785331b3 --- /dev/null +++ b/@fmri_data/plot.m @@ -0,0 +1,350 @@ +function plot(fmridat, plotmethod) +% plot(fmridat, [plotmethod]) +% +% Plot methods: +% ---------------------------------------- +% Plot data matrix +% plot(fmri_data_object) +% +% Plot means by condition +% plot(fmri_data_object, 'means_for_unique_Y') +% +% +% +% Output: +% --------------------------------------- +% 5 plots and an SPM orthviews presentation of the data. In the below and elsewhere, "image" connotes a 3D brain volume +% captured every TR. +% +% subplot 1: the fMRI data itself. Color is intensity of signal +% subplot 2: presented as a histogram of values for every voxel collected. +% The low values are typically out-of-brain voxels, as there is no signal +% there +% subplot 3: each point is an image. The point's X value is the mean +% intensity of every voxel in that image, and the Y value is the stdev of +% intensities for all voxels in that image +% subplot 4: covariance between images +% subplot 5: each point is an image (case = image). X value is image +% number in the run, Y is image mean intensity, and the size of the +% circular marker represents stdev for that image +% +% Orthviews: mean and STD for a given voxel averaged over time. Note that the values for mean and STD here are higher than in +% the plots above. That is because mean and STD are calculated here by +% voxel, but in the plots above they are calculated by image. Images also include out-of-brain areas. + + +if nargin < 2 + plotmethod = 'data'; +end + + +switch plotmethod + % ============================================================== + case 'data' + % ============================================================== + + if isempty(fmridat.dat) + warning('No data in .dat field.'); + return + end + + create_figure('fmri data matrix', 2, 3); + imagesc(fmridat.dat'); + colorbar + + axis tight; set(gca, 'YDir', 'Reverse') + title('fmri data .dat Data matrix'); + xlabel('Voxels'); ylabel('Images'); + drawnow + + tmp = mean(fmridat.dat(:)); + stmp = nanstd(fmridat.dat(:)); + myrange = [tmp - 3*stmp tmp + 3*stmp]; + set(gca, 'CLim', myrange); + drawnow + + + if ~isempty(fmridat.Y) + p = get(gca, 'Position'); ystart = p(2); ylen = p(4); + + axh = axes('Position', [.05 ystart .03 ylen]); + imagesc(fmridat.Y); + title('Y'); + axis tight; + end + drawnow + + % --------------------------------------------------------------- + % Covariance + % --------------------------------------------------------------- + + subplot(2, 3, 4); + covmtx = cov(fmridat.dat); + imagesc(covmtx); + axis tight; set(gca, 'YDir', 'Reverse') + title('cov(images)'); + colorbar + drawnow + + if ~isempty(fmridat.Y) + p = get(gca, 'Position'); ystart = p(2); ylen = p(4); + + axh = axes('Position', [.05 ystart .03 ylen]); + imagesc(fmridat.Y); + title('Y'); + axis tight; + + end + drawnow + + % --------------------------------------------------------------- + % Histogram + % --------------------------------------------------------------- + dattmp = fmridat.dat(:); + subplot(2, 3, 2); + [h, x] = hist(dattmp, 100); + han = bar(x, h); + set(han, 'FaceColor', [.3 .3 .3], 'EdgeColor', 'none'); + axis tight; + xlabel('Values'); ylabel('Frequency'); + title('Histogram of values'); + drawnow + + clear dattmp + + globalmean = nanmean(fmridat.dat); % global mean of each obs + globalstd = nanstd(fmridat.dat); % global mean of each obs + nobs = length(globalmean); + sz = rescale_range(globalstd, [1 6]); % marker size related to global std + sz(sz == 0) = 1; + %sz(sz < .5) = .5; + + % --------------------------------------------------------------- + % Global mean vs. std + % --------------------------------------------------------------- + subplot(2, 3, 3); hold on; + plot(globalmean, globalstd, 'k.'); + title('Image mean vs. std across voxels'); + xlabel('Image mean'); + ylabel('Image std'); + + % --------------------------------------------------------------- + % Global mean vs. time + % --------------------------------------------------------------- + if size(fmridat.dat,2) > 1 + subplot(2, 3, 5); hold on; + + plot(globalmean, '-'); + axis tight + + Y = 1:nobs; + Yname = 'Case number'; + + for i = 1:nobs + plot(Y(i), globalmean(i), 'ko', 'MarkerSize', sz(i), 'LineWidth', 1); + end + ylabel('Global mean'); + xlabel(Yname); + title('Globals for each case (size = spatial std)') + axis tight + drawnow + + if ~isempty(fmridat.Y) && size(fmridat.Y, 1) == nobs + + subplot(2, 3, 6); + Y = fmridat.Y; + Yname = 'Y values in fmri data obj'; + for i = 1:nobs + plot(Y(i), globalmean(i), 'ko', 'MarkerSize', sz(i), 'LineWidth', 1); + end + ylabel('Global mean'); + xlabel(Yname); + title('Globals for each case (size = spatial std)') + axis tight + drawnow + end + end + + + % [coeff, score, latent] = princomp(fmridat.dat, 'econ'); + % %d2 = mahal(score, score); + % plot(latent) + + + % --------------------------------------------------------------- + % Orthviews + % --------------------------------------------------------------- + % check to be sure: + fmridat.dat(isnan(fmridat.dat)) = 0; + + m = mean(fmridat.dat',1)'; %mean values of each voxel + s = std(fmridat.dat',1)'; %std of each voxel + d = m./s; + d(m == 0 | s == 0) = 0; + + if size(fmridat.dat,2) > 1 % if there is more than one image, show std and snr too + vecs_to_reconstruct = [m s d]; + else + vecs_to_reconstruct = [m];% else just show mean image + end + + if isempty(fmridat.volInfo) + disp('.volInfo is empty. Skipping orthviews and other brain plots.'); + else + create_orthviews(vecs_to_reconstruct, fmridat); + spm_orthviews_name_axis('Mean data', 1); + if size(fmridat.dat,2) > 1 + spm_orthviews_name_axis('STD of data', 2); + spm_orthviews_name_axis('Mean / STD', 3); + end + set(gcf, 'Name', 'Orthviews_fmri_data_mean_and_std'); + end + + % ============================================================== + case 'means_for_unique_Y' + % ============================================================== + + u = unique(fmridat.Y); + + [v, n] = size(fmridat.dat); + nu = length(u); + + if nu > 20 + error('More than 20 unique values of Y. For means_by_condition, Y should be discrete integer-valued.'); + end + + [means, stds] = deal(zeros(nu, v)); + + for i = 1:nu + means(i, :) = nanmean(fmridat.dat(:, fmridat.Y == u(i))'); + stds(i, :) = nanstd(fmridat.dat(:, fmridat.Y == u(i))'); + end + + create_figure('means by condition (unique Y values)', 2, 1); + imagesc(means); + colorbar + axis tight; set(gca, 'YDir', 'Reverse') + title('Means by condition'); + xlabel('Voxels'); + if iscell(fmridat.Y_names) && ~isempty(fmridat.Y_names) + set(gca, 'YTick', u, 'YTickLabel', fmridat.Y_names); + else + ylabel('Unique Y values'); + end + + drawnow + + subplot(2, 1, 2) + imagesc(stds); + colorbar + axis tight; set(gca, 'YDir', 'Reverse') + title('Standard deviations by condition'); + xlabel('Voxels'); + if iscell(fmridat.Y_names) && ~isempty(fmridat.Y_names) + set(gca, 'YTick', u, 'YTickLabel', fmridat.Y_names); + else + ylabel('Unique Y values'); + end + drawnow + + % --------------------------------------------------------------- + % Orthviews + % --------------------------------------------------------------- + if ~isempty(fmridat.volInfo) + vecs_to_reconstruct = means'; + create_orthviews(vecs_to_reconstruct, fmridat); + n = size(vecs_to_reconstruct, 2); + + if iscell(fmridat.Y_names) && ~isempty(fmridat.Y_names) && length(fmridat.Y_names) == n + axnames = fmridat.Y_names; + else + for i = 1:n, axnames{i} = sprintf('Y = %3.3f', i); end + end + + for i = 1:n + spm_orthviews_name_axis(axnames{i}, i); + end + set(gcf, 'Name', 'Orthviews_means_by_unique_Y'); + + + % --------------------------------------------------------------- + % Montage: mean across conditions + % --------------------------------------------------------------- + vecs_to_reconstruct = mean(means)'; + vecs_to_reconstruct(vecs_to_reconstruct < prctile(vecs_to_reconstruct, 70)) = 0; + fig_handle = create_montage(vecs_to_reconstruct, fmridat); + set(fig_handle, 'Name', 'Montage_mean_across_conditions') + + vecs_to_reconstruct = std(means)' ./ mean(means)'; + vecs_to_reconstruct(vecs_to_reconstruct < prctile(vecs_to_reconstruct, 70)) = 0; + fig_handle = create_montage(vecs_to_reconstruct, fmridat); + set(fig_handle, 'Name', 'Montage_coeff_of_var_across_conditions') + + + end + + + otherwise + error('Unknown plot method'); +end + +end + + +function create_orthviews(vecs_to_reconstruct, fmridat) + +vecs_to_reconstruct = zeroinsert(fmridat.removed_voxels, vecs_to_reconstruct); + +n = size(vecs_to_reconstruct, 2); +overlay = which('SPM8_colin27T1_seg.img'); +spm_check_registration(repmat(overlay, n, 1)); + +for i = 1:n + + cl{i} = iimg_indx2clusters(vecs_to_reconstruct(:, i), fmridat.volInfo); + cluster_orthviews(cl{i}, 'add', 'handle', i); + + spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 1 1], [.5 .5 .5], [1 .5 0]); + +end + +end + + +function fig_handle = create_montage(vecs_to_reconstruct, fmridat) + +n = size(vecs_to_reconstruct, 2); +overlay = which('SPM8_colin27T1_seg.img'); + +for i = 1:n + + dat = vecs_to_reconstruct(:, i); + % top and bottom 10% + dat(dat > prctile(dat, 10) & dat < prctile(dat, 90)) = 0; + + cl{i} = iimg_indx2clusters(dat, fmridat.volInfo); + + fig_handle(i) = montage_clusters(overlay, cl{i}, [2 2]); + + set(fig_handle, 'Name', sprintf('Montage %3.0f', i), 'Tag', sprintf('Montage %3.0f', i)) + +end + +end + + +function rx = rescale_range(x, y) +% re-scale x to range of y +m = range(y)./range(x); + +if isinf(m) + % no range/do not rescale + rx = x; +else + x = x - min(x); + rx = y(1) + x * ((y(2) - y(1)) ./ max(x)); +end + +end + + diff --git a/@fmri_data/predict.m b/@fmri_data/predict.m new file mode 100644 index 00000000..849b5169 --- /dev/null +++ b/@fmri_data/predict.m @@ -0,0 +1,1824 @@ +function [cverr, stats, optout] = predict(obj, varargin) +% Predict outcome (Y) from brain data and test cross-validated error rate for an fmri_data object +% +% [cverr, stats, optional_outputs] = predict(obj, varargin) +% +% Features: +% --------------------------------------------------------------------- +% - flexible specification of algorithm by function name +% - k-fold cross-validation, default = 5-fold, can enter custom fold membership +% - folds are stratified on outcome +% - choice of multiple error metrics (class loss, mse, etc.) +% - by default, chooses error metric based on outcome type (classes vs. continuous-valued) +% - returns all outputs for each fold returned by the algorithm in optout cell array variable +% - bootstrapping of weights built in [optional keyword] +% - select variable number of components (for pcr-based techniques) +% +% Inputs (obj is mandatory, rest are optional): +% --------------------------------------------------------------------- +% obj = fmri_data or image_vector object, with fields .dat (data used to predict) and .Y (outcome) +% +% Optional inputs with their default values: +% :-------------------------------------------- +% 'nfolds' = 5; % number of folds +% 'nfolds' = [vector of integers] % can also input vector of integers for holdout set IDs +% 'error_type' = 'mcr'; % mcr, mse: misclassification rate or mean sq. error +% 'algorithm_name' = 'cv_regress'; % name of m-file defining training/test function +% 'useparallel' = 1 % Use parallel processing, if available; follow by 1 for yes, 0 for no +% 'bootweights' = 0; % bootstrap voxel weights; enter +% 'bootweights' % do bootstrapping of weight maps (based on all observations) +% 'savebootweights' % save bootstraped weights (useful for combining across multiple iterations of predict()) +% 'bootsamples' = 100; % number of bootstrap samples to use +% 'numcomponents' = xxx % save first xxx components (for pca-based methods) +% 'nopcr' % for cv_lassopcr and cv_lassopcrmatlab: do not do pcr, use original variables +% 'lasso_num' = xxx % followed by number of components/vars to retain after shrinkage +% 'hvblock' = [h,v] % use hvblock cross-validation with a block size of 'h' (0 reduces to v-fold xval) and +% number of test observations 'v' (0 reduces to h-block xval) +% 'rolling' = [h,v,g] % use rolling cross-validation with a block size of 'h' (0 reduces to v-fold xval) and +% number of test observations 'v' (0 +% reduces to h-block xval), and a training size of g * 2 surrounding hv +% 'verbose' = 1 % Set to 0 to suppress output to command window +% +% Algorithm choices +% --------------------------------------------------------------------- +% You can input the name (as a string array) of any algorithm with the +% appropriate inputs and outputs. i.e., this can either be one of the +% built-in choices below, or the name of another m-file. +% The format for algorithm functions is : +% [yfit, other_outputs] = predfun(xtrain, ytrain, xtest, optional_inputs) +% Each algorithm can take/interpret its own optional inputs. +% For bootstrapping of weights, algorithms MUST RETURN 3 OUTPUTS +% (programming 'feature') +% +% To choose an algorithm, enter 'algorithm_name' followed by a text string +% with a built-in algorithm name, or a function handle for a custom algorithm +% Built-in algorithm choices include: +% cv_multregress : [default] multiple regression +% cv_univregress : Average predictions from separate univariate regression of outcome on each feature +% cv_svr : Support vector regression with Spider package; requires spider +% cv_pcr : Cross-validated principal components regression +% cv_lassopcr : Cross-val LASSO-PCR; can enter 'lasso_num' followed by components to retain by shrinkage +% NOTE: can enter 'EstimateParams' to use shrankage +% lasso method based on the estimated optimal lambda +% that minimizes the mean squared error (MSE) of nested +% cross-validation models. Output of nested cv model is +% saved in stats.other_output_cv{:,3}. Output includes +% 'Lambda' parameter and min MSE value. +% +% cv_lassopcrmatlab : Cross-val LASSO-PCR; can enter 'lasso_num' followed by components to retain by shrinkage +% NOTE: this uses the matlab implementation of LASSO, +% but can also run ridge or elastic net. Reduces to PCR +% when no lasso_num is entered by default. Use MSE for +% predicting continuous data and MCR for classifying +% binary data. +% NOTE: You can input any optional inputs that lassoglm +% takes. +% Enter 'Alpha', (0,1] as optional inputs to +% run ridge (Alpha approaches 0, but excluding 0), lasso (Alpha = 1), or elastic +% net (Alpha between 0 and 1) +% NOTE: Requires Matlab R2012a and higher. +% NOTE: Optional input: 'EstimateParams' - this will +% use grid search and nested cross validation to +% estimate Lambda and Alpha. Output is saved in +% stats.other_output_cv{:,3}. Output includes 'Alpha' +% parameter which is the elastic net mixture value +% between l1 and l2 regularization, 'Lambda' parameter, +% which is amount of LASSO regularization/shrinkage, and +% 'errorMatrix', which is the amount of error for each +% parameter combination. Use +% imagesc(obj.stats_other_output_cv{:,3}.errorMatrix) +% to view matrix. Min of this matrix is the best +% fitting parameters. +% +% +% cv_svm : Cross-val support vector machine using Spider package +% NOTE: This is sensitive to scale of outputs! Use -1 , 1 +% NOTE: Optional inputs: Slack var parameter: 'C=1' [default], 'C=0' etc. +% Distance from hyperplane saved in +% stats.other_output_cv{:,2}. Recommend using the reordered +% cross-validated distance from hyperplane saved in stats.other_output{3} +% stats.dist_from_hyperplane_xval = cross-validated distance from hyperplane +% stats.weight_obj = voxel (variable) weight object +% e.g., orthviews(stats.weight_obj) +% Intercept for calculating dist from hy is in stats.other_output_cv{:,3} +% e.g., dist_hy = stats.weight_obj.dat' * obj.dat, where obj is a new set of test images +% NOTE: To run nonlinear SVM using radial basis +% function. Add 'rbf' followed by size of sigma (e.g., 2). +% NOTE: To estimate some of the parameters using +% nested cross validation add 'EstimateParams' as optional input. +% NOTE: To run multiclass SVM (i.e., one vs rest) add +% 'MultiClass' as optional input. Important - Obj.Y must be a matrix (data x +% class) with a column of 1 and -1 indicating each +% class. For example, if using 3 classes, then obj.Y +% must have 3 columns. +% NOTE: To run a balanced SVM where the number of cases for each class are unequal (i.e., one vs rest) add +% 'Balanced' as optional input, followed by a numerical value indicating the ridge amount (e.g., 0.01). +% +% cv_multilevel_glm : Runs glmfit_multilevel. Must pass in ''subjIDs'' followed by an array specifying which subject each trial belongs to +% Subjects' trials must all be "adjacent", i.e., don't +% put some of subject 1's trials at the beginning and +% other trials at the end -- subjIDs does not handle +% this case correctly. Also, 2ND LEVEL PREDICTORS NOT +% CURRENTLY SUPPORTED. code can be expanded to support this. +% mean-centering X and/or Y will NOT impact the +% predictor betas. Note that it WILL impact the intercept +% esimate as well as how much variance is explained +% (pred_outcome_r). Stratified CV partition not +% supported either, pass in custom holdout set. +% +% +% Outputs: +% --------------------------------------------------------------------- +% Y : Copy of outcome data to be predicted +% algorithm_name : Name of algorithm; see options above +% function_call : String of the command evaluated to call the prediction function +% function_handle: Handle for the command evaluated to call the prediction function +% yfit : Predicted outcome data (cross-validated) +% err : Residuals/misclassification vector (cross-validated) +% error_type : Name of error metric used for cverr +% cverr : Cross-validated error +% nfolds : Number of folds in stratified cross-validation, or +% vector of integers for membership in custom holdout set of each fold +% -if k = 1, will estimate weights for full data object +% and not crossvalidate (useful for bootstrapping) +% cvpartition : Cross-val partition object or structure with fold info +% teIdx : Cell array of logical vectors with test samples in each fold +% trIdx : Cell array of logical vectors with training samples in each fold +% other_output : Other outputs returned by the algorithm; number and nature depend on algo choice; e.g., beta weights, svr weights, etc. +% For many algorithms, other_output{1} is a vector of +% weights on variables (e.g., voxels) +% other_output_descrip : String description of other outputs +% other_output_cv : Other outputs for each cross-validation fold +% other_output_cv_descrip: 'Other output from algorithm - for each CV fold' +% mse : For regression only; mean squared error +% rmse : For regression only; root mean squared error +% meanabserr : For regression only; mean absolute error +% pred_outcome_r : For regression only; prediction-outcome correlation +% WTS : bootstrapped weights on voxels +% weight_obj : for some algorithms, an fmri_data object with the predictive weights (from full sample) +% +% Examples: +% --------------------------------------------------------------------- +% obj = fmri_data; +% obj.dat = randn(30, 50); % 30 voxels, 50 images (observations) +% obj.Y = obj.dat' * rand(30, 1) + randn(50, 1); % toy Y, linear combo of X plus noise +% [cverr, stats, regression_outputs] = predict(obj); +% +% Simulated example with 100 observations, 1000 voxels, with bootstrapping +% dat = fmri_data; +% dat.Y = rand(100, 1); +% dat.dat = repmat(dat.Y', 1000, 1) + 10*rand(1000, 100); +% [err,stats] = predict(dat, 'bootweights', 'algorithm_name', 'cv_lassopcr'); +% +% [cverr, stats, regression_outputs] = predict(obj, 'nfolds', 3, 'error_type', 'meanabserr'); +% [cverr, stats, regression_outputs] = predict(obj, 'algorithm_name', 'cv_univregress', 'error_type', 'meanabserr'); +% [cverr, stats, optout] = predict(obj, 'algorithm_name', 'cv_lassopcr', 'lasso_num', 5, 'nfolds', 5, 'error_type', 'mse', 'bootweights'); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_svm', 'nfolds', 5, 'error_type', 'mse'); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_svm', 'rbf', 2, 'nfolds', 5, 'error_type', 'mse'); %SVM w/ radial basis function +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_svm', 'rbf', 2, 'EstimateParams', 'nfolds', 5, 'error_type', 'mse'); %SVM w/ radial basis function w/ parameters estimated using nested cross-valdiation +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_svm', 'nfolds', 5, 'MultiClass', 'error_type', 'mse'); +% +% Elastic net with first 10 components: +% [cverr, stats, optout] = predict(dat_masked, 'algorithm_name', 'cv_lassopcrmatlab', 'nfolds', 5, 'error_type', 'mse', 'numcomponents', 10, 'Alpha', .5); stats.pred_outcome_r +% +% Ridge with first 10 components: +% [cverr, stats, optout] = predict(dat_masked, 'algorithm_name', 'cv_lassopcrmatlab', 'nfolds', 5, 'error_type', 'mse', 'numcomponents', 10, 'Alpha', 0.00001); stats.pred_outcome_r +% +% Lasso with all components, but shrink to retain 2 components only: +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcrmatlab', 'nfolds', whfolds, 'nopcr', 'lasso_num', 2, 'Alpha', 1); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'nfolds', whfolds, 'lasso_num', 2); +% +% Lasso with the shrinkage methods based on the estimated optimal lambda that minimizes MSE of nested cross-validation models. +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'nfolds', whfolds, 'estimateparam'); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'nfolds', 5, 'estimateparam'); +% +% Lasso without doing PCR: +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcrmatlab', 'nfolds', whfolds, 'nopcr', 'lasso_num', 2, 'Alpha', 1); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'nfolds', whfolds, 'lasso_num', 2, 'nopcr'); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'nfolds', 5, 'estimateparam', 'nopcr'); +% +% Lasso pcr using hvblock cross-validation on time-series, h = 3, v = 5; +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'hvblock',[3,5]); +% +% Output display: +% orthviews(stats.weight_obj) +% line_plot_multisubject(stats.yfit, stats.Y, 'subjid', id_numbers); +% +% See also: +% --------------------------------------------------------------------- +% predict_test_suite method for fmri_data, which runs predict with multiple +% options and summarizes output. +% +% xval_regression_multisubject, xval_lasso_brain +% +% Original version: Copyright Tor Wager, Dec 2010 + + + + +% Programmers' notes: +% Dec 2011: Changed to input double format to algorithms; some have +% problems with single format +% +% March 2012: Changed default method to cv_pcr, and updated input option +% parsing to avoid silly mistakes in specifying algorithm. +% Changed line 377 to if rank(X) < size(sc, 1). Tested: pinv and normal inv +% give same results up to machine precision. +% +% 10/16/12: Luke Chang: Changed input to bootstrp to double because of problems with +% single format (see Tor's edits on 11/10/12) +% +% 11/2/2012: tor wager: cv_svm output added to return distance from hyperplane in stats.other_output_cv{:,2} +% +% 11/7/12: Luke Chang: added cv_lassopcrmatlab to use new matlab lasso. +% This uses coordinate descent methods compared to Least Angle Regression +% in the Rocha Lasso algorithm. This function can be used for prediction of continuous data or classification +% of binary data. For binary classification compare cverr to, Phi, or MSE in stats output. Still +% working on a good way to quantify accuracy using the predicted +% probabilities from logistic regression. Also working on methods to +% select optimal lambda for regularization. Make sure you change the Rocha +% lasso function name to 'lasso_rocha' to retain old functionality and prevent conflicts. +% +% 11/7/12: Luke Chang: added functionality to display xval iteration number +% and elapsed time +% +% 11/8/12: Luke Chang: fixed calculation of mse. Previous version calculated sum of squared error +% +% 11/10/12: Tor Wager: +% - forced double format at input processing stage +% - added optional inputs to lassoglm in cv_lassopcrmatlab +% - added functionality to use variable number of components +% - forced remove_empty on object to avoid potential problems with empty variables +% - added/improved documentation, cleaned up code structure for cv_lassopcrmatlab +% +% 11/28/12: Luke Chang: +% -fixed bug with optional inputs in cv_lassopcrmatlab - lassoglm +% didn't like empty cells in varargin +% -fixed bug with replace_empty(obj) that was returning brain +% weights that were a different size from the input*** +% -cleaned code structure for cv_lassopcrmatlab +% -added functionality to estimate lambda and alpha for +% lasspcrmatlab using grid search and nested cross validation +% +% 1/14/13: Tor Wager: SVM algorithm use updates +% - fixed bug getting distance from hyperplane with svm +% - fixed bug with input of slack var params ('C=x') in svm +% - return cross-validated distance from hyperplane in special +% output +% - added stats.weight_obj; ...and intercept values in stats.other_output_cv{:,3} +% +% 2/21/13: Luke Chang: +% - fixed bug with cv_lassopcr - now restimates betas selected +% with lasso using OLS (see hastie, friedman, tibrishani pg 92) +% - added new functionality to SVM, rbf kernel +% - added new functionality to SVM can estimate slack parameter +% (or sigma if using rbf) using nested xVal. For some reason +% this isn't working super well yet, could have something to do +% with loss function. Someone should look into this. +% +% 2/26/13: Luke Chang: +% - added new functionality to cv_svm - can now perform +% classification of multiple classes using one vs rest +% +% 3/5/13: Tor Wager: +% - lassopcr : fixed bug in lasso_num to return correct num components +% - added nopcr option to cv_lassopcr and cv_lassopcrmatlab +% - cv_lassopcrmatlab: fixed bug: lasso_num not selecting correctly +% was not re-fitting OLS solution after selecting components with lasso +% +% +% 3/6/13: Tor Wager +% - fixed minor bug introduced in last version with lassopcr/full rank +% - added stats.weight_obj, an fmri_data object with the predictive weights (from full sample) +% +% 3/8/13: Tor Wager +% - working on bootstrapping, not a full solution yet +% - rocha lasso seems to be ok, but others not (cv_pcr, matlab +% lasso) returning stable weights +% - added code to use only non-redundant components in pcr, to +% avoid warnings/instability during bootstrapping. +% +% 3/23/13: Wani Woo +% - implemented 'EstimateParams' option in rocha lasso. With this +% option, you can use the subset of coefficients that are non-zero +% predictors based on the optimal lambda estimation. The optimal +% lambda will be chosen based on the minimization of mean square +% error (MSE) of netsed cross-validation. +% - For this, cv_lassopcr now calls "lasso_cv.m" instead of "lasso_rocha.m" +% , but lasso_cv calls lasso_rocha. lasso_cv gives the results of nested +% cross-validation, which is using to select the optimal lambda, +% in addition to all the outputs that lasso_rocha gives. For this +% reason, I think it is beneficial to use lasso_cv instead of +% lasso_rocha. +% - In order to make this possible, I added a variable, +% cv_assignment, into funhan. However, only cv_lassopcr is +% actually using the variable. +% +% 6/10/13: Luke Chang +% -Added balanced_ridge option to SVM. +% -fixed some bugs with the cv_svm multiclass support +% +% 6/25/13: Luke Chang +% -Added try/catch on lassopcr. Will output nans if there is a +% problem running PCA. Should help with problems with svd +% convergence when bootsrapping. +% -turned off parallel by default for bootstrapping. memory is +% duplicated for each worker so will crash if not enough memory. +% Also bootstrp seems to preallocate. +% +% 6/29/13: Luke Chang +% -added option to save bootstrap weights 'savebootweights'. +% This is useful if you want to aggregate bootstrap samples from +% multiple iterations of predict() run at different times or +% on different computers +% +% 7/2/13: Luke Chang +% -added ability to not cross-validate by setting nfolds, k=1 +% -made a bunch of changes to bootstrapping to reduce memory +% demands and fixed bugs to output weights. +% -added nancorr to ignore nans when calculating correlation +% -added rng 'shuffle' to ensure that bootstrapping will use +% different inital seed. VERY IMPORTANT for aggregating across +% multiple boostrap sessions! +% +% 11/28/13: Luke Chang +% -added ability to use hv block cross-validation, which is good +% for timeseries data with stationary autocorrelation. +% Use 'hvblock,[h,v] +% +% 12/16/13: Luke Chang +% -added ability to use rolling block cross-validation with the +% ability to deal with autocorrelation, which is good +% for timeseries data with stationary autocorrelation. +% Use 'rolling,[h,v,g] +% 4/3/14: Luke Chang +% -fixed bug with SVM, cross-validated distance from hyper plane +% (hopefully, the distance from hyper plane is still correct) +% -fixed bug with SVM 'nfolds',1 + +% --------------------------------------------------------------------- + +% Defaults +% --------------------------------------------------------------------- + +nfolds = 5; +tsxval_hvblock = 0; +tsxval_rolling = 0; +%error_type = 'mcr'; % mcr, mse: misclassification rate or mean sq. error +algorithm_name = 'cv_pcr'; % name of m-file defining training/test function +useparallel = 'always'; % Use parallel processing, if available +bootweights = 0; % bootstrap voxel weights +verbose = 1; + +if length(unique(obj.Y)) == 2 + error_type = 'mcr'; +else + error_type = 'mse'; +end + +if size(obj.dat, 2) ~= size(obj.Y, 1) + if ~strcmp('MultiClass',varargin) %Added by LC 2/26/13 for new svm multiclass functionality + error('obj.dat must be [Predictors x observations] and obj.Y must be [observations x 1]'); + end +end + +% Mandatory conditioning of data object +% --------------------------------------------------------------------- + +%obj = remove_empty(obj); +%11/27/12: Luke Chang: This seems to be +%affecting the ability to use orthviews. This should be either removed or +%a 'replace_empty(obj) needs to be added somewhere. % TOR: orthviews should +%be able to handle this by doing replace_empty there if needed + +% force double to avoid various problems +obj.dat = double(obj.dat); +obj.Y = double(obj.Y); + +% --------------------------------------------------------------------- +% Split inputs into those that control crossval and those that +% belong to the algorithm (can be different for different +% algorithms). +% --------------------------------------------------------------------- + +predfun_inputs = {}; +bootfun_inputs = {}; % for setting number of bootstrap samples + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'nfolds', 'error_type', 'algorithm_name', 'useparallel', 'verbose'} + str = [varargin{i} ' = varargin{i + 1};']; + eval(str) + varargin{i} = []; + varargin{i + 1} = []; + + case {'cv_pcr', 'cv_multregress', 'cv_univregress', 'cv_svr', 'cv_lassopcr', 'cv_svm','cv_lassopcrmatlab'} + algorithm_name = varargin{i}; + + case {'bootweights'} + bootweights = 1; varargin{i} = []; + + case 'bootsamples' + bootfun_inputs{end+1} = 'bootsamples'; + bootfun_inputs{end+1} = varargin{i+1}; + bootweights = 1; + varargin{i} = []; + varargin{i+1} = []; + + case 'savebootweights' + bootfun_inputs{end+1} = 'savebootweights'; + bootweights = 1; + varargin{i} = []; + + case 'rolling' + tsxval_rolling = 1; + inval = varargin{i + 1}; + h = inval(1); + v = inval(2); + g = inval(3); + varargin{i} = []; + varargin{i + 1} = []; + + case 'hvblock' + tsxval_hvblock = 1; + inval = varargin{i + 1}; + h = inval(1); + v = inval(2); + varargin{i} = []; + varargin{i + 1} = []; + + otherwise + % use all other inputs as optional arguments to specific + % functions, to be interpreted by them + predfun_inputs{end + 1} = varargin{i}; + + if (i+1) <= length(varargin) && ~ischar(varargin{i + 1}) + predfun_inputs{end + 1} = varargin{i + 1}; + end + + end + end +end + +opt = statset('crossval'); +opt.UseParallel = useparallel; + +% --------------------------------------------------------------------- +% stratified partition, or custom holdout set +% --------------------------------------------------------------------- + +if numel(nfolds) > 1 % a vector; assume integers for holdout sets + % Custom holdout set + fold_indicator = nfolds; + u = unique(fold_indicator); + nfolds = length(u); + + cvpart = struct('NumTestSets', nfolds); + [trIdx, teIdx] = deal(cell(1, nfolds)); + + for i = 1:length(u) + teIdx{i} = fold_indicator == u(i); + trIdx{i} = ~teIdx{i}; + end + +elseif nfolds == 1 % special for 1 fold: use all obs for train and test; good for bootstrapping weights + [trIdx, teIdx] = deal(cell(1, nfolds)); + trIdx={ones(size(obj.Y))}; + teIdx={ones(size(obj.Y))}; + cvpart = struct('NumTestSets', length(trIdx)); %LC: 4/3/14: added this as it was missing + +elseif tsxval_hvblock == 1 %special case for timeseries CV using HVBlock : added LC: 11/28/13 + [trIdx, teIdx] = tscv(length(obj.Y), 'hvblock',[h,v]); + cvpart = struct('NumTestSets', length(trIdx)); + +elseif tsxval_rolling == 1 %special case for timeseries CV using HVBlock : added LC: 12/16/13 + [trIdx, teIdx] = tscv(length(obj.Y), 'rolling',[h,v,g]); + cvpart = struct('NumTestSets', length(trIdx)); + +else + + [trIdx, teIdx] = deal(cell(1, nfolds)); + + % Classification: Stratified holdout set + if length(unique(obj.Y)) < length(obj.Y) / 2 + + %cvpart = cvpartition(obj.Y,'k',nfolds); %changed by LC 2/26/13 to account for multiclass svm + cvpart = cvpartition(length(obj.Y),'k',nfolds); + + for i = 1:cvpart.NumTestSets + + trIdx{i} = cvpart.training(i); + teIdx{i} = cvpart.test(i); + + end + + else + % Regression: custom stratification + % cvpartition object will not stratify continuous values + % do our own + + cvpart = struct('NumTestSets', nfolds); + + [ys, indx] = sort(obj.Y); + for k = 1:nfolds + + wh_holdout = indx(k:nfolds:end); + if isempty(wh_holdout), error('Holdout set construction error: Dataset too small?'); end + + teIdx{k} = false(size(obj.Y)); + teIdx{k}(wh_holdout) = true; + + trIdx{k} = ~teIdx{k}; + end + end +end + +if verbose + fprintf('Cross-validated prediction with algorithm %s, %3.0f folds\n', algorithm_name, nfolds) +end + +% special for 1 fold: use all obs for train and test; good for +% bootstrapping weights +if nfolds == 1 + %trIdx{1} = teIdx{1}; %LC: this isn't actualy using all of the weights. + % 3/23/13 Wani Woo added the following three lines to get cv_assignment. See Programmers' note for the detail. + cv_assignment = double(teIdx{1}); +else + cv_assignment = sum((cat(2, teIdx{:}).*repmat((1:size(cat(2, teIdx{:}), 2)), size(cat(2, teIdx{:}), 1), 1))')'; +end + +% --------------------------------------------------------------------- +% build function handle, given custom inputs to function +% --------------------------------------------------------------------- + +% 3/23/13 Wani Woo added cv_assignment in funstr to feed this variable to cv_lassopcr +funstr = ['@(xtrain, ytrain, xtest, cv_assignment) ' algorithm_name '(xtrain, ytrain, xtest, cv_assignment, predfun_inputs{:})']; + +eval(['funhan = ' funstr ';']) + + +% --------------------------------------------------------------------- +% Cross-validated prediction and error +% --------------------------------------------------------------------- + +% Use this loop instead of the crossval function +% it's not complicated and allows more control. +% yfit = crossval(funhan, obj.dat',obj.Y, 'Partition', cvpart, 'Options', opt); + +yfit = zeros(size(obj.Y)); + +noptout = nargout(algorithm_name) - 1; +optout = cell(1, noptout); + +t1 = clock; + +% Fit on all data - apparent loss, optional outputs +[yfit_all, optout{:}] = funhan(obj.dat', obj.Y, obj.dat', cv_assignment); +% 3/23/13 Wani Woo added cv_assignment. + +if verbose + %fprintf(1,'\n_______________________________\n') + [hour, minute, second] = sec2hms(etime(clock,t1)); + fprintf(1,'\nCompleted fit for all data in: %3.0f hours %3.0f min %2.0f secs \n',hour,minute,second); +end + +% --------------------------------------------------------------------- +% Cross-validated prediction and error +% --------------------------------------------------------------------- + +% nworkers = matlabpool('size'); + +% if nworkers +% % slice +% for i = 1:cvpart.NumTestSets +% dat_tmp{i} = obj.dat(:, trIdx{i})'; +% end +% +% [yfit_tmp, cvtmp] = deal(cell(1, cvpart.NumTestSets)); +% +% parfor i = 1:cvpart.NumTestSets +% +% [yfit_tmp{i}, cvtmp{i}] = funhan(dat_tmp{i}, obj.Y(trIdx{i},:), obj.dat(:, teIdx{i})'); +% +% end +% +% % reassemble +% for i = 1:cvpart.NumTestSets +% yfit(teIdx{i}) = yfit_tmp{i}; +% cv_optout{i, :} = cvtmp{i}; +% end +% clear yfit_tmp dat_tmp cvtmp +% +% else + +if nfolds ~= 1 %skip if using nfolds 1, added 7/2/13 by LC + cv_optout = cell(cvpart.NumTestSets, noptout); + for i = 1:cvpart.NumTestSets + + % 3/23/13 Wani Woo added the following three lines to get a proper cv_assignment variable for cross-validation. + teIdx_cv = teIdx(1:end ~= i); + cv_assignment = sum((cat(2, teIdx_cv{:}).*repmat((1:size(cat(2, teIdx_cv{:}), 2)), size(cat(2, teIdx_cv{:}), 1), 1))')'; + cv_assignment(cv_assignment == 0) = []; + + % 9/8/13 Yoni Ashar added to pass correct subjIDs to each fold + if any(strcmp('subjIDs', predfun_inputs)) + subjIDs_index = 1 + find(strcmp('subjIDs', predfun_inputs)); + if ~exist('subjIDsFull', 'var') %first time through, save the full array + subjIDsFull = predfun_inputs{subjIDs_index}; + end + %overwrite subjIDs with appropriate subset of subjIDs + predfun_inputs{subjIDs_index} = subjIDsFull(trIdx{i}); + eval(['funhan = ' funstr ';']) % "reload" predfun_inputs + end + + t2 = clock; + + % [yfit(teIdx{i},:), cv_optout{i, :}] = funhan(obj.dat(:, trIdx{i})', obj.Y(trIdx{i},:), obj.dat(:, teIdx{i})'); + [yfit(teIdx{i},:), cv_optout{i, :}] = funhan(obj.dat(:, trIdx{i})', obj.Y(trIdx{i},:), obj.dat(:, teIdx{i})', cv_assignment); %changed by lc on 2/26/13 to add svm multiclass support + + % 3/23/13 Wani Woo added cv_assignment. + if verbose + [hour, minute, second] = sec2hms(etime(clock,t2)); + fprintf(1,['Fold ' num2str(i) '/' num2str(cvpart.NumTestSets) ' done in: %3.0f hours %3.0f min %2.0f sec\n'],hour,minute,second); + end + end + if verbose + [hour, minute, second] = sec2hms(etime(clock,t1)); + fprintf(1,'\nTotal Elapsed Time = %3.0f hours %3.0f min %2.0f sec\n',hour, minute, second); + end +else %added 7/213/ by lc for adding no cv option + yfit = yfit_all; + % cvpart = []; + cv_optout = []; +end +% end + +% --------------------------------------------------------------------- +% Get error +% --------------------------------------------------------------------- + +switch error_type + case {'mcr', 'class_loss'} + %err = obj.Y ~= yfit; + err = obj.Y ~= round(yfit); %10/7/12: Luke Chang: this will allow mcr to also be calculated using predicted probabilities + + %cverr = sum(err) ./ length(err); + cverr = sum(err) ./ length(err); + + phi = corr(obj.Y, yfit); %10/7/12: Luke Chang: this will calculate phi correlation coefficient between two binary variables + + case {'mse' 'rmse', 'meanabserr'} + err = obj.Y - yfit; + + %mse = mean(err' * err); %10/8/12: Luke Chang: I think this is only capturing sum of squared error + mse = (err' * err)/length(err); %This should be correct calculation of mean squared error + rmse = sqrt(mse); + meanabserr = nanmean(abs(err)); %if you are getting strange error here make sure yo are using matlab default nanmean (e.g., which nanmean) + r = corrcoef(obj.Y, yfit, 'rows', 'pairwise'); + r = r(1, 2); + + eval(['cverr = ' error_type ';']); + + otherwise + error('Illegal loss function type') +end + + +% --------------------------------------------------------------------- +% collect output +% --------------------------------------------------------------------- +stats = struct('Y', obj.Y, 'algorithm_name', algorithm_name, ... + 'function_call', funstr, 'function_handle', funhan, ... + 'yfit', yfit, 'err', err, 'error_type', error_type, 'cverr', cverr, ... + 'nfolds', 'nfolds', 'cvpartition', cvpart); + +stats.teIdx = teIdx; +stats.trIdx = trIdx; + +stats.other_output = optout; +stats.other_output_descrip = 'Other output from algorithm - trained on all data (these depend on algorithm)'; +stats.other_output_cv = cv_optout; +stats.other_output_cv_descrip = 'Other output from algorithm - for each CV fold'; + +%LC: 4/3/14: Removed this as it is causing errors and seems redundant with line 732. +% %For SVM reorder distance from hyperplane: 12/6/12: Luke Chang: not very elegant solution +% if strcmp(algorithm_name,'cv_svm') +% stats.other_output_cv{3}=ones(length(stats.Y),1); +% for i = 1:cvpart.NumTestSets +% stats.other_output{3}(find(teIdx{i}==1)) = stats.other_output_cv{i,2}; +% end +% end + +switch error_type + case {'mcr', 'class_loss'} + + stats.phi = phi; + + if strcmp(algorithm_name,'cv_lassopcrmatlab') + stats.me = mean(abs(obj.Y-yfit)); %calculate average distance from probability and outcome - should be more sensitive measure of accuracy than mcr for probabilities + end + + case {'mse' 'rmse', 'meanabserr'} + + stats.mse = mse; + stats.rmse = rmse; + stats.meanabserr = meanabserr; + stats.pred_outcome_r = r; +end + +% --------------------------------------------------------------------- +% Special output +% --------------------------------------------------------------------- +switch algorithm_name + + case 'cv_svm' + % cross-validated distance from hyperplane + if nfolds ~= 1 + dd = zeros(size(obj.Y)); + + for i = 1:length(teIdx) + dd(teIdx{i},:) = stats.other_output_cv{i, 2}; %added by lc 6/10/13 to accomodate multiclass + end + + stats.dist_from_hyperplane_xval = dd; + end + +end + +if length(stats.other_output{1}) == size(obj.dat, 1) + % other_output{1} is right size for weight vector + % assume it is and return weight_obj + w = mean(obj); + w.dat = stats.other_output{1}; + stats.weight_obj = w; +end + + +% --------------------------------------------------------------------- +% Bootstrap weights, if asked for +% --------------------------------------------------------------------- + +if nfolds == 1 + %trIdx{1} = teIdx{1}; + % 3/23/13 Wani Woo added the following three lines to get cv_assignment. See Programmers' note for the detail. + cv_assignment = double(teIdx{1}); +else + cv_assignment = sum((cat(2, teIdx{:}).*repmat((1:size(cat(2, teIdx{:}), 2)), size(cat(2, teIdx{:}), 1), 1))')'; +end + +if bootweights + stats.WTS = boot_weights(funhan, obj, cv_assignment, bootfun_inputs{:}); + fprintf('Returning weights and Z, p values in stats.WTS\n') +end + + +end % main function + + + + +% --------------------------------------------------------------------- +% --------------------------------------------------------------------- + +% ALGORITHMS + +% --------------------------------------------------------------------- +% --------------------------------------------------------------------- + + +function [yfit, b, intercept] = cv_multregress(xtrain, ytrain, xtest, cv_assignment, varargin) + +b = glmfit(xtrain, ytrain); % Note: many times faster than inv(X'*X)X' for large k +yfit = b(1) + xtest * b(2:end); + +intercept = b(1); +b = b(2:end); + +end + +% ----------------------------- algorithms ------------------------------- + +function [yfit, b, intercept] = cv_multilevel_glm(xtrain, ytrain, xtest, cv_assignment, varargin) + +disp('2nd LEVEL PREDICTORS NOT CURRENTLY SUPPORTED') + +i=find(strcmp('subjIDs', varargin)); +if numel(i) ~= 1 + error('When using multilevel glm, must pass in ''subjIDs'' followed by an array specifying which subject every trial belongs to') +end +subjIDs = varargin{i+1}; + +[Xml, Yml] = deal(cell(size(unique(subjIDs)))); + +diffs = diff(subjIDs); + +% build cell array of X and Y, using a varargin input identifying subjects. +count = 1; +for i=1:numel(ytrain) + Xml{count}(end+1,:) = xtrain(i,:); + Yml{count}(end+1,:) = ytrain(i); + + if i~=numel(ytrain) && diffs(i) >= 1, count = count+1; end % on to a new subject. all of a subject's trials must be adjacent to each other. +end + +% call ml glm +stats = glmfit_multilevel(Yml, Xml, [], varargin{:}); + +% test on xtest +intercept = stats.beta(1); +b = stats.beta(2:end)'; +yfit = intercept + xtest * b; + +end + +% ----------------------------- algorithms ------------------------------- + +function [yfit, vox_weights, intercept] = cv_pcr(xtrain, ytrain, xtest, cv_assignment, varargin) + +[pc, sc, eigval] = princomp(xtrain, 'econ'); + +% Choose number of components to save [optional] +wh = find(strcmp(varargin, 'numcomponents')); +if ~isempty(wh) && length(varargin) >= wh + 1 + + numc = varargin{wh + 1}; + + if numc > size(pc, 2) + disp('WARNING!! Number of components requested is more than unique components in training data.'); + numc = size(pc, 2); + end + pc = pc(:, 1:numc); +end + +sc = xtrain * pc; + +numcomps = rank(sc); + +% 3/8/13: TW: edited to use numcomps, because sc is not always full rank during bootstrapping +X = [ones(size(sc, 1), 1) sc(:, 1:numcomps)]; + +if rank(X) <= size(sc, 1) + b = pinv(X) * ytrain; % use pinv to stabilize; not full rank + % this will only happen when bootstrapping or otherwise when there are + % redundant rows +else + b = inv(X'*X)*X'*ytrain; +end + +% Programmers' notes: (tor) +% These all give the same answer for full-rank design (full component) +% X = [ones(size(sc, 1), 1) sc]; +% b1 = inv(X'*X)*X'*ytrain; +% b2 = pinv(X)*ytrain; +% b3 = glmfit(sc, ytrain); +% tic, for i = 1:1000, b1 = inv(X'*X)*X'*ytrain; end, toc +% tic, for i = 1:1000, b2 = pinv(X)*ytrain; end, toc +% tic, for i = 1:1000, b3 = glmfit(sc, ytrain); end, toc +% b1 is 6 x faster than b2, which is 2 x faster than b3 + +vox_weights = pc(:, 1:numcomps) * b(2:end); + +intercept = b(1); + +yfit = intercept + xtest * vox_weights; + +end + +% ----------------------------- algorithms ------------------------------- + + +function [yfit, vox_weights, intercept, out_cv] = cv_lassopcr(xtrain, ytrain, xtest, cv_assignment, varargin) % 3/23/13 Wani Woo added out_cv. + +doSkip = 0; %skip predict and output null \\ +dopcr = 1; +doNestedXval = 0; % 3/23/13 added by Wani Woo. + +wh = find(strcmp(varargin, 'nopcr')); +if ~isempty(wh), dopcr = 0; end + +wh = find(strcmpi(varargin, 'estimateparams')); % 3/23/13 added by Wani Woo. +if ~isempty(wh), doNestedXval = 1; end + +if dopcr + try %catch added 7/2/13 by LC + [pc, sc, eigval] = princomp(xtrain, 'econ'); + catch exception + sprintf(['ERROR! ' exception.message '\nSkipping lassoPCR']) + doSkip = 1; %skip predict and output null (useful for bootstrapping will ignore problems with convergence) + sc = xtrain; + end + + if ~doSkip %skip if problem with pca + % Choose number of components to save [optional] + wh = find(strcmp(varargin, 'numcomponents')); + if ~isempty(wh) && length(varargin) >= wh + 1 + + numc = varargin{wh + 1}; + + if numc > size(pc, 2) + disp('WARNING!! Number of components requested is more than unique components in training data.'); + numc = size(pc, 2); + end + pc = pc(:, 1:numc); + end + + sc = xtrain * pc; + end + +else + sc = xtrain; +end + + +% 3/14/13: tor changed because full rank was still returning unstable +% matrices (non-invertible) sometimes. +numcomps = rank(sc); +numcomps = rank(sc, .001); +%numcomps = rank(sc, mean(abs(sc(:))) ./ 10000); + +%X = [ones(size(sc, 1), 1) sc(:, 1:numcomps)]; + +%11/7/12: LC: Renamed to avoid conflict with matlab function +% 3/8/13: TW: edited to use numcomps, because sc is not always full rank during bootstrapping +% 3/23/13: Wani: changed lasso_rocha to lasso_cv. Using options, now we can +% actually reduce the features. + +options.alignment = 'lambda'; +options.cv_assignment = cv_assignment; + +if ~doSkip + + % 3/23/13: Wani: add dopcr option. + if dopcr + if doNestedXval, out = lasso_cv(ytrain, sc(:, 1:numcomps), length(unique(cv_assignment)), options); + else out = lasso_rocha(ytrain, sc(:, 1:numcomps)); end + else + if doNestedXval, out = lasso_cv(ytrain, sc, length(unique(cv_assignment)), options); + else out = lasso_rocha(ytrain, sc); end + end + % out = lasso_rocha(ytrain, sc(:, 1:numcomps)); + + n = size(out.beta, 1) - 1; + wh = find(strcmp(varargin, 'lasso_num')); + + if ~isempty(wh) && length(varargin) >= wh + 1 + n = varargin{wh + 1}; + if n > size(out.beta, 1) + error('You asked for more components/vars than there are in the dataset.') + end + elseif ~isempty(wh) + error('Follow lasso_num input with number of vars to keep'); + end + + %output original lasso parameters - removed 2/20/13 by LC + % b = [out.intercept(n); out.beta(n, :)']; + % vox_weights = pc * b(2:end); + % intercept = b(1); + % yfit = intercept + xtest * vox_weights; + + %refit lasso selected betas using OLS (see hastie, Friedman, Tibrishani, pg92 - added 2/20/13 by LC + % use n + 1 because 1st row has no non-zero betas + % 3/23/13: Wani: Actually out.beta(n+1,:) at a default setting (i.e., last + % set of betas) is a full model. To use a reduced model, we + % need to first estimate the optimal lambda that minimizes + % the MSE of cross-validation models. + + if ~doNestedXval + wh_beta = logical([out.intercept(n+1); out.beta(n+1, :)' ~= 0]); + out_cv = []; + else + wh_beta = logical([out.CV.intercept; out.CV.beta' ~= 0]); + out_cv.lambda = out.CV.lambda; + out_cv.min_MSE = out.CV.min_MSE; + end + + Xsubset = [ones(size(sc, 1), 1) sc(:, wh_beta(2:end))]; + betatmp = pinv(Xsubset) * ytrain; + betas = zeros(size(wh_beta)); + betas(wh_beta) = betatmp; + + if dopcr + vox_weights = pc(:, 1:numcomps) * betas(2:end); + else + vox_weights = betas(2:end); + end + + intercept = betas(1); + yfit = intercept + xtest * vox_weights; +else + yfit = nan(size(ytrain)); + vox_weights = nan(size(xtest,2),1); + intercept = nan; + out_cv = []; + +end + +end + +% ----------------------------- algorithms ------------------------------- + +function [yfit, vox_weights, intercept, out] = cv_lassopcrmatlab(xtrain, ytrain, xtest, cv_assignment, varargin) +% Notes: +% Added 11/7/12 by Luke Chang +% This will only run on Matlab 2012 or newer (i.e., 7.14.0) when the lasso function was implemented +% Lasso regularization %standardizes x & y by default +% This code can also be used to run ridge or elastic net regression by setting alpha to - 0 = ridge; 1 = lasso; anything else = elastic net +% This code can do prediction or classification. Important to note that weights in classification are in log-odds +% Can estimate optimal lambda or alpha through nested cross validation +% Should add functionality to select lambda, alpha, or both. + + +% --------------------------------------------------------------------- +% Defaults +% --------------------------------------------------------------------- + +alpha = 1; %indicates type of regularization to run for cv_lassopcrmatlab. runs lasso by default +n = 1; %Set Lasso_num +chooseComponents = 0; %indicate whether to choose a custom number of components +chooseLassoNum = 0; %indicate whether to choose a custom number of lasso numbers +doNestedXval = 0; %use nested cross-validation to pick lambda +estAlpha = 0; +estLambda = 0; +dopcr = 1; % in case we do not want to use PCR + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'nopcr' + dopcr = 0; % in case we do not want to use PCR + varargin{i} = []; + + case 'numcomponents' + varargin{i} = []; + numc = varargin{i + 1}; + if isempty(numc) || ~isnumeric(numc) + error('Follow numcomponents with number of components to keep'); + end + varargin{i+1} = []; + chooseComponents = 1; + case 'lasso_num' + varargin{i} = []; + lassonum = varargin{i + 1}; + if isempty(lassonum) || ~isnumeric(lassonum) + error('Follow lasso_num input with number of vars to keep'); + end + varargin{i+1} = []; + chooseLassoNum = 1; + case 'Alpha' + varargin{i} = []; + alpha = varargin{i+1}; + if isempty(alpha) || ~isnumeric(alpha) + error('Follow Alpha input with elastic net mixing parameter'); + end + varargin{i+1} = []; + case 'EstimateParams' + %Need to add functionality to selectively estimate alpha and/or lambda + varargin{i} = []; + params = varargin{i+1}; + varargin{i+1} = []; + if max(strcmp(params,'Alpha') | strcmp(params,'alpha')) + estAlpha = 1; + end + if max(strcmp(params,'Lambda') | strcmp(params,'lambda')) + estLambda = 1; + end + doNestedXval = 1; + end + end +end + +% check if using correct version of matlab +% ---------------------------------------------------------- + +if verLessThan('matlab', '7.14.0') + error('''cv_lassopcrmatlab'' is only compatible with matlab 7.14.0 and newer, try using old version ''cv_lassocvr'''); +end + +% determine if running prediction (continous) or classification (binary) +% ---------------------------------------------------------- + +if length(unique(ytrain)) == 2 %run in classification mode + fprintf(1, 'Classification mode (binomial) '); + runmode = 'classification'; + distribution = 'binomial'; + varargin{end + 1} = 'Link'; + varargin{end + 1} = 'logit'; + + + %check if y is in correct format + if length(unique(ytrain)) ~= 2 % tor changed this + error('Classification mode requires binary y with values of 1 or 0'); + end + + % enforce logical in case of [1, -1] inputs + ytrain = ytrain > 0; + +else %Run in prediction mode + runmode = 'regression'; + fprintf(1,'Regression mode (Gaussian) '); + distribution = 'normal'; + +end + +% Principal components +% ---------------------------------------------------------- +if dopcr + + [pc, sc, eigval] = princomp(xtrain, 'econ'); %econ is much faster and should be used when n <= p and returns n-1 components. additional components are meaningless + + % [optional] Choose number of components to save + if chooseComponents + if numc > size(pc, 2) + disp('WARNING!! Number of components requested is more than unique components in training data.'); + numc = size(pc, 2); + end + pc = pc(:, 1:numc); + end + + % 3/8/13: TW: edited to use numcomps, because sc is not always full rank during bootstrapping + numcomps = rank(pc); + + sc = xtrain * pc(:, 1:numcomps); %this is slightly different from princomp output due to the automatic centering + +else + pc = xtrain; + sc = xtrain; + numcomps = size(sc, 2); +end + +% Regression (penalized, normal or logistic) +% ---------------------------------------------------------- + + +% lassoglm will accept a number of optional inputs, which you can put +% in as optional inputs to predict.m. We need to eliminate invalid +% inputs earlier, however, or lassoglm will return an error. + +varargin(cellfun(@isempty,varargin)) = []; %clear empty cells to prevent errors in lasso + +if ~doNestedXval + + [b, stats] = lassoglm(sc, ytrain, distribution, 'Alpha', alpha, varargin{:}); + + % [Optional] Process inputs on desired penalization + % ---------------------------------------------------------- + %This allows you to select a lambda performs identically to the original + %rocha lassopcr (i.e., lambda decreases as numbers increase). Eventually + %as lambda approaches zero you will retain all original components. + + % n = stats.IndexMinDeviance; %this is the optimal lambda from nested cross validation + + if chooseLassoNum + n_in_model = size(sc, 2) - sum(b == 0); % how many variables have non-zero b + + n = min(find(n_in_model == lassonum)); % lowest lambda (least shrinkage) with lassonum components retained + + % sometimes this may not exist, depending on sampling res of b (100 steps by default...) + % if empty, find the next closest one by relaxing lambda a bit. + indx = 1; + while isempty(n) + n = min(find(n_in_model == lassonum + indx)); + indx = indx + 1; + end + + %n = 1 + size(pc, 2) - lassonum; % # betas, including intercept + if n < 1 + disp('WARNING!! Number of LASSO components retained is more than unique components in training data.'); + n = 1; + end + end + + % Collect outputs + % ---------------------------------------------------------- + + beta = b(:, n); + + % re-fit using OLS for non-zero components + wh = beta ~= 0; + bb = pinv([ones(size(ytrain)) sc(:, wh)]) * ytrain; + beta(wh) = bb(2:end); + + if dopcr + vox_weights = pc * beta; + else + vox_weights = beta; + end + + intercept = bb(1); %stats.Intercept(n); % after OLS re-fitting + out.alpha = stats.Alpha; + out.lambda = stats.Lambda(n); + + switch runmode + case 'classification' + yfit = intercept + xtest * vox_weights; + yfit = exp(yfit)./(1+exp(yfit)); %convert to probabilities + case 'regression' + yfit = intercept + xtest * vox_weights; + otherwise error('this should never happen.') + end + +else + %%Use nested xvalidation and grid search to find optimal lambda and alpha parameters + % NOTE: THIS COULD PROBABLY BE DONE USING LASSOGLM'S INTERNAL + % CROSS-VALIDATION + + %Set Initial Parameters + gridResolution = 20; + nFold = 5; + + %Create Parameter search grid + a = linspace(eps,1,gridResolution); %can't be 0 + l = linspace(eps,1,gridResolution); + + %set up nested xValidation - implement Tor's stratified code + [trIdx, teIdx] = deal(cell(1, nFold)); + + % Classification: Stratified holdout set + if length(unique(ytrain)) < length(ytrain) / 2 + + cvpart = cvpartition(ytrain,'k',nFold); + + for i = 1:cvpart.NumTestSets + trIdx{i} = cvpart.training(i); + teIdx{i} = cvpart.test(i); + end + + else + % Regression: custom stratification + % cvpartition object will not stratify continuous values + % do our own + + cvpart = struct('NumTestSets', nFold); + + [ys, indx] = sort(ytrain); + + for k = 1:nFold + wh_holdout = indx(k:nFold:end); + + if isempty(wh_holdout), error('Holdout set construction error: Dataset too small?'); end + + teIdx{k} = false(size(ytrain)); + teIdx{k}(wh_holdout) = true; + + trIdx{k} = ~teIdx{k}; + end + end + beta = cell(length(a),length(l),nFold); + stats = cell(length(a),length(l),nFold); + yfit = cell(length(a),length(l)); + for j = 1:(length(a)) + for k = 1:(length(l)) + yfit{j,k} = ones(length(ytrain),1); + end + end + + %Run nested xValidation + for i = 1:nFold + for j = 1:length(a) + for k = 1:length(l) + [beta{j,k,i}, stats{j,k,i}] = lassoglm(sc(trIdx{i},:), ytrain(trIdx{i}), distribution, 'Alpha', a(j), 'Lambda', l(k), varargin{:}); + vox_weights = pc * beta{j,k,i}; + intercept = stats{j,k,i}.Intercept; + switch runmode + case 'classification' + yfit{j,k}(teIdx{i}) = intercept + xtrain(teIdx{i},:) * vox_weights; %test fit on nested xval + yfit{j,k}(teIdx{i}) = exp(yfit{j,k}(teIdx{i}))./(1+exp(yfit{j,k}(teIdx{i}))); %convert to probabilities + case 'regression' + yfit{j,k}(teIdx{i}) = intercept + xtrain(teIdx{i},:) * vox_weights; + otherwise error('this should never happen.') + end + end + end + end + + %Create matrix of error to select optimal parameters + err = zeros(length(a),length(l)); + for j = 1:length(a) + for k = 1:length(l) + switch runmode + case 'classification' + %err(j,k) = sum(ytrain ~= round(yfit{j,k})) ./ length(ytrain); %classification error + err(j,k) = mean(abs(ytrain-yfit{j,k})); %calculate average distance from probability and outcome - should be more sensitive measure of accuracy than mcr for probabilities + case 'regression' + %err(j,k) = ((ytrain - yfit{j,k})' * (ytrain - yfit{j,k}))/length(ytrain - yfit{j,k}); %average sum of squared error + err(j,k) = median(abs(ytrain - yfit{j,k})); %median absolute deviation - should be more robust to outliers + end + end + end + + %find min of grid - if more than one select randomly from set. + %Alternatives for multiple minima: 1) smooth err matrix; 2) select + %minima that retains the most pc components. + clear jj kk + [jj,kk] = ind2sub(size(err), find(err == min(min(err)))); + if length(jj) > 1 + s = datasample(jj,1,'Replace',false); + jj = jj(s); + kk = kk(s); + end + + %Rerun lasso with cross-validated parameters + [b, stats] = lassoglm(sc, ytrain, distribution, 'Alpha', a(jj), 'Lambda', l(kk), varargin{:}); + + % Collect outputs + % ---------------------------------------------------------- + + beta = b(:, n); + vox_weights = pc(:, 1:numcomps) * beta; + intercept = stats.Intercept(n); + out.alpha = stats.Alpha; + out.lambda = stats.Lambda; + out.gridResolution = gridResolution; + out.errorMatrix = err; + + switch runmode + case 'classification' + yfit = intercept + xtest * vox_weights; + yfit = exp(yfit)./(1+exp(yfit)); %convert to probabilities + case 'regression' + yfit = intercept + xtest * vox_weights; + otherwise error('this should never happen.') + end +end + +end % algorithm + +% ----------------------------- algorithms ------------------------------- + + +function [yfit, b, yfit_vox] = cv_univregress(xtrain, ytrain, xtest, cv_assignment, varargin) +% A simple strategy that treats voxels as independent and aggregates +% univariate predictions across all voxels. +% Could be a good strategy of independent, redundant info is contained in +% input variables and there is little covariance across measures. + +[n, v] = size(xtrain); + +b = zeros(2, v); +onesvec = ones(n, 1); + +% Training +fprintf('Training...') +tic +for i = 1:v + + % X is brain, Y is outcome, for each voxel + X = [onesvec xtrain(:, i)]; + + %b = pinv(X) * Y + b(:, i) = X \ ytrain; % should be same, but faster + +end +toc + +% Testing +fprintf('Testing...') +nt = size(xtest, 1); +yfit_vox = zeros(nt, v); + +for i = 1:nt + yfit_vox(i, :) = b(1, :) + xtest(i, :) * b(2, :)'; +end +fprintf('Done. \n'); + +% Integrate separate models into one +yfit = mean(yfit_vox, 2); + +b = b'; + +end % function + +% ----------------------------- algorithms ------------------------------- + + +function [yfit, w, dummy] = cv_svr(xtrain, ytrain, xtest, cv_assignment, varargin) + +dataobj = data('spider data', xtrain, ytrain); + +% Define algorithm +%svrobj = svr({'C=1', 'optimizer="andre"', kernel({'rbf', 1})}) + +% slack parameter +slackstr = 'C=1'; +wh = find(strcmpi('C=', varargin)); +if ~isempty(wh), slackstr = varargin{wh(1)}; end + +svrobj = svr({slackstr, 'optimizer="andre"'}); + +% Training +fprintf('Training...') +t1 = tic; +[res, svrobj] = train(svrobj, dataobj); +w = get_w(svrobj); +t2 = toc(t1); +fprintf('Done in %3.2f sec\n', t2) + +% Testing +fprintf('Testing...') +res2 = test(svrobj, data('test data', xtest, [])); +yfit = res2.X; % this is proportional to res.X*w (perfectly correlated), but scale is different +fprintf('\n'); +% vec = [res.X res.Y dataobj.X * w' res2.X] +% corrcoef(vec) + +w = w'; +dummy = 0; + +end % function + +% ----------------------------- algorithms ------------------------------- +%This function has been replaced by a new one that can also run RBF - 2/21/13 LC + +% function [yfit, w, dist_from_hyplane, b0] = cv_svm(xtrain, ytrain, xtest, varargin) +% +% dataobj = data('spider data', xtrain, ytrain); +% +% % Define algorithm +% %svrobj = svr({'C=1', 'optimizer="andre"', kernel({'rbf', 1})}) +% +% % slack parameter +% slackstr = 'C=1'; +% wh = find(strncmpi('C=', varargin, 2)); +% if ~isempty(wh), slackstr = varargin{wh(1)}; end +% +% svrobj = svm({slackstr, 'optimizer="andre"'}); +% +% % Training +% fprintf('Training...') +% t1 = tic; +% [res, svrobj] = train(svrobj, dataobj); +% w = get_w(svrobj); +% t2 = toc(t1); +% fprintf('Done in %3.2f sec\n', t2) +% +% % Testing +% fprintf('Testing...') +% res2 = test(svrobj, data('test data', xtest, [])); +% yfit = res2.X; % this is proportional to res.X*w (perfectly correlated), but scale is different +% fprintf('\n'); +% % vec = [res.X res.Y dataobj.X * w' res2.X] +% % corrcoef(vec) +% +% w = w'; +% b0 = svrobj.b0; +% +% dist_from_hyplane = xtest * w + b0; +% +% %dummy = 0; +% +% end % function + +% ----------------------------- algorithms ------------------------------- +%NOTES: +%what parameters does rbf need? +%figure out how to estimate parameters, does spider have a xval gridsearch +%option? +% + + +function [yfit, w, dist_from_hyplane, intercept, svmpar] = cv_svm(xtrain, ytrain, xtest, cv_assignment, varargin) +%NOTES: +%-Estimate Parameters is working correctly, but doesn't seem like there is +%much variance in the loss function, which is resulting in the xVal +%procedure selecting the smallest possible values. One thing to look into +%is the loss function being used. +%-Add multiclass option - make sure Y can be bigger than one column in xval +%loop + +% ---------------------------------------------------------- +% Defaults +% ---------------------------------------------------------- + +slackstr = 'C=1'; %slack parameter +dorbf = 0; +doNestedXval = 0; %nested cross validation +doMultiClass = 0; %multiclass classification +doBalanced = 0; %balanced using ridge + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'C=' %slack parameter + slackstr = varargin{i}; + varargin{i} = []; + % slack parameter + % wh = find(strcmpi('C=', varargin)); + % if ~isempty(wh), slackstr = varargin{wh(1)}; end + case 'rbf' %Use radial basis function as kernel + varargin{i} = []; + sig = varargin{i+1}; + varargin{i+1} = []; + if isempty(sig) || ~isnumeric(sig) + error('Follow RBF input with sigma'); + end + dorbf = 1; + case 'EstimateParams' %Estimate parameters using nested x val + varargin{i} = []; + doNestedXval = 1; + % train 3 svms with C=1,2,3 and validate with 3 fold cross validation + %[r,a]=train(gridsel(param(svm,'C',[1,2,3]),{'score=cv;score.folds=3'}),gen(toy)) ; + case 'MultiClass' + varargin{i} = []; + doMultiClass = 1; + case 'Balanced' + varargin{i} = []; + ridgeAmt = varargin{i+1}; + varargin{i+1} = []; + if isempty(ridgeAmt) || ~isnumeric(ridgeAmt) + error('Follow Balanced input with ridge amount (numeric)'); + end + doBalanced = 1; + end + end +end + +dataobj = data('spider data', xtrain, ytrain); + +% ---------------------------------------------------------- +% Define Algorithm +% ---------------------------------------------------------- + +if doNestedXval %Estimate parameters via xVal + + fprintf('Estimating Parameters via Nested Cross Validation...') + t1 = tic; + + if ~dorbf %only estimate C parameter + + %Set Initial Parameters + gridResolution = 10; + nFold = 3; + paramGrid = linspace(eps,5,gridResolution); %Create Parameter search grid + + if ~doMultiClass + [r,a] = train(gridsel(param(svm('optimizer="andre"'),'C',paramGrid),{['score=cv;score.folds=' num2str(nFold)]}),dataobj); + else + [r,a] = train(gridsel(param(one_vs_rest(svm('optimizer="andre"')),'C',paramGrid),{['score=cv;score.folds=' num2str(nFold)]}),dataobj); + end + + slackstr = ['C=' num2str(a.C)]; %slack parameter + + else %estimate C & Sigma parameter for RBF + + cGridRes = 10; + sigGridRes = 10; + nFold = 3; + cGrid = linspace(eps,5,cGridRes); %Create Parameter search grid + sigGrid = linspace(eps,10,sigGridRes); %Create Parameter search grid + + if ~doMultiClass + [r,a] = train(gridsel(param(svm(kernel('rbf',sig)),{'C','kerparam'},{cGrid,sigGrid}),{['score=cv;score.folds=' num2str(nFold)]}),dataobj); + else + [r,a] = train(gridsel(param(one_vs_rest(svm(kernel('rbf',sig),'optimizer="andre"')),{'C','kerparam'},{cGrid,sigGrid}),{['score=cv;score.folds=' num2str(nFold)]}),dataobj); + end + + slackstr = ['C=' num2str(a.C)]; %slack parameter + sig = a.kerparam; %sigma parameter + + end + + t2 = toc(t1); + fprintf('Done in %3.2f sec\n', t2) + +end + +%Create SVM objects with correct parameters (i.e., refit after finding optimal parameter) +if ~dorbf + svmobj = svm({slackstr, 'optimizer="andre"'}); +else + svmobj = svm({slackstr, 'optimizer="andre"',kernel('rbf',sig)}); +end + +%Check if using multiclass svm, i.e., one vs rest +if doMultiClass + svmobj = one_vs_rest(svmobj); + display('Running in Multiclass Mode') +end + +%Check if using balanced ridge +if doBalanced + svmobj.balanced_ridge = ridgeAmt; +end + +% Training +fprintf('Training...') +t1 = tic; +[res, svmobj] = train(svmobj, dataobj, loss); + +% Testing +fprintf('Testing...') +res2 = test(svmobj, data('test data', xtest, [])); +yfit = res2.X; % this is proportional to res.X*w (perfectly correlated), but scale is different +fprintf('\n'); + +t2 = toc(t1); +fprintf('Done in %3.2f sec\n', t2) + +% vec = [res.X res.Y dataobj.X * w' res2.X] +% corrcoef(vec) + +% ---------------------------------------------------------- +% Collect Outputs +% ---------------------------------------------------------- + +if ~doMultiClass + w = get_w(svmobj)'; + b0 = svmobj.b0; + dist_from_hyplane = xtest * w + b0; + svmpar.kernel = 'linear'; +else + for i = 1:size(res2.X,2) + w(:,i) = get_w(svmobj{i})'; + b0(i) = svmobj{i}.b0; + dist_from_hyplane(:,i) = xtest * w(:,i) + b0(i); + + end +end + +intercept = b0; % 09/24/13 Wani added intercept +svmpar.C = slackstr; +if dorbf + svmpar.sigma = sig; + svmpar.kernel = 'rbf'; +end + +end % function + + + +% --------------------------------------------------------------------- +% --------------------------------------------------------------------- + +% BOOTSTRAPPING AND OTHER FUNCTIONS + +% --------------------------------------------------------------------- +% --------------------------------------------------------------------- + +function WTS = boot_weights(funhan, obj, cv_assignment, varargin) + +%default options +opt = statset('UseParallel','never'); +doParallel = 0; +doSaveWeights = 0; +bootsamples = 100; +WTS = struct; +rng 'shuffle' %pick random seed for randomization + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'parallel' + doParallel = 1; %run in parallel + varargin{i} = []; + opt = statset('UseParallel','always'); + try + if ~matlabpool('size'), matlabpool open, end + catch + disp('Problem starting matlab pool - JAVA issue?'); + end + case 'bootsamples' + bootsamples = varargin{i + 1}; + varargin{i} = []; + varargin{i + 1} = []; + + case 'savebootweights' + doSaveWeights = 1; %Save bootstrap weights + %weightname = varargin{i + 1}; + varargin{i} = []; + %varargin{i+1} = []; + end + end +end + + +%let's try directly inputting these to save RAM -luke +%d = obj.dat'; +%y = obj.Y; + +% anonymous function to return weights, using user's input algorithm +% w = bootfunhan(d, y, cv_assignment); +bootfunhan = @(d, y, cv_assignment) boot_weights_fcn(d, y, cv_assignment, funhan); + +%fprintf('Setting up bootstrap samples...'); +%bootsam = setup_boot_samples(d, bootsamples); + +fprintf('Bootstrapping weights, %3.0f samples...', bootsamples); +t0 = tic; +WTS.w = bootstrp(bootsamples, bootfunhan, obj.dat', obj.Y, cv_assignment, 'Options', opt); +fprintf('Done in %3.0f sec\n', toc(t0)); + +%[bootstat, bootsam] = bootstrp_havesamples(bootsam,bootfun,varargin) +%[bootstat, bootsam] = bootstrp_havesamples(bootsamples,bootfunhan, d, y); + +%7/2/13: Luke changed to use nan mean and outputs mean of weights and to reduce +%duplicating variables +WTS.wste = nanstd(WTS.w); +WTS.wmean = nanmean(WTS.w); +WTS.wste(WTS.wste == 0) = Inf; % in case unstable regression returns all zeros +WTS.wZ = WTS.wmean ./ WTS.wste; % tor changed from wmean; otherwise bootstrap variance in mean inc in error; Luke renamed to avoid confusion +WTS.wP = 2 * (1 - normcdf(abs(WTS.wZ))); + +if ~doSaveWeights %clear weights if they don't need to be saved (better for memory) + WTS.w = []; +end + +singlemn = bootfunhan(obj.dat', obj.Y, cv_assignment); +if min(size(singlemn)) > 1 % in case bootfunhan returns matrix; bootstrp vectorizes + singlemn = singlemn(:)'; +end + +%r = corr(wmean, singlemn,'rows','pairwise'); +r=nancorr(WTS.wmean,singlemn); %7/2/13: LC ignore nans + +% create_figure('bootweights', 1, 2); plot(wmean, mean(w), 'k.'); +% xlabel('Weights - full sample'); +% ylabel('Mean bootstrap weights'); +% subplot(1, 2, 2); hist(wZ, 100); +% title('Z-scores of voxel weights') + + + +fprintf('Correlation between bootstrapped mean weights and weights: %3.4f\n', r) +fprintf('Voxel weight Z-values range between %3.2f and %3.2f\n', min(WTS.wZ), max(WTS.wZ)); + +end + +% ------------ bootstrapping and other functions ------------------------- + + +function w = boot_weights_fcn(dat, Y, cv_assignment, funhan) +% runs algorithm on all data, returns weights; for bootstrapping + +% works for cv_pcr, which has 2 outputs; diff setup here for diff algorithms +optout = cell(1, 2); + +[yfit_all, optout{:}] = funhan(dat, Y, dat, cv_assignment); + +w = optout{1}'; +end + +% ------------ bootstrapping and other functions ------------------------- + +function bootsam = setup_boot_samples(x, bootsamples) +% bootsam = setup_boot_samples(x, bootsamples) + +% initalize random number generator to new values; bootstrp uses this +% there are problems with randomization if not done! +%rand('twister', sum(100*clock)) +rng('shuffle'); + +n = size(x, 1); + +bootsam = ceil(n*rand(n, bootsamples)); % x value is the same for all boot samples + +maxiter = 500; + +% check and replace invalid ones with no variance in predictor +% could do this later for mediator(s)/outcome, but may slow +% down/pose other problems? +wh_bad = ~any(diff(x(bootsam))); %| ~any(diff(m(bootsam))) +icount = 1; + +while any(wh_bad) + bootsam(:, wh_bad) = []; + %ix = size(bootsam, 2) + 1; + ntoadd = bootsamples - size(bootsam, 2); + + bootsam = [bootsam ceil(n*rand(n, ntoadd))]; % fill out rest of samples + + wh_bad = ~any(diff(x(bootsam))); + icount = icount + 1; + + if icount > maxiter + warning('PREDICT:cannotGetBootSamples', 'Cannot get bootstrap samples; max iterations exceeded. Sample size too small with categorical predictors?'); + bootsam(:, wh_bad) = []; + break + end +end + +end + +% ------------ bootstrapping and other functions ------------------------- + +function [hour, minute, second] = sec2hms(sec) +%SEC2HMS Convert seconds to hours, minutes and seconds. +% +% [HOUR, MINUTE, SECOND] = SEC2HMS(SEC) converts the number of seconds in +% SEC into hours, minutes and seconds. + +hour = fix(sec/3600); % get number of hours +sec = sec - 3600*hour; % remove the hours +minute = fix(sec/60); % get number of minutes +sec = sec - 60*minute; % remove the minutes +second = sec; +end + +% ------------ bootstrapping and other functions ------------------------- + +function rho = nancorr(a,b) +%calculate pearson correlation between a & b for nonnan values +keep = logical(~isnan(a) .* ~isnan(b)); +rho = (mean((a(keep)-mean(a(keep))) .* (b(keep)-mean(b(keep)))))/(std(a(keep))*std(b(keep))); +end diff --git a/@fmri_data/predict_test_suite.m b/@fmri_data/predict_test_suite.m new file mode 100644 index 00000000..2af8af2a --- /dev/null +++ b/@fmri_data/predict_test_suite.m @@ -0,0 +1,241 @@ +function [allcverr, allyhat] = predict_test_suite(dat, varargin) +% Run a set of cross-validated prediction algorithms on an fmri_data object +% and plot the outcome. +% +% Usage: +% ---------------------------------------------------------------- +% [allcverr, allyhat] = predict_test_suite(dat, [optional inputs]) +% +% Functionality: +% - Requires matlab 2012a or later for full functionality +% - Handles categorical or continuous outcomes automatically +% +% +% Inputs: +% ---------------------------------------------------------------- +% dat an fMRI data object. +% dat.Y must be assigned, and must have continuous or binary outcomes assigned. +% +% Optional: +% 'quick' Skip extended output +% 'nfolds', Followed by number of folds or custom holdout vector (default = 5-fold balanced) +% +% Outputs: +% ---------------------------------------------------------------- +% +% +% Examples: +% ---------------------------------------------------------------- +% predict_test_suite(dat, 'nfolds', subjid); +% +% Tor Wager, copyright 2012. Initial version: Nov 2012 + +% Programmers' notes: + +% Here are possible extensions to the functionality: +% 1) Test different data scaling methods in preprocess(dat) and/or +% rescale(dat) +% +% 2) Feature selection: Test effects of thresholding weight maps +% (and others) +% +% 3) Test "best case" feature selection effects - circular analysis +% +% Lasso trace plots? Optimal alpha/lambda in elastic net? + +create_figure('predicted-actual corr', 2, 4); + +nlevels = length(unique(dat.Y)); +nfolds = 5; + +% SET UP ALGORITHMS +% ------------------------------------------------------------------ +switch nlevels + case {1, 0} + error('NO VARIANCE IN obj.Y') + + case 2 + methodnames = {'cv_svm' 'cv_lassopcrmatlab'}; + errtype = 'mcr'; + outputsummaryfield = 'cverr'; + + otherwise + methodnames = {'cv_svr' 'cv_pcr' 'cv_lassopcrmatlab' 'cv_lassopcr'}; %'cv_univregress' + errtype = 'mse'; + outputsummaryfield = 'pred_outcome_r'; + +end + +% SET UP OPTIONAL INPUTS +% Pass all valid arguments specified here on to predict.m +% ------------------------------------------------------------------ +optargs = {}; +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands for predict + case {'nfolds', 'useparallel'} + optargs{end+1} = varargin{i}; + optargs{end+1} = varargin{i+1}; + end + end +end + +if ~any(strcmp(optargs, 'nfolds')) + optargs{end+1} = 'nfolds'; + optargs{end+1} = nfolds; +end + + + +allyhat = zeros(size(dat.Y, 1), length(methodnames)); + +allcverr = zeros(1, length(methodnames)); + +% RUN ALL ALGORITHMS +% ------------------------------------------------------------------ +for i = 1:length(methodnames) + + try + + [cverr, stats, optout] = predict(dat, 'algorithm_name', methodnames{i}, 'error_type', errtype, optargs{:}); + + allyhat(:, i) = stats.yfit; + + allcverr(i) = cverr; + + subplot(2, 4, i); + + plot_correlation_samefig(stats.yfit, stats.Y); + xlabel('Predicted (5-fold crossval)'); + ylabel('Observed') + title(sprintf('%s', methodnames{i})); + + plot_folds(stats) + drawnow + + catch + fprintf('ERROR running %s\n', methodnames{i}) + keyboard + %rethrow(lasterr) + end + +end % loop through methods + +% SUMMARY OUTPUT +% ------------------------------------------------------------------ +rmtx = corrcoef(allyhat); +disp('Correlations among predicted values across methods') +print_matrix(rmtx, methodnames, methodnames); + +% Quit now if we don't want extended output + +if any(strcmp(varargin, 'quick')), return, end + +% IMPACT OF COMPONENT SELECTION +% ------------------------------------------------------------------ +numc = round(linspace(1, max(2, size(dat.dat, 2) - 2), 10)); +numc = unique(numc); + +if nlevels == 2 + mymethodname = 'cv_lassopcrmatlab'; +else + mymethodname = 'cv_pcr'; +end + +for i = 1:length(numc) + + [cverr_comp(i), stats] = predict(dat, 'numcomponents', numc(i), 'algorithm_name', mymethodname, 'error_type', errtype, optargs{:}); + + r_comp(i) = stats.(outputsummaryfield); + + if nlevels == 2 + % reverse so that higher = better + r_comp(i) = 1 - r_comp(i); + end + +end + +whplot = length(methodnames) + 1; +subplot(2, 4, whplot) +axh(1) = gca; + +plot(numc, r_comp, 'o-', 'Color', [.3 .3 .3], 'MarkerFaceColor', [.5 .5 1], 'LineWidth', 2, 'MarkerSize', 10); +xlabel('Components'); +if nlevels == 2 + ylabel('Classification accuracy'); +else + ylabel('Pred-outcome correlation'); +end +title(sprintf('Component selection: %s', mymethodname)); + + +% IMPACT OF PENALIZATION +% ------------------------------------------------------------------ +mymethodname = 'cv_lassopcrmatlab'; + +for i = 1:length(numc) + + [cverr_comp(i), stats] = predict(dat, 'lasso_num', numc(i), 'algorithm_name', mymethodname, 'error_type', errtype, optargs{:}); + + r_comp(i) = stats.(outputsummaryfield); + + if nlevels == 2 + % reverse so that higher = better + r_comp(i) = 1 - r_comp(i); + end +end + +whplot = length(methodnames) + 2; +subplot(2, 4, whplot) +axh(2) = gca; + +plot(numc, r_comp, 'o-', 'Color', [.3 .3 .3], 'MarkerFaceColor', [.5 .5 1], 'LineWidth', 2, 'MarkerSize', 10); +xlabel('Lasso: components retained'); +if nlevels == 2 + ylabel('Classification accuracy'); +else + ylabel('Pred-outcome correlation'); +end +title(sprintf('LASSO penalization: %s', mymethodname)); + +subplot(2, 4, 8); axis off + +equalize_axes(axh); + +end % main function + + +function plot_folds(stats) + + +k = length(stats.teIdx); + +colors = scn_standard_colors(k); + +for i = 1:length(stats.teIdx) + + wh = find(stats.teIdx{i}); + + if length(wh) < 3 % don't do it for 1-2 points only + continue + end + + yfit = stats.yfit(wh); + y = stats.Y(wh); + + plot(yfit, y, 'o', 'MarkerFaceColor', colors{i}); + + + % ref line + b = glmfit(yfit, y); + + plot([min(yfit) max(yfit)], [b(1)+b(2)*min(yfit) b(1)+b(2)*max(yfit)], 'Color', colors{i}, 'LineWidth', 2); + +end + +axis tight + +end + + diff --git a/@fmri_data/regress.m b/@fmri_data/regress.m new file mode 100644 index 00000000..f3dae304 --- /dev/null +++ b/@fmri_data/regress.m @@ -0,0 +1,242 @@ +function [out, statimg] = regress(dat, varargin) +% [out, statimg] = regress(dat, [p-val threshold], [thresh_type], ['nodisplay']) +% +% regression method for fmri_data object +% regress dat.Y on dat.dat at each voxel, and return voxel-wise statistic +% images. +% +% Each column of Y is a predictor in a multiple regression, +% and the intercept is the last column. +% Thus, the output images correspond to the columns of Y, with the last +% column being the intercept. +% The reason the "Y" field is used as the "X" design matrix is that Y in +% the fmri_data object is reserved for outcome data/behavior. +% In the standard brain-mapping approach, Y is the predictor and each +% voxel's data is the outcome in a series of regressions. +% +% This function can also create a map of brain regions that predict the Y +% vector using the 'brainony' option. This is essentially a univariate +% version of the 'predict' command. There is also a robust option for this +% ('brainony', 'robust') +% +% Inputs: +% dat should be an fmri_data object with Y field defined. +% optional inputs in [ ] above are: +% p-value threshold +% string indicating threshold type (see help statistic_image.threshold for options) +% +% Optional Inputs: +% 'nointercept' estimates model without intercept +% 'nodisplay' does not display resulting model +% 'brainony' univariate approach to predict obj.Y from brain data +% +% Outputs: +% out is a structure of information about the regression +% statimg is a statistic_image object that can be thresholded and +% plotted/imaged. statimg.dat contains T-values, .p contains p-values for +% each regressor. +% +% Examples: +% % Run regression with liberal threshold +% [out, statimg] = regress(data_comb, .05, 'unc'); +% +% % Re-threshold at different values +% statimg = threshold(statimg, .05, 'fdr'); +% statimg = threshold(statimg, .001, 'unc'); +% +% % Re-display results of thresholding +% orthviews(statimg); +% +% %Run a regression predicting behavior from brain at liberal threshold +% [out, statimg] = regress(data_comb, .05, 'unc', 'brainony') +% +% %Run a robust regression predicting behavior from brain at liberal +% threshold. WARNING! Very slow! +% [out, statimg] = regress(data_comb, .05, 'unc', 'brainony','robust') + +%Notes: +% c Tor Wager, Dec 2010 +% Edited by Luke Chang, 9/27/2012 to add optional input to reverse X & Y (i.e., create a map of voxels that predict the behavioral variable) +% Edited by Luke Chang, 9/28/2012 to add optional input to run robust regression for brainony +% Edited by Luke Chang, 10/24/2012 to save residuals (i.e., out.r), which is helpful for denoising an image +% Edited by Luke Chang, 3/26/2013 to add optional input to not add an intercept - allows for more flexible modeling options + + +% default options for thresholding +inputargs = {[.001], 'uncorrected'}; + +dodisplay = 1; + +dobrainony = 0; %added by lc +dorobust = 0; %added by lc +dointercept = 1; %added by lc + +if ~isempty(varargin) + for i = 1:length(varargin) + inputargs{i} = varargin{i}; + end +end + +if any(strcmp(inputargs, 'nodisplay')) + i = find(strcmp(inputargs, 'nodisplay')); + dodisplay = 0; + inputargs(i) = []; +end + +if any(strcmp(inputargs, 'brainony')) + i = find(strcmp(inputargs, 'brainony')); + dobrainony = 1; + inputargs(i) = []; +end + +if any(strcmp(inputargs, 'robust')) + i = find(strcmp(inputargs, 'robust')); + dorobust = 1; + inputargs(i) = []; +end + +if any(strcmp(inputargs, 'nointercept')) + i = find(strcmp(inputargs, 'nointercept')); + dointercept = 0; + inputargs(i) = []; +end + +n = size(dat.dat, 2); +if n ~= size(dat.Y, 1), error('dat.dat must have same number of columns as dat.Y has rows.'); end + +if ~dobrainony %default is to regress Y on brain + + if dointercept %can fit model with no intercept: 3/26/13 LC + X = dat.Y; X(:, end+1) = 1; + else + X = dat.Y; + end + + % display info about regression + fprintf('regression > Y: %3.0f voxels. X: %3.0f obs, %3.0f regressors, intercept is last.\n', size(dat.dat, 1), n, size(X, 2)); + + % model fit + tic + [n, k] = size(X); + b = pinv(X) * dat.dat'; + + % error + r = dat.dat' - X*b; + sigma = std(r); + + stderr = ( diag(inv(X' * X)) .^ .5 ) * sigma; % params x voxels matrix of std. errors + + % inference + + t = b ./ stderr; + + dfe = n - k; + p = 2 * (1 - tcdf(abs(t), dfe)); + + sigma(sigma == 0) = Inf; + t(isnan(t)) = 0; + p(isnan(p)) = 0; + + toc +else %regress brain on Y + if ~dorobust %Run standard OLS regression + % display info about regression + fprintf('Predicting dat.Y from Brain Activity'); + + tic + warning off + for i=1:size(dat.dat,1) %slow, need to figure out how to vectorize this + + if dointercept %can fit model with no intercept: 3/26/13 LC + X = dat.Y; X(:, end+1) = 1; + else + X = dat.Y; + end + + % model fit + b(:,i) = pinv(X)*dat.Y; + + %error + r(:,i)=dat.Y-X*b(:,i); + sigma(i)=std(r(:,i)); + stderr(:,i) = (diag(inv(X'*X)).^ .5) * sigma(i); %warning about matrix being singular not sure what the problem is. + end + warning on + [n, k] = size(X); + % inference + dfe = n - k; + + t = b ./ stderr; + p = 2 * (1 - tcdf(abs(t), dfe)); + + sigma(sigma == 0) = Inf; + t(isnan(t)) = 0; + p(isnan(p)) = 0; + + toc + + else %Run robust regression %Warning! Very Slow using the loop + + fprintf('Predicting dat.Y from Brain Activity Using Robust Regression'); + tic + warning off + for i=1:size(dat.dat,1) %slow, need to figure out how to vectorize this + X=dat.dat(i,:)'; X(:, end+1) = 1; + Y=dat.Y; + + % model fit + [bb,stats]=robustfit(X, Y, 'bisquare', [], 'off'); + b(:,i)=bb; + t(:,i)=stats.t; + p(:,i)=stats.p; + stderr(:,i)=stats.se; + sigma(:,i)=stats.robust_s; %robust estimate of sigma. LC not sure this is the best choice can switch to 'OLS_s','MAD_s', or 's' + + end + warning on + + [n, k] = size(X); + dfe = stats.dfe; + + sigma(sigma == 0) = Inf; + t(isnan(t)) = 0; + p(isnan(p)) = 0; + + toc + + end +end + +% save results +statimg = statistic_image; +statimg.type = 'T'; +statimg.p = p'; +statimg.ste = stderr'; +statimg.N = n; +statimg.dat = t'; +statimg.dat_descrip = sprintf('t-values from OLS regression, intercept is last'); +statimg.volInfo = dat.volInfo; +statimg.removed_voxels = dat.removed_voxels; +statimg.removed_images = false; % this image does not have the same dims as the original dataset + +statimg = threshold(statimg, inputargs{:}); + +out.X = X; +out.b = b; +out.sigma = sigma; +out.stderr = stderr; +out.dfe = dfe; +out.t = t; +out.p = p; +out.r = r; %LC : 10/24/12 : save residual - helpful for denoising + + +% view results +if k < 10 & dodisplay + + orthviews(statimg); + spm_orthviews_name_axis('Intercept', size(statimg.dat, 2)) + +end + +end % function \ No newline at end of file diff --git a/@fmri_data/rescale.m b/@fmri_data/rescale.m new file mode 100644 index 00000000..570d7636 --- /dev/null +++ b/@fmri_data/rescale.m @@ -0,0 +1,238 @@ +function fmridat = rescale(fmridat, meth, varargin) +% fmridat = rescale(fmridat, meth) +% +% Rescales data in an fmri_data object +% Data is observations x images, so operating on the columns operates on +% images, and operating on the rows operates on voxels (or variables more +% generally) across images. +% +% Methods: +% 'centervoxels' +% 'zscorevoxels' +% 'centerimages' +% 'zscoreimages' +% 'rankvoxels' +% +% 'windsorizevoxels' +% 'percentchange' +% 'tanh' +% +% Appropriate for multi-session (time series) only: +% 'session_global_percent_change' +% 'session_global_z' +% 'session_multiplicative' +% +% see also fmri_data.preprocess + +switch meth + + case 'centervoxels' + + fmridat.dat = scale(fmridat.dat', 1)'; + + fmridat.history{end+1} = 'Centered voxels (rows) across images'; + + case 'zscorevoxels' + + fmridat.dat = scale(fmridat.dat')'; + + fmridat.history{end+1} = 'Z-scored voxels (rows) across images'; + + case 'rankvoxels' + for i = 1:size(fmridat.dat, 1) % for each voxel + + d = fmridat.dat(i, :)'; + if ~all(d == 0) + fmridat.dat(i, :) = rankdata(d)'; + end + + end + + case 'centerimages' + + % center images (observations) + fmridat.dat = scale(fmridat.dat, 1); + + fmridat.history{end+1} = 'Centered images (columns) across voxels'; + + case 'zscoreimages' + + fmridat.dat = scale(fmridat.dat); + + fmridat.history{end+1} = 'Z-scored imagesc(columns) across voxels'; + + case 'session_global_percent_change' + + nscan = fmridat.images_per_session; % num images per session + I = intercept_model(nscan); + for i = 1:size(I, 2) + + wh = find(I(:, i)); + y = fmridat.dat(:, wh)'; % y is images x voxels + gm = mean(y); % mean at each voxel, 1 x voxels + + % subtract mean at each vox, divide by global session mean + y = (y - repmat(gm, size(y, 1), 1)) ./ std(y(:)); + fmridat.dat(:, wh) = y; + end + + case 'session_spm_style' + % SPM's default method of global mean scaling + % useful for replicating SPM analyses or comparing SPM's scaling to + % other methods. + % not implemented yet because tor decided to use spm_global on images for comparison; this could be done though... + % see help spm_global + % nscan = fmridat.images_per_session; % num images per session + % I = intercept_model(nscan); + % for i = 1:size(I, 2) + % + % wh = find(I(:, i)); + % y = fmridat.dat(:, wh)'; % y is images x voxels + % gm = mean(y); % mean at each voxel, 1 x voxels + % + % % subtract mean at each vox, divide by global session mean + % y = (y - repmat(gm, size(y, 1), 1)) ./ std(y(:)); + % fmridat.dat(:, wh) = y; + % end + + + case 'session_global_z' + + % scale each session so that global brain mean and global brain std + % across time are the same for each session + + % underlying model: + % session-specific shifts in mean signal and scaling exist and are + % independent + + % minus global (whole-brain) mean / global (whole-brain) std. + % across time + + nscan = fmridat.images_per_session; % num images per session + I = intercept_model(nscan); + for i = 1:size(I, 2) + + wh = find(I(:, i)); + y = fmridat.dat(:, wh); + gm = mean(y); % mean at each time point + + % subtract mean at each vox, divide by session global std + y = (y - repmat(gm, size(y, 1), 1)) ./ std(y(:)); + fmridat.dat(:, wh) = y; + end + + case 'session_multiplicative' + + % scale - multiplicative + % underlying model: + % a is a process constant across time, but different for each voxel + % (T2 contrast as a function of voxel properties) + % b is a process constant across voxels, but different at each time + % point within a session + % (overall scaling, which varies across time) + % these interact multiplicatively to create variation in both + % global mean and std deviation jointly, which is why global mean + % and global std are intercorrelated correlated. + % + % image std scales with b, and so does image mean. + % model assumes a linear relationship between mean and std with + % slope = 1 + + gm = mean(fmridat.dat, 1); % mean across brain, obs series + gs = std(fmridat.dat, 1); + + create_figure('global mean vs std', 1, 2); + plot(gm, gs, 'k.') + xlabel('Image mean'); + ylabel('Image std'); + title('Mean vs. std, before scaling, each dot is 1 image') + drawnow + + % if length(varargin) == 0 || isempty(varargin{1}) + % error('Must enter number of images in each session as input argument'); + % end + + if isempty(fmridat.images_per_session) + fmridat.images_per_session = size(fmridat.dat, 2); + end + + nscan = fmridat.images_per_session; % num images per session + I = intercept_model(nscan); + + for i = 1:size(I, 2) + + wh = find(I(:, i)); + y = fmridat.dat(:, wh); + y(y < 0) = 0; % images should be all positive-valued + + a = mean(y')'; % mean across time, for each voxel + b = mean(y); % mean across voxels, for each time point + + % ystar is reconstruction based on marginal means + ystar = a*b ./ (mean(a)); + + fmridat.dat(:, wh) = y ./ (ystar + .05*mean(ystar(:))); % like m-estimator; avoid dividing by zero by adding a constant + + % fmridat.dat(:, wh) = y ./ repmat(b, size(y, 1), 1); % intensity normalization + end + + gm = mean(fmridat.dat, 1); % mean across brain, obs series + gs = std(fmridat.dat, 1); + + subplot(1, 2, 2) + plot(gm, gs, 'k.') + xlabel('Image mean'); + ylabel('Image std'); + title('After scaling') + drawnow + + + case 'windsorizevoxels' + + whbad = all(fmridat.dat == 0, 2); + nok = sum(~whbad); + + fprintf('windsorizing %3.0f voxels to 3 std: %05d', 0); + for i = 1:nok + if mod(i, 100) == 0, fprintf('\b\b\b\b\b%05d', i); end + fmridat.dat(i, :) = trimts(fmridat.dat(i, :)', 3, [])'; + end + + fmridat.history{end+1} = 'Windsorized each voxel data series to 3 sd'; + + case 'tanh' + % rescale variables by hyperbolic tangent function + % this will shrink the tails (and outliers) towards the mean, + % making subsequent algorithms more robust to outliers. + % However, it also truncates and flattens the distribution + % (non-normal) + + fmridat.dat = tanh(zscore(fmridat.dat'))'; + + case 'percentchange' + % scale each voxel (column) to percent signal change + % with a mean of 100 + % based on smoothed mean values across space (cols) + + m = mean(fmridat.dat')'; % mean at each voxel, voxels x 1 + + sfwhm = 16; + ms = iimg_smooth_3d(m, fmridat.volInfo, sfwhm, fmridat.removed_voxels); + + % subtract mean at each vox, divide by global session mean + fmridat.dat = 100 + 100 .* (fmridat.dat - repmat(m, 1, size(fmridat.dat, 2))) ./ repmat(ms, 1, size(fmridat.dat, 2)); + + fmridat.history{end+1} = 'Rescaled to mean 100, voxelwise % signal change with 16 mm fwhm smoothing of divisor mean'; + + case 'correly' + + % correl with y + % provides implicit feature selection when using algorithms that + % are scale-dependent (e.g., SVM, PCA) + + otherwise + error('Unknown scaling method.') + +end + +end \ No newline at end of file diff --git a/@fmri_data/saveplots.m b/@fmri_data/saveplots.m new file mode 100644 index 00000000..ce8e6bff --- /dev/null +++ b/@fmri_data/saveplots.m @@ -0,0 +1,47 @@ +function saveplots(fmri_dat, varargin) + +% Output dir +if isempty(varargin) + savedir = pwd; +else +savedir = varargin{1}; +end + +if ~exist(savedir, 'dir'), mkdir(savedir); end + + +% Register of possible plot names associated with this object type + +plotnames = {'fmri data matrix' ... + 'Orthviews_means_by_unique_Y' ... + 'means by condition (unique Y values)' ... + 'Montage_coeff_of_var_across_conditions' ... + 'Montage_mean_across_conditions' ... + 'Orthviews_fmri_data_mean_and_std' + }; + +for j = 1:length(plotnames) + + han = findobj('Name', plotnames{j}); + + if isempty(han), continue, end + + han = han(end); % only take last instance + if ishandle(han) && strcmp(get(han, 'Type'), 'figure') + + fprintf('Saving figure: %s\n', plotnames{j}); + figure(han); + scn_export_papersetup(500); + + name = get(han, 'Name'); + name(name == ' ') = '_'; + + name = fullfile(savedir, name); + + saveas(han, name, 'png'); + + end + +end + +end \ No newline at end of file diff --git a/@fmri_data/signtest.m b/@fmri_data/signtest.m new file mode 100644 index 00000000..70bc9047 --- /dev/null +++ b/@fmri_data/signtest.m @@ -0,0 +1,65 @@ +function [out, statimg] = signtest(dat, varargin) +% [out, statimg] = signtest(dat, [p-val threshold], [thresh_type]) +% +% sign test for each voxel of an fmri_data object +% returns voxel-wise statistic images. +% +% Inputs: +% dat should be an fmri_data object with .dat field containing voxels x observations matrix +% optional inputs in [ ] above are: +% p-value threshold +% string indicating threshold type (see help statistic_image.threshold for options) +% +% Outputs: +% out is a structure of information about the sign test +% statimg is a statistic_image object that can be thresholded and +% plotted/imaged. statimg.dat contains signed direction values, .p contains p-values +% +% c Tor Wager, 2011 +% See also: fmri_data.regress +% +% Examples: + +% +% + +% default options for thresholding +inputargs = {.001, 'uncorrected'}; + +if ~isempty(varargin) + for i = 1:length(varargin) + inputargs{i} = varargin{i}; + end +end + +n = size(dat.dat, 2); + +% display info about regression +fprintf('sign test > Y: %3.0f voxels. X: %3.0f obs, %3.0f tests\n', size(dat.dat, 1), n); + + +% model fit +tic + +out = signtest_matrix(dat.dat'); + +toc + +% save results +statimg = statistic_image; +statimg.type = 'signtest'; +statimg.p = out.p; +statimg.ste = NaN .* out.p; +statimg.N = out.n; +statimg.dat = out.direction; +statimg.dat_descrip = sprintf('Sign test direction: Pos for > 0, Neg for < 0'); +statimg.volInfo = dat.volInfo; +statimg.removed_voxels = dat.removed_voxels; +statimg.removed_images = false; % this image does not have the same dims as the original dataset + +statimg = threshold(statimg, inputargs{:}); + +% view results +orthviews(statimg); + +end % function \ No newline at end of file diff --git a/@fmri_data/ttest.m b/@fmri_data/ttest.m new file mode 100644 index 00000000..950e4d3e --- /dev/null +++ b/@fmri_data/ttest.m @@ -0,0 +1,58 @@ +function statsimg = ttest(fmridat, pvalthreshold, thresh_type) +% T-test on fmri_data class object +% statsimg = ttest(fmridat, pvalthreshold, thresh_type) +% +% ttest(fmridat, p-value threshold, thresh_type) +% +% p-value threshold: p-value, e.g., .05 or .001 or [.001 .01 .05] +% thresh_type: 'uncorrected', 'fwe', or 'fdr' +% +% e.g., +% T-test, Construct a stats_image object, threshold and display: +% statsimg = ttest(fmridat, .001, 'unc'); +% orthviews(statsimg); +% +% Re-threshold and display: +% statsimg = threshold(statsimg, .000001, 'unc'); +% orthviews(statsimg); +% +% statsimg = threshold(statsimg, .01, 'fdr'); +% orthviews(statsimg); +% +% NOTE: for two-sample T-test, use fmri_data.regress + +fprintf('One-sample t-test\n') +fprintf('Calculating t-statistics and p-values\n'); + +err = ste(fmridat.dat')'; + +t = nanmean(fmridat.dat')' ./ err; + +N = single(sum(~(isnan(fmridat.dat') | fmridat.dat' == 0) , 1)); + +df = size(fmridat.dat, 2) - 1; + +% two-tailed - now done in statistic_image constructor +% p = 2 * (1 - tcdf(abs(t), df)); + +% t = t'; +% p = p'; + +% removed: done in constructor: 'p', p, 'p_type', 'two-tailed', +statsimg = statistic_image('type', 't', 'dat', t, 'ste', err, 'N', N, 'volInfo', fmridat.mask.volInfo, 'dfe', df); + +% get rid of NaNs for empty vals +stats.p(isnan(t)) = 1; +stats.t(isnan(t)) = 0; + +statsimg.removed_voxels = fmridat.removed_voxels; + +if nargin > 2 + + statsimg = threshold(statsimg, pvalthreshold, thresh_type); + +end + + + +end diff --git a/@fmri_data/windsorize.m b/@fmri_data/windsorize.m new file mode 100644 index 00000000..2815869c --- /dev/null +++ b/@fmri_data/windsorize.m @@ -0,0 +1,46 @@ +function obj = windsorize(obj, varargin) +% +% obj = windsorize(obj, [madlimit]) +% +% Windsorize an fMRI data object to madlimit Median Absolute Deviations. +% Default = 5 MADs. +% Works across rows and columns. +% Registers this step in history. + + + +% --------------------------------------------------------------- +% Calculate and display descriptives +% --------------------------------------------------------------- + +madlimit = 5; +if ~isempty(varargin), madlimit = varargin{1}; end + +dattmp = obj.dat(:); +med = median(dattmp); +mabsd = mad(dattmp); +climits = [med - madlimit * mabsd med + madlimit * mabsd]; +wh_out = obj.dat < climits(1) | obj.dat > climits(2); +nout = sum(wh_out(:)); +percout = 100 * nout ./ prod(size(obj.dat)); + +nout_by_case = sum(wh_out, 2); + +sep = sprintf('____________________________________________\n'); +fprintf('%sfmri_data structure .dat field (data)\n%s', sep, sep); +fprintf('Median: %3.3f\nMean: %3.3f\n', med, mean(dattmp)); +fprintf('MAD: %3.3f\nSTD: %3.3f\n', mabsd, std(dattmp)); +fprintf('Max:%3.3f\nMin:%3.3f\n', max(dattmp), min(dattmp)); +fprintf('Control limits: %3.3f to %3.3f\n', climits); +fprintf('Outliers: %3.0f values, %3.2f%% of all values\n', nout, percout); +fprintf('Outliers by case (row): Max = %3.0f, Median = %3.0f, Min = %3.0f\n', max(nout_by_case), median(nout_by_case), min(nout_by_case)); + +datadj = obj.dat; +datadj(obj.dat < climits(1)) = climits(1); +datadj(obj.dat > climits(2)) = climits(2); + +obj.dat = datadj; +obj.history{end+1} = sprintf('dat windsorized to %3.1f MADs', madlimit); + + +end % function \ No newline at end of file diff --git a/@fmri_mask_image/fmri_mask_image.m b/@fmri_mask_image/fmri_mask_image.m new file mode 100644 index 00000000..6a4f544f --- /dev/null +++ b/@fmri_mask_image/fmri_mask_image.m @@ -0,0 +1,164 @@ +% What is it? +% ------------------------------ +% fmri_mask_image is a class of objects containing masks for neuroimaging +% data (not just fmri, really...) +% +% An object of this type is a structure that helps keep track of mask +% information, and what has happened to it, i.e., if it was resampled into +% the space of another image. +% +% Properties: +% ------------------------------ +% dat +% dat_descrip = 'Mask data'; +% +% volInfo = an SPM-style volume info structure (see spm_vol) with a bit +% more information, from iimg_read_image.m +% volInfo_descrip = 'Volume and in-area mask info from iimg_read_img'; +% +% space_defining_image_name = char(''); +% +%history = {}; +% +% Methods include: +% ------------------------------ +% resample_to_image_space +% +% Usage: +% ------------------------------ +% Class constuctor method: Usage for creating objects of this type: +% - Loads first image in 4-D volume only +% - Returns values within mask (excludes zeros and NaNs) +% - entering 'implicit' as an optional keyword will use fmri_mask_image to get an implicit mask +% +% obj = fmri_mask_image(image_name, ['implicit']) +% This command loads in an image file stored in image_name and returns a +% mask image object in obj with some fields filled in, including data. +% +% +% Properties: +% dat: [517845x1 single] +% dat_descrip: [1x92 char] +% volInfo: [1x1 struct] +% volInfo_descrip: [1x47 char] +% space_defining_image_name: '' +% history: {} +% +% To get a list in original full-image space of in-mask voxels, use this: +% indx = obj.volInfo.image_indx; +% indx(obj.volInfo.wh_inmask(obj.removed_voxels)) = false; + + + +classdef fmri_mask_image < image_vector + + properties + + %dat + %dat_descrip = 'Mask data'; + %history = {}; + + %volInfo = struct('XYZ', [0 0 0]', 'XYZmm', [0 0 0]', 'M', zeros(4)); + volInfo_descrip = 'Volume and in-area mask info from iimg_read_img'; + + space_defining_image_name = char(''); + + % volInfo has this: + % fname + % mat + % dim + % dt + % pinfo + % n + % descrip + % private + % nvox + % image_indx + + end + + methods + + function obj = fmri_mask_image(image_name, varargin) + + % class constructor method + % takes as input a mask image name + + % --------------------------------- + % Create empty image_vector object, and return if no additional + % arguments + % --------------------------------- + + obj.dat = []; + obj.volInfo = []; + obj.image_names = []; + obj.fullpath = char([]); + obj.files_exist = false; + obj.history = {}; + obj.volInfo_descrip = 'Empty'; + obj.space_defining_image_name = char(''); + + if nargin == 0 + return + + elseif isa(image_name, 'image_vector') + % map from existing image vector + % copy legal fields + + N = fieldnames(image_name); + Nmask = fieldnames(obj); + for i = 1:length(Nmask) + if ~isempty(strmatch(Nmask{i}, N)) + obj.(Nmask{i}) = image_name.(Nmask{i}); + end + end + + obj.dat = any(obj.dat, 2); + + elseif ischar(image_name) + + % initialize empty, return if no inputs + obj.dat = []; + obj.dat_descrip = 'Mask data'; + obj.volInfo = struct('XYZ', [0 0 0]', 'XYZmm', [0 0 0]', 'M', zeros(4)); + obj.volInfo_descrip = 'Volume and in-area mask info from iimg_read_img'; + obj.space_defining_image_name = char(''); + obj.history = {}; + + if nargin == 0 + return + end + + [obj.volInfo, obj.dat] = iimg_read_img(image_name, 2, 1, 1); % reads first vol only + obj.dat = single(obj.dat(obj.volInfo.wh_inmask)); + + obj.dat_descrip = image_name; + obj.removed_voxels = false(size(obj.dat, 1), 1); + + doimplicit = strmatch('implicit', varargin); + + if doimplicit + + fprintf('Calculating implicit mask '); + [dummy, dummy, nvox, is_inmask] = fmri_mask_thresh_canlab(obj); + fprintf('%3.0f voxels in, %3.0f voxels out of mask.\n', nvox, sum(~is_inmask)); + + obj = remove_empty(obj, ~is_inmask, []); + + end + +% if dofull +% maskobj.dat = ones(size(maskobj.volInfo.image_indx), 'single'); +% maskobj.removed_voxels = []; +% maskobj.volInfo.wh_inmask = (1:maskobj.volInfo.nvox)'; +% end +% + + end % data type/nargin switch + + end % function + + + end % methods + +end % classdef diff --git a/@fmri_mask_image/resample_to_image_space.m b/@fmri_mask_image/resample_to_image_space.m new file mode 100644 index 00000000..8b8f5d4a --- /dev/null +++ b/@fmri_mask_image/resample_to_image_space.m @@ -0,0 +1,131 @@ +function obj = resample_to_image_space(obj, sampleto, varargin) +% obj = resample_to_image_space(obj, sampleto ) +% +% Resamples data in an fmri_mask_image object (obj) to the space of another +% image (e.g., a functional image, for data extraction) +% The volInfo field will be the same as the sampleto volume info. +% The mask will have zeros in obj.dat for out-of-mask voxels. +% THIS FUNCTION USES SCN_MAP_IMAGE AND REQUIRES THAT THE ORIGINAL IMAGE BE +% AVAILABLE ON DISK. Multiple resamplings will break the function because +% the new space will be different from the original one on disk. Use the +% more general resample_space. +% +% % NOTE: Mask is *reloaded* from original data if space is remapped, and you +% cannot use manual thresholding of the mask. This is a feature of the +% map_to_image_space method and scn_map_image +% +% obj must be an fmri_mask_image object +% sampleto can be either: +% 1) An image name to sample to +% 2) Another fmri_mask_image object (but image must exist on path!) +% +% SEE ALSO: resample_space, for a method that does not require images to +% exist on disk on the path. +% +% Optional inputs: +% 'mask' : Apply sampleto as mask so that only voxels in the sampleto mask +% are retained in obj.dat. +% +% THIS FUNCTION WORKS, BUT IS DEPRECATED BECAUSE RESAMPLE_SPACE IS MORE +% GENERAL. resample_space does not require the resampling of the original +% image from disk, which this does. resample_space is slower, though. + +% Programmers' notes +% Tor: July 2011: Edited because old version will apply mask when resampling +% space. Edited default behavior to NOT mask with voxels only in sampleto +% space. This could cause bugs in other functions that need to be worked +% out. The optional argument 'mask' should produce the old default +% behavior. +% +% Oct 30, 2011: obj.dat field after sampling did not conform to standard, +% because only in-mask voxels in volInfo were not selected. This was +% fixed. + +switch class(sampleto) + case 'char' + image_name_to_sample_to = sampleto; + + volInfo_to = iimg_read_img(sampleto, 2, 1, 1); % read data from file, first volume only + + case {'image_vector', 'fmri_data', 'fmri_mask_image', 'statistic_image'} + + if any(strmatch('space_defining_image_name', fieldnames(sampleto), 'exact')) && ~isempty(sampleto.space_defining_image_name) + image_name_to_sample_to = sampleto.space_defining_image_name; + else + image_name_to_sample_to = sampleto.volInfo.fname; + end + + volInfo_to = sampleto.volInfo; + + otherwise + error('fmri_mask_image.resample_to_image_space: illegal sampleto input.'); +end + +obj.space_defining_image_name = image_name_to_sample_to; + +% do most of the work here. +obj.dat = scn_map_image(obj, volInfo_to); + +obj.dat = obj.dat(:); + +obj.history{end+1} = sprintf('Used image %s', obj.volInfo.fname); +obj.history{end+1} = sprintf('Sampled to space of %s', image_name_to_sample_to); + + + +obj.dat(isnan(obj.dat)) = 0; + +if any(strcmp(varargin, 'mask')) + obj.volInfo = volInfo_to; % This will define mask with voxels in sampleto image + + % Need the same voxels in each + % Possibilities: mask could have voxels outside image-defining mask + if size(obj.dat, 1) == volInfo_to.n_inmask % ~isfield(volInfo_to, 'n_inmask') || + % OK, do nothing + elseif size(obj.dat, 1) == size(volInfo_to.image_indx, 1) + obj.dat = obj.dat(volInfo_to.image_indx, :); + else + disp('Error: mask data is wrong size. Debug me.') + keyboard + end +else + + % Update volInfo structure to reflect resampling + % ------------------------------------------------------- + + obj.volInfo.fname = 'REMOVED: CHANGED SPACE'; + obj.volInfo.mat = volInfo_to.mat; + obj.volInfo.dim = volInfo_to.dim; + obj.volInfo.dt = volInfo_to.dt; + + + obj.volInfo(1).descrip = sprintf('Space of %s', image_name_to_sample_to); + + % Stuff for extended volInfo entries + obj.volInfo(1).image_indx = ~isnan(obj.dat) & abs(obj.dat) > 10*eps ; % indices of all voxels in mask + obj.volInfo(1).nvox = length(obj.dat); + + obj.volInfo(1).wh_inmask = find(obj.volInfo(1).image_indx); % indices of all voxels in mask + obj.volInfo(1).n_inmask = length(obj.volInfo(1).wh_inmask); + + [i, j, k] = ind2sub(obj.volInfo(1).dim(1:3), obj.volInfo(1).wh_inmask); + obj.volInfo(1).xyzlist = [i j k]; + + if obj.volInfo(1).n_inmask < 50000 + obj.volInfo(1).cluster = spm_clusters(obj.volInfo(1).xyzlist')'; + else + obj.volInfo(1).cluster = ones(obj.volInfo(1).n_inmask, 1); + end + + % obj.dat should contain only in-mask values. + obj.dat = obj.dat(obj.volInfo(1).wh_inmask, :); + +end + + + +obj.removed_voxels = false(size(obj.dat, 1), 1); + + +end % function + diff --git a/@fmri_model/add.m b/@fmri_model/add.m new file mode 100644 index 00000000..7b28fb15 --- /dev/null +++ b/@fmri_model/add.m @@ -0,0 +1,194 @@ +function obj = add(obj, varargin) + +if nargin == 1 + return +end + +% Identify which inputs are valid attribute names: +% Strings that are not preceded by another attribute name. + +% all valid fieldnames +valid_names = fieldnames(obj); + +[isattributename, isothermethod] = deal(false(1, length(varargin))); + +for i = 1:length(varargin) + if ischar(varargin{i}) && ~isempty(varargin{i}) && (i == 1 || ~isattributename(i - 1)) + + % Look for a field (attribute) with the input name + wh = strmatch(varargin{i}, valid_names, 'exact'); + + % is valid (existing) field? + if ~isempty(wh) + isattributename(i) = true; + else + % must be another potential input with a special + % method (defined below); or returns warning + isothermethod(i) = true; + end + end +end + +for i = find(isattributename) + % Replace this field value with the input + obj.(varargin{i}) = varargin{i + 1}; + + varargin{i + 1} = []; % eliminate to avoid parsing warnings + isothermethod(i + 1) = false; + + % special methods for specific fields + switch varargin{i} + + end +end + +% Now process arguments that may have a special meaning for this object +% class + +for i = find(isothermethod) + + if ~isothermethod(i) + % we have removed this because it was a string value for + % another input + continue + end + + switch varargin{i} + + + + case 'units' + valid_entries = {'secs', 'scans'}; + target_field = 'UNITS'; % field to add to + + % check for valid inputs and add if OK + obj.xBF = add_values_string(obj.xBF, valid_entries, target_field, varargin{i}, varargin{i + 1}); + + isothermethod(i + 1) = false; + + case 'onsets' + + % special function for checking SPM-style format rules + [ons, nsess, nconds] = check_req_and_length(obj, varargin{i+1}); + + indx = 1; + for ss = 1:nsess + for cond = 1:nconds + obj.Sess(ss).U(cond).ons = ons{indx}; + indx = indx + 1; + end + end + + isothermethod(i + 1) = false; + + case 'durations' + + % special function for checking SPM-style format rules + [durs, nsess, nconds] = check_req_and_length(obj, varargin{i+1}); + + indx = 1; + for ss = 1:nsess + for cond = 1:nconds + obj.Sess(ss).U(cond).dur = durs{indx}; + indx = indx + 1; + end + end + + isothermethod(i + 1) = false; + + case 'condition_names' + + % special function for checking SPM-style format rules + [names, nsess, nconds] = check_req_and_length(obj, varargin{i+1}); + + indx = 1; + for ss = 1:nsess + for condname = 1:nconds + obj.Sess(ss).U(condname).name = names{indx}; + indx = indx + 1; + end + end + + isothermethod(i + 1) = false; + + case {'pm', 'mod', 'modulator', 'PM', 'pmname', 'pmnames', 'PMnames'} + % + % Format: 'pm', *number of condition to modulate*, '*modulator name*', {cell with modulators for each session} + + switch varargin{i} + case {'pm', 'mod', 'modulator', 'PM'} + add2field = 'P'; + fprintf('Adding modulator values for conditions\n') + + case {'pmname', 'pmnames', 'PMnames'} + add2field = 'name'; + fprintf('Adding modulator names for conditions\n') + + otherwise + end + + [pm, nsess, nconds] = check_req_and_length(obj, varargin{i + 1}); + + + + indx = 1; + for ss = 1:nsess + for condname = 1:nconds + obj.Sess(ss).U(condname).P.(add2field) = pm{indx}; + indx = indx + 1; + end + end + + isothermethod(i + 1) = false; + + otherwise + warning('inputargs:BadInput', 'Unknown field: %s', varargin{i}); + + end % other valid entries + +end % process inputs + + +end + + + + + +function inputstruct = add_values_string(inputstruct, valid_entries, target_field, infieldname, values) + +if isempty(strmatch(values, valid_entries)) + + fprintf('Invalid entry for %s\nValid entries are:', infieldname); + disp(char(valid_entries{:})) + error('Exiting.'); + +else + + inputstruct.(target_field) = values; + +end + +end + + + + +function [inputvalue, nsess, nconds] = check_req_and_length(obj, inputvalue) + +fnames = fieldnames(obj); + +if isempty(strmatch('nscan', fnames)) + error('Define number of sessions with .nscan field before adding onsets, durations, names'); +end + +nsess = length(obj.nscan); + +if mod(length(inputvalue), nsess) + error('Length mismatch: length (n cells) of onsets, etc. must be evenly divisible by num sessions.'); +end + +nconds = length(inputvalue) ./ nsess; + +end + diff --git a/@fmri_model/build.m b/@fmri_model/build.m new file mode 100644 index 00000000..03061dfc --- /dev/null +++ b/@fmri_model/build.m @@ -0,0 +1,167 @@ +function obj = build(obj) +% +% obj = build(fmri_model_obj) +% +% Build the design matrix (xx) for an fmri_model object +% +% We assume that the same conditions are modeled for each session +% We assume that you have one basis set per condition (this is different +% from SPM, which only allows a single basis set across all conditions) + + +% Define sessions and number of conditions +nsess = length(obj.Sess); + +% add predictors for each session +[X, C, B, names] = deal(cell(1, nsess)); + +% Check assumptions and basis set +% We assume that the same conditions are modeled for each session +obj = check_model(obj); + +% Do most of the work here; get regressors, and modulators, and names +for s = 1:nsess + [X{s}, obj.Sess(s).delta, C{s}, B{s}, names{s}] = get_session_X(obj, s); +end + + +switch obj.build_method + case 'Separate sessions' + % of-interest part of design + iH = blkdiag(X{:}); + + obj.xX(1).name = cat(2, names{:}); + + case 'Concatenated sessions' + iH = cat(1, (X{:})); + + nconds = length(obj.Sess(1).U); + obj.xX(1).name = cell(1, nconds); + + obj = concat_names(obj); + + + otherwise + error('Unknown build method for fmri_model object. See help for valid strings.'); + +end + +% of-interest part of design +%iH = blkdiag(X{:}); +iC = blkdiag(C{:}); +iG = []; +iB = blkdiag(B{:}); + +obj.xX(1).X = [iH iC iG iB]; + +% indices for each partition +niH = size(iH); +niC = size(iC); +niG = size(iG); +niB = size(iB); + +sz = [niH; niC; niG; niB]; +if any(sz(:, 1) ~= sz(1) & sz(:, 1) ~= 0) + disp('Number of rows in interest, covs, global, and baseline partitions do not match.'); + disp('Debug me!') + keyboard +end + +wh = [ones(1, sz(1, 2)) 2*ones(1, sz(2, 2)) 3*ones(1, sz(3, 2)) 4*ones(1, sz(4, 2))]; +obj.xX(1).iH = find(wh == 1); +obj.xX(1).iC = find(wh == 2); +obj.xX(1).iG = find(wh == 3); +obj.xX(1).iB = find(wh == 4); + +% Condition assignments matrix +obj.xX(1).cond_assignments = get_condition_assignments(obj); + + +end % function + + + +% ---------------------------------------------- +% ---------------------------------------------- + +% SUB-functions + +% ---------------------------------------------- +% ---------------------------------------------- + + +function myname = replaceblanks(myname) + +myname(myname == ' ') = '-'; +tmp = diff(double(myname)) == 0; % fixed 2010a vs. b compat. +myname([false tmp] & myname == '-') = []; + +end + + +function obj = check_model(obj) + +nsess = length(obj.Sess); + + +nconds = length(obj.Sess(1).U); +if nconds == 0 + disp('You must assign conditions and onsets using fmri_model or add (method)') + disp('before building the design matrix.'); + error('Exiting'); +end +for i = 2:nsess + if any(length(obj.Sess(1).U) ~= nconds) + disp('The number of conditions must be the same for all sessions.') + error('Exiting'); + end +end + +% replicate xBF if necessary +if length(obj.xBF) == 1 + obj.xBF(2:nconds) = obj.xBF(1); +elseif length(obj.xBF) < nconds + disp('The number of basis set structures is > 1 but not equal to the number of conditions.') + error('Exiting'); +end + +for i = 1:nconds + obj.xBF(i).name = sprintf('%s for Condition %3.0f', obj.xBF(i).name, i); +end + +end + + + +function obj = concat_names(obj) + +% names +% --------------------------------------- +[obj.xX(1).name{:}] = deal(obj.Sess(1).U(:).name); + +% add PM names +s = 1; +for i = 1:length(obj.Sess(1).U) + is_pm = isfield(obj.Sess(s).U(i), 'P') && ~isempty(obj.Sess(s).U(i).P) && isfield(obj.Sess(s).U(i).P, 'P') && ~isempty(obj.Sess(s).U(i).P.P); + if is_pm + obj.xX(1).name{end + 1} = [obj.Sess(s).U(i).name '-' obj.Sess(s).U(i).P.name]; + end +end + +% Remove session labels from names +for i = 1:length(obj.xX(1).name) + myname = replaceblanks(obj.xX(1).name{i}); + wh = findstr(myname, 'Sess-'); + if ~isempty(wh) + to_remove = []; + for j = 1:length(wh) + to_remove = [to_remove wh(j) : wh(j) + 6]; + end + myname(to_remove) = []; + end + + obj.xX(1).name{i} = myname; +end + +end + diff --git a/@fmri_model/build_single_trial.m b/@fmri_model/build_single_trial.m new file mode 100644 index 00000000..d45efc3b --- /dev/null +++ b/@fmri_model/build_single_trial.m @@ -0,0 +1,210 @@ +function obj = build_single_trial(obj, inputhrf) +% +% obj = build_single_trial(fmri_model_obj, inputhrf) +% +% Build a single-trial design matrix (xx) for an fmri_model object +% +% We assume that the same conditions are modeled for each session +% We assume that you have one basis set per condition (this is different +% from SPM, which only allows a single basis set across all conditions) +% +% This is used in single_trial_estimates, which assumes that you have +% estimated an initial model and saved image data. +% +% The idea behind this is somewhat different from other canlab single-trial +% analyses, in that it takes in a single, custom HRF for each condition, +% rather than using a basis set. In single_trial_estimates, custom HRFs +% are created for each voxel by using the condition- and voxel-specific hrf +% estimates stored during model fitting. +% The sequence would be: +% 1 - robustfit(my_model), to fit average model and get HRF est for each +% voxel +% 2 - single_trial_estimates(my_model), to use this function to build +% single-trial design matrices and fit them. +% +% inputhrf should be a cell array of length nconds (number of conditions). + + +% Check assumptions and basis set +% We assume that the same conditions are modeled for each session +obj = check_model(obj); + +% ---------------------------------------------- +% Define sessions and number of conditions +% ---------------------------------------------- + +nsess = length(obj.Sess); +nconds = length(obj.Sess(1).U); +TR = obj.xY.RT; + +[sess_delta, sess_conditions, sess_X, sess_C, sess_B] = deal(cell(1, nsess)); + + +for s = 1:nsess + +[ons, name] = deal(cell(1, nconds)); + +[ons{:}] = deal(obj.Sess(s).U(:).ons); +%[name{:}] = deal(obj.Sess(s).U(:).name); + +% make sure onsets are in TRs, not secs +switch obj.xBF(1).UNITS + case 'secs' + % onsets are in sec, convert + for i = 1:nconds + ons{i} = ons{i} ./ TR; + end + + case {'tr', 'TR', 'trs'} + % ok, do nothing +end + +% ---------------------------------------------- +% Build predictors for each session +% ---------------------------------------------- + +delta = onsets2delta(ons, obj.nscan(s)); + +ns = size(delta, 1); + +[trialdelta, cond_assignment] = deal(cell(1, nconds)); + +% ---------------------------------------------- +% For each condition, parse into separate columns +% for each onset (single-trial) +% ---------------------------------------------- +for j = 1:nconds + + wh = find(delta(:, j)); + + trialdelta{j} = false(ns, length(wh)); + + for k = 1:length(wh) + trialdelta{j}(wh(k), k) = true; + end + + cond_assignment{j} = ones(length(wh), 1); + + bf = obj.xBF(j); + xvals = 0:TR:bf.length - TR; + hrf = interp1(xvals, inputhrf{j}(1:length(xvals)), 1:bf.dt:bf.length, 'spline', 'extrap'); + + condX{j} = getPredictors(trialdelta{j}, hrf, 16); + +end % conditions + +sess_delta{s} = cat(2, trialdelta{:}); % not needed? + +sess_conditions{s} = blkdiag(cond_assignment{:}); + +% design matrix - of interest +sess_X{s} = cat(2, condX{:}); + +% design matrix - nuisance +sess_C{s} = obj.Sess(s).C.C; + +sess_B{s} = ones(obj.nscan(s), 1); + +end % Session + +% ---------------------------------------------- +% Put the pieces together +% ---------------------------------------------- + +obj.xX.X = [blkdiag(sess_X{:}) blkdiag(sess_C{:}) blkdiag(sess_B{:})]; + +obj.xX.cond_assignments = cat(1, sess_conditions{:}); + +obj.xX.iH = find(any(obj.xX.cond_assignments, 2)); +obj.xX.iC = find(~any(obj.xX.cond_assignments, 2)); +obj.xX.iB = size(obj.xX.X, 2) - nsess : size(obj.xX.X, 2); + + +end % function + + + +% ---------------------------------------------- +% ---------------------------------------------- + +% SUB-functions + +% ---------------------------------------------- +% ---------------------------------------------- + + +function myname = replaceblanks(myname) + +myname(myname == ' ') = '-'; +tmp = diff(double(myname)) == 0; % fixed 2010a vs. b compat. +myname([false tmp] & myname == '-') = []; + +end + + +function obj = check_model(obj) + +nsess = length(obj.Sess); + + +nconds = length(obj.Sess(1).U); +if nconds == 0 + disp('You must assign conditions and onsets using fmri_model or add (method)') + disp('before building the design matrix.'); + error('Exiting'); +end +for i = 2:nsess + if any(length(obj.Sess(1).U) ~= nconds) + disp('The number of conditions must be the same for all sessions.') + error('Exiting'); + end +end + +% replicate xBF if necessary +if length(obj.xBF) == 1 + obj.xBF(2:nconds) = obj.xBF(1); +elseif length(obj.xBF) < nconds + disp('The number of basis set structures is > 1 but not equal to the number of conditions.') + error('Exiting'); +end + +for i = 1:nconds + obj.xBF(i).name = sprintf('%s for Condition %3.0f', obj.xBF(i).name, i); +end + +end + + + +function obj = concat_names(obj) + +% names +% --------------------------------------- +[obj.xX(1).name{:}] = deal(obj.Sess(1).U(:).name); + +% add PM names +s = 1; +for i = 1:length(obj.Sess(1).U) + is_pm = isfield(obj.Sess(s).U(i), 'P') && ~isempty(obj.Sess(s).U(i).P) && isfield(obj.Sess(s).U(i).P, 'P') && ~isempty(obj.Sess(s).U(i).P.P); + if is_pm + obj.xX(1).name{end + 1} = [obj.Sess(s).U(i).name '-' obj.Sess(s).U(i).P.name]; + end +end + +% Remove session labels from names +for i = 1:length(obj.xX(1).name) + myname = replaceblanks(obj.xX(1).name{i}); + wh = findstr(myname, 'Sess-'); + if ~isempty(wh) + to_remove = []; + for j = 1:length(wh) + to_remove = [to_remove wh(j) : wh(j) + 6]; + end + myname(to_remove) = []; + end + + obj.xX(1).name{i} = myname; +end + +end + diff --git a/@fmri_model/fmri_model.m b/@fmri_model/fmri_model.m new file mode 100644 index 00000000..6f0b684a --- /dev/null +++ b/@fmri_model/fmri_model.m @@ -0,0 +1,249 @@ + +% fmri_model +% +% Specify and/or create an fmri_model object. This object is designed to contain +% all the information about a model necessary to create a design matrix +% and, when combined with an fmri_data object, to fit the model. +% +% The information contained in this object (its attributes) are designed to +% be very similar or identical to those defined by SPM in using +% spm_fmri_design.m. This should help with inter-operability and standards +% for storing information. +% +% See methods(fmri_model) for things you can do with this. +% See the code of this function for additional details about all the +% fields. +% +% The general way to create/initialize CANlab objects is to call them with +% no or minimal input arguments. In this case, TR is required as the first +% input. After that, pairs of arguments can be entered in the +%'fieldname', value format standard for Matlab inputs. If the fields are +% valid attributes of the object, they will be entered. +% +% For example: +% +% my_model = fmri_model(2); +% creates an empty fmri_model object with a TR of 2 (which is used to +% create a default b-spline basis set for the HRF. +% +% my_model = fmri_model(2, 'nscan', [198 198 198]); +% creates an empty structure but assigns data to the field nscans, number +% of scans per session. +% +% my_model = fmri_model(TR, 'nscan', nscan, 'units', 'secs', 'onsets', ons_temperature, 'condition_names', names_temperature); +% does the above and also adds onsets and names to the appropriate place +% in the object structure. +% my_model.Sess(1).U(2).name : names for session 1, condition 2 +% my_model.Sess(1).U(2).ons : onsets for session 1, condition 2 in UNITS(secs or TRs) +% +% add methods: +% There are some special methods to add specific types of data: +% +% 'onsets', {cell array of onsets for conditions within sessions} +% +% 'condition_names', {cell array of condition names} -- assumed to be the same for all sessions +% +% 'pm', '*name of condition to modulate*', '*modulator name*', {cell with modulators for each session} +% +% You can also specify different basis sets for different conditions, which +% SPM will not allow you to do. Here is an example of a process of +% creating an fmri_model object from onsets, etc., building it, and then +% replacing the basis set for one event type with another one. +% +% reportmod_model = fmri_model(TR, 'nscan', nscan, 'units', 'secs', 'onsets', ons_reportmod, ... +% 'condition_names', names_reportmod, 'pm', PM, 'pmnames', PM_names); +% +% reportmod_model = build(reportmod_model); +% plot(reportmod_model) +% +% % Generate a new basis set for the Anticipation conditions (condition 1) +% [xBF_hires, xBF] = fmri_spline_basis(2, 'length', 12, 'nbasis', 3, 'order', 3, 'plot'); +% reportmod_model = replace_basis_set(reportmod_model, 1, xBF_hires); +% reportmod_model = build(reportmod_model); +% plot(reportmod_model) +% + +% SPM.Sess(s) +% U: - Input structure array +% C: - User specified covariate structure +% row: - scan indices for session s +% col: - effect indices for session s +% Fc: - F Contrast information for input-specific effects +% +% SPM.xX +% X: - design matrix +% iH: - vector of H partition (indicator variables) indices +% iC: - vector of C partition (covariates) indices +% iB: - vector of B partition (block effects) indices +% iG: - vector of G partition (nuisance variables) indices +% name: - cellstr of names for design matrix columns +% + +% 3rd level +% ------------------------------------------------------------------ +% SPM.Sess(s).U +% dt: - time bin length {seconds} +% name: - {1 x j} cell of names for each input or cause +% ons: - (q x 1) onsets for q trials {in UNITS} +% dur: - (q x 1) durations for trials {in UNITS} +% P: - Parameter stucture +% u: - (t x j) inputs or stimulus function matrix +% pst: - (1 x k) peristimulus times (seconds) +% +% +% SPM.Sess(s).C +% +% C: - [kx1 double] of user specified regressors +% name: - {1xk} cellstr of regressor names +% +% +% SPM.Sess(s).Fc +% +% i: - F Contrast colums for input-specific effects +% name: - F Contrast names for input-specific effects +% +% +% 4th level +% -------------------------------------------------------------- +% SPM.Sess(s).U(i).P(p) +% +% +% name: - parameter name +% P: - (q x 1) parameter matrix +% h: - order of polynomial expansion (0 = none) +% i: - sub-indices of U(i).u for plotting + +classdef fmri_model + + properties + + % custom: not in SPM + build_method + history + + % 1st level + % -------------------------------------------------------------------------- + % SPM. + xY % : [1x1 struct] - data structure + nscan % : [1xs double] - nscan(s) = number of scans in session s + xBF % : [1x1 struct] - Basis function structure + Sess % : [1xs struct] - Session structure array + xX % : [1x1 struct] - Design matrix structure + + end % properties + + + + methods + + % Class constructor + function obj = fmri_model(TR, varargin) + % + % [obj, cl_with_averages] = fmri_data(image_names, mask_image, varargin) + % + % Reads a set of image files and a mask image, and returns + % an fmri_data object with data for all in-mask voxels. + + % --------------------------------- + % Create empty fmri_data object, and return if no additional + % arguments + % --------------------------------- + + if nargin == 0 + error('Must define TR, repetition time for scans, as first input.') + end + + obj.build_method = 'Separate sessions'; + + obj.xY = []; % : [1x1 struct] - data structure + obj.nscan = []; % : [1xs double] - nscan(s) = number of scans in session s + obj.xBF = []; % : [1x1 struct] - Basis function structure + obj.Sess = []; % : [1xs struct] - Session structure array + obj.xX = []; + + % Default basis set + % ---------------------------------------------------------------------- + obj.xY.RT = TR; % : - repetition time {seconds) + + obj.xBF = fmri_spline_basis(TR, 0); + % obj.xBF.name: - name of basis set + % obj.xBF.length: - support of basis set {seconds} + % obj.xBF.order: - order of basis set + % obj.xBF.bf: - basis set matrix + % obj.xBF.dt = TR ./ 16; % : - length of time bin {seconds} + + obj.xBF.T = 16; % : - number of time bins per scan + obj.xBF.T0 = 1; % : - first time bin (see slice timing) + obj.xBF.UNITS = []; %: - 'scans'|'secs' - units in which onsets are specified + obj.xBF.Volterra = 1; % : - 1|2 - order of [Volterra] convolution + + % Parameters for parametric modulation + P = struct('name', '', 'P', [], 'h', 0, 'dur', [], 'i', []); + + % Onset structure + U = struct('dt', TR ./ 16, 'name', {}, 'ons', [], 'dur', 1, 'P', P, 'u', [], 'pst', []); + + % Covariate structure + C = struct('C', [], 'name', {'User-specified regressors here'}); + + % Session structure + S = struct('U', U, 'C', C, 'row', [], 'col', [], 'Fc', []); + + obj.Sess = S; + + % Design structure + obj.xX = struct('X', [], 'iH', [], 'iC', [], 'iB', [], 'iG', [], 'name', {}); + + % The code below can be generic to any class definition + % It parses 'fieldname', value pairs of inputs + % and returns a warning if unexpected strings are found. + + if nargin == 1 + return + end + + % all valid fieldnames + valid_names = fieldnames(obj); + + for i = 1:length(varargin) + if ischar(varargin{i}) + + % Look for a field (attribute) with the input name + wh = strmatch(varargin{i}, valid_names, 'exact'); + + % behaviors for valid fields + if ~isempty(wh) + + obj.(varargin{i}) = varargin{i + 1}; + + % eliminate strings to prevent warnings on char + % inputs + if ischar(varargin{i + 1}) + varargin{i + 1} = []; + end + + % special methods for specific fields + switch varargin{i} + + end + + else + % Unique to this method: special subfields/other valid + % entries + % Try to add using the add method, which returns a + % warning if the field name is invalid. + + obj = add(obj, varargin{i}, varargin{i + 1}); + + varargin{i + 1} = []; % eliminate to avoid confusing the parsing + + end % not empty + end % string input + end % process inputs + + + end % class constructor function + end % properties + + +end % classdef \ No newline at end of file diff --git a/@fmri_model/get_condition_assignments.m b/@fmri_model/get_condition_assignments.m new file mode 100644 index 00000000..8e8291f7 --- /dev/null +++ b/@fmri_model/get_condition_assignments.m @@ -0,0 +1,106 @@ +function cmatrix = get_condition_assignments(obj) +% +% Condition assignments +% --------------------------------------------------------------------- +% - Indicator matrix coding for which columns in X belong to the same +% modeled condition, and are part of the same HRF fit +% - There is one set of columns for each condition modeled, and one set of +% columns for each parametric modulator of each condition +% - Because parametric modulators may not exist for all conditions, we need +% to build this dynamically for modulators. +% +% Design matrix build (which calls method get_session_X) builds columns in +% this order: +% All within Session: +% Regressors of interest, basis functions within conditions +% Parametric modulators, basis functions within conditions +% Covariates of no interest +% Then: +% Baselines (session/run intercepts) +% +% This method is called automatically in the build method. + +switch obj.build_method + case 'Separate sessions' + nsess = length(obj.Sess); + + + case 'Concatenated sessions' + nsess = 1; + + + otherwise + error('Unknown build method for fmri_model object. See help for valid strings.'); + +end + + +for s = 1:nsess + nconds(s) = length(obj.Sess(s).U); +end +if any(diff(nconds)), error('Variable numbers of conditions; this should have been caught in build.'); end + + +% cell of indicator matrices for each session separately +all_indic = cell(1, nsess); + +for s = 1:nsess + + % regs of interest + % ----------------------------------------------------------- + sessindic = {}; + + for i = 1:nconds(s) + + + % Allow for variable number of bfs. + nbf = size(obj.xBF(i).bf, 2); + + % indicate a set of regressors based on basis set + sessindic{i} = true(nbf, 1); + + end + + % concatenate into block diagonal + sessindic = blkdiag(sessindic{:}); + + % Modulators + % ----------------------------------------------------------- + pmindic = {}; + + for i = 1:nconds(s) + is_pm = isfield(obj.Sess(s).U(i), 'P') && ~isempty(obj.Sess(s).U(i).P) && isfield(obj.Sess(s).U(i).P, 'P') && ~isempty(obj.Sess(s).U(i).P.P); + + if is_pm + % Allow for variable number of bfs. + nbf = size(obj.xBF(i).bf, 2); + + pmindic{end + 1} = true(nbf, 1); + end + end + if ~isempty(pmindic) + pmindic = blkdiag(pmindic{:}); + else + pmindic = []; + end + + % add them together in block diag form + all_indic{s} = blkdiag(sessindic, pmindic); + +end % session + +cmatrix = logical(blkdiag(all_indic{:})); + +ncols = size(obj.xX.X, 2); + +if size(cmatrix, 1) > ncols + error('More conditions of interest than rows in obj.xX.X!! Must build xX before running this.'); +end + +% add rows of zeros to match total number of columns +z = false(ncols - size(cmatrix, 1), size(cmatrix, 2)); + +cmatrix = [cmatrix; z]; + + +end % function diff --git a/@fmri_model/get_session_X.m b/@fmri_model/get_session_X.m new file mode 100644 index 00000000..976963a8 --- /dev/null +++ b/@fmri_model/get_session_X.m @@ -0,0 +1,162 @@ +function [Xs, delta, C, B, names] = get_session_X(obj, s) +% [Xs, delta, C, B, names] = get_session_X(obj, session number) +% +% +% Get design matrix (predictors) for one session of fmri_model object, using +% basis functions defined in the object and onsets for one session (s). +% + +% ---------------------------------------------- +% Define sessions and number of conditions +% ---------------------------------------------- + +nsess = length(obj.Sess); + +if s > nsess, error('Session %3.0f does not exist', s); end + + +TR = obj.xY.RT; + +nconds = length(obj.Sess(s).U); + +[ons, name] = deal(cell(1, nconds)); + +[ons{:}] = deal(obj.Sess(s).U(:).ons); +[name{:}] = deal(obj.Sess(s).U(:).name); + +% make sure onsets are in TRs, not secs +switch obj.xBF(1).UNITS + case 'secs' + % onsets are in sec, convert + for i = 1:nconds + ons{i} = ons{i} ./ TR; + end + + case {'tr', 'TR', 'trs'} + % ok, do nothing +end + +% ---------------------------------------------- +% Predictors +% ---------------------------------------------- + +delta = onsets2delta(ons, obj.nscan(s)); + +% Of-interest part of design matrix +% time res is defined as TR / 16, so build and downsample by 16 to TR +% Allow for different basis sets for each condition. + +Xs = cell(1, nconds); + +for i = 1:nconds + + bf = obj.xBF(i).bf; + + Xs{i} = getPredictors(delta(:, i), bf, 16); +end + +Xs = cat(2, Xs{:}); + +% covariate part of design matrix +C = []; +if ~isempty(obj.Sess(s).C) && isfield(obj.Sess(s).C, 'C') + C = obj.Sess(s).C.C; +end + +% baseline +% ---------------------------------------------- + +B = ones(obj.nscan(s), 1); + +% ---------------------------------------------- +% modulators +% ---------------------------------------------- + +Xs_pm = cell(1, nconds); + +for i = 1:nconds + is_pm = isfield(obj.Sess(s).U(i), 'P') && ~isempty(obj.Sess(s).U(i).P) && isfield(obj.Sess(s).U(i).P, 'P') && ~isempty(obj.Sess(s).U(i).P.P); + + if is_pm + + pm_vals = {obj.Sess(s).U(i).P.P}; + + model = onsets2parametric_mod_X(ons(i), pm_vals, obj.nscan(s), obj.xBF(i).bf, 16); + + Xs_pm{i} = model; + + end + +end + +Xs_pm = cat(2, Xs_pm{:}); + +% add to Xs +Xs = [Xs Xs_pm]; + +% ---------------------------------------------- +% get names for each BF +% Allow variable number of basis fcns for each condition +% ---------------------------------------------- + +names = {}; + +for i = 1:nconds + + nbf = size(obj.xBF(i).bf, 2); + + for j = 1:nbf + myname = [name{i} ' BF' num2str(j)]; + myname = replaceblanks(myname); + + names{end+1} = myname; + end +end + +all_pmnames = {}; + +for i = 1:nconds + is_pm = isfield(obj.Sess(s).U(i), 'P') && ~isempty(obj.Sess(s).U(i).P) && isfield(obj.Sess(s).U(i).P, 'P') && ~isempty(obj.Sess(s).U(i).P.P); + + if is_pm + % Get param mod names + % ------------------------------------------------ + pmnames = cell(1, nbf); + + for j = 1:nbf + myname = [obj.Sess(s).U(i).P.name ' BF' num2str(j)]; + myname = replaceblanks(myname); + + pmnames{1, j} = myname; + end + + all_pmnames = cat(2, all_pmnames, pmnames{:}); + + end +end + +names = names(:)'; + +names = [names all_pmnames]; + +end + +% ---------------------------------------------- +% ---------------------------------------------- + + +% ---------------------------------------------- +% ---------------------------------------------- + + +function myname = replaceblanks(myname) + +myname(myname == ' ') = '-'; +% this works in 2010b but not a... +%tmp = diff(num2str(myname)) == 0; + +tmp = diff(double(myname)) == 0; +myname([false tmp] & myname == '-') = []; + +end + diff --git a/@fmri_model/plot.m b/@fmri_model/plot.m new file mode 100644 index 00000000..daa6ab7b --- /dev/null +++ b/@fmri_model/plot.m @@ -0,0 +1,157 @@ +function plot(obj) +% +% plot(obj) +% plot an fmri_model object + +% ---------------------------------------------- +% Setup +% ---------------------------------------------- + +TR = obj.xY.RT; + +bf = obj.xBF.bf; + +% Define sessions and number of conditions +nsess = length(obj.Sess); + + + +% ---------------------------------------------- +% Image +% ---------------------------------------------- +create_figure('design matrix image'); +imagesc(obj.xX.X); +title('Design matrix (X)'); +set(gca, 'YDir', 'reverse'); +axis tight; +xlabel('Regressors'); +ylabel('Images (TRs)'); +colormap gray +drawnow + +% ---------------------------------------------- +% Basis sets +% ---------------------------------------------- +nconds = length(obj.xBF); +create_figure('basis sets', 1, nconds); +for i = 1:nconds + + subplot(1, nconds, i); + xx = linspace(0, obj.xBF(i).length - 1, size(obj.xBF(i).bf, 1)); + + plot(xx, obj.xBF(i).bf, 'LineWidth', 2); + title(sprintf('Basis set, Cond. %3.0f, %s', i, obj.xX.name{i})); + axis tight + xlabel('Time (sec)'); + +end + + +% ---------------------------------------------- +% Plot +% ---------------------------------------------- + + +switch obj.build_method +% ---------------------------------------------- + case 'Separate sessions' +% ---------------------------------------------- + nr = 2; nc = ceil(nsess) ./ 2; + + create_figure('design matrix', nr, nc); + + for i = 1:nsess + subplot(nr, nc, i) + set(gca, 'FontSize', 10); + + [Xs, dummy, C, dummy, names] = get_session_X(obj, i); + + plot_subfcn(obj, Xs, C, names); + title(sprintf('Session %3.0f', i)); + + end + + + + +% ---------------------------------------------- + case 'Concatenated sessions' +% ---------------------------------------------- + create_figure('design matrix'); + + plot_subfcn(obj, obj.xX.X(:, obj.xX.iH), obj.xX.X(:, obj.xX.iC), obj.xX.name); + + otherwise + error('Unknown build method for fmri_model object. See help for valid strings.'); + +end + + +end % main function + + + + +% ---------------------------------------------- +% ---------------------------------------------- + +% SUB-functions + +% ---------------------------------------------- +% ---------------------------------------------- + + +function plot_subfcn(obj, Xs, C, names) + +% ---------------------------------------------- +% SETUP +% ---------------------------------------------- + +% conditions for unique color codes +nconds = size(obj.xX.cond_assignments, 2); + +% bf = obj.xBF.bf; +% nbf = size(bf, 2); + +colors = {'r' 'g' 'b' 'm' [1 .5 0], [0 1 .5], [0 .5 1], [1 0 .5]}; +while length(colors) < nconds, colors = [colors colors]; end + +disp('Plotting design matrix cols'); + +% ---------------------------------------------- +% Plot +% ---------------------------------------------- + +han = plot_matrix_cols([Xs C], 'horiz'); +axis tight +drawnow + +% ***need to fix for Separate Session design + +for i = 1:nconds +% whstart = nbf * (i - 1) + 1; +% whend = nbf * i; + wh = find(obj.xX.cond_assignments(:, i)); + set(han(wh), 'Color', colors{i}); +end + +if ~isempty(C) + names = [names {'Covariates'}]; +end + +yvals = linspace(1, size([Xs C], 2), nconds + 2); +yvals = yvals(2:end-1); + +set(gca, 'YTick', yvals, 'YTickLabel', names) + +xlabel('Time (TRs)') + +title(obj.build_method); + +drawnow + +end + + + + diff --git a/@fmri_model/replace_basis_set.m b/@fmri_model/replace_basis_set.m new file mode 100644 index 00000000..e573e619 --- /dev/null +++ b/@fmri_model/replace_basis_set.m @@ -0,0 +1,34 @@ +function obj = replace_basis_set(obj, condition_num, xBF_hires) +% +% obj = replace_basis_set(obj, condition_num, xBF_hires) +% +% Replace a basis set in an fmri_model object with another one of your +% choosing. +% +% This allows one to use a custom basis set, and also to use different +% basis sets for different trial types. +% +% Each condition across all sessions must be modeled with the same basis +% set. That is, there can be only one basis set per condition, e.g., one +% for anticipation (used in each session) and one for pain. +% +% e.g., generate a custom spline basis set and use that for Condition 1, +% and the standard one for Condition 2: +% +% [xBF_hires, xBF] = fmri_spline_basis(2, 'length', 12, 'nbasis', 3, 'order', 3, 'plot'); + +% save this to get info that is not typically in basis set until after +% model is built. + +oldBF = obj.xBF(1); + +% SPM adds these things here, so we will too, for consistency +xBF_hires.T = oldBF.T; +xBF_hires.T0 = oldBF.T0; +xBF_hires.UNITS = oldBF.UNITS; +xBF_hires.Volterra = oldBF.Volterra; + +obj.xBF(condition_num) = xBF_hires; + +end + diff --git a/@fmri_model/robustfit.m b/@fmri_model/robustfit.m new file mode 100644 index 00000000..38c3dc18 --- /dev/null +++ b/@fmri_model/robustfit.m @@ -0,0 +1,670 @@ +function robustfit(fmri_model_obj, fmri_data_obj, varargin) +% robustfit(fmri_model_obj, fmri_data_obj, [optional args]) +% +% robust fit for a model object to data object +% +% Features: +% spatial smoothing of weights at 12 mm FWHM +% ridge regression ***not yet*** +% +% Preproc scaling: +% 1) Remove covariates using ridge reg; ridge trace for full model +% 2) scale to % signal change across time (cols) OR rank time points (for +% w/i ss predictions??) AND/OR rank or center rows (images; for 'shape' +% analysis +% +% Example: %sig across time, rank across rows: relative % sig change +% Different models of noise lead to different ideas about optimal preproc +% If large diffs in nuisance scaling in BOLD across individuals, ranking cols may +% be good idea. but then individual diffs in overall activity will be removed... +% +% Options: +% ------------------------------------------------------------------- +% 'tune', tuning const for robust reg +% 'iter', 'maxiterations', robust reg /WLS iterations. 1 = OLS only! +% 'smooth', 'spatial_smooth_fwhm', 0 or smoothing kernel for weights +% +% 'nosmooth', spatial_smooth_fwhm = 0; +% 'stats', 'calculate_stats', calculate_stats = 1; IN DEVELOPMENT +% 'noresiduals', write_residuals = 0; +% 'noplots', save_plots = 0; + +% Defaults/constants +% --------------------------------------------------------------------- + +tune = 4.6850; % from Matlab's implementation +bisquare_fcn = @(r) (abs(r)<1) .* (1 - r.^2).^2; + +maxiterations = 12; +spatial_smooth_fwhm = 12; % if 0, do not smooth at all + +calculate_stats = 0; +write_residuals = 1; +save_plots = 1; + +% Rotate nuisance regressors only to PCA space to avoid colinearity in wfit +fmri_model_obj = rotate_to_pca(fmri_model_obj); + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'tune', tune = varargin{i+1}; + case {'iter', 'maxiterations'}, maxiterations = varargin{i+1}; + case {'smooth', 'spatial_smooth_fwhm'}, spatial_smooth_fwhm = varargin{i+1}; + + case 'nosmooth', spatial_smooth_fwhm = 0; + case {'stats', 'calculate_stats'}, calculate_stats = 1; + case 'noresiduals', write_residuals = 0; + case 'noplots', save_plots = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if maxiterations == 1, disp('OLS only: no robust iterations'); end + +% display analysis info +% ---------------------------------------------------- + +SETUP = struct('fmri_data', [], 'fmri_model', [], 'algorithm_info', []); + +SETUP.fmri_data = struct('source_notes', fmri_data_obj.source_notes, ... + 'image_names', fmri_data_obj.image_names, ... + 'mask', fmri_data_obj.mask, ... + 'mask_descrip', fmri_data_obj.mask_descrip); + +SETUP.fmri_data.history = fmri_data_obj.history; + +SETUP.fmri_model = fmri_model_obj; + +SETUP.algorithm = struct('name', 'robust regression', ... + 'tune', tune, ... + 'maxiterations', maxiterations, ... + 'spatial_smoothing_fwhm', spatial_smooth_fwhm); + +% do not do this...for some reason, bug makes SETUP file 600 MB +% SETUP.algorithm.robust_weight_function = bisquare_fcn; + +t1 = tic; +fprintf('Saving SETUP.mat'); +save SETUP SETUP +t2 = toc(t1); +fprintf(' %3.0f s\n', t2); + + +% Check data and dims +% --------------------------------------------------------------------- +t1 = tic; + +% Plot and save output +if save_plots + save_plots_inlinefcn; +end + +X = fmri_model_obj.xX.X; +Y = fmri_data_obj.X; + +fmri_data_obj.X = []; % save space + +fprintf('Checking data'); +badvox = any(isnan(Y)) | all(Y == 0); +anybadvox = any(badvox); % use later as well +if anybadvox + fprintf('Voxels with bad values: %3.0f. (all zeros or some NaNs)\n', sum(badvox)); + Y(:, badvox) = []; + % we will have to insert these later. +end + +% We should not need this, as NaNs are illegal in fmri_data objects. +% fprintf('Removing NaNs\n'); +% [wasnan, X, Y] = nanremove(X,Y); + +[n, p] = size(X); +[n2, v] = size(Y); +if n ~= n2, error('model and data dims do not match'), end + +t2 = toc(t1); +fprintf(' %3.0f s\n', t2); + + +% Initialize vars +% --------------------------------------------------------------------- + +[ols_s, s] = deal(zeros(1, v)); + +tol_for_convergence = .001 * min([nanstd(X) nanstd(Y)]); % ...times the smallest beta + +% Initial OLS model +% --------------------------------------------------------------------- +t1 = tic; + +fprintf('Fitting OLS for %3.0f voxels', v); + +pinvX = pinv(X); + +b = pinvX * Y; + +fprintf('Getting residual error\n'); + +dfe = n - p; +r = Y - X*b; + +for i = 1:v + ols_s(i) = sqrt((r(:, i)' * r(:, i)) ./ dfe); % same as norm(y-X*b) / sqrt(dfe); % same as sqrt((r' * r) ./ (n - p) +end + +% estimate leverages for robust reweighting +H = X * pinvX; % Hat matrix; diags are leverage +h = diag(H); +adjfactor = 1 ./ sqrt(1-h); +adjfactor = repmat(adjfactor, 1, v); + +t2 = toc(t1); +fprintf(' %3.0f s\n', t2); + +% ------------------------------------------------------------------- +% Iterate robust regression +% ------------------------------------------------------------------- + +iter = 1; % Set counter +b0 = b; +max_dev = NaN; + +fprintf('Iteration '); + +while (iter < maxiterations) && (iter == 1 || max(sum((b - b0).^2)) > tol_for_convergence) + + t1 = tic; + + fprintf('%3.0f\n', iter); + + %fprintf('\b\b\b\b%3.0f ', iter); + iter = iter + 1; + + % ------------------------------------------------------------------- + % Get weights for robustfit + % ------------------------------------------------------------------- + + % adjust residuals + % ------------------------------------------------------------------- + fprintf(' Weighting residuals'); + + radj = r .* adjfactor; + + for i = 1:v + s(i) = madsigma(radj(:, i), p); + end + s(s == 0) = 1; + + radj = radj ./ repmat(s * tune, n, 1); + w = bisquare_fcn(radj); + + + erase_string(' Weighting residuals'); + + % Takes too long. Just do at end. + % % % Smooth weights + % % % ------------------------------------------------------------------- + % % + % % if spatial_smooth_fwhm + % % + % % fprintf(' Smoothing weights in 3D space: %04d', 0); + % % + % % % w is n x v, pass in transposed: + % % w = smooth_3d(w', fmri_data_obj.mask.volInfo, spatial_smooth_fwhm, badvox)'; + % % + % % erase_string(sprintf(' Smoothing weights in 3D space: %04d', 0)); + % % + % % end + + + % ------------------------------------------------------------------- + % Update betas and residuals + % ------------------------------------------------------------------- + + fprintf(' Updating betas for all voxels'); + + % for i = 1:v + % if i > 10, warning off; end % if weights are 0, this will give a warning % need to check out *** + % + % b(:, i) = wfit(Y(:, i), X, w(:, i)); + % end + + b = wfit_allvoxels(Y, X, w, size(X, 2), v); + + warning on + + erase_string(' Updating betas for all voxels'); + + fprintf(' Getting residuals'); + + r = Y - X * b; % residuals + + erase_string(' Getting residuals'); + + % save these + max_dev(iter) = max(sum((b - b0).^2)); + + t2 = toc(t1); + fprintf(' %3.0f s\n', t2); + +end % Loop + +% ------------------------------------------------------------------- +% ------------------------------------------------------------------- +% +% Final model - calculate and save results +% +% ------------------------------------------------------------------- +% ------------------------------------------------------------------- + +% Smooth weights +% ------------------------------------------------------------------- + +if maxiterations > 1 && spatial_smooth_fwhm + + fprintf('Smoothing weights in 3D space: %04d', 0); + + % w is n x v, pass in transposed: + w = smooth_3d(w', fmri_data_obj.mask.volInfo, spatial_smooth_fwhm, badvox)'; + + erase_string(sprintf('Smoothing weights in 3D space: %04d', 0)); + + fprintf('Recalculating final b and r'); + b = wfit_allvoxels(Y, X, w, size(X, 2), v); + r = Y - X * b; % residuals +end + + +SETUP.algorithm.max_dev = max_dev; +SETUP.algorithm.max_dev_descrip = 'Max deviation from old to new betas in interation; check convergence.'; + +% Write mask, betas, residuals, weights +% --------------------------------------------------------------------- + +vI = fmri_data_obj.mask.volInfo; + +% write mask +iimg_reconstruct_vols(fmri_data_obj.mask.dat, vI, 'outname', 'mask.img'); + + +% write betas and weights +write_image(b', 'regression_betas.img', vI, badvox); + +if maxiterations > 1 + write_image(w', 'robust_reg_weights.img', vI, badvox); +end + +if write_residuals + write_image(r', 'residuals.img', vI, badvox); +end + +% Stats, if asked for +% --------------------------------------------------------------------- + +fprintf('Final stats for all voxels\n'); + +if calculate_stats + + % we already have r and b + + % Standard error + MSE = single(zeros(1,v)); + v = single(zeros(n, v)); + + for i = 1:v + W = diag(w(:, i)); % Weight matrix, W = V^-1, inverse of cov.matrix + + MSE(i) = r(:,i)' * W * r(:,i); + + v(:, i) = diag(inv(X' * W * X)) .* MSE(i); % variances + + end + + % could smooth MSE and dfe + % could replace dfe with robust one?? + + MSE = MSE ./ dfe; + t = b ./ sqrt(v); + + p = 2 * ( 1 - tcdf(abs(t),dfe) ); + + write_image(t, 'regressors_t.img', vI, badvox) + write_image(p, 'regressors_p.img', vI, badvox) + +end + +% Write fitted responses (HRFs) +% --------------------------------------------------------------------- + +% For each condition and each image... +% Save HRFs(condition-specififc fits) +% Estimate htw +fprintf('Writing 4-D hrf images and height, time to peak, width, AUC images\n'); + +% list all bf +wh_bf = [1 2 1 2]; + +c = fmri_model_obj.xX.cond_assignments; + +for i = 1:size(c, 2) + t1 = tic; + + fprintf(' Condition %3.0f, %s\n', i, fmri_model_obj.xX.name{i}); + + cbeta = b(c(:, i), :); + cfit = fmri_model_obj.xBF(wh_bf(i)).bf * cbeta; + + % estimate h, t, w + % --------------------------------------------------------------------- + + fprintf('Estimating h, t, w, AUC'); + + % peak in first 20 sec of response, or length of response if shorter + % period is modeled + hconstraint = min( round(20 ./ fmri_model_obj.xBF(wh_bf(i)).dt), size(fmri_model_obj.xBF(wh_bf(i)), 1) ); + [h, t, wid, auc] = deal(zeros(v, 1)); + + for j = 1:v + [h(j, 1), t(j, 1), wid(j, 1), w_times, halfh, auc(j, 1)] = fir2htw2(cfit(:, j), hconstraint, 0); + end + + erase_string('Estimating h, t, w, AUC'); + + t2 = toc(t1); + fprintf(' h, t, w, AUC estimated in %3.0f s\n', t2); + + % write images + % --------------------------------------------------------------------- + t1 = tic; + + write_image(h, sprintf('amplitude_cond%03d_%s.img', i, fmri_model_obj.xX.name{i}), vI, badvox); + write_image(t, sprintf('time_to_peak_cond%03d_%s.img', i, fmri_model_obj.xX.name{i}), vI, badvox); + write_image(wid, sprintf('duration_cond%03d_%s.img', i, fmri_model_obj.xX.name{i}), vI, badvox); + write_image(auc, sprintf('AUC_cond%03d_%s.img', i, fmri_model_obj.xX.name{i}), vI, badvox); + + % downsample + cfit_at_TR = cfit(1:16:end, :); + + %write hrf images + write_image(cfit_at_TR', sprintf('hrf_cond%03d_%s.img', i, fmri_model_obj.xX.name{i}), vI, badvox); + + t2 = toc(t1); + fprintf(' Images written in %3.0f s\n', t2); +end + +% Ridge reg + +% Satterthwaite stuff +% See Module8_GLS_timeseries + +% % % H = X * inv(X' * W * X) * X' * W; +% % % trace(H) +% % % +% % % +% % % % This could be matrix-ized +% % % +% % % sw = sqrt(w(:, i)); +% % % [r c] = size(X); +% % % yw = Y(:, i) .* sw; +% % % xw = X .* sw(:,ones(1,c)); +% % % [Q,R]=qr(xw,0); +% % % b = R\(Q'*yw); +% % % +% +% v = repmat(diag(invxwx),1,v) .* repmat(MSE,k,1); +% +% t = b ./ sqrt(v); +% +% if nargout > 1, p = 2 * ( 1 - tcdf(abs(t),dfe) ); end +% +% Wi{i} = diag(W(:, i)); % +% +% invxvx{i} = inv(X' * Wi{i} * X); % Save these for later, for speed; don't need to re-use +% bforming{i} = invxvx{i} * X' * Wi{i}; +% % R = Wi.^.5 * (eye(n) - X * invxvx * X' * Wi{i}); % Residual inducing matrix +% R = Wi{i} .^ .5 * (eye(n) - X * bforming{i}); % Residual inducing matrix +% +% Q = R * inv(Wi{i}); % Q = RV +% dfe(i) = (trace(Q).^2)./trace(Q * Q); % Satterthwaite approximation for degrees of freedom + + +% --------------------------------------------------------------------- +% --------------------------------------------------------------------- +% +% Inline functions +% +% --------------------------------------------------------------------- +% --------------------------------------------------------------------- + + + function save_plots_inlinefcn + + savedir = fullfile(pwd, 'output_images'); + + plot(fmri_model_obj); + saveplots(fmri_model_obj, savedir); + + plot(fmri_data_obj) + saveplots(fmri_model_obj, savedir); + + end + + + +end % function + + + +function [b,R,xw] = wfit(y,x,w) + +% weighted least squares fit +% one voxel + +sw = sqrt(w); +[r c] = size(x); +yw = y .* sw; +xw = x .* sw(:,ones(1,c)); +[Q,R]=qr(xw,0); +b = R\(Q'*yw); + +end + + + +function [b,R,xw] = wfit_allvoxels(y, x, w, k, v) + +% ***why are some weights > 1??? + +% weighted least squares fit +% all voxels + +sw = w .^ .5; + +yw = y .* sw; + +b = zeros(k, v, 'single'); + +for i = 1:v + xw = x .* sw(:,i * ones(1,k)); + + [Q,R]=qr(xw, 0); + + b(:, i) = R\(Q'*yw(:, i)); +end + +end + + +function s = madsigma(r,p) + +% Compute sigma estimate using MAD of residuals + +m = median(r); +rs = sort(abs(r-m)); +if (abs(m) > rs(end)) + % Unexpectedly all residuals are very small + rs = sort(abs(r)); +end +s = median(rs(p:end)) / 0.6745; +if (s==0), s = .5*mean(rs); end + +end + +% +% function [t,p,b,v] = weighted_glmfit(X,Y,wts,dfe) +% +% v = size(Y,2); +% +% +% W = diag(wts); % Weight matrix +% invxwx = inv(X'*W*X); +% bform = invxwx * X'* W; % beta-forming matrix. hat = X * bform +% % +% % % rows are columns of design (X), cols are Y variables +% b = bform*Y; +% +% k = size(b,1); +% +% r = Y - X * b; % residuals +% +% % % standard error +% MSE = zeros(1,v); +% for i=1:v +% W = diag(w(:, i)); % Weight matrix +% MSE(i) = r(:,i)'*W*r(:,i); +% end, MSE = MSE/dfe; +% +% v = repmat(diag(invxwx),1,v) .* repmat(MSE,k,1); +% +% t = b ./ sqrt(v); +% +% if nargout > 1, p = 2 * ( 1 - tcdf(abs(t),dfe) ); end +% +% end +% +% +% function erase_string(str1) +% fprintf(1,repmat('\b',1,length(str1))); % erase string +% end + + + +function yout = zeroinsert(wasbad, y) +% yout = zeroinsert(wasbad, y) +% +% Re-insert removed CASES (rows) and fill with zeros +% wasbad is indicator for removed cases, of size size(yout). y is data. +% +% if you enter y', inserts VOXELS (cols). here, pass in v x n matrix, y' +% to fill empty/removed voxels + +% See nanremove.m and naninsert.m + +wh = find(wasbad); + +if isempty(wh), yout = y; return, end + +yout = zeros(length(wasbad), size(y, 2)); + +yout(1:wh(1) - 1, :) = y(1:wh(1) - 1, :); + +for i = 2:length(wh) + ystart = wh(i-1) + 2 - i; % NaN index value - num previous removed; wh(i-1) + 1 - i + 1; + yend = wh(i) - i; % wh(i) - 1 - i + 1 + + yout(wh(i-1) + 1 : wh(i) - 1, :) = y(ystart : yend, :); +end +% last segment +i = i+1; +if isempty(i), i = 2; end +ystart = wh(end) + 2 - i; + +if ystart <= size(y, 1) + yout(wh(end)+1:end, :) = y(ystart:size(y, 2), :); +end + +end + + + +function write_image(x, myname, vI, badvox) + +t1 = tic; + +% Replace bad vox for image writing + +if any(badvox) + x = zeroinsert(badvox, x); +end + +mypath = fullfile(pwd, myname); + +imgv = image_vector('X', x, 'volInfo', vI, 'filename', myname, 'fullpath', mypath); +write(imgv); + +t2 = toc(t1); +fprintf('Done in %3.0f s\n', t2); + +end + + + +function w = smooth_3d(w, volInfo, sfwhm, varargin) +% Smooth 3-D images stored in columns with FWHM in mm of sfwhm +% +% function w = smooth_3d(w, volInfo, sfwhm, [badvox]) +% +% Take v x n matrix w, and smooth columns (images), returning v x n matrix +% again +% Optional: if v is smaller than the original image because some voxels +% were removed, enter logical vector badvox, and the missing voxels +% will be filled with zeros. + + +% NOTE: horribly slow and memory intensive: +%wvol = iimg_reconstruct_vols(w', fmri_data_obj.mask.volInfo); + +if ~isempty(varargin), badvox = varargin{1}; end + +% transpose, and ... +% insert zeros back into bad vox +if any(badvox) + w = zeroinsert(badvox, w); +end + +n = size(w, 2); + +try + + for i = 1:n + fprintf('\b\b\b\b%04d', i); + + wvol = iimg_reconstruct_vols(w(:, i), volInfo); + + spm_smooth(wvol, wvol, [sfwhm sfwhm sfwhm]); + + % go back to vector + wvec = wvol(volInfo.wh_inmask); + + w(:, i) = wvec; + + end + +catch + disp('Error with volume reconstruction. Mask in fmri_data obj not properly defined?'); + disp('Stopped in debugger so you can check...'); + keyboard +end + +if any(badvox) + + w(badvox, :) = []; + +end + +end % function + + + diff --git a/@fmri_model/rotate_to_pca.m b/@fmri_model/rotate_to_pca.m new file mode 100644 index 00000000..6a0c797f --- /dev/null +++ b/@fmri_model/rotate_to_pca.m @@ -0,0 +1,85 @@ +% Rotate design matrix columns within all conditions to principal component projection. +function [obj, x_eig, c_eig] = rotate_to_pca(obj) + +eigval_cutoff = .1; % 100*eps; % or 1 + +meth = 'nuis only'; + +switch meth + case 'all' + + c = obj.xX.cond_assignments; + + % add column for all nuisance covs + c(:, end + 1) = (~any(c'))'; + + % here, if startval = size(c, 2), then + % it will do only nuisance covariates. This is the default behavior. + % if startval were 1, it would do ALL conditions. + startval = 1; + + case 'nuis only' + + % this for nuisance only instead + c = false(size(obj.xX.X, 1), 1); + c(obj.xX.iC) = true; + + startval = size(c, 2); + +end + + +[c_eig, x_eig] = deal(cell(1, size(c, 2))); + + + +for j = startval:size(c, 2) + % for each condition + + % columns for condition j + x = obj.xX.X(:, c(:, j)); + + % for determining which to save and which are empty + x2 = scale(x); + + [v, sc, lam] = princomp(x2, 'econ'); + wh_empty = lam < eigval_cutoff; + + v = princomp(x, 'econ'); + + % eliminate empty columns + v(:, wh_empty) = []; + x = x * v; + + % re-build c matrix + c_eig{j} = ones(size(x, 2), 1); + + x_eig{j} = x; + +end + +x_eig = cat(2, x_eig{:}); +c_eig = blkdiag(c_eig{:}); + +switch meth + case 'all' + + + + + case 'nuis only' + wh_to_replace = obj.xX.iC(1:size(x_eig, 2)); + wh_to_delete = obj.xX.iC(size(x_eig, 2)+1:end); + num_to_delete = length(wh_to_delete); + + obj.xX.X(:, wh_to_replace) = x_eig; + obj.xX.X(:, wh_to_delete) = []; + obj.xX.cond_assignments(wh_to_delete, :) = []; + + obj.xX.iB = obj.xX.iB - num_to_delete; + obj.xX.iC = wh_to_replace; + +end + +end + diff --git a/@fmri_model/saveplots.m b/@fmri_model/saveplots.m new file mode 100644 index 00000000..bd703662 --- /dev/null +++ b/@fmri_model/saveplots.m @@ -0,0 +1,39 @@ +function saveplots(fmri_model, varargin) + +% Output dir +if isempty(varargin) + savedir = pwd; +else +savedir = varargin{1}; +end + +if ~exist(savedir, 'dir'), mkdir(savedir); end + +% Register of possible plot names associated with this object type + +plotnames = {'basis sets' ... + 'design matrix image' ... + 'design matrix' ... + }; + +for j = 1:length(plotnames) + + han = findobj('Name', plotnames{j}); + if ~isempty(han) && ishandle(han) && strcmp(get(han, 'Type'), 'figure') + + fprintf('Saving figure: %s\n', plotnames{j}); + figure(han); + scn_export_papersetup(500); + + name = get(han, 'Name'); + name(name == ' ') = '_'; + + name = fullfile(savedir, name); + + saveas(han, name, 'png'); + + end + +end + +end \ No newline at end of file diff --git a/@fmri_model/single_trial_estimates.m b/@fmri_model/single_trial_estimates.m new file mode 100644 index 00000000..58f1c163 --- /dev/null +++ b/@fmri_model/single_trial_estimates.m @@ -0,0 +1,113 @@ +function single_trial_estimates(obj, fmri_data_obj) +% Write single trial estimates associated with an estimated fmri_model object. +% must have estimated the model (robustfit(obj); see fmri_model.robustfit) +% and saved hrf*.img images for each condition. +% +% Also input an fmri_data object with time series data. +% +% This function writes images, one 4-D image for each condition, with the +% number of frames equalling the number of trials (onsets) for that +% condition. +% +% It does this by constructing a separate design matrix for each voxel, +% which is based on the HRF estimates for that voxel for each condition. +% Fits for all conditions are added to the same model, so that their +% colinearity influences the single-trial parameter estimates. + +c = obj.xX.cond_assignments; +nconds = size(c, 2); + + +% check fmri_data_obj data +% --------------------------------------------------------------------- +Y = fmri_data_obj.X; + +fmri_data_obj.X = []; % save space + +fprintf('Checking data'); +badvox = any(isnan(Y)) | all(Y == 0); +anybadvox = any(badvox); % use later as well +if anybadvox + fprintf('Voxels with bad values: %3.0f. (all zeros or some NaNs)\n', sum(badvox)); + Y(:, badvox) = []; + % we will have to insert these later. +end + + +% load hrf images +% --------------------------------------------------------------------- +disp('Loading mask.img from current (analysis) directory:'); +mask = fmri_mask_image('mask.img'); + +disp('Single trial estimates for fmri_model object'); +disp('Loading hrf estimates from images...'); + +for i = 1:nconds + + fprintf('Condition %3.0f, %s', i, obj.xX.name{i}); + + hrfname = fullfile(pwd, sprintf('hrf_%s_cond%03d.img', obj.xX.name{i}, i)); + + if ~exist(hrfname, 'file') + disp('You must have already written HRF images in the current dir. See fmri_model.robustfit.'); + end + + hrfs{i} = fmri_data(hrfname, mask); + +end + +% voxels +v = size(hrfs{1}.X, 2); + +% test build to set up sizes +for ii = 1:nconds + voxelhrfs{ii} = hrfs{ii}.X(:, i); +end +obj = build_single_trial(obj, voxelhrfs); +nb = length(obj.xX.iH); +betas = zeros(nb, v, 'single'); + + +fprintf('Fitting single-trial for %3.0f voxels: 000000', v); +t1 = tic; + +for i = 1:v + + if mod(i, 100) == 0 || i == v + fprintf('\b\b\b\b\b\b%06d', i); + end + + for ii = 1:nconds + voxelhrfs{ii} = hrfs{ii}.X(:, i); + end + + % Build model + % --------------------------------------------------------------------- + obj = build_single_trial(obj, voxelhrfs); + + + % Fit OLS model (trial regressors to data) + % --------------------------------------------------------------------- + + b = pinv(obj.xX.X) * fmri_data_obj.X(:, i); + betas(:, v) = b(1:nb); +end + +t2 = toc(t1); +fprintf(' Done in %3.0f s\n', t2); + +% Parse betas into conditions and write images +% --------------------------------------------------------------------- +c = obj.xX.cond_assignments; +nconds = size(c, 2); %***need to add pms and use above +vI = mask.volInfo; + +for i = 1:nconds + imgname = sprintf('single_trial_amp_cond%3.0f_%s.img', i, obj.xX.name{i}); + + write_image(b(c(:, i), :)', imgname, vI, badvox); +end + + + +end % function \ No newline at end of file diff --git a/@fmri_timeseries/fmri_timeseries.m b/@fmri_timeseries/fmri_timeseries.m new file mode 100644 index 00000000..9e359b53 --- /dev/null +++ b/@fmri_timeseries/fmri_timeseries.m @@ -0,0 +1,62 @@ +classdef fmri_timeseries < fmri_data + + properties + subject = 'Subject_ID_here'; + study = 'Study_ID_here'; + + X = []; + X_descrip = 'time (observations) x voxels data'; + + volInfo = struct('XYZ', [0 0 0]', 'XYZmm', [0 0 0]', 'M', zeros(4)); + volInfo_descrip = 'Volume info from iimg_read_img'; + + image_names = char(''); + + onsets = cell(1); + onsets_descrip = 'Cell array with onset times for events, one cell per event type per session'; + + condition_names = cell(1); + condition_names_descrip = 'Cell array with names of conditions'; + + Y = []; + Y_descrip = 'Behavioral or outcome data matrix.'; + + covariates = 0; + covariates_descrip = 'Nuisance covariates associated with data'; + + history = {'raw'}; + history_descrip = 'Cell array of names of methods applied to this data, in order'; + + end % properties + + methods + + % ------------------------------------- + % Checks for legal data + % ------------------------------------- +% % +% % function obj = create(obj, varargin) +% % +% % N = fieldnames(obj); +% % +% % for i = 1:length(varargin) +% % if ischar(varargin{i}) +% % +% % % Look for a field (attribute) with the input name +% % wh = strmatch(varargin{i}, N, 'exact'); +% % +% % if ~isempty(wh) +% % +% % obj.(varargin{i}) = varargin{i + 1}; +% % +% % end +% % +% % end +% % end +% % +% % end % function + + end % methods + + +end \ No newline at end of file diff --git a/@image_vector/apply_mask.m b/@image_vector/apply_mask.m new file mode 100644 index 00000000..f887c01e --- /dev/null +++ b/@image_vector/apply_mask.m @@ -0,0 +1,225 @@ +function [dat, mask] = apply_mask(dat, mask, varargin) +% Apply a mask image (image filename or fmri_mask_image object) to an image_vector object +% stored in dat. +% +% This can be used to: +% - Mask an image_vector or fmri_data object with a mask +% - Obtain "pattern expression" for a weight map (entered as the +% mask, here) in a series of images stored in dat. +% +% The mask or weight map does not have to be in the same space as the dat; +% it will be resampled to the space of the data in dat. +% +% To extract pattern expression values for each ROI within a mask use extract_roi_averages() +% +% Optional inputs: +% 'pattern_expression' : calculate and return the cross-product of each +% image in dat and the values in the mask. This is useful if comparing +% expression values that are comprised of different datasets or differing +% number of voxels. +% +% 'correlation' : calculate the pearson correlation coefficient of each +% image in dat and the values in the mask. +% +% 'norm_mask': normalize the mask weights by L2 norm, for patt expression +% only. +% +% 'ignore_missing': use with pattern expression only. Ignore weights on voxels +% with zero values in test image. If this is not entered, the function will +% check for these values and give a warning. +% +% [dat, mask] = apply_mask(dat, mask) +% [dat, mask] = apply_mask(dat, mask image name) +% [dat, mask] = apply_mask(dat, mask image vector object) +% [pattern_exp_values] = apply_mask(dat, weight map image, 'pattern_expression', 'ignore_missing') +% [pattern_exp_values] = apply_mask(dat, weight map image, 'pattern_expression', 'ignore_missing','correlation') +% +% Notes: +% Last modified: 10/30/11 to add support for masks that are weight maps +% 12/15/13: Luke Chang - added correlation option for pattern-expression, + +% set options +dopatternexpression = 0; +donorm = 0; +doignoremissing = 0; +docorr = 0; %run correlation instead of dot-product for pattern expression + +if any(strcmp(varargin, 'pattern_expression')) + dopatternexpression = 1; + + if any(strcmp(varargin, 'ignore_missing')) + doignoremissing = 1; + end + + if any(strcmp(varargin, 'correlation')) % run correlation instead of dot-product + docorr = 1; + end + +end + + + +if any(strcmp(varargin, 'norm_mask')) % only good for pattern expression + donorm = 1; +end + +% create mask_image object if we have a filename +if ischar(mask) + mask = fmri_mask_image(mask); +end + +isdiff = compare_space(dat, mask); + +if isdiff == 1 || isdiff == 2 % diff space, not just diff voxels + + % Both work, but resample_space does not require going back to original + % images on disk. + %mask = resample_to_image_space(mask, dat); + mask = resample_space(mask, dat); + + % tor added may 1 - removed voxels was not legal otherwise + %mask.removed_voxels = mask.removed_voxels(mask.volInfo.wh_inmask); + % resample_space is not *always* returning legal sizes for removed + % vox? maybe this was updated to be legal + if length(mask.removed_voxels) == mask.volInfo.nvox + disp('Warning: resample_space returned illegal length for removed voxels. Fixing...'); + mask.removed_voxels = mask.removed_voxels(mask.volInfo.wh_inmask); + end + +end + +dat = remove_empty(dat); +nonemptydat = ~dat.removed_voxels; % remove these + +dat = replace_empty(dat); + +% Check/remove NaNs. This could be done in-object... +mask.dat(isnan(mask.dat)) = 0; + +% Replace if necessary +mask = replace_empty(mask); + +% save which are in mask, but do not replace with logical, because mask may +% have weights we want to preserve +inmaskdat = logical(mask.dat); + + +% Remove out-of-mask voxels +% --------------------------------------------------- + +% mask.dat has full list of voxels +% need vox in both mask and original data mask + +if size(mask.volInfo.image_indx, 1) == size(dat.volInfo.image_indx, 1) + n = size(mask.volInfo.image_indx, 1); + + if size(nonemptydat, 1) ~= n % should be all vox OR non-empty vox + nonemptydat = zeroinsert(~dat.volInfo.image_indx, nonemptydat); + end + + if size(inmaskdat, 1) ~= n + inmaskdat = zeroinsert(~mask.volInfo.image_indx, inmaskdat); + end + + inboth = inmaskdat & nonemptydat; + + % List in space of in-mask voxels in dat object. + % Remove these from the dat object + to_remove = ~inboth(dat.volInfo.wh_inmask); + + to_remove_mask = ~inboth(mask.volInfo.wh_inmask); + +elseif size(mask.dat, 1) == size(dat.volInfo.image_indx, 1) + + % mask vox are same as total image vox + nonemptydat = zeroinsert(~dat.volInfo.image_indx, nonemptydat); + inboth = inmaskdat & dat.volInfo.image_indx & nonemptydat; + + % List in space of in-mask voxels in dat object. + to_remove = ~inboth(dat.volInfo.wh_inmask); + + to_remove_mask = ~inboth(mask.volInfo.wh_inmask); + +elseif size(mask.dat, 1) == size(dat.volInfo.wh_inmask, 1) + % mask vox are same as in-mask voxels in dat + inboth = inmaskdat & dat.volInfo.image_indx(dat.volInfo.wh_inmask) & nonemptydat; + + % List in space of in-mask voxels in .dat field. + to_remove = ~inboth; + + to_remove_mask = ~inboth; + +else + fprintf('Sizes do not match! Likely bug in resample_to_image_space.\n') + fprintf('Vox in mask: %3.0f\n', size(mask.dat, 1)) + fprintf('Vox in dat - image volume: %3.0f\n', size(dat.volInfo.image_indx, 1)); + fprintf('Vox in dat - image in-mask area: %3.0f\n', size(dat.volInfo.wh_inmask, 1)); + disp('Stopping to debug'); + keyboard +end + +dat = remove_empty(dat, to_remove); +mask = remove_empty(mask, to_remove_mask); + +if dopatternexpression + %mask = replace_empty(mask); % need for weights to match + + weights = double(mask.dat); % force double b/c of matlab instabilities + + dat.dat = double(dat.dat); % force double b/c of matlab instabilities + + if donorm, weights = weights ./ norm(weights); end + + % CHECK and weight + % --------------------------------------------------------- + inmask = weights ~= 0 & ~isnan(weights); + badvals = sum(dat.dat(inmask, :) == 0); + + if ~any(badvals) + %weights(to_remove_mask) = []; + if ~docorr + dat = dat.dat' * weights; %dot-product + else + dat = corr(dat.dat,weights); %correlation + end + + elseif doignoremissing + + for i = 1:size(dat.dat, 2) + mydat = dat.dat(:, i); + myweights = weights; + myweights(mydat == 0 | isnan(mydat)) = 0; + if ~docorr + mypeval(i, 1) = mydat' * myweights; %dot product + else + mypeval(i,1) = corr(mydat, myweights); %correlation + end + end + + dat = mypeval; + + else + + disp('WARNING!!! SOME SUBJECTS HAVE ZERO VALUES WITHIN WEIGHT MASK.'); + disp('This could artifactually influence their scores if these 0 values are out of test data image.'); + + wh = find(badvals); + + fprintf('Total voxels in weight mask: %3.0f\n', sum(inmask)); + disp('Test images with bad values:'); + for i = 1:length(wh) + fprintf('Test image %3.0f: %3.0f zero values\n', wh(i), badvals(:, wh(i))); + end + + if ~docorr + dat = dat.dat' * weights; %Dot product + else + dat = corr(dat.dat,weights); %correlation + end + end + % End check and weight --------------------------------------------------------- + + +end + +end diff --git a/@image_vector/check_image_filenames.m b/@image_vector/check_image_filenames.m new file mode 100644 index 00000000..ad167f25 --- /dev/null +++ b/@image_vector/check_image_filenames.m @@ -0,0 +1,104 @@ +function obj = check_image_filenames(obj, varargin) +% Check whether images listed in obj.fullpath actually exist +% +% obj = check_image_filenames(obj, ['noverbose']) +% +% Behavior: +% If there are no file names, do nothing. +% If file names are entered and full path is not, attempt to find full +% path. +% If full path info is entered, check to see if files exist. +% Return output in obj.files_exist, and print a warning if only some exist. +% +% image names should be stored in .fullpath +% abbreviated image names may be stored in image_names. +% +% Notes: +% fullpath should have full path to each volume in a string matrixm, with +% trailing ,volume# for 4-D images as per SPM style expanded list. +% image_names should have image name only for each volume +% +% *** May still be debugging issues with 3-D vs. 4-D files + +% if 'noverbose' is entered, suppress output +verbose = isempty(strmatch('noverbose', varargin(cellfun(@ischar, varargin)))); + + +if isempty(obj.fullpath) && ~isempty(obj.image_names) + % get full path + if verbose, disp('Image names entered, but fullpath attribute is empty. Getting path info.'); end + fullnames = []; + imgnames = []; + + for i = 1:size(obj.image_names, 1) + + if any(obj.image_names(i, :) == filesep) + % may already have full path + fullnames = strvcat(fullnames, deblank(obj.image_names(i, :))); + [dd, ff, ee] = fileparts(deblank(obj.image_names(i, :))); + imgnames = strvcat(imgnames, [ff ee]); + + else + thisname = deblank(obj.image_names(i, :)); + fullnames = strvcat(fullnames, which(thisname)); + % if exist(which(thisname), 'file') + % elseif exist(fullfile(pwd, thisname), 'file') + % fullnames = strvcat(fullnames, fullfile(pwd, thisname)); + % end + imgnames = strvcat(imgnames, thisname); + end + end + + obj.fullpath = fullnames; + obj.image_names = imgnames; + + % disp('Warning: image names with full paths should be stored in obj.fullpath.') + % disp('Your fullpath field is empty, but you have some names in image_names.'); + % disp('These names will not be checked, and this program assumes the images'); + % disp('corresponding to your dataset do not exist.'); +end + +if isempty(obj.fullpath) + obj.files_exist = false(max([1 size(obj.image_names, 1) size(obj.fullpath, 1)]), 1); +end + +% Check if same size as number of volumes in data +if ~isempty(obj.fullpath) && size(obj.fullpath, 1) ~= size(obj.dat, 2) + if verbose + disp('.fullpath should have image name for each image column in .dat'); + disp('Attempting to expand image filenames in case image list is unexpanded 4-D images'); + end + obj.fullpath = expand_4d_filenames(obj.fullpath); +end + +for i = 1:size(obj.fullpath, 1) + + img = deblank(obj.fullpath(i, :)); + + % take off trailing commas (SPM notation for multiple vols) + wh = find(img == ','); + if ~isempty(wh) + wh = wh(end); + img(wh:end) = []; + end + + obj.files_exist(i, 1) = exist(img, 'file') > 0; + +end + +% If some, but not all, files exist, print a warning: +if any(obj.files_exist) + if sum(obj.files_exist) < size(obj.fullpath, 1) + if verbose + disp('Warning: *Some* files are missing. Printing index numbers of missing files:'); + disp(find(~obj.files_exist)); + end + end +end + +obj.history{end+1} = sprintf('Checked image files exist. All exist = %3.0f', ... + all(obj.files_exist)); + +end % function + + diff --git a/@image_vector/compare_space.m b/@image_vector/compare_space.m new file mode 100644 index 00000000..8d1a870e --- /dev/null +++ b/@image_vector/compare_space.m @@ -0,0 +1,28 @@ +function isdiff = compare_space(obj, obj2) +% function isdiff = compare_space(obj, obj2) +% +% Compare spaces of two image_vector objects +% +% Returns 0 if same, 1 if different spaces, 2 if no volInfo info for one or +% more objects. 3 if same space, but different in-mask voxels in .dat or +% volInfo.image_indx + +if isempty(obj.volInfo) || isempty(obj2.volInfo) + isdiff = 2; + return; +end + +n1 = [obj.volInfo.mat(:); obj.volInfo.dim(:); obj.volInfo.nvox]; + +n2 = [obj2.volInfo.mat(:); obj2.volInfo.dim(:); obj2.volInfo.nvox]; + +isdiff = any(n1 - n2); + +if ~isdiff + if size(obj.dat, 1) ~= size(obj2.dat, 1) || any(obj.volInfo.image_indx - obj2.volInfo.image_indx) + isdiff = 3; + end + +end + +end diff --git a/@image_vector/extract_roi_averages.m b/@image_vector/extract_roi_averages.m new file mode 100644 index 00000000..e2b98e16 --- /dev/null +++ b/@image_vector/extract_roi_averages.m @@ -0,0 +1,191 @@ +function cl = extract_roi_averages(obj, mask, varargin) +% cl = extract_roi_averages(image_vector obj, mask, [average_over]) +% +% This image_vector method a extracts and averages data stored in an fmri_data object +% from a set of ROIs defined in a mask. +% It is *slightly* different from the fmri_data method, as fmri_data has +% more fields. +% This version requires the mask_image to be in the same space as the obj. +% +% Regions to average over can be either regions of contiguous voxels +% bounded by voxels with values of 0 or NaN, which are considered non-data +% values, or regions defined by unique integer codes in the mask image +% (i.e., for atlas images with unique codes for each defined region.) +% +% Mask/Atlas image does NOT have to be in the same space as the images to +% extract from. It will be remapped/resliced. +% +% extracted data is returned in single data format. +% +% Inputs: +% 1 - char array of strings containing 4D image file names (data extracted from these) +% 2 - mask_image to extract from. +% +% Optional inputs: +% 'average_over': +% Default = 'contiguous_regions' to average over contiguous voxels +% bounded by voxels of 0 or NaN (non-data values) +% Alt. option = 'unique_mask_values' to average over unique integer codes in the mask image +% (i.e., for atlas images with unique codes for each defined +% region) +% +% Example: +% imgs_to_extract_from = filenames('w*.nii','char'); +% mask_image = which('anat_lbpa_thal.img'); +% [cl, imgdat] = extract_image_data(imgs_to_extract_from, mask_image); +% +% +% Related functions: +% For an non-object-oriented alternative, see extract_image_data.m + +fprintf('image_vector.extract_roi_averages: '); + +if ~isa(mask, 'image_vector') + mask = image_vector('image_names', mask); +end + +obj = replace_empty(obj); +mask = replace_empty(mask); + +% -------------------------------------------- +% Redefine elements of new mask to apply +% to keep only coords in original dataset +% -------------------------------------------- + +is_inmask = obj.volInfo.image_indx & mask.volInfo.image_indx; +mask.volInfo.image_indx = is_inmask; + +orig_wh_inmask = mask.volInfo.wh_inmask; % save for later - + +mask.volInfo.wh_inmask = find(mask.volInfo.image_indx); +mask.volInfo.n_inmask = length(mask.volInfo.wh_inmask); + +% need to limit coords in new mask to ONLY those in the orig mask as +% well. +wh_to_keep = is_inmask(orig_wh_inmask); +mask.volInfo.xyzlist = mask.volInfo.xyzlist(wh_to_keep, :); +mask.volInfo.cluster = mask.volInfo.cluster(wh_to_keep, :); + +% -------------------------------------------- +% Redefine data (temporarily; not passed out) +% to keep only coords in new mask +% -------------------------------------------- + +% need to limit data in obj to ONLY voxels that are also in the new +% mask. Index in space of original fmri_data object for which to keep: +wh_to_keep = is_inmask(obj.volInfo.wh_inmask); + +% eliminate out-of-mask voxels before indexing into them with new mask +obj.dat = obj.dat(wh_to_keep, :)'; + + +fprintf('\n'); + +% --------------------------------- +% define region object based on choices +% --------------------------------- + +average_over = 'unique_mask_values'; %'contiguous_regions' or 'unique_mask_values'; + +for varg = 1:length(varargin) + if ischar(varargin{varg}) + switch varargin{varg} + + % reserved keywords + case 'contiguous_regions', average_over = 'contiguous_regions'; + case 'unique_mask_values', average_over = 'unique_mask_values'; + + otherwise + disp('fmri_data.extract_roi_averages: Illegal string value for average_over.'); + fprintf('You entered ''%s''\n Valid values are %s or %s\n', varargin{varg}, '''contiguous_regions''', '''unique_mask_values'''); + error('Exiting'); + end + end +end + +cl = region(mask, average_over); +cl(1).source_images = obj.fullpath; + +% --------------------------------- +% Now get averages by cluster +% --------------------------------- + + +fprintf('Averaging data. '); + +switch size(mask.dat, 1) + case length(mask.volInfo.wh_inmask) + %maskData = mask.dat(logical(mask.volInfo.wh_inmask), :); + % We already have in-mask data; do not select + maskData = mask.dat; + + case length(mask.volInfo.image_indx) + maskData = mask.dat(logical(mask.volInfo.image_indx), :); + otherwise + error('mask .dat is wrong size.') +end + +switch average_over + + % Define integer codes for sets of voxels to average over. + + case 'unique_mask_values' + maskData = round(maskData); + u = unique(maskData)'; u(u == 0) = []; + nregions = length(u); + fprintf('Averaging over unique mask values, assuming integer-valued mask: %3.0f regions\n', nregions); + + + case 'contiguous_regions' + u = unique(mask.volInfo.cluster); u(u == 0) = []; + maskData = mask.volInfo.cluster; + + + case 'none' + cl = []; + return + + + otherwise + error('Illegal value for average_over. See help for this function.'); +end + + +% Now get the average activity in each region for the defined regions + +nregions = length(u); + +if nregions ~= length(cl) + disp('Num of regions in mask does not equal num of clusters to extract from.') + disp('The most likely cause is an ill-formed/outdated obj.volInfo.cluster field'); + disp('in the region-defining object. Try obj = reparse_contiguous(obj);'); + disp('Stopping in debugger so you can check/debug.'); + keyboard +end + +for i = 1:nregions + imgvec = maskData == u(i); + + + regiondat = obj.dat(:, imgvec); + + + if ~isempty(regiondat) + if size(regiondat, 2) == 1 + regionmean = double(regiondat); + else + regionmean = double(nanmean(regiondat')'); + end + cl(i).all_data = single(regiondat); + + else + regionmean = NaN .* zeros(size(dat, 1), 1); + end + + cl(i).dat = regionmean; + +end + +fprintf('Done.\n'); + +end % function \ No newline at end of file diff --git a/@image_vector/fastmontage.m b/@image_vector/fastmontage.m new file mode 100644 index 00000000..1f02d9c0 --- /dev/null +++ b/@image_vector/fastmontage.m @@ -0,0 +1,158 @@ +function fastmontage(dat, varargin) +% fastmontage(dat, [myview], ['spacing', slicespacing], ['vertical']) +% +% fastmontage(dat); Creates 3 separate montage views - ax, cor, sagg +% In special figure window +% +% fastmontage(dat, 'coronal'); +% fastmontage(dat, 'saggital', 'spacing', 10); +% fastmontage(dat, 'saggital', 'spacing', 10, 'vertical'); +% fastmontage(dat, 'saggital', 'slices_per_row', 12); +% +% Tor Wager, Aug 2012 + +do3views = 1; % composite with all 3 views (default) in special figure +myview = 'axial'; % default for single view +spacing = 1; +dovertical = 0; +stackorient = 2; % 2 for horiz, 1 for vertical +dotight = 1; % tight: show only non-zero slices +s = 12; % slices per row + +% Process inputs - single case +% in current axes +% ------------------------------------------------------------------------ +for i = 1:length(varargin) + do3views = 0; % if any optional arguments entered, turn off 3 views + + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case {'axial', 'saggital', 'coronal'}, myview = varargin{i}; + case 'spacing', spacing = varargin{i + 1}; + case 'vertical', stackorient = 1; + case 'slices_per_row', s = varargin{i + 1}; + + otherwise + disp('Warning: Unknown input.') + end + end +end + + +% ------------------------------------------------------------------------ +% Multi-view mode (default) +% +if do3views + fh = create_figure('fastmontage'); + ax1 = axes('OuterPosition', [.05 .05 .20 .90], 'Position', [.05 .05 .20 .90]); + fastmontage(dat, 'saggital', 'spacing', 8, 'vertical'); + + ax2 = axes('OuterPosition', [.229 .05 .8 .150]); %[.20 .05 .8 .30]); + set(ax2, 'Position', get(ax2, 'OuterPosition')); + fastmontage(dat, 'coronal', 'spacing', 8); + + ax3 = axes('OuterPosition', [.20 .25 .8 .70]); + set(ax3, 'Position', get(ax3, 'OuterPosition')); + fastmontage(dat, 'axial'); + + colormap gray + return +end + +% ------------------------------------------------------------------------ +% Main function for single montage +% + +if stackorient == 2, platestack = 1; else platestack = 1; end + +vdat = reconstruct_image(dat); + +% flip x dim if x voxel size is negative. other display/write methods also +% do this. +if sign(dat.volInfo.mat(1)) < 0 + vdat = flipdim(vdat, 1); +end + +if dotight + nullvox = vdat == 0 | isnan(vdat); + bottom = all(nullvox, 3); + nullx = squeeze(all(bottom, 2)); + vdat(nullx, :, :) = []; + + nully = squeeze(all(bottom, 1)); + vdat(:, nully, :) = []; + + side = squeeze(all(nullvox, 1)); + nullz = squeeze(all(side, 1)); + vdat(:, :, nullz) = []; + +end + +% Spacing and which slices +switch myview + case 'axial' + z = size(vdat, 3); + case 'saggital' + z = size(vdat, 1); + case 'coronal' + z = size(vdat, 2); + otherwise error('unknown slice view.') +end +whsl = 1:spacing:z; + +nrows = ceil(length(whsl) ./ s); + +% starting and ending values for slices +%stepby = ceil(length(whsl)/nrows); +% indices into whsl +for j = 1:nrows + rowslices{j} = whsl((j-1)*s+1 : min(j*s, length(whsl))); +end +% st = whsl(1:8:length(whsl)); +% en = min(st+stepby-1, length(whsl)); + +plate = cell(nrows, 1); + +for j = 1:nrows + stacked = []; + + for i = rowslices{j} %st(j):en(j) + + switch myview + case 'axial' + stacked{i} = vdat(:, :, i); + stacked{i} = rot90(stacked{i}); + case 'saggital' + stacked{i} = squeeze(vdat(i, :, :)); + stacked{i} = rot90(stacked{i}); + case 'coronal' + stacked{i} = squeeze(vdat(:, i, :)); + stacked{i} = rot90(stacked{i}); + otherwise error('unknown slice view.') + end + + end + + stacked = cat(stackorient, stacked{:}); + + plate{j} = stacked; + + if j > 1 && size(plate{j}, 2) < size(plate{1}, 2) + % pad + fullsize = size(plate{1}); + padval = 0; % mean(plate{j}(:)); % value to use for pad + plate{j} = [plate{j} padval .* ones(fullsize(1), fullsize(2) - size(plate{j}, 2))]; + end + +end % rows + + +plate = cat(platestack, plate{:}); + +imagesc(plate) +axis image +set(gca, 'YDir', 'Reverse') +axis off + +end diff --git a/@image_vector/flip.m b/@image_vector/flip.m new file mode 100644 index 00000000..87cdb990 --- /dev/null +++ b/@image_vector/flip.m @@ -0,0 +1,32 @@ +function dat = flip(dat, varargin) +% Flips an image_vector object left to right +% +% Optional: input 'mirror' to make a symmetrical image, averaging the left +% and right hemispheres +% +% dat = flip(dat, ['mirror']) +% +% tor. may 2012 + + +vdat = reconstruct_image(dat); +orig_dat = vdat(:); + +for i = 1:size(vdat, 3) + slice = vdat(:, :, i); + slice = flipdim(slice, 1); + vdat(:, :, i) = slice; +end + +vdat = vdat(:); + + % mirror +if ~isempty(varargin) && strcmp(varargin{1}, 'mirror') + vdat = (orig_dat + vdat) / 2; +end + +% rebuild mask around new non-zero values +dat = rebuild_volinfo_from_dat(dat, vdat); + +end + diff --git a/@image_vector/histogram.m b/@image_vector/histogram.m new file mode 100644 index 00000000..a18b8a5b --- /dev/null +++ b/@image_vector/histogram.m @@ -0,0 +1,19 @@ +function histogram(obj) + + +% --------------------------------------------------------------- +% Histogram +% --------------------------------------------------------------- +Xtmp = obj.dat(:); + +[h, x] = hist(Xtmp, 100); +han = bar(x, h); +set(han, 'FaceColor', [.3 .3 .3], 'EdgeColor', 'none'); +axis tight; +xlabel('Values'); ylabel('Frequency'); +title('Histogram of values'); +drawnow + + +end + diff --git a/@image_vector/history.m b/@image_vector/history.m new file mode 100644 index 00000000..a4f93a97 --- /dev/null +++ b/@image_vector/history.m @@ -0,0 +1,16 @@ +function history(dat) +% Display history for image_vector object + +u = '_________________________________________________'; +disp(u) +disp('History for image data object') +disp(u) + +fprintf('Data description:\n%s\n', dat.dat_descrip); + +disp(char(dat.history)) + +disp(u) + + +end diff --git a/@image_vector/horzcat.m b/@image_vector/horzcat.m new file mode 100644 index 00000000..f758c314 --- /dev/null +++ b/@image_vector/horzcat.m @@ -0,0 +1,25 @@ +function c = horzcat(varargin) +% function s = horzcat(varargin) +% +% Implements the horzcat ([a b]) operator on image_vector objects across voxels. +% Requires that each object has an equal number of columns and voxels +% +% Examples: +% c = [dat1 dat2]; +% +% Programmer Notes +% Created 3/14/14 by Luke Chang + + +dat = []; +for i = 1:nargin + %Check if image_vector object + if ~isa(varargin{i}, 'image_vector') + error('Input Data is not an image_vector object') + end + + dat = [dat, varargin{i}.dat]; +end + +c = varargin{1}; +c.dat = dat; diff --git a/@image_vector/ica.m b/@image_vector/ica.m new file mode 100644 index 00000000..2e4ae31f --- /dev/null +++ b/@image_vector/ica.m @@ -0,0 +1,54 @@ +function icadat = ica(fmridat_obj, varargin) +% Spatial ICA of an fmri_data object +% icadat = ica(fmridat_obj, [number of ICs to save]) +% icadat is also an fmri_data object, with .dat field voxels x components +% +% Notes: +% icasig = W * mixedsig +% icasig = icadat.dat' = W * fmridat_obj.dat' +% +% A is scaled version of fmridat_obj.dat' * icadat.dat +% A and W are stored in additional_info field of icadat + +nic = 30; +if length(varargin) > 0, nic = varargin{1}; end + +neig = size(fmridat_obj.dat, 2); + +[icasig, A, W] = icatb_fastICA(double(fmridat_obj.dat'), 'lastEig', neig, ... + 'numOfIC', nic, 'stabilization', 'on', 'verbose', 'on'); + +icadat = fmri_data; + +icadat.dat = icasig'; +icadat.mask = fmridat_obj.mask; +icadat.volInfo = fmridat_obj.volInfo; +icadat.removed_voxels = fmridat_obj.removed_voxels; +icadat.removed_images = 0; + +icadat.additional_info{1} = A; +icadat.additional_info{2} = W; +icadat.history{1} = 'Spatial ICA of fmri_data object.'; +icadat.history{2} = 'Stored A and W matrices in cells 1,2 of additional_info'; +icadat.source_notes = 'fastICA algorithm on images (spatial)'; + +% Show on orthviews + +tmpicadat = icadat; +for i = 1:size(tmpicadat.dat, 2) + d = tmpicadat.dat(:, i); + tmpicadat.dat(d < prctile(d, 85) & d > prctile(d, 15), i) = 0; + +end + +orthviews(tmpicadat); +spm_orthviews_white_background + +end + + +% m1 = prctile(icadat.dat(:), 5); +% m2 = prctile(icadat.dat(:), 95); +% +% spm_orthviews('window', 1:min(24, size(icadat.dat, 2)), [m1 m2]); + diff --git a/@image_vector/image_vector.m b/@image_vector/image_vector.m new file mode 100644 index 00000000..d57a5473 --- /dev/null +++ b/@image_vector/image_vector.m @@ -0,0 +1,97 @@ +classdef image_vector + + properties + + dat % generic name for data + dat_descrip + volInfo + + removed_voxels = logical(0); + removed_images = logical(0); + + image_names + fullpath + files_exist + history + + end + + methods + + % Class constructor + function obj = image_vector(varargin) + % Enter fieldname', value pairs in any order to create class + % instance + + % --------------------------------- + % Create empty image_vector object, and return if no additional + % arguments + % --------------------------------- + + obj.dat = []; + obj.volInfo = []; + obj.image_names = []; + obj.fullpath = char([]); + obj.files_exist = false; + obj.history = {}; + + % The code below can be generic to any class definition + % It parses 'fieldname', value pairs of inputs + % and returns a warning if unexpected strings are found. + + if nargin == 0 + return + end + + % all valid fieldnames + valid_names = fieldnames(obj); + + for i = 1:length(varargin) + if ischar(varargin{i}) + + % Look for a field (attribute) with the input name + wh = strmatch(varargin{i}, valid_names, 'exact'); + + % behaviors for valid fields + if ~isempty(wh) + + obj.(varargin{i}) = varargin{i + 1}; + + % eliminate strings to prevent warnings on char + % inputs + if ischar(varargin{i + 1}) + varargin{i + 1} = []; + end + + % special methods for specific fields + switch varargin{i} + + end + + else + warning('inputargs:BadInput', sprintf('Unknown field: %s', varargin{i})); + + end + end % string input + end % process inputs + + % load data, if we don't have data yet + % But we do have valid image names. + % ---------------------------------------- + + obj = check_image_filenames(obj); + + if isempty(obj.dat) && any(obj.files_exist) + obj = read_from_file(obj); + + elseif isempty(obj.dat) + disp('Warning: .dat is empty and files cannot be found. No image data in object.'); + end + + + end % class constructor + + end % methods + +end % classdef + diff --git a/@image_vector/isosurface.m b/@image_vector/isosurface.m new file mode 100644 index 00000000..c6a173e9 --- /dev/null +++ b/@image_vector/isosurface.m @@ -0,0 +1,25 @@ +function [p, mesh_struct] = isosurface(obj, mythresh) + +[~, ~, mesh_struct] = reconstruct_image(obj); % get volume data for slices +voldata = mesh_struct.voldata; + +mycolor = [.5 .5 .5]; +mysmoothbox = 3; +mygaussstd = 1; +%mythresh = 80; + +V = smooth3(voldata, 'gaussian', mysmoothbox, mygaussstd); + +Fvoldata = isosurface(mesh_struct.X, mesh_struct.Y, mesh_struct.Z, V, mythresh); + +p = patch('Faces',Fvoldata.faces,'Vertices',Fvoldata.vertices,'FaceColor',[.5 .5 .5], ... + 'EdgeColor','none','SpecularStrength',.2,'FaceAlpha',.3,'SpecularExponent',200); + +set(p, 'FaceColor', mycolor); + +lighting gouraud +camlight right +camlight left +material dull + +end \ No newline at end of file diff --git a/@image_vector/mean.m b/@image_vector/mean.m new file mode 100644 index 00000000..1ad5206e --- /dev/null +++ b/@image_vector/mean.m @@ -0,0 +1,71 @@ +function m = mean(obj, varargin) +% function m = mean(obj, [optional args]) +% +% Create an image_vector object with mean values for each voxel (cols) +% across images (rows) of an fmri_data object. +% +% m is an image_vector object whose data contains the mean values. +% +% Options are: +% 'write', followed by file name +% 'path', followed by location for file (default = current directory) +% 'orthviews' -> show orthviews for this image, same as orthviews(m) +% 'histogram' -> show histogram for this image, same as histogram(m) +% 'plot' -> do both +% +% Examples: +% If sdat is an fmri_data object with multiple images, +% m = mean(sdat, 'plot', 'write', anatmeanname, 'path', maskdir); +% + +fname = []; +fpath = pwd; +dohist = 0; +doorth = 0; +doplot = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case 'write', fname = varargin{i+1}; varargin{i+1} = []; + case 'path', fpath = varargin{i+1}; varargin{i+1} = []; + case 'plot', dohist = 1; doorth = 1; + + case {'hist', 'histogram'}, dohist = 1; + case {'orth', 'orthviews'}, doorth = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if isa(obj, 'fmri_data') + m = image_vector('dat', mean(obj.dat', 1)', 'volInfo', obj.mask.volInfo); +else + m = image_vector('dat', mean(obj.dat', 1)', 'volInfo', obj.volInfo); +end + +m.removed_voxels = obj.removed_voxels; + +if doplot || doorth + orthviews(m); +end + +if doplot || dohist + create_figure('image_histogram'); + histogram(m); +end + +if ~isempty(fname) + fullp = fullfile(fpath, fname); + fprintf('Writing mean image to disk') + + m.filename = fname; + m.fullpath = fullp; + + write(m); +end + + +end % function \ No newline at end of file diff --git a/@image_vector/minus.m b/@image_vector/minus.m new file mode 100644 index 00000000..3ee1c021 --- /dev/null +++ b/@image_vector/minus.m @@ -0,0 +1,33 @@ +function c = minus(obj1,obj2) +% function s = minus(obj1, obj2) +% +% Implements the minus (-) operator on image_vector objects across voxels. +% Requires that each object has an equal number of columns and voxels +% +% Examples: +% c = dat1 - dat2; +% +% Programmer Notes +% Created 3/14/14 by Luke Chang + + +%Check if image_vector object +if ~isa(obj1,'image_vector') || ~isa(obj2,'image_vector') + error('Input Data is not an image_vector object') +end + +%Check number of rows +if size(obj1.dat,1)~=size(obj2.dat,1) + error('number of voxels is different between objects.') +end + +%Check number of columns +if size(obj1.dat,2)~=size(obj2.dat,2) + error('number of observations is different between objects.') +end + +c = obj1; +c.dat = obj1.dat - obj2.dat; + + +end % function \ No newline at end of file diff --git a/@image_vector/montage.m b/@image_vector/montage.m new file mode 100644 index 00000000..e4ac7726 --- /dev/null +++ b/@image_vector/montage.m @@ -0,0 +1,93 @@ +function fig_handle = montage(image_obj, varargin) +% Create a montage of an image_vector (or statistic_image or fmri_data) object +% +% Usage: +% ------------------------------------------------------------------------- +% [fig_handle or o2 fmridisp object] = montage(image_obj, [optional arguments]) +% +% Optional inputs: +% 'fmridisplay' for fmridisplay object style montage [default] +% 'scnmontage' for circa 2008-style SCN lab montage for each image vector +% +% Examples: +% o2 = montage(mask); + +meth = 'fmridisplay'; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch vararagin{i} + case 'scnmontage', meth = 'scnmontage'; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +% number of images (cols) +n = size(image_obj.dat, 2); + +switch meth + case 'fmridisplay' + + r = region(image_obj); + + o2 = canlab_results_fmridisplay(r, 'noblobs', 'nooutline'); + + if n > 8 + disp('Warning: Showing first 8 images in data object only.'); + n = 8; + end + + for i = 1:n + + obj = image_obj; + obj.dat = obj.dat(:, i); + + if isa(image_obj, 'statistic_image') + obj.sig = obj.sig(:, i); + obj.dat = obj.dat .* obj.sig; + end + + if i == 1 + o2 = canlab_results_fmridisplay(region(obj), o2, 'nooutline'); + + else + o2 = canlab_results_fmridisplay(region(obj), o2, 'nooutline', 'addmontages'); + end + + drawnow + + %o2 = addblobs(o2, region(obj), 'splitcolor', {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}); + + end + + fig_handle = o2; + clear o2 + + case 'scnmontage' + + overlay = which('SPM8_colin27T1_seg.img'); + + for i = 1:n + + % data from this image + dat = image_obj.dat(:, i); + + % top and bottom 10% + %dat(dat > prctile(dat, 10) & dat < prctile(dat, 90)) = 0; + + cl{i} = iimg_indx2clusters(dat, image_obj.volInfo); + + fig_handle(i) = montage_clusters(overlay, cl{i}, [2 2]); + + set(fig_handle, 'Name', sprintf('Montage %3.0f', i), 'Tag', sprintf('Montage %3.0f', i)) + + end + + otherwise, warning(['Unknown input string option:' varargin{i}]); + +end % switch + +end % fnction diff --git a/@image_vector/orthviews.m b/@image_vector/orthviews.m new file mode 100644 index 00000000..5e1bb95b --- /dev/null +++ b/@image_vector/orthviews.m @@ -0,0 +1,99 @@ +function orthviews(image_obj, varargin) +% Orthviews display (SPM) for CANlab image_vector (or fmri_data, statistic_image) object +% +% Usage: +% orthviews(image_obj, varargin) +% +% Options +% 'posneg' input generates orthviews using solid colors. +% 'largest_region' to center the orthviews on the largest region in the +% image +% +% Copyright Tor Wager, 2011 +% +% --------------------------------------------------------------- + +overlay = which('SPM8_colin27T1_seg.img'); + +doposneg = 0; +doreg = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'posneg', doposneg = 1; + + % functional commands + case 'overlay', overlay = varargin{i + 1}; varargin{i + 1} = []; + + case {'largest_region', 'largest_cluster'}, doreg = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if ~exist(overlay, 'file') + overlay = which(overlay); +end + +if isempty(overlay) || ~exist(overlay, 'file') + disp('Cannot find overlay image:') + disp(overlay) + return +end + +% replace missing voxels if necessary +image_obj = replace_empty(image_obj); + +n = size(image_obj.dat, 2); % images are columns here + +if n > 24 + disp('Warning! Only first 24 images can displayed with spm_orthviews.'); + n = 24; +end + + +spm_check_registration(repmat(overlay, n, 1)); + +for i = 1:n + + cl{i} = iimg_indx2clusters(image_obj.dat(:, i), image_obj.volInfo); + + if doposneg + warning off + cl{i} = cluster2region(cl{i}); + warning on % name field warning + [clpos{i}, clneg{i}] = posneg_separate(cl{i}, 'Z'); + + if ~isempty(clpos{i}) + cluster_orthviews(clpos{i}, {[1 1 0]}, 'add', 'handle', i, 'solid'); + end + + if ~isempty(clneg{i}) + cluster_orthviews(clneg{i}, {[0 0 1]}, 'add', 'handle', i, 'solid'); + end + + else + cluster_orthviews(cl{i}, 'add', 'handle', i); + end + + spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 1 1], [.5 .5 .5], [1 .5 0]); + +end + +% center image +spm_orthviews('Reposition', [0 0 0]); + +% if requested, try to center on largest region +if doreg + [~,wh] = max(cat(1,cl{1}.numVox)); + spm_orthviews('Reposition', cl{1}(wh).mm_center); +end +drawnow; + +end % function + + + diff --git a/@image_vector/plot_current_orthviews_coord.m b/@image_vector/plot_current_orthviews_coord.m new file mode 100644 index 00000000..82ec4668 --- /dev/null +++ b/@image_vector/plot_current_orthviews_coord.m @@ -0,0 +1,39 @@ +function voxel_data_series = plot_current_orthviews_coord(dat) +% Retrieves and plots the image data series at the current crosshairs in spm_orthviews +% +% voxel_data_series = plot_current_orthviews_coord(dat) + +voxel_data_series = []; + +if isempty(dat.volInfo) || ~isfield(dat.volInfo, 'mat') || isempty(dat.volInfo.mat) + disp('Warning: could not find valid volInfo field in data object'); + return +end + +XYZ = mm2voxel(spm_orthviews('Pos'), dat.volInfo.mat); + +if isempty(XYZ) + disp('Warning: could not retrieve coordinate from SPM orthviews'); + return +end + +[isinmask, loc] = ismember(XYZ, dat.volInfo.xyzlist(~dat.removed_voxels, :), 'rows'); + +create_figure('image series'); +if isinmask + + voxel_data_series = dat.dat(loc, :); + plot(voxel_data_series, 'k.-'); + xlabel('Image series'); + ylabel('Value'); + + %hold on; plot(dat2.dat(loc, :) + 100, 'r.-'); + +else + + text(0, 0, 'Vox is outside mask', 'FontSize', 24); + set(gca, 'XLim', [-.5 1], 'YLim', [-1 1]); + axis off +end + +end \ No newline at end of file diff --git a/@image_vector/plus.m b/@image_vector/plus.m new file mode 100644 index 00000000..e8db705e --- /dev/null +++ b/@image_vector/plus.m @@ -0,0 +1,33 @@ +function c = plus(obj1,obj2) +% function s = plus(obj1, obj2) +% +% Implements the plus (+) operator on image_vector objects across voxels. +% Requires that each object has an equal number of columns and voxels +% +% Examples: +% c = dat1 + dat2; +% +% Programmer Notes +% Created 3/14/14 by Luke Chang + + +%Check if image_vector object +if ~isa(obj1,'image_vector') || ~isa(obj2,'image_vector') + error('Input Data is not an image_vector object') +end + +%Check number of rows +if size(obj1.dat,1)~=size(obj2.dat,1) + error('number of voxels is different between objects.') +end + +%Check number of columns +if size(obj1.dat,2)~=size(obj2.dat,2) + error('number of observations is different between objects.') +end + +c = obj1; +c.dat = obj1.dat + obj2.dat; + + +end % function \ No newline at end of file diff --git a/@image_vector/power.m b/@image_vector/power.m new file mode 100644 index 00000000..4faeaea3 --- /dev/null +++ b/@image_vector/power.m @@ -0,0 +1,22 @@ +function c = power(obj, b) +% function s = power(obj1, obj2) +% +% Implements the power (^) operator on image_vector objects across voxels. +% +% Examples: +% c = dat1^2; +% +% Programmer Notes +% Created 3/14/14 by Luke Chang + + +%Check if image_vector object +if ~isa(obj,'image_vector') + error('Input Data is not an image_vector object') +end + +c = obj; +c.dat = obj.dat.^b; + + +end % function \ No newline at end of file diff --git a/@image_vector/preprocess.m b/@image_vector/preprocess.m new file mode 100644 index 00000000..9aee0db5 --- /dev/null +++ b/@image_vector/preprocess.m @@ -0,0 +1,487 @@ +function obj = preprocess(obj, meth, varargin) +% obj = preprocess(obj, meth, varargin) +% +% Preprocesses data in an fmri_data object +% Data is observations (i.e., voxels, subjects) x images, so operating on the columns operates on +% images, and operating on the rows operates on voxels (or variables more +% generally) across images. +% +% meth: Options +% +% 'resid': Residualize voxels with respect to covariates +% Uses obj.covariates, obj.dat. +% Adds intercept automatically. You can tell it to add the mean response per voxel back in: +% obj = preprocess(obj, 'resid', [add mean back in flag]) +% +% 'hpfilter': High-pass filter and remove run intercepts and first two +% images per run. +% Uses obj.dat, obj.images_per_session +% obj = preprocess(obj, 'hpfilter', HPlen in s, TR) +% +% 'windsorize': Windsorize entire data matrix to 3 STD +% +% 'windsorizevoxels': Windsorize each time series in data matrix to 3 STD +% +% 'session_outliers': Identify session-wise (run-wise) outliers with significant +% based on mahalanobis distance with FDR-corrected P-values in chi-square test. +% Impute session grand mean outliers. +% +% 'outliers': Identify outlier time points for each session based on +% mahalanobis distance (see above) across global mean for slices and +% spatial STD for slices, as in scn_session_spike_id. +% Outliers at 3 SD based on timeseries added to obj.covariates. +% +% 'outliers_rmssd': Identify outlier time points for each session based on +% root-mean-square successive differences between images (across voxels.) +% this is the std (across voxels) of the successive diffs across images. +% Outliers at 3.5 SD based on timeseries added to obj.covariates. +% +% 'smooth': Smoothed images with Gaussian filter +% obj = preprocess(obj, 'smooth', FWHM in mm) +% % NOTE: SMOOTHING KERNEL MAY BE IN VOX, AS VOL INFO IS NOT PASSED IN +% +% 'interp_images': Interpolate all voxels in a series of images specified +% by logical vector whout. +% obj = preprocess(obj, 'interp_images', whout); +% +% Examples: two complementary ways to get and plot outliers: +% dat = preprocess(dat, 'outliers', 'plot'); +% subplot(5, 1, 5); % go to new panel... +% dat = preprocess(dat, 'outliers_rmssd', 'plot'); + +switch meth + + case {'resid', 'residuals'} + add_mean_in = 0; + if ~isempty(varargin) > 0 && varargin{1} + add_mean_in = 1; + end + + obj.dat = resid(obj.covariates, obj.dat', add_mean_in)'; + + obj.history{end + 1} = 'Residualized voxels with respect to covariates.'; + + + case 'hpfilter' + + + if length(varargin) == 0 + error('Enter HP filter cutoff in sec as 3rd argument.'); + end + + if length(varargin) == 1 + error('Enter TR in sec as 4th argument.'); + end + + hpcutoff = varargin{1}; + TR = varargin{2}; + + whgood = ~all(obj.dat == 0, 2); + + fprintf('removing intercepts, 1st 2 images/run, high-pass filtering %3.0f voxels at %3.0f sec', sum(whgood), hpcutoff); + + imageseriesmean = mean(obj.dat(whgood, :), 2); + + obj.dat(whgood, :) = hpfilter(obj.dat(whgood, :)', TR, hpcutoff, obj.images_per_session, [], 1:2)'; + + obj.dat(whgood, :) = obj.dat(whgood, :) + repmat(imageseriesmean, 1, size(obj.dat, 2)); + + obj.history{end+1} = 'HP filtered and residualized with respect to session intercepts and first 2 images of each run, mean added back in'; + + fprintf('\n'); + + case {'windsor', 'windsorize'} + % Windsorize entire data matrix + %m = mean(obj.dat(:)); + % note: the line above was giving different values for operation on + % single and double values in Matlab 2012a. Very disturbing! The + % correct value is given by double() or the line below. + m = sum(sum(obj.dat)) ./ numel(obj.dat); % save memory space + + s = 3 .* std(obj.dat(:)); + + whbad = obj.dat < m - s; + obj.dat(whbad) = m - s; + whbad2 = obj.dat > m + s; + obj.dat(whbad2) = m + s; + + nbad = sum(whbad(:)) + sum(whbad2(:)); + percbad = 100 .* nbad ./ numel(obj.dat); + + obj.history{end+1} = sprintf('Windsorized data matrix to 3 STD; adjusted %3.0f values, %3.1f%% of values', nbad, percbad); + disp(obj.history{end}); + + case 'windsorizevoxels' + + whbad = all(obj.dat == 0, 2); + nok = sum(~whbad); + + fprintf('windsorizing %3.0f voxels to 3 std: %05d', nok, 0); + for i = 1:nok + if mod(i, 100) == 0, fprintf('\b\b\b\b\b%05d', i); end + obj.dat(i, :) = trimts(obj.dat(i, :)', 3, [])'; + end + + obj.history{end+1} = 'Windsorized data voxel-wise to 3 STD'; + + case 'session_outliers' + + nscan = obj.images_per_session; % num images per session + I = intercept_model(nscan); + + obj.history{end+1} = 'Imputed mean for session outliers in GM/GSTD space: '; + + create_figure('session_outliers', 3, size(I, 2)); + + for i = 1:size(I, 2) + + wh = find(I(:, i)); + y = obj.dat(:, wh); + + mv = mean(y')'; % mean across time, for each voxel + m = mean(y)'; % mean across voxels, for each time point + s = std(y)'; % spatial std + + % mahalanobis: strange patterns across slices + d2 = mahal([m s], [m s]); + + % threshold + p = 1 - chi2cdf(d2, 2); + p(p == 0) = eps; + pthr = max(.05 / length(d2), FDR(p, .05)); % max of FDR or Bonferroni + if isempty(pthr), pthr = .05 / length(d2); end + whb = p < pthr; % which images are outliers + + % impute mean image for this session + y(:, whb) = repmat(mv, 1, sum(whb)); + obj.dat(:, wh) = y; + + % report + obj.history{end} = [obj.history{end} sprintf(' S%02d:%2.0f imgs', i, sum(whb))]; + + subplot(3, size(I, 2), i) + plot(m, s, 'k.') + plot(m(whb), s(whb), 'ro', 'LineWidth', 2); + xlabel('Image mean'); + if i == 1, ylabel('Image std'); end + title(['Session' num2str(i)]) + + subplot(3, size(I, 2), size(I, 2)+i) + plot(m, 'k.-') + plot(find(whb), m(whb), 'ro', 'LineWidth', 2); + xlabel('Time'); + if i == 1, ylabel('Image mean'); end + + m = mean(y)'; + subplot(3, size(I, 2), 2*size(I, 2)+i) + plot(m, 'k.-') + plot(find(whb), m(whb), 'ro', 'LineWidth', 2); + xlabel('Time'); + if i == 1, ylabel('Mean after adjustment'); end + + drawnow + + end % session + + disp(obj.history{end}); + + + case 'outliers' + % regress out outliers + stdev = 3; + + doplot = any(strcmp(varargin, 'plot')); + + nscan = obj.images_per_session; + if isempty(nscan), error('Enter obj.images_per_session to use this method.'); end + + I = intercept_model(nscan); + for i = 1:size(I, 2) + + clear gslice stdslice; % Wani added to fix a bug + + fprintf('Session %3.0f: ', i) + + wh = find(I(:, i)); + y = obj.dat(:, wh); + + g = mean(y)'; + + if isempty(obj.volInfo), error('obj.volInfo must be complete!'); end + + z = obj.volInfo.xyzlist(:, 3); + + if size(z, 1) == length(obj.removed_voxels) + z(obj.removed_voxels) = []; + end + + u = unique(z); + + for t = 1:size(y, 2) % time within session + + for j = 1:length(u) + gslice(j, t) = mean( obj.dat(z == u(j), t) ); + stdslice(j, t) = std( obj.dat(z == u(j), t) ); + end + + end + + wh_no_data = ~any(gslice')' | ~any(stdslice')'; + gslice(wh_no_data,:) = []; + stdslice(wh_no_data,:) = []; + + % mahalanobis: strange patterns across slices + d2 = mahal([stdslice' gslice'], [stdslice' gslice']); + + [gtrim, dummy, gspikes] = trimts(g, stdev, [], 1); + + [dummy, dummy, mahalspikes] = trimts(d2, stdev, [], 1); + spikes = unique([gspikes; mahalspikes]); + + spikesperimg = 100*length(spikes) ./ length(g); + snr = mean(g) ./ std(g); + + fprintf('%3.0f Potential outliers\t%%Spikes: %3.2f\tGlobal SNR (Mean/STD): %3.2f\n', length(spikes), spikesperimg , snr) + + nuisance_covs{i} = intercept_model(length(gtrim), spikes); + % get rid of intercept, because we may want to add this + % separately + nuisance_covs{i} = nuisance_covs{i}(:, 2:end); + + if doplot + gall{i} = g'; + gspikesall{i} = gspikes'; + gsliceall{i} = scale(gslice', 1)'; + stdsliceall{i} = scale(stdslice', 1)'; + d2all{i} = d2'; + end + + end % session + + covs = blkdiag(nuisance_covs{:}); + + % Plot + + if doplot + create_figure('sessions', 5, 1); + + subplot(5, 1, 1); + imagesc(cat(2, gsliceall{:})); axis tight + ylabel('slice'); + title('Global mean signal') + + subplot(5, 1, 2); + imagesc(cat(2, stdsliceall{:})); axis tight + ylabel('slice'); + title('Standard deviation across slice voxels') + + subplot(5, 1, 3); + toplot = cat(2, gall{:}); + draw_session_boxes(obj.images_per_session, toplot) + plot(toplot); axis tight + set(gca, 'YLim', [min(toplot) max(toplot)]) + title('Global in-brain signal') + + subplot(5, 1, 4); + toplot = cat(2, d2all{:}); + draw_session_boxes(obj.images_per_session, toplot) + plot(toplot); axis tight + set(gca, 'YLim', [min(toplot) max(toplot)]) + ylabel('d^2'); + title('Mahalanobis distance') + xlabel('Time (images)') + for i = 1:size(covs, 2) + lineh = plot_vertical_line(find(covs(:, i))); + set(lineh, 'Color', 'r'); + end + + subplot(5, 1, 5); axis off; % for rmssd plot... + + end + + obj.covariates = [obj.covariates covs]; + for i = 1:size(covs, 2), obj.covariate_names{end+1} = ['outlier_cov']; end + + obj.history{end+1} = sprintf('Added %3.0f global/mahal outlier covariates to covariates field.', size(covs, 2)); + disp(obj.history{end}); + + case 'outliers_rmssd' + % outliers from root mean square successive differences across + % images + + % remove intercepts first... + nscan = obj.images_per_session; + if isempty(nscan), error('Enter obj.images_per_session to use this method.'); end + + I = intercept_model(nscan); + datadj = (pinv(I) * obj.dat')'; + + sdlim = 3; + sdiffs = diff(obj.dat')'; % successive differences + sdiffs = [mean(sdiffs, 2) sdiffs]; % keep in image order; shift over one + mysd = std(sdiffs(:)); + + rmssd = (mean(sdiffs .^ 2)) .^ .5; %spatsd = std(sdiffs); % this is not quite the same thing! + + % avoid first time point being very different and influencing distribution and plots. + rmssd(1) = mean(rmssd); + + out_rmssd = [rmssd > mean(rmssd) + sdlim*std(rmssd)]; + + doplot = any(strcmp(varargin, 'plot')); + if doplot + %create_figure('outliers_rmssd', 2, 1); + draw_session_boxes(obj.images_per_session, rmssd) + plot(rmssd, 'b'); + title('Root mean square successive diffs (rmssd) across images'); + axis tight; + set(gca, 'YLim', [min(rmssd) max(rmssd)]) + ylabel('RMSSD'); + xlabel('Time (images)'); + end + + + % add covariates + wh = find(out_rmssd); + covs = zeros(length(out_rmssd), length(wh)); + for i = 1:length(wh) + covs(wh(i), i) = 1; + + if doplot, h = plot_vertical_line(wh(i)); set(h, 'Color', 'r'); end + end + + obj.covariates = [obj.covariates covs]; + for i = 1:size(covs, 2), obj.covariate_names{end+1} = ['outlier_rmssd_cov']; end + + obj.history{end} = [obj.history{end} sprintf('\nOutliers in RMSSD images: %3.0f%%, %2.0f imgs.\n', sum(out_rmssd)./length(out_rmssd), sum(out_rmssd))]; + disp(obj.history{end}); + + + case 'smooth' + + if length(varargin) == 0 + error('Enter smoothing FWHM in mm as 3rd argument.'); + end + + obj = replace_empty(obj); + obj.dat = iimg_smooth_3d(obj.dat, obj.volInfo, varargin{1}); + + obj.history{end+1} = sprintf('Smoothed images with %3.0f FWHM filter', varargin{1}); + disp(obj.history{end}); + + + case 'interp_images' + + if length(varargin) == 0 + error('whout argument missing. Must be logical vector of 1s and 0s for images to interpolate.'); + else + whout = varargin{1}; + end + + if any(whout ~= 1 & whout ~= 0) + error('whout must be logical vector of 1s and 0s for images to interpolate.'); + end + whout = logical(whout); + wh = find(whout); + whin = ~whout; % do just once + + + fprintf(1, 'Interpolating outlier images. Setup: ') + tic + obj = remove_empty(obj); + + t = 1:size(obj.dat, 2); + t(wh) = []; + v = size(obj.dat, 1); + + fprintf('%3.0f sec. Running: ', toc); + + tic + for i = 1:v + Vq = interp1(t, obj.dat(i, whin), wh, 'linear','extrap'); + % figure; plot(t, obj.dat(i, whin)); hold on; plot(wh, Vq, 'ro'); + obj.dat(i, wh) = Vq; + end + + fprintf('Done in %3.0f sec.\n', toc); + + obj.history{end + 1} = sprintf('Interpolated %3.0f images with 1-D linear interp.', length(wh)); + + otherwise + error('Unknown preprocessing method.') + + +end + +end + + + + +function draw_session_boxes(images_per_session, y) + + +nsess = length(images_per_session); +colors = {'k' 'k' 'k' 'k' 'k' 'k' 'k' 'k'}; %{'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; +while nsess > length(colors), colors = [colors colors]; end + +yval = min(y(:)) - .05 * min(y(:)); +yheight = (max(y(:)) + .05 * max(y(:))) - yval; + +c = cumsum(images_per_session); +st = [0 c(1:end-1)]; + +for jj = 1:2:nsess + h1 = drawbox(st(jj), images_per_session(jj), yval, yheight, colors{jj}(1)); + set(h1, 'FaceAlpha', .10, 'EdgeColor', 'none'); +end + +end + + + +function [r,X] = resid(X,y, varargin) +% [r,X] = resid(X,y, [add mean back in flag]) +% +% tor wager +% residuals from model fit +% +% adds intercept, if missing; adds mean response for each variable if +% requested +% Last edited: 6/2013 + +add_int = 0; +if ~isempty(varargin) > 0 && varargin{1} + add_int = 1; +end + +% find intercept +wint = all(X == repmat(X(1,:), size(X,1), 1)); +if ~any(wint) + X(:,end+1) = 1; + wint = zeros(1, size(X, 2)); + wint(end) = 1; +end + +y = double(y); + +% break up for efficiency - otherwise Matlab chokes with large + % matrices...(why?) + px = pinv(X); + pxy = px * y; + xpxy = X * pxy; + + +r = y - xpxy; % X * pinv(X) * y; + +if add_int + m = mean(y); + ym = repmat(m, size(X, 1), 1); + r = r + ym; +end + +end + + + + diff --git a/@image_vector/read_from_file.m b/@image_vector/read_from_file.m new file mode 100644 index 00000000..d8238b6c --- /dev/null +++ b/@image_vector/read_from_file.m @@ -0,0 +1,40 @@ +function obj = read_from_file(obj) +% Reads data from image filenames into obj.dat +% +% obj = read_from_file(obj) +% +% Try obj = check_image_filenames(obj) first. +% This is automatically called if you create a new image_vector object with +% names but do not directly enter data. e.g., the commands below will load data: +% +% name = 'salientmap.nii'; +% img = image_vector('image_names', name); +% + +disp('Reading image data into object .dat field.'); + +% if we have mask info, use that. Otherwise, create +if isempty(obj.volInfo) + disp('Creating mask info from first image and storing in .volInfo.'); + obj.volInfo = iimg_read_img(obj.fullpath(1, :), 2); +else + disp('Using mask and space-defining info already stored in .volInfo field.'); +end + +% Now extract the actual data from the mask +switch spm('Ver') + + case {'SPM8', 'SPM5'} + obj.dat = iimg_get_data(obj.volInfo, obj.fullpath, 'single', 'noexpand')'; + + case {'SPM2', 'SPM99'} + % legacy, for old SPM + obj.dat = iimg_get_data(obj.volInfo, obj.fullpath, 'single')'; + + otherwise + error('Unknown version of SPM! Update code, check path, etc.'); +end + + +end % function + diff --git a/@image_vector/rebuild_volinfo_from_dat.m b/@image_vector/rebuild_volinfo_from_dat.m new file mode 100644 index 00000000..904690c5 --- /dev/null +++ b/@image_vector/rebuild_volinfo_from_dat.m @@ -0,0 +1,34 @@ +function dat = rebuild_volinfo_from_dat(dat, newdat) +% Will rebuild volInfo (the image space, or sometimes "mask") from a vectorized image. +% In other words, will rebuild dat.volInfo from newdat. +% +% Also resets all voxels to be significant, if a statistic image +% +% INPUT +% dat: an image_vector +% newdat: a vector that MUST be size of ENTIRE image (dat.volInfo.nvox) +% +% OUTPUT +% dat: dat.dat contains the non-zero values of newdat, and dat.volInfo is +% correctly defining the image space + +if length(newdat) ~= dat.volInfo.nvox + error('newdat MUST be size of ENTIRE image (dat.volInfo.nvox). Image_vector.reconstruct_image may be helpful'); +end + +% rebuild volInfo from newdat +dat.volInfo.image_indx = newdat ~=0 & ~isnan(newdat); +dat.volInfo.wh_inmask = find(dat.volInfo.image_indx); +dat.volInfo.n_inmask = length(dat.volInfo.wh_inmask); +[i, j, k] = ind2sub(dat.volInfo(1).dim(1:3), dat.volInfo(1).wh_inmask); +dat.volInfo(1).xyzlist = [i j k]; + +% rebuild dat.dat with new data +dat.dat = newdat(dat.volInfo.image_indx); + +% rebuild volInfo.cluster +dat.volInfo.cluster = dat.volInfo.wh_inmask; % this forces reparse_contiguous to rebuild the cluster correctly. maybe not ideal solution, but maybe yes -- don't know the code well enough. Yoni 7/14 +dat = reparse_contiguous(dat); + +% rebuild sig field if needed +if isprop(dat, 'sig'), dat.sig = true(size(dat.dat)); end diff --git a/@image_vector/reconstruct_image.m b/@image_vector/reconstruct_image.m new file mode 100644 index 00000000..6ed594a6 --- /dev/null +++ b/@image_vector/reconstruct_image.m @@ -0,0 +1,71 @@ +function [voldata, vectorized_voldata, xyz_coord_struct] = reconstruct_image(obj) +% Reconstruct a 3-D or 4-D image from image_vector object obj +% [voldata, vectorized_voldata, xyz_coord_struct] = reconstruct_image(obj) +% +% voldata is and X x Y x Z x Images matrix +% vectorized_voldata is the same, with all voxels vectorized +% +% This output has one element for every voxel in THE ENTIRE IMAGE, and so +% can be very memory-intensive. But it's useful for lining up voxels +% across images with different masks/in-mask voxels. +% +% This function returns output in memory; +% see image_vector.write for writing .img files to disk. +% +% Outputs: +% voldata : 3-D recon volume +% vectorized_voldata : volume in column vetor, iimg_xxx function format +% xyz_coord_struct : has fields with coordinate information in mm (world) space +% x, y, z : vectors of coordinates in mm for each of the 3 +% dimensions of the image +% X, Y, Z : output matrices from meshgrid with mm coordinates, +% for volume visualization. +% These can be passed to surf or isocaps functions for volume +% visualization in world space (mm). +% +% Copyright 2011 tor wager + +% Programmers' notes: +% Aug 2012: This function does not flip the data based on the sign of x dimension. +% The flipping is applied in image writing / display in +% iimg_reconstruct_vols, write method, spm_orthviews, etc. +% +% July 2013 : Tor : Added xyz_coord_struct output + +obj = replace_empty(obj); +voldata = iimg_reconstruct_vols(obj.dat, obj.volInfo); + +if nargout > 1 + vectorized_voldata = voldata(:); +end + +if nargout > 2 + disp('Returning coordinates in mm and meshgrid matrices.'); + + % return xyz mm coordinates for full volume + xyzmin = voxel2mm([1 1 1]', obj.volInfo.mat); + xyzmax = voxel2mm(obj.volInfo.dim', obj.volInfo.mat); + + %voxsize = diag(obj.volInfo.mat(1:3, 1:3)); + + % rotate and flip for compatibility with addbrain.m direction + + for i = 1:size(voldata, 3) + + vout(:, :, i) = rot90(voldata(:, :, i)); + + vout(:, :, i) = flipdim(vout(:, :, i), 1); + end + + dims = size(voldata); + + % reverse dims 2 and 1 because dim 1 is y + x = linspace(xyzmin(1), xyzmax(1), dims(1))'; + y = linspace(xyzmin(2), xyzmax(2), dims(2))'; + z = linspace(xyzmin(3), xyzmax(3), dims(3))'; + + [X, Y, Z] = meshgrid(x, y, z); + + xyz_coord_struct = struct('voldata', vout, 'x', x, 'y', y, 'z', z, 'X', X, 'Y', Y, 'Z', Z); + +end \ No newline at end of file diff --git a/@image_vector/remove_empty.m b/@image_vector/remove_empty.m new file mode 100644 index 00000000..c770990e --- /dev/null +++ b/@image_vector/remove_empty.m @@ -0,0 +1,131 @@ +function dat = remove_empty(dat, varargin) +% dat = remove_empty(dat, [logical vector of custom voxels to remove], [logical vector of imgs to remove]) +% +% remove vox: logical vector of custom voxels to remove, VOX x 1 +% remove im: logical vector of custom images to remove, 1 x IMAGES +% +% indices of removed data will be stored in removed_voxels and +% removed_images fields, to preserve ability to later reconstruct into 3D images +% Indicator vectors stored in: +% removed_images +% removed_voxels +% +% see also: replace_empty + +% force logical +dat.removed_images = logical(dat.removed_images); +dat.removed_voxels = logical(dat.removed_voxels); + +% Make sure field orientations conform - diff versions of code are +% different +if length(dat.removed_images) > size(dat.removed_images, 1), dat.removed_images = dat.removed_images'; end +if length(dat.removed_voxels) > size(dat.removed_voxels, 1), dat.removed_voxels = dat.removed_voxels'; end + +% get any new empty images that didn't exist before +empty_images = all(dat.dat == 0 | isnan(dat.dat), 1)'; +empty_voxels = all(dat.dat' == 0 | isnan(dat.dat'), 1)'; + +% add in previously removed voxels/images +if any(dat.removed_images) + empty_images = logical(zeroinsert(dat.removed_images, empty_images)); +end + +if any(dat.removed_voxels) + empty_voxels = logical(zeroinsert(dat.removed_voxels, empty_voxels)); +end + +% 2010a : Getting error with automatic sparse vector creation +empty_images = full(empty_images); +empty_voxels = full(empty_voxels); + +% Now empties are original length. + +v = length(empty_voxels); +k = length(empty_images); + +dat.history{end+1} = sprintf('removed %3.0f empty voxels and %3.0f empty images', sum(empty_voxels), sum(empty_images)); + + +% add custom removal specifications +% ----------------------------------------------------------------------------------- + +if ~isempty(varargin) && any(varargin{1}) + + remvox = varargin{1}; + if length(remvox) > size(remvox, 1), remvox = remvox'; end + + if length(remvox) ~= v + error('Input to remove_empty for custom voxels must be logical vector of length == total num voxels') + end + + % new voxels to remove + empty_voxels = empty_voxels | remvox; + + dat.history{end+1} = sprintf('removed %3.0f custom-specified voxels', sum(remvox)); + +end + +if length(varargin) > 1 && any(varargin{2}) + + remimgs = varargin{2}; + if length(remimgs) > size(remimgs, 1), remimgs = remimgs'; end + + if length(remimgs) ~= k + error('Input to remove_empty for custom images must be logical vector of length == num images') + end + + % new images to remove + empty_images = empty_images | remimgs; + + dat.history{end+1} = sprintf('removed %3.0f custom-specified images', sum(remimgs)); + +end + +% now do two things: update overall removed vox/image list, and remove new +% empties/to-removes +% ----------------------------------------------------------------------------------- + +prevvox = dat.removed_voxels; +previmgs = dat.removed_images; + + % keep overall list + dat.removed_voxels = dat.removed_voxels | empty_voxels; + dat.removed_images = dat.removed_images | empty_images; + + % remove new voxels. make indices current with current .dat field. + if any(prevvox) + empty_voxels(prevvox) = []; + empty_images(previmgs) = []; + end + +% remove data +% ----------------------------------------------------------------------------------- +if ~isempty(dat.dat) + dat.dat(empty_voxels, :) = []; + dat.dat(:, empty_images) = []; +else + return +end + +% Remove special statistic_image fields +if isa(dat, 'statistic_image') + for myfields = {'p' 'ste' 'sig'} + if ~isempty(dat.(myfields{1})) + dat.(myfields{1})(empty_voxels, :) = []; + end + end +end + +% error checking +% ----------------------------------------------------------------------------------- +numbad = sum(dat.removed_voxels); +sy = size(dat.dat, 1); +len = length(dat.removed_voxels); + +if numbad + sy ~= len + disp('Illegal dat.removed_voxels vector. length must equal len of original dataset, size(y, 1) + # left-out cases.'); + error('Left out = %3.0f, size(y, 1) = %3.0f, sum = %3.0f, length(wasbad) = %3.0f', numbad, sy, numbad + sy, len); +end + + +end diff --git a/@image_vector/reparse_contiguous.m b/@image_vector/reparse_contiguous.m new file mode 100644 index 00000000..94cdd0c9 --- /dev/null +++ b/@image_vector/reparse_contiguous.m @@ -0,0 +1,56 @@ +function obj = reparse_contiguous(obj, varargin) +% obj = reparse_contiguous(obj, ['nonempty']) +% +% Re-construct list of contiguous voxels in an image based on in-image +% voxel coordinates. Coordinates are taken from obj.volInfo.xyzlist. +% Results are saved in obj.volInfo.cluster. +% xyzlist can be generated from iimg_read_img, and is done automatically by +% object-oriented fMRI image classes (fmri_image, image_vector, +% statistic_image) +% +% If 'nonempty' is entered as an optional argument, will use only voxels +% that are non-zero, non-nan in all columns of obj.dat. +% +% copyright tor wager, 2011 + +% Programmers' notes: +% Edited 1/27/13 by tor to use all columns when calculating 'nonempty' +% also fixed bug - was not using 'nonempty' input in some +% cases + +wh = true(size(obj.volInfo.cluster)); %obj.volInfo.wh_inmask; +obj.volInfo(1).cluster = zeros(size(wh)); + +% restrict to voxels with actual data if desired +if any(strcmp(varargin, 'nonempty')) + + obj = replace_empty(obj); + wh = all(obj.dat ~= 0, 2) & all(~isnan(obj.dat), 2); % this will expand wh to full in-mask dataset, .nvox + +end + + +% .cluster can be either the size of a reduced, in-mask dataset after removing empties +% or the size of the full in-mask dataset that defined the image +% (volInfo.nvox). We have to switch behavior according to which it is. + +if size(obj.volInfo(1).cluster, 1) == size(obj.volInfo(1).xyzlist, 1) + + if length(wh) ~= length(obj.volInfo(1).cluster) + error('Bug: length(wh) does not match number of elements in volInfo.cluster.'); + end + + newcl = spm_clusters(obj.volInfo(1).xyzlist(wh, :)')'; + obj.volInfo(1).cluster = zeros(size(wh)); + obj.volInfo(1).cluster(wh) = newcl; + +else % full in-mask -- assign to correct voxels + + obj.volInfo(1).cluster(wh) = spm_clusters(obj.volInfo(1).xyzlist(wh, :)')'; + +end + +end + + + diff --git a/@image_vector/replace_empty.m b/@image_vector/replace_empty.m new file mode 100644 index 00000000..d1b37316 --- /dev/null +++ b/@image_vector/replace_empty.m @@ -0,0 +1,62 @@ +function obj = replace_empty(obj, varargin) +% Replace empty/missing values in an image data object +% obj = replace_empty(obj, [optional keywords]) +% +% Replace missing values in obj.dat stored in obj.removed_voxels and +% obj.removed_images with zeros. This returns obj.dat in a format that can +% be reconstructed into a 3-D or 4-D image matrix for brain visualization. +% +% Optional keywords: +% 'voxels' or 'images': replace only missing voxels/images +% +% Tor Wager, 12/1/10 +% +% See also: remove_empty, zeroinsert, nanremove, naninsert + +dovoxels = 1; +doimages = 1; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'voxels', doimages = 0; + case 'images', dovoxels = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +len = length(obj.removed_images); + +% * need proper length checking +% if len > 1 && len ~= size(obj.dat, 2) +% error('Wrong length for removed_images field!'); +% end + + +if doimages && any(obj.removed_images) + + obj.dat = zeroinsert(obj.removed_images, obj.dat')'; + obj.removed_images = false(size(obj.removed_images)); + +end + +if dovoxels && any(obj.removed_voxels) + + obj.dat = zeroinsert(obj.removed_voxels, obj.dat); + + if isa(obj, 'statistic_image') +% if ~isempty(obj.p), obj.p = zeroinsert(obj.removed_voxels, obj.p); end % Wani deleted this line, Aug 2012 + if ~isempty(obj.p), obj.p = oneinsert(obj.removed_voxels, obj.p); end % Wani added this line, Aug 2012 - because the threshold.m function recognizes zeros for p values as significant voxels. + if ~isempty(obj.ste), obj.ste = zeroinsert(obj.removed_voxels, obj.ste); end + if ~isempty(obj.sig), obj.sig = zeroinsert(obj.removed_voxels, obj.sig); end + end + + obj.removed_voxels = false(size(obj.removed_voxels)); + +end + + +end \ No newline at end of file diff --git a/@image_vector/resample_space.m b/@image_vector/resample_space.m new file mode 100644 index 00000000..e991b147 --- /dev/null +++ b/@image_vector/resample_space.m @@ -0,0 +1,234 @@ +function obj = resample_space(obj, sampleto, varargin) +% Resample the images in an fmri_data object (obj) to the space of another +% image (sampleto; e.g., a mask image). Works for all image_vector objects. +% +% obj = resample_space(obj, sampleto, [sampling method]) +% +% Sampleto may be one of these: +% 1) a volInfo structure (the image does not have to exist on the path) +% 2) an image_vector, fmri_data, fmri_mask_image object +% 3) a string with the name of an image +% +% Can enter resampling method as optional input. Takes any input to +% interp3: +% 'nearest' - nearest neighbor interpolation +% 'linear' - linear interpolation (default) +% 'spline' - spline interpolation +% 'cubic' - cubic interpolation as long as the data is uniformly +% spaced, otherwise the same as 'spline' +% +% Examples: +% label_mask = fmri_data(which('atlas_labels_combined.img')); +% label_mask = resample_space(label_mask, ivec, 'nearest') % resamples and masks label image + +% programmers' notes: +% 1/27/2012 Tor edited to handle .mask field in fmri_data and .sig field in +% statistic_image. Was causing errors otherwise... +% Also changed automatic behavior to reparse contig voxels with +% 'nonempty' in output obj + +n_imgs = size(obj.dat, 2); + + +Vto = sampleto.volInfo; +SPACEto = define_sampling_space(Vto, 1); + +Vfrom = obj.volInfo; +SPACEfrom = define_sampling_space(Vfrom, 1); + +obj_out = obj; +obj_out.dat = []; +obj_out.volInfo = Vto; + +obj = replace_empty(obj); % to make sure vox line up + +for i = 1:n_imgs + + voldata = iimg_reconstruct_vols(obj.dat(:, i), obj.volInfo); + + resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); + + resampled_dat = resampled_dat(:); + + obj_out.dat(:, i) = resampled_dat(Vto.wh_inmask); + +end + +% in case of NaN values +obj_out.dat(isnan(obj_out.dat)) = 0; + + +if size(obj_out.dat, 1) == sum(obj_out.volInfo.image_indx) + % this should always/almost always be true - assign missing/removed vox + obj_out.removed_voxels = ~obj_out.volInfo.image_indx; + obj_out.removed_voxels = obj_out.removed_voxels(obj_out.volInfo.wh_inmask); +else + obj_out.removed_voxels = false; +end + +% add clusters if needed +if obj_out.volInfo(1).n_inmask < 50000 + obj_out.volInfo(1).cluster = spm_clusters(obj_out.volInfo(1).xyzlist')'; +else + obj_out.volInfo(1).cluster = ones(obj_out.volInfo(1).n_inmask, 1); +end + +if isa(obj_out, 'statistic_image') && ~isempty(obj_out.sig) + disp('resample_space: removing threshold information from statistic_image') + obj_out.sig = []; +end + +obj = obj_out; + +% This stuff below added 1/27/13 by tor + +% re-parse clusters +obj = reparse_contiguous(obj, 'nonempty'); + +if isa(obj, 'fmri_data') + % fmri_data has this field, but other image_vector objects do not. + obj.mask = resample_space(obj.mask, sampleto); +end + +if isa(obj, 'statistic_image') + % statistic_image has this field, but other image_vector objects do not. + obj.sig = ones(size(obj.dat)); + disp('.sig field reset. Re-threshold if necessary.'); +end + +obj.history{end+1} = sprintf('Resampled data to space of %s', sampleto.volInfo.fname); + + +end + +% % +% % % --------------------------------------------------------- +% % % Define image name to sample to and volInfo from input +% % % --------------------------------------------------------- +% % % Sampleto may be one of these: +% % % 1) a volInfo structure (the image does not have to exist on the path) +% % % 2) an image_vector, fmri_data, fmri_mask_image object +% % % 3) a string with the name of an image +% % +% % [image_name_to_sample_to, volInfo_to, imgobj_to] = define_sample_to_image(sampleto); +% % +% % fprintf('Resampling data from %3.0f images to space of %s\n', n_imgs, image_name_to_sample_to); +% % +% % % --------------------------------------------------------- +% % % If the images corresponding to data we're resampling don't exist, +% % % we must write them to disk +% % % --------------------------------------------------------- +% % obj = check_image_filenames(obj); +% % +% % if ~all(obj.files_exist) +% % disp('Writing temporary image file for resampling. This will be removed.') +% % fullpath_orig = obj.fullpath; % save the name for later +% % +% % str = 'qwertyuiopasdfghjkl'; +% % str = str(randperm(length(str))); +% % name = sprintf('tmp_image%s.img', str); +% % obj.fullpath = fullfile(pwd, name); +% % write(obj); +% % end +% % +% % newdat = zeros(volInfo_to.n_inmask, n_imgs, 'single'); +% % +% % for i = 1:n_imgs +% % +% % if size(obj.fullpath, 1) < i +% % error('%3.0f images in obj.dat, but only %3.0f image names in obj.fullpath.', n_imgs, size(obj.fullpath, 1)); +% % end +% % +% % % resample to new space and re-extract vector +% % loadImg = deblank(obj.fullpath(i, :)); % ',' num2str(i)]); %**may be issues with 4-D vs. 3-D files +% % +% % imgData = scn_map_image(loadImg, volInfo_to); +% % newdat(:, i) = imgData(volInfo_to.wh_inmask); +% % +% % end +% % +% % obj.dat = newdat; +% % obj.volInfo = volInfo_to; +% % +% % if isa(obj, 'fmri_data') +% % % fmri_data has this field, but other image_vector objects do not. +% % obj.mask = resample_to_image_space(obj.mask, imgobj_to); +% % end +% % +% % if isa(obj, 'statistic_image') +% % % statistic_image has this field, but other image_vector objects do not. +% % obj.sig = ones(size(obj.dat)); +% % disp('.sig field reset. Re-threshold if necessary.'); +% % end +% % +% % obj.history{end+1} = sprintf('Resampled data to space of %s', image_name_to_sample_to); +% % +% % % Clean up: remove temporary images +% % if ~all(obj.files_exist) +% % str = sprintf('!rm %s*', obj.fullpath(1:end-4)); +% % disp('Removing temporary files:') +% % disp(str) +% % eval(str) +% % obj.fullpath = fullpath_orig; +% % end +% % +% % % removed voxels must be updated due to resampling +% % obj.removed_voxels = false(obj.volInfo.n_inmask, 1); +% % +% % end % function +% % +% % % +% % % +% % % +% % % +% % % +% % +% % +% % function [image_name_to_sample_to, volInfo_to, imgobj_to] = define_sample_to_image(sampleto) +% % +% % switch class(sampleto) +% % case 'char' +% % image_name_to_sample_to = sampleto; +% % +% % volInfo_to = iimg_read_img(sampleto, 2, 1, 1); % read data from file, first volume only +% % +% % imgobj_to = fmri_mask_image(image_name_to_sample_to); +% % +% % case {'fmri_mask_image'} +% % +% % if ~isempty(sampleto.space_defining_image_name) +% % image_name_to_sample_to = sampleto.space_defining_image_name; +% % else +% % image_name_to_sample_to = sampleto.volInfo.fname; +% % end +% % +% % volInfo_to = sampleto.volInfo; +% % imgobj_to = sampleto; +% % +% % case {'image_vector', 'fmri_data', 'statistic_image'} +% % image_name_to_sample_to = sampleto.volInfo.fname; +% % volInfo_to = sampleto.volInfo; +% % imgobj_to = sampleto; +% % +% % case 'struct' +% % % assume its a volInfo structure +% % image_name_to_sample_to = sampleto.fname; +% % volInfo_to = sampleto; +% % +% % if exist(image_name_to_sample_to, 'file') +% % imgobj_to = fmri_mask_image(image_name_to_sample_to); +% % else +% % fprintf('Cannot resample to volInfo input struct because volInfo.fname image\n'); +% % fprintf('%s\n cannot be found.\n', image_name_to_sample_to); +% % end +% % +% % otherwise +% % disp('fmri_mask_image.resample_to_image_space: illegal sampleto input.'); +% % disp('Must be volInfo structure or image_vector, fmri_data, fmri_mask_image object') +% % disp('or a char name of an image.'); +% % error('exiting.'); +% % end +% % +% % end +% % +% % diff --git a/@image_vector/resample_time.m b/@image_vector/resample_time.m new file mode 100644 index 00000000..3662dc51 --- /dev/null +++ b/@image_vector/resample_time.m @@ -0,0 +1,69 @@ +function obj = resample_time(obj, source_TR, target_TR, varargin) + +% Resample the time-series images (source_time_interval) in an fmri_data object (obj) +% to the different time series (target_time_interval). Works for all image_vector objects. +% +% obj = resample_time(obj, source_time_interval, target_time_interval, varargin) +% +% options: +% 1. 'meth' (Interpolation methods) +% You can enter resampling method as optional input. Takes any input to +% 'nearest' - nearest neighbor interpolation +% 'linear' - linear interpolation (default) +% 'spline' - spline interpolation +% 'cubic' - cubic interpolation as long as the data is uniformly +% spaced, otherwise the same as 'spline' +% 2. 'slice' (a fraction of the slice timing correction) +% The default is 0.5, meaning if your TR is 2s, the time point of your TR image +% will be considered as the middle point of the TR bins. You can use this option +% to use different time points. If you are upsampling your data (i.e., +% your target TR is shorter than your source TR), you need to discard the +% first column of your data. This function will return the first time point data as NaN. +% +% Examples: +% +% dat = fmri_data('/Volumes/RAID1/labdata/current/BMRK3/Imaging/spatiotemp_biomarker/STmarker1.img'); +% +% dat = resample_time(dat, 2, 1.3) +% with options: +% dat = resample_time(dat, 2, 1.3, 'meth', 'linear', 'slice', .3) + +slice_frac = .5; +intp_meth = []; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'meth', 'methods'} + intp_meth = varargin{i+1}; + case {'slice'} + slice_frac = varargin{i+1}; + otherwise + end + end +end + +n_ts = size(obj.dat, 2); +n_vox = size(obj.dat,1); +obj_out = obj; +obj_out.dat = []; + +sc_start_time = source_TR.*slice_frac; +tg_start_time = target_TR.*slice_frac; + +end_time = source_TR.*n_ts-sc_start_time; + +[X, Y] = meshgrid(sc_start_time:source_TR:end_time, 1:n_vox); +[Xq, Yq] = meshgrid(tg_start_time:target_TR:end_time, 1:n_vox); + +if isempty(intp_meth) + obj_out.dat = interp2(X, Y, obj.dat, Xq, Yq); +else + obj_out.dat = interp2(X, Y, obj.dat, Xq, Yq, intp_meth); +end + +% obj_out.dat(:,(sum(isnan(obj_out.dat)) ~= 0)) = []; + +obj = obj_out; + +end \ No newline at end of file diff --git a/@image_vector/sagg_slice_movie.m b/@image_vector/sagg_slice_movie.m new file mode 100644 index 00000000..3356e800 --- /dev/null +++ b/@image_vector/sagg_slice_movie.m @@ -0,0 +1,138 @@ +function sagg_slice_movie(dat, varargin) +% sagg_slice_movie(dat, [full_path_of_movie_output_file,image_skip_interval]) +% +% Movie of successive differences (sagittal slice) +% Enter an image_vector or fmri_data object (usually with time series) +% +% Optional inputs: +% +% movie_output_file: a char array detailing the full path to save the +% movie file +% +% image_skip_interval: a integer value describing the interval +% between images in each subsequent frame of the movie +% (default = 1) +% +% +% Example: sagg_slice_movie(fmri_dat, ... +% '/Volumes/engram/labdata/fmri_data/Study1/Subj1/qc_images', 5) +% +% This would save an movie based on the images in fmri_dat to the +% above directory, with an interval of 5 images between each +% frame (so, the movie would show image 1, 6, 11, 16, etc) +% +% Edited 8/7/14 by Scott +% - added skip interval +% - updated help + + +% 8/7/14 Programmer Note: if more varargin options are desired in the +% future, the function call will likely need to be re-written. The +% current form exists for backwards compatibility - obviously changing +% the function call will mean that other functions that use this +% (preproc) will need to be modified + +% ------------------------------------------------------ +writetofile = 0; +image_interval = 1; + +%deal out varargin +i = 1; +while i <= length(varargin) + if ischar(varargin{i}) + movieoutfile = varargin{i}; + writetofile = 1; + elseif isnumeric(varargin{i}) + image_interval = varargin{i}; + end + i=i+1; +end + +sdlim = 3.5; + +mm = mean(dat); +sdiffs = diff(dat.dat')'; +sdiffs = [mean(sdiffs, 2) sdiffs]; % keep in image order +mysd = std(sdiffs(:)); +mylim = [mean(sdiffs(:)) - sdlim*mysd mean(sdiffs(:)) + sdlim*mysd]; + +%mymean = mean(dat.dat); % global mean +spatsd = std(sdiffs); % variation in successive diffs - like rmssd but without abs mean diff. artifacts in some parts of image... +rmssd = ( mean(sdiffs .^ 2) ) .^ .5; % rmssd - root mean square successive diffs + +% avoid first time point being very different and influencing distribution and plots. +spatsd(1) = mean(spatsd); +rmssd(1) = mean(rmssd); + +corrcoef(spatsd, rmssd) + +slow1 = [abs(rmssd) > mean(rmssd) + sdlim*std(rmssd)]; +slow2 = [abs(spatsd) > mean(spatsd) + sdlim*std(spatsd)]; +slow = slow1 | slow2; + +fh = create_figure('succ_diffs', 2, 1); +ax1 = subplot(2, 1, 1); +title('Root mean square successive diffs'); + +ax2 = subplot(2, 1, 2); +set(ax1, 'Position', [.13 .75 .77 .2]); + +ax3 = axes('Position', [.13 .48 .77 .2]); +set(ax3, 'FontSize', 16); + +axes(ax3); +title('STD of successive diffs'); +hold on; +axes(ax2); + +vdat = reconstruct_image(mm); +wh = round(size(vdat, 1)./2); + +plot(ax1, rmssd, 'k'); axis tight +axes(ax1), hold on; +hh = plot_horizontal_line(mean(rmssd) + sdlim*std(rmssd)); +set(hh, 'LineStyle', '--'); + +plot(ax3, spatsd, 'k'); axis tight +axes(ax3), hold on; +hh = plot_horizontal_line(mean(spatsd) + sdlim*std(spatsd)); +set(hh, 'LineStyle', '--'); + +axes(ax2); +imagesc(squeeze(vdat(wh, :, :))', mylim); +drawnow +hold off +colormap gray + +for i = 1:image_interval:size(sdiffs, 2) + + vh = plot(ax1, i, rmssd(i), 'ro', 'MarkerFaceColor', 'r'); + vh2 = plot(ax3, i, spatsd(i), 'ro', 'MarkerFaceColor', 'r'); + + mm.dat = sdiffs(:, i); + vdat = reconstruct_image(mm); + imagesc(squeeze(vdat(wh, :, :))', mylim); + axis image; + set(ax2, 'YDir', 'Normal') + xlabel('Successive differences (sagittal slice)'); + + drawnow + + if writetofile + F = getframe(fh); + if i == 1 + imwrite(F.cdata, movieoutfile,'tiff', 'Description', dat.fullpath, 'Resolution', 30); + else + imwrite(F.cdata, movieoutfile,'tiff', 'WriteMode', 'append', 'Resolution', 30); + end + + elseif slow(i) + pause(1); + end + + delete(vh); + delete(vh2); +end + +end + diff --git a/@image_vector/searchlight.m b/@image_vector/searchlight.m new file mode 100644 index 00000000..cbb95305 --- /dev/null +++ b/@image_vector/searchlight.m @@ -0,0 +1,643 @@ +function [results_obj, stats, indx] = searchlight(dat, varargin) +% Run searchlight multivariate prediction/classification on an image_vector +% or fmri_data object OR two objects, for cross-prediction. +% +% Usage: +% ------------------------------------------------------------------------- +% [list outputs here] = function_name(list inputs here, [optional inputs]) +% [results_obj, indx] = searchlight(dat, [optional inputs]) +% +% Features: +% - Runs searchlight with standard, pre-defined algorithms +% - Custom-entry definition of holdout sets +% - Can re-use searchlight spheres after initial definition +% - Custom-entry definition of any spheres/regions of interest +% - Uses Matlab's parallel processing toolbox (parfor) +% +% Type help image_vector.searchlight to display this help information +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Tor Wager and... +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% dat image_vector or fmri_data object with data +% dat.Y required: true outcomes for each observation (image) in dat +% +% Optional: Keyword followed by input variable: +% 'r' searchlight radius, voxels +% 'dat2' second dataset, for cross-prediction +% 'indx' sparse logical matrix. each COLUMN is index of inclusion sets for each region/sphere in searchlight +% This takes a long time to calculate, but can be saved and +% re-used for a given mask +% +% Outputs: +% ------------------------------------------------------------------------- +% results_obj fmri_data object with results maps +% stats selected statistics for each sphere in searchlight +% indx sparse logical matrix. each COLUMN is index of inclusion sets for each region/sphere in searchlight +% * this can be re-used for all data with the same +% mask/structure. * +% +% Examples: +% ------------------------------------------------------------------------- +% % Define a sensible gray-matter mask: +% % ------------------------------------- +% dat = fmri_data(which('scalped_avg152T1_graymatter.img')); +% dat = threshold(dat, [.8 Inf], 'raw-between'); +% dat = trim_mask(dat); +% +% % Create fake data and holdout indicator index vector +% % ------------------------------------- +% dat.dat = randn(dat.volInfo.n_inmask, 30); +% dat.Y = dat.dat(111111, :)' + .3 * randn(30, 1); +% holdout_set = ones(6, 1); for i = 2:5, holdout_set = [holdout_set; i*ones(6, 1)]; end +% +% % Run, and run again with existing indx +% % ------------------------------------- +% pool = parpool(12); % initialize parallel processing (12 cores) +% [results_obj, indx] = searchlight(dat, 'holdout_set', holdout_set); +% results_obj = searchlight(dat, 'holdout_set', holdout_set, 'indx', indx); +% +% See also: +% region.m, fmri_data.predict.m + +% Programmers' notes: +% List dates and changes here, and author of changes + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% For defining regions +r = 5; +indx = []; % cell; with logical index of inclusion sets for each region/sphere in searchlight + +% For prediction/classification +algorithm_name = 'cv_lassopcr'; +holdout_set = []; +results_obj = []; + +% process variable arguments +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'r', 'indx', 'algorithm_name', 'dat2', 'holdout_set'} + str = [varargin{i} ' = varargin{i + 1};']; + eval(str) + varargin{i} = []; + varargin{i + 1} = []; + + case {'cross_predict'} + algorithm_name = varargin{i}; + + + otherwise + % use all other inputs as optional arguments to specific + % functions, to be interpreted by them + predfun_inputs{end + 1} = varargin{i}; + + if (i+1) <= length(varargin) && ~ischar(varargin{i + 1}) + predfun_inputs{end + 1} = varargin{i + 1}; + end + + end + end +end + +n = dat.volInfo.n_inmask; + +%% Set up indices for spherical searchlight, if not entered previously + +if isempty(indx) + + indx = searchlight_sphere_prep(dat, r); + +else + + fprintf('Using input indx to define regions/spheres...\n'); + +end + + +%% Check for data and return if empty + +if ~isa(dat, 'fmri_data') || isempty(dat.Y) + + fprintf('Returning indx only: No data in dat.Y to predict or data is not an fmri_data object'); + + return + +end + + +%% Run cross-predict (or other) function + +% Get rough time estimate first +predict_time_estimate(dat, indx, algorithm_name, holdout_set); + +t = tic; +fprintf('Running prediction in each region...'); + +% parfor i = 1:n +% +% indxcell{i} = indx(:, i); +% +% end + +output_val = cell(1, n); + +parfor i = 1:n + + output_val{i} = predict_wrapper(dat, indx(:, i), algorithm_name, holdout_set); + +end + +e = toc(t); + +[hour, minute, second] = sec2hms(e); +fprintf(1,'Done in %3.0f hours %3.0f min %2.0f sec\n',hour, minute, second); + + +%% Save results map(s) in object + +results_obj = mean(dat); +results_obj.dat = cat(1, output_val.cverr{:}); + + +end % function + + + +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- +% +% Sub-functions +% +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- + + +% SPHERE PREP +% ------------------------------------------------------------------------- + +function indx = searchlight_sphere_prep(dat, r) + +n = dat.volInfo.n_inmask; +indx = cell(1, n); + +t = tic; +fprintf('Setting up seeds...'); + +parfor i = 1:n + seed{i} = dat.volInfo.xyzlist(i, :); +end + +e = toc(t); +fprintf('Done in %3.2f sec\n', e); + +% Set up indices for spherical searchlight +% ------------------------------------------------------------------------- +% These could be indices for ROIs, user input, previously saved indices... + +% First, a rough time estimate: +% ------------------------------------------------------------------------- +fprintf('Searchlight sphere construction can take 20 mins or more! (est: 20 mins with 8 processors/gray matter mask)\n'); +fprintf('It can be re-used once created for multiple analyses with the same region definitions\n'); +fprintf('Getting a rough time estimate for how long this will take...\n'); + +n_to_run = min(500, n); +t = tic; +parfor i = 1:n_to_run + + mydist = sum([dat.volInfo.xyzlist(:, 1) - seed{i}(1) dat.volInfo.xyzlist(:, 2) - seed{i}(2) dat.volInfo.xyzlist(:, 3) - seed{i}(3)] .^ 2, 2); + indx{i} = mydist <= r.^2; + +end +e = toc(t); +estim = e * n / n_to_run; + +[hour, minute, second] = sec2hms(estim); +fprintf(1,'\nEstimate for whole brain = %3.0f hours %3.0f min %2.0f sec\n',hour, minute, second); + +% Second, do it for all voxels/spheres: +% ------------------------------------------------------------------------- + +t = tic; + +fprintf('Constructing spheres for each seed...'); + +parfor i = 1:n + + mydist = sum([dat.volInfo.xyzlist(:, 1) - seed{i}(1) dat.volInfo.xyzlist(:, 2) - seed{i}(2) dat.volInfo.xyzlist(:, 3) - seed{i}(3)] .^ 2, 2); + indx{i} = mydist <= r.^2; + +end + +e = toc(t); +fprintf('Done in %3.2f sec\n', e); + +% Make sparse matrix +t = tic; +fprintf('Sparsifying region indices...'); + +indxcat = cat(2, indx{:}); +indxcat = sparse(indxcat); + +e = toc(t); +fprintf('Done in %3.2f sec\n', e); + + +% NOTE: IdenTICAL but twice as slow +% indx1 = cell(1, n); +% tic +% parfor i = 1:1000 +% indx1{i} = logical(iimg_xyz2spheres(seed{i}, dat.volInfo.xyzlist, r)); +% end +% toc + + +end % searchlight_sphere_prep + + + + +% PREDICTION +% ------------------------------------------------------------------------- + +function output_val = predict_wrapper(dat, indx, algorithm_name, holdout_set) +% This function wraps predict.m to use its functionality. +% It could be extended to handle optional inputs, etc. +% Right now it is basic. +% Note: pass in indx{i}, which becomes indx here, for ONE +% region/searchlight sphere + +dat.dat = dat.dat(indx, :); +dat.removed_voxels(~indx) = true; + +[cverr, stats] = predict(dat, 'algorithm_name', algorithm_name, 'nfolds', holdout_set, 'useparallel', 0, 'verbose', 0); + +% Parse output +% Only storing important info and full data weight map - we will probably +% want xval weight maps too at some point. We will also probably want a +% p-value and standard errors for accuracy too. + +output_val = struct; +switch algorithm_name + case 'cv_svm' + output_val.cverr = stats.cverr; + output_val.dist_from_hyperplane_xval = stats.dist_from_hyperplane_xval; + output_val.weight_obj = stats.weight_obj; + output_val.intercept = stats.other_output{3}; + + case 'cv_lassopcr' + output_val.yfit = stats.yfit; + output_val.pred_outcome_r = stats.pred_outcome_r; + output_val.weight_obj = stats.weight_obj; + output_val.intercept = stats.other_output{3}; +end +end % subfunction + + +function predict_time_estimate(dat, indx, algorithm_name, holdout_set) + +t = tic; +fprintf('Getting rough time estimate: Running prediction for up to 500 voxels...'); + +n = dat.volInfo.n_inmask; +output_val = cell(1, n); + +n_to_run = min(500, n); + +parfor i = 1:n_to_run + + output_val{i} = predict_wrapper(dat, indx(:, i), algorithm_name, holdout_set); + +end + +e = toc(t); +fprintf('Done in %3.2f sec\n', e); + +estim = e * n / n_to_run; + +[hour, minute, second] = sec2hms(estim); +fprintf(1,'Estimate for whole brain = %3.0f hours %3.0f min %2.0f sec\n',hour, minute, second); + +end + + +function [hour, minute, second] = sec2hms(sec) +%SEC2HMS Convert seconds to hours, minutes and seconds. +% +% [HOUR, MINUTE, SECOND] = SEC2HMS(SEC) converts the number of seconds in +% SEC into hours, minutes and seconds. + +hour = fix(sec/3600); % get number of hours +sec = sec - 3600*hour; % remove the hours +minute = fix(sec/60); % get number of minutes +sec = sec - 60*minute; % remove the minutes +second = sec; +end + +% % WANI'S CROSS-CLASSIFICATION FUNCTION - to edit and integrate +% % ------------------------------------------------------------------------- +% +% function [acc, p, se, stats1 stats2] = cv_svm_cross_subf(data1, data2, cv_assign, dobalanced, balanced_ridge, doscale) +% +% % run cv_svm on train data and test data +% +% if doscale +% data1 = rescale(data1, 'zscoreimages'); +% data2 = rescale(data2, 'zscoreimages'); +% end +% +% % preallocate some variables +% predicted11 = NaN(size(data1.Y)); +% predicted12 = NaN(size(data2.Y)); +% predicted21 = NaN(size(data1.Y)); +% predicted22 = NaN(size(data2.Y)); +% +% dist_from_hyper11 = NaN(size(data1.Y)); +% dist_from_hyper12 = NaN(size(data2.Y)); +% dist_from_hyper21 = NaN(size(data1.Y)); +% dist_from_hyper22 = NaN(size(data2.Y)); +% +% % get cv assignment +% u = unique(cv_assign); +% nfolds = length(u); +% [trIdx, teIdx] = deal(cell(1, nfolds)); +% +% for i = 1:length(u) +% teIdx{i} = cv_assign == u(i); +% trIdx{i} = ~teIdx{i}; +% end +% +% stats1.Y = data1.Y; +% stats2.Y = data2.Y; +% +% % Use all +% trainobj1 = data(double(data1.dat'),data1.Y); +% trainobj2 = data(double(data2.dat'),data2.Y); +% +% svmobj1 = svm({'optimizer="andre"','C=1','child=kernel'}); +% if dobalanced, svmobj1.balanced_ridge = balanced_ridge; end +% [~, svmobj1] = train(svmobj1, trainobj1); +% +% svmobj2 = svm({'optimizer="andre"','C=1','child=kernel'}); +% if dobalanced, svmobj2.balanced_ridge = balanced_ridge; end +% [~, svmobj2] = train(svmobj2, trainobj2); +% +% % 1) weights +% stats1.all{1} = get_w(svmobj1)'; +% stats2.all{1} = get_w(svmobj2)'; +% % 2) intercepts +% stats1.all{2} = svmobj1.b0; +% stats2.all{2} = svmobj2.b0; +% +% stats1.all_descrip = {'1:weights 2:intercept 3:distance from hyperplane for test data1 4: dist for data2'}; +% stats2.all_descrip = {'1:weights 2:intercept 3:distance from hyperplane for test data2 4: dist for data1'}; +% +% % Cross-val loop starts here +% +% for i = 1:numel(teIdx) +% +% % Prepare the hot-warm and Rej-friend data in Spider format +% trainobj1 = data(double(data1.dat'),data1.Y); +% trainobj2 = data(double(data2.dat'),data2.Y); +% +% % Select training and test data - trainobj, testobj +% testobj1 = trainobj1; +% testobj2 = trainobj2; +% +% trainobj1.X = trainobj1.X(trIdx{i}, :); +% trainobj1.Y = trainobj1.Y(trIdx{i}, :); +% +% trainobj2.X = trainobj2.X(trIdx{i}, :); +% trainobj2.Y = trainobj2.Y(trIdx{i}, :); +% +% testobj1.X = testobj1.X(teIdx{i}, :); +% testobj1.Y = testobj1.Y(teIdx{i}, :); +% +% testobj2.X = testobj2.X(teIdx{i}, :); +% testobj2.Y = testobj2.Y(teIdx{i}, :); +% +% +% %------------------------------------------------------------------- +% % Train on data1 and test on data1 and data2 +% +% % Set up an SVM object +% svmobj1 = svm({'optimizer="andre"','C=1','child=kernel'}); +% +% if dobalanced +% svmobj1.balanced_ridge = balanced_ridge; +% end +% +% % train on hw data set +% [~, svmobj1] = train(svmobj1, trainobj1); +% +% % Test on both HW and RF test set +% +% testobj11 = test(svmobj1, testobj1); +% testobj12 = test(svmobj1, testobj2); +% +% w = get_w(svmobj1)'; +% b0 = svmobj1.b0; +% +% dist11 = testobj1.X * w + b0; +% dist12 = testobj2.X * w + b0; +% +% if ~isequal((dist11>0)*2-1,testobj11.X) +% error('something is wrong'); +% elseif ~isequal((dist12>0)*2-1,testobj12.X) +% error('something is wrong'); +% end +% +% % Save predictions (cross-val) +% predicted11(teIdx{i}) = testobj11.X; +% predicted12(teIdx{i}) = testobj12.X; +% +% +% %------------------------------------------------------------------- +% % Train on RF and test on HW and RF +% +% % Set up an SVM object +% svmobj2 = svm({'optimizer="andre"','C=1','child=kernel'}); +% +% if dobalanced +% svmobj2.balanced_ridge = balanced_ridge; +% end +% % train on rf data set +% [~, svmobj2] = train(svmobj2, trainobj2); +% +% % Test on both HW and RF test set +% +% testobj21 = test(svmobj2, testobj1); +% testobj22 = test(svmobj2, testobj2); +% +% w = get_w(svmobj2)'; +% b0 = svmobj2.b0; +% +% dist21 = testobj1.X * w + b0; +% dist22 = testobj2.X * w + b0; +% +% if ~isequal((dist21>0)*2-1,testobj21.X) +% error('something is wrong'); +% elseif ~isequal((dist22>0)*2-1,testobj22.X) +% error('something is wrong'); +% end +% +% % Save predictions (cross-val) +% predicted21(teIdx{i}) = testobj21.X; +% predicted22(teIdx{i}) = testobj22.X; +% +% % get stats from svmobj +% % 1) weights +% stats1.cvoutput{i,1} = get_w(svmobj1)'; +% stats2.cvoutput{i,1} = get_w(svmobj2)'; +% % 2) intercepts +% stats1.cvoutput{i,2} = svmobj1.b0; +% stats2.cvoutput{i,2} = svmobj2.b0; +% % 3) distance from hyperplane for test data 1 and 2 +% stats1.cvoutput{i,3} = testobj1.X * stats1.cvoutput{i,1} + stats1.cvoutput{i,2}; +% stats1.cvoutput{i,4} = testobj2.X * stats1.cvoutput{i,1} + stats1.cvoutput{i,2}; +% +% stats2.cvoutput{i,3} = testobj1.X * stats2.cvoutput{i,1} + stats2.cvoutput{i,2}; +% stats2.cvoutput{i,4} = testobj2.X * stats2.cvoutput{i,1} + stats2.cvoutput{i,2}; +% +% stats1.cvoutput_descrip = {'1:weights 2:intercept 3:distance from hyperplane for test data1 4: dist for data2'}; +% stats2.cvoutput_descrip = {'1:weights 2:intercept 3:distance from hyperplane for test data2 4: dist for data1'}; +% +% dist_from_hyper11(teIdx{i}) = stats1.cvoutput{i,3}; +% dist_from_hyper12(teIdx{i}) = stats1.cvoutput{i,4}; +% dist_from_hyper21(teIdx{i}) = stats2.cvoutput{i,3}; +% dist_from_hyper22(teIdx{i}) = stats2.cvoutput{i,4}; +% end +% +% stats1.yfit = predicted11; +% stats2.yfit = predicted22; +% stats1.testfit = predicted12; +% stats2.testfit = predicted21; +% +% stats1.all{3} = dist_from_hyper11; +% stats1.all{4} = dist_from_hyper12; +% stats2.all{3} = dist_from_hyper22; +% stats2.all{4} = dist_from_hyper21; +% +% % RO on H vs. W +% % res_temp = binotest(predicted11(1:(length(predicted11)/2)) == +% %data1.Y(1:(length(predicted11)/2)), 0.5); +% % test_results.accuracy(1,1) = res_temp.prop; +% % test_results.accuracy_se(1,1) = res_temp.SE; +% % test_results.accuracy_p(1,1) = res_temp.p_val; +% % +% % res_temp = binotest(predicted12(1:(length(predicted12)/2)) == +% %data2.Y(1:(length(predicted12)/2)), 0.5); +% % test_results.accuracy(1,2) = res_temp.prop; +% % test_results.accuracy_se(1,2) = res_temp.SE; +% % test_results.accuracy_p(1,2) = res_temp.p_val; +% % +% % res_temp = binotest(predicted21(1:(length(predicted21)/2)) == +% %data1.Y(1:(length(predicted21)/2)), 0.5); +% % test_results.accuracy(1,3) = res_temp.prop; +% % test_results.accuracy_se(1,3) = res_temp.SE; +% % test_results.accuracy_p(1,3) = res_temp.p_val; +% % +% % res_temp = binotest(predicted22(1:(length(predicted22)/2)) == +% %data2.Y(1:(length(predicted22)/2)), 0.5); +% % test_results.accuracy(1,4) = res_temp.prop; +% % test_results.accuracy_se(1,4) = res_temp.SE; +% % test_results.accuracy_p(1,4) = res_temp.p_val; +% +% subjn = numel(teIdx); +% outcome = [true(subjn,1); false(subjn,1)]; +% +% hvsw_dist11 = stats1.all{3}(1:(subjn*2)); +% rvsf_dist12 = stats1.all{4}(1:(subjn*2)); +% hvsw_dist21 = stats2.all{4}(1:(subjn*2)); +% rvsf_dist22 = stats2.all{3}(1:(subjn*2)); +% +% +% % ROC = roc_plot(hvsw_dist11, outcome, 'threshold', 0); +% % test_results.accuracy_thresh0(1) = ROC.accuracy; +% % test_results.accuracy_thresh0_se(1) = ROC.accuracy_se; +% % test_results.accuracy_thresh0_p(1) = ROC.accuracy_p; +% +% try +% ROC = roc_plot(hvsw_dist11, outcome, 'twochoice'); +% acc(1) = ROC.accuracy; +% se(1) = ROC.accuracy_se; +% p(1) = ROC.accuracy_p; +% catch err +% % ROC = roc_plot(hvsw_dist11, outcome, 'twochoice'); +% acc(1) = NaN; +% se(1) = NaN; +% p(1) = NaN; +% end +% +% % +% % ROC = roc_plot_wani(rvsf_dist12, outcome, 'threshold', 0); +% % test_results.accuracy_thresh0(2) = ROC.accuracy; +% % test_results.accuracy_thresh0_se(2) = ROC.accuracy_se; +% % test_results.accuracy_thresh0_p(2) = ROC.accuracy_p; +% % +% try +% ROC = roc_plot(rvsf_dist12, outcome, 'twochoice'); +% acc(2) = ROC.accuracy; +% se(2) = ROC.accuracy_se; +% p(2) = ROC.accuracy_p; +% catch err +% acc(2) = NaN; +% se(2) = NaN; +% p(2) = NaN; +% end +% % +% % +% % ROC = roc_plot(hvsw_dist21, outcome, 'threshold', 0); +% % test_results.accuracy_thresh0(3) = ROC.accuracy; +% % test_results.accuracy_thresh0_se(3) = ROC.accuracy_se; +% % test_results.accuracy_thresh0_p(3) = ROC.accuracy_p; +% % +% try +% ROC = roc_plot(hvsw_dist21, outcome, 'twochoice'); +% acc(3) = ROC.accuracy; +% se(3) = ROC.accuracy_se; +% p(3) = ROC.accuracy_p; +% catch err +% acc(3) = NaN; +% se(3) = NaN; +% p(3) = NaN; +% end +% +% % ROC = roc_plot(rvsf_dist22, outcome, 'threshold', 0); +% % test_results.accuracy_thresh0(4) = ROC.accuracy; +% % test_results.accuracy_thresh0_se(4) = ROC.accuracy_se; +% % test_results.accuracy_thresh0_p(4) = ROC.accuracy_p; +% try +% ROC = roc_plot(rvsf_dist22, outcome, 'twochoice'); +% acc(4) = ROC.accuracy; +% se(4) = ROC.accuracy_se; +% p(4) = ROC.accuracy_p; +% catch err +% acc(4) = NaN; +% se(4) = NaN; +% p(4) = NaN; +% end +% +% close; + +% end \ No newline at end of file diff --git a/@image_vector/slices.m b/@image_vector/slices.m new file mode 100644 index 00000000..6086dcf1 --- /dev/null +++ b/@image_vector/slices.m @@ -0,0 +1,136 @@ +function o = slices(obj, varargin) +% Create a montage of single-slice results for every image in an +% image_vector object +% +% +% o = slices(obj, 'orientation', [orientation], 'slice', [slice_mm], 'nimages', [nimgs]) +% +% obj is an image_vector, fmri_data, or statistic_image object with +% multiple images (only the first 64 will display), which are stored as +% columns in its .dat field. +% +% Optional inputs: +% 'orientation' can be followed by 'saggital', 'axial', or 'coronal' +% 'slice_mm' is followed by the mm coord of the slice to display; default = 0 +% 'nimgs' can be followed by the number of images to display, 1:nimgs +% 'names' is followed by a cell array of names for the images. +% 'color' is followed by color vector or string specification. default is +% color-mapped with split colors (hot/cool) for pos and neg effects. +% 'outline' is followed by a color vector for outline around blobs. +% +% The output, o, is an fmridisplay object. +% +% This function uses fmridisplay objects, and may be memory-intensive for +% older computers. +% +% Common Errors: +% -------------------------------------------------------------- +% This function uses the volInfo.cluster field. If you create a mask in an +% ad hoc way, this field may not be updated. use this to fix: +% mask = reparse_contiguous(mask); +% +% Examples: +% -------------------------------------------------------------- +% slices(dat); +% slices(dat, 'orientation', 'axial'); +% slices(dat, 'slice', -5); % display sagg at x = -5 +% o = slices(dat, 'names', terms); % use 'terms' var as names +% +% o2 = slices(all_chi2_images, 'orientation', 'saggital', 'slice', 0); +% +% Copyright 2011, Tor Wager + +slice_mm = 0; +my_orientation = 'saggital'; +nimgs = size(obj.dat, 2); +dosplitcolor = 1; +outlinecolor = []; +for i = 1:nimgs, names{i, 1} = sprintf('Img %3.0f', i); end + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'orientation', my_orientation = varargin{i+1}; varargin{i+1} = []; + case 'slice', slice_mm = varargin{i+1}; + case 'nimages', nimgs = varargin{i+1}; + case 'names', names = varargin{i+1}; + + case 'color', dosplitcolor = 0; color = varargin{i + 1}; + case 'outline', outlinecolor = varargin{i + 1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +numax = [ceil(sqrt(nimgs)) floor(sqrt(nimgs))]; +if prod(numax) < nimgs, numax(2) = numax(2) + 1; end + +if nimgs > 64, disp('Displaying only first 64 images!'); end + +m = mean(obj); + +o = fmridisplay; +f1 = create_figure('slice_montage', numax(1), numax(2)); +colormap gray + +for i = 1:prod(numax) + newaxhan(i) = subplot(numax(1), numax(2), i); + axis off; +end + + + +% For each slice +for i = 1:nimgs + + %o = removeblobs(o); + o = montage(o, my_orientation, 'slice_range', [slice_mm slice_mm]); + enlarge_axes(gcf, .8) + + % add blobs + m.dat = obj.dat(:, i); + + if isa(obj, 'statistic_image') + m.dat(~obj.sig(:, i)) = 0; + end + + m = reparse_contiguous(m); + + cl = region(m); + + % cluster_orthviews(cl, {[1 0 0]}, 'solid'); + % %spm_orthviews_name_axis(names{i}, i); + % spm_orthviews('Position', [0 0 0]); + % s = input('Press a key'); + % + if dosplitcolor + o = addblobs(o, cl, 'splitcolor', {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}, 'wh_montages', i); + else + o = addblobs(o, cl, 'color', color, 'wh_montages', i); + end + + if ~isempty(outlinecolor) + o = addblobs(o, cl, 'color', outlinecolor, 'wh_montages', i, 'outline'); + end + + title(names{i}, 'FontSize', 24) + + % copy to composite figure + newhan(i) = copyobj(o.montage{i}.axis_handles(1), f1); + + o.montage{i}.axis_handles = newhan(i); + + % close the original + close + + newpos = get(newaxhan(i), 'Position'); + set(newhan(i), 'Position', newpos); + drawnow + +end + + +end % function \ No newline at end of file diff --git a/@image_vector/surface.m b/@image_vector/surface.m new file mode 100644 index 00000000..bb917792 --- /dev/null +++ b/@image_vector/surface.m @@ -0,0 +1,22 @@ +function [all_surf_handles, pcl, ncl] = surface(obj) +% [all_surf_handles, pcl, ncl] = surface(obj) +% +% Examples: +% ------------------------------------------------------------------------ +% % create an initial surface plot from an fmri_data object: +% han = surface(regionmasks{2}); +% +% Now add a second region in green: +% cluster_surf(region(regionmasks{2}), {[0 1 0]}, han, 5); + +if size(obj.dat, 2) > 1 + obj = mean(obj); +end + +r = region(obj); + + +[all_surf_handles, pcl, ncl] = surface(r); + +end + diff --git a/@image_vector/threshold.m b/@image_vector/threshold.m new file mode 100644 index 00000000..893fe4f1 --- /dev/null +++ b/@image_vector/threshold.m @@ -0,0 +1,90 @@ +function obj = threshold(obj, input_threshold, thresh_type, varargin) +% +% Threshold image_vector (or fmri_data or fmri_obj_image) object based on +% raw threshold values. For statistical thresholding, convert to a +% statistic_image object and see the threshold method for that object. +% +% Examples: +% -> Retain positive values, cluster extent > 100 voxels +% obj = threshold(obj, [0 Inf], 'raw-between', 'k', 100) +% +% -> Retain voxels with absolute value > 3 +% obj = threshold(obj, [-3 3], 'raw-outside') + +% Inputs +% ------------------------------------------------------- +k = 1; +dotrim = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'k' + k = varargin{i + 1}; + + if isempty(obj.volInfo) + error('You must add a volInfo structure to the statistic image object to do extent-based thresholding'); + end + + case 'trim_mask' + dotrim = 1; + + otherwise + error('Illegal argument keyword.'); + end + end +end + +switch lower(thresh_type) + + case {'raw-between', 'raw-outside'} + thresh = input_threshold; + + otherwise + error('Unknown threshold type. Enter raw-between or raw-outside') +end + +% ------------------------------------------------------- +obj = replace_empty(obj); + +switch lower(thresh_type) + + case 'raw-between' + wh = obj.dat > thresh(1) & obj.dat < thresh(2); + + fprintf('Keeping vals between %3.3f and %3.3f: %3.0f elements remain\n', thresh(1), thresh(2), sum(wh(:))); + + case 'raw-outside' + wh = obj.dat < thresh(1) | obj.dat > thresh(2); + + fprintf('Keeping vals outside of %3.3f to %3.3f: %3.0f elements remain\n', thresh(1), thresh(2), sum(wh(:))); + +end + +obj.dat(~wh) = 0; + + +% Apply size threshold +% -------------------------------------- +if k > 1 + fprintf('Applying cluster extent threshold\n'); + for i = 1:size(obj.dat, 2) + + whkeep = logical(iimg_cluster_extent(double(obj.dat(:, i) ~= 0), obj.volInfo, k)); + obj.dat(~whkeep, i) = 0; + + end +end + +obj = remove_empty(obj); + +% Clean up and trim +% -------------------------------------- + +if dotrim + obj = trim_mask(obj); +end + + +end % function + diff --git a/@image_vector/trim_mask.m b/@image_vector/trim_mask.m new file mode 100644 index 00000000..a6ee52e8 --- /dev/null +++ b/@image_vector/trim_mask.m @@ -0,0 +1,33 @@ +function obj = trim_mask(obj) +% Exclude empty voxels from mask information in obj.volInfo structure, and re-make obj.volInfo +% +% obj = trim_mask(obj) +% +% Tor Wager, 2013 + +obj = replace_empty(obj); +whomit = all(obj.dat == 0 | isnan(obj.dat), 2); + +obj.dat(whomit, :) = []; +if ~isempty(obj.removed_voxels) && length(obj.removed_voxels) == obj.volInfo.n_inmask + obj.removed_voxels(whomit) = []; +end +obj.volInfo.image_indx(obj.volInfo.wh_inmask(whomit)) = 0; +obj.volInfo.wh_inmask(whomit) = []; +obj.volInfo.xyzlist(whomit, :) = []; +obj.volInfo.cluster(whomit) = []; +obj.volInfo.n_inmask = length(obj.volInfo.wh_inmask); + +if isa(obj, 'fmri_data') + obj.mask.dat(whomit, :) = []; + if ~isempty(obj.mask.removed_voxels) && length(obj.mask.removed_voxels) == obj.mask.volInfo.n_inmask + obj.mask.removed_voxels(whomit) = []; + end + obj.mask.volInfo.image_indx(obj.mask.volInfo.wh_inmask(whomit)) = 0; + obj.mask.volInfo.wh_inmask(whomit) = []; + obj.mask.volInfo.xyzlist(whomit, :) = []; + obj.mask.volInfo.cluster(whomit) = []; + obj.mask.volInfo.n_inmask = length(obj.mask.volInfo.wh_inmask); +end + +end % function diff --git a/@image_vector/union.m b/@image_vector/union.m new file mode 100644 index 00000000..9087cbce --- /dev/null +++ b/@image_vector/union.m @@ -0,0 +1,110 @@ +function [dat, dati] = union(dat1, dat2, outputname) +% Union and intersection masks for two image_vector objects +% +% [dat_union, dat_intersection] = union(dat1, dat2, outputname) +% +% dat = union(dat1, dat2, outputname) +% % outputname = character array name for union image +% INCLUDE .img at the end. +% +% NOTE: must now be in same space! +% +% tor + +isdiff = compare_space(dat1, dat2); +if isdiff == 1 || isdiff == 2 + error('Spaces are not the same!') +end + + +vdat1 = reconstruct_image(dat1); +vdat2 = reconstruct_image(dat2); + +sz = size(vdat1); + +vdat1 = vdat1(:); +vdat2 = vdat2(:); + +both = vdat1 | vdat2; +oneandtwo = vdat1 & vdat2; + +dat = dat1; +dat.volInfo.fname = 'REMOVED BY UNION'; +dat.volInfo.descrip = 'UNION mask'; +dat.volInfo.image_indx = both; +dat.volInfo.nvox = length(both); +dat.volInfo.wh_inmask = find(both); +dat.volInfo.n_inmask = sum(both); + +[x, y, z] = ind2sub(sz, 1:length(vdat1)); +xyz = [x' y' z']; +dat.volInfo.xyzlist = xyz(both, :); + +if dat.volInfo(1).n_inmask < 50000 + dat.volInfo(1).cluster = spm_clusters(dat.volInfo(1).xyzlist')'; +else + dat.volInfo(1).cluster = ones(dat.volInfo(1).n_inmask, 1); +end + +if isa(dat1, 'statistic_image') + + [p, ste, sig] = deal(zeros(size(both))); + p(dat1.volInfo.wh_inmask, :) = dat1.p(:, 1); + ste(dat1.volInfo.wh_inmask, :) = dat1.ste(:, 1); + sig(dat1.volInfo.wh_inmask, :) = dat1.sig(:, 1); + +end + +if isa(dat2, 'statistic_image') + p(:, 2) = 0; + ste(:, 2) = 0; + sig(:, 2) = 0; + p(dat1.volInfo.wh_inmask, 2) = dat2.p(:, 1); + ste(dat1.volInfo.wh_inmask, 2) = dat2.ste(:, 1); + sig(dat1.volInfo.wh_inmask, 2) = dat2.sig(:, 1); + +end + +if isa(dat1, 'statistic_image') || isa(dat2, 'statistic_image') + p = p(both, :); + ste = ste(both, :); + sig = sig(both, :); + +end + +% could average here... +dat.dat = single(both(both)); + +if isa(dat1, 'statistic_image') + + dat.p = min(p, [], 2); + dat.ste = min(ste, [], 2); + dat.sig = max(sig, [], 2); +end + + +% intersection +dati = dat; +dati.volInfo.fname = 'REMOVED BY UNION'; +dati.volInfo.descrip = 'INTERSECTION mask'; + +dati.dat = single(oneandtwo(both)); + +if isa(dat1, 'statistic_image') + + dati.p = max(p, [], 2); + dati.ste = max(ste, [], 2); + dati.sig = min(sig, [], 2); +end + +if nargin > 2 + + dat.fullpath = fullfile(pwd, outputname); + write(dat) + +end + + +end + + diff --git a/@image_vector/write.m b/@image_vector/write.m new file mode 100644 index 00000000..7b17ec44 --- /dev/null +++ b/@image_vector/write.m @@ -0,0 +1,73 @@ +function write(obj, varargin) +% +% Write an image_vector object to an Analyze image. +% Option to write thresholded image, for statistic_image objects. +% +% obj.dat should contain data, with one COLUMN for each 3-D frame in the +% 4-D image to be written. +% +% Usage: +% write(obj) -> writes to the image(s) specified in obj.fullpath +% write(obj, 'thresh') -> for statistic_image objects, writes thresholded +% write(obj, 'fname', '~/Documents/test.nii') -> writes the image(s) to specific path +% +% For example: +% If m is an image_vector object, +% +% m.X(m.X < .12) = 0; % apply an arbitrary but reasonable custom threshold +% orthviews(m); +% write the thresholded image to disk: +% anatmeanname = 'mean_gray_matter_mask.img'; +% m.filename = anatmeanname; +% m.fullpath = fullfile(maskdir, anatmeanname); +% write(m) +% +% Option: +% 'mni' resample image to standard MNI FOV (91x109x91) +% uses mri_data.resample_space +% 'keepdt' output image will be keep original data type (default = float32) +% 'fname' writes out image to specific file name. 'fname' must be +% followed by image name with path + +% +% 2013/3/5: Luk[ea] added 'mni' option +% 2013/3/25: +% Luke[ea] added optional input to retain original datatype +% 2014/3/14: Luke added 'fname' option to specify filename + +if any(strcmp(varargin, 'fname')) % fname option -- added by Luke + obj.fullpath = varargin{find(strcmp(varargin, 'fname')) + 1}; %check if this works. +elseif isempty(obj.fullpath) + error('Define fullpath field with name and path before writing an image_vector object to disk'); +end + +% Replace empty vox/images +obj = replace_empty(obj); + +if any(strcmp(varargin, 'thresh')) + if ~isa(obj, 'statistic_image') + disp('Warning: Thresholding only works with statistic_image objects.'); + else + disp('Writing thresholded statistic image.'); + obj.dat(~obj.sig) = 0; + end +end + +% mni option -- added Luk[ea] +if any(strcmp(varargin, 'mni')) + evalc('mni = fmri_data(which(''brainmask.nii''));'); % evalc() used to silence output of fmri_data + obj = resample_space(obj,mni); +end + +% Keep original dt option -- added Luk[ea] +if any(strcmp(varargin,'keepdt')) + iimg_reconstruct_vols(obj.dat, obj.volInfo, 'outname', obj.fullpath, 'keepdt'); +else + iimg_reconstruct_vols(obj.dat, obj.volInfo, 'outname', obj.fullpath); +end + + +fprintf('Writing: \n%s\n', obj.fullpath); + + + diff --git a/@region/check_extracted_data.m b/@region/check_extracted_data.m new file mode 100644 index 00000000..e4be8410 --- /dev/null +++ b/@region/check_extracted_data.m @@ -0,0 +1,39 @@ +function isok = check_extracted_data(cl) +% isok = check_extracted_data(cl) +% +% cl must be a valid region object (see region.m) +% and cl(1).source_images must still be on the path. +% +% +% Checks the data, just in case of space/programming issues, +% by re-extracting the region average data from 5 random regions +% using spm_get_data.m, and compares it to the already-saved values +% +% You should not need to run this regularly -- but you should if you +% suspect things have gone awry. + +isok = 1; + +% Check that source images exist +imgs = check_valid_imagename(cl(1).source_images, 1); + +wh_cl = randperm(length(cl)); + +for cl_num = wh_cl(1:5) + +% Extract data from orig voxels +% --------------------------------- +Y = spm_get_data(imgs, cl(cl_num).XYZ); + +avgdat = [nanmean(Y')' cl(cl_num).dat]; + +rr = corrcoef(avgdat); + +isok = rr(1, 2) > .999; + +okstr = {'No' 'Yes'}; +fprintf('Checked data in region %3.0f, ok = %s\n', cl_num, okstr{1 + (rr(1, 2) > .999)}); + +end + +end % function diff --git a/@region/merge.m b/@region/merge.m new file mode 100644 index 00000000..f2b66dd0 --- /dev/null +++ b/@region/merge.m @@ -0,0 +1,76 @@ +function cl = merge(cl, wh_merge) +% +% Merge two or more regions together in a region object. +% Combines fields from all clusters in the named series with the first one +% in the series. +% +% wh_merge = [3 4]; +% cl = merge(cl, wh_merge) +% +% Tor Wager, April 2011 + +N = fieldnames(cl(wh_merge(1))); % add stuff to the first one in the list + +for j = 2:length(wh_merge) + + for i = 1:length(N) + + try + + switch N{i} + + % Update as strings by concatenating + case {'title' 'shorttitle' 'descrip1' 'descrip2' 'custom_info1' 'custom_info2' 'custom_info1_descrip' 'custom_info2_descrip'} + + cl(wh_merge(1)).(N{i}) = [cl(wh_merge(1)).(N{i}) ' MERGED WITH ' cl(wh_merge(j)).(N{i})]; + + % Merge by concatenating vertically + + + % Merge by concatenating horizontally + case {'XYZ' 'XYZmm' 'Z' 'threshold' 'all_data'} + cl(wh_merge(1)).(N{i}) = [cl(wh_merge(1)).(N{i}) cl(wh_merge(j)).(N{i})]; + + % Merge by weighted average of columns/data + case {'timeseries' 'contrastdata' 'dat'} + + mysum = cl(wh_merge(1)).numVox .* cl(wh_merge(1)).(N{i}) + cl(wh_merge(j)).numVox .* cl(wh_merge(j)).(N{i}); + mysum = mysum ./ (cl(wh_merge(1)).numVox + cl(wh_merge(j)).numVox); + + cl(wh_merge(1)).(N{i}) = mysum; + + % Don't update these now + case {'val_descrip' 'numpeaks' 'center' 'mm_center' 'source_images'} + + + % Check for compatibility + case {'M' 'dim' 'voxSize'} + + mycomp = cl(wh_merge(1)).(N{i}) - cl(wh_merge(j)).(N{i}); + + if any(mycomp(:)) + fprintf('WARNING! Field %s does not match for regions %3.0f and %3.0f. Combined values may be meaningless.\n', N{i}, 1, j); + end + + end % case + + catch + fprintf('WARNING! Field %s has sizes that do not match for regions %3.0f and %3.0f. Not combining this field.\n', N{i}, 1, j); + + end + + end % field i + + + % Update center, mm_center + cl(wh_merge(1)).center = nanmean(cl(wh_merge(1)).XYZ, 2)'; + cl(wh_merge(1)).mm_center = nanmean(cl(wh_merge(1)).XYZmm, 2)'; + +end % cluster j + +cl(wh_merge(2:end)) = []; + +end % function + + + diff --git a/@region/montage.m b/@region/montage.m new file mode 100644 index 00000000..c6b761b3 --- /dev/null +++ b/@region/montage.m @@ -0,0 +1,4 @@ + + function montage(obj, varargin) + montage_clusters([], obj, varargin{:}) + end \ No newline at end of file diff --git a/@region/orthviews.m b/@region/orthviews.m new file mode 100644 index 00000000..311e3f18 --- /dev/null +++ b/@region/orthviews.m @@ -0,0 +1,5 @@ +function orthviews(obj, varargin) + +cluster_orthviews(obj, varargin{:}); + +end \ No newline at end of file diff --git a/@region/posneg_separate.m b/@region/posneg_separate.m new file mode 100644 index 00000000..bb6e65e2 --- /dev/null +++ b/@region/posneg_separate.m @@ -0,0 +1,104 @@ +function [pcl, ncl] = posneg_separate(cl, varargin) +% Separate a region object (cl) into clusters with positive and negative +% peak values, based on max (peak) value in .val or .Z field (default = +% val) +% +% [pcl, ncl] = posneg_separate(cl, ['Z']) +% +% Returns pcl and ncl, region structures with positive- and negative-valued +% peaks, respectively, copied from the original cl input. +% +% Optional input: 'Z', to use .Z field +% +% Note: You may have to use reparse_continguous to get this to work right. +% r = reparse_continguous(r); +% [pcl, ncl] = posneg_separate(r); + +reparseflag = 0; % need to reparse if mixed clusters +myfield = 'val'; + +if any(strcmp(varargin, 'Z')) + myfield = 'Z'; +end + +pindx = 1; +nindx = 1; +pcl = []; +ncl = []; + +for i = 1:length(cl) + + vals = cl(i).(myfield); + + [dummy, wh] = max(abs(vals)); + + + sv = sign(vals); +whp = sv > 0; +whn = sv < 0; + + if all(whp) + % positive values + if isempty(pcl) + pcl = cl(i); + else + pcl(pindx) = cl(i); + end + pindx = pindx + 1; + + elseif all(whn) + % negative values + if isempty(ncl) + ncl = cl(i); + else + ncl(nindx) = cl(i); + end + nindx = nindx + 1; + + else % mixed. remove the appropriate values from each. + + reparseflag = 1; + + if isempty(pcl) + pcl = cl(i); + else + pcl(pindx) = cl(i); + end + + if isempty(ncl) + ncl = cl(i); + else + ncl(nindx) = cl(i); + end + + whrem = whn; + + if ~isempty(pcl(pindx).XYZ), pcl(pindx).XYZ(:, whrem) = []; end + if ~isempty(pcl(pindx).XYZmm), pcl(pindx).XYZmm(:, whrem) = []; end + if ~isempty(pcl(pindx).val), pcl(pindx).val(whrem, :) = []; end + if ~isempty(pcl(pindx).Z), pcl(pindx).Z(whrem) = []; end + if ~isempty(pcl(pindx).all_data), pcl(pindx).all_data(:, whrem) = []; end + + whrem = whp; + + if ~isempty(ncl(nindx).XYZ), ncl(nindx).XYZ(:, whrem) = []; end + if ~isempty(ncl(nindx).XYZmm), ncl(nindx).XYZmm(:, whrem) = []; end + if ~isempty(ncl(nindx).val), ncl(nindx).val(whrem, :) = []; end + if ~isempty(ncl(nindx).Z), ncl(nindx).Z(whrem) = []; end + if ~isempty(ncl(nindx).all_data), ncl(nindx).all_data(:, whrem) = []; end + + + pindx = pindx + 1; + nindx = nindx + 1; + end + +end + +if reparseflag + + pcl = reparse_continguous(pcl); + ncl = reparse_continguous(ncl); + +end + +end diff --git a/@region/region.m b/@region/region.m new file mode 100644 index 00000000..b90a27ab --- /dev/null +++ b/@region/region.m @@ -0,0 +1,354 @@ +% 'region' is a class of objects that contains information on groups of +% voxels ("regions") defined in various ways (contiguous voxels above a +% threshold in an analysis, based on atlases, etc.) +% +% the region class replaces the "clusters" structure in the scnlab toolbox, +% and all or almost all of the "clusters" functions should work for regions +% structures as well. The advantage of 'region' is that the class +% definition can help constrain the use and provide additional error +% checking, etc. +% +% Defining a region and initializing: +% ============================================ +% Usage: +% r = region(obj1, [obj2], [keywords] ) +% obj1: [fmri_data/statistic_image object to define regions] +% Can also be char array of image filename +% obj2: Optional: fmri_data/statistic_image object to extract data from +% keywords: Optional: 'unique_mask_values' or 'contiguous_regions' +% +% cl = region; % generates an empty structure +% % You can add fields yourself if you want to, but best to +% define based on existing file name or image_vector object +% +% Define regions based on continuous voxel values (in this example image, +% there is only one set of contiguous voxels, so 1 region...) +% +% mask_image = which('brainmask.nii'); +% cl = region(mask_image); +% +% INPUTS +% -------------------------------------------- +% There are two ways to define which voxels are grouped into a 'region', +% which becomes an element of the region variable cl. +% enter 'contiguous_regions' -> group by contiguous blobs +% or 'unique_mask_values' -> group by unique values in mask .dat field +% +% Note: 'contiguous_regions' uses contiguity/clustering information stored +% in mask.volInfo.cluster, which may not have veridical contiguity info if +% you have manipulated it or incorporated anatomical information in working with +% the mask object, or if you have borrowed the volInfo structure from +% another source in creating it. +% +% Examples +% -------------------------------------------- +% Define regions based on continuous values in an anatomical mask: +% +% mask_image = which('atlas_labels_combined.img'); +% cl = region(mask_image, 'unique_mask_values'); +% +% Resample mask_image to space of data_comb fmri_data object, and extract +% averages. Space is defined by data_comb. +% cl = extract_roi_averages(data_comb, mask_image, 'unique_mask_values'); +% +% Define regions based on unique voxels values in mask_image, and extract +% data stored in data_comb object, resampled to space of mask_image. +% Space is defined by mask_image: +% cl = region(mask_image, data_comb, 'unique_mask_values'); +% +% Reslice mask to space of functional images and define regions based on +% mask values in the functional space (good for extracting data, etc.) +% +% mask_image = which('anat_lbpa_thal.img'); +% mask = fmri_mask_image(mask_image); +% mask = resample_to_image_space(mask, image_names(1, :)); +% cl = region(mask); +% +% +% +% Methods +% ============================================ +% Try typing methods(cl) +% +% methods for 'regions' include: +% Visualization methods: +% montage, orthviews, surf, etc. +% +% + +classdef region + + properties + title = 'Untitled'; + shorttitle = 'region'; + descrip1 = 'Region: a group of voxels.' + descrip2 ='Some methods: orthviews, montage, surface, extract_data, table'; + + XYZ + XYZmm + val + val_descrip = 'Description of values for each voxel in val field.'; + + Z + Z_descrip = 'Legacy values for each voxel; Z-scores or other max stat'; + + threshold % legacy, for compatibility + voxSize + M + dim + numVox + numpeaks + + center + mm_center + + timeseries + contrastdata + dat + all_data + + source_images + + custom_info1 + custom_info1_descrip + + custom_info2 + custom_info2_descrip + + end % properties + + methods + + % class constructor method + % takes maskinput in one of several forms: + % 1) mask object + % 2) mask image file + % 3) mask vector data (???) + % + % takes one of two string inputs for how to define regions (see below) + % 'contiguous_regions' (default) + % 'unique_mask_values' + + function obj = region(maskinput, varargin) + + % initialize empty + % return if no additional args + + obj.title = 'Untitled'; + obj.shorttitle = 'region'; + obj.descrip1 = 'Region: a group of voxels.'; + obj.descrip2 ='Some methods: orthviews, montage, surface, extract_data, table'; + obj.XYZ = []; + obj.XYZmm = []; + obj.val = []; + obj.Z = []; + obj.val_descrip = 'Values for each voxel, usually max stat.'; + obj.Z_descrip = 'Legacy values for each voxel; Z-scores or other max stat'; + obj.threshold = []; % legacy, for compatibility + obj.voxSize = []; + obj.M = []; + obj.dim = []; + obj.numVox = 0; + obj.numpeaks = NaN; + obj.center = []; + obj.mm_center = []; + obj.timeseries = []; + obj.contrastdata = []; + obj.dat = []; + obj.all_data = []; + obj.source_images = char([]); + obj.custom_info1 = []; + obj.custom_info1_descrip = 'Mask image name for region definition'; + obj.custom_info2 = []; + obj.custom_info2_descrip = char([]); + + if nargin == 0 + return + end + + % --------------------------------- + % define mask object + % --------------------------------- + + if isa(maskinput, 'char') % string file name + mask = fmri_mask_image(maskinput); + + elseif isa(maskinput, 'image_vector') + % case {'fmri_data', 'fmri_mask_image', 'statistic_image', 'image_vector'} + + % special for thresholded stats images: use threshold + + % if sig field exists, use sig voxels only + if isa(maskinput, 'statistic_image') && ~isempty(maskinput.sig) + + for img = 1:size(maskinput.dat, 2) + maskinput.dat(~maskinput.sig(:, img), img) = 0; + end + + end + + mask = maskinput; + + else + error('region class constructor: unknown mask input type.') + end + clear maskinput + + % --------------------------------- + % define what to average over + % --------------------------------- + + average_over = 'contiguous_regions'; %'contiguous_regions' or 'unique_mask_values'; + + for varg = 1:length(varargin) + if ischar(varargin{varg}) + switch varargin{varg} + + % reserved keywords + case 'contiguous_regions', average_over = 'contiguous_regions'; + case 'unique_mask_values', average_over = 'unique_mask_values'; + + otherwise + disp('region class constructor: Illegal string value for average_over.'); + fprintf('You entered ''%s''\n Valid values are %s or %s\n', varargin{varg}, '''contiguous_regions''', '''unique_mask_values'''); + error('Exiting'); + end + else + if isa(varargin{varg}, 'image_vector') + % data object to extract from at end + + dataobj = varargin{varg}; + varargin{varg} = []; + + % Note: If you have manipulated an image_vector (e.g., fmri_data, + % statistic_image) object and eliminated some voxels, in order to create + % regions, contiguous voxels are automatically reparsed into regions using + % fmri_data.reparse_contiguous + + dataobj = reparse_contiguous(dataobj, 'nonempty'); + end + end + end + + % Mask data can already be reduced to those indexed by + % wh_inmask or not. + % In addition, voxels/images with empty values may be removed. + % So insert those first. + % Note: .dat can sometimes have 2+ cols, so use only first one + mask = replace_empty(mask); + + if size(mask.dat, 1) == mask.volInfo.n_inmask + maskData = mask.dat(:, 1); + + elseif size(mask.dat, 1) == mask.volInfo.nvox + % We have a full-length vector + maskData = mask.dat(mask.volInfo.wh_inmask(:, 1)); + else + error('Illegal size for mask.dat, because it does not match its volInfo structure.') + end + + % If extracting data, we'll recreate the regions in + % extract_roi_averages, so do that here and then exit. + % Otherwise, continue to region definition. + + if exist('dataobj', 'var') + % extract data + disp('> Found image data, extracting region averages.'); + + dataobj = replace_empty(dataobj); % may need to do this to get voxels to line up + + cs = compare_space(dataobj, mask); + if cs == 3 + error('Spaces for data object and mask object line up, but voxel numbers do not. Check.'); + elseif cs + disp('> Resampling to mask space first.'); + dataobj = resample_space(dataobj, mask); % resample data to mask space + end + + obj = extract_roi_averages(dataobj, mask, average_over); + return + end + + % --------------------------------- + % get unique values for voxel grouping code + % --------------------------------- + maskValues = maskData; % values to save + + switch average_over + + % Define integer codes for sets of voxels to average over. + + case 'unique_mask_values' + + maskData = round(maskData); + u = unique(maskData)'; u(u == 0) = []; + nregions = length(u); + fprintf('Grouping voxels with unique mask values, assuming integer-valued mask: %3.0f regions\n', nregions); + + + case 'contiguous_regions' + + isinmask = maskData ~= 0 & ~isnan(maskData); + + % re-make cluster ID for in-mask voxels + if sum(isinmask) < 50000 + mask.volInfo.cluster(isinmask) = spm_clusters(mask.volInfo.xyzlist(isinmask, :)')'; + else + % don't, and print warning + mask.volInfo.cluster(isinmask) = ones(sum(isinmask), 1); + disp('Warning: spm_cluster will not parse clusters for masks with > 50000 voxels.'); + end + + u = unique(mask.volInfo.cluster(isinmask)); u(u == 0) = []; + maskData(isinmask) = mask.volInfo.cluster(isinmask); + nregions = length(u); + fprintf('Grouping contiguous voxels: %3.0f regions\n', nregions); + + otherwise + error('This should never happen.'); + end + + % --------------------------------- + % Now define the regions + % --------------------------------- + + obj(1:nregions) = obj; % fill in all empty fields + + for i = 1:nregions + imgvec = maskData == u(i); + + obj(i).title = sprintf('Region %3.0f', i); + obj(i).shorttitle = sprintf('Region%03d', i); + obj(i).XYZ = mask.volInfo.xyzlist(imgvec,:)'; + obj(i).XYZmm = voxel2mm(obj(i).XYZ,mask.volInfo.mat); + + obj(i).val = maskValues(imgvec); %ones(1,size(obj(i).XYZ,2)); + obj(i).Z = maskValues(imgvec)'; %ones(1,size(obj(i).XYZ,2)); + + obj(i).voxSize = abs(diag(mask.volInfo.mat(1:3, 1:3))); + obj(i).M = mask.volInfo.mat; + obj(i).dim = mask.volInfo.dim; + obj(i).numVox = size(obj(i).XYZ, 2); + + obj(i).center = center_of_mass(obj(i).XYZ, obj(i).Z); + obj(i).mm_center = center_of_mass(obj(i).XYZmm, obj(i).Z); + + [dd, ff, ee] = fileparts(mask.volInfo.fname); + obj(i).custom_info1 = [ff ee]; + obj(i).custom_info1_descrip = 'Mask image name for region definition'; + + end + + if nregions == 0 + % obligatory things even for empty regions + obj(1).voxSize = abs(diag(mask.volInfo.mat(1:3, 1:3))); + obj(1).M = mask.volInfo.mat; + obj(1).dim = mask.volInfo.dim; + end + + end % class constructor function + + end % methods + + +end \ No newline at end of file diff --git a/@region/region2imagevec.m b/@region/region2imagevec.m new file mode 100644 index 00000000..d7e853cc --- /dev/null +++ b/@region/region2imagevec.m @@ -0,0 +1,67 @@ +function [ivecobj, orig_cluster_indx] = region2imagevec(cl) +% [ivecobj, orig_cluster_indx] = region2imagevec(cl) +% +% Convert a region object to an image_vector object, replacing the voxels +% and reconstructing as much info as possible. +% +% The .dat field of the new "ivecobj" is made from the cl.all_data field. +% if this is empty, uses cl.val field, then cl.Z as a backup. +% Mask information is available in ivecobj.volInfo. +% +% ivecobj = region2imagevec(cl) +% NEEDS SOME ADDITIONAL WORK/CHECKING + +ivecobj = image_vector; +ivecobj.volInfo.mat = cl(1).M; +ivecobj.volInfo.dim = cl(1).dim; + +% no, will be reordered +%ivecobj.volInfo.xyzlist = cat(2, cl.XYZ)'; + +mask = clusters2mask2011(cl, cl(1).dim); + +n = sum(mask(:) ~= 0); + +% add data. all_data if we have it, or .val or .Z +ivecobj.dat = cat(2, cl.all_data)'; +if isempty(ivecobj.dat) || all(ivecobj.dat == 0), ivecobj.dat = cat(1, cl.val); end +if isempty(ivecobj.dat) || all(ivecobj.dat == 0), ivecobj.dat = cat(2, cl.Z)'; end + +% tor changed april 28 2011 to be all voxels +ivecobj.removed_voxels = mask(:) == 0 | isnan(mask(:)); %false(n, 1); + +ivecobj.volInfo.image_indx = mask(:) ~= 0; +ivecobj.volInfo.n_inmask = n; + +ivecobj.volInfo.wh_inmask = find(ivecobj.volInfo.image_indx); + +% re-get continguity; don't just assume +orig_cluster_indx = mask(:); +orig_cluster_indx = orig_cluster_indx(ivecobj.volInfo.wh_inmask); + +%ivecobj.volInfo.cluster = mask(:); +%ivecobj.volInfo.cluster = ivecobj.volInfo.cluster(ivecobj.volInfo.wh_inmask); + +ivecobj.volInfo.nvox = prod(cl(1).dim); + +[i, j, k] = ind2sub(cl(1).dim, ivecobj.volInfo.wh_inmask); +ivecobj.volInfo.xyzlist = [i j k]; + +if ivecobj.volInfo.n_inmask < 50000 + ivecobj.volInfo.cluster = spm_clusters(ivecobj.volInfo.xyzlist')'; +else + ivecobj.volInfo.cluster = ones(ivecobj.volInfo.n_inmask, 1); +end + +ivecobj.volInfo.dt = [16 0]; % for reslicing compatibility +ivecobj.volInfo.fname = 'Reconstructed from clusters'; +% +% ivecobj.dat = cat(2, cl.all_data)'; +% ivecobj.removed_voxels = mask(:) == 0; +% ivecobj.volInfo.image_indx = true(size(ivecobj.removed_voxels)); +% ivecobj.volInfo.n_inmask = length(ivecobj.removed_voxels); +% ivecobj.volInfo.wh_inmask = [1:ivecobj.volInfo.n_inmask ]'; +% ivecobj.volInfo.cluster = mask(:); +% ivecobj.volInfo.nvox = prod(size(cl(1).dim)); + +end diff --git a/@region/region2struct.m b/@region/region2struct.m new file mode 100644 index 00000000..5ced27ca --- /dev/null +++ b/@region/region2struct.m @@ -0,0 +1,18 @@ +function cl = region2struct(cl) +% cl = region2struct(cl) +% +% Convert a region object to a simple structure, primarily for +% compatibility with other, older CANlab tools. +% +% see also cluster2region, for the reverse transformation + +warning off +for i = 1:length(cl) + cl2(i) = struct(cl(i)); +end +warning on + +cl = cl2; + +end + diff --git a/@region/reparse_continguous.m b/@region/reparse_continguous.m new file mode 100644 index 00000000..908d977c --- /dev/null +++ b/@region/reparse_continguous.m @@ -0,0 +1,39 @@ +function clout = reparse_continguous(cl) +% Re-define regions in region object based on contiguous blobs +% +% clout = reparse_continguous(cl) +% NEEDS SOME ADDITIONAL WORK/CHECKING + +ivec = region2imagevec(cl); + +clout = region(ivec, 'contiguous_regions'); + +% ---------------------------------------------- +% Add other fields +% ---------------------------------------------- + +clindx = ivec.volInfo.cluster; % wh are in new clusters +nvox = length(clindx); + +dat = cat(2, cl.all_data); + +if size(dat, 2) == nvox + + % matching field; get dat for new clout + for j = 1:length(clout) + clout(j).all_data = dat(:, clindx == j); + + clout(j).dat = nanmean(clout(j).all_data', 1)'; + end + +elseif isempty(dat) + % do nothing +else + disp('Warning! all_dat field is wrong size.'); + +end + +end + + + diff --git a/@region/subdivide_by_atlas.m b/@region/subdivide_by_atlas.m new file mode 100644 index 00000000..97af9523 --- /dev/null +++ b/@region/subdivide_by_atlas.m @@ -0,0 +1,83 @@ +function r = subdivide_by_atlas(r, varargin) +% r = subdivide_by_atlas(r, [atlas name]) +% +% Inputs: +% r = a region object, defined using region(mask) +% atlas name: Optional mask image with integer codes defining in-mask +% regions. Default is 'atlas_labels_combined.img' +% +% Output: A region object with separate clusters for each contiguous blob, +% subdivided by regions labeled in atlas. +% +% Example: +% r = subdivide_by_atlas(r); +% r(cat(1, r.numVox) < 20) = []; % get rid of small regions +% cluster_orthviews(r, 'unique'); + +if nargin < 2 || isempty(varargin{1}) +atlasname = which('atlas_labels_combined.img'); +else + atlasname = varargin{1}; +end + +if ~exist(atlasname, 'file') + if exist(which(atlasname), 'file') + atlasname = which(atlasname); + else + fprintf('Cannot find atlas image on path! Looking for:%s\n', atlasname); + end +end + + +%r = region(overlapmask); % r is input + +% if is region, reconstruct... +[ivec, orig_indx] = region2imagevec(r); + +label_mask = fmri_data(atlasname); + +% resample and mask label image +label_mask = resample_space(label_mask, ivec, 'nearest'); + +ulabels = unique(label_mask.dat); + +fprintf('%3.0f unique atlas labels in mask (label = 0 will be excluded).\n', length(ulabels)); + +% Define regions based on unique voxels values in mask_image, and extract +% data stored in data_comb object, resampled to space of mask_image. +% Space is defined by mask_image: +for i = 1:length(r) + %ivec = region2imagevec(r(i)); + %ivec = remove_empty(ivec); + + whvox = orig_indx == i; + nvox = sum(whvox); + + if nvox == 1 + rr{i} = r(i); % just copy - nothing to subdivide + else + mylabel = label_mask; + mylabel.dat(orig_indx ~= i, 1) = 0; % remove these - not in contiguous region + + if ~any(mylabel.dat) + % no labels - exclude + rr{i} = []; + else + rr{i} = region(mylabel, ivec, 'unique_mask_values'); + end + + end + +end + +for i = 1:length(rr) +wh_omit(1, i) = isempty(rr{i}); +end + +fprintf('%3.0f original regions do not have any labeled voxels.\n', sum(wh_omit)); + +r = cat(2, rr{:}); + + +end % function + diff --git a/@region/surface.m b/@region/surface.m new file mode 100644 index 00000000..e3621ad3 --- /dev/null +++ b/@region/surface.m @@ -0,0 +1,217 @@ +function [all_surf_handles, pcl, ncl] = surface(r, varargin) +% Surface method for region object - renders blobs on multiple types of 3-D surface +% +% Usage: +% ------------------------------------------------------------------------- +% [all_surf_handles, pcl, ncl] = surface(r, ['cutaways', any optional inputs to surface_cutaway]) +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% r A region object +% 'cutaway' String command for rendering cutaways instead of the default +% - default is call to mediation_brain_surface_figs +% - cutaways calls surface_cutaway +% - all optional arguments are passed to surface_cutaway +% 'rightsurface' String command for rendering a right frontal cortical +% view complementary to 'cutaways' +% +% 'foursurfaces' Compact plots of four surfaces +% +% Other optional inputs to surface_cutaway +% e.g., 'pos_colormap' +% +% Outputs: +% ------------------------------------------------------------------------- +% all_surf_handles surface patch handles +% pcl region object with positive-only clusters +% ncl region object with negative-only clusters +% +% Examples: +% ------------------------------------------------------------------------- +% Use surface(r), with optional arguments taken by surface_cutaway: +% poscm = colormap_tor([1 .3 0], [1 1 0]); % orange to yellow +% [all_surf_handles, pcl, ncl] = surface(r, 'cutaway', 'ycut_mm', -30, 'pos_colormap', poscm, 'existingfig'); +% +% See also: +% surface_cutaway, cluster_surf, mediation_brain_surface_figs + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- +% default options + +% optional inputs +allowable_args = {'cutaway' 'rightsurface' 'existingfig', 'foursurfaces'}; +default_values = {0, 0, 0, 0}; + +% actions for inputs can be: 'assign_next_input' or 'flag_on' +actions = {'flag_on', 'flag_on', 'flag_on', 'flag_on'}; + +% logical vector and indices of which inputs are text +textargs = cellfun(@ischar, varargin); +whtextargs = find(textargs); + +for i = 1:length(allowable_args) + + % assign default + % ------------------------------------------------------------------------- + + eval([allowable_args{i} ' = default_values{i};']); + + wh = strcmp(allowable_args{i}, varargin(textargs)); + + if any(wh) + % Optional argument has been entered + % ------------------------------------------------------------------------- + + wh = whtextargs(wh); + if length(wh) > 1, warning(['input ' allowable_args{i} ' is duplicated.']); end + + switch actions{i} + case 'assign_next_input' + eval([allowable_args{i} ' = varargin{wh(1) + 1};']); + + case 'flag_on' + eval([allowable_args{i} ' = 1;']); + + otherwise + error(['Coding bug: Illegal action for argument ' allowable_args{i}]) + end + + end % argument is input +end + +% END DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +[pcl, ncl] = posneg_separate(r, 'Z'); + +if cutaway + all_surf_handles = surface_cutaway('cl', r, varargin{:}); +end + +if rightsurface + % could easily be extended to any methods for addbrain.m, but may be + % better to build your own and pass in custom handles. + + if ~existingfig, create_figure('right_surface'); end + + all_surf_handles = addbrain('hires'); + view(133, 10); + set(all_surf_handles, 'FaceAlpha', .9); + axis tight, axis image + lightRestoreSingle; lighting gouraud + + all_surf_handles = surface_cutaway('cl', r, 'surface_handles', all_surf_handles, varargin{:}); + +end + +if foursurfaces + all_surf_handles = run_foursurfaces(r, existingfig, varargin{:}); +end + +if ~(cutaway || rightsurface || foursurfaces) + + % default method + all_surf_handles = mediation_brain_surface_figs({pcl}, {ncl}); +end + +% Or, could modify to use this as an option: +%cluster_surf(obj, varargin{:}) + +end + + + + + + +%% Subfunction + +function all_surf_handles = run_foursurfaces(r, existingfig, varargin) + +if ~existingfig, f1 = create_figure('foursurfaces'); else f1 = gcf; end + +all_surf_handles = []; + +nrows = 2; +ncols = 2; + +% Right lateral +% ------------------------------------------------------------------------ +figure(f1); +subplot(nrows, ncols , 1); +surfh = addbrain('hires right'); +set(surfh, 'FaceColor', [.5 .5 .5], 'FaceAlpha', 1); +view(90, 0) +lightRestoreSingle; axis image; axis off; lighting gouraud; material dull + +surfh2 = addbrain('brainstem'); +surfh2 = [surfh2 addbrain('thalamus')]; +set(surfh2, 'FaceColor', [.5 .5 .5], 'FaceAlpha', .8); +surfh = [surfh surfh2]; + +surfh = surface_cutaway('cl', r, 'surface_handles', surfh, varargin{:}); + +all_surf_handles = [all_surf_handles surfh]; + +% Right medial +% ------------------------------------------------------------------------ + +figure(f1); +axh = subplot(nrows, ncols , 4); +copyobj(surfh, axh); +view(270, 0); + +lightRestoreSingle; axis image; axis off; lighting gouraud; material dull + +% Left lateral +% ------------------------------------------------------------------------ + +figure(f1); +subplot(nrows, ncols , 2); +surfh = addbrain('hires left'); +set(surfh, 'FaceColor', [.5 .5 .5], 'FaceAlpha', 1); +view(270, 0) +lightRestoreSingle; axis image; axis off; lighting gouraud; material dull + +surfh2 = addbrain('brainstem'); +surfh2 = [surfh2 addbrain('thalamus')]; +set(surfh2, 'FaceColor', [.5 .5 .5], 'FaceAlpha', .8); +surfh = [surfh surfh2]; + +surfh = surface_cutaway('cl', r, 'surface_handles', surfh, varargin{:}); + +all_surf_handles = [all_surf_handles surfh]; + +% Left medial +% ------------------------------------------------------------------------ + +figure(f1); +axh = subplot(nrows, ncols , 3); +copyobj(surfh, axh); +view(90, 0); + +lightRestoreSingle; axis image; axis off; lighting gouraud; material dull + +lighting gouraud + +end + diff --git a/@region/table.m b/@region/table.m new file mode 100644 index 00000000..223d7df9 --- /dev/null +++ b/@region/table.m @@ -0,0 +1,79 @@ +function [poscl, negcl] = table(cl, varargin) +% Print a table of all regions in a region object (cl) +% +% [poscl, negcl] = table(cl, [optional inputs]) +% +% Optional inputs: +% 'k' : Print only regions with k or more contiguous voxels +% 'nosep' : do not separate cl with pos and neg effects based on peak in .val +% 'names' : name clusters before printing to table and output; saves in .shorttitle field +% 'forcenames' : force naming of cl by removing existing names in .shorttitle field +% +% Outputs: +% Returns region objects for cl with pos and neg effects, limited by size if entered +% and named if entered as optional input +% +% Copyright 2011, tor wager + +k = 0; +dosep = 1; +donames = 0; % name clusters before printing to table and output; saves in .shorttitle field +forcenames = 0; % force naming of cl by removing existing names in .shorttitle field + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case {'k', 'maxsize'}, k = varargin{i+1}; + case 'nosep', dosep = 0; + case {'names', 'name', 'donames'}, donames = 1; + case 'forcenames', forcenames = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +if k + cl(cat(1, cl.numVox) < k) = []; +end + +if donames + + if forcenames + for i = 1:length(cl) + cl(i).shorttitle = []; + end + end + + cl = cluster_names(cl); +end + +if dosep + % separate pos and neg + [poscl, negcl] = posneg_separate(cl); + fprintf('Positive Effects\n') +else + % just return cl in poscl + poscl = cl; + negcl = []; + fprintf('Table of all regions\n') +end + +if ~isempty(poscl) + cluster_table(poscl, 0, 0); +else + disp('No regions to display'); +end +fprintf('\nNegative Effects\n') + +if ~isempty(negcl) + cluster_table(negcl, 0, 0); +else + disp('No regions to display'); +end + +end + diff --git a/@statistic_image/convert2mask.m b/@statistic_image/convert2mask.m new file mode 100644 index 00000000..d030fd7b --- /dev/null +++ b/@statistic_image/convert2mask.m @@ -0,0 +1,51 @@ +function varargout = convert2mask(stats_image_obj) +% Converts each image in a statistic_image object into a mask object, based +% on significant voxels in the .sig field. +% +% [mask1, mask2, etc...] = convert2mask(stats_image_obj) +% +% Examples +% cl = region(convert2mask(timg), group) + +% enforce logical +stats_image_obj.sig = logical(stats_image_obj.sig); + +for i = 1:size(stats_image_obj.dat, 2) + % for each image + + sigi = stats_image_obj.sig(:, i); + + if isempty(sigi) + stats_image_obj.sig(:, i) = true(size(stats_image_obj.dat(:, i))); + end + + % Copy into a mask image + mask = fmri_mask_image; + + mask.volInfo = stats_image_obj.volInfo; + + mask.volInfo.image_indx(mask.volInfo.wh_inmask(~sigi)) = 0; + mask.volInfo.wh_inmask(~sigi) = []; + + mask.volInfo.xyzlist(~sigi, :) = []; + mask.volInfo.cluster(~sigi) = []; + + mask.volInfo.n_inmask = length(mask.volInfo.wh_inmask); + + mask.dat = zeros(mask.volInfo.nvox, 1, 'single'); + + mask.dat(stats_image_obj.volInfo.wh_inmask(sigi)) = 1; + + if ~isempty(stats_image_obj.image_names) + mask.image_names = stats_image_obj.image_names(i, :); + end + + mask.history = stats_image_obj.history; + + mask.history{end + 1} = 'Converted to mask_image object.'; + + varargout{i} = mask; + +end + +end % function \ No newline at end of file diff --git a/@statistic_image/multi_threshold.m b/@statistic_image/multi_threshold.m new file mode 100644 index 00000000..6acf3a85 --- /dev/null +++ b/@statistic_image/multi_threshold.m @@ -0,0 +1,209 @@ +function [o2, sig, pcl, ncl] = multi_threshold(dat, varargin) +% Multiple threshold function for statistic_image object for visualization +% +% Usage: +% ------------------------------------------------------------------------- +% [o2, sig, pcl, ncl] = multi_threshold(dat, [optional inputs]) +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% dat a statistic_image object +% +% Optional inputs: +% 'poscolors' followed by cell array of colors for positive values, one per thresh +% 'negcolors' followed by cell array of colors for negative values, one per thresh +% 'thresh' followed vector of p-value thresholds, one per thresh +% 'sizethresh' followed by vector of cluster sizes, one per thresh +% - this 'prunes' by default, so sizes after first can be 1 +% voxel +% 'nodisplay' suppress fmridisplay +% +% +% +% Outputs: +% ------------------------------------------------------------------------- +% o2 handle to fmridisplay object created by default +% sig vector of significant voxels at each thresh, for each region +% - cell array of images in object with matrix of values +% for each threshold +% pcl - positive valued clusters cell, one cell per threshold +% - pass inot mediation_brain_surface_figs.m +% ncl - positive valued clusters cell, one cell per threshold +% - pass inot mediation_brain_surface_figs.m +% +% Examples: +% ------------------------------------------------------------------------- +% +% [o2, sig, poscl, negcl] = multi_threshold(hr_intercept, 'nodisplay'); +% mediation_brain_surface_figs(poscl, negcl); +% +% See also: +% mediation_brain_surface_figs, iimg_multi_threshold, +% mediation_brain_results + +% Programmers' notes: +% List dates and changes here, and author of changes + + + +o2 = []; + +poscolors = {[1 1 0] [1 .5 0] [.7 0 0]}; +negcolors = {[0 0 1] [0 .5 1] [.4 0 .7]}; + +thresh = [.001 .005 .05]; +sizethresh = [10 1 1]; + +% optional inputs with default values +% ----------------------------------- +% - allowable_args is a cell array of argument names +% - avoid spaces, special characters, and names of existing functions +% - variables will be assigned based on these names +% i.e., if you use an arg named 'cl', a variable called cl will be +% created in the workspace + +allowable_args = {'poscolors', 'negcolors', 'thresh', 'sizethresh', ... + 'nodisplay', 'existingfig'}; + +default_values = {poscolors, negcolors, thresh, sizethresh, ... + 0, 0}; + +% define actions for each input +% ----------------------------------- +% - cell array with one cell for each allowable argument +% - these have special meanings in the code below +% - allowable actions for inputs in the code below are: 'assign_next_input' or 'flag_on' + +actions = {'assign_next_input', 'assign_next_input', 'assign_next_input', 'assign_next_input', ... + 'flag_on', 'flag_on'}; + +% logical vector and indices of which inputs are text +textargs = cellfun(@ischar, varargin); +whtextargs = find(textargs); + +for i = 1:length(allowable_args) + + % assign default + % ------------------------------------------------------------------------- + + eval([allowable_args{i} ' = default_values{i};']); + + wh = strcmp(allowable_args{i}, varargin(textargs)); + + if any(wh) + % Optional argument has been entered + % ------------------------------------------------------------------------- + + wh = whtextargs(wh); + if length(wh) > 1, warning(['input ' allowable_args{i} ' is duplicated.']); end + + switch actions{i} + case 'assign_next_input' + eval([allowable_args{i} ' = varargin{wh(1) + 1};']); + + case 'flag_on' + eval([allowable_args{i} ' = 1;']); + + otherwise + error(['Coding bug: Illegal action for argument ' allowable_args{i}]) + end + + end % argument is input +end + +% END DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + + + + +sig = {}; + +nimgs = size(dat.dat, 2); % <- number of 3-D images in dataset + +% get significant vectors sig, where sig is cell, one cell per image +for i = 1:length(thresh) + dat = threshold(dat, thresh(i), 'unc', 'k', sizethresh(i)); + dat = replace_empty(dat); + for j = 1:nimgs + sig{j}(:, i) = dat.sig(:, j); + end +end + +% prune +for j = 1:nimgs + for i = 2:length(thresh) + sig{j}(:, i) = iimg_cluster_prune(sig{j}(:, i), sig{j}(:, i-1), dat.volInfo); + end +end + + +% regions + +%r = cell(1, nimgs); +r = cell(1, length(thresh)); + +for i = 1:length(thresh) + for j = 1 %:nimgs % only does first in region anyway + dat.sig(:, j) = sig{j}(:, i); + r{i} = region(dat); + end + [pcl{i}, ncl{i}] = posneg_separate(reparse_continguous(r{i})); +end + +if nodisplay + return +end + + +% fmri display +o2 = fmridisplay; +xyz = [-20 -10 -6 -2 0 2 6 10 20]'; +xyz(:, 2:3) = 0; + +indx = 1; + +for j = 1:nimgs + + datplot = dat; + datplot.dat = dat.dat(:, j); + datplot.p = dat.p(:, j); + datplot.ste = dat.ste(:, j); + + o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); + axh = axes('Position', [0.05 0.4 .1 .5]); + o2 = montage(o2, 'saggital', 'wh_slice', [0 0 0], 'existing_axes', axh); + + for i = length(thresh):-1:1 + + datplot.sig = sig{j}(:, i); + + mycolors = [negcolors(i) negcolors(i) poscolors(i) poscolors(i)]; + + o2 = addblobs(o2, region(datplot), 'splitcolor', mycolors, 'wh_montages', indx); + o2 = addblobs(o2, region(datplot), 'splitcolor', mycolors, 'wh_montages', indx+1); + + end + + indx = indx + 2; + +end + +end % function diff --git a/@statistic_image/orthviews.m b/@statistic_image/orthviews.m new file mode 100644 index 00000000..19728026 --- /dev/null +++ b/@statistic_image/orthviews.m @@ -0,0 +1,121 @@ +function cl = orthviews(image_obj, varargin) +% +% --------------------------------------------------------------- +% Orthviews display (SPM) for CANlab object +% --------------------------------------------------------------- +% +% cl = orthviews(image_object) +% +% OR +% +% cl = orthviews(image_object, handle_number of existing orthviews) +% +% Output is clusters structure (see also region.m) +% +% e.g., +% T-test, Construct a stats_image object, threshold and display: +% statsimg = ttest(fmridat, .001, 'unc'); +% +% Re-threshold and display: +% statsimg = threshold(statsimg, .000001, 'unc'); +% orthviews(statsimg); +% +% statsimg = threshold(statsimg, .01, 'fdr'); +% orthviews(statsimg); +% +% Create an orthviews and view at multiple thresholds in different panes: +% overlay = which('SPM8_colin27T1_seg.img'); +% spm_check_registration(repmat(overlay, n, 1)); +% statsimg = ttest(fmridat); +% statsimg = threshold(statsimg, .001, 'unc'); +% orthviews(statsimg, 'handle', 1); +% +% statsimg = threshold(statsimg, .000001, 'unc'); +% orthviews(statsimg, 'handle', 2); + +input_handle = []; +cl = []; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case {'han', 'handle', 'input_handle'}, input_handle = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if isempty(input_handle) + % re-initialize new orthviews + + n = size(image_obj.dat, 2); + + overlay = which('SPM8_colin27T1_seg.img'); + + spm_check_registration(repmat(overlay, n, 1)); + + handle_indices = 1:n; +else + + % use existing + handle_indices = input_handle; + +end + +image_indx = 1; + +if isempty(image_obj.sig), image_obj.sig = true(size(image_obj.dat)); end + +% replace missing voxels if necessary +image_obj.sig = zeroinsert(image_obj.removed_voxels, image_obj.sig); +image_obj.dat = zeroinsert(image_obj.removed_voxels, image_obj.dat); + + +% do not do this, because it may replace deliberately removed imgs +% image_obj = replace_empty(image_obj); + + +for i = handle_indices + + if size(image_obj.sig, 2) < image_indx + disp('No sig vector; displaying all voxels') + end + + % use .sig field if we have it, otherwise use dat + if size(image_obj.sig, 2) >= image_indx && any(image_obj.sig(:, image_indx)) + cl{i} = iimg_indx2clusters(image_obj.dat(:, image_indx) .* double(image_obj.sig(:, image_indx)), image_obj.volInfo); + else + cl{i} = iimg_indx2clusters(image_obj.dat(:, image_indx), image_obj.volInfo); + %disp('No sig vector; displaying all voxels') + end + + if ~isempty(cl{i}) + if ~isempty(image_obj.image_names) && ischar(image_obj.image_names) + + if size(image_obj.image_names, 1) < image_indx + image_obj.image_names = char(image_obj.image_names, 'NO NAME'); + end + + fprintf('Displaying image %3.0f, %3.0f voxels: %s\n', i, sum(cat(1, cl{i}.numVox)), image_obj.image_names(image_indx, :)); + end + + cluster_orthviews(cl{i}, 'add', 'handle', i); + + elseif size(image_obj.image_names, 1) >= image_indx + fprintf('Image %3.0f empty: %s\n', i, image_obj.image_names(image_indx, :)); + else + fprintf('Image %3.0f empty\n', i); + end + + image_indx = image_indx + 1; +end + +spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 1 1], [.5 .5 .5], [1 .5 0]); + +spm_orthviews('Reposition', [0 0 0]); drawnow + +end % function + + diff --git a/@statistic_image/reparse_contiguous.m b/@statistic_image/reparse_contiguous.m new file mode 100644 index 00000000..34472840 --- /dev/null +++ b/@statistic_image/reparse_contiguous.m @@ -0,0 +1,69 @@ +function obj = reparse_contiguous(obj, varargin) +% obj = reparse_contiguous(obj, ['nonempty']) +% +% Re-construct list of contiguous voxels in an image based on in-image +% voxel coordinates. Coordinates are taken from obj.volInfo.xyzlist. +% Results are saved in obj.volInfo.cluster. +% xyzlist can be generated from iimg_read_img, and is done automatically by +% object-oriented fMRI image classes (fmri_image, image_vector, +% statistic_image) +% +% If 'nonempty' is entered as an optional argument, will use only voxels +% that are non-zero, non-nan in the first column of obj.dat. +% +% The statistic_image object version of reparse_contiguous uses +% the significance of the first image in the object (obj.sig(:, 1)) as a +% filter as well, so clustering will be based on the latest threshold applied. +% it is not usually necessary to enter 'nonempty'. +% +% Examples: +% ---------------------------------------- +% Given timg, a statistic_image object: +% test = reparse_contiguous(timg, 'nonempty'); +% cl = region(test, 'contiguous_regions'); +% cluster_orthviews(cl, 'unique') +% +% copyright tor wager, 2011 + +% Programmers' notes: +% 6/22/14: Tor changed behavior to use .sig field + +wh = true(size(obj.volInfo.cluster)); %obj.volInfo.wh_inmask; +obj.volInfo(1).cluster = zeros(size(wh)); + +obj = replace_empty(obj); + +% restrict to voxels with actual data if desired +if any(strcmp(varargin, 'nonempty')) + + wh = all(obj.dat ~= 0, 2) & all(~isnan(obj.dat), 2); + +end + +% 6/22/13 Tor Added to enforce consistency in objects across usage cases +if isempty(obj.sig) || (numel(obj.sig) == 1 && ~obj.sig) + obj.sig = true(size(obj.dat)); +end + +% .cluster can be either the size of a reduced, in-mask dataset after removing empties +% or the size of the full in-mask dataset that defined the image +% (volInfo.nvox). We have to switch behavior according to which it is. +if size(obj.volInfo(1).cluster, 1) == size(obj.volInfo(1).xyzlist, 1) + + wh = logical(obj.sig(:, 1)); + + newcl = spm_clusters(obj.volInfo(1).xyzlist(wh, :)')'; % 6/22/14 tor changed to use .sig + obj.volInfo(1).cluster(wh) = newcl; % tor changed to use .sig + +else % full in-mask -- assign to correct voxels + wh = wh & logical(obj.sig(:, 1)); + + obj.volInfo(1).cluster(wh) = spm_clusters(obj.volInfo(1).xyzlist(wh, :)')'; +end + +obj = remove_empty(obj); + +end + + + diff --git a/@statistic_image/statistic_image.m b/@statistic_image/statistic_image.m new file mode 100644 index 00000000..044de7c5 --- /dev/null +++ b/@statistic_image/statistic_image.m @@ -0,0 +1,204 @@ +classdef statistic_image < image_vector +% Class definition for a statistic_image object. +% Here is a short example of some things you can do with this object: +% +% As with any object class in this toolbox, you can create an object by +% specifying names of fields paired with values. You can also enter +% filenames when you call statistic_image and create an image by loading a +% file. Finally, some methods performed on other objects (e.g., predict, +% regress, and ttest for fmri_data objects) will return statistic_image +% objects. +% For example, the line of code below creates a map of random t-values: +% t = statistic_image('dat', rand(1000, 1), 'type', 't', 'dfe', 29); +% +% If you want to use the full functionality of the object type without +% errors, however, a .volInfo field will need to be attached with +% information about the image space. The easiest way to create this is to +% load an existing image, which will automatically read in the space along +% with other info: +% +% Start with the name of a statistic image we're interested in, with arbitrary values +% name = 'salientmap.nii'; +% +% % Load it into a statistic_image object +% img = statistic_image('image_names', name); +% +% % Plot a histogram of its values +% figure; histogram(img) +% +% % Threshold the image values; save values < 0 or > 5 +% img = threshold(img, [0 5], 'raw-outside'); +% +% % Plot a montage and spm-orthviews of the thresholded image +% montage(img); +% orthviews(img); +% +% % Remove the threshold; include all values between -Inf and Inf +% img = threshold(img, [-Inf Inf], 'raw-between'); +% +% % Show the orthviews plot of the unthresholded image +% orthviews(img); +% +% You can force an image to be of a particular type, e.g., 'T', in which +% case p-values will automatically be added. This is useful for +% thresholding. +% e.g., for an SPM T-map: +% wh = 1; +% load SPM +% img = sprintf('spmT_00%02d.img', wh(1)) +% t = statistic_image('image_names', img, 'type', 'T', 'dfe', SPM.xX.erdf); +% +% If 'type' is 'robreg', then assumes user is in a robust regression +% directory and will load in the robust_beta_000X.img and load in +% robust_p_000X.img as the p values. X is assumed = 1, but can be +% overloaded by passing in a dat_descrip field. i.e. +% deltadon=statistic_image('dat_descrip', 4, 'type', 'robreg') + + + properties + % properties of parent class are also inherited + + type + + p + + p_type + + ste + + threshold + + thr_type + + sig + + N + + dfe + + end + + methods + + function obj = statistic_image(varargin) + + % Enter fieldname', value pairs in any order to create class + % instance + + % --------------------------------- + % Create empty image_vector object, and return if no additional + % arguments + % --------------------------------- + obj.type = 'generic'; + %obj.X = []; alread defined + + obj.p = []; + obj.p_type = []; + obj.ste = []; + obj.threshold = []; + obj.thr_type = []; + obj.sig = []; + obj.dfe = []; + obj.N = []; + + % The code below can be generic to any class definition + % It parses 'fieldname', value pairs of inputs + % and returns a warning if unexpected strings are found. + + if nargin == 0 + return + end + + % all valid fieldnames + valid_names = fieldnames(obj); + + for i = 1:length(varargin) + if ischar(varargin{i}) + + % Look for a field (attribute) with the input name + wh = strmatch(varargin{i}, valid_names, 'exact'); + + % behaviors for valid fields + if ~isempty(wh) + + obj.(varargin{i}) = varargin{i + 1}; + + % eliminate strings to prevent warnings on char + % inputs + if ischar(varargin{i + 1}) + varargin{i + 1} = []; + end + + % special methods for specific fields + switch varargin{i} + + end + + else + warning('inputargs:BadInput', sprintf('Unknown field: %s', varargin{i})); + + end + end % string input + end % process inputs + + % if loading in a robust regression image, overload image_names + obj.image_names = sprintf('rob_beta_%04d.img',obj.dat_descrip); + + % load data, if we don't have data yet + % But we do have valid image names. + % ---------------------------------------- + + obj = check_image_filenames(obj); + + if isempty(obj.dat) && any(obj.files_exist) + obj = read_from_file(obj); + + elseif isempty(obj.dat) + disp('Warning: .dat is empty and files cannot be found. No image data in object.'); + end + + + % 6/22/13 Tor Added to enforce consistency in objects across usage cases + if isempty(obj.sig) || (numel(obj.sig) == 1 && ~obj.sig) + obj.sig = true(size(obj.dat)); + end + + % type - handle specific forced types + + switch obj.type + case {'T', 't'} + obj.type = 'T'; + + if isempty(obj.dfe) + error('If forcing object type = ''T'', enter dfe in call to object constructor.') + end + + obj.p = 2 * (1 - tcdf(abs(obj.dat), obj.dfe)); + obj.p_type = '2-tailed P-value from input dfe'; + + case 'p' + obj.type = 'p'; + obj.p = obj.dat; + + case 'robreg' + obj.type = 'robust regression: .dat is beta values, .p is robust p values'; + + pimg = image_vector('image_names', sprintf('rob_p_%04d.img',obj.dat_descrip)); + obj.p = pimg.dat; + obj.p_type = 'robust'; + obj.dat_descrip = ['robust regression for covariate number ' num2str(obj.dat_descrip)]; + + case 'generic' + % do nothing + + otherwise + error('User forced unknown statistic image type.') + end + + + end % class constructor + + end % methods + +end % classdef + diff --git a/@statistic_image/threshold.m b/@statistic_image/threshold.m new file mode 100644 index 00000000..7a1185a8 --- /dev/null +++ b/@statistic_image/threshold.m @@ -0,0 +1,216 @@ +function stats_image_obj = threshold(stats_image_obj, input_threshold, thresh_type, varargin) +% +% stats_image_obj = threshold(stats_image_obj, pvalthreshold or other thresh, thresh_type, ['k', extent_thresh]) +% +% Inputs: +% +% input_threshold: [pvalthreshold or other thresh] +% A numeric value corresponding to the threshold desired. +% Either a p-value or a range of raw values, depending on the threshold +% type. +% +% thresh_type: Threshold type +% can be one of: +% 'fdr' : FDR-correct based on p-values already stored in image .p field +% 'fwe' : FWE-correct; not implemented +% 'bfr' : Bonferroni correction (FWE). +% 'unc' : Uncorrected p-value threshold: p-value, e.g., .05 or .001 +% +% 'raw-between' : threshold raw image values; save those > input_threshold(1) and < input_threshold(2) +% 'raw-outside' : threshold raw image values; save those < input_threshold(1) or > input_threshold(2) +% +% 'k', followed by cluster extent in voxels: extent-based thresholding of +% any of the above +% +% Tor Wager, Dec 2010 +% +% 7/19/2013: added 'mask' and 'bfr'(bonferroni) option by Wani Woo. +% With the 'mask' option, you can define a space for the multiple comparison +% correction. +% e.g. dat = threshold(dat, 0.001, 'unc', 'k', 35, 'mask', which('scalped_avg152T1_graymatter_smoothed.img')); +% dat = threshold(dat, 0.001, 'unc', 'k', 35, 'mask', maskobj); + +k = 1; +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'k' + k = varargin{i + 1}; + + if isempty(stats_image_obj.volInfo) + error('You must add a volInfo structure to the statistic image object to do extent-based thresholding'); + end + + case 'mask' % 7/19/2013: added by Wani Woo. + + maskinput = varargin{i + 1}; varargin{i + 1} = 0; + switch class(maskinput) + case 'char' % string file name + maskobj = fmri_mask_image(maskinput); + + case 'fmri_mask_image' + maskobj = maskinput; + + otherwise + error('region class constructor: unknown mask input type.') + end + clear maskinput + + if length(maskobj.dat) ~= length(stats_image_obj.dat) + if sum(sum(maskobj.volInfo.mat ~= stats_image_obj.volInfo.mat)) + mask_mapdat = scn_map_image(maskobj, stats_image_obj); + maskdat = logical(mask_mapdat(stats_image_obj.volInfo.wh_inmask)); + else + error('VolInfo of mask and data are same, but data lengths are different.'); + end + else + if sum(sum(maskobj.volInfo.mat ~= stats_image_obj.volInfo.mat)) + error('VolInfo of mask and data are diffferent, but data lengths are same.'); + else + maskdat = logical(maskobj.dat); + end + end + + stats_image_obj = replace_empty(stats_image_obj); + stats_image_obj.p = stats_image_obj.p(maskdat); + stats_image_obj.dat = stats_image_obj.dat(maskdat); + if ~isempty(stats_image_obj.sig) + stats_image_obj.sig = stats_image_obj.sig(maskdat); + end + stats_image_obj.removed_voxels = ~maskdat; + + otherwise + error('Illegal argument keyword.'); + end + end +end + + +n = size(stats_image_obj.dat, 2); % images are columns here + +for i = 1:n + + % Determine threshold + % --------------------------------------------------- + + switch lower(thresh_type) + case 'fdr' + % fix zero P-vals, which can occur with some nonparam methods + % or large t-values + wh = stats_image_obj.p == 0 & abs(stats_image_obj.dat) > 1000*eps; + stats_image_obj.p(wh) = 1000*eps; + + thrval = FDR(stats_image_obj.p(:, i), input_threshold); + if isempty(thrval) + thresh(i) = 0; + else + thresh(i) = thrval; + end + fprintf('Image %3.0f FDR q < %3.3f threshold is %3.6f\n', i, input_threshold, thresh(i)); + stats_image_obj.threshold(i) = thresh(i); + + case 'fwe' + error('not implemented yet.') + + case {'bfr', 'bonferroni'} % 7/19/2013: added by Wani Woo. + thresh(i) = input_threshold/size(stats_image_obj.p,1); + stats_image_obj.threshold(i) = thresh(i); + + case {'unc', 'uncorrected'} + thresh(i) = input_threshold; + stats_image_obj.threshold(i) = thresh(i); + + case {'raw-between', 'raw-outside'} + thresh = input_threshold; + + otherwise + error('Unknown threshold type. Enter fdr, fwe, or uncorrected, or raw-between or raw-outside') + end + + % % Re-set .dat depending on image type (needs to be updated for other + % % image types) + % % --------------------------------------------------- + % switch + + switch lower(thresh_type) + + case {'fdr', 'fwe', 'unc', 'uncorrected', 'bfr', 'bonferroni'} + stats_image_obj.sig(:, i) = stats_image_obj.p(:, i) < thresh(i); + + % Apply size threshold + % -------------------------------------- + stats_image_obj = replace_empty(stats_image_obj); + if k > 1 + stats_image_obj.sig(:, i) = logical(iimg_cluster_extent(double(stats_image_obj.sig(:, i)), stats_image_obj.volInfo, k)); + end + + fprintf('\nImage %3.0f\nPositive effect: %3.0f voxels, min p-value: %3.8f\n', i, ... + sum(stats_image_obj.dat(:, i) > 0 & stats_image_obj.sig(:, i)), ... + min(stats_image_obj.p(stats_image_obj.dat(:, i) > 0, i))); + + fprintf('Negative effect: %3.0f voxels, min p-value: %3.8f\n', ... + sum(stats_image_obj.dat(:, i) < 0 & stats_image_obj.sig(:, i)), ... + min(stats_image_obj.p(stats_image_obj.dat(:, i) < 0, i))); + + case 'raw-between' + stats_image_obj.sig(:, i) = stats_image_obj.dat(:, i) > thresh(1) & stats_image_obj.dat(:, i) < thresh(2); + + % Apply size threshold + % -------------------------------------- + if k > 1 + stats_image_obj.sig(:, i) = logical(iimg_cluster_extent(double(stats_image_obj.sig(:, i)), stats_image_obj.volInfo, k)); + end + + fprintf('Keeping vals between %3.3f and %3.3f: %3.0f voxels in .sig\n', thresh(1), thresh(2), sum(stats_image_obj.sig(:, i))); + + case 'raw-outside' + stats_image_obj.sig(:, i) = stats_image_obj.dat(:, i) < thresh(1) | stats_image_obj.dat(:, i) > thresh(2); + + % Apply size threshold + % -------------------------------------- + if k > 1 + stats_image_obj.sig(:, i) = logical(iimg_cluster_extent(double(stats_image_obj.sig(:, i)), stats_image_obj.volInfo, k)); + end + + fprintf('Keeping vals outside of %3.3f to %3.3f: %3.0f voxels in .sig\n', thresh(1), thresh(2), sum(stats_image_obj.sig(:, i))); + + otherwise + error('Unknown threshold type. Enter fdr, fwe, or uncorrected') + end + + % Replace .dat field with zero for non-sig voxels, so that we can + % create regions, etc. .t field and .p field, etc., still have + % original info. + % But doing this here breaks the ability to re-threshold, so now done + % in region.m + %stats_image_obj.dat(~stats_image_obj.sig(:, i), i) = 0; + + +end % image loop + +if isempty(stats_image_obj.volInfo) + disp('Warning! volInfo not defined for stats image object. regions cannot be formed correctly from this image.') +else + + + % Update volInfo, for region-defining purposes. + % Note that this will only work for the FIRST image in the set + + stats_image_obj = reparse_contiguous(stats_image_obj); + + % i = 1; +% wh = logical(stats_image_obj.sig(:, i)); +% n = sum(wh); +% +% if n < 50000 +% stats_image_obj.volInfo(1).cluster = zeros(size(stats_image_obj.volInfo(1).cluster)); +% stats_image_obj.volInfo(1).cluster(wh) = spm_clusters(stats_image_obj.volInfo(1).xyzlist(wh, :)')'; +% else +% stats_image_obj.volInfo(1).cluster(wh) = ones(n, 1); +% end +end + +end % function + + + diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_FA.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_FA.m new file mode 100755 index 00000000..3e16ed9a --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_FA.m @@ -0,0 +1,55 @@ +function [clusters,m,mnames] = cluster_FA(clusters,OPT) +% [clusters,m,mnames] = cluster_FA(clusters,OPT) +% Gets independent components for canonical timeseries values from clusters +% +% Requires OPT structure with the following fields (examples shown here): +% O.HP = 100; +% O.TR = 1.5; +% O.doHP = 1; +% O.doLP = 0; +% O.scanadjust = 1; +% O.percent = 0; +% O.filtertype = 'spm'; +% O.nruns = 2; +% O.adjustmatrix = custom adjustment matrix to regress out (e.g., movement params) +% O.trimts = 3; +% +% Recommended now: average, or FA. ICA can sometimes reverse sign, so can +% PCA potentially. Not sure about sign thing. ICA and PCA enforce +% orthogonal canonical variates. +% +% Returns: clusters, with pcscore, ica, and varimax fields +% average of voxels is stored in timeseries +% +% Maybe the best thing is to take voxels that load highly on ICs or PCs and +% return selective averages of those subsets - so gives subclusters. +% +% m is a matrix of rows = time (or observations), cols = timecourses within +% and across regions +% +% see also cluster_princomp.m + +mnames = {}; + +for i = 1:length(clusters) + + m = clusters(i).all_data; + m2 = roi_timeseries(m,OPT); + + close all + + clusters(i).pcscore = m2.pcscore; + clusters(i).ica = m2.ica; + clusters(i).varimax = m2.varimax; + + tmp = [clusters(i).title '_' num2str(i) '_' num2str(clusters(i).mm_center(1)) num2str(clusters(i).mm_center(2)) num2str(clusters(i).mm_center(3))]; + tmp(tmp==' ' | tmp=='.') = ['_']; + + for j = 1:size(clusters(i).varimax,2) + mnames = [mnames {tmp}]; + end +end + +m = cat(2,clusters.varimax); + +return diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim.m new file mode 100644 index 00000000..5f4c3068 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim.m @@ -0,0 +1,167 @@ +function D = cluster_discrim(cl,beh,varargin) +% D = cluster_discrim(cl,beh,[covs of no interest]) +% +%x = cat(2,pain_matrix_early(:).timeseries); +%x = cat(2,pain_matrix_peak(:).timeseries); + +diary Cluster_Discrim_Output.txt + +disp('cluster_discrim.m: Predictions of behavior with contrast scores'); +fprintf(1,'\n-----------------------------------------------------------------\n'); + +disp(['Input clusters: ' inputname(1)]) +disp('Loading data from cl.timeseries'); + + +% Get values from clusters +% --------------------------------------------- +x = cat(2,cl(:).timeseries); +if any(isnan(sum(x))), + whnan = find(isnan(sum(x))); + cl(whnan) = []; x = cat(2,cl(:).timeseries); + disp(['Found NaNs! Removing clusters ' num2str(whnan)]); +end + + + +% Remove covariates of no interest +% --------------------------------------------- +covs = []; +if length(varargin) > 0 + covs = varargin{1}; + D.covs = covs; + disp('Found covariate(s) of no interest; Removing them from behavior and brain data.') + % partialcorr gets adjusted x, y, and correls; here just use it to + % get adjusted x + beh = partialcor([beh covs],ones(size(beh,1),1),1); + + for i = 1:size(x,2) + x(:,i) = partialcor([x(:,i) covs],ones(size(x,1),1),1); + end +end + + +D.data = x; + +figure;imagesc(x); colorbar; title('Data for discriminant analysis'); drawnow + +D.xyzmm = cat(1,cl(:).mm_center); + +y = beh; +D.beh = beh; + +y2 = mediansplit(y); +D.beh_medsplit = y2; + +if isfield(cl,'imnames'),D.imnames = cl(1).imnames;,end + +% Print univariate correlations with regions and location (t-test) +% --------------------------------------------- +disp('Correlations with behavior, after removing covariates of no interest.') +fprintf(1,'%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n','Cluster','Name','x','y','z','V','r', 'p', 'IRLSr', 'IRLSp','Zavg','Inc/Dec'); +warning off +fprintf(1,'\n'); +for i = 1:size(x,2) + [dummy,dummy,r,p,rrob,prob] = partialcor([beh covs],x(:,i),1); + + % print output + fprintf(1,'Cluster %3.0f\t',i); + if isfield(cl,'shorttitle'), fprintf(1,'%s\t',cl(i).shorttitle),end + if isfield(cl,'mm_center'),fprintf(1,'%3.0f\t%3.0f\t%3.0f\t',cl(i).mm_center);,end + if isfield(cl,'numVox'),fprintf(1,'%3.0f\t',cl(i).numVox);,end + fprintf(1,'%3.2f\t%3.2f\t%3.2f\t%3.2f\t',[r p rrob prob]) + + % t-test + [hh,p,ci,stats] = ttest(cl(i).timeseries); + strs = {'Decrease' '--' 'Increase'}; + if ~hh, strs = strs{2};,elseif stats.tstat >0,strs=strs{3};,else,strs = strs{1};end + fprintf(1,'%3.2f\t%s\t',stats.tstat,strs) + + + fprintf(1,'\n'); +end +warning on + +% -------------------------------------- +% get eigenvalues, choose # dims +% -------------------------------------- +disp('Computing significant principal components') +[v,score,e] = princomp(x); +[pc,D.pca_npm] = pca_npm(x,1000); +whsig = find(cumsum(D.pca_npm.sig) >= 1:length(D.pca_npm.sig)); +ndim = length(whsig) + 1; + +try, saveas(gcf,'Cluster_eigenplot','tif');,end + +% -------------------------------------- +% predict behavior with component scores +% -------------------------------------- + +fprintf(1,'\n-----------------------------------------------------------------\n'); +fprintf(1,'Stepwise regression: Predictions of behavior with component scores'); +fprintf(1,'\n-----------------------------------------------------------------\n'); + +D.PCR = stepwise_tor(score(:,1:ndim),y); + +sig = find(D.PCR.inmodel); +D.PCR.sscore = score(:,sig); % scores for significant components + +% plot all significant (now done in cluster_discrim_montage) +%figure('Color','w'); +%for i = 1:size(D.PCR.sscore,2) +% subplot(1,size(D.PCR.sscore,2),i); +% discrim_plot(D.PCR.sscore(:,i),y,0); +%end + +D.PCR.seigv = v(:,sig); % eigenvectors for sig components +D.PCR.seig = e(sig); % significant eigenvalues + +tmpc = corrcoef([D.PCR.sscore x]); +tmpc(1:size(D.PCR.sscore,2),:) = []; tmpc = tmpc(:,1:size(D.PCR.sscore,2)); +D.PCR.compcorr = tmpc; +D.PCR.compcorr_descrip = 'Regions x sig. comps, correlation with component values'; +D.PCR.whichcomps = sign(D.PCR.compcorr) .* real(abs(D.PCR.compcorr) > .4); + +tmpc(abs(tmpc) < .4) = 0; +D.PCR.threshcomps = tmpc; + +% -------------------------------------- +% manova to get categorical canonical discrim functions +% this gives us overall p-value and discrim functions +% -------------------------------------- +[manovasig,manovap,D.manova] = manova1(score(:,1:ndim),y2); +D.manova.p = manovap; +fprintf(1,'\n-----------------------------------------------------------------\n'); +fprintf(1,'Manova to discriminate high from low behavior'); +fprintf(1,'\n-----------------------------------------------------------------\n'); +fprintf(1,'Wilks lambda (%3.0f,%3.0f) = %3.2f\tChi-sq (%3.0f) = %3.2f\t, p = %3.4f\t\n',D.manova.dfB,D.manova.dfW,D.manova.lambda,D.manova.chisqdf,D.manova.chisq,D.manova.p); +%discrim_plot(D.manova.canon(:,1),y); +fprintf(1,'Eigenvalues\t') +fprintf(1,'%3.2f\t',D.manova.eigenval') +fprintf(1,'\n') +fprintf(1,'Eigenvector 1\t') +fprintf(1,'%3.2f\t',D.manova.eigenvec(:,1)') +fprintf(1,'\n') + +% -------------------------------------- +% visualize clusters on brain +% -------------------------------------- + +cluster_discrim_montage(cl,D.PCR.threshcomps,D.PCR.sscore,D.beh); + +% -------------------------------------- +% manova on individual regions?? +% -------------------------------------- + + +diary off + + +return + + + + + + + diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim_montage.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim_montage.m new file mode 100644 index 00000000..d854efe6 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim_montage.m @@ -0,0 +1,115 @@ +function cluster_discrim_montage(cl,thr,varargin) +% cluster_discrim_montage(cl,thr,[scores],[behavior]) +% +% For viewing clusters with nonzero entries in each column of thr +% with color assignments based on values in thr +% i.e., +% thr is thresholded component loadings, zero if not significant +% +% optional: scores and behavior arguments +% If entered, partial correlation plot will be shown +% using discrim_plot.m +% +% see also: +% cluster_discrim +% discrim_plot +% +% examples: +% to run stand-alone from output of cluster_discrim: +% cluster_discrim_montage(cl,D.PCR.threshcomps,D.PCR.sscore,D.beh); + + % --------------------------------------------- + % save clusters and get figures for each + % --------------------------------------------- + + + %mycols = {'r' 'b' 'g' 'y' 'c' 'm' 'k' 'k' 'k' 'k'}; str = []; + for i = 1:size(thr,2) + + fprintf(1,'\n--------------------------------\n'); + fprintf(1,'Sig. Component %3.0f',i); + fprintf(1,'\n--------------------------------\n'); + + wh = find(thr(:,i)); % positive and negative loading clusters + + fprintf(1,'%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n', ... + 'Cluster','Name','x','y','z','V','Comp.r','Comp.p', 'Beh.r', 'Beh.p','BehRobr', 'BehRobp','Zavg','Inc/Dec'); + + for j = 1:length(cl) + % save loading in Z-values + cl(j).Z = thr(j,i) .* ones(1,size(cl(j).XYZ,2)); + + if thr(j,i) + % print output + fprintf(1,'Cluster %3.0f\t',j); + if isfield(cl,'shorttitle'), fprintf(1,'%s\t',cl(j).shorttitle),end + if isfield(cl,'mm_center'),fprintf(1,'%3.0f\t%3.0f\t%3.0f\t',cl(j).mm_center);,end + if isfield(cl,'numVox'),fprintf(1,'%3.0f\t',cl(j).numVox);,end + fprintf(1,'%3.2f\t',thr(j,i)) + + if length(varargin) > 0 + scores = varargin{1}; + % correlations with components; we already have correls + % in thr input; get p-values + [dummy,dummy,r,p,rrob,prob] = partialcor([scores],cl(j).timeseries,1); + fprintf(1,'%3.4f\t',[p]) + end + + if length(varargin) > 0 + beh = varargin{2}; + % correlations with behavior + [dummy,dummy,r,p,rrob,prob] = partialcor(cl(j).timeseries,beh,1); + fprintf(1,'%3.2f\t%3.4f\t%3.2f\t%3.4f\t',[r p rrob prob]) + end + + % t-test + [hh,p,ci,stats] = ttest(cl(j).timeseries); + strs = {'Decrease' '--' 'Increase'}; + if ~hh, strs = strs{2};,elseif stats.tstat >0,strs=strs{3};,else,strs = strs{1};end + fprintf(1,'%3.2f\t%s\t',stats.tstat,strs) + + fprintf(1,'\n'); + end + end + + % orthviews + whpos = find(thr(:,i) > 0); + whneg = find(thr(:,i) < 0); + try + + if any(thr(wh,i) > 0), + cluster_orthviews(cl(whpos),{[1 0 0]}); + if any(thr(wh,i) < 0), + cluster_orthviews(cl(whneg),{[1 0 0]},'add'); % should have Z-scores reversed, so be neg + end + elseif any(thr(wh,i) < 0), + cluster_orthviews(cl(whneg),{[1 0 0]}); + end + + catch + disp('cannot make cluster_orthviews'), + end + + + % scatterplot, if we have necessaries + + if length(varargin) > 0 + try, scores = varargin{1}; beh = varargin{2};, catch, error('Enter scores and beh as 3rd and 4th args or stick w/two args.'),end + if ~exist('h'),h=[];,end + if ishandle(h) + axes(h); cla; + else + h = axes('Position',[.52 .08 .45 .38]); + end + y2 = discrim_plot(scores,beh,0,i); % partial residual plot of ith column of scores vs. beh + xlabel(['Component ' num2str(i)]) + end + + % montage + + montage_clusters([],cl(wh),{'r'},[2 2]); % should plot z-scores in red/blue + drawnow + + ans = input('Press return to continue.'); + + end \ No newline at end of file diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_network.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_network.m new file mode 100755 index 00000000..25a8e7d1 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_network.m @@ -0,0 +1,379 @@ +function [subcl,out,pccl] = cluster_network(cl,k,beh,varargin) +% [subcl,out,pccl] = cluster_network(cl,k,beh,varargin) +% +% uses clusters(i).BARPLOT.dat (preferred) or .data as data, with column k +% beh is n x m matrix of m covariates, (n subjects), double centered +% +% tor wager +% +% if varargin, saves data in file with string = varargin{1} +% +% subcl is clusters cell array with one clusters structure per class - +% separates positively and negatively weighted clusters. +% +% pccl is cl cell array for each significant PC +% Z field in each cluster has PC weights , for imaging +% Z values are actually correlation between timeseries and PC, which is +% a normalization of the PC weights (eigenvector weights) +% try montage_clusters([],pccl,[2 2]) + +doresid = 1; +dorobust = 0; % nonfunctional! change in prplot + +if length(varargin) > 0 + dosave = 1; + savestr = varargin{1}; + try, eval(['mkdir ' savestr]),catch,end + cd(savestr) +else + dosave = 0; + savestr = '-'; +end + +plotflag = 1; + +dat = []; dopk = 0; + +for i = 1:length(cl) + + if ~isfield(cl(i).BARPLOT,'dat') & ~isfield(cl(i).BARPLOT,'data') + dopk = 1; + % not implemented + end + + try + dat = [dat cl(i).BARPLOT.dat(:,k)]; + catch + dat = [dat cl(i).BARPLOT.data(:,k)]; + end +end + +% double-center +% +dat = scale(dat,1); dat = scale(dat',1)'; + +% --------------------------------------------- +% principal components +% --------------------------------------------- + +%out = rapca(dat); +%out.pcomps = out.T; +%out.weights = out.P; +%out.eigval = out.L; +fprintf(1,'pca_npm.') +[pc,stats] = pca_npm(dat,500); +out = stats; out.T = stats.score(:,stats.wh); out.P = pc; out.L =stats.eigval(stats.wh); +figure('Color','w');plot(out.eigval,'ro-','LineWidth',2); hold on; plot(out.thresh,'ks-','LineWidth',2) +legend({'Eigenvalues' 'Upper 95% permuted'}) + +out.k = k; +out.dat = dat; +out.beh = beh; + +for i = 1:size(out.T,2), + pccl{i} = cl; + for j = 1:length(cl) + pccl{i}(j).Z = ones(size(pccl{i}(j).Z)) .* out.wcor(j,i); + end + montage_clusters([],pccl{i},[2 2]) +end + + +% --------------------------------------------- +% classification of regions +% --------------------------------------------- + +maxclusters = length(out.L); + +%tmp = out.P * diag(out.L); % put original scaling back so that early eigs are weighted more heavily +%out.class = docluster(tmp',maxclusters,plotflag); + +%out.outliers = find(out.od > mean(out.od) + 1.5*std(out.od) | out.od < mean(out.od) - 1.5*std(out.od)); + +disp('cluster_network:') +%fprintf(1,'Input clusters:\t%3.0f\nGroups:\t%3.0f\n',length(cl),max(out.class)) +fprintf(1,'Input clusters:\t%3.0f\nGroups:\t%3.0f\n',length(cl),size(out.class,2)) +fprintf(1,'Data column:\t%3.0f\n',k) +fprintf(1,['Eigenvalues:\t']) + for i = 1:min(8,length(out.eigval)) + if out.sig(i),sstr = '*';,else,sstr='';,end + fprintf(1,'%3.2f%s\t',out.eigval(i),sstr) + end +fprintf(1,'\n') + +fprintf(1,['Var. explained:\t' repmat('%3.2f\t',1,length(out.L)) '\n'],out.expl(out.wh)) +fprintf(1,['p (nonparametric):\t' repmat('%3.6f\t',1,length(out.L)) '\n'],out.p(out.wh)) + + +c = tril(corrcoef(dat));c=c(:);c(c==1 | c == 0) = []; +disp(sprintf('PC1stats\tmean\tstd\tmin\tmax\n')) +disp(sprintf('%s\t%3.2f\t%3.2f\t%3.2f\t%3.2f\t',savestr,mean(c),std(c),min(c),max(c))) + + % --------------------------------------------- + % correlations between components and behavior + % --------------------------------------------- + + for j = 1:size(beh,2), + figure('Color','w');[r,str,sig,ry,rx,h] = prplot(out.T,beh,j); + set(gcf,'Position',[38 48 618 495]) + title(['Partial correlations btwn beh.' num2str(j) ' and PCs']) + out.CORREL.rcomp{j} = r; + out.CORREL.compsig{j} = sig; + out.CORREL.rcomp_names{j} = ['Corr. of beh. ' num2str(j) ' with PCs']; + + out.CORREL.raw_str = 'Rows are components, cols are beh vectors'; + for k = 1:size(out.T,2) + r = corrcoef(out.T(:,k),beh(:,j)); + out.CORREL.raw_compr(k,j) = r(1,2); + end + + if dosave & sig, + saveas(gcf,[savestr 'BEH' num2str(j) '_PCs.fig']) + saveas(gcf,[savestr 'BEH' num2str(j) '_PCs.tif']) + end + + end + + % + %for i = 1:max(out.class) + % out.classdata(:,i) = mean(dat(:,out.class==i),2); + %end + + + % --------------------------------------------- + % correlations between avgs and behavior + % --------------------------------------------- + + for j = 1:size(beh,2), + figure('Color','w');[r,str,sig,ry,rx,h] = prplot(out.classdata,beh,j); + set(gcf,'Position',[957.0000 64.0000 618.0000 495.0000]) + title(['Partial Correlations btwn beh.' num2str(j) ' and avg data of each class']) + out.CORREL.beh = beh; + out.CORREL.rclass{j} = r; + out.CORREL.classsig{j} = sig; + out.CORREL.rclass_names{j} = ['Corr. of beh. ' num2str(j) ' with class avgs']; + + for k = 1:size(out.classdata,2) + r = corrcoef(out.classdata(:,k),beh(:,j)); + out.CORREL.raw_classr(k,j) = r(1,2); + end + + if dosave, + saveas(gcf,[savestr 'BEH' num2str(j) '_class.fig']) + saveas(gcf,[savestr 'BEH' num2str(j) '_class.tif']) + end + end + + + % --------------------------------------------- + % table + % --------------------------------------------- + + fprintf(1,['Correlations\t']) + for i = 1:size(beh,2) + for j = 1:size(out.T,2) + fprintf(1,'BEH%3.0fCOMP%3.0f\t',i,j) + end + end + fprintf(1,'\n') + fprintf(1,['r: component scores\t\n']) + for i = 1:size(beh,2) + fprintf('Partial r with Beh %3.0f\t',i) + for j = 1:size(out.T,2) + if out.CORREL.compsig{i}(j), rstr='*';,else,rstr='';,end + fprintf(1,'%3.2f%s\t',out.CORREL.rcomp{i}(j),rstr) + end + fprintf(1,'\n') + end + fprintf(1,'\n') + + [dummy,dummy,dummy,dummy,rcrit]=r2z(.5,size(out.T,1),.05); + out.CORREL.rcrit = rcrit; + for i = 1:size(beh,2) + fprintf(1,'Raw r with Beh %3.0f\t',i) + for j = 1:size(out.T,2) + if out.CORREL.raw_compr(j,i) >= rcrit, rstr='*';,else,rstr='';,end + fprintf(1,'%3.2f%s\t',out.CORREL.raw_compr(j,i),rstr) + end + fprintf(1,'\n') + end + + fprintf(1,['r: class averages\t\n']) + for i = 1:size(beh,2) + fprintf('Partial r with Beh %3.0f\t',i) + for j = 1:size(out.class,2) + if out.CORREL.classsig{i}(j), rstr='*';,else,rstr='';,end + fprintf(1,'%3.2f%s\t',out.CORREL.rclass{i}(j),rstr) + end + fprintf(1,'\n') + end + + for i = 1:size(beh,2) + fprintf(1,'Raw r with Beh %3.0f\t',i) + for j = 1:size(out.class,2) + if out.CORREL.raw_classr(j,i) >= rcrit, rstr='*';,else,rstr='';,end + fprintf(1,'%3.2f%s\t',out.CORREL.raw_classr(j,i),rstr) + end + fprintf(1,'\n') + end + + %%% *** class data predicting beh + % for i = 1:size(beh,2) + % fprintf(1,'Raw r with Beh %3.0f\t',i) + % for j = 1:size(out.class,2) + % if out.CORREL.raw_classr(j,i) >= rcrit, rstr='*';,else,rstr='';,end + % fprintf(1,'%3.2f%s\t',out.CORREL.raw_classr(j,i),rstr) + % end + % fprintf(1,'\n') + %end + + fprintf(1,'\n') + fprintf(1,['prop. regions in class\t\n']) + tmp = sum(out.class)./size(out.class,1); + fprintf(1,repmat('%3.2f\t',1,length(tmp)),tmp) + fprintf(1,'\n') + fprintf(1,['r: avg class weights\n']) + for i = 1:length(out.wh) + for j = 1:size(out.class,2) + fprintf(1,'%3.2f\t',mean(out.pc(:,i) .* out.class(:,j))); + end + end + + fprintf(1,'\n') + fprintf(1,'\n') + + + % --------------------------------------------- + % save clusters and get figures for each + % --------------------------------------------- + mycols = {'r' 'b' 'g' 'y' 'c' 'm' 'k' 'k' 'k' 'k'}; str = []; + for i = 1:size(out.class,2) + %subcl{i} = cl(out.class==i); + subcl{i} = cl(find(out.class(:,i))); + str = [str ',subcl{' num2str(i) '}']; + end + str = ['montage_clusters([]' str ',[mycols(1:size(out.class,2)) {''k''} {''k''}]);']; + eval(str) + set(gcf,'Position',[266 73 711 1048]) + if dosave, + saveas(gcf,[savestr 'class_montage.fig']) + saveas(gcf,[savestr 'class_montage.fig']) + end + + % --------------------------------------------- + % residual table + % --------------------------------------------- + wh_pos = zeros(size(out.T,1),size(beh,2)); wh_neg = wh_pos; + + fprintf(1,['Cluster\tx\ty\tz\tVox\t']) + for i=1:length(out.L), fprintf(1,'%s\t',['R-C' num2str(i)]);,end + for i = 1:size(out.class,2),fprintf(1,'Class_%3.0f\t',i),end + + fprintf(1,'Comm.\t') + for i=1:size(beh,2), fprintf(1,'%s\t',['Res-BEH' num2str(i)]);,end + fprintf(1,'\n') + + for i = 1:length(cl) + + if isfield(cl,'shorttitle'),mstr=[cl(i).shorttitle '(cl. ' num2str(i) ')']; + elseif isfield(cl,'BAstr'), mstr = cl(i).BAstr;, else, mstr = ['CL' num2str(i)];,end + + fprintf(1,'%s\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t',mstr,cl(i).mm_center(1),cl(i).mm_center(2),cl(i).mm_center(3),cl(i).numVox) + fprintf(1,repmat('%3.2f\t',1,length(out.L)),out.wcor(i,:)) + for j = 1:size(out.class,2),fprintf(1,'%3.0f\t',out.class(i,j)),end + + if doresid + + X = [out.T ones(size(out.T,1),1)]; y = dat(:,i); + rd = y - X * (pinv(X) * y); + out.residstr = 'Residual activation after removing principal components'; + out.residuals(:,i) = rd; + out.communality(i) = 1 - (var(rd) ./ var(y)); + fprintf(1,'%3.2f\t',out.communality(i)); + + for j = 1:size(beh,2) + figure('Color','w');[r,str,sig,ry,rx,h] = prplot(rd,beh,j); + set(gcf,'Position',[1068 701 524 410]) + title(['RESIDUAL r: BEH' num2str(j) ' cl ' num2str(i) ' for data col. ' num2str(k)]) + + if dosave, + saveas(gcf,[savestr 'BEH' num2str(j) 'cl' num2str(i) '_residcor.fig']) + saveas(gcf,[savestr 'BEH' num2str(j) 'cl' num2str(i) '_residcor.tif']) + end + + if sig, + rstr='*';, + if r > 0,wh_pos(i,j) = 1;,elseif r < 0,wh_neg(i,j) = 1;,else, error('Uh-oh!'),end + else,rstr='';,close, + end + fprintf(1,'%3.2f%s\t',r,rstr); + end + + end + + fprintf(1,'\n') + end + + if doresid + % --------------------------------------------- + % show areas with residual correlations + % --------------------------------------------- + + for i = 1:size(beh,2) + mycols = {[1 .5 0] [.2 .6 .5]}; str = []; + + clpos{i} = cl(find(wh_pos(:,i))); + clneg{i} = cl(find(wh_neg(:,i))); + + if ~isempty(clpos{i}) + str = [str ',clpos{' num2str(i) '}']; + else + mycols = mycols(2); + end + + if ~isempty(clneg{i}) + str = [str ',clneg{' num2str(i) '}']; + end + + if ~isempty(clpos{i}) | ~isempty(clneg{i}) + str = ['montage_clusters([]' str ',mycols);']; + eval(str) + set(gcf,'Position',[266 73 711 1048]) + + if dosave, + saveas(gcf,[savestr 'BEH' num2str(i) '_resid_montage.fig']) + saveas(gcf,[savestr 'BEH' num2str(i) '_resid_montage.tif']) + end + end + end + + end % if doresid + + if dosave, eval(['save ' savestr '_clusters subcl out']),end + + + +return + + + + + + function class = docluster(a,maxclusters,doplot) + + Y = pdist(a','euclid'); % transpose so the voxels are observations, eigenvectors the variables + Z = linkage(Y,'complete'); + if maxclusters > 1 + class = cluster(Z,maxclusters)'; + + if doplot, + dendrogram(Z,0); title('Dendrogram for clustering') + end + + else + class = ones(1,size(a,2)); + end + + + +return \ No newline at end of file diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig.m new file mode 100755 index 00000000..0e9ab959 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig.m @@ -0,0 +1,350 @@ +function [c,cl] = cluster_nmdsfig(cl,fldname,colm,varargin) +% function [c,cl] = cluster_nmdsfig(cl,fldname,colm,[behavior],[ancova var],[covs of no interest],[overlay],[niter]) +% +% Makes a multidim scaling figure from clusters +% Plus some other plots +% +% Example: +% c = cluster_nmdsfig(cl,'BARPLOT.data',2); +% +% c is a structure with lots of info +% cl has names appended, if not entered previously +% +% behavioral input is of three types: +% outcome covariate of interest (1 only) +% ancova_var categorical ANCOVA coding var (2-levels, one only); +% generally not of interest +% IF ancova_var is entered, "PPI" of group x brain +% covariance interaction will be computed +% covs of no int of no interest; will be removed from brain and behavior +% before performing ANCOVA (for simplicity); 1 or more OK +% +% Tor Wager +% +% Examples: +% +% Use average data from cl.timeseries and correlate with EXPT.behavior. +% Covary out effects of order before running models; no ANCOVA +% [c,cl] = cluster_nmdsfig(cl,'timeseries',1,EXPT.behavior',[],EXPT.order2); +% +% More examples: +% c = +% cluster_nmdsfig(cl,'CONTRAST.yadj',1,repmat(EXPT.cov(:,1),4,1),[],[],EXPT.overlay,1000); +% [c,cl] = cluster_nmdsfig(cl,'CONTRAST.data',1,R.X(:,1),[],R.X(:,2),EXPT.overlay,200); +% [c,cl] = cluster_nmdsfig(cl,'timeseries',1,SETUP.Y,[],SETUP.X,EXPT.overlay,1000); + +% subdirectory: prompt +dosubdir = input('Create a subdirectory for png images? (type name or return to use current dir) ','s'); +if ~isempty(dosubdir) + mkdir(dosubdir) + cd(dosubdir) +end + +diary cluster_nmdsfig_output.txt +doranks = 1; +addbtwn = 0; + +if length(varargin) > 0, c.outcome = varargin{1}; else c.outcome = [];end +if length(varargin) > 1, c.ancova_codes = varargin{2}; else c.ancova_codes = [];end +if length(varargin) > 2, c.covs_nointerest = varargin{3}; else c.covs_nointerest = [];end +if length(varargin) > 3, overlay = varargin{4}; else overlay = [];end +if length(varargin) > 4, niter = varargin{5}; else niter = 1000;end + +cl(1).outcome = c.outcome; cl(1).ancova_codes = c.ancova_codes; cl(1).covs_nointerest=c.covs_nointerest; + +c.X = [c.outcome, c.covs_nointerest]; +contrasts = []; + +c.colors = {'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + +if ~isfield(c,'btwn_names') || isempty(c.btwn_names) + c.btwn_names = {'Behav'}; +end + + +% ----------------------------------------------------------------------- +% get data from selected field +% remove covariates of no interest from predictors and data +% ----------------------------------------------------------------------- + +c = nmdsfig_tools('get_data_matrix',c,cl,fldname,colm,contrasts,doranks); + +if addbtwn + % add between-subjects behavioral scores to dat; just to get stim coords; remove later... + c.dat = [c.outcome c.dat]; +end + +% ----------------------------------------------------------------------- +% get names +% ----------------------------------------------------------------------- + +cl = cluster_names(cl,1); +for i = 1:length(cl), c.names{i} = cl(i).shorttitle; end + + +% ----------------------------------------------------------------------- +% get correlations, significance, and number of dimensions +% If groups are entered and ANCOVA is specified, handle that +% Also get c.D, dissimilarity matrix for MDS +% ----------------------------------------------------------------------- + +c = nmdsfig_tools('get_correlations',c); + + +% MDS decomposition and plotting +% ----------------------------------------------------------------------- +% get number of dimensions that carry most of the variance. +% This is to simplify the space and make it less sparse -- +% expected to give better clustering results. +% +% Reduce to distances in ndims space, check this against actual distances +% ----------------------------------------------------------------------- + +% graphic check on number of dimensions, and MDS +[c.GroupSpace,c.obs,c.implied_dissim] = shepardplot(c.D,[]); +drawnow +c.ndims = size(c.GroupSpace,2); % set here; graphical decision within shepardplot + +nclust = 2:(size(c.GroupSpace,1) / 2)+1; % choose number of clusters to test + +save_figure('cluster_nmdsfig_shepardplot'); + +if addbtwn + % get coordinates for behavior along with the others + fprintf(1,'Using between-subjects covariates in MDS model.\n'); + + % save coordinates in MDS space for btwn data + c.btwn_coords = c.GroupSpace(1,:); + + % remove from GroupSpace + c.GroupSpace = c.GroupSpace(2:end,:); + + % remove from r + c.r = c.r(2:end,2:end); +end + +% ----------------------------------------------------------------------- +% testcluster: +% +% Permute objects in space, get null hypothesis cluster quality, and test +% observed cluster solution against this. +% ----------------------------------------------------------------------- + +% pval is p-values for each number of clusters tested +% classes = class ("network") assignments for best clustering solution +[c.ClusterSolution.pvals,c.ClusterSolution.classes, c.ClusterSolution.classnames ... + c.ClusterSolution.X,c.ClusterSolution.include,c.ClusterSolution.names]= ... + testclustnew(c.GroupSpace,nclust,c.ndims,niter,c.names,'keep','average'); + +if ~any(c.ClusterSolution.pvals < .05) + % nothing significant + c.ClusterSolution.classes = ones(size(c.ClusterSolution.classes)); +end + +%[bestpval,bestXc,bestnames,bestX,where,clustnames]=testcluster(X,clust,[r],[nperm],[names],[remove],[linkagetype]); + +save_figure('cluster_nmdsfig_testcluster'); + + + +% ----------------------------------------------------------------------- +% factor scores with reduced dimensions +% ----------------------------------------------------------------------- +r = c.r; +[tmp c.eigenvalues] = cmdscale(c.D); +sqL = sqrt(diag(c.eigenvalues(1:c.ndims))); +B = inv(r) * c.GroupSpace(:,1:c.ndims) * sqL; % inv(r) * factor loading matrix A = VsqrtL +c.factor_scores = c.dat * B; + +% ----------------------------------------------------------------------- +% Stepwise regression of dimensions on behavior +% ----------------------------------------------------------------------- + +if ~isempty(c.outcome) + fprintf(1,'\n'); + fprintf(1,'Prediction of behavior with component scores (from classic MDS)\n'); + + DATA.INDSCAL.STEPWISE = stepwise_tor(c.factor_scores,c.outcome); + fprintf(1,'\n'); +end + + + +% ----------------------------------------------------------------------- +% % plot nmds figures of this +% ----------------------------------------------------------------------- +dosideplots = 0; +figname = nmdsfig_tools('nmdsfig_plot',c, addbtwn, dosideplots, 'nofill'); + +save_figure(figname); + + +% ----------------------------------------------------------------------- +% separate plots of high/low behavior +% ----------------------------------------------------------------------- +if ~isempty(c.outcome) % plot interactions between beh and regional covariance + create_wide_figure; + subplot(1,2,1); + [r,p]=corrcoef(c.dat(c.outcome>median(c.outcome),:)); r(r>.9999) = 1; sig = sign(r) .* (p < .05); + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',sig); + title('High behavior') + + subplot(1,2,2) + [r,p]=corrcoef(c.dat(c.outcome<=median(c.outcome),:)); r(r>.9999) = 1; sig = sign(r) .* (p < .05); + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',sig); + title('Low behavior') + drawnow +end + +if ~isempty(c.ancova_codes) % plot interactions between beh and regional covariance + tor_fig(1,3); subplot(1,3,1); + [r,p]=corrcoef(c.dat(c.ancova_codes>median(c.ancova_codes),:)); r(r>.9999) = 1; sig = sign(r) .* (p < .05); + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',sig); + title('High ANCOVA group') + + subplot(1,3,2) + [r,p]=corrcoef(c.dat(c.ancova_codes.9999) = 1; sig = sign(r) .* (p < .05); + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',sig); + title('Low ANCOVA group') + drawnow + + subplot(1,3,3) + [r,p]=corrcoef(c.dat(c.ancova_codes.9999) = 1; sig = sign(r) .* (p < .05); + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',c.STATS.siginteract); + title('Interactions') + + +end + + +% ----------------------------------------------------------------------- +% Optional plots of interactions and correlations +% ----------------------------------------------------------------------- +if c.doancova && ~isempty(c.ancova_codes) + [i,j] = find(triu(c.STATS.siginteract)); + if ~isempty(i), + go = input([num2str(length(i)) ' significant behavior x covariance interactions. Plot? ']); + if go + for ind = 1:length(i) + cluster_orthviews(cl(i(ind)),{[1 0 0]}); + cluster_orthviews(cl(j(ind)),{[0 0 1]},'add'); + h = axes('Position',[.55 .05 .42 .42]); + [b,t,p] = ancova(c.ancova_codes,c.dat(:,i(ind)),c.dat(:,j(ind)),1); + xlabel(cl(i(ind)).shorttitle); ylabel(cl(j(ind)).shorttitle); + title([]); + input('Save and press a key for the next one') + end + end + end +end + +if c.doancova, c.doancova = 'Yes'; else c.doancova = 'No'; end + +if ~any(c.ClusterSolution.pvals < .05) + go = input('No significant clustering. Proceed with analyses anyway?'); + if ~go, return, end +end + + + + + +% ----------------------------------------------------------------------- +% Get average correlations within and between clusters +% +% Apply this clustering solution to the clusters +% Get individual 'network' scores and use them as predictors of behavior +% in multiple regression +% ----------------------------------------------------------------------- + +c = nmdsfig_tools('apply_clusters',c); + +c = nmdsfig_tools('predict_behavior',c,'regions'); +c = nmdsfig_tools('predict_behavior',c,'classes'); + + + + +% ----------------------------------------------------------------------- +% brain plots +% ----------------------------------------------------------------------- +if isempty(overlay) + cont = input('Show brain slices? '); + if ~cont + diary off + + save nmds_output c cl + cd .. + return + end + + overlay = spm_get(1,'*img','Select overlay image'); +else + % we have overlay, implies we want slices + + cluster_orthviews_classes(cl,c.ClusterSolution.classes,overlay,'axial',0); + save_figure('cluster_slices_axial'); + + % cluster_orthviews_showcenters(cl,'coronal',overlay); + % save_figure('cluster_slices_coronal'); + % + % cluster_orthviews_showcenters(cl,'saggital',overlay); + % save_figure('cluster_slices_saggital'); +end + +diary off + +save nmds_output c cl +cd .. + +return + + + + +% ----------------------------------------------------------------------- + +% ----------------------------------------------------------------------- + + +% Sub-functions + + +% ----------------------------------------------------------------------- + +% ----------------------------------------------------------------------- + + + + + + +% ----------------------------------------------------------------------- +% Utility functions +% ----------------------------------------------------------------------- +function save_figure(myname) +f1 = gcf; +scn_export_papersetup; +saveas(f1,myname,'png'); +return + + +function f1 = create_wide_figure +scnsize = get(0,'ScreenSize'); +f1 = figure('position',[50 50 scnsize(3)-100 scnsize(4)/2],'color','white'); +return + + + +function [pthr,sig] = fdr_correct_pvals(p,r) + +psq = p; psq(find(eye(size(p,1)))) = 0; +psq = squareform(psq); +pthr = FDR(p,.05); +if isempty(pthr), pthr = 0; end + +sig = sign(r) .* (p < pthr); + +return + + diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig_glassbrain.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig_glassbrain.m new file mode 100644 index 00000000..c5ef62e1 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig_glassbrain.m @@ -0,0 +1,245 @@ +function [mov, phan, phan2] = cluster_nmdsfig_glassbrain(cl,classes,colors,sigmat,sigmat2, varargin) + % [mov, linehandles, linehandles2] = cluster_nmdsfig_glassbrain(cl,classes,colors,sigmat,sigmat2, varargin) + % + % tor wager, nov. 06 + % edited: bug fix, april 2007; minor update, april 2011 + % + % classes = c.ClusterSolution.classes; + % sigmat = c.STATS.sigmat; Heavy lines, matrix indicator + % sigmat2 = c.STATS.sigmat2; Lighter lines + % + % sigmat has signed values for significant relationships among clusters + % colors is cell array of colors for each class (text or rgb vector) + % + % var args: + % 'samefig' or 'existingfig' : do not create new figure + % 'blobs' to image blobs + % 'spheres' to image spheres (default) + % 'nobrain' to avoid creating any new brain surface objects; just use + % existing + % 'radius', followed by radius of sphere, or vector of radii + % 'movie', make movie + % 'straight', no bend + % + % Example: + % ------------------------------------------- + % Use output from mediation_direct_effects.m + % and nmdsfig_tools.m + % + % sigmat = (direct_mtx > 0)' + (direct_mtx > 0); + % cluster_nmdsfig_glassbrain(cl, c.ClusterSolution.classes, c.colors, sigmat, []); + % i = 1; cluster_nmdsfig_glassbrain(cl, c.ClusterSolution.classes == i, c.colors(i), sigmat, []); + % i = 2; cluster_nmdsfig_glassbrain(cl, c.ClusterSolution.classes == i, c.colors(i), sigmat, []); + % view(135, 30); lightRestoreSingle(gca) + % scn_export_papersetup; saveas(gcf,['Network' num2str(i)],'png'); + % + % Example 2: + % ------------------------------------------ + % Output from parcel_clusters + % create_figure('glass'); cluster_nmdsfig_glassbrain( ... + % class_clusters{1}, ... + % ones(length(class_clusters{1}), 1), ... + % NMDS.basecolors(1), NMDS.stats.fdrsig, [], 'blobs'); + % + % create_figure('nmdsfig_3d_glass'); cluster_nmdsfig_glassbrain( ... + % parcel_cl_flat, c.ClusterSolution.classes, ... + % c.colors, [], [], 'blobs', 'movie', 'samefig'); + + + newfig = 1; + blobstyle = 'spheres'; + makemovie = 0; + mov = []; + dobrainstruct = 1; + phan = []; + phan2 = []; + myradius = 10; + bendperc = .2; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case {'samefig','existingfig'}, newfig = 0; + + case 'blobs', blobstyle = 'blobs'; + case 'spheres', blobstyle = 'spheres'; + case 'noblobs', blobstyle = 'none'; + + case 'nobrain', dobrainstruct = 0; + + case 'radius', myradius = varargin{i + 1}; + + case 'movie', makemovie = 1; + case 'straight', bendperc = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + fprintf('cluster_nmdsfig_glassbrain.m: ') + fprintf('Making glass brain.\nBlob style is %s. Choices are ''blobs'' or ''spheres''\n', blobstyle); + + + if newfig + f1 = create_figure('nmdsfig_3d_glass'); + + else + f1 = findobj('Tag', 'nmdsfig_3d_glass'); + if ishandle(f1) + figure(f1); + else + %warning('Looking for existing figure tagged as nmdsfig_3d_glass, but can''t find it.'); + disp('Using current figure.'); + f1 = gcf; %create_figure('nmdsfig_3d_glass'); + end + end + + n = length(cl); + + if any(classes == 0) + % remove these + wh = find(classes == 0); + cl(wh) = []; + classes(wh) = []; + + if ~isempty(sigmat), sigmat(wh,:) = []; sigmat(:,wh) = []; end + if ~isempty(sigmat2), sigmat2(wh,:) = []; sigmat2(:,wh) = []; end + + n = length(cl); + end + + + % Get colors + for i = 1:n + if classes(i) ~= 0 + mycolor = colors(classes(i)); mycolor = mycolor{1}; + if ischar(mycolor), mycolor = mycolor(1); end + + plotcolors{i} = mycolor; + end + end + if makemovie + axis vis3d + %hh = addbrain; set(hh, 'FaceAlpha', .1); + set(gca,'XLim', [-70 70], 'YLim', [-130 90], 'ZLim', [-60 80]); + axis off + material dull + mov = movie_tools('still',[],.05); + end + + % Image blobs or spheres on brain + % ---------------------------------------------------------- + switch blobstyle + + + + case 'blobs' + + for i = 1:n + + cltmp = cl(i); + while cltmp.numVox < 20, cltmp = enlarge_cluster(cltmp); end + + if classes(i) ~= 0 + h(i) = imageCluster('cluster',cltmp,'color',plotcolors{i},'alpha',1); + + if makemovie, material dull; mov = movie_tools('still',mov,.1); end + end + + end + + case 'spheres' + h = cluster_image_sphere(cl, 'colors', plotcolors, 'radius', myradius); + + case 'none' + % do nothing + + otherwise + error('Unknown blobstyle argument. blobs or spheres.'); + end + + + + % light lines + % ---------------------------------------------------------- + if ~isempty(sigmat2) + phan2 = []; + for i = 1:n + for j = (i+1):n + issig = sigmat2(i,j); + + if issig && ~(sigmat(i,j)) % exclude ones that meet higher threshold + + if sign(issig) < 0 + out = nmdsfig_tools('connect3d',cl(i),cl(j),[.3 .3 .8], 2, bendperc); + + else + out = nmdsfig_tools('connect3d',cl(i),cl(j),[.3 .3 .3], 2, bendperc); + end + phan2(end+1) = out.h; + + end + end + end + drawnow + end + + % heavy lines + % ---------------------------------------------------------- + if ~isempty(sigmat) + phan = []; + for i = 1:n + for j = (i+1):n + issig = sigmat(i,j); + + if issig + + if sign(issig) < 0 + out = nmdsfig_tools('connect3d',cl(i),cl(j), 'b', 3, bendperc); + else + out = nmdsfig_tools('connect3d',cl(i),cl(j), 'k', 3, bendperc); + end + phan(end+1) = out.h; + + end + end + end + end + drawnow + + + + % other elements + % ---------------------------------------------------------- + + if dobrainstruct + bstem = addbrain('brainstem'); + cblm = addbrain('cerebellum'); + rt = addbrain('right'); + set(rt,'FaceColor',[.5 .5 .5]); + set(rt,'FaceAlpha',.1) + + lf = addbrain('left'); + set(lf,'FaceColor',[.5 .5 .5]); + set(lf,'FaceAlpha',.1) + + view(270,5) + + lighting gouraud + lightRestoreSingle(gca) + end + + axis image; axis vis3d + axis off + material dull + + if makemovie %input('Make movie? (1/0) '); + + mov = movie_tools('rotate',90,15,mov,4); + mov = movie_tools('rotate',0,90,mov,4); + %mov = close(mov); + movie2avi(mov,'cluster_nmdsfig_glass_movie.avi') + + end \ No newline at end of file diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp.m new file mode 100755 index 00000000..ab6e66f8 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp.m @@ -0,0 +1,372 @@ +function [clusters,subclusters] = cluster_princomp(clusters,varargin) +% function [clusters,subclusters] = cluster_princomp(clusters,[behavioral score vector],[corr flag],[plotflag],[locflag]) +% +% ALSO TRY: subcluster_montage(subclusters{1}) % to plot the output +% +% clusters is structure of clusters from tor_extract_rois.m +% behavioral vector is row vector of behavioral or other scores to correlate +% corr flag: *1 = work on correlations among voxels, 2 = work on covariance +% plotflag: *1 = yes, 0 = no. plots. +% locflag: 1 yes, *0 no; add XYZ voxel locations (scaled) to data submitted to clustering +% pushes voxels closer in space to be classified in the same cluster +% +% try this to test the program on random data: +% cl(1).all_data = randn(23,30);cl(1).numVox = 30;cl = cluster_princomp(cl,EXPT.behavior,1,1); +% cl(1).all_data(:,1:10) = cl(1).all_data(:,1:10) + 10; cl = cluster_princomp(cl,EXPT.behavior,1,1); +% cl(1).all_data(:,25:30) = cl(1).all_data(:,25:30) + repmat((EXPT.behavior .* 3)',1,6); +% cl(1).all_data(:,21:24) = cl(1).all_data(:,21:24) + repmat((1:23)',1,4); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% mean-center everything now: +% cl.PCA = []; cl.all_data - cl.all_data - repmat(mean(cl.all_data),size(cl.all_data,1),1); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% add another correlated group: +% cl.all_data(:,1:5) = cl.all_data(:,1:5) + repmat(rand(23,1)*5,1,5); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% +% if component scores are used and correlated with behavior, this means that the subjects +% tend to show the behavioral effect who also show the pattern associated with comp. x. +% this may mean high on a number of voxels, or high on some and low on others. +% the weights may be used to interpret what the components mean, and this can be done +% graphically. +% +% t-tests on component scores have ambiguous interpretations, because a high t-score +% may indicate negative values or close-to-zero values on some voxels. +% a component could have the interpretation, "high on this component means high on V1 +% and low on V2." +% +% classifying voxels is done using cluster analysis (hierarchical, centroid linkage) +% on the voxels (observations) using the PCA weights (eigenvectors) as variables. +% This lets the clustering algorithm work in the reduced variable space with dimensionality +% equal to the number of components. +% The max number of clusters is restricted based on the gradient of the eigenvalues in the PCA +% maxclusters = 1 + the number of eigenvalues with gradient at least 20% of the initial drop +% from 1 to 2 eigenvalues. +% +% Requires clustering library in Matlab. +% Robust option also uses the robust PCA algorithm RAPCA, +% created by: +% Hubert, M., Rousseeuw, P.J., Verboven, S. (2002), +% "A fast method for robust principal components with applications to chemometrics", by Mia Hubert, Peter J. Rousseeuw, +% Chemometrics and Intelligent Laboratory Systems, 60, 101-111. +% +% + +corrflag = 1; plotflag = 1; robustflag = 1; locflag = 0; +if length(varargin) > 1, corrflag = varargin{2};, end +if length(varargin) > 2, plotflag = varargin{3};, end +if length(varargin) > 3, locflag = varargin{4};, end + +for i = 1:length(clusters) + subclusters{i} = []; clusters(i).PCA = []; + + % check for NaNs and remove those voxels + tst = any(isnan(clusters(i).all_data),1); + clusters(i).all_data(:,tst) = []; + clusters(i).XYZ(:,tst) = []; + clusters(i).XYZmm(:,tst) = []; + clusters(i).Z(:,tst) = []; + if any(tst),disp(['Warning! Removed ' num2str(sum(tst)) ' voxels with NaN values.']),end + + a = clusters(i).all_data; + + % scale here, because robust pca doesn't use correlations, does it? + % but robust PCA seems to be unaffected by scale changes on some variables + if corrflag, a = scale(a);,end + + % if we choose to add the XYZ flag to add locations to clustering criteria + if locflag, + wfactor = round(size(a,1) ./ 3); % weight for loc; higher = more weight on location + xyztmp = repmat(scale(clusters(i).XYZ')',wfactor,1); % scale to make comparable to img values, + % but multiply by weighting factor + a = [a; xyztmp]; + end + + + if size(a,2) > 2 % must have 3 voxels to try clustering + + % ------------------------------------------------------------------------------- + % * All the real work is done here. Compute pc's + % ------------------------------------------------------------------------------- + + if ~robustflag + [clusters(i).PCA.pcomps,clusters(i).PCA.weights,clusters(i).PCA.eigval,clusters(i).PCA.class] = pc(a,corrflag); + + % automatically pick number of clusters, based on gradient in eigenvalues + g = abs(gradient(clusters(i).PCA.eigval)); + maxclusters = sum(g > g(1).*.2) + 1; + else + + % pick number of dimensions by hand + disp(' ') + disp([num2str(clusters(i).numVox) ' voxels in main cluster']) + fprintf(1,'%3.2f Observations on %3.2f voxels\n',size(a,1),size(a,2)) + fprintf(1,'Save at least 2 eigenvectors to do clustering'); + % doing this on a' means voxels are observations, conditions/subj scores are variables + % we'll use the scores, which has voxels as rows and components as columns, to classify + out = rapca(a'); + clusters(i).PCA.pcomps = out.T; + clusters(i).PCA.weights = out.P; + clusters(i).PCA.eigval = out.L; + maxclusters = length(out.L); + end + + % ------------------------------------------------------------------------------- + % * All the real work is done here. Classify + % ------------------------------------------------------------------------------- + + if size(clusters(1).all_data,1) > 12, + disp('More than 12 dimensions (observations per voxel) in original data - using eigenvectors to classify') + close all; try, pack, catch, end + % we pick the number of CLASSES separately by hand, because # components not a good indicator + % of how many classes there are + clusters(i).PCA.class = docluster(out.T',[],plotflag); + else + clusters(i).PCA.class = docluster(out.T',[],plotflag); + end + + % clean up if using locflag + clusters(i).PCA.locflag = locflag; + if locflag, + clusters(i).PCA.pcomps = clusters(i).PCA.pcomps(1:size(clusters(i).all_data,1),:); + %clusters(i).PCA.weights = clusters(i).PCA.weights(1:size(clusters(i).all_data,1),:); + %clusters(i).PCA.avgs = clusters(i).PCA.avgs(1:size(clusters(i).all_data,1),:); + end + + + % ------------------------------------------------------------------------------- + % for each group, separate into contiguous clusters + % ------------------------------------------------------------------------------- + + grps = unique(clusters(i).PCA.class(clusters(i).PCA.class~=0)); % values are component of origin + for j = 1:length(grps), + wh = find(clusters(i).PCA.class==grps(j)); + XYZ = clusters(i).XYZ(:,wh); + cl_index = spm_clusters(XYZ) ./ 100; + clusters(i).PCA.class(wh) = clusters(i).PCA.class(wh) + cl_index; + end + ngrps = length(unique(clusters(i).PCA.class)); + fprintf(1,'%3.0f contiguous clusters separated by class',ngrps) + + % ------------------------------------------------------------------------------- + % average within classes / contiguous regions + % ------------------------------------------------------------------------------- + + grps = unique(clusters(i).PCA.class(clusters(i).PCA.class~=0)); % values are component of origin + for j = 1:length(grps), + clusters(i).PCA.avgs(:,j) = mean(a(:,find(clusters(i).PCA.class==grps(j))),2);, + freq(j) = sum(clusters(i).PCA.class == grps(j)); + end + + disp(['Cluster ' num2str(i) ', ' num2str(clusters(i).numVox) ' voxels: ' num2str(size(clusters(i).PCA.pcomps,2)) ' components']) + fprintf(1,'\tMean\tEigval\tcorrel\t') + + % ------------------------------------------------------------------------------- + % * display each component and correlation with behavior + % ------------------------------------------------------------------------------- + + for j = 1:size(clusters(i).PCA.pcomps,2) + + %[H,P,CI,STATS] = TTEST(clusters(i).PCA.pcomps(:,j),0,.05,0); + % skip the t-test. t-tests on component scores don't make a lot of sense. + fprintf(1,'\n\t%3.3f\t%3.3f\t',mean(clusters(i).PCA.pcomps(:,j)),clusters(i).PCA.eigval(j)) + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.pcomps(:,j),varargin{1}); + co = co(1,2); + fprintf(1,'%3.3f\t',co) + end + end + + end + fprintf(1,'\n') + + % ------------------------------------------------------------------------------- + % * display classification info + % ------------------------------------------------------------------------------- + disp(['Classified into ' num2str(ngrps) ' groups:']) + fprintf(1,'\tClass\tVoxels\tMean\tt\tp\tcorrect. p\tcorrel\t') + + % for each component, test mean value and correlation with behavior + for j = 1:length(grps) + + [H,P,CI,STATS] = TTEST(clusters(i).PCA.avgs(:,j),0,.05,0); + fprintf(1,'\n\t%3.0f\t%3.0f\t%3.3f\t%3.3f\t%3.3f\t%3.3f\t',j,freq(j),mean(clusters(i).PCA.avgs(:,j)),STATS.tstat,P,P .* size(clusters(i).PCA.avgs,2)) + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.avgs(:,j),varargin{1}); + co = co(1,2); + fprintf(1,'%3.3f\t',co) + end + end + + end + fprintf(1,'\n') + + % ------------------------------------------------------------------------------- + % * Plot, if requested + % ------------------------------------------------------------------------------- + + if plotflag, + figure('Color','w'), subplot(1,3,1), imagesc(a), title(['Cl ' num2str(i) ': Data']), xlabel('Voxels'),ylabel('Subjects') + subplot(1,3,2), imagesc(clusters(i).PCA.weights'), title(['Weights (eigenvectors)']), xlabel('Voxels'),ylabel('Eigenvectors') + subplot(1,3,3), imagesc(clusters(i).PCA.pcomps), title(['Component scores (predictions)']), xlabel('Voxels'),ylabel('Subjects') + + a = [clusters(i).PCA.class' a']; a=sortrows(a,1); a = a(:,2:end)'; + figure;subplot 131; imagesc(a),title(['Cl ' num2str(i) ':Data sorted by class']), xlabel('Class'),ylabel('Subjects'), + xlab = [sort(clusters(i).PCA.class(clusters(i).PCA.class~=0)) clusters(i).PCA.class(clusters(i).PCA.class==0)]; + set(gca,'XTick',1:length(clusters(i).PCA.class)); set(gca,'XTickLabel',xlab) + subplot 132; imagesc(clusters(i).PCA.avgs),title('Class averages'), xlabel('Class'),ylabel('Subjects'), + subplot 133; if length(varargin) > 0, if ~isempty(varargin{1}), imagesc(varargin{1}'), title('Behavior'),end,end + + end + + else + disp(['Cluster ' num2str(i) ' has less than 3 voxels.']) + clusters(i).PCA.class = ones(1,clusters(i).numVox); + clusters(i).PCA.avgs = clusters(i).timeseries; + grps = 1; + if ~isfield(clusters,'correl'), clusters(1).correl = [];, end + end + + + % ------------------------------------------------------------------------------- + % * separate into subclusters, based on class membership + % ------------------------------------------------------------------------------- + + if ~isfield(clusters,'correl'), clusters(i).correl = [];, end + disp('Recomputing correlations and z-scores and saving in subclusters') + if ~isfield(clusters,'XYZ'), clusters(i).XYZ = ones(3,size(clusters(i).all_data,2));, end + if ~isfield(clusters,'Z'), clusters(i).Z = ones(1,size(clusters(i).XYZ,2));, end + clear subc + + for j = 1:length(grps) + + try + subc(j) = clusters(i); + catch + warning('clusters does not have all required fields.'); break + end + + wh = find(clusters(i).PCA.class == grps(j)); + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.avgs(:,j),varargin{1}); + subc(j).correl = co(1,2); + end + end + + if isfield(subc(j),'title'), subc(j).title = [subc(j).title '_SUBCL_' num2str(j)];, end + if isfield(subc(j),'name'), subc(j).name = [subc(j).name '_SUBCL_' num2str(j)];, end + if isfield(subc(j),'numVox'), subc(j).numVox = length(wh);, end + if isfield(subc(j),'Z'), subc(j).Z = subc(j).Z(wh);, end + if isfield(subc(j),'XYZmm'), subc(j).XYZmm = subc(j).XYZmm(:,wh);, end + if isfield(subc(j),'XYZ'), subc(j).XYZ = subc(j).XYZ(:,wh);, end + + if isfield(subc(j),'timeseries'), subc(j).timeseries = nanmean(subc(j).all_data(:,wh)')';, end + %if isfield(subc(j),'snr'), subc(j).snr = subc(j).snr(wh);, end + if isfield(subc(j),'center'), subc(j).center = center_of_mass(subc(j).XYZ,subc(j).Z);, end + if isfield(subc(j),'mm_center'), subc(j).mm_center = center_of_mass(subc(j).XYZmm,subc(j).Z);, end + + if isfield(subc(j),'all_data'), + subc(j).all_data = subc(j).all_data(:,wh);, + for k = 1:size(subc(j).all_data,2) + [H,P,CI,STATS] = ttest(subc(j).all_data(:,k),0,.05,0); + subc(j).Z(k) = spm_t2z(STATS.tstat,STATS.df); + end + end + + + end + + subclusters{i} = subc; + + +end + + +return + + + +function [b,v,d,class] = pc(a,corrflag) +% a is original matrix, b is principal components, v is eigenvectors +% (weights on columns, which = weights on voxels) +% class is classification of voxels into groups based on component loadings + +if corrflag, [v,d]=eig(corrcoef(a));, else, [v,d]=eig(cov(a));,end +b = (pinv(v) * a')' ./ repmat((diag(d)').^.5,size(a,1),1); +% i made this up: think of rptating each subject's scores (in cols of a') +% by the rotation matrix pinv(v), and normalizing by the sqrt of the eigenvalues +% pinv(v) and v are rotation matrices because det = 1, no shearing or dilation +% +% this appears to work to give scores as well +% both methods (above,below) are scaled versions of the splus factor scores +% the problem is that doing it two different ways in splus flips the signs +% of some components and not others (gui vs cmd line). + +%X = a; R = corrcoef(a); A = v * (d^.5); B = inv(R) * A; +%scores = X * B; +% X is data, A is factor loading matrix, B is factor score coeff matrix +% this method, from the text, and the one giving b above produce identical results + +A = v * (d^.5); + +b = fliplr(b); v = fliplr(v); A = fliplr(A); %scores = fliplr(scores); + +num = min(10,sum(diag(d) >= 1)); +b = b(:,1:num); v = v(:,1:num); A = A(:,1:num); +origd = diag(d); +d = diag(d)'; d= fliplr(d); d = d(1:num); + +if num == 0, warning('No eigenvalues above 1!');, origd, class = []; + +else + % classify each voxel into a group based on loading + % use A, which re-introduces the comp variance, because we + % want relationships with more variance to count more. + % This just doesn't work so hot. See docluster, below. + + %wh = A' == repmat(max(A'),size(A,2),1); + %for i = 1:size(wh,2), tmp = find(wh(:,i));, class(i) = tmp(1); end + %class(max(A') < .3) = 0; + +end + + +%figure;plot(b,'r'),hold on;plot(a,'k'), hold on; plot(mean(a,2),'g--'),legend({'eig' 'orig' 'avg'}) + +return + + + +function class = docluster(a,maxclusters,doplot) + + Y = pdist(a','euclid'); % transpose so the voxels are observations, eigenvectors the variables + Z = linkage(Y,'complete'); + if maxclusters > 1 + class = cluster(Z,maxclusters)'; + + if doplot, + dendrogram(Z,0); title('Dendrogram for clustering') + end + + else + dendrogram(Z,0); title('Dendrogram for clustering') + set(gcf,'Position',[10 601 800 500]) + maxclusters = input('Pick number of classes to save: '); + + if maxclusters == 1, + class = ones(1,size(a,2)); + else + class = cluster(Z,maxclusters)'; + end + + end + + + +return + \ No newline at end of file diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp2.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp2.m new file mode 100644 index 00000000..87f9dd61 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp2.m @@ -0,0 +1,229 @@ +% function [classes, stats] = cluster_princomp2(y, outcome, covariates, varargin) +% +% tor wager, in progress April 2007 +% +% Example: Predict a behavioral outcome from data in a set of clusters, +% with covariates +% ----------------------------------------------------------------------- +% for i = 1:length(cl), y(:,i) = cl(i).CONTRAST.indiv_data; end +% covariates = EXPT.cov(:,2:end); +% outcome = EXPT.cov(:,1); +% covnames = EXPT.covnames(2:end); +% [classes, stats] = cluster_princomp2(y, outcome, covariates, covnames, 'cl', cl); +% +% With mediation SETUP file, on rank data +% This way removes all covariates first, so they cannot be related to the +% outcome in the end. +% c = []; c.covs_nointerest = SETUP.X; c.outcome = SETUP.Y; doranks = 1; +% c = nmdsfig_tools('get_data_matrix',c,cl,'timeseries',1,[],doranks); +% [classes, stats] = cluster_princomp2(c.dat, c.outcome, c.covs_nointerest, covnames, 'cl', cl); + +function [classes, stats] = cluster_princomp2(y, outcome, covariates, covnames, varargin) + +% defaults +% ----------------------------------------------------------------------- +niter = 1000; + +alph = .05; % for stepwise regression +doplot = 1; +dosave = 0; + +%cnames = {'red' 'blue' 'yellow' 'green' 'orange' 'cyan' 'purple'}; +%colors = {[1 0 0] [0 0 1] [1 1 0] [0 1 0] [1 .5 0] [0 .5 1] [.5 0 1]}; +cnames = {'yellow' 'green' 'blue' 'red' 'cyan' 'magenta' 'black'}; +colors = {'yo' 'go' 'bo' 'ro' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + + + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'cl', cl = varargin{i+1}; + case 'plot', doplot = varargin{i+1}; + case 'save', dosave = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +nanvals = any(isnan(y)); +if any(nanvals) + fprintf(1,'Removing columns with NaNs : %3.0f \n', find(nanvals)); + + y(:, nanvals) = []; + if exist('cl', 'var'), cl(nanvals) = []; end + +end + +[nobs, nregions] = size(y); + +% principal components +% ----------------------------------------------------------------------- + +[pc, stats] = pca_npm(y, niter); +ncomps = sum(stats.sig); + +scn_export_papersetup(400); +set(gcf,'Tag', 'eigenvalues') + +if dosave + saveas(gcf,'prediction_pca','png'); +end +% scores = stats.score(:, stats.sig); + +% classification into classes +% ----------------------------------------------------------------------- +maxtotest = min(7, nregions); +[bestpval,classes]=testclustnew(pc,2:maxtotest,ncomps,niter,[],'keep','average'); +nclasses = max(classes); + +scn_export_papersetup(400); +set(gcf,'Tag', 'clustering') + +if dosave + saveas(gcf,'prediction_clustering','png'); +end + +% % for i = 2:nregions +% % classes = clusterdata(pc, 'linkage', 'average', 'maxclust', i); +% % s = silhouette(pc, classes); sil(i) = mean(s); +% % end +%nclasses = size(stats.class, 2); + + +class_averages = zeros(nobs, nclasses); +for i = 1:nclasses + wh = (classes == i); + class_averages(:, i) = mean(y(:, wh), 2); +end + +%STEP = stepwise_tor(stats.classdata(:,1:3), EXPT.cov(:,1)); + +% names +% ----------------------------------------------------------------------- +names = cell(1, nclasses); +for i = 1:nclasses + names{i} = ['Brain ' num2str(i) ' (' cnames{i} ')']; +end + +names = [names covnames]; + +% model: predictors +% ----------------------------------------------------------------------- +X = [class_averages covariates]; + +% stepwise regression +% ----------------------------------------------------------------------- + +STEP = stepwise_tor(X, outcome, names, alph); + +stats.classes = classes; +stats.class_averages = class_averages; +stats.names = names; + +wh = logical(STEP.inmodel); +X = X(:, wh); +names = names(:, wh); +stats.STEP = STEP; +stats.sig_X = X; +stats.sig_names = names; +stats.outcome = outcome; +stats.covariates = covariates; + +% plot +% ----------------------------------------------------------------------- +if doplot && exist('cl','var') + domontage = 0; + cluster_orthviews_classes(cl,classes,[],'axial',domontage); + spm_orthviews('Xhairs', 'on') + + scn_export_papersetup(600); + set(gcf,'Tag', 'slices') + if dosave + saveas(gcf,'prediction_slices','png'); + end + +end + +nsig = size(X,2); + +if doplot && nsig > 0 + + + tor_fig(1, nsig); + set(gcf,'Tag','scatterplots'); + scn_export_papersetup(300); + + for i = 1:nsig + subplot(1, nsig, i); + + prplot(stats.outcome,stats.sig_X,i,0,colors(i)); + title(names{i}) + xlabel(' '); + ylabel(' '); + + end + + if dosave + saveas(gcf,'prediction_scatterplots','png'); + + save cluster_princomp2_stats stats + + print_output; + end + +end + +end + +% see cluster_orthviews_classes +% % +% % addflag = 'newfig'; +% % +% % for i = 1:nclasses +% % +% % % regions in this group +% % wh = classes == i; %logical(stats.class(:,i)'); +% % +% % cluster_orthviews(cl(wh), colors(i), 'overlay',[], addflag, 'solid'); +% % addflag = 'add'; +% % +% % +% % end + +function print_output + +diary cluster_princomp2_output.txt + +fprintf('\n\ncluster_princomp2\n------------------------------\n\n'); +fprintf('\nObservations: %3.0f', nobs); +fprintf('\nRegions (var): %3.0f', nregions); +fprintf('\nIterations: %3.0f', niter); +fprintf('\nSignificant PCs: %3.0f', ncomps); +fprintf('\nSignificant classes: %3.0f', nclasses); +fprintf('\nSig. stepwise predictors: %3.0f\n', nsig); + +fprintf('\n') +STEP = stepwise_tor(X, outcome, names, alph); +STEP; + +if exist('cl', 'var') && isfield(cl, 'shorttitle') + for i = 1:nclasses + fprintf('\nClass %3.0f\n', i); + for j = find(classes == i) + fprintf('%s ', cl(j).shorttitle); + end + fprintf('\n') + end +end + +fprintf('\n------------------------------\n'); + +diary off + +end + + diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/discrim_plot.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/discrim_plot.m new file mode 100644 index 00000000..1082b552 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/discrim_plot.m @@ -0,0 +1,31 @@ +function y2 = discrim_plot(discf,y,varargin) +% y2 = discrim_plot(discf,y,[new figure],[column to do partial corr plot of]) +% +% Plots correlation or partial correlation +% Color codes by median split of y +% +% see also: +% cluster_discrim +% cluster_discrim_montage + +dofig = 1; dopr = 0; +if length(varargin) > 0, dofig = varargin{1};,end +if length(varargin) > 1, dopr = varargin{2};,end + +y2 = mediansplit(y); + +if dopr + [discf(:,1),y,r,p,rrob,prob] = partialcor(discf,y,dopr); + fprintf(1,'\nCalculating partial correlation.\n') +end + + +if dofig, figure('Color','w');,end + +plot_correlation_samefig(discf(:,1),y,[],'ko',0,1); +wh = find(y2>0); wh2 = find(y2<0); +hold on; plot(discf(wh2,1),y(wh2),'bo','MarkerSize',10,'LineWidth',2); +hold on; plot(discf(wh,1),y(wh),'ro','MarkerSize',10,'LineWidth',2); +xlabel('Discriminant function'); ylabel('Behavior'); + +return diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/nmdsfig_tools.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/nmdsfig_tools.m new file mode 100644 index 00000000..c83710eb --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/nmdsfig_tools.m @@ -0,0 +1,1441 @@ +% +% Tools for using nmds multidimensional scaling plots and data structure +% the data structure variable in this code is called c (arbitrarily) +% See cluster_nmdsfig for a function that uses many of these. +% +% By Tor Wager, Aug-Sept 2006 +% V 1.2, April 2007 +% +% --------------------------------------------------------------------- +% -- +% Get data matrix, rank [optional], adjust for nuisance [optional] +% ----------------------------------------------------------------------- +% c = nmdsfig_tools('get_data_matrix',c,cl,fldname,colm,contrasts,doranks) +% c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.avg',1,[],1); +% +% define input c.covs_nointerest with covariates to remove them. +% define input c.outcome to also remove covs from outcome. +% +% ----------------------------------------------------------------------- +% get correlations, significance, and number of dimensions +% If groups are entered and ANCOVA is specified, handle that +% ----------------------------------------------------------------------- +% c = nmdsfig_tools('get_correlations',c); +% +% if c.ancova_codes is input, does ancova analysis and returns stats +% +% ----------------------------------------------------------------------- +% get cluster solution for a set of data (objectsByDimsData or +% GroupSpace) +% ----------------------------------------------------------------------- +% c = cluster_solution(c, objectsByDimsData, n_cluster_range, niter, names) +% +% names is cell of one name per object +% to set up input, use get_correlations, and then this: +% [c.GroupSpace,c.obs,c.implied_dissim] = shepardplot(c.D,[]); +% c = nmdsfig_tools('cluster_solution',c, c.GroupSpace, 2:5, 1000, []); +% +% ----------------------------------------------------------------------- +% Make a connectivity figure; with optional add btwn vars and side plots +% Uses stats from correlations +% ----------------------------------------------------------------------- +% nmdsfig_tools('nmdsfig_plot',c, addbtwn, dosideplots, fillstr) +% addbtwn, dosideplots are 1 or 0, fillstr is 'fill' or something else +% uses colors in c.colors if it exists. +% ----------------------------------------------------------------------- +% Make a connectivity figure; WITH outcome var and covariates on figure +% ----------------------------------------------------------------------- +% DO THIS USING c STRUCTURE AFTER CLUSTER SOLUTION IS DONE +% nmdsfig_tools('nmdsfig_plot_withcovs',c, cl, dorank) +% needs fields: c.outcome c.covs_nointerest +% +% ----------------------------------------------------------------------- +% Apply a classification to a set of data and r matrix, get class avgs +% ----------------------------------------------------------------------- +% c = nmdsfig_tools('apply_clusters',c) +% c.class_avg_dat contains class averages +% needs c.ClusterSolution.classes, c.r +% +% Can turn off interactive naming +% c = nmdsfig_tools('apply_clusters',c, 0); +% +% ----------------------------------------------------------------------- +% Get class avgs from some other data matrix using an existing c struct +% ----------------------------------------------------------------------- +% class_avg_dat = nmdsfig_tools('get_class_avgs',c,dat) +% needs c.ClusterSolution.classes, dat (data matrix) +% +% ----------------------------------------------------------------------- +% Predict behavior from either class average data or regions +% ----------------------------------------------------------------------- +% c = nmdsfig_tools('predict_behavior',c,'classes'); +% c = nmdsfig_tools('predict_behavior',c,'regions'); +% +% ----------------------------------------------------------------------- +% Add lines to a graph +% ----------------------------------------------------------------------- +% handles = nmdsfig_tools('drawlines',coords,significance_mtx, bendval); +% specify pos/neg colors and styles: +% nmdsfig_tools('drawlines',c.GroupSpace,c_compare.sig,[1 0 0;0 0 1],{':',':'}, .05); +% +% ----------------------------------------------------------------------- +% % Remove lines from a graph +% ----------------------------------------------------------------------- +% nmdsfig_tools('removelines'); +% +% ----------------------------------------------------------------------- +% Add stars where stepwise params are significant +% ----------------------------------------------------------------------- +% nmdsfig_tools('stepwise_stars',c,'classes') +% nmdsfig_tools('stepwise_stars',c,'regions') +% +% or where robust regression with behavior is significant +% nmdsfig_tools('robust_correl_stars',c,wh_reg) +% +% ----------------------------------------------------------------------- +% Get unique colors for each object, but similar colors within a class +% ----------------------------------------------------------------------- +% colors = nmdsfig_tools('class_colors',classes,basecolors) +% +% +% ----------------------------------------------------------------------- +% Connect two points x and y in 3-D space with a straight or curved line +% ----------------------------------------------------------------------- +% out = nmdsfig_tools('connect3d',x,y,color,thickness,bendpercent) +% x and y are [x,y,z] coordinate triples, or clusters with mm_center field. +% out.h is handles, out.coords are also returned +% bendpercent can be scalar or [x y z] triple for bend in each direction +% +% Note: for 3-D graphs, use cluster_nmdsfig_glassbrain.m +% +% ----------------------------------------------------------------------- +% Connect two points x and y in 2-D space with a straight or curved line +% ----------------------------------------------------------------------- +% out = nmdsfig_tools('connect2d',x,y,color,thickness,bendpercent) +% x and y are [x,y] coordinate doubles, or clusters with mm_center field. +% out.h is handles, out.coords are also returned +% bendpercent can be scalar or [x y] pair for bend in each direction +% e.g., +% out = nmdsfig_tools('connect2d', [-.3 0], [-.1 .1], 'b', 3, .1); +% +% SEQUENCES FOR ANALYSIS +% and examples +% ----------------------------------------------------------------------- +% See also: cluster_nmdsfig, for a batch sequence +% ----------------------------------------------------------------------- +% Use average data across conditions to get basic structure: +% c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.avg',1,[],1); +% c = nmdsfig_tools('get_correlations',c); +% nmdsfig_tools('nmdsfig_plot',c, 0, 0); +% c = nmdsfig_tools('apply_clusters',c); +% c = nmdsfig_tools('predict_behavior',c,'regions'); +% c = nmdsfig_tools('predict_behavior',c,'classes'); +% +% Get new data and impose the class structure on it: +% c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.data',1:2,[1 -1],0); +% c = nmdsfig_tools('predict_behavior',c,'regions'); +% c = nmdsfig_tools('get_correlations',c); +% c = nmdsfig_tools('apply_clusters',c); +% c = nmdsfig_tools('predict_behavior',c,'classes') +% +% Create a basic figure based on the averages, +% and then use new data to get lines +% add stars where there are sig. correls with behavior +% c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.avg',1,[],1); % ranks, for robustness +% c = nmdsfig_tools('get_correlations',c); +%f1 = nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'sig',c.STATS.sigmat,'names',c.names,'sizescale',[4 16]); +% nmdsfig_tools('removelines'); +% c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.data',1:2,[1 -1],0); +% c = nmdsfig_tools('get_correlations',c); +% nmdsfig_tools('drawlines',c.GroupSpace,c.STATS.sigmat); +% nmdsfig_tools('robust_correl_stars',c,1); +% +% Get correl diffs among class averages +% get_data_matrix adjusts for covs of no interest, ranks, and centers +%c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.data',2,[],0); yON = c.dat; % get rank data +%c = nmdsfig_tools('get_data_matrix',c,cl,'CONTRAST.data',1,[],0); yOFF = c.dat; % get rank data +%yONavgs = nmdsfig_tools('get_class_avgs',c,yON); +%yOFFavgs = nmdsfig_tools('get_class_avgs',c,yOFF); +% ycomp_struct = correl_compare_dep(yONavgs,yOFFavgs,.05,0); % data already +% %ranked, so no need to redo +% +% +% Complete example for pre-appraisal mediation data: +% For set-up, see mediation_brain, mediation_brain_results and +% mediation_brain_results_detail +% +% c = []; c.covs_nointerest = [SETUP.X SETUP.covariates]; c.outcome = SETUP.Y; doranks = 1; +% for i = 1:length(cl), c.names{i} = cl(i).shorttitle; end +% c = nmdsfig_tools('get_data_matrix',c,cl,'timeseries',1,[],doranks); +% c = nmdsfig_tools('get_correlations',c); +% [c.GroupSpace,c.obs,c.implied_dissim] = shepardplot(c.D,[]); +% c = nmdsfig_tools('cluster_solution',c, c.GroupSpace, 2:5, 1000, []); +% c = nmdsfig_tools('apply_clusters',c); +% c = nmdsfig_tools('predict_behavior',c,'classes'); +% cluster_orthviews_classes(cl,c.ClusterSolution.classes, EXPT.overlay, 'sagittal', 0); +% nmdsfig_tools('nmdsfig_plot',c, 0, 0, 'fill'); +% c_complete = nmdsfig_tools('nmdsfig_plot_withcovs',c, cl, doranks) + +function [c, varargout] = nmdsfig_tools(meth,varargin) + + c = []; + + % First define required inputs + % ----------------------------------------------------------------------- + switch meth + + case 'get_data_matrix' + % This should execute the function: + % c = get_data_matrix(c,cl,fldname,colm,contrasts,doranks) + innames = {'c' 'cl' 'fldname' 'colm' 'contrasts' 'doranks'}; + + case 'nmdsfig_plot' + % nmdsfig_plot(c, addbtwn, dosideplots) + innames = {'c' 'addbtwn' 'dosideplots' 'fillstr'}; + + case 'nmdsfig_plot_withcovs' + innames = {'c' 'cl' 'doranks'}; + + + case {'apply_clusters','get_correlations'} + % c = apply_clusters(c) + innames = {'c', 'dointeractive', 'corrtype'}; + if length(varargin) < 2, varargin{2} = []; end % optional; do interactive 1/0 + if length(varargin) < 3, varargin{3} = 'r'; end % optional; corrtype, 'r' = Pearson't (default) + + case {'cluster_solution'} + innames = {'c' 'objectsByDimsData' 'n_cluster_range' 'niter' 'names'}; + + % c = cluster_solution(c, objectsByDimsData, n_cluster_range, niter, names); + + case 'get_class_avgs' + % class_avg_dat = nmdsfig_tools('get_class_avgs',c,dat) + innames = {'c' 'dat'}; + + case 'predict_behavior' + %c = predict_behavior(c,meth) + innames = {'c' 'predmeth'}; + + case 'drawlines' + % [hhp,hhn] = drawlines(pc,sigmat,sigcol,legmat,[color],[style]) + + innames = {'pc','sigmat','color','style','bendval'}; + % optional: color/style format strings + if length(varargin) < 3, varargin{3} = []; end % optional + if length(varargin) < 4, varargin{4} = []; end % optional + if length(varargin) < 5, varargin{5} = []; end % optional + if length(varargin) > 5, error('Too many input arguments.'); end + + case 'removelines' + innames = {}; + + case 'stepwise_stars' + innames = {'c' 'meth'}; + + case 'robust_correl_stars' + innames = {'c' 'wh_reg'}; + + case 'class_colors' + innames = {'classes' 'basecolors'}; + if length(varargin) < 2, varargin{2} = []; end % optional + + case 'connect3d' + % h = nmdsfig_tools('connect3d',x,y,color,thickness,bendpercent,[nsamples]) + innames = {'x' 'y' 'color','thickness','bendpercent','nsamples'}; + if length(varargin) < 6, varargin{6} = []; end % optional + + case 'connect2d' + % out = connect2d(x,y,color,thickness,bendpercent,varargin) + innames = {'x' 'y' 'color','thickness','bendpercent','nsamples'}; + if length(varargin) < 6, varargin{6} = []; end % optional + + otherwise + error('Unknown method string.') + end + + % this generic code block reads varargin into variables for + % do it this way partly to keep track of vars by name + % ----------------------------------------------------------------------- + if length(varargin) < length(innames) + error('Wrong number of input arguments. Check help on method you are using.') + end + for i = 1:length(innames) + eval([innames{i} ' = varargin{' num2str(i) '};']); + end + + % Now execute subfunction + % ----------------------------------------------------------------------- + str = ['c = ' meth '(']; + for i = 1:length(varargin) + if i ~= 1, str = [str ',']; end + str = [str innames{i}]; + end + str = [str ');']; + + %disp(str) + eval(str) + + + return + + + + + + + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + % + % + % Sub-functions + % + % + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + % get data from selected field + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function c = get_data_matrix(c,cl,fldname,colm,contrasts,doranks) + + if ~isfield(c, 'outcome') + c.outcome = []; + end + + if ~isfield(c, 'covs_nointerest') + c.covs_nointerest = []; + end + + dat = []; + for i = 1:length(cl) + str = ['dat1 = cl(i).' fldname '(:,[' num2str(colm) ']);']; + eval(str) + + if ~isempty(contrasts), dat1 = dat1 * contrasts'; end + + if doranks, dat1 = rankdata(dat1); end + dat=[dat dat1]; + end + + if doranks + disp('Returning rank data in c.dat'); + c.dat_descrip = 'Dat and outcome data: Ranked'; + + if ~isempty(c.outcome) + disp('Ranking outcome data.') + c.outcome = rankdata(c.outcome); + end + + if ~isempty(c.covs_nointerest) + disp('Ranking covariate data.') + for j = 1:size(c.covs_nointerest, 2) + c.covs_nointerest(:,j) = rankdata(c.covs_nointerest(:,j)); + end + end + + + else + disp('Returning data in c.dat'); + c.dat_descrip = 'Dat and outcome data: Original (not ranked) '; + end + + c.doranks = doranks; + %if length(varargin) > 0,dat = [c.outcome dat];end + + % remove covariates of no interest, if any + % if data is not rank data, use robust regression + % otherwise, use OLS + % ----------------------------------------------------------------------- + + if isempty(c.covs_nointerest) + % do nothing + disp('nmdsfig_tools get_data_matrix : no covariates entered.') + c.dat_descrip = [c.dat_descrip ' raw (not adjusted)']; + else + disp('nmdsfig_tools get_data_matrix : Removing covariate(s) of no interest (using IRLS).') + c.dat_descrip = [c.dat_descrip ' Adjusted for covariates with IRLS']; + if ~isfield(c, 'outcome'), c.outcome = []; end + + if ~isempty(c.outcome) + % partialcorr gets adjusted x, y, and correls; here just use it to + % get adjusted x + %c.outcome = partialcor([c.outcome c.covs_nointerest],ones(size(c.outcome,1),1),1); + c.outcome = adjust_y(c.covs_nointerest,c.outcome,doranks); + end + + for i = 1:size(dat,2) + %dat(:,i) = partialcor([dat(:,i) c.covs_nointerest],ones(size(dat,1),1),1); + [dat(:,i), b(:, i)] = adjust_y(c.covs_nointerest,dat(:,i),doranks); + end + + c.nuisance_betas = b; + end + + c.dat = dat; + + + return + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % % Adjust x and y for regressors of no interest + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- +function [r, b] = adjust_y(x,y,doranks) + + if doranks + X = [x ones(size(x,1),1)]; + b = pinv(X) * y; + r = y - X * b; + else + [b,stats]=robustfit(x,y); + r = stats.resid; + end + + return + + + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Make an NMDSfig figure from a c structure + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function figname = nmdsfig_plot(c, addbtwn, dosideplots, fillstr) + + % Required fields: (See cluster_nmdsfig, for example) + % requires c. ... + % STATS.sigmat/sigmat2/siginteract + % outcome + % ClusterSolution.classes + % names + % GroupSpace (stim. coords) + % X + % r + % + % btwn_coords (for addbtwn option) + % factor_scores (for sideplots option) + + f1 = create_figure; + + colors = {'yo' 'bo' 'go' 'ro' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + + if isfield(c,'colors'), colors = c.colors; end + + + % for side plots + %subplot('position',[0.3 0.3 0.5 0.6]); + if ~isfield(c,'outcome'), c.outcome = []; end + + if ~isempty(c.outcome) % plot interactions between beh and regional covariance + if isfield(c.STATS,'siginteract') + % we have a group x cov interaction in ANCOVA + nmdsfig(GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',c.STATS.sigmat, ... + 'legend',{'Pos' 'Neg' 'Pos x Beh' 'Neg x Beh'},'sig2',c.STATS.siginteract, ... + 'colors', colors, fillstr); + figname = 'cluster_nmdsfig_ancova_interact'; + else + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',c.STATS.sigmat,'sig2', ... + c.STATS.sigmat2,'legend',{'Pos' 'Neg'},'sizescale',[4 16], ... + 'colors', colors, fillstr); + figname = 'cluster_nmdsfig_mdsfig'; + + end + else + nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',c.STATS.sigmat,'sig2', ... + c.STATS.sigmat2,'legend',{'Pos' 'Neg'},'sizescale',[4 16], ... + 'colors', colors, fillstr); + figname = 'cluster_nmdsfig_mdsfig'; + end + + try + nmdsfig_legend(c.GroupSpace,c.r); + catch + end + + save_figure('cluster_nmdsfig_legend',550); + + % behavior + if addbtwn + if ~isfield(c, 'btwn_coords'), error('You must enter c.btwn_coords to use btwn-subjects breakdown plot. See cluster_nmdsfig.'); end + + hold on; plot(c.btwn_coords(1),c.btwn_coords(2),'ko','MarkerSize',12,'LineWidth',3); + end + + + % MDSfig sideplots + % ----------------------------------------------------------------------- + + dosideplots = 0; + + if dosideplots + + % X-axis subplot: Component 1 + subplot('position',[0.25 0.1 0.6 0.1]); hold on; + set(gca,'FontSize',16); + fact = c.factor_scores(:,1); + if ~isempty(c.outcome), + fact=[fact c.outcome]; fact=sortrows(fact,2); + xlab='Activation score for each participant: Component 1'; + + lo = find(fact(:,2)median(fact(:,2))); + + plot(lo,fact(lo,1),'ko','LineWidth',2); + plot(hi,fact(hi,1),'k^','LineWidth',2); + set(gca,'XTick',[1 max(lo)+.5 size(fact,1)],'XTickLabel',{'Lowest' 'Median' 'Highest'}) + title(xlab) + xlabel('Behavior'); + ylabel('Score') + else + xlab='Scores on Component 1'; + plot(fact(:,1),'ko','LineWidth',2); + title(xlab); xlabel('Index'); ylabel('Score'); + end + + % Y-axis subplot: Component 2 + subplot('position',[0.1 0.3 0.1 0.6]); hold on; + set(gca,'FontSize',16); + fact = c.factor_scores(:,2); + if ~isempty(c.outcome), + fact=[fact c.outcome]; fact=sortrows(fact,2); + xlab='Component 2'; + + lo = find(fact(:,2)median(fact(:,2))); + + plot(fact(lo,1),lo,'ko','LineWidth',2); + plot(fact(hi,1),hi,'k^','LineWidth',2); + set(gca,'YTick',[1 max(lo)+.5 size(fact,1)],'YTickLabel',{'Lowest' 'Median' 'Highest'}) + title(xlab) + ylabel('Behavior'); + xlabel('Score') + else + xlab='Scores on Component 1'; + plot(fact(:,1),'ko','LineWidth',2); + title(xlab); ylabel('Index'); xlabel('Scores: Comp. 2') + end + + end % if dosideplots + + figure(f1) + axis off + drawnow + + save_figure('cluster_nmdsfig',500); + + return + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Re-plot an nmds figure with outcome and covs added (but not part of + % clustering solution); Use existing completed c structure + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- +function c_complete = nmdsfig_plot_withcovs(c, cl, doranks) + + extravars = [c.outcome c.covs_nointerest]; + nextra = size(extravars, 2); + + % get data again, and do not remove covariates + c_complete = c; + c_complete = rmfield(c_complete, 'covs_nointerest'); + c_complete = nmdsfig_tools('get_data_matrix',c_complete,cl,'timeseries',1,[],doranks); + + disp('Creating c_complete.dat with [Outcome Covs Vars]'); + c_complete.dat = [extravars c_complete.dat]; + + c_complete = nmdsfig_tools('get_correlations',c_complete); + [c_complete.GroupSpace,c_complete.obs,c_complete.implied_dissim] = shepardplot(c_complete.D,[]); + + c_complete.ClusterSolution = c.ClusterSolution; + + cl_id = max(c_complete.ClusterSolution.classes) + 1; % class ID for extra points + + c_complete.ClusterSolution.classes = [cl_id .* ones(nextra, 1); c_complete.ClusterSolution.classes]; + + extranames = {}; + if ~isempty(c.outcome), extranames{1} = 'Outcome'; end + if ~isempty(c.covs_nointerest) + for i = 1:size(c.covs_nointerest, 2) + extranames{end + 1} = ['Cov' num2str(i)]; + end + end + + c_complete.names = [extranames c.names]; + + nmdsfig_tools('nmdsfig_plot',c_complete, 0, 0, 'nofill'); + + saveas(gcf,'NMDS_complete_flat_plot_orig','png'); + + % direct effects, but should exclude OUTCOME? + [c_complete.direct_mtx, c_complete.mediated_mtx, c_complete.mediators] = matrix_direct_effects(c_complete.STATS.sigmat2, c_complete.dat); + + + + nmdsfig_tools('removelines'); + nmdsfig_tools('drawlines',c_complete.GroupSpace, c_complete.direct_mtx); + saveas(gcf,'NMDS_complete_flat_plot_direct','png'); + + + + + return + + + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Get correlation matrix, depending on ancova status + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + +function c = get_correlations(c, varargin) + + disp('Saving correlation matrix in c.r') + + if ~isfield(c, 'ancova_codes'), c.ancova_codes = []; end + + corrtype = 'r'; + if length(varargin) > 1, corrtype = varargin{2}; end + if ~strcmp(corrtype, 'r') && ~isempty(c.ancova_codes) + warning('nmdsfig_tools:incompat_inputs', 'Corr type cannot be entered with ANCOVA covariates.'); + end + + disp(['Correlation/association metric: ' corrtype]) + + if isempty(c.ancova_codes) + doancova = 0; + % no between covariates, just use correlations + if strcmp(corrtype, 'r') + [r,p]=corrcoef(c.dat); r(r>.9999) = 1; + else + [r,t,p] = correlation(corrtype, c.dat); + end + + c.STATS.corrtype = corrtype; + + [pthr,sig] = fdr_correct_pvals(p,r); + + c.STATS.p_thr = pthr; + + else + %doancova = input('Use covariate scores as covariate in ANCOVA? (inter-region correls then done w/i group): 1/0: '); + doancova = 1; + end + + if doancova + + disp('Using ANCOVA to model between-subjects effects') + disp('Inter-region correlations within groups; interaction tests group x brain covariance interaction') + % main effect of grp in this case is hi vs lo behavior on region Y (brain act.) after + % controlling for region X -- a bit hard to interpret + + if length(unique(c.ancova_codes) > 2), + disp('Binarizing ANCOVA code regressor into two groups.'); + c.ancova_codes = mediansplit(c.ancova_codes); + end + + [b,t,p] = ancova(c.ancova_codes,[],c.dat); % first cell is grp (diff), 2nd is r, 3rd is r diff x group + pall = p; % save all slopes + + r = b{2}; % overall slope + r = r + eye(size(r)); % add 1 to diagonal + p = pall{2}; + [c.STATS.pthr(1),sig] = fdr_correct_pvals(p,r); + + ri = b{3}; % behavior x covariance interaction + %ri = ri + eye(size(ri)); % add 1 to diagonal + pi = pall{3}; + [c.STATS.pthr(2),sigi] = fdr_correct_pvals(pi,ri); + + c.STATS.siginteract = sigi; + c.STATS.b = b; c.STATS.t = t; c.STATS.p = pall; + c.STATS.btp_descrip = 'first cell is grp (diff), 2nd is r, 3rd is r diff x group. betas, t-vals, p-vals'; + + else + c.STATS.p = p; + end + + c.doancova = doancova; + c.r = r; + c.STATS.sigmat = sig; + c.STATS.sigmat2 = (p < .05) .* sign(r); + c.STATS.p_descrip = 'sig. p-values are FDR corrected.'; + c.STATS.p_descrip2 = 'sigmat2 is p < .05 uncorrected.'; + + fprintf('Positive connections (q < .05 FDR): %3.0f\n', sum(c.r(find(c.STATS.sigmat(:))) > 0)) + fprintf('Negative connections (q < .05 FDR): %3.0f\n', sum(c.r(find(c.STATS.sigmat(:))) < 0)) + + disp('Getting dissimilarity matrix from correlation matrix, saving in c.D') + % scale correlations (or beta weights) so that they are a similarity matrix between 0 and 1. + c.D = (1 - c.r) ./ 2; + %c.D=distosim(c.D); + % Make sure it's a dissim. matrix + c.D=simtodis(c.D); + + return + + + + +function [pthr,sig] = fdr_correct_pvals(p,r) + + psq = p; psq(find(eye(size(p,1)))) = 0; + psq = squareform(psq); + pthr = FDR(p,.05); + if isempty(pthr), pthr = 0; end + + sig = sign(r) .* (p < pthr); + + return + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Find clusuter solution + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + +function c = cluster_solution(c, objectsByDimsData, n_cluster_range, niter, names) + % ----------------------------------------------------------------------- + % testcluster: + % + % Permute objects in space, get null hypothesis cluster quality, and test + % observed cluster solution against this. + % ----------------------------------------------------------------------- + + % pval is p-values for each number of clusters tested + % classes = class ("network") assignments for best clustering solution + + c.ndims = size(objectsByDimsData, 2); + c.GroupSpace = objectsByDimsData; + + [c.ClusterSolution.pvals, ... + c.ClusterSolution.classes, ... + c.ClusterSolution.classnames ... + c.ClusterSolution.X, ... + c.ClusterSolution.include, ... + c.ClusterSolution.names ] = ... + testclustnew(objectsByDimsData, n_cluster_range, c.ndims, niter, names, 'keep', 'average'); + + c.ClusterSolution.niter = niter; + + return + + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Apply clusuter solution and get class average data + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + +function c = apply_clusters(c, varargin) + + dointeractive = 1; + if length(varargin) > 0, dointeractive = varargin{1}; end + % + % requires c. ... + % ClusterSolution.classes + % + % uses c. ... + % outcome + % + % creates + % c.ClusterSolution.classnames + % c.class_avg_data + + % add names of regions if missing + if ~isfield(c, 'names') || isempty(c.names) || length(c.names) < size(c.dat, 2) + disp('Missing or invalid region names; adding.'); + for ii = 1:size(c.dat, 2) + c.names{ii} = ['V' num2str(ii)]; + end + end + + % name 'networks' + % ----------------------------------------------------------------------- + + if ~isfield(c,'ClusterSolution'), error('You need a ClusterSolution field first! ... try testclustnew'), end + + if ~isfield(c,'dat'), error('Enter data for each variable in .dat field first'), end + + if ~isfield(c,'colors') || isempty(c.colors) + %c.colors = {'yo' 'bo' 'go' 'ro' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + c.colors = {[1 1 0] [0 0 1] [0 1 0] [1 0 0] [1 .5 0] [0 1 1] [1 0 1] [.5 1 0] [0 .5 1] [0 1 .5] [1 0 .5]}; + end + + if ~isfield(c,'GroupSpace') + disp(c) + fieldnm = input('Type name of GroupSpace field with MCA/MDS/PCA coordinates: ', 's'); + myGroupSpace = c.(fieldnm); + else + myGroupSpace = c.GroupSpace; + end + + if ~isfield(c,'doancova') + c.doancova = 0; + end + + classnames = []; + if isfield(c,'APPLY_CLUSTER') && isfield(c.APPLY_CLUSTER,'names') + classnames = c.APPLY_CLUSTER.names; + end + + if (isempty(classnames) || length(classnames) < max(c.ClusterSolution.classes)) && (isempty(dointeractive) || dointeractive) + donames = input('Name Clusters (Classes)? (1/0) '); + + if donames + for i = 1:max(c.ClusterSolution.classes) + disp(['Network ' num2str(i)]) + tmp = find(c.ClusterSolution.classes == i); + for j = 1:length(tmp) + fprintf(1,'%s . ',c.names{tmp(j)}); + if j > 4, fprintf(1,'\n'),end + end + fprintf(1,'\n') + classnames{i} = input('Name this network: ','s'); + end + + else + for i = 1:max(c.ClusterSolution.classes) + classnames{i} = ['Net' num2str(i)]; + end + end + end + + % if we have a between-subjects covariate, enter it, otherwise empty + if ~isfield(c,'outcome'), c.outcome = []; end + + % Apply this clustering solution to the clusters + % Get individual 'network' scores and use them as predictors of behavior + % in multiple regression + % ----------------------------------------------------------------------- + + % now run for the average across task conditions + % average correlations within and between networks, and test + if isfield(c, 'r') + mycorrmtx = c.r; disp('Using .r field for correlation matrix.'); + elseif isfield(c, 'corr') + mycorrmtx = c.corr; disp('Using .corr field for correlation matrix.'); + else + error('Need .r or .corr field in input structure.') + end + + c.APPLY_CLUSTER = apply_cluster_solution(c.ClusterSolution.classes,... + mycorrmtx,... + 'names',classnames,'bcov',c.outcome, 'n', size(c.dat, 1), 'dointeractive', dointeractive); + + c.APPLY_CLUSTER.names = classnames; + + % matrix plot is not meaningful here; close + close + %save_figure('cluster_nmdsfig_rmatrix',400); + + % standardize variables? No, let's let low variance vars contribute less + % to avgs + + c.class_avg_dat = get_class_avgs(c,c.dat); + [c.APPLY_CLUSTER.avgr,c.APPLY_CLUSTER.avgp] = corrcoef(c.class_avg_dat); + c.APPLY_CLUSTER.avgsig = c.APPLY_CLUSTER.avgp < .055 .* sign(c.APPLY_CLUSTER.avgr); + + % get class (network) average stimulus locations (group space) and plot + % ----------------------------------------------------------------------- + + Gs = myGroupSpace(:,1:c.ndims); + cla = c.ClusterSolution.classes; + for i = 1:max(cla) + wh = find(cla == i); + if length(wh)>1; + classGs(i,:) = mean(Gs(wh,:)); + else + classGs(i,:) = Gs(wh,:); + end + end + c.APPLY_CLUSTER.Gs = classGs; + c.APPLY_CLUSTER.classes = 1:max(cla); + + % significance of r among class averages (done above) + % [r,p]=corrcoef(c.class_avg_dat); r(r>.9999) = 1; + % sig = (p < .05) .* sign(r); + %[pthr,sig] = fdr_correct_pvals(p,r); + + % Figures + if strcmp(c.doancova,'Yes'), create_wide_figure; subplot(1,2,1); else create_figure; end + set(gca,'FontSize',14) + nmdsfig(classGs(:,1:2),'names',c.APPLY_CLUSTER.names,'classes',c.APPLY_CLUSTER.classes, ... + 'sig', c.APPLY_CLUSTER.avgsig, ... %c.APPLY_CLUSTER.group_stats.sigu,'sizescale',[4 16]); + 'sizescale',[4 16],'colors',c.colors); + + title(['Correlations among class averages (p <= .05 two-tailed)']) + + if strcmp(c.doancova,'Yes'), subplot(1,2,2); + set(gca,'FontSize',14) + mdsfig(classGs(:,1:2),classnames,cla,c.STATS.siginteract); + title(['Group by cov interaction: ANCOVA']) + end + + save_figure('cluster_nmdsfig_class_lines_uncor',550); + + return + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Get class average data + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function class_avg_dat = get_class_avgs(c,dat) + + class_avg_dat = []; + + [nobs,nvars] = size(dat); + nclasses = length(c.ClusterSolution.classes); + if nvars ~= nclasses, error('Number of classes does not match num. columns in dat'); end + + for i = 1:max(c.ClusterSolution.classes) + wh = find(c.ClusterSolution.classes == i); + if length(wh) > 1 + class_avg_dat = [class_avg_dat nanmean(dat(:,wh)')']; + else + class_avg_dat = [class_avg_dat dat(:,wh)]; + end + end + + return + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Stepwise regressions on behavior + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function c = predict_behavior(c,meth) + % needs: + % c. + % outcome + % meth is 'classes' or 'regions' + + if isempty(c.outcome) + disp('Between-subjects c.outcome is empty. No correlations with behavior run.'); + return + end + + switch meth + case 'classes' + + % Add logistic regression for categorical 1/0 DVs! + % see also stepwise_tor.m which does this stuff. + disp(' ') + disp('------------------------------------------') + disp('Using networks to predict behavior') + disp('------------------------------------------') + disp(' ') + str = correlation_to_text([c.outcome c.class_avg_dat],[],[{'Behav'} c.APPLY_CLUSTER.class_names]); + + %[c.ClusterSolution.STEPWISE.b,c.ClusterSolution.STEPWISE.se,c.ClusterSolution.STEPWISE.pval, ... + %c.ClusterSolution.STEPWISE.inmodel,stats] = ... + %stepwisefit(c.class_avg_dat,c.outcome); + disp(' ') + c.class_STEPWISE = stepwise_tor(c.class_avg_dat,c.outcome,c.APPLY_CLUSTER.class_names); + STEP = c.class_STEPWISE; + names = c.APPLY_CLUSTER.class_names; + + case 'regions' + + %N = stats.dfe+stats.df0; + %r2 = (stats.SStotal-stats.SSresid) ./ stats.SStotal; + %adjr2 = 1 - (1-r2)*((N - 1) ./ (N - stats.df0 - 1)); + %fprintf(1,'Omnibus F(%3.0f,%3.0f) = %3.2f, RMSE = %3.2f, p = %3.4f, Adj R^2 = %3.2f\n', ... + %stats.df0,stats.dfe,stats.fstat, ... + %stats.rmse,stats.pval,adjr2); + %stats.adjr2 = adjr2; + %c.ClusterSolution.STEPWISE.stats = stats; + + disp(' ') + disp('------------------------------------------') + disp('Using individual regions to predict behavior'), + disp('------------------------------------------') + disp(' ') + str = correlation_to_text([c.outcome c.dat],[],[{'Behav'} c.names],1); + disp(' ') + c.indiv_STEPWISE = stepwise_tor(c.dat,c.outcome,c.names); + STEP = c.indiv_STEPWISE; + names = c.names; + + otherwise error('Unknown method. Valid = classes or regions') + end + + % ------------------------------------------ + % make plots + % ------------------------------------------ + doplot = 1; + + if ~isfield(c,'colors') + c.colors = {'yo' 'bo' 'go' 'ro' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + end + while length(c.colors) < size(c.dat, 2), c.colors = [c.colors c.colors]; end + + wh = logical(STEP.inmodel); + + switch meth + case 'classes' + X = c.class_avg_dat(:, wh); + case 'regions' + X = c.dat(:, wh); + end + + + names = names(:, wh); + + colors_to_use = c.colors(wh); + + nsig = size(X,2); + + if doplot && nsig > 0 + + disp('Plotting partial correlation scatterplots for significant variables.'); + + % Check for existing figure, and create if necessary + create_figure('class_scatterplots', 1, nsig); + + scn_export_papersetup(300); + + for i = 1:nsig + subplot(1, nsig, i); + + prplot(c.outcome, X, i, 0, colors_to_use(i)); + title(names{i}) + xlabel(' '); + ylabel(' '); + + end + + end + + + return + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Draw lines on nmdsfig graph + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function line_handles = drawlines(pc,sigmat,varargin) + % function handles_out = drawlines(pc,sigmat,[color],[style],[bendval]) + % + % pc is stim coords + % sigmat is matrix of which lines to draw + % sigcol is multiplier to scale line width (0 is fixed width) + % sigcol is a scalar between zero and one, lower is 'more salient' + % legmat is not used now (for legend stuff) + % + % Example: + % [hh3,hh4] = drawlines(pc,wh,sigcol(i),legmat,[.5 .5 .5; 0 1 1],[{':'} + % {':'}]); + + % sigcol is a scalar between zero and one, lower is 'more salient' + sigcol = 0; + hold on + + % linew + lw = 2; + + color(1,:) = [0 0 0]; % first line color, 'positive' + color(2,:) = [0 1 1]; % second line color, 'negative' + style{1} = '-'; % first line style + style{2} = '-'; % second line style + + if length(varargin) > 0 && ~isempty(varargin{1}), color = varargin{1}; end + if length(varargin) > 1 && ~isempty(varargin{2}), style = varargin{2}; sigcol = 0; end + + bendval = .1; + if length(varargin) > 2 && ~isempty(varargin{3}), bendval = varargin{3}; end + + hhp=[];hhn=[]; + for i = 1 : size(pc,1) + for j = i+1 : size(pc,1) + + if sigmat(i,j) > 0 || sigmat(i,j) < 0 + out = nmdsfig_tools('connect2d', [pc(i,1) pc(i,2)], [pc(j,1) pc(j,2)], 'b', lw - (lw*sigcol)-1, bendval); + + if sigmat(i,j) > 0 + hhp(end+1) = out.h; + %hhp(end+1) = line([pc(i,1) pc(j,1)],[pc(i,2) pc(j,2)]); + set(hhp,'Color',color(1,:) + [1 1 1] * abs(sigcol),'LineStyle',style{1},'LineWidth',lw - (lw*sigcol)-1) + elseif sigmat(i,j) < 0 + hhn(end+1) = out.h; + %hhn(end+1) = line([pc(i,1) pc(j,1)],[pc(i,2) pc(j,2)]); + set(hhn,'Color',color(2,:) + abs([sigcol sigcol 0]),'LineStyle',style{2},'LineWidth',lw - (lw*sigcol)-1) + end + + end + end + end + %legend ([hhp hhn],legmat); + + % store in gui data for later deletion, etc. + figh = gcf; + linehandles = [hhp hhn]; + data = guidata(figh); + if ~isfield(data,'linehandles') + data.linehandles = linehandles; + else + data.linehandles = [data.linehandles linehandles]; + end + guidata(figh,data); + + line_handles.hhp = hhp; + line_handles.hhn = hhn; + + return + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Remove lines from nmdsfig plot + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- +function doneok = removelines + + doneok = 0; + figh = findobj('Tag', 'nmdsfig'); + data = guidata(figh); + if isfield(data,'linehandles') && ~isempty(data.linehandles) + delete(data.linehandles(ishandle(data.linehandles))) + data.linehandles = []; + guidata(figh,data); + doneok = 1; + else + disp('Cannot find line handles to remove in guidata. Doing nothing.') + end + + return + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Put stars on figure where there are sig. relations in stepwise regression + % also do stars for robust correlations in glm (sep. function) + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- +function sig = stepwise_stars(c,meth) + + switch meth + case 'regions' + myfield = 'indiv_STEPWISE'; + myf2 = 'GroupSpace'; + + case 'classes' + myfield = 'class_STEPWISE'; + myf2 = 'APPLY_CLUSTER.Gs'; + + otherwise + error('Unknown method. regions or classes.') + end + + sig = c.(myfield).inmodel' .* c.(myfield).t; + plot_stars(c,sig,myf2); + + return + + +function sig = robust_correl_stars(c,wh_reg) + + [b,t,p,sig,F,fp,fsig,stat] = robust_reg_matrix([c.X ones(size(c.X,1),1)],c.dat,1); + sig = sig(wh_reg,:)' .* t(wh_reg,:)'; + plot_stars(c,sig,'GroupSpace'); + + return + + +function plot_stars(c,sig,myfield) + + wh = find(sig); + nsig = length(wh); + + if nsig == 0 + disp('No significant results to plot.') + return + end + + % coordinates + pc = c.(myfield); + pc = pc(wh,1:2); + signval = (sig(wh) > 0) + 1; % 1 for negative, 2 for positive + colors = {'bp' 'rp'}; + + for i = 1:nsig + plot(pc(i,1),pc(i,2),colors{signval(i)},'MarkerSize',18,'LineWidth',3); + end + + return + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Get unique colors for each object, but similar colors within a class + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function colors = class_colors(classes,basecolors) + + + nclasses = length(unique(classes)); + nobj = length(classes); + + if nargin < 2 || isempty(basecolors), basecolors = {[1 0 0] [0 1 0] [0 0 1] [1 1 0] [0 1 1] [1 0 1]}; end + + while nclasses > length(basecolors), basecolors = [basecolors basecolors]; end + + colors = cell(1,nobj); + mmax = .4; % max for off-color value + %omin = .8; % min for on-color value + rands = .25; + + for i = 1:max(classes) + + wh = find(classes == i); + + n = length(wh); + + x = sort(linspace(0,mmax,n),'descend')'; + + c = repmat(x,1,3); + + % random variation + c = c + rands .* (rand(size(c)) .* sign((rand(size(c)) > .5) - .5)); + c(c<0) = 0; + c(c>1) = 1; + + % add basecolor back in + whone = find(basecolors{i} > 0); + c(:,whone) = 1; + + for j = 1:n + colors(wh(j)) = {c(j,:)}; + end + + end + + % for i = 1:nobj + % fprintf(1,'%3.0f\t%3.2f %3.2f %3.2f\n',MCA.ClusterSolution.classes(i),colors{i}) + % end + return + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Utility functions + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % This is now its own separate function + % % function f1 = create_figure(tagname) + % % % checks for old figure, and creates new one + % % + % % if nargin < 1 || isempty(tagname) + % % tagname = 'nmdsfig'; + % % end + % % + % % old = findobj('Tag', tagname); + % % + % % if ~isempty(old) + % % disp(['Clearing old image: ' tagname]); + % % clf(old); + % % end + % % + % % scnsize = get(0,'ScreenSize'); + % % + % % xdim = min(scnsize(3)./2, 700); + % % ydim = min(scnsize(4)./2, 700); + % % + % % f1 = figure('position',round([50 50 xdim ydim]),'color','white'); + % % set(f1, 'Tag', tagname); + % % + % % set(gca, 'FontSize', 16); + % % + % % + % % return + + +function f1 = create_wide_figure + scnsize = get(0,'ScreenSize'); + + myposition = [50 50 scnsize(3)-100 scnsize(4)/2]; + myposition(3) = min(myposition(3), 1200); + myposition(4) = min(myposition(4), 500); + + f1 = figure('position',myposition,'color','white'); + return + + +function save_figure(myname,npts) + if nargin < 2, npts = 400; end + disp(['Saving figure: ' myname '.png']) + f1 = gcf; + scn_export_papersetup(npts); + saveas(f1,myname,'png'); + return + + + + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Connect two points in 3-D space with a straight or curved line + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + +function out = connect3d(x,y,color,thickness,bendpercent,varargin) + + % x, y inputs can be [x,y,z] coordinate triplets or clusters. + if isstruct(x) || isa(x, 'region'), x = x.mm_center; end + if isstruct(y) || isa(y, 'region'), y = y.mm_center; end + + % make x, y, z bend percents + if length(bendpercent) == 1 + bendpercent = repmat(bendpercent,1,3); + end + + % make 3 coords, so we can bend if we want to + xcoords = [x(1); x(1)+(y(1)-x(1))./2 + bendpercent(1)*x(1); y(1)]; + ycoords = [x(2); x(2)+(y(2)-x(2))./2 + bendpercent(2)*x(2); y(2)]; + zcoords = [x(3); x(3)+(y(3)-x(3))./2 + bendpercent(3)*x(3); y(3)]; + + if any(bendpercent) + % bow out: curved line + n = length(xcoords); + + nsamples = []; + if length(varargin) > 0 + nsamples = varargin{1}; + end + + if isempty(nsamples) + nsamples = 10 * n; + end + + t = 1:n; + ts = 1:((n-1)/(nsamples-1)):n; % spline grid + + xcoords = spline(t,xcoords,ts); + ycoords = spline(t,ycoords,ts); + zcoords = spline(t,zcoords,ts); + end + + h = plot3(xcoords,ycoords,zcoords,'Color',color,'LineWidth',thickness); + + out.h = h; + out.xcoords = xcoords; + out.ycoords = ycoords; + out.zcoords = zcoords; + + return + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- + + % Connect two points in 2-D space with a straight or curved line + + % ----------------------------------------------------------------------- + % ----------------------------------------------------------------------- +function out = connect2d(x,y,color,thickness,bendpercent,varargin) + + % x, y inputs can be [x,y,z] coordinate triplets or clusters. + if isstruct(x), x = x.mm_center(1:2); end + if isstruct(y), y = y.mm_center(1:2); end + + % make x, y bend percents + if length(bendpercent) == 1 + bendpercent = repmat(bendpercent,1,2); + end + + % make 2 coords, so we can bend if we want to + xcoords = [x(1); x(1)+(y(1)-x(1))./2 + bendpercent(1)*x(1); y(1)]; + ycoords = [x(2); x(2)+(y(2)-x(2))./2 + bendpercent(2)*x(2); y(2)]; + + if any(bendpercent) + % bow out: curved line + n = length(xcoords); + + nsamples = []; + if length(varargin) > 0 + nsamples = varargin{1}; + end + + if isempty(nsamples) + nsamples = 10 * n; + end + + t = 1:n; + ts = 1:((n-1)/(nsamples-1)):n; % spline grid + + xcoords = spline(t,xcoords,ts); + ycoords = spline(t,ycoords,ts); + + end + + h = plot(xcoords,ycoords,'Color',color,'LineWidth',thickness); + + out.h = h; + out.xcoords = xcoords; + out.ycoords = ycoords; + + return + + diff --git a/Cluster_contig_region_tools/Cluster-based_multivar_tools/partialcor.m b/Cluster_contig_region_tools/Cluster-based_multivar_tools/partialcor.m new file mode 100644 index 00000000..3bdd6eb3 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster-based_multivar_tools/partialcor.m @@ -0,0 +1,255 @@ +function [x,y,r,p,se,meany,stats] = partialcor(X,y,i,doprint,dorobust) +% function [x,y,r,p,se,meany,stats] = partialcor(X,y,i,[doprint],[dorobust]) +% +% partial correlation between column i of X and y +% X is n x k matrix of predictors (you can include an intercept; will be added if missing) +% y is n x 1 data vector +% +% x, y are adjusted predictor and data +% r, p are Pearson's correlation and p-value +% ***** Default option: use robust IRLS. ****** +% +% Warning: p-values are not adjusted for use of degrees of freedom in +% partial correlation computation w/o robust option! OK for robust +% +% Modified by Tor Wager, July 2006 +% :mean of y retained in case we're interested in the intercept +% :if X of interest is intercept, returns robust mean, controlling for +% other columns of X +% if robust outputs are requested, y is created from robust stats + +if nargin < 5, dorobust = 1; end +if nargin < 4, doprint = 0; end + +fullX = X; +yorig = y; + +% --------------------------------------------------- +% Prepare and check X matrix +% --------------------------------------------------- + +% x and y are adjusted predictor and data +x = X(:,i); +xorig = x; + +% add intercept if not already in model +no_intercept = ~any(all(diff(fullX) < eps)); +if no_intercept, fullX(:,end+1) = 1; end + +wh_intercept = find(all(diff(fullX) < eps)); + +% center predictors (except intercept) +% not needed; and this changes the interpretation of the betas in case x is +% not centered. +%fullX = scale(fullX,1); +%fullX(:,wh_intercept) = 1; + +redX = fullX; +redX(:,i) = []; % reduced model without param(s) of interest + +if ~(wh_intercept == i) + % we are removing the intercept, so we want to add it in later + wh_intercept_red = find(all(diff(redX) < eps)); +end + + +% --------------------------------------------------- +% Fit GLM model to get stats +% --------------------------------------------------- +if ~dorobust + % OLS + rstr = 'OLS '; + [b,dev,stats] = glmfit(fullX,y,'normal','constant','off'); + stats.w = ones(size(y,1),1); + stats.b = b; + +else + % robust IRLS + rstr = 'Robust '; + + [b,stats]=robustfit(fullX,y,'bisquare',[],'off'); + stats.b = b; +end + +% --------------------------------------------------- +% % Adjust x and y for regressors of no interest +% --------------------------------------------------- + +% weighted fit-forming matrix for no-interest cols of X +[bb,invxwx,bform,fits] = get_betas_singleweight(redX,y,stats.w); +y = y-fits; + +bbx = bform * x; +x = x - redX * bbx; + +if ~(wh_intercept == i) + % we are removing the intercept, so we want to add it in to show mean + y = y + bb(wh_intercept_red); + x = x + bbx(wh_intercept_red); +end + +% this code gives the same results,but not for adding in intercept +% W = diag(stats.w); +% %%%% this is slightly different: fitmtx = W * redX * pinv((W * redX)); +% fitmtx = redX * pinv((W * redX)); +% x = x - fitmtx * x + %%%but not here: must add rob mean mean(x); % keep mean b/c it's of interest in plot +% y = y - fitmtx * y + mean(y); + +if wh_intercept == i + % intercept value + r = stats.b(i); +else + r = partial_r_wt(x,y,stats.w); +end + +% --------------------------------------------------- +% % Opt: Model comparison version +% something seems wrong with this. +% --------------------------------------------------- +% Model comparison method +domodelc = 0; +if domodelc + if ~isempty(redX) + [r,r2,stats] = partial_r(fullX,redX,stats,yorig,wh_intercept,i,dorobust); + y = stats.resid + x * b(i); % adjusted y + else + % undefined, we have only the intercept + r = 0; + end +end + + +% this is the mean estimate because predictors are centered +meany = stats.b(wh_intercept); +t = stats.t(i); +p = stats.p(i); +se = stats.se(i); + + +if wh_intercept == i %all(x - mean(x) < eps) + % x is an intercept; weighted mean + %r = stats.w' * y ./ sum(stats.w); + pstr = 'intercept val'; +else + % weighted correlation + %r = weighted_corrcoef([x y],stats.w); + %r = r(1,2); + pstr = 'partial r'; +end + +if doprint + fprintf(1,'%s %s: %3.2f, b = %3.2f, se = %3.2f, t(%3.0f) = %3.2f, p = %3.4f\n',rstr,pstr,r,b(i),se,stats.dfe,stats.t(i),p); +end + + + +return + + + +function r = partial_r_wt(x,y,w) + +r = weighted_corrcoef([x y],w); +r = r(1,2); + +return + + + + + + +function [r_reduced,r2_reduced,stats] = partial_r(fullX,redX,stats,y,wh_intercept,wh_interest,dorobust) + +% "Model comparison" version +% pass in yorig + +if ~dorobust + % OLS + rstr = 'OLS '; +% [b,dev,stats] = glmfit(fullX,y,'normal','constant','off'); +% stats.w = ones(size(y,1),1); +% stats.b = b; + + if ~isempty(redX) + [br,dev,statsr] = glmfit(redX,y,'normal','constant','off'); + end + statsr.w = ones(size(y,1),1); + +else + % robust IRLS + rstr = 'Robust '; + +% [b,stats]=robustfit(fullX,y,'bisquare',[],'off'); +% stats.b = b; + + if ~isempty(redX) + [br,statsr]=robustfit(redX,y,'bisquare',[],'off'); + end +end + +mysign = sign(stats.b(wh_interest)); + +y = y - stats.b(wh_intercept); %mean(y); % deviations; only OK of covariates are centered! + +W = diag(stats.w); +W = W' * W; % squared weights + +SSt = y' * W * y; % total variability in data + +e = stats.resid; +er = statsr.resid; + +SSf = e' * W * e; % full model sum sq. error + +SSr = er' * W * er; % reduced model sse + +r2_reduced = (SSr - SSf) ./ SSt; % R2 accounted for by predictor of interest + +r_reduced = sqrt(r2_reduced); % partial correlation (absolute value) + +r_reduced = r_reduced * mysign; + +%r2_full = (SSt - SSf) ./ SSt; % full model r-square + +%r_full = sqrt(r2_full); % multiple R for full regression + +return + + + + + + + +function [betas,invxwx,bform,fits] = get_betas_singleweight(X,Y,w) + +W = diag(w); % Weight matrix + +%X = repmat(1,m,1); % Design matrix - 1 column of all ones to calculate average +% and, separately, use bcon if that's entered + +invxwx = inv(X'*W*X); +bform = invxwx * X'* W; % beta-forming matrix. hat = X * bform + +% rows are columns of design (X), cols are Y variables +betas = bform*Y; + +if nargout > 3 + fits = X * betas; +end + +return + +% if ~dorobust +% % X is no interest; x is of interest +% X(:,i) = []; +% +% W = X * pinv(X); +% +% x = x - W * x; +% +% % leave mean of y in in case we're interested in the intercept +% y = y - W * y + mean(y); + + diff --git a/Cluster_contig_region_tools/Cluster_based_statistics/cluster_names.m b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_names.m new file mode 100644 index 00000000..e9b38f1d --- /dev/null +++ b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_names.m @@ -0,0 +1,53 @@ +function [cl,names] = cluster_names(cl,addflag, varargin) +% function [cl,names] = cluster_names(cl,[addflag], [overlay],[rename]) +% +% Assign names to cl(x).shorttitle +% Do not use spaces or underscores or special chars for best results +% +% Addflag is optional; if 1, uses current orthview display +% +% If you have shorttitle field but want to rename, use rename flag = 1 + +spm_orthviews('Xhairs','on'); + +rename = []; +if nargin > 3, rename = varargin{2}; end + +ovl = []; +if nargin > 2, ovl = varargin{1}; end + +names = {}; + +dosurf = 0; + +if nargin > 1 && addflag + % don't create new figure +else + cluster_orthviews(cl,{[1 0 0]}, 'solid', 'overlay', ovl); +end + +for i = 1:length(cl) + + goname = 1; + + if dosurf, create_figure('Surface'); cluster_surf(cl(i), 5); end + + if isfield(cl(i),'shorttitle') && ~isempty(cl(i).shorttitle) + goname = 0; + end + + if rename + goname = 1; + end + + if goname + spm_orthviews('Reposition',cl(i).mm_center); + + cl(i).shorttitle = input('Enter short name for this cluster: ','s'); + + names{i} = cl(i).shorttitle; + end +end + + +return \ No newline at end of file diff --git a/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions.m b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions.m new file mode 100755 index 00000000..fcc8d41b --- /dev/null +++ b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions.m @@ -0,0 +1,296 @@ +function [cl,clpos,clneg,clrpos,clrneg] = cluster_sigregions(cl,pthr,varargin) +% [cl,clpos,clneg,clrpos,clrneg] = cluster_ttest(clusters,pthr,[behavioral regressor]) +% tor wager Oct 9, 2003 +% +% does t-tests on cluster timeseries and all voxels +% which should contain group data (e.g., con* data) +% +% if optional behavioral regressor is entered, +% computes cluster extent nonparametric p-values, and reports +% extent npm p-values for intercept and behavioral reg. +% +% writes output to cluster_ttest_output.txt + +fid = fopen('cluster_ttest_output.txt','a'); + +if length(varargin) > 0, beh = varargin{1};,end + +for i = 1:length(cl) + +% df = n - k primary threshold +tthr1 = abs(tinv(pthr,size(cl(i).all_data,1) - size(cl(i).ttest.t,1))); + +% ---------------------------------------- +% * do nonparametric permutation +% ---------------------------------------- + +if ~isfield(cl(i),'npm_ttest'),cl(i).npm_ttest = [];,end +if isfield(cl(i).npm_ttest,'tmax') & isfield(cl(i).npm_ttest,'textmax') + tthr = cl(i).npm_ttest.tthr ; + rthr = cl(i).npm_ttest.rthr ; + tnegthr = cl(i).npm_ttest.tnegthr ; + rnegthr = cl(i).npm_ttest.rnegthr ; + tmax = cl(i).npm_ttest.tmax ; + tmin = cl(i).npm_ttest.tmin ; + rmax = cl(i).npm_ttest.rmax ; + rmin = cl(i).npm_ttest.rmin ; + textmax = cl(i).npm_ttest.textmax ; + rextmax = cl(i).npm_ttest.rextmax ; +else + +[tthr,rthr,tnegthr,rnegthr,tmax,rmax,tmin,rmin,textmax,rextmax] = ... + npm_ttest(cl(i).all_data,1000,beh,pthr,cl(i).XYZ); +close + +cl(i).npm_ttest.tthr = tthr; +cl(i).npm_ttest.rthr = rthr; +cl(i).npm_ttest.tnegthr = tnegthr; +cl(i).npm_ttest.rnegthr = rnegthr; +cl(i).npm_ttest.tmax = tmax; +cl(i).npm_ttest.rmax = rmax; +cl(i).npm_ttest.textmax = textmax; +cl(i).npm_ttest.rextmax = rextmax; +cl(i).npm_ttest.tmin = tmin; +cl(i).npm_ttest.rmin = rmin; + +end + +fprintf(fid,'\nCl.\tVoxels\tThreshold\tMask\tData\t\n'); +fprintf(fid,'\tx\ty\tz\tVoxels\tCluster corr. p.\tMax. t\tCorrected p\tDirection of effect\tr\n'); +fprintf(fid,'%3.0f\t%3.0f\t%3.4f\t%s\t%s\t\n',i,cl(i).numVox,pthr,cl(i).P,cl(i).imP(1,:)); + +% ---------------------------------------- +% * get sig clusters +% ---------------------------------------- + +clpos(i).from_cluster = i; +clpos(i).M = cl(i).M; +clpos(i).voxSize = cl(i).voxSize; +clpos(i).threshold = tthr1; +clpos(i).title = 'Positive effects'; + +clrpos(i).from_cluster = i; +clrpos(i).M = cl(i).M; +clrpos(i).voxSize = cl(i).voxSize; +clrpos(i).threshold = tthr1; +clrpos(i).title = 'Positive correlations'; + +sig = cl(i).ttest.t > tthr1; + +if any(sig(1,:)) + clpos(i).XYZ = cl(i).XYZ(:,find(sig(1,:))); + clpos(i).XYZmm = cl(i).XYZmm(:,find(sig(1,:))); + clpos(i).Z = cl(i).Z(:,find(sig(1,:))); + clpos(i).t = cl(i).ttest.t(:,find(sig(1,:))); + gopos(i) = 1; + + % print table entry + wcl = spm_clusters(clpos(i).XYZ); + clpos(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clpos(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clpos(i).XYZmm(:,wh)';,end + nv = length(wh); + + if length(wh)>1,max_t = max(clpos(i).t(:,wh)'); max_t = max_t(1);, + else, max_t = clpos(i).t(1,wh); + end + + clpos(i).center = xyz; + + extcor_p = 1 - (sum(textmax <= nv) ./ length(textmax)); + max_p = 1 - (sum(tmax <= max_t) ./ length(tmax)); + + fprintf(fid,'\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t+\n', ... + xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p) + end + +else + gopos(i) = 0; +end + +if any(sig(2,:)) + clrpos(i).XYZ = cl(i).XYZ(:,find(sig(2,:))); + clrpos(i).XYZmm = cl(i).XYZmm(:,find(sig(2,:))); + clrpos(i).Z = cl(i).Z(:,find(sig(2,:))); + clrpos(i).t = cl(i).ttest.t(:,find(sig(2,:))); + gorpos(i) = 1; + + % print table entry + wcl = spm_clusters(clrpos(i).XYZ); + clrpos(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clrpos(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clrpos(i).XYZmm(:,wh)';,end + nv = length(wh); + + tmp = cl(i).all_data(:,find(sig(2,:))); + tmp = tmp(:,wh); + tmp = corrcoef([tmp beh]); + tmp = tmp(end,1:end-1); + max_r = max(tmp); + if max_r > .99, warning('problem?'),keyboard,end + + if length(wh)>1,max_t = max(clrpos(i).t(:,wh)'); max_t = max_t(2);, + else, max_t = clrpos(i).t(2,wh); + end + clrpos(i).center = xyz; + + extcor_p = 1 - sum(rextmax <= nv) ./ length(rextmax); + max_p = 1 - sum(rmax <= max_t) ./ length(rmax); + + fprintf(fid,'\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t+\t%3.2f\n', ... + xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p,max_r); + end + +else + gorpos(i) = 0; +end + +% negative + +clneg(i).from_cluster = i; +clneg(i).M = cl(i).M; +clneg(i).voxSize = cl(i).voxSize; +clneg(i).threshold = tthr1; +clneg(i).title = 'Negative effects'; + +clrneg(i).from_cluster = i; +clrneg(i).M = cl(i).M; +clrneg(i).voxSize = cl(i).voxSize; +clrneg(i).threshold = tthr1; +clrneg(i).title = 'Negative correlations'; + +sig = cl(i).ttest.t < -tthr1; + +if any(sig(1,:)) + clneg(i).XYZ = cl(i).XYZ(:,find(sig(1,:))); + clneg(i).XYZmm = cl(i).XYZmm(:,find(sig(1,:))); + clneg(i).Z = cl(i).Z(:,find(sig(1,:))); + clneg(i).t = cl(i).ttest.t(:,find(sig(1,:))); + goneg(i) = 1; + + % print table entry + wcl = spm_clusters(clneg(i).XYZ); + clneg(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clneg(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clpos(i).XYZmm(:,wh)';,end + nv = length(wh); + + if length(wh)>1,max_t = min(clneg(i).t(:,wh)'); max_t = max_t(1);, + else, max_t = clneg(i).t(1,wh); + end + + clneg(i).center = xyz; + + extcor_p = 1 - sum(textmax <= nv) ./ length(textmax); + max_p = 1 - sum(tmin >= max_t) ./ length(tmin); + + fprintf(fid,'\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t-\n', ... + xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p); + end + +else + goneg(i) = 0; +end + +if any(sig(2,:)) + clrneg(i).XYZ = cl(i).XYZ(:,find(sig(2,:))); + clrneg(i).XYZmm = cl(i).XYZmm(:,find(sig(2,:))); + clrneg(i).Z = cl(i).Z(:,find(sig(2,:))); + clrneg(i).t = cl(i).ttest.t(:,find(sig(2,:))); + gorneg(i) = 1; + + % print table entry + wcl = spm_clusters(clrneg(i).XYZ); + clrneg(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clrneg(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clrneg(i).XYZmm(:,wh)';,end + nv = length(wh); + + if length(wh)>1,max_t = min(clrneg(i).t(:,wh)'); max_t = max_t(2);, + else, max_t = clrneg(i).t(2,wh); + end + + tmp = cl(i).all_data(:,find(sig(2,:))); + tmp = tmp(:,wh); + tmp = corrcoef([tmp beh]); + tmp = tmp(end,1:end-1); + max_r = min(tmp); + if max_r > .99, warning('problem?'),keyboard,end + + clrneg(i).center = xyz; + + extcor_p = 1 - sum(rextmax <= nv) ./ length(rextmax); + max_p = 1 - sum(rmin >= max_t) ./ length(rmin); + + fprintf(fid,'\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t-\t%3.2f\t\n', ... + xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p,max_r); + end + +else + gorneg(i) = 0; +end + +end % loop through clusters + +fprintf(fid,'\n') + + +% Make montage +try + +if ~isempty(clpos) & ~isempty(clneg) + montage_clusters([],cl(unique(cat(2,[clpos.from_cluster clneg.from_cluster]))),clpos,clneg,{'k' 'r' 'b'},'nooverlap') + %hh=get(gcf,'Children'); + %for i = 1:length(hh),axes(hh(i));, h = findobj(gca,'FaceColor',[0 0 0]);, set(h,'FaceAlpha',.5) ,end +elseif ~isempty(clpos) + montage_clusters([],cl(cat(2,clpos.from_cluster)),clpos,{'k' 'r'},'nooverlap') +elseif ~isempty(clneg) + montage_clusters([],cl,clneg,{'k' 'b'},'nooverlap') +else + disp('No main contrast effects') +end + +if ~isempty(clrpos) & ~isempty(clrneg) + montage_clusters([],cl(unique(cat(2,[clrpos.from_cluster clrneg.from_cluster]))),clrpos,clrneg,{'k' 'r' 'b'},'nooverlap') +elseif ~isempty(clrpos) + montage_clusters([],cl(cat(2,clrpos.from_cluster)),clrpos,{'k' 'r'},'nooverlap') +elseif ~isempty(clrneg) + montage_clusters([],cl(cat(2,clrneg.from_cluster)),clrneg,{'k' 'b'},'nooverlap') +else + disp('No correlation effects') +end + +catch + disp('error with montage') +end + +fclose(fid); + +return + + + diff --git a/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions2.m b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions2.m new file mode 100755 index 00000000..79a73fc6 --- /dev/null +++ b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions2.m @@ -0,0 +1,321 @@ +function [cl,clpos,clneg,clrpos,clrneg] = cluster_sigregions(cl,pthr,varargin) +% [cl,clpos,clneg,clrpos,clrneg] = cluster_ttest(clusters,pthr,[behavioral regressor]) +% tor wager Oct 9, 2003 +% +% does t-tests on cluster timeseries and all voxels +% which should contain group data (e.g., con* data) +% +% if optional behavioral regressor is entered, +% computes cluster extent nonparametric p-values, and reports +% extent npm p-values for intercept and behavioral reg. +% +% writes output to cluster_ttest_output.txt + +fid = fopen('cluster_ttest_output.txt','a'); + +k=1; +if length(varargin) > 0, beh = varargin{1};,k=size(beh,2);,end + +outstr1 = []; outstr2 = []; outstr3 = []; outstr4 = []; +try,outstr = sprintf('\n%s\n',[cl(1).title ' ' cl(1).imP(1,:)]); +catch, outstr = sprintf('\n%s\n',[cl(1).title ' ' cl(1).imnames(1,:)]);, +end +outstr = [outstr sprintf('\nCl.\tVoxels\tThreshold\tMask\tData\t\n')]; +outstr1 = [outstr1 sprintf('\tx\ty\tz\tVoxels\tCluster corr. p.\tMax. t\tCorrected p\tDirection of effect\tr\n')]; +outstr2 = outstr1; outstr3 = outstr1; outstr4 = outstr1; + +for i = 1:length(cl) + +% df = n - k primary threshold +tthr1 = abs(tinv(pthr,size(cl(i).all_data,1) - k)); + +% ---------------------------------------- +% * do nonparametric permutation +% ---------------------------------------- +if ~isfield(cl(i),'npm_ttest'),cl(i).npm_ttest = [];,end +if isfield(cl(i).npm_ttest,'tmax') & isfield(cl(i).npm_ttest,'textmax') + tthr = cl(i).npm_ttest.tthr ; + rthr = cl(i).npm_ttest.rthr ; + tnegthr = cl(i).npm_ttest.tnegthr ; + rnegthr = cl(i).npm_ttest.rnegthr ; + tmax = cl(i).npm_ttest.tmax ; + tmin = cl(i).npm_ttest.tmin ; + rmax = cl(i).npm_ttest.rmax ; + rmin = cl(i).npm_ttest.rmin ; + textmax = cl(i).npm_ttest.textmax ; + rextmax = cl(i).npm_ttest.rextmax ; +else + +[tthr,rthr,tnegthr,rnegthr,tmax,rmax,tmin,rmin,textmax,rextmax] = ... + npm_ttest(cl(i).all_data,1000,beh,pthr,cl(i).XYZ); +close + +cl(i).npm_ttest.tthr = tthr; +cl(i).npm_ttest.rthr = rthr; +cl(i).npm_ttest.tnegthr = tnegthr; +cl(i).npm_ttest.rnegthr = rnegthr; +cl(i).npm_ttest.tmax = tmax; +cl(i).npm_ttest.rmax = rmax; +cl(i).npm_ttest.textmax = textmax; +cl(i).npm_ttest.rextmax = rextmax; +cl(i).npm_ttest.tmin = tmin; +cl(i).npm_ttest.rmin = rmin; + +end + +%outstr = [outstr sprintf('%3.0f\t%3.0f\t%3.4f\t%s\t%s\t\n',i,cl(i).numVox,pthr,cl(i).P,cl(i).imP(1,:))]; + +% ---------------------------------------- +% * get sig clusters +% ---------------------------------------- + +clpos(i).from_cluster = i; +clpos(i).M = cl(i).M; +clpos(i).voxSize = cl(i).voxSize; +clpos(i).threshold = tthr1; +clpos(i).title = 'Positive effects'; + +clrpos(i).from_cluster = i; +clrpos(i).M = cl(i).M; +clrpos(i).voxSize = cl(i).voxSize; +clrpos(i).threshold = tthr1; +clrpos(i).title = 'Positive correlations'; + +if isfield(cl(i).ttest,'t'), sig = cl(i).ttest.t > tthr1;, +else, warning(['No t field for cl(' num2str(i) ]), sig = [0;0]; +end + +if any(sig(1,:)) + clpos(i).XYZ = cl(i).XYZ(:,find(sig(1,:))); + clpos(i).XYZmm = cl(i).XYZmm(:,find(sig(1,:))); + clpos(i).Z = cl(i).Z(:,find(sig(1,:))); + clpos(i).t = cl(i).ttest.t(:,find(sig(1,:))); + gopos(i) = 1; + + % print table entry + wcl = spm_clusters(clpos(i).XYZ); + clpos(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clpos(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clpos(i).XYZmm(:,wh)';,end + nv = length(wh); + + if length(wh)>1,max_t = max(clpos(i).t(:,wh)'); max_t = max_t(1);, + else, max_t = clpos(i).t(1,wh); + end + + clpos(i).center = xyz; + + extcor_p = 1 - (sum(textmax <= nv) ./ length(textmax)); + max_p = 1 - (sum(tmax <= max_t) ./ length(tmax)); + + outstr1 = [outstr1 sprintf('%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t+\n', ... + clpos(i).from_cluster,xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p)]; + end + +else + gopos(i) = 0; +end + +if any(sig(2,:)) + clrpos(i).XYZ = cl(i).XYZ(:,find(sig(2,:))); + clrpos(i).XYZmm = cl(i).XYZmm(:,find(sig(2,:))); + clrpos(i).Z = cl(i).Z(:,find(sig(2,:))); + clrpos(i).t = cl(i).ttest.t(:,find(sig(2,:))); + gorpos(i) = 1; + + % print table entry + wcl = spm_clusters(clrpos(i).XYZ); + clrpos(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clrpos(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clrpos(i).XYZmm(:,wh)';,end + nv = length(wh); + + tmp = cl(i).all_data(:,find(sig(2,:))); + tmp = tmp(:,wh); + tmp = corrcoef([tmp beh]); + tmp = tmp(end,1:end-1); + max_r = max(tmp); + if max_r > .99, warning('problem?'),keyboard,end + + if length(wh)>1,max_t = max(clrpos(i).t(:,wh)'); max_t = max_t(2);, + else, max_t = clrpos(i).t(2,wh); + end + clrpos(i).center = xyz; + + extcor_p = 1 - sum(rextmax <= nv) ./ length(rextmax); + max_p = 1 - sum(rmax <= max_t) ./ length(rmax); + + outstr2 = [outstr2 sprintf('%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t+\t%3.2f\n', ... + clrpos(i).from_cluster,xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p,max_r)]; + end + +else + gorpos(i) = 0; +end + +% negative + +clneg(i).from_cluster = i; +clneg(i).M = cl(i).M; +clneg(i).voxSize = cl(i).voxSize; +clneg(i).threshold = tthr1; +clneg(i).title = 'Negative effects'; + +clrneg(i).from_cluster = i; +clrneg(i).M = cl(i).M; +clrneg(i).voxSize = cl(i).voxSize; +clrneg(i).threshold = tthr1; +clrneg(i).title = 'Negative correlations'; + +if isfield(cl(i).ttest,'t'), sig = cl(i).ttest.t < -tthr1;, +else, warning(['No t field for cl(' num2str(i) ]), sig = [0;0]; +end + +if any(sig(1,:)) + clneg(i).XYZ = cl(i).XYZ(:,find(sig(1,:))); + clneg(i).XYZmm = cl(i).XYZmm(:,find(sig(1,:))); + clneg(i).Z = cl(i).Z(:,find(sig(1,:))); + clneg(i).t = cl(i).ttest.t(:,find(sig(1,:))); + goneg(i) = 1; + + % print table entry + wcl = spm_clusters(clneg(i).XYZ); + clneg(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clneg(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clpos(i).XYZmm(:,wh)';,end + nv = length(wh); + + if length(wh)>1,max_t = min(clneg(i).t(:,wh)'); max_t = max_t(1);, + else, max_t = clneg(i).t(1,wh); + end + + clneg(i).center = xyz; + + extcor_p = 1 - sum(textmax <= nv) ./ length(textmax); + max_p = 1 - sum(tmin >= max_t) ./ length(tmin); + + outstr3 = [outstr3 sprintf('%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t-\n', ... + clneg(i).from_cluster,xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p)]; + end + +else + goneg(i) = 0; +end + +if any(sig(2,:)) + clrneg(i).XYZ = cl(i).XYZ(:,find(sig(2,:))); + clrneg(i).XYZmm = cl(i).XYZmm(:,find(sig(2,:))); + clrneg(i).Z = cl(i).Z(:,find(sig(2,:))); + clrneg(i).t = cl(i).ttest.t(:,find(sig(2,:))); + gorneg(i) = 1; + + % print table entry + wcl = spm_clusters(clrneg(i).XYZ); + clrneg(i).clusters = wcl; + for j = 1:max(wcl) + + wh = find(wcl==j); + + % corrected p + + xyz = mean(clrneg(i).XYZmm(:,wh),2)'; + if length(xyz) == 1, xyz = clrneg(i).XYZmm(:,wh)';,end + nv = length(wh); + + if length(wh)>1,max_t = min(clrneg(i).t(:,wh)'); max_t = max_t(2);, + else, max_t = clrneg(i).t(2,wh); + end + + tmp = cl(i).all_data(:,find(sig(2,:))); + tmp = tmp(:,wh); + tmp = corrcoef([tmp beh]); + tmp = tmp(end,1:end-1); + max_r = min(tmp); + if max_r > .99, warning('problem?'),keyboard,end + + clrneg(i).center = xyz; + + extcor_p = 1 - sum(rextmax <= nv) ./ length(rextmax); + max_p = 1 - sum(rmin >= max_t) ./ length(rmin); + + outstr4 = [outstr4 sprintf('%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.4f\t%3.2f\t%3.4f\t-\t%3.2f\t\n', ... + clrneg(i).from_cluster,xyz(1),xyz(2),xyz(3),nv,extcor_p,max_t,max_p,max_r)]; + end + +else + gorneg(i) = 0; +end + +end % loop through clusters + +outstr = [outstr sprintf('\n')]; +outstr1 = [outstr1 sprintf('\n')]; +outstr2 = [outstr2 sprintf('\n')]; +outstr3 = [outstr3 sprintf('\n')]; +outstr4 = [outstr4 sprintf('\n')]; + +disp([outstr outstr1 outstr2 outstr3 outstr4]) +outstr(outstr == '\') = '_'; +fprintf(fid,[outstr outstr1 outstr2 outstr3 outstr4]); +fclose(fid); + +% Make montage +gomontage = 0; + +if gomontage +try + +if ~isempty(clpos) & ~isempty(clneg) + montage_clusters([],cl(unique(cat(2,[clpos.from_cluster clneg.from_cluster]))),clpos,clneg,{'k' 'r' 'b'},'nooverlap') + %hh=get(gcf,'Children'); + %for i = 1:length(hh),axes(hh(i));, h = findobj(gca,'FaceColor',[0 0 0]);, set(h,'FaceAlpha',.5) ,end +elseif ~isempty(clpos) + montage_clusters([],cl(cat(2,clpos.from_cluster)),clpos,{'k' 'r'},'nooverlap') +elseif ~isempty(clneg) + montage_clusters([],cl,clneg,{'k' 'b'},'nooverlap') +else + disp('No main contrast effects') +end + +if ~isempty(clrpos) & ~isempty(clrneg) + montage_clusters([],cl(unique(cat(2,[clrpos.from_cluster clrneg.from_cluster]))),clrpos,clrneg,{'k' 'r' 'b'},'nooverlap') +elseif ~isempty(clrpos) + montage_clusters([],cl(cat(2,clrpos.from_cluster)),clrpos,{'k' 'r'},'nooverlap') +elseif ~isempty(clrneg) + montage_clusters([],cl(cat(2,clrneg.from_cluster)),clrneg,{'k' 'b'},'nooverlap') +else + disp('No correlation effects') +end + +catch + disp('error with montage') +end +end + +try, clpos = redefine_clusters(clpos);,catch,end +try, clrpos = redefine_clusters(clrpos);,catch,end +try, clneg = redefine_clusters(clneg);,catch,end +try, clrneg = redefine_clusters(clrneg);,catch,end + +return + + + diff --git a/Cluster_contig_region_tools/Cluster_based_statistics/cluster_ttest.m b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_ttest.m new file mode 100755 index 00000000..b55f5eea --- /dev/null +++ b/Cluster_contig_region_tools/Cluster_based_statistics/cluster_ttest.m @@ -0,0 +1,182 @@ +function clusters = cluster_ttest(clusters,varargin) +% clusters = cluster_ttest(clusters,[covariate]) +% tor wager Oct 9, 2003 +% +% does t-tests on cluster timeseries and all voxels +% which should contain group data (e.g., con* data) +% +% +fid = fopen('cluster_ttest_output.txt','a'); + +fprintf(fid,'\nT-test results for clusters\n'); +fprintf(fid,'Cluster\tOverall t\tOverall p\tNum vox\tMax t\tMin t\tBonf_thresh\tBonf_posvox\tBonf_negvox'); + +fprintf(fid,'\tNPM_posthresh\tNPM_negthresh\tNPM_posvox\tNPM_negvox\tpos_NPM_corp\tneg_NPM_corp'); + +if length(varargin) > 0, + beh = varargin{1};, + if size(beh,2) ~= 1, beh = beh';,end + %X = [beh ones(length(beh),1)]; + X = beh - mean(beh); + X(isnan(beh),:) = []; + clusters(1).covariate = beh; + fprintf(fid,'\tOverall r\tt\tp\tMax r\tMin r\tBonf_r_pos\tBonf_r_neg'); + fprintf(fid,'\tNPM_r+thresh\tNPM_r-thresh\tNPM_r_posvox\tNPM_r_negvox\tposr_NPM_corp\tnegr_NPM_corp'); + +else,beh = [];,X = []; % define for eliminating NaN voxels when no beh vector +end + + + +fprintf(fid,'\n'); + +for i = 1:length(clusters) + + + if ~isempty(beh) + + % simple regression + [b,dev,stat] = glmfit(X,clusters(i).timeseries); + if ~isfield(stat,'p'),stat.p = NaN;,stat.t = NaN;,end + stats.tstat = stat.t; + stats.p = stat.p; + stats.beta = stat.beta; + stats.df = stat.dfe; + tmp = corrcoef(X,clusters(i).timeseries); + roverall = tmp(1,2); + + else + % ttest + [h,p,ci,stats]=ttest(clusters(i).timeseries); + stats.p = p; + end + + clusters(i).overall_ttest = stats; + + fprintf(fid,'%3.0f\t%3.2f\t%3.4f\t%3.0f',i,stats.tstat(1),stats.p(1),clusters(i).numVox); + + % Bonferroni threshold + bfp = .05 ./ clusters(i).numVox; + bf = tinv(1-(bfp),stats.df); + + clear h, clear p, clear t, clear r, + for j = 1:size(clusters(i).all_data,2) + + if any(isnan(clusters(i).all_data(:,j))) + p(:,j) = zeros(size(X,2)+1,1) * NaN;,t(:,j) = zeros(size(X,2)+1,1) * NaN; + h(:,j) = zeros(size(X,2)+1,1); r(j) = NaN; + elseif ~isempty(beh) + + % simple regression + [b,dev,stat] = glmfit(X,clusters(i).all_data(:,j)); + if ~isfield(stat,'p'),stat.p = zeros(size(X,2)+1,1) * NaN;,stat.t = zeros(size(X,2)+1,1) * NaN;,end + h(:,j) = stat.p < bfp; + p(:,j) = stat.p; + t(:,j) = stat.t; + tmp = corrcoef(X,clusters(i).all_data(:,j)); + r(j) = tmp(1,2); + + else + + [h(j),p(j),ci,stats]=ttest(clusters(i).all_data(:,j),0,bfp); + t(j) = stats.tstat; + end + + + end + + mns = nanmean(clusters(i).all_data); + pos = h(1,:) & (mns > 0); + neg = h(1,:) & (mns < 0); + + if any(pos), clusters(i).ttest.poscenter = mean(clusters(i).XYZmm(:,find(pos)),2)';, end + if any(neg), clusters(i).ttest.negcenter = mean(clusters(i).XYZmm(:,find(neg)),2)';, end + + if ~isempty(beh) & size(h,1) > 1 + poscor = h(2,:) & (t(2,:) > 0); + negcor = h(2,:) & (t(2,:) < 0); + clusters(i).ttest.poscor = poscor; + clusters(i).ttest.negcor = negcor; + if any(poscor), clusters(i).ttest.posrcenter = mean(clusters(i).XYZmm(:,find(poscor)),2)';, end + if any(negcor), clusters(i).ttest.negrcenter = mean(clusters(i).XYZmm(:,find(negcor)),2)';, end + end + + tm = max(t(1,:)); + + % Max t\tBonf_thresh\tBonf_posvox\tBonf_negvox') + fprintf(fid,'\t%3.2f\t%3.2f\t%3.2f\t%3.0f\t%3.0f',tm,min(t(1,:)),bf,sum(pos),sum(neg)); + + % NPM simulation + [tthr,rthr,tnegthr,rnegthr,tmax,rmax,tmin,rmin,textmax,rextmax] = ... + npm_ttest(clusters(i).all_data,1000,beh,.005,clusters(i).XYZ); + close + + tcorp = 1 - (sum(tmax <= tm) ./ length(tmax)); + tcorpneg = 1 - (sum(tmin >= tm) ./ length(tmin)); + clusters(i).npm_ttest.tcorp = tcorp; + clusters(i).npm_ttest.tcorpneg = tcorpneg; + clusters(i).npm_ttest.tmax = tmax; + clusters(i).npm_ttest.tmin = tmin; + + if any(any(~isnan(t))) + + fprintf(fid,'\t%3.2f\t%3.2f\t%3.0f\t%3.0f\t%3.4f\t%3.4f', ... + tthr,tnegthr,sum(t(1,:)>tthr),sum(t(1,:)tthr), clusters(i).npm_ttest.poscenter = mean(clusters(i).XYZmm(:,find(t(1,:)>tthr)),2)';, end + if ~isempty(rthr), if any(t(2,:)>rthr), clusters(i).npm_ttest.posrcenter = mean(clusters(i).XYZmm(:,find(t(2,:)>rthr)),2)';, end, end + if any(t(1,:)= tm) ./ length(rmin)); + clusters(i).npm_ttest.rcorp = rcorp; + clusters(i).npm_ttest.rcorpneg = rcorpneg; + clusters(i).npm_ttest.rmax = rmax; + clusters(i).npm_ttest.rmin = rmin; + + fprintf(fid,'\t%3.2f\t%3.2f\t%3.0f\t%3.0f\t%3.4f\t%3.4f\t', ... + rthr,rnegthr,sum(t(2,:)>rthr),sum(t(2,:) 0, + % set up cluster extent threshold + pthr = varargin{1};, + XYZ = varargin{2};, + tthr = abs(tinv(pthr,size(x,1) - min(size(beh)) + 1)); % t threshold +else, pthr = [];, +end + +signv = sign(randn(size(x,1),niter)); + +figure('Color','w'); + + +for i = 1:niter + + % t-test + + xtmp = x .* repmat(signv(:,i),1,size(x,2)); + + ttmp = mean(xtmp) ./ (std(xtmp)./sqrt(size(xtmp,1))); + + tmax(i) = max(ttmp); + tmin(i) = min(ttmp); + + % cluster extent + + if ~isempty(pthr) + tmp = XYZ(:,ttmp > tthr); + if isempty(tmp), textmax(i) = 0;, + else, + tmp = spm_clusters(tmp); + textmax(i)=max(hist(tmp,max(tmp))); % get the max cluster size + end + end + + + if mod(i,100)==0, [pltmp,xx] = hist(tmax); plot(xx,pltmp,'r','LineWidth',2),title(['It ' num2str(i)]),drawnow,end + + % simple regression + + if ~isempty(beh) + + btmp = getRandom(beh); + + % vectorize simple regression + + XX = [ones(size(btmp,1),1) btmp]; + XXpinv = pinv(XX); + XTXI = diag(inv(XX' * XX)); + y = x; + %se = sqrt(diag(inv(XX' * XX)) .* (r'*r) / 2) + %b = pinv([ones(size(X,1),1) X]) * y; + b = XXpinv * y; + r = y - XX * b; + dfe = size(XX,1) - size(XX,2); + se = sqrt(repmat(XTXI,1,size(y,2)) .* repmat(var(r) .* (size(XX,1)-1) / dfe,size(XX,2),1)); + rtmp = b ./ se; + + rmax(i) = max(rtmp(2,:)); + rmin(i) = min(rtmp(2,:)); + + % cluster extent + + if ~isempty(pthr) + tmp = XYZ(:,rtmp(2,:) > tthr); + if isempty(tmp), rextmax(i) = 0;, + else, + tmp = spm_clusters(tmp); + rextmax(i)=max(hist(tmp,max(tmp))); % get the max cluster size + end + end + + if mod(i,100)==0, hold on, pltmp = hist(rmax); plot(xx,pltmp,'b','LineWidth',2),drawnow,end + end + +end + +if any(~isnan(tmax)) + tthr = prctile(tmax,95); + tnegthr = prctile(tmin,5); + if ~isempty(beh), + rthr = prctile(rmax,95);, + rnegthr = prctile(rmin,5); + end +else + return +end + +close + +return diff --git a/Cluster_contig_region_tools/anat_subclusters.m b/Cluster_contig_region_tools/anat_subclusters.m new file mode 100755 index 00000000..68e0666e --- /dev/null +++ b/Cluster_contig_region_tools/anat_subclusters.m @@ -0,0 +1,107 @@ +function clout = anat_subclusters(cl,varargin) +% clout = anat_subclusters(cl,[resume at],[output cl to resume]) +% +% Clusters voxels within 'clusters' structure based on anatomical locations +% in space. Outputs subgroups of smaller clusters. +% +% tor wager, 8/6/04 + +clout = []; startat = 1; + +if length(varargin) > 0 + startat = varargin{1}; +end +if length(varargin) > 1 + clout = varargin{2}; +end + +for i = startat:length(cl) + + if ~isfield(cl,'shorttitle'), cl(i).shorttitle = [];,end + + cluster_orthviews(cl(i),{[1 0 0]}) + + fprintf(1,'Cluster %3.0f, with %3.0f voxels.',i,cl(i).numVox); + if cl(i).numVox > 500, disp('Warning: may be slow! Sil plot not recommended'),end + %D = squareform(pdist(cl(i).XYZ')); + %[CLUST]=permnmds(D,100,min(5,round(size(D,2)./10)),3,1); %FIND N by K best cluster/element solution + % clas = CLUST.clustermat(CLUST.bestdims,:); + dos = input('Do silhouette plot? (1/0) '); + if dos, try, [s] = silhouetteplot(cl(i).XYZ');, catch, disp('Error making sil plot.'), end, end + + k = input('Choose number of subclusters: '); + if max(size(cl(i).XYZ)) > 3, + %T = clusterdata(cl(i).XYZ','maxclust',k,'linkage','average'); + + if k == 1 + T = ones(size(cl(i).XYZ,2),1)'; + else + %point estimate cluster solution + Y = pdist(cl(i).XYZ'); + Z = linkage_t(Y,'average'); + T = cluster(Z,k); + end + else + % not enough data! + T = ones(size(cl(i).XYZ,2),1)'; + end + + if size(T,1) > size(T,2), T = T';,end + if size(cl(i).Z,1) > size(cl(i).Z,2), cl(i).Z = cl(i).Z';,end + + for j = 1:k + + if any(T == j) + + wh = find(T == j); + + if isempty(clout), + clout = cl(i);, + else, + clout(end+1) = cl(i);, + + end + + clout(end).XYZ = clout(end).XYZ(:,wh); + clout(end).XYZmm = clout(end).XYZmm(:,wh); + clout(end).Z = clout(end).Z(:,wh); + clout(end).mm_center = mean(clout(end).XYZmm,2)'; + if isfield(clout,'all_data') + clout(end).all_data = clout(end).all_data(:,wh); + end + if isfield(clout,'timeseries'), + clout(end).timeseries = mean(clout(end).all_data,2); + end + + cluster_orthviews(clout(end),{rand(1,3)},'add') + clout(end).mm_center=mean(clout(end).XYZmm,2)'; + spm_orthviews('Reposition',clout(end).mm_center) + + clout(end).shorttitle = input('Enter short title for this cluster: ','s'); + end + + end + + + disp('Saving clout_tmp for recovery.'), save clout_tmp clout + + % done - display output clusters + %colors = {'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + %hh = findobj('Tag','Graphics'); + %h1 = get(hh,'Children'); + %f1 = figure('Color','w'); c= copyobj(h1,f1); + + %set(gcf,'Position', [ 210 322 929 407]) %[464 606 672./2 504./2]) + %for i = 1:length(c) + % tmp = get(c(i),'Position'); tmp(3) = tmp(3) .* .5; + % set(c(i),'Position',tmp); + %end + %keyboard + +end + +for ii = 1:length(clout), clout(ii).numVox = size(clout(ii).XYZmm,2);,end + + +return + diff --git a/Cluster_contig_region_tools/cluster2region.m b/Cluster_contig_region_tools/cluster2region.m new file mode 100644 index 00000000..0a599d2e --- /dev/null +++ b/Cluster_contig_region_tools/cluster2region.m @@ -0,0 +1,66 @@ +function obj = cluster2region(cl) +% obj = cluster2region(cl) +% +% Transform a CANlab/SCANlab "clusters" structure into a region object, the +% standard in 2011 toolbox functions and beyond. +% +% Tor Wager, Feb 2011 + + +N = fieldnames(cl); + +obj = region; +No = fieldnames(obj); +obj(1:length(cl)) = obj(1); + +for k = 1:length(cl) + + for i = 1:length(N) + + % Look for a field (attribute) with the input name + wh = strmatch(N{i}, No, 'exact'); + + if ~isempty(wh) + + obj(k).(N{i}) = cl(k).(N{i}); + + else + % make mapping with named fields in obj + + switch N{i} + + case {'from_label', 'from_cluster'} + + obj(k).custom_info1 = cl(k).(N{i}); + obj(k).custom_info1_descrip = N{i}; + + case 'Z' + obj(k).val = cl(k).Z; + obj(k).Z = cl(k).Z; + + case {'P', 'image_names'} + obj(k).source_images{1} = cl(k).(N{i}); + + case {'average_data' 'timeseries' 'contrast_data'} + obj(k).dat = cl(k).(N{i}); + + case {'threshold'} + % do nothing + + otherwise + if k == 1 + warning('cl2region:no comparable field %s', N{i}); + end + end + + + end % case field + + end + +end % cl + +end % function + + + diff --git a/Cluster_contig_region_tools/cluster2subclusters.m b/Cluster_contig_region_tools/cluster2subclusters.m new file mode 100644 index 00000000..0ebb40bf --- /dev/null +++ b/Cluster_contig_region_tools/cluster2subclusters.m @@ -0,0 +1,45 @@ +function cl = cluster2subclusters(cl_in,class) +% cl = cluster2subclusters(cl_in,class) +% +% take a single cluster cl_in and separate into subclusters +% based on vector of integers class +% +% class must code unique subclusters +% subcluster order is only preserved if class contains all integers from 1 +% to nclasses: +% i.e., class 3 will only be in subcluster 3 if there are no missing class +% numbers in class +% +% tor wager, july 06 + +classes = unique(class); % vector of class numbers + +nclust = length(classes); + +% Fill in fields +for i = 1:nclust + + % voxels in this cluster + wh_incluster = find(class == classes(i)); + nvox = length(wh_incluster); + + cl(i).title = sprintf('Subcluster of %3.0f vox from %s',nvox, cl_in.title); + + % copy these fields + for f = {'threshold' 'M' 'dim' 'voxSize'} + if isfield(cl_in,f{1}), cl(i).(f{1}) = cl_in.(f{1}); end + end + + % add new values for these fields + cl(i).name = ''; + cl(i).numVox = nvox; + + % select in_subcl voxels for these fields + for f = {'XYZ' 'XYZmm' 'Z'} + cl(i).(f{1}) = cl_in.(f{1})(:,wh_incluster); + end + + % center of mass + cl(i).mm_center = center_of_mass(cl(i).XYZmm,cl(i).Z); + +end diff --git a/Cluster_contig_region_tools/cluster_close_enough.m b/Cluster_contig_region_tools/cluster_close_enough.m new file mode 100644 index 00000000..bbcc8045 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_close_enough.m @@ -0,0 +1,32 @@ +function [close_enough,outside_range,nearest_distance,closest_cluster] = cluster_close_enough(cl_match_to,cl_match,mind) +%[close_enough,outside_range,nearest_distance,closest_cluster] = cluster_close_enough(cl_match_to,cl_match,mind) +% +% finds whether each cluster center in cl_match is within mind mm of a cluster +% center in cl_match_to. +% +% useful for selecting a list of clusters that are not close to another +% list to, e.g., make a table of. or this could be used to find clusters +% in a set of correlated clusters that are close to centers in activated +% clsuters. + +centers1 = cat(1,cl_match_to.mm_center); +centers2 = cat(1,cl_match.mm_center); + +nmatchto = size(centers1,1); +nmatch = size(centers2,1); + +% get matrix of distances, centers1 x centers2 +d = pdist([centers1; centers2]); +d = squareform(d); +d = d(1:nmatchto,nmatchto+1:end); + +[nearest_distance,closest_cluster] = min(d); + + +d = d < mind; + +close_enough = any(d); +outside_range = ~close_enough; + +return + diff --git a/Cluster_contig_region_tools/cluster_export_pngs.m b/Cluster_contig_region_tools/cluster_export_pngs.m new file mode 100644 index 00000000..80914c3a --- /dev/null +++ b/Cluster_contig_region_tools/cluster_export_pngs.m @@ -0,0 +1,67 @@ +function cl = cluster_export_pngs(cl,useexisting,overlay,xhairson) +%cl = cluster_export_pngs(cl,[useexisting],[overlayimagename],[xhairson]) +% +% save png images of SPM orthviews windows for each cluster in a set (cl +% structure) +% +% names from cl(x).shorttitle are used +% useexisting is optional: 1 uses existing orthviews display (default), 0 creates a +% new one with the clusters +% +% example: use existing: +% cluster_export_pngs(cl,1,EXPT.overlay); +% +% tor wager, aug 3, 06 + +if nargin < 2, useexisting = 1; end +if nargin < 3, overlay = []; end +if nargin < 4, xhairson = 0; end + +if ~useexisting + cluster_orthviews(cl,'bivalent'); +end + +% subdirectory: prompt +dosubdir = input('Create a subdirectory for png images? (type name or return to use current dir) ','s'); +if ~isempty(dosubdir) + mkdir(dosubdir) + cd(dosubdir) +end + +% Make sure we have names or ask to create them +if ~isfield(cl(1),'shorttitle') || isempty(cl(1).shorttitle) + donames = input('Name these clusters before saving? (1/0) '); + if donames + cl = cluster_names(cl,1); + else + for i = 1:length(cl) + cl(i).shorttitle = []; + end + end +end + +% set up orthviews figure +scn_export_spm_window('setup',overlay); + +if xhairson, spm_orthviews('Xhairs','on'); end + +% save png images of each cluster + +for i = 1:length(cl) + spm_orthviews('Reposition',cl(i).mm_center); + spm_orthviews_showposition; + + % evaluate any existing window button up function (e.g., update + % scatterplots) + fh = findobj('Type','Figure','Tag','Graphics'); + funh = get(fh,'WindowButtonUpFcn'); + funh() + + scn_export_spm_window('save',['Cl' num2str(i) '_' cl(i).shorttitle]); +end + +if ~isempty(dosubdir) + cd .. +end + +return diff --git a/Cluster_contig_region_tools/cluster_find_index.m b/Cluster_contig_region_tools/cluster_find_index.m new file mode 100644 index 00000000..1c513a13 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_find_index.m @@ -0,0 +1,34 @@ +function [wh_cluster, min_distance] = cluster_find_index(cl, varargin) +% +%function [wh_cluster, min_distance] = cluster_find_index(cl, [keep display flag, 1/0]) +% +% Ever see an interesting blob when visualizing a clusters structure, but +% don't know which index number in the clusters structure vector it +% corresponds to? +% +% With this function, find the index number of the closest cluster to one you specify +% graphically by clicking on. +% +keepdisplay = 0; +if length(varargin) > 0 + keepdisplay = varargin{1}; +end + +if ~keepdisplay + cluster_orthviews(cl, {[1 0 0]}); +end + +input('Click near the center of the cluster you want to find the index number of and press return.') + +centers = cat(1, cl.mm_center); +pos = spm_orthviews('Pos')'; + +d = sqrt(sum((centers - repmat(pos, length(cl), 1)) .^ 2, 2)); + +[min_distance, wh_cluster] = min(d); + +cluster_orthviews(cl(wh_cluster), {[0 1 0]}, 'add'); + +disp(['Closest cluster is number ' num2str(wh_cluster(1)), ' with a center of mass ' num2str(min_distance(1)) ' mm away.']) + +end diff --git a/Cluster_contig_region_tools/cluster_interp.m b/Cluster_contig_region_tools/cluster_interp.m new file mode 100644 index 00000000..d6546ae7 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_interp.m @@ -0,0 +1,88 @@ +function cl = cluster_interp(cl,varargin) +%function cl = cluster_interp(cl,maskimg,[keep sep clusters flag]) +% +% Interpolates voxels and mm in a clusters structure (cl) to match the +% image dimensions and voxel sizes of a target mask image. +% +% Example: +% cl = cluster_interp(cl,maskimg,1); +% +% default mask 2 x 2 x 2, SPM2 default: +% cl = cluster_interp(cl,[],1); +% + +%maskimg = which('scalped_avg152T1_graymatter_smoothed.img'); +maskimg = which('scalped_avg152T1.img'); +if length(varargin) > 0, maskimg = varargin{1};,end +if isempty(maskimg),maskimg = which('scalped_avg152T1.img');, end + + +dosep = 0;if length(varargin) > 1, dosep = varargin{2};,end +%dotext = 1; if dosep, dotext = 0;,end + +% recursive call if clusters are to be kept separate +if dosep, + fprintf(1,'\n---------------------------------------\n'); + fprintf(1,'cluster_interp.m: creating separate masks for each cluster to preserve separation\n'); + fprintf(1,'\n---------------------------------------\n'); + for i = 1:length(cl) + fprintf(1,'\n---------------------------------------\n'); + fprintf(1,'Cluster %3.0f ',i); + fprintf(1,'\n---------------------------------------\n'); + cltmp = cluster_interp(cl(i),maskimg); + + if i == 1, cl2 = cltmp;, else, cl2 = merge_clusters(cl2,cltmp);, end + end + fprintf(1,'\n---------------------------------------\n'); + cl = cl2; + + return +end + +% make mask of clusters + +Vcl.mat = cl(1).M; +Vcl.dim = [91 109 91 2]; +[m,Vcl] = clusters2mask(cl,Vcl); + + + +% reslice mask + +[tmp,newname] = reslice_imgs(maskimg,'clustermask.img'); +if newname(1) == filesep, newname = newname(2:end);,end + +% re-threshold mask to avoid larger clusters +Vcl = spm_vol(newname); v = spm_read_vols(Vcl); +v(v < .4) = 0; +spm_write_vol(Vcl,v); + +% get clusters back +cl2 = mask2clusters(newname); + + +% put coords, etc. back in original cluster structure +if length(cl) == length(cl2) + +for i = 1:length(cl2) + cl(i).numVox = cl2(i).numVox; + cl(i).voxSize = cl2(i).voxSize; + cl(i).M = cl2(i).M; + cl(i).Z = cl2(i).Z; + cl(i).XYZmm = cl2(i).XYZmm; + cl(i).XYZ = cl2(i).XYZ; + cl(i).center = cl2(i).center; + cl(i).mm_center = cl2(i).mm_center; +end + +else + disp('Number of clusters has changed; cannot save old info.'); + cl = cl2; +end + + +%disp('Removing clustermask.img') +!rm clustermask.img +!rm clustermask.hdr + +return \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_intersection.m b/Cluster_contig_region_tools/cluster_intersection.m new file mode 100644 index 00000000..01905351 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_intersection.m @@ -0,0 +1,94 @@ +% intersect_cl = cluster_intersection(cl1, cl2, cl3, ...) +% +% Computes the intersection of the clusters passed in. +% +% Sample usage: +% % simple intersection +% cl = cluster_intersection(robust0001_poscl(2), robust0002_poscl(4), robust0003_poscl(17)); +% +% % intersection between sets of clusters +% % alternatively, see cluster_set_intersection.m +% cl = cluster_intersection(clusters2CLU(robust0001_poscl), clusters2CLU(robust0002_poscl)); +% +% NB: Only works with single clusters. To compute the intersection between sets of clusters, +% use cluster_set_intersection() +% +% Created by Matthew Davidson, 07/08/24 + +function cl = cluster_intersection(varargin) + clusters = []; + parse_params(); + cl = blank_cluster(clusters); + + [cl.voxSize, cl.M] = intersection_cluster_dims(clusters); + cl.XYZmm = intersection_voxels(clusters)'; + + cl.XYZ = mm2voxel(cl.XYZmm, cl.M, 1)'; + cl.numVox = size(cl.XYZmm, 2); + cl.mm_center = mean(cl.XYZmm, 2)'; + cl.center = mean(cl.XYZ, 2)'; + + function parse_params() + for i=1:length(varargin) + switch(class(varargin{i})) + case 'struct' + clusters = merge_clusters(clusters, varargin{i}); %#ok + case 'char' + %nothing yet - placeholder for future + end + end + if(length(clusters) < 2) + error('Need 2 or more clusters to compute intersection.'); + end + end +end + + + +function [voxSize, M] = intersection_cluster_dims(clusters) + max_cluster_size = prod(clusters(1).voxSize); + largest_cluster_idx = 1; + + for i=2:length(clusters) + if(prod(clusters(i).voxSize) > max_cluster_size) + max_cluster_size = prod(clusters(i).voxSize); + largest_cluster_idx = i; + end + end + + voxSize = clusters(largest_cluster_idx).voxSize; + M = clusters(largest_cluster_idx).M; +end + +% function voxels = intersection_voxels(clusters, voxels) +% if(~exist('voxels', 'var')) +% voxels = orient_coord_list(clusters(1).XYZmm); +% else +% voxels = intersect(orient_coord_list(clusters(1).XYZmm), voxels, 'rows'); +% end +% if(length(clusters) > 1) +% voxels = intersection_voxels(clusters(2:end), voxels); +% end +% end +function voxels = intersection_voxels(clusters) + voxels = orient_coord_list(clusters(1).XYZmm); + for i=2:length(clusters) + voxels = intersect(orient_coord_list(clusters(i).XYZmm), voxels, 'rows'); + end +end + +function coords = orient_coord_list(coords) + if(size(coords, 1) == 3) + coords = coords'; + end +end + +function cl = blank_cluster(clusters) + cl.title = 'Intersection cluster'; + cl.name = 'Intersection cluster'; + cl.threshold = 1; + cl.Z = []; + cl.numPeaks = []; + if isfield(clusters, 'P'), cl.P = strvcat(clusters.P); end + if isfield(clusters, 'imP'),cl.imP = strvcat(clusters.imP); end +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_local_maxima.m b/Cluster_contig_region_tools/cluster_local_maxima.m new file mode 100644 index 00000000..0a8bbf1b --- /dev/null +++ b/Cluster_contig_region_tools/cluster_local_maxima.m @@ -0,0 +1,117 @@ +% [xyz, XYZmm, Z, class] = cluster_local_maxima(cl, [dthresh], [verbose]) +% +% clusters are chosen so that they must be at least dthresh mm apart +% default is 10 mm +% +% verbose output: 1/0, default is 0 +% +% additional optional outputs (slower): +% +% class: vector of integers for which subcluster this cluster belongs to +% +% tor wager + +function [xyz, XYZmm, Z, class] = cluster_local_maxima(cl, dthresh, verbose) + if nargin < 2 || isempty(dthresh), dthresh = 10; end + if nargin < 3, verbose = 0; end + + [N Z xyz] = spm_max(abs(cl.Z), cl.XYZ); + + XYZmm = voxel2mm(xyz, cl.M); + + + ncoord = size(cl.XYZmm, 2); + class = zeros(1, ncoord); + + if isempty(xyz), return, end % no peak maximum + + if verbose, fprintf(1, 'Local maxima (initial): %3.0f\n', length(Z)); end + + % find maxima within 10 mm and collapse + d = pdist(XYZmm'); + nd = length(d); + tooclose = d < dthresh; + + while any(tooclose) + + [d, nd, tooclose, xyz, XYZmm, Z] = omit_lowz_of_closest_pair(d, nd, tooclose, xyz, XYZmm, Z, dthresh, verbose); + + end + + if verbose, fprintf(1, 'Local maxima (final): %3.0f\n', length(Z)); end + + if nargout > 3 + % assign each voxel a subcluster based on closest local max + speaks = XYZmm'; + npeaks = length(Z); + + % this is slower even for only 1000 vox + %d = squareform(pdist([speaks; cl.XYZmm'])); + %d = d(1:npeaks, npeaks+1:end); % peaks x voxels + + for i = 1:ncoord + d = distance(cl.XYZmm(:, i)', speaks); + [mind, whclose] = min(d); + class(i) = whclose(1); + end + end +end + + + +function [d, nd, tooclose, xyz, XYZmm, Z] = omit_lowz_of_closest_pair(d, nd, tooclose, xyz, XYZmm, Z, dthresh, verbose) + + tooclose = tooclose .* d; + + % find minimum distance + tooclose(tooclose == 0) = Inf; + [mindist, wh] = min(tooclose); + + % get indices of two local maxima that are too close (in rows) + closest = zeros(1, nd); + closest(wh(1)) = mindist; + closest = squareform(closest); + [rows, cols] = find(closest); % need to return cols to get correct behavior + + % find which has the lowest Z-score + [lowZ, whz] = min(Z(rows)); % rows is sufficient b/c there is only one pair + + wh_to_omit = rows(whz); % which in list to omit + + if verbose + fprintf(1, 'Omitting index: %3.0f with z-value of %3.2f, which is %3.2f mm from another max whose z is %3.2f\n', ... + wh_to_omit, lowZ, mindist, max(Z(rows))); + end + + % eliminate important variables + xyz(:, wh_to_omit) = []; + XYZmm(:, wh_to_omit) = []; + Z(wh_to_omit) = []; + + % find maxima within 10 mm and collapse + d = pdist(XYZmm'); + nd = length(d); + tooclose = d < dthresh; + +end + + +function d = distance(u, v) +% d = distance(u, v) +% Euclidean distance between u and v +% u and v should be column vectors or matrices in k-dim space, where k is +% the number of columns of u and v. +% +% if u is a single row, replicates u to size(v,1) + +if size(u,1) < size(v,1) + u = repmat(u,size(v,1),1); + if size(u,1) < size(v,1), error('Inputs must have same number of rows, or one row for 1st input!'),end +end + +delt = u - v; +d = (sum(delt .^ 2,2)) .^.5; + +end + + diff --git a/Cluster_contig_region_tools/cluster_set_intersection.m b/Cluster_contig_region_tools/cluster_set_intersection.m new file mode 100644 index 00000000..c6c49d9e --- /dev/null +++ b/Cluster_contig_region_tools/cluster_set_intersection.m @@ -0,0 +1,29 @@ +% intersect_cl = cluster_set_intersection(cls1, cls2, cls3, ...) +% +% Computes the intersection of the sets of clusters passed in. +% +% Sample usage: +% cl = cluster_intersection(robust0001_poscls, robust0002_poscls, robust0003_poscls); +% +% NB: Designed for sets of clusters. To compute the intersection between individual clusters, +% use cluster_intersection(). cluster_set_intersection will work, but is not needed. +% +% Created by Matthew Davidson, 07/08/24 + +function cl = cluster_set_intersection(varargin) + cluster_sets = {}; + other_args = {}; + for i=1:length(varargin) + switch(class(varargin{i})) + case 'struct' + cluster_sets{end+1} = clusters2CLU(varargin{i}); + otherwise + other_args{end+1} = varargin{i}; + end + end + if(length(cluster_sets) < 2) + error('Need 2 or more cluster sets to compute intersection.'); + end + + cl = cluster_intersection(cluster_sets{:}, other_args{:}); +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_table.m b/Cluster_contig_region_tools/cluster_table.m new file mode 100755 index 00000000..107f462e --- /dev/null +++ b/Cluster_contig_region_tools/cluster_table.m @@ -0,0 +1,486 @@ +function clusters = cluster_table(clusters, varargin) +% function cluster_table(clusters, [opt] subclusters) +% Print output of clusters in table +% Tor Wager +% +% Option to print text labels from Carmack atlas +% Database loading is done from talairach_info.mat which should be in the +% path. +% To speed up performance, load talairach_info.mat in the base workspace or +% calling function and include xyz, L3 and L5 as inputs to cluster_table. +% +% cluster_table(cl); % create subclusters on the fly, prompt for labels +% cluster_table(cl, 0, 0); % no subclusters, no labels +% cluster_table(cl, 1, 0); % do subclusters, no labels +% cluster_table(cl, 1, 1); % create subclusters on the fly, do labels +% cluster_table(..., 'tal_info', xyz, L3, L5); % 3 input variables following +% 'tal_info' are interpreted as xyz, L3, and +% L5 from talairach_info.mat. +% cluster_table(..., 'talairach'); % loads labels from taldata.mat +% (Talairach database) instead of +% talairach_info.mat. Note that you should +% use the 'tal_info' call above if xyz, L3, +% and L5 have already been loaded to the +% workspace from taldata.mat. Also, if the +% talairach database is being used, your +% cl.XYZmm values MUST correspond to the +% TALAIRACH, NOT MNI, database, or the +% labels will be innaccurate. +% cluster_table(..., 'writefile','filename'); % print table to ASCII file, +% 'filename', instead of to +% the matlab command window. +% cluster_table(..., 'myfield'); % any set of inputs from above, also print +% clusters.myfield in output +% +% +% List of edits can be found in programmers' notes (edit this function) + +% Programmers' notes +% Created by Tor Wager +% Edited 10/17/06 by jared to obviate multiple calls to Carmack_get_label +% should reduce the time it takes to get labels for clusters by 50%. +% Also edited to allow loading of the Talairach, instead of Carmack, +% database. +% Edited 9/07/06 by jared to fix incorrect behavior when using 'tal_info' +% keyword. Added 'writefile' keyword to allow writing to file instead of +% the command window. +% Edited 7/13/06 by jared to obviate the use of global variables +% Edited 7/8/06 by tor to handle Z values that are not row vectors +% previously returned incorrect max stats if Z is column vector +% +% Edited 7/8/06 to add subclusters from spm_max functionality +% this is now the default + +verbose = 1; +subc = 1; +if isempty(clusters), disp('No results to print.'); return, end +if isfield(clusters, 'M'), M = clusters(1).M; end +if length(varargin) > 0, subc = varargin{1}; end +if length(varargin) > 1, dolabs = varargin{2}; end + +if isa(clusters, 'region') + warning off + for i = 1:length(clusters) + cl(i) = struct(clusters(i)); + end + warning on + clusters = cl; +end + +% ----------------------------------------------------------------------------------- +% Set up user-input fields +% ----------------------------------------------------------------------------------- + +printfields = {}; +i=3; +while i <= length(varargin) + if strcmp(varargin{i}, 'tal_info') + xyz=varargin{i+1};L3=varargin{i+2};L5=varargin{i+3}; + i=i+3; + elseif strcmp(varargin{i},'talairach') + fprintf(1, 'Loading database.'); + load taldata + elseif strcmp(varargin{i}, 'writefile') + writefile=1;outfilename=varargin{i+1}; + i=i+1; + elseif strcmp(varargin{i}, 'noverbose') + verbose = 0; + else + printfields{end+1} = varargin{i}; + end + i=i+1; +end + +if ~exist('writefile','var'),writefile=0;end + +% ----------------------------------------------------------------------------------- +% Set up subclustering option +% ----------------------------------------------------------------------------------- +dosubcluster = 1; % default + +if isstruct(subc) + % do nothing +elseif subc == 0 + % turn option off + dosubcluster = 0; +else + % create + if verbose, fprintf(1, 'Getting local maxima within 10 mm for subcluster reporting\n'); end + try + subc = subclusters_from_local_max(clusters, 10); + catch + disp('**************************************************************') + disp('Error in subclusters_from_local_max') + disp('This could have multiple causes. One cause may be that a continguous region in a cluster') + disp('Is too big for matlab''s memory to calculate distances among all voxels with pdist.'); + disp('If you get this error with small clusters, you may be missing subfunctions or have other related issues.') + + subc = clusters; + dosubcluster = 0; + + end +end + + +% ----------------------------------------------------------------------------------- +% Set up labeling output (see bottom of function for old BA code!!!) +% ----------------------------------------------------------------------------------- + + + +% new for Carmack labels, do L3 and L5 (most informative levels). +if ~exist('dolabs', 'var') + dolabs = input('Do text labels for clusters (current = Carmack labels)?'); +end + +if dolabs && ~(exist('talairach_info.mat') == 2) + disp('Cannot find talairach_info.mat, so cannot get Talairach labels.'); + dolabs = 0; +end + +if dolabs + global xyz L3 L5 + fprintf(1, 'Loading database.'); + if ~exist('xyz', 'var') || isempty(xyz), load talairach_info xyz, end + if ~exist('L3', 'var') || isempty(L3), load talairach_info L3, end + if ~exist('L5', 'var') || isempty(L5), load talairach_info L5, end + + fprintf(1, 'Done. Getting text labels.'); + fprintf(1, '%03d', 0); + for i = 1:length(clusters) + fprintf(1, '\b\b\b%03d', i); + [name, perc, number, totalnum, stricbm{i}] = Carmack_get_label(clusters(i).XYZmm, {L3,L5}, xyz); +% [name, perc, number, totalnum, stricbm2{i}] = Carmack_get_label(clusters(i).XYZmm, L5, xyz); + end + if dosubcluster + fprintf(1, '\nGetting text labels for subclusters.'); + fprintf(1, '%03d', 0); + for i = 1:length(subc) + fprintf(1, '\b\b\b%03d', i); + [name, perc, number, totalnum, stricbm2{i}] = Carmack_get_label(subc(i).XYZmm, {L3,L5}, xyz); + end + end + fprintf(1, 'Done.\n'); +end + + + +% ----------------------------------------------------------------------------------- +% Start print-out; loop through clusters +% ----------------------------------------------------------------------------------- + +for i = 1:length(clusters) + + if isfield(clusters, 'correl'), + if ~isempty(clusters(i).correl) + try + cmatx(i, 1) = clusters(i).correl; + catch + cmatx(i, 1) = NaN; + end + else + cmatx(i, 1) = NaN; + end + else cmatx(i, 1) = NaN; + end + + cmatx(i, 2) = clusters(i).numVox; + + cmatx(i, 3) = get_maxstat(clusters(i)); + + + % ----------------------------------------------------------------------------------- + % Define variables to report in an easy-to-access matrix (cmatx) + % ----------------------------------------------------------------------------------- + + if isfield(clusters, 'corr_range'), + cmatx(i, 8) = clusters(i).corr_range(1); + cmatx(i, 9) = clusters(i).corr_range(end); + end + + if isfield(clusters, 'snr_avgts'), + cmatx(i, 10) = clusters(i).snr_avgts; + end + + if isfield(clusters, 'snr'), + cmatx(i, 11) = min(clusters(i).snr); + cmatx(i, 12) = max(clusters(i).snr); + end + + if isfield(clusters, 'numpos') && isfield(clusters, 'power80'), + cmatx(i, 13) = clusters(i).numpos; + cmatx(i, 14) = ceil(clusters(i).power80); + end + + if isfield(clusters, 'numpeaks'), cmatx(i, 15) = clusters(i).numpeaks; end + + x(i) = clusters(i).mm_center(1); + y(i) = clusters(i).mm_center(2); + z(i) = clusters(i).mm_center(3); + + if exist('v') == 1 + vox = mm2voxel(clusters(i).mm_center, V); + cmatx(i, 16) = round(v(vox(1), vox(2), vox(3))); + end +end + +% ----------------------------------------------------------------------------------- +% Print table header +% ----------------------------------------------------------------------------------- + +disp(' ') +if isempty(clusters), disp('No clusters.'), return, end +if isfield(clusters, 'name'), disp(clusters(1).name), end +if isfield(clusters, 'descrip1'), disp(clusters(1).descrip1), end +if isfield(clusters, 'descrip2'), disp(clusters(1).descrip2), end + +if writefile + fid=fopen(outfilename,'w'); +else + fid=1; +end + +if isfield(clusters, 'Z_descrip') && verbose + fprintf('Z field contains: %s (shown in maxstat)\n', clusters(1).Z_descrip); +elseif verbose + fprintf('Z field contains: Unknown statistic (shown in maxstat).\n'); +end + +if isfield(clusters, 'center') && exist('M') == 1 && isfield(clusters, 'from_cluster') + % sort by which cluster its from + try cmatx = sortrows(cmatx, 4);catch end + fprintf(fid, 'corr\tvoxels\tmaxstat\tfrom_clust\tmax_coords\n'); + for i = 1:size(cmatx, 1) + fprintf(fid, '%3.2f\t%3.0f\t%3.2f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t\n', cmatx(i, 1), cmatx(i, 2), cmatx(i, 3), cmatx(i, 4), cmatx(i, 5), cmatx(i, 6), cmatx(i, 7)); + end +else + disp(' ') + if isfield(clusters, 'shorttitle'), fprintf(fid, 'Name\t'); end + fprintf(fid, 'index\tx\ty\tz\tcorr\tvoxels\tvolume_mm3\tmaxstat\t'); + if isfield(clusters, 'numpeaks'), fprintf(fid, 'numpeaks\t'); end + if isfield(clusters, 'corr_range'), fprintf(fid, 'mincorr\tmaxcorr\t'); end + if isfield(clusters, 'snr_avgts'), fprintf(fid, 'snr_avgts(d)\t'); end + if isfield(clusters, 'snr'), fprintf(fid, 'minsnr\tmaxsnr\t'); end + if isfield(clusters, 'numpos') && isfield(clusters, 'power80'), + fprintf(fid, 'numpos\tpower80\t'); + end + if exist('v') == 1 + fprintf(fid, ('BA\tBA_composition\t')); + end + if exist('stricbm') == 1 + %fprintf(fid, ('ICBM_single_subj\t')) + fprintf(fid, ('Tal_Labels\t')); + end +% +% if exist('stricbm2') == 1 +% %fprintf(fid, ('ICBM_single_subj\t')) +% fprintf(fid, ('Carmack_Level5\t')); +% end + + % print additional fields + for i = 1:length(printfields) + fprintf(fid, '%s\t', printfields{i}); + end + + fprintf(fid, '\n'); + + % ----------------------------------------------------------------------------------- + % Print a row for each cluster + % ----------------------------------------------------------------------------------- + + for i = 1:size(cmatx, 1) + if isfield(clusters, 'shorttitle'), fprintf(fid, '%s\t', clusters(i).shorttitle);end + fprintf(fid, '%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.0f\t%3.0f\t%3.2f\t', i, x(i), y(i), z(i), cmatx(i, 1), cmatx(i, 2), cmatx(i, 2).*prod(clusters(i).voxSize), cmatx(i, 3)); + if isfield(clusters, 'numpeaks'), fprintf(fid, '%3.0f\t', cmatx(i, 15)); end + if isfield(clusters, 'corr_range'), fprintf(fid, '%3.2f\t%3.2f\t', cmatx(i, 8), cmatx(i, 9)); end + if isfield(clusters, 'snr_avgts'), fprintf(fid, '%3.2f\t', cmatx(i, 10)); end + if isfield(clusters, 'snr'), fprintf(fid, '%3.2f\t%3.2f\t', cmatx(i, 11), cmatx(i, 12)); end + if isfield(clusters, 'numpos') && isfield(clusters, 'power80'), + fprintf(fid, '%3.0f\t%3.0f\t', cmatx(i, 13), cmatx(i, 14)); + end + if exist('v', 'var') == 1 + fprintf(fid, ('%3.0f\t'), cmatx(i, 16)); + end + + if exist('strs', 'var') == 1 + fprintf(fid, '%s\t', strs); + end + + if exist('stricbm', 'var') == 1 + fprintf(fid, ('%s\t'), stricbm{i}); + end + +% if exist('stricbm2', 'var') == 1 +% fprintf(fid, ('%s\t'), stricbm2{i}); +% end + + % print additional fields + for j = 1:length(printfields) + if ~isfield(clusters, printfields{j}) + warning([printfields{j} ' is not a field in clusters.']); + else + myval = clusters(i).(printfields{j}); + if isempty(myval), myval = NaN; end + + % get formatting string based on content + fmtstring = get_formatstring(myval); + + fprintf(fid, fmtstring, myval); + + + end + end + + fprintf(fid, '\n'); + + if dosubcluster + % print sub-cluster table + whsc = cat(1, subc.from_cluster) == i; + whsc = find(whsc)'; + if length(whsc)>1 + for j = whsc + if ~dolabs + print_row(subc(j), j, clusters, fid) + else + print_row(subc(j), j, clusters, fid,stricbm2) + end + end + end + end + + end +end + +if writefile,fclose(fid);end + +return + + + + + +function print_row(clusters, i, bigcl, fid, varargin) +% prints a row for a subcluster (varargin{1}) below its corresponding cluster + +if ~isempty(varargin) + stricbm2=varargin{1}; +end + +if isfield(clusters, 'correl'), cmatx(i, 1) = clusters(1).correl; +else cmatx(i, 1) = NaN; +end + +cmatx(i, 2) = clusters(1).numVox; +cmatx(i, 3) = get_maxstat(clusters(1)); + +if isfield(clusters, 'corr_range'), + cmatx(i, 8) = clusters(1).corr_range(1); + cmatx(i, 9) = clusters(1).corr_range(end); +elseif isfield(bigcl, 'corr_range'), + cmatx(i, 8) = NaN; + cmatx(i, 9) = NaN; +end + +if isfield(clusters, 'snr_avgts'), + cmatx(i, 10) = clusters(1).snr_avgts; +elseif isfield(bigcl, 'snr_avgts'), + cmatx(i, 10) = NaN; + cmatx(i, 10) = NaN; +end + +if isfield(clusters, 'snr'), + cmatx(i, 11) = min(clusters(1).snr); + cmatx(i, 12) = max(clusters(1).snr); +elseif isfield(bigcl, 'snr'), + cmatx(i, 11) = NaN; + cmatx(i, 12) = NaN; +end + +if isfield(clusters, 'numpos') && isfield(clusters, 'power80') + cmatx(i, 13) = clusters(1).numpos; + cmatx(i, 14) = ceil(clusters(1).power80); +elseif isfield(bigcl, 'numpos') && isfield(bigcl, 'power80') + cmatx(i, 13) = NaN; + cmatx(i, 14) = NaN; +end + +%if isfield(clusters, 'numpeaks'), +% cmatx(i, 15) = clusters(1).numpeaks; +%elseif isfield(bigcl, 'numpeaks'), +% cmatx(i, 15) = NaN; +%end + +fprintf(fid, '%s\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.0f\t%3.5f\t', ... + '->', clusters(1).mm_center(1), clusters(1).mm_center(2), clusters(1).mm_center(3), ... + cmatx(i, 1), cmatx(i, 2), cmatx(i, 3)); + +%if isfield(clusters, 'numpeaks'), fprintf(fid, '%3.0f\t', cmatx(i, 15)), end +fprintf(fid,'\t'); % skip numpeaks - makes no sense for subcluster +if isfield(bigcl, 'corr_range'), fprintf(fid, '%3.2f\t%3.2f\t', cmatx(i, 8), cmatx(i, 9)); end +if isfield(bigcl, 'snr_avgts'), fprintf(fid, '%3.2f\t', cmatx(i, 10)); end +if isfield(bigcl, 'snr'), fprintf(fid, '%3.2f\t%3.2f\t', cmatx(i, 11), cmatx(i, 12)); end +if isfield(bigcl, 'numpos') & isfield(bigcl, 'power80'), + fprintf(fid, '%3.0f\t%3.0f\t', cmatx(i, 13), cmatx(i, 14)); +end + +if exist('stricbm2', 'var') == 1 + fprintf(fid, ('%s\t'), stricbm2{i}); +end + +fprintf(fid, '\n'); + +return + + + + +function maxstat = get_maxstat(cl) +% max absolute value +if size(cl.Z, 2) ~= size(cl.XYZmm, 2), cl.Z = cl.Z'; end +[maxabs, whmax] = max(abs(cl.Z(1, :))); +maxstat = cl.Z(1, whmax(1)); +return + + + +function fmtstring = get_formatstring(myval) +if isempty(myval) + fmtstring = '%3.0f\t'; +elseif ischar(myval) + fmtstring = '%s\t'; +elseif isinteger(myval) || islogical(myval) || all( (myval - round(myval)) == 0 ) + fmtstring = repmat('%3.0f\t', 1, length(myval)); +elseif myval < .01 + fmtstring = repmat('%3.4f\t', 1, length(myval)); +else + fmtstring = repmat('%3.2f\t', 1, length(myval)); +end + +return + +% try to load table with BAs. Brodmann Areas. +% Old: for ICBM and old Talairach atlas. +% +% if isfield(clusters, 'BAstring') +% strs = str2mat(clusters.BAstring); +% else +% try +% V = spm_vol(which('Tal_gray.img')); v = spm_read_vols(V); +% V.M = V.mat; +% +% % try to get BA composition for all clusters +% %diary off +% %disp('Finding composition of all clusters - ctrl c to cancel') +% %[clusters, strs, clusvec, all_bas, ba_counts] = cluster_ba(clusters, 1:length(clusters)); +% %disp('Success - printing table.') +% %diary on +% +% catch +% end +% end + +% try to get ICBM composition for all clusters +% stricbm = icbm_orthview_label(clusters); +% for i = 1:length(clusters) +% clusters(i).ICBMstr = stricbm{i}; +% end \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_table_old.m b/Cluster_contig_region_tools/cluster_table_old.m new file mode 100755 index 00000000..4087833f --- /dev/null +++ b/Cluster_contig_region_tools/cluster_table_old.m @@ -0,0 +1,288 @@ +function clusters = cluster_table(clusters,varargin) +% function cluster_table(clusters,[opt] subclusters) +% Print output of clusters in table +% Tor Wager +% +% Option to print text labels from Carmack atlas +% Database loading is done from talaraich_info.mat +% which should be in the path. +% To speed up performance, declare global xyz, L3 and L5 +% in the base workspace and re-use them in repeated calls +% to cluster_table. + +global xyz L3 L5 + +if isfield(clusters,'M'),M = clusters(1).M;, end +if length(varargin) > 0, subc = varargin{1};, end + +% try to load table with BAs. Brodmann Areas. +% Old: for ICBM and old Talairach atlas. +% +% if isfield(clusters,'BAstring') +% strs = str2mat(clusters.BAstring); +% else +% try +% V = spm_vol(which('Tal_gray.img')); v = spm_read_vols(V); +% V.M = V.mat; +% +% % try to get BA composition for all clusters +% %diary off +% %disp('Finding composition of all clusters - ctrl c to cancel') +% %[clusters,strs,clusvec,all_bas,ba_counts] = cluster_ba(clusters,1:length(clusters)); +% %disp('Success - printing table.') +% %diary on +% +% catch +% end +% end + +% try to get ICBM composition for all clusters +% stricbm = icbm_orthview_label(clusters); +% for i = 1:length(clusters) +% clusters(i).ICBMstr = stricbm{i}; +% end + +% new for Carmack labels, do L3 and L5 (most informative levels). +dolabs = 1; %dolabs = input('Do text labels for clusters (current = Carmack labels)?'); + +if dolabs && ~(exist('talairach_info.mat') == 2) + disp('Cannot find talairach_info.mat, so cannot get Talairach labels.'); + dolabs = 0; +end + +if dolabs + fprintf(1,'Loading database.'); + if isempty(xyz), load talairach_info xyz, end + if isempty(L3), load talairach_info L3, end + if isempty(L5), load talairach_info L5, end + + fprintf(1,'Done. Getting text labels.'); + fprintf(1,'%03d',0); + for i = 1:length(clusters) + fprintf(1,'\b\b\b%03d',i); + [name,perc,number,totalnum,stricbm{i}] = Carmack_get_label(clusters(i).XYZmm,L3,xyz); + [name,perc,number,totalnum,stricbm2{i}] = Carmack_get_label(clusters(i).XYZmm,L5,xyz); + end + + fprintf(1,'Done.\n'); +end + +for i = 1:length(clusters) + + if isfield(clusters,'correl'), + if ~isempty(clusters(i).correl) + try + cmatx(i,1) = clusters(i).correl;, + catch + cmatx(i,1) = NaN; + end + else + cmatx(i,1) = NaN; + end + else cmatx(i,1) = NaN; + end + + cmatx(i,2) = clusters(i).numVox; + maxZ = clusters(i).Z(abs(clusters(i).Z(1,:)) == max(abs(clusters(i).Z(1,:)))); + cmatx(i,3) = maxZ(1); + + % not working yet + %clusters(i).cor_stat = tor_r2z(a(1,2),length(clusters(i).timeseries-1); + + % for use with tor_get_spheres2.m, breaking up cluster into spheres + % OLD + %if isfield(clusters,'center') & exist('M') == 1 & isfield(clusters,'from_cluster') + % cmatx(i,4) = clusters(i).from_cluster; + % centers{i} = (round(voxel2mm(clusters(i).XYZ(:,clusters(i).Z == max(clusters(i).Z)),M)')); + % cmatx(i,5) = centers{i}(1); + % cmatx(i,6) = centers{i}(2); + % cmatx(i,7) = centers{i}(3); + %end + + % ----------------------------------------------------------------------------------- + % Define variables to report in an easy-to-access matrix (cmatx) + % ----------------------------------------------------------------------------------- + + if isfield(clusters,'corr_range'), + cmatx(i,8) = clusters(i).corr_range(1); + cmatx(i,9) = clusters(i).corr_range(end); + end + + if isfield(clusters,'snr_avgts'), + cmatx(i,10) = clusters(i).snr_avgts; + end + + if isfield(clusters,'snr'), + cmatx(i,11) = min(clusters(i).snr); + cmatx(i,12) = max(clusters(i).snr); + end + + if isfield(clusters,'numpos') & isfield(clusters,'power80'), + cmatx(i,13) = clusters(i).numpos; + cmatx(i,14) = ceil(clusters(i).power80); + end + + if isfield(clusters,'numpeaks'), cmatx(i,15) = clusters(i).numpeaks;, end + + x(i) = clusters(i).mm_center(1); + y(i) = clusters(i).mm_center(2); + z(i) = clusters(i).mm_center(3); + + if exist('v') == 1 + vox = mm2voxel(clusters(i).mm_center,V); + cmatx(i,16) = round(v(vox(1),vox(2),vox(3))); + end +end + + % ----------------------------------------------------------------------------------- + % Print table header + % ----------------------------------------------------------------------------------- + + disp(' ') + if isempty(clusters), disp('No clusters.'), return, end + if isfield(clusters,'name'),disp(clusters(1).name),end + +if isfield(clusters,'center') & exist('M') == 1 & isfield(clusters,'from_cluster') + % sort by which cluster its from + try,cmatx = sortrows(cmatx,4);,catch,end + fprintf(1,'corr\tvoxels\tmaxZ\tfrom_clust\tmax_coords\n') + for i = 1:size(cmatx,1) + fprintf(1,'%3.2f\t%3.0f\t%3.2f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t\n',cmatx(i,1),cmatx(i,2),cmatx(i,3),cmatx(i,4),cmatx(i,5),cmatx(i,6),cmatx(i,7)) + end +else + disp(' ') + if isfield(clusters,'shorttitle'),fprintf(1,'Name\t'),end + fprintf(1,'index\tx\ty\tz\tcorr\tvoxels\tvolume_mm3\tmaxZ\t') + if isfield(clusters,'numpeaks'),fprintf(1,'numpeaks\t'), end + if isfield(clusters,'corr_range'), fprintf(1,'mincorr\tmaxcorr\t'), end + if isfield(clusters,'snr_avgts'),fprintf(1,'snr_avgts(d)\t'), end + if isfield(clusters,'snr'),fprintf(1,'minsnr\tmaxsnr\t'), end + if isfield(clusters,'numpos') & isfield(clusters,'power80'), + fprintf(1,'numpos\tpower80\t') + end + if exist('v') == 1 + fprintf(1,('BA\tBA_composition\t')) + end + if exist('stricbm') == 1 + %fprintf(1,('ICBM_single_subj\t')) + fprintf(1,('Carmack_Tal_Labels\t')) + end + + if exist('stricbm2') == 1 + %fprintf(1,('ICBM_single_subj\t')) + fprintf(1,('Carmack_Level5\t')) + end + fprintf(1,'\n') + + % ----------------------------------------------------------------------------------- + % Print a row for each cluster + % ----------------------------------------------------------------------------------- + + for i = 1:size(cmatx,1) + if isfield(clusters,'shorttitle'),fprintf(1,'%s\t',clusters(i).shorttitle);,end + fprintf(1,'%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.0f\t%3.0f\t%3.5f\t',i,x(i),y(i),z(i),cmatx(i,1),cmatx(i,2),cmatx(i,2).*prod(clusters(i).voxSize),cmatx(i,3)) + if isfield(clusters,'numpeaks'),fprintf(1,'%3.0f\t',cmatx(i,15)), end + if isfield(clusters,'corr_range'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,8),cmatx(i,9)), end + if isfield(clusters,'snr_avgts'), fprintf(1,'%3.2f\t',cmatx(i,10)), end + if isfield(clusters,'snr'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,11),cmatx(i,12)), end + if isfield(clusters,'numpos') & isfield(clusters,'power80'), + fprintf(1,'%3.0f\t%3.0f\t',cmatx(i,13),cmatx(i,14)) + end + if exist('v') == 1 + fprintf(1,('%3.0f\t'),cmatx(i,16)) + end + + if exist('strs') == 1 + fprintf(1,'%s\t',strs(i,:)) + end + + if exist('stricbm') == 1 + fprintf(1,('%s\t'),stricbm{i}) + end + + if exist('stricbm2') == 1 + fprintf(1,('%s\t'),stricbm2{i}) + end + + fprintf(1,'\n') + + if length(varargin) > 0 + % print sub-cluster table + whsc = cat(1,subc.from_cluster) == i; + for j = find(whsc)' + print_row(subc(j),j,clusters) + end + end + + end +end + +return + + + + + +function print_row(clusters,i,bigcl) +% prints a row for a subcluster (varargin{1}) below its corresponding cluster + + if isfield(clusters,'correl'), cmatx(i,1) = clusters(1).correl;, + else cmatx(i,1) = NaN; + end + + cmatx(i,2) = clusters(1).numVox; + cmatx(i,3) = max(clusters(1).Z); + + if isfield(clusters,'corr_range'), + cmatx(i,8) = clusters(1).corr_range(1); + cmatx(i,9) = clusters(1).corr_range(end); + elseif isfield(bigcl,'corr_range'), + cmatx(i,8) = NaN; + cmatx(i,9) = NaN; + end + + if isfield(clusters,'snr_avgts'), + cmatx(i,10) = clusters(1).snr_avgts; + elseif isfield(bigcl,'snr_avgts'), + cmatx(i,10) = NaN; + cmatx(i,10) = NaN; + end + + if isfield(clusters,'snr'), + cmatx(i,11) = min(clusters(1).snr); + cmatx(i,12) = max(clusters(1).snr); + elseif isfield(bigcl,'snr'), + cmatx(i,11) = NaN; + cmatx(i,12) = NaN; + end + + if isfield(clusters,'numpos') & isfield(clusters,'power80'), + cmatx(i,13) = clusters(1).numpos; + cmatx(i,14) = ceil(clusters(1).power80); + elseif isfield(bigcl,'numpos') & isfield(bigcl,'power80'), + cmatx(i,13) = NaN; + cmatx(i,14) = NaN; + end + + %if isfield(clusters,'numpeaks'), + % cmatx(i,15) = clusters(1).numpeaks;, + %elseif isfield(bigcl,'numpeaks'), + % cmatx(i,15) = NaN; + %end + +fprintf(1,'%s\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.0f\t%3.5f\t', ... + '->',clusters(1).mm_center(1),clusters(1).mm_center(2),clusters(1).mm_center(3), ... + cmatx(i,1),cmatx(i,2),cmatx(i,3)) + + %if isfield(clusters,'numpeaks'),fprintf(1,'%3.0f\t',cmatx(i,15)), end + fprintf('\t') % skip numpeaks - makes no sense for subcluster + if isfield(bigcl,'corr_range'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,8),cmatx(i,9)), end + if isfield(bigcl,'snr_avgts'), fprintf(1,'%3.2f\t',cmatx(i,10)), end + if isfield(bigcl,'snr'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,11),cmatx(i,12)), end + if isfield(bigcl,'numpos') & isfield(bigcl,'power80'), + fprintf(1,'%3.0f\t%3.0f\t',cmatx(i,13),cmatx(i,14)) + end + + fprintf(1,'\n') + +return \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_table_successive_threshold.m b/Cluster_contig_region_tools/cluster_table_successive_threshold.m new file mode 100644 index 00000000..4e581a52 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_table_successive_threshold.m @@ -0,0 +1,140 @@ +function cl = cluster_table_successive_threshold(cl,varargin) +% cl = cluster_table_successive_threshold(cl,[sizethr]) +% +% cluster table of a cell array of clusters cl{1} cl{2} etc. +% Prints table of cl{1} and then any additional regions in cl{2:n} that are +% not within 10 mm of a previously printed cluster +% +% also: merges clusters in set within 10 mm +% +% table titles are hard-coded to be consistent with meta-analysis toolbox right now +% +% tor wager, oct/nov 06 +% +% Examples: +% Print a series of tables with custom fields: +% cl = cluster_table_successive_threshold(cl,5,'myfield1','myfield2') +% +% use merge_nearby_clusters +% and subclusters_from_local_max +% or some other way to get clusters appropriately separated and distanced +% before running. +% see Meta_cluster_tools for code to run this for meta-analysis. + +higher_thr = []; +mind = 10; % distance +showsubpeaks = 0; % applies to 2nd-nth cells of clusters + +if nargin < 2, sizethr = 0; else sizethr = varargin{1}; end + +doextrafields = 0; +if nargin > 2 + doextrafields = 1; + % we have additional fields in clusters to print in table + myfields = varargin(2:end); +end + + + +% merge nearby clusters +% % for i = 1:length(cl) +% % if ~isempty(cl{i}) +% % cl{i} = merge_nearby_clusters(cl{i},mind); +% % end +% % end +% % +% % if doextrafields +% % sum_merged_vals; +% % end + +fprintf(1,'Height\n'); +if doextrafields + base = 'cluster_table(cl{1},showsubpeaks,0'; + estr = build_call(base,myfields); + eval(estr); +else + cluster_table(cl{1},1,0); +end + +fprintf(1,'\n'); + + +if isempty(cl{1}) + % do nothing +else +% % subc = subclusters_from_local_max(cl{1}, mind); +% % higher_thr = subc; + higher_thr = cl{1}; +end + +strs = {'Stringent' 'Medium' 'Lenient'}; + +for i = 2:length(cl) + + fprintf(1,'Additional regions at Extent: %s and size >= %3.0f\n',strs{i-1},sizethr); + % % subc = subclusters_from_local_max(cl{i}, 10); + + if ~isempty(cl{i}) + subc = cl{i}; + + if isempty(higher_thr) + dosubctable = showsubpeaks; % subclusters in table, this is first table... + outside_range = ones(1,length(subc)); + else + dosubctable = 0; + [close_enough,outside_range,nearest_distance,closest_cluster] = cluster_close_enough(higher_thr,subc,mind); + end + + % size threshold + sz = cat(1,subc.numVox); sz = sz' >= sizethr; + + if doextrafields + base = 'cluster_table(subc(outside_range & sz),dosubctable,0'; + estr = build_call(base,myfields); + eval(estr); + else + cluster_table(subc(outside_range & sz),dosubctable,0); + end + + fprintf(1,'\n'); + + higher_thr = merge_clusters(higher_thr,subc); + + % save for output, to plot and stuff + cl{i} = subc(outside_range & sz); + end +end + + +% nested functions +% % function sum_merged_vals +% % disp('Summing results of extra fields for merged clusters.') +% % disp('Appropriate for meta-analysis, but maybe not for other applications.') +% % for i = 1:length(cl) +% % for j = 1:length(cl{i}) +% % for k = 1:length(myfields) +% % cl{i}(j).(myfields{k}) = sum(cl{i}(j).(myfields{k})); +% % end +% % end +% % end +% % end + + +end % main function + + + + +function estr = build_call(estr,myfields) + % build table function call +%estr = 'cluster_table(subc(outside_range & sz),dosubctable,0'; + +for i = 1:length(myfields) + estr = [estr ',''' myfields{i} '''']; +end +estr = [estr ');']; + +end + + + \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/adjacent_test.m b/Cluster_contig_region_tools/cluster_tool/adjacent_test.m new file mode 100644 index 00000000..b3fcca9e --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/adjacent_test.m @@ -0,0 +1,283 @@ +function [ind]=adjacent_test(XYZ,varargin) + +% USAGE: +% +% [ind]=adjacent_test(XYZ) +% [ind]=adjacent_test(...,criterion) +% [ind]=adjacent_test(...,criterion,min_adj) +% [ind]=adjacent_test(...,criterion,min_adj,adjacency) +% [ind]=adjacent_test(...,criterion,min_adj,adjacency,erosions) +% +% Note that inputs must be specified in the correct order. +% +% Returns a vector of same length as the non-tripleton dimension of XYZ, +% with numeric values indicating which cluster each voxel belongs too. +% Thus, assuming each column refers to a voxel coordinate, +% XYZ(:,ind==1) is all of the voxels in one cluster, and +% XYZ(:,ind==2) is another. +% +% adjacency is a string, which must be 'surface' (6 connectivity scheme), +% 'edge' (18 connectivity scheme), or 'corner' (26 connectivity scheme). +% Default is 'edge'. +% +% criterion (must be scalar) is the number of voxels a voxel must be +% adjacent too for it to be considered a 'core' cluster voxel (all voxels +% adjacent to 'core' voxels will be included in a cluster, but those that +% are adjacent to voxels that are part of a cluster but are not themselves +% 'core' voxels will not be included in the cluster). Setting criteria +% above the connectivity value associated with a given adjacency criteria +% will result in no clusters. Default is 0. +% +% min_adj (must be scalar) is the minimum number of voxels that a voxel +% must be adjacent to for it to be included in a cluster. Voxels adjacent +% to less voxels than min_adj will recieve a value of 0 in ind. Setting +% criteria above the connectivity value associated with a given adjacency +% criteria will result in no clusters. Default is 0. +% +% +% erosions is the integer number of times that the input image will be +% eroded according to the criterion and adjacency inputs before 'core' +% cluster voxels are defined. Default is 1. +% +% +% Note that: +% ind=adjacent_test(XYZ,'edge'); %or ind=adjacent_test(XYZ,'edge',0,0,1); +% ind2=spm_clusters(XYZ); +% isequal(ind,ind2) +% +% ans = +% +% 1 +% +% +% +% + +persistent runN +runN = 1; + +if isempty(varargin) + criterion=0; + min_adj=0; + adjacency='edge'; + er = 1; +elseif length(varargin)<2 + criterion=varargin{1}; + min_adj=0; + adjacency='edge'; + er = 1; +elseif length(varargin)<3 + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency='edge'; + er = 1; +elseif length(varargin)<4 + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency=varargin{3}; + er = 1; +else + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency=varargin{3}; + er = varargin{4}; +end + +if ~strcmp(adjacency,'corner')&&~strcmp(adjacency,'edge')&&~strcmp(adjacency,'surface') + error('Adjacency input incorrectly specified') +end + +if size(XYZ,1)~=3 + if size(XYZ,2)~=3 + error('XYZ matrix must have a tripleton dimension!') + else + XYZ=XYZ'; + end +end + +if ~isequal(XYZ,round(XYZ))||any(XYZ(:)<1) + error('XYZ input contains invalid or duplicate coordinates!') +end + +if strcmp(adjacency,'edge')&&~criterion&&~min_adj&&er==1 + try ind=spm_clusters(XYZ);return,catch end +end + + + +ind = voxel_test([],XYZ,adjacency,min_adj,criterion); +for i = 2:er + ind = voxel_test(logical(ind),XYZ,adjacency,min_adj,criterion); +end + +k=1; +while k<=max(ind) + if ~any(ind==k) + ind(ind>k)=ind(ind>k)-1; + k=k-1; + end + k=k+1; +end + + + +disp('Attaching stray voxels to existing clusters') + +lost = []; +for i = 1:er + attach = zeros(size(ind)); + for k=1:length(ind) + + if ~rem(k,10000) + disp([int2str(((k+(i-1)*length(ind))/(er*length(ind)))*100) '% complete']) + end + + if ~ind(k) + + adj = find_adjacent(k,XYZ,adjacency,[]); + + if ( sum(adj) > min_adj && ( sum(adj)>criterion || i==er ) ) && any(ind(adj)) + attach(k) = 1; + end + end + end + + for k = 1:length(ind) + if attach(k) + + adj = find_adjacent(k,XYZ,adjacency,[]); + + if any(ind(adj)) + if min(nonzeros(ind(adj)))==max(ind(adj)) + ind(k) = max(ind(adj)); + elseif i < er + ind(k) = attach_lost(ind,adj); + else + lost(end+1) = k; + end + end + end + end +end + +disp('done') + +for i = 1:length(lost) + ind(lost(i)) = attach_lost(ind,find_adjacent(lost(i),XYZ,adjacency,[])); +end + +end + + + + +function ind = voxel_test(index,XYZ,adjacency,min_adj,criterion) + +persistent runN + +if isempty(runN) + runN = 1; +else + runN = runN + 1; +end + +ind = zeros(1,size(XYZ,2)); + +for k=1:length(ind) + + if ~rem(k,3000) + disp(['Erosion #' num2str(runN) ' is ' int2str((k/length(ind))*100) '% complete']) + end + + adj = find_adjacent(k,XYZ,adjacency,index); + + if sum(adj) < min_adj + 1 || sum(adj) < criterion + 1 + continue + end + + if any(ind(adj)) + ind(k) = min(nonzeros(ind(adj))); + if min(nonzeros(ind(adj)))~=max(ind(adj)) + cls = unique(nonzeros(ind(adj))); + for i = 1:length(cls) + ind(ind==cls(i)) = min(cls); + end + end + else + ind(k) = max(ind) + 1; + end +end + +end + + +function [adj] = find_adjacent(k,XYZ,adjacency,index) + +if isempty(index) + index = true(1,size(XYZ,2)); +end + +adjacent=zeros(size(XYZ)); +for dim = 1:3 + a = ( (XYZ(dim,k)==XYZ(dim,:)+1) | (XYZ(dim,k)==XYZ(dim,:)-1) ) & index; + b = (XYZ(dim,k)==XYZ(dim,:)) & index; + if ~isempty(a) + adjacent(dim,a)=1; + end + if ~isempty(b) + adjacent(dim,b)=2; + end +end + +% slower: but agrees in faces... +% tic +% tor_adj = [XYZ(1, :) - XYZ(1,k); XYZ(2, :) - XYZ(2,k); XYZ(3, :) - XYZ(3,k)]; +% tor_adj = abs(tor_adj); +% tor_adj(tor_adj > 1) = Inf; % not actually adjacent +% tor_adj = sum(tor_adj); +% tor_adj(~index) = Inf; +% toc + +% 1 = share face, 2 = share edge, 3 = share corner + +% in "adjacent" var: +% adjacent faces differ by only one coord +% adjacent edges differ by 1 or 2 coords +% adjacent corners differ by 1-3 coords + +% these methods also include "self" voxel +if strcmp(adjacency,'corner') + adj = all(adjacent); +elseif strcmp(adjacency,'edge') + adj = all(adjacent) & any(adjacent - 1); +elseif strcmp(adjacency,'surface') + adj = all(adjacent) & sum(adjacent) > 4; % tor fixed bug 12/11/13 +end + +end + + +function [lind] = attach_lost(ind,adj) + +cls=unique(nonzeros(ind(adj))); +for m=1:length(cls) + num(m)=sum((ind(adj))==cls(m)); +end +if sum(num==max(num))==1 + lind=cls(num==max(num)); +else + a=find(num==max(num)); + for m=1:length(a) + l(m)=sum(ind==cls(a(m))); + end + if sum(l==max(l))==1 +% warning(['Voxel at ' num2str(XYZ(1,lost(i))) ',' num2str(XYZ(2,lost(i))) ',' num2str(XYZ(3,lost(i))) ' shares an equal number of adjacent voxels with more than one seperate cluster. It is being associated with the largest of them.']); + lind=cls(a(l==max(l))); + else + warning(['Voxel shares an equal number of adjacent voxels with more than one seperate cluster of identical size. It is being randomly associated with one of them.']); + r=randperm(length(a)); + lind=cls(a(r(1))); + end +end + +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/check_cl.m b/Cluster_contig_region_tools/cluster_tool/check_cl.m new file mode 100644 index 00000000..119d8302 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/check_cl.m @@ -0,0 +1,141 @@ +function clout=check_cl(clin) + +% Usage: +% clout=check_cl(clin) +% +% Checks for the existence of important fields in the cl structure and +% attempts to resolve any problems by asking for using input at the command +% line. Checks some other possible problems in the cl structure as well. +% +% +fdir=pwd; + +if length(size(clin))>2||min(size(clin))>1 + clin=clin(:); +end + +if ~isfield(clin,'M')||~isfield(clin,'dim') + if ~isfield(clin,'mat') + beep + disp('Input structure does not contain either an affine transformations matrix or image dimension information.\n'); + space_src=input('Would you like to load the correct information from an image or a cluster [I/c]?\n','s'); + if strcmp(space_src,'c')||strcmp(space_src,'C') + handles.pwd=pwd;cd(fdir); + [FileName,PathName]=uigetfile('*.mat','Select .mat file containing cl structure'); + cd(handles.pwd); + fdir=PathName; + try load([PathName FileName]);M=cl(1).M;dim=cl(1).dim;catch,error('Could not retrieve affine matrix from cl structure in specified file. Possibly no cl structure in the .mat file'),end + else + handles.pwd=pwd;cd(fdir); + [FileName,PathName]=uigetfile('*.img','Select .img file'); + cd(handles.pwd); + fdir=PathName; + try space=iimg_read_img([PathName FileName]);M=space.mat;dim=space.dim;catch,error('Could not retrieve affine matrix from specified file'),end + end + for k=1:length(clin) + clin(k).M=M; + clin(k).dim=dim; + end + else + for k=1:length(clin) + clin(k).M=cl(k).mat; + end + clin=rmfield(clin,'mat'); + end +end + +if ~isfield(clin,'voxSize') + for k=1:length(clin) + clin(k).voxSize=diag(clin(k).M)'; + end +end + +poss{1}=clin(1).M; +for k=1:length(clin) + equal=0; + for j=1:length(poss) + if isequal(poss{j},clin(k).M) + equal=1; + end + end + if ~equal + poss{end+1}=clin(k).M; + end +end +if length(poss)>1 + disp('The input structure contains at least two non-identical affine matrices.\nFunctions that make reference to the image space of the structure typically use only the affine matrix stored in cl(1).M.\n'); + fix=input('Would you like to try to remedy this [Y/n]?\n','s'); + if ~strcmp(fix,'n')&&~strcmp(fix,'N') + for k=1:length(poss) + disp(['Matrix #' num2str(k) ':']) + fprintf(1,'%4.2f\t%4.2f\t%4.2f\t%4.2f\n%4.2f\t%4.2f\t%4.2f\t%4.2f\n%4.2f\t%4.2f\t%4.2f\t%4.2f\n%4.2f\t%4.2f\t%4.2f\t%4.2f\n\n\n',poss{k}) + end + toUse=input('Please enter the Matrix # for the affine matrix you would like to use.\nIt is highly recommended that you select the matrix that corresponds to the lowest spatial resolution.\n'); + M=poss{toUse}; + for k=1:length(clin) + if isequal(M,clin(k).M) + dim=clin(k).dim; + break + end + end + for k=1:length(clin) + if ~isequal(M,clin(k).M) + [clin(k).XYZ,point_list]=mmToVoxel(clin(k).XYZmm,M,'valid'); + clin(k).XYZmm=voxelToMm(clin(k).XYZ,M); + clin(k).M=M; + for m=length(point_list) + ind=find(point_list(:)==point_list(m)); + clin(k).Z(ind)=mean(clin(k).Z(ind)); + end + clin(k).Z=clin(k).Z(unique(point_list)); + end + clin(k).voxSize=diag(M(1:3,1:3))'; + clin(k).dim=dim; + clin(k).numVox=length(clin(k).Z); + end + end +end + +if ~isfield(clin,'title') + clin(1).title=input('cl structure has no title. Please enter one now:\n','s'); + for k=2:length(clin) + clin(k).title=clin(1).title; + end +end + +if ~isfield(clin,'threshold') + clin(1).threshold(1)=input('cl structure has no threshold. Please enter lower threshold:\n'); + clin(1).threshold(2)=input('Please enter upper threshold:\n'); + for k=2:length(clin) + clin(k).threshold=clin(1).threshold; + end +end + +if ~isfield(clin,'name') + for k=1:length(clin) + clin(k).name=''; + end +end + +if ~isfield(clin,'numVox') + for k=1:length(clin) + clin(k).numVox=length(clin(k).Z); + end +end + +for k=1:length(clin) + clin(k).mm_center=center_of_mass(clin(k).XYZmm,clin(k).Z); +end + +for k=1:length(clin) + if size(clin(k).XYZ,1)~=3 + clin(k).XYZ=clin(k).XYZ'; + end + if size(clin(k).XYZmm,1)~=3 + clin(k).XYZmm=clcin(k).XYZmm'; + end +end + +clout=clin; + +disp('Done checking cluster integrity.') \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/cl_cl_intersect.m b/Cluster_contig_region_tools/cluster_tool/cl_cl_intersect.m new file mode 100644 index 00000000..792be357 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cl_cl_intersect.m @@ -0,0 +1,105 @@ +function [clout]=cl_cl_intersect(clin1,clin2,varargin) + +% Usage: +% clout=cl_cl_intersect(clin1,clin2) +% clout=cl_cl_intersect(clin1,clin2,'switch') +% +% clout is a cl structure containing clusters of only those voxels that +% exist in both cl structures clin1 and clin2. +% +% If the clin1 and clin2 are not in the same image space, clin2 will be +% converted to the space of clin1 by finding the nearest mm point in the +% space of clin1 to each mm point in clin2. NOTE: ***** This may be +% undiserable if the voxel sizes in clin1 are substantially smaller than +% those in clin2--eg., a 4x4x4 voxel will be translated into a single 2x2x2 +% voxel, resulting in 3 'empty' 2x2x2 voxels in the space covered by the +% 4x4x4 voxel. For this reason, it is recommended that you place the lowest +% resolution cl structure in the first input. +% +% The cl.Z values and the threshold field are taken from clin1, and the +% values from clin2 are dropped. If the string 'switch' is included as in +% input, the Z values from clin2 will be used instead. +% + +sw=0; +if ~isempty(varargin) + for k=1:length(varargin) + if strcmp(varargin{k},'switch') + sw=1; + end + end +end + +XYZmm1=[]; +XYZmm2=[]; +Z=[]; +for k=1:length(clin1) + if size(clin1(k).XYZmm,1)==3 + XYZmm1(:,end+1:end+clin1(k).numVox)=clin1(k).XYZmm; + else + XYZmm1(:,end+1:end+clin1(k).numVox)=clin1(k).XYZmm'; + end + if ~sw + Z(end+1:end+clin1(k).numVox)=clin1(k).Z; + end +end + +for k=1:length(clin2) + if size(clin2(k).XYZmm,1)==3 + XYZmm2(:,end+1:end+clin2(k).numVox)=clin2(k).XYZmm; + else + XYZmm2(:,end+1:end+clin2(k).numVox)=clin2(k).XYZmm'; + end + if sw + Z(end+1:end+clin2(k).numVox)=clin2(k).Z; + end +end + +if clin1(1).voxSize(:)~=clin2(1).voxSize(:) + [XYZmm2,p_ind]=mmToVoxel(XYZmm2,clin1(1).M,'valid'); + if sw + for m=1:length(p_ind) + ind=find(p_ind(:)==p_ind(m)); + Z(ind)=mean(Z(ind)); + end + Z=Z(unique(p_ind)); + end + XYZmm2=voxelToMm(XYZmm2,clin1(1).M); +end + +XYZmm_out=[]; +Zout=[]; + +for k=1:size(XYZmm1,2) + a=find(XYZmm2(1,:)==XYZmm1(1,k)); + if ~isempty(a) + b=find(XYZmm2(2,a)==XYZmm1(2,k)); + if ~isempty(b) + c=find(XYZmm2(3,a(b))==XYZmm1(3,k)); + if ~isempty(c) + XYZmm_out(:,end+1)=XYZmm2(:,a(b(c))); + Zout(:,end+1)=Z(a(b(c))); + end + end + end +end +if isempty(XYZmm_out) + clout=struct('XYZ',{},'XYZmm',{},'name',{},'numVox',{},'M',{},'voxSize',{},'dim',{},'title',{},'threshold',{},'Z',{},'mm_center',{}); +else + XYZout=mmToVoxel(XYZmm_out,clin1(1).M,'valid'); + cl_ind=spm_clusters(XYZout); + for k=1:max(cl_ind) + clout(k).XYZ=XYZout(:,cl_ind==k); + clout(k).XYZmm=voxelToMm(clout(k).XYZ,clin1(1).M); + clout(k).name=''; + clout(k).numVox=length(find(cl_ind==k)); + clout(k).M=clin1(1).M; + clout(k).voxSize=clin1(1).voxSize; + clout(k).dim=clin1(1).dim; + clout(k).title=['Cluster of ' num2str(clout(k).numVox) ' voxels from the intersection of two clusters, created by cl_cl_intersect.m.']; + clout(k).threshold=clin1(1).threshold; + clout(k).Z=Zout(cl_ind==k); + clout(k).mm_center=center_of_mass(clout(k).XYZmm,clout(k).Z); + end +end + diff --git a/Cluster_contig_region_tools/cluster_tool/cl_img_intersect.m b/Cluster_contig_region_tools/cluster_tool/cl_img_intersect.m new file mode 100644 index 00000000..4cf8e2c8 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cl_img_intersect.m @@ -0,0 +1,100 @@ +function [clout]=cl_img_intersect(clin,img_path) + +% Usage: +% clout=cl_img_intersect(clin1,img) +% +% img may be a pathname or a volInfo structure. The function does not check +% the integrity of a volInfo structure but will attempt to operate on any +% structure that is passed to it. +% +% clout is a cl structure containing clusters of only those voxels that +% exist in both cl structure clin and are in-mask in the image specified by +% img_path. +% +% If clin and the image are not in the same image space, clin will be +% converted to the image space by finding the nearest mm point in the +% image space to each mm point in clin. NOTE: ***** This may be undiserable +% if the voxel sizes in the image are substantially smaller than those in +% clin2--eg., a 4x4x4 voxel will be translated into a single 2x2x2 voxel, +% resulting in 3 'empty' 2x2x2 voxels within the space covered by the 4x4x4 +% voxel. +% +% The cl.Z values and the threshold field are taken from clin1, and the +% values from the image are dropped. +% +% If multiple points in clin are mapped to the same point in the image, the +% mean of all Z values for points in clin mapped to that point in the image +% is used. +% + +if isstruct(img_path) + volInfo=img_path; + img_path=volInfo.fname; +else + volInfo=iimg_read_img(img_path,1); +end + +if size(volInfo.xyzlist,1)~=3 + volInfo.xyzlist=volInfo.xyzlist'; +end + +XYZ=[]; +Z=[]; +for k=1:length(clin) + if size(clin(k).XYZmm,1)~=3 + clin(k).XYZmm=clin(k).XYZmm'; + end + if size(clin(k).XYZ,1)~=3 + clin(k).XYZ=clin(k).XYZ'; + end + if clin(k).voxSize(:)~=diag(volInfo.mat(1:3,1:3)) + clin(k).voxSize=diag(volInfo.mat(1:3,1:3)); + [clin(k).XYZ,p_ind]=mmToVoxel(clin(k).XYZmm,volInfo.mat,'valid'); + for m=1:length(p_ind) + ind=find(p_ind(:)==p_ind(m)); + clin(k).Z(ind)=mean(clin(k).Z(ind)); + end + clin(k).Z=clin(k).Z(unique(p_ind)); + clin(k).XYZmm=voxelToMm(clin(k).XYZ,volInfo.mat); + clin(k).M=volInfo.mat; + clin(k).dim=volInfo.dim; + clin(k).numVox=size(clin(k).XYZmm,2); + end + XYZ(:,end+1:end+clin(k).numVox)=clin(k).XYZ; + Z(end+1:end+clin(k).numVox)=clin(k).Z; +end + +XYZ_out=[]; +Zout=[]; + +for k=1:size(XYZ,2) + a=find(volInfo.xyzlist(1,:)==XYZ(1,k)); + if ~isempty(a) + b=find(volInfo.xyzlist(2,a)==XYZ(2,k)); + if ~isempty(b) + c=find(volInfo.xyzlist(3,a(b))==XYZ(3,k)); + if ~isempty(c) + XYZ_out(:,end+1)=volInfo.xyzlist(:,a(b(c))); + Zout(:,end+1)=Z(k); + end + end + end +end +if isempty(XYZ_out); + clout=struct('XYZ',{},'XYZmm',{},'name',{},'numVox',{},'M',{},'voxSize',{},'dim',{},'title',{},'threshold',{},'Z',{},'mm_center',{}); +else + cl_ind=spm_clusters(XYZ_out); + for k=1:max(cl_ind) + clout(k).XYZ=XYZ_out(:,cl_ind==k); + clout(k).XYZmm=voxelToMm(clout(k).XYZ,clin(1).M); + clout(k).name=''; + clout(k).numVox=length(find(cl_ind==k)); + clout(k).M=clin(1).M; + clout(k).voxSize=clin(1).voxSize; + clout(k).dim=clin(1).dim; + clout(k).title=['Cluster of ' num2str(clout(k).numVox) ' voxels from the intersection of a cluster and ' img_path ', created by cl_img_intersect.m.']; + clout(k).threshold=clin(1).threshold; + clout(k).Z=Zout(cl_ind==k); + clout(k).mm_center=center_of_mass(clout(k).XYZmm,clout(k).Z); + end +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/cl_subdivide.m b/Cluster_contig_region_tools/cluster_tool/cl_subdivide.m new file mode 100644 index 00000000..0a719188 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cl_subdivide.m @@ -0,0 +1,158 @@ +function clout=cl_subdivide(clin,varargin) + +% Usage: +% +% [clout]=cl_subdivide(clin) +% [clout]=cl_subdivide(...,criterion) +% [clout]=cl_subdivide(...,criterion,min_adj) +% [clout]=cl_subdivide(...,criterion,min_adj,adjacency) +% [clout]=cl_subdivide(...,criterion,min_adj,adjacency,k) +% [clout]=cl_subdivide(...,criterion,min_adj,adjacency,k,erosions) +% +% Note that inputs must be specified in the correct order. +% +% In order to ensure that all the elements of clin are in the same voxel +% space, the function passes clin to check_cl.m. If there are issues in the +% cluster, some additional command line input may be requested from the +% user. +% +% Takes the cluster structure, clin, and subdivides clusters by requiring +% that 'core' cluster voxels have criterion (a scalar) adjacent voxels. +% Adjacent voxels are those that share at least one surface, edge, or +% corner, depending on the adjacency input. +% e.g., with 'corner' adjacency, each voxel can be adjacent to 26 other +% voxels. with 'edge', each vox can be adjacent to 14 (actually) +% +% adjacency is a string, which must be 'surface' (6 connectivity scheme), +% 'edge' (18 connectivity scheme), or 'corner' (26 connectivity scheme). +% Default is 'edge'. +% +% criterion (must be scalar) is the number of voxels a voxel must be +% adjacent too for it to be considered a 'core' cluster voxel (all voxels +% adjacent to 'core' voxels will be included in a cluster, but those that +% are adjacent to voxels that are part of a cluster but are not themselves +% 'core' voxels will not be included in the cluster). Default is 0. +% +% min_adj (must be scalar) is the minimum number of voxels that a voxel +% must be adjacent to for it to be included in a cluster. Voxels adjacent +% to less voxels than min_adj will recieve a value of 0 in ind. Default is +% 0. +% +% The optional input k will require that clusters in clout have at least k +% voxels (default is 1). +% +% erosions is the integer number of times that the input image will be +% eroded according to the criterion and adjacency inputs before 'core' +% cluster voxels are defined. Default is 1. +% +% +% See also: adjacent_test.m + + +clin=check_cl(clin); + +clout = clin; % initialize; tor added to avoid occasional errors + +if isempty(varargin) + criterion=0; + min_adj=0; + adjacency='edge'; + kt=1; + er=1; +elseif length(varargin)<2 + criterion=varargin{1}; + min_adj=0; + adjacency='edge'; + kt=1; + er=1; +elseif length(varargin)<3 + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency='edge'; + kt=1; + er=1; +elseif length(varargin)<4 + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency=varargin{3}; + kt=1; + er=1; +elseif length(varargin)<5 + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency=varargin{3}; + kt=varargin{4}; + er=1; +else + criterion=varargin{1}; + min_adj=varargin{2}; + adjacency=varargin{3}; + kt=varargin{4}; + er=varargin{5}; +end + +l = 0; +for k=1:length(clin) + l(k) = size(clin(k).XYZ,2); +end +XYZ = zeros(3,sum(l)); +Z = zeros(1,sum(l)); + +XYZ = cat(2, clin.XYZ); +Z = cat(2, clin.XYZ); +if isfield(clin, 'all_data'), all_data = cat(2, clin.all_data); end +if isfield(clin, 'val'), val = cat(1, clin.val); end + +% redundant with the above +% for k = 1:length(clin) +% if k > 1 +% XYZ(:,l(k-1)+1:l(k-1)+l(k))=clin(k).XYZ; +% Z(l(k-1)+1:l(k-1)+l(k))=clin(k).Z; +% else +% XYZ(:,1:l(k))=clin(k).XYZ; +% Z(1:l(k))=clin(k).Z; +% end +% end + +if size(XYZ,2)~=length(unique(XYZ','rows')) + XYZ=mmToVoxel(voxelToMm(XYZ,clin(1).M),clin(1).M,'valid'); +end + +ind=adjacent_test(XYZ,criterion,min_adj,adjacency,er); +clear adjacent_test + +k=1; +while k<=max(ind) + if sum(ind==k)k)=ind(ind>k)-1; + k=k-1; + end + k=k+1; +end + +for k=1:max(ind) + clout(k).XYZ=XYZ(:,ind==k); + clout(k).XYZmm=voxelToMm(clout(k).XYZ,clin(1).M); + clout(k).Z=Z(ind==k); + clout(k).mm_center=center_of_mass(clout(k).XYZmm,clout(k).Z); + clout(k).numVox=length(clout(k).Z); + clout(k).M=clin(1).M; + clout(k).voxSize=clin.voxSize; + clout(k).name=''; + clout(k).dim=clin(1).dim; + clout(k).threshold=clin(1).threshold; + clout(k).title=['Cluster of ' num2str(clout(k).numVox) ' voxels, created by cl_subdivide.m with criterion=' num2str(criterion) ', min_adj=' num2str(min_adj) ', adacency=' adjacency '.']; + + if isfield(clin, 'all_data') && ~isempty(all_data) + clout(k).all_data=all_data(:,ind==k); % all_data + end + + if isfield(clin, 'val') && ~isempty(val) + clout(k).val=val(ind==k, :); % all_data + end + +end + +end % function + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool.fig new file mode 100644 index 00000000..e0784e90 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool.m new file mode 100644 index 00000000..fdd28779 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool.m @@ -0,0 +1,182 @@ +function varargout = cluster_tool(varargin) +% CLUSTER_TOOL M-file for cluster_tool.fig +% CLUSTER_TOOL, by itself, creates a new CLUSTER_TOOL or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL returns the handle to a new CLUSTER_TOOL or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL.M with the given input arguments. +% +% CLUSTER_TOOL('Property','Value',...) creates a new CLUSTER_TOOL or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool + +% Last Modified by GUIDE v2.5 07-Sep-2006 19:41:04 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool is made visible. +function cluster_tool_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',[cluster_tool_getver]); +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + end +end + + +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool (see VARARGIN) + +% Choose default command line output for cluster_tool +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on button press in open_image. +function open_image_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),handles.append=cluster_tool_callgui('cluster_tool_append');else handles.append=0;end +handles.pwd=pwd;cd(handles.fdir); +[FileName,PathName]=uigetfile('*.img','Select image file.'); +cd(handles.pwd); +if isempty(FileName)||~ischar(FileName),beep,disp('No file found'),return,end +handles.fdir=PathName; +if handles.append,cluster_tool_thresh('img',[PathName FileName],'fdir',handles.fdir,'cl',handles.cl); +else cluster_tool_thresh('img',[PathName FileName],'fdir',handles.fdir); +end +guidata(handles.figure1,handles); +close(handles.figure1); + + +% --- Executes on button press in get_mat. +function get_mat_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),handles.append=cluster_tool_callgui('cluster_tool_append');else handles.append=0;end +handles.pwd=pwd;cd(handles.fdir); +[FileName,PathName]=uigetfile('*.mat','Select .mat file containing cl structure'); +cd(handles.pwd); +if ~PathName,return,end +handles.fdir=PathName; +load([PathName FileName]); +if ~exist('cl','var')||~isstruct(cl),beep,disp('Error: .mat file does not contain ''cl'' structure.'),guidata(hObject,handles);return,end +if handles.append,handles.cl=[handles.cl(:)' cl(:)'];else handles.cl=cl;end +guidata(hObject,handles); + + +% --- Executes on button press in quit. +function quit_Callback(hObject, eventdata, handles) + +close; + + +% --- Executes on button press in tools. +function tools_Callback(hObject, eventdata, handles) + +try + cluster_tool_tools('cl',handles.cl,'fdir',handles.fdir); +catch + beep,disp('Error: No ''cl'' structure in memory.'),return, +end +close(handles.figure1); + + +% --- Executes on button press in save. +function save_Callback(hObject, eventdata, handles) +cluster_tool_savefunc + + +% --- Executes on button press in mask. +function mask_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),handles.append=cluster_tool_callgui('cluster_tool_append');else handles.append=0;end +mask=cluster_tool_callgui('cluster_tool_maskq'); +if mask + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uigetfile('*.img','Select .img with DATA to extract...'); + cd(handles.pwd);if isempty(PathName),return,end + handles.fdir=PathName; + answer=inputdlg('If t image, enter df (otherwise cancel):'); + if isempty(answer),cl=mask2clusters([],[PathName FileName]); + else cl=mask2clusters([],[PathName FileName],str2double(answer));end +else answer=inputdlg('If t image, enter df (otherwise cancel):'); + if isempty(answer),cl=mask2clusters([]); + else cl=mask2clusters([],str2double(answer));end +end +if handles.append,handles.cl=[handles.cl(:)' cl(:)'],else handles.cl=cl;end +guidata(hObject,handles); + + +% --- Executes when user attempts to close figure1. +function figure1_CloseRequestFcn(hObject, eventdata, handles) +% Hint: delete(hObject) closes the figure +delete(hObject); + + + + +% --- Executes on button press in draw. +function draw_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),handles.append=cluster_tool_callgui('cluster_tool_append');else handles.append=0;end +if handles.append,cluster_tool_draw_sphere('cl',handles.cl,'fdir',handles.fdir);else cluster_tool_draw_sphere('fdir',handles.fdir);end +close(handles.figure1); + + + + +% --- Executes on button press in get_workspc. +function get_workspc_Callback(hObject, eventdata, handles) +varname=inputdlg('Enter variable name'); +if ~isempty(varname) + handles.cl=evalin('base',varname{1}); +end +guidata(hObject,handles); + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.fig new file mode 100644 index 00000000..d92cf3ea Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.m new file mode 100644 index 00000000..d84e6db2 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.m @@ -0,0 +1,107 @@ +function varargout = cluster_tool_append(varargin) +%CLUSTER_TOOL_APPEND M-file for cluster_tool_append.fig +% CLUSTER_TOOL_APPEND, by itself, creates a new CLUSTER_TOOL_APPEND or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_APPEND returns the handle to a new CLUSTER_TOOL_APPEND or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_APPEND('Property','Value',...) creates a new CLUSTER_TOOL_APPEND using the +% given property value pairs. Unrecognized properties are passed via +% varargin to cluster_tool_append_OpeningFcn. This calling syntax produces a +% warning when there is an existing singleton*. +% +% CLUSTER_TOOL_APPEND('CALLBACK') and CLUSTER_TOOL_APPEND('CALLBACK',hObject,...) call the +% local function named CALLBACK in CLUSTER_TOOL_APPEND.M with the given input +% arguments. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_append + +% Last Modified by GUIDE v2.5 13-Jul-2006 21:50:10 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_append_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_append_OutputFcn, ... + 'gui_LayoutFcn', [], ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_append is made visible. +function cluster_tool_append_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin unrecognized PropertyName/PropertyValue pairs from the +% command line (see VARARGIN) + +% Choose default command line output for cluster_tool_append +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_append wait for user response (see UIRESUME) +uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_append_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes when user attempts to close figure1. +function figure1_CloseRequestFcn(hObject, eventdata, handles) +% hObject handle to figure1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: delete(hObject) closes the figure +delete(hObject); + + +% --- Executes on button press in overwrite. +function overwrite_Callback(hObject, eventdata, handles) +handles.data=0; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to overwrite (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + +% --- Executes on button press in append. +function append_Callback(hObject, eventdata, handles) +handles.data=1; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to append (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_callgui.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_callgui.m new file mode 100644 index 00000000..cf5ce194 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_callgui.m @@ -0,0 +1,9 @@ +function out=cluster_tool_callgui(guitocall) + +% returns handles.data from guitocall and closes the gui. note that +% guitocall must use uiwait and uiresume WITHOUT the need to close. + +eval(['h=' guitocall ';']) +d=guidata(h); +out=d.data; +close(h); \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.fig new file mode 100644 index 00000000..29489d95 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.m new file mode 100644 index 00000000..c290d02f --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.m @@ -0,0 +1,336 @@ +function varargout = cluster_tool_draw_sphere(varargin) +% CLUSTER_SRC_TOOL_DRAW_SPHERE M-file for cluster_src_tool_draw_sphere.fig +% CLUSTER_SRC_TOOL_DRAW_SPHERE, by itself, creates a new CLUSTER_SRC_TOOL_DRAW_SPHERE or raises the existing +% singleton*. +% +% H = CLUSTER_SRC_TOOL_DRAW_SPHERE returns the handle to a new CLUSTER_SRC_TOOL_DRAW_SPHERE or the handle to +% the existing singleton*. +% +% CLUSTER_SRC_TOOL_DRAW_SPHERE('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_SRC_TOOL_DRAW_SPHERE.M with the given input arguments. +% +% CLUSTER_SRC_TOOL_DRAW_SPHERE('Property','Value',...) creates a new CLUSTER_SRC_TOOL_DRAW_SPHERE or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_src_tool_draw_sphere_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_src_tool_draw_sphere_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_src_tool_draw_sphere + +% Last Modified by GUIDE v2.5 01-Aug-2006 16:11:27 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_draw_sphere_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_draw_sphere_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_src_tool_draw_sphere is made visible. +function cluster_tool_draw_sphere_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',cluster_tool_getver); +handles.src='Image'; +handles.intype='Manual'; +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + elseif strcmp(varargin{k},'img'),handles.img=varargin{k+1}; + set(handles.sourcespace,'string',['Image: ' handles.img]); + end +end +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_src_tool_draw_sphere (see VARARGIN) + +% Choose default command line output for cluster_src_tool_draw_sphere +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_src_tool_draw_sphere wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_draw_sphere_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +function radius_Callback(hObject, eventdata, handles) +try handles.r=str2double(get(hObject,'string'));catch set(hObject,'string','');handles.r=[];end +guidata(hObject,handles); +% Hints: get(hObject,'String') returns contents of radius as text +% str2double(get(hObject,'String')) returns contents of radius as a double + + +% --- Executes during object creation, after setting all properties. +function radius_CreateFcn(hObject, eventdata, handles) + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + +% --- Executes on button press in selector. +function selector_Callback(hObject, eventdata, handles) +if strcmp(handles.src,'Image') + if isfield(handles,'img'),msgbox('Selected image will be used instead of previously selected image','','warn');end + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uigetfile('*.img','Select image file.'); + cd(handles.pwd); + if ~PathName,return,end + handles.fdir=PathName; + set(handles.sourcespace,'String',['Source: ' PathName FileName]); + handles.img=[PathName FileName]; +else + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uigetfile('*.mat','Select mat file.'); + cd(handles.pwd); + if ~PathName,return,end + handles.fdir=PathName; + load([PathName FileName]); + try + handles.cl_space=cl; + set(handles.sourcespace,'String',['Source: ' PathName FileName]); + handles.cl_space(1).fname=[PathName FileName]; + catch + beep,disp('Error: .mat file likely does not contain a cl structure'),return + end +end +guidata(hObject,handles); + + + +% --- Executes on button press in done. +function done_Callback(hObject, eventdata, handles) +try + if get(handles.image_src,'Value') + cl_space=iimg_read_img(handles.img,1); + else + cl_space=handles.cl_space(1); + end + if ~isfield(cl_space,'M') + if ~isfield(cl_space,'mat'),beep,disp('Error: no .M or .mat field in source space.'),return,end + M=cl_space.mat; + else + M=cl_space.M; + end + if get(handles.Man_in,'Value') + XYZmm=sphere_3d([handles.x_val handles.y_val handles.z_val]',handles.r,diag(M(1:3,1:3))'); + else + XYZmm=sphere_3d(handles.sphere_centers,handles.r,diag(M(1:3,1:3))'); + end + XYZ=mmToVoxel(XYZmm,M,'valid'); + cl_ind=spm_clusters(XYZ); + for k=1:max(cl_ind) + cl(k).XYZ=XYZ(:,find(cl_ind==k)); + cl(k).XYZmm=voxelToMm(cl(k).XYZ,M); + cl(k).numVox=length(find(cl_ind==k)); + cl(k).M=M; + cl(k).voxSize=diag(cl(k).M(1:3,1:3)); + cl(k).name=''; + cl(k).Z(1:cl(k).numVox)=1; + cl(k).mm_center=center_of_mass(cl(k).XYZmm,cl(k).Z); + cl(k).dim=cl_space.dim; + cl(k).threshold=[1 NaN]; + cl(k).title=['Cluster of ' num2str(cl(k).numVox) ' voxels, created by cluster_tool_draw_sphere GUI. Image space from: ' cl_space.fname]; + end + if get(handles.domask,'Value') + if get(handles.image_src,'Value') + cl=cl_img_intersect(cl,handles.img); + else + cl=cl_cl_intersect(cl,handles.cl_space); + end + end + if isfield(handles,'cl') + if isfield(handles,'cl'),append=cluster_tool_callgui('cluster_tool_append');else handles.append=0;end + if append + handles.cl=[handles.cl(:);cl(:)]; + else + handles.cl=cl; + end + else + handles.cl=cl; + end +catch beep,disp('Error: Not all required data is specified. Check that you have selected a file, specified a radius, and specified valid sphere centers.'),return +end +guidata(handles.figure1,handles); + + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool('fdir',handles.fdir);end +close(handles.figure1); + + +function x_Callback(hObject, eventdata, handles) +try handles.x_val=str2double(get(hObject,'string'));catch set(hObject,'string','');handles.x_val=[];end +guidata(hObject,handles); +% Hints: get(hObject,'String') returns contents of x as text +% str2double(get(hObject,'String')) returns contents of x as a double + + +% --- Executes during object creation, after setting all properties. +function x_CreateFcn(hObject, eventdata, handles) + + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + +function y_Callback(hObject, eventdata, handles) +try handles.y_val=str2double(get(hObject,'string'));catch set(hObject,'string','');handles.y_val=[];end +guidata(hObject,handles); + +% Hints: get(hObject,'String') returns contents of y as text +% str2double(get(hObject,'String')) returns contents of y as a double + + +% --- Executes during object creation, after setting all properties. +function y_CreateFcn(hObject, eventdata, handles) + + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + +function z_Callback(hObject, eventdata, handles) +try handles.z_val=str2double(get(hObject,'string'));catch set(hObject,'string','');handles.z_val=[];end +guidata(hObject,handles); + +% Hints: get(hObject,'String') returns contents of z as text +% str2double(get(hObject,'String')) returns contents of z as a double + + +% --- Executes during object creation, after setting all properties. +function z_CreateFcn(hObject, eventdata, handles) + + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + +function var_name_Callback(hObject, eventdata, handles) +try + handles.sphere_centers=evalin('base',get(hObject,'String')); +catch beep,disp('Error: Could not get data from workspace variable'),return,end +if size(size(handles.sphere_centers))~=2||~isnumeric(handles.sphere_centers)||(min(size(handles.sphere_centers))~=3&max(size(handles.sphere_centers))~=3) + beep,disp('Error: Specified variable does not appear to be a valid n x 3 or 3 x n array of center coordinates'),return +end +if size(handles.sphere_centers,1)~=3 + handles.sphere_centers=handles.sphere_centers'; +elseif size(handles.sphere_centers,2)==3 + beep,disp('Warning: Assuming that X Y Z values are in ROWS, not COLUMNS (that is, that each COLUMN is a point).'),disp('If this is not the case, transpose the matrix and try again') +end +guidata(hObject,handles); + +% Hints: get(hObject,'String') returns contents of var_name as text +% str2double(get(hObject,'String')) returns contents of var_name as a double + + +% --- Executes during object creation, after setting all properties. +function var_name_CreateFcn(hObject, eventdata, handles) + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + +% -------------------------------------------------------------------- +function source_selector_SelectionChangeFcn(hObject, eventdata, handles) +handles.src=get(hObject,'String'); +if strcmp(handles.src,'Image') + set(handles.selector,'String','Select Image'); + if isfield(handles,'img') + set(handles.sourcespace,'String',['Source: ' handles.img]); + else + set(handles.sourcespace,'string','Source: (none selected)'); + end +else + set(handles.selector,'String','Select File'); + if isfield(handles,'cl_space') + set(handles.sourcespace,'String',['Source: ' handles.cl_space.fname]); + else + set(handles.sourcespace,'String','Source: (none selected)'); + end +end +guidata(handles.figure1,handles); + +% -------------------------------------------------------------------- +function center_input_selector_SelectionChangeFcn(hObject, eventdata, handles) +handles.intype=get(hObject,'String'); +if strcmp(handles.intype,'Manual') + set(handles.x,'Visible','on'); + set(handles.y,'Visible','on'); + set(handles.z,'Visible','on'); + set(handles.x_text,'Visible','on'); + set(handles.y_text,'Visible','on'); + set(handles.z_text,'Visible','on'); + set(handles.var_name,'Visible','off'); + set(handles.center_text,'String','Sphere Center:'); + if isfield(handles,'wrkspc_var'),handles=rmfield(handles,'wrkspc_var');end +else + set(handles.x,'Visible','off'); + set(handles.y,'Visible','off'); + set(handles.z,'Visible','off'); + set(handles.x_text,'Visible','off'); + set(handles.y_text,'Visible','off'); + set(handles.z_text,'Visible','off'); + set(handles.var_name,'Visible','on'); + set(handles.center_text,'String','Variable Name:'); + if isfield(handles,'x_val'),handles=rmfield(handles,'x_val');end + if isfield(handles,'y_val'),handles=rmfield(handles,'y_val');end + if isfield(handles,'z_val'),handles=rmfield(handles,'z_val');end +end +guidata(handles.figure1,handles); + + + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.fig new file mode 100644 index 00000000..0bbf071f Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.m new file mode 100644 index 00000000..4e813ca3 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.m @@ -0,0 +1,146 @@ +function varargout = cluster_tool_edit(varargin) +% CLUSTER_TOOL_EDIT M-file for cluster_tool_edit.fig +% CLUSTER_TOOL_EDIT, by itself, creates a new CLUSTER_TOOL_EDIT or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_EDIT returns the handle to a new CLUSTER_TOOL_EDIT or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_EDIT('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL_EDIT.M with the given input arguments. +% +% CLUSTER_TOOL_EDIT('Property','Value',...) creates a new CLUSTER_TOOL_EDIT or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_edit_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_edit_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_edit + +% Last Modified by GUIDE v2.5 07-Sep-2006 17:34:51 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_edit_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_edit_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_edit is made visible. +function cluster_tool_edit_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',[cluster_tool_getver]); +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + end +end + +s=''; +for k=1:length(handles.cl) + if k~=1 + s=[s '|']; + end + s=[s 'Cluster of ' num2str(handles.cl(k).numVox) ' voxels with center ' num2str(handles.cl(k).mm_center(1)) ', ' num2str(handles.cl(k).mm_center(2)) ', ' num2str(handles.cl(k).mm_center(3)) '.']; +end +set(handles.cl_list,'String',s);set(handles.cl_list,'Max',length(handles.cl)); +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool_edit (see VARARGIN) + +% Choose default command line output for cluster_tool_edit +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_edit wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_edit_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on selection change in cl_list. +function cl_list_Callback(hObject, eventdata, handles) +% hObject handle to cl_list (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: contents = get(hObject,'String') returns cl_list contents as cell array +% contents{get(hObject,'Value')} returns selected item from cl_list + + +% --- Executes during object creation, after setting all properties. +function cl_list_CreateFcn(hObject, eventdata, handles) +% hObject handle to cl_list (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: listbox controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + +% --- Executes on button press in quit. +function quit_Callback(hObject, eventdata, handles) +delete(handles.figure1); + + + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool_tools('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool_tools('fdir',handles.fdir);end +delete(handles.figure1); + + + +% --- Executes on button press in save. +function save_Callback(hObject, eventdata, handles) +cluster_tool_savefunc + + +% --- Executes on button press in del. +function del_Callback(hObject, eventdata, handles) +handles.cl(get(handles.cl_list,'Value'))=[]; +set(handles.cl_list,'Max',length(handles.cl)); +s=get(handles.cl_list,'String'); +s(get(handles.cl_list,'Value'),:)=[]; +set(handles.cl_list,'Value',1); +set(handles.cl_list,'String',s); +guidata(hObject,handles); + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.fig new file mode 100644 index 00000000..04285a09 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.m new file mode 100644 index 00000000..a86a9979 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.m @@ -0,0 +1,121 @@ +function varargout = cluster_tool_getbetas(varargin) +% CLUSTER_TOOL_GETBETAS M-file for cluster_tool_getbetas.fig +% CLUSTER_TOOL_GETBETAS, by itself, creates a new CLUSTER_TOOL_GETBETAS or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_GETBETAS returns the handle to a new CLUSTER_TOOL_GETBETAS or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_GETBETAS('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL_GETBETAS.M with the given input arguments. +% +% CLUSTER_TOOL_GETBETAS('Property','Value',...) creates a new CLUSTER_TOOL_GETBETAS or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_getbetas_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_getbetas_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_getbetas + +% Last Modified by GUIDE v2.5 07-Sep-2006 19:22:45 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_getbetas_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_getbetas_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_getbetas is made visible. +function cluster_tool_getbetas_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',cluster_tool_getver); +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + end +end + +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool_getbetas (see VARARGIN) + +% Choose default command line output for cluster_tool_getbetas +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_getbetas wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_getbetas_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on button press in expt. +function expt_Callback(hObject, eventdata, handles) +try + handles.cl=extract_contrast_data(evalin('base','EXPT.SNPM.P'),handles.cl); +catch beep,disp('Error: Could not get EXPT.SNPM.P from base workspace') +end +guidata(hObject,handles); + + +% --- Executes on button press in get_from_img. +function get_from_img_Callback(hObject, eventdata, handles) +try p = spm_get([0 Inf],'*.img','Select .img(s) to extract data from',handles.fdir,0);catch end +if ~isempty(p) + for k=1:size(p,1) + P{k}=deblank(p(k,:)); + end + handles.cl=extract_contrast_data(P,handles.cl); +end +guidata(hObject,handles); + +% --- Executes on button press in quit. +function quit_Callback(hObject, eventdata, handles) +delete(handles.figure1); + + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool_tools('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool_tools('fdir',handles.fdir);end +delete(handles.figure1); + + +% --- Executes on button press in save. +function save_Callback(hObject, eventdata, handles) +cluster_tool_savefunc + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_getver.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_getver.m new file mode 100644 index 00000000..12e4b2c6 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_getver.m @@ -0,0 +1,3 @@ +function [ver]=cluster_tool_getver(); + +ver='SCAN-U Cluster Tool v1.0.1'; \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.fig new file mode 100644 index 00000000..ac5ab224 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.m new file mode 100644 index 00000000..07c5c96c --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.m @@ -0,0 +1,107 @@ +function varargout = cluster_tool_maskq(varargin) +%CLUSTER_TOOL_MASKQ M-file for cluster_tool_maskq.fig +% CLUSTER_TOOL_MASKQ, by itself, creates a new CLUSTER_TOOL_MASKQ or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_MASKQ returns the handle to a new CLUSTER_TOOL_MASKQ or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_MASKQ('Property','Value',...) creates a new CLUSTER_TOOL_MASKQ using the +% given property value pairs. Unrecognized properties are passed via +% varargin to cluster_tool_maskq_OpeningFcn. This calling syntax produces a +% warning when there is an existing singleton*. +% +% CLUSTER_TOOL_MASKQ('CALLBACK') and CLUSTER_TOOL_MASKQ('CALLBACK',hObject,...) call the +% local function named CALLBACK in CLUSTER_TOOL_MASKQ.M with the given input +% arguments. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_maskq + +% Last Modified by GUIDE v2.5 13-Jul-2006 21:50:10 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_maskq_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_maskq_OutputFcn, ... + 'gui_LayoutFcn', [], ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_maskq is made visible. +function cluster_tool_maskq_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin unrecognized PropertyName/PropertyValue pairs from the +% command line (see VARARGIN) + +% Choose default command line output for cluster_tool_maskq +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_maskq wait for user response (see UIRESUME) +uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_maskq_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes when user attempts to close figure1. +function figure1_CloseRequestFcn(hObject, eventdata, handles) +% hObject handle to figure1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: delete(hObject) closes the figure +delete(hObject); + + +% --- Executes on button press in overwrite. +function overwrite_Callback(hObject, eventdata, handles) +handles.data=0; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to overwrite (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + +% --- Executes on button press in append. +function append_Callback(hObject, eventdata, handles) +handles.data=1; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to append (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.fig new file mode 100644 index 00000000..4daa3e33 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.m new file mode 100644 index 00000000..b07a8518 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.m @@ -0,0 +1,107 @@ +function varargout = cluster_tool_masktypeq(varargin) +%CLUSTER_TOOL_MASKTYPEQ M-file for cluster_tool_masktypeq.fig +% CLUSTER_TOOL_MASKTYPEQ, by itself, creates a new CLUSTER_TOOL_MASKTYPEQ or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_MASKTYPEQ returns the handle to a new CLUSTER_TOOL_MASKTYPEQ or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_MASKTYPEQ('Property','Value',...) creates a new CLUSTER_TOOL_MASKTYPEQ using the +% given property value pairs. Unrecognized properties are passed via +% varargin to cluster_tool_masktypeq_OpeningFcn. This calling syntax produces a +% warning when there is an existing singleton*. +% +% CLUSTER_TOOL_MASKTYPEQ('CALLBACK') and CLUSTER_TOOL_MASKTYPEQ('CALLBACK',hObject,...) call the +% local function named CALLBACK in CLUSTER_TOOL_MASKTYPEQ.M with the given input +% arguments. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_masktypeq + +% Last Modified by GUIDE v2.5 13-Jul-2006 21:50:10 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_masktypeq_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_masktypeq_OutputFcn, ... + 'gui_LayoutFcn', [], ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_masktypeq is made visible. +function cluster_tool_masktypeq_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin unrecognized PropertyName/PropertyValue pairs from the +% command line (see VARARGIN) + +% Choose default command line output for cluster_tool_masktypeq +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_masktypeq wait for user response (see UIRESUME) +uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_masktypeq_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes when user attempts to close figure1. +function figure1_CloseRequestFcn(hObject, eventdata, handles) +% hObject handle to figure1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: delete(hObject) closes the figure +delete(hObject); + + +% --- Executes on button press in overwrite. +function overwrite_Callback(hObject, eventdata, handles) +handles.data=0; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to overwrite (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + +% --- Executes on button press in append. +function append_Callback(hObject, eventdata, handles) +handles.data=1; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to append (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_savefunc.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_savefunc.m new file mode 100644 index 00000000..f978a173 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_savefunc.m @@ -0,0 +1,10 @@ +try + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uiputfile('*.mat','Save .mat file...'); + handles.fdir=PathName;cd(handles.pwd); + varname=inputdlg('Select variable name for cl structure','Select var name',1,{'cl'}); + eval([varname{1} '=handles.cl;']) + save([PathName FileName],varname{1}); + guidata(hObject,handles); +catch beep,disp(['Error: Operation cancelled or no ''cl'' structure in memory.']) +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.fig new file mode 100644 index 00000000..7b78e3c7 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.m new file mode 100644 index 00000000..b034e85a --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.m @@ -0,0 +1,186 @@ +function varargout = cluster_tool_subdivide(varargin) +% CLUSTER_TOOL_SUBDIVIDE M-file for cluster_tool_subdivide.fig +% CLUSTER_TOOL_SUBDIVIDE, by itself, creates a new CLUSTER_TOOL_SUBDIVIDE or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_SUBDIVIDE returns the handle to a new CLUSTER_TOOL_SUBDIVIDE or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_SUBDIVIDE('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL_SUBDIVIDE.M with the given input arguments. +% +% CLUSTER_TOOL_SUBDIVIDE('Property','Value',...) creates a new CLUSTER_TOOL_SUBDIVIDE or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_subdivide_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_subdivide_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_subdivide + +% Last Modified by GUIDE v2.5 04-Sep-2006 11:37:00 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_subdivide_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_subdivide_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_subdivide is made visible. +function cluster_tool_subdivide_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',[cluster_tool_getver]); +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + end +end +handles.criterion=0;handles.k=1;handles.min_adj=0; +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool_subdivide (see VARARGIN) + +% Choose default command line output for cluster_tool_subdivide +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_subdivide wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_subdivide_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on button press in quit. +function quit_Callback(hObject, eventdata, handles) +delete(handles.figure1); + + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool_tools('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool_tools('fdir',handles.fdir);end +delete(handles.figure1); + + +% --- Executes on button press in save. +function save_Callback(hObject, eventdata, handles) +cluster_tool_savefunc + + +% --- Executes on selection change in adjacency. +function adjacency_Callback(hObject, eventdata, handles) + + + +% --- Executes during object creation, after setting all properties. +function adjacency_CreateFcn(hObject, eventdata, handles) +set(hObject,'String','Surface|Edge|Corner'); +set(hObject,'Value',2); +disp('**If a warning prints, you can safely ignore it.**') +% Hint: popupmenu controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + +% --- Executes on button press in done. +function done_Callback(hObject, eventdata, handles) +if get(handles.adjacency,'Value')==1 + adjacency='surface'; +elseif get(handles.adjacency,'Value')==2 + adjacency='edge'; +elseif get(handles.adjacency,'Value')==3 + adjacency='corner'; +end +cl=cl_subdivide(handles.cl,handles.criterion,handles.min_adj,adjacency,handles.k); +if ~isempty(cl) + handles.cl=cl; +else + warndlg('Resulting ''cl'' structure was empty, the initial structure is being kept in memory.'); +end +guidata(hObject,handles); + +function criterion_Callback(hObject, eventdata, handles) +try handles.criterion=str2double(get(hObject,'string'));catch set(hObject,'string','0');handles.criterion=0;end +guidata(hObject,handles); + +% --- Executes during object creation, after setting all properties. +function criterion_CreateFcn(hObject, eventdata, handles) + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + +function k_Callback(hObject, eventdata, handles) +try handles.k=str2double(get(hObject,'string'));catch set(hObject,'string','1');handles.k=1;end +guidata(hObject,handles); + + +% --- Executes during object creation, after setting all properties. +function k_CreateFcn(hObject, eventdata, handles) + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + + +function minadj_Callback(hObject, eventdata, handles) +try handles.min_adj=str2double(get(hObject,'string'));catch set(hObject,'string','0');handles.min_adj=0;end +guidata(hObject,handles); + + +% --- Executes during object creation, after setting all properties. +function minadj_CreateFcn(hObject, eventdata, handles) +% hObject handle to minadj (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.fig new file mode 100644 index 00000000..e5db7108 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.m new file mode 100644 index 00000000..c1d6e362 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.m @@ -0,0 +1,162 @@ +function varargout = cluster_tool_table(varargin) +% CLUSTER_TOOL_TABLE M-file for cluster_tool_table.fig +% CLUSTER_TOOL_TABLE, by itself, creates a new CLUSTER_TOOL_TABLE or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_TABLE returns the handle to a new CLUSTER_TOOL_TABLE or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_TABLE('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL_TABLE.M with the given input arguments. +% +% CLUSTER_TOOL_TABLE('Property','Value',...) creates a new CLUSTER_TOOL_TABLE or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_table_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_table_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_table + +% Last Modified by GUIDE v2.5 17-Oct-2006 10:33:23 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_table_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_table_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_table is made visible. +function cluster_tool_table_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',[cluster_tool_getver]); +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + end +end +% s={'Print Labels (Talairach atlas--coordinates converted','from MNI using mni2tal)'}; +% set(handles.tal_labels,'String',s); + + +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool_table (see VARARGIN) + +% Choose default command line output for cluster_tool_table +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_table wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_table_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + + + + +% --- Executes on button press in quit. +function quit_Callback(hObject, eventdata, handles) +delete(handles.figure1); + + + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool_tools('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool_tools('fdir',handles.fdir);end +delete(handles.figure1); + + +% --- Executes on button press in write. +function write_Callback(hObject, eventdata, handles) + + +% --- Executes on button press in do_subcl. +function do_subcl_Callback(hObject, eventdata, handles) + + +% --- Executes on button press in do_labels. +function do_labels_Callback(hObject, eventdata, handles) +if get(handles.do_labels,'Value') + set(handles.tal_labels,'Value',0) +end + +% --- Executes on button press in done. +function done_Callback(hObject, eventdata, handles) +if get(handles.tal_labels,'Value') + for i=length(handles.cl) + handles.cl(i).XYZmm=mni2tal(handles.cl(i).XYZmm); + end +end +s='cluster_table(handles.cl,get(handles.do_subcl,''Value''),any([get(handles.do_labels,''Value'') get(handles.tal_labels,''Value'')])'; +if get(handles.write,'Value') + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uiputfile('*.tsv','Save as tab seperated values...'); + cd(handles.pwd); + if isempty(FileName)||~ischar(FileName),beep,disp('No file found'),return,end + handles.fdir=PathName; + s=[s ',''writefile'',[PathName FileName]']; +end +if get(handles.do_labels,'Value')||get(handles.tal_labels,'Value') + try + xyz=evalin('base','xyz'); + L3=evalin('base','L3'); + L5=evalin('base','L5'); + s=[s ',''tal_info'',xyz,L3,L5']; + catch + if get(handles.tal_labels,'Value') + s=[s ',''talairach''']; + end + end +end +s=[s ');']; +eval(s) +beep,disp('Done printing table.') + + +function tal_labels_CreateFcn(hObject, eventdata,handles) +% s={'Print Labels (Talairach atlas--coordinates converted','from MNI using mni2tal)'}; +% set(handles.tal_labels,'String',s) + + +% --- Executes on button press in tal_labels. +function tal_labels_Callback(hObject, eventdata, handles) +if get(handles.tal_labels,'Value') + set(handles.do_labels,'Value',0) +end + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.fig new file mode 100644 index 00000000..dd39dabc Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.m new file mode 100644 index 00000000..a5d9588e --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.m @@ -0,0 +1,303 @@ +function varargout = cluster_tool_thresh(varargin) +% CLUSTER_TOOL_THRESH M-file for cluster_tool_thresh.fig +% CLUSTER_TOOL_THRESH, by itself, creates a new CLUSTER_TOOL_THRESH or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_THRESH returns the handle to a new CLUSTER_TOOL_THRESH or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_THRESH('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL_THRESH.M with the given input arguments. +% +% CLUSTER_TOOL_THRESH('Property','Value',...) creates a new CLUSTER_TOOL_THRESH or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_thresh_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_thresh_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_thresh + +% Last Modified by GUIDE v2.5 20-Jul-2006 15:43:35 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_thresh_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_thresh_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_thresh is made visible. +function cluster_tool_thresh_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',cluster_tool_getver); +set(handles.t,'value',1);set(handles.p,'value',0);set(handles.dat,'value',0); +set(handles.t_thresh,'value',1);set(handles.p_thresh,'value',0); +handles.thrshtyp='t';handles.imtype='t'; +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + elseif strcmp(varargin{k},'img'),handles.img=varargin{k+1}; + set(handles.imagename,'string',['Image: ' handles.img]); + end +end +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool_thresh (see VARARGIN) + +% Choose default command line output for cluster_tool_thresh +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_thresh wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_thresh_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + + + +% -------------------------------------------------------------------- +function imgtype_SelectionChangeFcn(hObject, eventdata, handles) +% hObject handle to imgtype (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +handles.imtype=get(hObject,'string'); +if strcmp(handles.imtype,'data')||strcmp(handles.imtype,'p') + set(handles.threshtype,'visible','off');set(handles.dftxt,'visible','off');set(handles.df,'visible','off'); +else set(handles.threshtype,'visible','on');set(handles.dftxt,'visible','on');set(handles.df,'visible','on'); +end +if strcmp(handles.imtype,'p'),set(handles.posneg,'visible','off','value',1); +elseif ~strcmp(handles.thrshtyp,'p'),set(handles.posneg,'visible','on'); +end +if ~strcmp(handles.imtype,'p'),set(handles.posneg,'visible','on');end +if strcmp(handles.thrshtyp,'t'),handles.thrshtyp='p';set(handles.p_thresh,'value',1);set(handles.t_thresh,'value',0);end +guidata(handles.figure1,handles); + + + +function thresh_Callback(hObject, eventdata, handles) +try handles.threshold=str2double(get(hObject,'string'));catch set(hObject,'string','');handles.threshold=[];end +guidata(hObject,handles); +% hObject handle to thresh (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of thresh as text +% str2double(get(hObject,'String')) returns contents of thresh as a double + + +% --- Executes during object creation, after setting all properties. +function thresh_CreateFcn(hObject, eventdata, handles) +% hObject handle to thresh (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + +% -------------------------------------------------------------------- +function threshtype_SelectionChangeFcn(hObject, eventdata, handles) +if strcmp(handles.imtype,'p') + set(handles.p_thresh,'value',1);set(handles.t_thresh,'value',0);set(handles.posneg,'visible','off','value',1); +else handles.thrshtyp=get(hObject,'string');if ~strcmp(handles.imtype,'t'),set(handles.posneg,'visible','on');end +end +guidata(handles.figure1,handles); +% hObject handle to threshtype (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + + + + +function df_Callback(hObject, eventdata, handles) +handles.dfd=str2double(get(hObject,'string')); +if isnan(handles.dfd),set(hObject,'string','');end +guidata(hObject,handles); +% hObject handle to df (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of df as text +% str2double(get(hObject,'String')) returns contents of df as a double + + +% --- Executes during object creation, after setting all properties. +function df_CreateFcn(hObject, eventdata, handles) +% hObject handle to df (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + + +function k_Callback(hObject, eventdata, handles) +handles.kd=str2double(get(hObject,'string')); +if isnan(handles.kd),set(hObject,'string','');handles.kd=0;end +guidata(hObject,handles); +% hObject handle to k (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of k as text +% str2double(get(hObject,'String')) returns contents of k as a double + + +% --- Executes during object creation, after setting all properties. +function k_CreateFcn(hObject, eventdata, handles) +% hObject handle to k (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + +% --- Executes on button press in imgsel. +function imgsel_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),handles.append=cluster_tool_callgui('cluster_tool_append');else handles.append=0;end +if isfield(handles,'img'),msgbox('Selected image will be used instead of previously selected image','','warn');end +handles.pwd=pwd;cd(handles.fdir); +[FileName,PathName]=uigetfile('*.img','Select image file.'); +cd(handles.pwd); +if isempty(PathName)||isnumeric(PathName),return,end +handles.fdir=PathName; +set(handles.imagename,'string',['Image: ' PathName FileName]); +handles.img=[PathName FileName]; +guidata(hObject,handles); +% hObject handle to imgsel (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + +% --- Executes on button press in done. +function done_Callback(hObject, eventdata, handles) +s='[dat,volInfo,cl]=iimg_threshold(handles.img,''imgtype'',handles.imtype,''thr'',handles.threshold'; +if get(handles.posneg,'value')==2&&~strcmp(handles.thrshtyp,'p'),handles.threshold=[-Inf -handles.threshold]; +elseif get(handles.posneg,'value')==3,s=[s ',''abs''']; +end +% try + if ~strcmp(handles.imtype,'data') + s=[s ',''threshtype'',handles.thrshtyp']; + if strcmp(handles.thrshtyp,'p')&&strcmp(handles.imtype,'t'),s=[s ',''df'',handles.dfd'];end + end + s=[s ',''thr'',handles.threshold']; + if handles.kd,s=[s ',''k'',handles.kd'];end + if get(handles.write,'value'),[FileName,PathName]=uiputfile('*.img','Select .img file to write'); + s=[s ',''outnames'',[PathName FileName]']; + end + s=[s ');']; + eval(s) +% catch beep,disp('Error: Not all inputs were specified, please try again.'),return,end +if isfield(handles,'cl'),handles.cl=[handles.cl(:)' cl(:)'];else handles.cl=cl;end +guidata(hObject,handles); +answer=questdlg('Clusters obtained! Return to Acquire/Create Cluster screen?'); +if strcmp(answer,'Yes'),ret_Callback(handles.ret,eventdata,handles);end + +% hObject handle to done (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + + + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool;end +close(handles.figure1); + +% hObject handle to ret (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + + + +% --- Executes on button press in write. +function write_Callback(hObject, eventdata, handles) +% hObject handle to write (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of write + + + + +% --- Executes on selection change in posneg. +function posneg_Callback(hObject, eventdata, handles) +% hObject handle to posneg (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: contents = get(hObject,'String') returns posneg contents as cell array +% contents{get(hObject,'Value')} returns selected item from posneg + + +% --- Executes during object creation, after setting all properties. +function posneg_CreateFcn(hObject, eventdata, handles) +set(hObject,'string','Positive|Negative|Both'); +% hObject handle to posneg (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: popupmenu controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +function imagename_CreateFcn(hObject,eventdata,handles) +set(hObject,'string',['Image: ' handles.img]); diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.fig new file mode 100644 index 00000000..d4089ab0 Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.m new file mode 100644 index 00000000..2489b3ee --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.m @@ -0,0 +1,213 @@ +function varargout = cluster_tool_tools(varargin) +% CLUSTER_TOOL_TOOLS M-file for cluster_tool_tools.fig +% CLUSTER_TOOL_TOOLS, by itself, creates a new CLUSTER_TOOL_TOOLS or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_TOOLS returns the handle to a new CLUSTER_TOOL_TOOLS or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_TOOLS('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CLUSTER_TOOL_TOOLS.M with the given input arguments. +% +% CLUSTER_TOOL_TOOLS('Property','Value',...) creates a new CLUSTER_TOOL_TOOLS or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before cluster_tool_tools_OpeningFunction gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to cluster_tool_tools_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_tools + +% Last Modified by GUIDE v2.5 07-Sep-2006 18:11:23 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_tools_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_tools_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_tools is made visible. +function cluster_tool_tools_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +set(handles.title,'string',[cluster_tool_getver]); +handles.pwd=pwd;handles.fdir=pwd; +for k=1:size(varargin(:),1) + if strcmp(varargin{k},'cl'),handles.cl=varargin{k+1}; + elseif strcmp(varargin{k},'fdir'),handles.fdir=varargin{k+1}; + end +end + +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to cluster_tool_tools (see VARARGIN) + +% Choose default command line output for cluster_tool_tools +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_tools wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_tools_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on button press in quit. +function quit_Callback(hObject, eventdata, handles) + +delete(handles.figure1); + +% --- Executes on button press in ret. +function ret_Callback(hObject, eventdata, handles) +if isfield(handles,'cl'),cluster_tool('cl',handles.cl,'fdir',handles.fdir); +else cluster_tool('fdir',handles.fdir);end +delete(handles.figure1); + + +% --- Executes on button press in save. +function save_Callback(hObject, eventdata, handles) +cluster_tool_savefunc + + +% --- Executes on button press in mask. +function mask_Callback(hObject, eventdata, handles) +cl_mask=cluster_tool_callgui('cluster_tool_masktypeq'); +if cl_mask + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uigetfile('*.mat','Select .mat file containing cl structure'); + cd(handles.pwd); + if ~PathName,return,end + handles.fdir=PathName; + load([PathName FileName]); + try mask=cl;catch,beep,disp('Error: No cl structure in .mat file'),return,end +else + handles.pwd=pwd;cd(handles.fdir); + [FileName,PathName]=uigetfile('*.img','Select mask .img'); + cd(handles.pwd); + if ~PathName,return,end + handles.fdir=PathName; + mask=iimg_read_img([PathName FileName],1); +end +if ~isfield(mask,'M') + if ~isfield(mask,'mat'),beep,disp('Error: no .M or .mat field in source space.'),return,end + mask.M=mask.mat; +end +if ~isequal(handles.cl(1).voxSize(:),diag(mask(1).M(1:3,1:3))) + warndlg('Warning: The mask you have selected is not in the same image space as the cl structure. The cl structure will be transferred into the space of the mask. Note that this may lead to undesirable behavior if the mask space has a higher spatial resolution than the space of the cl structure.') +end +if ~cl_mask + handles.cl=cl_img_intersect(handles.cl,mask); +else + handles.cl=cl_cl_intersect(mask,handles.cl,'switch'); +end +guidata(hObject,handles); + + +% --- Executes on button press in write. +function write_Callback(hObject, eventdata, handles) +handles.pwd=pwd;cd(handles.fdir); +[FileName,PathName]=uiputfile('*.img','Save .img file'); +cd(handles.pwd); +if ~PathName,return,end +handles.fdir=PathName; +use_values=cluster_tool_callgui('cluster_tool_writemasktypeq'); +if use_values + Z=[]; +end +XYZ=[]; +for k=1:length(handles.cl) + if size(handles.cl(k).XYZ,1)~=3 + handles.cl(k).XYZ=handles.cl(k).XYZ'; + end + XYZ(:,end+1:end+handles.cl(k).numVox)=handles.cl(k).XYZ; + if use_values + Z(end+1:end+handles.cl(k).numVox)=handles.cl(k).Z; + end +end +handles.cl(1).nvox=prod(handles.cl(1).dim(1:3)); +ind=sub2ind(handles.cl(1).dim(1:3),XYZ(1,:),XYZ(2,:),XYZ(3,:)); +dat=zeros(handles.cl(1).nvox,1); +if use_values + dat(ind)=Z; +else + dat(ind)=1; +end +if ~isfield(handles.cl,'mat') + if ~isfield(handles.cl,'M') + error('No affine matrix in cl structure. Please use the ''Check structure integrity'' tool (cl_check.m).') + else + handles.cl(1).mat=handles.cl(1).M; + end +end +iimg_write_images(dat,handles.cl(1),[PathName FileName]); +% guidata(hObject,handles); + +% --- Executes on button press in subdivide. +function subdivide_Callback(hObject, eventdata, handles) +cluster_tool_subdivide('cl',handles.cl,'fdir',handles.fdir); +close(handles.figure1); +% --- Executes on button press in recluster. +function recluster_Callback(hObject, eventdata, handles) + + +% --- Executes on button press in table. +function table_Callback(hObject, eventdata, handles) +cluster_tool_table('cl',handles.cl,'fdir',handles.fdir); +close(handles.figure1); + +% --- Executes on button press in edit. +function edit_Callback(hObject, eventdata, handles) +cluster_tool_edit('cl',handles.cl,'fdir',handles.fdir); +close(handles.figure1); + +% --- Executes on button press in check. +function check_Callback(hObject, eventdata, handles) +warndlg('Please watch the matlab command window and answer any questions asked there') +handles.cl=check_cl(handles.cl); +guidata(hObject,handles); + + + +% --- Executes when user attempts to close figure1. +function figure1_CloseRequestFcn(hObject, eventdata, handles) +delete(hObject); + + + + +% --- Executes on button press in get_betas. +function get_betas_Callback(hObject, eventdata, handles) +cluster_tool_getbetas('cl',handles.cl,'fdir',handles.fdir); +close(handles.figure1); + + diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.fig b/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.fig new file mode 100644 index 00000000..64b928ea Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.fig differ diff --git a/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.m b/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.m new file mode 100644 index 00000000..7fc20bbe --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.m @@ -0,0 +1,107 @@ +function varargout = cluster_tool_writemasktypeq(varargin) +%CLUSTER_TOOL_WRITEMASKTYPEQ M-file for cluster_tool_writemasktypeq.fig +% CLUSTER_TOOL_WRITEMASKTYPEQ, by itself, creates a new CLUSTER_TOOL_WRITEMASKTYPEQ or raises the existing +% singleton*. +% +% H = CLUSTER_TOOL_WRITEMASKTYPEQ returns the handle to a new CLUSTER_TOOL_WRITEMASKTYPEQ or the handle to +% the existing singleton*. +% +% CLUSTER_TOOL_WRITEMASKTYPEQ('Property','Value',...) creates a new CLUSTER_TOOL_WRITEMASKTYPEQ using the +% given property value pairs. Unrecognized properties are passed via +% varargin to cluster_tool_writemasktypeq_OpeningFcn. This calling syntax produces a +% warning when there is an existing singleton*. +% +% CLUSTER_TOOL_WRITEMASKTYPEQ('CALLBACK') and CLUSTER_TOOL_WRITEMASKTYPEQ('CALLBACK',hObject,...) call the +% local function named CALLBACK in CLUSTER_TOOL_WRITEMASKTYPEQ.M with the given input +% arguments. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help cluster_tool_writemasktypeq + +% Last Modified by GUIDE v2.5 13-Jul-2006 21:50:10 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @cluster_tool_writemasktypeq_OpeningFcn, ... + 'gui_OutputFcn', @cluster_tool_writemasktypeq_OutputFcn, ... + 'gui_LayoutFcn', [], ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before cluster_tool_writemasktypeq is made visible. +function cluster_tool_writemasktypeq_OpeningFcn(hObject, eventdata, handles, varargin) +movegui(hObject,'center'); +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin unrecognized PropertyName/PropertyValue pairs from the +% command line (see VARARGIN) + +% Choose default command line output for cluster_tool_writemasktypeq +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes cluster_tool_writemasktypeq wait for user response (see UIRESUME) +uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = cluster_tool_writemasktypeq_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes when user attempts to close figure1. +function figure1_CloseRequestFcn(hObject, eventdata, handles) +% hObject handle to figure1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: delete(hObject) closes the figure +delete(hObject); + + +% --- Executes on button press in overwrite. +function overwrite_Callback(hObject, eventdata, handles) +handles.data=0; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to overwrite (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + +% --- Executes on button press in append. +function append_Callback(hObject, eventdata, handles) +handles.data=1; +guidata(hObject,handles) +uiresume(handles.figure1) +% hObject handle to append (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + + diff --git a/Cluster_contig_region_tools/cluster_tool/mmToVoxel.m b/Cluster_contig_region_tools/cluster_tool/mmToVoxel.m new file mode 100644 index 00000000..b047a9ac --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/mmToVoxel.m @@ -0,0 +1,69 @@ +function varargout = mmToVoxel(XYZmm,M,varargin) +% Usage: +% XYZ = mmToVoxel(XYZmm,M) +% [XYZ,point_index] = mmToVoxel(XYZmm,M,'valid') +% +% XYZ is 3 vector point list, with XYZ assumed to be in ROWS if you input a +% list with 3 points. +% m is SPM mat - 4 x 4 affine transform (usually .M or .mat in a structure) +% (what's stored in the .mat file) +% +% use of the 'valid' text flag will force all output XYZ values to be real +% positive integers. This is done by rounding all values and eliminating +% all points with an X, Y, or Z value <1. Only unique points will be +% reported. +% +% the point_index output variable is a list of indices that can be used +% in a corresponding matrix or vector (e.g. a vector of Z scores +% corresponding to each voxel) for removing non-unique points. +% For example, the following code will take the mean of the Z scores of +% non-unique points assigned to the same coordinate by using the 'valid' +% switch, and ensure that each point in the Z vector still corresponds to +% the correct coordinate: +% [XYZ,point_index]=mmToVoxel(XYZmm,M,'valid'); +% for m=1:max(point_index) +% ind=find(point_index(:)==point_index(m)); +% Z(ind)=mean(Z(ind)); +% end +% Z=Z(unique(point_index)); +% +% the above is primarily useful when converting a list of XYZmm points that +% did not originate in the same space defined by the affine matrix. +% +% intended to replace mm2voxel, which is not symmetric with voxel2mm and +% requires a structure with a .M or .mat field in it to be passed in, +% rather than looking for the affine matrix to be passed in directly. +% +% Example: +% XYZ = mmToVoxel([x y z],volInfo.mat); + +valid=0; +if ~isempty(varargin) + for k=1:length(varargin) + if strcmp(varargin{k},'valid') + valid=1; + end + end +end + +flip=0; +if isempty(XYZmm), XYZ = [];, return, end +if size(XYZmm,1)~=3 + if size(XYZmm,2)~=3,error('XYZ matrix must have 3 elements in one of its dimensions!') + else XYZmm=XYZmm';flip=1; + end +end + +XYZmm(4,:) = 1; +XYZ=(M^-1)*XYZmm; +XYZ=XYZ(1:3,:); +if valid + XYZ=round(XYZ); + [row,col]=find(XYZ<1); + XYZ(:,col)=[]; + [l,m,n]=unique(XYZ','rows'); + XYZ=XYZ(:,unique(m)); + varargout{2}=m; +end +if flip,XYZ=XYZ';end +varargout{1}=XYZ; \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/space_match.m b/Cluster_contig_region_tools/cluster_tool/space_match.m new file mode 100644 index 00000000..fad6d804 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/space_match.m @@ -0,0 +1,94 @@ +function [XYZmm_out]=space_match(XYZmm,voxsize,varargin) + +% Usage: +% XYZmm_out=space_match(XYZmm,voxsize,['force']) +% +% Takes a series of points in mm coordinates, XYZmm, and matches each point +% to the nearest point in a space defined by voxsize and the coordinate +% point 0,0,0. +% +% Including the string 'force' will force a matched point that is +% equidistant from at least 2 points in the space to be assigned to the +% point with the highest absolute value in each dimension. +% + +if nargin>2 + if strcmp(varargin{1},'force') + force=1; + else + force=0; + end +else + force=0; +end + +if size(XYZmm,1)~=3 + XYZmm=XYZmm'; +end + +if length(XYZmm)>3 + l=length(XYZmm); +else + l=min(size(XYZmm)); +end + +XYZmm_out=zeros(size(XYZmm)); + +for k=1:l + hi=XYZmm(:,k)+voxsize(:); + lo=XYZmm(:,k)-voxsize(:); + lobound(1:3)=0;hibound(1:3)=0; + for i=1:3 + if lo(i)>0 + while lobound(i)<=lo(i) + lobound(i)=lobound(i)+voxsize(i); + end + lobound(i)=lobound(i)-voxsize(i); + else + while lobound(i)>=lo(i) + lobound(i)=lobound(i)-voxsize(i); + end + end + if hi(i)>0 + while hibound(i)<=hi(i) + hibound(i)=hibound(i)+voxsize(i); + end + else + while hibound(i)>=hi(i) + hibound(i)=hibound(i)-voxsize(i); + end + hibound(i)=hibound(i)+voxsize(i); + end + end + space=zeros([3 length(lobound(1):voxsize(1):hibound(1))*length(lobound(2):voxsize(2):hibound(2))*length(lobound(3):voxsize(3):hibound(3))]); + count=0; + for m=lobound(1):voxsize(1):hibound(1) + for n=lobound(2):voxsize(2):hibound(2) + for p=lobound(3):voxsize(3):hibound(3) + count=count+1; + space(:,count)=[m;n;p]; + end + end + end + dist=zeros([1 size(space,2)]); + for i=1:size(space,2) + dist(i)=sqrt((space(1,i)-XYZmm(1,k))^2+(space(2,i)-XYZmm(2,k))^2+(space(3,i)-XYZmm(3,k))^2); + end + a=find(dist==min(dist)); + if isscalar(a) + XYZmm_out(:,k)=space(:,a); + elseif force + b=find(sum(abs(space(:,a)))==max(sum(abs(space(:,a))))); + XYZmm_out(:,k)=space(:,a(b)); + else + r=randperm(length(a)); + XYZmm_out(:,k)=space(:,a(r(1))); + disp(['Warning: ' num2str(XYZmm(1,k)) ',' num2str(XYZmm(2,k)) ',' num2str(XYZmm(3,k)) 'is equidistant from at least 2 points. It has been set to ' num2str(XYZmm_out(1,k)) ',' num2str(XYZmm_out(2,k)) ',' num2str(XYZmm_out(3,l)) '.']) + end +end +% XYZmm_out=unique(XYZmm_out','rows')'; commented because it can cause +% XYZmm_out to be of different length than XYZmm, which can create problem +% outside of the function, such as when there are Z values corresponding to +% the elements of XYZmm that also need to be matched to XYZmm_out. This +% should be handled in calling functions. + \ No newline at end of file diff --git a/Cluster_contig_region_tools/cluster_tool/sphere_3d.m b/Cluster_contig_region_tools/cluster_tool/sphere_3d.m new file mode 100644 index 00000000..e74f23ca --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/sphere_3d.m @@ -0,0 +1,74 @@ +% Usage: +% spheres_out = sphere_3d(sphere_centers, r, [unit_length]); +% +% sphere_centers is row or column vector or matrix with rows equal to a +% point in some 3d space, must have 3 columns or rows. If both, it is +% assumed that X Y Z dimensions are specified by different rows, and that +% columns are points. Output is in the same format as the input. +% +% r is a vector of radii with length equal to the number of rows of +% sphere_centers. A scalar input may also be used, resulting in the same +% radii for all spheres. +% +% spheres_out is a matrix with rows equal to all points in the +% spheres defined by sphere_centers and r +% +% unit_length specifies the unit size of the space in each dimension (or a +% scalar for isotropic sizes). default is 1. +% +% sphere_3d assumes that the point 0, 0, 0 exists in the space, and will only +% report points that are integer multiples of unit length distance from +% 0, 0, 0. + +function spheres_out = sphere_3d(sphere_centers, r, varargin) + flip = 0; + if isempty(sphere_centers) || isempty(r) || (max(size(sphere_centers)) ~= 3 & min(size(sphere_centers))~= 3), error('Error: incorrect inputs'), end + if size(sphere_centers, 1) ~= 3 + sphere_centers = sphere_centers'; + flip = 1; + end + + for i = 1:length(varargin) + if isnumeric(varargin{i}), + if length(varargin{i}) == 1, unit_length = [varargin{i} varargin{i} varargin{i}]; + elseif length(varargin{i}) == 3, unit_length = varargin{i}; + else error('Error: unit_length incorrectly specified') + end + else disp(['Warning: do not recognize input ' num2str(i+nargin-length(varargin))]) + end + end + if ~exist('unit_length', 'var'), unit_length = [1 1 1]; end + + if length(r) == 1, r(1:size(sphere_centers, 2)) = r; end + + spheres_out = []; + + for i = 1:size(sphere_centers, 2) + boxmin = space_match(sphere_centers(:,i)-r(i), unit_length, 'force'); + boxmax = space_match(sphere_centers(:,i)+r(i), unit_length, 'force'); + candidates = zeros([3 length(boxmin(1):unit_length(1):boxmax(1))*length(boxmin(2):unit_length(2):boxmax(2))*length(boxmin(3):unit_length(3):boxmax(3))]); + count = 0; + for m = boxmin(1):unit_length(1):boxmax(1) + for n = boxmin(2):unit_length(2):boxmax(2) + for p = boxmin(3):unit_length(3):boxmax(3) + count = count+1; + candidates(:,count) = [m n p]'; + end + end + end + for k = 1:size(candidates, 2) + if sqrt(sum((sphere_centers(:,i)-candidates(:,k)).^2))>r(i) + candidates(:,k) = NaN; + end + end + [row, col] = find(isnan(candidates)); + col = unique(col); + if ~isempty(col), candidates(:,col) = []; end + spheres_out(:,end+1:end+size(candidates, 2)) = candidates; + end + spheres_out = space_match(spheres_out, unit_length); + spheres_out = unique(spheres_out', 'rows')'; + if isempty(spheres_out), spheres_out = space_match(sphere_centers, unit_length, 'force'); end + if flip, spheres_out = spheres_out'; end +end + diff --git a/Cluster_contig_region_tools/cluster_tool/taldata.mat b/Cluster_contig_region_tools/cluster_tool/taldata.mat new file mode 100644 index 00000000..cfded2bf Binary files /dev/null and b/Cluster_contig_region_tools/cluster_tool/taldata.mat differ diff --git a/Cluster_contig_region_tools/cluster_tool/voxelToMm.m b/Cluster_contig_region_tools/cluster_tool/voxelToMm.m new file mode 100644 index 00000000..9b364ef7 --- /dev/null +++ b/Cluster_contig_region_tools/cluster_tool/voxelToMm.m @@ -0,0 +1,26 @@ +function XYZmm = voxelToMm(XYZ,M) +% Usage: +% XYZmm = voxelToMm(XYZ,M) +% +% XYZ is 3 vector point list, with XYZ assumed to be in ROWS if you input a +% list with 3 points. +% m is SPM mat - 4 x 4 affine transform (usually .M or .mat in a structure) +% (what's stored in the .mat file) +% +% essentially a copy of voxel2mm, exists for symmetry with mmToVoxel. +% +% Example: +% XYZmm = voxelToMm([x y z],volInfo.mat); + +flip=0; +if isempty(XYZ), XYZmm = [];, return, end +if size(XYZ,1)~=3 + if size(XYZ,2)~=3,error('XYZ matrix must have 3 elements in one of its dimensions!') + else XYZ=XYZ';flip=1; + end +end + +XYZ(4,:) = 1; +XYZmm = M*XYZ; +XYZmm = XYZmm(1:3,:); +if flip,XYZmm=XYZmm';end \ No newline at end of file diff --git a/Cluster_contig_region_tools/clusters2CLU.m b/Cluster_contig_region_tools/clusters2CLU.m new file mode 100755 index 00000000..ca51394a --- /dev/null +++ b/Cluster_contig_region_tools/clusters2CLU.m @@ -0,0 +1,64 @@ +function CLU = clusters2CLU(clusters,varargin) + % function CLU = clusters2CLU(clusters,[opt] M) + % + % Inputting an M matrix will transform the coordinates + % by that M, to convert between voxel sizes, etc. + % + % by Tor Wager + + CLU = []; + if isempty(clusters), return, end + + if ~isfield(clusters(1),'threshold'), clusters(1).threshold = 1; end + + if isfield(clusters(1),'Z_descrip'), CLU.Z_descrip = clusters(1).Z_descrip; end + + CLU.XYZmm = cat(2,clusters.XYZmm); + CLU.mm_center = mean(CLU.XYZmm, 2)'; + CLU.XYZ = cat(2,clusters.XYZ); + try + CLU.Z = cat(2,clusters.Z); + catch + %CLU.Z = cat(1,clusters.Z)'; + for i = 1:length(clusters), if size(clusters(i).Z,1) > size(clusters(i).Z,2), clusters(i).Z = clusters(i).Z'; end, end + CLU.Z = cat(2,clusters.Z); + end + CLU.title = clusters(1).title; + + CLU.u = clusters(1).threshold; + CLU.threshold = clusters(1).threshold; + + CLU.voxSize = clusters(1).voxSize; + CLU.VOX = clusters(1).voxSize; + + try + CLU.P = strvcat(clusters.P); + CLU.imP = strvcat(clusters.imP); + catch + end + + if isfield(clusters, 'all_data') + CLU.all_data = cat(2, clusters.all_data); + end + + if nargin > 1 + CLU.M = varargin{1}; + CLU = transform_coordinates(CLU,CLU.M); + + elseif ~isfield(clusters,'M') + disp('Choose SPM.mat file with volume info (affine mat file)') + [SPM,VOL,xX,xCon,xSDM] = spm_getSPM; + CLU.M = VOL.M; + else + CLU.M = clusters(1).M; + end + + CLU.numVox = size(CLU.XYZmm,2); + + if isempty(CLU.Z) + disp('clusters Z field is empty. Filling with ones as a placeholder.') + CLU.Z = ones(1,size(CLU.XYZmm,2)); + end + + if size(CLU.Z,1) > 1, CLU.allZ = CLU.Z; CLU.Z = CLU.Z(1,:); end +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/clusters2mask.m b/Cluster_contig_region_tools/clusters2mask.m new file mode 100755 index 00000000..964f69ad --- /dev/null +++ b/Cluster_contig_region_tools/clusters2mask.m @@ -0,0 +1,102 @@ +function [m,V,cl] = clusters2mask(cl,V,varargin) + % [m,V,cl] = clusters2mask(cl,V,[opt: write Z-scores]) + % tor wager + % + % This function has 2 modes! If V is a structure: + % + % converts clusters structure to a mask image, given V structure with V.mat + % field. V.mat is an SPM mat file. V.dim is dims of image + % uses cl.XYZmm + % m is mask img data, V is mask vol info + % Also replaces cl.XYZ (voxels) + % + % If V is a vector of mask dimensions: + % + % converts clusters to mask image using existing XYZ and dims of mask + % + % see also voxels2mask, for a faster function that uses XYZ voxel coords + % + % Example: + % Save an image file with just one cluster from a set (#7 in this ex.) + % cl = mask2clusters('roi_group1.img'); + % V = spm_vol('roi_group1.img'); % we need .mat and .dim from this, or + % just dim + % [m,V,cl] = clusters2mask(cl(7),struct('mat',cl(1).M,'dim',V.dim)); + % or + % [m,V,cl] = clusters2mask(cl(7),V); + % + % clusters2mask(cl,struct('mat',V.mat,'dim',V.dim),0,'spm2_hy.img'); + % + % for SPM5: + % clusters2mask(cl,struct('mat',V.mat,'dim',V.dim, 'dt', V.dt),0,'spm2_hy.img'); + % clusters2mask(cl, + % struct('mat',MC_Setup.volInfo.mat,'dim',MC_Setup.volInfo.dim, 'dt', MC_Setup.volInfo.dt),0,'acc_roi_mask.img'); + + global defaults + + fname = 'clustermask.img'; + if length(varargin) > 1, fname = varargin{2}; end + + doZ = 0; + if length(varargin) > 0, doZ = varargin{1}; end + + if isstruct(V) + %%% MM way + + m = zeros(V.dim(1:3)); + + if isfield(V,'mat') + %mymat = V.mat; do nothing + elseif isfield(V,'M'); + V.mat = V.M; + else + error('Structure must have a mat or M field.'); + end + + for i = 1:length(cl) + + cl(i).XYZ = mm2voxel(cl(i).XYZmm,struct('M',V.mat),1)'; + + % eliminate out of range + + + ind = sub2ind(V.dim(1:3),cl(i).XYZ(1,:)',cl(i).XYZ(2,:)',cl(i).XYZ(3,:)'); + + if doZ, m(ind) = cl(i).Z;,else, m(ind) = 1; end + + end + + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + + if(isempty(defaults)) + spm_defaults(); + end + + if ~isfield(V, 'dt') + warning('Using default datatype; Enter dt field in input structure to use yours.'); + V.dt(1) = spm_type('float32'); + V.dt(2) = 1; + end + end + + V.fname = fname; + spm_write_vol(V,double(m)); + disp(['Written ' fname]) + cl = mask2clusters(fname); + + else + %%% VOXEL way + for i = 1:length(cl) + mask(:,:,:,i) = voxel2mask(cl(i).XYZ',V); + end + m = sum(mask,4); + m = double(m > 0); + end + + return diff --git a/Cluster_contig_region_tools/enlarge_cluster.m b/Cluster_contig_region_tools/enlarge_cluster.m new file mode 100755 index 00000000..0d9c6f56 --- /dev/null +++ b/Cluster_contig_region_tools/enlarge_cluster.m @@ -0,0 +1,24 @@ +% function cl = enlarge(cl) +% +% enlarge cluster + +function cl = enlarge_cluster(cl) + + XYZ = cl.XYZ; + XYZnew = XYZ; + + for i = 1:3 + Xtmp = XYZ; + Xtmp(i, :) = Xtmp(i, :) + 1; + XYZnew = [XYZnew Xtmp]; % add to dim + Xtmp(i, :) = Xtmp(i, :) - 2; + XYZnew = [XYZnew Xtmp]; % subtract 1 from dim + end + + XYZnew = unique(round(XYZnew)', 'rows')'; + cl.XYZ = XYZnew; + cl.XYZmm = voxel2mm(XYZnew, cl.M); + cl.Z = ones(1, size(cl.XYZ, 2)); + + cl.numVox = size(cl.XYZmm, 2); +end diff --git a/Cluster_contig_region_tools/image2clusters.m b/Cluster_contig_region_tools/image2clusters.m new file mode 100644 index 00000000..5d947ca7 --- /dev/null +++ b/Cluster_contig_region_tools/image2clusters.m @@ -0,0 +1,117 @@ +function varargout = image2clusters(varargin) +% NOTE--documented usage does not appear to correspond to function +% behavior--comment added by Jared 7/13/06 +% +% cl = image2clusters([overlay image name]) +% +% Menu-driven function for getting clusters from an image file (e.g., a +% t-image) +% +% Can also return clusters active in two contrasts, sorted by increases in +% both, decreases in both, inc in first, dec in first +% useful for testing whether something is both activated and correlated! +% e.g., see active_plus_corr_scatterplot_plugin +% +% [pospos,negneg,posneg,negpos] = image2clusters(overlay) + +overlay = which('scalped_single_subj_T1.img'); +if length(varargin) > 0 && ~isempty(varargin{1}), overlay = varargin{1};, end +if isempty(overlay), warning('Cannot find overlay image on path!');, end + + +dd = spm_get(1,'*img','Select image file to get clusters from.',pwd); + +ist = spm_input(['Is the image a t-image (1 or 0)? '],[],'y/n',[1 0],1); + + +if ist, + thr = spm_input(['Enter p-value threshold (uncorrected): ']); + df = spm_input(['Enter degrees of freedom (N - params estimated): ']); + + u = tinv(1-thr,df); +else + u = spm_input(['Enter threshold value for image: ']); +end + +kthr = spm_input(['Enter extent threshold in voxels: ']); + +%fx = spm_input(['Get which effects? Type pos, neg, or both: '],[],'s','both'); +fx = spm_input(['Get which effects? '],[],'b',{'pos' 'neg' 'both'},{'pos' 'neg' 'both'}); fx = fx{1}; + +cross = spm_input(['Mask with another t-image (e.g., correlation t-values): '],[],'y/n',[1 0],0); + +[P2,P,sigmat,sigmatneg] = threshold_imgs(dd,u,kthr,fx); +disp(['Thresholding image: created ' num2str(P2)]) + +if ~cross, + % go ahead + + cl = mask2clusters(P2); + + doplot = spm_input(['Display orthviews? '],[],'y/n',[1 0],0); + + if doplot + switch fx + case 'both' + cluster_orthviews(cl,'bivalent','overlay',overlay); + otherwise + + color = spm_input(['Enter vector of colors [r g b], e.g., [1 0 0] for red: ']); + cluster_orthviews(cl,{color},'overlay',overlay); + end + end + + varargout{1} = cl; + +else + % cross significant regions in first image with 2nd image + dd2 = spm_get(1,'*img','Select second t-image (e.g., correlation t-vals).',pwd); + thr = spm_input(['Enter p-value threshold (uncorrected): ']); + df = spm_input(['Enter degrees of freedom (N - params estimated): ']); + u = tinv(1-thr,df); + kthr = spm_input(['Enter extent threshold in voxels: ']); + %fx = spm_input(['Get which effects? Type pos, neg, or both: '],[],'s','both'); + fx = spm_input(['Get which effects? '],[],'b',{'pos' 'neg' 'both'},{'pos' 'neg' 'both'}); fx = fx{1}; + + [P3,tmp,sigmat,sigmatneg] = threshold_imgs(dd2,u,kthr,fx); + disp(['Thresholding image: created ' num2str(P3)]) + + V1 = spm_vol(P2); % first image + v1 = spm_read_vols(V1); + + V2 = spm_vol(P3); % second image + v2 = spm_read_vols(V2); + + pospos = mask2clusters(v1>0 & v2>0, V1.mat); if ~isempty(pospos),pospos(1).title = ['Positive in ' P2 ' and ' P3];,end + posneg = mask2clusters(v1>0 & v2<0, V1.mat); if ~isempty(posneg),posneg(1).title = ['Positive:' P2 ' and negative:' P3];,end + negneg = mask2clusters(v1<0 & v2<0, V1.mat); if ~isempty(negneg),negneg(1).title = ['Negative in ' P2 ' and ' P3];,end + negpos = mask2clusters(v1<0 & v2>0, V1.mat); if ~isempty(negpos),negpos(1).title = ['negative:' P2 ' and positive:' P3];,end + + cluster_orthviews(pospos,{[1 0 0]},'overlay',overlay); + cluster_orthviews(negneg,{[0 0 1]},'overlay',overlay,'add'); + cluster_orthviews(posneg,{[1 .5 0]},'overlay',overlay,'add'); + cluster_orthviews(negpos,{[0 .5 1]},'overlay',overlay,'add'); + + % make legend + names = {'Activated and + correlation' 'Deactivated and - correlation' 'Activated and - correlation' 'Deactivated and + correlation'}; + colors = [{[1 0 0]} {[0 0 1]} {[1 .5 0]} {[0 .5 1]}] + + curfig=gcf; + figure;set(gcf,'Color','w');set(gca,'FontSize',16); + makelegend(names,colors,1); + figure(curfig); + + cl = pospos; + cl = merge_clusters(cl,negneg); + cl = merge_clusters(cl,posneg); + cl = merge_clusters(cl,negpos); + + varargout{1} = cl; + varargout{2} = pospos; + %varargout{3} = negneg; + varargout{4} = posneg; + varargout{5} = negpos; +end + +return + diff --git a/Cluster_contig_region_tools/mask2clusters.m b/Cluster_contig_region_tools/mask2clusters.m new file mode 100755 index 00000000..1cd97477 --- /dev/null +++ b/Cluster_contig_region_tools/mask2clusters.m @@ -0,0 +1,169 @@ +function [clusters,CLU,subclusters] = mask2clusters(P,varargin) +%[clusters,CLU,subclusters] = mask2clusters(img mask file with voxels,[imgs to extract data from],[df]) +% +% tor wager +% extracts clusters and con img data from mask +% use with mask_intersection.m +% +% to get clusters but not extract data, enter only one argument. +% to get clusters and choose extraction imgs with the GUI, enter an empty [] 2nd argument. +% +% DOES *NOT* CONVERT BETWEEN DIFFERENT VOXEL SIZES AND POSITIONS BETWEEN IMNAMES AND SPM/VOL STRUCTS +% +% see also roi_probe +% +% modification 2/27/03 +% if no imgs are entered, Z-scores are values from mask +% if df is entered, values in mask img are converted to Z-scores with spm_t2z.m +% if extract img names are empty and df is entered, assume we're using values from mask as t-values +% and convert to Z-scores +% +% WARNING: for spm2 compatibility, ABSOLUTE VALUES of voxel sizes are +% returned; e.g., ignores analyze flipping in SPM2. +% +% % Matlab 6.5/OSX bug gives seg fault or something if mask is too big. +% +% Examples: +% +% cl = mask2clusters('myimage.img',[img string mtx],[]); % no z-score +% conversion, extracts data from [img string mtx] +% +%cl = mask2clusters('rob_tmap_0002_filt_t_3-05_k10_neg.img') +% +% This one works with already-loaded image data and a mat matrix: +% V = spm_vol('rob_tmap_0002_filt_t_3-05_k10_neg.img'); dat = spm_read_vols(V); +% cl = mask2clusters(dat,V.mat); +% + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% set up inputs +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +clusters = []; +df = []; imP = []; resl = 0; vmat = []; + + +for i = 1:length(varargin) + tmp = varargin{i}; + if isstr(tmp), imP = tmp; % images to extract data from + elseif isstruct(tmp), vmat = tmp.mat; % it's an spm_vol V struct + elseif isscalar(tmp), df = tmp; % df to convert from t to z-scores + elseif all(size(tmp) > 1), vmat = tmp; % it's a SPM .mat matrix to be used with raw data instead of string img name + end +end + +if isempty(P) + P = spm_get(1,'*img','Select mask image with voxels to extract'); +end + +if ischar(P) + P = deblank(P); + + if isempty(which(P)) && ~exist(P,'file') + disp(['Cannot find file: ' P]); + return + end + + if ~isempty(which(P)), P = which(P); end +end + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% get coordinates and height values from the mask +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +% img2voxel can take raw data, if vmat is entered; if string, vmat not +% used. +[CLU.XYZ,CLU.XYZmm,CLU.Z,CLU.V] = img2voxel(P,vmat); +CLU.XYZ = CLU.XYZ(1:3,:); + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% change pseudo-T or T values to Z scores +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +if ~isempty(imP) + if isempty(df) & length(varargin) > 1, df = size(imP,1) - 1;, end % to get df + if df == 0, df = [];, end +end + +if ~isempty(df) + disp(['Converting to Z scores based on ' num2str(df) ' df.']) + [CLU.Z] = spm_t2z(CLU.Z,df); +else + df = 0; + %disp('Saving values in mask file in clusters.Z (no z-score conversion)') + %CLU.Z = ones(1,size(CLU.XYZ,2)); +end + +if size(CLU.Z,1) > size(CLU.Z,2), CLU.Z = CLU.Z';, end + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% fill in other fields +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +% adjust crit_t from 0 to 1 for compatibility with fixed_TSU (Talairach) +% needs to be a non-zero value to display on Talairach +% ------------------------------------------------------ +CLU.crit_t = 1; + +CLU.cl_size = 0; +CLU.df = df; + +% for compatibility with SPM struct and cluster analysis +% ------------------------------------------------------ +CLU.voxSize = diag(CLU.V.mat)'; +CLU.voxSize = CLU.voxSize(1:3); +CLU.VOX = CLU.voxSize; % compatible with VOL structure +CLU.M = CLU.V(1).mat; + +CLU.u = CLU.crit_t; +CLU.k = CLU.cl_size; + +if isfield(CLU.V,'fname') + [a,b,c] = fileparts(CLU.V.fname); + CLU.title = [b]; +else + CLU.title = 'Analyze image file.'; +end + +if resl, imP = spm_get(Inf,'*img','Select imgs for data extraction.'); +else, % input imP % to get P +end + +%if ~exist(P(1,:)) +% warning('Image files cannot be found in ind. subject directories: Path has changed?') + % tries to re-find P - only works for my specific directory +% for i = 1:size(P,1) +% [d,f,e] = fileparts(P(1,:)); +% [dummy,d] = fileparts(d); +% newP{i,1}=fullfile('..',d,[f e]); +% end +% P = cell2mat(newP); +% disp(['Found: ' P(1,:) ' etc.']) +%end + +if isempty(CLU), + warning('EMPTY ... mask2clusters found no eligible voxels.') + clusters = []; + return +end + +if isempty(CLU.XYZ) | isempty(CLU.Z) + warning('EMPTY ... mask2clusters found no eligible voxels.') + clusters = []; + return +end + +[clusters] = tor_extract_rois(imP,CLU,CLU,1); +for i = 1:length(clusters), + clusters(i).P = P;, + clusters(i).imP = imP;, + if size(imP,1) == 1 & df == 0, + %disp('Saving values in mask file in clusters.Z') + clusters(i).Z = clusters(i).all_data; + end + + % for SPM2 compatibility + clusters(i).voxSize = abs(clusters(i).voxSize); +end + +if nargout > 2, [subclusters] = cluster_princomp(clusters); end + +return \ No newline at end of file diff --git a/Cluster_contig_region_tools/mask2struct.m b/Cluster_contig_region_tools/mask2struct.m new file mode 100755 index 00000000..3a966aaa --- /dev/null +++ b/Cluster_contig_region_tools/mask2struct.m @@ -0,0 +1,104 @@ +function V = mask2struct(maskname,varargin) +% function V = mask2struct(maskname,crit_t,cl_size) +% inputs: +% maskname: name of spmT, con, or filtered image +% without .img extension, in single quotes +% optional inputs: +% crit_t, cl_size: critical t and cluster size at which to mask +% +% output: structure compatible with SPM viewing +% and with cluster definition algorithm tor_extract_rois +% to extract clusters: +% [clusters] = tor_extract_rois(maskname,V,V); +% +% to display: +% spm_image (and choose anatomical) +% spm_orthviews('AddBlobs',1,V.XYZ,V.Z,V.mat) +% spm_orthviews('AddColouredBlobs',1,V.XYZ,V.Z,V.mat,[0 0 1]) +% +% to overlay on Talairach atlas +% fixed_TSU(clusters) +% +crit_t = 0; +cl_size = 0; +numClusters = NaN; + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% input arguments +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +if nargin > 1, crit_t = varargin{1};, end +if nargin > 2, cl_size = varargin{2};, end + + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% read in the mask file +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +inV = spm_vol(maskname); +[vol,hdr] = readim2(maskname); +vol = spm_read_vols(inV); % deal with SPM scaling factor! +vol = double(vol); + + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% turn mask into 1s and 0s +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +[maskedImage maskingImage] = maskImg(vol,crit_t,Inf); + % if crit_t = 0 + %maskedImage is all positive values + %maskingImage has all positive values = 1 + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% make SPM.mat-like structure V +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + % Get XYZ pointlist from mask and cluster size mask if spec + % ------------------------------------------------------ + if cl_size > 0 + [maskingImage,numClusters,XYZ] = clusterSizeMask(cl_size,maskingImage); + else + XYZ = mask2voxel(maskingImage); + XYZ = XYZ'; + end + + % Get Z output - intensity values for sig. voxels + % ------------------------------------------------------ + for i = 1:size(XYZ,2) + % row is y, col is x + Z(i) = vol(XYZ(1,i),XYZ(2,i),XYZ(3,i)); + end + + if isempty(XYZ) + Z = []; + end + + % get the header and volume information from the maskname + % ------------------------------------------------------ + V = spm_vol(maskname); + + % adjust crit_t from 0 to 1 for compatibility with fixed_TSU (Talairach) + % needs to be a non-zero value to display on Talairach + % ------------------------------------------------------ + if crit_t == 0, crit_t = 1;, end + + % add other fields of V + % ------------------------------------------------------ + V.maskingImage = maskingImage; + V.cl_size = cl_size; + V.crit_t = crit_t; + V.numClusters = numClusters; + V.XYZ = XYZ; + V.Z = Z; + V.XYZmm = voxel2mm(XYZ,V.mat); + + % for compatibility with SPM struct and cluster analysis + % ------------------------------------------------------ + V.voxSize = [hdr.xsize hdr.ysize hdr.zsize]'; + V.u = crit_t; + V.k = cl_size; + V.title = V.fname; + V.VOX = V.voxSize; % compatible with VOL structure + V.M = inV.mat; + + %eval(['save ' maskname '_struct V']) + +return \ No newline at end of file diff --git a/Cluster_contig_region_tools/merge_clusters.m b/Cluster_contig_region_tools/merge_clusters.m new file mode 100755 index 00000000..c381dc24 --- /dev/null +++ b/Cluster_contig_region_tools/merge_clusters.m @@ -0,0 +1,46 @@ +% subclusters = merge_clusters(clusters_to_match, subclusters_to_change) +% MERGE_CLUSTERS - function for synchronizing the field list of cluster structures +% and merging them +function outcl = merge_clusters(c2m, subcl) + if isempty(c2m) + disp('Merge clusters: Clusters to match is empty.'); + outcl = subcl; + return + end + + if isempty(subcl) + outcl = c2m; + return + end + + if ~iscell(subcl) + s{1} = subcl; + subcl = s; + end + outcl = c2m; + + for i = 1:length(subcl) + for j = 1:length(subcl{i}) + % make all field names match. + newcl{i}(j) = c2m(1); + N = fieldnames(newcl{i}(j)); + for NN = 1:length(N) + eval(['newcl{i}(j).' N{NN} ' = [];']) + end + + %N = fieldnames(clusters(i)); + for NN = 1:length(N) + str = ['newcl{i}(j).' N{NN} ' = subcl{i}(j).' N{NN} ';']; + try + eval(str) + catch + disp(['Making ' N{NN}]) + str = ['newcl{i}(j).' N{NN} ' = [];']; + eval(str) + end + end + end + + outcl = ([outcl, newcl{i}]); + end +end diff --git a/Cluster_contig_region_tools/merge_nearby_clusters.m b/Cluster_contig_region_tools/merge_nearby_clusters.m new file mode 100644 index 00000000..0ca52b0c --- /dev/null +++ b/Cluster_contig_region_tools/merge_nearby_clusters.m @@ -0,0 +1,109 @@ +% newcl = merge_nearby_clusters(cl, thr) +% +% Merge sets of clusters whose centers are all within thr mm of each other +% uses parcel_complete_sets.m +% +% The command below runs the function recursively until all clusters are > +% thr mm apart +% newcl = merge_nearby_clusters(cl, thr, 'recursive') +% +% tor wager, nov 06 +% + +function newcl = merge_nearby_clusters(cl, thr, varargin) + + if isempty(cl), newcl = []; return, end + + dorecursive = 0; + % run this function recursively if specified + if any(strcmp(varargin, 'recursive')) + d = 0; + while any(d < thr) + cl = merge_nearby_clusters(cl, thr); + d = pdist(cat(1, cl.mm_center)); + end + + newcl = cl; + + return + end + + + evalstr = ['s < ' num2str(thr)]; + + + % get distances based on cluster centers + xyz = cat(1, cl.mm_center); + d = pdist(xyz); + d = squareform(d); + + [mysets, n_in_set, sets_by_vars, classes] = parcel_complete_sets(d, 'dounique', 'nofuzzy', 'threshold', evalstr, 'min'); + + for i = 1:length(mysets) + wh = mysets{i}; % which clusters to merge into new cluster i + + % start the new cluster + newcl(i) = cl(wh(1)); + + % get target sizes to match + % updates N (names), nfields + get_fields_to_cat; + + for j = 2:length(wh) + for k = 1:nfields + [dat, sz] = get_sz_and_data(cl(wh(j)) , N{k}); + + update_newcl; + + end + + update_titles; + end + + newcl(i).numVox = size(newcl(i).XYZ, 2); + newcl(i).mm_center = center_of_mass(newcl(i).XYZmm, newcl(i).Z); + end + + % NESTED + % + % + + function get_fields_to_cat + N = fieldnames(cl); + nfields = length(N); + for k = 1:nfields + [dat, sz(k, :)] = get_sz_and_data(newcl(i), N{k}); + end + wh_horzcat = find(sz(:, 2) == newcl(i).numVox); + N = N(wh_horzcat); + + % remove specific problem fields + whr = find(strcmp(N, 'M')); if ~isempty(whr), N(whr) = []; end + whr = find(strcmp(N, 'mat')); if ~isempty(whr), N(whr) = []; end + whr = find(strcmp(N, 'dim')); if ~isempty(whr), N(whr) = []; end + whr = find(strcmp(N, 'voxSize')); if ~isempty(whr), N(whr) = []; end + + nfields = length(N); + end + + function update_newcl + if sz(1) == size(newcl(i).(N{k}), 1) + newcl(i).(N{k}) = [newcl(i).(N{k}) dat]; + else + warning(['Size mismatch in field ' N{k}]) + end + end + + function update_titles + if isfield(newcl, 'title'), newcl(i).title = str2mat(newcl(i).title, cl(wh(j)).title); end + if isfield(newcl, 'name'), newcl(i).name = str2mat(newcl(i).name, cl(wh(j)).name); end + if isfield(newcl, 'shorttitle'), newcl(i).shorttitle = str2mat(newcl(i).shorttitle, cl(wh(j)).shorttitle); end + end + +end + + +function [dat, sz] = get_sz_and_data(cl, fldname) + dat = cl.(fldname); % data for this field + sz = size(dat); % size of field data +end diff --git a/Cluster_contig_region_tools/subclusters_from_local_max.m b/Cluster_contig_region_tools/subclusters_from_local_max.m new file mode 100644 index 00000000..c48e4c2c --- /dev/null +++ b/Cluster_contig_region_tools/subclusters_from_local_max.m @@ -0,0 +1,60 @@ +% subcl = subclusters_from_local_max(cl, dist_thresh) +% Breaks apart a cluster into smaller clusters + +function subcl = subclusters_from_local_max(cl, dist_thresh) + if isempty(cl), return; end + subcl = []; + + if ~isfield(cl, 'dim'), cl(1).dim = []; end + N = fieldnames(cl(1)); + + for i = 1:length(cl) + [xyz, XYZmm, Z, class] = cluster_local_maxima(cl(i), dist_thresh, 0); + + if all(class == 0) % no peak maxima; just use the existing cluster + %this_subcl = cl; + % make same as subclusters by saving only relevant fields + + create_subcl; + this_subcl = add_fields(N, this_subcl, cl, i); + else + this_subcl = cluster2subclusters(cl(i), class); + this_subcl = add_fields(N, this_subcl, cl, i); + end + + % keep track of which larger cluster this came from; for cluster_table + for j = 1:length(this_subcl) + this_subcl(j).from_cluster = i; + end + + try + subcl = [subcl this_subcl]; + catch + warning('internal error with subcluster consistency. this should work fine, but fix code...'); + subcl = merge_clusters(subcl, this_subcl); + end + end + + + + function create_subcl() + + if ~isfield(cl(i), 'name'), cl(i).name = []; end + + % N is field names, cl = clusters, i = which cluster + this_subcl = struct('title', cl(i).title, 'threshold', cl(i).threshold, 'M', cl(i).M, 'dim', cl(i).dim, 'voxSize', cl(i).voxSize, ... + 'name', cl(i).name, 'numVox', cl(i).numVox, 'XYZ', cl(i).XYZ, 'XYZmm', cl(i).XYZmm, 'Z', cl(i).Z, ... + 'mm_center', cl(i).mm_center, 'from_cluster', i); + end +end + + +function this_subcl = add_fields(N, this_subcl, cl, i) + for j = 1:length(N) + for k = 1:length(this_subcl) + if ~isfield(this_subcl(k), N{j}) || isempty(this_subcl(k).(N{j})) + this_subcl(k).(N{j}) = cl(i).(N{j}); + end + end + end +end \ No newline at end of file diff --git a/Cluster_contig_region_tools/xyz2clusters.m b/Cluster_contig_region_tools/xyz2clusters.m new file mode 100755 index 00000000..6c8dcd56 --- /dev/null +++ b/Cluster_contig_region_tools/xyz2clusters.m @@ -0,0 +1,74 @@ +function clusters = xyz2clusters(xyz,P) +% function cl = xyz2clusters(xyz,P) +% +% converts a 3-column x, y, z list of mm coordinates to a clusters +% structure +% given P, the filename of an analyze .img file to provide dimensions and +% voxel sizes. +% +% Uses this info from the image: +% VOL.M % spm-style mat matrix +% VOL.VOX % voxel sizes +% +% SPM.Z % now 1s; could stores values in the original image in clusters.Z +% +% the following is created internally: +% SPM.XYZmm % mm coords, you input these +% SPM.XYZ % voxel coords +% + +%wh = strmatch('Thalamus',L3); whos wh + +V = spm_vol(P); + +VOL.M = V.mat; +VOL.VOX = diag(V.mat(1:3,1:3)); + +fprintf(1,'Converting mm to voxels -> '); +XYZ = mm2voxel(xyz,VOL,2); % unique reordered +fprintf(1,'%3.0f voxels -> ',size(XYZ,1)); +fprintf(1,'Converting voxels to mm -> '); +%XYZ = unique(XYZ,'rows'); +XYZ = XYZ'; + +SPM.XYZmm = voxel2mm(XYZ,VOL.M); +SPM.XYZ = XYZ; +SPM.Z = ones(1,size(XYZ,2)); + +fprintf(1,'Making clusters.\n'); + +%cl = tor_extract_rois([],SPM,VOL); + +% stuff from tor_extract_rois -- omit where it gets each region separately + + % ---------------------------------------------------------------------------------- + % define each cluster as cell in array. + % ---------------------------------------------------------------------------------- + clusters = []; +cl_index = spm_clusters(SPM.XYZ); +%cl_index = SPM.Z; %:size(SPM.XYZ,2); +cl.title = 'xyz'; + + for i = 1:max(cl_index) + % if verbose, fprintf(1,['\nExtracting cluster ' num2str(i) ' of ' num2str(max(cl_index))]),end + a = find(cl_index == i); + + %if isfield(SPM,'u'), cl.title = SPM.title;,else, cl.title = 'Untitled';,end + %if isfield(SPM,'u'), cl.threshold = SPM.u;,else, cl.threshold = NaN;,end + cl(i).voxSize = VOL.VOX; + cl(i).M = VOL.M; + cl(i).name = [cl.title '_' num2str(i) '_' mat2str(size(a,2)) '_voxels']; + cl(i).numVox = size(a,2); + cl(i).Z = SPM.Z(a); + cl(i).XYZmm = SPM.XYZmm(:,a); + cl(i).XYZ = SPM.XYZ(:,a); + + cl(i).center = mean(cl(i).XYZ'); + if size(cl(i).XYZmm,2) > 1, cl(i).mm_center = center_of_mass(cl(i).XYZmm,cl(i).Z); % mean(cl.XYZmm'); + else cl(i).mm_center = cl(i).XYZmm'; + end + clusters = [clusters, cl]; + + end + +return \ No newline at end of file diff --git a/Data_extraction/Grandfathered/check_timeseries_vals.m b/Data_extraction/Grandfathered/check_timeseries_vals.m new file mode 100755 index 00000000..b8b6a2c4 --- /dev/null +++ b/Data_extraction/Grandfathered/check_timeseries_vals.m @@ -0,0 +1,46 @@ +function chk = check_timeseries_vals(V,dat,coords) +% function chk = check_timeseries_vals(V,dat,coords) +% +% Checks a random subset of up to 5 images against extracted data values +% to make sure the right data is being extracted. +% +% V is memory-mapped volumes (see spm_vol) +% dat is extracted data +% coords is voxel coordinates, n rows x 3 columns +% +% tor wager + +n = min(5,length(V)); + +% get random set of n images, up to 5 +wh = randperm(length(V)); +wh = wh(1:n); + +% select these rows in V and dat +% dat is [images, coordinates] +V = V(wh); +dat = dat(wh,:); + +% get random set of nn coordinates, up to 5 + +nc = size(coords,1); +nn = min(5,nc); +whc = randperm(nc); +whc = wh(1:nn); +coords = coords(whc,:); + +% select these columns in dat +dat = dat(:,whc); + +v = spm_read_vols(V); +for i = 1:nn % for each coordinate + dat2(:,i) = squeeze(v(coords(i,1),coords(i,2),coords(i,3),:)); +end + +chk = dat - dat2; +chk = any(chk(:)); + +if chk, warning('Problem with timeseries!! Extracted data do not match expected values.');,end + +return + \ No newline at end of file diff --git a/Data_extraction/Grandfathered/timeseries4.m b/Data_extraction/Grandfathered/timeseries4.m new file mode 100755 index 00000000..3b6b2834 --- /dev/null +++ b/Data_extraction/Grandfathered/timeseries4.m @@ -0,0 +1,300 @@ +function [ts,vols,chunksize] = timeseries4(coords,P,varargin) +% function [ts,vols,chunksize] = timeseries4(coords,P,[chunksize],[nochk]) +% +% Simple extraction from images named in str mtx P or vols +% from voxel coordinates (not mm!) listed in coords +% +% P can be filenames in str matrix (char array) +% or 4-D array of all volume info (vols) +% (i.e., put vols in output back in as P) +% +% ts is timeseries, with fields avg and indiv for average cluster +% and individual voxels +% +% Loads images 'chunksize' at a time; default is based on memory +% size of 2^29 bytes; empty uses default +% Optional 4th argument suppresses data validity checking +% +% vols is 4-D array of all data, [x y z time(image)] +% +% Uses spm_vol and spm_read_vols and spm_slice_vol +% +% Tor Wager, 2/9/05 change from timeseries3: uses slice-by-slice +% method, faster. +% Tor Wager, 12/11/05 speedup for getdata with large n. voxels; cosmetic +% changes to output of volume method. + +% ------------------------------------------------------------------- +% * set up input arguments +% ------------------------------------------------------------------- +maxmem = 2^28; % note:tested the G5s up to 200 imgs, no slowdown, so no need to chunk... +chunksize = []; + +global defaults +if isempty(defaults), spm_defaults, end + +if ischar(P), + fprintf(1,'Map vols: '); t1= clock; + V = spm_vol(P); + nimages = length(V); % number of images in data + fprintf(1,'%3.0f s.\n', etime(clock,t1)); + +elseif ismatrix(P), + chunksize = NaN; + nimages = size(P,4); % number of images in data +else + error('P input must be string or data matrix.'); +end + +if length(varargin) > 0, + chunksize = varargin{1}; +end + + +if size(coords,2) ~= 3, coords = coords';, end + + +% ------------------------------------------------------------------- +% * get extraction method +% ------------------------------------------------------------------- +whslices = unique(coords(:,3)); % which slices to extract +nslices = length(whslices); % number of z slices to extract from + +extype = 'slice'; +if ~isstr(P),extype = 'volume';,end +if nslices > 15, extype = 'volume';,end + +% ------------------------------------------------------------------- +% * read images +% ------------------------------------------------------------------- + +switch extype + +% * slice loading method +% ------------------------------------------------------------------- +case 'slice' + + ts.indiv = NaN .* zeros(nimages,size(coords,1)); % placeholder for data extracted + + for i = 1:nslices + + sliceno = whslices(i); % slice number + + + whcoords = find(coords(:,3) == sliceno); % indices of in-slice coordinates + slcoords = coords(whcoords,:); % coordinates to extract data from for this slice + + sl = timeseries_extract_slice(V,sliceno); + + for c = 1:size(slcoords,1) + + ts.indiv(:,whcoords(c)) = sl(slcoords(c,1),slcoords(c,2),:); + + end + + ts.avg = nanmean(ts.indiv')'; + vols = sl; + +end + + +% * whole-brain loading method +% ------------------------------------------------------------------- +case 'volume' + + +if isempty(chunksize) + + v = spm_read_vols(V(1)); + + tmp = whos('v'); imgsize = tmp.bytes; + chunksize = floor(maxmem ./ imgsize); %round(maxmem ./ (size(coords,1)^3 .* 16)); % images to load at a time + +end + +if ischar(P) + + if length(V) < chunksize + fprintf(1,'\tChunking %3.0f into %3.0f imgs: ',length(V),chunksize); + t1 = clock; + fprintf(1,'Load: '); + vols = spm_read_vols(V); + fprintf(1,'%3.0f s. Cluster. ', etime(clock,t1)); + t1 = clock; + [ts.indiv,ts.avg] = getdata(vols,coords,maxmem); + fprintf(1,'%3.0f s. ', etime(clock,t1)); + + else + % chunk it! load chunksize images as a whole, extract, and + % concatenate + ts.indiv = []; ts.avg = []; + ind = 1; + for i = 1:chunksize:length(V) + t1 = clock; + fprintf(1,'Load: %3.0f ',i); + e = min(i+chunksize-1,length(V)); + wh = i:e; + vols = spm_read_vols(V(wh)); + fprintf(1,'%3.0f s. Cluster. ', etime(clock,t1)); + t1 = clock; + [indiv{ind},avg{ind}] = getdata(vols,coords,maxmem); + ind = ind + 1; + fprintf(1,'%3.0f s. ', etime(clock,t1)); + end + fprintf(1,'Cat. ') + ts.indiv = cat(1,indiv{:}); + ts.avg = cat(1,avg{:}); + clear indiv; clear avg; + end + +else + % volumes already loaded + vols = P; P = 1; + [ts.indiv,ts.avg] = getdata(vols,coords,maxmem); +end + + +end % end switch extraction type + + +% ------------------------------------------------------------------- +% * check for proper extraction against spm_read_vols +% ------------------------------------------------------------------- + +if length(varargin) > 1 | (~ischar(P)), + % already loaded or suppress checking. +else + fprintf(1,'Chk.\n') + chk = check_timeseries_vals(V,ts.indiv,coords); + if chk, keyboard, end +end + +return + + + + + + + + + + +function [ind,avg] = getdata(vols,coords,maxmem) + + if size(coords,1) == 1 % only one voxel + co = 1; + ind(:,co) = squeeze(vols(coords(co,1),coords(co,2),coords(co,3),:)); + + + elseif size(coords,1)^3 < inf % always do this. + + % time increases linearly with size of matrix; so do it in chunks. + csz = round(sqrt(size(coords,1))); % optimal chunk size to keep arrays as small as possible. + indx = 1; + for i = 1:csz:size(coords,1) + tmp = []; + for co = i:min(i+csz-1,size(coords,1)) + %t1 = clock; + tmp = [tmp squeeze(vols(coords(co,1),coords(co,2),coords(co,3),:))]; + %et(co) = etime(clock,t1); + end + ind{indx} = tmp; + indx = indx + 1; + %ind = [ind squeeze(vols(coords(co,1),coords(co,2),coords(co,3),:))]; + + end + ind = cat(2,ind{:}); + + + + else % not a good idea speed-wise, apparently. + tmp = vols(coords(:,1),coords(:,2),coords(:,3),:); % the values of interest are on the 3-D diagonals of tmp + s = size(coords,1); % we can get the index values for diagonals by skipping elements of sv, below + i = 1:s.^2 + s + 1:s^3; % same as t1 = [1:size(coords,1)]'; i = sub2ind(size(tmp),t1,t1,t1) + + sv = (1:s^3:prod(size(tmp))) - 1; % starting values for each volume (minus one, so we add this to i) + sv = repmat(sv,s,1) + repmat(i',1,size(sv,2)); % get the matrix of index values for each voxel at each time + ind = tmp(sv)'; + end + + if size(ind,2) > 1, + avg = nanmean(ind')'; + else + avg = ind; + end + +return + + + + + function sl = timeseries_extract_slice(V,sliceno); + % function sl = timeseries_extract_slice(V,sliceno) + % + % For a given set of image names or memory mapped volumes (V) + % extracts data from slice # sliceno and returns an X x Y x time + % matrix of data. + % uses spm_slice_vol.m + + if isstr(V), V = spm_vol(V);,end + + mat = spm_matrix([0 0 sliceno]); % matrix for spm_slice_vol + + for i = 1:length(V) + sl(:,:,i) = spm_slice_vol(V(i),mat,V(i).dim(1:2),0); + end + + return + + + + +function chk = check_timeseries_vals(V,dat,coords) +% function chk = check_timeseries_vals(V,dat,coords) +% +% Checks a random subset of up to 5 images against extracted data values +% to make sure the right data is being extracted. +% +% V is memory-mapped volumes (see spm_vol) +% dat is extracted data +% coords is voxel coordinates, n rows x 3 columns +% +% tor wager + +n = min(5,length(V)); + +% get random set of n images, up to 5 +wh = randperm(length(V)); +wh = wh(1:n); + +% select these rows in V and dat +% dat is [images, coordinates] +V = V(wh); +dat = dat(wh,:); + +% get random set of nn coordinates, up to 5 + +nc = size(coords,1); +nn = min(5,nc); +whc = randperm(nc); +whc = whc(1:nn); +coords = coords(whc,:); + +% select these columns in dat +dat = dat(:,whc); + +v = spm_read_vols(V); +for i = 1:nn % for each coordinate + dat2(:,i) = squeeze(v(coords(i,1),coords(i,2),coords(i,3),:)); +end + +chk = dat - dat2; +chk = any(chk(:)); + +if chk, warning('Problem with timeseries3!! Extracted data do not match expected values. Quitting at error');,end + +return + + + diff --git a/Data_extraction/canlab_maskstats.m b/Data_extraction/canlab_maskstats.m new file mode 100644 index 00000000..576cbd2e --- /dev/null +++ b/Data_extraction/canlab_maskstats.m @@ -0,0 +1,341 @@ +function MASKSTATS = canlab_maskstats(msks,imgs,varargin) +% MASKSTATS = canlab_maskstats(maskfiles,imgfiles,[options]) +% +% DESCRIPTION +% Produces comparison of pattern mask and images +% e.g., look at NPS pattern expression in set of beta images +% +% INPUT +% maskfiles - string or cellstring of mask filenames or fmri_data object +% imgfiles - string or cellstring of image filenames or fmri_data object +% +% OPTIONS +% 'ts' +% timeseries treatment: each string in imgfiles is assumed to be +% a 4D file. Data will be returned with one column per time series and +% one volume per row. If not all timeseries are same length, all will +% be NaN-padded to the length of the longest timeseries. +% Note: does not work with imgfiles input as fmri_data object +% 'dir2cell' +% will sort stats into cells based on directory containing the imgfile +% they belong to such that each cell contains one directory's worth of +% stats which is a vector with a value for each imgfile. +% EX: Input: a list of single trial betas for a set of subjects +% b = filenames('sub*/*heat_trials*.img'); +% ms = canlab_maskstats('nps',b,'dot_product','dir2cell'); +% Output: includes the set of cells that are input to a mediation +% analysis. +% 'keepzeros' +% don't remove zeros from imgfiles before taking measurements +% 'keepzerosmask' +% don't remove zeros from maskfiles before taking measurements +% 'single' +% leave data as single (DEFAULT: convert to double) +% 'trinarize' +% trinarize maskfile (set values larger than 0 to 1 and values less than +% zero to -1) +% 'noreshape' +% don't attempt to reshape results according to imgfiles array +% 'nobin' +% don't binarize mask before extracting mean or std +% +% Note: ts, dir2cell, and noreshape are mutually exclusive options +% +% BUILT-IN MASKS +% The following strings can be given as the maskfile argument to +% call up built-in mask files: +% 'nps' weights_NSF_grouppred_cvpcr.img +% 'nps_thresh' weights_NSF_grouppred_cvpcr_FDR05.img +% 'nps_thresh_smooth' weights_NSF_grouppred_cvpcr_FDR05_smoothed_fwhm05.img +% +% MEASURE OPTIONS +% 'all' +% add: mean, dot_product, centered_dot_product, +% cosine_similarity, and correlation +% 'mean' (DEFAULT) +% apply binarized mask to images and return means +% mean(img .* abs(bin(mask))) +% 'std' +% apply binarized mask to images and return standard deviations +% std(img .* abs(bin(mask))) +% 'dot_product' +% dot(mask, img) +% 'cosine_similarity' +% dot(mask, img) / (norm(mask) * norm(img)) +% 'correlation' +% corr(mask, img) +% 'centered_dot_product' +% dot(mask-mean(mask), img-mean(img)) +% + +% DETAILS +% - imgfiles are spatially resampled to maskfiles +% - voxels with zeros in maskfile are removed +% - in-mask voxels with zeros in imgfiles will generate warnings + +% AUTHOR: Luka Ruzic 2013 + + +%% parse arguments +OP = {}; % struct to contain desired "operations" (e.g., dot product, mean, etc) +ALLOPS = false; % adds all implemented operations +DO_TRINARIZE = false; % signed binarize option +DO_ZERO2NAN_DATA = true; % treat zeros as non-data in data input +DO_ZERO2NAN_MASK = true; % treat zeros as non-data in mask input +DO_RESHAPE = true; % attempt to reshape output data according to shape of input data +DO_BIN = true; % binarize mask +DO_TS = false; % timeseries mode +DO_CELLS = false; % cell array output mode + +i=1; +while i<=numel(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'binmean' 'mean' 'norm' ... + 'dot_product' 'centered_dot_product' 'cosine_similarity' 'correlation'} + OP{end+1} = varargin{i}; %#ok + case 'all' + ALLOPS = true; + case 'keepzeros' + DO_ZERO2NAN_DATA = false; + case 'keepzerosmask' + DO_ZERO2NAN_MASK = false; + case 'trinarize' + DO_TRINARIZE = true; + case 'nobin' + DO_BIN = false; + case 'noreshape' + DO_RESHAPE = false; + DO_CELLS = false; + DO_TS = false; + case 'ts' + DO_RESHAPE = true; + DO_TS = true; + DO_CELLS = false; + case 'dir2cell' + DO_RESHAPE = true; + DO_CELLS = true; + DO_TS = false; + otherwise + error('Unrecognized argument %s',varargin{i}) + end + elseif iscellstr(varargin{i}) + for j=1:numel(varargin{i}) + switch varargin{i}{j} + case {'binmean' 'mean' 'std' 'nonbinmean' 'nonbinstd' 'norm' ... + 'dot_product' 'centered_dot_product' 'cosine_similarity' 'correlation'} + OP{end+1} = varargin{i}{j}; %#ok + case 'all' + ALLOPS = true; + otherwise + error('Unrecognized argument %s',varargin{i}{j}) + end + end + else + disp(varargin{i}) + error('Above argument unrecognized') + end + i=i+1; +end + +if ALLOPS, OP = [OP 'mean' 'std' 'dot_product' 'centered_dot_product' 'cosine_similarity' 'correlation']; end + +if isempty(OP), OP = {'mean'}; end + + +%% error checking +if isempty(msks), error('Must provide one or more maskfiles'); end +if ischar(msks), msks = cellstr(msks); end +if ~iscellstr(msks) && ~isa(msks,'fmri_data'), error('maskfiles must be a string, cell array of strings, or fmri_data object');end + +if isempty(imgs), error('Must provide one or more imgfiles'); end +if ischar(imgs), imgs = cellstr(imgs); end +if ~iscellstr(imgs) && ~isa(imgs,'fmri_data'), error('imgfiles must be string, cell array of strings, or fmri_data object'); end + +MASKSTATS(numel(msks)) = struct; % initializing + + +%% prepare mask files +if ~isa(msks,'fmri_data') + for m = 1:numel(msks) + switch msks{m} + case 'nps' + msks{m} = which('weights_NSF_grouppred_cvpcr.img'); + case 'nps_thresh' + msks{m} = which('weights_NSF_grouppred_cvpcr_FDR05.img'); + case 'nps_thresh_smooth' + msks{m} = which('weights_NSF_grouppred_cvpcr_FDR05_smoothed_fwhm05.img'); + otherwise + if ~exist(msks{m},'file') + error('No such file: %s',msks{m}) + end + end + end +end + + +%% prepare data +% fprintf('LOADING IMAGE FILES\n') +if isa(imgs,'fmri_data') + imgdata = imgs; + imgdata = imgdata.replace_empty; + if DO_TS + warning('''ts'' mode is incompatible with fmri_data input: turning off'); %#ok + DO_TS = false; + end + imgfiles = cellstr(imgdata.fullpath); +else + imgfiles = imgs; + evalc('imgdata = fmri_data(imgfiles);'); +end + +imgdata.dat = double(imgdata.dat); % convert singles to doubles +if DO_ZERO2NAN_DATA, imgdata.dat(imgdata.dat==0) = NaN; end % remove zeros if desired + +if DO_TS + for i = 1:numel(imgfiles) + ni = nifti(imgfiles{i}); + tsdim(i) = ni.dat.dim(4); %#ok + end +end + +if DO_CELLS +% [ignore1 ignore2 x] = unique(regexprep(imgfiles,'/[^/]*$','')); %#ok + [ignore1 ignore2 x] = unique(regexprep(cellstr(imgdata.fullpath),'/[^/]*$','')); %#ok + reshaperows = histc(x,1:max(x)); +end + + +%% produce comparison +if isa(msks,'fmri_data') + allmaskdata = msks; + allmaskdata = allmaskdata.replace_empty; + nmasks = size(allmaskdata.dat,2); +else + nmasks = numel(msks); +end +for m = 1:nmasks + clear imgdat maskdat imgdatcent maskdatcent imgdatnorm maskdatnorm + + % save filenames + % imgs (modify cell array according to mode) + if DO_CELLS + for i = 1:numel(reshaperows) + st = sum(reshaperows(1:i-1))+1; + MASKSTATS(m).imgfiles{i} = imgfiles(st:st+reshaperows(i)-1); + end + elseif DO_TS + MASKSTATS(m).imgfiles = imgfiles'; + else + MASKSTATS(m).imgfiles = imgfiles; + end + + % load and prep mask data + + if isa(msks,'fmri_data') + maskdata = allmaskdata; + maskdata.dat(:,[1:size(allmaskdata.dat,2)]~=m) = []; + MASKSTATS(m).maskfile = deblank(allmaskdata.fullpath(m,:)); + else + evalc('maskdata = fmri_data(msks{m});'); + MASKSTATS(m).maskfile = msks{m}; + end + fprintf('GETTING DATA FOR MASK: %s\n',MASKSTATS(m).maskfile); + maskdata.dat = double(maskdata.dat); % convert singles to doubles + %if DO_ZERO2NAN_MASK, maskdata.dat(maskdata.dat == 0) = NaN; end % SHOULD THIS HAPPEN HERE? remove zeros + maskdata = maskdata.resample_space(imgdata); % resample + if DO_ZERO2NAN_MASK, maskdata.dat(maskdata.dat == 0) = NaN; end % remove zeros + + % select in-mask voxels + wh_inmask = ~isnan(maskdata.dat); + maskdat = maskdata.dat(wh_inmask); + imgdat = imgdata.dat(wh_inmask,:); + + % trinarize + if DO_TRINARIZE + maskdat(maskdat<0) = -1; + maskdat(maskdat>0) = 1; + end + + % warn about in-mask zeros + if DO_ZERO2NAN_DATA + inmaskzeros = isnan(imgdat); + if any(inmaskzeros) + fprintf('WARNING! There are zeros in your data within this mask!\n') + %fprintf('Consider carefully whether this impairs your ability to compare data points.\n') + %fprintf(' These are being ignored as non-data but your results will be impacted!\n') + for i = find(sum(inmaskzeros)) + fprintf(' %7d in-mask zeros in %s\n',sum(inmaskzeros(:,i)),deblank(imgdata.fullpath(i,:))); + end + end + end + + % perform operation(s) + for i = 1:numel(OP) + switch OP{i} + case 'mean' + if DO_BIN + MASKSTATS(m).stats.mean = nanmean(imgdat)'; + else + MASKSTATS(m).stats.nonbinmean = nanmean(bsxfun(@times,imgdat,maskdat))'; + end + + case 'std' + if DO_BIN + MASKSTATS(m).stats.std = nanstd(imgdat)'; + else + MASKSTATS(m).stats.nonbinstd = nanstd(bsxfun(@times,imgdat,maskdat))'; + end + + case 'norm' + MASKSTATS(m).stats.norm = (nansum(imgdat .^ 2) .^ .5)'; + + case 'dot_product' + MASKSTATS(m).stats.dot_product = nansum(bsxfun(@times,imgdat,maskdat))'; + + case 'centered_dot_product' + imgdatcent = bsxfun(@minus,imgdat,nanmean(imgdat)); + maskdatcent = bsxfun(@minus,maskdat,nanmean(maskdat)); + MASKSTATS(m).stats.centered_dot_product = nansum(bsxfun(@times,imgdatcent,maskdatcent))'; + + case 'cosine_similarity' + imgdatnorm = nansum(imgdat .^ 2) .^ .5; + maskdatnorm = nansum(maskdat .^ 2) .^ .5; + MASKSTATS(m).stats.cosine_similarity = (nansum(bsxfun(@times,imgdat,maskdat)) ./ (imgdatnorm .* maskdatnorm))'; + + case 'correlation' + MASKSTATS(m).stats.correlation = corr(imgdat,maskdat,'rows','pairwise'); + + otherwise + error('Comparison type (%s) unrecognized',OP{i}) + end + + % reshape according to mode + if DO_RESHAPE + if DO_TS + if numel(unique(tsdim)) == 1 + tslen = tsdim(1); + else + % not all timeseries are same length + % assume all start at same time and pad with NaNs + tslen = max(tsdim); + for j = 1:numel(tsdim) + cutind = ((j-1)*tslen) + tsdim(j); + MASKSTATS(m).stats.(OP{i}) = [... + MASKSTATS(m).stats.(OP{i})(1:cutind); ... + nan(tslen-tsdim(j),1); ... + MASKSTATS(m).stats.(OP{i})(cutind+1:end)]; + end + % alternative coding option: vector -> cells -> NaN-pad the cells -> cell2mat % + end + MASKSTATS(m).stats.(OP{i}) = reshape(MASKSTATS(m).stats.(OP{i}),[tslen numel(imgfiles)]); + elseif DO_CELLS + MASKSTATS(m).stats.(OP{i}) = mat2cell(MASKSTATS(m).stats.(OP{i}),reshaperows,1)'; + else + try MASKSTATS(m).stats.(OP{i}) = reshape(MASKSTATS(m).stats.(OP{i}),size(imgfiles)); end %#ok + end + end + end +end + +end diff --git a/Data_extraction/cluster_tmask.m b/Data_extraction/cluster_tmask.m new file mode 100755 index 00000000..ac994553 --- /dev/null +++ b/Data_extraction/cluster_tmask.m @@ -0,0 +1,221 @@ +function [cl, varargout] = cluster_tmask(cl, tm, si, varargin) +% [cl, varargout] = cluster_tmask(cl, tm, si, varargin) +% +% Given clusters and a string name of a t-image, finds voxels that exceed a +% specified t-threshold +% +% cl = clusters +% tm = t-image +% si = subject index integer +% [dat] = cluster_barplot data structure +% +% creates new XYZ in the space of t-image using cl.XYZmm coordinates in mm. +% Required fields of cl: XYZmm +% +% Calculates and saves single-subject data avgd over voxels if: + % A) cl.all_data field is present + % THIS WORKS if all_data has individual subject contrast estimates in + % it, with rows as subjects and columns as voxels + % indiv data saved in cl(region).timeseries(subject) + % + % B) cl.raw_data is present + % raw_data should be time x voxels x subjects, a 3D matrix + % see output of extract_raw_data. + % indiv data saved in cl(region).indiv_timeseries(:, subject) +% +% NOTE: Retains upper 50% of voxels; highest t-values + +% get thresholds + +%tt = [3 2]; % t-thresholds in absolute values, use with read_t + +tt = [50 100]; % % of voxeis to save, use with read_t2; 1st number is typically used, 2nd for very small regions. + +reverse_vals = 0; +if length(varargin) > 1, reverse_vals = varargin{2}; end + +V = spm_vol(tm); + +for i = 1:length(cl) + % save mask name + % make sure it appends it in the right place + if ~isfield(cl(i), 'INDIV'), cl(i).INDIV = []; end + if ~isfield(cl(i).INDIV, 'tname'), + cl(i).INDIV.tname = tm; + else + cl(i).INDIV.tname = str2mat(cl(i).INDIV.tname, tm); + end + + %cl(i).INDIV.tname = cl(i).INDIV.tname(1:end-1, :); + %cl(i).INDIV.tname(si, :) = tm; +end + + +for i = 1:length(cl) + + % get voxel coordinates for clusters + + XYZ = mm2voxel(cl(i).XYZmm, struct('M', V.mat), 1)'; + + % get t-values + % save values of t in tm, so that we pass in values next time to save + % time + [t, cl(i).INDIV.XYZ{si}, cl(i).INDIV.XYZmm{si}, cl(i).INDIV.sigt(si, :), cl(i).INDIV.maxt(si), tm, ... + cl(i).INDIV.center(si, :), cl(i).INDIV.mm_center(si, :)] = ... + read_t2(tm, V, XYZ, tt, reverse_vals); + + % Calculates and saves single-subject data avgd over voxels if: + % A) cl.all_data field is present + % THIS WORKS if all_data has individual subject contrast estimates in + % it, with rows as subjects and columns as voxels + % indiv data saved in cl(region).timeseries(subject) + % + % B) cl.raw_data is present + % raw_data should be time x voxels x subjects, a 3D matrix + % see output of extract_raw_data. + % indiv data saved in cl(region).indiv_timeseries(:, subject) + + if isfield(cl, 'all_data') + try + sigt = cl(i).INDIV.sigt(si, :); + if any(sigt) + cl(i).timeseries(si) = mean(cl(i).all_data(si, find(sigt))); + else + cl(i).timeseries(si) = NaN; + end + catch + % if the data in all_data is in the wrong format (e.g., + % timeseries data time x voxels) we may get an error; just move + % on. + end + end + + if isnan(cl(i).timeseries(si)), disp('NaNs when they shouldn''t be there!'), keyboard, end + + if isfield(cl, 'raw_data') + sigt = cl(i).INDIV.sigt(si, :); + if any(sigt) + tmp = squeeze(cl(i).raw_data(:, :, si)); + cl(i).indiv_timeseries(:, si) = mean(tmp(:, find(sigt))')'; + else + cl(i).indiv_timeseries(:, si) = repmat(NaN, size(cl(i).raw_data, 1), 1); + end + end + + + % average selected columns of data matrix entered as variable input + % dat is cell array, each cell is a matrix of subjects x voxels + % argument; compatible with cluster_barplot + + if length(varargin) > 0 + dat = varargin{1}; + sigt = cl(i).INDIV.sigt(si, :); + + if ~iscell(varargin), error('optional dat argument must be cell array, one cell per cluster within a cell per image list'), end + + if any(sigt) + datout(i) = mean(dat{i}(si, find(sigt))); + else + datout = NaN .* zeros(size(dat)); + end + + varargout{1} = datout; + end + +end + + +return + + + + +% based on t-value threshold +function [t, XYZ, XYZmm, ts, maxt, v, com, com_mm] = read_t(tm, V, XYZ, tt) + +if isstr(tm) + v = spm_read_vols(V); + v = v(:); +else + v = tm; +end + +ind = sub2ind(V.dim(1:3), XYZ(1, :)', XYZ(2, :)', XYZ(3, :)'); + +t = v(ind)'; + +ts = t > tt(1); + +maxt = max(t(t~=0)); + +% lower threshold, if necessary +if length(tt) > 1 && sum(ts) < 5 % 5 voxel cutoff + ts = t > tt(2); +end + +% save stats +if any(ts) + XYZ = XYZ(:, find(ts)); + XYZmm = voxel2mm(XYZ, V.mat); + com = center_of_mass(XYZ, t(find(ts))); + com_mm = center_of_mass(XYZmm, t(find(ts))); + %com = mean(XYZ, 2)'; + %com_mm = mean(XYZmm, 2)'; +else + XYZ = []; + XYZmm = []; + com = [NaN NaN NaN]; + com_mm = com; +end + +return + + +% get voxels based on percentage (tt) of region to save +function [t, XYZ, XYZmm, ts, maxt, v, com, com_mm] = read_t2(tm, V, XYZ, tt, reverse_vals) + + +if isstr(tm) + v = spm_read_vols(V); + v = v(:); +else + v = tm; +end + +ind = sub2ind(V.dim(1:3), XYZ(1, :)', XYZ(2, :)', XYZ(3, :)'); + +t = v(ind)'; + +if reverse_vals, t = -t; end + +% NaN out exactly zero voxels +t(t == 0) = NaN; + +% translate percentages into t-thresholds +tt = prctile(t, 100-tt); + +ts = t > tt(1); + +maxt = max(t(t~=0)); + +% lower threshold, if necessary, for smaller regions +if length(tt) > 1 && sum(ts) < 5 % 5 voxel cutoff + ts = t > tt(2); +end + +% save stats +if any(ts) + XYZ = XYZ(:, find(ts)); + XYZmm = voxel2mm(XYZ, V.mat); + com = center_of_mass(XYZ, t(find(ts))); + com_mm = center_of_mass(XYZmm, t(find(ts))); + %com = mean(XYZ, 2)'; + %com_mm = mean(XYZmm, 2)'; +else + XYZ = []; + XYZmm = []; + com = [NaN NaN NaN]; + com_mm = com; +end + +return \ No newline at end of file diff --git a/Data_extraction/extract_contrast_data.m b/Data_extraction/extract_contrast_data.m new file mode 100644 index 00000000..d2736bb8 --- /dev/null +++ b/Data_extraction/extract_contrast_data.m @@ -0,0 +1,510 @@ +function [clusters, subcl] = extract_contrast_data(P, clusters, varargin) +% function [clusters, subcl] = extract_contrast_data(P, clusters) +% +% this function does not plot, but separates subclusters using pca / cluster_princomp.m +% based on pattern across all conditions, covariance (not correlation), +% +% Inputs: +% 1 - cell array of strings containing image file names (data extracted from these) +% 2 - clusters +% [opt] - to get sub-clustering based on pca and clustering of voxels +% add the string 'subclusters' as an input argument +% +% [opt] - cell array of strings with condition names +% +% [opt] - 'split' value is 1: to split into 2 plots (first half and last half of P) +% value is 2: to plot individual subjects over bars +% +% [opt] - 'center' center parmeter values in plot (subtract row means) +% this gives closer to correct "within subjects" error bars +% and may be used when the overall parameter values have no meaning +% +% [opt] - 'covs', followed by between-subject covariates (e.g., behavioral regressors) +% plots remove these before plotting means and std. errors +% +% [opt] - 'max', to make plots based on max z-values within region for each dataset P +% not compatible with 'split' and 'center' (ignores these commands) +% right now, special for inhib - see also inhib2_cluster_barplot (good function) +% +% [opt] - 'indiv' to threshold based on individual t-statistics or contrast +% values +% FOLLOW with cell array of t-images or con images -- usually, there will be one cell, with images +% for each subject in rows, to define voxels for each ss. +% BUT Tnames can be the same length as +% contrast images, one t-img per subject per contrast, if +% desired. +% +% Outputs: +% clusters struture, with CONTRAST substructure added +% substructure contains data extracted and image file names +% +% This program uses XYZmm millimeter coordinates in clusters to find voxels +% So clusters and data files may have different dimensions. +% +% Examples: +% cl = extract_contrast_data(EXPT.SNPM.P, cl, 'indiv', EXPT.FILES.Timgs{1}); +% cluster_barplot(EXPT.SNPM.P(7:12), clusters(2:3), {'ObjE' 'AttE' 'InteractE' 'ObjI' 'AttI' 'InteractI'}, 'split') +% [clusters, subclusters] = cluster_barplot(EXPT.SNPM.P(17:24), clusters, 'subclusters', 'split') +% RS2_8vs2_placeboCP = cluster_barplot(EXPT.SNPM.P([8 10 12 14 +% 16]), RS2meta, 'indiv', T); +% +% also see mask2clusters.m, a simpler version that just extracts clusters from a mask file. +% +% by Tor Wager, 5/15/03 +% modified 3/19/04 by Tor to add individual subject plots + +diary off + +% ------------------------------------------------- +% * Set up optional inputs +% ------------------------------------------------- +splitat=0; dosubcl = 0; docenter = 0; covs = []; domax = 0; doindiv = 0; +for j = 1:length(P), cnames{j}=num2str(j); end + +for i = 1:length(varargin), + if iscell(varargin{i}), cnames = varargin{i}; %varargin{i} = []; + elseif strcmp('split', varargin{i}), if varargin{i+1}==2, splitat=2; else splitat=round(length(P)./2); end + elseif strcmp('subclusters', varargin{i}), dosubcl = 1; disp('Finding sub-clusters') + elseif strcmp('center', varargin{i}), docenter = 1; + elseif strcmp('covs', varargin{i}), covs = varargin{i+1}; + elseif strcmp('max', varargin{i}), domax = 1; + elseif strcmp('indiv', varargin{i}), doindiv = 1; + Tnames = varargin{i+1}; varargin{i+1} = 'saved.'; + end +end + +% convert to old format if necessary +if isa(clusters, 'region') + clusters = region2struct(clusters); +end + +if doindiv + % replicate names, in case only one set is entered + if ~iscell(Tnames), + Tnamestmp = Tnames; Tnames={};Tnames{1}=Tnamestmp; + end + if length(Tnames) < length(cnames), + disp('Found only one set of T-images; replicating for each set of data extracted.'); + end + while length(Tnames) < length(cnames), Tnames{end+1} = Tnames{1}; end +end + +%if ~isempty(strmatch('split', varargin)), splitat=round(length(P)./2); +%else splitat=length(P); end +subcl = []; +if isempty(clusters) + disp('Clusters is empty: No voxels for which to get data.'); + return +end + +% ------------------------------------------------- +% * load image files +% ------------------------------------------------- +fprintf(1, 'Loading images.') +CLU = clusters2CLU(clusters); +dat = cell(1, length(clusters)); +alldat = cell(1, length(clusters)); + + +V = spm_vol(P{1}(1, :)); +if any(any(V.mat - CLU.M)), + disp('CLUSTERS and IMAGE DIMENSIONS DIFFERENT! Resizing clusters.'), + %CLU = transform_coordinates(CLU, V.mat); + V.M = V.mat; + for i = 1:length(clusters), + clusters(i).XYZ = mm2voxel(clusters(i).XYZmm, V)'; clusters(i).M=V.mat; + clusters(i).voxSize = diag(V.M(1:3, 1:3))'; + clusters(i).XYZmm = voxel2mm(clusters(i).XYZ, clusters(i).M); + clusters(i).Z = ones(1, size(clusters(i).XYZ, 2)); + end + CLU = clusters2CLU(clusters); + for i = 1:length(clusters), clusters(i).u=CLU.u;clusters(i).VOX=CLU.VOX; end + doslow = 1; +else + doslow=0; +end + +% check if any contiguous clusters we lose +%tmpCL = tor_extract_rois([], CLU, CLU); +%if length(tmpCL) < length(clusters), doslow = 1; end +%clear tmpCL + +% reslice clusters if necessary. +if doslow + clusters = cluster_interp(clusters, P{1}(1, :), 1); + doslow = 0; +end + +for i = 1:length(P) + + %if ~doslow + % fast way: if all voxel sizes match + cl{i} = tor_extract_rois(P{i}, clusters); %CLU, CLU); + %end + + % concatenated data for each cluster + for j = 1:length(clusters) + + %if doslow + % slow way: to keep clusters separate that otherwise touch + % cl{i}(j) = tor_extract_rois(P{i}, clusters(j), CLU); + %end + + % alldat{cluster} is data x voxels, data is subj within conditions strung + % together + alldat{j} = [alldat{j}; cl{i}(j).all_data]; + + if length(cl{i}(j).timeseries) < size(dat{j}, 1), + cl{i}(j).timeseries = [cl{i}(j).timeseries; NaN*zeros(size(dat{j}, 1)-length(cl{i}(j).timeseries), 1)]; + end + + dat{j} = [dat{j} cl{i}(j).timeseries]; + + + + end + + +end % loop through P + +%V{1}(1).M = V{1}(1).mat; +%imgdims = size(vols{1}); +%mask = zeros(imgdims(1:3)); + + +% ------------------------------------------------- +% * subclusters +% ------------------------------------------------- +for i = 1:length(clusters) + + if dosubcl && clusters(i).numVox > 4, + % ------------------------------------------------- + % * subclusters + % ------------------------------------------------- + + [subcl{i}] = getsubcl(clusters(i), alldat{i}, length(P), size(P{1}, 1), cnames, splitat, i, docenter, covs); % does subclusters within it + if length(subcl{i}) > 1, + subcluster_montage(subcl{i}); % now plot all subcluster locations + saveas(gcf, ['cl' num2str(i) '_subcl_montage'], 'fig') + saveas(gcf, ['cl' num2str(i) '_subcl_montage'], 'tif') + end + else + + % no subclusters + + sterr = nanstd(dat{i}) ./ sqrt(size(dat{i}, 1)); + + if isfield(clusters, 'shorttitle'), mytit = clusters(i).shorttitle; + else mytit = clusters(i).title; + end + + str = sprintf('%s %2.0f (%3.0f, %3.0f, %3.0f), %3.0f voxels', ... + mytit, i, clusters(i).mm_center(1), ... + clusters(i).mm_center(2), clusters(i).mm_center(3), clusters(i).numVox); + + if domax + % set up + len = size(clusters(1).CONTRAST.data, 1); + tmpcl = []; tmpcl2 = []; + for j = 1:round(size(alldat{1}, 1) ./ len) + tmpcl{j}(i) = clusters(i); tmpcl{j}(i).all_data = alldat{i}(1+(j-1)*len:j*len, :); + tmpcl{j}(i).timeseries = mean(tmpcl{j}(i).all_data, 2); + + [sterr, tmp] = ste(tmpcl{j}(i).all_data); + clusters(i).Z(j, :) = spm_t2z(tmp, size(tmpcl{j}(i).all_data, 1)-1); + + wh = find(tmp == max(tmp)); wh = wh(1); + clusters(i).CONTRAST.grp_peakdat(j, :) = tmpcl{j}(i).all_data(:, wh); + + %tmpcl2{j}(i) = cluster_ttest(tmpcl{j}(i), covs); % add covariate here if necessary + end + % needs checking and/or debugging + %inhib2_barplot(clusters(i), tmpcl2{1}(i), tmpcl2{2}(i), tmpcl2{3}(i), 'common'); + keyboard + end + + + + %f = makefigure(dat{i}, str, cnames, splitat, docenter, covs); + %saveas(gcf, ['cl' num2str(i) '_bar'], 'fig') + %saveas(gcf, ['cl' num2str(i) '_bar'], 'tif') + + + clusters(i).CONTRAST.files = P; + clusters(i).CONTRAST.data = dat{i}; + clusters(i).CONTRAST.all_data = alldat{i}; + + if dosubcl + disp('Fewer than 5 voxels; skipping subclustering.') + + % make all field names match. + % won't work if this is the first one! + subcl{i} = subcl{1}(1); + N = fieldnames(subcl{i}); + for NN = 1:length(N) + eval(['subcl{i}(1).' N{NN} ' = [];']) + end + + N = fieldnames(clusters(i)); + for NN = 1:length(N) + eval(['subcl{i}(1).' N{NN} ' = clusters(i).' N{NN}]) + end + + end + + %close all + + end + + clusters(i).covs = covs; +end + + +fprintf(1, '\n') + +% extract spatial peak data and locations +for i = 1:length(cl) + cltmp = extract_ind_peak([], cl{i}); + for j = 1:length(cltmp) + clusters(j).CONTRAST.peakdata{i} = cltmp(j).peakdata; + clusters(j).CONTRAST.peakXYZ{i} = cltmp(j).peakXYZ; + clusters(j).CONTRAST.peakXYZmm{i} = cltmp(j).peakXYZmm; + end +end + + +% extract individual significant region center of mass +if doindiv + disp(['getting individual significance regions']) + disp(['Found ' num2str(size(Tnames{1}, 1)) ' T-images to get regions from']) + indpeak = []; + + reverse_vals = input('Enter 0 to get most positive voxels for each image, or 1 to get most negative ones: '); + + + for c = 1:size(Tnames, 2) % for each contrast tested + + for i = 1:size(Tnames{1}, 1) % for each subject + + % pass in clusters with all data saved for contrast c, Tnames + % for contrast c; timeseries saves data from this + + [tmp] = cluster_tmask(cl{c}, Tnames{c}(i, :), i, alldat, reverse_vals); + cl{c} = tmp; + + end + + % put stuff where it belongs + for j = 1:length(clusters) + + clusters(j).CONTRAST.indiv_data(:, c) = cl{c}(j).timeseries; + clusters(j).CONTRAST.indivXYZ{c} = cl{c}(j).INDIV.center; + clusters(j).CONTRAST.indivXYZmm{c} = cl{c}(j).INDIV.mm_center; + clusters(j).CONTRAST.indiv_sig{c} = cl{c}(j).INDIV.sigt; + clusters(j).CONTRAST.indivXYZall{c} = cl{c}(j).INDIV.XYZ; + clusters(j).CONTRAST.tname = cl{c}(j).INDIV.tname; + + end + + end + +end + +return + + + + + + +% ----------------------------------------------------------------------------- +% * sub-functions +% ----------------------------------------------------------------------------- + + + function [subclusters] = getsubcl(clusters, adat, numimglists, numimgs, varargin) + % always pass in only one cluster in clusters!! (vec of length 1) + % if > 1 output, subclusters is created and plotted here + % if subclusters, pass in cnames and splitat + + if length(varargin) > 0, cnames = varargin{1}; splitat = varargin{2}; + clnum=varargin{3}; docenter = varargin{4}; + end + if length(varargin) > 4, covs = varargin{5}; else covs = []; end + + % don't scale if this is across subjects, because voxels + % with more variation across ss SHOULD drive the pca + %adat{j} = scale(adat{j}); % scale so high-noise voxels + % % don't drive the pca + + % separate subclusters + subclusters.CONTRAST = []; % add for consistency of field names + clusters.all_data = adat; + + [clusters, subclusters] = cluster_princomp(clusters, []); % , 0, 0, 1); + + subclusters = subclusters{1}; + + % re-format averages within sub-regions for plotting + + for j = 1:length(subclusters) + vind = 1; + if size(subclusters(j).all_data, 2) > 1, + tmp = nanmean(subclusters(j).all_data')'; + else + tmp = subclusters(j).all_data; + end + + for k = 1:numimglists + subclusters(j).CONTRAST.dat(:, vind) = ... + tmp((k-1)*numimgs+1:k*numimgs); + vind = vind + 1; + end + + % now plot it + sterr = nanstd(subclusters(j).CONTRAST.dat) ./ sqrt(size(subclusters(j).CONTRAST.dat, 1)); + str = sprintf('Cl %3.0f (%4.0f vox) subcl at (%3.0f, %3.0f, %3.0f), %3.0f voxels', ... + clnum, clusters.numVox, subclusters(j).mm_center(1), ... + subclusters(j).mm_center(2), subclusters(j).mm_center(3), subclusters(j).numVox); + + f = makefigure(subclusters(j).CONTRAST.dat, str, cnames, splitat, docenter, covs); + saveas(gcf, ['cl' num2str(clnum) '_subc' num2str(j) '_bar'], 'fig') + saveas(gcf, ['cl' num2str(clnum) '_subc' num2str(j) '_bar'], 'tif') + end + + + return + + + + + + + + function f = makefigure(dat, varargin) + + if length(varargin) > 1, cnames=varargin{2}; else cnames = []; end + if length(varargin) > 2, splitat=varargin{3}; else splitat = 0; end + if length(varargin) > 3, docenter=varargin{4}; else docenter = 0; end + if length(varargin) > 4, covs = varargin{5}; covs(:, end+1) = 1; else covs = []; end + if covs == 1, covs = []; end % if empty, make it empty! + + dat1 = dat; % save original data for ind subj plot + + if ~isempty(covs), + covs(:, 1:end-1) = scale(covs(:, 1:end-1)); + for i = 1:size(dat, 2), + b = pinv(covs) * dat(:, i); + r = dat(:, i) - covs * b + b(end); + dat(:, i) = r; + end + end + + if docenter, + disp('Centering rows of data for plotting.') + dat = dat - repmat(nanmean(dat')', 1, size(dat, 2)); + + end + + doprintt = 1; + + if doprintt + % print t values + for i = 1:size(dat, 2) + [h, p, ci, stats] = ttest(dat(:, i)); + nums{i} = sprintf('%3.2f', stats.tstat); + end + end + + sterr = nanstd(dat) ./ sqrt(size(dat, 1)); + dat = nanmean(dat); + mysum = sign(dat) .* (sterr + abs(dat) + .15 .* abs(dat)); + + if splitat > 2, + f = figure('Color', 'w'); + + h(1) = subplot(1, 2, 1); set(gca, 'FontSize', 16); hold on; grid on; + bar(dat(1:splitat)); tor_bar_steplot(dat(1:splitat), sterr(1:splitat), {'b'}); + set(gca, 'XTick', 1:splitat) + xlabel('Conditions', 'FontSize', 18), ylabel('fMRI Signal', 'FontSize', 18) + if length(varargin) > 1, set(gca, 'XTickLabel', varargin{2}(1:splitat)), end + if length(varargin) > 0, title(varargin{1}, 'FontSize', 20), end + + for i = 1:splitat, text(i-.5, mysum(i), nums{i}, 'Color', 'k', 'FontWeight', 'b', 'FontSize', 14); end + + + h(2) = subplot(1, 2, 2); set(gca, 'FontSize', 16); hold on; grid on; + bar(dat(splitat+1:end)); tor_bar_steplot(dat(splitat+1:end), sterr(splitat+1:end), {'b'}); + set(gca, 'XTick', 1:length(dat)-splitat) + xlabel('Conditions', 'FontSize', 18) + if length(varargin) > 1, set(gca, 'XTickLabel', varargin{2}(splitat+1:end)), end + + for i = 1:length(dat)-splitat, text(i, mysum(splitat+i), nums{splitat+i}, 'Color', 'k', 'FontWeight', 'b', 'FontSize', 14); end + + equalize_axes(h); + set(gcf, 'Position', [195 308 1259 674]), drawnow + + elseif splitat == 2 + + % ------------------------ + % this plot does individual subject estimates + % ------------------------ + barplot_columns(dat1) + + + + % add reg lines, if covs and sig + for i = 1:size(dat1, 2) + [B, dev, stat]=glmfit(covs(:, 1), dat1(:, i)); tmp = corrcoef(covs(:, 1), dat1(:, i)); + fprintf(1, 'Cond. %3.0f: Bo: t = %3.2f, se = %3.4f, p = %3.4f B1: r = %3.2f, t = %3.2f, se = %3.4f, p = %3.4f\n', ... + i, stat.t(1), stat.se(1), stat.p(1), tmp(1, 2), stat.t(2), stat.se(2), stat.p(2)); + + end + + if ~isempty(cnames), set(gca, 'XTickLabel', cnames), end + + else + + % ------------------------ + % standard bar plot + % ------------------------ + + f = figure('Color', 'w'); set(gca, 'FontSize', 16); %hold on; grid on; + h = bar(dat); set(h, 'FaceColor', [.7 .7 .7]) + tor_bar_steplot(dat, sterr, {'k'}); + + doinhib2 = 0; + if doinhib2 + % special insert for inhib2 (triple inhibition) + set(gca, 'XTickLabel', {'GNG' 'Flanker' 'SRC' 'Saccade'}) + end + + dointext = 0; + if dointext + % special insert for intext + cla + xp = dat; h = bar([xp(1:3);xp(4:6)], 'grouped'); cm = [0 1 0;1 0 0; 0 0 1];colormap(cm) + xe = sterr; + tor_bar_steplot([xp(1:3)], xe(1:3), {'k'}, .55, .225) + tor_bar_steplot([xp(4:6)], xe(4:6), {'k'}, 1.55, .225) + set(gca, 'FontSize', 16, 'XTickLabel', {'External' 'Internal'});legend(h, {'Object switching' 'Attribute Switching' 'Interaction'}) + ylabel('BOLD Contrast') + end + + %set(gca, 'XTick', 1:size(dat, 2)) + %xlabel('Conditions', 'FontSize', 18), ylabel('fMRI Signal', 'FontSize', 18) + %if length(varargin) > 1, set(gca, 'XTickLabel', varargin{2}), end + if length(varargin) > 0, title(varargin{1}, 'FontSize', 20), end + + set(gcf, 'Position', [464 283 930 827]), drawnow + + + + end + + return + + + + + diff --git a/Data_extraction/extract_from_rois.m b/Data_extraction/extract_from_rois.m new file mode 100644 index 00000000..e077eaf8 --- /dev/null +++ b/Data_extraction/extract_from_rois.m @@ -0,0 +1,172 @@ +function [cl, imgdat] = extract_from_rois(imgs_to_extract_from, mask_image, varargin) + % [cl, imgdat] = extract_from_rois(imgs_to_extract_from, mask_image, varargin) + % + % Generic function for extracting image data from a mask or atlas image, + % and returning the data and averages within regions specified by the user. + % + % Regions to average over can be either regions of contiguous voxels + % bounded by voxels with values of 0 or NaN, which are considered non-data + % values, or regions defined by unique integer codes in the mask image + % (i.e., for atlas images with unique codes for each defined region.) + % + % Mask/Atlas image does NOT have to be in the same space as the images to + % extract from. It will be remapped/resliced. + % + % extracted data is returned in single data format. + % + % Inputs: + % 1 - char array of strings containing 4D image file names (data extracted from these) + % 2 - mask_image to extract from. + % + % Optional inputs: + % 'average_over': + % Default = 'unique_mask_values' to average over unique integer codes in the mask image + % (i.e., for atlas images with unique codes for each defined region) + % OPT = 'contiguous_regions' to average over contiguous voxels + % bounded by voxels of 0 or NaN (non-data values) + % + % + % Example: + % imgs_to_extract_from = filenames('w*.nii','char'); + % mask_image = which('anat_lbpa_thal.img'); + % [cl, imgdat] = extract_from_rois(imgs_to_extract_from, mask_image); + + + if isempty(mask_image) + mask_image = which('anat_lbpa_thal.img'); + end + + average_over = 'unique_mask_values'; %'contiguous_regions' or 'unique_mask_values'; + + for varg = 1:length(varargin) + if ischar(varargin{varg}) + switch varargin{varg} + + % reserved keywords + case 'contiguous_regions', average_over = 'contiguous_regions'; + case 'unique_mask_values', average_over = 'unique_mask_values'; + end + end + end + + + + + space_defining_image = deblank(imgs_to_extract_from(1, :)); + + + % Note: we need to write out the image here only because we need its + % anatomical boundaries (in-mask areas), contig voxels, etc. for later + + + tmpname = num2str(round(10*rand(1, 5))); tmpname(tmpname == ' ') = []; tmpname = ['tmp_mask_' tmpname '.img']; + + + maskData = scn_map_image(mask_image, space_defining_image, 'write', tmpname); + maskData = maskData(:); + + + volInfo = iimg_read_img(tmpname, 2); + maskData = maskData(volInfo.wh_inmask); + + + % This needs to be made Windows-compatible. kludgy now. + eval(['!rm ' tmpname]) + eval(['!rm ' tmpname(1:end-4) '.hdr']) + + + % Now we have the mask data and volInfo structure, and we can extract + + + + + %% + clear imgdat + switch spm('Ver') + + + case {'SPM8', 'SPM5'} + + + imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose', 'noexpand'); + + + case {'SPM2', 'SPM99'} + % legacy, for old SPM + + + imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose'); + + + otherwise + error('Unknown version of SPM! Update code, check path, etc.'); + end + + + %% Now get averages by cluster, if requested + + + switch average_over + + + % Define integer codes for sets of voxels to average over. + + + case 'unique_mask_values' + maskData = round(maskData); + u = unique(maskData)'; u(u == 0) = []; + nregions = length(u); + fprintf('Averaging over unique mask values, assuming integer-valued mask: %3.0f regions\n', nregions); + + + case 'contiguous_regions' + u = unique(volInfo.cluster); u(u == 0) = []; + maskData = volInfo.cluster; + + + case 'none' + + + otherwise + error('Illegal value for average_over. See help for this function.'); + end + + + %% Now get the average activity in each region and define a "cluster" + + + nregions = length(u); + + + cl = struct('title', 'title', 'threshold', NaN, 'Z', NaN, 'voxSize', abs(diag(volInfo.mat(1:3, 1:3)))', ... + 'XYZ', [0 0 0]', 'XYZmm', [0 0 0]', 'from_label', NaN, 'M', volInfo.mat, 'dim', volInfo.dim); + + + cl(1:nregions) = cl; + + + + + for i = 1:nregions + imgvec = maskData == u(i); + + + regiondat = imgdat(:, imgvec); + + + if ~isempty(regiondat) + regionmean = double(nanmean(regiondat')'); + + + else + regionmean = NaN .* zeros(size(dat, 1), 1); + end + + cl(i).average_data = regionmean; + cl(i).XYZ = volInfo.xyzlist(imgvec,:)'; + cl(i).XYZmm = voxel2mm(cl(i).XYZ,volInfo.mat); + + cl(i).Z = ones(1,size(cl(i).XYZ,2)); + + end +end \ No newline at end of file diff --git a/Data_extraction/extract_image_data.m b/Data_extraction/extract_image_data.m new file mode 100644 index 00000000..a2ebea37 --- /dev/null +++ b/Data_extraction/extract_image_data.m @@ -0,0 +1,178 @@ +function [imgdat, volInfo, cl] = extract_image_data(imgs_to_extract_from, mask_image, varargin) + % [imgdat, volInfo, cl] = extract_image_data(imgs_to_extract_from, mask_image, varargin) + % + % Generic function for extracting image data from a mask or atlas image, + % and returning the data and averages within regions specified by the user. + % + % Regions to average over can be either regions of contiguous voxels + % bounded by voxels with values of 0 or NaN, which are considered non-data + % values, or regions defined by unique integer codes in the mask image + % (i.e., for atlas images with unique codes for each defined region.) + % + % Mask/Atlas image does NOT have to be in the same space as the images to + % extract from. It will be remapped/resliced. + % + % extracted data is returned in single data format. + % + % Inputs: + % 1 - char array of strings containing 4D image file names (data extracted from these) + % 2 - mask_image to extract from. + % + % Optional inputs: + % 'average_over': + % Default = 'contiguous_regions' to average over contiguous voxels + % bounded by voxels of 0 or NaN (non-data values) + % Alt. option = 'unique_mask_values' to average over unique integer codes in the mask image + % (i.e., for atlas images with unique codes for each defined + % region) + % + % Example: + % imgs_to_extract_from = filenames('w*.nii','char'); + % mask_image = which('anat_lbpa_thal.img'); + % [imgdat, volInfo, cl] = extract_image_data(imgs_to_extract_from, mask_image, 'unique_mask_values'); + % + % Related functions: + % For an object-oriented alternative, see the fmri_data class and extract_roi_averages method + + + if nargin < 2 || isempty(mask_image) + mask_image = which('anat_lbpa_thal.img'); + fprintf('Using default mask: %s\n', mask_image); + if isempty(mask_image), error('Cannot find mask image!'); end + end + + average_over = 'contiguous_regions'; %'contiguous_regions' or 'unique_mask_values'; + + for varg = 1:length(varargin) + if ischar(varargin{varg}) + switch varargin{varg} + + % reserved keywords + case 'contiguous_regions', average_over = 'contiguous_regions'; + case 'unique_mask_values', average_over = 'unique_mask_values'; + end + end + end + + space_defining_image = deblank(imgs_to_extract_from(1, :)); + + + % Note: we need to write out the image here only because we need its + % anatomical boundaries (in-mask areas), contig voxels, etc. for later + + tmpname = num2str(round(10*rand(1, 5))); tmpname(tmpname == ' ') = []; + tmpname = ['tmp_mask_' tmpname '.img']; + + + maskData = scn_map_image(mask_image, space_defining_image, 'write', tmpname); + maskData = maskData(:); + + + volInfo = iimg_read_img(tmpname, 2); + maskData = maskData(volInfo.wh_inmask); + + + % This needs to be made Windows-compatible. kludgy now. + delete(tmpname); + delete([tmpname(1:end-4) '.hdr']); + + volInfo.fname = sprintf('%s (resliced to image space)', mask_image); + volInfo.maskname = mask_image; + + % Now we have the mask data and volInfo structure, and we can extract + + + + + %% + clear imgdat + switch spm('Ver') + + + case {'SPM8', 'SPM5'} + + + imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose', 'noexpand'); + + + case {'SPM2', 'SPM99'} + % legacy, for old SPM + + + imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose'); + + + otherwise + error('Unknown version of SPM! Update code, check path, etc.'); + end + + + %% Now get averages by cluster, if requested + + + switch average_over + + + % Define integer codes for sets of voxels to average over. + + + case 'unique_mask_values' + maskData = round(maskData); + u = unique(maskData)'; u(u == 0) = []; + nregions = length(u); + fprintf('Averaging over unique mask values, assuming integer-valued mask: %3.0f regions\n', nregions); + + + case 'contiguous_regions' + u = unique(volInfo.cluster); u(u == 0) = []; + maskData = volInfo.cluster; + + + case 'none' + cl = []; + return + + + otherwise + error('Illegal value for average_over. See help for this function.'); + end + + + %% Now get the average activity in each region and define a "cluster" + + + nregions = length(u); + + + cl = struct('title', 'title', 'threshold', NaN, 'Z', NaN, 'voxSize', abs(diag(volInfo.mat(1:3, 1:3)))', ... + 'XYZ', [0 0 0]', 'XYZmm', [0 0 0]', 'from_label', NaN, 'M', volInfo.mat, 'dim', volInfo.dim); + + + cl(1:nregions) = cl; + + + + + for i = 1:nregions + imgvec = maskData == u(i); + + + regiondat = imgdat(:, imgvec); + + + if ~isempty(regiondat) + regionmean = double(nanmean(regiondat')'); + + + else + regionmean = NaN .* zeros(size(dat, 1), 1); + end + + cl(i).average_data = regionmean; + cl(i).XYZ = volInfo.xyzlist(imgvec,:)'; + cl(i).XYZmm = voxel2mm(cl(i).XYZ,volInfo.mat); + + cl(i).Z = ones(1,size(cl(i).XYZ,2)); + + end +end \ No newline at end of file diff --git a/Data_extraction/extract_indiv_peak_data.m b/Data_extraction/extract_indiv_peak_data.m new file mode 100644 index 00000000..01731940 --- /dev/null +++ b/Data_extraction/extract_indiv_peak_data.m @@ -0,0 +1,55 @@ +function cl = extract_indiv_peak_data(cl,imgs) +% cl = extract_indiv_peak_data(cl,imgs) +% +% Purpose: to find individually significant regions within each subject +% and save average timecourses for each individual ROI for each subjec +% +% cl is a clusters structure with one element per region. Each element +% (cluster) is a structure containing coordinates and data. +% +% imgs is a string matrix with one row per subject, with names of images +% used to define thresholds. These may be contrast or t-images from +% individual subjects +% +% The method of extraction is defined in cluster_tmask, which is currently +% to use 50% of voxels with the highest values in imgs(subject) for each +% subject, or 100% if 50% returns less than 5 voxels. +% It's easy in cluster_tmask to use absolute t-thresholds instead. +% +% Required fields of cl: +% XYZmm, 3 x k list of k coordinates in mm space +% raw_data time x voxels x subjects matrix of data for this region +% +% Outputs appended to cl structure: +% indiv_timeseries, time x subjects averaged individual ROI timecourses +% +% .INDIV, a structure with the following information: +% tname, the name of the t-mask (or contrast mask) entered +% XYZ voxel coords for significant voxels for each subject +% XYZmm mm coords for sig voxels for each subject +% sigt logical matrix subjects x voxels for sig voxels +% maxt max map value for each subject +% center average coordinate for sig voxels for each subject +% mm_center the same in mm +% +% The spatial information may be used to correlate peak voxel location with +% behavior, for example. +% +% +% Tor Wager, 7/2005 +% + + +disp('extract_indiv_peak_data: getting individual significance regions from raw_data field') +fprintf(1,'Subject '); + +for i = 1:size(imgs,1) + + fprintf(1,'. %3.0f ',i); + cl = cluster_tmask(cl,imgs(i,:),i); + +end + +fprintf(1,'\n') + +return diff --git a/Data_extraction/extract_raw_data.m b/Data_extraction/extract_raw_data.m new file mode 100755 index 00000000..937d2b95 --- /dev/null +++ b/Data_extraction/extract_raw_data.m @@ -0,0 +1,469 @@ +% [clusters, cl_in_cell_struct] = extract_raw_data(EXPT, clusters, varargin) +% +% tor wager +% Extracts raw image data for each subject in each cluster +% averaging over cluster voxels (saved in clusters.all_data) +% and averaging over subjects (saved in clusters.timeseries) +% +% Options: High-pass filtering, spline detrending, principal components +% +% inputs: EXPT, as defined with get_expt_info.m +% clusters, as defined with tor_extract_rois.m +% Uses EXPT.FILES.im_files to get raw image names +% +% Optional: a string matrix of contrast or t-images to define +% individually significant regions. Empty input skips this step. +% +% For high-pass filtering (recommended), use the 'hpfilter' option, and +% enter HPDESIGN.TR, HPDESIGN.HP, and HPDESIGN.spersess = [number of images in each session], e.g., [220 220 220 220]. +% For session intercept fitting only, use EXPT.HP = Inf; +% requires hpfilter.m +% +% Optional inputs: +% case 'extract_from', extract_imgs = varargin{i + 1}; varargin{i + 1} = []; +% +% case 'define_ind_rois', imgs = varargin{i + 1}; varargin{i + 1} = []; +% case 'subjects', subjidxs = varargin{i + 1}; +% +% case 'noraw', doraw_data = 0; +% case {'dotrimts', 'windsorize'}, dotrimts = 0; +% +% case 'dospline', dospline = 1; +% case 'doprincomp', doprincomp = 1; +% case 'dospline', dospline = 1; +% case 'hpfilter', HPDESIGN = varargin{i + 1}; +% case 'multiple_volumes', multiple_volumes = 1; % skip checking if files exist, +% because "exist" can't read comma-appended vols +% +% case 'load_and_continue', load_and_continue = 1; doraw_data = 1; +% +% Example: Do 1st subject last, save results afterwards +% cl = extract_raw_data(EXPT, cl, 'subjects', [2:7 9:length(EXPT.FILES.im_files) 1]); +% save conjunction_cl_raw cl +% +% Modified March 9, 2008, Tor Wager; remove automatic spline detrending if no HP filter +% Mod. May 23, 2008, Tor Wager; re-format input options +% +% Example: Extract data from amygdala and put in convenient format: +% SETUP.data.M contains data images from a mediation analysis directory +% Amygdala mask images were created with SPM Anatomy toolbox +% cm = mask2clusters('/Users/tor/Documents/matlab_code/3DheadUtility/SPM2_brains/ROI_spmanatomy_CMamy_MNI.img'); +% bl = mask2clusters('/Users/tor/Documents/matlab_code/3DheadUtility/SPM2_brains/ROI_spmanatomy_BLamy_MNI.img'); +% [cl, clcell] = extract_raw_data([], [cm bl], 'extract_from', SETUP.data.M, 'noraw'); +% amy = mediation_multilev_reformat_cl(clcell) + +function [clusters, varargout] = extract_raw_data(EXPT, clusters, varargin) + global defaults; + + dotrimts = 0; % Windsorize voxel-by-voxel + dospline = 0; % spline detrend, if no HP filter + doprincomp = 0; % get principal components from clusters + doraw_data = 1; % save raw data in output + multiple_volumes = 0; %can check if files exist, because none are comma-appended vols + imgs = []; + load_and_continue = 0; % pick up where we left off + + % -------------------------------------------------------- + % Set up inputs + % -------------------------------------------------------- + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'extract_from', extract_imgs = varargin{i + 1}; varargin{i + 1} = []; + + case 'define_ind_rois', imgs = varargin{i + 1}; varargin{i + 1} = []; + case 'subjects', subjidxs = varargin{i + 1}; + + case 'noraw', doraw_data = 0; + case {'dotrimts', 'windsorize'}, dotrimts = 0; + + case 'dospline', dospline = 1; + case 'doprincomp', doprincomp = 1; + case 'dospline', dospline = 1; + case 'hpfilter', HPDESIGN = varargin{i + 1}; + case 'multiple_volumes', multiple_volumes = 1; + + case 'load_and_continue', load_and_continue = 1; doraw_data = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + % Set up images to extract from + if ~exist('extract_imgs', 'var') + if isfield(EXPT, 'FILES') && isfield(EXPT.FILES, 'im_files') + extract_imgs = EXPT.FILES.im_files; + else + disp('Cannot find either image names in extract_from input or EXPT.FILES.im_files.'); + error('Enter image names in cell array, ''extract_imgs'' followed by cells of image names (i.e., one per subject).'); + end + end + + % Set up save file name + if doraw_data && ~isfield(clusters, 'descrip') + clusters(1).descrip = input('Enter descriptive name for these clusters (no spaces or special chars): ', 's'); + end + + % Set up raw data + if doraw_data + fname = [clusters(1).descrip '_raw_data.mat']; + end + + % Set up continue + if load_and_continue + if exist(fname, 'file') + disp(['Loading saved data: ' fname]); + else + fname = spm_get(0, '*mat', 'Choose raw_data.mat file to load or done to start fresh'); + end + + if ~isempty(fname) && exist(fname, 'file') + load(fname) + else + error(['Cannot find saved file:' fname]) + end + + end + + % Set up subject indices + N = length(extract_imgs); + + if ~exist('subjidxs', 'var'), subjidxs = 1:N; end + + + % -------------------------------------------------------- + % try to get stuff for high-pass filtering + % -------------------------------------------------------- + dohp = 0; + TR = []; + HP = Inf; + spersess = []; + + if exist('HPDESIGN', 'var') && ~isempty(HPDESIGN) + if isfield(HPDESIGN, 'TR'), TR = HPDESIGN.TR; end + if isfield(HPDESIGN, 'HP'), HP = HPDESIGN.HP; end + if isfield(HPDESIGN, 'spersess') + spersess = HPDESIGN.spersess; + elseif isfield(HPDESIGN, 'FIR') && isfield(HPDESIGN.FIR, 'nruns') + spersess = HPDESIGN.FIR.nruns; + elseif isfield(HPDESIGN, 'DX') && isfield(HPDESIGN.DX, 'nsess') + spersess = HPDESIGN.DX.nruns; + end + end + + if ~isempty(TR) && ~isempty(HP) && ~isempty(spersess) + dohp = 1; + fprintf('Doing session intercept removal and high-pass filtering using %3.2f TR, %3.2f filter (s).\n', TR, HP); + + % set up filtering matrices for fast processing + y = ones(sum(spersess), 1); % dummy data + [y, I, S] = hpfilter(y, TR, HP, spersess); + else + if dospline + fprintf('HP filter info missing, using spline detrending every 100 images instead.\n'); + else + fprintf('HP filter info missing (no HP); spline detrend option off.\n'); + end + end + + if ~doprincomp + fprintf('\t(principal components option off.)\n') + end + + % -------------------------------------------------------- + % checks + % -------------------------------------------------------- + + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + if(isempty(defaults)) + spm_defaults(); + end + end + + % check clusters to see if SPM mat file info in clusters matches that of + % data to extract. Adjust voxel coordinates in clusters if necessary. + if multiple_volumes == 0 + fprintf('Checking whether files exist...') + for i = subjidxs + for j = 1:size(extract_imgs{i}, 1) + iname = deblank(extract_imgs{i}(j, :)); + if ~exist(iname, 'file') + disp(['Image does not exist! : ' iname]); + disp('Exiting extract_raw_data.m'); + return + end + end + end + elseif multiple_volumes == 1 + fprintf('Checking whether files exist...') + for i = subjidxs + for j = 1:size(extract_imgs{i}, 1) + iname = deblank(extract_imgs{i}(j, :)); + comma = find(iname(:)==','); + iname = iname(1:comma-1); + if ~exist(iname, 'file') + disp(['Image does not exist! : ' iname]); + disp('Exiting extract_raw_data.m'); + return + end + end + end + end + + fprintf('Checking coordinates...') + V = spm_vol(extract_imgs{1}(1, :)); + [chk, clusters] = check_spm_mat(clusters(1).M, V(1).mat, clusters); + if chk, fprintf('\nAdjusted vox coords to extracted image space using XYZmm; new XYZ and XYZmm created.\nASSUMING ALL IMAGES HAVE SAME DIM/SPACE AS FIRST!\n'), end + fprintf('\n') + + + + + % get max length, set up output + % -------------------------------------------------------- + nsubjects = max(subjidxs); + nimgs = zeros(1, nsubjects); + + for i = subjidxs + % get rid of empty image names + wh = all(extract_imgs{i} == ' ', 2); + extract_imgs{i}(logical(wh), :) = []; + + nimgs(i) = size(extract_imgs{i}, 1); + + end + maximgs = max(nimgs); + if ~all(nimgs(subjidxs) == maximgs) + fprintf('Warning! Not all image sets have same number of images\n'); + disp(nimgs(subjidxs)) + end + + % if we're not loading from file, initialize output vars + if ~exist('ts', 'var') + for j = 1:length(clusters) + ts{j} = NaN * zeros(maximgs, nsubjects); + + if(doraw_data) && ~exist('raw', 'var') + raw{j} = NaN * zeros(maximgs, size(clusters(j).XYZ, 2), nsubjects); + end + end + end + + % -------------------------------------------------------- + % loop through subjects, extract, and process + % -------------------------------------------------------- + + + + for i = subjidxs + t1 = clock; + fprintf('Subject %3.0f Extracting from %3.0f images...', i, nimgs(i)) + + no_errors = 1; + + % Main data extraction + % -------------------------------------------------------- + subjcl = tor_extract_rois(extract_imgs{i}, clusters); + + fprintf('%3.0f s. ', etime(clock, t1)); + + %if nimgs(i) > 0, no_errors = 0; end + + if nimgs(i) ~= maximgs + % Pad + for j = 1:length(subjcl) + subjcl(j).all_data = padwithnan(subjcl(j).all_data, ts{j}, 1); + end + end + +% % % check to see if this is the same length as first subject +% % if i ~= 1 && nimgs(i) > size(ts{1}, 1) +% % fprintf('Warning! Timeseries for subject %d is too long!!!\n', i); +% % no_errors = 0; +% % elseif i ~= 1 && nimgs(i) < size(ts{1}, 1) +% % fprintf('Warning! Timeseries for subject %d is too short!!!\n', i); +% % no_errors = 0; +% % +% % end + + if no_errors + fprintf(' Processing: ') + if dotrimts, fprintf(' Windsorizing. '); end + + t1 = clock; + + for j = 1:length(subjcl) + % index so 1st is cluster, matrix of subjects + if(doraw_data) + raw{j}(:, :, i) = subjcl(j).all_data; + end + subjcl(j).timeseries = []; + + % Windsorize and re-average + tmp = subjcl(j).all_data; + subjcl(j).all_data = []; + + for k = 1:size(tmp, 2) + if dotrimts + tmp(:, k) = trimts(tmp(:, k), 3, [], 1); % last arg does sess break removal and spike correct + end + + if dohp + tmp(:, k) = hpfilter(tmp(:, k), [], S, spersess, I); % high-pass + elseif dospline + tmp(:, k) = splineDetrend(tmp(:, k)); % spline detrend every 100 images - remove LF drift + end + end + ts{j}(:, i) = nanmean(tmp', 1)'; + + % eigenvectors and principal components + if doprincomp + warning off % divide by 0 warning if 1 voxel + [eigv, eigscore, eigval] = princomp(tmp); + ne = min(3, max(size(eigval))); % number to save + eigs{j}(:, i, 1:ne) = eigscore(:, 1:ne); % time x subjects x vectors + warning on + else + eigs{j} = []; + end + + % save names + if i == 1 && j == 1 + clusters(j).imnames = subjcl(j).imnames; + end + + clusters(j).imP{i} = extract_imgs{i}; + clusters(j).no_errors(i) = no_errors; + end + + fprintf('%3.0f s. \n', etime(clock, t1)); + else + fprintf('\tError in subject %d\n', i); + for j = 1:length(subjcl) + % bad subject, wrong # imgs + if(doraw_data), raw{j}(:, :, i) = NaN; end + ts{j}(:, i) = NaN; + eigs{j}(:, i, :) = NaN; + clusters(j).imP{i} = extract_imgs{i}; + clusters(j).no_errors(i) = no_errors; + end + end + + if(doraw_data) + save(fname, 'ts', 'raw', 'subjcl', 'eigs'); + end + % % save(fname, 'ts', 'subjcl', 'eigs'); + % % end + end % loop through subjects + + + % -------------------------------------------------------- + % attach data to main clusters + % -------------------------------------------------------- + + for j = 1:length(clusters) + ts{j}(ts{j} == 0) = NaN; + + clusters(j).all_data = ts{j}; % one column per subject, average over voxels + clusters(j).timeseries = nanmean(ts{j}')'; % average across subjects + if doprincomp, clusters(j).prin_comps = eigs{j}; end + if(doraw_data), clusters(j).raw_data = raw{j}; end % time x voxels x subjects + end + + % -------------------------------------------------------- + % extract individual peaks, if contrast/tmap input is entered + % -------------------------------------------------------- + + if ~isempty(imgs) + try + clusters = extract_indiv_peak_data(clusters, imgs); + catch + disp('Error using extract_indiv_peak_data') + end + end + + % -------------------------------------------------------- + % Reformat to cell structure with all data (clpos_data format) if asked + % for + % -------------------------------------------------------- + + if nargout > 0 + N = size(clusters(1).all_data, 2); + clusters_cell = cell(1, N); + + + for i = 1:N + for j = 1:length(clusters) + clusters_cell{i}(j) = clusters(j); + clusters_cell{i}(j).imP = clusters_cell{i}(j).imP{i}; + clusters_cell{i}(j).timeseries = clusters_cell{i}(j).all_data(:, i); + + if isfield(clusters_cell{i}(j), 'raw_data') + clusters_cell{i}(j).all_data = squeeze(clusters_cell{i}(j).raw_data(:, :, i)); + + else + clusters_cell{i}(j).all_data = []; + end + + end + + if isfield(clusters_cell{i}, 'raw_data') + clusters_cell{i} = rmfield(clusters_cell{i}, 'raw_data'); + end + + + end + + varargout{1} = clusters_cell; + end + + +end + + + + +function [chk, clusters] = check_spm_mat(mat1, mat2, clusters) + %check_spm_mat(mat1, mat2, clusters) + % mat1 is from clusters, mat2 is functional (imgs to extract) + + + chk = mat1 - mat2; + chk = chk(:); + chk = chk(1:end-1); % eliminate SPM scale factor + chk = any(chk); + + if chk + + % we need to get the correct voxel coords from mat2 (funct) into + % clusters, keeping the mm_coordinates the same! + VOL.M = mat2; + + for i = 1:length(clusters) + + clusters(i).XYZ = mm2voxel(clusters(i).XYZmm, VOL)'; % functional img space, cluster mm coordinates + + clusters(i).Z = ones(1, size(clusters(i).XYZ, 2)); + clusters(i).XYZmm = voxel2mm(clusters(i).XYZ, mat2); + + clusters(i).M = mat2; + + clusters(i).voxSize = diag(clusters(i).M(1:3, 1:3)'); + + % skip this, and clusters will have mm list of different length than + % voxel list (XYZ). data will be from voxel list. + %SPM.XYZmm = voxel2mm(SPM.XYZ, VOL.M); % slow, but gives unique voxels + end + end +end + + diff --git a/Data_extraction/read_hdr.m b/Data_extraction/read_hdr.m new file mode 100755 index 00000000..30ec67d5 --- /dev/null +++ b/Data_extraction/read_hdr.m @@ -0,0 +1,146 @@ +function hdr = read_hdr(name,varargin) +global SPM_scale_factor + +if nargin > 1, + dtype = varargin{1}; +else + dtype = 'n'; +end + +% list of datatypes to try if the first fails. +dtstring = {'l' 'b'}; + +% function hdr = read_hdr(name,[opt] datatype) +% Luis hernandez +% last edit 12-9-01 by tor wager +% +% Loads the analyze format header file from a file 'name' +% +% The function returns a structure defined as +% hdr = struct(... +% 'sizeof_hdr', fread(pFile, 1,'int32'),... +% 'pad1', setstr(fread(pFile, 28, 'char')),... +% 'extents', fread(pFile, 1,'int32'),... +% 'pad2', setstr(fread(pFile, 2, 'char')),... +% 'regular',setstr(fread(pFile, 1,'char')), ... +% 'pad3', setstr(fread(pFile,1, 'char')),... +% 'dims', fread(pFile, 1,'int16'),... +% 'xdim', fread(pFile, 1,'int16'),... +% 'ydim', fread(pFile, 1,'int16'),... +% 'zdim', fread(pFile, 1,'int16'),... +% 'tdim', fread(pFile, 1,'int16'),... +% 'pad4', setstr(fread(pFile,20, 'char')),... +% 'datatype', fread(pFile, 1,'int16'),... +% 'bits', fread(pFile, 1,'int16'),... +% 'pad5', setstr(fread(pFile, 6, 'char')),... +% 'xsize', fread(pFile, 1,'float'),... +% 'ysize', fread(pFile, 1,'float'),... +% 'zsize', fread(pFile, 1,'float'),... +% 'pad6', setstr(fread(pFile, 48, 'char'))... +% 'glmax', fread(pFile, 1,'int32'),... +% 'glmin', fread(pFile, 1,'int32'),... +% 'descrip', setstr(fread(pFile, 80,'char')),... +% 'aux_file' , setstr(fread(pFile,24,'char'))',... +% 'orient' , fread(pFile,1,'char'),... +% 'origin' , fread(pFile,5,'int16'),... +% 'generated' , setstr(fread(pFile,10,'char'))',... +% 'scannum' , setstr(fread(pFile,10,'char'))',... +% 'patient_id' , setstr(fread(pFile,10,'char'))',... +% 'exp_date' , setstr(fread(pFile,10,'char'))',... +% 'exp_time' , setstr(fread(pFile,10,'char'))',... +% 'hist_un0' , setstr(fread(pFile,3,'char'))',... +% 'views' , fread(pFile,1,'int32'),... +% 'vols_added' , fread(pFile,1,'int32'),... +% 'start_field' , fread(pFile,1,'int32'),... +% 'field_skip' , fread(pFile,1,'int32'),... +% 'omax' , fread(pFile,1,'int32'),... +% 'omin' , fread(pFile,1,'int32'),... +% 'smax' , fread(pFile,1,'int32'),... +% 'smin' , fread(pFile,1,'int32') ); + + + warning off + % Read in Headerfile into the hdrstruct + [pFile,messg] = fopen(name,'r',dtype); + if pFile == -1 + %msgbox(messg); + errordlg(['File not found! ' name]) + return; + end + + + hdr = struct(... + 'sizeof_hdr', fread(pFile, 1,'int32'),... + 'pad1', setstr(fread(pFile, 28, 'char')),... + 'extents', fread(pFile, 1,'int32'),... + 'pad2', setstr(fread(pFile, 2, 'char')),... + 'regular',setstr(fread(pFile, 1,'char')), ... + 'pad3', setstr(fread(pFile,1, 'char')),... + 'dims', fread(pFile, 1,'int16'),... + 'xdim', fread(pFile, 1,'int16'),... + 'ydim', fread(pFile, 1,'int16'),... + 'zdim', fread(pFile, 1,'int16'),... + 'tdim', fread(pFile, 1,'int16'),... + 'pad4', setstr(fread(pFile,20, 'char')),... + 'datatype', fread(pFile, 1,'int16'),... + 'bits', fread(pFile, 1,'int16'),... + 'pad5', setstr(fread(pFile, 6, 'char')),... + 'xsize', fread(pFile, 1,'float'),... + 'ysize', fread(pFile, 1,'float'),... + 'zsize', fread(pFile, 1,'float'),... + 'pad6', setstr(fread(pFile, 48, 'char')),... + 'glmax', fread(pFile, 1,'int32'),... + 'glmin', fread(pFile, 1,'int32'),... + 'descrip', setstr(fread(pFile, 80,'char')),... + 'aux_file' , setstr(fread(pFile,24,'char'))',... + 'orient' , fread(pFile,1,'char'),... + 'origin' , fread(pFile,5,'int16'),... + 'generated' , setstr(fread(pFile,10,'char'))',... + 'scannum' , setstr(fread(pFile,10,'char'))',... + 'patient_id' , setstr(fread(pFile,10,'char'))',... + 'exp_date' , setstr(fread(pFile,10,'char'))',... + 'exp_time' , setstr(fread(pFile,10,'char'))',... + 'hist_un0' , setstr(fread(pFile,3,'char'))',... + 'views' , fread(pFile,1,'int32'),... + 'vols_added' , fread(pFile,1,'int32'),... + 'start_field' , fread(pFile,1,'int32'),... + 'field_skip' , fread(pFile,1,'int32'),... + 'omax' , fread(pFile,1,'int32'),... + 'omin' , fread(pFile,1,'int32'),... + 'smax' , fread(pFile,1,'int32'),... + 'smin' , fread(pFile,1,'int32') ... + ); + + % this is where SPM hides its scaling factor when it writes floating point images in + % byte format to save space. Those crafty guys ... + fseek(pFile, 112, 'bof'); + hdr.SPM_scale = fread(pFile, 1, 'float'); + warning on + % if ~(hdr.SPM_scale == 1) & ~(hdr.SPM_scale ~= 0) ,disp(['SPM scale factor is ' num2str(hdr.SPM_scale)]),end + + fclose(pFile); + + if hdr.datatype <= 64 + % ok + + elseif nargin == 1 % only run the 1st time through. + j = 0; + while hdr.datatype > 64 + j = j+1; + + % exit the loop if end of string + if j > length(dtstring), + break, + end + + dtype = dtstring{j}; + hdr = read_hdr(name,dtype); + + end + + end + + +return + + diff --git a/Data_extraction/readim2.m b/Data_extraction/readim2.m new file mode 100755 index 00000000..f7779d1f --- /dev/null +++ b/Data_extraction/readim2.m @@ -0,0 +1,290 @@ +function [array,hdr,h,whichslices,rows,cols,figh] = readim2(varargin) +% [array,hdr,h,whichslices,rows,cols,figh] = readim2(basename or array [opt],'p' [opt], 'sagg' or 'cor' [opt],flipy[opy],range [opt]) +% outputs: +% 3d array of image +% hdr of image +% handles for axes of montage if plotting +% +% inputs: IN ANY ORDER +% basename of file, without image extension +% OR +% 3-D array in the workspace to plot +% OR +% nothing, to browse for file +% +% 'p' to plot montage of slices to the screen +% 'sagg' to rotate to saggital view +% 'cor' to rotate to coronal view +% +% 't' to save array as double instead of int16 - to save negative t values. +% +% Special Features: +% range, specified in mm, must be LAST and FIFTH input argument. +% - OR range can specify slices, e.g. 1:4:28 +% clim, color limits for axis plot, must be 1st or 2nd argument. form: [-1 1] +% +% +% Adapted 12/06/00 by Tor Wager from Luis Hernandez' original script +% Last modified 10/19/01 by Tor + +warning off + +if nargin < 1 + arg1 = [];arg2 = [];arg3 = [];arg4 = []; +else [array,basename,orient,print,tmap,flipy,usespm] = setup(nargin,varargin{1:end}); + justbase = basename; +end + +if size(size(array),2) == 3 + basename = 'wkspace variable'; +elseif nargin < 1 | ~exist('basename') + [filename,pathname] = uigetfile('*.img','pick an img file.'); + basename = [pathname filename(1:end-4)]; + justbase = filename(1:end-4); +elseif isempty(basename) | strcmp(basename,'null') | ~ischar(basename) + [filename,pathname] = uigetfile('*.img','pick an img file.'); + basename = [pathname filename(1:end-4)]; + justbase = filename(1:end-4); +end + +% ---------------------------------------------------------------------------------- +% * check to see if it exists, if not look somewhere else. this is totally unnecessary +% ---------------------------------------------------------------------------------- +% first try taking off the .img extension. +if ~(exist([basename '.img']) == 2) + basename = basename(1:end-4); +end + +if ~(exist([basename '.img']) == 2) + warning('File cannot be found in specified directory.') + basename = which([justbase '.img']); + basename = basename(1:end-4); + + disp(['Using: ' basename]) +end + + +% LOADING THE ARRAY +if isempty(array) + % Luis hernandez + % last edit 6-29-2000 + % get the header for the basename + eval(['hdr = read_hdr(''' basename '.hdr'');']) + eval(['[pFile,messg] = fopen(''' basename '.img'',''r'');']) + if pFile == -1 + disp(messg); + return; + end + + switch hdr.datatype + case 0 + fmt = 'int8'; + case 2 + fmt = 'uint8'; + case 4 + fmt = 'int16'; %'short'; + case 8 + fmt = 'int'; + case 16 + fmt = 'float'; + case 32 + fmt = 'float'; + xdim = hdr.xdim * 2; + ydim = hdr.ydim * 2; + case 64 + fmt = 'uint64'; + otherwise + warning(['Data Type ' num2str(hdr.datatype) 'Unsupported. Switching to big-endian...']); + eval(['hdr = read_hdr_b(''' basename '.hdr'');']) + status = fclose(pFile); + eval(['[pFile,messg] = fopen(''' basename '.img'',''r'',''b'');']) + if pFile == -1 + disp(messg); + return; + end + + switch hdr.datatype + case 0 + fmt = 'int8'; + case 2 + fmt = 'uint8'; + case 4 + fmt = 'int16'; %'short'; + case 8 + fmt = 'int'; + case 16 + fmt = 'float'; + case 32 + fmt = 'float'; + xdim = hdr.xdim * 2; + ydim = hdr.ydim * 2; + case 64 + fmt = 'uint64'; + otherwise + error('Cannot read native or big-endian format.') + end + end + + % ===== make space for double if tmap, int16 otherwise ===== + if tmap, array = zeros(hdr.xdim,hdr.ydim,1); + else array = int16(zeros(hdr.xdim,hdr.ydim,1)); + end + warning off + for i=1:hdr.zdim + a = (fread(pFile,[hdr.xdim, hdr.ydim], fmt)) ; + a(isnan(a)) = 0; + array(:,:,i) = a; + if mod(i,50) == 0,disp([' loaded ' num2str(i) ' slices']),end + end + warning on + fclose(pFile); + +else % if it's not a string + hdr.zdim = size(array,3); + justbase = 'workspace variable'; +end + +%ROTATIONS +if flipy + disp('flipping y dimension of img (columns of array).') + array = flipdim(array,2); +end + +if strcmp(orient,'sagg') + array = permute(array,[3 2 1]); + array = flipdim(array,1); +elseif strcmp(orient,'cor') + array = permute(array,[3 1 2]); + array = flipdim(array,1); +end + +% restrict range, if necessary +if nargin > 4 & ~ischar(varargin{5}) % & sum(size(varargin{1})==[1 2]) == 2 + range = varargin{5}; + if size(range,2) == 2 % range in mm, convert to slices + hdr.effzdim = size(array,3); + slicerange = round(range(1)/hdr.effzdim):round(range(2)/hdr.effzdim); + else % range should be row vector of slices + slicerange = range; + end + array = array(:,:,slicerange); +end + +[rows,cols,skip,whichslices] = getplotdims(array,print); + +h = []; +%DISPLAYING IMAGE +if print + whos array + array2 = double(array); + + if usespm + if(findobj('Tag','Graphics')),figure(findobj('Tag','Graphics')) + else spm_figure('Create','Graphics','Graphics') + end + figh = gcf; clf; + else + figh = figure; + end + + zoom = 6/max(rows,cols); + set(gcf,'Position',[46 16 1106 916]); + if size(varargin{1},2) == 2 %sum(size(varargin{1})==[1 2]) == 2 + clim = varargin{1}; + elseif size(varargin{2},2) == 2 %sum(size(varargin{2})==[1 2]) == 2 + clim = varargin{2}; + else clim = [min(min(min(array2))) max(max(max(array2)))]; + end + if clim(1) == clim(2), clim(2) = clim(2) + 1;,warning('All elements have same value!'),end + index = 1; + for i = whichslices + h(index) = subplot(rows,cols,index); + imagesc(array(:,:,i),[clim]); + axis off + axis square + if rows < 6 & cols < 6 + j = get(gca,'Position'); j = [j(1:2) .12 .12]; + set(gca,'Position', j); + end + index = index + 1; + camzoom(zoom) + dasp = get(gca,'DataAspectRatio'); + switch orient + case 'sagg' + daspect([dasp(1)*.78 dasp(2) 1]) + end + end + h(index) = subplot(rows,cols,index); + imagesc(clim); axis off + colorbar('horiz') % set(H,'XTick',[clim(1) clim(2)]) + cla, + text(0,1,justbase); + text(0,1.3,['Range = ' num2str(min(min(min(array2)))) ' to ' num2str(max(max(max(array2))))]) +end + +warning on + +return + +% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ % + +function [array,basename,orient,print,tmap,flipy,usespm] = setup(nargin,varargin) + +print = 0;tmap=0;orient='ax';flipy=0;usespm=0;array=[];basename='null'; + +for i = 1:nargin + if strcmp(varargin{i},'p'),print = 1;,end + if strcmp(varargin{i},'t'),tmap = 1;,end + if strcmp(varargin{i},'flipy'),flipy = 1;,end + if strcmp(varargin{i},'spm'),usespm = 1;,end + if strcmp(varargin{i},'cor'),orient = 'cor';,end + if strcmp(varargin{i},'sagg'),orient = 'sagg';,end + + if ~(strcmp(varargin{i},'p')|strcmp(varargin{i},'t')|strcmp(varargin{i},'flipy')|strcmp(varargin{i},'spm')| ... + strcmp(varargin{i},'cor')|strcmp(varargin{i},'sagg')|strcmp(varargin{i},'ax')) + if isstr(varargin{i}) + basename = varargin{i}; disp(['Loading image ' basename]) + elseif ndims(varargin{i}) == 3 + array = varargin{i}; basename = 'null'; + end + end +end + +return + + +% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ % +function [rows,cols,skip,whichslices] = getplotdims(array,print) + + if size(array,3) == 28 + rows = 6; cols = 5;skip = 1;whichslices = 1:size(array,3); + elseif size(array,3) == 35 + rows = 6; cols = 6;skip = 1;whichslices = 1:size(array,3); + else + if size(array,3) > 100 + skip = floor(size(array,3) / 28); + if print,disp(['selecting only every ' num2str(skip) 'th/rd slice.']),end + whichslices = 1:skip:size(array,3); + rows = 1;cols = 1;index = 1; + while rows * cols < ceil(size(array,3)/skip) + 1 + if mod(index,2) == 0,cols = cols + 1; + else rows = rows + 1; + end + index = index + 1; + end + else + skip = 1; + rows = 1;cols = 1;index = 1; + while rows * cols < ceil(size(array,3)/skip) + 1 + if mod(index,2) == 0,cols = cols + 1; + else rows = rows + 1; + end + index = index + 1; + end + whichslices = 1:size(array,3);index = 1; + end + end +return + + + \ No newline at end of file diff --git a/Data_extraction/sphere_roi_tool_2008.m b/Data_extraction/sphere_roi_tool_2008.m new file mode 100644 index 00000000..07a704d3 --- /dev/null +++ b/Data_extraction/sphere_roi_tool_2008.m @@ -0,0 +1,148 @@ +% [cl, all_data] = sphere_roi_tool_2008(imgs, [radius], [n x 3 coord list to use], ['useexisting']) +% +% This function extracts data from a sphere defined around a coordinate you choose. +% You enter a set of images. It will bring up the first image in SPM +% orthviews. You can click on a voxel in the image, and it will extract +% values from the timeseries. +% +% It returns an output structure, cl, that has the sphere coordinates, +% extracted data, and other information. It also makes a plot of the average values. +% +% all_data is all the data from each voxel, [images x voxels] +% +% Example: +%----------------------------------------------- +% imgs = spm_select(); +% spm_defaults +% +% [cl, all_data] = sphere_roi_tool_2008(imgs); +% +% Now that you have the cl sphere structure defined, you can visualize it +% in other ways: +% Let's make a surface plot (if your ROI is near the left medial surface): +% +% cluster_surf(cl, 5, 'left'); +% +% Let's make a montage plot: +% montage_clusters([], cl, {'r'}); +% +% +% You can also use this just to get a sphere, of any +% arbitrary radius, with no images to get data from. +% The command below uses a default template image and an 8 mm radius. +% cl = sphere_roi_tool_2008([], 8); +% +% If you enter an n x 3 list of coordinates, it will make spheres around +% them without you entering anything interactively: +% +% note: the snr in the sphere is mean signal for all voxels over time, +% divided by the *spatial* sd, ignoring variance over time +% +% Tor Wager, 2008; Update 2011 for automatic coords + +function [cl, all_data] = sphere_roi_tool_2008(imgs, varargin) + +radius = 10; +useexisting = 0; +coords = []; +if length(varargin) > 0, radius = varargin{1}; end + +for i = 1:length(varargin) + if ~ischar(varargin{i}) && size(varargin{i}, 2) == 3 + % coords + coords = varargin{i}; + end +end + +if any(strcmp(varargin, 'useexisting')) + useexisting = 1; +end + +if isempty(imgs), imgs = which('avg152T1.nii'); end + +if ~useexisting + spm_image('init', imgs(1,:)) +end + +create_figure('Average data values', 1, 1); + +quitnow = []; +cl = []; + +while isempty(quitnow) + + if isempty(coords) + % graphical selection of coordinates + + quitnow = input('Click on sphere center and press return, or 1+return to quit'); + + else + % automatically decide if done + if length(cl) == size(coords, 1) + quitnow = 1; + end + end + + if quitnow > 0 + disp('Exiting') + continue + + else + if isempty(coords) + pos = spm_orthviews('Pos'); + else + pos = coords(length(cl) + 1, :); + end + + cl = [cl get_sphere(pos, imgs, radius)]; + + create_figure('Average data values', 1, 1, 1); + plot(cl(end).timeseries, 'ko-'); + xlabel('Images'); ylabel('Values'); + end + +end + +all_data = cl(end).all_data; + +end + + + +function cl = get_sphere(pos, imgs, radius) + + spm_orthviews('Reposition', pos); + + disp('Extracting data'); + [data, XYZvoxSphere, XYZmmSphere] = iimg_sphere_timeseries(imgs, pos, radius); + + datamean = mean(data, 2); + glev = mean(datamean); + snr = glev ./ std(datamean); + + % Viz sphere + V = spm_vol(deblank(imgs(1,:))); + + cl(1).XYZ = XYZvoxSphere; + cl(1).XYZmm = XYZmmSphere; + cl(1).Z = ones(1, size(XYZvoxSphere, 2)); + cl(1).title = 'Sphere'; + cl(1).voxSize = diag(V(1).mat(1:3, 1:3))'; + cl(1).M = V(1).mat; % use first frame only if 4-D image + cl(1).timeseries = datamean; + cl(1).snr = snr; + cl(1).global_lev = glev; + cl(1).all_data = data; + + cl(1).mm_center = mean(cl(1).XYZmm, 2)'; + cl(1).numVox = size(cl(1).XYZ, 2); + + cl(1).dim = V.dim; + + cl(1).title = 'Sphere'; + + cluster_orthviews(cl, {[1 0 0]}, 'add', 'solid'); + drawnow + +end + diff --git a/Data_extraction/sphere_roi_tool_2008_conflict.m b/Data_extraction/sphere_roi_tool_2008_conflict.m new file mode 100644 index 00000000..92999566 --- /dev/null +++ b/Data_extraction/sphere_roi_tool_2008_conflict.m @@ -0,0 +1,147 @@ +<<<<<<< .mine +% [cl, all_data] = sphere_roi_tool_2008(imgs, [radius], [coordinates]) +======= +% [cl, all_data] = sphere_roi_tool_2008(imgs, [radius], ['useexisting']) +>>>>>>> .r1197 +% +% This function extracts data from a sphere defined around a coordinate you choose. +% You enter a set of images. It will bring up the first image in SPM +% orthviews. You can click on a voxel in the image, and it will extract +% values from the timeseries. +% +% It returns an output structure, cl, that has the sphere coordinates, +% extracted data, and other information. It also makes a plot of the average values. +% +% all_data is all the data from each voxel, [images x voxels] +% +% Example: +%----------------------------------------------- +% imgs = spm_select(); +% spm_defaults +% +% [cl, all_data] = sphere_roi_tool_2008(imgs); +% +% Now that you have the cl sphere structure defined, you can visualize it +% in other ways: +% Let's make a surface plot (if your ROI is near the left medial surface): +% +% cluster_surf(cl, 5, 'left'); +% +% Let's make a montage plot: +% montage_clusters([], cl, {'r'}); +% +% +% You can also use this just to get a sphere, of any +% arbitrary radius, with no images to get data from. +% The command below uses a default template image and an 8 mm radius. +% cl = sphere_roi_tool_2008([], 8); +% +% Tor Wager, 2008 + +function [cl, all_data] = sphere_roi_tool_2008(imgs, varargin) + +<<<<<<< .mine + radius = 10; + coords = []; + if length(varargin) > 0, radius = varargin{1}; end + if length(varargin) > 1, coords = varargin{2}; end +======= +radius = 10; +useexisting = 0; +if length(varargin) > 0, radius = varargin{1}; end +>>>>>>> .r1197 +if length(varargin) > 1 && strcmp(varargin{2}, 'useexisting') + useexisting = 1; +end + + if isempty(imgs), imgs = which('avg152T1.nii'); end + +<<<<<<< .mine + spm_image('init', imgs(1,:)) +======= +if ~useexisting + spm_image('init', imgs(1,:)) +end +>>>>>>> .r1197 + + create_figure('Average data values', 1, 1); + + quitnow = []; + cl = []; + + while isempty(quitnow) + + if isempty(coords) + % graphical selection of coordinates + + quitnow = input('Click on sphere center and press return, or 1+return to quit'); + + else + % automatic selection of coordinates +% V = spm_vol(imgs(1, :)); +% coords = mm2voxel(coords, V.mat); + + spm_orthviews('Reposition', coords); + end + + if quitnow > 0 + disp('Exiting') + continue + + else + pos = spm_orthviews('Pos'); + cl = [cl get_sphere(pos, imgs, radius)]; + + create_figure('Average data values', 1, 1, 1); + plot(cl(end).timeseries, 'ko-'); + xlabel('Images'); ylabel('Values'); + end + + if ~isempty(coords) + % automatic selection; quit after this + quitnow = 1; + end + + end + + all_data = cl(end).all_data; + +end + + + +function cl = get_sphere(pos, imgs, radius) + + spm_orthviews('Reposition', pos); + + disp('Extracting data'); + [data, XYZvoxSphere, XYZmmSphere] = iimg_sphere_timeseries(imgs, pos, radius); + + datamean = mean(data, 2); + glev = mean(datamean); + snr = glev ./ std(datamean); + + % Viz sphere + V = spm_vol(deblank(imgs(1,:))); + + cl(1).XYZ = XYZvoxSphere; + cl(1).XYZmm = XYZmmSphere; + cl(1).Z = ones(1, size(XYZvoxSphere, 2)); + cl(1).title = 'Sphere'; + cl(1).voxSize = diag(V.mat(1:3, 1:3))'; + cl(1).M = V.mat; + cl(1).timeseries = datamean; + cl(1).snr = snr; + cl(1).global_lev = glev; + cl(1).all_data = data; + + cl(1).mm_center = mean(cl(1).XYZmm, 2)'; + cl(1).numVox = size(cl(1).XYZ, 2); + + cl(1).title = 'Sphere'; + + cluster_orthviews(cl, {[1 0 0]}, 'add', 'solid'); + drawnow + +end + diff --git a/Data_extraction/timeseries_extract_slice.m b/Data_extraction/timeseries_extract_slice.m new file mode 100755 index 00000000..6cfa848b --- /dev/null +++ b/Data_extraction/timeseries_extract_slice.m @@ -0,0 +1,54 @@ +% function sl = timeseries_extract_slice(V,sliceno) +% +% For a given set of image names or memory mapped volumes (V) +% extracts data from slice # sliceno and returns an X x Y x time +% matrix of data. + +function sl = timeseries_extract_slice(V, sliceno, orientation) + + global defaults + + % defaults + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + + case 'SPM8' + % spm_defaults is a function + spm_defaults() + + otherwise + % unknown SPM + disp('Unknown version of SPM!'); + spm_defaults() + end + + + if ischar(V) + V = spm_vol(V); + end + + if(~exist('orientation', 'var') || isempty(orientation)) + orientation = 'axial'; + end + + switch(orientation) + case 'axial' + get_slice = @get_ax_slice; + case 'sagittal' + get_slice = @get_sag_slice; + case 'coronal' + get_slice = @get_cor_slice; + otherwise + error('Unknown orientation: %s\n', orientation); + end + + for i = 1:length(V) + sl(:,:,i) = get_slice(V(i), sliceno); + end +end \ No newline at end of file diff --git a/Data_extraction/tor_extract_rois.m b/Data_extraction/tor_extract_rois.m new file mode 100755 index 00000000..475e7bd1 --- /dev/null +++ b/Data_extraction/tor_extract_rois.m @@ -0,0 +1,279 @@ +function [clusters,SPM,xX,xCon] = tor_extract_rois(imnames,varargin) + % function [clusters, SPM, xX, xCon] = tor_extract_rois(imnames [can be empty],[opt] SPM, [opt] VOL, [opt] xX) + % + % this function gets timeseries data from all clusters in an SPM results output. + % input: + % imnames: a matrix of image names, in spm_list_files output format + % if empty, no timeseries data will be extracted. + % + % clusters: if only 2 arguments, clusters structure is 2nd arg, and we + % extract data using existing clusters structure + % + % If 3 arguments, enter SPM and VOL to extract data from VOXEL + % coordinates in these structures + % SPM: SPM variable from loaded results + % VOL: VOL variable from loaded results + % + % Optional 4th argument fits a design matrix and returns betas + % xX: xX design matrix to fit to timeseries + % OR 4th argument can be 0 (or any non-structure arg), suppressing verbose output. + % + % [Last 2 arguments are optional. Use if results are already loaded into workspace] + % + % Automatic fitting of model to cluster timeseries average using analyze_cluster_rois + % with High-Pass filter length of your choice. + % This only works if you input only the file names or input all optional arguments, including xX + % + % 10/17/01 by Tor Wager + % Last modified 3/19/04 by Tor, to get clusters structure as input and use + % timeseries3 instead of (bad on mac osx) timeseries2 + % + % NOTE (WARNING): WORKS ON XYZ VOXEL COORDINATES - TRANSFORMATION TO + % DIFFERENT SPACES ONLY IF ENTERING 2 ARGS, 1st one names, 2nd one clusters + % + % see transform_coordinates.m for transformation, or check_spm_mat, or cluster_interp. + % + % Functions called + % C:\matlabR12\toolbox\matlab\datatypes\squeeze.m + % c:\tor_scripts\voistatutility\nanmean.m + % (calls other spm functions) + % center_of_mass.m + % analyze_cluster_rois.m + + persistent prev_imnames + persistent Vimnames + + verbose = 0; + clusters = []; + SPM = []; + xX = []; + xCon = []; + clustersin = []; + + if nargin == 1 + % ---------------------------------------------------------------------------------- + % get SPM info from SPM.mat + % ---------------------------------------------------------------------------------- + [SPM,VOL,xX,xCon,xSDM] = spm_getSPM; + elseif nargin == 2 + clustersin = varargin{1}; + + if isempty(clustersin), return, end + + if ~isempty(imnames) + % ---------------------------------------------------------------------------------- + % check mat files for compatibility + % ----------------------------------------------------------------- + if isfield(clustersin,'M') + V = spm_vol(imnames(1,:)); + [chk,clustersin] = check_spm_mat(clustersin(1).M,V(1).mat,clustersin); + if chk, disp('Warning! Mat files for clusters and images do not match; using XYZmm to adjust cluster voxel coordinates'),end + end + end + + allxyz = cat(2,clustersin.XYZ); + elseif nargin == 3 + SPM = varargin{1}; + VOL = varargin{2}; + if isempty(SPM), return, end + if isempty(SPM.XYZ), disp('tor_extract_rois: no voxels to extract'), return, end + allxyz = SPM.XYZ; + elseif nargin == 4 + SPM = varargin{1}; + VOL = varargin{2}; + if isempty(SPM), return, end + if isempty(SPM.XYZ), disp('tor_extract_rois: no voxels to extract'), return, end + allxyz = SPM.XYZ; + + a = varargin{3}; + if isstruct(a), xX = a; + else verbose = 0; + end + elseif nargin ~= 3 + error('Wrong number of arguments. Use 1 if loading from SPM.mat, 3 args for no analysis, 4 with analysis.') + end + + % ---------------------------------------------------------------------------------- + % get cluster index number from each voxel + % ---------------------------------------------------------------------------------- + if isempty(clustersin) + try + % WORKS IN SPM5 -- CHANGING from old version which did not + % cluster for large voxel sets + cl_index = spm_clusters(SPM.XYZ); + +% if size(SPM.XYZ,2) > 60000 +% warning('Too many voxels to cluster! SPM bug.'); +% cl_index = ones(1,size(SPM.XYZ,2)); +% else +% cl_index = spm_clusters(SPM.XYZ); +% end + catch + disp('Error evaluating: cl_index = spm_clusters(SPM.XYZ);') + disp('No significant XYZ coordinates in SPM.XYZ!?') + clusters = []; + return + end + else + cl_index = 1:length(clustersin); + end + + + % ---------------------------------------------------------------------------------- + % get the data from ALL voxels; all_data stores raw data for all + % clusters + % ---------------------------------------------------------------------------------- + O.coords = allxyz'; + clear allxyz + if ~isempty(imnames) + if ( any(size(prev_imnames) ~= size(imnames)) || any(prev_imnames(:) ~= imnames(:)) || length(Vimnames) ~= size(imnames, 1)) + prev_imnames = imnames; + Vimnames = spm_vol(imnames); + else + disp('Using cached image names.'); + end + all_data = spm_get_data(Vimnames, O.coords'); + end + + + % ---------------------------------------------------------------------------------- + % define each cluster as cell in array. + % ---------------------------------------------------------------------------------- + + for i = 1:max(cl_index) + if verbose && i == 1 + fprintf(1,'\n\t%3.0f clusters: Extracting %03d ',max(cl_index),i), + elseif verbose + fprintf(1,'\b\b\b%03d',i) + if i == max(cl_index), fprintf(1,'\n'), end + end + + % voxels in this cluster + a = find(cl_index == i); + + + % make cluster, if we don't have it + if isempty(clustersin) + + if isfield(SPM,'title'), cl.title = SPM.title; else cl.title = ''; end + if isfield(SPM,'u'),cl.threshold = SPM.u; else cl.u = []; end + + if isfield(SPM,'Z_descrip'), cl.Z_descrip = SPM.Z_descrip; else cl.title = ''; end + + if isfield(VOL,'VOX') && isfield(VOL,'M') + cl.voxSize = VOL.VOX; + cl.M = VOL.M; + elseif isfield(VOL,'V') && isfield(VOL.V,'mat') + cl.voxSize = diag(VOL.V.mat(1:3,1:3))'; + cl.M = VOL.V.mat; + else + error('Cannot find mat file info in VOL'); + end + + cl.name = [cl.title '_' num2str(i) '_' mat2str(size(a,2)) '_voxels']; + cl.numVox = size(a,2); + cl.Z = SPM.Z(a); + cl.XYZmm = SPM.XYZmm(:,a); + cl.XYZ = SPM.XYZ(:,a); + if isfield(SPM,'df') && isfield(SPM,'STAT') && isfield(VOL,'R') && isfield(SPM,'n') && isfield(SPM,'u') && isfield(VOL,'FWHM') + cl.pVoxelLev = spm_P(1,0,max(cl.Z),SPM.df,SPM.STAT,VOL.R,SPM.n); + cl.pClustLev = spm_P(1,cl.numVox/prod(VOL.FWHM),SPM.u,SPM.df,SPM.STAT,VOL.R,SPM.n); + end + + if ~isempty(cl.Z), + % report number of sub-cluster peaks within cluster + [N] = spm_max(cl.Z,cl.XYZ); + cl.numpeaks = length(N); + end + + else % we already have clusters + cl = clustersin(i); + end + + % ---------------------------------------------------------------------------------- + % get the timeseries for the cluster + % ---------------------------------------------------------------------------------- + if ~isempty(imnames) + + if isempty(clustersin) + % no clusters input + cl.all_data = all_data(:,a); + + else + % clusters input + nv = size(clustersin(i).XYZ,2); + cl.all_data = all_data(:,1:nv); % take first n data vectors + all_data(:,1:nv) = []; % remove from all_data + end + + + + cl.timeseries = nanmean(cl.all_data',1)'; + + if size(imnames,1) < 200 && i == 1 + cl.imnames = imnames; + elseif i == 1 + cl.imnames = imnames([1 end],:); + else + cl.imnames = []; + end + + + % ---------------------------------------------------------------------------------- + % get the SNR for the cluster if it looks like rfx data rather than individual data + % ---------------------------------------------------------------------------------- + if size(imnames,1) < 60, + try + cl.snr_avgts = get_snr(cl.timeseries); + cl.snr = get_snr(cl.all_data); + if verbose, fprintf(1,' : avg SNR = %3.2f, range %3.2f - %3.2f \n ',cl.snr_avgts,min(cl.snr),max(cl.snr)), end + + % calculate # subjects w/ estimates above zero + cl.numpos = sum(cl.timeseries > 0); + + % calc 80% power level, no mult. comp corr + % from Jerry Dallal @ Tufts U. http://www.tufts.edu/~gdallal/SIZE.HTM + % (16 * sd^2 / est^2) + 1 + cl.power80 = (4 ./ cl.snr_avgts)^2 + 1; + + catch + warning('Problem with SNR calculation. Skipping.') + end + end + + else + % disp('No timeseries data extracted - image names empty.') + if isfield(SPM, 'all_data') && size(SPM.all_data, 2) == size(SPM.XYZmm, 2) + cl.all_data = SPM.all_data(:, a); + + cl.timeseries = nanmean(cl.all_data, 2); + end + + end + + cl.center = mean(cl.XYZ, 2)'; + + % in case we've added a full matrix of Z-scores with + % cluster_barplot, etc. + if min(size(cl.Z)) > 1, cl.Z = ones(1,size(cl.XYZ,2)); end + + if size(cl.XYZmm,2) > 1, cl.mm_center = center_of_mass(cl.XYZmm,cl.Z); % mean(cl.XYZmm'); + else cl.mm_center = cl.XYZmm'; + end + clusters = [clusters, cl]; + end + + + + if ~isempty(imnames) && exist('xX') && ~isempty(xX) + % ---------------------------------------------------------------------------------- + % adjust timeseries for each cluster and fit xX.X model to timeseries data + % ---------------------------------------------------------------------------------- + try + clusters = analyze_cluster_rois(clusters,xX); + catch + disp('Error analyzing timeseries clusters - skipping analysis.') + end + end +end + diff --git a/Data_processing_tools/center_of_mass.m b/Data_processing_tools/center_of_mass.m new file mode 100755 index 00000000..be993d09 --- /dev/null +++ b/Data_processing_tools/center_of_mass.m @@ -0,0 +1,47 @@ +function com = center_of_mass(XYZ,Z) +% com = center_of_mass(XYZ,Z) +% +% this function returns the center of mass of a cluster of voxels or mm coordinates +% defined as the nearest in-list coordinate to the average of the +% coordinate values weighted by the Z-score +% +% assigns a rank to each coordinate based on Z scores +% and includes +% +% enter a 3 x n list of XYZ coordinates +% returns 1 x 3 center of mass +% +% by Tor Wager +% +% Functions called: +% C:\matlabR12\toolbox\matlab\elmat\repmat.m + +% Edits: Oct 2012, to fix bug if Z values sum to exactly zero +% Now scales weights by abs value, instead of original value (Z) + +if size(Z,1) > size(Z,2), Z = Z'; end + +if all(Z) < 10*eps + disp('Center_of_mass warning: All Z values are zero'); + Z = ones(size(Z)); +end + +% Z = Z ./ sum(Z); + +Z = repmat(Z,3,1); + +if any(isnan(Z(:)) | isinf(Z(:))), Z = 1; end + +XYZw = XYZ .* abs(Z); % Wani modified Z into abs(Z), 11/12/12 + +%center = sum(XYZw,2) ./ sum(Z,2); +center = sum(XYZw,2) ./ sum(abs(Z),2); + +center = repmat(center,1,size(XYZ,2)); + +whch = find(sum((XYZ - center).^2) == min(sum((XYZ - center).^2))); + +com = XYZ(:,whch)'; +com = com(1,:); + +return \ No newline at end of file diff --git a/Data_processing_tools/detransition.m b/Data_processing_tools/detransition.m new file mode 100755 index 00000000..d6de8baa --- /dev/null +++ b/Data_processing_tools/detransition.m @@ -0,0 +1,41 @@ +function y = detransition(y,varargin) +% y = detransition(y,[doplot]) +% +% for fMRI timeseries that contains large 'jump' artifacts due to motion correction +% or other problems. +% removes these large spikes. +% updated version built into spikecorrect in trimts + +doplot = 1; +if length(varargin) > 0, doplot = varargin{1};,end + +if doplot, figure;subplot(1,2,1);plot(y,'k'); hold on; cols = {'b' 'g' 'r' 'c' 'm'}; subplot(1,2,2),yg = scale(diff(y));,[h,x]=hist(yg,30);,plot(x,h,'k'),drawnow,end + +thr = [3 3.3 3.5]; + +for iter = 1:3 + + yg = scale(diff(y)); + + % robust standard deviation on Windsorized data + sd = std(trimts(yg,2,[])); + yg = (yg-mean(yg)) ./ sd; + + yw = find(abs(yg) > thr(iter)); + + for i = 1:length(yw) + + y(yw(i)+1:end) = y(yw(i)+1:end) - (y(yw(i)+1) - y(yw(i))); + + end + + if doplot, subplot(1,2,1); plot(y,cols{iter}), + subplot(1,2,2); hold on; [h]=hist(yg,x);hh=bar(x,h);set(hh,'FaceColor',cols{iter}) + drawnow, ;pause(1), + end +end + + y = detrend(y,'linear'); + + return + \ No newline at end of file diff --git a/Data_processing_tools/downsample_scnlab.m b/Data_processing_tools/downsample_scnlab.m new file mode 100644 index 00000000..168a191f --- /dev/null +++ b/Data_processing_tools/downsample_scnlab.m @@ -0,0 +1,49 @@ +function [yi, xi] = downsample_scnlab(y, orig_samprate, new_samprate, varargin) + % [yi, xi] = downsample_scnlab(y, orig_samprate, new_samprate, [doplot]) +% +% orig_samprate : sampling rate in Hz +% new_samprate : desired sampling rate in Hz +% +% Uses linear interpolation to resample a vector from one sampling +% rate to another +% I prefer this to matlab's downsample.m because linear interpolation is +% robust and does fewer weird things to the data. +% +% Tor Wager, June 2009 +% +% example: +% ------------------------------------------------------------ +% Downsample a 100 Hz signal to a scanning TR of 2 sec +% signal at 100 Hz, sample to low-freq TR of 0.5 hz (2 sec TR) +% every 100 / TR = 100/.5 = 200 samples +% +% [yi, xi] = downsample_scnlab(y, 100, .5) + +doplot = 0; +if length(varargin) > 0, doplot = varargin{1}; end + +downsamplerate = orig_samprate ./ new_samprate; + +nobs = length(y); + + +% observations in resampled output +nobs_out = round(nobs ./ downsamplerate); + + +x = 1:nobs; + +xi = linspace(0, nobs, nobs_out); + + +yi = interp1(x, y, xi, 'linear', 'extrap'); + +if doplot + + create_figure('Downsample plot'); + plot(y) + hold on; plot(xi, yi, 'r') + +end + +end diff --git a/Data_processing_tools/fft_plot_scnlab.m b/Data_processing_tools/fft_plot_scnlab.m new file mode 100644 index 00000000..7ae716ed --- /dev/null +++ b/Data_processing_tools/fft_plot_scnlab.m @@ -0,0 +1,97 @@ +function [myfft, freq, handle] = fft_plot_scnlab(dat, TR, varargin) +% [myfft, freq, handle] = fft_plot_scnlab(dat, TR, varargin) +% +% INPUTS: +% dat is a data vector (column) +% +% TR is the sampling rate of the data you put in +% in seconds / sample, or 1/Hz +% +% Optional inputs: +% 'samefig' +% 'color, ['b'] or other color +% 'bar' +% 'linebar' : both line and bar +% +% Example: plot effects of filtering on a difference +% between two regressors +% --------------------------------------------------- +% spm_hplength = SPM.xX.K.HParam; +% d = SPM.xX.X * SPM.xCon(mycon).c(:, 1); +% create_figure('Contrast'); plot(d) % contrast we care about +% px = pinv(SPM.xX.K.X0); % pinv of the filtering matrix +% y = d; +% y = y - SPM.xX.K.X0 * px * y; % residuals after filtering +% +% [myfft, freq, handle] = fft_plot_scnlab(d, 2); +% hold on; +% [myfft2, freq2, handle] = fft_plot_scnlab(y, 2); set(handle,'Color','r') +% plot_vertical_line(1/spm_hplength) +% set(ans, 'Color', 'b', 'LineWidth', 3) + +fftfig = 1; +ptype = 'line'; +color = 'k'; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'samefig', fftfig = 0; + + case 'bar', ptype = 'bar'; + case 'color', color = varargin{i+1}; varargin{i+1} = []; + + case 'linebar', ptype = 'both'; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +n = length(dat); + +% from matlab example +% power = abs(Y(1:floor(n/2))).^2; +% nyquist = 1/2; +% freq = (1:n/2)/(n/2)*nyquist + +nyq = 1 ./ (2 * TR); + +timepts = floor(n ./ 2); + +freq = (0:timepts-1)/timepts * nyq; +%freq = linspace(0, nyq, timepts)'; + +myfft = fft(dat); %real(abs(fft(dat))); +myfft = abs(myfft(1:timepts)) .^ 2; % power + +myfft = myfft ./ sum(myfft); + +if fftfig + create_figure('fftplot',1, 1, 1); +else + hold on +end + +%myfft = myfft(1:timepts); +switch ptype + case 'line' + handle = plot(freq, myfft, '-', 'Color', color, 'LineWidth', 2); + + case 'bar' + handle = bar(freq, myfft); + set(handle, 'FaceColor', color); + + case 'both' + handle = bar(freq, myfft); + set(handle, 'FaceColor', color, 'EdgeColor', 'none'); + handle = plot(freq, myfft, '-', 'Color', color, 'LineWidth', 2); + + otherwise + error('unknown plot type'); +end + +end + diff --git a/Data_processing_tools/filterAdjust.m b/Data_processing_tools/filterAdjust.m new file mode 100755 index 00000000..adf48cce --- /dev/null +++ b/Data_processing_tools/filterAdjust.m @@ -0,0 +1,512 @@ +function [y,O,X,S] = filterAdjust(O) +% [y,O,X,S] = filterAdjust(OPTIONS) +% +% O.y = signal +% O.HP = high pass freq. cutoff +% O.TR = sampling rate in s +% +% O.doHP = [0 or 1] - do HP filter (spm), default is 0 +% O.doLP = [0, 1, 2] - do LP filter (spm), default is 0 +% 2 = Gaussian filter with TR*2 s length +% +% O.firstimg - sets values for first image in each run to mean of the +% remaining values. Good for removing first-image artifacts present in +% some scanner sequences. Default is 1. +% +% O.cyclecorrection - checks for unimodal (normally distributed) data +% within each session, because some bimodal data that cycles between two +% mean scanner values has been observed in some data. if a high proportion +% of outliers are found in non-normal data, subtracts mean of higher mode +% to adjust data. Sorry-not clear. Check the code. Default is 0 +% +% O.cyclecorrection2 - removes large transitions from data, as in +% detransition.m. Default is 0. Artifacts may be acquisition or +% motion-correction/resampling related. +% +% We do this after filtering, but if there's trouble, we re-do the +% scanadjust and filtering, because 'cycling' can affect these. +% +% O.scanadjust = [0 or 1] - adjust to scan means, default is 0 +% - If O.X is entered, assumes this is the session mean matrix, +% instead of recomputing. +% O.percent = [0 or 1] - adjust to percent change from baseline, default is 0 +% +% O.filtertype = filter style, default is 'none' +% 'spm' = use spm's filtering +% - if O.S is entered, uses this instead of +% recomputing +% 'fourier' = Doug's fourier filter +% 'fouriernotch' : Omit frequencies between HP(1) and HP(2) +% 'cheby' = chebyshev +% 'Luis' = Luis' custom filter +% 'none' = no filtering (or leave field out). +% +% O.HP +% for SPM, the filter cutoff in s +% for fourier, the HP value or the [HP LP] values +% -notches out everything slower than HP and faster than LP, in s +% (1/s = Hz). +% +% O.nruns = number of runs (scanadjust), default is 1 +% O.adjustmatrix = custom adjustment matrix to regress out (e.g., movement params) +% O.plot [0 or 1] = plots intermediate products, default is 0 +% O.verbose [0 or 1] = verbose output +% O.trimts [0 or std] = trim overall timseries values to std, 0 for no trimming +% O.lindetrend = specify linear detrending of timeseries. +% . occurs after adjustment and filtering and +% windsorizing +% . detrending option -> what to enter in this field: +% . no detrending -> empty, missing field, or 0 +% . detrending every n elements -> single number (n) +% . piecewise linear detrend -> ROW vector of breakpoints +% (do not specify 1 as the start of the 1st segment.) +% +% by Tor Wager, 09/24/01 +% modified 10/15/02 to add trial baseline adjustment and linear detrending. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% out = voistat('adjusty',y,HChoice,TR,nruns, [opt] adjustmatrix); +% disp('* voistat adjusty') + +if ~isfield(O,'verbose'), O.verbose = 0;,end +if ~isfield(O,'trimts'), O.trimts = 0;,end +if ~isfield(O,'lindetrend'), O.lindetrend = 0;, end +if ~isfield(O,'percent'), O.percent = 0;, end + +y = O.y; +TR = O.TR; +npoints = size(y,1); + +if size(y,2) > size(y,1), y = y';, end + +nruns = 1; +if isfield(O,'scanadjust'), if O.scanadjust, nruns = O.nruns;,end,else, O.scanadjust = 0;,end +if isfield(O,'adjustmatrix'), docustomadjust = 1;,adjustmatrix = O.adjustmatrix;,else, docustomadjust = 0;,adjustmatrix = [];,end + +scanlen = npoints./nruns; +if scanlen ~= round(scanlen),warning(['Scan length is not an integer! scanlen = ' num2str(scanlen) ', rounded = ' num2str(round(scanlen))]),end +scanlen = round(scanlen); + +wholeyavg = mean(y(~isnan(y))); +X = []; + +if ~isfield(O,'doHP'), O.doHP = 0;,HChoice = [];,else, HChoice = O.HP;end +if O.doHP,if isempty(HChoice),error('filterAdjust: specify HP filter length in input options!'),end,end +if ~isfield(O,'doLP'), O.doLP = 0;,end +if ~isfield(O,'filtertype'), O.filtertype = 'none';,end +if ~isfield(O,'plot'), O.plot = 0;,end +if ~isfield(O,'cyclecorrection'), O.cyclecorrection = 0;,end +if ~isfield(O,'cyclecorrection2'), O.cyclecorrection2 = 0;,end +if ~isfield(O,'firstimg'), O.firstimg = 1;,end + +% plot raw +if O.plot, f1 = figure('Color','w'); hold on; set(gca,'FontSize',16); plot(scale(y),'k');,title('Raw timeseries (standardized for display)'); pause(1);,end + +if ((O.doHP | O.doLP) & strcmp(O.filtertype,'spm')) + % MAKE FILTER - use_spm_filter.m + % -------------------------------------------------------- + % Ran out of memory doing this with whole ts, so trying to make more efficient...do it scan by scan. + if O.doHP | O.doLP + if isfield(O,'S') + if O.verbose, disp( ' ...voistat adjusty: Using input S filter: filter shape plot is final S matrix only'), end + S = O.S; KL = O.S; KH = O.S; + else + + + if O.verbose, disp( ' ...voistat adjusty: making S filter'), end + + + if O.doHP & O.doLP + + if O.doLP == 2 + + [S,KL,KH] = use_spm_filter(TR,scanlen,'Gaussian','specify',HChoice,TR*2); + + else + + [S,KL,KH] = use_spm_filter(TR,scanlen,'hrf','specify',HChoice); + + end + + + elseif O.doHP + + [S,KL,KH] = use_spm_filter(TR,scanlen,'none','specify',HChoice); + + elseif O.doLP == 2 + + [S,KL,KH] = use_spm_filter(TR,scanlen,'Gaussian','none',[],TR*2); + + elseif O.doLP + + [S,KL,KH] = use_spm_filter(TR,scanlen,'hrf','none',[]); + + else error('Script bug. Check filterAdjust.m') + + end + + end % if S + + + else warning('No filtering specified, though spm is filtertype.'), + S = []; + end + + if O.plot, figure; + subplot(1,3,1);imagesc(S); title('Smoothing matrix (spm) *used') + subplot(1,3,2);imagesc(KL); title('Low-pass filter(spm)') + subplot(1,3,3);imagesc(KH); title('High-pass filter(spm)') + pause(1) + close + end + +end + +if (O.doHP | O.doLP) & strcmp(O.filtertype,'none') + error('For filtering to be used, enter ''spm'' ''cheby'' ''doug'', etc. in O.filtertype.') +end + + +if O.cyclecorrection2 + if O.verbose, disp(' ...removing large transitions from data (presumably artifacts).'),end + + for startimg = 1:scanlen:npoints + tmp = y(startimg:startimg+scanlen-1); + tmp = detransition(tmp,O.plot); + y(startimg:startimg+scanlen-1) = tmp; + end + + if O.plot, figure(f1); hold off; plot(scale(y),'k'); hold on; title('Raw (detransitioned, std for display)');,pause(1);end +end + + +if O.scanadjust + % adjust scan means + % -------------------------------------------------------- + if O.verbose, disp(' ...adjusting scan means to session mean.'),end + + if isfield(O,'X') + X = O.X; + else + index = 1; + for startimg = 1:scanlen:npoints + X(startimg:startimg+scanlen-1,index) = 1; + index = index + 1; + end + end + + + if size(X,1) > size(y,1) + disp([' * * * voistat.m adjusty: WARNING: X is longer than y. truncating.']) + X = X(1:length(y),:); + end + + %y = y - (X * (X \ y)); % y-xB to adjust to mean. + y = y - X * pinv(X) * y; + + if O.plot, figure; + imagesc(X); title('Model matrix for effects to regress out.') + pause(1),close + + figure(f1); hold off; plot(scale(y),'k'); hold on; title('Session means removed, std for display)');,pause(1); + end + +end + +if O.firstimg + if O.verbose, disp(' ...Setting values of first 2 images in each run to timeseries mean value.'),end + tmp = 1:scanlen:length(y); + tmp = [tmp 2:scanlen:length(y)]; + + % first adjust to session means - rough estimate + tmpn = 1:length(y); tmpn(tmp) = []; + y(tmp) = mean(y(tmpn)); + + % then adjust to expected + tmpx = (1:length(y))'; + [P] = polyfit(tmpx,y,3); + yy = polyval(P,tmpx); + y(tmp) = yy(tmp); + + if O.plot, figure(f1); plot(scale(y),'m'); hold on; + plot(scale(yy)); + title('Raw (1-2 removed, std for display)');, + end +end + +switch O.filtertype + +case 'spm' +% filter each scan +% -------------------------------------------------------- +if ~isempty(S) + filty = []; + if O.verbose, disp(' ...applying to each session: high-pass filtering.'),end + + % whos y + + for startimg = 1:scanlen:npoints + try + sessy = y(startimg:startimg+scanlen-1,:); + catch + startimg + whos y + error([' voistat adjusty: ERROR. y is too short, npoints is wrong, or numscans is wrong? Check GUI values.']) + end + + try + sessy = S * sessy; + catch + try + sessy = (S * sessy')'; + catch + disp(' can''t multiply S and sessy: matrix dimensions not correct. Try transposing y?') + whos S + whos sessy + disp(['Scanlength = ' num2str(scanlen)]) + disp(['total time pts = ' num2str(npoints)]) + disp(['Num Sessions = ' num2str(nruns)]) + error('Exiting now...') + end + end + + filty = [filty; sessy]; + end + +% whos filty + + if O.plot + figure(f1);hold off; plot(scale(y),'b','LineWidth',1);, hold on; plot(scale(filty),'r','LineWidth',1); + legend({'Before filtering' 'After filtering'}) + + %pause(1),close + end + + y = filty; + +end + + % Old custom filter stuff. + %Wn = .35; + %[B,A] = cheby2(3,40,Wn); + %y = filter(B,A,y); + +% Doug's custom fourier LP filter. +%len = length(y) /2; +%ind = [1:len] ./ len; +%ff = 1./(1+exp((ind-.30)./0.05)); +%ff2 = [ff ff(len:-1:1)]; +%y = real(ifft(fft(y).*ff2')); + + + +case 'cheby' +if ~isempty(HChoice) + nyquist = TR/2; % in seconds, not frequencies TR/nyquist = .5 + Wn = [TR/HChoice .35]; + if O.verbose, disp([ 'voistat adjusty: Chebyshev filter freqency is ' num2str(Wn)]),end + for startimg = 1:scanlen:npoints + sessy = y(startimg:startimg+scanlen-1); + [B,A] = cheby2(1,20,Wn); + sessy = filter(B,A,y); + end +end + +case 'fourier' +if ~isempty(HChoice) + filty = []; nyquist = (1/TR)/2; lowerlim = 1/HChoice(1); + hzfreqs = (1:scanlen) ./ (scanlen * TR); + if length(HChoice) == 1 + ftopass = hzfreqs >= lowerlim & hzfreqs <= nyquist; + else + ftopass = hzfreqs >= lowerlim & hzfreqs <= 1./(HChoice(2)); + end + if O.verbose, disp([' ...applying to each session: notch filtering: ' num2str(lowerlim) ' to ' num2str(nyquist) ' Hz.']),end + for startimg = 1:scanlen:npoints + myfft = fft(y(startimg:startimg+scanlen-1)); + mypass = zeros(1,scanlen); + mypass(ftopass) = myfft(ftopass); + % plot(abs(myfft));hold on;plot(abs(mypass),'rx'); + sessy = real(ifft(mypass))'; + filty = [filty; sessy]; + end + y = filty; + %hold on; plot(y,'r'); +end + +case 'fouriernotch' +if ~isempty(HChoice) + filty = []; nyquist = (1/TR)/2; lowerlim = 1/HChoice(1); + hzfreqs = (1:scanlen) ./ (scanlen * TR); + ftopass = ones(size(hzfreqs)); + ftopass(hzfreqs > lowerlim & hzfreqs < 1./(HChoice(2))) = 0; + ftopass = find(ftopass); + + if O.verbose, disp([' ...applying to each session: notch filtering: ' num2str(lowerlim) ' to ' num2str(nyquist) ' Hz.']),end + for startimg = 1:scanlen:npoints + myfft = fft(y(startimg:startimg+scanlen-1)); + mypass = zeros(1,scanlen); + mypass(ftopass) = myfft(ftopass); + %figure; plot(abs(myfft));hold on;plot(abs(mypass),'rx'); + sessy = real(ifft(mypass))'; + filty = [filty; sessy]; + end + y = filty; + %hold on; plot(y,'r'); +end + +case 'bandpass' +if ~isempty(HChoice) + filty = []; nyquist = (1/TR)/2; lowerlim = 1/HChoice(1); + hzfreqs = (1:scanlen) ./ (scanlen * TR); + ftopass = zeros(size(hzfreqs)); + ftopass(hzfreqs > lowerlim & hzfreqs < 1./(HChoice(2))) = 1; + ftopass = find(ftopass); + + if O.verbose, disp([' ...applying to each session: band pass filtering: ' num2str(lowerlim) ' to ' num2str(1./(HChoice(2))) ' Hz.']),end + for startimg = 1:scanlen:npoints + myfft = fft(y(startimg:startimg+scanlen-1)); + mypass = zeros(1,scanlen); + mypass(ftopass) = myfft(ftopass); + %figure; plot(abs(myfft));hold on;plot(abs(mypass),'rx'); + sessy = real(ifft(mypass))'; + filty = [filty; sessy]; + end + y = filty; + %hold on; plot(y,'r'); +end + + +case 'Luis' + if O.plot + y = luisFilter(y,O.TR,.35,'v');pause(1),close,pause(1),close + else + y = luisFilter(y,O.TR,.35); + end + + +case 'none' + +end % end switch + + + +if O.cyclecorrection + % check for bimodal 'cycling' of mean BOLD signal within sessions, and + % correct if necessary, before removing scan means, etc. + % if we find problems, then re-do session-centering + % -------------------------------------------------------- + if O.verbose, disp(' ...checking for normally distributed data (to avoid bimodal cycling observed in some data).'),end + cycor = []; ind = 1; classes = []; + for startimg = 1:scanlen:npoints + tmp = y(startimg:startimg+scanlen-1); + + [tmp,IDX] = mixture_model(tmp,O.plot); + + if max(IDX) > 1 & ~isempty(S) + tmp = S * tmp; + if O.plot, subplot(1,3,1),hold on;plot(tmp,'g'),end + end + + y(startimg:startimg+scanlen-1) = tmp - mean(tmp); + classes(ind) = max(IDX); + + ind = ind+1; + end + cycor = classes > 1; + + if O.verbose, + + fprintf(1,' ...%3.0f Sessions were not normally distributed.\n',sum(cycor)), + if any(cycor), fprintf(1,['\t\t\tClasses by session: ' num2str(classes) '\n']),end + else + if any(cycor), fprintf(1,['\t\t\tClasses by session: ' num2str(classes) '\n']),end + end +end + + +% do overall trimming here +% -------------------------------------------------------- +if O.trimts + [y,ntrimmed] = trimts(y,O.trimts,[],1); + if O.verbose,fprintf(1,['\t\t...Windsorized ' num2str(ntrimmed) ' points from overall timeseries to ' num2str(O.trimts) ' std. deviations.\n']), end + + if O.plot + figure(f1); plot(scale(y),'k--');, legend({'Before filtering' 'After filtering' 'After filt+trimming'}) + end + %tpts = abs(y) > O.trimts .* std(y); + %y(tpts) = NaN; +end + + + +if O.lindetrend + % linear detrending at specified knot points + + if O.verbose, fprintf(1,['\t\t...piecewise linear detrending requested with ' num2str(length(O.lindetrend)) ' breakpoints.\n']),end + % set bp to [] for removal of one linear trend from the whole column + if O.lindetrend == 1, O.lindetrend = [];, end + % if a single number, interpret as knotpoints every n elements. + + if length(O.lindetrend) == 1, O.lindetrend = O.lindetrend:O.lindetrend:length(y);, end + + if O.plot, + figure; plot(y);, + set(gcf,'Position',[144 542 1306 504],'Color','w') + disp([' Breakpoints: ' num2str(O.lindetrend)]) + end + hold on; + y = detrend(y,'linear',O.lindetrend); + y = y + wholeyavg; % preserve mean of the original timeseries + + if O.plot, + figure(f1); hold on; plot(y,'m'); + plot([O.lindetrend' O.lindetrend']',[ones(length(O.lindetrend),1) * min(y) ones(length(O.lindetrend),1)*max(y)]','k') + legend({'Original' 'Detrended'}) + + mystd = std(y) * 3; + plot([0 length(y)],[mean(y)+mystd mean(y)+mystd],'k--') + plot([0 length(y)],[mean(y)-mystd mean(y)-mystd],'k--') + text(length(y)-10,mean(y)+mystd,'3 std lines') + + drawnow + pause(1) + %close + end +end + + + + +if O.percent + + if O.verbose, disp([' ...converting to % change from overall mean: original mean is ' num2str(wholeyavg)]),end + y = (y-mean(y))*100 / wholeyavg; % percent change from 0. + +end + +if docustomadjust + if O.verbose, disp(' ...adjusting for user-entered covariates -e.g. movement params'),end + if sum(sum(isnan(adjustmatrix))) > 0, + disp(' ...WARNING: NaN values in user covariates! Setting these to 0.') + adjustmatrix(isnan(adjustmatrix)) = 0; + end + try + X = [adjustmatrix]; + X(:,end+1) = 1; % add intercept + catch + warning(' voistat ''adjusty'': X and adjustmatrix are different sizes. no covariates entered.') + whos X + whos adjustmatrix + end + O.custombeta = pinv(X) * y; + y = y - X * O.custombeta; + end + + + +% figure; plot(abs(fft(y)),'ro');set(gca,'YLim',[0 1000]) + + +return +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/Data_processing_tools/fir2htw2.m b/Data_processing_tools/fir2htw2.m new file mode 100755 index 00000000..5710bbe1 --- /dev/null +++ b/Data_processing_tools/fir2htw2.m @@ -0,0 +1,203 @@ +function [h,t,w,w_times,halfh, auc] = fir2htw2(b,varargin) + % [h,t,w,w_times,halfh, auc] = fir2htw2(b,[hconstraint],[doplot],[colors cell]) + % estimates height, time to peak, and width of FIR response + % + % b : beta/estimate series for hemodynamic response curve + % hconstraint : max time in samples that can be considered the peak + % (default = last sample) + % doplot : flag for plot, 1/0 + % colors : cell vector of colors for plot + % + % tor wager, 2/14/05 + % + % minh = min height + % + % This version uses turning points (zero gradient) to find the largest + % "hump" in the data and the time it occurs. + % + % Example: + % hrf = spm_hrf(.5); hrf = hrf ./ max(hrf); hrf = hrf + .1 * randn(length(hrf), 1); + % create_figure('hrf'); plot(hrf); + % [h,t,w,w_times,halfh, auc] = fir2htw2(hrf, [], 1); + + if size(b,2) < length(b), b = b'; end + + if ~isempty(varargin) && ~isempty(varargin{1}) + hconstraint = varargin{1}; + else + hconstraint = length(b); + end + + if hconstraint > length(b) + warning('fir2htw is trying to use more betas than there are! limiting.') + hconstraint = length(b); + end + + if length(varargin) > 1, + doplot = varargin{2}; + else + doplot = 1; + end + + + % exit if empty + if isempty(b) || all(b==0) || all(isnan(b)) + h = NaN; t = h; w = h; w_times = [NaN NaN]; halfh = NaN; + warning('fir2htw: Empty, nan, or all zero values!') + return + end + + % exit if all vals are same + if all(b == mean(b)), h = NaN; t = h; w = h; w_times = [NaN NaN]; halfh = NaN; + warning('fir2htw: all values are the same!') + return + end + + + + colors = {'ro-'}; + if length(varargin) > 2, colors = varargin{3}; end + + + % find turning points in data + tmp = diff(b); + turnpts = find([0 diff(sign(tmp))]); + + if isempty(turnpts) + % monotonic increasing or decreasing function, can't define + turnpts = [find(b==min(b)) find(b==max(b))]; + end + + + + dat = b(1:hconstraint) - mean(b(1:2)); %- b(1); % ; - mean(b(1:2)); % deviations from first point (baseline) + + turnpts(find(turnpts > length(dat))) = []; % eliminate extra turn points after hconst + + if isempty(turnpts) + % monotonic increasing or decreasing function, can't define + turnpts = [find(dat==min(dat)) find(dat==max(dat))]; + end + + tpdat = abs(dat(turnpts)); + + t = turnpts(tpdat == max(tpdat)); % time of max absolute turning point + t = t(1); + h = dat(t); % the value at max + + + % calculations for width + halfh = .5 * h; % 1/2 the max to min distance + + %d = distance(halfh,dat); + + if h > 0 + wh = find(dat > halfh); + else + wh = find(dat < halfh); + end + + if isempty(wh), wh = NaN; end + + % first half + x2 = max(wh(1),1); % first above halfh + x1 = max(wh(1)-1,1); % first above-half - 1 + y2 = dat(x2); + y1 = dat(x1); + m = y2 - y1; % slope, x reduces to 1 (x2 - x1 = 1) + if m == 0 + w_times(1) = x1; % exact match + else + w_times(1) = x1 + (halfh - y1) ./ m; % solve y = mx + b for x* given m; y1 = b + end + + % second half + x1 = min(wh(end),hconstraint); % last one above halfh + x2 = min(wh(end)+1,hconstraint); % + 1 + y2 = dat(x2); y1 = dat(x1); + m = y2 - y1; % slope, x reduces to 1 (x2 - x1 = 1) + if m == 0 + w_times(2) = x1; % exact match + else + w_times(2) = x1 + (halfh - y1) ./ m; % solve y = mx + b for x* given m; y1 = b + end + + + w = w_times(2) - w_times(1); % width in elements + + % Add area under curve measure + if nargout > 5, auc = sum(dat); end + + + if doplot + minh = mean(b(1:2)); + try + + hold on + h1 = arrow([t minh],[t h+minh],'Length',12); % height arrow + h2 = arrow([0 h+minh],[t h+minh],'Length',12); % delay arrow + h3 = arrow([w_times(1) halfh+minh],[w_times(2) halfh+minh],'Length',12); + h4 = arrow([w_times(2) halfh+minh],[w_times(1) halfh+minh],'Length',12); + + h5 = text(t + .1 * w, halfh+minh + .4*(halfh+minh),'h','FontSize',16,'FontWeight','bold'); + h6 = text(t - .5 * w, h+minh - .2*(halfh+minh),'t','FontSize',16,'FontWeight','bold'); + h7 = text(t - .5 * w, halfh+minh - .2*(halfh+minh),'w','FontSize',16,'FontWeight','bold'); + + set(h1,'Color',colors{1}(1)) + set(h2,'Color',colors{1}(1)) + set(h3,'Color',colors{1}(1)) + set(h4,'Color',colors{1}(1)) + set(h5,'Color',colors{1}(1)) + set(h6,'Color',colors{1}(1)) + set(h7,'Color',colors{1}(1)) + + catch + warning('Error drawing arrows!') + end + end + + + +end + + +% OLD EXTRA STUFF + +% % range = max(b(1:hconstraint)) - min(b(1:hconstraint)); +% % +% % % find all values within some tolerance of the half-max height +% % % if not enough points found to determine width, then interpolate until we +% % % get values. +% % % it would be better to interpolate! +% % tolval = .02; wh = []; resampleval = 1; +% % +% % tol = tolval * range; % tolerance of tolval % of range +% % wh = find(d <= tol); % find all points within tolerance +% % +% % while length(find(wh>t)) < 1 | length(find(wh 10, warning('Could not find width!'), break, end +% % end +% % +% % % now find nearest elements to peak that are at max height +% % d_from_t = wh - t; +% % w_times = [max(d_from_t(d_from_t < 0)) min(d_from_t(d_from_t > 0))]; +% % +% % +% % +% % if length(w_times) > 1 +% % w = w_times(2) - w_times(1); % width in elements +% % w_times = t + w_times; % time indices of elements before and after peak at 1/2 max height +% % else +% % w_times = [NaN NaN]; +% % w = NaN; % width is undefined; 1st or last tp was max +% % end + + diff --git a/Data_processing_tools/get_snr.m b/Data_processing_tools/get_snr.m new file mode 100755 index 00000000..9622ffb8 --- /dev/null +++ b/Data_processing_tools/get_snr.m @@ -0,0 +1,13 @@ +function snr = get_snr(data) +% snr = get_snr(data) +% +% data is a matrix whos columns index voxels, and rows index subjects (or trials, etc.) +% +% Tor Wager + +mystd = nanstd(data); +mystd(mystd == 0) = NaN; +snr = nanmean(data) ./ mystd; + +return + diff --git a/Data_processing_tools/hpfilter.m b/Data_processing_tools/hpfilter.m new file mode 100755 index 00000000..c18ffd42 --- /dev/null +++ b/Data_processing_tools/hpfilter.m @@ -0,0 +1,150 @@ +% [y, pI, S, I] = hpfilter(y, TR, HP, spersess, varargin) +% +% +% Outputs: +% y filtered data, session intercepts removed +% pI intercept model X*pinv, such that y - pI * y removes intercept +% I intercept and dummy scan design matrix +% S smoothing model, such that S * y does HP filtering +% if sess lengths are unequal, make as long as the longest +% session; may do funny things to shorter ones? +% +% Inputs: +% y data to be filtered +% TR TR in seconds +% HP Highpass filter cutoff in seconds +% spersess scans per sessions; vector with length = # of sessions +% +% Optional inputs - in fixed order: +% 1: pI pseudoinverse of I matrix, for fast application to new y +% 2: dummy vector of fixed time points to model with dummy regressors in each run +% e.g., 1:2 models first two time points in each run with +% separate dummy regressor (appended to I) +% 3: dooutliers flag (1/0). If 1, imputes session mean to time points +% with data > 4 median absolute deviations from the session median (after +% filtering). Not part of scnlab standard preprocessing. Use with caution. +% +% EXamples: +% +% [y, I, S] = hpfilter(data, 2, 120, [169 169 169 173 173]); % slowest, creates +% intercept and smoothing matrix +% +% for subsequent voxels with the same session info, +% y = hpfilter(data, [], S, [169 169 169 173 173], I); +% +% y = clusters(1).indiv_timeseries(:, 1); +%[y, I, S] = hpfilter(y, 2, 120, [169 169 169 173 173]); +%y = clusters(1).indiv_timeseries(:, 1); +%[y] = hpfilter(y, [], S, [169 169 169 173 173], I); +% +% Regress out average activity in first 2 scans of each session (artifacts) +% y = hpfilter(raw, 2, 100, EXPT.FIR.nruns, [], 1:2); +% +% Another example set up first, then run on multi-voxel matrix: +% [y, pI, S, I] = hpfilter(data{1}(:,1), HPDESIGN.TR, HPDESIGN.HP, HPDESIGN.spersess, [], 1); +% tic, y = hpfilter(data{1}, [], S, HPDESIGN.spersess, pI); toc +% +% But if you just have one matrix, no need to set up, so this is just as +% fast: +% tic , [y, pI, S, I] = hpfilter(data{1}, HPDESIGN.TR, HPDESIGN.HP,HPDESIGN.spersess, [], 1);, toc +% +% +% tor wager +% Modified 5/12/06 to include dummy images for each session +% Modified April 07 to add sep. dummy covs for each session and add outlier +% option +% Also: y can now be a matrix; hpfilter operates on columns (faster than +% looping) + +function [y, pI, HP, incpt] = hpfilter(y, TR, HP, spersess, varargin) + incpt = []; + pI = []; + dummyscans = []; + dooutliers = 0; + + spersess = spersess(:)'; % enforce as row vector + + if ~isempty(varargin), pI = varargin{1}; end + if length(varargin) > 1, dummyscans = varargin{2}; end + if length(varargin) > 2, dooutliers = varargin{3}; end + + if isempty(pI) || ~ismatrix(pI) + % if we haven't entered this, then compute it from spersess + % spersess = [number of images in each run] vector + + incpt = intercept_model(spersess, dummyscans); + pI = incpt * pinv(incpt); + end + + n = size(y, 1); + if size(pI, 1) > n + disp('Warning: Intercept has more observations than data. spersess is wrong?') + if size(incpt, 1) > n + incpt = incpt(1:n, :); + end + + pI = pI(1:n, 1:n); + + tmp = cumsum(spersess); + while tmp(end) > length(y), + disp('Trying removal of a session.'); + spersess = spersess(1:end-1); + tmp = cumsum(spersess); + end + end + + % remove intercept + y = y - pI * y; + + if ~ismatrix(HP) || size(HP, 1) ~= size(y, 1) + % it's not already an SPM smoothing matrix + len = max(spersess); % max number of images in a session (run) + HP = use_spm_filter(TR, len, 'none', 'specify', HP); + end + + % starting and ending images for each session + st = cumsum([1 spersess(1:end-1)]); + en = cumsum(spersess); + + % high-pass filter here + for i = 1:length(st) + y(st(i):en(i), :) = HP(1:spersess(i), 1:spersess(i)) * y(st(i):en(i), :); + end + + % because intercept removal is applied before HP filtering, some residual + % effects of intercepts may remain, so remove these: + y = y - pI * y; + +if dooutliers + + n = size(y,1); + + % identify outliers and replace with timeseries mean + % (mean has no influence on model betas) + mady = repmat(mad(y), n, 1); + wh = (abs(y) > 4 * mady); + + subjectmeans = repmat(mean(y, 1), n, 1); + y(wh) = subjectmeans(wh); +end + + +end + + +% ISMATRIX: Returns 1 if the input matrix is 2+ dimensional, 0 if it is a scalar +% or vector. +% +% Usage ismat = ismatrix(X) +% +% RE Strauss, 5/19/00 + +function ismat = ismatrix(X) + [r,c] = size(X); + if (r>1 && c>1) + ismat = 1; + else + ismat = 0; + end + +end diff --git a/Data_processing_tools/htw_from_fit.m b/Data_processing_tools/htw_from_fit.m new file mode 100644 index 00000000..d1362106 --- /dev/null +++ b/Data_processing_tools/htw_from_fit.m @@ -0,0 +1,362 @@ +function [h, t, w, auc, w_times, halfh] = htw_from_fit(hrf, b, dt, varargin) + % [h, t, w, auc, w_times, halfh] = htw_from_fit(hrf, b, dt, [optional arguments]) + % + % estimates height, time to peak, width, and area under the curve of a fitted response + % + % tor wager, Sept 08 + % + % Inputs + % ----------------------------------------------------------------- + % hrf a matrix of columns that form a linear basis set for an + % event type in an fMRI design, t time points x k basis + % functions + % b betas associated with the columns of hrf, k x 1 + % dt the sampling resolution of hrf (in seconds) + % + % Optional: + % 'plot', make plot + % 'verbose', verbose output + % 'startval', followed by the starting value in sec within which to calculate peak + % 'endval', followed by the ending value in sec within which to calculate peak + % 'colors', followed by a cell array, for example, {'r'} or {[1 0 0]} + % + % This function is essentially the same as fir2htw2.m, but the main + % differences are: + % 1) It imposes a time constraint on the peak amplitude automatically, + % which is constrained to be between 4 seconds and 12 seconds + % (endval, which was hconstraint) by default. YOU MAY WANT TO CHANGE hconstraint + % depending on whether you're expecting delayed hemodynamic responses. + % This requires input of the sampling resolution (e.g., dt) + % + % 2) This function will automatically create fitted responses, given a + % basis set and betas (parameters). This is different from fir2htw2.m, + % which takes the fitted response as input. + % + % 3) The method for getting width (w) has been changed to work better + % for multi-modal (multi-peak) responses. + % + % Otherwise, the algorithm is the same. + % See Lindquist and Wager, 2007, for simulations that use a version of + % this method to estimate HRFs using different kinds of models. + % + % Notes on scaling: + % The scaling of the amplitude depends on the scaling of the hrf basis + % set, which (in SPM) depends on the time resolution. You should at + % least use an hrf basis set with the same scaling for all subjects in + % a group analysis. The amplitude of the fitted response is + % interpreted as the amplitude of the unit "impulse response," assuming + % that the hrf you enter here is the same as the impulse response + % function you used to create the design matrix. In SPM5, higher-res + % impulse response functions are normalized by their positive sum, and + % the higher the time resolution, the lower the amplitude of the unit + % HRF. (The scaling of regressors in the SPM design matrix isn't + % affected, because the hrf basis functions are convolved with a boxcar + % that also depends on the time resolution. The bottom line is that if + % all your subjects have the same scaling, you should be fine. And, + % secondly, the amplitudes that come out of this function reflect the + % scaling of the HRF you put in and are for an impulse response, NOT + % for an "event," and so the scaling here would not be expected to + % match up with the amplitudes on a plot that you'd get from a + % selective average time-course plot, unless you adjust by multiplying + % by the number of elements in SPMs "hi-res" onset boxcar to adjust. + % + % FOR example: + % With "zero-duration" events, an hrf input scaled to reflect "event response" amplitudes + % might look something like this: (***may not be exactly right because + % i think dt is in sec) + % figure; plot(conv(SPM.xBF.bf(:, 1), my_ons)) + % my_ons = ones(1, TR ./ SPM.xBF.dt .* SPM.Sess.U(1).dur(1)); + % + % If you have epochs and want "epoch response" amplitude, you have to consider that as well. + % If your durations are specified in TRs, and all durations are the + % same: + % TR = SPM.xY.RT; + % my_ons = ones(1, TR ./ SPM.xBF.dt .* SPM.Sess.U(1).dur(1)); + % + % minh = min height + % + % This version uses turning points (zero gradient) to find the largest + % "hump" in the data and the time it occurs. + % + % Examples: + % -------------------------------------------------------------------- + % Load and SPM mat file and use the basis set stored in that, and use + % that as the hrf. Generate some arbitrary combos to test different + % shapes: + % cd('my spm directory') + % load SPM + % [h, t, w, auc] = htw_from_fit(SPM.xBF.bf, [1 .4 .4]', SPM.xBF.dt, 'plot', 'verbose'); + % + % for i = 1:20 + % [h, t, w, auc] = htw_from_fit(SPM.xBF.bf, randn(3, 1), SPM.xBF.dt, 'plot'); pause(1.5); + % end + % + % -------------------------------------------------------------------- + % Generate an SPM basis set at a lower resolution, and try that: + % bf = spm_get_bf(struct('name', 'hrf (with time and dispersion derivatives)', 'length', 30, 'dt', 1)); + % for i = 1:20 + % [h, t, w, auc] = htw_from_fit(bf.bf, randn(3, 1), bf.dt, 'plot'); h, t, w, auc, pause(1.5) + % end + % -------------------------------------------------------------------- + + + + % -------------------------------------------------------- + % Variable input arguments + % -------------------------------------------------------- + + doplot = 0; + startval_sec = 4; + endval_sec = 12; + verbose = 0; + colors = {'ro-'}; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % reserved keywords + case 'plot', doplot = 1; + case 'verbose', verbose = 1; + + % optional inputs + case 'startval', startval_sec = varargin{i+1}; % the ending value in sec within which to calculate peak + case 'endval', endval_sec = varargin{i+1}; % the ending value in sec within which to calculate peak + + case 'colors', colors = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + % Set starting and ending values in samples (depending on dt, time res of hrf) + % -------------------------------------------------------- + + startval_samples = round(startval_sec ./ dt); + endval_samples = round(endval_sec ./ dt); + + [rows, cols] = size(hrf); + + if startval_samples < 1 + error('Illegal starting value in seconds.'); + end + + if endval_samples > rows + error('There do not seem to be enough samples in the HRF for the desired amplitude estimation window.') + end + + if verbose + fprintf('Number of basis functions: %3.0f\n', cols); + fprintf('Length of HRF in seconds : %3.1f seconds\n', rows .* dt); + + fprintf('Time resolution is : %3.4f seconds per sample in hrf\n', dt); + fprintf('Computing amplitude from max/min between : %3.2f to %3.2f seconds, %3.0f to %3.0f samples\n', startval_sec, endval_sec, startval_samples, endval_samples); + end + + % Make sure data format is correct, betas are column vector + + if size(b, 1) ~= size(hrf, 2) + warning('htw:BadInput', 'Betas should be column vectors...trying transpose...'); + b = b'; + end + + % get fitted response + % -------------------------------------------------------- + + fit = hrf * b; + + % for matrix + % ***still working + ntime = size(fit, 1); + ntests = size(fit, 2); + + isbad = all(fit - fit(ones(ntime, 1), :) == 0); + isbad = isbad | any(fit - fit(ones(size(fit, 1), 1), :) == NaN); + + % exit if empty + if all(isbad) %isempty(fit) || all(fit==0) || all(isnan(fit)) + h = NaN; t = h; w = h; w_times = [NaN NaN]; halfh = NaN; + warning('Empty, nan, or all zero values!') + return + end + + % exit if all vals are same + if all(fit == mean(fit)), h = NaN; t = h; w = h; w_times = [NaN NaN]; halfh = NaN; + warning('All fitted values are the same!') + return + end + + dat_for_amp_est = fit(startval_samples:endval_samples); + + % -------------------------------------------------------- + % find turning points in data (where approx. derivative is zero) + % -------------------------------------------------------- + tmp = diff(dat_for_amp_est); + turnpts = find([0; diff(sign(tmp))]); + + if isempty(turnpts) + % monotonic increasing or decreasing function, can't define + turnpts = [find(dat_for_amp_est == min(dat_for_amp_est)) find(dat_for_amp_est == max(dat_for_amp_est))]; + end + + + tpdat = abs(dat_for_amp_est(turnpts)); + + t = turnpts(tpdat == max(tpdat)); % time of max absolute turning point + t = t(1); + h = dat_for_amp_est(t); % the value at max + + % turnpts is in samples from startval_samples on; add to adjust to + % samples from time zero + t = t + startval_samples - 1; + + % -------------------------------------------------------- + % calculations for width + % -------------------------------------------------------- + halfh = .5 * h; % 1/2 the max to min distance + + + % First time point before t that crosses half-height (increasing for + % activations, decreasing for deactivations) + % ------------------------------------------ + crossings = diff(fit > halfh); + + eligible_times = find(crossings(1:t) == sign(h)); + if isempty(eligible_times) + w_times(1) = NaN; + else + w_times(1) = eligible_times(end); + + % interpolate to get more accurate estimate (relevant for low sampling res) + y1 = fit(w_times(1)); + y2 = fit(w_times(1) + 1); + m = y2 - y1; % slope, x reduces to 1 (x2 - x1 = 1) + + w_times(1) = w_times(1) + (halfh - y1) ./ m; % solve y = mx + b for x* given m; y1 = b + + end + + + % First time point after t that crosses half-height (decreasing for activations, increasing for deactivations) + % ------------------------------------------ + eligible_times = find(crossings(t:end) == -sign(h)); + if isempty(eligible_times) + w_times(1) = NaN; + else + w_times(2) = t + eligible_times(1); + + + % interpolate to get more accurate estimate (relevant for low sampling + % res) + y1 = fit(w_times(2)); + y2 = fit(w_times(2) + 1); + m = y2 - y1; % slope, x reduces to 1 (x2 - x1 = 1) + w_times(2) = w_times(2) + (halfh - y1) ./ m; % solve y = mx + b for x* given m; y1 = b + end + + w = w_times(2) - w_times(1); % width in elements + + + + % Finish up and plot + % -------------------------------------------------------- + + % Add area under curve measure + auc = sum(dat_for_amp_est); + + + % re-express times in sampling units (seconds or TRs, depending on what dt + % represents) + + t = t .* dt; + w_times = w_times * dt; + w = w * dt; + + + if doplot + minh = 0; %mean(fit(1:2)); + + create_figure('HTW Estimates', 1, 2); + plot(hrf); title('Basis set'); + + ylims = get(gca, 'YLim'); + h1 = drawbox(startval_samples, endval_samples - startval_samples, ylims(1), ylims(2) - ylims(1), [.9 .9 .9]); + set(h1, 'EdgeColor', 'none'); + text(startval_samples, ylims(2) - .05 * (ylims(2) - ylims(1)), 'Eligible area for peak', 'FontSize', 16); + + plot(hrf, 'LineWidth', 2); + xlabel('Samples'); + + + secs = (1:rows) .* dt; + + subplot(1, 2, 2); + plot(secs, fit, 'k', 'LineWidth', 3); + + ylims = get(gca, 'YLim'); + h1 = drawbox(startval_samples.* dt, endval_samples.* dt - startval_samples.* dt, ylims(1), ylims(2) - ylims(1), [.9 .9 .9]); + set(h1, 'EdgeColor', 'none'); + + plot(secs, fit, 'k', 'LineWidth', 3); + plot([startval_samples : endval_samples] * dt, dat_for_amp_est, 'g', 'LineWidth', 1); + + title('Fitted response and summary statistics') + + try + + hold on + h1 = arrow([t minh],[t h+minh],'Length',12); % height arrow + h2 = arrow([0 h+minh],[t h+minh],'Length',12); % delay arrow + h3 = arrow([w_times(1) halfh+minh],[w_times(2) halfh+minh],'Length',12); + h4 = arrow([w_times(2) halfh+minh],[w_times(1) halfh+minh],'Length',12); + + h5 = text(t + .1 * w, halfh+minh + .4*(halfh+minh),'h','FontSize',16,'FontWeight','bold'); + h6 = text(t - .5 * w, h+minh - .2*(halfh+minh),'t','FontSize',16,'FontWeight','bold'); + h7 = text(t - .5 * w, halfh+minh - .2*(halfh+minh),'w','FontSize',16,'FontWeight','bold'); + + set(h1,'Color',colors{1}(1)) + set(h2,'Color',colors{1}(1)) + set(h3,'Color',colors{1}(1)) + set(h4,'Color',colors{1}(1)) + set(h5,'Color',colors{1}(1)) + set(h6,'Color',colors{1}(1)) + set(h7,'Color',colors{1}(1)) + + catch + warning('Error drawing arrows!') + end + end + + xlabel('Sec (Samples * dt)'); + + drawnow + + + +end + + +% OLD WAY OF GETTING W +% % % first half +% % x2 = max(wh(1),1); % first above halfh +% % x1 = max(wh(1)-1,1); % first above-half - 1 +% % y2 = dat_for_amp_est(x2); +% % y1 = dat_for_amp_est(x1); +% % m = y2 - y1; % slope, x reduces to 1 (x2 - x1 = 1) +% % if m == 0 +% % w_times(1) = x1; % exact match +% % else +% % w_times(1) = x1 + (halfh - y1) ./ m; % solve y = mx + b for x* given m; y1 = b +% % end +% % +% % % second half +% % x1 = min(wh(end),hconstraint); % last one above halfh +% % x2 = min(wh(end)+1,hconstraint); % + 1 +% % y2 = dat_for_amp_est(x2); y1 = dat_for_amp_est(x1); +% % m = y2 - y1; % slope, x reduces to 1 (x2 - x1 = 1) +% % if m == 0 +% % w_times(2) = x1; % exact match +% % else +% % w_times(2) = x1 + (halfh - y1) ./ m; % solve y = mx + b for x* given m; y1 = b +% % end diff --git a/Data_processing_tools/luisFilter.m b/Data_processing_tools/luisFilter.m new file mode 100755 index 00000000..5a7edbe5 --- /dev/null +++ b/Data_processing_tools/luisFilter.m @@ -0,0 +1,91 @@ +function outdata = luisFilter(data, TR, cutoff, verbose) +% function outdata = luisFilter(data, TR, cutoff [, verbose]) +% +% Luis Hernandez. University of Michigan. Last Edit 9/13/01 +% +% This is a low pass FIR filter using the Parks Mclellan design algorithm +% The phase introduced by the filter is linear and is un-done by +% filtering the data again, backwards +% if you want to see what it does to the data, use verbose=1 +% if just want to filter the data, don't use the argument at all. +% + % close all + + %%%%%% These are the important lines of the code %%%%%%%%%%%%% + cutoff = cutoff / (2*TR) + b = remez(10, [0 cutoff-0.05 cutoff+0.05 1], [1 1 0 0]); + outdata = filtfilt(b,1, data); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if (nargin==4) + freqz(b) + figure + + % Read the data from file +% data = load(data_file); +% data = data(:,2); + len = size(data, 1); + time = [1:len] * TR; + subplot(2,1,1) , plot(time, abs(data)); + xlabel('sec.') + + % Fourier Transform the data + fdata = fftshift(fft(data)); + scalefactor = max(abs(fdata)); + + %frequency range: + f = [1:len]'; + f = (f - len/2 ) * 1/(TR * len); + whos + % Plotting the data before the filter: + + subplot(2,1,1) , hold on,plot(time, abs(data)) + subplot(2,1,2), plot(f, abs(fdata)); + subplot(2,1,2), axis([0 1/(2*TR) 0 scalefactor]); + xlabel('Hz.'); + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Make Fourier Domain Filter by hand + %ffilt = ones(len, 1); + %co = len/2 + cutoff * (TR * len); + %ffilt(co:end) = exp(-((f(co:end) - cutoff).^2) / 0.001 ); + %co = len/2 - cutoff * (TR * len); + %ffilt(1:co) = exp( -((f(1:co) + cutoff).^2) / 0.001 ); + + %subplot(2,1,2) , hold on , plot(f, ffilt*scalefactor/2 ,'g'); + %whos + + % Apply the filter to the data and IFT it + %fdata = fdata .* ffilt; + %data = ifft(fdata); + + + + + % Fourier Transform the data + outfdata = fftshift(fft(outdata)); + + + % Let's look at the impulse response of the filter: + impulse= zeros(size(data)); + + impulse(size(data,1)/2) = 0.1 *scalefactor; + impulse_response = filtfilt(b,1,impulse); + + freq_response = fftshift(fft(impulse_response)); + freq_impulse = fftshift(fft(impulse)); + + freq_gain = scalefactor/2 + 20*log10( abs(freq_response).^2 ./ abs(freq_impulse) ) ; + + %Plotting the reponse of the filter + subplot(2,1,1) , hold on,plot(time, abs(outdata), 'r'), title ('Time Domain'), legend('Before', 'After') + subplot(2,1,2), hold on, plot(f, abs(outfdata), 'r'), title ('Frequency Domain'); + + subplot(2,1,2), hold on, plot(f, freq_gain, 'g'),legend('Before','After','Filter Response'); + + %figure; + %plot(f, freq_gain, 'g'); +end + +return diff --git a/Data_processing_tools/nuisance_cov_estimates.m b/Data_processing_tools/nuisance_cov_estimates.m new file mode 100644 index 00000000..a41bbe4d --- /dev/null +++ b/Data_processing_tools/nuisance_cov_estimates.m @@ -0,0 +1,388 @@ +% nuisance_cov_estimates(X, images, SETUP, varargin) +% +% 1) Estimates F-map for model parameters of interest +% Writes to disk: +% 'F_cols_of_interest.img' 'p_cols_of_interest.img' (3-D images) +% 'resid_full_model.img' (a 4-D image) +% +% 2) Locates voxels whose activity is unrelated to the model +% 3) Extracts principal components from these voxels for use as covariates in subsequent +% models +% 4) Note: invalidates statistical inference in subsequent models based on +% these data, but may improve predictive accuracy and single-trial model. +% +% Defining the SETUP structure with inputs: +% ------------------------------------------------------------------------ +% % SETUP.(fields) +% .wh_of_interest vector of which columns of X matrix are of interest +% % columns of interest in X matrix; tests var explained by these with F-test +% +% .mask name of mask image +% +% .TR repetition time of volume (image) acquisition +% .HPlength high-pass filter length, in s +% .scans_per_session vector of # volumes in each run, e.g., [128 128 128 +% 128 128] +% .dummyscans indices of images in each run that will be modeled +% with separate dummy variables +% +% .startslice start at slice #... +% +% SETUP optional inputs: +% Also: 'nopreproc' to skip preprocessing (i.e., for trial-level inputs) +% +% Tor Wager, Nov 07 + +function nuisance_cov_estimates(X, images, SETUP, varargin) + max_to_save = 10; + + % --------------------------------------------------------------------- + % Set up preprocessing + % To skip, enter 'nopreproc' as var. arg. + % --------------------------------------------------------------------- + + [preprochandle, SETUP] = filter_setup(SETUP, X, varargin{:}); + + % --------------------------------------------------------------------- + % Set up F-test + % F-test: set up anonymous function for one voxel + % --------------------------------------------------------------------- + + [fhandle, SETUP] = f_test_setup(X, SETUP, images, preprochandle); + + save nuisance_SETUP SETUP + + % --------------------------------------------------------------------- + % Run preprocessing and analysis + % --------------------------------------------------------------------- + print_banner('Finding voxels that are not task-related.'); + + if ~isfield(SETUP, 'startslice') || isempty(SETUP.startslice), SETUP.startslice = 1; end + + [v1, v2, v3] = image_eval_function(images, fhandle, 'mask', SETUP.mask, 'preprochandle', preprochandle, 'outnames', SETUP.names, 'start', SETUP.startslice); + v1; v2; v3; + + % save results + fprintf('Printing results in spm .ps file'); + spm_image('init', 'F_cols_of_interest.img'); + spm_orthviews_name_axis('F values for X of interest', 1); + spm_print; + + % --------------------------------------------------------------------- + % Get voxels that are not task-responsive + % --------------------------------------------------------------------- + [yy, xyz, wh_not_modeled, volInfo] = get_non_task_responsive_voxels(images); + + + % --------------------------------------------------------------------- + % Get principal components of these voxels + % and save them. + % --------------------------------------------------------------------- + + [noise_components, pc, latent, num_to_save] = get_pcs(yy, max_to_save); + + NOISE = struct('xyz', xyz, 'images', images, 'pc', pc, 'latent', latent, 'wh_not_modeled', wh_not_modeled, 'noise_components', noise_components, 'num_to_save', num_to_save); + + % rsquare for each predictor + % -------------------------------------------------------------------- + for i = 1:size(X, 2) + [b, bint, r, rint, stats] = regress(X(:,i), [noise_components ones(size(X, 1), 1)]); + NOISE.descrip = 'Regression of noise components on columns of design matrix:'; + NOISE.noise_rsquare_for_each_predictor(i) = stats(1); + NOISE.noise_pval_for_each_predictor(i) = stats(3); + end + + save nuisance_SETUP -append NOISE + + % spatial maps of noise components + % -------------------------------------------------------------------- + spatial_map_names = []; + for i = 1:num_to_save + + dat = zeros(volInfo.n_inmask, 1); + dat(wh_not_modeled) = pc(:,i); + name = sprintf('noise_maps.img, %s', num2str(i)); + iimg_reconstruct_3dvol(dat, volInfo, 'outname', name, 'descrip', 'Created by nuisance_cov_estimates.m' ); + fprintf('Writing: %s\n', name); + spatial_map_names = strvcat(spatial_map_names, name); + end + + NOISE.spatial_map_names = spatial_map_names; + save nuisance_SETUP -append NOISE + + % write adjusted (denoised) images: residuals from nuisance params + SETUP = remove_noise_components(SETUP, NOISE); + save nuisance_SETUP -append SETUP + + + % show figures of results + % -------------------------------------------------------------------- + + X = SETUP.data.X; + + create_figure('Eigenvariate Plot', NOISE.num_to_save, 1) + for i = 1:NOISE.num_to_save, subplot(NOISE.num_to_save, 1, i); plot(NOISE.noise_components(:,i)); title(['Comp' num2str(i)]); axis auto; axis off; end + + % save results + scn_export_papersetup(600); + spm_print('Eigenvariate Plot'); + + create_figure('Rsquare'); bar(NOISE.noise_rsquare_for_each_predictor); set(gca, 'XTick', 0:size(X, 2) + 1) + title('Var explained by nuisance covs in each column of X'); + + % save results + scn_export_papersetup(600); + spm_print('Rsquare'); + + spm_check_registration(NOISE.spatial_map_names); + + global st + + for i = 1:NOISE.num_to_save + spm_orthviews_name_axis(['Comp' num2str(i)], i); + + vv = spm_read_vols(spm_vol(NOISE.spatial_map_names(i,:))); + vv = vv(:); mn = mean(vv) - 3 * std(vv); mx = mean(vv) + 3 * std(vv); + spm_orthviews('Window', i, [mn mx]) + + axes(st.vols{i}.ax{2}.ax) + title(sprintf('%3.4f %3.4f', mn, mx), 'FontSize', 12); + + end + + % save results + spm_print; +end % main function + + + + + +% -------------------------------------------------------------------- +% -------------------------------------------------------------------- + +% sub-functions + +% -------------------------------------------------------------------- +% -------------------------------------------------------------------- + + +% --------------------------------------------------------------------- +% STEP 1 +% --------------------------------------------------------------------- +function [fhandle, SETUP] = f_test_setup(X, SETUP, images, preprochandle) + % reduced model + Xred = X; Xred(:,SETUP.wh_of_interest) = []; + + % computational work + px = pinv(X); pxred = pinv(Xred); + + fhandle = @(y) F_test_full_vs_red(y, X, Xred, px, pxred); + + try + disp('Testing analysis function.'); + fhandle(randn(size(images, 1), 1)); + catch + disp('Error in function eval handle: Check function, inputs, and sizes.'); + rethrow(lasterr); + end + + + SETUP.names = {'F_cols_of_interest' 'p_cols_of_interest' 'resid_full_model'}; + + SETUP.preprochandle = preprochandle; + SETUP.fhandle = fhandle; + + SETUP.data.descrip = 'Data in noise component estimation function'; + SETUP.data.X = X; + SETUP.data.Xred = Xred; + SETUP.data.images = images; +end + + +% --------------------------------------------------------------------- +% STEP 2 +% --------------------------------------------------------------------- +function [yy, xyz, wh_not_modeled, volInfo] = get_non_task_responsive_voxels(images) + name = 'p_cols_of_interest.img'; + [volInfo, pvals] = iimg_read_img(name, 2); + pvals = pvals(volInfo.image_indx); + wh_not_modeled = pvals > .05; + n = sum(wh_not_modeled); + + iimg_reconstruct_3dvol(wh_not_modeled, volInfo, 'outname', 'mask_of_non_task_related.img'); + spm_image('init', 'mask_of_non_task_related.img'); + spm_orthviews_name_axis('Non-task related voxels', 1); + spm_print; + + fprintf('Non-zero p-values: %3.0f\n Voxels with little variability explained by model: %3.0f \n', volInfo.n_inmask, n); + if n < 1 + disp('Not enough voxels. Exiting.'); + end + + % get voxel coordinates + xyz = volInfo.xyzlist(wh_not_modeled,:); + xyz(:,end+1) = 1; xyz = xyz'; + + % get data from residual images: residualized wrt task and other nuisance + % covariates. this makes pcs non-redundant with other filtering and + % nuisance covs. + + fprintf('Counting residual images...'); + tic + n = scn_num_volumes('resid_full_model.img'); + resid_imgs = expand_4d_filenames('resid_full_model.img', n); + fprintf('Found %3.0f, done in %3.0f sec\n', n, toc); + + fprintf('Extracting data for non-responsive voxels...'); + tic + yy = spm_get_data(resid_imgs, xyz); + fprintf('%3.0f sec\n', toc); +end + + +% --------------------------------------------------------------------- +% STEP 3 +% --------------------------------------------------------------------- +function [noise_components, pc, latent, num_to_save] = get_pcs(yy, max_to_save) + print_banner('Getting principal components from non-task-related voxels.'); + + [pc, score, latent] = princomp(yy, 'econ'); + + % Eigenvalue plot + create_figure('Eigenvalue Plot'), bar(latent); + xlabel('Components'), ylabel('Variance') + scn_export_papersetup(500); + set(gca, 'XLim', [0 50]); + drawnow + spm_print('Eigenvalue Plot') + + num_to_save = min(max_to_save, sum(latent > 1)); + if num_to_save == 0 + disp('Uh-oh! No components to save. data problem.'); + keyboard + end + + num_to_save = max(num_to_save, 1); + + fprintf('Saving %3.0f components in NOISE struct in nuisance_SETUP.mat\n', num_to_save); + noise_components = score(:,1:num_to_save); +end + + +% --------------------------------------------------------------------- +% STEP 4 +% --------------------------------------------------------------------- +function SETUP = remove_noise_components(SETUP, NOISE) + % write adjusted (denoised) images: residuals from nuisance params + print_banner('Creating denoised_images.img'); + + X = NOISE.noise_components; + + % add covariates from original X + covs = SETUP.data.X; covs(:,SETUP.wh_of_interest) = []; + X = [X covs]; + X(:,end+1) = 1; % may not be necessary, if intercepts already in model, but shouldn't hurt. + Xred = X(:,end); + px = pinv(X); pxred = pinv(Xred); + + fhandle = @(y) F_test_full_vs_red(y, X, Xred, px, pxred); + SETUP.denoised_names = {'F_nuisance_covs' 'p_nuisance_covs' 'denoised_images'}; + [v1, v2, v3] = image_eval_function(SETUP.data.images, fhandle, 'mask', SETUP.mask, 'preprochandle', SETUP.preprochandle, 'outnames', SETUP.denoised_names, 'start', SETUP.startslice); + + SETUP.denoising_fhandle = fhandle; +end + + + +% --------------------------------------------------------------------- +% Subfcn: set up +% --------------------------------------------------------------------- +% Set up preprocessing +function [preprochandle, SETUP] = filter_setup(SETUP, X, varargin) + + preprochandle = []; + wh_elim = []; + hpflag = 1; % only does it if requested, though + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'custompreproc' + preprochandle = varargin{i + 1}; % e.g., 'custompreproc', @(data) scale(data) for z=scores; + + hpflag = 0; + SETUP.TR = NaN; + SETUP.HPlength = []; + SETUP.dummyscans = []; + wh_elim = i; + + case {'nopreproc'} + hpflag = 0; + SETUP.preproc = 0; + SETUP.TR = NaN; + SETUP.HPlength = []; + SETUP.dummyscans = []; + wh_elim = i; + + % We need to allow mediation SETUPions here, so eliminate this from list and do not error check here. + %otherwise, warning(['Unknown input string SETUPion:' varargin{i}]); + end + end + end + + varargin(wh_elim) = []; + + % required args + N = {'TR', 'mask', 'scans_per_session', 'preproc', 'HPlength', 'dummyscans', 'startslice'}; + %N = fieldnames(SETUP); + for i = 1:length(N) + if ~isfield(SETUP, N{i}) || isempty(SETUP.(N{i})) + switch N{i} + case {'TR', 'mask', 'scans_per_session', 'preproc'} + error(['Enter SETUP.' N{i}]); + + case 'HPlength' + SETUP.(N{i}) = []; + + case 'dummyscans' + SETUP.(N{i}) = 1:2; + + case 'startslice' + SETUP.startslice = 1; + + otherwise + disp('Warning! Unrecognized field in SETUPions structure SETUP.'); + end + end + end + + if SETUP.preproc && hpflag + [tmp, I, S] = hpfilter(X(:,1), SETUP.TR, SETUP.HPlength, SETUP.scans_per_session, SETUP.dummyscans); % creates intercept and smoothing matrices + + preprochandle = @(Y) hpfilter(Y, [], S, SETUP.scans_per_session, I); % function handle with embedded fixed inputs + + % test preprocessing + try + disp('Testing preprocessing function.'); + test = preprochandle(X(:,1)); + catch + disp('Testing of preprocessing failed! User should diagnose this error.'); + rethrow(lasterr); + end + + if any(isnan(test)) + disp('Warning! Some preprocessed data values in test are NaN. Problem with model or preproc function?'); + end + end +end + + + +function print_banner(str) + fprintf('\n---------------------------------------------------------------------\n') + fprintf('%s', str) + fprintf('\n---------------------------------------------------------------------\n') +end + diff --git a/Data_processing_tools/physio_data.m b/Data_processing_tools/physio_data.m new file mode 100644 index 00000000..cf8df9d6 --- /dev/null +++ b/Data_processing_tools/physio_data.m @@ -0,0 +1,480 @@ +classdef physio_data < design_matrix + + % physio_data: data class for creating a physiological data object + % + %-------------------------------------------------------------------------- + % This object is used to represent physiological data. The object uses the + % design_matrix() class to for the data set and has additional fields for the + % sampling frequency. + % + %-------------------------------------------------------------------------- + % Inputs: + % --------------------------------------------------------------------- + % dat : M x N numeric matrix containing Observations and Variables + % + % varname : Cell array containing variable names. Must + % match number of column in data matrix + % + % samplefreq : Sampling frequency + % + %-------------------------------------------------------------------------- + % Current Methods for physio_data (inherits from design_matrix class too) + %-------------------------------------------------------------------------- + % + % calc_rate : Calculate Rate of peaks + % downsample : downsample dataset + % filter : Filter data + % peakdetect : Find peaks in data + % physio_data : class constructor + % plot : Plot data + % save : Save object as .mat file + % smooth : Apply moving average to data + % + %-------------------------------------------------------------------------- + % Examples: + % --------------------------------------------------------------------- + % pulse = physio_data(data(:,2),{'pulse'},settings.fs); + % + % Also see PhysioData_Tutorial.m in Examples + % ------------------------------------------------------------------------- + % Author and copyright information: + % ------------------------------------------------------------------------- + % Copyright (C) 2014 Luke Chang + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + % ------------------------------------------------------------------------- + + % NOTES: + % add ability to import .acq files + % add ability to quickly check all peaks? + + properties + % inherits properties from design_matrix + samplefreq = []; + end + + methods + function obj = physio_data(dat, varname, samplefreq, varargin) + class constructor % Initialize instance of comp_model + + if(nargin > 2) + + %Add Data matrix + try + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + if(length(varname) ~= size(dat,2) || ~iscell(varname)); + error('Make sure the number of variable names corresponds to number of data columns.') + end + obj.dat = dat; + obj.varname = varname; + catch err + error('Make sure input variable names are in a cell array with length equal to number of data columns and data is a matrix.') + end + + %Add variable names + obj.varname = varname; + + %Add Sampling Frequency + obj.samplefreq = samplefreq; + + elseif(nargin > 1) + try + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + if(length(varname) ~= size(dat,2) || ~iscell(varname)); + error('Make sure the number of variable names corresponds to number of data columns.') + end + obj.dat = dat; + obj.varname = varname; + catch err + error('Make sure input variable names are in a cell array with length equal to number of data columns and data is a matrix.') + end + obj.varname = varname; + elseif(nargin > 0) + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + obj.dat = dat; + else % if nothing initialize empty object + return + end + end + + function obj = save(obj,varargin) + + % obj = save(obj) + % + % ------------------------------------------------------------------------- + % This function saves physio_data class as a .mat file to fname, + % or user specified path. fullfile(fpath,obj.model) + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % fpath : Specify file name path otherwise uses obj.fname + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % data.save('~/MATLAB/HR_Data.mat') + % + % data.save('~/MATLAB') + % + % ------------------------------------------------------------------------- + + if nargin >= 1 %use supplied file name + if ischar(varargin{1}) + save(varargin{1}, 'obj') + end + + elseif ~isempty(obj.fname) %use obj.fname + save(obj.fname, 'obj') + else + save([obj.varname{1} '_Data.mat'], 'obj') + end + end + + function f1 = plot(obj,varargin) + + % obj = plot(obj) + % + % ------------------------------------------------------------------------- + % This function plots physio_data.dat. + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % fpath : Specify file name path otherwise uses obj.fname + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % plot(data) + % + % ------------------------------------------------------------------------- + + figure; + f1 = plot(obj.dat, 'LineWidth', 2); + + end + + function obj = filter(obj,varargin) + + % obj = filter(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function applies a zero-order butterworth filter to + % obj.dat. + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % 'lowpass' : Followed by lowpass filter cutoff + % 'highpass' : Followed by highpass filter cutoff + % 'bandpass' : Followed by vector of low and high + % cutoff frequences (e.g., [.05, 2] + % 'order' : Follwed by filter order (default 1) + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % data = data.filter('lowpass', .05) + % + % data= data.filter('bandpass', [.05, 2]) + % + % ------------------------------------------------------------------------- + + % Defaults + doBandPass = 1; + doLowPass = 0; + doHighPass = 0; + cutoff = [0.015, 2]; + order = 1; + + % Parse input + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('lowpass',varargin{varg}) + cutoff = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + doBandPass = 0; + doLowPass = 1; + doHighPass = 0; + elseif strcmpi('highpass',varargin{varg}) + cutoff = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + doBandPass = 0; + doLowPass = 0; + doHighPass = 1; + elseif strcmpi('bandpass',varargin{varg}) + cutoff = varargin{varg + 1}; + if length(cutoff) ~= 2 + error('Please include a valid vector of low and high cutoff frequencies') + end + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('order',varargin{varg}) + order = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + end + end + end + + nyq = obj.samplefreq / 2; + + if doHighPass + [b, a]=butter(order, cutoff / nyq, 'high'); + obj.dat = filtfilt(b,a,obj.dat); % zero order + elseif doLowPass + [b, a]=butter(order, cutoff / nyq, 'low'); + obj.dat = filtfilt(b,a,obj.dat); % zero order + elseif doBandPass + [b, a]=butter(order, cutoff / nyq); + obj.dat = filtfilt(b,a,obj.dat); % zero order + end + end + + function obj = peakdetect(obj,varargin) + + % obj = peakdetect(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function applies matlab's peak detection algorithm to + % data set and adds a vector of the identified peaks to the end of obj.dat + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % 'minPeakHeight' : Followed by minimum peak height for + % peaks (default: 1 STD above mean) + % + % 'plot' : Plots data with peaks highlighted + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % data = peakdetect(data) + % data = peakdetect(data,'MinPeakHeight', .5) %Specify minimum peak threshold + % data = data.peakdetect('plot') + % ------------------------------------------------------------------------- + + % NOTES: + % Could add variable arguments in to findpeaks() + + % Defaults + doMinPeakHeight = 0; + doPlot = 0; + % minPeakHeight = mean(obj.dat) + std(obj.dat); % 1 std above mean + + % Parse input + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('minPeakHeight',varargin{varg}) + minPeakHeight = varargin{varg + 1}; + doMinPeakHeight = 1; + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('plot',varargin{varg}) + doPlot = 1; + varargin{varg} = []; + end + end + end + + % Use Matlab's findpeaks algorithm to find peaks + if ~doMinPeakHeight + [pks,locs] = findpeaks(obj.dat); %returns the indices of the local peaks with no threshold. + else + [pks,locs] = findpeaks(obj.dat, 'MinPeakHeight',minPeakHeight); %returns the indices of the local peaks exceeding 1 std of mean of filtered input. + end + + % Add Vector of peaks to end of obj.dat + obj.varname = horzcat(obj.varname, 'Peaks'); + obj.dat(:,end + 1) = zeros(size(obj.dat)); + obj.dat(locs, end) = 1; + + if doPlot + figure; + sample = 1:length(obj.dat); + plot(sample,obj.dat(:, 1:end - 1),sample(locs), pks,'rv','MarkerFaceColor','r'); + xlabel('sample'); ylabel('pulse') + title('Peak Detection') + end + end + + function obj = smooth(obj,varargin) + + % obj = smooth(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function uses matlab's smoothing algorithm to + % smooth obj.dat with a moving average of X span. + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % 'span' : Followed by number of samples to span (default: 5) + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % data = smooth(data) + % data = smooth(data,'span', 100) %Specify the span of the moving average window + % ------------------------------------------------------------------------- + + % NOTES: + % Could add variable arguments in to findpeaks() + + % Defaults + doSpan = 0; + + % Parse input + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('span',varargin{varg}) + span = varargin{varg + 1}; + doSpan = 1; + varargin{varg} = []; varargin{varg + 1} = []; + end + end + end + + % Use Matlab's findpeaks algorithm to find peaks + if ~doSpan + obj.dat = smooth(obj.dat); %span 5 + else + obj.dat = smooth(obj.dat, span); % custom span + end + end + + function obj = calc_rate(obj,varargin) + + % obj = calc_rate(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function calculates the rate of the Peaks calculated with a moving average + % window length of n samples. + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % 'WindowLength' : Followed by number of samples of window length + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % data = calc_rate('lowpass', .05) + % + % ------------------------------------------------------------------------- + + % Defaults + doWindowLen = 0; + + % Parse input + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('WindowLength',varargin{varg}) + windowlen = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + doWindowLen = 1; + end + end + end + + % Check if Peaks is in object + if ~any(strcmpi(obj.varname,'Peaks')) + error('The variable ''Peaks'' is not in object. Please run obj.peaks() before running this function.') + end + + obj.dat(:,end + 1) = nan(size(obj,1),1); % Add rate to end of obj.dat + obj.varname = horzcat(obj.varname,'Rate'); + halfwindow = floor(windowlen/2) * obj.samplefreq; + i = halfwindow; + while i + windowlen * obj.samplefreq <= size(obj,1) + obj.dat(i,end) = sum(obj.dat((i - halfwindow + 1):(i + halfwindow),strcmpi(obj.varname,'Peaks')) / windowlen) * 60; + i = i + 1; + end + end + + function obj = downsample(obj,varargin) + + % obj = downsample(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function downsamples the data. Can downsample by a + % factor of N (selects data every N samples) with 'factor' flag. Alternatively, + % can 'average' across samples within a grid of time. This is + % useful for averaging over TRs for a more reliable estimate when integrating + % with imaging data. + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % 'Factor' : Followed by factor to downsample the data. + % 'Average' : Followed by window size to average data + % over (e.g., tr for averaging over each tr) + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % data = data.downsample('Factor', 3) %downsample by a factor of 3 + % + % data = data.downsample('Average', 1.3) %average over 1.3s TRs + % + % ------------------------------------------------------------------------- + + % Defaults + doFactor = 0; + doAverage = 0; + + % Parse input + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('Factor',varargin{varg}) + f = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + doFactor = 1; + elseif strcmpi('Average',varargin{varg}) + av = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + doAverage = 1; + end + end + end + + % Downsample by a factor of 'f' + if doFactor + obj.dat = downsample(obj.dat, f); + obj.samplefreq = obj.samplefreq / f; + end + + % Average over window size of 'av' seconds + if doAverage + avsamp = av * obj.samplefreq; % average samples + nTR = floor(size(obj,1)/avsamp); %number of TRs in Dataset + i = 1; + start = 1; + stop = start + avsamp; + while i <= nTR + ds_dat(i,:) = nanmean(obj.dat(start: stop,:)); + i = i + 1; + start = start + avsamp; + stop = stop + avsamp; + end + obj.dat = ds_dat; % Update data with averaged data + obj.samplefreq = 1 / av; % New sampling frequency + end + end + + end %methods +end %classdef + diff --git a/Data_processing_tools/resample_scnlab.m b/Data_processing_tools/resample_scnlab.m new file mode 100644 index 00000000..69210e0d --- /dev/null +++ b/Data_processing_tools/resample_scnlab.m @@ -0,0 +1,100 @@ +function [y, x] = resample_scnlab(data, p, q, varargin) + % [y, x] = resample_scnlab(data, p, q) + % OR + % [y, x] = resample_scnlab(data, p, q, origHz, targetHz) +% +% Resample : Uses matlab's resample.m, but pads ends to avoid edge +% artifacts +% +% %Y = RESAMPLE(X,P,Q) resamples the sequence in vector X at P/Q times +% the original sample rate using a polyphase implementation. Y is P/Q +% times the length of X (or the ceiling of this if P/Q is not an integer). +% P and Q must be positive integers. +% +% Other features: +% Returns x values for resampled data in original index scale +% +% IF two additional args are entered (origHz and targetHz), +% p and q are determined automatically, based on your desired sampling rate +% (targetHz) +% +% Example: +% create_figure('test'); plot(y); [y2, x] = resample_scnlab(y, 1, 5); +% plot(x, y2, 'r'); +% +% Example: Use target Hz...take 100 Hz vector and resample at 20 Hz +% create_figure('test'); plot(y); [y2, x] = resample_scnlab(y, [], [], 100, 20); plot(x, y2, 'r'); + +if ~isempty(varargin) + if length(varargin) < 2, error('Enter both origHz and targetHz or neither.'); end + if ~isempty(p) || ~isempty(q), disp('Warning: p and q values entered will not be used. Using origHz and targetHz. Enter empty p and q to avoid this warning.'); end + + origHz = varargin{1}; + targetHz = varargin{2}; + + p = 1; q = origHz / targetHz; + + while (abs(p - round(p)) > eps || abs(q - round(q)) > eps) + p = p * 10, q = q * 10; + end +end + +% origHz = 1000; targetHz = 50; +% origHz / targetHz +% p = 1, q = origHz / targetHz +% q = origHz / targetHz +% origHz = 925; targetHz = 50; +% p = 1, q = origHz / targetHz +% p = p * 10, q = q* 10 + + +y = []; +x = []; + +if isempty(data), disp('No data to resample.'); return, end + +[nobs,ncols] = size(data); + +if nobs == 1 && ncols > 1 + disp('Transposing data...seems like you''ve entered a row vector. Pass in column vectors.') + data = data'; + + [nobs,ncols] = size(data); +end + +nshift = ceil(nobs ./ 10); + +nshiftds = ceil(nshift * p / q); % downsample + + +% filter +% ------------------------------------------- + + +if nobs < nshift, error('Not enough observations to support kernel.'); end +y = zeros(ceil(nobs * p / q) , ncols); + + +for i = 1:ncols + + % pad data at end to avoid edge artifacts + padstart = data(nshift:-1:1, i); + padend = data(end:-1:end-nshift, i); + + tmpy = resample([padstart; data(:,i); padend],p, q); + + + + y(:,i) = tmpy(nshiftds+1:nshiftds + ceil(nobs * p / q)); + +end + +x = linspace(1, nobs, (nobs) * p / q + 1)'; + +% quick fix +if length(x) > nobs + x = x(1:nobs); +end + + +return diff --git a/Data_processing_tools/scale.m b/Data_processing_tools/scale.m new file mode 100755 index 00000000..5c01d8e1 --- /dev/null +++ b/Data_processing_tools/scale.m @@ -0,0 +1,56 @@ +function xout = scale(x,varargin) +% x = scale(x,[just center]) +% +% centers and scales column vectors +% to mean 0 and st. deviation 1 + +%Programmer's notes: +% 2/17/14 - Luke: rewrote using vectorization instead of loop -MUCH FASTER! +% Still removes nans like original function +% 6/13/14 - Wani: after Luke's edit, mean-centering didn't work right. +% Fixed it. + +xout = NaN .* zeros(size(x)); + +[nanvec x_no_nan] = nanremove(x); %removes an entire row if any nan + +% no data. return original input. +if isempty(x_no_nan) + xout = x; + return +end + +x_no_nan = x_no_nan - repmat(mean(x_no_nan),size(x_no_nan,1),1); %remove mean + +if isempty(varargin) || varargin{1} == 0 + x_no_nan = bsxfun(@rdivide,x_no_nan,max(eps,std(x_no_nan))); %divide by std +end + +xout(~nanvec,:) = x_no_nan; + + +%TOR'S OLD SCALE USING LOOP - replaced by LC: 2/27/14 +% xout = NaN .* zeros(size(x)); +% +% for i = 1:size(x,2) +% +% [nanvec x_no_nan] = nanremove(x(:,i)); +% +% if isempty(x_no_nan) +% % no data. return original input. +% xout = x; +% return +% end +% +% x_no_nan = x_no_nan - mean(x_no_nan); %repmat(mean(x_no_nan),size(x_no_nan,1),1); +% +% if length(varargin) == 0 || varargin{1} == 0 +% +% x_no_nan = x_no_nan ./ max(eps, std(x_no_nan)); %repmat(std(x_no_nan),size(x_no_nan,1),1); +% +% end +% +% xout(~nanvec,i) = x_no_nan; +% end + +return \ No newline at end of file diff --git a/Data_processing_tools/scnlab_filter_fmri_data.m b/Data_processing_tools/scnlab_filter_fmri_data.m new file mode 100644 index 00000000..d491f24e --- /dev/null +++ b/Data_processing_tools/scnlab_filter_fmri_data.m @@ -0,0 +1,202 @@ +function names = scnlab_filter_fmri_data(imgs, mvmt, mask, tr, spersess, hp) + % names = scnlab_filter_fmri_data(imgs, mvmt, mask, tr, spersess, hp) + % + % Outlier and artifact removal for one subject + % Writes new output images for timeseries + % + % Example from NSF data: + % OUT = mean_image(imgs, 'mean_ravol.img',ones(size(imgs,1),1)); + %spm_imcalc_ui('mean_ravol.img', 'graymatter.img', 'i1 > 0'); + %spm_image('init', 'graymatter.img'); + % + % names = scnlab_filter_fmri_data(imgs, mvmt, 'graymatter.img', 2, repmat(184, 1, 6), 80); + + % ------------------------------------------------------- + tic + imgs = check_valid_imagename(imgs); + + disp('Loading data') + [dat, volInfo] = iimg_get_data(mask, imgs); + + + % ------------------------------------------------------- + + disp('Setting up filtering, and logging to scn_filter_output.txt') + diary scn_filter_output.txt + OPT = scnlab_outlier_id('setup', 'tr', tr, 'spersess', spersess, 'dummy', 1:2, 'hp', hp, 'mad', 4, 'niter', 5, 'mvmt', mvmt); + OPT.volInfo = volInfo; + diary off + + % ------------------------------------------------------- + + disp('Test run...') + fhandle = @(y) scnlab_outlier_id('data', y, 'options', OPT); + fhandle(dat(:,1)); + + disp('Saving OPT structure in scnlab_filter_setup.mat') + save scnlab_filter_setup OPT + + disp('Printing graphics image to scnlab_filter_output.ps'); + h = findobj('Tag', 'Data Detail'); + if ~isempty(h) && ishandle(h) + print(h, '-dpsc','-r200','scnlab_filter_output.ps','-append') + end + + % ------------------------------------------------------- + + disp('Running on all data.') + OPT.doplot = 0; + OPT.verbose = 0; + fhandle = @(y) scnlab_outlier_id('data', y, 'options', OPT); + [filt_dat, outliers, num_outliers, mvmt_rsquare, mvmt_baseline_rsquare, ypercent_change, raw_equalvar_p, ... + raw_equalvar_F, perc_equalvar_p, perc_equalvar_F ] = matrix_eval_function(dat, fhandle); + clear ybaseline + + + ypercent_change = ypercent_change'; + + filt_dat = filt_dat'; + avg = mean(filt_dat); + mabsdev = mad(filt_dat); + + + % ------------------------------------------------------- + + disp('writing mean, MAD, num_outlier, and mvmt_rsquare, etc. images in current directory.') + + + iimg_reconstruct_3dvol(mabsdev', volInfo, 'outname', 'scn_filter_MAD.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(avg', volInfo, 'outname', 'scn_filter_mean.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(num_outliers, volInfo, 'outname', 'scn_filter_num_outliers.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(mvmt_rsquare, volInfo, 'outname', 'scn_filter_mvmt_rsquare.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(mvmt_baseline_rsquare, volInfo, 'outname', 'scn_filter_mvmt_baseline_rsquare.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + + iimg_reconstruct_3dvol(raw_equalvar_p, volInfo, 'outname', 'scn_filter_raw_equalvar_p.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(raw_equalvar_F, volInfo, 'outname', 'scn_filter_raw_equalvar_F.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(perc_equalvar_p, volInfo, 'outname', 'scn_filter_perc_equalvar_p.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + iimg_reconstruct_3dvol(perc_equalvar_F, volInfo, 'outname', 'scn_filter_perc_equalvar_F.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + + rawvsperc = perc_equalvar_F - raw_equalvar_F; + iimg_reconstruct_3dvol(rawvsperc, volInfo, 'outname', 'scn_filter_perc_vs_raw_equalvar_F.img', 'descrip', 'Created with scnlab_filter_fmri_data.m'); + + spm_check_registration(char('scn_filter_mean.img', 'scn_filter_MAD.img', 'scn_filter_mvmt_rsquare.img', 'scn_filter_mvmt_baseline_rsquare.img', ... + 'scn_filter_num_outliers.img', ... + 'scn_filter_raw_equalvar_F.img', 'scn_filter_perc_equalvar_F.img', 'scn_filter_perc_vs_raw_equalvar_F.img')); + scale_windows + + + + disp('Printing graphics image to scnlab_filter_output.ps'); + h = findobj('Tag', 'Graphics'); + if ~isempty(h) && ishandle(h) + print(h, '-dpsc','-r200','scnlab_filter_output.ps','-append') + end + + % ------------------------------------------------------- + dowrite = 1; + names = []; + + if dowrite + disp('Writing f* time series images in original image directory.') + for i = 1:size(filt_dat, 1) + if i == 1 + names = write_img(filt_dat(i, :)', imgs(i, :), volInfo, 'f'); + else + + names = char(names, write_img(filt_dat(i, :)', imgs(i, :), volInfo, 'f')); + end + end + + if OPT.dopercent + disp('Writing perc* time series images in original image directory.') + for i = 1:size(filt_dat, 1) + if i == 1 + names = write_img(ypercent_change(i, :)', imgs(i, :), volInfo, 'perc'); + else + + names = char(names, write_img(ypercent_change(i, :)', imgs(i, :), volInfo, 'perc')); + end + end + end + + disp('Saving filtered image names as ''names'' in scnlab_filter_setup.mat'); + save scnlab_filter_setup -append names + else + disp('Not writing any images: flag is off.') + end + + % ------------------------------------------------------- + + disp('All done.') + toc + +end + + + + + +% ------------------------------------------------------- +% ------------------------------------------------------- +% ------------------------------------------------------- + + +function name = write_img(y, name, volInfo, prefix) + + name = make_img_filename(name, prefix); + iimg_reconstruct_3dvol(y, volInfo, 'outname', name, 'descrip', 'Created with scnlab_filter_fmri_data.m'); + +end + + +function name = make_img_filename(name, prefix) + [d, name] = fileparts(name); + name(name == '.') = '_'; + name = fullfile(d, [prefix name '.img']); +end + + +function scale_windows + + global st + + warning off + + win = 5; + v = spm_read_vols(spm_vol(st.vols{win}.fname)); + v = v(:); + v(v == 0) = []; + lb = min(v); + ub = prctile(v, 95); + spm_orthviews('Window', 3, [lb ub]) + title(st.vols{win}.ax{2}.ax, sprintf('%s\nDisplay Rng: %3.0f, %3.0f',st.vols{win}.fname,lb, ub),'FontSize', 12) + title(st.vols{win}.ax{3}.ax, sprintf('Mean: %3.0f\nRange: %3.0f to %3.0f', mean(v), min(v),max(v)),'FontSize', 10) + + + for win = [ 2 3 4 6 7 ] + v = spm_read_vols(spm_vol(st.vols{win}.fname)); + v = v(:); + v(v == 0) = []; + lb = 0; + ub = prctile(v, 95); + spm_orthviews('Window', win, [lb ub]) + title(st.vols{win}.ax{2}.ax, sprintf('%s\nDspRng: %3.4f, %3.4f',st.vols{win}.fname,lb, ub),'FontSize', 12) + title(st.vols{win}.ax{3}.ax, sprintf('Mean: %3.4f\nRng: %3.4f to %3.4f', mean(v), min(v),max(v)),'FontSize', 10) + end + + for win = [ 1 8 ] + v = spm_read_vols(spm_vol(st.vols{win}.fname)); + v = v(:); + v(v == 0) = []; + lb = mean(v)-3*std(v); + ub = mean(v)+3*std(v); + spm_orthviews('Window', win, [lb ub]) + title(st.vols{win}.ax{2}.ax, sprintf('%s\nDspRng: %3.4f, %3.4f',st.vols{win}.fname,lb, ub),'FontSize', 12) + title(st.vols{win}.ax{3}.ax, sprintf('Mean: %3.4f\nRng: %3.4f to %3.4f', mean(v), min(v),max(v)),'FontSize', 10) + end + + spm_orthviews('interp',0) + colormap jet + + warning on +end \ No newline at end of file diff --git a/Data_processing_tools/scnlab_outlier_id.m b/Data_processing_tools/scnlab_outlier_id.m new file mode 100644 index 00000000..2aaaa6d1 --- /dev/null +++ b/Data_processing_tools/scnlab_outlier_id.m @@ -0,0 +1,664 @@ +function varargout = scnlab_outlier_id(varargin) + % + % Created June 2007 + % Tor Wager + % + % Methods (modes of operation) + % --------------------------------------------------------------------- + % + % + % SETUP + % Run this method first to generate an options structure OPT + % that can be passed in along with any data vector for speedy + % processing + % + % * OPT = scnlab_outlier_id('setup', 'tr', 2, 'spersess', [184 184 + % 184 184 184 184], 'dummy', 1:3, 'hp', 100, 'mad', 4, 'niter', 3, 'mvmt', mvmt); + % + % --------------------------------------------------------------------- + % + % + % + % DATA + % Run this method second with an already-created OPT + % and a data vector from one time series + % + % [y2, outliers, num_outliers, mvmt_rsquare] = scnlab_outlier_id('data', y, 'options', OPT); + % + %% all outputs: + % [y2, out, nout, mvmtrsq, mvmt_baseline_rsquare, yperc, rawvarp, rawvarF, percvarp, percvarF, ... + % ybase] = scnlab_outlier_id('data', y, 'options', OPT); + % + % --------------------------------------------------------------------- + % + % Example: + % [dat, volInfo] = iimg_get_data('graymask.img', imgs); + % y = dat(:,1); + % % SETUP: + % OPT = scnlab_outlier_id('setup', 'tr', 2, 'spersess', [184 184 184 184 184 184], 'dummy', 1:3, 'hp', 100, 'mad', 4, 'niter', 3, 'mvmt', mvmt); + % % RUN: + % [y2, outliers, num_outliers] = scnlab_outlier_id('data', y, 'options', OPT); + % + % % EXAMPLE: run on whole brain + % [dat, volInfo] = iimg_get_data('graymask.img', imgs); + % OPT = scnlab_outlier_id('setup', 'tr', 2, 'spersess', [184 184 184 184 184 184], 'dummy', 1:2, 'hp', 100, 'mad', 4, 'niter', 5, 'mvmt', mvmt); + % OPT.doplot = 0; + % OPT.verbose = 0; + % fhandle = @(y) scnlab_outlier_id('data', y, 'options', OPT); + % y2 = matrix_eval_function(dat, fhandle)'; + % + + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'setup', meth = 'setup'; + case 'data', meth = 'data'; + end + end + end + + OPT.tr = []; + OPT.spersess = []; + OPT.dummy = []; + OPT.hp = []; + OPT.mad = 4; + OPT.niter = 3; + OPT.doplot = 1; + OPT.verbose = 1; + OPT.mvmt = []; + OPT.dopercent = 1; + + switch meth + + % --------------------------------------------------------------------- + % * + % * + % * SETUP + % * Run this method first to generate an options structure OPT + % that can be passed in along with any data vector for speedy + % processing + % * + % --------------------------------------------------------------------- + + case 'setup' + + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'tr', OPT.tr = varargin{i+1}; + case 'spersess', OPT.spersess = varargin{i+1}; + case 'dummy', OPT.dummy = varargin{i+1}; + case 'hp', OPT.hp = varargin{i+1}; + case 'mad', OPT.mad = varargin{i+1}; + case 'niter', OPT.niter = varargin{i+1}; + case 'plot', OPT.doplot = varargin{i+1}; + case 'verbose', OPT.verbose = varargin{i+1}; + + case 'mvmt', OPT.mvmt = varargin{i+1}; + + case 'nopercent', OPT.dopercent = 0; + + case {'setup', 'data'} % do nothing + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + if OPT.verbose + disp('Checking inputs.') + end + + if isempty(OPT.tr), error('Enter ''tr'' followed by tr of acquisition.'); end + if isempty(OPT.spersess), error('Enter ''spersess'' followed by vector of number of images in each session.'); end + + OPT.spersess = OPT.spersess(:)'; %enforce row vector + + % set up matrices for linear, invariant part + % --------------------------------------------------------------------- + nimgs = sum(OPT.spersess); + nsess = length(OPT.spersess); + + y = ones(nimgs, 1); + OPT.descrip = 'Fields below will be applied to data during processing.'; + OPT.descrip2 = 'They contain high-pass filter, session mean, and dummy scan information'; + % ***** does most of the work ****** + [y, OPT.IpinvI, OPT.HPmatrix] = hpfilter(y, OPT.tr, OPT.hp, OPT.spersess,[],OPT.dummy); + + + % Get problematic images from motion parameters + % --------------------------------------------------------------------- + if ~isempty(OPT.mvmt) + + nparams = size(OPT.mvmt, 2); + + if size(OPT.mvmt, 1) ~= length(y), warning('Length of movement parameters not equal to number of images!!!'); end + + OPT.mvmt_diff = [zeros(1, nparams); diff(OPT.mvmt)]; + + OPT.moved_more_than_point1 = any(abs(OPT.mvmt_diff(:,4:6)) > .1, 2); + OPT.rotate_more_than_point002 = any(abs(OPT.mvmt_diff(:,1:3)) > .002, 2); + + % convert to MADs + OPT.mvmt_diff = OPT.mvmt_diff ./ repmat(mad(OPT.mvmt_diff), size(OPT.mvmt_diff,1), 1); + + % ID outliers/images with high movement + + OPT.mvmt_outliers = any(abs(OPT.mvmt_diff) > OPT.mad, 2); + + % these would mark vals on either side of rapid transition + %OPT.mvmt_outliers + [zeros(1, nparams); OPT.mvmt_outliers(1:end-1, :)]; + + % Outliers: have to have substantial motion, and also be + % out of subject's distribution based on MAD + OPT.mvmt_outliers = any(OPT.mvmt_outliers, 2) & (OPT.moved_more_than_point1 | OPT.rotate_more_than_point002); + + % other movement things of interest + OPT.within_sess_mvmt = OPT.mvmt - OPT.IpinvI * OPT.mvmt; + + OPT.total_mvmt_displacement = max(OPT.mvmt) - min(OPT.mvmt); + en = cumsum(OPT.spersess); + st = [1 en(1:end-1)+1]; + + for jj = 1:length(OPT.spersess) + wh = st(jj):en(jj); + OPT.within_mvmt_displacement(jj, :) = max(OPT.within_sess_mvmt(wh,:)) - min(OPT.within_sess_mvmt(wh,:)); + end + + if OPT.doplot + create_figure1 + nfigrows; nfigcols; + plot_movement + end + + end + + + % print output + + if OPT.verbose + fprintf(1,'\nscnlab_outlier_id setup\n----------------------------------------\n'); + fprintf('Data: %3.0f observations in %3.0f runs (sessions) \n', nimgs, nsess); + fprintf('\t Images in each run:') + fprintf('%3.0f ', OPT.spersess); + fprintf('\n'); + fprintf('TR: %3.2f\n', OPT.tr); + + nystr = {'No' 'Yes'}; + + fprintf('High-pass filter: %s ', nystr{(~isempty(OPT.hp)) + 1}); + if ~isempty(OPT.hp) + fprintf(', HP length: %3.2f\n', OPT.hp); + else + fprintf('\n'); + end + + + fprintf('Movement parameters entered: %s \n', nystr{(~isempty(OPT.mvmt)) + 1}); + % if ~isempty(OPT.mvmt) + % fprintf('HP length: %3.2f\n', OPT.hp); + % else + % fprintf('\n'); + % end + + fprintf('MAD threshold for outliers: %3.2f\n', OPT.mad); + fprintf('Number of iterations: %3.0f\n', OPT.niter); + + if ~isempty(OPT.mvmt) + fprintf(1,'\nMovement summary\n----------------------------------------\n'); + fprintf('Maximum displacement across experiment\n') + fprintf('r\tp\ty\tx\ty\tz\t\n') + fprintf('%3.2f\t', OPT.total_mvmt_displacement); + fprintf('\n\n'); + + fprintf('Maximum displacement within sessions\n') + fprintf('r\tp\ty\tx\ty\tz\n') + for jj = 1:length(OPT.spersess) + fprintf('%3.2f\t', OPT.within_mvmt_displacement(jj, :)); + fprintf('\n'); + end + fprintf('(mean within session displacement)\n') + fprintf('%3.2f\t', mean(OPT.within_mvmt_displacement)); + fprintf('\n\n'); + + fprintf('Images with displacement > 0.1 mm: %3.0f\n', sum(OPT.moved_more_than_point1)) + fprintf('Images with rotation > 0.002 radians: %3.0f\n', sum(OPT.rotate_more_than_point002)) + fprintf('Images excluded as motion transients: %3.0f\n', sum(OPT.mvmt_outliers)) + + end + + end + + varargout{1} = OPT; + + + + + + + + + + + + + + % --------------------------------------------------------------------- + % * + % * + % * DATA + % * Run this method second with an already-created OPT + % and a data vector from one time series + % + % * + % --------------------------------------------------------------------- + + case {'data'} + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case {'opt', 'options'}, OPT = varargin{i+1}; + case {'y', 'data'}, y = varargin{i+1}; + + case 'plot', OPT.doplot = varargin{i+1}; + case 'verbose', OPT.verbose = varargin{i+1}; + + case {'setup'} % do nothing + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + n = size(y,1); + outliers = false(size(y)); + + % add movement outliers, if we have them + % --------------------------------------------------------------------- + if ~isempty(OPT.mvmt) + outliers(OPT.mvmt_outliers) = 1; + end + + + if OPT.doplot + % --------------------------------------------------------------------- + create_figure1 + subplot(nfigrows, nfigcols, 1); + + if ~isempty(OPT.mvmt) + plot_movement + end + + end + + + % setup for moving average + % starting and ending images for each session + st = cumsum([1 OPT.spersess(1:end-1)]); + en = cumsum(OPT.spersess); + yfit_baseline = zeros(n, 1); + + for i = 1:OPT.niter + + % moving average method + % --------------------------------------------------------------------- + iteration_moving_average + + % iteration_hp_filter + + end + + + % final step + % interpolate outliers + % --------------------------------------------------------------------- + + y = interpolate_outliers(y, yfit_baseline, outliers, n); + + + + yperc = 0; + raw_equalvar_p = 0; + raw_equalvar_F = 0; + perc_equalvar_p = 0; + perc_equalvar_F = 0; + + if OPT.dopercent + yperc = 100 .* y ./ yfit_baseline; + + % test equality of variance for y and yperc + % if percent change is better scaling, variances should be + % more equal across groups + + for ss = 1:length(st) + + group(st(ss):en(ss)) = ss; + + end + + % test equality of variances across sessions + [raw_equalvar_p, stats] = vartestn(y, group, 'off', 'robust'); + raw_equalvar_F = stats.fstat; + + [perc_equalvar_p, stats] = vartestn(yperc, group, 'off', 'robust'); + perc_equalvar_F = stats.fstat; + + end + + varargout{6} = yperc; + varargout{7} = raw_equalvar_p; + varargout{8} = raw_equalvar_F; + varargout{9} = perc_equalvar_p; + varargout{10} = perc_equalvar_F; + + + if OPT.doplot + plot_this_iteration + + subplot(nfigrows, nfigcols, 7) + + % plot sessions + draw_session_boxes(OPT, y) + end + + + % movement-related stats + % --------------------------------------------------------------------- + mvmt_rsquare = 0; vy = var(y); + + if ~isempty(OPT.mvmt) && ~isnan(vy) && vy > 0 + + [b, bint, r, rint, stats] = regress(y, [OPT.mvmt ones(n, 1)]); + mvmt_rsquare = stats(1); + + [b, bint, r, rint, stats] = regress(yfit_baseline, [OPT.mvmt ones(n, 1)]); + mvmt_baseline_rsquare = stats(1); + + end + + + varargout{1} = y; + varargout{2} = outliers; + varargout{3} = sum(outliers); + varargout{4} = mvmt_rsquare; + varargout{5} = mvmt_baseline_rsquare; + varargout{11} = yfit_baseline; % at end, so we don't have to save lots of data in matrix_eval_fcn + + + if OPT.verbose + fprintf(1,'\nData summary\n----------------------------------------\n'); + fprintf('Outliers\n') + fprintf('Total: %3.0f\n', sum(outliers)); + fprintf('Data for outliers estimated using linear interpolation.\n') + if ~isempty(OPT.mvmt) + fprintf('Movement-related exclusions: %3.0f\n', sum(OPT.mvmt_outliers)); + fprintf('% Var explained by mvmt params: %3.2f\n', mvmt_rsquare); + end + + fprintf('\n'); + + end + + otherwise + error('Unknown method') + + end + + + + + % --------------------------------------------------------------------- + % INLINE FUNCTIONS + % --------------------------------------------------------------------- + + function create_figure1 + + nfigrows = 4; + nfigcols = 2; + + create_figure('Data Detail', nfigrows, nfigcols); + + subplot(nfigrows,nfigcols,3); + % plot sessions + draw_session_boxes(OPT, y) + + plot(y, 'k', 'LineWidth', 2); + title('Original data') + set(gca,'XLim',[0 length(y)]) + + + subplot(nfigrows,nfigcols,4); + shaded_hist(y); + title('Histogram'); + + drawnow + + end + + function plot_movement + + subplot(nfigrows,nfigcols,1); + + % plot sessions + draw_session_boxes(OPT, OPT.mvmt) + plot(OPT.mvmt) + + title('Movement parameters') + ylabel('mm or degrees'); + set(gca,'XLim',[0 length(y)]) + + + subplot(nfigrows,nfigcols,2); + plot(OPT.mvmt_diff) + + % plot sessions + draw_session_boxes(OPT, OPT.mvmt) + + wh_outliers = find(OPT.mvmt_outliers); + if(~isempty(wh_outliers)) + for jj = wh_outliers + han = plot_vertical_line(jj,'k'); + set(han,'Color',[.7 .7 .7]); + end + end + plot(OPT.mvmt_diff); + %plot(repmat(find(OPT.mvmt_outliers), 1, size(OPT.mvmt, 2)), OPT.mvmt_diff(OPT.mvmt_outliers, :), 'rs'); + + title('Displacement (MADs)') + ylabel('Med Abs Dev'); + set(gca,'XLim',[0 length(y)], 'YLim', [-OPT.mad * 4 OPT.mad * 4]) + + end + + + + function iteration_moving_average + + + + % remove drift and session effects + % --------------------------------------------------------------------- + % for each session + for ss = 1:length(st) + + yfit_baseline(st(ss):en(ss)) = moving_average('gaussian',y(st(ss):en(ss)), round(OPT.hp ./ OPT.tr)); + + end + + % get adjusted timeseries + yadj = y - yfit_baseline; + + if OPT.doplot + + if i == 1, plot_first_pass; end + plot_this_iteration + + end + + % ID outliers on residuals and update outlier list + % --------------------------------------------------------------------- + mady = mad(yadj); + wh = abs(yadj) > mady * OPT.mad; + outliers = outliers | wh; + + % interpolate at outlier values + y(outliers) = yfit_baseline(outliers); + + end + + + + + function iteration_hp_filter + % impute mean to identified outliers + % --------------------------------------------------------------------- + y(outliers) = repmat(mean(y, 1), sum(outliers), 1); % impute mean + + % remove drift and session effects + % --------------------------------------------------------------------- + y = hpfilter(y, [], OPT.HPmatrix, OPT.spersess, OPT.IpinvI); + + if OPT.doplot + + if i == 1, plot_first_pass; end + plot_this_iteration + + end + + % ID outliers on residuals and update outlier list + % --------------------------------------------------------------------- + mady = mad(y); % repmat(mad(y), n, 1); + wh = (abs(y) > OPT.mad * mady); + + outliers = outliers | wh; % add to list of outliers + end + + + function plot_this_iteration + subplot(nfigrows, nfigcols, 7) + cla + if exist('yadj', 'var') % moving average + myploty = yadj; + else % hpfilter method + myploty = y; + end + + plot(myploty, 'k'); + hold on; + plot(find(outliers), myploty(outliers), 'rs', 'LineWidth', 2); + + set(gca,'XLim',[0 length(myploty)]) + title(['Iteration ' num2str(i)]); + + subplot(nfigrows,nfigcols,8); + cla + shaded_hist(myploty); + title('Histogram'); + + drawnow + pause(.5) + end + + + function plot_first_pass + subplot(nfigrows, nfigcols, 5) + cla + plot(y, 'k'); + hold on; + plot(find(outliers), y(outliers), 'rs', 'LineWidth', 2); + + set(gca,'XLim',[0 length(y)]) + title('First pass'); + + if exist('yfit_baseline', 'var') % moving average only + plot(yfit_baseline, 'r'); + end + + subplot(nfigrows,nfigcols,6); + cla + shaded_hist(y); + title('Histogram'); + + drawnow + pause(.5) + end + +end % END MAIN FUNCTION + + + +function draw_session_boxes(OPT, y) + + + nsess = length(OPT.spersess); + colors = {'k' 'k' 'k' 'k' 'k' 'k' 'k' 'k'}; %{'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + while nsess > length(colors), colors = [colors colors]; end + + yval = min(y(:)) + .05 * min(y(:)); + yheight = (max(y(:)) + .05 * max(y(:))) - min(y(:)); + c = cumsum(OPT.spersess); + st = [0 c(1:end-1)]; + + for jj = 1:2:nsess + h1 = drawbox(st(jj),OPT.spersess(jj),yval,yheight,colors{jj}(1)); + set(h1, 'FaceAlpha', .10, 'EdgeColor', 'none'); + end + +end + + +% ------------------------------------------------------------------------- +% Create a shaded gray-scale histogram with .05 2-tailed in darker gray +% ------------------------------------------------------------------------- +function shaded_hist(a, xx) + if nargin < 2 + nbins = max(10, round(length(a) ./ 20)); + nbins = min(nbins, 1000); + [h, xx] = hist(a, nbins); + else + h = hist(a, xx); + end + han = bar(xx, h); + set(han, 'FaceColor', [.7 .7 .7]); + set(han, 'EdgeColor', [.7 .7 .7]); + wh = (xx > prctile(a, 97.5) | xx < prctile(a, 2.5)); + h(~wh) = 0; + if any(wh') + hold on; + han = bar(xx, h); + set(han, 'FaceColor', 'r', 'EdgeColor', [.3 .3 .3]); + end + plot_vertical_line(0); +end + + +function y = interpolate_outliers(y, yfit_baseline, outliers, n) + y = y - yfit_baseline; + + time = 1:n; + whout = find(outliers); + + + yinterp = interp1(time(~outliers),y(~outliers),whout, 'linear'); + + % NaNs are used at ends + mynans = isnan(yinterp); + yinterp(mynans) = 0; % yfit_baseline(whout(mynans)); + + + y(outliers) = yinterp; + % % + % % % remove drift and session effects + % % + % % y = hpfilter(y, [], OPT.HPmatrix, OPT.spersess, + % OPT.IpinvI); +end + diff --git a/Data_processing_tools/selective_average.m b/Data_processing_tools/selective_average.m new file mode 100644 index 00000000..eb7ef373 --- /dev/null +++ b/Data_processing_tools/selective_average.m @@ -0,0 +1,201 @@ +function [averages, stderrs, data, indices] = selective_average(y, onsets, varargin) + % [averages, stderrs, data, indices] = selective_average(y, onsets, varargin) + % + % Purpose: Get a selective average of values of data vector y, given + % onsets specified in onsets. Onsets can be fractional; in this case, + % linear interpolation is used. + % + % INPUTS + % y is a data vector to get selective averages from. + % It should be a column vector. + % + % onsets should be a cell array, with one cell per condition + % each cell should contain a column vector of onset times in SAMPLES (same + % resolution as y; e.g., in TRs, if y is an fMRI time series. + % + % OPTIONAL INPUTS + % 't', followed by number of time points following onset to use; + % default is 20 + % + % 'plot', plot results. + % + % 'baseline', followed by vector of which time points are baseline + % values; will subtract from each + % + % OUTPUTS + % data, indices, averages, stderrs: Cell vectors, one cell per condition + % data, indices: time points (observations) x trials (onsets) + % + % indices : Cell vector, one cell per condition; time points (observations) x trials (onsets) + % + % + % Example: with fake data + % onsets = {[1 10 30 80]' [20 60 90]'}; y = (1:120)'; + % [averages, stderrs, data, indices] = selective_average(y, onsets, 't', 20) + % + % V = spm_vol(EXPT.FILES.im_files{1}); + % y = spm_get_data(V, [10 10 10 1]'); + % [averages, stderrs, data, indices] = selective_average(y, onsets2(1), 't', 20, 'plot'); + % + % + % Tor Wager, Dec 2007 + % Minor update: June 2009 + + % --------------------------------------------------------------------- + % Optional inputs + % --------------------------------------------------------------------- + t = 20; + doplot = 0; + basepts = []; + + % plot colors + colors = {'ro-' 'go-' 'bo-' 'yo-' 'co-' 'mo-' 'r^:' 'g^:' 'b^:' 'y^:' 'c^:' 'm^:'}; + + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case {'t', 'timepoints'}, t = varargin{i+1}; + case 'plot', doplot = 1; + + case {'baseline' 'basepts' 'basepoints'}, basepts = varargin{i+1}; + + case {'color' 'colors'}, colors = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + % --------------------------------------------------------------------- + % Initialize vars + % --------------------------------------------------------------------- + % onsets should be a cell array, with one cell per condition + % each cell should contain a column vector of onset times in SAMPLES + n_conditions = length(onsets); + + indices = cell(1, n_conditions); + data = cell(1, n_conditions); + averages = cell(1, n_conditions); + stderrs = cell(1, n_conditions); + + + % --------------------------------------------------------------------- + % Run + % --------------------------------------------------------------------- + for i = 1:n_conditions + + ons = onsets{i}; + + [indices{i}, n_onsets{i}] = get_indices(ons, t); + + data{i} = get_data(y, indices{i}, t, n_onsets{i}); + + [averages{i} stderrs{i}] = get_averages(data{i}, basepts, t); + + end + + + if doplot + + plot_results(averages, stderrs, t, colors); + + end + +end % END MAIN FUNCTION + + + + +% Sub-functions + + +function [indices, n_onsets] = get_indices(ons, t) + + n_onsets = size(ons, 1); + + to_add = (1:t)' - 1; % elements to add to form indices + + % matrix of values to add to each onset + add_mtx = to_add(:, ones(1, n_onsets)); + + % matrix form of onsets + ons_mtx = ons'; ons_mtx = ons_mtx(ones(t, 1), :); + + indices = ons_mtx + add_mtx; + +end + + + + +function data = get_data(y, indices, varargin) + + if all(indices(:) == round(indices(:))) + % easy, all indices are integers; just get data + data = y(indices); + + else + t = varargin{1}; + n_onsets = varargin{2}; + + % we have fractions of indices; linear interpolation + % should return same as above for integer indices + yest = interp1((1:length(y))', y, indices(:), 'linear'); + + data = reshape(yest, t, n_onsets); + end + +end + + + + +function [avg, stderr] = get_averages(data, basepts, t) + + % Adjust data, if asked for: subtract baseline points + % ----------------------------------------------------- + if ~isempty(basepts) + +% % fprintf('Subtracting baseline timepoints:') +% % fprintf(' %3.0f', basepts); +% % fprintf('\n') + + basemean = nanmean(data(basepts, :), 1); + basemean = basemean(ones(t, 1), :); + data = data - basemean; + + end + + % Now data should be time points (observations) x trials (onsets) + avg = nanmean(data, 2); + stderr = ste(data')'; + +end + + +function plot_results(averages, stderrs, t, varargin) + + to_add = (1:t)' - 1; % elements to add to form indices + + if ~isempty(varargin), colors = varargin{1}; end + + %create_figure('Selective Average Plot'); + + for i = 1:length(averages) + + if ischar(colors{i}) + plot(to_add, averages{i}, colors{i}, 'LineWidth', 2); + else + plot(to_add, averages{i}, 'Color', colors{i}, 'LineWidth', 2); + end + + end + + xlabel('Time from onset (samples)'); + ylabel('Average response level'); + +end diff --git a/Data_processing_tools/selective_average_group.m b/Data_processing_tools/selective_average_group.m new file mode 100644 index 00000000..90eaa084 --- /dev/null +++ b/Data_processing_tools/selective_average_group.m @@ -0,0 +1,178 @@ +function [group_avgs, group_stes, subject_avgs, subject_stes, braindata] = selective_average_group(V, onsets, xyz_mm_pos, varargin) + % + % DOCUMENTATION NOT COMPLETE! + % + % uses selective_average.m + % used in selective_average_interactive_view_init.m + % + % Example: + % [group_avgs, group_stes, subject_avgs, subject_stes] = selective_average_group(V, onsets, vox, 'basepts', 1:2, 'plotstes', 0); + % + % Format onsets from onsets2 (NSF study format) into correct format for + % % this function + % N = length(imgs); n_conditions = size(eventdesign{1}, 2); + % onsets = cell(1, N); + % for i = 1:N + % for j = 1:n_conditions + % onsets{i}{j} = onsets2{i}(find(eventdesign{i}(:, j))); + % end + % end + + % Optional inputs + % ----------------------------------------------------- + + basepts = []; + t = 20; + doplot = 1; % not used + plotstes = 1; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + + case {'baseline' 'basepts' 'basepoints'}, basepts = varargin{i+1}; + case {'t', 'timepoints'}, t = varargin{i+1}; + case 'plot', doplot = 1; + + case 'plotstes', plotstes = varargin{i+1}; + + + case {'S'}, S = varargin{i+1}; + case {'scans'}, scans_per_sess = varargin{i+1}; + case {'I'}, I = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + % Get voxel coordinates from mm coords for each subject + % Subjects may be in different space! + % ----------------------------------------------------- + N = length(V); + vox = zeros(3, N); + + for i = 1:N + + %pos = spm_orthviews('Pos'); + vox(:, i) = (mm2voxel(xyz_mm_pos, V{i}(1)))'; + end + + vox(4, :) = 1; + + meanvox = mean(vox, 2); + stdvox = std(vox')'; + inreg = all(stdvox == 0); + if inreg, regstr = 'All subjects in same space.'; else regstr = 'Subjects are in different spaces.'; end + + fprintf('Position: %3.0f %3.0f %3.0f , Voxel mean: %3.2f %3.2f %3.2f, Std: %3.2f %3.2f %3.2f %s\n', xyz_mm_pos(1), xyz_mm_pos(2), xyz_mm_pos(3), ... + meanvox(1), meanvox(2), meanvox(3), stdvox(1), stdvox(2), stdvox(3), regstr); + + go = input('Plot selective averages for this voxel? (1 or 0) : '); + + if ~go + group_avgs = []; + group_stes = []; + subject_avgs = []; + subject_stes = []; + braindata = []; + + return + end + + % conditions per subject; These are assumed to be the same for all + % subjects! + n_conditions = length(onsets{1}); + + % Load data for this voxel + % ----------------------------------------------------- + + + fprintf('Loading voxel data for all subjects: '); + tic + braindata = cell(1, N); + + for i = 1:N + fprintf('%3.0f ', i); + braindata{i} = spm_get_data(V{i}, vox(:, i)); + end + fprintf(' %3.0f s\n', toc) + + + % Should high-pass filter here! + % ----------------------------------------------------- + fprintf('High-pass filtering\n'); + for i = 1:N + braindata{i} = hpfilter(braindata{i}, [], S, scans_per_sess, I); + end + + + % Get selective averages for each subject + % ----------------------------------------------------- + + nx = ceil(sqrt(N)); + ny = floor(sqrt(N)); + create_figure('Selective average individual plot', nx, ny); + + subject_avgs = cell(1, n_conditions); + subject_stes = cell(1, n_conditions); + + for i = 1:N + + subplot(nx, ny, i); + [averages, stderrs] = selective_average(braindata{i}, onsets{i}, 't', t, 'plot'); + xlabel('Time'); ylabel('Response'); + + for j = 1:n_conditions + subject_avgs{j}(:, i) = averages{j}; + subject_stes{j}(:, i) = stderrs{j}; + end + + end + + % Adjust data, if asked for: subtract baseline points + % ----------------------------------------------------- + if ~isempty(basepts) + + fprintf('Subtracting baseline timepoints:') + fprintf(' %3.0f', basepts); + fprintf('\n') + + for j = 1:n_conditions + basemean = nanmean(subject_avgs{j}(basepts, :), 1); + + basemean = basemean(ones(t, 1), :); + + subject_avgs{j} = subject_avgs{j} - basemean; + + end + end + + + % Plot + % ----------------------------------------------------- + create_figure('Selective Average: Group Plot'); + + colors = {'ro-' 'go-' 'bo-' 'yo-' 'co-' 'mo-' 'r^:' 'g^:' 'b^:' 'y^:' 'c^:' 'm^:'}; + + x = (1:t)' - 1; + + group_avgs = cell(1, n_conditions); + group_stes = cell(1, n_conditions); + + for j = 1:n_conditions + + group_avgs{j} = nanmean(subject_avgs{j}, 2); + group_stes{j} = ste(subject_avgs{j}'); + + if plotstes, tor_fill_steplot(subject_avgs{j}', colors{j}, 0, [], x); end + + plot(x, group_avgs{j}, colors{j}, 'LineWidth', 3); + + end + + +end \ No newline at end of file diff --git a/Data_processing_tools/smooth_timeseries.m b/Data_processing_tools/smooth_timeseries.m new file mode 100755 index 00000000..128a8e02 --- /dev/null +++ b/Data_processing_tools/smooth_timeseries.m @@ -0,0 +1,29 @@ +function [x,V,xc] = smooth_timeseries(x,perc) +% [x,V] = smooth_timeseries(x,perc) +% tor wager +% make exponential smoothing function with perc proportion of data points +% (0 < perc < 1) +% OR specify length directly, as % of points to 0 weight +% +% apply smoothing filter V to data (x), V * x +% (works just as well for a matrix of column vectors) +% You could also apply it to a model matrix X + +if size(x,1) == 1, x = x';,end % transpose if necessary + +if perc > 1, len = perc;, +else + len = ceil(size(x,1) .* perc); +end +xc = 1:len; +xc = 1 ./ (1 + xc.^.5); +xc = xc - min(xc); +%xc = xc ./ sum(xc); irrelevant + +V = toeplitz(pad(xc',size(x,1)-length(xc))); +V = V ./ repmat(sum(V),size(V,1),1); +V = V'; + +x = V * x; + +return \ No newline at end of file diff --git a/Data_processing_tools/splineDetrend.m b/Data_processing_tools/splineDetrend.m new file mode 100755 index 00000000..2e78ec15 --- /dev/null +++ b/Data_processing_tools/splineDetrend.m @@ -0,0 +1,104 @@ +function [fv,bp,yy,myfft] = splineDetrend(v,varargin) +% [fv,bp,yy,myfft] = splineDetrend(v,'p' [opt]) +% input: v, a vector to be detrended +% output: fv, the detrended vector, bp, the knot points, +% and myfft, the abs(fft) of the detrended vector. +% +% What it does: +% A spline detrend with knot points every 2 s (hard coded number) +% I made this up too - it's not the FDA approved method. +% +% By Tor Wager, 08/04/01 +% tmp = cl(1).raw_data(:,1,6); tmp2 = trimts(tmp,3,[],1); [fv,bp,yy]=splineDetrend(tmp2); +% [fv,bp] = splineDetrend(tmp2,'p'); +% +% 'p' means plot +% any other opt argument sets the knots and is treated as an integer, with detrending every n +% elements. + +% ------------------------------------------------- +% * setup +% ------------------------------------------------- + +samprate = 1; % default to 1 to use every knotrate images +knotrate = 10; % every 100 images by default + +m = mean(v); + +plotme = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'p', plotme = 1; + otherwise error('Unrecognized argument: acceptable is ''p'' for plot.') + end + else + knotrate = (varargin{i}); + disp(['Setting knot rate to ' num2str(knotrate)]) + end +end + +% ------------------------------------------------- +% * detrend +% ------------------------------------------------- + +% knot point every 2 s at 200 Hz +bp = 1:knotrate.*samprate:length(v); + +% figure out mean for each segment - +% this is the interp knot point. +for i = 1:length(bp)-1 + data = v(bp(i):bp(i+1)-1); + y(i) = mean(data); + x(i) = mean(bp(i),bp(i+1)); +end + +% add endpoint +x = [x length(v)]; +y = [y y(end)]; + +xx = 1:length(v); +yy = spline(x,y,xx); +fv = v - yy'; + +fv = fv + m; + + + +% ------------------------------------------------- +% * plot +% ------------------------------------------------- +if plotme + +figure; subplot(2,1,1) +title('Unfiltered with spline fit and knot points'); +hold on; plot(v);plot(yy,'r','LineWidth',2) +ylim = get(gca,'YLim'); +for i = 1:length(bp),plot([bp(i) bp(i)],ylim,'k','LineWidth',.5),end +subplot(2,1,2); plot(fv); title('Detrended') + +% 200 is the sampling rate! +figure;hold on +x = (1:length(fv)) .* (samprate ./ length(fv)); +myfft = abs(fft(fv)); +plot(x,myfft) +set(gca,'XLim',[0 20]) + +ylim = get(gca,'YLim'); + +% find the max in the power spectrum below the nyquist limit +maxx = find(myfft(2:length(myfft./2)) == max(myfft(2:length(myfft./2)))); +% add 1 to adjust for fact that you started at 2:length... +maxx = maxx + 1; +% find the frequency of this. +maxx = x(maxx); + +plot([maxx(1) maxx(1)],ylim,'r') + +end + + + +return + diff --git a/Data_processing_tools/splinetrim.m b/Data_processing_tools/splinetrim.m new file mode 100755 index 00000000..d51a84e5 --- /dev/null +++ b/Data_processing_tools/splinetrim.m @@ -0,0 +1,157 @@ +function [y2,ntrimmed,spikes, yy] = splinetrim(y,varargin) +% function [y,ntrimmed,spikes, yfit] = splinetrim(y,[iqrmult],[knotrate],[X],['p']) +% +% Uses a robust measure of deviations in a timeseries gradient +% to find high-velocity 'spikes', presumed to be artifacts +% +% Uses spline interpolation to replace spikes with reasonable values. +% +% y a timeseries +% +% optional inputs: +% iqrmult how many times the interquartile range above which velocities are +% outliers, default is 1.5 +% knotrate sets knot points every k observations, default is 3 +% X matrix of session means or other linear regressors to remove +% 'p' plot the results +% X and 'p' can be entered in any order, but after iqrmult and knotrate +% +% Example (good for eye tracking): +% [y2,nt] = splinetrim(trialdat,3,5,'p'); nt +% +% 3/29/05, Tor Wager + + + +% ------------------------------------------------- +% * setup +% ------------------------------------------------- + +iqrmult = 3; % how many times the interquartile range above which pts are outliers +knotrate = 5; % in images +X = []; +plotme = 0; + + +if length(varargin) > 0, iqrmult = varargin{1};, end +if length(varargin) > 1, knotrate = varargin{2};, end + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'p', plotme = 1; + otherwise error('Unrecognized argument: acceptable is ''p'' for plot.') + end + else + if ndims(varargin{i}) == 2 & sum(size(varargin{i}))>2 + X = varargin{i}; + end + end +end +% filter y using X matrix; yf is residuals +if ~isempty(X), + mfit = X * pinv(X) * y; + y = y - mfit; +end + + +% --------------------------------------------------------- +% find spike regions +% --------------------------------------------------------- +% EXCLUDE FROM CONSIDERING AS KNOT POINTS BASED ON VELOCITY +veloc = gradient(y); veloc(end) = 0; % clamp last value to 0 +tmp = abs(veloc - median(veloc)); + +% threshold is mad + 1.5 times the interquartile range of abs. deviations +thr = median(tmp) + iqrmult * (prctile(tmp,75) - prctile(tmp,25)); + +spikes = tmp>thr; + + +% EXCLUDE FROM CONSIDERING AS KNOT POINTS BASED ON OUTLIER STATUS +d = abs(y - median(y)); % distance from mean +thr = median(d) + iqrmult * (prctile(d,75) - prctile(d,25)); +sptmp = d>thr; +spikes = spikes + sptmp; + + +% smooth this some, so that low-deviation regions in the middle of spikes +% do not get counted +spikes2 = smooth_timeseries(spikes,4); % excluded from being knot points + +spikes = find(spikes>0); % which points to interpolate in the end +spikes2 = find(spikes2>0); % larger set of pts to not include as knot pts + + +% ------------------------------------------------- +% * get spline fit +% ------------------------------------------------- +bp = zeros(size(y)); +bp(1:knotrate:length(y)) = 1; +bp(isnan(y)) = 0; % ignore NaNs +%bp(end) = 1; % clamp last point to be an endpt +bp(spikes2) = 0; % do not put knot points on spikes + +bp = find(bp); + +nbp = length(bp); +bpy = zeros(nbp, 1); + +% figure out medians for each segment - +% this is the interp knot point. +ytmp = y; +ytmp(spikes2) = NaN; % get rid of spikes + +for i = 1:nbp + + if i == 1 + st = 1; + else + st = bp(i) - round((bp(i) - bp(i-1)) ./ 2); + end + + if i == nbp + en = length(y); + else + en = bp(i) + round((bp(i+1) - bp(i)) ./ 2); + end + data = ytmp(st:en); + bpy(i) = median(data); + +end + + +% xx = 1:length(y); +yy = spline(bp, bpy, 1:length(y)); +if ~iscol(yy), yy = yy'; end + + +% ------------------------------------------------- +% * find pts that are really far from spline fit +% ------------------------------------------------- +d = abs(y - yy); % distance from spline fit +thr = median(d) + iqrmult * (prctile(d,75) - prctile(d,25)); + +spikes3 = d>thr; + +spikes = unique([spikes; find(spikes3>0)]); % final answer +% replace y data with spline fits where spikes occur +y2 = y; +y2(spikes) = yy(spikes); + + + +ntrimmed = length(spikes); + + +if plotme + figure; hold on; + plot(y,'k','LineWidth',2); + plot(y2,'g'); + plot(yy,'b'); + plot(bp, yy(bp), 'b.','MarkerSize', 10); + legend({'Original' 'Adjusted' 'Spline fit'}) + plot(spikes,y2(spikes),'ro','MarkerFaceColor','r','MarkerSize',6); +end + +return \ No newline at end of file diff --git a/Data_processing_tools/trimts.m b/Data_processing_tools/trimts.m new file mode 100755 index 00000000..86d74e9d --- /dev/null +++ b/Data_processing_tools/trimts.m @@ -0,0 +1,134 @@ +function [y,ntrimmed,allw] = trimts(y,sd,X,varargin) +% function [y,ntrimmed,allw] = trimts(y,sd,X,[do spike correct],[trimming iterations],[MADs]) +% 1. Adjusts for scan effects (unless X is empty) +% 2. Windsorizes timeseries to sd standard deviations +% - Recursive: 3 steps +% 3. Adds scan effects back into timeseries +% +% Spike correct: Some attempt at automatic adjustment for abrupt level +% shifts in data; default is 0, enter 1 to do this +% "Spikes" are IDd as values more than 10 MADs (by default) from moving average with 20 image FWHM +% Replaces "spike" data values with moving average +% +% iterations: number of cycles through trimming; default is 3 +% +% MADs: allows you to change the number of MADs above which values are IDd +% as "spikes" +% +% Modified 2/9/05, 02/2008 +% Tor Wager + +% filter y using X matrix; yf is residuals + +ntrimmed = 0; +allw = []; +MADs = 10; + +if nargin < 3, X = []; end + +if ~isempty(X), + mfit = X * pinv(X) * y; + yf = y - mfit; +else + yf = y; +end + +niter = 3; spike = 0; +if ~isempty(varargin), spike = varargin{1}; end +if length(varargin) > 1, niter = varargin{2}; end +if length(varargin) > 2, MADs = varargin{3}; end + +if spike + + % Figure out which values deviate from a moving average + basefit = moving_average('gaussian', yf, 20); + + devs = abs(yf - basefit); + + % More than 10 MADs from moving average with 20 image FWHM + allw = find( devs ./ median(devs) > MADs ) ; + + yf(allw) = basefit(allw); + +% create_figure('ts'); plot(yf); hold on; plot(basefit,'r'); +% plot_vertical_line(find(wh)); + +% % % +% % % % attempt to correct for session-to-session baseline diffs +% % % +% % % tmp = diff(yf); +% % % mad12 = median(abs(tmp)) * Inf; % robust est of change with time (dt) +% % % wh = find(abs(tmp) > mad12); +% % % n = 20; +% % % +% % % for i = 1:length(wh), +% % % st = max(wh(i) - (n-1),1); % start value for avg +% % % en = max(wh(i),1); +% % % st2 = wh(i)+1; +% % % en2 = min(wh(i)+n,length(yf)); % end value for after +% % % wh2 = st2:en2; +% % % m = mean(yf(wh(i)+1:en2)) - mean(yf(st:en)); % average of 5 tp after - 5 time points before +% % % %figure;plot(st:en2,yf(st:en2)); +% % % yf(wh(i)+1:end) = yf(wh(i)+1:end) - m;, +% % % end +% % % +% % % +% % % % do spike correction! Interpolate values linearly with 1 nearest +% % % % neighbor +% % % % +% % % % replace first val with mean +% % % n = min(length(yf),50); +% % % yf(1) = mean(yf(1:n)); +% % % +% % % tmp = diff(yf); +% % % mad5 = median(abs(tmp)) * 5; % robust est of tail of dist. of change with time (dt) +% % % wh = find(abs(tmp) > mad5); +% % % +% % % % find paired changes that are w/i 3 scans +% % % whd = diff(wh); +% % % wh = wh(whd < 3); +% % % whd = whd(whd < 3); +% % % +% % % % value of spike is avg of pre-spike and post-spike val. +% % % wh(wh == 1 | wh == length(yf)) = []; +% % % for i = 1:length(wh) +% % % if wh(i)+1+whd(i)<=length(yf); +% % % yf(wh(i)+1) = mean([yf(wh(i)) yf(wh(i)+1+whd(i))]); +% % % %else +% % % % yf(wh(i)+1) = mean([yf(wh(i)) yf(length(yf))]); +% % % end +% % % end + + +end + + + +% trim residuals to sd standard deviations +% "Windsorize" + +my = mean(yf); + +for i = 1:niter + + if all(yf - nanmean(yf) < eps), return, end % no variance in timeseries!! + + yf2 = scale(yf); + w = find(abs(yf2) > sd); + yf(w) = my + sd * std(yf) * sign(yf(w)-my); + + allw = [allw; w]; +end + +allw = unique(allw); + +% put means back into yf +if ~isempty(X) + y = yf + mfit; +else + y = yf; +end + +ntrimmed = length(allw); % w) + length(w2); + +return \ No newline at end of file diff --git a/Data_processing_tools/use_spm_filter.m b/Data_processing_tools/use_spm_filter.m new file mode 100755 index 00000000..dbd6a012 --- /dev/null +++ b/Data_processing_tools/use_spm_filter.m @@ -0,0 +1,266 @@ +function [S,KL,KH] = use_spm_filter(TR,dims,LChoice,HChoice,HParam,varargin) +% function [S,KL,KH] = use_spm_filter(TR,dim of filter,LChoice,HChoice,HP filter in s,[LP Gauss len in s]) +% K{s}.LChoice - Low-pass filtering {'hrf' 'Gaussian' 'none'} +% K{s}.LParam - Gaussian parameter in seconds +% K{s}.HChoice - High-pass filtering {'specify' 'none'} +% 05/22/01 Tor Wager + +K{1}.RT = TR; +K{1}.LChoice = LChoice; +K{1}.HChoice = HChoice; +K{1}.HParam = HParam; +K{1}.row = ones(dims,1); + +if length(varargin) > 0 + K{1}.LParam = varargin{1}; +end + + +KL = []; KH = []; + +spmS = spm_filter('set',K); + +S = eye(length(K{1}.row)); + +if ~strcmp(HChoice,'none') + KH = full(spmS{1}.KH); + S = S - KH * pinv(KH); +end + +if ~strcmp(LChoice,'none') + KL = full(spmS{1}.KL); + S = KL * S; % lowpass * highpass; hp = I - res forming mtx of S.KH +end +return + + + + + +function [vargout] = spm_filter(Action,K,Y) +% filter routine +% FORMAT [K] = spm_filter('set',K) +% FORMAT [Y] = spm_filter('apply',K,Y) +% +% Action - 'set' fills in filter structure K +% Action - 'apply' applies K to Y = K*Y +% K - filter convolution matrix or: +% K{s} - cell of structs containing session-specific specifications +% +% K{s}.RT - repeat time in seconds +% K{s}.row - row of Y constituting session s +% K{s}.LChoice - Low-pass filtering {'hrf' 'Gaussian' 'none'} +% K{s}.LParam - Gaussian parameter in seconds +% K{s}.HChoice - High-pass filtering {'specify' 'none'} +% K{s}.HParam - cut-off period in seconds +% +% K{s}.HP - low frequencies to be removed +% K{s}.LP - sparse toepltz low-pass convolution matrix +% +% Y - data matrix +% +% K - filter structure +% Y - filtered data K.K*Y +%___________________________________________________________________________ +% +% spm_filter implements band pass filtering in an efficient way by +% using explicitly the projector matrix form of the High pass +% component. spm_filter also configures the filter structure in +% accord with the specification fields if required +%___________________________________________________________________________ +% @(#)spm_filter.m 2.4 Karl Friston 99/08/31 + + +% set or apply +%--------------------------------------------------------------------------- +switch Action + + case 'set' + %------------------------------------------------------------------- + for s = 1:length(K) + + % matrix order + %----------------------------------------------------------- + k = length(K{s}.row); + + % make low pass filter + %----------------------------------------------------------- + switch K{s}.LChoice + + case 'none' + %--------------------------------------------------- + h = 1; + d = 0; + + case 'hrf' + %--------------------------------------------------- + h = spm_hrf(K{s}.RT); + h = [h; zeros(size(h))]; + g = abs(fft(h)); + h = real(ifft(g)); + h = fftshift(h)'; + n = length(h); + d = [1:n] - n/2 - 1; + + case 'Gaussian' + %--------------------------------------------------- + sigma = K{s}.LParam/K{s}.RT; + h = round(4*sigma); + h = exp(-[-h:h].^2/(2*sigma^2)); + n = length(h); + d = [1:n] - (n + 1)/2; + if n == 1, h = 1; end + + otherwise + %--------------------------------------------------- + error('Low pass Filter option unknown'); + return + + end + + % create and normalize low pass filter + %----------------------------------------------------------- + K{s}.KL = spdiags(ones(k,1)*h,d,k,k); + K{s}.KL = spdiags(1./sum(K{s}.KL')',0,k,k)*K{s}.KL; + + + % make high pass filter + %----------------------------------------------------------- + switch K{s}.HChoice + + case 'none' + %--------------------------------------------------- + K{s}.KH = []; + + case 'specify' + %--------------------------------------------------- + n = fix(2*(k*K{s}.RT)/K{s}.HParam + 1); + X = spm_dctmtx(k,n); + K{s}.KH = sparse(X(:,[2:n])); + + otherwise + %--------------------------------------------------- + error('High pass Filter option unknown'); + return + + end + + end + + % return structure + %------------------------------------------------------------------- + vargout = K; + + + case 'apply' + %------------------------------------------------------------------- + if iscell(K) + + + % ensure requisite feild are present + %----------------------------------------------------------- + if ~isfield(K{1},'KL') + K = spm_filter('set',K); + end + + for s = 1:length(K) + + % select data + %--------------------------------------------------- + y = Y(K{s}.row,:); + + % apply low pass filter + %--------------------------------------------------- + y = K{s}.KL*y; + + % apply high pass filter + %--------------------------------------------------- + if ~isempty(K{s}.KH) + y = y - K{s}.KH*(K{s}.KH'*y); + end + + % reset filtered data in Y + %--------------------------------------------------- + Y(K{s}.row,:) = y; + + end + + % K is simply a convolution matrix + %------------------------------------------------------------------- + else + Y = K*Y; + end + + % return filtered data + %------------------------------------------------------------------- + vargout = Y; + + + otherwise + %------------------------------------------------------------------- + warning('Filter option unknown'); + + +end + + + +function C = spm_dctmtx(N,K,n,f) +% Creates basis functions for Discrete Cosine Transform. +% FORMAT C = spm_dctmtx(N,K,n) +% OR C = spm_dctmtx(N,K) +% OR D = spm_dctmtx(N,K,n,'diff') +% OR D = spm_dctmtx(N,K,'diff') +% N - dimension +% K - order +% n - optional points to sample +%____________________________________________________________________________ +% spm_dctmtx creates a matrix for the first few basis functions of a one +% dimensional discrete cosine transform. +% With the 'diff' argument, spm_dctmtx produces the derivatives of the +% DCT. +% +% See: Fundamentals of Digital Image Processing (p 150-154). +% Anil K. Jain 1989. +%____________________________________________________________________________ +% @(#)spm_dctmtx.m 1.3 John Ashburner MRCCU/FIL 96/08/14 + +d = 0; + +if (nargin == 2) + n = (0:(N-1))'; + if (nargin == 3) + d = 1; + end +elseif (nargin == 3) + if (strcmp(n,'diff')) + d = 1; + n = (0:(N-1))'; + else + n = n(:); + end +elseif (nargin == 4) + n = n(:); + if (strcmp(f,'diff')) + d = 1; + else + error('Incorrect Usage'); + end +else + error('Incorrect Usage'); +end + +C = zeros(size(n,1),K); + +if (d == 0) + C(:,1)=ones(size(n,1),1)/sqrt(N); + for k=2:K + C(:,k) = sqrt(2/N)*cos(pi*(2*n+1)*(k-1)/(2*N)); + end +else + for k=2:K + C(:,k) = -2^(1/2)*(1/N)^(1/2)*sin(1/2*pi*(2*n*k-2*n+k-1)/N)*pi*(k-1)/N; + end +end + + diff --git a/EXPT_setup_tools/CreateExpt.m b/EXPT_setup_tools/CreateExpt.m new file mode 100755 index 00000000..43d7b7e2 --- /dev/null +++ b/EXPT_setup_tools/CreateExpt.m @@ -0,0 +1,552 @@ +function EXPT = CreateExpt(Action,varargin) +% EXPT = CreateExpt(Action,varargin) +% +% Creates an EXPT experiment information structure for particular analysis programs +% Checks for needed fields for the particular analysis type and attempts to +% create them if they're not there. +% +% Uses global EXPT structure. +% You must save EXPT.mat yourself after you create it. +% +% +% Tor Wager +% Last modified: Oct 2006; July 2007 +% +% Uses: +% +% Used in BrainTools GUI and with other toolboxes +% +% Methods (Actions) are: +% 'fir','hewma', 'parammod' 'robfit' 'extractdx' 'CreateField' +% +% General methods for setting up analysis-specific information structures +% ------------------------------------------------------------------------- +% EXPT = CreateExpt('Init'); % check for existing EXPT, load existing or use in-memory +% EXPT = CreateExpt('fir'); % set up FIR whole-brain model info +% EXPT = CreateExpt('hewma'); % set up HEWMA whole-brain model info +% EXPT = CreateExpt('parammod'); % set up parametric modulator model info +% EXPT = CreateExpt('robfit'); % set up Robust Regression analysis info +% EXPT = CreateExpt('extractdx'); % set up FIR estimate ROI data extraction info +% +% Methods for setting up specific fields in the EXPT structure +% ------------------------------------------------------------------------- +% EXPT = CreateExpt('CreateField',fieldname,substructure); +% Add a field if it doesn't already exist, in substructure specified by +% string +% Examples are: +% EXPT = CreateExpt('CreateField','subjects'); % create EXPT.subjects +% EXPT = CreateExpt('CreateField','P','SNPM'); % create EXPT.SNPM.P +% EXPT = CreateExpt('CreateField','dxbetas','FIR'); % get hrf timecourse +% image names +% +% EXPT = CreateExpt('extractdx'); % extraction of FIR timecourse setup +% EXPT = CreateExpt('CreateField','im_files'); +% +% tor wager, last update Nov 06 + + +try + EXPT = evalin('base', 'EXPT'); +catch + EXPT = []; +end + + + + if exist('EXPT','var') && isempty(EXPT) + disp('EXPT is empty. Assuming this is new EXPT (if you wanted to use a saved EXPT, you may have to load it.).'); + end + + %-----------------------------functions-called------------------------ + % + %-----------------------------functions-called------------------------ + + + %-Format arguments + %----------------------------------------------------------------------- + if nargin == 0, Action='Init'; end + + switch lower(Action) + + + %====================================================================== + % + % + % Methods of general utility + % These are called directly from the command line + % Or are called by the 'batch' analysis methods below + % + %====================================================================== + + case lower('Init') + % --------------------------------------------------------------------- + + % load EXPT + if isempty(EXPT) && ~(exist('EXPT.mat', 'file')) + disp('You need to create a variable that has information about the experiment (EXPT).'); + disp('This variable is called EXPT, and is stored in the main study directory in EXPT.mat') + disp('No EXPT file in current directory. '); + fprintf(1,'\n') + + elseif ~isempty(EXPT) + disp('Using EXPT already in memory.'); + + elseif exist('EXPT.mat', 'file') + disp('loading EXPT.mat from file in current directory'); load EXPT; + + end + + + case lower('CreateField') + % --------------------------------------------------------------------- + % creates a field with a specific name + switch length(varargin) + case 0, error('Must specify field name.'); + case 1, EXPT = CreateField(EXPT, varargin{1}); + case 2, EXPT = CreateField(EXPT, varargin{1},varargin{2}); + end + + + + %====================================================================== + % + % + % Analysis-specific methods + % These are 'batch' setups for specific analysis toolboxes + % in the SCANlab tool set. + % + %====================================================================== + + % --------------------------------------------------------------------- + case {'fir','hewma'} + % --------------------------------------------------------------------- + + show_banner(Action); + EXPT = CreateField(EXPT, 'subjects'); % List of subject directories + EXPT = CreateField(EXPT, 'im_files','FILES'); % Indiv. subject image names + EXPT = CreateField(EXPT, 'TR'); % Scanning TR + EXPT = CreateField(EXPT, 'HP','FIR'); % High-pass filter + EXPT = CreateField(EXPT, 'nruns','FIR'); + % nruns = number of sessions (intercepts) OR num. of images in each sess, + % e.g., [169 169 172] + + EXPT = CreateField(EXPT, 'numframes','FIR'); + % numframes = number of beta images per event type, e.g., [20 20 20] for FIR + % model + + EXPT = CreateField(EXPT, 'smoothlen','FIR'); % FIR beta-series smoothing (time to zero in imgs) + + EXPT = CreateField(EXPT, 'mask'); + + + + % --------------------------------------------------------------------- + case lower('parammod') + % --------------------------------------------------------------------- + disp('In progress.') + show_banner('ParamMod'); + CreateExpt('FIR') + + + + % --------------------------------------------------------------------- + case lower('robfit') + % --------------------------------------------------------------------- + show_banner('robfit'); + if isfield(EXPT, 'subjects') + cnew = input('Found EXPT.subjects. Re-create? (1/0) '); + if cnew, EXPT = rmfield(EXPT,'subjects'); end + end + EXPT = CreateField(EXPT, 'subjects'); % List of subject directories + if isfield(EXPT,'SNPM') && isfield(EXPT.SNPM,'P'), EXPT.SNPM = rmfield(EXPT.SNPM,'P'); end + if isfield(EXPT,'SNPM') && isfield(EXPT.SNPM,'connames'),EXPT.SNPM = rmfield(EXPT.SNPM,'connames'); end + if isfield(EXPT,'mask'),EXPT = rmfield(EXPT,'mask'); end + + EXPT = CreateField(EXPT, 'P','SNPM'); % images + EXPT = CreateField(EXPT, 'connames','SNPM'); % Contrast names + EXPT = CreateField(EXPT, 'mask'); + + + + % --------------------------------------------------------------------- + case lower('extractdx') + % --------------------------------------------------------------------- + % set up required information to extract FIR model H/T/W timecourses + % into chosen regions of interest (and plot). + show_banner('extractdx'); + + if ~isfield(EXPT.FIR,'dxnames'), error('Please enter cell array of FIR event names in EXPT.FIR.dxnames'); end + + + if ~isfield(EXPT.FIR,'TR'), + if isfield(EXPT,'TR') + EXPT.FIR.TR = EXPT.TR; + else + EXPT.FIR.TR = input('Please enter TR in s: '); + end + end + + if ~isfield(EXPT.FIR,'baseline'), EXPT.FIR.baseline = input('Please enter indices of baseline timepoints or []: '); end + + if ~isfield(EXPT.FIR,'regsofinterest') + disp(EXPT.FIR.dxnames) + wh = input('Enter vector of conditions to plot: '); + EXPT.FIR.regsofinterest = wh; + end + + if ~isfield(EXPT.FIR,'mcol') + mcol = input('Enter vector of colors, e.g. {''ro-'' ''gd'' etc.}: '); + EXPT.FIR.mcol = mcol; + end + + if ~isfield(EXPT.FIR,'indiv') + indiv = input('Enter 1 for breakdown by indiv diffs or 0 for not.'); + EXPT.FIR.indiv = indiv; + if indiv + if ~isfield(EXPT,'beh'), error('NO EXPT.beh vector for indiv diffs -- please enter.'); end + end + else + % do nothing; already exists + % indiv = EXPT.FIR.indiv; + end + + EXPT = CreateExpt('CreateField','dxbetas','FIR'); + + + + + otherwise + % --------------------------------------------------------------------- + error('Unknown action string') + + % --------------------------------------------------------------------- + + + + end + + % return EXPT to base workspace + assignin('base', 'EXPT', EXPT); + + return + + + + + + + + + + %======================================================================= + %======================================================================= + + % sub-functions + + + %======================================================================= + %======================================================================= + + + +function show_banner(str) + + % banner + fprintf(1,'\n* ======================================================================== *') + fprintf(1,'\n* *') + fprintf(1,'\n* CreateExpt: batch data structure setup *') + fprintf(1,'\n* *') + fprintf(1,'\n* Tor Wager, version July 2006 *') + fprintf(1,'\n* *') + fprintf(1,'\n* This function creates a data structure called EXPT that stores info *') + fprintf(1,'\n* about your experimental designs and parameters. This structure can be *') + fprintf(1,'\n* saved in a file called EXPT.mat and may be useful for reference. *') + fprintf(1,'\n* Many analysis toolboxes refer to various fields of EXPT to get filenames *') + fprintf(1,'\n* and other info needed for analysis. *') + fprintf(1,'\n* CreateExpt is a utility tool to help you set this up, but you can create *') + fprintf(1,'\n* or edit any of the fields manually. *') + fprintf(1,'\n* *') + fprintf(1,'\n* This process is sort of the ''weakest link'' in the analyses at present *') + fprintf(1,'\n* Please contact me if you run into bugs. *') + fprintf(1,'\n* *') + fprintf(1,'\n* ======================================================================== *\n') + fprintf(1,'Setting up analysis for: %s\n',str); + fprintf(1,'Required fields that CreateExpt will attempt to create are: \n'); + + switch lower(str) + + case {'fir','hewma'} + reqfields = {'EXPT.subjects' 'EXPT.FILES.im_files' 'EXPT.TR' 'HP' 'EXPT.FIR.nruns' 'EXPT.FIR.numframes' 'EXPT.FIR.smoothlen' 'EXPT.mask'}; + explan = {'List of subject directory names' ... + 'Timeseries image files (full preprocessed data) for each subject in cell array' ... + 'Repetition time (TR) of expt in sec' ... + 'High pass filter cutoff in sec' ... + 'Vector of number of images in each run, e.g., [160 160 180] for a 3-run expt.' ... + 'Number of FIR beta images per event type' ... + 'Exponential smoothing kernel length for FIR estimates (0 or integer)' ... + 'Name of mask image containing in-analysis voxels.'}; + + case {'robfit'} + reqfields = {'EXPT.subjects' 'EXPT.SNPM.P' 'EXPT.SNPM.connames' 'EXPT.SNPM.connums' 'EXPT.mask'}; + explan = {'List of subject directory names' ... + 'Cell array; each cell is a string matrix of image file names, one per subject; one cell per contrast' ... + 'String matrix of names of each contrast, corresponding to cells in EXPT.SNPM.P' ... + 'Numbers of contrast images,corresponding to cells in EXPT.SNPM.P; determines output directory names' ... + 'Name of mask image containing in-analysis voxels.'}; + + + case {'extractdx'} + + reqfields = {'EXPT.FIR.dxnames' 'EXPT.FIR.TR' 'EXPT.FIR.baseline' 'EXPT.FIR.regsofinterest' 'EXPT.FIR.mcol' 'EXPT.FIR.indiv' 'EXPT.FIR.dxbetas'}; + explan = {'Cell array of names of conditions for each FIR HRF estimated (create before running!)' ... + 'Repetition time (TR) of expt in sec' ... + 'Baseline timepoints each each FIR HRF (subtracted from HRF) ' ... + 'Indices of which conditions in EXPT.FIR.dxnames are of interest to plot' ... + 'Cell array of colors for each condition of interest' ... + 'Flag to separate plots by individual difference variable (1 or 0)' ... + 'Cell array of str matrices of all images dx* that contain FIR beta estimates, one cell per condition, Ss are rows' }; + + + + + otherwise, warning(['Unknown setup command option:' str]); + end + + for i = 1:length(reqfields), fprintf(1,'%s\t\t%s\n',reqfields{i},explan{i}); end + fprintf(1,'\n* ======================================================================== *\n') + fprintf(1,'\n'); + + return + + + + +function EXPT = CreateField(EXPT, fname,varargin) + % CreateField(EXPT, fname,[subfield string]) + % + % Multi-function function to create fields in EXPT + % + + %======================================================================= + % check to see if it exists; if so, leave alone + %======================================================================= + str = 'EXPT'; + + if ~isempty(varargin) + str = [str '.' varargin{1}]; + + isparentfield = isfield(EXPT,varargin{1}); + if ~isparentfield, EXPT.(varargin{1}) = []; end + + targetfield = [varargin{1} '.' fname]; % for image name utility + else + targetfield = fname; + end + + try + is = isfield(eval(str),fname) && ~isempty(eval([str '.' fname])); + catch + error('Trying to create subfield when parent doesn''t exist? Create parent field manually.'); + end + + if is + fprintf(1,'%s exists.\n',[str '.' fname]); + return + else + fprintf(1,'Creating field: %s.\n',[str '.' fname]); + end + + %======================================================================= + % field definition methods + %======================================================================= + switch lower(fname) + + + % --------------------------------------------- + % Names of each subject's directory + % --------------------------------------------- + case lower('subjects') + EXPT = get_expt_subdir(EXPT); + + % --------------------------------------------- + % Image files for single-subject models (processed imgs) + % --------------------------------------------- + case lower('im_files') + fprintf(1,'Getting individual functional images for analysis from subject directories.\n'); + wcard = input('Enter image wildcard (e.g., sn*img): ','s'); + sdir = input('Enter subdirectories within subject dirs to look in (e.g., scan*, or return for none): ','s'); + EXPT = getfunctnames2(EXPT,wcard,targetfield,sdir); + fprintf(1,'\n'); + + % --------------------------------------------- + % Parameters needed to run single-subject models + % --------------------------------------------- + case lower('TR') + if ~isempty(varargin) + EXPT.(varargin{1}).(fname) = input('Enter TR in s: '); + else + EXPT.(fname) = input('Enter TR in s: '); + end + + case lower('HP') + if ~isempty(varargin) + EXPT.(varargin{1}).(fname) = input('Enter HP filter in s: '); + else + EXPT.(fname) = input('Enter HP filter in s: '); + end + + case lower('nruns') + quest = sprintf('Enter number of sessions (intercepts) OR num. of images in each sess\ne.g., [169 169 172] for three runs with 169 imgs in first run. : '); + if ~isempty(varargin) + EXPT.(varargin{1}).(fname) = input(quest); + else + EXPT.(fname) = input(quest); + end + + case lower('numframes') + quest = sprintf('Enter number of beta images per event type in FIR, e.g., [20 20 20] : '); + if ~isempty(varargin) + EXPT.(varargin{1}).(fname) = input(quest); + else + EXPT.(fname) = input(quest); + end + + case lower('smoothlen') + quest = sprintf('Enter smoothing length time-to-zero influence (s) for beta FIR estimates (e.g., 6), : '); + if ~isempty(varargin) + EXPT.(varargin{1}).(fname) = input(quest); + else + EXPT.(fname) = input(quest); + end + + % --------------------------------------------- + % Get an analysis mask name and reslice if necessary + % --------------------------------------------- + case lower('mask') + P = scan_get_files(Inf,'*.img','Select mask image for analysis, or no image for default.',pwd); + if isempty(P), P = which('scalped_avg152T1_graymatter_smoothed.img'); end + if ~isempty(varargin) + EXPT.(varargin{1}).(fname) = P; + else + EXPT.(fname) = P; + end + + try + % check mask against first image here. + V1 = spm_vol(EXPT.mask); V1 = V1.mat; V2 = spm_vol(deblank(EXPT.FILES.im_files{1}(1,:))); V2 = V2.mat; + go = V1 - V2; go = any(go(:)); + if go + [P,EXPT.(fname)] = reslice_imgs(deblank(EXPT.FILES.im_files{1}(1,:)),EXPT.(fname),1); + end + disp('Checked mask against first image in EXPT.FILES.im_files. Resliced mask if necessary.'); + catch + % we don't always have im_files. + end + + + + % --------------------------------------------- + % for fir dxbeta images + % --------------------------------------------- + case lower('dxbetas') + startdir = pwd; + disp('Are the dx_beta*img files in subject directories below this directory (type 1 or 0):'); + isok = input(startdir); + if ~isok, startdir = scan_get_files(-1,'*','Select parent dir for Ss dirs with dx_beta*img'); end + + fprintf(1,'Getting individual HRF timecourses from dx_beta*img in subject directories.\n'); + fprintf(1,'(And making sure they''re in ascending numeric order).\n'); + EXPT = getfunctnames2(EXPT,'dx*img','FIR.dxbetas','.',startdir); + fprintf(1,'\n'); + + + % --------------------------------------------- + % For group analysis + % for EXPT.SNPM.P, filenames to put into robfit + % --------------------------------------------- + case lower('P') + + disp('Collect images for random effects.') + if ~isfield(EXPT,'subjects') || isempty(EXPT.subjects), error('You need to create EXPT.subjects first.'); end + + wcard = input('Enter image wildcard (e.g., con*img): ','s'); + + %d = dir([EXPT.subjects{1} filesep wcard]); + % More general: can handle subdirectories prefixed to image + % wildcard. + % get names of files with relative paths + d = filenames([EXPT.subjects{1} filesep wcard]); + dir_prefix = fileparts(wcard); + names = cell(length(d), 1); + + for i = 1:length(d) + [dd, ff, ee] = fileparts(d{i}); + names{i} = fullfile(dir_prefix, [ff ee]); + end + + + fprintf('Examining first subject. Found %3.0f images:\n', length(d)); + fprintf('%s\n', d{:}) + fprintf('Assuming same images exist for other subjects in same dir structure.\n') + + for i = 1:length(d) + for j = 1:length(EXPT.subjects) + + if j == 1 + EXPT.SNPM.P{i} = fullfile(pwd, EXPT.subjects{j}, names{i}); + else + EXPT.SNPM.P{i} = char(EXPT.SNPM.P{i}, fullfile(pwd, EXPT.subjects{j}, names{i})); + end + end + end + +% % for i = 1:length(d) +% % EXPT = getfunctnames2(EXPT,d(i).name,'tmp'); tmp = str2mat(EXPT.tmp{:}); +% % if ~isempty(varargin) +% % EXPT.(varargin{1}).(fname){i} = tmp; +% % disp(['Saved images in: EXPT.' varargin{1} '.' fname '{' num2str(i) '}']); +% % else +% % EXPT.(fname){i} = tmp; +% % disp(['Saved images in: EXPT.' fname '{' num2str(i) '}']); +% % end +% % end +% % EXPT = rmfield(EXPT,'tmp'); + + % --------------------------------------------- + % For group analysis + % for EXPT.SNPM, get contrast names to put into robfit + % --------------------------------------------- + case lower('connames') + + if isempty(varargin), error('You need to specify a substructure for the connames field. e.g., ''connames'',''SNPM'''); end + + disp('Add information about the contrast names.') + if ~isfield(EXPT,'SNPM'), error('You need to create EXPT.SNPM. Try EXPT = CreateExpt(''CreateField'',''P'',''SNPM'');'); end + if ~isfield(EXPT.SNPM,'P') || isempty(EXPT.SNPM.P), error('You need to get image names in EXPT.SNPM.P first. Try EXPT = CreateExpt(''CreateField'',''P'',''SNPM'');'); end + + EXPT.SNPM.connames = []; + for i = 1:length(EXPT.SNPM.P) + fprintf(1,'\nImages in this contrast: \n'); + disp(EXPT.SNPM.P{i}) + tmp = input('Enter contrast name for this set (avoid spaces/special chars): ','s'); + EXPT.SNPM.connames = str2mat(EXPT.SNPM.connames,tmp); + end + + EXPT.SNPM.connames = EXPT.SNPM.connames(2:end,:); + EXPT.SNPM.connums = 1:length(EXPT.SNPM.P); + + + otherwise + %======================================================================= + error('Unknown action string') + + %====================================================================== + + end + + + return + + + + diff --git a/EXPT_setup_tools/get_expt_subdir.m b/EXPT_setup_tools/get_expt_subdir.m new file mode 100755 index 00000000..cbfd840f --- /dev/null +++ b/EXPT_setup_tools/get_expt_subdir.m @@ -0,0 +1,21 @@ +function EXPT = get_expt_subdir(EXPT) +% EXPT = get_expt_subdir(EXPT) +% +% Gets names of individual subject directories and stores them in a field +% called EXPT.subjects +% +% example to create a new EXPT structure: +% EXPT = get_expt_subdir([]); + + +EXPT.subjects = []; +wc = input('Enter wildcard (e.g., 0*) :','s'); + +d = dir(wc); + +for i = 1:length(d), EXPT.subjects{i} = d(i).name; end + +return + + + \ No newline at end of file diff --git a/EXPT_setup_tools/getfunctnames2.m b/EXPT_setup_tools/getfunctnames2.m new file mode 100755 index 00000000..89e6b205 --- /dev/null +++ b/EXPT_setup_tools/getfunctnames2.m @@ -0,0 +1,78 @@ +function EXPT = getfunctnames(EXPT,varargin) +% EXPT = getfunctnames(EXPT,[image wildcard],[save in field],[search in subjects],[startdir]) +% +% Examples: +% +% for contrast/FIR model images +% EXPT = getfunctnames2(EXPT,'dx*img','FIR.dxbetas'); +% EXPT = getfunctnames2(EXPT,'swa*img','FILES.im_files_neg','functional/neg_scans/scan*'); +% +% Example to get a set of contrast images and store them in EXPT.SNPM.P +% for i = 1:9 +% EXPT.tmp = {}; +% EXPT = getfunctnames2(EXPT,['con_000' num2str(i) '.img'],'tmp'); EXPT.tmp = str2mat(EXPT.tmp{:}); +% EXPT.SNPM.P{i} = EXPT.tmp; +% end + +wcard = 'w*img'; if length(varargin) > 0, wcard = varargin{1}; end +fldname = 'nravols'; if length(varargin) > 1, fldname = varargin{2}; end +subjects = '.'; if length(varargin) > 2, subjects = varargin{3}; end +startdir = pwd; if length(varargin) > 3, startdir = varargin{4}; end + +origdir = pwd; +if isdir(startdir), cd(startdir), else, error('Start directory is invalid--not a directory.'); end + +fprintf(1,'Finding %s images in subjects %s, storing in %s\n',wcard,subjects,fldname); + +if ~isfield(EXPT, 'subjects') + disp('You must have a field in EXPT called EXPT.subjects that lists all subject names.') + disp('Each subject name should go in a cell in EXPT.subjects.'); + error('Exiting now.'); +end + +% simpler, faster way +for s = 1:length(EXPT.subjects) + + fprintf(1,'%s.',EXPT.subjects{s}) + + dd = []; p = pwd; + + %for i = 1:4 + + %d = dir(fullfile(EXPT.subjects{s} '/scan' num2str(i) '/' wcard]); d = str2mat(d.name); + + %d = get_filename2(fullfile(EXPT.subjects{s},subjects,wcard)); + d = filenames(fullfile(EXPT.subjects{s},subjects,wcard),'char'); + + %dp = fullfile(p,EXPT.subjects{s},['scan' num2str(i)],filesep); + + % add current path to start of names for full paths + dp = fullfile(p,filesep); + dp = repmat(dp,size(d,1),1); + d = [dp d]; + + dd = [dd; d]; +%end + +str = (['EXPT.' fldname '{s} = dd;']); +eval(str) +end + +d2 = d(:,end-7:end-4); +if length(unique(d2,'rows')) < length(d2) + disp('Multiple files with same number! Sorting off -- check output lists for order correctness.') + +else + + + fprintf(1,'\nChk sorting. '); + + str = (['EXPT.' fldname ' = sort_image_filenames([EXPT.' fldname ']);']); + disp(str) + eval(str) +end + +cd(origdir) + +return + diff --git a/Filename_tools/check_valid_imagename.m b/Filename_tools/check_valid_imagename.m new file mode 100755 index 00000000..746d9266 --- /dev/null +++ b/Filename_tools/check_valid_imagename.m @@ -0,0 +1,69 @@ +function P = check_valid_imagename(P, varargin) +% P = check_valid_imagename(P, [return error]) +% +% optional input: 0 to return empty output, 1 to break with an error message, +% 2 to return list of bad/missing images, +% or nothing to select missing filenames graphically + +if isempty(P) + if varargin{1} + error(sprintf('No images to check.\n')); + elseif ~isempty(varargin) + return + end +end + +wasbad = false(size(P, 1), 1); + +for i = 1:size(P,1) + + img = deblank(P(i, :)); + + % take off trailing commas (SPM notation for multiple vols) + wh = find(img == ','); + if ~isempty(wh) + wh = wh(end); + img(wh:end) = []; + end + + if ~(exist(img, 'file')) + + if ~isempty(varargin) && varargin{1} == 2 + wasbad(i, 1) = 1; + + elseif ~isempty(varargin) && varargin{1} == 1 + error(sprintf('Cannot find image:\n%s\n', P(i, :))); + + elseif ~isempty(varargin) + % do nothing - return empty P + P = []; + return + + else + fprintf(1,'Cannot find image:\n%s\nPlease select correct directory.\n',P(i,:)); + + dd = spm_select(1,'dir','Select directory.'); + dd = [dd filesep]; + + [myd] = fileparts(P(i,:)); + len = length(myd); + + P = [repmat(dd,size(P,1),1) P(:,len+1:end)]; + + disp(P(i,:)); + + end + + end +end % list of images + +if varargin{1} == 2 + fprintf('%3.0f OK images, %3.0f bad/missing images\n', sum(~wasbad), sum(wasbad)); + if any(wasbad) + fprintf('Missing images:\n'); + fprintf('%3.0f ', find(wasbad)) + fprintf('\n'); + end +end + +end diff --git a/Filename_tools/copy_image_files.m b/Filename_tools/copy_image_files.m new file mode 100644 index 00000000..2dbbc544 --- /dev/null +++ b/Filename_tools/copy_image_files.m @@ -0,0 +1,159 @@ +function output_names = copy_image_files(image_list, to_dir, varargin) +% +%function output_names = copy_image_files(image_list, to_dir, [method='copy' or 'move'], varargin) +% +% Copies a set of image files from one directory to another, creating the +% directory if needed. +% +% MAC OSX only!! +% +% For .img files, will look for a paired .hdr and copy it automatically. +% Works for non-image files (e.g., .txt) as well. +% +% Other variable args: +% Will take flags: +% 'append image number' -> will append a number to each file corresponding +% to the order listed, e.g., 1 for the first, 2 for the 2nd, etc. +% 'append string' -> followed by string to append +% +% with both flags and 'run' for append string, a matrix of +% [run1/vols.img; run2/vols.img] would become [vols_run0001.img vols_run0002.img] + +method = 'copy'; +donumberappend = 0; +appstr = []; + +if ~isempty(varargin), method = varargin{1}; end + +for i = 2:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'append image number', donumberappend = 1; + case 'append string', appstr = varargin{i + 1}; varargin{i + 1} = []; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +switch method + case {'copy', 'test'} + strbase = ['!cp ']; % copy, for mac + + case 'move' + strbase = ['!mv ']; % copy, for mac + + otherwise error('Enter ''copy'', ''move'', or ''test'' for method') +end + +if strcmp(method, 'test') + disp('TEST ONLY -- NOTHING WILL BE COPIED'); +end + + + + + + +% get/make output directory name +if ~exist(to_dir, 'dir') + fprintf('Creating directory: %s\n', to_dir); + mkdir(to_dir) +end + +output_names = []; + +for i = 1:size(image_list, 1) % for each + + numappstr = ''; + if donumberappend, numappstr = sprintf('%04d', i); end + + img_name = deblank(image_list(i, :)); + + % fix SPM-specific: remove ,# at end if added by SPM + if img_name(end-1) == ',' + img_name = img_name(1:end-2); + end + + if ~exist(img_name, 'file') + warning('copy_image_files:image does not exist', 'Check locations?'); + end + + [dd, ff, ee] = fileparts(img_name); + + % add escape characters before spaces + dd = add_escape_chars(dd); + + + inname1 = [ff ee]; + outname1 = [ff appstr numappstr ee]; + + + str1 = [strbase fullfile(dd, inname1) ' ' fullfile(to_dir, outname1)]; + disp(str1) + + if ~strcmp(method, 'test') + eval(str1) + end + + if strcmp(ee, '.img') % if Analyze... + inname2 = [ff '.hdr']; + inname3 = [ff '.mat']; + outname2 = [ff appstr numappstr '.hdr']; % look for header as well + outname3 = [ff appstr numappstr '.mat']; % look for mat file as well (SPM) + + if ~exist(fullfile(dd, inname2), 'file') + warning('copy_image_files:missing .hdr', '.hdr file missing!!!'); + end + + str2 = [strbase fullfile(dd, inname2) ' ' fullfile(to_dir, outname2)]; + disp(str2) + + str3 = [strbase fullfile(dd, inname3) ' ' fullfile(to_dir, outname3)]; + if exist(fullfile(dd, inname3), 'file'), disp(str3), end + + %should use this instead + %[ok, msg, msgid] = copyfile(subject_from_dir_final, subject_to_dir); + + + if ~strcmp(method, 'test') + eval(str2) + if exist(fullfile(dd, inname3), 'file'), eval(str3), end + end + + end + + output_names = strvcat(output_names, fullfile(to_dir, outname1)); + +end + + +disp('Done.') +disp(' '); + +end + + +function newff = add_escape_chars(ff) + +wh = find(deblank(ff) == ' '); +wh = [1 wh length(deblank(ff)) + 1]; +echar = '\'; + +for i = 1:length(wh) - 1 + + newff{1, i} = ff(wh(i):wh(i+1) - 1); + + if length(wh) > 2 && i < length(wh) - 2 + newff{1, i} = [newff{1, i} echar]; + end + +end + +newff = cat(2, newff{:}); + +end + + + diff --git a/Filename_tools/delete_ana_imgs.m b/Filename_tools/delete_ana_imgs.m new file mode 100644 index 00000000..e49dec5c --- /dev/null +++ b/Filename_tools/delete_ana_imgs.m @@ -0,0 +1,15 @@ +% delete_ana_imgs(imgs) +% Function to delete a list of .img files +% - automatically removes associated .hdr files as well. +% - accepts cellstr or char inputs + +function delete_ana_imgs(imgs) + imgs = cellstr(imgs); + for i = 1:length(imgs) + delete(imgs{i}); + delete([imgs{i}(1:end-4) '.hdr']); + if(exist([imgs{i}(1:end-4) '.mat'], 'file')) + delete([imgs{i}(1:end-4) '.mat']); + end + end +end \ No newline at end of file diff --git a/Filename_tools/dicom_tarzip.m b/Filename_tools/dicom_tarzip.m new file mode 100644 index 00000000..2fee2cb7 --- /dev/null +++ b/Filename_tools/dicom_tarzip.m @@ -0,0 +1,152 @@ +function dicom_tarzip(varargin) +% +% tars all .dcm files in subdirectories you specify, and deletes the +% original .dcm files if successful. +% +% Note: Ash's checksum program would be very useful to add here. +% +% Inputs: +% none (default): all directories under the current one +% directory wildcard, e.g., 'run*' to search only those subdirectories. +% absolute path names should also be ok. +% +% examples: +% +% dicom_tarzip +% +% dicom_tarzip('18*') +% +% Or, let's say you have runs within subjects, and dicom files within run dirs. +% Subject dirs all start with NSF*. run dirs all start with 18* +% Starting from the directory above subjects, you could do this to zip ALL subjects: +% subjdirs = dir('NSF*'); +% for s = 1:length(subjdirs) +% dicom_tarzip([subjdirs(s).name filesep '18*']); +% end +% +% Needless to say, use this with extreme caution, as it deletes your original files. +% And test it thoroughly with a backup dataset first!! + +dirwildcard = '*'; + +if ~isempty(varargin), dirwildcard = varargin{1}; end + +prevwd = pwd; + + +% list directories + +d = dir(dirwildcard); + +isd = cat(1, d.isdir); +d = d(isd); +d(strmatch('.', char(d.name))) = []; % exclude . dirs + +if isempty(d) + fprintf('No matching directories for %s. Quitting.\n', dirwildcard); + cd(prevwd); + return +else + disp('Working on directories:') + disp(char(d.name)) + disp(' ') +end + + +basedir = fullfile(pwd, fileparts(dirwildcard)); +% % dir returns relative paths, so go there if we have a path name for a +% % wildcard +% if ~isdir(basedir) +% fprintf('%s is not a directory. Quitting.\n\n', basedir); +% return +% end +if ~isempty(basedir), cd(basedir); end % this would be the subject directory if a path name was entered as a wildcard +fprintf('Working in dir: %s\n', basedir) + + +for i = 1:length(d) + + + + dicomtarname = [d(i).name filesep d(i).name '_dicoms.tar']; + + fprintf('Working on: %s.\n', dicomtarname); + + if exist(dicomtarname, 'file') + fprintf('%s already exists!!! Skipping.\n\n', dicomtarname); + continue + end + + str = ['!tar cvf ' dicomtarname ' ' d(i).name filesep '*dcm >& tarchecktmp.txt > tarfilelisttmp.txt']; + disp(str) + eval(str) + errchk = fileread('tarchecktmp.txt'); + isok = isempty(strfind(errchk, 'Error')); + + if ~isok + fprintf('Error in creating: %s .\n', dicomtarname); + disp(errchk) + disp('Removing failed tar archive.') + str = ['!rm ' dicomtarname]; + disp(str) + eval(str) + + else + % remove original files + fprintf('%s created successfully. Checking number of files...\n', dicomtarname); + + % check length/files + % TOO LONG str = ['!tar -t ' dicomtarname ' > tarchecktmp.txt']; + %filelist = fileread('tarfilelisttmp.txt'); + fid = fopen('tarfilelisttmp.txt'); + filelist = textscan(fid, '%s'); + fclose(fid); + filelist = filelist{1}; + nfiles = length(filelist); + + dd = dir([d(i).name filesep '*dcm']); + nfiles2 = length(dd); + + if nfiles == nfiles2 + fprintf('tar archive contains %3.0f files.\n', nfiles); + fprintf('Removing originals.\n'); + str = ['!rm ' d(i).name filesep '*dcm']; + disp(str) + eval(str) + disp(' ') + else + fprintf('tar archive contains %3.0f files, but counted %3.0f that should be there.\n', nfiles, nfiles2); + disp('Check this. Not removing originals.'); + end + + end + + !rm tarchecktmp.txt + !rm tarfilelisttmp.txt + + disp(' ') + + + + +end + +if ~isempty(basedir), cd(prevwd); end + +end % main function + +%dcmf = filenames([d(i).name filesep '*dcm'], 'char'); + +%% +% dcmf2 = []; +% if ~isempty(dcmf) +% for ff = 1:size(dcmf, 1) +% dcmf2 = strcat(dcmf2, [dcmf(ff, :) ' ']); +% end +% +% end +% str = ['!tar cvf ' d(i).name '_dicoms.tar ' dcmf2]; + + + + diff --git a/Filename_tools/escapeForShell.m b/Filename_tools/escapeForShell.m new file mode 100644 index 00000000..3a40587f --- /dev/null +++ b/Filename_tools/escapeForShell.m @@ -0,0 +1,31 @@ +function escapedString = escapeForShell(string) + % [escapedString] = escapeForShell(string) + % + % Returns converted string for use with the current OS's shell. Hence, it's + % operation will be different, depending on where it's executed. + + if(ischar(string)) + for i=1:size(string,1) + escapedString{i} = efsReplace(string(i,:)); + end + escapedString = char(escapedString); + elseif(iscellstr(string)) + for i=1:length(string) + escapedString{i} = efsReplace(string{i}); + end + else + error('Input is not a string or cellstr.'); + end +end + +function e = efsReplace(s) + e = s; + if(~ispc()) + e = strrep(e, '\', '\\'); + end + e = strrep(e, '"', '\"'); + e = strrep(e, '''', '\'''); + e = strrep(e, ' ', '\ '); + e = strrep(e, '(', '\('); + e = strrep(e, ')', '\)'); +end \ No newline at end of file diff --git a/Filename_tools/expand_4d_filenames.m b/Filename_tools/expand_4d_filenames.m new file mode 100644 index 00000000..15dba3a1 --- /dev/null +++ b/Filename_tools/expand_4d_filenames.m @@ -0,0 +1,118 @@ +% [spm_imgs] = expand_4d_filenames(imgs, [nvols]) +% +% Expand a list of image names to 4-D name format as used by SPM2/5 +% Also compatible with SPM8. +% +% This function runs with either imgs, nvols, or both variables defined +% If the images exist, spm_imgs is returned as a char matrix with each row +% in the following format: 'path/filename.ext,[N]' where [N] is the number +% of the given image. The space after [N] is padded with blanks to ensure +% that all the image names fit properly. +% +% spm_imgs = expand_4d_filenames('run03.img', 10) + +function [spm_imgs] = expand_4d_filenames(imgs, nvols) + + spm_imgs = {}; + if(~exist('imgs', 'var') || isempty(imgs)), error('imgs variable is empty in expand_4d_filenames'); end + imgs = cellstr(imgs); + num_files = length(imgs); + + if(exist('nvols', 'var')) + if(length(nvols) == 1) + nvols = 1:nvols; + end + nvols = repmat({nvols}, num_files, 1); + end + + for i = 1:num_files + img_name = imgs{i}; + + if(any(img_name == ',')) + spm_imgs{end+1} = img_name; + + elseif(~exist(img_name, 'file')) + + spm_imgs(i) = cell(1, 1); + + if ~exist('nvols', 'var') + % not existing image AND no vol information + spm_imgs{i}{1} = []; + + else + % no existing images, BUT we have nvols + % create a list of to-be-created images + fprintf('No existing file (yet): %s\n', img_name); + disp('Returning names of to-be-created volumes.') + + for j = nvols{i} + curr_frame = [img_name ',' int2str(j)]; + spm_imgs{i}{end+1} = curr_frame; + end + + end + + if iscell(spm_imgs{i}) + spm_imgs{i} = strvcat(spm_imgs{i}{:}); + end + + else %existing image, but unknown volume number... + % .. basically just uses spm_vol + if(nargin < 2) + nvols{i} = 1:num_frames(img_name); + end + + for j = nvols{i} + curr_frame = [img_name ',' int2str(j)]; + spm_imgs{end+1} = curr_frame; + end + end + end + + spm_imgs = char(spm_imgs); +end + + + +% .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% number of images n stored in this file +% % ------------------------------------------------------------------- +function [n] = num_frames(img_name) + + % This used FSL if available, but I commented it out. + % % global FSLDIR; + % % + % % if(isempty(FSLDIR)) + % % scn_setup(); + % % end + % % + % % if(exist(sprintf('%s/bin/fslnvols', FSLDIR), 'file')) + % % cmd = sprintf('export FSLDIR=%s && . %s/etc/fslconf/fsl.sh && %s/bin/fslnvols "%s" ', FSLDIR, FSLDIR, FSLDIR, img_name); + % % [error_status, result] = system(cmd); + % % if(error_status) + % % error(result); + % % else + % % n = str2double(result); + % % end + % % else + + switch(spm('ver')) + case 'SPM2' + V = spm_vol(img_name); + + fp = fopen(img_name); + fseek(fp,0,'eof'); + Len = ftell(fp); + fclose(fp); + n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); + + case {'SPM5', 'SPM8'} + V = spm_vol(img_name); + n = length(V); + + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + % end + +end \ No newline at end of file diff --git a/Filename_tools/filename_get_new_root_dir.m b/Filename_tools/filename_get_new_root_dir.m new file mode 100755 index 00000000..c465baeb --- /dev/null +++ b/Filename_tools/filename_get_new_root_dir.m @@ -0,0 +1,60 @@ +function Pout = filename_get_new_root_dir(P,newroot,nlevels) +% Pout = filename_get_new_root_dir(P,newroot,nlevels) +% +% P is files (string matrix) +% +% Append a new root directory to filenames +% if newroot is empty, prompt +% +% Keeps nlevels dirs deep from old directory: +% 1 keeps only filename +% 2 keeps filename plus last subdir +% 3 keeps filename plus last 2 subdirs, etc. +% +% tor wager +% +% Examples: +% Pout = filename_get_new_root_dir(EXPT.SNPM.P{3},pwd,2) +% +% Change root dir to pwd for all images in EXPT +% for i=1:length(EXPT.SNPM.P), EXPT.SNPM.P{i} = filename_get_new_root_dir(EXPT.SNPM.P{i},pwd,2); end +% +% Note: see regexprep.m for a simpler way!!! This function could be +% improved by using it. + +if isempty(newroot), newroot = spm_get(-1,'*','Get new root directory'); end + +Pout = get_Pout(P(1,:),newroot,nlevels); + + +for i = 2:size(P,1) + + Pout = str2mat(Pout,get_Pout(P(i,:),newroot,nlevels)); + +end + +for i = 1:size(Pout, 1) + isfile(i) = exist(deblank(Pout(i,:)), 'file'); +end +fprintf(1,'Found %3.0f valid files.\n', sum(isfile > 0)); + + +return + + +function Pout = get_Pout(p,str,nlevels); + +p = deblank(p); +[d,f,e] = fileparts(p); +filename = [f e]; + +for i = 2:nlevels + [d,dd] = fileparts(d); + filename = fullfile(dd,filename); +end + +Pout = fullfile(str,filename); + + +return + diff --git a/Filename_tools/filenames.m b/Filename_tools/filenames.m new file mode 100644 index 00000000..85b3a02b --- /dev/null +++ b/Filename_tools/filenames.m @@ -0,0 +1,149 @@ +% files = FILENAMES(pattern, ['absolute'], ['cellstr'|'char'], ['sortField', fieldNums], ['sortFieldSeparator', separator_string], ['verbose']) +% +% A utility function that uses ls or dir on a pattern and returns a list of +% filenames +% OPTIONS: +% 'absolute' - forces filenames to have an absolute path from root - default +% is to return based upon the pattern (i.e., a relative pattern will +% produce relative paths, an absolute pattern will return absolute +% paths +% 'cellstr' - returns filenames as a cell array of strings, a cellstr - +% this is the default behavior +% 'char' - return filenames as a character array +% 'verbose' - display commands +% PARAMS: +% 'sortField' - if set, sort the filenames numerically based on the fields specified - +% type "man sort" in a terminal window for more info +% 'sortFieldSeparator' - string to determine what is considered a field +% in the filename - defaults to whitespace +% +% Examples: +% % returns a cellstr of dcm files, sorted by the 3rd field, as +% % separated by the '-' character +% dcm_files = filenames('*.dcm', 'sortField', 3, 'sortFieldSeparator', '-'); + +function files = filenames(pattern, varargin) + + returnChar = 0; + mustBeAbsolute = 0; + sortField = []; + sortFieldSeparator = []; + verbose = 0; + + for i=1:length(varargin) + if(ischar(varargin{i})) + switch(varargin{i}) + case 'char' + returnChar = 1; + case 'cellstr' + returnChar = 0; + case 'absolute' + mustBeAbsolute = 1; + case 'sortField' + sortField = varargin{i+1}; + case 'sortFieldSeparator' + sortFieldSeparator = varargin{i+1}; + case 'verbose' + verbose = 1; + end + end + end + + if(mustBeAbsolute && ~isAbsolutePath(pattern)) + pattern = fullfile(pwd(), pattern); + end + + isPCandIsRelative = pcIsRelativePath(pattern); + command = shellCommand(pattern, isPCandIsRelative, sortField, sortFieldSeparator); + if(verbose), disp(command); end + [status, output] = system(command); + outputString = java.lang.String(deblank(output)); + + if(outputString.indexOf('Parameter format not correct') ~= -1) + error('Incorrect format for "%s". Are you sure you''re using the right kind of slashes?', pattern); + elseif(outputString.indexOf('No such file or directory') ~= -1 || outputString.indexOf('File Not Found') ~= -1) + if(returnChar), files = []; else files = {}; end + else + files = char(outputString.split('\n')); + if(isPCandIsRelative) + basepath = fileparts(pattern); + if(~isempty(basepath)) + files = [repmat([basepath filesep()], size(files,1), 1) files]; + end + end + if(~returnChar) + files = cellstr(files); + end + end + files = removeBlankLines(files); +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Subfunctions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function command = shellCommand(pattern, isPCandIsRelative, sortField, sortFieldSeparator) + if(isunix()) + command = sprintf('ls -1d %s', escapeForShell(pattern)); + if(~isempty(sortField)) + command = [command ' | ' sortCommand(sortField, sortFieldSeparator)]; + end + elseif(ispc()) + if(~isempty(sortField)) + error('Cannot currently sort on PCs. No sort command.'); + end + whFileSeps = strfind(pattern, filesep()); + whWildcards = strfind(pattern, '*'); + % if there's a wildcard before the last file separator, we have a problem + if(length(whFileSeps) > 0 && length(whWildcards) > 0 && whWildcards(1) < whFileSeps(end)) + error('There appears to be a wildcard before the last file separator in "%s", which Windows cannot handle, unfortunately.', pattern); + else + if(isPCandIsRelative) + command = sprintf('dir /B %s', escapeForShell(pattern)); + else + command = sprintf('dir /B /S %s', escapeForShell(pattern)); + end + end + else + error('What kinda system you got?'); + end +end + +function command = sortCommand(sortField, sortFieldSeparator) + commandParams = {}; + if(~isempty(sortFieldSeparator)) + commandParams{end+1} = sprintf('-t ''%s''', sortFieldSeparator); + end + + for i=1:length(sortField) + commandParams{end+1} = [' -k ' num2str(sortField(i))]; + end + command = sprintf('sort -n %s', implode(commandParams, ' ')); +end + +function isPCandIsRelative = pcIsRelativePath(pattern) + isPCandIsRelative = ispc() && ~isAbsolutePath(pattern); +end + +function isAbsolute = isAbsolutePath(pattern) + isAbsolute = 0; + if(ispc() && (~isempty(strfind(pattern, '\\')) || ~isempty(strfind(pattern, ':\')))) + isAbsolute = 1; + elseif(isunix()) + location = strfind(pattern, '/'); + if(~isempty(location) && any(location == 1)) + isAbsolute = 1; + end + end +end + +function files = removeBlankLines(files) + if(iscellstr(files)) + wh_empty = cellfun(@isempty, files); + files(wh_empty) = []; + elseif(ischar(files)) + files = char(removeBlankLines(cellstr(files))); + end +end diff --git a/Filename_tools/getfullpath.m b/Filename_tools/getfullpath.m new file mode 100755 index 00000000..3dbc0aa9 --- /dev/null +++ b/Filename_tools/getfullpath.m @@ -0,0 +1,38 @@ +function Pso = fullpath(Pspm) +% Pso = fullpath(Pspm) +% tor wager +% Pspm is file name with no path or relative path +% Searches for file in curr dir, then in specified dir +% Returns absolute path name (full path) + +for i = 1:size(Pspm,1) + +[d f e] = fileparts(deblank(Pspm(i,:))); +Ps = which(['./' f e]); + +if isempty(Ps) + + if ~isempty(d), + cwd = pwd; + try + eval(['cd(''' d ''')']);, + catch + warning('Specified directory does not exist!') + Ps = []; + return + end + + Ps = which(deblank(Pspm(i,:))); + cd(cwd) + end +end + +if i > 1 + Pso = str2mat(Pso,Ps); +else + Pso = Ps; +end + +end + +return diff --git a/Filename_tools/nums_from_text.m b/Filename_tools/nums_from_text.m new file mode 100755 index 00000000..e747af8e --- /dev/null +++ b/Filename_tools/nums_from_text.m @@ -0,0 +1,60 @@ +function [nums,whnums] = nums_from_text(mytext) +% function [nums,whnums] = nums_from_text(mytext) +% +% returns numeric values of numbers embedded in text strings +% given text string input +% tor wager + +nums = NaN; +ind = 1; +numind = 1; +mytext = [mytext ' ']; + +whnums = false(size(mytext)); + +for i = 1:length(mytext) + + tmp = str2num(mytext(i)); + + if ~isempty(tmp) && isreal(tmp) + mynum(ind) = tmp; + catflag = 1; + ind = ind + 1; + + whnums(i) = 1; + else + catflag = 0; + ind = 1; + + if exist('mynum') == 1 + nums(numind) = catnums(mynum); + numind = numind + 1; + end + + clear mynum + + end +end + +whomit = zeros(1,length(nums)); +for k = 1:length(nums), if ~isreal(nums(k)), whomit(k) = 1; end, end +nums(find(whomit)) = []; +if isempty(nums), nums = NaN; end + +return + + + +function mynum = catnums(mynum) + + if length(mynum) > 1 + mult = 10 * ones(1,length(mynum)); + mult = mult .^ (length(mynum)-1:-1:0); + else + mult = 1; + end + + mynum = sum(mynum .* mult); + +return + diff --git a/Filename_tools/remove_disdaq_vols.m b/Filename_tools/remove_disdaq_vols.m new file mode 100644 index 00000000..fffbe950 --- /dev/null +++ b/Filename_tools/remove_disdaq_vols.m @@ -0,0 +1,82 @@ +% new_files = remove_disdaq_vols(img_files, num_vols_per_run, num_disdaq_vols, ['overwrite', 0|1], ['FSLOUTPUTTYPE', fsl_output_type], ['strict', 0|1]) +% Inputs: +% img_files - cellstr of files to remove disdaqs from - each cell represents a run +% num_vols_per_run - vector of volume counts per run, *not* including disdaq vols +% num_disdaq_vols - constant describing how many data points to remove from the beginning of each run +% 'overwrite' - if set, will overwriting images in place - defaults to 0 +% 'FSLOUTPUTTYPE' - outputfile type - defaults to 'NIFTI' +% 'strict' - if set, will error out unless data given to it is +% exactly proper length - defaults to 1 - turn off ONLY with good reason +% +% Outputs: +% new_files = input files, but with a 'd' prepended, unless 'overwriting' was specified +% +% NB: Not set up for 3d files yet!!! +% +% E.g.: +% for an experiment +% num_vols_per_run = [124 140 109]; % NOT including disdaqs +% num_disdaq_vols = 4; + +function new_files = remove_disdaq_vols(img_files, num_vols_per_run, num_disdaq_vols, varargin) + global FSLDIR; + scn_setup(); + + % Flags + strict = 1; + overwriting = 0; + return_char = 0; + fsl_output_type = 'NIFTI'; + + % Check inputs + if ~isempty(varargin) + for i = 1:length(varargin) + if ischar(varargin{i}) + switch(varargin{i}) + case {'overwrite' 'overwriting'} + overwriting = varargin{i+1}; + case 'strict' + strict = varargin{i+1}; + case {'fsl_output_type', 'FSLOUTPUTTYPE'} + fsl_output_type = varargin{i+1}; + end + end + end + end + + if(ischar(img_files)) + disp('Cellstr not passed in, changing...') + img_files = cellstr(img_files); + return_char = 1; + end + + % Use FSL to remove disdaqs + new_files = cell(size(img_files)); + for i=1:length(img_files) + current_file = img_files{i}; + num_frames = scn_num_volumes(current_file); + + if(strict && (num_frames ~= num_vols_per_run(i) + num_disdaq_vols(i))) + error('The number of vols (%d) + disdaqs (%d) don''t add up to the length (%d) of the file (''%s'') passed in.', num_vols_per_run, num_disdaq_vols, num_frames, current_file); + else + if(overwriting) + output_file = current_file; + else + [d f e] = fileparts(current_file); + output_file = fullfile(d, ['d' f]); + end + + fslroi_command = sprintf('export FSLDIR=%s && . %s/etc/fslconf/fsl.sh && FSLOUTPUTTYPE=%s && %s/bin/fslroi %s %s %d %d', ... + FSLDIR, FSLDIR, fsl_output_type, FSLDIR, current_file, output_file, num_disdaq_vols, num_vols_per_run); + disp(fslroi_command); + system(fslroi_command); + end + + new_files{i} = [output_file e]; + end + + % Convert output back to char is only a single file was passed in + if(return_char) + new_files = char(new_files); + end +end \ No newline at end of file diff --git a/Filename_tools/rename_lowercase.m b/Filename_tools/rename_lowercase.m new file mode 100755 index 00000000..a33c9f59 --- /dev/null +++ b/Filename_tools/rename_lowercase.m @@ -0,0 +1,24 @@ +function rename_lowercase +% rename_lowercase +% +% tor wager, renames all files in dir with lower case versions +% if all characters are capitalized. +% +% batch example: +%d = dir('0*'); for i=1:length(d),cd(d(i).name),rename_lowercase, cd .., end + +d = dir; for i = 3:length(d), + nm1 =(d(i).name);nm2=lower(nm1); + str=['!mv ' nm1 ' ' nm2]; + + if strcmp(nm1,'SPM.MAT'), + str = ['!mv ' nm1 ' SPM.mat']; + disp(str) + eval(str) + elseif strcmp(upper(nm1),nm1) + disp(str) + eval(str) + end +end + +return \ No newline at end of file diff --git a/Filename_tools/rename_uppercase.m b/Filename_tools/rename_uppercase.m new file mode 100755 index 00000000..8cf1211f --- /dev/null +++ b/Filename_tools/rename_uppercase.m @@ -0,0 +1,24 @@ +function rename_lowercase +% rename_lowercase +% +% tor wager, renames all files in dir with lower case versions +% if all characters are capitalized. +% +% batch example: +%d = dir('0*'); for i=1:length(d),cd(d(i).name),rename_lowercase, cd .., end + +d = dir; for i = 3:length(d), + nm1 =(d(i).name);nm2=upper(nm1); + str=['!mv ' nm1 ' ' nm2]; + + if strcmp(nm1,'SPM.MAT'), + str = ['!mv ' nm1 ' SPM.mat']; + disp(str) + eval(str) + elseif strcmp(lower(nm1),nm1) + disp(str) + eval(str) + end +end + +return \ No newline at end of file diff --git a/Filename_tools/scan_get_files.m b/Filename_tools/scan_get_files.m new file mode 100644 index 00000000..c1188aa1 --- /dev/null +++ b/Filename_tools/scan_get_files.m @@ -0,0 +1,34 @@ +% files = scan_get_files(n, filt, mesg, wd) +% +% Drop-in replacement for spm_get. +% Main advantage is that it can use spm_select for SPM5+ to mimic older spm_get behavior. + +function files = scan_get_files(n, filt, mesg, wd) + if(~exist('wd', 'var') || isempty(wd)) + wd = pwd(); + end + + switch(spm('Ver', [], 1)) + case 'SPM2' + files = spm_get(n, filt, mesg, wd); + case {'SPM5', 'SPM8'} + if(any(n < 0)) + typ = 'dir'; + n = abs(n); + select_filt = strrep(filt, '*', '.*'); + else + switch(deblank(filt)) + case {'*.img' '*img' '*.nii' '*nii' '*.hdr' '*hdr'} + typ = 'image'; + select_filt = '.*'; + case {'*.mat' '*mat'} + typ = 'mat'; + select_filt = '.*'; + otherwise + typ = 'any'; + select_filt = strrep(filt, '*', '.*'); + end + end + files = spm_select(n, typ, mesg, [], wd, select_filt); + end +end \ No newline at end of file diff --git a/Filename_tools/scn_get_image_names.m b/Filename_tools/scn_get_image_names.m new file mode 100644 index 00000000..b2764bf6 --- /dev/null +++ b/Filename_tools/scn_get_image_names.m @@ -0,0 +1,48 @@ +function [imgs, sess_images, spersess] = scn_get_image_names(nruns, ndisdaqs) + + if nargin < 2, ndisdaqs = 0; + else + fprintf('Removing first %03d image names from each session.\n', ndisdaqs); + end + + % get image names + % -------------------------------------------------------------- + filestr = ['r[1-' num2str(nruns) ']' filesep 'vols.V*.img']; + disp(['Getting images: ' filestr]) + imgs = filenames(filestr, 'char', 'absolute'); + + clear sess_images + + for i = 1:nruns + filestr = ['r[' num2str(i) ']' filesep 'vols.V*.img']; + sess_images{i} = filenames(filestr, 'char', 'absolute'); + + % disdaqs + if ~isempty(sess_images{i}) && size(sess_images{i}, 1) > ndisdaqs && ndisdaqs + sess_images{i}(1:ndisdaqs, :) = []; + end + + end + + wh_empty = false(1, nruns); + spersess = zeros(1, nruns); + for i = 1:nruns + if isempty(sess_images{i}) || size(sess_images{1}, 1) == 0 % do seem to need 2nd expression + wh_empty(i) = true; + else + spersess(i) = size(sess_images{1}, 1); + end + end + + if ndisdaqs + % re-get all image names, w.o disdaqs + imgs = char(sess_images{:}); + end + + fprintf('Sessions (runs) with images: %03d\tSessions without images:%03d\n', sum(~wh_empty), sum(wh_empty)); + spersess(wh_empty) = []; + fprintf('Images per Session (spersess):\n'); + fprintf('%3.0f\t', spersess); + fprintf('\n'); + +end diff --git a/Filename_tools/sort_image_filenames.m b/Filename_tools/sort_image_filenames.m new file mode 100755 index 00000000..cb009170 --- /dev/null +++ b/Filename_tools/sort_image_filenames.m @@ -0,0 +1,46 @@ +function [P, resort] = sort_image_filenames(P) +% [P, indices] = sort_image_filenames(P) +% +% Not all image name listing functions return imgs in the correct numbered +% order! +% +% This function resorts a string matrix of image file names by image number, +% in ascending order +% At most, filename can have one number in it, or error is returned +% +% P can be a string matrix of a cell of string matrices +% Tor Wager, April 2005 + +% if cell, call this function recursively +if iscell(P) + for i = 1:length(P) + fprintf(1,'%3.0f',i); + P{i} = sort_image_filenames(P{i}); + end + fprintf(1,'\n') + return +end + +if size(P,1) == 1, return, end + +nums = []; +for j = 1:size(P,1) + [dd,ff,ee]=fileparts(P(j,:)); + + % find the last real number in the name + tmp = nums_from_text(ff); + tmp(tmp ~= real(tmp)) = []; + nums(j) = tmp(end); +end + +[n2,resort] = sort(nums); + +if any(n2-nums), + fprintf(1,'Resorting.') + + P = P(resort,:); +end + + + +return diff --git a/GLM_Batch_tools/canlab_glm_README.txt b/GLM_Batch_tools/canlab_glm_README.txt new file mode 100644 index 00000000..86efa4ad --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_README.txt @@ -0,0 +1,85 @@ +OVERVIEW +canlab_glm_* are a set of functions to handle the running +of glm analyses of fmri data. They use SPM for analysis of +individual subjects and Tor Wager's robust regression +functions (robfit) for group level analysis. Details can +be found in the description of the DSGN structure (below) +and in the help statements for the functions, but here is a brief +overview: + +Before running canlab_glm functions, you will need to have: +- preprocessed functional data +- modeling files (SPM style "conditions" and "regressors" MAT-files) +- a DSGN structure loaded in your workspace or saved as a MAT-file + + +PREPROCESSING +You can use canlab_preproc_2012 for your preprocessing needs. Whatever +you use, you must end up with 4D functional image files that have the +same name for each subject and the relative path to each subject's directory. +See DSGN.funcnames + + +SPM "REGRESSORS" +A simple script can make multiple regressors files, one +for each run, from the products of canlab_preproc_2012 +(several wagerlab studies have used modified version of the same +script, make_noise_model1.m, for this purpose). If needed, you +can also include regressors of interest in your multiple regressors +files. You can also save multiple files and call a different one +for each analysis you run. +See DSGN.multireg + + +SPM "CONDITIONS" +Similarly, you must make conditions files, typically using +data in your experiment logs (e.g., .edat files from E-Prime). +This is often the most complicated process in running an +analysis and varies the most from one experiment to the next. +See DSGN.contrasts + + +LOWER LEVELS IN SPM +With these files in place, you will then need to assemble a +DSGN structure (probably using a script) that will be passed into +canlab_glm_subject_levels (defines all aspects of the design/model +for the analysis). + +canlab_glm_subject_levels will take DSGN as a +variable (from the workspace) or as a MAT-file, so your setup +script can either save DSGN in a MAT-file or simply load the +DSGN variable into the workspace. + +Ex: +>> setup_pr_model2 +>> canlab_glm_subject_levels(DSGN, 'email', 'ruzic@colorado.edu') +OR (if saved as a MAT-file) +>> canlab_glm_subject_levels('pr_model2.mat') + +Make sure you check out the available options in +help canlab_glm_subject_levels. + +SINGLE TRIAL MODELS +See: https://canlabreposguide.hackpad.com for more information +In your DSGN file, add: +DSGN.singletrials{1}={1 1 0 0}; Where the number of entries in the array is +equivalent to the number of conditions in your DSGN file. You indicate with +a 1 if you want that condition to be modeled as single trials. 0 will not. + +GROUP LEVELS WITH ROBFIT +With subject levels completed, you can run group levels with +robfit using canlab_glm_group_levels. This function is built +with defaults that can be overridden. If you did lower levels +using canlab_glm_subject_levels, you can reuse the DSGN structure +(or MAT-file containing it). + +>> canlab_glm_group_levels(DSGN, 'o', '../second_level/pr_model2') + +(By default it the output will be written to the same +directory as the subject level analyses. The standard +wagerlab organization is to keep group level analyses +together in a separate directory, "second_level", hence +the 'o' option in the above command.) + +Make sure you check out the available options in +help canlab_glm_subject_levels. diff --git a/GLM_Batch_tools/canlab_glm_dsgninfo.txt b/GLM_Batch_tools/canlab_glm_dsgninfo.txt new file mode 100644 index 00000000..297f54a4 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_dsgninfo.txt @@ -0,0 +1,199 @@ +DSGN STRUCTURE +This is the structure variable used by canlab_glm_subject_levels to define an analysis. +(note: optional fields/fields with defaults may be left undefined) + + ________________________________________________________________________________ +| DSGN.metadata | +| a fieldname for the user to use as desired (typically for annotation). | +| EX: DSGN.metadata.notes = 'this design uses a modified HRF from model 4'; | +|________________________________________________________________________________| + ________________________________________________________________________________ +| DSGN.modeldir | +| a path (absolute preferred) to the directory holding the subject | +| level analyses | +| EX: DSGN.modeldir = '/data/projects/wagerlab/current/labdata/ilcp/Imaging/analyses/first_level/model1'; +|________________________________________________________________________________| + + ________________________________________________________________________________ +| DSGN.subjects | +| cell array of subject directories (absolute paths) | +| EX: DSGN.subjects = filenames('../ilcp[0-9]*','absolute'); | +| EX: DSGN.subjects = importdata('good_subs.txt'); | +|________________________________________________________________________________| + + ________________________________________________________________________________ +| DSGN.funcnames | +| cell array of strings that specify location of 4D functional data | +| files within subject directories (may include wildcards) | +| EX: DSGN.funcnames{1} = 'Functional/Preprocessed/r1/task.nii'; | +| EX: DSGN.funcnames{1} = 'Functional/Preprocessed/*/run*.nii'; | +| (note: the resulting ordering of functional files determines the | +| session order--must coincide with DSGN.conditions) | ++-- OPTIONAL FIELDS -------------------------------------------------------------+ +| - DSGN.allowmissingfunc (DEFAULT: false) | +| if true, allow functional files to be missing | +| - DSGN.concatenation (DEFAULT: none) | +| cell array of arrays of runs to concatenate | +| (sparse: unconcatenated runs need not be specified) | +| EX: DSGN.concatenation = {[1 2] [6:10]} | +| this will concatenate runs 1 and 2, 6-10, and leave 3-5 separated | +| concatenation procedure includes: | +| - concatenation of functional data (DSGN.funcnames) | +| (NOTE: if DSGN.allowmissingfunc is set, each DSGN.funcnames cell | +| must match no more than one file) | +| - concatenate conditions (DSGN.conditions) | +| (NOTE: it is assumed that concatenated runs each have the same | +| conditions in same order) | +| - merge regressors in block diagonal fashion (DSGN.multireg) | +| - add intercept regressors for all but one run in each concatenation | +| (one left out because SPM still includes intercept for session) | +| - add linear trend regressors for each run in concatenation | +| - DSGN.customrunintercepts | +| Note: This will only have an effect if using DSGN.convolution | +| cell array of vectors specifying custom intercepts | +| one cell per intercept, numbers in vectors specify runs to include in | +| intercept regressor. (refer to pre-concatenation run numbers) | +| keep in mind that SPM will assign an intercept regressor to each "session" | +| (where a session is a run or, when using DSGN.concatenation, a | +| concatenation of runs). It's important to avoid rank deficiency! | +| when using this option, no intercepts will be added by DSGN.concatenation | +| EX: If you have 8 runs, you're concatenating them all, but you want session| +| intercepts for the first 4 and last 4 runs + leftover run intercepts: | +| DSGN.concatenation = [1:8]; | +| DSGN.customrunintercepts = {2 3 4 1:4 6 7 8}; | +| This will create 8 intercepts: one each for runs 2 3 4 6 7 and 8, one | +| for runs 1-4, and one (added by SPM) for runs 1-8 | +|________________________________________________________________________________| + + ________________________________________________________________________________ +| DSGN.tr repetition time (or "TR") of functional data (in seconds)) | +| DSGN.hpf high pass filter (in seconds) | +| DSGN.fmri_t microtime resolution (DEFAULT: 16) | +| should be left at default unless TR is quite long | +| DSGN.fmri_t0 microtime onset | +| must correspond to reference slice used in slice time correction | +|________________________________________________________________________________| + + ________________________________________________________________________________ +| DSGN.conditions | +| cell array (one cell per session) of cell arrays (one cell per | +| condition) of MAT-file names (acceptable wildcards: [] {} ? *) | +| each MAT-file contains one or more conditions specified SPM-style (see below)| +| if only one session is specified, it will be applied to all sessions | +| location of MAT-files: see DSGN.modelingfilesdir (see below) | +| EX: DSGN.conditions{2} = {'heat' 'rate'}; | +| if the 2nd functional file is .../r2/swraPAINTASK.nii, then this | +| will add the conditions defined in: | +| .../r2/DSGN.modelingfilesdir/heat.mat | +| .../r2/DSGN.modelingfilesdir/rate.mat | +| NOTE: all timing is in seconds (not TRs) | +| | +| Example SPM-style condition: | +| name{1} = 'heat'; | +| onset{1} = [16.97 54.84 98.57 138.25 179.95]; | +| duration{1} = [10.75 11.25 11.00 11.75 11.50]; | +| (See spm fmri -> help -> spm.stats.fmri_spec -> pages 5-10) | ++-- OPTIONAL FIELDS -------------------------------------------------------------+ +| - DSGN.pmods (DEFAULT: no pmods) | +| cell array (one cell per session) of cell arrays (one cell per condition) | +| of cell arrays (one cell per modulator) of MAT-file names | +| each MAT-file contains cell arrays of SPM pmod fields: name, param, poly. | +| example mat contents: | +| name{1} = 'rating'; | +| param{1} = [50 13 32 69 54 71 10 6]; | +| poly{1} = 1; | +| (note: these cell arrays of pmod fields will be converted into arrays of | +| pmod structs for SPM (e.g., name{2} -> pmod(2).name)) | +| location of MAT-files: DSGN.modelingfilesdir (see below) | +| EX: DSGN.pmods{2}{4} = {'temp' 'rating'}; | +| if the 2nd functional file is .../r2/swraPAINTASK.nii, the 4th condition | +| will get the pmods defined in: | +| .../r2/DSGN.modelingfilesdir/temp.mat | +| .../r2/DSGN.modelingfilesdir/rating.mat | +| - DSGN.convolution (DEFAULT: hrf.derivs = [0 0]) | +| structure specifying the convolution to use for conditions | +| different fields required depending on convolution type | +| AVAILABLE TYPES (declared as string DSGN.convolution.type) | +| 'hrf' (Hemodynamic Response Function) | +| convolution.time (1/0 switches for time derivatives) | +| convolution.dispersion (1/0 switches for dispersion derivatives) | +| EX: DSGN.convolution.hrf.derivs = [1 0]; % HRF w/ time deriv | +| 'fir' (Finite Impulse Response function) | +| convolution.windowlength (post-stimulus window length (in seconds)) | +| convolution.order (number of basis functions) | +| convolution.keepduration (optional flag. DEFAULT: FALSE) | +| if FALSE, set all durations for all conditions to 0 | +| EX: DSGN.convolution.type = 'fir'; | +| DSGN.convolution.windowlength = 46; | +| DSGN.convolution.order = 23; | +| - DSGN.ar1 (DEFAULT: false) | +| if true, use autoregressive AR(1) to model serial correlations | +| (see SPM manual) | +| - DSGN.notimemod (DEFAULT: false) | +| if true, turn off time modulation of conditions | +| (see the 'notimemod' option to canlab_spm_contrast_job) | +| - DSGN.singletrials | +| a cell array (1 cell per session) of cell arrays (1 cell per condition) of | +| (corresponding to DSGN.conditions) of true/false values indicating | +| whether to convert specified condition to set of single trial conditions | +| (e.g., from 1 condition with n onsets to n conditions with 1 onset each) | +| - DSGN.singletrialsall (DEFAULT: false) | +| set DSGN.singletrials to true for all conditions | +| - DSGN.modelingfilesdir (DEFAULT: 'spm_modeling') | +| directory containing modeling files describing functional data | +| (see DSGN.conditions, DSGN.pmods, DSGN.multireg) | +| this directory lives in same directory as the described functional file | +| EX: DSGN.modelingfilesdir = 'modeling_files2' | +| if the functional file for the 1st session is .../r1/swraPAINTASK.nii | +| the files defining conditions, pmods, and regressors for the 1st | +| session will be found in .../r1/modeling_files2 | +| - DSGN.allowemptycond (DEFAULT: false) | +| if true, allow empty conditions | +| this is useful, for example, when you want to model errors for some | +| subjects but not all subjects have errors | +| WARNING: skipped conditions should NOT be included in contrasts! | +| (at least not without re-weighting between subjects) | +| - DSGN.allowmissingcondfiles (DEFAULT: false) | +| if true, throw warning instead of error when no file(s) are found | +| corresponding to a MAT-file name/wildcard | +|________________________________________________________________________________| + + ________________________________________________________________________________ +| DSGN.multireg (DEFAULT: no multiple regressors file) | +| give the name of an SPM-style "multiple regressors" MAT-file containing | +| a single TRs x regressors matrix names "R" | +| ('.mat' will be appended to the file name) | +| location of MAT-file: DSGN.modelingfilesdir (see above) | +| EX: DSGN.multireg = 'noise_model1'; | +|________________________________________________________________________________| + + ________________________________________________________________________________ +| DSGN.contrasts (DEFAULT: no contrasts) | +| cell array (one cell per contrast) of contrast definitions | +| see canlab_spm_contrast_job for syntax | +| EX: DSGN.contrasts{1} = {{'heat_H'}}; | +| DSGN.contrasts{2} = {{'heat_H'} {'heat_L'}}; | ++-- OPTIONAL FIELDS -------------------------------------------------------------+ +| - DSGN.contrastnames (DEFAULT: automatic names from canlab_spm_contrast_job) | +| cell array (one cell per contrast) containing strings to name contrasts | +| (sparse: unspecified contrasts may be left blank or undefined) | +| (see canlab_spm_contrast_job 'names' option) | +| EX: DSGN.contrasts{2} = 'heat_H-L'; | +| DSGN.contrastweights (DEFAULT: [1] or [1 -1] as needed) | +| cell array (one cell per contrast) containing matrices with custom weights | +| (sparse: unspecified contrasts may be left blank or undefined) | +| (see canlab_spm_contrast_job 'weights' option) | +| EX: DSGN.contrasts{3} = {{'heat_H'} {'heat_MH'} {'heat_ML'} {'heat_L'}}; | +| DSGN.contrastweights{3} = [3 1 -1 -3]; | +| - DSGN.regmatching (DEFAULT: 'anywhere') | +| can be set to 'exact' or 'regexp' | +| (see canlab_spm_contrast_job) | +| - DSGN.defaultsuffix (DEFAULT: no default suffix) | +| define a default string that each contrast ends with (e.g., '\*bf\(1\)') | +| only used when DSGN.regmatching == regexp | +| EX: DSGN.defaultsuffix = '\*bf\(1\)'; | +| (see canlab_spm_contrast_job) | +| - DSGN.noscale (DEFAULT: false) | +| if true, use the 'noscale' option to canlab_spm_contrast_job | +| see canlab_spm_contrast_job | +|________________________________________________________________________________| diff --git a/GLM_Batch_tools/canlab_glm_example_DSGN_setup.txt b/GLM_Batch_tools/canlab_glm_example_DSGN_setup.txt new file mode 100644 index 00000000..dd14de7a --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_example_DSGN_setup.txt @@ -0,0 +1,75 @@ +%% AN EXAMPLE OF SETTING UP THE DSGN STRUCT FOR canlab_glm_subject_levels +% WARNING: none of these values are to be taken as recommendations! +% (to make executable, this would be saved as an M-file) + +% All of the options below that are set to false here need not be included +% (they default to false) but have been included in this example simply +% as a reminder that they exist. + +DSGN.metadata.notes = 'this is just an example DSGN setup in case you want to see how Luka set up his DSGN struct'; + +%% INPUT +DSGN.modeldir = '/dreamio3/wagerlab/labdata/current/ilcp/Imaging/analyses/first_level/pr_model2'; +DSGN.subjects = importdata('/dreamio3/wagerlab/labdata/current/ilcp/Imaging/analyses/good_subjects.lst'); +DSGN.funcnames = {'Functional/Preprocessed/s*/swraPAINTASK*.nii'}; +DSGN.allowmissingfunc = false; +DSGN.concatenation = {[1:4]}; + +% alternatives: +% DSGN.subjects = filenames('/dreamio3/wagerlab/labdata/current/ilcp/Imaging/ilcp*_S*_OC*'); +% load EXPT; DSGN.subjects = EXPT.subjects; + + +%% PARAMETERS +DSGN.tr = 1.98; +DSGN.hpf = 128; +DSGN.fmri_t0 = 8; + + +%% MODELING (task conditions, noise regressors, etc) +DSGN.modelingfilesdir = 'modeling_files_v2'; + +% (the use of c is merely to facilitate editing: allows easy deletion/addition, +% creation of accompanying sparse arrays, etc) +c=0; +c=c+1; DSGN.conditions{1}{c} = 'heatantic'; +c=c+1; DSGN.conditions{1}{c} = 'heat_H'; +DSGN.pmods{1}{c} = {'pain_rating_H'}; +c=c+1; DSGN.conditions{1}{c} = 'heat_MH'; +DSGN.pmods{1}{c} = {'pain_rating_MH'}; +c=c+1; DSGN.conditions{1}{c} = 'heat_ML'; +DSGN.pmods{1}{c} = {'pain_rating_ML'}; +c=c+1; DSGN.conditions{1}{c} = 'heat_L'; +DSGN.pmods{1}{c} = {'pain_rating_L'}; + +DSGN.allowemptycond = false; +DSGN.notimemod = false; + +DSGN.multireg = 'noise_model_1'; + +%% SINGLE TRIAL +DSGN.singletrials{1}={1 1 0 0}; +%% this specifies which conditions will be modeled as single trials, in the order that you listed your conditions + +%% CONTRASTS +DSGN.noscale = false; +DSGN.regmatching = 'regexp'; +DSGN.defaultsuffix = ' \*bf\(1\)$'; + +c=0; +c=c+1; DSGN.contrasts{c} = {{'heatantic'}}; +c=c+1; DSGN.contrasts{c} = {{'heat_H'}}; +c=c+1; DSGN.contrasts{c} = {{'heat_MH'}}; +c=c+1; DSGN.contrasts{c} = {{'heat_ML'}}; +c=c+1; DSGN.contrasts{c} = {{'heat_L'}}; +c=c+1; DSGN.contrasts{c} = {{'heat_H'} {'heat_L'}}; +c=c+1; DSGN.contrasts{c} = {{'heat_H'} {'heat_MH'} {'heat_ML'} {'heat_L'}}; +DSGN.contrastweights{c} = [3 1 -1 -3]; +DSGN.contrastnames{c} = 'heat_linear; +c=c+1; DSGN.contrasts{c} = {{'heat.* xpain_rating^1*bf(1)$'}}; + + + + +%% SAVE +save pr_model2 DSGN diff --git a/GLM_Batch_tools/canlab_glm_getinfo.m b/GLM_Batch_tools/canlab_glm_getinfo.m new file mode 100644 index 00000000..70dc9344 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_getinfo.m @@ -0,0 +1,263 @@ +function [infos] = canlab_glm_getinfo(modeldir,varargin) +% +% SUBJECT LEVEL input +% INFO = canlab_glm_getinfo(spm_subject_level_directory, option, [n]) +% Get information out of an spm subject level analysis. +% +% OPTIONS (each option can be called by the listed letter or word + a number, when noted) +% 'i' 'input' number of volumes and (first) volume name for each run +% 'b' 'betas' [n] beta names (for nth session) +% 'B' 'taskbetas' [n] beta names (that didn't come from multiple regressors) (for nth session) +% 'c' 'cons' [n] contrast names (for nth contrast) +% 'C' 'conw' [n] beta names and weights for contrasts (for nth con) +% 'v' 'image' [n] create figure of design matrix (for nth session) +% (design matrix is multiplied by 100 for visibility) +% (works well for multiple runs) +% 'V' 'taskimage' [n] same as 'image', but only for task betas +% 'imagesc' same as 'image', but uses imagesc +% (works well for single runs) +% +% +% GROUP LEVEL input +% INFO = canlab_glm_getinfo(robfit_group_level_directory, option, [n]) +% Get information out of a robfit group level analysis. +% +% OPTIONS (each option can be called by the listed word or letter + a number, when noted) +% Any of the subject level options can be used on a group level robfit +% analysis by prefixing '1i' (output is generated based on the first input +% to the first robust analysis). +% EX: canlab_glm_getinfo('second_level/model3','1iconw') +% +% 'i' 'input' [n] input contrasts by number and name (for nth analysis) +% 'I' 'allinput' [n] input images (for nth analysis) +% 'm' 'model' weights by subject (i.e., directory containing input contrast images) +% 'M' 'allmodels' [n] weights and input images (for nth analysis) +% +% ASSUMPTIONS: In some options, the first contrasts and group level analysis +% directories are assumed to represent the rest, which may not be the +% case. +% NOTE: group level options do not yet return a usable INFO struct. +% + +if nargin ~= 2 && nargin ~= 3 + error('USAGE: INFO = canlab_glm_getinfo(spm_dir|robfit_dir,option,[n])') +end + +opt = varargin{1}; +if nargin == 3, s = varargin{2}; else s = 0; end + +if ~exist(modeldir,'dir') + error('No such directory: %s',modeldir) +else + spmmat = fullfile(modeldir,'SPM.mat'); + setupmats = filenames(fullfile(modeldir,'robust[0-9][0-9][0-9][0-9]','SETUP.mat')); + if exist(spmmat,'file') + infos = get_subject_level_info(modeldir,opt,s); + elseif ~isempty(setupmats) + if regexp(opt,'^1i') + load(setupmats{1}); + firstinput = fileparts(deblank(SETUP.files(1,:))); + infos = get_subject_level_info(firstinput,regexprep(opt,'^1i',''),s); + else + infos = get_group_level_info(modeldir,opt,s); + end + else + error('%s is neither a subject level SPM directory nor a group level robfit directory',modeldir); + end +end + + +end + + +function [infos] = get_subject_level_info(modeldir,opt,s) + +load(fullfile(modeldir,'SPM')); + +switch opt + case {'c' 'con' 'cons'} + if s + infos.contrast_name{1} = SPM.xCon(s).name; + else + infos.contrast_name = cellstr(strvcat(SPM.xCon.name)); %#ok + end + + fprintf('CONTRASTS:\n') + for i=1:numel(infos.contrast_name) + fprintf('%4d %s\n',i,infos.contrast_name{i}); + end + + case {'C' 'conw' 'consw'} + if s + infos.contrast_name{1} = SPM.xCon(s).name; + infos.beta_numbers{1} = find(SPM.xCon(s).c); + infos.beta_names{1} = cellstr(strvcat(SPM.xX.name{infos.beta_numbers{1}})); %#ok + infos.beta_weights{1} = SPM.xCon(s).c(infos.beta_numbers{1}); + else + infos.contrast_name = cellstr(strvcat(SPM.xCon.name)); %#ok + infos.contrast_num = 1:numel(infos.contrast_name); + for i = 1:numel(infos.contrast_name); + infos.beta_numbers{i} = find(SPM.xCon(i).c); + infos.beta_names{i} = cellstr(strvcat(SPM.xX.name{infos.beta_numbers{i}})); %#ok + infos.beta_weights{i} = SPM.xCon(i).c(infos.beta_numbers{i}); + end + end + + for s = 1:numel(infos.contrast_name) + fprintf('%3d: %s:\n',s,infos.contrast_name{s}) + for i=1:numel(infos.beta_names{s}) + fprintf('\t%10.5f\t%s\n',infos.beta_weights{s}(i),infos.beta_names{s}{i}); + end + end + + + case {'b' 'betas'} + if s + infos.beta_number = find(~cellfun('isempty',regexp(SPM.xX.name,sprintf('^Sn\\(%d\\)',s))))'; + infos.beta_name = cellstr(strvcat(SPM.xX.name{infos.beta_number})); %#ok + else + infos.beta_name = cellstr(strvcat(SPM.xX.name)); %#ok + infos.beta_number = [1:numel(infos.beta_name)]'; %#ok + end + + for i = 1:numel(infos.beta_name) + fprintf('%4d %s\n',infos.beta_number(i),infos.beta_name{i}); + end + + + case {'B' 'taskbetas'} + n = cellfun('isempty',regexp(SPM.xX.name,' R[0-9]*$'))'; + if s + n = n & ~cellfun('isempty',regexp(SPM.xX.name,sprintf('^Sn\\(%d\\)',s)))'; + end + n = n & cellfun('isempty',regexp(SPM.xX.name,'constant$'))'; + infos.beta_number = find(n); + infos.beta_name = cellstr(strvcat(SPM.xX.name{infos.beta_number})); %#ok + + for i = 1:numel(infos.beta_number) + fprintf('%4d %s\n',infos.beta_number(i),infos.beta_name{i}); + end + + + case 'nsess' + infos.nsess = numel(SPM.Sess); + fprintf('%d\n',infos.nsess); + + + case {'i' 'input' 'sess'} + infos.nsess = numel(SPM.nscan); + infos.nscan = SPM.nscan; + + n=1; for i=1:infos.nsess, first(i)=n; n=n+infos.nscan(i); end %#ok + + fprintf('%4s %5s %s\n','sess','nscan','first_volume_name') + for i = 1:numel(first) + infos.first_volume_name{i} = SPM.xY.VY(first(i)).fname; + fprintf('%4d %5d %s\n',i,infos.nscan(i),infos.first_volume_name{i}) + end + + + case {'v' 'image'} + if s + canlab_glm_getinfo(modeldir,'betas',s); + infos.X = SPM.xX.X(SPM.Sess(s).row,SPM.Sess(s).col); + else + canlab_glm_getinfo(modeldir,'betas'); + infos.X = SPM.xX.X; + end + + figure; image(infos.X * 40); + colormap('Bone'); + + + case {'V' 'taskimage'} +% n = cellfun('isempty',regexp(SPM.xX.name,' R[0-9]*$')); + if s + n = canlab_glm_getinfo(modeldir,'taskbetas',s); + %n = n .* ~cellfun('isempty',regexp(SPM.xX.name,['^Sn\(' num2str(s) '\)'])); + infos.X = SPM.xX.X(SPM.Sess(s).row,n.beta_number); + else + n = canlab_glm_getinfo(modeldir,'taskbetas'); + infos.X = SPM.xX.X(:,n.beta_number); + end + figure; image(infos.X * 400) + colormap('Bone'); + + case {'vsc' 'imagesc'} + spmlowerinfo(modeldir,'betas') + if s + canlab_glm_getinfo(modeldir,'betas',s) + figure; imagesc(SPM.xX.X(SPM.Sess(s).row,SPM.Sess(s).col)) + colormap('Bone'); + else + canlab_glm_getinfo(modeldir,'betas') + figure; imagesc(SPM.xX.X) + colormap('Bone'); + end + + + otherwise + error('Unrecognized option for spm lower level analyses: %s',opt) +end + +end + +function [infos] = get_group_level_info(modeldir,opt,s) + +infos = []; + +% load SETUP structs +f = filenames(fullfile(modeldir,'robust[0-9][0-9][0-9][0-9]')); +for i=1:numel(f), + load(fullfile(f{i},'SETUP')); + SETUPS(i) = SETUP; %#ok + clear SETUP +end + +switch opt + case {'i' 'input'} + % load lower levels + evalc('tmp = canlab_glm_getinfo(fileparts(SETUPS(i).V(1).fname),''cons'');'); + connames = tmp.contrast_name; + if ~s, s = 1:numel(SETUPS); end + fprintf('%-12s %-12s %-s\n','dir','input_con','con_name') + for i = s + [ignore finf] = fileparts(SETUPS(i).V(1).fname); %#ok + + fprintf('%-12s',SETUPS(i).dir) + fprintf(' %-12s',finf) + if regexp(finf,'^con_') + fprintf(' "%-s"',connames{str2num(regexprep(finf,'^con_',''))}) %#ok + end + fprintf('\n') + end + case {'I' 'allinput'} + if ~s, s = 1:numel(SETUPS); end + for i = s + fprintf('%s:\n',SETUPS(i).dir) + disp(SETUPS(i).files) + end + case {'m' 'model'} + fprintf('weight input\n') + for i = 1:numel(SETUPS(1).X) + fprintf('%-7.3f %s\n',SETUPS(1).X(i),regexprep(SETUPS(1).V(i).fname,'/[^/]*$','')) + end + case {'M' 'allmodels'} + if ~s, s = 1:numel(SETUPS); end + for m = s + fprintf('%s\n',SETUPS(m).dir) + fprintf('weight input\n') + for i = 1:numel(SETUPS(m).X) + fprintf('%-7.3f %s\n',SETUPS(m).X(i),SETUPS(m).V(i).fname) + end + end + + otherwise + error('Unrecognized option for robfit analyses: %s',opt) +end + + + +end + + \ No newline at end of file diff --git a/GLM_Batch_tools/canlab_glm_group_levels.m b/GLM_Batch_tools/canlab_glm_group_levels.m new file mode 100755 index 00000000..c26bfab8 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_group_levels.m @@ -0,0 +1,735 @@ +function canlab_glm_group_levels(varargin) +% canlab_glm_group_levels([options]) +% +% DESCRIPTION +% Performs group level robust GLM analysis with robfit +% 1 sets up analysis +% 2 runs robfit +% 3 (optionally) make inverse p maps (for FSL viewing) +% 4 (optionally) estimate significant cluster sizes +% 5 publishes analysis with robfit_results_batch +% +% OPTIONS +% (see DEFAULTS below) +% 's', subjects +% cell array of filenames of subject-level analysis directories IN +% modeldir +% (note: modeldir won't be prepended to absolute paths) +% 'm', modeldir +% filename of directory containing subject level analyses +% 'o', grpmodeldir +% output directory name +% 'c', cov +% a matrix describing group level model +% (do not include intercept, it is automatically included as first regressor) +% see help robfit +% note: requires specifying an output directory name +% note: ordering of inputs (rows) must match subjects ordering +% 'n', covname +% a cell array of names for the covariates in cov +% 'f', covfile +% a csv file will specify the group level model: +% first column, header 'subject', contains names of subject +% directories (to be found in modeldir) +% subsequent columns have covariates, headers are names of covariates +% name of covfile will be name of group analysis directory (placed in +% grpmodeldir) +% 'mask', maskimage +% filename of mask image +% DSGN +% will use the following fields of the DSGN structure: +% modeldir = DSGN.modeldir +% subjects = DSGN.subjects +% maskimage = DSGN.mask +% +% NOTE: A covfile will cause other specifications of subject, cov, and +% covnames to be ignored. +% If parameters are defined more than once (e.g., modeldir or subjects), +% only the last entered option will count. +% +% DEFAULTS: +% subjects = all SPM.mat-containing directories in modeldir +% modeldir = pwd +% grpmodeldir = modeldir/one_sample_t_test +% cov = {} (run 1 sample t-test, see help robfit) +% covname = 'groupmean' +% mask = 'brainmask.nii' +% +% OPTIONS +% 'README' +% prints canlab_glm_README, an overview of canlab_glm_{subject,group}_levels +% 'overwrite' +% overwrite existing output directories +% 'noresults' +% don't run/publish robfit_results_batch +% 'onlyresults' +% just run/publish robfit_results_batch, don't run robfit (assumes existing analyses) +% 'whichcons', [which cons] +% vector of contrasts to analyze (DEFAULT: aall subject level contrasts) +% see [which cons] in help robfit +% 'invp' [, target_space_image] +% generate inverse p maps and resample to the voxel and image dimensions +% of target_space_image +% (viewable on dream with ~ruzicl/scripts/invpview) +% 'nolinks' +% do not make directory of named links to robust directories (using contrast names) +% 'dream' +% if you're running on the dream cluster, this option will cause +% all analyses (e.g., lower level contrasts) to be run in parallel +% (submitted with matlab DCS and the Sun Grid Engine) +% Note: currently only works with MATLAB R2009a +% 'email', address +% send notification email to address when done running +% + +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Luka Ruzic +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% + +% Programmers' notes +% NOT READY YET: +% 'grf' +% estimate significant cluster sizes for each contrast using GRF theory +% will run at a=.05, voxelwise thresholds of .05, .01, .005, .001 +% see help estimate_cluster_extent + +%% SET UP +STARTTIME = datestr(now,31); + +STARTINGDIR = pwd; + +% path +addpath(genpath('/usr/local/spm/spm8/matlabbatch')); +addpath(genpath('/usr/local/spm/spm8/apriori')); +addpath(genpath('/usr/local/spm/spm8/canonical')); + +% set defaults +OPTS.parallel_dream = ''; +OPTS.overwrite = false; +OPTS.run_results = true; +OPTS.run_robfit = true; +OPTS.run_printconnames = true; +OPTS.run_invp = false; +OPTS.run_grf = false; +OPTS.run_links = true; +OPTS.nocatch = false; + +pthresh = [.05 .01 .005 .001]; % for significant cluster extent estimation + +EXPT.cov = []; +EXPT.mask = which('brainmask.nii'); + +% set up statuses +robfitstatus = 0; +invpstatus = 0; +grfstatus = 0; +linkdirstatus = 0; +reviewstatus = 0; + +diarydirname = 'canlab_glm_logs'; +diaryfilename = ['group_levels_' regexprep(regexprep(STARTTIME,' ','_'), ':','') '.log']; + +%% parse arguments +i=1; +while i<=numel(varargin) + if isstruct(varargin{i}) + DSGN = varargin{i}; + if isfield(DSGN,'modeldir') + modeldir = DSGN.modeldir; + end + if isfield(DSGN,'subjects') + for s = 1:numel(DSGN.subjects) + [dummy subname{s}] = fileparts(DSGN.subjects{s}); %#ok + sublevs{s} = subname{s}; %#ok + end + end + if isfield(DSGN,'mask') + EXPT.mask = DSGN.mask; + end + elseif ischar(varargin{i}) + switch varargin{i} + case {'README'} + system(sprintf('cat %s',which('canlab_glm_README.txt'))); + return; + case {'dream'} + version = ver('matlab'); + switch version.Release + case '(R2009a)' + OPTS.parallel_dream = '2009a'; + case '(R2011a)' + OPTS.parallel_dream = '2011a'; + otherwise + error('Current version of matlab (%s)',version.Release) + end + case {'f'} + i=i+1; + covfile = varargin{i}; + case {'c'} + i=i+1; + EXPT.cov = varargin{i}; + case {'n'} + i=i+1; + EXPT.covnames = ['intercept' varargin{i}]; + case {'s'} + i=i+1; + sublevs = varargin{i}; + case {'d' 'm'} + i=i+1; + modeldir = varargin{i}; + case {'mask'} + i=i+1; + EXPT.mask = varargin{i}; + case {'overwrite'} + OPTS.overwrite = true; + case {'noresults'} + OPTS.run_results = false; + case {'onlyresults'} + OPTS.run_robfit = false; + case {'whichcons'} + i=i+1; + includedcons = varargin{i}; + case {'invp'} + OPTS.run_invp = true; + case {'grf'} + OPTS.run_grf = true; + case {'nolinks'} + OPTS.run_links = false; + case {'nocatch'} + OPTS.nocatch = true; + case {'o'} + i=i+1; + grpmodeldir = varargin{i}; + case {'email'} + i=i+1; + address = varargin{i}; + otherwise + error(['Unrecognized argument: ' varargin{i}]) + end + else + disp(varargin{i}) + error('Above argument is unrecognized.') + end + i=i+1; +end + +% MODELDIR +if ~exist('modeldir','var') + modeldir = pwd; +end +if ~exist(modeldir,'dir') + error(['No such directory: ' modeldir]) +else + % make absolute (unsure if necessary) + modeldir = filenames(modeldir,'absolute','char'); +end + + +% SUBLEVS +if exist('sublevs','var') + for s = 1:numel(sublevs) + if ~strcmp(sublevs{s}(1),'/') + sublevs{s} = fullfile(modeldir,sublevs{s}); %#ok + end + end +end + + +% GRPMODELDIR +if ~exist('grpmodeldir','var') + if exist('modeldir','var') + grpmodeldir = fullfile(modeldir,'one_sample_t_test'); + else + grpmodeldir = fullfile(pwd,'one_sample_t_test'); + end +end +% make grpmodeldir +if ~exist(grpmodeldir,'dir') + mkdir(grpmodeldir) +end +% make absolute +grpmodeldir = filenames(grpmodeldir,'absolute','char'); + + +% COV +if exist('covfile','var') + if ~exist(covfile,'file') + error(['No such file: ' covfile]) + else + [d f e] = fileparts(covfile); %#ok + + % grpmodeldir + grpmodeldir = fullfile(fileparts(grpmodeldir),f); + + % sublevs + tempsubsfile = fullfile(d,[f '_tempsubs.txt']); + system(['cut -d, -f1 < ' covfile ' | tail -n +2 > ' tempsubsfile]); + sublevs = importdata(tempsubsfile); delete(tempsubsfile); + if isnumeric(sublevs), sublevs = cellstr(num2str(sublevs)); end + for i=1:numel(sublevs) + sublevs{i} = fullfile(modeldir,sublevs{i}); + end + + % cov, covnames + tempcovsfile = fullfile(d,[f '_tempcovs.txt']); + system(['cut -s -d, -f2- < ' covfile ' > ' tempcovsfile]); + s = importdata(tempcovsfile); delete(tempcovsfile); + if isfield(s,'data') + EXPT.cov = s.data; + EXPT.covnames = ['intercept' s.colheaders]; + else + EXPT.covnames = {'groupmean'}; + end + end +else + if isempty(EXPT.cov) + EXPT.covnames = {'groupmean'}; + else + if ~exist('grpmodeldir','var') + error('cov has been specified: must specify grpmodeldir.') + end + if isfield(EXPT,'covnames') + if numel(EXPT.cov) ~= numel(EXPT.covnames) + error('Number of covariate names (%d) does not match number of covariates (%d).',numel(EXPT.covnames),numel(EXPT.cov)) + end + else + EXPT.covnames{1} = 'intercept'; + for i=2:numel(EXPT.cov) + EXPT.covnames{i} = sprintf('grpcov%d',i-1); + end + end + end +end + + +% set up diary - to be on except when calling other functions that do their own logging +diarydir = fullfile(grpmodeldir,diarydirname); +if ~exist(diarydir,'dir'), mkdir(diarydir); end +diarydir = filenames(diarydir,'absolute','char'); +diaryname = fullfile(diarydir,diaryfilename); +fulldiaryname = regexprep(diaryname,'\.log$','_full.log'); +fprintf('Writing to logfile: %s\n',diaryname) +diary(diaryname), fprintf('> STARTED: %s\n',STARTTIME), diary off + +% make working directory +wd = regexprep(diaryname,'\.log$',''); +mkdir(wd) + +%% SET UP AND RUN ROBFIT +diary(diaryname), announce_string('ROBUST REGRESSION'), diary off +if ~OPTS.run_robfit + diary(diaryname), fprintf('> SKIPPED: turned off in options\n'); diary off +else + % subject level analyses + 1 SPM.mat (assumed representative) + if ~exist('sublevs','var') + diary(diaryname), fprintf('> No subject levels specified: looking for all subject level analyses in:\n\t%s\n',modeldir); diary off + spmfiles = filenames(fullfile(modeldir,'*/SPM.mat'),'absolute'); + if isempty(spmfiles) + diary(diaryname), fprintf('> ERROR: No subject level analyses found.\n'), diary off + cd(STARTINGDIR) + return + end + for i=1:numel(spmfiles), [sublevs{i}] = fileparts(spmfiles{i}); end %#ok + sublevs = unique(sublevs); + spmfile = spmfiles{1}; + else + spmfile = fullfile(sublevs{1},'SPM.mat'); + end + + diary(diaryname) + fprintf('> Subject level analyses:\n') + for i=1:numel(sublevs) + fprintf('> %-8d%s\n',i,sublevs{i}) + end + diary off + + if exist(spmfile,'file') + load(spmfile) + else + diary(diaryname), fprintf('> ERROR: No such file: %s\n',spmfile); diary off + cd(STARTINGDIR) + return + end + + % connames, connums, P + diary(diaryname), fprintf('> Loading contrast names from:\n> \t%s\n',spmfile); diary off + tcons = strmatch('T', char(SPM.xCon.STAT)); + EXPT.SNPM.connames = strvcat(SPM.xCon(tcons).name); %#ok + + ncons = size(EXPT.SNPM.connames,1); + EXPT.SNPM.connums = 1:ncons; + EXPT.SNPM.P = {}; + for c = 1:ncons + confiles = {}; + for s = 1:numel(sublevs) + confilename = fullfile(sublevs{s}, sprintf('con_%04d.img', tcons(c))); + confile = filenames(confilename, 'char', 'absolute'); + if exist(confile,'file') + confiles{s} = confile; %#ok + else + diary(diaryname), fprintf('> ERROR: No such contrast file: %s\n',confilename); diary off + cd(STARTINGDIR) + return + end + end + EXPT.SNPM.P{c} = strvcat(confiles); %#ok + end + + + %% RUN ROBFIT + diary(diaryname), fprintf('> Group level analysis directory:\n> \t%s\n',grpmodeldir); diary off + cd(grpmodeldir); + + save EXPT EXPT + + % which contrasts should be run + if ~exist('includedcons','var') + includedcons = []; + for c = 1:numel(EXPT.SNPM.connums) + if OPTS.overwrite || numel(filenames(fullfile(pwd,sprintf('robust%04d', c),'rob_p_*img'))) < size(EXPT.cov,2)+1 + includedcons = [includedcons, c]; %#ok + end + end + end + + if isempty(includedcons) + diary(diaryname), fprintf('> No robust regressions to run.\n'); diary off + else + diary(diaryname) + fprintf('> Subject level contrasts:\n'); + for i=1:size(EXPT.SNPM.connames,1) + fprintf('> '); + if ~sum(find(includedcons == i)), fprintf('*EXCLUDED* '); end + fprintf(' %-4d%s\n',i,EXPT.SNPM.connames(i,:)); + end + fprintf('> Group level predictors:\n'); + for i=1:numel(EXPT.covnames) + fprintf('> %-4d%s\n',i,EXPT.covnames{i}); + end + diary off + + njobs = numel(includedcons) * (1+numel(EXPT.covnames)); + if ~isempty(OPTS.parallel_dream) && njobs>1 + % define scheduler + sched = findResource('scheduler', 'type', 'generic'); + set(sched,'ClusterSize', 1); + set(sched,'DataLocation',wd); + switch OPTS.parallel_dream + case '2009a' + set(sched,'ClusterMatlabRoot','/usr/local/matlab/R2009a'); + set(sched,'ParallelSubmitFcn',@sgeParallelSubmitFcn); + set(sched,'SubmitFcn',@sgeSubmitFcn); + set(sched,'DestroyJobFcn', @sgeDestroyJobFcn); + set(sched,'DestroyTaskFcn',@sgeDestroyTaskFcn); + case '2011a' + set(sched,'ClusterMatlabRoot','/usr/local/matlab/R2011a'); + set(sched,'ParallelSubmitFcn',@parallelSubmitFcn); + set(sched,'SubmitFcn',@distributedSubmitFcn); + set(sched,'DestroyJobFcn', @destroyJobFcn); + set(sched,'DestroyTaskFcn',@destroyTaskFcn); + end + set(sched,'ClusterOsType','unix'); + set(sched,'HasSharedFileSystem', true); + nargsout = 1; + + % submit jobs + diary(diaryname), fprintf('> %s\tSubmitting jobs to cluster\n',datestr(now,31)); date; diary off + for c = 1:numel(includedcons) + % save(fullfile(wd,sprintf('env_%04d',c)),'EXPT','includedcons','OPTS','grpmodeldir','pthresh'); + save(fullfile(wd,sprintf('env_%04d',c)),'EXPT','includedcons','OPTS','grpmodeldir','STARTINGDIR'); + j = sched.createJob(); + set(j,'PathDependencies',cellstr(path)); + createTask(j, str2func('canlab_glm_group_levels_run1input'), nargsout, {wd c}); + alltasks = get(j, 'Tasks'); + set(alltasks, 'CaptureCommandWindowOutput', true); + diary(diaryname), fprintf('> Submitting analysis for contrast number %d\n',includedcons(c)); diary off + submit(j); + end + + % wait + diary(diaryname), fprintf('> WAITING for jobs to stop running (they may run for a while)\n'); diary off + t1 = clock; + notdone = true; + fprintf('Elapsed minutes: ') + while notdone + notdone = false; + for i=1:8, fprintf('\b'); end; fprintf('%8.1f',etime(clock,t1)/60); + pause(10) + jobstatefiles = filenames(sprintf('%s/Job*.state.mat',wd),'absolute'); + for s = 1:numel(jobstatefiles) + jobstate = textread(jobstatefiles{s},'%s'); + if strcmp(jobstate,'running') || strcmp(jobstate,'queued'), notdone = true; break; end + end + end + diary(diaryname), fprintf('> \n> %s\tJobs done running\n',datestr(now,31)); diary off + + % handle output arguments/streams + diary(diaryname), fprintf('> GATHERING job outputs\n'); diary off + jobdirs = filenames(sprintf('%s/Job*[0-9]',wd),'absolute'); + for i = 1:numel(jobdirs) + % load state and output + jobin = load(sprintf('%s/Task1.in.mat',jobdirs{i})); + jobout = load(sprintf('%s/Task1.out.mat',jobdirs{i})); + + % parse output arguments + robfitstatuses(jobin.argsin{2}) = jobout.argsout{1}; %#ok +% grfstatuses(jobin.argsin{2}) = jobout.argsout{2}; %#ok + + % output stream + diary(fullfile(wd,sprintf('cmdwnd_%04d.txt',jobin.argsin{2}))), regexprep(jobout.commandwindowoutput,'\b[^\n]*\n',' LINES WITH BACKSPACES OMITTED\n'), diary off + end + % merge statuses + if any(robfitstatuses==-1), robfitstatus = -1; else robfitstatus = 1; end +% if any(grfstatuses==-1), grfstatus = -1; else grfstatus = 1; end + + % output stream + [ignore ignore] = system(sprintf('cat %s/cmdwnd_*txt > %s',wd,fulldiaryname)); %#ok + + % merge diaries + [ignore ignore] = system(sprintf('grep ''^> '' %s >> %s',fulldiaryname,diaryname)); %#ok + else + cmd = 'EXPT = robfit(EXPT, includedcons, 0, EXPT.mask);'; + diary(diaryname), disp(cmd); diary off + try + eval(cmd) + robfitstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc); + else diary(diaryname), fprintf('> %s\n',getReport(exc,'extended')), diary off; end + robfitstatus = -1; + end + end + end + + + %% post processes + % write contrast names to textfiles + if OPTS.run_printconnames + subconnamefile = fullfile(grpmodeldir,'subconnames.txt'); + grpconnamefile = fullfile(grpmodeldir,'grpconnames.txt'); + diary(diaryname), announce_string('WRITING CONTRAST NAMES FILES'), diary off + diary(diaryname), fprintf('> %s\n> %s\n',subconnamefile,grpconnamefile), diary off + + fid = fopen(subconnamefile,'w'); + for i = 1:size(EXPT.SNPM.connames,1) + fprintf(fid,'> %d %s\n',i,deblank(EXPT.SNPM.connames(i,:))); + end + fclose(fid); + + fid = fopen(grpconnamefile,'w'); + for i = 1:numel(EXPT.covnames) + fprintf(fid,'> %d %s\n',i,EXPT.covnames{i}); + end + fclose(fid); + end + + + % estimate significant cluster sizes + % if ~OPTS.parallel_dream && OPTS.run_grf + if OPTS.run_grf + diary(diaryname), announce_string('SIGNIFICANT CLUSTER SIZE ESTIMATION using GRF'), diary off + graymattermask = which('scalped_avg152T1_graymatter_smoothed.img'); %#ok used in eval + try + robdirs = filenames(fullfile(grpmodeldir,'robust[0-9][0-9][0-9][0-9]'),'absolute'); + for i = 1:numel(robdirs) + load(fullfile(robdirs{i},'SETUP.mat')); + + cmd = 'sigclext = estimate_cluster_extent(.05, pthresh, SETUP.files, ''mask'', graymattermask);'; + diary(diaryname), fprintf('> %s\n',cmd), diary off + eval(cmd); + close all + + fout = fullfile(robdirs{i},'significant_cluster_extents_grf.txt'); + dlmwrite(fout,[pthresh' sigclext(:,1)],'precision','%g','delimiter',' '); %#ok + end + + grfstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryname), fprintf('> %s\n',getReport(exc,'extended')), diary off; end + grfstatus = -1; + end + end + + + % make invp maps + if OPTS.run_invp + cmd = sprintf('robust_results_make_invp_maps(''d'',''%s'');',grpmodeldir); + diary(diaryname), announce_string('MAKING INVERSE P MAPS'), diary off + try + eval(cmd); + invpstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc); + else diary(diaryname), fprintf('> %s\n',getReport(exc,'extended')), diary off; end + invpstatus = -1; + end + end + + + % make named links to robust directories + if OPTS.run_links + diary(diaryname), announce_string('MAKING NAMED LINKS TO ROBUST DIRECTORIES'), diary off + try + % get grpmodeldir without /../ + if ismac + [dummy absgrpmodeldir] = system(sprintf('cd %s; pwd -P',grpmodeldir)); %#ok ~ doesn't work in older matlabs + else + [dummy absgrpmodeldir] = system(sprintf('readlink -f %s',grpmodeldir)); %#ok ~ doesn't work in older matlabs + end + absgrpmodeldir = deblank(absgrpmodeldir); + % make output directory + linkdir = fullfile(absgrpmodeldir,'named_robust_directories'); + mkdir(linkdir); + % make links + for i = 1:size(EXPT.SNPM.connames,1) + % get absolute path of robust directory (with no /../) + robdir = fullfile(absgrpmodeldir, sprintf('robust%04d',i)); + + % clean up contrast name for filesystem + linkname = deblank(EXPT.SNPM.connames(i,:)); + linkname = regexprep(linkname,'[()]',''); + linkname = regexprep(linkname,'[^0-9A-Za-z-_]','_'); + linkname = fullfile(linkdir,linkname); + + % make link + diary(diaryname) + fprintf('> %20s: %s\n','robust directory',robdir) + fprintf('> %20s: %s\n','named link',linkname) + eval(sprintf('!ln -s %s %s',robdir,linkname)) + diary off + end + + linkdirstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc); + else diary(diaryname), fprintf('> %s\n',getReport(exc,'extended')), diary off; end + linkdirstatus = -1; + end + end + + +end + + +%% PUBLISH RESULTS +diary(diaryname), announce_string('PUBLISH RESULTS'), diary off +if ~OPTS.run_results + diary(diaryname), fprintf('> SKIPPED: turned off in options.\n'); diary off +elseif robfitstatus == -1 + diary(diaryname), fprintf('> SKIPPED: robfit failed.\n'); diary off +else + try + canlab_glm_publish('g', grpmodeldir); + reviewstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc); + else diary(diaryname), fprintf('> %s\n',getReport(exc,'extended')), diary off; end + reviewstatus = -1; + end +end + +%% failure report +diary(diaryname) +announce_string('FAILURE REPORT') +fail = 0; +if robfitstatus == -1 + fprintf('> \n> WARNING: robfit failed.\n') + fail=fail+1; +end +if invpstatus == -1 + fprintf('> \n> WARNING: inverse p map making failed.\n') + fail=fail+1; +end +if grfstatus == -1 + fprintf('> \n> WARNING: cluster size estimation failed.\n') + fail=fail+1; +end +if linkdirstatus == -1 + fprintf('> \n> WARNING: named linking to robust directories failed.\n') + fail=fail+1; +end +if reviewstatus == -1 + fprintf('> \n> WARNING: review publishing failed.\n') + fail=fail+1; +end + +if fail==0, fprintf('> \n> RAN WITH NO PROBLEMS (or at least so it seems).\n'); end +diary off + + +%% email notification +if exist('address','var') + diary(diaryname) + try + [ignore output] = system(sprintf('printf "canlab_glm_group_levels has finished running.\nDirectory: %s\n\nLog file: %s\nFailed parts: %d\n" | mail -v -s "canlab_glm_group_levels done" %s',grpmodeldir,diaryname,fail,address)); %#ok + catch %#ok + fprintf('The email notification job failed.\n'); + end + diary off +end + +%% clean up +diary(diaryname), fprintf('> \n> \n> FINISHED: %s\n',datestr(now,31)), diary off + +cd(STARTINGDIR) + +end + + + +% ------------------------------------------------------------------------- +% SUBFUNCTIONS ---------------------------------------------------------- +% ------------------------------------------------------------------------- + + +function announce_string(string) + +s = sprintf('-- %s --',string); +l = regexprep(s,'.','-'); +fprintf('> \n> \n> \n> %s\n> %s\n> %s\n> \n',l,s,l); + +end + + +%% From Kathy Pearson: +% replace each first instance of SPM-like output backspace with newline; +% ignore additional backspaces found in sequence +% +% function [wrapstr] = nobackspace(str) +% +% wrapstr = str; +% i = strfind(wrapstr, 8); +% if ~isempty(i) +% k = 0; +% n = length(str); +% first8 = 1; +% for j = 1:n +% if str(j) == 8 +% if first8 +% k = k + 1; +% wrapstr(k) = 10; +% first8 = 0; +% end +% else +% k = k + 1; +% wrapstr(k) = str(j); +% first8 = 1; +% end +% end +% wrapstr = wrapstr(1:k); +% end +% +% end diff --git a/GLM_Batch_tools/canlab_glm_group_levels_run1input.m b/GLM_Batch_tools/canlab_glm_group_levels_run1input.m new file mode 100644 index 00000000..24ba1750 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_group_levels_run1input.m @@ -0,0 +1,83 @@ +function [robfitstatus] = canlab_glm_group_levels_run1input(wd, c) +% child process of canlab_glm_group_levels +% (see canlab_glm_README.txt for an overview) + +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Luka Ruzic +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% + +% Programmers' notes: + + +%% PREP +load(fullfile(wd,sprintf('env_%04d',c))); +% diaryname = fullfile(wd,sprintf('diary_%04d.log',c)); +cd(grpmodeldir) + +robfitstatus = 0; %#ok +% grfstatus = 0; + +%% robfit +cmd = 'robfit(EXPT, includedcons(c), 0, EXPT.mask)'; +% diary(diaryname), fprintf('> %s\n',cmd); diary off +fprintf('> %s\n',cmd); +try + eval(cmd) + robfitstatus = 1; +catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc); + else fprintf('> %s\n',getReport(exc,'extended')); end + % else diary(diaryname), fprintf(getReport(exc,'extended')), diary off; end + robfitstatus = -1; +end + + +%% grf % having problems with this running in parallel (display problems?) +% if OPTS.run_grf +% robdir = filenames(fullfile(grpmodeldir,sprintf('robust%04d',includedcons(c))),'char','absolute'); +% if ~isempty(robdir) +% announce_string('SIGNIFICANT CLUSTER SIZE ESTIMATION using GRF') +% try +% load(fullfile(robdir,'SETUP.mat')); +% +% cmd = 'sigclext = estimate_cluster_extent(.05, pthresh, SETUP.files);'; +% fprintf('> %s\n',cmd) +% eval(cmd); +% close all +% +% fout = fullfile(robdir,'significant_cluster_extents_grf.txt'); +% dlmwrite(fout,[pthresh' sigclext(:,1)],'precision','%g','delimiter',' '); %#ok +% +% grfstatus = 1; +% catch exc +% if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) +% else fprintf('> %s\n',getReport(exc,'extended')); end +% grfstatus = -1; +% end +% end +% end + +end + + + +function announce_string(string) + +s = sprintf('-- %s --',string); +l = regexprep(s,'.','-'); +fprintf('> \n> \n> \n> %s\n> %s\n> %s\n> \n',l,s,l); + +end diff --git a/GLM_Batch_tools/canlab_glm_maskstats.m b/GLM_Batch_tools/canlab_glm_maskstats.m new file mode 100644 index 00000000..8c614695 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_maskstats.m @@ -0,0 +1,493 @@ +function [MASKSTATS] = canlab_glm_maskstats(DIRS,MASK,varargin) +% MASKSTATS = canlab_glm_maskstats(robfitdir, mask, [options]) +% +% DESCRIPTION +% Returns a MASKSTATS structure containing data from robfitdir's input subject level +% SPM analyses. +% +% output structure: +% MASKSTATS +% COV - subject x covariate matrix (EXPT.cov from robfit design) +% COVNAME - names of covariates in COV +% MASK - array of structs (1 per mask) +% MASKFILE - the filename of the mask used +% SUB - struct containing data from subject level images +% CON - struct contains data from contrast images +% NAME - cell array (1 cell per contrast) of contrast names +% IMGFILES - cell array (1 cell per contrast) of character arrays of +% contrast image filenames +% (MEASURE) - subject X contrast matrix of measures (see canlab_maskstats) +% COVxCON.(MEASURE) - arrays of covariate matrix X contrast means correlation +% results (RHO and P, see help corr()) +% GRP - struct containing data from group level images +% BETA - struct array (1 struct per group level regressor) of data +% from beta images +% NAME - name of group level regressor +% IMGFILES - cell array (1 cell per robust directory) of beta image files +% (MEASURE) - row vector (1 value per robust directory) of measures +% (see canlab_maskstats) +% +% The following plots are saved in each mask's directory in the plots directory: +% - contrast means by contrast (means are lines across subjects on x axis) +% - contrast means by subject (means are dots, lined up along the x axis by contrast) +% - group level betas (bar plot with group level regressors grouped by +% subject level contrasts) +% if there's more than one regressor in the group level model, for each regressor: +% - scatter plot of subject level contrast means against group level regressor +% +% ARGUMENTS +% robfitdir +% a directory in which robfit was run +% contains robfit directories (e.g., robust0001) +% preferably contains EXPT.mat or EXPTm.mat +% mask +% a filename or cell array of filenames of masks to apply to data +% OPTIONS +% MEASURE OPTIONS +% (see canlab_maskstats) (DEFAULT: mean (within mask's non-zero voxels)) +% 'cons', connums +% (vector of contrast numbers) +% only include data from contrasts as specified by numbers +% 'cons', conname(s) +% (string or cell array of strings) +% only include data from contrasts as specified by name +% 'plots' +% make plots +% 'od', dir +% will save plots in dir (DEFAULT: robfitdir/stats_scatterplots) +% + +% Programmers' notes: +% to write: +% save data option +% save as csv option +% option to include subject-level beta data +% option to break input mask into regions? +% within subjects error bars +% grouped bar plots? +% save volInfo (for mask? betas? cons? from fmri_data or spm?) + + +%% SETUP +% set defaults +DO_PLOTS = false; +OP = {}; +ALLOPS = false; + +applymaskopts = ''; + +% modify arguments as needed +if ~iscell(MASK), MASK = {MASK}; end + +% error checking +if ~exist('DIRS','var') || isempty(DIRS) + error('No robftidir specified.') +end +if ~exist('MASK','var') || isempty(MASK) + error('No MASK(s) specified.') +end + +% this gets checked/errored in canlab_maskstats +% for i=1:numel(MASK) +% if ~exist(MASK{i},'file') && ~any(~cellfun('isempty',regexp({'nps','nps_thresh','nps_thresh_smooth'},['^' MASK{i} '$']))) +% error('No such file: %s',MASK{i}) +% end +% end + +% parse varargin +i=1; +while i<=numel(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'c' 'cons'} + i=i+1; + if ischar(varargin{i}) + included_connames = varargin(i); + elseif iscellstr(varargin{i}) + included_connames = varargin{i}; + elseif isnumeric(varargin{i}) + included_connums = varargin{i}; + else + error('argument to ''c'' must be number, string, or cell string') + end + case 'od' + i=i+1; + plotdir = varargin{i}; + case 'plots' + DO_PLOTS = true; + case {'binmean' 'mean' 'std' 'nonbinmean' 'nonbinstd' 'norm' ... + 'dot_product' 'centered_dot_product' 'cosine_similarity' 'correlation'} + OP{end+1} = varargin{i}; %#ok + case 'all' + ALLOPS = true; + case 'l1norm' + applymaskopts = [applymaskopts ', ''l1norm''']; %#ok + case 'l2norm' + applymaskopts = [applymaskopts ', ''l2norm''']; %#ok + otherwise + error(['UNRECOGNIZED OPTION: ' varargin{i}]) + end + elseif iscellstr(varargin{i}) + for j=1:numel(varargin{i}) + switch varargin{i}{j} + case {'binmean' 'mean' 'std' 'nonbinmean' 'nonbinstd' 'norm' ... + 'dot_product' 'centered_dot_product' 'cosine_similarity' 'correlation'} + OP{end+1} = varargin{i}{j}; %#ok + case 'all' + ALLOPS = true; + otherwise + error('Unrecognized argument %s',varargin{i}{j}) + end + end + else + disp(varargin{i}) + error('Above option UNRECOGNIZED') + end + i=i+1; +end + +if ALLOPS, OP = {'cosine_similarity' 'dot_product' 'correlation' 'mean' 'std' 'centered_dot_product'}; end + +if isempty(OP), OP = {'mean'}; end + + + + +%% PREP +if ischar(DIRS) + DO_GRPLEV = true; + BASEDOUT = DIRS; + + robdirs = filenames(fullfile(DIRS,'robust[0-9][0-9][0-9][0-9]'),'absolute'); + if isempty(robdirs) + error('No such directories: %s/robust[0-9][0-9][0-9][0-9]',DIRS) + end + fprintf('... LOADING SETUP files from %d robust* directories in %s\n',numel(robdirs),DIRS) + SETUPS = cell(numel(robdirs),1); allconnames = SETUPS; allconfiles = SETUPS; + for r = 1:numel(robdirs) + load(fullfile(robdirs{r},'SETUP')); + SETUPS{r} = SETUP; + allconnames{r} = SETUP.name; + allconfiles{r} = SETUP.files; + clear SETUP + end + + % load EXPT + if exist(fullfile(DIRS,'EXPTm.mat'),'file') + load(fullfile(DIRS,'EXPTm')); + EXPT = EXPTm; + clear EXPTm; + elseif exist(fullfile(DIRS,'EXPT.mat'),'file') + load(fullfile(DIRS,'EXPT')); + end + + % get covariates, if any + if size(SETUPS{1}.X,2)>1 + fprintf('... GETTING between-subjects covariate(s)\n') + cov = SETUPS{1}.X(:,2:end); + + covname = {}; + if isfield(EXPT,'covnames') + covname = EXPT.covnames(2:end); + end + else + cov = {}; + covname = ''; + end + MASKSTATS.cov = cov; + MASKSTATS.covname = covname; +elseif iscellstr(DIRS) + DO_GRPLEV = false; + BASEDOUT = pwd; + + for i = 1:numel(DIRS) + clear SPM spmmat + spmmat = fullfile(DIRS{i},'SPM.mat'); + if isempty(spmmat) + error('No such file: %s',spmmat) + end + load(spmmat); + tmpconnames{i} = cellstr(char(SPM.xCon.name)); %#ok + ntmpconnames(i) = size(tmpconnames{i},1); %#ok + allconfiles{i} = filenames(fullfile(DIRS{i}),'con_*.img'); %#ok + end + + if size(unique(ntmpconnames))~=1 + error('Not all subject levels have same contrast file names.') + end + allconnames = tmpconnames{1}; + clear SPM spmmat tmpconnames +end + +if DO_PLOTS && ~exist('plotdir','var') + plotdir = fullfile(BASEDOUT,'maskstats_plots'); +end + + +%% MAIN +% for r=1:numel(MASK) +% clear mask +% +% mask = MASK{r}; +% [ignore maskname] = fileparts(mask); +% fprintf('... GETTING data for mask: %s\n',mask) + +%% SUBJECT LEVEL STAT MAPS (cons) +fprintf('SUBJECT LEVELS\n') + +clear lconname lconfiles lcondata + +% get filenames +fprintf('Subject level contrasts:\n') +n=0; +lconfiles = {}; +if exist('included_connums','var') + connums = included_connums; +else + connums = 1:numel(allconfiles); +end +for i = connums + thisconname = deblank(allconnames{i}); + if exist('included_connames','var') && ~any(~cellfun('isempty',(regexp(thisconname,included_connames)))) + fprintf('EXCLUDED: %s\n',thisconname) + continue + else + n=n+1; + included_robustdirs(n) = i; %#ok + end + % name + lconname{n} = thisconname; %#ok + fprintf('%s\n',lconname{n}) + % files + lconfiles = [lconfiles cellstr(SETUPS{i}.files)]; %#ok +end + +% get data +lmaskstats = canlab_maskstats(MASK,lconfiles,OP); + +% pack output struct +for m=1:numel(MASK) + MASKSTATS.mask(m).mask = lmaskstats(m).maskfile; + MASKSTATS.mask(m).sub.con = lmaskstats(m).stats; + MASKSTATS.mask(m).sub.con.imgfiles = lmaskstats(m).imgfiles; + MASKSTATS.mask(m).sub.con.name = lconname; + if ~isempty(cov) + for i=1:numel(OP) + [rho pval] = corr(cov,MASKSTATS.mask(m).sub.con.(OP{i})); + MASKSTATS.mask(m).sub.covXcon.(OP{i}).rho = rho; + MASKSTATS.mask(m).sub.covXcon.(OP{i}).pval = pval; + end + end +end + + +% make plots +if DO_PLOTS + fprintf('... MAKING plots\n') + for m = 1:numel(MASK) + [ignore maskname] = fileparts(MASK{m}); %#ok + + dout = fullfile(plotdir,maskname); + if ~exist(dout,'dir'), mkdir(dout); end + + + subticklabels = {}; + for i = 1:size(char(MASKSTATS.mask(m).sub.con.imgfiles{:,1}),1) + subticklabels{i} = regexprep(deblank(MASKSTATS.mask(m).sub.con.imgfiles{i,1}),'.*/([^/]*)/[^/]*','$1'); %#ok + subticklabels{i} = sprintf('%s (%d)',subticklabels{i},i); %#ok + end + + for o=1:numel(OP) + switch OP{o} + case {'mean' 'dot_product' 'centered_dot_product' 'cosine_similarity' 'correlation'} + % line plot (one line for each contrast, across subjects) + + figure; plot(MASKSTATS.mask(m).sub.con.(OP{o})); + if strcmp(OP{o},'mean') + title(sprintf('subject level contrasts mean within %s',maskname),'interpreter','none'); + else + title(sprintf('subject level contrasts %ss with %s',OP{o},maskname),'interpreter','none'); + end + ylabel(sprintf('beta contrast %s',OP{o}),'interpreter','none'); + xaxis_labeling('subjects',subticklabels) + fout = fullfile(dout,sprintf('contrasts_by_subject__%ss.png',OP{o})); + fprintf('saving: %s\n',fout); + saveas(gcf,fout); + close gcf + + % bar plot (mean across subjects per contrast) + evalc(['barplot_columns2(MASKSTATS.mask(m).sub.con.(OP{o}),maskname,'... + '''labels'',MASKSTATS.mask(m).sub.con.name,' ... + '''ylabel'',OP{o},''plabels'',''robust'',''ind'');']); + fout = fullfile(dout,sprintf('group_mean_%s.png',OP{o})); + fprintf('saving: %s\n',fout); + scn_export_papersetup(500); + saveas(gcf,fout); + close gcf + + %%% covariate plots + if ~isempty(cov) + subdout = fullfile(dout,'scatterplots'); + if ~exist(subdout,'dir'), mkdir(subdout); end + for i = 1:size(cov,2) + for j = 1:size(MASKSTATS.mask(m).sub.con.(OP{o}),2) + figure; + plot(cov(:,i),MASKSTATS.mask(m).sub.con.(OP{o})(:,j),'o'); + lsline; + xlabel(covname{i},'interpreter','none'); + ylabel(sprintf('beta (meant within %s)',maskname),'interpreter','none'); + title(sprintf('%s correlation with %s (r=%.4f, p<%.4f)',... + MASKSTATS.mask(m).sub.con.name{j},covname{i},... + MASKSTATS.mask(m).sub.covXcon.(OP{o}).rho(i,j),... + MASKSTATS.mask(m).sub.covXcon.(OP{o}).pval(i,j)),... + 'interpreter','none'); + + scon = regexprep(lconname{j},'[()]',''); + scon = regexprep(scon,'[^A-Za-z0-9_+.-]',''); + + gcov = regexprep(covname{i},'[()]',''); + gcov = regexprep(gcov,'[^A-Za-z0-9_+.-]',''); + + fout = fullfile(subdout,sprintf('%s_%ss__by__%s.png',scon,OP{o},gcov)); + fprintf('saving: %s\n',fout) + saveas(gcf,fout); + close gcf + end + end + end + end + end + end +end + + +%% GROUP LEVEL STAT MAPS (beta) +if DO_GRPLEV + fprintf('GROUP LEVELS\n') + nhcons = size(SETUPS{1}.X,2); + hbetafiles = {}; + hbetaname = cell(nhcons,1); + for b = 1:nhcons + % get name + if exist('EXPT','var') && isfield(EXPT,'covnames') + hbetaname{b} = EXPT.covnames{b}; + else + hbetaname{b} = sprintf('hbeta%04d',b); + end + + % get list of files + temphbetafiles = {}; + for i = included_robustdirs + temphbetafiles{end+1,1} = fullfile(robdirs{i},sprintf('rob_beta_%04d.img',b)); %#ok + end + hbetafiles{b} = temphbetafiles; %#ok + end + hbetafiles = horzcat(hbetafiles{:}); + + % extract data + hmaskstats = canlab_maskstats(MASK,hbetafiles,OP); + + % pack output struct + for b = 1:nhcons + for m=1:numel(MASK) + for o=1:numel(OP) + MASKSTATS.mask(m).grp.beta(b).(OP{o}) = hmaskstats(m).stats.(OP{o})(:,b); + end + MASKSTATS.mask(m).grp.beta(b).imgfiles = hmaskstats(m).imgfiles; + MASKSTATS.mask(m).grp.beta(b).name = hbetaname{b}; + end + end + + + + if DO_PLOTS + dout = fullfile(plotdir,maskname); + if ~exist(dout,'dir'), mkdir(dout); end + + fprintf('... MAKING plots\n') + for m = 1:numel(MASK) + conticklabels = {}; + for i = 1:numel(MASKSTATS.mask(m).sub.con.name) + conticklabels{i} = MASKSTATS.mask(m).sub.con.name{i}; %#ok + end + + for o = 1:numel(OP) + X = []; + for b = 1:nhcons + X = [X MASKSTATS.mask(m).grp.beta(b).(OP{o})]; %#ok + end + + figure; bar(X,'group'); + title(sprintf('group level betas %s with %s',OP{o},maskname),'Interpreter','none'); + ylabel(sprintf('beta %s',OP{o}),'Interpreter','none'); + legend(strvcat(MASKSTATS.mask(m).grp.beta.name)); %#ok + xaxis_labeling('subject level contrasts',conticklabels); + fout = fullfile(dout,sprintf('group_level_betas_%s.png',OP{o})); + fprintf('saving: %s\n',fout); + saveas(gcf,fout); + close gcf + end + end + end +end + +%% clean up +close all + +if DO_PLOTS + % save MASKSTATS + for m = 1:numel(MASK) + [ignore maskname] = fileparts(MASK{m}); %#ok + dout = fullfile(plotdir,maskname); + maskstats = MASKSTATS.mask(m); %#ok + save(fullfile(dout,'raw_data'),'maskstats'); + end +end + + + +end + + + +%% slightly edited code from mathworks.com (search "matlab angled tick labels") +function xaxis_labeling(xaxislabel,ticklabels) + +% reduce size of axis to fit labels +pos = get(gca,'Position'); +set(gca,'Position',[pos(1), .2, pos(3) .65]) + +% set tick locations +Xt = 1:numel(ticklabels); +Xl = [0 numel(ticklabels)+1]; +set(gca,'XTick',Xt,'Xlim',Xl); + +ax = axis; % Current axis limits +axis(axis); % Set the axis limit modes to manual +Yl = ax(3:4); % Y-axis limits + +% Place the text labels +t = text(Xt,Yl(1)*ones(1,numel(Xt)),ticklabels,'Interpreter','none'); +set(t,'HorizontalAlignment','right','VerticalAlignment','top', ... + 'Rotation',45); + +% Remove the default labels +set(gca,'XTickLabel','') + +% get the Extent of each text object +for i = 1:length(ticklabels) + ext(i,:) = get(t(i),'Extent'); %#ok +end + +% Determine lower point for alignment of text +LowYPoint = min(ext(:,2)); + +% Place the axis label +XMidPoint = Xl(1) + abs(diff(Xl))/2; +text(XMidPoint,LowYPoint,xaxislabel,'VerticalAlignment','top','HorizontalAlignment','center'); + +% adjust width +OP = get(gca,'OuterPosition'); +set(gcf,'OuterPosition',[OP(1) OP(2) numel(ticklabels)*50 OP(3)]) + +end diff --git a/GLM_Batch_tools/canlab_glm_moreinfo.txt b/GLM_Batch_tools/canlab_glm_moreinfo.txt new file mode 100644 index 00000000..f2e84366 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_moreinfo.txt @@ -0,0 +1,79 @@ +OVERVIEW +canlab_glm_* are a set of functions to handle the running +of glm analyses of fmri data. They use SPM for analysis of +individual subjects and Tor Wager's robust regression +functions (robfit) for group level analysis. Details can +be found in the description of the DSGN structure (below) +and in the help statements for the functions, but here is a brief +overview: + +Before running canlab_glm functions, you will need to have: +- preprocessed functional data +- modeling files (SPM style "conditions" and "regressors" MAT-files) +- a DSGN structure loaded in your workspace or saved as a MAT-file + + +PREPROCESSING +You can use canlab_preproc_2012 for your preprocessing needs. Whatever +you use, you must end up with 4D functional image files that have the +same name for each subject and the relative path to each subject's directory. +See DSGN.funcnames + + +SPM "REGRESSORS" +A simple script can make multiple regressors files, one +for each run, from the products of canlab_preproc_2012 +(several wagerlab studies have used modified version of the same +script, make_noise_model1.m, for this purpose). If needed, you +can also include regressors of interest in your multiple regressors +files. You can also save multiple files and call a different one +for each analysis you run. +See DSGN.multireg + + +SPM "CONDITIONS" +Similarly, you must make conditions files, typically using +data in your experiment logs (e.g., .edat files from E-Prime). +This is often the most complicated process in running an +analysis and varies the most from one experiment to the next. +See DSGN.contrasts + + +LOWER LEVELS IN SPM +With these files in place, you will then need to assemble a +DSGN structure (probably using a script) that will be passed into +canlab_glm_subject_levels (defines all aspects of the design/model +for the analysis). + +canlab_glm_subject_levels will take DSGN as a +variable (from the workspace) or as a MAT-file, so your setup +script can either save DSGN in a MAT-file or simply load the +DSGN variable into the workspace. + +Ex: +>> setup_pr_model2 +>> canlab_glm_subject_levels(DSGN, 'email', 'ruzic@colorado.edu') +OR (if saved as a MAT-file) +>> canlab_glm_subject_levels('pr_model2.mat') + +Make sure you check out the available options in +help canlab_glm_subject_levels. + + +GROUP LEVELS WITH ROBFIT +With subject levels completed, you can run group levels with +robfit using canlab_glm_group_levels. This function is built +with defaults that can be overridden. If you did lower levels +using canlab_glm_subject_levels, you can reuse the DSGN structure +(or MAT-file containing it). + +>> canlab_glm_group_levels(DSGN, 'o', '../second_level/pr_model2') + +(By default it the output will be written to the same +directory as the subject level analyses. The standard +wagerlab organization is to keep group level analyses +together in a separate directory, "second_level", hence +the 'o' option in the above command.) + +Make sure you check out the available options in +help canlab_glm_subject_levels. diff --git a/GLM_Batch_tools/canlab_glm_publish.m b/GLM_Batch_tools/canlab_glm_publish.m new file mode 100644 index 00000000..18d4528a --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_publish.m @@ -0,0 +1,165 @@ +function canlab_glm_publish(varargin) +% canlab_glm_publish(directory_specifications [options]) +% +% DIRECTORY SPECIFICATION +% 's', dirs +% Generates HTML reports of the output from scn_spm_design_check for +% directories in cell array dires (string allowable for single dir). +% If a directory is a subject level analysis, the HTML will be generated +% for that subject in the analysis directory. +% EX: canlab_glm_publish('s',{'model1/1011' 'model1/1014'}) +% If a directory contains subject level analyses, an HTML will be +% generated with output for each subject level analysis. +% EX: canlab_glm_publish('s','model1') +% ASSUMPTION: lower level analyses contain an SPM.mat file. +% +% 'g', dirs +% For each "robfit directory" in cell array dirs, will run robust_results_batch +% on all contrast directories (e.g., robust0001/) (string allowable for single dir). +% ("robfit directories" contain robfit contrast directories (like robust0001)) +% EITHER directories contain EXPT.mat files (see help robfit) +% OR an EXPT struct is loaded in the workspace and a single directory is specified +% OR will do best to figure out info normally contained in EXPT.mat +% EX: canlab_glm_publish('g', {'group_n35' 'group_anxiety_n35' 'group_sadness_n35'}) +% +% NOTE: directory paths may be absolute or relative (to working directory) +% +% +% OPTIONS +% 't', {[pthresh clustersize] ...} +% Use the paired voxelwise_pthresh and minimum_cluster_size thresholds +% with which to produce robfit results maps. +% This option must follow immediately after a 'g' option (see above) and +% will only apply to the analyses specified in that option. +% ONLY applies to robfit directories (no bearing on lower level design checks) +% DEFAULT: {[.001 5] [.005 1] [.05 1]} +% EX: canlab_glm_publish('g', pwd, 't', {[.001 1] [.005 10] [.05 10] [.01 25]}) +% +% 'email', address +% send notification email to address when done running +% EX: canlab_glm_publish('g', pwd, 'email', 'ruzic@colorado.edu') +% + + +%% setup +thresh = [.001 .005 .05]; +size = [5 1 1]; + +STARTINGDIR = pwd; + +%% parse arguments + +i=1; +while i<=numel(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'s' 'l'} + i=i+1; + if isstr(varargin{i}) + varargin{i} = {varargin{i}}; + elseif ~iscellstr(varargin{i}) + error('[Argument to ''s'' must a be string or cell array.]') + end + spmlower_report(STARTINGDIR,varargin{i}); + + case {'g' 'r'} + i=i+1; + if isstr(varargin{i}) + varargin{i} = {varargin{i}}; + elseif ~iscellstr(varargin{i}) + error('[Argument to ''g'' must a be string or cell array.]') + end + robfitdirs = varargin{i}; + + if i < nargin && strcmp(varargin{i+1},'t') + i=i+2; + t = cell2mat(varargin{i}); + thresh = t(1:2:end); + size = t(2:2:end); + end + + robfit_report(STARTINGDIR,robfitdirs,thresh,size); + + case {'email'} + i=i+1; + address = varargin{i}; + + otherwise + error(['Unrecognized argument: ' varargin{i}]) + + end + else + error(['Unrecognized argument: ' varargin{i}]) + end + i=i+1; +end + +%% clean up +cd(STARTINGDIR) + +%% email notification +if exist('address','var') + try + [ignore output] = system(sprintf('printf "canlab_glm_publish has finished running.\n" | mail -v -s "canlab_glm_publish done" %s',address)); %#ok + catch %#ok + fprintf('The notification email failed to send.\n'); + end +end + +end + + +function spmlower_report(STARTINGDIR,analysisdirs) + +for i = 1:numel(analysisdirs) + if regexp(analysisdirs{i},'^/') + targdir = analysisdirs{i}; + else + targdir = fullfile(STARTINGDIR, analysisdirs{i}); + end + + if ~exist(targdir,'dir') + warning('No such directory: %s', targdir) + continue + end + + + cd(targdir); + + outputdir = fullfile(targdir, 'design_review_html'); + mkdir(outputdir) + + p = struct('useNewFigure', false, 'maxHeight', 1500, 'maxWidth', 1200, ... + 'outputDir', outputdir, 'showCode', false); + + fout = publish('canlab_glm_publish_subject_levels.m', p); + fprintf('Created subject level design review:\n\t%s\n', fout); +end + +end + + + +%% robfit group analyses (robust_results_batch) +function robfit_report(STARTINGDIR,robfitdirs,thresh,size) + +assignin('base','thresh',thresh) +assignin('base','size',size) + +for i = 1:numel(robfitdirs) + if regexp(robfitdirs{i},'^/') + targdir = robfitdirs{i}; + else + targdir = fullfile(STARTINGDIR, robfitdirs{i}); + end + + if ~exist(targdir,'dir') + fprintf('*** WARNING: skipping robfit directory (not found): %s\n', targdir) + continue + end + + cd(targdir); + canlab_glm_publish_group_levels +end + +end diff --git a/GLM_Batch_tools/canlab_glm_publish_group_levels.m b/GLM_Batch_tools/canlab_glm_publish_group_levels.m new file mode 100644 index 00000000..58a4219f --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_publish_group_levels.m @@ -0,0 +1,65 @@ +% child script of canlab_glm_publish +% runs robust_results_batch in publishable manner +% runs in directory containing robust* directories + +%% prep +z = '________________________________________________________________________________________________________'; +assignin('base','z',z); +if ~exist('thresh','var'), thresh = [.001 .005 .05]; assignin('base','thresh',thresh); end +if ~exist('size','var'), size = [5 1 1]; assignin('base','size',size); end + +if exist('EXPT','var') + for i = 1:length(EXPT.SNPM.P) + robregdirs{i} = fullfile(pwd,sprintf('robust%04d',i)); + connames{i} = EXPT.SNPM.connames(i,:); + end +else + d = filenames('robust[0-9][0-9][0-9][0-9]','absolute'); + if isempty(d), d{1} = pwd; end + for i = 1:numel(d) + robregdirs{i} = d{i}; + load(fullfile(robregdirs{i},'SETUP.mat')); + load(fullfile(fileparts(SETUP.files(1,:)),'SPM.mat')); + c = regexprep(SETUP.files(1,:),'.*con_0*([0-9]*)\.img','$1'); + connames{i} = SPM.xCon(str2num(c)).name; + end +end + + +%% write script +scriptfile = 'robfit_results.m'; +scriptfileabs = fullfile(pwd,scriptfile); +fid = fopen(scriptfile,'w'); + +for i = 1:length(robregdirs) + fprintf(fid,'%%%% contrast(%d) %s\n',i,connames{i}); + fprintf(fid,'cd(''%s'')\n',robregdirs{i}); + fprintf(fid,'fprintf(''%%s\\n%%s\\n%%s\\n%%s\\n%%s\\n%%s\\n'',z,z,''%s'',''%s'',z,z)\n',connames{i},robregdirs{i}); + + fprintf(fid,'try\n'); + maskimg = fullfile(robregdirs{i}, 'rob_tmap_0001.img'); % any image in space with non-zero vals for all vox would do + fprintf(fid,'robust_results_batch(''thresh'', thresh, ''size'', size, ''prune'', ''mask'', ''%s'');\n',maskimg); + + fprintf(fid,'catch exc\n'); + fprintf(fid,'disp(getReport(exc,''extended''))\n'); + fprintf(fid,'end\n'); +end +fprintf(fid,'close all\n'); + +fclose(fid); + + +%% publish script +outputdir = fullfile(pwd, 'Robust_Regression_Results_html'); +mkdir(outputdir); + +p = struct('useNewFigure', false, 'maxHeight', 1500, 'maxWidth', 1200, ... + 'outputDir', outputdir, 'showCode', false); + +fout = publish(scriptfile, p); +fprintf('Created robfit results directory:\n%s\nHTML report: %s\n', outputdir, fout); + + +%% clean up +close all +delete(scriptfileabs) diff --git a/GLM_Batch_tools/canlab_glm_publish_subject_levels.m b/GLM_Batch_tools/canlab_glm_publish_subject_levels.m new file mode 100644 index 00000000..3951df59 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_publish_subject_levels.m @@ -0,0 +1,33 @@ +% child script of canlab_glm_publish +% runs scn_spm_design_check in publishable manner +% runs in directory containing subject level SPM analyses + + +z = '_________________________________________________'; +diaryname = fullfile('model_review_work_log.txt'); + + +if exist(fullfile(pwd,'SPM.mat'),'file') + sublevs{1} = pwd; +else + % figure out which directories are lower level analyses + % criterion: they contain an SPM.mat file + spmfiles = filenames(fullfile('*','SPM.mat'),'absolute'); + sublevs = cellfun(@fileparts,spmfiles,'UniformOutput',false); +end + +for i = 1:numel(sublevs) + diary(diaryname), fprintf('%s\n%s\n%s\n', z, sublevs{i}, z), diary off + + try + scn_spm_design_check(sublevs{i}, 'events_only'); + snapnow + catch exc + diary(diaryname) + disp(getReport(exc,'extended')) +% fprintf('Design review FAILED for: %s.\n', sublevs{i}) + diary off + end +end + +close all diff --git a/GLM_Batch_tools/canlab_glm_roistats.m b/GLM_Batch_tools/canlab_glm_roistats.m new file mode 100644 index 00000000..2e6de4dd --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_roistats.m @@ -0,0 +1,527 @@ +function [ROISTATS] = canlab_glm_roistats(DIRS,ROI,varargin) +% Use canlab_glm_maskstats instead + +fprintf('Use canlab_glm_maskstats instead\n'); + +return + +% ROISTATS = canlab_glm_roistats(robfitdir, roi) +% +% DESCRIPTION +% Returns a ROISTATS structure containing data from robfitdir's input subject level +% SPM analyses. +% +% output structure: +% ROISTATS +% COV - subject x covariate matrix (EXPT.cov from robfit design) +% COVNAME - names of covariates in COV +% ROI - array of structs (1 per roi) +% MASK - the filename of the roi mask used +% SUB - struct containing data from subject level images +% CON - struct contains data from contrast images +% NAME - cell array (1 cell per contrast) of contrast names +% FILES - cell array (1 cell per contrast) of character arrays of +% contrast image filenames +% DATA - cell array (1 cell per contrast) of voxel x subject matrices of +% contrast values (vectorized image data from dat field of fmri_data objects) +% MEANS - subject x contrast matrix of contrast means across voxels within +% roi mask +% COVxCON - arrays of covariate matrix X contrast means correlation +% results (RHO and P, see help corr()) +% GRP - struct containing data from group level images +% BETA - struct array (1 struct per group level regressor) of data +% from beta images +% NAME - name of group level regressor +% FILES - cell array (1 cell per robust directory) of beta image files +% DATA - cell array (1 cell per robust directory) of vectorized image +% data +% MEANS - array (1 value per robust directory) of beta means +% across voxels within roi mask +% +% The following plots are saved in each roi's directory in the plots directory: +% - contrast means by contrast (means are lines across subjects on x axis) +% - contrast means by subject (means are dots, lined up along the x axis by contrast) +% - group level betas (bar plot with group level regressors grouped by +% subject level contrasts) +% if there's more than one regressor in the group level model, for each regressor: +% - scatter plot of subject level contrast means against group level regressor +% +% ARGUMENTS +% robfitdir +% a directory in which robfit was run +% contains robfit directories (e.g., robust0001) +% preferably contains EXPT.mat or EXPTm.mat +% roi +% a filename or cell array of filenames of masks to apply to data +% OPTIONS +% 'c', connums +% (vector of contrast numbers) +% only include data from contrasts specified by numbers +% 'c', conname(s) +% (string or cell array of strings) +% only include data from contrasts specified by name +% 'data' +% include DATA cells in output structure (they can take up space) +% 'noplots' +% no plots will be made +% 'od', dir +% will save plots in dir (DEFAULT: robfitdir/roistats_scatterplots) +% 'dotproduct' +% include dot product of weighted mask multiplication ("pattern expression") +% + +% Programmers' notes: +% to write: +% save data option +% save as csv option +% option to include subject-level beta data +% option to break input mask into regions +% within subjects error bars +% grouped bar plots? +% save volInfo (for mask? betas? cons? from fmri_data or spm?) + + +%% SETUP +% set defaults +DO_PLOTS = true; +DO_SAVEDATA = false; +DO_DOTPRODUCT = false; + +applymaskopts = ''; + +% modify arguments as needed +if ~iscell(ROI), ROI = {ROI}; end + +% error checking +if ~exist('DIRS','var') || isempty(DIRS) + error('No robftidir specified.') +end +if ~exist('ROI','var') || isempty(ROI) + error('No ROI(s) specified.') +end +for i=1:numel(ROI) + if ~exist(ROI{i},'file') + error('No such file: %s',ROI{i}) + end +end + +% parse varargin +i=1; +while i<=numel(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'c' + i=i+1; + if ischar(varargin{i}) + included_connames = {varargin{i}}; + elseif iscellstr(varargin{i}) + included_connames = varargin{i}; + elseif isnumeric(varargin{i}) + included_connums = varargin{i}; + else + error('argument to ''c'' must be number, string, or cell string') + end + case 'data' + DO_SAVEDATA = true; + case 'od' + i=i+1; + plotdir = varargin{i}; + case 'noplots' + DO_PLOTS = false; + case 'dotproduct' + DO_DOTPRODUCT = true; + case 'l1norm' + applymaskopts = [applymaskopts ', ''l1norm''']; + case 'l2norm' + applymaskopts = [applymaskopts ', ''l2norm''']; + otherwise + error(['UNRECOGNIZED OPTION: ' varargin{i}]) + end + else + disp(varargin{i}) + error('Above option UNRECOGNIZED') + end + i=i+1; +end + + + + +%% PREP +if ischar(DIRS) + DO_GRPLEV = true; + BASEDOUT = DIRS; + + robdirs = filenames(fullfile(DIRS,'robust[0-9][0-9][0-9][0-9]'),'absolute'); + if isempty(robdirs) + error('No such directories: %s/robust[0-9][0-9][0-9][0-9]',DIRS) + end + fprintf('... LOADING SETUP files from %d robust* directories in %s\n',numel(robdirs),DIRS) + for r = 1:numel(robdirs) + load(fullfile(robdirs{r},'SETUP')); + SETUPS{r} = SETUP; %#ok + allconnames{r} = SETUP.name; + allconfiles{r} = SETUP.files; + clear SETUP + end + + % load EXPT + if exist(fullfile(DIRS,'EXPTm.mat'),'file') + load(fullfile(DIRS,'EXPTm')); + EXPT = EXPTm; + clear EXPTm; + elseif exist(fullfile(DIRS,'EXPT.mat'),'file') + load(fullfile(DIRS,'EXPT')); + end + + % get covariates, if any + if size(SETUPS{1}.X,2)>1 + fprintf('... GETTING between-subjects covariate(s)\n') + cov = SETUPS{1}.X(:,2:end); + + covname = {}; + if isfield(EXPT,'covnames') + covname = EXPT.covnames(2:end); + end + else + cov = {}; + covname = ''; + end + ROISTATS.cov = cov; + ROISTATS.covname = covname; +elseif iscellstr(DIRS) + DO_GRPLEV = false; + BASEDOUT = pwd; + + for i = 1:numel(DIRS) + clear SPM spmmat + spmmat = fullfile(DIRS{i},'SPM.mat'); + if isempty(spmmat) + error('No such file: %s',spmmat) + end + load(spmmat); + tmpconnames{i} = [SPM.xCon.name]; + allconfiles{i} = filenames(fullfile(DIRS{i}),'con_*.img'); + end + + if size(unique(tmpconnames))~=1 + error('Not all subject levels have same contrast file names.') + end + allconnames = cellstr(strvcat(SPM.xCon.name)); + clear SPM spmmat tmpconnames +end + + +%% MAIN +for r=1:numel(ROI) + clear mask + + mask = ROI{r}; + [ignore roiname] = fileparts(mask); + fprintf('... GETTING data for roi: %s\n',mask) + + %% SUBJECT LEVEL STAT MAPS (cons) + clear lconname lconfiles lcondata + + %% load data + lcondotproduct = []; + + fprintf('Subject level contrasts:\n') + n=0; + if exist('included_connums','var') + connums = included_connums; + else + connums = 1:numel(allconfiles); + end + for i = connums + thisconname = deblank(allconnames{i}); + if exist('included_connames','var') && ~any(~cellfun('isempty',(regexp(thisconname,included_connames)))) + fprintf('EXCLUDED: %s\n',thisconname) + continue + else + n=n+1; + included_robustdirs(n) = i; %#ok + end + % name + lconname{n} = thisconname; %#ok + fprintf('%s\n',lconname{n}) + % files + lconfiles{n} = SETUPS{i}.files; %#ok + % data + clear dat + evalc('dat = fmri_data(lconfiles{n},mask);'); + lcondata{n} = dat.dat; %#ok + + if DO_DOTPRODUCT % this needs to be done with the full fmri_data object, so this is the best place for it, though it belongs conceptually in the next section + clear dotproduct + evalc(['dotproduct = apply_mask(dat,mask,''pattern_expression''' applymaskopts ');']); + lcondotproduct = [lcondotproduct dotproduct]; %#ok + end + end + + + %% get subject level stats + lconmeans = []; + fprintf('... GETTING stats from subject level contrasts\n') + for i = 1:numel(lcondata) + % means + if size(lcondata{i},1) == 1 + lconmeans = [lconmeans lcondata{i}']; %#ok + else + lconmeans = [lconmeans nanmean(lcondata{i})']; %#ok + end + end + + %% pack struct + ROISTATS.roi(r).mask = mask; + ROISTATS.roi(r).sub.con.name = lconname; + ROISTATS.roi(r).sub.con.files = lconfiles; + ROISTATS.roi(r).sub.con.data = lcondata; + ROISTATS.roi(r).sub.con.means = lconmeans; + if DO_DOTPRODUCT, ROISTATS.roi(r).sub.con.dotproduct = lcondotproduct; end + + + %% do t-testing on dotproduct values + if DO_DOTPRODUCT + [tt.hypothesis tt.p tt.ci tt.stats] = ttest(ROISTATS.roi(r).sub.con.dotproduct); + ROISTATS.roi(r).sub.con.dotproduct_ttest = tt; + end + + %% make plots + if DO_PLOTS + if ~exist('plotdir','var') + if DO_GRPLEV + dout = fullfile(DIRS,'roistats_plots',roiname); + else + dout = fullfile(pwd,'roistats_plots',roiname); + end + else + dout = fullfile(plotdir,roiname); + end + if ~exist(dout,'dir'), mkdir(dout); end + + fprintf('... MAKING plots\n') + + conticklabels = {}; + for i = 1:numel(ROISTATS.roi(r).sub.con.name) + conticklabels{i} = sprintf('%s (%d)',ROISTATS.roi(r).sub.con.name{i},i); %#ok + end + + subticklabels = {}; + for i = 1:size(ROISTATS.roi(r).sub.con.files{1},1) + subticklabels{i} = regexprep(deblank(ROISTATS.roi(r).sub.con.files{1}(i,:)),'.*/([^/]*)/[^/]*','$1'); %#ok + subticklabels{i} = sprintf('%s (%d)',subticklabels{i},i); %#ok + end + + %%% means plots + % contrast means across subjects + figure; plot(ROISTATS.roi(r).sub.con.means); + title(sprintf('subject level contrasts (mean within %s)',roiname),'interpreter','none'); + ylabel('beta contrast'); + xaxis_labeling('subjects',subticklabels) + fout = fullfile(dout,'contrast_means_by_subject.png'); + fprintf('saving: %s\n',fout); + saveas(gcf,fout); + close gcf + + % contrast means by contrast + figure; plot(ROISTATS.roi(r).sub.con.means','o'); + title(sprintf('subject level contrasts (mean within %s)',roiname),'interpreter','none'); + ylabel('beta contrast'); + xaxis_labeling('contrasts',conticklabels) + fout = fullfile(dout,'contrast_means_by_contrast.png'); + fprintf('saving: %s\n',fout); + saveas(gcf,fout); + close gcf + + if DO_DOTPRODUCT + % dotproduct plot + conticklabels = {}; + for i = 1:numel(ROISTATS.roi(r).sub.con.name) + conticklabels{i} = sprintf('%s (%d)',ROISTATS.roi(r).sub.con.name{i},i); %#ok + end + + figure; bar(nanmean(ROISTATS.roi(r).sub.con.dotproduct)); + title(sprintf('average dot product across subjects (weights: %s)',roiname),'interpreter','none'); + ylabel('dot product'); + xaxis_labeling('subject level contrasts',conticklabels); + fout = fullfile(dout,'group_mean_dotproduct.png'); + fprintf('saving: %s\n',fout); + saveas(gcf,fout); + close gcf + end + + + %%% covariate plots + if ~isempty(cov) + [rho pval] = corr(cov,lconmeans); + ROISTATS.roi(r).sub.con.covXcon.rho = rho; + ROISTATS.roi(r).sub.con.covXcon.pval = pval; + + for i = 1:size(cov,2) + for j = 1:size(lconmeans,2) + figure; + plot(cov(:,i),lconmeans(:,j),'o'); + lsline; + xlabel(covname{i},'interpreter','none'); + ylabel(sprintf('beta (meant within %s)',roiname),'interpreter','none'); + title(sprintf('%s correlation with %s (r=%.4f, p<%.4f)',lconname{j},covname{i},rho(i,j),pval(i,j)),'interpreter','none'); + + scon = regexprep(lconname{j},'[()]',''); + scon = regexprep(scon,'[^A-Za-z0-9_+.-]',''); + + gcov = regexprep(covname{i},'[()]',''); + gcov = regexprep(gcov,'[^A-Za-z0-9_+.-]',''); + + fout = fullfile(dout,[scon '__by__' gcov '.png']); + fprintf('saving: %s\n',fout) + saveas(gcf,fout); + close gcf + end + end + end + end + + + %% GROUP LEVEL STAT MAPS (beta) + + %% load data + clear hbetaname hbetafiles hbetadata hbetadataobj hbetameans hbetadotproduct + + hbetadotproduct = []; + + for b = 1:size(SETUPS{1}.X,2) + % name + if exist('EXPT','var') && isfield(EXPT,'covnames') + hbetaname = EXPT.covnames{b}; + else + hbetaname = 'hbeta1'; + end + + n=0; + for i = included_robustdirs + n=n+1; + % file + hbetafiles{n} = fullfile(robdirs{i},sprintf('rob_beta_%04d.img',b)); %#ok + % data + clear dat + evalc('dat = fmri_data(hbetafiles{n},mask);'); + hbetadata{n} = dat.dat; %#ok + + if DO_DOTPRODUCT + clear dotproduct + evalc(['dotproduct = apply_mask(dat,mask,''pattern_expression''' applymaskopts ');']); + hbetadotproduct = [hbetadotproduct dotproduct]; %#ok + end + end + + % means + hbetameans = []; + fprintf('... GETTING stats from group level beta: %s\n',hbetaname) + for i = 1:numel(hbetadata) + if size(hbetadata{i},1) == 1 + hbetameans = [hbetameans hbetadata{i}']; %#ok + else + hbetameans = [hbetameans nanmean(hbetadata{i})']; %#ok + end + end + + % pack struct + ROISTATS.roi(r).grp.beta(b).name = hbetaname; + ROISTATS.roi(r).grp.beta(b).files = hbetafiles; + ROISTATS.roi(r).grp.beta(b).data = hbetadata; + ROISTATS.roi(r).grp.beta(b).means = hbetameans; + if DO_DOTPRODUCT, ROISTATS.roi(r).grp.beta(b).dotproduct = hbetadotproduct; end + end + + if DO_PLOTS + if ~exist('plotdir','var') + dout = fullfile(BASEDOUT,'roistats_plots',roiname); + else + dout = fullfile(plotdir,roiname); + end + if ~exist(dout,'dir'), mkdir(dout); end + + fprintf('... MAKING plots\n') + + conticklabels = {}; + for i = 1:numel(ROISTATS.roi(r).sub.con.name) + conticklabels{i} = sprintf('%s (%d)',ROISTATS.roi(r).sub.con.name{i},i); %#ok + end + + X = []; + for b = 1:size(SETUPS{1}.X,2) + X = [X; ROISTATS.roi(r).grp.beta(b).means]; %#ok + end + X = X'; + + figure; bar(X,'group'); + title(sprintf('group level betas (mean within %s)',roiname),'interpreter','none'); + ylabel('beta'); + legend(strvcat(ROISTATS.roi(r).grp.beta.name)); %#ok + xaxis_labeling('subject level contrasts',conticklabels); + fout = fullfile(dout,'group_level_betas.png'); + fprintf('saving: %s\n',fout); + saveas(gcf,fout); + close gcf + end + + + %% clean up + close all +end + + +%% save +if ~DO_SAVEDATA + for r=1:numel(ROISTATS.roi) + ROISTATS.roi(r).grp.beta = rmfield(ROISTATS.roi(r).grp.beta, 'data'); + ROISTATS.roi(r).sub.con = rmfield(ROISTATS.roi(r).sub.con, 'data'); + end +end + +end + + +%% slightly edited code from mathworks.com (search "matlab angled tick labels") +function xaxis_labeling(xaxislabel,ticklabels) + +% reduce size of axis to fit labels +pos = get(gca,'Position'); +set(gca,'Position',[pos(1), .2, pos(3) .65]) + +% set tick locations +Xt = 1:numel(ticklabels); +Xl = [0 numel(ticklabels)+1]; +set(gca,'XTick',Xt,'Xlim',Xl); + + +ax = axis; % Current axis limits +axis(axis); % Set the axis limit modes to manual +Yl = ax(3:4); % Y-axis limits + +% Place the text labels +t = text(Xt,Yl(1)*ones(1,numel(Xt)),ticklabels,'Interpreter','none'); +set(t,'HorizontalAlignment','right','VerticalAlignment','top', ... + 'Rotation',45); + +% Remove the default labels +set(gca,'XTickLabel','') + +% get the Extent of each text object +for i = 1:length(ticklabels) + ext(i,:) = get(t(i),'Extent'); %#ok +end + +% Determine lower point for alignment of text +LowYPoint = min(ext(:,2)); + +% Place the axis label +XMidPoint = Xl(1) + abs(diff(Xl))/2; +text(XMidPoint,LowYPoint,xaxislabel,'VerticalAlignment','top','HorizontalAlignment','center'); + +% adjust width +OP = get(gca,'OuterPosition'); +set(gcf,'OuterPosition',[OP(1) OP(2) numel(ticklabels)*50 OP(3)]) + +end diff --git a/GLM_Batch_tools/canlab_glm_subject_levels.m b/GLM_Batch_tools/canlab_glm_subject_levels.m new file mode 100644 index 00000000..f277f9b9 --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_subject_levels.m @@ -0,0 +1,623 @@ +function canlab_glm_subject_levels(dsgnarg, varargin) +% canlab_glm_subject_levels(DSGN [options]) +% +% DESCRIPTION +% Performs lower level GLM analysis with SPM: +% 1 specifies model +% 2 estimates model +% 3 generates contrast images for model +% 4 creates directory with named links to spmT and con maps +% 5 publishes analyses with scn_spm_design_check +% +% DSGN struct - defines the model and analysis parameters +% canlab_glm_subject_levels('README') to see description +% +% OPTIONS +% 'README' +% prints canlab_glm_README, an overview of canlab_glm_{subject,group}_levels +% 'dsgninfo' +% prints description of DSGN structure +% 'subjects', subject_list +% ignore DSGN.subjects, use cell array subject_list +% 'overwrite' +% turn on overwriting of existing analyses (DEFAULT: skip existing) +% 'onlycons' +% only run contrast job (no model specification or estimation) +% note: will overwrite existing contrasts +% note: to not run contrasts, simply do not include a contrasts field in DSGN +% 'addcons' +% only run contrasts that aren't already in SPM.mat +% option to canlab_spm_contrast_job +% 'nodelete' +% do not delete existing contrasts (consider using addcons, above) +% option to canlab_spm_contrast_job +% 'nolinks' +% will not make directory with named links to contrast images +% 'noreview' +% will not run scn_spm_design_check +% 'dream' +% if you're running on the dream cluster, this option will cause +% all subjects to be run in parallel (submitted with matlab DCS and +% the Sun Grid Engine) +% Note: currently only works with MATLAB R2009a +% 'email', address +% send notification email to address when done running +% +% Model specification and estimation done by canlab_spm_fmri_model_job +% Contrasts are specified by canlab_spm_contrast_job_luka +% see that function for more info. +% + +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Luka Ruzic +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% + +% Programmers' notes: + +%% SET UP +STARTTIME = datestr(now,31); +STARTINGDIR = pwd; + +% set path +if exist('canlab_preproc.m','file') ~= 2 + try + addpath(genpath('/data/projects/wagerlab/Repository')); + catch %#ok + error('Failed to add canlab repository (/data/projects/wagerlab/Repository) to path') + end +end +addpath(genpath('/usr/local/spm/spm8/matlabbatch')); +addpath(genpath('/usr/local/spm/spm8/apriori')); +addpath(genpath('/usr/local/spm/spm8/canonical')); + + +% set defaults +diarydirname = 'canlab_glm_logs'; +diaryfilename = ['subject_levels_' regexprep(regexprep(STARTTIME,' ','_'), ':','') '.log']; + +OPTS.parallel_dream = ''; +OPTS.overwrite = false; +OPTS.onlycons = false; +OPTS.nocatch = false; +OPTS.run_renaming = true; +OPTS.run_hist = false; % hidden option +OPTS.run_review = true; +OPTS.modeljob = ''; +OPTS.conjob = ''; + + +%% PARSE ARGUMENTS +% get DSGN structure +if ischar(dsgnarg) + if strcmp(dsgnarg,'README') + system(sprintf('cat %s',which('canlab_glm_README.txt'))); + return; + elseif strcmp(dsgnarg,'dsgninfo') + system(sprintf('cat %s',which('canlab_glm_dsgninfo.txt'))); + return; + elseif any(regexp(dsgnarg,'\.mat$')) && exist(dsgnarg,'file') + load(dsgnarg); + else + error('Unrecognized argument: %s', dsgnarg) + end +elseif isstruct(dsgnarg) + DSGN = dsgnarg; +else + error('DSGN structure must be given as first argument, either as a variable or as a matfile.') +end + +% go through varargin +i=1; +while i<=numel(varargin) + if ischar(varargin{i}) + switch(varargin{i}) + case {'README'} + system(sprintf('cat %s',which('canlab_glm_README.txt'))); + return; + case {'dream'} + version = ver('matlab'); + switch version.Release + case '(R2009a)' + OPTS.parallel_dream = '2009a'; + case '(R2011a)' + OPTS.parallel_dream = '2011a'; + otherwise + error('Current version of matlab (%s)',version.Release) + end + case {'dsgninfo'} + system(sprintf('cat %s',which('canlab_glm_dsgninfo.txt'))); + return; + case {'subjects'} + i=i+1; + subjects = varargin{i}; + case {'overwrite'} + OPTS.overwrite = true; + case {'runhist'} % hidden option + OPTS.run_hist = true; + case {'onlycons'} + OPTS.onlycons = true; + case {'addcons'} + OPTS.conjob = [OPTS.conjob ',''addcons''']; + case {'nocatch'} + OPTS.nocatch = true; + case {'nodelete'} + OPTS.conjob = [OPTS.conjob ',''nodelete''']; + case {'nolinks'} + OPTS.run_renaming = false; + case {'noreview'} + OPTS.run_review = false; + case {'email'} + i=i+1; + address = varargin{i}; + otherwise + error(['UNRECOGNIZED OPTION: ' varargin{i}]) + end + else + disp(varargin{i}) + error('Above option UNRECOGNIZED') + end + i=i+1; +end + + + +%% DSGN-parsing +% catch bad fields +allowablefields = {... + 'metadata' ... + 'modeldir' ... + 'subjects' ... + 'funcnames' 'allowmissingfunc' ... + 'concatenation' ... + 'tr' 'hpf' 'fmri_t' 'fmri_t0' ... + 'conditions' 'pmods' 'convolution' 'multireg' 'singletrialsall' 'singletrials' 'ar1'... + 'allowmissingcondfiles' 'allowemptycond' 'notimemod' 'modelingfilesdir' ... + 'customrunintercepts' ... + 'contrasts' 'contrastnames' 'contrastweights' ... + 'regmatching' 'defaultsuffix' 'noscale' ... + 'timingcheck' + }; +actualfields = fieldnames(DSGN); +for i = 1:numel(actualfields) + if isempty(strmatch(actualfields{i},allowablefields,'exact')) + error('UNRECOGNIZED DSGN FIELD: DSGN.%s', actualfields{i}) + end +end + + +% parse inputs, set defaults for missing inputs, etc +if ~isfield(DSGN,'modeldir') + error('No modeldir specified') + %DSGN.modeldir = pwd; +end + +if exist('subjects','var') + DSGN.subjects = subjects; +else + if ~isfield(DSGN,'subjects') + error('No subjects specified') + end +end +regexprep(DSGN.subjects,'/$',''); + +if ~isfield(DSGN,'contrastnames'), DSGN.contrastnames = {}; end +if ~isfield(DSGN,'contrastweights'), DSGN.contrastweights = {}; end + +if isfield(DSGN,'defaultsuffix') + OPTS.conjob = [OPTS.conjob ',''suffix'',''' DSGN.defaultsuffix '''']; +end + +if isfield(DSGN,'noscale') + if ~islogical(DSGN.noscale) && ~isnumeric(DSGN.noscale) + error('DSGN.noscale must be true/false or 1/0') + end + if DSGN.noscale, OPTS.conjob = [OPTS.conjob ',''noscale''']; end +end + +if isfield(DSGN,'regmatching') + OPTS.conjob = [OPTS.conjob ',''' DSGN.regmatching '''']; +end + + +if ~OPTS.onlycons % check all the model spec/estimation stuff + if ~isfield(DSGN,'funcnames'), error('No functional data specified'); end + if ~isfield(DSGN,'allowmissingfunc'), DSGN.allowmissingfunc = false; end + + if isfield(DSGN,'timingcheck') + if ~isfield(DSGN.timingcheck,'condition') + error('Must define DSGN.timingcheck.condition'); + end + + if ~isfield(DSGN.timingcheck,'mask') + error('Must define DSGN.timingcheck.mask'); + end + if ~isfield(DSGN.timingcheck,'stat') + DSGN.timingcheck.stat = 'mean'; + end + end + + if ~isfield(DSGN,'conditions'), fprintf('WARNING: No conditions specified.\n'); end + if ~isfield(DSGN,'allowmissingcondfiles'), DSGN.allowmissingcondfiles = false; end + if ~isfield(DSGN,'allowemptycond'), DSGN.allowemptycond = false; end + + if ~isfield(DSGN,'convolution') + DSGN.convolution.type = 'hrf'; + DSGN.convolution.time = 0; + DSGN.convolution.dispersion = 0; + end + + if ~isfield(DSGN,'singletrialsall'), DSGN.singletrialsall = false; end + + if ~isfield(DSGN,'ar1'), DSGN.ar1 = false; end + + if isfield(DSGN,'notimemod') && DSGN.notimemod + OPTS.modeljob = [OPTS.modeljob ',''notimemod''']; + end + if ~isfield(DSGN,'modelingfilesdir'), DSGN.modelingfilesdir = 'spm_modeling'; end + + if ~isfield(DSGN,'tr'), error('no TR specified'); end + if ~isfield(DSGN,'hpf'), error('no HPF specified'); end + if isfield(DSGN,'fmri_t') + OPTS.modeljob = [OPTS.modeljob ',''fmri_t'',' num2str(DSGN.fmri_t)]; + end + if isfield(DSGN,'fmri_t0') + OPTS.modeljob = [OPTS.modeljob ',''fmri_t0'',' num2str(DSGN.fmri_t0)]; + else + error('DSGN.fmri_t0 (microtime onset) not specified') + end + + if isfield(DSGN,'multireg') + DSGN.multireg = [regexprep(DSGN.multireg, '\.mat$', '') '.mat']; + else + DSGN.multireg = ''; + end + + if ~isfield(DSGN,'concatenation') + DSGN.concatenation = {}; + end + + % check for uneven numbers of functional files + for s = 1:numel(DSGN.subjects) + for r = 1:numel(DSGN.funcnames) + next(r) = numel(filenames(fullfile(DSGN.subjects{s},DSGN.funcnames{r}))); %#ok + end + if DSGN.allowmissingfunc + if sum(next>1)~=0 + if ~isempty(DSGN.concatenation) + fprintf('ERROR: If allowmissingfunc is set and concatenation is desired,\n') + fprintf(' DSGN.funcnames strings must match one functional data file each\n') + return + elseif numel(DSGN.conditions)>1 + fprintf('ERROR: If allowmissing func is set:') + fprintf(' EITHER only one session''s worth of conditions is specified\n') + fprintf(' OR DSGN.funcnames strings match one functional data file each\n') + return + end + end + elseif s>1 && any(next~=last)~=0 + fprintf('Number of functional images retrieved is not consistent across subjects.\n') + fprintf('(fix this or use DSGN.allowmissingfunc)\n'); + return + end + last = next; + end +end + + + +%% PREP WORK +if ~exist(DSGN.modeldir,'dir') + fprintf('Making subject level models directory: %s\n',DSGN.modeldir) + mkdir(DSGN.modeldir) +else + fprintf('Moving to existing subject level models directory: %s\n',DSGN.modeldir) +end +cd(DSGN.modeldir); + +% initialize SPM's job manager +spm_jobman('initcfg'); + +% initialize statuses +modelstatus = zeros(1,numel(DSGN.subjects)); +constatus = zeros(1,numel(DSGN.subjects)); +linkstatus = zeros(1,numel(DSGN.subjects)); +histstatus = []; % hidden option +reviewstatus = []; +tchkstatus = []; + +% diary - to be on except when calling other functions that do their own logging +diarydir = fullfile(DSGN.modeldir,diarydirname); +if ~exist(diarydir,'dir'), mkdir(diarydir); end +diaryname = fullfile(diarydir,diaryfilename); +fulldiaryname = regexprep(diaryname,'\.log$','_full.log'); +fprintf('Writing to logfile: %s\n',diaryname) +diary(diaryname), fprintf('STARTED: %s\n',STARTTIME), diary off + +% make working directory +wd = regexprep(diaryname,'\.log$',''); +mkdir(wd) + + + +%% RUN LOWER LEVELS +if ~isempty(OPTS.parallel_dream) + % define scheduler + sched = findResource('scheduler', 'type', 'generic'); + set(sched,'ClusterSize', 1); + set(sched,'DataLocation',wd); + switch OPTS.parallel_dream + case '2009a' + set(sched,'ClusterMatlabRoot','/usr/local/matlab/R2009a'); + set(sched,'ParallelSubmitFcn',@sgeParallelSubmitFcn); + set(sched,'SubmitFcn',@sgeSubmitFcn); + set(sched,'DestroyJobFcn', @sgeDestroyJobFcn); + set(sched,'DestroyTaskFcn',@sgeDestroyTaskFcn); + case '2011a' + set(sched,'ClusterMatlabRoot','/usr/local/matlab/R2011a'); + set(sched,'ParallelSubmitFcn',@parallelSubmitFcn); + set(sched,'SubmitFcn',@distributedSubmitFcn); + set(sched,'DestroyJobFcn', @destroyJobFcn); + set(sched,'DestroyTaskFcn',@destroyTaskFcn); + end + set(sched,'ClusterOsType','unix'); + set(sched,'HasSharedFileSystem', true); + nargsout = 3; + + % submit jobs + diary(diaryname), fprintf('%s\tSubmitting jobs to cluster\n',datestr(now,31)); date; diary off + for s = 1:numel(DSGN.subjects) + save(fullfile(wd,sprintf('env_%04d',s)),'DSGN','OPTS','STARTINGDIR'); + + j = sched.createJob(); + set(j,'PathDependencies',cellstr(path)); + createTask(j, str2func('canlab_glm_subject_levels_run1subject'), nargsout, {wd s}); + alltasks = get(j, 'Tasks'); + set(alltasks, 'CaptureCommandWindowOutput', true); + diary(diaryname), fprintf('Submitting analysis for %s\n',DSGN.subjects{s}); diary off + submit(j); + end + + % wait + % look into using wait, waitForState + diary(diaryname), fprintf('WAITING for jobs to stop running (they may run for a while)\n'); diary off + t1 = clock; + notdone = true; + fprintf('Elapsed minutes: ') + while notdone + notdone = false; + for i=1:8, fprintf('\b'); end; fprintf('%8.1f',etime(clock,t1)/60); + pause(10) + jobstatefiles = filenames(sprintf('%s/Job*.state.mat',wd),'absolute'); + for s = 1:numel(jobstatefiles) + jobstate = textread(jobstatefiles{s},'%s'); + if strcmp(jobstate,'running') || strcmp(jobstate,'queued'), notdone = true; break; end + end + end + diary(diaryname), fprintf('\n%s\tJobs done running\n',datestr(now,31)); diary off + + % handle output arguments/streams + diary(diaryname), fprintf('GATHERING job outputs\n'); diary off + jobdirs = filenames(sprintf('%s/Job*[0-9]',wd),'absolute'); + for i = 1:numel(jobdirs) + % load state and output + jobin = load(sprintf('%s/Task1.in.mat',jobdirs{i})); + jobout = load(sprintf('%s/Task1.out.mat',jobdirs{i})); + + % parse output arguments + modelstatus(jobin.argsin{2}) = jobout.argsout{1}; + constatus(jobin.argsin{2}) = jobout.argsout{2}; + linkstatus(jobin.argsin{2}) = jobout.argsout{3}; + + % output stream + cw = jobout.commandwindowoutput; + cw = regexprep(cw,'^> ',''); + cw = regexprep(cw,'\n> ','\n'); + cw = regexprep(cw,'\b[^\n]*\n',' LINES WITH BACKSPACES OMITTED\n'); + diary(fullfile(wd,sprintf('cmdwnd_%04d.txt',jobin.argsin{2}))), disp(cw), diary off + end + % output stream + [ignore ignore] = system(sprintf('cat %s/cmdwnd_*txt > %s',wd,fulldiaryname)); %#ok + + % merge diaries + [ignore ignore] = system(sprintf('rm %s/diary*',wd)); %#ok + [ignore ignore] = system(sprintf('grep ''^> '' %s | sed ''s|^> ||'' >> %s',fulldiaryname,diaryname)); %#ok +else + for s = 1:numel(DSGN.subjects) + save(fullfile(wd,sprintf('env_%04d',s)),'DSGN','OPTS','STARTINGDIR'); + [modelstatus(s) constatus(s) linkstatus(s)] = canlab_glm_subject_levels_run1subject(wd,s); + end + + % merge diaries + [ignore ignore] = system(sprintf('cat %s/diary* >> %s',wd,diaryname)); %#ok +end + + +%% POST ANALYSIS PROCESSES +diary(diaryname) +fprintf('\n\n\n') +fprintf('-------------------------------\n') +fprintf('-- POST ANALYSIS PROCESSES --\n') +fprintf('-------------------------------\n') +diary off +if sum(modelstatus==-1) || sum(constatus==-1) + diary(diaryname), fprintf('SKIPPED: some model jobs or contrast jobs failed.\n'), diary off +else + % TIMING CHECK + if isfield(DSGN,'timingcheck') + diary(diaryname), fprintf('\n... GENERATING TIMING CHECK REPORTS\n'), diary off + try + % publish_timing_check(DSGN); + canlab_glm_subject_levels_timingcheck(DSGN); + tchkstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryname), fprintf('%s\n',getReport(exc,'extended')); diary off; end + tchkstatus = -1; + end + end + + if sum(constatus==1) == 0 + diary(diaryname), fprintf('SKIPPED: no new contrasts have been run.\n'), diary off + else + % T MAP HISTOGRAMS + if OPTS.run_hist % hidden option + diary(diaryname), fprintf('\n... T STATISTIC HISTOGRAMS.\n'), diary off + try + cd(DSGN.modeldir) + batch_t_histograms('o','t_histograms') + close all + histstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('%s\n',getReport(exc,'extended')); diary off; end + histstatus = -1; + end + end + + + + % CANLAB DESIGN REVIEW + diary(diaryname), fprintf('\n... GENERATING DESIGN REVIEWS\n'), diary off + if ~OPTS.run_review + diary(diaryname), fprintf('SKIPPED: switched off\n'), diary off + else + try + canlab_glm_publish('s',DSGN.modeldir); + reviewstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('%s\n',getReport(exc,'extended')); diary off; end + reviewstatus = -1; + end + end + end +end + + +%% failure report +diary(diaryname) +fprintf('\n\n\n') +fprintf('----------------------\n') +fprintf('-- FAILURE REPORT --\n') +fprintf('----------------------\n') +nfail = failure_report(DSGN,modelstatus,constatus,linkstatus,histstatus,reviewstatus,tchkstatus); +diary off + + +%% email notification +if exist('address','var') + try + [ignore output] = system(sprintf('printf "canlab_glm_subject_levels has finished running.\nDirectory: %s\nLog file: %s\nFailure count: %d\n" | mail -v -s "canlab_glm_subject_levels done" %s',DSGN.modeldir,diaryname,nfail,address)); %#ok + catch %#ok + diary(diaryname), fprintf('The notification email failed to send.\n'), diary off + end +end + + +%% clean up +diary(diaryname), fprintf('\n\nFINISHED: %s\n',datestr(now,31)), diary off + +cd(STARTINGDIR) + +end + + + + +% ------------------------------------------------------------------------- +% SUBFUNCTIONS ---------------------------------------------------------- +% ------------------------------------------------------------------------- + +%% +function [nfail] = failure_report(DSGN,modelstatus,constatus,linkstatus,histstatus,reviewstatus,tchkstatus) + +nfail = 0; +if sum(modelstatus == -1) + fprintf('\nFAILED model spec/estim jobs: %d of %d\n',sum(modelstatus==-1),sum(modelstatus~=0)) + fprintf('%s\n',DSGN.subjects{modelstatus == -1}) + nfail = nfail+sum(modelstatus == -1); +end +if sum(constatus == -1) + fprintf('\nFAILED contrast jobs: %d of %d\n',sum(constatus == -1),sum(constatus ~= 0)) + fprintf('%s\n',DSGN.subjects{constatus == -1}) + nfail = nfail + sum(constatus == -1); +end +if sum(linkstatus == -1) + fprintf('\nFAILED named linking: %d of %d\n',sum(linkstatus == -1),sum(linkstatus ~= 0)) + fprintf('%s\n',DSGN.subjects{linkstatus == -1}) + nfail = nfail + sum(linkstatus == -1); +end +if histstatus == -1, fprintf('\nFAILED t histogram making.\n'); nfail=nfail+1; end +if reviewstatus == -1, fprintf('\nFAILED design reviewing.\n'); nfail=nfail+1; end +if tchkstatus == -1, fprintf('\nFAILED timing check report.\n'); nfail=nfail+1; end + +if ~nfail, fprintf('\nRAN WITH NO PROBLEMS (or at least so it seems).\n'); end + +end + + +% function publish_timing_check(DSGN) +% +% assignin('base','MASK',DSGN.timingcheck.mask); +% assignin('base','OP',DSGN.timingcheck.stat); +% +% beforedir = pwd; +% +% cd(DSGN.modeldir); +% +% outputdir = fullfile(pwd,'timing_check'); +% if exist(outputdir,'dir'), rmdir(outputdir,'s'); end +% mkdir(outputdir); +% +% p = struct('useNewFigure', false, 'maxHeight', 1500, 'maxWidth', 1200, ... +% 'outputDir', outputdir, 'showCode', false); +% +% fout = publish('canlab_glm_subject_levels_timingcheck.m',p); +% fprintf('Created subject level timing check:\n\t%s\n',fout); +% +% cd(beforedir) +% +% end + +%% From Kathy Pearson: +% replace each first instance of SPM-like output backspace with newline; +% ignore additional backspaces found in sequence +% +function [wrapstr] = nobackspace(str) %#ok + +wrapstr = str; +i = strfind(wrapstr, 8); +if ~isempty(i) + k = 0; + n = length(str); + first8 = 1; + for j = 1:n + if str(j) == 8 + if first8 + k = k + 1; + wrapstr(k) = 10; + first8 = 0; + end + else + k = k + 1; + wrapstr(k) = str(j); + first8 = 1; + end + end + wrapstr = wrapstr(1:k); +end + +end diff --git a/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m b/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m new file mode 100644 index 00000000..868766dd --- /dev/null +++ b/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m @@ -0,0 +1,697 @@ +function [modelstatus constatus linkstatus] = canlab_glm_subject_levels_run1subject(wd, s) +% child process of canlab_glm_subject_levels +% (see canlab_glm_README.txt for an overview) + +% Programmer's notes: + +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Luka Ruzic +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% + +%% PREP +load(fullfile(wd,sprintf('env_%04d',s))); +diaryfile = fullfile(wd,sprintf('diary_%04d.log',s)); + +batchname = 'spm_specify_and_estimate_model'; + +if ~isempty(OPTS.parallel_dream) %#ok + z = '> '; +else + z = ''; +end + +% initialize statuses +modelstatus = 0; +constatus = 0; +linkstatus = 0; + +%subject-specific setup +[ignore subnum] = fileparts(DSGN.subjects{s}); %#ok +submodeldir = fullfile(DSGN.modeldir, subnum); % make it later when you know it's not getting skipped +batchfile = fullfile(submodeldir, batchname); + +diary(diaryfile) +fprintf('%s\n%s\n%s\n',z,z,z); +fprintf('%s------------------------------\n',z); +fprintf('%s-- SUBJECT LEVEL ANALYSIS --\n',z); +fprintf('%s------------------------------\n',z); +fprintf('%s\n%sOutput Directory:\n%s\t%s\n',z,z,z,submodeldir); +diary off + + +%% MODEL SPECIFICATION AND ESTIMATION JOBS +diary(diaryfile), fprintf('%s\n%s... MODEL SPECIFICATION AND ESTIMATION JOBS\n',z,z), diary off +if OPTS.onlycons + diary(diaryfile), fprintf('%sSKIPPED: turned off in options.\n',z), diary off +else + run_this_model = true; + if exist(submodeldir,'dir') + %if ~numel(filenames(fullfile(submodeldir,'beta_*.img'))) + try status = importdata(fullfile(submodeldir,'.ssglm_model_status')); catch, status = ''; end %#ok + if strcmp(status,'started') + diary(diaryfile), fprintf('%sDELETING existing analysis directory: unfinished.\n',z), diary off + rmdir(submodeldir,'s') + elseif numel(filenames(fullfile(submodeldir,'beta_*.img'))) == 0 + diary(diaryfile), fprintf('%sDELETING existing analysis directory: no betas.\n',z), diary off + rmdir(submodeldir,'s') + else + if OPTS.overwrite + diary(diaryfile), fprintf('%sOVERWRITING: analysis directory exists.\n',z), diary off + rmdir(submodeldir,'s') + else + diary(diaryfile), fprintf('%sSKIPPED: analysis directory exists.\n',z), diary off + run_this_model = false; + end + end + end + + + if run_this_model + if ~exist(DSGN.subjects{s},'dir') + diary(diaryfile), fprintf(sprintf('%sERROR: no such data directory: %s\n',z,DSGN.subjects{s})), diary off + modelstatus = -1; + return + end + mkdir(submodeldir); + eval(sprintf('!echo started > %s',fullfile(submodeldir,'.ssglm_model_status'))) + save(fullfile(submodeldir,'DSGN'),'DSGN'); + + %% GET RUNS + diary(diaryfile), fprintf('%sADDING input functional data\n',z), diary off + clear runs runs3d + try + diary(diaryfile) + [runs runs3d] = find_runs(DSGN,s,z); + diary off + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + modelstatus = -1; + return + end + + diary(diaryfile) + fprintf('Functional data:\n'); + for r = 1:numel(runs), fprintf('%s\t%3d\t%s\n',z,r,runs{r}); end + diary off + + + %% PARSE CONDITIONS, REGRESSORS + diary(diaryfile), fprintf('%sADDING conditions and regressors\n',z), diary off + clear names onsets durations pmods multipleregressors + try + [names onsets durations pmods multipleregressors] = parse_conditions(DSGN,runs,z); + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + modelstatus = -1; + return + end + + %% CONCATENATION (if desired) + if ~isempty(DSGN.concatenation) + diary(diaryfile), fprintf('%sCONCATENATING data according to DSGN.concatenation:\n',z), diary off + try + diary(diaryfile) + [runs3d names onsets durations pmods multipleregressors] = concatdata(DSGN,submodeldir,runs,runs3d,names,onsets,durations,pmods,multipleregressors,z); %#ok + diary off + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + modelstatus = -1; + return + end + end + + %% CONVERT CONDITIONS TO SINGLE TRIALS ANALYSIS (if desired) + if DSGN.singletrialsall || isfield(DSGN,'singletrials') + diary(diaryfile), fprintf('%sCONVERTING conditions to single trials analysis (one condition per trial)',z), diary off + try + diary(diaryfile) + [names onsets durations pmods] = convert_to_single_trials(DSGN,names,onsets,durations,pmods); + diary off + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + modelstatus = -1; + return + end + end + + %% RUN SPECIFICATION AND ESTIMATION BATCH + % convert into flat arrays + conditions_by_run for canlab_spm_fmri_model_job + clear conditions_by_run + try + diary(diaryfile) + [runs runs3d names onsets durations pmods conditions_by_run OPTS] = prep_for_canlab_spm_fmri_model_job(DSGN,OPTS,runs,runs3d,names,onsets,durations,pmods,z); %#ok + diary off + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + modelstatus = -1; + return + end + + modeljobcmd = ['matlabbatch = canlab_spm_fmri_model_job(submodeldir, DSGN.tr, DSGN.hpf, runs3d, conditions_by_run, onsets, durations, names, multipleregressors, ''pmod'', pmods ' OPTS.modeljob ');']; + diary(diaryfile), fprintf('%s\nRUNNING model\n\t%s\n',modeljobcmd,z), diary off + try + eval(modeljobcmd); + save(batchfile, 'matlabbatch'); + spm_jobman('run', matlabbatch); + eval(sprintf('!echo finished > %s',fullfile(submodeldir,'.ssglm_model_status'))) + modelstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + modelstatus = -1; + return + end + end + close all +end + + + +%% CONTRAST JOB +diary(diaryfile), fprintf('%s\n%s... CONTRAST JOB.\n',z,z), diary off +try status = importdata(fullfile(submodeldir,'.ssglm_contrast_status')); catch, status = ''; end %#ok +if ~isfield(DSGN,'contrasts') || ~numel(DSGN.contrasts)~=0 + diary(diaryfile), fprintf('%sSKIPPED: no contrasts specified.\n',z), diary off +else + if modelstatus ~= 1 % otherwise the model ran and must try to run cons + if modelstatus == -1 + diary(diaryfile), fprintf('%sSKIPPED: model failed.\n',z), diary off + return + elseif ~OPTS.onlycons && numel(filenames(fullfile(submodeldir, 'spmT_*.img'))) == numel(DSGN.contrasts) && ~strcmp(status,'started') + diary(diaryfile), fprintf('%sSKIPPED: already previously run, (and onlycons is not set).\n',z), diary off + return + end + end + + conjobcmd = ['canlab_spm_contrast_job_luka(submodeldir, DSGN.contrasts, ''names'', DSGN.contrastnames, ''weights'', DSGN.contrastweights' OPTS.conjob ');']; + diary(diaryfile), disp(conjobcmd), diary off + try + eval(sprintf('!echo started > %s',fullfile(submodeldir,'.ssglm_contrast_status'))) + eval(conjobcmd); + eval(sprintf('!echo finished > %s',fullfile(submodeldir,'.ssglm_contrast_status'))) + constatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + constatus = -1; + return + end +end + + + +%% MAKE NAMED LINKS TO T MAPS +if OPTS.run_renaming && constatus==1 + diary(diaryfile), fprintf('%s\n%s... MAKING named links to spmT maps.\n',z,z), diary off + try + link_to_stat_maps(submodeldir) + linkstatus = 1; + catch exc + if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) + else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end + linkstatus = -1; + end +end + + +end + + +% ------------------------------------------------------------------------- +% SUBFUNCTIONS ---------------------------------------------------------- +% ------------------------------------------------------------------------- + + +%% +function [runs runs3d] = find_runs(DSGN,session,z) + +r=1; +for f = 1:numel(DSGN.funcnames) + % find runs using path from funcnames (wildcards optional) + runstoadd = filenames(fullfile(DSGN.subjects{session}, DSGN.funcnames{f})); + if isempty(runstoadd) + if DSGN.allowmissingfunc + fprintf('%sWARNING: no runs found with: %s\n',z,DSGN.funcnames{f}) + runs{r} = ''; %#ok + runs3d{r} = ''; %#ok + r=r+1; + continue + else + error('%sno runs found with: %s\n\t(to allow, set DSGN.allowmissingfunc)',z,DSGN.funcnames{f}) + end + end + + % expand + for j = 1:numel(runstoadd) + runs{r} = runstoadd{j}; %#ok + runs3d{r} = cellstr(expand_4d_filenames(runs{r})); %#ok + r=r+1; + end +end + +end + + +%% +function [names onsets durations pmods multipleregressors] = parse_conditions(DSGN,runs,z) + +newpmod = struct('name', [], 'param', [], 'poly', []); +emptypmod = newpmod([]); + +for session = 1:numel(runs) + % skip empty runs + if isempty(runs{session}), continue; end + + % if only first session described, use for all sessions + if size(DSGN.conditions,2) == 1 + sess = 1; + else + sess = session; + end + + % specify modeling files directory + [funcdir] = fileparts(runs{session}); + mfdir = fullfile(funcdir, DSGN.modelingfilesdir); + + c = 1; % keeps track of condition number + + for i = 1:numel(DSGN.conditions{sess}) + clear matfiles + + if ~isempty(regexp(DSGN.conditions{sess}{i},'[[]{}*?]','once')) + wc = fullfile(mfdir,DSGN.conditions{sess}{i}); + matfiles = filenames(wc); + if isempty(matfiles) + if DSGN.allowmissingcondfiles + fprintf('%sWARNING: No conditions files found with wildcard: %s\n',z,wc) + continue + else + error('%sNo conditions files found with wildcard: %s\n (See DSGN.allowmissingcondfiles)',z,wc) + end + end + else + matfiles{1} = fullfile(mfdir,DSGN.conditions{sess}{i}); + matfiles{1} = [regexprep(matfiles{1},'.mat$','') '.mat']; + if ~exist(matfiles{1},'file') + if DSGN.allowmissingcondfiles + fprintf('%sWARNING: No such conditions file: %s\n',z,matfiles{1}) + continue + else + error('%sNo such conditions file: %s\n (See DSGN.allowmissingcondfiles)',z,matfiles{1}) + end + end + end + + % get pmods + if isfield(DSGN,'pmods') + try + currpmods = emptypmod; + for j = 1:numel(DSGN.pmods{sess}{i}) + matfile = fullfile(mfdir,DSGN.pmods{sess}{i}{j}); + matfile = [regexprep(matfile,'.mat$','') '.mat']; + if ~exist(matfile,'file'), error('%sNo such pmods file: %s',z,matfile); end + pmodinfo = load(matfile); + + for k = 1:numel(pmodinfo.name) + l = size(currpmods,2) + 1; + % initialize + currpmods(l) = newpmod; + currpmods(l).name = pmodinfo.name{k}; + currpmods(l).param = pmodinfo.param{k}; + currpmods(l).poly = pmodinfo.poly{k}; + end + end + catch %#ok + currpmods = emptypmod; + end + else + currpmods = emptypmod; + end + + for m = 1:numel(matfiles) + condinfo = load(matfiles{m}); + + % loop through all conditions in mat + for j = 1:numel(condinfo.name) + if ~numel(condinfo.onset{j}) + names{session}{c} = condinfo.name{j}; %#ok + onsets{session}{c} = []; %#ok + durations{session}{c} = []; %#ok + pmods{session}{c} = emptypmod; %#ok + else + % load name + names{session}{c} = condinfo.name{j}; %#ok + + % load onsets + if size(condinfo.onset{j},1) == 1 && size(condinfo.onset{j},2) > 1 + condinfo.onset{j} = condinfo.onset{j}'; + elseif size(condinfo.onset{j},1) > 1 && size(condinfo.onset{j},2) > 1 + error('onsets field in %s stored as matrix (must be scalar or vector)',matfiles{m}); + end + onsets{session}{c} = condinfo.onset{j}; %#ok + + % load durations + if size(condinfo.duration{j},1) == 1 && size(condinfo.duration{j},2) > 1 + condinfo.duration{j} = condinfo.duration{j}'; + elseif size(condinfo.duration{j},1) > 1 && size(condinfo.duration{j},2) > 1 + error('durations field in %s stored as matrix (must be scalar or vector)',matfiles{m}); + end + durations{session}{c} = condinfo.duration{j}; %#ok + + % add currently indicated pmods + pmods{session}{c} = currpmods; %#ok + + % load pmods from condition file + if isfield(condinfo,'pmod') && ~isempty(condinfo.pmod) + for k = 1:numel(condinfo.pmod.name) + l = size(pmods{session}{c},2) + 1; + pmods{session}{c}(l) = newpmod; %#ok + pmods{session}{c}(l).name = condinfo.pmod.name{k}; %#ok + pmods{session}{c}(l).param = condinfo.pmod.param{k}; %#ok + pmods{session}{c}(l).poly = condinfo.pmod.poly{k}; %#ok + end + end + end + c=c+1; + end + end + + end + + % retrieve multiple regressors file + if ~isempty(DSGN.multireg) + multiregfile = fullfile(mfdir, DSGN.multireg); +% if ~exist(multiregfile,'file'), error('> No such multiple regressors file : %s',multiregfile); end + multipleregressors{session} = multiregfile; %#ok + else + multipleregressors{session} = {}; %#ok + end +end + +end + + +%% +function [runs3d names onsets durations pmods multipleregressors] = concatdata(DSGN,submodeldir,oldruns,oldruns3d,oldnames,oldonsets,olddurations,oldpmods,oldmultipleregressors,z) + + +emptyruns = find(cellfun('isempty',oldruns)); +if DSGN.allowmissingfunc && ~isempty(emptyruns) + i=1; + for c = 1:numel(DSGN.concatenation) + concat{i} = []; %#ok + for r = 1:numel(DSGN.concatenation{c}) + if ~any(DSGN.concatenation{c}(r) == emptyruns) + concat{i} = [concat{i} DSGN.concatenation{c}(r)]; %#ok + end + end + if (numel(concat{i}) > 1), i=i+1; end + end +else + concat = DSGN.concatenation; +end + +for i = 1:numel(concat) + fprintf('%s\tsession %d is run(s):',z,i); + for j = 1:numel(concat{i}) + fprintf(' %3d', concat{i}(j)); + end + fprintf('\n'); +end + +% initialize +runs3d = {}; +names = {}; +onsets = {}; +durations = {}; +pmods = {}; +multipleregressors = ''; + +for sess = 1:numel(concat) + oldsess1 = concat{sess}(1); + + % concatenate functional data + runs3d{sess} = []; %#ok + for r = 1:numel(concat{sess}) + oldsess = concat{sess}(r); + runs3d{sess} = [runs3d{sess}; cellstr(expand_4d_filenames(oldruns{oldsess}))]; %#ok + end + + starttime = 0; + for r = 1:numel(concat{sess}) + oldsess = concat{sess}(r); + for cond = 1:numel(oldonsets{oldsess}) + % names + if r==1 + names{sess}{cond} = oldnames{oldsess}{cond}; %#ok + elseif ~strcmp(names{sess}{cond},oldnames{oldsess}{cond}) + error(['Inconsistent names for condition ' num2str(cond) ' across sessions (e.g., ' names{sess}{cond} ', ' oldnames{oldsess}{cond} ')']) + end + + % onsets + if r==1, onsets{sess}{cond} = []; end %#ok + onsets{sess}{cond} = [onsets{sess}{cond}; oldonsets{oldsess}{cond} + starttime]; %#ok + + % durations + if r==1, durations{sess}{cond} = []; end %#ok + if numel(olddurations{oldsess}{cond}) == 1 + % extend single duration across all onsets (in case single duration is different across runs being concatenated) + olddurations{oldsess}{cond} = repmat(olddurations{oldsess}{cond},size(oldonsets{oldsess}{cond},1),1); + end + durations{sess}{cond} = [durations{sess}{cond}; olddurations{oldsess}{cond}]; %#ok + + % pmods + if ~numel(oldpmods{oldsess}{cond}) + pmods{sess}{cond} = oldpmods{oldsess}{cond}; %#ok + else + for p = 1:size(oldpmods{oldsess}{cond},2) + if r==1 + pmods{sess}{cond}(p).poly = oldpmods{oldsess1}{cond}(p).poly; %#ok + pmods{sess}{cond}(p).name = oldpmods{oldsess1}{cond}(p).name; %#ok + pmods{sess}{cond}(p).param = []; %#ok + end + pmods{sess}{cond}(p).param = [pmods{sess}{cond}(p).param; oldpmods{oldsess}{cond}(p).param]; %#ok + end + end + end + starttime = starttime + (DSGN.tr * numel(oldruns3d{oldsess})); + end + + % concatenate regressors + multipleregressors{sess} = fullfile(submodeldir,sprintf('multireg_%d.mat',sess)); + newR = []; + cri = {}; + for r = 1:numel(concat{sess}) + oldsess = concat{sess}(r); + if ~isempty(oldmultipleregressors{oldsess}) + load(oldmultipleregressors{oldsess}); + oldR = R; + else + oldR=[]; + end + if ~isfield(DSGN,'customrunintercepts') + % add intercept (ignore first one) + if r>1 + oldR(:,end+1) = 1; %#ok + end + else + % initialize + if isempty(oldR) + tmpn = nifti(oldruns{oldsess}); + cri{r} = zeros(size(tmpn.dat,4),numel(DSGN.customrunintercepts)); %#ok + else + cri{r} = zeros(size(oldR,1),numel(DSGN.customrunintercepts)); %#ok + end + + for i = 1:numel(DSGN.customrunintercepts) + if any(oldsess == DSGN.customrunintercepts{i}) + cri{r}(:,i) = 1; %#ok + end + end + end + % add linear trend + oldR(:,end+1) = scale([1:size(oldR,1)]'); %#ok + + % append to growing block diagonal nuisance matrix + newR = blkdiag(newR,oldR); + end + R = [newR vertcat(cri{:})]; + save(multipleregressors{sess}, 'R'); +end + +% add rest of stuff +catruns = cell2mat(concat); +for r = 1:numel(oldruns) + if ~any(catruns == r) && ~isempty(oldruns{r}) + fprintf ('%s\tsession %d is run(s): %3d\n',z,numel(runs3d)+1,r) + runs3d{end+1} = oldruns3d{r}; %#ok + names{end+1} = oldnames{r}; %#ok + onsets{end+1} = oldonsets{r}; %#ok + durations{end+1} = olddurations{r}; %#ok + pmods{end+1} = oldpmods{r}; %#ok + multipleregressors{end+1} = oldmultipleregressors{r}; %#ok + end +end + +end + + +%% +function [names onsets durations pmods] = convert_to_single_trials(DSGN,oldnames,oldonsets,olddurations,oldpmods) + +newpmod = struct('name', [], 'param', [], 'poly', []); +emptypmod = newpmod([]); + +if isfield(DSGN,'singletrials') && numel(DSGN.singletrials) == 1 + for s = 2:numel(oldnames) + DSGN.singletrials{s} = DSGN.singletrials{1}; + end +end + +for s = 1:numel(oldonsets) + o = 1; + for c = 1:numel(oldonsets{s}) + if numel(olddurations{s}{c})==1 + olddurations{s}{c} = repmat(olddurations{s}{c},numel(oldonsets{s}{c}),1); + end + % note: look for better way to deal with sparse cell array! + try + thiscond = logical(DSGN.singletrials{s}{c}); + if isempty(DSGN.singletrials{s}{c}), DSGN.singletrials{s}{c} = false; end + catch %#ok + thiscond = false; + end + + if DSGN.singletrialsall || thiscond + if sum(size(oldpmods{s}{c})==[0 0])~=2 + error('Sorry, single trials option is not currently compatible with pmods') + end + for t = 1:numel(oldonsets{s}{c}) + names{s}{o} = sprintf('%s_trial%04d',oldnames{s}{c},t); %#ok + onsets{s}{o} = oldonsets{s}{c}(t); %#ok + durations{s}{o} = olddurations{s}{c}(t); %#ok + pmods{s}{o} = emptypmod; %#ok + o=o+1; + end + else + names{s}{o} = oldnames{s}{c}; %#ok + onsets{s}{o} = oldonsets{s}{c}; %#ok + durations{s}{o} = olddurations{s}{c}; %#ok + pmods{s}{o} = oldpmods{s}{c}; %#ok + o=o+1; + end + end +end + +end + + +%% +function [nonemptyruns nonemptyruns3d flatnames flatonsets flatdurations flatpmods conditions_by_run OPTS] = prep_for_canlab_spm_fmri_model_job(DSGN,OPTS,runs,runs3d,names,onsets,durations,pmods,z) + +nonemptyruns = {}; +nonemptyruns3d = {}; +for i=1:numel(runs3d) + if ~isempty(runs3d{i}) + nonemptyruns{end+1} = runs{i}; %#ok + nonemptyruns3d{end+1} = runs3d{i}; %#ok + end +end + +flatnames = {}; +flatonsets = {}; +flatdurations = {}; +flatpmods = {}; +conditions_by_run = []; +newsess = 0; +for session = find(~cellfun('isempty',runs3d)) %1:numel(names) + newsess = newsess+1; + i=0; + for cond = 1:numel(names{session}) + if isempty(onsets{session}{cond}) + if DSGN.allowemptycond + fprintf('%sWARNING: no onsets in session %d, condition %d: %s\n',z,session,cond,names{session}{cond}) + continue + else + error('no onsets in session %d, condition %d: %s\n\t(to allow, set DSGN.allowemptycond)',session,cond,names{session}{cond}) + end + end + flatnames{end+1} = [names{session}{cond} ' ']; %#ok % space added to separate out tmods, pmods, basis functions, etc + flatonsets{end+1} = onsets{session}{cond}; %#ok + flatdurations{end+1} = durations{session}{cond}; %#ok + flatpmods{end+1} = pmods{session}{cond}; %#ok + i=i+1; + end + conditions_by_run(newsess) = i; %#ok +end + +switch DSGN.convolution.type + case 'hrf' + OPTS.modeljob = [OPTS.modeljob ',' '''hrf''' ',' num2str(DSGN.convolution.time) ',' num2str(DSGN.convolution.dispersion)]; + case 'fir' + OPTS.modeljob = [OPTS.modeljob ',' '''fir''' ',' num2str(DSGN.convolution.windowlength) ',' num2str(DSGN.convolution.order)]; + if ~isfield(DSGN.convolution,'keepdurations') || ~DSGN.convolution.keepdurations + % zero-out durations + for c = 1:numel(flatdurations) + flatdurations{c} = 0; %#ok + end + end + otherwise + error('Unrecognized convolution type: %s',DSGN.convolution.type) +end + +if DSGN.ar1 + OPTS.modeljob = [OPTS.modeljob ',''AR(1)''']; +end + +end + + +%% +function link_to_stat_maps(targdir) + +startingdir = pwd; + +cd(targdir) +load('SPM.mat') +% load('contrastnames.mat') + +renamedir = fullfile(pwd, 'named_statmaps'); +if exist(renamedir,'dir'), rmdir(renamedir,'s'); end +mkdir(renamedir); + +for i=1:numel(SPM.xCon) + mapname = SPM.xCon(i).name; + % clean up the name for filename friendliness + mapname = regexprep(mapname,'[()]',''); + mapname = regexprep(mapname,'[^0-9A-Za-z-_]','_'); + for stat = {'spmT' 'con'} + for ext = {'img' 'hdr'} + imgname = fullfile(pwd, sprintf('%s_%04d.%s',stat{1},i,ext{1})); + linkname = fullfile(renamedir, [stat{1} '_' mapname '.' ext{1}]); + eval(['!ln -v -s ' imgname ' ' linkname]); + end + end +end + +cd(startingdir) + +end diff --git a/HRF_Est_Toolbox2/Anneal_Logit.m b/HRF_Est_Toolbox2/Anneal_Logit.m new file mode 100755 index 00000000..2e8c8168 --- /dev/null +++ b/HRF_Est_Toolbox2/Anneal_Logit.m @@ -0,0 +1,127 @@ +function [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(theta0,t,tc,Run) +% +% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) +% +% Estimate inverse logit (IL) HRF model using Simulated Annealing +% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates +% +% INPUT: theta0, t, tc, Run +% Run = stick function +% tc = time course +% t = vector of time points +% theta0 = initial value for the parameter vector +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Initial values + +iter = 15000; % Number of iterations +theta = theta0; % Set initial value for the parameter vector +h0 = cost(theta0,t,tc,Run); % Calculate cost of initial estimate +LB = [0.05, 1, 0, 0.05, 5, 0, 10]; % Lower bounds for parameters +UB = [10, 15, 5, 10, 15, 5, 30]; % Upper bounds for parameters + +% +% These values may need tweaking depending on the individual situation. +% + +r1= 0.001; % A parameters +r1b= 0.001; % A parameters +r2 = 0.05; % T parameters +r3 = 0.001; % delta parameters + +t1 = [1 4]; +t1b = [6]; +t2 = [2 5 7]; +t3 = [3]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +u = zeros(1,7); + +HH = zeros(1+iter,7); % Keep track of theta_i +HH(1,:) = theta0; +P = zeros(1+iter,1); +C = zeros(1+iter,1); % Keep track of the cost function +C(1) = h0; + +cnt = 0; +for i=1:iter, + + T = 100/log(1+i); %Temperature function (may require tweaking) + th = zeros(1,7); + ind = 0; + + % Choose a new candidate solution theta_{i+1}, based on a random perturbation of the current solution of theta_{i}. + % Check new parameters are within accepted bounds + while ( (sum((LB-th)>0) + sum((th-UB)>0)) > 0), + + % Perturb solution + + u(t1) = normrnd(0,r1,1,2); + u(t1b) = normrnd(0,r1b,1,1); + u(t2) = normrnd(0,r2,1,3); + u(t3) = normrnd(0,r3,1,1); + + % Update solution + th = theta + u; + ind = ind + 1; + + if(ind > 500), + warning('stuck!'); + return; + end; + end; + + h = cost(th,t,tc,Run); + C(i+1) = h; + delta = h - h0; + + % Determine whether to update the parameter vector. + if (unifrnd(0,1) < min(exp(-delta/T),1)), + theta = th; + h0=h; + cnt = cnt+1; + end; + + HH(i+1,:) = theta; + P(i+1) = min(exp(-delta/T),1); + +end; + +%cnt/iter + +[a,b] = min(C); +theta = HH(b,:); +%h + + +% Additional outputs +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Get HRF for final model +if nargout > 4 + hrf = Get_Logit(theta(1:7),t); % Calculate HRF estimate (fit, given theta) +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Convolve HRF and stick function +if nargout > 5 + len = length(Run); + fit = conv(Run, hrf); + fit = fit(1:len); + e = tc - fit; +end + +if nargout > 7 +% [param] = get_parameters_logit(hrf,t,theta); + param = get_parameters2(hrf,t); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +return diff --git a/HRF_Est_Toolbox2/Det_Logit.m b/HRF_Est_Toolbox2/Det_Logit.m new file mode 100755 index 00000000..18653eb0 --- /dev/null +++ b/HRF_Est_Toolbox2/Det_Logit.m @@ -0,0 +1,116 @@ +function [VM, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run) +% +% [VM, h, fit, e, param] = Det_Logit_allstim(V0,t,tc,Run) +% +% Estimate inverse logit (IL) HRF model +% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates +% +% INPUT: V0, t, tc, Run +% Run = stick function +% tc = time course +% t = vector of time points +% V0 = initial value for the parameter vector +% +% By Martin Lindquist, Christian Waugh and Tor Wager +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/26/10 (ML) + + +numstim = length(Run); +len = length(Run{1}); + +% LB = [0.05, 1, 0, 0.05, 4, 0, 10]; % Lower bounds for parameters +% UB = [10, 15, 10, 10, 15, 5, 50]; % Upper bounds for parameters +% LB = repmat(LB, 1, numstim); +% UB = repmat(UB, 1, numstim); + +% Remove intercept + +%b0 = pinv(ones(length(tc),1))*tc; +%tc = tc - b0; + +% Find optimal values + +options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX',1e-6,'TolFun',1e-6,'Display','off'); + +%VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); +VM = fminsearch(@msq_logit,V0,options,Run,t,tc); + +% Use optimal values to fit hemodynamic response functions +hrf =zeros(length(t),numstim); +fitt = zeros(len,numstim); +param = zeros(3,numstim); + +for g = 1:numstim + hrf(:,g) = il_hdmf_tw2(t,VM(((g-1)*7+1):(g*7))); % Calculate HRF estimate (fit, given theta) + param(:,g) = get_parameters2(hrf(:,g),t); + fits(:,g) = conv(Run{g}, hrf(:,g)); + fitt(:,g) = fits(1:len,g); +end + +fit = sum(fitt,2); +e = tc-fit; +%fit = fit + b0; + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% SUBFUNCTIONS +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function m=msq_logit(V,Run, t, tc) + +numstim = length(Run); +len = length(Run{1}); +h = zeros(length(t),numstim); +yhatt =zeros(len,numstim); + +for k = 1:numstim + h(:,k) = il_hdmf_tw2(t,V(((k-1)*7+1):(k*7))); % Get IL model corresponding to parameters V + yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function + yhatt(:,k) = yhat(1:len,k); +end + +yhat2 = sum(yhatt,2); %Sum models together to get overall estimate + +m = sum((tc-yhat2).^2); % Calculate cost function + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [h,base] = il_hdmf_tw2(t,V) +% inverse logit -- creates fitted curve from parameter estimates +% +% t = vector of time points +% V = parameters + +% 3 logistic functions to be summed together +base = zeros(length(t),3); +A1 = V(1); +T1 = V(2); +d1 = V(3); +A2 = V(4); +T2 = V(5); +A3 = V(6); +T3 = V(7); +d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); +d3 = abs(d2)-abs(d1); + +base(:,1)= d1*ilogit(A1*(t-T1))'; +base(:,2)= d2*ilogit(A2*(t-T2))'; +base(:,3)= d3*ilogit(A3*(t-T3))'; +h = sum(base,2)'; + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [L] = ilogit(t) +L = exp(t)./(1+exp(t)); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/HRF_Est_Toolbox2/Example.m b/HRF_Est_Toolbox2/Example.m new file mode 100644 index 00000000..0384bd4c --- /dev/null +++ b/HRF_Est_Toolbox2/Example.m @@ -0,0 +1,215 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Example code for estimating the HRF using the Inverse-Logit Model, a +% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. +% Also the code illustrates our code for detecting model misspecification. +% +% By Martin Lindquist and Tor Wager +% Created 10/02/09 +% Last edited 05/20/13 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Load time course +% + +mypath = which('ilogit'); +if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end +[mydir] = fileparts(mypath) + +load(fullfile(mydir,'timecourse')) + +tc = (tc- mean(tc))/std(tc); +len = length(tc); + + +%% Or: create your own +[xBF] = spm_get_bf(struct('dt', .5, 'name', 'hrf (with time and dispersion derivatives)', 'length', 32)); +clear Xtrue +for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 1 1 1 1 ]'); + Xtrue(:, i) = xx(1:66); +end +for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); + Xtrue(:, i) = xx(1:66); +end +hrf = Xtrue * [1 .3 .2]'; +xsecs = 0:.5:32; + +hrf = [ 0; 0; hrf]; +hrf = hrf(1:length(xsecs)); +hrf = hrf ./ max(hrf); +figure; plot(xsecs, hrf, 'k') +%hrf = hrf(1:4:end); % downsample to TR, if TR is > 0.5 + + +R = randperm(640); R = sort(R(1:36)); +Run = zeros(640,1); +for i=1:length(R), Run(R(i)) = 1; end; +true_sig = conv(Run, hrf); +true_sig = true_sig(1:640); + +% tc_noise = noise_arp(640, [.7 .2]); +% tc = true_sig + 0.1 * tc_noise; +tc = true_sig; +%figure; plot(tc); + + +Runc{1} = Run; + +%% + +create_figure; subplot(3,1,1); han = plot(tc); +title('Sample time course'); drawnow + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Settings +% + +TR = 0.5; +%T = round(30/TR); +T = 30; +t = 1:TR:T; % samples at which to get Logit HRF Estimate +FWHM = 4; % FWHM for residual scan +pval = 0.01; +df = 600; +alpha = 0.001; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Create stick function (sample event onsets) +% Variable R contains onset times +% Variable Run contains stick (a.k.a. delta or indicator) function + +% R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; +% Run = zeros(640,1); +% for i=1:length(R), Run(R(i)) = 1; end; +% + +try + hold on; + hh = plot_onsets(R,'k',-3,1, 1); + drawnow +catch + disp('Couldn''t find function to add onset sticks to plot. Skipping.') +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using IL-function + +% Choose mode (deterministic/stochastic) + +mode = 0; % 0 - deterministic aproach + % 1 - simulated annealing approach + % Please note that when using simulated annealing approach you + % may need to perform some tuning before use. + +[h1, fit1, e1, param] = Fit_Logit2(tc,TR,Runc,T,mode); +[pv sres sres_ns1] = ResidScan(e1, FWHM); +[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Runc, alpha); + +hold on; han(2) = plot(fit1,'r'); + +disp('Summary: IL_function'); + +disp('Amplitude:'); disp(param(1)); +disp('Time-to-peak:'); disp(param(2)*TR); +disp('Width:'); disp(param(3)*TR); + +disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); +disp('Mis-modeling:'); disp(pv); +disp('Power Loss:'); disp(PowLoss1); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using FIR-model + +% Choose mode (FIR/sFIR) + +mode = 1; % 0 - FIR + % 1 - smooth FIR + +[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Runc,T,mode); +[pv sres sres_ns2] = ResidScan(e2, FWHM); +[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Runc, alpha); + +hold on; han(3) = plot(fit2,'g'); + +disp('Summary: FIR'); + +disp('Amplitude'); disp(param(1)); +disp('Time-to-peak'); disp(param(2)*TR); +disp('Width'); disp(param(3)*TR); + +disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); +disp('Mis-modeling'); disp(pv); +disp('Power Loss:'); disp(PowLoss2); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using Canonical HRF + 2 derivatives + +p=1; + +[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Runc,30,p); +[pv sres sres_ns3] = ResidScan(e3, FWHM); +[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Runc, alpha); + +hold on; han(4) = plot(fit3,'m'); + +legend(han,{'Data' 'IL' 'sFIR' 'DD'}) + + +disp('Summary: Canonical + 2 derivatives'); + +disp('Amplitude'); disp(param(1)); +disp('Time-to-peak'); disp(param(2)*TR); +disp('Width'); disp(param(3)*TR); + +disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); +disp('Mis-modeling'); disp(pv); +disp('Power Loss:'); disp(PowLoss3); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%figure; +%% + +subplot(3,2,5); hold on; +plot(xsecs, hrf, 'k') +xsecs1 = xsecs(1:length(h1)); +han2 = plot(xsecs1, h1,'r'); +xsecs2 = xsecs(1:length(h2)); +han2(2) = plot(xsecs2, h2,'g'); +xsecs3 = xsecs(1:length(h3)); +han2(3) = plot(xsecs3, h3,'m'); +legend(han2,{'IL' 'sFIR' 'DD'}) +title('Estimated HRF'); + + +subplot(3,1,2); hold on; +hh = plot_onsets(R,'k',-3,1); +drawnow + +han3 = plot(sres_ns1,'r'); +hold on; han3(2) = plot(sres_ns2,'g'); +hold on; han3(3) = plot(sres_ns3,'m'); +hold on; plot((1:len),zeros(len,1),'--k'); +legend(han3,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (time course)'); + + +subplot(3,2,6); hold on; + +[s1] = Fit_sFIR(sres_ns1,TR,Runc,T,0); +[s2] = Fit_sFIR(sres_ns2,TR,Runc,T,0); +[s3] = Fit_sFIR(sres_ns3,TR,Runc,T,0); + +han4 = plot(s1(1:T),'r'); +hold on; han4(2) = plot(s2(1:T),'g'); +hold on; han4(3) = plot(s3(1:T),'m'); +hold on; plot((1:T),zeros(T,1),'--k'); +legend(han4,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (HRF)'); diff --git a/HRF_Est_Toolbox2/Fit_Canonical_HRF.m b/HRF_Est_Toolbox2/Fit_Canonical_HRF.m new file mode 100755 index 00000000..abec42bb --- /dev/null +++ b/HRF_Est_Toolbox2/Fit_Canonical_HRF.m @@ -0,0 +1,121 @@ +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,Runs,T,p) +% +% Fits GLM using canonical hrf (with option of using time and dispersion derivatives)'; +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% p - Model type +% +% Options: p=1 - only canonical HRF +% p=2 - canonical + temporal derivative +% p=3 - canonical + time and dispersion derivative +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% info - struct containing design matrices, beta values etc +% +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/17/13 (ML) + +%tc = tc'; +d = length(Run); +len = length(Run{1}); +t=1:TR:T; + +X = zeros(len,p*d); +param = zeros(3,d); + +[h, dh, dh2] = CanonicalBasisSet(TR); + +for i=1:d, + v = conv(Run{i},h); + X(:,(i-1)*p+1) = v(1:len); + + if (p>1) + v = conv(Run{i},dh); + X(:,(i-1)*p+2) = v(1:len); + end + + if (p>2) + v = conv(Run{i},dh2); + X(:,(i-1)*p+3) = v(1:len); + end +end + +X = [(zeros(len,1)+1) X]; + +b = pinv(X)*tc; +e = tc-X*b; +fit = X*b; + +b = reshape(b(2:end),p,d)'; +bc = zeros(d,1); + +for i=1:d, + if (p == 1) + bc(i) = b(i,1); + H = h; + elseif (p==2) + bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2); + H = [h dh]; + elseif (p>2) + bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2 + (b(i,3))^2); + H = [h dh dh2]; + end + +end + +hrf = H*b'; + +for i=1:d, + param(:,i) = get_parameters2(hrf(:,i),1:length(t)); +end; + +info ={}; +info.b = b; +info.bc = bc; +info.X = X; +info.H =H; + +end + +% END MAIN FUNCTION +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Subfunctions +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [h, dh, dh2] = CanonicalBasisSet(TR) + +len = round(30/TR); +xBF.dt = TR; +xBF.length= len; +xBF.name = 'hrf (with time and dispersion derivatives)'; +xBF = spm_get_bf(xBF); + +v1 = xBF.bf(1:len,1); +v2 = xBF.bf(1:len,2); +v3 = xBF.bf(1:len,3); + +h = v1; +dh = v2 - (v2'*v1/norm(v1)^2).*v1; +dh2 = v3 - (v3'*v1/norm(v1)^2).*v1 - (v3'*dh/norm(dh)^2).*dh; + +h = h./max(h); +dh = dh./max(dh); +dh2 = dh2./max(dh2); + +end \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Fit_Logit2.m b/HRF_Est_Toolbox2/Fit_Logit2.m new file mode 100755 index 00000000..47ca3012 --- /dev/null +++ b/HRF_Est_Toolbox2/Fit_Logit2.m @@ -0,0 +1,59 @@ +function [hrf, fit, e, param] = Fit_Logit2(tc, TR, Run, T, mode) +% function [hrf, fit, e, param] = Fit_Logit(tc,Run,t,mode) +% +% Fits FIR and smooth FIR model +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% mode - deterministic or stochastic +% options: +% 0 - deterministic aproach +% 1 - simulated annealing approach +% Please note that when using simulated annealing approach you +% may need to perform some tuning before use. +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/31/13 (ML) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit the Logit model + +numstim = length(Run); +len = length(Run{1}); + +t=1:TR:T; + +V0 = [ 1 6 1 0.5 10 1 15]; % initial values for logit fit +V0 = repmat(V0,1,numstim); + +if (mode == 1 && numstim>1), + disp('Multi-stimulus annealing function currently not implemented. Switching to "deterministic mode"') + mode = 0; +end; + +% Estimate theta (logit parameters) + +if (mode == 1) + disp('Stochastic Mode'); + + Runs = Run{1}; + [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(V0,t,tc,Runs); + +elseif (mode == 0) +% disp('Deterministic Mode'); + [theta, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run); + +end + +end diff --git a/HRF_Est_Toolbox2/Fit_sFIR.m b/HRF_Est_Toolbox2/Fit_sFIR.m new file mode 100755 index 00000000..796bb425 --- /dev/null +++ b/HRF_Est_Toolbox2/Fit_sFIR.m @@ -0,0 +1,75 @@ +function [hrf, fit, e, param] = Fit_sFIR(tc, TR, Run, T, mode) +% function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) +% +% Fits FIR and smooth FIR model +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% mode - FIR or smooth FIR +% options: +% 0 - standard FIR +% 1 - smooth FIR +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/17/13 (ML) + +numstim = length(Run); +len = length(Run{1}); +t=1:TR:T; +tlen = length(t); + +Runs = zeros(len,numstim); +for i=1:numstim, + Runs(:,i) = Run{i}; +end; + +[DX] = tor_make_deconv_mtx3(Runs,tlen,1); + +if mode == 1 + + C=(1:tlen)'*(ones(1,tlen)); + h = sqrt(1/(7/TR)); % 7 seconds smoothing - ref. Goutte + + v = 0.1; + sig = 1; + + R = v*exp(-h/2*(C-C').^2); + RI = inv(R); + MRI = zeros(numstim*tlen+1); + for i=1:numstim, + MRI(((i-1)*tlen+1):(i*tlen),((i-1)*tlen+1):(i*tlen)) = RI; + end; + + b = inv(DX'*DX+sig^2*MRI)*DX'*tc; + fit = DX*b; + e = tc - DX*b; + +elseif mode == 0 + + b = pinv(DX)*tc; + fit = DX*b; + e = tc - DX*b; + +end + + +hrf =zeros(tlen,numstim); +param = zeros(3,numstim); + +for i=1:numstim, + hrf(:,i) = b(((i-1)*tlen+1):(i*tlen))'; + param(:,i) = get_parameters2(hrf(:,i),(1:tlen)); +end; + +end \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Get_Logit.m b/HRF_Est_Toolbox2/Get_Logit.m new file mode 100755 index 00000000..6a4c3dfe --- /dev/null +++ b/HRF_Est_Toolbox2/Get_Logit.m @@ -0,0 +1 @@ +function [h, base] = Get_Logit(V,t) % % [h] = get_logit(V,t) % % Calculate inverse logit (IL) HRF model % Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates % % INPUT: V, t % t = vector of time points % V = parameters % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % A1 = V(1); T1 = V(2); d1 = V(3); A2 = V(4); T2 = V(5); A3 = V(6); T3 = V(7); d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); d3 = abs(d2)-abs(d1); h = d1*ilogit(A1*(t-T1))+d2*ilogit(A2*(t-T2))+d3*ilogit(A3*(t-T3)); % Superimpose 3 IL functions h = h'; base =zeros(3,length(t)); base(1,:) = ilogit(A1*(t-T1)); base(2,:) = ilogit(A2*(t-T2)); base(3,:) = ilogit(A3*(t-T3)); return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/HMHRFest.m b/HRF_Est_Toolbox2/HMHRFest.m new file mode 100644 index 00000000..b52f809a --- /dev/null +++ b/HRF_Est_Toolbox2/HMHRFest.m @@ -0,0 +1,602 @@ +function [Res] = HMHRFest(y, Runs, TR, nbasis, norder) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% HRF estimation algorithm +% +% By David Degras and Martin Lindquist +% Created: 08/22/12 +% Last edited: 03/27/14 +% +% INPUTS: +% +% y: Data matrix (#time points) by (#subjects) by (#voxels) +% Runs: Stick functions for each subject (#time points) by (#conditions) by (#subjects) +% TR: Time resolution +% nbasis: Number of b-spline basis +% norder: Order of b-spline basis + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Set initial values + +% TR = 1; % TR of experiment +% nbasis = 20; % Number of b-spline basis +% norder = 6; % Order of b-spline basis + +[len, sub, voxpar] = size(y); +[~, L, ~] = size(Runs); + +I = 1; % Number of groups +N1 = sub; % N1: Number of subjects in Group 1 +Tlen = 30/TR; % Length of HRF +q = 2; % Number of nuisance parameters +p = 1; % AR order +lambda = 50; % Smoothing parameter + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Info about the parcellation + +voxels = sum(voxpar); % Total number of voxels +parcels = length(voxpar); % Number of parcels +parind = zeros(voxpar(1),1)+1; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Create a struct containing all initial values + +Init = {}; +Init.I = I; +Init.sub = sub; +Init.len = len; +Init.N1 = N1; +Init.L = L; +Init.TR = TR; +Init.Tlen = Tlen; +Init.nbasis = nbasis; +Init.norder = norder; +Init.q = q; +Init.p = p; +Init.lambda = lambda; +Init.voxels = voxels; +Init.parcels = parcels; +Init.voxpar = voxpar; +Init.parind = parind; + + +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Generate basis sets for potential HRF shapes. + +hphi = zeros(3,nbasis); % Basis sets for 3 potenital HRF shapes +hf = CanonicalBasisSet(TR); + +% Create bspline basis set +basis = create_bspline_basis([0,Tlen], nbasis+8, norder); +B = eval_basis((1:Tlen),basis); +B = B(:,6:end-3); + + +Init.B = B; +SM = zeros(Tlen,3); +for i=1:3, + + Run = zeros(Tlen,1); + Run(1:(2*(i-1)+1)) = 1; + s = conv(hf,Run); + s = s(1:Tlen); + s = s./max(s); + + SM(:,i) = s; + tmp = (inv(B'*B)*B'*s)'; + hphi(i,:) = tmp./sum(tmp.^2); + +end + +hphi = orth(hphi')'; % Orthogonalize + +Init.hphi = hphi; + + +G0 = (inv(B'*B)*B'*hf); % Need these values for Hotellings test. + + + +%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Estimation of HRF and nuisance parameters + +Beta = zeros(L, voxels); % Voxel-wise beta +G = zeros(nbasis, voxels); % Voxel-wise gamma +K = zeros(N1*nbasis*L, voxels); % Voxel-wise ksi +% E = zeros(len, voxels); % Voxel-wise epsilon +Ar = zeros(p,voxels); % Voxel-wise AR coeficients +S2e = zeros(1,voxels); % Voxel-wise withing-subject variance +CC = zeros(1,voxels); % Voxel-wise Lagrange Multiplier + +% Create initial covariance matrix (indentity matrix for all subjects) + +iV = zeros(len,len,sub); +for j=1:sub + iV(:,:,j) = eye(len); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Step 1: Pilot Estimation of the brain response +% First-pass estimation of beta and gamma for each voxel + +for v = 1:voxels + + [beta, gamma, C, AE, sig2e, ksiv, ~, XB, Phi, P] = EstimationVoxel(y(:,:,v), iV, Runs, Init); + + % Store results + Beta(:,v) = beta; + G(:,v) = gamma; + Ar(:,v) = AE'; + S2e(v) = sig2e; + K(:,v) = ksiv; + CC(:,v) = C; +% E(:,v) = epsv; + +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Step 2: Estimation of the temporal dependence + +A = zeros(len,len,parcels); +sig2_eps = zeros(1,parcels); +phi = zeros(p,parcels); + +% Estimate parcel-specific matrix Am (AR components) + +for m = 1:parcels + + ind = (parind == m); + % vind = find(ind); + phi(:,m) = mean(Ar(:,ind)); + sig2_eps(m) = mean(S2e(ind)); + + Atmp = eye(len); + for t=1:(len-1) + Atmp = Atmp + (phi.^t).*(diag(ones(len-t,1),t) + diag(ones(len-t,1),-t)); + end + A(:,:,m) = (1/(1-phi^2))*Atmp; + +end + + +% Estimate rho + +rho_l = zeros(nbasis,L,voxels); +sig2_ksi = zeros(L,voxels); +Tksi = zeros(L*nbasis,L*nbasis); + +for v=1:voxels + + Ksi2 = zeros(1,L); + KK = zeros(nbasis,nbasis,L); + Tksi_l = zeros(nbasis,nbasis,L); + + for j=1:N1, + + ksiv = K(:,v); + ksi = ksiv((j-1)*nbasis*L+1:(j*nbasis*L)); + ksi = reshape(ksi,nbasis,L); + + for l=1:L + Ksi2(l) = Ksi2(l) + ksi(:,l)'*ksi(:,l); + KK(:,:,l) = KK(:,:,l) + ksi(:,l)*ksi(:,l)'; + end + + end + + for l=1:L + + sig2_ksi(l,v) = Ksi2(l)/(N1*nbasis); + CVM = KK(:,:,l)/N1; + + [~,CM] = cov2corr(CVM); + + for k=1:nbasis + rho_l(k,l,v) = mean(diag(CM,(k-1))); + end + + Tksi_l(:,:,l) = toeplitz(rho_l(:,l,v)); + Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis, v) = Tksi_l(:,:,l); + + end + +end + + +rho = mean(rho_l,3); +Tksi = mean(Tksi,3); +rho_new = rho; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% EM-algorithm + +% START HERE + +maxiter = 10; +tol = 1e-6; +rho_norm = 100; +sig_norm = 100; +niter = 1; +%options = optimset('MaxFunEvals',1000,'Maxiter',1000,'TolX',1e-6,'TolFun',1e-6,'Display','off'); +options = optimset('MaxFunEvals',500,'Maxiter',500,'TolX',1e-6,'TolFun',1e-6,'Display','off'); + +while (rho_norm>=tol && sig_norm>=tol && (niter <= maxiter)) + + rho_tmp = zeros(nbasis,L,voxels); + Tksi_tmp = zeros(L*nbasis,L*nbasis,voxels); + + sig2_ksi_old = sig2_ksi; + rho_old = rho_new; + + for v=1:voxels + + if (sum(sig2_ksi ==0) == 0) + isig2k = 1./sig2_ksi(:,v); + else + isig2k = zeros(size(sig2_ksi)); + end + + m = parind(v); + iA = inv(A(:,:,m)); + isig2e = 1/sig2_eps(m); + iTksi = pinv(Tksi); + C = zeros(nbasis,nbasis,L); + + k2 = kron(diag(isig2k),eye(nbasis)); + kbg = kron(beta,gamma); + for j=1:N1 + + Bj = inv(XB(:,:,j)'*(isig2e*iA)*XB(:,:,j) + iTksi + k2); + Rj = eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'*iV(:,:,j); + ksi = Bj*XB(:,:,j)'*(isig2e*iA)*Rj*(y(:,j,v) - XB(:,:,j)*kbg); + K((j-1)*L*nbasis+1:j*L*nbasis,v) = ksi; + + for l=1:L + C(:,:,l) = C(:,:,l) + (ksi((l-1)*nbasis+1:l*nbasis)*ksi((l-1)*nbasis+1:l*nbasis)'); % + Bj((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis)); + end + + end + + for l=1:L + dbstop if error + + rho_tmp(1,l,v) = 1; + rho_tmp(2:end,l,v) = fminsearch(@minQ,rho_old(2:end,l),options,diag(isig2k),N1,nbasis); + + Tksi_tmp((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis, v) = toeplitz(rho_new(:,l)); + sig2_ksi(l,v) = max(0, trace(pinv(Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis))*C(:,:,l))/(N1*nbasis)); + end + + end + + rho_new = mean(rho_tmp,3); + Tksi = mean(Tksi_tmp,3); + + rho_norm = norm(rho_new - rho_old); + sig_norm = norm(sig2_ksi - sig2_ksi_old); + niter = niter + 1; + +end + +% END HERE +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Step 3: Estimation of between-subject variance + + +AA = zeros(L,L); +for l1=1:L + for l2=1:L + for j=1:N1 + XBj1 = XB(:,(l1-1)*nbasis+1:l1*nbasis,j); + XBj2 = XB(:,(l2-1)*nbasis+1:l2*nbasis,j); + AA(l1,l2) = AA(l1,l2) + trace(XBj1*Tksi((l1-1)*nbasis+1:l1*nbasis, (l1-1)*nbasis+1:l1*nbasis)*XBj1'*XBj2*Tksi((l2-1)*nbasis+1:l2*nbasis, (l2-1)*nbasis+1:l2*nbasis)*XBj2'); + end + end +end +AA = (AA+AA')/2; + +for v =1:voxels + + bb = zeros(L,1); + m = parind(v); + + for j=1:N1 + + Rj = iV(:,:,j)*(eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'); + r_j = Rj*y(:,j) - Rj*XB(:,:,j)*kron(Beta(:,v),G(:,v)); + + for l=1:L + XBj = XB(:,(l-1)*nbasis+1:l*nbasis,j); + bb(l) = bb(l) + (r_j'*XBj*Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis)*XBj'*r_j - sig2_eps(m)*trace(Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis)*XBj'*A(:,:,m)*XBj)); + end + end + sig2_ksi(:,v) = quadprog(AA,bb,[],[],[],[],zeros(L,1)); + +end + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Second-pass estimation of beta and gamma for each voxel + +% sig2_ksi = zeros(1,voxels); + +for v = 1:voxels + + % Compute inverse of total covariance matrix V + iV = zeros(len,len,sub); + D = diag(sig2_ksi(:,v)); + for j=1:sub + V = XB(:,:,j)*(kron(D,eye(nbasis)))*Tksi*XB(:,:,j)' + sig2_eps(parind(v))*A(:,:,m); + iV(:,:,j) = inv(V); + end + + % Second-pass estimate of beta and gamma +% [beta, gamma, C, AE, sig2e, ksiv, epsv, XB, Phi] = EstimationVoxel(y(:,:,v), iV, Runs, Init); + [beta, gamma, C] = EstimationVoxel(y(:,:,v), iV, Runs, Init); + Beta(:,v) = beta; + G(:,v) = gamma; + CC(:,v) = C; + K(:,v) = ksiv; +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Inference + +sbeta = zeros(L,voxels); +sgamma = zeros(nbasis,voxels); +HT = zeros(1,voxels); + +sbeta2 = zeros(L,voxels); +sbeta3 = zeros(L,voxels); + + +Beta2 = Beta; +whmax = zeros(size(Beta)); + +for v = 1:voxels + + M=0; + Ny =0; + for j=1:sub + Rj = eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'*iV(:,:,j); + M = M + XB(:,:,j)'*Rj'*iV(:,:,j)*Rj*XB(:,:,j); + Ny = Ny + XB(:,:,j)'*iV(:,:,j)*Rj*y(:,j); + end + + ZB = kron(eye(L),G(:,v)); + VB = ZB'*M*ZB; + sbeta(:,v) = sqrtm(VB)*Beta(:,v); + + for l=1:L + hh = B*G; + tmp = hh*Beta(l,v); + [a,b] = max(abs(tmp)); + Beta2(l,v) = tmp(b); + whmax(l,v) = b; + end + + sbeta2(:,v) = sqrtm(VB)*Beta2(:,v); + + ZG = kron(Beta(:,v),eye(nbasis)); + VG = ZG'*M*ZG; + sgamma(:,v) = sqrtm(VG)*(G(:,v)-G0); + + HT(v) = (G(:,v)-G0)'*sqrtm(VG)*(G(:,v)-G0); + + +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Bookkeeping +Res = {}; +Res.beta = Beta; % Voxel-wise beta +Res.gamma = G; % Voxel-wise gamma +Res.H = B*G; % Voxel-wise HRF estimates +% Res.Err = Err; +Res.AR = phi; +Res.S2e = sig2_eps; +% Res.K = K; +Res.S2k = sig2_ksi; +Res.rho = rho_new; + +Res.sbeta = sbeta; +Res.beta2 = Beta2; + +Res.sgamma = sgamma; +Res.sbeta2 = sbeta2; + +Res.whmax = whmax; +Res.HT = HT; +Res.K = K; + +% Res.sgamma2 = sgamma2; +% Res.SigmaE = SigmaE; +% Res.sPhi = sPhi; +% Res.Vbeta = Vb; +% Res.Vgamma = Vg; + + +end + +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% SUBFUNCTIONS +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +function Q=minQ(rho,D, N1,nbasis) + +rho = [1; rho]; + +Tksi = toeplitz(rho); + +Q = (N1*nbasis*log(det(D)) +N1*log(det(Tksi)) + N1); + +end + + +function [beta, gamma, C, AR, sig2e, ksiv, epsv, XB, Phi, P] = EstimationVoxel(y, iV, Runs, Init) + +% Initial values +nbasis = Init.nbasis; +%norder = Init.norder; +Tlen = Init.Tlen; +len = Init.len; +L = Init.L; +N1 = Init.N1; +q = Init.q; +p = Init.p; +lambda = Init.lambda; +hphi = Init.hphi; +B = Init.B; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Create model matrices + + +% Create design matrix + +XB = zeros(len, nbasis*L , N1); +for j=1:N1 + Xj = []; + for l=1:L + Xjl = tor_make_deconv_mtx3(Runs(:,l,j),Tlen,1); + Xj = [Xj Xjl(:,1:Tlen)*B]; + end + + XB(:,:,j) = Xj; +end + +% Create nuisance matrix +Phi_ij = zeros(len,q); +Phi_ij(:,1) = 1; +Phi_ij(:,2) = (1:len)./len; +% Phi_ij(:,3) = Phi_ij(:,2).^2; +Phi_ij = orth(Phi_ij); % Orthogonalize matrix + +Phi = zeros(len, q, N1); +for j=1:N1 + Phi(:,:,j) = Phi_ij; +end + +%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Estimation algorithm + + +% Step 1: Compute penalty + +P = eye(nbasis); + +numh = size(hphi,1); +for q = 1:numh, + P = P - hphi(q,:)'*hphi(q,:); +end + + +% Step 2: Estimate initial value of gamma + +sXRX = 0; +sXRy = 0; + +for j=1:N1, + Rj = iV(:,:,j)*(eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'*iV(:,:,j)); + sXRX = sXRX + XB(:,:,j)'*Rj*XB(:,:,j); + sXRy = sXRy + XB(:,:,j)'*Rj*y(:,j); +end + +h0 = inv(sXRX + N1*lambda*kron(eye(L),P))*sXRy; +h0 = reshape(h0,nbasis,L); +tmp = sum(h0,2); +gamma = tmp./norm(tmp,2); + +% Step 3: Iteratively estimate beta and gamma + +betaold = zeros(L,1); +beta = ones(L,1); +gammaold = zeros(size(gamma)); + + +cnt = 0; +while ((norm(gamma - gammaold) > 0.0001 || norm(beta-betaold) > 0.0001) && cnt < 1000) + betaold = beta; + gammaold = gamma; + cnt = cnt + 1; + + Z = kron(eye(L),gamma); + W1 = Z'*sXRX*Z; + W2 = Z'*sXRy; + beta = inv(W1'*W1)*W1'*W2; + + + Z = kron(beta,eye(nbasis)); + W1 = Z'*sXRX*Z + N1*lambda*P; + W2 = Z'*sXRy; + + [U,D,~] = svd(W1); + lam = diag(D); + + [C,~,flag] = fzero(@(C) sum(((W2'*U).^2)'./((lam+C).^2)) - 1, 0); + if (flag == -6) + C = 0.1; + end + + W1 = W1 + C*eye(nbasis); + gamma = inv(W1'*W1)*W1'*W2; + gamma = gamma./norm(gamma,2); + + if (sum(abs(beta)) < 0.001), cnt = 1000; end % End if all beta are close to zeros +end + +% Step 4: Estimation of temporal dependence + + +AR = zeros(N1,p); +sig2e = zeros(N1,1); +ksiv = zeros(N1*nbasis*L,1); +epsv = zeros(len,1); + +for j=1:N1, + + Rj = iV(:,:,j)*(eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'); + r_j = Rj*y(:,j) - Rj*XB(:,:,j)*kron(beta,gamma); + ksi = inv(XB(:,:,j)'*XB(:,:,j) + 1000*eye(L*nbasis))*XB(:,:,j)'*r_j; + + epsilon = r_j; + epsv = epsv + epsilon; + + [a,e] = aryule(epsilon,p); + + AR(j,:) = -a(2:end); + sig2e(j) = e; + + ksiv((j-1)*nbasis*L+1:(j*nbasis*L)) = ksi; + +end + +AR = mean(AR); +sig2e = max(mean(sig2e),0); + +end \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/Anneal_Logit_allstim.m b/HRF_Est_Toolbox2/Old_stuff/Anneal_Logit_allstim.m new file mode 100755 index 00000000..26b0ecaf --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/Anneal_Logit_allstim.m @@ -0,0 +1,152 @@ +function [theta,HH,C,P,hrf,fit] = Anneal_Logit_allstim(theta0,t,tc,Run) +% +% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) +% +% Estimate inverse logit (IL) HRF model using Simulated Annealing +% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates +% +% INPUT: theta0, t, tc, Run +% Run = stick function +% tc = time course +% t = vector of time points +% theta0 = initial value for the parameter vector +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% +% further edited by Christian Waugh to include multiple trialtypes + +numstim = length(Run); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Initial values + +iter = 3000*numstim; % Number of iterations +theta = theta0; % Set initial value for the parameter vector +h0 = cost_allstim(theta0,t,tc,Run); % Calculate cost of initial estimate +%LB = [0.05, 0, 0, 0.05, 2.5, 0.05, 5]; % Previous Lower bounds for parameters +%UB = [5, 5, 2, 2, 7.5, 2, 10]; +LB = [0.05, 1, 0, 0.01, 2.5, 0.05, 3]; % Lower bounds for parameters +UB = [6, 5, 2, 2, 7.5, 3, 10]; % Upper bounds for parameters +LB = repmat(LB, 1, numstim); +UB = repmat(UB, 1, numstim); + +% +% These values may need tweaking depending on the individual situation. +% + +r1= 0.1; % delta parameters +r1b= 0.1; % delta parameters +r2 = .5; % T parameters +r3 = 0.1; % alpha parameters + +t1 = [1 4]; +t1b = [6]; +t2 = [2 5 7]; +t3 = [3]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +u = zeros(1,7); +u = repmat(u, 1, numstim); + +HH = zeros(1+iter,7*numstim); % Keep track of theta_i +HH(1,:) = theta0; +P = zeros(1+iter,1); +C = zeros(1+iter,1); % Keep track of the cost function +C(1) = h0; + +cnt = 0; +for i=1:iter, + + T = 200/log(1+i); %Temperature function (may require tweaking) + th = zeros(1,7); + th = repmat(th, 1, numstim); + ind = 0; + %time = zeros(numstim, 1); + + % Choose a new candidate solution theta_{i+1}, based on a random perturbation of the current solution of theta_{i}. + % Check new parameters are within accepted bounds + while ( (sum((LB-th)>0) + sum((th-UB)>0)) > 0) %|| (min(time) == 0), + + % Perturb solution + + for g = 0:(numstim-1) + u(t1+g*7) = normrnd(0,r1,1,2); + u(t1b+g*7) = normrnd(0,r1b,1,1); + u(t2+g*7) = normrnd(0,r2,1,3); + u(t3+g*7) = normrnd(0,r3,1,1); + end + + + % Update solution + th = theta + u; + ind = ind + 1; + + %include below if you want the times of inflection of the IL curves + %to be in a specific order (i.e. T1 < T2 < T3) + %for g = 1:numstim + % if th(g*7-5) < th(g*7-2) && th(g*7-2) < th(g*7) + % time(g) = 1; + % else + % time(g) = 0; + %end + %end + + if(ind > 500), + warning('stuck!'); + th = theta; + %return; + end; + end; + + h = cost_allstim(th,t,tc,Run); + C(i+1) = h; + delta = h - h0; + + % Determine whether to update the parameter vector. + if (unifrnd(0,1) < min(exp(-delta/T),1)), + theta = th; + h0=h; + cnt = cnt+1; + end; + + HH(i+1,:) = theta; + P(i+1) = min(exp(-delta/T),1); + +end; + +%cnt/iter + +[a,b] = min(C); +theta = HH(b,:); +%h + + +% Additional outputs +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Get HRF for final model +if nargout > 4 + hrf = zeros(length(t),numstim); +for g = 1:numstim + hrf(:,g) = Get_Logit(theta(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) +end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Convolve HRF and stick function +if nargout > 5 + len = length(Run{1}); + fitt = zeros(len,numstim); +for g = 1:numstim + fits(:,g) = conv(Run{g}, hrf(:,g)); + fitt(:,g) = fits(1:len,g); +end + fit = sum(fitt,2); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +return diff --git a/HRF_Est_Toolbox2/Old_stuff/Det_Logitold.m b/HRF_Est_Toolbox2/Old_stuff/Det_Logitold.m new file mode 100755 index 00000000..54ccf283 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/Det_Logitold.m @@ -0,0 +1,128 @@ +function [VM, h, fit, e, param] = Det_Logit(V0,t,tc,Run) +% +% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) +% +% Estimate inverse logit (IL) HRF model +% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates +% +% INPUT: V0, t, tc, Run +% Run = stick function +% tc = time course +% t = vector of time points +% V0 = initial value for the parameter vector +% +% By Martin Lindquist and Tor Wager +% Edited 10/01/09 +% + +% Find optimal values +options = optimset('MaxFunEvals',10000000,'Maxiter',10000000,'TolX',1e-8,'TolFun',1e-8,'Display','off'); +VM = fminsearch(@msq_logit,V0,options,Run,t,tc); +VM + +% Use optimal values to fit hemodynamic response functions +h = il_hdmf_tw2(t,VM(1:7)); + +%[param] = get_parameters2(h,t); +[param] = get_parameters_logit(h,t,VM(1:7)); + +len = length(Run); +fit = conv(Run, h); +fit = fit(1:len); + +e = tc-fit; + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% SUBFUNCTIONS +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function m=msq_logit(V,Run, t, tc) + +HR = il_hdmf_tw2(t,V(1:7)); +len = length(Run); +timecourse = conv(Run, HR); +timecourse = timecourse(1:len); + +m=sum((tc-timecourse).^2); + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [h,base] = il_hdmf_tw2(t,V) +% inverse logit -- creates fitted curve from parameter estimates +% +% t = vector of time points +% V = parameters + +% 3 logistic functions to be summed together +base = zeros(length(t),3); +A1 = V(1); +T1 = V(2); +d1 = V(3); +A2 = V(4); +T2 = V(5); +A3 = V(6); +T3 = V(7); +d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); +d3 = abs(d2)-abs(d1); + +base(:,1)= d1*ilogit(A1*(t-T1))'; +base(:,2)= d2*ilogit(A2*(t-T2))'; +base(:,3)= d3*ilogit(A3*(t-T3))'; +h = sum(base,2)'; + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [L] = ilogit(t) +L = exp(t)./(1+exp(t)); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% function [param] = get_parameters2(hdrf,t) +% % Find model parameters +% % +% % Height - h +% % Time to peak - p (in time units of TR seconds) +% % Width (at half peak) - w +% +% % Calculate Heights and Time to peak: +% +% n = ceil(t(end)*0.8); +% [h,p] = max(abs(hdrf(1:n))); +% h = hdrf(p); +% +% if (h >0) +% v = (hdrf >= h/2); +% else +% v = (hdrf <= h/2); +% end; +% +% [a,b] = min(diff(v)); +% v(b+1:end) = 0; +% w = sum(v); +% +% cnt = p-1; +% g =hdrf(2:end) - hdrf(1:(end-1)); +% while((cnt > 0) & (abs(g(cnt)) <0.001)), +% h = hdrf(cnt); +% p = cnt; +% cnt = cnt-1; +% end; +% +% param = zeros(3,1); +% param(1) = h; +% param(2) = p; +% param(3) = w; +% +% end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim.m b/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim.m new file mode 100755 index 00000000..80ff2a53 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim.m @@ -0,0 +1 @@ +function [VM, hrf, C, fit] = Fit_Logit_allstim(V0,t,tc,Run) % [h1, h2, VM] = Fit_logit(RunA, RunB, tc, t) % Fit inverse logit function model to time course % Initial values numstim = length(Run); len = length(Run{1}); %V0 = [ 0.5 5 1 0.5 25 -1.5 0.5 50]; %V0 = [1 3 1.5 0.5 5 3 15]; LB = [0.05, 1, 0, 0.05, 2.5, 0.05, 4]; % Previous Lower bounds for parameters UB = [5, 5, 2, 2, 7.5, 2, 10]; %LB = [0.05, 1, 0, 0.01, 2.5, 0.05, 3]; % Lower bounds for parameters %UB = [6, 13, 2, 2, 15.5, 3, 18]; % Upper bounds for parameters LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); % Find optimal values %options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','Final'); options = optimset('MaxFunEvals',5000,'Maxiter',5000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','off'); %VM = fminsearch(@msq_timecourse,V02,options,t,tc,RunA,RunB,RunC ); VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); % Use optimal values to fit hemodynamic response functions hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) end for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); C = sum((fit-tc).^2); return; \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/Fit_sFIRold.m b/HRF_Est_Toolbox2/Old_stuff/Fit_sFIRold.m new file mode 100755 index 00000000..298f7d81 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/Fit_sFIRold.m @@ -0,0 +1,56 @@ +function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) +% function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) +% +% Fits FIR and smooth FIR model +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% mode - FIR or smooth FIR +% options: +% 0 - standard FIR +% 1 - smooth FIR +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% +% Created by Martin Lindquist on 10/02/09 + +[DX] = tor_make_deconv_mtx3(Runs,T,1); +DX2 = DX(:,1:T); +num = T; + +if mode == 1 + + C=(1:num)'*(ones(1,num)); + h = sqrt(1/(7/TR)); % 7 seconds smoothing - ref. Goutte + + v = 0.1; + sig = 1; + + R = v*exp(-h/2*(C-C').^2); + RI = inv(R); + + b = inv(DX2'*DX2+sig^2*RI)*DX2'*tc; + fit = DX2*b; + e = tc - DX2*b; + +elseif mode == 0 + + b = pinv(DX)*tc; + fit = DX*b; + e = tc - DX*b; + +end + +hrf = b; +param = get_parameters2(b,T); + +end \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit.m b/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit.m new file mode 100755 index 00000000..e276fe6b --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit.m @@ -0,0 +1,77 @@ +function [param] = get_parameters_logit(hdrf,t,VM) +% +% [param] = get_parameters_logit(hdrf,t,VM) +% +% Estimate Height, time-to-peak and Width for the inverse logit model +% +% INPUT: hdrf,t,VM +% hdrf - HRF estimated using IL model +% t - vector of time points +% VM - IL model parameters +% +% OUTPUT: param =[h,p,w] +% Height - h +% Time to peak - p (in time units of TR seconds) +% Width (at half peak) - w +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% + +% Estimate Heights and Time to peak: + +[dL, dL2, d1, d2] = dilogit_dt2(t,VM); % Calculate first and second derivativeof IL model +g = (abs(diff(sign(dL))) >= 1); +cnt = max(1,ceil(VM(2))); %Peak has to be further along then T1 + +p =-1; +while(cnt < max(t)), + if((g(cnt) == 1) & (dL2(cnt) <= 0)), + p = cnt; + h = hdrf(p); + cnt = max(t) +1; + end; + cnt = cnt+1; +end; + +if (p == -1), + [h,p] = max(hdrf); +end; + + +% Interpolate to get finer estimation of p +if (p>1 & p= h/2); +[a,b2] = min(diff(v)); +v(b2+1:end) = 0; +[a,b1] = max(v); + +% Interpolate to get finer estimation of v + +if (p>1 && p= h/2)); + tp = b2 + (0:0.01:1); + htmp = Get_Logit(VM,tp); + delta2 = sum((htmp >= h/2)); + w = sum(v) + delta1/100 + delta2/100; +else + w = sum(v)+1; +end; + +% Set output vector param +param = zeros(3,1); +param(1) = h; +param(2) = p; +param(3) = w; + +return; diff --git a/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit2_2hrfs.m b/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit2_2hrfs.m new file mode 100755 index 00000000..ca4d2abc --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit2_2hrfs.m @@ -0,0 +1,176 @@ +function [param, hdrf2] = Get_parameters_Logit2_2hrfs(hdrf,t,VM,t1t2) +% +% [param] = get_parameters_logit(hdrf,t,VM) +% +% Different from version 1 in that it only calculates increases as 'peaks' +% Estimate Height, time-to-peak and Width for the inverse logit model +% +% INPUT: hdrf,t,VM (all with two rows for each HRF of interest) +% hdrf - HRF estimated using IL model +% t - vector of time points +% VM - IL model parameters +% t1t2 - number of time points separating the onset of the first and second +% hrfs + +% OUTPUT: param =[h,p,w] +% Height - h +% Time to peak - p (in time units of TR seconds) +% Width (at half peak) - w +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% +% In addition, it'll calculate the width from two HRFs put together in case +% your design is such that two events are dependent on each other (i.e. +% cue->stimulus) +% + +% added by Christian Waugh + +% Estimate Heights and Time to peak for first HRF: + +[dL, dL2, d1, d2] = dilogit_dt2(t,VM(1, :)); % Calculate first and second derivativeof IL model +g = (abs(diff(sign(dL))) >= 1); +%cnt = max(1,ceil(VM(1, :)(2))); %Peak has to be further along then T1 +cnt = max(1,ceil(min(min(VM(1,2),VM(1,5)),VM(1,7)))); + +p =-1; + +while(cnt < max(t)), + if((g(cnt) == 1) && (dL2(cnt) <= 0)), + p = cnt; + h = hdrf(p,1); + cnt = max(t) +1; + end; + cnt = cnt+1; +end; + + +if (p == -1) + [h,p] = max(hdrf(:,1)); +end; + + +% Interpolate to get finer estimation of p +if (p>1 && p= h/2); +[a,b2] = min(diff([v;0])); +if b2 < length(v) +v(b2+1:end) = 0; +end +[a,b1] = max(v); + +% Interpolate to get finer estimation of v + +if (p>1 && p= h/2)); + tp = b2 + (0:0.01:1); + + htmp = Get_Logit(VM(1, :),tp); + delta2 = sum((htmp >= h/2)); + w = sum(v) + delta1/100 + delta2/100; +else + w = sum(v)+1; +end; + +% Set output vector param +param = zeros(7,1); +param(1) = h; +param(2) = p; +param(3) = w; + + +% Estimate Heights and Time to peak for second HRF: + +[dLb, dL2b, d1b, d2b] = dilogit_dt2(t,VM(2, :)); % Calculate first and second derivativeof IL model +gb = (abs(diff(sign(dLb))) >= 1); +%cnt = max(1,ceil(VM(1, :)(2))); %Peak has to be further along then T1 +cntb = max(1,ceil(min(min(VM(2,2),VM(2,5)),VM(2,7)))); + +pb =-1; +hb = 0; + + +while(cntb < max(t)), + if((gb(cntb) == 1) && (dL2b(cntb) <= 0)), + pb = cntb; + hb = hdrf(pb,2); + cntb = max(t) +1; + end; + cntb = cntb+1; +end; + + +if (pb == -1) + [hb,pb] = max(hdrf(:,2)); +end; + + +% Interpolate to get finer estimation of p +if (pb>1 & pb= hb/2); + +[ab,b2b] = min(diff([vb;0])); +if b2b < length(vb) +vb(b2b+1:end) = 0; +end +[ab,b1b] = max(vb); + +% Interpolate to get finer estimation of v + +if (pb>1 & pb= hb/2)); + tpb = b2b + (0:0.01:1); + + htmpb = Get_Logit(VM(2, :),tpb); + delta2 = sum((htmpb >= hb/2)); + wb = sum(vb) + delta1/100 + delta2/100; +else, + wb = sum(vb)+1; +end; + +% Set output vector param +param(4) = hb; +param(5) = pb; +param(6) = wb; + + +% Combine hrfs 1 and 2 to get an overall width estimate + +hdrf2 = [hdrf(:,1); repmat(0,t1t2,1)] + [repmat(0,t1t2,1); hdrf(:,2)]; + + vc = (hdrf2 >= h/2); + +[ac,b2c] = min(diff([vc;0])); +if b2c < length(vc) +vc(b2c+1:end) = 0; +end +[ac,b1c] = max(vc); + + +wc = sum(vc)+1; + +param(7) = wc; + +return; diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Anneal_Logit.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Anneal_Logit.m new file mode 100755 index 00000000..ed29be6b --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Anneal_Logit.m @@ -0,0 +1,126 @@ +function [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(theta0,t,tc,Run) +% +% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) +% +% Estimate inverse logit (IL) HRF model using Simulated Annealing +% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates +% +% INPUT: theta0, t, tc, Run +% Run = stick function +% tc = time course +% t = vector of time points +% theta0 = initial value for the parameter vector +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Initial values + +iter = 15000; % Number of iterations +theta = theta0; % Set initial value for the parameter vector +h0 = cost(theta0,t,tc,Run); % Calculate cost of initial estimate +LB = [0.05, 1, 0, 0.05, 5, 0, 10]; % Lower bounds for parameters +UB = [10, 15, 5, 10, 15, 5, 30]; % Upper bounds for parameters + +% +% These values may need tweaking depending on the individual situation. +% + +r1= 0.001; % A parameters +r1b= 0.001; % A parameters +r2 = 0.05; % T parameters +r3 = 0.001; % delta parameters + +t1 = [1 4]; +t1b = [6]; +t2 = [2 5 7]; +t3 = [3]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +u = zeros(1,7); + +HH = zeros(1+iter,7); % Keep track of theta_i +HH(1,:) = theta0; +P = zeros(1+iter,1); +C = zeros(1+iter,1); % Keep track of the cost function +C(1) = h0; + +cnt = 0; +for i=1:iter, + + T = 100/log(1+i); %Temperature function (may require tweaking) + th = zeros(1,7); + ind = 0; + + % Choose a new candidate solution theta_{i+1}, based on a random perturbation of the current solution of theta_{i}. + % Check new parameters are within accepted bounds + while ( (sum((LB-th)>0) + sum((th-UB)>0)) > 0), + + % Perturb solution + + u(t1) = normrnd(0,r1,1,2); + u(t1b) = normrnd(0,r1b,1,1); + u(t2) = normrnd(0,r2,1,3); + u(t3) = normrnd(0,r3,1,1); + + % Update solution + th = theta + u; + ind = ind + 1; + + if(ind > 500), + warning('stuck!'); + return; + end; + end; + + h = cost(th,t,tc,Run); + C(i+1) = h; + delta = h - h0; + + % Determine whether to update the parameter vector. + if (unifrnd(0,1) < min(exp(-delta/T),1)), + theta = th; + h0=h; + cnt = cnt+1; + end; + + HH(i+1,:) = theta; + P(i+1) = min(exp(-delta/T),1); + +end; + +%cnt/iter + +[a,b] = min(C); +theta = HH(b,:); +%h + + +% Additional outputs +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Get HRF for final model +if nargout > 4 + hrf = Get_Logit(theta(1:7),t); % Calculate HRF estimate (fit, given theta) +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Convolve HRF and stick function +if nargout > 5 + len = length(Run); + fit = conv(Run, hrf); + fit = fit(1:len); + e = tc - fit; +end + +if nargout > 7 + [param] = get_parameters_logit(hrf,t,theta); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +return diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Det_Logit.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Det_Logit.m new file mode 100755 index 00000000..7e443e13 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Det_Logit.m @@ -0,0 +1,116 @@ +function [VM, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run) +% +% [VM, h, fit, e, param] = Det_Logit_allstim(V0,t,tc,Run) +% +% Estimate inverse logit (IL) HRF model +% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates +% +% INPUT: V0, t, tc, Run +% Run = stick function +% tc = time course +% t = vector of time points +% V0 = initial value for the parameter vector +% +% By Martin Lindquist, Christian Waugh and Tor Wager +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/26/10 (ML) + + +numstim = length(Run); +len = length(Run{1}); + +% LB = [0.05, 1, 0, 0.05, 4, 0, 10]; % Lower bounds for parameters +% UB = [10, 15, 10, 10, 15, 5, 50]; % Upper bounds for parameters +% LB = repmat(LB, 1, numstim); +% UB = repmat(UB, 1, numstim); + +% Remove intercept + +b0 = pinv(ones(length(tc),1))*tc; +tc = tc - b0; + +% Find optimal values + +options = optimset('MaxFunEvals',10000000,'Maxiter',10000000,'TolX',1e-8,'TolFun',1e-8,'Display','off'); + +%VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); +VM = fminsearch(@msq_logit,V0,options,Run,t,tc); + +% Use optimal values to fit hemodynamic response functions +hrf =zeros(length(t),numstim); +fitt = zeros(len,numstim); +param = zeros(3,numstim); + +for g = 1:numstim + hrf(:,g) = il_hdmf_tw2(t,VM(((g-1)*7+1):(g*7))); % Calculate HRF estimate (fit, given theta) + param(:,g) = get_parameters2(hrf(:,g),t(end)); + fits(:,g) = conv(Run{g}, hrf(:,g)); + fitt(:,g) = fits(1:len,g); +end + +fit = sum(fitt,2); +e = tc-fit; +fit = fit + b0; + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% SUBFUNCTIONS +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function m=msq_logit(V,Run, t, tc) + +numstim = length(Run); +len = length(Run{1}); +h = zeros(length(t),numstim); +yhatt =zeros(len,numstim); + +for k = 1:numstim + h(:,k) = il_hdmf_tw2(t,V(((k-1)*7+1):(k*7))); % Get IL model corresponding to parameters V + yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function + yhatt(:,k) = yhat(1:len,k); +end + +yhat2 = sum(yhatt,2); %Sum models together to get overall estimate + +m = sum((tc-yhat2).^2); % Calculate cost function + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [h,base] = il_hdmf_tw2(t,V) +% inverse logit -- creates fitted curve from parameter estimates +% +% t = vector of time points +% V = parameters + +% 3 logistic functions to be summed together +base = zeros(length(t),3); +A1 = V(1); +T1 = V(2); +d1 = V(3); +A2 = V(4); +T2 = V(5); +A3 = V(6); +T3 = V(7); +d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); +d3 = abs(d2)-abs(d1); + +base(:,1)= d1*ilogit(A1*(t-T1))'; +base(:,2)= d2*ilogit(A2*(t-T2))'; +base(:,3)= d3*ilogit(A3*(t-T3))'; +h = sum(base,2)'; + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [L] = ilogit(t) +L = exp(t)./(1+exp(t)); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example.m new file mode 100755 index 00000000..4d192dad --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example.m @@ -0,0 +1,176 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Example code for estimating the HRF using the Inverse-Logit Model, a +% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. +% Also the code illustrates our code for detecting model misspecification. +% +% By Martin Lindquist and Tor Wager +% Created: 10/02/09 +% Last edited: 05/26/10 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Load time course +% + +mypath = which('ilogit'); +if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end +[mydir] = fileparts(mypath) + +load(fullfile(mydir,'timecourse')) + +tc = (tc- mean(tc))/std(tc); +len = length(tc); +figure; subplot(3,1,1); han = plot(tc); +title('Sample time course'); drawnow + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Settings +% + +TR = 0.5; +T = round(30/TR); +t = 1:T; % samples at which to get Logit HRF Estimate +FWHM = 4; % FWHM for residual scan +pval = 0.01; +df = 600; +alpha = 0.001; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Create stick function (sample event onsets) +% Variable R contains onset times +% Variable Run contains stick (a.k.a. delta or indicator) function + +R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; +Runs = zeros(640,1); +for i=1:length(R), Runs(R(i)) = 1; end; + +Run = []; +Run{1}=Runs; + + +try + hold on; + hh = plot_onsets(R,'k',-3,1); + drawnow +catch + disp('Couldn''t find function to add onset sticks to plot. Skipping.') +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using IL-function + +% Choose mode (deterministic/stochastic) + +mode = 0; % 0 - deterministic aproach + % 1 - simulated annealing approach + % Please note that when using simulated annealing approach you + % may need to perform some tuning before use. + + +[h1, fit1, e1, param] = Fit_Logit(tc,Run,t,mode); +[pv sres sres_ns1] = ResidScan(e1, FWHM); +[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Run, alpha); + +hold on; han(2) = plot(fit1,'r'); + +disp('Summary: IL_function'); + +disp('Amplitude:'); disp(param(1)); +disp('Time-to-peak:'); disp(param(2)); +disp('Width:'); disp(param(3)); + +disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); +disp('Mis-modeling:'); disp(pv); +disp('Power Loss:'); disp(PowLoss1); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using FIR-model + +% Choose mode (FIR/sFIR) + +mode = 1; % 0 - FIR + % 1 - smooth FIR + +[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Run,T,mode); +[pv sres sres_ns2] = ResidScan(e2, FWHM); +[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Run, alpha); + +hold on; han(3) = plot(fit2,'g'); + +disp('Summary: FIR'); + +disp('Amplitude'); disp(param(1)); +disp('Time-to-peak'); disp(param(2)); +disp('Width'); disp(param(3)); + +disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); +disp('Mis-modeling'); disp(pv); +disp('Power Loss:'); disp(PowLoss2); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using Canonical HRF + 2 derivatives + +p=1; + +[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Run,T,p); +[pv sres sres_ns3] = ResidScan(e3, FWHM); +[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Run, alpha); + +hold on; han(4) = plot(fit3,'m'); + +legend(han,{'Data' 'IL' 'sFIR' 'DD'}) + + +disp('Summary: Canonical + 2 derivatives'); + +disp('Amplitude'); disp(param(1)); +disp('Time-to-peak'); disp(param(2)); +disp('Width'); disp(param(3)); + +disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); +disp('Mis-modeling'); disp(pv); +disp('Power Loss:'); disp(PowLoss3); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%figure; + + +subplot(3,2,5); +han2 = plot(h1,'r'); +hold on; han2(2) = plot(h2,'g'); +hold on; han2(3) = plot(h3,'m'); +legend(han2,{'IL' 'sFIR' 'DD'}) +title('Estimated HRF'); + + +subplot(3,1,2); hold on; +hh = plot_onsets(R,'k',-3,1); +drawnow + +han3 = plot(sres_ns1,'r'); +hold on; han3(2) = plot(sres_ns2,'g'); +hold on; han3(3) = plot(sres_ns3,'m'); +hold on; plot((1:len),zeros(len,1),'--k'); +legend(han3,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (time course)'); + + +subplot(3,2,6); hold on; + +[s1] = Fit_sFIR(sres_ns1,TR,Run,T,0); +[s2] = Fit_sFIR(sres_ns2,TR,Run,T,0); +[s3] = Fit_sFIR(sres_ns3,TR,Run,T,0); + +han4 = plot(s1(1:T),'r'); +hold on; han4(2) = plot(s2(1:T),'g'); +hold on; han4(3) = plot(s3(1:T),'m'); +hold on; plot((1:T),zeros(T,1),'--k'); +legend(han4,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (HRF)'); diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example2.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example2.m new file mode 100755 index 00000000..f0b77ceb --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example2.m @@ -0,0 +1,196 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Example code for estimating the HRF using the Inverse-Logit Model, a +% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. +% Also the code illustrates our code for detecting model misspecification. +% +% This example illustrates the multi-stimulus version +% +% By Martin Lindquist +% Created: 05/26/10 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Load time course +% + +mypath = which('ilogit'); +if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end +[mydir] = fileparts(mypath) + + +h = spm_hrf(1); +h = h./max(h); + +RunA = zeros(60,10); +RunA(1,:) = 1; +RunA = reshape(RunA,600,1); +RunB = zeros(60,10); +RunB(31,:) = 1; +RunB = reshape(RunB,600,1); + +tc = 2*conv(RunA,h) + conv(RunB,h); +tc = tc(1:600); +tc = tc+normrnd(0,1,600,1); + +Run =[]; +Run{1} = RunA; +Run{2} = RunB; + +len = length(tc); + +figure; subplot(3,1,1); han = plot(tc); +title('Sample time course'); drawnow + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Settings +% + +TR = 1; +T = round(30/TR); +t = 1:T; % samples at which to get Logit HRF Estimate +FWHM = 4; % FWHM for residual scan +pval = 0.01; +df = 600; +alpha = 0.001; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Create stick function (sample event onsets) +% Variable R contains onset times +% Variable Run contains stick (a.k.a. delta or indicator) function + +RA = [1 61 121 181 241 301 361 421 481 541]; +RB = [31 91 151 211 271 331 391 451 511 571]; + + +try + hold on; + hh = plot_onsets(RA,'k',-3,1); + hh = plot_onsets(RB,'r',-3,0.5); + drawnow +catch + disp('Couldn''t find function to add onset sticks to plot. Skipping.') +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using IL-function + +% Choose mode (deterministic/stochastic) + +mode = 0; % 0 - deterministic aproach + % 1 - simulated annealing approach + % Please note that when using simulated annealing approach you + % may need to perform some tuning before use. + + + + +[h1, fit1, e1, param] = Fit_Logit(tc,Run,t,mode); +[pv sres sres_ns1] = ResidScan(e1, FWHM); +[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Run, alpha); + +hold on; han(2) = plot(fit1,'r'); + +disp('Summary: IL_function'); + +disp('HRF - Event A'); +disp('Amplitude:'); disp(param(1,1)); +disp('Time-to-peak:'); disp(param(2,1)); +disp('Width:'); disp(param(3,1)); + +disp('HRF - Event B'); +disp('Amplitude:'); disp(param(1,2)); +disp('Time-to-peak:'); disp(param(2,2)); +disp('Width:'); disp(param(3,2)); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using FIR-model + +% Choose mode (FIR/sFIR) + +mode = 1; % 0 - FIR + % 1 - smooth FIR + +[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Run,T,mode); +[pv sres sres_ns2] = ResidScan(e2, FWHM); +[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Run, alpha); + +hold on; han(3) = plot(fit2,'g'); + +disp('Summary: FIR'); + +disp('HRF - Event A'); +disp('Amplitude:'); disp(param(1,1)); +disp('Time-to-peak:'); disp(param(2,1)); +disp('Width:'); disp(param(3,1)); + +disp('HRF - Event B'); +disp('Amplitude:'); disp(param(1,2)); +disp('Time-to-peak:'); disp(param(2,2)); +disp('Width:'); disp(param(3,2)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using Canonical HRF + 2 derivatives + +p=1; + +[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Run,T,p); +[pv sres sres_ns3] = ResidScan(e3, FWHM); +[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Run, alpha); + +hold on; han(4) = plot(fit3,'m'); + +legend(han,{'Data' 'IL' 'sFIR' 'DD'}) + + +disp('Summary: Canonical + 2 derivatives'); + +disp('HRF - Event A'); +disp('Amplitude:'); disp(param(1,1)); +disp('Time-to-peak:'); disp(param(2,1)); +disp('Width:'); disp(param(3,1)); + +disp('HRF - Event B'); +disp('Amplitude:'); disp(param(1,2)); +disp('Time-to-peak:'); disp(param(2,2)); +disp('Width:'); disp(param(3,2)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%figure; + + +subplot(3,2,5); +han2 = plot(h1(:,1),'r'); +hold on; han2(2) = plot(h2(:,1),'g'); +hold on; han2(3) = plot(h3(:,1),'m'); +legend(han2,{'IL' 'sFIR' 'DD'}) +title('Estimated HRF - Event A'); + + +subplot(3,1,2); hold on; +hh = plot_onsets(RA,'k',-3,1); +hh = plot_onsets(RB,'r',-3,0.5); +drawnow + +han3 = plot(sres_ns1,'r'); +hold on; han3(2) = plot(sres_ns2,'g'); +hold on; han3(3) = plot(sres_ns3,'m'); +hold on; plot((1:len),zeros(len,1),'--k'); +legend(han3,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (time course)'); + + +subplot(3,2,6); hold on; + +han4 = plot(h2(:,1),'r'); +hold on; han4(2) = plot(h2(:,2),'g'); +hold on; han4(3) = plot(h3(:,2),'m'); +legend(han4,{'IL' 'sFIR' 'DD'}) +title('Estimated HRF - Event A'); diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example_old.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example_old.m new file mode 100755 index 00000000..f3ed2282 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example_old.m @@ -0,0 +1,207 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Example code for estimating the HRF using the Inverse-Logit Model, a +% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. +% Also the code illustrates our code for detecting model misspecification. +% +% By Martin Lindquist and Tor Wager +% Edited 05/17/13 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Load time course +% + +mypath = which('ilogit'); +if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end +[mydir] = fileparts(mypath) + +load(fullfile(mydir,'timecourse')) + +tc = (tc- mean(tc))/std(tc); +len = length(tc); + + +%% Or: create your own +[xBF] = spm_get_bf(struct('dt', .5, 'name', 'hrf (with time and dispersion derivatives)', 'length', 32)); +clear Xtrue +for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 1 1 1 1 ]'); + Xtrue(:, i) = xx(1:66); +end +for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); + Xtrue(:, i) = xx(1:66); +end +hrf = Xtrue * [1 .3 .2]'; +xsecs = 0:.5:32; +hrf = hrf(1:length(xsecs)); +hrf = hrf ./ max(hrf); +figure; plot(xsecs, hrf, 'k') +%hrf = hrf(1:4:end); % downsample to TR, if TR is > 0.5 + + +R = randperm(640); R = sort(R(1:36)); +Run = zeros(640,1); +for i=1:length(R), Run(R(i)) = 1; end; +true_sig = conv(Run, hrf); +true_sig = true_sig(1:640); + +tc_noise = noise_arp(640, [.7 .2]); +tc = true_sig + 0 * tc_noise; +%figure; plot(tc); + + +%% + +create_figure; subplot(3,1,1); han = plot(tc); +title('Sample time course'); drawnow + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Settings +% + +TR = 0.5; +% T = round(30/TR); +% t = 1:T; % samples at which to get Logit HRF Estimate +T = 30; +FWHM = 4; % FWHM for residual scan +pval = 0.01; +df = 600; +alpha = 0.001; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Create stick function (sample event onsets) +% Variable R contains onset times +% Variable Run contains stick (a.k.a. delta or indicator) function + +R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; +Run = zeros(640,1); +for i=1:length(R), Run(R(i)) = 1; end; +Runc = {}; +Runc{1} = Run; + +try + hold on; + hh = plot_onsets(R,'k',-3,1); + drawnow +catch + disp('Couldn''t find function to add onset sticks to plot. Skipping.') +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using IL-function + +% Choose mode (deterministic/stochastic) + +mode = 1; % 0 - deterministic aproach + % 1 - simulated annealing approach + % Please note that when using simulated annealing approach you + % may need to perform some tuning before use. + +%[h1, fit1, e1, param] = Fit_Logit_allstim(tc,Run,t,mode); +V0 = [ 1 6 1 0.5 10 1 15]; +[VM, h1, fit1, e1, param] = Fit_Logit_allstim(V0,tc,TR, Runc, T); +[pv sres sres_ns1] = ResidScan(e1, FWHM); +[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Runc, alpha); + +hold on; han(2) = plot(fit1,'r'); + +disp('Summary: IL_function'); + +disp('Amplitude:'); disp(param(1)); +disp('Time-to-peak:'); disp(param(2)); +disp('Width:'); disp(param(3)); + +disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); +disp('Mis-modeling:'); disp(pv); +disp('Power Loss:'); disp(PowLoss1); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using FIR-model + +% Choose mode (FIR/sFIR) + +mode = 1; % 0 - FIR + % 1 - smooth FIR + +[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Runc,T,mode); +[pv sres sres_ns2] = ResidScan(e2, FWHM); +[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Runc, alpha); + +hold on; han(3) = plot(fit2,'g'); + +disp('Summary: FIR'); + +disp('Amplitude'); disp(param(1)); +disp('Time-to-peak'); disp(param(2)); +disp('Width'); disp(param(3)); + +disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); +disp('Mis-modeling'); disp(pv); +disp('Power Loss:'); disp(PowLoss2); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using Canonical HRF + 2 derivatives + +p=1; + +[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Runc,T,p); +[pv sres sres_ns3] = ResidScan(e3, FWHM); +[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Runc, alpha); + +hold on; han(4) = plot(fit3,'m'); + +legend(han,{'Data' 'IL' 'sFIR' 'DD'}) + + +disp('Summary: Canonical + 2 derivatives'); + +disp('Amplitude'); disp(param(1)); +disp('Time-to-peak'); disp(param(2)); +disp('Width'); disp(param(3)); + +disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); +disp('Mis-modeling'); disp(pv); +disp('Power Loss:'); disp(PowLoss3); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%figure; + + +subplot(3,2,5); +han2 = plot(h1,'r'); +hold on; han2(2) = plot(h2,'g'); +hold on; han2(3) = plot(h3,'m'); +legend(han2,{'IL' 'sFIR' 'DD'}) +title('Estimated HRF'); + + +subplot(3,1,2); hold on; +hh = plot_onsets(R,'k',-3,1); +drawnow + +han3 = plot(sres_ns1,'r'); +hold on; han3(2) = plot(sres_ns2,'g'); +hold on; han3(3) = plot(sres_ns3,'m'); +hold on; plot((1:len),zeros(len,1),'--k'); +legend(han3,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (time course)'); + + +subplot(3,2,6); hold on; + +[s1] = Fit_sFIR(sres_ns1,TR,Runc,T,0); +[s2] = Fit_sFIR(sres_ns2,TR,Runc,T,0); +[s3] = Fit_sFIR(sres_ns3,TR,Runc,T,0); + +han4 = plot(s1(1:T),'r'); +hold on; han4(2) = plot(s2(1:T),'g'); +hold on; han4(3) = plot(s3(1:T),'m'); +hold on; plot((1:T),zeros(T,1),'--k'); +legend(han4,{'IL' 'sFIR' 'DD'}) +title('Mis-modeling (HRF)'); diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Canonical_HRF.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Canonical_HRF.m new file mode 100755 index 00000000..0cac4d57 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Canonical_HRF.m @@ -0,0 +1,119 @@ +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,Runs,T,p) +% +% Fits GLM using canonical hrf (with option of using time and dispersion derivatives)'; +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% p - Model type +% +% Options: p=0 - only canonical HRF +% p=1 - canonical + temporal derivative +% p=2 - canonical + time and dispersion derivative +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% info - struct containing design matrices, beta values etc +% +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/26/10 (ML) + +d = length(Run); +len = length(Run{1}); + +X = zeros(len,p*d); +param = zeros(3,d); + +[h, dh, dh2] = CanonicalBasisSet(TR); + +for i=1:d, + v = conv(Run{i},h); + X(:,(i-1)*p+1) = v(1:len); + + if (p>1) + v = conv(Run{i},dh); + X(:,(i-1)*p+2) = v(1:len); + end + + if (p>2) + v = conv(Run{i},dh2); + X(:,(i-1)*p+3) = v(1:len); + end +end + +X = [(zeros(len,1)+1) X]; + +b = pinv(X)*tc; +e = tc-X*b; +fit = X*b; + +b = reshape(b(2:end),p,d)'; +bc = zeros(d,1); + +for i=1:d, + if (p == 1) + bc(i) = b(i,1); + H = h; + elseif (p==2) + bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2); + H = [h dh]; + elseif (p>2) + bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2 + (b(i,3))^2); + H = [h dh dh2]; + end + +end + +hrf = H*b'; + +for i=1:d, + param(:,i) = get_parameters2(hrf(:,i),T); +end; + +info ={}; +info.b = b; +info.bc = bc; +info.X = X; +info.H =H; + +end + +% END MAIN FUNCTION +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Subfunctions +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [h, dh, dh2] = CanonicalBasisSet(TR) + +len = round(30/TR); +xBF.dt = TR; +xBF.length= len; +xBF.name = 'hrf (with time and dispersion derivatives)'; +xBF = spm_get_bf(xBF); + +v1 = xBF.bf(1:len,1); +v2 = xBF.bf(1:len,2); +v3 = xBF.bf(1:len,3); + +h = v1; +dh = v2 - (v2'*v1/norm(v1)^2).*v1; +dh2 = v3 - (v3'*v1/norm(v1)^2).*v1 - (v3'*dh/norm(dh)^2).*dh; + +h = h./max(h); +dh = dh./max(dh); +dh2 = dh2./max(dh2); + +end \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit.m new file mode 100755 index 00000000..1fe50030 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit.m @@ -0,0 +1,57 @@ +function [hrf, fit, e, param] = Fit_Logit(tc,Run,t,mode) +% function [hrf, fit, e, param] = Fit_Logit(tc,Run,t,mode) +% +% Fits FIR and smooth FIR model +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% mode - deterministic or stochastic +% options: +% 0 - deterministic aproach +% 1 - simulated annealing approach +% Please note that when using simulated annealing approach you +% may need to perform some tuning before use. +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/26/10 (ML) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit the Logit model + +numstim = length(Run); +len = length(Run{1}); + +V0 = [ 1 6 1 0.5 10 1 15]; % initial values for logit fit +V0 = repmat(V0,1,numstim); + +if (mode == 1 && numstim>1), + disp('Multi-stimulus annealing function currently not implemented. Switching to "deterministic mode"') + mode = 0; +end; + +% Estimate theta (logit parameters) + +if (mode == 1) + disp('Stochastic Mode'); + + Runs = Run{1}; + [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(V0,t,tc,Runs); + +elseif (mode == 0) + disp('Deterministic Mode'); + [theta, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run); + +end + +end diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim.m new file mode 100755 index 00000000..bb697d63 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim.m @@ -0,0 +1 @@ +function [VM, hrf, fit, e, param] = Fit_Logit_allstim(V0,tc,TR, Run, T) % Fit inverse logit function model to time course % Multi-stimulus case % % INPUT: % % V0 = initial parameters % tc = time course % TR = time resolution % Run = stick function (one trial type per cell) % % % OUTPUT: % % VM = estimated parameters % hrf = estimated HRF % C = SSE % fit - fitted time course % param - estimated height, time to peak and width for each HRF numstim = length(Run); len = length(Run{1}); t=1:TR:T; % Initial values LB = [0.05, 1, 0, 0.05, 5, 0.05, 8]; % Previous Lower bounds for parameters UB = [5, 10, 2.5, 2, 15, 2, 20]; LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); V0 = repmat(V0, 1, numstim); % Find optimal values options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-6, 'TolFun', 1e-6,'Display','off'); VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); % Use optimal values to fit hemodynamic response functions hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); param = zeros(3,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) param(:,g) = get_parameters2(hrf(:,g),(1:length(t))); end % Get fitted time courses and residuals for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); e = tc-fit; return; \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim_2.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim_2.m new file mode 100755 index 00000000..95139b90 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim_2.m @@ -0,0 +1 @@ +function [VM, hrf, fit] = Fit_Logit_allstim_2(V0,tc,tr, Run, down) % Fit inverse logit function model to time course % Multi-condition case % % Last edited by ML on 02/12/13 % % INPUTS: % % V0 - Initial values for IL-model % tc - time course data % tr - TR % Run - a cell array containing stick functions (one for each condition) % down - downsampling factor % % OUTPUTS: % % VM - final parameter values for IL-model % hrf - estimated HRFs for each condition % fit - estimated time course % Initial values numstim = length(Run); len = length(Run{1}); LB = [0.05, 1, 0, 0.05, 5, 0.05, 8]; % Previous Lower bounds for parameters UB = [2 10, 2, 2, 15, 1, 20]; % LB = [0.05, 1, 0, 0.05, 5, 0.05, 8, 0]; % Previous Lower bounds for parameters % UB = [2 10, 5, 2, 15, 1, 20, 5]; LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); V0 = repmat(V0,1,numstim); % Find optimal values %options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','Final'); options = optimset('MaxFunEvals',100000,'Maxiter',100000,'TolX', 1e-8, 'TolFun', 1e-8,'Display','off'); %VM = fminsearch(@msq_timecourse,V02,options,t,tc,RunA,RunB,RunC ); VM = fminsearchbnd(@cost_allstim_2, V0, LB, UB, options,tr,tc,Run,down); % Use optimal values to fit hemodynamic response functions t=0:(1/down):30; hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) end for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); return; \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_sFIR.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_sFIR.m new file mode 100755 index 00000000..5ce0c6e7 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_sFIR.m @@ -0,0 +1,77 @@ +function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Run,T,mode) +% function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) +% +% Fits FIR and smooth FIR model +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% mode - FIR or smooth FIR +% options: +% 0 - standard FIR +% 1 - smooth FIR +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% +% Created by Martin Lindquist on 10/02/09 +% Last edited: 05/26/10 (ML) + +numstim = length(Run); +len = length(Run{1}); + + +Runs = zeros(len,numstim); +for i=1:numstim, + Runs(:,i) = Run{i}; +end; + +[DX] = tor_make_deconv_mtx3(Runs,T,1); + +% DX2 = DX(:,1:T); +% num = T; + +if mode == 1 + + C=(1:T)'*(ones(1,T)); + h = sqrt(1/(7/TR)); % 7 seconds smoothing - ref. Goutte + + v = 0.1; + sig = 1; + + R = v*exp(-h/2*(C-C').^2); + RI = inv(R); + MRI = zeros(numstim*T+1); + for i=1:numstim, + MRI(((i-1)*T+1):(i*T),((i-1)*T+1):(i*T)) = RI; + end; + + b = inv(DX'*DX+sig^2*MRI)*DX'*tc; + fit = DX*b; + e = tc - DX*b; + +elseif mode == 0 + + b = pinv(DX)*tc; + fit = DX*b; + e = tc - DX*b; + +end + + +hrf =zeros(T,numstim); +param = zeros(3,numstim); + +for i=1:numstim, + hrf(:,i) = b(((i-1)*T+1):(i*T))'; + param(:,i) = get_parameters2(hrf(:,i),T); +end; + +end \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit.m new file mode 100755 index 00000000..c33a6d43 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit.m @@ -0,0 +1 @@ +function [h, base] = get_logit(V,t) % % [h] = get_logit(V,t) % % Calculate inverse logit (IL) HRF model % Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates % % INPUT: V, t % t = vector of time points % V = parameters % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % A1 = V(1); T1 = V(2); d1 = V(3); A2 = V(4); T2 = V(5); A3 = V(6); T3 = V(7); d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); d3 = abs(d2)-abs(d1); h = d1*ilogit(A1*(t-T1))+d2*ilogit(A2*(t-T2))+d3*ilogit(A3*(t-T3)); % Superimpose 3 IL functions h = h'; base =zeros(3,length(t)); base(1,:) = ilogit(A1*(t-T1)); base(2,:) = ilogit(A2*(t-T2)); base(3,:) = ilogit(A3*(t-T3)); return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit_2.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit_2.m new file mode 100755 index 00000000..2930b4e7 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit_2.m @@ -0,0 +1 @@ +function [h, base] = get_logit_2(V,t) % % [h] = get_logit(V,t) % % Calculate inverse logit (IL) HRF model % Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates % % INPUT: V, t % t = vector of time points % V = parameters % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % T = t(end); A1 = V(1); T1 = V(2); d1 = V(3); A2 = V(4); T2 = V(5); A3 = V(6); T3 = V(7); d3 = V(8); % d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); % d3 = abs(d2)-abs(d1); m1 = ilogit(A1*(1-T1)); m2 = ilogit(A2*(1-T2)); m3 = ilogit(A3*(1-T3)); w1 = ilogit(A1*(T-T1)); w2 = ilogit(A2*(T-T2)); w3 = ilogit(A3*(T-T3)); d2 = -d1*(m1 - m3*w1/w3)/(m2 - m3*w2/3); h = d1*ilogit(A1*(t-T1))+d2*ilogit(A2*(t-T2))+d3*ilogit(A3*(t-T3)); % Superimpose 3 IL functions h = h'; base =zeros(3,length(t)); base(1,:) = ilogit(A1*(t-T1)); base(2,:) = ilogit(A2*(t-T2)); base(3,:) = ilogit(A3*(t-T3)); return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Inverse_Logit_Tool_Instructions.rtf b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Inverse_Logit_Tool_Instructions.rtf new file mode 100755 index 00000000..790a7dbc --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Inverse_Logit_Tool_Instructions.rtf @@ -0,0 +1,69 @@ +{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf420 +{\fonttbl\f0\fswiss\fcharset77 Helvetica;\f1\fswiss\fcharset77 CenturyGothic-BoldItalic;} +{\colortbl;\red255\green255\blue255;\red0\green6\blue240;} +\margl1440\margr1440\vieww5880\viewh19560\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural + +\f0\fs36 \cf0 Inverse Logit Hemodynamic Response Estimation Toolbox +\fs24 \ +\ + +\f1\i\b\fs28 Version: +\f0\i0\b0\fs24 \ +12/12/06, Last Edit: 12/12/06\ +\ + +\f1\i\b\fs28 Authors: +\f0\i0\b0\fs24 \ +Martin Lindquist, Dept. of Statistics, Columbia University\ +Tor Wager, Dept. of Psychology, Columbia University\ +\ + +\f1\i\b\fs28 Reference: +\f0\i0\b0\fs24 \ +\ +\pard\pardeftab720\sa360\ql\qnatural +{\field{\*\fldinst{HYPERLINK "http://www.columbia.edu/cu/psychology/tor/Papers/Model_hrf-1.doc"}}{\fldrslt \cf0 \ul \ulc2 Lindquist, M. and Wager, T. D.\'ca (in press). Validity and Power in Hemodynamic Response Modeling:\'ca A comparison study and a new approach.\'ca \ulnone Human Brain Mapping (2007); early version available online.}}\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural +\cf0 \ + +\f1\i\b\fs28 Purpose:\ +\ + +\f0\i0\b0\fs24 This toolbox will estimate parameters for a flexible hemodynamic response function (HRF) using three superimposed inverse logit functions. The HRF is fit using seven parameters, and can capture a variety of HRF shapes, including sustained activations. \ +\ +Main inputs include a data timeseries and indicator (stick) function for the onsets of events. +\f1\i\b\fs28 \ +\ + +\f0\i0\b0\fs24 The function can be used with ROI timeseries or incorporated into whole-brain HRF-estimation tools. +\f1\i\b\fs28 \ +\ + +\f0\i0\b0\fs24 \ + +\f1\i\b\fs28 Main Functions:\ + +\f0\i0\b0\fs24 \ +(Specific help for each function is available by typing\ +>> help myfunctionname \ +at the >> matlab prompt.)\ +\ +Example.m\ +-------------------------------------------------------------------------------------------------\ +Example script that loads example timeseries data and fits the model. Plots results.\ +\ +Anneal_Logit.m\ +-------------------------------------------------------------------------------------------------\ +Main function for fitting inverse logit (IL) HRF estimate to timeseries.\ +Simulated annealing is built into function as a solution-finder.\ +\ +Get_Logit.m\ +-------------------------------------------------------------------------------------------------\ +Create HRF estimate from IL parameters\ +\ +cost.m\ +-------------------------------------------------------------------------------------------------\ +Least-squares cost function for the IL model\ +\ +} \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerLoss.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerLoss.m new file mode 100755 index 00000000..53015042 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerLoss.m @@ -0,0 +1,38 @@ +function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) +% function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) +% +% Estimates Power-loss due to mis-modeling. +% +% INPUT: +% +% modres - residuals +% modfit - model fit +% moddf - model degrees of freedom +% tc - time course +% TR - time resolution +% Runs - expermental design +% alpha - alpha value +% +% OUTPUT: +% +% PowLoss - Estimated power loss +% +% + +len = length(tc); % length of time course +T = round(30./TR); % length of estimated HRF +tstar = tinv(1-alpha,moddf); % t-threshold + +% Fit FIR model to find 'baseline' power. +[h, fit, e] = Fit_sFIR(tc,TR,Run,T,1); +s = (1/(len-T))*e'*e; +t = 1/sqrt(s*inv(fit'*fit)); +basePow = 1- nctcdf(tstar,(len-T),t); + +% Compute model power. +sig = (1/moddf)*modres'*modres; +ts = 1/sqrt(sig*inv(modfit'*modfit)); +modPow = 1- nctcdf(tstar,moddf,ts); + +% Compute 'power loss' +PowLoss = basePow - modPow; diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerSim.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerSim.m new file mode 100755 index 00000000..71d9f65c --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerSim.m @@ -0,0 +1,37 @@ +N = 1000; +M = 10; +P = zeros(N,M); +B = zeros(N,M); + +Yind = zeros(60,1); +Yind(21:40) = 1; +X2 = Yind; +sigma = 0.5; +tstar = tinv(1-0.05,59); + +for i=1:M, + for rep = 1:N, + + X = zeros(60,1); + X((20+i):(40+i-1)) = 1; + Y = Yind + normrnd(0,sigma,60,1); + b = pinv(X)*Y; + e = Y-X*b; + sig = sqrt(e'*e/59); + t = b./(sig/(X'*X)); + delta = 1/sig; + Pow = 1- nctcdf(tstar,59,delta); + + b2 = pinv(X2)*Y; + e2 = Y-X2*b2; + sig2 = sqrt(e2'*e2/59); + t2 = b2./(sig2/(X2'*X2)); + delta2 = 1/sig2; + Pow2 = 1- nctcdf(tstar,59,delta2); + + + P(rep,i) = Pow2-Pow; + B(rep,i) = b-1; + + end; +end; diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ResidScan.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ResidScan.m new file mode 100755 index 00000000..ade1c88c --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ResidScan.m @@ -0,0 +1,107 @@ +function [p sres sres_ns] = ResidScan(res, FWHM) +% function [p sres sres_ns] = ResidScan(res, FWHM) +% +% Calculates P(M>=t) where M is the max value of the smoothed residuals. +% In this implementation the residuals are smoothed using a Gaussian +% kernel. +% +% INPUT: +% +% res - residual time course +% FWHM - Full Width Half Maximum (in time units) +% +% OUTPUT: +% +% p - pvalues +% sres - smoothed residuals +% sres_ns - smoothed residuals (non standardized) +% +% By Martin Lindquist & Ji-Meng Loh, July 2007 +% +% Edited by ML on 10/02/09 + +res_ns = res; +res = res./std(res); +len = length(res); + +% Create Gaussian Kernel +sig = ceil(FWHM/(2*sqrt(2*log(2)))); +klen = 3*sig; +kern = normpdf((-klen:klen),0,sig); +kern = kern./sqrt(sum(kern.^2)); + +% Convolve +x = conv(res,kern); +sres = x((klen + 1):(end-klen)); + +x = conv(res_ns,kern/sum(kern)); +sres_ns = x((klen + 1):(end-klen)); + + +% Find Max value +[a,location] = max(abs(sres)); + +% Find p-values using Gaussian Random Field theory +z = Euler_p(1, a, len, FWHM); +z = 2*z; %Two-sided test +p = min(1, z); + +end + +% END MAIN FUNCTION + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Subfunctions +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +function pval = Euler_p(myDim, value, N, fwhm) +% function z = Euler_p(myDim, value, N, fwhm) +% +% Finds the p value using the expected Euler characteristic. +% +% This function returns P(M \ge value) using the approximation +% \sum_{d=0}^D R_d(V) \rho_d(value) following Worsley et al's "A Unified +% Statistical Approach for Determining Significant Signals in Images of +% Cerebral Activation". +% +% INPUTS: +% +% myDim - the number of dimensions in the data +% value - the value of the maximum. +% N - the number of (time) points in that 1 dimension +% fwhm - the full width half maximum +% +% OUTPUTS: +% +% pval - the p-value + +% NOTE: CURRENTLY THIS FUNCTION IS ONLY IMPLEMENTED FOR THE 1D CASE + + % Constants + myfactor = 4*log(2); + pi2 = 2*pi; + exptsq = exp(-(value^2)/2); + + % Euler Characteristc Densties + rho = zeros(5,1); + rho(1) = 1-normcdf(value); + rho(2) = myfactor^(0.5)*exptsq/pi2; + rho(3) = myfactor * exptsq * value / (pi2 ^ (1.5)); + rho(4) = myfactor ^ (1.5) * exptsq * (value^2-1) / (pi2 ^2); + rho(5) = myfactor ^2 * exptsq * (value^3-3*value) / (pi2 ^ (5/2)); + + % Resel Count + R0 = 1; + R1 = N/fwhm; + + % P-value + pval = R0 * rho(1) + R1 * rho(2); + +end + + diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ScanSim4.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ScanSim4.m new file mode 100755 index 00000000..9f8b91b8 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ScanSim4.m @@ -0,0 +1,67 @@ +TR = 0.5; +len = 200; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Compute hrf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t=0.1:TR:30; +a1 =6; +a2 = 12; +b1 = 0.9; +b2 =0.9; +c = 0.35; + +d1 = a1*b1; +d2 = a2*b2; + +h = ((t./d1).^a1).*exp(-(t-d1)./b1) - c*((t./d2).^a2).*exp(-(t-d2)./b2); +h = h./max(h); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% X = zeros(len,4); +% X(:,1) = 1; +% X(:,2) = (1:len)/len; +% X(:,3) = X(:,2).^2; +% +% Run = zeros(40,5); +% Run(1:20,:) = 1; +% Run = reshape(Run,200,1); +% q = conv(Run,h); +% X(:,4) = q(1:len); + + +X = zeros(len,2); +X(:,1) = 1; +Run = zeros(200,1); +Run(50) = 1; +Run(150) = 1; +q = conv(Run,h); +X(:,2) = q(1:len); + + +Run = zeros(200,1); +Run(50) = 1; +Run(160) = 1; +tc = conv(Run,h); +tc = tc(1:len) + normrnd(0,0.3,200,1); + + +beta = pinv(X)*tc; +e = tc-X*beta; +sigma = sqrt((1/(len-size(X,2)))*sum(e.^2)); +%c = [0 0 0 1]; +c = [0 1]; +se = sigma.*sqrt(c*inv(X'*X)*c'); +t = beta/se; +pval = 2*tcdf(-abs(t),len-4); +df = len - 4; + +[z sres sres_ns] = ResidScan(e, 4); +[b bias pl pc pe] = BiasPowerloss(tc, X,c,beta,df,z,0.05); + +beta +b +bias +pl diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/get_parameters2.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/get_parameters2.m new file mode 100755 index 00000000..db26f8d0 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/get_parameters2.m @@ -0,0 +1,43 @@ +function [param] = get_parameters2(hdrf,t) + +% Find model parameters +% +% Height - h +% Time to peak - p (in time units of TR seconds) +% Width (at half peak) - w + + +% Calculate Heights and Time to peak: + +n = t(end)*0.6; + +[h,p] = max(abs(hdrf(1:n))); +h = hdrf(p); + +if (p > t(end)*0.6), warning('Late time to peak'), end; + +if (h >0) + v = (hdrf >= h/2); +else + v = (hdrf <= h/2); +end; + +[a,b] = min(diff(v)); +v(b+1:end) = 0; +w = sum(v); + +cnt = p-1; +g =hdrf(2:end) - hdrf(1:(end-1)); +while((cnt > 0) & (abs(g(cnt)) <0.001)), + h = hdrf(cnt); + p = cnt; + cnt = cnt-1; +end; + + +param = zeros(3,1); +param(1) = h; +param(2) = p; +param(3) = w; + +return; diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ilogit.m b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ilogit.m new file mode 100755 index 00000000..833986a2 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ilogit.m @@ -0,0 +1,13 @@ +function [L] = ilogit(t) +% +% function [L] = ilogit(t) +% +% Calculate the inverse logit function corresponding to the value t +% +% OUTPUT: L = exp(t)./(1+exp(t)); +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% + +L = exp(t)./(1+exp(t)); \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse.mat b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse.mat new file mode 100755 index 00000000..fb694160 Binary files /dev/null and b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse.mat differ diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_2.mat b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_2.mat new file mode 100755 index 00000000..7a1c0700 Binary files /dev/null and b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_2.mat differ diff --git a/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_old.mat b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_old.mat new file mode 100755 index 00000000..fb694160 Binary files /dev/null and b/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_old.mat differ diff --git a/HRF_Est_Toolbox2/Old_stuff/ScanSim4.m b/HRF_Est_Toolbox2/Old_stuff/ScanSim4.m new file mode 100755 index 00000000..9f8b91b8 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/ScanSim4.m @@ -0,0 +1,67 @@ +TR = 0.5; +len = 200; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Compute hrf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t=0.1:TR:30; +a1 =6; +a2 = 12; +b1 = 0.9; +b2 =0.9; +c = 0.35; + +d1 = a1*b1; +d2 = a2*b2; + +h = ((t./d1).^a1).*exp(-(t-d1)./b1) - c*((t./d2).^a2).*exp(-(t-d2)./b2); +h = h./max(h); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% X = zeros(len,4); +% X(:,1) = 1; +% X(:,2) = (1:len)/len; +% X(:,3) = X(:,2).^2; +% +% Run = zeros(40,5); +% Run(1:20,:) = 1; +% Run = reshape(Run,200,1); +% q = conv(Run,h); +% X(:,4) = q(1:len); + + +X = zeros(len,2); +X(:,1) = 1; +Run = zeros(200,1); +Run(50) = 1; +Run(150) = 1; +q = conv(Run,h); +X(:,2) = q(1:len); + + +Run = zeros(200,1); +Run(50) = 1; +Run(160) = 1; +tc = conv(Run,h); +tc = tc(1:len) + normrnd(0,0.3,200,1); + + +beta = pinv(X)*tc; +e = tc-X*beta; +sigma = sqrt((1/(len-size(X,2)))*sum(e.^2)); +%c = [0 0 0 1]; +c = [0 1]; +se = sigma.*sqrt(c*inv(X'*X)*c'); +t = beta/se; +pval = 2*tcdf(-abs(t),len-4); +df = len - 4; + +[z sres sres_ns] = ResidScan(e, 4); +[b bias pl pc pe] = BiasPowerloss(tc, X,c,beta,df,z,0.05); + +beta +b +bias +pl diff --git a/HRF_Est_Toolbox2/Old_stuff/cost.m b/HRF_Est_Toolbox2/Old_stuff/cost.m new file mode 100755 index 00000000..589007e6 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/cost.m @@ -0,0 +1 @@ +function Q = cost(V,t,tc,Run) % % Least-squares cost function for the IL model % % INPUT: % Run = stick function % tc = time course % t = vector of time points % V = parameters % % OUTPUT: % Q = cost % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % len = length(Run); h1 = Get_Logit(V(1:7),t); % Get IL model corresponding to parameters V yhat = conv(Run, h1); % Convolve IL model with stick function yhat = yhat(1:len); Q = sum((yhat-tc).^2); % Calculate cost function return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/cost2.m b/HRF_Est_Toolbox2/Old_stuff/cost2.m new file mode 100755 index 00000000..0aa67b4c --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/cost2.m @@ -0,0 +1 @@ +function Q = cost2(V,t,tc,RunA,RunB) % % inverse logit -- creates fitted curve from parameter estimates % % t = vector of time points % V = parameters % % 3 logistic functions to be summed together % % h1 = get_logit(V(1:7),t); % h2 = get_logit(V(8:14),t); % h1 = get_logit(V(1:8),t); % h2 = get_logit(V(9:16),t); type = 7; if (type == 7), h1 = get_logit(V(1:7),t); h2 = get_logit(V(8:14),t); elseif(type == 9), h1 = get_logit(V(1:9),t); h2 = get_logit(V(10:18),t); end; len = length(RunA); tcA = conv(RunA, h1); tcA = tcA(1:len); len = length(RunB); tcB = conv(RunB, h2); tcB = tcB(1:len); yhat = tcA + tcB; Q = sum((yhat-tc).^2); return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/cost_allstim.m b/HRF_Est_Toolbox2/Old_stuff/cost_allstim.m new file mode 100755 index 00000000..7c743b31 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/cost_allstim.m @@ -0,0 +1,33 @@ +function Q = cost_allstim(V,t,tc,Run) +% +% Least-squares cost function for the IL model +% +% INPUT: +% Run = stick function +% tc = time course +% t = vector of time points +% V = parameters +% +% OUTPUT: +% Q = cost +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% Further edited by Christian Waugh 2/15/08 to include multiple trialtypes + +numstim = length(Run); +len = length(Run{1}); +h = zeros(length(t),numstim); +yhatt =zeros(len,numstim); + +for k = 1:numstim + h(:,k) = Get_Logit(V(k*7-6:k*7),t); % Get IL model corresponding to parameters V + yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function + yhatt(:,k) = yhat(1:len,k); +end + +yhat2 = sum(yhatt,2); %Sum models together to get overall estimate + +Q = sum((yhat2-tc).^2); % Calculate cost function + +return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/cost_allstim_2.m b/HRF_Est_Toolbox2/Old_stuff/cost_allstim_2.m new file mode 100755 index 00000000..83cdadd5 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/cost_allstim_2.m @@ -0,0 +1,43 @@ +function Q = cost_allstim_2(V,tr,tc,Run,down) +% +% Least-squares cost function for the IL model +% Multi-condition case +% +% INPUT: +% down = downsampling factor +% Run = stick function +% tc = time course +% tr = repetition time +% V = parameters +% +% OUTPUT: +% Q = cost +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% Further edited by Christian Waugh 2/15/08 to include multiple trialtypes +% Edited by ML on 02/12/13 + +t = 0:(1/down):30; + +numstim = length(Run); +len = length(Run{1}); +h = zeros(length(t),numstim); +yhatt =zeros(len,numstim); + + +for k = 1:numstim + + h(:,k) = Get_Logit(V(k*7-6:k*7),t); % Get IL model corresponding to parameters V + yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function + yhatt(:,k) = yhat(1:len,k); + +end + +tt = 1:(tr*down):len; + +yhat2 = sum(yhatt,2); %Sum models together to get overall estimate + +Q = sum((yhat2(tt)-tc).^2); % Calculate cost function + +return \ No newline at end of file diff --git a/HRF_Est_Toolbox2/Old_stuff/dilogit_dt2.m b/HRF_Est_Toolbox2/Old_stuff/dilogit_dt2.m new file mode 100755 index 00000000..b9b12d41 --- /dev/null +++ b/HRF_Est_Toolbox2/Old_stuff/dilogit_dt2.m @@ -0,0 +1,35 @@ +function [dLdt, d2Ldt2, d1, d2] = dilogit_dt2(t,V) +% +% [dLdt, d2Ldt2, d1, d2] = dilogit_dt2(t,V) +% +% Calculate first and second temoral derivative of inverse logit (IL) HRF model +% curve +% +% INPUT: V, t +% t = vector of time points +% V = parameters +% +% OUTPUT: dLdt, d2Ldt2 +% dLdt = first temporal derivative of IL model +% d2Ldt2 = second temporal derivative of IL model +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% + +% Set parameter values +A1 = V(1); +T1 = V(2); +d1 = V(3); +A2 = V(4); +T2 = V(5); +A3 = V(6); +T3 = V(7); +d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); +d3 = abs(d2)-abs(d1); + +% Calculate the first and second derivative +dLdt = d1*A1*ilogit(A1*(t-T1))./(1+exp(A1*(t-T1))) + d2*A2*ilogit(A2*(t-T2))./(1+exp(A2*(t-T2))) + d3*A3*ilogit(A3*(t-T3))./(1+exp(A3*(t-T3))); +d2Ldt2 = d1*(A1^2)*(exp(A1*(t-T1)) - exp(2*A1*(t-T1)))./((1+exp(A1*(t-T1))).^3) + d2*(A2^2)*(exp(A2*(t-T2)) - exp(2*A2*(t-T2)))./((1+exp(A2*(t-T2))).^3) + d3*(A3^2)*(exp(A3*(t-T3)) - exp(2*A3*(t-T3)))./((1+exp(A3*(t-T3))).^3); + +return; \ No newline at end of file diff --git a/HRF_Est_Toolbox2/PowerLoss.m b/HRF_Est_Toolbox2/PowerLoss.m new file mode 100755 index 00000000..56800778 --- /dev/null +++ b/HRF_Est_Toolbox2/PowerLoss.m @@ -0,0 +1,39 @@ +function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) +% function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) +% +% Estimates Power-loss due to mis-modeling. +% +% INPUT: +% +% modres - residuals +% modfit - model fit +% moddf - model degrees of freedom +% tc - time course +% TR - time resolution +% Runs - expermental design +% alpha - alpha value +% +% OUTPUT: +% +% PowLoss - Estimated power loss +% +% + +len = length(tc); % length of time course +%T = round(30./TR); % length of estimated HRF +T = 30; +tstar = tinv(1-alpha,moddf); % t-threshold + +% Fit FIR model to find 'baseline' power. +[h, fit, e] = Fit_sFIR(tc,TR,Run,T,1); +s = (1/(len-T))*e'*e; +t = 1/sqrt(s*inv(fit'*fit)); +basePow = 1- nctcdf(tstar,(len-T),t); + +% Compute model power. +sig = (1/moddf)*modres'*modres; +ts = 1/sqrt(sig*inv(modfit'*modfit)); +modPow = 1- nctcdf(tstar,moddf,ts); + +% Compute 'power loss' +PowLoss = basePow - modPow; diff --git a/HRF_Est_Toolbox2/PowerSim.m b/HRF_Est_Toolbox2/PowerSim.m new file mode 100755 index 00000000..71d9f65c --- /dev/null +++ b/HRF_Est_Toolbox2/PowerSim.m @@ -0,0 +1,37 @@ +N = 1000; +M = 10; +P = zeros(N,M); +B = zeros(N,M); + +Yind = zeros(60,1); +Yind(21:40) = 1; +X2 = Yind; +sigma = 0.5; +tstar = tinv(1-0.05,59); + +for i=1:M, + for rep = 1:N, + + X = zeros(60,1); + X((20+i):(40+i-1)) = 1; + Y = Yind + normrnd(0,sigma,60,1); + b = pinv(X)*Y; + e = Y-X*b; + sig = sqrt(e'*e/59); + t = b./(sig/(X'*X)); + delta = 1/sig; + Pow = 1- nctcdf(tstar,59,delta); + + b2 = pinv(X2)*Y; + e2 = Y-X2*b2; + sig2 = sqrt(e2'*e2/59); + t2 = b2./(sig2/(X2'*X2)); + delta2 = 1/sig2; + Pow2 = 1- nctcdf(tstar,59,delta2); + + + P(rep,i) = Pow2-Pow; + B(rep,i) = b-1; + + end; +end; diff --git a/HRF_Est_Toolbox2/README.pptx b/HRF_Est_Toolbox2/README.pptx new file mode 100644 index 00000000..5b106e7c Binary files /dev/null and b/HRF_Est_Toolbox2/README.pptx differ diff --git a/HRF_Est_Toolbox2/ResidScan.m b/HRF_Est_Toolbox2/ResidScan.m new file mode 100755 index 00000000..ade1c88c --- /dev/null +++ b/HRF_Est_Toolbox2/ResidScan.m @@ -0,0 +1,107 @@ +function [p sres sres_ns] = ResidScan(res, FWHM) +% function [p sres sres_ns] = ResidScan(res, FWHM) +% +% Calculates P(M>=t) where M is the max value of the smoothed residuals. +% In this implementation the residuals are smoothed using a Gaussian +% kernel. +% +% INPUT: +% +% res - residual time course +% FWHM - Full Width Half Maximum (in time units) +% +% OUTPUT: +% +% p - pvalues +% sres - smoothed residuals +% sres_ns - smoothed residuals (non standardized) +% +% By Martin Lindquist & Ji-Meng Loh, July 2007 +% +% Edited by ML on 10/02/09 + +res_ns = res; +res = res./std(res); +len = length(res); + +% Create Gaussian Kernel +sig = ceil(FWHM/(2*sqrt(2*log(2)))); +klen = 3*sig; +kern = normpdf((-klen:klen),0,sig); +kern = kern./sqrt(sum(kern.^2)); + +% Convolve +x = conv(res,kern); +sres = x((klen + 1):(end-klen)); + +x = conv(res_ns,kern/sum(kern)); +sres_ns = x((klen + 1):(end-klen)); + + +% Find Max value +[a,location] = max(abs(sres)); + +% Find p-values using Gaussian Random Field theory +z = Euler_p(1, a, len, FWHM); +z = 2*z; %Two-sided test +p = min(1, z); + +end + +% END MAIN FUNCTION + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Subfunctions +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +function pval = Euler_p(myDim, value, N, fwhm) +% function z = Euler_p(myDim, value, N, fwhm) +% +% Finds the p value using the expected Euler characteristic. +% +% This function returns P(M \ge value) using the approximation +% \sum_{d=0}^D R_d(V) \rho_d(value) following Worsley et al's "A Unified +% Statistical Approach for Determining Significant Signals in Images of +% Cerebral Activation". +% +% INPUTS: +% +% myDim - the number of dimensions in the data +% value - the value of the maximum. +% N - the number of (time) points in that 1 dimension +% fwhm - the full width half maximum +% +% OUTPUTS: +% +% pval - the p-value + +% NOTE: CURRENTLY THIS FUNCTION IS ONLY IMPLEMENTED FOR THE 1D CASE + + % Constants + myfactor = 4*log(2); + pi2 = 2*pi; + exptsq = exp(-(value^2)/2); + + % Euler Characteristc Densties + rho = zeros(5,1); + rho(1) = 1-normcdf(value); + rho(2) = myfactor^(0.5)*exptsq/pi2; + rho(3) = myfactor * exptsq * value / (pi2 ^ (1.5)); + rho(4) = myfactor ^ (1.5) * exptsq * (value^2-1) / (pi2 ^2); + rho(5) = myfactor ^2 * exptsq * (value^3-3*value) / (pi2 ^ (5/2)); + + % Resel Count + R0 = 1; + R1 = N/fwhm; + + % P-value + pval = R0 * rho(1) + R1 * rho(2); + +end + + diff --git a/HRF_Est_Toolbox2/get_parameters2.m b/HRF_Est_Toolbox2/get_parameters2.m new file mode 100755 index 00000000..5e85894e --- /dev/null +++ b/HRF_Est_Toolbox2/get_parameters2.m @@ -0,0 +1,46 @@ +function [param] = get_parameters2(hdrf,t) +% +% Find model parameters +% +% Height - h +% Time to peak - p (in time units of TR seconds) +% Width (at half peak) - w + + +% Calculate Heights and Time to peak: + +% delta = 1/(t(2)-t(1)); +% n = round(t(end)*0.6*delta) +n = round(length(t)*0.8); + +[~,p] = max(abs(hdrf(1:n))); +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 (h >0) + v = (hdrf >= h/2); +else + v = (hdrf <= h/2); +end; + +[~,b] = min(diff(v)); +v(b+1:end) = 0; +w = sum(v); + +cnt = p-1; +g =hdrf(2:end) - hdrf(1:(end-1)); +while((cnt > 0) && (abs(g(cnt)) <0.001)), + h = hdrf(cnt); + p = cnt; + cnt = cnt-1; +end; + + +param = zeros(3,1); +param(1) = h; +param(2) = p; +param(3) = w; + +return; diff --git a/HRF_Est_Toolbox2/hrf_fit_one_voxel.m b/HRF_Est_Toolbox2/hrf_fit_one_voxel.m new file mode 100644 index 00000000..c031d98a --- /dev/null +++ b/HRF_Est_Toolbox2/hrf_fit_one_voxel.m @@ -0,0 +1,82 @@ +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,type,mode) +% +% HRF estimation function for a single voxel; +% +% Implemented methods include: IL-model (Deterministic/Stochastic), FIR +% (Regular/Smooth), and HRF (Canonical/+ temporal/+ temporal & dispersion) +% +% INPUTS: +% +% tc - time course +% TR - time resolution +% Runs - expermental design +% T - length of estimated HRF +% type - Model type +% mode - Mode +% +% Options: p=1 - only canonical HRF +% p=2 - canonical + temporal derivative +% p=3 - canonical + time and dispersion derivative +% +% OUTPUTS: +% +% hrf - estimated hemodynamic response function +% fit - estimated time course +% e - residual time course +% param - estimated amplitude, height and width +% +% Created by Martin Lindquist on 04/11/14 + + +if (strcmp(method,'IL')), + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using IL-function + +% Choose mode (deterministic/stochastic) + +% mode = 0; % 0 - deterministic aproach + % 1 - simulated annealing approach + % 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); + param(2:3,:) = param(2:3,:).*TR; + + +elseif (strcmp(method,'FIR')), + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using FIR-model + +% Choose mode (FIR/sFIR) + +% mode = 1; % 0 - FIR + % 1 - smooth FIR + + [h, fit, e, param] = Fit_sFIR(tc,TR,Runc,T,mode); + param(2:3,:) = param(2:3,:).*TR; + + +elseif (strcmp(method,'CHRF')), + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Fit HRF using Canonical HRF + 2 derivatives + +% mode = 0; % 0 - HRF + % 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); + param(2:3,:) = param(2:3,:).*TR; + +else + warning('Incorrect Model Type. Use IL, FIR or CHRF'); +end + + + +end % function + diff --git a/HRF_Est_Toolbox2/ilogit.m b/HRF_Est_Toolbox2/ilogit.m new file mode 100755 index 00000000..833986a2 --- /dev/null +++ b/HRF_Est_Toolbox2/ilogit.m @@ -0,0 +1,13 @@ +function [L] = ilogit(t) +% +% function [L] = ilogit(t) +% +% Calculate the inverse logit function corresponding to the value t +% +% OUTPUT: L = exp(t)./(1+exp(t)); +% +% By Martin Lindquist and Tor Wager +% Edited 12/12/06 +% + +L = exp(t)./(1+exp(t)); \ No newline at end of file diff --git a/HRF_Est_Toolbox2/timecourse.mat b/HRF_Est_Toolbox2/timecourse.mat new file mode 100644 index 00000000..fb694160 Binary files /dev/null and b/HRF_Est_Toolbox2/timecourse.mat differ diff --git a/Image_computation_tools/apply_derivative_boost.m b/Image_computation_tools/apply_derivative_boost.m new file mode 100644 index 00000000..2d2786e2 --- /dev/null +++ b/Image_computation_tools/apply_derivative_boost.m @@ -0,0 +1,599 @@ +% function apply_derivative_boost(varargin) +% +% allows for recalculation of amplitude images from the fitted responses +% for each trial type. Necessary for calculating group statistics when +% using multiple basis functions +% +% NOTES: +% DB estimation only works for 2 specific basis sets: +% timeonly, with 2 parameters (canonical + time derivative) +% timedispersion,' with 3 parameters (canonical hrf + temporal and spatial dispersion) +% +% This function loads the SPM.mat file in the current directory and uses +% the basis set specified in the loaded SPM structure. +% +% INPUTS +% ------------------------------------------------------------------------- +% Optional inputs: You must enter something here to specify what the function should do: +% ------------------------------------------------------------------------- +% 'amplitudes' will only create amping images (combination of betas +% across basis functions) +% +% 'contrasts' assumes that amping images are already created; will only +% create contrast images +% +% 'all' will run both the amplitudes and contrasts sections +% +% In addition, 'amplitudes' now has two separate parts: +% - The first uses Vince Calhoun's derivative boost (Calhoun, 2004) to +% estimate amplitudes. NOTE: *We have not worked out the scaling yet, so +% I'm not sure this is working right* +% To turn this OFF, enter 'nodb' as an optional argument +% +% - The second way uses our HTW code to estimate height, time to peak, +% width, and area under the curve (see Lindquist & Wager 2007 for +% simulations using a version of this code). +% It requires SCANlab specific functions, in SCN_Core_Support +% (unlike the deriv. boost). +% To turn this OFF, enter 'nohtw' as an optional argument +% +% 'startend', followed by starting and ending values in seconds for amplitude estimation window (for HTW estimation only). +% If you do not enter this, it will show you a plot and ask you to pick +% these values. +% If you enter them here as inputs, you can skip the interactive step and +% loop over subjects. +% +% 'condition_numbers', followed by which index numbers in your list should +% be used to calculate h, t, w from. You should use this if you are +% entering regressors of no interest, besides the intercepts. +% +% ------------------------------------------------------------------------- +% +% IMPORTANT FOR CONTRASTS: +% disp('Using contrasts entered as F-contrasts. Assuming the first contrast vector in each F-contrast ' +% disp('is a contrast of interest across the CANONICAL basis function regressors.') +% +% RUN THIS IN COMMAND WINDOW TO BATCH +% subj = dir('06*') +% for i = 1:length(subj), cd(subj(i).name), apply_derivative_boost, cd('..'); end +% +% ANOTHER BATCH EXAMPLE: +% d = dir('remi*'); d = d(cat(2, d.isdir)); [mydirs{1:length(d)}] = deal(d.name) +% for i = 1:length(mydirs), cd(mydirs{i}), apply_derivative_boost('all', 'nodb', 'startend', [4 15]), cd('..'); end +% +% An example for an event-related design, specifying condition numbers to get HTW from: +% apply_derivative_boost('all', 'nodb', 'contrasts', 'condition_numbers', 1:14, 'startend', [4 10]); +% +% EXAMPLE: CALCULATE CONTRASTS ONLY ON ALREADY-ESTIMATED HTW IMAGES +% apply_derivative_boost('contrasts','condition_numbers',1:14); + +function apply_derivative_boost(varargin) + + % --------------------------------------------- + % --------------------------------------------- + + % SETUP INPUTS + + % --------------------------------------------- + % --------------------------------------------- + + spmname = fullfile(pwd, 'SPM.mat'); + if ~exist(spmname, 'file'), error('You must be in an SPM 1st-level results directory with SPM5 SPM.mat file.'); end + + load(spmname); + + nbf = size(SPM.xBF.bf, 2); + + switch nbf + case 2 + derivative_case = 'timeonly'; + case 3 + derivative_case = 'timedispersion'; + + otherwise + warning('Deriv. Boost only works for SPM canonical hrf with time or time + dispersion derivatives. This SPM.mat doesn''t match those specs.'); + end + + % Not used; only for image eval function + % boost = @(b) sign(b(1)) .* sqrt(sum(b .^ 2)); + + + docontrasts = 0; + doamps = 0; + nodb = 0; + nohtw = 0; + condition_numbers = []; + do_downsample = []; % downsample; default = 1 sec if units are seconds (1/dt) + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'all', docontrasts = 1; doamps = 1; + + case 'amplitudes', doamps = 1; + + case 'contrasts', docontrasts = 1; + + case 'nodb', nodb = 1; % skip DB estimation + + case 'nohtw', nohtw = 1; % skip HTW estimation + + case 'startend', startend = varargin{i + 1}; % starting and ending values in seconds for amplitude estimation window (for HTW estimation only). + + case 'condition_numbers', condition_numbers = varargin{i + 1}; + + case 'nodownsample', do_downsample = 0; + + case 'downsample', do_downsample = varargin{i + 1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if isempty(do_downsample) + % default downsampling + do_downsample = round(1 ./ SPM.xBF.dt); + end + + if ~(docontrasts || doamps) + disp('Nothing to do! Enter ''contrasts'' ''amplitudes'' or ''all'' as input argument.'); + return + end + + + if doamps + % --------------------------------------------- + % --------------------------------------------- + + % ESTIMATE AMPLITUDES + + % --------------------------------------------- + % --------------------------------------------- + + + % --------------------------------------------- + % FILE NAMES + % --------------------------------------------- + + imgs = dir('beta*img'); imgs = char(imgs.name); + n = size(imgs, 1); + + fprintf('Found %3.0f images', n); fprintf('\n'); + + %load(spmname); + nsess = length(SPM.Sess); + fprintf('I think there are %3.0f sessions (runs)', nsess); fprintf('\n'); + + if ~isempty(condition_numbers) + wh_intercept = true(1, size(imgs, 1)); % exclude these + wh_intercept(condition_numbers) = 0; + wh_intercept = find(wh_intercept); + + fprintf('\nIncluding only these images: '); + fprintf('%3.0f ', condition_numbers); + fprintf('\n'); + + else + wh_intercept = SPM.xX.iB; + fprintf('\nI think these images are intercepts, and am not using them: '); + fprintf('%3.0f ', wh_intercept); + fprintf('\n'); + end + + imgs(wh_intercept, :) = []; + + %not used; only when using image_eval_function + %mask_img = './mask.img'; + + n = size(imgs, 1); + + + if n / nbf ~= round(n / nbf), error('Error! Wrong number of images for the specified number of basis functions.'); end + + + + if nodb + % skip Derivative Boost estimation and go straight to HTW + else + + % --------------------------------------------- + % CALCULATE + % --------------------------------------------- + cond_indx = 1; + + for i = 1: nbf : (n - nbf + 1) + + imgs_cond = imgs(i : i+nbf - 1, :); + + disp('Working on :') + disp(imgs_cond) + + out_name = sprintf('db_amplitude_%03d.img', cond_indx); + + % This code uses SCN lab tools to create images + % --------------------------------------------- + % % y = image_eval_function(imgs_cond, boost, 'mask', mask_img, ... + % % 'outimagelabels', {out_name}); + % --------------------------------------------- + + % This code uses SPM instead + % --------------------------------------------- + switch derivative_case + case 'timeonly' + spm_imcalc_ui(imgs_cond, out_name, 'sign(i1) .* sqrt(i1.^2 + i2.^2)'); + case 'timedispersion' + spm_imcalc_ui(imgs_cond, out_name, 'sign(i1) .* sqrt(i1.^2 + i2.^2 + i3.^2)'); + otherwise + error('Basis set is incompatible with DB estimation!'); + end + + fprintf('Created %s\n', out_name); + % --------------------------------------------- + + cond_indx = cond_indx + 1; + end + + % Get and save names + db_amp_names = []; + for i = 1:nsess + sessnames = char(SPM.Sess(i).Fc.name); + sessnames = [repmat(sprintf('Sess%02d_', i), size(sessnames, 1), 1) sessnames]; + db_amp_names = strvcat(db_amp_names, sessnames); + end + + save db_amplitude_names db_amp_names + disp(db_amp_names); + disp(' ') + disp('Saved DB amplitude condition names for each image in db_amplitude_names.mat'); + + fprintf('\n*-----------------------------*\nApplied DB successfully\n*-----------------------------*\n') + + end + + if nohtw + % skip this + + else + + % --------------------------------------------- + % Estimated amplitude from fit: HTW + % --------------------------------------------- + % This code uses SCN lab tools to create images + + disp(' ') + disp('Next: Estimating amplitude, time to peak, width, and area-under-curve images from fitted response using SCN lab code.') + disp(' ') + + % downsample bf, if requested + if do_downsample + mytimeres = SPM.xBF.dt * do_downsample; + SPM.xBF.bf = downsample(SPM.xBF.bf, do_downsample); + + else + mytimeres = SPM.xBF.dt; + end + + if exist('startend', 'var') + % just check, and use input values + if length(startend) ~= 2, error('Startend input must have two values, a starting and ending value in seconds for the amp. estimate window'), end + else + % Set range in sec + htw_from_fit(SPM.xBF.bf, ones(size(SPM.xBF.bf, 2), 1), mytimeres, 'plot', 'verbose'); + + disp(' ') + disp('Enter the range in seconds within which to estimate peak amplitude.') + disp('Example: type [2.5 9] and press return for a typical event-related setup.'); + disp('More sustained responses, like pain responses, may require a longer window.'); + disp('This estimates the amplitude of the IMPULSE RESPONSE, before convolution with the stimulus function') + disp('so if you have an epoch design, a typical window of [2.5 9] sec is still appropriate.'); + disp('Also note: AUC images are calculated as the area under the curve within the window you specify.') + disp(' ') + + startend = input('Enter your choice in [ ] and press return: '); + end + + % Test your choice by showing you a plot + htwfunction = @(b) htw_from_fit(SPM.xBF.bf, b, mytimeres, 'startval', startend(1), 'endval', startend(2), 'plot', 'verbose'); + htwfunction(ones(size(SPM.xBF.bf, 2), 1)) + drawnow + + % Create without plot option for loop through brain. + htwfunction = @(b) htw_from_fit(SPM.xBF.bf, b, mytimeres, 'startval', startend(1), 'endval', startend(2)); + + disp('Check the screen for a plot of your choice of window.') + disp(' ') + + + % --------------------------------------------- + % CALCULATE + % --------------------------------------------- + cond_indx = 1; + + for i = 1: nbf : (n - nbf + 1) + + imgs_cond = imgs(i : i+nbf - 1, :); + + disp('Working on :') + disp(imgs_cond) + + clear out_name + out_name{1} = sprintf('htw_amplitude_%03d.img', cond_indx); + out_name{2} = sprintf('htw_time_to_peak_%03d.img', cond_indx); + out_name{3} = sprintf('htw_width_%03d.img', cond_indx); + out_name{4} = sprintf('htw_area_under_curve_%03d.img', cond_indx); + + % --------------------------------------------- + [h, t, w, auc] = image_eval_function(imgs_cond, htwfunction, 'mask', 'mask.img', ... + 'outimagelabels', out_name); + + h;t;w;auc; % we need the outputs above to tell it to write 4 images. + % --------------------------------------------- + + cond_indx = cond_indx + 1; + end + + % Get and save names + htw_amp_names = []; + for i = 1:nsess + sessnames = char(SPM.Sess(i).Fc.name); + sessnames = [repmat(sprintf('Sess%02d_', i), size(sessnames, 1), 1) sessnames]; + htw_amp_names = strvcat(htw_amp_names, sessnames); + end + + if ~exist(fullfile(pwd, 'db_amplitude_names.mat'), 'file') + save db_amplitude_names htw_amp_names + else + save db_amplitude_names -append htw_amp_names + end + disp(htw_amp_names); + disp(' ') + disp('Saved HTW amplitude condition names for each image in db_amplitude_names.mat'); + + fprintf('\n*-----------------------------*\nApplied HTW estimation successfully\n*-----------------------------*\n') + + end + + end % amplitudes + + + + % --------------------------------------------- + % --------------------------------------------- + + % CREATE CONTRAST FOR THIS SUBJECT + + % --------------------------------------------- + % --------------------------------------------- + + if docontrasts + + % Load contrast vectors + + % ----------------------------------------- + %load(spmname); + % nsess = length(SPM.Sess); + + % Define which indices to exclude from contrasts + if ~isempty(condition_numbers) + nconvals = length(SPM.xCon(1).c(:, 1)); % test contrast + wh_intercept = true(1, nconvals); % exclude these + wh_intercept(condition_numbers) = 0; + wh_intercept = find(wh_intercept); + + fprintf('\nIncluding only these images: '); + fprintf('%3.0f ', condition_numbers); + fprintf('\n'); + + else + wh_intercept = SPM.xX.iB; + fprintf('\nI think these images are intercepts, and am not using them: '); + fprintf('%3.0f ', wh_intercept); + fprintf('\n'); + end + + + if ~isfield(SPM, 'xCon') + error('Enter F-contrasts first, with the first contrast vector in each F-contrast the contrast across the CANONICAL basis function.'); + else + disp('Using contrasts entered as F-contrasts. Assuming the first contrast vector in each F-contrast ') + disp('is a contrast of interest across the CANONICAL basis function regressors.') + end + + wh_F = strmatch('F', char(SPM.xCon.STAT), 'exact'); + + if isempty(wh_F), error('No F-contrasts entered yet.'); end + + % All sets of contrast images + % ------------------------------------------ + ampimgs_name = 'db_amplitude_*img'; + + ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); + + if ~isempty(ampimgs) + contrast_image_names_dbamp = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf); + else + disp(['Checked for but did not find: ']); + end + + + + + % HTW amplitude + % ------------------------------------------ + ampimgs_name = 'htw_amplitude_*img'; + + ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); + + if ~isempty(ampimgs) + contrast_image_names_htwamp = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf); + else + disp(['Checked for but did not find: ' ampimgs_name]); + end + + + % HTW time + % ------------------------------------------ + ampimgs_name = 'htw_time_to_peak_*img'; + + ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); + + if ~isempty(ampimgs) + contrast_image_names_htwtime = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf); + else + disp(['Checked for but did not find: ' ampimgs_name]); + end + + + % HTW width + % ------------------------------------------ + ampimgs_name = 'htw_width_*img'; + + ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); + + if ~isempty(ampimgs) + contrast_image_names_htwwid = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf); + else + disp(['Checked for but did not find: ' ampimgs_name]); + end + + + % HTW area + % ------------------------------------------ + ampimgs_name = 'htw_area_under_curve_*img'; + + ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); + + if ~isempty(ampimgs) + contrast_image_names_htwarea = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf); + else + disp(['Checked for but did not find: ' ampimgs_name]); + end + + check_it = whos('contrast_image_names*'); + + if isempty(check_it) + disp('No valid images found to create contrasts on'); + + else + save('db_amplitude_names', '-append', 'contrast_image_names*'); + disp('Saved lists of contrast image names in db_amplitude_names.mat'); + end + + end + + + + + %% INLINE + + + +end % main function + + + + + + + +function contrast_image_names = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf) + + + n = size(ampimgs, 1); + + fprintf('Found %3.0f images:\n', n); + disp(ampimgs) + + disp('Reading image data.') + V = spm_vol(ampimgs); + vols = spm_read_vols(V); + + % spm_check_registration(ampimgs); + % colormap jet + + contrast_image_names = []; + + for i = 1:length(wh_F) + + + name = SPM.xCon(wh_F(i)).name; + disp(['Calculating contrast on: ' name]) + original_name = name; + + name = deblank(name); + wh_bad = (name == ' ' | name == ',' | name == '.' | name == '^' | name == '~' | name == '''' | name == ':' | name == '*' | name == '%' | name == ';' | name == '@' | name == '&'); + name(wh_bad) = []; + + name = ['con_' ampimgs_name(1:8) '_' name '.img']; + + c = SPM.xCon(wh_F(i)).c(:, 1); + c(wh_intercept) = []; + c = c(1 : nbf : end); + + if length(c) ~= n, error('Contrast is wrong length for some reason! Coding error in this function? Or wrong number of db_amplitude images.'); end + + fprintf('Contrast values: ') + fprintf('%01d ', c) + fprintf('\n') + + % calculate and save + + contrast_image_calc(name, c, vols, V, original_name) + + disp(['Written: ' name]); + + contrast_image_names = strvcat(contrast_image_names, name); + + disp(' '); + + end + + fprintf('\n*-----------------------------*\nContrasts Done successfully!\n*-----------------------------*\n') + spm_check_registration(contrast_image_names); + +end + + + + + +function contrast_image_calc(Q, myc, vols, V, original_name) + + % FROM: + % function contrast_image(Q, myc) + % + % Tor Wager + % + % Creates a contrast image called Q (do not include path) + % Given a list of img files P (spm format, with path) + % and a contrast vector myc + % In the directory of 1st image in P. + + + if ~(length(myc) == size(vols,4)) + error('Contrast vector length is not equal to number of image files.') + end + + myc2 = zeros(size(vols)); + + for i = 1:length(myc) + myc2(:,:,:,i) = myc(i); + end + + cvol = vols .* myc2; + cvol = sum(cvol,4); + + % ------------------------- + % write + % ------------------------- + dd = fileparts(V(1).fname); + Q = fullfile(dd, Q); + Vo = V(1); + Vo.fname = Q; + Vo.descrip = ['Contrast ' original_name]; + + spm_write_vol(Vo,cvol); + +end diff --git a/Image_computation_tools/canlab_create_wm_ventricle_masks.m b/Image_computation_tools/canlab_create_wm_ventricle_masks.m new file mode 100644 index 00000000..910c6a73 --- /dev/null +++ b/Image_computation_tools/canlab_create_wm_ventricle_masks.m @@ -0,0 +1,93 @@ +function canlab_create_wm_ventricle_masks(wm_mask, gm_mask, varargin) + +% function canlab_create_wm_ventricle_masks(wm_mask, gm_mask) +% This function saves white matter and ventricle masks. +% output: "white_matter.img" and "ventricles.img" in the same folder of +% the input structural files +% +% input +% wm_mask: white matter structural image file +% eg) wm_mask = filenames('Structural/SPGR/wc2*.nii', 'char', 'absolute'); +% gm_mask: gray matter structural image file +% eg) gm_mask = filenames('Structural/SPGR/wc1*.nii', 'char', 'absolute'); +% optional: if 1st varargin is a number, how liberal or conserative to be in estimating ventricles. 0 +% is most conservative and will yield no ventricles, 1 is very liberal. If +% not passed in, default of .4 used. + +% 5/4/2012 by Tor Wager and Wani Woo +% 7/16/2014 creation of ventricle mask updated by Yoni Ashar + +canonvent_mask = which('canonical_ventricles.img'); +bstem = which('spm2_brainstem.img'); +canonical_wm = which('white.nii'); + +if isempty(canonvent_mask) || isempty(bstem) || isempty(canonical_wm) + error('If you want to use this function, you need ''canonical_ventricles.img'', ''spm2_brainstem.img'', and ''white.nii'' in your path.'); +end + +if ~exist(wm_mask, 'file') || ~exist(gm_mask, 'file') + error('Mask files passed in as parameters do not exist.') +end + +vent_thr = .4; +if nargin>0 && isnumeric(varargin{1}) + vent_thr = varargin{1}; +end + +%% WHITE MATTER + +wm = statistic_image('image_names', wm_mask); +wm = threshold(wm, [.99 1.1], 'raw-between'); +%orthviews(wm) + +% mask with canonical +canonwm = statistic_image('image_names', canonical_wm); +canonwm = threshold(canonwm, [.5 1.1], 'raw-between'); + +wm = apply_mask(wm, canonwm); +%% + +bstem = statistic_image('image_names', bstem); +bstem = resample_space(bstem, wm, 'nearest'); + +wm = replace_empty(wm); +bstem = replace_empty(bstem); +% remove brainstem voxels +wm = remove_empty(wm, logical(bstem.dat)); + +% write +d = fileparts(wm.fullpath); +wm.fullpath = fullfile(d, 'white_matter.img'); +write(wm) + + +%% VENTRICLE + + +% find voxels in canonical brain and in canonical ventricles, +% but not in WM or gray matter + +gm = statistic_image('image_names', gm_mask); +gm = threshold(gm, [vent_thr 1.1], 'raw-between'); + +wm = statistic_image('image_names', wm_mask); +wm = threshold(wm, [vent_thr 1.1], 'raw-between'); + +% before reconstructing, "manually" enforce the threshold +gm.dat = gm.dat .* gm.sig; +wm.dat = wm.dat .* wm.sig; + +wm_r = reconstruct_image(wm); +gm_r = reconstruct_image(gm); + +in_neither = ~(gm_r | wm_r); +vent = rebuild_volinfo_from_dat(image_vector('image_names', wm_mask), in_neither(:)); % wm_mask just provides image space + +% now only take part of image in the canonical ventricles area +vent = apply_mask(vent, statistic_image('image_names', canonvent_mask)); + +% write +vent.fullpath = fullfile(fileparts(wm.fullpath), 'ventricles.img'); +write(vent, 'mni') + +return diff --git a/Image_computation_tools/derivative_boost_cheat_sheet.docx b/Image_computation_tools/derivative_boost_cheat_sheet.docx new file mode 100644 index 00000000..80d51700 Binary files /dev/null and b/Image_computation_tools/derivative_boost_cheat_sheet.docx differ diff --git a/Image_computation_tools/example_shell_single_level_analysis.m b/Image_computation_tools/example_shell_single_level_analysis.m new file mode 100644 index 00000000..972ff7de --- /dev/null +++ b/Image_computation_tools/example_shell_single_level_analysis.m @@ -0,0 +1,128 @@ +%function my_function_name(imgs, fixed_inputs, varargin) +% +% imgs image names, in string matrix +% fixed_inputs one or more inputs that should always be entered +% varargin optional inputs. Entered as 'name', value pairs of input +% arguments +% +function my_function_name(imgs, fixed_inputs, varargin) + + % ------------------------------------------------------------- + % Define Optional Inputs Here + % ------------------------------------------------------------- + + % Default values for all optional inputs + % --------------------------------------- + nobs = size(imgs, 1); + weights = ones(nobs, 1); % default weights + contrasts = []; + contrastnames = {}; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case 'contrasts', contrasts = varargin{i+1}; + + case 'weights', weights = varargin{i+1}; + + case 'contrastnames', contrastnames = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + % Check weights + wtol = max(sqrt(weights))*eps^(1/3); + if any(weights == 0) || any(weights < wtol) + fprintf('Some weights are too low or badly scaled. Setting zero weights to %3.4f\n.', wtol); + weights(weights < wtol) = wtol; + end + + W = diag(weights); + + % [t, df, beta/contrast, Phi, sigma,stebeta, F] = + % fit_gls(y,X,c,p,[PX, equal to pinv(X), for speed]) + + + % Map mask image into space of imgs, if not already, and write + % 'mask.img' + scn_map_image(maskimg, imgs(1, :), 'write', 'mask.img'); + maskimg = 'mask.img'; + + % Setup inputs and function handle + % ------------------------------------------------------------- + wh = intercept(X, 'which'); + if isempty(wh), disp('Warning! No intercept found in model.'); end + + %PX = pinv(X); + PX = inv(X' * W * X) * X' * W; % General weighted + + % Check if X is OK + condx = cond(X); + fprintf('Condition number of design matrix: %3.2f\n', condx); + if condx > 10000, error('Design matrix is not estimable!'); end + + fhandle = @(y) fit_gls(y, X, contrasts, arorder, PX, weights); + + + % Setup output image names + % ------------------------------------------------------------- + % [beta, t, pvals, convals, con_t, con_pvals, sigma, Phi, df, stebeta, conste, F] + + %if isempty(contrasts), betaconname = 'beta'; else betaconname = 'contrast'; end + + names = cell(1, 12); + names{7} = 'sigma.img'; + names{8} = 'phi.img'; + names{9} = 'df.img'; + names{12} = 'omnibus_F.img'; + + for i = [1:6 10:11] % Potential Multi-image outputs + for j = 1:length(conditionnames) + switch i + case 1 + names{i} = char(names{i}, ['beta_' conditionnames{j} '.img']); + case 2 + names{i} = char(names{i}, ['t_' conditionnames{j} '.img']); + case 3 + names{i} = char(names{i}, ['p_' conditionnames{j} '.img']); + + + + case 10 + names{i} = char(names{i}, ['ste_beta_' conditionnames{j} '.img']); + + end + end + + for j = 1:length(contrastnames) + switch i + case 4 + names{i} = char(names{i}, ['con_beta_' contrastnames{j} '.img']); + case 5 + names{i} = char(names{i}, ['con_t_' contrastnames{j} '.img']); + case 6 + names{i} = char(names{i}, ['con_p_' contrastnames{j} '.img']); + case 11 + names{i} = char(names{i}, ['ste_con_' contrastnames{j} '.img']); + end + end + + end + + for i = [1:6 10:11] + names{i} = names{i}(2:end, :); + end + + save fit_gls_brain_SETUP imgs X arorder contrasts names conditionnames contrastnames maskimg + + % Run + % ------------------------------------------------------------- + [beta, t, pvals, convals, con_t, con_pvals, sigma, Phi, df, stebeta, conste, F] = ... + image_eval_function(imgs, fhandle, 'mask', maskimg, 'outimagelabels' , names); + + % other optional args: 'outimagelabels' , 'connames' + +end diff --git a/Image_computation_tools/fisherp.m b/Image_computation_tools/fisherp.m new file mode 100755 index 00000000..5e79b60e --- /dev/null +++ b/Image_computation_tools/fisherp.m @@ -0,0 +1,50 @@ +function [z,p,sig,pt] = fisherp(p,varargin) +% function [z,p,sig,pt] = fisherp(p,[alph]) +% +% inputs: p values in 4-D array +% 1st 3 dims are within images, dim4 = image +% optional: alpha value for thresholding +% +% outputs: +% z Fisher's combined test statistic, compare to normal +% p p-values for combined test +% sig signficance 1 / 0 binary mask, p < .05 (or alph) FDR-corr +% pt p-value threshold for FDR corrected significance at alph +% +% tor wager +% +% Described in: +% Lazar, N. A., Luna, B., Sweeney, J. A., & Eddy, W. F. (2002). +% Combining brains: a survey of methods for statistical pooling +% of information. Neuroimage, 16(2), 538-550. +% +% Stouffer, S. A., Suchman, E. A., DeVinney, L. C., Star, S. A., and +% Williams, R. M. 1949. The American Soldier: Vol. I. Adjustment +% During Army Life. Princeton University Press, Princeton. +% +% Threshold is determined with False Discovery Rate (Benjamini & Hochberg, 1995) + +if length(varargin) > 0, alph = varargin{1};,else,alph = 0.05;,end + +lastdim = length(size(p)); +k = size(p,lastdim); + +z = -2*sum(log(p),lastdim); +p = 1 - chi2cdf(z(:),2*k); % distributed as chi-square with 2k df +p = reshape(p,size(z)); + +% eliminate voxels outside of brain, all 0, 1, or all NaN values +p(all(p == 0,lastdim)) = NaN; +p(all(p == 1,lastdim)) = NaN; + +pp = p(:); pp(isnan(pp)) = []; +pt = FDR(pp,alph); +if isempty(pt), pt = -Inf;, end + +%z = sum(norminv(1 - p),lastdim) ./ sqrt(k); +%p = normcdf(1-z); + +%if alph, sig = p <= alph;,end +sig = p <= pt; + +return diff --git a/Image_computation_tools/get_mask_vol.m b/Image_computation_tools/get_mask_vol.m new file mode 100755 index 00000000..3326fb59 --- /dev/null +++ b/Image_computation_tools/get_mask_vol.m @@ -0,0 +1,24 @@ +function mask = get_mask_vol(Pf,Pm) +% function mask = get_mask_vol(Pf,Pm) +% +% Tor Wager +% Returns 3-D volume of binary mask values, +% in the space of the functional image Pf. +% +% Pf filename of functional image +% Pm filename of mask image (should be 1's and 0's) + + Vm = spm_vol(Pm); + Vf = spm_vol(Pf); + if diag(Vm.mat(1:3,1:3)) ~= diag(Vf.mat(1:3,1:3)) % unequal voxel sizes + disp('Reslicing mask image to dims of functional img') + P = reslice_imgs(Pf,Pm,0); + [d f e] = fileparts(Pm); + Pm = fullfile(d,['r' f e]); + Vm = spm_vol(Pm); + end + mask = spm_read_vols(Vm); + mask = double(mask > .5); + +return + diff --git a/Image_computation_tools/image_eval_function.m b/Image_computation_tools/image_eval_function.m new file mode 100644 index 00000000..f7231fda --- /dev/null +++ b/Image_computation_tools/image_eval_function.m @@ -0,0 +1,590 @@ +function varargout = image_eval_function(imageNames, fhandle, varargin) + % varargout = image_eval_function(imgnames, fhandle, ['mask', mask], ['preprochandle', preprochandle], varargin) + % other optional args: 'outimagelabels' , 'connames' + % + % evaluate any function, defined by the function handle fhandle, + % on each in-mask voxel for a set of images. + % + % varargout = image_eval_function(Y, fhandle, varargin) + % evaluate fhandle on paired columns of X and Y + % + % NOTE: You must call image_eval_function with outputs, one output for + % each output you're requesting from the voxel-level function + % e.g., [t, df, betaorcontrast, Phi, sigma, stebeta, F, pvals] = ... + % image_eval_function(imgs, fhandle, 'mask', maskimg, 'outimagelabels' , names); + % + % fhandle is a function handle: + % fhandle = @(variable inputs) fit_gls(variable_input, fixed_inputs); + % fhandle = @(y) fit_gls(y, X, c, p, PX); + % + % 'outimagelabels' should be followed by a set of image names, one name + % per output of fhandle per element. e.g., outnames{j}{i} is the output + % image for output j and element (input image) i. elements may be images for each + % subject, if a set of one image per subject is entered, or something + % else depending on the nature of input imgs. + % Note: do not include suffixes: no .img + % + % Example: Generalized least squares fitting on 100 Y-variables, same X + % --------------------------------------------------------------------- + % + % Get image list + % imgs = filenames('trial_height*img', 'char') + % imgs = sort_image_filenames(imgs) + % + % Get pre-stored design matrix + % X = eventdesign{3}; + % + % preprochandle = @(y) trimts(y, 3, []); + % + % Example: Generate an image with the number of in-analysis (valid) + % subjects in each voxel + % EXPT.SNPM.P{2} is a list of subject-level contrast images. + % fhan = @(y) sum(abs(y) > 0 & ~isnan(y)); + % y = image_eval_function(EXPT.SNPM.P{2}, fhan, 'mask', EXPT.mask, 'outimagelabels', {{'sum_valid_antic.img'}}); + + % y = rand(100, 100); X = y + rand(100, 1); X(:,end+1) = 1; c = [1 0]'; p = 2; PX = pinv(X); + % fhandle = @(y) fit_gls(y, X, c, p, PX); + % [t, df, beta, Phi, sigma, stebeta, F] = fhandle(y); + % + % tor wager, jan 31, 2007 + % + % + % + + % setup inputs + nout = nargout; + setup_inputs(nout); + connames; + outimagelabels; + + % test data: Use to check and define outputs before running + % --------------------------------------------------------- + disp('Evaluating test data to get output structure.'); + ytest = randn(size(imageNames, 1), 1); + if ~isempty(preprochandle), ytest = preprochandle(ytest); end + + % must hard-code outputs; not ideal... + out1 = []; out2 = []; out3 = []; out4 = []; out5 = []; out6 = []; out7 = []; out8 = []; out9 = []; out10 = []; + out11 = []; out12 = []; out13 = []; out14 = []; out15 = []; out16 = []; out17 = []; out18 = []; out19 = []; out20 = []; + + % outputs are called out1, out2, ... etc. + build_test_eval_string(nargout); + eval(fstr); + + % put all outputs together in cell array + outputs = []; + for i = 1:nout + eval(['outputs{i} = out' num2str(i) ';']); + end + + % get names of images + + define_output_names(); + outimagenames; + + % Create or get memory-mapped volumes for output images + % Vout is the spm_vol structure for each ouput image + % --------------------------------------------------------------------- + maskInfo = iimg_read_img(imageNames(1,:), 2); + + + + %This doesn't seem to be necessary. iimg_reconstruct_vols does it. + %Vout = create_output_images(maskInfo, outimagenames, nimgs_this_output); + + + fprintf('Outputs: %3.0f images\n', nout); + for ii = 1:nout + if ~isempty(outimagenames{ii}) + fprintf(' %s \t- %3.0f volume(s)\n', outimagenames{ii}(1,:), nimgs_this_output(ii)); + end + end + fprintf('\n'); + + % Prepare data matrix + % --------------------------------------------------------------------- + fprintf('Loading data... ') + tic + [Y, maskInfo] = iimg_get_data(mask, imageNames); + fprintf(' %3.0f sec\n', toc); + + + + % perform preprocessing function at each voxel + % --------------------------------------------------------------------- + [n, v] = size(Y); + if ~isempty(preprochandle) + fprintf('Preprocessing:\n') + disp(preprochandle); + updateiterations = 1:round(v ./ 100):v; + updateperc = round(linspace(0, 100, length(updateiterations))); + fprintf('Working ...') + + for i = 1:v + Y(:,i) = preprochandle(Y(:,i)); + + update = ( updateiterations == i ); + if any(update), fprintf('\b\b\b\b%3d%%', updateperc(update)); end + end + + fprintf(' Done. \n') + end + + fprintf('Running analysis on function: ') + disp(fhandle); + + + % fit function at each voxel + % --------------------------------------------------------------------- + + % build string (fstr) that defines outputs + build_eval_string(nargout); + + % must hard-code outputs; not ideal... + out1 = []; out2 = []; out3 = []; out4 = []; out5 = []; out6 = []; out7 = []; out8 = []; out9 = []; out10 = []; + + % outputs are called out1, out2, ... etc. + eval(fstr); + + % put all outputs together in cell array + outputs = []; + for i = 1:nargout + eval(['outputs{i} = out' num2str(i) ';']); + end + + % clear outputs to save memory + clear out1 out2 out3 out4 out5 out6 out7 out8 out9 out10 + + % Example of what this might look like for a specific application: + %[t, df, beta, Phi, sigma, stebeta, F] = matrix_eval_function(Y, fhandle); + % + % Evaluated at each voxel: fhandle = @(y) fit_gls(y, X, c, p, PX); + + + + % reconstruct images and write + % --------------------------------------------------------------------- + + + + % write them + fprintf('Writing output images... '); + tic + + write_output_images; + + fprintf(' %3.0f sec\n', toc); + + varargout = outputs; + + %END OF MAIN FUNCTION CODE + + + % ------------------------------------------------------------------- + % + % + % INLINE FUNCTIONS + % + % + % + % ------------------------------------------------------------------- + + function setup_inputs(nout) + mask = []; + preprochandle = []; + outimagelabels = cell(1, nout); + connames = {}; + startslice = 1; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'mask', mask = varargin{i+1}; varargin{i+1} = []; + case 'data', dat = varargin{i+1}; varargin{i+1} = []; + case 'preprochandle', preprochandle = varargin{i+1}; varargin{i+1} = []; + + case {'outnames', 'outimagenames', 'outimagelabels'}, outimagelabels = varargin{i+1}; varargin{i+1} = []; + case 'connames', connames = varargin{i+1}; varargin{i+1} = []; + + case {'start', 'startslice', 'slice'}, startslice = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if isempty(mask), error('mask is empty. no-mask opt. not implemented yet.'); + else fprintf('Found mask: %s\n', mask); + end + + if nout ~= length(outimagelabels) + disp('Warning! Length of outimagelabels does not match number of outputs requested.'); + end + + imageNames = char(imageNames); + end + + % % ------------------------------------------------------------------- + % % build string to evaluate that defines outputs and inputs + % % ------------------------------------------------------------------- + function build_eval_string(numargs) + fstr = '['; + for arg = 1:numargs + fstr = [fstr 'out' num2str(arg)]; + if arg ~= numargs + fstr = [fstr ', ']; + else + fstr = [fstr '] = matrix_eval_function(Y, fhandle);']; + end + end + end + + function build_test_eval_string(numargs) + fstr = '['; + for arg = 1:numargs + fstr = [fstr 'out' num2str(arg)]; + if arg ~= numargs + fstr = [fstr ', ']; + else + fstr = [fstr '] = fhandle(ytest);']; + end + end + end + + % % ------------------------------------------------------------------- + % % print banner and define update points for text output + % % ------------------------------------------------------------------- + function print_banner() + fprintf('matrix_eval_function.m') + fprintf('\n_______________________________\n') + fprintf('Evaluating this function on %3.0f variables:\n', v); + disp(fhandle); + fprintf('...using this command:\n%s\n', fstr); + fprintf('_______________________________\n') + str = sprintf('Running ... Done %03d%%', 0); fprintf(str); + updateiterations = 1:round(v ./ 100):v; + updateperc = round(linspace(0, 100, length(updateiterations))); + end + + + % % ------------------------------------------------------------------- + % % Define image output names + % % ------------------------------------------------------------------- + % % % function define_output_names + % % % + % % % isok = []; + % % % % enter full set of output image names in outputimagelabels{i}{j} + % % % % check and see if conditions are met: + % % % for i = 1:nout + % % % if length(outimagelabels) >= i + % % % if length(outimagelabels{i}) == size(outputs{i}, 2) + % % % + % % % for j = 1 : length(outimagelabels{i}) + % % % if ischar(outimagelabels{i}{j}) + % % % isok(i, j) = 1; + % % % end + % % % end + % % % end + % % % end + % % % end + % % % + % % % % if all is good, do nothing further; else, create + % % % if ~isempty(isok) && all(isok(:)) + % % % disp('Using input image labels as-is'); + % % % outimagenames = outimagelabels; + % % % return + % % % + % % % else + % % % disp('Attempting to create output image names.'); + % % % end + % % % + % % % % enter stem for output names for each output in + % % % % outputimagelabels{x}, and names for each element + % % % % (subject/contrast/etc) image within each output in connames + % % % % or no connames for numbers + % % % + % % % if ~exist('connames', 'var'), connames = []; end + % % % nout = length(outputs); + % % % + % % % nc = length(connames); + % % % + % % % outimagenames = cell(1, nout); + % % % for i = 1:length(outputs) + % % % % same number of output params as connnames? + % % % if nc == size(outputs{i}, 2) + % % % for j = 1:nc + % % % outimagenames{i}{j} = [outimagelabels{i} '_' connames{j} '.img']; + % % % end + % % % elseif size(outputs{i}, 2) == 1 + % % % % only one, don't number + % % % outimagenames{i}{1} = [outimagelabels{i} '.img']; + % % % else + % % % for j = 1:size(outputs{i}, 2) + % % % outimagenames{i}{j} = [outimagelabels{i} num2str(j) '.img']; + % % % end + % % % end + % % % end + % % % + % % % nimgs_this_output = ones(1, nout); + % % % + % % % for ii = 1:nout + % % % nimgs_this_output(ii) = numel(outputs{ii}); + % % % end + % % % + % % % end + + + function define_output_names() + for i = 1:nout + if length(outimagelabels) >= i % if we have an output image name for this output + outnm = outimagelabels{i}; + + % Make sure we have .img extensions + for j = 1:size(outnm, 1) % for each name in this set, if there is more than one + this_img = deblank(outnm(j, :)); + + if ~strcmp(this_img(end-3:end), '.img') + % we need to add img extenstion + error('You must add .img to the end of each name.'); + + %outnm(j, :) = [outnm(j, :) '.img']; + end + end + + %outimagenames{i} = outnm; + end + end + + nimgs_this_output = ones(1, nout); + + for ii = 1:nout + nimgs_this_output(ii) = numel(outputs{ii}); + end + + for ii = 1:nout + if nimgs_this_output(ii) > 1 + % if long enough, leave it alone + if size(outimagelabels{ii}, 1) == nimgs_this_output(ii) + % we're OK, leave the names alone + outimagenames{ii} = outimagelabels{ii}; + + elseif size(outimagelabels{ii}, 1) == 1 + + % we have single name, but multiple output vols requested; expand into multi-volume set + spm_imgs = expand_4d_filenames(outimagelabels{ii}, nimgs_this_output(ii)); + + outimagenames{ii} = spm_imgs; + + else + % something is wrong. + disp('Image names and requested output lengths do not match up.') + disp('Either enter a string matrix defining image names for each element of each output,') + disp('or a single image name for the 4-D image for multiple-element outputs.'); + error('Quitting.'); + + end + else + outimagenames{ii} = outimagelabels{ii}; + end + end + end + + + % % ------------------------------------------------------------------- + % % Write to disk + % % ------------------------------------------------------------------- + function write_output_images + nout = length(outputs); + nc = length(connames); + for i = 1:nout + nimgs = size(outputs{i}, 2); + if nimgs > 1, fprintf(' %03d', 0); end + + for j = 1:nimgs % changed output file names from previous cell array to list + + if nimgs > 1, fprintf('\b\b\b\b %03d', j); end + + % Note: 12/5/2007; with SPM2 at least, the range of a + % multi-volume image seems to be clipped for each image to + % the range of the first volume when the image is created. + % Therefore, it is important to create the images with the + % appropriate range! + % has something to do with spm scaling factor: + % pinfo scale and offset seem to be set based on first + % volume only. creating first using spm_create_vol may not + % help...? + % if multiple images, re-create first image with range of + % data across images... + % kludgy fix... + % Note: 4/1/08: This was an SPM scaling factor issue. + % Images are now created by iimg_reconstruct_vols with + % scaling factors of intercept = 0, slope = 1 + + if nimgs > 1 && j == 1 + % % % %mymax = max(abs(outputs{i}(:))); % do the below to + % % % %avoid out of memory errors + % % % mymax = max( max(max(outputs{i})), abs(min(min(outputs{i}))) ); + tmp = outputs{i}(:,j)'; + % % % tmp(1) = mymax; tmp(2) = -mymax; + iimg_reconstruct_vols(tmp', maskInfo, 'outname', deblank(outimagenames{i}(j,:))); + + % code for checking stuff: + %i, j, tmp = outputs{i}(:,j)'; create_figure('plot'); plot(tmp), spm_image('init', deblank(outimagenames{i}(j,:))), global VV, VV = spm_vol(outimagenames{i}(j,:)); spm_type(VV.dim(4)) , spm_type(VV.private.hdr.dime.datatype), global vv, vv = spm_read_vols(VV); min(vv(:)), max(vv(:)) + else + iimg_reconstruct_vols(outputs{i}(:,j), maskInfo, 'outname', deblank(outimagenames{i}(j,:))); + end + + + % re-write first image, if needed + if nimgs > 1 && j == nimgs + iimg_reconstruct_vols(outputs{i}(:,1), maskInfo, 'outname', deblank(outimagenames{i}(1,:))); + end + end + end + end +end + + + + + +% % ------------------------------------------------------------------- +% % Create new output images or check for existing ones +% % ------------------------------------------------------------------- + +function Vout = create_output_images(maskInfo, outimagenames, nimgs_this_output) + + Vout = {}; + + for i = 1:length(outimagenames) + + if size(outimagenames{i}, 1) == nimgs_this_output(i) + % One named image per output volume + elseif size(outimagenames{i}, 1) == 1 + + else + % Mismatch! + error('There seems to be an image name / output number mismatch.') + end + + fprintf('Output %3.0f : %3.0f output volumes ', i, nimgs_this_output(i)) + + for j = 1:size(outimagenames{i}, 1) + % Named images or 4-D volumes (in which case, create first frame only) + + % % % % enforce no filename extension; ext of type .img will be added + % % % % below + % % % + % % % [dummy, outimagenames{i}, e] = fileparts(outimagenames{i}(1,:)); + % % % namestr = ['.' filesep outimagenames{i} '.img']; + + namestr = deblank(outimagenames{i}(j, :)); + + if exist(namestr, 'file') + % image already exists; add to current + fprintf(' ...Found existing: %s\n', namestr); + + else + % don't need to create ALL images, cause will write whole-images + % later... + fprintf(' ...Creating: %s\n', namestr); + + Vout{i}{j} = make_output_image(maskInfo, namestr, ' ', 1); % single-volume only + end + end + end + +end + +% % Create one output image +% % ------------------------------------------------------------------- +function V = make_output_image(maskInfo, fname, descrip, n) + V = struct('fname', '', 'dim', maskInfo.dim, 'mat', maskInfo.mat, 'pinfo', maskInfo.pinfo); + + %V.dim(4) = spm_type(Type); + + % set data type to float + switch(spm('Ver')) + case 'SPM2' + Type = 'float'; %'double'; + V.dim(4) = spm_type(Type); + case 'SPM5' + Type = 'float32'; + V.dt(1) = spm_type(Type); % NOT TESTED. %'float32'); + V.dt(2) = maskInfo.dt(2); + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + + V.fname = fname; + V.descrip = descrip; + V.n = n; + spm_create_vol(V); + + V.pinfo(1) = 1; % set image scaling factor to 1 + V.pinfo(2) = 0; % set image offset to 0 + V.pinfo(3) = prod(V.dim(1:3)) * spm_type(Type, 'bits') / 8 * (n-1); + + dat = NaN .* zeros(V.dim(1:3)); + spm_write_vol(V, dat); + +end + +% % % .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% % % how many are in this volume? +% % % see also: scn_num_volumes.m +% % % % ------------------------------------------------------------------- +% % function n = Nvol(V) +% % % number of images n stored in this file +% % +% % if ~isstruct(V) +% % V = spm_vol(V); +% % spm_close_vol(V); +% % end +% % +% % fp = fopen(V.fname); +% % fseek(fp, 0, 'eof'); +% % Len = ftell(fp); +% % fclose(fp); +% % +% % switch(spm('Ver')) +% % case 'SPM2' +% % mydt = V.dim(4); +% % case 'SPM5' +% % mydt = V.dt(1); +% % otherwise +% % error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); +% % end +% % n = Len/(prod(V.dim(1:3))*spm_type(mydt, 'bits')/8); +% % end + +% .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% how many are in this volume? +% % ------------------------------------------------------------------- +function n = Nvol(V) + % number of images n stored in this file + + if ~isstruct(V) + V = spm_vol(V); + %spm_close_vol(V); % spm2 + end + + switch(spm('Ver')) + case 'SPM2' + fp = fopen(V.fname); + fseek(fp,0,'eof'); + Len = ftell(fp); + fclose(fp); + n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); + + case 'SPM5' + n = length(V); + + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + +end + diff --git a/Image_computation_tools/image_eval_function_multisubj.m b/Image_computation_tools/image_eval_function_multisubj.m new file mode 100644 index 00000000..86fda2a5 --- /dev/null +++ b/Image_computation_tools/image_eval_function_multisubj.m @@ -0,0 +1,712 @@ +function image_eval_function_multisubj(imageNames,fhandle,varargin) + % image_eval_function_multisubj(imgnames,fhandle,['mask',mask],['preprochandle',preprochandle],['outnames',outimagelabels],varargin) + % other optional args: 'outimagelabels' , 'connames' + % + % evaluate any function, defined by the function handle fhandle, + % on each in-mask voxel for a set of images. + % imageNames is a cell array of N cells, each containing images for one + % replication (i.e., subject) + % + % At each voxel, a cell array is formed, one cell per subject. + % This would correspond to a matrix is formed of t x N, where t is time and N is + % replication (subject) but cells can deal with unequal data vector lengths for each subject. + % The anonymous function in fhandle should operate on data from each cell (subject). + % + % varargout = image_eval_function(Y,fhandle,varargin) + % evaluate fhandle on paired columns of X and Y + % + % fhandle is a function handle: + % fhandle = @(variable inputs) fit_gls(variable_input,fixed_inputs); + % fhandle = @(y) fit_gls(y,X,c,p,PX); + % + % specify the outputs by adding them as output image names. + % The number of outputs returned is determined by the number of named + % images in the list entered following the 'outnames' keyword. + % + % Note on output images: You specify the names of the output images + % One image will be written per output of fhandle. + % The images will have one volume per element of the output variable. + % If you are returning an output with one value per subject, for + % example, then a single image will be written with one volume in it + % per subject. + % + % preprochandle is a function handle. + % it encapsulates the preprocessing function. + % the function should work on each cell (subject) of a t x N cell array of time courses for each + % subject, where each cell contains a t x v matrix of data from a + % slice. The preproc function should thus be able to handle a whole slice as + % input. + % the function can itself be a cell array with multiple handles in + % different cells + % + % Example: Generalized least squares fitting on 100 Y-variables, same X + % --------------------------------------------------------------------- + % + % Get image list + % imgs = filenames('trial_height*img','char') + % imgs = sort_image_filenames(imgs) + % + % Get pre-stored design matrix + % X = eventdesign{3}; + % + % preprochandle = @(y) trimts(y,3,[]); + % + + % y = rand(100,100); X = y + rand(100,1); X(:,end+1) = 1; c = [1 0]'; p = 2; PX = pinv(X); + % fhandle = @(y) fit_gls(y,X,c,p,PX); + % [t, df, beta, Phi, sigma,stebeta, F] = fhandle(y); + % + % tor wager, jan 31, 2007 + % + + % setup inputs + setup_inputs; + connames; + outimagenames; + + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + + case 'SPM8' + % spm_defaults is a function + spm_defaults() + + otherwise + % unknown SPM + disp('Unknown version of SPM!'); + spm_defaults() + end + + doload = 0; % load key vars from file -- TEMPORARY + + % Prepare data images and map into volumes V + % --------------------------------------------------------------------- + disp('Preparing data.') + + % Make sure mask is in same dims as input images, and write mask.img to + % current directory + + scn_map_image(mask, imageNames{1}(1,:), 'write', 'mask.img'); + maskInfo = iimg_read_img('mask.img', 2); + + + fprintf('Mask info:\nDimensions: %3.0f %3.0f %3.0f\n', maskInfo.dim(1:3)) + fprintf('Voxels in mask: %3.0f\n', maskInfo.n_inmask) + + v = maskInfo.n_inmask; + + N = length(imageNames); + n = zeros(1, N); + V = cell(1, N); + + if doload, load V_tmp, end + + fprintf('Mapping all input data volumes to memory\n') + + for ii = 1:N + + n(ii) = size(imageNames{ii}, 1); + + fprintf('Subject %3.0f, %3.0f images.\n', ii, n(ii)); + + if ~doload, + V{ii} = spm_vol(imageNames{ii}); + + switch lower(spm('Ver')) + + case 'spm2' + % OK + case 'spm5' + % update image number + n(ii) = length(V{ii}); + + case 'spm8' + % update image number + n(ii) = length(V{ii}); + + otherwise + error('I don''t recognize your version of SPM!'); + + end + + + end + + + end + + + % set up preprocessing + % --------------------------------------------------------------------- + ynstr = {'Off' 'On'}; + fprintf(1,'Preprocessing is %s\n', ynstr{~isempty(preprochandle) + 1}) + if ~isempty(preprochandle) + fprintf(1,'Preprocessing handle for subject 1:\n'); + disp(preprochandle{1}) + end + + + % set up function evaluation + % --------------------------------------------------------------------- + + % build string (fstr) that defines outputs + fstr = build_eval_string(nout); + + % must hard-code outputs; not ideal... + out1 = []; out2 = []; out3 = []; out4 = []; out5 = []; out6 = []; out7 = []; out8 = []; out9 = []; out10 = []; + out11 = []; out12 = []; out13 = []; out14 = []; out15 = []; out16 = []; out17 = []; out18 = []; out19 = []; out20 = []; + out21 = []; out22 = []; out23 = []; out24 = []; out25 = []; out26 = []; out27 = []; out28 = []; out29 = []; out30 = []; + out31 = []; out32 = []; out33 = []; out34 = []; out35 = []; out36 = []; out37 = []; out38 = []; out39 = []; out40 = []; + + if nout > 40, error('Can only have up to 40 outputs.'); end + + % outputs are called out1, out2, ... etc. + + % Example of what this might look like for a specific application: + %[t, df, beta, Phi, sigma,stebeta, F] = matrix_eval_function(Y,fhandle); + % + % Evaluated at each voxel: fhandle = @(y) fit_gls(y,X,c,p,PX); + + + % do dummy test to get sizes of outputs + % which are determined flexbily based on what function handle does. + % + % each element in output is stored in an image volume + % elements in the same output (out1, out2, etc.) are stored as volumes + % in the same .img file, indexed by .n fiels in the spm_vol structure + % (these are stored during processing on 3rd dim of sliceoutput) + % + % to access a volume, try spm_vol('imagename.img, 3') for 3rd volume in + % .img file. + + Y = cell(1, N); + for ii = 1:N + Y{ii} = rand(n(ii), 1); + end + + if ~isempty(preprochandle) + + for ii = 1:N + str = [num2str(ii) ' Preprocess Test']; + fprintf('%s',str); + + if iscell(preprochandle) + Y{ii} = preprochandle{ii}(Y{ii}); + else + Y{ii} = preprochandle(Y{ii}); + end + + erase_string(str) + end + end + + eval(fstr); % this evaluates the function + + for ii = 1:nout + str = ['outputs{ii} = out' num2str(ii) ';']; + eval(str) + end + + % not necessary if we're writing multiple vols to same image + %outimagenames = define_output_names; + + nimgs_this_output = ones(1, nout); + + for ii = 1:nout + nimgs_this_output(ii) = numel(outputs{ii}); + end + + + % Create or get memory-mapped volumes for output images + % Vout is the spm_vol structure for each ouput image + % --------------------------------------------------------------------- + Vout = create_output_images(maskInfo, outimagenames, nimgs_this_output); + + fprintf('Outputs: %3.0f images\n', nout); + for ii = 1:nout + fprintf(' %s \t- %3.0f volume(s)\n', outimagenames{ii}, nimgs_this_output(ii)); + end + fprintf('\n'); + + + + disp('This will be evaluated at each voxel:') + disp(fstr) + disp(' '); + + + + % for each slice + % --------------------------------------------------------------------- + + nZ = maskInfo.dim(3); + + %if doload + % which_slices = 3; + %else + which_slices = startslice : nZ; + %end + + for whslice = which_slices + + % Load slice + % --------------------------------------------------------------------- + t1 = clock; + + clear Yslice + [Yslice, wh_in_slice] = load_slice_data(whslice, V, maskInfo, preprochandle); + + fprintf(' Elapsed: %3.0f s\n', etime(clock, t1)); + vox_in_slice = sum(wh_in_slice); + + + % get analyzed data for this slice (or empty values if no eligible voxels) + % --------------------------------------------------------------------- + sliceoutputs = analyze_slice_data; + + + % write data from slice + % --------------------------------------------------------------------- + write_output_slice(Vout, sliceoutputs, whslice); + + end % end slices + + + %END OF MAIN FUNCTION CODE + + + + + + + + % ------------------------------------------------------------------- + % + % + % INLINE FUNCTIONS + % + % + % + % ------------------------------------------------------------------- + + function setup_inputs + mask = []; + preprochandle = []; + outimagenames = cell(1); + connames = {}; + + startslice = 1; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'mask', mask = varargin{i+1}; varargin{i+1} = []; + + case 'preprochandle', preprochandle = varargin{i+1}; varargin{i+1} = []; + + case {'outnames' 'outimagenames'}, outimagenames = varargin{i+1}; varargin{i+1} = []; + nout = length(outimagenames); + + case {'start','startslice','slice'}, startslice = varargin{i+1}; + + case 'connames', connames = varargin{i+1}; varargin{i+1} = []; % Not used anymore + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if isempty(mask), error('mask is empty. no-mask opt. not implemented yet.'); + else fprintf(1,'Found mask: %s\n',mask); + end + + end + + + + + + % % ------------------------------------------------------------------- + % % Analyze data for this slice (inline) + % % ------------------------------------------------------------------- + + function sliceoutputs = analyze_slice_data + + updateiterations = 1:round(vox_in_slice ./ 100):vox_in_slice; + updateperc = round(linspace(0,100,length(updateiterations))); + + + fprintf(1,'Analysis ...') + + % initialize output images + sliceoutputs = cell(1, nout); + for ii = 1:nout + sliceoutputs{ii} = NaN .* zeros([maskInfo.dim(1:2) nimgs_this_output(ii)]); + end + + x = maskInfo.xyzlist(wh_in_slice, 1); + y = maskInfo.xyzlist(wh_in_slice, 2); + + t1 = clock; + + % for each voxel in the slice + for ii = 1:vox_in_slice + + % get data + + for subj = 1:N, Y{subj} = Yslice{subj}(:, ii); end + + % fit function at this voxel + % --------------------------------------------------------------------- + + eval(fstr) + + % save output in slice matrices + % --------------------------------------------------------------------- + for jj = 1:nout + str = ['sliceoutputs{jj}(x(ii), y(ii), :) = out' num2str(jj) '(:);']; + eval(str) + end + + update = ( updateiterations == ii ); + if any(update), fprintf(1,'\b\b\b\b%3d%%',updateperc(update)); end + + end + + fprintf(' Elapsed: %3.0f s\n', etime(clock, t1)); + + end + + + + +end % END MAIN FUNCTION + + + + + +% ------------------------------------------------------------------- +% +% +% SUB-FUNCTIONS +% +% +% +% ------------------------------------------------------------------- + + +% % ------------------------------------------------------------------- +% % build string to evaluate that defines outputs and inputs +% % ------------------------------------------------------------------- + +function fstr = build_eval_string(numargs) + fstr = '['; + for arg = 1:numargs + fstr = [fstr 'out' num2str(arg)]; + if arg ~= numargs + fstr = [fstr ',']; + else + fstr = [fstr '] = fhandle(Y);']; + end + end + + +end + + + +% % ------------------------------------------------------------------- +% % Load and preprocess (if specified) data for this slice +% % ------------------------------------------------------------------- + +function [Y, wh_in_slice] = load_slice_data(whslice, V, maskInfo, preprochandle) + + wh_in_slice = maskInfo.xyzlist(:,3) == whslice; + xyzslice = maskInfo.xyzlist(wh_in_slice, :); + xyzslice(:, end+1) = 1; + xyzslice = xyzslice'; + + fprintf('Slice %3.0f, %3.0f in-mask voxels\n', whslice, sum(wh_in_slice)); + fprintf('Loading and preprocessing for this slice for subject: '); + + N = length(V); + + Y = cell(1, N); + + if isempty(xyzslice) + fprintf(' No in-mask data '); + return + end + + for i = 1:N + % load + str = [num2str(i) ' Load ']; + fprintf('%s',str); + Y{i} = spm_get_data(V{i}, xyzslice); + + % preprocess + erase_string(str) + + if ~isempty(preprochandle) + + str = [num2str(i) ' Preprocess ']; + fprintf('%s',str); + + if iscell(preprochandle) + Y{i} = preprochandle{i}(Y{i}); + else + Y{i} = preprochandle(Y{i}); + end + + erase_string(str) + end + + + end + + + % Check for missing data in each set + wh_bad_vox = cell(1, N); + wh_missing_data = cell(1, N); + + fprintf('Summary of missing or bad voxels:\n'); + + for i = 1:N + + wh_bad_vox{i} = all(Y{i} == 0) | all(isnan(Y{i})); + wh_missing_data{i} = any(Y{i} == 0) | any(isnan(Y{i})); + + fprintf('Dataset %3.0f: %3.0f voxels have no data, and %3.0f voxels have missing data.\n', i, sum(wh_bad_vox{i}), sum(wh_missing_data{i})); + + end + + + +end + + +% % ------------------------------------------------------------------- +% % Create new output images or check for existing ones +% % ------------------------------------------------------------------- + +function Vout = create_output_images(maskInfo, outimagenames, nimgs_this_output) + + for i = 1:length(outimagenames) + + % enforce no filename extension; ext of type .img will be added + % later. + [dummy, outimagenames{i}, e] = fileparts(outimagenames{i}); + + namestr = ['.' filesep outimagenames{i} '.img']; + + if exist(namestr, 'file') + % image already exists; add to current + fprintf(1,' ...Found existing: %s\n', outimagenames{i}); + + % check how many images are in it + n = Nvol(namestr); + + for j = 1:nimgs_this_output(i) + + if j > n + % for any un-made volumes + fprintf(' ...adding volume %3.0f to %s\n', j, outimagenames{i}); + Vout{i}{j} = make_output_image(maskInfo, outimagenames{i}, ' ', j); + else + Vout{i}{j} = spm_vol(sprintf('%s, %d',namestr, j)); + end + + end + + + else + + + fprintf(1,' ...Creating: %s\n', outimagenames{i}); + for j = 1:nimgs_this_output(i) + % images stored in this file. Each is stored using 'n' field + % in same 4-D .img file. + Vout{i}{j} = make_output_image(maskInfo, outimagenames{i}, ' ',j); + end + end + end + +end + +% % Create one output image +% % ------------------------------------------------------------------- +function V = make_output_image(maskInfo, fname, descrip,n) + + + V = struct('fname','', 'dim', maskInfo.dim, 'mat',maskInfo.mat, 'pinfo', [1 0 maskInfo.pinfo(3, 1)]'); % scaling factors 1 0, keep data type from mask + + % set data type to float + switch(spm('Ver')) + case 'SPM2' + Type = 'double'; + V.dim(4) = spm_type(Type); + case {'SPM5', 'SPM8'} + Type = 'float32'; + V.dt(1) = spm_type(Type); % NOT TESTED. %'float32'); + V.dt(2) = 1; + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + + V.fname = [fname '.img']; + V.descrip = descrip; + V.n = n; + spm_create_vol(V); + + V.pinfo(3)=prod(V.dim(1:3))*spm_type(Type,'bits')/8*(n-1); + + dat = NaN .* zeros(V.dim(1:3)); + spm_write_vol(V, dat); + + % re-load to get full volinfo structure with private (Nifti), for this + % 3-D volume only + V = spm_vol([V.fname ', ' num2str(n)]); + +end + +% % % .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% % % how many are in this volume? +% % % % ------------------------------------------------------------------- +% % function n = Nvol(V) +% % % number of images n stored in this file +% % +% % if ~isstruct(V) +% % V = spm_vol(V); +% % spm_close_vol(V); +% % end +% % +% % fp = fopen(V.fname); +% % fseek(fp,0,'eof'); +% % Len = ftell(fp); +% % fclose(fp); +% % n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); +% % +% % end + +% .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% how many are in this volume? +% % ------------------------------------------------------------------- +function n = Nvol(V) + % number of images n stored in this file + + if ~isstruct(V) + V = spm_vol(V); + %spm_close_vol(V); % spm2 + end + + switch(spm('Ver')) + case 'SPM2' + fp = fopen(V.fname); + fseek(fp,0,'eof'); + Len = ftell(fp); + fclose(fp); + n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); + + case {'SPM5', 'SPM8'} + n = length(V); + + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + +end + + + +% % ------------------------------------------------------------------- +% % Write to disk +% % ------------------------------------------------------------------- +function write_output_slice(Vout, sliceoutputs, whslice) + + fprintf(1,'Writing slice %3.0f in output images.\n', whslice); + + for i = 1:length(Vout) % length(sliceoutputs) + + scn_write_plane(cat(2, Vout{i}{:}), sliceoutputs{i}, whslice); + + % % % for j = 1:length(Vout{i}) % size(sliceoutputs{i},3) % for each image in 3rd dim + % % % + % % % myslice = sliceoutputs{i}(:,:,j); + % % % + % % % spm_write_plane(Vout{i}{j}, myslice, whslice); + % % % + % % % %iimg_reconstruct_3dvol(myslice(:), maskInfo, 'outname', outimagenames{i}{j}, 'slice', whslice); + % % % + % % % end + end +end + + + + + +% OLD inline functions no longer used + +% % ------------------------------------------------------------------- +% % print banner and define update points for text output (inline) +% % ------------------------------------------------------------------- +% function print_banner +% +% fprintf(1,'matrix_eval_function.m') +% fprintf(1,'\n_______________________________\n') +% fprintf(1,'Evaluating this function on %3.0f variables:\n',v); +% disp(fhandle); +% fprintf(1,'...using this command:\n%s\n',fstr); +% fprintf(1,'_______________________________\n') +% str = sprintf('Running ... Done %03d%%',0); fprintf(1,str); +% updateiterations = 1:round(v ./ 100):v; +% updateperc = round(linspace(0,100,length(updateiterations))); +% end +% +% +% % % ------------------------------------------------------------------- +% % % Define image output names (inline) +% % % ------------------------------------------------------------------- +% function outimagenames = define_output_names +% +% %%% OLD: not used now +% %%% outimagelabels should be one per output +% % names would be created for each image +% % this has been replaced by a scheme which writes multiple vols to +% % same image file +% +% % outputs for one voxel; for formatting +% outputs = cell(1, nout); +% for i = 1:nout, str = ['outputs{i} = out' num2str(i) ';']; eval(str); end +% +% if ~exist('connames','var'), connames = []; end +% +% nc = length(connames); +% +% outimagenames = cell(1,nout); +% for i = 1:nout +% % same number of output params as connnames? +% if nc == size(outputs{i},2) +% for j = 1:nc +% outimagenames{i}{j} = [outimagelabels{i} '_' connames{j} '.img']; +% end +% elseif size(outputs{i},2) == 1 +% % only one, don't number +% outimagenames{i}{1} = [outimagelabels{i} '.img']; +% else +% for j = 1:size(outputs{i},2) +% outimagenames{i}{j} = [outimagelabels{i} num2str(j) '.img']; +% end +% end +% end +% +% end diff --git a/Image_computation_tools/image_histogram1d.m b/Image_computation_tools/image_histogram1d.m new file mode 100644 index 00000000..4ebf6517 --- /dev/null +++ b/Image_computation_tools/image_histogram1d.m @@ -0,0 +1,195 @@ +function [cl2,classes] = image_histogram1d(varargin) +%[cl2,classes] = image_histogram1d([image name],[overlay]) +% +% Visualize a change_point map stored in hewma_cp.img +% (output from hewma2) +% and classify voxels into groupings based on CP + +name = 'hewma_cp.img'; +%name = 'hewma_runlen.img'; +overlay = []; + +if length(varargin) > 0, name = varargin{1};,end +if length(varargin) > 1, overlay = varargin{2};,end + +% ---------------------------------------------------- +% initial viewing of CP map +% ---------------------------------------------------- + +[CLU.XYZ,CLU.XYZmm,CLU.Z,CLU.V] = img2voxel(name); +CLU.XYZ = CLU.XYZ(1:3,:); +%cl = mask2clusters(name); + +%cluster_orthviews(cl,'overlay',overlay); +v2 = CLU.Z; + +% ---------------------------------------------------- +% load data +% ---------------------------------------------------- + +%v = spm_read_vols(spm_vol(name)); +%v2 = v(:); + +%discretize for convenience +v2 = round(v2.*100); +%v2(abs(v2) < eps | isnan(v2)) = []; % shoul + + +% ---------------------------------------------------- +% make histogram and get clusters (classifications) of CPs +% ---------------------------------------------------- + +nbins = input('Number of bins: '); %unique(v2); +tor_fig; f1 = gcf; [h,x] = hist(v2./100,nbins); hh = bar(x,h); set(hh,'FaceColor',[.5 .5 .5]) + +nclasses = input('How many classes?'); +str = sprintf('%3.0f Classes: color image overlay starting at which class (e.g., 1 for all classes): ',nclasses) +startclass = input(str); + +err = 1; indx = 1; +while err + try + classes = kmeans(v2, nclasses); % ,'start','uniform'); + err = 0; + catch + end + indx = indx + 1; + if indx == 11, disp('kmeans: tried 10 times. No solution.'); err = 0;, return, end +end + + + +% For each x, find the modal class +% this is used to color the histogram +x100 = x*100; +binwid = diff(x100); binwid = binwid(1)./2; + +for i =1:length(x100) + xrange = [x100(i)-binwid x100(i)+binwid]; + xclass = classes(v2>xrange(1) & v2<=xrange(2)); + binclass(i) = mode(xclass); +end + +%v2 = round(v2.*100); +% % classmap's elements are the range of values in v2 +% mincp = min(v2); +% maxcp = max(v2); +% for i = mincp:maxcp, tmp = unique(classes(find(v2==i))); +% if isempty(tmp), classmap(i) = 0;, +% else,classmap(i) = tmp(1);, end +% end + +% CLU = clusters2CLU(cl); +% CLU.Z = v2'; +% CLU.Z(abs(CLU.Z)= range(1)); + % hh = bar(x(wh),h(wh)); set(hh,'FaceColor',colors{i}); + + wh = find(binclass == indx(i)); + hh = bar(x(wh),h(wh)); set(hh,'FaceColor',colors{i}); + +end +xlabel('Image value') +ylabel('Number of voxels') + + +% ---------------------------------------------------- +% re-make separate clusters for each class +% and plot on brain +% ---------------------------------------------------- + +clear cl2 +for i = startclass:nclasses + CLUtmp = CLU; + wh = find(CLUtmp.Z == indx(i)); + CLUtmp.XYZmm = CLUtmp.XYZmm(:,wh); + CLUtmp.XYZ = CLUtmp.XYZ(:,wh); + CLUtmp.Z = CLUtmp.Z(:,wh); + CLUtmp.cp = CLUtmp.cp(:,wh); + cl2{i} = tor_extract_rois([],CLUtmp,CLUtmp); + + if i == startclass + cluster_orthviews(cl2{i},colors(i),'overlay',overlay); + else + cluster_orthviews(cl2{i},colors(i),'add','overlay',overlay); + end +end + +% write mask of sig. results +domask = input('Write mask of colormapped voxels? (1 or 0): '); +if domask + xyz = []; + for i=1:length(cl2) + if ~isempty(cl2{i}), xyz = [xyz cl2{i}.XYZ];, end + end + V = spm_vol(name); + V.fname = 'img_hist_mask.img'; + mask = voxel2mask(xyz', V.dim(1:3)); + spm_write_vol(V,mask); +end + + +return + + + diff --git a/Image_computation_tools/maskImg.m b/Image_computation_tools/maskImg.m new file mode 100755 index 00000000..07ed8cc6 --- /dev/null +++ b/Image_computation_tools/maskImg.m @@ -0,0 +1,31 @@ +%+--------------------------------------------------------- +%| +%| Robert C. Welsh +%| University of Michigan +%| Department of Radiology +%| +%| A routine to mask an image given the input range. +%| +%| Copyright 2000/01 +%| +%| Version 0.8 - October 19, 2000 +%| +%| function [maskedImage maskingImage]] = ... +%| maskImg(inputImage,maskLow,maskHi) +%| +%+--------------------------------------------------------- + +function [maskedImage, maskingImage] = maskImg(inputImage,maskLow,maskHi) + +maskedLowImage = max(0,inputImage - maskLow); + +maskedHiImage = max(0,inputImage - maskHi); + +maskingImage = xor(maskedLowImage,maskedHiImage); + +maskedImage = maskingImage.*inputImage; + +% +% End +% +return \ No newline at end of file diff --git a/Image_computation_tools/mask_create_from_image_set.m b/Image_computation_tools/mask_create_from_image_set.m new file mode 100644 index 00000000..cb9bbcc7 --- /dev/null +++ b/Image_computation_tools/mask_create_from_image_set.m @@ -0,0 +1,77 @@ +function mask = mask_create_from_image_set(imgs, outname, atleastN, varargin) + % mask = mask_create_from_image_set(imgs, outname, atleastN, ['sum']) + % + % Take a set of images and create a mask of voxels in which at least N + % subjects have valid (not exactly zero, non NaN) data. + % + % This makes a useful results mask for a set of images, i.e., in a + % group analysis. + % + % Optional: 'sum' input writes the sum image instead of the mask image, + % so that the values in the image reflect the number of input images + % with valid values. + % + % compatible with SPM5 and above only! + % + % Tor Wager, April 2, 2008 + % Edit Sept 13, 2008: Sum image output option + % + % Examples: + % mask_create_from_image_set(EXPT.SNPM.P{1}, 'mask_all_valid.img'); + % + % imgs = filenames('vascular_mask_*img'); + % mask_create_from_image_set(imgs, 'vascular_group_sum.img', 6, 'sum'); + + write_sum = 0; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case {'sum', 'sumimage'}, write_sum = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + imgs = strvcat(imgs); % enforce string + + if nargin < 3 || isempty(atleastN), atleastN = size(imgs, 1); end + + spm_defaults + + disp('Mapping images.'); + V = spm_vol(imgs); + + disp('Checking dimensions and spaces.'); + anybad = iimg_check_volinfo(V(1), V); + if anybad, error('Exiting.'); end + + disp('Reading images.'); + dat = spm_read_vols(V); + + % number of subjects for which each vox is valid + sumimg = sum(abs(dat) > eps & ~isnan(dat), 4); + + % threshold + mask = double(sumimg >= atleastN); + + + % write output + if ~isempty(outname) + + + outV = struct( 'fname', outname, 'mat', V(1).mat, 'dim', V(1).dim, 'dt', V(1).dt, 'pinfo', [1 0 0]', 'n', [1 V(1).n(2)] ); + outV.descrip = sprintf('At least %3.0f out of %3.0f images valid', atleastN, size(imgs, 1)); + + fprintf('Writing: %s\n', outV.fname); + + if write_sum + spm_write_vol(outV, sumimg); + else + spm_write_vol(outV, mask); + end + end + +end diff --git a/Image_computation_tools/mask_create_results_mask.m b/Image_computation_tools/mask_create_results_mask.m new file mode 100644 index 00000000..20ce1c15 --- /dev/null +++ b/Image_computation_tools/mask_create_results_mask.m @@ -0,0 +1,51 @@ +function [cl_out, out_name] = mask_create_results_mask(mask, study_img, kern_size, varargin) + % [cl_out, out_name] = mask_create_results_mask(mask, study_img, kern_size, [opt: expression to evaluate on mask]) + % + % Takes a mask image name (mask) + % smooths it by smooth_mm (optional; enter 0 for kern_size to avoid this) + % Resamples it to study image dimensions (study_img) + % writes output image + % Optional: Applies an expression to be evaluated to the image, + % in spm_imcalc format, e.g., 'i1 < .05' (uses spm_imcalc_ui.m) + % + % returns mask_clusters (mask_cl) and mask name (out_name) + % + %Example: + % mask = which('spm2_amy.img') + % study_img = '/Users/tor/Documents/Tor_Documents/PublishedProjects/inpress_2007_Emotion_handbook_2006/an_metaFWE_rad10/exp_vs_percept/Activation_FWE_all.img'; + % kern_size = 3; + % cl_out = mask_create_results_mask(mask, study_img, kern_size); + % cluster_orthviews(cl_out, {[0 1 0]}, 'add', 'handle', 1); +% +% [cl_out, out_name] = mask_create_results_mask('X-Y_total_pvals.img', 'X-Y_total_pvals.img', 0, 'i1 < .05 & i1 > eps'); +% spm_image('init', 'X-Y_total_pvals.img'); +% cluster_orthviews(cl_out, {[1 0 0]}, 'trans', 'add'); + + [dd, ff, ee] = fileparts(mask); + + out_name = ['mask_' ff ee]; + disp(['Creating: ' out_name]); + + % warning: spm_smooth will introduce non-zero values even with a kernel size + % of 0. avoid by conditional statement: + if kern_size + spm_smooth(mask, out_name, kern_size) + scn_map_image(out_name, study_img, 'write', out_name); + + else + scn_map_image(mask, study_img, 'write', out_name); + end + + + %spm_conv_vol(maskData,maskData,2,2,2,[0 0 0]); + + + % optional: threshold/eval + if ~isempty(varargin) + out_name = spm_imcalc_ui(out_name, out_name, varargin{1}); + end + + + cl_out = mask2clusters(out_name); + +end \ No newline at end of file diff --git a/Image_computation_tools/mask_fisher.m b/Image_computation_tools/mask_fisher.m new file mode 100755 index 00000000..d1a19284 --- /dev/null +++ b/Image_computation_tools/mask_fisher.m @@ -0,0 +1,178 @@ +function [clpos,clneg] = mask_fisher(clsize,outname,k,Pimg,Timg) +% function [clpos,clneg] = mask_fisher(clsize,outname,k,Pimg,Timg) +% by Tor Wager +% +% This function performs combination of p-values across images using the +% Fisher method (see refs below). Voxels are thresholded with FDR, and can +% be positive and negative at once if both positive and negative sig +% effects exist in input images. FDR correction is based on 2-tailed +% p-values - so this function assumes 2-tailed p-values!! (robfit does +% this, glmfit and robustfit both return 2-tailed p-values as well.) +% +% Assumes input p-values are 1-tailed, and divides by two to get 2-tailed +% p-value inputs!! +% +% Inputs: +% Pimg string mtx of p-value image names +% Timg t-value img names, used to ensure signs are same across all tests +% k num voxels/num comparisons **NOT USED NOW - FDR USED INSTEAD +% if empty, uses # of non-zero, non-NaN values in images +% +% empty i1 prompts for graphic selection of filenames +% extra arguments are more file names for 3 - n-way intersection +% empty outname prompts for entry of output img file name +% +% Described in: +% Lazar, N. A., Luna, B., Sweeney, J. A., & Eddy, W. F. (2002). +% Combining brains: a survey of methods for statistical pooling +% of information. Neuroimage, 16(2), 538-550. +% +% Stouffer, S. A., Suchman, E. A., DeVinney, L. C., Star, S. A., and +% Williams, R. M. 1949. The American Soldier: Vol. I. Adjustment +% During Army Life. Princeton University Press, Princeton. +% +% Pimg=get_filename2('rob*\*_p_*0002.img'); Timg = get_filename2('rob*\*_tmap_0002.img') +% + +% get file names and calcstr to evaluate +% ------------------------------------------------ + +if isempty(Pimg),Pimg = spm_get(Inf,'*.img','Select p-value images',pwd,0);,end +if isempty(Timg),Timg = spm_get(Inf,'*.img','Select t-value images',pwd,0);,end + +V =spm_vol(Pimg); v = spm_read_vols(V); +Vt =spm_vol(Timg); vt = spm_read_vols(Vt); + +% this was used to make sure all values were of the same sign +% but if we use 2-tailed p-values, this isn't necessary. +% so we include an omnibus, and then these two as supplements. +pos = all(vt>0,4); +neg = all(vt<0,4); + +% just include voxels in which one value at least is nonzero +%pos = any(vt>0,4); neg = any(vt<0,4); + +% this was used before FDR correction was implemented - for bonferroni only +if isempty(k) + mask = all(~isnan(v) & v ~= 0,4); + k = sum(mask(:)); +end + +[z,p,sig,pt] = fisherp(v,.05); %.05 ./ k); + +disp('---------------------------------------') +disp('Output in mask_fisher_output.txt') +disp('---------------------------------------') +diary mask_fisher_output.txt + +disp([inputname(2) ' Size threshold: ' inputname(1)]) +disp(' ') +p = p(:); p(p==0 | isnan(p)) = []; +fprintf(1,'\nMinimum combined p-value: %3.4f\n', min(p)) + +omnisig = z .* sig; +possig = z .* pos .* sig; +negsig = z .* neg .* sig; + +tmp=omnisig(:); s = sum(~isnan(tmp) & tmp ~= 0); +fprintf(1,'Omnibus: %3.0f voxels\n',s); + +tmp=possig(:); s = sum(~isnan(tmp) & tmp ~= 0); +tmp=negsig(:); sn = sum(~isnan(tmp) & tmp ~= 0); +fprintf(1,'All betas Positive: %3.0f voxels, Negative: %3.0f voxels\n',s,sn); + +fprintf(1,'Correction with Bonferroni, %3.0f voxels',k) +fprintf(1,'\nCorrection with FDR, p threshold = %3.4f, Z = %3.2f',pt,norminv(pt)) +mynum = 2*size(Pimg,1); +tmp=z .* pos; tmp=tmp(:); tmp(tmp==0 | isnan(tmp)) = []; s = length(tmp); s2=max(tmp); +fprintf(1,'\nAll betas Positive: %3.0f eligible voxels with same signs, Max chisq(%2.0f) = %3.2f, p = %3.4f (corrected = %3.4f)\n',s,mynum,s2,1-chi2cdf(s2,mynum),(1-chi2cdf(s2,mynum)).*k); + +tmp=z .* neg; tmp=tmp(:); tmp(tmp==0 | isnan(tmp)) = []; s = length(tmp); s2=max(tmp); +fprintf(1,'All betas Negative: %3.0f eligible voxels with same signs, Max chisq(%2.0f) = %3.2f, p = %3.4f (corrected = %3.4f)\n',s,mynum,s2,1-chi2cdf(s2,mynum),(1-chi2cdf(s2,mynum)).*k); + +diary off + +[d,f,e]=fileparts(outname); + +tmp = fullfile(d,[f 'omni_fisher.img']); VV = V(1); VV.fname = tmp; +spm_write_vol(VV,omnisig); + +warning off + +if any(omnisig(:)) + +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(VV.fname,0,clsize); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois(Timg,CLU,CLU); +save fisher_omni_clusters clusters +try cluster_orthviews(clusters,{[1 0 0]}), catch,end + +end + + +tmp = fullfile(d,[f 'pos_fisher.img']); VV = V(1); VV.fname = tmp; +spm_write_vol(VV,possig); + +if any(possig(:)) +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(VV.fname,0,clsize); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois(Timg,CLU,CLU); +clusters = chi2z(clusters,mynum); +[d,f] = fileparts(VV.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters CLU']) + montage_clusters([],clusters,{'r'}); + clpos = clusters; +end + +end + + + +tmp = fullfile(d,[f 'neg_fisher.img']); VV = V(1); VV.fname = tmp; +spm_write_vol(VV,negsig); + + +if any(negsig(:)) +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(VV.fname,0,clsize); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois(Timg,CLU,CLU); +clusters = chi2z(clusters,mynum); +[d,f] = fileparts(VV.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters CLU']) + montage_clusters([],clusters,{'b'}); + clneg = clusters; +end +end + +% apply cluster size threshold +% ------------------------------------------------ +%[vol,numClusters,XYZ] = clusterSizeMask(clsize,vol); +%disp(['Found ' num2str(numClusters) ' clusters at k = ' num2str(clsize)]) +diary on +if exist('clpos') == 1, fprintf(1,'\nPOSITIVE %s',clpos(1).title), cluster_table(clpos);,end +if exist('clneg') == 1, fprintf(1,'\nNEGATIVE %s',clneg(1).title), cluster_table(clneg);,end +diary off + +warning on +return + + + + +function c = chi2z(c,df) + +for i = 1:length(c) + + tmp = c(i).Z; + tmp = 1 - chi2cdf(tmp,df); + c(i).Z = norminv(1 - tmp); + +end + +return + diff --git a/Image_computation_tools/mask_image.m b/Image_computation_tools/mask_image.m new file mode 100644 index 00000000..01dedba7 --- /dev/null +++ b/Image_computation_tools/mask_image.m @@ -0,0 +1,135 @@ +function dat = mask_image(img, mask, outname, varargin) + % masked_dat = mask_image(img, mask, outname, ['reverse']) + % + % Mask an image file (img) with a mask (mask), and save in outname. + % zero and NaN values are considered invalid values, and so voxels with + % these values are considered "excluded" + % + % Optional arguments: + % 'minmask' : mask values less than next argument are excluded + % 'maxmask' : mask values greater than next argument are excluded + % 'minimg' : img values less than next argument are excluded + % 'maximg' : img values greater than next argument are excluded + % + % 'abs': impose min/max thresholds based on absolute values + % + % 'reverse' : make "reverse mask," including only previously excluded + % areas (values of zero or NaN) + % Note: applies to mask, not img values + % So values with 0 in img will always be 0, whether + % standard or "reverse" mask is used. + % + % 'binary' : make the image values binary (i.e., create a new mask) + % + % This function can handle images of different dimensions. The output + % image will use the dimensions of img. + % + % Examples: + % + % Create an image with non-zero numbers only where p-values in an image are greater than .05 + % img = 'X-M_pvals.img'; mask = 'X-M_pvals.img'; maxmask = .05; outname = 'notX_p05.img'; + % mask_image(img, mask, outname, 'reverse', 'maxmask', maxmask); + % spm_image('init', outname); + % + % mask_image(my_mean_image, 'functional_mask.img', ... + % 'functional_mask.img', 'minimg', cutoff, 'abs'); + % + % + % mask_image('n15_avgpet.img',EXPT.mask,'n15_avgpet_brain.img'); + + doreverse = 0; + minmask = 0; + maxmask = NaN; + minimg = 0; + maximg = NaN; + doabs = 0; + dobinary = 0; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'reverse', doreverse = 1; + + case {'abs', 'absolute'}, doabs = 1; + + case {'minmask'}, minmask = varargin{i + 1}; + + case {'maxmask'}, maxmask = varargin{i + 1}; + + case {'minimg'}, minimg = varargin{i + 1}; + + case {'maximg'}, maximg = varargin{i + 1}; + + case {'binary'}, dobinary = 1; + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + V = spm_vol(img); v = spm_read_vols(V); + + %M = spm_vol(mask); %m = spm_read_vols(M); + m = scn_map_image(mask, img); + + % apply threshold(s), if any + % ------------------------------------------- + if doabs, absstr = ' (absolute value of) '; else absstr = [' ']; end + + if minmask + fprintf('Excluding voxels with%smask values less than %3.4f from mask area.\n', absstr, minmask); + + if doabs, to_use = abs(m); else to_use = m; end + m(to_use < minmask) = 0; + end + + if ~isnan(maxmask) + fprintf('Excluding voxels with%smask values greater than %3.4f from mask area.\n', absstr, maxmask); + + if doabs, to_use = abs(m); else to_use = m; end + m(to_use > maxmask) = 0; + end + + if minimg + fprintf('Excluding voxels with%simg values less than %3.4f from mask area.\n', absstr, minimg); + + if doabs, to_use = abs(v); else to_use = v; end + v(to_use < minimg) = 0; + end + + if ~isnan(maximg) + fprintf('Excluding voxels with%simg values greater than %3.4f from mask area.\n', absstr, maximg); + + if doabs, to_use = abs(v); else to_use = v; end + v(to_use > maximg) = 0; + end + + + + % Mask + % ------------------------------------------- + if doreverse + % Reverse mask + fprintf('Creating reverse mask.\n'); + dat = double(v .* (abs(m) == 0 | isnan(m))); + else + % Standard mask + dat = double(v .* (abs(m) > 0)); + + end + V.fname = outname; + + if dobinary + disp('Making output image binary') + dat = double(abs(dat) > 0) & ~isnan(dat); + end + + fprintf('Writing %s\n', V.fname); + + spm_write_vol(V,dat); + + % end + + return + diff --git a/Image_computation_tools/mask_intersection.m b/Image_computation_tools/mask_intersection.m new file mode 100755 index 00000000..fe616182 --- /dev/null +++ b/Image_computation_tools/mask_intersection.m @@ -0,0 +1,67 @@ +function [vol,V,XYZ,clusters,Q] = mask_intersection(clsize,outname,i1,i2,varargin) +% function [vol,V,XYZ,clusters,Q] = mask_intersection(clsize,outname,i1,i2,varargin) +% by Tor Wager +% +% empty i1 prompts for graphic selection of filenames +% extra arguments are more file names for 3 - n-way intersection +% empty outname prompts for entry of output img file name +% + +% get file names and calcstr to evaluate +% ------------------------------------------------ + +calcstr = 'i1 .* i2'; + +if isempty(i1), + P = spm_get(Inf,'*.img','Select binary mask images to intersect',pwd,0); + + if size(P,1) < 1, error('Must select at least 2 images'), end + + for i = 3:size(P,1) + calcstr = [calcstr ' .* i' num2str(i)]; + end +else + P = str2mat(i1,i2); +end + + +for i = 1:length(varargin) + P = str2mat(P,varargin{i}); + calcstr = [calcstr ' .* i' num2str(i+2)]; +end + +disp(calcstr) +P + +% make the intersection +% ------------------------------------------------ +Q = spm_imcalc_ui(P,outname,calcstr); + + +% load the intersection mask file +% ------------------------------------------------ +[vol,hdr] = readim2(Q(1:end-4)); +V = spm_vol(Q); + +% apply cluster size threshold +% ------------------------------------------------ +[vol,numClusters,XYZ] = clusterSizeMask(clsize,vol); +disp(['Found ' num2str(numClusters) ' clusters at k = ' num2str(clsize)]) + + +% write the size-filtered results back to the file +% ------------------------------------------------ +V = spm_write_vol(V,vol); + + +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(V.fname,.5,clsize); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois([],CLU,CLU); +[d,f] = fileparts(V.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters CLU']) +end + +return + diff --git a/Image_computation_tools/mask_intersection2.m b/Image_computation_tools/mask_intersection2.m new file mode 100755 index 00000000..50dc8c03 --- /dev/null +++ b/Image_computation_tools/mask_intersection2.m @@ -0,0 +1,74 @@ +function [clusters,vol,V,XYZ,Q] = mask_intersection2(clsize,outname,P,calcstr) +% function [clusters,vol,V,XYZ,Q] = mask_intersection2(clsize,outname,P,calcstr) +% by Tor Wager +% +% empty P prompts for graphic selection of filenames +% extra arguments are more file names for 3 - n-way intersection +% empty outname prompts for entry of output img file name +% +% this version allows contrasts +% last argument, calcstr, is the string to evaluate in imcalc +% +% e.g., intersection of 4 images: +% 'i1 & i2 & i3 & i4 & ~(isnan(i1) | isnan(i2) | isnan(i3) | isnan(i4))' +% e.g., 'i1 & i2 & ~i3 & ~i4 & ~(isnan(i1) | isnan(i2)) +% = intersection of i1 and i2 and not i3 and not i4 +% or 'i1 & i2 & isnan(i3) & isnan(i4) & ~(isnan(i1) | isnan(i2))' +% ... for nan masked images +% +% cl = mask_intersection2(5,'intersect001.img',P,'i1 & i2 & i3 & i4 & ~(isnan(i1) | isnan(i2) | isnan(i3) | isnan(i4))'); + +% get file names and calcstr to evaluate +% ------------------------------------------------ + +if isempty(P) + P = spm_get(Inf,'*.img','Select binary mask images to intersect',pwd,0); + + if size(P,1) < 1, error('Must select at least 2 images'), end + + for i = 3:size(P,1) + calcstr = [calcstr ' .* i' num2str(i)]; + end +end + +disp(calcstr); +disp(P); + +% make the intersection +% ------------------------------------------------ +Q = spm_imcalc_ui(P,outname,calcstr); + + +% load the intersection mask file +% ------------------------------------------------ +V = spm_vol(Q); +vol = spm_read_vols(V); + +% apply cluster size threshold +% ------------------------------------------------ +if isempty(clsize) || clsize == 0 + vol = double(vol); +else + if clsize > 0 + [vol,numClusters,XYZ] = clusterSizeMask(clsize,vol); + disp(['Found ' num2str(numClusters) ' clusters at k = ' num2str(clsize)]) + end +end + + +% write the size-filtered results back to the file +% ------------------------------------------------ +V = spm_write_vol(V,vol); + + +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(V.fname,.5,clsize); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois([],CLU,CLU); +[d,f] = fileparts(V.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters CLU']) +end + +return + diff --git a/Image_computation_tools/mask_stouffer.m b/Image_computation_tools/mask_stouffer.m new file mode 100755 index 00000000..05c5258a --- /dev/null +++ b/Image_computation_tools/mask_stouffer.m @@ -0,0 +1,107 @@ +function [vol,V,XYZ,clusters,Q] = mask_stouffer(clsize,outname,k,Pimg,Timg) +% function [vol,V,XYZ,clusters,Q] = mask_stouffer(clsize,outname,k,Pimg,Timg) +% by Tor Wager +% +% This function performs combination of p-values across images using the +% Stouffer method (see refs below). +% +% Inputs: +% Pimg string mtx of p-value image names +% Timg t-value img names, used to ensure signs are same across all tests +% k num voxels/num comparisons +% if empty, uses # of non-zero, non-NaN values in images +% +% empty i1 prompts for graphic selection of filenames +% extra arguments are more file names for 3 - n-way intersection +% empty outname prompts for entry of output img file name +% +% Described in: +% Lazar, N. A., Luna, B., Sweeney, J. A., & Eddy, W. F. (2002). +% Combining brains: a survey of methods for statistical pooling +% of information. Neuroimage, 16(2), 538-550. +% +% Stouffer, S. A., Suchman, E. A., DeVinney, L. C., Star, S. A., and +% Williams, R. M. 1949. The American Soldier: Vol. I. Adjustment +% During Army Life. Princeton University Press, Princeton. +% +% Pimg=get_filename2('rob*\*_p_*0002.img'); Timg = get_filename2('rob*\*_tmap_0002.img') +% + +% get file names and calcstr to evaluate +% ------------------------------------------------ + +if isempty(Pimg),Pimg = spm_get(Inf,'*.img','Select p-value images',pwd,0);,end +if isempty(Timg),Timg = spm_get(Inf,'*.img','Select t-value images',pwd,0);,end + +V =spm_vol(Pimg); v = spm_read_vols(V); +Vt =spm_vol(Timg); vt = spm_read_vols(Vt); + +pos = all(vt>0,4); +neg = all(vt<0,4); + +if isempty(k) + mask = all(~isnan(v) & v ~= 0,4); + k = sum(mask); +end + +[z,p,sig] = stouffer(v,.05 ./ k); + +p = p(:); p(p==0 | isnan(p)) = []; +fprintf('\nMinimum combined p-value: %3.4f\n', min(p)) + +possig = z .* pos .* sig; +negsig = z .* neg .* sig; + +tmp=possig(:); s = sum(possig(~isnan(possig))); +tmp=negsig(:); sn = sum(negsig(~isnan(negsig))); +fprintf(1,'Positive: %3.0f voxels, Negative: %3.0f voxels\n',s,sn); + + +tmp=z .* pos; tmp=tmp(:); tmp(tmp==0 | isnan(tmp)) = []; s = length(tmp); s2=max(tmp); +fprintf(1,'\nPositive: %3.0f eligible voxels with same signs, Max Z = %3.2f, p = %3.4f\n',s,s2,normcdf(1-s2)); + +tmp=z .* neg; tmp=tmp(:); tmp(tmp==0 | isnan(tmp)) = []; s = length(tmp); s2=max(tmp); +fprintf(1,'Negative: %3.0f eligible voxels with same signs, Max Z = %3.2f, p = %3.4f\n',s,s2,normcdf(1-s2)); + + +s = sum(possig(~isnan(possig))); +tmp=negsig(:); sn = sum(negsig(~isnan(negsig))); +fprintf(1,'\nPositive: %3.0f voxels, Negative: %3.0f voxels\n',s,sn); + +[d,f,e]=fileparts(outname); +tmp = fullfile(d,[f '_stoufZ.img']); VV = V(1); VV.fname = tmp; +spm_write_vol(VV,possig); + +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(VV.fname,0,0); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois([],CLU,CLU); +[d,f] = fileparts(VV.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters CLU']) +end + + +tmp = fullfile(d,[f '_stoufZ_pos_thresh.img']); VV = V(1); VV.fname = tmp; +spm_write_vol(VV,negsig); + +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +CLU = mask2struct(VV.fname,0,0); % should be 0 or 1, so .5 will get all 1's +clusters = tor_extract_rois([],CLU,CLU); +[d,f] = fileparts(VV.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters CLU']) +end + + +% apply cluster size threshold +% ------------------------------------------------ +%[vol,numClusters,XYZ] = clusterSizeMask(clsize,vol); +%disp(['Found ' num2str(numClusters) ' clusters at k = ' num2str(clsize)]) + + + + +return + diff --git a/Image_computation_tools/mask_union.m b/Image_computation_tools/mask_union.m new file mode 100755 index 00000000..60185217 --- /dev/null +++ b/Image_computation_tools/mask_union.m @@ -0,0 +1,77 @@ +function [vol,V,XYZ,clusters,Q] = mask_union(clsize,outname,i1,i2,varargin) +% function [vol,V,XYZ,clusters,Q] = mask_union(clsize,outname,i1,i2,varargin) +% by Tor Wager +% +% empty i1 prompts for graphic selection of filenames +% extra arguments are more file names for 3 - n-way intersection +% empty outname prompts for entry of output img file name +% + +if isempty(clsize), clsize = 0;,end +XYZ = []; vol = []; V = []; clusters = []; + +% get file names and calcstr to evaluate +% ------------------------------------------------ + +calcstr = '(i1 > 0) | (i2 > 0)'; +%calcstr2 = ' & (~isnan(i1) & ~isnan(i2)'; + +if isempty(i1), + P = spm_get(Inf,'*.img','Select binary mask images to get union of',pwd,0); + + if size(P,1) < 1, error('Must select at least 2 images'), end + + for i = 3:size(P,1) + calcstr = [calcstr ' | (i' num2str(i) ' > 0)']; + %calcstr2 = [calcstr2 ' & ~isnan(i' num2str(i) ')']; + end +else + P = str2mat(i1,i2); +end + + +for i = 1:length(varargin) + P = str2mat(P,varargin{i}); + calcstr = [calcstr ' | (i' num2str(i+2) ' > 0)']; + %calcstr2 = [calcstr2 ' & ~isnan(i' num2str(i+2) ')']; +end + +% make the intersection (union, in this case) +% ------------------------------------------------ +%calcstr = [calcstr ')']; +%calcstr2 = [calcstr2 ')']; + +Q = spm_imcalc_ui(P,outname,[calcstr]); +V = spm_vol(Q); + +% apply cluster size threshold +% ------------------------------------------------ +if clsize > 0 + % load the intersection mask file + % ------------------------------------------------ + [vol,hdr] = readim2(Q(1:end-4)); + [vol,numClusters,XYZ] = clusterSizeMask(clsize,vol); + disp(['Found ' num2str(numClusters) ' clusters at k = ' num2str(clsize)]) + + % write the size-filtered results back to the file + % ------------------------------------------------ + vol = double(vol); + V = spm_write_vol(V,vol); +end + + + + + +% make a structure like SPM.mat which can be used to extract clusters +% ------------------------------------------------ +%CLU = mask2struct(V.fname,.5,clsize); % should be 0 or 1, so .5 will get all 1's +%clusters = tor_extract_rois([],CLU,CLU); +clusters = mask2clusters(V.fname); +[d,f] = fileparts(V.fname); +if ~isempty(clusters) + eval(['save ' f '_clusters clusters']) +end + +return + diff --git a/Image_computation_tools/mean_image.m b/Image_computation_tools/mean_image.m new file mode 100644 index 00000000..89b5b022 --- /dev/null +++ b/Image_computation_tools/mean_image.m @@ -0,0 +1,370 @@ +% OUT = mean_image(input file names, output file name, [weights], [string command args]); +% +% Creates a weighted mean of images based on weights for each image +% +% String arguments: +% 'normlike' : do in style of mean_warped_image, for norm toolbox; +% ignore other string arguments +% +% 'reweight' : weight by distance from median in 2nd round of averaging +% 'plot' : plot output +% 'sharpen' : segments and smooths within tissue classes +% +% Examples: +% w = filenames('w*T1.img', 'char') +% w = mean_image(w, 'final_mean.img', [], 'normlike'); % trimmed, weighted best 50% +% +% worig = filenames('hr*/wT1.img', 'char') % no weights +% w = mean_image(worig, 'orig_mean_noweights.img', []); +% +% +% mean_image(DB.PP, 'Activation.img', DB.studyweight); +% +% mean_image(P, 'mean_wmeanw1007.img', ones(size(P, 1), 1), 'sharpen', 'plot', 'reweight'); +% +% P = filenames('w*mr.img', 1); +% +% Enter empty inputs for default settings. +% +% OUT.P = filenames +% OUT.dist + +function OUT = mean_image(VP, Pout, w, varargin) + % -------------------------------------- + % inputs + % -------------------------------------- + dosharp = 0; + dodist = 0; + doplot = 0; + doreweight = 0; + donormlike = 0; + + dozscore = 0; + + for i = 1:length(varargin) + if ischar(varargin{i}) + if strcmp(varargin{i}, 'sharpen'), dosharp = 1; end + %if strcmp(varargin{i}, 'dist'), dodist = 1; end + if strcmp(varargin{i}, 'plot'), doplot = 1; end + if strcmp(varargin{i}, 'reweight'), doreweight = 1; end + if strcmp(varargin{i}, 'normlike'), donormlike = 1; end + + if strcmp(varargin{i}, 'zscore'), dozscore = 1; end + end + end + + if isempty(VP) + VP = spm_vol(spm_get(1, '*', 'Select images')); + else + if(iscellstr(VP)), + VP = char(VP); + end + P = VP; + VP = spm_vol(VP); + end + n = length(VP); + + if ~exist('Pout', 'var') || isempty(Pout), Pout = 'mean.nii'; end + if ~exist('w', 'var') || isempty(w), w = ones(n, 1); end + + OUT.P = VP; + + % -------------------------------------- + % do mean in style of mean_warped_image + % -------------------------------------- + % output is weights + if donormlike + OUT = do_mean_like_mean_warped_image(P, Pout); + return + end + + % -------------------------------------- + % make mean image + % -------------------------------------- + + status_string = sprintf('%3d', 0); + fprintf(['Percent done: ' status_string]); + + + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + case 'SPM8' + spm_defaults() + end + + % initialize output + V = VP(1); + V.fname = Pout; + V.n(1) = 1; % write to first volume + + dat = zeros(V.dim(1:3)); + + % read and weight + + for i = 1:n + v = iimg_read_vols(VP(i)); + + if dozscore + v = center_scale(v); + end + + dat = dat + v .* w(i); + + erase_string(status_string); + status_string = sprintf('%3d', round(i/n*100)); + fprintf(status_string); + end + + % divide by N + dat = dat ./ n; + + fprintf('\n'); + + % write output + %strrep required to make file extension correct + V.fname = strrep(V.fname, '.nii,1', '.nii'); + V.fname = strrep(V.fname, '.img,1', '.img'); + spm_write_vol(V, dat); + + if doplot + z = round(V.dim(3)./2); + f1 = figure; + imagesc(dat(:,:,z)); + axis image; + axis off; + title('Mean'); drawnow + end + + + + + + % -------------------------------------- + % Reweight + % -------------------------------------- + + if doreweight + fprintf(1, ' Reweighting.000'); + + for i = 1:n + fitness(i) = norm_eval_fitness(tempdat, normdat, 0); + end + + imgMedian = zeros(n, 1); + imgMean = imgMedian; + imgStd = imgMedian; + + medianDev = zeros(n, 5); + newdat = zeros(V.dim(1:3)); + + % subtract median from mean image + [dat, datMedian] = subtract_median(dat); + + % get medians and subtract + for i = 1:n + + fprintf(1, '\b\b\b%03d', i); + + v = spm_read_vols(spm_vol(deblank(P(i,:)))); + + [v, imgMedian(i)] = subtract_median(v); + + %imgMean(i) = mean(vi); + %imgStd(i) = std(vi); + + % distance from median-centered old mean image + vi = v - dat; vi = abs(vi(:)); + vi(isnan(vi) | vi==0) = []; + if isempty(vi), warning('Image is EXACTLY the median image.'); end + + % get median deviation, and 5th and 95th prctiles + medianDev(i,:) = prctile(vi, [50 5 20 80 95]); + + % get new weights based on distance from mean image + w(i) = datMedian./medianDev(i, 1); + + newdat = newdat + v .* w(i); + + end + + newdat = newdat .* sum(w); % normalize to keep in same scale + + whnull = isnan(newdat) | newdat==0; + %newdat = newdat + mean(imgMedian); % add median back in to average + newdat(whnull) = 0; + + dat = newdat; + OUT.medianDev = medianDev; + OUT.prctiles = [50 5 20 80 95]; + + if doplot + tor_fig; + plot(medianDev(:,1), 'o-'); + ylabel('Med. Abs. Dev from median image'); + xlabel('Image number'); grid on; + + figure(f1); subplot(4, 1, 2); + imagesc(dat(:,:,z)); + axis image; + axis off; + title('Reweighted');drawnow + end + + % write output + spm_write_vol(V, dat); + end + + + % -------------------------------------- + % Sharpen + % -------------------------------------- + + if dosharp + fprintf(1, ' Sharpening: segmenting.'); + + defaults.segment.estimate.reg = .1;% was .01; + VO = spm_segment(Pout, which('T1.mnc'), defaults.segment); + + spm_write_vol(VO(1), VO(1).dat); + spm_write_vol(VO(2), VO(2).dat); + spm_write_vol(VO(3), VO(3).dat); + + fprintf(1, ' Lightening white matter.'); + + thr = prctile(VO(2).dat(:), 90); + whiteMask = double(VO(2).dat > thr); + + mystd = .2 .* nanstd(dat(:)); + dat = dat + whiteMask .* mystd; + + % write output + name = [Pout(1:end-4) '_sharp.img']; + V.fname = name; + spm_write_vol(V, dat); + + % make brain mask + brainMask = double(VO(1).dat>0 | VO(2).dat>0); + + name = [Pout(1:end-4) '_brain.img']; + V.fname = name; + spm_write_vol(V, brainMask); + + ovl = dat .* brainMask; + name = [Pout(1:end-4) '_overlay.img']; + V.fname = name; + spm_write_vol(V, ovl); + + if doplot + figure(f1); subplot(4, 1, 3); + imagesc(dat(:,:,z)); + axis off; + axis image; + title('Sharpened');drawnow + + subplot(4, 1, 4); + imagesc(ovl(:,:,z)); + axis off; + axis image; + title('Overlay');drawnow + end + end +end + + + +function [dat, datMedian] = subtract_median(dat) + vi = dat(:); + vi(isnan(vi) | vi==0) = []; + datMedian = median(vi); + + whnull = isnan(dat) | dat==0; + + dat = dat - datMedian; + dat(whnull) = 0; +end + + + +function v = center_scale(v) + vi = v(:); + vi(isnan(vi) | vi==0) = []; + v = (v - mean(vi)) ./ std(vi); +end + + + + +function weights = do_mean_like_mean_warped_image(P, outname) + spm_defaults; + defaults.analyze.flip = 0; + [volTemplate, normdat] = iimg_read_img(P); + + t1 = clock; + n = size(P, 1); + + % Provisional mean + % ------------------------------------ + fprintf(1, 'Initial. '); + + normdat(isnan(normdat)) = 0; + + % convert in-mask voxels to mean = gm, so averaging is not distorted by + % overall intensity differences among images + % and fitness weights determine relative contribution of images + means = mean(normdat); % mean of each image + gm = mean(means); % global mean + scalef = gm ./ means; % scaling factor to norm each image to grand mean + + for i = 1:n + normdat(:,i) = normdat(:,i) .* scalef(i); + end + + meandat = mean(normdat, 2); + vox = size(meandat, 1); + + + fprintf(1, 'Weighted mean of best 50%%. '); + % Weights based on closeness to mean + % ------------------------------------ + for i = 1:n + fitness(i) = norm_eval_fitness(meandat, normdat(:,i), 0); + end + + mfit = nanmedian(fitness); + weights = fitness; + weights(weights < mfit) = 0; + + weights = weights ./ sum(weights); + + if all(weights == 0) + warning('All weights are zero!') + weights = ones(1, n); + end + + % Weighted mean + % ------------------------------------ + meandat = zeros(vox, 1); + for i = 1:n + + if weights(i) > 0 % just to save time + meandat = meandat + weights(i) .* normdat(:,i); + end + + end + + fprintf(1, 'Done: Writing\n'); + % Write output image + % This will be the new normalization template + % ------------------------------------ + meandata = iimg_reconstruct_3dvol(meandat, volTemplate, 'outname', outname); + + fprintf(1, 'Mean warped image completed: %3.0f s\n', etime(clock, t1)); + + fprintf(1, '-----------------------------\n'); +end \ No newline at end of file diff --git a/Image_computation_tools/percent_sig_image.m b/Image_computation_tools/percent_sig_image.m new file mode 100644 index 00000000..462e1ded --- /dev/null +++ b/Image_computation_tools/percent_sig_image.m @@ -0,0 +1,43 @@ +function Vo = percent_sig_image(imgs, baseimg, outname) +% function Vo = percent_sig_image(imgs, baseimg, outname) +% +% Tor Wager +% +% Creates a percent signal change image saved in outname by dividing each +% image by baseimg + + +V = spm_vol(imgs); +vols = spm_read_vols(V); + +Vb = spm_vol(baseimg); +basei = spm_read_vols(Vb); + +basei(basei <= 10*eps) = NaN; + +if size(basei, 4) > 1 + error('Baseline image cannot be 4-D') +end + +for i = 1:size(vols, 4) % for each volume if 4-D + + vols(:, :, :, i) = vols(:, :, :, i) ./ basei; + +end + + +% ------------------------- +% write +% ------------------------- +fprintf('Writing:') +for i = 1:size(vols, 4) + [d,f,e] = fileparts(V(i).fname); + + Q = fullfile(d, outname); + fprintf(' %s\n', Q) + Vo = V(i); + Vo.fname = Q; + Vo.descrip = ['% Change from ' Vb.fname]; + + spm_write_vol(Vo, vols(:, :, :, i)); +end \ No newline at end of file diff --git a/Image_computation_tools/reslice_imgs.m b/Image_computation_tools/reslice_imgs.m new file mode 100755 index 00000000..1da740cc --- /dev/null +++ b/Image_computation_tools/reslice_imgs.m @@ -0,0 +1,86 @@ +% function [P, reslicedImgs] = reslice_imgs(sampleTo, resliceThis, [domask], [overwrite]) +% +% arguments are file names of .img files +% if empty, select from GUI +% +% if domask, recalculates a 1 or 0 mask for each image file in resliceThis +% if overwrite, overwrite the original instead of prepending an 'r' +% +% Example: +% Reslice a mask image into the space of some functional images, and move +% to the current directory +% ----------------------------------------------------------------------- +% [tmp, maskname] = reslice_imgs(image_names(1, :), maskname, 1); +% eval(['!mv ' maskname ' ./']) +% eval(['!mv ' maskname(1:end-4) '.hdr ./']) + +function [P, reslicedImgs] = reslice_imgs(sampleTo, resliceThis, varargin) + domask = 0; + overwrite = 0; + + % using b-spline or fourier really screwed up some of my masks, so trilinear may + % be better! + flags = struct('interp', 1, ... % b-spline + 'mask', 0, ... % do not mask + 'mean', 0, ... % do not write mean image + 'hold', -1, ... % i don't think this is used anymore + 'which', 1, ... % reslice 2nd-nth only + 'wrap', [0 0 0]' ... % the default; don't know what this is + ); + + if length(varargin) > 0 + domask = varargin{1}; + end + if length(varargin) > 1 + overwrite = varargin{2}; + end + + if isempty(sampleTo) + sampleTo = spm_get(1, '*.img', 'Select image with desired dimensions', pwd, 0); + end + + if isempty(resliceThis) + resliceThis = spm_get(Inf, '*.img', 'Select image(s) to resample', pwd, 0); + end + + P = str2mat(sampleTo, resliceThis); + spm_reslice(P, flags) + + + if domask + disp('Re-thresholding masks...') + + for i = 1:size(resliceThis, 1) + [d, f, e] = fileparts(deblank(resliceThis(i, :))); + resliced_img = new_resliced_name(overwrite, d, f, e); + spm_imcalc_ui(resliced_img, resliced_img, 'i1>0'); + end + end + + for i = 1:size(P, 1)-1 + [d, f, e] = fileparts(deblank(P(i+1, :))); + resliced_img = new_resliced_name(overwrite, d, f, e); + + if(overwrite) + movefile(fullfile(d, ['r' f e]), resliced_img); + movefile(fullfile(d, ['r' f '.hdr']), [resliced_img(1:end-4) '.hdr']); + if(exist(fullfile(d, ['r' f '.mat']), 'file')) + movefile(fullfile(d, ['r' f '.mat']), [resliced_img(1:end-4) '.mat']); + end + end + + if i==1 + reslicedImgs = resliced_img; + else + reslicedImgs = strvcat(reslicedImgs, resliced_img); + end + end +end + +function reslice_name = new_resliced_name(overwrite, d, f, e) + if(overwrite) + reslice_name = fullfile(d, [f e]); + else + reslice_name = fullfile(d, ['r' f e]); + end +end \ No newline at end of file diff --git a/Image_computation_tools/reverse_mask.m b/Image_computation_tools/reverse_mask.m new file mode 100755 index 00000000..6142b09a --- /dev/null +++ b/Image_computation_tools/reverse_mask.m @@ -0,0 +1,10 @@ +function reverse_mask(inname,outname) +% reverse_mask(inname,outname) +% +% Changes 1 and greater's to 0's in an .img file, and vice versa +% NaNs are still NaNs. + +Q = spm_imcalc_ui(inname,outname,'abs(i1) < eps'); + +return + diff --git a/Image_computation_tools/scn_num_volumes.m b/Image_computation_tools/scn_num_volumes.m new file mode 100644 index 00000000..e0c35c4d --- /dev/null +++ b/Image_computation_tools/scn_num_volumes.m @@ -0,0 +1,64 @@ +% [n, V] = scn_num_volumes(V) +% input V is spm_vol structure, or image filename +% output V is spm_vol structure +% +% counts number of images n stored in a 4-D file +% .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% how many are in this volume? +% +% Adapted (copied) from code by Tom Nichols. +% % ------------------------------------------------------------------- +% % function n = scn_num_volumes(V) +% % +% % +% % if ~isstruct(V) +% % V = spm_vol(V); +% % spm_close_vol(V); +% % end +% % +% % fp = fopen(V.fname); +% % fseek(fp,0,'eof'); +% % Len = ftell(fp); +% % fclose(fp); +% % +% % switch(spm('Ver')) +% % case 'SPM2' +% % mydt = V.dim(4); +% % case 'SPM5' +% % mydt = V.dt(1); +% % otherwise +% % error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); +% % end +% % n = Len/(prod(V.dim(1:3))*spm_type(mydt,'bits')/8); +% % +% % end + +% search index words: number of volumes, nvol + +% .img files can contain multiple volumes (indexed by .n in spm_vol structure). +% how many are in this volume? +% % ------------------------------------------------------------------- +function [n, V] = scn_num_volumes(V) + % number of images n stored in this file + + if ~isstruct(V) + V = spm_vol(V); + %spm_close_vol(V); % spm2 + end + + switch(spm('Ver')) + case 'SPM2' + fp = fopen(V.fname); + fseek(fp,0,'eof'); + Len = ftell(fp); + fclose(fp); + n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); + + case {'SPM5' 'SPM8'} + n = length(V); + + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + +end \ No newline at end of file diff --git a/Image_computation_tools/scn_write_plane.m b/Image_computation_tools/scn_write_plane.m new file mode 100644 index 00000000..954343fa --- /dev/null +++ b/Image_computation_tools/scn_write_plane.m @@ -0,0 +1,200 @@ +function V = scn_write_plane(filenames_or_V, dat, wh_slice, varargin) +% V = scn_write_plane(filenames_or_V, dat, wh_slice, [exampleV]) +% +% filenames_or_V is string matrix or cell array of names, or structures created with spm_vol(names) +% dat is 3-D array of data, vox x vox x slices, with data on 3rd dim being +% written to separate image files +% wh_slice slice number (voxel space) +% exampleV is optional, but required if filenames are passed in. +% +% Images can be 3D or 4D (4D is possible with SPM2+ using ,xx indexing, or SPM5+). +% +% Write a plane, given filenames or spm_vol structures and 4-D data with +% the slice to write. +% +% SPM2/5 compatible, and creates image if necessary from file name and +% example V structure. +% +% Tor Wager, March 2008 +% +% Examples: +% P = char({'one.img', 'two.img', 'three.img'}) +% dat = randn(64, 64, 3); +% Vout = scn_write_plane(P, dat, wh_slice, V) +% spm_image('init', Vout(1).fname); +% +% +% Notes on SPM5 and why we need to do what we do the way we do it: +% Write data...in SPM5, by accessing file_array object in +% V.private directly (with spm_write_plane. spm_write_vol +% first creates NIFTI obj/file array and then uses spm_write_plane.) +% The file_array object points to data in +% the actual file, so when values are assigned to the array +% object, they are written directly in the file. +% These values depend on the offset and slope values (spm +% scaling factors!) in V.private.dat as well, so care must be +% taken to assign data to a file_array object with the correct +% scaling factors. This is why it is better to load the +% structure one wants to write to with spm_vol first, so that +% the name in V.private.dat.fname will be correct, and +% V.private.dat.scl_slope and ...inter will be correct as well. +%Vout = spm_vol(V(i)); % loads correct .private info from V.fname +% +% in SPM5, this simply assigns data in Vout.private.dat +% in SPM2, it does something different, but should be +% compatible, since spm_vol was used above... +% spm_write_plane(Vout, fsl(:, :, i), slicei); +% if isfield(V, 'dt'), Vout.dt = V.dt; end % SPM5 only +% if isfield(V, 'n'), Vout.n = V.n; end % SPM5 only +% Vout = spm_create_vol(Vout); +% else +% Vout = spm_vol(V(i).fname); +% end +% spm_write_plane(Vout, fsl(:, :, i), slicei); + +% ------------------------------------------------------ +% Make sure all images exist, and create if not +% Return V structure of spm_vol mapped image structures +% ------------------------------------------------------ + +if ~isstruct(filenames_or_V) + % Assume we have a string matrix of file names + % Make sure they exist... + % create volumes, if necessary + + + % Make sure char array, from cell... + if iscell(filenames_or_V), filenames_or_V = char(filenames_or_V{:}); end + + for i = 1:size(filenames_or_V, 1) + currentfilename = deblank(filenames_or_V(i, :)); + + if ~(exist(currentfilename, 'file')) + + fprintf('Creating Image: %3.0f ', i); + + if isempty(varargin) && ~exist('exampleV', 'var') + error('Must pass in example V struct!'); + elseif ~exist('exampleV', 'var') + exampleV = varargin{1}; + end + + % Create empty float image with no spm scaling (pinfo intercept is 0, + % slope is 1) + % -------------------------------------------------------------- + % V.fname - the filename of the image. + % V.dim - the x, y and z dimensions of the volume + % V.dt - A 1x2 array. First element is datatype (see spm_type). + % The second is 1 or 0 depending on the + % endian-ness. + % * 1 is big-endian, 0 is little-endian + % V.mat - a 4x4 affine transformation matrix mapping from + % voxel coordinates to real world coordinates. + % V.pinfo - plane info for each plane of the volume. + % V.pinfo(1,:) - scale for each plane + % V.pinfo(2,:) - offset for each plane + % The true voxel intensities of the jth image are given + % by: val*V.pinfo(1,j) + V.pinfo(2,j) + % V.pinfo(3,:) - offset into image (in bytes). + % If the size of pinfo is 3x1, then the volume is assumed + % to be contiguous and each plane has the same scalefactor + % and offset. + + Vout = struct('fname', currentfilename, 'mat', exampleV.mat, 'dim', exampleV.dim, 'pinfo', [1 0 0]'); + %if isfield(exampleV, 'dt'), Vout.dt = exampleV.dt; end % SPM5 only; data type + if isfield(exampleV, 'n'), Vout.n = exampleV.n; end % SPM5 only + + % Make sure datatype is float / float32 + switch(spm('Ver')) + case 'SPM2' + Vout.dim(4) = spm_type('float'); + + case 'SPM5' + Vout.dt = [spm_type('float32') 0]; + if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end % use endian-ness of example. + + case 'SPM8' + Vout.dt = [spm_type('float32') 0]; + if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end % use endian-ness of example. + + otherwise + error('Unknown SPM version.'); + end + + try + V(i) = spm_create_vol(Vout); + create_empty(V(i)); + + catch + try + % You cannot create only some volumes in a list, if + % others exist...work-around... + Vtmp = spm_create_vol(Vout); + Vtmp = spm_vol(Vtmp); + create_empty(Vtmp); + V(i) = spm_vol(Vtmp.fname); + catch + + disp('Cannot create vol. This could occur for several reasons:') + disp('1) you may not have write permission.') + disp('2) Other reasons...?'); + error('Quitting.'); + end + end + + else + % it exists; get it + V(i) = spm_vol(currentfilename); + end + end + + + +else + % fprintf('Checking spm_vol structures '); + % Make sure spm_vol structure (SPM5 only) + % alter data type if needed + switch(spm('Ver')) + case 'SPM2' + V = filenames_or_V; % Skip because hard to check. + case {'SPM5', 'SPM8'} + V = spm_vol(filenames_or_V); +% otherwise +% error('Fix or verify image writing for SPMxx. Not implemented yet.'); + otherwise + error('Unknown SPM version.'); + end +end + +str = sprintf('Writing slice data: %04d', 0); +fprintf('%s', str) + +% V is a structure array, one structure per filename +% For each file to write to...write a plane + +for i = 1:length(V) + if mod(i, 10) == 0, fprintf('\b\b\b\b%04d', i); end + + spm_write_plane(V(i), dat(:, :, i), wh_slice); +end + +erase_string(str) +%fprintf('\n'); + +end + + + +function create_empty(V) +% Write empty data +switch(spm('Ver')) + case 'SPM2' + spm_write_vol(V, zeros(V.dim(1:3))); + case {'SPM5', 'SPM8'} + % assign directly + for i = 1:length(V), V(i).private.dat(:, :, :) = 0; end + otherwise + error('Unknown SPM version.') +end +end + diff --git a/Image_computation_tools/tor_global.m b/Image_computation_tools/tor_global.m new file mode 100644 index 00000000..981b7cb0 --- /dev/null +++ b/Image_computation_tools/tor_global.m @@ -0,0 +1,142 @@ +% [g, gslice, stdslice, s]] = tor_global(P, [maskname]) +% +% Compute global image values within an optional mask +% alternative to spm_global +% +% Pass in a file name for mask: +% g = tor_global(P, 'my_gray_mask.img') +% +% Use Worsley's fmri_mask_thresh to implicitly mask based on first image: +% g = tor_global(P, 1); +% +% No masking, use whole image +% g = tor_global(P) +% +% +% OUTPUT +% -------------------------------- +% g: array mean intensity for each image +% gslice: 2D array with mean intensity for each slice +% stdslice: 2D array with std for each slice +% s: array std for each image + +function [g, gslice, stdslice, s] = tor_global(P, varargin) + + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + + case 'SPM8' + % spm_defaults is a function + spm_defaults() + + otherwise + % unknown SPM + disp('Unknown version of SPM!'); + spm_defaults() + end + + fprintf('getting globals.\t'); + + V = spm_vol(P); + num_vols = length(V); + mask = []; + + if ~isempty(varargin) + % we have some mask info + % If mask is left empty, then we will just not mask at all. + maskinfo = varargin{1}; + + if isscalar(maskinfo) && maskinfo % It's just a 1 or something, implicit mask + % ---------------------------------------------------------------------------------- + % * create mask from FIRST IMAGE and mask all subjects' values + % so that we show only those voxels existing for all subjects. + % ---------------------------------------------------------------------------------- + % it's a threshold + if ~exist('fmri_mask_thresh.m', 'file') + error('You must have fmri_mask_thresh.m from Keith Worsley''s FMRISTAT package.'); + end + mask_thresh = fmri_mask_thresh(V(1).fname); + mask_dat = spm_read_vols(V(1)); + mask = mask_dat > mask_thresh; % create mask data + clear mask_dat + + elseif ischar(maskinfo) + % ---------------------------------------------------------------------------------- + % * load mask, from FILE and mask all subjects' values + % so that we show only those voxels existing for all subjects. + % ---------------------------------------------------------------------------------- % + + fprintf('masking volumes.\t') + % maskinfo is a filename + mask = scn_map_image(maskinfo, V(1).fname); + mask = double(mask ~= 0 & ~isnan(mask)); + end + + end + + g = zeros(num_vols, 1); + gslice = []; + + + new_status = sprintf('Image %4d', 0); + fprintf(new_status); + for i = 1:num_vols + old_status = new_status; + new_status = sprintf('Image %d/%d', i, num_vols); + erase_and_display(old_status, new_status); + + [g(i), gslice(:,i), stdslice(:, i), s(i)] = get_global(V(i), mask); + end + + fprintf('\nFinished computing global on %d images.\n', num_vols); +end + + + +% V is a 3D image +% returns: +% g: mean intensity for the image +% gslice: array with mean intensity for each slice, +% stdslice: array with std for each slice +% s: std for image +function [g, gslice, stdslice,s] = get_global(V, mask) + dat = spm_read_vols(V); + + if ~isempty(mask), dat = dat .* mask; end + + + % slice-by-slice globals + z = size(dat, 3); %num of slices + gslice = zeros(z, 1); + + for i = 1:z + slice = dat(:,:,i); + slice = slice(:); + slice(isnan(slice) | slice == 0) = []; + + if ~isempty(slice) + gslice(i) = mean(slice); %mean value for slice + stdslice(i) = std(slice); % STD for slice + else + gslice(i) = 0; + stdslice(i) = 0; + end + end + + dat = dat(:); + dat(isnan(dat) | dat == 0) = []; + + g = mean(dat); %mean value for image + + s = std(dat); %STD for image + +end + + + diff --git a/Image_computation_tools/tor_spm_mean_ui.m b/Image_computation_tools/tor_spm_mean_ui.m new file mode 100755 index 00000000..d7c60604 --- /dev/null +++ b/Image_computation_tools/tor_spm_mean_ui.m @@ -0,0 +1,79 @@ +function tor_spm_mean_ui(P,varargin) +%tor_spm_mean_ui(Pinputnames,[outputname]) +% promts for a series of images and averages them +% FORMAT spm_mean_ui +%_______________________________________________________________________ +% +% spm_mean_ui simply averages a set of images to produce a mean image +% that is written as type int16 to "mean.img" (in the current directory). +% +% The images must have the same dimensions, orientations (as defined by +% the Origin header field or any associated *.mat files), and the same +% voxel sizes. +% +% This is not a "softmean" - zero voxels are treated as zero. +%_______________________________________________________________________ +% @(#)spm_mean_ui.m 2.4 John Ashburner, Andrew Holmes 98/10/21 +SCCSid = '2.4'; + +% Tor modified: select output file name +%----------------------------------------------------------------------- +if nargin > 1, + outname = varargin{1}; +else + outname = 'mean.img'; +end + + + +%-Say hello +%----------------------------------------------------------------------- +SPMid = spm('FnBanner',mfilename,SCCSid); + + +%-Select images & check dimensions, orientations and voxel sizes +%----------------------------------------------------------------------- +if nargin == 0 + fprintf('\t...select files') + P = spm_get(Inf,'.img','Select images to be averaged'); + fprintf(' ...mapping & checking files') +end + +Vi = spm_vol(P); + +n = prod(size(Vi)); +if n==0, fprintf('\t%s : no images selected\n\n',mfilename), return, end + +if n>1 & any(any(diff(cat(1,Vi.dim),1,1),1)&[1,1,1,0]) + error('images don''t all have same dimensions'), end +if any(any(any(diff(cat(3,Vi.mat),1,3),3))) + error('images don''t all have same orientation & voxel size'), end + + +%-Compute mean and write headers etc. +%----------------------------------------------------------------------- +fprintf(' ...computing') +Vo = struct( 'fname', outname,... + 'dim', [Vi(1).dim(1:3),4],... + 'mat', Vi(1).mat,... + 'pinfo', [1.0,0,0]',... + 'descrip', 'spm - mean image'); + +%-Adjust scalefactors by 1/n to effect mean by summing +for i=1:prod(size(Vi)) + Vi(i).pinfo(1:2,:) = Vi(i).pinfo(1:2,:)/n; end; + +%-Write basic header +spm_create_image(Vo); + +%-Use spm_add to do the donkey work +Vo.pinfo(1,1) = spm_add(Vi,Vo); + +%-Write header (complete with scaling information) +spm_create_image(Vo); + + +%-End - report back +%----------------------------------------------------------------------- +fprintf(' ...done\n') +fprintf('\tMean image written to file ''%s'' in current directory\n\n',Vo.fname) diff --git a/Image_space_tools/check_spm_mat.m b/Image_space_tools/check_spm_mat.m new file mode 100755 index 00000000..ef2efcd6 --- /dev/null +++ b/Image_space_tools/check_spm_mat.m @@ -0,0 +1,35 @@ +function [chk, clusters] = check_spm_mat(mat1,mat2,clusters) +%check_spm_mat(mat1,mat2,clusters) +% mat1 is from clusters, mat2 is functional (imgs to extract) + + +chk = mat1 - mat2; chk = chk(:); +chk = chk(1:end-1); % eliminate SPM scale factor +chk = any(chk); + +if chk + + % we need to get the correct voxel coords from mat2 (funct) into + % clusters, keeping the mm_coordinates the same! + VOL.M = mat2; + + for i = 1:length(clusters) + + clusters(i).XYZ = mm2voxel(clusters(i).XYZmm,VOL)'; % functional img space, cluster mm coordinates + + clusters(i).Z = ones(1,size(clusters(i).XYZ,2)); + clusters(i).XYZmm = voxel2mm(clusters(i).XYZ,mat2); + + clusters(i).M = mat2; + + clusters(i).voxSize = diag(clusters(i).M(1:3,1:3)'); + + clusters(i).numVox = size(clusters(i).XYZmm,2); + + % skip this, and clusters will have mm list of different length than + % voxel list (XYZ). data will be from voxel list. + %SPM.XYZmm = voxel2mm(SPM.XYZ,VOL.M); % slow, but gives unique voxels + end +end + +return \ No newline at end of file diff --git a/Image_space_tools/check_spm_matfiles.m b/Image_space_tools/check_spm_matfiles.m new file mode 100644 index 00000000..75f4a175 --- /dev/null +++ b/Image_space_tools/check_spm_matfiles.m @@ -0,0 +1,32 @@ +function [anybad] = check_spm_matfiles(P) +% [anybad] = check_spm_matfiles(P) +% +% Check a list of SPM-style images to see if they all have the same space, +% dims + +V=spm_vol(P); +for i=2:length(V), + chk = V(1).mat - V(i).mat; chk = chk(:); + chk = chk(1:end-1); % eliminate SPM scale factor + chk = any(chk); + notok(i) = chk; +end + +if any(notok) + wh = find(notok); + + disp(['The following images'' mat files differend from the first:']) + disp(num2str(wh)); + + disp(['First mat:']); + disp(V(1).mat); + + disp(['Bad mats:']); + for i = wh + disp(V(i).fname); + disp(V(i).mat); + end +end + +anybad = any(notok); +return diff --git a/Image_space_tools/img2voxel.m b/Image_space_tools/img2voxel.m new file mode 100755 index 00000000..595eb73b --- /dev/null +++ b/Image_space_tools/img2voxel.m @@ -0,0 +1,71 @@ +function [XYZ,XYZmm,val,V] = img2voxel(P,varargin) +% +% given a mask or filtered image file name, +% returns XYZ coordinates in voxels and mm +% of nonzero, non-NaN voxels +% +% and img values at these coordinates in val +% +% Tor Wager 02/04/02 +% Modified 2/15/06 to take raw data as well as image name +% + +XYZ = []; +XYZmm = []; +val = []; +V = []; + +% needed for raw data input only +if length(varargin) > 0, vmat = varargin{1}; V.mat = vmat;, end + +if isstr(P) +% it's an image name + + V = spm_vol(P); + + if length(V) > 1, error('Input only 1 image at once!'), end + + try + vol = spm_read_vols(V); + catch + disp(['filename is: ' V.fname]) + disp('Cannot read!') + return + end + +else + % it's loaded image data + vol = P; +end + + +% mask out zeros +% ------------------------------------------------------------------- +vol = double(vol); +vol(vol == 0) = NaN; + + +% get XYZ +% ------------------------------------------------------------------- +indx = find(~isnan(vol)); +[x,y,z] = ind2sub(size(vol),indx); + +XYZ = [x y z]'; +XYZ = XYZ(1:3,:); + + +% get XYZmm +% (add one to multiply by the constant shift (offset from edge) in mat) +% ------------------------------------------------------------------- +XYZ(4,:) = 1; + +XYZmm = V.mat * XYZ; + +XYZmm = XYZmm(1:3,:); + + +% get val +% ------------------------------------------------------------------- +val = vol(indx); + +return \ No newline at end of file diff --git a/Image_space_tools/mask2voxel.m b/Image_space_tools/mask2voxel.m new file mode 100755 index 00000000..57a3fc21 --- /dev/null +++ b/Image_space_tools/mask2voxel.m @@ -0,0 +1,30 @@ +% function voxels = mask2voxel(mask) +% convert from 3-D mask to voxel list in canonical orientation +% [i j k] = row, column, slice +% [x y z] in brain if brain is in analyze format +% (x is rows, y is columns, z is slices) +% +% Tor Wager, 10/17/01 +% +% Refactored to be orders of magnitude faster +% Matthew Davidson, 6/1/2006 + +function voxels = mask2voxel(mask) + + % Loop below is very slow. The find() function is much faster. + % + % voxels = []; + % index = 1; + % for i = 1:size(mask,1) + % for j = 1:size(mask,2) + % for k = 1:size(mask,3) + % if mask(i,j,k) > 0, voxels(index,:) = [i j k];,index = index+1;,end + % end + % end + % end + + [X Y Z] = ind2sub(size(mask), find(mask > 0)); + voxels = [X Y Z]; + voxels = sortrows(voxels); + +end \ No newline at end of file diff --git a/Image_space_tools/mm2voxel.m b/Image_space_tools/mm2voxel.m new file mode 100644 index 00000000..bbbe4bfe --- /dev/null +++ b/Image_space_tools/mm2voxel.m @@ -0,0 +1,69 @@ +% function XYZout = mm2voxel(XYZ, M, [uniqueness options]) +% +% VOL must contain M or mat field, which is mat file matrix in SPM +% XYZ can be either 3 rows or 3 columns +% by transforming to x y z in ROWS, coords in COLUMNS +% so if you have 3 coordinates, you'd better put the 3 coords +% in different COLS, with ROWS coding x, y, z! +% +% If a 3rd argument is entered, enter either: +% 1 uniqueness not required; allows repeats +% 2 unique and sorted +% Otherwise, voxels are unique and relative order is preserved. +% NB: Unless you require that relative order be preserved, *ALWAYS* set a +% uniqueness option. They are orders of magnitude faster. + +function XYZout = mm2voxel(XYZ, M, varargin) + XYZout = []; + + if isempty(XYZ) + return; + end + + if isstruct(M) + if isfield(M, 'mat') + M = M.mat; + elseif isfield(M, 'M') + M = M.M; + else + error('Unable to identify affine matrix in structure in M\n'); + end + end + + + % transpose XYZ so that x y z are in rows and coords are in cols + % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if size(XYZ,1) ~= 3 + XYZ = XYZ'; + end + + XYZout = [XYZ; ones(1, size(XYZ,2))]; + XYZout = (M\XYZout)'; + XYZout(:,4) = []; + XYZout = round(XYZout); + XYZout(XYZout == 0) = 1; + + % make sure voxel coords are integers, and there are no repetitions + % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + % XYZout = unique(round(XYZout),'rows'); + + % long method: do not sort (unique.m sorts rows) + if nargin < 3 + i = 1; + while i < size(XYZout,1) + whch = find(ismember(XYZout, XYZout(i,:), 'rows')); + if length(whch > 1) + XYZout(whch(2:end),:) = []; + end + i = i+1; + end + else + if varargin{1} == 2 + XYZout = unique(round(XYZout),'rows'); + elseif varargin{1} == 1 + % do nothing + else + warning('mm2voxel: Enter 1 or 2 for optional argument!') + end + end +end diff --git a/Image_space_tools/mni2tal.m b/Image_space_tools/mni2tal.m new file mode 100755 index 00000000..cb07a425 --- /dev/null +++ b/Image_space_tools/mni2tal.m @@ -0,0 +1,33 @@ +function outpoints = mni2tal(inpoints) +% Converts coordinates from MNI brain to best guess +% for equivalent Talairach coordinates +% FORMAT outpoints = mni2tal(inpoints) +% Where inpoints is N by 3 or 3 by N matrix of coordinates +% (N being the number of points) +% outpoints is the coordinate matrix with Talairach points +% Matthew Brett 10/8/99 + +dimdim = find(size(inpoints) == 3); +if isempty(dimdim) + error('input must be a N by 3 or 3 by N matrix') +end +if dimdim == 2 + inpoints = inpoints'; +end + +% Transformation matrices, different zooms above/below AC +upT = spm_matrix([0 0 0 0.05 0 0 0.99 0.97 0.92]); +downT = spm_matrix([0 0 0 0.05 0 0 0.99 0.97 0.84]); + +tmp = inpoints(3,:)<0; % 1 if below AC +inpoints = [inpoints; ones(1, size(inpoints, 2))]; +inpoints(:, tmp) = downT * inpoints(:, tmp); +inpoints(:, ~tmp) = upT * inpoints(:, ~tmp); +outpoints = inpoints(1:3, :); +if dimdim == 2 + outpoints = outpoints'; +end + + + + diff --git a/Image_space_tools/scn_map_image.m b/Image_space_tools/scn_map_image.m new file mode 100644 index 00000000..6380b80e --- /dev/null +++ b/Image_space_tools/scn_map_image.m @@ -0,0 +1,144 @@ +function [img, Vto] = scn_map_image(loadImg, sampleTo, varargin) + % [imgData, volInfo_mapto] = scn_map_image(loadImg, sampleTo, varargin) + % + % Tor Wager, July 2007 + % edited, Oct 2010, to take volInfo and fmri_mask_image inputs as well + % as image file names + % + % This function takes an image name in loadImg + % and loads the data, resampling to the space defined + % in the image sampleTo. The resampled image will retain + % the data type of the input image. + % + % Optional: + % 'write' followed by name of resampled image to write + % + % Compatible with SPM5/8. + % + % Input images can have the following formats: + % 1) String with name of image file (.img or .nii) + % 2) spm_vol-style V struct (see spm_vol) + % 3) volInfo struct (see iimg_read_img) + % 4) fmri_mask_image object (see fmri_mask_image) + % + % Examples: + % img = scn_map_image(EXPT.mask, EXPT.SNPM.P{1}(1,:), 'write', 'resliced_mask.img'); + % + % + + % optional input arguments + % ------------------------------------------------------- + if ~isempty(varargin) + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case {'write', 'name', 'outname'} + outname = varargin{i+1}; + varargin{i+1} = []; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + end + + + % image whose space to sample into + % ------------------------------------------------------- + if ischar(sampleTo) + sampleTo = deblank(sampleTo); + Vto = spm_vol(sampleTo); % volume to sample TO + + % Could already be a volInfo structure + elseif isa(sampleTo, 'image_vector') + Vto = sampleTo.volInfo; + + elseif isstruct(sampleTo) + Vto = sampleTo; + end + + Vto = Vto(1); % for SPM5 compat + + if isempty(Vto) + fprintf('%s is empty or missing.\n', sampleTo) + return + end + + Mto = Vto(1).mat; % mat to sample TO + + + % image to load and sample to new space + % ------------------------------------------------------- + if ischar(loadImg) + loadImg = deblank(loadImg); + Vmap = spm_vol(loadImg); % mask to resample + + % Could already be a volInfo structure + elseif isa(loadImg, 'image_vector') % also includes 'fmri_mask_image' 'fmri_data' + Vmap = loadImg.volInfo; + + elseif isstruct(loadImg) + Vmap = loadImg; + + end + + if isempty(Vmap) + fprintf('%s is empty or missing.\n', loadImg) + return + end + + Mmap = Vmap(1).mat; + + + + % image dimensions + % ------------------------------------------------------- + dim = Vto(1).dim(1:3); + + + % output image data + % ------------------------------------------------------- + img = zeros(dim); + + + % check for space change + % ------------------------------------------------------- + if strcmp(Vmap(1).fname, 'REMOVED: CHANGED SPACE') + + disp('You have re-mapped the space of this image, and cannot use scn_map_image.m') + disp('A common reason for this error is trying to map a data object to another space twice,') + disp('which cannot currently be done.'); + error('Unsupported operation: See message above.') + + end + + + % map slice-by-slice + % ------------------------------------------------------- + for j = 1:dim(3) + + Mslice = spm_matrix([0 0 j]); % Matrix specifying this slice + + Mtrans = Mto \ Mmap \ Mslice; % Affine mappping mtx: Mask -> TOvol + + img(:, :, j) = spm_slice_vol(Vmap(1), Mtrans, dim(1:2), [0 NaN]); + + + end + + % create image, if asked for + % ------------------------------------------------------- + if exist('outname', 'var') + Vout = Vto; + Vout.dt(1) = Vmap.dt(1); % keep data type of input image + Vout.fname = outname; + Vout = spm_create_vol(Vout); + + spm_write_vol(Vout, img); + end + + + +end diff --git a/Image_space_tools/scn_resample_voxel_size.m b/Image_space_tools/scn_resample_voxel_size.m new file mode 100644 index 00000000..2e701005 --- /dev/null +++ b/Image_space_tools/scn_resample_voxel_size.m @@ -0,0 +1,142 @@ +function [img, Vto] = scn_resample_voxel_size(loadImg, voxsize, varargin) + % [imgData, volInfo_mapto] = scn_resample_voxel_size(loadImg, voxsize, varargin) + % + % Tor Wager, Oct 2010 + % take volInfo and fmri_mask_image inputs as well + % as image file names + % + % This function takes an image name in loadImg + % and loads the data, resampling to the space defined + % in the image sampleTo. + % + % Optional: + % 'write' followed by name of resampled image to write + % + % Compatible with SPM5/8. + % + % Input images can have the following formats: + % 1) String with name of image file (.img or .nii) + % 2) spm_vol-style V struct (see spm_vol) + % 3) volInfo struct (see iimg_read_img) + % 4) fmri_mask_image object (see fmri_mask_image) + % + % Examples: + % Reslice standard brain mask to 3 x 3 x 3 voxels. + % img = which('brainmask.nii'); + % [dat, Vto] = scn_resample_voxel_size(img, [3 3 3], 'write', 'test.img'); + % spm_image('init', 'test.img'); + % spm_check_registration(char(img, 'test.img')); +% % + + % optional input arguments + % ------------------------------------------------------- + if ~isempty(varargin) + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case {'write', 'name', 'outname'} + outname = varargin{i+1}; + varargin{i+1} = []; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + end + + + % image whose space to sample into + % ------------------------------------------------------- + sampleTo = loadImg; % we're just going to change the voxel size + + if ischar(sampleTo) + sampleTo = deblank(sampleTo); + Vto = spm_vol(sampleTo); % volume to sample TO + + % Could already be a volInfo structure + elseif strcmp(class(sampleTo), 'fmri_mask_image') + Vto = sampleTo.volInfo; + + elseif isstruct(sampleTo) + Vto = sampleTo; + end + + Vto = Vto(1); % for SPM5 compat + + if isempty(Vto) + fprintf('%s is empty or missing.\n', sampleTo) + return + end + + Mto = Vto(1).mat; % mat to sample TO + + % now apply voxel size change + for i = 1:3 + Mto(i, i) = sign(Mto(i, i)) * voxsize(i); + end + + % image to load and sample to new space + % ------------------------------------------------------- + if ischar(loadImg) + loadImg = deblank(loadImg); + Vmap = spm_vol(loadImg); % mask to resample + + % Could already be a volInfo structure + elseif strcmp(class(loadImg), 'fmri_mask_image') | strcmp(class(loadImg), 'image_vector') | strcmp(class(loadImg), 'fmri_data') + Vmap = loadImg.volInfo; + + elseif isstruct(loadImg) + Vmap = loadImg; + + end + + if isempty(Vmap) + fprintf('%s is empty or missing.\n', loadImg) + return + end + + Mmap = Vmap(1).mat; + + + + % image dimensions + % ------------------------------------------------------- + %dim = Vto(1).dim(1:3); + % re-make dim based on new voxel size + dim = round(abs(Vto.dim .* diag(Vto.mat(1:3, 1:3))') ./ voxsize); + + % output image data + % ------------------------------------------------------- + img = zeros(dim); + + + % map slice-by-slice + % ------------------------------------------------------- + for j = 1:dim(3) + + Mslice = spm_matrix([0 0 j]); % Matrix specifying this slice + + Mtrans = Mto \ Mmap \ Mslice; % Affine mappping mtx: Mask -> TOvol + + img(:, :, j) = spm_slice_vol(Vmap(1), Mtrans, dim(1:2), [0 NaN]); + + + end + + % create image, if asked for + % ------------------------------------------------------- + if exist('outname', 'var') + Vout = Vto; + Vout.fname = outname; + Vout.mat = Mto; + Vout.dim = dim; + Vout = spm_create_vol(Vout); + + spm_write_vol(Vout, img); + end + + + +end diff --git a/Image_space_tools/tal2mni.m b/Image_space_tools/tal2mni.m new file mode 100755 index 00000000..aa190726 --- /dev/null +++ b/Image_space_tools/tal2mni.m @@ -0,0 +1,33 @@ +function outpoints = tal2mni(inpoints) +% Converts coordinates to MNI brain best guess +% from Talairach coordinates +% FORMAT outpoints = tal2mni(inpoints) +% Where inpoints is N by 3 or 3 by N matrix of coordinates +% (N being the number of points) +% outpoints is the coordinate matrix with MNI points +% Matthew Brett 2/2/01 + +dimdim = find(size(inpoints) == 3); +if isempty(dimdim) + error('input must be a N by 3 or 3 by N matrix') +end +if dimdim == 2 + inpoints = inpoints'; +end + +% Transformation matrices, different zooms above/below AC +rotn = spm_matrix([0 0 0 0.05]); +upz = spm_matrix([0 0 0 0 0 0 0.99 0.97 0.92]); +downz = spm_matrix([0 0 0 0 0 0 0.99 0.97 0.84]); + +inpoints = [inpoints; ones(1, size(inpoints, 2))]; +% Apply inverse translation +inpoints = inv(rotn)*inpoints; + +tmp = inpoints(3,:)<0; % 1 if below AC +inpoints(:, tmp) = inv(downz) * inpoints(:, tmp); +inpoints(:, ~tmp) = inv(upz) * inpoints(:, ~tmp); +outpoints = inpoints(1:3, :); +if dimdim == 2 + outpoints = outpoints'; +end \ No newline at end of file diff --git a/Image_space_tools/tal2vox.m b/Image_space_tools/tal2vox.m new file mode 100755 index 00000000..3410a93b --- /dev/null +++ b/Image_space_tools/tal2vox.m @@ -0,0 +1,21 @@ +function vox=tal2vox(tal,VOL) +% converts from talairach coordinate to voxel coordinate +% based on variables from SPM.M (passed here for +% faster operation) +% e.g., foo=tal2vox([-30 28 -30], VOL) + +if(isfield(VOL, 'M')) + M = VOL.M; +elseif(isfield(VOL, 'mat')) + M = VOL.mat; +else + error('Error in %s: VOL does not have an "M" or a "mat" field.', mfilename); +end + +vox=[0 0 0]; +vox(1)=(tal(1)-M(1,4))/M(1,1); +vox(2)=(tal(2)-M(2,4))/M(2,2); +vox(3)=(tal(3)-M(3,4))/M(3,3); + +return + diff --git a/Image_space_tools/transform_coordinates.m b/Image_space_tools/transform_coordinates.m new file mode 100755 index 00000000..1b46c59b --- /dev/null +++ b/Image_space_tools/transform_coordinates.m @@ -0,0 +1,25 @@ +function CLU = transform_coordinates(CLU,mat) +% CLU = transform_coordinates(CLU,mat) +% +% transforms XYZ voxel coordinates in CLU (clusters or CLU) +% to new voxel coordinates, given mm coordinates in CLU +% and a mat file describing the transformation, as in SPM99 +% +% This preserves the order of the voxels, but is slower +% and gives UNIQUE XYZ voxels given XYZmm. +% see mm2voxel.m +% +% tor wager + +V.M = mat; + +for i = 1:length(CLU) + + CLU(i).XYZ = mm2voxel(CLU(i).XYZmm,V)'; + CLU(i).XYZmm = voxel2mm(CLU(i).XYZ,mat); + +end + +return + + \ No newline at end of file diff --git a/Image_space_tools/voxel2mask.m b/Image_space_tools/voxel2mask.m new file mode 100755 index 00000000..ecab2d47 --- /dev/null +++ b/Image_space_tools/voxel2mask.m @@ -0,0 +1,44 @@ +function mask = voxel2mask(voxels,maskdims) +% function mask = voxel2mask(voxels, x y z mask dimensions) +% voxels: +% 3 column vectors +% [i j k] = row, column, slice +% [x y z] in brain if brain is in analyze format +% (x is rows, y is columns, z is slices) +% Tor Wager, 10/17/01 + +[n, m] = size(voxels); +wh_bad = false(n, 1); + +% Check for illegal voxels +if m ~= 3, warning('voxel2mask: Illegal voxel input list', 'Voxels must be k x 3 matrix'); end + +mv = max(voxels); +if any(mv > maskdims) + warning('voxel2mask: Illegal voxel input list', 'Voxels outside mask, Voxel indices > mask dims'); + wh_bad = any(voxels > mv(ones(n, 1), :), 2); +end + +mv = min(voxels); +if any(mv < 1) + warning('voxel2mask: Illegal voxel input list', 'Voxels outside mask, Voxel indices < 1'); + wh_bad = [wh_bad | any(voxels < ones(n, 3), 2)]; +end + +if ~isempty(wh_bad) + wh_bad = find(wh_bad); + disp('You need to check your images!'); + disp('Offending voxel numbers: '); disp(wh_bad) + disp('Offending voxel coordinates: '); disp(voxels(wh_bad, :)) + voxels(wh_bad, :) = []; +end + +mask = zeros(maskdims); + +for i = 1:size(voxels, 1) + mask(voxels(i,1),voxels(i,2),voxels(i,3)) = 1; +end + +mask = double(mask); + +return \ No newline at end of file diff --git a/Image_space_tools/voxel2mm.m b/Image_space_tools/voxel2mm.m new file mode 100755 index 00000000..15fc243e --- /dev/null +++ b/Image_space_tools/voxel2mm.m @@ -0,0 +1,23 @@ +function XYZmm = voxel2mm(XYZ,m) +%function XYZmm = voxel2mm(XYZ,m) +% XYZ is 3 vector point list (3 rows, n columns) +% m is SPM mat - 4 x 4 affine transform +% (what's stored in the .mat file) +% +% Verified that this works 10/27/01. +% Tor Wager, 10/27/01 +% +% Example: +% XYZmm = voxel2mm([x y z]',V.mat); + +if isempty(XYZ), XYZmm = [];, return, end + +% add one to multiply by the constant shift (offset from edge) in mat +% ------------------------------------------------------------------- +XYZ(4,:) = 1; + +XYZmm = m * XYZ; + +XYZmm = XYZmm(1:3,:); + +return \ No newline at end of file diff --git a/Image_thresholding/FDR.m b/Image_thresholding/FDR.m new file mode 100755 index 00000000..ba745783 --- /dev/null +++ b/Image_thresholding/FDR.m @@ -0,0 +1,37 @@ +function [pID,pN] = FDR(p,q) + % FORMAT pt = FDR(p,q) + % + % p - vector of p-values + % q - False Discovery Rate level + % + % pID - p-value threshold based on independence or positive dependence + % pN - Nonparametric p-value threshold + %______________________________________________________________________________ + % @(#)FDR.m 1.3 Tom Nichols 02/01/18 + + % The checking code below was added by Tor Wager + p(isnan(p)) = []; + + if any(p == 0) + disp('******************************************') + disp('Warning! Some p-values are zero.') + disp('FDR.m will interpret these as ineligible voxels.') + disp('If these are valid p-values, they should have some not-exactly-zero value.') + p(p == 0) = []; + disp('******************************************') + + end + + p = sort(p(:)); + V = length(p); + I = (1:V)'; + + cVID = 1; + cVN = sum(1./(1:V)); + + pID = p(max(find(p<=I/V*q/cVID))); + pN = p(max(find(p<=I/V*q/cVN))); + + return + + diff --git a/Image_thresholding/cl_ext_3dClustSim.m b/Image_thresholding/cl_ext_3dClustSim.m new file mode 100644 index 00000000..1ab504df --- /dev/null +++ b/Image_thresholding/cl_ext_3dClustSim.m @@ -0,0 +1,132 @@ +function [cl_ext_ClustSim, fwhm_vox] = cl_ext_3dClustSim(corrected_p, prim_p, residual_images, mask, voxelsize_mm, ClustSim_dir, varargin) + +% [cl_ext_ClustSim, fwhm] = cl_ext_3dClustSim(corrected_p, prim_p, residual_images, mask, voxelsize_mm, ClustSim_dir, varargin) +% +% inputs: +% 1. corrected_p: cluster-extent corrected p value +% e.g.) if cluster-extent corrected p < .05: corrected_p = .05 +% 2. prim_p: primary threshold for height (i.e., cluster-defining threshold) +% e.g.) prim_p = [0.01 0.005 0.001]; +% 3. residual_images = residual image names; if you used +% cl_ext_make_resid.m, this should be 'Res4d.nii'. +% e.g.) residual_images = filenames('Res4d.hdr', 'char', 'absolute'); +% residual_images = filenames('Res4d.nii', 'char', 'absolute'); +% 4. mask = mask image name (should have header) +% e.g.) mask = filenames('mask.hdr', 'char', 'absolute'); +% mask = filenames('mask.nii', 'char', 'absolute'); +% 5. voxel sizes in milimeter. e.g) voxelsize_mm = [2 2 2]; +% 6. 3dClustSim_dir: directory where alphasim is installed. +% e.g.) 3dClustSim_dir = '/Users/clinpsywoo/abin/macosx_10.6_Intel_64'; +% If you don't have 3dClustSim, see http://afni.nimh.nih.gov/pub/dist/HOWTO/howto/ht00_inst/html/index.shtml +% +% Output: +% cl_ext_ClustSim - cl_ext_ClustSim is the cluster size that makes a corrected p value under +% corrected_p (e.g., 0.05). +% fwhm (x, y, z in voxel) - intrinsic smoothness level estimated by AFNI(3dFWHMx). +% If you want to convert this into mm, you need to multiply these +% values by voxel sizes in mm. +% +% options: +% 'iter': you can set up the iteration number for Monte Carlo simulation. +% default is doing 1000 iterations. +% 'twotail': default is one-tail - with this option, primary_p/2 will be used +% for all clsuter extent estimations. +% 'fwhm': you can add fwhm manually +% +% Choong-Wan (Wani) Woo, 01/21/2013 +% modified by Wani, 05/18/2013 + + +%% go to the alphasim directory +curr_dir = pwd; +cd(ClustSim_dir); + +%% defaults +iter = 1000; +IsTwoTailed = 0; +manual_fwhm = 0; +usemask = 1; + +%% get options +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'iter', iter = varargin{i+1}; + case {'twotail', 'twotails', 'twotailed'}, IsTwoTailed = 1; + case 'fwhm', fwhm = varargin{i+1}; manual_fwhm = 1; + end + end +end + +%% estimate smoothness +if ~manual_fwhm + if usemask + eval_function_fwhm = ['unset DYLD_LIBRARY_PATH; ./3dFWHMx -mask ' mask ' -detrend -dset ' residual_images]; + else + eval_function_fwhm = ['unset DYLD_LIBRARY_PATH; ./3dFWHMx -detrend -dset ' residual_images]; + end + [status, res_fwhm] = unix(eval_function_fwhm); + + k = textscan(res_fwhm, '%s'); k = k{1}; + b = []; for i = 1:length(k), b = cat(1,b,str2num(k{i})); end + + fwhm = b(end-2:end); +end + +fwhm = fwhm'; +fwhm_vox = fwhm/voxelsize_mm; +% 06/27/13 Wani: 3dFWHMx gives fwhm in mm, not vox. + +% if sum(size(voxelsize_mm) == size(fwhm)) > 0 +% fwhm_mm = voxelsize_mm.*fwhm; +% else +% fwhm = fwhm'; +% fwhm_mm = voxelsize_mm.*fwhm; +% end + +%% calculate cluster extent size + +if IsTwoTailed + eval_function_cl_ext = ['unset DYLD_LIBRARY_PATH; ./3dClustSim -mask ' mask ' -dxyz ' num2str(voxelsize_mm) ... + ' -iter ' num2str(iter) ' -pthr ' num2str(prim_p/2) ' -fwhmxyz ' num2str(fwhm) ' -athr ' num2str(corrected_p)]; +else + eval_function_cl_ext = ['unset DYLD_LIBRARY_PATH; ./3dClustSim -mask ' mask ' -dxyz ' num2str(voxelsize_mm) ... + ' -iter ' num2str(iter) ' -pthr ' num2str(prim_p) ' -fwhmxyz ' num2str(fwhm) ' -athr ' num2str(corrected_p)]; +end + +[status, res_ext] = unix(eval_function_cl_ext); + +clear k b; +k = textscan(res_ext, '%s'); k = k{1}; +b = []; for i = 1:length(k), if isnumeric(str2num(k{i})), b = cat(1,b,str2num(k{i})); end, end + +for i = 1:length(prim_p) + if IsTwoTailed + ii = find(b == prim_p(i)/2); + else + ii = find(b == prim_p(i)); + end + + if numel(ii) == 2 + prim_p_idx(i) = ii(2); + elseif numel(ii) == 1 + prim_p_idx(i) = NaN; + elseif numel(ii) > 3 + if i > 1 + if sum(ii == prim_p_idx(i-1)+2) == 0, prim_p_idx(i) = NaN; + else prim_p_idx(i) = ii(ii == prim_p_idx(i-1)+2); + end + else + prim_p_idx(i) = NaN; + end + end +end + +cl_ext_ClustSim = zeros(size(prim_p))'; +cl_ext_ClustSim(isnan(prim_p_idx)) = NaN; + +cl_ext_ClustSim(~isnan(prim_p_idx)) = round(b(prim_p_idx(~isnan(prim_p_idx))+1)); + +cd(curr_dir); + +return \ No newline at end of file diff --git a/Image_thresholding/cl_ext_make_resid.m b/Image_thresholding/cl_ext_make_resid.m new file mode 100644 index 00000000..a6650921 --- /dev/null +++ b/Image_thresholding/cl_ext_make_resid.m @@ -0,0 +1,143 @@ +function cl_ext_make_resid(con_files, varargin) + +% function cl_ext_make_resid(conimgs, varargin) +% +% Overview: this function will create residual images (4d) and mask image in +% a current or assigned directory in order to use them in estimating smoothness +% (relevant functions: spm_est_smoothness (SPM), 3dFWHMx (AFNI), smoothest (FSL). +% +% Inputs: +% - con_files = contrast image file names; This could be a cell array or +% strings. This could be 4d images. +% Best: Input a cell string. e.g., for a string matrix: +% Use cl_ext_make_resid(cellstr(imgs)); % save residual images +% +% * If you are not providing the absolute paths of the images, you need to +% be in the directory that has the image files. +% +% Outputs: +% - Res4d.nii: residual images saved by SPM. +% - mask.nii: the mask image that was used. +% +% Options for varargin: +% - 'mask': This option can be used to estimate a cluster size for the correction for multiple +% comparisons "within the mask". You can put in a ROI mask or gray matter, +% whatever. If you don't specify a mask image, brainmask.nii (default) will be +% used, but the image has to be in your path. +% e.g.) mask = fullfile(basedir, 'ROI_image.img'); +% mask = which('scalped_avg152T1_graymatter_smoothed.img'); % limited to gray matter +% - 'outputdir': With this option, this will save residual and mask images and in the +% outputdir directory. If you don't give outputdir, the current directory +% will be used (default). +% +% This function calls cl_ext_spm_spm.m, which is a modified spm_spm not to +% delete residual images. +% +% Choong-Wan (Wani) Woo, 01/22/2013 +% modified by Wani, 05/18/2013 + +outputdir = pwd; +mask = []; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'mask', mask = varargin{i+1}; + case 'outputdir', outputdir = varargin{i+1}; + end + end +end + +temp_dir = fullfile(outputdir, 'cl_extent_estimation'); +mkdir(temp_dir); + +if isempty(mask) + try + mask = which('brainmask.nii'); + catch + warning('We tried to use a default mask (brainmask.nii), but we couldn''t use it. Maybe you need to add "brainmask.nii" into your path. We are going to use the first contrast image as a mask.'); + mask = conimgs{1}; + end +end + +matlabbatch{1}.spm.stats.factorial_design.dir{1} = temp_dir; % result dir + +% file names should be in cell arrays. + +if ~iscell(con_files) + if size(con_files, 1) == 1 + con_files = filenames(con_files, 'absolute', 'char'); + con_files = expand_4d_filenames(con_files); + for ii = 1:size(con_files,1), conimgs{ii} = deblank(con_files(ii,:)); end + else + for ii = 1:size(con_files,1) + conimgs(ii) = filenames(deblank(con_files(ii,:)), 'absolute'); % wani added this line for efficiency +% con_files_temp(ii,:) = filenames(con_files(ii,:), 'absolute', 'char'); + end +% con_files = con_files_temp; + end +% for i = 1:size(con_files,1) +% conimgs{i} = deblank(con_files(i,:)); +% end + conimgs = conimgs'; +else + if length(con_files) == 1 + con_files = expand_4d_filenames(con_files); + for i = 1:size(con_files,1) + conimgs{i} = deblank(con_files(i,:)); + end + conimgs = conimgs'; + else + conimgs = con_files; + end +end + +con_num = length(conimgs); +if con_num == 1 + error('The number of contrast images is one. Please check the image names.'); +end + +matlabbatch{1}.spm.stats.factorial_design.des.t1.scans = conimgs; % each con image for 2nd level +% matlabbatch{1}.spm.stats.factorial_design.cov = struct('c', [], 'cname',[],'iCFI',[],'iCC',[]); + +matlabbatch{1}.spm.stats.factorial_design.masking.tm.tm_none = 1; +matlabbatch{1}.spm.stats.factorial_design.masking.im = 0; + +matlabbatch{1}.spm.stats.factorial_design.masking.em{1} = mask; + +matlabbatch{1}.spm.stats.factorial_design.globalc.g_omit = 1; +matlabbatch{1}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1; +matlabbatch{1}.spm.stats.factorial_design.globalm.glonorm = 1; + +cd(temp_dir); +save spm_2nd matlabbatch; + +ndf = [1 con_num-1]; + +if exist('SPM.mat', 'file'), delete('SPM.mat'); end +spm_jobman('run', 'spm_2nd.mat'); + +load SPM.mat; + +try + cl_ext_spm_spm(SPM); % you need cl_ext_spm_spm.m in your path +catch + error('You need "cl_ext_spm_spm.m" in your path.'); +end + +mask_img = filenames('mask*.img','char'); +Res_imgs = filenames('ResI_*.img', 'char'); + +dat_mask = fmri_data(mask_img); +dat_mask.fullpath = fullfile(outputdir, 'mask.nii'); +write(dat_mask); + +dat_res = fmri_data(Res_imgs); +dat_res.fullpath = fullfile(outputdir, 'Res4d.nii'); +write(dat_res); + +% rmdir(temp_dir, 's'); + +cd(outputdir); + +end \ No newline at end of file diff --git a/Image_thresholding/cl_ext_spm_grf.m b/Image_thresholding/cl_ext_spm_grf.m new file mode 100644 index 00000000..788e32ff --- /dev/null +++ b/Image_thresholding/cl_ext_spm_grf.m @@ -0,0 +1,102 @@ +function [cl_ext_spm, fwhm] = cl_ext_spm_grf(corrected_p, prim_p, residual_images, mask, varargin) + +% [cl_ext, fwhm] = cl_ext_spm_grf(corrected_p, prim_p, residual_images, mask, varargin) +% +% Overview: this function is designed to estimate a cluster extent size for +% the correction for multiple comparisons based on a Gaussian Random Field +% Theory using SPM toolboxes. +% +% Inputs: +% - corrected_p = corrected p value +% e.g.) cluster-extent corrected p < .05: corrected_p = .05 +% - prim_p = primary threshold for height (i.e., cluster-defining threshold) +% e.g.) prim_p = [0.01 0.005 0.001]; +% - residual_images = residual image names; if you used +% cl_ext_make_resid.m, this should be 'Res4d.nii' +% - mask = mask image name +% +% Output: +% cl_ext_spm - cl_ext_spm is the cluster size that makes a corrected p value under +% corrected_p (e.g., 0.05). +% fwhm (x, y, z in voxels) - intrinsic smoothness level estimated by SPM (spm_est_smoothness.m) +% If you want to convert this into mm, you need to multiply these +% values by voxel sizes in mm. +% +% Options: +% - 'doplot' +% - 'twotail': default is one-tail - with this option, primary_p/2 will be used +% for all clsuter extent estimations. +% +% Choong-Wan (Wani) Woo, 08/13/2012 +% modified by Wani, 05/18/2013 + +doplot = false; +isTwoTailed = false; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'doplot', doplot = true; + case 'twotail', isTwoTailed = true; + end + end +end + +con_num = size(expand_4d_filenames(residual_images),1); +ndf = [1 con_num-1]; + +[fwhm, dummy, r] = spm_est_smoothness(residual_images, mask, [con_num con_num-1]); + +V2R = 1/prod(fwhm); + +cl_ext = zeros(length(prim_p),2); + +if doplot + scrsz = get(0, 'ScreenSize'); + create_figure('cluster_extent_spm'); + set(gcf, 'Position', [1 scrsz(4)/2 scrsz(3)/1.5 scrsz(4)/2]); +end + +for i = 1:length(prim_p) + if isTwoTailed + u(i) = spm_u(prim_p(i)/2, ndf, 'T'); + else + u(i) = spm_u(prim_p(i), ndf, 'T'); + end +end + +for i = 1:length(prim_p) % cluster-extent threshold (k) based on RF in SPM + + P = []; + + for j = 1:100000 + k = j*V2R; % convert the number of voxels into the number of resels + + P(end+1,1) = spm_P_RF(1,k,u(i),ndf,'T',r,1); + P(end,2) = j; + + if P(end,1) <= corrected_p + cl_ext(i,1) = j; + cl_ext(i,2) = P(end,1); + break + end + end + + if doplot + hh(i) = subplot(1,length(prim_p),i); + plot(P(:,2), P(:,1), '-b', 'LineWidth', 1.5); + xlabel('cluster extent size', 'FontSize', 16); + ylabel('corrected P value', 'FontSize', 16); + eval(['title(hh(i), ''Primary P:' num2str(prim_p(i)), ''', ''fontsize'', 16);']); + set(gca, 'FontSize', 15); + hold on; + plot(cl_ext(i,1), cl_ext(i,2), 'r+', 'MarkerSize', 15); + eval(['text(cl_ext(i,1)/2, cl_ext(i,2), ''+ cl size:' num2str(cl_ext(i,1)) ''', ''FontSize'', 14);']); + end + +end + +cl_ext_spm = cl_ext(:,1); + +return + diff --git a/Image_thresholding/cl_ext_spm_spm.m b/Image_thresholding/cl_ext_spm_spm.m new file mode 100644 index 00000000..f59c45cd --- /dev/null +++ b/Image_thresholding/cl_ext_spm_spm.m @@ -0,0 +1,971 @@ +function [SPM] = cl_ext_spm_spm(SPM) +% [Re]ML Estimation of a General Linear Model +% FORMAT [SPM] = spm_spm(SPM) +% +% Required fields of SPM: +% +% xY.VY - nScan x 1 struct array of image handles (see spm_vol) +% Images must have the same orientation, voxel size and data type +% - Any scaling should have already been applied via the image handle +% scalefactors. +% +% xX - Structure containing design matrix information +% - Required fields are: +% xX.X - Design matrix (raw, not temporally smoothed) +% xX.name - cellstr of parameter names corresponding to columns +% of design matrix +% - Optional fields are: +% xX.K - cell of session-specific structures (see spm_filter) +% - Design & data are pre-multiplied by K +% (K*Y = K*X*beta + K*e) +% - Note that K should not smooth across block boundaries +% - defaults to speye(size(xX.X,1)) +% xX.W - Optional whitening/weighting matrix used to give +% weighted least squares estimates (WLS). If not specified +% spm_spm will set this to whiten the data and render +% the OLS estimates maximum likelihood +% i.e. W*W' = inv(xVi.V). +% +% xVi - Structure describing intrinsic temporal non-sphericity +% - Required fields are: +% xVi.Vi - array of non-sphericity components +% - defaults to {speye(size(xX.X,1))} - i.i.d. +% - specifying a cell array of constraints (Qi) +% These constraints invoke spm_reml to estimate +% hyperparameters assuming V is constant over voxels. +% that provide a high precise estimate of xX.V +% - Optional fields are: +% xX.V - Optional non-sphericity matrix. Cov(e) = sigma^2*V +% If not specified spm_spm will compute this using +% a 1st pass to identify significant voxels over which +% to estimate V. A 2nd pass is then used to re-estimate +% the parameters with WLS and save the ML estimates +% (unless xX.W is already specified). +% +% xM - Structure containing masking information, or a simple column vector +% of thresholds corresponding to the images in VY [default: -Inf] +% - If a structure, the required fields are: +% xM.TH - nVar x nScan matrix of analysis thresholds, one per image +% xM.I - Implicit masking (0=>none, 1 => implicit zero/NaN mask) +% xM.VM - struct array of explicit mask image handles +% - (empty if no explicit masks) +% - Explicit mask images are >0 for valid voxels to assess. +% - Mask images can have any orientation, voxel size or data +% type. They are interpolated using nearest neighbour +% interpolation to the voxel locations of the data Y. +% - Note that voxels with constant data (i.e. the same value across +% scans) are also automatically masked out. +% +% swd - Directory where the output files will be saved [default: pwd] +% If exists, it becomes the current working directory. +% +% In addition, global SPM "defaults" variable is used (see spm_defaults): +% +% stats..UFp - critical F-threshold for selecting voxels over +% which the non-sphericity is estimated (if +% required) [default: 0.001] +% +% stats.maxres - maximum number of residual images for smoothness +% estimation +% +% stats.maxmem - maximum amount of data processed at a time (in bytes) +% +% modality - SPM modality {'PET','FMRI','EEG'} +% +%__________________________________________________________________________ +% +% spm_spm is the heart of the SPM package. Given image files and a +% General Linear Model, it estimates the model parameters, variance +% hyperparameters, and smoothness of standardised residual fields, writing +% these out to disk in the current working directory for later +% interrogation in the results section. (NB: Existing analyses in the +% current working directory are overwritten). This directory +% now becomes the working directory for this analysis and all saved +% images are relative to this directory. +% +% The model is expressed via the design matrix (xX.X). The basic model +% at each voxel is of the form is Y = X*B + e, for data Y, design +% matrix X, (unknown) parameters B and residual errors e. The errors +% are assumed to have a normal distribution. +% +% Sometimes confounds (e.g. drift terms in fMRI) are necessary. These +% can be specified directly in the design matrix or implicitly, in terms +% of a residual forming matrix K to give a generalised linear model +% K*Y = K*X*B + K*e. In fact K can be any matrix (e.g. a convolution +% matrix). +% +% In some instances i.i.d. assumptions about errors do not hold. For +% example, with serially correlated (fMRI) data or correlations among the +% levels of a factor in repeated measures designs. This non-sphericity +% can be specified in terms of components (SPM.xVi.Vi{i}). If specified +% these covariance components will then be estimated with ReML (restricted +% maximum likelihood) hyperparameters. This estimation assumes the same +% non-sphericity for voxels that exceed the global F-threshold. The ReML +% estimates can then be used to whiten the data giving maximum likelihood +% (ML) or Gauss-Markov estimators. This entails a second pass of the data +% with an augmented model K*W*Y = K*W*X*B + K*W*e where W*W' = inv(xVi.V). +% xVi.V is the non-sphericity based on the hyperparameter estimates. +% W is stored in xX.W and cov(K*W*e) in xX.V. The covariance of the +% parameter estimates is then xX.Bcov = pinv(K*W*X)*xX.V*pinv(K*W*X)'. +% +% If you do not want ML estimates but want to use ordinary least squares +% (OLS) then simply set SPM.xX.W to the identity matrix. Any non-sphericity +% V will still be estimated but will be used to adjust the degrees of freedom +% of the ensuing statistics using the Satterthwaite approximation (c.f. +% the Greenhouse-Geisser corrections). +% +% If [non-spherical] variance components Vi are not specified xVi.Vi and +% xVi.V default to the identity matrix (i.e. i.i.d). The parameters are +% then estimated by OLS. In this instance the OLS and ML estimates are +% the same. +% +% Note that only a single voxel-specific hyperparameter (i.e. variance +% component) is estimated, even if V is not i.i.d. This means spm_spm +% always implements a fixed-effects model. +% Random effects models can be emulated using a multi-stage procedure: +% This entails summarising the data with contrasts such that the fixed +% effects in a second model on the summary data are those effects of +% interest (i.e. the population effects). This means contrasts are +% re-entered into spm_spm to make an inference (SPM) at the next +% level. At this higher hierarchical level the residual variance for the +% model contains the appropriate variance components from lower levels. +% See spm_RandFX.man for further details and below. +% +% Under the additional assumption that the standardised error fields +% are non-stationary standard Gaussian random fields, results from +% Random field theory can be applied to estimate the significance +% statistic images (SPM's) adjusting p values for the multiple tests +% at all voxels in the search volume. The parameters required for +% this random field correction are the volume, and Lambda, the covariance +% matrix of partial derivatives of the standardised error fields, estimated +% by spm_est_smoothness. +% +% ---------------- +% +% The volume analysed is the intersection of the threshold masks, +% explicit masks and implicit masks. See spm_spm_ui for further details +% on masking options. +% +%-------------------------------------------------------------------------- +% +% The output of spm_spm takes the form of an SPM.mat file of the analysis +% parameters, and 'float' flat-file images of the parameter and variance +% [hyperparameter] estimates. An 8bit zero-one mask image indicating the +% voxels assessed is also written out, with zero indicating voxels outside +% tha analysed volume. +% +% ---------------- +% +% The following SPM.fields are set by spm_spm (unless specified) +% +% xVi.V - estimated non-sphericity trace(V) = rank(V) +% xVi.h - hyperparameters xVi.V = xVi.h(1)*xVi.Vi{1} + ... +% xVi.Cy - spatially whitened (used by ReML to estimate h) +% xVi.CY - <(Y - )*(Y - )'> (used by spm_spm_Bayes) +% +% ---------------- +% +% Vbeta - struct array of beta image handles (relative) +% VResMS - file struct of ResMS image handle (relative) +% VM - file struct of Mask image handle (relative) +% +% ---------------- +% +% xX.W - if not specified W*W' = inv(x.Vi.V) +% xX.V - V matrix (K*W*Vi*W'*K') = correlations after K*W is applied +% xX.xKXs - space structure for K*W*X, the 'filtered and whitened' +% design matrix +% - given as spm_sp('Set',xX.K*xX.W*xX.X) - see spm_sp +% xX.pKX - pseudoinverse of K*W*X, computed by spm_sp +% xX.Bcov - xX.pKX*xX.V*xX.pKX - variance-covariance matrix of +% parameter estimates +% (when multiplied by the voxel-specific hyperparameter ResMS +% of the parameter estimates (ResSS/xX.trRV = ResMS) ) +% xX.trRV - trace of R*V +% xX.trRVRV - trace of RVRV +% xX.erdf - effective residual degrees of freedom (trRV^2/trRVRV) +% xX.nKX - design matrix (xX.xKXs.X) scaled for display +% (see spm_DesMtx('sca',... for details) +% +% ---------------- +% +% xVol.M - 4x4 voxel->mm transformation matrix +% xVol.iM - 4x4 mm->voxel transformation matrix +% xVol.DIM - image dimensions - column vector (in voxels) +% xVol.XYZ - 3 x S vector of in-mask voxel coordinates +% xVol.S - Lebesgue measure or volume (in voxels) +% xVol.R - vector of resel counts (in resels) +% xVol.FWHM - Smoothness of components - FWHM, (in voxels) +% +% ---------------- +% +% xCon - Contrast structure (created by spm_FcUtil.m) +% xCon.name - Name of contrast +% xCon.STAT - 'F', 'T' or 'P' - for F/T-contrast ('P' for PPMs) +% xCon.c - (F) Contrast weights +% xCon.X0 - Reduced design matrix (spans design space under Ho) +% It is in the form of a matrix (spm99b) or the +% coordinates of this matrix in the orthogonal basis +% of xX.X defined in spm_sp. +% xCon.iX0 - Indicates how contrast was specified: +% If by columns for reduced design matrix then iX0 contains +% the column indices. Otherwise, it's a string containing +% the spm_FcUtil 'Set' action: Usually one of {'c','c+','X0'} +% (Usually this is the input argument F_iX0.) +% xCon.X1o - Remaining design space (orthogonal to X0). +% It is in the form of a matrix (spm99b) or the +% coordinates of this matrix in the orthogonal basis +% of xX.X defined in spm_sp. +% xCon.eidf - Effective interest degrees of freedom (numerator df) +% xCon.Vcon - ...for handle of contrast/ESS image (empty at this stage) +% xCon.Vspm - ...for handle of SPM image (empty at this stage) +% +% ---------------- +% +% +% The following images are written to file +% +% mask.{img,hdr} - analysis mask image +% 8-bit (uint8) image of zero-s & one's indicating which voxels were +% included in the analysis. This mask image is the intersection of the +% explicit, implicit and threshold masks specified in the xM argument. +% The XYZ matrix contains the voxel coordinates of all voxels in the +% analysis mask. The mask image is included for reference, but is not +% explicitly used by the results section. +% +% ---------------- +% +% beta_????.{img,hdr} - parameter images +% These are 32-bit (float32) images of the parameter estimates. The image +% files are numbered according to the corresponding column of the +% design matrix. Voxels outside the analysis mask (mask.img) are given +% value NaN. +% +% ---------------- +% +% ResMS.{img,hdr} - estimated residual variance image +% This is a 64-bit (float64) image of the residual variance estimate. +% Voxels outside the analysis mask are given value NaN. +% +% ---------------- +% +% RPV.{img,hdr} - estimated resels per voxel image +% This is a 64-bit (float64) image of the RESELs per voxel estimate. +% Voxels outside the analysis mask are given value 0. These images +% reflect the nonstationary aspects the spatial autocorrelations. +% +% ---------------- +% +% ResI_????.{img,hdr} - standardised residual (temporary) images +% These are 64-bit (float64) images of standardised residuals. At most +% maxres images will be saved and used by spm_est_smoothness, after which +% they will be deleted. +% +%-------------------------------------------------------------------------- +% +% References: +% +% Christensen R (1996) Plane Answers to Complex Questions +% Springer Verlag +% +% Friston KJ, Holmes AP, Worsley KJ, Poline JB, Frith CD, Frackowiak RSJ (1995) +% ``Statistical Parametric Maps in Functional Imaging: +% A General Linear Approach'' +% Human Brain Mapping 2:189-210 +% +% Worsley KJ, Friston KJ (1995) +% ``Analysis of fMRI Time-Series Revisited - Again'' +% NeuroImage 2:173-181 +% +%__________________________________________________________________________ +% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging + +% Andrew Holmes, Jean-Baptiste Poline & Karl Friston +% $Id: spm_spm.m 3960 2010-06-30 17:41:24Z ged $ + +SVNid = '$Rev: 3960 $'; + +%-Say hello +%-------------------------------------------------------------------------- +SPMid = spm('FnBanner',mfilename,SVNid); +Finter = spm('FigName','Stats: estimation...'); spm('Pointer','Watch'); + +%-Get SPM.mat[s] if necessary +%-------------------------------------------------------------------------- +if nargin == 0 + P = cellstr(spm_select(Inf,'^SPM\.mat$','Select SPM.mat[s]')); + for i = 1:length(P) + swd = fileparts(P{i}); + load(fullfile(swd,'SPM.mat')); + SPM.swd = swd; + spm_spm(SPM); + end + return +end + +%-Change to SPM.swd if specified +%-------------------------------------------------------------------------- +try + cd(SPM.swd); +catch + SPM.swd = pwd; +end + +%-Ensure data are assigned +%-------------------------------------------------------------------------- +try + SPM.xY.VY; +catch + spm('alert!','Please assign data to this design', mfilename); + spm('FigName','Stats: done',Finter); spm('Pointer','Arrow') + return +end + +%-Delete files from previous analyses +%-------------------------------------------------------------------------- +if exist(fullfile(SPM.swd,'mask.img'),'file') == 2 + + str = {'Current directory contains SPM estimation files:',... + 'pwd = ',SPM.swd,... + 'Existing results will be overwritten!'}; + if spm_input(str,1,'bd','stop|continue',[1,0],1) + spm('FigName','Stats: done',Finter); spm('Pointer','Arrow') + return + else + warning('Overwriting old results\n\t (pwd = %s) ',SPM.swd); + try, SPM = rmfield(SPM,'xVol'); end + end +end + +files = {'^mask\..{3}$','^ResMS\..{3}$','^RPV\..{3}$',... + '^beta_.{4}\..{3}$','^con_.{4}\..{3}$','^ResI_.{4}\..{3}$',... + '^ess_.{4}\..{3}$', '^spm\w{1}_.{4}\..{3}$'}; + +for i = 1:length(files) + j = spm_select('List',SPM.swd,files{i}); + for k = 1:size(j,1) + spm_unlink(deblank(j(k,:))); + end +end + + +%========================================================================== +% - A N A L Y S I S P R E L I M I N A R I E S +%========================================================================== + +%-Initialise +%========================================================================== +fprintf('%-40s: %30s','Initialising parameters','...computing'); %-# +xX = SPM.xX; +[nScan nBeta] = size(xX.X); + + +%-If xM is not a structure then assume it's a vector of thresholds +%-------------------------------------------------------------------------- +try + xM = SPM.xM; +catch + xM = -Inf(nScan,1); +end +if ~isstruct(xM) + xM = struct('T', [],... + 'TH', xM,... + 'I', 0,... + 'VM', {[]},... + 'xs', struct('Masking','analysis threshold')); +end + +%-Check confounds (xX.K) and non-sphericity (xVi) +%-------------------------------------------------------------------------- +if ~isfield(xX,'K') + xX.K = 1; +end +try + %-If covariance components are specified use them + %---------------------------------------------------------------------- + xVi = SPM.xVi; +catch + + %-otherwise assume i.i.d. + %---------------------------------------------------------------------- + xVi = struct( 'form', 'i.i.d.',... + 'V', speye(nScan,nScan)); +end + + +%-Get non-sphericity V +%========================================================================== +try + %-If xVi.V is specified proceed directly to parameter estimation + %---------------------------------------------------------------------- + V = xVi.V; + str = 'parameter estimation'; + +catch + + % otherwise invoke ReML selecting voxels under i.i.d assumptions + %---------------------------------------------------------------------- + V = speye(nScan,nScan); + str = '[hyper]parameter estimation'; +end + +%-Get whitening/Weighting matrix: If xX.W exists we will save WLS estimates +%-------------------------------------------------------------------------- +try + %-If W is specified, use it + %---------------------------------------------------------------------- + W = xX.W; +catch + + if isfield(xVi,'V') + + % otherwise make W a whitening filter W*W' = inv(V) + %------------------------------------------------------------------ + W = spm_sqrtm(spm_inv(xVi.V)); + W = W.*(abs(W) > 1e-6); + xX.W = sparse(W); + + else + % unless xVi.V has not been estimated - requiring 2 passes + %------------------------------------------------------------------ + W = speye(nScan,nScan); + str = 'hyperparameter estimation (1st pass)'; + end +end + + +%-Design space and projector matrix [pseudoinverse] for WLS +%========================================================================== +xX.xKXs = spm_sp('Set',spm_filter(xX.K,W*xX.X)); % KWX +xX.xKXs.X = full(xX.xKXs.X); +xX.pKX = spm_sp('x-',xX.xKXs); % projector +erdf = spm_SpUtil('trRV',xX.xKXs); % Working error df + +%-If xVi.V is not defined compute Hsqr and F-threshold under i.i.d. +%-------------------------------------------------------------------------- +if ~isfield(xVi,'V') + + Fcname = 'effects of interest'; + iX0 = [SPM.xX.iB SPM.xX.iG]; + xCon = spm_FcUtil('Set',Fcname,'F','iX0',iX0,xX.xKXs); + X1o = spm_FcUtil('X1o', xCon(1),xX.xKXs); + Hsqr = spm_FcUtil('Hsqr',xCon(1),xX.xKXs); + trRV = spm_SpUtil('trRV',xX.xKXs); + trMV = spm_SpUtil('trMV',X1o); + + % Threshold for voxels entering non-sphericity estimates + %---------------------------------------------------------------------- + try + modality = lower(spm_get_defaults('modality')); + UFp = spm_get_defaults(['stats.' modality '.ufp']); + catch + UFp = 0.001; + end + UF = spm_invFcdf(1 - UFp,[trMV,trRV]); +end + +%-Image dimensions and data +%========================================================================== +VY = SPM.xY.VY; +spm_check_orientations(VY); + +% check files exists and try pwd +%-------------------------------------------------------------------------- +for i = 1:numel(VY) + if ~spm_existfile(VY(i).fname) + [p,n,e] = fileparts(VY(i).fname); + VY(i).fname = [n,e]; + end +end + +M = VY(1).mat; +DIM = VY(1).dim(1:3)'; +xdim = DIM(1); ydim = DIM(2); zdim = DIM(3); +YNaNrep = spm_type(VY(1).dt(1),'nanrep'); + + +%-Maximum number of residual images for smoothness estimation +%-------------------------------------------------------------------------- +MAXRES = spm_get_defaults('stats.maxres'); +nSres = min(nScan,MAXRES); + + +fprintf('%s%30s\n',repmat(sprintf('\b'),1,30),'...done'); %-# + + +%-Initialise output images (unless this is a 1st pass for ReML) +%========================================================================== +if isfield(xX,'W') + fprintf('%-40s: %30s','Output images','...initialising'); %-# + + %-Initialise new mask name: current mask & conditions on voxels + %---------------------------------------------------------------------- + VM = struct('fname', 'mask.img',... + 'dim', DIM',... + 'dt', [spm_type('uint8') spm_platform('bigend')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip','spm_spm:resultant analysis mask'); + VM = spm_create_vol(VM); + + + %-Initialise beta image files + %---------------------------------------------------------------------- + Vbeta(1:nBeta) = deal(struct(... + 'fname', [],... + 'dim', DIM',... + 'dt', [spm_type('float32') spm_platform('bigend')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', '')); + + for i = 1:nBeta + Vbeta(i).fname = sprintf('beta_%04d.img',i); + Vbeta(i).descrip = sprintf('spm_spm:beta (%04d) - %s',i,xX.name{i}); + end + Vbeta = spm_create_vol(Vbeta); + + + %-Initialise residual sum of squares image file + %---------------------------------------------------------------------- + VResMS = struct('fname', 'ResMS.img',... + 'dim', DIM',... + 'dt', [spm_type('float64') spm_platform('bigend')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', 'spm_spm:Residual sum-of-squares'); + VResMS = spm_create_vol(VResMS); + + + %-Initialise standardised residual images + %---------------------------------------------------------------------- + VResI(1:nSres) = deal(struct(... + 'fname', [],... + 'dim', DIM',... + 'dt', [spm_type('float64') spm_platform('bigend')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', 'spm_spm:StandardisedResiduals')); + + for i = 1:nSres + VResI(i).fname = sprintf('ResI_%04d.img', i); + VResI(i).descrip = sprintf('spm_spm:ResI (%04d)', i); + end + VResI = spm_create_vol(VResI); + fprintf('%s%30s\n',repmat(sprintf('\b'),1,30),'...initialised'); %-# +end % (xX,'W') + + +%========================================================================== +% - F I T M O D E L & W R I T E P A R A M E T E R I M A G E S +%========================================================================== + +%-MAXMEM is the maximum amount of data processed at a time (bytes) +%-------------------------------------------------------------------------- +MAXMEM = spm_get_defaults('stats.maxmem'); +mmv = MAXMEM/8/nScan; +blksz = min(xdim*ydim,ceil(mmv)); %-block size +nbch = ceil(xdim*ydim/blksz); %-# blocks +nbz = max(1,min(zdim,floor(mmv/(xdim*ydim)))); nbz = 1; %-# planes +blksz = blksz * nbz; + +%-Initialise variables used in the loop +%========================================================================== +[xords, yords] = ndgrid(1:xdim, 1:ydim); +xords = xords(:)'; yords = yords(:)'; % plane X,Y coordinates +S = 0; % Volume (voxels) +s = 0; % Volume (voxels > UF) +Cy = 0; % spatially whitened +CY = 0; % <(Y - ) * (Y - )'> +EY = 0; % for ReML +i_res = round(linspace(1,nScan,nSres))'; % Indices for residual + +%-Initialise XYZ matrix of in-mask voxel co-ordinates (real space) +%-------------------------------------------------------------------------- +XYZ = zeros(3,xdim*ydim*zdim); + +%-Cycle over bunches blocks within planes to avoid memory problems +%========================================================================== +spm_progress_bar('Init',100,str,''); + +for z = 1:nbz:zdim %-loop over planes (2D or 3D data) + + % current plane-specific parameters + %---------------------------------------------------------------------- + CrPl = z:min(z+nbz-1,zdim); %-plane list + zords = CrPl(:)*ones(1,xdim*ydim); %-plane Z coordinates + CrBl = []; %-parameter estimates + CrResI = []; %-residuals + CrResSS = []; %-residual sum of squares + Q = []; %-in mask indices for this plane + + for bch = 1:nbch %-loop over blocks + + %-Print progress information in command window + %------------------------------------------------------------------ + if numel(CrPl) == 1 + str = sprintf('Plane %3d/%-3d, block %3d/%-3d',... + z,zdim,bch,nbch); + else + str = sprintf('Planes %3d-%-3d/%-3d',z,CrPl(end),zdim); + end + if z == 1 && bch == 1 + str2 = ''; + else + str2 = repmat(sprintf('\b'),1,72); + end + fprintf('%s%-40s: %30s',str2,str,' '); + + %-construct list of voxels in this block + %------------------------------------------------------------------ + I = (1:blksz) + (bch - 1)*blksz; %-voxel indices + I = I(I <= numel(CrPl)*xdim*ydim); %-truncate + xyz = [repmat(xords,1,numel(CrPl)); ... + repmat(yords,1,numel(CrPl)); ... + reshape(zords',1,[])]; + xyz = xyz(:,I); %-voxel coordinates + nVox = size(xyz,2); %-number of voxels + + %-Get data & construct analysis mask + %================================================================= + fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...read & mask data') + Cm = true(1,nVox); %-current mask + + + %-Compute explicit mask + % (note that these may not have same orientations) + %------------------------------------------------------------------ + for i = 1:length(xM.VM) + + %-Coordinates in mask image + %-------------------------------------------------------------- + j = xM.VM(i).mat\M*[xyz;ones(1,nVox)]; + + %-Load mask image within current mask & update mask + %-------------------------------------------------------------- + Cm(Cm) = spm_get_data(xM.VM(i),j(:,Cm),false) > 0; + end + + %-Get the data in mask, compute threshold & implicit masks + %------------------------------------------------------------------ + Y = zeros(nScan,nVox); + for i = 1:nScan + + %-Load data in mask + %-------------------------------------------------------------- + if ~any(Cm), break, end %-Break if empty mask + Y(i,Cm) = spm_get_data(VY(i),xyz(:,Cm),false); + + Cm(Cm) = Y(i,Cm) > xM.TH(i); %-Threshold (& NaN) mask + if xM.I && ~YNaNrep && xM.TH(i) < 0 %-Use implicit mask + Cm(Cm) = abs(Y(i,Cm)) > eps; + end + end + + %-Mask out voxels where data is constant + %------------------------------------------------------------------ + Cm(Cm) = any(diff(Y(:,Cm),1)); + Y = Y(:,Cm); %-Data within mask + CrS = sum(Cm); %-# current voxels + + + %================================================================== + %-Proceed with General Linear Model (if there are voxels) + %================================================================== + if CrS + + %-Whiten/Weight data and remove filter confounds + %-------------------------------------------------------------- + fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...filtering');%-# + + KWY = spm_filter(xX.K,W*Y); + + %-General linear model: Weighted least squares estimation + %-------------------------------------------------------------- + fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...estimation');%-# + + beta = xX.pKX*KWY; %-Parameter estimates + res = spm_sp('r',xX.xKXs,KWY); %-Residuals + ResSS = sum(res.^2); %-Residual SSQ + clear KWY %-Clear to save memory + + + %-If ReML hyperparameters are needed for xVi.V + %-------------------------------------------------------------- + if ~isfield(xVi,'V') + + %-F-threshold & accumulate spatially whitened Y*Y' + %---------------------------------------------------------- + j = sum((Hsqr*beta).^2,1)/trMV > UF*ResSS/trRV; + j = find(j); + if ~isempty(j) + q = size(j,2); + s = s + q; + q = spdiags(sqrt(trRV./ResSS(j)'),0,q,q); + Y = Y(:,j)*q; + Cy = Cy + Y*Y'; + end + + end % (xVi,'V') + + + %-if we are saving the WLS (ML) parameters + %-------------------------------------------------------------- + if isfield(xX,'W') + + %-sample covariance and mean of Y (all voxels) + %---------------------------------------------------------- + CY = CY + Y*Y'; + EY = EY + sum(Y,2); + + %-Save betas etc. for current plane as we go along + %---------------------------------------------------------- + CrBl = [CrBl, beta]; + CrResI = [CrResI, res(i_res,:)]; + CrResSS = [CrResSS, ResSS]; + + end % (xX,'W') + clear Y %-Clear to save memory + + end % (CrS) + + %-Append new inmask voxel locations and volumes + %------------------------------------------------------------------ + XYZ(:,S + (1:CrS)) = xyz(:,Cm); %-InMask XYZ voxel coords + Q = [Q I(Cm)]; %-InMask XYZ voxel indices + S = S + CrS; %-Volume analysed (voxels) + + end % (bch) + + + %-Plane complete, write plane to image files (unless 1st pass) + %====================================================================== + if isfield(xX,'W') + + fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...saving plane'); %-# + + jj = NaN(xdim,ydim,numel(CrPl)); + + %-Write Mask image + %------------------------------------------------------------------ + if ~isempty(Q), jj(Q) = 1; end + VM = spm_write_plane(VM, ~isnan(jj), CrPl); + + %-Write beta images + %------------------------------------------------------------------ + for i = 1:nBeta + if ~isempty(Q), jj(Q) = CrBl(i,:); end + Vbeta(i) = spm_write_plane(Vbeta(i), jj, CrPl); + end + + %-Write standardised residual images + %------------------------------------------------------------------ + for i = 1:nSres + if ~isempty(Q), jj(Q) = CrResI(i,:)./sqrt(CrResSS/erdf); end + VResI(i) = spm_write_plane(VResI(i), jj, CrPl); + end + + %-Write ResSS into ResMS (variance) image scaled by tr(RV) above + %------------------------------------------------------------------ + if ~isempty(Q), jj(Q) = CrResSS; end + VResMS = spm_write_plane(VResMS, jj, CrPl); + + end % (xX,'W') + + %-Report progress + %---------------------------------------------------------------------- + fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...done'); %-# + spm_progress_bar('Set',100*(bch + nbch*(z - 1))/(nbch*zdim)); + + +end % (for z = 1:zdim) +fprintf('\n'); %-# +spm_progress_bar('Clear') + +%========================================================================== +% - P O S T E S T I M A T I O N C L E A N U P +%========================================================================== +if S == 0, spm('alert!','No inmask voxels - empty analysis!'); return; end + +%-average sample covariance and mean of Y (over voxels) +%-------------------------------------------------------------------------- +CY = CY/S; +EY = EY/S; +CY = CY - EY*EY'; + +%-If not defined, compute non-sphericity V using ReML Hyperparameters +%========================================================================== +if ~isfield(xVi,'V') + + %-check there are signficant voxels + %---------------------------------------------------------------------- + if s == 0 + spm('FigName','Stats: no significant voxels',Finter); + spm('Pointer','Arrow'); + if isfield(SPM.xGX,'rg')&&~isempty(SPM.xGX.rg) + figure(Finter); + plot(SPM.xGX.rg); + spm('alert*',{'Please check your data'; ... + 'There are no significant voxels';... + 'The globals are plotted for diagnosis'}); + else + spm('alert*',{'Please check your data'; ... + 'There are no significant voxels'}); + end + warning('Please check your data: There are no significant voxels.'); + return + end + + %-ReML estimate of residual correlations through hyperparameters (h) + %---------------------------------------------------------------------- + str = 'Temporal non-sphericity (over voxels)'; + fprintf('%-40s: %30s\n',str,'...ReML estimation'); %-# + Cy = Cy/s; + + % ReML for separable designs and covariance components + %---------------------------------------------------------------------- + if isstruct(xX.K) + m = length(xVi.Vi); + h = zeros(m,1); + V = sparse(nScan,nScan); + for i = 1:length(xX.K) + + % extract blocks from bases + %-------------------------------------------------------------- + q = xX.K(i).row; + p = []; + Qp = {}; + for j = 1:m + if nnz(xVi.Vi{j}(q,q)) + Qp{end + 1} = xVi.Vi{j}(q,q); + p = [p j]; + end + end + + % design space for ReML (with confounds in filter) + %-------------------------------------------------------------- + Xp = xX.X(q,:); + try + Xp = [Xp xX.K(i).X0]; + end + + % ReML + %-------------------------------------------------------------- + fprintf('%-30s\n',sprintf(' ReML Block %i',i)); + [Vp,hp] = spm_reml(Cy(q,q),Xp,Qp); + V(q,q) = V(q,q) + Vp; + h(p) = hp; + end + else + [V,h] = spm_reml(Cy,xX.X,xVi.Vi); + end + + % normalize non-sphericity and save hyperparameters + %---------------------------------------------------------------------- + V = V*nScan/trace(V); + xVi.h = h; + xVi.V = V; % Save non-sphericity xVi.V + xVi.Cy = Cy; % spatially whitened + SPM.xVi = xVi; % non-sphericity structure + + % If xX.W is not specified use W*W' = inv(V) to give ML estimators + %---------------------------------------------------------------------- + if ~isfield(xX,'W') + if spm_matlab_version_chk('7') >=0 + save('SPM','SPM','-V6'); + else + save('SPM','SPM'); + end + clear + load SPM + SPM = spm_spm(SPM); + return + end +end + + +%-Use non-sphericity xVi.V to compute [effective] degrees of freedom +%========================================================================== +xX.V = spm_filter(xX.K,spm_filter(xX.K,W*V*W')');% KWVW'K' +[trRV trRVRV] = spm_SpUtil('trRV',xX.xKXs,xX.V); % trRV (for X) +xX.trRV = trRV; % +xX.trRVRV = trRVRV; %-Satterthwaite +xX.erdf = trRV^2/trRVRV; % approximation +xX.Bcov = xX.pKX*xX.V*xX.pKX'; % Cov(beta) + + +%-Set VResMS scalefactor as 1/trRV (raw voxel data is ResSS) +%-------------------------------------------------------------------------- +VResMS.pinfo(1) = 1/xX.trRV; +VResMS = spm_create_vol(VResMS); + +%-Smoothness estimates of component fields and RESEL counts for volume +%========================================================================== +try + FWHM = SPM.xVol.FWHM; + VRpv = SPM.xVol.VRpv; + R = SPM.xVol.R; +catch + [FWHM,VRpv,R] = spm_est_smoothness(VResI,VM,[nScan erdf]); +end + +%-Delete the residuals images +%========================================================================== +% j = spm_select('List',SPM.swd,'^ResI_.{4}\..{3}$'); +% for k = 1:size(j,1) +% spm_unlink(deblank(j(k,:))); +% end + + +%-Compute scaled design matrix for display purposes +%-------------------------------------------------------------------------- +xX.nKX = spm_DesMtx('sca',xX.xKXs.X,xX.name); + + +%-Save remaining results files and analysis parameters +%========================================================================== +fprintf('%-40s: %30s','Saving results','...writing'); %-# + +%-place fields in SPM +%-------------------------------------------------------------------------- +SPM.xVol.XYZ = XYZ(:,1:S); %-InMask XYZ coords (voxels) +SPM.xVol.M = M; %-voxels -> mm +SPM.xVol.iM = inv(M); %-mm -> voxels +SPM.xVol.DIM = DIM; %-image dimensions +SPM.xVol.FWHM = FWHM; %-Smoothness data +SPM.xVol.R = R; %-Resel counts +SPM.xVol.S = S; %-Volume (voxels) +SPM.xVol.VRpv = VRpv; %-Filehandle - Resels per voxel + +SPM.Vbeta = Vbeta; %-Filehandle - Beta +SPM.VResMS = VResMS; %-Filehandle - Hyperparameter +SPM.VM = VM; %-Filehandle - Mask + +SPM.xVi = xVi; % non-sphericity structure +SPM.xVi.CY = CY; %-<(Y - )*(Y - )'> + +SPM.xX = xX; %-design structure + +SPM.xM = xM; %-mask structure + +SPM.xCon = struct([]); %-contrast structure + +SPM.SPMid = SPMid; +SPM.swd = pwd; + + +%-Save analysis parameters in SPM.mat file +%-------------------------------------------------------------------------- +if spm_matlab_version_chk('7') >=0 + save('SPM','SPM','-V6'); +else + save('SPM','SPM'); +end + +%========================================================================== +%- E N D: Cleanup GUI +%========================================================================== +fprintf('%s%30s\n',repmat(sprintf('\b'),1,30),'...done') %-# +spm('FigName','Stats: done',Finter); spm('Pointer','Arrow') +fprintf('%-40s: %30s\n','Completed',spm('time')) %-# +fprintf('...use the results section for assessment\n\n') %-# diff --git a/Image_thresholding/clusterSizeMask.m b/Image_thresholding/clusterSizeMask.m new file mode 100755 index 00000000..e2fda239 --- /dev/null +++ b/Image_thresholding/clusterSizeMask.m @@ -0,0 +1,59 @@ +function [mask,numClusters,XYZ] = clusterSizeMask(sizeThresh,height_mask) +% function [mask,numClusters,XYZ] = clusterSizeMask(sizeThresh,height_mask) +% +% Tor Wager, 10/27/01 + + +mask = []; numClusters = 0;, XYZ = []; + +% Get point list of activated voxels +% ------------------------------------------------------ +voxels = mask2voxel(height_mask); % returns n x 3 +voxels = voxels'; % put xyz in a single column + +if isempty(voxels) + disp('No voxels meet height threshold.') + mask = zeros(size(height_mask)); + return +end + +% Get cluster indices of voxels +% ------------------------------------------------------ +[cl_index] = spm_clusters(voxels); + + +% Find index of voxels of sufficient size +% ------------------------------------------------------ +if ~isempty(sizeThresh) & sizeThresh > 0 + for i = 1:max(cl_index) + a(cl_index == i) = sum(cl_index == i); + end +else + sizeThresh = 0; + a = ones(size(cl_index)); +end + +which_vox = (a >= sizeThresh); +numClusters = sum(length(unique(cl_index(find(a >= sizeThresh))))); + +if numClusters == 0 + disp('No clusters meet extent threshold.') + mask = zeros(size(height_mask)); + XYZ = []; + return +end + +voxels = voxels(:,which_vox); + + +% Voxel point list output - 3 vector +% ------------------------------------------------------ +XYZ = voxels; + + +% Convert back to mask for mask output +% ------------------------------------------------------ +voxels = voxels'; % convert back to row vectors +mask = voxel2mask(voxels,size(height_mask)); + +return \ No newline at end of file diff --git a/Image_thresholding/scn_spm_threshold.m b/Image_thresholding/scn_spm_threshold.m new file mode 100644 index 00000000..58885888 --- /dev/null +++ b/Image_thresholding/scn_spm_threshold.m @@ -0,0 +1,17 @@ + +% img should be residual images, I believe. + +VM = spm_vol(mask); + +[FWHM,VRpv] = spm_est_smoothness(img, mask); +R = spm_resels_vol(VM, FWHM)'; + +volInfo = iimg_read_img(mask, 2); + +u = spm_uc(.05, [1 20], 'T', R, 1, volInfo.n_inmask); + +[P p Em En EN] = spm_P(c,k,Z,df,STAT,R,n,S) + +k = 20 +u = .01 +[P Pn Em En EN] = spm_P(1, k, u, [1 20], 'T', R, 1, volInfo.n_inmask); diff --git a/Image_thresholding/threshold_imgs.m b/Image_thresholding/threshold_imgs.m new file mode 100755 index 00000000..42e3d494 --- /dev/null +++ b/Image_thresholding/threshold_imgs.m @@ -0,0 +1,127 @@ +% function [P2,P,sigmat,sigmatneg] = threshold_imgs(dd,u,[k],['pos' 'neg' 'both']) +% +% dd list of filenames (str matrix) +% u height threshold for images +% k extent threshold for contiguous voxels +% [str] 'pos' 'neg' or 'both', to values above, below, +% - and + threshold +% +% +% tor wager +% +% output - threshold t - generic function +% also do: plot rob vs ols benefit by tissue class and ols-irls average +% example: +% [P2,P,s,sn] = threshold_imgs(p([5 8],:),tinv(1-.001,10),0,'both'); +% compare_filtered_t([],P2(1,:),P2(2,:)) +%[p2,p1] = threshold_imgs('irls-ols_z_0001.img',norminv(.9),0,'both'); +% % compare_filtered_t([],P2(1,:),P2(2,:),p2) +%h = image_scatterplot(str2mat(P,p1),'avgvs3'); +%xlabel('Average OLS and Robust t-value'), ylabel('Z-score of Robust - OLS difference') +% +% example: +% s = str2mat('rob_tmap_0002.img','rob_tmap_0003.img'); +% P = threshold_imgs(s,tinv(1-.05,36),[],'pos');P = threshold_imgs(s,tinv(1-.05,36),[],'neg'); + +function [P2,P,sigmat,sigmatneg] = threshold_imgs(dd,u,varargin) + t = u; % t-threshold + iind = 1; % grandfathered + jind = 1; % index of images + + % build output string + str = ['t_' sprintf('%3.2f',u)]; tmp = find(str=='.'); str(tmp)='-';, tmp = find(str==' ');str(tmp)=[]; + tmp = '0'; + if length(varargin) > 0, k = varargin{1}; if k > 0, tmp = num2str(k);,end, end + str = [str '_k' tmp]; + if length(varargin) > 1, tmp = varargin{2};, else,tmp = 'pos';, end + str = [str '_' tmp]; + + for j = 1:size(dd,1) + + % go to directory so we can find image, save full path name of image + + cwd = pwd; + [dr,ff,ee] = fileparts(deblank(dd(j,:))); + if ~isempty(dr), cd(dr), end + dr = pwd; + name = which([ff ee]); + outname = fullfile(dr,[ff '_filt_' str ee]); + if j ==1, P = name; P2 = outname; else, P = str2mat(P,name);,P2 = str2mat(P2,outname);,end + + cd(cwd) + + % read the image + + V = spm_vol(name); + v = spm_read_vols(V); + + coln{jind} = V.fname; + + %t = tinv(1-u,df); + disp(['Threshold is ' num2str(t)]) + + if length(varargin) > 1 + if strcmp(varargin{2},'pos') + v(v < t) = NaN; + elseif strcmp(varargin{2},'neg') + v(-v < t) = NaN; + elseif strcmp(varargin{2},'both') + v(abs(v) < t) = NaN; + else error('Unknown string input.') + end + else + % save both pos and neg + v(abs(v) < t) = NaN; + %figure;hist(v(v ~= 0)) + end + + sigmat(iind,jind) = sum(~isnan(v(:)) & v(:) > 0); + sigmatneg(iind,jind) = sum(~isnan(v(:)) & v(:) < 0); + fprintf(1,'Sig + voxels: %3.0f, - vox: %3.0f', sigmat(iind,jind),sigmatneg(iind,jind)) + + V.descrip = ['Image thresholded at ' num2str(u)]; + + if length(varargin) > 0 + k = varargin{1}; + if k > 0 + fprintf(1,' Thr. k = %3.0f ',k) + + V.descrip = ['Image thresholded at ' num2str(u) ', k = ' num2str(k)]; + + if any(v(:) > 0) + mask = clusterSizeMask(k,v); + else + mask = zeros(size(v)); + end + + if any(v(:) < 0) + mask2 = clusterSizeMask(k,-v); % to save negative results, filtered out earlier if not requested + else + mask2 = zeros(size(v)); + end + + mask = mask | mask2; + v = mask .* v; + + sigmat(iind,jind) = sum(~isnan(v(:)) & v(:) > 0); + sigmatneg(iind,jind) = sum(~isnan(v(:)) & v(:) < 0); + fprintf(1,'\tAfter size thresh: + %3.0f, - %3.0f',sigmat(iind,jind),sigmatneg(iind,jind)) + end + + V.descrip = [V.descrip ' and k = ' num2str(varargin{1})]; + end + fprintf(1,'\n') + + jind = jind + 1; + + if length(varargin) > 1, V.descrip = [V.descrip varargin{2}];, end + + V.fname = outname; + spm_write_vol(V,v); + disp(['Written: ' V.fname]) + + + end % img + + return + diff --git a/Index_image_manip_tools/flip_endianness.m b/Index_image_manip_tools/flip_endianness.m new file mode 100644 index 00000000..cfc82485 --- /dev/null +++ b/Index_image_manip_tools/flip_endianness.m @@ -0,0 +1,38 @@ +function flip_endianness(imgs) + if(iscellstr(imgs)) + imgs = char(imgs); + end + fprintf('Loading meta-info...\n'); + Vimgs = spm_vol(imgs); + + fprintf('Flipping... '); + for i=1:length(Vimgs) + status_string = sprintf('%d/%d', i, length(Vimgs)); + fprintf(status_string); + + % if(isfield(Vimgs(i), 'dt')) + % else + % if(Vimgs(i).dim(4) >= 256) + % Vimgs(i).dim(4) = Vimgs(i).dim(4) / 256; + % else + % Vimgs(i).dim(4) = Vimgs(i).dim(4) * 256; + % end + % Vimgs(i).private.hdr.dime.datatype = Vimgs(i).dim(4); + % end + % + % data = spm_read_vols(Vimgs(i)); + % spm_write_vol(Vimgs(i), data); + + [DIM,VOX,SCALE,TYPE,OFFSET,ORIGIN,DESCRIP] = spm_hread(Vimgs(i).fname); + if(TYPE >= 256) + TYPE = TYPE / 256; + else + TYPE = TYPE * 256; + end + spm_hwrite(Vimgs(i).fname,DIM,VOX,SCALE,TYPE,OFFSET,ORIGIN,DESCRIP); + + erase_string(status_string); + end + + fprintf('\nFinished converting %d images.\n', length(Vimgs)); +end \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_check_indx.m b/Index_image_manip_tools/iimg_check_indx.m new file mode 100644 index 00000000..3be12f86 --- /dev/null +++ b/Index_image_manip_tools/iimg_check_indx.m @@ -0,0 +1,47 @@ +function [dattype,dat] = iimg_check_indx(dat,volInfo,varargin) +% [dattype,dat] = iimg_check_indx(dat,volInfo,[outtype]) +% +% dattype is 'full' or 'masked' +% optional: outtype: 'full' or 'masked' output +% +% tor wager + + +% make sure data is correct format + +if isstr(dat), error('iimg_check_indx does not work with image files. use iimg_read_img first'); end + +% make sure volInfo fields exist + +if ~isfield(volInfo,'nvox') || ~isfield(volInfo,'n_inmask') + error('iimg_check_indx requires nvox and n_inmask fields for volInfo. use iimg_read_img with extended output = 1 or 2') +end + +% check length of dat and return dattype + +if size(dat,1) == volInfo.nvox + dattype = 'full'; +elseif size(dat,1) == volInfo.n_inmask + dattype = 'masked'; +else error('data vector does not match size of image in volInfo!') +end + +% format output +if length(varargin) > 0 && nargout > 1 + outtype = varargin{1}; % full or masked + + if ~strcmp(dattype, outtype) + switch outtype + case 'masked' + dat = dat(volInfo.wh_inmask); + case 'full' + dat2 = zeros(volInfo.nvox,1); + dat2(volInfo.wh_inmask) = dat; + dat = dat2; + otherwise + error('Unknown output type: choose ''masked'' or ''full''') + end + end +end + +return \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_check_volinfo.m b/Index_image_manip_tools/iimg_check_volinfo.m new file mode 100644 index 00000000..fcba37d7 --- /dev/null +++ b/Index_image_manip_tools/iimg_check_volinfo.m @@ -0,0 +1,51 @@ +function [anybad,wh] = iimg_check_volinfo(maskInfo,imgInfo) +% anybad = iimg_check_volinfo(maskInfo,imgInfo) +% +% Checks a series of image .mat files and dims against a reference +% (maskInfo) +% +% maskInfo and volInfo are spm-style volume info structures +% see spm_vol.m +% +wh = []; + +n = length(imgInfo); +notok = zeros(1,n); + +tol = .01; + +for i=1:n + chk = abs(maskInfo.mat - imgInfo(i).mat) > tol; + + chk = any(diag(chk(1:3, 1:3))); + +% % chk = chk(:); +% % chk = chk(1:end-1); % eliminate SPM scale factor and translation +% % chk1 = any(chk); + + chk2 = any(maskInfo.dim(1:3) - imgInfo(i).dim(1:3)); + + notok(i) = chk | chk2; +end + +anybad = any(notok); + +if anybad + wh = find(notok); + + disp('The following images'' mat files or dims differed from the first:') + disp(num2str(wh)); + + disp('First mat:'); + disp(maskInfo.mat); + disp(maskInfo.dim); + + disp('Bad mats:'); + for i = wh + disp(imgInfo(i).fname); + disp(imgInfo(i).mat); + disp(imgInfo(i).dim); + end +end + +return diff --git a/Index_image_manip_tools/iimg_cluster_extent.m b/Index_image_manip_tools/iimg_cluster_extent.m new file mode 100644 index 00000000..54ad91d4 --- /dev/null +++ b/Index_image_manip_tools/iimg_cluster_extent.m @@ -0,0 +1,39 @@ +function [dat,nvox] = iimg_cluster_extent(dat,volInfo,k) +%[dat,nvox] = iimg_cluster_extent(dat,volInfo,k) +% +% Apply a cluster size threshold to a series of index image data vectors +% (each img is one column) +% given volInfo (must be created with iimg_read_img, with extended output) +% +% dat may be an indexed image of all image values or only those in mask +% defined by volInfo + +% do nothing if extent threshold +if isempty(k) || k < 2, nvox = []; return, end + +if size(dat,1) == volInfo.nvox + dattype = 'full'; +elseif size(dat,1) == volInfo.n_inmask + dattype = 'masked'; +else error('data vector does not match size of image in volInfo!') +end + +switch dattype + case 'full', [clindx,nvox] = iimg_cluster_index(dat(volInfo.wh_inmask, :), volInfo.xyzlist', k); + case 'masked', [clindx,nvox] = iimg_cluster_index(dat,volInfo.xyzlist',k); +end + + +switch dattype + case 'full' + for i = 1:size(dat,2) + clindx2 = zeros(volInfo.nvox,1); % put in original index dims + clindx2(volInfo.wh_inmask) = clindx(:,i); + dat(:,i) = dat(:,i) .* (clindx2 > 0); + end + + case 'masked' + dat = dat .* (clindx > 0); +end + +return \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_cluster_index.m b/Index_image_manip_tools/iimg_cluster_index.m new file mode 100644 index 00000000..05265c36 --- /dev/null +++ b/Index_image_manip_tools/iimg_cluster_index.m @@ -0,0 +1,92 @@ +function [clindx,nvox] = iimg_cluster_index(dat,xyz,k) +% [clindx,nvox] = iimg_cluster_index(dat,xyz,[k]) +% xyz is 3 x n list of voxel coords, volInfo.xyzlist' +% dat is index vector of image values +% +% Returns: cluster index and cluster sizes for non-zero, non-nan voxels + +%% initialize +nimgs = size(dat,2); +clindx = zeros(size(dat)); +wh_good_data = cell(1,nimgs); +nvox = cell(1,nimgs); + +%% Get index values +for i = 1:size(dat,2) % for each image index + + % return cluster index for each voxel, or zero if no data + [clindx(:,i),wh_good_data{i}] = cluster_index(dat(:,i),xyz); + + % CLuster extent, if entered + if nargin > 2 && k > 1 + [nvox{i},clindx(:,i),wh_omit] = iimg_cluster_sizes(clindx(:,i),clindx(:,i),k); + else + nvox{i} = iimg_cluster_sizes(clindx(:,i)); + end + +end + +%% + +return + + + + + + +%% eliminate zero or NaN data values +% And make sure we get spm cluster indices +function [clindx,wh_good_data] = cluster_index(dat,xyz) + +clindx = zeros(size(dat)); +wh_good_data = find(dat & ~isnan(dat)); + +if isempty(wh_good_data), return, end + +xyz = xyz(:,wh_good_data); % in-image coordinates + +clusters = get_cluster_index(xyz); +clindx(wh_good_data) = clusters; + + +%% use spm clusters +function clusters = get_cluster_index(xyz) +nvox = size(xyz,2); +if nvox == 0, clusters = []; return, end +if nvox < 50000 + clusters = spm_clusters(xyz)'; +else + disp('Too many voxels for spm_cluster. Blobs will not be correctly broken up into contiguous clusters.') + clusters = ones(nvox,1); +end +return + + +%% Count voxels in each contiguous cluster and threshold, if additional +% args are entered +function [nvox,dat,wh_omit] = iimg_cluster_sizes(clindx,dat,k) + +nclust = max(clindx); +nvox = zeros(1,nclust); +if nargin > 2 + wh_omit = false(size(dat,1),1); +end + +for i = 1:nclust + wh = find(clindx == i); + nvox(i) = length(wh); + + if nargin > 2 + if nvox(i) < k + wh_omit(wh) = 1; + end + end +end + +if nargin > 2 + nvox(nvox < k) = []; + dat(wh_omit) = 0; +end + +return diff --git a/Index_image_manip_tools/iimg_cluster_intersect.m b/Index_image_manip_tools/iimg_cluster_intersect.m new file mode 100644 index 00000000..690bf461 --- /dev/null +++ b/Index_image_manip_tools/iimg_cluster_intersect.m @@ -0,0 +1,32 @@ +function [cl1,cl2,dat1,dat2] = iimg_cluster_intersect(dat1,dat2,volInfo) +% [cl1,cl2,dat1,dat2] = iimg_cluster_intersect(dat1,dat2,volInfo) +% +% Prunes two image data vectors (dat1 and dat2) assumed to contain suprathreshold +% contiguous clusters ("blobs") by saving only those blobs that have one or +% more significant (nonzero) elements in both images +% +% volInfo.xyzlist must contain xyz coordinates corresponding to dat1 and +% dat2 +% dat1 and dat2 can be either image-length or in-mask length vectorized +% images +% +% Try iimg_threshold to create dat vectors from Analyze images (e.g., +% statistic images) +% +% The outputs cl1 and cl2 are the overlap (intersection) clusters, in the +% space of dat1 (cl1) and dat2 (cl2). The outputs dat1 and dat2 are the +% 'pruned' intersection data vectors. +% +% tor wager, july 06 + +% prune dat1 with sig values in dat2 +[dat1] = iimg_cluster_prune(dat1,dat2,volInfo); + +% do the reverse +[dat2] = iimg_cluster_prune(dat2,dat1,volInfo); + +% make clusters +cl1 = iimg_indx2clusters(dat1,volInfo); +cl2 = iimg_indx2clusters(dat2,volInfo); + +return diff --git a/Index_image_manip_tools/iimg_cluster_prune.m b/Index_image_manip_tools/iimg_cluster_prune.m new file mode 100755 index 00000000..9d5dfed8 --- /dev/null +++ b/Index_image_manip_tools/iimg_cluster_prune.m @@ -0,0 +1,50 @@ +function [dat_out,clindx,keepit] = iimg_cluster_prune(dat,datsig,volInfo) +% [dat_out,clindx,keepit] = iimg_cluster_prune(dat,datsig,volInfo) +% +% Prunes an image data vector (dat) assumed to contain suprathreshold +% contiguous clusters ("blobs") by saving only those blobs that have one or +% more significant (nonzero) elements in another image, datsig +% +% One intended use is to define an FWE-corrected significance map in +% datsig, and report blobs at some lower threshold (in dat) that have at +% least one corrected voxel. +% +% Try iimg_threshold to create dat vectors from Analyze images (e.g., +% statistic images) +% +% tor wager, july 06 + +% check data type and reduce to in-mask only if necessary +[dattype,dat] = iimg_check_indx(dat,volInfo,'masked'); +[dattype2,datsig] = iimg_check_indx(datsig,volInfo,'masked'); + +% output in same format as dat input +switch dattype + case 'full' + dat_out = zeros(volInfo.nvox,1); % in full image space + case 'masked' + dat_out = zeros(volInfo.n_inmask,1); % in-mask space + otherwise + error('Internal error. dattype should have been checked earlier.') +end + +clindx = iimg_cluster_index(dat,volInfo.xyzlist'); + +n = max(clindx); +keepit = zeros(1,n); + +for i = 1:n + wh = clindx == i; + keepit(i) = any(datsig(wh)); + + if keepit(i) + switch dattype + case 'full' + dat_out(volInfo.wh_inmask(wh)) = dat(wh); + case 'masked' + dat_out(wh) = dat(wh); + end + end +end + +return diff --git a/Index_image_manip_tools/iimg_clusters2indx.m b/Index_image_manip_tools/iimg_clusters2indx.m new file mode 100644 index 00000000..b1624691 --- /dev/null +++ b/Index_image_manip_tools/iimg_clusters2indx.m @@ -0,0 +1,33 @@ +function [imgvec,maskvec] = iimg_clusters2indx(cl,volInfo) +% [imgvec,maskvec] = iimg_clusters2indx(cl,volInfo) +% [imgvec,maskvec] = iimg_clusters2indx(cl,my_image_name) +% +% Take a clusters structure and turn it into an indexed image of dims +% volInfo.dim +% +% imgvec: vector of all image voxels +% maskvec: vector of in-mask voxels +% uses +% volInfo.nvox, .wh_inmask, .dim + +% convert image to volinfo struct, if necessary +if isstr(volInfo) + volInfo = iimg_read_img(volInfo,2); +end + +imgvec = false(volInfo.nvox,1); + +if isempty(cl) + maskvec = false(volInfo.n_inmask,1); + return +end + +xyz = cat(2,cl.XYZ); + +wh = sub2ind(volInfo.dim(1:3),xyz(1,:)',xyz(2,:)',xyz(3,:)'); + +imgvec(wh) = 1; + +maskvec = imgvec(volInfo.wh_inmask); + +return diff --git a/Index_image_manip_tools/iimg_get_data.m b/Index_image_manip_tools/iimg_get_data.m new file mode 100644 index 00000000..01603ae2 --- /dev/null +++ b/Index_image_manip_tools/iimg_get_data.m @@ -0,0 +1,144 @@ +% [dat, maskInfo] = iimg_get_data(mask, imageNames, varargin) +% +% Read data within a mask (described by maskInfo) for 3-D or 4-D image data +% See examples below. +% +% 3 input options for mask: +% 1) a filename string, e.g., 'mask.img' +% 2) volInfo structure (from iimg_read_img) +% 3) xyz list, n rows x 3 voxel coordinates +% +% Optional inputs: +% +% 'nocheck': Skip checking of image dimensions for homogeneity (saves time) +% 'verbose': Verbose output +% 'single' : This option may be helpful for large datasets. It +% pre-allocates a single-precision matrix for the data (which +% saves space), and then loads the data one image at a time. +% 'noexpand': Skip expanding the list of volumes for 4-D filenames. The +% expansion is the default, and is compatible with spm2 and spm5/8, but is +% not needed for spm5 and above. +% +% Examples: +% 1) [dat, volInfo] = iimg_get_data('graymask.img', imgs); +% +% 3) xyz = [20 20 20; 25 25 25; 30 30 30; 5 5 5]; +% [dat, volInfo] = iimg_get_data(xyz, imgs); +% +% Example of image reading +% ------------------------------------------------------------------------- +% % 1) Get volume info from first volume of a 3-D or 4-D image +% % volInfo structure has necessary info for converting to/from +% % "vectorized" format +% % dat returns 4-D data from entire image (all volumes) +% +% img_name = 'test_run1_pca.img'; +% [maskInfo, dat] = iimg_read_img(img_name, 2); +% +% % 2) Get data in "vectorized" image format for each volume in the +% % image. Works for a list of images too. data is in-mask voxels x volumes +% % this can be useful when you want to return whole-brain data for many +% % images, but in a search volume only (i.e., no extra-brain voxels) +% +% data = iimg_get_data(maskInfo, img_name); +% data = data'; % make sure columns are volumes +% +% % 3) Write out a 4-D image with the same data, called test_run1_pca2.img +% voldat3D = iimg_reconstruct_vols(data, maskInfo, 'outname', +% 'test_run1_pca2.img'); + +function [dat, maskInfo] = iimg_get_data(mask, imageNames, varargin) +docheck = 1; +verbose = 0; +dosingle = 0; +doexpand = 1; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case 'nocheck', docheck = 0; + case 'verbose', verbose = 1; + case 'noverbose', % nothing more to do + case 'single', dosingle = 1; % varargin{i+1}; + case 'noexpand', doexpand = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +% get mask info structure and image info +% ------------------------------------------------------------ +if verbose, fprintf('loading mask. '); tic, end + +if isstruct(mask) + maskInfo = mask; + if ~isfield(maskInfo, 'xyzlist') + error('Need xyzlist in maskInfo. Try using extended output flag with iimg_read_img.'); + end +elseif ischar(mask) + maskInfo = iimg_read_img(mask, 1); +else + % xyz list + maskInfo = struct('xyzlist', mask); + docheck = 0; +end + +wasempty = isempty(imageNames); + +% Expand 4-D filenames for both SPM2 and SPM5 compatibility +if doexpand + if verbose, fprintf('expanding image name list. '); end + imageNames = expand_4d_filenames(char(imageNames)); % for SPM2 compatibility +end + +if isempty(imageNames) + if wasempty, error('Image name list is empty'); + else error('Image names do not seem to exist on path'); + end +end + +if verbose, fprintf('mapping volumes. '); end +imgInfo = spm_vol(imageNames); + +% Check the dimensions +% ------------------------------------------------------------ +% only necessary to check the first image, because spm_vol checks the set +if docheck + if verbose, fprintf('\nchecking that dimensions and voxel sizes of volumes are the same. '); end + anybad = iimg_check_volinfo(maskInfo, imgInfo(1)); + if anybad + disp('Reslice images so dimensions and vox sizes match.'); + disp('Try using the function scn_map_image for an easy way to do this.'); + error('Exiting.'); + end +end + +if dosingle + nvols = length(imgInfo); + nvox = size(maskInfo.xyzlist, 1); + if verbose, fprintf('\nPre-allocating data array. Needed: %3.0f bytes\n', 4*nvols*nvox); end + dat = zeros(nvols, nvox,'single'); + + % load one-at-a-time into single matrix, to save memory + if verbose, fprintf('Loading image number: %5.0f', 0); end + + for i = 1:nvols + if verbose, fprintf('\b\b\b\b\b%5.0f', i); end + dat(i, :) = spm_get_data(imgInfo(i), maskInfo.xyzlist'); + end + + if verbose, fprintf('\n'); end +else + if verbose, fprintf('\nLoading data into double-precision array'); end + dat = spm_get_data(imgInfo, maskInfo.xyzlist'); + +end + +if verbose, toc, end +% Notes: +% This is faster for lists with many voxels (like 200,000) +% but is slower for 44,000 voxels +%tic, dat2 = spm_read_vols(spm_vol(imageNames));, toc +end \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_indx2clusters.m b/Index_image_manip_tools/iimg_indx2clusters.m new file mode 100644 index 00000000..07bdfa9a --- /dev/null +++ b/Index_image_manip_tools/iimg_indx2clusters.m @@ -0,0 +1,183 @@ +% [cl, dat] = iimg_indx2clusters(dat, volInfo, [u], [k]) +% +% cl is clusters +% volInfo is vol info, see iimg_read_img +% +% input dat is data from iimg_read_img, or any vectorized image +% +% output dat is vector of voxels in volInfo.xyzlist( in mask ) +% whose elements are the cluster in cl they belong to (useful for adding +% fields later) +% +% Optional: +% u is height threshold +% if single element, only dat values > u will be saved in clusters +% if two elements, only dat values BETWEEN u(1) and u(2) will be saved +% This convention is the same as that in iimg_threshold. +% +% Tor Wager, updated 4/07 (documentation only) +% Updated March 16, 2009. Fixed bug in cluster extent threhshold dat output +% and bug in returning z-scores for matching voxels when extent threshold + +% abs. is used + +function [cl, dat] = iimg_indx2clusters(dat, volInfo, u, k) + + cl = []; + + %% check for fields + if ~isfield(volInfo,'n_inmask') + error('volInfo.n_inmask and other fields not found. Use iimg_read_image.m with extended output flag to prepare volInfo.'); + end + + %% Prepare data from volinfo + if nargin < 3 + u = 'unknown'; + end + + + if size(dat,1) == volInfo.nvox + z = dat(volInfo.wh_inmask); + elseif size(dat,1) == volInfo.n_inmask + z = dat; + else + error('data vector does not match size of image in volInfo!') + end + + if nargin >= 3 && ~isempty(u) + % impose threshold + if length(u) == 2 + z(z < u(1) | z > u(2)) = 0; + else + z(z < u) = 0; + end + end + + xyz = volInfo.xyzlist'; + XYZmm = voxel2mm(xyz, volInfo.mat); + + + %% eliminate zero or NaN data values + % And make sure we get spm cluster indices + z(isnan(z)) = 0; + wh = ~z; + if any(wh) + % we eliminate some voxels that are in-mask but don't have valid values + z(wh) = []; + xyz(:,wh) = []; + XYZmm(:,wh) = []; + clusters = get_cluster_index(xyz); + elseif isfield(volInfo, 'clusters') + clusters = volInfo.cluster; + else + clusters = spm_clusters(volInfo.xyzlist')'; + end + + sig_regions = ~wh; + + %% save dat output with cluster indices in elements: which cl for each vox + if nargout > 1 + dat = zeros(volInfo.n_inmask, 1); %double(dat); + dat(sig_regions) = clusters; + end + + %% Cluster size threshold, if specified + if nargin > 3 && k > 1 + [nvox, dat, wh_omit_cl, wh_omit_vox] = iimg_cluster_sizes(clusters, dat, k, sig_regions); + + clusters(wh_omit_vox) = []; + xyz(:,wh_omit_vox) = []; + XYZmm(:,wh_omit_vox) = []; + z(wh_omit_vox) = []; + + %% 2nd output stuff +% if nargout > 1 +% for i = 1:length(wh_omit) +% dat(dat == wh_omit(i)) = 0; +% end +% end + end + + %% Return if no voxels + if isempty(clusters), return; end + + %% define cluster structure + clnumbers = unique(clusters); % some could be missing because they're too small + nclust = length(clnumbers); + cl = struct('title',[],'threshold',[],'M',[],'dim',[],'voxSize',[],'name',[],'Z',[],'XYZmm',[],'XYZ',[]); + cl = repmat(cl,1,nclust); + + %% Fill in fields + for i = 1:nclust + % voxels in this cluster + wh_incluster = find(clusters == clnumbers(i)); + nvox = length(wh_incluster); + + % re-number dat (cluster index) according to final output + dat(dat == clnumbers(i)) = i; + + cl(i).title = sprintf('Cluster of %3.0f voxels from %s',nvox, volInfo.fname); + + % some included for backward compatibility + cl(i).threshold = u; + cl(i).M = volInfo.mat; + cl(i).dim = volInfo.dim; + cl(i).voxSize = diag(volInfo.mat(1:3,1:3))'; + cl(i).name = ''; + cl(i).numVox = nvox; + cl(i).Z = z(wh_incluster)'; + + + cl(i).XYZmm = XYZmm(:,wh_incluster); + cl(i).XYZ = xyz(:,wh_incluster); + + cl(i).mm_center = center_of_mass(cl(i).XYZmm,cl(i).Z); + end +end + + + + +% Sub-functions + +function clusters = get_cluster_index(xyz) +nvox = size(xyz,2); +if nvox == 0 + clusters = []; + return; + + % WORKS IN SPM5 -- CHANGING + % elseif nvox < 50000 +else + clusters = spm_clusters(xyz)'; + % else + % clusters = ones(nvox,1); +end +end + + +% Count voxels in each contiguous cluster and threshold, if additional +% args are entered +function [nvox, dat, wh_omit_cl, wh_omit_vox] = iimg_cluster_sizes(clindx, dat, k, sig_regions) + nclust = max(clindx); + nvox = zeros(1,nclust); + if nargin > 2 + wh_omit_cl = false(nclust,1); + wh_omit_vox = false(length(clindx),1); + end + + for i = 1:nclust + wh = find(clindx == i); + nvox(i) = length(wh); + + if nargin > 2 + if nvox(i) < k + wh_omit_cl(i) = 1; + wh_omit_vox(wh) = 1; + end + end + end + + sr = find(sig_regions); + dat(sr(wh_omit_vox)) = 0; +end + diff --git a/Index_image_manip_tools/iimg_indx2contiguousxyz.m b/Index_image_manip_tools/iimg_indx2contiguousxyz.m new file mode 100755 index 00000000..b93bf78c --- /dev/null +++ b/Index_image_manip_tools/iimg_indx2contiguousxyz.m @@ -0,0 +1,40 @@ +function cl = iimg_indx2contiguousxyz(dat,volInfo,remove_mean_flag) +% cl = iimg_indx2contiguousxyz(dat,volInfo,[remove_mean_flag]) +% +% Take in index image data vector (in-mask values only) and a volume info structure with xyzlist +% and return a cl structure whose XYZ values list contiguous sets of voxels +% ("blobs") +% +% If a 3rd arg is entered, means of each blob are subtracted +% This is to facilitate randomizing blob centers in +% meta_stochastic_activation_blobs.m +% +% Tor Wager, June 06 + +if nargin > 2, docenter = 1; else docenter = 0; end + +%n = size(dat,1); + +wh = find(dat); +xyz = volInfo.xyzlist(wh,:)'; + +cl = []; +if isempty(xyz), return, end + +clusterid = spm_clusters(xyz); + +for i = 1:max(clusterid) + cl(i).XYZ = xyz(:,clusterid==i); + + if docenter + m = mean(cl(i).XYZ,2); + cl(i).XYZ(1,:) = cl(i).XYZ(1,:) - m(1); + cl(i).XYZ(2,:) = cl(i).XYZ(2,:) - m(2); + cl(i).XYZ(3,:) = cl(i).XYZ(3,:) - m(3); + + cl(i).XYZ = round(cl(i).XYZ); + end + +end + +return \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_intersection.m b/Index_image_manip_tools/iimg_intersection.m new file mode 100644 index 00000000..80ade5ee --- /dev/null +++ b/Index_image_manip_tools/iimg_intersection.m @@ -0,0 +1,126 @@ +function [int_dat,mask_vol,outname] = iimg_intersection(varargin) +%[int_dat,mask_vol,outname] = iimg_intersection(name1, name2, etc.) +% [int_dat,mask_vol,outname] = iimg_intersection(dat1, dat2, etc.) +% +% Make a mask of the intersection of n image files (pos or neg values) +% If 'name' followed by an image file name is entered, writes output to +% that image +% +% If special string 'posneg' is entered, separates first two images into +% combinations of pos/neg values in each image. +% The order returned in columns of int_dat is pospos, posneg, negpos, and +% negneg +% +% fastest if one output requested. +% +% ------------------------------------------------------ +% setup input read images and special strings +% ------------------------------------------------------ + +dat = []; % data +doposneg = 0; % separate results for pos and neg combos + +if nargin == 0 + imname = spm_get(1); + [volInfo,dat] = iimg_read_img(imname); +else + for i = 1:length(varargin) + v = varargin{i}; + if isstruct(v), volInfo = v; + + % put control strings (special strings) here + elseif ischar(v) && strcmp(v,'posneg') + doposneg = 1; % separate positive and negative overlap + + elseif ischar(v) && strcmp(v,'name') + outname = varargin{i+1}; + + elseif i > 1 && ischar(varargin{i-1}) && strcmp(varargin{i-1},'name') + % do nothing; this is the output name, ignore it + + else % treat this input as an input image file or data vector + + if isempty(dat) %~exist('volInfo','var') % first image, use as mask space + imname = v; + [volInfo,d] = iimg_read_img(imname, 2); + + else + d = scn_map_image(v, imname); + d = d(:); + end + + if size(dat,1) ~= 0 && size(d,1) ~= size(dat,1) + error('Not all images are the same size.'); + else + dat = [dat d]; + end + end + end +end + +if ~exist('volInfo','var') || isempty(volInfo) + error('You must enter at least one image file name or enter a volInfo structure as input.') +end + +% ------------------------------------------------------ +% setup input read images and special strings +% ------------------------------------------------------ +if doposneg + fprintf(1,'Separating combinations of pos and neg values for first two input images.\n') + int_dat = dat(:,1) > 0 & dat(:,2) > 0; % pos pos + int_dat = [int_dat dat(:,1) > 0 & dat(:,2) < 0]; % pos neg + int_dat = [int_dat dat(:,1) < 0 & dat(:,2) > 0]; % neg pos + int_dat = [int_dat dat(:,1) < 0 & dat(:,2) < 0]; % neg neg +else + % 'normal' way; abs value + int_dat = all(abs(dat),2); +end + +if nargout >= 2 && ~exist('outname','var') + outname = 'iimg_intersection_output_image.img'; + + if doposneg + for i = 1:4 + mask_vol(:,:,:,i) = iimg_reconstruct_3dvol(int_dat(:,i),volInfo); + end + else + mask_vol = iimg_reconstruct_3dvol(int_dat,volInfo); + end + + % ------------------------------------------------------ + % write output images + % ------------------------------------------------------ + +elseif exist('outname','var') + + %name = input('Enter output filename: ','s'); + if doposneg + name1 = append_to_name(outname,'pospos'); + mask_vol(:,:,:,1) = iimg_reconstruct_3dvol(int_dat(:,1),volInfo,'outname',name1,'descrip','Positive values for both imgs'); + + name1 = append_to_name(outname,'posneg'); + mask_vol(:,:,:,2) = iimg_reconstruct_3dvol(int_dat(:,2),volInfo,'outname',name1,'descrip','Positive values for 1st, neg for 2nd img'); + + name1 = append_to_name(outname,'negpos'); + mask_vol(:,:,:,3) = iimg_reconstruct_3dvol(int_dat(:,3),volInfo,'outname',name1,'descrip','Positive values for 2nd, neg for 1st img'); + + name1 = append_to_name(outname,'negneg'); + mask_vol(:,:,:,4) = iimg_reconstruct_3dvol(int_dat(:,4),volInfo,'outname',name1,'descrip','Negative values for both imgs'); + + else + + mask_vol = iimg_reconstruct_3dvol(int_dat,volInfo,'outname',outname,'descrip','intersection image created with iimg_intersection'); + end +end + +return + + + + +function name = append_to_name(outname,str) + +[d,f,e] = fileparts(outname); +name = fullfile(d,[f str e]); + +return \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_make_sure_indx.m b/Index_image_manip_tools/iimg_make_sure_indx.m new file mode 100644 index 00000000..16c2d7fd --- /dev/null +++ b/Index_image_manip_tools/iimg_make_sure_indx.m @@ -0,0 +1,26 @@ +function dat = iimg_make_sure_indx(inputarg) +% dat = iimg_make_sure_indx(inputarg) +% +% Make sure input is an index list, and convert if not + +[m,n] = size(inputarg); + +if isstr(inputarg) + % it's a file name + [V,dat] = iimg_read_image(inputarg); + +elseif n > 1 + % it's a matrix + dat = inputarg(:); + +else + % it's an index vector + % do nothing + dat = inputarg; +end + +return + + + + diff --git a/Index_image_manip_tools/iimg_mask.m b/Index_image_manip_tools/iimg_mask.m new file mode 100644 index 00000000..ee3bb328 --- /dev/null +++ b/Index_image_manip_tools/iimg_mask.m @@ -0,0 +1,78 @@ +function [dat, volInfo, masked_vol] = iimg_mask(maskindx,dat,varargin) +% [masked_indx, volInfo, masked_vol] = iimg_mask(maskindx,dat,[volInfo], [outname]) +% +% Masks index image vector or filenames in dat +% With img vector or filenames in maskindx +% * Checks dimensions to make sure origins and vox. sizes are same +% +% fastest way to mask an image: +% [masked_indx_data, volInfo] = iimg_mask(maskindx,dat) +% +% or +% +% [masked_indx, xyz, masked_vol] = iimg_mask(mask filename,image filenames) +% +% even slower: reconstruct a 3-D volume and return in masked_vol + + +[volInfo, maskindx] = iimg_read_img(maskindx,1); +[volInfod, dat] = iimg_read_img(dat); + +% input volInfo overrides; in case maskindx is data vector instead of +% filename +if length(varargin) > 0 && ~isempty(varargin{1}), volInfo = varargin{1}; end + + +% deal with multiple images +% -------------------------------------------- +[nvox,nimgs] = size(dat); + + +% reduce dat, if maskindx is reduced to in-mask only +% if both are image files, this accomplishes the masking +% -------------------------------------------- +if nvox == volInfo.nvox && size(maskindx,1) ~= volInfo.nvox + dat = dat(volInfo.wh_inmask,:); +end + + +% check sizes +% -------------------------------------------- +if size(maskindx,1) - size(dat,1) + error('Sizes of mask and image data do not match.'); +end + + +if isstruct(volInfo) && isstruct(volInfod) + % voxel sizes and origins + m = [diag(volInfo.mat(1:3,1:3))' volInfo.mat(1:3,4)']; + m2 = [diag(volInfod.mat(1:3,1:3))' volInfod.mat(1:3,4)']; + + if any(m-m2) + error('Voxel sizes or origins for mask and image data do not match.'); + end +end + + +% masking +% -------------------------------------------- +dat(~maskindx) = 0; + + +if nargout > 2 + % slower, reconstruct mask + %if nargin < 3, error('You must enter volume structure V to reconstruct 3-D vol');, end + + for i = 1:nimgs + if length(varargin) > 1 + outname = varargin{2}; + masked_vol(:,:,:,i) = iimg_reconstruct_3dvol(dat(:,i),volInfo, 'outname', outname); + + else + masked_vol(:,:,:,i) = iimg_reconstruct_3dvol(dat(:,i),volInfo); + end + + end +end + +return diff --git a/Index_image_manip_tools/iimg_multi_threshold.m b/Index_image_manip_tools/iimg_multi_threshold.m new file mode 100755 index 00000000..75b1aead --- /dev/null +++ b/Index_image_manip_tools/iimg_multi_threshold.m @@ -0,0 +1,539 @@ +% [cl, dat, cl_extent, dat_extent] = iimg_multi_threshold(inname, varargin) +% +% Multi-threshold application and orthview display of blobs +% +% inname: Either the name of an image file or a data vector +% if data, enter volInfo structure separately as below. +% +% Command strings: +% 'prune' : consider only contiguous blobs for which at least 1 voxel meets +% the most stringent threshold +% 'pruneseed' : followed by a vectorized thresholded activation image +% Only those blobs overlapping with at least one 'seed' data voxel +% are saved +% Also: the first color (highest threshold) in the output images is assigned to the seed +% 'add' : add new blobs to existing orthviews +% 'p' : if image is a p-value image (and thresholds are p-values) +% +% 'thresh' : followed by a vector of n thresholds, most to least stringent +% 'size' : followed by a vector of size thresholds +% 'volInfo': followed by volInfo struct; needed if inname is data rather +% than filenames +% 'overlay' : followed by overlay image (anatomical) filename +% 'transseed': transparent seed +% 'hideseed': do not show seed regions on plot +% 'pos': positive results only +% 'neg': negative results only +% 'add': add to existing multi-threshold plot +% +% Needs (development): Legend, nice handling of colors, input +% colors, color maps specified with command words (like 'red') +% +% tor wager, July 1, 2006 +% +% Examples: +% inname = 'Activation_proportion.img'; +% [cl, dat] = iimg_multi_threshold(inname, 'prune', 'thresh', [.1 .5 .3], 'size', [1 5 10]); +% +% cl2 = iimg_multi_threshold(Pimg(1, :), 'thresh', [.001 .01 .05], 'size', [3 5 10], 'p'); +% cl2 = iimg_multi_threshold(Pimg(1, :), 'thresh', [.001 .01 .05], 'size', [3 3 3], 'p', 'prune'); +% +% from act + corr results (see robust_results_act_plus_corr) +% First prepare 'seed' regions that overlap with correlated regions, then +% use multi_threshold to see full extent of clusters +% [dattype, seeddat] = iimg_check_indx(res.act.overlapdat, res.volInfo, 'full'); +% [cl, dat] = iimg_multi_threshold('rob_p_0001.img', 'p', 'thresh', [.001 .01 .05], 'size', [1 1 1], 'pruneseed', seeddat) +% +% Display an F-map from robust regression on a customized mean anatomical, +% with pruning. +% cl = iimg_multi_threshold('rob_pmap_full.img', 'thresh', [.001 .01 .05], 'size', [1 1 1], 'p', 'prune', 'overlay', EXPT.overlay); +% +% Display regions at 3 thresholds with an input data 'seed' vector +% [cl, dat] = iimg_multi_threshold(pvals, 'p', 'thresh', [.001 .01 .05], 'size', [1 1 1], 'pruneseed', p_sig', 'volInfo', R.volInfo); +% +% As above, but ONLY POSITIVE RESULTS +% [cl, datout] = iimg_multi_threshold(pimg1, 'thresh', [.001 .01 .05], 'size', [1 1 1], 'p', 'pruneseed', dat, 'overlay', EXPT.overlay, 'colors', colors, 'transseed', 'pos', 'rob_tmap_0001.img'); +% +% Complete example for showing positive and negative blobs: +% [clpos] = iimg_multi_threshold('slope_p.img', 'p', 'thresh', [.005 .01 .05], 'size', [1 1 1], 'prune', 'overlay', anat, 'pos', 'slope_t.img'); +% [clneg] = iimg_multi_threshold('slope_p.img', 'p', 'thresh', [.005 .01 .05], 'size', [1 1 1], 'prune', 'overlay', anat, 'neg', 'slope_t.img', 'add', 'colors', {[0 0 1] [0 .5 1] [.4 .5 1]}); + +% Programmers' notes: +% tor: changed to SPM8 overlay, April 2011 +% tor: fixed bug in table printing (cluster_extent) that was not using sign +% info to calculate extent, returning unsigned extent maps identical across +% positive and negative results in some cases. + +function [cl, dat, cl_extent, dat_extent] = iimg_multi_threshold(inname, varargin) + +% -------------------------------------- +% Setup and defaults +% -------------------------------------- + +cl = []; +cl_extent = []; +dat = []; +dat_extent = []; +signdat = []; + +% spm 99 overlay = which('scalped_single_subj_T1.img'); +overlay = which('SPM8_colin27T1_seg.img'); % spm8 seg cleaned up +if isempty(overlay) + overlay = which('spm2_single_subj_T1_scalped.img'); +end + +mask = []; +thresh = [.10 .05 .04]; +szthresh = [1 10 10]; +colors = [1 1 0; 1 .5 0; 1 .3 .3; 0 1 0; 0 0 1]; +dotransseed = 0; +dohideseed = 0; +add2existing = 0; +dopruneclusters = 0; +ispimg = 0; +posnegstr = 'both'; +signimg = []; +wh_handle = 1; +fdr_thresh = .05; % default FDR threshold + +for i = 1:length(varargin) + if ischar(varargin{i}) && ~isempty(varargin{i}) + switch varargin{i} + % reserved keywords + case 'prune', dopruneclusters = 1; + case 'add', add2existing = 1; + case 'p', ispimg = 1; + + % functional commands + case 'pruneseed', dopruneclusters = 1; pruneseed = varargin{i+1}; + case 'noprune', dopruneclusters = 0; + + case 'thresh', thresh = varargin{i+1}; + case 'size', szthresh = varargin{i+1}; + case 'volInfo', volInfo = varargin{i+1}; + case 'overlay', overlay = varargin{i+1}; varargin{i+1} = []; + case 'mask', mask = varargin{i+1}; varargin{i+1} = []; + case 'colors', colors = varargin{i+1}; + case 'transseed', dotransseed = 1; + case 'hideseed', dohideseed = 1; + case 'pos', posnegstr = 'pos'; signimg = varargin{i+1}; varargin{i+1} = []; % image containing signs of vectors + case 'neg', posnegstr = 'neg'; signimg = varargin{i+1}; varargin{i+1} = []; + + case 'fdrthresh', fdr_thresh = varargin{i + 1}; + + case 'handle', wh_handle = varargin{i+1}; + + case 'noadd' % do nothing + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if ischar(inname) && ~isempty(inname) + % deal with 4-D files: use only first volume + inname = check_valid_imagename(inname, 1); + + inname = expand_4d_filenames(inname); + if size(inname, 1) > 1 + fprintf('Using only first volume in 4-D image file: \n%s\n', inname(1,:)); + inname = inname(1, :); + end + +elseif isempty(inname) + disp('Input image name is empty!') + return +end + +if exist('signimg', 'var') && ~isempty(signimg) && ischar(signimg) + signimg = check_valid_imagename(signimg, 1); + + % deal with 4-D files: use only first volume + signimg = expand_4d_filenames(signimg); + if size(signimg, 1) > 1 + fprintf('Using only first volume in 4-D image file: \n%s\n', signimg(1,:)); + signimg = signimg(1, :); + end +end + +num_thresholds = length(thresh); + +fprintf(1, '\niimg_multi_threshold viewer\n=====================================================\n'); +fprintf(1, 'Showing positive or negative: %s\n', posnegstr); +fprintf(1, 'Overlay is: %s\n', overlay); +if isempty(mask) + fprintf(1, 'No mask specified in iimg_multi_threshold\n'); +elseif ischar(mask) + fprintf(1, 'Mask is: %s\n', mask); +else + fprintf(1, 'Mask is entered in iimg_multi_threshold as a vector of numerical values\n'); +end + +ynstr = {'No' 'Yes'}; +fprintf(1, 'Entered p-value image? : %s\n', ynstr{ispimg+1}); +fprintf(1, 'Height thresholds:') +fprintf(1, '%3.4f ', thresh); +fprintf(1, '\n'); +fprintf(1, 'Extent thresholds: '); +fprintf(1, '%3.0f ', szthresh); +fprintf(1, '\n'); +ynstr = {'No' 'Yes'}; +fprintf(1, 'Show only contiguous with seed regions: %s\n ', ynstr{dopruneclusters+1}); +fprintf(1, '\n'); + + + +% -------------------------------------- +% Load image with signed values, if we have one +% -------------------------------------- +if ~isempty(signimg) + + if ischar(signimg) + [dummy, signdat] = iimg_read_img(signimg); + + elseif isa(signimg, 'image_vector') + signdat = signimg.dat(:, 1); + + elseif isa(signimg, 'double') + signdat = signimg; + + else + error('Enter string name or image_vector object for sign image.'); + end + + switch posnegstr + case 'pos' + signdat = signdat > 0; + case 'neg' + signdat = signdat < 0; + otherwise + error('Sign string should be pos or neg.'); + end +end + +% -------------------------------------- +% Apply mask now, to simplify things later +% Also apply mask from sign image, if we have one +% Now mask has all relevant masking data +% -------------------------------------- +% Tor edited 7/2014 + +if ~isempty(mask) && ischar(mask) + mask = iimg_read_img(mask); +end + +if ~isempty(signdat) + if isempty(mask), mask = true(size(signdat)); end + + if any(size(signdat) ~= size(mask)) + error('Mask and sign data image do not appear to be the same dims. Check inputs.'); + end + mask = mask .* signdat; +end + +% -------------------------------------- +% Threshold images - FIRST threshold only +% -------------------------------------- +if ispimg + current_thresh = [0 thresh(1)]; + lowest_thresh = [0 thresh(end)]; % for pruning based on extent + + disp('Warning: p-values in cl.Z will not give valid spm_max subclusters.') + disp('log(1/p) saved in output cl.Z field.'); + + imgtype = 'p'; + if(thresh(1) == Inf) + threshtype = 'fdr'; + current_thresh = fdr_thresh; % default FDR or user-specified + else + threshtype = 'p'; + end +else + if exist('signimg', 'var') && ~isempty(signimg) && ischar(signimg) && strcmp(posnegstr, 'neg') + % reverse to go from -Inf to cutoff for negative responses only + current_thresh = [-Inf thresh(1)]; + lowest_thresh = [-Inf thresh(end)]; + else + current_thresh = [thresh(1) Inf]; + lowest_thresh = [thresh(end) Inf]; + end + imgtype = 'data'; + threshtype = 'none'; +end + +% Actually load the image and threshold +% -------------------------------------- +if isa(inname, 'statistic_image') + dat = threshold(inname, current_thresh(2), 'unc', 'k', szthresh(1)); + volInfo = dat.volInfo; + dat = replace_empty(dat); + dat = dat.dat(:, 1); + +elseif exist('volInfo', 'var') + % use input volInfo if entered; this is so you can input an indexed image + % instead of a filename and get extended output (cluster sizes) + dat = iimg_threshold(inname, 'thr', current_thresh, 'k', szthresh(1), 'imgtype', imgtype, 'threshtype', threshtype, 'mask', mask, 'volInfo', volInfo); + +elseif ischar(inname) + % This option if inputs are filenames + [dat, volInfo] = iimg_threshold(inname, 'thr', current_thresh, 'k', szthresh(1), 'imgtype', imgtype, 'threshtype', threshtype, 'mask', mask); +else + error('Enter statistic_image object or string file name for input image.'); +end + + +% restrict to only pos or neg values if posneg option is specified +if ~isempty(signimg) + dat = dat .* signdat; % all data, which will be used as pruneseed + + if exist('pruneseed', 'var') + pruneseed = pruneseed .* signdat; + end +end + + +if dopruneclusters + if exist('pruneseed', 'var') + % find vox at Lowest threshold that are contiguous with seed + % this defines 'extent'. Then, later, get any voxels that are + % contiguous with the voxels in the extent region at higher thresholds. + % this way, voxels at an intermediate thresh that are contiguous with + % the extent region but not with the actual seed are included. + % -------------------------------------- + % tor edit: 8/1/09. add mask. when masking, extent region can + % cover masked areas, leaving 'orphan' voxels that do not obey + % the extent threshold + % tor edit: 7/22/14. mask should now incorporate signdat info as well. + + dat_extent = get_extent_regions(inname, pruneseed, lowest_thresh, szthresh(end), volInfo, mask); + else + % use highest threshold as seed + dat_extent = get_extent_regions(inname, dat(:,1), lowest_thresh, szthresh(end), volInfo, mask); + end + + cl_extent = iimg_indx2clusters(dat_extent, volInfo); + + if sum( abs(dat_extent) > eps*10 ) > 50000 + disp('Warning! Too many voxels at lowest threshold to cluster. Cluster extent-based pruning will not work correctly.'); + end + +else + % aug 2010 fix: if only one threshold, clpos_extent == clpos, so + % mediation tables will print correctly + dat_extent = get_extent_regions_noprune(inname, lowest_thresh, szthresh(end), volInfo, mask); + cl_extent = iimg_indx2clusters(dat_extent, volInfo); +end + + + + +% -------------------------------------- +% Get other thresholds +% -------------------------------------- + +dat = [dat zeros(size(dat, 1), length(thresh)-1)]; + +if dopruneclusters % exist('pruneseed', 'var') % Tor changed, 8/1/09 + % save only blobs that show some activation in extent region (lowest thresh) around seed input image + dat(:,1) = iimg_cluster_prune(dat(:,1), dat_extent, volInfo); +end + +for i = 2:num_thresholds + + if ispimg + current_thresh = [0 thresh(i)]; + + imgtype = 'p'; + if(thresh(i) == Inf) + threshtype = 'fdr'; + else + threshtype = 'p'; + end + else + current_thresh = [thresh(i) Inf]; + imgtype = 'data'; + threshtype = 'none'; + end + + + if thresh(i) >= 0 + dat(:,i) = iimg_threshold(inname, 'thr', current_thresh, 'k', szthresh(i), 'imgtype', imgtype, 'threshtype', threshtype, 'mask', mask, 'volInfo', volInfo); + else + % negative threshold; invalid for p-maps + dat(:,i) = iimg_threshold(inname, 'thr', [-Inf thresh(i)], 'k', szthresh(i), 'imgtype', imgtype, 'threshtype', threshtype, 'mask', mask, 'volInfo', volInfo); + end + + if dopruneclusters + % -------------------------------------- + % save only blobs that show some activation + % WITHIN extent region ; Tor, 8/1/09 + % -------------------------------------- + dat(:, i) = dat(:, i) .* abs(dat_extent) > 10*eps; + + % if exist('pruneseed', 'var') + % dat(:,i) = iimg_cluster_prune(dat(:,i), dat_extent, volInfo); + % else + % dat(:,i) = iimg_cluster_prune(dat(:,i), dat_extent, volInfo); + % end + end +end + + +% -------------------------------------- +% Load image with signed values, if we have one +% Apply sign to dat +% -------------------------------------- +if ~isempty(signimg) + % restrict to only pos or neg values if posneg option is specified + dat = dat .* repmat(signdat, 1, num_thresholds); +end + + + + +% -------------------------------------- +% Append 'seed', if entered, as "highest threshold" image vector +% So it gets shown and assigned the first color +% -------------------------------------- +if exist('pruneseed', 'var') + if dotransseed + % don't include seed in things to plot w/solid colors + % this also means don't remove voxels that are in seed regions from + % lower thresholds + clseed = iimg_indx2clusters(pruneseed, volInfo); + else + dat = [pruneseed dat]; + num_thresholds = num_thresholds+1; + end +end + +% -------------------------------------- +% Report significant voxels +% -------------------------------------- + +% Get rid of voxels that appear in earlier ("higher") maps +mysum = cumsum(dat, 2); +mysum(:, end) = []; +mysum = [zeros(size(mysum, 1), 1) mysum]; +mysum = mysum > 0; +dat(mysum) = 0; + +% -------------------------------------- +% Report significant voxels +% -------------------------------------- +sigvox = sum(dat>0); + +% make clusters +cl = cell(1, num_thresholds); +for i = 1:num_thresholds + + %voldata = iimg_reconstruct_3dvol(dat(:,i), volInfo); + %cl{i} = mask2clusters(voldata, volInfo.mat); + + cl{i} = iimg_indx2clusters(dat(:,i), volInfo); +end + + +% -------------------------------------- +% define colors +% -------------------------------------- +%colors = [linspace(1, 0, 3)' linspace(1, .3, 3)' linspace(0, .3, 3)']; + + +% -------------------------------------- +% image clusters on brain (orthviews) +% -------------------------------------- + +if add2existing, addstr = 'add'; else addstr = 'noadd'; end + +if exist('pruneseed', 'var') + if dotransseed && ~dohideseed + cluster_orthviews(clseed, {colors(end, :)}, 'overlay', overlay, 'trans', addstr, 'handle', wh_handle, 'skipempty'); + addstr = 'add'; + end +end + +for i = 1:num_thresholds + if i == 1 && dohideseed && ~dotransseed + % dat(:,1) is seed, but we have asked not to show it + % do nothing + else + if iscell(colors) + thiscolor = colors{i}; + else + thiscolor = colors(i,:); + end + + cluster_orthviews(cl{i}, {thiscolor}, 'overlay', overlay, 'solid', addstr, 'handle', wh_handle, 'skipempty'); + if ~isempty(cl{i}), addstr = 'add'; end % after first one, add additional to existing + + if ispimg + % replace p-values with log(1/p) in Z field. Higher is thus more + % sig. for compatibility with spm_max + for j = 1:length(cl{i}) + cl{i}(j).Z_descrip = 'Log(1/p)'; + cl{i}(j).Z = log(1./cl{i}(j).Z); + end + end + + end +end + + +% Title on figure +fh = findobj('Tag', 'Graphics'); +ch = get(fh, 'Children'); +if isempty(ch) + disp('Cannot find SPM graphics window. It should have a tag of ''Graphics''.'); +else + for i= 1:length(ch), mytype = get(ch(i), 'Type'); wh(i)=strcmp(mytype, 'axes'); end + if ~exist('wh','var') + disp('Found SPM graphics window, but cannot find any axes.'); + else + axish = ch(wh); + %axish = sort(axish); + axes(axish(1)); + end +end + +if exist('pruneseed', 'var') && ~dotransseed + seedstr = 'seed '; +else + seedstr = ' '; +end +if(any(isinf(thresh))) + thrstr = repmat(' %3.6f', 1, length(thresh)-1); + str = sprintf(['%s thr: %s FDR' thrstr], posnegstr, seedstr, thresh(~isinf(thresh))); +else + thrstr = repmat(' %3.6f', 1, length(thresh)); + str = sprintf(['%s thr: %s' thrstr], posnegstr, seedstr, thresh); +end + +title(str, 'FontSize', 18); +end + + + + +function dat_extent = get_extent_regions(inname, pruneseed, lowest_thresh, szthresh, volInfo, mask) +if lowest_thresh >= 0 + dat = iimg_threshold(inname, 'thr', lowest_thresh, 'k', szthresh, 'volInfo', volInfo, 'mask', mask); +else + % negative threshold; invalid for p-maps + dat = iimg_threshold(inname, 'thr', [-Inf lowest_thresh(1)], 'k', szthresh, 'volInfo', volInfo, 'mask', mask); +end + +dat_extent = iimg_cluster_prune(dat, pruneseed, volInfo); +end + + +function dat_extent = get_extent_regions_noprune(inname, lowest_thresh, szthresh, volInfo, mask) + +if lowest_thresh >= 0 + dat = iimg_threshold(inname, 'thr', lowest_thresh, 'k', szthresh, 'volInfo', volInfo, 'mask', mask); +else + % negative threshold; invalid for p-maps + dat = iimg_threshold(inname, 'thr', [-Inf lowest_thresh(1)], 'k', szthresh, 'volInfo', volInfo, 'mask', mask); +end + +dat_extent = dat; +end diff --git a/Index_image_manip_tools/iimg_princomp.m b/Index_image_manip_tools/iimg_princomp.m new file mode 100644 index 00000000..11ea2bbd --- /dev/null +++ b/Index_image_manip_tools/iimg_princomp.m @@ -0,0 +1,56 @@ +function [volInfo,clusters] = iimg_princomp(maskname,image_names) +%[volInfo,clusters] = iimg_princomp(maskname,image_names) + +%% Get data and principal components +[dat,volInfo] = iimg_get_data(maskname,image_names); + +[eigvec, compscore, eigval] = princomp(dat,'econ'); +figure;plot(eigval,'ko-'); + +nc = input('how many comps to save? '); +clusters = cell(1,nc); + +volInfo.eigval = eigval; +volInfo.eigvec = eigvec(:,1:nc); +volInfo.score = compscore(:,1:nc); + +%% loop thru components +xbar = mean(dat); + +for i = 1:nc + k = i; + + %% flip component + comp = eigvec(:,k); cscore = compscore(:,k); + X = comp; X(:,end+1) = 1; + b = pinv(X) * xbar'; + if b(1) < 0, + comp = -comp; cscore = -cscore; + end + + volInfo.eigvec(:,k) = comp; + volInfo.score(:,k) = cscore; + + %% correlation with component + x = corrcoef([cscore dat]); + x = x(1,2:end)'; + + volInfo.corr_with_comps(:,k) = x; + + %% get high voxel loadings + sigvox = (abs(x) > .5) .* sign(x); + + avgregion = dat * sigvox; + voldata = iimg_reconstruct_3dvol(sigvox,volInfo); + cl = mask2clusters(voldata,volInfo.mat); + cluster_orthviews(cl,'bivalent'); + + clusters{k} = cl; + volInfo.component_names{k} = input('Name this component: ','s'); + volInfo.high_loadings(:,k) = sigvox; + volInfo.averages(:,k) = avgregion; +end +%% end loop + +return + diff --git a/Index_image_manip_tools/iimg_princomp_display.m b/Index_image_manip_tools/iimg_princomp_display.m new file mode 100644 index 00000000..8c12b492 --- /dev/null +++ b/Index_image_manip_tools/iimg_princomp_display.m @@ -0,0 +1,35 @@ +function cl = iimg_princomp_display(volInfo,k,overlay,dofigs) +% cl = iimg_princomp_display(volInfo,k,overlay,dofigs) +% Show output from iimg_princomp +% +% Example: +% cl2 = iimg_princomp_display(volInfo2,1,EXPT.overlay); + +%overlay = []; +%k = 1; + +tor_fig; plot(volInfo.eigval(:,k),'ko-','LineWidth',2); +title('Eigenvalues'); + +indx = double(volInfo.image_indx); +mycorrs = volInfo.corr_with_comps(:,k); +mycorrs(abs(mycorrs) < .5) = 0; +indx(volInfo.wh_inmask) = mycorrs; + +voldata = iimg_reconstruct_3dvol(indx,volInfo); +cl = mask2clusters(double(voldata),volInfo.mat); + +if dofigs +montage_clusters(overlay,cl,[2 2]); +%montage_clusters(overlay,cl,{'y'}); + +surfh = cluster_surf(cl,5,'heatmap'); +lightRestoreSingle(gca); axis off; set(gcf,'Color','w'); material dull +end + + +%% in progress: make scatterplot with behaviora +% tor_fig; r = prplot(beh,scale(X),1,1,{'yo'}); +% xlabel('Average activity in network (z-score)'); +% ylabel('Reappraisal success (reported)'); + diff --git a/Index_image_manip_tools/iimg_read_img.m b/Index_image_manip_tools/iimg_read_img.m new file mode 100644 index 00000000..0fd7465d --- /dev/null +++ b/Index_image_manip_tools/iimg_read_img.m @@ -0,0 +1,282 @@ +% [volInfo, dat] = iimg_read_img(inputimgs, [extended_output_flag (1|2)], [reading_data (0|1)], [first vol only (0|1)]); +% +% - reads 3-D or 4-D img/nii files, with extended volInfo output +% - flag to read first volume only +% - can read iimg_data vectorized form as well as file names (just passes dat to output) +% - uses SPM +% +% volInfo fields: +% - all fields returned by spm_vol, plus: +% .nvox - number of voxels in 3d image +% .image_indx - logical index (e.g., [0 1 1 0 1 ...]) of all nonzero voxels +% .wh_inmask (extended_output_flag > 0) - linear index (e.g., [2 3 5 ...]) - same as image_indx, but as a result of find +% .n_inmask (extended_output_flag > 0) - length of .wh_inmask +% .xyzlist (extended_output_flag > 0) - voxel coords of .wh_inmask voxels +% .cluster (extended_output_flag > 1) - cluster structure of in-mask voxels +% +% +% Extended output flag values: +% 1: Add xyz coord list and linear index of which voxels nonzero & non-nan in mask +% 2: Add clusters from spm_clusters +% +% Enforces that dat is a vector of data values, regardless of input. +% +% Example: +% imname = 'rob_tmap_0001_filt_t_3-05_k5_pos.img'; +% volInfo = iimg_read_img(imname); +% +% WARNING: +% The behavior of this function is SPM version dependent. +% volInfo only contains the spm_vol structure of the FIRST image!!! +% Data in SPM5/8 should be 4-D, but in SPM2 should be 3-D +% +% Example of image reading/writing +% ------------------------------------------------------------------------- +% % 1) Get volume info from first volume of a 3-D or 4-D image +% % volInfo structure has necessary info for converting to/from +% % "vectorized" format +% % dat returns 4-D data from entire image (all volumes) +% +% img_name = 'test_run1_pca.img'; +% [maskInfo, dat] = iimg_read_img(img_name, 2); +% +% % 2) Get data in "vectorized" image format for each volume in the +% % image. Works for a list of images too. data is in-mask voxels x volumes +% % this can be useful when you want to return whole-brain data for many +% % images, but in a search volume only (i.e., no extra-brain voxels) +% +% data = iimg_get_data(maskInfo, img_name); +% data = data'; % make sure columns are volumes +% +% % 3) Write out a 4-D image with the same data, called test_run1_pca2.img +% voldat3D = iimg_reconstruct_vols(data, maskInfo, 'outname', +% 'test_run1_pca2.img'); + +function [volInfo, dat] = iimg_read_img(inputimgs, extended_output_flag, reading_data, first_vol) + +% -------------------------------------- +% * Set up arguments +% -------------------------------------- +if ~exist('inputimgs', 'var') || isempty(inputimgs) + inputimgs = spm_get(Inf); +end +if ~exist('extended_output_flag', 'var') || isempty(extended_output_flag) + extended_output_flag = 0; +end +if ~exist('reading_data', 'var') || isempty(reading_data) + reading_data = 1; +end + +if ~exist('first_vol', 'var') || isempty(first_vol) + first_vol = 0; +end + +if(~reading_data && extended_output_flag) + warning('If not reading in file data, the extended_output_flag has no meaning'); +end + +imgtype = get_img_type(inputimgs); + +volInfo = []; +num_imgs = size(inputimgs, 1); +if first_vol, num_imgs = 1; end + +switch imgtype + case 'name' + % -------------------------------------- + % * String matrix of filenames + % -------------------------------------- + + space_defining_image = inputimgs(1, :); + if first_vol + whcomma = find(space_defining_image == ','); + if ~isempty(whcomma) + space_defining_image = space_defining_image(1:whcomma-1); + end + space_defining_image = [space_defining_image ',1']; + end + + [volInfo, dat] = iimg_read_from_file(space_defining_image, extended_output_flag, reading_data); + + if(reading_data && num_imgs > 1) + %% load other images; check to make sure they're in same dims as first + dat = [dat zeros(volInfo.nvox, num_imgs-1)]; + + for i = 2:num_imgs + [indx data] = iimg_read_from_file(inputimgs(i, :), 0, reading_data); + + if length(data) ~= volInfo.nvox + fprintf('Warning: img dims for %s do not match 1st image. Resampling.\n', inputimgs(i, :)); + + data = scn_map_image(inputimgs(i, :), inputimgs(1, :)); + data = data(:); + + end + + dat(:, i) = data; + end + end + + case '3dvols' + % -------------------------------------- + % * matrix of 3-D image data volumes + % -------------------------------------- + + [x, y, z, n] = size(inputimgs); + nvox = prod(x, y, z); + dat = zeros(nvox, n); + + for i = 1:num_imgs + tmp = inputimgs(:, :, :, i); + dat(:, i) = tmp(:); + end + + dat(isnan(dat)) = 0; + + case 'indx' + % -------------------------------------- + % * Already in index -- target format + % -------------------------------------- + dat = inputimgs; +end +end + + + + + +function imgtype = get_img_type(inputimgs) +if ischar(inputimgs) + imgtype = 'name'; + + return +end + +[x, y, z] = size(inputimgs); + +if isa(inputimgs, 'statistic_image') + imgtype = 'statistic_image'; +elseif z > 1 + % 3-D image(s) + imgtype = '3dvols'; +elseif x > 1 + % voxels x images 2-D index matrix + imgtype = 'indx'; +else + error('I don''t recognize the data format of inputimgs.'); +end +end + + + + +function [volInfo, dat] = iimg_read_from_file(imname, extended_output_flag, reading_data) + +imname = vol_file_check(imname); + +% % tor: oct 2010: speed up by adding ,1 to get only first vol in spm +% [pth, nam, ext] = fileparts(imname); +% wh_comma = find(ext == ','); +% if isempty(wh_comma) +% imname = [imname ',1']; +% end + +volInfo = spm_vol(imname); + +% Changed Feb 2008 to deal with 4-D image reading in SPM5 +nimgs = length(volInfo); + +for i = 1:nimgs + volInfo(i).nvox = prod(volInfo(i).dim(1:3)); +end + +dat = []; + +if(reading_data) + + switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + + case 'SPM8' + % spm_defaults is a function + spm('Defaults', 'fmri') + + otherwise + % unknown SPM + disp('Unknown version of SPM!'); + spm_defaults() + end + + + % pre-allocate array + dat = zeros(volInfo(1).nvox, nimgs); + + for i = 1:nimgs + dat_vol = spm_read_vols(volInfo(i)); + dat(:, i) = dat_vol(:); + + volInfo(i).image_indx = dat(:) ~= 0; % locate all voxels in-mask + end + + if extended_output_flag + % Return this for first image only right now. + volInfo(1).wh_inmask = find(volInfo(1).image_indx); % indices of all voxels in mask + + volInfo(1).n_inmask = size(volInfo(1).wh_inmask, 1); + + [i, j, k] = ind2sub(volInfo(1).dim(1:3), volInfo(1).wh_inmask); + volInfo(1).xyzlist = [i j k]; + + if extended_output_flag > 1 + if volInfo(1).n_inmask < 50000 + volInfo(1).cluster = spm_clusters(volInfo(1).xyzlist')'; + else + volInfo(1).cluster = ones(volInfo(1).n_inmask, 1); + end + end + end + + dat(isnan(dat)) = 0; +end + + +% Return only the first volInfo struture because it contains generic info +% about all volumes. Don't need to return every volume for our +% purposes. +volInfo = volInfo(1); + +end + +function imname = vol_file_check(imname) +if(~ischar(imname) || isempty(imname)) + error('Image name is not string or is empty.'); +end + +imname = deblank(imname); +[pth, nam, ext] = fileparts(imname); +wh_comma = find(ext == ','); +if(~isempty(wh_comma)) + wh_comma = wh_comma(1); + no_comma_ext = ext(1:(wh_comma-1)); + test_imname = fullfile(pth, [nam no_comma_ext]); +else + test_imname = imname; +end + +% make sure it exists +if(~exist(test_imname, 'file')) + which_imname = which(test_imname); % try to find on path + if(~exist(which_imname, 'file')) + error('Image "%s" does not exist or cannot be found on path!', test_imname); + else + imname = [which_imname ext(wh_comma:end)]; + end +end + +end diff --git a/Index_image_manip_tools/iimg_read_vols.m b/Index_image_manip_tools/iimg_read_vols.m new file mode 100644 index 00000000..536d798a --- /dev/null +++ b/Index_image_manip_tools/iimg_read_vols.m @@ -0,0 +1,30 @@ +% Drop-in replacement for spm_read_vols +% Primary difference is that if the images have the same voxel size but are NOT resliced +% (e.g., have differing affine matrices), this will handle reading each individually and +% putting them together. + +function [Y, XYZ] = iimg_read_vols(V, mask) + + if nargin<2, mask = 0; end + if nargin<1, error('insufficient arguments'); end + + if length(V)>1 && any(any(diff(cat(1, V.dim), 1, 1), 1)) + error('images don''t all have the same dimensions'); + end + if any(any(any(diff(cat(3, V.mat), 1, 3), 3))) + %error('images don''t all have same orientation & voxel size'); + for i = 1:length(V) + Y(:,:,:,i) = spm_read_vols(V(i), mask); + end + + if nargout > 1 + [R, C, P] = ndgrid(1:V(1).dim(1), 1:V(1).dim(2), 1:V(1).dim(3)); + RCP = [R(:)';C(:)';P(:)']; + clear R C P + RCP(4,:) = 1; + XYZ = V(1).mat(1:3,:)*RCP; + end + else + [Y, XYZ] = spm_read_vols(V, mask); + end +end \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_reconstruct_3dvol.m b/Index_image_manip_tools/iimg_reconstruct_3dvol.m new file mode 100644 index 00000000..0972bcbe --- /dev/null +++ b/Index_image_manip_tools/iimg_reconstruct_3dvol.m @@ -0,0 +1,153 @@ +% voldata = iimg_reconstruct_3dvol(dat, volInfo, [optional args]) +% Reconstruct a 3-D volume from a dat index list +% +% +% Optional inputs: if entered, will write .img file to disk +% 'outname', followed by output name +% 'descrip', followed by description for img file +% 'slice', followed by slice number of single-slice data in image +% +% THIS FUNCTION IS DEPRECATED. USE IIMG_RECONSTRUCT_VOLS.M' +% WHICH CAN DEAL WITH 4-D VOLUMES AS WELL + +function voldata = iimg_reconstruct_3dvol(dat, volInfo, varargin) + +disp('THIS FUNCTION IS DEPRECATED. USE IIMG_RECONSTRUCT_VOLS.M'); + +% Do not force spm scaling factors and get rid of private, which may +% contain information from other images/irrelevant info for this... +if isfield(volInfo, 'pinfo'), volInfo = rmfield(volInfo, 'pinfo'); end +if isfield(volInfo, 'private'), volInfo = rmfield(volInfo, 'private'); end + + is_writing_file = 0; + is_single_slice = 0; + slice_number = NaN; + descrip = 'Created by iimg_reconstruct_3dvol'; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch lower(varargin{i}) + case 'outname' + outname = varargin{i+1}; + is_writing_file = 1; + + case 'descrip' + descrip = varargin{i+1}; + + case 'slice' + is_single_slice = 1; + slice_number = varargin{i+1}; + end + end + end + + % check data and make sure it conforms to full-length index vector + % adjust if only in-mask voxels are in data + dat = check_dat(dat, volInfo, is_single_slice, slice_number); + + if is_single_slice + voldata = reshape(dat, volInfo.dim(1:2)); + else + voldata = reshape(dat, volInfo.dim(1:3)); + end + + if is_writing_file + if is_single_slice + write_file(voldata, volInfo, outname, descrip, slice_number); + else + write_file(voldata, volInfo, outname, descrip); + end + end + +end % End Main Function + + +% ------------------------------------------------------------------ +% * Check and make sure data is in full list; adjust if mask-only +% ------------------------------------------------------------------ +function dat = check_dat(dat, volInfo, is_single_slice, varargin) + % last arg is slice no., if applicable + + if ~isfield(volInfo, 'nvox') + error('volInfo.nvox and other fields not found. Use iimg_read_image.m to prepare volInfo.'); + end + + ndat = length(dat); + + if is_single_slice + % data should either equal prod(volInfo(1:2)) or fewer elements, in + % which case one slice will be reconstructed + nslice = prod(volInfo.dim(1:2)); + z = volInfo.xyzlist(:,3); + slice_number = varargin{1}; + + if ndat ~= nslice + % get indices in full volume of this slice + st = 1 + (slice_number - 1) .* nslice; % starting index of this slice + en = slice_number .* nslice; + + tmpdat = zeros(nslice, 1); % full volume, so we know how voxels map into 3-D array + tmpdat(volInfo.image_indx(st:en)) = dat; + dat = tmpdat; + end + + % if data already includes all voxels, do nothing, otherwise assume data is only in-mask, and reconstruct + elseif ndat ~= volInfo.nvox + if ~isfield(volInfo, 'n_inmask') + error('volInfo.n_inmask not found. Use iimg_read_img.m with extended output flag to prepare volInfo.'); + end + + if ndat ~= volInfo.n_inmask + error('volume info in struct does not seem to match number of voxels in index image.'); + else + tmpdat = zeros(volInfo.nvox, 1); + tmpdat(volInfo.image_indx) = dat; + dat = tmpdat; + end + end +end + +% ------------------------------------------------------------------ +% Write full image or slice, if last arg. entered +% ------------------------------------------------------------------ +function write_file(voldata, volInfo, outname, descrip, varargin) + [volInfo.fname, volInfo.n(1)] = make_img_filename(outname); + volInfo.descrip = descrip; + + if length(varargin) > 0 + slice_number = varargin{1}; + % SPM5 uses .private field in spm_write_plane...make sure we have + % correct .private loaded from file by re-mapping volume. + spm_write_plane(spm_vol(volInfo.fname), voldata, slice_number); + else + warning('off'); % empty images return many warnings + spm_write_vol(volInfo, voldata); + warning('on'); + end +end + +function [name, n] = make_img_filename(name) +[d, name, ext] = fileparts(name); +name(name == '.') = '_'; + +% changed nov 07 to preserve commas for multi-volume images. +% handle the comma to index volume: return as n and take it off the +% filename +t = find(ext==','); + +n = 1; +if ~isempty(t) + t = t(1); + n1 = ext((t+1):end); + if ~isempty(n1), + n = str2num(n1); + ext = ext(1:(t-1)); + end +end + +name = fullfile(d, [name ext]); + +if ~strcmp(name(end-3:end), '.img') && ~strcmp(name(end-3:end), '.nii') + name = [name '.img']; +end +end diff --git a/Index_image_manip_tools/iimg_reconstruct_vols.m b/Index_image_manip_tools/iimg_reconstruct_vols.m new file mode 100644 index 00000000..b79cf9dd --- /dev/null +++ b/Index_image_manip_tools/iimg_reconstruct_vols.m @@ -0,0 +1,261 @@ +% voldata = iimg_reconstruct_vols(dat, volInfo, [optional args]) +% Reconstruct a 3-D or 4-D volume from a "dat" matrix of vectorized images +% +% +% Optional inputs: if entered, will write .img file to disk +% 'outname', followed by output name +% 'descrip', followed by description for img file +% 'slice', followed by slice number of single-slice data in image +% +% Example of image reading +% ------------------------------------------------------------------------- +% % 1) Get volume info from first volume of a 3-D or 4-D image +% % volInfo structure has necessary info for converting to/from +% % "vectorized" format +% % dat returns 4-D data from entire image (all volumes) +% +% img_name = 'test_run1_pca.img'; +% [maskInfo, dat] = iimg_read_img(img_name, 2); +% +% % 2) Get data in "vectorized" image format for each volume in the +% % image. Works for a list of images too. data is in-mask voxels x volumes +% % this can be useful when you want to return whole-brain data for many +% % images, but in a search volume only (i.e., no extra-brain voxels) +% +% data = iimg_get_data(maskInfo, img_name); +% data = data'; % make sure columns are volumes +% +% % 3) Write out a 4-D image with the same data, called test_run1_pca2.img +% voldat3D = iimg_reconstruct_vols(data, maskInfo, 'outname', 'test_run1_pca2.img'); + +% NOTES: +% 2013/3/25: +% Luke[ea] added optional input to retain original datatype (default = float32) + +function voldata = iimg_reconstruct_vols(dat, volInfo, varargin) + is_writing_file = 0; + is_single_slice = 0; + keepdt = 0; + slice_number = NaN; + descrip = 'Created by iimg_reconstruct_vols'; + + % This stuff is now handled by scn_write_plane... + % Do not force spm scaling factors and get rid of private, which may + % contain information from other images/irrelevant info for this... + % % if isfield(volInfo, 'pinfo'), volInfo = rmfield(volInfo, 'pinfo'); end + % % if isfield(volInfo, 'private'), volInfo = rmfield(volInfo, 'private'); end + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch lower(varargin{i}) + case 'outname' + outname = varargin{i+1}; + is_writing_file = 1; + + case 'descrip' + descrip = varargin{i+1}; + + case 'slice' + is_single_slice = 1; + slice_number = varargin{i+1}; + + case 'keepdt' + keepdt = 1; + end + end + end + + % strip scaling factors from volInfo so that they will be + % recalculated in a way appropriate for this data. + % volInfo is intended to be dim info, but not scaling factor info. + + if isfield(volInfo, 'pinfo'), volInfo = rmfield(volInfo, 'pinfo'); end + + % check data and make sure it conforms to full-length index vector + % adjust if only in-mask voxels are in data + % added dtfloat as optional input. - added 3/25/13 luk[ea] + [dat volInfo] = check_dat(dat, volInfo, is_single_slice, keepdt, slice_number ); + + nimgs = size(dat, 2); % one column in vectorized format per 3D image + + % Create empty array + if is_single_slice + voldata = zeros([volInfo.dim(1:2) nimgs]); + else + voldata = zeros([volInfo.dim(1:3) nimgs]); + end + + % Fill with data + for i = 1:nimgs + if is_single_slice + voldata(:, :, i) = reshape(dat(:, i), volInfo.dim(1:2)); + else + voldata(:, :, :, i) = reshape(dat(:, i), volInfo.dim(1:3)); + end + end + + if is_writing_file + if is_single_slice + write_file(voldata, volInfo, outname, descrip, slice_number); + else + write_file(voldata, volInfo, outname, descrip); + end + end + +end % End Main Function + + +% ------------------------------------------------------------------ +% * Check and make sure data is in full list; adjust if mask-only; alter datatype of volInfo if needed +% ------------------------------------------------------------------ +function [dat volInfo] = check_dat(dat, volInfo, is_single_slice, keepdt, varargin) + % last arg is slice no., if applicable + + if ~isfield(volInfo, 'nvox') + error('volInfo.nvox and other fields not found. Use iimg_read_image.m to prepare volInfo.'); + end + + ndat = size(dat, 1); + + if is_single_slice + % data should either equal prod(volInfo(1:2)) or fewer elements, in + % which case one slice will be reconstructed + nslice = prod(volInfo.dim(1:2)); + %z = volInfo.xyzlist(:,3); + slice_number = varargin{1}; + + if ndat ~= nslice + % get indices in full volume of this slice + st = 1 + (slice_number - 1) .* nslice; % starting index of this slice + en = slice_number .* nslice; + + tmpdat = zeros(nslice, 1); % full volume, so we know how voxels map into 3-D array + tmpdat(volInfo.image_indx(st:en)) = dat; + dat = tmpdat; + end + + + elseif ndat ~= volInfo.nvox + % if data already includes all voxels, do nothing, otherwise assume data is only in-mask, and reconstruct + if ~isfield(volInfo, 'n_inmask') + error('volInfo.n_inmask not found. Use iimg_read_img.m with extended output flag to prepare volInfo.'); + end + + if ndat ~= volInfo.n_inmask + error('volume info in struct does not seem to match number of voxels in index image.'); + else + % we have in-mask data only and have to put it back in the right + % place in the original image + nimgs = size(dat, 2); % number of images + tmpdat = zeros(volInfo.nvox, nimgs); + for i = 1:nimgs + tmpdat(volInfo.image_indx, i) = dat(:, i); + end + dat = tmpdat; + end + end + + % alter data type if needed + switch(spm('Ver')) + case 'SPM2' + if spm_type(volInfo.dim(4), 'swapped') + disp('Swapped datatypes are supported by SPM2 but not SPM5. Un-swapping.'); + volInfo.dim(4) = volInfo.dim(4) ./ 256; + + if(spm_type(volInfo.dim(4), 'intt')) + volInfo.dim(4) = spm_type('float'); + end + end + + case {'SPM5', 'SPM8'} %added keepdt to retain original dt if requested : luk(ea) + %Outputs float32 by default otherwise keeps original data type + %if 'keepdt' is used as optional input. + if isfield(volInfo, 'dt') && spm_type(volInfo.dt(1), 'intt') && ~keepdt + volInfo.dt(1) = spm_type('float32'); + end + + otherwise + error('Unrecognized SPM type. Please update code or use SPM2/5/8!'); + end +end + +% ------------------------------------------------------------------ +% Write full image or slice, if last arg. entered +% ------------------------------------------------------------------ +function write_file(voldata, volInfo, outname, descrip, varargin) + + sz = size(voldata); + % get number of images for volume or slice data + if length(sz) > 3 + nimgs = sz(4); + elseif ~isempty(varargin) + % we have one slice for nimgs images, slices are concat. in 3rd dim + if length(sz) == 2 % we've passed in only a slice + nimgs = 1; + else + nimgs = sz(3); + end + else + nimgs = 1; + end + + if ~isempty(varargin) + % We have a slice, and use spm_write_plane.m + % write one slice for a series of images + slice_number = varargin{1}; + + filenames = []; + for i = 1:nimgs + filenames = char(filenames, make_img_filename(outname, i)); + end + filenames = filenames(2:end, :); + + % Note: pinfo is set to [1 0 0] for new images; no rescaling of + % images. + scn_write_plane(filenames, voldata, slice_number, volInfo); + + else + warning('off'); % empty images return many warnings + for i = 1:nimgs + [volInfo.fname, volInfo.n(1)] = make_img_filename(outname, i); + volInfo.descrip = descrip; + + % Note: done above: strip scaling factors from volInfo so that they will be + % recalculated in a way appropriate for this data. + % volInfo is intended to be dim info, but not scaling factor info. + + spm_write_vol(volInfo, squeeze(voldata(:, :, :, i))); + end + warning('on'); + end +end + + +function [name, n] = make_img_filename(name, imagenum) + [d, name, ext] = fileparts(name); + name(name == '.') = '_'; + + % If you pass in a filename with a ,### suffix, it will keep that suffix by + % using the volInfo.n field, which tells spm_write_vol which image to write + % to + % If you pass in a single name and are writing 4-D images, it will + % increment the image number counter. + % + % changed nov 07 to preserve commas for multi-volume images. + % handle the comma to index volume: return as n and take it off the + % filename + t = find(ext==','); + + if ~isempty(t) + t = t(1); + n1 = ext((t+1):end); + if ~isempty(n1), + n = str2num(n1); + ext = ext(1:(t-1)); + end + else + n = imagenum; + end + + name = fullfile(d, [name ext]); +end diff --git a/Index_image_manip_tools/iimg_reslice.m b/Index_image_manip_tools/iimg_reslice.m new file mode 100644 index 00000000..b707fc53 --- /dev/null +++ b/Index_image_manip_tools/iimg_reslice.m @@ -0,0 +1,94 @@ +%out = iimg_reslice(matchto, reslicethis, varargin) +% +%out = iimg_reslice(matchto, reslicethis, 'write', 'outname', 'myimg.img') +% +% tor wager +% nov. 06 + +function out = iimg_reslice(matchto, reslicethis, varargin) + % default flags + % interp = 2 is trilinear + flags = struct('interp', 2, 'vox', NaN, 'bb', NaN, 'wrap', [0 0 0], 'preserve', 0); + % spm options for interpolation and wrapping + d = [flags.interp*[1 1 1]' flags.wrap(:)]; + + write_vol = 0; + if any(strcmp(varargin, 'write')) + write_vol = 1; + end + + % get output image name + outname = 'resliced_image.img'; + argno = find(strcmp(varargin, 'outname')); + if argno + outname = varargin{argno + 1}; + end + + % get info of image to match to + volInfo = iimg_read_img(matchto); + + + % grid in space of target image + [x1, x2] = ndgrid(1:volInfo.dim(1), 1:volInfo.dim(2)); + + out = zeros(volInfo.dim(1:3)); + + for i = 1 % for each image + % get info for image to reslice + volInfo2 = spm_vol(reslicethis); + %[volInfo2, reslicethis] = iimg_read_img(reslicethis); + + if write_vol + VO = volInfo2; + VO.fname = outname; + + switch(spm('Ver')) + case 'SPM2' + VO.dim = [volInfo.dim(1:3) volInfo2.dim(4)]; + case {'SPM5', 'SPM8'} + VO.dt = volInfo.dt; + VO.private.dat.fname = outname; + + otherwise + error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + end + + VO.mat = volInfo.mat; + VO.descrip = 'iimg_reslice - resliced - trilinear'; + end + + + % final affine matrix + % mat is affine mtx of image + % Target\Object maps target to object + affinemat = inv(volInfo.mat\volInfo2.mat); + + + C = spm_bsplinc(volInfo2, d); + + % for each slice + + for x3 = 1:volInfo.dim(3) + [tmp, y1, y2, y3] = getmask(affinemat, x1, x2, x3, volInfo2.dim(1:3), flags.wrap); + out(:, :, x3) = spm_bsplins(C, y1, y2, y3, d); + end + + if write_vol + spm_write_vol(VO, out); + end + end +end + + + + +function [Mask, y1, y2, y3] = getmask(M, x1, x2, x3, dim, wrp) + tiny = 5e-2; % From spm_vol_utils.c + y1 = M(1, 1)*x1+M(1, 2)*x2+(M(1, 3)*x3+M(1, 4)); + y2 = M(2, 1)*x1+M(2, 2)*x2+(M(2, 3)*x3+M(2, 4)); + y3 = M(3, 1)*x1+M(3, 2)*x2+(M(3, 3)*x3+M(3, 4)); + Mask = logical(ones(size(y1))); + if ~wrp(1), Mask = Mask & (y1 >= (1-tiny) & y1 <= (dim(1)+tiny)); end; + if ~wrp(2), Mask = Mask & (y2 >= (1-tiny) & y2 <= (dim(2)+tiny)); end; + if ~wrp(3), Mask = Mask & (y3 >= (1-tiny) & y3 <= (dim(3)+tiny)); end; +end diff --git a/Index_image_manip_tools/iimg_smooth_3d.m b/Index_image_manip_tools/iimg_smooth_3d.m new file mode 100644 index 00000000..6b060317 --- /dev/null +++ b/Index_image_manip_tools/iimg_smooth_3d.m @@ -0,0 +1,60 @@ +function w = iimg_smooth_3d(w, volInfo, sfwhm, varargin) +% Smooth 3-D images stored in columns with FWHM in mm of sfwhm +% +% function w = smooth_3d(w, volInfo, sfwhm, [badvox]) +% NOTE: SMOOTHING KERNEL MAY BE IN VOX, AS VOL INFO IS NOT PASSED IN +% +% Take v x n matrix w, and smooth columns (images), returning v x n matrix +% again +% Optional: if v is smaller than the original image because some voxels +% were removed, enter logical vector badvox, and the missing voxels +% will be filled with zeros. +% +% volInfo is volume info structure from iimg_read_img.m + +% NOTE: 4-D version is horribly slow and memory intensive: +%wvol = iimg_reconstruct_vols(w', fmri_data_obj.mask.volInfo); + +badvox = 0; +if ~isempty(varargin), badvox = varargin{1}; end + +% transpose, and ... +% insert zeros back into bad vox +if any(badvox) + w = zeroinsert(badvox, w); +end + +n = size(w, 2); + +try + % reconstruct each image separately and smooth it, + % then convert back. + + for i = 1:n + fprintf('\b\b\b\b%04d', i); + + wvol = iimg_reconstruct_vols(w(:, i), volInfo); + + spm_smooth(wvol, wvol, [sfwhm sfwhm sfwhm]); + + % go back to vector + wvec = wvol(volInfo.wh_inmask); + + w(:, i) = wvec; + + end + +catch + disp('Error with volume reconstruction. Mask in fmri_data obj not properly defined?'); + disp('Stopped in debugger so you can check...'); + keyboard +end + +if any(badvox) + + w(badvox, :) = []; + +end + +end % function + diff --git a/Index_image_manip_tools/iimg_sphere_timeseries.m b/Index_image_manip_tools/iimg_sphere_timeseries.m new file mode 100644 index 00000000..6152135e --- /dev/null +++ b/Index_image_manip_tools/iimg_sphere_timeseries.m @@ -0,0 +1,21 @@ +% function [data, XYZvoxSphere, XYZmmSphere] = iimg_sphere_timeseries(images, XYZmm, radius) +% images - list of image files +% XYZmm - [3 x n] array of mm coords +% radius - radius in mm of sphere to generate +% +% data - voxel data +% XYZvoxSphere - voxel + +function [data, XYZvoxSphere, XYZmmSphere] = iimg_sphere_timeseries(images, XYZmmCenter, radius) + Vimages = spm_vol(images); + [XYZvox(1,:) XYZvox(2,:) XYZvox(3,:)] = ind2sub(Vimages(1).dim(1:3), 1:prod(Vimages(1).dim(1:3))); + XYZmm = Vimages(1).mat(1:3, :)*[XYZvox; ones(1, size(XYZvox, 2))]; + + dist = [XYZmm(1,:) - XYZmmCenter(1); XYZmm(2,:) - XYZmmCenter(2); XYZmm(3,:) - XYZmmCenter(3)]; + whVoxelsInSphere = find(sum(dist.^2) <= radius^2); + + XYZmmSphere = unique(XYZmm(:,whVoxelsInSphere)', 'rows')'; + XYZvoxSphere = unique(XYZvox(:,whVoxelsInSphere)', 'rows')'; + + data = spm_get_data(Vimages, XYZvoxSphere); +end \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_stouffer.m b/Index_image_manip_tools/iimg_stouffer.m new file mode 100644 index 00000000..201f70e8 --- /dev/null +++ b/Index_image_manip_tools/iimg_stouffer.m @@ -0,0 +1,104 @@ +function cl = iimg_stouffer(Pimg,maskname,thr,clsize,outname) +% function cl = iimg_stouffer(Pimg,maskname,thr,clsize,outname) +% by Tor Wager +% +% This function performs combination of p-values across images using the +% Stouffer method (see refs below). +% +% Inputs: +% Pimg string mtx of p-value image names +% k num voxels/num comparisons +% if empty, uses # of non-zero, non-NaN values in images +% +% empty Pimg prompts for graphic selection of filenames +% empty outname prompts for entry of output img file name +% +% Outputs: +% clusters, cl{1} is stouffer, cl{2} is image 1, cl{3} is image 2 +% +% Described in: +% Lazar, N. A., Luna, B., Sweeney, J. A., & Eddy, W. F. (2002). +% Combining brains: a survey of methods for statistical pooling +% of information. Neuroimage, 16(2), 538-550. +% +% Stouffer, S. A., Suchman, E. A., DeVinney, L. C., Star, S. A., and +% Williams, R. M. 1949. The American Soldier: Vol. I. Adjustment +% During Army Life. Princeton University Press, Princeton. +% +% + +% get file names and calcstr to evaluate +% ------------------------------------------------ + +if isempty(Pimg),Pimg = spm_get(Inf,'*.img','Select p-value images',pwd,0);,end + +if ~isempty(maskname) + [dat, volInfo] = iimg_mask(maskname,Pimg); +else + [volInfo,dat] = iimg_read_img(Pimg,1); + dat = dat(volInfo.wh_inmask,:); +end + +k = volInfo.n_inmask; +fprintf('\nSearch space is %3.0f voxels', k) + +[z,p,bonf_sig] = stouffer(dat,.05 ./ k); % returns bonferroni sig. + + +% individual images +nimgs = size(dat,2); +cl = cell(1,nimgs+1); + +for i = 1:nimgs + fprintf('\n\nImage %3.0f\n---------------------\n\n',i); + [bonfp, fdrp, uncp] = image_stats(dat(:,i), k, thr); + + % output clusters + psig = dat(:,i) <= uncp; + cl{i+1} = iimg_indx2clusters(psig,volInfo); +end + +fprintf('\n\nCombined p-values: Stouffer\n---------------------\n\n'); +[bonfp, fdrp, uncp] = image_stats(p, k, thr); + +% output clusters +psig = p <= uncp; +cl{1} = iimg_indx2clusters(psig,volInfo); + +% old way of getting clusters +%voldata = iimg_reconstruct_3dvol(psig,volInfo); +%cl = mask2clusters(voldata,volInfo.mat); + + + + +return + + + +function [bonfp, fdrp, uncp] = image_stats(p, k,uncp) + +fprintf('\nMinimum p-value: %3.6f\n', min(p)) + +bonfp = .05 ./ k; bonfz = norminv(1-bonfp); +nbonf = sum(p <= bonfp); + +fdrp = FDR(p,.05); +if isempty(fdrp), fdrp = 0; end +fdrz = norminv(1-fdrp); +nfdr = sum(p <= fdrp); + +uncz = norminv(1-uncp); +nunc = sum(p <= uncp); + +fprintf('\nSignificant voxels:\tMethod\tZ thresh\tp thresh\tNumber\n') + +fprintf('\tBonferroni\t%3.2f\t%3.6f\t%3.0f\n', bonfz, bonfp, nbonf) + +fprintf('\tFDR\t%3.2f\t%3.6f\t%3.0f\n', fdrz, fdrp, nfdr) + +fprintf('\tp < %3.4f\t%3.2f\t%3.6f\t%3.0f\n', uncp, uncz, uncp, nunc) + +fprintf('\n') + +return diff --git a/Index_image_manip_tools/iimg_threshold.m b/Index_image_manip_tools/iimg_threshold.m new file mode 100644 index 00000000..95a10e3b --- /dev/null +++ b/Index_image_manip_tools/iimg_threshold.m @@ -0,0 +1,301 @@ +% function [dat, volInfo, cl] = iimg_threshold(image_names, varargin) +% Thresholds images to be at least thresh(1) and at most thresh(2) +% +% Input types for image names: +% 1) String matrix of filenames +% 2) 4-D array of 3-D data volumes +% 3) voxels x images 2-D index array +% 4) image_vector object +% +% Outputs: +% dat, index vector of thresholded data +% volInfo, structure of info about volume +% cl, optional (slower) clusters structure from dat +% +% Command strings: +% 'imgtype', followed by 't', 'p', or 'data' (default) +% specify type of values in image +% +% 'threshtype', followed by 't' or 'p' +% +% 'df', followed by degrees of freedom, for p- and t-image threshold calc +% +% 'k', followed by extent threshold in voxels (slower) +% +% 'abs', absolute value must be > threshold. Use to get both pos. and neg. +% results in two-tailed test. +% +% 'intersect': Create intersection of images; uses abs, so + and - values +% count as yesses for intersection +% +% 'contrast', followed by contrasts across images +% +% 'volInfo': followed by volInfo structure. Necessary for extended output +% +% +% Authorship and Updates: +% * * +% Created by Tor Wager, edited by Matthew Davidson +% Update April 2007 by TW : correct FDR-thresholding bug +% +% +% +% Examples: +% Threshold an image set (p) to be at least zero +% ----------------------------------------------------------- +% image_names = +% /Users/tor/Documents/Tor_Documents/CurrentExperiments/Lab/Pre-appraisal/Results/rscalped_avg152T1_graymatter_smoothed.img +% /Users/tor/Documents/Tor_Documents/CurrentExperiments/Lab/Pre-appraisal/Results/t_intercept.img +% +% [dat, volInfo] = iimg_threshold(image_names, 'thr', 0); +% +% Do the same, but take the intersection and write an output image +% ----------------------------------------------------------- +% [dat, volInfo] = iimg_threshold(image_names, 'thr', 3, 'outnames', 'intersct', 'masked_t.img'); +% +% +% Threshold a t-image based on a p-value and a df +% ----------------------------------------------------------- +% image_names = '/Users/tor/Documents/Tor_Documents/CurrentExperiments/Lab/Pre-appraisal/Results/t_intercept.img' +% [dat, volInfo] = iimg_threshold(image_names, 'thr', .005, 'imgtype', 't', 'threshtype', 'p', 'df', 28, 'outnames', 'thresh_t.img'); +% +% cl = mask2clusters('masked_t.img'); +% cluster_orthviews(cl); +% +% The same, but threshold based on absolute value (+ and - values) +% [dat, volInfo] = iimg_threshold(image_names, 'thr', .005, 'abs', 'imgtype', 't', 'threshtype', 'p', 'df', 28, 'outnames', 'thresh_t.img'); +% +% % Threshold a p-value image directly +% ----------------------------------------------------------- +% [dat, volInfo] = iimg_threshold('X-M-Y_pvals.img', 'thr', [0 .05], 'outnames', 'X-M-Y_pvals_thresh.img'); +% [dat, volInfo, cl] = iimg_threshold(inname, 'thr', [0 .05], 'outnames', outname); +% +% Threshold a p-value image, with cluster sizes +% [dat, volInfo, cl] = iimg_threshold(inname, 'thr', [0 .05], 'k', 10); +% +% % Threshold and display a t-image using FDR, getting both positive and negative results +% ----------------------------------------------------------- +% [dat, volInfo, cl] = iimg_threshold('contrast_t.img', 'imgtype', 't', 'df', 37, 'thr', .2, 'threshtype', 'fdr', 'k', 3, 'abs'); +% cluster_orthviews(cl); +% spm_orthviews_hotcool_colormap(cat(2,cl.Z), 1.52); +% + +function [dat, volInfo, cl] = iimg_threshold(image_names, varargin) + %% -------------------------------------- + % * Set up arguments + % -------------------------------------- + + % defaults + % maskname = deblank(image_names(1, :)); + df = []; + thr = [0 Inf]; % must be > 0, < Inf + imgtype = 'data'; % could be data, t, p, or F + threshtype = 'none'; + mask = []; + k = 1; + dointersect = 0; + con = []; + outnames = []; + doabs = 0; % absolute value thresholding + extended_output_flag = 2; %*(nargout > 2); % yes if we've requested clusters to be created needed for extent also + + % inputs + for i = 1:length(varargin) + arg = varargin{i}; + if ischar(arg) + switch lower(arg) + case 'imgtype', imgtype = varargin{i+1}; + case 'threshtype', threshtype = varargin{i+1}; + case 'df', df = varargin{i+1}; + case {'thr', 'threshold'}, thr = varargin{i+1}; + case 'k', k = varargin{i+1}; + case 'intersect', dointersect = 1; + case 'mask', mask = varargin{i+1}; + case 'contrast', con = varargin{i+1}; + case 'outnames', outnames = varargin{i+1}; + case 'abs', doabs = 1; + case 'volinfo', volInfo = varargin{i+1}; + end + end + end + + % Make upper threshold infinity if not entered + if(length(thr) < 2) + switch threshtype + case 'p' + thr = [0 thr]; + case 'fdr' + % threshold should be a single p-value, which is converted + % to [0 fdr_thresh] by convert_threshold, below + % check and make sure p-value + if thr < eps || thr > 1 + error('Invalid threshold (thr) for FDR: must be p-value threshold.'); + end + + case 't' + thr = [thr Inf]; + + case 'none' + % we should have a data image + if ~strcmp(imgtype, 'data'), error('You must use imgtype ''data'' (default) if you do not enter a threshold type (''t'' or ''p'')'); end + + thr = [thr Inf]; + + otherwise + error('Unknown threshold type (''threshtype'').'); + end + end + + %% -------------------------------------- + % * Read image data + % -------------------------------------- + if isa(image_names, 'statistic_image') + tmpVolInfo = image_names.volInfo; + image_names = replace_empty(image_names); + dat = image_names.dat(:, 1); + else + [tmpVolInfo, dat] = iimg_read_img(image_names, extended_output_flag); + end + + if issparse(dat), dat = full(dat); end + clear image_names + + % use input volInfo if entered; this is so you can input an indexed image + % instead of a filename and get extended output (cluster sizes) + if ~exist('volInfo', 'var'), volInfo = tmpVolInfo; end + clear tmpVolInfo + + if ~isempty(mask) + dat = iimg_mask(mask,dat,volInfo); + end + + if dointersect + % If any of the input images has a zero (the designated "no value") for a voxel, then clear it out for all images + wh = any(dat==0,2); + dat(wh,:) = 0; + end + + % -------------------------------------- + % * Convert and apply threshold + % -------------------------------------- + thr = convert_threshold(imgtype, threshtype, thr, df, dat); + + if doabs + whzero = abs(dat) < thr(1) | abs(dat) > thr(2); % remove these values + else + whzero = dat < thr(1) | dat > thr(2); + end + dat(whzero) = 0; + + %% -------------------------------------- + % * Apply size threshold + % -------------------------------------- + [dat, nvox] = iimg_cluster_extent(dat, volInfo, k); + + + %% -------------------------------------- + % special operations + % -------------------------------------- + if ~isempty(con) + % Apply contrast(s) across images + dat = apply_contrast(dat, con); + end + + %% -------------------------------------- + % * Write out image names, if asked + % -------------------------------------- + if ~isempty(outnames) + iimg_write_images(dat, volInfo, outnames); + end + + %% -------------------------------------- + % * clusters output + % -------------------------------------- + if extended_output_flag + + % could enter u and k here, but don't need to because it's already + % thresholded; also, neg. elements will be removed in some cases if + % threshold is re-applied here. + + %cl = iimg_indx2clusters(dat, volInfo, thr, k); + cl = iimg_indx2clusters(dat, volInfo); + end +end + + + + +% Sub-functions + +function thr = convert_threshold(imgtype, threshtype, thr, df, dat) + % handle FDR + switch imgtype + case 'data' + if(strcmp(threshtype, 'fdr')) + error('FDR thresholding has no meaning for data images. Try using a t- or a p-image'); + end + case 't' + switch threshtype + case 't' + case 'p' + % convert p-value threshold to t-value threshold + if ~exist('df', 'var') || isempty(df) + error('You must enter df to use p-value based thresholding with a t img.'); + end + thr(~isinf(thr)) = tinv(1-thr(~isinf(thr)), df); + case 'fdr' + % convert t-values in image to p-values for FDR + % thresholding + if ~exist('df', 'var') || isempty(df) + error('You must enter df to use p-value based thresholding with a t img.'); + end + pdat = 1 - tcdf(dat(dat ~= 0 & ~isnan(dat)), df); % zero is a special invalid value + + fdr_threshold = FDR(pdat, thr); % get p-value threshold + if isempty(fdr_threshold), fdr_threshold = 0; end + + fprintf('Computed FDR threshold. For p < %.3f corrected, p-threshold is %.7f, ', thr, fdr_threshold); + thr = [tinv(1-fdr_threshold, df) Inf]; + + fprintf('t-threshold is %.2f\n', thr(1)); + + otherwise + error('Threshold type must be t or p values for t-images') + end + + case 'p' + switch threshtype + case 't' + if ~exist('df', 'var') || isempty(df) + error('You must enter df to use t-value based thresholding with a p img.'); + end + thr = 1 - tcdf(thr, df); + case 'p' % do nothing + case 'fdr' + + dat = dat(dat ~= 0 & ~isnan(dat)); % zero is a special invalid value + + fdr_threshold = FDR(dat, thr); + if isempty(fdr_threshold), fdr_threshold = 0; end + + %thr(isinf(thr)) = fdr_threshold; + fprintf('Computed FDR threshold. For p < %.3f corrected, p-threshold is %.7f, ', thr, fdr_threshold); + + thr = [0 fdr_threshold]; % from zero to new thresh + otherwise + error('Threshold type must be t or p values for p-images') + end + end +end + + +function condat = apply_contrast(dat, con) + if size(con, 2) ~= size(dat, 2) + error('Contrast matrix must have as many columns as images.'); + end + + condat(:, i) = dat * con'; +end + + + diff --git a/Index_image_manip_tools/iimg_weighted_ttest.m b/Index_image_manip_tools/iimg_weighted_ttest.m new file mode 100644 index 00000000..811deb04 --- /dev/null +++ b/Index_image_manip_tools/iimg_weighted_ttest.m @@ -0,0 +1,84 @@ +function iimg_weighted_ttest(image_names,varargin) +% +% fast weighted test +% +% Tor Wager +% +% Examples: +% +% iimg_weighted_ttest(image_names,'mask',maskname) + + +% -------------------------------------- +% * Set up arguments +% -------------------------------------- + +% defaults +maskname = deblank(image_names(1,:)); +w = []; varY = []; bcon = []; + +% inputs +for i = 1:length(varargin) + arg = varargin{i}; + if ischar(arg) + switch lower(arg) + case 'mask', maskname = varargin{i+1}; + case 'w', w = varargin{i+1}; + case 'btwn', bcon = contrast_code(varargin{i+1}); + case 'varY', varY = varargin{i+1}; + end + end +end + + +% -------------------------------------- +% * Load data +% -------------------------------------- + +str = display_string('Reading data.'); + +[dat,maskInfo] = iimg_get_data(maskname,image_names); + +erase_string(str); + + +% -------------------------------------- +% * Computation +% -------------------------------------- + +str = display_string('Statistics.'); + +[means,stats] = weighted_reg(dat,'w',w,'varY',varY,'btwn',bcon,'uni'); + +erase_string(str); + + +% -------------------------------------- +% * Write output +% -------------------------------------- +descrip = sprintf('dfe = %3.2f',stats.dfe); +meanvol = iimg_reconstruct_3dvol(means.Ymean',maskInfo, 'outname','mean.img', 'descrip',['mean ' descrip]); +tvol = iimg_reconstruct_3dvol(stats.t',maskInfo, 'outname','t_intercept.img', 'descrip',['t-image of intercept ' descrip]); +pvol = iimg_reconstruct_3dvol(stats.p',maskInfo, 'outname','p_intercept.img', 'descrip',['p-image of intercept ' descrip]); + + +return + + + + + +function str = display_string(str) +str = sprintf(str); fprintf(1,'%s',str); +return + + +function erase_string(str) + +len = length(str); +str2 = repmat('\b',1,len); + +fprintf(1,str2); + +return + diff --git a/Index_image_manip_tools/iimg_write_images.m b/Index_image_manip_tools/iimg_write_images.m new file mode 100644 index 00000000..3170df7e --- /dev/null +++ b/Index_image_manip_tools/iimg_write_images.m @@ -0,0 +1,42 @@ +% iimg_write_images(dat,volInfo,outnames) +% +% Write a series of Analyze images given: +% dat, voxels x images matrix of image data in index format +% volInfo, spm-style info structure; see iimg_read_img +% outnames, a string matrix (or cell array) of output filenames, or the name of a 4-D file to create + +function iimg_write_images(dat, volInfo, outnames) + + if isempty(outnames) + error('You must specify a string array of output names, or a single 4-D image file name'); + end + + if iscell(outnames) + outnames = char(outnames{:}); + end + + n = size(dat,2); + if size(outnames,1) ~= n + error('Number of images (columns of dat) must == number of output img names.'); + end + + if volInfo.nvox ~= size(dat,1) + error('Dims of dat (vox x images) and volInfo do not match.'); + end + + is4d = 0; + if size(dat, 2) > 1 && size(outnames, 1) == 1, is4d = 1; end + + if is4d + iimg_reconstruct_vols(dat,volInfo,'outname',deblank(outnames)); + + else + for i = 1:n + %iimg_reconstruct_3dvol(dat(:,i),volInfo,'outname',deblank(outnames(i,:))); + % This handles volume creation, etc. It also works for a 4-D + % image (but all volumes must be created at once) + iimg_reconstruct_vols(dat(:,i),volInfo,'outname',deblank(outnames(i,:))); + end + end + +end \ No newline at end of file diff --git a/Index_image_manip_tools/iimg_xyz2indx.m b/Index_image_manip_tools/iimg_xyz2indx.m new file mode 100644 index 00000000..627e453d --- /dev/null +++ b/Index_image_manip_tools/iimg_xyz2indx.m @@ -0,0 +1,12 @@ +% [indx, wh] = iimg_xyz2indx(xyz, input type:['mm' or 'vox'], [V: needed if 'mm']) + +function [indx, wh] = iimg_xyz2indx(xyz, xyztype, V) + if nargin > 1 && strcmp(xyztype, 'mm') + xyz = mm2voxel(xyz, V); + end + + indx = zeros(V.nvox, 1); + wh = sub2ind(V.dim(1:3), xyz(:, 1), xyz(:, 2), xyz(:, 3)); + indx(wh) = 1; +end + diff --git a/Index_image_manip_tools/iimg_xyz2spheres.m b/Index_image_manip_tools/iimg_xyz2spheres.m new file mode 100644 index 00000000..32c826ab --- /dev/null +++ b/Index_image_manip_tools/iimg_xyz2spheres.m @@ -0,0 +1,107 @@ +function indx = iimg_xyz2spheres(xyz, mask_xyzlist, r) + % indx = iimg_xyz2spheres(xyz, mask_xyzlist, r) + % + % + % mask_xyzlist is a list of voxel coords of all in-mask voxels + % xyz is a list of voxels to be convolved with spheres + % r is sphere radius (voxels) +% +% more generally, finds mask_xyzlist entries that are within r units of xyz +% i.e., to find database points within r mm of a cluster xyz list: +% indx = iimg_xyz2spheres(clusterxyz,databasexyz,r) +% +% indx is length mask_xyzlist and contains all in-mask, in-sphere voxels +% for all xyz coordinates. + +% See programmers' notes testing this function: Tested OK, 7/28/13, tor + + num_mask_voxels = size(mask_xyzlist,1); + indx = zeros(num_mask_voxels,1); + + if isempty(xyz), return; end + + wh_orig = (1:num_mask_voxels)'; % original voxel index + + + % eliminate mask_xyzlist points that are not even close (outside box) + cmax = max(xyz,[],1); + cmin = min(xyz,[],1); + d = [(mask_xyzlist - repmat(cmax,num_mask_voxels,1)) (repmat(cmin,num_mask_voxels,1) - mask_xyzlist)]; + wh = any(d > r,2); + + mask_xyzlist(wh,:) = []; + wh_orig(wh) = []; + + r2 = r.^2; + + % for each voxel, find mask_xyzlist coords within r + % add ones to indx + for i = 1:size(xyz,1) + sphere_center = xyz(i,:); + xyz_candidates = mask_xyzlist; + wh_candidates = wh_orig; + + % find list xyz in box around coord + wh = abs(xyz_candidates(:,1) - sphere_center(1)) > r; + xyz_candidates(wh,:) = []; + wh_candidates(wh) = []; + + wh = abs(xyz_candidates(:,2) - sphere_center(2)) > r; + xyz_candidates(wh,:) = []; + wh_candidates(wh) = []; + + wh = abs(xyz_candidates(:,3) - sphere_center(3)) > r; + xyz_candidates(wh,:) = []; + wh_candidates(wh) = []; + + num_candidates = size(xyz_candidates,1); + d = sum((repmat(sphere_center,num_candidates,1) - xyz_candidates ).^2, 2); + + wh = wh_candidates(d <= r2); + + indx(wh) = 1; + end +end + + +% Programmers' notes: Test + +% img = which('spm2_hipp.img') +% dat = fmri_data(img); +% dat = remove_empty(dat); +% +% % Target +% myxyz1 = dat.volInfo.xyzlist(~dat.removed_voxels , :); +% XYZc = voxel2mm(myxyz1', dat.volInfo.mat)'; +% +% r = region(dat); +% cluster_orthviews(r, {[1 0 0]}, 'solid'); +% +% dat2 = preprocess(dat, 'smooth', 4); +% dat2 = threshold(dat2, [.1 Inf], 'raw-between'); +% myxyz = dat.volInfo.xyzlist(~dat2.removed_voxels , :); +% myxyz = voxel2mm(myxyz', dat2.volInfo.mat)'; +% +% indx2 = iimg_xyz2spheres(XYZc, myxyz, mydist); +% dat2.dat(~indx2) = 0; +% dat2 = remove_empty(dat2); +% +% r2 = region(dat2); +% cluster_orthviews(r2, {[0 1 0]}, 'add'); +% +% %% +% mydist = 2; +% +% dat2 = preprocess(dat, 'smooth', 4); +% dat2 = threshold(dat2, [.1 Inf], 'raw-between'); +% myxyz = dat.volInfo.xyzlist(~dat2.removed_voxels , :); +% myxyz = voxel2mm(myxyz', dat2.volInfo.mat)'; +% +% indx2 = iimg_xyz2spheres(XYZc, myxyz, mydist); +% dat2.dat(~indx2) = 0; +% dat2 = remove_empty(dat2); +% +% r2 = region(dat2); +% cluster_orthviews(r, {[1 0 0]}, 'solid'); +% cluster_orthviews(r2, {[0 0 1]}, 'add'); + diff --git a/Index_image_manip_tools/nans2zeros.m b/Index_image_manip_tools/nans2zeros.m new file mode 100644 index 00000000..d3815075 --- /dev/null +++ b/Index_image_manip_tools/nans2zeros.m @@ -0,0 +1,21 @@ +function nans2zeros(imgs) + if(iscellstr(imgs)) + imgs = char(imgs); + end + fprintf('Loading meta-info...\n'); + Vimgs = spm_vol(imgs); + + fprintf('Zeroing... '); + for i=1:length(Vimgs) + status_string = sprintf('%d/%d', i, length(Vimgs)); + fprintf(status_string); + + data = spm_read_vols(Vimgs(i)); + data(isnan(data)) = 0; + spm_write_vol(Vimgs(i), data); + + erase_string(status_string); + end + + fprintf('\nFinished converting %d images.\n', length(Vimgs)); +end \ No newline at end of file diff --git a/Misc_utilities/CERTreader.m b/Misc_utilities/CERTreader.m new file mode 100644 index 00000000..51c888f5 --- /dev/null +++ b/Misc_utilities/CERTreader.m @@ -0,0 +1,18 @@ +% given an output file from CERT, returns a struct with the data +% +% input: +% filename +% +% output: +% struct with the data + +function AUs = CERTreader(fname) + +r=robustcsvread(fname, 'rows_to_skip', 1, 'delim', '\t'); + +fs = fields(r); +for i=1:length(fs) + r.(fs{i}) = str2num(char(r.(fs{i}))); +end + +AUs=r; \ No newline at end of file diff --git a/Misc_utilities/append.m b/Misc_utilities/append.m new file mode 100644 index 00000000..3cc51d19 --- /dev/null +++ b/Misc_utilities/append.m @@ -0,0 +1,25 @@ +% APPEND Appends second parameter to the end of the first. If field is set, +% writes into the field instead of directly into the variable. +% +% a = append(a, data, [field]) +% +% This function solely exists because Matlab has no ability to create +% empty objects (e.g. with one dimension being 0) and thus, trying to append +% to an empty array results in type cast errors, since empty matrices are +% double by default. + +function a = append(a, b, field) + if(exist('field', 'var')) + if(isempty(a)) + a(1).(field) = b; + else + a(end+1).(field) = b; + end + else + if(isempty(a)) + a = b; + else + a(end+1) = b; + end + end +end \ No newline at end of file diff --git a/Misc_utilities/blank_struct.m b/Misc_utilities/blank_struct.m new file mode 100755 index 00000000..84efd396 --- /dev/null +++ b/Misc_utilities/blank_struct.m @@ -0,0 +1,23 @@ +%--------------------------------------------------------------------- +% Utility function +% 2/15/10 Joe Wielgosz +% +% Create a blank structure, using an existing one as a template. +% +% Outputs: +% C: structure containing fields of A , with values set to empty array +% +% Inputs: +% A: template structure +% blank_val: all fields will be set to this value, e.g. [] or '' +% +% Only works for single-element non-nested structures, for now +function C = blank_struct(A, blank_val) +% TODO: if B is array then loop.. +fields = fieldnames(A); +C = struct; +for i = 1:length(fields) + f = fields{i}; + C = setfield(C, f, blank_val); + +end \ No newline at end of file diff --git a/Misc_utilities/checkMatlabVersion.m b/Misc_utilities/checkMatlabVersion.m new file mode 100644 index 00000000..0bcd8f7c --- /dev/null +++ b/Misc_utilities/checkMatlabVersion.m @@ -0,0 +1,24 @@ +function ok = checkMatlabVersion(datestr) + % ok = checkMatlabVersion(datestr) + % + % % check Matlab version against release date of version needed (or + % recommended) + % datestr format is one of the forms taken by datenum. + % + % Example: check for Matlab release date on or after Aug. 3, 2006 + % ok = checkMatlabVersion('8/3/2006') + % + % tor wager, 12/10/2006 + + ok = 1; + +% check Matlab version + datethiscopy = datenum(version('-date')); + dateneeded = datenum('8/3/2006'); % R2006a + if datethiscopy < dateneeded + disp('Warning: this version of Matlab is older than the recommended version.'); + disp('This function may not work correctly'); + ok = 0; + end + +end diff --git a/Misc_utilities/circle.m b/Misc_utilities/circle.m new file mode 100644 index 00000000..9923a904 --- /dev/null +++ b/Misc_utilities/circle.m @@ -0,0 +1,30 @@ +function [h, fillh] = circle(center, radius, varargin) + % draws a circle + %[h, fillh] = circle(center, radius, ['fill'], [color string or numbers]) + % + % 'fill' requires the geom2d toolbox, by David Legland + + + x = pi*[0:.5:2]; + y = [0 1 0 -1 0 1 0; + 1 0 1 0 -1 0 1] * radius; + pp = spline(x, y); + circ_data = ppval(pp, linspace(0, 2*pi, 101)); + h = plot(circ_data(1,:) + center(1), circ_data(2,:) + center(2)); + %axis equal + + if length(varargin) > 0 && strcmp(varargin{1}, 'fill') + + xy = circleAsPolygon([center radius], 20); + fillh = fillPolygon(xy); + + delete(h) + h = []; + + if length(varargin) > 1 + set(fillh, 'FaceColor', varargin{2}) + end + + end + +end \ No newline at end of file diff --git a/Misc_utilities/combine_structs.m b/Misc_utilities/combine_structs.m new file mode 100755 index 00000000..c7b478d8 --- /dev/null +++ b/Misc_utilities/combine_structs.m @@ -0,0 +1,31 @@ +%--------------------------------------------------------------------- +% Utility function +% 2/15/10 Joe Wielgosz +% +% Combine the fields of two structures. +% +% Outputs: +% C: structure containing fields of both A and B +% +% Inputs: +% A, B: structs to be combined +% prefix: string applied to fields of structure B +% +% Only works for single-element non-nested structures, for now +function C = combine_structs(A, B, prefix) +C = A; + +% TODO: if B is array then loop.. +fields = fieldnames(B); +for i = 1:length(fields) + if exist('prefix', 'var') + f = [prefix fields{i}]; + else + f = fields{i}; + end + val = getfield(B, fields{i}); + % TODO: check for conflicts... + % TODO: if substruct then recursively combine subfields.. + C = setfield(C, f, val); + +end \ No newline at end of file diff --git a/Misc_utilities/condf2indic.m b/Misc_utilities/condf2indic.m new file mode 100644 index 00000000..1ccf743a --- /dev/null +++ b/Misc_utilities/condf2indic.m @@ -0,0 +1,21 @@ +% function [indic, xlevels] = condf2indic(X) +% +% Create N x k indicator matrix of ones/zeros from N x 1 list of numeric values (X) +% +% indic is returned as single precision type, because it can then be used +% as a design matrix in a GLM +% xlevels are the values of X corresponding to columns of indic +% +% tor wager, nov 2007 + +function [indic, xlevels] = condf2indic(X) + + [xlevels, ind1, indx] = unique(X); + + indic = single(false(length(indx), length(xlevels))); + + for i = 1:length(xlevels) + indic(indx == i, i) = 1; + end + +end \ No newline at end of file diff --git a/Misc_utilities/depfun_aggregate.m b/Misc_utilities/depfun_aggregate.m new file mode 100644 index 00000000..9a7bb98f --- /dev/null +++ b/Misc_utilities/depfun_aggregate.m @@ -0,0 +1,37 @@ +% dependencies = depfun_aggregate(funname, excludes, save_dir) +% depfun_aggregate is for packaging all the files needed for a function in one spot +% +% INPUTS +% funname - function to analyze +% excludes - cellstrs to exclude from the matches +% save_dir - directory to copy files to - defaults to 'tmp_files' +% +% E.g.: +% dependencies = depfun_aggregate('whole_brain_fir', {'spm2', 'spm5'}, 'fir_files') + +function dependencies = depfun_aggregate(funname, excludes, save_dir) + + dependencies = depfun(funname, '-quiet'); + dependencies = remove_str_from_cell(dependencies, '/MATLAB'); + + if(~isempty(excludes)) + excludes = cellstr(excludes); + for i=1:length(excludes) + dependencies = remove_str_from_cell(dependencies, excludes{i}); + end + end + + if(~exist('save_dir', 'var') || isempty(save_dir)) + save_dir = 'tmp_files'; + end + mkdir(save_dir); + fprintf(1, 'Saving to %s\n', save_dir); + + %NB: copyfile() should be used for platform-independence, but + %its handling permissions incorrectly on my machine - Matthew!rm + for i = 1:length(dependencies) + system(['cp ' dependencies{i} ' ' save_dir '/']); +% copyfile(dependencies{i}, 'tmp_files/'); + fprintf(1, 'Copied %s to %s.\n', dependencies{i}, save_dir); + end +end \ No newline at end of file diff --git a/Misc_utilities/distance.m b/Misc_utilities/distance.m new file mode 100755 index 00000000..45ad9b20 --- /dev/null +++ b/Misc_utilities/distance.m @@ -0,0 +1,18 @@ +function d = distance(u, v) +% d = distance(u, v) +% Euclidean distance between u and v +% u and v should be column vectors or matrices in k-dim space, where k is +% the number of columns of u and v. +% +% if u is a single row, replicates u to size(v,1) + +if size(u,1) < size(v,1) + u = repmat(u,size(v,1),1); + if size(u,1) < size(v,1), error('Inputs must have same number of rows, or one row for 1st input!'),end +end + +delt = u - v; +d = (sum(delt .^ 2,2)) .^.5; + +return + diff --git a/Misc_utilities/distance_euclid.m b/Misc_utilities/distance_euclid.m new file mode 100644 index 00000000..79f7b30d --- /dev/null +++ b/Misc_utilities/distance_euclid.m @@ -0,0 +1,18 @@ +function d = distance_euclid(u, v) +% d = distance_euclid(u, v) +% Euclidean distance between u and v +% u and v should be column vectors or matrices in k-dim space, where k is +% the number of columns of u and v. +% +% if u is a single row, replicates u to size(v,1) + +if size(u,1) < size(v,1) + u = repmat(u,size(v,1),1); + if size(u,1) < size(v,1), error('Inputs must have same number of rows, or one row for 1st input!'),end +end + +delt = u - v; +d = (sum(delt .^ 2,2)) .^.5; + +return + diff --git a/Misc_utilities/documentation_template.m b/Misc_utilities/documentation_template.m new file mode 100644 index 00000000..5cd1e507 --- /dev/null +++ b/Misc_utilities/documentation_template.m @@ -0,0 +1,167 @@ +% Standard text for function documentation +% +% First line: One-line summary description of function +% +% Usage: +% ------------------------------------------------------------------------- +% [list outputs here] = function_name(list inputs here, [optional inputs]) +% +% For objects: Type methods(object_name) for a list of special commands +% Type help object_name.method_name for help on specific +% methods. +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% xxx xxx +% +% Outputs: +% ------------------------------------------------------------------------- +% xxx xxx +% +% Examples: +% ------------------------------------------------------------------------- +% +% give examples here +% +% See also: +% * list other functions related to this one, and alternatives* + +% Programmers' notes: +% List dates and changes here, and author of changes + +% BELOW IS A STANDARD TEMPLATE FOR DEFINING VARIABLE (OPTIONAL) INPUT +% ARGUMENTS. MANY FUNCTIONS NEED TO PARSE OPTIONAL ARGS, SO THIS MAY BE +% USEFUL. + + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% Defaults +% ----------------------------------- +% initalize optional variables to default values here. +rowsz = []; +doplot = 0; +basistype = 'spm+disp'; + + +% optional inputs with default values +% ----------------------------------- + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'rows', rowsz = varargin{i+1}; varargin{i+1} = []; + case 'plot', doplot = 1; + case 'basistype', basistype = varargin{i+1}; varargin{i+1} = []; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +% THIS IS ANOTHER WAY THAT IS MORE STREAMLINED, BUT ACTUALLY LESS INTUITIVE + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% Defaults +% ----------------------------------- +% Set color maps for + / - values +poscm = colormap_tor([1 0 .5], [1 1 0], [.9 .6 .1]); %reddish-purple to orange to yellow +negcm = colormap_tor([0 0 1], [0 1 1], [.5 0 1]); % cyan to purple to dark blue + +% optional inputs with default values +% ----------------------------------- +% - allowable_args is a cell array of argument names +% - avoid spaces, special characters, and names of existing functions +% - variables will be assigned based on these names +% i.e., if you use an arg named 'cl', a variable called cl will be +% created in the workspace + +allowable_args = {'cl', 'ycut_mm', 'pos_colormap', 'neg_colormap', ... + 'surface_handles', 'existingfig'}; + +default_values = {[], [], poscm, negcm, ... + [], 0}; + +% define actions for each input +% ----------------------------------- +% - cell array with one cell for each allowable argument +% - these have special meanings in the code below +% - allowable actions for inputs in the code below are: 'assign_next_input' or 'flag_on' + +actions = {'assign_next_input', 'assign_next_input', 'assign_next_input', 'assign_next_input', ... + 'assign_next_input', 'flag_on'}; + +% logical vector and indices of which inputs are text +textargs = cellfun(@ischar, varargin); +whtextargs = find(textargs); + +for i = 1:length(allowable_args) + + % assign default + % ------------------------------------------------------------------------- + + eval([allowable_args{i} ' = default_values{i};']); + + wh = strcmp(allowable_args{i}, varargin(textargs)); + + if any(wh) + % Optional argument has been entered + % ------------------------------------------------------------------------- + + wh = whtextargs(wh); + if length(wh) > 1, warning(['input ' allowable_args{i} ' is duplicated.']); end + + switch actions{i} + case 'assign_next_input' + eval([allowable_args{i} ' = varargin{wh(1) + 1};']); + + case 'flag_on' + eval([allowable_args{i} ' = 1;']); + + otherwise + error(['Coding bug: Illegal action for argument ' allowable_args{i}]) + end + + end % argument is input +end + +% END DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + + +% Below are some default headers + +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- +% +% Sub-functions +% +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- + + + diff --git a/Misc_utilities/erase_and_display.m b/Misc_utilities/erase_and_display.m new file mode 100644 index 00000000..e0ba2a71 --- /dev/null +++ b/Misc_utilities/erase_and_display.m @@ -0,0 +1,6 @@ +% function erase_and_display(old_str, new_str) +% Backspaces the number of chars of the length of old_str, then prints new_str + +function erase_and_display(old_str, new_str) + fprintf([repmat('\b', 1, length(old_str)) new_str]); +end \ No newline at end of file diff --git a/Misc_utilities/erase_string.m b/Misc_utilities/erase_string.m new file mode 100644 index 00000000..665117bf --- /dev/null +++ b/Misc_utilities/erase_string.m @@ -0,0 +1,6 @@ +% function erase_string(str) +% Backspaces the number of chars of the length of str + +function erase_string(str) + fprintf(1, repmat('\b', 1, length(str))); +end \ No newline at end of file diff --git a/Misc_utilities/explode.m b/Misc_utilities/explode.m new file mode 100644 index 00000000..b21487b1 --- /dev/null +++ b/Misc_utilities/explode.m @@ -0,0 +1,58 @@ +function [split,numpieces]=explode(string,delimiters) +%EXPLODE Splits string into pieces. +% EXPLODE(STRING,DELIMITERS) returns a cell array with the pieces +% of STRING found between any of the characters in DELIMITERS. +% +% [SPLIT,NUMPIECES] = EXPLODE(STRING,DELIMITERS) also returns the +% number of pieces found in STRING. +% +% Input arguments: +% STRING - the string to split (string) +% DELIMITERS - the delimiter characters (string) +% Output arguments: +% SPLIT - the split string (cell array), each cell is a piece +% NUMPIECES - the number of pieces found (integer) +% +% Example: +% STRING = 'ab_c,d,e fgh' +% DELIMITERS = '_,' +% [SPLIT,NUMPIECES] = EXPLODE(STRING,DELIMITERS) +% SPLIT = 'ab' 'c' 'd' 'e fgh' +% NUMPIECES = 4 +% +% See also IMPLODE, STRTOK +% +% Created: Sara Silva (sara@itqb.unl.pt) - 2002.04.30 +% +% Modified: Matthew Davidson (matthew@psych.columbia.edu) - 2005.10.11 + +if isempty(string) % empty string, return empty and 0 pieces + split{1}=''; + numpieces=0; + +elseif isempty(delimiters) % no delimiters, return whole string in 1 piece + split{1}=string; + numpieces=1; + +else % non-empty string and delimiters, the correct case + + remainder=string; + i=0; + + % If the first letter is in the delimiters, add a blank piece at the + % beginning + firstletteridx = strfind(delimiters,string(1)); + if ~isempty(firstletteridx) + i=i+1; + split{i}=''; + remainder = string(2:end); + end + + while ~isempty(remainder) + [piece,remainder]=strtok(remainder,delimiters); + i=i+1; + split{i}=piece; + end + numpieces=i; + +end diff --git a/Misc_utilities/fast_conv_fft.m b/Misc_utilities/fast_conv_fft.m new file mode 100644 index 00000000..c20fa33c --- /dev/null +++ b/Misc_utilities/fast_conv_fft.m @@ -0,0 +1,105 @@ +function y = fast_conv_fft(hrf,x,varargin) +% y = fast_conv_fft(hrf,x) +% +% Much faster than conv or using matrix multiplication. +% +% hrf should be length of x +% +% tor wager, jan 07 +% +% y = fast_conv_fft(hrf,x); +% y = fast_conv_fft(hrf,x3,'deconv'); +% + +dodeconv = 0; +if length(varargin) + for i = 1:length(varargin) + switch varargin{i} + case {'deconv','deconvolve'}; + dodeconv = 1; + otherwise + error('Unrecognized input string.'); + + end + end +end + +% make sure HRF is long enough +if length(hrf) < length(x) + nel = length(x) - length(hrf); + hrf = [hrf; zeros(nel,1)]; +end + + +len = length(x); +nz = round(len ./ 2); +z = zeros(nz,1); + + +if dodeconv + % deconvolve + + % x is a truncated, convolved timeseries + % we want to deconvolve to find y given the kernel hrf + % because x is truncated, we need to pad x with the values + % that we would get if we convolved [x; zeros(kl,1)] with + % the hrf. + + kl = max(find(hrf ~= 0)); % kernel length + + % pad with mean to avoid truncation artifact at end + % and zeros to avoid aliasing (?) at beginning + % this is an approx. solution, which we use in padding in next step + m = x(end-1:-1:1); %repmat(mean(x), 10*sum(hrf ~= 0),1); + zm = zeros(size(m)); + + fftx = fft([x;m]); + ffthrf = fft([hrf;zm]); + + y = real(ifft(fftx ./ ffthrf)); + y = y(1:len); + + % now we use our estimate of stim. series y to get ending values that + % have been truncated from x + % final values are inaccurate, so let's taper down... + + w = linspace(1,0,4)'; + + % taper towards mean + endy = y(end-kl+1:end); + lastvals = w .* endy(end-3:end); + endy(end-3:end) = lastvals; + endy = [endy; zeros(kl,1)]; + + % get what truncated vals should look like + endvals = fast_conv_fft(hrf(1:kl),endy); + extra = endvals(end-kl+1:end); + + % re-do deconv + zm = zeros(size(extra)); + + fftx = fft([x;extra]); + ffthrf = fft([hrf;zm]); + + y = real(ifft(fftx ./ ffthrf)); + + % not needed...the above is pretty good + % taper because we still have the problem +% % y = y(1:len); +% % +% % +% % endvals = w .* y(end-3:end) + (1-w) .* mean(y(1:end-4)); +% % y(end-3:end) = endvals; + +else + % convolve +% pad with zeros to avoid edge effects/aliasing +fftx = fft([x;z]); +ffthrf = fft([hrf;z]); + + y = real(ifft(fftx .* ffthrf)); +end + +y = y(1:len); + +end diff --git a/Misc_utilities/getRandom.m b/Misc_utilities/getRandom.m new file mode 100755 index 00000000..c0e88a4a --- /dev/null +++ b/Misc_utilities/getRandom.m @@ -0,0 +1,20 @@ +function stimList = getRandom(stimList) +% stimList = getRandom(stimList) +% +% Randomizes rows of a matrix, preserving dependencies between columns +% Tor Wager, created 2 / 01, last modified 2/25/04 to sort whole matrix + +%input: a col. vector of stimulus conditions, e.g. 1 1 1 1 2 2 2 2 3 3 4 4 + +%output: a randomized permutation of this vector + +randvector = rand(size(stimList,1),1); % create random vector + +stimList(:,size(stimList,2)+1) = randvector; + +stimList = sortrows(stimList,size(stimList,2)); % sort the rows by random seed + +stimList = stimList(:,1:end-1); + +return + diff --git a/Misc_utilities/get_first_help_lines.m b/Misc_utilities/get_first_help_lines.m new file mode 100644 index 00000000..a603cc40 --- /dev/null +++ b/Misc_utilities/get_first_help_lines.m @@ -0,0 +1,36 @@ +function helptext = get_first_help_lines(functionname, maxlines) +% Returns the first n lines of the help for a function in a cell array +% +% helptext = get_first_help_lines(functionname, [maxlines]) +% helptext = get_first_help_lines(functionname, maxlines) +% +% Examples: +% helptext = get_first_help_lines('fmri_data.apply_mask', 5); +% char(helptext{:}) +% +% Tor Wager, Oct 2012 + +if nargin < 2 + maxlines = 5; +end + + +h = help(functionname); + +isnewline = find(h == sprintf('\n')); + +nlines = min(maxlines, length(isnewline)); + +h = h(1:isnewline(nlines)); + +isnewline = [1 isnewline]; + +for i = 1:nlines + + helptext{i} = h(isnewline(i):isnewline(i+1)-1); + + helptext{i}(helptext{i} == sprintf('\n')) = []; + +end + +end % function \ No newline at end of file diff --git a/Misc_utilities/html/print_obj_oriented_help.html b/Misc_utilities/html/print_obj_oriented_help.html new file mode 100644 index 00000000..1aeccaa1 --- /dev/null +++ b/Misc_utilities/html/print_obj_oriented_help.html @@ -0,0 +1,487 @@ + + + + + METHODS for canlab object-oriented tools

METHODS for canlab object-oriented tools

The list below prints the first 200 characters of help for each method. You can get additional information by typing >>help objecttype.methodname in the Matlab command window.

Contents

z = '_________________________________________';
+
+%objname = 'fmri_data'; % for example
+
+fprintf('%%%% %s\n', objname);
+
+m = methods(objname);
+
%% fmri_data
+
for i = 1:length(m)
+

Method

New method begins here

    % *Method name*
+    mname = [objname '.' m{i}];
+    h = help(mname);
+    %h = help(m{i});
+
+    len = min(200, length(h));
+
+    fprintf('\n%s\n%s\n%s\n', z, mname, z);
+
+    disp(h(1:len));
+
+    fprintf('\n')
+
+end
+
+_________________________________________
+fmri_data.apply_mask
+_________________________________________
+  Apply a mask image (image filename or fmri_mask_image object) to an image_vector object
+  stored in dat
+ 
+  This can be used to:
+  - Mask an image_vector or fmri_data object with a mask
+  - Obtain "
+
+
+_________________________________________
+fmri_data.check_image_filenames
+_________________________________________
+  Check whether images listed in obj.fullpath actually exist
+ 
+  obj = check_image_filenames(obj)
+ 
+  Behavior:
+  If there are no file names, do nothing.
+  If file names are entered and full path is n
+
+
+_________________________________________
+fmri_data.compare_space
+_________________________________________
+  function isdiff = compare_space(obj, obj2)
+ 
+  Compare spaces of two image_vector objects
+ 
+  Returns 0 if same, 1 if different spaces, 2 if no volInfo info for one or
+  more objects. 3 if same spac
+
+
+_________________________________________
+fmri_data.create
+_________________________________________
+
+
+_________________________________________
+fmri_data.extract_roi_averages
+_________________________________________
+  cl = extract_roi_averages(fmri_data obj, [mask_image], [average_over])
+ 
+  This fmri_data method a extracts and averages data stored in an fmri_data object 
+  from a set of ROIs defined in a mask.
+ 
+
+
+_________________________________________
+fmri_data.fastmontage
+_________________________________________
+  fastmontage(dat, [myview], ['spacing', slicespacing], ['vertical'])
+ 
+  fastmontage(dat);  Creates 3 separate montage views - ax, cor, sagg
+                     In special figure window
+ 
+  fastmont
+
+
+_________________________________________
+fmri_data.flip
+_________________________________________
+  Flips an image_vector object left to right
+ 
+  Optional: input 'mirror' to make a symmetrical image, averaging the left
+  and right hemispheres
+ 
+  dat = flip(dat, ['mirror'])
+ 
+  tor. may 2012
+
+Hel
+
+
+_________________________________________
+fmri_data.fmri_data
+_________________________________________
+  [obj, cl_with_averages] = fmri_data(image_names, mask_image, varargin)
+ 
+  Reads a set of image files and a mask image, and returns
+  an fmri_data object with data for all in-mask voxels.
+
+
+
+_________________________________________
+fmri_data.histogram
+_________________________________________
+  ---------------------------------------------------------------
+  Histogram
+  ---------------------------------------------------------------
+
+Help for fmri_data/histogram is inherited from supercla
+
+
+_________________________________________
+fmri_data.history
+_________________________________________
+  Display history for image_vector object
+
+Help for fmri_data/history is inherited from superclass IMAGE_VECTOR
+
+
+
+_________________________________________
+fmri_data.ica
+_________________________________________
+  Spatial ICA of an fmri_data object
+  icadat = ica(fmridat_obj, [number of ICs to save])
+  icadat is also an fmri_data object, with .dat field voxels x components
+ 
+  Notes:
+  icasig = W * mixedsig
+ 
+
+
+_________________________________________
+fmri_data.mean
+_________________________________________
+  function m = mean(obj, [optional args])
+ 
+  Create an image_vector object with mean values for each voxel (cols)
+  across images (rows) of an fmri_data object.
+ 
+  m is an image_vector object whose 
+
+
+_________________________________________
+fmri_data.montage
+_________________________________________
+  fig_handle = montage(image_obj)
+ 
+
+Help for fmri_data/montage is inherited from superclass IMAGE_VECTOR
+
+
+
+_________________________________________
+fmri_data.orthviews
+_________________________________________
+  Orthviews display (SPM) for CANlab image_vector (or fmri_data, statistic_image) object
+ 
+  Usage:
+  orthviews(image_obj, ['posneg'])
+  Optional 'posneg' input generates orthviews using solid colors.
+
+
+_________________________________________
+fmri_data.plot
+_________________________________________
+  plot(fmridat, [plotmethod])
+ 
+  Plot methods:
+  ----------------------------------------
+  Plot data matrix
+  plot(fmri_data_object)
+ 
+  Plot means by condition
+  plot(fmri_data_object, 'means_for_u
+
+
+_________________________________________
+fmri_data.plot_current_orthviews_coord
+_________________________________________
+  Retrieves and plots the image data series at the current crosshairs in spm_orthviews
+ 
+  voxel_data_series = plot_current_orthviews_coord(dat)
+
+Help for fmri_data/plot_current_orthviews_coord is inh
+
+
+_________________________________________
+fmri_data.predict
+_________________________________________
+  Predict outcome (Y) from brain data and test cross-validated error rate for an fmri_data object
+ 
+  [cverr, stats, optional_outputs] = predict(obj, varargin)
+ 
+  Features:
+  ------------------------
+
+
+_________________________________________
+fmri_data.preprocess
+_________________________________________
+  obj = preprocess(obj, meth, varargin)
+ 
+  Preprocesses data in an fmri_data object
+  Data is observations (i.e., voxels, subjects) x images, so operating on the columns operates on
+  images, and ope
+
+
+_________________________________________
+fmri_data.read_from_file
+_________________________________________
+  Reads data from image filenames into obj.dat
+ 
+  obj = read_from_file(obj)
+  
+  Try obj = check_image_filenames(obj) first.
+  This is automatically called if you create a new image_vector object wit
+
+
+_________________________________________
+fmri_data.reconstruct_image
+_________________________________________
+  Reconstruct a 3-D or 4-D image from image_vector object obj
+  [voldata, vectorized_voldata] = reconstruct_image(obj)
+ 
+  voldata is and X x Y x Z x Images matrix
+  vectorized_voldata is the same, wi
+
+
+_________________________________________
+fmri_data.regress
+_________________________________________
+  [out, statimg] = regress(dat, [p-val threshold], [thresh_type], ['nodisplay'])
+ 
+  regression method for fmri_data object
+  regress dat.Y on dat.dat at each voxel, and return voxel-wise statistic
+  
+
+
+_________________________________________
+fmri_data.remove_empty
+_________________________________________
+  dat = remove_empty(dat, [logical vector of custom voxels to remove], [logical vector of imgs to remove])
+ 
+  remove vox: logical vector of custom voxels to remove, VOX x 1
+  remove im: logical vecto
+
+
+_________________________________________
+fmri_data.reparse_contiguous
+_________________________________________
+  obj = reparse_contiguous(obj, ['nonempty'])
+ 
+  Re-construct list of contiguous voxels in an image based on in-image
+  voxel coordinates.  Coordinates are taken from obj.volInfo.xyzlist.
+  Results a
+
+
+_________________________________________
+fmri_data.replace_empty
+_________________________________________
+  Replace empty/missing values in an image data object
+  obj = replace_empty(obj, [optional keywords])
+ 
+  Replace missing values in obj.dat stored in obj.removed_voxels and
+  obj.removed_images with 
+
+
+_________________________________________
+fmri_data.resample_space
+_________________________________________
+  Resample the images in an fmri_data object (obj) to the space of another
+  image (sampleto; e.g., a mask image). Works for all image_vector objects.
+ 
+  obj = resample_space(obj, sampleto, [sampling
+
+
+_________________________________________
+fmri_data.rescale
+_________________________________________
+  fmridat = rescale(fmridat, meth)
+ 
+  Rescales data in an fmri_data object
+  Data is observations x images, so operating on the columns operates on
+  images, and operating on the rows operates on vox
+
+
+_________________________________________
+fmri_data.sagg_slice_movie
+_________________________________________
+  sagg_slice_movie(dat, [full_path_of_movie_output_file])
+ 
+  Movie of successive differences (sagittal slice)
+  Enter an image_vector or fmri_data object (usually with time series)
+ 
+
+Help for fmri_d
+
+
+_________________________________________
+fmri_data.saveplots
+_________________________________________
+  Output dir
+
+
+
+_________________________________________
+fmri_data.signtest
+_________________________________________
+  [out, statimg] = signtest(dat, [p-val threshold], [thresh_type])
+ 
+  sign test for each voxel of an fmri_data object
+  returns voxel-wise statistic images.
+ 
+  Inputs:
+  dat should be an fmri_data o
+
+
+_________________________________________
+fmri_data.slices
+_________________________________________
+  Create a montage of single-slice results for every image in an
+  image_vector object
+ 
+ 
+  o = slices(obj, 'orientation', [orientation], 'slice', [slice_mm], 'nimages', [nimgs])
+ 
+  obj is an image_
+
+
+_________________________________________
+fmri_data.ttest
+_________________________________________
+  T-test on fmri_data class object
+  statsimg = ttest(fmridat, pvalthreshold, thresh_type)
+ 
+  ttest(fmridat, p-value threshold, thresh_type)
+ 
+  p-value threshold: p-value, e.g., .05 or .001 or [.001
+
+
+_________________________________________
+fmri_data.union
+_________________________________________
+  Union of two image_vector objects
+ 
+  dat = union(dat1, dat2, outputname)
+     % outputname = character array name for union image
+                    INCLUDE .img at the end.
+ 
+  NOTE: must now be 
+
+
+_________________________________________
+fmri_data.windsorize
+_________________________________________
+  obj = windsorize(obj, [madlimit])
+ 
+  Windsorize an fMRI data object to madlimit Median Absolute Deviations.
+  Default = 5 MADs.
+  Works across rows and columns.
+  Registers this step in history.
+
+
+
+_________________________________________
+fmri_data.write
+_________________________________________
+  Write an image_vector object to an Analyze image.
+  Option to write thresholded image, for statistic_image objects.
+ 
+  obj.dat should contain data, with one COLUMN for each 3-D frame in the
+  4-D i
+
+
\ No newline at end of file diff --git a/Misc_utilities/implode.m b/Misc_utilities/implode.m new file mode 100644 index 00000000..c91eadab --- /dev/null +++ b/Misc_utilities/implode.m @@ -0,0 +1,34 @@ +function string=implode(pieces,delimiter) +%IMPLODE Joins strings with delimiter in between. +% IMPLODE(PIECES,DELIMITER) returns a string containing all the +% strings in PIECES joined with the DELIMITER string in between. +% +% Input arguments: +% PIECES - the pieces of string to join (cell array), each cell is a piece +% DELIMITER - the delimiter string to put between the pieces (string) +% Output arguments: +% STRING - all the pieces joined with the delimiter in between (string) +% +% Example: +% PIECES = {'ab','c','d','e fgh'} +% DELIMITER = '->' +% STRING = IMPLODE(PIECES,DELIMITER) +% STRING = ab->c->d->e fgh +% +% See also EXPLODE, STRCAT +% +% Created: Sara Silva (sara@itqb.unl.pt) - 2002.08.25 + +if isempty(pieces) % no pieces to join, return empty string + string=''; + +else % no need for delimiters yet, so far there's only one piece + string=pieces{1}; +end + +l=length(pieces); +p=1; +while p 0 + Bnew = pwn_expand(B, dim, diff); + Anew = A; + elseif diff < 0 + Anew = pwn_expand(A, dim, diff); + Bnew = B; + else + Anew = A; + Bnew = B; + end +end + +function outarray = pwn_expand(inarray, dim, diff) + sznandims = size(inarray); + sznandims(dim) = abs(diff); + outarray = cat(dim, inarray, NaN(sznandims)); +end \ No newline at end of file diff --git a/Misc_utilities/parse_char_to_cell.m b/Misc_utilities/parse_char_to_cell.m new file mode 100644 index 00000000..71908653 --- /dev/null +++ b/Misc_utilities/parse_char_to_cell.m @@ -0,0 +1,32 @@ +function outcell = parse_char_to_cell(invar,sepval) +% function outcell = parse_char_to_cell(invar,sepval) +% take a row of characters and separate into cells, breaking at either +% spaces or tabs +% +% tor wager +% +% Example: +% copy from Excel as row, then parse: +% disorder = ['SAD PTSD PTSD PTSD PTSD PTSD SP SAD PTSD PTSD PTSD PTSD PTSD SAD SAD PTSD PTSD SP SP SP PTSD PTSD PTSD PTSD SAD SAD SAD SP SAD SAD SAD SP SP SP SAD SP PTSD SP PTSD PTSD']; +% disorder = parse_char_to_cell(disorder, 'tab'); + +switch sepval + case 'space' +whsep = find(invar == ' '); + case 'tab' +whsep = find(invar == sprintf('\t')); + otherwise + error('Enter space or tab') +end + +st = [1 whsep+1]; +en = [st(2:end) - 1 length(invar)+1]; +n = length(st); + +for i = 1:n + outcell{i} = invar(st(i):en(i)-1); +end + +end + + \ No newline at end of file diff --git a/Misc_utilities/parse_edat_txt.m b/Misc_utilities/parse_edat_txt.m new file mode 100755 index 00000000..2c3f7e2c --- /dev/null +++ b/Misc_utilities/parse_edat_txt.m @@ -0,0 +1,152 @@ +%--------------------------------------------------------------------- +% EPrime helper function +% 2/22/10 Joe Wielgosz +% +% Reads EPrime .txt output (equivalent to EDAT) directly into matlab cell arrays/structures +% +% Outputs: +% +% edat_struct: Structure containing the following fields: +% +% header: 1-element structure whose fields are header items from EDAT +% run: 1-element structure whose fields are run-specific items from EDAT +% trials: n-element structure whose fields are trial-specific items from EDAT +% where n is the number of trials +% +% edat_cells: Structure containing the following fields: +% +% header_cols: 1-row cell array whose fields are header column names from EDAT +% run_cols: 1-row cell array whose cells are run-specific column names from EDAT +% trials_cols: 1-row cell array whose cells are trial-specific column names from EDAT +% +% header: 1-row cell array whose cells are header items from EDAT +% run: 1-row cell array whose cells are run-specific items from EDAT +% trials: n-row cell array whose cells are trial-specific items from EDAT +% where n is the number of trials +% +% Inputs: +% +% fname: name of file to parse +% + + +function [edat_struct, edat_cells] = parse_edat_txt(fname) + +% Two passes are necessary: +% 1) find all fields used in the file +% 2) actually read the data +for pass = 1:2 + + if (isunix) + warning off + f = fopen(fname, 'r', 'l', 'UTF-16'); %this encoding worked for opening a file in Unix that was created by Windows + warning on + else + f = fopen(fname); + end + + line_idx = 1; + row_idx = [0 0 0]; + start_row = false; + end_row = false; + + % Modes during parse: + % 1: header + % 2: run + % 3: trials + for mode = 1:3 + if pass == 1 + template_struct{mode} = struct; + else % pass == 2 + template_struct{mode} = orderfields(blank_struct(template_struct{mode}, '')); + + switch mode + case {1} + edat_cells.header_cols = fieldnames(template_struct{mode})'; + case {2} + edat_cells.run_cols = fieldnames(template_struct{mode})'; + case {3} + edat_cells.trials_cols = fieldnames(template_struct{mode})'; + end + end + end + + while feof(f) == 0 + + tline = fgetl(f); + %if (skip), tline = tline(3:end); end + + %Check for start/end of block in file + switch tline + case {'*** Header Start ***'} + mode = 1; + start_row = true; + case {'*** LogFrame Start ***'} + mode = 2; + start_row = true; + case {' *** LogFrame Start ***'} + mode = 3; + start_row = true; + case {'*** Header End ***', '*** LogFrame End ***', ' *** LogFrame End ***'} + end_row = true; + end + + + if start_row % Start of block in file + + row_idx(mode) = row_idx(mode)+1; + col_idx = 1; + start_row = false; + row_struct = struct; + + elseif end_row % End of block in file + + if pass == 1 + + % Include any field which has appeared before + template_struct{mode} = combine_structs(template_struct{mode}, row_struct); + + else % pass == 2 + + full_row_struct = orderfields(combine_structs(template_struct{mode}, row_struct)); + + switch mode + case {1} + edat_struct.header(row_idx(mode)) = full_row_struct; + edat_cells.header(row_idx(mode),:) = struct2cell(full_row_struct); + case {2} + edat_struct.run(row_idx(mode)) = full_row_struct; + edat_cells.run(row_idx(mode),:) = struct2cell(full_row_struct); + case {3} + edat_struct.trials(row_idx(mode)) = full_row_struct; + edat_cells.trials(row_idx(mode),:) = struct2cell(full_row_struct); + end + end + + end_row = false; + + else % Middle of block, or junk + + pair = regexp(tline, '^\t*(?[^:\t]+): (?.+)$', 'names'); + + if ~isempty(pair) % Ignore junk + + clean_col_name = regexprep(pair.col, '\.', '_'); + row_struct.(clean_col_name) = pair.val; + col_idx = col_idx+1; + + end + end + + line_idx = line_idx+1; + + end % while + + fclose(f); + +end %for + +end + + + diff --git a/Misc_utilities/print_matrix.m b/Misc_utilities/print_matrix.m new file mode 100755 index 00000000..cddf4037 --- /dev/null +++ b/Misc_utilities/print_matrix.m @@ -0,0 +1,60 @@ +function print_matrix(x,varargin) +% print_matrix(x,[col names cell array], [row names cell], [format string]) +% +% tor wager +% prints matrix values as tab delimited, 2 decimal places +% +% Examples: +% ------------------------------------------------------------- +% t = [1 2; 3 4; 5 6]; +% print_matrix(t,{'col1' 'col2'},{'row1' 'row2' 'row3'); +% +% print_matrix(rand(5), [], [], '%3.2f'); +% print_matrix(rand(5), {'A' 'B' 'C' 'D' 'E'}, {'A' 'B' 'C' 'D' 'E'}, '%3.2f'); +% print_matrix(rand(5), {'A' 'B' 'C' 'D' 'E'}, {'A' 'B' 'C' 'D' 'E'}, '%d'); + +fmtstring = '%3.4f'; +if length(varargin) > 2 && ~isempty(varargin{3}) + fmtstring = varargin{3}; +end + +% set up +s = size(x,2); +str = [repmat([fmtstring '\t'],1,s) '\n']; +colnames = ''; % default added by Luka, 5/2013 + +if length(varargin) > 0 && ~isempty(varargin{1}) + colnames = varargin{1}; +end + +if length(varargin) > 1 && ~isempty(varargin{2}) + rownames = varargin{2}; + colnames = [{' '} colnames]; +end + + +% Print names +if length(varargin) > 0 && ~isempty(varargin{1}) + for i = 1:length(colnames) + fprintf(1,'%s\t',colnames{i}), + end + fprintf(1,'\n') +end + +% print matrix +if length(varargin) > 1 && ~isempty(varargin{2}) + for i = 1:size(x,1) + fprintf(1,'%s\t',rownames{i}) + fprintf(1,str,x(i,:)); + end +else + disp(sprintf(str,x')) +end + + + + + + + +end diff --git a/Misc_utilities/print_obj_oriented_help.m b/Misc_utilities/print_obj_oriented_help.m new file mode 100644 index 00000000..4f27d808 --- /dev/null +++ b/Misc_utilities/print_obj_oriented_help.m @@ -0,0 +1,35 @@ +%% METHODS for canlab object-oriented tools +% The list below prints the first 200 characters of help for each method. +% You can get additional information by typing >>help objecttype.methodname +% in the Matlab command window. +%% +z = '_________________________________________'; + +%objname = 'fmri_data'; % for example + +fprintf('%%%% %s\n', objname); + +m = methods(objname); +%% + +for i = 1:length(m) + %%% Method + % New method begins here + + % *Method name* + mname = [objname '.' m{i}]; + h = help(mname); + %h = help(m{i}); + + len = min(200, length(h)); + + fprintf('\n%s\n%s\n%s\n', z, mname, z); + + disp(h(1:len)); + + fprintf('\n') + +end + + + diff --git a/Misc_utilities/progressbar.m b/Misc_utilities/progressbar.m new file mode 100644 index 00000000..ab24fef8 --- /dev/null +++ b/Misc_utilities/progressbar.m @@ -0,0 +1,44 @@ +function [f,ax] = progressbar(meth,val) +% progressbar(meth,val) +% Create a progress bar window with tag 'progressbar' +% that can be updated +% +% meth can be 'init' or 'update' +% x-axis limits are 0 - 100, so val should be % complete for best results +% +% the f and ax handles are not really needed +% as 'update' finds the axis with 'progressbar' tag. +% +% progressbar('update',100*i./nvars); +% +% tor wager + + f = []; + ax = []; + + switch meth + case 'init' + f = figure('Color','w'); + tmp = get(gcf,'Position') .* [1 1 .5 .1]; + set(gcf,'Position',tmp) + set(gcf,'MenuBar','none','NumberTitle','off') + figure(f), set(gca,'Xlim',[0 100]) + subplot(1,1,1); + ax = findobj('Type','Axes'); + set(ax,'XLim',[0 100],'YLim',[0 1]); + set(ax,'Tag','progressbar') + drawnow + + case 'update' + ax = findobj('Tag','progressbar'); + if isempty(ax), progressbar('init'); end + delete(findobj(ax,'Type','patch')); % delete current bars + barh(val); + set(ax,'XLim',[0 100]); + drawnow + + otherwise + error('progressbar: Unknown method'); + end + + return diff --git a/Misc_utilities/publish_obj_oriented_help.m b/Misc_utilities/publish_obj_oriented_help.m new file mode 100644 index 00000000..8d225a1f --- /dev/null +++ b/Misc_utilities/publish_obj_oriented_help.m @@ -0,0 +1,112 @@ +function publish_obj_oriented_help + +outputbase = '/Users/tor/Dropbox/psyc7215_class_files/Part2_Machine_Learning/Toolboxes/help'; +if ~exist(outputbase, 'dir'), mkdir(outputbase); end + + +for objnames = {'fmri_data' 'statistic_image' 'image_vector' 'fmridisplay'} + +objname = objnames{1}; + +outputdir = fullfile(outputbase, objname); +if ~exist(outputdir, 'dir'), mkdir(outputdir); end + +% Dynamically generate script for each object +% This allows us to use markup to create headers, etc. +% +% output in main help directory + +%scriptname = 'print_obj_oriented_help.m'; +scriptname = generate_script(outputdir, objname); +addpath(outputdir) + +p = struct('useNewFigure', false, 'maxHeight', 1500, 'maxWidth', 1200, ... + 'outputDir', outputdir, 'showCode', false); + +publish(scriptname, p) + +end + +end % main function + +function scriptname = generate_script(outputdir, objname) +% generates script for publish + +scriptname = fullfile(outputdir, ['index.m']); +[fid, message] = fopen(scriptname, 'w'); + +if fid < 0, disp(message), end + +z = '_________________________________________'; + +maxlines = 10; % lines of help to print + +% Start dynamically generating script +% Get object and method names in script + str = ['objname = ''' objname ''';']; + fprintf(fid, '%s\n', str); + + str = ['m = methods(objname);']; + fprintf(fid, '%s\n', str); + + str = ['maxlen = 200; % characters']; + fprintf(fid, '%s\n', str); + + +% Do object constructor method first + +fprintf(fid, '%%%% %s Object Class\n', objname); + +h = help(objname); +%len = min(maxlen, length(h)); + +% Separate comment lines for Description markup + +helptext = get_first_help_lines(objname, maxlines); + +for i = 1:length(helptext) + fprintf(fid, '%% %s\n', helptext{i}); +end + +fprintf(fid, '%% \n%% METHODS\n%% ___________________________________\n\n'); + +m = methods(objname); + +for i = 1:length(m) + %%% Method + % New method begins here + + fprintf(fid, '%%%% %s\n', m{i}); + + + % Get help (in script) + + + str = ['mname = [objname ''.' m{i} '''];']; + fprintf(fid, '%s\n', str); + + str = ['h = help(mname);']; + fprintf(fid, '%s\n', str); + + str = ['len = min(maxlen, length(h));']; + fprintf(fid, '%s\n', str); + + % get and print method name in bold + mname = [objname '.' m{i}]; + + fprintf(fid, '\n%% *%s*\n', mname); + + % print help + fprintf(fid, 'fprintf(''%%s\\n'', h(1:len));\n'); + +end + +fclose(fid); + +% for development, run this to print script: +% eval(['!more ' scriptname]) + + + +end % function + diff --git a/Misc_utilities/read_edat_output_2008.m b/Misc_utilities/read_edat_output_2008.m new file mode 100644 index 00000000..30fd9098 --- /dev/null +++ b/Misc_utilities/read_edat_output_2008.m @@ -0,0 +1,258 @@ +% DATA = read_edat_output_2008(fname, varargin) +% +% Function that creates a structure DATA containing columns +% of the edat file output (saved in text tab delimited "excel" format) +% +% For this code to work on a Mac, you must: 1) export .edat2 file as an Excel file, +% then 2) open this file in Excel on a Mac and save as a .csv, 3) read that +% .csv file +% +% Tor Wager, Oct 2008 +% +% Examples: +% ----------------------------------------- +% fname = 'myfile.txt'; +% DATA = read_edat_output_2008(fname) +% +% Defaults: +% These are the default formats this function expects: +% tab delimited, 1 header row, then row of column names, then data +% +% You can override some of them by using the following -- +% E.g., for zero header rows and comma delimited data: +% DATA = read_edat_output_2008(fname, 'nheaderrows', 0, 'mydelimiter', ',') +% +% You can force the number of columns to be a certain value by doing the +% following: +% DATA = read_edat_output_2008(fname, 'nheaderrows', 1, 'numc', 103); +% +% This could be useful if your last row contains empty cells at the end, +% which will mess up the automatic calculation of number of columns. + + +function DATA = read_edat_output_2008(fname, varargin) + +% ------------------------------------------------------------------------ +% read the database initially to get all column names, etc. +% ------------------------------------------------------------------------ +DATA = []; + +% assume you have one header row +nheaderrows = 1; +mydelimiter = '\t'; +numc = []; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'nheaderrows', nheaderrows = varargin{i+1}; + case 'mydelimiter', mydelimiter = varargin{i+1}; + + case 'numc', numc = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +col1 = textread(fname, '%s%*[^\n]', 'delimiter', mydelimiter, 'headerlines', nheaderrows); + +nrows = length(col1); + + +%numc = 148; +[d] = textread(fname,'%s','headerlines', nheaderrows, 'delimiter', mydelimiter); + +for i = 1:length(d), if isempty(d{i}), d{i} = 'NaN'; end, end + +if isempty(numc) + % determine from data + numc = length(d) ./ nrows; +else + % force numc to be input value + disp(['Forcing number of columns to be ' num2str(numc)]) + totalel = nrows * numc; + if length(d) < totalel + numtoadd = totalel - length(d); + for i = 1:numtoadd + d{end+1} = 'NaN'; + end + else + d = d(1:totalel); + end +end + +% % if strcmp(d{1},'This file contained edited data.'), +% % % add an extra input row! +% % disp('edited data: extra row.') +% % addone = 1; +% % [d] = textread(fname,'%s','headerlines',2,'delimiter','\t'); +% % end + +if mod(length(d), nrows) == 0 + fprintf('This data matrix appears to be OK, with %3.0f rows (including col. names) x %3.0f cols\n', nrows, numc) +else + fprintf('This data matrix IS NOT OK\n') + fprintf('i think it has %3.0f header rows, %3.2f data rows (including col. names) and %3.2f cols\n', nheaderrows, nrows, numc) + + disp('Here are the first 10 rows of the first column: ') + disp(char(col1{1:10})) + + disp('One potential cause is if you have empty spaces in the last row in your file.') + disp('then matlab will think it''s reached the end of the file and you will have too few rows.') + disp('Try entering ''numc'' as an input with the number of columns, or delete the last row.') + + return +end + +names = d(1:numc)'; disp(['Last name column read is ' names{end}]) + +disp(' ') +disp('Your column names:') +fprintf('%s ', names{:}); +disp(' ') + +% ------------------------------------------------------------------------ +% replace illegal characters that will cause Matlab problems +% ------------------------------------------------------------------------ + +for i = 1:length(names), names{i}(findstr('.',names{i})) = '_'; end +for i = 1:length(names), names{i}(findstr('[',names{i})) = ''; end +for i = 1:length(names), names{i}(findstr(']',names{i})) = ''; end +for i = 1:length(names), names{i}(findstr(':',names{i})) = ''; end +for i = 1:length(names), names{i}(findstr('=',names{i})) = ''; end + + +% ------------------------------------------------------------------------ +% Get data matrix in appropriate format +% ------------------------------------------------------------------------ + +% This only works if there are no blanks +%dmat = reshape(d, numc, nrows)'; + +% The code below works with blanks + +% dmat(1, :) = []; % get rid of names + +% adjust for header rows +nrows = nrows - 1; + +fmtstring = '%s%*[^\n]'; + +for i = 1:numc + %mycol = dmat(:, i); + + % read in the nth column, skipping header rows and column names + mycol = textread(fname, fmtstring, 'delimiter', mydelimiter, 'headerlines', nheaderrows + 1); + + fmtstring = ['%*s' fmtstring]; % add this so we skip this col next time + + % Get the first non-empty entry and determine if it's numeric or text + isvalidnumber(i) = 0; + for j = 1:length(mycol) + empt = isempty(mycol{j}); + + if ~empt + isvalidnumber(i) = ~isempty(str2num(mycol{j})); + break + end + end + + + if isvalidnumber(i) + % This column has numbers + for j = 1:nrows + try + mynum = str2num(mycol{j}); % sometimes, with 17:40:12 notation, returns more than 1 number + if isempty(mynum), mynum = NaN; end + + catch + disp('We seem to be mixing up text and numbers in columns. Are your rows and cols correct?') + disp('Here''s the current column (1st 15 rows)') + mycol(1:15) + + disp('Here''s the first 10 rows and cols:') + dmat(1:10, 1:10) + + keyboard + end + + DATA.(names{i})(j, 1) = mynum(1); + end + else + % This column is text + DATA.(names{i}) = mycol; + end + +end + + +end + + + +% % +% % +% % +% % % ------------------------------------------------------------------------ +% % % create formatting string with number of columns and output var names +% % % ------------------------------------------------------------------------ +% % +% % fmt = repmat('%s',1,numc); +% % fmt = [fmt '%*[^\n]']; +% % +% % outs = ['[' names{1}]; +% % for i = 2:numc, outs = [outs ',' names{i}]; end +% % outs = [outs ']']; +% % +% % % works, but requires an extra step +% % %outs = ['[out{1}']; +% % %for i = 2:numc, outs = [outs ',out{' num2str(i) '}'];, end +% % %outs = [outs ']']; +% % +% % % ------------------------------------------------------------------------ +% % % read the database again with proper formatting and output names +% % % ------------------------------------------------------------------------ +% % +% % if addone +% % str = [outs ' = textread(''' fname ''',fmt,''headerlines'',3,''delimiter'',''\t'');']; +% % else +% % str = [outs ' = textread(''' fname ''',fmt,''headerlines'',2,''delimiter'',''\t'');']; +% % end +% % +% % eval(str) +% % +% % warning off +% % ww = whos('*RT*'); ww = ww(end).name; eval(['ww = isempty(str2num(' ww '{1}));']) +% % warning on +% % if ww, +% % str = [outs ' = textread(''' fname ''',fmt,''headerlines'',3,''delimiter'',''\t'');']; +% % eval(str) +% % end +% % +% % % not necessary if all filenames are OK +% % %for i = 1:length(outs), +% % % eval([names{i} ' = out{i};']) +% % %end +% % +% % +% % % ------------------------------------------------------------------------ +% % % convert blanks to NaN's, to avoid losing placeholders, and +% % % convert columns with numeric information to numeric vectors +% % % ------------------------------------------------------------------------ +% % for i = 1:numc +% % +% % % The replacing blanks part +% % eval(['wh = strmatch('' '',str2mat(' names{i} '{:}));']) +% % eval([names{i} '(wh) = {''NaN''};']) +% % +% % % the conversion to numbers part +% % eval(['a = str2num(str2mat(' names{i} '{:}));']) +% % if isempty(a) | sum(isnan(a)) == length(a) +% % % leave it alone; it's text +% % else +% % eval([names{i} ' = a;']), +% % end +% % end + diff --git a/Misc_utilities/remove_str_from_cell.m b/Misc_utilities/remove_str_from_cell.m new file mode 100644 index 00000000..948c8abf --- /dev/null +++ b/Misc_utilities/remove_str_from_cell.m @@ -0,0 +1,8 @@ +%cellarray = remove_str_from_cell(cellarray,str) + +function cellarray = remove_str_from_cell(cellarray,str) + + wh = cellfun(@isempty, strfind(cellarray, str)); + cellarray = cellarray(wh); + +end diff --git a/Misc_utilities/robustcsvread.m b/Misc_utilities/robustcsvread.m new file mode 100644 index 00000000..7c6fc9fe --- /dev/null +++ b/Misc_utilities/robustcsvread.m @@ -0,0 +1,85 @@ +% ROBUSTCSVREAD reads in CSV files with +% different number of columns on different +% lines +% +% This returns a struct, with one field per column of the csv file. +% Each field is a cell array whose length = rows in the csv file. Column +% names are assumed to be in the first row. +% If column names are invalid struct field names, edits them by replacing +% funky characters with an underscore, or if first char is a number, I +% prepend aa_ to the field name. +% +% varargin: +% cols: how many cols to read in, by defaults reads them all +% rows_to_skip: how many rows to skip +% delim: cell delimiter +% missing: followed by cell array, first cell is val for missing, +% second cell is what to replace with +% +% +% extended by Yoni Ashar, 10/2012 +% +% original code off the fileexchange, by +% robbins@bloomberg.net +% michael.robbins@us.cibc.com +function MM=robustcsvread(filename, varargin) + +cols = NaN; +n_h = 0; +delim = ','; +missing_data = 0; +for i=1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'cols' + cols = varargin{i+1}; + case 'rows_to_skip' + n_h = varargin{i+1}; + case 'delim' + delim = varargin{i+1}; + case 'missing' + missing_data = 1; + missing = varargin{i+1}{1}; + replace_missing = varargin{i+1}{2}; + end + end +end + + +if ~exist(filename, 'file'), MM=[]; warning('File does not exist'); fprintf('%s\n', filename), return, end + +fid=fopen(filename,'r'); +slurp=fscanf(fid,'%c'); +fclose(fid); +M=strread(slurp,'%s','delimiter','\n'); +for i=1:length(M) + temp=strread(M{i},'%s','delimiter',delim); + for j=1:length(temp) + if missing_data && strcmp(temp{j}, missing), temp{j} = replace_missing; end + MM{i,j}=temp{j}; + end; +end; + + +% Yoni's extension is below +data = MM; +clear MM; + +for i=1:n_h + data(1,:) = []; +end + +%one line silly hack for a project Yoni needed... please forgive. +if isequal(data{1,2},'"ANSWER - ENTER A'), data{1,2} = 'A'; end + +for i=1:min(cols,size(data,2)) + data{1,i} = strtrim(data{1,i}); + % replace bad characters with _ + data{1,i} = regexprep(data{1,i}, '\W', '_'); + % if col name starts with a number, preprend an 'aa' + if isstrprop(data{1,i}(1), 'digit'), data{1,i} = ['aa_' data{1,i}]; end + + cmd = ['MM.' data{1,i} ' = data(2:end,' num2str(i) ');']; + eval(cmd); +end + diff --git a/Misc_utilities/scn_get_datetime.m b/Misc_utilities/scn_get_datetime.m new file mode 100644 index 00000000..a9f047b8 --- /dev/null +++ b/Misc_utilities/scn_get_datetime.m @@ -0,0 +1,23 @@ +function str = scn_get_datetime(varargin) +% str = scn_get_datetime +% +% pass in 'ymd' to get the string in yyyy_mm_dd-HH_MM format, so that +% alphanumeric order will correspond to chronological order +% +% +% Returns a string with the date and time +% Useful for annotating data and output + +if strmatch('ymd', varargin) + datestring=datestr(date, 'yyyy-mm-dd'); + +else + datestring=date; +end + + cval = fix(clock); + + str = sprintf('%s_%02d:%02d', datestring, cval(4), cval(5)); + + str(str == ':') = '_'; +end diff --git a/Misc_utilities/scn_mat_conform.m b/Misc_utilities/scn_mat_conform.m new file mode 100644 index 00000000..8dcb85bb --- /dev/null +++ b/Misc_utilities/scn_mat_conform.m @@ -0,0 +1,43 @@ +function in = scn_mat_conform(in) + %function in = scn_mat_conform(in) + % + % sets flipping to 0 (no flip) in SPM2 and adjusts mat file accordingly + % input in spm-style mat file or struct with .mat or .M fields + % + % tor wager, dec 06 + + global defaults + + if isstruct(in) + if isfield(in,'mat') + in.mat = scn_mat_conform(in.mat); + end + + if isfield(in,'M') + in.M = scn_mat_conform(in.M); + end + + return + end + + + + if in(1) < 0 + disp('Warning: Image has negative x voxel size, indicating ''flipped'' in SPM.') + disp('This will be changed to positive. This program does not do any image flipping.') + + in(1) = abs(in(1)); % voxel size + in(1,4) = -(in(1,4)); % origin offset + end + + if isempty(defaults) + spm_defaults + end + + if defaults.analyze.flip + disp('Warning: Setting defaults.analyze.flip to 0. No flipping.') + defaults.analyze.flip = 0; + end + + + return \ No newline at end of file diff --git a/Misc_utilities/search_struct_fields.m b/Misc_utilities/search_struct_fields.m new file mode 100644 index 00000000..a1726e3d --- /dev/null +++ b/Misc_utilities/search_struct_fields.m @@ -0,0 +1,51 @@ +% found_paths = search_struct_fields(search_struct, fieldname) +% returns a list of all paths inside structure search_struct that match the fieldname (or start with it) +% +% E.g.: +% foo = []; +% foo.foo = []; +% foo.foo.foo = []; +% search_struct_fields(foo, 'foo') +% ans = +% +% 'foo.foo' +% 'foo.foo.foo' +% +% or: +% +% search_struct_fields(SPM, 'x') +% ans = +% +% 'SPM.xX' +% 'SPM.xM' +% 'SPM.xsDes' +% 'SPM.xX.xVi' +% 'SPM.xX.xKXs' +% 'SPM.xM.xs' + +function found_paths = search_struct_fields(search_struct, fieldname, fieldpath) + found_paths = {}; + if(~isstruct(search_struct)) + error('search_struct not a structure'); + end + if(~exist('fieldpath', 'var') || isempty(fieldpath)) + fieldpath = inputname(1); + end + if(length(search_struct) > 1) + fieldpath = [fieldpath '(:)']; + end + + names = fieldnames(search_struct); + matches = strmatch(fieldname, names); + + if(~isempty(matches)) + found_paths = cellstr([repmat([fieldpath '.'], length(matches), 1) strvcat(names{matches})]); + end + + for i=1:length(names) + if(isstruct(search_struct(1).(names{i}))) + new_fieldpath = [fieldpath '.' names{i}]; + found_paths = [found_paths; search_struct_fields(search_struct(1).(names{i}), fieldname, new_fieldpath)]; + end + end +end \ No newline at end of file diff --git a/Misc_utilities/setFigHeight.m b/Misc_utilities/setFigHeight.m new file mode 100644 index 00000000..8506f13b --- /dev/null +++ b/Misc_utilities/setFigHeight.m @@ -0,0 +1,8 @@ +function setFigHeight(hFig, height) + if(~ishandle(hFig)) + error('SCANLab:invalidHandleError', 'Figure handle %d is not a valid handle', hFig); + else + figPos = get(hFig, 'Position'); + set(hFig, 'Position', [figPos(1:3) height]); + end +end \ No newline at end of file diff --git a/Misc_utilities/setFigWidth.m b/Misc_utilities/setFigWidth.m new file mode 100644 index 00000000..c8088199 --- /dev/null +++ b/Misc_utilities/setFigWidth.m @@ -0,0 +1,8 @@ +function setFigWidth(hFig, width) + if(~ishandle(hFig)) + error('SCANLab:invalidHandleError', 'Figure handle %d is not a valid handle', hFig); + else + figPos = get(hFig, 'Position'); + set(hFig, 'Position', [figPos(1:2) width figPos(4)]); + end +end \ No newline at end of file diff --git a/Misc_utilities/strip_path_dirs.m b/Misc_utilities/strip_path_dirs.m new file mode 100644 index 00000000..d10a5f66 --- /dev/null +++ b/Misc_utilities/strip_path_dirs.m @@ -0,0 +1,21 @@ +% function strip_path_dirs(regexes) +% Removes directories from the Matlab path, based on the rgex patterns passed in. + +function strip_path_dirs(patterns) + if(ischar(patterns)) + patterns = cellstr(patterns); + end + + %matches = regexp(path(), sprintf('[^%s]+%s[^%s]+', pathsep, patterns{i}, pathsep), 'match'); + %matches = regexp(path(), sprintf('([^%s]+)', pathsep()), 'match'); + for i=1:length(patterns) + matches = regexp(path(), sprintf('[^%s]*%s[^%s]*', pathsep, patterns{i}, pathsep), 'match'); + rmpath(implode(matches, pathsep)); + end + +% for i=1:length(bad_dirs) +% rmpath(implode(bad_dirs{i}, pathsep)); +% end +% + savepath(); +end \ No newline at end of file diff --git a/Misc_utilities/strip_svn_dirs.m b/Misc_utilities/strip_svn_dirs.m new file mode 100644 index 00000000..69106dc1 --- /dev/null +++ b/Misc_utilities/strip_svn_dirs.m @@ -0,0 +1,4 @@ +% Removes the .svn dirs from the path +function strip_svn_dirs() + strip_path_dirs('\.svn'); +end \ No newline at end of file diff --git a/Misc_utilities/strrep_recurse.m b/Misc_utilities/strrep_recurse.m new file mode 100644 index 00000000..a9d445de --- /dev/null +++ b/Misc_utilities/strrep_recurse.m @@ -0,0 +1,46 @@ +% new_var = strrep_recurse(old_var, old_string, new_string) +% Recursively traverses depth-first through an entire variable, replacing +% old_string with new_string everywhere it goes + +function new_var = strrep_recurse(old_var, old_string, new_string, depth) + MAX_DEPTH = 200; + new_var = old_var; + + if(~exist('depth', 'var') || isempty(depth)) + depth = 1; + elseif(depth > MAX_DEPTH) + error('Exceeding max structure depth of %d. If this is legitimate, please edit this function.\n', depth); + end + + % Below code does not work as Matlab R2007B has some massive bug in the class() function. + % It works when executed manually, but not as part of a func... wtf? + % field_class = class(new_var.(fnames{i})); + % switch(field_class) + % case 'struct' + % new_var.(fnames{i}) = struct_strrep(new_var.(fnames{i}), old_string, new_string); + % case 'cell' + % if(iscellstr(new_var.(fnames{i}))) + % new_var.(fnames{i}) = strrep(new_var.(fnames{i}), old_string, new_string); + % end + % case 'char' + % new_var.(fnames{i}) = char(strrep(cellstr(new_var.(fnames{i})), old_string, new_string)); + % end + + % Not using switch due to some weird bug in class() function in 2007B + if(isstruct(new_var)) + for i=1:length(new_var) + fnames = fieldnames(new_var(i)); + for j=1:length(fnames) + new_var(i).(fnames{j}) = strrep_recurse(new_var(i).(fnames{j}), old_string, new_string, depth + 1); + end + end + elseif(iscellstr(new_var)) + new_var = strrep(new_var, old_string, new_string); + elseif(ischar(new_var)) + new_var = char(strrep(cellstr(new_var), old_string, new_string)); + elseif(iscell(new_var)) + for i=1:length(new_var) + new_var{i} = strrep_recurse(new_var{i}, old_string, new_string, depth + 1); + end + end +end diff --git a/Misc_utilities/struct_strrep.m b/Misc_utilities/struct_strrep.m new file mode 100644 index 00000000..ae9be4e0 --- /dev/null +++ b/Misc_utilities/struct_strrep.m @@ -0,0 +1,45 @@ +% new_struct = struct_strrep(old_struct, old_string, new_string) +% Traverses depth-first through an entire structure, replacing +% old_string with new_string everywhere it goes + +function new_struct = struct_strrep(old_struct, old_string, new_string, depth) + MAX_STRUCT_DEPTH = 200; + new_struct = old_struct; + + if(~exist('depth', 'var') || isempty(depth)) + depth = 1; + elseif(depth > MAX_STRUCT_DEPTH) + error('Exceeding max structure depth of %d. If this is legitimate, please edit this function.\n', depth); + end + + fnames = fieldnames(new_struct); + for i = 1:length(fnames) + % Below code does not work as Matlab R2007B has some massive bug in the class() function. + % It works when executed manualy, but not as part of a func... wtf? + % field_class = class(new_struct.(fnames{i})); + % switch(field_class) + % case 'struct' + % new_struct.(fnames{i}) = struct_strrep(new_struct.(fnames{i}), old_string, new_string); + % case 'cell' + % if(iscellstr(new_struct.(fnames{i}))) + % new_struct.(fnames{i}) = strrep(new_struct.(fnames{i}), old_string, new_string); + % end + % case 'char' + % new_struct.(fnames{i}) = char(strrep(cellstr(new_struct.(fnames{i})), old_string, new_string)); + % end + + if(isstruct(new_struct.(fnames{i}))) + for j=1:length(new_struct.(fnames{i})) + new_struct.(fnames{i})(j) = struct_strrep(new_struct.(fnames{i})(j), old_string, new_string, depth + 1); + end + elseif(iscellstr(new_struct.(fnames{i}))) + new_struct.(fnames{i}) = strrep(new_struct.(fnames{i}), old_string, new_string); + elseif(ischar(new_struct.(fnames{i}))) + new_struct.(fnames{i}) = char(strrep(cellstr(new_struct.(fnames{i})), old_string, new_string)); + elseif(iscell(new_struct.(fnames{i}))) + for j=1:length(new_struct.(fnames{i})) + new_struct.(fnames{i}){j} = struct_strrep(new_struct.(fnames{i}){j}, old_string, new_string, depth + 1); + end + end + end +end \ No newline at end of file diff --git a/Misc_utilities/subplots.m b/Misc_utilities/subplots.m new file mode 100644 index 00000000..ea9c1906 --- /dev/null +++ b/Misc_utilities/subplots.m @@ -0,0 +1,46 @@ +function subplots(data, varargin) + figureTitle = ''; + numRows = []; + numCols = []; + xAxis = []; + linkingAxes = 1; + + + for i=1:length(varargin) + if(ischar(varargin{i}) || isscalar(varargin{i})) + switch(varargin{i}) + case 'Title' + figureTitle = varargin{i+1}; + case 'NumRows' + numRows = varargin{i+1}; + case 'NumCols' + numCols = varargin{i+1}; + case 'LinkingAxes' + linkingAxes = varargin{i+1}; + case {'XAxis', 'xAxis'} + xAxis = varargin{i+1}; + end + end + end + + if(isempty(numCols) && isempty(numRows)) + numRows = size(data,2); + numCols = 1; + elseif(~isempty(numCols) && isempty(numRows)) + numRows = ceil(size(data,2)/numCols); + end + + figure('Name',figureTitle); + %subplot(numRows, numCols, 1); + if(isempty(xAxis)) + xAxis = (1:length(data)); + end + ah = []; + for i=1:size(data,2) + ah(i) = subplot(numRows, numCols, i); + plot(xAxis, data(:,i)); + end + if(linkingAxes) + linkaxes(ah); + end +end \ No newline at end of file diff --git a/Misc_utilities/tor_ga.m b/Misc_utilities/tor_ga.m new file mode 100755 index 00000000..22869a8c --- /dev/null +++ b/Misc_utilities/tor_ga.m @@ -0,0 +1,742 @@ +function [best_params,fit,beff,in,isconverged] = tor_ga(gensize,numgen,inputs,ofun,varargin) + %[best_params,fit,beff,in,isconverged] = tor_ga(gensize,numgen,inputs,ofun,[optional in any order: genfun,fixed inputs,cmd strings]) + % + % ---------------------------------------------------------------------- + % inputs a cell array describing the inputs to the optimization function + % (parameters to be optimized). + % ---------------------------------------------------------------------- + % Each cell of inputs is a p x q matrix of parameters. + % p and q are arbitrary, as each organism is described by a p x q + % matrix...but the objective function must be able to handle inputs in + % the format you provide. + % Internally, a set of 'organisms' is created that is + % params x params x organisms (3-D). + % This matrix is subject to crossover across orgs. separately for each + % cell. + % If inputs is a p x q matrix, it will be placed in a single cell. + % + % By default, it is only necessary to enter a single set of params + % for an example organism. The range of those input values is used to + % generate random starting values for each organism. + % + % There can be more than one set of + % parameters that are combined in some way by ofun to produce a fitness + % value. if there is more than one set of input parameters, + % inputs should be entered as a cell array, one cell per input. + % inputs should be in ORDER of inputs entered to ofun! + % + % **RECOMMENDED** + % If you enter each cell of inputs as a 3-D array so that inputs(:,:,1) + % is the min acceptable value for each param and inputs(:,:,2) is the + % max acceptable value, then the ga will create a series of organisms + % at start that evenly span the range of the multivariate parameter + % space, with the spacing between values determined by the gensize. + % This can provide a huge advantage in efficiency for the GA. + % This is the idea behind the "Sobol sequence," which chooses values + % that evenly span a multivariate space. + % With this option, if gensize is sufficiently large and the param + % space is sufficiently small, then the ga may find the correct + % solution on the first iteration. + % However, it is not likely to work well if the num. params >> gensize + % For example: + % start = [-15 -15]; + % start(:,:,2) = [15 15]; + % [best_params,fit,beff,in] = tor_ga(324,30,{start},objfun_ga,'genconverge',5); + % + % ---------------------------------------------------------------------- + % ofun the objective function that combines the inputs. + % ---------------------------------------------------------------------- + % There are two options for passing this in: + % 1) enter the name of the function as a string. the program creates a handle for the + % function, and evaluates it using inputs specified in the inputs variable. + % In this case, pass in fixed inputs after ofun, in the varargin fields + % fixed inputs optional, fixed inputs that do not change! same structure + % as inputs. + % + % 2) You can also enter ofun as a function handle durectly, with fixed inputs already + % embedded before running the program. + % The function should take as input a param list, and return fitness. + % e.g., objhan = @(params) my_function_name(params,fixed_inputs1,fixed_inputs1,fixed_inputs1); + % e.g., objhan = @(wh) prospect_organism(ceil(wh),pop,truep,iter); + % Pass in objhan as the 'ofun' input argument + % + % ---------------------------------------------------------------------- + % genfun [optional] input param generation function + % ---------------------------------------------------------------------- + % A function handle that generates a parameter set for each organism + % + % ---------------------------------------------------------------------- + % command strings + % ---------------------------------------------------------------------- + % 'noverbose' turn off verbose reporting and plots + % 'genconverge' followed by integer x: converge if no change in last x generations + % + % + % by Tor Wager, Last updated: Feb 2007, then June 2010 to add seeds + % + % Examples: + % ---------------------------------------------------------------------- + % Example for fitting indscal model: + % inputs{1} = X; fixin{1} = sp; fixin{2} = B1; fixin{3} = B2; + % tor_ga(30,10,inputs,'indscalf',fixin); + % + % W = rand(size(W)); W(1,:) = [10 10];, W(2,:) = [-10 -10]; + %inputs{2} = W; + % + % --------------------------------------------------------------------- + % Example: Optimize gambles for prospect theory model + % See prospect_optimize_design.m for definition of population of + % gambles from which to draw (pop), truep, iter (all fixed inputs) + % + % objhan = @(wh) prospect_organism(ceil(wh),pop,truep,iter); + % genfun = @() randsample(gindx,ntrials,'true')'; + % [best_params,fit,beff,in] = tor_ga(5,3,wh,objhan,genfun); + % --------------------------------------------------------------------- + % + % Using string inputs to control behavior: + % --------------------------------------------------------------------- + % [best_params,fit,beff,in] = tor_ga(300,30,{[15; -15]},objfun_ga,'genconverge',5,'noverbose'); + + t0 = clock; + + % -------------------------------------------------------------------- + % * set up inputs + % -------------------------------------------------------------------- + + % objective function + % two modes: 1) given string, construct string to evaluate with fixed inputs + % 2) given function handle with fixed inputs embedded, evaluate + % directly + if ischar(ofun) + evalmode = 'string'; + eval(['fun = @' ofun ';']), disp(['Objective function is ' ofun ]) + else + evalmode = 'handle'; + fun = ofun; + + end + + % format inputs correctly + if ~iscell(inputs), tmp=inputs; clear inputs; inputs{1} = tmp; end + + switch evalmode + case 'string' + % inputs - create string that tells feval what arguments to put in + + + estr = 'f = feval(fun,in{1}(:,:,j)'; + for i = 2:length(inputs) + estr = [estr ',in{' num2str(i) '}(:,:,j)']; + end + case 'handle' + end + + % optional inputs - fixed, non-optimized inputs + % -------------------------------------------------------------------- + + doverbose = 1; + genconverge = 20; + paramtype = 'continuous'; + dostochastic = 0; + seeds = {}; + + if ~isempty(varargin) + for i = 1:length(varargin) + + if isempty(varargin{i}) + % do nothing + + elseif strcmp(class(varargin{i}),'function_handle') + % this is the org. generation function + genfun = varargin{i}; + + elseif ischar(varargin{i}) + % command string + switch varargin{i} + case 'noverbose', doverbose = 0; + case 'genconverge', genconverge = varargin{i+1}; varargin{i+1} = []; + + case {'integer', 'discrete'}, paramtype = 'discrete'; + + case 'stochastic', dostochastic = 1; + + case {'seed', 'seeds'}, seeds = varargin{i+1}; varargin{i+1} = []; + + otherwise + disp('Warning: Unknown string input.') + end + + else + % this is fixed inputs + fixin = varargin{i}; + if ~iscell(fixin), tmp=fixin; clear fixin; fixin{1} = tmp; end + + for j = 1:length(fixin) + estr = [estr ',fixin{' num2str(j) '}']; + end + + end + + end + end + +% Check +if strcmp(paramtype, 'discrete') && dostochastic + error('Parameter type must be continuous in order to use stochastic noise option.'); +end + + + if doverbose + + fprintf(1,'___________________________________________________________\n') + switch evalmode + case 'string' + estr = [estr ');']; + disp(['Evaluation string is ' estr]) + case 'handle' + disp('Will evaluate this objective function: ') + disp(fun) + end + + disp(['GA thinks parameters are ' paramtype]) + ynstr = {'No' 'Yes'}; + fprintf('Status of stochastic noise addition on each generation: %s\n', ynstr{dostochastic + 1}); + fprintf('Will consider GA converged after %3.0f generations with no change\n', genconverge); + + fprintf(1,'___________________________________________________________\n') + end + + % -------------------------------------------------------------------- + % * create start state + % -------------------------------------------------------------------- + % create start state for fixed inputs + + for i = 1:length(inputs) + + if exist('genfun','var') + % we have a custom organism-generation function handle + if doverbose, disp('Generating starting values with custom (user input) organism generation function.'); end + + for j = 1:gensize + in{i}(:,:,j) = genfun(); + end + elseif size(inputs{i},3) > 1 + % we have a range of min/max values entered. create param + % values that span range. + if doverbose, disp('Creating starting values that evenly span parameter space.'); end + + [in{i},gensize] = sobol_start_state(inputs{i},gensize,doverbose); + + % we need these to limit noise later + mininputs = repmat(inputs{1}(:,:,1), [1 1 gensize]); + maxinputs = repmat(inputs{1}(:,:,2), [1 1 gensize]); + + + else + % we have only single starting value; use total range to create + % random start state + if doverbose, disp('Generating starting values with random parameter values.'); end + + in{i} = random_start_state(inputs{i},gensize,paramtype); + + + + end + + end + + % add custom seeds - tor added June 2010 + % -------------------------------------------------------------------- + if exist('seeds', 'var') && ~isempty(seeds) + + for i = 1:length(seeds) + in{1}(:, :, end+1) = seeds{i}; + end + + gensize = size(in{1}, 3); + + end + + % -------------------------------------------------------------------- + % * set up info for adding randomness in case we get stuck later + % -------------------------------------------------------------------- + % matrix of multiplier values for each param in first cell to add noise + % (for continuous parameter spaces only) + % or some needed info for discrete re-randomization + if strcmp(paramtype, 'continuous') + r = range(cat(3,in{:}),3); noisestd = .1 .* r; + noisestd = repmat(noisestd,[1 1 size(in{1},3)]); + + elseif strcmp(paramtype, 'discrete') + [m, n] = size(inputs{1}); + possiblevals = unique(inputs{1}); % should enter all possible values in input matrix + nvals = length(possiblevals); + end + + % -------------------------------------------------------------------- + % * iterate + % -------------------------------------------------------------------- + + if doverbose + f1 = create_figure('Fitness by generation'); set(gcf,'Color','w'); hold on; title('Max fitness'),xlabel('Generation') + + f2 = create_figure('Params by generation'); set(gcf,'Color','w'); hold on; title('Parameter estimates'),xlabel('Generation') + + tmp = in{1}(:,:,1); tmp = tmp(:); + all_bp = NaN .* zeros(numgen, length(tmp)); + end + + beff = NaN .* zeros(1,numgen); + gentime = NaN .* zeros(1,numgen); + isconverged = 0; + + fit = NaN .* zeros(gensize,numgen); + + + + + for i = 1:numgen + + t1 = clock; + + if doverbose + str = sprintf('Generation: %3.0f ',i); fprintf(1,str); + str = sprintf('Eval. fitness '); fprintf(1,str); + end + + for j = 1:gensize + + % -------------------------------------------------------------------- + % * make models + % -------------------------------------------------------------------- + + % -------------------------------------------------------------------- + % * test models + % -------------------------------------------------------------------- + switch evalmode + case 'string' + % if using function eval string + eval(estr) % e.g., f = fun(in{1}(:,:,j),fixin{1},fixin{2},fixin{3}); + fit(j,i) = f; % fitness of each model in each generation + case 'handle' + fit(j,i) = fun(in{1}(:,:,j)); % in is matrix of params for all organisms, with 3rd-D, j, indexing org + end + + + end + + if doverbose + erase_string(str); + str = sprintf('Crossover '); fprintf(1,str); + end + + % -------------------------------------------------------------------- + % * save best and crossover + % -------------------------------------------------------------------- + eff = fit(:,i)'; + + % run crossover for each separate input matrix to be recombined + % best_params contains best parameters of this generation + + for j = 1:length(inputs) + % replace in with crossover; b = index of best before xover + % best_params is best of this generation + % index of best after xover is 1 (hard-coded) + % dostochastic indicates whether (some) children have some random + % noise added to parent values + + % Note: inputs are shuffled after this point, and eff no longer + % applies. best input is saved in position 1, and in + % best_params{j} + [in{j}, b, best_params{j}] = xover(in{j},eff); + end + + % -------------------------------------------------------------------- + % * add noise to perturb if needed + % -------------------------------------------------------------------- + + if dostochastic + % We've asked to add random noise to new organisms on every + % generation. + first_rand = ceil(gensize ./ 2); + in{1}(:,:,first_rand:end) = in{1}(:,:,first_rand:end) + noisestd(:,:,first_rand:end) .* randn(size(in{1}(:,:,first_rand:end))); + + % adaptively shrink noise + r = range(cat(3,in{:}),3); noisestd = .1 .* r; + noisestd = repmat(noisestd,[1 1 size(in{1},3)]); + + + elseif b == 1 && ~exist('genfun', 'var') % then we do not have a custom input function, so do noise stuff + % add noise to inputs that are not best (1), if best = 1 (best of + % last gen also, no change) + % only works & is appropriate if param values are continuous (cont. + % underlying space), which may not be the case with custom obj. + % function + + switch paramtype + case 'continuous' + + in{1}(:,:,2:end) = in{1}(:,:,2:end) + noisestd(:,:,2:end) .* randn(size(in{1}(:,:,2:end))); + + if exist('mininputs', 'var') + % we have a range of min/max values entered. do not exceed max + % range + wh = in{1} < mininputs; in{1}(wh) = mininputs(wh); + wh = in{1} > maxinputs; in{1}(wh) = maxinputs(wh); + + end + + case 'discrete' + % get new start vectors for 50% + + wh = randperm(gensize); + wh = wh(1 : round(gensize ./ 2)); + wh(wh == 1) = []; + for j = wh + in{1}(:,:,j) = get_discrete_start_params(possiblevals, nvals, m, n); + end + + otherwise + error('Unknown parameter input type.'); + + end + + end + + % -------------------------------------------------------------------- + % * print output, if requested + % -------------------------------------------------------------------- + gentime(i) = etime(clock,t1); + + if doverbose + erase_string(str); + str = sprintf('Time: %3.0f ',etime(clock,t1)); fprintf(1,str); + end + + % use original eff vector to get best, in case fitness is stochastic + meff = eff(eff == max(eff)); meff = meff(1); + beff(i) = meff; + + if doverbose + fprintf(1,'Best: # %3.0f, Fitness: %3.2f',b,meff) + figure(f1); plot(beff,'k','Linewidth',2); drawnow + + mybp = best_params{:}; + if size(mybp, 2) < length(mybp), mybp = mybp'; end + all_bp(i, :) = mybp; + + figure(f2); cla; plot(all_bp); drawnow + + + if length(mybp) < 10 + fprintf(1,' Best params: ') + fprintf(1,'%3.2f ', mybp); + end + + if dostochastic && (b == 1 && ~exist('genfun', 'var')) % changed 2010 to fix bug with no-stochastic option + fprintf(' Noise added to param estimates with stdev %3.3f', noisestd(1)); + end + + fprintf('\n') + + end + + % -------------------------------------------------------------------- + % * Check for convergence + % -------------------------------------------------------------------- + % two criteria: no efficiency above median efficiency, and no change in the last genconverge generations + + if i > genconverge + last_gens = beff(i-genconverge+1:i); + unique_in_last_gens = length(unique(last_gens)); + % if the above is one, there has been no change in last n + % generations + else + unique_in_last_gens = Inf; + end + + if ~(any(eff > median(eff))) || unique_in_last_gens == 1 + isconverged = 1; + if doverbose, disp(['System converged at generation ' num2str(i)]), end + + beff(i+1:end) = []; + gentime(i+1:end) = []; + fit(:,i+1:end) = []; + + break + end + + end % End loop through generations + + if doverbose + % final report + ynstr = {'No' 'Yes'}; + fprintf(1,'\nGA Finished\n') + fprintf(1,'___________________________________________________________\n') + fprintf(1,'Generations: %3.0f \nOrganisms per generation: %3.0f\n',numgen,gensize); + fprintf(1,'\nInitial fitness (average): %3.4f \nFinal fitness: %3.4f\n',mean(fit(:,1)),beff(end)); + fprintf(1,'\nConverged: %s \n',ynstr{isconverged+1}); + + fprintf(1,'\nAverage time per iteration: %3.0f\n',mean(gentime)); + fprintf(1,'Average time per organism: %3.0f\n',mean(gentime)./gensize); + fprintf(1,'Total time: %3.0f\n',etime(clock,t0)); + fprintf(1,'___________________________________________________________\n') + end + + return + + + + + + % -------------------------------------------------------------------- + % * get random start state of gensize organisms + % -------------------------------------------------------------------- + function in = random_start_state(inputs,gensize,paramtype) + % determine range + r = [min(inputs(:)) max(inputs(:))]; + + [m, n] = size(inputs); + + if strcmp(paramtype, 'discrete') + possiblevals = unique(inputs); % should enter all possible values in input matrix + nvals = length(possiblevals); + end + + % create in variable = cells are inputs, columns are param sets, rows + % params within sets. + % first one is always the input you put in! + in(:,:,1) = inputs; % + randn(size(inputs)) .* std(inputs(:)); + + % rest of the population + + for j = 2:round(gensize./.8) + + switch paramtype + case 'continuous' + % 80% is random within 2*range of inputs + tmp = rand(m,n); tmp = tmp.*r(2).*2 + r(1); + + case 'discrete' + tmp = get_discrete_start_params(possiblevals, nvals, m, n); + otherwise + error('Unknown parameter distribution type'); + end + + in(:,:,j) = tmp; + end + + % j = 3rd dim = organism + + for j = round(gensize./.8):gensize + switch paramtype + case 'continuous' + % 20% is input + noise + tmp = randn(size(inputs)) .* std(inputs(:)) + inputs; + + case 'discrete' + tmp = get_discrete_start_params(possiblevals, nvals, m, n); + end + + in(:,:,j) = tmp; + end + + return + + function tmp = get_discrete_start_params(possiblevals, nvals, m, n) + tmp = zeros(m, n); + for row = 1:m + for col = 1:n + % discrete value from possible set + randp = randperm(nvals); + tmp(row, col) = possiblevals(randp(1)); + end + end + + return + + % -------------------------------------------------------------------- + % * get start state of gensize organisms that evenly spans param space + % -------------------------------------------------------------------- + function [in,gensize] = sobol_start_state(inputs,gensize,doverbose) + + rows = size(inputs,1); + cols = size(inputs,2); + + n = numel(inputs(:,:,1)); % n params, equals n dimensions + + npoints = ceil(gensize .^ (1./n)); % points in each dimension + + lowvals = inputs(:,:,1); + hivals = inputs(:,:,2); + + str = 'combvals = combvec('; + + for i = 1:n + % get start values for this parameter + vals{i} = linspace(lowvals(i), hivals(i),npoints); + + if i > 1, str = [str ', ']; end + str = [str 'vals{' num2str(i) '}']; + end + str = [str ');']; + + vals; + % create combvec; each col is an organism, each row a + % parameter + combvals = []; + eval(str) + %%gensize = size(combvals,2); % new gen size; won't work for > 1 + %%inputs cell + + if doverbose + fprintf(1,'Creating start state that spans the input range of param values.\n'); + fprintf(1,'Params: %3.0f, points in range for each param: %3.0f\n',n,npoints); + fprintf(1,'nearest ideal generation size is %3.0f; Actual gensize is %3.0f\n',size(combvals,2),gensize); + end + + combvals = combvals(:,1:gensize); + + % reshape combvec to format of input + + for j = 1:gensize + + thisorg = reshape(combvals(:,j),rows,cols); + in(:,:,j) = thisorg; + + end + + + return + + + + + % -------------------------------------------------------------------- + % * get best half, crossover to fill in lists + % -------------------------------------------------------------------- +function [newvec,b,best_params] = xover(paramvec,eff, paramtype) + % + % paramvec is 3-D matrix, 3rd dim is realization (organism) + % columns are sets of params to be crossed over. + % + % b is index of best param vec - may have random noise, etc. added in + % newvec + % best_params is original best param vec, stored in newvec(:,:,1) + + % best one before xover + b = find(eff == max(eff)); b = b(1); best_params = paramvec(:,:,b); + + + w = find(eff > median(eff)); + + % we can only do crossover if not all the designs are the same + % --------------------------------------------------- + if isempty(w) + + warning('Extremely homogenous sample!') + % add a little random noise to efficiency - 1% of var of efficiency + eff = eff + randn(1,length(eff)) .* .01 * mean(eff); + + w = find(eff > median(eff)); + + if isempty(w) + w = 1:round(size(eff,2)./2); + end + + end + + % save best half + % --------------------------------------------------- + newvec = paramvec(:,:,w); + [nparams,dummy,last] = size(newvec); % save size for adding random variation to existing + + + % % CODE commented out for adding random noise right now. + % % n = length(newvec(:)); + % % n = round(n/10); % 10% - to add random noise + + % number of crossover points + nxover = max(1,round(nparams ./ 50)); % 5; + + % fill in 2nd half with crossovers of first half + % --------------------------------------------------- + n_to_fill = size(paramvec,3) - last; + + for v = 1:n_to_fill + + % choose two random integers within 1st half + w = ceil(rand(1,2) * last); + + babyv = []; + + for i = 1:size(newvec,2) % for each set of parameters (columns) + + %babyv(:,i) = rcomb(newvec(:,i,w(1)),newvec(:,i,w(2))); + + babyv(:,i) = rcomb_multi(newvec(:,i,w(1)),newvec(:,i,w(2)),nparams,nxover); + end + + % add to others + newvec(:,:,last+v) = babyv; + + + end + + % add random variation to existing best half + % % % wh = round(rand(1,n) .* n); + % % % wh(wh < 1) = 1; wh(wh > n) = n; + % % % + % % % nv = newvec(:,:,1:last); + % % % nv(wh) = nv(wh) + randn(size(wh)); + % % % newvec = cat(3,nv,newvec(:,:,last+1:end)); + + newvec(:,:,1) = best_params; % re-insert best one + + + return + + + % -------------------------------------------------------------------- + % * crossover + % -------------------------------------------------------------------- +function c = rcomb(a,b) + % combines 2 vectors of numbers at a random crossover point + + w = ceil(rand * (length(a) - 1)); + c = a(1:w); + c = [c; b(w+1:end)]; + + return + +function c = rcomb_multi(a,b,n,nxover) + + % % n = length(a); + % % nxover = max(1,round(n ./ 50)); % 5; % number of crossover points + st = randperm(n); + + % divide the vectors up into chunks at random points + st = [1 sort(st(1:nxover)) n]; + en = (st(2:end))-1; st = st(1:end-1); en(end) = n; + + c = a; + for i = 1:2:length(st) + c(st(i):en(i)) = b(st(i):en(i)); + end + + % example: + % a = (1:100)'; + % b = 1000+a; + % nxover = 10; + % ...run code... + % figure;plot(c) + + return + + +function erase_string(str1) + fprintf(1,repmat('\b',1,length(str1))); % erase string + return + + + diff --git a/Misc_utilities/zeroinsert.m b/Misc_utilities/zeroinsert.m new file mode 100644 index 00000000..86c23899 --- /dev/null +++ b/Misc_utilities/zeroinsert.m @@ -0,0 +1,45 @@ +function yout = zeroinsert(wasbad, y) +% yout = zeroinsert(wasbad, y) +% +% Re-insert removed CASES (rows) and fill with zeros +% wasbad is indicator for removed cases, of size size(yout). y is data. +% +% if you enter y', inserts VARIABLES (cols). here, pass in v x n matrix, y' +% to fill empty/removed vars + +% See nanremove.m and naninsert.m + +wh = find(wasbad); + +if isempty(wh), yout = y; return, end + +if length(wh) + size(y, 1) ~= length(wasbad) + disp('Illegal removed-cases vector. length must equal size(y, 1) + # left-out cases.'); + fprintf('Left out = %3.0f, size(y, 1) = %3.0f, sum = %3.0f, length(wasbad) = %3.0f', length(wh), size(y, 1), length(wh)+size(y, 1), length(wasbad)); + error('Quitting') +end + +yout = zeros(length(wasbad), size(y, 2)); + +yout(1:wh(1) - 1, :) = y(1:wh(1) - 1, :); + +for i = 2:length(wh) + ystart = wh(i-1) + 2 - i; % NaN index value - num previous removed; wh(i-1) + 1 - i + 1; + yend = wh(i) - i; % wh(i) - 1 - i + 1 + + if yend > size(y, 1) + error('Illegal value of %3.0f data index, which exceeds data rows of %3.0f. Bad removed-cases vector?', yend, size(y, 1)); + end + + yout(wh(i-1) + 1 : wh(i) - 1, :) = y(ystart : yend, :); +end +% last segment +i = i+1; +if isempty(i), i = 2; end +ystart = wh(end) + 2 - i; + +if ystart <= size(y, 1) + yout(wh(end)+1:end, :) = y(ystart:size(y, 1), :); +end + +end diff --git a/Model_building_tools/canlab_spm_first_level_spec.m b/Model_building_tools/canlab_spm_first_level_spec.m new file mode 100644 index 00000000..76b6cb93 --- /dev/null +++ b/Model_building_tools/canlab_spm_first_level_spec.m @@ -0,0 +1,75 @@ +bf = SPM.xBF.bf; + +V = SPM.xBF.Volterra; +U = SPM.Sess(s).U; +v = length(U); + +Uname = U(i).name(1); +catch + str = sprintf('name for condition/trial %d ?',i); + + % Covariates: spm_fMRI_design 285 + %---------------------------------------------------------- + C = SPM.Sess(s).C.C; + Cname = SPM.Sess(s).C.name; + + + % Onsets + % spm_get_onsets.m + %---------------------------------------------------------- + + + ons = U(i).ons; + ons = ons(:); + catch + ons = []; + end + if isempty(ons) + str = ['vector of onsets - ' Uname{1}]; + ons = spm_input(str,4,'r',' ',[Inf 1]); + U(i).ons = ons(:); + end + + dur = U(i).dur; + + xP = U(i).P; + Pname = xP(1).name; + + switch Pname + + case 'none' + %---------------------------------------------------------- + xP.name = 'none'; + xP.h = 0; + + end + + % Parametric modulators + %---------------------------------------------------------- + + Pname = {'none','time','other'}; + Pname = spm_input('parametric modulation',6,'b',Pname); + + case 'other' + %---------------------------------------------------------- + str = ['# parameters (' Uname{1} ')']; + for q = 1:spm_input(str,7,'n1',1); + + % get names and parametric variates + %------------------------------------------------------ + str = sprintf('parameter %d name',q); + Pname = spm_input(str,7,'s'); + P = spm_input(Pname,7,'r',[],[length(ons),1]); + + % order of polynomial expansion h + %------------------------------------------------------ + h = spm_input('polynomial order',8,'n1',1); + + % sub-indices and inputs + %------------------------------------------------------ + xP(q).name = Pname; + xP(q).P = P(:); + xP(q).h = h; + + end + \ No newline at end of file diff --git a/Model_building_tools/comp_model.m b/Model_building_tools/comp_model.m new file mode 100644 index 00000000..6219052b --- /dev/null +++ b/Model_building_tools/comp_model.m @@ -0,0 +1,605 @@ +classdef comp_model < design_matrix + + % comp_model: data class for creating a computational model + % + % Computational Model Class: comp_model + % + %-------------------------------------------------------------------------- + % This object is used to fit a computational model to a multi-subject + % dataset. The object uses the design_matrix() class to for the data set + % and has additional fields for the model and parameters for the model + % fitting procedure such as the parameter constraints, number of + % iterations, and type of estimation (e.g., maximum likelihood or least + % squares). + % + %-------------------------------------------------------------------------- + % Inputs: + % --------------------------------------------------------------------- + % dat : M x N numeric matrix containing Observations and Variables + % + % varname : Cell array containing variable names. Must + % match number of column in data matrix + % model : name of model file (must be on matlab path) + % + %-------------------------------------------------------------------------- + % Current Methods for comp_model (inherits from design_matrix class too) + %-------------------------------------------------------------------------- + % + % avg_aic : display average AIC value + % avg_bic : display average BIC value + % avg_params : display average parameter estimates + % comp_model : class constructor + % fit_model : estimate parameters using model + % get_aic : extract all subject's AIC values + % get_bic : extract all subject's BIC values + % get_params : extract all subject's estimated + % parameters + % plot : plot average model predictions across subjects + % summary : display summary table for model + % save : save object as .mat file + % write_tables : write out parameter estimates and trial-to-trial + % predictions to csv data frame. + % + %-------------------------------------------------------------------------- + % Examples: + % --------------------------------------------------------------------- + % m1 = comp_model([ones(10,1), (1:10)', (1:10).^2'],{'Intercept','X','X2'},'Linear_Model') + % + % Also see CompModel_Tutorial.m in Examples + % ------------------------------------------------------------------------- + % Author and copyright information: + % ------------------------------------------------------------------------- + % Copyright (C) 2014 Luke Chang + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + % ------------------------------------------------------------------------- + + properties + % inherits properties from design_matrix + model = []; + param_min = []; + param_max = []; + nStart = []; + esttype = []; + params = []; + trial = []; + end + + methods + function obj = comp_model(dat, varname, model, varargin) + class constructor % Initialize instance of comp_model + + if(nargin > 2) + + %Add Data matrix + try + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + if(length(varname) ~= size(dat,2) || ~iscell(varname)); + error('Make sure the number of variable names corresponds to number of data columns.') + end + obj.dat = dat; + obj.varname = varname; + catch err + error('Make sure input variable names are in a cell array with length equal to number of data columns and data is a matrix.') + end + + %Check if model is a matlab function on path + testpath = exist(model); + if testpath ~= 2 + error(['Make sure model : ' model ' is on your matlab path.']) + else + obj.model = model; + end + + %Add variable names + obj.varname = varname; + + elseif(nargin > 1) + try + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + if(length(varname) ~= size(dat,2) || ~iscell(varname)); + error('Make sure the number of variable names corresponds to number of data columns.') + end + obj.dat = dat; + obj.varname = varname; + catch err + error('Make sure input variable names are in a cell array with length equal to number of data columns and data is a matrix.') + end + obj.varname = varname; + elseif(nargin > 0) + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + obj.dat = dat; + else % if nothing initialize empty object + return + end + + % Parse input + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('param_min',varargin{varg}) + obj.param_min = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('param_max',varargin{varg}) + obj.param_max = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('nStart',varargin{varg}) + obj.nStart = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('esttype',varargin{varg}) + obj.esttype = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('params',varargin{varg}) + obj.params = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('trial',varargin{varg}) + obj.trial = varargin{varg + 1}; + varargin{varg} = []; varargin{varg + 1} = []; + end + end + end + end + + function obj = save(obj,varargin) + + % obj = save(obj) + % + % ------------------------------------------------------------------------- + % This function saves comp_model class as a .mat file to fname, + % or user specified path. fullfile(fpath,obj.model) + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % fpath Specify file name path otherwise uses obj.fname + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % lin_model.save('~/MATLAB') + % + % ------------------------------------------------------------------------- + % Author and copyright information: + % ------------------------------------------------------------------------- + % Copyright (C) 2014 Luke Chang + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + % ------------------------------------------------------------------------- + + if nargin > 1 %use supplied file name + if ischar(varargin{1}) + save(fullfile(varargin{1},[obj.model '.mat']), 'obj') + end + + elseif ~isempty(obj.fname) %use obj.fname + save(obj.fname, 'obj') + else + save([obj.model '.mat'], 'obj') + end + end + + function obj = write_tables(obj, varargin) + + % obj = write_tables(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function writes out a separate table for obj.params and + % obj.trial to csv file. File name will be fullfile(fpath, [obj.model '_Params.csv']) + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % fpath Specify file name path otherwise uses obj.fname + % or current working directory path + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % write_tables(lin_model) + % + % lin_model.write_tables('~/MATLAB') + % + % ------------------------------------------------------------------------- + % Author and copyright information: + % ------------------------------------------------------------------------- + % Copyright (C) 2014 Luke Chang + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + % ------------------------------------------------------------------------- + + if nargin > 1 %use supplied file name + if ischar(varargin{1}) + dlmwrite(fullfile(varargin{1},[obj.model '_Params.csv']), obj.params, 'delimiter',',') %Params + dlmwrite(fullfile(varargin{1},[obj.model '_Trial.csv']), obj.trial, 'delimiter',',') %TrialData + end + + elseif ~isempty(obj.fname) %use obj.fname + dlmwrite(fullfile(obj.fname,[obj.model '_Params.csv']), obj.params, 'delimiter',',') %Params + dlmwrite(fullfile(obj.fname,[obj.model '_Trial.csv']), obj.trial, 'delimiter',',') %TrialData + else + dlmwrite([obj.model '_Params.csv'], obj.params, 'delimiter',',') %Params + dlmwrite([obj.model '_Trial.csv'], obj.trial, 'delimiter',',') %TrialData + end + end + + function obj = fit_model(obj, varargin) + + % model_output = fit_model(obj, varargin) + % + % ------------------------------------------------------------------------- + % This function will fit a model (model) using fmincon to a dataset (data) + % multiple times with random start values (nStart) with the parameters being + % constrained to a lower bound (param_min) and upper bound (param_max). + % Estimates a separate parameter to each subject (indicated in 1st column + % of dataset). Requires some helper functions from my github repository + % (https://github.com/ljchang/toolbox/tree/master/Matlab). Clone this + % repository and add paths to Matlab. Requires that the model be a + % named function and that it can parse the input data. + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % show_subject Displays Subject ID for every iteration. Helpful for + % debugging. Off by default. + % + % persistent_fmincon Continues running fmincon despite error. Good for + % salvaging data if a model is having difficulty converging + % + % ------------------------------------------------------------------------- + % OUTPUTS: + % ------------------------------------------------------------------------- + % obj com_model class instance containing all of the trial-trial data and + % Parameter estimates + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % lin_model = fit_model(lin_model) + % lin_model = fit_model(lin_model, 'persistent_fmincon', 'show_subject') + % + % ------------------------------------------------------------------------- + % Author and copyright information: + % ------------------------------------------------------------------------- + % Copyright (C) 2014 Luke Chang + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + % ------------------------------------------------------------------------- + + %-------------------------------------------------------------------------- + % Setup + %-------------------------------------------------------------------------- + + global trialout + + % Defaults + showSubject = 0; + persist = 0; + + % Parse Inputs + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi(varargin(varg),'show_subject') + showSubject = 1; + varargin(varg) = []; + elseif strcmpi(varargin,'persistent_fmincon') + persist = 1; + varargin(varg) = []; + end + end + end + + %-------------------------------------------------------------------------- + % Run Model separately for every subject + %-------------------------------------------------------------------------- + + Subjects = unique(obj.dat(:,1)); + allout = []; + for s = 1:length(Subjects) + if showSubject; display(['Subject ', num2str(Subjects(s))]); end %Show Subject ID for every iteration if requested + + sdat = obj.dat(obj.dat(:,1)==Subjects(s),:); %Select subject's data + + xpar = zeros(obj.nStart,length(obj.param_min)); fval = zeros(obj.nStart,1); exitflag = zeros(obj.nStart,1); out = {obj.nStart,1}; %Initialize values to workspace + + for iter = 1:obj.nStart %Loop through multiple iterations of nStart + + %generate random initial starting values for free parameters + for ii = 1:length(obj.param_min) + ipar(ii) = random('Uniform',obj.param_min(ii),obj.param_max(ii),1,1); + end + + if ~persist + eval(['[xpar(iter,1:length(obj.param_min)) fval(iter) exitflag(iter) out{iter}]=fmincon(@' obj.model ', ipar, [], [], [], [], obj.param_min, obj.param_max, [], [], sdat);']) + else + try + eval(['[xpar(iter,1:length(obj.param_min)) fval(iter) exitflag(iter) out{iter}]=fmincon(@' obj.model ', ipar, [], [], [], [], obj.param_min, obj.param_max, [], [], sdat);']) + catch + display('Fmincon Could Not Converge. Skipping Iteration') + xpar(iter,1:length(obj.param_min)) = nan(1,length(obj.param_min)); + fval(iter) = nan; + exitflag(iter) = nan; + out{iter} = nan; + end + end + end + + %Find Best fitting parameter if running multiple starting parameters + [xParMin, fvalMin] = FindParamMin(xpar, fval); + + %output parameters + params(s,1) = Subjects(s); + params(s,2:length(xParMin) + 1) = xParMin; + params(s,length(xParMin) + 2) = fvalMin; + if obj.esttype == 'LLE' + params(s,length(xParMin) + 3) = penalizedmodelfit(-fvalMin, size(sdat,1), length(xParMin), 'type', obj.esttype, 'metric', 'AIC'); + params(s,length(xParMin) + 4) = penalizedmodelfit(-fvalMin, size(sdat,1), length(xParMin), 'type', obj.esttype, 'metric', 'BIC'); + elseif obj.esttype =='SSE' + params(s,length(xParMin) + 3) = penalizedmodelfit(fvalMin, size(sdat,1), length(xParMin), 'type', obj.esttype, 'metric', 'AIC'); + params(s,length(xParMin) + 4) = penalizedmodelfit(fvalMin, size(sdat,1), length(xParMin), 'type', obj.esttype, 'metric', 'BIC'); + end + + %aggregate trials + allout = [allout; trialout]; + end + + %-------------------------------------------------------------------------- + % Collate Output + %-------------------------------------------------------------------------- + + obj.params = params; + obj.trial = allout; + + end %Function end + + function aic = avg_aic(obj) + % aic = avg_aic(obj) + % + % ------------------------------------------------------------------------- + % This function returns the average AIC values after using + % model_fit() + % ------------------------------------------------------------------------- + + aic = nanmean(obj.params(:,end - 1)); + + end + + function bic = avg_bic(obj) + % aic = avg_aic(obj) + % + % ------------------------------------------------------------------------- + % This function returns the average AIC values after using + % model_fit() + % ------------------------------------------------------------------------- + + bic = nanmean(obj.params(:,end)); + end + + function avgparams = avg_params(obj) + % avgparams = avg_params(obj) + % + % ------------------------------------------------------------------------- + % This function returns the average parameter estimates across + % subjects. Assumes that parameters start on the second column + % ------------------------------------------------------------------------- + + avgparams = nanmean(obj.params(:,2:length(obj.param_min))); + end + + function aic = get_aic(obj) + % aic = get_aic(obj) + % + % ------------------------------------------------------------------------- + % This function returns all subject's AIC values after using + % model_fit() + % ------------------------------------------------------------------------- + + aic = obj.params(:,end - 1); + + end + + function bic = get_bic(obj) + % bic = get_bic(obj) + % + % ------------------------------------------------------------------------- + % This function returns all subject's BIC values after using + % model_fit() + % ------------------------------------------------------------------------- + + bic = obj.params(:,end); + + end + + function params = get_params(obj) + % aic = get_params(obj) + % + % ------------------------------------------------------------------------- + % This function returns all subject's estimated parameter values after using + % model_fit() + % ------------------------------------------------------------------------- + + params = obj.params(:,2:length(obj.param_min + 1)); + + end + + function summary = summary(obj) + % summary = summary(obj) + % + % ------------------------------------------------------------------------- + % This function returns the average parameter estimates across + % subjects. Assumes that parameters start on the second + % column. Also returns average AIC, average BIC, average final + % minimized value, and number of subjects + % ------------------------------------------------------------------------- + + sprintf(['Summary of Model: ' obj.model ... + '\n-----------------------------------------' ... + '\nAverage Parameters:\t' num2str(nanmean(obj.params(:,2:length(obj.param_min)))) ... + '\nAverage AIC:\t\t' num2str(nanmean(obj.params(:,end - 1))) ... + '\nAverage BIC:\t\t' num2str(nanmean(obj.params(:,end))) ... + '\nAverage ' obj.esttype ':\t\t' num2str(nanmean(obj.params(:,end-3))) ... + '\nNumber of Subjects:\t' num2str(size(obj.params,1)) ... + '\n-----------------------------------------']) + + end + + function f1 = plot(obj, columns, varargin) + % f1 = plot(obj) + % + % ------------------------------------------------------------------------- + % This function will plot the predictions of a model averaged over subjects. + % + % ------------------------------------------------------------------------- + % INPUTS: + % ------------------------------------------------------------------------- + % columns Columns of obj.trial to plot + % + % ------------------------------------------------------------------------- + % OPTIONAL INPUTS: + % ------------------------------------------------------------------------- + % 'title' Followed by title of Plot + % + % 'xlabel' Followed by xlabel name + % + % 'ylabel' Followed by ylabel name + % + % 'xticklabel' Followed by cell array of xticklabel names + % + % 'legend' Followed by cell array of legend names + % + % ------------------------------------------------------------------------- + % OUTPUTS: + % ------------------------------------------------------------------------- + % f1 figure handle + % + % ------------------------------------------------------------------------- + % EXAMPLES: + % ------------------------------------------------------------------------- + % plot(lin_model, [3,4]) + % plot(lin_model, [3,4], 'title', 'Linear Model', 'xlabel','session', 'ylabel', 'Average BDI', 'legend', {'Predicted','Observed'}) + % + % ------------------------------------------------------------------------- + % Author and copyright information: + % ------------------------------------------------------------------------- + % Copyright (C) 2014 Luke Chang + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + % ------------------------------------------------------------------------- + + sub = unique(obj.trial(:,1)); + trial = unique(obj.trial(:,2)); + + if nargin < 2 + error('Please add a vector indicating which columns of trial to plot') + end + + counter = 1; + for c = columns + for t = 1:length(trial) + datmn(t,counter) = nanmean(obj.trial(obj.trial(:,2)==trial(t),c)); + datse(t,counter) = nanstd(obj.trial(obj.trial(:,2)==trial(t),c)) / sqrt(length(sub)); + end + counter = counter + 1; + end + + figure; + plot(datmn,'LineWidth',2) + + % Parse input for plot parameters + for varg = 1:length(varargin) + if ischar(varargin{varg}) + if strcmpi('title',varargin{varg}) + eval(['title(''' varargin{varg + 1} ''')']); + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('xlabel',varargin{varg}) + eval(['xlabel(''' varargin{varg + 1} ''')']); + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('ylabel',varargin{varg}) + eval(['ylabel(''' varargin{varg + 1} ''')']); + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('legend',varargin{varg}) + l = varargin{varg+1}; + str = 'legend({'; + for i = 1:length(l) + str = [str ' ''' l{i} '''']; + end + str = [str ' })']; + eval(str) + varargin{varg} = []; varargin{varg + 1} = []; + elseif strcmpi('xticklabel',varargin{varg}) + l = varargin{varg+1}; + str = 'set(gca,''XTickLabel'' , {'; + for i = 1:length(l) + str = [str ' ''' l{i} '''']; + end + str = [str ' })']; + eval(str) + varargin{varg} = []; varargin{varg + 1} = []; + end + end + end + + end %end plot function + + end %methods +end %class + diff --git a/Model_building_tools/design_matrix.m b/Model_building_tools/design_matrix.m new file mode 100644 index 00000000..b88cafe1 --- /dev/null +++ b/Model_building_tools/design_matrix.m @@ -0,0 +1,619 @@ +classdef design_matrix + % design_matrix: data class for creating a design matrix to be used with a linear model including fmri data. + % + % + % Inputs: + % --------------------------------------------------------------------- + % dat : M x N numeric matrix containing Observations and Variables + % If dat is a file name then will try to + % import data into design_matrix object. + % Make sure there is only text in header and + % not anywhere else. + % + % varname : Cell array containing variable names. Must + % match number of column in data matrix + % + % Examples: + % --------------------------------------------------------------------- + % DM = design_matrix([ones(10,1), (1:10)', (1:10).^2'],{'Intercept','X','X2'}) + % + % Original version: Copyright Luke Chang 2/2014 + + % Notes: + % Need to add these Methods: + % -create regressor from stim times + % -pca + + properties + dat = []; + varname = {}; + fname = ''; + end + + methods + function obj = design_matrix(dat, varname) + class constructor % Initialize instance of design_matrix + + if(nargin > 1) + try + if(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + end + if(length(varname) ~= size(dat,2) || ~iscell(varname)); + error('Make sure the number of variable names corresponds to number of data columns.') + end + obj.dat = dat; + obj.varname = varname; + catch err + error('Make sure input variable names are in a cell array with length equal to number of data columns and data is a matrix.') + end + obj.varname = varname; + + elseif(nargin > 0) + + %import data into design_matrix if dat is a filename + if ischar(dat) + ftest = exist(dat,'file'); %check if valid file + if ftest == 2 + data = importdata(dat); + obj.dat = data.data; + obj.varname = data.colheaders; + end + + elseif(~ismatrix(dat) || ~isnumeric(dat) || iscell(dat)) + error('Make sure input data is a matrix') + else + + obj.dat = dat; + end + + else % if nothing initialize empty object + return + end + end + + function names(obj) + % names(obj) + % + % List variable names for each regressor + display(obj.varname) + end + + function dim = size(obj, varargin) + % dim = size(obj, varargin) + % + % Return dimensions of design matrix + % Optional Input: Indicate Dimension(row = 1 or column = 2) + + if nargin > 1 + dim = size(obj.dat, varargin{1}); + else + dim = size(obj.dat); + end + end + + function plot(obj) + % plot(obj) + % + % Plot design matrix + imagesc(obj.dat) + end + + function save(obj, fname) + % save(obj, fname) + % + % Save Design Matrix to file + + save(fname, obj) + end + + function obj = addvariable(obj, x, varargin) + % obj = addvariable(obj, x, varargin) + % + % Add regressor to design matrix + % + % optional inputs + % ------------------------------------------------------------------- + % 'Name' : 'Name' followed by Variable name + % Default is 'newVx' + % + % 'Order' : 'Order' followed by where to insert new data columns location + % Default is end + + % Check Inputs + if size(x,1) ~= size(obj,1) + error('Make sure new variable column is the same length as design matrix') + end + + % Defaults + for i = 1:size(x,2) + newname{i} = ['newV' num2str(i)]; + end + isnewname = 0; + varorder = size(obj,2); %add to end + + % Parse inputs + % ------------------------------------------------------------------- + for varg = 1:length(varargin) + if ischar(varargin{varg}) + % reserved keywords + if strcmpi('name',varargin{varg}) + if length(varargin{varg + 1}) == size(x,2) + newname = varargin{varg + 1}; + isnewname = 1; + else + error('Make sure ''Name'' is follwed by a valid variable name') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + + if strcmpi('order',varargin{varg}) + if isnumeric(varargin{varg + 1}) && varargin{varg + 1} <= size(obj,2) + varorder = varargin{varg + 1}; + else + error('Make sure ''Order'' is follwed by a valid column number') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + end + end + % ------------------------------------------------------------------- + + % Update Name vector if not empty + if ~isempty(obj.varname) + if varorder == 1 %begin + obj.varname = [newname, obj.varname]; + elseif varorder == size(obj,2) %end + obj.varname = [obj.varname, newname]; + else %Somewhere inbetween + obj.varname = [obj.varname(1:varorder), newname, obj.varname(varorder + 1 : end)]; + end + end + + % Add new variables to design Matrix + if varorder == 1 %begin + obj.dat = [x, obj.dat]; + elseif varorder == size(obj,2) %end + obj.dat = [obj.dat, x]; + else %Somewhere inbetween + obj.dat = [obj.dat(:,1:varorder), x, obj.dat(:,varorder + 1 : end)]; + end + end + + function obj = zscore(obj, varargin) + % obj = zscore(obj, varargin) + % + % Standardize columns of design matrix + % + % optional inputs + % ------------------------------------------------------------------- + % 'center' : Only remove mean, don't standardize + + % Defaults + center = 0; %Only remove mean + + % optional inputs + % ------------------------------------------------------------------- + for varg = 1:length(varargin) + if ischar(varargin{varg}) + % reserved keywords + if strcmpi('center',varargin{varg}) + center=1; + varargin{varg} = {}; + end + end + end + % ------------------------------------------------------------------- + + if center + obj.dat = obj.dat - repmat(mean(obj.dat),size(obj,1),1); + else + obj.dat = zscore(obj.dat); + end + end + + function obj = removevariable(obj, x) + % obj = removevariable(obj, x) + % + % Remove columns from design_matrix + % + % Inputs + % ------------------------------------------------------------------- + % x : Input vector of columns to remove + + obj.dat(:,x) = []; + obj.varname(x) = []; + end + + function obj = addintercept(obj) + % obj = addintercept(obj) + % + % Add intercept to design matrix + + %Check if Intercept exists + if sum(strcmpi(obj.varname,'intercept')) > 0 + error('Intercept Name already included in obj.varname') + end + + %Check if any variable only includes ones + for i = 1:size(obj,2) + if sum(obj.dat(:,i)==1) == size(obj,1) + error('There is already a column of ones that resembles an intercept') + end + end + + obj.dat = [obj.dat, ones(size(obj,1),1)]; + obj.varname = [obj.varname, 'Intercept']; + end + + function obj = removeintercept(obj) + % obj = removeintercept(obj) + % + % Remove intercept from design matrix + + %Check if any variable only includes ones or if intercept is in varname + for i = 1:size(obj,2) + whereint(i) = sum(obj.dat(:,i)==1) == size(obj,1); + end + if sum(whereint) == 0 + error('There does not appear to be any column of ones that resembles an intercept') + elseif sum(strcmpi('intercept',obj.varname)) == 0 + error('Intercept Name is not included in obj.varname') + end + + % now remove intercept + obj.dat(:,whereint) = []; + obj.varname(whereint) = []; + end + + function vif = vif(obj, varargin) + % vif = vif(obj, varargin) + % + % Check for multicollinearity by getting variance inflation factors + % + % See original getvif.m in canlab repository - OptimizeDesign11/core_functions/getvif.m + % + % optional inputs + % ------------------------------------------------------------------- + % 'nointercept' : Remove intercept (turned off by default) + + % Defaults + noint = 0; + if strcmpi('nointercept',varargin) + noint = 1; + end + + %Remove intercept if asked + if noint + obj = removeintercept(obj); + end + + %Calculate VIF + for i = 1:size(obj.dat,2) + X = obj.dat; + y = X(:,i); + X(:,i) = []; + b = X\y; + fits = X * b; + rsquare = var(fits) / var(y); + + if rsquare == 1,rsquare = .9999999;end + + vif(i) = 1 / (1 - rsquare); + end + end + + function r = corr(obj) + % Calculate pairwise correlation of regressors in design_matrix + + r = corr(obj.dat); + end + + function obj = normalizedrank(obj, varargin) + % obj = normalizedrank(obj, varargin) + % + % Rank each regressor and normalize between [0,1] + % + % See normalizedrank.m for optional inputs + + obj.dat = normalizedrank(obj.dat, varargin); + end + + function obj = conv_hrf(obj, varargin) + % obj = conv_hrf(obj, varargin) + % + % Convolve each regressors with hemodynamic response function + % Uses spm_hrf.m + % + % optional inputs + % ------------------------------------------------------------------- + % 'tr' : Input TR to use for creating HRF + % (e.g., 'tr', 3) + % + % 'select' : Select Input vector of regressors to convolve + % (e.g., 'select', [2,4]) + % + % 'custom_hrf' : Use custom HRF (e.g., 'custom_hrf', [1.00, 0.59, 0.39, 0.27] + + % Defaults + include = (1:size(obj,2)); %Convolve entire Design Matrix by default + tr = 2; + + % Check if spm_hrf is on path + checkspm = which('spm_hrf.m'); + if isempty(checkspm), error('Make sure spm is in matlab path'); end + + % Parse inputs + % ------------------------------------------------------------------- + for varg = 1:length(varargin) + if ischar(varargin{varg}) + % reserved keywords + if strcmpi('tr',varargin{varg}) + if isnumeric(varargin{varg + 1}) + tr = varargin{varg + 1}; + crf = spm_hrf(tr); + else + error('Make sure ''tr'' is followed by valid number') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + + if strcmpi('select',varargin{varg}) + if isnumeric(varargin{varg + 1}) && all(varargin{varg + 1} <= size(obj,2)) + include = varargin{varg + 1}; + else + error('Make sure ''select'' is followed by a valid column number') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + + if strcmpi('custom_hrf',varargin{varg}) + if isnumeric(varargin{varg + 1}) && all(varargin{varg + 1} <= size(obj,2)) + crf = varargin{varg + 1}; + else + error('Make sure ''custom_hrf'' is followed by a vector of a canonical response function') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + end + end + % ------------------------------------------------------------------- + + %Convolution of task + for i = 1:length(include) + convdat = conv(obj.dat(:,include(i)),crf); + + %Cut off extra data from convolution + obj.dat(:,include(i)) = convdat(1:size(obj,1)); + end + end + + function obj = hpfilter(obj, varargin) + % obj = hpfilter(obj, varargin) + % + % Add High pass filter design matrix using spm's discrete + % cosine Transform (spm_filter.m) + % + % optional inputs + % ------------------------------------------------------------------- + % 'tr' : Input TR to use for creating HRF + % (e.g., 'tr', 3) Default = 2; + % + % 'duration' : Duration of high pass filter in seconds + % (e.g., 'duration', 100) Default = 180; + + % Defaults + tr = 2; + filterlength = 180; + + % Check if spm_hrf is on path + checkspm = which('spm_filter.m'); + if isempty(checkspm), error('Make sure spm is in matlab path'); end + + % Parse inputs + % ------------------------------------------------------------------- + for varg = 1:length(varargin) + if ischar(varargin{varg}) + % reserved keywords + if strcmpi('tr',varargin{varg}) + if isnumeric(varargin{varg + 1}) + tr = varargin{varg + 1}; + else + error('Make sure ''tr'' is followed by valid number') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + if strcmpi('duration',varargin{varg}) + if isnumeric(varargin{varg + 1}) + filterlength = varargin{varg + 1}; + else + error('Make sure ''duration'' is followed by valid number') + end + varargin{varg} = {}; varargin{varg + 1} = {}; + end + end + end + % ------------------------------------------------------------------- + + %create high pass filter + K.RT = tr; + K.row = 1:size(obj,1); + K.HParam = filterlength; + nK = spm_filter(K); + if isempty(nK.X0), error('Check if filter duration is too long'); end + + %Add filter to design_matrix + obj.dat = [obj.dat, nK.X0]; + + %Add variable names + for i = 1:size(nK.X0,2) + filtname{i} = ['hpfilter' num2str(i)]; + end + obj.varname = [obj.varname, filtname]; + end + + function [B,BINT,R,RINT,STATS] = regress(Y, obj) + % [B,BINT,R,RINT,STATS] = regress(Y, obj) + % + % Regress design matrix on vector Y + % Uses matlab's regress function + + [B,BINT,R,RINT,STATS] = regress(Y, obj.dat); + end + + function obj = onsettimes(obj, onset, names, tr, timing ) + % obj = onsettimes(obj, onset, names, tr, timing ) + % + %Create stimulus regressor from onset times + % + % Inputs + % ------------------------------------------------------------------- + % onset : Input cell array of onset times for each + % regressor in FSL's 3 column format (e.g., onset in sec, duration in sec, weight). + % + % names : Cell array of variable names corresponding + % to each onset cell (e.g., {'BlueOn','RedOn'}) + % + % tr : Repetition time (e.g., 2) + % + % timing : Timing converstion from onset array to design matrix + % (e.g., 'sec2tr','tr2sec','sec2sec',or + % 'tr2tr'). Need to know which format each + % array is in. + + %Convert Onset Times Into Boxcar Regressors + r = zeros(size(obj,1),length(onset)); + for i = 1:length(onset) + for j = 1:size(onset{i},1) + switch timing + case 'sec2tr' + if floor(onset{i}(j,1)/tr) == 0 + r(1 : 1 + ceil(onset{i}(j,2)), i) = onset{i}(j,3); + else + r(floor(onset{i}(j,1) / tr) : floor(onset{i}(j,1) / tr) + ceil(onset{i}(j,2) / tr) - 1, i) = onset{i}(j,3); + end + case 'tr2sec' + r(floor(onset{i}(j,1) * tr) : floor(onset{i}(j,1) * tr) + ceil(onset{i}(j,2) * tr) - 1, i) = onset{i}(j,3); + case {'sec2sec', 'tr2tr'} + r(floor(onset{i}(j,1)) : floor(onset{i}(j,1)) + ceil(onset{i}(j,2)) - 1, i) = onset{i}(j,3); + end + end + end + obj.dat = [obj.dat, r]; + + %Add Variable names + obj.varname = [obj.varname, names]; + end + + function obj = write(obj, varargin) + % obj = write(obj, varargin) + % + % ------------------------------------------------------------------- + % write design_matrix object into csv file. Will use obj.fname + % or can specify optional name + % + % ------------------------------------------------------------------- + % Optional Inputs + % ------------------------------------------------------------------- + % fname : path and file name of csv file. + % ------------------------------------------------------------------- + + + if nargin > 1 %use supplied file name + if ischar(varargin{1}) + hdr = sprintf('%s,',obj.varname{:}); + hdr(end) = ''; + dlmwrite(varargin{1}, hdr,'') %Write Header 1st + dlmwrite(varargin{1}, obj.dat, 'delimiter',',','-append') %Append data + end + + elseif ~isempty(obj.fname) %use obj.fname + hdr = sprintf('%s,',obj.varname{:}); + hdr(end) = ''; + dlmwrite(obj.fname, hdr, ''); %Write Header 1st + dlmwrite(obj.fname, obj.dat, 'delimiter',',','-append') %Append data + else + error('Please supply valid file name with path to save.') + end + + end + + function c = horzcat(varargin) + % function c = horzcat(varargin) + % ------------------------------------------------------------------- + % Implements the horzcat ([a b]) operator on design_matrix objects across variables. + % Requires that each object has an equal number of rows + % ------------------------------------------------------------------- + % Examples: + % c = [dm1 dm2]; + % ------------------------------------------------------------------- + + %check if number of rows is the same + %check if varnames are the same + nrow = []; + for i = 1:nargin + nrow(i) = size(varargin{i},1); + end + for i = 1:nargin + for j = 1:nargin + if nrow(i)~=nrow(j) + error('objects have a different number of rows') + end + end + end + + dat = []; + varname = []; + for i = 1:nargin + %Check if design_matrix object + if ~isa(varargin{i}, 'design_matrix') + error('Input Data is not an design_matrix object') + end + + dat = [dat, varargin{i}.dat]; + varname = [varname, varargin{i}.varname]; + end + + c = varargin{1}; + c.dat = dat; + c.varname = varname; + end + + function c = vertcat(varargin) + % function c = vertcat(varargin) + % ------------------------------------------------------------------- + % Implements the vertcat ([a b]) operator on design_matrix objects across rows. + % Requires that each object has an equal number of columns and + % that each varname is identical + % ------------------------------------------------------------------- + % Examples: + % c = [dm1; dm2]; + % ------------------------------------------------------------------- + + %check if varnames are the same + varname = {}; + for i = 1:nargin + varname{i} = varargin{i}.varname; + end + for i = 1:nargin + for j = 1:nargin + if ~strcmpi(varname{i},varname{j}) + error('variable names do not match') + end + end + end + dat = []; + for i = 1:nargin + %Check if design_matrix object + if ~isa(varargin{i}, 'design_matrix') + error('Input Data is not an design_matrix object') + end + dat = [dat; varargin{i}.dat]; + end + + c = varargin{1}; + c.dat = dat; + end + + end %methods +end %class \ No newline at end of file diff --git a/Model_building_tools/fmri_spline_basis.m b/Model_building_tools/fmri_spline_basis.m new file mode 100644 index 00000000..d957949d --- /dev/null +++ b/Model_building_tools/fmri_spline_basis.m @@ -0,0 +1,108 @@ +function [xBF_hires, xBF] = fmri_spline_basis(TR, varargin) + +% xBF = spline_hrf_basis(TR, optional args) +% +% Inputs: +% TR : repetition time; sampling resolution of data +% 'plot' : optional: plot basis set +% 'nbasis' : optional: number of knot points +% 'order' : optional: order of spline model (# matched derivatives) +% 'length' : optional: length of window to model, in seconds +% +% Outputs: +% +% xBF.dt - time bin length {seconds} +% xBF.name - description of basis functions specified +% xBF.length - window length (seconds) +% xBF.order - order +% xBF.bf - Matrix of basis functions +% +% 32 second long spline basis set for fmri model +% +% xBF_hires: Sampled at high resolution, TR * 16 +% xBF: Sampled at TR +% +% Examples: +% [xBF_hires, xBF] = fmri_spline_basis(TR, varargin) +% [xBF_hires, xBF] = fmri_spline_basis(2, 'length', 12, 'nbasis', 3, 'order', 3, 'plot'); + +doplot = 0; +nBas = 8; % number of basis functions; higher = more spatial resolution. Must be at least k. +k = 4; % spline order; higher = spline fit curvature matched on more derivatives; 2 = linear; 3 = quadratic; 4 = cubic (default) +windowlen = 32; % in sec + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'plot', doplot = 1; + case 'nbasis', nBas = varargin{i+1}; + case 'order', k = varargin{i+1}; + case 'length', windowlen = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +n_elements = ceil(windowlen ./ TR); % how many elements to span with basis + + +bf = Bspline(1:n_elements,k,[ones(1,k-1) linspace(1,n_elements+0.1,nBas-1) (n_elements+0.1)*ones(1,k-1)]); + +xBF = struct('name', 'bspline_basis', 'dt', TR, 'bf', bf, 'length', windowlen, 'order', '8 bf, 4th order'); + +% high-res one + +n_elements = ceil(16 .* windowlen ./ TR); % how many elements to span with basis + +bf = Bspline(1:n_elements,k,[ones(1,k-1) linspace(1,n_elements+0.1,nBas-1) (n_elements+0.1)*ones(1,k-1)]); + +xBF_hires = struct('name', 'bspline_basis', 'dt', TR ./ 16, 'bf', bf, 'length', windowlen, 'nbasis', nBas, 'order', k); + +if doplot + + plot_inline1() + +end + + + function plot_inline1 + + create_figure('basis functions', 2, 2); + plot(bf, 'LineWidth', 2) + title('Basis set'); + + subplot(2, 2, 2); + title('Simulated true HRF') + hold on; + + h = spm_hrf(TR./16) ./ max(spm_hrf(TR./16)); + plot((1:length(h)) ./ 16, h, 'k', 'LineWidth', 2) + + subplot(2, 2, 3); + title('Example data (blue) and fits (red), hi-res') + hold on; + + h = h + .3 * noise_arp(length(h), [.5 .1]); + plot((1:length(h)) ./ 16, h) + + f = xBF_hires.bf * pinv(xBF_hires.bf) * h(1:size(xBF_hires.bf, 1)); + plot((1:length(f)) ./ 16, f, 'Color', [1 .5 0]) + + subplot(2, 2, 4); + title('Example data (blue) and fits (red), sampled at TR') + hold on; + + y_at_tr = h(1:16:end); + plot((1:length(y_at_tr)) - 1, y_at_tr, 'bo-', 'LineWidth', 2) + + f_at_tr = xBF.bf * pinv(xBF.bf) * h(1:16:size(xBF_hires.bf, 1)); + plot((1:length(f_at_tr)) - 1, f_at_tr, 'ro-', 'LineWidth', 2) + + end + +end % main fcn + \ No newline at end of file diff --git a/Model_building_tools/getPredictors.m b/Model_building_tools/getPredictors.m new file mode 100755 index 00000000..137373c1 --- /dev/null +++ b/Model_building_tools/getPredictors.m @@ -0,0 +1,282 @@ +function [model,delta] = getPredictors(stimList, HRF, varargin) +% function [model,delta] = getPredictors(stimList, HRF, varargin) +% +% Build predictors and delta functions, given a condition function or delta +% function and either a convolution matrix or vector. +% +% IMPORTANT: YOU MUST ADD THE INTERCEPT YOURSELF! +% +% stimList: condition function OR delta function (1/0 indicator) +% HRF: 1) hemodynamic response function +% 2) Basis set (columns) +% 3) or convolution matrix (columns +% are HRF), defined as: +% HRF = tril(toeplitz(hrf)); +% +% multiple column vectors for HRF are treated as basis functions! +% varargin for downsampleing: +% 'dsrate': takes every nth element of the design matrix +% 'dslen': the target number (length) you want to downsample to +% +% example: TR = 2, 16 samples per second in hi-res delta dhr +% X = getPredictors(dhr, hrf, 'dsrate', res*TR); +% X = getPredictors(dhr, hrf, 'dslen', len/(res*TR)); +% +%inputs: +% 1 a col. vector of stimulus conditions OR a delta function matrix +% 2 an HRF vector sampled at the frequency of the stimulus vector, OR +% a convolution matrix H (empty for default) +% 3 Optional: downsampling factor for final design (i.e., TR) +% 4 Optional: parametric modulator keyword and modulator values +% 'parametric_singleregressor' : Parametrically modulate onsets by +% modulator values, using single regressor with modulated amplitude +% Enter a cell array with modulator values for each event +% type, with a column vector (empty cell for no modulation) +% 'parametric_standard' : Parametrically modulate onsets by +% modulator values, using two regressors per event type - One +% to model the average response, and one for the +% mean-centered modulator values +% +%outputs: +% 1 a n x 2 matrix of regressors (cols) for each condition +% 2 a n x k delta matrix with onsets +% +% Tor Wager, last modified 2/22/04 to center predictors +% modified to optionally take H convolution matrix as input +% in place of HRF vector. See convmtx.m (Buracas,mseq toolbox) +% +% Modified 10/2011 by Tor to add parametric modulators +% +% stimList can be condition function e.g., [1 3 2 4 3 2 1]' or +% delta matrix (n x k), n samples and k conditions, e.g., [1 0 0 0 1 0 1]' +% +% Resampling: the default N in matlab resample has built-in antialiasing, +% but may not be good for fmri designs! The appropriate downsampling +% is expected to be res*TR (res is units of samples/s), but we use 0 +% because the model will depend on the analysis method used, and this is +% the most veridical approach. With N = 0, every ith sample is used, where +% i is the downsampling factor you input. Popular choices are 16*TR (for +% onsets2delta.m), using the SPM default res of 16. +% Delta is NOT resampled. +% +% example: TR = 2, 16 samples per second in hi-res delta dhr +% [tmp,d] = downsample_delta(dhr,16*2); X=getPredictors(d,hrf); + +% Notes: +% 3/15/12: Tor updated to consider any stimlist with > 30 conditions to be +% a delta matrix, so that one can input continuous neural response +% functions and treat them as delta matrices for direct convolution. +% +% 9/3/12: Wani updated this function for two reasons. 1) to fix an error +% when one condition has two or more parametric modulators. 2) to fix an +% error due to RT with decimal points. For 2), make two options for downsampling +% : 'dsrate' and 'dslen'. + +model = []; +issquare = all(size(HRF) == size(HRF,1)); % if square mtx, assume convolution mtx + +% make sure HRF is column vector, if single vector +if size(HRF,1) == 1, HRF = HRF'; end + +% parametric modulators +doing_parametric_standard = any(strcmp(varargin, 'parametric_standard')); +doing_parametric_singleregressor = any(strcmp(varargin, 'parametric_singleregressor')); + +if doing_parametric_standard || doing_parametric_singleregressor + wh = find(strcmp(varargin, 'parametric_standard') | strcmp(varargin, 'parametric_singleregressor')); + pm_vals = varargin{wh(1) + 1}; + + if ~iscell(pm_vals) + error('pm_vals must be cell array.') + end +end + +use_downsample_rate = any(strcmp(varargin, 'dsrate')); +use_downsample_length = any(strcmp(varargin, 'dslen')); +if use_downsample_rate, wh = find(strcmp(varargin, 'dsrate')); rate_downsample = varargin{wh(1) +1}; end +if use_downsample_length, wh = find(strcmp(varargin, 'dslen')); length_downsample = varargin{wh(1) +1}; end + + +if isdeltamtx(stimList) % delta matrix + % ------------------------------------------------------------------------------------------------- + % * If delta function + % ------------------------------------------------------------------------------------------------- + + delta = stimList; + + if ~issquare + for i = 1:size(delta,2) + + for j = 1:size(HRF,2) + + if doing_parametric_standard || doing_parametric_singleregressor + if length(pm_vals) < i, error('PM values cell array is too short!'); end + + model = [model pmconv(double(delta(:,i)), HRF(:,j), pm_vals{i}, doing_parametric_standard, doing_parametric_singleregressor)]; + else + model(:,end+1) = conv(double(delta(:,i)), HRF(:,j)); % Changed for Matlab 7.9+ compatibility - Thanks, Liane + end + end + end + end + +else + % ------------------------------------------------------------------------------------------------- + % * If condition function + % ------------------------------------------------------------------------------------------------- + + for i = 1:max(stimList(:,1)) % condition function + + %delta(:,i) = (stimList == i); + delta(:,i) = cast((stimList == i), 'single'); % Changed for Matlab 7.9+ compatibility -- thanks, Liane Montana and Bruce McCandliss + + if doing_parametric_standard || doing_parametric_singleregressor + if length(pm_vals) < i, error('PM values cell array is too short!'); end + + model = [model pmconv(double(delta(:,i)), HRF(:,j), pm_vals{i}, doing_parametric_standard, doing_parametric_singleregressor)]; + end + + if ~issquare + for j = 1:size(HRF,2) + model(:,end+1) = conv(double(delta(:,i)), HRF(:,j)); % Changed for Matlab 7.9+ compatibility - Thanks, Liane + end + end + + end +end + +% ------------------------------------------------------------------------------------------------- +% * If conv. matrix +% ------------------------------------------------------------------------------------------------- + +if issquare % convolution matrix + model = HRF * delta; +end + +model = model(1:size(stimList,1),:); % eliminate extra values + +% downsample, if necessary +if use_downsample_rate || use_downsample_length % Wani modified this +% if ~isempty(varargin) + + % dsrate = varargin{1}; % Wani + [n, k] = size(model); + % nt = n ./ dsrate; % Wani + if use_downsample_rate, nt = n ./ rate_downsample; end % Wani modified this line + if use_downsample_length, nt = length_downsample; rate_downsample = n/length_downsample; end % Wani added this line + t = (1:n)'; % minor change to save time, tor: 10/12/10 + + if nt-round(nt)>.000001, error('Length of stimList is not evenly divisible by downsampling factor.'); end + + modeli = zeros(round(nt) , k); + + for i = 1:size(model, 2) + + xi = 1:rate_downsample:n; % downsample rate + + modeli(:, i) = interp1(t, model(:, i), xi, 'linear', 'extrap'); % tor: 10/12/10 : minor change: add extrap + end + + model = modeli; + %model = model(1:varargin{1}:end,:); % equivalent to resample(X,1,varargin{1},0) +end + +% do not do this if you want to do nonlinear saturation! +% not necessary, i think. +%model = model - repmat(mean(model),size(model,1),1); % center predictors + +end + + + + +function isdelta = isdeltamtx(stimList) + +isdelta = 0; + +if islogical(stimList) || all( sum(stimList == 0 | stimList == 1) == size(stimList, 1) ) % all zeros or ones + isdelta = 1; + +elseif min(size(stimList)) > 1 % multiple columns + isdelta = 1; + +elseif length(unique(stimList)) > 30 % custom neural response function? + +end + +end % subfunction + + + +function [model, delta] = pmconv(delta, HRF, pm_vals, doing_parametric_standard, doing_parametric_singleregressor) + +if doing_parametric_standard && doing_parametric_singleregressor + error('Cannot do both standard and single-regressor parametric modulator.') +end + +% HRF can be basis set, but SINGLE basis function is entered +% this is for one regressor/event type, with multiple basis images + +% must identify FIRST events, i.e., preceded by 0 +% for epochs... and must get back epoch duration, too. +%wh = find(delta); +wh = find(delta & [1; diff(delta) == 1]); + +% wh_end values, for durs +wh_end = find(~delta & [0; diff(delta) == -1]); +if delta(end), wh_end(end+1) = length(delta); end % fix if last event goes to end + +% transpose if horiz +if diff(size(pm_vals)) > 0, pm_vals = pm_vals'; end + +if ~isempty(pm_vals) && length(wh) ~= length(pm_vals) + disp('Number of events in parametric modulator does not match number of events in delta function.') + fprintf('%3.0f events in delta function, %3.0f events in param. modulator\n', sum(delta), length(pm_vals)); + + disp('Bad input? Stopping in debugger so you can check. Type dbcont or dbquit to continue.') + keyboard +end + +% get modulated delta function + +if ~isempty(pm_vals) && doing_parametric_standard + % create a second delta function that is modulated + pm_vals = scale(pm_vals, 1); + delta = repmat(delta, 1, size(pm_vals,2)+1); % Wani modified this line to fix an error when pm_vals has two or more columns. + % delta(:, end + 1) = delta; + +elseif ~isempty(pm_vals) && doing_parametric_singleregressor + % do nothing; we will just use pm vals + +elseif isempty(pm_vals) +else + error('This should not happen'); +end + +% This is where they get added. +% tor adjusted aug 2012 to account for epoch case. +if ~isempty(pm_vals) + for i = 1:length(wh) + delta(wh(i):wh_end(i), end) = pm_vals(i); + end +end + +% Convolve all preds for this trial type +lendelta = size(delta, 1); + +for i = 1:size(delta, 2) + + %for j = 1:size(HRF, 2) + tmp = conv(double(delta(:,i)), HRF); % Changed for Matlab 7.9+ compatibility - Thanks, Liane + + X{i} = tmp(1:lendelta); + %end + +end + +model = cat(2, X{:}); + + +end + + diff --git a/Model_building_tools/ideal_deconv6.m b/Model_building_tools/ideal_deconv6.m new file mode 100755 index 00000000..1c265851 --- /dev/null +++ b/Model_building_tools/ideal_deconv6.m @@ -0,0 +1,130 @@ +function [rmsd,msdstd,msdb,biasmean,meanest,min95est,max95est,ALLINFO,hrf,snr,TR] = ideal_deconv6(conditions,mspec,ttype) +% [rmsd,msdstd,msdb,biasmean,meanest,min95est,max95est,ALLINFO,hrf,snr,TR] = ideal_deconv6(conditions,mspec,ttype) +% +% tests deconvolution matrix directly against idealized data +% you put in the exact temporal sequence to be deconvolved, +% in the form of the DX matrix. +% +% Tor Wager, 4/19/02 +% +% inputs: +% DX deconvolution matrix +% tp time points estimated for each condition in DX +% TR repetition time of scan +% ttype trial types to test (out of 1:n different conditions in DX) +% recommended for time saving to use ttype = a single number only +% +% +% this function is like ideal_deconv5, but tests variability across designs as well. + +hrf = spm_hrf(mspec.TR); +hrf = hrf ./ max(hrf); + +% hrfin contains cell array of hrfs for each condition +[DX] = construct_model(mspec,conditions,[]); +[delta, wd, wb] = DX_find_delta(DX); +for j = 1:length(wd) + hrfin{j} = hrf; + + % when epoch or block regressor - now only works for one-part designs + if length(conditions) >= j + if isfield(conditions(j),'stimlength') + if conditions(j).stimlength > 1 + hrfin{j} = conv(hrf,ones(conditions(j).stimlength,1)); + hrfin{j} = hrfin{j} ./ max(hrfin{j}); + end + end + end + +end + +% for custom hrf for one trial type +%hrf2 = sum([[zeros(5,1);hrf] [hrf;zeros(5,1)]],2); +%hrfin{2} = hrf2; + + +ndesigns = 2; +nnoise = 100; +snr = 1; +designs = 1:ndesigns; + +for j = ttype + + clear rmsd, clear msdstd, clear msdb, clear biasmean, clear meanest, clear min95est, clear max95est + + for i = 1:ndesigns % for this many designs + + % rmsd sqrt of mean squared dev. of estimates from true response + % msdstd single obs. 95% confidence interval for error (2-tailed) + % msdb abs dev. from ideal response for each time point + % biasmean mean bias (above or below true response) for each time pt. + % meanest mean estimate of the response (estimated hrf) + % min95est 95% of individual regressions fall within this window... + % max95est between min95est and max95est + + [DX] = construct_model(mspec,conditions,[]); + + [rmsd(i),msdstd(i,:),msdb(i,:),biasmean(i,:),meanest(i,:),min95est(i,:),max95est(i,:),INFO] ... + = dx_estimate_dev(DX,snr,hrfin,j,nnoise); + + end + + + + % plotting + + + + plot_ideal_deconv5(rmsd,msdstd,msdb,biasmean,meanest,min95est,max95est,INFO,hrfin{j},designs,mspec.TR); + + % re-plot first graph to fit this format + subplot 221 + cla + plot([(1:ndesigns); (1:ndesigns)], msdstd','k-','LineWidth',2) + hold on; + plot(rmsd,'k.','LineWidth',2) + xlabel('Design Realization') + + subplot 222 + cla + plot(repmat((1:size(meanest,2))' .* mspec.TR,1,size(meanest,1)), meanest') + h = plot((1:length(hrf)) * mspec.TR, hrf,'k','LineWidth',2); + if ~isempty(INFO.autocorrelation) + h(2) = plot((1:length(INFO.autocorrelation))./(length(INFO.autocorrelation) / length(hrf)),INFO.autocorrelation,'k--','LineWidth',2); + myleg = {'HRF','ACF'}; + else + myleg = {'HRF - white noise simulated'}; + end + legend(h,myleg) + + INFO.TR = mspec.TR; + ALLINFO(j) = INFO; + + a = get(gcf,'Children'); + axes(a(3)) + ylabel('Design Realization') + set(gca,'YTick',[1 ndesigns]) + set(gca,'YTickLabel',[1 ndesigns]) + + %subplot 223 + %hold on + %ylabel('Design Realization') + %set(gca,'YTickLabel',[1 ndesigns]) + + axes(a(5)) + ylabel('Design Realization') + set(gca,'YTick',[1 ndesigns]) + set(gca,'YTickLabel',[1 ndesigns]) + + %subplot 224 + %hold on + %ylabel('Design Realization') + %set(gca,'YTickLabel',[1 ndesigns]) + + figure;set(gcf,'Color','w') + a = mean((biasmean),2); +hist(a,length(a) / 4) +title(['Frequency histogram of mean estimation bias across ' num2str(nnoise) ' noise realizations per model']) +end + +return diff --git a/Model_building_tools/intercept_model.m b/Model_building_tools/intercept_model.m new file mode 100755 index 00000000..399fb10e --- /dev/null +++ b/Model_building_tools/intercept_model.m @@ -0,0 +1,75 @@ +% x = intercept_model(nvols_per_run, [indx of dummy scans in each session]) +% +% Build design matrix X for intercepts +% given vector of session lengths [s1 s2 s3] in images +% +% Examples: +% nvols_per_run = [166 166 144 137]; +% x = intercept_model(nvols_per_run); +% +% x = intercept_model(repmat(166, 1, 5)); +% +% Xi = intercept_model(EXPT.FIR.nruns, 1:2); +% +% tor modified april 07: separate column for each run + +function x = intercept_model(nvols_per_run, varargin) + if ~isvector(nvols_per_run), error('nvols_per_run must be a vector of the number of volumes for each run'); end + nvols_per_run = nvols_per_run(:)'; %ensure row vector + + npoints = sum(nvols_per_run); % number of time points total + nruns = length(nvols_per_run); % number of runs + + x = zeros(npoints, nruns); + + st = cumsum([1 nvols_per_run]); + en = st(2:end) - 1; % ending values + st = st(1:end-1); % starting values + + for i = 1:nruns + x(st(i):en(i), i) = 1; + end + + if length(varargin) > 0 && ~isempty(varargin{1}) + % Model dummy regressors + x = [x model_dummy(npoints, st', varargin{1})]; + end +end + + + +function x = model_dummy(npoints, st, wh) + % dummy regressors for first scan or two (at least, that's the intended + % use) + % separate column for each run! + + k = length(wh) .* length(st); + x = zeros(npoints, k); + + dosamecol = 0; + + if dosamecol + for i = 1:k + ind = st(r) + wh(i) - 1; % which to filter + ind(ind < 1) = []; % if negative numbers (end of runs), this makes it ok + x(ind, i) = 1; + end + + else + + colindx = 1; + + for r = 1:length(st) % for each run + for i = 1:length(wh) + + ind = st(r) + wh(i) - 1; % which to filter + ind(ind < 1) = []; % if negative numbers (end of runs), this makes it ok + + x(ind, colindx) = 1; + colindx = colindx + 1; + end + end + + end + +end diff --git a/Model_building_tools/make_ideal_data.m b/Model_building_tools/make_ideal_data.m new file mode 100755 index 00000000..33b3a666 --- /dev/null +++ b/Model_building_tools/make_ideal_data.m @@ -0,0 +1,31 @@ +function ideal_data = make_ideal_data(hrf,sf,eres,finalLength) +% function ideal_data = make_ideal_data(hrf,sf,eres,finalLength) +% + +mylen = size(sf{1},1); + +% ------------------------------------------------------------------- +% * make hi-res ideal data with eres timebins per TR +% ------------------------------------------------------------------- + +ideal_data = []; +for i = 1:length(sf) + mydata = conv(hrf,full(sf{i})); + mydata = mydata(1:mylen); + ideal_data(:,i) = mydata; +end +ideal_data = sum(ideal_data,2); + +% ------------------------------------------------------------------- +% * downsample ideal data to TR +% ------------------------------------------------------------------- +%i2 = ideal_data; + +ideal_data = resample(ideal_data,1,eres); +ideal_data = ideal_data(1:finalLength); + +%figure;hold on +%plot(1:1/length(i2):2-1/length(i2),i2) +%plot(1:1/length(ideal_data):2-1/length(ideal_data),ideal_data,'r') + +return \ No newline at end of file diff --git a/Model_building_tools/modifiedconv.m b/Model_building_tools/modifiedconv.m new file mode 100755 index 00000000..114053f6 --- /dev/null +++ b/Model_building_tools/modifiedconv.m @@ -0,0 +1,118 @@ +function reg = modifiedconv(tr,condf,varargin) +% model = modifiedconv(tr,condf,heighteq [all opt],delayeq,ttopeakeq,uonseteq) +% +% 06/20/01 Tor Wager +% +% tr = repetition time (sampling rate) of scanning, in seconds +% condf = condition function +% an indicator vector of zeros and ones, where ones indicate event +% onsets +% +% USES nonlinear saturation in height only +% with a guess as to what the decrease in saturation is as a function of +% the time since previous stimulation (exponential model, alpha version) +% +% example: +% condf = [1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]'; +% X = modifiedconv(2,condf); +% X is convolved predictor +%plot(X) +%X2 = conv(condf,spm_hrf(2)./max(spm_hrf(2))); +%hold on; plot(X2,'r'); legend({'Modified' 'Linear'}) +% +% Please see: +% Wager, T. D., Hernandez, L., Vasquez, A., Nichols, T., and Noll, D. +% C. (in press). Accounting for nonlinear BOLD effects in fMRI: Parameter +% estimates and model for accurate prediction in variable-duration blocked +% and rapid event-related studies. Neuroimage. + +%heighteq = []; delayeq = []; peakeq = []; uonseteq = []; + +% --------------------------------------------------------------------- +% * defaults +% --------------------------------------------------------------------- +height = 1; delay = 0; peak = 6; uonset = 16; +dispers = 1; udisp = 1; rtou = 6; klength = 32; +rtou = Inf; % get rid of undershoot + +p = [peak uonset dispers udisp rtou delay klength]; + +hrf = spm_hrf(tr,p)./ max(spm_hrf(tr,p)); + +%heighteq = inline('1.7141.*(exp(-2.1038.*x)) + 0.4932.*(exp(-0.0770.*x))'); +%delayeq = inline('-13.4097.*(exp(-1.0746.*x)) + 4.8733.*(exp(-0.1979.*x))'); +%peakeq = inline('37.5445.*(exp(-2.6760.*x)) + -3.2046.*(exp(-0.2120.*x)) + 5.6344');% + +% new idea: model nonlinear saturation response as a function of +% a fixed saturation response (that we know is e.g., 1-(.6658 x original) with a +% stimulus 1 s before) x an exponential discount factor for how LONG AGO the +% previous stimulus occurred +% SO THAT: Rnow = Ro - sum(s(t-to)gamm(t-to) +% gam(1) should be 1-.6658, and area under gam should be no more than 1, +% because you can never lose more than 100% of the signal +% BUT we may end up having to take interactions betwn stim into account, +% which is harder: e.g., a stim occurs .5 s before and another occurs .3 +% sec before. The .3 one is going to produce a smaller response and thus +% less saturation, and you can never saturate more than 1 (100%) even w/ +% many stimuli occurring in a short time frame (w/i 1 s) +% SO this model, now, can predict negative response values with more than +% 100% sat. + +gam = inline('(1-.6658) * (1./exp(-a))*exp(-a*t)','t','a'); +a = .7; % let's assume this is the exp for now. gives 70% sat w/hist 1:5, 74% w/hist 1:10 + + +myzeros = zeros(length(condf),1); +mylen = length(myzeros); +numels = 12 ./ tr; % number of elements to count + +reg = myzeros; + +whstim = find(condf); % which elements contain indicators of stimulation + +if any(condf(whstim)>1), + disp('warning: modifiedconv not valid for onset mag > 1, which you appear to have entered.'), +end + +for i = 1:length(whstim) + + j = whstim(i); % index of which element + + % for each element, get predicted height + + trialdelta = myzeros; + trialdelta(j) = 1; + trialp = p; + + % figure out how many of same type came before + % 1 is "first stim in sequence" + + %myc = condf(j-min(29,j)+1:j) + %myw = timeweights(end-length(myc)+1:end) + + myc = condf(j-min(numels,j)+1:j); % recent events, including current stim + + wh = find(myc(1:end-1)); % which elements + + times = length(myc) - wh; % times at which these occurred in elements + times = times * tr; % convert to seconds + + s = myc(wh); % stimulus intensity at each time, probably 1 for typical indicator vector + + sat = sum(s .* gam(times,a)); % total saturation + + height = max(0,1 - sat); % magnitude of this event + + trialdelta = myzeros; + trialdelta(j) = condf(j); + + mytrialpred = conv(height.*hrf,trialdelta); + mytrialpred = mytrialpred(1:mylen); + + reg = reg + mytrialpred; + + +end + + +return diff --git a/Model_building_tools/onsets2delta.m b/Model_building_tools/onsets2delta.m new file mode 100755 index 00000000..4e466774 --- /dev/null +++ b/Model_building_tools/onsets2delta.m @@ -0,0 +1,82 @@ +function delta = onsets2delta(ons, len) +% delta = onsets2delta(ons, [length: num rows in original units]) +% +% Tor Wager, 2 / 24 / 04, update 10 / 12 / 10 +% +% Builds high-res delta function, given cell array of onset times +% +% Inputs: +% ------------------------------------------------------------------ +% ons - onsets for each of a series of conditions +% One cell per condition per session, e.g., ons{1} = [24 27 29 44]'; +% Units are arbitrary (e.g., TRs or seconds) +% +% Onsets are assumed to start at time 0 (0 is start of run/session) +% +% e.g., from an SPM.mat fmri design structure, for one session: +% ons = cell(1, nconds); +% [ons{:}] = deal(reportmod_model.Sess(1).U(:).ons) +% +% len - optional: number of rows in original units. Useful for making +% a design matrix with the right number of rows after convolution and downsampling +% +% Outputs: +% ------------------------------------------------------------------ +% delta - indicator matrix of vectors for each condition with 1/0 for each onset +% Type is logical; you may want to do double(delta) before operating +% Resolution of high-res delta functions = original units (secs or TRs) * 16 +% The number of rows is the max onset + 1, times 16 +% +% For what to do with output, see: +% getPredictors : for design-matrix building +% downsample_canlab : for downsampling to TR/secs +% +% See also ONSETS2FMRIDESIGN and object-oriented fmri_model object +% (methods: build, etc.) +% +% Revision: tor: 10/12/10, for integration with object-oriented fmri_model + + +% ---------------------------------------------- +% Defaults +% ---------------------------------------------- + +res = 16; % resolution of high-res delta functions = orig. units * 16 + +if nargin == 1 + len = res * (max(cat(1, ons{:})) + 1); +else + len = res * len; +end + +% base indicator: zeros +cf = false(len,1); + +nconds = length(ons); +delta = false(len, nconds); %cell(1, length(ons)); + +% ---------------------------------------------- +% Error checking +% ---------------------------------------------- +if ~iscell(ons), error('ons must be cell array'); end + +if any(diff(cat(1, ons{:})) == 0) + warning('onsets2delta: repeated onsets', 'Some onsets are identical'); +end + +% ---------------------------------------------- +% Build +% ---------------------------------------------- +for i = 1:nconds + + cf2 = cf; + + cf2(round(ons{i}*res) + 1) = true; % first TR is time 0, element 1 + + if length(cf2) > len, error('Some onsets occur after the end of the session. Mismatch in units??'); end + + delta(:, i) = cf2; +end + +return + diff --git a/Model_building_tools/onsets2dx.m b/Model_building_tools/onsets2dx.m new file mode 100644 index 00000000..aa15d2d0 --- /dev/null +++ b/Model_building_tools/onsets2dx.m @@ -0,0 +1,44 @@ +% function [DX,delta] = onsets2dx(onsets, TR, scansperrun, numseconds) +% +% onsets - cell array whose length is num runs * num conditions, +% e.g., {run 1 ant onsets, run 1 stim onsets, run 2 ant onsets, run 2 stim +% onsets} +% TR - TR in seconds +% scansperrun - number of volumes in each run +% numseconds - number of seconds after event onsets to generate regressors for [default: 32] +% +% E.g.: +% TR = 2; +% scansperrun = [192 196 196 184 190 192]; +% numseconds = 30; +% [DX,delta] = onsets2dx(onsets, TR, scansperrun, numseconds) +% EXPT.FIR.model{subjectnumber} = DX; + +function [DX,delta] = onsets2dx(onsets, TR, scansperrun, numseconds) + + if ~iscell(onsets) + for i = 1:size(onsets,2), tmp{i} = onsets(:,i); end + onsets = tmp; + end + + if(~exist('numseconds', 'var') || isempty(numseconds)) + numseconds = 32; + end + + evtsperrun = length(onsets) ./ length(scansperrun); + + delta = {}; + + for i = 1:length(scansperrun) + % onsets for this run + st = (i - 1) .* evtsperrun + 1; + wh = st:(st + evtsperrun - 1); + + % delta for this run + [x,d] = onsets2delta(onsets(wh), TR, scansperrun(i)*TR - 1); + + delta = [delta; d]; + end + + DX = tor_make_deconv_mtx3(cell2mat(delta), round(numseconds./TR), 1, 0, 1, 0, scansperrun); +end diff --git a/Model_building_tools/onsets2fmridesign.m b/Model_building_tools/onsets2fmridesign.m new file mode 100644 index 00000000..2e17e102 --- /dev/null +++ b/Model_building_tools/onsets2fmridesign.m @@ -0,0 +1,310 @@ +% [X, delta, delta_hires, hrf] = onsets2fmridesign(onsets, TR, [len], [custom hrf or basis set name],[other optional args, i.e., norm]) +% Tor Wager, 2 / 24 / 04 +% Modified/updated 2/08 by Tor to add capacity for durations +% Modified 10/1/11 by Tor to fix custom HRF sampling +% Modified 3/14/12 by Tor to check variable duration and add noampscale option +% +% Builds design matrix X and delta function, given cell array of onset times in s +% One cell per condition per session, e.g., ons{1} = [24 27 29 44]'; +% +% Summary: +% - handles multiple conditions +% - handles custom HRFs and multiple basis functions +% - handles two kinds of parametric modulators +% - handles variable-duration onsets +% - not yet: variable-duration parametric +% modulators +% +% +% Inputs: +% onsets: +% - 1st column is onsets for events, +% - 2nd column is optional durations for each event +% - Enter single condition or cell vector with cells for each condition (each event type). +% TR: RT in seconds +% len: optional length in s for model, or [] to skip if using additional. +% "len" is usually the number of images multiplied by TR. +% +% args +% hrf name: 1) a string used by spm_get_bf.m +% 2) a custom HRF, sampled in seconds +% 3) or [] to skip +% 'norm' : mean-center, orthogonalize, and L2-norm basis set +% 'parametric_singleregressor' : Parametrically modulate onsets by +% modulator values, using single regressor with modulated amplitude +% Enter a cell array with modulator values for each event +% type, with a column vector (empty cell for no modulation) +% 'parametric_standard' : Parametrically modulate onsets by +% modulator values, using two regressors per event type - One +% to model the average response, and one for the +% mean-centered modulator values +% of modulator values in each cell +% 'noampscale' Do not scale HRF to max amplitude = 1; SPM default is not to scale. +% 'noundershoot' Do not model undershoot - ONLY when using the canonical HRF +% 'customneural' Followed by a vector of custom neural values (instead of +% standard event/epochs), sampled in sec. +% +% Limitation: can only handle two events max within the same TR +% +% Outputs: +% X: model, sampled at TR +% delta: indicator mtx, sampled at TR +% delta_hires: indicator sampled at 16 Hz +% hrf: hemodynamic response function, sampled at 16 Hz +% +% Test examples: +% X = onsets2fmridesign(ons, TR, size(imgs, 1) .* TR, 'hrf (with time derivative)'); +% +% X = onsets2fmridesign({[0 30 60]' [15 45 90']}, 1.5, 180, spm_hrf(1), 'parametric_standard', {[2 .5 1]' [1 2 2.5]'}); figure; plot(X) +% X = onsets2fmridesign({[0 30 60]' [15 45 90']}, 1, 180, spm_hrf(1), 'parametric_standard', {[2 .5 1]' [1 2 2.5]'}); figure; plot(X) +% X = onsets2fmridesign({[0 30 60]' [15 45 90']}, 2, 180, spm_hrf(1), 'parametric_standard', {[2 .5 1]' [1 2 2.5]'}); figure; plot(X) +% X = onsets2fmridesign({[0 30 60]' [15 45 90']}, 2, 180, spm_hrf(1), 'parametric_singleregressor', {[2 .5 1]' [1 2 2.5]'}); figure; plot(X) +% X = onsets2fmridesign({[0 30 60]' [15 45 90']}, 1, 180, spm_hrf(1), 'parametric_singleregressor', {[2 .5 1]' [1 2 2.5]'}); figure; plot(X) +% +% Here, spm_hrf(1) is for the canonical HRF in spm. +% +% X = onsets2fmridesign(ons, 2, size(dat.dat, 2) .* 2, [], [], 'noampscale'); +% +% X2 = onsets2fmridesign(onsp, 2, length(dat.removed_images) .* 2, [], [], 'customneural', report_predictor); +% +% Programmers' notes: +% 3/16/12: Tor modified custom HRF to divide by sum, as SPM basis functions +% are scaled by default. +% 9/3/12: Wani modified len and getPredictors.m to fix a bug when you're +% using TR with some decimal points (e.g., TR = 1.3) + +function [X, delta, delta_hires, hrf] = onsets2fmridesign(ons, TR, varargin) +% ---------------------------------------------- +% Defaults +% ---------------------------------------------- + +res = 16; % resolution, in samples per second + +% Other optional inputs after fixed ones +% - - - - - - - - - - - - - - - - - - - - +donorm = 0; +doampscale = 1; +hrfparams = [6 16 1 1 6 0 32]; +customneural = []; % custom neural response; neither impulse nor epoch +dosingletrial = 0; +docheckorientation = 1; + +for i = 3:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % reserved keywords + case 'norm', donorm = 1; + + case {'parametric_standard', 'parametric_singleregressor'} + % pm_mods = varargin{i + 1}; unnecessary, as passed + % into getPredictors + + case 'noampscale', doampscale = 0; + + case 'noundershoot', hrfparams(5) = Inf; + + case 'customneural', customneural = varargin{i + 1}; + + case 'singletrial', dosingletrial = 1; docheckorientation = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +if ~iscell(ons), ons = {ons}; end + +if dosingletrial + % single trial: break events into separate regressors/onset vectors + for i = 1:length(ons) + sonsets{i} = cell(1, length(ons{i})); % event-specific onsets - init + + for j = 1:length(sonsets{i}) + sonsets{i}{j} = ons{i}(j, 1); + if size(ons{i}, 2) > 1 % if durations... + sonsets{i}{j} = [sonsets{i}{j} ons{i}(j, 2)]; % append duration + end + end + + end % original onset conditions + ons = cat(2, sonsets{:}); % flatten to simply model trials within event type. +end + + +% Optional: pre-specified length, or [] +% - - - - - - - - - - - - - - - - - - - - + +if ~isempty(varargin) && ~isempty(varargin{1}) % pre-specified length + + len_original = varargin{1}; % this line is added by Wani to make this work for TR = 1.3 + len = varargin{1}; % this line is modified by Wani from the next line + % len = ceil(varargin{1}); + for i = 1:length(ons) + if docheckorientation + % make column vectors if needed + if length(ons{i}) > size(ons{i}, 1) + disp('Warning! onsets2fmridesign thinks you''ve entered row vectors for onsets and is transposing. Enter col vectors!'); + ons{i} = ons{i}'; + end + end + + % make sure nothing goes past end of session + past_end = round(ons{i}(:,1)) > len; + if any(past_end) + fprintf('Warning! onsets2fmridesign found %d onsets after the end of the session and is omitting them.\n', sum(past_end)); + end + ons{i}(past_end,:) = []; + end + len = ceil(len .* res); +else + len = round(max(cat(1, ons{:})) * res); + len = ceil(ceil(len/(res*TR)) * res * TR); % modified by Wani to make this work for TR = 1.3 + len = len(1); +end + +cf = zeros(len, 1); + +% Optional: basis set name, or [] +% - - - - - - - - - - - - - - - - - - - - +if length(varargin) > 1 && ~isempty(varargin{2}) + if ischar(varargin{2}) + % basis set: Any of the named basis sets from SPM spm_get_bf + + % bf = spm_get_bf(struct('name', 'hrf (with time and dispersion derivatives)', 'length', 30, 'dt', 1)); + bf = spm_get_bf(struct('name', varargin{2}, 'length', 30, 'dt', 1/res)); + hrf = bf.bf; + + else + % custom HRF; do not norm by max + % assume sampled in sec, and sample at high res first + % scale to sum to 1 like SPM does + hrf = varargin{2}; + lenh = length(hrf); + hrf = interp1((1:lenh)', hrf, (1:(1/res):lenh), 'linear', 'extrap'); + hrf = hrf ./ sum(hrf); + end +else + hrf = spm_hrf(1/res, hrfparams); % canonical hrf + + if doampscale + hrf = hrf ./ max(hrf); + end +end + +% ---------------------------------------------- +% Normalize basis set, if requested +% ---------------------------------------------- + +if donorm + % Mean-center + hrf = scale(hrf, 1); + kk = size(hrf, 2); + + % Orthogonalize "later" basis funtions wrt "earlier" (assumed to be more important) ones + if size(hrf, 2) > 1, hrf = spm_orth(hrf); end + + % Normalize to L2 norm + for ii = 1:kk + hrf(:, ii) = hrf(:, ii) ./ norm(hrf(:, ii)); + end +end + +% ---------------------------------------------- +% Set up custom neural response function +% ---------------------------------------------- + +if ~isempty(customneural) + % assume sampled in sec, and sample at high res first + % scale to sum to 1 like SPM does + + nsamp = length(customneural); + customneural = interp1((1:nsamp)', customneural, (1:(1/res):nsamp), 'linear', 'extrap'); + + if isrow(customneural), customneural = customneural'; end + + nsamp = length(customneural); + +end + +% ---------------------------------------------- +% BUILD DESIGN +% see also getDesign5.m +% ---------------------------------------------- + +for i = 1:length(ons) + + if any(round(ons{i}(:,1)) < 0) + error('Illegal onsets (negative values) -- check onsets!'); + end + + cf2(:,i) = cf; % add empty column for this condition + + if isempty(customneural) + % Event-related response (or epoch) + cf2(round(ons{i}(:,1)*res) + 1, i) = 1; % specify indicators; first TR is time 0, element 1 + + else + % Custom neural response + for j = 1:size(ons{i}, 1) + + first_sample = round(ons{i}(j, 1)*res) + 1; + end_in_samples = min(len, first_sample + nsamp - 1); + + prevneural = cf2(first_sample:end_in_samples, i); + cf2(first_sample:end_in_samples, i) = prevneural + customneural(1:length(prevneural)); % add to condition function + end + end + + we_have_durations = size(ons{i}, 2) > 1; + + if we_have_durations && ~isempty(customneural) + error('You cannot specify epoch durations and a custom neural function.'); + end + + if we_have_durations + % Build epochs + for j = 1:size(ons{i}, 1) + dur_in_s = ons{i}(j, 2); + first_sample = round(ons{i}(j, 1)*res) + 1; + end_in_samples = min(len, round(first_sample + dur_in_s * res)); + + cf2(first_sample:end_in_samples, i) = 1; % add to condition function + end + + end + + cf2 = cf2(1:len,:); +end + +delta_hires = cf2; + +% convolve. this now does parametric modulators as well. +X = getPredictors(delta_hires, hrf, 'dslen', len_original/TR, varargin{:}); % added len_original by Wani +X(:,end+1) = 1; + + +% for HRF shape estimation +len2 = ceil(max(vertcat(ons{:})) ./ TR); +cf = zeros(len2(1), 1); +cf2 = []; + +for i = 1:length(ons) + tmp = 1 + round(ons{i}(:,1)./TR); + cf2{i} = cf; + cf2{i}(tmp) = 1; % time 0 is first element + + repeats = tmp(find(diff(tmp) == 0)); + cf2{i}(repeats) = cf2{i}(repeats) + 1; +end + +delta = cf2; + +if ~isempty(varargin) + for i = 1:length(ons) + delta{i} = pad(delta{i}, ceil(len./res./TR - length(delta{i}))); + end +end +end + diff --git a/Model_building_tools/onsets2parametric_mod_X.m b/Model_building_tools/onsets2parametric_mod_X.m new file mode 100644 index 00000000..dde49d75 --- /dev/null +++ b/Model_building_tools/onsets2parametric_mod_X.m @@ -0,0 +1,86 @@ +function model = onsets2parametric_mod_X(ons, pm_vals, nscan, basisset, varargin) + +% ons = ons{1}; %obj.Sess(1).U(1).ons ./ TR; +% pm_vals = obj.Sess(1).U(1).P.P; +% nscan = obj.nscan(s); + +if ~iscell(ons) + error('ons must be cell array.') +end + +if ~iscell(pm_vals) + error('pm_vals must be cell array.') +end + +nconds = length(ons); + +delta = double(onsets2delta(ons, nscan)) ; % double from logical + + +% make sure basisset is column vector, if single vector +if size(basisset,1) == 1, basisset = basisset'; end +nbf = size(basisset, 2); + + +% get modulated delta function +for i = 1:nconds + + wh = find(delta(:, i)); + pm_u = scale(pm_vals{i}, 1); + + if length(wh) ~= length(pm_u) + disp('Number of events in parametric modulator does not match number of events in delta function.') + disp('Bad input? Stopping in debugger so you can check.') + keyboard + end + + delta(wh, i) = pm_u; +end + +% Convolve +for i = 1:nconds + + lendelta = size(delta, 1); + X{i} = zeros(lendelta, nbf); + + for j = 1:size(basisset,2) + tmp = conv(double(delta(:,i)), basisset(:,j)); % Changed for Matlab 7.9+ compatibility - Thanks, Liane + + X{i}(:, j) = tmp(1:lendelta); + end + +end + +model = cat(2, X{:}); + + +% downsample, if necessary +if ~isempty(varargin) + + dsrate = varargin{1}; + [n, k] = size(model); + nt = n ./ dsrate; + t = (1:n)'; % minor change to save time, tor: 10/12/10 + + if nt ~= round(nt), error('Length of stimList is not evenly divisible by downsampling factor.'); end + + modeli = zeros(nt , k); + + for i = 1:size(model, 2) + + xi = 1:varargin{1}:n; % downsample rate + + modeli(:, i) = interp1(t, model(:, i), xi, 'linear', 'extrap'); % tor: 10/12/10 : minor change: add extrap + end + + model = modeli; + %model = model(1:varargin{1}:end,:); % equivalent to resample(X,1,varargin{1},0) +end + + +model = model(1:nscan,:); % eliminate extra values + + + +end + diff --git a/Model_building_tools/plot_ideal_deconv5.m b/Model_building_tools/plot_ideal_deconv5.m new file mode 100755 index 00000000..d345c85b --- /dev/null +++ b/Model_building_tools/plot_ideal_deconv5.m @@ -0,0 +1,101 @@ +function plot_ideal_deconv5(rmsd,msdstd,msdb,biasmean,meanest,min95est,max95est,INFO,hrf,snr,TR,varargin) +%function plot_ideal_deconv5(rmsd,msdstd,msdb,biasmean,meanest,min95est,max95est,INFO,hrf,snr,TR,[truer]) +% Tor Wager +% delta should be matrix of column vectors +% optional: truer, vector of "true" responses for each trial type + + +msdblim = max(max(abs(msdb))); +bmlim = max(max(abs(biasmean))); bmlim = [-bmlim bmlim]; + +if isfield(INFO,'ideal_data') & isfield(INFO,'delta') + figure('Color','w'), myleg{1} = 'Ideal response'; + h(1) = plot(INFO.ideal_data,'LineWidth',2);, hold on; + mycols = {'r' 'g' 'y' 'c' 'm'}; mycols = repmat(mycols,1,10); + delta = INFO.delta; + for i = 1:size(delta,2) + tmp1 = find(delta(:,i)); + hh = plot([tmp1 tmp1]',[-1*ones(size(tmp1)) zeros(size(tmp1))]',mycols{i},'LineWidth',2); + h(i+1) = hh(1); + myleg{i+1} = ['Trial type ' num2str(i) ' onsets']; + end + set(gca,'FontSize',18) + legend(myleg) + set(gcf,'Position',[19 606 1561 360]) + + %tmp = conv(hrf,delta(:,1));figure;plot(tmp),title('ttype1') + %tmp = conv(hrf,sum(delta,2));figure;plot(tmp),title('alltypes') +end +%figure;plot([find(tmp);find(tmp)],[zeros(size(find(tmp)),1); ones(size(find(tmp)),1)]) + +if length(varargin) > 0 + truer = varargin{1}; +else + truer = ones(1,size(INFO.delta,2)); +end + + +figure; subplot 221; hold on; set(gcf,'Color','w'); +if ~isempty(INFO.autocorrelation) + figure('Color','w'); set(gca,'FontSize',18) + plot((1:length(INFO.autocorrelation))./(length(INFO.autocorrelation) / length(hrf)),INFO.autocorrelation,'k-','LineWidth',2) +end +title('Noise autocorrelation function') + +% old plot of RMSD +% ----------------- +%plot(snr,rmsd,'ko-','MarkerFaceColor','k','MarkerSize',3,'LineWidth',2) +%plot(snr,msdstd(:,1),'k--') +%plot(snr,msdstd(:,2),'k--') +%legend({'Mean population value' '95% CI for one regression'}) +%ylabel('Root Mean Squared Deviation') +%xlabel('Signal to Noise Ratio') + +%title(['Est. - True response: ' INFO.condition_tested],'FontSize',14) + +subplot 222; hold on; +x = (1:length(hrf)) * TR; +plot(x, hrf.*truer(INFO.ttype),'k','LineWidth',2) +myleg = {'HRF'}; +legend(myleg) +title(['Est. vs. True response: ' INFO.condition_tested],'FontSize',14) +xlabel('Time from trial onset') + +mycolors = {'r' 'b' 'g' 'c' 'm'}; +for i = 1:min(5,length(snr)) + plot(x(1:size(meanest,2)),meanest(i,:),mycolors{i}) + myleg{end+1} = ['Estimated, snr = ' num2str(snr(i))]; +end + +if ~isempty(min95est) & ~isempty(max95est) + plot(x(1:size(min95est,2)),min95est(1,:),'r--'); + plot(x(1:size(min95est,2)),max95est(1,:),'r--'); + myleg{end+1} = ['95% CI for ind. regressions, snr = ' num2str(snr(1))]; +end +legend(myleg) + + +subplot 223; hold on; +imagesc(msdb,[0 msdblim]), colormap copper +xlabel('Time from trial onset') +ylabel('Signal to Noise Ratio') +%set(gca,'YDir','Reverse') +set(gca,'YTick',1:length(snr)) +set(gca,'YTickLabel',snr) +set(gca,'XTick',(1:2:INFO.estlength)) +set(gca,'XTickLabel',get(gca,'XTick') .* TR) +title('Mean abs. dev. by time from event onset','FontSize',14) +colorbar('horiz') + +subplot 224; hold on; +imagesc(biasmean,bmlim), colormap jet +xlabel('Time from trial onset') +ylabel('Signal to Noise Ratio') +set(gca,'YTick',1:length(snr)) +set(gca,'YTickLabel',snr) +set(gca,'XTick',(1:2:INFO.estlength)) +set(gca,'XTickLabel',get(gca,'XTick') .* TR) +title('Bias in estimation by time from event onset','FontSize',14) +colorbar('horiz') + +return diff --git a/Model_building_tools/spm_mat2batchinput.m b/Model_building_tools/spm_mat2batchinput.m new file mode 100644 index 00000000..930d59b5 --- /dev/null +++ b/Model_building_tools/spm_mat2batchinput.m @@ -0,0 +1,65 @@ +function [imgs, TR, scanspersess, names, onsets, durations] = spm_mat2batchinput(SPM) +% [imgs, TR, scanspersess, names, onsets, durations] = spm_mat2batchinput(SPM) +% +% Extract info from SPM structure and format in batch input mode +% +% Tor Wager, Feb 2011 +% Could be extended to include nuisance covs, parametric modulators, etc. +% Right now this is very basic. +% +% input into SPM8: tmod = time modulator (number per cell, order, linear = 1) +% For parametric modulation include a structure array, which is up to 1 x n in size, called pmod. n must be less +%than or equal to the number of cells in the names/onsets/durations cell arrays. The structure array pmod +%must have the fields: name, param and poly. Each of these fields is in turn a cell array to allow the inclusion +% R for regressors of no interest +% +% NOTES: +% - this is a preliminary version of this function and does not extract +% covariates, parametric modulators, etc. +% +% - Returns output in the format you would use to enter into the SPM GUI to +% set up a model. Same format is used by canlab_spm_fmri_model_job.m + +% check input image names + +if iscell(SPM.xY.P) + imgs = char(SPM.xY.P{:}); +else + imgs = SPM.xY.P; +end + +TR = SPM.xY.RT; +scanspersess = SPM.nscan; + +[names, onsets, durations, covs] = deal({}); + +% list regressor names +for sess = 1:length(SPM.Sess) + + names = [names cat(1, SPM.Sess(sess).U(:).name)']; + %disp(sprintf('Session %3.0f', sess)); + +end + + +% check onsets +% 0 should be the start of the first image acquisition + +for sess = 1:length(SPM.Sess) + + %disp(sprintf('Session %3.0f', sess)); + + for evt = 1:length(SPM.Sess(sess).U) + % disp(['Session ' num2str(sess) ' event type: ' SPM.Sess(sess).U(evt).name]) + % [SPM.Sess(sess).U(evt).ons SPM.Sess(sess).U(evt).durations] + + onsets = [onsets {SPM.Sess(sess).U(evt).ons}]; + durations = [durations {SPM.Sess(sess).U(evt).dur}]; + + + end + + +end + + diff --git a/Model_building_tools/tor_make_deconv_mtx3.m b/Model_building_tools/tor_make_deconv_mtx3.m new file mode 100755 index 00000000..b28ba37a --- /dev/null +++ b/Model_building_tools/tor_make_deconv_mtx3.m @@ -0,0 +1,184 @@ +function [DX,sf] = tor_make_deconv_mtx3(sf,tp,eres,varargin) +% function [DX,sf] = tor_make_deconv_mtx(sf,tp,eres,[opt] TRs before stim onset,[num. sessions],[docenter],[scanspersess]) +% sf: cell array of stick functions, one per condition +% all sf cells should be of the same length +% Or matrix of stick functions, 1 column per condition +% +% +% tp: number of timepoints to estimate in hrf deconvolution matrix +% eres: timebins in sf array for each TR +% +% DX: deconvolution matrix +% estimates O.tp time points for each condition +% Time resolution is in TRs +% +% sf: stick function resampled at TR +% +% Optional: +% 1 - TRs before: 0 or number of time-points to shift LEFT +% 2 - number of sessions; if > 1, adds session-specific intercepts +% 3 - docenter, 1/0 for do/do not center columns, default 0 +% 4 - scanspersess: how many scans per session? prevents regressors from +% running over into the next session (recursive). +% +% +% No parametric modulation of sf's allowed. +% +% Tor Wager, 10/20/01 modified 9/20/02 for variable tp's for diff evt types +% modified 4/22/04 to center columns, 2/9/05 for multi-session boundary +% respect + +docenter = 0; +if nargin > 5, docenter = varargin{3};,end + +if ~iscell(sf) + %sf = mat2cell(sf,size(sf,1),ones(size(sf,2))); + for i = 1:size(sf,2), sf2{i} = sf(:,i);, end + sf = sf2; +end + +if length(tp) == 1, tp = repmat(tp,1,length(sf));, end +if length(tp) ~= length(sf), error('timepoints vectors (tp) and stick function (sf) lengths do not match!'), end + +tbefore = 0; +nsess = size(sf,1); + +if nargin > 4, nsess = varargin{2};, end +if nargin > 3, tbefore = varargin{1};, end + +shiftElements = eres; +% each time point is timeRes TRs. + + + + +% multiple sessions +% prevent regressors from running across session lines +if nargin > 6, numframes = varargin{4};, + + st = cumsum([1 numframes]); + en = st(2:end) - 1; % ending values + st = st(1:end-1); % starting values + + for sess = 1:length(numframes) + + % get ons for this session only + for i = 1:length(sf) + sfsess{i} = sf{i}(st(sess):en(sess)); + end + + % get DX for this session only, no centering + [DXs{sess,1}] = tor_make_deconv_mtx3(sfsess,tp,eres,varargin{1},varargin{2},0); + end + + % concatenate across sessions + DX = cat(1,DXs{:}); + + % intercepts + %warning('intercept term removed'); + DX = DX(:,1:end-1); % remove overall intercept + DX = [DX intercept_model(repmat(numframes, 1, length(numframes)))]; % get session-specific + +else + % run the single-session model + + +% ------------------------------------------------------------------- +% * downsample sf to number of TRs +% ------------------------------------------------------------------- +numtrs = round(length(sf{1}) ./ eres); +myzeros = zeros(numtrs,1); +origsf = sf; + +for i = 1:length(sf) + Snumtrs = length(sf{i}) ./ eres; + if Snumtrs ~= round(Snumtrs), warning(['sf{ ' num2str(i) '}: length not evenly divisible by eres.']),end + if numtrs ~= Snumtrs, warning(['sf{ ' num2str(i) '}: different length than sf{1}.']),end + + inums = find(sf{i} > 0); + inums = inums ./ eres; % convert to TRs + inums = ceil(inums); % nearest TR + % i'm getting weird effects with round + % and stimuli onsets in between TRs - + % if the 1 in the matrix occurs before the + % onset of the actual event, the est. HRF is inverted - + % thus i'm now using ceiling. + inums(inums == 0) = 1; % never use 0th element + sf{i} = myzeros; + sf{i}(inums) = 1; % always use 1 for sf +end + +% plot to check sampling of delta function +%figure; +%for i = 1:length(sf) +% subplot(length(sf),1,i);hold on +% plot(1:1/length(origsf{i}):2-1/length(origsf{i}),origsf{i}) +% plot(1:1/length(sf{i}):2-1/length(sf{i}),sf{i},'r') +%end + +% ------------------------------------------------------------------- +% * make deconvolution matrix DX +% ------------------------------------------------------------------- + +index = 1; +for i = 1:size(sf,2) + + if tbefore ~= 0 + for j = tbefore:-1:1 + mysf = [sf{i}(j+1:end); zeros(j,1)]; + DX(:,index) = mysf; + index = index + 1; + end + end + + DX(:,index) = sf{i}; + index = index + 1; + inums = find(sf{i} == 1); + + for j = 2:tp(i) + inums = inums + 1; % + 1 because we've downsampled already. + shiftElements; + reg = myzeros; + reg(inums) = 1; + reg = reg(1:numtrs); + while length(reg) < size(DX,1), reg = [reg;0];,end % add 0's if too short + try + DX(:,index) = reg; + catch + whos DX + whos reg + error('Different column lengths!') + end + index = index + 1; + end + +end + +% ------------------------------------------------------------------- +% * add intercept +% ------------------------------------------------------------------- +if nsess < 2 + DX(:,end+1) = 1; +else + index = 1; + scanlen = size(DX,1) ./ nsess; + if round(scanlen) ~= scanlen, warning('Model length is not an even multiple of scan length.'),end + + for startimg = 1:scanlen:size(DX,1) + X(startimg:startimg+scanlen-1,index) = 1; + index = index + 1; + end + + DX = [DX X]; +end + + +end % multi-session vs. single + +if docenter + % center columns (not intercepts) + wh = 1:size(DX,2)-nsess; + DX(:,wh) = DX(:,wh) - repmat(mean(DX(:,wh)),size(DX,1),1); +end + + +return \ No newline at end of file diff --git a/Parcellation_tools/cluster_princomp.m b/Parcellation_tools/cluster_princomp.m new file mode 100755 index 00000000..ab6e66f8 --- /dev/null +++ b/Parcellation_tools/cluster_princomp.m @@ -0,0 +1,372 @@ +function [clusters,subclusters] = cluster_princomp(clusters,varargin) +% function [clusters,subclusters] = cluster_princomp(clusters,[behavioral score vector],[corr flag],[plotflag],[locflag]) +% +% ALSO TRY: subcluster_montage(subclusters{1}) % to plot the output +% +% clusters is structure of clusters from tor_extract_rois.m +% behavioral vector is row vector of behavioral or other scores to correlate +% corr flag: *1 = work on correlations among voxels, 2 = work on covariance +% plotflag: *1 = yes, 0 = no. plots. +% locflag: 1 yes, *0 no; add XYZ voxel locations (scaled) to data submitted to clustering +% pushes voxels closer in space to be classified in the same cluster +% +% try this to test the program on random data: +% cl(1).all_data = randn(23,30);cl(1).numVox = 30;cl = cluster_princomp(cl,EXPT.behavior,1,1); +% cl(1).all_data(:,1:10) = cl(1).all_data(:,1:10) + 10; cl = cluster_princomp(cl,EXPT.behavior,1,1); +% cl(1).all_data(:,25:30) = cl(1).all_data(:,25:30) + repmat((EXPT.behavior .* 3)',1,6); +% cl(1).all_data(:,21:24) = cl(1).all_data(:,21:24) + repmat((1:23)',1,4); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% mean-center everything now: +% cl.PCA = []; cl.all_data - cl.all_data - repmat(mean(cl.all_data),size(cl.all_data,1),1); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% add another correlated group: +% cl.all_data(:,1:5) = cl.all_data(:,1:5) + repmat(rand(23,1)*5,1,5); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% +% if component scores are used and correlated with behavior, this means that the subjects +% tend to show the behavioral effect who also show the pattern associated with comp. x. +% this may mean high on a number of voxels, or high on some and low on others. +% the weights may be used to interpret what the components mean, and this can be done +% graphically. +% +% t-tests on component scores have ambiguous interpretations, because a high t-score +% may indicate negative values or close-to-zero values on some voxels. +% a component could have the interpretation, "high on this component means high on V1 +% and low on V2." +% +% classifying voxels is done using cluster analysis (hierarchical, centroid linkage) +% on the voxels (observations) using the PCA weights (eigenvectors) as variables. +% This lets the clustering algorithm work in the reduced variable space with dimensionality +% equal to the number of components. +% The max number of clusters is restricted based on the gradient of the eigenvalues in the PCA +% maxclusters = 1 + the number of eigenvalues with gradient at least 20% of the initial drop +% from 1 to 2 eigenvalues. +% +% Requires clustering library in Matlab. +% Robust option also uses the robust PCA algorithm RAPCA, +% created by: +% Hubert, M., Rousseeuw, P.J., Verboven, S. (2002), +% "A fast method for robust principal components with applications to chemometrics", by Mia Hubert, Peter J. Rousseeuw, +% Chemometrics and Intelligent Laboratory Systems, 60, 101-111. +% +% + +corrflag = 1; plotflag = 1; robustflag = 1; locflag = 0; +if length(varargin) > 1, corrflag = varargin{2};, end +if length(varargin) > 2, plotflag = varargin{3};, end +if length(varargin) > 3, locflag = varargin{4};, end + +for i = 1:length(clusters) + subclusters{i} = []; clusters(i).PCA = []; + + % check for NaNs and remove those voxels + tst = any(isnan(clusters(i).all_data),1); + clusters(i).all_data(:,tst) = []; + clusters(i).XYZ(:,tst) = []; + clusters(i).XYZmm(:,tst) = []; + clusters(i).Z(:,tst) = []; + if any(tst),disp(['Warning! Removed ' num2str(sum(tst)) ' voxels with NaN values.']),end + + a = clusters(i).all_data; + + % scale here, because robust pca doesn't use correlations, does it? + % but robust PCA seems to be unaffected by scale changes on some variables + if corrflag, a = scale(a);,end + + % if we choose to add the XYZ flag to add locations to clustering criteria + if locflag, + wfactor = round(size(a,1) ./ 3); % weight for loc; higher = more weight on location + xyztmp = repmat(scale(clusters(i).XYZ')',wfactor,1); % scale to make comparable to img values, + % but multiply by weighting factor + a = [a; xyztmp]; + end + + + if size(a,2) > 2 % must have 3 voxels to try clustering + + % ------------------------------------------------------------------------------- + % * All the real work is done here. Compute pc's + % ------------------------------------------------------------------------------- + + if ~robustflag + [clusters(i).PCA.pcomps,clusters(i).PCA.weights,clusters(i).PCA.eigval,clusters(i).PCA.class] = pc(a,corrflag); + + % automatically pick number of clusters, based on gradient in eigenvalues + g = abs(gradient(clusters(i).PCA.eigval)); + maxclusters = sum(g > g(1).*.2) + 1; + else + + % pick number of dimensions by hand + disp(' ') + disp([num2str(clusters(i).numVox) ' voxels in main cluster']) + fprintf(1,'%3.2f Observations on %3.2f voxels\n',size(a,1),size(a,2)) + fprintf(1,'Save at least 2 eigenvectors to do clustering'); + % doing this on a' means voxels are observations, conditions/subj scores are variables + % we'll use the scores, which has voxels as rows and components as columns, to classify + out = rapca(a'); + clusters(i).PCA.pcomps = out.T; + clusters(i).PCA.weights = out.P; + clusters(i).PCA.eigval = out.L; + maxclusters = length(out.L); + end + + % ------------------------------------------------------------------------------- + % * All the real work is done here. Classify + % ------------------------------------------------------------------------------- + + if size(clusters(1).all_data,1) > 12, + disp('More than 12 dimensions (observations per voxel) in original data - using eigenvectors to classify') + close all; try, pack, catch, end + % we pick the number of CLASSES separately by hand, because # components not a good indicator + % of how many classes there are + clusters(i).PCA.class = docluster(out.T',[],plotflag); + else + clusters(i).PCA.class = docluster(out.T',[],plotflag); + end + + % clean up if using locflag + clusters(i).PCA.locflag = locflag; + if locflag, + clusters(i).PCA.pcomps = clusters(i).PCA.pcomps(1:size(clusters(i).all_data,1),:); + %clusters(i).PCA.weights = clusters(i).PCA.weights(1:size(clusters(i).all_data,1),:); + %clusters(i).PCA.avgs = clusters(i).PCA.avgs(1:size(clusters(i).all_data,1),:); + end + + + % ------------------------------------------------------------------------------- + % for each group, separate into contiguous clusters + % ------------------------------------------------------------------------------- + + grps = unique(clusters(i).PCA.class(clusters(i).PCA.class~=0)); % values are component of origin + for j = 1:length(grps), + wh = find(clusters(i).PCA.class==grps(j)); + XYZ = clusters(i).XYZ(:,wh); + cl_index = spm_clusters(XYZ) ./ 100; + clusters(i).PCA.class(wh) = clusters(i).PCA.class(wh) + cl_index; + end + ngrps = length(unique(clusters(i).PCA.class)); + fprintf(1,'%3.0f contiguous clusters separated by class',ngrps) + + % ------------------------------------------------------------------------------- + % average within classes / contiguous regions + % ------------------------------------------------------------------------------- + + grps = unique(clusters(i).PCA.class(clusters(i).PCA.class~=0)); % values are component of origin + for j = 1:length(grps), + clusters(i).PCA.avgs(:,j) = mean(a(:,find(clusters(i).PCA.class==grps(j))),2);, + freq(j) = sum(clusters(i).PCA.class == grps(j)); + end + + disp(['Cluster ' num2str(i) ', ' num2str(clusters(i).numVox) ' voxels: ' num2str(size(clusters(i).PCA.pcomps,2)) ' components']) + fprintf(1,'\tMean\tEigval\tcorrel\t') + + % ------------------------------------------------------------------------------- + % * display each component and correlation with behavior + % ------------------------------------------------------------------------------- + + for j = 1:size(clusters(i).PCA.pcomps,2) + + %[H,P,CI,STATS] = TTEST(clusters(i).PCA.pcomps(:,j),0,.05,0); + % skip the t-test. t-tests on component scores don't make a lot of sense. + fprintf(1,'\n\t%3.3f\t%3.3f\t',mean(clusters(i).PCA.pcomps(:,j)),clusters(i).PCA.eigval(j)) + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.pcomps(:,j),varargin{1}); + co = co(1,2); + fprintf(1,'%3.3f\t',co) + end + end + + end + fprintf(1,'\n') + + % ------------------------------------------------------------------------------- + % * display classification info + % ------------------------------------------------------------------------------- + disp(['Classified into ' num2str(ngrps) ' groups:']) + fprintf(1,'\tClass\tVoxels\tMean\tt\tp\tcorrect. p\tcorrel\t') + + % for each component, test mean value and correlation with behavior + for j = 1:length(grps) + + [H,P,CI,STATS] = TTEST(clusters(i).PCA.avgs(:,j),0,.05,0); + fprintf(1,'\n\t%3.0f\t%3.0f\t%3.3f\t%3.3f\t%3.3f\t%3.3f\t',j,freq(j),mean(clusters(i).PCA.avgs(:,j)),STATS.tstat,P,P .* size(clusters(i).PCA.avgs,2)) + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.avgs(:,j),varargin{1}); + co = co(1,2); + fprintf(1,'%3.3f\t',co) + end + end + + end + fprintf(1,'\n') + + % ------------------------------------------------------------------------------- + % * Plot, if requested + % ------------------------------------------------------------------------------- + + if plotflag, + figure('Color','w'), subplot(1,3,1), imagesc(a), title(['Cl ' num2str(i) ': Data']), xlabel('Voxels'),ylabel('Subjects') + subplot(1,3,2), imagesc(clusters(i).PCA.weights'), title(['Weights (eigenvectors)']), xlabel('Voxels'),ylabel('Eigenvectors') + subplot(1,3,3), imagesc(clusters(i).PCA.pcomps), title(['Component scores (predictions)']), xlabel('Voxels'),ylabel('Subjects') + + a = [clusters(i).PCA.class' a']; a=sortrows(a,1); a = a(:,2:end)'; + figure;subplot 131; imagesc(a),title(['Cl ' num2str(i) ':Data sorted by class']), xlabel('Class'),ylabel('Subjects'), + xlab = [sort(clusters(i).PCA.class(clusters(i).PCA.class~=0)) clusters(i).PCA.class(clusters(i).PCA.class==0)]; + set(gca,'XTick',1:length(clusters(i).PCA.class)); set(gca,'XTickLabel',xlab) + subplot 132; imagesc(clusters(i).PCA.avgs),title('Class averages'), xlabel('Class'),ylabel('Subjects'), + subplot 133; if length(varargin) > 0, if ~isempty(varargin{1}), imagesc(varargin{1}'), title('Behavior'),end,end + + end + + else + disp(['Cluster ' num2str(i) ' has less than 3 voxels.']) + clusters(i).PCA.class = ones(1,clusters(i).numVox); + clusters(i).PCA.avgs = clusters(i).timeseries; + grps = 1; + if ~isfield(clusters,'correl'), clusters(1).correl = [];, end + end + + + % ------------------------------------------------------------------------------- + % * separate into subclusters, based on class membership + % ------------------------------------------------------------------------------- + + if ~isfield(clusters,'correl'), clusters(i).correl = [];, end + disp('Recomputing correlations and z-scores and saving in subclusters') + if ~isfield(clusters,'XYZ'), clusters(i).XYZ = ones(3,size(clusters(i).all_data,2));, end + if ~isfield(clusters,'Z'), clusters(i).Z = ones(1,size(clusters(i).XYZ,2));, end + clear subc + + for j = 1:length(grps) + + try + subc(j) = clusters(i); + catch + warning('clusters does not have all required fields.'); break + end + + wh = find(clusters(i).PCA.class == grps(j)); + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.avgs(:,j),varargin{1}); + subc(j).correl = co(1,2); + end + end + + if isfield(subc(j),'title'), subc(j).title = [subc(j).title '_SUBCL_' num2str(j)];, end + if isfield(subc(j),'name'), subc(j).name = [subc(j).name '_SUBCL_' num2str(j)];, end + if isfield(subc(j),'numVox'), subc(j).numVox = length(wh);, end + if isfield(subc(j),'Z'), subc(j).Z = subc(j).Z(wh);, end + if isfield(subc(j),'XYZmm'), subc(j).XYZmm = subc(j).XYZmm(:,wh);, end + if isfield(subc(j),'XYZ'), subc(j).XYZ = subc(j).XYZ(:,wh);, end + + if isfield(subc(j),'timeseries'), subc(j).timeseries = nanmean(subc(j).all_data(:,wh)')';, end + %if isfield(subc(j),'snr'), subc(j).snr = subc(j).snr(wh);, end + if isfield(subc(j),'center'), subc(j).center = center_of_mass(subc(j).XYZ,subc(j).Z);, end + if isfield(subc(j),'mm_center'), subc(j).mm_center = center_of_mass(subc(j).XYZmm,subc(j).Z);, end + + if isfield(subc(j),'all_data'), + subc(j).all_data = subc(j).all_data(:,wh);, + for k = 1:size(subc(j).all_data,2) + [H,P,CI,STATS] = ttest(subc(j).all_data(:,k),0,.05,0); + subc(j).Z(k) = spm_t2z(STATS.tstat,STATS.df); + end + end + + + end + + subclusters{i} = subc; + + +end + + +return + + + +function [b,v,d,class] = pc(a,corrflag) +% a is original matrix, b is principal components, v is eigenvectors +% (weights on columns, which = weights on voxels) +% class is classification of voxels into groups based on component loadings + +if corrflag, [v,d]=eig(corrcoef(a));, else, [v,d]=eig(cov(a));,end +b = (pinv(v) * a')' ./ repmat((diag(d)').^.5,size(a,1),1); +% i made this up: think of rptating each subject's scores (in cols of a') +% by the rotation matrix pinv(v), and normalizing by the sqrt of the eigenvalues +% pinv(v) and v are rotation matrices because det = 1, no shearing or dilation +% +% this appears to work to give scores as well +% both methods (above,below) are scaled versions of the splus factor scores +% the problem is that doing it two different ways in splus flips the signs +% of some components and not others (gui vs cmd line). + +%X = a; R = corrcoef(a); A = v * (d^.5); B = inv(R) * A; +%scores = X * B; +% X is data, A is factor loading matrix, B is factor score coeff matrix +% this method, from the text, and the one giving b above produce identical results + +A = v * (d^.5); + +b = fliplr(b); v = fliplr(v); A = fliplr(A); %scores = fliplr(scores); + +num = min(10,sum(diag(d) >= 1)); +b = b(:,1:num); v = v(:,1:num); A = A(:,1:num); +origd = diag(d); +d = diag(d)'; d= fliplr(d); d = d(1:num); + +if num == 0, warning('No eigenvalues above 1!');, origd, class = []; + +else + % classify each voxel into a group based on loading + % use A, which re-introduces the comp variance, because we + % want relationships with more variance to count more. + % This just doesn't work so hot. See docluster, below. + + %wh = A' == repmat(max(A'),size(A,2),1); + %for i = 1:size(wh,2), tmp = find(wh(:,i));, class(i) = tmp(1); end + %class(max(A') < .3) = 0; + +end + + +%figure;plot(b,'r'),hold on;plot(a,'k'), hold on; plot(mean(a,2),'g--'),legend({'eig' 'orig' 'avg'}) + +return + + + +function class = docluster(a,maxclusters,doplot) + + Y = pdist(a','euclid'); % transpose so the voxels are observations, eigenvectors the variables + Z = linkage(Y,'complete'); + if maxclusters > 1 + class = cluster(Z,maxclusters)'; + + if doplot, + dendrogram(Z,0); title('Dendrogram for clustering') + end + + else + dendrogram(Z,0); title('Dendrogram for clustering') + set(gcf,'Position',[10 601 800 500]) + maxclusters = input('Pick number of classes to save: '); + + if maxclusters == 1, + class = ones(1,size(a,2)); + else + class = cluster(Z,maxclusters)'; + end + + end + + + +return + \ No newline at end of file diff --git a/Parcellation_tools/inconsistent.m b/Parcellation_tools/inconsistent.m new file mode 100755 index 00000000..945e8869 --- /dev/null +++ b/Parcellation_tools/inconsistent.m @@ -0,0 +1,75 @@ +function Y = inconsistent(Z,depth) +%INCONSISTENT Inconsistent values of a cluster tree. +% Y = INCONSISTENT(Z) computes the inconsistent value of each non-leaf +% node in the hierarchical cluster tree Z. Z is a (M-1)-by-3 matrix +% generated by the function LINKAGE. Each inconsistent value is a +% measure of separation between the two clusters whose merge is +% represented by that node, compared to the separation between +% subclusters merged within those clusters. +% +% Y = INCONSISTENT(Z, DEPTH) computes inconsistent values by looking +% to a depth DEPTH below each node. +% +% Y is a (M-1)-by-4 matrix, with rows corresponding to each of the +% non-leaf nodes represented in Z. INCONSISTENT computes the +% inconsistent value for node (M+i) using S_i, the set of nodes less than +% DEPTH branches below node (M+i), excluding any leaf nodes. Then +% +% Y(i,1) = mean(Z(S_i,3)), the mean height of nodes in S_i +% Y(i,2) = std(Z(S_i,3)), the standard deviation of node heights in S_i +% Y(i,3) = length(S_i), the number of nodes in S_i +% Y(i,4) = (Z(i,3) - Y(i,1))/Y(i,2), the inconsistent value +% +% The default value for DEPTH is 2. +% +% See also PDIST, LINKAGE, COPHENET, DENDROGRAM, CLUSTER, CLUSTERDATA. + +% Copyright 1993-2004 The MathWorks, Inc. +% $Revision: 1.10.4.3 $ + +if nargin < 2, depth = 2; end + +m = size(Z,1); + +Y = zeros(m,4); + +for k = 1:m + s = zeros(4,1); + s = tracetree(Z, s, k, depth); + Y(k,1) = s(1)/s(3); % average edge length + V = (s(2) - (s(1)*s(1))/s(3))/(s(3)-(s(3)~=1)); + Y(k,2) = sqrt(max(0,V)); % standard deviation, avoid roundoff to neg number + Y(k,3) = s(3); % number of edges + if Y(k,2) > 0 + Y(k,4) = (Z(k,3) - Y(k,1))/Y(k,2); + else + Y(k,4) = 0; + end +end + +function s = tracetree(Z,s,k,depth) +% iterative function to search down the tree. +m = size(Z,1)+1; +klist = zeros(m,1); +klist(1) = k; +dlist(1) = depth; +topk = 1; +currk = 1; +while(currk <= topk) + k = klist(currk); + depth = dlist(currk); + s(1) = s(1) + Z(k,3); % sum of the edge lengths so far + s(2) = s(2) + Z(k,3)*Z(k,3); % sum of the square of the edge length + s(3) = s(3) + 1; % number of the edges so far + + if depth > 1 % depth is greater than 0, need to go down further + for i = Z(k,1:2); % left and right subtree indices + if i > m % node i is not a leaf, it has subtrees + topk = topk+1; + klist(topk) = i-m; + dlist(topk) = depth-1; + end + end + end + currk = currk+1; +end diff --git a/Parcellation_tools/mask_princomp.m b/Parcellation_tools/mask_princomp.m new file mode 100755 index 00000000..bda580ac --- /dev/null +++ b/Parcellation_tools/mask_princomp.m @@ -0,0 +1,325 @@ +function [clout] = mask_princomp(clusters,varargin) +% function [clusters] = mask_princomp(clusters,[behavioral score vector],[corr flag],[plotflag],[saveflag]) +% +% this function is just like cluster_princomp, except that it works on the SET +% of activations in all clusters, rather than within each cluster. +% +% clusters is structure of clusters from tor_extract_rois.m +% +% behavioral vector is row vector of behavioral or other scores to correlate +% corr flag: 1 = work on correlations among voxels, 2 = work on covariance +% plotflag: 1 = yes, 0 = no. plots. +% +% +% try this to test the program on random data: +% cl(1).all_data = randn(23,30);cl(1).numVox = 30;cl = cluster_princomp(cl,EXPT.behavior,1,1); +% cl(1).all_data(:,1:10) = cl(1).all_data(:,1:10) + 10; cl = cluster_princomp(cl,EXPT.behavior,1,1); +% cl(1).all_data(:,25:30) = cl(1).all_data(:,25:30) + repmat((EXPT.behavior .* 3)',1,6); +% cl(1).all_data(:,21:24) = cl(1).all_data(:,21:24) + repmat((1:23)',1,4); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% mean-center everything now: +% cl.PCA = []; cl.all_data - cl.all_data - repmat(mean(cl.all_data),size(cl.all_data,1),1); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% add another correlated group: +% cl.all_data(:,1:5) = cl.all_data(:,1:5) + repmat(rand(23,1)*5,1,5); +% cl = cluster_princomp(cl,EXPT.behavior,1,1); +% +% if component scores are used and correlated with behavior, this means that the subjects +% tend to show the behavioral effect who also show the pattern associated with comp. x. +% this may mean high on a number of voxels, or high on some and low on others. +% the weights may be used to interpret what the components mean, and this can be done +% graphically. +% +% t-tests on component scores have ambiguous interpretations, because a high t-score +% may indicate negative values or close-to-zero values on some voxels. +% a component could have the interpretation, "high on this component means high on V1 +% and low on V2." +% +% classifying voxels is done using cluster analysis (hierarchical, centroid linkage) +% on the voxels (observations) using the PCA weights (eigenvectors) as variables. +% This lets the clustering algorithm work in the reduced variable space with dimensionality +% equal to the number of components. +% The max number of clusters is restricted based on the gradient of the eigenvalues in the PCA +% maxclusters = 1 + the number of eigenvalues with gradient at least 20% of the initial drop +% from 1 to 2 eigenvalues. +% +% Requires clustering library in Matlab. +% Robust option also uses the robust PCA algorithm RAPCA, +% created by: +% Hubert, M., Rousseeuw, P.J., Verboven, S. (2002), +% "A fast method for robust principal components with applications to chemometrics", by Mia Hubert, Peter J. Rousseeuw, +% Chemometrics and Intelligent Laboratory Systems, 60, 101-111. +% +% + +corrflag = 1; plotflag = 1; robustflag = 1; saveflag = 0; +if length(varargin) > 1, corrflag = varargin{2};, end +if length(varargin) > 2, plotflag = varargin{3};, end +if length(varargin) > 3, saveflag = varargin{4};, end + +% get all the voxels and data together + +CLU = clusters2CLU(clusters); +CLU.all_data = cat(2,clusters.all_data); + +clear clusters; +clusters(1) = CLU; +i = 1; +subclusters{1} = []; clusters(i).PCA = []; + +disp('Output saved in mask_princomp.out') +diary mask_princomp.out + +a = CLU.all_data; + + if size(a,2) > 2 % must have 3 voxels to try clustering + + % ------------------------------------------------------------------------------- + % * All the real work is done here. Compute pc's and clustering + % ------------------------------------------------------------------------------- + + if ~robustflag + [clusters(i).PCA.pcomps,clusters(i).PCA.weights,clusters(i).PCA.eigval,clusters(i).PCA.class] = pc(a,corrflag); + + % automatically pick number of clusters, based on gradient in eigenvalues + g = abs(gradient(clusters(i).PCA.eigval)); + maxclusters = sum(g > g(1).*.2) + 1; + else + + % pick number of clusters by hand + out = rapca(a); + clusters(i).PCA.pcomps = out.T; + clusters(i).PCA.weights = out.P; + clusters(i).PCA.eigval = out.L; + maxclusters = length(out.L); + + if saveflag, + try mkdir mask_pca_results, catch, end + saveas(gcf,'mask_pca_results/robust_pca_diagnostic','fig'), close + saveas(gcf,'mask_pca_results/robust_eigenvalues','fig'), close + end + + end + + clusters(i).PCA.class = docluster(a,maxclusters,plotflag); + + grps = unique(clusters(i).PCA.class(clusters(i).PCA.class~=0)); % values are component of origin + for j = 1:length(grps), + clusters(i).PCA.avgs(:,j) = mean(a(:,find(clusters(i).PCA.class==grps(j))),2);, + freq(j) = sum(clusters(i).PCA.class == grps(j)); + end + + disp([clusters(i).title ', ' num2str(clusters(i).numVox) ' voxels: ' num2str(size(clusters(i).PCA.pcomps,2)) ' components']) + fprintf(1,'\tMean\tEigval\tcorrel\t') + + % ------------------------------------------------------------------------------- + % * display each component and correlation with behavior + % ------------------------------------------------------------------------------- + + for j = 1:size(clusters(i).PCA.pcomps,2) + + %[H,P,CI,STATS] = TTEST(clusters(i).PCA.pcomps(:,j),0,.05,0); + % skip the t-test. t-tests on component scores don't make a lot of sense. + fprintf(1,'\n\t%3.3f\t%3.3f\t',mean(clusters(i).PCA.pcomps(:,j)),clusters(i).PCA.eigval(j)) + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.pcomps(:,j),varargin{1}); + co = co(1,2); + fprintf(1,'%3.3f\t',co) + end + end + + end + fprintf(1,'\n') + + % ------------------------------------------------------------------------------- + % * display classification info + % ------------------------------------------------------------------------------- + disp(['Classified into ' num2str(max(clusters(i).PCA.class)) ' groups:']) + fprintf(1,'\tClass\tVoxels\tMean\tt\tp\tcorrect. p\tcorrel\t') + + % for each component, test mean value and correlation with behavior + for j = 1:length(grps) + + [H,P,CI,STATS] = TTEST(clusters(i).PCA.avgs(:,j),0,.05,0); + fprintf(1,'\n\t%3.0f\t%3.0f\t%3.3f\t%3.3f\t%3.3f\t%3.3f\t',j,freq(j),mean(clusters(i).PCA.avgs(:,j)),STATS.tstat,P,P .* size(clusters(i).PCA.avgs,2)) + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.avgs(:,j),varargin{1}); + co = co(1,2); + fprintf(1,'%3.3f\t',co) + end + end + + end + fprintf(1,'\n') + + % ------------------------------------------------------------------------------- + % * Plot, if requested + % ------------------------------------------------------------------------------- + + if plotflag, + figure('Color','w'), subplot(1,3,1), imagesc(a), title(['Cl ' num2str(i) ': Data']), xlabel('Voxels'),ylabel('Subjects') + subplot(1,3,2), imagesc(clusters(i).PCA.weights'), title(['Weights (eigenvectors)']), xlabel('Voxels'),ylabel('Eigenvectors') + subplot(1,3,3), imagesc(clusters(i).PCA.pcomps), title(['Component scores (predictions)']), xlabel('Voxels'),ylabel('Subjects') + + if saveflag, + try mkdir mask_pca_results, catch, end + disp('Images saved in mask_pca_results folder') + saveas(gcf,'mask_pca_results/PCA1','fig'), + end + + a = [clusters(i).PCA.class' a']; a=sortrows(a,1); a = a(:,2:end)'; + figure;subplot 131; imagesc(a),title(['Cl ' num2str(i) ':Data sorted by class']), xlabel('Class'),ylabel('Subjects'), + xlab = [sort(clusters(i).PCA.class(clusters(i).PCA.class~=0)) clusters(i).PCA.class(clusters(i).PCA.class==0)]; + set(gca,'XTick',1:length(clusters(i).PCA.class)); set(gca,'XTickLabel',xlab) + subplot 132; imagesc(clusters(i).PCA.avgs),title('Class averages'), xlabel('Class'),ylabel('Subjects'), + subplot 133; if length(varargin) > 0, if ~isempty(varargin{1}), imagesc(varargin{1}'), title('Behavior'),end,end + + if saveflag, saveas(gcf,'mask_pca_results/PCA_with_clustering','fig'),end + end + + else + disp(['Cluster ' num2str(i) ' has less than 3 voxels.']) + clusters(i).PCA.class = ones(1,clusters(i).numVox); + clusters(i).PCA.avgs = clusters(i).timeseries; + grps = 1; + if ~isfield(clusters,'correl'), clusters(1).correl = [];, end + end + + diary off + + % ------------------------------------------------------------------------------- + % * separate into subclusters, based on class membership + % ------------------------------------------------------------------------------- + + for j = 1:length(grps) + + wh = find(clusters(i).PCA.class == grps(j)); + + if length(varargin) > 0 + if ~isempty(varargin{1}) + co = corrcoef(clusters(i).PCA.avgs(:,j),varargin{1}); + subc(j).correl = co(1,2); + end + end + + CLUcomp = clusters(1); + CLUcomp.XYZmm = CLUcomp.XYZmm(:,wh); + CLUcomp.XYZ = CLUcomp.XYZ(:,wh); + CLUcomp.Z = CLUcomp.Z(:,wh); + CLUcomp.all_data = CLUcomp.all_data(:,wh); + CLUcomp.numVox = size(CLUcomp.all_data,2); + + clout{j} = tor_extract_rois([],CLUcomp,CLUcomp); + if plotflag + montage_clusters([],clout{j}) + if saveflag, saveas(gcf,['mask_pca_results/montage_subset' num2str(j)],'fig'),end + end + + end + + + +end + + +return + + + +function [b,v,d,class] = pc(a,corrflag) +% a is original matrix, b is principal components, v is eigenvectors +% (weights on columns, which = weights on voxels) +% class is classification of voxels into groups based on component loadings + +if corrflag, [v,d]=eig(corrcoef(a));, else, [v,d]=eig(cov(a));,end +b = (pinv(v) * a')' ./ repmat((diag(d)').^.5,size(a,1),1); +% i made this up: think of rptating each subject's scores (in cols of a') +% by the rotation matrix pinv(v), and normalizing by the sqrt of the eigenvalues +% pinv(v) and v are rotation matrices because det = 1, no shearing or dilation +% +% this appears to work to give scores as well +% both methods (above,below) are scaled versions of the splus factor scores +% the problem is that doing it two different ways in splus flips the signs +% of some components and not others (gui vs cmd line). + +%X = a; R = corrcoef(a); A = v * (d^.5); B = inv(R) * A; +%scores = X * B; +% X is data, A is factor loading matrix, B is factor score coeff matrix +% this method, from the text, and the one giving b above produce identical results + +A = v * (d^.5); + +b = fliplr(b); v = fliplr(v); A = fliplr(A); %scores = fliplr(scores); + +num = min(10,sum(diag(d) >= 1)); +b = b(:,1:num); v = v(:,1:num); A = A(:,1:num); +origd = diag(d); +d = diag(d)'; d= fliplr(d); d = d(1:num); + +if num == 0, warning('No eigenvalues above 1!');, origd, class = []; + +else + % classify each voxel into a group based on loading + % use A, which re-introduces the comp variance, because we + % want relationships with more variance to count more. + % This just doesn't work so hot. See docluster, below. + + %wh = A' == repmat(max(A'),size(A,2),1); + %for i = 1:size(wh,2), tmp = find(wh(:,i));, class(i) = tmp(1); end + %class(max(A') < .3) = 0; + +end + + +%figure;plot(b,'r'),hold on;plot(a,'k'), hold on; plot(mean(a,2),'g--'),legend({'eig' 'orig' 'avg'}) + +return + + + +function class = docluster(a,maxclusters,doplot) + + + if size(a,2) > 300, + Y = pdist_by_parts(a'); + else + Y = pdist(a','euclid'); % transpose so the voxels are observations, eigenvectors the variables + end + Z = linkage(Y,'centroid'); + class = cluster(Z,maxclusters)'; + + if doplot, + dendrogram(Z,0); title('Dendrogram for clustering') + end + +return + + +function pd = pdist_by_parts(x) + +disp(['Computing distances between observations']) + +indx = 1; +vindx = 2; + +for i = 1:size(x,1) + indx = 1; % faster this way + for j = vindx:size(x,1) + pd{i}(indx) = (sum((x(i,:) - x(j,:)).^2)).^.5; + indx = indx+1; + end + vindx = vindx+1; + if mod(i,100)==0,fprintf(1,'.'),end +end + +pd = cat(2,pd{:}); +fprintf(1,'done\n') + +return + + + \ No newline at end of file diff --git a/Parcellation_tools/parcel_cl_nmds.m b/Parcellation_tools/parcel_cl_nmds.m new file mode 100644 index 00000000..eac55684 --- /dev/null +++ b/Parcellation_tools/parcel_cl_nmds.m @@ -0,0 +1,128 @@ +% [parcel_cl_avgs, NMDS, class_clusters] = parcel_cl_nmds(parcel_cl_avgs) +% +% Documentation not complete. please update me. +% Tor Wager, Oct 2008 +% +% Example: +% ----------------------------------------- +% load Parcellation_info/parcellation.mat +% [parcel_cl_avgs, NMDS, class_clusters] = parcel_cl_nmds(parcel_cl_avgs) +% +% parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, 'save') +% parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, 'save', 'savedir', 'Parcellation_info') +% +% Complete methods example: +% 1. Dimension reduction +% +% Clustering of multivariate data is most stable when the data is not sparse, i.e., the dimensionality is low relative to the number of observations. To limit the dimensionality of the data, a spatio-temporal dimension-reduction step is first performed on the [n x v x N] data matrix of AUC data for n trials x v voxels x N participants (here, n = 48 trials [usually], v = 17,112 voxels , and N = 26 participants). A temporal data reduction is first performed to identify components with correlated AUC trial time series within each participant, followed by a spatial reduction to identify components with correlated spatial patterns across subjects. First, the [n x v] matrix of AUC data for each participant was subjected to PCA, using the [v x v] correlation matrices. Based on the scree plots across subjects, we saved the first 7 eigenvectors (spatial maps). These eigenvectors explained 74 +- 2.5% (st. dev. across subjects) of the variance in the full dataset. These eigenvectors were scaled by their variances (eigenvalues) and concatenated across subjects to form an [v x N*7] matrix of eigenvectors. This matrix was subjected to second (across participant) PCA step to identify components with similar spatial maps across participants. We retained 12 eigenvectors (maps) based on the scree plot, which explained 65% of the variance across individuals. Component scores in this space were used for clustering. This is a data reduction step, and the results are not expected to depend strongly on the number of eigenvectors retained at either step, as long as most of the variance in the data is explained. +% +% 2. Parcellation +% +% Hierarchical agglomerative clustering with average linkage was used to group voxels into parcels--sets of contiguous voxels with similar profiles--in the [v x 12] matrix of component maps. The goal of parcellation was to reduce the space from voxels to parcels (regions) for non-metric multidimensional scaling (NMDS)-based clustering of regions, so that NMDS is computationally tractable. Voxels whose trial AUC time series did not correlate with that of other voxels in the same parcel at p < .001 (in a random effects analysis across participants) were pruned from each parcel. Out of 140 parcels total, 127 parcels with more than 3 voxels after pruning were retained for subsequent analysis. +% +% 3. NMDS and clustering +% +% Parcels were now treated as the unit of analysis, and the AUC trial time series data were extracted from each parcel for each subject. Values in the [n trials x parcels x N subjects] data matrix were z-scored within participant to remove inter-subject differences in scaling, then concatenated into an [n*N x parcels] data matrix. Correlations among parcels were converted into a [parcels x parcels] matrix of distances using the formula distance = (1 - r) / 2. The NMDS stress plot (which operates on ranked distances and is therefore more robust to outliers and does not require a strictly Euclidean distance space) was examined and 7 dimensions (which explained 79% of the variance in distances) were retained for the final cluster analysis. +% Hierarchical agglomerative clustering with average linkage was used to cluster the parcels in this space into interconnected networks with correlated AUC trial time series. To choose the number of clusters in the final solution (k), for every possible choice of clusters between k = 2 and 20, we compared the cluster solution to the average and standard deviation of 1,000 clustering iterations with permuted parcel time series. This yielded a Z-score ([actual solution - mean permuted solution] / standard deviation of permuted solution for each value of k. The best solution was k = 13, with a value of Z = 5.57 compared to the null-hypothesis single-cluster solution (p < .0001). + +function [parcel_cl_avgs, NMDS, class_clusters] = parcel_cl_nmds(parcel_cl_avgs) + + % Get networks of these parcels + % --------------------------- + disp('Getting networks of parcels') + + clear data + N = length(parcel_cl_avgs(1).timeseries); + for i = 1:length(parcel_cl_avgs), for j = 1:N, data{j}(:,i) = parcel_cl_avgs(i).timeseries{j}; end, end + + % Correlate like this: + % --------------------------------------- + % NMDS = xcorr_multisubject(data); + % NMDS.stats.D = (1 - NMDS.stats.mean) ./ 2; + + % OR like this, if we want to be closer to data and don't need inferences across ss on correlations: + % --------------------------------------- + data = []; + for i = 1:length(parcel_cl_avgs) + + % zscore within, or at least center to avoid individual diffs in + % baseline driving correlations + for s = 1:N + parcel_cl_avgs(i).timeseries{s} = scale(parcel_cl_avgs(i).timeseries{s}); + end + + data(:, i) = cat(1, parcel_cl_avgs(i).timeseries{:}); + end + + desired_alpha = .01; + fprintf(['Saving FDR-corrected connections at q < %3.3f corrected\n' desired_alpha]); + + NMDS.parcel_data = data; + [NMDS.stats.c, NMDS.stats.pvals] = corrcoef(data); + [NMDS.stats.fdrthr, NMDS.stats.fdrsig] = fdr_correct_pvals(NMDS.stats.pvals, NMDS.stats.c, desired_alpha); + + NMDS.stats.D = (1 - NMDS.stats.c) ./ 2; + + % Pick number of dimensions + maxclasses = round(length(parcel_cl_avgs) ./ 10); + maxclasses = min(maxclasses, 20); + maxclasses = max(maxclasses, 5); + + disp('Choosing number of dimensions') + [NMDS.stats_mds.GroupSpace,NMDS.stats_mds.obs,NMDS.stats_mds.implied_dissim] = shepardplot(NMDS.stats.D, maxclasses); + + % Clustering with permutation test to get number of clusters + + disp(['Clustering regions in k-D space: 2 to ' num2str(maxclasses) ' classes.']) + + NMDS.stats_mds = nmdsfig_tools('cluster_solution',NMDS.stats_mds, NMDS.stats_mds.GroupSpace, 2:maxclasses, 1000, []); + + + % Set Colors + basecolors = {[1 0 0] [0 1 0] [0 0 1] [1 1 0] [0 1 1] [1 0 1] ... + [1 .5 0] [.5 1 0] [.5 0 1] [1 0 .5] [0 1 .5] [0 .5 1]}; + + NMDS.basecolors = basecolors; + +% % Make figure +% disp('Visualizing results') +% create_figure('nmdsfig') + +% nmdsfig(NMDS.stats_mds.GroupSpace,'classes',NMDS.stats_mds.ClusterSolution.classes, ... +% 'names', [],'sig',NMDS.stats.fdrsig, 'fill', 'colors', basecolors); + + % disp('Saving NMDS structure with networks in parcellation.mat') + % save(fullfile(mysavedir, 'parcellation.mat'), '-append', 'class_clusters', 'parcel*') + + % re-define class clusters + clear class_clusters + for i = 1:max(NMDS.stats_mds.ClusterSolution.classes) + wh = find(NMDS.stats_mds.ClusterSolution.classes == i); + class_clusters{i} = parcel_cl_avgs(wh); + + % refine class (network) membership + [parcel_cl_avgs(wh).from_class] = deal(i); + end + + % disp('Saving NMDS structure and final class clusters in parcellation.mat') + % save(fullfile(mysavedir, 'parcellation.mat'), '-append', 'NMDS', 'class_clusters') + + +end + + +function [pthr,sig] = fdr_correct_pvals(p, r, desired_alpha) + + psq = p; psq(find(eye(size(p,1)))) = 0; + psq = squareform(psq); + pthr = FDR(p, desired_alpha); + if isempty(pthr), pthr = 0; end + + sig = sign(r) .* (p < pthr); + + sig(isnan(sig)) = 0; +end + + + + diff --git a/Parcellation_tools/parcel_cl_nmds_plots.m b/Parcellation_tools/parcel_cl_nmds_plots.m new file mode 100644 index 00000000..86cf4275 --- /dev/null +++ b/Parcellation_tools/parcel_cl_nmds_plots.m @@ -0,0 +1,323 @@ +% Plot: data panel +% +% case 'save', dosave = 1; +% case {'savedir', 'mysavedir'}, mysavedir = varargin{i+1}; +% case {'figs', 'dotfigs'}, savedotfigs = 1; +% +% Doc not complete yet. Please update me! +% +% See parcel_clusters.m +% See parcel_cl_nmds.m +% +% load Parcellation_info/parcellation.mat +% +% parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, 'save') +% parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, 'save', 'savedir', 'Parcellation_info') +% parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, 'save', 'savedir', 'Parcellation_info_tor_mask_try1', 'savedotfig') + +function parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, varargin) + + % -------------------------------------------------- + % SETUP + % -------------------------------------------------- + + dosave = 0; + mysavedir = []; + savedotfigs = 0; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'save', dosave = 1; + + case {'savedir', 'mysavedir'}, mysavedir = varargin{i+1}; + + case {'figs', 'dotfigs'}, savedotfigs = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + N = length(parcel_cl_avgs(1).timeseries); + + if ~isfield(NMDS, 'basecolors') + NMDS.basecolors = {[1 0 0] [0 1 0] [0 0 1] [1 1 0] [0 1 1] [1 0 1] ... + [1 .5 0] [.5 1 0] [.5 0 1] [1 0 .5] [0 1 .5] [0 .5 1]}; + end + + basecolors = NMDS.basecolors; + + % NMDS plot + % -------------------------------------------------- + % Make figure + disp('Visualizing results') + create_figure('nmdsfig'); + + nmdsfig(NMDS.stats_mds.GroupSpace,'classes',NMDS.stats_mds.ClusterSolution.classes, ... + 'names', [],'sig',NMDS.stats.fdrsig, 'fill', 'colors', basecolors); + % -------------------------------------------------- + + % -------------------------------------------------- + % Get class clusters (convenient format) + % -------------------------------------------------- + + % re-define class clusters + clear class_clusters + for i = 1:max(NMDS.stats_mds.ClusterSolution.classes) + wh = find(NMDS.stats_mds.ClusterSolution.classes == i); + class_clusters{i} = parcel_cl_avgs(wh); + + % refine class (network) membership + [parcel_cl_avgs(wh).from_class] = deal(i); + end + + + nclasses = length(class_clusters); % redefine based on results + + % -------------------------------------------------- + % get index of which parcels (clusters) are in which classes + % -------------------------------------------------- + + parcelindx = cat(1, parcel_cl_avgs.from_class); + [parcelindx, ivals] = sort(parcelindx); + cl = parcel_cl_avgs(ivals); + + % -------------------------------------------------- + % Get data + % -------------------------------------------------- + + data = []; + for i = 1:length(cl) + + % zscore within, or at least center to avoid individual diffs in + % baseline driving correlations + for s = 1:N + cl(i).timeseries{s} = scale(cl(i).timeseries{s}); + end + + data(:, i) = cat(1, cl(i).timeseries{:}); + end + + for s = 1:N-1, len(s) = length(cl(1).timeseries{s}); end + len = cumsum([1 len]); + + % -------------------------------------------------- + % Panel TS figure + % -------------------------------------------------- + + create_figure('Panel_timeseries', nclasses, 1); + for i = 1:nclasses + subplot(nclasses, 1, i); + hold on; + mycolor = basecolors{i}; + + [lh, fh] = plot_error(data(:, parcelindx == i)'); + mymean = mean(data(:, parcelindx == i)'); + + set(lh, 'Color', mycolor); + set(fh, 'FaceColor', mycolor); + set(gca, 'YLim', [min(mymean) max(mymean)]); + axis tight + hold on + plot_vertical_line(len); + + if i == 1 + for j = 1:length(len), text(len(j), max(mymean), ['Subj' num2str(j)]); end + end + + axis off + drawnow + end + + % -------------------------------------------------- + % Correlation figure + % -------------------------------------------------- + + + % get within-parcel correlations + %----------------------------------- + for i = 1:length(cl) + dd = cat(1, cl(i).all_data{:}); + rr = corrcoef(dd); + rr = rr .* (1 - eye(size(rr))); + within_corr(i) = mean(squareform(rr)); + end + + create_figure('Correlations'); + [r, p] = corrcoef(data); + + % put withins on diagonal + r = r .* (1 - eye(size(r))); + r = r + diag(within_corr); + %r(p > .05) = NaN; + + % put spaces in to mark off classes + %----------------------------------- + %wh_spaces = [0; diff(parcelindx)]; + rnew = []; + pinew = []; + for i = 1:nclasses + rnew = [rnew; r(parcelindx == i, :)]; + rnew(end + 1, :) = NaN; + + pinew = [pinew; parcelindx(parcelindx == i)]; + pinew(end+1) = NaN; + + end + + rnew2 = []; + for i = 1:nclasses + rnew2 = [rnew2 rnew(:, parcelindx == i)]; + rnew2(:, end + 1) = NaN; + end + r = rnew2; + + % Image the correlation matrix + %----------------------------------- + imagesc(r); colorbar + set(gca,'YDir', 'Reverse') + axis tight + newcm = colormap_tor([0 0 .7], [1 1 0], [1 .5 0]); + colormap(newcm) + title('Correlations among parcels'); + xlabel('Parcel index number'); + + % Color bars for class ID + %----------------------------------- + axpos = get(gca, 'Position'); + axh = axes('Position', [.05 axpos(2) .04 axpos(4)]); + set(axh, 'YDir', 'Reverse', 'YLim', [1 length(pinew)]); + hold on; + for i = 1:nclasses + + yy = find(pinew == i); + plot(.7 * ones(size(yy)), yy, '-', 'Color', basecolors{i}, 'LineWidth', 10); + + text(-2, round(median(yy)), num2str(i), 'FontSize', 18, 'Color', 'k'); + + end + axis off + drawnow + + + % -------------------------------------------------- + % Orthviews of parcels + % -------------------------------------------------- + + nclasses = length(class_clusters); % redefine based on results + + + while length(basecolors) < nclasses + basecolors{end+1} = basecolors{end - 11} + basecolors{end - 6} ./ 2; + end + + parcelindx = cat(1, parcel_cl_avgs.from_class); + + colors = nmdsfig_tools('class_colors',parcelindx,basecolors); + + addstr = 'noadd'; + + for i = 1:length(parcel_cl_avgs) + + cluster_orthviews(parcel_cl_avgs(i), colors(i), addstr, 'solid'); + + addstr = 'add'; + end + + % -------------------------------------------------- + % Montages of parcels + % -------------------------------------------------- + + disp(['Saving montages in ' mysavedir]) + + for i = 1:length(class_clusters) + + mycolor = basecolors(i); + + montage_clusters([], class_clusters{i}, mycolor); + drawnow + + savename = fullfile(mysavedir, ['Montage_class' num2str(i)]); + saveas(gcf, savename,'png') + + end + + if dosave + save_figures(1, savedotfigs, mysavedir); + end + + % -------------------------------------------------- + % Glass brains + % -------------------------------------------------- + for i = 1:length(class_clusters) + create_figure('glass'); + + mysig = NMDS.stats.fdrsig; + mysig = mysig(:, parcelindx == i); + mysig = mysig(parcelindx == i, :); + + cluster_nmdsfig_glassbrain( ... + class_clusters{i}, ... + ones(length(class_clusters{i}), 1), ... + NMDS.basecolors(i), ... + mysig, [], 'blobs'); + + if dosave + saveas(gcf, fullfile(mysavedir, ['glass_network_' num2str(i)]), 'png'); + if savedotfigs + saveas(gcf, fullfile(mysavedir, ['glass_network_' num2str(i)]), 'fig'); + end + end + + end + + + +end + + + +% ------------------------------------------------------------------------- +% Save all figures +% ------------------------------------------------------------------------- +function save_figures(verbose, savedotfigs, mysavedir) + + if nargin == 0, verbose = 1; end + if nargin == 1, savedotfigs = 0; end + if nargin == 2 || isempty(mysavedir), mysavedir = '.'; end + + fignames = {'nmdsfig' 'Panel_timeseries' 'Correlations'}; + + for i = 1:length(fignames) + name = fignames{i}; + + h = findobj('Tag', name); + if ~isempty(h) + + if length(h) > 1 + if verbose, disp(['Warning: More than one plot with tag ' name '. Saving highest-numbered figure.']); end + h = max(h); + end + + figure(h) + scn_export_papersetup(500); + + if savedotfigs + if verbose + fprintf('Saving: %s%s\n', fullfile(mysavedir, name), '.fig'); + end + + saveas(h, fullfile(mysavedir, name), 'fig'); + end + + if verbose + fprintf('Saving: %s%s\n', fullfile(mysavedir, name), '.png'); + end + saveas(h, fullfile(mysavedir, name), 'png'); + end + end + +end \ No newline at end of file diff --git a/Parcellation_tools/parcel_clusters.m b/Parcellation_tools/parcel_clusters.m new file mode 100644 index 00000000..3469fbb5 --- /dev/null +++ b/Parcellation_tools/parcel_clusters.m @@ -0,0 +1,522 @@ +function parcel_clusters(clpos_data, clneg_data) +% +% parcel_clusters(clpos_data, clneg_data) +% No outputs. Saves all output in separate directory. +% +% First, get eigenvectors for each subject. +% We're interested obtaining PARCELS of voxels that tend to co-activate, +% or have the same activation profile. +% We can find these by using clustering algorithms to group voxels with +% similar profiles. +% Because we have a many voxel x many voxel covariance matrix for each +% subject (lots of data!), it's important to reduce the dimensionality of +% the problem and peform clustering on a REDUCED_DIMENSIONAL space. +% We use PCA to do this. Instead of clustering activation profiles (e.g., +% time-courses) directly, we cluster eigenvector loadings for each voxel on +% a reduced set of components that explains most of the variance in the +% data. +% Similar voxels will have similar loadings across the set of +% eigenvectors. e.g., two voxels may load high on components [1 3 and 5], +% and low on components [10 and 13]. If they have the same pattern of +% loadings, they should be considered part of the same CLASS. Groups +% of voxels that are contiguous in space and are members of the same CLASS +% are called parcels. +% +% Images and outputs are saved in their own subdirectory called +% Parcellation_info +% +% The main outputs are: +% parcel_cl % parcels, one cell per subject, one parcel per +% element within cells. same format as clpos_data +% +% parcel_cl_avgs % parcels, one parcel per element within cells. +% same format as clpos_data2 +% +% parcel_cl_avgs(x).timeseries contains one cell per subject, with data +% averaged across voxels within that parcel for that subject +% This kind of output is useful, because you can input it directly into +% other mediation analyses. +% [paths, stats2] = mediation(SETUP.data.X, SETUP.data.Y, parcel_cl_avgs(1).timeseries, 'plots', 'verbose', 'names', {'Hi-Low Cue' 'Pain Report' 'Parcel'}, 'boot'); +% cluster_orthviews(parcel_cl_avgs(1), {[0 1 0]}, 'add'); +% +% %cd('/Volumes/SCNAlpha/Data_and_Tools/SpeechTask/analysis/wb_multisubject_correl_HR_corrected/mediation_Xprepvsb_Mbrain_Yhr') +%load cl_b_fdr05_002_01_k3_1_1_prune +% then run. + +% There are 2 dimension-reduction steps: +% 1 within-subjects +% 2 is on eigenvectors concatenated across subjects + +initial_eigval_limit = 15; % Number of eigenvalues to save initially for each subject + % Need this to reduce computational burden + +n_eigs = 7; % Number of eigenvectors to use per subject in the clustering algorithm. + +% We form a matrix of [voxels x subjects*eigenvectors] (called group_eigenvectors) +% For example, 7 eigenvectors x 10 subjects means clustering will be done +% in a 70 dimensional space. +% This matrix is subjected to a second step of PCA data reduction. A +% smaller number of scores are saved, and these scores represent sets of +% eigenvector loadings in the original 70 dimensional space. + + +n_eigs_across = 12; % Number of dimensions to save out of the original subjects*eigenvectors set + % Clustering is done on component scores. + % These are canonical eigenvector patterns + % that capture regular variations across + % subjects. Think of them as compressed + % eigenvectors. + +% More n_eigs_across means that clustering will be done in a more complex space. +% Increasing this will tend to split the voxels into smaller parcels, and +% decreasing it will tend to lump them into larger parcels. + +n_class_range = [2:20]; % test solutions from this many to this many CLASSES + +nclasses = 20; + +% The final number of classes you want to use in clustering. +% Increasing this will tend to split the voxels into smaller parcels, and +% decreasing it will tend to lump them into larger parcels. + +do_nclasses_search = 0; % search over n_class_range? + +doprune = 1; % prune voxels and parcels that are too small or whose voxels don't inter-correlate + +mysavedir = 'Parcellation_info'; +if ~exist(mysavedir, 'dir'), mkdir(mysavedir), end + + +create_figure('eigenvalues'); + +nsubjects = length(clpos_data); +fprintf('Subjects: %3.0f\n', nsubjects); + +if ~isempty(clneg_data) + test_subj_dat = [cat(2, clpos_data{1}(:).all_data) cat(2, clneg_data{1}(:).all_data)]; +else + test_subj_dat = [cat(2, clpos_data{1}(:).all_data)]; +end + +nvox = size(test_subj_dat, 2); +fprintf('Voxels in mask area: %3.0f\n', nvox); + + +group_eigenvalues = zeros(nsubjects, initial_eigval_limit); + +group_scores = cell(1, nsubjects); + + +fprintf('PCA: Subject ') + +%Clustering of multivariate data is most stable when the data is not sparse, +% i.e., the dimensionality is low relative to the number of observations. +% To limit the dimensionality of the data, a spatio-temporal dimension-reduction +% step is first performed on the [n x v x N] data matrix of AUC data for +% n trials x v voxels x N participants. A temporal data reduction is first performed +% to identify components with correlated AUC trial time series within each participant, +% followed by a spatial reduction to identify components with correlated spatial patterns +% across subjects. First, the [n x v] matrix of AUC data for each participant was +% subjected to PCA, using the [v x v] correlation matrices. Based on the scree plots +% across subjects, we saved the first [n_eigs] eigenvectors. +% These eigenvectors explained xxx +- xxx% (st. dev. across subjects) of the variance +% in the full dataset. These eigenvectors were scaled by their variances (eigenvalues) +% and concatenated across subjects to form an [v x N*7] matrix of eigenvectors, +% where N=27 subjects. This matrix was subjected to another (spatial) PCA step to +% identify components with similar spatial maps across participants. +% We retained [n_eigs_across] eigenvectors based on the scree plot, which explained xx% of the +% variance across individuals. This is a data reduction step, and the results are not +% expected to depend strongly on the number of eigenvectors retained at either step, +% as long as most of the variance in the data is explained. + +% Temporal reduction +% ------------------------------------------------------------------------- +for s = 1:nsubjects + + fprintf('%3.0f', s) + + if ~isempty(clneg_data) + subj_dat = [cat(2, clpos_data{s}(:).all_data) cat(2, clneg_data{s}(:).all_data)]; + else + subj_dat = [cat(2, clpos_data{s}(:).all_data)]; + end + + nanvec = any(isnan(subj_dat)) | all(subj_dat == 0); + wh_bad = find(nanvec); + if any(wh_bad) + fprintf('Warning! Subject %3.0f s has missing data (0 or NaN) for these voxels: ', s) + fprintf('%3.0f ', wh_bad); + fprintf('\n') + + subj_dat(:, wh_bad) = []; + end + + % to do PCA on correlation matrix rather than cov + subj_dat = zscore(subj_dat); + + %[U, eigenvalues, eigenvectors] = svd(subj_dat, 'econ'); % almost, but + %scaling isn't right, so just use princomp, which does it all + + [eigenvectors, score, eigenvalues] = princomp(subj_dat, 'econ'); + + clear subj_dat + + % insert bad voxels back in + if any(wh_bad) + for i = 1:size(eigenvectors, 2) + ev(:, i) = naninsert(nanvec, eigenvectors(:, i)); + end + + ev(isnan(ev)) = 0; + eigenvectors = ev; + end + + % The first [initial_eigval_limit] + group_eigenvalues(s, :) = eigenvalues(1:initial_eigval_limit)'; + + % we want to scale the eigenvectors by their variances (eigenvalues), + % so that components that account for more variation in the data are weighted more heavily. + eigenvectors = eigenvectors(:, 1:initial_eigval_limit) * diag(eigenvalues(1:initial_eigval_limit)); + + group_eigenvectors{s} = eigenvectors; + + plot(group_eigenvalues(s, :), 'ko-'); + drawnow + + % Calculate variance explained for first [initial_eigval_limit] + % eigenvalues. + ev = cumsum(group_eigenvalues'); + ev = ev ./ repmat(sum(group_eigenvalues', 1), size(ev, 1), 1); + evn = ev(n_eigs, :); % explained variance for these eigs; approximate as it is only proportion of first n eigs + fprintf('Temporal reduction: Eigenvectors explain %3.2f%% +- %3.2f%% of variance.', 100*mean(evn), 100*std(evn)); + +end + +clear eigenvalues eigenvectors + + +%% + +%n_eigs = input('Enter number of eigenvectors to save: '); + +plot_vertical_line(n_eigs); +drawnow + +if ~exist(mysavedir, 'dir'), mkdir(mysavedir); end + +saveas(gcf, fullfile(mysavedir, 'Eigenvalues'), 'png'); + + +for i = 1:nsubjects + group_eigenvectors{s}(:, n_eigs + 1 : end) = []; +end + +group_eigenvectors = cat(2, group_eigenvectors{:}); + +%% + +%input('Enter range of clusters: '); +%niter = 100; + +names = []; + +% c = nmdsfig_tools('cluster_solution', [], group_eigenvectors, n_class_range, niter, names); + +%% 2nd Dimension redution step : to get scores in lower-dim space for +% clustering + +% Spatial reduction +% ------------------------------------------------------------------------- + +[across_eigenvectors, across_scores, across_eigenvalues] = princomp(group_eigenvectors, 'econ'); + +create_figure('group_eigenvalues', 1, 3); +plot(across_eigenvalues(1:initial_eigval_limit), 'ko-'); +title('Across subjects eigenvalues'); + +% Calculate variance explained for first [n_eigs_across] eigenvalues. +ev = cumsum(across_eigenvalues); +ev = ev ./ sum(across_eigenvalues); +evn = ev(n_eigs_across, :); % explained variance for these eigs; approximate as it is only proportion of first n eigs +fprintf('Spatial reduction: Eigenvectors explain %3.2f%% of variance.', 100*evn); + +%n_eigs_across = input('Enter number of eigenvectors to save: '); + +scores_to_cluster = across_scores(:, 1:n_eigs_across); + +plot_vertical_line(n_eigs_across); + +subplot(1, 3, 2) +imagesc(scores_to_cluster); + +drawnow +%% CLUSTER voxels + +classes = []; +sil_vals = {}; +mean_sil = []; + +if do_nclasses_search + + fprintf('Clustering : ') + + % problem with silhouette is that it really finds natural break-point in + % data; so looks good for 2 clusters with pos/neg groups in data usually. + + clear s + + for i = n_class_range + + fprintf('%3.0f ', i); + + classes = clusterdata(scores_to_cluster, 'linkage', 'average', 'maxclust', i); + sil_vals{i} = silhouette(scores_to_cluster, classes); + mean_sil(i) = mean(sil_vals{i}); + + end + + fprintf('\n'); + + subplot(1, 3, 3) + plot(n_class_range, mean_sil(n_class_range), 'ko-', 'MarkerFaceColor', [.2 .6 1], 'LineWidth', 2); + + saveas(gcf, fullfile(mysavedir, 'Group_eigs_and_clustering'), 'png'); + +end + + +cl = [clpos_data clneg_data]; + +disp(['Saving data file: ' mysavedir filesep 'parcellation.mat']) +save(fullfile(mysavedir, 'parcellation'), 'cl', 'n*', '*eig*', '*score*', 'classes', '*sil*') + + +%% % Get parcels of contiguous regions +% Save averages over voxels for each subject, within each region + +fprintf('Getting parcels and associated data: Clustering with %3.0f classes\n', nclasses) + +classes = clusterdata(scores_to_cluster, 'linkage', 'average', 'maxclust', nclasses); + +clear parcel_cl +disp('Getting contiguous regions: These become parcels'); + +fprintf('Subject '); +for s = 1:nsubjects + + fprintf('%3.0f', s) + + if ~isempty(clneg_data) + cl = [clpos_data{s} clneg_data{s}]; + else + cl = clpos_data{s}; + end + + CLU = clusters2CLU(cl); + + for i = 1:max(classes) + + my_cl = CLU; + my_cl.XYZmm = my_cl.XYZmm(:, classes == i); + my_cl.XYZ = my_cl.XYZ(:, classes == i); + my_cl.Z = my_cl.Z(:, classes == i); + + my_cl.all_data = my_cl.all_data(:, classes == i); + + class_clusters{i} = tor_extract_rois([], my_cl, my_cl); + + [class_clusters{i}.from_class] = deal(i); + end + + parcel_cl{s} = cat(2, class_clusters{:}); + +end +fprintf('\n') + +% Prune parcels here +if doprune + [parcel_cl, meanpval, meancor] = prune_parcels(parcel_cl); + + disp('Saving meanpval and meancor for pruned parcels in parcellation.mat') + save(fullfile(mysavedir, 'parcellation.mat'), '-append', 'meanpval', 'meancor') +end + + +parcel_cl_avgs = parcel_cl{1}; + +% another convenient format +for i = 1:length(parcel_cl_avgs) + parcel_cl_avgs(i).all_data = cell(1, nsubjects); + parcel_cl_avgs(i).timeseries = cell(1, nsubjects); + for s = 1:nsubjects + parcel_cl_avgs(i).all_data{s} = parcel_cl{s}(i).all_data; + parcel_cl_avgs(i).timeseries{s} = parcel_cl{s}(i).timeseries; + end +end + +fprintf('\n') + +disp('Saving parcel_cl and parcel_cl_avgs in parcellation.mat') +save(fullfile(mysavedir, 'parcellation.mat'), '-append', 'class_clusters', 'parcel*') + +%volInfo = iimg_read_img('mask.img', 2); + +%% RE-do networks on these parcels +% re-define class clusters +% refine class (network) membership +% --------------------------- +[parcel_cl_avgs, NMDS, class_clusters] = parcel_cl_nmds(parcel_cl_avgs); + +disp('Saving NMDS structure and final class clusters in parcellation.mat') +save(fullfile(mysavedir, 'parcellation.mat'), '-append', 'NMDS', 'class_clusters') + +%% Plots +% --------------------------- +% Plot: data panel +% Orthviews of parcels +% Montages of parcels +parcel_cl_nmds_plots(parcel_cl_avgs, NMDS, 'save', 'savedir', 'Parcellation_info') + +end + + + +%% Sub-functions + + +function [parcel_cl, meanpval, meancor] = prune_parcels(parcel_cl) + + nsubjects = length(parcel_cl); + + %% remove parcels with too few voxels + vcutoff = 3; + p_cutoff = .002; + + whomit = false(size(parcel_cl{1})); + nvox = cat(1, parcel_cl{1}.numVox); + whomit(nvox < vcutoff) = 1; + fprintf('Eliminated %3.0f parcels smaller than %3.0f voxels\n', sum(whomit), vcutoff) + fprintf('Keeping %3.0f parcels\n', sum(~whomit)) + for s = 1:nsubjects + parcel_cl{s}(whomit) = []; + end + + nparcels = length(parcel_cl{s}); + [meancor, meanpval, vox_removed] = deal(zeros(nparcels, 1)); + omit_parcels = false(nparcels, 1); + + % + % Get data across subjects for one parcel + for p = 1:nparcels + + fprintf('Parcel %3.0f ', p); + + % Get data across subjects for one parcel (p) + % --------------------------------------------- + dat = cell(nsubjects, 1); + for s = 1:nsubjects + dat{s} = parcel_cl{s}(p).all_data; + end + %dat = cat(1, dat{:}); + + % Correlate, and get stats + % --------------------------------------------- + %[c, pvals] = corrcoef(dat); + clear c + for i = 1:nsubjects + c(:, :, i) = corrcoef(dat{i}); + end + + % Note: p vals will not be correct if there are NaNs! + + mc = nanmean(c, 3); + se = nanstd(c, 0, 3) ./ sqrt(nsubjects); + + mc = mc .* (1 - eye(size(mc))); + mc = squareform(mc); + + se = se .* (1 - eye(size(se))); + se = squareform(se); + + t = mc ./ se; + pvals = 2 .* (1 - tcdf(abs(t), nsubjects - 1)); + + meanpval(p) = mean(pvals); + meancor(p) = mean(mc); + + pvals = squareform(pvals); + + % Omit the bad (unrelated) voxels + % --------------------------------------------- + pv = mean(pvals, 2); + + wh = pv > p_cutoff; + + vox_removed(p) = sum(wh); + + if sum(wh) > length(wh) - vcutoff + 1 + % we have zero or 1 valid voxels left; omit the whole parcel + omit_parcels(p) = 1; + elseif any(wh) + % omit bad voxels + + for s = 1:nsubjects + parcel_cl{s}(p).XYZmm(:, wh) = []; + parcel_cl{s}(p).XYZ(:, wh) = []; + parcel_cl{s}(p).all_data(:, wh) = []; + + parcel_cl{s}(p).timeseries = nanmean(parcel_cl{s}(p).all_data, 2); + parcel_cl{s}(p).numVox = sum(~wh); + + end + + pvals(wh, :) = []; + pvals(:, wh) = []; + mc = squareform(mc); + mc(wh, :) = []; + mc(:, wh) = []; + + meanpval(p) = mean(squareform(pvals)); + meancor(p) = mean(squareform(mc)); + end + + + + fprintf(' mean inter-voxel corr: %3.2f, mean p = %3.4f, vox excluded = %3.0f', meancor(p), meanpval(p), vox_removed(p)) + if omit_parcels(p), fprintf(' OMITTED'); end + fprintf('\n') + + end + + % remove whole bad parcels + for s = 1:nsubjects + parcel_cl{s}(omit_parcels) = []; + end + + meanpval(omit_parcels) = []; + meancor(omit_parcels) = []; + + % remove parcels that are now too small + whomit = false(size(parcel_cl{1})); + nvox = cat(1, parcel_cl{1}.numVox); + whomit(nvox < vcutoff) = 1; + fprintf('Eliminated %3.0f parcels smaller than %3.0f voxels\n', sum(whomit), vcutoff) + fprintf('Keeping %3.0f parcels\n', sum(~whomit)) + for s = 1:nsubjects + parcel_cl{s}(whomit) = []; + end + + nvox = cat(1, parcel_cl{1}.numVox); + create_figure('Pruned parcels', 2, 1); + plot(nvox, 'ko', 'MarkerSize', 8, 'MarkerFaceColor', [.5 .5 1]); + title('Number of voxels in each parcel'); + xlabel('Parcel index number') + plot_horizontal_line(vcutoff) + + subplot(2, 1, 2) + plot(meancor, 'ko', 'MarkerSize', 8, 'MarkerFaceColor', [.5 .5 1]); + title('Mean within-subject correlation value with other voxels in parcel') + drawnow + +end + diff --git a/Parcellation_tools/parcel_complete_sets.m b/Parcellation_tools/parcel_complete_sets.m new file mode 100644 index 00000000..451ed9e6 --- /dev/null +++ b/Parcellation_tools/parcel_complete_sets.m @@ -0,0 +1,211 @@ +function [mysets,n_in_set,sets_by_vars,classes] = parcel_complete_sets(s,varargin) + % [mysets,n_in_set,sets_by_vars,classes] = parcel_complete_sets(s,['dounique','nofuzzy']) + % + % Find sets of elements in logical n x n matrix s in which all pairs in set are 'true' (1 in + % matrix s) + % Optional: input a distance/correlation/etc. matrix and threshold + % + % Optional inputs: + % 'dounique' : provides single-variable sets as well + % 'nofuzzy' : chooses closest set for each var so that each var + % can belong to only one set + % 'threshold' : followed by threshold thr for matrix s. + % if thr is a string, thr will be evaluated on s: e.g., 's < 10' + % in this case, thr should be a logical expr. involving s + % if thr is a number, s >= thr will be evaluated + % the resulting logical matrix will be used to determine sets + % 'min' or 'max' : works only with 'nofuzzy' option. + % if max, uses max to find most similar set; good if s is a + % covariance matrix + % if min, uses min to find closest set; good if s is a + % distance matrix + % default is max + % + % %%% runs on Matlab 7.2 or higher %%% + % tor wager, nov 10, 2006 + % + % Example: + % Find sets of coordinates within 10 mm of one another + % xyz = cat(1,cl{1}.mm_center); + % d = pdist(xyz); d = squareform(d); + % [mysets,n_in_set,sets_by_vars,classes] = parcel_complete_sets(d,'dounique','nofuzzy','threshold','s<10','min'); + + + % process inputs and initialize variables + % --------------------------------------------------------------- + %keywords = {'dounique' 'nofuzzy' 'threshold'}; + keywords = varargin; + classes = []; + minmax = 'max'; + + if any(strcmp(keywords,'min')), minmax = 'min'; end + if any(strcmp(keywords,'max')), minmax = 'max'; end + + if any(strcmp(keywords,'threshold')) + thr = keywords{find(strcmp(keywords,'threshold')) + 1}; % get threshold + sorig = s; % save original for 'nofuzzy' option (only needed here) + + if ischar(thr) + eval(['s = ' thr ';']); + else + s = s >= thr; + end + else + s = logical(s); + end + + % make sure diagonals are zeros, so that a var. can't correlate with itself + s = s .* (1 - eye(size(s))); + + % this is a list of all the unique pairs of 'true' values + [r,c] = find(triu(s)); + + % number of pairs of 'true' values + n = length(r); + + mysets = cell(1,n); + + n_in_set = zeros(1,n); + sets_by_vars = false(n,size(s,2)); + + % for each pair, get the set of variables connected with all members of the + % set (recursive search) + % get sets where all in set are 'related' (1 in matrix) + % --------------------------------------------------------------- + for i = 1:n + mysets{i} = get_set; + + % n_in_set(i) = length(mysets{i}); + + sets_by_vars(i,mysets{i}) = 1; + end + + % % [sets_by_vars,i] = unique(sets_by_vars,'rows'); + % % mysets = mysets(i); + % % n_in_set = n_in_set(i); + get_outputs_from_sets_by_vars; + + in_sets = sum(sets_by_vars,1); % how many sets each var is in + + + if any(strcmp(keywords,'dounique')) + % for each original var, make sure it's in a set + % if none, create unique set + % --------------------------------------------------------------- + newsets = find(in_sets == 0); + nnew = length(newsets); + [add_to_sets{1:nnew}] = deal(1); + mysets = [mysets add_to_sets]; + n_in_set = [n_in_set ones(1,nnew)]; + nvars = size(s,1); + add_to_sbyv = zeros(nnew,nvars); + for i = 1:nnew + add_to_sbyv(i,newsets(i)) = 1; + end + sets_by_vars = [sets_by_vars; add_to_sbyv]; + + end + + if any(strcmp(keywords,'nofuzzy')) + % for each original var, make sure it's in only one set + % if > 1, choose closest set + % --------------------------------------------------------------- + manysets = find(in_sets > 1); + + for i = manysets + select_closest_set; + end + + % recompute outputs + get_outputs_from_sets_by_vars; + + [classes,tmp] = find(sets_by_vars); + + end + + %%% End main function %%% + %%% Matlab 7.2 or higher needed for nested functions, below %%% + + + + + % --------------------------------------------------------------- + % nested functions + % --------------------------------------------------------------- + + function myset = get_set + % start with the pair of correlations... + % myset is the indices of variables that belong to a set. + myset = [r(i) c(i)]; + r_this_set = s(myset,:); + + % ...and iteratively find others that correlate with all members of the + % set. + + while true + % add2set gives indices of variables that are correlated with all of the + % others in the set + add2set = find(all(r_this_set)); + %if any(add2set == 16), keyboard; end + + if isempty(add2set), break, end + + % add them one at a time to avoid adding incompatible vars + myset = [myset add2set(1)]; + r_this_set = s(myset,:); + end + + end + + function get_outputs_from_sets_by_vars + + [sets_by_vars] = unique(sets_by_vars,'rows'); + emptysets = sum(sets_by_vars,2) == 0; + sets_by_vars(emptysets,:) = []; + nsets = size(sets_by_vars,1); + mysets = cell(1,nsets); + + for i = 1:nsets + mysets{i} = find(sets_by_vars(i,:)); + end + n_in_set = sum(sets_by_vars,2)'; + + end + + function select_closest_set + wh = find(sets_by_vars(:,i)); % the sets this var is in + len = length(wh); + meanset = zeros(1,len); + + % pick closest (farthest) candidate set in terms of sim. + for j = 1:len % for each candidate set + thisset = mysets{wh(j)}; + thisset(thisset == i) = []; % get members of candidate set not self + + % mean relationship values w/other members + + if exist('sorig','var') % use actual values if we have them + meanset(j) = mean(sorig(thisset,i)); + else + meanset(j) = mean(s(thisset,i)); + end + end + + % pick closest (farthest) candidate; in case of ties, pick first one + switch minmax + case 'max' + [mymax,whmax] = max(meanset); + myset = wh(whmax); + case 'min' + [mymax,whmax] = min(meanset); + myset = wh(whmax); + end + + % select vals in set; remove other memberships + % remove from sets_by_vars + sets_by_vars(:,i) = 0; + sets_by_vars(myset,i) = 1; + + end + +end % end main function diff --git a/Parcellation_tools/parcel_images.m b/Parcellation_tools/parcel_images.m new file mode 100644 index 00000000..41abb5bb --- /dev/null +++ b/Parcellation_tools/parcel_images.m @@ -0,0 +1,451 @@ +function parcel_images(image_names, extract_mask, nuisance_covs) +% function parcel_images(image_names, extract_mask, nuisance_covs) +% +% parcel_images +% Principal components + anatomical parcellation of data +% Tor Wager, v. June 2010 +% +% This function performs the following steps, in this order +% ============================================================ +% - map mask to functional space +% - extract data from in-mask voxels +% - remove nuisance covariates (before assessing connectivity) +% - data reduction (pca) +% - plot cases (detect outliers) +% - separate data into a priori anatomical regions (LBPA40 hard-coded +% right now; downloadable from web; see wiki) +% (save label image mapped to functional space) +% - cluster voxels in each region to get parcels +% - save parcels and images +% - NMDS on the parcels to group them into "networks" (default = use rank data) +% - Visualization of the networks +% ============================================================ +% +% Outputs: +% Creates and goes to a new directory: parcel_images_output +% Outputs saved to disk include +% (1) An image with unique numerical codes for each parcel +% (2) A 'clusters' structure containing the parcels, with image data extracted and +% averaged over voxels within each parcel +% +% Inputs: +% image_names: names of images to extract data from, and to use for +% functional parcellation. SEE ALTERNATE FORMAT BELOW FOR DIRECT DATA INPUT +% extract_mask: a mask image +% nuisance_covs: columns of a matrix to remove from the data, so that this +% subspace is not used to determine connectivity +% e.g., nuisance_covs = SPM.xX.X(:, 1:3); if these are nuisance +% covariates... +% +% Alternate input formats for image_names: +% If you have data already extracted, image_names can be a structure with +% these fields: +% image_names.V, spm_vol-style volume info for the mask volume and image space +% image_names.data, extracted data from all valid in-mask (non-zero, +% non-nan) voxels, one column per voxel, in standard matlab (:) order. +% The mask and the data must match! +% +% + +spm_defaults + +docorrmtx = 0; % 0 to work on cov matrix for PCA (good for meta-analysis), or 1 to work on correlation matrix (maybe better for other cases?) + +% Go to directory: output images will be written here +% ============================================================ +[dd, currdir] = fileparts(pwd); +if strcmp(currdir, 'parcel_images_output') + disp('Already in parcel_images_output directory. Proceeding...'); +else + if ~exist(fullfile(pwd, 'parcel_images_output'), 'file') + disp('Creating directory: parcel_images_output'); + mkdir('parcel_images_output'); + end + cd('parcel_images_output'); +end + +% ============================================================ +% Map mask image to image space +% ============================================================ +if isstruct(image_names) + scn_map_image(extract_mask, image_names.V(1).fname, 'write', 'extract_mask.img'); +elseif ischar(image_names) + scn_map_image(extract_mask, image_names(1,:), 'write', 'extract_mask.img'); +else + error('image_names must be either a string array of names or a structure with vol info and data. see the help page.'); +end +extract_mask = fullfile(pwd, 'extract_mask.img'); + +% ============================================================ +% Map anatomical label image to functional space and write +% ============================================================ + +labelimg = which('atlas_labels_combined.img'); %anat_lbpa_thal.img'); %% 'lpba40.spm5.avg152T1.label.nii'); +disp(labelimg) + +if ~exist(labelimg, 'file') + cd .. + fprintf('Cannot find label image: %s\n', labelimg) + error('Must be on path'); +end + +% note: must be nearest neighbor interp to work!! +scn_map_image(labelimg, extract_mask, 'write', 'label_image_resliced.img'); +labelimg = fullfile(pwd, 'label_image_resliced.img'); + +% ============================================================ +% Extract data +% ============================================================ + + [dat, maskInfo] = extract_image_data(extract_mask, image_names); + + % memory saver: don't need image_names.data anymore + if isstruct(image_names), image_names.data = []; end + +% ============================================================ +% Remove nuisance covs +% ============================================================ +if ~isempty(nuisance_covs) + disp('Removing nuisance covariates') + dat = dat - nuisance_covs * pinv(nuisance_covs) * dat; +else + disp('No nuisance covariates specified.') +end + +%% +% ============================================================ +% Data reduction +% ============================================================ +mysize = prod(size(dat)); +isbigmatrix = mysize > 10^ 8; + +if ~isbigmatrix && any(isnan(dat(:))) + disp('warning - replacing nans with zeros'); + dat(isnan(dat))=0; +end + +if isbigmatrix + disp('Running PCA within each anatomical parcel instead of at the start.'); + % [U, sigma, pc] = lansvd(dat, nrun, 'L'); + % sigma = diag(sigma); + % score = U .* repmat(sigma',nrun,1); % == x0*coeff ***test to make sure output variables are same as svds and debug +else + + [pc, score, latent] = run_pca(dat, docorrmtx); + % Eigenvalue plot + figure('Color','w'), set(gca,'FontSize',18), bar(latent) + xlabel('Components'),ylabel('Variance'); drawnow + + + num_to_save = sum(latent>1); + disp(num_to_save) + + % ============================================================ + % Plot cases : diagnostic + % ============================================================ + + % Component plot of images (are there groups of points? + % this suggests clusters of images/subjects) + create_figure('nmdsfig'); nmdsfig(score(:, 1:2)); drawnow + + % clustering of cases + % unusual cases (outliers) will have different class from most subjects + subj_classes = clusterdata(score(:,1:num_to_save),'maxclust',10,'linkage','average'); + create_figure('nmdsfig'); nmdsfig(score(:, 1:2), 'classes', subj_classes); + title('Plot of cases: Unusual cases are possible outliers'); + +end + +%% +% ============================================================ +% Read all anatomical labels +% ============================================================ + +[labelInfo, labels] = iimg_read_img(labelimg, 2); +clear labelInfo +labels = round(labels); % small errors in resampling (?) +in_mask_labels = labels(maskInfo.wh_inmask); +all_labels = unique(in_mask_labels); + +in_mask_labels = uint16(in_mask_labels); +clear labels + +% treat zero as a non-label +all_labels(all_labels == 0) = []; + +parcel_labels = zeros(maskInfo.n_inmask, 1); + +%% +% ============================================================ +% Cluster within anatomical regions +% ============================================================ +% Loop through anatomical labels, and cluster data based on similarity +% within each region. 'region' refers to an anatomical region specified a +% priori, and 'parcel' refers to a functionally homogenous subset of voxels +% in that region. + +fprintf('Parcellating %3.0f anatomical regions. 000', length(all_labels)); + +for i = 1:length(all_labels) + fprintf('\b\b\b%3.0f', i); + + current_label = all_labels(i); + wh_in_region = in_mask_labels == current_label; + if sum(wh_in_region)>1 + + %wh_in_region_image_space = maskInfo.wh_inmask(wh_in_region); + + % view this cluster + current_cl = iimg_indx2clusters(double(wh_in_region), maskInfo); + cluster_orthviews(current_cl, {[1 0 0]}, 'solid'); + + % if huge matrix: PCA here + if isbigmatrix + clear pc score latent ans + vox_dat = dat(:, wh_in_region); + + % memory saver: zero out dat for these voxels in sparse matrix; + % re-load later + dat(:, wh_in_region) = 0; + + [pc, score, latent] = run_pca(vox_dat, docorrmtx); %[pc, score, latent] = run_pca + clear vox_dat score + num_to_save = sum(latent>1); + vox_pcs = double(pc(:, 1:num_to_save)); + + else + % select principal component weights for this region and cluster + vox_pcs = double(pc(wh_in_region, 1:num_to_save)); + end + + max_parcels = max(2, round(sum(wh_in_region) ./ 50)); + vox_classes = clusterdata(vox_pcs,'maxclust',max_parcels,'linkage','average'); + + % assign unique value for each parcel + % This may NOT work will all labeling schemes!! + current_unique_id = repmat(i * 100, sum(wh_in_region), 1) + vox_classes; + parcel_labels(wh_in_region) = current_unique_id; + + + % save clusters structure for each parcel + wh_in_region_index = find(wh_in_region); + + for j = 1:max(vox_classes) + wh_in_parcel = wh_in_region_index(vox_classes == j); + in_parcel = false(length(wh_in_region), 1); + in_parcel(wh_in_parcel) = 1; + + tmp_cl = iimg_indx2clusters(in_parcel, maskInfo); + % if more than 1, just choose largest for cluster structure + % this will not match label image exactly (!) + howmany = cat(1, tmp_cl.numVox); + mymax = find(howmany == max(howmany)); + mymax = mymax(1); + + parcel_cl{i}(j) = tmp_cl(mymax); + end + % visualize the parcels for this region + cluster_orthviews(parcel_cl{i}, 'unique', 'add'); + spm_orthviews('Reposition', current_cl.mm_center) + else + !echo skipping this region - all_labels + num2str(i) + end + +end + +% ============================================================ +% Save output +% ============================================================ + +% Image of parcels +% ===================================== +% write image with unique labels +labelimg_out_name = 'parcel_labels.img'; +if size(parcel_labels, 1) == 1, parcel_labels = parcel_labels'; end + +if isstruct(image_names) + % if struct, we have changed mask, need to go back to original one + [dummy, maskInfo_orig] = iimg_get_data(extract_mask, extract_mask, 'single', 'noexpand'); + clear dummy + iimg_reconstruct_vols(parcel_labels, maskInfo_orig, 'outname', labelimg_out_name); +else + iimg_reconstruct_vols(parcel_labels, maskInfo, 'outname', labelimg_out_name); +end +disp(['Written: ' labelimg_out_name]); + + +% Clusters of parcels with data +% ===================================== +parcel_cl_flat = cat(2, parcel_cl{:}); + +% extent: 3 vox +parcel_cl_flat(cat(1, parcel_cl_flat.numVox) < 3) = []; + +save parcel_clusters_file parcel_cl +clear parcel_cl parcel_labels wh_in_reg* + +% Write parcel images +nvox = maskInfo.nvox; +imgdat = zeros(nvox, 1); +for i = 1:length(parcel_cl_flat) + pvec = iimg_clusters2indx(parcel_cl_flat(i), maskInfo); + imgdat(pvec) = i; +end +iimg_reconstruct_vols(imgdat, maskInfo, 'outname', 'parcels.img', 'descrip', 'Clusters within combined anatomical mask boundaries'); +disp('Written: parcels.img (4-D image with mask volume for each parcel'); + + +% memory saver: re-load here, after clustering +%[dat, maskInfo] = extract_image_data(extract_mask, image_names); + +% extract original image data again, and average over voxels within parcel +if isstruct(image_names) + % Need to test data extraction and implement!! Use, for each parcel:: + for i = 1:length(parcel_cl_flat) + [imgvec, maskvec] = iimg_clusters2indx(parcel_cl_flat(i), maskInfo); + parcel_cl_flat(i).all_data = dat(:, maskvec); + parcel_cl_flat(i).timeseries = nanmean(parcel_cl_flat(i).all_data')'; + end +else + parcel_cl_flat = tor_extract_rois(image_names, parcel_cl_flat); +end +disp('Extracted and averaged image data for each parcel, attached to parcel_cl_flat variable'); + +save parcel_clusters_file -append parcel_cl_flat +disp('Saved parcel_cl_flat in parcel_clusters_file.mat'); + +% memory saver: view parcels at the end +% alternative method for large datasets + +clear dat + +% view all the parcels in unique colors +if isbigmatrix + cluster_orthviews(parcel_cl_flat(1), {rand(1, 3)}); + for i = 2:length(parcel_cl_flat) + cluster_orthviews(parcel_cl_flat(i), {rand(1, 3)}); + end +else + cluster_orthviews(parcel_cl_flat, 'unique'); +end + +sizes = cat(1, parcel_cl_flat.numVox); +create_figure('Voxels per parcel'); hist(sizes, 100); + +end % main function + + + + +function [dat, maskInfo] = extract_image_data(extract_mask, image_names) + % Extract, using single-precision array and sequential image loading to save space + % for large datasets. 'noexpand' is a bit faster and is compatible with SPM5 and above. + if ischar(image_names) + [dat, maskInfo] = iimg_get_data(extract_mask, image_names, 'verbose', 'single', 'noexpand'); + else + % struct + maskindex = logical(iimg_get_data(image_names.V.fname, extract_mask, 2, 'noexpand')); % 1/0 for in-mask/not + if issparse(image_names.data) + dat = image_names.data(:, maskindex); % single(full(image_names.data(:, maskindex))); %image_names.data(:, maskindex); + else + dat = single(image_names.data(:, maskindex)); + end + image_names.data = []; + maskInfo = image_names.V; + maskInfo.wh_inmask = maskInfo.wh_inmask(maskindex); + maskInfo.n_inmask = length(maskInfo.wh_inmask); + maskInfo.xyzlist = maskInfo.xyzlist(maskindex, :); + end + +end + + +function [pc, score, latent] = run_pca(dat, docorrmtx) + + [nobs, nvars] = size(dat); + ncomponents = min( [min(size(dat)) 50 ] ); + + if docorrmtx, dat = zscore(dat); end + +% if nvars > 4000 + warning off % lots of "slow matlab code" warnings + [U, sigma, pc] = lansvd(dat, ncomponents, 'L'); + sigma = diag(sigma); + score = U .* repmat(sigma', nobs, 1); % == x0*coeff + sigma = sigma ./ sqrt(nobs-1); + latent = sigma.^2; + warning on + +% elseif issparse(dat) +% % note: this can be slower and return different values from princomp. actually different? i think so +% % also: needs testing/debugging. +% nrun = min(n, 50); +% [U, sigma, pc] = svds(dat, nrun); % could be too big -- no memory!! [U, sigma, pc] = svds(dat, n); +% score = U .* repmat(diag(sigma)',n,1); % == x0*coeff +% sigma = sigma ./ sqrt(n-1); +% latent = sigma.^2; + +% else +% +% [pc, score, latent] = princomp(single(full(dat)), 'econ'); +% +% end + +end % run_pca + +% %% +% % ============================================================ +% % Multivariate networks: cluster regions into networks +% % ============================================================ +% c = []; +% doranks = 1; +% c = nmdsfig_tools('get_data_matrix',c, parcel_cl_flat,'timeseries',1,[],doranks); +% c = nmdsfig_tools('get_correlations',c); +% [c.GroupSpace,c.obs,c.implied_dissim] = shepardplot(c.D,[]); +% +% disp('saving key info in c variable in nmds_c_structure.mat'); +% save nmds_c_structure c +% % at least n parcels/2, and if n parcels/2 > 5, then at least 5 but up to +% % nparcels/10 +% max_cl_to_test = min(max(5, round(length(parcel_cl_flat) ./ 10)), round(length(parcel_cl_flat)./2)); +% +% c = nmdsfig_tools('cluster_solution',c, c.GroupSpace, 2:max_cl_to_test, 1000, []); +% +% +% c.colors = cluster_orthviews_classes(parcel_cl_flat,c.ClusterSolution.classes, [], [], 1); +% saveas(gcf,'network_montage','fig'); +% scn_export_papersetup(550); +% saveas(gcf,'network_montage','png'); +% +% % apply and make network plot +% c = nmdsfig_tools('apply_clusters',c); +% +% saveas(gcf,'network_nmds_fig','fig'); +% scn_export_papersetup(450); +% saveas(gcf,'network_nmds_fig','png'); +% +% %nmdsfig_tools('nmdsfig_plot',c, 0, 0, 'fill'); +% +% disp('saving updated key info in nmds_c_structure.mat'); +% save nmds_c_structure c +% +% +% % name networks +% c.APPLY_CLUSTER.names = cell(1, length(c.APPLY_CLUSTER.classes)); +% for i = 1:length(c.APPLY_CLUSTER.classes) +% wh = find(c.ClusterSolution.classes == i); +% cluster_orthviews(parcel_cl_flat(wh), c.colors(i), 'solid'); +% c.APPLY_CLUSTER.names{i} = input(sprintf('Name network %3.0f: ', i), 's'); +% end +% +% disp('saving updated key info in nmds_c_structure.mat'); +% save nmds_c_structure c +% +% % re-make to leave open for display +% cluster_orthviews_classes(parcel_cl_flat,c.ClusterSolution.classes, [], [], 1); +% scn_export_papersetup(700); saveas(gcf, 'cluster_montage_networks.png'); +% +% cd('..'); +% %% +% end % end function diff --git a/ROI_drawing_tools/add2mask.m b/ROI_drawing_tools/add2mask.m new file mode 100755 index 00000000..253fb8bd --- /dev/null +++ b/ROI_drawing_tools/add2mask.m @@ -0,0 +1,37 @@ +function add2mask(mask, x, r,varargin) +% add2mask(mask, x, r,varargin) +% adds or subtracts spheres around x coordinates to/from existing mask +% +% mask is a string filename +% x is n x 3 list of coordinates +% r is radius +% a 4th argument causes us to SUBTRACT! +% +% e.g., +% mask = 'insula_from_part1.img' +% x = [-29.6 28.6 5.3; 37.0 27.5 3.2; -34.9 8.5 -14.8;38.1 10.6 -14.8]; +% add2mask(mask,x,8); +% + +% get spheres +add3 = sphere_mask(mask,x,r,'add3.img'); + +% add or subtract +if length(varargin) == 0 + disp('ADDING spheres to mask') + tor_spm_mean_ui(str2mat(mask,'add3.img'),'add3.img') + Q = spm_imcalc_ui('add3.img','add3.img','i1>0'); +else + disp('SUBTRACTING spheres from mask') + Q = spm_imcalc_ui(str2mat(mask,'add3.img'),'add3.img','i1>0 & ~i2'); +end + +disp('Written new mask: add3.img') + +%Q = spm_imcalc_ui('add3.img','add3.img','i1>0'); +%P =%str2mat('add1.img','add2.img','add3.img','smoothed_insula_LR_thresh.img') +%tor_spm_mean_ui(P,'smoothed_insula2.img') +%[v,V,xyz,insula_gray_clusters] = mask_intersection([],'smoothed_insula2.img','smoothed_insula2.img',which('ICBM_brainonly_1mm_seg1.img')); + + +x = [-29.6 28.6 5.3; 37.0 27.5 3.2; -34.9 8.5 -14.8;38.1 10.6 -14.8]; \ No newline at end of file diff --git a/ROI_drawing_tools/clusters2roimask.m b/ROI_drawing_tools/clusters2roimask.m new file mode 100755 index 00000000..823f719d --- /dev/null +++ b/ROI_drawing_tools/clusters2roimask.m @@ -0,0 +1,137 @@ +function [clout] = clusters2roimask(cl) +% [clout] = clusters2roimask(cl) +% The purpose of this function is to facilitate making masks with ROIs for +% future studies, give a clusters structure. ROIs are constrained to be +% within activation blobs specified by input clusters, and are masked by +% selected ICBM regions. Clusters may be smoothed before or after masking. +% +% Option to do 3 things, in order: +% 1) Enlarge selected clusters +% 2) Mask clusters with anatomical regions from ICBM atlas +% 3) Subdivide clusters using hierarchical clustering of voxel coordinates +% +% output is a clusters file and a mask file in a 2 x 2 x 2 standard brain +% space. Well, no mask file yet. And no shrinking. +% +% Tor Wager, Aug 2004. + +gom = input('Mask clusters with anatomical regions from ICBM (1/0)? '); +clout = []; + +for i = 1:length(cl) + + icbm_localize(cl(i)); + [str,num] = icbm_orthview_label(cl(i)); + + % ------------------------------------------------------- + % Cluster enlargement + % ------------------------------------------------------- + + go = 1; + while go + go = input('Enlarge (1) or shrink (-1) cluster, 0 if done? '); + if go > 0 + cl(i) = enlarge_cluster(cl(i)); + elseif go < 0 + cl(i) = shrink(cl(i)); + else + end + [num,name] = icbm_localize(cl(i)); + [str,num,name,perc] = icbm_orthview_label(cl(i)); + end + + if ~gom, clout = cl;, end + + % ------------------------------------------------------- + % Cluster masking + % ------------------------------------------------------- + disp('Here is a list of anatomical regions for this cluster, indexed by number:') + disp('You can combine anatomical regions by entering a vector of numbers: [3 60]') + + gom1 = gom; % save orig all clu + clxyz = round(cl(i).XYZmm'); + + + while gom + % ITERATE CHOICE of ANATOMICA REG + % + + % re-display list + + for jj = 1:length(num) + fprintf('%3.0f\tPerc: %3.0f\t%s\t\n',num(jj),perc(jj),name{jj}) + end + + + % get choice and make sure its valid + + stopme = 0; + while stopme == 0 + stopme = 1; + gom = input('Mask with: Enter anatomy number, vector to combine, or 0 if done: '); + if gom ~= 0, + for jj = 1:length(gom), if ~any(gom(jj) == num), stopme = 0;, end, end + end + end + + + if gom + % Do the mask for THIS CLUSTER + + if isempty(clout), clout = cl(i);, else, clout(end+1) = cl(i);,end + + XYZmm = []; XYZ = []; + for jj = 1:length(gom) + [XYZmmtmp,XYZtmp] = icbm_reader('coordinates',gom(jj)); + XYZmm = [XYZmm XYZmmtmp]; + XYZ = [XYZ XYZtmp]; + end + + % lump together if multiple areas + + if iscell(XYZmm), tmp = cat(2,XYZmm{:});, XYZmm = unique(tmp','rows')';, end + if iscell(XYZ), tmp = cat(2,XYZ{:});, XYZ = unique(tmp','rows')'; end + + + %[xyztmp,b,c] = intersect(XYZmm',round(cl.XYZmm)','rows'); + [clout(end).XYZmm] = dominance_point_match(round(cl(i).XYZmm'),XYZmm',0)'; + clout(end).XYZ = mm2voxel(clout(end).XYZmm,cl(i),1)'; + clout(end).Z = ones(1,size(clout(end).XYZ,2)); + + % keep track of all XYZmm coordinates used; remaining final cluster is + % unused in any mask. + [dummy,wh] = dominance_point_match(clxyz,XYZmm',0); + clxyz(wh,:) = []; % eliminate used points + + % Display + + cluster_orthviews(clout(end),{rand(1,3)},'add') + spm_orthviews('Reposition',cl(i).mm_center) + disp('Saved masked cluster in cl output structure.') + end + end + + gom = gom1; + + % Enter unused voxels in last cluster + if ~isempty(clxyz) + if isempty(clout), clout = cl(i);, else, clout(end+1) = cl(i);,end + + [clout(end).XYZmm] = dominance_point_match(round(cl(i).XYZmm'),clxyz,0)'; + clout(end).XYZ = mm2voxel(clout(end).XYZmm,cl(i),1)'; + clout(end).Z = ones(1,size(clout(end).XYZ,2)); + end + +end + +gom = input('Further subdivide clusters with anatomy? (1/0)? '); +if gom + clout = anat_subclusters(clout); +end + + +return + + + + \ No newline at end of file diff --git a/ROI_drawing_tools/draw_anatomical_roi.m b/ROI_drawing_tools/draw_anatomical_roi.m new file mode 100755 index 00000000..6c634c06 --- /dev/null +++ b/ROI_drawing_tools/draw_anatomical_roi.m @@ -0,0 +1,110 @@ +% just run it. +% +% Prompts you for all necessary info, including image to draw on +% and output image file name. +% +% Writes a mask *img file in analyze format that you can view in spm +% +% Functions called: +% c:\tor_scripts\voistatutility\readim2.m +% read_hdr.m +% +% c:\tor_scripts\roiutility\imgmanip\getvoxels3.m +% C:\matlabR12\toolbox\matlab\elmat\ind2sub.m +% +% c:\tor_scripts\roiutility\cluster\tor_extract_rois.m +% C:\matlabR12\toolbox\matlab\datatypes\squeeze.m +% c:\tor_scripts\voistatutility\nanmean.m +% center_of_mass.m +% C:\matlabR12\toolbox\matlab\elmat\repmat.m +% (calls other spm functions) +% +% c:\tor_scripts\fixedfxutility\voxel2mm.m +% +% spm_vol.m +% spm_read_vols.m +% +% +% +% by Tor Wager last modified 9/22/02 +% +% NOTE: readim2 works on little endian systems (Linux). +% If you're using Unix, you should modify this script +% to call the function readim2_b.m instead. +% +% +%P = spm_get(1,'*.img','Choose whole-brain anatomical mask'); +P = spm_select(1,'image','Choose whole-brain anatomical mask'); + +V = spm_vol(P); +vol = spm_read_vols(V); +vol = double(vol); + + +dosagg = input('Press 1 to choose saggital slices, or 0 for axial.'); +% if saggital, use vol, not rvol +% y is not reversed +% ginput x = brain y, ginput y = brainz, slice = x + +if dosagg + [rvol2] = readim2(vol,'p','sagg'); +else + for i = 1:size(vol,3) + rvol(:,:,i) = rot90(vol(:,:,i)); + end + + [rvol2] = readim2(rvol,'p'); +end + +colormap gray +wslices = input('Enter range of slices (e.g, 20:30) '); +close + +if dosagg + [rvol2,hdr,h] = readim2(vol,'p','sagg','noflipy',wslices); + colormap gray + [voxels,mask] = getvoxels3(h,vol,wslices,'sagg'); +else + [rvol2,hdr,h] = readim2(rvol,'p','ax','noflipy',wslices); + colormap gray + [voxels,mask] = getvoxels3(h,vol,wslices); +end + + +%for i = 1:size(mask,3) +% rmask(:,:,i) = rot90(mask(:,:,i),3); +%end + +V.fname = input('Enter output filename, no quotes: ','s'); +spm_write_vol(V,mask); +[d,fname,e] = fileparts(V.fname); +saveas(gcf,fname,'fig') + +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +% cluster structure stuff for ROIs +% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +CLU.M = V.mat; +CLU.voxSize = diag(V.mat)'; +CLU.voxSize = CLU.voxSize(1:3); +CLU.VOX = CLU.voxSize; +CLU.XYZ = voxels'; +CLU.XYZmm = voxel2mm(voxels',V.mat); +CLU.Z = ones(1,size(CLU.XYZ,2)); +CLU.crit_t = 1; +CLU.cl_size = 0; +CLU.u = CLU.crit_t; +CLU.k = CLU.cl_size; +CLU.title = fname; + +clusters = tor_extract_rois([],CLU,CLU); +eval(['save ' fname '_clusters CLU clusters']) + +disp(' ') +newim = input('Press 1 to choose a display image, or anything else to use your prior mask.'); +if newim, P = spm_get(1,'*.img','Choose overlay image');,end + +spm_image('init',P) +for i = 1:length(clusters) + spm_orthviews('AddColouredBlobs',1,clusters(i).XYZ,clusters(i).Z,CLU.M,rand(1,3)) +end diff --git a/ROI_drawing_tools/draw_anatomical_roi_2008.m b/ROI_drawing_tools/draw_anatomical_roi_2008.m new file mode 100644 index 00000000..617cda2c --- /dev/null +++ b/ROI_drawing_tools/draw_anatomical_roi_2008.m @@ -0,0 +1,720 @@ +% Quick start guide: +% type draw_anatomical_roi_2008 +% +% draw_anatomical_roi_2008('init'); +% draw_anatomical_roi_2008('load', 'ROI_midbrain.img'); +% draw_anatomical_roi_2008('init', 'overlay', 'remi_mean_T2.img'); +% +% no arguments: init +% 'init' : initialize gui and orthviews and remove previous ROI +% 'load' : load an ROI from a mask +% 'free' : draw an ROI freehand (click on one of the slices in the Slices window 1st) +% 'poly' : don't run this yet +% 'add' : add a region you've drawn on a slice to your ROI +% 'remove' : remove a region you've drawn from your ROI +% 'smooth' : 3-d smoothing of ROI +% 'write' : write mask image of ROI and return clusters to workspace +% 'exit': exit. ROI data is stored in the Slices figure, so you can +% continue to edit, etc. after exiting. +% +% More notes: +% You can use cluster_orthviews to image multiple blobs, and then +% draw relative to those. +% this function saves it's data in the Slices figure, so you can draw, +% re-initialize the orthviews, and keep drawing before you save. +% +% e.g., +% cluster_orthviews(red, {[1 0 0]}, 'overlay', 'remi_mean_T2.img'); +% cluster_orthviews(stn, {[0 1 0]}, 'add'); +% set(findobj('Tag','Graphics'), 'WindowButtonUpFcn', 'draw_anatomical_roi_2008(''moveslice'');'); +% Use the spm_orthviews menu to ZOOM IN...and keep drawing! +% +% Example of brainstem ROI drawing: +% draw_anatomical_roi_2008('init', 'overlay', 'remi_mean_T2.img'); +% set(findobj('Tag','Graphics'), 'WindowButtonUpFcn', ''); +% cluster_orthviews(red, {[1 0 0]}, 'overlay', 'remi_mean_T2.img'); +% stn = mask2clusters('ROI_STN.img'); +% cluster_orthviews(stn, {[0 1 1]}, 'add'); +% % Now zoom in to the midbrain in SPM orthviews and draw new ROIs +% +% Tor Wager, Dec 2008 + +function draw_anatomical_roi_2008(meth, varargin) + +if nargin < 1, meth = 'init'; end + +while ~strcmp(meth, 'exit') + +% switch meth +% % for drawing tools only, get tag based on which axis you clicked on +% % update orientation data.orient +% case {'free', 'poly'} +% get_current_orientation; +% end + + + switch meth + + case 'choose_next' + meth = input('Enter command(init/load/free/poly/add/remove/smooth/write/exit): ', 's'); % 'free'; + + case 'init' + init_slice_figure(varargin{:}); + move_slice; + meth = 'exit'; + + case 'load' + load_vol(varargin{:}); + meth = 'exit'; + + case 'moveslice', move_slice; + % This is primarily used as the mouse-up function in + % spm_orthviews, and is run when you click the orthviews + meth = 'exit'; + + case 'free' + disp('Draw freehand region on the axial slice in Slices fig.'); + draw_freehand; + meth = 'exit'; + + case 'freesagg' + disp('Draw freehand region on the saggital slice in Slices fig.'); + data = get_gui_data; + axis_handle = data.saggh; + draw_freehand(axis_handle); + meth = 'exit'; + + case 'zoomin', zoom_in; + meth = 'exit'; + + case 'zoomout', zoom_out; + meth = 'exit'; + + case 'poly' + draw_poly; + meth = 'exit'; + + case 'add' + add_region_to_roi_mask; + meth = 'exit'; + + case 'remove' + add_region_to_roi_mask('remove'); + meth = 'exit'; + + case 'smooth' + smooth_vol(varargin{:}); + meth = 'exit'; + + case 'write' + write_roi_mask; + meth = 'exit'; + + case 'exit' + % quit out + return + + otherwise + disp('unknown method.') + meth = 'choose_next'; + + end + + +end % while loop + +end + +% -------------------------------- +% Get data from GUI figure +% -------------------------------- + +function [data, f] = get_gui_data +f = findobj('Type', 'Figure', 'Tag', 'Slices'); +data = guidata(f); +end + +% -------------------------------- +% Update orientation of figure in just-clicked-on axis +% -------------------------------- + +function get_current_orientation + +[data, f] = get_gui_data; + + +mytag = get(gca, 'Tag'); +switch mytag + case 'ax', orient = 'ax'; + case 'sagg', orient = 'sagg'; + otherwise + disp('Click on one of the slices in the Slices window.'); + orient = []; +end + +data.orient = orient; +guidata(f, data); + +end + +% -------------------------------- +% Initialize orthviews and slice figure (which stores data) +% -------------------------------- + +function init_slice_figure(varargin) +disp('Initializing orthviews and slices.'); +disp('This clears any previous ROI you''ve been building.') +disp('Use free or poly commands to draw regions on slices,') +disp('and then ''add'' to add them to your ROI if you are'); +disp('satisfied with them.') + +ovl = which('scalped_avg152T1.img'); + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'ovl', 'overlay'}, ovl = varargin{i+1}; varargin{i+1} = []; + otherwise, warning('drawRoi:unknownoption', ['Unknown input string option:' varargin{i}]); + end + end +end + +V = spm_vol(ovl); +dat = spm_read_vols(V); +dim = V.dim; + +spm_image('init', ovl); +orthfig = findobj('Type', 'Figure', 'Tag', 'Graphics'); +set(orthfig, 'WindowButtonUpFcn', 'draw_anatomical_roi_2008(''moveslice'');'); + +f = create_figure('Slices', 2, 2); +%data = guidata(f); +data = []; +data.ovl = ovl; +data.V = V; +data.dat = dat; +data.dim = dim; + +subplot(2, 2, 1); hold off; +data.axh = gca; +set(gca, 'Tag', 'ax'); % Tag is used for getting which axis you clicked; must be re-set every time hold is off +subplot(2, 2, 2); hold off; +data.saggh = gca; +set(gca, 'Tag', 'sagg'); + +subplot(2, 2, 3); hold off; +% data.corh = gca; +% set(gca, 'Tag', 'cor'); + +create_uibuttons; + +create_figure('Volume'); +data.surfh = gca; + +Rvol = zeros(data.dim); +data.Rvol = Rvol; +guidata(f, data); +end + +% -------------------------------- +% Load from mask .img file +% -------------------------------- +function load_vol(varargin) + +if nargin < 1, img = spm_select(1); +else img = varargin{1}; +end + +[data, f] = get_gui_data; + +dat = scn_map_image(img, data.V.fname); +dat = ~isnan(dat) & dat ~= 0; +data.Rvol = dat; + +guidata(f, data); + +move_slice; +draw_volume_render; +end + +% -------------------------------- +% Redraw slices based on current orthviews position +% -------------------------------- + +function move_slice +%disp('Showing slices based on current coordinate position.'); +coord = spm_orthviews('Pos'); + +[data, f] = get_gui_data; +data.coord = coord; + +vox = mm2voxel(coord, data.V.mat); +data.wh_slice_ax = round(vox(3)); +data.wh_slice_sagg = round(vox(1)); + +figure(f) + +if isempty(data.axh) || isempty(data.saggh) || ~ishandle(data.axh) || ~ishandle(data.saggh) + disp('Slices window was closed. Re-''init'''); + return +end + +% draw slices +axslice = data.dat(:, :, data.wh_slice_ax); +saggslice = rot90(squeeze(data.dat(data.wh_slice_sagg, :, :))); + +axes(data.axh); hold off; +imagesc(axslice); +axis tight, axis image, hold on + +axes(data.saggh); hold off; +imagesc(saggslice); +axis tight, axis image, hold on + +% set axis limits if we have them from before +% (we don't have them if we're initializing) +if isfield(data, 'axislimits') && ~isempty(data.axislimits) + set(data.axh, 'XLim', data.axislimits.axx); + set(data.axh, 'YLim', data.axislimits.axy); + set(data.saggh, 'XLim', data.axislimits.saggx); + set(data.saggh, 'YLim', data.axislimits.saggy); + + axslice = axslice(ceil(data.axislimits.axy(1)) : floor(data.axislimits.axy(2)), ... + ceil(data.axislimits.axx(1)) : floor(data.axislimits.axx(2))); + + saggslice = saggslice(ceil(data.axislimits.saggy(1)) : floor(data.axislimits.saggy(2)), ... + ceil(data.axislimits.saggx(1)) : floor(data.axislimits.saggx(2))); +end + +% set color limits (for good contrast) +stdax = std(axslice(:)); +stdsagg = std(saggslice(:)); +meanax = mean(axslice(:)); +meansagg = mean(saggslice(:)); +axclim = [meanax - 2*stdax meanax + 2*stdax]; +saggclim = [meansagg - 2*stdsagg meansagg + 2*stdsagg]; +%axclim = [prctile(axslice(:), 5) prctile(axslice(:), 95)]; +%saggclim = [prctile(saggslice(:), 5) prctile(saggslice(:), 95)]; +set(data.axh, 'CLim', axclim) +set(data.saggh, 'CLim', saggclim) + + + +set(data.axh, 'Tag', 'ax'); % Tag is used for getting which axis you clicked; must be re-set every time hold is off +set(data.saggh, 'Tag', 'sagg'); + +% Add previous regions +myslice = zeros(data.dim(1:2)); +myslice(:, :, 2) = data.Rvol(:, :, data.wh_slice_ax); + +% because of bug when zooming in, we want to make the space tight around +% regions + +FVC = isocaps(myslice, 0, 'zmax'); +axes(data.axh); +patch(FVC, 'EdgeColor', 'g', 'FaceColor', 'g'); +%set(patchh, 'FaceAlpha', .5) + +clear myslice, myslice(:, :, 1) = zeros(data.dim([3 2])); +myslice(:, :, 2) = rot90(squeeze(data.Rvol(data.wh_slice_sagg, :, :))); +FVC = isocaps(myslice, 0, 'zmax'); +axes(data.saggh); +patch(FVC, 'EdgeColor', 'g', 'FaceColor', 'g'); %, %'FaceAlpha', .5); + +axis tight, axis image, hold on, colormap gray + +guidata(f, data); +end + +% -------------------------------- +% Region drawing tools +% -------------------------------- + +function draw_freehand(varargin) + +data = get_gui_data; + +if nargin > 0, axis_handle = varargin{1}; +else axis_handle = data.axh; +end +axes(axis_handle); + +get_current_orientation; +[data, f] = get_gui_data; + +if isfield(data, 'fh') && ishandle(data.fh), delete(data.fh); end + +try + h = imfreehand(axis_handle); +catch + disp('Error with freehand draw due to unknown axis-handling bug.'); + disp('You should probably close the Slices figure') + disp('and re-initialize (''write'' first if you want).'); + return +end + +api = iptgetapi(h); +position = api.getPosition(); +xi = position(:, 1); +yi = position(:, 2); + +data.fh = fill(xi, yi, 'r', 'FaceAlpha', .4); + +if isempty(data.orient); % bad axis + disp('Click on one of the slices in the ''Slices'' figure and re-enter free command.') + return +end + +switch data.orient + + case 'ax' + R = poly2mask(position(:, 1),position(:, 2),data.dim(1),data.dim(2)); + + case 'sagg' + R = poly2mask(position(:, 1),position(:, 2),data.dim(3),data.dim(2)); + R = rot90(R, -1); +end + +data.R = R; + +guidata(f, data); + +% re-set window button +%set(f, 'WindowButtonUpFcn', 'draw_anatomical_roi_2008(''free'')'); + +end + +% -------------------------------- +% Zoom in and set explicit axis limits +% -------------------------------- +function zoom_in + +disp('Click on the top left and then bottom right') +disp('of the desired zoom area in the Slices window.'); +pos = ginput(2); +get_current_orientation; + +[data, f] = get_gui_data; + +set(gca, 'XLim', sort(pos(1:2, 1))); +set(gca, 'YLim', sort(pos(1:2, 2))); + +% get axis limits. +% We will use these to constrain view area +data.axislimits.axx = get(data.axh, 'XLim'); +data.axislimits.axy = get(data.axh, 'YLim'); +data.axislimits.saggx = get(data.saggh, 'XLim'); +data.axislimits.saggy = get(data.saggh, 'YLim'); + +guidata(f, data); + +end + +% -------------------------------- +% Zoom out: remove explicit axis limits +% -------------------------------- +function zoom_out +[data, f] = get_gui_data; +if isfield(data, 'axislimits'); + data = rmfield(data, 'axislimits'); + guidata(f, data); +end + +move_slice; +end + +% -------------------------------- +% Region drawing tools: poly +% -------------------------------- +function draw_poly +disp('Drawing movable polygon region.'); +disp('NOT COMPLETE OPTION YET.'); + +% [data, f] = get_gui_data; +% if isfield(data, 'fh') && ishandle(data.fh), delete(data.fh); end +% +% if isempty(data.orient); % bad axis +% disp('Click on one of the slices in the ''Slices'' figure and re-enter free command.') +% return +% end +% +% % polygon +% [data.R, xi, yi] = roipoly; +% +% data.fh = fill(xi, yi, 'r', 'FaceAlpha', .4); +% +% guidata(f, data); +end + +% ----------------------------------------- +% Add (or remove) region to the ROI volume +% ----------------------------------------- + +function add_region_to_roi_mask(varargin) + +doremove = 0; % remove region instead of adding it +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'remove', doremove = 1; + otherwise, warning('drawRoi:unknownoption', ['Unknown input string option:' varargin{i}]); + end + end +end + +if doremove + disp('Removing region from ROI mask.'); + addstr = 'noadd'; +else + disp('Adding region to ROI mask.'); + addstr = 'add'; +end + +[data, f] = get_gui_data; +thickness = 3; +%data.R = repmat(data.R, [1 1 thickness]); + +switch data.orient + case 'ax' + slice_vals = data.wh_slice_ax - (thickness - 1)/2 : data.wh_slice_ax + (thickness - 1)/2; + Rslices = data.Rvol(:, :, slice_vals); + if doremove + oldR = data.R(:, :, slice_vals); + Rslices(oldR == 1) = 0; + else + Rslices(data.R == 1) = 1; + end + data.Rvol(:, :, slice_vals) = Rslices; + %data.Rvol(:, :, slice_vals) = double(data.Rvol(:, :, slice_vals)) + data.R; + + case 'sagg' + slice_vals = data.wh_slice_sagg - (thickness - 1)/2 : data.wh_slice_sagg + (thickness - 1)/2; + + % if new slice, permute and replicate to thickness. if not, don't + % if we click "add" on an empty slice, it will have old data in + % it... + if length(size(data.R)) ~= 3 + data.R = repmat(data.R, [1 1 thickness]); + data.R = permute(data.R, [3 1 2]); % Slices with data to replace + end + + if doremove + oldR = data.Rvol(slice_vals, :, :); + oldR(logical(data.R)) = 0; + data.Rvol(slice_vals, :, :) = oldR; + + else % add + data.Rvol(slice_vals, :, :) = double(data.Rvol(slice_vals, :, :) | data.R); + end + +% data.R = permute(data.R, [3 1 2]); % Slices with data to replace +% Rslices = data.Rvol(slice_vals, :, :); % previous data to manipulate, from whole volume +% if doremove +% oldR = data.R(slice_vals, :, :); +% Rslices(oldR == 1) = 0; +% else % add +% oldR = permute(data.R, [3 1 2]); +% Rslices(data.R == 1) = 1; % preserve previous 1 values +% end +% data.Rvol(slice_vals, :, :) = Rslices; +% %data.Rvol(slice_vals, :, :) =double(data.Rvol(slice_vals, :, :)) + data.R; + +end + +guidata(f, data); + +% show it +move_slice; +draw_volume_render(addstr); + +end + +% -------------------------------- +% Write ROI mask +% -------------------------------- + +function write_roi_mask +f = findobj('Type', 'Figure', 'Tag', 'Slices'); +data = guidata(f); + +data.Vout.fname = input('Enter file name (e.g., ROI.img): ', 's'); +data.Vout.mat = data.V.mat; +data.Vout.dim = data.V.dim; +data.Vout.n = [1 1]; +data.Vout = spm_create_vol(data.Vout); +spm_write_vol(data.Vout, data.Rvol); +disp(['Written: ' data.Vout.fname]); + +cl_roi = mask2clusters(data.Vout.fname); + +cluster_orthviews(cl_roi, {[1 0 0]}, 'overlay', data.ovl); + +assignin('base', 'cl_roi', cl_roi) +disp('Assigned cluster cl_roi in base workspace.'); + +% set callback so we can keep drawing +orthfig = findobj('Type', 'Figure', 'Tag', 'Graphics'); +set(orthfig, 'WindowButtonUpFcn', 'draw_anatomical_roi_2008(''moveslice'');'); + +end + +% -------------------------------- +% orthviews and vol rendering +% -------------------------------- + +function draw_volume_render(addstr) + +if nargin < 1, addstr = 'add'; end + +data = get_gui_data; + +cl = mask2clusters(data.Rvol, data.V.mat); +if isempty(cl) + spm_image('init', data.ovl); +else + cluster_orthviews(cl, {[0 1 0]}, addstr, 'overlay', data.ovl); +end + +% do this again, just in case it gets messed up +orthfig = findobj('Type', 'Figure', 'Tag', 'Graphics'); +set(orthfig, 'WindowButtonUpFcn', 'draw_anatomical_roi_2008(''moveslice'');'); + +axes(data.surfh); hold on; +if isfield(data, 'surfobjh') && ishandle(data.surfobjh) + delete(data.surfobjh) +else + % draw new surface + % brainh = addbrain; set(brainh, 'FaceAlpha', .2); + view(132, 30); + lighting gouraud; lightFollowView; material dull; + axis image, axis tight + rotate3d off +end + +data.cl = cl; +if ~isempty(cl) + data.surfobjh = imageCluster('cluster', cl, 'color', 'g'); + axis image; axis tight; lightRestoreSingle; lighting gouraud + rotate3d off +else + data.surfobjh = []; +end + +end + +function smooth_vol(varargin) + +disp('Smoothing ROI region.'); + +[data, f] = get_gui_data; + +data.Rvol = smooth3(data.Rvol, 'g', 5); +data.Rvol = data.Rvol > .5; + +guidata(f, data); + +move_slice; +draw_volume_render('noadd'); + +end + + +function create_uibuttons + +[data, f] = get_gui_data; + +x1start = 30; +x2start = 200; + +str = 'draw_anatomical_roi_2008(''free'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Draw (axial)',... +'Position',[x1start 200-35*0 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +str = 'draw_anatomical_roi_2008(''freesagg'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Draw (saggital)',... +'Position',[x2start 200-35*0 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +% ------ +str = 'draw_anatomical_roi_2008(''add'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Add region',... +'Position',[x1start 200-35*1 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +% ------ +str = 'draw_anatomical_roi_2008(''remove'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Remove region',... +'Position',[x1start 200-35*2 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +% ------ +str = 'draw_anatomical_roi_2008(''write'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Write ROI mask',... +'Position',[x1start 200-35*3 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +% ------ +str = 'draw_anatomical_roi_2008(''smooth'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Smooth 3D ROI',... +'Position',[x1start 200-35*4 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +% ------ +str = 'draw_anatomical_roi_2008(''zoomin'');'; +str = expand_callback_str(str); + +uicontrol(f,'String','Zoom in',... +'Position',[x2start 200-35*1 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); +axis off + +str = 'draw_anatomical_roi_2008(''zoomout'');'; +str = expand_callback_str(str); +uicontrol(f,'String','Zoom out',... +'Position',[x2start 200-35*2 150 30],... +'CallBack', str,... +'Interruptible','on',... +'ForegroundColor','k','FontWeight','b'); + +axis off +end + +function str = expand_callback_str(str) + +str2 = []; +for i = 1:length(str) + if str(i) == '''', str2(end+1) = ''''; str2(end+1) = ''''; + else str2(end+1) = str(i); + end +end + +str = ['disp(''' char(str2) '''), ' str ]; % display then execute + +end diff --git a/ROI_drawing_tools/sphere_mask.m b/ROI_drawing_tools/sphere_mask.m new file mode 100755 index 00000000..db328672 --- /dev/null +++ b/ROI_drawing_tools/sphere_mask.m @@ -0,0 +1,150 @@ +function [clusters,CLU] = sphere_mask(P,XYZmm,r,outname,varargin) +% [clusters,maskCLU] = sphere_mask(fname,XYZmm,r,outname,[maskname],[overlay]) +% +% Creates mask images and clusters for a set of spheres defined around +% coordinates you specify. Used for creating regions of interest (ROIs). +% Spheres may be masked with an anatomical mask file. +% +% +% P is input image name with correct dimensions and vox sizes for your +% study +% XYZmm is mm coordinates (row vector) for sphere center +% r is radius in mm +% outname is string for output mask name, e.g., 'sphere_mask.img' +% [maskname] is optional mask .img containing additional constraints +% (e.g., gray matter mask, etc.), can be in different dimensions +% see also mask2clusters, montage_clusters +% +% tor wager, Fall 2003. +% modified March 2004 to provide additional anatomical masking based on +% anatomical ROI (e.g., a gray matter region from the ICBM template) +% +% Example: +% tmp = sphere_mask(EXPT.SNPM.P{1}(1,:),[25.6 -45.5 77.0],10,'test.img','ICBM_area74.img'); +% icbm_localize(tmp) +% +% M =which('ICBM_brainonly_1mm_seg1.img') +% cl = sphere_mask(d(1).name,[35 -57 54; -23 -59 56;13 -63 62; -7 -77 50; 25 -79 30; -23 -81 18;41 -7 46;-37 -7 46; 35 -1 30; 29 -3 60; -29 -13 46; -5 -1 56;-37 29 30; 53 21 32;51 11 -4;43 -69 14;-45 -71 12;29 -83 4;11 -89 -4;-11 -79 2; -1 -95 -14; 17 -99 -8;31 -77 -20;-23 -81 -20;41 -71 -20;-43 -79 -12],8,'tmp.img',M); +% +% Matlab 6.5/OSX bug gives seg fault or something if mask is too big. + +CLU = []; + +% call this function recursively, saving mask CLU after first time +if size(XYZmm,1) > 1 + disp('Multiple ROI mode') + cl = []; + for i = 1:size(XYZmm,1) + if length(varargin) > 0, [out,varargin{1}] = sphere_mask(P,XYZmm(i,:),r,outname,varargin{1});, + else, [out] = sphere_mask(P,XYZmm(i,:),r,outname); + end + cl = [cl,out]; + end + clusters = cl; + + if length(varargin) > 1, ovl = varargin{2};, else, ovl = [];, end + %montage_clusters(which(P),clusters,{'r'}); + cluster_orthviews(clusters,{[1 0 0]},'overlay',ovl) + + V = spm_vol(P); V.fname = outname; + [m] = clusters2mask(clusters,V.dim(1:3)); + spm_write_vol(V,m); + +else + +P = which(P); +if isempty(P), P = which('scalped_avg152T1_graymatter_smoothed.img');,end + +if isempty(P), disp(['Cannot find image with vox sizes for your study.']);,end + +V = spm_vol(P); + + +if length(varargin) > 0 + m = varargin{1}; + if isstr(m), m = which(m);, end + if isempty(m), error('Cannot find mask image.');, end +end + +V.M = V.mat; +XYZ = mm2voxel(XYZmm,V); +rv = abs(r ./ diag(V.M(1:3,1:3))'); % voxel coords + + +% build box (list of XYZ voxel coords) + +lim = round([XYZ - rv; XYZ + rv]); +diffs = diff(lim); + +xtmp = prod([diffs(2)+1 diffs(3)+1]); +ztmp = prod([diffs(1)+1 diffs(2)+1]); + +x = repmat((lim(1,1):lim(2,1))',xtmp,1); + +y = []; for i=1:diffs(2)+1, + ytmp = repmat(lim(1,2)+i-1,diffs(1)+1,1); y = [y;ytmp];, +end +y = repmat(y,diffs(3)+1,1); + +ztmp = repmat(lim(1,3),ztmp,1); +z = []; +for i = 1:diffs(3)+1 + z = [z; ztmp+i-1]; +end + +xyz2 = [x y z]; + +xyz2mm = voxel2mm(xyz2',V.mat)'; +xyzmm = repmat(XYZmm,size(xyz2,1),1); + +if isempty(xyzmm), error('No coordinates in mask.'), end + +d = sum((xyz2mm - xyzmm) .^ 2,2) .^ .5; + + +if length(varargin) > 0 + % mask - determine in-mask voxels + fprintf(1,'Applying mask... ') + %m = varargin{1}; + if isstr(m), [dum,CLU] = mask2clusters(m); % mask name, + else + CLU = m; % input CLU structure + end + [t1,t2,t3]=intersect(round(xyz2mm),round(CLU.XYZmm'),'rows'); + t4 = zeros(size(xyz2mm,1),1); t4(t2) = 1; +else + % No mask, all voxels are OK + t4 = ones(size(xyz2mm,1),1); +end + +wh = find(d <= r & t4); + + + +xyz2 = xyz2(wh,:); +xyz2mm = xyz2mm(wh,:); + +clusters = xyz2clusters(xyz2mm,P); + +% mask = voxel2mask(xyz2,V.dim(1:3)); +% +% if length(size(mask)) > 3 +% mask = mask(:,:,:,1); +% end +% +% V.fname = outname; +% spm_write_vol(V,mask); +% fprintf(1,['Written ' V.fname]) +% +% clusters = mask2clusters(outname); +% if isempty(clusters) +% fprintf(1,'Empty clusters!') +% else +% %cluster_orthviews(clusters,{[1 0 0]}) +% %montage_clusters(P,clusters,{'r'}); +% end + +end + + +return diff --git a/Statistics_tools/Bspline.m b/Statistics_tools/Bspline.m new file mode 100644 index 00000000..381c09dd --- /dev/null +++ b/Statistics_tools/Bspline.m @@ -0,0 +1,91 @@ +function B = Bspline(t,k,u,v,ForceSup) +% function Bspline(t,k,u[,v]) +% +% Create B-Spline basis of order k, with knots u, evaluated at t. +% If control verticies v are specified then then B is the spline +% function instead of the basis. +% +% u must be at least length(k)+1 +% +% $Id: Bspline.m,v 1.3 1998/11/09 00:55:06 nicholst Exp $ + +global iB + +if (nargin<3) + help Bspline + return +end + +if (k+1>length(u)) + error('u must be at least length k+1') +end + +% Silent flag for forcing the support to be defined between u(k) +% and u(end-k+1) +if (nargin<5) + ForceSup = 1; +end + +if ((nargin>3) & (length(v)+k ~= length(u)) & (ForceSup)) + error(sprintf('%d knots requires %d control verticies', ... + length(u), length(u)-k)) +end + +% columnize +t=t(:); +u=u(:); + +nBasis = length(u)-k; +B = zeros(length(t),nBasis); +iB = zeros(1,nBasis); + +for i=1:nBasis + B(:,i) = recu(t,i,k,u); + iB(i) = (u(i+k)-u(i))/k; +end + +% zero outside of valid range, if there's enough +if (length(u)>=2*k) + if (ForceSup) + B(t3) + B = B*v(:); +end + + +return + + +function B = recu(t,i,k,u) + +if (k==1) + + if (u(i)==u(i+1)) + B = zeros(size(t)); + else + B = (u(i)<=t) & (t 0); +lt(isnan(a)) = nan; + +%remove by a factor of unit +i = 1; +while i < max(predrange) + lt(i:i+unit-1,i) = 0; + i = i + 1; +end + +n = length(predrange)-unit; +miss = tril(isnan(lt)); %calculate lower triangle accuracy +miss(logical(eye(length(predrange))))=0; %remove any nans on diagonal +total = (n*(n+1)/2) - sum(sum(miss)); %subtract out any nans from total +acc = sum(nansum(lt))/total; + + diff --git a/Statistics_tools/F_test_full_vs_red.m b/Statistics_tools/F_test_full_vs_red.m new file mode 100644 index 00000000..d76753b4 --- /dev/null +++ b/Statistics_tools/F_test_full_vs_red.m @@ -0,0 +1,52 @@ +% [F, p, resid, df_model, df_error] = F_test_full_vs_red(y, X, Xred, px, pxred) +% +% E.g.: +% +% X = randn(100, 3); Xred = X(:,1); y = X(:,2) + randn(100, 1); +% px = pinv(X); pxred = pinv(Xred); +% [F, p, resid] = F_test_full_vs_red(y, X, Xred, px, pxred); +% +% Test full-model F-value against regress.m +% Tested OK on 11/27/07, by tor +% Xred = X(:,end); % intercept only +% px = pinv(X); +% pxred = pinv(Xred); +% [F, p, resid, dfm, dfe] = F_test_full_vs_red(y, X, Xred, px, pxred); % full model F-test +% [b, bint, r, rint, stats] = regress(y, X); + +function [F, p, resid, df_model, df_error] = F_test_full_vs_red(y, X, Xred, px, pxred) + T = length(y); % Length of time course + + k = size(px, 1); % predictors: full model + kred = size(pxred, 1); % predictors: reduced model + + % Degrees of freedom: model: Full - reduced + df_model = k - kred; % degrees of freedom for model (param - 1) + df_error = T - k; % error DF, full model + + % Step 1: Find the OLS solution for the FULL model + % --------------------------------------------------- + beta = px * y; % Beta values + resid = y - X * beta; % Residuals + + % Sums of squares + %SST = y' * y; + SSE = resid' * resid; + %SSfull = SST - SSE; + var_est = SSE / df_error; % Estimate of Sigma^2 + + % Step 2: Find the OLS solution for the REDUCED model + % F-test for full vs. reduced + % --------------------------------------------------- + betared = pxred * y; % Beta values + residred = y - Xred * betared; + SSEred = residred' * residred; + %SSred = betared' * Xred' * y; % Full model sum of squares + + % F stat + % (SSred - SSfull) ./ (var * df_model) + F = (SSEred - SSE) / (var_est * df_model); % F-statistic - compare with F-distribution with (param-1, df) degrees of freedom + + p = 1 - fcdf(F, df_model, df_error); +end + diff --git a/Statistics_tools/F_test_no_intercept.m b/Statistics_tools/F_test_no_intercept.m new file mode 100644 index 00000000..091a0b9e --- /dev/null +++ b/Statistics_tools/F_test_no_intercept.m @@ -0,0 +1,68 @@ +function [Fobs, p, dfb, dfe] = F_test_no_intercept(X,y,s) +% [Fobs, p, dfb, dfe] = F_test_no_intercept(X,y,s) +% +% Test of the null hypothesis that the ENTIRE regression model X explains no +% variance in the data (y) +% +% WOrking function; in progress +% +% Assess false positive rate with robust regression: +% iter = 5000; +% warning off +% fvals = zeros(1,iter); pvals = fvals; +% for i = 1:iter +% X = randn(10,1); y = randn(10,1); +% [bb,stats]=robustfit(X,y); +% % [bb,dev,stats] =glmfit(X,y); +% [Fobs, p, dfb, dfe] = F_test_no_intercept(X,y,stats.s); +% fvals(i) = Fobs; pvals(i) = p; +% end +% fpr = sum(pvals<.05) ./ iter +% warning on +% +% Based on: +% Johnson & Wichern, 5th ed., p. 371, Result 7.6 +% +% for a regression model with k predictors, and q regression parameters +% in reduced model, test the addition of q+1...k by extra sums of squares +% [n(s2red - s2full) / (k-q)] / [n*s2full / (n-k-1)] ~ F(k-q),(n-k-1) +% +% i'm trying to generalize this to q = 0, when we care about the intercept + +q = 0; % number of params in reduced model; 0 for test of ENTIRE model, including intercept +interceptp = 0; % number of intercept params; 0 for test of ENTIRE model, 1 for standard intercept + +[n,k] = size(X); + +if no_intercept(X) + k = k + 1; +end + +s2 = s ^ 2; +s2red = y' * y ./ n; % full variance, zero parameters fit + +varexp = s2red - s2; + +dfb = k - q; % + 1 because X doesn't contain intercept +dfe = n - k - interceptp; + +Fnum = n * varexp / dfb; + +Fden = n * s2 / dfe; + +Fobs = Fnum / Fden; + +p = 1 - fcdf(Fobs,dfb,dfe); + +return + + + + + +function val = no_intercept(X) + +val = all(any(diff(X))); + +return + diff --git a/Statistics_tools/ICC.m b/Statistics_tools/ICC.m new file mode 100644 index 00000000..6a1480fb --- /dev/null +++ b/Statistics_tools/ICC.m @@ -0,0 +1,144 @@ +function out = ICC(cse,typ,dat) + % iccvalue = ICC([1 to 6],['single' or 'k'], data matrix) + % +%function to work out ICCs according to shrout & fleiss' schema (Shrout PE, +%Fleiss JL. Intraclass correlations: uses in assessing rater reliability. +%Psychol Bull. 1979;86:420-428). +% +% Modified 10/09 by Tor Wager; minor bug fix and changes to documentation +% +% 'dat' is data whose *columns* represent k different raters (judges) & whose +% *rows* represent n different cases or targets being measured. Each target +% is assumed to be a random sample from a population of targets. +% +% 'cse' is either 1,2,3. 'cse' is: 1 if each target is measured by a +% different set of raters from a population of raters, 2 if each target is +% measured by the same raters, but that these raters are sampled from a +% population of raters, 3 if each target is measured by the same raters and +% these raters are the only raters of interest. +% +% 'typ' is either 'single' or 'k' & denotes whether the ICC is based on a +% single measurement or on an average of k measurements, where k = the +% number of ratings/raters. +% +% This has been tested using the example data in the paper by shrout & fleiss. +% +% Example: out = ICC(3,'k',S_Fdata) +% returns ICC(3,k) of data 'S_Fdata' to double 'out'. +% +% Kevin Brownhill, Imaging Sciences, KCL, London kevin.brownhill@kcl.ac.uk +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +% +% Additional documentation +% iccvalue = ICC([1 to 6],['single' or 'k'], data matrix) +% +% Here, columns are 'judges', or more generally, 'measures' that are +% usually ideally intercorrelated. Rows are items being assessed. +% The ICC assesses the proportion of variance attributed to the items, +% shared across measures. +% As the correlation between the measures grows, the icc grows. +% Another way of saying this is that if the rows are consistently different +% across measures, the icc will be high. +% +% Think of rows as criminals, and columns as judges. The data values are 'guilt scores', +% where higher is more guilty. If all the judges agree, the most guilty +% cases will be rated as most guilty by all judges, and the icc will be +% high. This is actually consistent with Case 2 or 3 in Shrout and Fleiss. +% +% In Case 1, the columns don't have any real meaning, as there are +% different 'judges' for each row, and variance components due to judge +% cannot be separated from error and the judge x target interaction. +% In Case 2 and 3, they are crossed. Case 2 treats judge as a random +% effect, whereas Case 3 treats judge as a fixed effect. +% +% If the data were an individual differences study of cognitive performance, +% then the rows would be subjects, and the columns would be tests. +% A high icc would indicate a high correlation across the tests, which +% indicates that subjects are reliably different from one another, i.e., +% that a large proportion of the total variance is related to subject. +% In such a case, as tests are fixed entities, then Case 3 might be +% appropriate. +% +% Cronbach's alpha is equal to ICC(3, k) - case 3, k +% This assumes no target x rater interaction +% +% More examples: +% +%dat = mvnrnd([1 1 1], [1 .5 .5; .5 1 .5; .5 .5 1], 50); whos dat +% corrcoef(dat) +% ri = ICC(2, 'k', dat) +% dat = mvnrnd([1 1 1], [1 .9 .9; .9 1 .9; .9 .9 1], 50); whos dat +% corrcoef(dat) +% ri = ICC(2, 'k', dat) +% +% In the example below, judges (measures) have systematically different +% means, and the ICC values are different. ICC(1, 1) is low because judge +% is not considered as a source of variance. ICC(2, 1) is higher, but +% intermediate, because judge is considered as a random effect and modeled, +% but we want to generalize to new judges. ICC(3, 1) is highest, because +% judge is modeled +% dat = mvnrnd([1 2 3], [1 .5 .5; .5 1 .5; .5 .5 1], 50); whos dat +% ri = ICC(1, 'single', dat) +% ri = ICC(2, 'single', dat) +% ri = ICC(3, 'single', dat) + + +%number of raters/ratings +k = size(dat,2); +%number of targets +n = size(dat,1); +%mean per target +mpt = mean(dat,2); +%mean per rater/rating +mpr = mean(dat); +%get total mean +tm = mean(mpt); +%within target sum sqrs +WSS = sum(sum(bsxfun(@minus,dat,mpt).^2)); +%within target mean sqrs +WMS = WSS / (n * (k - 1)); +%between rater sum sqrs +RSS = sum((mpr - tm).^2) * n; +%between rater mean sqrs +RMS = RSS / (k - 1); +% %get total sum sqrs +% TSS = sum(sum((dat - tm).^2)); +%between target sum sqrs +BSS = sum((mpt - tm).^2) * k; +%between targets mean squares +BMS = BSS / (n - 1); +%residual sum of squares +ESS = WSS - RSS; +%residual mean sqrs +EMS = ESS / ((k - 1) * (n - 1)); +switch cse + case 1 + switch typ + case 'single' + out = (BMS - WMS) / (BMS + (k - 1) * WMS); + case 'k' + out = (BMS - WMS) / BMS; + otherwise + error('Wrong value for input typ') + end + case 2 + switch typ + case 'single' + out = (BMS - EMS) / (BMS + (k - 1) * EMS + k * (RMS - EMS) / n); + case 'k' + out = (BMS - EMS) / (BMS + (RMS - EMS) / n); + otherwise + error('Wrong value for input typ') + end + case 3 + switch typ + case 'single' + out = (BMS - EMS) / (BMS + (k - 1) * EMS); + case 'k' + out = (BMS - EMS) / BMS; + otherwise + error('Wrong value for input typ') + end + otherwise + error('Wrong value for input cse') +end \ No newline at end of file diff --git a/Statistics_tools/ancova.m b/Statistics_tools/ancova.m new file mode 100755 index 00000000..a3609f81 --- /dev/null +++ b/Statistics_tools/ancova.m @@ -0,0 +1,115 @@ +function [b,t,p,hh] = ancova(groups,x,y,varargin) +% [b,t,p,pthandles] = ancova(groups,x,y,[plot],[covs of no interest]) +% +% +% elements of b, t, p +% 1st = intercept, 2nd = group effect, 3rd = slope, 4th = grp x slope +% interaction + +% ----------------------------------------- +% recursive -- call ancova repeately if y is a matrix +% get pairwise standardized slopes (corrs) +% and group diffs and slope interaction for all +% pairs of y vectors +% ----------------------------------------- +hh = []; +doplot = 0; if length(varargin) > 0, doplot = varargin{1};,end + +% covariates of no interest +if length(varargin) > 1, + covs = varargin{2};, + + [x,y,r,p,rrob,prob] = partialcor([x covs],y,1); + % doesn't adjust groups now... + +end + + + + + +if size(y,2) > 1 & isempty(x) + + for i = 1:size(y,2)-1 + for j = i+1:size(y,2) + + [b,t,p] = ancova(groups,y(:,i),y(:,j)); + bb{1}(i,j) = b(2); tt{1}(i,j) = t(2); pp{1}(i,j) = p(2); % group effect = cell 1 + bb{2}(i,j) = b(3); tt{2}(i,j) = t(3); pp{2}(i,j) = p(3); % slope = cell 2 + bb{3}(i,j) = b(4); tt{3}(i,j) = t(4); pp{3}(i,j) = p(4); % grp x slope = cell 3 + + end + end + + % clean up last row + for i = 1:length(bb) + bb{i}(end+1,:) = 0; bb{i} = bb{i} + bb{i}'; + tt{i}(end+1,:) = 0; tt{i} = tt{i} + tt{i}'; + pp{i}(end+1,:) = 0; pp{i} = pp{i} + pp{i}'; + end + + b = bb; + t = tt; + p = pp; + return + +end + + +% ----------------------------------------- +% basic ancova program +% ----------------------------------------- + +% center and format +x = [scale(groups,1) scale(x,1)]; +%y = scale(y); + +% add interaction in slopes (diff. slopes model) + +x(:,3) = scale(x(:,1).*x(:,2)); + +[b,dev,stats]=glmfit(x,y); +bi = b(end); ti = stats.t(end); ppi = stats.p(end); + +if ppi > .05 + + % no sig. interaction, drop interaction and use parallel slopes + + [b,dev,stats]=glmfit(x(:,1:2),y); + b(end+1) = bi; stats.t(end+1) = ti; stats.p(end+1) = ppi; + +end + +t = stats.t; +p = stats.p; + + +% ----------------------------------------- +% ancova plot +% ----------------------------------------- + + +if doplot + hh = []; + colors = {'yo' 'm^'}; + [uni,b,grps] = unique(groups); + legstr = {['Group 1: ' num2str(uni(1))] ['Group 2: ' num2str(uni(2))]}; + uni = 1:max(grps); + % median split, if continuous, high then low + if length(uni) > 2, + uni = [1 2]; grps = grps.*0; + grps(groups>median(groups)) = 1; + grps(groups 1 && ~isempty(varargin{1}) + + covs = varargin{1}; + + if size(covs,1) ~= size(dat,1), error('Covs and dat must have same no. of observations.'); end + + disp('Warning: covariates are no longer removed for within-subject SE bars.'); + +end + +% number of subjects and conditions +[n, k] = size(dat); + +df_s = n - 1; % degrees of freedom for subjects +df_c = k - 1; % degrees of freedom for conditions +df_sxc = df_s * df_c; % degrees of freedom for subj x condition + +% within-subject errors +errs = scale(scale(dat, 1)', 1)'; + +% Sums of squared errors +SS_sxc = errs(:)' * errs(:); + +% Mean squared error, accounting for reduced df due to estimating marginal means +MS_sxc = SS_sxc / df_sxc; + +se_within = sqrt(MS_sxc / n); + +ci = se_within * tinv(1 - (.05 / 2), df_sxc); + +stats.se_within = se_within; +stats.ci = ci; +stats.ci_descrip = '95% confidence interval'; +stats.SS_sxc = SS_sxc; +stats.MS_sxc = MS_sxc; + +% Extra stuff from ANOVA table +% --------------------------------------------------------------- +% Mean square for condition: Variance of condition means * sample +% size...average squared variance accounted for by condition means +stats.MS_cond = var( mean(dat) - mean(dat(:)) ) * n; + +stats.MS_subject = var( mean(dat') - mean(dat(:)) ) * k; + +datv = dat(:); +stats.MS_total = scale(datv, 1)' * scale(datv, 1); + +return + + + diff --git a/Statistics_tools/bayes_get_probabilities.m b/Statistics_tools/bayes_get_probabilities.m new file mode 100644 index 00000000..22ac8d31 --- /dev/null +++ b/Statistics_tools/bayes_get_probabilities.m @@ -0,0 +1,147 @@ +function [priors, pa1_given_t, pa0_given_t, varargout] = bayes_get_probabilities(Y, Xi, k) +% +% [priors, pa1_given_t, pa0_given_t, pt_given_act1, pt_given_act0, pa1_given_not_t] = +% bayes_get_probabilities(Y, Xi, k) +% +% k is regularization param, biases towards 0.5 +% Y is data matrix, obs x features, 1/0 (active/not) +% Xi is task indicator matrix +% +% tor wager, oct 07 +% This is a sub-function of classify_naive_bayes.m +% For complete help, see classify_naive_bayes.m +% +% + +[nobs, nfeatures] = size(Y); +nclasses = size(Xi, 2); + +% Priors and evidence: Marginal probability estimates +% ----------------------------------------------------- +sumx = sum(Xi); % number of obs in each class +N = sum(sumx); % total obs +repsum = sumx(ones(nfeatures, 1), :); + +% priors, p(t) for each of t tasks +% assume Xi are task indicators, must be zero or one +priors = sumx ./ N; + +% evidence, p(a) for each activation state S = {0, 1} + +% Note: Only return this in 'full' output mode, because classification can be +% (is) made on the likelihood * p(t), which is proportional to p(t | a) +% also, this way, we can accumulate likelihood based on the dist. of all +% the data, and apply the priors only once. + +% % % no need to save? +% % evidence_yes = sum(Y) ./ N; +% % evidence_no = 1 - evidence_yes; + +% Likelihoods and posteriors: conditional probabilities +% ----------------------------------------------------- + +% likelihoods: p(activity | task) +xcount = Y' * Xi; % activation count for each task +xnotcount = repsum - xcount; + +% k is regularization param, biases towards 0.5 (2 classes) or 1/# classes +pa1_given_t = (xcount + k) ./ (nclasses*k + repsum); %(repmat(sumx, nfeatures, 1)); + +pa0_given_t = (xnotcount + k) ./ (nclasses*k + repsum); %(repmat(sumx, nfeatures, 1)); + + +% % % Likelihood ratio +% % evidence_yes = (sum(Y) ./ nobs)'; +% % evidence_yes = evidence_yes(:, ones(1, nclasses)); +% % % add .1 to regularize... +% % lr = ( (pa1_given_t + .1) ./ (evidence_yes + .1) ) - 1; + +%%% need to apply priors only once!! Not to each voxel. Don't need to +%%% return full posterior for each voxel. +% posteriors: p(task | activity) +if nargout > 3 + + notY = ~Y; + sumv = sum(Y); + sumnotv = sum(notY); + + pt_given_act1 = xcount ./ (sumv(ones(2, 1), :)'); + pt_given_act0 = (notY' * Xi) ./ (sumnotv(ones(2, 1), :)'); + + pt_given_act1(pt_given_act1 == 0) = .001; + pt_given_act1(pt_given_act1 == 1) = .999; + + pt_given_act0(pt_given_act0 == 0) = .001; + pt_given_act0(pt_given_act0 == 1) = .999; + + varargout{1} = pt_given_act1; + varargout{2} = pt_given_act0; +end + + +if nargout > 5 + pa1_given_not_t = xnotcount ./ (N - repsum); + + pa1_given_not_t(pa1_given_not_t == 0) = .001; + pa1_given_not_t(pa1_given_not_t == 1) = .999; + + varargout{3} = pa1_given_not_t; +end + + + + +% % % % % priors, p(t) for each of t tasks +% % % % % assume Xi are task indicators, must be zero or one +% % % % priors = sum(Xi) ./ sum(sum(Xi)); +% % % % +% % % % % P(act | task), sum(active and task) / sum(task) +% % % % % P(Y | Xi) +% % % % % nfeatures x nclasses +% % % % % assume act. is 0 or 1 +% % % % %fprintf('Calculating likelihood. '); +% % % % +% % % % % We add 1 to implement "add 1" or Laplace smoothing +% % % % % so that classes will never have estimates of exactly zero. +% % % % % This avoids the problem of sparseness w/a limited training set. +% % % % % and the line below is no longer needed +% % % % const = 1; % not sure if this is right. +% % % % % but adding any constant means that the less frequent classes will +% % % % % have higher estimates for pa_ if there are no activations; that means +% % % % % that a study that activates where no other studies did will tend to +% % % % % be classified as the less frequent task! not advantageous... +% % % % % p(A = yes | T = t) +% % % % %pa_given_t = ((Xi' * Y)' + 1) ./ (repmat(sum(Xi), nfeatures, 1) + const); +% % % % pa_given_t = ((Xi' * Y)') ./ (repmat(sum(Xi), nfeatures, 1)); +% % % % +% % % % % make sure no zeros, b/c we can't take log of 0; regularize +% % % % % This is important because it tells us how to treat the case when we +% % % % % have *no* activations of a particular class in a voxel +% % % % % small values here mean that we think it's *very* unlikely. +% % % % +% % % % % this is always < # obs for any class, but is equal across classes, so +% % % % % pa_ will be equal for all classes if there is no evidence. +% % % % pa_given_t(pa_given_t < eps) = 1 ./ nobs; +% % % % pa_given_t(pa_given_t == 1) = 1 - (1 ./ nobs) ; % we use this in test to get p(notA | T=t), so we need this line too +% % % % +% % % % % scaling factor to get actual probabilities p(task | act) +% % % % % this is the Evidence, the joint p(act1, act2, ... etc.) +% % % % % as long as there are no features with no activations, this will not +% % % % % blow up. +% % % % % even if no zeros in overall dataset, may be 0's in xvalidation +% % % % +% % % % % joint probability of act1, act2, act3 ... activation in voxel1, 2, 3, etc. +% % % % %fprintf('Calculating evidence. '); +% % % % pa = sum(Y) ./ nobs; +% % % % pa(pa < eps) = eps; +% % % % +% % % % log_evidence_act = log( pa ) ; +% % % % log_evidence_notact = log ( 1 - pa ); +% % % % true_class = indic2condf(Xi); +% % % % +% % % % bayes_model = struct('nobs', nobs, 'nfeatures', nfeatures, 'nclasses', nclasses, ... +% % % % 'whkeep', whkeep, 'whkeep_from_notempty', whkeep_from_notempty, ... +% % % % 'priors', priors, 'pa_given_t', pa_given_t, 'log_evidence_act', log_evidence_act, 'log_evidence_notact', log_evidence_notact, ... +% % % % 'Xi', Xi, 'true_class', true_class); + +%fprintf('Done. \n'); \ No newline at end of file diff --git a/Statistics_tools/bayes_get_probabilities_2010.m b/Statistics_tools/bayes_get_probabilities_2010.m new file mode 100644 index 00000000..77698946 --- /dev/null +++ b/Statistics_tools/bayes_get_probabilities_2010.m @@ -0,0 +1,151 @@ +function [priors_Pt, pa1_given_t, pa0_given_t, varargout] = bayes_get_probabilities_2010(Y, Xi, k) +% +% [priors_Pt, pa1_given_t, pa0_given_t, pt_given_act1, pt_given_act0, pa1_given_not_t] = +% bayes_get_probabilities_2010(Y, Xi, k) +% +% k is regularization param, biases P(activity | task class) towards 0.5 +% Y is data matrix, obs x features, 1/0 (active/not) +% Xi is task indicator matrix +% +% tor wager, oct 07 +% This is a sub-function of classify_naive_bayes.m +% For complete help, see classify_naive_bayes.m +% +% + +[nobs, nfeatures] = size(Y); +nclasses = size(Xi, 2); + +% priors_Pt and evidence: Marginal probability estimates +% ----------------------------------------------------- +sumx = sum(Xi); % number of obs in each class +N = sum(sumx); % total obs +repsum = sumx(ones(nfeatures, 1), :); + +% priors_Pt, p(t) for each of t tasks +% assume Xi are task indicators, must be zero or one +priors_Pt = sumx ./ N; + + +%priors_Pt = sum(Xi) ./ sum(Xi(:)); + + +% evidence, p(a) for each activation state S = {0, 1} + +% Note: Only return this in 'full' output mode, because classification can be +% (is) made on the likelihood * p(t), which is proportional to p(t | a) +% also, this way, we can accumulate likelihood based on the dist. of all +% the data, and apply the priors_Pt only once. + +% % % no need to save? +% % evidence_yes = sum(Y) ./ N; +% % evidence_no = 1 - evidence_yes; + +% Likelihoods and posteriors: conditional probabilities +% ----------------------------------------------------- + +% likelihoods: p(activity | task) +xcount = Y' * Xi; % activation count for each task +xnotcount = repsum - xcount; + +% k is regularization param, biases towards 0.5 (2 classes) or 1/# classes +pa1_given_t = (xcount + k) ./ (nclasses*k + repsum); %(repmat(sumx, nfeatures, 1)); + +pa0_given_t = (xnotcount + k) ./ (nclasses*k + repsum); %(repmat(sumx, nfeatures, 1)); + + +% % % Likelihood ratio +% % evidence_yes = (sum(Y) ./ nobs)'; +% % evidence_yes = evidence_yes(:, ones(1, nclasses)); +% % % add .1 to regularize... +% % lr = ( (pa1_given_t + .1) ./ (evidence_yes + .1) ) - 1; + +%%% need to apply priors_Pt only once!! Not to each voxel. Don't need to +%%% return full posterior for each voxel. +% posteriors: p(task | activity) +if nargout > 3 + + notY = ~Y; + sumv = sum(Y); + sumnotv = sum(notY); + + pt_given_act1 = xcount ./ (sumv(ones(2, 1), :)'); + pt_given_act0 = (notY' * Xi) ./ (sumnotv(ones(2, 1), :)'); + + pt_given_act1(pt_given_act1 == 0) = .001; + pt_given_act1(pt_given_act1 == 1) = .999; + + pt_given_act0(pt_given_act0 == 0) = .001; + pt_given_act0(pt_given_act0 == 1) = .999; + + varargout{1} = pt_given_act1; + varargout{2} = pt_given_act0; +end + + +if nargout > 5 + pa1_given_not_t = xnotcount ./ (N - repsum); + + pa1_given_not_t(pa1_given_not_t == 0) = .001; + pa1_given_not_t(pa1_given_not_t == 1) = .999; + + varargout{3} = pa1_given_not_t; +end + + + + +% % % % % priors_Pt, p(t) for each of t tasks +% % % % % assume Xi are task indicators, must be zero or one +% % % % priors_Pt = sum(Xi) ./ sum(sum(Xi)); +% % % % +% % % % % P(act | task), sum(active and task) / sum(task) +% % % % % P(Y | Xi) +% % % % % nfeatures x nclasses +% % % % % assume act. is 0 or 1 +% % % % %fprintf('Calculating likelihood. '); +% % % % +% % % % % We add 1 to implement "add 1" or Laplace smoothing +% % % % % so that classes will never have estimates of exactly zero. +% % % % % This avoids the problem of sparseness w/a limited training set. +% % % % % and the line below is no longer needed +% % % % const = 1; % not sure if this is right. +% % % % % but adding any constant means that the less frequent classes will +% % % % % have higher estimates for pa_ if there are no activations; that means +% % % % % that a study that activates where no other studies did will tend to +% % % % % be classified as the less frequent task! not advantageous... +% % % % % p(A = yes | T = t) +% % % % %pa_given_t = ((Xi' * Y)' + 1) ./ (repmat(sum(Xi), nfeatures, 1) + const); +% % % % pa_given_t = ((Xi' * Y)') ./ (repmat(sum(Xi), nfeatures, 1)); +% % % % +% % % % % make sure no zeros, b/c we can't take log of 0; regularize +% % % % % This is important because it tells us how to treat the case when we +% % % % % have *no* activations of a particular class in a voxel +% % % % % small values here mean that we think it's *very* unlikely. +% % % % +% % % % % this is always < # obs for any class, but is equal across classes, so +% % % % % pa_ will be equal for all classes if there is no evidence. +% % % % pa_given_t(pa_given_t < eps) = 1 ./ nobs; +% % % % pa_given_t(pa_given_t == 1) = 1 - (1 ./ nobs) ; % we use this in test to get p(notA | T=t), so we need this line too +% % % % +% % % % % scaling factor to get actual probabilities p(task | act) +% % % % % this is the Evidence, the joint p(act1, act2, ... etc.) +% % % % % as long as there are no features with no activations, this will not +% % % % % blow up. +% % % % % even if no zeros in overall dataset, may be 0's in xvalidation +% % % % +% % % % % joint probability of act1, act2, act3 ... activation in voxel1, 2, 3, etc. +% % % % %fprintf('Calculating evidence. '); +% % % % pa = sum(Y) ./ nobs; +% % % % pa(pa < eps) = eps; +% % % % +% % % % log_evidence_act = log( pa ) ; +% % % % log_evidence_notact = log ( 1 - pa ); +% % % % true_class = indic2condf(Xi); +% % % % +% % % % bayes_model = struct('nobs', nobs, 'nfeatures', nfeatures, 'nclasses', nclasses, ... +% % % % 'whkeep', whkeep, 'whkeep_from_notempty', whkeep_from_notempty, ... +% % % % 'priors_Pt', priors_Pt, 'pa_given_t', pa_given_t, 'log_evidence_act', log_evidence_act, 'log_evidence_notact', log_evidence_notact, ... +% % % % 'Xi', Xi, 'true_class', true_class); + +%fprintf('Done. \n'); \ No newline at end of file diff --git a/Statistics_tools/bayes_meta_feature_abstract.m b/Statistics_tools/bayes_meta_feature_abstract.m new file mode 100644 index 00000000..aada7461 --- /dev/null +++ b/Statistics_tools/bayes_meta_feature_abstract.m @@ -0,0 +1,74 @@ +function [reducedY, cl, cluster_indx] = bayes_meta_feature_abstract(Y, thresh, shrink, bayes_model, volInfo, doplot) +% +% Purpose: Instead of original matrix of activations in studies x voxels, +% we may want to work with a data matrix of activations in contiguous +% regions x voxels +% This function calculates Y = 0,1 for Y = 1 in any voxel in each +% contiguous cluster +% +% reducedY = bayes_meta_feature_abstract(Y, .1, bayes_model, volInfo); +% xval = classify_naive_bayes('xval', reducedY, Xi, 0, 0, bestk, bestg ); +% xval.prop_correct_by_class +% +% needs bayes_model.pa1_given_t, nclasses, params.k + +fprintf(1, 'Getting likelihood. '); + +nfeatures_orig = length(bayes_model.whkeep); + +pa1_given_t = zeros(nfeatures_orig, bayes_model.nclasses); +pa1_given_t(bayes_model.whkeep,:) = bayes_model.pa1_given_t; + +evidence_yes = (sum(Y(:, bayes_model.whkeep_from_notempty)) ./ bayes_model.nobs)'; +evidence_yes = evidence_yes(:, ones(1, bayes_model.nclasses)); +tmp = zeros(nfeatures_orig, bayes_model.nclasses); +tmp(bayes_model.whkeep,:) = evidence_yes; +evidence_yes = tmp; + +lr = ( (pa1_given_t + shrink) ./ (evidence_yes + shrink) ) - 1; + + +% % pt = zeros(nfeatures_orig, bayes_model.nclasses); +% % pt(bayes_model.whkeep,:) = bayes_model.pt_given_act1; + + +lr(~bayes_model.whkeep, :) = 0; + +if doplot, create_figure('Hist', 1, 2); hist(lr); plot_vertical_line(thresh); plot_vertical_line(-thresh); end + +lr(lr < thresh & lr > -thresh) = 0; + +if doplot, subplot(1, 2, 2); + tmp = lr(:); + tmp(tmp == 0) = []; + hist(tmp, 100); plot_vertical_line(thresh); plot_vertical_line(-thresh); + sum(lr > 0) + sum(lr < 0) +end + + +% ------------------------------------------------------- +fprintf(1, 'Getting clusters. '); + +% get all clusters +% % % cl = iimg_indx2clusters(lr(:, 1), volInfo); +% % % +% % % for i = 2:size(lr, 2) +% % % cl = [cl iimg_indx2clusters(lr(:, i), volInfo)]; +% % % end +% % % +% % % % reduce to remove overlap +% % % clu = clusters2CLU(cl); +% % % cl = tor_extract_rois([], clu, clu); +% this code does the same as the code above: +[cl, cluster_indx] = iimg_indx2clusters(any(lr')', volInfo); + +fprintf(1, '%3.0f regions. ', length(cl)) +% +%cl = classify_naive_bayes('plot', bayes_model, 'lr', thresh, {[1 .7 0] [0 0 1]}); + +fprintf(1, 'Getting data in clusters. \n'); + +reducedY = Meta_cluster_tools('getdata',cl, Y',volInfo); + +end \ No newline at end of file diff --git a/Statistics_tools/binotest.m b/Statistics_tools/binotest.m new file mode 100644 index 00000000..cfb7726c --- /dev/null +++ b/Statistics_tools/binotest.m @@ -0,0 +1,30 @@ +function RES = binotest(X, p) +% RES = binotest(X, p) +% +% Test the number of "hits" in each column of X against a null-hypothesis +% proportion p, using a binomial test. +% Assumes elements of each column of X are independent Bernoulli trials. +% +% X is a matrix of "hits" and "misses", coded as 1s and 0s. +% p is the null hypothesis proportion of "hits", e.g., often p = 0.5 +% +% Tor Wager, April 2011 + +X = double(X); % just in case + +% Binomial test +% "By chance, prob of getting this many or more hits are..." +n = size(X, 1) .* ones(1, size(X, 2)); +hits = sum(X); +prop = hits ./ n; + +%binop = 2 * (1 - binocdf(hits, n, .5)); % two-tailed P-values; wrong +%because does not count prob of THIS MANY or more; counts prob of more. + +binop = 2 * min(binocdf(hits, n, p), (1 - binocdf(hits - 1, n, p))); + +binose = sqrt(prop .* (1-prop) ./ n); % based on estimated proportions + +RES = struct('n', n, 'hits', hits, 'prop', prop, 'p_val', binop, 'SE', binose); + +end diff --git a/Statistics_tools/binotest_dependent.m b/Statistics_tools/binotest_dependent.m new file mode 100644 index 00000000..48020dde --- /dev/null +++ b/Statistics_tools/binotest_dependent.m @@ -0,0 +1,163 @@ +function [varargout] = binotest_dependent(X, Po) + +% [varargout] = binotest_dependent(X, Po) +% -------------------------------------------------------------------------% +% This function runs several different types of tests on dependent binomial data. +% Overall, it tests the number of "hits" for each subject (row in X) against a null-hypothesis +% proportion p, across all subjects using a Z-test (two-tailed). +% The second level null hypothesis should be approximated by a normal +% distribution with a mean of p. This approach assumes that each subject +% has an equal number of independent Bernoulli trials (columns in X) and +% that the number of subjects exceeds n=20 the test will be more accurate as n -> infinity. +% Also, calculates tests for each separate trial (e.g., subject columns), +% and the difference between proportions (two proportion z-test). +% +% ------------------------------------------------------------------------- +% Inputs: +% ------------------------------------------------------------------------- +% X: X is a matrix of "hits" and "misses", coded as 1s and 0s. +% where rows = subjects and columns = observations within +% subject +% +% Po: Po is the null hypothesis proportion of "hits", e.g., often p = 0.5 +% +% ----------------------------------------------------------------------- +% Outputs: +% ------------------------------------------------------------------------- + +% RES [1:5] a structure containing the output of the stats for the +% z-test, includes the number of subject (N), number of overall +% hits (hits), the overall proportion of hits (prop), the standard +% deviation (SE), z-statistic (Z), and the two tailed p-value (pval) +% trial across all subjects. Assumes independence +% +% RES1: Independent Single Interval Test for Column 1 (Column 1 only against Po) +% RES2: Independent Single Interval Test for Column 2 (Column 2 only against Po) +% RES3: Two proportion dependent difference z-test (Column 1 minus Column 2 against 0) +% RES4: Dependent single-interval test (Mean of Column 1 and Column 2 against Po) +% RES5: Two proportion dependent addition z-test (Column 1 plus Column 2 against 2 * Po) +% (Similar to mean, not sure what this will be used for) +% +% Examples: +% ------------------------------------------------------------------------- +% +% [RES1, RES2, RES3, RES4, RES5] = binotest_dependent([1,1,1,1,0; 1,0,1,0,1]',.5) +% +% ------------------------------------------------------------------------- +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Luke Chang & Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% ------------------------------------------------------------------------- + +% ------------------------------------------------------------------------- +% Process Data +% ------------------------------------------------------------------------- + +X = double(X); % just in case +[N, k] = size(X); % number of subjects x dependent obs per person +A = X(:, 1); +B = X(:, 2); + +% ------------------------------------------------------------------------- +% Test if Data is in Correct Format +% ------------------------------------------------------------------------- +if k > 2, error('NOT IMPLEMENTED FOR MORE THAN 2 DEP OBSERVATIONS PER UNIT'); end + +u = unique(X(:)); +if any(u ~= 1 & u ~= 0), error('X MUST HAVE VALUES OF 1 OR 0 ONLY.'); end + +if N < 20 + warning(['Running dependent binomial test requires many subjects to approximate normal distribution. You are only using ' num2str(length(unique(subject_id))) ' subjects. Interpret results with caution.']) +end + +% ------------------------------------------------------------------------- +% Calculate Variance and Covariance +% ------------------------------------------------------------------------- +% First, calculate variance of the quantity we want for a single trial, +% using Pa and Pb as estimates for the binomial parameters +% (This is not the SD of the SAMPLING distribution, but the SD of the +% distribution of interest (e.g., variance of sum, difference) +% Later, we will use this to get the SD for the SAMPLING distribution, +% which is used to construct confidence intervals and tests. + +%Calculate Probabilities of each Subject Trial +Pa = sum(A) ./ N; % P-hat for A +Pb = sum(B) ./ N; % P-hat for B + +% Calculate Variance for each Subject Trial +Va = Pa * (1 - Pa); % ./ N; +Vb = Pb * (1 - Pb); % ./ N; + +% Calculate Covariance between Trials +% C = ( (sum(A .* B) ./ N) * (sum(~A .* ~B) ./ N) ) - ( (sum(A .* ~B) ./ N) * (sum(B .* ~A) ./ N) ); % % Agresti, p. 410 SAME as below +C = ( (sum(A .* B) ./ N) - (Pa * Pb) ); % Covariance - expected prop under independence + +% Calculate Variance for Test Statistics adjusting for covariance +Vaplusb = Va + Vb - (2 * C); +Vameanb = ( Va + Vb - (2 * C) ) / 4; % / 4 because V(aX) = a^2Var(X) +Vaminusb = Va + Vb + (2 * C); + +% ------------------------------------------------------------------------- +% Define Functions +% ------------------------------------------------------------------------- + +sdfunction = @(V, N) (V ./ N) .^ .5; % Calculate Standard Deviation +zfunc = @(Px, Po, SD) (Px - Po) ./ SD; % Calculate Z-Value +pfunction_gauss = @(z) 2 .* min(normcdf(z, 0, 1), normcdf(z, 0, 1, 'upper')); %Calculate p-value for z-distribution + +% pfunction_bino = @(N, Po, hits) 2 * min(binocdf(hits, N, Po), (1 - binocdf(hits - 1, N, Po))); %Calculate p-value for binomial distribution + +% ------------------------------------------------------------------------- +% Define Various Confidence Intervals - TO DO - See Agresti Book +% ------------------------------------------------------------------------- + +% switch cimethod +% case 'Wald' +% +% cifunc = @(z) +% +% case 'scoring' % default +% +% otherwise +% error('Unknown CI method. Check inputs.'); +% end + +% Now, we will use this to get the SD for the SAMPLING distribution, +% which is used to construct confidence intervals and tests. +% e.g., SDa = (Va ./ N) .^ .5; + +% ------------------------------------------------------------------------- +% Calculate test statistics +% ------------------------------------------------------------------------- +% Use z-test (large-sample approximation; ok for N = 20+) + +names = {'P1' 'P2' 'P1minusP2' 'PavgP1P2' 'P1plusP2' }; +est = [Pa Pb Pa-Pb mean([Pa Pb]) Pa+Pb]; +Vs = [Va Vb Vaminusb Vameanb Vaplusb]; +Pnull = [Po Po 0 Po 2*Po]; + +for i = 1:length(names) + SDs(i) = sdfunction(Vs(i), N); + Z(i) = zfunc(est(i), Pnull(i), SDs(i)); + pval(i) = pfunction_gauss(Z(i)); + %ci(i) = add confidence intervals + + RES{i} = struct('n', N, 'hits', est(i).*N, 'prop', est(i), 'SE', SDs(i), 'Z', Z(i), 'p_val', pval(i)); + + varargout{i} = RES{i}; +end + +end % function diff --git a/Statistics_tools/cancor.m b/Statistics_tools/cancor.m new file mode 100755 index 00000000..bb11e8e2 --- /dev/null +++ b/Statistics_tools/cancor.m @@ -0,0 +1,131 @@ +function [cc,stats] = cancor(X,w,varargin); +% [cc,stats] = cancor(X,w,[permutations],[MCD robust outlier removal]) +% +% X data matrix, columns are variables, rows observations +% w first w columns are Set 1, rest are Set 2 +% +% cc canonical correlations +% UV canonical variates +% UVa caonical variates for set a +% UVb canonical variates for set b +% ccor correlations between canonical variates and X variables +% ab weights of canon. variates in row vectors, e.g., U = Xa' +% +% tor wager, 6 / 4 / 03 +% Johnson & Wichern, Applied Multivariate Statistical Analysis, 5th ed. +% tested on examples from the book. cc and ab are right, not positive about ccor +% +% Example: +% Compute canonical correlations between first 2 and last 2 columns +% of X, testing against permuted column data with 1000 iterations. +% +% [cc,stats] = cancor(X,2,1000,1); + +if length(varargin) > 0, perms = varargin{1};,else,perms = 0;,end + +% ---------------------------------------------------------- +% outliers +% ---------------------------------------------------------- + +if length(varargin) > 1 + [res]=fastmcd_noplot(X); + + % remove n most extreme outliers and recompute correlation + wh = res.flag==0; nout = sum(res.flag==0); + X(wh,:) = []; + stats.nout = nout; +end + +% ---------------------------------------------------------- +% canonical variates +% ---------------------------------------------------------- + +[cc,UV,ccor,ab,UVa,UVb] = docor(X,w); + +for i = 1:perms, % permute columns and test H0: no relation + + xtst=X; + for j = 1:size(X,2) + xtst(:,j) = getRandom(X(:,j)); + end + cctst(i,:) = docor(xtst,w); + +end + +stats.cc = cc; +stats.UV = UV; +stats.UVa = UVa; +stats.UVb = UVb; +stats.ab = ab; +stats.ccor = ccor; + +if perms + stats.cctst = cctst; + stats.perms = perms; + stats.thresh = prctile(cctst,95); + stats.sig = cc > stats.thresh; + stats.p = sum(cctst > repmat(cc,size(cctst,1),1)) ./ size(cctst,1); +end + +return + + + +function [cc,UV,ccor,ab,UVa,UVb] = docor(X,w) + +nomore = min(w,size(X,2)-w); + +c = corrcoef(X); + +c11 = c(1:w,1:w); +c12 = c(1:w,w+1:end); +c21 = c(w+1:end,1:w); +c22 = c(w+1:end,w+1:end); + +m1 = c11^-.5 * c12 * c22^-1 * c21 * c11^-.5; +%m2 = c22^-.5 * c21 * c11^-1 * c12 * c22^-.5; + +%[e,d1]=eig(m1); % this changes the order of the eigenvectors arbitrarily, very annoying +[e,d1]=pcacov(m1); + +e = e(:,1:nomore); +d1 = d1(1:nomore); +a = c11^-.5 * e; + +b = c22^-1 * c21 * a; % unscaled version of b +%tmp2 = tmp' * c22 * tmp % = d1 + +%cc = sqrt(diag(d1))'; % canonical correlations +cc = sqrt(d1)'; +cv=repmat(1./cc,size(b,1),1); +b = b .* cv; + +% wrong +%[f,d2]=eig(m2); +%b = c22^-.5 * f; + +if nargout > 1 + + UV = X * [a;b]; + + UVa = X(:,1:length(a)) * a; + UVb = X(:,length(a)+1:end) * b; + + % correlations of X with canonical + % wrong for some reason + V = diag(std(X)); + cor1 = a' * c11 * V(1:w,1:w); + cor2 = b' * c22 * V(w+1:end,w+1:end); + ccor = [cor1 cor2]; + + for i = 1:size(UV,2) + for j = 1:size(X,2) + tmp = corrcoef(UV(:,i),X(:,j)); + ccor(i,j) = tmp(1,2); + end + end + + ab = [a;b]'; +end + +return diff --git a/Statistics_tools/classify_bayes.m b/Statistics_tools/classify_bayes.m new file mode 100644 index 00000000..9acd675c --- /dev/null +++ b/Statistics_tools/classify_bayes.m @@ -0,0 +1,420 @@ +function [corrclass, taskclass, realclass, likeratio, m, misclass,ptask,indx,cl] = classify_bayes(meth,y,Xi,varargin) + % [corrclass, taskclass, realclass, likeratio, m, misclass,ptask,indx,cl] = classify_bayes(meth,y,Xi,[var args]) + % + % INPUTS: + % + % y, observations (e.g., studies) x variables (e.g., brain voxels) + % + % y = SOMResults.dat'; + %Xi = MC_Setup.Xi(:,1:2); + % + % meth can be: + % {'linear','diagLinear','quadratic','diagQuadratic','mahalanobis'} : + % discriminant analysis + % 'bayes' : simple Bayes posterior prob classifier + % + % tor wager, 10/2/06 + % + % Feature selection parameters: optional inputs + % % selectivity_cutoff: max probability of task given a + % response in a + % variable, divided by number of tasks. + % 1 = variable must exceed .5 for 2 tasks, .2 for 5 tasks, etc. + % 1.5 = .3 for 5 tasks, .75 for 2 tasks, etc. + % 0 = no selectivity + % + % activation_cutoff: max proportion of studies of some type that produced a + % response in a variable (e.g., voxel) + % .1 is default + % 0 is no selectivity + % + % Examples: + % [corrclass, taskclass, realclass, likeratio, m, misclass] = ... + % classify_bayes('bayes',MC_Setup.unweighted_study_data',Xi,'selectivity_cutoff',1); corrclass + % + % [corrclass, taskclass, realclass, likeratio, m, misclass] = + % classify_bayes('bayes',MC_Setup.unweighted_study_data',Xi,'selectivity_cutoff',1,'activation_cutoff',.08); corrclass +% +% Batch modes: +% Permutation test: input 'permtest' followed by number of permutations +% Do not request more than one output variable + % [corrclass_nullhyp] = classify_bayes('bayes',dat,Xi,'selectivity_cutoff',1.8,'activation_cutoff',.02,'permtest',5); + % + % To get mask index of which areas meet feature selection: + % whsave = sum(indx > 0, 2) > 0; + % + + % select features first, instead of in x-validation (makes x-val + % invalid; don't do it) + selectfirst = 0; + + dofeature = 1; + selectivity_cutoff = 1.5; + activation_cutoff = .1; + dopca = 0; + doxval = 1; + doplot = 1; + + for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % reserved keywords + case 'nofeature', dofeature = 0; + case 'reduction', dopca = 0; + case 'noxval', doxval = 0; + case 'noplot', doplot = 0; + case 'selectfirst', selectfirst = 1; + + % functional commands + case 'selectivity_cutoff', selectivity_cutoff = varargin{i+1}; + case 'activation_cutoff', activation_cutoff = varargin{i+1}; + + % batch modes + case 'permtest' % batch permutation test on shuffled task indicators + nperms = varargin{i+1}; + if ~exist('varargin','var'), varargin = {}; end + corrclass = batch_permtest(nperms,meth,y,Xi,varargin); + + return + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + origvars = size(y,2); + [nstudies,ntasks] = size(Xi); + realclass = indic2condf(Xi); + + % eliminate no-variance voxels + % ----------------------------------------------------------------- + [y,whsave] = eliminate_constant(y); + + nvars = size(y,2); + nalltasks = sum(sum(Xi)); + + fprintf(1,'\n') + str = ('Inital computation'); disp(str) + + % get max and min ptask, to avoid taking log of 1 or 0 + % ----------------------------------------------------------------- + % can't take log of 0, so shrink these extreme values + sy = sum(y); + ptask = (Xi' * y) ./ repmat(sy,ntasks,1); + mymax = max(ptask(ptask < 1)); + mymin = min(ptask(ptask > 0)); + + erase_string(str) + + % y is all data + % trainset = training set, studies x voxels (regions) + % testvec = test vector, 1 x voxels (regions) + + selectivity_cutoff = selectivity_cutoff./ntasks; + str = sprintf('Feature selection: selectivity (/ntasks): %3.2f, activation: %3.2f\n',selectivity_cutoff,activation_cutoff); + disp(str) + + if dofeature && selectfirst + [y,whsave] = select_features(ptask,Xi,y,selectivity_cutoff,activation_cutoff,whsave); + nvars = size(y,2); + end + + if dopca + % reduce data + % ----------------------------------------------------------------- + y = pca_reduction(y); + end + + maxlike = zeros(nstudies,1); + taskclass = zeros(nstudies,1); + likeratio = zeros(nstudies,1); + + if doxval + % ----------------------------------------------------------------- + fprintf(1,'Cross-validating: 00000'); + for i = 1:nstudies + + if mod(i,10) == 0, fprintf(1,'\b\b\b\b\b%05d',i); end + testvec = y(i,:)'; + trainset = y; + trainset(i,:) = []; + + X = Xi; + X(i,:) = []; + + sumtasks = sum(trainset); sumtasks(sumtasks==0) = 1; % avoid warning for empty tasks + ptask = (X' * trainset) ./ repmat(sumtasks,ntasks,1); + % feature selection + if dofeature && ~selectfirst + trainset = select_features(ptask,X,trainset,selectivity_cutoff,activation_cutoff,whsave); + end + + [taskclass(i),maxlike(i),likeratio(i)] = get_class(meth,X,trainset,testvec,ntasks,mymax,mymin,ptask); + end + + else + % ----------------------------------------------------------------- + % no x validation + ptask(ptask == 1) = mymax; + ptask(ptask == 0) = mymin; + + for i = 1:nstudies + testvec = y(i,:)'; + [taskclass(i),maxlike(i),likeratio(i)] = choose_most_likely(ptask,testvec); % taking the log inside here is not efficient + end + end %doxval + + + + % finish up: final features + sumtasks = sum(y); sumtasks(sumtasks==0) = 1; % avoid warning for empty tasks + ptask = (Xi' * y) ./ repmat(sumtasks,ntasks,1); + if dofeature + [y,whsave,nvars] = select_features(ptask,Xi,y,selectivity_cutoff,activation_cutoff,whsave); + end + + sumtasks = sum(y); sumtasks(sumtasks==0) = 1; % avoid warning for empty tasks + ptask = (Xi' * y) ./ repmat(sumtasks,ntasks,1); + + [m,dp,corr,far,misclass] = confusion_matrix(realclass,taskclass); + + wh = (realclass ~= 0); + n = sum(wh); + corrclass = 1 - sum(misclass) ./ n; + + % get class vectors for output, in original image length + [mx,r] = max(ptask,[],1); + + indx = zeros(origvars,ntasks); + for i = 1:ntasks + thistask = (r == i); % & (max(ptask) > ptaskcutoff); + indx(whsave(thistask),i) = ptask(i,thistask)'; + end + + cl = []; + if doplot + cl = classify_viz_regions(indx); + end + + return + + + % notes + + + %%%no prior_task = sum(Xi) ./ sum(Xi(:)); + %ptask = ptask .* repmat(prior_task',1,nvars); + + % this is exactly proportional to above but wrong. + %pa = repmat(sum(y) ./ nstudies,ntasks,1); + %ptask = (Xi' * y) ./ nalltasks ./ pa; + + + + % ==================================================================== + % -------------------------------------------------------------------- + % + % Support functions + % + % -------------------------------------------------------------------- + % ==================================================================== + +function [taskclass,maxlike,likeratio] = get_class(meth,X,trainset,testvec,ntasks,mymax,mymin,varargin) + switch meth + case 'bayes' + % proportion of activations that came from each task + % p task | activation, with flat priors for task + % posterior probability of task | activation in voxel (Region) + if length(varargin) > 0 + % do this for x-validation; we already computed ptask + ptask = varargin{1}; + else + sumtasks = sum(trainset); sumtasks(sumtasks==0) = 1; % avoid warning for empty tasks + ptask = (X' * trainset) ./ repmat(sumtasks,ntasks,1); + end + + %(xy / sumx) * (sumx./allsum) ./ (sumy./nstudies) + % add task priors + %prior_task = sum(X) ./ sum(X(:)); + %ptask = ptask .* repmat(prior_task',1,nvars); + + % gives posterior probability + % % pa = repmat(sum(trainset) ./ (nstudies-1),ntasks,1); + % % ptask = (Xi' * trainset) ./ (nalltasks-1) ./ pa(j); + + ptask(ptask == 1) = mymax; + ptask(ptask == 0) = mymin; + + % choose most likely task + [taskclass,maxlike,likeratio] = choose_most_likely(ptask,testvec); + +% % case 'naivebayes' +% % [taskclass log_joint best_log map] = classify_naive_bayes('test', Y, bayes_model); + + + case {'linear','diagLinear','quadratic','diagQuadratic','mahalanobis'} + condf = realclass; + condf(i) = []; + %[taskclass(i),err,ptask,logp] = classify(testvec',trainset,condf,meth); + [taskclass(i),err] = classify(testvec',trainset,condf,meth); + + otherwise error('Unknown method.') + end + + return + + +function [taskclass,maxlike,likeratio] = choose_most_likely(ptask,testvec) + + + ptask = log(ptask); % likelihoods; summing these = taking product of probabilities + + taskprob = ptask * testvec; + + % choose most likely task + [maxlike,taskclass] = max(taskprob); + if all(taskprob == 0) + likeratio = NaN; + else + likeratio = max(taskprob) ./ min(taskprob); + end + + return + + +function [y,wh] = eliminate_constant(y) + str = sprintf('Eliminating constant variables'); disp(str); + %whomit = find(all(y == repmat(mean(y),nstudies,1))); + tmp = sum(y); + whomsave = ~(tmp == 0); %| tmp == nstudies); + + y = y(:,whomsave); + wh = find(whomsave); + erase_string(str); + + return + + + +function [y,whsave,nvars] = select_features(ptask,Xi,y,selectivity_cutoff,activation_cutoff,whsave) + % select features + % ----------------------------------------------------------------- + + maxp = max(ptask); % max prob of task across vars + nvars = size(y,2); + pt = (Xi' * y) ./ repmat(sum(Xi)',1,nvars); % proportion of each task that activated in an area + ptmax = max(pt); + whomsave = maxp > (selectivity_cutoff) & ptmax > activation_cutoff; + + if sum(whomsave) == 0 + disp('Warning: No variables meet feature selection criteria.'); + whomsave = (maxp == max(maxp)); + end + + whsave = whsave(whomsave); % overall indices of saved voxels + y = y(:,whomsave); + ptask = ptask(:,whomsave); + nvars = size(y,2); + %fprintf(1,'Selected: %3.0f ',nvars); + % get weights on voxels based on n activations.... ****** + + return + + +function score = pca_reduction(x) + + str = sprintf('\nData reduction'); disp(str); + [pc,score,latent] = princomp(full(x),'econ'); + + % % if issparse(x) + % % [score,S,V] = svds(x,'econ'); + % % else + % % [score,S,V] = svd(x,'econ'); + % % end + % % latent = diag(S); + + % Eigenvalue plot + figure('Color','w'), set(gca,'FontSize',18),bar(latent), + xlabel('Components'),ylabel('Variance') + + erase_string(str); + num_to_save = sum(latent>1); + num_to_save = input(sprintf('Enter num. to save (%3.0f are > 1) : ',num_to_save)); + + score = score(:,1:num_to_save); + + return + + + + % ==================================================================== + % -------------------------------------------------------------------- + % + % Batch mode functions: permutation test + % + % -------------------------------------------------------------------- + % ==================================================================== + +function corrclass = batch_permtest(nperms,meth,y,Xi,varinputs) + + % build f call based on inputs + str = 'ccperm = classify_bayes(meth,y,Xiperm,''noplot'''; + if ~isempty(varinputs) + for i = 1:length(varinputs) + % remove call to permtest + toadd = varinputs{i}; + if ischar(toadd) && strcmp(toadd,'permtest') + varinputs{i} = []; varinputs{i+1} = []; + end + + % add input + toadd = varinputs{i}; + + if isempty(toadd) + % nothing to do. + elseif ischar(toadd) + toadd = ['''' toadd '''']; + else + toadd = num2str(toadd); + end + + if isempty(toadd) + % nothing to do. + else + str = [str ',' toadd]; + end + end + end + str = [str ');']; + + fprintf(1,'Permutation test on permuted Xi.\nPermutations: %3.0f\n',nperms); + fprintf(1,'Function call:\n%s\n',str); + + corrclass = zeros(1,nperms); + for i = 1:nperms + Xiperm = shuffles(Xi); + eval(str); + corrclass(i) = ccperm; + + if mod(i,10) == 0 + disp('Saving results so far in tmp_corrclass_permuted'); + save tmp_corrclass_permuted corrclass + end + end + + return + + + + % ==================================================================== + % -------------------------------------------------------------------- + % + % Other utility functions + % + % -------------------------------------------------------------------- + % ==================================================================== + +function erase_string(str) + fprintf(1,repmat('\b',1,length(str)+1)); % erase string + %fprintf(1,'\n'); + return diff --git a/Statistics_tools/classify_choose_most_likely.m b/Statistics_tools/classify_choose_most_likely.m new file mode 100644 index 00000000..6fc77a74 --- /dev/null +++ b/Statistics_tools/classify_choose_most_likely.m @@ -0,0 +1,42 @@ +function [taskclass,maxlike,likeratio, taskprob] = classify_choose_most_likely(ptask,testvec) +% +%[taskclass,maxlike,likeratio, taskprob] = classify_choose_most_likely(ptask,testvec) +% +% Given : +% ptask, likelihood of each task given activation, p(task | activation) +% classses x variables (features, brain voxels) +% +% testvec, activation values across features +% variables x 1 +% +% Return: +% taskclass: integer for which is max likelihood class +% maxlike : log likelihood of chosen class given data (if testvec is 1/0 +% indicator) +% likeratio : likelihood ratio for most likely vs. least likely class +% +% Example: +% whsave = sum(indx > 0, 2) > 0; % voxels in original image space that +% were in dataset +% +% [tc, ml, lr] = classify_choose_most_likely(ptask, MC_Setup.unweighted_study_data(whsave,1)) + + ptask = log(ptask); % likelihoods; summing these = taking product of probabilities + + taskprob = ptask * testvec; + + % choose most likely task + [maxlike,taskclass] = max(taskprob); + if all(taskprob == 0) + likeratio = NaN; + else + likeratio = max(taskprob) ./ min(taskprob); + end + + % get relative probabilities of being in each class; not sure about + % scale!! + taskprob = exp(taskprob ./ size(ptask, 2)); + taskprob = taskprob ./ sum(taskprob); + + return + diff --git a/Statistics_tools/classify_mds_plot.m b/Statistics_tools/classify_mds_plot.m new file mode 100644 index 00000000..10cd110d --- /dev/null +++ b/Statistics_tools/classify_mds_plot.m @@ -0,0 +1,68 @@ + +% needs output from classify_bayes +% indx = voxels (all voxels) x classes (task states) +% ptask = p(task) within reduced feature set + +nclasses = size(ptask, 1); +whsave = sum(indx > 0, 2) > 0; + +% Get MDS coordinates +% ----------------------------------------------- +r = corrcoef(ptask'); +D = 1 - r; + +[mds_dims, stress, disp] = shepardplot(D, 4); + + +% Get coordinates of ideal post. probabilities +% ----------------------------------------------- +prjn_matrix = pinv(mds_dims); + +ideal_coords = prjn_matrix * eye(nclasses); + + +%% Plot them +% ----------------------------------------------- +colors = {[1 0 0] [0 1 0] [1 0 1] [1 1 0] [0 0 1]}; +%plot(mds_dims(:,1), mds_dims(:,2), 'ko', 'MarkerFaceColor', [.5 .5 .5]); +nmdsfig(ideal_coords', 'classes', 1:nclasses, 'names', names, 'colors', colors, 'sizes', 16); + + +%% Set up to classify images + +meta_space_image = MC_Setup.V.fname; +% /Users/tor/Documents/matlab_code/3DheadUtility/canonical_brains/SPM99_sca +% lped_brains/scalped_avg152T1_graymatter_smoothed.img + +all_images = spm_get(Inf); +nimgs = size(all_images, 1); + + +%% Take a new data vector and plot it in the space +% ----------------------------------------------- +hold on; + +for i = 1:nimgs + image_to_classify = all_images(i, :); + + % /Users/tor/Documents/Tor_Documents/CurrentExperiments/Lab_Emotion/Resil2_Speech_Task/SPEECH_TASK_RESULTS/speech_seed_sh5_norob/avg_r_HR_0001.img + imgdata = scn_map_image(image_to_classify, meta_space_image); + imgvec = imgdata(MC_Setup.volInfo.wh_inmask); + + imgvec = imgvec(whsave); + + imgvec = scale(imgvec, 1); + imgvec(isnan(imgvec)) = 0; + + [predicted_class, ml, lr, taskprob] = classify_choose_most_likely(ptask, imgvec); + coords = prjn_matrix * taskprob; + plot3(coords(1), coords(2), coords(3), 'o', 'Color', colors{predicted_class}, 'MarkerFaceColor', colors{predicted_class}); + + fprintf(1, '%3.0f ', predicted_class); + fprintf(1, '%3.2f ', taskprob); + fprintf(1, '\n') + + drawnow + pause(.5) + +end \ No newline at end of file diff --git a/Statistics_tools/classify_naive_bayes.m b/Statistics_tools/classify_naive_bayes.m new file mode 100644 index 00000000..fa6b406d --- /dev/null +++ b/Statistics_tools/classify_naive_bayes.m @@ -0,0 +1,893 @@ +function varargout = classify_naive_bayes(meth, varargin) +% +% Naive Bayes classifier +% +% Tor Wager, Sept 2007 +% +% ========================================================= +% SETUP: set up model structure +% --------------------------------------------------------- +% bayes_model = classify_naive_bayes('setup', Y, Xi, [activation_cutoff, selectivity_cutoff]); +% Inputs: +% Y is full (not sparse); empty features will be eliminated +% Xi is obs x classes, an indicator matrix of 1's and 0's +% +% ========================================================= +% TEST: test classifier; make a prediction about classes from data +% --------------------------------------------------------- +% [class_est log_joint best_log map p_obs_act_given_class] = classify_naive_bayes('test', Y, bayes_model); +% +% +% ========================================================= +% EVAL: evaluate classification accuracy +% --------------------------------------------------------- +% [prop_correct, confusion_mtx, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes('eval', true_class, class_est, wh_obs); +% +% +% ========================================================= +% APPARENT: apparent classification; with full dataset +% --------------------------------------------------------- +% bayes_model = classify_naive_bayes('apparent', Y, bayes_model); +% +% +% ========================================================= +% XVAL: crossvalidate +% --------------------------------------------------------- +% xval = classify_naive_bayes('xval', Y, Xi); +% +% bayes_model = classify_naive_bayes('write', bayes_model, Y, volInfo, conditionnames) +% bayes_model = classify_naive_bayes('write', bayes_model, Y, MC_Setup.volInfo, MC_Setup.Xinms); +% +% To add feature abstraction step within xval: +% xval = classify_naive_bayes('xval', Y, Xi, 0, .9, .05, 1, volInfo ); +% +% ========================================================= +% PLOT +% --------------------------------------------------------- +% classify_naive_bayes('plot', bayes_model, ['pa|t', 'lr', 'lr surface', 'map plot', or 'class plot']); +% +% Optional inputs (any order): +% Threshold (abs. value), and colors in cell array +% classify_naive_bayes('plot', bayes_model, 'lr', .10, {[1 .7 0] [0 0 1]}); +% +% +% Example: +% ========================================================================== +% [bayes_model, Y] = classify_naive_bayes('setup', Y, Xi); +% +% % Test obs. 2 +% tic, [class_est log_joint best_log map] = classify_naive_bayes('test',Y(2,:), bayes_model); toc +% +% % Get apparent classification rate and look at confusion matrix +% tic, bayes_model = classify_naive_bayes('apparent', Y, bayes_model); toc +% bayes_model.apparent.confusion_mtx +% +% % Cross-validate +% bayes_model.xval = classify_naive_bayes('xval', Y, Xi); +% bayes_model.xval.confusion_mtx +% +% Example 2: +% --------------------------------------------------------- +% Select features, and do apparent and cross-validated classification +% Y = MC_Setup.unweighted_study_data'; +% wh = sum(Y) > 5; +% Y = Y(:, wh); +% whos Y +% [bayes_model, Y] = classify_naive_bayes('setup', Y, Xi); +% bayes_model = classify_naive_bayes('apparent', Y, bayes_model); +% bayes_model.apparent.prop_correct, bayes_model.apparent.confusion_mtx +% bayes_model.xval = classify_naive_bayes('xval', Y, Xi); +% bayes_model.xval.prop_correct, bayes_model.xval.confusion_mtx +% +% Example 3: +% create_figure('hist'); hist(bayes_model.pa1_given_t, 100); +% xval = classify_naive_bayes('xval', Y, Xi, .05, .3); +% +% Get results from key regions and run classifier only on those regions: +% --------------------------------------------------------- +% cl = classify_naive_bayes('plot', bayes_model, 'lr', .10, {[1 .7 0] [0 0 1]}); +% [studybyroi,studybyset] = Meta_cluster_tools('getdata',cl{1},dat',volInfo); +% bayes_model_regions = classify_naive_bayes('setup', studybyroi, Xi); +% bayes_model_regions = classify_naive_bayes('apparent', studybyroi,bayes_model_regions); +% disp('Apparent confusion') +% disp(bayes_model_regions.apparent.confusion_mtx) +% +% bayes_model_regions.xval = classify_naive_bayes('xval', studybyroi, Xi); +% disp('Cross-validated confusion') +% disp(bayes_model_regions.xval.confusion_mtx) +% +% fprintf('Proportion correct: Apparent: %3.0f%% Xval: %3.0f%%\n', 100*bayes_model_regions.apparent.prop_correct, 100*bayes_model_regions.xval.prop_correct); +% fprintf('Proportion correct by class: \t'); fprintf('%3.0f%%\t', 100*bayes_model_regions.xval.prop_correct_by_class); +% fprintf('\n'); + +% sz = cat(1,cl{1}(:).numVox); +% cl{1}(sz < 10) = []; +% +% subcl = subclusters_from_local_max(cl{1}, 10); + +switch meth + + + case 'setup' + Y = full(varargin{1}); + Xi = varargin{2}; + + activation_cutoff = []; + selectivity_cutoff = []; + return_full_ps = 1; % for faster processing if not all fields are needed (i.e., in xval) + + if length(varargin) > 2 + + activation_cutoff = varargin{3}; + selectivity_cutoff = varargin{4}; + + end + + if length(varargin) > 4 + return_full_ps = varargin{5}; + end + + k = 1; % default k regularization param + if length(varargin) > 5 + k = varargin{6}; + end + + + % setup + [Y, whkeep] = eliminate_empty_Y(Y); + + [bayes_model, Y] = classify_naive_bayes_setup(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep, return_full_ps, k); + + gam = 1; % default gam prior weighting param + if length(varargin) > 6 + gam = varargin{7}; + end + bayes_model.params.gam = gam; + + varargout{1} = bayes_model; + varargout{2} = Y; + + + case 'apparent' + + Y = full(varargin{1}); + bayes_model = varargin{2}; + + bayes_model.true_class = indic2condf(bayes_model.Xi); + + % test apparent classification rate + [bayes_model.apparent.class_est bayes_model.apparent.log_joint bayes_model.apparent.best_log bayes_model.apparent.map bayes_model.apparent.pact_given_task] = ... + classify_naive_bayes_test(Y, bayes_model); + + [bayes_model.apparent.prop_correct, bayes_model.apparent.confusion_mtx, bayes_model.apparent.misclass, ... + bayes_model.apparent.prop_correct_by_class, bayes_model.apparent.chance, bayes_model.apparent.chance_95_ci] = ... + classify_naive_bayes_eval(bayes_model.true_class, bayes_model.apparent.class_est); + + + varargout{1} = bayes_model; + + + + case 'test' + Y = full(varargin{1}); + bayes_model = varargin{2}; + + [class_est log_joint best_log map p_obs_act_given_class] = classify_naive_bayes_test(Y, bayes_model); + + varargout{1} = class_est; + varargout{2} = log_joint; + varargout{3} = best_log; + varargout{4} = map; + varargout{5} = p_obs_act_given_class; + + case 'eval' + bayes_model = varargin{1}; + class_est = varargin{2}; + wh_obs = []; + if length(varargin) > 2, wh_obs = varargin{3}; end + + [prop_correct, confusion_mtx, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes_eval(bayes_model, class_est, wh_obs); + + varargout{1} = prop_correct; + varargout{2} = confusion_mtx; + varargout{3} = misclass; + varargout{4} = prop_correct_by_class; + varargout{5} = chance; + varargout{6} = chance_95_ci; + + + + case 'abstract' + %bayes_model = classify_naive_bayes('abstract', Y, thresh, bayes_model, volInfo); + + Y = full(varargin{1}); + thresh = varargin{2}; + + bayes_model = varargin{3}; + volInfo = varargin{4}; + + reducedY = bayes_meta_feature_abstract(Y, thresh, bayes_model, volInfo); + + [bayes_model, Y] = classify_naive_bayes('setup', reducedY, bayes_model.Xi, bayes_model.params.activation_cutoff, bayes_model.params.selectivity_cutoff, 1, bayes_model.params.k); + + varargout{1} = bayes_model; + varargout{2} = Y; + + case 'xval' + Y = full(varargin{1}); + Xi = varargin{2}; + + activation_cutoff = []; + selectivity_cutoff = []; + if length(varargin) > 2 + + activation_cutoff = varargin{3}; + selectivity_cutoff = varargin{4}; + + end + + k = .5; + gam = 1; + if length(varargin) > 4 + + k = varargin{5}; + gam = varargin{6}; + + end + + if length(varargin) > 6 + volInfo = varargin{7}; + do_feature_abstract = 1; + end + + + % initialize + if do_feature_abstract, Yorig = Y; end + + [Y, whkeep] = eliminate_empty_Y(Y); + + Y = sparse(Y); + + nobs = size(Y, 1); + fprintf('\nCross-validating %3.0f observations: 00000', nobs); + + log_joint = zeros(nobs, size(Xi, 2)); + map = zeros(nobs, size(Xi, 2)); + class_est = zeros(nobs, 1); + best_log = zeros(nobs, 1); + + true_class = indic2condf(Xi); + + % Update volinfo for feature abstraction +% % volInfo.wh_inmask = double(whkeep'); +% % volInfo.n_inmask = sum(whkeep); +% % volInfo.image_indx(volInfo.image_indx) = whkeep'; +% % volInfo.xyzlist(~whkeep',:) = []; + + % run + for i = 1:nobs + + if mod(i,10) == 0, fprintf(1,'\b\b\b\b\b%05d',i); end + + include_in_training = true(nobs, 1); + include_in_training(i) = 0; + + % setup: does feature-selection as well, if requested; no + % need to return Y, because ... + bayes_model = classify_naive_bayes_setup(Y(include_in_training, :), Xi(include_in_training, :), activation_cutoff, selectivity_cutoff, whkeep, 0, k); + + bayes_model.params.k = k; + bayes_model.params.gam = gam; + + testdata = Y(i, bayes_model.whkeep_from_notempty); + + % feature abstraction step + if do_feature_abstract + [reducedY, cl, cluster_indx] = bayes_meta_feature_abstract(Yorig(include_in_training, :), .1, .1, bayes_model, volInfo, 0); + cluster_indx = cluster_indx(bayes_model.whkeep); + + bayes_model = classify_naive_bayes('setup', reducedY, Xi(include_in_training, :), 0, 0, gam, k); + + wh_cl = cluster_indx(testdata); + wh_cl = unique(wh_cl); wh_cl(wh_cl == 0) = []; + testdata = false(1, bayes_model.nfeatures); + testdata(wh_cl) = 1; + + end + + [class_est(i) log_joint(i,:) best_log(i) map(i,:)] = classify_naive_bayes_test(testdata, bayes_model); + end + + fprintf(1, '\n'); + + [prop_correct, confusion_mtx, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes_eval(true_class, class_est); + + varargout{1} = struct('class_est', class_est, 'log_joint', log_joint, 'best_log', best_log, 'map', map, ... + 'prop_correct', prop_correct, 'prop_correct_by_class', prop_correct_by_class, 'confusion_mtx', confusion_mtx, ... + 'misclass', misclass, 'chance', chance, 'chance_95_ci', chance_95_ci, 'params', bayes_model.params); + + + case {'write', 'images', 'write_images'} + bayes_model = varargin{1}; + Y = varargin{2}; + volInfo = varargin{3}; + conditionnames = varargin{4}; + + bayes_model = write_images(bayes_model, Y, volInfo, conditionnames); + varargout{1} = bayes_model; + + + case {'plot', 'output'} + bayes_model = varargin{1}; + disptype = varargin{2}; + + cl = display_output(bayes_model, disptype, varargin{3:end}); + + varargout{1} = cl; + + otherwise + error('Unknown method (invalid first argument).'); +end + + + + + + +end % end main function + + + + + + + + + + +function [bayes_model, Y] = classify_naive_bayes_setup(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep, return_full_ps, k) + +% Y is data matrix, nobs observations x nfeatures variables +% in a meta-analysis, e.g., this is studies x voxels +% +% Each row of Y is an observation; if Y has one row, we're classifying an +% observation (e.g., one brain map) +% +% Xi are indicators for tasks (classes), ntasks columns of nobs elements +% (i.e., an nobs x ntasks matrix) + +% eliminate features with no 'on' values (activations) + + +% k is regularization param, biases towards 0.5 +if nargin < 7, k = 1; end + +whkeep_from_notempty = whkeep; % indicator of features after feature-selection, in index of not-empties + +% feature selection, if requested +if ~isempty(selectivity_cutoff) && ~isempty(activation_cutoff) + + [Y, whkeep, whkeep_from_notempty] = select_features(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep); + +end + +[nobs, nfeatures] = size(Y); +nclasses = size(Xi, 2); + +if return_full_ps + [priors, pa1_given_t, pa0_given_t, pt_given_act1, pt_given_act0, pa1_given_not_t] = ... + bayes_get_probabilities(Y, Xi, k); + +else + % key results (not all) only; saves computation time in + % crossvalidation. + [priors, pa1_given_t, pa0_given_t] = bayes_get_probabilities(Y, Xi, k); + +end + +true_class = indic2condf(Xi); + + +bayes_model = struct('nobs', nobs, 'nfeatures', nfeatures, 'nclasses', nclasses, ... + 'whkeep', whkeep, 'whkeep_from_notempty', whkeep_from_notempty, ... + 'params', [], ... + 'priors', priors, 'pa1_given_t', pa1_given_t, 'pa0_given_t', pa0_given_t, ... + 'Xi', Xi, 'true_class', true_class); + +bayes_model.params.gam = 1; % gamma, weighting on prior vs. joint evidence +bayes_model.params.k = k; % k, regularization of pa|t, bias towards 0.5. +bayes_model.params.activation_cutoff = activation_cutoff; +bayes_model.params.selectivity_cutoff = selectivity_cutoff; + +if return_full_ps + bayes_model.pt_given_act1 = pt_given_act1; + bayes_model.pt_given_act0 = pt_given_act0; + bayes_model.pa1_given_not_t = pa1_given_not_t; + +end + + +end + + + + +function [class_est log_joint best_log map p_obs_act_given_class] = classify_naive_bayes_test(Y, bayes_model) + +% Estimate classes, log joint p, and MAP estimates for a test data set + +% P(task | act) +% The var. below is all that is needed to choose the best class. +% argmax log_joint +% do this for each observation + +[nobs, nfeatures] = size(Y); + +if nfeatures ~= bayes_model.nfeatures + % try eliminating zero voxels + Y = Y(:, bayes_model.whkeep); + [nobs, nfeatures] = size(Y); +end + +if nfeatures ~= bayes_model.nfeatures + error('Number of features (i.e., voxels/regions) in Y must equal that in bayes_model structure or that of original training data.'); +end + +log_joint = zeros(nobs, bayes_model.nclasses); +map = zeros(nobs, bayes_model.nclasses); + +class_est = zeros(nobs, 1); +best_log = zeros(nobs, 1); + +gam = bayes_model.params.gam; % prior weighting factor; from 0 to nfeatures. + +logpriors = log(bayes_model.priors); +%logpriors = logpriors(ones(nfeatures, bayes_model.nclasses)); + +for i = 1:nobs + + wh_active = Y(i,:)' > 0; + + % sum of log prob. of activation at each active voxel, plus sum of + % log prob. of NOT activation at each inactive voxel (1 - p(activation)) + % Same as ln of product of all p(act state = observed state | + % class) for all features + + % prob. that activity state is the observed one in the test vector, + % given each class. + % p(Fi = fi | C = c) + % p(ACTi = acti | T = t) + + % % % % p_obs_act_given_class(wh_active, :) = bayes_model.pt_given_act1(wh_active, :); % p(active | t, obs active) + % % % % p_obs_act_given_class(~wh_active, :) = bayes_model.pt_given_act0(~wh_active, :); + + p_obs_act_given_class = zeros(nfeatures, bayes_model.nclasses); + p_obs_act_given_class(wh_active, :) = bayes_model.pa1_given_t(wh_active, :); % p(active | t, obs active) + p_obs_act_given_class(~wh_active, :) = bayes_model.pa0_given_t(~wh_active, :); % p(not active | t, obs not active) + + loglikelihood = sum(log(p_obs_act_given_class)); + log_joint(i,:) = gam * log(bayes_model.priors) + loglikelihood; % log of joint prob. p(T, Y), p(task, act1, act2, act3, etc.) + + %log_joint(i,:) = sum(log(p_obs_act_given_class)); % proportional to the posterior. + + [best_log(i) class_est(i)] = max(log_joint(i,:)); + + % Divide joint by evidence + % get scaled estimate of p(task | activation) + % These are Max. a posteriori estimates, and values may be > 1 because + % we're assuming independence when that assumption is not likely to be true + % * something still wrong with this + % % % % + % % % % % evidence + % % % % log_evidence = sum(bayes_model.log_evidence_act(wh_active)) + sum(bayes_model.log_evidence_notact(~wh_active)); + % % % % % because something is wrong, exp() gives Inf values a lot, so isn't + % very sensible. + % % % % map(i,:) = log_joint(i,:) - log_evidence; %exp(log_joint(i,:) - log_evidence); + + + map(i,:) = exp(log_joint(i,:) ./ (gam + nfeatures) ); %exp(log_joint(i, :)); % will be very small + %sum(log_joint) + nfeatures * logpriors; + + %map = map ./ sum(map); + + +end + + +end + + + + +function [prop_correct, m, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes_eval(true_class, class_est, wh_obs) + +if nargin < 3 || isempty(wh_obs) + % assume we have all observations. + wh_obs = 1:length(true_class); +end + +[m,dp,corr,far,misclass] = confusion_matrix(true_class(wh_obs),class_est); + +totaln = sum(m(:)); +prop_correct = trace(m) ./ totaln; + +nclasses = length(unique(true_class(true_class ~= 0))); + +prop_correct_by_class = diag(m) ./ sum(m,2); + + +chance = 1 ./ nclasses; +chance_95_ci = [chance - sqrt(chance * (1 - chance) ./ totaln) chance + sqrt(chance * (1 - chance) ./ totaln)]; + +end + + + +function [Y, whkeep] = eliminate_empty_Y(Y) + +fprintf('Eliminating any empty features. \n'); + +% using whkeep helps avoid out of memory errors. +whzero = (sum(Y) < eps); +whkeep = ~whzero; +Y = Y(:, whkeep); + +end + + + +function [Y, whkeep, whomsave] = select_features(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep) + +% select features +% ----------------------------------------------------------------- + +[nobs, nfeatures] = size(Y); +ntasks = size(Xi,2); + +sumtasks = sum(Y); sumtasks(sumtasks==0) = 1; % avoid warning for empty tasks + +% p(task | activation) estimates +ptask = (Xi' * Y) ./ repmat(sumtasks,ntasks,1); + +maxp = max(ptask); % max prob of task across vars + +pt = (Xi' * Y) ./ repmat(sum(Xi)',1,nfeatures); % proportion of each task that activated in an area +ptmax = max(pt); +whomsave = maxp > (selectivity_cutoff) & ptmax > activation_cutoff; + +if sum(whomsave) == 0 + disp('Warning: No variables meet feature selection criteria.'); + whomsave = (maxp == max(maxp)); +end + +whkeep(whkeep) = whomsave; % indices of saved voxels in whole-brain (original) index vector +%whkeep = whkeep(whomsave); % overall indices of saved voxels +Y = Y(:,whomsave); + +%fprintf(1,'Selected: %3.0f ',nvars); +% get weights on voxels based on n activations.... ****** + +end + + + + +function bayes_model = write_images(bayes_model, Y, volInfo, conditionnames) +% bayes_model = write_images(bayes_model, MC_Setup.volInfo, MC_Setup.Xinms) + +% Xi = bayes_model.Xi; + +%[priors, pa1_given_t, pa0_given_t, varargout] = bayes_get_probabilities(Y, Xi, bayes_model.params.k); + +nfeatures_orig = length(bayes_model.whkeep); + +pa1_given_t = NaN * zeros(nfeatures_orig, bayes_model.nclasses); +pa1_given_not_t = NaN * zeros(nfeatures_orig, bayes_model.nclasses); + +pt_given_act1 = NaN * zeros(nfeatures_orig, bayes_model.nclasses); +pt_given_act0 = NaN * zeros(nfeatures_orig, bayes_model.nclasses); + +pa1_given_t(bayes_model.whkeep,:) = bayes_model.pa1_given_t; +pa1_given_not_t(bayes_model.whkeep,:) = bayes_model.pa1_given_not_t; + +pt_given_act1(bayes_model.whkeep,:) = bayes_model.pt_given_act1; +pt_given_act0(bayes_model.whkeep,:) = bayes_model.pt_given_act0; + + +% now done in bayes_get_probabilities +% % pa1_given_not_t = (double(~(Xi)') * Y)' ./ repmat(sum(~Xi), size(Y, 2), 1); +% % pa1_given_not_t(pa1_given_not_t < eps) = 1 ./ bayes_model.nobs; +% % pa1_given_not_t(pa1_given_not_t == 1) = 1 - (1 ./ bayes_model.nobs); + +%%%%pa1_given_not_t(~bayes_model.whkeep, :) = NaN; + + +disp('Writing images to disk in the current directory.'); + +for i = 1:bayes_model.nclasses + pa1_given_tname{i} = ['pa|t_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pa1_given_t(:,i), volInfo, 'outname', pa1_given_tname{i}); + + disp(pa1_given_tname{i}) + + pa_given_nottname{i} = ['pa|nott_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pa1_given_not_t(:,i), volInfo, 'outname', pa_given_nottname{i}); + + disp(pa_given_nottname{i}) + + pt_given_aname{i} = ['pt|a_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pt_given_act1(:,i), volInfo, 'outname', pt_given_aname{i}); + + disp(pt_given_aname{i}) + + pt_given_notaname{i} = ['pt|nota_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pt_given_act0(:,i), volInfo, 'outname', pt_given_notaname{i}); + + disp(pt_given_notaname{i}) +end + + +% likelihood ratio at each voxel +% should penalize this in some way for low activations... +%pa1_given_t(pa1_given_t == 0) = 1; +%pa1_given_not_t(pa1_given_not_t == 0) = 1; + +% higher numbers mean more association between activation and class. +% lr for 2 classes can both be positive IF not all obs. fall within class 1 +% or class 2!!! +evidence_yes = (sum(Y) ./ bayes_model.nobs)'; +evidence_yes = evidence_yes(:, ones(1, bayes_model.nclasses)); + +% % priors = NaN * zeros(nfeatures_orig, bayes_model.nclasses); +% % priors(bayes_model.whkeep,:) = bayes_model.priors(ones(bayes_model.nfeatures, bayes_model.nclasses)); + +% add .1 to regularize...this could be k param +lr = ( (pa1_given_t + .1) ./ (evidence_yes + .1) ) - 1; +%pt_given_act1 ./ pt_given_act0; +lr(lr < .05 & lr > -.05) = NaN; +%lr = log(lr); + +for i = 1:bayes_model.nclasses + lrname{i} = ['likeratio_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(lr(:,i), volInfo, 'outname', lrname{i}); + + disp(lrname{i}) +end + +bayes_model.image_output.volInfo = volInfo; +bayes_model.image_output.pa1_given_tname = pa1_given_tname; +bayes_model.image_output.pa_given_nottname = pa_given_nottname; + +bayes_model.image_output.pt_given_aname = pt_given_aname; +bayes_model.image_output.pt_given_notaname = pt_given_notaname; + +bayes_model.image_output.likerationame = lrname; + +bayes_model.image_output.pa1_given_t = pa1_given_t; +bayes_model.image_output.pa1_given_not_t = pa1_given_not_t; +bayes_model.image_output.likeratio = lr; + +end + + + + +function cl = display_output(bayes_model, disptype, varargin) +% display_output(bayes_model, disptype, varargin) + +switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + + case 'SPM8' + % spm_defaults is a function + spm_defaults() + + otherwise + % unknown SPM + disp('Unknown version of SPM!'); + spm_defaults() +end + +cl = []; +colors1 = [1 .7 0]; +colors2 = [0 0 1]; + +for i = 1:length(varargin) + if iscell(varargin{i}) + colors1 = varargin{i}{1}; + colors2 = varargin{i}{2}; + elseif length(varargin{i}) == 1 + thresh = varargin{i}; + end +end + +switch disptype + + case {'pa|t', 'lr', 'pt|a'} + + overlay = which('spm2_single_subj_T1_scalped.img'); + spm_check_registration(repmat(overlay, bayes_model.nclasses, 1)); + + for i = 1:bayes_model.nclasses + + switch disptype + case 'pa|t' + cl{i} = mask2clusters(bayes_model.image_output.pa1_given_tname{i}); + fprintf('Displayed: p(a | t) for each task.\n'); + + spm_orthviews_name_axis(bayes_model.image_output.pa1_given_tname{i}, i); + + + case 'lr' + cl{i} = mask2clusters(bayes_model.image_output.likerationame{i}); + fprintf('Displayed: regions with positive likelihood ratio for each task.\n'); + + spm_orthviews_name_axis(bayes_model.image_output.likerationame{i}, i); + + case 'pt|a' + cl{i} = mask2clusters(bayes_model.image_output.pt_given_aname{i}); + fprintf('Displayed: p(t | a) for each task.\n'); + + spm_orthviews_name_axis(bayes_model.image_output.pt_given_aname{i}, i); + + end + + % threshold + if exist('thresh', 'var') + for j = 1:length(cl{i}) + wh_omit = abs( cat(2, cl{i}(j).Z) ) < thresh; + cl{i}(j).XYZ(:, wh_omit) = []; + cl{i}(j).XYZmm(:, wh_omit) = []; + cl{i}(j).Z(:, wh_omit) = []; + end + + clu = clusters2CLU(cl{i}); + cl{i} = tor_extract_rois([], clu, clu); + + end + + + cluster_orthviews(cl{i}, 'handle', i, 'add'); + + end + + switch disptype + case 'pa|t' + %spm_orthviews_change_colormap([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow + spm_orthviews_change_colormap([.5 0 0], [1 1 0], [1 .5 0]); %blue to yellow + + case 'lr' + spm_orthviews_change_colormap(colors2, colors1); %blue to white to yellow + + case 'pt|a' + spm_orthviews_change_colormap(colors2, colors1); + + end + + case 'lr surface' + + poscm = colormap_tor([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow + negcm = colormap_tor([0 0 1], [0 .3 1]); % light blue to dark blue (shouldn't be needed!) + + + for i = 1:bayes_model.nclasses + + cl{i} = mask2clusters(bayes_model.image_output.likerationame{i}); + + fprintf('Displayed: regions with positive likelihood ratio for each task.\n'); + + % Left medial and lateral surface + % ---------------------------------------------------------- + create_figure('Brain Surface'); scn_export_papersetup(800); + + cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'hires left'); + sh = cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'brainstem'); + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_left_medial'], 'png'); + + view(270,0); lightRestoreSingle; + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_left_lateral'], 'png'); + + % Right medial and lateral surface + % ---------------------------------------------------------- + create_figure('Brain Surface'); cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'hires right'); + sh = cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'brainstem'); + scn_export_papersetup(800); saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_right_lateral'], 'png'); + + view(270,0); lightRestoreSingle; + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_right_medial'], 'png'); + + % Limbic + % --------------------------------------------------------- + create_figure('Brain Surface'); cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'limbic'); + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_limbic'], 'png'); + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_limbic'], 'fig'); + end + + + case 'map plot' + + % MAP plot + + colors = {'b' 'r' 'k' 'g' 'm'}; + create_figure('MAP Plot across observations'); + for i = 1:bayes_model.nclasses + + trueclassi = find(bayes_model.Xi(:,i) == 1); + myclassmap = bayes_model.apparent.map(trueclassi, i); + + plot(bayes_model.apparent.map(:, i), 'color', colors{i}); + plot(trueclassi, bayes_model.apparent.map(find(bayes_model.Xi(:,i) == 1), i), 'o', 'Color', colors{i},'MarkerFaceColor',colors{i}); + + incorrect_obs = ~(myclassmap == max(bayes_model.apparent.map(trueclassi, :), [], 2)); + incorrect_obs_vec = zeros(bayes_model.nobs, 1); + incorrect_obs_vec(trueclassi(incorrect_obs)) = 1; + + plot(find(incorrect_obs_vec), myclassmap(incorrect_obs), 'kx', 'MarkerSize', 12, 'LineWidth', 2); + end + xlabel('Observations'); ylabel('log(MAP) (up to scaling constant)'); + + + + case 'class plot' + + pr = bayes_model.priors; + fr = [linspace(1, 0, 10)' linspace(0, 1, 10)']; % frequencies of guesses, from always A to always B + + xfr = linspace(1, 0, 10); + + %hr_byclass = fr .* repmat(pr, size(fr, 1), 1) + colors = {'b' 'r' 'k' 'g' 'm'}; + + hr = fr * pr'; + + create_figure('Summary Line Plot'); + %plot(xfr, hr_byclass); + plot(xfr, hr, 'k', 'LineWidth', 2); + + phighestfreq = sum(bayes_model.xval.class_est == wh) ./ length(bayes_model.xval.class_est); % p chosen for highest-frequency class + plot(phighestfreq, bayes_model.xval.prop_correct, 'ko', 'LineWidth', 2, 'MarkerFaceColor', [.5 .5 .5], 'MarkerSize', 12); + + xx = repmat(phighestfreq, 2, bayes_model.nclasses); + yy = [bayes_model.xval.prop_correct_by_class'; bayes_model.xval.prop_correct_by_class']; + + for i = 1:size(yy, 2) + plot(xx(:,i), yy(:,i), 'o', 'Color', colors{i}, 'LineWidth', 2, 'MarkerFaceColor', colors{i}, 'MarkerSize', 12); + end + + % Standard error of hit rate + % var(hits for each category) = p*(1-p)*N for that category + % where p is the prior p(correct) for that category, and N is the number of + % choices in that category given the frequency of sampling that category. + + se_hr = sqrt( hr .* (1 - hr) ./ bayes_model.nobs ); + ci95_hr = 1.96 * se_hr; + + plot(xfr, hr + ci95_hr, 'k--', 'LineWidth', 1); + plot(xfr, hr - ci95_hr, 'k--', 'LineWidth', 1); + fill_around_line(hr, ci95_hr, 'k', xfr); + + ylabel('Correct classification rate'); + xlabel('Prop. choices from highest class'); + + + % N = fr .* bayes_model.nobs; % for each of k categories, in a row + + % pq = repmat(pr .* (1 - pr), size(fr,1), 1); + % varsum = sum(pq .* N, 2) + % + % + % se_hr = sqrt( sum(repmat(pr .* (1 - pr), size(fr,1), 1) ./ (bayes_model.nobs .* fr), 2) ) + % se_hr(isinf(se_hr)) = 0; + + + otherwise + error('Unknown display option.') +end + +end diff --git a/Statistics_tools/classify_naive_bayes_2010.m b/Statistics_tools/classify_naive_bayes_2010.m new file mode 100644 index 00000000..39e5efab --- /dev/null +++ b/Statistics_tools/classify_naive_bayes_2010.m @@ -0,0 +1,900 @@ +function varargout = classify_naive_bayes(meth, varargin) +% +% Naive Bayes classifier +% +% Tor Wager, Sept 2007 +% +% ========================================================= +% SETUP: set up model structure +% --------------------------------------------------------- +% bayes_model = classify_naive_bayes('setup', Y, Xi, [activation_cutoff, selectivity_cutoff]); +% Inputs: +% Y is full (not sparse); empty features will be eliminated +% Xi is obs x classes, an indicator matrix of 1's and 0's +% +% ========================================================= +% TEST: test classifier; make a prediction about classes from data +% --------------------------------------------------------- +% [class_est log_joint best_log map p_obs_act_given_class] = classify_naive_bayes('test', Y, bayes_model); +% +% +% ========================================================= +% EVAL: evaluate classification accuracy +% --------------------------------------------------------- +% [prop_correct, confusion_mtx, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes('eval', true_class, class_est, wh_obs); +% +% +% ========================================================= +% APPARENT: apparent classification; with full dataset +% --------------------------------------------------------- +% bayes_model = classify_naive_bayes('apparent', Y, bayes_model); +% +% +% ========================================================= +% XVAL: crossvalidate +% --------------------------------------------------------- +% xval = classify_naive_bayes('xval', Y, Xi); +% +% bayes_model = classify_naive_bayes('write', bayes_model, Y, volInfo, conditionnames) +% bayes_model = classify_naive_bayes('write', bayes_model, Y, MC_Setup.volInfo, MC_Setup.Xinms); +% +% To add feature abstraction step within xval: +% xval = classify_naive_bayes('xval', Y, Xi, 0, .9, .05, 1, volInfo ); +% +% ========================================================= +% PLOT +% --------------------------------------------------------- +% classify_naive_bayes('plot', bayes_model, ['pa|t', 'lr', 'lr surface', 'map plot', or 'class plot']); +% +% Optional inputs (any order): +% Threshold (abs. value), and colors in cell array +% classify_naive_bayes('plot', bayes_model, 'lr', .10, {[1 .7 0] [0 0 1]}); +% +% +% Example: +% ========================================================================== +% [bayes_model, Y] = classify_naive_bayes('setup', Y, Xi); +% +% % Test obs. 2 +% tic, [class_est log_joint best_log map] = classify_naive_bayes('test',Y(2,:), bayes_model); toc +% +% % Get apparent classification rate and look at confusion matrix +% tic, bayes_model = classify_naive_bayes('apparent', Y, bayes_model); toc +% bayes_model.apparent.confusion_mtx +% +% % Cross-validate +% bayes_model.xval = classify_naive_bayes('xval', Y, Xi); +% bayes_model.xval.confusion_mtx +% +% Example 2: +% --------------------------------------------------------- +% Select features, and do apparent and cross-validated classification +% Y = MC_Setup.unweighted_study_data'; +% wh = sum(Y) > 5; +% Y = Y(:, wh); +% whos Y +% [bayes_model, Y] = classify_naive_bayes('setup', Y, Xi); +% bayes_model = classify_naive_bayes('apparent', Y, bayes_model); +% bayes_model.apparent.prop_correct, bayes_model.apparent.confusion_mtx +% bayes_model.xval = classify_naive_bayes('xval', Y, Xi); +% bayes_model.xval.prop_correct, bayes_model.xval.confusion_mtx +% +% Example 3: +% create_figure('hist'); hist(bayes_model.pa1_given_t, 100); +% xval = classify_naive_bayes('xval', Y, Xi, .05, .3); +% +% Get results from key regions and run classifier only on those regions: +% --------------------------------------------------------- +% cl = classify_naive_bayes('plot', bayes_model, 'lr', .10, {[1 .7 0] [0 0 1]}); +% [studybyroi,studybyset] = Meta_cluster_tools('getdata',cl{1},dat',volInfo); +% bayes_model_regions = classify_naive_bayes('setup', studybyroi, Xi); +% bayes_model_regions = classify_naive_bayes('apparent', studybyroi,bayes_model_regions); +% disp('Apparent confusion') +% disp(bayes_model_regions.apparent.confusion_mtx) +% +% bayes_model_regions.xval = classify_naive_bayes('xval', studybyroi, Xi); +% disp('Cross-validated confusion') +% disp(bayes_model_regions.xval.confusion_mtx) +% +% fprintf('Proportion correct: Apparent: %3.0f%% Xval: %3.0f%%\n', 100*bayes_model_regions.apparent.prop_correct, 100*bayes_model_regions.xval.prop_correct); +% fprintf('Proportion correct by class: \t'); fprintf('%3.0f%%\t', 100*bayes_model_regions.xval.prop_correct_by_class); +% fprintf('\n'); + +% sz = cat(1,cl{1}(:).numVox); +% cl{1}(sz < 10) = []; +% +% subcl = subclusters_from_local_max(cl{1}, 10); + +switch meth + + + case 'setup' + Y = full(varargin{1}); + Xi = varargin{2}; + + activation_cutoff = []; + selectivity_cutoff = []; + return_full_ps = 1; % for faster processing if not all fields are needed (i.e., in xval) + + if length(varargin) > 2 + + activation_cutoff = varargin{3}; + selectivity_cutoff = varargin{4}; + + end + + if length(varargin) > 4 + return_full_ps = varargin{5}; + end + + k = 1; % default k regularization param + if length(varargin) > 5 + k = varargin{6}; + end + + + % setup + [Y, whkeep] = eliminate_empty_Y(Y); + + [bayes_model, Y] = classify_naive_bayes_setup(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep, return_full_ps, k); + + gam = 1; % default gam prior weighting param + if length(varargin) > 6 + gam = varargin{7}; + end + bayes_model.params.gam = gam; + + varargout{1} = bayes_model; + varargout{2} = Y; + + + case 'apparent' + + Y = full(varargin{1}); + bayes_model = varargin{2}; + + bayes_model.true_class = indic2condf(bayes_model.Xi); + + % test apparent classification rate + [bayes_model.apparent.class_est bayes_model.apparent.log_joint bayes_model.apparent.best_log bayes_model.apparent.map bayes_model.apparent.pact_given_task] = ... + classify_naive_bayes_test(Y, bayes_model); + + [bayes_model.apparent.prop_correct, bayes_model.apparent.confusion_mtx, bayes_model.apparent.misclass, ... + bayes_model.apparent.prop_correct_by_class, bayes_model.apparent.chance, bayes_model.apparent.chance_95_ci] = ... + classify_naive_bayes_eval(bayes_model.true_class, bayes_model.apparent.class_est); + + + varargout{1} = bayes_model; + + + + case 'test' + Y = full(varargin{1}); + bayes_model = varargin{2}; + + [class_est log_joint best_log map p_obs_act_given_class] = classify_naive_bayes_test(Y, bayes_model); + + varargout{1} = class_est; + varargout{2} = log_joint; + varargout{3} = best_log; + varargout{4} = map; + varargout{5} = p_obs_act_given_class; + + case 'eval' + bayes_model = varargin{1}; + class_est = varargin{2}; + wh_obs = []; + if length(varargin) > 2, wh_obs = varargin{3}; end + + [prop_correct, confusion_mtx, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes_eval(bayes_model, class_est, wh_obs); + + varargout{1} = prop_correct; + varargout{2} = confusion_mtx; + varargout{3} = misclass; + varargout{4} = prop_correct_by_class; + varargout{5} = chance; + varargout{6} = chance_95_ci; + + + + case 'abstract' + %bayes_model = classify_naive_bayes('abstract', Y, thresh, bayes_model, volInfo); + + Y = full(varargin{1}); + thresh = varargin{2}; + + bayes_model = varargin{3}; + volInfo = varargin{4}; + + reducedY = bayes_meta_feature_abstract(Y, thresh, bayes_model, volInfo); + + [bayes_model, Y] = classify_naive_bayes('setup', reducedY, bayes_model.Xi, bayes_model.params.activation_cutoff, bayes_model.params.selectivity_cutoff, 1, bayes_model.params.k); + + varargout{1} = bayes_model; + varargout{2} = Y; + + case 'xval' + Y = full(varargin{1}); + Xi = varargin{2}; + + activation_cutoff = []; + selectivity_cutoff = []; + if length(varargin) > 2 + + activation_cutoff = varargin{3}; + selectivity_cutoff = varargin{4}; + + end + + k = .5; + gam = 1; + if length(varargin) > 4 + + k = varargin{5}; + gam = varargin{6}; + + end + + if length(varargin) > 6 + volInfo = varargin{7}; + do_feature_abstract = 1; + end + + + % initialize + if do_feature_abstract, Yorig = Y; end + + [Y, whkeep] = eliminate_empty_Y(Y); + + Y = sparse(Y); + + nobs = size(Y, 1); + fprintf('\nCross-validating %3.0f observations: 00000', nobs); + + log_joint = zeros(nobs, size(Xi, 2)); + map = zeros(nobs, size(Xi, 2)); + class_est = zeros(nobs, 1); + best_log = zeros(nobs, 1); + + true_class = indic2condf(Xi); + + % Update volinfo for feature abstraction +% % volInfo.wh_inmask = double(whkeep'); +% % volInfo.n_inmask = sum(whkeep); +% % volInfo.image_indx(volInfo.image_indx) = whkeep'; +% % volInfo.xyzlist(~whkeep',:) = []; + + % ---------------------------------------------------------------------- + % run Cross-validation + % ---------------------------------------------------------------------- + for i = 1:nobs + + if mod(i,10) == 0, fprintf(1,'\b\b\b\b\b%05d',i); end + + include_in_training = true(nobs, 1); + include_in_training(i) = 0; + + % setup: does feature-selection as well, if requested; no + % need to return Y, because ... + bayes_model = classify_naive_bayes_setup(Y(include_in_training, :), Xi(include_in_training, :), activation_cutoff, selectivity_cutoff, whkeep, 0, k); + + bayes_model.params.k = k; + bayes_model.params.gam = gam; + + testdata = Y(i, bayes_model.whkeep_from_notempty); + + % feature abstraction step + % Purpose: Instead of original matrix of activations in studies x voxels, + % we may want to work with a data matrix of activations in contiguous + % regions x voxels + % This function calculates Y = 0,1 for Y = 1 in any voxel in each + % contiguous cluster + if do_feature_abstract + [reducedY, dummy, cluster_indx] = bayes_meta_feature_abstract(Yorig(include_in_training, :), .1, .1, bayes_model, volInfo, 0); + cluster_indx = cluster_indx(bayes_model.whkeep); + + bayes_model = classify_naive_bayes('setup', reducedY, Xi(include_in_training, :), 0, 0, gam, k); + + wh_cl = cluster_indx(testdata); + wh_cl = unique(wh_cl); wh_cl(wh_cl == 0) = []; + testdata = false(1, bayes_model.nfeatures); + testdata(wh_cl) = 1; + + end + + [class_est(i) log_joint(i,:) best_log(i) map(i,:)] = classify_naive_bayes_test(testdata, bayes_model); + end + + fprintf(1, '\n'); + + [prop_correct, confusion_mtx, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes_eval(true_class, class_est); + + varargout{1} = struct('class_est', class_est, 'log_joint', log_joint, 'best_log', best_log, 'map', map, ... + 'prop_correct', prop_correct, 'prop_correct_by_class', prop_correct_by_class, 'confusion_mtx', confusion_mtx, ... + 'misclass', misclass, 'chance', chance, 'chance_95_ci', chance_95_ci, 'params', bayes_model.params); + + + case {'write', 'images', 'write_images'} + bayes_model = varargin{1}; + Y = varargin{2}; + volInfo = varargin{3}; + conditionnames = varargin{4}; + + bayes_model = write_images(bayes_model, Y, volInfo, conditionnames); + varargout{1} = bayes_model; + + + case {'plot', 'output'} + bayes_model = varargin{1}; + disptype = varargin{2}; + + cl = display_output(bayes_model, disptype, varargin{3:end}); + + varargout{1} = cl; + + otherwise + error('Unknown method (invalid first argument).'); +end + + + + + + +end % end main function + + + + + + + + + + +function [bayes_model, Y] = classify_naive_bayes_setup(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep, return_full_ps, k) + +% Y is data matrix, nobs observations x nfeatures variables +% in a meta-analysis, e.g., this is studies x voxels +% +% Each row of Y is an observation; if Y has one row, we're classifying an +% observation (e.g., one brain map) +% +% Xi are indicators for tasks (classes), ntasks columns of nobs elements +% (i.e., an nobs x ntasks matrix) + +% eliminate features with no 'on' values (activations) + + +% k is regularization param, biases towards 0.5 +if nargin < 7, k = 1; end + +whkeep_from_notempty = whkeep; % indicator of features after feature-selection, in index of not-empties + +% feature selection, if requested +if ~isempty(selectivity_cutoff) && ~isempty(activation_cutoff) + + [Y, whkeep, whkeep_from_notempty] = select_features(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep); + +end + +[nobs, nfeatures] = size(Y); +nclasses = size(Xi, 2); + +if return_full_ps + [priors, pa1_given_t, pa0_given_t, pt_given_act1, pt_given_act0, pa1_given_not_t] = ... + bayes_get_probabilities_2010(Y, Xi, k); + +else + % key results (not all) only; saves computation time in + % crossvalidation. + [priors, pa1_given_t, pa0_given_t] = bayes_get_probabilities_2010(Y, Xi, k); + +end + +true_class = indic2condf(Xi); + + +bayes_model = struct('nobs', nobs, 'nfeatures', nfeatures, 'nclasses', nclasses, ... + 'whkeep', whkeep, 'whkeep_from_notempty', whkeep_from_notempty, ... + 'params', [], ... + 'priors', priors, 'pa1_given_t', pa1_given_t, 'pa0_given_t', pa0_given_t, ... + 'Xi', Xi, 'true_class', true_class); + +bayes_model.params.gam = 1; % gamma, weighting on prior vs. joint evidence +bayes_model.params.k = k; % k, regularization of pa|t, bias towards 0.5. +bayes_model.params.activation_cutoff = activation_cutoff; +bayes_model.params.selectivity_cutoff = selectivity_cutoff; + +if return_full_ps + bayes_model.pt_given_act1 = pt_given_act1; + bayes_model.pt_given_act0 = pt_given_act0; + bayes_model.pa1_given_not_t = pa1_given_not_t; + +end + + +end + + + + +function [class_est log_joint best_log map p_obs_act_given_class] = classify_naive_bayes_test(Y, bayes_model) + +% Estimate classes, log joint p, and MAP estimates for a test data set + +% P(task | act) +% The var. below is all that is needed to choose the best class. +% argmax log_joint +% do this for each observation + +[nobs, nfeatures] = size(Y); + +if nfeatures ~= bayes_model.nfeatures + % try eliminating zero voxels + Y = Y(:, bayes_model.whkeep); + [nobs, nfeatures] = size(Y); +end + +if nfeatures ~= bayes_model.nfeatures + error('Number of features (i.e., voxels/regions) in Y must equal that in bayes_model structure or that of original training data.'); +end + +log_joint = zeros(nobs, bayes_model.nclasses); +map = zeros(nobs, bayes_model.nclasses); + +class_est = zeros(nobs, 1); +best_log = zeros(nobs, 1); + +gam = bayes_model.params.gam; % prior weighting factor; from 0 to nfeatures. + +logpriors = log(bayes_model.priors); +%logpriors = logpriors(ones(nfeatures, bayes_model.nclasses)); + +for i = 1:nobs + + wh_active = Y(i,:)' > 0; + + % sum of log prob. of activation at each active voxel, plus sum of + % log prob. of NOT activation at each inactive voxel (1 - p(activation)) + % Same as ln of product of all p(act state = observed state | + % class) for all features + + % prob. that activity state is the observed one in the test vector, + % given each class. + % p(Fi = fi | C = c) + % p(ACTi = acti | T = t) + + % % % % p_obs_act_given_class(wh_active, :) = bayes_model.pt_given_act1(wh_active, :); % p(active | t, obs active) + % % % % p_obs_act_given_class(~wh_active, :) = bayes_model.pt_given_act0(~wh_active, :); + + p_obs_act_given_class = zeros(nfeatures, bayes_model.nclasses); + p_obs_act_given_class(wh_active, :) = bayes_model.pa1_given_t(wh_active, :); % p(active | t, obs active) + p_obs_act_given_class(~wh_active, :) = bayes_model.pa0_given_t(~wh_active, :); % p(not active | t, obs not active) + + loglikelihood = sum(log(p_obs_act_given_class)); + log_joint(i,:) = gam * log(bayes_model.priors) + loglikelihood; % log of joint prob. p(T, Y), p(task, act1, act2, act3, etc.) + + %log_joint(i,:) = sum(log(p_obs_act_given_class)); % proportional to the posterior. + + [best_log(i) class_est(i)] = max(log_joint(i,:)); + + % Divide joint by evidence + % get scaled estimate of p(task | activation) + % These are Max. a posteriori estimates, and values may be > 1 because + % we're assuming independence when that assumption is not likely to be true + % * something still wrong with this + % % % % + % % % % % evidence + % % % % log_evidence = sum(bayes_model.log_evidence_act(wh_active)) + sum(bayes_model.log_evidence_notact(~wh_active)); + % % % % % because something is wrong, exp() gives Inf values a lot, so isn't + % very sensible. + % % % % map(i,:) = log_joint(i,:) - log_evidence; %exp(log_joint(i,:) - log_evidence); + + + map(i,:) = exp(log_joint(i,:) ./ (gam + nfeatures) ); %exp(log_joint(i, :)); % will be very small + %sum(log_joint) + nfeatures * logpriors; + + %map = map ./ sum(map); + + +end + + +end + + + + +function [prop_correct, m, misclass, prop_correct_by_class, chance, chance_95_ci] = classify_naive_bayes_eval(true_class, class_est, wh_obs) + +if nargin < 3 || isempty(wh_obs) + % assume we have all observations. + wh_obs = 1:length(true_class); +end + +[m,dp,corr,far,misclass] = confusion_matrix(true_class(wh_obs),class_est); + +totaln = sum(m(:)); +prop_correct = trace(m) ./ totaln; + +nclasses = length(unique(true_class(true_class ~= 0))); + +prop_correct_by_class = diag(m) ./ sum(m,2); + + +chance = 1 ./ nclasses; +chance_95_ci = [chance - sqrt(chance * (1 - chance) ./ totaln) chance + sqrt(chance * (1 - chance) ./ totaln)]; + +end + + + +function [Y, whkeep] = eliminate_empty_Y(Y) + +fprintf('Eliminating any empty features. \n'); + +% using whkeep helps avoid out of memory errors. +whzero = (sum(Y) < eps); +whkeep = ~whzero; +Y = Y(:, whkeep); + +end + + + +function [Y, whkeep, whomsave] = select_features(Y, Xi, activation_cutoff, selectivity_cutoff, whkeep) + +% select features +% ----------------------------------------------------------------- + +[nobs, nfeatures] = size(Y); +ntasks = size(Xi,2); + +sumtasks = sum(Y); sumtasks(sumtasks==0) = 1; % avoid warning for empty tasks + +% p(task | activation) estimates +ptask = (Xi' * Y) ./ repmat(sumtasks,ntasks,1); + +maxp = max(ptask); % max prob of task across vars + +pt = (Xi' * Y) ./ repmat(sum(Xi)',1,nfeatures); % proportion of each task that activated in an area +ptmax = max(pt); +whomsave = maxp > (selectivity_cutoff) & ptmax > activation_cutoff; + +if sum(whomsave) == 0 + disp('Warning: No variables meet feature selection criteria.'); + whomsave = (maxp == max(maxp)); +end + +whkeep(whkeep) = whomsave; % indices of saved voxels in whole-brain (original) index vector +%whkeep = whkeep(whomsave); % overall indices of saved voxels +Y = Y(:,whomsave); + +%fprintf(1,'Selected: %3.0f ',nvars); +% get weights on voxels based on n activations.... ****** + +end + + + + +function bayes_model = write_images(bayes_model, Y, volInfo, conditionnames) +% bayes_model = write_images(bayes_model, MC_Setup.volInfo, MC_Setup.Xinms) + +% Xi = bayes_model.Xi; + +%[priors, pa1_given_t, pa0_given_t, varargout] = bayes_get_probabilities_2010(Y, Xi, bayes_model.params.k); + +nfeatures_orig = length(bayes_model.whkeep); + +pa1_given_t = NaN * zeros(nfeatures_orig, bayes_model.nclasses); +pa1_given_not_t = NaN * zeros(nfeatures_orig, bayes_model.nclasses); + +pt_given_act1 = NaN * zeros(nfeatures_orig, bayes_model.nclasses); +pt_given_act0 = NaN * zeros(nfeatures_orig, bayes_model.nclasses); + +pa1_given_t(bayes_model.whkeep,:) = bayes_model.pa1_given_t; +pa1_given_not_t(bayes_model.whkeep,:) = bayes_model.pa1_given_not_t; + +pt_given_act1(bayes_model.whkeep,:) = bayes_model.pt_given_act1; +pt_given_act0(bayes_model.whkeep,:) = bayes_model.pt_given_act0; + + +% now done in bayes_get_probabilities_2010 +% % pa1_given_not_t = (double(~(Xi)') * Y)' ./ repmat(sum(~Xi), size(Y, 2), 1); +% % pa1_given_not_t(pa1_given_not_t < eps) = 1 ./ bayes_model.nobs; +% % pa1_given_not_t(pa1_given_not_t == 1) = 1 - (1 ./ bayes_model.nobs); + +%%%%pa1_given_not_t(~bayes_model.whkeep, :) = NaN; + + +disp('Writing images to disk in the current directory.'); + +for i = 1:bayes_model.nclasses + pa1_given_tname{i} = ['pa|t_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pa1_given_t(:,i), volInfo, 'outname', pa1_given_tname{i}); + + disp(pa1_given_tname{i}) + + pa_given_nottname{i} = ['pa|nott_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pa1_given_not_t(:,i), volInfo, 'outname', pa_given_nottname{i}); + + disp(pa_given_nottname{i}) + + pt_given_aname{i} = ['pt|a_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pt_given_act1(:,i), volInfo, 'outname', pt_given_aname{i}); + + disp(pt_given_aname{i}) + + pt_given_notaname{i} = ['pt|nota_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(pt_given_act0(:,i), volInfo, 'outname', pt_given_notaname{i}); + + disp(pt_given_notaname{i}) +end + + +% likelihood ratio at each voxel +% should penalize this in some way for low activations... +%pa1_given_t(pa1_given_t == 0) = 1; +%pa1_given_not_t(pa1_given_not_t == 0) = 1; + +% higher numbers mean more association between activation and class. +% lr for 2 classes can both be positive IF not all obs. fall within class 1 +% or class 2!!! +evidence_yes = (sum(Y) ./ bayes_model.nobs)'; +evidence_yes = evidence_yes(:, ones(1, bayes_model.nclasses)); + +% % priors = NaN * zeros(nfeatures_orig, bayes_model.nclasses); +% % priors(bayes_model.whkeep,:) = bayes_model.priors(ones(bayes_model.nfeatures, bayes_model.nclasses)); + +% add .1 to regularize...this could be k param +lr = ( (pa1_given_t + .1) ./ (evidence_yes + .1) ) - 1; +%pt_given_act1 ./ pt_given_act0; +lr(lr < .05 & lr > -.05) = NaN; +%lr = log(lr); + +for i = 1:bayes_model.nclasses + lrname{i} = ['likeratio_' conditionnames{i} '.img']; + iimg_reconstruct_3dvol(lr(:,i), volInfo, 'outname', lrname{i}); + + disp(lrname{i}) +end + +bayes_model.image_output.volInfo = volInfo; +bayes_model.image_output.pa1_given_tname = pa1_given_tname; +bayes_model.image_output.pa_given_nottname = pa_given_nottname; + +bayes_model.image_output.pt_given_aname = pt_given_aname; +bayes_model.image_output.pt_given_notaname = pt_given_notaname; + +bayes_model.image_output.likerationame = lrname; + +bayes_model.image_output.pa1_given_t = pa1_given_t; +bayes_model.image_output.pa1_given_not_t = pa1_given_not_t; +bayes_model.image_output.likeratio = lr; + +end + + + + +function cl = display_output(bayes_model, disptype, varargin) +% display_output(bayes_model, disptype, varargin) + +switch spm('Ver') + case 'SPM2' + % spm_defaults is a script + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + + case 'SPM5' + % spm_defaults is a function + spm_defaults() + + case 'SPM8' + % spm_defaults is a function + spm_defaults() + + otherwise + % unknown SPM + disp('Unknown version of SPM!'); + spm_defaults() +end + +cl = []; +colors1 = [1 .7 0]; +colors2 = [0 0 1]; + +for i = 1:length(varargin) + if iscell(varargin{i}) + colors1 = varargin{i}{1}; + colors2 = varargin{i}{2}; + elseif length(varargin{i}) == 1 + thresh = varargin{i}; + end +end + +switch disptype + + case {'pa|t', 'lr', 'pt|a'} + + overlay = which('spm2_single_subj_T1_scalped.img'); + spm_check_registration(repmat(overlay, bayes_model.nclasses, 1)); + + for i = 1:bayes_model.nclasses + + switch disptype + case 'pa|t' + cl{i} = mask2clusters(bayes_model.image_output.pa1_given_tname{i}); + fprintf('Displayed: p(a | t) for each task.\n'); + + spm_orthviews_name_axis(bayes_model.image_output.pa1_given_tname{i}, i); + + + case 'lr' + cl{i} = mask2clusters(bayes_model.image_output.likerationame{i}); + fprintf('Displayed: regions with positive likelihood ratio for each task.\n'); + + spm_orthviews_name_axis(bayes_model.image_output.likerationame{i}, i); + + case 'pt|a' + cl{i} = mask2clusters(bayes_model.image_output.pt_given_aname{i}); + fprintf('Displayed: p(t | a) for each task.\n'); + + spm_orthviews_name_axis(bayes_model.image_output.pt_given_aname{i}, i); + + end + + % threshold + if exist('thresh', 'var') + for j = 1:length(cl{i}) + wh_omit = abs( cat(2, cl{i}(j).Z) ) < thresh; + cl{i}(j).XYZ(:, wh_omit) = []; + cl{i}(j).XYZmm(:, wh_omit) = []; + cl{i}(j).Z(:, wh_omit) = []; + end + + clu = clusters2CLU(cl{i}); + cl{i} = tor_extract_rois([], clu, clu); + + end + + + cluster_orthviews(cl{i}, 'handle', i, 'add'); + + end + + switch disptype + case 'pa|t' + %spm_orthviews_change_colormap([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow + spm_orthviews_change_colormap([.5 0 0], [1 1 0], [1 .5 0]); %blue to yellow + + case 'lr' + spm_orthviews_change_colormap(colors2, colors1); %blue to white to yellow + + case 'pt|a' + spm_orthviews_change_colormap(colors2, colors1); + + end + + case 'lr surface' + + poscm = colormap_tor([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow + negcm = colormap_tor([0 0 1], [0 .3 1]); % light blue to dark blue (shouldn't be needed!) + + + for i = 1:bayes_model.nclasses + + cl{i} = mask2clusters(bayes_model.image_output.likerationame{i}); + + fprintf('Displayed: regions with positive likelihood ratio for each task.\n'); + + % Left medial and lateral surface + % ---------------------------------------------------------- + create_figure('Brain Surface'); scn_export_papersetup(800); + + cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'hires left'); + sh = cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'brainstem'); + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_left_medial'], 'png'); + + view(270,0); lightRestoreSingle; + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_left_lateral'], 'png'); + + % Right medial and lateral surface + % ---------------------------------------------------------- + create_figure('Brain Surface'); cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'hires right'); + sh = cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'brainstem'); + scn_export_papersetup(800); saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_right_lateral'], 'png'); + + view(270,0); lightRestoreSingle; + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_right_medial'], 'png'); + + % Limbic + % --------------------------------------------------------- + create_figure('Brain Surface'); cluster_surf(cl{i}, 4, 'heatmap', 'colormaps', poscm, negcm, 'limbic'); + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_limbic'], 'png'); + saveas(gcf, [bayes_model.image_output.likerationame{i}(1:end-4) '_limbic'], 'fig'); + end + + + case 'map plot' + + % MAP plot + + colors = {'b' 'r' 'k' 'g' 'm'}; + create_figure('MAP Plot across observations'); + for i = 1:bayes_model.nclasses + + trueclassi = find(bayes_model.Xi(:,i) == 1); + myclassmap = bayes_model.apparent.map(trueclassi, i); + + plot(bayes_model.apparent.map(:, i), 'color', colors{i}); + plot(trueclassi, bayes_model.apparent.map(find(bayes_model.Xi(:,i) == 1), i), 'o', 'Color', colors{i},'MarkerFaceColor',colors{i}); + + incorrect_obs = ~(myclassmap == max(bayes_model.apparent.map(trueclassi, :), [], 2)); + incorrect_obs_vec = zeros(bayes_model.nobs, 1); + incorrect_obs_vec(trueclassi(incorrect_obs)) = 1; + + plot(find(incorrect_obs_vec), myclassmap(incorrect_obs), 'kx', 'MarkerSize', 12, 'LineWidth', 2); + end + xlabel('Observations'); ylabel('log(MAP) (up to scaling constant)'); + + + + case 'class plot' + + pr = bayes_model.priors; + fr = [linspace(1, 0, 10)' linspace(0, 1, 10)']; % frequencies of guesses, from always A to always B + + xfr = linspace(1, 0, 10); + + %hr_byclass = fr .* repmat(pr, size(fr, 1), 1) + colors = {'b' 'r' 'k' 'g' 'm'}; + + hr = fr * pr'; + + create_figure('Summary Line Plot'); + %plot(xfr, hr_byclass); + plot(xfr, hr, 'k', 'LineWidth', 2); + + phighestfreq = sum(bayes_model.xval.class_est == wh) ./ length(bayes_model.xval.class_est); % p chosen for highest-frequency class + plot(phighestfreq, bayes_model.xval.prop_correct, 'ko', 'LineWidth', 2, 'MarkerFaceColor', [.5 .5 .5], 'MarkerSize', 12); + + xx = repmat(phighestfreq, 2, bayes_model.nclasses); + yy = [bayes_model.xval.prop_correct_by_class'; bayes_model.xval.prop_correct_by_class']; + + for i = 1:size(yy, 2) + plot(xx(:,i), yy(:,i), 'o', 'Color', colors{i}, 'LineWidth', 2, 'MarkerFaceColor', colors{i}, 'MarkerSize', 12); + end + + % Standard error of hit rate + % var(hits for each category) = p*(1-p)*N for that category + % where p is the prior p(correct) for that category, and N is the number of + % choices in that category given the frequency of sampling that category. + + se_hr = sqrt( hr .* (1 - hr) ./ bayes_model.nobs ); + ci95_hr = 1.96 * se_hr; + + plot(xfr, hr + ci95_hr, 'k--', 'LineWidth', 1); + plot(xfr, hr - ci95_hr, 'k--', 'LineWidth', 1); + fill_around_line(hr, ci95_hr, 'k', xfr); + + ylabel('Correct classification rate'); + xlabel('Prop. choices from highest class'); + + + % N = fr .* bayes_model.nobs; % for each of k categories, in a row + + % pq = repmat(pr .* (1 - pr), size(fr,1), 1); + % varsum = sum(pq .* N, 2) + % + % + % se_hr = sqrt( sum(repmat(pr .* (1 - pr), size(fr,1), 1) ./ (bayes_model.nobs .* fr), 2) ) + % se_hr(isinf(se_hr)) = 0; + + + otherwise + error('Unknown display option.') +end + +end diff --git a/Statistics_tools/classify_naive_bayes_objfun.m b/Statistics_tools/classify_naive_bayes_objfun.m new file mode 100644 index 00000000..220c5295 --- /dev/null +++ b/Statistics_tools/classify_naive_bayes_objfun.m @@ -0,0 +1,19 @@ +function goodness = classify_naive_bayes_objfun(dat, Xi, volInfo, a, s, g, k, t, h) +% +% goodness = classify_naive_bayes_objfun(dat, Xi, volInfo, 0, .9, 1, .05, .1, .1); +% +% +%objfun = @(t, h) classify_naive_bayes_objfun(dat, Xi, volInfo, 0, .9, 1, .05, t, h); +%goodness = objfun(.1, .1) + +bayes_model = classify_naive_bayes('setup', dat, Xi, a, s, g, k); + +doplot = 0; +reducedY = bayes_meta_feature_abstract(dat, t, h, bayes_model, volInfo, doplot); + +xval = classify_naive_bayes('xval', reducedY, Xi, 0, 0, g, k ); + +goodness = mean(xval.prop_correct_by_class); + +end + diff --git a/Statistics_tools/classify_viz_regions.m b/Statistics_tools/classify_viz_regions.m new file mode 100644 index 00000000..6328158b --- /dev/null +++ b/Statistics_tools/classify_viz_regions.m @@ -0,0 +1,77 @@ +function cl = classify_viz_regions(indx,colors,ptaskcutoff,sizecutoff,maskimage,names) +% cl = classify_viz_regions(indx,colors,ptaskcutoff,sizecutoff,maskimage,names) +% +% visualize output of classify_bayes.m, or comparable output +% indx should be voxels x images, with prob task (ptask) in non-zero +% elements +% +% tor wager +% +% examples: +% cl = classify_viz_regions(indx,[],.6,5); +% cl = classify_viz_regions(indx,[],.6,5,[],names); +% +cl = []; + + if nargin < 2 || isempty(colors), colors = {[1 0 0] [0 1 0] [1 0 1] [1 1 0] [0 0 1]}; end + if nargin < 3 || isempty(ptaskcutoff),ptaskcutoff = .4; end + if nargin < 4 || isempty(sizecutoff),sizecutoff = 3; end + if nargin < 5 || isempty(maskimage),maskimage = which('scalped_avg152T1_graymatter_smoothed.img'); end + if nargin < 6, names = []; end + + [nvox,ntasks] = size(indx); + + while length(colors) < ntasks + colors{end+1} = rand(1,3); + end + + volInfo = iimg_read_img(maskimage,2); + + if (nvox ~= volInfo.nvox) && (nvox ~= volInfo.n_inmask) + disp('Cannot plot because # vars does not match # voxels in mask.'); + return + end + + addstr = 'noadd'; + + % light colors, first threshold + indxlite = indx; + indxlite(indxlite >= ptaskcutoff) = 0; + + + for i = 1:ntasks + + cl{i} = iimg_indx2clusters(indxlite(:,i),volInfo); + + if ~isempty(cl{i}) + sz = cat(1,cl{i}.numVox); + sz = sz < sizecutoff; + cl{i}(sz) = []; + + mycolor = {(.7 .* colors{i})}; %^+ ([1 1 1].*.5)}; + cluster_orthviews(cl{i},mycolor,'solid',addstr); + addstr = 'add'; + end + end + + % solid colors, higher threshold + indx(indx < ptaskcutoff) = 0; + + for i = 1:ntasks + + cl{i} = iimg_indx2clusters(indx(:,i),volInfo,ptaskcutoff,sizecutoff); + + if ~isempty(cl{i}) + sz = cat(1,cl{i}.numVox); + sz = sz < sizecutoff; + cl{i}(sz) = []; + + cluster_orthviews(cl{i},colors(i),'solid',addstr); + addstr = 'add'; + end + + end + + if ~isempty(names), makelegend(names,colors,1); end + + return diff --git a/Statistics_tools/contrast_code.m b/Statistics_tools/contrast_code.m new file mode 100644 index 00000000..3bd45ac1 --- /dev/null +++ b/Statistics_tools/contrast_code.m @@ -0,0 +1,28 @@ +function [vec, outnames] = contrast_code(vec) +% [vec,outnames] = contrast_code(vec) +% +% Changes values to 1, -1, or 0 for contrast coding + +outnames = []; + +if iscell(vec) && ~isempty(vec) + [indic, names] = string2indicator(vec); + + for i = 2:length(names) + vec = indic(:, i - 1) - indic(:, i); + outnames{i - 1} = [names{i - 1} ' - ' names{i}]; + end + +elseif ~isempty(vec) + wh = find(vec > 0); + + vec(wh) = 1; + + wh = find(vec < 0); + + vec(wh) = -1; + +end + +return + diff --git a/Statistics_tools/correl_compare_dep.m b/Statistics_tools/correl_compare_dep.m new file mode 100644 index 00000000..0e43a765 --- /dev/null +++ b/Statistics_tools/correl_compare_dep.m @@ -0,0 +1,271 @@ +function out = correl_compare_dep(y1,y2,varargin) + % out = correl_compare_dep(y1,y2,['alpha',myalpha],['rank'],['table']) + % + % Compare dependent correlations between pairs of vectors in y1 and y2. + % Each of y1 and y2 would contain at least 2 columns, which would be + % correlated and saved in r1 and r2 matrices in output. + % Then, the r1 and r2 matrices are subtracted, and P-values are + % returned for the differences. + % - In the simplest case, y1 would contain vectors [a b] and y2 would + % contain vectors [a c]. tests are provided on the a-b vs. a-c + % difference in correlations. + % + % Repeats dep. correl. analysis for each pair of columns + % Returns results in correlation matrix form, where number of rows and + % cols. are the number of pairs [y1(:,i) y2(:,i)] + % + % myalpha is 2-tailed alpha value; p-values are 2-tailed + % FDR correction is at .01, 2-tailed + % + % Based on Steiger, 1980, tests for comparing dependent correlations. + % + % for i = 1:length(cl), y1(:,i) = cl.CONTRAST.data(:,2); y2(:,i) = cl.CONTRAST.data(:,1); end + % for i = 1:length(cl), y1(:,i) = cl(i).CONTRAST.data(:,2); y2(:,i) = cl(i).CONTRAST.data(:,1); end + % y1 is matrix of obs x data vectors for condition 1 + % y2 is matrix of obs x data vectors for condition 2 + % out = correl_compare_dep(y1,y2) + % + % figure('Color','w');nmdsfig(c.GroupSpace,c.ClusterSolution.classes, ... + % c.names,out.sig,1,{'Pos' 'Neg'}); + % nmdsfig_legend(c.ClusterSolution.X,c.r) + % + % Example: compare correlations on cluster averages + % c_compare = + % correl_compare_dep(y1avg,y2avg,'alpha',.05,'rank','table','names',c.APPLY_CLUSTER.names); + % + % out = correl_compare_dep([ypred pain],[ypred temp], 'alpha', .06, 'table', 'names', {'biomarker resp' 'pain or temp'}); + + + myalpha = .05; + dorankdata = 0; + dotable = 0; + names = []; + + for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % functional commands + case 'alpha', myalpha = varargin{i+1}; + case 'rank', dorankdata = 1; + case 'table', dotable = 1; + case 'names', names = varargin{i+1}; + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + [N,npairs] = size(y1); + + + [rows,cols,ncorr] = corrcoef_indices(npairs); + + if nargin < 4, dorankdata = 0; end + if dorankdata + str = sprintf('Ranking data: Nonparametric correlations'); fprintf(1,str); + + for i = 1:npairs + y1(:,i) = rankdata(y1(:,i)); + y2(:,i) = rankdata(y2(:,i)); + end + else + str = sprintf('Assuming continuous data (no ranks).'); fprintf(1,str); + end + + erase_string(str); + + i = 1; j = 2; k = 3; h = 4; + + str = sprintf('Computing differences among correlations %04d',0); fprintf(1,str); + + diffr = zeros(ncorr,1); + Zstar2 = zeros(ncorr,1); + rr = zeros(ncorr,2); + + for cc = 1:ncorr + + fprintf(1,'\b\b\b\b%04d',cc); + + % get full correlation matrix for the 4 variables involved + dat = [y1(:,[rows(cc) cols(cc)]) y2(:,[rows(cc) cols(cc)])]; + + % get differences between correlation z-values (estimate) + [diffr(cc),r,rr(cc,:),diffrz,z] = correlation_diffs(dat,i,j,k,h); + + s = covcorr(r,i,j,k,h); % covariance of corr coeffs + + Zstar2(cc) = diffrz * sqrt( (N-3) ./ (2-(2*s)) ); + + % bootstrap + %vals = bootstrp(5000,@correlation_diffs,dat,i,j,k,h); + %Zboot(cc) = mean(vals) ./ std(vals); + + %if Zstar2(cc) > 2, keyboard, end + end + + erase_string(str); + + + pvec = 2 * (1 - normcdf(abs(Zstar2))); + + r1 = reconstruct(rr(:,1),npairs,ncorr,rows,cols); + r2 = reconstruct(rr(:,2),npairs,ncorr,rows,cols); + + Z = reconstruct(Zstar2,npairs,ncorr,rows,cols); + p = reconstruct(pvec,npairs,ncorr,rows,cols); + + dat = [rr diffr Zstar2 pvec]; + dat = [rows cols dat]; + dat = dat(pvec <= myalpha,:); + + diffr = reconstruct(diffr,npairs,ncorr,rows,cols); + + sig = (p <= myalpha - eye(size(p))) .* sign(diffr); + + % FDR corrected + pthr = FDR(pvec,.05); + if isempty(pthr), pthr = 0; end + + sigfdr = (p <= pthr) .* sign(diffr); + + out = struct('alpha',myalpha,'r1',r1,'r2',r2,'diffr',diffr, ... + 'Z',Z,'p',p,'sig',sig,'pthr',pthr,'sigfdr',sigfdr,'sigstats',dat); + + % output table... + if dotable + if isempty(names) + disp(['No names entered; try ''names'' keyword to add them.']); + for i = 1:npairs, names{i} = ['R' num2str(i)]; end + end + + out.names = names; + + disp(['Uncorrected, p < ' num2str(myalpha)]) + maketable(out,'sig',names); + disp('') + + disp(['FDR corrected, p < ' num2str(myalpha)]) + maketable(out,'sigfdr',names); + disp('') + end + + + + return + + +function [diffr,r,rr,diffrz,z] = correlation_diffs(dat,i,j,k,h) + % get differences between correlation z-values + r = corrcoef(dat); + + rr = [r(i,j) r(k,h)]; % correls to be compared + diffr = -diff(rr); % negative sign means we get (1) - (2) + + if nargout > 3 + z = .5 .* log( (1+rr) ./ (1-rr) ); + diffrz = -diff(z); % diff btwn correl z values + end + + return + + + +function [rows,cols,ncorr] = corrcoef_indices(npairs) + % upper triangle only + tmp = triu(ones(npairs)); + tmp = tmp - eye(npairs); + [rows,cols] = find(tmp); + ncorr = length(rows); + return + + + + +function s = covcorr(R,i,j,k,h) + + % pool correl. coeff for more stable var est. + % as they are equal under Ho. Steiger, 1980, eq. 14 for Z* + pooledr = mean([R(i,j) R(k,h)]); + s = pearsonf(R,i,j,k,h) ./ ( corvar(pooledr) ); + + return + + +function cv = corvar(r) + % variance of a correlation coefficient + % simplified form (special case) of pearsonf + cv = (1 - r^2) ^ 2; + return + + + % % function cv = co(R,x,y) + % % % x and y are 2-vector indices of matrix R to compute covariance for + % % % cv is covariance + % % cv = pearsonf(R,x(1),x(2),y(1),y(2)); + % % return + + +function pf = pearsonf(R,i,j,k,h) + + % Pearson-Filon: covariance (or var) of element i,j with element k,h + % depending on correlation values + + % for variance of 1 correl, works as well, and reduces to: + % (1 - rr(1)^2)^2 + + pf = (1/2) .* R(i,j) .* R(k,h) .* ... + ( R(i,k).^2 + R(i,h).^2 + R(j,k).^2 + R(j,h).^2 ) + ... + R(i,k) .* R(j,h) + R(i,h) .* R(j,k) - ... + R(i,j) .* ( R(j,k) .* R(j,h) + R(i,k) .* R(i,h) ) - ... + R(k,h) .* ( R(j,k) .* R(i,k) + R(j,h) .* R(i,h) ); + + + return + + + + + % function pf = pearsonf2(r,j,k,h,m) + % + % pf = .5 * ( (r(j,h) - r(j,k)*r(k,h)) * (r(k,m) - r(k,h)*r(h,m)) + ... + % (r(j,m) - r(j,h)*r(h,m)) * (r(k,h) - r(k,j)*r(j,h)) + ... + % (r(j,h) - r(j,m)*r(m,h)) * (r(k,m) - r(k,j)*r(j,m)) + ... + % (r(j,m) - r(j,k)*r(k,m)) * (r(k,h) - r(k,m)*r(m,h)) ); + % + % return + +function valmat = reconstruct(vals,npairs,ncorr,rows,cols) + + valmat = zeros(npairs); + for i = 1:ncorr + valmat(rows(i),cols(i)) = vals(i); + end + valmat = valmat + valmat'; + + return + + + +function erase_string(str1) + fprintf(1,repmat('\b',1,length(str1))); % erase string + return + + +function maketable(c_compare,whfield,names) + + str = {'-' '+'}; + [rows,cols] = find(triu(c_compare.(whfield))); + if isempty(rows) + disp('No significant results.') + else + fprintf(1,'Name1\tName2\trow\tcol.\t+ or -\tr1\tr2\tZ\tp\n'); + for i = 1:length(rows) + fprintf(1,'%s\t%s\t%3.0f\t%3.0f\t%s\t%3.3f\t%3.3f\t%3.2f\t%3.4f\n', ... + names{rows(i)}, names{cols(i)},rows(i),cols(i), ... + str{1.5 + (.5.*c_compare.(whfield)(rows(i),cols(i)))}, ... % + or - sign + c_compare.r1(rows(i),cols(i)),c_compare.r2(rows(i),cols(i)), ... % correlations + c_compare.Z(rows(i),cols(i)),c_compare.p(rows(i),cols(i))); % Z and p + end + end + fprintf(1,'\n'); + return \ No newline at end of file diff --git a/Statistics_tools/correl_compare_dep_permtest.m b/Statistics_tools/correl_compare_dep_permtest.m new file mode 100644 index 00000000..a4458e9b --- /dev/null +++ b/Statistics_tools/correl_compare_dep_permtest.m @@ -0,0 +1,362 @@ +function out = correl_compare_dep_permtest(y1,y2,varargin) + % out = correl_compare_dep_permtest(y1,y2,['alpha',myalpha],['rank'],['table']) + % + % ---------------------------------------- + % PERMUTATION TEST for correl_compare_dep + % ---------------------------------------- + % Compare dependent correlations between pairs of vectors in y1 and y2 + % Repeats dep. correl. analysis for each pair of columns + % Returns results in correlation matrix form, where number of rows and + % cols. are the number of pairs [y1(:,i) y2(:,i)] + % + % myalpha is 2-tailed alpha value; p-values are 2-tailed + % FDR correction is at .01, 2-tailed + % + % Based on Steiger, 1980, tests for comparing dependent correlations. + % + % for i = 1:length(cl), y1(:,i) = cl.CONTRAST.data(:,2); y2(:,i) = cl.CONTRAST.data(:,1); end + % for i = 1:length(cl), y1(:,i) = cl(i).CONTRAST.data(:,2); y2(:,i) = cl(i).CONTRAST.data(:,1); end + % y1 is matrix of obs x data vectors for condition 1 + % y2 is matrix of obs x data vectors for condition 2 + % out = correl_compare_dep(y1,y2) + % + % figure('Color','w');nmdsfig(c.GroupSpace,c.ClusterSolution.classes, ... + % c.names,out.sig,1,{'Pos' 'Neg'}); + % nmdsfig_legend(c.ClusterSolution.X,c.r) + % + % Example: compare correlations on cluster averages + % c_compare = + % correl_compare_dep(y1avg,y2avg,'alpha',.05,'rank','table','names',c.APPLY_CLUSTER.names); + + + myalpha = .05; + dorankdata = 0; + dotable = 0; + names = []; + + for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % functional commands + case 'alpha', myalpha = varargin{i+1}; + case 'rank', dorankdata = 1; + case 'table', dotable = 1; + case 'names', names = varargin{i+1}; + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + [N,npairs] = size(y1); + + + [rows,cols,ncorr] = corrcoef_indices(npairs); + + if nargin < 4, dorankdata = 0; end + if dorankdata + str = sprintf('Ranking data: Nonparametric correlations'); fprintf(1,str); + + for i = 1:npairs + y1(:,i) = rankdata(y1(:,i)); + y2(:,i) = rankdata(y2(:,i)); + end + else + str = sprintf('Assuming continuous data (no ranks).'); fprintf(1,str); + end + + erase_string(str); + + i = 1; j = 2; k = 3; h = 4; + + % ---------------------------------------- + %%% Correct Permutation %%% + % ---------------------------------------- + str = sprintf('Computing differences among correlations %04d',0); fprintf(1,str); + + [diffr,Zstar2,rr,pvec] = compare_dep_subfcn(ncorr,y1,y2,rows,cols,i,j,k,h,N); + + erase_string(str); + + % ---------------------------------------- + %%% All output stuff %%% + % ---------------------------------------- + r1 = reconstruct(rr(:,1),npairs,ncorr,rows,cols); + r2 = reconstruct(rr(:,2),npairs,ncorr,rows,cols); + + Z = reconstruct(Zstar2,npairs,ncorr,rows,cols); + p = reconstruct(pvec,npairs,ncorr,rows,cols); + + dat = [rr diffr Zstar2 pvec]; + dat = [rows cols dat]; + dat = dat(pvec <= myalpha,:); + + diffr = reconstruct(diffr,npairs,ncorr,rows,cols); + + sig = (p <= myalpha - eye(size(p))) .* sign(diffr); + + % FDR corrected + pthr = FDR(pvec,.05); + if isempty(pthr), pthr = 0; end + + sigfdr = (p <= pthr) .* sign(diffr); + + out = struct('alpha',myalpha,'r1',r1,'r2',r2,'diffr',diffr, ... + 'Z',Z,'p',p,'sig',sig,'pthr',pthr,'sigfdr',sigfdr,'sigstats',dat); + + % output table... + if dotable + if isempty(names) + disp(['No names entered; try ''names'' keyword to add them.']); + for ii = 1:npairs, names{ii} = ['R' num2str(ii)]; end + end + + disp(['Uncorrected, p < ' num2str(myalpha)]) + maketable(out,'sig',names); + disp('') + + disp(['FDR corrected, p < ' num2str(myalpha)]) + maketable(out,'sigfdr',names); + disp('') + end + + % ---------------------------------------- + %%% Permutations %%% + % ---------------------------------------- + niter = 5000; + + [out.nalpha,out.n01,out.nalpha_posvsneg,out.n01_posvsneg] = do_perms; + out.crit_n_at_alpha = prctile(out.nalpha,95); + out.crit_n_at_01 = prctile(out.n01,95); + out.crit_sumsigned_at_alpha = prctile(out.nalpha_posvsneg,97.5); + out.crit_sumsigned_at_01 = prctile(out.n01_posvsneg,97.5); + + out.numsig_at_alpha = sum(squareform(out.p) <= out.alpha); + out.numsig_at_01 = sum(squareform(out.p) <= .01); + + out.sumsigned_at_alpha = sum( (squareform(out.p) <= out.alpha) .* sign(squareform(out.diffr)) ); + out.sumsigned_at_01 = sum( (squareform(out.p) <= .01) .* sign(squareform(out.diffr)) ); + + + if dotable + fprintf(1,'Alpha: %3.4f, Num. sig: %3.0f, Critical num sig: %3.0f\n',out.alpha,out.numsig_at_alpha,out.crit_n_at_alpha); + fprintf(1,'Alpha: %3.4f, Num. sig: %3.0f, Critical num sig: %3.0f\n',.01,out.numsig_at_01,out.crit_n_at_01); + + fprintf(1,'Alpha: %3.4f, Sum of signed sig.: %3.0f, Critical num 2-tailed: %3.0f\n',out.alpha,out.sumsigned_at_alpha, out.crit_sumsigned_at_alpha); + fprintf(1,'Alpha: %3.4f, Sum of signed sig.: %3.0f, Critical num 2-tailed: %3.0f\n',.01,out.sumsigned_at_01, out.crit_sumsigned_at_01); + + end + + + % nested function + % get number of significant effects under null hypothesis at alpha and at + % .01 + function [nalpha,n01,nalpha_posvsneg,n01_posvsneg] = do_perms + + nalpha = zeros(1,niter); + n01 = nalpha; + nalpha_posvsneg = nalpha; + n01_posvsneg = nalpha; + + fprintf(1,'Getting distribution for number of significant correlations.\n Running %3.0f permutations: %04d',niter,0); + + for ii = 1:niter + fprintf(1,'\b\b\b\b%04d',ii); + + % permute data + for jj = 2:npairs + wh = randperm(N); + y1(:,jj) = y1(wh,jj); + y2(:,jj) = y2(wh,jj); + end + + % test correlations + [diffr,Zstar2,rr,pvec] = compare_dep_subfcn(ncorr,y1,y2,rows,cols,i,j,k,h,N,0); + + nalpha(ii) = sum(pvec <= myalpha); + n01(ii) = sum(pvec <= .01); + + nalpha_posvsneg(ii) = sum( (pvec <= myalpha) .* sign(diffr) ); + n01_posvsneg(ii) = sum( (pvec <= .01) .* sign(diffr) ); + + end + fprintf(1,'\n'); + + end + + end %%% end main function + + + + + + + + function [diffr,Zstar2,rr,pvec] = compare_dep_subfcn(ncorr,y1,y2,rows,cols,i,j,k,h,N,dotext) + + diffr = zeros(ncorr,1); % correlation difference + Zstar2 = zeros(ncorr,1); % from Steiger, the Z-score + rr = zeros(ncorr,2); % the correl values for y1 and y2 + + if ~exist('dotext','var'), dotext = 1; end + + for cc = 1:ncorr + + if dotext, fprintf(1,'\b\b\b\b%04d',cc); end + + % get full correlation matrix for the 4 variables involved + dat = [y1(:,[rows(cc) cols(cc)]) y2(:,[rows(cc) cols(cc)])]; + + % get differences between correlation z-values (estimate) + [diffr(cc),r,rr(cc,:),diffrz,z] = correlation_diffs(dat,i,j,k,h); + + s = covcorr(r,i,j,k,h); % covariance of corr coeffs + + Zstar2(cc) = diffrz * sqrt( (N-3) ./ (2-(2*s)) ); + + % bootstrap + %vals = bootstrp(5000,@correlation_diffs,dat,i,j,k,h); + %Zboot(cc) = mean(vals) ./ std(vals); + + %if Zstar2(cc) > 2, keyboard, end + end + + pvec = 2 * (1 - normcdf(abs(Zstar2))); + + end + + + + +function [diffr,r,rr,diffrz,z] = correlation_diffs(dat,i,j,k,h) + % get differences between correlation z-values + r = corrcoef(dat); + + rr = [r(i,j) r(k,h)]; % correls to be compared + diffr = -diff(rr); % negative sign means we get (1) - (2) + + if nargout > 3 + z = .5 .* log( (1+rr) ./ (1-rr) ); + diffrz = -diff(z); % diff btwn correl z values + end + + end + + + +function [rows,cols,ncorr] = corrcoef_indices(npairs) + % upper triangle only + tmp = triu(ones(npairs)); + tmp = tmp - eye(npairs); + [rows,cols] = find(tmp); + ncorr = length(rows); + end + + + + +function s = covcorr(R,i,j,k,h) + + % pool correl. coeff for more stable var est. + % as they are equal under Ho. Steiger, 1980, eq. 14 for Z* + pooledr = mean([R(i,j) R(k,h)]); + s = pearsonf(R,i,j,k,h) ./ ( corvar(pooledr) ); + + end + + +function cv = corvar(r) + % variance of a correlation coefficient + % simplified form (special case) of pearsonf + cv = (1 - r^2) ^ 2; + end + + + % % function cv = co(R,x,y) + % % % x and y are 2-vector indices of matrix R to compute covariance for + % % % cv is covariance + % % cv = pearsonf(R,x(1),x(2),y(1),y(2)); + % % end + + +function pf = pearsonf(R,i,j,k,h) + + % Pearson-Filon: covariance (or var) of element i,j with element k,h + % depending on correlation values + + % for variance of 1 correl, works as well, and reduces to: + % (1 - rr(1)^2)^2 + + pf = (1/2) .* R(i,j) .* R(k,h) .* ... + ( R(i,k).^2 + R(i,h).^2 + R(j,k).^2 + R(j,h).^2 ) + ... + R(i,k) .* R(j,h) + R(i,h) .* R(j,k) - ... + R(i,j) .* ( R(j,k) .* R(j,h) + R(i,k) .* R(i,h) ) - ... + R(k,h) .* ( R(j,k) .* R(i,k) + R(j,h) .* R(i,h) ); + + + end + + + + + % function pf = pearsonf2(r,j,k,h,m) + % + % pf = .5 * ( (r(j,h) - r(j,k)*r(k,h)) * (r(k,m) - r(k,h)*r(h,m)) + ... + % (r(j,m) - r(j,h)*r(h,m)) * (r(k,h) - r(k,j)*r(j,h)) + ... + % (r(j,h) - r(j,m)*r(m,h)) * (r(k,m) - r(k,j)*r(j,m)) + ... + % (r(j,m) - r(j,k)*r(k,m)) * (r(k,h) - r(k,m)*r(m,h)) ); + % + % end + +function valmat = reconstruct(vals,npairs,ncorr,rows,cols) + + valmat = zeros(npairs); + for i = 1:ncorr + valmat(rows(i),cols(i)) = vals(i); + end + valmat = valmat + valmat'; + + end + + + +function erase_string(str1) + fprintf(1,repmat('\b',1,length(str1))); % erase string + end + + +% function maketable(c_compare,whfield,names) +% [rows,cols] = find(triu(c_compare.(whfield))); +% if isempty(rows) +% disp('No significant results.') +% else +% fprintf(1,'Name1\tName2\trow\tcol.\t+ or -\tZ\tp\n'); +% for i = 1:length(rows) +% fprintf(1,'%s\t%s\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.4f\n',names{rows(i)}, ... +% names{cols(i)},rows(i),cols(i),c_compare.(whfield)(rows(i),cols(i)),c_compare.Z(rows(i),cols(i)),c_compare.p(rows(i),cols(i))); +% end +% end +% fprintf(1,'\n'); +% end + + +function maketable(c_compare,whfield,names) + + str = {'-' '+'}; + [rows,cols] = find(triu(c_compare.(whfield))); + if isempty(rows) + disp('No significant results.') + else + fprintf(1,'Name1\tName2\trow\tcol.\t+ or -\tr1\tr2\tZ\tp\n'); + for i = 1:length(rows) + fprintf(1,'%s\t%s\t%3.0f\t%3.0f\t%s\t%3.3f\t%3.3f\t%3.2f\t%3.4f\n', ... + names{rows(i)}, names{cols(i)},rows(i),cols(i), ... + str{1.5 + (.5.*c_compare.(whfield)(rows(i),cols(i)))}, ... % + or - sign + c_compare.r1(rows(i),cols(i)),c_compare.r2(rows(i),cols(i)), ... % correlations + c_compare.Z(rows(i),cols(i)),c_compare.p(rows(i),cols(i))); % Z and p + end + end + fprintf(1,'\n'); +end + + \ No newline at end of file diff --git a/Statistics_tools/correl_compare_dep_search.m b/Statistics_tools/correl_compare_dep_search.m new file mode 100644 index 00000000..12dd41eb --- /dev/null +++ b/Statistics_tools/correl_compare_dep_search.m @@ -0,0 +1,360 @@ +function correl_compare_dep_search(seed1,seed2,y1,y2,varargin) + % correl_compare_dep_search(seed1,seed2,y1,y2,['alpha',myalpha],['rank'],['mask',maskimage]) + % + % Compare dependent correlations between seed1<->y1 and seed2<->y2 + % + % Repeats dep. correl. analysis for each pair of columns + % Returns results in correlation matrix form, where number of rows and + % cols. are the number of pairs [y1(:,i) y2(:,i)] + % + % myalpha is 2-tailed alpha value; p-values are 2-tailed + % FDR correction is at .01, 2-tailed + % + % Based on Steiger, 1980, tests for comparing dependent correlations. + % + % for i = 1:length(cl), y1(:,i) = cl.CONTRAST.data(:,2); y2(:,i) = cl.CONTRAST.data(:,1); end + % for i = 1:length(cl), y1(:,i) = cl(i).CONTRAST.data(:,2); y2(:,i) = cl(i).CONTRAST.data(:,1); end + % y1 is matrix of obs x data vectors for condition 1 + % y2 is matrix of obs x data vectors for condition 2 + % out = correl_compare_dep(y1,y2) + % + % figure('Color','w');nmdsfig(c.GroupSpace,c.ClusterSolution.classes, ... + % c.names,out.sig,1,{'Pos' 'Neg'}); + % nmdsfig_legend(c.ClusterSolution.X,c.r) + % + % Example: compare correlations on cluster averages + % c_compare = + % correl_compare_dep(y1avg,y2avg,'alpha',.05,'rank','table','names',c.APPLY_CLUSTER.names); + % + % + % Example: + % -------------------------------------------------------------------- + % %%%get image names + % EXPT.subjects = {'ambar_carvalho' 'andreas_nguyen' 'angela_valle' 'brad_wilson' 'dominic_ricci'}; + % EXPT = getfunctnames2(EXPT,'con_0004.img','tmp','SPM_analysis/physical_pain/full_model_gv_p_v_np') + % self = str2mat(EXPT.tmp{:}) + % + % EXPT = getfunctnames2(EXPT,'con_0003.img','tmp','SPM_analysis/Videos/event_pain_only') + % other = str2mat(EXPT.tmp{:}) + % + %maskimage = which('scalped_avg152T1_graymatter_smoothed.img') + % + % %%%get seeds + %cd Jamil/GROUP_ANALYSES/Overlaps/Overlap_29_Aug/ + %cl = mask2clusters('con_0002.img'); + %cl = cl(4) + % cl = extract_contrast_data([{self} {other}],cl); + % seedself = cl(1).CONTRAST.data(:,1); + %seedother = cl(1).CONTRAST.data(:,2); + % + % correl_compare_dep_search(seedself,seedother,self,other,'alpha',.005,'mask',mask); + % RESULTS: + % cl = mask2clusters('Correl_seed1_sig.img'); cluster_orthviews(cl,'bivalent'); + % + % Try the whole thing on ranks: + %correl_compare_dep_search(seedself,seedother,self,other,'alpha',.005,'mask',mask,'rank'); + % + % + % Edited: Tor Wager, Feb 2010; cosmetic fixes only + + myalpha = .05; + dorankdata = 0; + dotable = 0; + names = []; + + for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % functional commands + case 'alpha', myalpha = varargin{i+1}; + case 'rank', dorankdata = 1; + case 'table', dotable = 1; + case 'names', names = varargin{i+1}; + case 'mask', maskimage = varargin{i+1}; varargin{i+1} = []; + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + % if image names instead of data matrix, then load data + if isstr(y1) + + imgs1 = y1; + imgs2 = y2; + [y1,volInfo] = iimg_get_data(maskimage,imgs1); + [y2,volInfo] = iimg_get_data(maskimage,imgs2); + + end + + + + + [N,nvox] = size(y1); + + + %[rows,cols,ncorr] = corrcoef_indices(nvox); + + if nargin < 4, dorankdata = 0; end + if dorankdata + str = sprintf('Ranking data: Nonparametric correlations'); fprintf(1,str); + + seed1 = rankdata(seed1); + seed2 = rankdata(seed2); + + for i = 1:nvox + y1(:,i) = rankdata(y1(:,i)); + y2(:,i) = rankdata(y2(:,i)); + end + else + str = sprintf('Assuming continuous data (no ranks).'); fprintf(1,str); + end + + erase_string(str); + + i = 1; j = 2; k = 3; h = 4; + + str = sprintf('Computing differences among correlations %06d',0); fprintf(1,str); + + diffr = zeros(nvox,1); + Zstar2 = zeros(nvox,1); + rr = zeros(nvox,2); + p1 = zeros(nvox,1); + p2 = zeros(nvox,1); + + % ------------------------------------------------------- + % loop through all voxels + % ------------------------------------------------------- + + for cc = 1:nvox + + fprintf(1,'\b\b\b\b\b\b%06d',cc); + + % get full correlation matrix for the 4 variables involved + dat = [seed1 y1(:,cc) seed2 y2(:,cc)]; + + % get differences between correlation z-values (estimate) + % diffr = difference between correls + % rr = correls for condition A, then B (columns) + % diffrz = Z-score of difference + + [diffr(cc),r,rr(cc,:),diffrz,z,pp] = correlation_diffs(dat,i,j,k,h); + + % p-values for seed analysis of cond1, cond2 + p1(cc) = pp(i,j); + p2(cc) = pp(k,h); + + s = covcorr(r,i,j,k,h); % covariance of corr coeffs + + Zstar2(cc) = diffrz * sqrt( (N-3) ./ (2-(2*s)) ); + + end + + erase_string(str); + + % all p-values + % ------------------------------------------------------- + + pdiff = 2 * (1 - normcdf(abs(Zstar2))); + + r1 = rr(:,1); + r2 = rr(:,2); + + + % threshold: differences + [sigdiff,pthrdiff,sigfdrdiff] = apply_threshold(myalpha,pdiff,diffr); + + [sig1,pthr1,sigfdr1] = apply_threshold(myalpha,p1,rr(:,1)); + [sig2,pthr2,sigfdr2] = apply_threshold(myalpha,p2,rr(:,2)); + + % write images: differences + % ---------------------------- + painpag(diffr,volInfo,'outname','Correl_diff_r.img','descrip','Difference in correl with seed A - B'); + iimg_reconstruct_vols(pdiff,volInfo,'outname','Correl_diff_p.img','descrip','Difference in correl with seed A - B'); + iimg_reconstruct_vols(Zstar2,volInfo,'outname','Correl_diff_Z.img','descrip','Z*2 from Steiger, 1980'); + iimg_reconstruct_vols(pdiff,volInfo,'outname','Correl_diff_p.img','descrip','Difference in correl with seed A - B'); + + if any(sigdiff) + iimg_reconstruct_vols(sigdiff,volInfo,'outname','Correl_diff_sig.img','descrip',['Sig uncorrected: ' num2str(myalpha)]); + else + disp(['Difference: No significant results at ' num2str(myalpha)]); + end + + if any(sigfdrdiff) + iimg_reconstruct_vols(sigfdrdiff,volInfo,'outname','Correl_diff_sigfdr.img','descrip','Sig FDR, p<.05 '); + else + disp(['Difference: No significant results at FDR .05']); + end + + % write images: seed 1 + % ---------------------------- + iimg_reconstruct_vols(r1,volInfo,'outname','Correl_seed1_r.img','descrip','seed1 correlation'); + iimg_reconstruct_vols(p1,volInfo,'outname','Correl_seed1_p.img','descrip','Seed1 correl with seed A - B'); + + if any(sig1) + iimg_reconstruct_vols(sig1,volInfo,'outname','Correl_seed1_sig.img','descrip',['Sig uncorrected: ' num2str(myalpha)]); + else + disp(['Seed1: No significant results at ' num2str(myalpha)]); + end + + if any(sigfdr1) + iimg_reconstruct_vols(sigfdr1,volInfo,'outname','Correl_seed1_sigfdr.img','descrip','Sig FDR, p<.05 '); + else + disp(['Seed1: No significant results at FDR .05']); + end + + % write images: seed 2 + % ---------------------------- + iimg_reconstruct_vols(r2,volInfo,'outname','Correl_seed2_r.img','descrip','seed2 correlation'); + iimg_reconstruct_vols(p2,volInfo,'outname','Correl_seed2_p.img','descrip','Seed2 correl with seed A - B'); + + if any(sig2) + iimg_reconstruct_vols(sig2,volInfo,'outname','Correl_seed2_sig.img','descrip',['Sig uncorrected: ' num2str(myalpha)]); + else + disp(['Seed2: No significant results at ' num2str(myalpha)]); + end + + if any(sigfdr2) + iimg_reconstruct_vols(sigfdr2,volInfo,'outname','Correl_seed2_sigfdr.img','descrip','Sig FDR, p<.05 '); + else + disp(['Seed2: No significant results at FDR .05']); + end + + + + return + + + + + + +function [sig,pthr,sigfdr] = apply_threshold(myalpha,p,r) + + % uncorrected sign. voxels + sig = (p <= myalpha - eye(size(p))) .* sign(r); + + % FDR corrected + pthr = FDR(p,.05); + if isempty(pthr), pthr = 0; end + + sigfdr = (p <= pthr) .* sign(r); + + return + + + +function [diffr,r,rr,diffrz,z,p] = correlation_diffs(dat,i,j,k,h) + % get differences between correlation z-values + [r,p] = corrcoef(dat); + + rr = [r(i,j) r(k,h)]; % correls to be compared + diffr = -diff(rr); % negative sign means we get (1) - (2) + + if nargout > 3 + z = .5 .* log( (1+rr) ./ (1-rr) ); + diffrz = -diff(z); % diff btwn correl z values + end + + return + + + +function [rows,cols,ncorr] = corrcoef_indices(nvox) + % upper triangle only + tmp = triu(ones(nvox)); + tmp = tmp - eye(nvox); + [rows,cols] = find(tmp); + ncorr = length(rows); + return + + + + +function s = covcorr(R,i,j,k,h) + + % pool correl. coeff for more stable var est. + % as they are equal under Ho. Steiger, 1980, eq. 14 for Z* + pooledr = mean([R(i,j) R(k,h)]); + s = pearsonf(R,i,j,k,h) ./ ( corvar(pooledr) ); + + return + + +function cv = corvar(r) + % variance of a correlation coefficient + % simplified form (special case) of pearsonf + cv = (1 - r^2) ^ 2; + return + + + % % function cv = co(R,x,y) + % % % x and y are 2-vector indices of matrix R to compute covariance for + % % % cv is covariance + % % cv = pearsonf(R,x(1),x(2),y(1),y(2)); + % % return + + +function pf = pearsonf(R,i,j,k,h) + + % Pearson-Filon: covariance (or var) of element i,j with element k,h + % depending on correlation values + + % for variance of 1 correl, works as well, and reduces to: + % (1 - rr(1)^2)^2 + + pf = (1/2) .* R(i,j) .* R(k,h) .* ... + ( R(i,k).^2 + R(i,h).^2 + R(j,k).^2 + R(j,h).^2 ) + ... + R(i,k) .* R(j,h) + R(i,h) .* R(j,k) - ... + R(i,j) .* ( R(j,k) .* R(j,h) + R(i,k) .* R(i,h) ) - ... + R(k,h) .* ( R(j,k) .* R(i,k) + R(j,h) .* R(i,h) ); + + + return + + + + + % function pf = pearsonf2(r,j,k,h,m) + % + % pf = .5 * ( (r(j,h) - r(j,k)*r(k,h)) * (r(k,m) - r(k,h)*r(h,m)) + ... + % (r(j,m) - r(j,h)*r(h,m)) * (r(k,h) - r(k,j)*r(j,h)) + ... + % (r(j,h) - r(j,m)*r(m,h)) * (r(k,m) - r(k,j)*r(j,m)) + ... + % (r(j,m) - r(j,k)*r(k,m)) * (r(k,h) - r(k,m)*r(m,h)) ); + % + % return + +function valmat = reconstruct(vals,nvox,ncorr,rows,cols) + + valmat = zeros(nvox); + for i = 1:ncorr + valmat(rows(i),cols(i)) = vals(i); + end + valmat = valmat + valmat'; + + return + + + +function erase_string(str1) + fprintf(1,repmat('\b',1,length(str1))); % erase string + return + + +function maketable(c_compare,whfield,names) + + str = {'-' '+'}; + [rows,cols] = find(triu(c_compare.(whfield))); + if isempty(rows) + disp('No significant results.') + else + fprintf(1,'Name1\tName2\trow\tcol.\t+ or -\tr1\tr2\tZ\tp\n'); + for i = 1:length(rows) + fprintf(1,'%s\t%s\t%3.0f\t%3.0f\t%s\t%3.3f\t%3.3f\t%3.2f\t%3.4f\n', ... + names{rows(i)}, names{cols(i)},rows(i),cols(i), ... + str{1.5 + (.5.*c_compare.(whfield)(rows(i),cols(i)))}, ... % + or - sign + c_compare.r1(rows(i),cols(i)),c_compare.r2(rows(i),cols(i)), ... % correlations + c_compare.Z(rows(i),cols(i)),c_compare.p(rows(i),cols(i))); % Z and p + end + end + fprintf(1,'\n'); + return \ No newline at end of file diff --git a/Statistics_tools/correl_compare_indep.m b/Statistics_tools/correl_compare_indep.m new file mode 100644 index 00000000..9a62a2ee --- /dev/null +++ b/Statistics_tools/correl_compare_indep.m @@ -0,0 +1,78 @@ +function [r1, r2, rdiff, rdiff_p, rdiff_zscore] = correl_compare_indep(y_g1, y_g2, varargin) + % [r1, r2, rdiff, rdiff_p, rdiff_zscore] = correl_compare_indep(y_g1, y_g2, varargin) + % + % Compute statistics on the difference between correlation coefficients for indepndent samples g1 and g2. + % correlations are computed over y (n observations x k variables) for each + % group. + % + % Tor Wager, Aug. 2008 + % + % Source, Hubert Blalock, Social Statistics, NY: McGraw-Hill, 1972: 406-407. + % From notes on Garson stats website. + % + % Edit: March 2010-add no verbose option. input 'noverbose' + % Bug fix in Fisher's Z transform + + verbose = 1; + if length(varargin) > 0 + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'noverbose', verbose = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + end + + k = size(y_g1, 2); + + if k ~= size(y_g2, 2), error('Num. variables for each group must match.'); end + + r1 = corrcoef(y_g1); + r2 = corrcoef(y_g2); + + n1 = size(y_g1, 1); + n2 = size(y_g1, 1); + + % Fisher's Z scores and stats on each group + [tmp, r1_sig] = r2z(r1(:), n1); + r1_z = fisherz(r1(:)); + + r1_sig = reshape(r1_sig, k, k); + r1_z = reshape(r1_z, k, k); + + [tmp, r2_sig] = r2z(r2(:), n2); + r2_z = fisherz(r2(:)); + + r2_sig = reshape(r2_sig, k, k); + r2_z = reshape(r2_z, k, k); + + if verbose + disp('Group 1'); correlation_to_text( r1, r1_sig); + disp('Group 2'); correlation_to_text( r2, r2_sig); + end + + % difference and stats + + rdiff = r1 - r2; + + rdiff_z = r1_z - r2_z; + rdiff_se = sqrt(1./(n1 - 3) + 1./(n2 - 3)); + + rdiff_zscore = rdiff_z ./ rdiff_se; + + rdiff_p = 2 * (1 - normcdf(abs(rdiff_zscore))); + + if verbose + disp('Difference'); + print_matrix(rdiff); + + disp('Difference p-values'); + print_matrix(rdiff_p); + end + +end + + diff --git a/Statistics_tools/correl_compare_indep_inputr.m b/Statistics_tools/correl_compare_indep_inputr.m new file mode 100644 index 00000000..cac8c067 --- /dev/null +++ b/Statistics_tools/correl_compare_indep_inputr.m @@ -0,0 +1,41 @@ +function [rdiff, Z, pval, stats] = correl_compare_indep_inputr(r1, r2, N1, N2, varargin) + % [rdiff, Z, pval, stats] = correl_compare_indep_inputr(r1, r2, N1, N2, varargin) + % + % Compare two Pearson's correlation values (r1 and r2) collected from independent + % samples with sample sizes N1 and N2 + % + % Tor Wager, March 2010 + % + % Based on: www.stat-help.com/ + % see also: correl_compare_indep.m and correl_compare_dep.m + % + % Example: + % [rdiff, Z, pval, stats] = correl_compare_indep_inputr(.33, .77, 100, 100) + + if nargin == 0 + disp('Using sample values for r1 r2 N1 N2') + N1 = 100; + N2 = 100; + r1 = .33; + r2 = .77; + end + + rdiff = r1 - r2; + + sediff = sqrt(1/(N1-3) + 1/(N2-3)); % Standard error of difference between two standard normals, with df = N - 3 for correlation coeff + zdiff = fisherz(r1) - fisherz(r2); % difference in Fisher's z values + + Z = zdiff ./ sediff; % Z-score (inferential stat) + + pval = 2 * (1 - normcdf(abs(Z))); % p-value + + myalpha = .05; % ROI + sig = pval < myalpha; + + stats = struct('r1', r1, 'r2', r2, 'N1', N1, 'N2', N2, 'diff_r_values', rdiff, ... + 'fishers_z_diff', zdiff, 'Z_stat', Z, 'p', pval, 'alpha', myalpha, 'sig', sig); + +end + +%out = correl_compare_dep(y1,y2, 'alpha',myalpha) + diff --git a/Statistics_tools/correl_compare_permute.m b/Statistics_tools/correl_compare_permute.m new file mode 100644 index 00000000..7e979391 --- /dev/null +++ b/Statistics_tools/correl_compare_permute.m @@ -0,0 +1,112 @@ +function OUT = correl_compare_permute(meth,dat,nperms,condition) +% function correl_compare_permute(meth,dat,nperms,condition) +% +% General function for comparing correlation matrices on two different sets +% of observations +% +% dat is obs x variables, and an n x n association matrix will be computed +% on the n(n - 1) pairs of columns using one of the methods in +% correlation.m and specified by meth +% +% this is a work in progress, and documentation is incomplete. +% tor wager, Jan 15, 2007 + +if isempty(meth), meth = 'taub'; end + +% set up permutations +fprintf(1,'Setting up permutations. '); +[n,m] = size(dat); + +permindx = permute_setupperms(n,nperms); + + +% correct permutation +fprintf(1,'Correct perm. '); +[cdiff, cdiffmax,c1,c2] = get_corrmat(meth,dat,condition); + + +% init perms +cdiffprm = zeros(m,m,nperms); +cdiffmaxp = zeros(nperms,1); +cdiff_matrix = zeros(nperms,m*(m-1)/2); + +fprintf(1,'Permuting: %05d',0); + +for i = 1:nperms + + if mod(i,10) == 0 + fprintf(1,'\b\b\b\b\b%05d',i); + end + + % permute rows to shuffle condition assignment labels + datp = dat(permindx(i,:),:); + + [cdiffprm(:,:,i), cdiffmaxp(i),null,null,cdiff_matrix(i,:)] = get_corrmat(meth,datp,condition); + +end + +fprintf(1,' Done.\n'); + +OUT = struct('meth',meth,'c1',c1,'c2',c2,'cdiff',cdiff,'cdiffprm',cdiffprm,'cdiff_matrix',cdiff_matrix,'cdiffmaxp',cdiffmaxp); + +% Results and thresholding +OUT.diff_corrected05thr = prctile(OUT.cdiffmaxp,95); + +[rows,cols,ncorr] = corrcoef_indices(m); +for cc = 1:ncorr + + % do threshold on each pair separately + this_cor = squeeze(OUT.cdiffprm(rows(cc),cols(cc),:)); % null-hypothesis + % one-tailed + pv = sum(this_cor <= OUT.cdiff(rows(cc),cols(cc))) ./ nperms; + pval(cc) = min(pv,1-pv); + +end + +OUT.pdiff = reconstruct(pval,m,ncorr,rows,cols); +OUT.pdiff = OUT.pdiff + eye(m); + +end + + + + +function [cdiff, cdiffmax,c1,c2,cdiff_matrix] = get_corrmat(meth,dat,condition) + +c1 = correlation('taub',dat(condition == 1,:)); +c2 = correlation('taub',dat(condition == 2,:)); + +cdiff = c1 - c2; + +% works for any diff between correls, two tailed +cdiff_matrix = squareform(cdiff); +cdiffmax = max(abs(cdiff_matrix)); + +% works for any correlation with ones on the diagonal +% two-tailed +%cdiffmax = max(abs(squareform(cdiff - 1) + 1)); + +end + + +function [rows,cols,ncorr] = corrcoef_indices(npairs) + % upper triangle only + tmp = triu(ones(npairs)); + tmp = tmp - eye(npairs); + [rows,cols] = find(tmp); + ncorr = length(rows); +end + + + +function valmat = reconstruct(vals,corrmatsize,ncorr,rows,cols) + + valmat = zeros(corrmatsize); + for i = 1:ncorr + valmat(rows(i),cols(i)) = vals(i); + end + valmat = valmat + valmat'; + +end + + diff --git a/Statistics_tools/correlation.m b/Statistics_tools/correlation.m new file mode 100644 index 00000000..8f4ec772 --- /dev/null +++ b/Statistics_tools/correlation.m @@ -0,0 +1,380 @@ +function [corr,t,p, fdrsig, fdrthresh] = correlation(meth,x,varargin) +% [corr,t,p,fdrp, fdrthresh] = correlation(method,x,[y],['matrix']) +% multiple types of correlations, including Spearman's rho (nonparametric) and phi (dichotomous) +% +% tor wager, november 2006, Jan 2007 +% IN PROGRESS : Warning : Use at your own risk. +% Some methods are not adequately tested yet. +% Spearman's rho does not correct for ties +% +% Methods: +% Pearson's r. Enter: {'r','pearson',[]} +% IRLS Enter: {'irls','robust'} +% Phi Enter: {'phi'} +% Spearman's rho Enter: {'rho','spearman'} +% Kendall's Tau (a) Enter: {'taua','kendalla'} +% Tau (b) Enter: {'tau','kendall','taub','kendallb'} +% Gamma Ehter: {'gamma','kruskal'} +% +% +% Examples: +% +% % Corelation between two variables, Pearson's +% % -------------------------------- +% x = rand(10,1); y = rand(10,1); +% [corr,t,p] = correlation('r',x,y); +% +% % Correlation matrix of 10 variables, phi correlation: +% % -------------------------------- +% studybyroi = magic(10); +% [corr,t,p] = correlation('phi',studybyroi); +% +% See also correlation_fast_series.m + +corr = []; t = []; p = []; + + +% get y data, if entered +% -------------------------------- +for i = 1:length(varargin) + if ~ischar(varargin{i}) && ~any(size(varargin{i}) - size(x)) + y = varargin{i}; + end +end + +% get flag for whether to compute correls among all possible pairs +% -------------------------------- +matrixFormFlag = 0; +if any(strcmp(varargin,'matrix')) || ~exist('y','var') + matrixFormFlag = 1; +end + +[n,nvars] = size(x); + +% Get indices +% -------------------------------- +if matrixFormFlag + % input is matrix + [rows,cols,npairs] = corrcoef_indices(nvars); + +else + % input is columns of consecutive pairs to correlate + npairs = size(x,2); % number of pairs of correlations to compute +end + +doverbose = 0; +if npairs > 1000, doverbose = 1; fprintf('%03d%%', 0); end + +% Compute correlations +% -------------------------------- +for i = 1:npairs + + if doverbose, fprintf('\b\b\b\b%03d%%', round(i*100 ./ npairs)), end + + % get data + if matrixFormFlag + x1 = x(:,rows(i)); x2 = x(:,cols(i)); + else + x1 = x(:,i); x2 = y(:,i); + end + + if nargout > 1 + [c,tt,pp] = compute_single_correl(meth,x1,x2,n); + else + c = compute_single_correl(meth,x1,x2,n); + end + + corr(i,1) = c; + + if nargout > 1 + if ~isempty(tt), t(i,1) = tt; end + if ~isempty(pp), p(i,1) = pp; end + end +end + +% Reconstruct into matrix, if needed +% -------------------------------- +if matrixFormFlag + corr = reconstruct(corr,nvars,npairs,rows,cols); + + corr = corr + eye(nvars); + + if ~isempty(t), t = reconstruct(t,nvars,npairs,rows,cols); end + + if ~isempty(p), p = reconstruct(p,nvars,npairs,rows,cols); end + + % FDR correction, if requested + % only works for matrices, otherwise error + if nargout > 3 + [fdrthresh,fdrsig] = fdr_correct_pvals(p,corr); + end + +elseif nargout > 3 + error('FDR output is only allowed for matrix form output. Try requesting fewer outputs') +end + + + + +return + + + + + +% sub-functions + + + +function [est,t,p] = compute_single_correl(meth,x,y,n) + +est = []; +t = []; +p = []; + +switch lower(meth) + + case {'irls','robust'} + [b,stats]=robustfit(x,y,'bisquare'); %,[],'off'); + est = weighted_corrcoef([x y], stats.w); + est = est(1,2); + + t = stats.t(2); + p = stats.p(2); + + case {'r','pearson',[]} + + if islogical(x) + warning('Logical vectors not appropriate for Pearson''s r.'); + x = double(x); + y = double(y); + end + + [est,p] = corrcoef([x y]); + est = est(1,2); + p = p(1,2); + + case 'phi' + % Phi is for dichotomous (2-level) variables + % formula from Robert Yaffee, NYU, online. + + + tab = make_crosstabs(x,y); + num = det(tab); % same as: num = tab(1,1)*tab(2,2) - tab(1,2)*tab(2,1); + den = ( prod(sum(tab,2)) .* prod(sum(tab,1)) ).^.5; + est = num ./ den; + + % F = varexp/dfexp / varunexp/dferror; t = sqrt(F) + if nargout > 1 % for speed + t = est .* sqrt((n - 2) ./ (1 - est.^2)); + p = 2 .* (1 - tcdf(abs(t),n-2)); % two-tailed p-value + end + %wts = []; + %nonpar = 0; + % this works,but is unsigned + %[chi2,df,p,sig] = chi2test([x y],'obs'); %,wts,nonpar); + %est = (chi2 ./ n) .^ .5; + + + case {'rho','spearman'} + + % This method uses midrank method to handle ties; needs checking + % vs. SPSS + D = rankdata(x) - rankdata(y); + est = 1 - (6*(D'*D)) ./ (n * (n^2-1)); + + % same as : corrcoef(rankdata(x),rankdata(y)); + % but faster + % tested against SPSS 11.04 + % problem with large n? + + % % n = 1000; + % % tic, for i = 1:100, x = rand(n,1); y = x + rand(n,1); + % % corrcoef(rankdata(x),rankdata(y)); + % % end, toc + % % tic, for i = 1:100, x = rand(n,1); y = x + rand(n,1); + % % n = size(x,1); + % % D = rankdata(x) - rankdata(y); + % % 1 - (6*(D'*D)) ./ (n * ((n^2)-1)); + % % end, toc + + + case {'taua','kendalla'} + + % checked 1/13/07 against http://www.wessa.net/rwasp_kendall.wasp + % should probably be checked against SPSS + + [r,i] = sort(x); + y = y(i); % get ordered y; don't really need to rank + + % count number of ranks above each successive value of y + for i = 1:(n - 1) + P(i) = sum( y(i+1:end) > y(i) ); + end + est = 4 * sum(P) ./ (n * (n - 1)) - 1; + + if nargout > 1 % for speed + % actually a z-score + t = 3 * est * (n * (n - 1)) .^.5 ./ (2 * (2 * n + 5)) .^.5; + p = 1 - normcdf(abs(t)); + + if p == 0, p = 10*eps; end + end + + case {'tau','kendall','taub','kendallb'} + + % checked 1/13/07 against http://www.wessa.net/rwasp_kendall.wasp + % should probably be checked against SPSS + + [r,i] = sort(x); + y = y(i); % get ordered y + + % count number of ranks above each successive value of y + for i = 1:(n - 1) + wh = r > r(i); % find pts with x value greater than r(i); handle x ties + P(i) = sum( y(wh) > y(i) ); % concordances + N(i) = sum( y(wh) < y(i) ); % discordances + end + + %% *** maybe ties have to include all values that are tied"?? + % http://www.statsdirect.com/help/nonparametric_methods/kend.htm + xties = get_ties(x); + yties = get_ties(y); + + num = sum(P) - sum(N); + + n1 = n * (n - 1) ./ 2; + + % From http://www.unesco.org/webworld/idams/advguide/Chapt4_2.htm + % and http://www.statsdirect.com/help/nonparametric_methods/kend.htm + den = sqrt( (n1 - sum(xties)) * (n1 - sum(yties)) ); + + % Tau b : from http://www.unesco.org/webworld/idams/advguide/Chapt4_2.htm + %den = sqrt((sum(P) + sum(N) + (n - sum(xties))) * (sum(P) + sum(N) + (n - sum(yties)))); + + est = num ./ den; + + if nargout > 1 % for speed + % actually a z-score + t = 3 * est * (n * (n - 1)) .^.5 ./ (2 * (2 * n + 5)) .^.5; + p = 1 - normcdf(abs(t)); + end + + + case {'gamma','kruskal'} + + [r,i] = sort(x); + y = y(i); % get ranks of y + + % count number of ranks above each successive value of y + for i = 1:(n - 1) + wh = r > r(i); % find pts with x value greater than r(i); handle x ties + P(i) = sum( y(wh) > y(i) ); % concordances + N(i) = sum( y(wh) < y(i) ); % discordances + end + + num = sum(P) - sum(N); + den = sum(P) + sum(N); + est = num ./ den; + + otherwise + error('Unknown correlation option'); +end + + + +return + + + + + + +function tab = make_crosstabs(x,y) +% make table (crosstabs) +u1 = unique(x); +u2 = unique(y); +len1 = length(u1); +len2 = length(u2); +tab = zeros(len1,len2); +w = 1; % weights + +for i = 1:length(u1) % rows are 0 then 1 on the first var, "Nos" then "Yesses" + for j = 1:length(u2) % for each column + tab(i,j) = sum( (x == u1(i) & y == u2(j)) .* w ); + end +end + +return + + + +function ties = get_ties(x) +r = rankdata(x, 'nomidrank'); +for i = 1:length(unique(r)) + ties(i) = sum(r == i); +end +ties = sum(ties .* (ties - 1) ./ 2); +return + + + +function [rows,cols,npairs] = corrcoef_indices(nvars) + + if nvars > 1000 + fprintf('Setting up indices of matrix.'); + % added to deal with large datasets + tmp = triu(true(nvars)); + + for i = 1:nvars + tmp(i, i) = 0; + end + +% % create logical identity matrix of large size +% t2 = eye(1000); +% t2 = logical(t2); +% eyemtx = t2; +% while size(eyemtx, 2) < nvars +% eyemtx = blkdiag(eyemtx, t2); +% end +% eyemtx = eyemtx(1:nvars, 1:nvars); +% +% tmp = tmp - eyemtx; + + fprintf(1,'Done.\n'); + + else + % upper triangle only + tmp = triu(ones(nvars)); + tmp = tmp - eye(nvars); + end + + [rows,cols] = find(tmp); + npairs = length(rows); + + return + + + +function valmat = reconstruct(vals,nvars,npairs,rows,cols) + +valmat = zeros(nvars); +for i = 1:npairs + valmat(rows(i),cols(i)) = vals(i); +end +valmat = valmat + valmat'; + +return + + +function [pthr,sig] = fdr_correct_pvals(p,r) + + psq = p; psq(find(eye(size(p,1)))) = 0; + psq = squareform(psq); + pthr = FDR(p,.05); + if isempty(pthr), pthr = 0; end + + sig = sign(r) .* (p < pthr); + + return + \ No newline at end of file diff --git a/Statistics_tools/correlation_fast_series.m b/Statistics_tools/correlation_fast_series.m new file mode 100644 index 00000000..32aa2c26 --- /dev/null +++ b/Statistics_tools/correlation_fast_series.m @@ -0,0 +1,70 @@ +function [r, p, Tstat] = correlation_fast_series(Xi, Yi) +% [r, p, t] = correlation_fast_series(Xi, Yi) +% +% Fast, memory-efficient way to get correlation between each column of Xi and Yi +% +% Tor Wager, June 2010 +% +% Notes: tested against corrcoef.m on June 28, 2010, tor + +% Test code +% r = ((Xi' * Yi) ./ N) ./ sqrt(diag(Xi' * Xi) ./ N * (Yi' * Yi) ./ N); +% [r2, p, Tstat] = correlation_fast_series(Xi, Yi); +% [r3, p2] = corrcoef([Yi, Xi]); r3 = r3(1, 2:end); p2 = p2(1, 2:end); +% create_figure('test', 1, 3); plot(r3, r, 'kx'); axis equal; grid on; subplot(1, 3, 2); plot(r3, r2, 'kx'); axis equal; grid on; subplot(1, 3, 3); plot(p2, p, 'kx'); axis equal; grid on; + +[N, m] = size(Xi); + +vx = var(Xi, 0)'; +vy = var(Yi, 0); +Xi = scale(Xi, 1); Yi = scale(Yi, 1); +r = ((Xi' * Yi) ./ N) ./ sqrt(vx * vy); + +% This works, but is too memory intensive because of Xi*Xi +% also, needs mean-centering?? +%r = ((Xi' * Yi) ./ N) ./ sqrt(diag(Xi' * Xi) ./ N * (Yi' * Yi) ./ N); + +% adjust for perfect correlations; avoid warnings +% done in corrcoef.m +r(r == 1) = 1 - 100*eps; +r(r == -1) = -1 + 100*eps; + +Tstat = r .* sqrt((N-2) ./ (1 - r.^2)); + p = zeros(m, 1, class(Xi)); + p = 2 * tpvalue(-abs(Tstat), N - 2); % two-tailed p-value + + + +% Borrowed from corrcoef.m, Matlab 7.5 +function p = tpvalue(x,v) +%TPVALUE Compute p-value for t statistic + +normcutoff = 1e7; +if length(x)~=1 && length(v)==1 + v = repmat(v,size(x)); +end + +% Initialize P. +p = NaN(size(x)); +nans = (isnan(x) | ~(0 (0 normcutoff); +p(normal) = 0.5 * erfc(-x(normal) ./ sqrt(2)); + +% See Abramowitz and Stegun, formulas 26.5.27 and 26.7.1 +gen = ~(cauchy | normal | nans); +p(gen) = betainc(v(gen) ./ (v(gen) + x(gen).^2), v(gen)/2, 0.5)/2; + +% Adjust for x>0. Right now p<0.5, so this is numerically safe. +reflect = gen & (x > 0); +p(reflect) = 1 - p(reflect); + +% Make the result exact for the median. +p(x == 0 & ~nans) = 0.5; diff --git a/Statistics_tools/dice_coeff_image.m b/Statistics_tools/dice_coeff_image.m new file mode 100644 index 00000000..17cb57fe --- /dev/null +++ b/Statistics_tools/dice_coeff_image.m @@ -0,0 +1,37 @@ +function dice_coeff = dice_coeff_image(imagelist) + % dice_coeff = dice_coeff_image(imagelist) + % + % dice coefficient for the overlap between each pair of a set of images + % tor wager, June 2010 + + N = size(imagelist, 1); + if N == 1, error('Must enter at least two images'); end + + [maskInfo, dat] = iimg_read_img(imagelist(1, :)); + + for i = 2:N + + dat2 = scn_map_image(imagelist(i, :), imagelist(1, :)); + dat2 = dat2(:); + + dat = [dat dat2]; + + end + + dat = double(abs(dat) > eps); + + n = dat'*dat; % intersection * 2 + + % simple, but not very efficient + for i = 1:N + for j = 1:N + + d(i, j) = sum(dat(:, i) | dat(:, j)); % union + + end + end + + dice_coeff = n ./ d; + +end + diff --git a/Statistics_tools/exhaustive_map.m b/Statistics_tools/exhaustive_map.m new file mode 100644 index 00000000..f16aab64 --- /dev/null +++ b/Statistics_tools/exhaustive_map.m @@ -0,0 +1,27 @@ +function [params, maxobj, objmap] = exhaustive_map(p1vals, p2vals, objfun) + + [d1,d2] = meshgrid(p1vals, p2vals); + + objmap = NaN .* zeros(size(d1)); + + [m,n] = size(d1); + for j = 1:m + for k = 1:n + objmap(j,k) = objfun(d1(j,k), d2(j,k)); + end + end + + [w1,w2] = find(objmap == max(objmap(:))); + maxobj = objmap(w1,w2); + + for j = 1:size(w1, 1) + best_d1(j, 1) = d1(w1(j), w2(j)); + best_d2(j, 1) = d2(w1(j), w2(j)); + end + + params = [best_d1 best_d2]; + + % plot + + [data_matrix12, xvals2, yvals2, data_matrix22, bestx, besty, bestz] = surf_plot_tor(objmap, p1vals, p2vals, 'X', 'Y', 'Objective'); +end \ No newline at end of file diff --git a/Statistics_tools/fisherz.m b/Statistics_tools/fisherz.m new file mode 100644 index 00000000..8e6e1242 --- /dev/null +++ b/Statistics_tools/fisherz.m @@ -0,0 +1,11 @@ +% Fisher's r to z' transform, and the inverse +% [z, r] outputs: +% z = z', treating input r as correlation +% r = r, treating input r as a z' score +function [z, r] = fisherz(r) + z = .5 .* log((1+r) ./ (1-r)); % Fisher's r-to-z transform + + if nargout > 0 + r = (exp(2.*r) - 1) ./ (exp(2.*r) + 1); % inverse + end +end \ No newline at end of file diff --git a/Statistics_tools/fit_gls.m b/Statistics_tools/fit_gls.m new file mode 100644 index 00000000..00557e30 --- /dev/null +++ b/Statistics_tools/fit_gls.m @@ -0,0 +1 @@ +function [beta, t, pvals, convals, con_t, con_pvals, sigma, Phi, df, stebeta, conste, F] = fit_gls(y, X, c, p, varargin) % % [beta, t, pvals, convals, con_t, con_pvals, sigma, Phi, df, stebeta, conste, F] = fit_gls(y,X,c,p,[PX, equal to pinv(X), for speed], [Weights]) % % Fit a linear model using generalized least squares and an AR(p) model % % This program uses the Cochrane-Orcutt algorithm to iteratively find the % GLS solution and estimate the noise parameters. % % Step 1: Find the OLS solution. % % Step 2: Use residuals from the previous fit to estimate the parameters in % the AR(p) noise model. % % Step 3: Find the GLS solution using the covariance matrix corresponding % to an AR(p) model with parameters estimated in Step 2 inserted. % % Step 4: Repeat steps 2-3 until convergence. % % INPUT: % % y - fMRI time course (T x 1 vector) % X - Design matrix (T x param matrix) % c - contrast vector(s) (param x # contrasts matrix) % p - order of AR model. % PX : pinv(X), for speeded, repeated calculations with different y vectors % Note: if using weights, px = inv(X' * W * X) * X' * W; % where W = diag(Weights); % Weights: Optional, vector of weights for each observation % Empty or missing: Unweighted analysis. % % Note that setting p=0 implies a white noise model. % % OUTPUT: % % t - t-value for the contrast c'beta % df - degrees of freedom using Satterthwaite approximation % beta - beta vector % Phi - vector of coefficients in AR(p) model % sigma - standard deviation % stebeta - standard error of betas % % % by Martin Lindquist % % Last updated: 3/29/08, Tor Wager, added weighted least squares % verified that beta and t-values are identical to % glmfit.m in matlab7.5 with ar p = 0 % ***AR(p) with weighted least squares needs to be % checked. behaving reasonably. % 4/1/08, Tor : output stats for boht betas and contrasts % Reorganized order of outputs % 5/15/08 Tor : Weird things happening with single inputs; particulaly with aryule; force % double % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% y = double(y); X = double(X); T = length(y); % Length of time course k = size(X, 2); % predictors if length(varargin) > 1 w = double(varargin{2}); % weights for weighted least squares else w = ones(T, 1); end W = diag(w); sqrtW = sqrt(W); % for weighted residuals if ~isempty(varargin) && ~isempty(varargin{1}) px = double(varargin{1}); else invxvx = inv(X' * W * X); % we can re-use this later if p == 0 px = invxvx * X' * W; end % Step 1: Find the OLS solution beta = px*y; % Beta values; weighted, if weights are used resid = sqrtW * y - sqrtW * X * beta; % Residuals (Weighted, if weights are used) sigma = sqrt((1 / (T - k)) * resid' * resid); %sum(resid.^2))); % Estimate of Sigma % Weighted residuals: Three equivalent ways % We pick one that is compatible with AR estimation % 1) % beta = px*y; % Beta values % resid = y - X*beta; % Residuals % sigma = sqrt((1 / (T - k)) * resid' * W * resid); %sum(resid.^2))); % Estimate of Sigma % 2) %r2 = sqrt(W) * resid; sigma2 = sqrt((1 / (T - k)) * r2' * r2) % 3) % r2 = sqrt(W) * y - sqrt(W) * X * beta; % sigma2 = sqrt((1 / (T - k)) * r2' * r2) % Stuff needed for future iterations iV = W; % for ar p = 0 case A = W; % for ar p = 0 case Phi = 0; betaold = 0; % Steps 2-4: Find the GLS solution by iteration % If p=0, skip this step. Appropriate solution already calculated above. % Continue iteration until convergence or for at most 10 loops. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Note to Jack and Tor: Keith Worsley uses a similar algorithm when fitting % a GLM with an AR(p) noise model. However, he skips the iterative step % and only goes through the loop one time. He claims that this is enough. I % am not entirely convinced, therefore it is probably better to go through % a few times if needed. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% i=1; % Set counter while i < 10 && p > 0 && (i == 1 || sum((beta - betaold).^2) > 0.001) % Do up to 10 iterations, if arp > 0 and either first iteration or % there's a difference from last iteration % resid = y - X*beta; % Calculate residuals of current model fit resid = sqrtW * y - sqrtW * X * beta; % Residuals (Weighted, if weights are used) % Estimate AR parameters using residuals [a,e] = aryule(resid, p); Phi =zeros(length(a)-1,1); Phi(1:p) = -a(2:(p+1)); %sigma = sqrt(e); % swrtW*e?? % Moved later, because never used until %after iteration loop % Find the inverse of the covariance matrix A = sqrtW; % ***should be sqrt(W)? for j=1:p %A = A + diag(-Phi(j)*ones((T-j),1),-j); A = A + sqrtW * diag(-Phi(j)*ones((T-j),1),-j); end; %create_figure('A'); imagesc(A, [-.2 .2]); colorbar, drawnow, input(' ') iV = A*A'; % New weights, with AR estimates, The inverse of the covariance matrix betaold = beta; % Set old solution to be betaold beta = inv(X'*iV*X)*X'*iV*y; % Calculate new solution i = i+1; % Add one to counter %create_figure('COV'); imagesc(iV, [-.02 .02]); colorbar, drawnow, input(' ') end if p > 0 % re-calc sigma sigma = sqrt(e); invxvx = inv(X'*iV*X); % Should we use the kind of thing below? Seems like sqrt(e) is unweighted, % though it seems reasonable... % sqrtiv = sqrt(iV); % resid = sqrtiv * y - sqrtiv * X * beta; % Residuals (Weighted, if weights are used) % sigma = sqrt((1 / (T - k)) * resid' * resid) end R = (eye(T) - X * invxvx * X' * iV); % Residual inducing matrix Wd = R * A * inv(iV) * A'; % inv(iV) = Covariance matrix df = (trace(Wd).^2)./trace(Wd*Wd); % Satterthwaite approximation for degrees of freedom % Should check on below: this creates difference in t-values from % glmfit...but why wouldn't we need to re-calculate a (larger) sigma if we have reduced df? % if df ~= (T - k) % Tor added 3/29, have to re-calculate sigma % sigma = sqrt((1 / df) * resid' * iV * resid); %sum(resid.^2))); % Estimate of Sigma % end varbeta = sigma^2.* invxvx; % Var(beta) stebeta = diag(varbeta).^.5; t = beta ./ stebeta; convals = []; conste = []; con_t = []; con_pvals = []; F = []; if ~isempty(c) % Contrast(s) convals = (c' * beta); conste = diag(c' * varbeta * c).^.5; % tor added as output con_t = convals ./ conste; %sqrt(c'*varbeta*c); % t-value end % get rid of nuisance regressors that aren't in any contrast % wh = stebeta > 0; % c = c(wh,wh); % beta = beta(wh); % stebeta = stebeta(wh); % % if ~isempty(c) % beta = (c' * beta); % %beta = beta(wh); % t = beta ./ stebeta; %sqrt(c'*varbeta*c); % t-value % else % t = beta(wh) ./ stebeta; % end %%%%%% Test H0: beta_1 = beta_2 = .... = beta_param = 0 if nargout > 6 SSE = y'*y - beta'*X'*y; % Error sum of squares mSSE = SSE/df; J = ones(T); SST=y'*y - (1/T).*y'*J*y; % Total sum of squares SSM=SST-SSE; % Model sum of squares dfSSM=length(c) - 1; % degrees of freedom for model (param - 1) mSSM=SSM/dfSSM; F=mSSM/mSSE; % F-statistic - compare with F-distruibution with (param-1, df) degrees of freedom end % % get contrast values if we need those % if ~isempty(c) % beta = (beta' * c)'; % end if nargout > 2 pvals = 2 .* (1 - tcdf(abs(t), df)); % two-tailed % make sure p-values for valid results are not zero... pvals(pvals == 0 & beta ~= 0 & ~isnan(beta)) = 1000*eps; if ~isempty(c) && nargout > 5 con_pvals = 2 .* (1 - tcdf(abs(con_t), df)); % two-tailed con_pvals(con_pvals == 0 & convals ~= 0 & ~isnan(convals)) = 1000*eps; end end return \ No newline at end of file diff --git a/Statistics_tools/fit_gls_brain.m b/Statistics_tools/fit_gls_brain.m new file mode 100644 index 00000000..7ac395cc --- /dev/null +++ b/Statistics_tools/fit_gls_brain.m @@ -0,0 +1,204 @@ +% +% function fit_gls_brain(imgs, X, arorder, conditionnames, maskimg, ['contrasts', contrast_mtx, 'contrastnames', contrastnames], ['weights', weights]) +% +% Run single trial model to get trial amplitude, width, AUC, etc. +% Take one of those (e.g., AUC) and get a list of trial images +% Pass that into this function. +% +% fit_gls_brain(trial_amp_imgs, eventdesign{1}, contrasts +% +% You must add the intercept to X yourself if you want one! +% +% contrasts can be empty, and if it is, this function will write images +% for statistics on individual predictors. If you enter contrast values, +% then it will write images for contrasts instead. +% +% conditionnames = column or contrast image names, in cell array +% e.g. eventnames{1} = {'high' 'medium' 'low' 'warm'} +% +% Output: +% t +% +% Example: +% This one looks @ significance for betas in X model: +% ----------------------------------------------------------------- +% load ../Multilev_mediation-try4(resliced)_10k/mediation_SETUP.mat +% imgs = SETUP.data.M{1}; +% X = eventdesign{1}; +% arorder = 1; +% contrasts = []; +% conditionnames = eventnames{1}; +% maskimg = spm_select(1); % try gray matter mask... +% fit_gls_brain(imgs, X, arorder, contrasts, conditionnames, maskimg) +% +% Now define contrasts and re-run on contrast values: +% contrasts = [3 1 -1 -3; .25 .25 .25 .25; 1 0 0 -1]' +% conditionnames = {'Linearpain' 'Average_resp' 'High-Low'}; +% fit_gls_brain(imgs, X, arorder, contrasts, conditionnames, maskimg) + +function fit_gls_brain(imgs, X, arorder, conditionnames, maskimg, varargin) + + % Optional inputs + % ------------------------------------------------------------- + nobs = size(imgs, 1); + weights = ones(nobs, 1); % default weights + contrasts = []; + contrastnames = {}; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case 'contrasts', contrasts = varargin{i+1}; + + case 'weights', weights = varargin{i+1}; + + case 'contrastnames', contrastnames = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + % Check weights + wtol = max(sqrt(weights))*eps^(1/3); + if any(weights == 0) || any(weights < wtol) + fprintf('Some weights are too low or badly scaled. Setting zero weights to %3.4f\n.', wtol); + weights(weights < wtol) = wtol; + end + + W = diag(weights); + + % [t, df, beta/contrast, Phi, sigma,stebeta, F] = + % fit_gls(y,X,c,p,[PX, equal to pinv(X), for speed]) + + + % Map mask image into space of imgs, if not already, and write + % 'mask.img' + scn_map_image(maskimg, imgs(1, :), 'write', 'mask.img'); + maskimg = 'mask.img'; + + % Setup inputs and function handle + % ------------------------------------------------------------- + wh = intercept(X, 'which'); + if isempty(wh), disp('Warning! No intercept found in model.'); end + + %PX = pinv(X); + PX = inv(X' * W * X) * X' * W; % General weighted + + % Check if X is OK + condx = cond(X); + fprintf('Condition number of design matrix: %3.2f\n', condx); + if condx > 10000, error('Design matrix is not estimable!'); end + + fhandle = @(y) fit_gls(y, X, contrasts, arorder, PX, weights); + + + % Setup output image names + % ------------------------------------------------------------- + % [beta, t, pvals, convals, con_t, con_pvals, sigma, Phi, df, stebeta, conste, F] + + %if isempty(contrasts), betaconname = 'beta'; else betaconname = 'contrast'; end + + names = cell(1, 12); + names{7} = 'sigma.img'; + names{8} = 'phi.img'; + names{9} = 'df.img'; + names{12} = 'omnibus_F.img'; + + for i = [1:6 10:11] % Potential Multi-image outputs + for j = 1:length(conditionnames) + switch i + case 1 + names{i} = char(names{i}, ['beta_' conditionnames{j} '.img']); + case 2 + names{i} = char(names{i}, ['t_' conditionnames{j} '.img']); + case 3 + names{i} = char(names{i}, ['p_' conditionnames{j} '.img']); + + + + case 10 + names{i} = char(names{i}, ['ste_beta_' conditionnames{j} '.img']); + + end + end + + for j = 1:length(contrastnames) + switch i + case 4 + names{i} = char(names{i}, ['con_beta_' contrastnames{j} '.img']); + case 5 + names{i} = char(names{i}, ['con_t_' contrastnames{j} '.img']); + case 6 + names{i} = char(names{i}, ['con_p_' contrastnames{j} '.img']); + case 11 + names{i} = char(names{i}, ['ste_con_' contrastnames{j} '.img']); + end + end + + end + + for i = [1:6 10:11] + names{i} = names{i}(2:end, :); + end + + save fit_gls_brain_SETUP imgs X arorder contrasts names conditionnames contrastnames maskimg + + % Run + % ------------------------------------------------------------- + [beta, t, pvals, convals, con_t, con_pvals, sigma, Phi, df, stebeta, conste, F] = ... + image_eval_function(imgs, fhandle, 'mask', maskimg, 'outimagelabels' , names); + + % other optional args: 'outimagelabels' , 'connames' + +end + + + + +% % % +% % % +% % % [t, df, beta, Phi, sigma, stebeta] = fit_gls(h, trial2ndX, [], 1, trial2ndpx); +% % % +% % % conditiont(i(k), j(k), :) = t; +% % % conditionste(i(k), j(k), :) = stebeta; +% % % conditionmean(i(k), j(k), :) = beta; +% % % trialphi(i(k), j(k), :) = Phi; +% % % +% % % conditionp(i(k), j(k), :) = 2 .* (1 - tcdf(abs(t), df)); +% % % +% % % if ~isempty(c) +% % % stecons = diag(c' * diag(stebeta).^2 * c).^.5; +% % % con_est = c' * beta; +% % % tcons = con_est ./ stecons; +% % % +% % % contrastest(i(k), j(k), :) = con_est; +% % % contrastt(i(k), j(k), :) = tcons; +% % % contrastste(i(k), j(k), :) = stecons; +% % % contrastp(i(k), j(k), :) = 2 .* (1 - tcdf(abs(tcons), df)); +% % % end +% % % +% % % +% % % % 2nd-level estimation of ratings model with AR(1) model +% % % % use first param, ignore intercept +% % % % ------------------------------------------------------------- +% % % if exist('ratings','var') +% % % [t, df, beta, Phi, sigma, stebeta] = fit_gls(h, ratings, [], 1, ratingspx); +% % % +% % % % 1st col. is rating effect +% % % ratingsest(i(k), j(k), 1) = beta(1); +% % % ratingst(i(k), j(k), 1) = t(1); +% % % ratingsste(i(k), j(k), 1) = stebeta(1); +% % % ratingsp(i(k), j(k), 1) = 2 .* (1 - tcdf(abs(t(1)), df)); +% % % +% % % % 2nd column is intercept +% % % intcptest(i(k), j(k), 1) = beta(2); +% % % intcptt(i(k), j(k), 1) = t(2); +% % % intcptste(i(k), j(k), 1) = stebeta(2); +% % % intcptp(i(k), j(k), 1) = 2 .* (1 - tcdf(abs(t(2)), df)); +% % % +% % % end +% % % end +% % % +% % % if k == 1000, fprintf(1, '%3.0f s per 1000 vox.', etime(clock, et)), end diff --git a/Statistics_tools/glmfit_general.m b/Statistics_tools/glmfit_general.m new file mode 100644 index 00000000..c92f55c7 --- /dev/null +++ b/Statistics_tools/glmfit_general.m @@ -0,0 +1,708 @@ +% This function was designed to run a second-level GLS analysis on +% coefficients and variance estimates from a 1st-level model, but it may be +% used generally to implement weighted GLS with options to bootstrap or run +% a sign permutation test to get p-values. +% +% inputs: +% Y = data for n subjects on k variables +% run tests on each column of y independently +% if this is a 2nd-level analysis in a two-level model, Y is coefficients from 1st level (mu-hat) +% +% s2 = estimates of variance (sigma-squared??) for k variables +% +% X = design matrix; include intercept (first) + predictors +% +% Estimation method: +% 'unweighted' / 'weighted' +% 'robust' / 'nonrobust' +% 's2', +% NEW: followed by n-length cell array of var/cov mtx +% (xtxi*sigma2) estimates from previous level +% OLD: followed by n x k matrix of first-level variance estimates +% +% Inference method: +% 't-test' +% 'bootstrap' +% 'signperm' +% +% +% Optional inputs +% 'names' : names of each col. of y: string followed by input +% 'name' : text string tag for analysis +% 'verbose' : print verbose output and tables +% 'nresample' : number of boot/sign perm samples to run; string followed by input +% 'noint' : do not require intercept to be first column of X. +% +% Notes: +% Same as glmfit.m (2007a) without weights.' +% With weights, we estimate w and use Satterthwaite, whereas glmfit uses +% n - k for dfe (assumes correct weights are known.) +% +% Outputs: +% stats.b_star = Weighted fixed effects estimate (if using 'weighted' option) +% stats.Y_star = Y_star'; % Empirical Bayes estimates; don't use for group +% inference, but save; +% if this is run in a context of a multi-level model, these are the individual subjects' +% (first-level) Empirical Bayes slope estimates +% +% Output to screen: +% Coeff is "beta", the effect magnitude +% STE is the standard error, a function of variance and df +% T = beta / STE +% Z = beta / the STE for infinite df +% p +% +% +% % +% % Examples: +% ------------------------------------------------------------------------- +% +% Y = randn(100, 4); X = Y(:,1) + randn(100,1); X = [ones(size(X)) X]; +% stats = glmfit_general(Y, X, 'verbose'); +% stats +% first_lev_var = rand(100, 4); +% +% stats = glmfit_general(Y, X, 'weighted', first_lev_var, 'dfwithin', 20, ... +% 'verbose', 'names', {'DMPFC' 'ACC' 'VMPFC' 'SII'}, ... +% 'beta_names', {'Mean activity' 'Rating covariate'}, ... +% 'analysisname','My Sample ROI analysis'); +% +% + + +function varargout = glmfit_general(Y, X, varargin) + +% ------------------------------------------------------------------------- +% Setup inputs, print info to screen if verbose +% ------------------------------------------------------------------------- + +varargout = cell(1, nargout - 1); + +%if ~exist('varargin', 'var') || isempty(varargin), varargin = {0}; end + +[Y, X, names, analysisname, beta_names, ... + robust_option, weight_option, inference_option, ... + s2within, dfwithin, ... + verbose, dosave, doplots, ... + targetu, nresample, whpvals_for_boot, ... + permsign] = ... + setup_inputs(Y, X, varargin{:}); + +if verbose, totalt = clock; end + + +% ------------------------------------------------------------------------- +% Set up efficient estimator of group mean; used in bootstrap, etc. +% MUST DEFINE these functions AFTER getting X +% ------------------------------------------------------------------------- + +% First level/Single level paths: set handle for mediation function to run +%if dorobust, mediationfun = @fast_robust_ab; else mediationfun = @fast_ols_ab; end + +W = ones(size(Y)) ./ size(Y, 1); % wts. sum to 1, b/c weighted mean function requires this + +% First pass: W will always be all equal weights (all ones) +% Or, if unweighted, skip this step and proceed to main analysis +% ----------------------------------------------------------------- + +switch weight_option + + case 'unweighted' + % do nothing; we already have equal weights (IID case) + btwn_var_est = []; + + case 'weighted' + + + % Determine weights from 1st-level (prior level) variances and + % variance estimates for glm at this level + % ------------------------------------------------------------ + % FIRST PASS - unweighted + %[b, s2between_ols] = GLScalc(Y, W, X, bforming_fcn); + [b, s2between_ols] = scn_stats_helper_functions('gls', Y, W, X); + + + % get weights function + % redefine W + + % OLD: needs to be fixed... + %[W, btwn_var_est] = get_weights_based_on_varcomponents(s2within, s2between_ols, dfwithin, verbose); + + % NEW: based on Raudenbush & Bryk (in progress...) s2within should + % be series of V matrices in cell array + + if verbose, fprintf('R & B - type Empirical Bayes reweighting.\n'); end + + % Y is data, which are presumed to be estimates from a previous + % level of analysis + [Y_star, b, Vgam_hat, gam_t, btwn_var_est, W] = RB_empirical_bayes_params(Y', s2within); + + statsx.b_star = b'; + statsx.b_star_descrip = 'R & B reweighted fixed-effects estimates'; + statsx.Y_star = Y_star'; % individual subjects' (first-level) Empirical Bayes estimates; don't use for group inference, but save + statsx.Y_star_descrip = 'Empirical Bayes estimates of data after re-weighting'; + +end + + + + + +% Main estimation, with whatever weights we have +% Whether analysis is 'weighted' depends on contents of W +% (equal W for unweighted, variable W for weighted.) +% ----------------------------------------------------------------- + +% first get GLS betas and p-values, to determine weights and +% boot samples in bootstrap. +[b, s2between_ols, stats] = scn_stats_helper_functions('gls', Y, W, X); +stats.analysisname = analysisname; + +if exist('statsx', 'var') + FF = fieldnames(statsx); + for i = 1:length(FF), stats.(FF{i}) = statsx.(FF{i}); end +end + +switch inference_option + case 't-test' + %[b, s2between_ols, stats] = GLScalc(Y, W, X, bforming_fcn); + % Nothing further to do. + + case 'bootstrap' + % BOOTcalc + stats = scn_stats_helper_functions('boot', Y, W, X, nresample, stats, whpvals_for_boot, targetu, verbose ); + stats.analysisname = [stats.analysisname '. Bootstrap inference']; + + case 'signperm' + % SIGNcalc + stats = scn_stats_helper_functions('signperm', Y, W, X, nresample, stats, permsign, verbose ); + stats.analysisname = [stats.analysisname '. Sign permutation inference for intercept']; + + otherwise + error('Unknown inference_option.'); + +end + +% Save output structure +stats.btwn_var_est = btwn_var_est; +stats.W = W; + +varnames = {'analysisname', 'names', 'beta_names', 'robust_option', 'weight_option', 'inference_option', 's2within', 'dfwithin', ... + 'targetu', 'nresample', 'whpvals_for_boot', 'permsign', 'Y', 'X'}; + +stats.inputOptions = create_struct(varnames); + +varargout{1} = stats; + + + + +if verbose + + fprintf('Total time: %3.2f s\n', etime(clock, totalt)); + + % print output table + % using stats structure + + scn_stats_helper_functions('print', stats) + +end + + + + +% _________________________________________________________________________ +% +% +% +% * Inline functions +% +% +% +%__________________________________________________________________________ + + + function newstruct = create_struct(varnames) + + newstruct = struct(); + + for i = 1:length(varnames) + eval(['newstruct.(varnames{i}) = ' varnames{i} ';']); + end + + end + + + +end % end main function + + + + + + + + + + + + + + + + + + + + + + +% _________________________________________________________________________ +% +% +% +% * Sub-functions +% +% +% +%__________________________________________________________________________ + + + + +% ------------------------------------------------------------------------- +% Setup inputs, print info to screen if verbose +% ------------------------------------------------------------------------- +function [Y, X, names, analysisname, beta_names, robust_option, weight_option, inference_option, ... + s2within, dfwithin, ... + verbose, dosave, doplots, ... + targetu, nresample, whpvals_for_boot, ... + permsign] = ... + setup_inputs(Y, X, varargin) + +% Initial compliance checks +% ---------------------------------------------------------------- + +% remove NaNs +[nout, Y, X] = nanremove(Y, X); + +[n, k] = size(X); +nvars = size(Y, 2); +nobs_tmp = size(Y, 1); + +% Check sizes +if nobs_tmp ~= n, error('Y and X must have same number of rows!'); end + +if any(strcmp(varargin, 'noint')) + % No intercept +else + % Check intercept + if ~(all(X(:, 1) == X(1))) + error('First column of X must be an intercept column. (e.g., all ones)'); + end +end + +beta_names = cell(1, k); +for i = 1:k + beta_names{i} = sprintf('Predictor %02d', i); +end + + +% Defaults +% ---------------------------------------------------------------- + +% General Defaults +names = cell(1, nvars); % variable names, columns of Y +for i = 1:nvars, names{i} = ['Y' num2str(i)]; end + +analysisname = 'GLS analysis.'; + +% Estimation Defaults +robust_option = 'no'; % robust IRLS; 'no' 'yes' +weight_option = 'unweighted'; % 'weighted' 'unweighted' +s2within = []; +dfwithin = []; + +% Inference defaults +inference_option = 't-test'; % 't-test' 'bootstrap' 'signperm' + +% Display control defaults +verbose = 0; % verbose output +dosave = 0; % save figures at end +doplots = 0; % make plots +savefilename = 'glmfit_general_output.txt'; + +% Bootstrap defaults +targetu = .20; % proportion contribution of boot procedure to p-value +nresample = 1000; % initial bootstrap samples +whpvals_for_boot = 1:size(Y,2); % indices of p-values, the min of which is used to determine boot samples needed +% lower p-values require more boot samples +% for the p-vals to be meaningful. + +% Sign perm defaults +permsign = []; % empty: setup new sign permutation indices +% if entered: keep same permutation matrix +% across repeated calls (much faster! but +% reduces accuracy in simulations!) + +% Inputs +% ---------------------------------------------------------------- +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % General Defaults + case 'names', names = varargin{i+1}; varargin{i+1} = []; + case 'analysisname', analysisname = varargin{i+1}; varargin{i+1} = []; + case 'beta_names', beta_names = varargin{i+1}; varargin{i+1} = []; + + % Estimation Defaults + case 'robust', robust_option = 'yes'; + case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; s2within = varargin{i + 1}; + case 'unweighted' % already got this, do nothing + + case {'dfwithin'}, dfwithin = varargin{i + 1}; + + % Inference defaults + case {'boot1', 'boot', 'bootstrap'}, inference_option = 'bootstrap'; + case {'sign perm', 'signperm', 'sign'}, inference_option = 'signperm'; + case {'t-test', 'ttest'}, inference_option = 't-test'; + + + % Display control defaults + case 'plots', doplots = 1; + case 'noplots', doplots = 0; + + case {'dosave', 'save', 'saveplots'}, dosave = 1; + case 'nosave', dosave = 0; + case 'verbose', verbose = 1; + case 'noverbose', verbose = 0; + case {'savefile', 'savefilename'}, savefilename = varargin{i + 1}; varargin{i+1} = []; + + % Bootstrap defaults + case {'nresample' 'bootsamples', 'nperms'}, nresample = varargin{i+1}; + case {'pvals', 'whpvals_for_boot'}, whpvals_for_boot = varargin{i+1}; + case 'targetu', targetu = varargin{i+1}; %**** not used in output yet! + + % Sign perm defaults + case {'permsign'}, permsign = varargin{i+1}; + + case 'intercept' % do nothing; because we pass this option in from calling function; avoid warning + otherwise + fprintf('Warning! Unknown input string option: %s', varargin{i}); + + end + end +end + +% start diary, if appropriate +if dosave + if verbose + fprintf('Saving output in file %s\n', savefilename) + end + diary(savefilename); + +end + +% Check for conflicting input arguments +% ***IMPLEMENT LATER*** + +if strcmp(weight_option, 'weighted') + if isempty(s2within) || isempty(dfwithin) + error('You must enter ''s2'', s2within, ''dfwithin'', df within as input arguments to use weighted option.'); + end +end + + +if strcmp(robust_option, 'yes') + warning('off', 'stats:statrobustfit:IterationLimit'); + if verbose, disp('Note: Turning off iteration limit warning for robustfit.'); end +end + +if verbose + fprintf('Analysis description: %s \n', analysisname); + fprintf('GLS analysis\n\nObservations: %3.0f, Predictors: %3.0f\n', n, k); + + fprintf('Outcome names: '); + for i = 1:nvars + fprintf('%s\t',names{i}); + end + fprintf(1,'\n'); + + fprintf('Weighting option: %s\nInference option: %s\n\n', weight_option, inference_option); + + nms = {'No' 'Yes'}; + + fprintf('Other Options:\n\tPlots: %s\n\tRobust: %s\n\tSave: %s\n\tOutput file: %s\n\t', ... + nms{doplots+1}, robust_option, nms{dosave+1}, savefilename); +end + + +% Bootstrap / signperm specific options: +% ***implement later*** + +end + + + + + + +% ------------------------------------------------------------------------- +% Get weights from combination of within-ss and btwn-ss variances +% ------------------------------------------------------------------------- +% get_w +% naive estimate of weights, based on 1 / (s2within + s2between) +% runs OLScalc to get s2between + +% ------------------------------------------------------------------------- +% For multilevel model: Get weights +% 1st-level standard errors & variance components +% ------------------------------------------------------------------------- +function [W, btwn_var_est] = get_weights_based_on_varcomponents(s2within, s2between, dfwithin, verbose) + +if verbose, fprintf('\nEstimating variance components based on OLS variance estimates\n'); end + + +% ANOVA approach. MLE estimate; not ReML. +% Use SST = SSB + SSW. Solve for SSB. +% +% dfwithin is within-subjects degrees of freedom, e.g., n. obs per +% subject - no. params est. per subject. e.g., N - k within each subject +% % * must be col. vector +% +% n = number of subjects +% nvars, number of tests +% totaln, total observations; subjects x obs per subject + + +[n] = size(s2within, 1); % obs. x variables + +% make a col. vector +if max(size(dfwithin)) == 1 + dfwithin = ones(n, 1) .* dfwithin; + +elseif isrow(dfwithin) + dfwithin = dfwithin'; + +end + +totaln = sum(dfwithin); % total n obs. within subjects summed over subjects %%sum(n-1); + +SST = totaln .* s2between; % 1 x nvars, one est. for each test (column of Y) + +SSW = dfwithin' * s2within; % 1 x nvars, within-subjects sum sq. errors. dfwithin must be row vector + +btwn_var_est = (SST - SSW) / ( n-1 ); + +btwn_var_est = max(0, btwn_var_est); % restrict to positive values. + + +%stats2.std_beta = stats2.ste .* wistats.avg_sx ./ wistats.avg_sy; +%stats2.btwn_prop = stats2.std_beta ./ (stats2.std_beta + mean(wistats.ste)); + +W = 1 ./ ( s2within + repmat(btwn_var_est, n, 1) ); + +% normalize by sum so weights sum to 1, for consistency +% DOES NOT affect stats. or scaling of betas. + +W = W ./ repmat(sum(W), n, 1); + +end + +% + + + + + + +% % % % +% % % % +% % % % % ------------------------------------------------------------------------- +% % % % % GLS estimation of betas +% % % % % ------------------------------------------------------------------------- +% % % % +% % % % function b = glsfunction(Y, W, X) +% % % % +% % % % [n, k] = size(X); +% % % % nvars = size(Y, 2); +% % % % +% % % % b = zeros(k, nvars); +% % % % +% % % % +% % % % if k == 1 && all(X == X(1)) +% % % % +% % % % % intercept only; fast computation +% % % % % Weighted mean function: +% % % % % Very efficient when there are no predictors other than the intercept +% % % % % Faster than looping, and faster than using mean() for equal weights +% % % % +% % % % b = diag(W'*Y)'; +% % % % +% % % % +% % % % else +% % % % % predictors; need full computation +% % % % % Full GLS function, needed if there are predictors +% % % % +% % % % for i = 1:nvars +% % % % +% % % % Wi = diag(W(:, i)); +% % % % +% % % % b(:, i) = inv(X' * Wi * X) * X' * Wi * Y(:, i); +% % % % +% % % % end +% % % % +% % % % end +% % % % +% % % % end +% % % % +% % % % +% % % % +% % % % % ------------------------------------------------------------------------- +% % % % % GLS estimation of betas, ols variances, and full stats if 3rd output +% % % % % is requested +% % % % % ------------------------------------------------------------------------- +% % % % +% % % % function [b, s2between_ols, stats] = GLScalc(Y, W, X, bforming_fcn) +% % % % % OLScalc +% % % % % calculate group betas, group variance +% % % % % Optional: full inference outputs (takes longer) +% % % % +% % % % +% % % % b = bforming_fcn(Y, W); +% % % % +% % % % % Optional additional computations, in case we want to return just b +% % % % % and go really fast (for bootstrapping) +% % % % +% % % % if nargout > 1 +% % % % +% % % % [n, k] = size(X); +% % % % nvars = size(Y, 2); +% % % % +% % % % % -------------------------------------- +% % % % % * Residuals +% % % % % -------------------------------------- +% % % % +% % % % e = Y - X * b; % residuals +% % % % +% % % % % +% % % % % OLS residual variance +% % % % % If bootstrapping or permuting, use this to get weights +% % % % +% % % % dfe_ols = (n - k) .* ones(1, nvars); +% % % % +% % % % +% % % % s2between_ols = (1 ./ dfe_ols .* sum(e .^ 2)); % Estimates of Sigma^2 for each col. of Y +% % % % +% % % % end +% % % % +% % % % if nargout > 2 +% % % % +% % % % % -------------------------------------- +% % % % % * Degrees of freedom for each column of Y +% % % % % -------------------------------------- +% % % % dfe = dfe_ols; +% % % % +% % % % +% % % % % Get design-related component of STE (includes n) +% % % % % replace dfe with Sattherwaite approx. if necessary +% % % % +% % % % Xste = zeros(k, nvars); +% % % % +% % % % isweighted = 0; +% % % % +% % % % % ***bug here: recomputes s2between too many times...diff +% % % % % numbers... +% % % % for i = 1:nvars +% % % % if all(W(:, i) == W(1, i)) +% % % % % weights are equal +% % % % invxvx = inv(X' * X); +% % % % Xste(:, i) = diag(invxvx); % design-related contribution to variance, k x nvars +% % % % +% % % % % -------------------------------------- +% % % % % * Residual variance +% % % % % -------------------------------------- +% % % % %s2between(i) = diag(e' * e)' ./ dfe(i); % var for each col of Y, 1 x nvars +% % % % s2between(i) = s2between_ols(i); +% % % % +% % % % else +% % % % % all weights are not equal +% % % % isweighted = 1; +% % % % +% % % % Wi = diag(W(:, i)); % Wi = V^-1, inverse of cov.matrix +% % % % +% % % % invxvx = inv(X' * Wi * X); +% % % % +% % % % Xste(:, i) = diag(invxvx); % design-related contribution to variance, k x nvars +% % % % R = Wi.^.5 * (eye(n) - X * invxvx * X' * Wi); % Residual inducing matrix +% % % % +% % % % Q = R * inv(Wi); % Q = RV +% % % % dfe(i) = (trace(Q).^2)./trace(Q * Q); % Satterthwaite approximation for degrees of freedom +% % % % +% % % % % -------------------------------------- +% % % % % * Residual variance +% % % % % -------------------------------------- +% % % % e = R * Y(:, i); % weighted residuals +% % % % s2between(i) = diag(e' * e)' ./ dfe(i); % var for each col of Y, 1 x nvars +% % % % end +% % % % +% % % % end +% % % % +% % % % +% % % % +% % % % % -------------------------------------- +% % % % % * Standard errors of coefficients +% % % % % -------------------------------------- +% % % % sterrs = ( Xste .* repmat(s2between, k, 1) ) .^ .5; +% % % % +% % % % % ------------------------------------------------------------------------- +% % % % % Get statistic structure from OLS regression, including p-values and conf. intervals +% % % % % ------------------------------------------------------------------------- +% % % % +% % % % +% % % % stats.mean = b(1, :); % intercept; mean response +% % % % stats.mean_descrip = 'Intercept of each col. of Y; (mean response if predictors are centered)'; +% % % % +% % % % stats.b = b; +% % % % stats.b_descrip = 'betas (regression coefficients), k predictors x nvars'; +% % % % +% % % % stats.var = s2between; +% % % % stats.var_descrip = 'Residual variance of each col. of Y'; +% % % % +% % % % stats.ste = sterrs; +% % % % stats.ste_descrip = 'Std. error of each beta for each col. of Y, k predictors x nvars'; +% % % % +% % % % stats.t = b ./ sterrs; +% % % % +% % % % stats.dfe = dfe; +% % % % stats.dfe_descrip = 'error DF for each col. of Y, Satterthwaite corrected if necessary; 1 x nvars'; +% % % % +% % % % +% % % % +% % % % stats.e = e; +% % % % if ~isweighted +% % % % stats.e_descrip = 'unweighted (OLS) residuals'; +% % % % else +% % % % stats.e_descrip = 'weighted residuals (resid. from weighted GLS model.)'; +% % % % end +% % % % +% % % % for i = 1:k +% % % % +% % % % stats.p(i, :) = min(1, (2 .* (1 - tcdf(abs(stats.t(i, :)), stats.dfe)))); +% % % % +% % % % stats.p(i, :) = max(stats.p(i, :), eps); +% % % % +% % % % end +% % % % +% % % % stats.p_descrip = 'Two-tailed p-values'; +% % % % +% % % % end +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % end +% % % % + diff --git a/Statistics_tools/glmfit_multilevel.m b/Statistics_tools/glmfit_multilevel.m new file mode 100644 index 00000000..e8608e7b --- /dev/null +++ b/Statistics_tools/glmfit_multilevel.m @@ -0,0 +1,499 @@ +function stats = glmfit_multilevel(Y, X1, X2, varargin) +% stats = glmfit_multilevel(Y, X1, X2, varargin) +% +% Y is data +% - in cell array, one cell per subject +% Column vector of subject outcome data in each cell. +% +% +% X1 and X2 are first and 2nd level design matrices +% - X1 in cell array, one cell per subject +% design matrix for each subject in each cell. +% *columns must code for the same variable for all subjects* +% +% - X2 in rect. matrix +% - can be empty (intercept only) +% +% E.g., with one 2nd-level predictor: +% stats = glmfit_multilevel(Y, X1, X2, ... +% 'names', {'Int' 'Temp'}, 'beta_names', {'2nd-level Intercept (overall group effect)' '2nd-lev predictor: Group membership'}); +% +% OUTPUT +% stats is structure with results +% intercept is always added as first column! +% (do not add intercept to input predictors) +% +% see glmfit_general.m for varargin variable input options. +% +% tor wager, sept. 2007 +% Modified Oct 31, 2008 -- add matrix input format as well as cell +% +% Examples: +% len = 200; sub = 20; +% x = zeros(len,sub); +% x(11:20,:) = 2; % create signal +% x(111:120,:) = 2; +% c = normrnd(0.5,0.1,sub,1); % slope between-subjects variations +% d = normrnd(3,0.2,sub,1); % intercept between-subjects variations +% % Create y: Add between-subjects error (random effects) and measurement noise +% % (within-subjects error) +% for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,0.5,len,1); +% end; +% +% for i = 1:size(y, 2), YY{i} = y(:, i); end +% for i = 1:size(y, 2), XX{i} = x(:, i); end +% +% % one-sample t-test, weighted by inv of btwn + within vars +% stats = glmfit_multilevel(YY, XX, [], 'verbose', 'weighted'); +% +% statsg = glmfit_multilevel(y, x, covti, 'names', {'L1 Intercept' 'L1 Slope'},... +% 'beta_names', {'Group Average', 'L2_Covt'}); +% +% Input options: +% % % +% % % % General Defaults +% % case 'names', Names of first-level predictors, starting +% with 'Intercept', in cell array +% +% % case 'analysisname', analysisname = varargin{i+1}; varargin{i+1} = []; +% % case 'beta_names', beta_names = Names of 2nd-level predictors, starting +% with 'Intercept', in cell array +% % +% % % Estimation Defaults +% % case 'robust', robust_option = 'yes'; +% % case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; +% % +% % % Inference defaults +% % case {'boot1', 'boot', 'bootstrap'}, inference_option = 'bootstrap'; +% % case {'sign perm', 'signperm', 'sign'}, inference_option = 'signperm'; +% % case {'t-test', 'ttest'}, inference_option = 't-test'; +% % +% % % Display control defaults +% % case 'plots', doplots = 1; plotstr = 'plots'; +% % case 'noplots', doplots = 0; plotstr = 'noplots'; +% % +% % case {'dosave', 'save', 'saveplots'}, dosave = 1; savestr = 'save'; +% % case 'verbose', verbose = 1; verbstr = 'verbose'; +% % case 'noverbose', verbose = 0; verbstr = 'noverbose'; +% % +% % case {'savefile', 'savefilename'}, savefilename = varargin{i + 1}; varargin{i+1} = []; +% % +% % % Bootstrap defaults +% % case 'nresample', nresample = varargin{i+1}; +% % case {'pvals', 'whpvals_for_boot'}, whpvals_for_boot = varargin{i+1}; +% % +% % +% % % Sign perm defaults +% % case {'permsign'}, permsign = varargin{i+1}; +% +% Scroll up for examples! + +% Programmer's notes: +% 9/2/09: Tor and Lauren: Edited to drop NaNs within-subject, and drop +% subject only if there are too few observations to estimate. + +if ~iscell(Y) + N = size(Y, 2); + for i = 1:N + YY{i} = Y(:, i); + end + Y = YY; + clear YY +end + +if ~iscell(X1) + N2 = size(X1, 2); + if N ~= N2, error('Sizes of X and Y do not match'); end + for i = 1:N + XX{i} = X1(:, i); + end + X1 = XX; + clear XX +end + +N = length(Y); +if N ~= length(X1) + error('Enter one cell per subject for each of X and Y'); +end + + + + +% first level: SETUP +% ------------------------------------------------------------------- +% set up first-level X matrix (sample) +if any(strcmp(varargin, 'noint')) % no-intercept version + X1tmp = X1{1}; +else + X1tmp = setup_X_matrix(X1{1}); % intercept first +end + +k = size(X1tmp, 2); % num predictors; assumed to be the same!! + + +% Second level: SETUP +% Need to remove 2nd-level units with NaN data at first level +% ------------------------------------------------------------------- +wh_omit = false(1, N); +for i = 1:N + %if any(isnan(Y{i})) || any(isnan(X1{i}(:))), wh_omit(i) = 1; end + + can_be_nans = length(Y{i}) - k - 1; % up to this many can be NaN, still leaving 1 degree of freedom + if can_be_nans < 0, warning('Warning: you might be overparameterized! Seems like you have more predictors than observations'); end + if sum(isnan(Y{i}) | any(isnan(X1{i}), 2)) > can_be_nans + wh_omit(i) = 1; + end +end + +if any(wh_omit) + if isempty(X2), X2 = ones(N, 1); end + + Y(wh_omit) = []; + X1(wh_omit) = []; + X2(wh_omit, :) = []; + N = length(Y); +end + + +beta = zeros(k, N); +sterr = zeros(k, N); +t = zeros(k, N); +p = zeros(k, N); +dfe = zeros(1, N); +%phi = zeros(arorder, N); + +% first level: ESTIMATE +% ------------------------------------------------------------------- +for i = 1:N + + [beta(:, i), sterr(:, i), t(:, i), p(:, i), dfe(:, i), phi(:,i), V{i}] = first_level_model(Y{i}, X1{i}, varargin{:}); + + % V{i} is var/cov matrix (xtxi)*sigmasq +end + +varnames = {'beta' 't' 'p' 'dfe' 'phi'}; +first_level = create_struct(varnames); +first_level.ste = sterr; + + + +% second level: Finish SETUP and ESTIMATE +% ------------------------------------------------------------------- +% set up second-level X matrix: intercept first +X2 = setup_X_matrix(X2, beta(1,:)'); + +% set up second-level options +% names of outcomes become beta_names here b/c second level test on 1st +% level betas +[beta_names1, analysisname, beta_names2, robust_option, weight_option, inference_option, ... + verbose, dosave, doplots, ... + verbstr, savestr, plotstr, ... + targetu, nresample, whpvals_for_boot, ... + permsign] = ... + setup_inputs(beta', X2, varargin{:}); + + + +switch weight_option + case 'weighted' + % Note: R & B-style : replaced sterr' with V + + stats = glmfit_general( ... + beta', X2, ... + 'analysisname', analysisname, 'names', beta_names1, 'beta_names', beta_names2, ... + verbstr, savestr, plotstr, ... + weight_option, V, inference_option, 'dfwithin', dfe', ... + 'targetu', targetu, 'nresample', nresample, ... + 'whpvals_for_boot', whpvals_for_boot, 'permsign', permsign); + + case 'unweighted' + + stats = glmfit_general( ... + beta', X2, ... + 'analysisname', analysisname, 'names', beta_names1, 'beta_names', beta_names2, ... + verbstr, savestr, plotstr, ... + weight_option, inference_option, ... + 'targetu', targetu, 'nresample', nresample, ... + 'whpvals_for_boot', whpvals_for_boot, 'permsign', permsign); + + otherwise + error('uh-oh') +end + +stats.first_level = first_level; + +if doplots + scn_stats_helper_functions('xyplot', X1, Y, weight_option, 'names', beta_names1, 'nostats'); + xlabel('X'); ylabel('Y'); +end + +% _________________________________________________________________________ +% +% +% +% * Inline functions +% +% +% +%__________________________________________________________________________ + + + function newstruct = create_struct(varnames) + + newstruct = struct(); + + for i = 1:length(varnames) + eval(['newstruct.(varnames{i}) = ' varnames{i} ';']); + end + + end + + +end % END MAIN FUNCTION + + + + + +function [b, sterr, t, p, dfe, phi, V] = first_level_model(y, X, varargin) + +% defaults +% ------------------------------------------------------------------- +verbose = 0; +verbstr = 'noverbose'; +arorder = 0; % or Zero for no AR +interceptstr = 'intercept'; + +% optional inputs +% ------------------------------------------------------------------- +for varg = 1:length(varargin) + if ischar(varargin{varg}) + switch varargin{varg} + + % reserved keywords + case 'verbose all', verbose = 1; verbstr = 'verbose'; + case 'verbose', % do nothing + case {'ar', 'arorder'} , arorder = varargin{varg+1}; + %otherwise, disp(['Unknown input string option: ' varargin{varg}]); + + case 'noint', interceptstr = 'noint'; + end + end +end + +k = size(X, 2) + 1; + +[whnan X y] = nanremove(X, y); + +if isempty(X) + % no data + + [b, t, p, sterr] = deal(NaN * ones(k, 1)); + dfe = deal(NaN); + if arorder, phi = NaN * ones(arorder, 1); else phi = NaN; end + V = []; + + return + +end + +% set up X matrix: intercept first +if ~strcmp(interceptstr, 'noint') + X = setup_X_matrix(X, y); +end + +if arorder + % if we have missing observations or redundant columns, let's + % regularize a bit so we can still estimate this, using a ridge prior + % The degree of regularization is arbitrary. + % V is used to estimate variance components and re-weight. + if rank(X) < size(X, 2) + disp('WARNING! RANK DEFICIENT. THIS FUNCTION WILL RETURN AN ERROR.') + X = [X; eye(size(X, 2)) ./ size(X, 1)]; + if ~strcmp(interceptstr, 'noint'), X(:, 1) = 1; end + y = [y; ones(size(X, 2), 1) .* nanmean(y)]; + end + + [t, dfe, b, phi, sigma, sterr] = fit_gls(y, X, [], arorder); + p = 2 * (1 - tcdf(abs(t), dfe)); % two-tailed + + V = inv(X' * X) * sigma .^ 2; % Var/Cov mtx, Precision^-1, used in weighted est. and empirical bayes + +else + % if we have missing observations or redundant columns, let's + % regularize a bit so we can still estimate this, using a ridge prior + % The degree of regularization is arbitrary. + if rank(X) < size(X, 2) + disp('WARNING! RANK DEFICIENT. THIS FUNCTION WILL RETURN AN ERROR.') + X = [X; eye(size(X, 2)) ./ size(X, 1)]; + if ~strcmp(interceptstr, 'noint'), X(:, 1) = 1; end + y = [y; ones(size(X, 2), 1) .* nanmean(y)]; + end + + stats = glmfit_general(y, X, verbstr, interceptstr); + t = stats.t; dfe = stats.dfe; b = stats.beta; phi = NaN; sterr = stats.ste; p = stats.p; + + V = inv(X' * X) * stats.var; % Var/Cov mtx, Precision^-1, used in weighted est. and empirical bayes +end + + + +end + + + + + +function X = setup_X_matrix(X, y) + +% set up X matrix: intercept first +[n, k] = size(X); + +if n == 0, n = size(y, 1); end + +equal_x = false(1, k); + +for i = 1:k + + if all(X(:, i) == X(1, i)) + % weights are equal + equal_x(i) = 1; + + end + +end + +if any(equal_x) + disp('Warning: some columns of X have no variance. Do not enter intercept in X; it will be added automatically as the first predictor.'); + X(:, equal_x) = []; +end + +X = [ones(n, 1) X]; + +end + + + + + + +% ------------------------------------------------------------------------- +% Setup inputs, print info to screen if verbose +% THIS IS FOR SECOND-LEVEL MODEL -- NOT FIRST +% ------------------------------------------------------------------------- +function [names, analysisname, beta_names, robust_option, weight_option, inference_option, ... + verbose, dosave, doplots, ... + verbstr, savestr, plotstr, ... + targetu, nresample, whpvals_for_boot, ... + permsign] = ... + setup_inputs(Y, X, varargin) + +% Initial compliance checks +% ---------------------------------------------------------------- +[n, k] = size(X); +nvars = size(Y, 2); +nobs_tmp = size(Y, 1); + +% Check sizes +if nobs_tmp ~= n, error('Y and X must have same number of rows!'); end + +% Check intercept +if ~(all(X(:, 1) == X(1))) + error('First column of X must be an intercept column. (e.g., all ones)'); +end + +beta_names = cell(1, k); +for i = 1:k + beta_names{i} = sprintf('2nd-level B%02d', i); +end + + +% Defaults +% ---------------------------------------------------------------- + +% General Defaults +names = cell(1, nvars); % variable names, columns of Y +for i = 1:nvars, names{i} = ['1st-level B' num2str(i)]; end + +analysisname = 'Second Level of Multilevel Model '; + +% Estimation Defaults +robust_option = 'no'; % robust IRLS; 'no' 'yes' +weight_option = 'unweighted'; % 'weighted' 'unweighted' + +% Inference defaults +inference_option = 't-test'; % 't-test' 'bootstrap' 'signperm' + +% Display control defaults +verbose = 1; % verbose output +dosave = 0; % save figures at end +doplots = 0; % make plots +plotstr = 'noplots'; +savestr = 'nosave'; +verbstr = 'verbose'; + +savefilename = 'glmfit_general_output.txt'; + +% Bootstrap defaults +targetu = .20; % proportion contribution of boot procedure to p-value +nresample = 1000; % initial bootstrap samples +whpvals_for_boot = 1:size(Y,2); % indices of p-values, the min of which is used to determine boot samples needed +% lower p-values require more boot samples +% for the p-vals to be meaningful. + +% Sign perm defaults +permsign = []; % empty: setup new sign permutation indices +% if entered: keep same permutation matrix +% across repeated calls (much faster! but +% reduces accuracy in simulations!) + +% Inputs +% ---------------------------------------------------------------- +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % General Defaults + case 'names', names = varargin{i+1}; varargin{i+1} = []; + case 'analysisname', analysisname = varargin{i+1}; varargin{i+1} = []; + case 'beta_names', beta_names = varargin{i+1}; varargin{i+1} = []; + + % Estimation Defaults + case 'robust', robust_option = 'yes'; + case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; + + % Inference defaults + case {'boot1', 'boot', 'bootstrap'}, inference_option = 'bootstrap'; + case {'sign perm', 'signperm', 'sign'}, inference_option = 'signperm'; + case {'t-test', 'ttest'}, inference_option = 't-test'; + + % Display control defaults + case {'plot', 'plots'}, doplots = 1; plotstr = 'plots'; + case {'noplots', 'noplot'}, doplots = 0; plotstr = 'noplots'; + + case {'dosave', 'save', 'saveplots'}, dosave = 1; savestr = 'save'; + case 'verbose', verbose = 1; verbstr = 'verbose'; + case 'noverbose', verbose = 0; verbstr = 'noverbose'; + + case {'savefile', 'savefilename'}, savefilename = varargin{i + 1}; varargin{i+1} = []; + + % Bootstrap defaults + case 'nresample', nresample = varargin{i+1}; + case {'pvals', 'whpvals_for_boot'}, whpvals_for_boot = varargin{i+1}; + + + % Sign perm defaults + case {'permsign'}, permsign = varargin{i+1}; + + case 'intercept' + + otherwise + fprintf('Warning! Unknown input string option: %s', varargin{i}); + + end + end +end + +% all the checking, etc. is done in glmfit_general + + +end \ No newline at end of file diff --git a/Statistics_tools/intercept.m b/Statistics_tools/intercept.m new file mode 100644 index 00000000..5a77a573 --- /dev/null +++ b/Statistics_tools/intercept.m @@ -0,0 +1,45 @@ +function X = intercept(X, meth) +% +% Intercept-related functions for working with design matrices, etc. +% +% Return which columns are intercept(s), if any +% wh = intercept(X, 'which') +% +% Remove an intercept, if there is one +% X = intercept(X, 'remove'); +% +% Add an intercept to the end +% X = intercept(X, 'add'); +% +% Ensure that the intercept is at the end, moving or adding it as necessary +% X = intercept(X, 'end'); +% +% which column is the intercept? +wh_is_intercept = find( ~any(diff(X)) ); + +switch meth + + case 'which' + X = wh_is_intercept; + + case 'remove' + + if ~isempty( wh_is_intercept ), X(:, wh_is_intercept) = []; end + + case 'add' + + X(:, end+1) = 1; + + case 'end' + + X = intercept(X, 'remove'); + X = intercept(X, 'add'); + + otherwise + + error('Unknown method'); + +end + +end + \ No newline at end of file diff --git a/Statistics_tools/loess_multilevel.m b/Statistics_tools/loess_multilevel.m new file mode 100644 index 00000000..4b554d67 --- /dev/null +++ b/Statistics_tools/loess_multilevel.m @@ -0,0 +1,132 @@ +function stats = loess_multilevel(X, Y, varargin) +% stats = loess_multilevel(X, Y, varargin) +% +% Options: +% case 'fit_and_average', meth = 'fit_and_average'; +% case 'regularization', regularization = varargin{i+1}; +% case 'order', loessorder = varargin{i+1}; +% case {'robust', 'dorobust'}, dorobust = varargin{i+1}; +% case {'nboot', 'bootsamples'}, nboot = varargin{i+1}; +% case 'plot', doplot = 1; +% case {'plotall'}, plotsummary = 0; doplot = 1; +% case 'color', color = varargin{i+1}; +% case {'existingfig', 'samefig'}, newfig = 0; +% Tor Wager; see also scn_stats_helper_functions +% +% Examples: +% stats = loess_multilevel(models{i}.X, models{i}.Y, 'regularization', .8, 'order', 1, 'color', [.8 .3 0], 'plot', 'samefig'); + +if ~exist('loess.m', 'file') + disp('You must have loess.m from the Dataviz toolbox on your path.'); + return +end + +N = length(X); + +regularization = .8; +loessorder = 1; % 1 or 2, linear or quadratic +dorobust = 0; % 0 or 1, bisquare weighting +doplot = 0; +plotsummary = 1; +color = 'b'; +newfig = 1; +meth = 'bootstrap'; % pool and bootstrap; alternative: fit individually and average, 'fit_and_average' +nboot = 30; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'fit_and_average', meth = 'fit_and_average'; + case 'regularization', regularization = varargin{i+1}; + case 'order', loessorder = varargin{i+1}; + case {'robust', 'dorobust'}, dorobust = varargin{i+1}; + case {'nboot', 'bootsamples'}, nboot = varargin{i+1}; + case 'plot', doplot = 1; + case {'plotall'}, plotsummary = 0; doplot = 1; + case 'color', color = varargin{i+1}; + case {'existingfig', 'samefig'}, newfig = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +if doplot + if newfig, create_figure('X - Y plot'); end + + if ~plotsummary + for i = 1:N + hh(i) = plot(X{i}, Y{i}, 'ko', 'MarkerSize', 4); + end + end + +end + +% Set range +% ------------------------------------- +allx = cat(1, X{:}); +allx = allx(:); +x = [min(allx) max(allx)]; +clear allx +xx = linspace(x(1), x(2), 100); + +% Get Loess fits +% ------------------------------------- +switch meth + case 'bootstrap' + X = double(cat(1, X{:})); + Y = double(cat(1, Y{:})); + + bootsam = setup_boot_samples(X, nboot); + + %matlabpool open; % only works with the Parallel Computing Toolbox + opt = statset('UseParallel','always'); + + fhan = @(x, y) loess(x, y, xx, regularization, loessorder, dorobust); + + fprintf('Bootstrapping %3.0f samples. ', nboot); + t1 = tic; + yy = bootstrp_havesamples(bootsam, fhan, X, Y); + toc(t1); + + case 'fit_and_average' + for i = 1:N + + yy(i, :) = loess(X{i}, Y{i}, xx, regularization, loessorder, dorobust); + + % avoid crazy extrapolation -- limit prediction to range of data + wh_out = xx < min(X{i}) | xx > max(X{i}); + yy(i, wh_out) = NaN; + + if doplot && ~plotsummary + plot(xx, yy(i,:)) + end + + end +end + + +stats.fit_mean = nanmean(yy)'; + +switch meth + case 'bootstrap' + stats.fit_se = std(yy)'; % std of bootstrap dist is ste + case 'fit_and_average' + stats.fit_se = ste(yy)'; +end + +stats.fit_sd = nanstd(yy)'; +stats.fit_indiv = yy; +stats.xvals = xx; + +if doplot + stats.error_fill_handle = fill_around_line(stats.fit_mean, stats.fit_se, color, stats.xvals); + stats.line_handle = plot(stats.xvals, stats.fit_mean, 'Color', color, 'LineWidth', 3); + stats.error_fill_handle = plot(stats.xvals, stats.fit_mean+stats.fit_se, 'Color', color, 'LineWidth', .5); + stats.error_fill_handle = [stats.error_fill_handle plot(stats.xvals, stats.fit_mean-stats.fit_se, 'Color', color, 'LineWidth', .5)]; +end + +end diff --git a/Statistics_tools/matrix_eval_function.m b/Statistics_tools/matrix_eval_function.m new file mode 100644 index 00000000..3b28ba05 --- /dev/null +++ b/Statistics_tools/matrix_eval_function.m @@ -0,0 +1,223 @@ +function varargout = matrix_eval_function(Y,fhandle,varargin) + % varargout = matrix_eval_function(Y,fhandle,varargin) + % evaluate any arbitrary function fhandle on each column of an input matrix Y + % + % varargout = matrix_eval_function(Y,fhandle,varargin) + % evaluate fhandle on paired columns of X and Y + % + % fhandle is a function handle: + % fhandle = @(variable inputs) fit_gls(variable_input,fixed_inputs); + % fhandle = @(y) fit_gls(y,X,c,p,PX); + % + % Function is fhandle should return (multiple possible) outputs, each + % in a row vector. Matrix outputs of this function are Y-cols x output + % values for each output argument. + % + % Example: Generalized least squares fitting on 100 Y-variables, same X + % --------------------------------------------------------------------- + % y = rand(100,100); X = y + rand(100,1); X(:,end+1) = 1; c = [1 0]'; p = 2; PX = pinv(X); + % fhandle = @(y) fit_gls(y,X,c,p,PX); + % [t, df, beta, Phi, sigma,stebeta, F] = fhandle(y); + % + % tor wager, jan 31, 2007 + % + + varargout = {}; + + if nargout == 0, disp('No outputs requested.'); return, end + + % % ------------------------------------------------------------------- + % % Setup variable X and check + % define n and v, # observations and # variables + % % ------------------------------------------------------------------- + setup_inputs_and_check_sizes; + + whichGood = find(whichGood); + + if isempty(whichGood), error('No valid variables with non-zero variance to be analyzed.'); end + + t1 = clock; + + % do this to make sure these are returned so they may be used in later + % inline + n; v; + + % % ------------------------------------------------------------------- + % % build string to evaluate that defines outputs and inputs + % % ------------------------------------------------------------------- + build_eval_string(nargout); + + % % ------------------------------------------------------------------- + % % initialize outputs with "Good" data vector (those that have + % variability) + % we need to do this to get sizes of each output, which vary depending + % on the input function. + % then we can skip some vars that have no Y variance. + % also make sure we can store everything in memory. + % % ------------------------------------------------------------------- + i = whichGood(1); + eval(fstr); + for i = 1:nargout + varargout{i} = NaN .* zeros(v,size(varargout{i},2)); + end + + + % % ------------------------------------------------------------------- + % % Evaluate vectors with "Good" data + % % ------------------------------------------------------------------- + + if doverbose + % print banner and define text update points + print_banner; + end + + for i = whichGood + % only run for Y-columns that have some variance; otherwise return + % NaN + + % evaluate the function for this variable (column) + eval(fstr); + + if doverbose + % print string + update = ( updateiterations == i ); + if any(update), fprintf(1,'\b\b\b\b%3d%%',updateperc(update)); end + end + + end + + + if doverbose + fprintf(1,'\n_______________________________\n') + endt = etime(clock,t1); + endt = endt ./ 60; + fprintf(1,'\nDone in %3.0f hrs, %3.0f min, with %3.3f s per variable \n',floor(endt./60),rem(endt,60),endt.*60./v); + end + + + %END OF MAIN FUNCTION CODE + + + % ------------------------------------------------------------------- + % + % + % INLINE FUNCTIONS + % + % + % + % ------------------------------------------------------------------- + + % % ------------------------------------------------------------------- + % % Setup inputs, including variable X and check + % % ------------------------------------------------------------------- + function setup_inputs_and_check_sizes + dochk = 1; + doverbose = 1; + do_variable_X = 0; + if length(varargin) > 0 + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'verbose','verb'}, doverbose = 1; + case {'noverb','noverbose'}, doverbose = 0; + case {'nochk'}, dochk = 0; + otherwise + disp('Warning: unrecognized string input argument.'); + end + else + X = varargin{i}; + do_variable_X = 1; + end + end + end + + [n,v] = size(Y); + whichGood = true(1,v); % OK analysis voxels + + if dochk + [val,whichGood] = no_variance(Y); + if val, disp('Warning: Some Y vectors have no variability!'); end + end + + if do_variable_X + + if isempty(X), error('X should be omitted or be matrix of size(Y)'); end + [n2,k] = size(X); + + if dochk + if n ~= n2, error('data and model sizes do not match.'); end + if v ~= k, error('data and model sizes do not match.'); end + end + + + end + + + + end + + + % % fstr = ['[t, df, beta, Phi, sigma,stebeta, F] = fhandle(y);']; + % % eval(fstr) + + + % % ------------------------------------------------------------------- + % % build string to evaluate that defines outputs and inputs + % % ------------------------------------------------------------------- + function build_eval_string(numargs) + fstr = '['; + for arg = 1:numargs + fstr = [fstr 'varargout{' num2str(arg) '}(i,:)']; + if arg ~= numargs + fstr = [fstr ',']; + else + fstr = [fstr '] = fhandle(double(Y(:,i)));']; + end + end + + + end + + + % % ------------------------------------------------------------------- + % % print banner and define update points for text output + % % ------------------------------------------------------------------- + function print_banner + + fprintf(1,'matrix_eval_function.m') + fprintf(1,'\n_______________________________\n') + fprintf(1,'Evaluating this function on %3.0f variables:\n',v); + disp(fhandle); + fprintf(1,'...using this command:\n%s\n',fstr); + fprintf(1,'_______________________________\n') + str = sprintf('Running ... Done %03d%%',0); fprintf(1,str); + updateiterations = 1:round(v ./ 100):v; + updateperc = round(linspace(0,100,length(updateiterations))); + end + +end % END MAIN FUNCTION + + + + +% % ------------------------------------------------------------------- +% % Sub-functions +% % ------------------------------------------------------------------- + +function val = no_intercept(X) + + val = all(any(diff(X,1,1),1)); + +end + +function [val,whichGood] = no_variance(Y) + + whichGood = any(diff(Y,1,1),1); + val = ~all(whichGood); + +end + +function erase_string(str1) + fprintf(1,repmat('\b',1,length(str1))); % erase string +end + diff --git a/Statistics_tools/mediansplit.m b/Statistics_tools/mediansplit.m new file mode 100644 index 00000000..1e816c5f --- /dev/null +++ b/Statistics_tools/mediansplit.m @@ -0,0 +1,12 @@ +function y = mediansplit(y) +% y = mediansplit(y) + +wh = find(y > median(y)); + +y = -1 * ones(size(y)); +y(wh) = 1; + +% sometimes this can return all -1s or all 1s + + +return \ No newline at end of file diff --git a/Statistics_tools/monotonic_regression.m b/Statistics_tools/monotonic_regression.m new file mode 100644 index 00000000..4060680e --- /dev/null +++ b/Statistics_tools/monotonic_regression.m @@ -0,0 +1,66 @@ +function [yhat,yhat_sorted,err,goodness,mymad] = monotonic_regression(x,y,doplot) + % monotonic_regression(x,y,doplot) +% see lsqisotonic.m +% +% tor wager, nov. 06 +% +% mymad is median absolute deviation from isotonic regression line +% goodness was mad(yhat) / mymad : ratio of mean values to error +% is now % variance explained by fit (r-square) + +if nargin < 3, doplot = 0; end + +if doplot + figure('Color','w');plot(x,y,'ko','MarkerFaceColor','k') +end + +n = numel(x); + +[xyord,ord] = sortrows([x(:) y(:)]); +iord(ord) = 1:n; +xyord = double(xyord); +% Initialize fitted values to the given values. +yhat = xyord(:,2); +w = ones(size(yhat)); + +if(n <= intmax('uint16')) + dtconv = @uint16; +elseif(n <= intmax('uint32')) + dtconv = @uint32; +else + dtconv = @double; +end + +block = dtconv(1:n); + + +while true + % If all blocks are monotonic, then we're done. + diffs = diff(yhat); + if all(diffs >= 0), break; end + + % Otherwise, merge blocks of non-increasing fitted values, and set the + % fitted value within each block equal to a constant, the weighted mean + % of values in that block. + idx = dtconv(cumsum([1; (diffs>0)])); + sumyhat = accumarray(idx,w.*yhat); + w = accumarray(idx,w); + yhat = sumyhat ./ w; + block = idx(block); +end + + +yhat_sorted = yhat(block); + +if doplot + hold on; plot(xyord(:,1),yhat_sorted,'ro-','LineWidth',2); +end + +yhat = reshape(yhat_sorted(iord), size(y)); +err = y - yhat; +if nargout > 4, mymad = mad(err); end +%goodness = mad(yhat) ./ mymad; +vv = var(y); +goodness = 1 - (var(err) ./ vv); + +return \ No newline at end of file diff --git a/Statistics_tools/moving_average.m b/Statistics_tools/moving_average.m new file mode 100644 index 00000000..d1291471 --- /dev/null +++ b/Statistics_tools/moving_average.m @@ -0,0 +1,54 @@ +function y = moving_average(meth,data,varargin) +% +% Symmetrical moving average filters +% (matlab's internal moving avg filter functions are asymmetric) +% +% Works on each column of the data input matrix +% +% y = moving_average('gaussian',data,fwhm) +% note: fwhm is not actually fhwm now...it's related to width though +% (temporary) +% y = moving_average('gaussian',data,20); + +nshift = 0; + +% set up the kernel +% ------------------------------------------- +switch meth + + case 'gaussian' + + ntrials = varargin{1}; + kern = normpdf(-3:6/ntrials:3); + kern = kern./sum(kern); + + mymax = find(kern == max(kern)); + nshift = mymax - 1; % kernel shifts by n points; adjust + + + otherwise error('Unknown method.') + +end + +% filter +% ------------------------------------------- +[nobs,ncols] = size(data); + +if nobs < nshift, error('Not enough observations to support kernel.'); end +y = zeros(nobs,ncols); + + +for i = 1:ncols + + % pad data at beginning and end to avoid edge artifacts + padbegin = data(nshift:-1:1, i); + paddat = data(end:-1:end-nshift,i); + tmpy = conv([padbegin; data(:,i); paddat],kern); + + y(:,i) = tmpy(nshift + nshift+1:nshift + nshift + nobs); + +end + + + +return diff --git a/Statistics_tools/noise_arp.m b/Statistics_tools/noise_arp.m new file mode 100644 index 00000000..1fed9418 --- /dev/null +++ b/Statistics_tools/noise_arp.m @@ -0,0 +1,38 @@ +function [w,e] = noise_arp(n, phi, sigma, e) + % w = noise_arp(n, phi) + % + % Generate n-length AR(p) noise vector w + % given phi (default = [.5 .1]) + % Based on Gaussian noise process e with standard deviation sigma (default = 1) + % + % w will have a variance greater than that of the underlyling gaussian + % process e + % + % Martin Lindquist / Tor Wager, Feb 2007 + + if nargin < 2 + phi = [0.5 .1]; + end + + if nargin < 3 + sigma = 1; + end + + % number of AR parameters (model order): + p = length(phi); + + if nargin < 4 + % underlying gaussian noise process: + e = normrnd(0,sigma,n,1); + end + + % w is colored noise: + w = e; + + for i= (p+1):n + arpart = phi * w(i-1 : -1 : i-p); + + w(i) = arpart + e(i); + end + +end \ No newline at end of file diff --git a/Statistics_tools/nonlin_fit.m b/Statistics_tools/nonlin_fit.m new file mode 100644 index 00000000..8693d500 --- /dev/null +++ b/Statistics_tools/nonlin_fit.m @@ -0,0 +1,396 @@ +function [p,errval,fit,linkfun,fhan] = nonlin_fit(y,x,varargin) +% Purpose: +% Multi-purpose nonlinear fitting to data +% +% Author and date / revision info: +% Tor Wager, Dec. 6, 2006 % created +% Tor Dec. 10 % last modified +% +% Function definition: +% [p,errval,fit] = nonlin_fit(y,x,['link',linktype],['err',objtype],['start',startp], ... +% ['plot'],['verbose'],[noverbose'],['quickstart'],['smartstart']) +% +% (Optional arguments are in [ ]) +% +% _________________________________________________________________ +% Arguments: +% y data vector +% x vector of x-values +% linktype functional form to fit to data. options are: +% 'sigmoid' 'exp0' 'exp2' 'exp3' 'exps' +% objtype error measures to be minimized. options are: +% 'sse' : sums of squared errors +% 'mad' : median absolute deviation (more robust to outliers) +% 'plot' Create plot of data and fit +% startp Vector of starting parameters for minimization +% _________________________________________________________________ +% +% Functions +% 'sigmoid' +% 'exp1', 'exp2', 'exp3' Exponential function +% 'exp1' y = e^(ax) with free parameter a +% 'exp2' y = b*e^(ax) with free a, b +% 'exp3' y = c + b*e^(ax) with free a, b, c +% +% Power functions: 'pow0' 'pow2' 'pow3' 'pows1' 'pows2' 'pows' +% 'pows' a + -(bx)^(-c) with params a, b, c +% simplifies to 1 - (1/x) with all p = 1 +% range: (for + b and c) -Inf at x = 0, to asymptote = a at x = Inf. +% +% Usage: +% Default behavior: sigmoid fit, SSE objective: +% [p,sse,fit] = nonlin_fit(y,x,'plot'); +% +% Specify sigmoid function and SSE objective, starting at params [2 1 1]: +% [p,sse,fit] = nonlin_fit(y,x,'start',[2 1 1],'link','sigmoid','err','sse'); +% +% Specify median absolute deviation error minimization (more robust): +% [p,sse,fit] = nonlin_fit(y,x,'err','mad','plot'); +% +% Fit and add a fit line on a graph +% [p,errval,fit,linkfun,fhan] = nonlin_fit(y,x,'linktype','exps','start',[1.2 .5 .8]); +% fitx = 1:.1:5; fitline = fhan(p,fitx); +% hold on; plot(fitx,fitline,'r'); +% +% Extended examples +% _________________________________________________________________ +% Example: sigmoid fit to y +% Generate fake data: +% +% x = -5:.01:5; +% sigmoid = inline('p(1) .* ( 1 ./ (1 + p(2)*exp(-p(3)*x)) )','p','x'); +% y = sigmoid([2 1 1.5],x); +% y = y + .3 .* randn(1,size(y,2)) ; +% +% Fit, starting at param values [1 1 1]: +% [p,sse,fit] = nonlin_fit(y,x,'plot','start',[1 1 1]); +% +% Bit more complicated data +% x2 = -5:.01:5; +% y = sigmoid([2 1 .5],x2); +% y = [y 2*ones(1,100)]; +% y = y + .3 .* randn(1,size(y,2)) ; +% x = 1:length(y); +% +% Exponential function (see also exp1, exp2, exp3 keywords) +% expfun = inline('p(1)*exp(p(2)*x)','p','x'); % the exponential function +% +% % simulated sample data +% x = -1:.01:5; +% y = expfun([.5 .7], x); +% y = y + 1 .* randn(1,size(y,2)); +% +% funhandle = @(p, x) expfun(p, x); % function handle to pass in +% [p,sse,fit] = nonlin_fit(y,x,'linktype', funhandle, 'plot','start',[1 1]); +% +% Then, try to fit one-parameter exponential model to the same data: +% [p,sse,fit] = nonlin_fit(y,x,'linktype', 'exp1', 'plot','start', 1); +% _________________________________________________________________ +% +% +% to-do ideas: +% rsquare and other output stats in output structure +% convergence flag : run 10 times w/random starting points and assess +% convergence +% sobol : sample space of starting params with sobol sequence +% verbose output table with verbose flag +% + +% setup defaults and inputs +% _________________________________________________________________ +[linktype,objtype,startp,doplot,doverbose,dosmartstart] = setup_inputs; +startp; + + +% specify link function, transform of x to fit to y +% _________________________________________________________________ +[fhan, linkfun] = get_link_function(); + + +% specify error (objective) function +% function to be minimized +% _________________________________________________________________ + +objfun = get_objective_function(); + + + +% get starting parameter estimates: +% input, default, or 'smartstart' (specific for function) + +startp = get_start_estimates(startp); + +% run minimization of errfun +% find parameters p +% _________________________________________________________________ +options = optimset('Display','off','MaxIter',100000); % ,%'LevenbergMarquardt','on'); + +PROBLEM = struct('objective',objfun,'x0',startp,'options',options,'solver','fminsearch'); + +[p,errval,exitflag,output] = fminsearch(PROBLEM); +% Note: output could be used for verbose reporting and problem checking + + +% get additional outputs: fits, errors, etc. +% _________________________________________________________________ +fit = fhan(p,x); + +if ~isreal(objfun(p)), disp('Error is not a real number!'); end +if isinf(objfun(p)), disp('Error is infinite!'); end +if exitflag == 0, disp('Max function evaluations reached!'); end + +if doplot + + figure('Color','w'); + plot(x,y,'o','MarkerFaceColor',[.5 .5 .5],'MarkerSize',6); + + % this only works for some functions + fitx = min(x):.1:max(x); fitline = fhan(p,fitx); + hold on; plot(fitx,fitline,'k','LineWidth',2); + +end + + +%%% END MAIN FUNCTION %%% + + + + + + + + + + + + + +% Nested functions + + +% _________________________________________________________________ + +% setup defaults and inputs +% _________________________________________________________________ + function [linktype,objtype,startp,doplot,doverbose,dosmartstart] = setup_inputs + + doplot = 0; + doverbose = 1; + dosmartstart = 1; + linktype = 'sigmoid'; + objtype = 'sse'; + startp = [1 1 1]; + + for i = 1:length(varargin) + if isstr(varargin{i}) + switch lower(varargin{i}) + % reserved keywords + case 'plot', doplot = 1; + case {'smart','smartstart'}, dosmartstart = 1; + case {'quick','quickstart'}, dosmartstart = 0; + case {'verb','verbose'}, doverbose = 1; + case {'noverb','noverbose'}, doverbose = 0; + + % functional commands + case {'link','linktype'}, linktype = varargin{i+1}; varargin{i+1} = []; + case 'err', objtype = varargin{i+1}; varargin{i+1} = []; + case 'start', startp = varargin{i+1}; + + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + end + +if doverbose + % check Matlab version + ok = checkMatlabVersion('8/3/2006'); +end + + +% _________________________________________________________________ + +% specify link function, transform of x to fit to y +% _________________________________________________________________ +% + function [fhan, linkfun] = get_link_function + + if strcmp(class(linktype),'function_handle') + % it's already a custom function handle. + fhan = linktype; + linkfun = linktype; + + else + % p is a vector of parameters; x is the x-data + + switch linktype + + case 'sigmoid' + % sigmoid, 3-parameter: amplitude, scale (shift), exponent (steepness) + %linkfun = inline('p(1) .* ( 1 ./ (1 + p(2)*exp(-p(3)*x)) )','p','x'); + % altered: ( 1 ./ (1 + exp(-p(3)*(x - p(2)))) ) + + % time-shifted sigmoid: params are amplitude, shift, and exponent + % (steepness) + linkfun = inline('p(1) .* ( 1 ./ (1 + exp(-p(3)*(x - p(2)))) )','p','x'); + + + + case {'pow0','power0'} + linkfun = inline('x.^p(1)','p','x'); + + case {'pow2','power2'} + linkfun = inline('p(1) .* x.^p(2)','p','x'); + + case {'pow3','power3'} + linkfun = inline('p(1) + p(2) .* x.^p(3)','p','x'); + + case {'pows1'} + linkfun = inline('3.5 + -(.005.*x).^-p(1)','p','x'); + + case {'pows2'} + linkfun = inline('1 + -(p(1).*x).^-p(2)','p','x'); + + case {'pows'} + linkfun = inline('p(1) + -(p(2).*x).^-p(3)','p','x'); + % simplifies to 1 - (1/x) with all p = 1 + + + case {'exp1','exponential1'} + linkfun = inline('exp(p(1)*x)','p','x'); + + case {'exp2','exponential2'} + linkfun = inline('p(2) .* exp(p(1)*x)','p','x'); + + case {'exp3','exponential3'} + linkfun = inline('p(3) + p(2) .* exp(p(1)*x)','p','x'); + + + otherwise + error('Unknown string command for objective function.') + end + + % create a handle to the link function, to pass into error function + %fhan = @(p) linkfun(p,x); + fhan = @(p,x) linkfun(p,x); + + end + end + + + +% _________________________________________________________________ + +% specify error (objective) function +% function to be minimized +% _________________________________________________________________ + function objfun = get_objective_function + + switch objtype + + case 'sse' + % sums of squared errors (standard) + % this would be a way to call this function directly + % sse = objfun(y,x,fhan,p); + + % Command-line call form: + % objfun = @(y,x,fhan,p) sum((y - fhan(p,x)).^2); + + % fminsearch call form: + objfun = @(p) sum((y - fhan(p,x)).^2); + %objfun = @(p) sum((y - fhan(p,x)).^2); + + % Notes: + % fminsearch needs a single-argument function + % but if your objfun has multiple arguments, fminsearch + % will use values of variables in existing workspace + % as long as their names in the definition of objfun + % are the same as those in the workspace. + % that is why there are different command-line and fminsearch call forms + % for objfun. + + % old + %myerrfun = inline( 'sum((y - myfun(p,x)).^2)','y','x','myfun','p' ); + %objfun = @(p) myerrfun(y,x,fhan,p); + + case 'mad' + objfun = @(p) mad(y - fhan(p,x)); + + otherwise + error('Unknown string command for objective function.') + end + + end + + +% _________________________________________________________________ + +% get starting parameter estimates: +% input, default, or 'smartstart' (specific for function) +% _________________________________________________________________ + + + function startp = get_start_estimates(startp) + + % return if user-input + if exist('startp','var') + return + end + +% if ~ischar(linktype) +% startp = []; +% return +% end + + switch linktype + + case 'sigmoid' + + % quick-start, default + startp = [1 1 1]; + + if dosmartstart + + len = length(y); + % get central 50% of x-values + iq = iqr(x)./2; med = median(x); + st = floor(med - iq); en = ceil(med + iq); + + for i = st:en + dy(i) = sum(y(1:i))./i - sum(y(i+1:end))./(len-i); + end + [mmymax,wh] = max(abs(dy)); + + + % shift param + startp(2) = x(wh); + + % amplitude param + startp(1) = abs( sum(y(1:wh))./wh - sum(y(wh+1:end))./(len-wh) ); + + end + + case {'exp3','exponential3','exps'} + startp = [1 1 1]; + + case {'exp2','exponential2','exps2'} + startp = [1 1]; + + case {'exp0','exponential0','exps1'} + startp = [1]; + + otherwise + error('Unknown Link Type in get_start_estimates subfcn.') + end + end + + + +end % end main function + + + + + diff --git a/Statistics_tools/nonlin_param_mod_brain.m b/Statistics_tools/nonlin_param_mod_brain.m new file mode 100644 index 00000000..bbe13c6b --- /dev/null +++ b/Statistics_tools/nonlin_param_mod_brain.m @@ -0,0 +1,192 @@ +function nonlin_param_mod_brain(ons, modulator, image_names, SETUP, varargin) + % nonlin_param_mod_brain(X, image_names, SETUP, [SETUPional inputs]) + % + % Nonlinear fits with a parametric modulator on a set of brain images + % + % Inputs + % ------------------------------------------------ + % ons onsets for each condition; one cell per + % condition, one col. vector per series of onsets + % modulator modulator values for each condition; same + % format as above + % image_names outcome variable; Images (volume names) for each subject, in + % string matrix (list of image names); 3-D for + % now! + % + % SETUP.(fields) + % .mask name of mask image + % .preprocX flag for whether to HP filter X data + % .preprocY flag for whether to HP filter Y data + % 'nopreproc' Turn off preproc + % + % .TR repetition time of volume (image) acquisition + % .HPlength high-pass filter length, in s + % .scans_per_session vector of # volumes in each run, e.g., [128 128 128 128 128] + % .dummyscans indices of images in each run that will be modeled + % with separate dummy variables + % .startslice starting slice number (to resume analysis) + % SETUPional inputs: + % Any of the SETUPions in mediation.m + % Also: 'nopreproc' to skip preprocessing (i.e., for trial-level inputs) + % + % Tor Wager, May 2008 + % + % Examples: + + + + + % --------------------------------------------------------------------- + % Set up preprocessing + % To skip, enter 'nopreproc' as var. arg. + % --------------------------------------------------------------------- + + [preprochandle, SETUP] = filter_setup(SETUP, varargin{:}); + + % SETUPional: preproc Y? Should do mostly only if not brain + % Should not do if using trial-level estimates + + N = size(image_names, 1); % number of subjects; one cell per subject + + % do high-pass filter preprocessing on X, if specified + if SETUP.preprocX, for i = 1:N, X{i} = preprochandle(X{i}); end, end + + if ~SETUP.preprocY + preprochandle = []; + + else + tmp = cell(1, N); + for i = 1:N, tmp{i} = preprochandle; end + preprochandle = tmp; + end + + tr = SETUP.TR; + + + % --------------------------------------------------------------------- + % Set up mask + % --------------------------------------------------------------------- + SETUP.mask_unresampled = SETUP.mask; + disp('Writing resampled mask.img in current directory.'); + + scn_map_image(SETUP.mask, image_names(1, :), 'write', 'mask.img'); + SETUP.mask = fullfile(pwd, 'mask.img'); + + % --------------------------------------------------------------------- + % Set up analysis + % --------------------------------------------------------------------- + + % THIS stuff is the same for each voxel + % -------------------------------------- + modulator_centered = modulator - nanmean(modulator); + xvals = (1:N)'; + + + % This runs the whole thing given a data vector (y) + fhandle = @(y) nonlin_param_modulator(y, ons, modulator_centered, tr, xvals); + + + SETUP.names = {'amplitude_mean.img' 'amplitude_by_modulator.img' 'duration_mean.img' 'duration_by_modulator.img' ... + 'intercept.img' 'auc_mean_trial.img' ... + 'auc_by_modulator.img' 'variance.img' }; + + SETUP.preprochandle = preprochandle; + SETUP.fhandle = fhandle; + + SETUP.data.descrip = 'Data after any preprocessing specified (for X and Y)'; + SETUP.data.ons = ons; + SETUP.data.modulator = modulator; + SETUP.data.modulator_centered = modulator_centered; + SETUP.data.image_names = image_names; + save nonlin_param_mod_SETUP SETUP + + % --------------------------------------------------------------------- + % Run preprocessing and analysis + % --------------------------------------------------------------------- + if ~isfield(SETUP, 'startslice') || isempty(SETUP.startslice), SETUP.startslice = 1; end + + [a, b, c, d, e, f, g, h] = image_eval_function(image_names, fhandle, 'mask', SETUP.mask, 'preprochandle', preprochandle, 'outnames', SETUP.names, 'start', SETUP.startslice); + + + + +end % End Main Function + + + + + +% Set up preprocessing +function [preprochandle, SETUP] = filter_setup(SETUP, varargin) + + preprochandle = []; + wh_elim = []; + hpflag = 1; % only does it if requested, though + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'custompreproc' + preprochandle = varargin{i + 1}; % e.g., 'custompreproc', @(data) scale(data) for z=scores; + + hpflag = 0; + SETUP.TR = NaN; + SETUP.HPlength = []; + SETUP.dummyscans = []; + wh_elim = i; + + case {'nopreproc'} + hpflag = 0; + SETUP.preprocY = 0; SETUP.preprocX = 0; + if ~isfield(SETUP, 'TR') || isempty(SETUP.TR), SETUP.TR = NaN; end + SETUP.HPlength = []; + SETUP.dummyscans = []; + wh_elim = i; + + % We need to allow mediation SETUPions here, so eliminate this from list and do not error check here. + %otherwise, warning(['Unknown input string SETUPion:' varargin{i}]); + end + end + end + + varargin(wh_elim) = []; + + N = fieldnames(SETUP); + for i = 1:length(N) + if ~isfield(SETUP, N{i}) || isempty(SETUP.(N{i})) + switch N{i} + case {'TR', 'mask', 'scans_per_session', 'preprocY'} + error(['Enter SETUP.' N{i}]); + + case 'HPlength' + SETUP.(N{i}) = []; + + case 'dummyscans' + SETUP.(N{i}) = 1:2; + + otherwise + disp('Warning! Unrecognized field in SETUPions structure SETUP.'); + end + end + end + + SETUP.preproc_any = SETUP.preprocX || SETUP.preprocY + + if ~isfield(SETUP, 'TR') || isempty(SETUP.TR) || isnan(SETUP.TR) + error('SETUP.TR must be entered.'); + end + + if SETUP.preproc_any && hpflag + + + error('THIS CODE IS NOT SET UP TO PREPROCESS YET.'); + + [tmp, I, S] = hpfilter(X(:,1), SETUP.TR, SETUP.HPlength, SETUP.scans_per_session, SETUP.dummyscans); % creates intercept and smoothing matrices + + preprochandle = @(Y) hpfilter(Y, [], S, SETUP.scans_per_session, I); % function handle with embedded fixed inputs + + end + +end + diff --git a/Statistics_tools/nonlin_param_modulator.m b/Statistics_tools/nonlin_param_modulator.m new file mode 100644 index 00000000..525690ca --- /dev/null +++ b/Statistics_tools/nonlin_param_modulator.m @@ -0,0 +1,89 @@ +function [h_mean h_by_mod d_mean d_by_mod intcpt auc_mean_trial auc_by_modulator errval] = nonlin_param_modulator(y, ons, modulator_centered, tr, xvals) + % + % [h_mean h_by_mod d_mean d_by_mod intcpt auc_mean_trial auc_by_modulator errval] = nonlin_param_modulator(y, ons, modulator_centered, tr, xvals) + % + % USE THIS with nonlin_param_mod_brain.m + % This function takes data (y) and other things (onsets, etc.) and + % produces parameter estimates for amplitude, duration, amp*modulator, + % dur*modulator + % + % See rt_fit_brain.m for more info, and for examples creating fit + % plots, etc. + % + % Example: + % y = rand(360, 1); + % [h_mean h_by_mod d_mean d_by_mod intcpt auc_mean_trial auc_by_modulator errval] = nonlin_param_modulator(y, ons, scale(RTs, 1), 2); + % nonlin_parammod_predfun(y, ons, scale(RTs), [h_mean h_by_mod d_mean d_by_mod intcpt], 'plot'); + % hold on; plot(y, 'k') + % + % Tor Wager, May 2008 + + % % % THIS stuff is the same for each voxel + % % % -------------------------------------- + % % RTcenter = RTs - mean(RTs); + % % xvals = (1:length(y))'; + % % + % % % fitting function: times is a dummy var to + % % % get this to work with nonlin_fit + % % fhan = @(p, times) nonlin_parammod_predfun(y,ons,RTcenter,p); + % % + % % fitting_fun = @(y) nonlin_fit(y, xvals, 'link', fhan, 'start',[1 1 1 1 mean(y)]); + + if nargin < 5 || isempty(xvals) + xvals = (1:length(y))'; + end + + if nargin < 4 || isempty(tr) + disp('Using default TR of 1 s'); + tr = 1; + end + + + % fitting function: times is a dummy var to + % get this to work with nonlin_fit + fhan = @(p, times) nonlin_parammod_predfun(y, ons, modulator_centered, p, 'tr', tr); + + % This gives you param estimates, given a data vector + %fitting_fun = @(y) nonlin_fit(y, xvals, 'link', fhan, 'start',[1 0 1 0 mean(y)]); + + % This stuff is different for each voxel + [p, errval] = nonlin_fit(y, xvals, 'link', fhan, 'start',[1 0 1 0 mean(y)]); %fitting_fun(y); + + h_mean = p(1); + h_by_mod = p(2); + d_mean = p(3); + d_by_mod = p(4); + + intcpt = p(5); + + auc_mean_trial = p(1) * p(3); % height * width = area of average response + + % with unit param. modulator increase, what is change in AUC? + % Height(PM = 1) * Width(PM = 1) - auc_mean_trial + + auc_by_modulator = (p(1) + p(2) .* 1) * (p(3) + p(4) .* 1) - auc_mean_trial; + + +end + + + +% +% true_p = [1 1.5 3 1.5 0]; % true parameters +% y = fhan(true_p); % generate simulated "true" signal +% [p, errval, fit] = fitting_fun(y); +% true_p +% p +% create_figure('test'); plot(y); hold on; plot(fit, 'r'); + +%p = lsqcurvefit(fhan,[1 1 1 1 .5], xvals, y) + +% p = params +% 1) mag. scale (amplitude) +% 2) mag. X RT slope (linear effect of RT) +% 3) duration intercept (neural epoch duration) +% 4) duration x RT slope (linear effect of RT on duration) +% 5) overall fitted response intercept + + + diff --git a/Statistics_tools/nonlin_parammod_predfun.m b/Statistics_tools/nonlin_parammod_predfun.m new file mode 100644 index 00000000..4fcb74e3 --- /dev/null +++ b/Statistics_tools/nonlin_parammod_predfun.m @@ -0,0 +1,271 @@ +function [yhat,yhi,loTime,highTime,x] = nonlin_parammod_predfun(y,ons,pm_vals,p,varargin) +% Predict data given onsets, parametric modulator values, and parameter +% estimates for effects of events and PMs on the magnitude and delay of an +% estimated neural 'boxcar.' +% +% A standard HRF model is assumed, and the parameters are estimates of how +% trials affect the 'neural' stimulus function. +% +% This function replaces nonlin_parammod_predfun.m, which is obsolete +% +% [yhat,yhi,loTime,highTime,x] = nonlin_parammod_predfun(y,ons,pm_vals,p,varargin) +% +% +% p = params +% 1) mag. scale (amplitude) +% 2) mag. X RT slope (linear effect of RT) +% 3) duration intercept (neural epoch duration) +% 4) duration x RT slope (linear effect of RT on duration) +% 5) overall fitted response intercept +% +% INPUTS: +% y, data; can be dummy data, needs only be the correct size +% This function does not actually fit the data, so y is only used +% to get the correct vector size +% +% ons, onsets (in samples) +% pm_vals, modulator values, preferably centered +% p, parameter values; see below +% varargin: optional inputs, see below +% +% Special cases of parameter sets +% ------------------------------------------------------------------------- +% p = [1 0 1 0 0]; % "Impulse model" : fixed boxcar of 1 s +% +% p = [1 0 3 0 0]; % "Epoch model" : fixed boxcar of 3 s +% +% p = [1 1 1 0 0]; % "Parametric modulator" : Impulse height modulated by +% RT (with centered pm_vals) +% +% p = [1 0 0 1 0]; % "Variable epoch": convolve RT with duration +% (with non-centered, raw pm_vals). Parametric +% modulation of duration +% +% p = [1 1 1 1 0]; % combo of Parametric and Duration modulators of +% impulses +% +% p = [1 1.5 3 1.5 0]; % combo of Parametric and Duration modulators of +% a 3-s epoch model +% +% event signal magnitude = p1 * p2*RT % was previously: RT^p2 +% event signal duration = p3 + pm_vals * p4 +% +% OPTIONAL INPUTS +% ------------------------------------------------------------------------- +% case 'random', dorandom = 1; +% case 'plot', doplot = 1; +% +% case 'hrf', hrf = varargin{i + 1}; % HIGH_RES HRF +% case 'tr', followed by TR +% +% tor wager, jan 07 +% +% Examples of generating predicted BOLD timeseries: +% ------------------------------------------------------------------------ +% y = rand(330,1); +% ons = [1:20:320]'; pm_vals = rand(size(ons)); +% [yhat,yhi] = nonlin_parammod_predfun(y,ons,pm_vals,[1 1 1 0 0],'plot'); % linear RT modulation of height +% [yhat,yhi] = nonlin_parammod_predfun(y,ons,pm_vals,[1 0 1 10 0],'plot'); % linear RT modulation of duration +% +% [yhat,yhi] = nonlin_parammod_predfun(y,ons,pm_vals,[.5 1 1 0 0],'plot'); % other linear RT modulation of height +% [yhat,yhi] = nonlin_parammod_predfun(y,ons,pm_vals,[.5 .7 1 5 0],'plot'); % saturated RT modulation of height; linear width +% +% Example of fitting observed timeseries and estimating parameters: +% ------------------------------------------------------------------------ +% * SEE ALSO nonlin_param_modulator +% +% RTcenter = pm_vals - mean(pm_vals); +% true_p = [1 1.5 3 1.5 0]; % true parameters +% xvals = (1:length(y))'; +% +% % fitting function: times is a dummy var to +% % get this to work with nonlin_fit +% fhan = @(p, times) nonlin_parammod_predfun(y,ons,RTcenter,p); +% +% y = fhan(true_p); % generate simulated "true" signal +% +% fitting_fun = @(y) nonlin_fit(y, xvals, 'link', fhan, 'start',[1 1 1 1 .5]); +% [p, errval, fit] = fitting_fun(y) % get parameters for a timeseries of +% % interest +% +% [p,errval,fit] = nonlin_fit(y,(1:length(y))','link',fhan,'start',[1 1 1 1 .5]); +% hold on; plot(fit,'r'); +% +% A second example using the genetic algorithm: +% ------------------------------------------------------------------------ +% objfun = @(p) sum(abs(y - fhan(p))); % objective function: absolute error +% start = [-10 -10 0 -10 0]; start(:,:,2) = [10 10 10 10 2000]; +% [best_params,fit,beff,in] = tor_ga(200,50,{start},objfun,'genconverge',5,'noverbose'); +% %***note: does not work now, needs debugging*** +% +% Simulation: Test cov of param estimates +% ------------------------------------------------------------------------ +% Run 1000 times to see cov. of param estimates +% Right now: ****development: p(1,2) are highly + corr, p(3:4) are high - +% corr +% should probably choose one or the other for each. +% for i = 1:1000 +% yi = y + randn(length(y),1) * 10; % new noise +% p(i,:) = fitting_fun(yi); +% if mod(i,10) == 0, fprintf(1,' %3.0f',i); end +% end +% +% Note: Could create linear basis set of plausible forms + +if ~isrow(pm_vals), pm_vals = pm_vals'; end + +loResolution = 1; % in seconds (this is the TR) +hiResolution =.1; % in seconds +relSampRate = round(loResolution./hiResolution); +runDuration = 0; + +% setup inputs +dorandom = 0; +doplot = 0; +if length(varargin) > 0 + for i = 1:length(varargin) + if ischar(varargin{i}) + switch lower(varargin{i}) + case 'random', dorandom = 1; + case 'plot', doplot = 1; + + case 'hrf', hrf = varargin{i + 1}; + + case 'tr' + loResolution = varargin{i + 1}; + relSampRate = round(loResolution./hiResolution); + + end + end + end +end + +if dorandom + setup_random; +else + setup_vars; +end + +% m, magnitude exponent +% p(2) = 2) mag. X RT exponent +% magnitude = p(1) .* pm_vals .^ p(2); % p(2) .^ pm_vals; %pm_vals .^ p(2); +% duration = relSampRate .* (p(3) + p(4) .* pm_vals); % in hi-res samples ; could be .* pm_vals, but then neg. durations are possible? + +% want RT effects to be independent from overall trial effects +% with centered pm_vals, this should be accomplished by: + +magnitude = p(1) + p(2) .* pm_vals; % .^ p(2); +duration = relSampRate .* (p(3) + p(4) .* pm_vals); + +% but pm_vals must be positive to use exponents, e.g., pm_vals .^ p(2), otherwise fractional p's will +% give imaginary numbers. + +% specify 'neural' boxcars based on mag and dur +xbox = get_boxcars(ons, x, magnitude, duration); + +if length(hrf) > length(xbox), hrf = hrf(1:length(xbox)); end + +% convolve +yhi = p(5) + fast_conv_fft(hrf, xbox); + +if length(yhi) < runDuration + yhi = pad(yhi, runDuration); +elseif length(yhi) > runDuration + yhi = yhi(1:runDuration); +end + +% downsample and add intercept +yhat = yhi(1:relSampRate:end); + +n = length(y); +if length(yhat) > n, yhat = yhat(1:n); end + +if doplot || nargout > 2 + loTime = (1:length(y))' .* loResolution - 1; +end + +if doplot + create_figure('Predicted fMRI Response'); + barht = max(yhi) ./ (4.*max(x)); + + %tor_fig; + plot(highTime,barht.*x,'k',highTime,yhi,'b',loTime,yhat,'r'); + bar(highTime,barht.*x,'FaceColor',[.4 .4 .4],'EdgeColor','none'); +end + +% END MAIN FUNCTION + + + function setup_random + runDuration=330; % in seconds + highTime=linspace(0, runDuration, runDuration/hiResolution)'; + + hrf=gam(highTime,.2,.5,3,-.06,3,7,0); + hrf=hrf/max(hrf); + + % get onsets + numTrials = 20; + x = zeros(3300,1); + ons = randperm(length(x)); ons = ons(1:numTrials); + x(ons) = 1; + + % % z = zeros(length(hrf),1); z(1) = hrf(1); + % % HRF = toeplitz(z,hrf); + + % get pm_vals + a=2.5; + b=.13; + minRT=.5; + pm_vals=gamrnd(a,b,numTrials,1)+minRT; + + end + + + function setup_vars + + runDuration = relSampRate .* length(y); % in elements + x = zeros(runDuration,1); + + % ons entered in TRs, convert to hi-res + % would multiply by 1./hiRate if entered in s + ons = round(relSampRate .* ons); + ons(ons == 0) = 1; % onsets at zero -> first high-res sample + + % setup HRF + highTime = (0:runDuration-1)' .* hiResolution; %linspace(0, runDuration, runDuration/hiResolution)'; + + %hrf=gam(highTime,.2,.5,3,-.06,3,7,0); + if ~exist('hrf', 'var') + hrf = spm_hrf(hiResolution); + hrf = hrf/max(hrf); + end + + hrf = pad(hrf,x); + + % Check + if length(pm_vals) ~= length(ons) + error('Length of pm_vals and length of ons must match.'); + end + + end + + +end % END MAIN FUNCTION + + +function x = get_boxcars(ons,x,magnitude,duration) + +duration = round(duration); + +% x is high-res indicator + +for i = 1:length(ons) + % for each onset + st = ons(i); + en = ons(i) + duration(i) - 1; + + x(st : en) = magnitude(i); +end + + +end \ No newline at end of file diff --git a/Statistics_tools/pairwise_diffs.m b/Statistics_tools/pairwise_diffs.m new file mode 100644 index 00000000..f8562d9f --- /dev/null +++ b/Statistics_tools/pairwise_diffs.m @@ -0,0 +1,65 @@ +function d = pairwise_diffs(x, varargin) +% Pairwise operations on columns of a matrix x, for each row +% +% d = pairwise_diffs(x, [function handle]) +% +% Inputs and outputs: +% ------------------------------------------------------------------------ +% Inputs: +% x, an n x k matrix +% optional: a function handle for the operation to perform on pairwise +% elements of x(i, :) +% +% Outputs: +% if x is n x k, d is n x (k*(k-1)/2) +% columns of x are arranged this way, e.g., with k = 5: +% [x(1, 1) - x(1, 4) x(1, 1) - x(1, 5) x(1, 2) - x(1, 3) ...] +% +% squareform(d(1, :)) is a matrix of pairwise diffs for d +% +% Examples: +% ------------------------------------------------------------------------ +% x = magic(5) % generate data +% d = pairwise_diffs(x); % d = pairwise differences +% row1 = squareform(d(1, :)); % square matrix of pairwise diffs for row 1 +% +% d = pairwise_diffs(x, @(a, b) a + b); % return pairwise sum instead +% +% Tor Wager, Nov 2012 + +% default: difference operator +fhan = @(a, b) a - b; + +if length(varargin) > 0, fhan = varargin{1}; end + +for i = 1:size(x, 1) + + d(i, :) = get_result(x(i, :), fhan); + +end + + +end % function + + +% Subfunctions + +function d = get_result(x, fhan) + + n = length(x); + indx = 1; + + for i = 1:n + + for j = i+1:n + + d(1, indx) = fhan(x(i), x(j)); + indx = indx + 1; + + end + + end + + +end + diff --git a/Statistics_tools/partition_variables_indevel.m b/Statistics_tools/partition_variables_indevel.m new file mode 100644 index 00000000..f7533712 --- /dev/null +++ b/Statistics_tools/partition_variables_indevel.m @@ -0,0 +1,276 @@ +function best_classes = partition_variables_indevel(x, k, obsk) + % + % + % partition n x v matrix into k classes + % maximize within-class condition number or minimize cov. determinant + % minimize between-class condition number or maximize cov. determinant... + + % simulate two-class data + % nvars = 100; nsubj = 20; corval = .5; + % S = eye(nvars./2) + corval*(1 - eye(nvars./2)); S = blkdiag(S,S); S(S==0) = corval; x = mvnrnd(zeros(1,nvars), S, nsubj); det((corrcoef(x))) + % figure; imagesc(corrcoef(x)); colorbar + + + % create population of possible solutions + % k^v possible solutions, where v is variables and k is classes + % we can only do this with very small sets of variables + + if nargin < 3, obsk = 1; end % classes of observations + v = size(x,2); + npossible = k ^ v; + + + if npossible <= 10 ^ 5 + fprintf(1,'Could do exhaustive search\n'); + clist = repmat({1:k}, 1, v); + pop = combvec(clist{:}); + end + + % ------------------------------------------------------------ + % set up GA + % ------------------------------------------------------------ + gensize = 1000; + numgen = 30; + fprintf(1,'Running GA with %3.0f organisms and %3.0f generations.\n', gensize, numgen); + + % this defines the fitness function to maximize + % the variable input parameter is class assignment + % other parameters (data and max # of classes) are fixed + if obsk == 1 + objective_function = @(class) mean_correl_fitness(class, x, k); + else + objective_function = @(class) twoway_correl_fitness(class, x, k); + end + + inputs{1} = ones(v, 1); + inputs{1}(1:k) = (1:k)'; + + % ------------------------------------------------------------ + % run GA + % ------------------------------------------------------------ + + [best_classes,fit,beff,in] = tor_ga(gensize,numgen,inputs,objective_function,'discrete'); + best_classes = best_classes{1}; + + if obsk > 1 + + n = size(x,1); % # observations + + best_obsclasses = best_classes(1:n); + best_classes = best_classes(n+1:end); + + end + + % ------------------------------------------------------------ + % plots, etc. + % ------------------------------------------------------------ + %%%fit = determinant_fitness(class, x, k) + [bestsort, wh] = sort(best_classes); + xsort = x(:, wh); + + classavg = []; + for i = unique(best_classes)' + classavg(:,end+1) = mean(x(:, best_classes == i),2); + end + + figure; subplot(3,2,1) + imagesc(xsort); xlabel('Variables'); ylabel('Cases'); + subplot(3,2,2); + imagesc(classavg); xlabel('Class'); ylabel('Cases'); + subplot(3,2,3); + imagesc(bestsort'); xlabel('Class'); + + subplot(3,2,5); + imagesc(corrcoef(xsort)); xlabel('Variables'); ylabel('Variables'); + + + + figure; subplot(3,2,1) + imagesc(x); xlabel('Variables'); ylabel('Cases'); + subplot(3,2,2); + imagesc(classavg); xlabel('Class'); ylabel('Cases'); + subplot(3,2,3); + imagesc(best_classes'); xlabel('Class'); + + subplot(3,2,5); + imagesc(corrcoef(x)); xlabel('Variables'); ylabel('Variables'); + + +end + + + + + + +function fit = determinant_fitness(class, x, k) + % higher is better + + + c = corrcoef(x); + + bindx = 1; + + % get non-empty classes; these are omitted from wiclass and btclass + % they do not influence fitness score at all + + valid_classes = unique(class)'; + n_valid_classes = length(valid_classes); + + if iscol(valid_classes), valid_classes = valid_classes'; end + + % initialize this to ones so empty classes contribute -log(1) = 0 to + % overall fitness + wiclass = ones(1, k); + + btclass = []; + + for i = valid_classes % for each class + wh = class == i; + + wiclass(i) = det(c(wh,wh)); % determinant of within-class vars; minimize this. range: of det: btwn 0 and 1 + + for j = i+1 : k + + if any(valid_classes == j) + % if this class is valid + whj = class == j; + + btwncorr = abs(c(wh,whj)); + + btclass(bindx) = -mean(log(btwncorr(:))); % product of between-class vars; minimize this; maximize -log + % don't know how to scale this relative to wiclass; range of mean: btwn 0 and 1 + + bindx = bindx + 1; + + else + % skip + end + end + + end + + wiclass = -log(wiclass); + + nbtwn_els = (bindx - 1); + fit = sum([wiclass ./ n_valid_classes btclass ./ nbtwn_els]); + +end + + + + + +function fit = twoway_correl_fitness(class, x, k) + % class is concatenated: observation class, then variable class + + v = size(x,2); % # variables + + n = length(class) - v; % # observations + + obsclass = class(1:n); + class = class(n+1:end); + + valid_classes = unique(obsclass)'; + + for i = valid_classes + + whobs = obsclass == i; % which observations for this obs. grouping + fit(i) = mean_correl_fitness(class, x(whobs, :), k); + + npooled(i) = sum(whobs); + end + + fit = sum(fit .* npooled) ./ sum(npooled); + +end + + + + + + +function fit = mean_correl_fitness(class, x, k) + % higher is better + + c = corrcoef(x); + + % ------------------------------------------------------------ + % setup + % ------------------------------------------------------------ + + bindx = 1; + + % get non-empty classes; these are omitted from wiclass and btclass + % they do not influence fitness score at all + + valid_classes = unique(class)'; + n_valid_classes = length(valid_classes); + + if iscol(valid_classes), valid_classes = valid_classes'; end + + % initialize this to zeros so empty classes 0 to + % overall fitness + wiclass = zeros(1, k); + nwi = zeros(1, k); + + btclass = []; + + % ------------------------------------------------------------ + % loop through classes + % ------------------------------------------------------------ + + for i = valid_classes % for each class + + % within-class + % ------------------------------------------------------------ + wh = class == i; + + wicorr = c(wh,wh); + nwi(i) = sum(wh); + + if nwi(i) == 1 + wiclass(i) = 1; + else + wicorr = wicorr - eye(size(wicorr)); + wicorr = squareform(wicorr); + + wiclass(i) = mean(wicorr); % correl of within-class vars; maximize this. + end + + for j = i+1 : k + % between-class + % ------------------------------------------------------------ + + if any(valid_classes == j) + % if this class is valid + whj = class == j; + + btwncorr = c(wh,whj); + btwncorr = btwncorr(:); + + btclass(bindx) = mean(btwncorr); % condition # of btwn-class vars; minimize this. + nbt(bindx) = length(btwncorr); + + bindx = bindx + 1; + + else + % skip + end + end + + end + + % finish up + % ------------------------------------------------------------ + + nbtwn_els = (bindx - 1); + + wifit = sum( wiclass .* nwi ) ./ sum(nwi); % pos is good, zero or neg is bad , range -1 - 1 + %btfit = sum( abs(btclass) .* nbt ) ./ sum(nbt); % zero is good, pos or neg is bad , range 0 - 1 + btfit = sum( (btclass) .* nbt ) ./ sum(nbt); % neg better than 0 better than pos + + % total range: -2 to 1; random start should be around 0 + fit = wifit - btfit; + +end \ No newline at end of file diff --git a/Statistics_tools/permute_setupperms.m b/Statistics_tools/permute_setupperms.m new file mode 100644 index 00000000..25f37c7b --- /dev/null +++ b/Statistics_tools/permute_setupperms.m @@ -0,0 +1,122 @@ +function permindx = permute_setupperms(n,nperms) +% +% Set up permutations x observations matrix of observation indices for +% permutation test +% +% Uses code from SnPM3b by Tom Nichols +% adapted for this function by Tor Wager +% +% permindx = permute_setupperms(n,nperms) % approximate test with nperms obs. +% permindx = permute_setupperms(n,[]) +% +% permindx = permute_setupperms(n) % exact test (all permutations) of +% n observations +% +% + +if nargin < 2 || isempty(nperms) || nperms == 0 + OUT.meth = 'exact'; + nperms = gamma(n+1); + use_approximate = 0; +else + nperms = min(gamma(n+1),nperms); + use_approximate = 1; + + if nperms > 50000 + warning('More than 50,000 permutations! May cause time/memory problems'); + end +end + +% interactive +% % nperms = gamma(n+1); +% % use_approximate = input(sprintf('%d Perms. Enter return for exact test, or num. of perms in random subset: ',nperms)); +% % if isempty(use_approximate) +% % use_approximate = 0; +% % else +% % nperms = min(use_approximate,nperms); +% % use_approximate = 1; +% % end + +if use_approximate + % taken from snpm3b code, by Tom Nichols + %-Approximate test : + % Build up random subset of all (within n) permutations + %=============================================================== + rand('seed',sum(100*clock)) %-Initialise random number generator + permindx = zeros(nperms,n); + permindx(1,:) = 1+rem(0:(n-1), n); + + for i = 2:nperms+1 + permindx(i,:) = randperm(n); + end + b = unique(permindx, 'rows'); + if(size(b,1) ~= size(permindx, 1)) + while(size(b,1) ~= size(permindx, 1)) + permindx = b; + for i = (size(permindx,1)+1):nperms+1 + permindx(i,:) = randperm(n); + end + b = unique(permindx, 'rows'); + end + permindx = [b(1,:); b(randperm(size(b,1)-1)+1,:)]; + end + + % null permutation is not included in perm indx + permindx = permindx(2:end,:); +else + % taken from snpm3b code, by Tom Nichols + % + %-Full permutation test : + % Build up exhaustive matrix of permutations + %=============================================================== + %-Compute permutations for a single exchangability block + %--------------------------------------------------------------- + %-Initialise Xblkpermindx & remaining numbers + Xblkpermindx = []; + lef = [1:n]'; + %-Loop through numbers left to add to permutations, accumulating permindx + for i = n:-1:1 + %-Expand Xblkpermindx & lef + tmp = round(exp(gammaln(n+1)-gammaln(i+1))); + Exp = meshgrid(1:tmp,1:i); Exp = Exp(:)'; + if ~isempty(Xblkpermindx), Xblkpermindx = Xblkpermindx(:,Exp); end + lef = lef(:,Exp); + %-Work out sampling for lef + tmp1 = round(exp(gammaln(n+1)-gammaln(i+1))); + tmp2 = round(exp(gammaln(n+1)-gammaln(i))); + sam = 1+rem(0:i*tmp1-1,i) + ([1:tmp2]-1)*i; + %-Add samplings from lef to Xblkpermindx + Xblkpermindx = [Xblkpermindx; lef(sam)]; + %-Delete sampled items from lef & condition size + lef(sam) = []; + tmp = round(exp(gammaln(n+1)-gammaln((i-1)+1))); + lef = reshape(lef,(i-1),tmp); + %NB:gamma(n+1)/gamma((i-1)+1) == size(Xblkpermindx,2); + end + clear lef Exp sam i + %-Reorient so permutations are in rows + permindx = Xblkpermindx'; +end + +%-Check, condition and randomise permindx +%----------------------------------------------------------------------- +%-Check permindxs sum within Xblks to sum to 1 +if ~all(all(sum(permindx,2) == (n+1)*n/2 )) + error('Invalid permindx computed!'), end + +%-Convert to full permutations from permutations within blocks +nperms = size(permindx,1); + +%-Randomise order of permindxs (except first) to allow interim analysis +rand('seed',sum(100*clock)) %-Initialise random number generator +permindx=[permindx(1,:);permindx(randperm(nperms-1)+1,:)]; + +%-Check first permutation is null permutation +if ~use_approximate + if ~all(permindx(1,:)==[1:n]) + error('permindx(1,:)~=[1:n]'); + end +end + + +end diff --git a/Statistics_tools/permute_signtest.m b/Statistics_tools/permute_signtest.m new file mode 100644 index 00000000..d1dc4f09 --- /dev/null +++ b/Statistics_tools/permute_signtest.m @@ -0,0 +1,166 @@ +function [p, Z, xbar, permsign, pmean] = permute_signtest(data, nperms, w, permsign) +% [p, Z, xbar, permsign, pmean] = permute_signtest(data, nperms, [w], [permsign]) +% One-sample t-test against zero on each column of data +% Using weighted sign permutation test +% +% p-values are 2-tailed +% +% Inputs: +% data: n x k matrix of k vectors to test against zero +% nperms: number of permutations +% w: n x k matrix of weights +% weights for each column should sum to 1 +% default (for empty or missing input) is equal weights, i.e., 1/n +% +% permindx: nperms x n matrix of exact permutation indices +% default (for empty or missing input) generates nperms permutations +% +% Tor Wager, march 2007 +% +% Example: Generate 5-vector dataset and test, first generating perms, then +% using already-generated ones +% tic, [p, z, xbar, permsign] = permute_signtest(x, 1000); toc +% tic, [p2, z2, xbar, permsign] = permute_signtest(x, [], [], permsign); toc +% +% +% % Example: Generate fake data and simulate false positive rate +% permsign = []; % initialize to empty for first iteration +% nperms = 2000; nreps = 5000; +% tic, for i = 1:nreps +% x = randn(20,1); stat = mean(x); +% [p(i), z(i), xbar, permsign] = permute_signtest(x, nperms, [], permsign); +% if mod(i,10) == 0,fprintf(1,'%03d ',i); end +% end +% fprintf(1,'\n'); +% +% % Example: Generate fake data and simulate false positive rate +% % This uses the column-wise capabilities of this function and is much +% % faster +% nperms = 2000; nreps = 5000; nsubj = 15; +% x = randn(nsubj, nreps); +% [p, z, xbar, permsign] = permute_signtest(x, nperms); + + +[n, k] = size(data); % observations x columns + +if nargin < 4 || isempty(permsign) + [permsign, nperms] = setup_signperms(n, nperms); % rows are permutations +else + nperms = size(permsign, 2); +end + +% this would work for permuting rows, i.e., for correlation test +% % if nargin < 4 || isempty(permindx) +% % permindx = permute_setupperms(n, nperms); % rows are permutations +% % else +% % nperms = size(permindx, 1); +% % end + +if nargin < 3 || isempty(w) + w = ones(n, k) ./ n; +end + +pmean = zeros(nperms, k); + +wmean = @(data, w) diag(w'*data)'; + +% correct permutation + +xbar = wmean(data, w); + + +% sign permutations +for i = 1:nperms + + signs = repmat(permsign(:,i), 1, k); + + pdata = data .* signs; + + % permutation-distribution means + pmean(i,:) = wmean(pdata, w); + +end + + +xbarmtx = repmat(xbar, nperms, 1); +p = min( [sum(pmean <= xbarmtx); sum(pmean >= xbarmtx)] ) ./ nperms; + +p = 2 .* p; % two-tailed + +% make sure p is not 1 or 0 due to limited bootstrap samples +p = max(p, 1 ./ nperms); +p = min(p, 1 - 1./nperms); + +% adjust signs of z-scores to reflect direction of effect +Z = abs(norminv(p ./ 2)) .* sign(xbar); + +end + + + +function [permsign, nperms] = setup_signperms(n, nperms) +% get all possible combinations of [1 -1] signs for n subjects +% 2^n possible combos + +% re-set rand number generator, which is used by randperm +rand('twister',sum(100*clock)) + +% % t1 = clock; +% % fprintf(1, 'Setting up perms '); + +% can only have as many perms as unique values. +% if we have *all* unique perms, this is an exact test +nperms = min(nperms, 2^n); + +% columns are all possible combos + +if 2^n <= 32768 + % feasible to get *all* sign permutations; n = 15 or less + + vals = cell(1,n); + [vals{:}] = deal([1 -1]); + + permsign = combvec(vals{:}); + + % omit correct permutation + permsign(:,1) = []; + + nc = size(permsign,2); + + if isempty(nperms) + % do nothing besides set nperms + nperms = nc; + + elseif nperms < nc + % pick random subset + p = randperm(nc); + permsign = permsign(:, p(1:nperms)); + + elseif nperms > nc + % % % fprintf(1,'Warning: only %3.0f perms available. Min p-value is %3.6f\n', nc, 1./nc) + nperms = nc; + + end + +else + % too many perms of n [-1 1] signs + % generate random subset + n_to_start_with = min(2 * nperms, 2 * 2^n); % generate a finite number greater than needed; we want unique perms only + + permsign = -1 + 2 * round(rand(n, n_to_start_with)); + + permsign = unique(permsign', 'rows')'; + + % Fill in add'l perms, if any needed + % allow some repeats; less precise, but will help in generating perms fast + n_needed_now = nperms - size(permsign, 2); + permsign = [permsign -1 + 2 * round(rand(n, n_needed_now))]; +end + +% % % fprintf(1, 'Done in %3.0f s', etime(clock, t1)); + +end + + + + diff --git a/Statistics_tools/plssquash.m b/Statistics_tools/plssquash.m new file mode 100644 index 00000000..25cc7253 --- /dev/null +++ b/Statistics_tools/plssquash.m @@ -0,0 +1,86 @@ +function [V, S, varexp, w, Yhat] = plssquash(X, Y, varargin) +%[V, S, varexp, w, Yhat] = plssquash(X, Y, varargin) +% +% Tor Wager, 9/12/09 +% +% decomposes data X into K components that are ordered in their +% covariance with Y, designed to predict orthogonal parts of Y +% +% V = 'eigenvetors', or weights, on data (columns) +% S = score matrix, N x K +% varexp = sqrt(r-square) with first k components predicting Y +% w = V*b, integrated weights. for predicting new data, pred = X*w +% Yhat = X*V*b, or S*b +% +% Optional inputs: +% case {'noplot'}, turn off plotting +% case 'ndims', save only first ndims (K) vectors + +doplot = 1; +ndims = length(Y) - 1; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'noplot'}, doplot = 0; + case 'ndims', ndims = varargin{i + 1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +% univariate covariance-based weights +% imperfect prediction +X = X - repmat(mean(X), size(X, 1), 1); %scale(X); +Y = scale(Y); + +% +% neither V nor S appears to have orthogonal columns, though many are +% later columns, as df in Y is approached, appear to be highly colinear +% this may be because all variance in Y is basically explained... + +clear V % V is voxel weights for each component 1:K +clear S +rY = Y; % Y values to successively predict; intialize to Y + +for i = 1:ndims %ceil(length(Y)./2) + + % V = voxel weights, based on univariate relationship with rY + V(:,i) = (X' * rY); %.^ .5; + V(:,i) = V(:,i) ./ norm(V(:,i)); + + % score matrix, N x K + % these will ultimately be predictors for Y + % chosen to maximize predictive power with few orthogonal components + S = X*V; + + rY = Y - S * pinv(S) * Y; + + b = pinv(S) * Y; + Yhat = S * b; + + rsq(i) = 1 - var(rY) ./ var(Y); + + if doplot + create_figure('Fit'); plot(Yhat, Y, 'kx'); refline; + + rsq_adj(i) = 1 - (1-rsq(i)) * ((size(Y,1)-1) ./ (size(Y,1)-i-1)); + + title(sprintf('cum. r-squared = %3.1f, adj = %3.1f', 100*rsq(i), 100*rsq_adj(i))); + drawnow; pause(.3) + end + +end + +varexp = sqrt(rsq); + +b = pinv(S) * Y; +Yhat = S * b; +w = V * b; % final weight vector + +%prediction = mean(training) + data*S*b +%Yhat = X * V * b; +%figure; plot(Yhat, Y, 'kx') + +end diff --git a/Statistics_tools/princomp_largedata.m b/Statistics_tools/princomp_largedata.m new file mode 100644 index 00000000..9f3cca1d --- /dev/null +++ b/Statistics_tools/princomp_largedata.m @@ -0,0 +1,142 @@ +function [coeff, score, latent, tsquare] = princomp_largedata(x,econFlag) +%PRINCOMP_LARGEDATA Principal Components Analysis. +% This is a version created for large data sets by Matthew Davidson +% The default Matlab PRINCOMP is naive to large data sets. Out-of-memory +% errors are easily obtained on imaging data. The solution is to replace +% concise but inefficient calls to repmat with loops and to eliminate +% large, unused variables. +% +% Tested on random data sets and produces identical output as original +% PRINCOMP. Speed penalty is drastic for small data. 50% slower on +% 50x1000 element data set, but on a 50x10000, only 2% slower. +% +% COEFF = PRINCOMP(X) performs principal components analysis on the N-by-P +% data matrix X, and returns the principal component coefficients, also +% known as loadings. Rows of X correspond to observations, columns to +% variables. COEFF is a P-by-P matrix, each column containing coefficients +% for one principal component. The columns are in order of decreasing +% component variance. +% +% PRINCOMP centers X by subtracting off column means, but does not +% rescale the columns of X. To perform PCA with standardized variables, +% i.e., based on correlations, use PRINCOMP(ZSCORE(X)). To perform PCA +% directly on a covariance or correlation matrix, use PCACOV. +% +% [COEFF, SCORE] = PRINCOMP(X) returns the principal component scores, +% i.e., the representation of X in the principal component space. Rows +% of SCORE correspond to observations, columns to components. +% +% [COEFF, SCORE, LATENT] = PRINCOMP(X) returns the principal component +% variances, i.e., the eigenvalues of the covariance matrix of X, in +% LATENT. +% +% [COEFF, SCORE, LATENT, TSQUARED] = PRINCOMP(X) returns Hotelling's +% T-squared statistic for each observation in X. +% +% When N <= P, SCORE(:,N:P) and LATENT(N:P) are necessarily zero, and the +% columns of COEFF(:,N:P) define directions that are orthogonal to X. +% +% [...] = PRINCOMP(X,'econ') returns only the elements of LATENT that are +% not necessarily zero, i.e., when N <= P, only the first N-1, and the +% corresponding columns of COEFF and SCORE. This can be significantly +% faster when P >> N. +% +% See also BARTTEST, BIPLOT, CANONCORR, FACTORAN, PCACOV, PCARES, ROTATEFACTORS. + +% References: +% [1] Jackson, J.E., A User's Guide to Principal Components, +% Wiley, 1988. +% [2] Jolliffe, I.T. Principal Component Analysis, 2nd ed., +% Springer, 2002. +% [3] Krzanowski, W.J., Principles of Multivariate Analysis, +% Oxford University Press, 1988. +% [4] Seber, G.A.F., Multivariate Observations, Wiley, 1984. + +% Copyright 1993-2005 The MathWorks, Inc. +% $Revision: 2.9.2.9 $ $Date: 2006/10/02 16:35:01 $ + +% When X has more variables than observations, the default behavior is to +% return all the pc's, even those that have zero variance. When econFlag +% is 'econ', those will not be returned. +if nargin < 2, econFlag = 0; end + +[n,p] = size(x); +if isempty(x) + pOrZero = ~isequal(econFlag, 'econ') * p; + coeff = zeros(p,pOrZero); coeff(1:p+1:end) = 1; + score = zeros(n,pOrZero); + latent = zeros(pOrZero,1); + tsquare = zeros(n,1); + return +end + +% Center X by subtracting off column means +%x0 = x - repmat(mean(x,1),n,1); +for i=1:size(x, 2) + x(:,i) = x(:,i) - mean(x(:,i)); +end +r = min(n-1,p); % max possible rank of X0 + +% The principal component coefficients are the eigenvectors of +% S = X0'*X0./(n-1), but computed using SVD. +[U,sigma,coeff] = svd(x,econFlag); % put in 1/sqrt(n-1) later + +if nargout < 2 + % When econFlag is 'econ', only (n-1) components should be returned. + % See comment below. + if (n <= p) && isequal(econFlag, 'econ') + coeff(:,n) = []; + end + +else + % Project X0 onto the principal component axes to get the scores. + if n == 1 % sigma might have only 1 row + sigma = sigma(1); + else + sigma = diag(sigma); + end + score = U .* repmat(sigma',n,1); % == x*coeff + sigma = sigma ./ sqrt(n-1); + + % When X has at least as many variables as observations, eigenvalues + % n:p of S are exactly zero. + if n <= p + % When econFlag is 'econ', nothing corresponding to the zero + % eigenvalues should be returned. svd(,'econ') won't have + % returned anything corresponding to components (n+1):p, so we + % just have to cut off the n-th component. + if isequal(econFlag, 'econ') + sigma(n,:) = []; % make sure this shrinks as a column + coeff(:,n) = []; + score(:,n) = []; + + % Otherwise, set those eigenvalues and the corresponding scores to + % exactly zero. svd(,0) won't have returned columns of U + % corresponding to components (n+1):p, need to fill those out. + else + sigma(n:p,1) = 0; % make sure this extends as a column + score(:,n:p) = 0; + end + end + + % The variances of the pc's are the eigenvalues of S = X0'*X0./(n-1). + latent = sigma.^2; + + % Hotelling's T-squared statistic is the sum of squares of the + % standardized scores, i.e., Mahalanobis distances. When X appears to + % have column rank < r, ignore components that are orthogonal to the + % data. + if nargout == 4 + if n > 1 + q = sum(sigma > max(n,p).*eps(sigma(1))); + if q < r + warning('stats:princomp:colRankDefX', ... + ['Columns of X are linearly dependent to within machine precision.\n' ... + 'Using only the first %d components to compute TSQUARED.'],q); + end + else + q = 0; + end + tsquare = (n-1) .* sum(U(:,1:q).^2,2); % == sum((score*diag(1./sigma)).^2,2) + end +end diff --git a/Statistics_tools/prplot_multilevel.m b/Statistics_tools/prplot_multilevel.m new file mode 100644 index 00000000..4542bf12 --- /dev/null +++ b/Statistics_tools/prplot_multilevel.m @@ -0,0 +1,53 @@ +function [X_resid, Y_resid, handles] = prplot_multilevel(Y, X, wh_col) +% [X_resid, Y_resid, handles] = prplot_multilevel(Y, X, wh_col) +% +% Partial correlation plot for multi-level analysis +% Uses unweighted estimates. +% +% do not enter intercept in X + +N = length(X); + +for i = 1:N + + X_other{i} = X{i}; + X_other{i}(:, wh_col) = []; + + X_part{i} = X{i}(:, wh_col); + +end + +wh = true(size(X{1}, 2) + 1, 1); % assume X is same size; k vars + intercept + +wh(wh_col + 1) = false; % omit from betas; consider intercept + +stats = glmfit_multilevel(Y, X_other, [], 'noverbose'); + +for i = 1:N + + Y_resid{i} = Y{i} - [ones(size(X_other{i}, 1), 1) X_other{i}] * stats.first_level.beta(:, i); + +end + +stats = glmfit_multilevel(X_part, X_other, [], 'noverbose'); + +for i = 1:N + + X_resid{i} = X_part{i} - [ones(size(X_other{i}, 1), 1) X_other{i}] * stats.first_level.beta(:, i); + +end + + +handles.overall = plot(cat(1, X_resid{:}), cat(1, Y_resid{:}), 'ko'); + +% partial fit plot +for i = 1:N + + handles.indiv = plot(X_resid{i}, Y_resid{i},'Color', rand(1, 3)); + +end + +handles.refline = refline; +set(handles.refline, 'LineWidth', 3); + +end % function \ No newline at end of file diff --git a/Statistics_tools/r2z.m b/Statistics_tools/r2z.m new file mode 100755 index 00000000..ab0d9be7 --- /dev/null +++ b/Statistics_tools/r2z.m @@ -0,0 +1,73 @@ +function [rci,sig,Z,p,rcrit] = r2z(r,n,varargin) +% [rci,sig,Z,p,rcrit] = r2z(r,n,[alph]) +% +% Fisher's r to Z transformation +% for providing CIs for a correlation +% +% n = # of observations going into correlation +% (count each row/subject once) +% +% df = n - 3 +% alph = two-tailed p-value cutoff, +% default is p < .05 +% +% rci = confidence interval in correlation values +% sig = significant at alpha value? +% Z = z-scores of correlations +% p = p-values +% +% can take a vector of r values +% +% tor wager +% +% Example: +%[rci,sig,z] = r2z(.1:.05:.9,5,.05); figure('Color','w');hold on; plot(.1:.05:.9,rci(:,1),'g','LineWidth',2) +%[rci,sig,z] = r2z(.1:.05:.9,10,.05); hold on; plot(.1:.05:.9,rci(:,1),'r','LineWidth',2) +%[rci,sig,z] = r2z(.1:.05:.9,20,.05); hold on; plot(.1:.05:.9,rci(:,1),'b','LineWidth',2) +%[rci,sig,z] = r2z(.1:.05:.9,40,.05); hold on; plot(.1:.05:.9,rci(:,1),'m','LineWidth',2) +%[rci,sig,z] = r2z(.1:.05:.9,80,.05); hold on; plot(.1:.05:.9,rci(:,1),'k','LineWidth',2) +%set(gca,'FontSize',18) +%legend({'n = 5' 'n = 10' 'n = 20' 'n = 40' 'n = 80'}) +%title('.05 Confidence interval lower bound on Pearson''s r') +%xlabel('Correlation (r)') +%ylabel('CI Lower Bound (r)') +% c=.2:.01:.5;,[rci,sig,z,p,rcrit]=r2z(c,39,.05);[c' sig p] +% +% ind=1;for i=1:10:5000,[rci,s,z,p,rc(ind)]=r2z(.5,39,.05/i);,ind=ind+1;,end +% figure;plot(1:10:5000,rc);title('Critical r with Bonf correction'),xlabel('Comparisons') + +alph = .05; +if length(varargin) > 0, alph = varargin{1};, end + +if size(r,2) > size(r,1), r = r';, end + +z = .5 * log( (1+r) ./ (1-r) ); + +% critical Z at alpha (two-tailed) +zc = norminv(1-alph/2); + +% standard deviation of z (= standard error) +sd = (1 ./ (n - 3)) .^.5; + +% p-values +Z = z ./ sd; p = (1 - normcdf(abs(Z))) .* 2; + +% critical r +rcrit = (exp(2.*zc.*sd) - 1) ./ (exp(2.*zc.*sd) + 1); + +% lower and upper bounds = confindence interval +zci(:,1) = z - (sd*zc); +zci(:,2) = z + (sd*zc); + +% transform CIs back to r + +rci = (exp(2.*zci) - 1) ./ (exp(2.*zci) + 1); + +% cap at 1 - shoudn't need this +%rci(:,2) = min([rci(:,2) ones(size(rci,1),1)]')'; + +% significant? + +sig = rci(:,1) > 0 | rci(:,2) < 0; + +return diff --git a/Statistics_tools/regress_best_subsets_ga.m b/Statistics_tools/regress_best_subsets_ga.m new file mode 100644 index 00000000..bd15b82d --- /dev/null +++ b/Statistics_tools/regress_best_subsets_ga.m @@ -0,0 +1,119 @@ +function [wh_predictors, betas, b, stat] = regress_best_subsets_ga(X, Y) +% [wh_predictors, betas, b_subset, stat_subset] = regress_best_subsets_ga(X, Y) +% GA-based best subsets regression +% +% Y is outcome data +% X is predictor matrix +% wh_predictors is the primary outcome -- it is vector of which predictors to include in the model +% +% the objective criterion is AIC + +seeds = get_seeds(X, Y); + +% Example: get_AIC(X, Y, seeds(:, 1)) + +% The fitness function is higher when AIC is lower +% The 1/AIC transformation would introduce a nonlinear weighting function; +% so would -log(AIC) +% log(1/AIC) is approximately linear. + +%fitness_fcn = @(wh_preds) 1 ./ get_AIC(X, Y, wh_preds) +fitness_fcn = @(wh_preds) log(1 ./ get_AIC(X, Y, wh_preds)); + +% for i = 1:size(seeds, 2) +% a(i) = get_AIC(X, Y, seeds(:, i)); +% f(i) = fitness_fcn(seeds(:, i)); +% end + +% Run GA + +example_vec = {seeds(:, 1)}; % this is 'inputs', what will be passed to the objective function (ofun) in the GA + +for i = 1:size(seeds, 2), gaseeds{i} = seeds(:, i); end + +[best_params,fit,beff,in,isconverged] = tor_ga(300, 50, example_vec, fitness_fcn, 'discrete', 'seeds', gaseeds, 'genconverge', 5); + +wh_predictors = logical(best_params{1}); + + Xs = X(:, wh_predictors); + [b, dev, stat] = glmfit(Xs, Y); % subset plus intercept first + + betas = zeros(size(X, 2) + 1, 1); + betas(logical([1; wh_predictors])) = b; % add the intercept always + +end + + +function aic = get_AIC(X, Y, wh_preds) + + X = X(:, logical(wh_preds)); + [nobs, npreds] = size(X); + + [b, dev, stat] = glmfit(X, Y); + RSS = stat.resid' * stat.resid; + + % AIC = 2k - 2ln(L), k = npreds, L = likelihood + % AIC = 2k + n(ln(2piRSS/n) + 1) + % + % simplifying to relative values (we need only relative AIC, not + % absolute: + + npreds = sum(wh_preds) + 1; % include intercept + aic = 2 * npreds + nobs * log(RSS); + +end + + +function seeds = get_seeds(X, Y) + +[nobs, npreds] = size(X); +z = false(npreds, 1); + +% seed the GA with stepwise regression results and univariate results (top +% 1, 2, ... k) + +[b, dev, stat] = glmfit(X, Y); +b = abs(b(2:end)); +t = abs(stat.t(2:end)); + +% Set of candidate subsets with descending order of abs magnitude of betas +[bs, wh] = sort(b, 1, 'descend'); +bseeds = false(npreds); + +for i = 1:npreds + my_subset = z; + my_subset(wh(1:i)) = true; + bseeds(:, i) = my_subset; +end + +% Set of candidate subsets with descending order of abs magnitude of +% t-values (last one is redundant with bseeds) +[ts, wh] = sort(t, 1, 'descend'); +tseeds = false(npreds, npreds - 1); +for i = 1:npreds - 1 + my_subset = z; + my_subset(wh(1:i)) = true; + tseeds(:, i) = my_subset; +end + +% single-predictor seeds with each variable +eseeds = logical(eye(npreds)); + +% stepwise seeds +pentervals = [.05:.05:.95]; +for i = 1:length(pentervals) + [b, se, p, inmodel] = stepwisefit(X, Y, 'penter', pentervals(i), 'display', 'off'); + fseeds(:, i) = inmodel'; +end + +seeds = [bseeds tseeds eseeds fseeds]; + +% random seeds +numseeds = max(100, 200 - size(seeds, 2)); % at least 100 seeds +rseeds = rand(npreds, numseeds); +rseeds = rseeds > .5; + +seeds = [seeds rseeds]; + +end + diff --git a/Statistics_tools/repeated_ancova.m b/Statistics_tools/repeated_ancova.m new file mode 100644 index 00000000..119a2c1f --- /dev/null +++ b/Statistics_tools/repeated_ancova.m @@ -0,0 +1,366 @@ +function [b,stats,yadj] = repeated_ancova(X,Y,wicons,btwnnames,winames,ynames,varargin) +%[b,stats,yadj] = repeated_ancova(X,Y,wicons,btwnnames,winames,ynames,varargin) +% +% Repeated measures ANCOVA with table and plot +% uses Robust IRLS +% +% tor wager, Aug. 06 +% +%Y = rand(15,2); +%X = Y + rand(15,2); +%cons = [-1 1 -1 1; 1 1 -1 -1]; % placebo vs control, hot vs. warm +% +% X = R.X(:,1:2); +% Y = cl(2).CONTRAST.data; +% cons = [-1 1 0 0; 0 0 -1 1]; % placebo vs control for heat then warm +% repeated_ancova(X,Y,cons,{'Reported Placebo (C - P)' 'Order'},{'P-C Heat' 'P-C Warm'},{'CH' 'PH' 'CW' 'PW'}); +% +% DOES NOT WORK WITH FIXED BTWN-SUBJECTS COVARIATES +% X must be a random variable that is observed multiple times for each +% subject, as does Y + +dotable = 1; +doplot = 1; + +for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % reserved keywords + case 'notable', dotable = 0; + case 'noplot', doplot = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +[n,r] = size(Y); + +y = Y(:); + +obs = size(y,1); % total num observations + +% get predictors for between-subjects effects +% x +% --------------------------------------------------- +if all(size(X) - [n r] == 0) + % X is obs. x conditions for a single predictor + nbtwn = 1; + x = X(:); %repmat(X,r,1); + btwnnames = repmat(btwnnames,1,r); + +elseif size(X,1) == n + disp('Warning!!!!!! Results are not valid for fixed within-subjects contrasts.') + % X is a between-Ss effect replicated for each measure + nbtwn = size(X,2); + for i = 1:nbtwn + x(:,i) = repmat(X(:,i),r,1); + end +end + +x = scale(x,1); % center x + +% get predictors for repeated measures effects +% c +% --------------------------------------------------- +% effect of repeated measure on y (repeated condition difference in ANCOVA) +% at x = 0 +c = expand_contrasts(wicons,n,r); +nwithin = size(c,2); + + + +% get preds for interaction between continuous x and c +% xc +% --------------------------------------------------- +% difference in x-y slopes for different repeated measures in c +if nbtwn + [xc,xcnames,ninteract] = make_interactions(nbtwn,nwithin,x,c,n,r,btwnnames,winames); +else + xc = []; xcnames = {}; ninteract = 0; +end + + + +% Subject effects, obs x n-1 + +%nused = 1 + nbtwn ; % warning! not sure if this is right; only needed if btwn effects are replicated for all conditions +% THE ABOVE IS BAD; CANNOT ESTIMATE MODEL WHEN X IS FIXED. + +S = repmat([eye(n-1); zeros(1,n-1)], r, 1); +%S = S(:,nused+1:end); + +% y is regressed on x, controlling for S, and testing c + +% full model +M = [x c xc S]; M(:,end+1) = 1; +p = size(M,2); % total num params estimated +xtxitx = pinv(M); + +res = y - M * xtxitx * y; % residuals +SSr = res' * res; % sums of squared residuals +%dfe = obs - p; % error df +%dfb = p - 1; % full model df + +% reduced model: Subject effects only +Mr = S; Mr(:,end+1) = 1; +res = y - Mr * pinv(Mr) * y; % residuals +SSr2 = res' * res; % sums of squared residuals (2) + +pr = size(Mr,2); % total num params estimated, including intercept = n +dfe = obs - pr; % error df +MSerr = SSr2 ./ dfe; + +% model comparison +SSrdiff = SSr2 - SSr; % sums of squares explained by model params x c xc +dfdiff = p - pr; % parameters used to explain SSrdiff +MSmodel = SSrdiff ./ dfdiff; % mean square for non-subject, non-intercept effects in model + + +F = MSmodel ./ MSerr; +p = 1 - fcdf(F,dfdiff,dfe); % all this is very approx now!!! it's late. + +[b,stats] = robustfit(M,y,'bisquare',[],'off'); + +wh_reg = 1; + +if nargout > 2 + % get which regs are of interest for fitted data + z = get_which_of_interest(wh_reg,nbtwn,nwithin,ninteract,S); + + yadj = get_yadj(wh_reg,x,y,M,z,b,n,r); +end + +% table +if dotable + regtable(b,stats,nbtwn,nwithin,ninteract,btwnnames,winames,xcnames); +end + + +% plot +if doplot + %regplot(wh_reg,b,x,y,X,Y,M,S,nbtwn,nwithin,ninteract,n,r,c,btwnnames,winames,ynames); + regplot2(X,Y,r,stats.w,btwnnames,ynames); + ylabel('Y'); + + z = get_which_of_interest(wh_reg,nbtwn,nwithin,ninteract,S); + yadj = get_yadj(wh_reg,x,y,M,z,b,n,r); + regplot2(X,yadj,r,stats.w,btwnnames,ynames); + +end + +return + + + + + + +% setup: get within-subjects contrasts + +function c = expand_contrasts(cons,n,r) + +ncons = size(cons,1); +c = zeros(n*r,ncons); + +for i = 1:ncons + mycon = zeros(n*r,1); + for j = 1:r + mycon((j-1)*n+1 : j*n) = cons(i,j); + end + c(:,i) = mycon; +end + +return + + + +function [xc,xcnames,ninteract] = make_interactions(nbtwn,nwithin,x,c,n,r,btwnnames,winames) +ninteract = nbtwn * nwithin; +xc = zeros(n*r,ninteract); +xccounter = 1; +for i = 1:nbtwn + for j = 1:nwithin + xc(:,xccounter) = x(:,i) .* c(:,j); + xcnames{xccounter} = [btwnnames{i} '*' winames{j}]; + xccounter = xccounter + 1; + end +end +return + + + +function regtable(b,stats,nbtwn,nwithin,ninteract,btwnnames,winames,xcnames) +fprintf(1,'Parameter\tb-hat\tt\tp\t \n') + +fprintf(1,'Between-subjects effects \n') +wh = 1:nbtwn; % which columns for these effects +for i=1:nbtwn + fprintf(1,'%s\t%3.2f\t%3.2f\t%3.4f\t \n', ... + btwnnames{i},b(wh(i)),stats.t(wh(i)),stats.p(wh(i))); +end + +fprintf(1,'Within-subjects condition effects \n') +wh = nbtwn+1:nbtwn+nwithin; +for i=1:nwithin + fprintf(1,'%s\t%3.2f\t%3.2f\t%3.4f\t \n', ... + winames{i},b(wh(i)),stats.t(wh(i)),stats.p(wh(i))); +end + +fprintf(1,'Between * Within interactions \n') +wh = nbtwn+nwithin+1 : nbtwn+nwithin+ninteract; +for i=1:ninteract + fprintf(1,'%s\t%3.2f\t%3.2f\t%3.4f\t \n', ... + xcnames{i},b(wh(i)),stats.t(wh(i)),stats.p(wh(i))); +end + +return + + + + + +function z = get_which_of_interest(wh_reg,nbtwn,nwithin,ninteract,S) +z = zeros(1,nbtwn); +z(wh_reg) = 1; % between of interest +z = [z ones(1,nwithin)]; % all within are of interest +zxc = zeros(1,ninteract); % interaction effects +xccounter = 1; +for i = 1:nbtwn + for j = 1:nwithin + if i == wh_reg, zxc(xccounter) = 1; end + xccounter = xccounter + 1; + end +end +z = [z zxc]; +z = [z zeros(1,size(S,2))]; % subject effects +z = [z 1]; % intercept +z = logical(z); +return + + +function yadj = get_yadj(wh_reg,x,y,M,z,b,n,r); +% x (of interest) and adjusted y +xdata = reshape(x(:,wh_reg),n,r); +yadj = y - M(:,~z) * b(~z); +yadj = reshape(yadj,n,r); + + + +function regplot2(X,Y,r,w,btwnnames,ynames) + +tor_fig; set(gca,'FontSize',24) +colors = {'ro' 'gs' 'cv' 'mh'}; +for i = 1:r + h = plot_correlation(X(:,i),Y(:,i),'colors',colors(i),'robust','noprint','weights',w); + han(i) = h{1}(1); +end + +han2 = findobj(gcf,'Type','text'); +delete(han2) + +% range over which to get fits for line +xrange = max(X(:,1)) - min(X(:,1)); +minx = min(X(:,1)) - .1*xrange; +maxx = max(X(:,1)) + .1*xrange; + +set(gca,'XLim',[minx maxx]); + +% Y range +yrange = max(Y(:)) - min(Y(:)); +miny = min(Y(:)) - .1*yrange; +maxy = max(Y(:)) + .1*yrange; +set(gca,'YLim',[miny maxy]); + +xlabel(btwnnames{1}); +ylabel('Adjusted data'); +%ynames = {'HC' 'HA' 'LC' 'LA'}; +legend(han,ynames) + +scn_export_papersetup(400); + + +return + + + +% OLD FUNCTION--but a goodie. this was for fixed regressors btwn. +function regplot(wh_reg,b,x,y,X,Y,M,S,nbtwn,nwithin,ninteract,n,r,c,btwnnames,winames,ynames) + +npar = nbtwn+nwithin+ninteract; + +% only significant betas +% sig = stats.p < .10; +% sig = sig(1:npar); +% bsig = b(1:npar) .* sig; +% bsig = [bsig; b(end)]; + +% all betas for btwn of interest and w/i conditions +% bsig = zeros(1,npar)'; +% bsig(wh_reg) = b(wh_reg); % regressor of interest +% wh = nbtwn+1:nbtwn+nwithin; % condition, within Ss effects +% bsig(wh) = b(wh); +% wh = nbtwn+nwithin+1:npar; % interactions with reg of interest +% wh = wh(1:2); %%%%%kludgy fix for reg 1 only +% bsig(wh) = b(wh); +% bsig = [bsig; b(end)]; + +% range over which to get fits for line +xrange = max(X(:,1)) - min(X(:,1)); +minx = min(X(:,1)) - .1*xrange; +maxx = max(X(:,1)) + .1*xrange; + +% get predicted values for this btwn effect (xf) +x2 = linspace(minx,maxx,n)'; +x2 = repmat(x2,1,r); +x2 = x2(:); +xf = x; +xf(:,wh_reg) = x2; + +% get interactions +[xcf,xcnamesf,ninteract] = make_interactions(nbtwn,nwithin,xf,c,n,r,btwnnames,winames); + +% Model for pred values +Mf = [xf c xcf S]; +Mf(:,end+1) = 1; + +% get which regs are of interest for fitted data +z = get_which_of_interest(wh_reg,nbtwn,nwithin,ninteract,S); + +%fit lines +f = Mf(:,z) * b(z); % fitted data (for lines) +f = reshape(f,n,r); % fits +xfit = reshape(x2,n,r); % x values for fits + + +% x (of interest) and adjusted y +xdata = X; +%xdata = reshape(x(:,wh_reg),n,r); +yadj = y - M(:,~z) * b(~z); +yadj = reshape(yadj,n,r); + +% make figure +tor_fig; set(gca,'FontSize',24) +colors = {'ro' 'bs' 'kv' 'gp'}; +for i = 1:r + plot(xdata(:,i),yadj(:,i),colors{i},'MarkerFaceColor',colors{i}(1)); % adjusted data + han(i) = plot(xfit(:,i),f(:,i),[colors{i}(1) '-']); % fit line +end + +xlabel(btwnnames{wh_reg}); +ylabel('Adjusted data'); +%ynames = {'HC' 'HA' 'LC' 'LA'}; +legend(han,ynames) + +set(gca,'XLim',[minx maxx]); + +% Y range +yrange = max(yadj(:)) - min(yadj(:)); +miny = min(yadj(:)) - .1*yrange; +maxy = max(yadj(:)) + .1*yrange; +set(gca,'YLim',[miny maxy]); + +scn_export_papersetup(400); + +return diff --git a/Statistics_tools/rmanova2.m b/Statistics_tools/rmanova2.m new file mode 100644 index 00000000..5870b0c7 --- /dev/null +++ b/Statistics_tools/rmanova2.m @@ -0,0 +1,241 @@ +function stats = rmanova2(data,alpha,doplot,ttst) +% +%Repeated-measures two-way ANOVA +% +%USAGE: stats = rmanova2(data,[alpha],[doplot],[ttst]); +% +%INPUTS: +% data: can be one of two formats: +% 1. Cell array - each row represents a level of factor 1, and +% each column represents a level of factor 2. Each cell contains a +% vector of values of the dependent variable for each subject. +% 2. Matrix - each row represents a trial, with the following +% columns: +% column1 - dependent variable +% column2 - grouping variable for subject +% column3 - grouping variable for factor 1 +% column4 - grouping variable for factor 2 +% alpha (optional): p-value threshold (default: 0.05) +% doplot (optional): if 1, will produce a line plot. +% Works only for cell input data (default: 1) +% ttst (optional): if 1, will perform pairwise t-tests (default: 0) +% +% Aaron Schurger (2005.02.04) +% Derived from Keppel & Wickens (2004) "Design and Analysis" ch. 18 +% Modified by Sam Gershman (2006.11.16) +% + +if nargin < 2; alpha = 0.05; doplot = 0; ttst = 0; end +if nargin < 3; doplot = 1; ttst = 0; end +if nargin < 4; ttst = 0; end + +if iscell(data); + Y = []; S = []; F1 = []; F2 = []; + [m n] = size(data); + for i = 1:m; + for j = 1:n; + Y = cat(1,Y,data{i,j}); + nsubs = length(data{i,j}); + S = cat(2,S,1:nsubs); + F1 = cat(1,F1,zeros(nsubs,1)+i); + F2 = cat(1,F2,zeros(nsubs,1)+j); + end + end + S = S'; +else + Y = data(:,1); + S = data(:,2); + F1 = data(:,3); F2 = data(:,4); + clear data + su = unique(S); f1u = unique(F1); f2u = unique(F2); + for a = 1:length(f1u); + for b = 1:length(f2u); + ii = intersect(find(F1==f1u(a)),find(F2==f2u(b))); + d = [Y(ii) S(ii)]; + d = sortrows(d,2); + data{a,b} = d(:,1); + end + end +end + +F1_lvls = unique(F1); +F2_lvls = unique(F2); +Subjs = unique(S); + +a = length(F1_lvls); % # of levels in factor 1 +b = length(F2_lvls); % # of levels in factor 2 +n = length(Subjs); % # of subjects + +INDS = cell(a,b,n); % this will hold arrays of indices +CELLS = cell(a,b,n); % this will hold the data for each subject X condition +MEANS = zeros(a,b,n); % this will hold the means for each subj X condition + +% Calculate means for each subject X condition. +% Keep data in CELLS, because in future we may want to allow options for +% how to compute the means (e.g. leaving out outliers > 3stdev, etc...). +for i=1:a % F1 + for j=1:b % F2 + for k=1:n % Subjs + INDS{i,j,k} = find(F1==F1_lvls(i) & F2==F2_lvls(j) & S==Subjs(k)); + CELLS{i,j,k} = Y(INDS{i,j,k}); + MEANS(i,j,k) = mean(CELLS{i,j,k}); + end + end +end + +% make tables (see table 18.1, p. 402) +AB = reshape(sum(MEANS,3),a,b); % across subjects +AS = reshape(sum(MEANS,2),a,n); % across factor 2 +BS = reshape(sum(MEANS,1),b,n); % across factor 1 + +A = sum(AB,2); % sum across columns, so result is ax1 column vector +B = sum(AB,1); % sum across rows, so result is 1xb row vector +S = sum(AS,1); % sum across columns, so result is 1xs row vector +T = sum(sum(A)); % could sum either A or B or S, choice is arbitrary + +% degrees of freedom +dfA = a-1; +dfB = b-1; +dfAB = (a-1)*(b-1); +dfS = n-1; +dfAS = (a-1)*(n-1); +dfBS = (b-1)*(n-1); +dfABS = (a-1)*(b-1)*(n-1); + +% bracket terms (expected value) +expA = sum(A.^2)./(b*n); +expB = sum(B.^2)./(a*n); +expAB = sum(sum(AB.^2))./n; +expS = sum(S.^2)./(a*b); +expAS = sum(sum(AS.^2))./b; +expBS = sum(sum(BS.^2))./a; +expY = sum(Y.^2); +expT = T^2 / (a*b*n); + +% sums of squares +ssA = expA - expT; +ssB = expB - expT; +ssAB = expAB - expA - expB + expT; +ssS = expS - expT; +ssAS = expAS - expA - expS + expT; +ssBS = expBS - expB - expS + expT; +ssABS = expY - expAB - expAS - expBS + expA + expB + expS - expT; +ssTot = expY - expT; + +% mean squares +msA = ssA / dfA; +msB = ssB / dfB; +msAB = ssAB / dfAB; +msS = ssS / dfS; +msAS = ssAS / dfAS; +msBS = ssBS / dfBS; +msABS = ssABS / dfABS; + +% f statistic +fA = msA / msAS; +fB = msB / msBS; +fAB = msAB / msABS; + +% p values +pA = 1-fcdf(fA,dfA,dfAS); +pB = 1-fcdf(fB,dfB,dfBS); +pAB = 1-fcdf(fAB,dfAB,dfABS); + +% return values + stats.SS1 = ssA; + stats.df1 = dfA; + stats.MS1 = msA; + stats.F1 = fA; + stats.P1 = pA; + stats.SS2 = ssB; + stats.df2 = dfB; + stats.MS2 = msB; + stats.F2 = fB; + stats.P2 = pB; + stats.SS12 = ssAB; + stats.df12 = dfAB; + stats.MS12 = msAB; + stats.F12 = fAB; + stats.P12 = pAB; + + stats.alpha = alpha; + + %decision rule +if stats.P1 < alpha; stats.significant1 = 'Yes'; else; stats.significant1 = 'No'; end; +if stats.P2 < alpha; stats.significant2 = 'Yes'; else; stats.significant2 = 'No'; end; +if stats.P12 < alpha; stats.significant12 = 'Yes'; else; stats.significant12 = 'No'; end; + +%make line plots +if doplot + anova_line_plot(data); + +% [m n] = size(data); plotcounter = 0; +% figure; +% for i = 1:m; +% for j = 1:n; +% plotcounter = plotcounter + 1; +% subplot(m,n,plotcounter); +% boxplot(data{i,j}); +% xlabel([num2str(i),',',num2str(j)]); +% set(gca,'XTickLabel',[]); +% end +% end +end + +%pairwise t-tests +if ttst; + disp('Performing post-hoc t-tests...'); + tcounter = 0; a = 0; b = 0; + [m n] = size(data); + for t1 = 1:m; + for t2 = 1:n; + a = a + 1; b = 0; + for t3 = 1:m; + for t4 = 1:n; + b = b + 1; + if b > a; + tcounter = tcounter + 1; + [h,p,ci,stat] = ttest(data{t1,t2},data{t3,t4}); + stats.ttests(tcounter).P = p; + stats.ttests(tcounter).comparison = [num2str(t1),',',num2str(t2),' > ',num2str(t3),',',num2str(t4)]; + stats.ttests(tcounter).means = [mean(data{t1,t2}) mean(data{t3,t4})]; + stats.ttests(tcounter).ci = ci'; + end + end + end + end + end +end + +end + + +function anova_line_plot(anova_dat) + + if ~iscell(anova_dat) + error('For line plots, enter cells rather than matrix input!'); + end + +for i = 1:size(anova_dat, 1) +for j = 1:size(anova_dat, 2) +linedat(i, j) = nanmean(anova_dat{i, j}); +lineste(i, j) = ste(anova_dat{i, j}); +end +end + +% Transpose so F2 is on X-axis, F1 is lines +linedat = linedat'; +lineste = lineste'; + +create_figure('Lines'); + +plot(linedat, 'o-', 'LineWidth', 3, 'MarkerSize', 12, 'MarkerFaceColor', [.5 .5 .5]) + +set(gca, 'XLim', [.5 size(anova_dat, 2)+.5], 'XTick', [1:size(anova_dat, 2)]); xlabel('Factor 2'); + +for j = 1:size(anova_dat, 2), f1names{j} = ['F1: Level ' num2str(j)]; end + +legend(f1names) + +end + diff --git a/Statistics_tools/robust_reg_pooled.m b/Statistics_tools/robust_reg_pooled.m new file mode 100644 index 00000000..0b659ae4 --- /dev/null +++ b/Statistics_tools/robust_reg_pooled.m @@ -0,0 +1,146 @@ +function [betas,w] = robust_reg_pooled(X,Y) +% [betas,w] = robust_reg_pooled(X,Y) +% [betas,stats] = weighted_reg(X,Y,'w',w,'uni'); + +% Get rid of missing values +nancols = find(any(isnan(Y) | Y==0,1)); +Y(:,nancols) = []; + +[m,n] = size(Y); +w = ones(m,1); + +% pool weights across all voxels +[betas,invxwx,bform,fits] = get_betas_singleweight(X,Y,w); + +% -------------------------------------- +% * Weights and computational setup +% -------------------------------------- + +% this is done only once in robust regression (not iterated) +lev = diag(X * bform); % leverage, diagonals of hat matrix +w = 1./lev; % starting weights, 1 / leverages + +h = min(.9999, sum(lev.*lev,2)); + +adjfactor = 1 ./ sqrt(1-h); +adjfactor = repmat(adjfactor,1,n); +xrank = rank(X); % should recompute (?), but this is rough... + +% tiny_s = 1e-6 * mean(std(Y),2); +% if tiny_s==0 +% tiny_s = 1; +% end + +% -------------------------------------- +% * Set up iterations +% -------------------------------------- + +D = sqrt(eps(class(X))); +tol = max(D*max(abs(betas))); +ntol = n; %round(n .* .95); % number of regressions that should have converged before we proceed + +iter = 0; +nconverged = 0; +iterlim = 50; +str = sprintf('Target: %3.0f%% converged. Iterating %03d',100*ntol./n,0); disp(str); + +% -------------------------------------- +% * Iterate to convergence +% -------------------------------------- + +while (iter==0) || (nconverged < ntol) + iter = iter+1; + fprintf(1,'\b\b\b\b%03d',iter); + str2 = sprintf('%3.0f%% converged',round(100*nconverged./n)); disp(str2); + + if (iter>iterlim) + warning('Iteration limit reached.'); + break; + end + + % Compute residuals from previous fit, then compute weights + r = Y - fits; + radjust = r .* adjfactor; + w = bisquare_weight(r,radjust,xrank); + w = mean(w,2); % pool weights over voxels + + oldbetas = betas; + % re-fit betas based on new weights + [betas,invxwx,bform,fits] = get_betas_singleweight(X,Y,w); + + nconverged = sum(1 - any(abs(betas-oldbetas) > tol)); % number of regressions converged + erase_string(str2); +end + +erase_string(str); + +return + + + + + +% -------------------------------------- +% +% * Sub-functions +% +% -------------------------------------- + + + +function [betas,invxwx,bform,fits] = get_betas_singleweight(X,Y,w) + +W = diag(w); % Weight matrix + +%X = repmat(1,m,1); % Design matrix - 1 column of all ones to calculate average +% and, separately, use bcon if that's entered + +invxwx = inv(X'*W*X); +bform = invxwx * X'* W; % beta-forming matrix. hat = X * bform + +% rows are columns of design (X), cols are Y variables +betas = bform*Y; + +if nargout > 3 + fits = X * betas; +end + +return + + + +function w = bisquare_weight(r,radjust,xrank) +% r is residuals +% radjust is adjustment factor: DuMouchel & O'Brien +% xrank is rank of weighted X matrix (design) +% w is weights from bisquare function +% n is number of Y variables to replicate weights over +tuneconst = 4.685; + +r = r .* radjust; +s = mad_sigma_pooled(r,xrank); +r = r ./ (s*tuneconst); +w = (abs(r)<1) .* (1 - r.^2).^2; +return + + +function s = mad_sigma_pooled(r,xrank) +% Compute std estimate using MAD of residuals from 0 +rsort = sort(abs(r)); +rsort = rsort(xrank:end,:); % eliminate smallest; like reducing df +s = median(rsort(:)) / 0.6745; +return + +function str = display_string(str) +str = sprintf(str); fprintf(1,'%s',str); +return + + +function erase_string(str) + +len = length(str); +str2 = repmat('\b',1,len); + +fprintf(1,str2); + +return \ No newline at end of file diff --git a/Statistics_tools/roc_boot.m b/Statistics_tools/roc_boot.m new file mode 100644 index 00000000..3d1df286 --- /dev/null +++ b/Statistics_tools/roc_boot.m @@ -0,0 +1,52 @@ +function [ci, names] = roc_boot(input_vals, binary_outcome, thr, verbose) +% [ci, names] = roc_boot(input_vals, binary_outcome, thr, [verbose flag]) +% +% Returns bootstrapped 95% confidence intervals for sensitivity, +% specificity, and PPV at a given threshold. +% +% thr = ROC.class_threshold +% input_vals = input(ind); +% binary_outcome = outcome(ind); + + +if nargin < 4, verbose = 1; end + +bootfun = @(input_vals, binary_outcome) classification_stats(input_vals, binary_outcome, thr); + +sens_spec_ppv = bootstrp(1000, bootfun, input_vals, binary_outcome); + +% 95% CIs +for i = 1:3 + ci{i} = [prctile(sens_spec_ppv(:, i), 5) prctile(sens_spec_ppv(:, i), 95)]; +end + +names = {'95% CI for sensitivity' '95% CI for specificity' '95% CI for PPV'}; + +if verbose + ssp = bootfun(input_vals, binary_outcome); + + for i = 1:3 + fprintf('%s\t%.0f%% +- (%.0f-%.0f%%)\n', names{i}, ssp(i)*100, ci{i}(1)*100, ci{i}(2)*100); + end + +end + + +end % main function + + +function sens_spec_ppv = classification_stats(input_vals, binary_outcome, thr) + +wh = input_vals >= thr; + +tpr = sum(wh(binary_outcome)) ./ sum(binary_outcome); +fpr = sum(wh(~binary_outcome)) ./ sum(~binary_outcome); + +npos = sum(tpr .* binary_outcome); +nfp = sum(fpr .* ~binary_outcome); +ppv = npos ./ (npos + nfp); + +sens_spec_ppv = [tpr 1-fpr ppv]; + +end + diff --git a/Statistics_tools/roc_calc.m b/Statistics_tools/roc_calc.m new file mode 100644 index 00000000..2aead02d --- /dev/null +++ b/Statistics_tools/roc_calc.m @@ -0,0 +1,89 @@ +function [xvals, tpr, fpr, auc, c_bias] = roc_calc(input_vals, binary_outcome, xvals) +% Calculate Receiver Operating Characteristic plot (ROC) given P-values +% +% function [xvals, tpr, fpr, auc, c_bias] = roc_calc(input_vals or input values, binary_outcome, [xvals : threshold vals to assess]) +% +% input_vals : continuous-valued observations to classify (e.g., fMRI activity) +% +% binary_outcome : 1 / 0 vector of which input observations are "hits" +% +% +% xvals : Criterion values you put in or every 10th percentile of the input +% data distribution by default +% +% tpr : True positive rate for every step of ROC curve (sensitivity) +% fpr : False positive rate (1 - specificity) +% auc : Empirical estimate of area under the ROC curve +% c_bias : c measure of response bias at each step; MacMillan and Creelman 2005 +% +% Examples: +% +% pvals = STATS.WTS.p; % May not work for p-values? may need to convert +% to t or something. +% isnull = DATA.true_weights == 0; +% [xvals, tpr, fpr] = roc_calc(pvals, isnull); +% figure; plot(fpr, tpr, 'ko-','Color', 'k', 'LineWidth', 2); +% +% figure; plot(xvals, fpr, 'bo-') +% hold on; plot([0 1], [0 1], 'k', 'LineWidth', 2); +% set(gca, 'XLim', [0 .2], 'YLim', [0 .2]) +% xlabel('Nominal false positive rate'); +% ylabel('Actual false positive rate'); +% +% See also roc_plot.m + +if ~islogical(binary_outcome), disp('Warning!! binary_outcome must be logical.'); end +binary_outcome = logical(binary_outcome); + +if nargin < 3 || isempty(xvals) + xvals = prctile(xvals, [0:10:100]); +end + +[tpr, fpr] = deal(zeros(size(xvals))); + +indx = 1; +for x = xvals + wh = input_vals >= x; + + tpr(indx) = sum(wh(binary_outcome)) ./ sum(binary_outcome); + fpr(indx) = sum(wh(~binary_outcome)) ./ sum(~binary_outcome); + + indx = indx + 1; +end + +auc = calc_auc(fpr, tpr); + +c_bias = ( norminv(max(.0001, min(0.9999, tpr))) + norminv(max(.0001, min(0.9999, fpr))) ) ./ 2; + +end % function + + + + + +function auc = calc_auc(fpr, tpr) + +[u, wh] = unique(fpr); +u2 = tpr(wh); + +% fix for AUC = 1 if no overlap; triangle method not perfectly accurate +% here. +if any(u == 0 & u2 == 1), auc = 1; return, end + +for i = 2:length(u) + + xdiff = u(i) - u(i - 1); + ydiff = u2(i) - u2(i - 1); + a(i) = xdiff * u2(i - 1) + xdiff * ydiff / 2; % area of rect + area of triangle + +end + + +auc = sum(a); + +end + + + + + diff --git a/Statistics_tools/roc_plot.m b/Statistics_tools/roc_plot.m new file mode 100644 index 00000000..b9b83a72 --- /dev/null +++ b/Statistics_tools/roc_plot.m @@ -0,0 +1,716 @@ +function ROC = roc_plot(input_values, binary_outcome, varargin) +% ROC = roc_plot(input_values, binary_outcome, ['include', include]) +% +% This function makes a specific kind of ROC curve plot, based on input +% values along a continuous distribution and a binary outcome variable +% (logical) +% Include is an optional logical variable of cases to include +% +% Optional inputs: +% --------------------------------------------------------------------- +% 'include' : followed by logical vector of cases to include +% 'threshold' : followed by a priori threshold cutoff for determining misclassification +% 'threshold_type' : followed by thresh type: choices below: +% 'Optimal balanced error rate' +% 'Optimal overall accuracy' [default] +% 'Minimum SDT bias' +% [Enter threshold OR threshold_type] +% +% 'color': followed by color, e.g., 'r' or [1 .5 0] +% 'plotmethod': followed by 'deciles' [default] or 'observed' +% 'nonormfit': suppress normal curve fitting to ROC +% 'plothistograms': plot histograms of the signal present/absent +% distributions +% 'writerscoreplus': Write text file for input into RScorePlus by Lew Harvey +% 'boot' [default]: Bootstrap 95% confidence intervals for sens, spec, PPV at threshold +% 'noboot': Skip bootstrap +% 'balanced': Balanced accuracy for single interval classification +% 'dependent': followed by vector of subject IDs, e.g., ('dependent',[1,1,2,2,3,3]. +% This will perform multilevel version of binomial test for single interval classifation. +% +% Outputs: +% --------------------------------------------------------------------- +% A structure containing the true and false pos rates (tpr, fpr) along the curve +% and the criterion threshold values of the input variable (thr) corresponding to these rates. +% Uses the function roc_calc.m +% +% Also returns some information about misclassified observations +% and line handle for ROC line plot and other statistics: +% area under ROC curve +% accuracy statistics based on binomial test +% PPV +% +% Tor Wager, Feb 2012 +% See Notes in text for more programming details. +% +% Examples: +% ROC = roc_plot(pattern_exp_values, ishot); +% ROC = roc_plot(pattern_exp_values, ishot, 'threshold', 2.5); +% ROC = roc_plot(pattern_exp_values, ishot, 'color', 'r', 'twochoice'); +% ROC = roc_plot(pattern_exp_values, ishot, 'color', 'r', 'twochoice', 'nonormfit'); +% ROC = roc_plot(pexp, logical(outcome), 'color', 'g', 'plothistograms', 'threshold', 0.3188); +% ROC = roc_plot(pexp, logical(outcome), 'twochoice', 'color', 'b', 'plothistograms'); +% ROC = roc_plot(pexp, logical(outcome), 'writerscoreplus'); +% ROC = roc_plot(pexp, logical(outcome), 'color', 'r', 'plotmethod', 'observed', 'plothistograms'); +% ROC = roc_plot(pexp, logical(outcome), 'color', 'm', 'plotmethod', 'observed', 'plothistograms', 'Optimal overall accuracy'); +% +% Notes: +% Edited 3/17/2012 to add standard Gaussian signal detection fit curves, +% effect size estimates based on Gaussian equal variance model +% +% Edited 3/20/2012 to fix AUC estimate and add/change output. +% +% Tested 3/20/12 against RScorePlus. There are some consequences of +% binning for input to RScorePlus, inclding that the "response" criteria are inferred +% in RScorePlus, but they are given if we are selecting arbitrary criteria based on +% continuous measures (e.g., brain activity). This influences the actual +% estimates of the mean and std. signal distributions, as well as the +% sens/spec estimates within response bins. This function does not use +% arbitrary bins whenever possible, and uses the full ROC across thresholds +% corresponding to each unique increment in specificity for AUC +% calculation. +% +% Edited 3/11/2014: Luke Chang to add balanced accuracy option for single +% interval classification when classes are unbalanced +% +% Edited 6/24/2014: Luke Chang to add multilevel binomial test for single +% interval classification. Assumes each subject has equal number of +% trials. Requires at least more than 20 subjects to ensure distribution +% is reasonably approximated by normal distribution. Uses one sample-test +% across subjects. + +include = true(size(binary_outcome)); +threshold_type = 'Optimal overall accuracy'; +class_thr = []; +color = [.2 .2 .2]; +plotmethod = 'deciles'; %'npoints'; % 'observed'; +donormfit = 1; +istwochoice = 0; +reportstats90 = 0; +plothistograms = 0; +writerscoreplus = 0; +doboot = 1; +dobalanced = 0; +doDependent = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'include', include = logical(varargin{i+1}); + + case 'threshold' + class_thr = varargin{i + 1}; + threshold_type = 'A priori threshold'; + + case {'threshold_type'} + threshold_type = varargin{i + 1}; varargin{i + 1} = []; + + case {'Optimal overall accuracy', 'Optimal balanced error rate', 'Minimum SDT bias'} + threshold_type = varargin{i}; + + case 'color' + color = varargin{i + 1}; varargin{i + 1} = []; + + case 'plotmethod' + plotmethod = varargin{i + 1}; varargin{i + 1} = []; + + case 'nonormfit' + donormfit = 0; + + case {'twochoice', 'forcedchoice', 'pairedobservations'} + istwochoice = 1; + disp('ROC for two-choice classification of paired observations.') + disp('Assumes pos and null outcome observations have the same subject order.') + disp('Using a priori threshold of 0 for pairwise differences.') + + case 'reportstats90', reportstats90 = 1; + + case 'plothistograms', plothistograms = 1; + + case 'boot', doboot = 1; + case 'noboot', doboot = 0; + + case 'balanced' + dobalanced = 1; + + case 'dependent' + doDependent = 1; + subject_id = varargin{i + 1}; + if(~ismatrix(subject_id) || ~isnumeric(subject_id) || length(input_values)~=length(subject_id)) + error('Make sure ''dependent'' flag is followed by valid subject_id vector') + end + + disp('ROC for single interval classification of paired observations.') + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if length(include) ~= length(binary_outcome) || ~any(include) + error('Problem with include variable') +end + +input_values = input_values(include); +binary_outcome = logical(binary_outcome(include)); + +% Deal with paired observations +% ------------------------------------------------------------------------- + +if istwochoice + % Adjust input scores to reflect differences from the mean of each pair + % This allows us to do forced-choice classification for pairs based on + % which is higher. The higher one will always be above the mean. + % The threshold used here should be zero. + + meanscores = (input_values(binary_outcome) + input_values(~binary_outcome)) ./ 2; + + input_values(binary_outcome) = input_values(binary_outcome) - meanscores; + input_values(~binary_outcome) = input_values(~binary_outcome) - meanscores; + + threshold_type = 'A priori threshold'; + class_thr = 0; +end + + + +% Get ROC values and main results +% ------------------------------------------------------------------------- + +thr = linspace(min(input_values), max(input_values), 50*length(binary_outcome)); %min(input_values):.01:max(input_values); + +% AUC will be replaced with theoretical value in 2AFC case! +[dummy, tpr, fpr, auc, c_bias] = roc_calc(input_values, binary_outcome, thr); + +% count signal present and signal absent +n1 = sum(~isnan(input_values(binary_outcome)) & ~isinf(input_values(binary_outcome))); +n0 = sum(~isnan(input_values(~binary_outcome)) & ~isinf(input_values(~binary_outcome))); + +% Get criterion threshold for final stats output +% ------------------------------------------------------------------------- + +switch threshold_type + case 'Optimal balanced error rate' + + avg = mean([tpr; 1-fpr]); + [dummy, wh] = max(avg); + class_thr = thr(wh); + + case 'A priori threshold' + + % Report a priori threshold + % wh = find(thr <= class_thr); + % wh = wh(end); + + case 'Optimal overall accuracy' + + ncorrt = tpr .* n1; + ncorrf = (1 - fpr) .* n0; + + mysum = sum([ncorrt; ncorrf]); + [dummy, wh] = max(mysum); + class_thr = thr(wh); + + case 'Minimum SDT bias' + [dummy, wh] = min(abs(c_bias)); + class_thr = thr(wh); + + otherwise + error('Unknown threshold type') +end + + +% Save stuff +% ------------------------------------------------------------------------- + +ROC.all_vals.thr = thr; + +ROC.class_threshold = class_thr; +ROC.sensitivity = sum(input_values(binary_outcome) >= class_thr) ./ n1; % go back to original thresh for precision when using specific input thresh +ROC.specificity = 1 - ( sum(input_values(~binary_outcome) >= class_thr) ./ n0 ); +ROC.AUC = auc; +ROC.AUC_descrip = 'Numerically integrated, nonparametric area under curve'; + +% vectors of true/false positives and accuracy +if istwochoice + falseneg = (input_values <= class_thr & binary_outcome); % Wani added this line to fix an error when two values are same (in the two-choice test) +else + falseneg = (input_values < class_thr & binary_outcome); +end +falsepos = (input_values >= class_thr & ~binary_outcome); +misclass = falseneg | falsepos; +truepos = binary_outcome & ~misclass; +trueneg = ~binary_outcome & ~misclass; + +ROC.threshold_type_for_misclass = threshold_type; + + +if istwochoice + % Reshape to reflect pairs for stats/output + sz = [length(misclass) ./ 2 2]; + + if any(sz ~= round(sz)) + disp('Two-choice classification assumes you enter paired observations in order:') + disp('signal present for obs (1:n) followed by signal absent for (1:n) in the same order or vice versa.') + error('The input has the wrong size.') + end + + truepos = truepos(binary_outcome); + trueneg = trueneg(~binary_outcome); + falseneg = falseneg(binary_outcome); + falsepos = falsepos(~binary_outcome); + misclass = falsepos | falseneg; + + if any(truepos & falsepos) || any(trueneg & falseneg) + disp('Two-choice classification assumes you enter paired observations in order:') + disp('signal present for obs (1:n) followed by signal absent for (1:n) in the same order or vice versa.') + error('Inconsistent output: The observations are likely not in order.') + end + +end + +% Stuff for figuring out which points are misclassified + +ROC.observations.truepos = truepos; +ROC.observations.trueneg = trueneg; +ROC.observations.falseneg = falseneg; +ROC.observations.falsepos = falsepos; +ROC.observations.misclass = misclass; + +ROC.PPV = sum(ROC.observations.truepos) ./ (sum(ROC.observations.truepos) + sum(ROC.observations.falsepos)); + +% Accuracy stats +if ~dobalanced + accuracy = 1 - (sum(misclass) ./ length(misclass)); +else + accuracy = (sum(truepos)/(sum(truepos) + sum(falseneg)) + sum(trueneg)/(sum(trueneg) + sum(falsepos)))/2; +end + +ROC.accuracy = accuracy; + +if ~doDependent + RES = binotest(double(~misclass), .5); +else %Run hierarchical version of binomial test + + %Create new subject matrix + subID = unique(subject_id); + for i = 1:length(subID) + sdat(i,:) = ~misclass(subject_id == subID(i)); + end + [RES1, RES2, RES3, RES4] = binotest_dependent(sdat,.5); + RES = RES4; + +end +ROC.N = RES.n; +ROC.accuracy_p = RES.p_val; +ROC.accuracy_se = RES.SE; + +% Stuff for re-plotting full ROC +ROC.all_vals.tpr = tpr; +ROC.all_vals.fpr = fpr; + +% ------------------------------------------------------------------------- +% Plot stuff +% +% Get ROC values for plot - default is to plot 10 points +% ------------------------------------------------------------------------- + +switch plotmethod + case 'deciles' + plotthr = prctile(input_values, [10 20 30 40 50 60 70 80 90]); + [dummy, plottpr, plotfpr] = roc_calc(input_values, binary_outcome, plotthr); + plotsymbol = 'o'; + linewid = 2; + + case 'observed' + plotthr = thr; + plottpr = tpr; + plotfpr = fpr; + plotsymbol = '-'; + linewid = 3; +end + + + +han = plot(plotfpr, plottpr, plotsymbol, 'Color', color, 'LineWidth', linewid); + +set(gca, 'FontSize', 32, 'XLim', [-.02 1.02], 'XTick', 0:.2:1, 'YTick', 0:.2:1); +xlabel('(1 - Specificity)'); +ylabel('Sensitivity') + + +inline_plothistograms(); + + +% effect size +% ------------------------------------------------------------------------- + +if istwochoice + % Two-alternative forced choice + % ------------------------------------------------------------- + + diffscores = input_values(binary_outcome) - input_values(~binary_outcome); + + meandiff = mean(diffscores); + d = meandiff ./ std(diffscores); + + % forced-choice is variance of the difference, which is 2 * pooled variance + % std of difference therefore = pooledsd * sqrt(2), and pooledsd = std(diffs)/sqrt(2) + pooledsd = std(diffscores) ./ sqrt(2); + + d_a_model = meandiff ./ pooledsd; % estimate of what d_a would be for single-interval + + % From Lew Harvey's notes - this should be the "observed" d_a based on + % empirical accuracy. But it will be inaccurate as accuracy approaches + % 1. Use "model" because it's closer to the data, no norminv inaccuracy + d_a_obs = sqrt(2) * norminv(ROC.accuracy); + + if donormfit + % standard equal-variance signal detection + x = [-3:.1:3]; + tprn = 1 - normcdf(x, d, 1); + fprn = 1 - normcdf(x, -d, 1); + hold on; + han = [han plot(fprn, tprn, '-', 'Color', color, 'LineWidth', linewid)]; + end + + aucn = calc_auc(fprn, tprn); + expected_acc = 1 - normcdf(0, d, 1); % expected to be = to the AUC! + + ROC.Gaussian_model.type = 'Two-alternative forced choice'; + ROC.Gaussian_model.names = {'diff_scores'}; + ROC.Gaussian_model.means = meandiff; + ROC.Gaussian_model.n = length(diffscores); + ROC.Gaussian_model.sd = std(diffscores); + ROC.Gaussian_model.d_a = d_a_model; + ROC.Gaussian_model.d_a_descrip = '''Observed'' d_a based on empirical accuracy.'; + ROC.Gaussian_model.sensitivity = expected_acc; + ROC.Gaussian_model.specificity = expected_acc; + + % PPV is just the accuracy, too. + ROC.Gaussian_model.PPV = ROC.Gaussian_model.sensitivity ./ (ROC.Gaussian_model.sensitivity + 1 - ROC.Gaussian_model.specificity); + + ROC.Gaussian_model.AUC_numerical_integration = aucn; + ROC.Gaussian_model.AUC = normcdf(d_a_model/sqrt(2)); % should just invert the accuracy equation. ...and yes, it does. same as accuracy. + +else + % single-interval + % ------------------------------------------------------------ + % N1 = length(input_values(binary_outcome)); done above + % N2 = length(input_values(~binary_outcome)); + + meanpres = mean(input_values(binary_outcome)); + meanabs = mean(input_values(~binary_outcome)); + + v1 = var(input_values(binary_outcome)); + v0 = var(input_values(~binary_outcome)); + + pooledsd = sqrt((v1.*(n1-1) + v0.*(n0-1)) ./ (n1 + n0 - 2)); + + d = (meanpres - meanabs) ./ pooledsd; + + zpres = meanpres ./ pooledsd; + zabs = meanabs ./ pooledsd; + + if donormfit + % integrate across PDFs for each of signal absent, signal present distributions + x = [zabs-3:.1:zpres+3]; % relative to null dist + tprn = 1 - normcdf(x, zpres, 1); + fprn = 1 - normcdf(x, zabs, 1); + hold on; + han = [han plot(fprn, tprn, '-', 'Color', color, 'LineWidth', linewid)]; + end + + aucn = calc_auc(fprn, tprn); + + ROC.Gaussian_model.type = 'Single-interval'; + ROC.Gaussian_model.names = {'sig. abs.' 'sig. pres.'}; + ROC.Gaussian_model.means = [meanabs meanpres]; + ROC.Gaussian_model.n = [n0 n1]; + ROC.Gaussian_model.sd = [sqrt(v0) sqrt(v1)]; + ROC.Gaussian_model.mean_diff = meanpres - meanabs; + ROC.Gaussian_model.pooledsd = pooledsd; + ROC.Gaussian_model.d_a = d; + ROC.Gaussian_model.sensitivity = 1 - normcdf(class_thr, meanpres, sqrt(v1)); + ROC.Gaussian_model.specificity = normcdf(class_thr, meanabs, sqrt(v0)); + ROC.Gaussian_model.PPV = ROC.Gaussian_model.sensitivity ./ (ROC.Gaussian_model.sensitivity + 1 - ROC.Gaussian_model.specificity); + + ROC.Gaussian_model.AUC_numerical_integration = aucn; + ROC.Gaussian_model.AUC = normcdf(d/sqrt(2)); + + +end + +ROC.line_handle = han; + +% Boostrap, if asked for +% ------------------------------------------------------------------------- +if doboot + + [ci, names] = roc_boot(input_values, binary_outcome, ROC.class_threshold, 0); + + ROC.sensitivity_ci = ci{1}; + ROC.specificity_ci = ci{2}; + ROC.PPV_ci = ci{3}; + +end + + + + +% fprintf('\nROC_PLOT Output: %s, %s\n', ROC.Gaussian_model.type, threshold_type); +% +% fprintf(' Nonparametric AUC:\t%3.2f\tParametric d_a:\t%3.2f\n', ROC.AUC, ROC.Gaussian_model.d_a); +% +% fprintf(' Threshold:\t%3.2f\tSens:\t%3.0f%%\tSpec:\t%3.0f%%\tPPV:\t%3.0f%%\n', ... +% ROC.class_threshold, 100*ROC.sensitivity, 100*ROC.specificity, 100*ROC.PPV); +% +% fprintf(' Accuracy:\t%3.0f%% +- %3.1f%% (SE), P = %3.6f\n', ... +% 100*ROC.accuracy, 100*ROC.accuracy_se, ROC.accuracy_p); + +% Single line format +fprintf('\nROC_PLOT Output: %s, %s\n', ROC.Gaussian_model.type, threshold_type); + +if doboot + fprintf('Threshold:\t%3.2f\tSens:\t%3.0f%% CI(%.0f%%-%.0f%%)\tSpec:\t%3.0f%% CI(%.0f%%-%.0f%%)\tPPV:\t%3.0f%% CI(%.0f%%-%.0f%%)\t', ... + ROC.class_threshold, 100*ROC.sensitivity, 100*ROC.sensitivity_ci, 100*ROC.specificity, 100*ROC.specificity_ci, 100*ROC.PPV, 100*ROC.PPV_ci); +else + fprintf('Threshold:\t%3.2f\tSens:\t%3.0f%%\tSpec:\t%3.0f%%\tPPV:\t%3.0f%%\t', ... + ROC.class_threshold, 100*ROC.sensitivity, 100*ROC.specificity, 100*ROC.PPV); +end + +fprintf('Nonparametric AUC:\t%3.2f\tParametric d_a:\t%3.2f\t', ROC.AUC, ROC.Gaussian_model.d_a); + +fprintf(' Accuracy:\t%3.0f%% +- %3.1f%% (SE), P = %3.6f\n', ... + 100*ROC.accuracy, 100*ROC.accuracy_se, ROC.accuracy_p); + + +% Report stats (max sens) at 90% specificity +if reportstats90 + + ROC = report_at_threshold_spec(); + +end + + +% Calculate statistics by criterion threshold value +% Parallels output of RSCOREplus by Lew Harvey +% ------------------------------------------------------------------------- + +bins = [-Inf plotthr Inf]; % bin edges: EDGES(k) <= X(i) < EDGES(k+1) +npoints = length(bins); +s0 = histc(input_values(~binary_outcome), bins)'; +s1 = histc(input_values(binary_outcome), bins)'; + +for i = 1:length(bins) + %bin_names{i} = sprintf('R_%3.1f', bins(i)); + bin_names{i} = sprintf('R_%d', i); +end + +ROC.Binned_output.s0 = s0; +ROC.Binned_output.s1 = s1; +ROC.Binned_output.bin_edges = bins; +ROC.Binned_output.bin_names = bin_names; + +ROC.Binned_output.spec_bins = cumsum(s0) ./ sum(s0); +ROC.Binned_output.sens_bins = 1 - (cumsum(s1) ./ sum(s1)); +ROC.Binned_output.PPV_bins = (ROC.Binned_output.sens_bins .* sum(s1)) ./ (ROC.Binned_output.sens_bins .* sum(s1) + (1 - ROC.Binned_output.spec_bins) .* sum(s0)); + + + + +if writerscoreplus + + write_rscoreplus_input() + +end + +% INLINE FUNCTIONS +% ------------------------------------------------------------------------- + + + + function inline_plothistograms() + + if plothistograms + + if ~isempty(findobj(get(0, 'Children'), 'type', 'figure')) + returncurrfig = 1; + end + + if returncurrfig + figh = gcf; + end + + % plot histograms... + + create_figure('distributions'); + h = histfit(input_values(binary_outcome), 20); + hbar = get(h(1), 'Children'); + set(hbar, 'FaceAlpha', .3, 'FaceColor', [0 0 1], 'EdgeColor', 'none'); + % changing in diff versions of matlab... + if isempty(hbar) + set(h(1), 'FaceAlpha', .3, 'FaceColor', [0 0 1], 'EdgeColor', 'none'); + end + + set(h(2), 'Color', [0 0 1]); + + hold on; + h = histfit(input_values(~binary_outcome), 20); + hbar = get(h(1), 'Children'); + set(hbar, 'FaceAlpha', .3, 'FaceColor', [0 1 0], 'EdgeColor', 'none'); + + % changing in diff versions of matlab... + if isempty(hbar) + set(h(1), 'FaceAlpha', .3, 'FaceColor', [0 1 0], 'EdgeColor', 'none'); + end + + set(h(2), 'Color', [0 .4 0]); + + h = plot_vertical_line(class_thr); + set(h, 'Color', 'k', 'LineStyle', '-', 'LineWidth', 2) + + if returncurrfig + figure(figh) + end + + end + + + end % inline + + + + function write_rscoreplus_input() + + fname = 'roc_rscoreplus_input.txt'; + disp('Writing RScorePlus input .txt file: %s\n', fname); + + fid = fopen(fullfile(pwd, fname), 'w+'); + if fid == -1, disp('Cannot open rscoreplus_input.txt file. Skipping.'); return, end + + fprintf(fid, 'Heading\nroc_plot.m_output\n'); + + % parameters => see rscore plus help on lew harvey's website + fprintf(fid, '%01d\t%01d\t%01d\t%01d\t%01d\t%01d\t', length(bins), 2, 1, 0, 0, 1); + + if istwochoice, paradigmstr = 'MAFC'; else paradigmstr = 'SINT'; end + fprintf(fid, '%s\n', paradigmstr); + + fprintf(fid, 'labels\t'); + fprintf(fid, '%s\t', bin_names{:}); + fprintf(fid, '\n'); + + fprintf(fid, 's0\t'); + fprintf(fid, '%d\t', s0); + fprintf(fid, '\n'); + + fprintf(fid, 's1\t'); + fprintf(fid, '%d\t', s1); + fprintf(fid, '\n'); + + fprintf(fid, '%3.1f\t%3.1f\t%3.1f\t%3.1f\n', 0, 1, .5, 1); % params of reference dist? + fprintf(fid, '%01d\t%01d\t%01d\t%01d\n', 0, 0, 1, 1); % params of reference dist? + + fprintf(fid, 'end of file\n-1\n'); + fclose(fid); + + end % inline + + + + function report_at_threshold_spec + wh = find(fpr <= .1); + [mymax, wh2] = max(tpr(wh)); + wh = wh(wh2); + if ~isempty(wh), wh = wh(1); else wh = NaN; end + + npos = sum(tpr(wh) .* binary_outcome); + nfp = sum(fpr(wh) .* ~binary_outcome); + ppv = npos ./ (npos + nfp); + + if isnan(wh) + fprintf('At 90+%% Spec: Thresh = %3.2f, Sensitivity = %3.0f%%, Specificity = %3.0f%%, PPV = %3.0f%%\n', NaN, NaN, NaN, NaN); + + [ROC.thresh_90_percent_spec, ROC.sens_90_percent_spec] = deal(NaN); + else + + fprintf('At 90+%% Spec: Thresh = %3.2f, Sensitivity = %3.0f%%, Specificity = %3.0f%%, PPV = %3.0f%%\n', thr(wh), tpr(wh)*100, (1-fpr(wh))*100, ppv * 100); + + ROC.thresh_90_percent_spec = thr(wh); + ROC.sens_90_percent_spec = tpr(wh); + end + + wh = find(fpr <= .05); + [mymax, wh2] = max(tpr(wh)); + wh = wh(wh2); + if ~isempty(wh), wh = wh(1); else wh = NaN; end + + if isnan(wh) + fprintf('At 95+%% Spec: Thresh = %3.2f, Sensitivity = %3.0f%%, Specificity = %3.0f%%, PPV = %3.0f%%\n', NaN, NaN, NaN, NaN); + + [ROC.thresh_95_percent_spec, ROC.sens_95_percent_spec] = deal(NaN); + + else + fprintf('At 95+%% Spec: Thresh = %3.2f, Sensitivity = %3.0f%%, Specificity = %3.0f%%, PPV = %3.0f%%\n', thr(wh), tpr(wh)*100, (1-fpr(wh))*100, ppv * 100); + + ROC.thresh_95_percent_spec = thr(wh); + ROC.sens_95_percent_spec = tpr(wh); + end + + end % inline + + +end % main function + +% +% function [xvals, tpr, fpr] = roc_calc(input_vals, binary_outcome, xvals) +% % Calculate Receiver Operating Characteristic plot (ROC) given P-values +% % +% % function [xvals, tpr, fpr] = roc_calc(input_vals or input values, input_vals, [treshold vals to assess]) +% % +% % Modified from roc_calc.m in scnlab tools +% % Tor Wager, 2012 +% +% +% [tpr, fpr] = deal(zeros(size(xvals))); +% +% indx = 1; +% for x = xvals +% wh = input_vals >= x; +% +% tpr(indx) = sum(wh(binary_outcome)) ./ sum(binary_outcome); +% fpr(indx) = sum(wh(~binary_outcome)) ./ sum(~binary_outcome); +% +% indx = indx + 1; +% end +% +% end % function + + + + +function auc = calc_auc(fpr, tpr) + +[u, wh] = unique(fpr); +u2 = tpr(wh); + +% fix for AUC = 1 if no overlap; triangle method not perfectly accurate +% here. +if any(u == 0 & u2 == 1), auc = 1; return, end + +for i = 2:length(u) + + xdiff = u(i) - u(i - 1); + ydiff = u2(i) - u2(i - 1); + a(i) = xdiff * u2(i - 1) + xdiff * ydiff / 2; % area of rect + area of triangle + +end + + +auc = sum(a); + +end + + diff --git a/Statistics_tools/rsquare_calc.m b/Statistics_tools/rsquare_calc.m new file mode 100644 index 00000000..4e2f3228 --- /dev/null +++ b/Statistics_tools/rsquare_calc.m @@ -0,0 +1,22 @@ +function rsquare = rsquare_calc(X, Y) +% +% rsquare(X, y) +% +% Returns variance in each col. of Y explained by X + +% make sure X has intercept +X = intercept(X, 'end'); + +for i = 1:size(Y, 2) + + y = Y(:,i); + + b = X\y; + + fits = X * b; + + rsquare(i) = var(fits) / var(y); + +end + +end \ No newline at end of file diff --git a/Statistics_tools/rsquare_multiple_regions_multilevel.m b/Statistics_tools/rsquare_multiple_regions_multilevel.m new file mode 100644 index 00000000..ad8832b3 --- /dev/null +++ b/Statistics_tools/rsquare_multiple_regions_multilevel.m @@ -0,0 +1,227 @@ +function stats = rsquare_multiple_regions_multilevel(Y, X, varargin) +% +% stats = rsquare_multiple_regions_multilevel(Y, X, varargin) +% +% Tor Wager, June 2008 +% Predict outcome var (y) using data from multiple vars (X, e.g., brain regions) +% Test R-square against permuted data +% +% Variable args: +% 'colors', followed by colors cell ({'r' 'g' 'b'}) for each col. of X +% 'nperms', then num perms +% +% var args not all done yet (in development). +% +% Example: +% %% SETUP data +% +% cl = [clpos_data clneg_data]; +% +% cl(cat(1, cl.numVox) < min_cluster_size) = []; +% +% % get brain data cell +% +% for i = 1:size(cl(1).all_data, 2) +% for c = 1:length(cl) +% data{i}(:,c) = cl(c).all_data(:, i); +% end +% end +% +% %% NMDS ANALYSIS +% OUT = []; +% +% OUT.ridge = matrix_direct_effects_ridge(data); +% D = OUT.ridge.mean; D(find(eye(size(D)))) = 1; +% D = (D' + D) ./ 2; +% OUT.ridge.D = (1 - D) ./ 2; +% [OUT.stats_mds.GroupSpace,OUT.stats_mds.obs,OUT.stats_mds.implied_dissim] = shepardplot(OUT.ridge.D,[]); +% +% OUT.stats_mds = nmdsfig_tools('cluster_solution',OUT.stats_mds, OUT.stats_mds.GroupSpace, 2:max_networks, nperms, []); +% OUT.stats_mds.colors = {'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; +% create_figure('nmdsfig'); +% +% OUT.stats_mds.names = []; +% nmdsfig(OUT.stats_mds.GroupSpace,'classes',OUT.stats_mds.ClusterSolution.classes,'names',OUT.stats_mds.names,'sig',OUT.ridge.fdrsig); +% hh = nmdsfig_fill(OUT.stats_mds); +% axis image; axis equal +% +% % +% %% Multiple regions predict behavior +% +% % Design matrix with cluster averages +% classes = OUT.stats_mds.ClusterSolution.classes; +% clear X +% for i = 1:length(data) +% +% for j = 1:max(classes) +% +% X{i}(:, j) = nanmean(data{i}(:, classes == j), 2); +% +% end +% +% end +% +% +% OUT.stats_regression = rsquare_multiple_regions_multilevel(acc_x_dist, X, 'colors', OUT.stats_mds.colors, 'nperms', 100); +% + + +nperms = 100; + + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'colors', colors = varargin{i+1}; + + case 'nperms', nperms = varargin{i + 1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +%% initial model fits, all together, then separately to get order +stats = glmfit_multilevel(Y, X, ones(length(X), 1), 'weighted'); + +nvars = size(X{1}, 2); + +for i = 1:nvars + for s = 1:length(X) + Xr{s} = [X{s}(:, i)]; % reduced model, with only this regressor. + end + + stats_ind = glmfit_multilevel(Y, Xr, ones(length(X), 1), 'weighted'); + stats.separate_reg_pvals(i) = stats_ind.p(2); + +end + +%%% **** Notes: should do best 1, then best combo of 2, best 3...etc **** +%%% permute additional param after best combo from prev. step + +%% Predicting using variable # regions +[tmp, myorder] = sort(stats.separate_reg_pvals); % eliminate intercept +stats.myorder = myorder; + +for i = 1:length(myorder) + + for s = 1:length(X) + + Xr{s} = [ones(size(X{s}, 1), 1) X{s}(:, myorder(1:i))]; % reduced model, with regressors ordered by predictive accuracy. + + [B,BINT,R,RINT,sstats] = regress(Y{s}, Xr{s}); + + r2(s, i) = sstats(1); + + end + +end + +create_figure('R-squared'); barplot_columns(r2, 'R-squared', [], 'nofig', 'noind'); +xlabel('Number of networks in model'); +ylabel('Average R^2 value'); + +if exist('colors', 'var') && ~isempty(colors) + + for i = 1:size(r2, 2) + hbar(i) = bar(i, nanmean(r2(:, i)), 'FaceColor', colors{myorder(i)}(1)); + end + +end + +stats.observed_r2 = r2; + +drawnow + +%% permutation + +clear permindx +for i = 1:length(X) + permindx{i} = permute_setupperms(size(X{i}, 1), nperms); + + %Xrp{s} = [ones(size(X{s}, 1), 1) X{s}(:, myorder(1:i))]; % reduced model, with regressors ordered by predictive accuracy. + +end + +[r2meanp, b1mean] = deal(zeros(nperms, length(myorder))); + + +%% +for p = 1:nperms + + fprintf('%3.0f ', p); + + if mod(p, 20) == 0, fprintf(1, '\n'); end + + [r2, bb] = deal(zeros(length(X), length(myorder))); + + for i = 1:length(myorder) + + for s = 1:length(X) + + % Method 1: Permute all variables (permute data) + Xr{s} = [ones(size(X{s}, 1), 1) X{s}(:, myorder(1:i))]; % reduced model, with regressors ordered by predictive accuracy. + + Yp{s} = Y{s}(permindx{s}(p, :)', :); % permute + +% % % Method 2: Permute only the i:endth regressors +% % % part to not permute: conditional on this being in model +% % Xsnp = X{s}(:, myorder(1:i-1)); +% % +% % % part to permute: if everything else were random... +% % Xsp = X{s}(:, myorder(i:end)); +% % Xsp = Xsp(permindx{s}(p, :)', :); % permute +% % Xr{s} = [ones(size(X{s}, 1), 1) Xsnp Xsp]; % reduced model, with regressors ordered by predictive accuracy. +% % Yp{s} = Y{s}; + + [B,BINT,R,RINT,sstats] = regress(Yp{s}, Xr{s}); + + r2(s, i) = sstats(1); + + bb(s, i) = B(2); + + % % Xrp_ml{s} = [X{s}(permindx{s}(p, :)', myorder(1:i))]; % reduced model, with regressors ordered by predictive accuracy. + + end + + % % stats = glmfit_multilevel(Y, Xrp_ml, ones(length(X), 1), 'weighted', 'noverbose'); + % % multilev_pvals{i}(p, :) = stats.p; + + end + + r2meanp(p, :) = nanmean(r2); + + b1mean(p, :) = nanmean(bb); + +end + +fprintf(1, '\n'); + +bar((1:nvars) + .2, mean(r2meanp), 'FaceColor', [.2 .2 .2]); + +%% Z-scores and p-values based on perm dist +fp = r2meanp > repmat(nanmean(stats.observed_r2, 1), nperms, 1); + +stats.permute.pvals = sum(double(fp), 1) ./ nperms; + +yval = nanmean(stats.observed_r2) + .15 * nanmean(stats.observed_r2); + +wh = find(stats.permute.pvals <= .001); +if ~isempty(wh), for i = 1:length(wh), text(wh(i), yval(wh(i)), '***', 'FontSize', 18); end, end + +wh = find(stats.permute.pvals <= .01 & stats.permute.pvals > .001); +if ~isempty(wh), for i = 1:length(wh), text(wh(i), yval(wh(i)), '**', 'FontSize', 18); end, end + +wh = find(stats.permute.pvals <= .05 & stats.permute.pvals > .01); +if ~isempty(wh), for i = 1:length(wh), text(wh(i), yval(wh(i)), '*', 'FontSize', 18); end, end + +%% +disp('Added .permute field to stats output strucuture.'); +stats.permute.nperms = nperms; +stats.permute.r2meanp = r2meanp; +stats.permute.r2mean_ub = prctile(stats.permute.r2meanp, 95); +stats.permute.permindx = permindx; + +stats.permute.b1mean = b1mean; \ No newline at end of file diff --git a/Statistics_tools/scn_stats_helper_functions.m b/Statistics_tools/scn_stats_helper_functions.m new file mode 100644 index 00000000..f5de12a8 --- /dev/null +++ b/Statistics_tools/scn_stats_helper_functions.m @@ -0,0 +1,1377 @@ +function varargout = scn_stats_helper_functions(meth, varargin) +% +% Helper functions for stats routines, plotting, and printing +% +% Available methods +% 'print' : print outcome table from stats structure +% 'gls' : GLS function, weighted or unweighted; with AR model if +% specified as last input +% 'boot' : Boostrapping of GLS +% 'signperm': Sign permutation test for intercept of GLS +% +% 'plot' : +% +% 'loess_xy' : loess plots of multilevel X and Y data +% X is cell array with X data for each subject, Y is cell array, same format +% 'xycatplot' +% 'loess_partial' +% 'xyplot' % multilevel line plot of x vs y within subjects, grouping trials by +% optional G variable. X can be categorical or continuous. +% +% 'xytspanelplot' : X and Y are timeseries data; separate panel plot for each +% cell +% +% FORMAT STRINGS +% -------------------------------------------------------------------------------------------- +% scn_stats_helper_functions('print', stats, stats_within) +% See glmfit_general for context and usage +% +% [b, s2between_ols, stats] = scn_stats_helper_functions('gls', Y, W, X) +% [b, s2between_ols] = scn_stats_helper_functions('gls', Y, W, X, arorder); +% See glmfit_general for context and usage +% +% stats = scn_stats_helper_functions('boot', Y, W, X, bootsamples, stats, whpvals_for_boot, targetu, verbose ) +% stats = scn_stats_helper_functions('boot', Y, stats.W, X, 1000, stats, 1:size(Y,2), .005, 1 ) +% +% stats = scn_stats_helper_functions('signperm', Y, W, X, nperms, stats, permsign, verbose ) +% stats = scn_stats_helper_functions('signperm', Y, W, X, 5000, stats, [], 1 ); +% +% stats = scn_stats_helper_functions('xycatplot', X, Y); +% stats_plot = scn_stats_helper_functions('xycatplot', stats.inputOptions.X, stats.inputOptions.Y, 'weighted', 'samefig'); +% +% scn_stats_helper_functions('xyplot', data(1:19), SETUP.data.Y, 'weighted', 'groupby', G, 'colors', {'y' [.2 .2 .2]});'y' +% +% Example: using sign permutation test +% -------------------------------------------------------------------------------------------- +% *Note: Out of memory errors for large n! +% +% Y = randn(20, 4); X = Y(:,1) + randn(20,1); X = [ones(size(X)) X]; +% first_lev_var = rand(20, 4); +% stats = glmfit_general(Y, X, 'weighted', 's2', first_lev_var, 'dfwithin', 20, 'verbose'); +% W = stats.W; +% stats = scn_stats_helper_functions('signperm', Y, W, X, 5000, stats, [], 1 ); +% % Re-run using already set-up permsign: +% stats = scn_stats_helper_functions('signperm', Y, W, X, 5000, stats, stats.permsign, 1 ); +% +% +% scn_stats_helper_functions('loess_xy', stats.inputOptions.X, stats.inputOptions.Y) +% +% +% Example: Loading mediation clusters and making line plot +% G = SETUP.data.X; +% whcl = 30; cluster_orthviews(clneg_data{1}(whcl)); +% clear data, for i = 1:length(clneg_data), data{i} = clneg_data{i}(whcl).timeseries; end +% scn_stats_helper_functions('xyplot', data, SETUP.data.Y, 'weighted', 'groupby', G, 'colors', {'y' [.2 .2 .2]}); +% scn_stats_helper_functions('xyplot', SETUP.data.Y, data, 'weighted', 'groupby', G, 'colors', {'y' [.4 .4 .4]}); xlabel('Report'); ylabel('Brain'); +% +% Called by: +% mediation.m +% glmfit_general.m +% +% Tor Wager, Sept 2007 + + + + +switch meth + + + % descriptives + % ------------------------------------------ + case 'means' + + X = []; % matrix of categorical selection variables + G = []; % indicator for cases to select + Y = []; % outcome + mymeans = []; + mystes = []; + gval = []; + mycounts = []; + mylevels = cell(1, 2); + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case {'X'}, X = varargin{i+1}; + case {'G', 'S'}, G = varargin{i+1}; + case {'Y'}, Y = varargin{i+1}; + case 'gval', gval = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if iscell(X) + X = cat(1, X{:}); + end + + if iscell(Y) + Y = cat(1, Y{:}); + end + + if iscell(G) + G = cat(1, G{:}); + end + + % select cases + if ~isempty(G) + if isempty(gval) + G = logical(G); + X = X(G, :); + Y = Y(G, :); + else + X = X(G == gval, :); + Y = Y(G == gval, :); + end + end + + for i = 1:size(X, 2) + mylevels{i} = sort(unique(X(:, i))); + end + + % Adjust if no second var entered + if isempty(mylevels{2}) + X(:, 2) = 1; + mylevels{2} = 1; + end + + for i = 1:length(mylevels{1}) + + wh{1} = X(:, 1) == mylevels{1}(i); + + for j = 1:length(mylevels{2}) + + wh{2} = X(:, 2) == mylevels{2}(j); + + wh_cases = wh{1} & wh{2}; + + mycounts(i, j) = sum(wh_cases); + + if mycounts(i, j) < 1 + + mymeans(i, j) = NaN; + mystes(i, j) = NaN; + + else + + mymeans(i, j) = nanmean(Y(wh_cases)); + mystes(i, j) = ste(Y(wh_cases)); + + end + + end + + end + + for i = 1:length(mylevels{1}) + levelnames{1}{i} = sprintf('V1 = %3.2f', mylevels{1}(i)); + end + + for i = 1:length(mylevels{2}) + levelnames{2}{i} = sprintf('V2 = %3.2f', mylevels{2}(i)); + end + + varargout{1} = struct('means', mymeans, 'stes', mystes, 'counts', mycounts, 'levels', [], 'levelnames', []); + + varargout{1}.levels = mylevels; + varargout{1}.levelnames = levelnames; + + + + + % estimation + % ---------------------------------------------------------------- + case 'gls' + if nargout == 1 + % fastest + varargout{1} = GLScalc(varargin{:}); + + elseif nargout == 2 + [b, s2between_ols] = GLScalc(varargin{:}); + varargout{1} = b; + varargout{2} = s2between_ols; + + else + % slowest + [b, s2between_ols, stats] = GLScalc(varargin{:}); + varargout{1} = b; + varargout{2} = s2between_ols; + varargout{3} = stats; + end + + % printing and plotting + % ---------------------------------------------------------------- + + case 'print' + try + print_outcome(varargin{:}); + catch + disp('Improper use of stats print option. You must create a valid stats structure,'); + disp('e.g., with mediation.m or glmfit_general.m or igls.m'); + rethrow(lasterror); + end + + case 'xycatplot', varargout{1} = xycatplot(varargin{:}); + + case 'loess_xy', loessxy(varargin{:}); + + case 'loess_partial', loess_partial(varargin{:}); + + case 'xyplot', xyplot(varargin{:}); + + case 'xytspanelplot', varargout{1} = xytspanelplot(varargin{:}); + + % bootstrapping and nonparametrics + % ---------------------------------------------------------------- + + case 'boot' + varargout{1} = bootstrap_gls(varargin{:}); + + case 'signperm' + varargout{1} = signperm_gls(varargin{:}); + + + otherwise + error('Unknown method: Improper usage of this function.') +end + + + + + + + +end + + + + + + +% _________________________________________________________________________ +% +% +% +% * GLS Estimation functions +% +% +% +%__________________________________________________________________________ + + + + +% ------------------------------------------------------------------------- +% GLS estimation of betas, ols variances, and full stats if 3rd output +% is requested +% ------------------------------------------------------------------------- + +function [b, s2between_ols, stats] = GLScalc(Y, W, X, varargin) +% OLScalc +% calculate group betas, group variance +% Optional: full inference outputs (takes longer) + +% ------------------------------------------------------------------------- +% GLS estimation of betas +% ------------------------------------------------------------------------- + +arorder = 0; +if ~isempty(varargin), arorder = varargin{1}; end + +[n, k] = size(X); +nvars = size(Y, 2); + +b = zeros(k, nvars); + + +if k == 1 && all(X == X(1)) && (nargout == 1) + + % intercept only; fast computation + % Weighted mean function: + % Very efficient when there are no predictors other than the intercept + % Faster than looping, and faster than using mean() for equal weights + + b = diag(W'*Y)'; + + +else + % predictors; need full computation + % OR we are getting variances, and need full computation for them + % anyway + % Full GLS function, needed if there are predictors + + + % setup stuff + + Wi = cell(1, k); + invxvx = cell(1, nvars); + bforming = cell(1, nvars); + equal_weights = false(1, nvars); + + for i = 1:nvars + + if all(W(:, i) == W(1, i)) + % weights are equal + equal_weights(i) = 1; + + end + + end + + isweighted = 0; + + % get matrices for outcome vars in OLS case + if any(equal_weights) + + tmp = inv(X' * X); % Save these for later, for speed; don't need to re-use + invxvx(equal_weights) = {tmp}; + bforming(equal_weights) = {tmp * X'}; + end + + % get betas (coefficients) and other necessary matrices for weighted columns + for i = 1:nvars + + if ~equal_weights(i) + isweighted = 1; + + Wi{i} = diag(W(:, i)); % Wi = V^-1, inverse of cov.matrix + + invxvx{i} = inv(X' * Wi{i} * X); % Save these for later, for speed; don't need to re-use + bforming{i} = invxvx{i} * X' * Wi{i}; + + end + + b(:, i) = bforming{i} * Y(:, i); + %b(:, i) = inv(X' * Wi * X) * X' * Wi * Y(:, i); + + + % Add AR(p) model stuff here + if arorder > 0 + % Warning: Tested March08...not same as fit_gls, check.**** + disp('Warning: Check AR code'); + [b(:, i),stebetatmp, varbetatmp, tmpi, tmpv, dfe_ols(i), Phi] = ... + ar_iterate_core(X, Y(:, i), b(:, i), n, arorder); + + end + + end +end + + +% Optional additional computations: optional in case we want to return just b +% and go really fast (e.g., for bootstrapping) + +if nargout > 1 + + % -------------------------------------- + % * Residuals + % -------------------------------------- + + e = Y - X * b; % residuals + + % + % OLS residual variance + % If bootstrapping or permuting, use this to get weights + + if ~(arorder > 0) + dfe_ols = (n - k) .* ones(1, nvars); + end + + % -------------------------------------- + % * Residual variance + % -------------------------------------- + %s2between = diag(e' * e)' ./ dfe; % var for each col of Y, 1 x nvars + s2between_ols = (1 ./ dfe_ols .* sum(e .^ 2)); % Estimates of Sigma^2 for each col. of Y + +end + +if nargout > 2 + % Weighted variance (s2) and full stats + + % -------------------------------------- + % * Degrees of freedom for each column of Y + % -------------------------------------- + dfe = dfe_ols; + + + % Get design-related component of STE (includes n) + % replace dfe with Sattherwaite approx. if necessary + + Xste = zeros(k, nvars); + + + % -------------------------------------- + % * Residual variances, std. errors for each column of Y + % -------------------------------------- + for i = 1:nvars + + Xste(:, i) = diag(invxvx{i}); % design-related contribution to variance, k x nvars + + if equal_weights(i) + % weights are equal + + s2between(i) = s2between_ols(i); % Residual variance for this outcome variable + + else + % all weights are not equal + % Update dfe and s2between + + % R = Wi.^.5 * (eye(n) - X * invxvx * X' * Wi{i}); % Residual inducing matrix + R = Wi{i} .^ .5 * (eye(n) - X * bforming{i}); % Residual inducing matrix + + Q = R * inv(Wi{i}); % Q = RV + dfe(i) = (trace(Q).^2)./trace(Q * Q); % Satterthwaite approximation for degrees of freedom + + % -------------------------------------- + % * Residual variance + % -------------------------------------- + e = R * Y(:, i); % weighted residuals + s2between(i) = diag(e' * e)' ./ dfe(i); % var for each col of Y, 1 x nvars + end + + end + + % -------------------------------------- + % * Standard errors of coefficients + % -------------------------------------- + sterrs = ( Xste .* repmat(s2between, k, 1) ) .^ .5; + + % ------------------------------------------------------------------------- + % Get statistic structure from OLS regression, including p-values and conf. intervals + % ------------------------------------------------------------------------- + + + stats.mean = b(1, :); % intercept; mean response + stats.mean_descrip = 'Intercept of each col. of Y; (mean response if predictors are centered)'; + + stats.beta = b; + stats.beta_descrip = 'betas (regression coefficients), k predictors x nvars'; + + stats.var = s2between; + stats.var_descrip = 'Residual variance of each col. of Y'; + + stats.ste = sterrs; + stats.ste_descrip = 'Std. error of each beta for each col. of Y, k predictors x nvars'; + + stats.t = b ./ sterrs; + + stats.dfe = dfe; + stats.dfe_descrip = 'error DF for each col. of Y, Satterthwaite corrected if necessary; 1 x nvars'; + + + + stats.e = e; + if ~isweighted + stats.e_descrip = 'unweighted (OLS) residuals'; + else + stats.e_descrip = 'weighted residuals (resid. from weighted GLS model.)'; + end + + for i = 1:k + + stats.p(i, :) = min(1, (2 .* (1 - tcdf(abs(stats.t(i, :)), stats.dfe)))); + + stats.p(i, :) = max(stats.p(i, :), eps); + + end + + stats.p_descrip = 'Two-tailed p-values'; + +end + +end + + + + + + +% _________________________________________________________________________ +% +% +% +% * Printing and plotting +% +% +% +%__________________________________________________________________________ + + + + +% ------------------------------------------------------------------------- +% Print summary of mediation analysis/stats results to screen +% ------------------------------------------------------------------------- +function print_outcome(stats, stats1) + +if nargin < 2, stats1 = []; end + +fprintf('\n________________________________________\n') +if isfield(stats.inputOptions, 'inference_option') + infopt = stats.inputOptions.inference_option; +else + infopt = 'GLS'; +end + +if isfield(stats, 'nresample') + fprintf('Final %s samples: %3.0f\n', infopt, stats.nresample) +end +if strcmp(infopt, 'bootstrap') && isfield(stats, 'alphaaccept') + fprintf('Average p-value tolerance (average max alpha): %3.4f\n', stats.alphaaccept) +end + +% convergence values (first level) +if ~isempty(stats1) + if isfield(stats1, 'isconverged') && isfield(stats1, 'mean') + N = size(stats1.mean, 1); + mysum = sum(stats1.isconverged); + myperc = 100*mysum ./ N; + fprintf('Number converged: %3.0f, %3.0f%%\n', mysum, myperc) + end +end + + +Z = abs(norminv(stats.p)) .* sign(stats.beta); +n_predictors = size(stats.beta, 1); + +if isfield(stats.inputOptions, 'beta_names') + pred_names = stats.inputOptions.beta_names; +else + for i = 1:n_predictors + pred_names{i} = sprintf('Predictor %02d', i); + end +end + +fprintf('\n%s\n\tOutcome variables:\n', stats.analysisname) +fprintf('\t') +fprintf('%s\t', stats.inputOptions.names{:}) +fprintf('\n') +if isfield(stats, 'mean') + print_line('Adj. mean', stats.mean); +end + +for i = 1:n_predictors + fprintf('\n'); + fprintf('%s\n\t', pred_names{i}) + fprintf('%s\t', stats.inputOptions.names{:}) + fprintf('\n'); + + print_line('Coeff', stats.beta(i,:)); + print_line('STE', stats.ste(i,:)) + + if isfield(stats, 't') + print_line('t', stats.t(i,:)) + else + print_line('t (~N)', stats.beta(i,:) ./stats.ste(i,:)) + end + + print_line('Z', Z(i,:)) + + print_line('p', stats.p(i,:), 4) + fprintf('\n') + +end + + + + +fprintf('________________________________________\n') +end + + +function print_line(hdr, data, dec) +if nargin == 2 + dec = num2str(2); +else + dec = num2str(dec); +end + +fprintf('%s\t', hdr) +if iscell(data) + eval(['fprintf(''%3.' dec 'f\t'', data{:})']); +else + eval(['fprintf(''%3.' dec 'f\t'', data)']); +end +fprintf('\n'); +end + + +% ------------------------------------------------------------------------- +% LOESS plots of N subjects, categorical X vs Y +% ------------------------------------------------------------------------- +function stats = xycatplot(X, Y, varargin) + +regularization = .8; % for loess +newfig = 1; +weight_option = 'unweighted'; % for glmfit +doloess = 0; +doind = 1; +groupvar = []; % should be cell for each subject with integer values + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % General Defaults + case {'reg', 'regularization'}, regularization = varargin{i + 1}; + case 'samefig', newfig = 0; + case 'beta_names', beta_names = varargin{i+1}; varargin{i+1} = []; + + % Estimation Defaults + case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; + + case 'loess', doloess = 1; + case 'noind', doind = 0; + + case {'groupby', 'groupvar'}, groupvar = varargin{i+1}; + + otherwise + disp('Warning! Unknown input string option.'); + + end + end +end + +if newfig, create_figure('Y as a function of categorical X'); end + +N = length(X); +xlevels = cell(1, N); +Xdes = cell(1, N); + +for i = 1:N + + [Xdes{i}, xlevels{i}] = condf2indic(X{i}); + + if ~isempty(groupvar) + % Split Xdes up by high vs. low on group var, within each subject + gv = repmat(contrast_code(groupvar{i}), 1, length(xlevels{i})); + Xdes{i} = [Xdes{i} .* (gv == 1) Xdes{i} .* (gv == -1)]; + + xlevels{i} = [xlevels{i}; xlevels{i}]; + end + + yavgs{i} = pinv(Xdes{i}) * Y{i}; + + if doind + plot(xlevels{i}, yavgs{i}, 'o-', 'Color', [.6 .6 .6], 'MarkerSize', 6, 'MarkerFaceColor', [.8 .8 .8]); + end +end + +% get group average and stats +stats = glmfit_multilevel(Y, X, [], 'beta_names', {'Intercept' 'Slope'}, weight_option, 'noverbose'); + +%stats_cat = glmfit_multilevel(Y, Xdes, [], 'beta_names', {'Intercept' 'Slope'}, weight_option, 'noverbose'); + +stats.individual.Xdes = Xdes; +stats.individual.xlevels = xlevels; +stats.individual.yavgs = yavgs; + +% Loess regression line for group average +if doloess + regularization = .8; + x = get(gca, 'XLim'); + xx = linspace(x(1), x(2), 50); + + yy = loess(cat(1, xlevels{:}), cat(1, yavgs{:}), xx, regularization, 1); + + plot(xx, yy, 'r', 'LineWidth', 3); + + disp('Bootstrapping group avg. error') + fcnhandle = @(x, y) loess(x, y, xx, regularization, 1); + yyboot = bootstrp(100, fcnhandle, cat(1, xlevels{:}), cat(1, yavgs{:})); + + upperlim = yy + std(yyboot); + lowerlim = yy - std(yyboot); + + plot(xx, upperlim, 'r--', 'LineWidth', 2); + plot(xx, lowerlim, 'r--', 'LineWidth', 2); + +else + % standard categorical plot: Fixed effects + % ------------------------------------------ + XX = cat(1, X{:}); + YY = cat(1, Y{:}); + + if ~isempty(groupvar) + GG = cat(1, groupvar{:}); + end + + mylevels = sort(unique(XX)); + + for i = 1:length(mylevels) + if ~isempty(groupvar) + % two groups + mymeans(i) = nanmean(YY(XX == mylevels(i) & GG == 1)); + mystes(i) = ste(YY(XX == mylevels(i) & GG == 1)); + mymeans2(i) = nanmean(YY(XX == mylevels(i) & GG == -1)); + mystes2(i) = ste(YY(XX == mylevels(i) & GG == -1)); + + else + % single group + mymeans(i) = nanmean(YY(XX == mylevels(i))); + mystes(i) = ste(YY(XX == mylevels(i))); + end + end + + + h = tor_line_steplot(mymeans, mystes, {'k'}, mylevels); + set(h, 'LineWidth', 2); + h = tor_line_steplot(mymeans, -mystes, {'k'}, mylevels); + set(h, 'LineWidth', 2); + plot(mylevels, mymeans, 'ko-', 'MarkerSize', 12, 'MarkerFaceColor', [0 .7 .2], 'LineWidth', 4); + + if ~isempty(groupvar) + h = tor_line_steplot(mymeans2, mystes2, {'b'}, mylevels); + set(h, 'LineWidth', 2); + h = tor_line_steplot(mymeans2, -mystes2, {'b'}, mylevels); + set(h, 'LineWidth', 2); + plot(mylevels, mymeans2, 'o-', 'Color', [.3 .3 1], 'MarkerSize', 12, 'MarkerFaceColor', [0 0 1], 'LineWidth', 4); + end + + % ------------------------------------------ + % standard categorical plot: centered Empirical Bayes estimates + % NOTE: ASSUMES ALL LEVELS ARE SAME ACROSS SUBJECTS RIGHT NOW! + % ------------------------------------------ + + create_figure('X vs. Y with centered Empirical Bayes estimates'); + stats_cat = glmfit_multilevel(Y, Xdes, [], 'beta_names', {'Intercept' 'Slope'}, weight_option, 'noverbose', 'noint'); + + % Individuals: Center Empirical Bayes estimates of individual means and add in grand + % mean + Y2 = stats_cat.Y_star - repmat(nanmean(stats_cat.Y_star, 2), 1, length(xlevels{1})); + Y2 = Y2 + nanmean(stats_cat.Y_star(:)); + hold on; plot(xlevels{1}, Y2, 'o-', 'Color', [.6 .6 .6],'MarkerSize', 6, 'MarkerFaceColor', [.8 .8 .8]); + + h = tor_line_steplot(stats_cat.b_star, mystes, {'k'}, mylevels); + set(h, 'LineWidth', 2); + h = tor_line_steplot(stats_cat.b_star, -mystes, {'k'}, mylevels); + set(h, 'LineWidth', 2); + + % could use b_star, but check this... + hold on; plot(xlevels{1}, stats_cat.b_star, 'ks-', 'MarkerSize', 12, 'MarkerFaceColor', [0 .7 .2], 'LineWidth', 4); + + +end + + +end + + +% ------------------------------------------------------------------------- +% LOESS plots of N subjects, X vs Y +% ------------------------------------------------------------------------- +function loessxy(X, Y, varargin) +% loessxy(X, Y) +% +% Loess plot; X is cell array with X data for each subject, Y is cell +% array, same format + +%LOESS PLOTS + +regularization = .8; +if ~isempty(varargin), regularization = varargin{1}; end + +create_figure('X - Y plot'); +N = length(X); +for i = 1:N + hh(i) = plot(X{i}, Y{i}, 'ko', 'MarkerSize', 4); + + %refline(hh); +end + +x = get(gca, 'XLim'); +xx = linspace(x(1), x(2), 50); + +for i = 1:N + + yy(i, :) = loess(X{i}, Y{i}, xx, regularization, 1); + plot(xx, yy(i,:)) + +end + +title('Loess: X - Y'); +xlabel('Temperature'); +ylabel('Pain report'); + +plot(xx, mean(yy), 'r', 'LineWidth', 2); + + +end + + +% ------------------------------------------------------------------------- +% Print summary of mediation analysis/stats results to screen +% ------------------------------------------------------------------------- +function loess_partial(M, Y, X, varargin) +% partial regression LOESS plots, M vs Y controlling for X + + +regularization = .8; % for loess +newfig = 1; +weight_option = 'unweighted'; % for glmfit + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % General Defaults + case {'reg', 'regularization'}, regularization = varargin{i + 1}; + + case 'samefig', newfig = 0; + + case 'beta_names', beta_names = varargin{i+1}; varargin{i+1} = []; + + % Estimation Defaults + case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; + + + otherwise + disp('Warning! Unknown input string option.'); + + end + end +end + +newy = {}; +newm = {}; + +if newfig, create_figure('M - Y partial plot'); end + +N = length(X); +for i = 1:N + + [b, dev, stat1] = glmfit(X{i}, Y{i}); + newy{i} = stat1.resid; + [b, dev, stat2] = glmfit(X{i}, M{i}); + newm{i} = stat2.resid; + +end + +for i = 1:N + hh(i) = plot(newm{i}, newy{i}, 'ko', 'MarkerSize', 4); +end + +% get range of support for loess +alldat = cat(1, newm{:}); +x(1) = prctile(alldat, 1); +x(2) = prctile(alldat, 99); +xx = linspace(x(1), x(2), 50); + +for i = 1:N + + yy(i, :) = loess(newm{i}, newy{i}, xx, regularization, 1); + plot(xx, yy(i,:)) + +end + +plot(xx, mean(yy), 'r', 'LineWidth', 2); + +title('Loess: M - Y partial plot'); +xlabel('Brain activity (partial)'); +ylabel('Pain report (partial)'); + +end + + + +% ------------------------------------------------------------------------- +% LOESS plots of N subjects, categorical X vs Y +% ------------------------------------------------------------------------- +function stats = xyplot(X, Y, varargin) + +regularization = .8; % for loess +newfig = 1; +weight_option = 'unweighted'; % for glmfit + +G = cell(size(X)); % grouping variable for trial types +for i = 1:length(G), G{i} = ones(size(X{i}, 1), 1); end + +npredictors = size(X{1}, 2); +names = {'Intercept'}; +for i = 1:npredictors + names{i + 1} = ['X' num2str(i)]; +end + +colors = {'b' 'g' 'r' [1 .5 0] [0 .5 1] [.5 0 1]}; +iscatx = 0; + +dostats = 1; +doind = 1; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % General Defaults + case {'reg', 'regularization'}, regularization = varargin{i + 1}; + + case 'samefig', newfig = 0; + + case 'beta_names', beta_names = varargin{i+1}; varargin{i+1} = []; + + case 'names', names = varargin{i+1}; varargin{i+1} = []; + + case 'groupby', G = varargin{i+1}; + + case 'colors', colors = varargin{i+1}; + + case {'cat', 'catx'}, iscatx = 1; + + % Estimation Defaults + case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; + + case 'nostats', dostats = 0; + case 'noind', doind = 0; + + otherwise + disp('Warning! Unknown input string option.'); + + end + end +end + +if newfig + f1 = create_figure('Y as a function of X', 1, 2); +else + f1 = gcf; +end + + +N = length(X); +xlevels = cell(1, N); +Xdes = cell(1, N); + +npanels = ceil(sqrt(N)); +if doind + f2 = create_figure('Individual X-Y', npanels, npanels); +end + +figure(f1); + +Glevels = unique(cat(1, G{:})); + +if length(Glevels) > 1 + disp('Grouping levels, from lowest to highest:'); + disp(Glevels) + disp(colors); +end + +while length(colors) < length(Glevels), colors = [colors colors]; end + +for group = 1:length(Glevels) + + if doind + figure(f1); + subplot(1, 2, 2); + title('Individuals'); + end + + for i = 1:N + + wh = G{i} == Glevels(group); + + xvals{i} = X{i}(wh); + yvals{i} = Y{i}(wh); + + if iscatx % categorical X + [Xdes{i}, xlevels{i}] = condf2indic(xvals{i}); + + else % continuous X + + % exclude bad continuous values (exact 0 or NaN) + whbad = isnan(xvals{i})| xvals{i} == 0 | isnan(yvals{i}) | yvals{i} == 0; + xvals{i}(whbad) = []; + yvals{i}(whbad) = []; + + if isempty(xvals{i}) + fprintf('No valid data for subject %3.0f. Skipping.\n', i); + xlevels{i} = []; + yavgs{i} = []; + continue + end + + Xdes{i} = xvals{i}; + Xdes{i}(:, end+1) = 1; + xlevels{i} = [nanmean(xvals{i}) - nanstd(xvals{i}) nanmean(xvals{i}) + nanstd(xvals{i})]'; %[prctile(X{i}, 20) prctile(X{i}, 80)]'; + end + + if size(Xdes{i}, 1) ~= size(yvals{i}, 1), error('Lengths of X and Y do not match.'); end + + if iscatx % categorical X + yavgs{i} = pinv(Xdes{i}) * yvals{i}; + + else % continuous X, get fits + b{i} = pinv(Xdes{i}) * yvals{i}; + yavgs{i} = [xlevels{i} ones(size(xlevels{i}))] * b{i}; + end + + if doind + + figure(f1); + subplot(1, 2, 2); + plot( xvals{i}, yvals{i}, '.', 'Color', [.5 .5 .5] ); % trials + plot(xlevels{i}, yavgs{i}, 'o-', 'Color', colors{group}, 'MarkerSize', 6, 'MarkerFaceColor', [.3 .3 .3]); + + % Individual panels figure + + figure(f2); + subplot(npanels, npanels, i) + + plot( xvals{i}, yvals{i}, '.', 'Color', [.5 .5 .5] ); % trials + plot(xlevels{i}, yavgs{i}, 'o-', 'Color', colors{group}, 'MarkerSize', 6, 'MarkerFaceColor', [.3 .3 .3]); + end + + end + + + % Group average for this group of trials + % % % % % % % % % + xmean = nanmean(cat(2, xlevels{:}), 2); + xste = ste(cat(2, xlevels{:})')'; + + ymean = nanmean(cat(2, yavgs{:}), 2); + yste = ste(cat(2, yavgs{:})')'; + + figure(f1) + subplot(1, 2, 1); + title('Group average'); + + for i = 1:length(xmean) + plot([xmean(i) xmean(i)], [ymean(i) - yste(i) ymean(i) + yste(i)], 'Color', colors{group}, 'LineWidth', 4); + plot([xmean(i) - xste(i) xmean(i) + xste(i)], [ymean(i) ymean(i)], 'Color', colors{group}, 'LineWidth', 4); + end + plot(xmean, ymean, 'o-', 'Color', colors{group}, 'MarkerSize', 10, 'MarkerFaceColor', colors{group}, 'LineWidth', 4); + hold on + +end % end groups + +% add group effects and interactions with group for stats +% Create a set of group contrast vars [group - prev group] + + +gnames = {}; +ncon = length(Glevels) - 1; +if ncon > 0 + + + for group = 2:length(Glevels) + for i = 1:N + X{i}(:, end+1) = double(G{i} == Glevels(group - 1)); % subsequent is neg + X{i}(:, end) = double(G{i} == Glevels(group)); % first is pos + + % interaction with X (centered pred.) + X{i}(:, end+1) = scale(X{i}(:, end), 1) .* scale(X{i}(:, 1)); + + end + + gnames{end + 1} = sprintf('Trial_group%01d-%01d', group - 1, group); + gnames{end + 1} = sprintf('X x Grp%01d-%01d', group - 1, group); + end + + names = [names gnames]; +end + +% get stats +if dostats + stats = glmfit_multilevel(Y, X, [], weight_option, 'verbose', 'noplots', 'names', names); + + stats.individual.Xdes = Xdes; + stats.individual.xlevels = xlevels; + stats.individual.yavgs = yavgs; +else + stats = []; +end + + + +end + + + +% ------------------------------------------------------------------------- +% Panel plot for each subject +% ------------------------------------------------------------------------- +function stats = xytspanelplot(X, Y, varargin) + +stats = []; +newfig = 1; +weight_option = 'unweighted'; % for glmfit +dofit = 0; +dozscore = 0; +colors = {'r' 'k'}; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % General Defaults + case {'t', 'T'}, t = varargin{i + 1}; varargin{i + 1} = []; + + case 'samefig', newfig = 0; + + case {'fit', 'dofit'}, dofit = 1; + + % Estimation Defaults + case {'weight', 'weighted', 'var', 's2'}, weight_option = 'weighted'; + + case 'zscore', dozscore = 1; + + otherwise + disp('Warning! Unknown input string option.'); + + end + end +end + +if ~iscell(X), X = mat2cell(X, size(X, 1), ones(1, size(X, 2))); end +if ~iscell(Y), Y = mat2cell(Y, size(Y, 1), ones(1, size(Y, 2))); end + +N = length(X); +nr = ceil(sqrt(N)); +nc = floor(sqrt(N)); + +if nr * nc < N, nc = nc + 1; end % just in case + +if newfig, create_figure('XY Panel Plot', nr, nc); end + +for i = 1:N + if dozscore + X{i} = scale(X{i}); + Y{i} = scale(Y{i}); + end +end + +stats = glmfit_multilevel(Y, X, [], weight_option, 'names', {'Intcpt' 'X'}); + +if dofit + + % get fits + for i = 1:N + model = X{i}; model = [ones(size(model)) model]; + X{i} = model * stats.first_level.beta(:, i); + end +end + +for i = 1:N + + subplot(nr, nc, i) + + if ~exist('t', 'var'), t = 1:size(X{i}, 1); end + + plot(t, X{i}, colors{1}); + plot(t, Y{i}, colors{2}); + +end + +end + + + +% _________________________________________________________________________ +% +% +% +% * Bootstrapping functions +% +% +% +%__________________________________________________________________________ + + + +% ------------------------------------------------------------------------- +% Bootstrap test for 2nd level +% ------------------------------------------------------------------------- +function stats = bootstrap_gls(Y, W, X, bootsamples, stats, whpvals_for_boot, targetu, verbose ) + +% stats structure should already have b's (coeffs, will remain same) and p-values (will be +% updated) from a GLS model. +% Updates: +% .means, .p, .ste using bootstrapping +% +% pass in weights based on OLS + +nvars = size(Y, 2); +k = size(X, 2); + +myb = stats.beta(:); +if diff(size(myb)) < 0, myb = myb'; end + +stats.gls_p = stats.p; + + +% Get boot samples needed based on OLS +% Add samples as needed to ensure p-value is reasonably accurate +% Be: Boot samples needed so that Expected (average) p-value +% error due to limited-sample bootstrap is targetu*100 % +% pass in OLS p-values + + + + +[final_boot_samples, alphaaccept] = get_boot_samples_needed(stats.p, whpvals_for_boot, targetu, bootsamples, verbose); % uses whpvals_for_boot, returns Be + + +% set up boostrap beta-generating function +% generic for multiple cols of Y (outcomes), multiple cols. of X +% (predictors), weights equal or not (W) +% much faster if X is intercept only! +% with multiple predictors, much faster if W are equal +% will not work as-is for multiple predictors! must string out and +% reshape with wrapper function +wmean = @(Y, W, X) gls_wrapper(Y, W, X); + +if verbose, fprintf('Bootstrapping %3.0f samples...', final_boot_samples); end + +% initalize random number generator to new values; bootstrp uses this +rand('twister',sum(100*clock)) + +% start with weights all equal whether multilevel or not +means = bootstrp(final_boot_samples, wmean, Y, W, X); + +stats = getstats(means, stats, k, nvars); +stats.analysisname = 'Bootstrapped statistics'; + +if verbose, t12 = clock; end + +% bias correction for final bootstrap samples +%[p, z] = bootbca_pval(testvalue, bootfun, bstat, stat, [x], [other inputs to bootfun]) + +stats.prctilep = reshape(stats.p, k, nvars); % percentile method, biased +[stats.p, stats.z] = bootbca_pval(0, wmean, means, myb, Y, W, X); +stats.p = reshape(stats.p, k, nvars); +stats.z = reshape(stats.z, k, nvars); + +stats.biascorrect = 'BCa bias corrected'; +stats.alphaaccept = alphaaccept; + +if verbose, t13 = clock; end +if verbose, fprintf(' Bootstrap done in %3.2f s, bias correct in %3.2f s \n', etime(clock, t12), etime(clock, t13)); end +end + + +% ------------------------------------------------------------------------- +% Wrapper to return beta matrix strung out in a line, for bootstrapping +% ------------------------------------------------------------------------- +function b = gls_wrapper(Y, W, X) +b = scn_stats_helper_functions('gls', Y, W, X); +b = b(:); + +if diff(size(b)) < 0, b = b'; end +end + + +% ------------------------------------------------------------------------- +% Figure out how many more bootstrap samples to run (general) +% ------------------------------------------------------------------------- +function [Be, alphaaccept] = get_boot_samples_needed(p, whpvals_for_boot, targetu, bootsamples, verbose) +minp = min(p(whpvals_for_boot)); +[B95, Be, alphaaccept] = Bneeded(max(.005, minp), targetu); +Be = max(bootsamples, ceil(Be)); % additional number to run + +if verbose, fprintf(' Min p-value is %3.6f. Needed: %3.0f samples\n ', minp, Be), end +end + +% ------------------------------------------------------------------------- +% Get statistic structure from bootstrapped samples, including p-values and conf. intervals +% ------------------------------------------------------------------------- +function stats = getstats(bp, stats, k, nvars) + +stats.mean = reshape(mean(bp), k, nvars); +stats.ste = reshape(std(bp), k, nvars); + +stats.p = 2.*(min(sum(bp<=0), sum(bp>=0)) ./ size(bp, 1)); + +% avoid exactly-zero p-values +% replace with 1/# bootstrap samples +stats.p = max(stats.p, 1./size(bp, 1)); + +stats.p = reshape(stats.p, k, nvars); +end + + + + + + +% _________________________________________________________________________ +% +% +% +% * Nonparametric test functions +% +% +% +%__________________________________________________________________________ + + + + +% ------------------------------------------------------------------------- +% Permutation test for 2nd level +% ------------------------------------------------------------------------- +function stats = signperm_gls(Y, W, X, nperms, stats, permsign, verbose ) +% +% Sign permutation test on intercept only! +% +% +% First column of X must be interecept! +% +% if permsign is passed in: uses exact permutations passed in +% set up valid perms using permute_setupperms.m +% save time by keeping permutations the same once we've got them. +% may *not* want to do this in sims, as it introduces some +% dependence across replications! +% +% if permsign is empty, creates new permsign matrix +% +% stats structure should be output from GLS analysis (see glmfit_general or +% GLS helper function in scn_stats_helper_functions +% Updates: +% stats.p, stats.z +% Does not update: +% stats.beta b's (coeffs, will remain same) +% stats.ste +% stats.means +% stats.names +% +% pass in weights based on OLS (Get weights from GLS fit) + +if verbose, fprintf('Nonparametric sign permutation test with %3.0f permutations...', nperms); t12 = clock; end + +% start with weights all equal whether multilevel or not + +% residuals adjusted for intercept, if multiple columns entered +% for intercept only, this is not necessary +k = size(X, 2); +[n, nvars] = size(Y); + +if k == 1 + % intercept only + resid = Y; +else + resid = zeros(n, nvars); + + for i = 1:nvars + if all(W(:, i) == W(1, i)) + resid(:, i) = Y(:, i) - X(:, 2:end) * stats.beta(2:end, :); % unweighted + else + % weighted. But probably won't matter + % ****this needs to be checked for accuracy + Wi = diag(W(:, i)); + resid(:, i) = Wi * Y(:, i) - Wi * X(:, 2:end) * stats.beta(2:end, i); % unweighted + end + end +end + + +[p, Z, xbar, permsign] = permute_signtest(resid, nperms, W, permsign); + +stats.p_descrip2 = 'Intercept p-values: Based on sign permutation test'; +stats.z_descrip2 = 'Intercept z-values: Based on sign permutation test'; + +stats.z(1,:) = Z; +stats.p(1,:) = p; +stats.permsign = permsign; + +if verbose, fprintf(' Done in %3.2f s \n', etime(clock, t12)); end + +end diff --git a/Statistics_tools/searchlight_disti.m b/Statistics_tools/searchlight_disti.m new file mode 100644 index 00000000..76a7c7e6 --- /dev/null +++ b/Statistics_tools/searchlight_disti.m @@ -0,0 +1,307 @@ +function out = searchlight_disti(dat, mask, dist_i, additional_inputs) + +% run the actual searchlight analysis on each brain chunck +% searchlight_dream.m will generate codes to run this funtion. + +% Usage: +% ------------------------------------------------------------------------- +% out = searchlight_disti(dat, mask, dist_i, [additional_inputs]) +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo, Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% For example and usage, please see +% searchlight_dream.m +% +% see also +% xval_cross_classify.m, fmri_data.predict.m, + +% Programmers' notes: +% + +%% set-up variables + +do_cross = false; +do_saveweight = false; +r = 3; % default radius (in voxel) + +% parsing varargin (additional_inputs) + +for i = 1:length(additional_inputs) + if ischar(additional_inputs{i}) + switch additional_inputs{i} + % functional commands + case 'dat2' + do_cross = true; + dat2 = additional_inputs{i+1}; + additional_inputs{i} = []; additional_inputs{i+1} = []; + case 'algorithm_name' + algorithm_name = additional_inputs{i+1}; + additional_inputs{i} = []; additional_inputs{i+1} = []; + case 'r' + r = additional_inputs{i+1}; + additional_inputs{i} = []; additional_inputs{i+1} = []; + case 'cv_assign' + cv_assign = additional_inputs{i+1}; + additional_inputs{i} = []; additional_inputs{i+1} = []; + case 'save_weights' + do_saveweight = true; + additional_inputs{i} = []; + end + end +end + +% prep dat2 if do_cross + +if do_cross + [dat2, mask] = apply_mask(dat2, mask); + dat2 = trim_mask(dat2); +end + +%% searchlight prep + +% data check +if do_cross, if any(sum(sum(dat.volInfo.xyzlist ~= dat2.volInfo.xyzlist))), error('dat and dat2 should be in the same space.'); end, end + +% get voxel index to run +vox_to_run = find(dist_i)'; + +index_within_this_run = 0; +for i = vox_to_run %(1):vox_to_run(10) + searchlight_indx = searchlight_sphere_prep(dat.volInfo.xyzlist, i, r); + + % data prep + + if do_cross + results = xval_cross_classfy_wrapper(algorithm_name, dat, dat2, cv_assign, searchlight_indx, do_saveweight, additional_inputs); + else + results = predict_wrapper(algorithm_name, dat, cv_assign, searchlight_indx, do_saveweight, additional_inputs); + end + + % output prep + if ~exist('out', 'var') + for jj = 1:numel(results{1}) + f = fields(results{1}{jj}); + for ii = 1:numel(f) + eval(['out.test_results{jj}.' f{ii} ' = NaN(dat.volInfo.n_inmask, size(results{1}{jj}.' f{ii} ',2));']); + end + end + end + + index_within_this_run = index_within_this_run+1; + out = parse_results(out, results, i, index_within_this_run); + +end + +end + + +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- +% +% Sub-functions +% +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- + +% ------------------------------------------------------------------------- +% searchlight_sphere_prep + +function indx = searchlight_sphere_prep(xyz, i, r) +seed = xyz(i,:); +indx = sum([xyz(:,1)-seed(1) xyz(:,2)-seed(2) xyz(:,3)-seed(3)].^2, 2) <= r.^2; +end + +% ------------------------------------------------------------------------- +% xval_cross_classification + +function results = xval_cross_classfy_wrapper(algorithm_name, dat1, dat2, cv_assign, searchlight_indx, do_saveweight, additional_inputs) + +whempty = cellfun(@isempty, additional_inputs); +additional_inputs(whempty)=[]; + +for i = 1:2 + eval(['dat' num2str(i) '.dat = dat' num2str(i) '.dat(searchlight_indx,:);']); + eval(['dat' num2str(i) '.removed_voxels(~searchlight_indx) = true;']); +end + +if do_saveweight + [results{1}, results{2}, results{3}] = xval_cross_classfy(algorithm_name, dat1, dat2, cv_assign, additional_inputs{:}); +else + results{1} = xval_cross_classfy(algorithm_name, dat1, dat2, cv_assign, additional_inputs{:}); +end + +end + +% ------------------------------------------------------------------------- +% predict wrapper + +function results = predict_wrapper(algorithm_name, dat, cv_assign, searchlight_indx, do_saveweight, additional_inputs) + +whempty = cellfun(@isempty, additional_inputs); +additional_inputs(whempty)=[]; + +[out_method, test_Y, dat] = setup_testvar(dat, additional_inputs{:}); + +dat.dat = dat.dat(searchlight_indx, :); +dat.removed_voxels(~searchlight_indx) = true; + +[dummy, stats] = predict(dat, 'algorithm_name', algorithm_name, 'nfolds', cv_assign, 'verbose', 0); + +if do_saveweight + stats = rmfield(stats, {'weight_obj', 'teIdx', 'trIdx'}); + results{1} = get_test_results(stats, test_Y, out_method, algorithm_name); + results{2} = stats; +else + stats = rmfield(stats, {'weight_obj', 'teIdx', 'trIdx'}); + results{1} = get_test_results(stats, test_Y, out_method, algorithm_name); +end + +end + +% ------------------------------------------------------------------------- +% parse test_results + +function out = parse_results(out, results, i, index_within_this_run) + +for j = 1:numel(results{1}) + + f = fields(results{1}{j}); + + for ii = 1:numel(f) + eval(['out.test_results{j}.' f{ii} '(i,:) = results{1}{j}.' f{ii} ';']); + end +end + +if numel(results) == 2 + out.stats{index_within_this_run} = results{2}; + out.stats{index_within_this_run}.vox_indx = i; +elseif numel(results) == 3 + for ii = 1:2 + eval(['out.stats' num2str(ii) '{index_within_this_run} = results{' num2str(ii+1) '};']); + eval(['out.stats' num2str(ii) '{index_within_this_run}.vox_indx = i;']); + end +end + +end + + +% ------------------------------------------------------------------------- +% set up test variables + +function [out_method, test_Y, dat] = setup_testvar(dat, varargin) + +% default for binary +out_method_input = 'twochoice'; + +% get outcome method input +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'outcome_method'} + out_method_input = varargin{i+1}; + end + end +end + +% if dat.Y is the test variable +if size(dat.Y,2) == 1 + + if numel(unique(dat.Y(dat.Y~=0)))==2 + out_method{1} = out_method_input; + test_Y{1} = dat.Y; + + elseif numel(unique(dat.Y(dat.Y~=0))) > 2 + out_method{1} = 'correlation'; + test_Y1{1} = dat.Y; + end + +% if data.Y has more than one column +elseif size(dat.Y,2) > 1 + + for i = 2:size(dat.Y,2) + + if numel(unique(dat.Y(dat.Y(:,i)~=0,i)))==2 + out_method{i-1} = out_method_input; + test_Y{i-1} = dat.Y(:,i); + elseif numel(unique(dat.Y(dat.Y(:,i)~=0,i))) > 2 + out_method{i-1} = 'correlation'; + test_Y{i-1} = dat.Y(:,i); + end + + end +end + +% delete test variables from data +dat.Y = dat.Y(:,1); + +end + +function test_results = get_test_results(stats, test_Y, out_method) + +for i = 1:numel(out_method) + + whempty = test_Y{i} == 0; + + switch out_method{i} + case 'correlation' + [test_results{i}.r, test_results{i}.p] = corr(stats.yfit(~whempty), test_Y{i}(~whempty)); + case 'singleinterval' + try + if ~isempty(strfind(algorithm_name, 'svm')) + ROC = roc_plot(stats.dist_from_hyperplane_xval, test_Y{i}==1, 'include', ~whempty, 'threshold', 0, 'balanced'); + elseif ~isempty(strfind(algorithm_name, 'lassopcr')) + roc_temp = roc_plot(stats.Y, test_Y{i}==1, 'include', ~whempty, 'balanced'); + thr = roc_temp.class_threshold; + ROC = roc_plot(stats.yfit, test_Y{i}==1, 'include', ~whempty, 'threshold', thr, 'balanced'); + end + test_results{i}.acc = ROC.accuracy; + test_results{i}.se = ROC.accuracy_se; + test_results{i}.p = ROC.accuracy_p; + test_restuls{i}.thr = ROC.class_threshold; + catch dummy + test_results{i}.acc = NaN; + test_results{i}.se = NaN; + test_results{i}.p = NaN; + test_restuls{i}.thr = NaN; + end + + test_results{i}.p(test_results{i}.p > 1) = 1; + + case 'twochoice' + try + if ~isempty(strfind(algorithm_name, 'svm')) + ROC = roc_plot(stats.dist_from_hyperplane_xval, test_Y{i}==1, 'twochoice', 'include', ~whempty); + elseif ~isempty(strfind(algorithm_name, 'lassopcr')) + ROC = roc_plot(stats.yfit, test_Y{i}==1, 'twochoice', 'include', ~whempty); + end + test_results{i}.acc = ROC.accuracy; + test_results{i}.se = ROC.accuracy_se; + test_results{i}.p = ROC.accuracy_p; + catch dummy + test_results{i}.acc = NaN; + test_results{i}.se = NaN; + test_results{i}.p = NaN; + end + + test_results{i}.p(test_results{i}.p > 1) = 1; + end +end + +close all; + +end \ No newline at end of file diff --git a/Statistics_tools/searchlight_dream.m b/Statistics_tools/searchlight_dream.m new file mode 100644 index 00000000..3e0e6b35 --- /dev/null +++ b/Statistics_tools/searchlight_dream.m @@ -0,0 +1,270 @@ +function searchlight_dream(dat, dist_n, mask, varargin) + +% This function generates codes for submitting multiple distributed jobs to +% clusters to run a searchlight analysis on multiple chunks of the brain. +% +% Usage: +% ------------------------------------------------------------------------- +% searchlight_dream(dat, dist_n, mask, 'algorithm_name', 'cv_svm' (or 'cv_lassopcr'), 'cv_assign', whfolds, [optional input]) +% +% Features: +% - generates dist_n scripts in modeldir (or current directory) +% - can run a searchlight analysis on one dataset, or two datasets +% (cross-classification) +% - can apply different radius +% - can obtain cross-validated results with 'cv_assign' option +% - can use SVM (linear svm is a default) and LASSO-PCR. You need to have +% a spider toolbox and lasso rocha toolbox in your path +% - you can save predictive weights for each searchlight or discard them +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% dat image_vector or fmri_data object with data +% dat1.Y(:,1) for svm: true(1) or false(-1) for each observation (image) in Y(:,1) +% for lassopcr: continuous value for Y(:,1) +% dat1.Y(:,[2:n]) Test sets: could be binary: and true(1), false(-1), +% ignore(0) or continuous values +% dist_n The number of jobs (brain chunks) you want to create +% mask This will be run on voxels within the mask +% e.g., which('scalped_avg152T1_graymatter.img') +% 'algorithm_name' should be followed by 'cv_svm' or 'cv_lassopcr' +% 'cv_assign' a vector of integers for membership in custom holdout set +% of each fold +% +% Optional inputs: +% ------------------------------------------------------------------------- +% 'dat2' cross-classification; should be followed by dat2 and dat2.Y +% dat2.Y(:,1) - for training/testing, dat2(:,[2:n]) - for testing +% 'r' searchlight sphere radius (in voxel) (default: r = 3 voxels) +% 'modeldir' the directory where all the variables and results will be +% saved; should be followed by a directory location +% (default: the current directory) +% 'scale' z-scored input data in image_vector or fmri_data object +% (default = false) +% 'balanced' use the balanced ridge option - balanced ridge value should +% be followed. (default = false) +% 'outcome_method' followed by the following options +% 'correlation' - "default" for for continuous measures +% 'twochoice'- "default" for binary outcome +% 'singleinterval' - for binary outcome +% 'save_weights' save weights for each searchlight (default = false) +% 'email' should be followed by an email adress (default = false) +% +% Outputs: +% ------------------------------------------------------------------------- +% This function will generate codes that call "searchlight_disti.m", which +% will save the following output variables. +% +% out.test_results: accuracy, p, and se for binary classification, and +% correlation (pearson), p for prediction of continuous values +% For the cross-classification, test_results will save +% four values for each searchlight. The order of the test +% results are [dat1-on-dat1, dat1-on-dat2, dat2-on-dat1, +% dat2-on-dat2]. All results are cross-validated results. +% +% out.stats1 & stats2 stats1 and stats2 are similar to the outputs of predict +% function. +% +% Examples for lassopcr: +% ----------------------------------------------------- +% % data preparation +% % --------------------------------------------------- +% dat = fmri_data(which('brainmask.nii')); +% dat.dat = randn(dat.volInfo.n_inmask, 30); +% +% % setting up training values +% % --------------------------------------------------- +% dat.Y = dat.dat(111111, :)' + .3 * randn(30, 1); +% +% % setting up testing values +% % --------------------------------------------------- +% dat.Y(:,2) = [ones(10,1); zeros(10,1); -ones(10,1)]; +% dat.Y(:,3) = dat.dat(111111, :)' + .3 * randn(30, 1); +% mask = which('scalped_avg152T1_graymatter.img'); +% dist_n = 50; +% +% % data for cross classification +% % --------------------------------------------------- +% dat2 = fmri_data(which('brainmask.nii')); +% dat2.dat = randn(dat.volInfo.n_inmask, 30); +% dat2.Y = dat2.dat(111, :)' + .3 * randn(30, 1); +% dat2.Y(:,2) = dat.Y(:,2); +% dat2.Y(:,3) = dat2.dat(111111, :)' + .3 * randn(30, 1); +% +% % setting up other variables +% % --------------------------------------------------- +% r = 6; +% modeldir = '/dreamio/home/chwo9116/Documents/searchlight_dream_test'; +% holdout_set = ones(6, 1); for i = 2:5, holdout_set = [holdout_set; i*ones(6, 1)]; end +% +% % generate scripts in modeldir +% % --------------------------------------------------- +% searchlight_dream(dat, dist_n, mask, 'dat2', dat2, 'algorithm_name', ... +% 'cv_lassopcr', 'r', 6, 'modeldir', modeldir, 'cv_assign', holdout_set, ... +% 'save_weights', 'outcome_method', 'singleinterval'); +% +% See also: +% searchlight_disti.m, xval_cross_classify.m, fmri_data.predict.m, + +% Programmers' notes: +% + +%% where to save? + +modeldir = pwd; +send_email = false; +email_address = []; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case 'modeldir' + modeldir = varargin{i+1}; + varargin{i} = []; varargin{i+1} = []; + case 'email' + send_email = true; + email_address = varargin{i+1}; + varargin{i} = []; varargin{i+1} = []; + end + end +end + +%% distribution prep +dat = apply_mask(dat, mask); +dat = trim_mask(dat); +vox_num = dat.volInfo.n_inmask; + +dist_indx = distribution_prep(dist_n, vox_num); + +% save basic variables +if exist(modeldir, 'dir') + fprintf('modeldir is already existing. deleting and recreating the directory...\n'); + rmdir(modeldir, 's'); mkdir(modeldir); +else + mkdir(modeldir); +end + +savename = fullfile(modeldir, 'searchlight_dream_variables.mat'); +save(savename, '-v7.3', 'dat', 'mask', 'dist_indx', 'modeldir', 'varargin'); + +for i = 1:dist_n + generate_scripts(savename, modeldir, i, send_email, email_address, varargin); +end + +fprintf('Scripts have been generated in %s.\n', modeldir); + +end + + +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- +% +% Sub-functions +% +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- + +% ------------------------------------------------------------------------- +% Prep distributing jobs: Get dist_indx + +function dist_indx = distribution_prep(dist_n, vox_num) + +% preparation of distribution indx + +unit_num = ceil(vox_num/dist_n); +unit = true(unit_num,1); + +for i = 1:dist_n + start_point = (unit_num.*(i-1)+1); + end_point = min(start_point+unit_num-1, vox_num); + dist_indx(start_point:end_point,i) = unit(1:end_point-start_point+1); +end + +dist_indx = logical(dist_indx); + +end + +% ------------------------------------------------------------------------- +% save scripts in modeldir + +function generate_scripts(savename, modeldir, i, send_email, email_address, varargin) + +% write the codes +postfix = date; +postfix(strfind(postfix, '-')) = []; +distrib_script = fullfile(modeldir, ['searchlight_dream_variables_' num2str(i) '_' postfix '.m']); + +% if overwrite +% prev_script = fullfile(modeldir, ['searchlight_dream_variables_*.m']); +% delete(prev_script); +% end + +FID = fopen(distrib_script, 'w'); + +% description +fprintf(FID, ['%% searchlight analysis: variables are saved in ' savename '\n']); +fprintf(FID, '\n'); + +fprintf(FID, '%% path definition \n'); +fprintf(FID, 'reposdir = ''/dreamio3/wagerlab/Repository'';\n'); +fprintf(FID, 'addpath(genpath(reposdir));\n'); +fprintf(FID, 'spmdir = ''/usr/local/spm/spm8/'';\n'); +fprintf(FID, 'addpath(genpath(spmdir));\n'); +fprintf(FID, 'lassodir = ''/dreamio/home/chwo9116/codes_ineed/MATLAB/machine_learning/lasso_rocha'';\n'); +fprintf(FID, 'addpath(genpath(lassodir));\n'); +fprintf(FID, 'spiderdir = ''/dreamio/home/chwo9116/codes_ineed/MATLAB/spider'';\n'); +fprintf(FID, 'addpath(genpath(spiderdir));\n'); +fprintf(FID, 'mycodedir = ''/dreamio/home/chwo9116/codes_ineed/MATLAB/mycodes'';\n'); +fprintf(FID, 'addpath(mycodedir);\n'); +fprintf(FID, '\n'); +fprintf(FID, 'use_spider;\n'); +fprintf(FID, '\n'); + +% load variables +fprintf(FID, '%% load variables \n'); +fprintf(FID, ['load(''' savename ''');\n']); + +% run the analysis +fprintf(FID, '%% running searchlight \n'); +fprintf(FID, '\n'); +fprintf(FID, ['out = searchlight_disti(dat, mask, dist_indx(:,' num2str(i) '), varargin);\n']); + +fprintf(FID, 'fout = fields(out);\n'); +fprintf(FID, 'save_vars = '''';\n'); +fprintf(FID, 'for i = 1:numel(fout)\n'); +fprintf(FID, '\teval([fout{i} ''= out.'' fout{i} '';'']);\n'); +fprintf(FID, '\tsave_vars = [save_vars '''''''' char(fout{i}) '''''', ''];\n'); +fprintf(FID, 'end\n'); +fprintf(FID, 'clear out;\n'); +fprintf(FID, '\n'); + +% save data +fprintf(FID, '%% save output\n'); +fprintf(FID, ['save_newname = fullfile(''' modeldir '/searchlight_results_' num2str(i) '_' postfix '.mat'');\n']); +fprintf(FID, 'eval([''save(save_newname, '' save_vars ''''''-v7.3'''');'']);\n'); +fprintf(FID, '\n'); +if send_email + fprintf(FID, ['content = ''' modeldir '/searchlight_results_' num2str(i) '_' postfix ''';\n']); + fprintf(FID, ['sendmail_wani(''' email_address ''', ''searchlight_job_done'', content);\n']); +end +fclose(FID); + +end diff --git a/Statistics_tools/searchlight_saveresults.m b/Statistics_tools/searchlight_saveresults.m new file mode 100644 index 00000000..02131733 --- /dev/null +++ b/Statistics_tools/searchlight_saveresults.m @@ -0,0 +1,119 @@ +function searchlight_saveresults(modeldir) + +% This function combines and saves searchlight analysis results. +% +% Usage: +% ------------------------------------------------------------------------- +% searchlight_saveresults(modeldir) +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% modeldir the directory where the searchlight result mat files (e.g., +% searchlight_results_*_01Aug2014.mat) +% +% Outputs: +% ------------------------------------------------------------------------- +% This function saves the brain maps where each voxel contains a summary +% stat value (e.g., accuracy or outcome correlation) in the modeldir. The +% naming convention of the result maps are as follow: +% results_searchlight_(a)_(b)_dat(c).nii +% (a) will be a number - the number of test results +% (b) acc: accuracy, r: outcome correlation, p: p-values, se: standard error, +% and thr: threshold for the single-interval test +% (c) If the test was on one dataset, (c) will be empty, but if the +% test was cross-prediction, (c) will be 11, 12, 21, or 22. +% 11: trained on the first dataset, and tested on the first dataset +% 12: trained on the first dataset, and tested on the second dataset +% 21: trained on the second dataset, and tested on the first dataset +% 22: trained on the second dataset, and tested on the second dataset +% +% Output Examples +% ----------------------------------------------------- +% results_searchlight_1_r_dat11.nii (outcome correlation) +% results_searchlight_1_p_dat11.nii (p value for the correlation values) +% +% results_searchlight_2_acc_dat11.nii (accuracy) +% results_searchlight_2_p_dat11.nii (p value for the accuracy) +% results_searchlight_2_se_dat11.nii (standard error for the accuracy) +% results_searchlight_2_thr_dat11.nii (threshold for the accuracy test) + + +load(fullfile(modeldir, 'searchlight_dream_variables.mat')); +data = load(fullfile(modeldir, 'searchlight_dream_variables.mat'), 'varargin'); + +do_cross = false; + +% parsing varargin to know it's cross_classify or not +for i = 1:numel(data.varargin) + if ischar(data.varargin{i}) + switch data.varargin{i} + case 'dat2' + do_cross = true; + end + end +end + +res_files = filenames(fullfile(modeldir, 'searchlight_results_*mat')); + +% load one test_results to set up results variables +load(res_files{1}, 'test_results'); + +% image frame +if do_cross + dat.dat = zeros(size(dat.dat,1),4); +else + dat.dat = zeros(size(dat.dat,1),1); +end + +% results fields +for m = 1:numel(test_results) + res_fields{m} = fields(test_results{m}); + for j = 1:numel(res_fields{m}), eval(['dat_' num2str(m) '_' res_fields{m}{j} ' = dat;']); end +end + +% combining results +for ii = 1:numel(res_files) + load(res_files{ii}, 'test_results'); + + for m = 1:numel(test_results) + for j = 1:numel(res_fields{m}) + eval(['test_results{' num2str(m) '}.' res_fields{m}{j} '(isnan(test_results{' num2str(m) '}.' res_fields{m}{j} ')) = 0;']); + eval(['dat_' num2str(m) '_' res_fields{m}{j} '.dat = dat_' num2str(m) '_' res_fields{m}{j} '.dat + test_results{' num2str(m) '}.' res_fields{m}{j} ';']); + end + end +end + +% image names for each column +if do_cross + col_names = {'dat11', 'dat12', 'dat21', 'dat22'}; +else + col_names = {'dat'}; +end + +% write images +for m = 1:numel(test_results) + for jj = 1:numel(res_fields{m}) + eval(['for ii = 1:size(dat_' num2str(m) '_' res_fields{m}{jj} '.dat,2), dat.dat = dat_' ... + num2str(m) '_' res_fields{m}{jj} '.dat(:,ii); dat.fullpath = fullfile(modeldir, [''results_searchlight_' ... + num2str(m) '_' res_fields{m}{jj} '_'' col_names{ii} ''.nii'']); write(dat); end']); + end +end + +end diff --git a/Statistics_tools/shift_correl.m b/Statistics_tools/shift_correl.m new file mode 100755 index 00000000..0a458664 --- /dev/null +++ b/Statistics_tools/shift_correl.m @@ -0,0 +1,294 @@ +% function [shiftvals, corrvals, bestshift, bestcorr, aout, bout] = shift_correl(a, b, ['max_shift', max_shift], ['shift_step', shift_step], ['use_rob_stats', 0|1], ['return_betas', 0|1], ['display_plots', 0|1],['priors',[mu sig]]) +% +% shifts a backwards and forwards by max_shift elements (default 12) +% in each direction, computing the correlation +% between a and b at each shift value +% +% Thus, negative values mean a happens AFTER b +% Positive values means b happens later +% +% truncates the tails of a and b where necessary +% to ensure they're the same length. +% +% INPUTS: +% a, b two vectors to correlate +% max_shift max number of elements to shift (default 12) +% shift_step size of incremental shifts (default .1) +% use_rob_stats robust regression, IRLS (default 0) +% return_betas return betas rather than corr coeffs (default 0) +% display_plots display plot of shift vals and correlations/betas (default 0) +% NOTE: robust r value of chosen solution may not be max, +% as weighted_corrcoef now does not account for variance +% inflation with low weights for some observations +% priors Incorporate gaussian priors with mean priors(1) and std +% priors(2) +% example: +% +% % first extract data for a subject into clusters: +% clusters = roi_probe(spm_get(Inf), 'snpm0002_varsm_cov_clusters.mat'); +% +% % OR +% +% clusters = mask2clusters('SnPMt_filtered.img', spm_get(Inf)); +% +% % Then do the shifting: +% +% for i=1:length(clusters) +% [shiftvals, corrvals] = shift_correl(xX.X(:, 1), clusters(i).timeseries); +% title(['Cluster' num2str(i)]); +% disp(['Cluster ' num2str(i) ' estimate shift by ' num2str(1.5 * shiftvals(corrvals == max(corrvals)))]); +% end +% +% +% Simulation: +% =================================================== +% trueval = 1; n = 100; noisevar = 0; +% a = randn(n,1); b = a + randn(n,1).*noisevar; +% a=smooth_timeseries(a,n./5); b=smooth_timeseries(b,n./5); +% a = shift_signal(a,trueval); b = b(1:length(a)); +% figure;plot(a);hold on; plot(b,'r') +%[shiftvals, corrvals, bestshift, bestcorr, aout, bout] = ... +% shift_correl(a, b, 'max_shift', 4, 'shift_step', .2, 'use_rob_stats', 1, 'return_betas', 0, 'display_plots', 1,'optimize'); bestshift +% +% r = .7; shiftstep = 1; optstr = 'optimize'; % or 'noopt' +% tic +% for i = 1:10 +% ab = mvnrnd([0 0],[1 r; r 1],n); a = ab(:,1); b = ab(:,2); +% a=smooth_timeseries(a,n./5); b=smooth_timeseries(b,n./5); a = shift_signal(a,trueval); b = b(1:length(a)); +% [shiftvals, corrvals, bestshift(i), bestcorr(i), aout, bout] = shift_correl(a, b, 'max_shift', 4, 'shift_step', shiftstep, 'use_rob_stats', 1,optstr); +% end +% toc + +function [shiftvals, corrvals, bestshift, bestcorr, aout, bout] = shift_correl(a, b, varargin) + displaying_plots = 0; + using_rob_stats = 0; + returning_betas = 0; + shiftvals = []; + corrvals = []; + max_shift = 12; + shift_step = 1; + do_optimize = 0; + priors = []; +ar_model = 0; + + for i=1:length(varargin) + if ischar(varargin{i}) + switch(varargin{i}) + case 'max_shift' + max_shift = varargin{i+1}; + case 'shift_step' + shift_step = varargin{i+1}; + case {'using_rob_stats' 'use_rob_stats'} + warning('off','stats:statrobustfit:IterationLimit'); + using_rob_stats = varargin{i+1}; + case {'returning_betas' 'return_betas'} + returning_betas = varargin{i+1}; + case {'displaying_plots' 'display_plots'} + displaying_plots = varargin{i+1}; + case 'optimize' + do_optimize = 1; + case {'prior' 'priors' 'prior_prob'} + priors = varargin{i+1}; + + case {'ar', 'ar1', 'ar_model'} + ar_model = 1; + + case {'ar2'} + ar_model = 2; + end + end + end + + if(size(a,2)~=1), a = a'; end + if(size(b,2)~=1), b = b'; end + + whnan = find(isnan(a) | isnan(b)); + a(whnan) = []; + b(whnan) = []; + + shiftvals = -max_shift:shift_step:max_shift; + len = length(shiftvals); + corrvals = zeros(1,len); + pvals = zeros(1,len); + + for shift = 1:len + + pvals(shift) = do_shift_corr(shiftvals(shift),a,b,[],using_rob_stats,returning_betas,priors, ar_model); + + end + + bestidx = find(pvals == min(pvals)); + bestshift = shiftvals(bestidx); + + % get aout and bout, based on bestshift + truncate_output_vectors(); + + bestcorr = compute_corrs_or_betas(aout,bout,using_rob_stats,returning_betas); + %bestcorr = corrvals(bestidx); + + % refine search with nonlinear fit + if do_optimize + options = optimset('MaxFunEvals',100000,'Maxiter',10000000,'Display','off','LevenbergMarquardt','on'); + limits = [bestshift - .99 bestshift + .99]; + bestshift = fminsearch(@(shift) do_shift_corr(shift,a,b,limits,using_rob_stats,returning_betas,priors,ar_model),bestshift,options); + + % get aout and bout, based on bestshift + truncate_output_vectors(); + + % get best corr + bestcorr = compute_corrs_or_betas(aout,bout,using_rob_stats,returning_betas); + else + truncate_output_vectors(); + end + + if displaying_plots + % get all corrvals + for shift = 1:len + [shiftedvec,unshiftedvec] = shift_vec(a,b,shiftvals(shift)); + corrvals(shift) = compute_corrs_or_betas(shiftedvec,unshiftedvec,using_rob_stats,returning_betas); + end + + display_plots(); + end + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%% + % Nested functions + %%%%%%%%%%%%%%%%%%%%%%%%% + + + + function truncate_output_vectors() + if bestshift > 0 + bout = shift_signal(b, bestshift); + aout = a(1:length(bout)); + elseif bestshift < 0 + aout = shift_signal(a, bestshift); + bout = b(1:length(aout)); + else + aout = a; + bout = b; + end + end + + + function display_plots() + create_figure('shift_correl_plot', 1, 2); + + subplot(1, 2, 1); + plot(shiftvals, corrvals, 'ko-', 'LineWidth', 2); hold on; + plot(bestshift, bestcorr, 'rx', 'MarkerSize', 16, 'LineWidth', 2); + + xlabel('Shift (in elements)'); + if(returning_betas) + ylabel('Betas'); + else + ylabel('Correlation'); + end + + subplot(1, 2, 2); + plot_correlation_samefig(aout, bout, [], 'k.', 0, using_rob_stats); + xlabel('a'); + ylabel('b'); + end + +end % main function + + +% this function is used in fminsearch optimization +% returns p-values, which are minimized in the optimization +% alternatively, if return betas is specified, returns 1./abs(beta) which +% should also be minimized by fminsearch or comparable algorithm +function pval = do_shift_corr(shift,a,b,limits,using_rob_stats,returning_betas,priors,ar_model) + + % constrain to prespecified limits + if ~isempty(limits), shift = max(shift,limits(1)); shift = min(shift,limits(2)); end + + %if abs(shift) > max_shift, shift = sign(shift) .* max_shift; end + + % compute + [shiftedvec,unshiftedvec] = shift_vec(a,b,shift); + compute_pvals_or_betas(); % returns log pval, or 1/abs(slope beta). Lower values are better. + + % incorporate priors, if we have them + % P(h | dat) = P(dat | h)P(dat) / P(h) + % assume P(dat) = 1. It's unknown. P(h) is priorp + if ~isempty(priors) + priorp = log(max(normpdf(shift,priors(1),priors(2)),eps)); + pval = pval - priorp; % This is P(dat) / P(h), in log units. pval is actually log. adding logs multiplies p-values. + end + + %correrr = 1 ./ abs(corrval); + + %inline + % ----------------------------------- + function compute_pvals_or_betas() + % Returns subtractand of p-values ( 1 - p ) or betas + if all(shiftedvec - mean(shiftedvec) < eps) || all(unshiftedvec - mean(unshiftedvec) < eps) + % no variance in a or b + pval = NaN; + elseif using_rob_stats && returning_betas + betas = robustfit(shiftedvec, unshiftedvec); + pval = 1./abs(betas(2)); + + elseif using_rob_stats + [betas, stats] = robustfit(shiftedvec, unshiftedvec, 'bisquare'); %#ok + pval = log(stats.p(2)); + %r = weighted_corrcoef([shiftedvec unshiftedvec], stats.w); + %corrval = r(1, 2); + + elseif returning_betas + betas = glmfit(shiftedvec, unshiftedvec); + pval = 1./abs(betas(2)); + %corrval = betas(2); + + elseif ar_model % ar(2) + [betas, t, pval] = fit_gls(shiftedvec,unshiftedvec, [], ar_model); + + else + [R,p] = corrcoef(shiftedvec, unshiftedvec); + %corrval = R(1, 2); + pval = log(p(1,2)); + end + end +end + + + +function [shiftedvec,unshiftedvec] = shift_vec(a,b,shift) + if(shift > 0) + shiftedvec = shift_signal(b, shift); + unshiftedvec = a(1:length(shiftedvec)); + else + shiftedvec = shift_signal(a, shift); + unshiftedvec = b(1:length(shiftedvec)); + end +end + + +function val = compute_corrs_or_betas(shiftedvec,unshiftedvec,using_rob_stats,returning_betas) + + % remove for speed + %if all(shiftedvec - mean(shiftedvec) < eps) || all(unshiftedvec - mean(unshiftedvec) < eps) + % no variance in a or b + % val = NaN; + if using_rob_stats && returning_betas + betas = robustfit(shiftedvec, unshiftedvec); + val = betas(2); + elseif using_rob_stats + [betas, stats] = robustfit(shiftedvec, unshiftedvec, 'bisquare'); %#ok + % if(~iscol(shiftedvec)), shiftedvec = shiftedvec'; end + % if(~iscol(unshiftedvec)), unshiftedvec = shiftedvec'; end + r = weighted_corrcoef([shiftedvec unshiftedvec], stats.w); + val = r(1, 2); + elseif returning_betas + betas = glmfit(shiftedvec, unshiftedvec); + val = betas(2); + else + R = corrcoef(shiftedvec, unshiftedvec); + val = R(1, 2); + end +end \ No newline at end of file diff --git a/Statistics_tools/shift_signal.m b/Statistics_tools/shift_signal.m new file mode 100755 index 00000000..bb542522 --- /dev/null +++ b/Statistics_tools/shift_signal.m @@ -0,0 +1 @@ +% function [shiftedy] = shift_signal(y, shiftby, [interpolation method]) % % Shifts signal contained in y back 'shiftby' steps % % Note the variable shiftby should be positive. function shiftedy = shift_signal(y, shiftby, interp_method) if(~isvector(y)) error('shift_signal can only shift vectors'); end shiftby = abs(shiftby); if(~exist('interp_method', 'var') || isempty(interp_method)) interp_method = 'linear'; end if(mod(shiftby, 1) ~= 0) shiftedy = interp1(y, (shiftby+1):length(y), interp_method); else shiftedy = y(shiftby+1:end); end if size(shiftedy,2)~=1, shiftedy = shiftedy'; end end \ No newline at end of file diff --git a/Statistics_tools/signtest_matrix.m b/Statistics_tools/signtest_matrix.m new file mode 100644 index 00000000..6eab59d1 --- /dev/null +++ b/Statistics_tools/signtest_matrix.m @@ -0,0 +1,70 @@ +function stats = signtest_matrix(dat) +% stats = signtest_matrix(dat) +% +% This is a matric-ized version of Matlab 2010's signtest.m +% it returns identical P-values to Matlab's function +% +% copyright 2011, tor wager + +% n: how many obs in sign test +n = size(dat, 1); +n_omit = sum(dat == 0 | isnan(dat)); +n = (n - n_omit)'; + +isvalid = n > 0; + +dat(:, ~isvalid) = []; +n(~isvalid) = []; + +npos = sum(dat > 0); +nneg = sum(dat < 0); +sgn = min(npos, nneg)'; +mydirection = double(npos > nneg)'; +mydirection(mydirection == 0) = -1; + +if max(n) < 100 + method = 'exact'; +else + method = 'approximate'; +end + + +if isequal(method,'exact') + p = min(1, 2 .* binocdf(sgn, n, 0.5)); % p > 1 means center value double-counted + zval = NaN .* ones(size(p)); +else + % Do a continuity correction, keeping in mind the right direction + z = (npos-nneg - sign(npos-nneg)) ./ (n .^ .5); + p = 2 .* normcdf(-abs(z), 0, 1); + stats.zval = z; + stats.p = p; +end + +% FDR correction +% p = p(stats.isvalid); % already done +pthr = FDR(p, .05); +if isempty(pthr), pthr = Inf; end + +stats.pthr_fdr05 = pthr; +sig = p < .05; +fdr_sig = p < pthr; + + +% Re-insert removed CASES (rows) and fill with zeros +mydirection = zeroinsert(~isvalid, mydirection); +n = zeroinsert(~isvalid, n); +p = zeroinsert(~isvalid, p); +zval = zeroinsert(~isvalid, zval); +sig = zeroinsert(~isvalid, sig); +fdr_sig = zeroinsert(~isvalid, fdr_sig); + + +stats = struct('method', method, 'isvalid', isvalid, 'n', n, ... +'direction', mydirection, 'p', p, 'zval', zval, 'pthr_fdr05', pthr, ... +'sig', sig, 'fdr_sig', fdr_sig); + + +end % function + + + \ No newline at end of file diff --git a/Statistics_tools/ste.m b/Statistics_tools/ste.m new file mode 100755 index 00000000..46c20601 --- /dev/null +++ b/Statistics_tools/ste.m @@ -0,0 +1,30 @@ +function [my_ste,t,n,p,m] = ste(dat) +% function [my_ste,t,n_in_column,p,mean] = ste(dat) +% +% tor wager +% +% standard error of the mean +% and t-values, columnwise +% and p-values, if asked for +% +% omits NaN values row-wise within each column +% (edit Nov 30, 2006 by tor) +% +% does NOT use n - 1 +% matches matlab t-test function + +n = sum(~isnan(dat),1); +%if ~isempty(whom), dat(whom,:) = [];, end + +m = nanmean(dat); +my_ste = nanstd(dat) ./ (n.^.5); + +if nargout > 1 + t = m ./ my_ste; +end + +if nargout > 3 + p = 2 .* (1 - tcdf(abs(t), n - 1)); +end + +return \ No newline at end of file diff --git a/Statistics_tools/stepwise_tor.m b/Statistics_tools/stepwise_tor.m new file mode 100755 index 00000000..0d1c2f1d --- /dev/null +++ b/Statistics_tools/stepwise_tor.m @@ -0,0 +1,50 @@ +function STEPWISE = stepwise_tor(dat,y,varargin) +% STEPWISE = stepwise_tor(dat, y, [pred. names], [alpha]) +% +% Stepwise regression using Matlab with a couple of extras: +% Print omnibus F-values for stepwise regression +% get adjusted R-squared +% save output structure +% +% tor wager + +alph = .05; +if length(varargin) > 1 + alph = varargin{2}; +end + +if length(varargin) > 0 + nms = varargin{1}; +else + for i = 1:size(dat,2), nms{i} = ['V ' num2str(i)]; end +end + + % Add logistic regression for categorical 1/0 DVs! + + + [STEPWISE.b,STEPWISE.se,STEPWISE.pval, ... + STEPWISE.inmodel,stats] = ... + stepwisefit(dat,y,'penter',alph,'display','off'); + + t = stats.TSTAT; + STEPWISE.t = t; + + S = STEPWISE; + tabled = [S.b S.se t S.inmodel' S.pval]; tabnames = {'Beta' 'Std. Err.' 't-value' 'In Model' 'p-value'}; + print_matrix(tabled, tabnames,nms); + + N = stats.dfe+stats.df0; + r2 = (stats.SStotal-stats.SSresid) ./ stats.SStotal; + adjr2 = 1 - (1-r2)*((N - 1) ./ (N - stats.df0 - 1)); + + fprintf(1,'\nOmnibus F(%3.0f,%3.0f) = %3.2f, RMSE = %3.2f, p = %3.6f, Adj R^2 = %3.2f\n', ... + stats.df0,stats.dfe,stats.fstat, ... + stats.rmse,stats.pval,adjr2); + stats.adjr2 = adjr2; + STEPWISE.stats = stats; + + + + + return + \ No newline at end of file diff --git a/Statistics_tools/stouffer.m b/Statistics_tools/stouffer.m new file mode 100755 index 00000000..494e8433 --- /dev/null +++ b/Statistics_tools/stouffer.m @@ -0,0 +1,35 @@ +function [z,p,sig] = stouffer(p,varargin) +% function [z,p,sig] = stouffer(p,[alph]) +% +% inputs: p values in 4-D array +% 1st 3 dims are within images, dim4 = image +% optional: alpha value for thresholding +% +% outputs: +% z stouffer's combined test statistic, compare to normal +% p p-values for combined test +% sig signficance 1 / 0 binary mask, if alpha is specified +% +% tor wager +% +% Described in: +% Lazar, N. A., Luna, B., Sweeney, J. A., & Eddy, W. F. (2002). +% Combining brains: a survey of methods for statistical pooling +% of information. Neuroimage, 16(2), 538-550. +% +% Stouffer, S. A., Suchman, E. A., DeVinney, L. C., Star, S. A., and +% Williams, R. M. 1949. The American Soldier: Vol. I. Adjustment +% During Army Life. Princeton University Press, Princeton. +% + +if length(varargin) > 0, alph = varargin{1};,else,alph = 0;,end + +lastdim = length(size(p)); +k = size(p,lastdim); + +z = sum(norminv(1 - p),lastdim) ./ sqrt(k); +p = normcdf(1-z); + +if alph, sig = p <= alph;,end + +return diff --git a/Statistics_tools/subset_indicator_matrix.m b/Statistics_tools/subset_indicator_matrix.m new file mode 100644 index 00000000..c058d20f --- /dev/null +++ b/Statistics_tools/subset_indicator_matrix.m @@ -0,0 +1,17 @@ +function subsets = subset_indicator_matrix(n) + % subsets = subset_indicator_matrix(n) + % + % Create a matrix whose rows contain indicators (1/0 values) for all + % possible subsets of n variables + % + % Use this, for example, to create a matrix that tells you all possible + % combinations of regressors to include in a regression. + % + % Tor Wager, March 07 + + + vals = cell(1,n); + [vals{:}] = deal([1 0]); + subsets = combvec(vals{:})'; + +end \ No newline at end of file diff --git a/Statistics_tools/t_test2.m b/Statistics_tools/t_test2.m new file mode 100755 index 00000000..8f964203 --- /dev/null +++ b/Statistics_tools/t_test2.m @@ -0,0 +1,85 @@ +function [h,sig,ci,tval,ser] = t_test2(x,m,alpha,tail) +%TTEST Hypothesis test: Compares the sample average to a constant. +% [H,SIG] = TTEST(X,M,ALPHA,TAIL) performs a T-test to determine +% if a sample from a normal distribution (in X) could have mean M. +% M = 0, ALPHA = 0.05 and TAIL = 0 by default. +% +% The Null hypothesis is: "mean is equal to M". +% For TAIL=0, alternative: "mean is not M". +% For TAIL=1, alternative: "mean is greater than M" +% For TAIL=-1, alternative: "mean is less than M" +% TAIL = 0 by default. +% +% ALPHA is desired significance level. +% SIG is the probability of observing the given result by chance +% given that the null hypothesis is true. Small values of SIG cast +% doubt on the validity of the null hypothesis. +% H=0 => "Do not reject null hypothesis at significance level of alpha." +% H=1 => "Reject null hypothesis at significance level of alpha." + +% References: +% [1] E. Kreyszig, "Introductory Mathematical Statistics", +% John Wiley, 1970, page 206. + +% Copyright (c) 1993-98 by The MathWorks, Inc. +% $Revision: 2.7 $ $Date: 1998/05/28 20:13:56 $ + +if nargin < 1, + error('Requires at least one input argument.'); +end + +[m1 n1] = size(x); +if (m1 ~= 1 & n1 ~= 1) + error('First argument has to be a vector.'); +end + +if nargin < 2 + m = 0; +end + +if nargin < 4, + tail = 0; +end + +if nargin < 3, + alpha = 0.05; +end + +if (alpha <= 0 | alpha >= 1) + fprintf('Warning: significance level must be between 0 and 1\n'); + h = NaN; + sig = NaN; + ci = [NaN NaN]; + return; +end + +samplesize = length(x); +xmean = mean(x); +ser = std(x) ./ sqrt(samplesize); +tval = (xmean - m) / ser; +sig = tcdf(tval,samplesize - 1); + +% the significance just found is for the tail = -1 test + +crit = tinv(1 - alpha,samplesize - 1) .* ser; + +if tail == 1 + sig = 1 - sig; +elseif tail == 0 + sig = 2 * min(sig,1 - sig); + crit = tinv((1 - alpha / 2),samplesize - 1) .* ser; +end + +ci = [(xmean - crit) (xmean + crit)]; + +% if tval > 0, sig = 1-sig;,end + +% Determine if the actual significance exceeds the desired significance +h = 0; +if sig <= alpha, + h = 1; +end + +if isnan(sig), + h = NaN; +end diff --git a/Statistics_tools/time_varying_estimate.m b/Statistics_tools/time_varying_estimate.m new file mode 100644 index 00000000..d35b5e17 --- /dev/null +++ b/Statistics_tools/time_varying_estimate.m @@ -0,0 +1,137 @@ +function y = time_varying_estimate(meth,data,varargin) +% y = time_varying_estimate(meth,data,[window width],[function handle]) +% +% Performs correlation operation (default) +% or any function you pass in (as a function handle) +% on symmetric moving average of data +% +% Works on all columns of the data input matrix together +% so function handles can be multivariate +% +% Window options (meth argument): +% 'gaussian' +% 'tukey' +% +% Optional input: stepby +% Sometimes input data is very high resolution, and it would take too much +% time to work element by element across the inputs. You can enter an +% option here to compute estimates at every n-th lag. +% +% Examples: +% +% y = time_varying_estimate('gaussian',data,20); +% +% Generate sample data: +% x = mvnrnd([0 0], [1 .6; .6 1], 100); +% +% Correlation between columns of x: +% r = time_varying_estimate('tukey', x, 20); +% +% St. deviation of first column +% mystd = time_varying_estimate('tukey', x(:, 1), 20, @(y) std(y)); +% +% By Tor Wager +% Last updated: Dec 2008 + +nshift = 0; +stepby = 1; % step size for shift + +% set up the kernel +% ------------------------------------------- +switch meth + + case 'gaussian' + + ntrials = varargin{1}; + kern = normpdf(-3:6/ntrials:3); + kern = kern./max(kern); % norm to max of 1 + + mymax = find(kern == max(kern)); + nshift = mymax - 1; % kernel shifts by n points; adjust + + kern = kern'; % column + + case 'tukey' + % Window length is the zero-influence to zero-influence time + kern = tukeywin(varargin{1}); + nshift = round(varargin{1} ./ 2); + + % kludgey adjust in case we need extra element + %if length(i - shift : i + nshift) > length(kern) + kern = [kern; 0]; + %end + otherwise error('Unknown method.') + +end + +% set up function handle +% ------------------------------------------- +if length(varargin) > 1 + fhandle = varargin{2}; +else + fhandle = @(y) my_corrcoef(y); +end + +if length(varargin) > 2 + stepby = varargin{3}; +end + +% set up data +% ------------------------------------------- +[nobs,ncols] = size(data); + +% replicate kernel for each column +kern = repmat(kern,1,ncols); + +if nobs < nshift, error('Not enough observations to support kernel.'); end +y = zeros(nobs,1); + +% pad data at ends to avoid edge artifacts +% ------------------------------------------- +paddat = data(end:-1:end-nshift,:); + +data = [data; paddat]; + +paddat = data(nshift:-1:1,:); + +data = [paddat; data]; + + +% execute +% ------------------------------------------- +for i = [(nshift+1):stepby:(nobs + nshift) (nobs + nshift)] + % start at nshift to avoid ends; data is padded + + % set up windowed data + + dati = data(i - nshift : i + nshift,:); + + dati = scale(dati,1) .* kern; % center and multiply by kern so data taper towards mean + + % execute: IN DEVELOPMENT: THIS is hard-coded for weighted correlation + %r = weighted_corrcoef(dati,kern(:,1)); + %r = corrcoef(dati); + + y(i, :) = fhandle(dati); %r(1,2); + + %y(:,i) = tmpy(nshift+1:nshift+nobs); + +end + +if stepby == 1 + y = y( (nshift+1):(nobs + nshift) ); + +else + indx = [(nshift+1):stepby:(nobs + nshift) (nobs + nshift)]; + y = interp1(indx, y(indx), (nshift+1):(nobs + nshift)); + +end + +end + + + +function y = my_corrcoef(dati) +r = corrcoef(dati); +y = r(1,2); +end diff --git a/Statistics_tools/tscv.m b/Statistics_tools/tscv.m new file mode 100644 index 00000000..c583ecce --- /dev/null +++ b/Statistics_tools/tscv.m @@ -0,0 +1,117 @@ +function [trIdx, teIdx] = tscv(vectorlen, varargin) +% Create a crossvalidation test and train index for time series data. +% Larger h will ensure less dependence. Larger v creates larger test sets +% Number of folds = vectorlen - (2*h + 2*v) +% +% -See http://robjhyndman.com/hyndsight/tscvexample/ for more info about rolling cv +% -See Racine, J. (2000). Consistent cross-validatory model-selection for dependent data: hv-block cross-validation. Journal of Econometrics, 99(1), 39-61. +% +% [trIdx, teIdx] = rollingcv(vectorlen, stepsize) +% +% Inputs: +% --------------------------------------------------------------------- +% vectorlen : length of vector to create holdout cross-validation set +% +% Optional inputs with their default values: +% :-------------------------------------------- +% +% 'hvblock' = [h,v] : use hvblock cross-validation with a block +% size of 'h' (0 reduces to v-fold xval)and +% number of test observations 'v' (0 reduces +% to h-block xval) +% +% 'rolling' = [h,v,g] : use hvblock cross-validation with g training steps +% surrounding hv block. Akin to Rolling +% crossval. Same properties as hvblock. +% +% Outputs: +% --------------------------------------------------------------------- +% trIdx : structure with training label index +% teIdx : structure with test label index +% +% Examples: +% --------------------------------------------------------------------- +% [trIdx, teIdx] = tscv(100, 'hvblock',[5,2]); % use hvblock with h=5 and v=2 +% [trIdx, teIdx] = tscv(100, 'rolling',[5,2,10]); % use hvblock with h=5, v=2 and g=10 +% +% Original version: Copyright Luke Chang & Hedwig Eisenbarth 11/2013 + +% Programmer's Notes: +% LC 11/28/13: +% -changed input and documentation +% -Don't use matlab functions as variable names (e.g., median) - +% median > mid +% rewrote the hv block so that it loops though all available data - +% need to finish coding the rollingcv option +% LC & HE 12/16/13: +% -added rollingcv option + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'hvblock'} + xval_type = varargin{i}; + inval = varargin{i + 1}; + h = inval(1); + v = inval(2); + varargin{i} = []; + varargin{i + 1} = []; + + case {'rolling'} + xval_type = varargin{i}; + inval = varargin{i + 1}; + h = inval(1); + v = inval(2); + g = inval(3); + varargin{i} = []; + varargin{i + 1} = []; + end + end +end + +switch xval_type + case 'hvblock' + %hv cross validation: leave completely out h steps around the test interval + %See Racine, J. (2000). Consistent cross-validatory model-selection for dependent data: hv-block cross-validation. Journal of Econometrics, 99(1), 39-61. + + stepsize = 2*v + 2*h + 1; + + if stepsize > vectorlen; + error('stepsize is too large, please decrease') + end + + start = 1; + while start <= vectorlen - stepsize + 1 + trIdx{start} = true(vectorlen,1); + teIdx{start} = false(vectorlen,1); + trIdx{start}(start:(start + 2*h + 2*v)) = false; %train set = everything - 2*v + 2*h + 1 + teIdx{start}((start + h):(start + h + 2*v)) = true; %test set = 2*v + 1 + start = start + 1; + end + + case 'rolling' % this needs to be fixed. + %rolling cross validation: leave completely out h steps around the + %test interval with g training steps + %See http://robjhyndman.com/hyndsight/tscvexample/ for more info + + if g == 0 + error('g must be greater than 0') + end + + stepsize = 2*g + 2*v + 2*h + 1; + + if stepsize > vectorlen; + error('stepsize is too large, please decrease') + end + start = 1; + while start <= vectorlen - stepsize + 1 + trIdx{start} = false(vectorlen,1); + teIdx{start} = false(vectorlen,1); + trIdx{start}(start:(start + g - 1)) = true; %train set = everything - 2*v + 2*h + 1 + trIdx{start}(start + g + 2*h + 2*v+1:(start + 2*g + 2*h + 2*v)) = true; %train set = everything - 2*v + 2*h + 1 + teIdx{start}((start + g + h):(start + g + h + 2*v)) = true; %test set = 2*v + 1 + start = start + 1; + end +end + diff --git a/Statistics_tools/tsquaretest.m b/Statistics_tools/tsquaretest.m new file mode 100644 index 00000000..d3fe4e9d --- /dev/null +++ b/Statistics_tools/tsquaretest.m @@ -0,0 +1,50 @@ +function [t2, S, p, t2crit, dfb, dfe, sc] = tsquaretest(X,pthresh, u0) +% +% Hotelling's t-square test that the multivariate sample mean of X is +% different from u0. +% +% X is an observations x variables data matrix +% If pthresh is entered, the critical t2 value is returned +% If u0 is not entered, the default null hypothesis is mean zero (the +% origin) +% +% tor wager, july 7, 2006 + +[n,k] = size(X); + +if nargin < 3 + % null hypothesis test value + u0 = zeros(1,k); +end + +% mean +xbar = mean(X); + +% deviations +Xdev = X - repmat(xbar,n,1); + +% sample covariance (follows a Wishart dist) +S = Xdev' * Xdev ./ (n-1); + +% Hotelling's t-square +t2 = n * (xbar - u0) * inv(S) * (xbar - u0)'; + + +% scaling of F dist; t2 follows sc*F(k,n-k) +sc = k * (n-1) ./ (n - k); + +% degrees of freedom +dfb = k; dfe = n - k; + +if nargin > 1 + % critical t2 value + t2crit = sc * finv(1 - pthresh,dfb,dfe); +else + t2crit = []; +end + +% p-value +p = 1 - fcdf(t2 ./ sc,dfb,dfe); + +return + diff --git a/Statistics_tools/ttest2_printout.m b/Statistics_tools/ttest2_printout.m new file mode 100644 index 00000000..6e91d014 --- /dev/null +++ b/Statistics_tools/ttest2_printout.m @@ -0,0 +1,45 @@ +function [H,p,ci,stats] = ttest2_printout(sc1,sc2,varargin) +% [H,p,ci,stats] = ttest2_printout(sc1,sc2,[doplot],[covts]) +% +% one or two sample t-test printout and plot +% covariates are not done yet! +% +% sc1: data from first group +% sc2: data from second group (if missing or empty, performs one-sample +% t-test) +% +% tor wager, last updated Sept 2007 (cosmetic update) + +if length(varargin) > 2 + % covariates of no interest + X = varargin{2}; + % NOT DONE YET. +end + +if nargin == 1 || isempty(sc2) + [H,p,ci,stats] = ttest(sc1,0,.05,'both'); + fprintf(1,'u1 = %3.2f, t(%3.1f) = %3.2f, p = %3.4f\n',nanmean(sc1),stats.df,stats.tstat,p); +else + [H,p,ci,stats] = ttest2(sc1,sc2,.05,'both','unequal'); + fprintf(1,'u1 = %3.2f, u2 = %3.2f, udiff = %3.2f, t(%3.1f) = %3.2f, p = %3.4f\n',nanmean(sc1),nanmean(sc2),nanmean(sc1) - nanmean(sc2),stats.df,stats.tstat, p); +end + + +if length(varargin) > 0 && varargin{1} + means = [nanmean(sc1) nanmean(sc2)]; + + hh = bar(means); + set(hh,'FaceColor',[.7 .7 .7]); + + se = stats.sd ./ sqrt(length(sc1)+length(sc2)); + + se = se'; + tor_bar_steplot(means,se,{'k'}); + + set(gca,'XTick',[1 2],'XTickLabel',{'High' 'Low'}); + +end + + +return + diff --git a/Statistics_tools/ttest3d.m b/Statistics_tools/ttest3d.m new file mode 100755 index 00000000..8384ac2b --- /dev/null +++ b/Statistics_tools/ttest3d.m @@ -0,0 +1,92 @@ +function [mxc,t,sig,out] = ttest3d(xc) + % [mean,t,sig,out] = ttest3d(xc) + % + % calculate t-statistic values for a k x k x n 3-D matrix of + % values across n subjects. + % stats on each element, across 3rd dim + % + % tor wager + % + % to print output, see also: + % correlation_to_text(mxc,sig); + + % ------------------------------------------------------------ + % stats on each element, across 3rd dim + % ------------------------------------------------------------ + + warning off, clear stelatency, clear tlat, clear stecorr, clear tcorr + % standard errors and t-values, element by element + + for j = 1 : size(xc,2) + for k = 1 : size(xc,2) + + tmp = squeeze(xc(j,k,:)); + %z = .5 * log( (1+tmp) ./ (1-tmp) ); % Fisher's r-to-z + %transform (old, for + %correlation inputs) + [stecorr(j,k), t(j,k), nobs(j, k), p(j, k), mxc(j, k)] = ste(tmp); % correl in rand fx analysis across ss + end + end + warning on + + % done above + %mxc = nanmean(xc,3); % mean cross-correlations among k regions, group average + + out.mean = mxc; + out.ste = stecorr; + out.t = t; + + out.nobs = nobs; + + p(p == 0) = 1000*eps; % fix for very very sig effects + + out.pvals = p; + + % Alpha correction - bonferroni. + numtests = (size(mxc,1)*(size(mxc,1)-1))/2; % number of obs in upper triangle + corrp = 1 - (0.05 / (2 * ( numtests ))); % 2-tailed corr p + + %crit_t = tinv_t(corrp,size(xc,3)-1); % critical t-value for corrected significance + %crit_tu = tinv_t(1-(.05/2),size(xc,3)-1); % critical t-value for uncorrected significance + + %out.numtests = numtests; + out.bonf_corr_pthresh = corrp; + %out.crit_t = crit_t; + %out.crit_tu = crit_tu; + %out.crit_t_descrip = 'Critical t-values for Bonferoni correction on number of upper triangular elements'; + %out.crit_tu_descrip = 'Uncorrected critical t-value, p < .05, 2-tailed'; + out.numcomps = numtests; + + sig = p < corrp; %abs(t) > crit_t; + sigu = p < .05; %abs(t) > crit_tu; + + sig = sig .* sign(mxc); + sigu = sigu .* sign(mxc); + + sig(isnan(sig)) = 0; + sigu(isnan(sigu)) = 0; + + out.sig = sig; + out.sigu = sigu; + + N = size(xc, 3); + %out.pvals = 1 - tcdf(abs(t), N - 1); + %out.pvals_descrip = 'one-tailed p-values'; + + [out.fdrthr, out.fdrsig] = fdr_correct_pvals(out.pvals, t); + +end + + +function [pthr,sig] = fdr_correct_pvals(p,r) + + psq = p; psq(find(eye(size(p,1)))) = 0; + psq = squareform(psq); + pthr = FDR(p,.05); + if isempty(pthr), pthr = 0; end + + sig = sign(r) .* (p < pthr); + + sig(isnan(sig)) = 0; +end + diff --git a/Statistics_tools/var_prctile.m b/Statistics_tools/var_prctile.m new file mode 100644 index 00000000..841b4551 --- /dev/null +++ b/Statistics_tools/var_prctile.m @@ -0,0 +1,153 @@ +function [var_prc,pci,Nneeded,pcitarget] = var_prctile(p,n_in_sample,varargin) +% [var_prc,pci,Nneeded,pcitarget] = var_prctile(p,n_in_sample,['nboot',nboot],['x',data]) +% +% [var_prc,pci,Nneeded,pcitarget] = var_prctile(p,50,'nboot',5000) +% +% +% p is desired prctile of data (threshold) +% nboot is number of bootstrap samples (if bootstrapping) +% n_in_sample is number of obs. in original sample +% +% x is data sample of distribution of interest (empirical pdf based on +% this) +% Note: using empirical PDF/CDF depends a great deal on choice of h (see +% code). +% +% tor wager, jan 2007 +% +% Nneeded = []; +% for p = [.05:-.001:.001] +% [var_prc,pci,Nneeded(end+1),pcitarget] = var_prctile(x,p); +% end +% figure; plot([.05:-.001:.001],Nneeded) + +nboot = Inf; +norm_model = 1; +x = []; +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'nboot', nboot = varargin{i+1}; % # bootstrap samples + case {'x','data'}, x = varargin{i+1}; norm_model = 0; % use empirical PDF + + otherwise + error('Unknown string option'); + end + end +end + +% ------------------------------------------------------------ +% * get PDF +% ------------------------------------------------------------ + + +if norm_model + % get normal PDF at p-th percentile + % ------------------------------------------- + prc = norminv(p); + pdfval = normpdf(prc); +else + % empirical estimate of PDF from x (data) + % ------------------------------------------- + % prc is x-score at prctile of interest + prc = prctile(x,100*p); + + % now we need pdf at that prctile. + % pick some unit h, and differentiate around prc + + h = max(.01,1000 ./ length(x)); % enough so we have reasonable idea + h = min(h,p); + h = max(h,1000 ./ length(x)); + + pdfval = 0; + + while pdfval == 0 + pdfval = emppdf(x,prc,h); + + h = h + .01; + end +end + + +% ------------------------------------------------------------ +% * get variance +% ------------------------------------------------------------ + +%for normal: fit = ( 1./ (normpdf(norminv(p)).^2) ) .* (p *(1-p)./N); +% from Brown and Wolfe, asymptotic +%var_prc = ( 1./ (pdfval.^2) ) .* (p *(1-p)./N); + +% from Martin's bootstrap book, Ch 19, Eq. 19.7, p. 275 +var_prc = ( 1./ (pdfval.^2) ) .* (p *(1-p)) .* (1./nboot + 1./n_in_sample); + +if nargout < 2, return, end + + +% ------------------------------------------------------------ +% * get confidence interval +% ------------------------------------------------------------ + +halfci_prc = 1.96 .* sqrt(var_prc); + +% lower and upper bounds on p-value derived from distribution of x +pci = get_pval_ci(x,prc,halfci_prc); + +if nargout < 3, return, end + +% now get nboot needed to achieve target +% --------------------------------------- + +% expression for tolerance: if upper bound of p-value is within 10% of p-value +maxN = 50000; % maximum number of iterations +Nneeded = 500; % starting estimate for nboot needed (should be fairly low) +Nstep = 500; % increase Nneeded in units of Nstep until satisfied +p_upper_bound = p + .1 * p; % upper bound for p desired; now 10% larger than p +pcitarget = Inf; % target conf. interval for p-values with Nneeded boot samples + +% computations we don't have to repeat. Divide by sqrt(N) to get halfci of prc +halfci_squared_determiner = 1.96 .* sqrt( ( 1./ (pdfval.^2) ) .* p *(1-p) ); + +while ( max(pcitarget) > p_upper_bound ) && ( Nneeded < maxN ) + + Nneeded = Nneeded + Nstep; + halfci_prc = halfci_squared_determiner ./ sqrt(Nneeded); + pcitarget = get_pval_ci(x,prc,halfci_prc); + +end + + + + + +end + + + +function pci = get_pval_ci(x,prc,halfci_prc) +% get confidence interval for p-value, based on data x, x-axis-value prc, +% and confidence half-interval for prc + +if isempty(x) + % normal CDF + pci = [max(0,normcdf(prc - halfci_prc)) min(1,normcdf(prc + halfci_prc))]; +else + % empirical CDF + pci = [max(0,empcdf(x,prc - halfci_prc)) min(1,empcdf(x,prc + halfci_prc))]; +end + +end + + + +function pdfval = emppdf(x,prc,h) +% empirical PDF of data numerically differentiated in a window h +pdfval = ( sum(x <= prc + h) - sum(x <= prc - h) ) ./ (length(x) * 2 * h); + +end + + +function p = empcdf(x,prc) +% empirical CDF of data x at prctile prc + +p = sum(x <= prc) ./ length(x); +end \ No newline at end of file diff --git a/Statistics_tools/weighted_glmfit.m b/Statistics_tools/weighted_glmfit.m new file mode 100644 index 00000000..259a2cdd --- /dev/null +++ b/Statistics_tools/weighted_glmfit.m @@ -0,0 +1 @@ +function [betas,stats] = weighted_glmfit(Y,varargin) % Calculate weighted average using weighted linear least squares % See examples below for usage % % Model: % Y_i = 1*Ypop + noise % % INPUT: % % Y - data matrix (nsub x T) % w = weights % varY - variance of data at each time point (nsub x T) + var between % NOTE: TOR CHANGED INPUT TO ASSUME THAT WE SHOULD ENTER VARWI + VARBETWEEN % % OUTPUT: % % Ymean = weighted mean of each column of Y % dfe = error degrees of freedom, adjusted for inequality of variance % (Sattherwaite) and pooled across data columns % % Extended output in stats structure: % stats.t = t-values for weighted t-test % stats.p = 2-tailed p-values for weighted t-test % % r = weighted correlation coeff across columns of Y % xy = weighted covariance matrix % v = weighted variance estimates for each column of Y % * sqrt(v) is the standard error of the mean (or grp difference) % % stats.fits = fits for each group (Ymean by group), low contrast weight group then high % Fastest if no stats are asked for. % % Computation time: % For FULL stats report % * Triples from 500 -> 1000 columns of Y, continues to increase % % For mean/dfe only, fast for full dataset (many columns of Y) % % Examples: % -------------------------------------- % Basic multivariate stats for 1000 columns of dat, no weighting % Multivariate covariances are meaningful if cols of Y are organized, e.g., % timeseries % [means,stats] = weighted_glmfit(dat(:,1:1000)); % % The same, but return univariate stats only (good for large Y) % [means,stats] = weighted_glmfit(dat,'uni'); % % A weighted version, where we put in the weights, and with a design matrix too: % [means,stats] = weighted_glmfit(X,dat,'uni','w',weights); % % A weighted version, where weights are determined from w/i subject variances: % [means,stats] = weighted_glmfit(X,dat,'uni','vary',variances); % -------------------------------------- % * Set up arguments % -------------------------------------- if nargin == 0, error('Must at least enter data as 1st argument.'); end domultivariate = 0; % multivariate covariance est for Y zpdiff = []; w = []; varY = []; X = []; for i = 1:length(varargin) arg = varargin{i}; if ischar(arg) switch lower(arg) case 'w', w = varargin{i+1}; case 'vary', varY = varargin{i+1}; case 'uni', domultivariate = 0; case 'multi', domultivariate = 1; case {'X','x'}, X = varargin{i+1}; end end end % fill in missing inputs with default values if ~is_entered(w), w = ones(m,1); end if ~is_entered(varY), varY = ones(m,1); end if ~is_entered(X), X = ones(m,1); end % Get rid of missing values nancols = find(any(isnan(Y) | Y==0,1)); Y(:,nancols) = []; [m,n] = size(Y); % -------------------------------------- % * Means and contrast % -------------------------------------- % pool weights across all voxels [betas,invxwx,bform,fits] = get_betas_singleweight(X,Y,w); if nargout == 1, return, end % -------------------------------------- % * Residuals % -------------------------------------- e = Y - fits; % residuals % -------------------------------------- % * Degrees of freedom % -------------------------------------- [dfe,dfediff] = get_dfe(m,n,X,bform,varY,0); if ~domultivariate % ====================================== % % % Univariate stats: MSE, t, and p-values % % % ====================================== % -------------------------------------- % * Mean squared error % -------------------------------------- W = diag(w); % Weight matrix % Loop version of MSE: avoids out of memory errors for large voxel sets MSE = zeros(1,n); for i=1:n, MSE(i) = e(:,i)'*W*e(:,i); end, MSE = MSE/dfe; k = size(betas,1); v = repmat(diag(invxwx),1,n) .* repmat(MSE,k,1); %v = invxwx * MSE; % variances for mean % output stats.descrip1 = 'Univariate stats for test against zero:'; stats.v = v; stats.v_descrip = 'V = ste^2; variance of mean estimate'; stats.t = betas ./ sqrt(v); stats.p = 2 * ( 1 - tcdf(abs(stats.t),dfe) ); stats.dfe = dfe; else % ====================================== % % % Multivariate stats: MSE, cov(Y), r(Y) % Useful for simulating t-values under dependence % % ====================================== % -------------------------------------- % * Mean squared error % -------------------------------------- % additional output: covariance matrix for betas and zdiff across time % (columns) % and correlation matrix for betas and zdiff % used in Monte Carlo simulations for controlling false positives % across columns MSE = (e'*W*e)/dfe; % Mean square error if dobtwn MSEdiff = (ediff'*W*ediff)/dfediff; end % -------------------------------------- % * Estimated covariance and correlation % Estimated between-subjects variance (v) % -------------------------------------- xy = invxwx * MSE; % Covariance matrix for betas; xy = 0.5*(xy+xy'); % Remove rounding error if dobtwn xydiff = inv(bcon'*W*bcon)*MSEdiff; % Covariance matrix for betas; xydiff = 0.5*(xydiff+xydiff'); end v = diag(xy); % Variance for betas if dobtwn vdiff = diag(xydiff); end r = xy./sqrt(v*v'); % Correlation matrix for betas if dobtwn rdiff = xydiff./sqrt(vdiff*vdiff'); % Correlation matrix for betas end stats.descrip1 = 'Multivariate stats for test against zero:'; stats.r = r; stats.v = v; stats.xy = xy; stats.t = betas ./ sqrt(v'); stats.p = 2 * ( 1 - tcdf(abs(stats.t),dfe) ); end return function [dfe,dfediff] = get_dfe(m,n,X,bform,varY,dobtwn,hatdiff,bcon) dfediff = []; % Set up residual-forming matrix % -------------------------------------- dfe_v = zeros(n,1); R = eye(m) - X*bform; % residual inducing matrix % contrast, if entered if dobtwn dfe_vdiff = zeros(n,1); Rdiff = eye(m) - bcon * hatdiff; end % Calculate effective degrees of freedom % -------------------------------------- have_unique_vars = size(varY,2) == n; if ~have_unique_vars % Only one (pooled?) vector of variance estimates % -------------------------------------- V = diag(varY(:,1)); dfe = (trace(R*V)^2)/trace(R*V*R*V); % Satherwaite approximation if dobtwn, dfediff = (trace(Rdiff*V)^2)/trace(Rdiff*V*Rdiff*V); end else % Variance estimates for each data vector % -------------------------------------- for i=1:n, % make diagonal matrix of variances V = diag(varY(:,i)); dfe_v(i) = (trace(R*V)^2)/trace(R*V*R*V); % Satherwaite approximation if dobtwn dfe_vdiff(i) = (trace(Rdiff*V)^2)/trace(Rdiff*V*Rdiff*V); end end dfe = mean(dfe_v); % Calculate average df over all columns (pool over data vectors) if dobtwn dfediff = mean(dfe_vdiff); end end return function bool = is_entered(x) bool = exist('x','var') && ~isempty(x); return % % % Duplicated in robust_reg_pooled % % function [betas,invxwx,bform,fits] = get_betas_singleweight(X,Y,w) W = diag(w); % Weight matrix %X = repmat(1,m,1); % Design matrix - 1 column of all ones to calculate average % and, separately, use bcon if that's entered invxwx = inv(X'*W*X); bform = invxwx * X'* W; % beta-forming matrix. hat = X * bform % rows are columns of design (X), cols are Y variables betas = bform*Y; if nargout > 3 fits = X * betas; end return function w = bisquare_weight(r,radjust,xrank) % r is residuals % radjust is adjustment factor: DuMouchel & O'Brien % xrank is rank of weighted X matrix (design) % w is weights from bisquare function % n is number of Y variables to replicate weights over tuneconst = 4.685; r = r .* radjust; s = mad_sigma_pooled(r,xrank); r = r ./ (s*tuneconst); w = (abs(r)<1) .* (1 - r.^2).^2; return function s = mad_sigma_pooled(r,xrank) % Compute std estimate using MAD of residuals from 0 rsort = sort(abs(r)); rsort = rsort(xrank:end,:); % eliminate smallest; like reducing df s = median(rsort(:)) / 0.6745; return function str = display_string(str) str = sprintf(str); fprintf(1,'%s',str); return function erase_string(str) len = length(str); str2 = repmat('\b',1,len); fprintf(1,str2); return \ No newline at end of file diff --git a/Statistics_tools/xcorr_xy_multisubject.m b/Statistics_tools/xcorr_xy_multisubject.m new file mode 100644 index 00000000..17dfa262 --- /dev/null +++ b/Statistics_tools/xcorr_xy_multisubject.m @@ -0,0 +1,94 @@ +function stats = xcorr_xy_multisubject(X, Y) +% +% This function will cross-correlate two variables, X and Y for each of N +% subjects. Correlation and latency values will be saved. +% Second-level tests are done across the N subjects on each of the +% correlation and latency values. +% +% X must be an observations x N matrix +% **NOTE: observations are assumed to be timeseries values!** +% **this DOES matter because an AR(2) model is used...see below*** +% Y must be the same. +% cross-correlations will be computed for pairs of columns, separately for +% each successive column of X / Y +% +% This function ues shift_correl.m, with a two-pass procedure. +% The first pass provides initial estimates, including an estimate of the latency standard +% deviation, which is used as an Empirical Bayes prior. The second pass is used to +% apply the EBayes priors and estimate latencies and cross-correlation values. +% An ar(2) model is used to estimate the DF. +% +% The estimates will be biased towards zero in the second pass, but they +% will likely be lower variance. +% +% stats.latency and stats.association return group-level stats on these +% computed using a sign pemutation test (to avoid normality assumption). +% the "association" test is a test of the relationship, but is different +% than a standard multilevel model because it uses correlation values rather +% than slope values, and the sign permutation test. + +detailplots = 1; + +% ------------------------------------------------------------------------- +% FIRST PASS +% ------------------------------------------------------------------------- + +for i = 1:size(X, 2) + if ~any(isnan(Y(:, i))) + [shiftvals(i, :), corrvals(i, :), bestshift(i, 1), bestcorr(i, 1)] = ... + shift_correl(X(:, i), Y(:, i), 'max_shift', 4, 'shift_step', .2, 'use_rob_stats', 0, ... + 'return_betas', 0, 'display_plots', 0,'optimize'); + else + shiftvals(i, :) = NaN; + corrvals(i, :) = NaN; + [bestshift(i, 1), bestcorr(i, 1)] = deal(NaN); + end +end + + +% ------------------------------------------------------------------------- +% SECOND PASS +% ------------------------------------------------------------------------- + + ebayes_priors = [0 nanstd(bestshift)]; + + for i = 1:size(X, 2) + if ~any(isnan(Y(:, i))) + [shiftvals(i, :), corrvals(i, :), bestshift(i, 1), bestcorr(i, 1), aout, bout] = ... + shift_correl(X(:, i), Y(:, i), 'max_shift', 4, 'shift_step', .2, 'use_rob_stats', 0, ... + 'return_betas', 0, 'display_plots', detailplots, 'optimize', 'priors', ebayes_priors, 'ar2'); + close + else + shiftvals(i, :) = NaN; + corrvals(i, :) = NaN; + [bestshift(i, 1), bestcorr(i, 1)] = deal(NaN); + end + end + + +% ------------------------------------------------------------------------- +% STATS +% ------------------------------------------------------------------------- +X2 = ones(size(bestshift)); +stats.latency = glmfit_general(bestshift, X2, 'signperm', 'nresample', 10000); + +stats.association = glmfit_general(bestcorr, X2, 'signperm', 'nresample', 10000); + +stats.latency.individuals = bestshift; +stats.association.individuals = bestcorr; + +% ------------------------------------------------------------------------- +% PLOT +% ------------------------------------------------------------------------- + + create_figure('Shift', 1, 2); hist(bestshift, 30); plot_vertical_line(mean(bestshift)); + subplot(1, 2, 2); plot_error(mean(shiftvals)', corrvals); + hold on + plot_vertical_line(0); + set(gca, 'FontSize', 24); + xlabel('Time shift'); + ylabel('Cross-correlation'); + axis auto + axis tight + +end diff --git a/Statistics_tools/xval_bestsubsets_brain.m b/Statistics_tools/xval_bestsubsets_brain.m new file mode 100644 index 00000000..846f586a --- /dev/null +++ b/Statistics_tools/xval_bestsubsets_brain.m @@ -0,0 +1,602 @@ +function STATS_FINAL = xval_bestsubsets_brain(my_outcomes, imgs, varargin) + %STATS_FINAL = xval_bestsubsets_brain(my_outcomes, imgs, varargin) + % + % PCA-bestsubsets based prediction on a set of brain images + % Tor Wager, Sept. 2009 + % + % + % Optional inputs: + % case 'reversecolors', reversecolors = 1; + % case 'skipnonparam', dononparam = 0; + % case 'skipsurface', dosurface = 0; + % case {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; + % case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + % case 'niter', niter = varargin{i+1}; + % case 'more_imgs', followed by 2nd (or nth) set of + % images in cell array {} + % Example + % ----------------------------------------------------------------------- + % mkdir bestsubsets_xvalidation_frontal + % cd bestsubsets_xvalidation_frontal/ + % mask = '/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/newmask_mapped.img'; + % load('/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/robust0004/robust0001/SETUP.mat') + % imgs = {SETUP.files}; + % my_outcomes = {SETUP.X(:, 2)}; + % covs = {SETUP.X(:, [3 4 5])}; + % outcome_name = 'Placebo Analgesia (C-P)'; + % covnames = {'Order' 'Study' 'Study x Placebo'}; + % STATS_FINAL = xval_bestsubsets_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors', 'skipsurface', 'skipnonparam'); + % + % or + % + % STATS_FINAL = xval_bestsubsets_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors'); + + % ----------------------------------------------------------------------- + % Optional inputs + % ----------------------------------------------------------------------- + spm_defaults + covs = []; + mask = which('brainmask.nii'); + reversecolors = 0; + dononparam = 1; + dosurface = 1; + niter = 200; + covnames = {'Unknown'}; + outcome_name = 'Unknown'; + verbose = 1; + imgs2 = {}; + dosave = 0; + ndims = 20; + plsstr = 'pca'; + dochoose_regparams_str = 'verbose'; % switch to 'optimize_regularization' to do inner xval + holdout_method = 'loo'; % see xval_regression_multisubject + + inputOptions.all_optional_inputs = varargin; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Process control + case 'reversecolors', reversecolors = 1; + case 'skipnonparam', dononparam = 0; + case 'skipsurface', dosurface = 0; + + % Covariates and Masking + case {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; + case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + case 'niter', niter = varargin{i+1}; + + % Input images + case 'more_imgs', imgs2{end+1} = varargin{i+1}; + fprintf('Found additional image set %3.0f\n', length(imgs2)); + + + % Naming output and saving + case 'outcome_name', outcome_name = varargin{i+1}; varargin{i + 1} = []; + case 'covnames', covnames = varargin{i+1}; varargin{i + 1} = []; + case 'save', dosave = 1; + + % Dimension reduction + case 'ndims', ndims = varargin{i+1}; varargin{i + 1} = []; + if strcmp(ndims, 'all') + ndims = size(imgs{1}, 1) - 2; + fprintf('Choosing %3.0f dims for PCA/PLS\n', ndims); + end + + case 'pca', plsstr = 'pca'; % default... + case 'pls', plsstr = 'pls'; + + % Shrinkage/regularization parameters + + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams_str = 'optimize_regularization'; + + % Holdout set selection + + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + + case {'variable', 'verbose'} % do nothing; needed later + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + inputOptions.all_outcomes = my_outcomes; + inputOptions.outcome_name = outcome_name; + inputOptions.imgs = imgs; + inputOptions.covs = covs; + inputOptions.covnames = covnames; + inputOptions.mask = mask; + inputOptions.ndims = ndims; + inputOptions.plsstr = plsstr; + inputOptions.reversecolors = reversecolors; + inputOptions.dochoose_regparams_str = dochoose_regparams_str; + inputOptions.holdout_method = holdout_method; + + % set up the mask + % ------------------------------------------------------------------------- + if isempty(mask), error('Mask is empty and/or default mask ''brainmask.nii'' cannot be found on path.'); end + if exist(fullfile(pwd, 'mask.img')) && verbose, disp('''mask.img'' already exists in this directory. Replacing.'); else disp('Creating mask.img'); end + scn_map_image(mask, deblank(imgs{1}(1,:)), 'write', 'mask.img'); + maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); + + inputOptions.maskInfo = maskInfo; + + datasets = length(imgs); % each Subject would constitute a "dataset" for a multi-level/within-subjects analysis + + % load image data + % ------------------------------------------------------------------------- + fprintf('Loading all datasets (may be memory-intensive for multi-subject fMRI): '); + for i = 1:datasets + fprintf('%02d', i); + dat{i} = iimg_get_data(maskInfo, imgs{i}); + size_orig{i} = size(dat{i}); + + if ~isempty(imgs2) + for imgset = 1:length(imgs2) + dat{i} = [dat{i} iimg_get_data(maskInfo, imgs2{imgset}{i})]; + end + + end + + end + fprintf('\n'); + + %% + + STATS_FINAL = struct('inputOptions', inputOptions); + + % Run it: covs only + % ------------------------------------------------------------------------- + STATS_FINAL.covs = []; + if ~isempty(covs) + disp('==================================') + disp('Covariates only: OLS') + STATS_FINAL.covs = xval_regression_multisubject('ols', my_outcomes, covs, 'holdout_method', holdout_method); + + if dosave, create_figure('fitplot', 2, 2, 1); scn_export_papersetup(400); saveas(gcf, 'bestsubsets_xval_brain_fits_covs_only.png'); end + end + + % Run it: data only + % ------------------------------------------------------------------------- + if ~isempty(covs) + disp('==================================') + disp('Data only: bestsubsets') + STATS_FINAL.data = xval_regression_multisubject('bestsubsets', my_outcomes, dat, 'pca', 'ndims', ndims, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + else + STATS_FINAL.data = 'see .full_model'; + end + + if dosave, create_figure('fitplot', 2, 2, 1); scn_export_papersetup(400); saveas(gcf, 'bestsubsets_xval_brain_fits_brain_only.png'); end + + % Run it: combined + % ------------------------------------------------------------------------- + + % My best guess about what will work: bestsubsets, with as many dims retained as possible + % * NOTE: all of the main inputs should be cell arrays + STATS_FINAL.full_model = []; + if ~isempty(covs) + disp('==================================') + disp('Data plus covariates full model: bestsubsets') + STATS_FINAL.full_model = xval_regression_multisubject('bestsubsets', my_outcomes, dat, 'pca', 'ndims', ndims, 'covs', covs, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + else + disp('Data only full model: bestsubsets') + disp('==================================') + STATS_FINAL.full_model = xval_regression_multisubject('bestsubsets', my_outcomes, dat, 'pca', 'ndims', ndims, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + end + + if dosave + save STATS_xval_output STATS_FINAL + create_figure('fitplot', 2, 2, 1); scn_export_papersetup(400); saveas(gcf, 'bestsubsets_xval_brain_fits_full_model.png'); + end + + %% Figures: Scatterplot plus bars + % ------------------------------------------------------------------------- + + create_figure('Scatterplot: Full model', 1, 2); + [r,istr,sig,h] = plot_correlation_samefig(STATS_FINAL.full_model.subjfit{1}, STATS_FINAL.inputOptions.all_outcomes{1}); + set(gca,'FontSize', 24) + xlabel('Cross-validated prediction'); + ylabel('Outcome'); + title('bestsubsets cross-validation'); + set(h, 'MarkerSize', 10, 'MarkerFaceColor', [.0 .4 .8], 'LineWidth', 2); + h = findobj(gca, 'Type', 'Text'); + set(h, 'FontSize', 24) + + % bars + subplot(1, 2, 2) + set(gca,'FontSize', 24) + + if ~isempty(covs) + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.covs.pred_err ... + STATS_FINAL.data.pred_err STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Covs' 'Brain' 'Full'}; + else + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Brain'}; + end + + han = bar(pevals); set(han, 'FaceColor', [.5 .5 .5]); + set(gca, 'XTick', 1:length(penames), 'XTickLabel', penames); + axis tight + ylabel('Prediction error'); + + if dosave + scn_export_papersetup(500); saveas(gcf, 'Pred_Outcome_Scatter', 'png'); + end + + % is accuracy predicted by Y and/or covariates? + % **** + + % This function saves maps, plots, etc. and does a bootstrap test to + % threshold the voxel weights. + % It obviates the code below. + if dosurface + STATS_VOXWEIGHTS = xval_regression_multisubject_bootstrapweightmap('bestsubsets', my_outcomes, dat, maskInfo, varargin, 'pca', 'ndims', ndims, 'covs', covs, plsstr); + save STATS_VOXWEIGHTS STATS_VOXWEIGHTS + end + +% % % %% Get mean bestsubsets betas +% % % % ------------------------------------------------------------------------- +% % % +% % % bonf_thresh = norminv(1 - .025 ./ size(dat{1}, 2)); +% % % +% % % mystd = std(STATS_FINAL.full_model.vox_weights'); +% % % +% % % [my_ste,t,n_in_column,p,m] = ste(STATS_FINAL.full_model.vox_weights'); +% % % Z = (m ./ mystd)'; +% % % +% % % iimg_reconstruct_vols(m(1: size_orig{i}(2))', maskInfo, 'outname', 'xval_bestsubsets_wts_mean.img'); +% % % iimg_reconstruct_vols(Z(1: size_orig{i}(2)), maskInfo, 'outname', 'xval_bestsubsets_Z.img'); +% % % +% % % if length(m) > size_orig{i} +% % % iimg_reconstruct_vols(m(size_orig{i}(2)+1:end)', maskInfo, 'outname', 'xval_bestsubsets_wts_mean_imageset2.img'); +% % % iimg_reconstruct_vols(Z(size_orig{i}(2)+1:end), maskInfo, 'outname', 'xval_bestsubsets_Z_imageset2.img'); +% % % end +% % % +% % % sig_vox = abs(Z) > bonf_thresh; +% % % Z_thresh = Z; +% % % Z_thresh(~sig_vox) = 0; +% % % iimg_reconstruct_vols(Z_thresh(1: size_orig{i}(2)), maskInfo, 'outname', 'xval_bestsubsets_Z_bonf_thresh.img'); +% % % +% % % if length(m) > size_orig{i} +% % % iimg_reconstruct_vols(Z_thresh(size_orig{i}(2)+1:end), maskInfo, 'outname', 'xval_bestsubsets_Z_bonf_thresh_imageset2.img'); +% % % end +% % % +% % % %% Orthviews +% % % % ------------------------------------------------------------------------- +% % % +% % % if any(Z_thresh) +% % % cl = mask2clusters('xval_bestsubsets_Z_bonf_thresh.img'); +% % % else +% % % disp('No significant voxel weights at bonferroni corrected threshold.') +% % % cl = []; +% % % end +% % % +% % % poscm2 = colormap_tor([1 .5 0], [1 1 0]); +% % % negcm2 = colormap_tor([.5 0 1], [0 0 1]); +% % % +% % % cluster_orthviews(cl); +% % % +% % % +% % % if reversecolors +% % % cm = spm_orthviews_change_colormap([1 1 0], [0 0 1], [1 0 .4], [.4 .6 1] ); +% % % else +% % % cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [.4 .6 1], [1 0 .4]); +% % % end +% % % +% % % STATS_FINAL.full_model.cl = cl; +% % % +% % % %cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 0 1], [0 0 1], [0 +% % % %.5 1], [0 .5 1], [.5 .5 .5], [.5 .5 .5], [.7 0 0], [1 .5 0], [1 .5 0], [1 1 0]); +% % % +% % % % cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 0 1], [0 0 1], [0 .5 1], [0 .5 1], [.5 .5 .5], [.5 .5 .5], [.7 0 0], [1 .5 0], [1 .5 0], [1 1 0]); +% % % +% % % %% Brain Surface Plot +% % % % ------------------------------------------------------------------------- +% % % +% % % if dosurface && ~isempty(cl) +% % % +% % % create_figure('Brain_Surface', 2, 2); +% % % +% % % if reversecolors +% % % negcm = colormap_tor([1 0 .4], [1 1 0]); +% % % poscm = colormap_tor([.4 .6 1], [0 0 1]); +% % % else +% % % poscm = colormap_tor([1 0 .4], [1 1 0]); +% % % negcm = colormap_tor([.4 .6 1], [0 0 1]); +% % % end +% % % +% % % han1 = addbrain('hires'); +% % % cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han1); +% % % +% % % %replace with 'hires' and re-run to do whole cortical surface +% % % create_figure('Brain_Surface', 2, 2, 1); +% % % subplot(2, 2, 2); +% % % han = addbrain('hires right'); set(han, 'FaceAlpha', 1); +% % % +% % % drawnow +% % % cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); +% % % set(han, 'FaceAlpha', 1); +% % % axis image; lightRestoreSingle; +% % % lighting gouraud +% % % +% % % create_figure('Brain_Surface', 2, 2, 1); +% % % subplot(2, 2, 3); +% % % han = addbrain('hires left'); set(han, 'FaceAlpha', 1); +% % % axis image; lightRestoreSingle; +% % % lighting gouraud +% % % drawnow +% % % cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); +% % % +% % % create_figure('Brain_Surface', 2, 2, 1); +% % % subplot(2, 2, 4); +% % % han = addbrain('limbic'); set(han, 'FaceAlpha', 1); +% % % axis image; lightRestoreSingle; +% % % lighting gouraud +% % % drawnow +% % % cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); +% % % +% % % set(han(end), 'FaceAlpha', .2) +% % % han2 = addbrain('brainstem'); +% % % cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); +% % % view(135, 15) +% % % +% % % create_figure('Brain_Surface', 2, 2, 1); +% % % subplot(2, 2, 1) +% % % han = findobj(gca,'Type','patch'); set(han, 'FaceAlpha',1) +% % % axis image +% % % +% % % if dosave +% % % scn_export_papersetup(600); saveas(gcf, 'Surface1', 'png'); +% % % subplot(2, 2, 1); +% % % view(135, 20); lightRestoreSingle; +% % % saveas(gcf, 'Surface2', 'png'); +% % % view(225, 20); lightRestoreSingle; +% % % saveas(gcf, 'Surface3', 'png'); +% % % view(90, 3); lightRestoreSingle; +% % % saveas(gcf, 'Surface4', 'png'); +% % % view(270, 3); lightRestoreSingle; +% % % saveas(gcf, 'Surface5', 'png'); +% % % end + +% % % end + + %% NONPARAMETRIC TEST + + if dononparam + + disp('PERMUTATION TEST FOR NULL HYPOTHESIS'); + + + % Permute : covariates only + % ============================================== + if ~isempty(covs) + clear my_outcomesp all_r pe + ndims = min(size(covs{1}, 2) - 1, size(covs{1}, 1) - 2); % will not work for multilevel with diff. dimensions per dataset + + fprintf('Covariates only: Running %3.0f iterations\n', niter); + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + if size(covs, 2) > 1 + STATSpcovs = xval_regression_multisubject('bestsubsets', my_outcomesp, covs, 'noverbose', 'holdout_method', holdout_method); %,'pca', 'ndims', ndims); + else + STATSpcovs = xval_regression_multisubject('ols', my_outcomesp, covs, 'noverbose', 'holdout_method', holdout_method); %,'pca', 'ndims', ndims); + end + + fprintf('%3.2f ', STATSpcovs.r_each_subject); + + all_r(i) = STATSpcovs.r_each_subject; + pe(i) = STATSpcovs.pred_err; + + end + + STATS_FINAL.covs.permuted.pe_values = pe; + STATS_FINAL.covs.permuted.pe_mean = mean(pe); + STATS_FINAL.covs.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.covs.permuted.r_values = all_r; + STATS_FINAL.covs.permuted.r_mean = mean(all_r); + STATS_FINAL.covs.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + + save STATS_xval_output_permsbackup STATS_FINAL + end + + fprintf('\n') + + end + + % Permute : random subset of 1000 voxels + % ============================================== + n_vox = 1000; + if size(dat{1}, 2) > n_vox + clear my_outcomesp my_datp all_r pe + ndims = size(dat{1}, 1) - 2; + + fprintf('1000 vox subsets: %3.0f iterations: %3.0f', niter, 0); + + for i = 1:niter + + fprintf('\b\b\b%3.0f', i); + + rand('twister',sum(100*clock)) ; % was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + ix2 = randperm(size(dat{s}, 2)); + + my_outcomesp{s} = my_outcomes{s}(ix); + my_datp{s} = dat{s}(:, ix2(1:n_vox)); + + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSpv= xval_regression_multisubject('bestsubsets', my_outcomesp, my_datp, 'pca', plsstr, 'ndims', ndims, dochoose_regparams_str, 'holdout_method', holdout_method, 'noverbose'); + + all_r(i) = STATSpv.r_each_subject; + pe(i) = STATSpv.pred_err; + + end + + fprintf('%3.2f ', all_r); + fprintf('\n') + + STATS_FINAL.full_model.permuted.v1000_pe_values = pe; + STATS_FINAL.full_model.permuted.v1000_pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.v1000_pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.full_model.permuted.v1000_r_values = all_r; + STATS_FINAL.full_model.permuted.v1000_r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.v1000_r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + end + + % Permute : Full data + % ============================================== + clear my_outcomesp all_r pe + ndims = size(dat{1}, 1) - 2; + + fprintf('1000 vox subsets: %3.0f iterations\n', niter); + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSp = xval_regression_multisubject('bestsubsets', my_outcomesp, dat, 'pca', plsstr, 'ndims', ndims, dochoose_regparams_str, 'holdout_method', holdout_method); + + fprintf('Iteration %3.0f : r = %3.2f\n', i, STATSp.r_each_subject); + + all_r(i) = STATSp.r_each_subject; + pe(i) = STATSp.pred_err; + + end + + STATS_FINAL.full_model.permuted.pe_values = pe; + STATS_FINAL.full_model.permuted.pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + + STATS_FINAL.full_model.permuted.r_values = all_r; + STATS_FINAL.full_model.permuted.r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + + end % if do nonparam + +end % main function + + + +% - Extra stuff - + +% % %% Bootstrap the covariates +% % % +% % % This is the "Leave one out bootstrap" of Efron and Tibshirani, JASA, 1997 +% % % Err(1) +% % % They show that this can be done by taking the error on each point from +% % % bootstrap samples that do not happen to contain that point +% % % +% % % they say that for continuous outcomes and predictors (our case), they +% % % expect the cross-val estimate and the 632+ bootstrap estimate to be more +% % % similar. the benefit is mainly for discontinuous outcomes (1/0) +% % % +% % % they say that Err(1) is biased upwards compared to the nearly unbiased +% % % CV(1) (leave-one-out cross-validation) +% % % it estimates "half-sample" xval +% % +% % bootfun = @(Y, X) xval_regression_boostrap_wrapper(Y, X); +% % STATS_FINAL.bootstrap.covs_only_r_rsq = bootstrp(200, bootfun, my_outcomes_orig{1}, covs{1}); +% % STATS_FINAL.bootstrap.covs_only_summary = [mean(STATS_FINAL.bootstrap.covs_only_r_rsq) std(STATS_FINAL.bootstrap.covs_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.covs_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = bootstrp(20, bootfun, my_outcomes_orig{1}, dat{1}); +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = [STATS_FINAL.bootstrap.dat_only_r_rsq; bootstrp(80, bootfun, my_outcomes_orig{1}, dat{1})]; +% % +% % STATS_FINAL.bootstrap.dat_only_summary = [mean(STATS_FINAL.bootstrap.dat_only_r_rsq) std(STATS_FINAL.bootstrap.dat_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.dat_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % %% Nonparametric test with N variables (voxels) selected at random +% % +% % s = 1; +% % +% % +% % n_vox = [1000:2000:50000]; +% % niter = [1 50]; +% % +% % if niter(1) == 1, all_r_perm = zeros(niter(2), length(n_vox + 1)); end +% % +% % for i = 1:length(n_vox) +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject('bestsubsets', {my_outcomes{1}(ix)}, {dat{1}(:, ix2(1:n_vox(i)))}, 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow +% % end +% % +% % % with full sample +% % n_vox(end+1) = size(dat{s}, 2); +% % i = length(n_vox); +% % +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % %ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject('bestsubsets', {my_outcomes{1}(ix)}, dat(1), 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow diff --git a/Statistics_tools/xval_cross_classfy.m b/Statistics_tools/xval_cross_classfy.m new file mode 100644 index 00000000..3cb6c69a --- /dev/null +++ b/Statistics_tools/xval_cross_classfy.m @@ -0,0 +1,561 @@ +function [test_results, stats1, stats2] = xval_cross_classfy(algorithm_name, data1, data2, cv_assign, varargin) + +% Run SVM or LASSOPCR on two image_vector or fmri_data objects for cross-classification. +% +% Usage: +% ------------------------------------------------------------------------- +% [test_results, stats1, stats2] = xval_cross_classfy(algorithm_name, dat1, dat2, cv_assign, [optional inputs]) +% +% Features: +% - ... +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% algorithm_name: 'cv_svm' (linear svm) or 'cv_lassopcr' +% ------------------------------------------------------------------------- +% Data: +% +% dat1 image_vector or fmri_data object with data +% dat1.Y(:,1) for cv_svm: true(1) or false(-1) for each observation (image) in Y(:,1) +% for cv_lassopcr: continuous value for Y(:,1) +% +% dat2 image_vector or fmri_data object with data +% dat2.Y for cv_svm: true(1) or false(-1) for each observation (image) in Y(:,1) +% for cv_lassopcr: continuous value for Y(:,1), +% +% dat1.Y(:,[2:n]), dat2.Y(:,[2:n]) +% Test sets: could be binary: and true(1), false(-1), +% ignore(0) or continuous values +% +% cv_assign vector of integers for membership in custom holdout set of each fold +% If two datasets have different order of cross-validation +% folds, you can put two different cv_assign vector in cell +% arrays. e.g.) cv_assign{1} = whfolds1, cv_assign{2} = whfolds2 +% +% Optional inputs: +% ------------------------------------------------------------------------- +% 'scale' z-scored input data in image_vector or fmri_data object +% 'balanced' use the balanced ridge option - balanced ridge value should +% be followed. +% 'outcome_method' followed by the following options +% 'correlation' - "default" for for continuous measures +% 'twochoice'- "default" for binary outcome +% 'singleinterval' - for binary outcome +% +% +% Outputs: +% ------------------------------------------------------------------------- +% test_results: for binary classification - four values of accuracy, p, se +% for continuous prediction - four values of r(pearson's +% correlation), p-value. +% The order of the test results are [dat1-on-dat1, dat1-on-dat2, +% dat2-on-dat1, dat2-on-dat2]. All results are cross-validated +% results. +% +% stats1, stats2 stats1 and stats2 are similar to the outputs of predict +% function. help fmri_data.predict +% +% Examples: +% % Data preparation +% % ------------------------------------- +% dat1 = fmri_data(which('scalped_avg152T1_graymatter.img')); +% dat1 = threshold(dat, [.8 Inf], 'raw-between'); +% dat1 = trim_mask(dat); +% dat2 = dat1; +% +% % Create fake data and holdout indicator index vector +% % ------------------------------------- +% dat1.dat = randn(dat.volInfo.n_inmask, 30); +% dat2.dat = randn(dat.volInfo.n_inmask, 30); +% +% % for svm +% % dat1.Y = ((dat.dat(111111, :)' + .3 * randn(30, 1))>0).*2-1; +% % dat2.Y = ((dat.dat(111, :)' + .3 * randn(30, 1))>0).*2-1; +% % cv_assign = ones(6, 1); for i = 2:5, cv_assign = [cv_assign; i*ones(6, 1)]; end +% +% % for cv_lassopcr +% dat1.Y = dat.dat(111111, :)' + .3 * randn(30, 1); +% dat2.Y = dat.dat(111, :)' + .3 * randn(30, 1); +% dat1.Y(:,2) = [ones(10,1); zeros(10,1); -ones(10,1)]; +% dat2.Y(:,2) = [-ones(10,1); zeros(10,1); ones(10,1)]; +% cv_assign = ones(6, 1); for i = 2:5, cv_assign = [cv_assign; i*ones(6, 1)]; end +% +% % Run, and run again with existing indx +% % ------------------------------------- +% % [test_results, stats1, stats2] = xval_cross_classfy('cv_svm', dat1, dat2, cv_assign, 'outcome_method', 'singleinterval') +% [test_results, stats1, stats2] = xval_cross_classfy('cv_lassopcr', dat1, dat2, cv_assign, 'outcome_method', 'singleinterval') +% +% See also: +% fmri_data.predict.m + +% Programmers' notes: +% + + +% ----- defaults and input ----- + +doscale = false; +dobalanced = false; balanced_ridge = []; + +% ----- organize test variables ----- +[out_method, test_Y1, test_Y2, data1, data2] = setup_testvar(data1, data2, varargin); + +% ----- optional inputs ----- +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'scale'} + doscale = true; + case {'balanced'} + dobalanced = true; + balanced_ridge = varargin{i+1}; + end + end +end + +% ----- z-score data if doscale ----- +if doscale + data1 = rescale(data1, 'zscoreimages'); + data2 = rescale(data2, 'zscoreimages'); +end + +% ----- run algorithms ----- + +switch algorithm_name + case 'cv_svm' + [test_results, stats1, stats2] = cv_svm_cross_classfy(data1, data2, cv_assign, test_Y1, test_Y2, out_method, dobalanced, balanced_ridge); + case 'cv_lassopcr' + [test_results, stats1, stats2] = cv_lassopcr_cross_classfy(data1, data2, cv_assign, test_Y1, test_Y2, out_method); +end + +end + + +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- +% +% Sub-functions +% +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- + +% ------------------------------------------------------------------------- +% CV-ASSIGNMENT + +function [teIdx, trIdx] = do_cv_assign(cv_assign) + +% assign cross-validation +if ~iscell(cv_assign) + for j = 1:size(cv_assign,2) + temp_cv{j} = cv_assign(:,j); + end + cv_assign = temp_cv; +end + +if numel(cv_assign) == 1, cv_assign{2} = cv_assign{1}; end + +for j = 1:numel(cv_assign) + u = unique(cv_assign{j}); + nfolds = length(u); + [trIdx{j}, teIdx{j}] = deal(cell(1, nfolds)); + + for i = 1:length(u) + teIdx{j}{i} = cv_assign{j} == u(i); + trIdx{j}{i} = ~teIdx{j}{i}; + end +end + +end + +% ------------------------------------------------------------------------- +% CHECK SVM ERRORS, IF ANY + +function test_error(testobj1, testobj2, testobj3, testobj4, w, b0) + +% briefly check everything is right +test_dist1 = testobj1.X * w + b0; +test_dist2 = testobj2.X * w + b0; + +if ~isequal((test_dist1>0)*2-1,testobj3.X) + error('Something is wrong.'); +elseif ~isequal((test_dist2>0)*2-1,testobj4.X) + error('Something is wrong.'); +end + +end + +% ------------------------------------------------------------------------- +% set up test variables + +function [out_method, test_Y1, test_Y2, data1, data2] = setup_testvar(data1, data2, varargin) + +% default for binary +out_method_input = 'twochoice'; + +% get outcome method input +for i = 1:length(varargin{1}) + if ischar(varargin{1}{i}) + switch varargin{1}{i} + case {'outcome_method'} + out_method_input = varargin{1}{i+1}; + end + end +end + +% first, check the data +if size(data1.Y,2) ~= size(data2.Y,2) + error('The column size of Y should be same between dat1 and dat2.') + +% if data.Y is the test variable +elseif size(data1.Y,2) == 1 + + if numel(unique(data1.Y(data1.Y~=0)))==2 && numel(unique(data2.Y(data2.Y~=0)))==2 + out_method{1} = out_method_input; + test_Y1{1} = data1.Y; test_Y2{1} = data2.Y; + + elseif numel(unique(data1.Y(data1.Y~=0))) > 2 && numel(unique(data2.Y(data2.Y~=0))) > 2 + out_method{1} = 'correlation'; + test_Y1{1} = data1.Y; test_Y2{1} = data2.Y; + + else + error('dat1.Y and dat2.Y are not consistent.'); + end + +% if data.Y has more than one column +elseif size(data1.Y,2) > 1 + + for i = 2:size(data1.Y,2) + + if numel(unique(data1.Y(data1.Y(:,i)~=0,i)))==2 && numel(unique(data2.Y(data2.Y(:,i)~=0,i)))==2 + out_method{i-1} = out_method_input; + test_Y1{i-1} = data1.Y(:,i); test_Y2{i-1} = data2.Y(:,i); + elseif numel(unique(data1.Y(data1.Y(:,i)~=0,i))) > 2 && numel(unique(data2.Y(data2.Y(:,i)~=0,i))) > 2 + out_method{i-1} = 'correlation'; + test_Y1{i-1} = data1.Y(:,i); test_Y2{i-1} = data2.Y(:,i); + else + error('dat1.Y and dat2.Y are not consistent.'); + end + + end +end + +% delete test variables from data +data1.Y = data1.Y(:,1); +data2.Y = data2.Y(:,1); + +end + +% ------------------------------------------------------------------------- +% get test results + +function test_results = get_test_results(stats1, stats2, test_Y1, test_Y2, out_method) + +out_data = {'stats1.all{3}', 'stats1.all{4}', 'stats2.all{4}', 'stats2.all{3}'}; +out_test = {'test_Y1', 'test_Y2', 'test_Y1', 'test_Y2'}; +out_whempty = {'whempty1', 'whempty2', 'whempty1', 'whempty2'}; + +for i = 1:numel(out_method) + + whempty1 = test_Y1{i} == 0; + whempty2 = test_Y2{i} == 0; + + switch out_method{i} + case 'correlation' + + for j = 1:4 + eval(['[test_results{i}.r(' num2str(j) '), test_results{i}.p(' num2str(j) ')] = corr(' out_data{j} '(~' out_whempty{j} '), ' out_test{j} '{i}(~' out_whempty{j} '));']); + end + + case 'singleinterval' + + roc_temp1 = roc_plot(stats1.Y, test_Y1{i} == 1, 'include', ~whempty1, 'balanced'); + thr{1} = roc_temp1.class_threshold; thr{2} = thr{1}; + roc_temp2 = roc_plot(stats2.Y, test_Y2{i} == 1, 'include', ~whempty2, 'balanced'); + thr{3} = roc_temp2.class_threshold; thr{4} = thr{3}; + + for j = 1:4 + try + eval(['ROC = roc_plot(' out_data{j} ', ' out_test{j} '{i}==1, ''include'', ~' out_whempty{j} ', ''balanced'', ''threshold'', thr{' num2str(j) '});']); + eval(['test_results{i}.acc(' num2str(j) ') = ROC.accuracy;']); + eval(['test_results{i}.se(' num2str(j) ') = ROC.accuracy_se;']); + eval(['test_results{i}.p(' num2str(j) ') = ROC.accuracy_p;']); + eval(['test_results{i}.thr(' num2str(j) ') = ROC.class_threshold;']); + + catch dummy + eval(['test_results{i}.acc(' num2str(j) ') = NaN;']); + eval(['test_results{i}.se(' num2str(j) ') = NaN;']); + eval(['test_results{i}.p(' num2str(j) ') = NaN;']); + eval(['test_results{i}.thr(' num2str(j) ') = NaN;']); + end + + end + + test_results{i}.p(test_results{i}.p > 1) = 1; + + case 'twochoice' + + for j = 1:4 + try + eval(['ROC = roc_plot(' out_data{j} ', ' out_test{j} '{i}==1, ''twochoice'', ''include'', ~' out_whempty{j} ');']); + eval(['test_results{i}.acc(' num2str(j) ') = ROC.accuracy;']); + eval(['test_results{i}.se(' num2str(j) ') = ROC.accuracy_se;']); + eval(['test_results{i}.p(' num2str(j) ') = ROC.accuracy_p;']); + catch dummy + eval(['test_results{i}.acc(' num2str(j) ') = NaN;']); + eval(['test_results{i}.se(' num2str(j) ') = NaN;']); + eval(['test_results{i}.p(' num2str(j) ') = NaN;']); + end + end + + test_results{i}.p(test_results{i}.p > 1) = 1; + end +end + +close all; + +end + +% ------------------------------------------------------------------------- +% CROSS-CLASSIFY USING CV-SVM + +function [test_results, stats1, stats2] = cv_svm_cross_classfy(data1, data2, cv_assign, test_Y1, test_Y2, out_method, dobalanced, balanced_ridge) + +% ----- activate the spider toolbox. ----- +% %% this is slow. Please activate spider toolbox before running this function +% if isempty(which('use_spider.m')) +% error('Error: You need to have the Spider toolbox in your path. Check your path.'); +% else +% use_spider; +% end + +% ----- preallocate variables ----- +for j = 1:2 + eval(['predicted1' num2str(j) ' = NaN(size(data' num2str(j) '.Y));']); + eval(['predicted2' num2str(j) ' = NaN(size(data' num2str(j) '.Y));']); + eval(['dist_from_hyper1' num2str(j) ' = NaN(size(data' num2str(j) '.Y));']); + eval(['dist_from_hyper2' num2str(j) ' = NaN(size(data' num2str(j) '.Y));']); +end + +% ----- get cv assignment ----- +[teIdx, trIdx] = do_cv_assign(cv_assign); + +% ----- setup the variables for SVM ----- +for j = 1:2, eval(['trainobj' num2str(j) ' = data(double(data' num2str(j) '.dat''),data' num2str(j) '.Y);']); end + +% ----- run it with all data (not cv)----- +for j = 1:2 + eval(['svmobj' num2str(j) ' = svm({''optimizer="andre"'',''C=1'',''child=kernel''});']); + eval(['if dobalanced, svmobj' num2str(j) '.balanced_ridge = balanced_ridge; end']); + eval(['[~, svmobj' num2str(j) '] = train(svmobj' num2str(j) ', trainobj' num2str(j) ');']); +end + +% ----- outputs I: using all data 1 & 2 ----- +for j = 1:2 + eval(['stats' num2str(j) '.Y = data' num2str(j) '.Y;']); + % output: weights + eval(['stats' num2str(j) '.all{1} = get_w(svmobj' num2str(j) ')'';']); + % output: intercepts + eval(['stats' num2str(j) '.all{2} = svmobj' num2str(j) '.b0;']); + % description of out outputs: 3 and 4 have not been collected yet, though. + eval(['stats' num2str(j) '.all_descrip = {''1:weights 2:intercept 3:distance from hyperplane for testing data' num2str(j) '(xval) 4: dist for the other data(xval)''};']); +end + +% ----- Cross-validation loop starts here ----- + +if numel(teIdx)>1, if ~isequal(numel(teIdx{1}), numel(teIdx{2})), error('The number of folds should be same between dat1 and dat2.'); end, end + +for i = 1:numel(teIdx{1}) + + % Prepare the data in Spider format + for j = 1:2 + % prepare train data 1 & 2 + eval(['trainobj' num2str(j) ' = data(double(data' num2str(j) '.dat''),data' num2str(j) '.Y);']); + eval(['testobj' num2str(j) ' = trainobj' num2str(j) ';']); + + eval(['trainobj' num2str(j) '.X = trainobj' num2str(j) '.X(trIdx{j}{i}, :);']); + eval(['trainobj' num2str(j) '.Y = trainobj' num2str(j) '.Y(trIdx{j}{i}, :);']); + % prepare test data 1 & 2 + eval(['testobj' num2str(j) '.X = testobj' num2str(j) '.X(teIdx{j}{i}, :);']); + % eval(['testobj' num2str(j) '.Y = testobj' num2str(j) '.Y(teIdx{i}, :);']); + end + + + % ----- train on data1 & 2 and test on data1 and data2 ----- + + for j = 1:2 + % -- set-up an SVM object + eval(['svmobj' num2str(j) ' = svm({''optimizer="andre"'',''C=1'',''child=kernel''});']); + eval(['if dobalanced, svmobj' num2str(j) '.balanced_ridge = balanced_ridge; end']); + + % -- train on data1 + eval(['[~, svmobj' num2str(j) '] = train(svmobj' num2str(j) ', trainobj' num2str(j) ');']) + + % -- test on data1 and data2 + eval(['testobj' num2str(j) '1 = test(svmobj' num2str(j) ', testobj1);']); + eval(['testobj' num2str(j) '2 = test(svmobj' num2str(j) ', testobj2);']); + + eval(['w = get_w(svmobj' num2str(j) ')'';']); + eval(['b0 = svmobj' num2str(j) '.b0;']); + + eval(['test_error(testobj1, testobj2, testobj' num2str(j) '1, testobj' num2str(j) '2, w, b0);']); + + % -- save predictions (cross-val) + eval(['predicted' num2str(j) '1(teIdx{1}{i}) = testobj' num2str(j) '1.X;']); + eval(['predicted' num2str(j) '2(teIdx{2}{i}) = testobj' num2str(j) '2.X;']); + end + + % ----- outputs II: cross-val data ----- + + for j = 1:2 + % output: cv-weights + eval(['stats' num2str(j) '.cvoutput{i,1} = get_w(svmobj' num2str(j) ')'';']); + % output: cv-intercepts + eval(['stats' num2str(j) '.cvoutput{i,2} = svmobj' num2str(j) '.b0;']); + % output: cv-distance-from-hyperplane for test data1 and data2 + if j == 1 + stats1.cvoutput{i,3} = testobj1.X * stats1.cvoutput{i,1} + stats1.cvoutput{i,2}; + stats1.cvoutput{i,4} = testobj2.X * stats1.cvoutput{i,1} + stats1.cvoutput{i,2}; + stats1.cvoutput_descrip = {'1:weights 2:intercept 3:distance from hyperplane for testing data2(xval) 4: dist for data1(xval)'}; + dist_from_hyper11(teIdx{j}{i}) = stats1.cvoutput{i,3}; + dist_from_hyper12(teIdx{j}{i}) = stats1.cvoutput{i,4}; + else + stats2.cvoutput{i,3} = testobj2.X * stats2.cvoutput{i,1} + stats2.cvoutput{i,2}; + stats2.cvoutput{i,4} = testobj1.X * stats2.cvoutput{i,1} + stats2.cvoutput{i,2}; + stats2.cvoutput_descrip = {'1:weights 2:intercept 3:distance from hyperplane for testing data2(xval) 4: dist for data1(xval)'}; + dist_from_hyper21(teIdx{j}{i}) = stats2.cvoutput{i,4}; + dist_from_hyper22(teIdx{j}{i}) = stats2.cvoutput{i,3}; + end + end + +end + +% ----- outputs I again: cross-val data ----- + +stats1.yfit = predicted11; +stats2.yfit = predicted22; +stats1.crosstestfit = predicted12; +stats2.crosstestfit = predicted21; + +stats1.all{3} = dist_from_hyper11; +stats1.all{4} = dist_from_hyper12; +stats2.all{3} = dist_from_hyper22; +stats2.all{4} = dist_from_hyper21; + +test_results = get_test_results(stats1, stats2, test_Y1, test_Y2, out_method); + +end + +% ------------------------------------------------------------------------- +% CROSS-CLASSIFY USING CV-LASSOPCR + +function [test_results, stats1, stats2] = cv_lassopcr_cross_classfy(data1, data2, cv_assign, test_Y1, test_Y2, out_method) + +% ----- check lassopcr is in the path ----- +if isempty(which('lasso_rocha.m')) + error('Error: You need to have the lasso (rocha) toolbox in your path. Check your path.'); +end + +% ----- preallocate variables ----- +for j = 1:2 + eval(['predicted1' num2str(j) ' = NaN(size(data' num2str(j) '.Y));']); + eval(['predicted2' num2str(j) ' = NaN(size(data' num2str(j) '.Y));']); +end + +% ----- get cv assignment ----- +[teIdx, trIdx] = do_cv_assign(cv_assign); + +% ----- run it with all data (not cv)----- +for j = 1:2, eval(['[dummy, res' num2str(j) '] = predict(data' num2str(j) ', ''algorithm_name'', ''cv_lassopcr'', ''nfolds'', 1);']); end + +% ----- outputs I: using all data ----- +for j = 1:2 + eval(['stats' num2str(j) '.Y = res' num2str(j) '.Y;']); + % output: weights and intercepts + eval(['for i = 1:2, stats' num2str(j) '.all{i} = res' num2str(j) '.other_output{i}; end']); + % description of out outputs: 3 and 4 have not been collected yet, though. + eval(['stats' num2str(j) '.all_descrip = {''1:weights 2:intercept 3:pexp from testing data' num2str(j) '(xval) 4: pexp for the other data(xval)''};']); +end + +% ----- Cross-validation loop starts here ----- + +if numel(teIdx)>1, if ~isequal(numel(teIdx{1}), numel(teIdx{2})), error('The number of folds should be same between dat1 and dat2.'); end, end + +for i = 1:numel(teIdx{1}) + + for j = 1:2 + % prepare training data 1 & 2 + eval(['trainobj' num2str(j) ' = data' num2str(j) ';']); + eval(['trainobj' num2str(j) '.dat = data' num2str(j) '.dat(:,trIdx{j}{i});']); + eval(['trainobj' num2str(j) '.Y = data' num2str(j) '.Y(trIdx{j}{i},1);']); + % prepare test data 1 & 2 + eval(['testobj' num2str(j) ' = data' num2str(j) ';']); + eval(['testobj' num2str(j) '.dat = data' num2str(j) '.dat(:, teIdx{j}{i});']); + end + + % --- train on data1 and test on data1 and data2 --- + + for j = 1:2 + % -- train on data1 + eval(['[dummy, res' num2str(j) '] = predict(trainobj' num2str(j) ', ''algorithm_name'', ''cv_lassopcr'', ''nfolds'', 1);']); + + % -- save predictions (cross-val) + eval(['pexp' num2str(j) '1 = testobj1.dat'' * res' num2str(j) '.other_output{1} + res' num2str(j) '.other_output{2};']); + eval(['pexp' num2str(j) '2 = testobj2.dat'' * res' num2str(j) '.other_output{1} + res' num2str(j) '.other_output{2};']); + eval(['predicted' num2str(j) '1(teIdx{1}{i}) = pexp' num2str(j) '1;']); + eval(['predicted' num2str(j) '2(teIdx{2}{i}) = pexp' num2str(j) '2;']); + end + + + % ----- outputs II: cross-val data ----- + + for j = 1:2 + % output: cv-weights + eval(['stats' num2str(j) '.cvoutput{i,1} = res' num2str(j) '.other_output{1};']); + % output: cv-intercepts + eval(['stats' num2str(j) '.cvoutput{i,2} = res' num2str(j) '.other_output{2};']); + % output: cv-pexp for test data1 and data2 + if j == 1 + stats1.cvoutput{i,3} = pexp11; + stats1.cvoutput{i,4} = pexp12; + stats1.cvoutput_descrip = {'1:weights 2:intercept 3:pexp for testing data1(xval) 4: dist for data2(xval)'}; + else + stats2.cvoutput{i,3} = pexp22; + stats2.cvoutput{i,4} = pexp21; + stats2.cvoutput_descrip = {'1:weights 2:intercept 3:pexp for testing data2(xval) 4: dist for data1(xval)'}; + end + end +end + +stats1.yfit = predicted11; +stats2.yfit = predicted22; +stats1.crosstestfit = predicted12; +stats2.crosstestfit = predicted21; +stats1.test_Y = test_Y2; +stats2.test_Y = test_Y1; + +stats1.all{3} = predicted11; +stats1.all{4} = predicted12; +stats2.all{3} = predicted22; +stats2.all{4} = predicted21; + +test_results = get_test_results(stats1, stats2, test_Y1, test_Y2, out_method); + +end \ No newline at end of file diff --git a/Statistics_tools/xval_featureselect_nmdscluster.m b/Statistics_tools/xval_featureselect_nmdscluster.m new file mode 100644 index 00000000..4194583e --- /dev/null +++ b/Statistics_tools/xval_featureselect_nmdscluster.m @@ -0,0 +1,171 @@ +function pred_value = xval_featureselect_nmdscluster(X, Y, pthreshold, holdout_method, xyzlist) +% +% pred_value = xval_featureselect_nmdscluster(X, Y, p-value selection for univariate feature selection, holdout_method) +% +% pred_value: cross-validated predictions of outcome data +% X: n x variables matrix of predictors +% Y: n x 1 vector of outcomes +% +% Tor Wager, June 2010 +% +% Go to any LASSO output directory and run this: +% +% maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); +% dat{1} = iimg_get_data(maskInfo, anticimages); +% pred_value = xval_simple_ols_loo(X, Y) + + +[N, k] = size(X); +pred_value = zeros(N, 1); + +holdout_set = nested_select_holdout_set; + +create_figure('test', 1, 2); + +fprintf('Fold: %03d', 0); +for wh_fold = 1:length(holdout_set) + + fprintf('\b\b\b%3.0f ', wh_fold); + + % select training data + Xi = X; + Yi = Y; + + Yi(holdout_set{wh_fold}) = []; + Xi(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) + + Xtest = X(holdout_set{wh_fold}, :); + Ytest = Y(holdout_set{wh_fold}, :); % only used later when we test prediction + + ntrain = size(Xi, 1); + nholdout = size(Xtest, 1); + + % select features based on univariate correlations + [r, p, Tstat] = correlation_fast_series(Xi, Yi); + wh_features = p <= pthreshold; + nfeatures(wh_fold) = sum(wh_features); + if nfeatures(wh_fold) == 0 + disp('Warning: no features pass threshold'); + wh_features = p <= prctile(p, 10); + end + + Xi = Xi(:, wh_features); + + c = []; c.outcome = Yi; c.covs_nointerest = []; + % get average data from contiguous blobs (??) + xyz = xyzlist(wh_features, :); + contig = spm_clusters(xyzlist(wh_features, :)'); + for ii = 1:max(contig) + if sum(contig == ii) < 3, contig(contig == ii) = 0; end + end + u = unique(contig); u(u == 0) = []; + for ii = 1:length(u) + c.dat(:, ii) = nanmean(Xi(:, contig == u(ii))')'; + end + c = nmdsfig_tools('get_correlations',c); + c.GroupSpace = mdscale(c.D, 8, 'Replicates', 10,'Options',statset('MaxIter',500),'Criterion','sstress'); + c.ClusterSolution.classes = clusterdata(c.GroupSpace, 'maxclust', 5, 'linkage', 'average'); + + c.APPLY_CLUSTER = apply_cluster_solution(c.ClusterSolution.classes,... + c.r,... + 'names',[],'bcov',c.outcome, 'n', size(c.dat, 1), 'dointeractive', 0); + class_avg_dat = []; + for i = 1:max(c.ClusterSolution.classes) + wh = find(c.ClusterSolution.classes == i); + if length(wh) > 1 + class_avg_dat = [class_avg_dat nanmean(c.dat(:,wh)')']; + else + class_avg_dat = [class_avg_dat c.dat(:,wh)]; + end + end + c.class_avg_dat = class_avg_dat; + + c.class_STEPWISE = stepwise_tor(c.class_avg_dat,c.outcome,c.APPLY_CLUSTER.class_names); + + Xi = [ones(ntrain, 1) c.class_avg_dat(:, c.class_STEPWISE.inmodel)]; % add intercept + + % apply to Xtest + c.testdat = zeros(size(Xtest, 1), size(c.dat, 2)); + for ii = 1:length(u) + c.testdat(:, ii) = nanmean(Xtest(:, contig == u(ii))')'; + end + class_avg_dat = []; + for i = 1:max(c.ClusterSolution.classes) + wh = find(c.ClusterSolution.classes == i); + if length(wh) > 1 + class_avg_dat = [class_avg_dat nanmean(c.testdat(:,wh)')']; + else + class_avg_dat = [class_avg_dat c.testdat(:,wh)]; + end + end + c.class_avg_testdat = class_avg_dat; + Xtest = c.class_avg_testdat(:, c.class_STEPWISE.inmodel); + + % Make prediction + b = pinv(Xi) * Yi; + pred_value(holdout_set{wh_fold}, 1) = [ones(nholdout, 1) Xtest] * b; + + create_figure('test', 1, 2, 1); subplot(1, 2, 1); plot(Xi*b, Yi, 'ko'); + plot(pred_value(holdout_set{wh_fold}, 1), Ytest, 'ro', 'MarkerFaceColor', 'r'); + subplot(1, 2, 2); + title('Black circles = training set; Red = holdout obs'); + drawnow; + +end + +cm = colormap(jet(N)); +figure; hold on; +for i = 1:N + plot(pred_value(i), Y(i), 'ko', 'MarkerFaceColor', cm(i, :)); +end +xlabel('Predicted outcome (xval)'); ylabel('Outcome'); +title('Color = order in data series'); + + + + +function holdout_set = nested_select_holdout_set + % purpose: return holdout_set variable + + nobs_s = length(Y); + + switch lower(holdout_method) + case 'loo' + holdout_set = cell(1, nobs_s); + for i = 1:nobs_s, holdout_set{i} = i; end + + case 'l4o_covbalanced' + disp('Selecting holdout sets: Balancing on covariates and outcome, and also trying to ensure that each obs is selected equally often.'); + holdout_proportion = 4 ./ nobs_s; % prop for leave-4-out + nfolds = 40; + if isempty(cov_val) + wh_holdout = xval_select_holdout_set(Y, [], nfolds, holdout_proportion, verbose); + else + wh_holdout = xval_select_holdout_set(Y, cov_val, nfolds, holdout_proportion, verbose); + end + holdout_set = cell(1, nfolds); + for k = 1:nfolds + holdout_set{k} = wh_holdout(:, k); + end + + case 'categorical_covs' + + holdout_set = xval_select_holdout_set_categoricalcovs(cov_val); + + case 'balanced4' + nfolds = 4; + holdout_set = cell(1, nfolds); + [ys, indx] = sort(Y); + for k = 1:nfolds + + holdout_set{k} = indx(k:nfolds:end); + if isempty(holdout_set{k}), error('Holdout set construction error: Dataset too small?'); end + + end + + otherwise error('Unknown holdout method. See help.'); + end +end + +end % main function + diff --git a/Statistics_tools/xval_lasso_brain.m b/Statistics_tools/xval_lasso_brain.m new file mode 100644 index 00000000..3d08a6a3 --- /dev/null +++ b/Statistics_tools/xval_lasso_brain.m @@ -0,0 +1,683 @@ +function STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, varargin) +%STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, varargin) +% +% PCA-Lasso based prediction on a set of brain images +% Tor Wager, Sept. 2009 +% +% Inputs: +% my_outcomes: a cell array of outcomes for each dataset +% imgs: a cell array of image names for each dataset +% +% Alternative: "Object-oriented" mode +% my_outcomes = []; +% imgs = an fmri_data object, with imgs.Y = outcomes +% +% Optional inputs: +% 'reversecolors', reversecolors = 1; +% 'skipnonparam', dononparam = 0; +% 'skipsurface', dosurface = 0; +% {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; +% 'mask', mask = varargin{i+1}; varargin{i + 1} = []; +% 'niter', niter = varargin{i+1}; +% 'more_imgs', followed by 2nd (or nth) set of +% images in cell array {} +% 'outcome_name', outcome_name = varargin{i+1}; varargin{i + 1} = []; +% 'covnames', covnames = varargin{i+1}; varargin{i + 1} = []; +% 'save', dosave = 1; +% 'ndims', ndims = varargin{i+1}; varargin{i + 1} = []; +% 'pca', plsstr = 'pca'; % default... +% 'pls', plsstr = 'pls'; +% {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams_str = 'optimize_regularization'; +% {'holdout_method'} followed by string e.g., 'balanced4' +% or custom holdout set of integers for each test set; see +% xval_regression_multisubject +% +% Examples +% ----------------------------------------------------------------------- +% mkdir LASSO_xvalidation_frontal +% cd LASSO_xvalidation_frontal/ +% mask = '/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/newmask_mapped.img'; +% load('/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/robust0004/robust0001/SETUP.mat') +% imgs = {SETUP.files}; +% my_outcomes = {SETUP.X(:, 2)}; +% covs = {SETUP.X(:, [3 4 5])}; +% outcome_name = 'Placebo Analgesia (C-P)'; +% covnames = {'Order' 'Study' 'Study x Placebo'}; +% STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors', 'skipsurface', 'skipnonparam'); +% +% or +% +% STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors'); +% +% STATS_FINAL = xval_lasso_brain({img_dat_cat_masked.Y}, {imgs}, 'mask', maskname, 'outcome_name', img_dat_cat_masked.Y_names, 'niter', 1000, 'holdout_method', id_numbers, 'save'); +% +% For fmri_data object inputs: +% STATS_FINAL = xval_lasso_brain([], img_dat_cat_masked, 'mask', maskname, 'niter', 1000, 'holdout_method', id_numbers, 'save'); + +% Programmers' notes +% Updated 3/7/2013 tor wager +% - changed rng to rng from 'twister' (legacy) +% - documentation +% - updated to take object-oriented fmri_data file as input + + + % ----------------------------------------------------------------------- + % Optional inputs + % ----------------------------------------------------------------------- + spm_defaults + covs = []; + mask = which('brainmask.nii'); + reversecolors = 0; + dononparam = 1; + dosurface = 1; + niter = 200; + covnames = {'Unknown'}; + outcome_name = 'Unknown'; + verbose = 1; + imgs2 = {}; + dosave = 0; + ndims = 'variable'; + plsstr = 'pca'; + dochoose_regparams_str = 'verbose'; % switch to 'optimize_regularization' to do inner xval + holdout_method = 'loo'; % see xval_regression_multisubject + + if isa(imgs, 'image_vector') + outcome_name = imgs.Y_names; + my_outcomes = {imgs.Y}; + end + + inputOptions.all_optional_inputs = varargin; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Process control + case 'reversecolors', reversecolors = 1; + case 'skipnonparam', dononparam = 0; + case 'skipsurface', dosurface = 0; + + % Covariates and Masking + case {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; + case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + case 'niter', niter = varargin{i+1}; + + % Input images + case 'more_imgs', imgs2{end+1} = varargin{i+1}; + fprintf('Found additional image set %3.0f\n', length(imgs2)); + + + % Naming output and saving + case 'outcome_name', outcome_name = varargin{i+1}; varargin{i + 1} = []; + case 'covnames', covnames = varargin{i+1}; varargin{i + 1} = []; + case 'save', dosave = 1; + + % Dimension reduction + case 'ndims', ndims = varargin{i+1}; varargin{i + 1} = []; + if strcmp(ndims, 'all') + ndims = size(imgs{1}, 1) - 2; + fprintf('Choosing %3.0f dims for PCA/PLS\n', ndims); + end + + case 'pca', plsstr = 'pca'; % default... + case 'pls', plsstr = 'pls'; + + % Shrinkage/regularization parameters + + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams_str = 'optimize_regularization'; + + % Holdout set selection + + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + + case {'variable', 'verbose'} % do nothing; needed later + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + inputOptions.all_outcomes = my_outcomes; + inputOptions.outcome_name = outcome_name; + inputOptions.imgs = imgs; + inputOptions.covs = covs; + inputOptions.covnames = covnames; + inputOptions.mask = mask; + inputOptions.ndims = ndims; + inputOptions.plsstr = plsstr; + inputOptions.reversecolors = reversecolors; + inputOptions.dochoose_regparams_str = dochoose_regparams_str; + inputOptions.holdout_method = holdout_method; + + % set up the mask + % ------------------------------------------------------------------------- + if isempty(mask), error('Mask is empty and/or default mask ''brainmask.nii'' cannot be found on path.'); end + + if isa(imgs, 'image_vector') + if isa(mask, 'image_vector') + + mask = resample_space(mask, imgs); + + else % mask is string + + mask = fmri_data(mask); + end + maskInfo = mask.volInfo; + + else + + if exist(fullfile(pwd, 'mask.img')) && verbose, disp('''mask.img'' already exists in this directory. Replacing.'); else disp('Creating mask.img'); end + scn_map_image(mask, deblank(imgs{1}(1,:)), 'write', 'mask.img'); + maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); + end + + inputOptions.maskInfo = maskInfo; + + datasets = length(imgs); % each Subject would constitute a "dataset" for a multi-level/within-subjects analysis + + % load image data and apply mask + % ------------------------------------------------------------------------- + % Object-oriented input + if isa(imgs, 'image_vector') + fprintf('Loading all datasets and applying mask '); + imgs = apply_mask(imgs, mask); + dat{1} = imgs.dat'; + size_orig{1} = [1 imgs.volInfo.n_inmask]; % for reconstruction + + if ~isempty(imgs2) && isa(imgs, 'image_vector') + imgs2 = apply_mask(imgs2, mask); + dat{1} = [dat{1} imgs2.dat']; + elseif ~isempty(imgs2) + error('imags and imgs2 must both be objects or neither can be an object.'); + end + + else + % load from image names + + fprintf('Loading all datasets (may be memory-intensive for multi-subject fMRI): '); + for i = 1:datasets + fprintf('%02d', i); + dat{i} = iimg_get_data(maskInfo, imgs{i}); + size_orig{i} = size(dat{i}); + + if ~isempty(imgs2) + for imgset = 1:length(imgs2) + dat{i} = [dat{i} iimg_get_data(maskInfo, imgs2{imgset}{i})]; + end + + end + + end + fprintf('\n'); + end + + %% + + STATS_FINAL = struct('inputOptions', inputOptions); + + % Run it: covs only + % ------------------------------------------------------------------------- + STATS_FINAL.covs = []; + if ~isempty(covs) + disp('==================================') + disp('Covariates only: OLS') + STATS_FINAL.covs = xval_regression_multisubject('ols', my_outcomes, covs, 'holdout_method', holdout_method); + end + + % Run it: data only + % ------------------------------------------------------------------------- + if ~isempty(covs) + disp('==================================') + disp('Data only: lasso') + STATS_FINAL.data = xval_regression_multisubject('lasso', my_outcomes, dat, 'pca', 'ndims', ndims, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + else + STATS_FINAL.data = 'see .full_model'; + end + + % Run it: combined + % ------------------------------------------------------------------------- + + % My best guess about what will work: Lasso, with as many dims retained as possible + % * NOTE: all of the main inputs should be cell arrays + STATS_FINAL.full_model = []; + if ~isempty(covs) + disp('==================================') + disp('Data plus covariates full model: lasso') + STATS_FINAL.full_model = xval_regression_multisubject('lasso', my_outcomes, dat, 'pca', 'ndims', ndims, 'cov', covs, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + else + disp('Data only full model: lasso') + disp('==================================') + STATS_FINAL.full_model = xval_regression_multisubject('lasso', my_outcomes, dat, 'pca', 'ndims', ndims, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + end + + if dosave + save STATS_xval_output STATS_FINAL + end + + %% Figures: Scatterplot plus bars + % ------------------------------------------------------------------------- + + create_figure('Scatterplot: Full model', 1, 2); + [r,istr,sig,h] = plot_correlation_samefig(STATS_FINAL.full_model.subjfit{1}, STATS_FINAL.inputOptions.all_outcomes{1}); + set(gca,'FontSize', 24) + xlabel('Cross-validated prediction'); + ylabel('Outcome'); + title('Lasso cross-validation'); + set(h, 'MarkerSize', 10, 'MarkerFaceColor', [.0 .4 .8], 'LineWidth', 2); + h = findobj(gca, 'Type', 'Text'); + set(h, 'FontSize', 24) + + % bars + subplot(1, 2, 2) + set(gca,'FontSize', 24) + + if ~isempty(covs) + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.covs.pred_err ... + STATS_FINAL.data.pred_err STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Covs' 'Brain' 'Full'}; + else + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Brain'}; + end + + han = bar(pevals); set(han, 'FaceColor', [.5 .5 .5]); + set(gca, 'XTick', 1:length(penames), 'XTickLabel', penames); + axis tight + ylabel('Prediction error'); + + if dosave + scn_export_papersetup(500); saveas(gcf, 'Pred_Outcome_Scatter', 'png'); + end + + % is accuracy predicted by Y and/or covariates? + % **** + + + + %% Get mean Lasso betas + % ------------------------------------------------------------------------- + + bonf_thresh = norminv(1 - .025 ./ size(dat{1}, 2)); + + mystd = std(STATS_FINAL.full_model.vox_weights'); % not actual valid threshold because training sets are not independent + + [my_ste,t,n_in_column,p,m] = ste(STATS_FINAL.full_model.vox_weights'); + Z = (m ./ mystd)'; + + if isa(mask, 'image_vector') + not_in_mask = mask.dat == 0; + my_ste = zeroinsert(not_in_mask, my_ste')'; + t = zeroinsert(not_in_mask, t')'; + n_in_column = zeroinsert(not_in_mask, n_in_column')'; + p = zeroinsert(not_in_mask, p')'; + m = zeroinsert(not_in_mask, m')'; + Z = zeroinsert(not_in_mask, Z); + end + + i = 1; + iimg_reconstruct_vols(m(1: size_orig{i}(2))', maskInfo, 'outname', 'xval_lasso_wts_mean.img'); + iimg_reconstruct_vols(Z(1: size_orig{i}(2)), maskInfo, 'outname', 'xval_lasso_Z.img'); + + if length(m) > size_orig{i} + iimg_reconstruct_vols(m(size_orig{i}(2)+1:end)', maskInfo, 'outname', 'xval_lasso_wts_mean_imageset2.img'); + iimg_reconstruct_vols(Z(size_orig{i}(2)+1:end), maskInfo, 'outname', 'xval_lasso_Z_imageset2.img'); + end + + sig_vox = abs(Z) > bonf_thresh; + Z_thresh = Z; + Z_thresh(~sig_vox) = 0; + iimg_reconstruct_vols(Z_thresh(1: size_orig{i}(2)), maskInfo, 'outname', 'xval_lasso_Z_bonf_thresh.img'); + + if length(m) > size_orig{i} + iimg_reconstruct_vols(Z_thresh(size_orig{i}(2)+1:end), maskInfo, 'outname', 'xval_lasso_Z_bonf_thresh.img'); + end + + %% Orthviews + % ------------------------------------------------------------------------- + + if any(Z_thresh) + cl = mask2clusters('xval_lasso_Z_bonf_thresh.img'); + else + disp('No significant voxel weights at bonferroni corrected threshold.') + cl = []; + end + + poscm2 = colormap_tor([1 .5 0], [1 1 0]); + negcm2 = colormap_tor([.5 0 1], [0 0 1]); + + cluster_orthviews(cl); + + + if reversecolors + cm = spm_orthviews_change_colormap([1 1 0], [0 0 1], [1 0 .4], [.4 .6 1] ); + else + cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [.4 .6 1], [1 0 .4]); + end + + STATS_FINAL.full_model.cl = cl; + + %cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 0 1], [0 0 1], [0 + %.5 1], [0 .5 1], [.5 .5 .5], [.5 .5 .5], [.7 0 0], [1 .5 0], [1 .5 0], [1 1 0]); + + % cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 0 1], [0 0 1], [0 .5 1], [0 .5 1], [.5 .5 .5], [.5 .5 .5], [.7 0 0], [1 .5 0], [1 .5 0], [1 1 0]); + + %% Brain Surface Plot + % ------------------------------------------------------------------------- + + if dosurface + + create_figure('Brain_Surface', 2, 2); + + if reversecolors + negcm = colormap_tor([1 0 .4], [1 1 0]); + poscm = colormap_tor([.4 .6 1], [0 0 1]); + else + poscm = colormap_tor([1 0 .4], [1 1 0]); + negcm = colormap_tor([.4 .6 1], [0 0 1]); + end + + han1 = addbrain('hires'); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han1); + + %replace with 'hires' and re-run to do whole cortical surface + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 2); + han = addbrain('hires right'); set(han, 'FaceAlpha', 1); + + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 3); + han = addbrain('hires left'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 4); + han = addbrain('limbic'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + set(han(end), 'FaceAlpha', .2) + han2 = addbrain('brainstem'); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + view(135, 15) + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 1) + han = findobj(gca,'Type','patch'); set(han, 'FaceAlpha',1) + axis image + + if dosave + scn_export_papersetup(600); saveas(gcf, 'Surface1', 'png'); + subplot(2, 2, 1); + view(135, 20); lightRestoreSingle; + saveas(gcf, 'Surface2', 'png'); + view(225, 20); lightRestoreSingle; + saveas(gcf, 'Surface3', 'png'); + view(90, 3); lightRestoreSingle; + saveas(gcf, 'Surface4', 'png'); + view(270, 3); lightRestoreSingle; + saveas(gcf, 'Surface5', 'png'); + end + + end + + %% NONPARAMETRIC TEST + + if dononparam + + disp('PERMUTATION TEST FOR NULL HYPOTHESIS'); + + + % Permute : covariates only + % ============================================== + if ~isempty(covs) + clear my_outcomesp all_r pe + ndims = min(size(covs{1}, 2) - 1, size(covs{1}, 1) - 2); % will not work for multilevel with diff. dimensions per dataset + + fprintf('Covariates only: Running %3.0f iterations\n', niter); + + for i = 1:niter + + %rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + try + rng('shuffle'); + catch + rand('twister',sum(100*clock)) ; % was getting wacky results suggesting randperm is not producing indep. perms... + end + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + if size(covs, 2) > 1 + STATSpcovs = xval_regression_multisubject('lasso', my_outcomesp, covs, 'noverbose', 'holdout_method', holdout_method); %,'pca', 'ndims', ndims); + else + STATSpcovs = xval_regression_multisubject('ols', my_outcomesp, covs, 'noverbose', 'holdout_method', holdout_method); %,'pca', 'ndims', ndims); + end + + fprintf('%3.2f ', STATSpcovs.r_each_subject); + + all_r(i) = STATSpcovs.r_each_subject; + pe(i) = STATSpcovs.pred_err; + + end + + STATS_FINAL.covs.permuted.pe_values = pe; + STATS_FINAL.covs.permuted.pe_mean = mean(pe); + STATS_FINAL.covs.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.covs.permuted.r_values = all_r; + STATS_FINAL.covs.permuted.r_mean = mean(all_r); + STATS_FINAL.covs.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + + fprintf('\n') + + end + + % Permute : random subset of 1000 voxels + % ============================================== + n_vox = 1000; + if size(dat{1}, 2) > n_vox + clear my_outcomesp my_datp all_r pe + ndims = size(dat{1}, 1) - 2; + + fprintf('1000 vox subsets: %3.0f iterations: %3.0f', niter, 0); + + for i = 1:niter + + fprintf('\b\b\b%3.0f', i); + + try + rng('shuffle'); + catch + rand('twister',sum(100*clock)) ; % was getting wacky results suggesting randperm is not producing indep. perms... + end + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + ix2 = randperm(size(dat{s}, 2)); + + my_outcomesp{s} = my_outcomes{s}(ix); + my_datp{s} = dat{s}(:, ix2(1:n_vox)); + + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSpv= xval_regression_multisubject('lasso', my_outcomesp, my_datp, 'pca', plsstr, 'ndims', ndims, dochoose_regparams_str, 'holdout_method', holdout_method, 'noverbose'); + + all_r(i) = STATSpv.r_each_subject; + pe(i) = STATSpv.pred_err; + + end + + fprintf('%3.2f ', all_r); + fprintf('\n') + + STATS_FINAL.full_model.permuted.v1000_pe_values = pe; + STATS_FINAL.full_model.permuted.v1000_pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.v1000_pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.full_model.permuted.v1000_r_values = all_r; + STATS_FINAL.full_model.permuted.v1000_r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.v1000_r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + end + + % Permute : Full data + % ============================================== + clear my_outcomesp all_r pe + ndims = size(dat{1}, 1) - 2; + + fprintf('1000 vox subsets: %3.0f iterations\n', niter); + + for i = 1:niter + + %rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + try + rng('shuffle'); + catch + rand('twister',sum(100*clock)) ; % was getting wacky results suggesting randperm is not producing indep. perms... + end + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSp = xval_regression_multisubject('lasso', my_outcomesp, dat, 'pca', plsstr, 'ndims', ndims, dochoose_regparams_str, 'holdout_method', holdout_method); + + fprintf('Iteration %3.0f : r = %3.2f\n', STATSp.r_each_subject); + + all_r(i) = STATSp.r_each_subject; + pe(i) = STATSp.pred_err; + + end + + STATS_FINAL.full_model.permuted.pe_values = pe; + STATS_FINAL.full_model.permuted.pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + + STATS_FINAL.full_model.permuted.r_values = all_r; + STATS_FINAL.full_model.permuted.r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + + end % if do nonparam + +end % main function + + + +% - Extra stuff - + +% % %% Bootstrap the covariates +% % % +% % % This is the "Leave one out bootstrap" of Efron and Tibshirani, JASA, 1997 +% % % Err(1) +% % % They show that this can be done by taking the error on each point from +% % % bootstrap samples that do not happen to contain that point +% % % +% % % they say that for continuous outcomes and predictors (our case), they +% % % expect the cross-val estimate and the 632+ bootstrap estimate to be more +% % % similar. the benefit is mainly for discontinuous outcomes (1/0) +% % % +% % % they say that Err(1) is biased upwards compared to the nearly unbiased +% % % CV(1) (leave-one-out cross-validation) +% % % it estimates "half-sample" xval +% % +% % bootfun = @(Y, X) xval_regression_boostrap_wrapper(Y, X); +% % STATS_FINAL.bootstrap.covs_only_r_rsq = bootstrp(200, bootfun, my_outcomes_orig{1}, covs{1}); +% % STATS_FINAL.bootstrap.covs_only_summary = [mean(STATS_FINAL.bootstrap.covs_only_r_rsq) std(STATS_FINAL.bootstrap.covs_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.covs_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = bootstrp(20, bootfun, my_outcomes_orig{1}, dat{1}); +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = [STATS_FINAL.bootstrap.dat_only_r_rsq; bootstrp(80, bootfun, my_outcomes_orig{1}, dat{1})]; +% % +% % STATS_FINAL.bootstrap.dat_only_summary = [mean(STATS_FINAL.bootstrap.dat_only_r_rsq) std(STATS_FINAL.bootstrap.dat_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.dat_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % %% Nonparametric test with N variables (voxels) selected at random +% % +% % s = 1; +% % +% % +% % n_vox = [1000:2000:50000]; +% % niter = [1 50]; +% % +% % if niter(1) == 1, all_r_perm = zeros(niter(2), length(n_vox + 1)); end +% % +% % for i = 1:length(n_vox) +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject('lasso', {my_outcomes{1}(ix)}, {dat{1}(:, ix2(1:n_vox(i)))}, 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow +% % end +% % +% % % with full sample +% % n_vox(end+1) = size(dat{s}, 2); +% % i = length(n_vox); +% % +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % %ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject('lasso', {my_outcomes{1}(ix)}, dat(1), 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow diff --git a/Statistics_tools/xval_lasso_brain_featureselect.m b/Statistics_tools/xval_lasso_brain_featureselect.m new file mode 100644 index 00000000..2deba72b --- /dev/null +++ b/Statistics_tools/xval_lasso_brain_featureselect.m @@ -0,0 +1,520 @@ +function STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, varargin) + %STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, varargin) + % + % PCA-Lasso based prediction on a set of brain images + % Tor Wager, Sept. 2009 + % + % + % Optional inputs: + % case 'reversecolors', reversecolors = 1; + % case 'skipnonparam', dononparam = 0; + % case 'skipsurface', dosurface = 0; + % case {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; + % case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + % case 'niter', niter = varargin{i+1}; + % case 'more_imgs', followed by 2nd (or nth) set of + % images in cell array {} + % Example + % ----------------------------------------------------------------------- + % mkdir LASSO_xvalidation_frontal + % cd LASSO_xvalidation_frontal/ + % mask = '/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/newmask_mapped.img'; + % load('/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/robust0004/robust0001/SETUP.mat') + % imgs = {SETUP.files}; + % my_outcomes = {SETUP.X(:, 2)}; + % covs = {SETUP.X(:, [3 4 5])}; + % outcome_name = 'Placebo Analgesia (C-P)'; + % covnames = {'Order' 'Study' 'Study x Placebo'}; + % STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors', 'skipsurface', 'skipnonparam'); + % + % or + % + % STATS_FINAL = xval_lasso_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors'); + + % ----------------------------------------------------------------------- + % Optional inputs + % ----------------------------------------------------------------------- + spm_defaults + covs = []; + mask = which('brainmask.nii'); + reversecolors = 0; + dononparam = 1; + dosurface = 1; + niter = 200; + covnames = {'Unknown'}; + outcome_name = 'Unknown'; + verbose = 1; + imgs2 = {}; + dosave = 0; + ndims = 20; + plsstr = 'pca'; + dochoose_regparams_str = 'verbose'; % switch to 'optimize_regularization' to do inner xval + holdout_method = 'loo'; % see xval_regression_multisubject_featureselect + + inputOptions.all_optional_inputs = varargin; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Process control + case 'reversecolors', reversecolors = 1; + case 'skipnonparam', dononparam = 0; + case 'skipsurface', dosurface = 0; + + % Covariates and Masking + case {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; + case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + case 'niter', niter = varargin{i+1}; + + % Input images + case 'more_imgs', imgs2{end+1} = varargin{i+1}; + fprintf('Found additional image set %3.0f\n', length(imgs2)); + + + % Naming output and saving + case 'outcome_name', outcome_name = varargin{i+1}; varargin{i + 1} = []; + case 'covnames', covnames = varargin{i+1}; varargin{i + 1} = []; + case 'save', dosave = 1; + + % Dimension reduction + case 'ndims', ndims = varargin{i+1}; varargin{i + 1} = []; + if strcmp(ndims, 'all') + ndims = size(imgs{1}, 1) - 2; + fprintf('Choosing %3.0f dims for PCA/PLS\n', ndims); + end + + case 'pca', plsstr = 'pca'; % default... + case 'pls', plsstr = 'pls'; + + % Shrinkage/regularization parameters + + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams_str = 'optimize_regularization'; + + % Holdout set selection + + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + + case {'variable', 'verbose'} % do nothing; needed later + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + inputOptions.all_outcomes = my_outcomes; + inputOptions.outcome_name = outcome_name; + inputOptions.imgs = imgs; + inputOptions.covs = covs; + inputOptions.covnames = covnames; + inputOptions.mask = mask; + inputOptions.ndims = ndims; + inputOptions.plsstr = plsstr; + inputOptions.reversecolors = reversecolors; + inputOptions.dochoose_regparams_str = dochoose_regparams_str; + inputOptions.holdout_method = holdout_method; + + % set up the mask + % ------------------------------------------------------------------------- + if isempty(mask), error('Mask is empty and/or default mask ''brainmask.nii'' cannot be found on path.'); end + if exist(fullfile(pwd, 'mask.img')) && verbose, disp('''mask.img'' already exists in this directory. Replacing.'); else disp('Creating mask.img'); end + scn_map_image(mask, deblank(imgs{1}(1,:)), 'write', 'mask.img'); + maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); + + inputOptions.maskInfo = maskInfo; + + datasets = length(imgs); % each Subject would constitute a "dataset" for a multi-level/within-subjects analysis + + % load image data + % ------------------------------------------------------------------------- + fprintf('Loading all datasets (may be memory-intensive for multi-subject fMRI): '); + for i = 1:datasets + fprintf('%02d', i); + dat{i} = iimg_get_data(maskInfo, imgs{i}); + size_orig{i} = size(dat{i}); + + if ~isempty(imgs2) + for imgset = 1:length(imgs2) + dat{i} = [dat{i} iimg_get_data(maskInfo, imgs2{imgset}{i})]; + end + + end + + end + fprintf('\n'); + + %% + + STATS_FINAL = struct('inputOptions', inputOptions); + + % Run it: covs only + % ------------------------------------------------------------------------- + STATS_FINAL.covs = []; + if ~isempty(covs) + disp('==================================') + disp('Covariates only: OLS') + STATS_FINAL.covs = xval_regression_multisubject_featureselect('ols', my_outcomes, covs, 'holdout_method', holdout_method); + + if dosave, create_figure('fitplot', 2, 2, 1); scn_export_papersetup(400); saveas(gcf, 'Lasso_xval_brain_fits_covs_only.png'); end + end + + % Run it: data only + % ------------------------------------------------------------------------- + if ~isempty(covs) + disp('==================================') + disp('Data only: lasso') + STATS_FINAL.data = xval_regression_multisubject_featureselect('lasso', my_outcomes, dat, 'pca', 'ndims', ndims, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + else + STATS_FINAL.data = 'see .full_model'; + end + + if dosave, create_figure('fitplot', 2, 2, 1); scn_export_papersetup(400); saveas(gcf, 'Lasso_xval_brain_fits_brain_only.png'); end + + % Run it: combined + % ------------------------------------------------------------------------- + + % My best guess about what will work: Lasso, with as many dims retained as possible + % * NOTE: all of the main inputs should be cell arrays + STATS_FINAL.full_model = []; + if ~isempty(covs) + disp('==================================') + disp('Data plus covariates full model: lasso') + STATS_FINAL.full_model = xval_regression_multisubject_featureselect('lasso', my_outcomes, dat, 'pca', 'ndims', ndims, 'covs', covs, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + else + disp('Data only full model: lasso') + disp('==================================') + STATS_FINAL.full_model = xval_regression_multisubject_featureselect('lasso', my_outcomes, dat, 'pca', 'ndims', ndims, plsstr, dochoose_regparams_str, 'holdout_method', holdout_method); + end + + if dosave + save STATS_xval_output STATS_FINAL + create_figure('fitplot', 2, 2, 1); scn_export_papersetup(400); saveas(gcf, 'Lasso_xval_brain_fits_full_model.png'); + end + + %% Figures: Scatterplot plus bars + % ------------------------------------------------------------------------- + + create_figure('Scatterplot: Full model', 1, 2); + [r,istr,sig,h] = plot_correlation_samefig(STATS_FINAL.full_model.subjfit{1}, STATS_FINAL.inputOptions.all_outcomes{1}); + set(gca,'FontSize', 24) + xlabel('Cross-validated prediction'); + ylabel('Outcome'); + title('Lasso cross-validation'); + set(h, 'MarkerSize', 10, 'MarkerFaceColor', [.0 .4 .8], 'LineWidth', 2); + h = findobj(gca, 'Type', 'Text'); + set(h, 'FontSize', 24) + + % bars + subplot(1, 2, 2) + set(gca,'FontSize', 24) + + if ~isempty(covs) + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.covs.pred_err ... + STATS_FINAL.data.pred_err STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Covs' 'Brain' 'Full'}; + else + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Brain'}; + end + + han = bar(pevals); set(han, 'FaceColor', [.5 .5 .5]); + set(gca, 'XTick', 1:length(penames), 'XTickLabel', penames); + axis tight + ylabel('Prediction error'); + + if dosave + scn_export_papersetup(500); saveas(gcf, 'Pred_Outcome_Scatter', 'png'); + end + + % is accuracy predicted by Y and/or covariates? + % **** + + + %% NONPARAMETRIC TEST + + if dononparam + + disp('PERMUTATION TEST FOR NULL HYPOTHESIS'); + + + % Permute : covariates only + % ============================================== + if ~isempty(covs) + clear my_outcomesp all_r pe + ndims = min(size(covs{1}, 2) - 1, size(covs{1}, 1) - 2); % will not work for multilevel with diff. dimensions per dataset + + fprintf('Covariates only: Running %3.0f iterations\n', niter); + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + if size(covs, 2) > 1 + STATSpcovs = xval_regression_multisubject_featureselect('lasso', my_outcomesp, covs, 'noverbose', 'holdout_method', holdout_method); %,'pca', 'ndims', ndims); + else + STATSpcovs = xval_regression_multisubject_featureselect('ols', my_outcomesp, covs, 'noverbose', 'holdout_method', holdout_method); %,'pca', 'ndims', ndims); + end + + fprintf('%3.2f ', STATSpcovs.r_each_subject); + + all_r(i) = STATSpcovs.r_each_subject; + pe(i) = STATSpcovs.pred_err; + + end + + STATS_FINAL.covs.permuted.pe_values = pe; + STATS_FINAL.covs.permuted.pe_mean = mean(pe); + STATS_FINAL.covs.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.covs.permuted.r_values = all_r; + STATS_FINAL.covs.permuted.r_mean = mean(all_r); + STATS_FINAL.covs.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + + save STATS_xval_output_permsbackup STATS_FINAL + end + + fprintf('\n') + + end + +% % % Permute : random subset of 1000 voxels +% % % ============================================== +% % n_vox = 1000; +% % if size(dat{1}, 2) > n_vox +% % clear my_outcomesp my_datp all_r pe +% % ndims = size(dat{1}, 1) - 2; +% % +% % fprintf('1000 vox subsets: %3.0f iterations: %3.0f', niter, 0); +% % +% % for i = 1:niter +% % +% % fprintf('\b\b\b%3.0f', i); +% % +% % rand('twister',sum(100*clock)) ; % was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % my_outcomes = STATS_FINAL.inputOptions.all_outcomes; +% % +% % % permute +% % for s = 1:datasets +% % ix = randperm(length(my_outcomes{s})); +% % ix2 = randperm(size(dat{s}, 2)); +% % +% % my_outcomesp{s} = my_outcomes{s}(ix); +% % my_datp{s} = dat{s}(:, ix2(1:n_vox)); +% % +% % end +% % +% % %% Do the prediction +% % % ------------------------------------------------------------------ +% % STATSpv= xval_regression_multisubject_featureselect('lasso', my_outcomesp, my_datp, 'pca', plsstr, 'ndims', ndims, dochoose_regparams_str, 'holdout_method', holdout_method, 'noverbose'); +% % +% % all_r(i) = STATSpv.r_each_subject; +% % pe(i) = STATSpv.pred_err; +% % +% % end +% % +% % fprintf('%3.2f ', all_r); +% % fprintf('\n') +% % +% % STATS_FINAL.full_model.permuted.v1000_pe_values = pe; +% % STATS_FINAL.full_model.permuted.v1000_pe_mean = mean(pe); +% % STATS_FINAL.full_model.permuted.v1000_pe_ci = [prctile(pe, 5) prctile(pe, 95)]; +% % STATS_FINAL.full_model.permuted.v1000_r_values = all_r; +% % STATS_FINAL.full_model.permuted.v1000_r_mean = mean(all_r); +% % STATS_FINAL.full_model.permuted.v1000_r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; +% % if dosave +% % save STATS_xval_output -append STATS_FINAL +% % end +% % end + + % Permute : Full data + % ============================================== + clear my_outcomesp all_r pe + ndims = size(dat{1}, 1) - 2; + + fprintf('Full-dataset permutation: %3.0f iterations\n', niter); + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSp = xval_regression_multisubject_featureselect('lasso', my_outcomesp, dat, 'pca', plsstr, 'ndims', ndims, dochoose_regparams_str, 'holdout_method', holdout_method, 'noverbose'); + + fprintf('Iteration %3.0f : r = %3.2f\n', i, STATSp.r_each_subject); + + all_r(i) = STATSp.r_each_subject; + pe(i) = STATSp.pred_err; + + end + + STATS_FINAL.full_model.permuted.pe_values = pe; + STATS_FINAL.full_model.permuted.pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + + STATS_FINAL.full_model.permuted.r_values = all_r; + STATS_FINAL.full_model.permuted.r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + + % Histogram plot + permuted_histogram_plot(STATS_FINAL); + + % turn surface off if not sig + if STATS_FINAL.full_model.pred_err > STATS_FINAL.full_model.permuted.pe_ci(1) % not sig + dosurface = 0; + end + + end % if do nonparam + + + % This function saves maps, plots, etc. and does a bootstrap test to + % threshold the voxel weights. + % It obviates the code below. + if dosurface + STATS_VOXWEIGHTS = xval_regression_multisubject_featureselect_bootstrapweightmap('lasso', my_outcomes, dat, maskInfo, varargin, 'pca', 'ndims', ndims, 'covs', covs, plsstr); + save STATS_VOXWEIGHTS STATS_VOXWEIGHTS + end + + + +end % main function + + + + + + +function permuted_histogram_plot(STATS_FINAL) + create_figure('Actual_vs_perm', 1, 2); + set(gca, 'FontSize', 24); + + hist(STATS_FINAL.full_model.permuted.r_values, 15); + h = findobj(gcf, 'Type', 'Patch'); + set(h, 'FaceColor', [.6 .6 .6]); + h = plot_vertical_line(STATS_FINAL.full_model.r_each_subject); set(h, 'Color', 'r', 'LineWidth', 5); + h = plot_vertical_line(prctile(STATS_FINAL.full_model.permuted.r_values, 95)); set(h, 'LineStyle', '--', 'LineWidth', 2); + h = plot_vertical_line(prctile(STATS_FINAL.full_model.permuted.r_values, 5)); set(h, 'LineStyle', '--', 'LineWidth', 2); + xlabel('Correlation: Predicted and Outcome'); + ylabel('Frequency'); + + subplot(1, 2, 2) + set(gca, 'FontSize', 24); + hist(STATS_FINAL.full_model.permuted.pe_values, 15); + h = findobj(gcf, 'Type', 'Patch'); + set(h, 'FaceColor', [.6 .6 .6]); + h = plot_vertical_line(STATS_FINAL.full_model.pred_err); set(h, 'Color', 'r', 'LineWidth', 5); + h = plot_vertical_line(prctile(STATS_FINAL.full_model.permuted.pe_values, 95)); set(h, 'LineStyle', '--', 'LineWidth', 2); + h = plot_vertical_line(prctile(STATS_FINAL.full_model.permuted.pe_values, 5)); set(h, 'LineStyle', '--', 'LineWidth', 2); + xlabel('Prediction error'); + ylabel('Frequency'); + + scn_export_papersetup; saveas(gcf, 'Permuted_vs_actual_histograms.png'); +end + + + +% - Extra stuff - + +% % %% Bootstrap the covariates +% % % +% % % This is the "Leave one out bootstrap" of Efron and Tibshirani, JASA, 1997 +% % % Err(1) +% % % They show that this can be done by taking the error on each point from +% % % bootstrap samples that do not happen to contain that point +% % % +% % % they say that for continuous outcomes and predictors (our case), they +% % % expect the cross-val estimate and the 632+ bootstrap estimate to be more +% % % similar. the benefit is mainly for discontinuous outcomes (1/0) +% % % +% % % they say that Err(1) is biased upwards compared to the nearly unbiased +% % % CV(1) (leave-one-out cross-validation) +% % % it estimates "half-sample" xval +% % +% % bootfun = @(Y, X) xval_regression_boostrap_wrapper(Y, X); +% % STATS_FINAL.bootstrap.covs_only_r_rsq = bootstrp(200, bootfun, my_outcomes_orig{1}, covs{1}); +% % STATS_FINAL.bootstrap.covs_only_summary = [mean(STATS_FINAL.bootstrap.covs_only_r_rsq) std(STATS_FINAL.bootstrap.covs_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.covs_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = bootstrp(20, bootfun, my_outcomes_orig{1}, dat{1}); +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = [STATS_FINAL.bootstrap.dat_only_r_rsq; bootstrp(80, bootfun, my_outcomes_orig{1}, dat{1})]; +% % +% % STATS_FINAL.bootstrap.dat_only_summary = [mean(STATS_FINAL.bootstrap.dat_only_r_rsq) std(STATS_FINAL.bootstrap.dat_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.dat_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % %% Nonparametric test with N variables (voxels) selected at random +% % +% % s = 1; +% % +% % +% % n_vox = [1000:2000:50000]; +% % niter = [1 50]; +% % +% % if niter(1) == 1, all_r_perm = zeros(niter(2), length(n_vox + 1)); end +% % +% % for i = 1:length(n_vox) +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject_featureselect('lasso', {my_outcomes{1}(ix)}, {dat{1}(:, ix2(1:n_vox(i)))}, 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow +% % end +% % +% % % with full sample +% % n_vox(end+1) = size(dat{s}, 2); +% % i = length(n_vox); +% % +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % %ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject_featureselect('lasso', {my_outcomes{1}(ix)}, dat(1), 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow diff --git a/Statistics_tools/xval_lasso_trace.m b/Statistics_tools/xval_lasso_trace.m new file mode 100644 index 00000000..09fbc992 --- /dev/null +++ b/Statistics_tools/xval_lasso_trace.m @@ -0,0 +1,149 @@ +function STATS = xval_lasso_trace(Y, data, whfolds, names) +% STATS = xval_lasso_trace(Y, data, whfolds, names) +% +% y = outcome +% data = obs x variables predictor matrix +% whfolds = integer vector of test set membership for each obs, obs x 1 +% names = cell array of names for each predictor variable +% +% Tor Wager, July 2011 + +colors = scn_standard_colors(size(data, 2)); + +lassopath = '/Users/tor/Documents/matlab_code_external/machine_learning/lasso_rocha/lasso'; +addpath(lassopath) + +%% Basic cross-validated prediction + +% STATS = xval_regression_multisubject('ols', {Y}, {data}, 'holdout_method', 'balanced4'); +% +% STATS = xval_regression_multisubject('ols', {Y}, {data}, 'holdout_method', whfolds); + +STATS = xval_regression_multisubject('ols', {Y}, {data}, 'holdout_method', whfolds); + +fprintf('r = %3.2f\tr^2 = %3.2f\tpe = %3.4f\n', STATS.r_each_subject, STATS.r_squared, STATS.pred_err); + +%% Now leave one var out at a time + +for i = 1:size(data, 2) + + data2 = data; + data2(:, i) = []; + + STATS = xval_regression_multisubject('ols', {Y}, {data2}, 'holdout_method', whfolds, 'noverbose'); + + xval_pe(i, 1) = STATS.pred_err; + +end + +%% Print Leave out one var output + +disp('Leave-one-variable out prediction error, sorted from highest to lowest beta weight.') + +STATS = xval_regression_multisubject('ols', {Y}, {data}, 'holdout_method', whfolds, 'noverbose'); + +[b, wh] = sort(abs(STATS.subjbetas{1}(2:end)), 'descend'); +names2 = names(wh); +xval_pe2 = xval_pe(wh); + +fprintf('\n%s\t%s\t%s\n', 'Name', 'xvalBeta', 'LeaveOutVarPE'); + +for i = 1:length(wh) + + fprintf('%s\t%3.2f\t%3.4f\n', names2{i}, b(i), xval_pe2(i)); + +end + +create_figure('all vars', 2, 1); + +w = STATS.mean_vox_weights; % STATS.subjbetas{1}(2:end) +hh = barh(w); +set(hh, 'FaceColor', [.5 .5 .5]); +set(gca, 'YDir', 'Reverse', 'YTick', 1:length(names), 'YTickLabel', names, 'YLim', [0 length(names)+1], 'FontSize', 24) +xlabel('Beta weights'); +title('OLS'); + +% prediction-outcome correlation with all vars +r_all = STATS.r_each_subject; + +%% LASSO + +out = lasso_rocha(Y, data); + +[k, v] = size(out.beta); % steps, vars + +subplot(2, 1, 2); +w = out.beta(end, :)'; +hh = barh(w); +set(hh, 'FaceColor', [.5 .5 .5]); +set(gca, 'YDir', 'Reverse', 'YTick', 1:length(names), 'YTickLabel', names, 'YLim', [0 length(names)+1], 'FontSize', 24) +xlabel('Beta weights'); +title('LASSO - NO PENALTY'); + +%% LASSO trace plot + +create_figure('Lassopath', 3, 1); + +for i = 1:size(out.beta, 2) + plot(0:(k-1), out.beta(:, i), 'o-', 'LineWidth', 3, 'color', colors{i}); +end + +legend(names) +ylabel('Regression coefficient'); +drawnow + +xval_pe = NaN; + +for i = 2:k % omit first step with all zero betas + + STATS = xval_regression_multisubject('lasso', {Y}, {data}, 'holdout_method', whfolds, 'noverbose', 'lassopath', out.lambda(i)); + + % xval_regression_multisubject will fit the OLS model after selecting variables. + xval_pe(i, 1) = STATS.pred_err; + + r(i, 1) = STATS.r_each_subject; % prediction-outcome correlation + + % for BIC + v(i-1, 1) = STATS.var_full; + f(i-1, 1) = i - 1; % number of free params + n(i-1, 1) = length(Y); % this should be independent; or else estimate df?... + + % alternatives: all produces same results, 3/5/13 +% dat = fmri_data; +% dat.Y = Y; dat.dat = data'; +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcr', 'nfolds', whfolds, 'lasso_num', i-1, 'nopcr'); +% [cverr, stats, optout] = predict(dat, 'algorithm_name', 'cv_lassopcrmatlab', 'nfolds', whfolds, 'lasso_num', i-1, 'Alpha', 1, 'nopcr'); +end + +subplot(3, 1, 2); +plot(0:(k-1), xval_pe, 'ko-', 'LineWidth', 3); + +hh = plot_horizontal_line(STATS.pred_err_null); +set(hh, 'LineStyle', ':'); +xlabel('Number of variables in model') +ylabel('Error'); +title('Cross-validated prediction error'); + +subplot(3, 1, 3); +plot(0:(k-1), r.^2, 'ko-', 'LineWidth', 3); + +hh = plot_horizontal_line(r_all.^2); +set(hh, 'LineStyle', ':'); +xlabel('Number of variables in model') +ylabel('r^2'); +title('Variance explained'); + +% BIC: This assumes observations are independent... + +bic = n.*log(v) + f.*log(n); +[mm, i] = min(bic); + + + +% Do one more time for output +STATS = xval_regression_multisubject('lasso', {Y}, {data}, 'holdout_method', whfolds, 'noverbose', 'lassopath', out.lambda(i+1)); + + + + +end % function diff --git a/Statistics_tools/xval_regression_bootstrapweightmap_brainplots.m b/Statistics_tools/xval_regression_bootstrapweightmap_brainplots.m new file mode 100644 index 00000000..6ab76065 --- /dev/null +++ b/Statistics_tools/xval_regression_bootstrapweightmap_brainplots.m @@ -0,0 +1,341 @@ +function OUT = xval_regression_bootstrapweightmap_brainplots(STATS, varargin) +% OUT = xval_regression_bootstrapweightmap_brainplots(STATS, varargin) +% +% Creates plots for xval regression bootstrap STATS structure +% See xval_regression_multisubject_bootstrapweightmap.m +% +% Options: +% ================================================= +% {'newmontage', 'montage'}, donewmontage = 1; +% {'oldmontage'}, dooldmontage = 1; +% {'surface'}, dosurface = 1; +% {'diary'}, dodiary = 1; +% {'save'}, dosave = 1; +% 'reversecolors', reversecolors = 1; +% +% OUT has the thresholds and positive/negative clusters for each threshold +% +% Examples: +% ================================================= +% This saves all output except old-style montages in the current directory: +% xval_regression_bootstrapweightmap_brainplots(STATS,'montage', 'surface', 'diary', 'save'); + + +donewmontage = 0; +dooldmontage = 0; +dosurface = 0; +dodiary = 0; +dosave = 0; +reversecolors = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'newmontage', 'montage'}, donewmontage = 1; + + case {'oldmontage'}, dooldmontage = 1; + case {'surface'}, dosurface = 1; + case {'diary'}, dodiary = 1; + case {'save'}, dosave = 1; + + case 'reversecolors', reversecolors = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +volInfo = STATS.VOXWEIGHTS.volInfo; +w = STATS.VOXWEIGHTS.vox_weights{1}; +p = STATS.VOXWEIGHTS.pval_2tail; + +% pvals will be min for out-of-mask voxels +p(w' == 0) = 1; + +% Print summary table +% ---------------------------------------------- + +if dodiary + + diary bootstrap_weightmap_output.txt + +end + +% Thresholds +% ---------------------------------------------- + +thresh = [Inf .001 .002 .005 .01 .05]; +sigvox = sum(STATS.VOXWEIGHTS.sigfdr); +threshnames = {'FDR' '001' '002' '005' '01' '05'}; + +thresh(1) = STATS.VOXWEIGHTS.pthr_fdr; + +% Sig voxels and clusters +% ---------------------------------------------- + +for ii = 2:length(thresh) + sigvox(ii) = sum(p < thresh(ii) & w' ~= 0); +end + +fprintf('In-Mask Space is %3.0f voxels\n', volInfo.n_inmask) +fprintf('Valid non-empty voxels (for FDR calculation): %3.0f voxels\n\n', sum(w' ~= 0)) + +fprintf('FDR threshold = %3.6f, sig vox = %3.0f\n', STATS.VOXWEIGHTS.pthr_fdr, sum(STATS.VOXWEIGHTS.sigfdr)) +for ii = 2:length(thresh) + fprintf('p < %3.4f, sig vox = %3.0f\n', thresh(ii), sigvox(ii)) +end + +if dodiary + + diary off + +end + +OUT = struct('thresh', thresh, 'sigvox', sigvox, 'threshnames', threshnames, 'clpos', [], 'clneg', []); + +% Set Colors +% ---------------------------------------------- + +if reversecolors, revstr = '_revcolors'; else revstr = ''; end + +if reversecolors + negcm = colormap_tor([.8 0 0], [1 1 0], [1 1 0]); + poscm = colormap_tor([.4 .8 .6], [0 0 1], [0 0 1]); +else + poscm = colormap_tor([.8 0 0], [1 1 0], [1 1 0]); + negcm = colormap_tor([.4 .8 .6], [0 0 1], [0 0 1]); +end + +% Save weight image +% ---------------------------------------------- +w = STATS.VOXWEIGHTS.vox_weights{1}; + +if dosave + iimg_reconstruct_vols(w, volInfo, 'outname', 'voxelweights.img'); + + iimg_reconstruct_vols(p', volInfo, 'outname', 'voxelweight_pvalues.img'); + +end + +% ------------------------------------------------------------- +% Save weight images and display montages for each threshold +% ------------------------------------------------------------- + +% NEW MONTAGE SETUP +% ---------------------------------------------- +if donewmontage + obj = fmridisplay; % Create object with canonical underlay + obj = montage(obj, 'onerow'); % Show axial montage of underlay + enlarge_axes(gcf, .95); +end + +for i = 1:length(thresh) + + if ~sigvox(i), continue, end % skip if no vox + + outbase = sprintf('voxelweights_p_%s%s', threshnames{i}, revstr); + + montagebase = sprintf('thresh_p_%s_%3.0fvox%s', threshnames{i}, sigvox(i), revstr); + mb = montagebase; mb(mb == '_') = ' '; + + disp(outbase) + + % Save weight image + % ---------------------------------------------- + outname = [outbase '.img']; + wthr = w; + wthr(p > thresh(i)) = 0; + + if dosave + iimg_reconstruct_vols(wthr, volInfo, 'outname', outname); + end + + % ***Note: this can fail if no variability in Z values, i.e. if + % few booot samples are used. should fix...*** + + % Clusters + zthr = sign(w) .* norminv(1 - p'); + zthr(p > thresh(i)) = 0; + % wpos = wthr; wpos(wpos < 0) = 0; + % wneg = wthr; wneg(wneg > 0) = 0; + zpos = zthr; zpos(zpos < 0) = 0; + zneg = zthr; zneg(zneg > 0) = 0; + clpos = iimg_indx2clusters(zpos, volInfo); + clneg = iimg_indx2clusters(zneg, volInfo); + clpos(cat(1, clpos.numVox) < 3) = []; + clneg(cat(1, clneg.numVox) < 3) = []; + + OUT(i).clpos = clpos; + OUT(i).clneg = clneg; + + % NEW MONTAGE blobs + % ------------------------------- + if donewmontage + + obj = removeblobs(obj); + + obj = addblobs(obj, clpos, 'maxcolor', [1 1 0], 'mincolor', [1 .3 0]); + obj = addblobs(obj, clneg, 'maxcolor', [.5 0 1], 'mincolor', [0 0 1]); + + axes(obj.montage{1}.axis_handles(2)) + title([mb ' k=3 contig'], 'FontSize', 18); + + obj = legend(obj); + + if dosave + scn_export_papersetup(500); + saveas(gcf, [montagebase '_new_axial.png']); + saveas(gcf, [montagebase '_new_axial.fig']); + end + + end + + if dosave + diary bootstrap_weightmap_output.txt + end + + fprintf('\n===============================\n') + + disp([mb 'k=3 contig']) + fprintf('===============================\n') + fprintf('POSITIVE EFFECTS\n_______________________________\n') + + cluster_table(clpos, 0 , 0); + + fprintf('\nNEGATIVE EFFECTS\n_______________________________\n') + + cluster_table(clneg, 0 , 0); + + if dooldmontage + + % Orthviews + % ---------------------------------------------- + cl = iimg_indx2clusters(wthr, volInfo); + cluster_orthviews(cl) + spm_orthviews_hotcool_colormap(w, [prctile(w, 50)]); + cm = get(gcf, 'Colormap'); + + % adjust color map + %isgray = all(cm - repmat(mean(cm, 2), 1, 3) < .01, 2) + cm2 = [cm(1:64, :); negcm(end:-2:1, :); poscm(1:2:end, :)]; + colormap(cm2) + + % Montages from orthviews + % ---------------------------------------------- + overlay = which('SPM8_colin27T1_seg.img'); + + slices_fig_h = cluster_orthviews_montage(6, 'axial', overlay, 0, 'onerow'); + %colormap(cm); h = findobj(gcf, 'Type', 'Text'); delete(h) + if dosave + scn_export_papersetup(500); saveas(gcf, [montagebase '_axial.png']); + end + + slices_fig_h = cluster_orthviews_montage(6, 'sagittal', overlay, 0, 'onerow'); + %colormap(cm); h = findobj(gcf, 'Type', 'Text'); delete(h) + if dosave + scn_export_papersetup(500); saveas(gcf, [montagebase '_sagittal.png']); + end + + slices_fig_h = cluster_orthviews_montage(6, 'coronal', overlay, 0, 'onerow'); + %colormap(cm); h = findobj(gcf, 'Type', 'Text'); delete(h) + if dosave + scn_export_papersetup(500); saveas(gcf, [montagebase '_coronal.png']); + end + + end + +end + + +% ----------------------------------------------- +% Figure - surface, varying threshold +% ----------------------------------------------- + +if dosurface + + for i = 1:length(thresh) + + if ~sigvox(i), continue, end % skip if no vox + + outname = sprintf('thresh_p_%s_%3.0fvox%s', threshnames{i}, sigvox(i), revstr); + + disp(outname) + + %outbase = 'voxelweights_p002'; + %outname = [outbase '.img']; + + wthr = w; wthr(p > thresh(i)+eps) = 0; + cl = iimg_indx2clusters(wthr, volInfo); + + create_figure('Brain_Surface', 2, 2); + + + han1 = addbrain('hires'); + set(han1, 'FaceAlpha', 1); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han1); + + %replace with 'hires' and re-run to do whole cortical surface + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 2); + han = addbrain('hires right'); set(han, 'FaceAlpha', 1); + + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 3); + han = addbrain('hires left'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 4); + han = addbrain('limbic'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + set(han(end), 'FaceAlpha', .2) + han2 = addbrain('brainstem'); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + view(135, 15) + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 1) + han = findobj(gca,'Type','patch'); set(han, 'FaceAlpha',1) + axis image + + if dosave + scn_export_papersetup(600); saveas(gcf, [outname '_Surface1'], 'png'); + subplot(2, 2, 1); + view(135, 20); lightRestoreSingle; + saveas(gcf, [outname '_Surface2'], 'png'); + view(225, 20); lightRestoreSingle; + saveas(gcf, [outname '_Surface3'], 'png'); + view(90, 3); lightRestoreSingle; + subplot(2, 2, 4); + view(135, 0); lightRestoreSingle; + saveas(gcf, [outname '_Surface4'], 'png'); + subplot(2, 2, 1); + view(270, 3); lightRestoreSingle; + subplot(2, 2, 4); + view(235, 0); lightRestoreSingle; + saveas(gcf, [outname '_Surface5'], 'png'); + subplot(2, 2, 1); + view(180, -90); lightRestoreSingle; + saveas(gcf, [outname '_Surface6'], 'png'); + end + + end % threshold loop + +end % surface + + +end \ No newline at end of file diff --git a/Statistics_tools/xval_regression_multisubject.m b/Statistics_tools/xval_regression_multisubject.m new file mode 100644 index 00000000..85c5fac7 --- /dev/null +++ b/Statistics_tools/xval_regression_multisubject.m @@ -0,0 +1,1064 @@ +function STATS = xval_regression_multisubject(fit_method, Y, X, varargin) + % STATS = xval_regression_multisubject(fit_method, Y, X, varargin) + % + % CROSS-VALIDATED Regression + % Leave-one observation out, predict outcomes for each missing holdout_set. + % OR define other types of holdout sets, as described below + % + % Y = outcome, N x 1, enter for each separate dataset (e.g., subject) in cells {} + % X = predictors, N x variables, enter for each separate dataset (e.g., subject) in cells {} + % fit_method = 'lasso' 'ols' 'ridge' or 'robust' + % + % optional inputs: + % 'pca', PCA data reduction first + % 'ndims', dims to save from PCA + % 'variable', retain max dims for each subject + % case {'cov', 'covs'}, cov = varargin{i+1}; NOTE:covariates are added to training data if entered. + % + % {'lassopath', 'ridgek', 'regparams'}, regparams = varargin{i+1}; + % - For LASSO: This is the lambda reg param, should be between 0 and 1 + % + % {'noverb', 'noverbose'}, verbose = 0; + % {'lowverb', 'loverb', 'lowverbose'}, verboseL = 1; verbose = 0; + % 'nested_choose_ndims', dochoose_ndims = 1; + % + % 'optimize_regularization', optimize LASSO lambda or ridge parameter + % using inner x-val loop with balanced4 selection and least squares + % nonlinear fit + % + % 'holdout_method', followed by the name of a holdout method: + % 'loo' leave-one-out + % 'categorical_covs' balanced on categorical covariates + % 'l4o_covbalanced' leave-4-out, balanced on combo of Y and continuous + % covariates (logistic regression/propensity score based method) + % 'balanced4' 4-fold, balanced on Y (every 4th element of sorted Y) + % Or CUSTOM holdout set:Enter integer list of which obs belong to which holdout set. + % + % Single-level analysis: enter a single cell with Y and X data. + % rows are observations. (e.g., subjects) + % + % Multi-level analysis: enter a cell per subject with Y and X data. + % rows are observations within-subjects and should be independent for valid + % cross-validation. + % + % STATS = xval_regression_multisubject('lasso', pain_scores, data, 'pca', + % 'ndims', 'variable'); + % SEE THE WIKI FOR EXAMPLES, ETC. + % Tor Wager, 3/17/09 + + % Programmers' notes + % June 19, 2011: minor change to LASSO lambda selection when entering + % regparam + % + % July 31, 2011: change to weight map: use whole sample for + % weights/betas + % + % Feb 6, 2012: Minor bug fix in multisubject mode + % + % Nov 10, 2012: Tor Wager: Updated name of lasso to lasso_rocha.m for + % matlab compatibilty. Forced double-format for vars to avoid + % single-format problems. + + % Set defaults + % ----------------------------------------------------------- + nested_setdefaults(); + docenterrowsX; dosave; + + % Variable dimensions: retain max possible for each subject + % If choose ndims is selected, then pick the number here + % Based on size of input data only (not optimized; full dims) + % ----------------------------------------------------------- + nested_choose_ndims(); + + [STATS.INPUTS.pcsquash, STATS.INPUTS.num_dims, STATS.INPUTS.Y, STATS.INPUTS.holdout_method] = deal(pcsquash, num_dims, Y, holdout_method); + %STATS.INPUTS.X = X; + + %include = 1:N; % which subjects to include + + for s = 1:N + % =========================================== + % DATASET (SUBJECT) LOOP + % This function will run separately for each dataset ("subject" in a + % multi-level analysis) in the input cell arrays + % With one dataset, it runs cross-validation for that dataset only + % =========================================== + + nested_prepdata(); % remove NaNs and initialize fit variable (output) + nanvox; % we will need to pass this into another inline later + + % Return holdout_set{} defining folds and holdout set for each fold + holdout_set = nested_select_holdout_set(); + STATS.INPUTS.holdout_set{s} = holdout_set; + + checkrowdependence(X{s}, verbose || verboseL); + % rows are assumed to be independent; if they are not, can get + % perfect cross-validated predictions of even random outcomes + + % -------------------------------------------------------------------------------- + % Cross-validation folds + % -------------------------------------------------------------------------------- + + % THIS COULD BE PARFOR + for wh_fold = 1:length(holdout_set) %holdout_set = 1:length(Y{s}) + + if verbose || verboseL, fprintf('\b\b\b%3.0f', wh_fold); end + + % Parse into training and test data for this fold + % Defines: train_y (outcome) and train_dat (predictors), + % test_dat. test outcome not saved. + % covariates are added to training data if entered. + nested_select_training_test_data(); + + % Parameter optimization by inner cross-validation + % Inner cross-validation could take a long time + % -------------------------------------------------------------------------------- + + if dochoose_ndims + % update num_dims(s), update regparams***incorporate this + % into single inner-xval loop + [LASSO, regparams, best_ndims] = inner_xval_lasso(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash); + + end + + if dochoose_regparams + % Replace regparams with optimized regparams + regparams = inner_xval_optimize(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash, verbose); + + STATS.all_reg_hyperparams(wh_fold) = regparams; + end + + % Dim reduction + % -------------------------------------------------------------------------------- + if pcsquash + [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, num_dims(s), doplssquash); + end + + % Fit + % -------------------------------------------------------------------------------- + % subjbetas{s} is a list of voxel weights for one subject, typically + nvox = size(X{s}, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! +% ncovs = size(train_covs, 2); % needed to get voxel weights only +% [subjbetas{s}, STATS.vox_weights(:, wh_fold)] = do_fit(fit_method, train_y, [train_covs train_dat], pcsquash, v, nvox, regparams, ncovs); + + [subjbetas{s}, STATS.vox_weights(:, wh_fold)] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams); + + switch fit_method + case {'logistic', 'logistictrain'} + eta = [1 test_dat] * subjbetas{s}; + fit(holdout_set{wh_fold}, 1) = 1 ./ (1 + exp(-eta)); + + otherwise + % pred for the left-out observation + if length(subjbetas{s}) == 1 + % this is the 'intercept-only' model, where we have + % information only about the mean (cross-validated) + fit(holdout_set{wh_fold}, 1) = 1 * subjbetas{s}; + else + %fit(holdout_set{wh_fold}, 1) = [ones(size(test_dat, 1), 1) test_covs test_dat] * subjbetas{s}; + fit(holdout_set{wh_fold}, 1) = [ones(size(test_dat, 1), 1) test_dat] * subjbetas{s}; + end + end + + % plot apparent fit +% if verbose +% if wh_fold ==1, create_figure('Intercepts'); end +% create_figure('Training data', 1, 2); plot([ones(size(train_dat, 1), 1) train_dat] * subjbetas{s}, train_y, 'ko', 'MarkerFaceColor', [.5 .5 .5]); +% hold on; plot(fit(holdout_set{wh_fold}, 1), Y{s}(holdout_set{wh_fold}), 'ko', 'MarkerFaceColor', 'r', 'MarkerSize', 8); +% create_figure('Intercepts', 1, 1, 1); hold on; plot(holdout_set{wh_fold}, subjbetas{s}(1), 'ks', 'MarkerFaceColor', 'k'); title('Intercept'); drawnow +% end + +% Diagnostic: Is apparent fit related to covariates? +% train_covs = cov_val{1}; train_covs(holdout_set{wh_fold}, :) = []; +% [b, dev, statsss] = glmfit(train_covs, [ones(size(train_dat, 1), 1) train_dat] * subjbetas{s}); +% glm_table(statsss); + + my_intercepts{s}(wh_fold, 1) = subjbetas{s}(1); + + end % xval + + if verbose || verboseL, toc, end + + % --------------------------------------------------------------------- + % end cross-val loop + % --------------------------------------------------------------------- + + subjfit{s} = fit; + + nested_output_metrics_and_plots(); + + % --------------------------------------------------------------------- + % Get weights and betas across ALL observations + % Tor added 7/31/11 + % --------------------------------------------------------------------- + + if verbose + disp('Getting final variable weights across all observations'); + end + + weight_y = Y{s}; + + if ~isempty(cov_val) + weight_dat = [X{s} cov_val{s}]; + else + weight_dat = X{s}; + end + + % Dim reduction + % -------------------------------------------------------------------------------- + if pcsquash + [v, weight_dat] = do_pcsquash(weight_y, weight_dat, weight_dat, num_dims(s), doplssquash); + end + + % subjbetas are final weights after OLS + [subjbetas{s}, STATS.mean_vox_weights(:, s)] = do_fit(fit_method, weight_y, weight_dat, pcsquash, v, nvox, regparams); + + if pcsquash + % add NaNs back in to preserve voxel order + STATS.mean_vox_weights(:, s) = naninsert(nanvox{s}, STATS.mean_vox_weights(:, s)); + + end + + + + end % dataset/subject loop + + + + STATS.Y_orig = Y_orig; + if ~isempty(cov_val) + STATS.note = 'covariates were added to predictive model'; %, if covs %removed in Y_orig stored here, if covs were entered'; + else + STATS.note = 'no covariates entered'; + end + + STATS.my_intercepts = my_intercepts; + + STATS.devs_from_mean_only_model = devs_from_mean_only_model; + STATS.devs_from_full_model = devs_from_full_model; + STATS.var_null = var_null; + STATS.var_full = var_full; + + % Null model leave-one-out prediction error + % By predicting based on the mean of OTHER subjects, we've increased + % the variance + STATS.pred_err_null = var_null .^ .5; + + STATS.pred_err = pred_err; + STATS.pred_err_descrip = 'Apparent error/loss: Root mean squared deviation from observed outcomes'; + STATS.var_reduction = rsq; + STATS.var_reduction_descrip = 'Percent reduction in outcome variance due to model; negative values indicate added variance.'; + + STATS.subjfit = subjfit; + STATS.subjbetas = subjbetas; + STATS.r_each_subject = rr; + STATS.r_squared = STATS.r_each_subject .^ 2; + STATS.r_each_subject_note = 'r value: correlation between predicted and outcome values'; + STATS.r_each_subject_note2 = 'may not be very interpretable, because null model r = -1.0'; + + if verbose + disp(['Mean correlation is: ' num2str(mean(rr))]) + disp(['Mean proportion of original variance explained is: ' num2str(mean(rsq))]) + disp('The number above is based on reduction of original variance;'); + disp('it can be negative if predictors are not helpful because they add noise, increasing the overall variance!') + disp(' ') + fprintf('Null model pred. error is %3.2f, and full model is %3.2f\n', STATS.pred_err_null(1), STATS.pred_err(1)); + disp(' ') + end + + + + % ======================================================================= + % ======================================================================= + % + %Inline (nested) functions + % + % ======================================================================= + % ======================================================================= + + function nested_setdefaults + pcsquash = 0; + doplssquash = 0; + num_dims = 2; + cov_val = []; + verbose = 1; + verboseL = 0; + lassopath = '/Users/tor/Documents/matlab_code_external/machine_learning/lasso_rocha/lasso'; + doplot = 1; + dochoose_ndims = 0; + regparams = []; + dochoose_regparams = 0; + holdout_method = 'loo'; + docenterrowsX = 0; + dosave = 1; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Data + case {'centerX', 'centerx', 'docenterrowsX'}, docenterrowsX = 1; + + % Dimension reduction + case {'pca', 'pcsquash'}, pcsquash = 1; + case {'pls', 'plssquash'}, pcsquash = 1; doplssquash = 1; + case {'num_dims', 'ndims'}, num_dims = varargin{i+1}; varargin{i + 1} = []; + case 'nested_choose_ndims', dochoose_ndims = 1; + + % Covariates + case {'cov_val', 'covs', 'cov'}, cov_val = varargin{i+1}; + + % Shrinkage/regularization parameters + case {'lassopath', 'ridgek', 'regparams'}, regparams = varargin{i+1}; varargin{i + 1} = []; + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams = 1; + + % Holdout set selection + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + % Output control + case {'noverb', 'noverbose'}, verbose = 0; + case {'lowverb', 'loverb', 'lowverbose'}, verboseL = 1; verbose = 0; + case 'verbose' % default + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if verbose + fprintf('xval_regression_multisubject\nVerbose mode (enter ''lowverbose'' to minimize or ''noverbose'' to turn off verbose output)\n') + end + + N = length(Y); % number of subjects/datasets + [subjbetas, subjfit] = deal(cell(1, N)); + + fit = zeros(length(Y{1})); + + if strcmp(fit_method, 'lasso') + if ~exist('lasso_rocha.m', 'file') + addpath(lassopath) + end + end + + end % defaults setting function + + + % ======================================================================= + + function nested_prepdata + if verbose + fprintf('Dataset %3.0f\n> -------------------------------\n ', s); + elseif verboseL + fprintf('%3.0f ', s); + end + + % remove NaNs + % --------------------------------------------------------------------- + % X variables are done first, on the presumption that variables + % (voxels) are many and observations are few + + nanvox{s} = any(isnan(X{s})); + + if all(nanvox{s}), error('All X variables appeared to have NaN values for one or more observations.'); end + if verbose && sum(nanvox{s}), fprintf('Removed %3.0f X variables with NaNs\n', sum(nanvox{s})); end + + X{s}(:, nanvox{s}) = []; + + if isempty(cov_val) + [wasnan{s}, Y{s}, X{s}] = nanremove(Y{s}, X{s}); + else + [wasnan{s}, Y{s}, X{s}, cov_val{s}] = nanremove(Y{s}, X{s}, cov_val{s}); + end + if all(wasnan{s}), error('All observations appeared to have NaN values for one or more variables.'); end + if verbose && sum(wasnan{s}), fprintf('Removed %3.0f observations with NaNs\n ', sum(wasnan{s})); end + + Y_orig{s} = Y{s}; + + % force double + X{s} = double(X{s}); + Y_orig{s} = double(Y_orig{s}); + + if docenterrowsX + disp('Row-centering requested: Centering each row of predictors'); + X{s} = scale(X{s}', 1)'; + end + + fit = NaN * zeros(size(Y{s})); + tic + + % --------------------------------------------------------------------- + % initialize optional things + v = []; + + if verbose || verboseL, fprintf('Fold %3.0f', 0); end + + end + + % ======================================================================= + function checkrowdependence(X, verbose) + % rows are assumed to be independent; if they are not, can get + % perfect cross-validated predictions of even random outcomes + + [N, k] = size(X); + r = rank(X'); + if r < N && verbose, disp('WARNING!!! ROWS ARE DEPENDENT. CROSS-VALIDATION MAY BE INVALID.'); end +% +% cc = corrcoef(X'); +% cc = cc - eye(size(cc)); +% [maxcorr, wh] = max(cc(:)); +% +% vifs = getvif(dat', 1) +% vifs = getvif(dat', 1); [maxvif, whv] = max(vifs) + + end + + % ======================================================================= + + function nested_choose_ndims() + + if isstr(num_dims) && strcmp(num_dims, 'variable') + if pcsquash == 0, warning('xval:ConflictingInputs', 'PC squash is off, so num_dims input will not be used.'); end + + if verbose, fprintf('Variable number of dimensions: choosing: '); end + + clear num_dims + for i = 1:N + if ~isempty(cov_val) + num_dims(i) = min(size(X{i}, 1) - 2, size(X{i}, 2) + size(cov_val{i}, 2) - 2); + else + num_dims(i) = min(size(X{i}, 1) - 2, size(X{i}, 2) - 2); + end + if verbose, fprintf('%03d ', num_dims(i)); end + end + if verbose, fprintf('\n'); end + end + + if length(num_dims) == 1 && N > 1, num_dims = repmat(num_dims, N, 1); end + + if dochoose_ndims + disp('Inner cross-validation; this could take a long time') + end + + end + + % ======================================================================= + + + function holdout_set = nested_select_holdout_set + % purpose: return holdout_set variable + + nobs_s = length(Y{s}); + + if ~ischar(holdout_method) + % assume holdout method entered is actual holdout set, integer + % list of which obs belong to which test set + u = unique(holdout_method); + + holdout_set = cell(1, length(u)); + for i = 1:length(u) + holdout_set{i} = find(holdout_method == i); + end + + return + end + + switch lower(holdout_method) + case 'loo' + holdout_set = cell(1, nobs_s); + for i = 1:nobs_s, holdout_set{i} = i; end + + case 'l4o_covbalanced' + disp('Selecting holdout sets: Balancing on covariates and outcome, and also trying to ensure that each obs is selected equally often.'); + holdout_proportion = 4 ./ nobs_s; % prop for leave-4-out + nfolds = 40; + if isempty(cov_val) + wh_holdout = xval_select_holdout_set(Y{s}, [], nfolds, holdout_proportion, verbose); + else + wh_holdout = xval_select_holdout_set(Y{s}, cov_val{s}, nfolds, holdout_proportion, verbose); + end + holdout_set = cell(1, nfolds); + for k = 1:nfolds + holdout_set{k} = wh_holdout(:, k); + end + + case 'categorical_covs' + + holdout_set = xval_select_holdout_set_categoricalcovs(cov_val{s}); + + case 'balanced4' + nfolds = 4; + holdout_set = cell(1, nfolds); + [ys, indx] = sort(Y{s}); + for k = 1:nfolds + + holdout_set{k} = indx(k:nfolds:end); + if isempty(holdout_set{k}), error('Holdout set construction error: Dataset too small?'); end + + end + + otherwise error('Unknown holdout method. See help.'); + end + end + + + % ======================================================================= + + function nested_select_training_test_data + % Select training/test data + % ----------------------------- + + train_y = Y{s}; + train_y(holdout_set{wh_fold}) = []; + + if ~isempty(cov_val) + train_dat = [X{s} cov_val{s}]; + else + train_dat = X{s}; + end + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! +% % if ~isempty(cov_val) +% % train_covs = cov_val{s}; +% % test_covs = train_covs(holdout_set{wh_fold}, :); +% % train_covs(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) +% % else +% % train_covs = []; +% % test_covs = []; +% % end + + test_dat = train_dat(holdout_set{wh_fold}, :); + train_dat(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) + + + end % data selection function + + % ======================================================================= + + + function nested_output_metrics_and_plots + + + pefcn = inline('var(y - f) .^ .5', 'y', 'f'); + + if verbose || verboseL + + create_figure('fitplot', 2, 2); + plot(Y_orig{s}, 'o-'); + hold on; plot(fit, 'rx-'); + title('Predicted (red), Actual (blue) over obs'); + + subplot(2, 2, 2); + plot_correlation_samefig(fit, Y_orig{s}); + xlabel('Predicted value'); ylabel('Actual value') + + plot([min(Y_orig{s}) max(Y_orig{s})], [min(Y_orig{s}) max(Y_orig{s})], 'b--', 'LineWidth', 2); + axis tight + + subplot(2, 2, 3); + hold on; plot(1:length(my_intercepts{s}), my_intercepts{s}, 'ks', 'MarkerFaceColor', 'k'); + title('Intercept'); + + subplot(2, 2, 4); + rescalevals = [0:.1:2]; + for j = 1:length(rescalevals) + mype(j) = pefcn(Y_orig{s}, rescalevals(j) .* subjfit{s}); + end + plot(rescalevals, mype, 'k-'); + plot_vertical_line(0); + axis auto; axis tight; + title('Pred err as a function of prediction rescaling'); + drawnow + + if ~isempty(cov_val) + if dosave, diary('xval_regression_multisubject_output.txt'); end + + disp('Test of whether predicted values are related to covariates:'); + [b, dev, statsss] = glmfit(cov_val{1}, subjfit{s}); + glm_table(statsss); + + if dosave, diary off; end + end + + end % verbose + + myr = corrcoef(subjfit{s}, Y_orig{s}); rr(s) = myr(1,2); + pred_err(s, 1) = var(Y_orig{s} - subjfit{s}) .^ .5; % almost the same as (sum((Y_orig{s} - subjfit{s}).^2) ./ (length(Y_orig{s}) - 1) ) .^.5 + + % Null model leave-one-out prediction error + % By predicting based on the mean of OTHER subjects, we've increased + % the variance; we need to adjust + try + + jstat = jackknife(@mean, Y_orig{s}); + + catch + disp('Weird Matlab 2010a jackknife error. Debug me?!!'); + jstat = Y_orig{s}; + end + + devs_from_mean_only_model = Y_orig{s} - jstat; + + devs_from_full_model = Y_orig{s} - subjfit{s}; + + var_null(s) = var(devs_from_mean_only_model); + var_full(s) = var(devs_from_full_model); + + rsq(s, 1) = 1 - var_full(s) ./ var_null(s); + + end + + % ======================================================================= + % ======================================================================= + % ======================================================================= + + +end % main function + + + +% ======================================================================= +% ======================================================================= +% ======================================================================= +% ======================================================================= + +% -------------------------------- +% Sub-functions +% +% -------------------------------- + +% ======================================================================= +% ======================================================================= +% ======================================================================= +% ======================================================================= + +function [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, num_dims, doplssquash) + % PCA or PLS squash, returns train_dat (scores) and v (weight vectors) + % and test_dat, with applied weights + % test_dat is used only to multiply by v to prepare for testing + + % may have to adjust num_dims depending on size of holdout set + num_dims = min(num_dims, size(train_dat, 1) - 1); + + if doplssquash + %[v, train_dat] = plssquash(train_dat, train_y, 'num_dims', num_dims, 'noplot'); + + [T,P,W,Wstar,U,b,C,Bpls, v, Xhat,Yhat,R2X,R2Y] = PLS_nipals(train_dat,train_y, num_dims); + + % was returning singles sometimes... + v = double(v); + + % re-do train_dat by taking PLS weighting, [ones train_dat] * v (Bpls_star) = fit + train_dat = Yhat; + test_dat = [1 test_dat] * v; + + % create_figure('tmp1'); plot(Yhat, train_y, 'ko'); drawnow + % xlabel('Yhat from PLS'); ylabel('Actual yhat'); + + else + [v, scores] = princomp(train_dat, 'econ'); % scores = train_dat, up to scaling factor!! can't use scores from princomp-diff scaling + train_dat = train_dat * v; + v = v(:, 1:num_dims); % eigenvectors + train_dat = train_dat(:, 1:num_dims); % train_dat now becomes the scores + + test_dat = test_dat * v; %(:, 1:num_dims(s)); % get scores for missing test subj + end + +end + +% ================================ +% ================================ + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! + %function [betas, vox_weights, varargout] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams, ncovs) + +function [betas, vox_weights, varargout] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams) + % subjbetas{s} is a list of voxel weights for one subject, typically + % (in a linear model) (nvox+1) x 1, where +1 refers to the intercept parameter. + % in the case of functional mediation, this could be nvox x tpoints. + % + % fits are the predicted outcome (y) values, given the model parameter estimates + % and known information for the left-out observation. In the functional mediation case, + % a different method for producing fits is necessary. + out = []; + if nargin < 7, regparams = []; end + + switch fit_method + case 'ols' + Xs = [ones(size(train_dat, 1), 1) train_dat]; + %betas = pinv(Xs) * train_y; + % this produces DIFFERENT results from standard with v >> N + % better weight stability, but also apparent positive bias (in + % weights?) + + %betas = inv(Xs'*Xs) * Xs' * train_y; + betas = pinv(Xs) * train_y; + + % changed back, Oct 2011, because inv too slow, and pinv should + % be more stable overall. + + case 'ridge' + if isempty(regparams) + shrinkage_param = 0; % ols + else + shrinkage_param = regparams(1); + end + Xs = train_dat; % with scaling off, constant is automatically added + betas = ridge(train_y, Xs, shrinkage_param, 0); + + + case 'robust' + Xs = train_dat; + betas = robustfit(Xs, train_y); + + + case 'bestsubsets' + + if ~pcsquash + error('Best subsets will not work with over-identified model, so use pcsquash'); + end + + Xs = train_dat; + if size(Xs, 2) >= size(Xs, 1)-1 + warning('AIC best subsets will not work well with full redundancy.'); + end + + %Xs = Xs(:, 1:end-3); % remove last PC to prevent over-identification (redundancy). AIC will select all predictors with redundant model. + [wh_predictors, betas] = regress_best_subsets_ga(Xs, train_y); + + + case 'lasso' + % Note: train_dat should not have intercept; included automatically + wh_trace = regparams; % empty, or takes input lambda value + if size(train_dat, 2) == 1, error('LASSO will not work right with only one predictor variable'); end + + if ~exist('lasso_rocha.m', 'file') + error('THE LASSO USED HERE HAS BEEN RENAMED LASSO_ROCHA FOR MATLAB COMPATIBILITY. LASSO_ROCHA.M MUST BE ON YOUR MATLAB PATH.'); + end + + out = lasso_rocha(train_y, train_dat); + + %out = lasso_selection(out, 'bic'); % requires mods to original + %functions; doesn't work for me + + if isempty(out.beta) + % this is the 'intercept-only' model, where we have + % information only about the mean (cross-validated) + % will not work with lasso as implemented though... + wh_trace = []; + betas = []; %out.intercept(wh_trace); + + else + if isempty(wh_trace) % if empty, choose OLS + wh_trace = size(out.beta, 1); + else + % we have a parameter entered - find which element + % corresponds to the input lambda value + notok = all(out.beta == 0, 2); % can return all invalid values for b + diffvec = abs(regparams - out.lambda); + diffvec(notok) = Inf; + wh_trace = find(diffvec == min(diffvec)); + wh_trace = wh_trace(end); + +% create_figure('trace', 2, 1); +% plot(out.lambda, out.beta); +% h = plot_vertical_line(regparams); +% h2 = plot_vertical_line(out.lambda(wh_trace)); +% set(h2, 'Color', 'r'); +% subplot(2, 1, 2); +% plot(out.beta(wh_trace, :)); title('Betas') +% drawnow +% pause(1); + end + + % re-fit with OLS, using only non-zero elements + % see Hastie et al., "Elements" book, p. 92 + + wh_in_model = logical([1 (out.beta(wh_trace, :) ~= 0)]'); + + Xs = [ones(size(train_dat, 1), 1) train_dat(:, wh_in_model(2:end))]; + + betatmp = pinv(Xs) * train_y; + + betas = zeros(size(wh_in_model)); + betas(wh_in_model) = betatmp; + + %betas = [out.intercept(wh_trace) out.beta(wh_trace, :)]'; + end + + case 'logistic' + + betas = glmfit(train_dat, [train_y ones(size(train_y))], 'binomial', 'link', 'logit'); + + % plot apparent + % % eta = [ones(size(train_dat, 1), 1) train_dat] * betas; + % % fit = 1 ./ (1 + exp(-eta)); + % % create_figure('tmp'); + % % plot(train_dat, train_y, 'ko'); + % % vals = [-1.5:.1:1.5]'; + % % etafit = [ones(length(vals), 1) vals] * betas; + % % fitcurve = 1 ./ (1 + exp(-etafit)); + % % plot([-1.5:.1:1.5], fitcurve, 'k'); + % % set(gca, 'XLim', [min(train_dat) max(train_dat)]); + % % etazero = -betas(1) ./ betas(2); % point at which p(lie) in logistic model = .5 + % % han = plot_vertical_line(etazero); + % % set(han, 'LineStyle', ':'); + % % drawnow + + % % % case 'logisticpls' + % % % now done in plssquash... + % % % [T,P,W,Wstar,U,b,C,Bpls,Bpls_star,Xhat,Yhat,R2X,R2Y] = PLS_nipals(train_dat,train_y, 10); + % % % betas was Bpls_star; [1 test_x] * Bpls_star = fit = trial score, + % % % which is X in the logistic model below: + % % % + % % % Xtrain = [ones(size(train_dat, 1), 1) train_dat] * Bpls_star; % this gives us the PLS-optimized features + % % % betas = glmfit(Xtrain, [train_y ones(size(train_y))], 'binomial', 'link', 'logit'); + % % % + % % % fit_eta = betas(1) + betas(2) * ([1 test_data]*Bpls_star) + % % % fit = 1 ./ (1 + exp(-fit_eta)); + + case 'logistictrain' + [models] = classifierLogisticRegression( train_dat, train_y, [] ); + betas = models{3}(:, 1); % intercept seems to be added as first predictor + + otherwise error('Unknown fit method') + end + + if nargout > 2, varargout{1} = out; end + + % Vox weights + % ------------ + switch fit_method + case {'ols', 'ridge', 'robust', 'bestsubsets', 'logistic', 'logistictrain'} + if pcsquash + %vox_weights = v(1:nvox, :) * betas(2+ncovs:end); + vox_weights = v(1:nvox, :) * betas(2:end); + else + vox_weights = betas(2:end); + end + + case 'lasso' + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! + % if pcsquash + % vox_weights = v(1:nvox, :) * out.beta(wh_trace, ncovs+1:end)'; + % else + % vox_weights = out.beta(wh_trace, ncovs+1:end)'; + % end + + % NOTE: tor edited to use re-fit OLS solution in case of + % penalization. + + if pcsquash + vox_weights = v(1:nvox, :) * betas(2:end); % out.beta(wh_trace, :)'; + else + vox_weights = betas(2:end); % out.beta(wh_trace, :)'; + end + + otherwise error('Unknown fit method') + end + + +end % function + +% ================================ +% ================================ + +% % % function [LASSO, wh_trace, best_ndims] = inner_xval_lasso(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash) +% % % +% % % % Inner cross-validation; this could take a long time +% % % % ----------------------------- +% % % +% % % % update num_dims(s), update wh_trace +% % % +% % % n_inner_obs = length(train_y); +% % % nvox = size(train_dat, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) +% % % +% % % best_ndims = size(train_dat, 2); % initialize num_dims value +% % % +% % % % choose dims to test -- either variable, or single value +% % % % -------------------------------------------------------------------- +% % % if pcsquash && dochoose_ndims +% % % dims_to_test = 2:num_dims; % placeholder; select all features +% % % else +% % % % run once, select all features +% % % dims_to_test = num_dims; +% % % end +% % % +% % % % choose lasso shrinkage parameter with inner cross-validation loop +% % % % ------------------------------------------------------------------- +% % % if strcmp(fit_method, 'lasso') +% % % if pcsquash +% % % % 2nd train_dat(1,:)is test dat, but irrelevant here-- we just need dims +% % % [v, X2, tmp_train_dat] = do_pcsquash(train_y, train_dat, train_dat(1,:), dims_to_test(end), doplssquash); +% % % clear v tmp_train_dat +% % % end +% % % out = lasso(train_y, X2); % to get penalty values +% % % penalty_values = out.penalty; %linspace(0, max(out.penalty), 10); % resolution: 10 +% % % +% % % fit = zeros(n_inner_obs, length(dims_to_test), length(penalty_values)); +% % % +% % % end +% % % +% % % % Inner cross-validation +% % % % -------------------------- +% % % for ii = 1:n_inner_obs +% % % +% % % % fprintf('\b\b\b%3.0f', holdout_set); +% % % +% % % % Select data +% % % Y2 = train_y; +% % % Y2(ii) = []; +% % % X2 = train_dat; +% % % X2(ii, :) = []; +% % % test_dat = train_dat(ii, :); +% % % +% % % % Dim reduction: with max number +% % % % We will use selected numbers of these +% % % % Must re-do PCA in inner loop to avoid bias +% % % % ----------------------------- +% % % if pcsquash +% % % [v, X2, test_dat] = do_pcsquash(Y2, X2, test_dat, dims_to_test(end), doplssquash); +% % % end +% % % +% % % for jj = 1:length(dims_to_test) +% % % % Dimension selection loop +% % % +% % % % Fit +% % % % ----------------------------- +% % % % we may also have a lasso shrinkage (penalty) parameter to choose +% % % % this gives us the whole lasso trace, though +% % % wh_trace = []; +% % % [subjbetas{ii,jj}, tmp_vox_weights, out2] = do_fit(fit_method, Y2, ... +% % % X2(:, 1:dims_to_test(jj)), pcsquash, v(:, 1:dims_to_test(jj)), ... +% % % nvox, wh_trace); +% % % +% % % clear tmp_vox_weights +% % % +% % % % pred for the left-out observation +% % % +% % % +% % % if strcmp(fit_method, 'lasso') +% % % % get trace from out struct +% % % % penalty values vary across validation loop iterations +% % % % fit = fit values for each possible choice of penalty +% % % % test_dat is component scores if pcsquash, otherwise dims to +% % % % test is all +% % % fit(ii, jj, 1) = out2.intercept(end)' + test_dat(1:dims_to_test(jj)) * out2.beta(end, :)'; +% % % +% % % % lfit: lasso fit for each trace +% % % lfit = out2.intercept' + test_dat(1:dims_to_test(jj)) * out2.beta'; +% % % % interpolate to standard trace values +% % % lfit = interp1(out2.penalty, lfit, penalty_values); +% % % +% % % fit(ii, jj, 1:length(penalty_values)) = lfit; +% % % % this should go on 3rd dim of fit +% % % +% % % % trials x penalty choices +% % % % fit_inner_xval{jj}(ii, :) = interp1(out2.penalty, fitiijj, penalty_values); +% % % % err_inner_xval{jj}(ii, :) = train_y(ii) - fit_inner_xval{jj}(ii, :); +% % % else +% % % +% % % % this is for the OLS solution (max trace value) +% % % if length(subjbetas{ii,jj}) == 1 +% % % % this is the 'intercept-only' model +% % % fit(ii, jj, 1) = 1 * subjbetas{ii,jj}; +% % % else +% % % fit(ii, jj, 1) = [1 test_dat] * subjbetas{ii,jj}; +% % % end +% % % end +% % % +% % % +% % % +% % % end % Dims loop +% % % end % ii inner xval loop +% % % +% % % for j = 1:length(dims_to_test) +% % % +% % % for k = 1:size(fit, 3) +% % % vec = squeeze(fit(:, j, k)); +% % % cc = corrcoef(vec, train_y); +% % % r(j, k) = cc(1,2); +% % % pe(j, k) = sqrt(sum((train_y - vec) .^2)); +% % % end +% % % +% % % end +% % % +% % % [wh_ndims, wh_penalty] = find(pe == min(pe(:))); +% % % wh_ndims = wh_ndims(1); +% % % wh_penalty = wh_penalty(1); +% % % +% % % best_penalty = penalty_values(wh_penalty); +% % % best_dims = dims_to_test(wh_ndims); +% % % best_pe = pe(wh_ndims, wh_penalty); +% % % +% % % if doplot +% % % create_figure('Error by dims and penalty values'); +% % % +% % % [Xf, Yf] = meshgrid(penalty_values, dims_to_test); +% % % surf(Xf, Yf, pe) +% % % xlabel('Penalty values') +% % % ylabel('Dimensions') +% % % view(135, 30); +% % % hold on; +% % % plot3(best_penalty, best_dims, best_pe, 'ko','MarkerFaceColor', 'r', 'MarkerSize', 8); +% % % +% % % end +% % % +% % % LASSO = struct('best_penalty', best_penalty, 'best_dims', best_dims, 'best_pe', best_pe, ... +% % % 'wh_ndims', wh_ndims, 'wh_penalty', wh_penalty); +% % % +% % % end % function + + +% -------------------------------------------------- +% Inner cross-val: Choose best ridge param +% -------------------------------------------------- + +function best_paramval = inner_xval_optimize(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash, verbose) + + if verbose + fprintf('\nOptimizing params using inner x-val'); + end + + switch fit_method + case {'ridge'} + bounds = [0, 10*size(train_dat, 2)]; + + case {'lasso'} + bounds = [0, 1]; % bounds on lambda + + otherwise, error('xval_reg:NotImplemented',sprintf('Fit method ''%s'' not compatible with dochoose_regparams', fit_method)); + end + + if pcsquash, pcastr = 'pcsquash'; else pcastr = 'noverbose'; end % set PCA string + if pcsquash && doplssquash, plsstr = 'pls'; else plsstr = 'noverbose'; end % set PLS option (not tested) + + t0 = clock; + + % paramval could be a ridge or LASSO shrinkage value + fit_fcn = @(paramval) xval_given_param(paramval, fit_method, train_y, train_dat, pcastr, plsstr, num_dims); + + best_paramval = fminbnd(fit_fcn, bounds(1), bounds(2)); + + if verbose + fprintf(': Done: %3.2f sec. ', etime(clock, t0)); + fprintf('Best regularization parameter value = %3.2f\n', best_paramval) + end + +end % inner_xval_optimize_ridge + +% PE-generating function for ridge + +function pe = xval_given_param(paramval, fit_method, Y, data, pcastr, plsstr, num_dims) + % paramval could be a ridge or LASSO shrinkage value + STATS = xval_regression_multisubject(fit_method, {Y}, {data}, 'regparams', paramval, 'noverbose', 'holdout_method', 'balanced4', pcastr, plsstr, 'num_dims', num_dims); + pe = STATS.pred_err; +end diff --git a/Statistics_tools/xval_regression_multisubject_bootstrapweightmap.m b/Statistics_tools/xval_regression_multisubject_bootstrapweightmap.m new file mode 100644 index 00000000..3f177009 --- /dev/null +++ b/Statistics_tools/xval_regression_multisubject_bootstrapweightmap.m @@ -0,0 +1,962 @@ +function STATS = xval_regression_multisubject_bootstrapweightmap(fit_method, Y, X, volInfo, varargin) + % STATS = xval_regression_multisubject_bootstrapweightmap(fit_method, Y, X, volInfo, varargin) + % + % CROSS-VALIDATED (JACKKNIFE) REGRESSION + % Leave-one observation out, predict outcomes for each missing holdout_set. + % THIS IS LIKE XVAL_REGRESSION_MULTISUBJECT, but IS SPECIFIC FOR + % CREATING P-VALUES FOR BOOSTRAPPED VOXEL WEIGHTS. So it requires an + % additional input, volInfo (see below). + % + % Y = outcome, holdout_set x 1 + % X = predictors, holdout_set x variables + % fit_method = 'lasso' 'ols' 'ridge' or 'robust' + % optional inputs: + % 'pca', PCA data reduction first + % 'ndims', dims to save from PCA + % 'variable', retain max dims for each subject + % case {'cov', 'covs'}, cov = varargin{i+1}; NOTE:covariates are added to training data if entered. + % {'lassopath', 'ridgek', 'regparams'}, regparams = varargin{i+1}; + % {'noverb', 'noverbose'}, verbose = 0; + % {'lowverb', 'loverb', 'lowverbose'}, verboseL = 1; verbose = 0; + % 'nested_choose_ndims', dochoose_ndims = 1; + % + % volInfo: volume info structure with locations of voxels in X + % as created by iimg_read_img + % + % Single-level analysis: enter a single cell with Y and X data. + % rows are observations. (e.g., subjects) + % + % Multi-level analysis: enter a cell per subject with Y and X data. + % rows are observations within-subjects and should be independent for valid + % cross-validation. + % + % STATS = xval_regression_multisubject('lasso', pain_scores, data, 'pca', + % 'ndims', 'variable'); + % SEE THE WIKI FOR EXAMPLES, ETC. + % See also xval_lasso_brain.m + % See also xval_regression_bootstrapweightmap_brainplots(STATS) + % + % Tor Wager, 3/17/09 + + % Set defaults + % ----------------------------------------------------------- + nested_setdefaults(); + verbose; + + % Variable dimensions: retain max possible for each subject + % If choose ndims is selected, then pick the number here + % Based on size of input data only (not optimized; full dims) + % ----------------------------------------------------------- + %nested_choose_ndims(); + + [STATS.INPUTS.pcsquash, STATS.INPUTS.num_dims, STATS.INPUTS.Y, STATS.INPUTS.holdout_method] = deal(pcsquash, num_dims, Y, holdout_method); + %STATS.INPUTS.X = X; + + %include = 1:N; % which subjects to include + + for s = 1:N + % =========================================== + % DATASET (SUBJECT) LOOP + % This function will run separately for each dataset ("subject" in a + % multi-level analysis) in the input cell arrays + % With one dataset, it runs cross-validation for that dataset only + % =========================================== + + nested_prepdata(); % remove NaNs and initialize fit variable (output) + nanvox; % we will need to pass this into another inline later + + % Return holdout_set{} defining folds and holdout set for each fold +% holdout_set = nested_select_holdout_set(); +% STATS.INPUTS.holdout_set{s} = holdout_set; + + train_y = Y{s}; % all data, here -- not leaving out holdout set + + if ~isempty(cov_val) + train_dat = [X{s} cov_val{s}]; + else + train_dat = [X{s}]; + end + + test_dat = train_dat; % no holdout; this is to get voxel weights + + % Dim reduction + % -------------------------------------------------------------------------------- + if pcsquash + [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, num_dims(s), doplssquash); + %STATS.VOXWEIGHTS.eigenvectors = v; + end + + % Fit + % -------------------------------------------------------------------------------- + % subjbetas{s} is a list of voxel weights for one subject, typically + nvox = size(X{s}, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) + [STATS.VOXWEIGHTS.betas{s}, STATS.VOXWEIGHTS.vox_weights{s}] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams); + + % -------------------------------------------------------------------------------- + % -------------------------------------------------------------------------------- + + % Bootstrap the above + + % -------------------------------------------------------------------------------- + % -------------------------------------------------------------------------------- + + nbootsamples = 5000; % 1000 does not give p < .001. 2001+ does. + + bootsam = setup_boot_samples(train_y, nbootsamples); + + STATS.INPUTS.nbootsamples = nbootsamples; + STATS.VOXWEIGHTS.bootbetas = cell(1, nbootsamples); + STATS.VOXWEIGHTS.boot_vox_weights = zeros(length(STATS.VOXWEIGHTS.vox_weights{s}), nbootsamples, 'single'); + STATS.VOXWEIGHTS.volInfo = volInfo; + + fprintf('Getting p-values for voxel weights: Running %3.0f bootstrap samples: 000', nbootsamples); + + poolstate = matlabpool('size'); + if poolstate + fprintf('Parallel mode\n'); + nested_bootstrap_loop_parallel + else + nested_bootstrap_loop + end + + testvalue = 0; + % % i = 1 + bstat = STATS.VOXWEIGHTS.boot_vox_weights'; %(i, :) + + pct_lowertail = sum(bstat < testvalue) ./ nbootsamples; + pct_uppertail = sum(bstat > testvalue) ./ nbootsamples; + + % lower tail is smaller for positive effect. is_lowertail == 1 effects + % should be positive. + is_lowertail = pct_lowertail < pct_uppertail; + + % make sure pct is not 1 or 0 due to limited bootstrap samples + pct_lowertail = max(pct_lowertail, 1 ./ nbootsamples); + pct_uppertail = min(pct_uppertail, 1 - 1./nbootsamples); + + pct_uppertail = max(pct_uppertail, 1 ./ nbootsamples); + pct_lowertail = min(pct_lowertail, 1 - 1./nbootsamples); + + z_lowertail = norminv(pct_lowertail); % neg z for pos effect, and vice versa + z_uppertail = norminv(pct_uppertail); + + z = z_uppertail; + z(is_lowertail) = -z_lowertail(is_lowertail); % sign of this is irrelevant because we take min (p, 1-p) below + + p = normcdf(z); + p = [p; 1 - p]; + p = 2 .* min(p); + + % pvals will be min for out-of-mask voxels + p(STATS.VOXWEIGHTS.vox_weights{s}' == 0) = 1; + p_for_fdr = p(STATS.VOXWEIGHTS.vox_weights{s}' ~= 0); + + STATS.VOXWEIGHTS.pval_2tail = p; + STATS.VOXWEIGHTS.pval_2tail_descrip = 'Bootstrapped p-values for voxel weights'; + + STATS.VOXWEIGHTS.pthr_fdr = FDR(p_for_fdr, .05); + if isempty(STATS.VOXWEIGHTS.pthr_fdr), STATS.VOXWEIGHTS.pthr_fdr = 0; end + STATS.VOXWEIGHTS.sigfdr = p < STATS.VOXWEIGHTS.pthr_fdr; + + % ****edit this to make output images + nested_output_metrics_and_plots(); + + end % dataset/subject loop + + + STATS.Y_orig = Y_orig; + if ~isempty(cov_val) + STATS.note = 'covariates were added to predictive model'; %, if covs %removed in Y_orig stored here, if covs were entered'; + else + STATS.note = 'no covariates entered'; + end +% % +% % STATS.devs_from_mean_only_model = devs_from_mean_only_model; +% % STATS.devs_from_full_model = devs_from_full_model; +% % STATS.var_null = var_null; +% % STATS.var_full = var_full; +% % +% % % Null model leave-one-out prediction error +% % % By predicting based on the mean of OTHER subjects, we've increased +% % % the variance +% % STATS.pred_err_null = var_null .^ .5; +% % +% % STATS.pred_err = pred_err; +% % STATS.pred_err_descrip = 'Apparent error/loss: Root mean squared deviation from observed outcomes'; +% % STATS.var_reduction = rsq; +% % STATS.var_reduction_descrip = 'Percent reduction in outcome variance due to model; negative values indicate added variance.'; +% % +% % STATS.subjfit = subjfit; +% % STATS.subjbetas = subjbetas; +% % STATS.r_each_subject = rr; +% % STATS.r_squared = STATS.r_each_subject .^ 2; +% % STATS.r_each_subject_note = 'r value: correlation between predicted and outcome values'; +% % STATS.r_each_subject_note2 = 'may not be very interpretable, because null model r = -1.0'; +% % +% % if verbose +% % disp(['Mean correlation is: ' num2str(mean(rr))]) +% % disp(['Mean proportion of original variance explained is: ' num2str(mean(rsq))]) +% % disp('The number above is based on reduction of original variance;'); +% % disp('it can be negative if predictors are not helpful because they add noise, increasing the overall variance!') +% % disp(' ') +% % fprintf('Null model pred. error is %3.2f, and full model is %3.2f\n', STATS.pred_err_null(1), STATS.pred_err(1)); +% % disp(' ') +% % end + + + + % ======================================================================= + % ======================================================================= + % + %Inline (nested) functions + % + % ======================================================================= + % ======================================================================= + + function nested_setdefaults + pcsquash = 0; + doplssquash = 0; + num_dims = 2; + cov_val = []; + verbose = 1; + verboseL = 0; + lassopath = '/Users/tor/Documents/matlab_code_external/machine_learning/lasso_rocha/lasso'; + doplot = 1; + dochoose_ndims = 0; + regparams = []; + dochoose_regparams = 0; + holdout_method = 'loo'; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Dimension reduction + case {'pca', 'pcsquash'}, pcsquash = 1; + case {'pls', 'plssquash'}, pcsquash = 1; doplssquash = 1; + case {'num_dims', 'ndims'}, num_dims = varargin{i+1}; varargin{i + 1} = []; + case 'nested_choose_ndims', dochoose_ndims = 1; + + % Covariates + case {'cov_val', 'covs', 'cov'}, cov_val = varargin{i+1}; + + % Shrinkage/regularization parameters + case {'lassopath', 'ridgek', 'regparams'}, regparams = varargin{i+1}; varargin{i + 1} = []; + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams = 1; + + % Holdout set selection + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + % Output control + case {'noverb', 'noverbose'}, verbose = 0; + case {'lowverb', 'loverb', 'lowverbose'}, verboseL = 1; verbose = 0; + case 'verbose' % default + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if verbose + fprintf('xval_regression_multisubject\nVerbose mode (enter ''lowverbose'' to minimize or ''noverbose'' to turn off verbose output)\n') + end + + N = length(Y); % number of subjects/datasets + [subjbetas, subjfit] = deal(cell(1, N)); + + fit = zeros(length(Y{1})); + + if strcmp(fit_method, 'lasso'), addpath(lassopath), end + + end % defaults setting function + + + % ======================================================================= + + function nested_prepdata + if verbose + fprintf('Dataset %3.0f\n> -------------------------------\n ', s); + elseif verboseL + fprintf('%3.0f ', s); + end + + % remove NaNs + % --------------------------------------------------------------------- + % X variables are done first, on the presumption that variables + % (voxels) are many and observations are few + nanvox{s} = any(isnan(X{s})); + if all(nanvox{s}), error('All X variables appeared to have NaN values for one or more observations.'); end + if verbose && sum(nanvox{s}), fprintf('Removed %3.0f X variables with NaNs\n', sum(nanvox{s})); end + X{s}(:, nanvox{s}) = []; + + if isempty(cov_val) + [wasnan{s}, Y{s}, X{s}] = nanremove(Y{s}, X{s}); + else + [wasnan{s}, Y{s}, X{s}, cov_val{s}] = nanremove(Y{s}, X{s}, cov_val{s}); + end + if all(wasnan{s}), error('All observations appeared to have NaN values for one or more variables.'); end + if verbose && sum(wasnan{s}), fprintf('Removed %3.0f observations with NaNs\n ', sum(wasnan{s})); end + + Y_orig{s} = Y{s}; + + fit = NaN * zeros(size(Y{s})); + tic + + % --------------------------------------------------------------------- + % initialize optional things + v = []; + + if verbose || verboseL, fprintf('Fold %3.0f', 0); end + + end + + + % ======================================================================= + + function nested_choose_ndims() + + if isstr(num_dims) && strcmp(num_dims, 'variable') + if pcsquash == 0, warning('xval:ConflictingInputs', 'PC squash is off, so num_dims input will not be used.'); end + + if verbose, fprintf('Variable number of dimensions: choosing: '); end + + clear num_dims + for i = 1:N + if ~isempty(cov_val) + num_dims(i) = min(size(X{i}, 1) - 2, size(X{i}, 2) + size(cov_val{i}, 2) - 2); + else + num_dims(i) = min(size(X{i}, 1) - 2, size(X{i}, 2) - 2); + end + if verbose, fprintf('%03d ', num_dims(i)); end + end + if verbose, fprintf('\n'); end + end + + if length(num_dims) == 1 && N > 1, num_dims = repmat(num_dims, N, 1); end + + if dochoose_ndims + disp('Inner cross-validation; this could take a long time') + end + + end + + % ======================================================================= +% % % +% % % function holdout_set = nested_select_holdout_set +% % % % purpose: return holdout_set variable +% % % +% % % nobs_s = length(Y{s}); +% % % +% % % switch lower(holdout_method) +% % % case 'loo' +% % % holdout_set = cell(1, nobs_s); +% % % for i = 1:nobs_s, holdout_set{i} = i; end +% % % +% % % case 'l2o' +% % % +% % % case 'balanced4' +% % % nfolds = 4; +% % % holdout_set = cell(1, nfolds); +% % % [ys, indx] = sort(Y{s}); +% % % for k = 1:nfolds +% % % +% % % holdout_set{k} = indx(k:nfolds:end); +% % % if isempty(holdout_set{k}), error('Holdout set construction error: Dataset too small?'); end +% % % +% % % end +% % % +% % % otherwise error('Unknown holdout method. See help.'); +% % % end +% % % end +% % % +% % % +% % % % ======================================================================= +% % % +% % % function nested_select_training_test_data +% % % % Select training/test data +% % % % ----------------------------- +% % % train_y = Y{s}; +% % % train_y(holdout_set{wh_fold}) = []; +% % % +% % % if ~isempty(cov_val) +% % % train_dat = [X{s} cov_val{s}]; +% % % else +% % % train_dat = [X{s}]; +% % % end +% % % +% % % test_dat = train_dat(holdout_set{wh_fold}, :); +% % % train_dat(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) +% % % +% % % end % data selection function + + % ======================================================================= + + function nested_bootstrap_loop + + tic + + for booti = 1:nbootsamples + + fprintf('\b\b\b%3.0f', booti); + + train_y = Y{s}(bootsam(:, booti)); % all data, here -- not leaving out holdout set + + if ~isempty(cov_val) + train_dat = [X{s}(bootsam(:, booti), :) cov_val{s}(bootsam(:, booti), :)]; + else + train_dat = [X{s}(bootsam(:, booti), :)]; + end + + test_dat = train_dat; % no holdout; this is to get voxel weights + + if pcsquash + my_ndims = min(length(unique(bootsam(:, booti))) - 2, num_dims(s)); + [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, my_ndims, doplssquash); + %STATS.VOXWEIGHTS.eigenvectors = v; + end + + nvox = size(X{s}, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) + [STATS.VOXWEIGHTS.bootbetas{booti}, STATS.VOXWEIGHTS.boot_vox_weights(:, booti)] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams); + + end % end bootstrap + + fprintf('\n') + toc + + end + % ======================================================================= + function nested_bootstrap_loop_parallel + + % slice vars +% clear train_y train_dat test_dat +% for booti = 1:nbootsamples +% +% +% +% % do this here just so we can clear bootsam +% if pcsquash +% my_ndims{booti} = min(length(unique(bootsam(:, booti))) - 2, num_dims(s)); +% end +% +% +% end + +% clear bootsam +% test_dat = train_dat; % no holdout; this is to get voxel weights + nvox = size(X{s}, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) + + [train_dat test_dat train_y v my_ndims] = deal(cell(1, nbootsamples)); + + % PARALLEL BOOTSTRAP LOOP + tic + fprintf('Done: ') + parfor booti = 1:nbootsamples + + if mod(booti, 10) == 0, fprintf(' %3.0f', booti); end + drawnow + + train_y{booti} = Y{s}(bootsam(:, booti)); % all data, here -- not leaving out holdout set + + if ~isempty(cov_val) + train_dat{booti} = [X{s}(bootsam(:, booti), :) cov_val{s}(bootsam(:, booti), :)]; + else + train_dat{booti} = [X{s}(bootsam(:, booti), :)]; + end + + test_dat{booti} = train_dat{booti}; % no holdout; this is to get voxel weights + + if pcsquash + my_ndims{booti} = min(length(unique(bootsam(:, booti))) - 2, num_dims(s)); + [v{booti}, train_dat{booti}, test_dat{booti}] = do_pcsquash(train_y{booti}, train_dat{booti}, test_dat{booti}, my_ndims{booti}, doplssquash); + end + + [bootbetas{booti}, boot_vox_weights(:, booti)] = do_fit(fit_method, train_y{booti}, train_dat{booti}, pcsquash, v{booti}, nvox, regparams); + + train_y{booti} = []; + train_dat{booti} = []; + test_dat{booti} = []; + v{booti} = []; + + end % end bootstrap + + fprintf('\n') + toc + + STATS.VOXWEIGHTS.bootbetas = bootbetas; + STATS.VOXWEIGHTS.boot_vox_weights = boot_vox_weights; + + end + + + + % ======================================================================= + + + function nested_output_metrics_and_plots + + % Print summary table + % ---------------------------------------------- + diary bootstrap_weightmap_output.txt + + thresh = [Inf .001 .002 .005 .01 .05]; + sigvox = sum(STATS.VOXWEIGHTS.sigfdr); + threshnames = {'FDR' '001' '002' '005' '01' '05'}; + + thresh(1) = STATS.VOXWEIGHTS.pthr_fdr; + + for ii = 2:length(thresh) + sigvox(ii) = sum(STATS.VOXWEIGHTS.pval_2tail < thresh(ii)); + end + + fprintf('In-Mask Space is %3.0f\n', volInfo.n_inmask) + fprintf('FDR threshold = %3.6f, sig vox = %3.0f\n', STATS.VOXWEIGHTS.pthr_fdr, sum(STATS.VOXWEIGHTS.sigfdr)) + for ii = 2:length(thresh) + fprintf('p < %3.4f, sig vox = %3.0f\n', thresh(ii), sigvox(ii)) + end + + diary off + + % Set Colors + % ---------------------------------------------- + reversecolors = 0; + if reversecolors, revstr = '_revcolors'; else revstr = ''; end + + if reversecolors + negcm = colormap_tor([.8 0 0], [1 1 0], [1 1 0]); + poscm = colormap_tor([.4 .8 .6], [0 0 1], [0 0 1]); + else + poscm = colormap_tor([.8 0 0], [1 1 0], [1 1 0]); + negcm = colormap_tor([.4 .8 .6], [0 0 1], [0 0 1]); + end + + % Save weight image + % ---------------------------------------------- + w = STATS.VOXWEIGHTS.vox_weights{1}; + iimg_reconstruct_vols(w, volInfo, 'outname', 'voxelweights.img'); + + iimg_reconstruct_vols(p', volInfo, 'outname', 'voxelweight_pvalues.img'); + + % ***save legend, summarize pos and neg weights*** + + % ------------------------------------------------------------- + % Save weight images and display montages for each threshold + % ------------------------------------------------------------- + + % NEW MONTAGE SETUP + % ------------------------------- + obj = fmridisplay; % Create object with canonical underlay + obj = montage(obj, 'onerow'); % Show axial montage of underlay + %obj = montage(obj, 'axial', 'slice_range', [-40 55], 'onerow', 'spacing', 8); + enlarge_axes(gcf, .95); + obj = montage(obj, 'saggital', 'slice_range', [-6 6], 'onerow'); + + for i = 1:length(thresh) + + if ~sigvox(i), continue, end % skip if no vox + + outbase = sprintf('voxelweights_p_%s%s', threshnames{i}, revstr); + + montagebase = sprintf('thresh_p_%s_%3.0fvox%s', threshnames{i}, sigvox(i), revstr); + + disp(outbase) + + % Save weight image + % ---------------------------------------------- + outname = [outbase '.img']; + wthr = w; + wthr(p > thresh(i)) = 0; + iimg_reconstruct_vols(wthr, volInfo, 'outname', outname); + + % ***Note: this can fail if no variability in Z values, i.e. if + % few booot samples are used. should fix...*** + + % NEW MONTAGE blobs + % ------------------------------- + obj = removeblobs(obj); + + zthr = sign(w) .* norminv(1 - p'); + zthr(p > thresh(i)) = 0; +% wpos = wthr; wpos(wpos < 0) = 0; +% wneg = wthr; wneg(wneg > 0) = 0; + zpos = zthr; zpos(zpos < 0) = 0; + zneg = zthr; zneg(zneg > 0) = 0; + clpos = iimg_indx2clusters(zpos, volInfo); + clneg = iimg_indx2clusters(zneg, volInfo); + clpos(cat(1, clpos.numVox) < 3) = []; + clneg(cat(1, clneg.numVox) < 3) = []; + + obj = addblobs(obj, clpos, 'maxcolor', [1 1 0], 'mincolor', [1 .3 0]); + obj = addblobs(obj, clneg, 'maxcolor', [0 0 1], 'mincolor', [.7 0 1]); + + mb = montagebase; mb(mb == '_') = ' '; + axes(obj.montage{1}.axis_handles(2)) + title([mb ' k=3 contig'], 'FontSize', 18); + + obj = legend(obj); + + scn_export_papersetup(500); saveas(gcf, [montagebase '_new_axial.png']); + + diary bootstrap_weightmap_output.txt + fprintf('\n===============================\n') + + disp([mb 'k=3 contig']) + fprintf('===============================\n') + fprintf('POSITIVE EFFECTS\n_______________________________\n') + + cluster_table(clpos, 0 , 0); + + fprintf('\nNEGATIVE EFFECTS\n_______________________________\n') + + cluster_table(clneg, 0 , 0); +% % +% % % Orthviews +% % % ---------------------------------------------- +% % cl = iimg_indx2clusters(wthr, volInfo); +% % cluster_orthviews(cl) +% % spm_orthviews_hotcool_colormap(w, [prctile(w, 50)]); +% % cm = get(gcf, 'Colormap'); +% % +% % % adjust color map +% % %isgray = all(cm - repmat(mean(cm, 2), 1, 3) < .01, 2) +% % cm2 = [cm(1:64, :); negcm(end:-2:1, :); poscm(1:2:end, :)]; +% % colormap(cm2) +% % +% % % Montages from orthviews +% % % ---------------------------------------------- +% % overlay = which('SPM8_colin27T1_seg.img'); +% % +% % slices_fig_h = cluster_orthviews_montage(6, 'axial', overlay, 0, 'onerow'); +% % %colormap(cm); h = findobj(gcf, 'Type', 'Text'); delete(h) +% % scn_export_papersetup(500); saveas(gcf, [montagebase '_axial.png']); +% % +% % slices_fig_h = cluster_orthviews_montage(6, 'sagittal', overlay, 0, 'onerow'); +% % %colormap(cm); h = findobj(gcf, 'Type', 'Text'); delete(h) +% % scn_export_papersetup(500); saveas(gcf, [montagebase '_sagittal.png']); +% % +% % slices_fig_h = cluster_orthviews_montage(6, 'coronal', overlay, 0, 'onerow'); +% % %colormap(cm); h = findobj(gcf, 'Type', 'Text'); delete(h) +% % scn_export_papersetup(500); saveas(gcf, [montagebase '_coronal.png']); +% % + + end + + % ----------------------------------------------- + % Figure - surface, varying threshold + % ----------------------------------------------- + + + for i = 1:length(thresh) + + if ~sigvox(i), continue, end % skip if no vox + + outname = sprintf('thresh_p_%s_%3.0fvox%s', threshnames{i}, sigvox(i), revstr); + + disp(outname) + + %outbase = 'voxelweights_p002'; + %outname = [outbase '.img']; + + wthr = w; wthr(p > thresh(i)+eps) = 0; + cl = iimg_indx2clusters(wthr, volInfo); + + create_figure('Brain_Surface', 2, 2); + + + han1 = addbrain('hires'); + set(han1, 'FaceAlpha', 1); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han1); + + %replace with 'hires' and re-run to do whole cortical surface + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 2); + han = addbrain('hires right'); set(han, 'FaceAlpha', 1); + + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 3); + han = addbrain('hires left'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 4); + han = addbrain('limbic'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + set(han(end), 'FaceAlpha', .2) + han2 = addbrain('brainstem'); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + view(135, 15) + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 1) + han = findobj(gca,'Type','patch'); set(han, 'FaceAlpha',1) + axis image + + scn_export_papersetup(600); saveas(gcf, [outname '_Surface1'], 'png'); + subplot(2, 2, 1); + view(135, 20); lightRestoreSingle; + saveas(gcf, [outname '_Surface2'], 'png'); + view(225, 20); lightRestoreSingle; + saveas(gcf, [outname '_Surface3'], 'png'); + view(90, 3); lightRestoreSingle; + subplot(2, 2, 4); + view(135, 0); lightRestoreSingle; + saveas(gcf, [outname '_Surface4'], 'png'); + subplot(2, 2, 1); + view(270, 3); lightRestoreSingle; + subplot(2, 2, 4); + view(235, 0); lightRestoreSingle; + saveas(gcf, [outname '_Surface5'], 'png'); + subplot(2, 2, 1); + view(180, -90); lightRestoreSingle; + saveas(gcf, [outname '_Surface6'], 'png'); + + end % threshold loop + + + end + + % ======================================================================= + % ======================================================================= + % ======================================================================= + + +end % main function + + + +% ======================================================================= +% ======================================================================= +% ======================================================================= +% ======================================================================= + +% -------------------------------- +% Sub-functions +% +% -------------------------------- + +% ======================================================================= +% ======================================================================= +% ======================================================================= +% ======================================================================= + +function [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, num_dims, doplssquash) + % PCA or PLS squash, returns train_dat (scores) and v (weight vectors) + % and test_dat, with applied weights + % test_dat is used only to multiply by v to prepare for testing + + % may have to adjust num_dims depending on size of holdout set + num_dims = min(num_dims, size(train_dat, 1) - 1); + + if doplssquash + %[v, train_dat] = plssquash(train_dat, train_y, 'num_dims', num_dims, 'noplot'); + + [T,P,W,Wstar,U,b,C,Bpls, v, Xhat,Yhat,R2X,R2Y] = PLS_nipals(train_dat,train_y, num_dims); + + % was returning singles sometimes... + v = double(v); + + % re-do train_dat by taking PLS weighting, [ones train_dat] * v (Bpls_star) = fit + train_dat = Yhat; + test_dat = [1 test_dat] * v; + + % create_figure('tmp1'); plot(Yhat, train_y, 'ko'); drawnow + % xlabel('Yhat from PLS'); ylabel('Actual yhat'); + + else + [v, scores] = princomp(train_dat, 'econ'); % scores = train_dat, up to scaling factor!! can't use scores from princomp-diff scaling + train_dat = train_dat * v; + v = v(:, 1:num_dims); % eigenvectors + train_dat = train_dat(:, 1:num_dims); % train_dat now becomes the scores + + test_dat = test_dat * v; %(:, 1:num_dims(s)); % get scores for missing test subj + end + +end + +% ================================ +% ================================ + +function [betas, vox_weights, varargout] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams) + % subjbetas{s} is a list of voxel weights for one subject, typically + % (in a linear model) (nvox+1) x 1, where +1 refers to the intercept parameter. + % in the case of functional mediation, this could be nvox x tpoints. + % + % fits are the predicted outcome (y) values, given the model parameter estimates + % and known information for the left-out observation. In the functional mediation case, + % a different method for producing fits is necessary. + out = []; + if nargin < 7, regparams = []; end + + switch fit_method + case 'ols' + Xs = [ones(size(train_dat, 1), 1) train_dat]; + betas = pinv(Xs) * train_y; + + case 'ridge' + if isempty(regparams) + shrinkage_param = 0; % ols + else + shrinkage_param = regparams(1); + end + Xs = train_dat; % with scaling off, constant is automatically added + betas = ridge(train_y, Xs, shrinkage_param, 0); + + if pcsquash + vox_weights = v * betas; + else + vox_weights = betas; + end + + case 'robust' + Xs = train_dat; + betas = robustfit(Xs, train_y); + + if pcsquash + vox_weights = v * betas; + else + vox_weights = betas; + end + + case 'bestsubsets' + + if ~pcsquash + error('Best subsets will not work with over-identified model, so use pcsquash'); + end + + Xs = train_dat; + if size(Xs, 2) >= size(Xs, 1)-1 + warning('AIC best subsets will not work well with full redundancy.'); + end + + %Xs = Xs(:, 1:end-3); % remove last PC to prevent over-identification (redundancy). AIC will select all predictors with redundant model. + [wh_predictors, betas] = regress_best_subsets_ga(Xs, train_y); + + + case 'lasso' + % Note: train_dat should not have intercept; included automatically + wh_trace = regparams; + if size(train_dat, 2) == 1, error('LASSO will not work right with only one predictor variable'); end + + out = lasso(train_y, train_dat); + + %out = lasso_selection(out, 'bic'); % requires mods to original + %functions; doesn't work for me + + if isempty(out.beta) + % this is the 'intercept-only' model, where we have + % information only about the mean (cross-validated) + % will not work with lasso as implemented though... + wh_trace = []; + betas = []; %out.intercept(wh_trace); + + else + if isempty(regparams) % if empty, choose OLS + wh_trace = size(out.beta, 1); + else + % we have a parameter entered - find which element + % corresponds to the input lambda value + diffvec = abs(regparams - out.lambda); + wh_trace = find(diffvec == min(diffvec)); + wh_trace = wh_trace(1); + + end + betas = [out.intercept(wh_trace) out.beta(wh_trace, :)]'; + end + + case 'logistic' + + betas = glmfit(train_dat, [train_y ones(size(train_y))], 'binomial', 'link', 'logit'); + + case 'logistictrain' + [models] = classifierLogisticRegression( train_dat, train_y, [] ); + betas = models{3}(:, 1); % intercept seems to be added as first predictor + + otherwise error('Unknown fit method') + end + + if nargout > 2, varargout{1} = out; end + + % Vox weights + % ------------ + switch fit_method + case {'ols', 'ridge', 'robust', 'bestsubsets', 'logistic', 'logistictrain'} + if pcsquash + vox_weights = v(1:nvox, :) * betas(2:end); + else + vox_weights = betas(2:end); + end + + case 'lasso' + + if pcsquash + vox_weights = v(1:nvox, :) * out.beta(wh_trace, :)'; + else + vox_weights = out.beta(wh_trace, :)'; + end + + otherwise error('Unknown fit method') + end + + +end % function + +% ================================ +% ================================ + + + +% -------------------------------------------------- +% Inner cross-val: Choose best ridge param +% -------------------------------------------------- + +function best_paramval = inner_xval_optimize(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash, verbose) + + if verbose + fprintf('\nOptimizing params using inner x-val'); + end + + switch fit_method + case {'ridge'} + bounds = [0, 10*size(train_dat, 2)]; + + case {'lasso'} + bounds = [0, .5]; % *****???what should it be? + + otherwise, error('xval_reg:NotImplemented',sprintf('Fit method ''%s'' not compatible with dochoose_regparams', fit_method)); + end + + if pcsquash, pcastr = 'pcsquash'; else pcastr = 'noverbose'; end % set PCA string + if pcsquash && doplssquash, plsstr = 'pls'; else plsstr = 'noverbose'; end % set PLS option (not tested) + + t0 = clock; + + % paramval could be a ridge or LASSO shrinkage value + fit_fcn = @(paramval) xval_given_param(paramval, fit_method, train_y, train_dat, pcastr, plsstr, num_dims); + + best_paramval = fminbnd(fit_fcn, bounds(1), bounds(2)); + + if verbose + fprintf(': Done: %3.2f sec. ', etime(clock, t0)); + fprintf('Best regularization parameter value = %3.2f\n', best_paramval) + end + +end % inner_xval_optimize_ridge + +% PE-generating function for ridge + +function pe = xval_given_param(paramval, fit_method, Y, data, pcastr, plsstr, num_dims) + % paramval could be a ridge or LASSO shrinkage value + STATS = xval_regression_multisubject(fit_method, {Y}, {data}, 'regparams', paramval, 'noverbose', 'holdout_method', 'balanced4', pcastr, plsstr, 'num_dims', num_dims); + pe = STATS.pred_err; +end diff --git a/Statistics_tools/xval_regression_multisubject_featureselect.m b/Statistics_tools/xval_regression_multisubject_featureselect.m new file mode 100644 index 00000000..aa4d63bf --- /dev/null +++ b/Statistics_tools/xval_regression_multisubject_featureselect.m @@ -0,0 +1,960 @@ +function STATS = xval_regression_multisubject(fit_method, Y, X, varargin) + % STATS = xval_regression_multisubject(fit_method, Y, X, varargin) + % + % CROSS-VALIDATED (JACKKNIFE) REGRESSION + % Leave-one observation out, predict outcomes for each missing holdout_set. + % + % Y = outcome, holdout_set x 1 + % X = predictors, holdout_set x variables + % fit_method = 'lasso' 'ols' 'ridge' or 'robust' + % optional inputs: + % 'pca', PCA data reduction first + % 'ndims', dims to save from PCA + % 'variable', retain max dims for each subject + % case {'cov', 'covs'}, cov = varargin{i+1}; NOTE:covariates are added to training data if entered. + % {'lassopath', 'ridgek', 'regparams'}, regparams = varargin{i+1}; + % {'noverb', 'noverbose'}, verbose = 0; + % {'lowverb', 'loverb', 'lowverbose'}, verboseL = 1; verbose = 0; + % 'nested_choose_ndims', dochoose_ndims = 1; + % + % 'optimize_regularization', optimize LASSO lambda or ridge parameter + % using inner x-val loop with balanced4 selection and least squares + % nonlinear fit + % + % 'holdout_method', followed by the name of a holdout method: + % 'loo' leave-one-out + % 'categorical_covs' balanced on categorical covariates + % 'l4o_covbalanced' leave-4-out, balanced on combo of Y and continuous + % covariates (logistic regression/propensity score based method) + % 'balanced4' 4-fold, balanced on Y (every 4th element of sorted Y) + % + % Single-level analysis: enter a single cell with Y and X data. + % rows are observations. (e.g., subjects) + % + % Multi-level analysis: enter a cell per subject with Y and X data. + % rows are observations within-subjects and should be independent for valid + % cross-validation. + % + % STATS = xval_regression_multisubject('lasso', pain_scores, data, 'pca', + % 'ndims', 'variable'); + % SEE THE WIKI FOR EXAMPLES, ETC. + % Tor Wager, 3/17/09 + + % Set defaults + % ----------------------------------------------------------- + nested_setdefaults(); + docenterrowsX; dosave; + + % Variable dimensions: retain max possible for each subject + % If choose ndims is selected, then pick the number here + % Based on size of input data only (not optimized; full dims) + % ----------------------------------------------------------- + nested_choose_ndims(); + + [STATS.INPUTS.pcsquash, STATS.INPUTS.num_dims, STATS.INPUTS.Y, STATS.INPUTS.holdout_method] = deal(pcsquash, num_dims, Y, holdout_method); + %STATS.INPUTS.X = X; + + %include = 1:N; % which subjects to include + + for s = 1:N + % =========================================== + % DATASET (SUBJECT) LOOP + % This function will run separately for each dataset ("subject" in a + % multi-level analysis) in the input cell arrays + % With one dataset, it runs cross-validation for that dataset only + % =========================================== + + nested_prepdata(); % remove NaNs and initialize fit variable (output) + nanvox; % we will need to pass this into another inline later + + % Return holdout_set{} defining folds and holdout set for each fold + holdout_set = nested_select_holdout_set(); + STATS.INPUTS.holdout_set{s} = holdout_set; + + checkrowdependence(X{s}, verbose || verboseL); + % rows are assumed to be independent; if they are not, can get + % perfect cross-validated predictions of even random outcomes + + % -------------------------------------------------------------------------------- + % Cross-validation folds + % -------------------------------------------------------------------------------- + + % THIS COULD BE PARFOR + for wh_fold = 1:length(holdout_set) %holdout_set = 1:length(Y{s}) + + if verbose || verboseL, fprintf('\b\b\b%3.0f', wh_fold); end + + % Parse into training and test data for this fold + % Defines: train_y (outcome) and train_dat (predictors), + % test_dat. test outcome not saved. + % covariates are added to training data if entered. + nested_select_training_test_data(); + + % voxel selection + % select features based on univariate correlations + pthreshold = .05; + [r, p, Tstat] = correlation_fast_series(train_dat, train_y); + wh_features = p <= pthreshold; + nfeatures(wh_fold) = sum(wh_features); + if nfeatures(wh_fold) == 0 + disp('Warning: no features pass threshold'); + wh_features = p <= prctile(p, 10); + end + train_dat = train_dat(:, wh_features); + test_dat = test_dat(:, wh_features); + + % Parameter optimization by inner cross-validation + % Inner cross-validation could take a long time + % -------------------------------------------------------------------------------- + + if dochoose_ndims + % update num_dims(s), update regparams***incorporate this + % into single inner-xval loop + [LASSO, regparams, best_ndims] = inner_xval_lasso(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash); + + end + + if dochoose_regparams + % Replace regparams with optimized regparams + regparams = inner_xval_optimize(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash, verbose); + + STATS.all_reg_hyperparams(wh_fold) = regparams; + end + + % Dim reduction + % -------------------------------------------------------------------------------- + if pcsquash + [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, num_dims(s), doplssquash); + end + + % Fit + % -------------------------------------------------------------------------------- + % subjbetas{s} is a list of voxel weights for one subject, typically + nvox = size(X{s}, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! +% ncovs = size(train_covs, 2); % needed to get voxel weights only +% [subjbetas{s}, STATS.vox_weights(:, wh_fold)] = do_fit(fit_method, train_y, [train_covs train_dat], pcsquash, v, nvox, regparams, ncovs); + + [subjbetas{s}, STATS.vox_weights(:, wh_fold)] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams); + + switch fit_method + case {'logistic', 'logistictrain'} + eta = [1 test_dat] * subjbetas{s}; + fit(holdout_set{wh_fold}, 1) = 1 ./ (1 + exp(-eta)); + + otherwise + % pred for the left-out observation + if length(subjbetas{s}) == 1 + % this is the 'intercept-only' model, where we have + % information only about the mean (cross-validated) + fit(holdout_set{wh_fold}, 1) = 1 * subjbetas{s}; + else + %fit(holdout_set{wh_fold}, 1) = [ones(size(test_dat, 1), 1) test_covs test_dat] * subjbetas{s}; + fit(holdout_set{wh_fold}, 1) = [ones(size(test_dat, 1), 1) test_dat] * subjbetas{s}; + end + end + + % plot apparent fit +% if verbose +% if wh_fold ==1, create_figure('Intercepts'); end +% create_figure('Training data', 1, 2); plot([ones(size(train_dat, 1), 1) train_dat] * subjbetas{s}, train_y, 'ko', 'MarkerFaceColor', [.5 .5 .5]); +% hold on; plot(fit(holdout_set{wh_fold}, 1), Y{s}(holdout_set{wh_fold}), 'ko', 'MarkerFaceColor', 'r', 'MarkerSize', 8); +% create_figure('Intercepts', 1, 1, 1); hold on; plot(holdout_set{wh_fold}, subjbetas{s}(1), 'ks', 'MarkerFaceColor', 'k'); title('Intercept'); drawnow +% end + +% Diagnostic: Is apparent fit related to covariates? +% train_covs = cov_val{1}; train_covs(holdout_set{wh_fold}, :) = []; +% [b, dev, statsss] = glmfit(train_covs, [ones(size(train_dat, 1), 1) train_dat] * subjbetas{s}); +% glm_table(statsss); + + my_intercepts{s}(wh_fold, 1) = subjbetas{s}(1); + + end % xval + + if verbose || verboseL, toc, end + + + % --------------------------------------------------------------------- + % end cross-val loop + % --------------------------------------------------------------------- + + subjfit{s} = fit; + + nested_output_metrics_and_plots(); + + + end % dataset/subject loop + + + STATS.Y_orig = Y_orig; + if ~isempty(cov_val) + STATS.note = 'covariates were added to predictive model'; %, if covs %removed in Y_orig stored here, if covs were entered'; + else + STATS.note = 'no covariates entered'; + end + + STATS.devs_from_mean_only_model = devs_from_mean_only_model; + STATS.devs_from_full_model = devs_from_full_model; + STATS.var_null = var_null; + STATS.var_full = var_full; + + % Null model leave-one-out prediction error + % By predicting based on the mean of OTHER subjects, we've increased + % the variance + STATS.pred_err_null = var_null .^ .5; + + STATS.pred_err = pred_err; + STATS.pred_err_descrip = 'Apparent error/loss: Root mean squared deviation from observed outcomes'; + STATS.var_reduction = rsq; + STATS.var_reduction_descrip = 'Percent reduction in outcome variance due to model; negative values indicate added variance.'; + + STATS.subjfit = subjfit; + STATS.subjbetas = subjbetas; + STATS.r_each_subject = rr; + STATS.r_squared = STATS.r_each_subject .^ 2; + STATS.r_each_subject_note = 'r value: correlation between predicted and outcome values'; + STATS.r_each_subject_note2 = 'may not be very interpretable, because null model r = -1.0'; + + if verbose + disp(['Mean correlation is: ' num2str(mean(rr))]) + disp(['Mean proportion of original variance explained is: ' num2str(mean(rsq))]) + disp('The number above is based on reduction of original variance;'); + disp('it can be negative if predictors are not helpful because they add noise, increasing the overall variance!') + disp(' ') + fprintf('Null model pred. error is %3.2f, and full model is %3.2f\n', STATS.pred_err_null(1), STATS.pred_err(1)); + disp(' ') + end + + + + % ======================================================================= + % ======================================================================= + % + %Inline (nested) functions + % + % ======================================================================= + % ======================================================================= + + function nested_setdefaults + pcsquash = 0; + doplssquash = 0; + num_dims = 2; + cov_val = []; + verbose = 1; + verboseL = 0; + lassopath = '/Users/tor/Documents/matlab_code_external/machine_learning/lasso_rocha/lasso'; + doplot = 1; + dochoose_ndims = 0; + regparams = []; + dochoose_regparams = 0; + holdout_method = 'loo'; + docenterrowsX = 0; + dosave = 1; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Data + case {'centerX', 'centerx', 'docenterrowsX'}, docenterrowsX = 1; + + % Dimension reduction + case {'pca', 'pcsquash'}, pcsquash = 1; + case {'pls', 'plssquash'}, pcsquash = 1; doplssquash = 1; + case {'num_dims', 'ndims'}, num_dims = varargin{i+1}; varargin{i + 1} = []; + case 'nested_choose_ndims', dochoose_ndims = 1; + + % Covariates + case {'cov_val', 'covs', 'cov'}, cov_val = varargin{i+1}; + + % Shrinkage/regularization parameters + case {'lassopath', 'ridgek', 'regparams'}, regparams = varargin{i+1}; varargin{i + 1} = []; + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams = 1; + + % Holdout set selection + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + % Output control + case {'noverb', 'noverbose'}, verbose = 0; + case {'lowverb', 'loverb', 'lowverbose'}, verboseL = 1; verbose = 0; + case 'verbose' % default + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if verbose + fprintf('xval_regression_multisubject\nVerbose mode (enter ''lowverbose'' to minimize or ''noverbose'' to turn off verbose output)\n') + end + + N = length(Y); % number of subjects/datasets + [subjbetas, subjfit] = deal(cell(1, N)); + + fit = zeros(length(Y{1})); + + if strcmp(fit_method, 'lasso'), addpath(lassopath), end + + end % defaults setting function + + + % ======================================================================= + + function nested_prepdata + if verbose + fprintf('Dataset %3.0f\n> -------------------------------\n ', s); + elseif verboseL + fprintf('%3.0f ', s); + end + + % remove NaNs + % --------------------------------------------------------------------- + % X variables are done first, on the presumption that variables + % (voxels) are many and observations are few + nanvox{s} = any(isnan(X{s})); + if all(nanvox{s}), error('All X variables appeared to have NaN values for one or more observations.'); end + if verbose && sum(nanvox{s}), fprintf('Removed %3.0f X variables with NaNs\n', sum(nanvox{s})); end + X{s}(:, nanvox{s}) = []; + + if isempty(cov_val) + [wasnan{s}, Y{s}, X{s}] = nanremove(Y{s}, X{s}); + else + [wasnan{s}, Y{s}, X{s}, cov_val{s}] = nanremove(Y{s}, X{s}, cov_val{s}); + end + if all(wasnan{s}), error('All observations appeared to have NaN values for one or more variables.'); end + if verbose && sum(wasnan{s}), fprintf('Removed %3.0f observations with NaNs\n ', sum(wasnan{s})); end + + Y_orig{s} = Y{s}; + + if docenterrowsX + disp('Row-centering requested: Centering each row of predictors'); + X{s} = scale(X{s}', 1)'; + end + + fit = NaN * zeros(size(Y{s})); + tic + + % --------------------------------------------------------------------- + % initialize optional things + v = []; + + if verbose || verboseL, fprintf('Fold %3.0f', 0); end + + end + + % ======================================================================= + function checkrowdependence(X, verbose) + % rows are assumed to be independent; if they are not, can get + % perfect cross-validated predictions of even random outcomes + + [N, k] = size(X); + r = rank(X'); + if r < N && verbose, disp('WARNING!!! ROWS ARE DEPENDENT. CROSS-VALIDATION MAY BE INVALID.'); end +% +% cc = corrcoef(X'); +% cc = cc - eye(size(cc)); +% [maxcorr, wh] = max(cc(:)); +% +% vifs = getvif(dat', 1) +% vifs = getvif(dat', 1); [maxvif, whv] = max(vifs) + + end + + % ======================================================================= + + function nested_choose_ndims() + + if isstr(num_dims) && strcmp(num_dims, 'variable') + if pcsquash == 0, warning('xval:ConflictingInputs', 'PC squash is off, so num_dims input will not be used.'); end + + if verbose, fprintf('Variable number of dimensions: choosing: '); end + + clear num_dims + for i = 1:N + if ~isempty(cov_val) + num_dims(i) = min(size(X{i}, 1) - 2, size(X{i}, 2) + size(cov_val{i}, 2) - 2); + else + num_dims(i) = min(size(X{i}, 1) - 2, size(X{i}, 2) - 2); + end + if verbose, fprintf('%03d ', num_dims(i)); end + end + if verbose, fprintf('\n'); end + end + + if length(num_dims) == 1 && N > 1, num_dims = repmat(num_dims, N, 1); end + + if dochoose_ndims + disp('Inner cross-validation; this could take a long time') + end + + end + + % ======================================================================= + + + function holdout_set = nested_select_holdout_set + % purpose: return holdout_set variable + + nobs_s = length(Y{s}); + + switch lower(holdout_method) + case 'loo' + holdout_set = cell(1, nobs_s); + for i = 1:nobs_s, holdout_set{i} = i; end + + case 'l4o_covbalanced' + disp('Selecting holdout sets: Balancing on covariates and outcome, and also trying to ensure that each obs is selected equally often.'); + holdout_proportion = 4 ./ nobs_s; % prop for leave-4-out + nfolds = 40; + if isempty(cov_val) + wh_holdout = xval_select_holdout_set(Y{s}, [], nfolds, holdout_proportion, verbose); + else + wh_holdout = xval_select_holdout_set(Y{s}, cov_val{s}, nfolds, holdout_proportion, verbose); + end + holdout_set = cell(1, nfolds); + for k = 1:nfolds + holdout_set{k} = wh_holdout(:, k); + end + + case 'categorical_covs' + + holdout_set = xval_select_holdout_set_categoricalcovs(cov_val{s}); + + case 'balanced4' + nfolds = 4; + holdout_set = cell(1, nfolds); + [ys, indx] = sort(Y{s}); + for k = 1:nfolds + + holdout_set{k} = indx(k:nfolds:end); + if isempty(holdout_set{k}), error('Holdout set construction error: Dataset too small?'); end + + end + + otherwise error('Unknown holdout method. See help.'); + end + end + + + % ======================================================================= + + function nested_select_training_test_data + % Select training/test data + % ----------------------------- + + train_y = Y{s}; + train_y(holdout_set{wh_fold}) = []; + + if ~isempty(cov_val) + train_dat = [X{s} cov_val{s}]; + else + train_dat = X{s}; + end + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! +% % if ~isempty(cov_val) +% % train_covs = cov_val{s}; +% % test_covs = train_covs(holdout_set{wh_fold}, :); +% % train_covs(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) +% % else +% % train_covs = []; +% % test_covs = []; +% % end + + test_dat = train_dat(holdout_set{wh_fold}, :); + train_dat(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) + + + end % data selection function + + % ======================================================================= + + + function nested_output_metrics_and_plots + + if pcsquash + % add NaNs back in to preserve voxel order + meanvoxweights = naninsert(nanvox{s}, mean(STATS.vox_weights, 2)); + + STATS.mean_vox_weights(:, s) = meanvoxweights; + end + + pefcn = inline('var(y - f) .^ .5', 'y', 'f'); + + if verbose || verboseL + + create_figure('fitplot', 2, 2); + plot(Y_orig{s}, 'o-'); + hold on; plot(fit, 'rx-'); + title('Prediced (red), Actual (blue) over obs'); + + subplot(2, 2, 2); + plot_correlation_samefig(fit, Y_orig{s}); + xlabel('Predicted value'); ylabel('Actual value') + + plot([min(Y_orig{s}) max(Y_orig{s})], [min(Y_orig{s}) max(Y_orig{s})], 'b--', 'LineWidth', 2); + axis tight + + subplot(2, 2, 3); + hold on; plot(1:length(my_intercepts{s}), my_intercepts{s}, 'ks', 'MarkerFaceColor', 'k'); + title('Intercept'); + + subplot(2, 2, 4); + rescalevals = [0:.1:2]; + for j = 1:length(rescalevals) + mype(j) = pefcn(Y_orig{s}, rescalevals(j) .* subjfit{s}); + end + plot(rescalevals, mype, 'k-'); + plot_vertical_line(0); + axis auto; axis tight; + title('Pred err as a function of prediction rescaling'); + drawnow + + if ~isempty(cov_val) + if dosave, diary('xval_regression_multisubject_output.txt'); end + + disp('Test of whether predicted values are related to covariates:'); + [b, dev, statsss] = glmfit(cov_val{1}, subjfit{s}); + glm_table(statsss); + + if dosave, diary off; end + end + + end % verbose + + myr = corrcoef(subjfit{s}, Y_orig{s}); rr(s) = myr(1,2); + pred_err(s, 1) = var(Y_orig{s} - subjfit{s}) .^ .5; % almost the same as (sum((Y_orig{s} - subjfit{s}).^2) ./ (length(Y_orig{s}) - 1) ) .^.5 + + % Null model leave-one-out prediction error + % By predicting based on the mean of OTHER subjects, we've increased + % the variance; we need to adjust + jstat = jackknife(@mean, Y_orig{s}); + + devs_from_mean_only_model = Y_orig{s} - jstat; + + devs_from_full_model = Y_orig{s} - subjfit{s}; + + var_null(s) = var(devs_from_mean_only_model); + var_full(s) = var(devs_from_full_model); + + rsq(s, 1) = 1 - var_full ./ var_null(s); + + end + + % ======================================================================= + % ======================================================================= + % ======================================================================= + + +end % main function + + + +% ======================================================================= +% ======================================================================= +% ======================================================================= +% ======================================================================= + +% -------------------------------- +% Sub-functions +% +% -------------------------------- + +% ======================================================================= +% ======================================================================= +% ======================================================================= +% ======================================================================= + +function [v, train_dat, test_dat] = do_pcsquash(train_y, train_dat, test_dat, num_dims, doplssquash) + % PCA or PLS squash, returns train_dat (scores) and v (weight vectors) + % and test_dat, with applied weights + % test_dat is used only to multiply by v to prepare for testing + + % may have to adjust num_dims depending on size of holdout set + num_dims = min(num_dims, size(train_dat, 1) - 1); + + if doplssquash + %[v, train_dat] = plssquash(train_dat, train_y, 'num_dims', num_dims, 'noplot'); + + [T,P,W,Wstar,U,b,C,Bpls, v, Xhat,Yhat,R2X,R2Y] = PLS_nipals(train_dat,train_y, num_dims); + + % was returning singles sometimes... + v = double(v); + + % re-do train_dat by taking PLS weighting, [ones train_dat] * v (Bpls_star) = fit + train_dat = Yhat; + test_dat = [1 test_dat] * v; + + % create_figure('tmp1'); plot(Yhat, train_y, 'ko'); drawnow + % xlabel('Yhat from PLS'); ylabel('Actual yhat'); + + else + [v, scores] = princomp(train_dat, 'econ'); % scores = train_dat, up to scaling factor!! can't use scores from princomp-diff scaling + train_dat = train_dat * v; + v = v(:, 1:num_dims); % eigenvectors + train_dat = train_dat(:, 1:num_dims); % train_dat now becomes the scores + + test_dat = test_dat * v; %(:, 1:num_dims(s)); % get scores for missing test subj + end + +end + +% ================================ +% ================================ + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! + %function [betas, vox_weights, varargout] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams, ncovs) + +function [betas, vox_weights, varargout] = do_fit(fit_method, train_y, train_dat, pcsquash, v, nvox, regparams) + % subjbetas{s} is a list of voxel weights for one subject, typically + % (in a linear model) (nvox+1) x 1, where +1 refers to the intercept parameter. + % in the case of functional mediation, this could be nvox x tpoints. + % + % fits are the predicted outcome (y) values, given the model parameter estimates + % and known information for the left-out observation. In the functional mediation case, + % a different method for producing fits is necessary. + out = []; + if nargin < 7, regparams = []; end + + switch fit_method + case 'ols' + Xs = [ones(size(train_dat, 1), 1) train_dat]; + betas = pinv(Xs) * train_y; + + case 'ridge' + if isempty(regparams) + shrinkage_param = 0; % ols + else + shrinkage_param = regparams(1); + end + Xs = train_dat; % with scaling off, constant is automatically added + betas = ridge(train_y, Xs, shrinkage_param, 0); + + + case 'robust' + Xs = train_dat; + betas = robustfit(Xs, train_y); + + + case 'bestsubsets' + + if ~pcsquash + error('Best subsets will not work with over-identified model, so use pcsquash'); + end + + Xs = train_dat; + if size(Xs, 2) >= size(Xs, 1)-1 + warning('AIC best subsets will not work well with full redundancy.'); + end + + %Xs = Xs(:, 1:end-3); % remove last PC to prevent over-identification (redundancy). AIC will select all predictors with redundant model. + [wh_predictors, betas] = regress_best_subsets_ga(Xs, train_y); + + + case 'lasso' + % Note: train_dat should not have intercept; included automatically + wh_trace = regparams; + if size(train_dat, 2) == 1, error('LASSO will not work right with only one predictor variable'); end + + out = lasso(train_y, train_dat); + + %out = lasso_selection(out, 'bic'); % requires mods to original + %functions; doesn't work for me + + if isempty(out.beta) + % this is the 'intercept-only' model, where we have + % information only about the mean (cross-validated) + % will not work with lasso as implemented though... + wh_trace = []; + betas = []; %out.intercept(wh_trace); + + else + if isempty(regparams) % if empty, choose OLS + wh_trace = size(out.beta, 1); + else + % we have a parameter entered - find which element + % corresponds to the input lambda value + diffvec = abs(regparams - out.lambda); + wh_trace = find(diffvec == min(diffvec)); + wh_trace = wh_trace(1); + + end + betas = [out.intercept(wh_trace) out.beta(wh_trace, :)]'; + end + + case 'logistic' + + betas = glmfit(train_dat, [train_y ones(size(train_y))], 'binomial', 'link', 'logit'); + + % plot apparent + % % eta = [ones(size(train_dat, 1), 1) train_dat] * betas; + % % fit = 1 ./ (1 + exp(-eta)); + % % create_figure('tmp'); + % % plot(train_dat, train_y, 'ko'); + % % vals = [-1.5:.1:1.5]'; + % % etafit = [ones(length(vals), 1) vals] * betas; + % % fitcurve = 1 ./ (1 + exp(-etafit)); + % % plot([-1.5:.1:1.5], fitcurve, 'k'); + % % set(gca, 'XLim', [min(train_dat) max(train_dat)]); + % % etazero = -betas(1) ./ betas(2); % point at which p(lie) in logistic model = .5 + % % han = plot_vertical_line(etazero); + % % set(han, 'LineStyle', ':'); + % % drawnow + + % % % case 'logisticpls' + % % % now done in plssquash... + % % % [T,P,W,Wstar,U,b,C,Bpls,Bpls_star,Xhat,Yhat,R2X,R2Y] = PLS_nipals(train_dat,train_y, 10); + % % % betas was Bpls_star; [1 test_x] * Bpls_star = fit = trial score, + % % % which is X in the logistic model below: + % % % + % % % Xtrain = [ones(size(train_dat, 1), 1) train_dat] * Bpls_star; % this gives us the PLS-optimized features + % % % betas = glmfit(Xtrain, [train_y ones(size(train_y))], 'binomial', 'link', 'logit'); + % % % + % % % fit_eta = betas(1) + betas(2) * ([1 test_data]*Bpls_star) + % % % fit = 1 ./ (1 + exp(-fit_eta)); + + case 'logistictrain' + [models] = classifierLogisticRegression( train_dat, train_y, [] ); + betas = models{3}(:, 1); % intercept seems to be added as first predictor + + otherwise error('Unknown fit method') + end + + if nargout > 2, varargout{1} = out; end + + % Vox weights + % ------------ + switch fit_method + case {'ols', 'ridge', 'robust', 'bestsubsets', 'logistic', 'logistictrain'} + if pcsquash + %vox_weights = v(1:nvox, :) * betas(2+ncovs:end); + vox_weights = v(1:nvox, :) * betas(2:end); + else + vox_weights = betas(2:end); + end + + case 'lasso' + + % This is for the version where covs are always added after + % pcsquash; but if covs are not meaningful, can increase + % error! + % if pcsquash + % vox_weights = v(1:nvox, :) * out.beta(wh_trace, ncovs+1:end)'; + % else + % vox_weights = out.beta(wh_trace, ncovs+1:end)'; + % end + + vox_weights = zeros(nvox, 1); + +% if pcsquash +% vox_weights = v(1:nvox, :) * out.beta(wh_trace, :)'; +% else +% vox_weights = out.beta(wh_trace, :)'; +% end + + otherwise error('Unknown fit method') + end + + +end % function + +% ================================ +% ================================ + +% % % function [LASSO, wh_trace, best_ndims] = inner_xval_lasso(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash) +% % % +% % % % Inner cross-validation; this could take a long time +% % % % ----------------------------- +% % % +% % % % update num_dims(s), update wh_trace +% % % +% % % n_inner_obs = length(train_y); +% % % nvox = size(train_dat, 2); % original voxels, not including covs or NaN voxels (will add in NaNs later) +% % % +% % % best_ndims = size(train_dat, 2); % initialize num_dims value +% % % +% % % % choose dims to test -- either variable, or single value +% % % % -------------------------------------------------------------------- +% % % if pcsquash && dochoose_ndims +% % % dims_to_test = 2:num_dims; % placeholder; select all features +% % % else +% % % % run once, select all features +% % % dims_to_test = num_dims; +% % % end +% % % +% % % % choose lasso shrinkage parameter with inner cross-validation loop +% % % % ------------------------------------------------------------------- +% % % if strcmp(fit_method, 'lasso') +% % % if pcsquash +% % % % 2nd train_dat(1,:)is test dat, but irrelevant here-- we just need dims +% % % [v, X2, tmp_train_dat] = do_pcsquash(train_y, train_dat, train_dat(1,:), dims_to_test(end), doplssquash); +% % % clear v tmp_train_dat +% % % end +% % % out = lasso(train_y, X2); % to get penalty values +% % % penalty_values = out.penalty; %linspace(0, max(out.penalty), 10); % resolution: 10 +% % % +% % % fit = zeros(n_inner_obs, length(dims_to_test), length(penalty_values)); +% % % +% % % end +% % % +% % % % Inner cross-validation +% % % % -------------------------- +% % % for ii = 1:n_inner_obs +% % % +% % % % fprintf('\b\b\b%3.0f', holdout_set); +% % % +% % % % Select data +% % % Y2 = train_y; +% % % Y2(ii) = []; +% % % X2 = train_dat; +% % % X2(ii, :) = []; +% % % test_dat = train_dat(ii, :); +% % % +% % % % Dim reduction: with max number +% % % % We will use selected numbers of these +% % % % Must re-do PCA in inner loop to avoid bias +% % % % ----------------------------- +% % % if pcsquash +% % % [v, X2, test_dat] = do_pcsquash(Y2, X2, test_dat, dims_to_test(end), doplssquash); +% % % end +% % % +% % % for jj = 1:length(dims_to_test) +% % % % Dimension selection loop +% % % +% % % % Fit +% % % % ----------------------------- +% % % % we may also have a lasso shrinkage (penalty) parameter to choose +% % % % this gives us the whole lasso trace, though +% % % wh_trace = []; +% % % [subjbetas{ii,jj}, tmp_vox_weights, out2] = do_fit(fit_method, Y2, ... +% % % X2(:, 1:dims_to_test(jj)), pcsquash, v(:, 1:dims_to_test(jj)), ... +% % % nvox, wh_trace); +% % % +% % % clear tmp_vox_weights +% % % +% % % % pred for the left-out observation +% % % +% % % +% % % if strcmp(fit_method, 'lasso') +% % % % get trace from out struct +% % % % penalty values vary across validation loop iterations +% % % % fit = fit values for each possible choice of penalty +% % % % test_dat is component scores if pcsquash, otherwise dims to +% % % % test is all +% % % fit(ii, jj, 1) = out2.intercept(end)' + test_dat(1:dims_to_test(jj)) * out2.beta(end, :)'; +% % % +% % % % lfit: lasso fit for each trace +% % % lfit = out2.intercept' + test_dat(1:dims_to_test(jj)) * out2.beta'; +% % % % interpolate to standard trace values +% % % lfit = interp1(out2.penalty, lfit, penalty_values); +% % % +% % % fit(ii, jj, 1:length(penalty_values)) = lfit; +% % % % this should go on 3rd dim of fit +% % % +% % % % trials x penalty choices +% % % % fit_inner_xval{jj}(ii, :) = interp1(out2.penalty, fitiijj, penalty_values); +% % % % err_inner_xval{jj}(ii, :) = train_y(ii) - fit_inner_xval{jj}(ii, :); +% % % else +% % % +% % % % this is for the OLS solution (max trace value) +% % % if length(subjbetas{ii,jj}) == 1 +% % % % this is the 'intercept-only' model +% % % fit(ii, jj, 1) = 1 * subjbetas{ii,jj}; +% % % else +% % % fit(ii, jj, 1) = [1 test_dat] * subjbetas{ii,jj}; +% % % end +% % % end +% % % +% % % +% % % +% % % end % Dims loop +% % % end % ii inner xval loop +% % % +% % % for j = 1:length(dims_to_test) +% % % +% % % for k = 1:size(fit, 3) +% % % vec = squeeze(fit(:, j, k)); +% % % cc = corrcoef(vec, train_y); +% % % r(j, k) = cc(1,2); +% % % pe(j, k) = sqrt(sum((train_y - vec) .^2)); +% % % end +% % % +% % % end +% % % +% % % [wh_ndims, wh_penalty] = find(pe == min(pe(:))); +% % % wh_ndims = wh_ndims(1); +% % % wh_penalty = wh_penalty(1); +% % % +% % % best_penalty = penalty_values(wh_penalty); +% % % best_dims = dims_to_test(wh_ndims); +% % % best_pe = pe(wh_ndims, wh_penalty); +% % % +% % % if doplot +% % % create_figure('Error by dims and penalty values'); +% % % +% % % [Xf, Yf] = meshgrid(penalty_values, dims_to_test); +% % % surf(Xf, Yf, pe) +% % % xlabel('Penalty values') +% % % ylabel('Dimensions') +% % % view(135, 30); +% % % hold on; +% % % plot3(best_penalty, best_dims, best_pe, 'ko','MarkerFaceColor', 'r', 'MarkerSize', 8); +% % % +% % % end +% % % +% % % LASSO = struct('best_penalty', best_penalty, 'best_dims', best_dims, 'best_pe', best_pe, ... +% % % 'wh_ndims', wh_ndims, 'wh_penalty', wh_penalty); +% % % +% % % end % function + + +% -------------------------------------------------- +% Inner cross-val: Choose best ridge param +% -------------------------------------------------- + +function best_paramval = inner_xval_optimize(fit_method, train_y, train_dat, pcsquash, doplot, num_dims, dochoose_ndims, doplssquash, verbose) + + if verbose + fprintf('\nOptimizing params using inner x-val'); + end + + switch fit_method + case {'ridge'} + bounds = [0, 10*size(train_dat, 2)]; + + case {'lasso'} + bounds = [0, .5]; % *****???what should it be? + + otherwise, error('xval_reg:NotImplemented',sprintf('Fit method ''%s'' not compatible with dochoose_regparams', fit_method)); + end + + if pcsquash, pcastr = 'pcsquash'; else pcastr = 'noverbose'; end % set PCA string + if pcsquash && doplssquash, plsstr = 'pls'; else plsstr = 'noverbose'; end % set PLS option (not tested) + + t0 = clock; + + % paramval could be a ridge or LASSO shrinkage value + fit_fcn = @(paramval) xval_given_param(paramval, fit_method, train_y, train_dat, pcastr, plsstr, num_dims); + + best_paramval = fminbnd(fit_fcn, bounds(1), bounds(2)); + + if verbose + fprintf(': Done: %3.2f sec. ', etime(clock, t0)); + fprintf('Best regularization parameter value = %3.2f\n', best_paramval) + end + +end % inner_xval_optimize_ridge + +% PE-generating function for ridge + +function pe = xval_given_param(paramval, fit_method, Y, data, pcastr, plsstr, num_dims) + % paramval could be a ridge or LASSO shrinkage value + STATS = xval_regression_multisubject(fit_method, {Y}, {data}, 'regparams', paramval, 'noverbose', 'holdout_method', 'balanced4', pcastr, plsstr, 'num_dims', num_dims); + pe = STATS.pred_err; +end diff --git a/Statistics_tools/xval_ridge_brain.m b/Statistics_tools/xval_ridge_brain.m new file mode 100644 index 00000000..04ddfbf2 --- /dev/null +++ b/Statistics_tools/xval_ridge_brain.m @@ -0,0 +1,559 @@ +function STATS_FINAL = xval_ridge_brain(my_outcomes, imgs, varargin) + %STATS_FINAL = xval_ridge_brain(my_outcomes, imgs, varargin) + % + % PCA-Lasso based prediction on a set of brain images + % Tor Wager, Sept. 2009 + % + % + % Optional inputs: + % case 'reversecolors', reversecolors = 1; + % case 'skipnonparam', dononparam = 0; + % case 'skipsurface', dosurface = 0; + % case {'cov', 'covs', 'covariates'}, covs = + % varargin{i+1}; + % case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + % case 'niter', niter = varargin{i+1}; + % case 'more_imgs', followed by 2nd (or nth) set of + % images in cell array {} + % Example + % ----------------------------------------------------------------------- + % mkdir LASSO_xvalidation_frontal + % cd LASSO_xvalidation_frontal/ + % mask = '/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/newmask_mapped.img'; + % load('/Users/tor/Documents/Tor_Documents/CurrentExperiments/Combined_sample_2007/robust0004/robust0001/SETUP.mat') + % imgs = {SETUP.files}; + % my_outcomes = {SETUP.X(:, 2)}; + % covs = {SETUP.X(:, [3 4 5])}; + % outcome_name = 'Placebo Analgesia (C-P)'; + % covnames = {'Order' 'Study' 'Study x Placebo'}; + % STATS_FINAL = xval_ridge_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors', 'skipsurface', 'skipnonparam'); + % + % or + % + % STATS_FINAL = xval_ridge_brain(my_outcomes, imgs, 'mask', mask, 'covs', covs, 'reversecolors'); + + % ----------------------------------------------------------------------- + % Optional inputs + % ----------------------------------------------------------------------- + spm_defaults + covs = []; + mask = which('brainmask.nii'); + reversecolors = 0; + dononparam = 1; + dosurface = 1; + niter = 200; + covnames = {'Unknown'}; + outcome_name = 'Unknown'; + verbose = 1; + imgs2 = {}; + dosave = 0; + + % do not do dim reduction for ridge + ndims = 0; + plsstr = 'verbose'; + + dochoose_regparams_str = 'verbose'; % switch to 'optimize_regularization' to do inner xval + holdout_method = 'loo'; % see xval_regression_multisubject + + inputOptions.all_optional_inputs = varargin; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % Process control + case 'reversecolors', reversecolors = 1; + case 'skipnonparam', dononparam = 0; + case 'skipsurface', dosurface = 0; + + % Covariates and Masking + case {'cov', 'covs', 'covariates'}, covs = varargin{i+1}; + case 'mask', mask = varargin{i+1}; varargin{i + 1} = []; + case 'niter', niter = varargin{i+1}; + + % Input images + case 'more_imgs', imgs2{end+1} = varargin{i+1}; + fprintf('Found additional image set %3.0f\n', length(imgs2)); + + + % Naming output and saving + case 'outcome_name', outcome_name = varargin{i+1}; varargin{i + 1} = []; + case 'covnames', covnames = varargin{i+1}; varargin{i + 1} = []; + case 'save', dosave = 1; + + % Dimension reduction +% case 'ndims', ndims = varargin{i+1}; varargin{i + 1} = []; +% if strcmp(ndims, 'all') +% ndims = size(imgs{1}, 1) - 2; +% fprintf('Choosing %3.0f dims for PCA/PLS\n', ndims); +% end +% case 'pls', plsstr = 'pls'; + + % Shrinkage/regularization parameters + + case {'choose_regparams', 'dochoose_regparams', 'optimize_regularization'}, dochoose_regparams_str = 'optimize_regularization'; + + % Holdout set selection + + case {'holdout_method'}, holdout_method = varargin{i+1}; varargin{i + 1} = []; + + + case 'variable' % do nothing; needed later + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + inputOptions.all_outcomes = my_outcomes; + inputOptions.outcome_name = outcome_name; + inputOptions.imgs = imgs; + inputOptions.covs = covs; + inputOptions.covnames = covnames; + inputOptions.mask = mask; + inputOptions.ndims = ndims; + inputOptions.plsstr = plsstr; + inputOptions.reversecolors = reversecolors; + inputOptions.dochoose_regparams_str = dochoose_regparams_str; + inputOptions.holdout_method = holdout_method; + + % set up the mask + % ------------------------------------------------------------------------- + if isempty(mask), error('Mask is empty and/or default mask ''brainmask.nii'' cannot be found on path.'); end + if exist(fullfile(pwd, 'mask.img')) && verbose, disp('''mask.img'' already exists in this directory. Replacing.'); else disp('Creating mask.img'); end + scn_map_image(mask, deblank(imgs{1}(1,:)), 'write', 'mask.img'); + maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); + + inputOptions.maskInfo = maskInfo; + + datasets = length(imgs); % each Subject would constitute a "dataset" for a multi-level/within-subjects analysis + + % load image data + % ------------------------------------------------------------------------- + fprintf('Loading all datasets (may be memory-intensive for multi-subject fMRI): '); + for i = 1:datasets + fprintf('%02d', i); + dat{i} = iimg_get_data(maskInfo, imgs{i}); + + if ~isempty(imgs2) + for imgset = 1:length(imgs2) + dat{i} = [dat{i} iimg_get_data(maskInfo, imgs2{imgset}{i})]; + end + + end + + end + fprintf('\n'); + + %% + + STATS_FINAL = struct('inputOptions', inputOptions); + + % Run it: covs only + % ------------------------------------------------------------------------- + STATS_FINAL.covs = []; + if ~isempty(covs) + disp('==================================') + disp('Covariates only: OLS') + STATS_FINAL.covs = xval_regression_multisubject('ols', my_outcomes, covs, 'holdout_method', holdout_method); + end + + % Run it: data only + % ------------------------------------------------------------------------- + if ~isempty(covs) + disp('==================================') + disp('Data only: ridge') + STATS_FINAL.data = xval_regression_multisubject('ridge', my_outcomes, dat, dochoose_regparams_str, 'holdout_method', holdout_method); + else + STATS_FINAL.data = 'see .full_model'; + end + + % Run it: combined + % ------------------------------------------------------------------------- + + % My best guess about what will work: Lasso, with as many dims retained as possible + % * NOTE: all of the main inputs should be cell arrays + STATS_FINAL.full_model = []; + if ~isempty(covs) + disp('==================================') + disp('Data plus covariates full model: ridge') + STATS_FINAL.full_model = xval_regression_multisubject('ridge', my_outcomes, dat, 'cov', covs, dochoose_regparams_str, 'holdout_method', holdout_method); + else + disp('Data only full model: ridge') + disp('==================================') + STATS_FINAL.full_model = xval_regression_multisubject('ridge', my_outcomes, dat, dochoose_regparams_str, 'holdout_method', holdout_method); + end + + if dosave + save STATS_xval_output STATS_FINAL + end + + %% Figures: Scatterplot plus bars + % ------------------------------------------------------------------------- + + create_figure('Scatterplot: Full model', 1, 2); + [r,istr,sig,h] = plot_correlation_samefig(STATS_FINAL.full_model.subjfit{1}, STATS_FINAL.inputOptions.all_outcomes{1}); + set(gca,'FontSize', 24) + xlabel('Cross-validated prediction'); + ylabel('Outcome'); + title('Lasso cross-validation'); + set(h, 'MarkerSize', 10, 'MarkerFaceColor', [.0 .4 .8], 'LineWidth', 2); + h = findobj(gca, 'Type', 'Text'); + set(h, 'FontSize', 24) + + % bars + subplot(1, 2, 2) + set(gca,'FontSize', 24) + + if ~isempty(covs) + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.covs.pred_err ... + STATS_FINAL.data.pred_err STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Covs' 'Brain' 'Full'}; + else + pevals = [std(STATS_FINAL.inputOptions.all_outcomes{1}) ... + STATS_FINAL.full_model.pred_err_null STATS_FINAL.full_model.pred_err]; + penames = {'Var(Y)' 'Mean' 'Brain'}; + end + + han = bar(pevals); set(han, 'FaceColor', [.5 .5 .5]); + set(gca, 'XTick', 1:length(penames), 'XTickLabels', penames); + axis tight + ylabel('Prediction error'); + + if dosave + scn_export_papersetup(500); saveas(gcf, 'Pred_Outcome_Scatter', 'png'); + end + + %% Get mean Lasso betas + % ------------------------------------------------------------------------- + + bonf_thresh = norminv(1 - .025 ./ size(dat{1}, 2)); + + mystd = std(STATS_FINAL.full_model.vox_weights'); + + [my_ste,t,n_in_column,p,m] = ste(STATS_FINAL.full_model.vox_weights'); + Z = (m ./ mystd)'; + + iimg_reconstruct_vols(m', maskInfo, 'outname', 'xval_ridge_wts_mean.img'); + iimg_reconstruct_vols(m', maskInfo, 'outname', 'xval_ridge_Z.img'); + + sig_vox = abs(Z) > bonf_thresh; + Z_thresh = Z; + Z_thresh(~sig_vox) = 0; + iimg_reconstruct_vols(Z_thresh, maskInfo, 'outname', 'xval_ridge_Z_bonf_thresh.img'); + + + + %% Orthviews + % ------------------------------------------------------------------------- + + cl = mask2clusters('xval_ridge_Z_bonf_thresh.img'); + + poscm2 = colormap_tor([1 .5 0], [1 1 0]); + negcm2 = colormap_tor([.5 0 1], [0 0 1]); + + cluster_orthviews(cl); + + + if reversecolors + cm = spm_orthviews_change_colormap([1 1 0], [0 0 1], [1 0 .4], [.4 .6 1] ); + else + cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [.4 .6 1], [1 0 .4]); + end + + STATS_FINAL.full_model.cl = cl; + + %cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 0 1], [0 0 1], [0 + %.5 1], [0 .5 1], [.5 .5 .5], [.5 .5 .5], [.7 0 0], [1 .5 0], [1 .5 0], [1 1 0]); + + % cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 0 1], [0 0 1], [0 .5 1], [0 .5 1], [.5 .5 .5], [.5 .5 .5], [.7 0 0], [1 .5 0], [1 .5 0], [1 1 0]); + + %% Brain Surface Plot + % ------------------------------------------------------------------------- + + if dosurface + + create_figure('Brain_Surface', 2, 2); + + if reversecolors + negcm = colormap_tor([1 0 .4], [1 1 0]); + poscm = colormap_tor([.4 .6 1], [0 0 1]); + else + poscm = colormap_tor([1 0 .4], [1 1 0]); + negcm = colormap_tor([.4 .6 1], [0 0 1]); + end + + han1 = addbrain('hires'); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han1); + + %replace with 'hires' and re-run to do whole cortical surface + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 2); + han = addbrain('hires right'); set(han, 'FaceAlpha', 1); + + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 3); + han = addbrain('hires left'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 4); + han = addbrain('limbic'); set(han, 'FaceAlpha', 1); + axis image; lightRestoreSingle; + lighting gouraud + drawnow + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + + set(han(end), 'FaceAlpha', .2) + han2 = addbrain('brainstem'); + cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, han); + view(135, 15) + + create_figure('Brain_Surface', 2, 2, 1); + subplot(2, 2, 1) + han = findobj(gca,'Type','patch'); set(han, 'FaceAlpha',1) + axis image + + if dosave + scn_export_papersetup(600); saveas(gcf, 'Surface1', 'png'); + subplot(2, 2, 1); + view(135, 20); lightRestoreSingle; + saveas(gcf, 'Surface2', 'png'); + view(225, 20); lightRestoreSingle; + saveas(gcf, 'Surface3', 'png'); + view(90, 3); lightRestoreSingle; + saveas(gcf, 'Surface4', 'png'); + view(270, 3); lightRestoreSingle; + saveas(gcf, 'Surface5', 'png'); + end + + end + + %% NONPARAMETRIC TEST + + if dononparam + + % Permute : covariates only + % ============================================== + if ~isempty(covs) + clear my_outcomesp all_r pe + ndims = min(size(covs{1}, 2) - 1, size(covs{1}, 1) - 2); % will not work for multilevel with diff. dimensions per dataset + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + if size(covs, 2) > 1 + STATSpcovs = xval_regression_multisubject('ridge', my_outcomesp, covs, 'noverbose'); %,'pca', 'ndims', ndims); + else + STATSpcovs = xval_regression_multisubject('ols', my_outcomesp, covs, 'noverbose'); %,'pca', 'ndims', ndims); + end + + fprintf('%3.2f ', STATSpcovs.r_each_subject); + + all_r(i) = STATSpcovs.r_each_subject; + pe(i) = STATSpcovs.pred_err; + + end + + STATS_FINAL.covs.permuted.pe_values = pe; + STATS_FINAL.covs.permuted.pe_mean = mean(pe); + STATS_FINAL.covs.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.covs.permuted.r_values = all_r; + STATS_FINAL.covs.permuted.r_mean = mean(all_r); + STATS_FINAL.covs.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + end + + % Permute : random subset of 1000 voxels + % ============================================== + n_vox = 1000; + if size(dat{1}, 2) > n_vox + clear my_outcomesp my_datp all_r pe + ndims = size(dat{1}, 1) - 2; + + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + ix2 = randperm(size(dat{s}, 2)); + + my_outcomesp{s} = my_outcomes{s}(ix); + my_datp{s} = dat{s}(:, ix2(1:n_vox)); + + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSpv= xval_regression_multisubject('ridge', my_outcomesp, my_datp, 'pca', 'ndims', ndims); + + fprintf('%3.2f ', STATSpv.r_each_subject); + + all_r(i) = STATSpv.r_each_subject; + pe(i) = STATSpv.pred_err; + + end + + STATS_FINAL.full_model.permuted.v1000_pe_values = pe; + STATS_FINAL.full_model.permuted.v1000_pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.v1000_pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + STATS_FINAL.full_model.permuted.v1000_r_values = all_r; + STATS_FINAL.full_model.permuted.v1000_r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.v1000_r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + end + + % Permute : Full data + % ============================================== + clear my_outcomesp all_r pe + ndims = size(dat{1}, 1) - 2; + + for i = 1:niter + + rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... + + my_outcomes = STATS_FINAL.inputOptions.all_outcomes; + + % permute + for s = 1:datasets + ix = randperm(length(my_outcomes{s})); + my_outcomesp{s} = my_outcomes{s}(ix); + end + + %% Do the prediction + % ------------------------------------------------------------------ + STATSp = xval_regression_multisubject('ridge', my_outcomesp, dat, 'pca', 'ndims', ndims); + + fprintf('%3.2f ', STATSp.r_each_subject); + + all_r(i) = STATSp.r_each_subject; + pe(i) = STATSp.pred_err; + + end + + STATS_FINAL.full_model.permuted.pe_values = pe; + STATS_FINAL.full_model.permuted.pe_mean = mean(pe); + STATS_FINAL.full_model.permuted.pe_ci = [prctile(pe, 5) prctile(pe, 95)]; + + STATS_FINAL.full_model.permuted.r_values = all_r; + STATS_FINAL.full_model.permuted.r_mean = mean(all_r); + STATS_FINAL.full_model.permuted.r_ci = [prctile(all_r, 5) prctile(all_r, 95)]; + if dosave + save STATS_xval_output -append STATS_FINAL + end + + end % if do nonparam + +end % main function + + + +% - Extra stuff - + +% % %% Bootstrap the covariates +% % % +% % % This is the "Leave one out bootstrap" of Efron and Tibshirani, JASA, 1997 +% % % Err(1) +% % % They show that this can be done by taking the error on each point from +% % % bootstrap samples that do not happen to contain that point +% % % +% % % they say that for continuous outcomes and predictors (our case), they +% % % expect the cross-val estimate and the 632+ bootstrap estimate to be more +% % % similar. the benefit is mainly for discontinuous outcomes (1/0) +% % % +% % % they say that Err(1) is biased upwards compared to the nearly unbiased +% % % CV(1) (leave-one-out cross-validation) +% % % it estimates "half-sample" xval +% % +% % bootfun = @(Y, X) xval_regression_boostrap_wrapper(Y, X); +% % STATS_FINAL.bootstrap.covs_only_r_rsq = bootstrp(200, bootfun, my_outcomes_orig{1}, covs{1}); +% % STATS_FINAL.bootstrap.covs_only_summary = [mean(STATS_FINAL.bootstrap.covs_only_r_rsq) std(STATS_FINAL.bootstrap.covs_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.covs_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = bootstrp(20, bootfun, my_outcomes_orig{1}, dat{1}); +% % +% % STATS_FINAL.bootstrap.dat_only_r_rsq = [STATS_FINAL.bootstrap.dat_only_r_rsq; bootstrp(80, bootfun, my_outcomes_orig{1}, dat{1})]; +% % +% % STATS_FINAL.bootstrap.dat_only_summary = [mean(STATS_FINAL.bootstrap.dat_only_r_rsq) std(STATS_FINAL.bootstrap.dat_only_r_rsq) ]; +% % STATS_FINAL.bootstrap.dat_only_summary_descrip = 'mean std of bootstrapped values' +% % +% % %% Nonparametric test with N variables (voxels) selected at random +% % +% % s = 1; +% % +% % +% % n_vox = [1000:2000:50000]; +% % niter = [1 50]; +% % +% % if niter(1) == 1, all_r_perm = zeros(niter(2), length(n_vox + 1)); end +% % +% % for i = 1:length(n_vox) +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject('ridge', {my_outcomes{1}(ix)}, {dat{1}(:, ix2(1:n_vox(i)))}, 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow +% % end +% % +% % % with full sample +% % n_vox(end+1) = size(dat{s}, 2); +% % i = length(n_vox); +% % +% % for j = niter(1):niter(2) +% % +% % rand('twister',sum(100*clock)) ; % trying this; was getting wacky results suggesting randperm is not producing indep. perms... +% % +% % ix = randperm(length(my_outcomes{s})); +% % %ix2 = randperm(size(dat{s}, 2)); +% % +% % STATSperm= xval_regression_multisubject('ridge', {my_outcomes{1}(ix)}, dat(1), 'pca', 'ndims', ndims); +% % +% % all_r_perm(j, i) = STATSperm.r_each_subject; +% % +% % end +% % +% % create_figure('permuted'); +% % plot(n_vox(1:size(all_r_perm, 2)), mean(all_r_perm), 'ko-', 'Linewidth', 3); +% % xlabel('Number of voxels'); ylabel('Average predicted/actual correlation'); +% % drawnow diff --git a/Statistics_tools/xval_select_holdout_set.m b/Statistics_tools/xval_select_holdout_set.m new file mode 100644 index 00000000..0e3eb44a --- /dev/null +++ b/Statistics_tools/xval_select_holdout_set.m @@ -0,0 +1,177 @@ +function wh_holdout_final = xval_select_holdout_set(Y, covs, nfolds, holdout_proportion, varargin) + % + % wh_holdout_final = xval_select_holdout_set(Y, covs, nfolds, holdout_proportion, [doplot]) + % + % Selects one or more holdout sets balancing on an outcome and set of + % covariates. The idea is inspired by Rubin's propensity score method, and + % the logic is that selecting holdout sets that are balanced on the outcome + % and other variables of no interest will reduce the variance in the + % estimated predictive accuracy in a cross-validated parameter optimization + % scheme. The goal of this algorithm is thus to choose holdout sets where + % the probability of holdout assignment is balanced on (independent of) the covariates. + % + % For assessing accuracy with little bias, a leave-one-out holdout method + % can be a good choice, as it has minimum bias. However, the accuracy + % estimates have high variance, which often motivates k-fold schemes with + % larger holdout sets. For model/parameter selection (e.g., in inner + % cross-validation schemes, or when model selection is the primary goal), + % larger holdout sets and small training sets may be desirable. See Shao, + % 1998, JASA. + % + % It is also advantageous to have training/holdout sets that are independent + % of one another, but this is not optimized here. + % + % Input variables: + % Y = outcome variable + % covs = covariates to balance on + % nfolds = number of holdout sets desired + % holdout_proportion = how much of the sample is reserved for holdout, [0 1] + % + % Output variables: + % wh_holdout = a matrix of [obs x nfolds], containing optimized holdout sets + % + % Example inputs: + % Y = randn(30, 1); + % covs = randn(N, 1); + % nfolds = 10; + % holdout_proportion = .5; + % + % Example function call: + % wh_holdout = xval_select_holdout_set(Y, covs, 20, .5); + % + % Notes: + % There is a plot option hard-coded in the script for demonstration + % purposes. + + doplot = 0; + if ~isempty(varargin), doplot = varargin{1}; end + + anyzero = 1; + wh_holdout_final = []; + + wh_holdout_final = generate_balanced_sets(Y, covs, nfolds, holdout_proportion, doplot); + + % find any missing observations + % Make sure all observations are selected equally often, to the + % degree possible + % Find the holdout sets that select the most frequently used observations + obssum = sum(wh_holdout_final, 2); + ismissing = double(obssum == 0); + + while any(ismissing) + + wh_holdout_new = generate_balanced_sets(Y, covs, nfolds, holdout_proportion, doplot); % obs x sets + + new_to_save = wh_holdout_new' * ismissing; % these holdout sets have the missing obs from before + + %frequent_obs_score = wh_holdout_new' * obssum; % higher values mean the holdout set uses more frequently selected observations (bad) + + % Replace the ones with the most frequent scores with the new ones + % to keep + frequent_obs_score = wh_holdout_final' * obssum; % higher values mean the holdout set uses more frequently selected observations (bad) + [f2, indx] = sort(frequent_obs_score, 1, 'descend'); + + if any(new_to_save) + + new_to_save = find(new_to_save); + wh_to_replace = indx(1:length(new_to_save)); + wh_holdout_final(:, wh_to_replace) = wh_holdout_new(:, new_to_save); + + % it is possible that we've replaced one that causes another obs to + % be missing, so we need the while loop + obssum = sum(wh_holdout_final, 2); + ismissing = double(obssum == 0); + else + + %% do nothing ismissing = 1; + + end + + end + + + +end % function + + + +function wh_holdout_final = generate_balanced_sets(Y, covs, nfolds, holdout_proportion, doplot) + + N = length(Y); + + niter = nfolds * 10; + [betavals, tvals] = deal(zeros(1, niter)); + wh_holdout = false(N, niter); + + warning off % iteration limit... + + for i = 1:niter + + wh_holdout(:, i) = zeros(N, 1); + nh = round(N .* holdout_proportion); + + wh = randperm(N); + wh = wh(1:nh); + wh_holdout(wh, i) = 1; + + % For testing purposes + %covs = mvnrnd([1 3], [1 .4; .4 1], N); % random outcome and covs + + % p-value is linear with respect to betas near the origin (50%) + % so we can select subsets based on beta alone. + % + b = glmfit([Y covs], wh_holdout(:, i), 'binomial', 'link', 'logit'); + + % This is used for testing purposes + if doplot + [b, dev, stat] = glmfit([Y covs], wh_holdout(:, i), 'binomial', 'link', 'logit'); + + % + % % plot partial fit wrt Y + % yfit = [min(Y):.1:max(Y)]; + % xfit = glmval(b(1:2), yfit, 'logit'); + % + % create_figure; plot(Y, wh_holdout(:, i), 'ko','MarkerFaceColor', 'k'); + % set(gca, 'XLim', [-.5 1.5]); + % plot(yfit, xfit, 'r'); + % ylabel('P(holdout)'); xlabel('outcome'); + + tvals(i) = stat.t(2); + end + + betavals(i) = b(2); + % + + end + + warning on + + % Save the most balanced holdout sets + + cutoff = prctile(abs(betavals), 10); % save top 10% -- that ensures us the right size holdout set + wh_to_keep = abs(betavals) <= cutoff; + wh_holdout_final = wh_holdout(:, wh_to_keep); + + if doplot + create_figure('plot'); plot_correlation_samefig(betavals, tvals); + plot(betavals(wh_to_keep), tvals(wh_to_keep), 'go'); + xlabel('Logistic reg beta'); ylabel('T-value'); + title('Selection set estimates for logistic reg, outcome') + + % plot partial fit wrt Y for one example holdout set + [b, dev, stat] = glmfit([Y covs], wh_holdout(:, 1), 'binomial', 'link', 'logit'); + + yfit = [min(Y):.1:max(Y)]; + xfit = glmval(b(1:2), yfit, 'logit'); + + create_figure('Example holdout set'); plot(Y, wh_holdout(:, 1), 'ko','MarkerFaceColor', 'k'); + set(gca, 'XLim', [-.5 1.5]); + plot(yfit, xfit, 'r'); + ylabel('P(holdout)'); xlabel('Outcome'); + axis auto + title('Example holdout set: P(holdout|Y)') + drawnow + end + +end + diff --git a/Statistics_tools/xval_select_holdout_set_categoricalcovs.m b/Statistics_tools/xval_select_holdout_set_categoricalcovs.m new file mode 100644 index 00000000..c001458a --- /dev/null +++ b/Statistics_tools/xval_select_holdout_set_categoricalcovs.m @@ -0,0 +1,48 @@ +function holdout_sets = xval_select_holdout_set_categoricalcovs(covs) + +u = unique(covs, 'rows'); +nu = size(u, 1); % number of unique cells; also size of holdout sets + +[N, k] = size(covs); + +onevec = ones(N, 1); + +% indices for each cell +for i = 1:nu + cell_indices{i} = all(covs - u(i * onevec, :) == 0, 2); +end + +nobs_per_cell = sum(cat(2, cell_indices{:})); +nfolds = max(nobs_per_cell); % number of folds; max() leaves some unbalanced holdout sets; min keeps balance but does not test all obs + +% random selection of one obs per cell for each fold +for i = 1:nu + wh_obs{i} = find(cell_indices{i}); + wh_obs{i} = wh_obs{i}(randperm(nobs_per_cell(i))); +end + +% deal observations into folds +for i = 1:nfolds + wh = []; + for j = 1:nu + if nobs_per_cell(j) >= i + wh = [wh wh_obs{j}(i)]; + end + end + + holdout_sets{i} = logical(false * onevec); + holdout_sets{i}(wh) = true; + +end + +end % function + +% checking stuff +% allsets = cat(2, holdout_set{:}); +% sum(allsets) +% sum(allsets, 2) +% covs(holdout_set{1}, :) +% covs(holdout_set{2}, :) +% covs(holdout_set{3}, :) +% covs(holdout_set{4}, :) +% covs(holdout_set{end}, :) \ No newline at end of file diff --git a/Statistics_tools/xval_simple_ols_featureselect.m b/Statistics_tools/xval_simple_ols_featureselect.m new file mode 100644 index 00000000..b31cb8f4 --- /dev/null +++ b/Statistics_tools/xval_simple_ols_featureselect.m @@ -0,0 +1,123 @@ +function pred_value = xval_simple_ols_featureselect(X, Y, pthreshold, holdout_method) +% Check: a very simple leave-one-out cross-validated regression +% pred_value = xval_simple_ols_loo(X, Y, p-value selection for univariate feature selection, holdout_method) +% +% pred_value: cross-validated predictions of outcome data +% X: n x variables matrix of predictors +% Y: n x 1 vector of outcomes +% +% Tor Wager, June 2010 +% +% Go to any LASSO output directory and run this: +% +% maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); +% dat{1} = iimg_get_data(maskInfo, anticimages); +% pred_value = xval_simple_ols_loo(X, Y) + + +[N, k] = size(X); +pred_value = zeros(N, 1); + +holdout_set = nested_select_holdout_set; + +create_figure('test', 1, 2); + +fprintf('Fold: %03d', 0); +for wh_fold = 1:length(holdout_set) + + fprintf('\b\b\b%3.0f ', wh_fold); + + % select training data + Xi = X; + Yi = Y; + + Yi(holdout_set{wh_fold}) = []; + Xi(holdout_set{wh_fold}, :) = []; % leave out the missing observation(s) + + Xtest = X(holdout_set{wh_fold}, :); + Ytest = Y(holdout_set{wh_fold}, :); % only used later when we test prediction + + ntrain = size(Xi, 1); + nholdout = size(Xtest, 1); + + % select features based on univariate correlations + [r, p, Tstat] = correlation_fast_series(Xi, Yi); + wh_features = p <= pthreshold; + nfeatures(wh_fold) = sum(wh_features); + if nfeatures(wh_fold) == 0 + disp('Warning: no features pass threshold'); + wh_features = p <= prctile(p, 10); + end + + Xi = Xi(:, wh_features); + + Xi = [ones(ntrain, 1) Xi]; % add intercept + + % Make prediction + b = pinv(Xi) * Yi; + pred_value(holdout_set{wh_fold}, 1) = [ones(nholdout, 1) Xtest(:, wh_features)] * b; + + create_figure('test', 1, 2, 1); subplot(1, 2, 1); plot(Xi*b, Yi, 'ko'); + plot(pred_value(holdout_set{wh_fold}, 1), Ytest, 'ro', 'MarkerFaceColor', 'r'); + subplot(1, 2, 2); + title('Black circles = training set; Red = holdout obs'); + drawnow; + +end + +cm = colormap(jet(N)); +figure; hold on; +for i = 1:N + plot(pred_value(i), Y(i), 'ko', 'MarkerFaceColor', cm(i, :)); +end +xlabel('Predicted outcome (xval)'); ylabel('Outcome'); +title('Color = order in data series'); + + + + +function holdout_set = nested_select_holdout_set + % purpose: return holdout_set variable + + nobs_s = length(Y); + + switch lower(holdout_method) + case 'loo' + holdout_set = cell(1, nobs_s); + for i = 1:nobs_s, holdout_set{i} = i; end + + case 'l4o_covbalanced' + disp('Selecting holdout sets: Balancing on covariates and outcome, and also trying to ensure that each obs is selected equally often.'); + holdout_proportion = 4 ./ nobs_s; % prop for leave-4-out + nfolds = 40; + if isempty(cov_val) + wh_holdout = xval_select_holdout_set(Y, [], nfolds, holdout_proportion, verbose); + else + wh_holdout = xval_select_holdout_set(Y, cov_val, nfolds, holdout_proportion, verbose); + end + holdout_set = cell(1, nfolds); + for k = 1:nfolds + holdout_set{k} = wh_holdout(:, k); + end + + case 'categorical_covs' + + holdout_set = xval_select_holdout_set_categoricalcovs(cov_val); + + case 'balanced4' + nfolds = 4; + holdout_set = cell(1, nfolds); + [ys, indx] = sort(Y); + for k = 1:nfolds + + holdout_set{k} = indx(k:nfolds:end); + if isempty(holdout_set{k}), error('Holdout set construction error: Dataset too small?'); end + + end + + otherwise error('Unknown holdout method. See help.'); + end +end + +end % main function + diff --git a/Statistics_tools/xval_simple_ols_loo.m b/Statistics_tools/xval_simple_ols_loo.m new file mode 100644 index 00000000..ff9abf92 --- /dev/null +++ b/Statistics_tools/xval_simple_ols_loo.m @@ -0,0 +1,66 @@ +function pred_value = xval_simple_ols_loo(X, Y) +% Check: a very simple leave-one-out cross-validated regression +% pred_value = xval_simple_ols_loo(X, Y) +% +% pred_value: cross-validated predictions of outcome data +% X: n x variables matrix of predictors +% Y: n x 1 vector of outcomes +% +% Tor Wager, June 2010 +% +% Go to any LASSO output directory and run this: +% +% maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); +% dat{1} = iimg_get_data(maskInfo, anticimages); +% pred_value = xval_simple_ols_loo(X, Y) + + +[N, k] = size(X); +pred_value = zeros(N, 1); + + +create_figure('test', 1, 2) + +for i = 1:N + + % select training data + Xi = X; Xi(i, :) = []; + Yi = Y; Yi(i) = []; + + Xi = [ones(N-1, 1) Xi]; % add intercept + + % Make prediction + b = pinv(Xi) * Yi; + pred_value(i, 1) = [1 X(i, :)] * b; + %pred_value(i, 1) = X(i, :) * b; + + create_figure('test', 1, 2, 1); subplot(1, 2, 1); plot(Xi*b, Yi, 'ko'); + plot(pred_value(i, 1), Y(i), 'ro', 'MarkerFaceColor', 'r'); + subplot(1, 2, 2); + plot([ones(N, 1) X] * b, 'k'); + title('Black circles = training set; Red = holdout obs'); + %plot(X * b, 'k'); + drawnow; + %pause(.1); + + %intercept_vals(i, 1) = b(1); +end + +cm = colormap(jet(N)); +figure; hold on; +for i = 1:N + plot(pred_value(i), Y(i), 'ko', 'MarkerFaceColor', cm(i, :)); +end +xlabel('Predicted outcome (xval)'); ylabel('Outcome'); +title('Color = order in data series'); + + +% figure; hold on; +% for i = 1:N +% plot(intercept_vals(i), pred_value(i), 'ko', 'MarkerFaceColor', cm(i, :)); +% end + +% +% figure; hold on; +% plot(pred_value(whorder(1:24)), Y(whorder(1:24)), 'bo', 'MarkerFaceColor', 'b'); +% plot(pred_value(whorder(25:end)), Y(whorder(25:end)), 'ro', 'MarkerFaceColor', 'r'); diff --git a/Statistics_tools/xval_simple_ols_loo_featureselect.m b/Statistics_tools/xval_simple_ols_loo_featureselect.m new file mode 100644 index 00000000..54d86b44 --- /dev/null +++ b/Statistics_tools/xval_simple_ols_loo_featureselect.m @@ -0,0 +1,75 @@ +function pred_value = xval_simple_ols_loo(X, Y, pthreshold) +% Check: a very simple leave-one-out cross-validated regression +% pred_value = xval_simple_ols_loo(X, Y, p-value selection for univariate feature selection) +% +% pred_value: cross-validated predictions of outcome data +% X: n x variables matrix of predictors +% Y: n x 1 vector of outcomes +% +% Tor Wager, June 2010 +% +% Go to any LASSO output directory and run this: +% +% maskInfo = iimg_read_img(fullfile(pwd, 'mask.img'), 2); +% dat{1} = iimg_get_data(maskInfo, anticimages); +% pred_value = xval_simple_ols_loo(X, Y) + + +[N, k] = size(X); +pred_value = zeros(N, 1); + + +create_figure('test', 1, 2); + +fprintf('Fold: %03d', 0); +for i = 1:N + + fprintf('\b\b\b%3.0f ', i); + + % select training data + Xi = X; Xi(i, :) = []; + Yi = Y; Yi(i) = []; + + % select features based on univariate correlations + [r, p, Tstat] = correlation_fast_series(Xi, Yi); + wh_features = p <= pthreshold; + nfeatures(i) = sum(wh_features); + if nfeatures(i) == 0 + disp('Warning: no features pass threshold'); + wh_features = p <= prctile(p, 10); + else + Xi = Xi(:, wh_features); + end + + Xi = [ones(N-1, 1) Xi]; % add intercept + + % Make prediction + b = pinv(Xi) * Yi; + pred_value(i, 1) = [1 X(i, wh_features)] * b; + + create_figure('test', 1, 2, 1); subplot(1, 2, 1); plot(Xi*b, Yi, 'ko'); + plot(pred_value(i, 1), Y(i), 'ro', 'MarkerFaceColor', 'r'); + subplot(1, 2, 2); + title('Black circles = training set; Red = holdout obs'); + drawnow; + +end + +cm = colormap(jet(N)); +figure; hold on; +for i = 1:N + plot(pred_value(i), Y(i), 'ko', 'MarkerFaceColor', cm(i, :)); +end +xlabel('Predicted outcome (xval)'); ylabel('Outcome'); +title('Color = order in data series'); + + +% figure; hold on; +% for i = 1:N +% plot(intercept_vals(i), pred_value(i), 'ko', 'MarkerFaceColor', cm(i, :)); +% end + +% +% figure; hold on; +% plot(pred_value(whorder(1:24)), Y(whorder(1:24)), 'bo', 'MarkerFaceColor', 'b'); +% plot(pred_value(whorder(25:end)), Y(whorder(25:end)), 'ro', 'MarkerFaceColor', 'r'); diff --git a/Visualization_functions/Support/bar_centers.m b/Visualization_functions/Support/bar_centers.m new file mode 100644 index 00000000..2c862e05 --- /dev/null +++ b/Visualization_functions/Support/bar_centers.m @@ -0,0 +1,15 @@ +% function centers = bar_centers(handles) +% +% BAR_CENTERS determines the actual X centers of grouped bars for further plotting purposes +% Inputs: +% handles - a handle or handles to start searching from. Ideally, this should be the barseries handle(s), but axes handles will usually work, too +% Outputs: +% centers - a list of centers, one row per group + +function centers = bar_centers(handles) + hPatches = findobj(handles, 'Type', 'patch'); + for i=1:length(hPatches) + centers(i,:) = mean(get(hPatches(i), 'XData')); + end + centers = flipud(centers); +end \ No newline at end of file diff --git a/Visualization_functions/Support/drawbox.m b/Visualization_functions/Support/drawbox.m new file mode 100644 index 00000000..6f5cb53a --- /dev/null +++ b/Visualization_functions/Support/drawbox.m @@ -0,0 +1,9 @@ +function h1 = drawbox(time,dur,ystart,yheight,color); +% h1 = drawbox(xstart,xlen,ystart,ylen,color); +% +x = [0 1 1 0]; x = x * dur + time; +y = [0 0 1 1]; y = y * yheight + ystart; + +h1 = fill(x,y,color,'FaceAlpha',.5); + +return diff --git a/Visualization_functions/Support/enlarge_axes.m b/Visualization_functions/Support/enlarge_axes.m new file mode 100755 index 00000000..8ed3e4bc --- /dev/null +++ b/Visualization_functions/Support/enlarge_axes.m @@ -0,0 +1,41 @@ +% function enlarge_axes(figure_handle, scale_factor, [zoom value]) +% +% Shrink: +% enlarge_axes(gcf, .8) +% +% Grow: +% enlarge_axes(gcf, 1.3) +% +% Grow and zoom: +% enlarge_axes(gcf, 1.3, 1.2) +% +% Just zoom in: +% enlarge_axes(gcf, 1, 1.2) +% +function enlarge_axes(fh, scale_factor, varargin) + + zoomval = 0; + if length(varargin) > 0, zoomval = varargin{1}; end + + figure(fh); + + if nargin < 2, scale_factor = 1.35; end + + h = findobj(get(gcf, 'Children'), 'Type', 'axes'); + for i = 1:length(h) + pp = get(h(i), 'Position'); +% +% +% set(h(i), 'OuterPosition', pp); +% set(h(i), 'Position', pp); + + + set(h(i), 'Position', [pp(1) pp(2) pp(3)*scale_factor pp(4)*scale_factor]); + + if zoomval + axes(h(i)) + camzoom(zoomval) + end + + end +end diff --git a/Visualization_functions/Support/equalize_axes.m b/Visualization_functions/Support/equalize_axes.m new file mode 100755 index 00000000..c800f7ec --- /dev/null +++ b/Visualization_functions/Support/equalize_axes.m @@ -0,0 +1,40 @@ +function equalize_axes(hvec,varargin) +%equalize_axes(hvec,[y only!]) +% hvec is vector of axis handles +% tor wager + +if length(varargin) > 0, yonly = 1; else yonly = 0; end + +hvec(~strcmp(get(hvec, 'Type'), 'axes')) = []; + +if isempty(hvec), disp('No valid axes'); return, end + +for i = 1:length(hvec) + + xm = get(hvec(i),'XLim'); + xmax(i) = xm(2); + xmin(i) = xm(1); + + ym = get(hvec(i),'YLim'); + ymax(i) = ym(2); + ymin(i) = ym(1); + +end + +xmax = max(xmax); +xmin = min(xmin); +ymax = max(ymax); +ymin = min(ymin); + + +for i = 1:length(hvec) + + axes(hvec(i)) + if yonly + set(gca,'YLim',[ymin ymax]) + else + axis([xmin xmax ymin ymax]) + end +end + +end \ No newline at end of file diff --git a/Visualization_functions/Support/fill_around_line.m b/Visualization_functions/Support/fill_around_line.m new file mode 100755 index 00000000..e288ecfc --- /dev/null +++ b/Visualization_functions/Support/fill_around_line.m @@ -0,0 +1,34 @@ +function hh = fill_around_line(dat,err,color,varargin) +% h =fill_around_line(dat,err,color,[x indices]) +% fills a region around a vector (dat) with + or - err +% +% used in tor_fill_steplot.m + +if length(err) == 1 + err = repmat(err,length(dat),1); +end + +hh = []; + +xn = 1:length(err); +if length(varargin) > 0, xn = varargin{1}; end + +for i = 1:length(err)-1 + hold on; + yplus1 = dat(i)+err(i); + yplus2 = dat(i+1)+err(i+1); + yminus1= dat(i)-err(i); + yminus2 = dat(i+1)-err(i+1); + +% if ischar(color) + + hh(end+1) = fill([xn(i) xn(i) xn(i+1) xn(i+1)],[yminus1 yplus1 yplus2 yminus2],color,'Edgecolor','none','FaceAlpha',.3); +% +% else +% hh(end+1) = fill([xn(i) xn(i) xn(i+1) xn(i+1)],[yminus1 yplus1 yplus2 yminus2],'FaceColor', color,'Edgecolor','none','FaceAlpha',.3); +% end + +end + +return + diff --git a/Visualization_functions/Support/lightFollowView.m b/Visualization_functions/Support/lightFollowView.m new file mode 100755 index 00000000..c7b54bdf --- /dev/null +++ b/Visualization_functions/Support/lightFollowView.m @@ -0,0 +1,22 @@ +function lightFollowView +% this uses the current axis and sets the lightangle to follow the camera view. +% to set, myLight = camlight(0,0); set(myLight,'Tag','myLight') +% call with: +% set(gcf, 'WindowButtonUpFcn', 'lightFollowView'); + + + %rotate3d('up'); % call default processing (3D rotation enabled) + % Matlab 7.2 compat. : changed by Tor, 5/22/06 + rotate3d on + + %---------------------------------------------------------------------------------- + % Set light position into current camera position + %---------------------------------------------------------------------------------- + + set(0,'CurrentFigure', gcf) + set(gcf, 'CurrentAxes', gca); + hLight = findobj('Type', 'light', 'Tag', 'myLight'); + set(hLight, 'Position', get(gca, 'CameraPosition')); + +return + \ No newline at end of file diff --git a/Visualization_functions/Support/lightRestoreSingle.m b/Visualization_functions/Support/lightRestoreSingle.m new file mode 100644 index 00000000..4d5d349f --- /dev/null +++ b/Visualization_functions/Support/lightRestoreSingle.m @@ -0,0 +1,12 @@ +function [h,az,el] = lightRestoreSingle(axishandle) +% [h,az,el] = lightRestoreSingle(axishandle) +% delete all lights from axis and install only a single one +% return handle in h + +if nargin == 0, axishandle = gca; end + +lh = lighthandles(axishandle); +delete(lh) +[az,el]=view; h = lightangle(az,el); + +return diff --git a/Visualization_functions/Support/lighthandles.m b/Visualization_functions/Support/lighthandles.m new file mode 100644 index 00000000..2eed862c --- /dev/null +++ b/Visualization_functions/Support/lighthandles.m @@ -0,0 +1,11 @@ +function lh = lighthandles(axishandle) +%lh = lighthandles(axishandle) +% get all light objects associated with the current axis +% see lightRestoreSingle + +hans = get(axishandle,'Children'); +lh = find(strcmp(get(hans,'Type'),'light')); +lh = hans(lh); + +return + diff --git a/Visualization_functions/Support/montage_zoom.m b/Visualization_functions/Support/montage_zoom.m new file mode 100755 index 00000000..f7f465c7 --- /dev/null +++ b/Visualization_functions/Support/montage_zoom.m @@ -0,0 +1,23 @@ +function montage_zoom +% montage_zoom, no arguments +% in a montage created with montage_clusters, +% zooms in on current axis +% +% tor wager + +todel = get(gcf,'Children'); +todel = todel(~(todel == gca)); + +%tmp = find(todel == gca); +%try,delete(todel(1:tmp-2)),catch,end + +set(todel,'Position',[0.01 0.01 0.02 0.02]) + +%todel = get(gcf,'Children'); +%todel = todel(~(todel == gca)); +%delete(todel) + +set(gca,'Position',[0.1300 0.1100 0.7750 0.8150]) + +h = findobj(gca,'Type','text'); +set(h,'FontSize',24) \ No newline at end of file diff --git a/Visualization_functions/Support/movie_rotation.m b/Visualization_functions/Support/movie_rotation.m new file mode 100644 index 00000000..ddcc3604 --- /dev/null +++ b/Visualization_functions/Support/movie_rotation.m @@ -0,0 +1,79 @@ +function mov = movie_rotation(targetaz,targetel,mov) +% mov = movie_rotation(targetaz,targetel,mov) +% +% Make a movie of a rotating brain +% +% Examples: +% +% mov = movie_rotation(135,30) +% mov = movie_rotation(250,30,mov) +% +% Batch mode: +% mov = movie_rotation('batch360.1'); +% mov = movie_rotation('right2left'); +% +% tor wager, may 06 + +% Batch-mode: pre-set movies +if strcmp(targetaz,'batch360.1') + axis vis3d + axis image + view(0,90); axis off; set(gcf,'Color','w'); + mov = movie_rotation(135,30); + mov = movie_rotation(250,30,mov); + mov = movie_rotation(360,90,mov); + mov = close(mov); + return +elseif strcmp(targetaz,'right2left') + + + + +if nargin < 3 || isempty(mov) + mov = avifile('mymovie.avi','Quality',90,'Compression','None','Fps',10); +end + + % add to existing + %O = struct('add2movie',[],'zoom',1,'azOffset',[],'elOffset',[],'timecourse',[],'fps',5,'length',6); + + axis vis3d + + [az,el]=view; + + myaz = linspace(az,targetaz,20); + myel = linspace(el,targetel,20); + + for i = 1:length(myaz) + + H = gca; + drawnow + + try + mov = addframe(mov,H); + catch + disp('Cannot write frame. Failed to set stream format??') + mov = close(mov); + end + + view(myaz(i),myel(i)); + + axis image + lightRestoreSingle(gca); + % if i >= 10, lightFollowView, end + % + % if i == 10, + % lighting phong + % end + + end + + try + mov = addframe(mov,H); + catch + disp('Cannot write frame. Failed to set stream format??') + mov = close(mov); + end + +return + + \ No newline at end of file diff --git a/Visualization_functions/Support/movie_tools.m b/Visualization_functions/Support/movie_tools.m new file mode 100644 index 00000000..a877ad59 --- /dev/null +++ b/Visualization_functions/Support/movie_tools.m @@ -0,0 +1,397 @@ +function mov = movie_tools(meth,varargin) +% Make a movie of a rotating brain +% mov = movie_tools('rotate',targetaz,targetel,mov,movlength) +% +% Examples: +% +% mov = movie_tools('rotate',135,30) +% mov = movie_tools('rotate',250,30,mov) +% +% mov = movie_tools('rotate',targetaz,targetel,mov,movlength,starttranspval,endtranspval,handles_for_trans) +% mov = movie_tools('rotate',270,30,mov,3,.05,.8,p9); % with transparency +% Batch mode: +% mov = movie_tools('batch360.1'); +% mov = movie_tools('right2left'); +% +% mov = movie_tools('transparent',startt,endt,handles,mov,movlength) +% mov = movie_tools('transparent',1,.15,p10,mov,2.5); +% +% Add still frames +% mov = movie_tools('still',mov,1); +% mov = movie_tools('still',mov,movlength); +% +% zoom +% mov = movie_tools('zoom',.8,mov,1); +% +% +% add lines that trace over time +% mov = movie_tools('lines',startcoords,endcoords,mov,color,bendval,movlength,startt,endt,handles,targetaz,targetel); +% mov2 = movie_tools('lines',x,y,[],[],[0 -.1 0],2,[],[],[],[],[]); +% +% Save movie: +% This has been changing through the years, but here's the latest +% as of 2013 March: +% vid = VideoWriter('faculty_maps.avi'); +% open(vid) +% for i = 1:length(mov), writeVideo(vid, mov(i)); end +% close(vid) +% +% +% tor wager, may 06 +% updated sept 06 + +mov = []; +movlength = 3; % in s + +global FRAMESTYLE +global DRYRUN + +DRYRUN = 0; +if DRYRUN, disp('DRY RUN -- NOT SAVING FRAMES'); end + +FRAMESTYLE = 'matlab'; % 'matlab' or 'avi', hard-coded here + +switch meth + % Batch-mode: pre-set movies + + case'batch360.1' + axis vis3d, axis image + view(0,90); axis off; %set(gcf,'Color','w'); + mov = movie_tools('rotate',135,30,mov); + mov = movie_tools('rotate',250,30,mov); + mov = movie_tools('rotate',360,90,mov); + + if strcmp(FRAMESTYLE, 'avi'), mov = close(mov); end + return + + case 'right2left' + axis vis3d, axis image + view(240,30) + mov = movie_tools('rotate',90,5,mov); + + % command modes + + case 'rotate' + targetaz = varargin{1}; + targetel = varargin{2}; + startt = []; endt = []; handles = []; + if length(varargin) > 2, mov = varargin{3}; end + if length(varargin) > 3, movlength = varargin{4}; end + if length(varargin) > 4, startt = varargin{5}; end + if length(varargin) > 5, endt = varargin{6}; end + if length(varargin) > 6, handles = varargin{7}; end + mov = movie_rotation(targetaz,targetel,mov,movlength,startt,endt,handles); + + + case 'transparent' + startt = varargin{1}; + endt = varargin{2}; + if length(varargin) > 2, handles = varargin{3}; end + if length(varargin) > 3, mov = varargin{4}; end + if length(varargin) > 4, movlength = varargin{5}; end + + mov = movie_transparent(startt,endt,handles,mov,movlength); + + case 'zoom' + myzoom = varargin{1}; + if length(varargin) > 1, mov = varargin{2}; end + if length(varargin) > 2, movlength = varargin{3}; end + + mov = movie_zoom(myzoom,mov,movlength); + + + case 'still' + if length(varargin) > 0, mov = varargin{1}; end + if length(varargin) > 1, movlength = varargin{2}; end + + mov = movie_still(mov,movlength) + + + case 'lines' + % notes: this can be used to do combos of any of rotation, + % transparency, and lines + + % defaults + mov = []; color = 'k'; bendval = 0; + startt = 1; endt1 = 1; handles = []; + targetaz = []; targetel = []; + + startcoords = varargin{1}; + endcoords = varargin{2}; + if length(varargin) > 2, mov = varargin{3}; end + if length(varargin) > 3, color = varargin{4}; end + if length(varargin) > 4, bendval = varargin{5}; end + if length(varargin) > 5, movlength = varargin{6}; end + + if length(varargin) > 6, startt = varargin{7}; end + if length(varargin) > 7, endt = varargin{8}; end + if length(varargin) > 8, handles = varargin{9}; end + + if length(varargin) > 9, targetaz = varargin{10}; end + if length(varargin) > 10, targetel = varargin{11}; end + + mov = movie_lines(startcoords,endcoords,mov,color,bendval,movlength,startt,endt,handles,targetaz,targetel); + + otherwise + error('Unknown method') + +end + + +return + + + +% ------------------------------------------------------------ +% rotation +% ------------------------------------------------------------ + +function mov = movie_rotation(targetaz,targetel,mov,movlength,varargin) + +if nargin < 4, movlength = 3; end % in s + +[mov,nframes,axh,az,el] = setup_movie(mov,movlength); + +% setup optional transparency +dotrans = 0; +if length(varargin) > 0 && ~isempty(varargin{1}) + dotrans = 1; + startt = varargin{1}; + endt = varargin{2}; + handles = varargin{3}; + mytrans = linspace(startt,endt,nframes); +end + +myaz = linspace(az,targetaz,nframes); +myel = linspace(el,targetel,nframes); + +for i = 1:nframes + + mov = add_a_frame(mov,axh); + view(myaz(i),myel(i)); + + if dotrans + set(handles,'FaceAlpha',mytrans(i)); + end + +end + +mov = add_a_frame(mov,axh); + +return + + + +% ------------------------------------------------------------ +% still +% ------------------------------------------------------------ + +function mov = movie_still(mov,movlength) + +if nargin < 2, movlength = 3; end % in s + +[mov,nframes,axh,az,el] = setup_movie(mov,movlength); +for i = 1:nframes + mov = add_a_frame(mov,axh); +end +return + + + +% ------------------------------------------------------------ +% transparency +% ------------------------------------------------------------ + +function mov = movie_transparent(startt,endt,handles,mov,movlength) + +if nargin < 4, movlength = 3; end % in s + + +[mov,nframes,axh,az,el] = setup_movie(mov,movlength); + +mytrans = linspace(startt,endt,nframes); + +for i = 1:nframes + + mov = add_a_frame(mov,axh); + set(handles,'FaceAlpha',mytrans(i)); + +end + +mov = add_a_frame(mov,axh); + +return + + +% ------------------------------------------------------------ +% zoom +% ------------------------------------------------------------ + +function mov = movie_zoom(myzoom,mov,movlength) + +if nargin < 3, movlength = 3; end % in s + + +[mov,nframes,axh,az,el] = setup_movie(mov,movlength); + +myzoom = 1 + ( (myzoom-1) ./ nframes); + +for i = 1:nframes + + mov = add_a_frame(mov,axh); + camzoom(myzoom); + +end + +mov = add_a_frame(mov,axh); + +return + + + +% ------------------------------------------------------------ +% lines (and rotate and transparency) +% ------------------------------------------------------------ +function mov = movie_lines(startcoords,endcoords,mov,color,bendval,movlength,startt,endt,handles,targetaz,targetel) + + +[mov,nframes,axh,az,el] = setup_movie(mov,movlength); + +if isempty(targetaz), targetaz = az; end +if isempty(targetel), targetel = el; end + +% set up rotation +% --------------------------------- +myaz = linspace(az,targetaz,nframes); +myel = linspace(el,targetel,nframes); + +% set up transparency +% --------------------------------- +if ~isempty(startt) + dotrans = 1; + mytrans = linspace(startt,endt,nframes); +else + dotrans = 0; +end + +% set up lines +% --------------------------------- +if isempty(color), color = 'k'; end + + +% get entire path +for i = 1:size(startcoords,1) % coords are one triplet per row. each row is a graphic line + + out{i} = nmdsfig_tools('connect3d',startcoords(i,:),endcoords(i,:),color,4,bendval,nframes); + delete(out{i}.h); % we will draw this piece by piece later +end + + +% make each frame +% --------------------------------- +lineh = []; +hold on + +for i = 1:nframes + + mov = add_a_frame(mov,axh); + view(myaz(i),myel(i)); + + if dotrans + set(handles,'FaceAlpha',mytrans(i)); + end + + % draw all lines up to point specified for this frame + if ishandle(lineh), delete(lineh); lineh = []; end + + for n = 1:length(out) + h = plot3(out{n}.xcoords(1:i),out{n}.ycoords(1:i),out{n}.zcoords(1:i),'Color', color,'LineWidth',3); + + h2 = plot3(out{n}.xcoords(i),out{n}.ycoords(i),out{n}.zcoords(i),'*','Color',[1 .8 0],'MarkerFaceColor',[1 1 0],'MarkerSize',12); + lineh = [lineh h h2]; + end + + +end + +mov = add_a_frame(mov,axh); + + +return + + + + +function [mov,nframes,axh,az,el] = setup_movie(mov,movlength) + +global FRAMESTYLE + +switch(FRAMESTYLE) + case 'avi' % write to avi format directly + axh = gca; + case 'matlab' % matlab movie format + axh = gcf; % we need whole figure; pixel dims cannot change +end + +fps = 10; +nframes = movlength .* fps; + +switch(FRAMESTYLE) + case 'avi' % write to avi format directly + if isempty(mov) + mov = avifile('mymovie.avi','Quality',75,'Compression','None','Fps',fps); + end + + case 'matlab' % matlab movie format + % do nothing; matlab movie fmt +end + +% add to existing +%O = struct('add2movie',[],'zoom',1,'azOffset',[],'elOffset',[],'timecourse',[],'fps',5,'length',6); + +%axis vis3d, axis image + +[az,el]=view; + +return + + + +function mov = add_a_frame(mov,H) + +global FRAMESTYLE + +global DRYRUN + +if DRYRUN, pause(.1); return, end + +switch(FRAMESTYLE) + case 'avi' + lightRestoreSingle(H); + drawnow + + try + mov = addframe(mov,H); + catch + disp('Cannot write frame. Failed to set stream format??') + mov = close(mov); + end + + case 'matlab' + lightRestoreSingle(gca); + + % make current to avoid including extra junk occluding your figure + figure(gcf); %get(H, 'parent')); + drawnow + + if isempty(mov) + mov = getframe(H); + else + mov(end+1) = getframe(H); + end + + otherwise + error('Unknown FRAMESTYLE in movie_tools.m'); +end + +return \ No newline at end of file diff --git a/Visualization_functions/Support/plot_onsets.m b/Visualization_functions/Support/plot_onsets.m new file mode 100755 index 00000000..c5744e43 --- /dev/null +++ b/Visualization_functions/Support/plot_onsets.m @@ -0,0 +1,60 @@ +% hh = plot_onsets(d1, [color], [miny], [scalef], [duration]) +% +% plots non-zero elements of an indicator vector or a list of onset times as vertical lines +% +% d1 is indicator vector; uses entries as scaling for height +% +% optional inputs: +% color, e.g., 'r' +% miny, shifts bars up/down to miny +% scalef, scales height of bars +% +% e.g., +% plot_onsets(d1, 'k', -.5, .4) +% +% + +function hh = plot_onsets(d1, varargin) + if ~any(d1(2:end) == 0) % we have an onset list, get indicator + n = round(max(d1)); + z = zeros(n, 1); + z(round(d1)+1) = 1; % 0 is start of 1st onset, 1 of 1st element + d1 = z; % convert to indicator + %wh = d1; + end + + + + % make a row vector + if size(d1, 1) > size(d1, 2) + d1 = d1'; + end + + % indicator + wh = find(d1); + + + color = 'k'; + miny = 0; + scalef = 1; + dur = 0; + + if length(varargin) > 0, color = varargin{1}; end + if length(varargin) > 1, miny = varargin{2}; end + if length(varargin) > 2, scalef = varargin{3}; end + if length(varargin) > 3, dur = varargin{4}; end + + % adjust by -1 so first element is time 0 + if dur == 0 + % events + hh = plot([wh; wh]-1, [zeros(size(wh))+miny; miny+scalef.*d1(wh)], color); + else + % epochs + hh = []; + for i = 1:length(wh) + hh(end+1) = drawbox(wh(i)-1, dur, miny, scalef.*d1(wh(i)), color); + end + end +end + + diff --git a/Visualization_functions/Support/squeeze_axes.m b/Visualization_functions/Support/squeeze_axes.m new file mode 100644 index 00000000..5c8f2a70 --- /dev/null +++ b/Visualization_functions/Support/squeeze_axes.m @@ -0,0 +1,71 @@ +function squeeze_axes(h, squeezepercent) +% squeeze_axes(h, squeezepercent) +% +% Given axis handles h, squeeze them to the left by squeezepercent %. +% This is useful for montages overlapping brain slices. +% +% e.g., for an fmridisplay montage object: +% squeeze_axes(obj.montage{1}.axis_handles, 10) +% +% Negative entries will separate axes, so the operation is reversible: +% squeeze_axes(obj.montage{1}.axis_handles, -10) +% +% you can do this with any axes, e.g., +% h = findobj(gcf, 'Type', 'axes') +% squeeze_axes(h, 10) + +% tor wager, copyright 2011 +% + +% (xperc yperc xextent yextent) +% pos = get(h, 'Position'); +% pos = cat(1, pos{:}); +% +% spacing = diff(pos(:, 1)); % x-position spacing (usually all the same) +% +% shiftby = (spacing .* squeezepercent ./ 100); +% +% shiftby = [0; shiftby .* (1:length(h)-1)']; % cumulative +% +% pos(:, 1) = pos(:, 1) - shiftby; +% +% for i = 1:length(h) +% set(h(i), 'Position', pos(i, :)); +% end +% +% % left bottom justify +pos = get(h, 'Position'); +pos = cat(1, pos{:}); +pos(:, 1) = pos(:, 1) - min(pos(:, 1)) + .05; +pos(:, 2) = pos(:, 2) - min(pos(:, 2)) + .05; + +for i = 1:length(h) +set(h(i), 'Position', pos(i, :)); +end + +% % shrink figure +% % ---------------------------------------------------- +% % [lastpos, wh] = max(pos(:, 1)); +% % lastpos = lastpos + pos(wh, 2) + .05; % right-most value, in % +% +% f = get(h(1), 'Parent'); +% fpos = get(f, 'Position'); +% % (xstart ystart from bottom L, xstretch ystretch) +% +% %fpos(3) = fpos(3) .* lastpos; +% fpos(3) = fpos(3) - (fpos(3) .* squeezepercent/100); +% +% set(f, 'Position', fpos); +% +% % now make x and y axis positions larger +% % ---------------------------------------------------- +% pos = get(h, 'Position'); +% pos = cat(1, pos{:}); +% +% pos(:, [3 4]) = pos(:, [3 4]) + (pos(:, [3 4]) .* squeezepercent/100); +% +% for i = 1:length(h) +% set(h(i), 'Position', pos(i, :)); +% end + +end % function \ No newline at end of file diff --git a/Visualization_functions/Support/steplot.m b/Visualization_functions/Support/steplot.m new file mode 100755 index 00000000..035952c0 --- /dev/null +++ b/Visualization_functions/Support/steplot.m @@ -0,0 +1,103 @@ +function steplot(x,plottype,inwid); +%function steplot(x,plottype,inwid); +% x must be a subs*data matrix. if 3d, will plot 2nd dim as separate lines. +% plottype can be 'b' (bar) or 'l' (line); +% inwid (optional) is the width of the se bars (default 1/10 of bar width). + +if nargin<3 + inwid=10; +end + +if nargin<2 + plottype='l'; +end + +if nargin>1; + if plottype~='l' & plottype~='b' & plottype~='p' & plottype~='x'; + disp('plottype must be line (l) or bar (b) or point or xy'); + end +end + + +if ndims(x)>3 + error('cant plot a 4d matrix'); +end + +if ndims(x)==2; + +np=size(x,2); +% get standard errors +steX=(std(x,[],1))/sqrt(size(x,1)); %standard error is std/root n + +%mean +mx=squeeze(mean(x)); + +%reshape +%figure; +if plottype=='l'; +plot(mx,'linewidth',4); +elseif plottype=='p'; +plot(mx,'o','color','k','markerfacecolor','k','markersize',10); +elseif plottype=='b'; +bar(mx); +end +% set(gca,'Ylim',[ 0 0.5]); + +hold on; + +for n=1:np; + line([n,n],[mx(n)-(steX(n)/2),mx(n)+(steX(n)/2)],'color','k','linewidth',2); + line([n-1/inwid,n+1/inwid],[mx(n)-(steX(n)/2),mx(n)-(steX(n)/2)],'color','k','linewidth',2); + line([n-1/inwid,n+1/inwid],[mx(n)+(steX(n)/2),mx(n)+(steX(n)/2)],'color','k','linewidth',2); +end + +elseif ndims(x)==3; + +if plottype=='l'; +bw=1; %linewidth +np=size(x,3); +X=x(:,:); +steX=std(X,[],1)/sqrt(size(x,1)); +steX=reshape(steX,size(x,2),np); + +mx=squeeze(mean(x)); +%figure; +plot(mx','linewidth',3); +hold on; + +for l=1:size(x,2); +for n=1:np; + line([n,n],[mx(l,n)-(steX(l,n)/2),mx(l,n)+(steX(l,n)/2)],'color','k','linewidth',bw); + line([n-1/inwid,n+1/inwid],[mx(l,n)-(steX(l,n)/2),mx(l,n)-(steX(l,n)/2)],'color','k','linewidth',bw); + line([n-1/inwid,n+1/inwid],[mx(l,n)+(steX(l,n)/2),mx(l,n)+(steX(l,n)/2)],'color','k','linewidth',bw); +end +end + +elseif plottype=='b'; +bw=2; +mx=squeeze(mean(x)); +h=bar(mx'); +h1=get(gca,'Children');a=get(h1);; +np=size(x,3); +X=x(:,:); +steX=std(X,[],1)/sqrt(size(x,1)); +steX=reshape(steX,size(x,2),np); + +for l=1:size(x,2); + a1=get(a(l).Children);a1.XData; + wherex={a1.XData};wherey={a1.YData}; + whereX=wherex{1};whereY=wherey{1}; + wid=(whereX(3)-whereX(1))./inwid; + wX=(whereX(3,:)+whereX(1,:))./2; + wY=(whereY(2,:)); + for n=1:np; + line([wX(n) wX(n)],[wY(n)-steX(l,n) wY(n)+steX(l,n)],'color','k','linewidth',bw); + line([wX(n)-wid wX(n)+wid],[wY(n)-steX(l,n) wY(n)-steX(l,n)],'color','k','linewidth',bw); + line([wX(n)-wid wX(n)+wid],[wY(n)+steX(l,n) wY(n)+steX(l,n)],'color','k','linewidth',bw); + end +end + + +end +end + \ No newline at end of file diff --git a/Visualization_functions/Support/tor_bar_steplot.m b/Visualization_functions/Support/tor_bar_steplot.m new file mode 100755 index 00000000..cff21d44 --- /dev/null +++ b/Visualization_functions/Support/tor_bar_steplot.m @@ -0,0 +1,67 @@ +% function tor_bar_steplot(avg, ste, plotcolor, [x locations], [shift by], [multx by]) +% +% for a GROUPED 2 x 2 error bar plot (or 2 x n): +% tor_bar_steplot(means, se, {'k'}, .35, .5, .2) +% +% tor_bar_steplot(d(2, :), s(2, :), {'k'}, 1.6, .27) +% tor_bar_steplot(d(1, :), s(1, :), {'k'}, .35, .5, .2) + +function tor_bar_steplot(avg, ste, plotcolor, varargin) + wid = .1; + + if length(varargin) > 0 + xlocations = varargin{1}; + wid = min(diff(xlocations)) .* .3; + else xlocations = 1:size(ste, 2); + end + if length(varargin) > 1, shift = varargin{2}; else shift = 0; end + if length(varargin) > 2, multx = varargin{3}; else multx = 1; end + if length(varargin) > 3, shift2 = varargin{4}; else shift2 = 0; end + + hold on + if isempty(plotcolor) + plotcolor{1} = 'b'; + end + %wid = get(gca, 'XLim'); + %wid = (wid(2) - wid(1))/50; + + + + %if isfield(Op, 'window'), xlocations = Op.window(1):Op.window(2);, end + + if length(xlocations) ~= size(avg, 2), + warning('Window is wrong length, using default'); %#ok + xlocations = 1:size(avg, 2); + end + + for i = 1:size(ste, 2) + if length(plotcolor) < i + plotcolor{i} = plotcolor{1}; + end + + xloc = xlocations(i); + + if avg(i) < 0 + multy = -1; + else + multy = 1; + end + + try + if mod(i, 2) == 0 + shft = shift - shift2; + else + shft = shift; + end + + plot([xloc*multx+shft xloc*multx+shft], [avg(i) avg(i) + multy*ste(i)], plotcolor{i}(1)) + plot([xloc*multx-wid+shft xloc*multx+wid+shft], [avg(i) + multy*ste(i) avg(i) + multy*ste(i)], plotcolor{i}(1)) + catch + disp('Can''t plot error bar.') + i + avg + ste + plotcolor + end + end +end diff --git a/Visualization_functions/Support/tor_fig.m b/Visualization_functions/Support/tor_fig.m new file mode 100755 index 00000000..497480d1 --- /dev/null +++ b/Visualization_functions/Support/tor_fig.m @@ -0,0 +1,24 @@ +function [f1,colors,axh] = tor_fig(varargin) +% function [f1,colors,axh] = tor_fig(varargin) +% +% Creates a figure, hold on, fontsize 18 +% +% Var args are number of rows, then columns for subplot +fs=18; +if length(varargin)>2;,varargin{3}=fs;,end + +f1 = figure('Color','w'); set(gca,'FontSize',18),hold on +colors = {'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + +if length(varargin) > 0 + i = varargin{1}; + j = varargin{2}; + np = i * j; + for k = 1:np + axh(k) = subplot(i,j,k); + set(gca,'FontSize',fs),hold on + end + axes(axh(1)); +end + +return \ No newline at end of file diff --git a/Visualization_functions/Support/tor_line_steplot.m b/Visualization_functions/Support/tor_line_steplot.m new file mode 100644 index 00000000..ae094074 --- /dev/null +++ b/Visualization_functions/Support/tor_line_steplot.m @@ -0,0 +1,80 @@ +% function lineh = tor_bar_steplot(avg, ste, plotcolor, [x locations], [shift by], [multx by]) +% +% for a GROUPED 2 x 2 error bar plot (or 2 x n): +% tor_bar_steplot(means, se, {'k'}, .35, .5, .2) +% +% tor_line_steplot(d(2, :), s(2, :), {'k'}, 1.6, .27) +% tor_line_steplot(d(1, :), s(1, :), {'k'}, .35, .5, .2) +% +% This function has been largely replaced with a better, more flexible one! +% See lineplot_columns (takes in data matrix, not avg/ste), so this +% function may still be useful sometimes. +% out = lineplot_columns(hotopen, 'color', 'r', 'marker', 'none', 'w', 1, 'shade'); + +function lineh = tor_line_steplot(avg, ste, plotcolor, varargin) + wid = .1; + + lineh = []; + + if length(varargin) > 0 + xlocations = varargin{1}; + wid = min(diff(xlocations)) .* .3; + else xlocations = 1:size(ste, 2); + end + if length(varargin) > 1, shift = varargin{2}; else shift = 0; end + if length(varargin) > 2, multx = varargin{3}; else multx = 1; end + if length(varargin) > 3, shift2 = varargin{4}; else shift2 = 0; end + + hold on + if isempty(plotcolor) + plotcolor{1} = 'b'; + end + %wid = get(gca, 'XLim'); + %wid = (wid(2) - wid(1))/50; + + + + %if isfield(Op, 'window'), xlocations = Op.window(1):Op.window(2);, end + + if length(xlocations) ~= size(avg, 2), + warning('Window is wrong length, using default'); %#ok + xlocations = 1:size(avg, 2); + end + + for i = 1:size(ste, 2) + if length(plotcolor) < i + plotcolor{i} = plotcolor{1}; + end + + xloc = xlocations(i); + + if avg(i) < 0 + multy = -1; + else + multy = 1; + end + + try + if mod(i, 2) == 0 + shft = shift - shift2; + else + shft = shift; + end + + lineh = [lineh plot([xloc*multx+shft xloc*multx+shft], [avg(i) avg(i) + multy*ste(i)], plotcolor{i}(1))]; + lineh = [lineh plot([xloc*multx-wid+shft xloc*multx+wid+shft], [avg(i) + multy*ste(i) avg(i) + multy*ste(i)], plotcolor{i}(1))]; + + catch + disp('Can''t plot error bar.') + i + avg + ste + plotcolor + xloc + multx + multy + wid + shft + end + end +end diff --git a/Visualization_functions/Support/tor_plot_avgs.m b/Visualization_functions/Support/tor_plot_avgs.m new file mode 100755 index 00000000..54375c26 --- /dev/null +++ b/Visualization_functions/Support/tor_plot_avgs.m @@ -0,0 +1,87 @@ +function tor_plot_avgs(AVG,STE,varargin) +% function tor_plot_avgs(AVG,STE,varargin) +% +% ------ plot the selective average -------- +% Tor Wager, 10/25/01 + + +plotcolor = {'ro-' 'r^--' 'bo-' 'b^--' 'k^--' 'ko-' 'g^--' 'go-' 'm^--' 'mo-' }; +steplot = 1; +for i = 1:length(AVG) + mylegend{i} = ['Condition ' num2str(i)]; +end +mytitle = 'Selective Average'; + +if nargin > 2 + Op = varargin{1}; + if isfield(Op,'nosteplot'),if Op.nosteplot == 1, steplot = 0;,end, end + if isfield(Op,'colors'),plotcolor = Op.colors;,end + if isfield(Op,'legend'),mylegend = Op.legend;,end + if isfield(Op,'title'),mytitle = Op.title;,end +end + +cla +hold on + + for g = 1:length(AVG) + + avg = AVG{g}; + + % set x axis values + % --------------------------------------------------------------- + myx = 1:size(avg,2); + if isfield(Op,'window'),myx = Op.window(1):Op.window(2);,end + if length(myx) ~= size(avg,2), + warning('Window is wrong length, using default') + myx = 1:size(avg,2); + end + + try + plot(myx,avg,plotcolor{g},'LineWidth',2) + catch + plotcolor{g} = 'k'; + plot(myx,avg,plotcolor{g},'LineWidth',2) + end + + wid = get(gca,'XLim'); + wid = (wid(2) - wid(1))/50; + end + + +if steplot + for g = 1:length(AVG) + avg = AVG{g}; + ste = STE{g}; + + % set x axis values + % --------------------------------------------------------------- + myx = 1:size(avg,2); + if isfield(Op,'window'),myx = Op.window(1):Op.window(2);,end + if length(myx) ~= size(avg,2), + warning('Window is wrong length, using default') + myx = 1:size(avg,2); + end + + for i = 1:size(avg,2) + j = myx(i); + try + plot([j j],[avg(i)-ste(i) avg(i)+ste(i)],plotcolor{g}(1)) + plot([j-wid j+wid],[avg(i)-ste(i) avg(i)-ste(i)],plotcolor{g}(1)) + plot([j-wid j+wid],[avg(i)+ste(i) avg(i)+ste(i)],plotcolor{g}(1)) + catch + disp('Can''t plot error bar.') + i + avg + ste + plotcolor + end + end + end +end + +grid on +title(mytitle,'FontSize',14) +legend(mylegend,0) + +return + \ No newline at end of file diff --git a/Visualization_functions/active_plus_corr_scatterplot_plugin.m b/Visualization_functions/active_plus_corr_scatterplot_plugin.m new file mode 100644 index 00000000..481cad41 --- /dev/null +++ b/Visualization_functions/active_plus_corr_scatterplot_plugin.m @@ -0,0 +1,86 @@ +disp('Running: active_plus_corr_scatterplot_plugin'); + +% ------------------------------------------------ +% define some defaults +% ------------------------------------------------ + +%if length(varargin) > 0, overlay = varargin{1};, end +if ~isfield(EXPT,'overlay') || isempty(EXPT.overlay), + EXPT.overlay = which('scalped_single_subj_T1.img'); +end +if isempty(which(EXPT.overlay)), warning('Cannot find overlay image on path!'); end +disp(['Overlay: ' EXPT.overlay]); + +clusterfield = 'timeseries'; + +if ~(exist('EXPT') == 1) + error('You must load a valid EXPT structure with required fields to run. Best to run this function from the GUI.'); +end + +% ------------------------------------------------ +% get a directory and go to it. +% ------------------------------------------------ +dirname = spm_get(-1,'rob*','Choose robust* or robseed* directory.'); +if dirname(end) == '.', dirname = dirname(1:end-2); elseif dirname(end) == filesep, dirname = dirname(1:end-1); end + +disp(['Going to: ' dirname]) +cd(dirname) + +% ------------------------------------------------ +% tell whether we're in a robust* or robseed* directory, and do the +% appropriate thing. +% ------------------------------------------------ +[dd,robdir] = fileparts(dirname); + +dirtype = robdir(1:end-4); % robust or robseed +% dirnum = str2num(robdir(end-3:end)); % which number -- to get correct data +dirnum = find(EXPT.SNPM.connums == str2num(robdir(end-3:end))); % which number -- to get correct data + +% ------------------------------------------------ +% get clusters from results image +% save average data in 'timeseries' field, cl.timeseries +% ------------------------------------------------ +cl = image2clusters(EXPT.overlay); % could be masked ordered or not. +cl = tor_extract_rois(EXPT.SNPM.P{dirnum},cl); + +disp('Based on directory name, extracted cluster average data from: '); disp(EXPT.SNPM.P{dirnum}); +disp('Stored data from images in cl.timeseries'); + +switch dirtype + + case 'robust' + + disp(EXPT) + disp('Note: behavioral/other covariates should be entered in EXPT.cov.'); + whcol = spm_input('Enter column of EXPT.cov to plot against data: ',[],[],1); + + for i = 1:length(cl), cl(i).xdat = EXPT.cov(:,whcol); end + + docovs = 0; + if isfield(EXPT,'cov') && size(EXPT.cov,2) > 1 + docovs = spm_input('Adjust for other columns of EXPT.cov? (1/0) ',[],[],1); + end + if docovs, for i = 1:length(cl), cl(i).nuisance = EXPT.cov; cl(i).nuisance(:,whcol) = []; end , end + + + case 'robseed' + + disp(EXPT) + disp('Note: seed data should be entered in EXPT.seeds.'); + if isfield(EXPT,'seednames'), disp(str2mat(EXPT.seednames)); end + whcol = spm_input('Enter column of EXPT.seeds to plot against data: ',[],[],1); + + for i = 1:length(cl), cl(i).xdat = EXPT.seeds(:,whcol); end + + + otherwise + error('You must choose a robust or robseed directory.'); + +end + + + +% now we're ready to make the plot, and set the button up function to the +% command below: +[xdat,ydat] = cluster_interactive_scatterplot(cl); + diff --git a/Visualization_functions/add_surface.m b/Visualization_functions/add_surface.m new file mode 100644 index 00000000..544e83a6 --- /dev/null +++ b/Visualization_functions/add_surface.m @@ -0,0 +1,12 @@ +function p = add_surface(surf_mat) + Ps = which(surf_mat); %'c:\tor_scripts\3DheadUtility\surf_single_subj_T1_gray.mat'; + if isempty(Ps), disp(['I need the file: ' surf_mat]); return; end + + %Ps = which('surf_single_subj_grayR.mat'); + %Ps = which('surf_brain_render_T1_preCarmack.mat'); + load(Ps) + p = patch('Faces',faces,'Vertices',vertices,'FaceColor',[.5 .5 .5], ... + 'EdgeColor','none','SpecularStrength',.2,'FaceAlpha',1,'SpecularExponent',200); + + set(p,'FaceAlpha',.3) +end diff --git a/Visualization_functions/addbrain.m b/Visualization_functions/addbrain.m new file mode 100755 index 00000000..5fb18be3 --- /dev/null +++ b/Visualization_functions/addbrain.m @@ -0,0 +1,367 @@ +function p = addbrain(varargin) +% handle = addbrain([method],enter 2nd arg to suppress lighting changes) +% quick function to add transparent brain surface to figure +% +% % han = addbrain; % lateral surface +% han = addbrain('brainstem'); +% +% NOTE: this version uses structures in SPM2 space (Colin atlas) +% Available keywords: +% 'transparent_surface' : the default. 2 mm res SPM2 brain surface +% 'hires' : a high-resolution surface (from Caret segmentation) +% 'hires left' : hi-resolution left medial with cerebellum (Caret seg) +% 'hires right' : same, right hem +% 'left' : 2 mm resolution left hem, no cerebellum +% 'right' +% 'brainstem' +% 'brainbottom' +% 'amygdala' +% 'thalamus' +% 'hippocampus' +% midbrain' +% 'caudate' +% 'globus pallidus' +% 'putamen' +% 'nucleus accumbens' +% 'hypothalamus' +% 'cerebellum' +% case {'md','mediodorsal'} +% case {'cm','centromedian'} +% case 'pbn' +% case 'rvm' +% case 'nts' +% case {'lc'} +% case {'sn', 'substantia nigra'} +% case {'stn', 'subthalamic nucleus'} +% case {'rn', 'red nucleus'} +% case {'olive', 'inferior olive'} +% case {'nrm', 'raphe magnus'} +% +% han = addbrain('colorchange',my_rgb_color,han); +% Works on patch object in input handle han +% or, if han is empty, on all patch objects in the current figure. +% Only works for changing from gray background right now. (Future update to handle any +% background.) + +p = []; +meth = 'transparent_surface'; + +docolor = 1; +if length(varargin) > 0 + meth = varargin{1}; +end + +if length(varargin) > 1 + color = varargin{2}; +end + +switch meth + + case 'colorchange' + myp = varargin{3}; + if isempty(myp), myp = findobj(gcf,'Type','Patch'); end + background_colorchange(myp,color); + docolor = 0; + + case 'left' + pname = 'surf_spm2_left.mat'; % moderate res, no cerebellum + + p = add_surface(pname); + set(p,'FaceColor',[.8 .65 .5]); + + view(90,0); axis off; axis image; lightRestoreSingle(gca); material dull; + + case 'hires left' + pname = 'surf_spm2_brain_left.mat'; % high res, with cblm. caret segmentation + + p = add_surface(pname); + set(p,'FaceColor',[.8 .65 .5]); + + view(90,0); axis off; axis image; lightRestoreSingle(gca); material dull; + + case 'right' + + pname = 'surf_spm2_right.mat'; %'surf_single_subj_grayR.mat'; + + p = add_surface(pname); + + set(p,'FaceColor',[.8 .65 .5]); + view(270,0); axis off; axis image; lightRestoreSingle(gca); material dull; + + case 'hires right' + pname = 'surf_spm2_brain_right.mat'; % high res, with cblm. caret segmentation + + p = add_surface(pname); + + set(p,'FaceColor',[.8 .65 .5]); + view(270,0); axis off; axis image; lightRestoreSingle(gca); material dull; + + case 'transparent_surface' + + %spm99 pname = 'surf_single_subj_T1_gray.mat'; %'surf_single_subj_gw_sparse.mat'; % + pname = 'surf_spm2_brain.mat'; % medium res, caret segmentation + + p = add_surface(pname); + + case 'hires' + + pname = 'surf_spm2_brain_1mm.mat'; % hi res, caret segmentation + + p = add_surface(pname); + + case 'brainstem' + + pname = 'surf_spm2_brainstem.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[.5 .65 .4]); + + case 'brainbottom' + [D,Ds,hdr,p,bestCoords] = tor_3d('whichcuts','z','coords',[0 0 -20],'filename','scalped_single_subj_T1'); + set(p(1),'FaceColor',[.6 .4 .3]); colormap copper;material dull;axis off + h = findobj('Type','Light'); delete(h); [az,el]=view;lightangle(az,el); lightangle(az-180,el-60); + set(p,'FaceAlpha',1) + + case 'amygdala' + % Carmack + % P = which('Tal_Amy.img'); + % [p,outP,FV, cl, myLight] = mask2surface(P,0,[0 0 .5]); + % if findstr(P,'Tal_Amy.img'), str(49:58) = '[.4 .4 .6]'; , end + + % ICBM + % load amy_clusters + % p = imageCluster('cluster',amy,'color',[0 0 .5],'alpha',.5); + + pname = 'surf_spm2_amy.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[0 0 .5]); + + case 'thalamus' + % P = which('carmack_thal_bstem.mat'); load(P) + % p = imageCluster('cluster',thal,'color',[0 .8 .3],'alpha',.5); + pname = 'surf_spm2_thal.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[.9 .65 .5]); + + case {'hippocampus', 'hipp'} + %Carmack + % P = which('Tal_Hip.img'); + % [p,outP,FV, cl, myLight] = mask2surface(P,0,[.5 .5 0]); + % if findstr(P,'Tal_Hip.img'), str(49:58) = '[.5 .6 .6]';, end + + % ICBM + %load hipp_clusters hipp + %p = imageCluster('cluster',hipp,'color',[.5 .6 .6],'alpha',.5); + + pname = 'surf_spm2_hipp.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[.7 .4 .4]); + + case 'midbrain' + P = which('carmack_thal_bstem.mat'); + load(P) + p = imageCluster('cluster',midbrain,'color',[.7 .3 0],'alpha',.5); + + case 'caudate' + %P = which('Tal_Cau.img'); %which('ICBM_caudate.img'); %('Tal_Cau.img'); % + %[p,outP,FV, cl, myLight] = mask2surface(P,0,[0 0 .5]); + %if findstr(P,'Tal_Cau.img'), str(49:58) = '[.5 .6 .6]'; delete(p(1)); p = p(2);,eval(str),end + % P = which('carmack_more_clusters.mat'); load(P) + % p = imageCluster('cluster',cau,'color',[.5 .6 .6],'alpha',.5); + + pname = 'surf_spm2_caudate.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[.5 .6 .6]); + + case {'globus pallidus', 'gp'} + P = which('Tal_Glo.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[.5 .6 .5]); + if findstr(P,'Tal_Glo.img'), str(49:58) = '[.5 .6 .5]'; , end + + case {'putamen', 'put'} + %P = which('Tal_Put.img'); + %[p,outP,FV, cl, myLight] = mask2surface(P,0,[.9 .4 0]); + %if findstr(P,'Tal_Put.img'), str(49:58) = '[.5 .5 .6]'; , end + %P = which('carmack_more_clusters.mat'); load(P)] + fbase = 'LBPA40_spm5_label_clusters.mat'; + fname = which(fbase); if isempty(fname), error(['Looking for ' fbase]); end + load(fname) + put = cat(2, cl{51:52}); + p = imageCluster('cluster',put,'color',[.5 .5 .6],'alpha',.5); + + case {'nucleus accumbens','nacc','nac'} + % P = which('NucAccumb_clusters.mat'); + % load(P) + % p(1) = imageCluster('cluster',cl(3),'color',[0 .5 0],'alpha',.5); + % p(2) = imageCluster('cluster',cl(4),'color',[0 .5 0],'alpha',.5); , + + pname = 'surf_spm2_nac.mat'; + p = add_surface(pname); + set(p,'FaceColor',[0 .5 0]); + + case {'hypothalamus','hy','hythal'} + %P = which('Tal_Put.img'); + %[p,outP,FV, cl, myLight] = mask2surface(P,0,[.9 .4 0]); + %if findstr(P,'Tal_Put.img'), str(49:58) = '[.5 .5 .6]'; , end + + % P = which('carmack_more_clusters.mat'); load(P) + % p = imageCluster('cluster',hy,'color',[1 1 0],'alpha',.5); + + % Carmack + % load hy_clusters hy + % p = imageCluster('cluster',hy,'color',[1 1 0],'alpha',.5); + + pname = 'surf_spm2_hythal.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[.9 .85 .5]); + + case {'cerebellum','cblm'} + pname = 'surf_spm2_cblm.mat'; + + p = add_surface(pname); + set(p,'FaceColor',[.8 .65 .8]); + + case 'limbic' + p = []; + myp = addbrain('amygdala');p = [p myp]; + myp = addbrain('hypothalamus');p = [p myp]; + myp = addbrain('hippocampus');p = [p myp]; + myp = addbrain('thalamus');p = [p myp]; + myp = addbrain('nacc');p = [p myp]; + myp = addbrain('caudate');p = [p myp]; + myp = addbrain('left');p = [p myp]; + set(p,'FaceAlpha',1); + + axis image; axis vis3d; lighting gouraud; lightRestoreSingle(gca) + + + case 'pag' + load pag_cl + p = imageCluster('cluster',pag,'color',[1 0 0],'alpha',1); + + %P = which('ROI_pag.img'); + %P = which('spm5_pag.img'); + %[p,outP,FV, cl, myLight] = mask2surface(P,0,[1 0 0]); + + case {'md','mediodorsal'} + load thal_brainstem_approx_working MD + p = imageCluster('cluster',MD,'color',[1 0 0],'alpha',1); + + case {'cm','centromedian'} + load thal_brainstem_approx_working CM + p = imageCluster('cluster',CM,'color',[1 .7 0],'alpha',1); + + case 'pbn' + load pbn_cl + p = imageCluster('cluster',pbn,'color',[1 .5 0],'alpha',1); + + case 'rvm' + load rvm_cl + p = imageCluster('cluster',rvm,'color',[1 .2 .1],'alpha',1); + + case 'nts' + load nts_cl + p = imageCluster('cluster',nts,'color',[0 0 1],'alpha',1); + + case {'lc'} + P = which('ROI_LC.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[1 .5 0]); + + case {'sn', 'substantia nigra'} + P = which('ROI_SN.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[0 0 .5]); + + case {'stn', 'subthalamic nucleus'} + P = which('ROI_STN.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[1 0 .5]); + + case {'rn', 'red nucleus'} + P = which('ROI_red_nucleus.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[1 0 0]); + + case {'olive', 'inferior olive'} + P = which('ROI_inf_olive.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[.5 1 .5]); + + case {'nrm', 'raphe magnus'} + P = which('ROI_raphe_magnus.img'); + [p,outP,FV, cl, myLight] = mask2surface(P,0,[.3 0 1]); + + + + + otherwise + error('Unknown method.'); +end + + +switch meth + + case 'limbic' + % do nothing; multi-region + otherwise + if all(ishandle(p)) + set(p, 'Tag', meth); + end +end + +if docolor && exist('color','var') + set(p,'FaceColor',color); +end + + +% suppress lighting if 2nd arg, otherwise, do it. + +if length(varargin) > 2 + lighting gouraud; + axis image; %myLight = camlight(0,0);set(myLight,'Tag','myLight'); + lightRestoreSingle(gca); camlight right + %set(gcf, 'WindowButtonUpFcn', 'lightFollowView');lightFollowView +end + + +drawnow +axis vis3d +%view(135,30) + + +return + + + + +function p = add_surface(pname) +Ps = which(pname); %'c:\tor_scripts\3DheadUtility\surf_single_subj_T1_gray.mat'; +if isempty(Ps), disp(['I need the file: ' pname]); return; end + +%Ps = which('surf_single_subj_grayR.mat'); +%Ps = which('surf_brain_render_T1_preCarmack.mat'); +load(Ps) +p = patch('Faces',faces,'Vertices',vertices,'FaceColor',[.5 .5 .5], ... + 'EdgeColor','none','SpecularStrength',.2,'FaceAlpha',1,'SpecularExponent',200); + +set(p,'FaceAlpha',.3) +return + + +function background_colorchange(myp,mycolor) + + +for i = 1:length(myp) + p = myp(i); + + + % change non-active to input color + vdat = get(p,'FaceVertexCData'); + wh = find(all(vdat == .5,2)); + vdat(wh,:) = repmat(mycolor,length(wh),1); + set(p,'FaceVertexCData',vdat); + +end + +return diff --git a/Visualization_functions/addbrainleft.m b/Visualization_functions/addbrainleft.m new file mode 100644 index 00000000..89c06c30 --- /dev/null +++ b/Visualization_functions/addbrainleft.m @@ -0,0 +1,24 @@ +function p = addbrain(varargin) +% handle = addbrain(enter arg to suppress lighting changes) +% quick function to add transparent brain surface to figure +% + +Ps = which('surf_single_subj_T1_gray.mat'); %'c:\tor_scripts\3DheadUtility\surf_single_subj_T1_gray.mat'; +Ps = which('surf_single_subj_grayL.mat'); +%Ps = which('surf_brain_render_T1_preCarmack.mat'); +load(Ps) + p = patch('Faces',faces,'Vertices',vertices,'FaceColor',[.5 .5 .5], ... + 'EdgeColor','none','SpecularStrength',.2,'FaceAlpha',1,'SpecularExponent',200); + +if length(varargin) == 0 + lighting gouraud;camlight right + axis image; myLight = camlight(0,0);set(myLight,'Tag','myLight'); + set(gcf, 'WindowButtonUpFcn', 'lightFollowView');lightFollowView +end + + set(p,'FaceAlpha',.3) + + drawnow + axis vis3d + %view(135,30) + return \ No newline at end of file diff --git a/Visualization_functions/addbrainright.m b/Visualization_functions/addbrainright.m new file mode 100644 index 00000000..202fb194 --- /dev/null +++ b/Visualization_functions/addbrainright.m @@ -0,0 +1,24 @@ +function p = addbrain(varargin) +% handle = addbrain(enter arg to suppress lighting changes) +% quick function to add transparent brain surface to figure +% + +Ps = which('surf_single_subj_T1_gray.mat'); %'c:\tor_scripts\3DheadUtility\surf_single_subj_T1_gray.mat'; +Ps = which('surf_single_subj_grayR.mat'); +%Ps = which('surf_brain_render_T1_preCarmack.mat'); +load(Ps) + p = patch('Faces',faces,'Vertices',vertices,'FaceColor',[.5 .5 .5], ... + 'EdgeColor','none','SpecularStrength',.2,'FaceAlpha',1,'SpecularExponent',200); + +if length(varargin) == 0 + lighting gouraud;camlight right + axis image; myLight = camlight(0,0);set(myLight,'Tag','myLight'); + set(gcf, 'WindowButtonUpFcn', 'lightFollowView');lightFollowView +end + + set(p,'FaceAlpha',.3) + + drawnow + axis vis3d + %view(135,30) + return \ No newline at end of file diff --git a/Visualization_functions/arrow.m b/Visualization_functions/arrow.m new file mode 100755 index 00000000..b8adb77e --- /dev/null +++ b/Visualization_functions/arrow.m @@ -0,0 +1,1340 @@ +function [h,yy,zz] = arrow(varargin) +% ARROW Draw a line with an arrowhead. +% +% ARROW(Start,Stop) draws a line with an arrow from Start to Stop (points +% should be vectors of length 2 or 3, or matrices with 2 or 3 +% columns), and returns the graphics handle of the arrow(s). +% +% ARROW uses the mouse (click-drag) to create an arrow. +% +% ARROW DEMO & ARROW DEMO2 show 3-D & 2-D demos of the capabilities of ARROW. +% +% ARROW may be called with a normal argument list or a property-based list. +% ARROW(Start,Stop,Length,BaseAngle,TipAngle,Width,Page,CrossDir) is +% the full normal argument list, where all but the Start and Stop +% points are optional. If you need to specify a later argument (e.g., +% Page) but want default values of earlier ones (e.g., TipAngle), +% pass an empty matrix for the earlier ones (e.g., TipAngle=[]). +% +% ARROW('Property1',PropVal1,'Property2',PropVal2,...) creates arrows with the +% given properties, using default values for any unspecified or given as +% 'default' or NaN. Some properties used for line and patch objects are +% used in a modified fashion, others are passed directly to LINE, PATCH, +% or SET. For a detailed properties explanation, call ARROW PROPERTIES. +% +% Start The starting points. B +% Stop The end points. /|\ ^ +% Length Length of the arrowhead in pixels. /|||\ | +% BaseAngle Base angle in degrees (ADE). //|||\\ L| +% TipAngle Tip angle in degrees (ABC). ///|||\\\ e| +% Width Width of the base in pixels. ////|||\\\\ n| +% Page Use hardcopy proportions. /////|D|\\\\\ g| +% CrossDir Vector || to arrowhead plane. //// ||| \\\\ t| +% NormalDir Vector out of arrowhead plane. /// ||| \\\ h| +% Ends Which end has an arrowhead. //<----->|| \\ | +% ObjectHandles Vector of handles to update. / base ||| \ V +% E angle||<-------->C +% ARROW(H,'Prop1',PropVal1,...), where H is a |||tipangle +% vector of handles to previously-created arrows ||| +% and/or line objects, will update the previously- ||| +% created arrows according to the current view -->|A|<-- width +% and any specified properties, and will convert +% two-point line objects to corresponding arrows. ARROW(H) will update +% the arrows if the current view has changed. Root, figure, or axes +% handles included in H are replaced by all descendant Arrow objects. +% +% A property list can follow any specified normal argument list, e.g., +% ARROW([1 2 3],[0 0 0],36,'BaseAngle',60) creates an arrow from (1,2,3) to +% the origin, with an arrowhead of length 36 pixels and 60-degree base angle. +% +% The basic arguments or properties can generally be vectorized to create +% multiple arrows with the same call. This is done by passing a property +% with one row per arrow, or, if all arrows are to have the same property +% value, just one row may be specified. +% +% You may want to execute AXIS(AXIS) before calling ARROW so it doesn't change +% the axes on you; ARROW determines the sizes of arrow components BEFORE the +% arrow is plotted, so if ARROW changes axis limits, arrows may be malformed. +% +% This version of ARROW uses features of MATLAB 5 and is incompatible with +% earlier MATLAB versions (ARROW for MATLAB 4.2c is available separately); +% some problems with perspective plots still exist. + +% Copyright (c)1995-1997, Erik A. Johnson , 8/14/97 + +% Revision history: +% 8/14/97 EAJ Added workaround for MATLAB 5.1 scalar logical transpose bug. +% 7/21/97 EAJ Fixed a few misc bugs. +% 7/14/97 EAJ Make arrow([],'Prop',...) do nothing (no old handles) +% 6/23/97 EAJ MATLAB 5 compatible version, release. +% 5/27/97 EAJ Added Line Arrows back in. Corrected a few bugs. +% 5/26/97 EAJ Changed missing Start/Stop to mouse-selected arrows. +% 5/19/97 EAJ MATLAB 5 compatible version, beta. +% 4/13/97 EAJ MATLAB 5 compatible version, alpha. +% 1/31/97 EAJ Fixed bug with multiple arrows and unspecified Z coords. +% 12/05/96 EAJ Fixed one more bug with log plots and NormalDir specified +% 10/24/96 EAJ Fixed bug with log plots and NormalDir specified +% 11/13/95 EAJ Corrected handling for 'reverse' axis directions +% 10/06/95 EAJ Corrected occasional conflict with SUBPLOT +% 4/24/95 EAJ A major rewrite. +% Fall 94 EAJ Original code. + +% Things to be done: +% - segment parsing, computing, and plotting into separate subfunctions +% - change computing from Xform to Camera paradigms +% + this will help especially with 3-D perspective plots +% + if the WarpToFill section works right, remove warning code +% + when perpsective works properly, remove perspective warning code +% - add cell property values and struct property name/values (like get/set) +% - get rid of NaN as the "default" data label +% + perhaps change userdata to a struct and don't include (or leave +% empty) the values specified as default; or use a cell containing +% an empty matrix for a default value +% - add functionality of GET to retrieve current values of ARROW properties +% +% Modified slightly by Tor Wager in 2 places for Matlab 6.5 compat. with +% NaNs. +% + +% Many thanks to Keith Rogers for his many excellent +% suggestions and beta testing. Check out his shareware package MATDRAW. +% He has permission to distribute ARROW with MATDRAW. + +% global variable initialization +global ARROW_PERSP_WARN ARROW_STRETCH_WARN ARROW_AXLIMITS +if isempty(ARROW_PERSP_WARN ), ARROW_PERSP_WARN =1; end; +if isempty(ARROW_STRETCH_WARN), ARROW_STRETCH_WARN=1; end; + +% Handle callbacks +if (nargin>0 & isstr(varargin{1}) & strcmp(lower(varargin{1}),'callback')), + arrow_callback(varargin{2:end}); return; +end; + +% Are we doing the demo? +c = sprintf('\n'); +if (nargin==1 & isstr(varargin{1})), + arg1 = lower(varargin{1}); + if strncmp(arg1,'prop',4), arrow_props; + elseif strncmp(arg1,'demo',4) + clf reset + demo_info = arrow_demo; + if ~strncmp(arg1,'demo2',5), + hh=arrow_demo3(demo_info); + else, + hh=arrow_demo2(demo_info); + end; + if (nargout>=1), h=hh; end; + elseif strncmp(arg1,'fixlimits',3), + arrow_fixlimits(ARROW_AXLIMITS); + ARROW_AXLIMITS=[]; + elseif strncmp(arg1,'help',4), + disp(help(mfilename)); + else, + error([upper(mfilename) ' got an unknown single-argument string ''' deblank(arg1) '''.']); + end; + return; +end; + +% Check # of arguments +if (nargout>3), error([upper(mfilename) ' produces at most 3 output arguments.']); end; + +% find first property number +firstprop = nargin+1; +for k=1:length(varargin), if ~isnumeric(varargin{k}), firstprop=k; break; end; end; +lastnumeric = firstprop-1; + +% check property list +if (firstprop<=nargin), + for k=firstprop:2:nargin, + curarg = varargin{k}; + if ~isstr(curarg) | sum(size(curarg)>1)>1, + error([upper(mfilename) ' requires that a property name be a single string.']); + end; + end; + if (rem(nargin-firstprop,2)~=1), + error([upper(mfilename) ' requires that the property ''' ... + varargin{nargin} ''' be paired with a property value.']); + end; +end; + +% default output +if (nargout>0), h=[]; end; +if (nargout>1), yy=[]; end; +if (nargout>2), zz=[]; end; + +% set values to empty matrices +start = []; +stop = []; +len = []; +baseangle = []; +tipangle = []; +wid = []; +page = []; +crossdir = []; +ends = []; +ax = []; +oldh = []; +ispatch = []; +defstart = [NaN NaN NaN]; +defstop = [NaN NaN NaN]; +deflen = 16; +defbaseangle = 90; +deftipangle = 16; +defwid = 0; +defpage = 0; +defcrossdir = [NaN NaN NaN]; +defends = 1; +defoldh = []; +defispatch = 1; + +% The 'Tag' we'll put on our arrows +ArrowTag = 'Arrow'; + +% check for oldstyle arguments +if (firstprop==2), + % assume arg1 is a set of handles + oldh = varargin{1}(:); + if isempty(oldh), return; end; +elseif (firstprop>9), + error([upper(mfilename) ' takes at most 8 non-property arguments.']); +elseif (firstprop>2), + s = str2mat('start','stop','len','baseangle','tipangle','wid','page','crossdir'); + for k=1:firstprop-1, eval([deblank(s(k,:)) '=varargin{k};']); end; +end; + +% parse property pairs +extraprops={}; +for k=firstprop:2:nargin, + prop = varargin{k}; + val = varargin{k+1}; + prop = [lower(prop(:)') ' ']; + if strncmp(prop,'start' ,5), start = val; + elseif strncmp(prop,'stop' ,4), stop = val; + elseif strncmp(prop,'len' ,3), len = val(:); + elseif strncmp(prop,'base' ,4), baseangle = val(:); + elseif strncmp(prop,'tip' ,3), tipangle = val(:); + elseif strncmp(prop,'wid' ,3), wid = val(:); + elseif strncmp(prop,'page' ,4), page = val; + elseif strncmp(prop,'cross' ,5), crossdir = val; + elseif strncmp(prop,'norm' ,4), if (isstr(val)), crossdir=val; else, crossdir=val*sqrt(-1); end; + elseif strncmp(prop,'end' ,3), ends = val; + elseif strncmp(prop,'object',6), oldh = val(:); + elseif strncmp(prop,'handle',6), oldh = val(:); + elseif strncmp(prop,'type' ,4), ispatch = val; + elseif strncmp(prop,'userd' ,5), %ignore it + else, + % make sure it is a valid patch or line property + eval('get(0,[''DefaultPatch'' varargin{k}]);err=0;','err=1;'); errstr=lasterr; + if (err), eval('get(0,[''DefaultLine'' varargin{k}]);err=0;','err=1;'); end; + if (err), + errstr(1:max(find(errstr==setstr(13)|errstr==setstr(10)))) = ''; + error([upper(mfilename) ' got ' errstr]); + end; + extraprops={extraprops{:},varargin{k},val}; + end; +end; + +% Check if we got 'default' values +start = arrow_defcheck(start ,defstart ,'Start' ); +stop = arrow_defcheck(stop ,defstop ,'Stop' ); +len = arrow_defcheck(len ,deflen ,'Length' ); +baseangle = arrow_defcheck(baseangle,defbaseangle,'BaseAngle' ); +tipangle = arrow_defcheck(tipangle ,deftipangle ,'TipAngle' ); +wid = arrow_defcheck(wid ,defwid ,'Width' ); +crossdir = arrow_defcheck(crossdir ,defcrossdir ,'CrossDir' ); +page = arrow_defcheck(page ,defpage ,'Page' ); +ends = arrow_defcheck(ends ,defends ,'' ); +oldh = arrow_defcheck(oldh ,[] ,'ObjectHandles'); +ispatch = arrow_defcheck(ispatch ,defispatch ,'' ); + +% check transpose on arguments +[m,n]=size(start ); if any(m==[2 3])&(n==1|n>3), start = start'; end; +[m,n]=size(stop ); if any(m==[2 3])&(n==1|n>3), stop = stop'; end; +[m,n]=size(crossdir); if any(m==[2 3])&(n==1|n>3), crossdir = crossdir'; end; + +% convert strings to numbers +if ~isempty(ends) & isstr(ends), + endsorig = ends; + [m,n] = size(ends); + col = lower([ends(:,1:min(3,n)) ones(m,max(0,3-n))*' ']); + ends = NaN*ones(m,1); + oo = ones(1,m); + ii=find(all(col'==['non']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*0; end; + ii=find(all(col'==['sto']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*1; end; + ii=find(all(col'==['sta']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*2; end; + ii=find(all(col'==['bot']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*3; end; + if any(isnan(ends)), + ii = min(find(isnan(ends))); + error([upper(mfilename) ' does not recognize ' deblank(endsorig(ii,:)) ' as a valid Ends value.']); + end; +else, + ends = ends(:); +end; +if ~isempty(ispatch) & isstr(ispatch), + col = lower(ispatch(:,1)); + patchchar='p'; linechar='l'; defchar=' '; + mask = col~=patchchar & col~=linechar & col~=defchar; + if any(mask) + error([upper(mfilename) ' does not recognize ' deblank(ispatch(min(find(mask)),:)) ' as a valid Type value.']); + end; + ispatch = (col==patchchar)*1 + (col==linechar)*0 + (col==defchar)*defispatch; +else, + ispatch = ispatch(:); +end; +oldh = oldh(:); + +% check object handles +if ~all(ishandle(oldh)), error([upper(mfilename) ' got invalid object handles.']); end; + +% expand root, figure, and axes handles +if ~isempty(oldh), + ohtype = get(oldh,'Type'); + mask = strcmp(ohtype,'root') | strcmp(ohtype,'figure') | strcmp(ohtype,'axes'); + if any(mask), + oldh = num2cell(oldh); + for ii=find(mask)', + oldh(ii) = {findobj(oldh{ii},'Tag',ArrowTag)}; + end; + oldh = cat(1,oldh{:}); + if isempty(oldh), return; end; % no arrows to modify, so just leave + end; +end; + +% largest argument length +[mstart,junk]=size(start); [mstop,junk]=size(stop); [mcrossdir,junk]=size(crossdir); +argsizes = [length(oldh) mstart mstop ... + length(len) length(baseangle) length(tipangle) ... + length(wid) length(page) mcrossdir length(ends) ]; +args=['length(ObjectHandle) '; ... + '#rows(Start) '; ... + '#rows(Stop) '; ... + 'length(Length) '; ... + 'length(BaseAngle) '; ... + 'length(TipAngle) '; ... + 'length(Width) '; ... + 'length(Page) '; ... + '#rows(CrossDir) '; ... + '#rows(Ends) ']; +if (any(imag(crossdir(:))~=0)), + args(9,:) = '#rows(NormalDir) '; +end; +if isempty(oldh), + narrows = max(argsizes); +else, + narrows = length(oldh); +end; +if (narrows<=0), narrows=1; end; + +% Check size of arguments +ii = find((argsizes~=0)&(argsizes~=1)&(argsizes~=narrows)); +if ~isempty(ii), + s = args(ii',:); + while ((size(s,2)>1)&((abs(s(:,size(s,2)))==0)|(abs(s(:,size(s,2)))==abs(' ')))), + s = s(:,1:size(s,2)-1); + end; + s = [ones(length(ii),1)*[upper(mfilename) ' requires that '] s ... + ones(length(ii),1)*[' equal the # of arrows (' num2str(narrows) ').' c]]; + s = s'; + s = s(:)'; + s = s(1:length(s)-1); + error(setstr(s)); +end; + +% check element length in Start, Stop, and CrossDir +if ~isempty(start), + [m,n] = size(start); + if (n==2), + start = [start NaN*ones(m,1)]; + elseif (n~=3), + error([upper(mfilename) ' requires 2- or 3-element Start points.']); + end; +end; +if ~isempty(stop), + [m,n] = size(stop); + if (n==2), + stop = [stop NaN*ones(m,1)]; + elseif (n~=3), + error([upper(mfilename) ' requires 2- or 3-element Stop points.']); + end; +end; +if ~isempty(crossdir), + [m,n] = size(crossdir); + if (n<3), + crossdir = [crossdir NaN*ones(m,3-n)]; + elseif (n~=3), + if (all(imag(crossdir(:))==0)), + error([upper(mfilename) ' requires 2- or 3-element CrossDir vectors.']); + else, + error([upper(mfilename) ' requires 2- or 3-element NormalDir vectors.']); + end; + end; +end; + +% fill empty arguments +if isempty(start ), start = [Inf Inf Inf]; end; +if isempty(stop ), stop = [Inf Inf Inf]; end; +if isempty(len ), len = Inf; end; +if isempty(baseangle ), baseangle = Inf; end; +if isempty(tipangle ), tipangle = Inf; end; +if isempty(wid ), wid = Inf; end; +if isempty(page ), page = Inf; end; +if isempty(crossdir ), crossdir = [Inf Inf Inf]; end; +if isempty(ends ), ends = Inf; end; +if isempty(ispatch ), ispatch = Inf; end; + +% expand single-column arguments +o = ones(narrows,1); +if (size(start ,1)==1), start = o * start ; end; +if (size(stop ,1)==1), stop = o * stop ; end; +if (length(len )==1), len = o * len ; end; +if (length(baseangle )==1), baseangle = o * baseangle ; end; +if (length(tipangle )==1), tipangle = o * tipangle ; end; +if (length(wid )==1), wid = o * wid ; end; +if (length(page )==1), page = o * page ; end; +if (size(crossdir ,1)==1), crossdir = o * crossdir ; end; +if (length(ends )==1), ends = o * ends ; end; +if (length(ispatch )==1), ispatch = o * ispatch ; end; +ax = o * gca; + +% if we've got handles, get the defaults from the handles +if ~isempty(oldh), + for k=1:narrows, + oh = oldh(k); + ud = get(oh,'UserData'); + ax(k) = get(oh,'Parent'); + ohtype = get(oh,'Type'); + if strcmp(get(oh,'Tag'),ArrowTag), % if it's an arrow already + if isinf(ispatch(k)), ispatch(k)=strcmp(ohtype,'patch'); end; + % arrow UserData format: [start' stop' len base tip wid page crossdir' ends] + start0 = ud(1:3); + stop0 = ud(4:6); + if (isinf(len(k))), len(k) = ud( 7); end; + if (isinf(baseangle(k))), baseangle(k) = ud( 8); end; + if (isinf(tipangle(k))), tipangle(k) = ud( 9); end; + if (isinf(wid(k))), wid(k) = ud(10); end; + if (isinf(page(k))), page(k) = ud(11); end; + if (isinf(crossdir(k,1))), crossdir(k,1) = ud(12); end; + if (isinf(crossdir(k,2))), crossdir(k,2) = ud(13); end; + if (isinf(crossdir(k,3))), crossdir(k,3) = ud(14); end; + if (isinf(ends(k))), ends(k) = ud(15); end; + elseif strcmp(ohtype,'line')|strcmp(ohtype,'patch'), % it's a non-arrow line or patch + convLineToPatch = 1; %set to make arrow patches when converting from lines. + if isinf(ispatch(k)), ispatch(k)=convLineToPatch|strcmp(ohtype,'patch'); end; + x=get(oh,'XData'); x=x(~isnan(x(:))); if isempty(x), x=NaN; end; + y=get(oh,'YData'); y=y(~isnan(y(:))); if isempty(y), y=NaN; end; + z=get(oh,'ZData'); z=z(~isnan(z(:))); if isempty(z), z=NaN; end; + start0 = [x(1) y(1) z(1) ]; + stop0 = [x(end) y(end) z(end)]; + else, + error([upper(mfilename) ' cannot convert ' ohtype ' objects.']); + end; + ii=find(isinf(start(k,:))); if ~isempty(ii), start(k,ii)=start0(ii); end; + ii=find(isinf(stop( k,:))); if ~isempty(ii), stop( k,ii)=stop0( ii); end; + end; +end; + +% convert Inf's to NaN's +start( isinf(start )) = NaN; +stop( isinf(stop )) = NaN; +len( isinf(len )) = NaN; +baseangle( isinf(baseangle)) = NaN; +tipangle( isinf(tipangle )) = NaN; +wid( isinf(wid )) = NaN; +page( isinf(page )) = NaN; +crossdir( isinf(crossdir )) = NaN; +ends( isinf(ends )) = NaN; +ispatch( isinf(ispatch )) = NaN; + +% set up the UserData data (here so not corrupted by log10's and such) +ud = [start stop len baseangle tipangle wid page crossdir ends]; + +% Set Page defaults + +% TOR +if isnan(page), page = 0;,end +page = (~isnan(page))&(page); + + +% Get axes limits, range, min; correct for aspect ratio and log scale +axm = zeros(3,narrows); +axr = zeros(3,narrows); +axrev = zeros(3,narrows); +ap = zeros(2,narrows); +xyzlog = zeros(3,narrows); +limmin = zeros(2,narrows); +limrange = zeros(2,narrows); +oldaxlims = zeros(narrows,7); +oneax = all(ax==ax(1)); +if (oneax), + T = zeros(4,4); + invT = zeros(4,4); +else, + T = zeros(16,narrows); + invT = zeros(16,narrows); +end; +axnotdone = logical(ones(size(ax))); +while (any(axnotdone)), + ii = min(find(axnotdone)); + curax = ax(ii); + curpage = page(ii); + % get axes limits and aspect ratio + axl = [get(curax,'XLim'); get(curax,'YLim'); get(curax,'ZLim')]; + oldaxlims(min(find(oldaxlims(:,1)==0)),:) = [curax reshape(axl',1,6)]; + % get axes size in pixels (points) + u = get(curax,'Units'); + axposoldunits = get(curax,'Position'); + really_curpage = curpage & strcmp(u,'normalized'); + if (really_curpage), + curfig = get(curax,'Parent'); + pu = get(curfig,'PaperUnits'); + set(curfig,'PaperUnits','points'); + pp = get(curfig,'PaperPosition'); + set(curfig,'PaperUnits',pu); + set(curax,'Units','pixels'); + curapscreen = get(curax,'Position'); + set(curax,'Units','normalized'); + curap = pp.*get(curax,'Position'); + else, + set(curax,'Units','pixels'); + curapscreen = get(curax,'Position'); + curap = curapscreen; + end; + set(curax,'Units',u); + set(curax,'Position',axposoldunits); + % handle non-stretched axes position + str_stretch = { 'DataAspectRatioMode' ; ... + 'PlotBoxAspectRatioMode' ; ... + 'CameraViewAngleMode' }; + str_camera = { 'CameraPositionMode' ; ... + 'CameraTargetMode' ; ... + 'CameraViewAngleMode' ; ... + 'CameraUpVectorMode' }; + notstretched = strcmp(get(curax,str_stretch),'manual'); + manualcamera = strcmp(get(curax,str_camera),'manual'); + if ~arrow_WarpToFill(notstretched,manualcamera,curax), + % give a warning that this has not been thoroughly tested + if 0 & ARROW_STRETCH_WARN, + ARROW_STRETCH_WARN = 0; + strs = {str_stretch{1:2},str_camera{:}}; + strs = [char(ones(length(strs),1)*sprintf('\n ')) char(strs)]'; + warning([upper(mfilename) ' may not yet work quite right ' ... + 'if any of the following are ''manual'':' strs(:).']); + end; + % find the true pixel size of the actual axes + texttmp = text(axl(1,[1 2 2 1 1 2 2 1]), ... + axl(2,[1 1 2 2 1 1 2 2]), ... + axl(3,[1 1 1 1 2 2 2 2]),''); + set(texttmp,'Units','points'); + textpos = get(texttmp,'Position'); + delete(texttmp); + textpos = cat(1,textpos{:}); + textpos = max(textpos(:,1:2)) - min(textpos(:,1:2)); + % adjust the axes position + if (really_curpage), + % adjust to printed size + textpos = textpos * min(curap(3:4)./textpos); + curap = [curap(1:2)+(curap(3:4)-textpos)/2 textpos]; + else, + % adjust for pixel roundoff + textpos = textpos * min(curapscreen(3:4)./textpos); + curap = [curap(1:2)+(curap(3:4)-textpos)/2 textpos]; + end; + end; + if ARROW_PERSP_WARN & ~strcmp(get(curax,'Projection'),'orthographic'), + ARROW_PERSP_WARN = 0; + warning([upper(mfilename) ' does not yet work right for 3-D perspective projection.']); + end; + % adjust limits for log scale on axes + curxyzlog = [strcmp(get(curax,'XScale'),'log'); ... + strcmp(get(curax,'YScale'),'log'); ... + strcmp(get(curax,'ZScale'),'log')]; + if (any(curxyzlog)), + ii = find([curxyzlog;curxyzlog]); + if (any(axl(ii)<=0)), + error([upper(mfilename) ' does not support non-positive limits on log-scaled axes.']); + else, + axl(ii) = log10(axl(ii)); + end; + end; + % correct for 'reverse' direction on axes; + curreverse = [strcmp(get(curax,'XDir'),'reverse'); ... + strcmp(get(curax,'YDir'),'reverse'); ... + strcmp(get(curax,'ZDir'),'reverse')]; + ii = find(curreverse); + if ~isempty(ii), + axl(ii,[1 2])=-axl(ii,[2 1]); + end; + % compute the range of 2-D values + curT = get(curax,'Xform'); + lim = curT*[0 1 0 1 0 1 0 1;0 0 1 1 0 0 1 1;0 0 0 0 1 1 1 1;1 1 1 1 1 1 1 1]; + lim = lim(1:2,:)./([1;1]*lim(4,:)); + curlimmin = min(lim')'; + curlimrange = max(lim')' - curlimmin; + curinvT = inv(curT); + if (~oneax), + curT = curT.'; + curinvT = curinvT.'; + curT = curT(:); + curinvT = curinvT(:); + end; + % check which arrows to which cur corresponds + ii = find((ax==curax)&(page==curpage)); + oo = ones(1,length(ii)); + axr(:,ii) = diff(axl')' * oo; + axm(:,ii) = axl(:,1) * oo; + axrev(:,ii) = curreverse * oo; + ap(:,ii) = curap(3:4)' * oo; + xyzlog(:,ii) = curxyzlog * oo; + limmin(:,ii) = curlimmin * oo; + limrange(:,ii) = curlimrange * oo; + if (oneax), + T = curT; + invT = curinvT; + else, + T(:,ii) = curT * oo; + invT(:,ii) = curinvT * oo; + end; + axnotdone(ii) = zeros(1,length(ii)); +end; +oldaxlims(oldaxlims(:,1)==0,:)=[]; + +% correct for log scales +curxyzlog = xyzlog.'; +ii = find(curxyzlog(:)); +if ~isempty(ii), + start( ii) = real(log10(start( ii))); + stop( ii) = real(log10(stop( ii))); + if (all(imag(crossdir)==0)), % pulled (ii) subscript on crossdir, 12/5/96 eaj + crossdir(ii) = real(log10(crossdir(ii))); + end; +end; + +% correct for reverse directions +ii = find(axrev.'); +if ~isempty(ii), + start( ii) = -start( ii); + stop( ii) = -stop( ii); + crossdir(ii) = -crossdir(ii); +end; + +% transpose start/stop values +start = start.'; +stop = stop.'; + +% take care of defaults, page was done above +ii=find(isnan(start(:) )); if ~isempty(ii), start(ii) = axm(ii)+axr(ii)/2; end; +ii=find(isnan(stop(:) )); if ~isempty(ii), stop(ii) = axm(ii)+axr(ii)/2; end; +ii=find(isnan(crossdir(:) )); if ~isempty(ii), crossdir(ii) = zeros(length(ii),1); end; +ii=find(isnan(len )); if ~isempty(ii), len(ii) = ones(length(ii),1)*deflen; end; +ii=find(isnan(baseangle )); if ~isempty(ii), baseangle(ii) = ones(length(ii),1)*defbaseangle; end; +ii=find(isnan(tipangle )); if ~isempty(ii), tipangle(ii) = ones(length(ii),1)*deftipangle; end; +ii=find(isnan(wid )); if ~isempty(ii), wid(ii) = ones(length(ii),1)*defwid; end; +ii=find(isnan(ends )); if ~isempty(ii), ends(ii) = ones(length(ii),1)*defends; end; + +% transpose rest of values +len = len.'; +baseangle = baseangle.'; +tipangle = tipangle.'; +wid = wid.'; +page = page.'; +crossdir = crossdir.'; +ends = ends.'; +ax = ax.'; + +% given x, a 3xN matrix of points in 3-space; +% want to convert to X, the corresponding 4xN 2-space matrix +% +% tmp1=[(x-axm)./axr; ones(1,size(x,1))]; +% if (oneax), X=T*tmp1; +% else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1; +% tmp2=zeros(4,4*N); tmp2(:)=tmp1(:); +% X=zeros(4,N); X(:)=sum(tmp2)'; end; +% X = X ./ (ones(4,1)*X(4,:)); + +% for all points with start==stop, start=stop-(verysmallvalue)*(up-direction); +ii = find(all(start==stop)); +if ~isempty(ii), + % find an arrowdir vertical on screen and perpendicular to viewer + % transform to 2-D + tmp1 = [(stop(:,ii)-axm(:,ii))./axr(:,ii);ones(1,length(ii))]; + if (oneax), twoD=T*tmp1; + else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T(:,ii).*tmp1; + tmp2=zeros(4,4*length(ii)); tmp2(:)=tmp1(:); + twoD=zeros(4,length(ii)); twoD(:)=sum(tmp2)'; end; + twoD=twoD./(ones(4,1)*twoD(4,:)); + % move the start point down just slightly + tmp1 = twoD + [0;-1/1000;0;0]*(limrange(2,ii)./ap(2,ii)); + % transform back to 3-D + if (oneax), threeD=invT*tmp1; + else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT(:,ii).*tmp1; + tmp2=zeros(4,4*length(ii)); tmp2(:)=tmp1(:); + threeD=zeros(4,length(ii)); threeD(:)=sum(tmp2)'; end; + start(:,ii) = (threeD(1:3,:)./(ones(3,1)*threeD(4,:))).*axr(:,ii)+axm(:,ii); +end; + +% compute along-arrow points +% transform Start points + tmp1=[(start-axm)./axr;ones(1,narrows)]; + if (oneax), X0=T*tmp1; + else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1; + tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:); + X0=zeros(4,narrows); X0(:)=sum(tmp2)'; end; + X0=X0./(ones(4,1)*X0(4,:)); +% transform Stop points + tmp1=[(stop-axm)./axr;ones(1,narrows)]; + if (oneax), Xf=T*tmp1; + else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1; + tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:); + Xf=zeros(4,narrows); Xf(:)=sum(tmp2)'; end; + Xf=Xf./(ones(4,1)*Xf(4,:)); +% compute pixel distance between points + D = sqrt(sum(((Xf(1:2,:)-X0(1:2,:)).*(ap./limrange)).^2)); +% compute and modify along-arrow distances + len1 = len; + len2 = len - (len.*tan(tipangle/180*pi)-wid/2).*tan((90-baseangle)/180*pi); + slen0 = zeros(1,narrows); + slen1 = len1 .* ((ends==2)|(ends==3)); + slen2 = len2 .* ((ends==2)|(ends==3)); + len0 = zeros(1,narrows); + len1 = len1 .* ((ends==1)|(ends==3)); + len2 = len2 .* ((ends==1)|(ends==3)); + % for no start arrowhead + ii=find((ends==1)&(D0), set(H,extraprops{:}); end; + % handle choosing arrow Start and/or Stop locations if unspecified + [H,oldaxlims,errstr] = arrow_clicks(H,ud,x,y,z,ax,oldaxlims); + if ~isempty(errstr), error([upper(mfilename) ' got ' errstr]); end; + % set the output + if (nargout>0), h=H; end; + % make sure the axis limits did not change + if isempty(oldaxlims), + ARROW_AXLIMITS = []; + else, + lims = get(oldaxlims(:,1),{'XLim','YLim','ZLim'})'; + lims = reshape(cat(2,lims{:}),6,size(lims,2)); + mask = arrow_is2DXY(oldaxlims(:,1)); + oldaxlims(mask,6:7) = lims(5:6,mask)'; + ARROW_AXLIMITS = oldaxlims(find(any(oldaxlims(:,2:7)'~=lims)),:); + if ~isempty(ARROW_AXLIMITS), + warning(arrow_warnlimits(ARROW_AXLIMITS,narrows)); + end; + end; +else, + % don't create the patch, just return the data + h=x; + yy=y; + zz=z; +end; + + + +function out = arrow_defcheck(in,def,prop) +% check if we got 'default' values + out = in; + if ~isstr(in), return; end; + if size(in,1)==1 & strncmp(lower(in),'def',3), + out = def; + elseif ~isempty(prop), + error([upper(mfilename) ' does not recognize ''' in(:)' ''' as a valid ''' prop ''' string.']); + end; + + + +function [H,oldaxlims,errstr] = arrow_clicks(H,ud,x,y,z,ax,oldaxlims) +% handle choosing arrow Start and/or Stop locations if necessary + errstr = ''; + if isempty(H)|isempty(ud)|isempty(x), return; end; + % determine which (if any) need Start and/or Stop + needStart = all(isnan(ud(:,1:3)'))'; + needStop = all(isnan(ud(:,4:6)'))'; + mask = any(needStart|needStop); + if ~any(mask), return; end; + ud(~mask,:)=[]; ax(:,~mask)=[]; + x(:,~mask)=[]; y(:,~mask)=[]; z(:,~mask)=[]; + % make them invisible for the time being + set(H,'Visible','off'); + % save the current axes and limits modes; set to manual for the time being + oldAx = gca; + limModes=get(ax(:),{'XLimMode','YLimMode','ZLimMode'}); + set(ax(:),{'XLimMode','YLimMode','ZLimMode'},{'manual','manual','manual'}); + % loop over each arrow that requires attention + jj = find(mask); + for ii=1:length(jj), + h = H(jj(ii)); + axes(ax(ii)); + % figure out correct call + if needStart(ii), prop='Start'; else, prop='Stop'; end; + [wasInterrupted,errstr] = arrow_click(needStart(ii)&needStop(ii),h,prop,ax(ii)); + % handle errors and control-C + if wasInterrupted, + delete(H(jj(ii:end))); + H(jj(ii:end))=[]; + oldaxlims(jj(ii:end),:)=[]; + break; + end; + end; + % restore the axes and limit modes + axes(oldAx); + set(ax(:),{'XLimMode','YLimMode','ZLimMode'},limModes); + +function [wasInterrupted,errstr] = arrow_click(lockStart,H,prop,ax) +% handle the clicks for one arrow + fig = get(ax,'Parent'); + % save some things + oldFigProps = {'Pointer','WindowButtonMotionFcn','WindowButtonUpFcn'}; + oldFigValue = get(fig,oldFigProps); + oldArrowProps = {'EraseMode'}; + oldArrowValue = get(H,oldArrowProps); + set(H,'EraseMode','background'); %because 'xor' makes shaft invisible unless Width>1 + global ARROW_CLICK_H ARROW_CLICK_PROP ARROW_CLICK_AX ARROW_CLICK_USE_Z + ARROW_CLICK_H=H; ARROW_CLICK_PROP=prop; ARROW_CLICK_AX=ax; + ARROW_CLICK_USE_Z=~arrow_is2DXY(ax)|~arrow_planarkids(ax); + set(fig,'Pointer','crosshair'); + % set up the WindowButtonMotion so we can see the arrow while moving around + set(fig,'WindowButtonUpFcn','set(gcf,''WindowButtonUpFcn'','''')', ... + 'WindowButtonMotionFcn',''); + if ~lockStart, + set(H,'Visible','on'); + set(fig,'WindowButtonMotionFcn',[mfilename '(''callback'',''motion'');']); + end; + % wait for the button to be pressed + [wasKeyPress,wasInterrupted,errstr] = arrow_wfbdown(fig); + % if we wanted to click-drag, set the Start point + if lockStart & ~wasInterrupted, + pt = arrow_point(ARROW_CLICK_AX,ARROW_CLICK_USE_Z); + feval(mfilename,H,'Start',pt,'Stop',pt); + set(H,'Visible','on'); + ARROW_CLICK_PROP='Stop'; + set(fig,'WindowButtonMotionFcn',[mfilename '(''callback'',''motion'');']); + % wait for the mouse button to be released + eval('waitfor(fig,''WindowButtonUpFcn'','''');','wasInterrupted=1;'); + if wasInterrupted, errstr=lasterr; end; + end; + if ~wasInterrupted, feval(mfilename,'callback','motion'); end; + % restore some things + set(gcf,oldFigProps,oldFigValue); + set(H,oldArrowProps,oldArrowValue); + +function arrow_callback(varargin) +% handle redrawing callbacks + if nargin==0, return; end; + str = varargin{1}; + if ~isstr(str), error([upper(mfilename) ' got an invalid Callback command.']); end; + s = lower(str); + if strcmp(s,'motion'), + % motion callback + global ARROW_CLICK_H ARROW_CLICK_PROP ARROW_CLICK_AX ARROW_CLICK_USE_Z + feval(mfilename,ARROW_CLICK_H,ARROW_CLICK_PROP,arrow_point(ARROW_CLICK_AX,ARROW_CLICK_USE_Z)); + drawnow; + else, + error([upper(mfilename) ' does not recognize ''' str(:).' ''' as a valid Callback option.']); + end; + +function out = arrow_point(ax,use_z) +% return the point on the given axes + if nargin==0, ax=gca; end; + if nargin<2, use_z=~arrow_is2DXY(ax)|~arrow_planarkids(ax); end; + out = get(ax,'CurrentPoint'); + out = out(1,:); + if ~use_z, out=out(1:2); end; + +function [wasKeyPress,wasInterrupted,errstr] = arrow_wfbdown(fig) +% wait for button down ignoring object ButtonDownFcn's + if nargin==0, fig=gcf; end; + errstr = ''; + % save ButtonDownFcn values + objs = findobj(fig); + buttonDownFcns = get(objs,'ButtonDownFcn'); + mask=~strcmp(buttonDownFcns,''); objs=objs(mask); buttonDownFcns=buttonDownFcns(mask); + set(objs,'ButtonDownFcn',''); + % save other figure values + figProps = {'KeyPressFcn','WindowButtonDownFcn'}; + figValue = get(fig,figProps); + % do the real work + set(fig,'KeyPressFcn','set(gcf,''KeyPressFcn'','''',''WindowButtonDownFcn'','''');', ... + 'WindowButtonDownFcn','set(gcf,''WindowButtonDownFcn'','''')'); + lasterr(''); + wasInterrupted=0; eval('waitfor(fig,''WindowButtonDownFcn'','''');','wasInterrupted=1;'); + wasKeyPress = ~wasInterrupted & strcmp(get(fig,'KeyPressFcn'),''); + if wasInterrupted, errstr=lasterr; end; + % restore ButtonDownFcn and other figure values + set(objs,'ButtonDownFcn',buttonDownFcns); + set(fig,figProps,figValue); + + + +function [out,is2D] = arrow_is2DXY(ax) +% check if axes are 2-D X-Y plots + % may not work for modified camera angles, etc. + out = zeros(size(ax)); % 2-D X-Y plots + is2D = out; % any 2-D plots + views = get(ax(:),{'View'}); + views = cat(1,views{:}); + out(:) = abs(views(:,2))==90; + is2D(:) = out(:) | all(rem(views',90)==0)'; + +function out = arrow_planarkids(ax) +% check if axes descendents all have empty ZData (lines,patches,surfaces) + out = logical(ones(size(ax))); + allkids = get(ax(:),{'Children'}); + for k=1:length(allkids), + kids = get([findobj(allkids{k},'flat','Type','line') + findobj(allkids{k},'flat','Type','patch') + findobj(allkids{k},'flat','Type','surface')],{'ZData'}); + for j=1:length(kids), + if ~isempty(kids{j}), out(k)=logical(0); break; end; + end; + end; + + + +function arrow_fixlimits(axlimits) +% reset the axis limits as necessary + if isempty(axlimits), disp([upper(mfilename) ' does not remember any axis limits to reset.']); end; + for k=1:size(axlimits,1), + if any(get(axlimits(k,1),'XLim')~=axlimits(k,2:3)), set(axlimits(k,1),'XLim',axlimits(k,2:3)); end; + if any(get(axlimits(k,1),'YLim')~=axlimits(k,4:5)), set(axlimits(k,1),'YLim',axlimits(k,4:5)); end; + if any(get(axlimits(k,1),'ZLim')~=axlimits(k,6:7)), set(axlimits(k,1),'ZLim',axlimits(k,6:7)); end; + end; + + + +function out = arrow_WarpToFill(notstretched,manualcamera,curax) +% check if we are in "WarpToFill" mode. + out = strcmp(get(curax,'WarpToFill'),'on'); + % 'WarpToFill' is undocumented, so may need to replace this by + % out = ~( any(notstretched) & any(manualcamera) ); + + + +function out = arrow_warnlimits(axlimits,narrows) +% create a warning message if we've changed the axis limits + msg = ''; + switch (size(axlimits,1)==1) + case 1, msg=''; + case 2, msg='on two axes '; + otherwise, msg='on several axes '; + end; + msg = [upper(mfilename) ' changed the axis limits ' msg ... + 'when adding the arrow']; + if (narrows>1), msg=[msg 's']; end; + out = [msg '.' sprintf('\n') ' Call ' upper(mfilename) ... + ' FIXLIMITS to reset them now.']; + + + +function arrow_copyprops(fm,to) +% copy line properties to patches + props = {'EraseMode','LineStyle','LineWidth','Marker','MarkerSize',... + 'MarkerEdgeColor','MarkerFaceColor','ButtonDownFcn', ... + 'Clipping','DeleteFcn','BusyAction','HandleVisibility', ... + 'Selected','SelectionHighlight','Visible'}; + lineprops = {'Color', props{:}}; + patchprops = {'EdgeColor',props{:}}; + patch2props = {'FaceColor',patchprops{:}}; + fmpatch = strcmp(get(fm,'Type'),'patch'); + topatch = strcmp(get(to,'Type'),'patch'); + set(to( fmpatch& topatch),patch2props,get(fm( fmpatch& topatch),patch2props)); %p->p + set(to(~fmpatch&~topatch),lineprops, get(fm(~fmpatch&~topatch),lineprops )); %l->l + set(to( fmpatch&~topatch),lineprops, get(fm( fmpatch&~topatch),patchprops )); %p->l + set(to(~fmpatch& topatch),patchprops, get(fm(~fmpatch& topatch),lineprops) ,'FaceColor','none'); %l->p + + + +function arrow_props +% display further help info about ARROW properties + c = sprintf('\n'); + disp([c ... + 'ARROW Properties: Default values are given in [square brackets], and other' c ... + ' acceptable equivalent property names are in (parenthesis).' c c ... + ' Start The starting points. For N arrows, B' c ... + ' this should be a Nx2 or Nx3 matrix. /|\ ^' c ... + ' Stop The end points. For N arrows, this /|||\ |' c ... + ' should be a Nx2 or Nx3 matrix. //|||\\ L|' c ... + ' Length Length of the arrowhead (in pixels on ///|||\\\ e|' c ... + ' screen, points on a page). [16] (Len) ////|||\\\\ n|' c ... + ' BaseAngle Angle (degrees) of the base angle /////|D|\\\\\ g|' c ... + ' ADE. For a simple stick arrow, use //// ||| \\\\ t|' c ... + ' BaseAngle=TipAngle. [90] (Base) /// ||| \\\ h|' c ... + ' TipAngle Angle (degrees) of tip angle ABC. //<----->|| \\ |' c ... + ' [16] (Tip) / base ||| \ V' c ... + ' Width Width of the base in pixels. Not E angle ||<-------->C' c ... + ' the ''LineWidth'' prop. [0] (Wid) |||tipangle' c ... + ' Page If provided, non-empty, and not NaN, |||' c ... + ' this causes ARROW to use hardcopy |||' c ... + ' rather than onscreen proportions. A' c ... + ' This is important if screen aspect --> <-- width' c ... + ' ratio and hardcopy aspect ratio are ----CrossDir---->' c ... + ' vastly different. []' c... + ' CrossDir A vector giving the direction towards which the fletches' c ... + ' on the arrow should go. [computed such that it is perpen-' c ... + ' dicular to both the arrow direction and the view direction' c ... + ' (i.e., as if it was pasted on a normal 2-D graph)] (Note' c ... + ' that CrossDir is a vector. Also note that if an axis is' c ... + ' plotted on a log scale, then the corresponding component' c ... + ' of CrossDir must also be set appropriately, i.e., to 1 for' c ... + ' no change in that direction, >1 for a positive change, >0' c ... + ' and <1 for negative change.)' c ... + ' NormalDir A vector normal to the fletch direction (CrossDir is then' c ... + ' computed by the vector cross product [Line]x[NormalDir]). []' c ... + ' (Note that NormalDir is a vector. Unlike CrossDir,' c ... + ' NormalDir is used as is regardless of log-scaled axes.)' c ... + ' Ends Set which end has an arrowhead. Valid values are ''none'',' c ... + ' ''stop'', ''start'', and ''both''. [''stop''] (End)' c... + ' ObjectHandles Vector of handles to previously-created arrows to be' c ... + ' updated or line objects to be converted to arrows.' c ... + ' [] (Object,Handle)' c ]); + + + +function out = arrow_demo + % demo + % create the data + [x,y,z] = peaks; + [ddd,out.iii]=max(z(:)); + out.axlim = [min(x(:)) max(x(:)) min(y(:)) max(y(:)) min(z(:)) max(z(:))]; + + % modify it by inserting some NaN's + [m,n] = size(z); + m = floor(m/2); + n = floor(n/2); + z(1:m,1:n) = NaN*ones(m,n); + + % graph it + clf('reset'); + out.hs=surf(x,y,z); + out.x=x; out.y=y; out.z=z; + xlabel('x'); ylabel('y'); + +function h = arrow_demo3(in) + % set the view + axlim = in.axlim; + axis(axlim); + zlabel('z'); + %set(in.hs,'FaceColor','interp'); + view(viewmtx(-37.5,30,20)); + title(['Demo of the capabilities of the ARROW function in 3-D']); + + % Normal blue arrow + h1 = feval(mfilename,[axlim(1) axlim(4) 4],[-.8 1.2 4], ... + 'EdgeColor','b','FaceColor','b'); + + % Normal white arrow, clipped by the surface + h2 = feval(mfilename,axlim([1 4 6]),[0 2 4]); + t=text(-2.4,2.7,7.7,'arrow clipped by surf'); + + % Baseangle<90 + h3 = feval(mfilename,[3 .125 3.5],[1.375 0.125 3.5],30,50); + t2=text(3.1,.125,3.5,'local maximum'); + + % Baseangle<90, fill and edge colors different + h4 = feval(mfilename,axlim(1:2:5)*.5,[0 0 0],36,60,25, ... + 'EdgeColor','b','FaceColor','c'); + t3=text(axlim(1)*.5,axlim(3)*.5,axlim(5)*.5-.75,'origin'); + set(t3,'HorizontalAlignment','center'); + + % Baseangle>90, black fill + h5 = feval(mfilename,[-2.9 2.9 3],[-1.3 .4 3.2],30,120,[],6, ... + 'EdgeColor','r','FaceColor','k','LineWidth',2); + + % Baseangle>90, no fill + h6 = feval(mfilename,[-2.9 2.9 1.3],[-1.3 .4 1.5],30,120,[],6, ... + 'EdgeColor','r','FaceColor','none','LineWidth',2); + + % Stick arrow + h7 = feval(mfilename,[-1.6 -1.65 -6.5],[0 -1.65 -6.5],[],16,16); + t4=text(-1.5,-1.65,-7.25,'global mininum'); + set(t4,'HorizontalAlignment','center'); + + % Normal, black fill + h8 = feval(mfilename,[-1.4 0 -7.2],[-1.4 0 -3],'FaceColor','k'); + t5=text(-1.5,0,-7.75,'local minimum'); + set(t5,'HorizontalAlignment','center'); + + % Gray fill, crossdir specified, 'LineStyle' -- + h9 = feval(mfilename,[-3 2.2 -6],[-3 2.2 -.05],36,[],27,6,[],[0 -1 0], ... + 'EdgeColor','k','FaceColor',.75*[1 1 1],'LineStyle','--'); + + % a series of normal arrows, linearly spaced, crossdir specified + h10y=(0:4)'/3; + h10 = feval(mfilename,[-3*ones(size(h10y)) h10y -6.5*ones(size(h10y))], ... + [-3*ones(size(h10y)) h10y -.05*ones(size(h10y))], ... + 12,[],[],[],[],[0 -1 0]); + + % a series of normal arrows, linearly spaced + h11x=(1:.33:2.8)'; + h11 = feval(mfilename,[h11x -3*ones(size(h11x)) 6.5*ones(size(h11x))], ... + [h11x -3*ones(size(h11x)) -.05*ones(size(h11x))]); + + % series of magenta arrows, radially oriented, crossdir specified + h12x=2; h12y=-3; h12z=axlim(5)/2; h12xr=1; h12zr=h12z; ir=.15;or=.81; + h12t=(0:11)'/6*pi; + h12 = feval(mfilename, ... + [h12x+h12xr*cos(h12t)*ir h12y*ones(size(h12t)) ... + h12z+h12zr*sin(h12t)*ir],[h12x+h12xr*cos(h12t)*or ... + h12y*ones(size(h12t)) h12z+h12zr*sin(h12t)*or], ... + 10,[],[],[],[], ... + [-h12xr*sin(h12t) zeros(size(h12t)) h12zr*cos(h12t)],... + 'FaceColor','none','EdgeColor','m'); + + % series of normal arrows, tangentially oriented, crossdir specified + or13=.91; h13t=(0:.5:12)'/6*pi; + locs = [h12x+h12xr*cos(h13t)*or13 h12y*ones(size(h13t)) h12z+h12zr*sin(h13t)*or13]; + h13 = feval(mfilename,locs(1:end-1,:),locs(2:end,:),6); + + % arrow with no line ==> oriented downwards + h14 = feval(mfilename,[3 3 .100001],[3 3 .1],30); + t6=text(3,3,3.6,'no line'); set(t6,'HorizontalAlignment','center'); + + % arrow with arrowheads at both ends + h15 = feval(mfilename,[-.5 -3 -3],[1 -3 -3],'Ends','both','FaceColor','g', ... + 'Length',20,'Width',3,'CrossDir',[0 0 1],'TipAngle',25); + + h=[h1;h2;h3;h4;h5;h6;h7;h8;h9;h10;h11;h12;h13;h14;h15]; + +function h = arrow_demo2(in) + axlim = in.axlim; + dolog = 1; + if (dolog), set(in.hs,'YData',10.^get(in.hs,'YData')); end; + shading('interp'); + view(2); + title(['Demo of the capabilities of the ARROW function in 2-D']); + hold on; [C,H]=contour(in.x,in.y,in.z,20,'-'); hold off; + for k=H', + set(k,'ZData',(axlim(6)+1)*ones(size(get(k,'XData'))),'Color','k'); + if (dolog), set(k,'YData',10.^get(k,'YData')); end; + end; + if (dolog), axis([axlim(1:2) 10.^axlim(3:4)]); set(gca,'YScale','log'); + else, axis(axlim(1:4)); end; + + % Normal blue arrow + start = [axlim(1) axlim(4) axlim(6)+2]; + stop = [in.x(in.iii) in.y(in.iii) axlim(6)+2]; + if (dolog), start(:,2)=10.^start(:,2); stop(:,2)=10.^stop(:,2); end; + h1 = feval(mfilename,start,stop,'EdgeColor','b','FaceColor','b'); + + % three arrows with varying fill, width, and baseangle + start = [-3 -3 10; -3 -1.5 10; -1.5 -3 10]; + stop = [-.03 -.03 10; -.03 -1.5 10; -1.5 -.03 10]; + if (dolog), start(:,2)=10.^start(:,2); stop(:,2)=10.^stop(:,2); end; + h2 = feval(mfilename,start,stop,24,[90;60;120],[],[0;0;4],'Ends',str2mat('both','stop','stop')); + set(h2(2),'EdgeColor',[0 .35 0],'FaceColor',[0 .85 .85]); + set(h2(3),'EdgeColor','r','FaceColor',[1 .5 1]); + h=[h1;h2]; diff --git a/Visualization_functions/bar_wani.m b/Visualization_functions/bar_wani.m new file mode 100644 index 00000000..2c09e86a --- /dev/null +++ b/Visualization_functions/bar_wani.m @@ -0,0 +1,474 @@ +function handle = bar_wani(y, e, bar_width, varargin) + +% Draw a bar plot with error bars with some additional useful features. +% +% Usage: +% ------------------------------------------------------------------------- +% h = bar_wani(y, e, bar_width, varargin) +% +% Inputs: +% ------------------------------------------------------------------------- +% y y values for bars (row: bar grouping, column: bar values +% within a bar group) (e.g., if there are m bar groups and +% n bars for each group, y should be a m x n matrix) +% e error bars (m x n matrix) +% bar_width value for bar width between 0 and 1. This will determine +% the intervals between bars. +% +% Optional inputs: Enter keyword followed by variable with values +% 'ylim' y axis range, (e.g., 'ylim', [-1 1]) +% 'ytick' y tick values (e.g., 'ytick', -.002:.001:.002) +% 'errbar_width' the horizontal width of error bars (e.g., 'errbar_width', [-.01 .01]) +% 'colors' bar colors: each row determines the color for each bar in order (n x 3 matrix) +% 'ast' put asterisks according to p values, which should be +% given. (e.g., 'ast', p [m x n]) *p<.05, **p<.01, ***p<.001 +% 'btwlines' this option puts lines between bar groups. This is +% followed by the line style (e.g., 'btwlines', '--'); +% 'dosave' followed by savename (e.g., 'dosave', savename) +% +% Some advanced options +% 'scatter' show individual data points, which should be in cell array +% 'text' this will put a number for each bar (e.g., 'text', round(y) [m x n]) +% 'ast_adj_y_pos' When the asterisk locations (on y axis) for bars with positive +% values are off, you can adjust it using this option +% 'ast_adj_y_neg' When the asterisk locations (on y axis) for bars with negative +% values are off, you can adjust it using this option +% 'ast_adj_x' When the asterisk locations (on x axis) are off, you +% can adjust it using this option +% 'bar_edgecol' You can use different colors for bar edges (col [n x 3 matrix]) +% 'bar_edgewidth' You can change linewidths for bar edges +% +% Outputs: +% ------------------------------------------------------------------------- +% h graphic handles for a bar plot +% +% Examples: you can see the output in +% http://wagerlab.colorado.edu/wiki/doku.php/help/core/figure_gallery +% ------------------------------------------------------------------------- +% % data +% y = [-0.6518 -0.6934 -0.5417 -0.6496 -0.5946 -0.3839 +% 1.1511 0.9090 1.1681 1.2892 0.9346 1.1383]; +% e = [0.3226 0.2936 0.3080 0.3203 0.3368 0.3167 +% 0.4026 0.4088 0.4012 0.5586 0.3734 0.4257]; +% p = [0.0433 0.0182 0.0785 0.0426 0.0775 0.2255 +% 0.0042 0.0262 0.0036 0.0210 0.0123 0.0075]; +% +% col = [0 0.1157 0.2686 +% 0.1157 0.2765 0.4725 +% 0.4843 0.1157 0.1078 +% 0.3667 0.4765 0.1353 +% 0.2765 0.1902 0.3824 +% 0.0922 0.4216 0.5118 +% 0.7941 0.3235 0]; +% +% % draw +% h = bar_wani(y, e, .8, 'colors', col, 'errbar_width', [0 0], 'ast', p, 'ylim', [-2.5 2.5], 'ytick', -2:2, 'ast_adj_x', 0, 'ast_adj_y_neg', .15); +% set(gca, 'ytickLabel', num2str(get(gca, 'ytick')')); +% set(h, 'position', [1 531 399 169]); +% +% savename = 'example_barwani.pdf'; +% +% try +% pagesetup(h); +% saveas(h, savename); +% catch +% pagesetup(h); +% saveas(h, savename); +% end +% +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo + +% Programmers' notes: + + +dosave = 0; +doman_ylim = 0; +docolor = 0; +doast = 0; +doerrbar_width = 0; +doytick = 0; +dobtwlines = 0; +dotext = 0; +doscatter = 0; +ast_adj_y_pos = .2; +ast_adj_y_neg = .5; +ast_adj_x = 0; +text_adj_y = 0; +bar_edgewidth = 1.8; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'dosave', 'save'} + dosave = 1; + savename = varargin{i+1}; + case {'ylim'} + doman_ylim = 1; + ylim = varargin{i+1}; + case {'colors', 'color'} + docolor = 1; colors = varargin{i+1}; + case {'ast'} + doast = 1; p = varargin{i+1}; + case {'errbar_width'} + doerrbar_width = 1; errwidth = varargin{i+1}; + case {'ytick'} + doytick = 1; ytick = varargin{i+1}; + case {'btwlines'} + dobtwlines = 1; btwlnstyle = varargin{i+1}; + case {'text'} + dotext = 1; text_bar = varargin{i+1}; + case {'scatter'} + doscatter = 1; sc_data = fliplr(varargin{i+1}); + case {'ast_adj_y_pos'} + ast_adj_y_pos = ast_adj_y_pos + varargin{i+1}; + case {'ast_adj_y_neg'} + ast_adj_y_neg = ast_adj_y_neg + varargin{i+1}; + case {'ast_adj_x'} + ast_adj_x = ast_adj_x + varargin{i+1}; + case {'text_adj_y'} + text_adj_y = varargin{i+1}; + case {'bar_edgecol'} + bar_edgecol = varargin{i+1}; + case {'bar_edgewidth'} + bar_edgewidth = varargin{i+1}; + end + end +end + +handle = create_figure('Bar plot'); +set(gcf, 'Position', [1 450 300.*size(y,1) 250]); + +barnum = size(y,2); +grnum = size(y,1); + +if ~doman_ylim + step = (max(max(y+e))-min(min(y+e)))*.2; + ymax = max(max(y+e)) + step; + ymin = min(min(y-e)) - step; +else + ymin = ylim(1); + ymax = ylim(2); +end + +barweb(y, e, bar_width, [], [], [], [], [], [], [], [], []); +set(gcf, 'Color', 'w') +set(gca, 'ylim', [ymin ymax], 'XLim', [0.55 .45+size(y,1)], 'fontsize', 20, 'linewidth', 1.8); % **ADJUST**: adjust basic setting for axis + +if doytick + set(gca, 'ytick', ytick); +end + +h = get(gca, 'children'); + +if docolor + for i = (barnum+1):2*barnum + try + colors_flip = fliplr(colors); + set(h(i), 'FaceColor', colors_flip{i-barnum}); + catch + colors_flip = flipud(colors); + set(h(i), 'FaceColor', colors_flip(i-barnum,:)); + end + end +end + +if doast + ast = p_asterisk(p); + for i = 1:barnum + for j = 1:grnum + if y(j,i) > 0 + ast_loc(j,i) = y(j,i) + e(j,i) + ast_adj_y_pos * max(max(e)); + else + ast_loc(j,i) = y(j,i) - e(j,i) - ast_adj_y_neg * max(max(e)); + end + end + end +end + +for i = 1:barnum + if ~doerrbar_width + errwidth = [-.03 .03]; + end + + hh = get(h(i), 'children'); + xdata = get(hh(2), 'xData'); + + for j = 1:(length(xdata)/9) + xdata(9*(j-1)+4:9*(j-1)+5) = xdata(9*(j-1)+1:9*(j-1)+2)+errwidth ; + xdata(9*(j-1)+7:9*(j-1)+8) = xdata(9*(j-1)+1:9*(j-1)+2)+errwidth ; + if doast + text(double(xdata(9*(j-1)+1)+ast_adj_x), double(ast_loc(j,barnum-i+1)), ast{j,barnum-i+1}, 'fontsize', 18, 'HorizontalAlignment','center'); % **ADJUST** font size + if dotext + text_y = ymax*1.15 + text_adj_y; + text(xdata(9*(j-1)+1), text_y, num2str(text_bar(j, (barnum-i+1))), 'fontsize', 20, 'HorizontalAlignment','center'); % **ADJUST** font size + end + end + + if doscatter + hold on; scatter(double(xdata(9*(j-1)+1)).*ones(size(sc_data{j,i})), sc_data{j,i}, 4, 'b', 'filled'); + if ymin > min(sc_data{j,i}), ymin = min(sc_data{j,i}); set(gca, 'ylim', [ymin ymax]); end + if ymax < max(sc_data{j,i}), ymax = max(sc_data{j,i}); set(gca, 'ylim', [ymin ymax]); end + end + + if grnum == 1 + break + end + end + + set(hh(2), 'xdata', xdata); + set(h(i), 'lineWidth', 2); +end + + +j = 1; +if ~exist('bar_edgecol', 'var'), bar_edgecol = repmat([0 0 0], barnum, 1); end +for i = barnum+1:2*barnum, set(h(i), 'lineWidth', bar_edgewidth, 'edgecolor', bar_edgecol(j,:)); j = j+1; end % **ADJUST** bar linewidth and edge color + +% btwlines: drawing lines between bar groups +if dobtwlines + btwx = 1.5:1:grnum; + btwy = get(gca, 'ylim'); + btwx = repmat(btwx', 1, 2); + btwy = repmat(btwy, size(btwx,1),1); + btwlnwidth = 1; + btwcol = [.3 .3 .3]; + for i = 1:size(btwx,1) + line(btwx(i,:), btwy(i,:), 'linestyle', btwlnstyle, 'linewidth', btwlnwidth, 'color', btwcol) + end +end + +if dosave + try + pagesetup(handle); + saveas(handle, savename); + catch + pagesetup(handle); + saveas(handle, savename); + end +end + +end + +function ast = p_asterisk(p) +% function ast = p_asterisk(p) +% change p values into asterisk +% + +p = double(p < .05) + double(p < .01) + double(p < .001); + +ast = cell(size(p)); + +for i = 1:size(p,1) + for j = 1:size(p,2) + if p(i,j) > 0 + ast{i,j} = repmat('*', 1, p(i,j)); + else + ast{i,j} = ''; + end + end +end + +end + + +function handles = barweb(barvalues, errors, width, groupnames, bw_title, bw_xlabel, bw_ylabel, bw_colormap, gridstatus, bw_legend, error_sides, legend_type) + +% This function is from http://www.mathworks.com/matlabcentral/fileexchange/10803-barweb--bargraph-with-error-bars- + +% Usage: handles = barweb(barvalues, errors, width, groupnames, bw_title, bw_xlabel, bw_ylabel, bw_colormap, gridstatus, bw_legend, error_sides, legend_type) +% +% Ex: handles = barweb(my_barvalues, my_errors, [], [], [], [], [], bone, [], bw_legend, 1, 'axis') +% +% barweb is the m-by-n matrix of barvalues to be plotted. +% barweb calls the MATLAB bar function and plots m groups of n bars using the width and bw_colormap parameters. +% If you want all the bars to be the same color, then set bw_colormap equal to the RBG matrix value ie. (bw_colormap = [1 0 0] for all red bars) +% barweb then calls the MATLAB errorbar function to draw barvalues with error bars of length error. +% groupnames is an m-length cellstr vector of groupnames (i.e. groupnames = {'group 1'; 'group 2'}). For no groupnames, enter [] or {} +% The errors matrix is of the same form of the barvalues matrix, namely m group of n errors. +% Gridstatus is either 'x','xy', 'y', or 'none' for no grid. +% No legend will be shown if the legend paramter is not provided +% 'error_sides = 2' plots +/- std while 'error_sides = 1' plots just + std +% legend_type = 'axis' produces the legend along the x-axis while legend_type = 'plot' produces the standard legend. See figure for more details +% +% The following default values are used if parameters are left out or skipped by using []. +% width = 1 (0 < width < 1; widths greater than 1 will produce overlapping bars) +% groupnames = '1', '2', ... number_of_groups +% bw_title, bw_xlabel, bw_ylabel = [] +% bw_color_map = jet +% gridstatus = 'none' +% bw_legend = [] +% error_sides = 2; +% legend_type = 'plot'; +% +% A list of handles are returned so that the user can change the properties of the plot +% handles.ax: handle to current axis +% handles.bars: handle to bar plot +% handles.errors: a vector of handles to the error plots, with each handle corresponding to a column in the error matrix +% handles.legend: handle to legend +% +% See the MATLAB functions bar and errorbar for more information +% +% Author: Bolu Ajiboye +% Created: October 18, 2005 (ver 1.0) +% Updated: Dec 07, 2006 (ver 2.1) +% Updated: July 21, 2008 (ver 2.3) + +% Get function arguments +if nargin < 2 + error('Must have at least the first two arguments: barweb(barvalues, errors, width, groupnames, bw_title, bw_xlabel, bw_ylabel, bw_colormap, gridstatus, bw_legend, barwebtype)'); +elseif nargin == 2 + width = 1; + groupnames = 1:size(barvalues,1); + bw_title = []; + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 3 + groupnames = 1:size(barvalues,1); + bw_title = []; + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 4 + bw_title = []; + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 5 + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 6 + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 7 + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 8 + gridstatus = 'none'; + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 9 + bw_legend = []; + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 10 + error_sides = 2; + legend_type = 'plot'; +elseif nargin == 11 + legend_type = 'plot'; +end + +change_axis = 0; +ymax = 0; + +if size(barvalues,1) ~= size(errors,1) || size(barvalues,2) ~= size(errors,2) + error('barvalues and errors matrix must be of same dimension'); +else + if size(barvalues,2) == 1 + barvalues = barvalues'; + errors = errors'; + end + if size(barvalues,1) == 1 + barvalues = [barvalues; zeros(1,length(barvalues))]; + errors = [errors; zeros(1,size(barvalues,2))]; + change_axis = 1; + end + numgroups = size(barvalues, 1); % number of groups + numbars = size(barvalues, 2); % number of bars in a group + if isempty(width) + width = 1; + end + + % Plot bars + handles.bars = bar(barvalues, width,'edgecolor','k', 'linewidth', 2); + hold on + if ~isempty(bw_colormap) + colormap(bw_colormap); + else + colormap(jet); + end + if ~isempty(bw_legend) && ~strcmp(legend_type, 'axis') + handles.legend = legend(bw_legend, 'location', 'best', 'fontsize',12); + legend boxoff; + else + handles.legend = []; + end + + % Plot erros + for i = 1:numbars + x =get(get(handles.bars(i),'children'), 'xdata'); + x = mean(x([1 3],:)); + handles.errors(i) = errorbar(x, barvalues(:,i), errors(:,i), 'k', 'linestyle', 'none', 'linewidth', 2); + ymax = max([ymax; barvalues(:,i)+errors(:,i)]); + ymin = min([ymax; barvalues(:,i)+errors(:,i)]); % wani added + end + + if error_sides == 1 + set(gca,'children', flipud(get(gca,'children'))); + end + + ylim([ymin*1.1 ymax*1.1]); % wani changed + + xlim([0.5 numgroups-change_axis+0.5]); + + if strcmp(legend_type, 'axis') + for i = 1:numbars + xdata = get(handles.errors(i),'xdata'); + for j = 1:length(xdata) + text(xdata(j), -0.03*ymax*1.1, bw_legend(i), 'Rotation', 60, 'fontsize', 12, 'HorizontalAlignment', 'right'); + end + end + set(gca,'xaxislocation','top'); + end + + if ~isempty(bw_title) + title(bw_title, 'fontsize',14); + end + if ~isempty(bw_xlabel) + xlabel(bw_xlabel, 'fontsize',14); + end + if ~isempty(bw_ylabel) + ylabel(bw_ylabel, 'fontsize',14); + end + + set(gca, 'xticklabel', groupnames, 'box', 'off', 'ticklength', [0 0], 'fontsize', 12, 'xtick',1:numgroups, 'linewidth', 2,'xgrid','off','ygrid','off'); + if ~isempty(gridstatus) && any(gridstatus == 'x') + set(gca,'xgrid','on'); + end + if ~isempty(gridstatus) && any(gridstatus == 'y') + set(gca,'ygrid','on'); + end + + handles.ax = gca; + + hold off +end + +end diff --git a/Visualization_functions/barplot_colored.m b/Visualization_functions/barplot_colored.m new file mode 100644 index 00000000..c133811f --- /dev/null +++ b/Visualization_functions/barplot_colored.m @@ -0,0 +1,109 @@ +function [h,s]=barplot_colored(data,varargin) + +% haven't had time to document this yet +% this is a good function though +% within-subject error bars now added; use 'within' +% +% [h,s]=barplot_colored(data,varargin) +% +% h is the axes handle, s is the handle to its children +% +% Var args: strings, followed by values for each +% if strcmp(varargin{k},'colormap') +% eval(['colormapfun=@' varargin{k+1} ';']) +% elseif strcmp(varargin{k},'title') +% title=varargin{k+1}; +% elseif strcmp(varargin{k},'XTickLabels') +% XTickLabels=varargin{k+1}; +% elseif strcmp(varargin{k},'ylabel') +% ylabel=varargin{k+1}; +% elseif strcmp(varargin{k},'xlabel') +% xlabel=varargin{k+1}; +% elseif strcmp(varargin{k},'x') +% x=varargin{k+1}; +% elseif strcmp(varargin{k},'within') -> DO within-subject STE +% end +% +% NOTE: For this function, keywords must be even-numbered argument entries, +% e.g., arg 2, 4, 6. Odd argument entries are values. +% For example: This works, and you need the extra empty arg after 'within' +% [h1, s1] = barplot_colored(pexp1, 'within', ' ', 'title', 'Pattern expression', 'XTickLabels', dat.Y_names, 'x', 1:nterms); +% +% You can assign arbitrary colors to bars by setting the colormap: +% [h, s] = barplot_colored([corr_temp corr_rep]); +% cm = [1 .5 0; .5 0 1]; +% colormap(cm) +% +% Example: A grouped barplot +% --------------------------------------------------- +% dat = rand(20, 4); +% create_figure('bars'); +% [h1, s1] = barplot_colored(dat, 'x', [1 2 4 5]); +% % set(h2, 'BarWidth', .9) +% colormap([1 0 0; 0 0 1; 1 0 0; 0 0 1]) + +if iscell(data) + for k=1:length(data) + means(k)=mean(data{k}); + stderr(k)=std(data{k})/sqrt(length(data{k})); + end +else + means=mean(data); + stderr=std(data)/sqrt(size(data,1)); +end + +x = 1:length(means); % can replace x values + +for k=1:2:length(varargin) + if strcmp(varargin{k},'colormap') + eval(['colormapfun=@' varargin{k+1} ';']) + elseif strcmp(varargin{k},'title') + mytitle=varargin{k+1}; + elseif strcmp(varargin{k},'XTickLabel') || strcmp(varargin{k},'XTickLabels') + XTickLabel=varargin{k+1}; + elseif strcmp(varargin{k},'Ylabel') + Ylabel=varargin{k+1}; + elseif strcmp(varargin{k},'Xlabel') + Xlabel=varargin{k+1}; + elseif strcmp(varargin{k},'x') + x=varargin{k+1}; + + elseif strcmp(varargin{k},'within') + if iscell(data), error('Within error bars not implemented for cell input data'); end + + stderr = barplot_get_within_ste(data); + stderr = repmat(stderr, 1, length(means)); + end +end + + +if ~exist('colormapfun','var') + colormapfun=@hsv; +end + +h=bar(x, means); +s=get(h,'Children'); + +colormap(colormapfun(length(means))); +set(s,'CData',1:length(means)); + +hold on + +errorbar(x, means,stderr,'k','LineWidth',2,'LineStyle','none') +set(gca,'Xlim',[0 max(x)+1]) +if exist('XTickLabel','var') + set(gca,'XTickLabel',XTickLabel, 'XTick', x) +else + set(gca,'XTickLabel',[]) +end +if exist('mytitle','var') + title(mytitle) +end +if exist('Ylabel','var') + ylabel(Ylabel) +end +if exist('Xlabel','var') + xlabel(Xlabel) +end + +hold off \ No newline at end of file diff --git a/Visualization_functions/barplot_columns.m b/Visualization_functions/barplot_columns.m new file mode 100755 index 00000000..717b24ab --- /dev/null +++ b/Visualization_functions/barplot_columns.m @@ -0,0 +1,338 @@ +function [hout,dat,xdat, h] = barplot_columns(dat,varargin) +% [axishandle,adjusted data,x-data, barhandle] = barplot_columns(dat,[title],[covs],[extra string args]) +% +% This function makes a barplot of columns of data, with standard error +% bars. Optional arguments include removing continuous covariates before plotting, +% robust (IRLS) estimation of means and correlations with covariates, and +% within-subject error bars based on the subject x condition interaction +% (overall), which is not quite the standard error contrasts of interest, +% but is the standard error for a 1-way repeated measures ANOVA. +% +% plots circles around points at z >= 1.96 +% plots individual points, unless you enter 4th argument +% +% if dat is a cell array, each entry becomes one "bar". Useful if n +% observations is different for each column. +% +% Examples: Just plot means and SE +% h = barplot_columns(tmp,'Cluster 1',[],1); +% +% Optional arguments +% 1 - Title for figure +% 2 - covariates +% 3 - String Arguments +% 'nofig' : do not make figure +% 'noind' : do not plot individual scores +% 'plotout': circle potential outliers at z>1.96 in red +% 'dorob' : do robust IRLS means and correlations +% 'dolines' : plot lines showing individual effects +% 'within' : within-subjects standard errors, followed by contrast +% matrix +% '95CI' : error bars are 95% CI instead of SE +% 'line' : Make line plot instead of bar plot +% 'number' : plot case numbers instead of points +% 'x' : followed by x-axis values for bars +% 'color' : followed by color for bars (text: 'r' or [r g b]) OR +% cell array with names of colors cell for each line/bar +% +% +% Examples: +% barplot_columns(ctmp,'RT effects by Switch Type',overall_sw,'nofig','dorob') +% +% Standard Errors ARE NOT Adjusted for covariate, right now. +% +% Example: within-subjects std. errors +% barplot_columns(dat, 'Means', [], 'nofig', 'within', c); +% +% The example below uses color, width, and xposition arguments to make a grouped +% barplot showing effects for two groups: +% exp_dat = EXPT.error_rates(EXPT.group==1,:); +% control_dat = EXPT.error_rates(EXPT.group==-1,:); +% barplot_columns(exp_dat, 'Error rates', [], 'nofig', 'noind', 'color', 'r','width', .4); +% barplot_columns(control_dat, 'Error rates', [], 'nofig', 'noind', 'color', 'b','width', .4, 'x', (1:9)+.5); +% set(gca, 'XLim', [0 10], 'XTick', 1:9) + + +% ---------------------------------------------------- +% > Set up input arguments +% ---------------------------------------------------- + +dofig = 1; doind = 1; plotout = 0; dorob = 0; xdat = []; dolines = 0; +dowithin = 0; donumber = 0; dojitter = 1; % jitter is for numbers only +mycolor = [.8 .8 .8]; +barwidth = .8; +dolineplot = 0; +do95CI = 0; + +%handle input of different lens -- passed in as cell array +if iscell(dat) + maxlen=0; + for i=1:length(dat) + if length(dat{i}) > maxlen, maxlen = length(dat{i}); end + end + + dat2 = repmat(NaN, maxlen, length(dat)); + for i=1:length(dat) + dat2(1:length(dat{i}),i) = dat{i}; + end + dat = dat2; +end + +xvals = 1:size(dat, 2); + +if length(varargin) > 2 + for i = 3:length(varargin) + if strcmp(varargin{i},'nofig'), dofig = 0; end + if strcmp(varargin{i},'noind'), doind = 0; end + if strcmp(varargin{i},'plotout'), plotout = 1; end + if strcmp(varargin{i},'dorob'), dorob = 1; end + if strcmp(varargin{i},'dolines'), dolines = 1; end + if strcmp(varargin{i},'number'), donumber = 1; end + if strcmp(varargin{i}, 'within'), dowithin = 1; end %cons = varargin{i + 1}; end + if strcmp(varargin{i}, '95CI'), do95CI = 1; end + if strcmp(varargin{i},'line'), dolineplot = 1; end + if strcmp(varargin{i}, 'x'), xvals = varargin{i + 1}; end + if strcmp(varargin{i}, 'color'), mycolor = varargin{i + 1}; end + if strcmp(varargin{i}, 'width'), barwidth = varargin{i + 1}; end + + end +end + +dat = double(dat); + +if length(varargin) > 1, + covs = varargin{2}; if ~isempty(covs), covs = scale(covs,1); end +else covs = []; +end + +% delete nans casewise +%wh = find(any(isnan(dat),2)); +%dat(wh,:) = []; +%if ~isempty(covs), covs(wh,:) = []; end + +% replace nans with mean +for i = 1:size(dat,2), + if any(isnan(dat(:,i))) + warning('Some NaNs!') + %dat(find(isnan(dat(:,i))),i) = nanmean(dat(:,i)); + end +end + +% find NaN columns +wh = find(all(isnan(dat),1)); +dat(:,wh) = 0; + +%dat1 = dat; % save original data for ind subj plot + +% get final design matrix, intercept is last column +[nn,ny] = size(dat); +k = size(covs,2); + +% add intercept if not already in model +if ~isempty(covs) + wh_oldintercept = find(all(diff(covs) < eps)); + covs(:,wh_oldintercept) = []; +end + +X = [covs ones(nn,1)]; +wh_intercept = k+1; + +% ---------------------------------------------------- +% > Get means and standard error of means +% With robust option, if specified, and removing +% covariates, if there are any. +% ---------------------------------------------------- + + +% if dorob +% % ROBUST FIT +stderr = []; + +%[b,t,p,sig,f,fp,fsig,stat] = robust_reg_matrix(X,dat,1); + +%for i = 1:k +% [x,y,r,p,mycor(1,i),prob,serob] = partialcor(X,y,i) + +% key vars are : +% mymeans, stderr, mycor + +wh_reg = 1; % regressor of interest + +for i = 1:ny + + fprintf(1,'\nColumn %3.0f:\n',i); + + % remove nans from this column + tmpy = dat(:,i); + tmpx = X; +% % wh = find(any(isnan(X),2) | any(isnan(tmpy),2)); +% % tmpx(wh,:) = []; tmpy(wh) = []; + + [wasnan, tmpx, tmpy] = nanremove(tmpx, tmpy); + + n(i) = size(tmpy,1); + + % get mean and standard error of intercept (robust or OLS) + % y is adjusted for all non-intercept covs + % stats has weights, stats.w, which are all 1 for OLS + [x,newy,r,p,stderr(i),mymeans(i),stats] = partialcor(tmpx,tmpy,wh_intercept,1,dorob); + + %95% CI? + if do95CI, stderr(i) = stderr(i) * 1.96; end + + y(:,i) = naninsert(wasnan, newy); + + %%%not needed y(:,i) = y(:,i) + mymeans(i); % add mean + myweights(:,i) = naninsert(wasnan, stats.w); + + if ~isempty(covs) + % cov of interest here is fixed at 1 (see above) + + % if we have covs, leave in cov. of interest (cov1) + % y is adjusted for all non-intercept covs + [x,y(:,i),mycor(i),mycorrp(i)] = partialcor(tmpx,tmpy,wh_reg,1,dorob); + + %not needed %%% y(:,i) = y(:,i) + mymeans(i); % add mean + + end + + fprintf(1,'\n'); +end + +dat = y; % adjusted data, for plot + +if dowithin + within_ste = barplot_get_within_ste(dat); + + stderr = repmat(within_ste, 1, size(dat, 2)); +end + +% ---------------------------------------------------- +% > Make figure +% ---------------------------------------------------- + +if dofig + f = figure('Color','w'); hout = gca; set(gca,'FontSize',18); %hold on; grid on; +else + f = get(gcf); hout = gca; set(gca,'FontSize',18); hold on; +end + + +% ---------------------------------------------------- +% > BARPLOT (or line plot) +% ---------------------------------------------------- + +if dolineplot + h = plot(xvals, mymeans, 'o-', 'Color', mycolor, 'MarkerFaceColor', mycolor, 'MarkerSize', 8); + h2 = errorbar(xvals, mymeans, stderr, stderr); + set(h2, 'LineWidth', 2, 'Color', mycolor); +else + h = bar(xvals, mymeans, barwidth); + if iscell(mycolor) + % each bar a different color + for i = 1:length(xvals) + bar(xvals(i), mymeans(i), 'FaceColor', mycolor{i}); + end + else %all bars the same color + set(h,'FaceColor', mycolor); %,'LineWidth',2) + end + tor_bar_steplot(mymeans,stderr,{'k'}, xvals); +end + +%set(gca,'XLim',[0 ny+1],'XTick',1:ny,'XTickLabel',1:ny) +set(gca,'XLim',[min(xvals)-.5 max(xvals) + .5],'XTick',xvals,'XTickLabel',xvals) +xlabel('Task Condition'), ylabel('BOLD contrast') + +if length(varargin) > 0, title(varargin{1},'FontSize',24),end +%set(gcf,'Position',[464 283 930 827]), drawnow + +% sort individual scores by covariate, if covs +if ~isempty(covs) + + [sortedcov,indx] = sort(covs(:,wh_reg)); + dat = dat(indx,:); + sortedw = myweights(indx,:); + + x = (0:(nn-1)) ./ (nn+5) - (.5 - 1/(nn-5)); +else + x = zeros(1,nn) - .1; + sortedw = myweights; +end + +%s = std(dat1); + +if ~doind + % do nothing + +else + % ---------------------------------------------------- + % > Plot individuals + % ---------------------------------------------------- + + x = [x; x]; + + hold on + for i = 1:size(dat,2) + % marker + if mod(i,2)==0, mym='^'; myc=[.2 .2 .2]; else mym='o'; myc=[0 0 0]; end + + for j = 1:size(dat,1) + % color by weight + myc = [1 1 1] - ([1 1 1] .* sortedw(j,i)); + + if i == 1 + xdat(j,:) = x(1,j) + (1:size(dat,2)); % save x values for output (for line plotting) + + % plot lines (if requested) + if dolines + plot(xdat(j,:),dat(j,:),'k','LineWidth',.5,'Color',[.7 .7 .7]); + end + end + + % plot marker + if donumber && ~(any(isnan(x(:, j))) || isnan(dat(j, i))) + plot(x(:,j) + i,[dat(j,i) dat(j,i)]', 'w.'); % to set axis scale appropriately + text(x(1,j) + i, dat(j,i), num2str(j), 'Color', [0 0 0], 'FontSize', 14) + + elseif ~(any(isnan(x(:, j))) || isnan(dat(j, i))) + plot(x(:,j) + i,[dat(j,i) dat(j,i)]',mym,'Color',[0 0 0],'LineWidth',1,'MarkerFaceColor',myc) + end + + end + + z = (dat(:,i) - mean(dat(:,i))) ./ std(dat(:,i)); wh = find(abs(z) >= 1.96); + + % print z-scores of potential outliers to text + %if ~isempty(wh), fprintf(1,'\nCond %3.0f\t',i),for j = 1:length(wh), fprintf(1,'%3.2f\t',z(wh(j))); end,end + + if plotout + if ~isempty(wh),plot(x(:,wh) + i,[dat(wh,i) dat(wh,i)]','ro','MarkerSize',14,'LineWidth',2),end + end + + end + +% if dolines +% for i = 1:size(dat,1) +% plot(xdat(i,:),dat(i,:),'k','LineWidth',.5,'Color',[.7 .7 .7]); +% end +% end + +if donumber && dojitter + + % Jitter positions + han = findobj(gca, 'Type', 'text'); + + x = get(han, 'Position'); x = cat(1, x{:}); + n = size(x, 1); + x(:, 1) = x(:, 1) + .2 * (rand(n, 1) - .5); + for i = 1:size(x, 1) + set(han(i), 'Position', x(i, :)); + end + + % Bold + set(han, 'FontWeight', 'b', 'Color', 'b'); + +end % jitter + + +end % plot individuals \ No newline at end of file diff --git a/Visualization_functions/barplot_columns2.m b/Visualization_functions/barplot_columns2.m new file mode 100644 index 00000000..cafbd014 --- /dev/null +++ b/Visualization_functions/barplot_columns2.m @@ -0,0 +1,462 @@ +function [hout,dat,xdat, h] = barplot_columns2(dat,plottitle,varargin) +% [axishandle,adjusted data,x-data,barhandle] = barplot_columns(dat,title,[options]) +% +% Makes a barplot of columns of data, with standard error bars. +% Optional arguments include removing continuous covariates before plotting, +% robust (IRLS) estimation of means and correlations with covariates, and +% within-subject error bars based on the subject x condition interaction +% (overall), which is not quite the standard error contrasts of interest, +% but is the standard error for a 1-way repeated measures ANOVA. +% +% plots circles around points at z >= 1.96 +% plots individual points, unless you enter 4th argument +% +% if dat is a cell array, each entry becomes one "bar". Useful if n +% observations is different for each column. +% +% Examples: Just plot means and SE +% h = barplot_columns(tmp,'Cluster 1'); +% +% Options +% 'cov' : followed by matrix of covariates +% 'labels' : followed by cellstring of bar labels +% 'xlabelslant' : followed by number of degrees to slant bar labels +% (DEFAULT: 45) +% 'nofig' : do not make figure +% 'ind' : plot individual scores on top of bars +% 'plotout': circle potential outliers at z>1.96 in red +% 'robust' : do robust IRLS means and correlations +% 'indlines' : plot lines showing individual effects +% 'within' : within-subjects standard errors, followed by contrast +% matrix +% 'line' : Make line plot instead of bar plot +% 'number' : plot case numbers instead of points +% 'x' : followed by x-axis values for bars +% 'color' : followed by color for bars (text: 'r' or [r g b]) OR +% cell array with names of colors cell for each line/bar +% 'denan' : remove rows that have NaNs +% +% +% Examples: +% barplot_columns(ctmp,'RT effects by Switch Type',overall_sw,'nofig','robust') +% +% Standard Errors ARE NOT Adjusted for covariate, right now. +% +% Example: within-subjects std. errors +% barplot_columns(dat, 'Means', 'nofig', 'within', 'ind'); +% +% The example below uses color, width, and xposition arguments to make a grouped +% barplot showing effects for two groups: +% exp_dat = EXPT.error_rates(EXPT.group==1,:); +% control_dat = EXPT.error_rates(EXPT.group==-1,:); +% barplot_columns(exp_dat, 'Error rates', 'nofig', 'color', 'r', 'width', .4); +% barplot_columns(control_dat, 'Error rates', 'nofig', 'color', 'b', 'width', .4, 'x', (1:9)+.5); +% set(gca, 'XLim', [0 10], 'XTick', 1:9) + +% PROGRAMMERS' NOTES +% 4/2013 (Luka): changed input format (title and cov now varargin options, not sequential arguments) +% 4/2013 (Luka): changed input parsing +% 4/2013 (Luka): added labels option (adds diagonal labels) +% 4/2013 (Luka): moved labeling to after doind (axis is stable then) +% 4/2013 (Luka): added plabels and toplabels options +% 5/2013 (Luka): made no individuals default +% need to sort out plabel option (including p values from glms in plot) +% right now does not account for inclusion of cov +% figure out how to add stars, legend? + +% ---------------------------------------------------- +% > Set up input arguments +% ---------------------------------------------------- +DO_fig = 1; +DO_ind = 0; +DO_plotout = 0; +DO_rob = 0; +DO_indlines = 0; +DO_within = 0; +DO_number = 0; +DO_lineplot = 0; +DO_plabels = 0; +DO_denan = 0; + +xlabelslant = 45; +mycolor = [.8 .8 .8]; +barwidth = .8; +xdat = []; +covs = []; +xl = ''; +yl = ''; + +%handle input of different lens -- passed in as cell array +if iscell(dat) + maxlen=0; + for i=1:length(dat) + if length(dat{i}) > maxlen, maxlen = length(dat{i}); end + end + + dat2 = repmat(NaN, maxlen, length(dat)); + for i=1:length(dat) + dat2(1:length(dat{i}),i) = dat{i}; + end + dat = dat2; +end + +xvals = 1:size(dat, 2); + +i=1; +while i <= numel(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'cov' + covs = scale(varargin{i+1},1); + i=i+1; + case 'labels' + xlabels = varargin{i+1}; + i=i+1; + case 'xlabelslant' + xlabelslant = varargin{i+1}; + i=i+1; + case 'xlabel' + xl = varargin{i+1}; + i=i+1; + case 'ylabel' + yl = varargin{i+1}; + i=i+1; + case 'ylim', ylimits = varargin{i+1}; i=i+1; + case 'nofig', DO_fig = 0; + case 'ind', DO_ind = 1; + case 'plotout', DO_plotout = 1; + case 'robust', DO_rob = 1; + case 'denan', DO_denan = 1; + case 'indlines', DO_indlines = 1; + case 'number', DO_number = 1; + case 'within', DO_within = 1; + case 'line', DO_lineplot = 1; + case 'plabels', DO_plabels = 1; + case 'toplabels' + toplabels = varargin{i+1}; + i=i+1; + case 'x' + xvals = varargin{i+1}; + i=i+1; + case 'color' + mycolor = varargin{i+1}; + i=i+1; + case 'width' + barwidth = varargin{i+1}; + i=i+1; + otherwise + error(['UNRECOGNIZED OPTION: ' varargin{i}]) + end + else + disp(varargin{i}) + error('Above option UNRECOGNIZED') + end + i=i+1; +end + +if exist('xlabels','var') && numel(xlabels) ~= size(dat,2) + error('Number of labels given (%d) does not match the number of bars (%d)\n',numel(xlabels),size(dat,2)) +end +if ~exist('plottitle','var') + error('Second argument (title) is mandatory.') +end + +if DO_denan + % delete nans casewise + wh = find(any(isnan(dat),2)); + if ~isempty(wh) + fprintf('WARNING: The following rows are being excluded because they include NaNs:\n') + fprintf('%d\n',wh) + dat(wh,:) = []; + if ~isempty(covs), covs(wh,:) = []; end + end +end + +% replace nans with mean +for i = 1:size(dat,2), + if any(isnan(dat(:,i))) + warning('Some NaNs!') + %dat(find(isnan(dat(:,i))),i) = nanmean(dat(:,i)); + end +end + +% find NaN columns +wh = find(all(isnan(dat),1)); +dat(:,wh) = 0; + +% get final design matrix, intercept is last column +[nn,ny] = size(dat); +k = size(covs,2); + +% add intercept if not already in model +if ~isempty(covs) + wh_oldintercept = find(all(diff(covs) < eps)); + covs(:,wh_oldintercept) = []; +end + +X = [covs ones(nn,1)]; +wh_intercept = k+1; + +% ---------------------------------------------------- +% > Get means and standard error of means +% With robust option, if specified, and removing +% covariates, if there are any. +% ---------------------------------------------------- + + +% if DO_rob +% % ROBUST FIT +stderr = []; + +%[b,t,p,sig,f,fp,fsig,stat] = robust_reg_matrix(X,dat,1); + +%for i = 1:k +% [x,y,r,p,mycor(1,i),prob,serob] = partialcor(X,y,i) + +% key vars are : +% mymeans, stderr, mycor + +wh_reg = 1; % regressor of interest + +for i = 1:ny + fprintf(1,'\nColumn %3.0f:\n',i); + + % remove nans from this column + tmpy = dat(:,i); + tmpx = X; + % % wh = find(any(isnan(X),2) | any(isnan(tmpy),2)); + % % tmpx(wh,:) = []; tmpy(wh) = []; + [wasnan, tmpx, tmpy] = nanremove(tmpx, tmpy); + + n(i) = size(tmpy,1); + + % get mean and standard error of intercept (robust or OLS) + % y is adjusted for all non-intercept covs + % stats has weights, stats.w, which are all 1 for OLS + [x,newy,r,p{i},stderr(i),mymeans(i),stats] = partialcor(tmpx,tmpy,wh_intercept,1,DO_rob); + + y(:,i) = naninsert(wasnan, newy); + + %%%not needed y(:,i) = y(:,i) + mymeans(i); % add mean + myweights(:,i) = naninsert(wasnan, stats.w); + + if ~isempty(covs) + % cov of interest here is fixed at 1 (see above) + + % if we have covs, leave in cov. of interest (cov1) + % y is adjusted for all non-intercept covs + [x,y(:,i),mycor(i),mycorrp(i)] = partialcor(tmpx,tmpy,wh_reg,1,DO_rob); + + %not needed %%% y(:,i) = y(:,i) + mymeans(i); % add mean + end + + fprintf(1,'\n'); +end +dat = y; % adjusted data, for plot + +if DO_within + within_ste = barplot_get_within_ste(dat); + stderr = repmat(within_ste, 1, size(dat, 2)); +end + + +% ---------------------------------------------------- +% > Make figure +% ---------------------------------------------------- + +if DO_fig + f = figure('Color','w'); hout = gca; set(gca,'FontSize',18); %hold on; grid on; +else + f = get(gcf); hout = gca; set(gca,'FontSize',18); hold on; +end + + +% ---------------------------------------------------- +% > BARPLOT (or line plot) +% ---------------------------------------------------- + +if DO_lineplot + h = plot(xvals, mymeans, 'o-', 'Color', mycolor, 'MarkerFaceColor', mycolor, 'MarkerSize', 8); + h2 = errorbar(xvals, mymeans, stderr, stderr); + set(h2, 'LineWidth', 2, 'Color', mycolor); +else + h = bar(xvals, mymeans, barwidth); + if iscell(mycolor) + % each bar a different color + hold on + for i = 1:length(xvals) + bar(xvals(i), mymeans(i), barwidth, 'FaceColor', mycolor{i}, 'LineWidth', 2); + end + hold off + else %all bars the same color + set(h,'FaceColor', mycolor); %,'LineWidth',2) + end + + tor_bar_steplot(mymeans,stderr,{'k'}, xvals); +end + +if exist('ylimits','var') + ylim(ylimits); +end + +% sort individual scores by covariate, if covs +if ~isempty(covs) + [sortedcov,indx] = sort(covs(:,wh_reg)); + dat = dat(indx,:); + sortedw = myweights(indx,:); + + x = (0:(nn-1)) ./ (nn+5) - (.5 - 1/(nn-5)); +else + x = zeros(1,nn) - .1; + sortedw = myweights; +end + +%s = std(dat1); + +if DO_ind + % ---------------------------------------------------- + % > Plot individuals + % ---------------------------------------------------- + + x = [x; x]; + + hold on + for i = 1:size(dat,2) + % marker + if mod(i,2)==0, mym='^'; myc=[.2 .2 .2]; else mym='o'; myc=[0 0 0]; end + + for j = 1:size(dat,1) + % color by weight + myc = [1 1 1] - ([1 1 1] .* sortedw(j,i)); + + if i == 1 + xdat(j,:) = x(1,j) + (1:size(dat,2)); % save x values for output (for line plotting) + + % plot lines (if requested) + if DO_indlines + plot(xdat(j,:),dat(j,:),'k','LineWidth',.5,'Color',[.7 .7 .7]); + end + end + + % plot marker + if DO_number && ~(any(isnan(x(:, j))) | isnan(dat(j, i))) + plot(x(:,j) + i,[dat(j,i) dat(j,i)]', 'w.'); % to set axis scale appropriately + text(x(:,j) + i,[dat(j,i) dat(j,i)]',num2str(j),'Color',[0 0 0],'FontSize', 14) + elseif ~(any(isnan(x(:, j))) | isnan(dat(j, i))) + plot(x(:,j) + i,[dat(j,i) dat(j,i)]',mym,'Color',[0 0 0],'LineWidth',1,'MarkerFaceColor',myc) + end + end + + z = (dat(:,i) - mean(dat(:,i))) ./ std(dat(:,i)); wh = find(abs(z) >= 1.96); + + % print z-scores of potential outliers to text + %if ~isempty(wh), fprintf(1,'\nCond %3.0f\t',i),for j = 1:length(wh), fprintf(1,'%3.2f\t',z(wh(j))); end,end + + if DO_plotout + if ~isempty(wh), plot(x(:,wh) + i,[dat(wh,i) dat(wh,i)]','ro','MarkerSize',14,'LineWidth',2), end + end + end +end % plot individuals + + +% now that axes are stabilized, add diagonal x labels +%set(gca,'XLim',[0 ny+1],'XTick',1:ny,'XTickLabel',1:ny) +if ~exist('xlabels','var') + set(gca,'XLim',[min(xvals)-.5 max(xvals) + .5],'XTick',xvals,'XTickLabel',xvals) + if ~isempty(xl), xlabel(xl,'Interpreter','none'); end +else + set(gca,'XLim',[min(xvals)-.5 max(xvals) + .5]) + xaxis_labeling(xl,xlabels,xlabelslant) +end + +if ~isempty(yl), ylabel(yl,'Interpreter','none'); end + + +title(plottitle,'FontSize',24,'Interpreter','none') +%set(gcf,'Position',[464 283 930 827]), drawnow + +if exist('toplabels','var') || (DO_plabels && size(dat,1)>1 && exist('p','var')) + xd = get(get(h,'Children'),'XData'); + yl = get(get(h,'Parent'),'YLim'); + for i=1:size(xd,2) + tx = (xd(2,i)+xd(3,i))/2; + ty = yl(2); + if exist('toplabels','var') + if iscell(toplabels) + plabel = toplabels{i}; + else + plabel = sprintf('%5.3f',toplabels(i)); + end + else + plabel = sprintf('%5.3f',p{i}); + end + text(tx,ty,plabel,'HorizontalAlignment','center','VerticalAlignment','bottom','FontSize',12); + end +end + +end + + +%% slightly edited code from mathworks.com (search "matlab angled tick labels") +% added by Luka 4/2013 +function xaxis_labeling(xaxislabel,ticklabels,slant) + +% set tick locations +Xt = 1:numel(ticklabels); +Xl = [0 numel(ticklabels)+1]; +set(gca,'XTick',Xt,'Xlim',Xl); + +if slant == 0 + set(gca,'XTickLabel',''); + set(gca,'XTickLabel',ticklabels); +else + % % reduce size of axis to fit labels + % pos = get(gca,'Position'); + % set(gca,'Position',[pos(1), .2, pos(3) .65]) + ax = axis; % Current axis limits + axis(axis); % Set the axis limit modes to manual + Yl = ax(3:4); % Y-axis limits + + % Place the text labels + t = text(Xt,Yl(1)*ones(1,numel(Xt)),ticklabels,'Interpreter','none'); + set(t,'HorizontalAlignment','right','VerticalAlignment','top', ... + 'Rotation',slant,'FontSize',15); + + % Remove the default labels + set(gca,'XTickLabel',''); + + % get the Extent of each text object + for i = 1:length(ticklabels) + ext(i,:) = get(t(i),'Extent'); %#ok + set(t(i),'Units','pixels'); + extp(i,:) = get(t(i),'Extent'); %#ok + set(t(i),'Units','data'); + end + + % Determine lower point for alignment of text + LowYPoint = min(ext(:,2)); + + % reduce size of axis to fit labels + pos = get(gca,'Position'); + set(gca,'Position',[pos(1), .2, pos(3) .65]) + + % Place the axis label + if ~isempty(xaxislabel), + XMidPoint = Xl(1) + abs(diff(Xl))/2; + t2 = text(XMidPoint,LowYPoint,xaxislabel,'VerticalAlignment','top','HorizontalAlignment','center','Interpreter','none'); + set(t2,'FontSize',18); + t2ex = get(t2,'Extent'); + else + t2ex = [0 0]; + end + + % adjust size of window + pos = get(gcf,'Position'); + apos = get(gca,'Position'); + ss = get(0,'ScreenSize'); + newpos4 = pos(4)-t2ex(2)+200; + set(gcf,'Position',[pos(1) ss(4)-newpos4-100 pos(3) newpos4]); + set(gca,'Position',[apos(1) apos(2) apos(3) 1-apos(2)-.1]); +end + +end \ No newline at end of file diff --git a/Visualization_functions/barplot_columns3.m b/Visualization_functions/barplot_columns3.m new file mode 100644 index 00000000..c2c92c12 --- /dev/null +++ b/Visualization_functions/barplot_columns3.m @@ -0,0 +1,512 @@ +function [hout,dat,xdat, h] = barplot_columns2(dat,plottitle,varargin) +% [axishandle,adjusted data,x-data,barhandle] = barplot_columns(dat,title,[options]) +% +% Makes a barplot of columns of data, with standard error bars. +% Optional arguments include removing continuous covariates before plotting, +% robust (IRLS) estimation of means and correlations with covariates, and +% within-subject error bars based on the subject x condition interaction +% (overall), which is not quite the standard error contrasts of interest, +% but is the standard error for a 1-way repeated measures ANOVA. +% +% plots circles around points at z >= 1.96 +% plots individual points, unless you enter 4th argument +% +% if dat is a cell array, each entry becomes one "bar". Useful if n +% observations is different for each column. +% +% Examples: Just plot means and SE +% h = barplot_columns(tmp,'Cluster 1'); +% +% Options +% 'cov' : followed by matrix of covariates +% 'labels' : followed by cellstring of bar labels +% 'nofig' : do not make figure +% 'ind' : plot individual scores on top of bars +% 'plotout': circle potential outliers at z>1.96 in red +% 'robust' : do robust IRLS means and correlations +% 'indlines' : plot lines showing individual effects +% 'within' : within-subjects standard errors, followed by contrast +% matrix +% 'line' : Make line plot instead of bar plot +% 'number' : plot case numbers instead of points +% 'x' : followed by x-axis values for bars +% 'color' : followed by color for bars (text: 'r' or [r g b]) OR +% cell array with names of colors cell for each line/bar +% 'cons' : followed by contrastXweights matrix +% +% To convert from long form to wide form +% 'subcol' column numbers with subject numbers +% 'propcols' vector of column numbers with properties +% 'propnames' cell array of names of properties +% +% +% Examples: +% barplot_columns(ctmp,'RT effects by Switch Type',overall_sw,'nofig','robust') +% +% Standard Errors ARE NOT Adjusted for covariate, right now. +% +% Example: within-subjects std. errors +% barplot_columns(dat, 'Means', 'nofig', 'within', 'ind'); +% +% The example below uses color, width, and xposition arguments to make a grouped +% barplot showing effects for two groups: +% exp_dat = EXPT.error_rates(EXPT.group==1,:); +% control_dat = EXPT.error_rates(EXPT.group==-1,:); +% barplot_columns(exp_dat, 'Error rates', 'nofig', 'color', 'r', 'width', .4); +% barplot_columns(control_dat, 'Error rates', 'nofig', 'color', 'b', 'width', .4, 'x', (1:9)+.5); +% set(gca, 'XLim', [0 10], 'XTick', 1:9) + +% PROGRAMMERS' NOTES +% 4/2013 (Luka): changed input format (title and cov now varargin options, not sequential arguments) +% 4/2013 (Luka): changed input parsing +% 4/2013 (Luka): added labels option (adds diagonal labels) +% 4/2013 (Luka): moved labeling to after doind (axis is stable then) +% 4/2013 (Luka): added plabels and toplabels options +% 5/2013 (Luka): made no individuals default +% need to sort out plabel option (including p values from glms in plot) +% right now does not account for inclusion of cov +% figure out how to add stars, legend? + +% ---------------------------------------------------- +% > Set up input arguments +% ---------------------------------------------------- +DO_fig = 1; +DO_ind = 0; +DO_plotout = 0; +DO_rob = 0; +DO_indlines = 0; +DO_within = 0; +DO_number = 0; +DO_lineplot = 0; +DO_plabels = 0; +DO_denan = 0; + +mycolor = [.8 .8 .8]; +barwidth = .8; +xdat = []; +covs = []; + +%handle input of different lens -- passed in as cell array +if iscell(dat) + maxlen=0; + for i=1:length(dat) + if length(dat{i}) > maxlen, maxlen = length(dat{i}); end + end + + dat2 = repmat(NaN, maxlen, length(dat)); + for i=1:length(dat) + dat2(1:length(dat{i}),i) = dat{i}; + end + dat = dat2; +end + +i=1; +while i <= numel(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'subcol' + subcol = varargin{i+1}; + i=i+1; + case 'propcols' + propcols = varargin{i+1}; + i=i+1; + case 'propnames' + propnames = varargin{i+1}; + i=i+1; + case 'cons' + cons = varargin{i+1}; + i=i+1; + case 'connames' + connames = varargin{i+1}; + i=i+1; + case 'cov' + covs = scale(varargin{i+1},1); + i=i+1; + case 'labels' + xlabels = varargin{i+1}; + i=i+1; + case 'ylabel' + yl = varargin{i+1}; + i=i+1; + case 'ylim', ylimits = varargin{i+1}; i=i+1; + case 'nofig', DO_fig = 0; + case 'ind', DO_ind = 1; + case 'plotout', DO_plotout = 1; + case 'robust', DO_rob = 1; + case 'denan', DO_denan = 1; + case 'indlines', DO_indlines = 1; + case 'number', DO_number = 1; + case 'within', DO_within = 1; + case 'line', DO_lineplot = 1; + case 'plabels', DO_plabels = 1; + case 'toplabels' + toplabels = varargin{i+1}; + i=i+1; + case 'x' + xvals = varargin{i+1}; + i=i+1; + case 'color' + mycolor = varargin{i+1}; + i=i+1; + case 'width' + barwidth = varargin{i+1}; + i=i+1; + otherwise + error(['UNRECOGNIZED OPTION: ' varargin{i}]) + end + else + disp(varargin{i}) + error('Above option UNRECOGNIZED') + end + i=i+1; +end + +if exist('xlabels','var') && numel(xlabels) ~= size(dat,2) + error('Number of labels given (%d) does not match the number of bars (%d)\n',numel(xlabels),size(dat,2)) +end +if ~exist('plottitle','var') + error('Second argument (title) is mandatory.') +end + + + +if exist('subcol','var') && exist('propcols','var') + datacols = setdiff(1:size(dat,2),union(subcol,propcols)); + [propcombos ignore prowcode] = unique(dat(:,propcols),'rows'); + [subjects ignore srowcode]= unique(dat(:,subcol)); + dat2 = []; + for s = 1:numel(subjects) + newrow = []; + for c = 1:size(propcombos,1) + newrow = [newrow nanmean(dat(srowcode==s & prowcode==c,datacols))]; + end + dat2(s,:) = newrow; + end + + if exist('xlabels','var') + if numel(xlabels) ~= size(propcombos,1) + error('number of labels must match number of property combos') + end + else + for c = 1:size(propcombos,1) + xlabels{c} = ''; + for p = 1:size(propcombos,2) + xlabels{c} = [xlabels{c} '_' propnames{p} '(' num2str(propcombos(c,p)) ')']; + end + end + xlabels = regexprep(xlabels,'^_',''); + end +end +clear s c p i j +dat=dat2; + +if exist('cons','var') + dat2 = []; + xlabels2 = {}; + for i=1:size(cons,1) + dat2(:,i) = sum(dat .* repmat(cons(i,:),size(dat,1),1),2); + if exist('connames','var') + xlabels2{i} = connames{i}; + else + xlabels2{i} = sprintf('con%04d',num2str()); + end + end + + dat = dat2; + xlabels = xlabels2; +end + +if ~exist('xvals','var'), xvals = 1:size(dat, 2); end + +if DO_denan + % delete nans casewise + wh = find(any(isnan(dat),2)); + if ~isempty(wh) + fprintf('WARNING: The following rows are being excluded because they include NaNs:\n') + fprintf('%d\n',wh) + dat(wh,:) = []; + if ~isempty(covs), covs(wh,:) = []; end + end +end + +% replace nans with mean +for i = 1:size(dat,2), + if any(isnan(dat(:,i))) + warning('Some NaNs!') + %dat(find(isnan(dat(:,i))),i) = nanmean(dat(:,i)); + end +end + +% find NaN columns +wh = find(all(isnan(dat),1)); +dat(:,wh) = 0; + +% get final design matrix, intercept is last column +[nn,ny] = size(dat); +k = size(covs,2); + +% add intercept if not already in model +if ~isempty(covs) + wh_oldintercept = find(all(diff(covs) < eps)); + covs(:,wh_oldintercept) = []; +end + +X = [covs ones(nn,1)]; +wh_intercept = k+1; + +% ---------------------------------------------------- +% > Get means and standard error of means +% With robust option, if specified, and removing +% covariates, if there are any. +% ---------------------------------------------------- + + +% if DO_rob +% % ROBUST FIT +stderr = []; + +%[b,t,p,sig,f,fp,fsig,stat] = robust_reg_matrix(X,dat,1); + +%for i = 1:k +% [x,y,r,p,mycor(1,i),prob,serob] = partialcor(X,y,i) + +% key vars are : +% mymeans, stderr, mycor + +wh_reg = 1; % regressor of interest + +for i = 1:ny + fprintf(1,'\nColumn %3.0f:\n',i); + + % remove nans from this column + tmpy = dat(:,i); + tmpx = X; + % % wh = find(any(isnan(X),2) | any(isnan(tmpy),2)); + % % tmpx(wh,:) = []; tmpy(wh) = []; + [wasnan, tmpx, tmpy] = nanremove(tmpx, tmpy); + + n(i) = size(tmpy,1); + + % get mean and standard error of intercept (robust or OLS) + % y is adjusted for all non-intercept covs + % stats has weights, stats.w, which are all 1 for OLS + [x,newy,r,p{i},stderr(i),mymeans(i),stats] = partialcor(tmpx,tmpy,wh_intercept,1,DO_rob); + + y(:,i) = naninsert(wasnan, newy); + + %%%not needed y(:,i) = y(:,i) + mymeans(i); % add mean + myweights(:,i) = naninsert(wasnan, stats.w); + + if ~isempty(covs) + % cov of interest here is fixed at 1 (see above) + + % if we have covs, leave in cov. of interest (cov1) + % y is adjusted for all non-intercept covs + [x,y(:,i),mycor(i),mycorrp(i)] = partialcor(tmpx,tmpy,wh_reg,1,DO_rob); + + %not needed %%% y(:,i) = y(:,i) + mymeans(i); % add mean + end + + fprintf(1,'\n'); +end +dat = y; % adjusted data, for plot + +if DO_within + within_ste = barplot_get_within_ste(dat); + stderr = repmat(within_ste, 1, size(dat, 2)); +end + + +% ---------------------------------------------------- +% > Make figure +% ---------------------------------------------------- + +if DO_fig + f = figure('Color','w'); hout = gca; set(gca,'FontSize',18); %hold on; grid on; +else + f = get(gcf); hout = gca; set(gca,'FontSize',18); hold on; +end + + +% ---------------------------------------------------- +% > BARPLOT (or line plot) +% ---------------------------------------------------- + +if DO_lineplot + h = plot(xvals, mymeans, 'o-', 'Color', mycolor, 'MarkerFaceColor', mycolor, 'MarkerSize', 8); + h2 = errorbar(xvals, mymeans, stderr, stderr); + set(h2, 'LineWidth', 2, 'Color', mycolor); +else + h = bar(xvals, mymeans, barwidth); + if iscell(mycolor) + % each bar a different color + hold on + for i = 1:length(xvals) + bar(xvals(i), mymeans(i), barwidth, 'FaceColor', mycolor{i}, 'LineWidth', 2); + end + hold off + else %all bars the same color + set(h,'FaceColor', mycolor); %,'LineWidth',2) + end + + tor_bar_steplot(mymeans,stderr,{'k'}, xvals); +end + +if exist('ylimits','var') + ylim(ylimits); +end + +% sort individual scores by covariate, if covs +if ~isempty(covs) + [sortedcov,indx] = sort(covs(:,wh_reg)); + dat = dat(indx,:); + sortedw = myweights(indx,:); + + x = (0:(nn-1)) ./ (nn+5) - (.5 - 1/(nn-5)); +else + x = zeros(1,nn) - .1; + sortedw = myweights; +end + +%s = std(dat1); + +if DO_ind + % ---------------------------------------------------- + % > Plot individuals + % ---------------------------------------------------- + + x = [x; x]; + + hold on + for i = 1:size(dat,2) + % marker + if mod(i,2)==0, mym='^'; myc=[.2 .2 .2]; else mym='o'; myc=[0 0 0]; end + + for j = 1:size(dat,1) + % color by weight + myc = [1 1 1] - ([1 1 1] .* sortedw(j,i)); + + if i == 1 + xdat(j,:) = x(1,j) + (1:size(dat,2)); % save x values for output (for line plotting) + + % plot lines (if requested) + if DO_indlines + plot(xdat(j,:),dat(j,:),'k','LineWidth',.5,'Color',[.7 .7 .7]); + end + end + + % plot marker + if DO_number && ~(any(isnan(x(:, j))) | isnan(dat(j, i))) + plot(x(:,j) + i,[dat(j,i) dat(j,i)]', 'w.'); % to set axis scale appropriately + text(x(:,j) + i,[dat(j,i) dat(j,i)]',num2str(j),'Color',[0 0 0],'FontSize', 14) + elseif ~(any(isnan(x(:, j))) | isnan(dat(j, i))) + plot(x(:,j) + i,[dat(j,i) dat(j,i)]',mym,'Color',[0 0 0],'LineWidth',1,'MarkerFaceColor',myc) + end + end + + z = (dat(:,i) - mean(dat(:,i))) ./ std(dat(:,i)); wh = find(abs(z) >= 1.96); + + % print z-scores of potential outliers to text + %if ~isempty(wh), fprintf(1,'\nCond %3.0f\t',i),for j = 1:length(wh), fprintf(1,'%3.2f\t',z(wh(j))); end,end + + if DO_plotout + if ~isempty(wh), plot(x(:,wh) + i,[dat(wh,i) dat(wh,i)]','ro','MarkerSize',14,'LineWidth',2), end + end + end +end % plot individuals + + +% now that axes are stabilized, add diagonal x labels +%set(gca,'XLim',[0 ny+1],'XTick',1:ny,'XTickLabel',1:ny) +if ~exist('xlabels','var') + set(gca,'XLim',[min(xvals)-.5 max(xvals) + .5],'XTick',xvals,'XTickLabel',xvals) + xlabel('Task Condition') +else + set(gca,'XLim',[min(xvals)-.5 max(xvals) + .5]) + xaxis_labeling('Task Condition',xlabels); +end + +if exist('yl','var') + ylabel(yl); +end + +title(plottitle,'FontSize',24) +%set(gcf,'Position',[464 283 930 827]), drawnow + +if exist('toplabels','var') || (DO_plabels && size(dat,1)>1 && exist('p','var')) + xd = get(get(h,'Children'),'XData'); + yl = get(get(h,'Parent'),'YLim'); + for i=1:size(xd,2) + tx = (xd(2,i)+xd(3,i))/2; + ty = yl(2); + if exist('toplabels','var') + if iscell(toplabels) + plabel = toplabels{i}; + else + plabel = sprintf('%5.3f',toplabels(i)); + end + else + plabel = sprintf('%5.3f',p{i}); + end + text(tx,ty,plabel,'HorizontalAlignment','center','VerticalAlignment','bottom','FontSize',12); + end +end + +end + + +%% slightly edited code from mathworks.com (search "matlab angled tick labels") +% added by Luka 4/2013 +function xaxis_labeling(xaxislabel,ticklabels) + +% % reduce size of axis to fit labels +% pos = get(gca,'Position'); +% set(gca,'Position',[pos(1), .2, pos(3) .65]) + +% set tick locations +Xt = 1:numel(ticklabels); +Xl = [0 numel(ticklabels)+1]; +set(gca,'XTick',Xt,'Xlim',Xl); + +ax = axis; % Current axis limits +axis(axis); % Set the axis limit modes to manual +Yl = ax(3:4); % Y-axis limits + +% Place the text labels +t = text(Xt,Yl(1)*ones(1,numel(Xt)),ticklabels,'Interpreter','none'); +set(t,'HorizontalAlignment','right','VerticalAlignment','top', ... + 'Rotation',45,'FontSize',15); + +% Remove the default labels +set(gca,'XTickLabel',''); + +% get the Extent of each text object +for i = 1:length(ticklabels) + ext(i,:) = get(t(i),'Extent'); %#ok + set(t(i),'Units','pixels'); + extp(i,:) = get(t(i),'Extent'); %#ok + set(t(i),'Units','data'); +end + +% Determine lower point for alignment of text +LowYPoint = min(ext(:,2)); + +% reduce size of axis to fit labels +pos = get(gca,'Position'); +set(gca,'Position',[pos(1), .2, pos(3) .65]) + +% Place the axis label +XMidPoint = Xl(1) + abs(diff(Xl))/2; +t2 = text(XMidPoint,LowYPoint,xaxislabel,'VerticalAlignment','top','HorizontalAlignment','center'); +set(t2,'FontSize',18); +t2ex = get(t2,'Extent'); + +% adjust size of window +pos = get(gcf,'Position'); +apos = get(gca,'Position'); +ss = get(0,'ScreenSize'); +newpos4 = pos(4)-t2ex(2)+200; +set(gcf,'Position',[pos(1) ss(4)-newpos4-100 pos(3) newpos4]); +set(gca,'Position',[apos(1) apos(2) apos(3) 1-apos(2)-.1]); + +end \ No newline at end of file diff --git a/Visualization_functions/barplot_grouped.m b/Visualization_functions/barplot_grouped.m new file mode 100644 index 00000000..a2200bf2 --- /dev/null +++ b/Visualization_functions/barplot_grouped.m @@ -0,0 +1,171 @@ +function han = barplot_grouped(dat,X,xnames,seriesnames,varargin) + % han = barplot_grouped(dat,X,xnames,seriesnames, [optional args]); + % + % dat = n x 4 data matrix (2 x 2 grouping only for now, but easy to expand + % later) + % X = covariates, no intercept; will be centered by this function + % xnames = x-axis labels + % seriesnames = series labels + % + % First two bars are group, and last two bars are group + % + % Optional inputs (keywords): + % 'within' within-error flag. 1 = errors based on subject x + % condition interaction + % 'stars' put stars for significance on graph (default) + % 'nostars' do not plot stars + % 'bars' ...followed by number of bars in group + % 'pvals' ...followed by matrix of p-values (for stars; will calculate if missing) + % 'inputmeans' input means and errors in first 2 inputs rather than data + % and X + % 'colors' ...followed by colors, e.g., mycol = {[1 0 0] [0 1 0] [1 0 1] [1 1 0] [0 0 1]} + + if isempty(dat), disp('Nothing to plot.'), return, end + + dowithin = 0; + dostars = 1; + nbars = 2; % bars per group + pvals = []; + inputmeans = 0; + + for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % reserved keywords + case 'within', dowithin = 1; + case 'stars', dostars = 1; + case 'nostars', dostars = 0; + case 'inputmeans', inputmeans = 1; m = dat; se = X; + + % functional commands + case 'bars', nbars = varargin{i+1}; + case 'pvals', pvals = varargin{i+1}; + + case 'colors',mycolors = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + if inputmeans + [ngroups,nbars] = size(m); + + n = 204; %input('Enter sample size: '); + + if isempty(pvals) && dostars + disp('Must enter p-values for stars.'), dostars = 0; + else + p = pvals'; p = p(:)'; pvals = p; + end + + % rearrange into vector format for compatibility + b = m'; b = b(:)'; + s = se'; s = s(:)'; se = s; + + else + % we have data + ncols = size(dat,2); + if mod(ncols,nbars) ~= 0 + error('number of data columns must be divisible by number of bars per group. try ''bars'' input option.'); + end + + ngroups = ncols ./ nbars; + n = size(dat, 1); + + X = scale(X,1); X = [ones(n,1) X]; % design matrix + b = pinv(X) * dat; % b(1,:) are means, removing centered covariates + resid = dat - X*b; sigma = std(resid); + + Xerr = sqrt(diag(inv(X'*X))); + sterr = Xerr * sigma; % rows are predictors, cols are data columns + + m = b(1,:); se = sterr(1,:); % for intercept; other rows are for covariates + + if isempty(pvals) + pvals = 2 * ( 1 - tcdf( abs(b(1,:)./se),size(X,1)-size(X,2) ) ); + end + + if dowithin + se = barplot_get_within_ste(dat); + se = repmat(se,1, ngroups .* nbars); + end + + %tor_fig; + m = reshape(m,nbars,ngroups)'; % rows are groups; plot bars in same order as original means + end + + han = bar(m,'grouped'); + + set(han(1),'FaceColor',[.2 .2 .2]); + if length(han) > 1, set(han(2),'FaceColor',[.8 .8 .8]); end + if exist('mycolors','var') && iscell(mycolors) && ~isempty(mycolors(1)) + for i =1:length(han), set(han(i),'FaceColor',mycolors{i}); end + end + + if length(han) > 1 + set(gca,'XTick',1:ngroups,'XTickLabel',xnames); + legend(han, seriesnames); + else + set(gca,'XTick',1:nbars,'XTickLabel',xnames); + end + + xlocs = bar_centers(han); + xlocs = sort(xlocs(:)); + + tor_bar_steplot(b(1,:), se, {'k'}, xlocs) + + %tor_bar_steplot(b(1,:),se,{'k'},.35,.5,.2); + + if dostars + star_plot(b(1,:),pvals,se,nbars,ngroups); + end + + % set y-axis limits + mx = max(b(1,:)+se); mi = min(b(1,:)-se); + sc = .1 * (mx-mi); + yl = [mi-sc mx+sc]; + set(gca,'YLim',yl); + + + return + + +function star_plot(b,pvals,se,nbars,ngroups) + + xoffs = [-.15 .10]; % x offset, neg then pos + starwid = .06; + + for mybar = 1:nbars + y = b(1,mybar:nbars:end); + p = pvals(mybar:nbars:end); + s = se(mybar:nbars:end); + + yval = sign(y) .* ( abs(y) + abs(s) + abs(s) .* .1 ); + + % adjust neg y-values for height of char + yadj = zeros(size(y)); + yadj(yval < 0) = -.02; + yval = yval + yadj; + + xval = (1:ngroups) + xoffs(mybar); + + % adjust for width of star + xval = xval - .5*starwid; + + for i = 1:length(yval) + if p(i) < .0015, mystr = '***'; xadj = -starwid*1.5; + elseif p(i) < .015, mystr = '**'; xadj = -starwid; + elseif p(i) < .055, mystr = '*'; xadj = 0; + else mystr = ''; xadj = 0; + end + sh(i) = text(xval(i)+xadj,yval(i),mystr,'FontSize',24); + end + end + + return + + + + diff --git a/Visualization_functions/barplotter.m b/Visualization_functions/barplotter.m new file mode 100644 index 00000000..7b1ec090 --- /dev/null +++ b/Visualization_functions/barplotter.m @@ -0,0 +1,359 @@ +% Usage: +% +% h = barplotter(data) +% +% Creates a barplot out of the columns or elements (if data is a cell +% array containing vectors) of data. +% +% h = barplotter(..., 'groups', grouping) +% +% grouping must have the same number of columns or elements (if data is a +% cell array) as data, and must consist of integers beginning with 1 and +% ending with the total number of groups. Data belonging to the same group +% will be 'clustered' together in the plot. Ommitting groups (eg, [1 1 3 3]) +% will create extra spacing between groups. +% +% h = barplotter(..., 'std') +% +% overrides the default behavior and plots standard deviation bars instead +% of standard error bars +% +% h = barplotter(..., 'CI', alpha) +% +% this will override the default behavior (as well as 'std') and plot +% 1-alpha confidence intervals. +% +% h = barplotter(..., 'labels', labels) +% +% labels must be a cell array of strings with the same number of elements +% as data has columns or elements. The x-axis will be labeled with these. +% +% h = barplotter(..., 'label_groups') +% +% Applies labels to groups instead to individual bars +% +% h = barplotter(..., 'legend', names) +% +% Plots a legend in the figure, with labels corresponding to the elements +% of the cell vector of strings, names. +% +% h = barplotter(..., 'plegend', names, p) +% +% As 'legend', above, but p is a vector of the barplots to include in the +% legend (e.g. if you plotted 6 bars and p = [1 3], only the first and third +% bar would be included in the legend. +% +% h = barplotter(..., 'PlotLineHor', value) +% +% Plots a horizontal line at the Y value indicated. +% +% h = barplotter(..., 'LinePlot', colors, styles, markers) +% +% Changes the graph from a bar graph to a line graph. Setting groups will +% cause one line to be drawn for each group. +% +% h = barplotter(..., 'ErrorWidth', errorwidth) +% +% Sets the line weight (in points) for error bars +% +% h = barplotter(..., 'PropertyName', PropertyValue) +% +% Properties correspond to various Matlab figure properties, as +% appropriate. Currently supported properties (more to be added) are: +% +% 'Title' +% 'XLabel' +% 'YLabel' +% 'YLim' +% 'XLim' +% 'YTick' +% 'YTickLabel' +% 'YMinorTick' +% 'FontSize' +% 'xFontSize' %for xlabel +% 'yFontSize' %for ylabel +% 'tFontSize' %for title +% 'FaceColor' %note that you may specify a matrix of 3-element RGB vectors, +% rather than a single vector. Barplotter will then cycle +% the rows of the matrix until all bars have been drawn. +% 'Colormap' +% 'GridLineStyle' % - | - -| {:} | -. | none +% 'TickDir' % in or out +% 'MarkerSize' +% 'LineWidth' +% +% +% +% Note that this function has no native error handling. If you're +% encountering inexplicable errors it's likely because you haven't passed +% in the correct inputs. +% 02/10/07 Jared Van Snellenberg + +function h = barplotter(data, varargin) + +if ~iscell(data) + X = data; + clear data + for k = 1:size(X, 2) + data(k) = {X(:,k)}; + end + clear X +else + data = data(:); +end + +SD = 0; +CI = 0; +fc = 'flat'; +grouping = ones(size(data))'; +colormapfun = @hsv; +xlabels = []; +Title = []; +Xlabel = []; +Ylabel = []; +fontsize = 18; +xfontsize = 20; +yfontsize = 20; +tfontsize = 20; +lgroups = 0; +leg = 0; +grd='none'; +drawline=0; +lineplot=0; +yminortick='off'; +tickdir = 'out'; +markersize = 12; +linewidth = 2; +errorwidth = 1; + +for k = 1:length(varargin) + if ischar(varargin{k}) + switch(varargin{k}) + case 'groups' + grouping = varargin{k+1}; + case 'std' + SD = 1; + case 'CI' + CI = 1; + alpha = varargin{k+1}; + case 'labels' + xlabels = varargin{k+1}; + case 'label_groups' + lgroups = 1; + case 'YLim' + yl = varargin{k+1}; + case 'XLim' + xl=varargin{k+1}; + case 'YTick' + ytick = varargin{k+1}; + case 'YTickLabel' + yticklabels=varargin{k+1}; + case 'YMinorTick' + yminortick='on'; + case 'FaceColor' + fc = varargin{k+1}; + case 'Colormap' + colormapfun = str2func(varargin{k+1}); + case 'Title' + Title = varargin{k+1}; + case 'XLabel' + Xlabel = varargin{k+1}; + case 'YLabel' + Ylabel = varargin{k+1}; + case 'FontSize' + fontsize = varargin{k+1}; + case 'xFontSize' + xfontsize = varargin{k+1}; + case 'yFontSize' + yfontsize = varargin{k+1}; + case 'tFontSize' + tfontsize = varargin{k+1}; + case 'legend' + leg = 1; + names = varargin{k+1}; + p = 1:length(data); + case 'plegend' + leg = 1; + names = varargin{k+1}; + p = varargin{k+2}; + case 'GridLineStyle' + grd=varargin{k+1}; + case 'PlotLineHor' + drawline=1; + yLine=varargin{k+1}; + case 'LinePlot' + lineplot = 1; + colors = varargin{k+1}; + styles = varargin{k+2}; + markers = varargin{k+3}; + case 'TickDir' + tickdir = varargin{k+1}; + case 'MarkerSize' + markersize = varargin{k+1}; + case 'LineWidth' + linewidth = varargin{k+1}; + case 'ErrorWidth' + errorwidth = varargin{k+1}; + end + end +end + +if isnumeric(fc) + while size(fc, 1) ~= length(data) + fc = [fc;fc]; + if size(fc, 1)>length(data) + fc(length(data)+1:end,:) = []; + end + end + cm = fc; + fc = 'flat'; +else + cm = colormapfun(length(data)); +end +if strcmp(fc, 'none') + for k = 1:length(data) + C{k} = 'none'; + end +else + for k = 1:length(data) + C{k} = cm(k,:); + end +end + + +for k = 1:length(data) + means(k) = mean(data{k}); + if CI + err(:,k) = [means(k)+tinv(1-alpha/2, length(data{k}-1))*std(data{k})/sqrt(length(data{k}))... + means(k)-tinv(1-alpha/2, length(data{k}-1))*std(data{k})/sqrt(length(data{k}))]; + elseif SD + err(:,k) = [means(k)+std(data{k}); means(k)-std(data{k})]; + else + err(:,k) = [means(k)+std(data{k})/sqrt(length(data{k})); means(k)-std(data{k})/sqrt(length(data{k}))]; + end +end + +if ~exist('xl','var') + if ~lineplot + xl = [0 max(grouping)*0.5+length(data)+0.5]; + else + xl = [0 (length(data) / max(grouping) + 1)]; +end + +if ~exist('yl', 'var') + yl = max(max(err)); + if yl>0 + count = 0; + if yl>1 + while floor(yl) + yl = yl/10; + count = count+1; + end + yl = yl*10; + count = count-1; + else + while ~floor(yl) + yl = yl*10; + count = count-1; + end + end + if yl>0.9 + yl = ceil(yl)*10^count; + else + yl = ceil(yl*1.1)*10^count; + end + yl = [min([0 min(min(err))+min(min(err))/10]) yl]; + else + yl = min(min(err)); + count = 0; + if yl < -1 + while ceil(yl) + yl = yl/10; + count = count+1; + end + yl = yl*10; + count = count-1; + else + while ~ceil(yl) + yl = yl*10; + count = count-1; + end + end + if yl < -0.9 + yl = floor(yl)*10^count; + else + yl = floor(yl*1.1)*10^count; + end + yl = [yl 0]; + end +end + +figure; +h = axes('GridLineStyle',grd,'YGrid','on','YMinorTick',yminortick); +plotcount = 0; +Xtick = []; +set(h, 'XLim', xl, 'YLim', yl) +if drawline + hold on + line(xl,[yLine yLine],'Color','k','LineStyle','--','LineWidth',2); + hold off +end + +if lineplot + for k = 1:max(grouping) + plotcount = plotcount+1; + grp = find(grouping == k); + if size(grp,1)>1 + grp=grp'; + end + hold on + A(plotcount) = line(1:length(grp),means(grp),'Color',colors{k},'LineStyle',styles{k},'Marker',markers{k},'MarkerSize',markersize,'LineWidth',linewidth); + count=1; + for j = grp + line([count count-0.1 count-0.1; count count+0.1 count+0.1],[err(1, j) err(1, j) err(2, j); err(2, j) err(1, j) err(2, j)],'Color',colors{k},'LineStyle',styles{1},'LineWidth',errorwidth); + count = count + 1; + end + hold off + end + Xtick = 1:sum(grouping == 1); +else + for k = 1:max(grouping) + grp = find(grouping == k); + if size(grp,1)>1 + grp=grp'; + end + for j = grp + plotcount = plotcount+1; + hold on + A(plotcount) = area(h, [grouping(j)*0.5+plotcount-0.5-0.4 grouping(j)*0.5+plotcount-0.5+0.4], [means(j) means(j)], 'FaceColor', C{j}); + line(... + [grouping(j)*0.5+plotcount-0.5 grouping(j)*0.5+plotcount-0.5-0.2 grouping(j)*0.5+plotcount-0.5-0.2;... + grouping(j)*0.5+plotcount-0.5 grouping(j)*0.5+plotcount-0.5+0.2 grouping(j)*0.5+plotcount-0.5+0.2], ... + [err(1, j) err(1, j) err(2, j); err(2, j) err(1, j) err(2, j)], 'Color', 'k','LineWidth',errorwidth); + hold off + if lgroups + if j == grp(end) + Xtick(end+1) = grouping(j)*0.5+plotcount-length(grp)/2; + end + else + Xtick(end+1) = grouping(j)*0.5+plotcount-0.5; + end + end + end +end +if leg + legend(A(p), names{:}); +end +set(h, 'XTick', Xtick, 'XTickLabel', xlabels) +if exist('ytick', 'var') + set(h, 'YTick', ytick) +end +if exist('yticklabels','var') + set(h,'YTickLabel',yticklabels) +end +xlabel(h, Xlabel, 'FontSize', xfontsize) +ylabel(h, Ylabel, 'FontSize', yfontsize) +title(h, Title, 'FontSize', tfontsize) +set(h,'FontSize', fontsize) +set(h,'TickDir',tickdir) +end \ No newline at end of file diff --git a/Visualization_functions/canlab_force_directed_graph.m b/Visualization_functions/canlab_force_directed_graph.m new file mode 100644 index 00000000..4358a61e --- /dev/null +++ b/Visualization_functions/canlab_force_directed_graph.m @@ -0,0 +1,519 @@ +function [stats, handles] = canlab_force_directed_graph(activationdata, varargin) +% Creates a force-directed graph from a set of variables, and plots +% clusters on 3-D brain as well if entered. Requires matlab BGL toolbox. +% +% Usage: +% ------------------------------------------------------------------------- +% canlab_force_directed_graph(activationdata, ['cl', cl]) +% +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs: +% ------------------------------------------------------------------------- +% 'cl' followed by clusters or region structure with brain clusters +% +% Optional inputs: Enter keyword followed by variable with values +% 'threshtype' +% 'connectmetric' +% 'sizescale' +% 'setcolors' Cell array of colors for each group, [1 x g] +% 'rset' Cell of vectors, with indices (integers) of member +% elements in each group, [1 x g] cell +% 'names' +% 'namesfield' +% +% Outputs: +% ------------------------------------------------------------------------- +% stats structure with descriptive statistics, including +% betweenness-centrality, degree of each node +% +% Examples: +% ------------------------------------------------------------------------- +% +% [stats, handles] = canlab_force_directed_graph(activationdata, 'cl', cl, 'namesfield', 'shorttitle'); +% [stats, handles] = canlab_force_directed_graph(activationdata, 'cl', cl, 'namesfield', 'shorttitle', 'degree'); +% [stats, handles] = canlab_force_directed_graph(activationdata, 'cl', cl, 'namesfield', 'shorttitle', 'degree', 'rset', rset, 'setcolors', setcolors); +% +% +% See also: +% * list other functions related to this one, and alternatives* + +% Programmers' notes: +% List dates and changes here, and author of changes + +% BELOW IS A STANDARD TEMPLATE FOR DEFINING VARIABLE (OPTIONAL) INPUT +% ARGUMENTS. MANY FUNCTIONS NEED TO PARSE OPTIONAL ARGS, SO THIS MAY BE +% USEFUL. + + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% Defaults +% ----------------------------------- +shan = []; % outputs +spherehan = []; +handles = []; + +cl = []; +threshtype = 'bonf'; +connectmetric = 'corr'; % or partial_corr +sizescale = 'sigmoid'; + +setcolors = []; % control of color subgroups +rset = []; + +ptsizetype = 'bc'; +names = []; +namesfield = []; % enter field name, e.g., 'shorttitle' + +% Variable arg inputs +% ----------------------------------- + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'degree', ptsizetype = 'degree'; + %case 'design' + + % functional commands + case 'cl', cl = varargin{i + 1}; varargin{i + 1} = []; + + case {'threshtype' 'connectmetric' 'sizescale' 'setcolors' 'rset' 'names' 'namesfield'} + eval([varargin{i} ' = varargin{i + 1}; varargin{i + 1} = [];']) + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +% Check vars, etc. +% --------------------------------------------------------------------- +g = genpath('/Users/tor/Documents/matlab_code_external/matlab_bgl'); +addpath(g); + +if ~exist('fruchterman_reingold_force_directed_layout.m', 'file') + error('Must have Matlab BGL toolbox on path (external toolbox)'); +end + +activationdata = double(activationdata); + +if ~isempty(cl) && (size(activationdata, 2) ~= length(cl)) + disp('activationdata must have as many columns as clusters has elements.') + error('check activationdata and clusters to make sure they match.') +end + +if isempty(rset) + rset = {1:length(cl)}; +end + +if isempty(setcolors) + setcolors = scn_standard_colors(length(rset)); +end + +if ~isempty(namesfield) + names = {cl.(namesfield)}'; +end + +% ------------------------------------------------------------------------- +% CALCULATIONS +% ------------------------------------------------------------------------- + +[r, rp] = corr(double(activationdata)); +sz = size(r, 1); + +switch threshtype + case 'bonf' + thr = .05 ./ (sz * (sz - 1) / 2); % bonferroni... +end + +% Partial correlations +[b, p] = calc_partial_r(activationdata); + +% Threshold +sig = sparse(double(p < thr & b > 0)); +signeg = sparse(-double(p < thr & b < 0)); +sig = sig + signeg; + +% Threshold based on significant partial regression effects +% C is connectivity matrix for graph + +switch connectmetric + case 'partial_corr' + + C = b; + C(~sig) = 0; + + case 'corr' % default + C = r; + C(~sig) = 0; + + otherwise error('Unknown connectmetric'); +end + +% Enforce format for graph +C = (C + C') ./ 2; +C = sparse(C); + +% Graph Stats +% ----------------------------------------------- +bc = betweenness_centrality(abs(C)); % abs if ignoring neg connections + +% shortest paths: used as estimate of connectivity +[D S] = mean_path_length(r, rset); + +deg = full(sum(C ~= 0))'; + +stats.names = names; +stats.rset = rset; +stats.C = C; +stats.r = r; +stats.b = b; +stats.thr = thr; +stats.betweenness = bc; +stats.path_length_distance = D; +stats.degree = deg; +stats.mean_path_by_rset = S; + +switch ptsizetype + case 'bc' + ptsizemetric = bc; + case 'degree' + ptsizemetric = deg; + case 'none' + ptsizemetric = ones(size(bc)); + otherwise error('Unknown ptsizetype'); +end + +% ------------------------------------------------------------------------- +% Force-directed graph +% ------------------------------------------------------------------------- + +% Xc is coordinate matrix +Xc = fruchterman_reingold_force_directed_layout(C); + +if ~isempty(cl) +create_figure('graph', 1, 2); +else + create_figure('graph'); +end + +gplot(sig>0, Xc, 'k'); +axis image +lh = findobj(gca, 'Type', 'line'); +set(lh, 'Color', [.5 .5 .5]) + +gplot(sig<0, Xc, 'b'); +lh = findobj(gca, 'Type', 'line', 'color', 'b'); +set(lh, 'Color', [0 .5 1], 'LineStyle', ':') + +% Dots + +switch sizescale + case 'linear' +% ivals = 4 + 15 * intensity.(cnames{i}) ./max(intensity.(cnames{i})); % LC none of these variables exist in the workspace + ivals =4 + 15 * ptsizemetric ./ max(ptsizemetric); + + case 'sigmoid' + % Rationale: avoids some VERY large points in areas highly + % focused on as a priori ROIs, with extreme z-scores in + % intensity relative to other areas. + + ivals = sigmoidscale(ptsizemetric); +end + +ph = []; +for k = 1:length(rset) + + for j = 1:length(rset{k}) + + ph(end+1) = plot(Xc(rset{k}(j), 1), Xc(rset{k}(j), 2), 'o', 'Color', setcolors{k}, 'MarkerSize', ivals(rset{k}(j)), 'MarkerFaceColor', setcolors{k}); + + if ~isempty(names) + offset = .02 * range(get(gca, 'XLim')); + text(Xc(rset{k}(j), 1)+offset, Xc(rset{k}(j), 2), names{rset{k}(j)}, 'Color', 'k', 'FontSize', 14); + end + end + +end + +handles.lh = lh; +handles.ph = ph; + + +xlim = get(gca, 'XLim'); rg = range(xlim) * [-.05 .05]; xlim = xlim + rg; +ylim = get(gca, 'YLim'); rg = range(ylim) * [-.05 .05]; ylim = ylim + rg; +set(gca, 'XLim', xlim, 'YLim', ylim); +axis off +drawnow + +if isempty(cl), return, end + +stats.Xc = Xc; +stats.ivals = ivals; + +% ------------------------------------------------------------------------- +% Subcortical surface +% Only if cl is entered +% ------------------------------------------------------------------------- + +% NOTE: you need 3dHeadUtilityLite on your matlab path. +subplot(1, 2, 2) + +% regioncenters +xyz = cat(1, cl.mm_center); + +DB = struct('xyz', xyz, 'x', xyz(:, 1), 'y', xyz(:, 2), 'z', xyz(:, 3)); + +ivals = sigmoidscale(ptsizemetric, 2, 6); + +% Make Brain with Spheres +% ------------------------------------------------------------ + +[shan, spherehan] = connectivity3dbrain(xyz, rset, ivals, setcolors, names); + +% Add lines +% ------------------------------------------------------------ + +[~, linehandles] = cluster_nmdsfig_glassbrain(cl,ones(length(cl), 1), {'k'}, sig, [], 'samefig', 'nobrain', 'noblobs', 'straight'); +set(linehandles, 'LineWidth', 2) + +drawnow +%saveas(gcf, fullfile(savefigdir, ['graph3d_v2_' cnames{i} '.png'])); + +handles.surfhan = shan; +handles.spherehan = spherehan; +handles.linehandles = linehandles; + +end % function + + + + +function [b p] = calc_partial_r(activationdata) + +X = activationdata; +X = zscore(X); +%X(isnan(X)) = mean(X(~is; + +clear b p + +for i = 1:size(X, 2) + y = X(:, i); + xx = X; + xx(:, i) = 1; % intercept; need it, and also placeholder + + [bb, dev, stats] = glmfit(xx, y, 'normal', 'constant', 'off'); + + b(:, i) = bb; + p(:, i) = stats.p; + p(i, i) = 1; + b(i, i) = NaN; + +end + + +end % function + + + + + +function ivals = sigmoidscale(ivals, varargin) +% ivals = sigmoidscale(ivals, [lower bound], [upper bound]) +% +% rescales a vector based on sigmoid function of zcore(input values) + +% Rationale: avoids some VERY large points in areas highly +% focused on as a priori ROIs, with extreme z-scores in +% intensity relative to other areas. + +% scale size of nodes - intensity, or betweenness +ivals = zscore(ivals); + +A = 4; % lower asymptote +K = 15; % upper asymptote + +if nargin > 2 + A = varargin{1}; + K = varargin{2}; +end + +B = 2; % growth rate +v = .5; % high-growth asymptote +Q = .5; +M = .5; % time of max growth, if v = Q + +richards = @(x, A, K, B, v, Q, M) A + (K - A) ./ ((1+Q*exp(-B*(x-M))).^(1/v)); + +%figure; plot(sort(ivals), richards(sort(ivals), A,K,B,v,Q,M)); +ivals = richards(ivals, A,K,B,v,Q,M); + +end + + + + +function [C S] = mean_path_length(r, rset) +% compute matrix of 1/path lengths for n x n matrix of regions, C (connectivity) +% and 1 / mean path length for sets of regions specified by rset +% +% r is a correlation matrix +% rset is a cell array of length k, for k sets, with vectors describing the +% indices of members of each set. +% +% e.g., r = region_r{i}; + +r(isnan(r)) = 0; +r = (r' + r) ./ 2; % enforce symmetry, just in case + + +% shortest paths: used to calculate connectivity +rtmp = sparse(r); +rtmp(rtmp < 0) = 0; + +% Note: Uses Floyd-Warshall method if 10% non-zero elements or more +C = all_shortest_paths(rtmp); + + +% for each set, average elements of D corresponding to pairs of SETS +% return averages in S +% +% e.g., if D is min path length, S is the average min path length for Set 1 +% vs. 2, 1 vs. 3, 2 vs. 3, etc. + +% for inf values, we must impute some finite value or every average is Inf +% (i.e., if any regions are unconnected) +% impute max: +% but maybe better to work on unthresholded r matrix. +% Or, even better, return 1/S, 1 / mean path length, so unconnected +% regions/sets get 0. +% mx = max(D(~isinf(D))); +% D(isinf(D)) = mx; + +S = zeros(length(rset)); + +for m = 1:length(rset)-1 + for n = m:length(rset) + + % set m to set n relationships (e.g., shortest path lengths) + vals = 1 ./ C(rset{m}, rset{n}); + + % average, excluding "self-connections" with value Inf + S(m, n) = mean(vals(~isinf(vals))); + + end +end + +S = S + S'; + +end + + + +function [shan, spherehan] = connectivity3dbrain(regioncenters, rset, ivals, setcolors, names) + +% Colors +% ---------------------------------------------------------- + +ctxcolors = cell(1, size(regioncenters, 1)); + +for i = 1:length(rset) + + for j = 1:length(rset{i}) + + wh = rset{i}(j); + + ctxcolors(rset{i}) = setcolors(i); + + end +end + +% Brain +% ---------------------------------------------------------- + +%create_figure('subcortex'); + +shan = addbrain('limbic'); +delete(shan(end)); +shan = shan(1:end-1); + +shan = [shan addbrain('hires left')]; +shan = [shan addbrain('brainstem')]; + +set(shan(end-1), 'FaceColor', [.5 .5 .5], 'FaceAlpha', .15); +set(shan(1:end-2), 'FaceColor', [.5 .5 .5], 'FaceAlpha', .3); +set(shan(end), 'FaceColor', [.5 .5 .5], 'FaceAlpha', .3); + +view(89, 1); +lightRestoreSingle + +lighting gouraud + +% Spheres +% ---------------------------------------------------------- + +% add spheres +% cortex is special, because it involves different colors + +% xyz = regioncenters(rset{1}, :); +% sz = ivals(rset{1}); +% if ~isempty(names) +% mynames = names(rset{1}); +% end + +xyz = regioncenters; +sz = ivals; +if ~isempty(names) + mynames = names; +end + +for i = 1:size(xyz, 1) + spherehan(i) = cluster_image_sphere(xyz(i, :), 'color', ctxcolors{i}, 'radius', sz(i)); + + if ~isempty(names) + offset = .04 * range(get(gca, 'XLim')); + text(xyz(i, 1)+offset, xyz(i, 2)+offset, xyz(i, 3)+offset, mynames{i}, 'Color', 'k', 'FontSize', 14); + end +end +spherehan = {spherehan}; + +% for i = 2:length(rset) +% +% xyz = regioncenters(rset{i}, :); +% sz = ivals(rset{i}); +% if ~isempty(names) +% mynames = names(rset{i}); +% end +% +% spherehan{i} = cluster_image_sphere(xyz, 'color', setcolors{i}, 'radius', sz); +% +% if ~isempty(names) +% offset = .02 * range(get(gca, 'XLim')); +% for j = 1:size(xyz, 1) +% text(xyz(j, 1)+offset, xyz(j, 2)+offset, xyz(j, 3)+offset, mynames{j}, 'Color', 'k', 'FontSize', 14); +% end +% end +% +% end + + +end % function diff --git a/Visualization_functions/canlab_results_fmridisplay.m b/Visualization_functions/canlab_results_fmridisplay.m new file mode 100644 index 00000000..828490c3 --- /dev/null +++ b/Visualization_functions/canlab_results_fmridisplay.m @@ -0,0 +1,229 @@ +function o2 = canlab_results_fmridisplay(input_activation, varargin) + +% usage: function canlab_results_fmridisplay(input_activation, [optional inputs]) +% Tor Wager +% 1/27/2012 +% purpose: This function display fmri results. +% +% input: input_activation - nii, img, +% This image has the blobs you want to +% display. You can also enter a cl "clusters" structure or +% "region" object. +% +% you can also get a thresholded image like the examples used here +% from a number of places - by thresholding your results in SPM +% and using "write filtered" to save the image, by creating masks +% from meta-analysis or anatomical atlases, or by using +% mediation_brain_results, robust_results_threshold, +% robust_results_batch_script, threshold_imgs, or object +% oriented tools including fmri_data and statistic_image objects. +% +% optional inputs: +% ------------------------------------------------------------------------- +% 'noblobs' : do not display blobs +% 'nooutline' : do not display blob outlines +% 'addmontages' : when entering existing fmridisplay obj, add new montages +% 'noremove' : do not remove current blobs when adding new ones +% 'outlinecolor : followed by new outline color +% 'splitcolor' : followed by 4-cell new split colormap colors (help fmridisplay or edit code for defaults as example) +% 'montagetype' : 'full' for full montages of axial and sagg slices. +% 'compact' [default] for single-figure parasagittal and +% axials slices. +% * Other inputs to addblobs (fmridisplay method) are allowed, e.g., 'cmaprange', [-2 2], 'trans' +% See help fmridisplay +% e.g., 'color', [1 0 0] +% +% You can also input an existing fmridisplay object, and it will use the +% one you have created rather than setting up the canonical slices. +% +% example script: +% ------------------------------------------------------------------------- +% input_activation = 'Pick_Atlas_PAL_large.nii'; +% +% % set up the anatomical underlay and display blobs +% % (see the code of this function and help fmridisplay for more examples) +% +% o2 = canlab_results_fmridisplay(input_activation); +% +% %% ========== remove those blobs and change the color ========== +% +% cl = mask2clusters(input_activation); +% removeblobs(o2); +% o2 = addblobs(o2, cl, 'color', [0 0 1]); +% +% %% ========== OR +% +% r = region(input_activation); +% o2 = removeblobs(o2); +% o2 = addblobs(o2, r, 'color', [1 0 0]); +% +% %% ========== If you want to start over with a new fmridisplay object, +% % make sure to clear o2, because it uses lots of memory +% +% % This image should be on your path in the "canlab_canonical_brains" subfolder: +% +% input_activation = 'pain-emotion_2s_z_val_FDR_05.img'; +% clear o2 +% close all +% o2 = canlab_results_fmridisplay(input_activation); +% +% %% ========== save PNGs of your images to insert into powerpoint, etc. +% % for your paper/presentation +% +% scn_export_papersetup(400); +% saveas(gcf, 'results_images/pain_meta_fmridisplay_example_sagittal.png'); +% +% scn_export_papersetup(350); +% saveas(gcf, 'results_images/pain_meta_fmridisplay_example_sagittal.png'); +% +% Change colors, removing old blobs and replacing with new ones: +% o2 = canlab_results_fmridisplay(d, o2, 'cmaprange', [.3 .45], 'splitcolor', {[0 0 1] [.3 0 .8] [.9 0 .5] [1 1 0]}, 'outlinecolor', [.5 0 .5]); + + +if ~which('fmridisplay.m') + disp('fmridisplay is not on path. it is in canlab tools, which must be on your path!') + return +end + + +if ischar(input_activation) + cl = mask2clusters(input_activation); + +elseif isstruct(input_activation) || isa(input_activation, 'region') + cl = input_activation; + +elseif isa(input_activation, 'image_vector') + cl = region(input_activation); + +else + error('I don''t recognize the format of input_activation. It should be a thresholded mask, clusters, or region object'); +end + +% process input arguments +% -------------------------------------------- +doblobs = true; +dooutline = true; +doaddmontages = false; +doremove = true; +outlinecolor = [0 0 0]; +splitcolor = {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}; +montagetype = 'compact'; + +wh = strcmp(varargin, 'noblobs'); +if any(wh), doblobs = false; varargin(wh) = []; end + +wh = strcmp(varargin, 'nooutline'); +if any(wh), dooutline = false; varargin(wh) = []; end + +wh = strcmp(varargin, 'addmontages'); +if any(wh), doaddmontages = true; varargin(wh) = []; end + +wh = strcmp(varargin, 'outlinecolor'); +if any(wh), wh = find(wh); outlinecolor = varargin{wh(1) + 1}; end + +wh = strcmp(varargin, 'splitcolor'); +if any(wh), wh = find(wh); splitcolor = varargin{wh(1) + 1}; end + +wh = strcmp(varargin, 'noremove'); +if any(wh), doremove = false; varargin(wh) = []; end + +wh = strcmp(varargin, 'full'); +if any(wh), montagetype = varargin{find(wh)}; varargin(wh) = []; end + +wh = strcmp(varargin, 'compact'); +if any(wh), montagetype = varargin{find(wh)}; varargin(wh) = []; end + +wh = false(1, length(varargin)); +for i = 1:length(varargin) + wh(i) = isa(varargin{i}, 'fmridisplay'); + if wh(i), o2 = varargin{wh}; end +end +varargin(wh) = []; + +xyz = [-20 -10 -6 -2 0 2 6 10 20]'; +xyz(:, 2:3) = 0; + + +if ~exist('o2', 'var') + + % set up fmridisplay + % -------------------------------------------- + % you only need to do this once + % then you can add montages, add and remove blobs, add and remove points (for + % meta-analysis), etc. + + disp('Setting up fmridisplay objects'); + disp('This takes a lot of memory, and can hang if you have too little.'); + + o2 = fmridisplay; + + % You can customize these and run them from the command line + + switch montagetype + case 'full' + o2 = montage(o2, 'saggital', 'wh_slice', xyz, 'onerow'); + o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); + + case 'compact' + o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); + axh = axes('Position', [0.05 0.4 .1 .5]); + o2 = montage(o2, 'saggital', 'wh_slice', [0 0 0], 'existing_axes', axh); + + otherwise error('illegal montage type. choose full or compact.') + end + + wh_montages = [1 2]; + + +else + disp('Using existing fmridisplay object'); + + % Other inputs will be passed into addblobs + existingmons = length(o2.montage); + + if doaddmontages + % use same o2, but add montages + switch montagetype + case 'full' + o2 = montage(o2, 'saggital', 'wh_slice', xyz, 'onerow'); + o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); + + case 'compact' + o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); + axh = axes('Position', [0.05 0.4 .1 .5]); + o2 = montage(o2, 'saggital', 'wh_slice', [0 0 0], 'existing_axes', axh); + + otherwise error('illegal montage type. choose full or compact.') + end + + wh_montages = existingmons + [1 2]; + + else + if doremove + o2 = removeblobs(o2); + end + wh_montages = 1:existingmons; + + end + +end + +% Now we can add blobs +% -------------------------------------------- + +% they are added to all montages by default, but you can specify selected +% montages if you want to as well. + +% it's easy to remove them as well: +% o2 = removeblobs(o2); + +if doblobs + o2 = addblobs(o2, cl, 'splitcolor', splitcolor, 'wh_montages', wh_montages, varargin{:}); +end + +if dooutline + o2 = addblobs(o2, cl, 'color', outlinecolor, 'outline', 'wh_montages', wh_montages); +end + + +end % function diff --git a/Visualization_functions/close_non_spm_graphics_figures.m b/Visualization_functions/close_non_spm_graphics_figures.m new file mode 100644 index 00000000..20f5879a --- /dev/null +++ b/Visualization_functions/close_non_spm_graphics_figures.m @@ -0,0 +1,12 @@ +function close_non_spm_graphics_figures +%close_non_spm_graphics_figures +% +% closes all figure windows that are not the SPM orthviews window + + +fh = findobj('Type','Figure'); +spmf = findobj('Tag','Graphics'); +if ~isempty(spmf), fh(fh - spmf == 0) = []; end +close(fh) + +return \ No newline at end of file diff --git a/Visualization_functions/cluster_barplot.m b/Visualization_functions/cluster_barplot.m new file mode 100755 index 00000000..30f31e95 --- /dev/null +++ b/Visualization_functions/cluster_barplot.m @@ -0,0 +1,474 @@ +% function [clusters, subcl] = cluster_barplot(P, clusters, varargin) +% +% this function not only plots, but separates subclusters using pca / cluster_princomp.m +% based on pattern across all conditions, covariance (not correlation), +% +% Inputs: +% 1 - cell array of strings containing image file names (data extracted from these) +% 2 - clusters +% [opt] - 'subclusters' to get sub-clustering based on pca and clustering of voxels +% +% [opt] - cell array of strings with condition names +% +% [opt] - 'split' value is 1: to split into 2 plots (first half and last half of P) +% value is 2: to plot individual subjects over bars +% +% [opt] - 'center' center parameter values in plot (subtract row means) +% this gives closer to correct "within subjects" error bars +% and may be used when the overall parameter values have no meaning +% +% [opt] - 'covs', followed by between-subject covariates (e.g., behavioral regressors) +% plots remove these before plotting means and std. errors +% +% [opt] - 'max', to make plots based on max z-values within region for each dataset P +% not compatible with 'split' and 'center' (ignores these commands) +% right now, special for inhib - see also inhib2_cluster_barplot (good function) +% +% [opt] - 'indiv' to threshold based on individual t-statistics +% FOLLOW with cell array of t-images -- usually, there will be one cell, with images +% for each subject in rows, to define voxels for each ss. +% BUT Tnames can be the same length as +% contrast images, one t-img per subject per contrast, if +% desired. +% +% Outputs: +% clusters struture, with BARPLOT substructure added +% substructure contains data extracted and image file names +% +% This program uses XYZmm millimeter coordinates in clusters to find voxels +% So clusters and data files may have different dimensions. +% +% Examples: +% cluster_barplot(EXPT.SNPM.P(4:6), clusters(2:3)) +% cluster_barplot(EXPT.SNPM.P(7:12), clusters(2:3), {'ObjE' 'AttE' 'InteractE' 'ObjI' 'AttI' 'InteractI'}, 'split') +% [clusters, subclusters] = cluster_barplot(EXPT.SNPM.P(17:24), clusters, 'subclusters', 'split') +% RS2_8vs2_placeboCP = cluster_barplot(EXPT.SNPM.P([8 10 12 14 +% 16]), RS2meta, 'indiv', T); +% +% also see mask2clusters.m, a simpler version that just extracts clusters from a mask file. +% +% by Tor Wager, 5/15/03 +% modified 3/19/04 by Tor to add individual subject plots + +function [clusters, subcl] = cluster_barplot(P, clusters, varargin) + % ------------------------------------------------- + % * Set up optional inputs + % ------------------------------------------------- + splitat = 0; + dosubcl = 0; + docenter = 0; + covs = []; + domax = 0; + doindiv = 0; + + for j = 1:length(P), cnames{j} = num2str(j); end + + for i = 1:length(varargin), + if iscell(varargin{i}), cnames = varargin{i}; + elseif strcmp('split', varargin{i}), if varargin{i+1}==2, splitat=2; else splitat=round(length(P)./2); end + elseif strcmp('subclusters', varargin{i}), dosubcl = 1; disp('Finding sub-clusters') + elseif strcmp('center', varargin{i}), docenter = 1; + elseif strcmp('covs', varargin{i}), covs = varargin{i+1}; + elseif strcmp('max', varargin{i}), domax = 1; + elseif strcmp('indiv', varargin{i}), doindiv = 1; + Tnames = varargin{i+1}; varargin{i+1} = 'saved.'; + end + end + + if doindiv + % replicate names, in case only one set is entered + if ~iscell(Tnames) + Tnamestmp = Tnames; + Tnames={}; + Tnames{1}=Tnamestmp; + end + while length(Tnames) < length(cnames) + Tnames{end+1} = Tnames{1}; + end + end + + %if ~isempty(strmatch('split', varargin)), splitat=round(length(P)./2);, else splitat=length(P);, end + + % ------------------------------------------------- + % * load image files + % ------------------------------------------------- + fprintf(1, 'Loading images.') + CLU = clusters2CLU(clusters); + dat = cell(1, length(clusters)); + alldat = cell(1, length(clusters)); + + + V = spm_vol(P{1}(1, :)); + if any(any(V.mat - CLU.M)) + disp('CLUSTERS and IMAGE DIMENSIONS DIFFERENT! Resizing clusters.'), + %CLU = transform_coordinates(CLU, V.mat); + V.M = V.mat; + for i = 1:length(clusters), + clusters(i).XYZ = mm2voxel(clusters(i).XYZmm, V)'; clusters(i).M=V.mat; + clusters(i).voxSize = diag(V.M(1:3, 1:3))'; + clusters(i).XYZmm = voxel2mm(clusters(i).XYZ, clusters(i).M); + clusters(i).Z = ones(1, size(clusters(i).XYZ, 2)); + end + CLU = clusters2CLU(clusters); + for i = 1:length(clusters) + clusters(i).u=CLU.u; + clusters(i).VOX=CLU.VOX; + end + doslow = 1; + else + doslow = 0; + end + + % check if any contiguous clusters we lose + %tmpCL = tor_extract_rois([], CLU, CLU); + %if length(tmpCL) < length(clusters), doslow = 1;, end + %clear tmpCL + + for i = 1:length(P) + + if ~doslow + % fast way: if all voxel sizes match + cl{i} = tor_extract_rois(P{i}, clusters); %CLU, CLU); + end + + % concatenated data for each cluster + for j = 1:length(clusters) + + if doslow + % slow way: to keep clusters separate that otherwise touch + cl{i}(j) = tor_extract_rois(P{i}, clusters(j), CLU); + end + + % alldat{cluster} is data x voxels, data is subj within conditions strung + % together + alldat{j} = [alldat{j}; cl{i}(j).all_data]; + + if length(cl{i}(j).timeseries) < size(dat{j}, 1), + cl{i}(j).timeseries = [cl{i}(j).timeseries; NaN*zeros(size(dat{j}, 1)-length(cl{i}(j).timeseries), 1)]; + end + + dat{j} = [dat{j} cl{i}(j).timeseries]; + end + + %V{i} = spm_vol(P{i}); + %vols{i} = spm_read_vols(V{i}); + end + + %V{1}(1).M = V{1}(1).mat; + %imgdims = size(vols{1}); + %mask = zeros(imgdims(1:3)); + + + % ------------------------------------------------- + % * subclusters + % ------------------------------------------------- + fprintf(1, 'Plotting clusters.') + for i = 1:length(clusters) + + if dosubcl && clusters(i).numVox > 4 + % ------------------------------------------------- + % * subclusters + % ------------------------------------------------- + + [subcl{i}] = getsubcl(clusters(i), alldat{i}, length(P), size(P{1}, 1), cnames, splitat, i, docenter, covs); % does subclusters within it + if length(subcl{i}) > 1, + subcluster_montage(subcl{i}); % now plot all subcluster locations + saveas(gcf, ['cl' num2str(i) '_subcl_montage'], 'fig') + saveas(gcf, ['cl' num2str(i) '_subcl_montage'], 'tif') + end + else + + % no subclusters + + ste = nanstd(dat{i}) ./ sqrt(size(dat{i}, 1)); + + if isfield(clusters, 'shorttitle') + mytit = clusters(i).shorttitle; + else + mytit = clusters(i).title; + end + + str = sprintf('%s %2.0f (%3.0f, %3.0f, %3.0f), %3.0f voxels', ... + mytit, i, clusters(i).mm_center(1), ... + clusters(i).mm_center(2), clusters(i).mm_center(3), clusters(i).numVox); + + if domax + % set up + len = size(clusters(1).BARPLOT.data, 1); + tmpcl = []; tmpcl2 = []; + for j = 1:round(size(alldat{1}, 1) ./ len) + tmpcl{j}(i) = clusters(i); tmpcl{j}(i).all_data = alldat{i}(1+(j-1)*len:j*len, :); + tmpcl{j}(i).timeseries = mean(tmpcl{j}(i).all_data, 2); + + [ste, tmp] = ste(tmpcl{j}(i).all_data); + clusters(i).Z(j, :) = spm_t2z(tmp, size(tmpcl{j}(i).all_data, 1)-1); + + wh = find(tmp == max(tmp)); wh = wh(1); + clusters(i).BARPLOT.grp_peakdat(j, :) = tmpcl{j}(i).all_data(:, wh); + + %tmpcl2{j}(i) = cluster_ttest(tmpcl{j}(i), covs); % add covariate here if necessary + end + % needs checking and/or debugging + %inhib2_barplot(clusters(i), tmpcl2{1}(i), tmpcl2{2}(i), tmpcl2{3}(i), 'common'); + keyboard + end + + + + f = makefigure(dat{i}, str, cnames, splitat, docenter, covs); + try + saveas(gcf, ['cl' num2str(i) '_bar'], 'fig') + saveas(gcf, ['cl' num2str(i) '_bar'], 'tif') + catch + disp('error saving figure.'); + end + + clusters(i).BARPLOT.files = P; + clusters(i).BARPLOT.data = dat{i}; + clusters(i).BARPLOT.all_data = alldat{i}; + + if dosubcl + disp('Fewer than 5 voxels; skipping subclustering.') + + % make all field names match. + % won't work if this is the first one! + subcl{i} = subcl{1}(1); + N = fieldnames(subcl{i}); + for NN = 1:length(N) + eval(['subcl{i}(1).' N{NN} ' = [];']) + end + + N = fieldnames(clusters(i)); + for NN = 1:length(N) + eval(['subcl{i}(1).' N{NN} ' = clusters(i).' N{NN}]) + end + end + close all + end + clusters(i).covs = covs; + end + + + fprintf(1, '\n') + + % extract spatial peak data and locations + for i = 1:length(cl) + cltmp = extract_ind_peak([], cl{i}); + for j = 1:length(cltmp) + clusters(j).BARPLOT.peakdata{i} = cltmp(j).peakdata; + clusters(j).BARPLOT.peakXYZ{i} = cltmp(j).peakXYZ; + clusters(j).BARPLOT.peakXYZmm{i} = cltmp(j).peakXYZmm; + end + end + + + % extract individual significant region center of mass + if doindiv + disp('getting individual significance regions') + indpeak = []; + + for c = 1:size(Tnames, 2) % for each contrast tested + for i = 1:size(Tnames{1}, 1) % for each subject + % pass in clusters with all data saved for contrast c, Tnames + % for contrast c; timeseries saves data from this + + [tmp] = cluster_tmask(cl{c}, Tnames{c}(i, :), i, alldat); + cl{c} = tmp; + end + + % put stuff where it belongs + for j = 1:length(clusters) + clusters(j).BARPLOT.indiv_data(:, c) = cl{c}(j).timeseries; + clusters(j).BARPLOT.indivXYZ{c} = cl{c}(j).INDIV.center; + clusters(j).BARPLOT.indivXYZmm{c} = cl{c}(j).INDIV.mm_center; + clusters(j).BARPLOT.indiv_sig{c} = cl{c}(j).INDIV.sigt; + clusters(j).BARPLOT.indivXYZall{c} = cl{c}(j).INDIV.XYZ; + end + end + end +end + + + +% ----------------------------------------------------------------------------- +% * sub-functions +% ----------------------------------------------------------------------------- + + +function [subclusters] = getsubcl(clusters, adat, numimglists, numimgs, varargin) + % always pass in only one cluster in clusters!! (vec of length 1) + % if > 1 output, subclusters is created and plotted here + % if subclusters, pass in cnames and splitat + + if length(varargin) > 0, cnames = varargin{1}; splitat = varargin{2}; + clnum=varargin{3}; docenter = varargin{4}; + end + if length(varargin) > 4, covs = varargin{5}; else covs = []; end + + % don't scale if this is across subjects, because voxels + % with more variation across ss SHOULD drive the pca + %adat{j} = scale(adat{j}); % scale so high-noise voxels + % % don't drive the pca + + % separate subclusters + subclusters.BARPLOT = []; % add for consistency of field names + clusters.all_data = adat; + + [clusters, subclusters] = cluster_princomp(clusters, []); % , 0, 0, 1); + + subclusters = subclusters{1}; + + % re-format averages within sub-regions for plotting + + for j = 1:length(subclusters) + vind = 1; + if size(subclusters(j).all_data, 2) > 1, + tmp = nanmean(subclusters(j).all_data')'; + else + tmp = subclusters(j).all_data; + end + + for k = 1:numimglists + subclusters(j).BARPLOT.dat(:, vind) = ... + tmp((k-1)*numimgs+1:k*numimgs); + vind = vind + 1; + end + + % now plot it + ste = nanstd(subclusters(j).BARPLOT.dat) ./ sqrt(size(subclusters(j).BARPLOT.dat, 1)); + str = sprintf('Cl %3.0f (%4.0f vox) subcl at (%3.0f, %3.0f, %3.0f), %3.0f voxels', ... + clnum, clusters.numVox, subclusters(j).mm_center(1), ... + subclusters(j).mm_center(2), subclusters(j).mm_center(3), subclusters(j).numVox); + + f = makefigure(subclusters(j).BARPLOT.dat, str, cnames, splitat, docenter, covs); + saveas(gcf, ['cl' num2str(clnum) '_subc' num2str(j) '_bar'], 'fig') + saveas(gcf, ['cl' num2str(clnum) '_subc' num2str(j) '_bar'], 'tif') + end +end + + + +function f = makefigure(dat, varargin) + + if length(varargin) > 1, cnames=varargin{2}; else cnames = []; end + if length(varargin) > 2, splitat=varargin{3}; else splitat = 0; end + if length(varargin) > 3, docenter=varargin{4}; else docenter = 0; end + if length(varargin) > 4, covs = varargin{5}; covs(:, end+1) = 1; else covs = []; end + if covs == 1, covs = []; end % if empty, make it empty! + + dat1 = dat; % save original data for ind subj plot + + if ~isempty(covs) + covs(:, 1:end-1) = scale(covs(:, 1:end-1)); + for i = 1:size(dat, 2), + b = pinv(covs) * dat(:, i); + r = dat(:, i) - covs * b + b(end); + dat(:, i) = r; + end + end + + if docenter + disp('Centering rows of data for plotting.') + dat = dat - repmat(nanmean(dat')', 1, size(dat, 2)); + + end + + doprintt = 1; + + if doprintt + % print t values + for i = 1:size(dat, 2) + [h, p, ci, stats] = ttest(dat(:, i)); + nums{i} = sprintf('%3.2f', stats.tstat); + end + end + + ste = nanstd(dat) ./ sqrt(size(dat, 1)); + dat = nanmean(dat); + mysum = sign(dat) .* (ste + abs(dat) + .15 .* abs(dat)); + + if splitat > 2 + f = figure('Color', 'w'); + + h(1) = subplot(1, 2, 1); set(gca, 'FontSize', 16); hold on; grid on; + bar(dat(1:splitat)); tor_bar_steplot(dat(1:splitat), ste(1:splitat), {'b'}); + set(gca, 'XTick', 1:splitat) + xlabel('Conditions', 'FontSize', 18), ylabel('fMRI Signal', 'FontSize', 18) + if length(varargin) > 1, set(gca, 'XTickLabel', varargin{2}(1:splitat)), end + if length(varargin) > 0, title(varargin{1}, 'FontSize', 20), end + + for i = 1:splitat, text(i-.5, mysum(i), nums{i}, 'Color', 'k', 'FontWeight', 'b', 'FontSize', 14); end + + + h(2) = subplot(1, 2, 2); set(gca, 'FontSize', 16); hold on; grid on; + bar(dat(splitat+1:end)); tor_bar_steplot(dat(splitat+1:end), ste(splitat+1:end), {'b'}); + set(gca, 'XTick', 1:length(dat)-splitat) + xlabel('Conditions', 'FontSize', 18) + if length(varargin) > 1, set(gca, 'XTickLabel', varargin{2}(splitat+1:end)), end + + for i = 1:length(dat)-splitat, text(i, mysum(splitat+i), nums{splitat+i}, 'Color', 'k', 'FontWeight', 'b', 'FontSize', 14); end + + equalize_axes(h); + set(gcf, 'Position', [195 308 1259 674]), drawnow + + elseif splitat == 2 + + % ------------------------ + % this plot does individual subject estimates + % ------------------------ + barplot_columns(dat1) + + + + % add reg lines, if covs and sig + for i = 1:size(dat1, 2) + [B, dev, stat]=glmfit(covs(:, 1), dat1(:, i)); tmp = corrcoef(covs(:, 1), dat1(:, i)); + fprintf(1, 'Cond. %3.0f: Bo: t = %3.2f, se = %3.4f, p = %3.4f B1: r = %3.2f, t = %3.2f, se = %3.4f, p = %3.4f\n', ... + i, stat.t(1), stat.se(1), stat.p(1), tmp(1, 2), stat.t(2), stat.se(2), stat.p(2)); + + end + + if ~isempty(cnames), set(gca, 'XTickLabel', cnames), end + + else + + % ------------------------ + % standard bar plot + % ------------------------ + + f = figure('Color', 'w'); set(gca, 'FontSize', 16); %hold on; grid on; + h = bar(dat); set(h, 'FaceColor', [.7 .7 .7]) + tor_bar_steplot(dat, ste, {'k'}); + + doinhib2 = 0; + if doinhib2 + % special insert for inhib2 (triple inhibition) + set(gca, 'XTickLabel', {'GNG' 'Flanker' 'SRC' 'Saccade'}) + end + + dointext = 0; + if dointext + % special insert for intext + cla + xp = dat; h = bar([xp(1:3);xp(4:6)], 'grouped'); cm = [0 1 0;1 0 0; 0 0 1];colormap(cm) + xe = ste; + tor_bar_steplot(xp(1:3), xe(1:3), {'k'}, .55, .225) + tor_bar_steplot(xp(4:6), xe(4:6), {'k'}, 1.55, .225) + set(gca, 'FontSize', 16, 'XTickLabel', {'External' 'Internal'});legend(h, {'Object switching' 'Attribute Switching' 'Interaction'}) + ylabel('BOLD Contrast') + end + + %set(gca, 'XTick', 1:size(dat, 2)) + %xlabel('Conditions', 'FontSize', 18), ylabel('fMRI Signal', 'FontSize', 18) + %if length(varargin) > 1, set(gca, 'XTickLabel', varargin{2}), end + if length(varargin) > 0, title(varargin{1}, 'FontSize', 20), end + + set(gcf, 'Position', [464 283 930 827]); + drawnow + end +end + + + + + diff --git a/Visualization_functions/cluster_cutaways.m b/Visualization_functions/cluster_cutaways.m new file mode 100755 index 00000000..22a63a24 --- /dev/null +++ b/Visualization_functions/cluster_cutaways.m @@ -0,0 +1,74 @@ +function O = cluster_cutaways(clusters,textprefix,mycolor,whichcuts,varargin) +% function O = cluster_cutaways(clusters,textprefix,mycolor,whichcuts,[coords for center of cuts],[revx]) +% example: O = cluster_cutaways(clusters,'myoutname','y','yzx',[0 0 0],[revx]) +% +% groups clusters to image on brains by the first letter in whichcuts +% therefore, if you enter 'y' for whichcuts, the program will select +% clusters with similar y values (w/i 15 mm) and image them on the same +% brain. +% +% you need files called brain_render_T1.img/hdr on the path +% these should be scalped anatomicals for the brain image. +% you would also need 'brain_render_T1.mat' for the +% transparent brain surface, if you changed this script to get +% the transparent brain surface. +% +% if it cuts from the wrong side, try 'revx' as input into tor_3d.m +% or enter anything as 2nd var arg. +% +% uses renderCluster_ui.m +% +% by Tor Wager, Jan 2003 + +mm = cat(1,clusters.mm_center); +d = tril(dist(mm(:,2)') < 15); %distances +bestc = [0 0 0]; +revx = 0; + +done = zeros(1,length(clusters)); + +if length(varargin) > 0, bestc = varargin{1};, else bestc = [0 0 0];, end +if length(varargin) > 1, revx = varargin{2};, else revx = 0;, end + +clind = 1; +while any(~done) + + whclust = (d(:,clind)); + whclust(find(done)) = 0; + whclust = find(whclust); + done(whclust) = 1; + + if ~isempty(whclust) + figure('Color','w'); + O = struct('dohead','y','dobrain','n','get_from','workspace', ... + 'clusters',clusters, ... + 'which_cl',whclust,'whichc',whichcuts,'bestCoords',bestc,'clcol',mycolor,'addtext','n', ... + 'head','brain_render_T1','revx',revx); + + O = renderCluster_ui(O); set(O.headp(1:2:end),'FaceColor',[.5 .5 .5]);material dull + h = findobj('Type','Light');delete(h) + + if whichcuts(1) == 'y', view(171,6),[az,el] = view; hh = lightangle(az,el); %hh = lightangle(az,el); + elseif whichcuts(1) == 'z', view(0,70);[az,el] = view; hh = lightangle(az,el); + elseif whichcuts(1) == 'x', view(90,10);[az,el] = view; hh = lightangle(az,el); + end + + %h = findobj('Type','Light');delete(h),h(1) = camlight(90,0); h(2) = camlight(270,0); h(3) = camlight(270,0); + + if clusters(clind).numVox < 20 & clusters(clind).numVox > 1, + %hold on, plot3(clusters(1).XYZmm(1,:),clusters(1).XYZmm(2,:),clusters(1).XYZmm(3,:),'o','Color',mycolor,'MarkerFaceColor',mycolor,'MarkerSize',10) + end + + + saveas(gcf,[textprefix '_' whichcuts '_group' num2str(clind)],'tif'); + saveas(gcf,[textprefix '_' whichcuts '_group' num2str(clind)],'fig'); + close + end + + clind = clind+1; + end + + + return + + \ No newline at end of file diff --git a/Visualization_functions/cluster_image_shape.m b/Visualization_functions/cluster_image_shape.m new file mode 100644 index 00000000..e3b00455 --- /dev/null +++ b/Visualization_functions/cluster_image_shape.m @@ -0,0 +1,199 @@ +function [hpatch, cl] = cluster_image_shape(cl, varargin) +% function [hpatch, cl] = cluster_image_sphere(cl or [k x 3 list of mm coords], varargin) +% +% Images spheres at cluster centers +% Combine with addbrain and cluster_tools('connect3d') +% or cluster_nmdsfig_glassbrain +% to get 3-D plots of connected blobs +% +% Outputs: patch handles, and new cl with sphere coordiates in XYZmm and +% XYZ +% +% Optional inputs: +% {'color', 'colors'}, mycolor = varargin{i+1}; +% 'radius', myradius = varargin{i+1}; +% +% +% Tor Wager, July 2007 (original version) +% +% Usage: +% ------------------------------------------------------------------------- +% function [hpatch, cl] = cluster_image_sphere(cl) +% With optional arguments: +% [hpatch, cl] = cluster_image_sphere(cl, 'color', 'b', 'radius', 10) +% [hpatch, cl] = cluster_image_sphere(cl, 'color', {'r' 'g' 'b' etc}, 'radius', 10) +% +% Example: Given an MNI coordinate, plot a sphere on a brain surface +% ------------------------------------------------------------------------- +% my_mm_coord = [40, 46, 22]'; +% create_figure('surface') +% cl = []; +% cl.XYZmm = my_mm_coord; +% cl.mm_center = my_mm_coord'; +% V = spm_vol(which('brainmask.nii')); +% cl.M = V.mat; +% [hpatch, cl] = cluster_image_sphere(cl, 'color', 'g', 'radius', 10) +% p = addbrain; +% set(p, 'FaceAlpha', 1); +% axis image +% view(135, 30); lighting gouraud; lightRestoreSingle; material dull; +% +% Example: Turn xyz mm coordinates into clusters and image them +% ------------------------------------------------------------------------- +% my_mm_coord = [40 46 22; 50 26 40; 45 36 50; 60 12 0] +% [hpatch, cl] = cluster_image_sphere(my_mm_coord, 'color', 'b', 'radius', 4); + +% Programmers' notes: +% Tor Wager, July 2007 +% updated April 2011 for flexible radius +% updated 12/2012 for xyz to spheres + +hpatch = []; +mycolor = 'r'; +myradius = 8; +shape = 'sphere'; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case {'color', 'colors'}, mycolor = varargin{i+1}; varargin{i+1} = []; + case 'cube', shape = 'cube'; + case 'sphere', shape = 'sphere'; + case 'radius', myradius = varargin{i+1}; + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if isa(cl, 'region') + cl = region2struct(cl); +end + +% if cl is actually a series of xyz coordinates... +if ~isstruct(cl) + cl = create_cl_struct(cl); +end + +if length(myradius) == 1 + myradius = repmat(myradius, 1, length(cl)); +end + +% get colors for each sphere +if length(mycolor) == 1 || ~iscell(mycolor) + mycolor = repmat({mycolor}, 1, length(cl)); +end + +%newcl = convert_cl_xyz_to_sphere(cl(1), myradius); +for i = 1:length(cl) + if strcmp(shape,'sphere') + newcl(i) = convert_cl_xyz_to_sphere(cl(i), myradius(i)); + elseif strcmp(shape,'cube') + newcl(i) = convert_cl_xyz_to_cube(cl(i), myradius(i)); + else + error('programmers error: shape = ''%s''',shape) + end + + hpatch(i) = image_isosurface(newcl(i), mycolor{i}); +end + +cl = newcl; + +end + + +function cl = create_cl_struct(xyz) + +V = spm_vol(which('brainmask.nii')); +if size(xyz, 2) ~= 3, xyz = xyz'; end +if size(xyz, 2) ~= 3, error('Bad input xyz coordinates?'); end + +cl = []; +for i = 1:size(xyz, 1) + my_mm_coord = xyz(i, :); + cl(i).XYZmm = my_mm_coord'; + cl(i).mm_center = my_mm_coord; + cl(i).M = V.mat; +end + +end + + +function cl = convert_cl_xyz_to_sphere(cl, myradius) + + newXYZmm = round(points_in_sphere(cl(1).mm_center, myradius)'); + + cl(1).Z = ones(1, size(newXYZmm,2)); + cl(1).XYZmm = newXYZmm; + cl(1).XYZ = mm2voxel(cl(1).XYZmm, cl(1).M, 1)'; + cl(1).numVox = size(cl(1).XYZ, 2); + +end + + +function cl = convert_cl_xyz_to_cube(cl, myradius) + +c = round(cl(1).mm_center); +r = round(myradius); +twor = 2 * r; +newXYZmm = []; +for x = c(1) - r : c(1) + twor + for y = c(2) - r : c(2) + twor + for z = c(3) - r : c(3) + twor + newXYZmm = [newXYZmm [x y z]']; + end + end +end + +cl(1).Z = ones(1, size(newXYZmm,2)); +cl(1).XYZmm = newXYZmm; +cl(1).XYZ = mm2voxel(cl(1).XYZmm, cl(1).M, 1)'; +cl(1).numVox = size(cl(1).XYZ, 2); + +end + + +function hpatch = image_isosurface(cl, mycolor) + + % controls padding to make sure we cover whole area + padval = 1; + + % controls smoothness, etc. + mythresh = .5; + mysmoothbox = 3; + mygaussstd = 1; + + mm_coords = cl(1).XYZmm; + + xyzmin = min(mm_coords') - padval; % minus/plus for padding + xyzmax = max(mm_coords') + padval; + + [X, Y, Z] = meshgrid(xyzmin(1):xyzmax(1), xyzmin(2):xyzmax(2), xyzmin(3):xyzmax(3)); + + + % construct volume data for area to image + + xvox = mm_coords(1,:) - xyzmin(1) + 1; + yvox = mm_coords(2,:) - xyzmin(2) + 1; + zvox = mm_coords(3,:) - xyzmin(3) + 1; + + V = zeros(size(X)); + + for i = 1:size(xvox, 2) + V(xvox(i), yvox(i), zvox(i)) = cl(1).Z(i); + end + + % not needed if we have all mm points in sphere + %VI = interp3(cl(1).XYZmm(1,:), cl(1).XYZmm(2,:), cl(1).XYZmm(3,:), cl(1).Z, X, Y, Z); + + V = smooth3(V, 'gaussian', mysmoothbox, mygaussstd); + FV = isosurface(X,Y,Z,V, mythresh); + + hpatch = patch(FV); + set(hpatch, 'EdgeColor', 'none', 'FaceColor', mycolor); + + drawnow + + %lighting gouraud + +end \ No newline at end of file diff --git a/Visualization_functions/cluster_image_sphere.m b/Visualization_functions/cluster_image_sphere.m new file mode 100644 index 00000000..7c5751c9 --- /dev/null +++ b/Visualization_functions/cluster_image_sphere.m @@ -0,0 +1,178 @@ +function [hpatch, cl] = cluster_image_sphere(cl, varargin) +% function [hpatch, cl] = cluster_image_sphere(cl or [k x 3 list of mm coords], varargin) +% +% Images spheres at cluster centers +% Combine with addbrain and cluster_tools('connect3d') +% or cluster_nmdsfig_glassbrain to get 3-D plots of connected blobs +% or surface_cutaway +% +% Outputs: patch handles, and new cl with sphere coordiates in XYZmm and +% XYZ +% +% Optional inputs: +% {'color', 'colors'}, mycolor = varargin{i+1}; +% 'radius', myradius = varargin{i+1}; +% +% +% Tor Wager, July 2007 (original version) +% +% Usage: +% ------------------------------------------------------------------------- +% function [hpatch, cl] = cluster_image_sphere(cl) +% With optional arguments: +% [hpatch, cl] = cluster_image_sphere(cl, 'color', 'b', 'radius', 10) +% [hpatch, cl] = cluster_image_sphere(cl, 'color', {'r' 'g' 'b' etc}, 'radius', 10) +% +% Example: Given an MNI coordinate, plot a sphere on a brain surface +% ------------------------------------------------------------------------- +% my_mm_coord = [40, 46, 22]'; +% create_figure('surface') +% cl = []; +% cl.XYZmm = my_mm_coord; +% cl.mm_center = my_mm_coord'; +% V = spm_vol(which('brainmask.nii')); +% cl.M = V.mat; +% [hpatch, cl] = cluster_image_sphere(cl, 'color', 'g', 'radius', 10) +% p = addbrain; +% set(p, 'FaceAlpha', 1); +% axis image +% view(135, 30); lighting gouraud; lightRestoreSingle; material dull; +% +% Example: Turn xyz mm coordinates into clusters and image them +% ------------------------------------------------------------------------- +% my_mm_coord = [40 46 22; 50 26 40; 45 36 50; 60 12 0] +% [hpatch, cl] = cluster_image_sphere(my_mm_coord, 'color', 'b', 'radius', 4); + +% Programmers' notes: +% Tor Wager, July 2007 +% updated April 2011 for flexible radius +% updated 12/2012 for xyz to spheres + + mycolor = 'r'; + myradius = 8; + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case {'color', 'colors'}, mycolor = varargin{i+1}; varargin{i+1} = []; + + case 'radius', myradius = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + if isempty(cl) + hpatch = []; + return + end + + if isa(cl, 'region') + cl = region2struct(cl); + end + + % if cl is actually a series of xyz coordinates... + if ~isstruct(cl) + cl = create_cl_struct(cl); + end + + if length(myradius) == 1 + myradius = repmat(myradius, 1, length(cl)); + end + + % get colors for each sphere + if length(mycolor) == 1 || ~iscell(mycolor) + mycolor = repmat({mycolor}, 1, length(cl)); + end + + %newcl = convert_cl_xyz_to_sphere(cl(1), myradius); + + for i = 1:length(cl) + + newcl(i) = convert_cl_xyz_to_sphere(cl(i), myradius(i)); + + hpatch(i) = image_isosurface(newcl(i), mycolor{i}); + + + end + + cl = newcl; + +end + + +function cl = create_cl_struct(xyz) + +V = spm_vol(which('brainmask.nii')); +if size(xyz, 2) ~= 3, xyz = xyz'; end +if size(xyz, 2) ~= 3, error('Bad input xyz coordinates?'); end + +cl = []; +for i = 1:size(xyz, 1) + my_mm_coord = xyz(i, :); + cl(i).XYZmm = my_mm_coord'; + cl(i).mm_center = my_mm_coord; + cl(i).M = V.mat; +end + +end + + +function cl = convert_cl_xyz_to_sphere(cl, myradius) + + newXYZmm = round(points_in_sphere(cl(1).mm_center, myradius)'); + + cl(1).Z = ones(1, size(newXYZmm,2)); + cl(1).XYZmm = newXYZmm; + cl(1).XYZ = mm2voxel(cl(1).XYZmm, cl(1).M, 1)'; + cl(1).numVox = size(cl(1).XYZ, 2); + +end + + +function hpatch = image_isosurface(cl, mycolor) + + % controls padding to make sure we cover whole area + padval = 1; + + % controls smoothness, etc. + mythresh = .5; + mysmoothbox = 3; + mygaussstd = 1; + + mm_coords = cl(1).XYZmm; + + xyzmin = min(mm_coords') - padval; % minus/plus for padding + xyzmax = max(mm_coords') + padval; + + [X, Y, Z] = meshgrid(xyzmin(1):xyzmax(1), xyzmin(2):xyzmax(2), xyzmin(3):xyzmax(3)); + + + % construct volume data for area to image + + xvox = mm_coords(1,:) - xyzmin(1) + 1; + yvox = mm_coords(2,:) - xyzmin(2) + 1; + zvox = mm_coords(3,:) - xyzmin(3) + 1; + + V = zeros(size(X)); + + for i = 1:size(xvox, 2) + V(xvox(i), yvox(i), zvox(i)) = cl(1).Z(i); + end + + % not needed if we have all mm points in sphere + %VI = interp3(cl(1).XYZmm(1,:), cl(1).XYZmm(2,:), cl(1).XYZmm(3,:), cl(1).Z, X, Y, Z); + + V = smooth3(V, 'gaussian', mysmoothbox, mygaussstd); + FV = isosurface(X,Y,Z,V, mythresh); + + hpatch = patch(FV); + set(hpatch, 'EdgeColor', 'none', 'FaceColor', mycolor); + + drawnow + + %lighting gouraud + +end \ No newline at end of file diff --git a/Visualization_functions/cluster_orthviews.m b/Visualization_functions/cluster_orthviews.m new file mode 100755 index 00000000..ba82cc59 --- /dev/null +++ b/Visualization_functions/cluster_orthviews.m @@ -0,0 +1,981 @@ +% cluster_orthviews(inputs in any order) +% +% This function uses spm_orthviews to display activation blobs from a +% clusters structure on a canonical structural brain image. Multiple +% clusters may be plotted in multiple colors, and blobs may be added to an +% existing orthviews figure. +% +% inputs: +% clusters structures +% colors cell array, e.g., {[0 0 1] [1 0 0] [0 1 0]} +% if no colors cell array, uses Z- or t-scores to color map +% +% opt: 'add' to suppress making new orthviews +% 'copy' to copy to smaller subfigure with empty axis beside it +% 'unique' to display in unique colors for each cluster +% 'overlay', followed by name of image, to use a custom anatomical overlay +% 'bivalent', to plot increases in solid colors specified by colors cell +% {1} and {2} +% OR, if no colors entered, use hot/cool map in +% spm_orthviews_hotcool_colormap +% +% Options if you specify colors (addColouredBlobs): +% 'trans', to make blobs transparent +% 'solid', to make them solid +% +% Options for using spm's color map (addBlobs) +% 'blue', display in blue split color map instead of default red-yellow +% +% 'handle', followed by integer for which orthviews window to use (default = 1) +% +% By Tor Wager +% Last update: April 2007 by TW : add hot/cool colormap to bivalent option +% Add handles option +% + +function cluster_orthviews(varargin) + + spm_defaults; + + %overlay = which('scalped_single_subj_T1.img'); + overlay = which('SPM8_colin27T1_seg.img'); % spm8 seg cleaned up + donew = 1; % new fig + docopy = 0; % copy to new axis + douniquecolors = 0; + dobiv = 0; + dotrans = 0; + dosolid = 0; + doblue = 0; + initwhitebg = 0; % automatic white background for spm_orthviews + skipempty = 0; + + wh_handle = 1; + + cl = []; + cols = []; + + for i = 1:length(varargin) + if iscell(varargin{i}), cols = varargin{i}; + elseif isstruct(varargin{i}) || strcmp(class(varargin{i}), 'region') + cl{end+1} = varargin{i}; + elseif ischar(varargin{i}) + switch(varargin{i}) + case 'add' + donew = 0; + case 'copy' + docopy = 1; + case 'unique' + douniquecolors = 1; + case 'overlay' + overlay = deblank(varargin{i+1}); + case 'bivalent' + dobiv = 1; + case 'trans' + dotrans = 1; + case 'solid' + dosolid = 1; + + case 'blue' + doblue = 1; + + case 'handle' + wh_handle = varargin{i+1}; + + case 'skipempty' + skipempty = 1; + + case {'whitebg', 'white', 'initwhitebg'} + initwhitebg = 1; + + end + end + end + + if ischar(overlay) + [path, name, ext] = fileparts(overlay); + if(isempty(path)) + overlay = which(overlay); + end + elseif isempty(overlay) + overlay = which('spm2_single_subj_T1_scalped.img'); % spm2 + end + + if isempty(overlay) + + disp('No valid overlay image specified, and I cannot find the default one.'); + error('The default is SPM8_colin27T1_seg.img, and should be on your path.'); + + end + + if isempty(cl) + if skipempty + % do nothing + else + spm_check_registration(overlay); + end + + if initwhitebg, doinitwhitebg; end + return + end + + for i = 1:length(cl) + if dotrans, cl{i} = setup_trans(cl{i}); end + if dosolid, cl{i} = setup_solid(cl{i}); end + end + + for i = 1:length(cl) + CLU{i} = clusters2CLU(cl{i}); + if ~isfield(CLU{i}, 'Z'), CLU{i}.Z = ones(1, size(CLU{i}.XYZ, 2)); end + if min(size(CLU{i}.Z)) > 1, CLU{i}.Z = CLU{i}.Z(1, :); end + end + + + + % ----------------------------------------------------------- + % Bivalent response: red = activation, blue = deactivation + % ----------------------------------------------------------- + if dobiv + + if ~isempty(cols) + % we have solid colors (addColoredBlobs, not the heat-mapping) + [poscl, negcl] = cluster_separate_posneg(cl{1}, 'z=1'); + + if length(cols) < 2 + cols = [cols {[0 0 1]}]; + end + + %view clusters + cluster_orthviews(poscl, cols{1}, 'overlay', overlay); + if ~isempty(poscl) + cluster_orthviews(negcl, cols{2}, 'add', 'overlay', overlay); + else + cluster_orthviews(negcl, cols{2}, 'overlay', overlay); + end + + else + % we have color mapping mapping + cluster_orthviews(cl{1}, 'overlay', overlay); + + try + Zvals = cat(2,cl{1}.Z); + catch + error('Invalid clusters.Z field'); + end + spm_orthviews_hotcool_colormap(Zvals, min(abs(Zvals)) .* .95); + + end + + return + end + + + if donew, warning off, spm_check_registration(overlay), set(gcf, 'Resize', 'on'); warning on, end %spm_image('init', overlay), end + + if douniquecolors + % unique colors for each blob + colors = scn_standard_colors(50); + + ind = 1; + for i = 1:length(cl) + while length(colors) < length(cl{i}) + colors = scn_standard_colors(length(cl{i})); + %colors = [colors {rand(1, 3)}]; + end + for j = 1:length(cl{i}) + CLUtmp = clusters2CLU(cl{i}(j)); + if ~isfield(CLUtmp, 'Z'), CLUtmp.Z = ones(1, size(CLUtmp.XYZ, 2)); end + if min(size(CLUtmp.Z)) > 1, CLUtmp.Z = CLUtmp.Z(1, :); end + spm_orthviews('AddColouredBlobs', wh_handle, CLUtmp.XYZ, CLUtmp.Z(1, :), CLUtmp.M, colors{ind}) + ind = ind + 1; + end + end + else + for i = 1:length(cl) + if isempty(cols) || length(cols) < i + spm_orthviews('AddBlobs', wh_handle, CLU{i}.XYZ, CLU{i}.Z, CLU{i}.M) + + if doblue + % seems to work when we copy into window, but + % not in this script... + myfig = findobj('Tag','Graphics'); + figure(myfig); + cm = get(myfig,'Colormap'); + cm(65:end,:) = cm(65:end,[3 2 1]); + colormap(cm); + end + + else + spm_orthviews('AddColouredBlobs', wh_handle, CLU{i}.XYZ, CLU{i}.Z(1, :), CLU{i}.M, cols{i}) + end + end + end + + + if docopy + hh = gcf; + h1 = get(hh, 'Children'); + f1 = figure('Color', 'w'); + c = copyobj(h1, f1); + + set(gcf, 'Position', [210 322 929 407]); + for i = 1:length(c) + copypos = get(c(i), 'Position'); + copypos(3) = copypos(3) .* .5; + set(c(i), 'Position', copypos); + end + keyboard + end + + mypos = mean(cl{1}(1).XYZmm, 2); + if length(mypos) < 3, mypos = cl{1}(1).XYZmm; end + if isempty(mypos), mypos = [0 0 0]'; end + spm_orthviews('Reposition', mypos); + + + % try to set the window button up function to show x, y, z position + % coordinates + fh = findobj('Tag', 'Graphics'); + if isempty(get(fh, 'WindowButtonUpFcn')) + set(fh, 'WindowButtonUpFcn', 'spm_orthviews_showposition;'); + spm_orthviews_showposition; + + elseif strcmp(get(fh, 'WindowButtonUpFcn'), 'spm_orthviews_showposition;') + % we already have it; do nothing, and no warning + else + % warning + disp('A windowbtnup function already exists; not showing position coordinates.') + end + + + if initwhitebg, doinitwhitebg; end + +end % main function + + + + + + + +% --------------------------------------------------------------------- +% +% +% Sub-functions +% (Needed only for bivalent option) +% +% --------------------------------------------------------------------- + + +function [poscl, negcl] = cluster_separate_posneg(cl, varargin) + clu = clusters2CLU(cl); + + whpos = find(clu.Z > 0); + whneg = find(clu.Z < 0); + N = fieldnames(clu); + + + if length(varargin) > 0 + switch varargin{1} + case 'z=1' + clu.Z = ones(size(clu.Z)); + end + end + + poscl = []; + negcl = []; + for i = 1:length(N) + if length(clu.(N{i})) == length(clu.Z) + poscl.(N{i}) = clu.(N{i})(:, whpos); + negcl.(N{i}) = clu.(N{i})(:, whneg); + else + poscl.(N{i}) = clu.(N{i}); + negcl.(N{i}) = clu.(N{i}); + end + end + + poscl = tor_extract_rois([], poscl, poscl); + negcl = tor_extract_rois([], negcl, negcl); +end + + + +function cl = setup_trans(cl) + z = cat(2, cl.Z); z = z(:); + %sd = nanstd(z); + %z = z./sd; + %for i = 1:length(cl) + % cl(i).Z = cl(i).Z ./ sd; + %end + cl(1).Z(1) = max(z) * 2.5; +end + + +function cl = setup_solid(cl) + for i = 1:length(cl) + cl(i).Z = ones(1, size(cl(i).XYZmm, 2)); + end +end + + + + +% function CLU = clusters2CLU(clusters, [opt] M) +% +% Inputting an M matrix will transform the coordinates +% by that M, to convert between voxel sizes, etc. +% +% by Tor Wager +function CLU = clusters2CLU(clusters, varargin) + + if ~isfield(clusters(1), 'threshold'), clusters(1).threshold = 1; end + + CLU.XYZmm = cat(2, clusters.XYZmm); + CLU.XYZ = cat(2, clusters.XYZ); + try + CLU.Z = cat(2, clusters.Z); + catch + %CLU.Z = cat(1, clusters.Z)'; + for i = 1:length(clusters) + if size(clusters(i).Z, 1) > size(clusters(i).Z, 2) + clusters(i).Z = clusters(i).Z'; + end + end + CLU.Z = cat(2, clusters.Z); + end + CLU.title = clusters(1).title; + + CLU.u = clusters(1).threshold; + CLU.threshold = clusters(1).threshold; + + CLU.voxSize = clusters(1).voxSize; + CLU.VOX = clusters(1).voxSize; + + if nargin > 1 + CLU.M = varargin{1}; + CLU = transform_coordinates(CLU, CLU.M); + else + switch class(clusters) + case 'struct' + has_M_field = isfield(clusters, 'M'); + + case 'region' + has_M_field = ~isempty(strmatch('M', fieldnames(clusters))); + + otherwise + error('cluster_orthviews: cl input is wrong class...'); + end + + if has_M_field + CLU.M = clusters(1).M; + else + error('invalid clusters variable: clusters must have defined M field/attribute.'); + end + end + + CLU.numVox = size(CLU.XYZmm, 2); + + if isempty(CLU.Z), + disp('clusters Z field is empty. Filling with ones as a placeholder.') + CLU.Z = ones(1, size(CLU.XYZmm, 2)); + end + + if size(CLU.Z, 1) > 1, CLU.allZ = CLU.Z; CLU.Z = CLU.Z(1, :); end +end + + +% function [clusters, SPM, xX, xCon] = tor_extract_rois(imnames [can be empty], [opt] SPM, [opt] VOL, [opt] xX) +% +% this function gets timeseries data from all clusters in an SPM results output. +% input: +% imnames: a matrix of image names, in spm_list_files output format +% if empty, no timeseries data will be extracted. +% +% clusters: if only 2 arguments, clusters structure is 2nd arg, and we +% extract data using existing clusters structure +% +% If 3 arguments, enter SPM and VOL to extract data from VOXEL +% coordinates in these structures +% SPM: SPM variable from loaded results +% VOL: VOL variable from loaded results +% +% Optional 4th argument fits a design matrix and returns betas +% xX: xX design matrix to fit to timeseries +% OR 4th argument can be 0 (or any non-structure arg), suppressing verbose output. +% +% [Last 2 arguments are optional. Use if results are already loaded into workspace] +% +% Automatic fitting of model to cluster timeseries average using analyze_cluster_rois +% with High-Pass filter length of your choice. +% This only works if you input only the file names or input all optional arguments, including xX +% +% 10/17/01 by Tor Wager +% Last modified 3/19/04 by Tor, to get clusters structure as input and use +% timeseries3 instead of (bad on mac osx) timeseries2 +% +% NOTE (WARNING): WORKS ON XYZ VOXEL COORDINATES - TRANSFORMATION TO +% DIFFERENT SPACES ONLY IF ENTERING 2 ARGS, 1st one names, 2nd one clusters +% +% see transform_coordinates.m for transformation, or check_spm_mat, or cluster_interp. +% +% Functions called +% C:\matlabR12\toolbox\matlab\datatypes\squeeze.m +% c:\tor_scripts\voistatutility\nanmean.m +% (calls other spm functions) +% center_of_mass.m +% analyze_cluster_rois.m +function [clusters, SPM, xX, xCon] = tor_extract_rois(imnames, varargin) + + verbose = 0; + clusters = []; clustersin = []; + + if nargin == 1 + % ---------------------------------------------------------------------------------- + % get SPM info from SPM.mat + % ---------------------------------------------------------------------------------- + [SPM, VOL, xX, xCon, xSDM] = spm_getSPM; + elseif nargin == 2 + clustersin = varargin{1}; + + if ~isempty(imnames), + % ---------------------------------------------------------------------------------- + % check mat files for compatibility + % ----------------------------------------------------------------- + if isfield(clustersin, 'M') + V = spm_vol(imnames(1, :)); + [chk, clustersin] = check_spm_mat(clustersin(1).M, V(1).mat, clustersin); + if chk, disp('Warning! Mat files for clusters and images do not match; using XYZmm to adjust cluster voxel coordinates'), end + end + end + + allxyz = cat(2, clustersin.XYZ); + elseif nargin == 3 + SPM = varargin{1}; + VOL = varargin{2}; + if isempty(SPM.XYZ), disp('tor_extract_rois: no voxels to extract'), return, end + allxyz = SPM.XYZ; + elseif nargin == 4 + SPM = varargin{1}; + VOL = varargin{2}; + if isempty(SPM.XYZ), disp('tor_extract_rois: no voxels to extract'), return, end + allxyz = SPM.XYZ; + + a = varargin{3}; + if isstruct(a), xX = a; + else verbose = 0; + end + elseif nargin ~= 3 + error('Wrong number of arguments. Use 1 if loading from SPM.mat, 3 args for no analysis, 4 with analysis.') + end + + % ---------------------------------------------------------------------------------- + % get cluster index number from each voxel + % ---------------------------------------------------------------------------------- + if isempty(clustersin) + try + cl_index = spm_clusters(SPM.XYZ); + catch + disp('Error evaluating: cl_index = spm_clusters(SPM.XYZ);') + disp('No significant XYZ coordinates in SPM.XYZ!?') + clusters = []; + return + end + else + cl_index = 1:length(clustersin); + end + + % ---------------------------------------------------------------------------------- + % load image files, if possible + % ---------------------------------------------------------------------------------- + if ~isempty(imnames) + if size(imnames, 1) < 100 + if verbose, fprintf(1, '\nReading %3.0f images...', size(imnames, 1)); end + % Image loading a la SPM, and manual extraction. + V = spm_vol(imnames); + vols = spm_read_vols(V); + end + end + + + + % ---------------------------------------------------------------------------------- + % get the data from ALL voxels; all_data stores raw data for all + % clusters + % ---------------------------------------------------------------------------------- + O.coords = allxyz'; clear allxyz + if ~isempty(imnames), + + if size(imnames, 1) < 100 + % we have already loaded images + ts = timeseries4(O.coords, vols); + all_data = ts.indiv; + else % load the images and extract data + ts = timeseries4(O.coords, imnames); + all_data = ts.indiv; + + % timeseries2 does not work on Mac OSX + % timeseries2 - maybe slower?, but more memory efficient for large n of images + % does not support multiple datatypes w.i timeseries - e.g., for masked 1st subject + %try + % ts = timeseries2('multi', imnames, O); + % cl.timeseries = ts.avg; + end + end + + + % ---------------------------------------------------------------------------------- + % define each cluster as cell in array. + % ---------------------------------------------------------------------------------- + + for i = 1:max(cl_index) + if verbose && i == 1 + fprintf(1, '\n\t%3.0f clusters: Extracting %03d ', max(cl_index), i), + elseif verbose + fprintf(1, '\b\b\b%03d', i) + if i == max(cl_index) + fprintf(1, '\n') + end + end + + % voxels in this cluster + a = find(cl_index == i); + + + % make cluster, if we don't have it + if isempty(clustersin) + cl.title = SPM.title; + cl.threshold = SPM.u; + cl.voxSize = VOL.VOX; + cl.M = VOL.M; + cl.name = [cl.title '_' num2str(i) '_' mat2str(size(a, 2)) '_voxels']; + cl.numVox = size(a, 2); + cl.Z = SPM.Z(a); + cl.XYZmm = SPM.XYZmm(:, a); + cl.XYZ = SPM.XYZ(:, a); + + try + cl.pVoxelLev = spm_P(1, 0, max(cl.Z), SPM.df, SPM.STAT, VOL.R, SPM.n); + cl.pClustLev = spm_P(1, cl.numVox/prod(VOL.FWHM), SPM.u, SPM.df, SPM.STAT, VOL.R, SPM.n); + catch + % warning('Can''t get SPM p voxel and cluster values. Skipping spm_P.') + end + + if ~isempty(cl.Z), + % report number of sub-cluster peaks within cluster + [N] = spm_max(cl.Z, cl.XYZ); + cl.numpeaks = length(N); + end + else % we already have clusters + cl = clustersin(i); + end + + % ---------------------------------------------------------------------------------- + % get the timeseries for the cluster + % ---------------------------------------------------------------------------------- + if ~isempty(imnames) + + if isempty(clustersin) + % no clusters input + cl.all_data = all_data(:, a); + else + % clusters input + nv = size(clustersin(i).XYZ, 2); + cl.all_data = all_data(:, 1:nv); % take first n data vectors + all_data(:, 1:nv) = []; % remove from all_data + end + + + cl.timeseries = nanmean(cl.all_data', 1)'; + + if size(imnames, 1) < 200 && i == 1 + cl.imnames = imnames; + elseif i == 1 + cl.imnames = imnames([1 end], :); + else + cl.imnames = []; + end + + + % ---------------------------------------------------------------------------------- + % get the SNR for the cluster if it looks like rfx data rather than individual data + % ---------------------------------------------------------------------------------- + if size(imnames, 1) < 60 + try + cl.snr_avgts = get_snr(cl.timeseries); + cl.snr = get_snr(cl.all_data); + if verbose, fprintf(1, ' : avg SNR = %3.2f, range %3.2f - %3.2f ', cl.snr_avgts, min(cl.snr), max(cl.snr)), end + + % calculate # subjects w/ estimates above zero + cl.numpos = sum(cl.timeseries > 0); + + % calc 80% power level, no mult. comp corr + % from Jerry Dallal @ Tufts U. http://www.tufts.edu/~gdallal/SIZE.HTM + % (16 * sd^2 / est^2) + 1 + cl.power80 = (4 ./ cl.snr_avgts)^2 + 1; + catch + warning('Problem with SNR calculation.') + keyboard + end + end + + else + % disp('No timeseries data extracted - image names empty.') + end + + cl.center = mean(cl.XYZ', 1); + + % in case we've added a full matrix of Z-scores with + % cluster_barplot, etc. + if min(size(cl.Z)) > 1, cl.Z = ones(1, size(cl.XYZ, 2)); end + + if size(cl.XYZmm, 2) > 1 + cl.mm_center = center_of_mass(cl.XYZmm, cl.Z); + else + cl.mm_center = cl.XYZmm'; + end + clusters = [clusters, cl]; + end + + + + + if ~isempty(imnames) && exist('xX') == 1 + + % ---------------------------------------------------------------------------------- + % adjust timeseries for each cluster and fit xX.X model to timeseries data + % ---------------------------------------------------------------------------------- + try + clusters = analyze_cluster_rois(clusters, xX); + catch + disp('Error analyzing timeseries clusters - skipping analysis.') + end + end + + % ---------------------------------------------------------------------------------- + % save this data in current directory + % ---------------------------------------------------------------------------------- + %matname = ['clusters_' deblank(SPM.title(~(SPM.title==' ')))]; + %str = ['save ' matname ' clusters']; + %eval(str) +end + + + +% function [ts, vols, chunksize] = timeseries4(coords, P, [chunksize], [nochk]) +% +% Simple extraction from images named in str mtx P or vols +% from voxel coordinates (not mm!) listed in coords +% +% P can be filenames in str matrix (char array) +% or 4-D array of all volume info (vols) +% (i.e., put vols in output back in as P) +% +% ts is timeseries, with fields avg and indiv for average cluster +% and individual voxels +% +% Loads images 'chunksize' at a time; default is based on memory +% size of 2^29 bytes; empty uses default +% Optional 4th argument suppresses data validity checking +% +% vols is 4-D array of all data, [x y z time(image)] +% +% Uses spm_vol and spm_read_vols and spm_slice_vol +% +% Tor Wager, 2/9/05 change from timeseries3: uses slice-by-slice +% method, faster. +% Tor Wager, 12/11/05 speedup for getdata with large n. voxels; cosmetic +% changes to output of volume method. + +function [ts, vols, chunksize] = timeseries4(coords, P, varargin) + + % ------------------------------------------------------------------- + % * set up input arguments + % ------------------------------------------------------------------- + maxmem = 2^28; % note:tested the G5s up to 200 imgs, no slowdown, so no need to chunk... + chunksize = []; + + global defaults + if isempty(defaults), spm_defaults, end + + if ischar(P), + fprintf(1, 'Map vols: '); t1= clock; + V = spm_vol(P); + nimages = length(V); % number of images in data + fprintf(1, '%3.0f s.\n', etime(clock, t1)); + elseif ismatrix(P), + chunksize = NaN; + nimages = size(P, 4); % number of images in data + else + error('P input must be string or data matrix.'); + end + + if length(varargin) > 0, + chunksize = varargin{1}; + end + + + if size(coords, 2) ~= 3, coords = coords'; end + + + % ------------------------------------------------------------------- + % * get extraction method + % ------------------------------------------------------------------- + whslices = unique(coords(:, 3)); % which slices to extract + nslices = length(whslices); % number of z slices to extract from + + extract_type = 'slice'; + if ~ischar(P), extract_type = 'volume'; end + if nslices > 15, extract_type = 'volume'; end + + % ------------------------------------------------------------------- + % * read images + % ------------------------------------------------------------------- + + switch extract_type + % * slice loading method + % ------------------------------------------------------------------- + case 'slice' + + ts.indiv = NaN .* zeros(nimages, size(coords, 1)); % placeholder for data extracted + + for i = 1:nslices + + sliceno = whslices(i); % slice number + + + whcoords = find(coords(:, 3) == sliceno); % indices of in-slice coordinates + slcoords = coords(whcoords, :); % coordinates to extract data from for this slice + + sl = timeseries_extract_slice(V, sliceno); + + for c = 1:size(slcoords, 1) + + ts.indiv(:, whcoords(c)) = sl(slcoords(c, 1), slcoords(c, 2), :); + + end + + ts.avg = nanmean(ts.indiv')'; + vols = sl; + end + + + % * whole-brain loading method + % ------------------------------------------------------------------- + case 'volume' + if isempty(chunksize) + v = spm_read_vols(V(1)); + + tmp = whos('v'); imgsize = tmp.bytes; + chunksize = floor(maxmem ./ imgsize); %round(maxmem ./ (size(coords, 1)^3 .* 16)); % images to load at a time + end + + if ischar(P) + if length(V) < chunksize + fprintf(1, '\tChunking %3.0f into %3.0f imgs: ', length(V), chunksize); + t1 = clock; + fprintf(1, 'Load: '); + vols = spm_read_vols(V); + fprintf(1, '%3.0f s. Cluster. ', etime(clock, t1)); + t1 = clock; + [ts.indiv, ts.avg] = getdata(vols, coords, maxmem); + fprintf(1, '%3.0f s. ', etime(clock, t1)); + else + % chunk it! load chunksize images as a whole, extract, and + % concatenate + ts.indiv = []; ts.avg = []; + ind = 1; + for i = 1:chunksize:length(V) + t1 = clock; + fprintf(1, 'Load: %3.0f ', i); + e = min(i+chunksize-1, length(V)); + wh = i:e; + vols = spm_read_vols(V(wh)); + fprintf(1, '%3.0f s. Cluster. ', etime(clock, t1)); + t1 = clock; + [indiv{ind}, avg{ind}] = getdata(vols, coords, maxmem); + ind = ind + 1; + fprintf(1, '%3.0f s. ', etime(clock, t1)); + end + fprintf(1, 'Cat. ') + ts.indiv = cat(1, indiv{:}); + ts.avg = cat(1, avg{:}); + clear indiv; clear avg; + end + else + % volumes already loaded + vols = P; P = 1; + [ts.indiv, ts.avg] = getdata(vols, coords, maxmem); + end + end % end switch extraction type + + + % ------------------------------------------------------------------- + % * check for proper extraction against spm_read_vols + % ------------------------------------------------------------------- + + if length(varargin) > 1 || ~ischar(P) + % already loaded or suppress checking. + else + fprintf(1, 'Chk.\n') + chk = check_timeseries_vals(V, ts.indiv, coords); + if chk, keyboard, end + end +end + + + + + +function [ind, avg] = getdata(vols, coords, maxmem) + if size(coords, 1) == 1 % only one voxel + co = 1; + ind(:, co) = squeeze(vols(coords(co, 1), coords(co, 2), coords(co, 3), :)); + elseif size(coords, 1)^3 < inf % always do this. + % time increases linearly with size of matrix; so do it in chunks. + csz = round(sqrt(size(coords, 1))); % optimal chunk size to keep arrays as small as possible. + indx = 1; + for i = 1:csz:size(coords, 1) + tmp = []; + for co = i:min(i+csz-1, size(coords, 1)) + %t1 = clock; + tmp = [tmp squeeze(vols(coords(co, 1), coords(co, 2), coords(co, 3), :))]; + %et(co) = etime(clock, t1); + end + ind{indx} = tmp; + indx = indx + 1; + %ind = [ind squeeze(vols(coords(co, 1), coords(co, 2), coords(co, 3), :))]; + end + ind = cat(2, ind{:}); + else % not a good idea speed-wise, apparently. + tmp = vols(coords(:, 1), coords(:, 2), coords(:, 3), :); % the values of interest are on the 3-D diagonals of tmp + s = size(coords, 1); % we can get the index values for diagonals by skipping elements of sv, below + i = 1:s.^2 + s + 1:s^3; % same as t1 = [1:size(coords, 1)]'; i = sub2ind(size(tmp), t1, t1, t1) + + sv = (1:s^3:prod(size(tmp))) - 1; % starting values for each volume (minus one, so we add this to i) + sv = repmat(sv, s, 1) + repmat(i', 1, size(sv, 2)); % get the matrix of index values for each voxel at each time + ind = tmp(sv)'; + end + + if size(ind, 2) > 1, + avg = nanmean(ind')'; + else + avg = ind; + end +end + + + + +function sl = timeseries_extract_slice(V, sliceno) + % function sl = timeseries_extract_slice(V, sliceno) + % + % For a given set of image names or memory mapped volumes (V) + % extracts data from slice # sliceno and returns an X x Y x time + % matrix of data. + % uses spm_slice_vol.m + + if ischar(V), V = spm_vol(V); end + + mat = spm_matrix([0 0 sliceno]); % matrix for spm_slice_vol + + for i = 1:length(V) + sl(:, :, i) = spm_slice_vol(V(i), mat, V(i).dim(1:2), 0); + end +end + + + + +function chk = check_timeseries_vals(V, dat, coords) + % function chk = check_timeseries_vals(V, dat, coords) + % + % Checks a random subset of up to 5 images against extracted data values + % to make sure the right data is being extracted. + % + % V is memory-mapped volumes (see spm_vol) + % dat is extracted data + % coords is voxel coordinates, n rows x 3 columns + % + % tor wager + + n = min(5, length(V)); + + % get random set of n images, up to 5 + wh = randperm(length(V)); + wh = wh(1:n); + + % select these rows in V and dat + % dat is [images, coordinates] + V = V(wh); + dat = dat(wh, :); + + % get random set of nn coordinates, up to 5 + + nc = size(coords, 1); + nn = min(5, nc); + whc = randperm(nc); + whc = whc(1:nn); + coords = coords(whc, :); + + % select these columns in dat + dat = dat(:, whc); + + v = spm_read_vols(V); + for i = 1:nn % for each coordinate + dat2(:, i) = squeeze(v(coords(i, 1), coords(i, 2), coords(i, 3), :)); + end + + chk = dat - dat2; + chk = any(chk(:)); + + if chk, warning('Problem with timeseries3!! Extracted data do not match expected values. Quitting at error'); end +end + + + +function snr = get_snr(data) + % snr = get_snr(data) + % + % data is a matrix whos columns index voxels, and rows index subjects (or trials, etc.) + % + % Tor Wager + + mystd = nanstd(data); + mystd(mystd == 0) = NaN; + snr = nanmean(data) ./ mystd; +end + + +function doinitwhitebg + try + global st + st.vols{1}.black2white = 1; + bwexist = strfind(st.plugins, 'black2white'); + bwexist = any(cat(2, bwexist{:})); + if ~bwexist + st.plugins{end+1} = 'black2white'; + end + fh = findobj('Type', 'Figure', 'Tag', 'Graphics'); % spm fig + if exist('fh') && ishandle(fh), set(fh, 'Color', 'w'); end + + catch + disp('Error in development version of initwhitebg!!') + end +end + + +% ISMATRIX: Returns 1 if the input matrix is 2+ dimensional, 0 if it is a scalar +% or vector. +% +% Usage ismat = ismatrix(X) +% +% RE Strauss, 5/19/00 + +function ismat = ismatrix(X) + [r,c] = size(X); + if (r>1 && c>1) + ismat = 1; + else + ismat = 0; + end + +end diff --git a/Visualization_functions/cluster_orthviews_classes.m b/Visualization_functions/cluster_orthviews_classes.m new file mode 100644 index 00000000..68cf1473 --- /dev/null +++ b/Visualization_functions/cluster_orthviews_classes.m @@ -0,0 +1,81 @@ +function colors2 = cluster_orthviews_classes(cl,classes,overlay,myview,domontage, varargin) +%function colors2 = cluster_orthviews_classes(cl,classes,overlay,myview,domontage, [orthview axis], [colors cell]) +% +% Makes montage and cluster orthviews of classes of clusters in different +% colors (hard coded colors right now). +% +% Inputs: +% cl clusters structure with regions +% classes integers with classes (i.e., networks) to be color-coded on plot +% overlay anatomical underlay image +% myview if entered, shows centers on plot +% domontage if non-zero, make montages of networks +% [orthview axis] Optional; integer for which orthviews axis to use, 1:kk +% +% Output: Cell of colors, in order, for use in other functions +% +% tor wager +% Colors update, minor improvements, Jan 2010 +% +% Examples: +%classes = c.ClusterSolution.classes; +%overlay = EXPT.overlay; +% cluster_orthviews_classes(cl,c.ClusterSolution.classes, EXPT.overlay, 'saggital', 0); +% cluster_orthviews_classes(cl,c.ClusterSolution.classes, EXPT.overlay, 'axial', 0); +% cluster_orthviews_classes(cl,c.ClusterSolution.classes, EXPT.overlay, 'coronal', 0); +% cluster_orthviews_classes(cl,c.ClusterSolution.classes, EXPT.overlay, [], 0); + +whichorth = 1; +if ~isempty(varargin), whichorth = varargin{1}; end + +if length(varargin) > 1 + colors2 = varargin{2}; + disp('Using input colors: '); %colors2{:} +else + % text colors no longer needed -- can handle string colors + %colors = {'yo' 'bo' 'go' 'ro' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + disp('Using default colors. '); + colors2 = {[1 1 0] [0 0 1] [0 1 0] [1 0 0] [1 .5 0] [0 1 1] [1 0 1] [.5 1 0] [0 .5 1] [0 1 .5] [1 0 .5]}; +end + +while length(colors2) < max(classes) + colors2tmp = colors2; + for jj = 1:length(colors2tmp) + colors2tmp{jj}(colors2tmp{jj} > .8) = colors2tmp{jj}(colors2tmp{jj} > .8) - .2; + colors2tmp{jj}(colors2tmp{jj} < .2) = colors2tmp{jj}(colors2tmp{jj} < .2) + .2; + end + colors2 = [colors2 colors2tmp]; +end +colors2 = colors2(1:max(classes)); + +% orthviews +cluster_orthviews(cl(classes==1),colors2(1),'overlay',overlay,'solid'); + +for i = 2:max(classes) + cluster_orthviews(cl(classes==i),colors2(i),'add','solid', 'handle', whichorth); +end + +if ~isempty(myview) && ischar(myview) + cluster_orthviews_showcenters(cl,myview,overlay,0); +end + +if domontage + +% montage: build function call +str = ['montage_clusters(overlay,']; +for i = 1:max(classes) + clc{i} = cl(classes==i); + str = [str 'clc{' num2str(i) '},']; +end +str = [str '{']; +for i = 1:max(classes) + %str = [str '''' colors2{i} '''']; % no longer need solid colors + str = [str '[' num2str(colors2{i}) '] ']; + %if i ~= max(classes), str = [str ' ']; end +end +str = [str '}, ''nooverlap'');']; +eval(str) +end + +return + diff --git a/Visualization_functions/cluster_orthviews_montage.m b/Visualization_functions/cluster_orthviews_montage.m new file mode 100644 index 00000000..329f580d --- /dev/null +++ b/Visualization_functions/cluster_orthviews_montage.m @@ -0,0 +1,338 @@ +% [slices_fig_h, slice_mm_coords, slice_vox_coords, axis_handles] = cluster_orthviews_montage(spacing, myview, [overlay], [other optional args]) +% +% Runs on top of spm_orthviews, creates montages from current orthviews +% display, whatever it is +% +% Usage: +% cluster_orthviews_montage(6, 'coronal'); % 6 mm spacing +% cluster_orthviews_montage(10, 'sagittal', 'range', [-10 10]); % 10 mm spacing sag view with only parasagittal slices +% cluster_orthviews_montage(12, 'axial'); % 12 mm spacing, axial view +% +% additional options: enter AFTER overlay: +% 'whichorth', whichorth = varargin{i+1}; varargin{i:i+1} = []; +% 'onerow', doonerow = 1; varargin{i} = []; +% 'range', followed by [min max] mm coords for slices +% 'xhairs', xhairs = 1; turn on cross-hairs on slice plot +% +% tor wager, aug 2006; updated (minor) April 2011. Update: Aug 2012 - +% changed default slices to match canlab_results_fmridisplay +% +% used in cluster_orthviews_classes + +function [slices_fig_h, slice_mm_coords, slice_vox_coords, newax] = cluster_orthviews_montage(spacing, myview, varargin) + +overlay = []; +xhairs = 0; +doonerow = 0; +whichorth = []; + +if length(varargin) > 0 + overlay = varargin{1}; varargin{1} = []; +end + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'whichorth', whichorth = varargin{i+1}; varargin{i:i+1} = []; + case 'onerow', doonerow = 1; varargin{i} = []; + + case 'range' + srange = varargin{i+1}; + if length(srange) ~= 2, error('Range must be [min max] mm coords'); end + + case 'xhairs', xhairs = 1; + + otherwise, warning('scn_tools:badInput', ['Unknown input string option:' varargin{i}]); + end + end +end + +if isempty(overlay), overlay = which('SPM8_colin27T1_seg.img'); end %which('scalped_single_subj_T1.img'); end + +% set up orthviews to be OK +try % will crash if no blobs + scn_export_spm_window('setup', overlay); +catch + disp('Error setting up orthviews window. Running with no blobs?'); +end +% spm_orthviews_white_background + +% set(get(findobj('Tag','Graphics'), 'Children'), 'Color', 'white'); +set(findobj('Type', 'axes', 'Parent', findobj('Tag','Graphics')), 'Color', 'black'); +if xhairs, spm_orthviews('Xhairs', 'on'); end + +% Set window to be equal/constant +[volInfo, dat] = iimg_read_img(overlay, 2); +dat = dat(volInfo.wh_inmask); +spm_orthviews('Window', whichorth, [prctile(dat, 2) prctile(dat, 98)]); + +myviews = {'axial' 'coronal' 'sagittal'}; % for selecting SPM window +whview = find(strcmp(myviews, myview)); +if isempty(whview), error('myview must be axial, coronal, or sagittal.'); end + + +switch whview + case 1 % axial + if ~exist('srange', 'var'), srange = [-40 50]; end + + cen = [srange(1):spacing:srange(2)]'; + slice_mm_coords = cen; + cen = [zeros(length(cen), 2) cen]; + xyzvox = mm2voxel(cen, volInfo.mat); + slice_vox_coords = xyzvox(:, 3); + case 2 + if ~exist('srange', 'var'), srange = [-90 55]; end + + cen = [srange(1):spacing:srange(2)]'; + slice_mm_coords = cen; + cen = [zeros(length(cen),1) cen zeros(length(cen),1)]; + xyzvox = mm2voxel(cen, volInfo.mat); + slice_vox_coords = xyzvox(:, 2); + case 3 + if ~exist('srange', 'var'), srange = [-20 20]; end + + cen = [srange(1):spacing:srange(2)]'; + slice_mm_coords = cen; + cen = [ cen zeros(length(cen), 2)]; + xyzvox = mm2voxel(cen, volInfo.mat); + slice_vox_coords = xyzvox(:, 1); +end + + + + +myviews2 = {'sagittal' 'coronal' 'axial' }; % for selecting coord +whcoord = strmatch(myview, myviews2) ; + +% get text string base +mystr = {'x = ' 'y = ' 'z = '}; +textbase = mystr{whcoord}; + +% get optimal number of axes +num_axes = size(cen, 1); +rc = ceil(sqrt(num_axes)); + +if isempty(whichorth) + axh = get_orth_axishandles; +else + axh = get_orth_axishandles_whichorth(whichorth); +end + +axh = axh(whview); + +% Set up figure + +slices_fig_h = create_figure(['montage_' myview]); +set(slices_fig_h, 'Color', 'k'); + +% copy existing colormap +fh = findobj('Tag', 'Graphics'); +if strcmp(get(fh, 'Type'), 'figure') && ishandle(fh) + set(slices_fig_h, 'colormap', get(fh, 'colormap')) +end + +% Resize figure based on view +ss = get(0, 'ScreenSize'); +if doonerow + switch myview + case {'axial'} + set(slices_fig_h, 'Position', [round(ss(3)/12) round(ss(4)*.9) round(ss(3)*.9) round(ss(4)/7) ]) + case {'coronal'} + set(slices_fig_h, 'Position', [round(ss(3)/12) round(ss(4)*.5) round(ss(3)*.9) round(ss(4)/7) ]) + case 'sagittal' + set(slices_fig_h, 'Position', [round(ss(3)/12) round(ss(4)*.7) round(ss(3)*.6) round(ss(4)/5.5) ]) + end +else + switch myview + case {'axial'} + %how far right, how far up, how big across, how big up + set(slices_fig_h, 'Position', [round(ss(3)/12) round(ss(4)/12) round(ss(3)*.7) round(ss(4)*.7) ]) + case {'coronal'} + set(slices_fig_h, 'Position', [round(ss(3)/12) round(ss(4)/12) round(ss(3)*.7) round(ss(4)*.7) ]) + case {'sagittal'} + set(slices_fig_h, 'Position', [round(ss(3)/12) round(ss(4)/12) round(ss(3)*.7) round(ss(4)*.7) ]) + end +end + +for i = 1:num_axes + + if doonerow + newax(i) = subplot(1, num_axes, i); + else + newax(i) = subplot(rc, rc, i); + end + + axis off; +end + + +for i = 1:num_axes + spm_orthviews('Reposition', cen(i, :)); + + spm_orthviews_showposition; + + if ~ishandle(axh) + disp('SPM figure was deleted or does not exist, possibly because a copied montage figure was closed? Skipping'); + continue + end + + copyobj(get(axh, 'Children'), newax(i)); + axes(newax(i)); + axis image + + ehan = get(newax(i), 'Children'); + + %DeleteFcn: This prevents deleting windows from clearing SPM orthviews + %windows + set(ehan, 'DeleteFcn', []); + + % Transparent background + wh = strcmp('image', get(ehan, 'Type')); % | strcmp('line', get(ehan, 'Type')); + ehan = ehan(wh); + + % set transparent value for clear axes + myAlphaData = ~(all(get(ehan, 'CData') == 0, 3) | (all(get(ehan, 'CData') == 1, 3))); + + % If we set alphadata to clear for BG and axis color to none, we get clear + % axes + set(ehan, 'AlphaDataMapping', 'scaled', 'AlphaData', myAlphaData) + + + % get rid of green text + h = findobj(newax(i), 'Type', 'text'); + delete(h); + + h = findobj(newax(i), 'Type', 'text'); + if length(h) > 1, h= h(1); end % kludgy fix for multiple matches *** + + % try to set a reasonable font size + pos = get(newax(i), 'Position'); + height = pos(3); + fs = max(10, round(height * 70)); + set(h, 'FontSize', fs) + % set position of text (move down and right) + pos = get(h, 'Position'); + pos(2) = 0; %pos(2) - fs./2; + pos(1) = 0; + set(h, 'Position', pos) + % set text string based on cen + %set(h, 'String', [textbase num2str(cen(i))]); + + % elseif ishandle(h) + % delete(h); + % end +end + +% adjust axes +h = findobj(slices_fig_h, 'Type', 'axes'); +pos = get(h, 'Position'); +pos = cat(1, pos{:}); +% pos2 = get(h, 'OuterPosition'); +% pos2 = cat(1, pos2{:}); + +% % left bottom justify +%pos(:, 1) = pos(:, 1) - min(pos(:, 1)) + .03; + + +if doonerow + % respace based on figure: X start + pos(:, 1) = linspace((.98-(1./length(h))), .02, length(h)); + % X width + pos(:, 3) = 1./length(h); + % % left bottom justify + pos(:, 2) = pos(:, 2) - min(pos(:, 2)) + .05; +else + newrows = [1; 1+find(diff(pos(:,1))>0)]; + spacing = newrows(2)-newrows(1); + for i = 1:numel(newrows) + pos(newrows(i):spacing+newrows(i)-1,1) = linspace((.98-(1./spacing)), .02, spacing); + pos(newrows(i):spacing+newrows(i)-1,2) = .05+(i-1)*(.75/(numel(newrows)-1))*ones(spacing,1); + end + pos(:,3) = 1./spacing; + switch myview + case {'coronal'} + pos(:,4) = pos(:,4)*1.2; + end +end + +for i = 1:length(h) + set(h(i), 'OuterPosition', pos(i, :)); + set(h(i), 'Position', pos(i, :)); +end + +% If we set alphadata to clear for BG and axis color to none, we get clear axes +set(h, 'Color', 'none') + +% try to set a reasonable enlargement factor +% n = 1 + .2 * log(num_axes ./ 2); +% n = max(n, 1); n = min(n, 3); +% enlarge_axes(gcf, 1, n) + +% set background color to print as whatever it is on screen +set(gcf,'InvertHardcopy', 'off'); + +h = findobj(slices_fig_h, 'Type', 'text'); +set(h, 'FontSize', 24) + +end + + + + +function axish = get_orth_axishandles + +% Get figure and axis handles +fh = findobj('Tag', 'Graphics'); +ch = get(fh, 'Children'); +for i= 1:length(ch) + mytype = get(ch(i), 'Type'); + wh(i) = strcmp(mytype, 'axes'); +end +axish = ch(find(wh)); + +if isempty(axish) + disp('SPM figure orthviews do not exist'); + disp('You must clear figures for orthviews with previous copies of orthviews objects') + disp('Before setting up spm orthviews.') + disp('Clearing existing figures...try re-running now.') + + % clear existing windows + figh = findobj('Tag', 'montage_axial'); + if ishandle(figh), clf(figh); end + + figh = findobj('Tag', 'montage_coronal'); + if ishandle(figh), clf(figh); end + + figh = findobj('Tag', 'montage_sagittal'); + if ishandle(figh), clf(figh); end + +end + +% get which axis is which +for i = 1:length(axish) + poss(i, :) = get(axish(i), 'Position'); +end + +% get rid of extra axes we may have created in the 4th quadrant +other_axes = find(any(poss(:, 1) > .45 & poss(:, 2) < .2, 2)); +axish(other_axes) = []; +poss(other_axes, :) = []; + +% sort into order: axial, coronal, saggital +ssum = sum(poss(:, 1:2), 2); +[ssum, ind] = sort(ssum); +axish = axish(ind); + +end + +function axish = get_orth_axishandles_whichorth(whichorth) +% Get the axis handles for the current orthviews +global st +for i = 1:length(st.vols), wh(i) = ~isempty(st.vols{i}); end +wh = find(wh); wh = wh(whichorth); +axish = cat(1, st.vols{wh}.ax{:}); +axish = sort(cat(1, axish(:).ax)); + +end + diff --git a/Visualization_functions/cluster_orthviews_overlap.m b/Visualization_functions/cluster_orthviews_overlap.m new file mode 100644 index 00000000..3651e275 --- /dev/null +++ b/Visualization_functions/cluster_orthviews_overlap.m @@ -0,0 +1,90 @@ +function cluster_orthviews_overlap(mask1, mask2, varargin) + % cluster_orthviews_overlap(mask1, mask2, [colors cell]) + % + % Plot two clusters on the orthviews, and their intersections for + % activations and deactivations in intermediate colors + % + % A good function, but not polished yet. needs debugging for various + % minor issues. + % tor wager, june 2010 + + colors = {[1 0 0] [0 0 1] [1 1 0] [0 1 1]}; + + if length(varargin) > 0 + colors = varargin{1}; + end + +% split into pos and neg +[maskInfo, dat] = iimg_read_img(mask1, 2); +pos = dat; pos(dat < 0) = 0; +neg = dat; neg(dat > 0) = 0; +posmask1 = pos; +negmask1 = neg; + +[maskInfo2] = iimg_read_img(mask2, 2); +dat2 = scn_map_image(mask2, mask1); +dat2 = dat2(:); +% t2 = scn_map_image(mask2, tdat2); +% t2 = t2(:); +% dat2 = dat2 .* t2; + +pos = dat2; pos(dat2 < 0) = 0; +neg = dat2; neg(dat2 > 0) = 0; +posmask2 = pos; +negmask2 = neg; + +% get intersections + +int_dat = iimg_intersection(mask1, mask2, 'posneg'); % intersections +pos12 = int_dat(:, 1); +neg12 = int_dat(:, 4); + +%remove intersections from main mask dat +posmask1(pos12) = 0; +posmask2(pos12) = 0; +negmask1(neg12) = 0; +negmask2(neg12) = 0; + +% get clusters + +poscl1 = iimg_indx2clusters(posmask1, maskInfo); +poscl2 = iimg_indx2clusters(posmask2, maskInfo2); + +negcl1 = iimg_indx2clusters(negmask1, maskInfo); +negcl2 = iimg_indx2clusters(negmask2, maskInfo2); + +poscl12 = iimg_indx2clusters(pos12, maskInfo); +negcl12 = iimg_indx2clusters(neg12, maskInfo); + +% montage + +% pos1 neg1 pos2 neg2 +%colors = {[1 0 0] [0 0 1] [1 1 0] [0 1 1]}; +% reverse +%colors = {[0 0 1] [1 0 0] [0 1 1] [1 1 0]}; + +cluster_orthviews('whitebg'); + +if ~isempty(poscl1), cluster_orthviews(poscl1, colors(1), 'solid', 'add'); end +if ~isempty(negcl1), cluster_orthviews(negcl1, colors(2), 'solid', 'add'); end + +if ~isempty(poscl2), cluster_orthviews(poscl2, colors(3), 'solid', 'add'); end +if ~isempty(negcl2), cluster_orthviews(negcl2, colors(4), 'solid', 'add'); end + +if ~isempty(poscl12), cluster_orthviews(poscl12, {mean([colors{1}; colors{3}])}, 'solid', 'add'); end +if ~isempty(negcl12), cluster_orthviews(negcl12, {mean([colors{2}; colors{4}])}, 'solid', 'add'); end + +cluster_orthviews_montage(6, 'axial'); +h = findobj(gcf,'Type', 'Text'); delete(h); + +cluster_orthviews_montage(6, 'sagittal'); +h = findobj(gcf,'Type', 'Text'); delete(h); + +disp('Positive overlap (blue)'); +disp(' ') +cluster_table(poscl12, 0, 0); +disp(' ') +disp('Negative overlap (yellow)'); +cluster_table(negcl12, 0, 0); +disp(' ') + diff --git a/Visualization_functions/cluster_orthviews_overlap2.m b/Visualization_functions/cluster_orthviews_overlap2.m new file mode 100644 index 00000000..b552ef35 --- /dev/null +++ b/Visualization_functions/cluster_orthviews_overlap2.m @@ -0,0 +1,212 @@ +function cluster_orthviews_overlap2(masks, varargin) +% cluster_orthviews_overlap2(masks, ['colors', colors cell], ['surface'], ['negative'] ) +% +% Plot blobs on the orthviews, and their intersections for +% activations and deactivations in intermediate colors +% Positive effects only!!! +% +% A good function, but not polished yet. needs debugging for various +% minor issues. +% tor wager, june 2010 + +if isa(masks, 'image_vector') + loadfromfile = 0; + n = size(masks.dat, 2); + +elseif isstruct(masks) + % cl structure + loadfromfile = 0; + n = length(masks); + +elseif ischar(masks) + % file names + loadfromfile = 1; + n = size(masks, 1); + +else + error('Inputs must be all cl structs or mask filenames'); +end + +colors = scn_standard_colors(n); + +dosurface = 0; +domontage = 1; +signstr = 'positive'; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'colors', colors = varargin{i+1}; + case 'surface', dosurface = 1; + case 'negative', signstr = 'negative'; + case 'nomontage', domontage = 0; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if loadfromfile + + error('Not implemented yet. Use object-oriented tools to get fmri_data object'); + +% [maskInfo1, dat1] = iimg_read_img(mask1, 2); +% [maskInfo2, dat2] = iimg_read_img(mask2, 2); +% [maskInfo3, dat3] = iimg_read_img(mask3, 2); +% +% % positive only +% dat1 = dat1 > 0; +% dat2 = dat2 > 0; +% dat3 = dat3 > 0; +% +% int_dat = iimg_intersection(mask1, mask2, 'posneg'); % intersections +% pos12 = int_dat(:, 1); +% +% int_dat = iimg_intersection(mask1, mask3, 'posneg'); % intersections +% pos13 = int_dat(:, 1); +% +% int_dat = iimg_intersection(mask2, mask3, 'posneg'); % intersections +% pos23 = int_dat(:, 1); +% +% pos123 = pos12 & pos13; +% +% intersect_all = any([pos12 pos13 pos23 pos123], 2); +% +% %remove higher-order intersections from mask dat +% dat1(intersect_all) = 0; +% dat2(intersect_all) = 0; +% dat3(intersect_all) = 0; +% +% pos12(pos123) = 0; +% pos23(pos123) = 0; +% pos13(pos123) = 0; +% +% % make clusters +% clear cl +% +% cl{1} = iimg_indx2clusters(dat1, maskInfo1); +% cl{2} = iimg_indx2clusters(dat2, maskInfo2); +% cl{3} = iimg_indx2clusters(dat3, maskInfo3); +% +% cl{4} = iimg_indx2clusters(pos12, maskInfo1); +% cl{5} = iimg_indx2clusters(pos13, maskInfo2); +% cl{6} = iimg_indx2clusters(pos23, maskInfo3); +% +% cl{7} = iimg_indx2clusters(pos123, maskInfo1); + +else + % get data from indicators + + dat = masks.dat; + if isa(masks, 'statistic_image') && size(masks.sig, 1) == size(masks.dat, 1) + dat = double(dat) .* double(masks.sig); + end + + if strcmp(signstr, 'positive') + dat = dat > 0; % positive + elseif strcmp(signstr, 'negative') + dat = dat < 0; % negative + end + +end + +disp('Getting overlap colors'); + +u = unique(dat, 'rows'); +u(sum(u, 2) == 0, :) = []; + +if isempty(u) + disp('No valid voxels'); + return +end + +dat_final = dat; + +for i = 1:size(u, 1) + % each unique combo of colors + + mycolors = colors(u(i, :)); + mycolors = cat(1, mycolors{:}); + + [C,IA] = intersect(dat, u(i, :), 'rows'); + + to_add = false(size(dat, 1), 1); + to_add(IA) = 1; + + dat_final(:, end+1) = to_add; + + colors{end+1} = mean(mycolors, 1); + +end + +disp('Getting clusters'); + +for i = 1:size(dat_final, 2) + + cl{i} = iimg_indx2clusters(double(dat_final(:, i)), masks.volInfo); + +end + +disp('Displaying clusters'); + + +cluster_orthviews(); +for i = 1:size(dat_final, 2) + if ~isempty(cl{i}), cluster_orthviews(cl{i}, colors(i), 'add', 'solid'); end +end + +if domontage + + cluster_orthviews_montage(8, 'axial', [], 'onerow'); + h = findobj(gcf,'Type', 'Text'); delete(h); + + cluster_orthviews_montage(8, 'sagittal', [], 'onerow'); + h = findobj(gcf,'Type', 'Text'); delete(h); + +end + + +if dosurface + + create_figure; + s = addbrain('hires left'); set(s, 'FaceColor', [.5 .5 .5], 'FaceAlpha', 1); + + for i = 1:size(dat_final, 2) + cluster_surf(cl{i}, colors(i), 2, s); + end + + scn_export_papersetup; saveas(gcf, 'cluster_overlap2_Lmedial.png'); + view(270, 0); lightFollowView + scn_export_papersetup; saveas(gcf, 'cluster_overlap2_Lateral.png'); + + create_figure('s2'); + s = addbrain('hires right'); set(s, 'FaceColor', [.5 .5 .5], 'FaceAlpha', 1); + + for i = 1:size(dat_final, 2) + cluster_surf(cl{i}, colors(i), 2, s); + end + + lightFollowView + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Rmedial.png'); + view(90, 0); lightFollowView + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Rateral.png'); + + create_figure('s3'); + s = addbrain('limbic'); + s = [s addbrain('brainstem')]; + axis auto + set(s, 'FaceColor', [.5 .5 .5]); + set(s(end-1), 'FaceAlpha', .15); + view(135, 10) + + for i = 1:size(dat_final, 2) + cluster_surf(cl{i}, colors(i), 2, s); + end + + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Lmedial.png'); + +end + +end diff --git a/Visualization_functions/cluster_orthviews_overlap_3colors.m b/Visualization_functions/cluster_orthviews_overlap_3colors.m new file mode 100644 index 00000000..69abb6f7 --- /dev/null +++ b/Visualization_functions/cluster_orthviews_overlap_3colors.m @@ -0,0 +1,227 @@ +function cluster_orthviews_overlap_3colors(mask1, mask2, mask3, varargin) +% cluster_orthviews_overlap_3colors(mask1, mask2, mask3, ['colors', colors cell], ['surface'] ) +% +% Plot three clusters on the orthviews, and their intersections for +% activations and deactivations in intermediate colors +% Positive effects only!!! +% +% A good function, but not polished yet. needs debugging for various +% minor issues. +% +% mask1 mask2 mask3 are either image names (preferred!) or clusters +% structures. for clusters structures, need to add .dim field +% and you need the 2010 object-oriented code in the canlab repository. +% +% tor wager, june 2010 + + +colors = {[0 0 1] [1 0 0] [1 1 0]}; +dosurface = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'betas' + case 'design' + + % functional commands + case 'colors', colors = varargin{i+1}; + case 'surface', dosurface = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if isstruct(mask1) && isstruct(mask2) && isstruct(mask3) + loadfromfile = 0; +elseif ischar(mask1) && ischar(mask2) && ischar(mask3) + loadfromfile = 1; +else + error('Inputs must be all cl structs or mask filenames'); +end + +if loadfromfile + + [maskInfo1, dat1] = iimg_read_img(mask1, 2); + [maskInfo2, dat2] = iimg_read_img(mask2, 2); + [maskInfo3, dat3] = iimg_read_img(mask3, 2); + + % positive only + dat1 = dat1 > 0; + dat2 = dat2 > 0; + dat3 = dat3 > 0; + + int_dat = iimg_intersection(mask1, mask2, 'posneg'); % intersections + pos12 = int_dat(:, 1); + + int_dat = iimg_intersection(mask1, mask3, 'posneg'); % intersections + pos13 = int_dat(:, 1); + + int_dat = iimg_intersection(mask2, mask3, 'posneg'); % intersections + pos23 = int_dat(:, 1); + + pos123 = pos12 & pos13; + + intersect_all = any([pos12 pos13 pos23 pos123], 2); + + %remove higher-order intersections from mask dat + dat1(intersect_all) = 0; + dat2(intersect_all) = 0; + dat3(intersect_all) = 0; + + pos12(pos123) = 0; + pos23(pos123) = 0; + pos13(pos123) = 0; + + % make clusters + clear cl + + cl{1} = iimg_indx2clusters(dat1, maskInfo1); + cl{2} = iimg_indx2clusters(dat2, maskInfo2); + cl{3} = iimg_indx2clusters(dat3, maskInfo3); + + cl{4} = iimg_indx2clusters(pos12, maskInfo1); + cl{5} = iimg_indx2clusters(pos13, maskInfo2); + cl{6} = iimg_indx2clusters(pos23, maskInfo3); + + cl{7} = iimg_indx2clusters(pos123, maskInfo1); + +else + % Work from clusters directly + + % convert to masks + + if ~isfield(mask1, 'dim') || isempty(mask1(1).dim) || ... + ~isfield(mask2, 'dim') || isempty(mask2(1).dim) || ... + ~isfield(mask3, 'dim') || isempty(mask3(1).dim) + + error('Enter .dim (image dimension in voxels) field in all cl structs. e.g., [91 109 91]'); + end + + r = cluster2region(mask1); + m1 = region2imagevec(r); + + r = cluster2region(mask2); + m2 = region2imagevec(r); + + r = cluster2region(mask3); + m3 = region2imagevec(r); + + m1 = replace_empty(m1); + m2 = replace_empty(m2); + m3 = replace_empty(m3); + + dat1 = m1.dat > 0; + dat2 = m2.dat > 0; + dat3 = m3.dat > 0; + + pos123 = all([dat1 dat2 dat3], 2); + pos12 = all([dat1 dat2], 2); + pos23 = all([dat2 dat3], 2); + pos13 = all([dat1 dat3], 2); + + intersect_all = any([pos12 pos13 pos23 pos123], 2); + + %remove higher-order intersections from mask dat + dat1(intersect_all) = 0; + dat2(intersect_all) = 0; + dat3(intersect_all) = 0; + + pos12(pos123) = 0; + pos23(pos123) = 0; + pos13(pos123) = 0; + + % make clusters + clear cl + + cl{1} = iimg_indx2clusters(dat1, m1.volInfo); + cl{2} = iimg_indx2clusters(dat2, m2.volInfo); + cl{3} = iimg_indx2clusters(dat3, m3.volInfo); + + cl{4} = iimg_indx2clusters(pos12, m1.volInfo); + cl{5} = iimg_indx2clusters(pos13, m1.volInfo); + cl{6} = iimg_indx2clusters(pos23, m1.volInfo); + + cl{7} = iimg_indx2clusters(pos123, m1.volInfo); + + + +end + + +% set colors for overlap +% colors{end+1} = {mean([colors{1}; colors{2}])}; +% colors{end+1} = {mean([colors{1}; colors{3}])}; +% colors{end+1} = {mean([colors{2}; colors{3}])}; +% +% colors{end+1} = {mean([colors{1}; colors{2}; colors{3}])}; + +colors(end+1) = {2*mean([colors{1}; colors{2}])}; colors{end}(colors{end} > 1) = 1; +colors(end+1) = {2*mean([colors{1}; colors{3}])}; colors{end}(colors{end} > 1) = 1; +colors(end+1) = {2*mean([colors{2}; colors{3}])}; colors{end}(colors{end} > 1) = 1; + +colors(end+1) = {3*mean([colors{1}; colors{2}; colors{3}])}; + + +cluster_orthviews(); +for i = 1:7 + if ~isempty(cl{i}), cluster_orthviews(cl{i}, colors(i), 'add', 'solid'); end +end + +cluster_orthviews_montage(10, 'axial', [], 'onerow', 'range', [-35 55]); +h = findobj(gcf,'Type', 'Text'); set(h, 'FontSize', 18) %delete(h); + +cluster_orthviews_montage(10, 'sagittal', [], 'onerow', 'range', [-40 40]); +h = findobj(gcf,'Type', 'Text'); set(h, 'FontSize', 18) %delete(h); + + +if dosurface + + create_figure; + s = addbrain('hires left'); set(s, 'FaceColor', [.5 .5 .5], 'FaceAlpha', 1); + for i = 1:7 + cluster_surf(cl{i}, colors(i), 2, s); + end + for i = 4:7 + cluster_surf(cl{i}, colors{i}(1), 2, s); + end + + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Lmedial.png'); + view(270, 0); lightFollowView + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Lateral.png'); + + create_figure('s2'); + s = addbrain('hires right'); set(s, 'FaceColor', [.5 .5 .5], 'FaceAlpha', 1); + for i = 1:3 + cluster_surf(cl{i}, colors(i), 2, s); + end + for i = 4:7 + cluster_surf(cl{i}, colors{i}(1), 2, s); + end + lightFollowView + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Rmedial.png'); + view(90, 0); lightFollowView + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Rateral.png'); + + create_figure('s3'); + s = addbrain('limbic'); + s = [s addbrain('brainstem')]; + axis auto + set(s, 'FaceColor', [.5 .5 .5]); + set(s(end-1), 'FaceAlpha', .15); + view(135, 10) + for i = 1:3 + cluster_surf(cl{i}, colors(i), 2, s); + end + for i = 4:7 + cluster_surf(cl{i}, colors{i}(1), 2, s); + end + scn_export_papersetup; saveas(gcf, 'cluster_overlap_3colors_Lmedial.png'); + + + +end + +end diff --git a/Visualization_functions/cluster_orthviews_showcenters.m b/Visualization_functions/cluster_orthviews_showcenters.m new file mode 100644 index 00000000..bd157bac --- /dev/null +++ b/Visualization_functions/cluster_orthviews_showcenters.m @@ -0,0 +1,159 @@ +% slices_fig_h = cluster_orthviews_showcenters(cl, myview, [overlay], [xhairs], [order slices flag], [background color], [var args]) +% +% cluster_orthviews_showcenters(cl, 'coronal'); +% cluster_orthviews_showcenters(cl, 'sagittal'); +% cluster_orthviews_showcenters(cl, 'axial'); +% +% tor wager, aug 2006 +% used in cluster_orthviews_classes + +function slices_fig_h = cluster_orthviews_showcenters(cl, myview, overlay, xhairs, sliceorder, bgcolor, varargin) + + if nargin < 3 || isempty(overlay), overlay = which('scalped_single_subj_T1.img'); end + if nargin < 4, xhairs = 1; end + if nargin < 5, sliceorder = 0; end + + whichorth = []; + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'whichorth', whichorth = varargin{i+1}; + + otherwise, warning('scn_tools:badInput', ['Unknown input string option:' varargin{i}]); + end + end + end + + % set up orthviews to be OK + scn_export_spm_window('setup', overlay); +% set(get(findobj('Tag','Graphics'), 'Children'), 'Color', 'white'); + set(findobj('Type', 'axes', 'Parent', findobj('Tag','Graphics')), 'Color', 'white'); + if xhairs, spm_orthviews('Xhairs', 'on'); end + + % Set window to be equal/constant + [volInf_tmp, dat] = iimg_read_img(overlay, 2); + dat = dat(volInf_tmp.wh_inmask); + spm_orthviews('Window', whichorth, [prctile(dat, 2) prctile(dat, 98)]); + + myviews = {'axial' 'coronal' 'sagittal'}; % for selecting SPM window + whview = find(strcmp(myviews, myview)); + if isempty(whview), error('myview must be axial, coronal, or sagittal.'); end + + if sliceorder + % order slices from negative to positive along this dimension + myviews2 = {'sagittal' 'coronal' 'axial' }; % for selecting coord + whcoord = strmatch(myview, myviews2) ; + cen = cat(1, cl.mm_center); + [cen, wh] = unique(cen(:, whcoord)); + % remove duplicates and sort + cl = cl(wh); + + % get text string base + mystr = {'x = ' 'y = ' 'z = '}; + textbase = mystr{whcoord}; + end + + + % get optimal number of axes + num_clusters = length(cl); + rc = ceil(sqrt(num_clusters)); + + if isempty(whichorth) + axh = get_orth_axishandles; + else + axh = get_orth_axishandles_whichorth(whichorth); + end + + axh = axh(whview); + + slices_fig_h = figure; %create_figure(myview); + set(slices_fig_h, 'Color', 'k'); + + for i = 1:num_clusters + newax(i) = subplot(rc, rc, i); + axis off; + end + + + for i = 1:num_clusters + %cluster_orthviews(cl{i}, colors2(classes(i)), 'overlay', overlay); + spm_orthviews('Reposition', cl(i).mm_center); + + copyobj(get(axh, 'Children'), newax(i)); + axes(newax(i)); + axis image + + h = findobj(newax(i), 'Type', 'text'); + h= h(1); % kludgy fix for multiple matches *** + + if sliceorder + % try to set a reasonable font size + pos = get(newax(i), 'Position'); + height = pos(3); + fs = round(height * 70); + set(h, 'FontSize', fs) + % set position of text (move down and right) + pos = get(h, 'Position'); + pos(2) = 0; %pos(2) - fs./2; + pos(1) = 0; + set(h, 'Position', pos) + % set text string based on cen + set(h, 'String', [textbase num2str(cen(i))]); + elseif ishandle(h) + delete(h); + end + end + + % try to set a reasonable enlargement factor + n = 1 + .15 * log(num_clusters ./ 2); + n = max(n, 1); n = min(n, 3); + enlarge_axes(gcf, n) + + % set background color to print as whatever it is on screen + set(gcf,'InvertHardcopy', 'off'); +end + + + + +function axish = get_orth_axishandles + + % Get figure and axis handles + fh = findobj('Tag', 'Graphics'); + ch = get(fh, 'Children'); + for i= 1:length(ch) + mytype = get(ch(i), 'Type'); + wh(i) = strcmp(mytype, 'axes'); + end + axish = ch(find(wh)); + + if isempty(axish), error('SPM figure orthviews do not exist'); end + + % get which axis is which + for i = 1:length(axish) + poss(i, :) = get(axish(i), 'Position'); + end + + % get rid of extra axes we may have created in the 4th quadrant + other_axes = find(any(poss(:, 1) > .45 & poss(:, 2) < .2, 2)); + axish(other_axes) = []; + poss(other_axes, :) = []; + + % sort into order: axial, coronal, saggital + ssum = sum(poss(:, 1:2), 2); + [ssum, ind] = sort(ssum); + axish = axish(ind); + +end + +function axish = get_orth_axishandles_whichorth(whichorth) +% Get the axis handles for the current orthviews +global st +for i = 1:length(st.vols), wh(i) = ~isempty(st.vols{i}); end +wh = find(wh); wh = wh(whichorth); +axish = cat(1, st.vols{wh}.ax{:}); +axish = sort(cat(1, axish(:).ax)); + +end + diff --git a/Visualization_functions/cluster_surf.m b/Visualization_functions/cluster_surf.m new file mode 100755 index 00000000..bbf152fd --- /dev/null +++ b/Visualization_functions/cluster_surf.m @@ -0,0 +1,697 @@ +function [p,str] = cluster_surf(varargin) +% [suface_handle,colorchangestring] = cluster_surf(varargin) +% Tor Wager +% surface plot of clusters on a standard brain +% +% INPUTS, in any order: +% --------------------------------------------------------------------- +% - CLUSTERS: clusters structures, as created in tor_extract_rois.m +% +% - COLORS: cell array of colors for each cluster: {[1 0 0] [0 1 0] [0 0 1]} +% if number of colors specified is greater than number of clusters +% structures entered, n+1 and n+2 colors are overlap of 2 and overlap +% of all clusters, respectively. +% +% - SURFACE MAT FILE: file name of mat file containing brain surface vertices and faces +% as created with isosurface. +% +% - SPECIAL SURFACE KEYWORDS: special string: 'bg' 'hipp' (hcmp,thal,amy) +% number of mm to plot from surface (mmdeep) +% +% - Special keywords for sets of surfaces are: +% left, right, bg, limbic, cerebellum, brainstem +% +% - Other keywords: 'left' 'right' 'amygdala' 'thalamus' 'hippocampus' ' +% 'midbrain' 'caudate' 'globus pallidus' 'putamen' 'nucleus accumbens' 'hypothalamus' +% 'cerebellum' +% +% - EXISTING SURFACE HANDLE(S): handles for surface patches, created, +% e.g., with addbrain.m. This lets you be very flexible in the +% surfaces you image onto. +% +% 'colorscale'. This scales colors by Z-scores of voxels if used +% - Uses input color, unless also used with 'heatmap' +% - Z scores should be in ROW vector +% - use with 'normalize' to scale Z-scores between -1 and 1 +% - will also create transparent effects, mixing +% blob color with existing surface color in linear +% proportion to Z-scores +% +% 'heatmap'. Map Z-scores to surface colors +% - Used WITH or instead of 'colorscale' +% - Blobs can have a range of colors +% - Use with REFERENCE RANGE option below to control scale +% - solid colors entered as input will be ignored +% - use with 'colormaps' option below to be flexible +% in which color maps you apply. +% - if 'colorscale' is also used, will produce transparent blobs. +% +% - REFERENCE RANGE : reference Z-scores range, [zmin_act zmax_act +% zmax_negact zmin_negact], e.g., [0 5 -5 0], use only +% with 'heatmap' option +% to get refZ from clusters, try: +% clZ = cat(2,clusters.Z); refZ = [min(clZ(clZ > 0)) max(clZ) min(clZ(clZ < 0)) min(clZ)]; +% 'colormaps' +% - followed by custom [colors x 3] matrices for positive colors +% and negative colors. +% - matlab can create some: e.g., colormap summer, jet, etc. +% others can be created with colormap_tor.m +% +% see also img2surf.m +% +% color [0 1 1] (cyan) is reserved for the overlap color btwn cluster sets. +% +% examples: +% --------------------------------------------------------------------- +% P = 'C:\tor_scripts\3DheadUtility\canonical_brains\surf_single_subj_T1_gray.mat'; +% cluster_surf(tcl,acl,P,10,{[0 1 0] [1 0 0]},'colorscale','heatmap') +% +% or P = h (surface handle) to use current surface in figure, and refZ +% cluster_surf(tcl,acl,h,[3 5 -5 -3],10,{[0 1 0] [1 0 0]},'colorscale','heatmap') +% +% More examples: +% cluster_surf(cl,2,'heatmap'); % brain surface. vertices colored @2 mm +% cluster_surf(cl,2,'bg','heatmap'); % heatmap on basal ganglia +% cluster_surf(cl,5,'left','heatmap'); % heatmap on left surface @5 mm +% cluster_surf(cl,2,'right','heatmap'); +% +% A multi-color, multi-threshold display on the cerebellum +% colors = {[1 1 0] [1 .5 0] [1 .3 .3]}; +% tor_fig; +% sh = cluster_surf(cl{3},colors(3),5,'cerebellum'); +% cluster_surf(cl{2},colors(2),5,sh); +% cluster_surf(cl{1},colors(1),5,sh); +% +% Custom colormaps: +% create_figure('Brain Surface'); cluster_surf(cl, 2, 'heatmap','left'); +% +% poscm = colormap_tor([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow +% negcm = colormap_tor([0 0 1], [0 .3 1]); % light blue to dark blue +% create_figure('Brain Surface'); cluster_surf(cl, 2, 'heatmap', 'colormaps', poscm, negcm, 'left'); +% +% Single-color transparent map (green): +% cluster_surf(cl, 2, {[0 1 0]}, 'colorscale', p3(2), 'normalize'); + +% ------------------------------------------------------------------------- +% * set up input arguments and defaults +% ------------------------------------------------------------------------- +mmdeep = 10; +cscale = 0; +heatm = 0; +viewdeg = [135 30]; +cl = []; + +%P = which('surf_single_subj_T1_gray.mat'); +P = which('surf_spm2_brain.mat'); + +% default color maps +poscm = colormap_tor([.7 0 0], [1 1 0]); +negcm = colormap_tor([0 0 1], [0 .5 .5]); + +actcolors = []; % used with heatmap +donormalize = 0; % used with colorscale + +clind = 1; +for i = 1:length(varargin) + + if isempty(varargin{i}) + % ignore it + elseif isstruct(varargin{i}) || isa(varargin{i}, 'region') + cl{clind} = varargin{i}; clind = clind+1; + + elseif iscell(varargin{i}), mycolors = varargin{i}; + + elseif isstr(varargin{i}) + if strcmp(varargin{i},'colorscale'), cscale = 1; + + elseif strcmp(varargin{i},'normalize'), donormalize = 1; + + elseif strcmp(varargin{i},'heatmap'), heatm = 1; + + elseif strcmp(varargin{i},'colormaps') + disp('Using custom color maps.'); + poscm = varargin{i + 1}; varargin{i + 1} = []; + negcm = varargin{i + 2}; varargin{i + 2} = []; + + elseif strcmp(varargin{i},'left') + P = which('surf_spm2_left.mat'); %which('surf_single_subj_grayL.mat'); + viewdeg = [90 0]; + + elseif strcmp(varargin{i},'right') + P = which('surf_spm2_right.mat'); %which('surf_single_subj_grayR.mat'); + viewdeg = [270 0]; + + elseif strcmp(varargin{i},'hires left') + P = which('surf_spm2_brain_left.mat'); %which('surf_single_subj_grayL.mat'); + viewdeg = [90 0]; + + elseif strcmp(varargin{i},'hires right') + P = which('surf_spm2_brain_right.mat'); %which('surf_single_subj_grayR.mat'); + viewdeg = [270 0]; + + else P = varargin{i}; + end + + elseif all(ishandle(varargin{i})) && all(varargin{i} ~= round(varargin{i})) + disp('Found surface patch handles - plotting on existing surfaces.'); + P = varargin{i}; % handle(s) for existing surface + + % get rid of later calls to other surfaces + wh = find(strcmp(varargin,'left') | strcmp(varargin,'right') | strcmp(varargin,'hires left') | strcmp(varargin,'hires right')); + for jj = 1:length(wh), varargin{wh(jj)} = []; end + + elseif any(ishandle(varargin{i})) && all(varargin{i} ~= round(varargin{i})) + disp('Found existing surface patch handles, but some are invalid. Check handles.'); + error('Exiting') + + elseif length(varargin{i}) > 1, % it's a vector + refZ = varargin{i}; + + else % it's a number, mmdeep + mmdeep = varargin{i}; + + end + +end + +if ~exist('cl', 'var') || isempty(cl) + disp('cluster_surf.m: No clusters to plot. Try addbrain for brain surfaces with no activation.'); + p = []; str = []; + return +end + +if ~exist('mycolors', 'var') + mycolors = scn_standard_colors(length(cl)); +end + +if isempty(P) + disp(['Cannot find: ' P]); + P = spm_get(1,'*mat','Choose brain surface file'); +end + + +disp('cluster_surf') +disp('___________________________________________') +fprintf('\t%3.0f cluster structures entered\n',length(cl)) +disp(' Colors are:') +for i = 1:length(mycolors) + disp([' ' num2str(mycolors{i})]) +end +if length(mycolors) > length(cl) + disp([' overlap color is ' num2str(mycolors{length(cl)+1})]) + ovlc = ['[' num2str(mycolors{length(cl)+1}) ']']; +else + ovlc = '[0 1 1]'; +end + +if length(mycolors) > length(cl)+1 && length(cl) > 2 + disp([' all overlap color is ' num2str(mycolors{length(cl)+2})]) + aovlc = ['[' num2str(mycolors{length(cl)+2}) ']']; +else + aovlc = '[1 1 1]'; +end + +disp([' Surface stored in: ' P]) + +fprintf(' Building XYZ coord list\n'); + +% ------------------------------------------------------------------------- +% * build xyz list +% +% also get cscale values for each coordinate, and alphascale values if both +% heatmap and colorscale options are entered +% ------------------------------------------------------------------------- +for i = 1:length(cl) % each cell is a whole vector of cl, not a single region + + xyz{i} = cat(2,cl{i}.XYZmm)'; + + if cscale || heatm + for j = 1:length(cl{i}) + if size(cl{i}(j).Z,1) > size(cl{i}(j).Z,2) + cl{i}(j).Z = cl{i}(j).Z'; + end + end + Z{i} = cat(2,cl{i}.Z)'; + + % order voxels from lowest to highest, so that peak colors + % appear because they are plotted last + tmp = [xyz{i} Z{i}]; + tmp = sortrows(tmp,4); + xyz{i} = tmp(:,1:3); + Z{i} = tmp(:,4);1 ./mad(abs(Z{i})) + + if cscale + if donormalize + Z{i} = Z{i} ./ max(Z{i}); + end + + if heatm + % treat colorscale as alpha scaling to add transparent blobs + % (preserve existing surface; good for isosurface objects) + + Za = abs(Z{i}); + a = prctile(Za, 85); % midpoint of Z{i} defines transparency 0.5 + b = 1./mad(Za); % multiplier for Z for sigmoid + + sZ = 1 ./ (1 + exp(-b*(Za-a))); + + % fix, if all constant + sZ(isnan(sZ)) = 1; + + alphascale{i} = sZ; + + % this is further adusted based on radius + alphascale{i} = 5 * alphascale{i} ./ mmdeep^3; % should be 3? + %alphascale + + + + end + + else + % if heat map only, set mycolor{1} = [1 1 1] + mycolors{1} = [1 1 1]; + end + end +end + +% ------------------------------------------------------------ +% for heatmap option: get actcolors +% ------------------------------------------------------------- +if heatm + fprintf(' Getting heat-mapped colors\n'); + if exist('refZ') == 1 + actcolors = get_actcolors(Z, refZ, poscm, negcm); + else + actcolors = get_actcolors(Z, [], poscm, negcm); + end +end + + +% ------------------------------------------------------------------------- +% * build function call +% ------------------------------------------------------------------------- +fprintf(' Building color change function call\n'); + +if length(cl) > 2 && exist('aovlc') == 1 + str = ['[c,alld] = getVertexColors(xyz{1},p,mycolors{1},[.5 .5 .5],' num2str(mmdeep) ',''ovlcolor'',' ovlc ',''allcolor'',' aovlc]; +else + str = ['[c,alld] = getVertexColors(xyz{1},p,mycolors{1},[.5 .5 .5],' num2str(mmdeep) ',''ovlcolor'',' ovlc]; +end + +if heatm + str = [str ',''colorscale'',actcolors{1}']; + if cscale + % treat colorscale as alpha scaling to add transparent blobs + str = [str ',''alphascale'',alphascale{1}']; + end +elseif cscale + % cscale alone - treat cscale as color-mapping index for single + % colors in actcolors + str = [str ',''colorscale'',Z{1}']; +end + + +for i = 2:length(cl) + str = [str ',''vert'',xyz{' num2str(i) '},mycolors{' num2str(i) '}']; + if heatm + str = [str ',''colorscale'',actcolors{' num2str(i) '}']; + if cscale + % treat colorscale as alpha scaling to add transparent blobs + str = [str ',''alphascale'',alphascale{' num2str(i) '}']; + end + elseif cscale + % cscale alone - treat cscale as color-mapping index for single + % colors in actcolors + str = [str ',''colorscale'',Z{' num2str(i) '}']; + end +end + +str = [str ');']; + +%p = P(end); +%co = get(p, 'FaceVertexCData'); +if exist('alphascale','var') + alphascale{1} = alphascale{1} * 12; +end +%eval(str) +%set(p, 'FaceVertexCData', co); + +% ------------------------------------------------------------------------- +% * run brain surface +% ------------------------------------------------------------------------- +if ishandle(P) % no input file, use existing handle + fprintf(' Using existing surface image\n'); + fprintf(' Running color change.\n'); + for i = 1:length(P) + p = P(i); + disp([' eval: ' str]) + eval(str) + end +else + % we have either an input file or a special string ('bg') + fprintf(' Loading surface image\n'); + [dtmp,ftmp,etmp]=fileparts(P); + + if strcmp(etmp,'.mat') + + load(P); + + %%figure + p = patch('Faces',faces,'Vertices',vertices,'FaceColor',[.5 .5 .5], ... + 'EdgeColor','none','SpecularStrength',.2,'FaceAlpha',1,'SpecularExponent',200); + lighting gouraud;camlight right + axis image; + lightRestoreSingle(gca); + %myLight = camlight(0,0);set(myLight,'Tag','myLight'); + %set(gcf, 'WindowButtonUpFcn', 'lightFollowView');lightFollowView + + view(viewdeg(1),viewdeg(2)); + drawnow + + + % ------------------------------------------------------------------------- + % * run color change + % ------------------------------------------------------------------------- + fprintf(' Running color change.\n'); + disp([' eval: ' str]) + eval(str); + + + % this for subcortex stuff + elseif strcmp(P,'bg') + p = []; + myp = addbrain('caudate');p = [p myp]; + run_colorchange(myp,str,xyz,mycolors); + + myp = addbrain('globus pallidus');p = [p myp]; + run_colorchange(myp,str,xyz,mycolors); + + myp = addbrain('putamen');p = [p myp]; + run_colorchange(myp,str,xyz,mycolors); + + set(myp,'FaceAlpha',1); + + axis image; axis vis3d; lighting gouraud; lightRestoreSingle(gca) + + + elseif strcmp(P,'limbic') + p = []; + + myp = addbrain('amygdala');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('hypothalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('hippocampus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('thalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('nucleus accumbens');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + + myp = addbrain('left');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + set(myp,'FaceAlpha',1); + + axis image; axis vis3d; lighting gouraud; lightRestoreSingle(gca) + + elseif strcmp(P,'brainstem') + p = []; + + myp = addbrain('thalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('hypothalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('brainstem');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + %myp = addbrain('caudate');p = [p myp]; + %run_colorchange(myp,str,xyz, mycolors, actcolors); + + + view(90,10); axis image; axis vis3d; lighting gouraud; lightRestoreSingle(gca) + + elseif strcmp(P,'subcortex') + p = []; + + myp = addbrain('thalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('hypothalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('brainstem');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('amygdala');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('hippocampus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('thalamus');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('nucleus accumbens');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('caudate');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + myp = addbrain('putamen');p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + % myp = addbrain('globus pallidus');p = [p myp]; %Seems to be missing from canonical folder +% run_colorchange(myp,str,xyz, mycolors, actcolors); + + view(90,10); axis image; axis vis3d; lighting gouraud; lightRestoreSingle(gca) + + elseif strcmp(P,'cerebellum') || strcmp(P,'amygdala') || strcmp(P,'hypothalamus') ... + || strcmp(P,'thalamus') || strcmp(P,'midbrain') || strcmp(P,'caudate') ... + || strcmp(P,'globus pallidus') || strcmp(P,'putamen') || strcmp(P,'nucleus accumbens') ... + || strcmp(P,'hippocampus') + % this uses addbrain and works with any of its keywords + p = []; + + myp = addbrain(P);p = [p myp]; + run_colorchange(myp,str,xyz, mycolors, actcolors); + + view(90,10); axis image; axis vis3d; lighting gouraud; lightRestoreSingle(gca) + + + else + error('Must input mat surf file or img file to convert to surf') + end + +end % if ishandle + +lighting gouraud +lightRestoreSingle(gca); +material dull +axis off +set(gcf,'Color','w') +%scn_export_papersetup(400); % this will mess up movies!! + +disp('Finished!') +disp('___________________________________________') + +return + + + + + + +function actcolor = get_actcolors(datavaluesets, refZ, poscm, negcm) + +% refZ is fixed reference range for colors; should be empty to use +% range of data + +% ------------------------------------------------------------ +% for heatmap option: define color maps - biscale hot/cool +% ------------------------------------------------------------- + +% % % % % color map - hot +% % % % % -------------------------------------------- +% % % % %h1 = (0:1/99:1)'; +% % % % h1 = linspace(.7,1,300)'; +% % % % %h2 = ones(size(h1)); +% % % % h2 = linspace(0,.8,200)'; +% % % % h2 = [h2; linspace(.8,1,100)']; +% % % % +% % % % h3 = zeros(size(h1)); +% % % % h = [h1 h2 h3]; +% % % % %h = [h1 h3 h3; h2 h1 h3; h2 h2 h1]; +% % % % %h(1:75,:) = []; % take only red values to start +% % % % % in new matlab: h = colormap(hot(300)); +% % % % +% % % % % color map - winter +% % % % % -------------------------------------------- +% % % % %h1 = (0:1/249:1)'; +% % % % h1 = linspace(0,.5,250)'; +% % % % %h2 = (1:-1/(249*2):.2)'; +% % % % h2 = linspace(1,.7,250)'; +% % % % h3 = zeros(size(h1)); +% % % % hc = [h3 h1 h2]; + +% log scale : transform +% for i = 1:length(datavaluesets) +% pos = datavaluesets{i} > 0; +% datavaluesets{i}(pos) = log(datavaluesets{i}(pos)); +% +% neg = datavaluesets{i} < 0; +% datavaluesets{i}(neg) = -log(abs(datavaluesets{i}(neg))); +% end + +actcolor = map_data_to_colormap(datavaluesets, poscm, negcm, refZ); + + +% % % % % ------------------------------------------------------------- +% % % % % determine overall z-score range +% % % % % ------------------------------------------------------------- +% % % % +% % % % zrange = cat(2,sz{:}); +% % % % tmp = zrange(zrange > 0); +% % % % tmpc = zrange(zrange < 0); +% % % % +% % % % if ~isempty(tmp) +% % % % zrange = [min(tmp) max(tmp)]; +% % % % if length(varargin) > 0, zrange = varargin{1}(1:2);,end % for reference range, use first 2 +% % % % %zh = zrange(1):(zrange(2)-zrange(1))./224:zrange(2); +% % % % +% % % % %zh = linspace(zrange(1),zrange(2),299); +% % % % zh = linspace(zrange(1)*zrange(1),zrange(2),299); +% % % % zh = round(zh*100); +% % % % +% % % % if isempty(zh), zh = [1 1 0], end % only one element? +% % % % end +% % % % +% % % % if ~isempty(tmpc) +% % % % zrangec = [min(tmpc) max(tmpc)]; +% % % % if length(varargin) > 0, zrangec = varargin{1}(3:4);,end % for reference range, use first 2 +% % % % %zhc = zrangec(1):(zrangec(2)-zrangec(1))./249:zrangec(2); +% % % % zhc = linspace(zrangec(1),zrangec(2),249); +% % % % zhc = round(zhc*100); +% % % % if isempty(zhc), zhc = [0 0 1], end % only one element? +% % % % end +% % % % +% % % % % ------------------------------------------------------------- +% % % % % loop through sets of input coordinates +% % % % % ------------------------------------------------------------- +% % % % % break up coords into list +% % % % % xyz2 = {}; indx = 1; +% % % % % for kk = 1:1000:size(xyz,1) +% % % % % setwh{indx} = (kk:min(size(xyz,1),kk+1000))'; +% % % % % xyz2{indx} = xyz(setwh{indx},:); +% % % % % +% % % % % indx = indx + 1; +% % % % % end +% % % % % +% % % % % +% % % % % for myset = 1:length(sets) +% % % % +% % % % +% % % % +% % % % +% % % % for i = 1:length(sz) +% % % % +% % % % % ------------------------------------------------------------- +% % % % % find color for each xyz +% % % % % ------------------------------------------------------------- +% % % % clear h2,clear wh +% % % % myz = sz{i}; +% % % % +% % % % for j = 1:length(myz) +% % % % if myz(j) >= 0, docool = 0; else, docool = 1;, end +% % % % +% % % % if docool, +% % % % +% % % % tmp = find(round(myz(j)*100) == zhc); +% % % % if isempty(tmp), +% % % % tmp = find((zhc-round(myz(j)*100)).^2 == min((zhc-round(myz(j)*100)).^2)); +% % % % end +% % % % else +% % % % tmp = find(round(myz(j)*100) == zh); +% % % % if isempty(tmp), +% % % % tmp = find((zh-round(myz(j)*100)).^2 == min((zh-round(myz(j)*100)).^2)); +% % % % end +% % % % end +% % % % +% % % % wh(j) = tmp(1); +% % % % +% % % % if docool +% % % % actcolor{i}(j,:) = hc(wh(j),:); +% % % % else +% % % % actcolor{i}(j,:) = h(wh(j),:); +% % % % end +% % % % +% % % % end +% % % % +% % % % end +% % % % + + + +% % % % % ------------------------------------------------------------- +% % % % % color scale bar - we must create by hand +% % % % % ------------------------------------------------------------- +% % % % if length(varargin) == 0 +% % % % try +% % % % +% % % % zrange = cat(2,datavaluesets{:}); +% % % % tmp = zrange(zrange > 0); +% % % % tmpc = zrange(zrange < 0); +% % % % +% % % % if ~isempty(tmp) +% % % % figure('Color','w'); subplot(4,1,1);hold on; +% % % % zh2 = zh./100; +% % % % +% % % % for i = 2:size(h,1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)],[0 1 1 0],h(i,:),'EdgeColor','none');, end +% % % % set(gca,'YTickLabel',''); +% % % % xlabel('Z-score','FontSize',14) +% % % % +% % % % docolbar = 0; +% % % % end +% % % % +% % % % if ~isempty(tmpc) +% % % % figure('Color','w'); subplot(4,1,2); hold on; +% % % % zh2 = zhc./100; +% % % % axis([0 .3 zh2(1) zh2(end)]),hold on +% % % % for i = 1:size(h,1), plot([0 1],[zh2(i) zh2(i)],'Color',hc(i,:));, end +% % % % set(gca,'XTickLabel',''); % ylabel('Z-score') +% % % % h3 = get(gcf,'Position'); +% % % % set(gcf,'Position',[h3(1:2) h3(3)*.3 h3(4)*.5]) +% % % % docolbar = 0; +% % % % end +% % % % +% % % % catch +% % % % figure; disp('Cannot make colorbar. Only one voxel?') +% % % % end +% % % % +% % % % end % if no reference Z +return + + + + +function run_colorchange(myp,str,xyz, mycolors, actcolors) + +set(myp,'FaceAlpha',1); + + +for i = 1:length(myp) + p = myp(i); + + % get original color + origcolor = get(p,'FaceColor'); + + % color change + eval(str); + + % change non-active back to original color + vdat = get(p,'FaceVertexCData'); + wh = find(all(vdat == .5,2)); + vdat(wh,:) = repmat(origcolor,length(wh),1); + set(p,'FaceVertexCData',vdat); + +end +p = myp; +lighting gouraud; lightRestoreSingle(gca); + +return + + + + + diff --git a/Visualization_functions/cluster_surf_batch.m b/Visualization_functions/cluster_surf_batch.m new file mode 100644 index 00000000..4f19f9ab --- /dev/null +++ b/Visualization_functions/cluster_surf_batch.m @@ -0,0 +1,177 @@ +function surf_handles = cluster_surf_batch(varargin) +% surf_handles = cluster_surf_batch(varargin) +% +% +% Uses +% Single-map visualization +% ------------------------------------------------------------------ +% P2 = threshold_imgs('rob_tmap_0001.img',tinv(1-.005,12),15,'pos'); +% cluster_surf_batch(P2); +% +% surf_handles = cluster_surf_batch(cl,{[1 0 0]},cl2); +% +% Two maps with overlap +% ------------------------------------------------------------------ +% surf_handles = cluster_surf_batch(cl,{[1 0 0] [0 1 0] [1 1 0]},cl2); + + +% Input arguments +% cl, then colors, then cl2, in that order + +surf_handles = []; + +if length(varargin) == 0 + disp('Choose thresholded image to get clusters from.') + imgname = spm_get(1); + cl = mask2clusters(imgname); + +elseif isstruct(varargin{1}) + cl = varargin{1}; + +elseif isstr(varargin{1}) + cl = mask2clusters(varargin{1}); + +else + error('You must input either nothing, an image name, or a cluster structure.'); +end + +morecl = {}; +ovlstr = []; +mydistance = 5; +color = {[1 0 0]}; + +for i = 2:length(varargin) + v = varargin{i}; + if iscell(v) + color = v; + elseif isstruct(v) + morecl{end+1} = v; + elseif ischar(v) + ovlstr = v; % could enter any string that goes into cluster_orthviews + elseif length(v) == 1 + mydistance = v; + end +end + + +% if length(varargin) > 1 +% color = varargin{2}; +% end +% +% cl2 = []; +% if length(varargin) > 2 +% cl2 = varargin{3}; +% end +% cl3 = []; +% if length(varargin) > 3 +% cl3 = varargin{4}; +% end +% cl4 = []; +% if length(varargin) > 4 +% cl4 = varargin{5}; +% end +while length(color) < length(morecl)+2, color{end+1} = rand(1,3); end + +if strcmp(ovlstr,'nooverlap'), color = color(1:(length(morecl)+1)); end + +fprintf(1,'Colors:\n'); +for i=1:length(color) + if i <= length(morecl) + 1 + fprintf(1,'Cluster set %3.0f\t%3.2f %3.2f %3.2f\n',i,color{i}); + else + fprintf(1,'Overlap\t%3.2f %3.2f %3.2f\n',color{i}); + end +end +fprintf(1,'\n') + + +if strcmp(ovlstr,'nooverlap') + surfhan = []; + tor_fig; + surfhan = make_surface([],cl,morecl,mydistance,color); +% + [hh1,hh2,hh3,hl,a1,a2,a3] = make_figure_into_orthviews; + view(180,-90); [az,el]=view; h = lightangle(az,el); +% +tor_fig; + sh = make_surface('brainstem',cl,morecl,mydistance,color); + surfhan = [surfhan sh]; + scn_export_papersetup(600) + % saveas(gcf,'brainstem','fig') + % saveas(gcf,'brainstem','png') + + tor_fig; + sh = make_surface('limbic',cl,morecl,mydistance,color); + surfhan = [surfhan sh]; + scn_export_papersetup(600) + % saveas(gcf,'left_limbic','fig') + % saveas(gcf,'left_limbic','png') + + sh = make_surface('left',cl,morecl,mydistance,color); + surfhan = [surfhan sh]; +% + sh = make_surface('right',cl,morecl,mydistance,color); + surfhan = [surfhan sh]; +% +% sh = make_surface('bg',cl,morecl,mydistance,color); +% surfhan = [surfhan sh]; + +surf_handles = surfhan; + +else + + for i = 1:4 + if length(morecl) < i, morecl{i} = []; end + end + + % Run surfaces + + surfhan = cluster_surf(cl,cl2,mydistance,color(1:3)); + if(~isempty(cl3) && ~isempty(cl4)), surfhan = cluster_surf(surfhan, cl3,cl4,mydistance,color(4:6));end + set(gcf,'Color','w'); axis off; camzoom(1.3) + + [hh1,hh2,hh3,hl,a1,a2,a3] = make_figure_into_orthviews; + view(180,-90); [az,el]=view; h = lightangle(az,el); + + sh2 = cluster_surf(cl,cl2,mydistance,'left',color(1:3)); + if(~isempty(cl3) && ~isempty(cl4)), sh2 = cluster_surf(sh2, cl3,cl4,mydistance,color(4:6));end + [az,el]=view; h = lightangle(az,el);set(gcf,'Color','w'); axis off; + + sh3 = cluster_surf(cl,cl2,mydistance,'right',color(1:3)); + if(~isempty(cl3) && ~isempty(cl4)), sh3 = cluster_surf(sh3, cl3,cl4,mydistance,color(4:6));end + [az,el]=view; h = lightangle(az,el);set(gcf,'Color','w'); axis off; + + sh4 = cluster_surf(cl,cl2,mydistance,'bg',color(1:3)); + if(~isempty(cl3) && ~isempty(cl4)), sh4 = cluster_surf(sh4, cl3,cl4,mydistance,color(4:6));end + lightRestoreSingle(gca); + [az,el]=view; h = lightangle(az,el);set(gcf,'Color','w'); axis off; + + surf_handles = [surfhan sh2 sh3 sh4]; + +end + + +return + + + + + + function surfhan = make_surface(typestr,cl,morecl,mydistance,color) + + if isempty(typestr), typestr = mydistance; end % kludgy fix to avoid empty input + + surfhan = []; %cluster_surf(cl,mydistance,color{1}); + for i = length(morecl):-1:1 + if i == length(morecl) + surfhan = cluster_surf(surfhan,morecl{i},mydistance,color(i+1),typestr); + else + cluster_surf(surfhan,morecl{i},mydistance,color(i+1)); + end + %surfhan = [surfhan sh]; + end + + surfhan = cluster_surf(surfhan,cl,mydistance,color(1)); + %surfhan = [surfhan sh]; + + return \ No newline at end of file diff --git a/Visualization_functions/cluster_surf_batch2.m b/Visualization_functions/cluster_surf_batch2.m new file mode 100644 index 00000000..787e3496 --- /dev/null +++ b/Visualization_functions/cluster_surf_batch2.m @@ -0,0 +1,129 @@ +% surf_handles = cluster_surf_batch(varargin) +% +% +% Uses +% Single-map visualization +% ------------------------------------------------------------------ +% P2 = threshold_imgs('rob_tmap_0001.img', tinv(1-.005, 12), 15, 'pos'); +% cluster_surf_batch(P2); +% +% surf_handles = cluster_surf_batch({cl cl2}, {[1 0 0]}); +% +% Two maps with overlap +% ------------------------------------------------------------------ +% surf_handles = cluster_surf_batch(cl, {[1 0 0] [0 1 0] [1 1 0]}, cl2); + + +function surf_handles = cluster_surf_batch2(varargin) + OVERLAP_COLOR = [1 1 1]; + DEFAULT_DISTANCE = 5; + + dooverlap = 0; + overlap_color = OVERLAP_COLOR; + regions = {[], 'brainstem', 'limbic', 'left', 'right'}; + + for i=1:length(varargin) + if(ischar(varargin{i})) + switch(varargin{i}) + case 'clusters' + cluster_input = varargin{i+1}; + if(iscell(cluster_input) && isstruct(cluster_input{1})) + cl_groups = cluster_input; + elseif ischar(cluster_input) + cl_groups = mask2clusters(cluster_input); + else + error('You must input either an image name, or a cell array containing cluster structure arrays in each cell for the clusters.'); + end + case 'colors' + colors = varargin{i+1}; + case 'overlap' + dooverlap = varargin{i+1}; + case {'overlap color', 'overlapcolor'} + overlap_color = varargin{i+1}; + case 'regions' + regions = varargin{i+1}; + end + end + end + + if(~exist('cl_groups', 'var') || isempty(cl_groups)) + disp('Choose thresholded image to get clusters from.') + imgname = spm_get(1); + cl_groups = {mask2clusters(imgname)}; + end + + if(~exist('colors', 'var') || isempty(colors)) + colors = {[1 1 0] [1 .5 0] [1 .2 0] [0 0 1] [0 .2 1] [0 .5 1]}; + end + if(length(colors) < length(cl_groups)) + for i=length(colors):length(cl_groups) + colors{i} = rand(1,3); + end + end + + local_print_colors(colors, cl_groups, dooverlap, overlap_color); + + surf_handles = []; + for i=1:length(regions) + surf_handles(end+1) = local_display_cluster_surf(regions{i}, cl_groups, colors, dooverlap, overlap_color, DEFAULT_DISTANCE); + + if(isempty(regions{i})) + camzoom(1.3); + make_figure_into_orthviews(); + view(180, -90); + else + switch(regions{i}) + case 'bg' + lightRestoreSingle(gca); + case 'brainstem' + scn_export_papersetup(600); + case 'limbic' + scn_export_papersetup(600); + end + end + [az, el]=view; + h = lightangle(az, el); + end +end + + + + +%--------------------------- +% Local functions +%--------------------------- +function hSurfFig = local_display_cluster_surf(region, cl_groups, colors, dooverlap, overlap_color, mm_depth) + if(dooverlap) + hSurfFig = cluster_surf(cl_groups{1}, cl_groups{2}, mm_depth, region, [colors(1:2) overlap_color]); + + if(~isempty(cl_groups{3}) && isempty(cl_groups{4})) + hSurfFig = cluster_surf(hSurfFig, cl_groups{3}, mm_depth, colors(3)); + elseif(~isempty(cl_groups{3}) && ~isempty(cl_groups{4})) + hSurfFig = cluster_surf(hSurfFig, cl_groups{3}, cl_groups{4}, mm_depth, [colors(3:4) overlap_color]); + end + + if(~isempty(cl_groups{5}) && isempty(cl_groups{6})) + hSurfFig = cluster_surf(hSurfFig, cl_groups{5}, mm_depth, colors(5)); + elseif(~isempty(cl_groups{5}) && ~isempty(cl_groups{6})) + hSurfFig = cluster_surf(hSurfFig, cl_groups{5}, cl_groups{6}, mm_depth, [colors(5:6) overlap_color]); + end + else + hSurfFig = cluster_surf(cl_groups{1}, mm_depth, region, colors(1)); + for i=2:length(cl_groups) + cluster_surf(hSurfFig, cl_groups{i}, mm_depth, colors(i)); + end + end + + set(gcf, 'Color', 'w'); + axis off; +end + +function local_print_colors(colors, cl_groups, dooverlap, overlap_color) + for i=1:length(cl_groups) + fprintf(1, 'Cluster set %d colors: %3.2f %3.2f %3.2f\n', i, colors{i}); + end + if(dooverlap) + fprintf(1, 'Overlap color: %3.2f %3.2f %3.2f\n', overlap_color); + end + fprintf(1,'\n') +end \ No newline at end of file diff --git a/Visualization_functions/colormap_tor.m b/Visualization_functions/colormap_tor.m new file mode 100644 index 00000000..363bdbb7 --- /dev/null +++ b/Visualization_functions/colormap_tor.m @@ -0,0 +1,69 @@ +function newcm = colormap_tor(lowcolor, hicolor, varargin) + % newcolormap = colormap_tor(lowcolor, hicolor, [midcolor], [midcolor2], etc.) + % + % Create a new colormap of your choosing. + % + % colormap_tor([.2 .2 .6], [1 1 0]); % slate to yellow + % colormap_tor([.9 .5 .2], [1 1 0]); % orange to yellow + % colormap_tor([.8 .1 .1], [1 1 0], [.9 .6 .1]); %red to orange to yellow + % colormap_tor([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow + % + % tor wager, sept. 2007 + + + n = 64; + newcm = NaN .* zeros(n, 3); %initialize + + + if nargin < 3 + % no mid-color + newcm = [linspace(lowcolor(1), hicolor(1), n)' linspace(lowcolor(2), hicolor(2), n)' linspace(lowcolor(3), hicolor(3), n)' ]; + else + % we have more colors + newcm = [linspace(lowcolor(1), varargin{1}(1), n/2)' linspace(lowcolor(2), varargin{1}(2), n/2)' linspace(lowcolor(3),varargin{1}(3), n/2)' ]; + + for i = 1:length(varargin) - 1 + newcm1 = [linspace(varargin{i}(1), varargin{i+1}(1), n/2)' linspace(varargin{i}(2), varargin{i+1}(2), n/2)' linspace(varargin{i}(3),varargin{i+1}(3), n/2)' ]; + newcm = [newcm; newcm1]; + end + + newcm1 = [linspace(varargin{end}(1), hicolor(1), n/2)' linspace(varargin{end}(2), hicolor(2), n/2)' linspace(varargin{end}(3),hicolor(3), n/2)' ]; + newcm = [newcm; newcm1]; + +% % +% % % we have a mid-color +% % newcm1 = [linspace(lowcolor(1), midcolor(1), n/2)' linspace(lowcolor(2), midcolor(2), n/2)' linspace(lowcolor(3), midcolor(3), n/2)' ]; +% % newcm2 = [linspace(midcolor(1), hicolor(1), n/2)' linspace(midcolor(2), hicolor(2), n/2)' linspace(midcolor(3), hicolor(3), n/2)' ]; +% % +% % newcm = [newcm1; newcm2]; + end + +end + +% % +% % % ------------------------------------------------------------ +% % % for heatmap option: define color maps - biscale hot/cool +% % % ------------------------------------------------------------- +% % +% % % color map - hot +% % % -------------------------------------------- +% % %h1 = (0:1/99:1)'; +% % h1 = linspace(.7,1,300)'; +% % %h2 = ones(size(h1)); +% % h2 = linspace(0,.8,200)'; +% % h2 = [h2; linspace(.8,1,100)']; +% % +% % h3 = zeros(size(h1)); +% % h = [h1 h2 h3]; +% % %h = [h1 h3 h3; h2 h1 h3; h2 h2 h1]; +% % %h(1:75,:) = []; % take only red values to start +% % % in new matlab: h = colormap(hot(300)); +% % +% % % color map - winter +% % % -------------------------------------------- +% % %h1 = (0:1/249:1)'; +% % h1 = linspace(0,.5,250)'; +% % %h2 = (1:-1/(249*2):.2)'; +% % h2 = linspace(1,.7,250)'; +% % h3 = zeros(size(h1)); +% % hc = [h3 h1 h2]; diff --git a/Visualization_functions/compare_filtered_t.m b/Visualization_functions/compare_filtered_t.m new file mode 100755 index 00000000..5440433d --- /dev/null +++ b/Visualization_functions/compare_filtered_t.m @@ -0,0 +1,78 @@ +function compare_filtered_t(anatP,varargin) +% function compare_filtered_t(anatP,P1,P2, etc...) +% tor wager +% +% example: +% compare_filtered_t([],'rob_tmap_filtered_0001.img','rob_tmap_filtered_0002.img') +% +% Threshold spm T images and display them together in SPM orthviews +% threshold_spm_t(.005,22,0,'pos') +% compare_filtered_t([],'rfx0009/spmT_filtered_0002.img','rfx0011/spmT_filtered_0002.img', ... +% 'rfx0013/spmT_filtered_0002.img','rfx0015/spmT_filtered_0002.img','rfx0017/spmT_filtered_0002.img') + +if isempty(anatP), + + anatP = which('scalped_single_subj_T1.img');, + if isempty(anatP), anatP = spm_get(1,'img','Choose anatomical overlay image');,end + +end + +anatP = repmat(anatP,length(varargin),1);, + +spm_check_registration(anatP) + +for i = 1:length(varargin) + + V = spm_vol(varargin{i}); + v = spm_read_vols(V); + + vv{i} = v; + + wh = find(abs(v) > 0); + [x,y,z] = ind2sub(size(v),wh); + Z = v(wh); + XYZ = [x y z]'; + spm_orthviews('AddBlobs',i,XYZ,Z,V.mat) + sxyz{i} = XYZ; + sz{i} = Z'; +end + +go = 1; +if go + +if length(varargin) > 1 + vv{1}(isnan(vv{1})) = 0; vv{2}(isnan(vv{2})) = 0; + vvmax = squeeze(sum(sum(abs(vv{1} - vv{2})))); + vvmax = find(vvmax == max(vvmax)); + mm = voxel2mm([0 0 vvmax]',V.mat); mm = mm(3); + + disp(['Maximally different slice btwn imgs 1 and 2 is slice ' num2str(vvmax) ' mm = ' num2str(mm)]) + spm_orthviews('Reposition',[0 0 mm]) + spm_orthviews('Xhairs','off') + + for i = 1:length(varargin) + + + end + V.M = V.mat; + + ZZ = cat(2,sz{:}); [tmp,x] = hist(ZZ,50); + figure('Color','w'), hold on + mycol = {'r' 'b' 'g' 'r--' 'b--' 'g--'}; + + for i= 1:length(sz) + zhist{i} = hist(sz{i},x); + plot(x,zhist{i},mycol{i},'LineWidth',2),hold on; + myleg{i} = varargin{i}; + end + + xlabel('t-score','FontSize',14),ylabel('Frequency','FontSize',14),legend(myleg) + + compare_slice(anatP,sxyz,sz,V,5) + + + + end +end + +return diff --git a/Visualization_functions/compare_slice.m b/Visualization_functions/compare_slice.m new file mode 100755 index 00000000..fb935bfe --- /dev/null +++ b/Visualization_functions/compare_slice.m @@ -0,0 +1,193 @@ +function compare_slice(ovlP,sxyz,sz,VOL,varargin) +% compare_slice(ovlP,sxyz,sz,VOL) +% tor wager, 2/16/03 +% +% ovlP is name of overlay image file (anatomical) +% +% sxzy is a cell array, where each cell is a list of +% VOXEL coordinates in 3 x n matrix +% +% sz is a cell array, where each cell contains the z values +% corresponding to sxyz. This is used to create +% pseudocolor on the plots. +% +% VOL is a structure containing the field M, +% where M is the SPM99-style mapping matrix from voxel to mm +% space. This is used to map sxyz values onto the overlay +% image. +% VOL must also contain field dim, which has the voxel dims +% of the results image for all sxyz. +% All sxyz must have the same dimensions! + +if length(varargin) > 0, ptsize = varargin{1};, else, ptsize = 5;, end + +docolbar = 1; + +% ------------------------------------------------------------- +% * Compare subjects on a slice +% ------------------------------------------------------------- +bgV = spm_vol(ovlP); bgV = bgV(1); +v = spm_read_vols(bgV); +bgV.M = bgV.mat; + + +ss = 1; +while ~isempty(ss) + + ss = input(['Pick a slice (1:' num2str(VOL.dim(3)) ')']); + + if isempty(ss),break,end + + figure('Color','w'); + rc = 1; rc2 = 1; wh = 1; + while rc * rc2 < length(sxyz), %add +1 to include colorbar on this fig + if wh == 1, rc2 = rc2+1;, else, rc = rc+1;, end + wh = ~wh; + end + + ssmm = voxel2mm([0 0 ss]',VOL.M); + sso = mm2voxel(ssmm,bgV); sso = sso(3); + + % ------------------------------------------------------------- + % define color maps - biscale hot/cool + % ------------------------------------------------------------- + + % color map - hot + % -------------------------------------------- + h1 = (0:1/99:1)'; + h2 = ones(size(h1)); + h3 = zeros(size(h1)); + h = [h1 h3 h3; h2 h1 h3; h2 h2 h1]; + h(1:50,:) = []; % take only red values to start + % in new matlab: h = colormap(hot(300)); + + % color map - winter + % -------------------------------------------- + h1 = (0:1/249:1)'; + h2 = (1:-1/(249*2):.5)'; + h3 = zeros(size(h1)); + hc = [h3 h1 h2]; + + % ------------------------------------------------------------- + % determine overall z-score range + % ------------------------------------------------------------- + + zrange = cat(2,sz{:}); + tmp = zrange(zrange > 0); + tmpc = zrange(zrange < 0); + + if ~isempty(tmp) + zrange = [min(tmp) max(tmp)]; + zh = zrange(1):(zrange(2)-zrange(1))./249:zrange(2); + zh = round(zh*100); + end + + if ~isempty(tmpc) + zrangec = [min(tmpc) max(tmpc)]; + zhc = zrangec(1):(zrangec(2)-zrangec(1))./249:zrangec(2); + zhc = round(zhc*100); + end + + % ------------------------------------------------------------- + % loop through sets of input coordinates + % ------------------------------------------------------------- + + for i = 1:length(sxyz) + + % ------------------------------------------------------------- + % create image of the slice + % ------------------------------------------------------------- + subplot(rc,rc2,i), imagesc(v(:,:,sso)'); hold on; colormap gray + set(gca,'YDir','normal'),axis image, axis off + + % ------------------------------------------------------------- + % select xyz coordinates in slice and transform + % ------------------------------------------------------------- + myxyz = sxyz{i}(:,sxyz{i}(3,:) == ss); + myz = sz{i}(sxyz{i}(3,:) == ss); + + myxyzmm = voxel2mm(myxyz,VOL.M); + myxyz = mm2voxel(myxyzmm,bgV,1)'; + + + % ------------------------------------------------------------- + % find color for each xyz and plot + % ------------------------------------------------------------- + clear h2,clear wh + for j = 1:length(myz) + if myz(j) > 0, docool = 0; else, docool = 1;, end + + if docool, + tmp = find(round(myz(j)*100) == zhc); + if isempty(tmp), + tmp = find((zhc-round(myz(j)*100)).^2 == min((zhc-round(myz(j)*100)).^2)); + end + else + tmp = find(round(myz(j)*100) == zh); + if isempty(tmp), + tmp = find((zh-round(myz(j)*100)).^2 == min((zh-round(myz(j)*100)).^2)); + end + end + + wh(j) = tmp(1); + + if docool + h2(j) = plot(myxyz(1,j),myxyz(2,j),'Color',hc(wh(j),:),'MarkerSize',ptsize,'MarkerFaceColor',hc(wh(j),:)); + else + h2(j) = plot(myxyz(1,j),myxyz(2,j),'Color',h(wh(j),:),'MarkerSize',ptsize,'MarkerFaceColor',h(wh(j),:)); + end + + end + if exist('h2') == 1, set(h2,'Marker','square'),end + + drawnow + end + + % ------------------------------------------------------------- + % color scale bar - we must create by hand + % ------------------------------------------------------------- + + %cc = colormap(gray); cc(1:10,:) = repmat([1 1 1],10,1); % [0 0 .3] for dark blue + %colormap(cc) + + if docolbar + % only on the 1st time thru + % weird bug in this; make sep fig + %subplot(rc,rc2,length(sxyz)+1),hold on + + zrange = cat(2,sz{:}); + tmp = zrange(zrange > 0); + tmpc = zrange(zrange < 0); + + if ~isempty(tmp) + figure('Color','w'); hold on; + zh2 = zh./100; + %axis([0 .3 zh2(1) zh2(end)]),hold on + %for i = 1:size(h,1), plot([0 1],[zh2(i) zh2(i)],'Color',h(i,:));, end + %set(gca,'XTickLabel',''); % ylabel('Z-score') + %h3 = get(gcf,'Position'); + %set(gcf,'Position',[h3(1:2) h3(3)*.3 h3(4)*.5]) + + for i = 2:size(h,1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)],[0 1 1 0],h(i,:),'EdgeColor','none');, end + set(gca,'YTickLabel',''); % ylabel('Z-score') + xlabel('Z-score','FontSize',14) + + docolbar = 0; + end + + + if ~isempty(tmpc) + figure('Color','w'); hold on; + zh2 = zhc./100; + axis([0 .3 zh2(1) zh2(end)]),hold on + for i = 1:size(h,1), plot([0 1],[zh2(i) zh2(i)],'Color',hc(i,:));, end + set(gca,'XTickLabel',''); % ylabel('Z-score') + h3 = get(gcf,'Position'); + set(gcf,'Position',[h3(1:2) h3(3)*.3 h3(4)*.5]) + docolbar = 0; + end + + + end % make colorbar figures + +end % end loop \ No newline at end of file diff --git a/Visualization_functions/conf_region.m b/Visualization_functions/conf_region.m new file mode 100755 index 00000000..e3559a37 --- /dev/null +++ b/Visualization_functions/conf_region.m @@ -0,0 +1,145 @@ +function [ax,ci,cen,ub,lb,S,e,lam,Fm,Fc,F,pval, msb] = conf_region(X,varargin) +% [ax,ci,cen,ub,lb,S,e,lam,Fm,Fc,F,pval, msb] = conf_region(X,[doplot]) +% +% alternative conf region multivariate based on Johnson & Wichern, 4th ed., p. 236 +% 2D (3D) +% +% ax: axes of confidence region, scaled to be half-length of confidence hyperellipsoid +% ci: length of axes of conf region (diagonal matrix), axes in reverse order of importance +% as in ouput of eig +% cen: center of region (means of variables) +% ub: upper boundary coordinates for region, rows are dims (vars), cols index coordinates +% last coordinate is on axis of greatest variation ("reverse order"), from output of eig +% lb: lower boundary coordinates +% to plot, try: plot(ub(1,:),ub(2,:),'bo'); hold on; plot(lb(1,:),lb(2,:),'ro') +% S: covariance matrix +% e eigenvectors +% lam eigenvalues +% Fm degrees of freedom multiplier, p(n-1) / n(n-p) +% Fc critical F value based on alpha level +% F F value for test - probably not quite right +% pval p value for test - probably not quite right +% +% To plot: +% Either enter 1 or color as a 2nd argument, or do it yourself: +% +% [ax,ci,cen,ub,lb,S,e,lam] = conf_region(X); +% b = pinv([X(:,1) ones(size(X,1),1)] ) * X(:,2); +% theta = atan(b(1)) - pi/2; +% [h,h2] = plot_ellipse(cen(1),cen(2),theta,ci(1,1),ci(2,2)); +% +% Edit this function for more examples +% There is also an example for rendering individual subject conf regions + +% Example: +% N = 250; +% X = mvnrnd([1 2], [1 .6; .6 1], N); +% [ax,ci,cen,ub,lb,S,e,lam] = conf_region(X); +% b = pinv([X(:,1) ones(size(X,1),1)] ) * X(:,2); +% theta = atan(b(1)) - pi/2; +% %create_figure('test'); +% [h,h2] = plot_ellipse(cen(1),cen(2),theta,ci(1,1),ci(2,2)); +% +% % plot standard deviation - for boostrapping, or for individual cases +% [h,h2] = plot_ellipse(cen(1),cen(2),theta,ci(1,1)*sqrt(N),ci(2,2)*sqrt(N)); + +% Example: Render individual subject conf regions +% X = x(wh, [3 1]); +% dfe = size(X, 1) - size(X, 2); % df +% alph = .1; % .1 for 90%, .05 for 95% +% tcrit = tinv(1 - alph, dfe); +% ci = ((lam) .^ .5) .* tcrit; +% b = pinv([X(:,1) ones(size(X,1),1)] ) * X(:,2); +% theta = atan(b(1)) - pi/2; +% [h,h2] = plot_ellipse(cen(1),cen(2),theta,ci(1,1),ci(2,2)); +% +% set(h, 'Color', 'k', 'LineWidth', 1); +% set(h2, 'FaceColor', [.5 1 0]); + + + +doplot = 0; +if length(varargin) > 0, doplot = varargin{1}; end + +% ----------------------------------------------------- +% * determine multivariate standard deviation matrix S +% ----------------------------------------------------- + +% center +Xs = X - repmat(mean(X),size(X,1),1); + +% covariance matrix S +S = (Xs' * Xs) ./ (size(Xs,1)-1); + +% ----------------------------------------------------- +% * get squared distance +% ----------------------------------------------------- + +% squared distance, Johnson & Wichern p. 201 +% (X-mean(X))' * inv(S) * (X - mean(X)) generalized to matrices +d = Xs * inv(S) * Xs'; +d = diag(d); % what do the off-diagonals signify? + +% ----------------------------------------------------- +% * get critical F and coefficient of mult. for axes +% ----------------------------------------------------- +p = size(X,2); +n = size(X,1); +Fm = (p * (n - 1)) ./ (n * (n - p)); +a = .95; % alpha +Fc = finv(a,p,n - p); % critical F +coef = sqrt(Fm .* Fc); + +[e,lam] = eig(S); + +% ----------------------------------------------------- +% * get F and p-values +% ratio of squared distances (mahal?) between to within +% ----------------------------------------------------- +msb = pdist([mean(X); zeros(1,size(X,2))]); % .^ 2; +msw = sum(d) ./ (n-p); +F = msb / msw; +pval = 1 - fcdf(F,p,n-p); % - this is wrong I think. do nonparam. + +% nonparametric +niter = 1000;dnull = []; +signv = sign(randn(size(X,1),niter)); +for i = 1:niter + xtmp = X .* repmat(signv(:,i),1,size(X,2)); + dnull(i) = pdist([mean(xtmp); zeros(1,size(X,2))]); +end +pval = sum(msb <= dnull) ./ length(dnull); + + +% ----------------------------------------------------- +% * confidence interval, in units - mult by axes. +% ----------------------------------------------------- +ci = ((lam) .^ .5 .* coef); +ax = e * ci; + +cen = mean(X)'; +lb = repmat(cen,1,size(e,2)) - (e * ci); +ub = repmat(cen,1,size(e,2)) + (e * ci); + + +if doplot + % 2-d plot + + % degrees or slope to angle in radians + %ang = corrcoef(X); ang = ang(1,2); + %theta = acos(ang-(pi/2)); + %pi * ang / 180 + + b = pinv([X(:,1) ones(size(X,1),1)] ) * X(:,2); + theta = atan(b(1)) - pi/2; + [h,h2] = plot_ellipse(cen(1),cen(2),theta,ci(1,1),ci(2,2)); + set(h,'Color',[1 1 1],'LineWidth',.5); + + if length(doplot) == 3 , set(h2,'FaceColor',doplot); end + if ischar(doplot), set(h2,'FaceColor',doplot(1)); end + + drawnow +end + + +return \ No newline at end of file diff --git a/Visualization_functions/create_figure.m b/Visualization_functions/create_figure.m new file mode 100644 index 00000000..ce10f6cd --- /dev/null +++ b/Visualization_functions/create_figure.m @@ -0,0 +1,71 @@ +function f1 = create_figure(tagname, varargin) + % f1 = create_figure(['tagname'], [subplotrows], [subplotcols], [do not clear flag]) + % + % checks for old figure with tag of tagname + % clears it if it exists, or creates new one if it doesn't + + if nargin < 1 || isempty(tagname) + tagname = 'nmdsfig'; + end + + doclear = 1; % clear if new or if old and existing + if length(varargin) > 2 && varargin{3} + % use same figure; do not clear + doclear = 0; + end + + old = findobj('Tag', tagname); + old = old( strcmp( get(old, 'Type'), 'figure' ) ); + + if ~isempty(old) + + if length(old) > 1 + % multiple figures with same tag! + close(old(2:end)) + old = old(1); + end + + if doclear, clf(old); end + + f1 = old; + + else + % Or create new + + scnsize = get(0,'ScreenSize'); + + xdim = min(scnsize(3)./2, 700); + ydim = min(scnsize(4)./2, 700); + + f1 = figure('position',round([50 50 xdim ydim]),'color','white'); + set(f1, 'Tag', tagname, 'Name', tagname); + end + + % activate this figure + figure(f1); + + + if doclear % true for new figs or cleared ones + + % Create subplots, if requested; set axis font sizes + + if length(varargin) > 0 + i = max(1, varargin{1}); + j = max(1, varargin{2}); + else + i = 1; + j = 1; + end + + np = max(1, i * j); + + for k = 1:np + axh(k) = subplot(i,j,k); + cla; + set(gca,'FontSize',18),hold on + end + axes(axh(1)); + + end + + return \ No newline at end of file diff --git a/Visualization_functions/errorbar_horizontal.m b/Visualization_functions/errorbar_horizontal.m new file mode 100644 index 00000000..e332d621 --- /dev/null +++ b/Visualization_functions/errorbar_horizontal.m @@ -0,0 +1,382 @@ +function hh = errorbar_horizontal(varargin) +%errorbar_horizontal: Error bar plot. +% errorbar_horizontal(X,Y,L,U) plots the graph of vector X vs. vector Y with +% error bars specified by the vectors L and U. L and U contain the +% lower and upper error ranges for each point in Y. Each error bar +% is L(i) + U(i) long and is drawn a distance of U(i) above and L(i) +% below the points in (X,Y). The vectors X,Y,L and U must all be +% the same length. If X,Y,L and U are matrices then each column +% produces a separate line. +% +% errorbar_horizontal(X,Y,E) or errorbar_horizontal(Y,E) plots Y with error bars [Y-E Y+E]. +% ERRORBAR(...,'LineSpec') uses the color and linestyle specified by +% the string 'LineSpec'. The color is applied to the data line and +% error bars while the linestyle and marker are applied to the data +% line only. See PLOT for possibilities. +% +% errorbar_horizontal(AX,...) plots into AX instead of GCA. +% +% H = errorbar_horizontal(...) returns a vector of errorbarseries handles in H. +% +% For example, +% x = 1:10; +% y = sin(x); +% e = std(y)*ones(size(x)); +% errorbar(x,y,e) +% draws symmetric error bars of unit standard deviation. +% +% NOTE: Tor Wager modified from Matlab's ERRORBAR function to plot +% HORIZONTAL error bars. +% +% EXAMPLE: +% If means(:, 1) is x-values, means(:, 2) is y-values, stds(:, 1) is error +% on x, and stds(:, 2) is error on y, then: +% +% lineh = errorbar_horizontal(means(:, 1), means(:, 2), stds(:, 1)); +% linehx = errorbar(means(:, 1), means(:, 2), stds(:, 2)); + +[v6,args] = usev6plotapi(varargin{:}); +if v6 + warning(['MATLAB:', mfilename, ':DeprecatedV6Argument'],... + ['The ''v6'' argument to %s is deprecated,',... + ' and will no longer be supported in a future release.'], upper(mfilename)); + h = Lerrorbarv6(args{:}); +else + [cax,args,nargs] = axescheck(args{:}); + error(nargchk(1,inf,nargs,'struct')); + [pvpairs,args,nargs,msg] = parseargs(args); + if ~isempty(msg), error(msg); end %#ok + error(nargchk(2,4,nargs,'struct')); + + hasXData = nargs ~= 2; + x = []; + switch nargs + case 2 + [y,u] = deal(args{1:nargs}); + u = abs(u); + l = u; + case 3 + [x,y,u] = deal(args{1:nargs}); + if min(size(x))==1, x = x(:); end + u = abs(u); + l = u; + case 4 + [x,y,l,u] = deal(args{1:nargs}); + if min(size(x))==1, x = x(:); end + end + if min(size(u))==1, u = u(:); end + if min(size(l))==1, l = l(:); end + if min(size(y))==1, y = y(:); end + n = size(y,2); + + % Make sure that x,y,l and u all are the same size: + if isempty(x) + x = ones(size(y)); + end + if ~isequal(size(x),size(y),size(l),size(u)) + error('MATLAB:errorbar:InputSizeMisMatch',... + 'X, Y and error bars must all be the same length'); + end + + % handle vectorized data sources and display names + extrapairs = cell(n,0); + if ~isempty(pvpairs) && (n > 1) + [extrapairs, pvpairs] = vectorizepvpairs(pvpairs,n,... + {'XDataSource','YDataSource',... + 'UDataSource','LDataSource',... + 'DisplayName'}); + end + + if isempty(cax) || isa(handle(cax),'hg.axes') + cax = newplot(cax); + parax = cax; + else + parax = cax; + cax = ancestor(cax,'Axes'); + end + + h = []; + autoColor = ~any(strcmpi('color',pvpairs(1:2:end))); + autoStyle = ~any(strcmpi('linestyle',pvpairs(1:2:end))); + xdata = {}; + for k=1:n + % extract data from vectorizing over columns + if hasXData + xdata = {'XData', double(full(x(:,k)))}; % tor modified to avoid private fcn + end + [ls,c,m] = nextstyle(cax,autoColor,autoStyle,k==1); + + h = [h torDrawHorizErrBars(y, x, l, u, cax)]; + +% % h = [h specgraph.errorbarseries('YData',double(full(y(:,k))),... +% % 'UData',double(full(u(:,k))),... +% % 'LData',double(full(l(:,k))),xdata{:},... +% % 'Color',c,'LineStyle',ls,'Marker',m,... +% % pvpairs{:},extrapairs{k,:},'parent',parax)]; + + end +% if autoColor +% set(h,'CodeGenColorMode','auto'); +% end +% set(h,'RefreshMode','auto'); + plotdoneevent(cax,h); + h = double(h); +end + +if nargout>0, hh = h; end + +function h = Lerrorbarv6(varargin) +% Parse possible Axes input +error(nargchk(2,6,nargin,'struct')); +[cax,args,nargs] = axescheck(varargin{:}); + +x = args{1}; +y = args{2}; +if nargs > 2, l = args{3}; end +if nargs > 3, u = args{4}; end +if nargs > 4, symbol = args{5}; end + +if min(size(x))==1, + npt = length(x); + x = x(:); + y = y(:); + if nargs > 2, + if ~ischar(l) + l = l(:); + end + if nargs > 3 + if ~ischar(u) + u = u(:); + end + end + end +else + npt = size(x,1); +end + +if nargs == 3 + if ~ischar(l) + u = l; + symbol = '-'; + else + symbol = l; + l = y; + u = y; + y = x; + n = size(y,2); + x(:) = (1:npt)'*ones(1,n); + end +end + +if nargs == 4 + if ischar(u), + symbol = u; + u = l; + else + symbol = '-'; + end +end + + +if nargs == 2 + l = y; + u = y; + y = x; + n = size(y,2); + x(:) = (1:npt)'*ones(1,n); + symbol = '-'; +end + +u = abs(u); +l = abs(l); + +if ischar(x) || ischar(y) || ischar(u) || ischar(l) + error(id('NumericInputs'),'Arguments must be numeric.') +end + +if ~isequal(size(x),size(y)) || ~isequal(size(x),size(l)) || ~isequal(size(x),size(u)), + error(id('InputSizeMismatch'),'The sizes of X, Y, L and U must be the same.'); +end + +tee = (max(x(:))-min(x(:)))/100; % make tee .02 x-distance for error bars +xl = x - tee; +xr = x + tee; +ytop = y + u; +ybot = y - l; +n = size(y,2); + +% Plot graph and bars +cax = newplot(cax); +hold_state = ishold(cax); + +% build up nan-separated vector for bars +xb = zeros(npt*9,n); +xb(1:9:end,:) = x; +xb(2:9:end,:) = x; +xb(3:9:end,:) = NaN; +xb(4:9:end,:) = xl; +xb(5:9:end,:) = xr; +xb(6:9:end,:) = NaN; +xb(7:9:end,:) = xl; +xb(8:9:end,:) = xr; +xb(9:9:end,:) = NaN; + +yb = zeros(npt*9,n); +yb(1:9:end,:) = ytop; +yb(2:9:end,:) = ybot; +yb(3:9:end,:) = NaN; +yb(4:9:end,:) = ytop; +yb(5:9:end,:) = ytop; +yb(6:9:end,:) = NaN; +yb(7:9:end,:) = ybot; +yb(8:9:end,:) = ybot; +yb(9:9:end,:) = NaN; + +[ls,col,mark,msg] = colstyle(symbol); +if ~isempty(msg), error(msg); end %#ok +symbol = [ls mark col]; % Use marker only on data part +esymbol = ['-' col]; % Make sure bars are solid + +% ERRORBAR calls the 'v6' version of PLOT, and temporarily modifies global +% state by turning the MATLAB:plot:DeprecatedV6Argument warning off and +% on again. +oldWarn = warning('query','MATLAB:plot:DeprecatedV6Argument'); +warning('off','MATLAB:plot:DeprecatedV6Argument'); +try + h = plot('v6',xb,yb,esymbol,'parent',cax); hold(cax,'on') + h = [h;plot('v6',x,y,symbol,'parent',cax)]; +catch + warning(oldWarn); + rethrow(lasterror); +end +warning(oldWarn); + +if ~hold_state, hold(cax,'off'); end + +function [pvpairs,args,nargs,msg] = parseargs(args) +% separate pv-pairs from opening arguments +[args,pvpairs] = parseparams(args); +% check for LINESPEC +if ~isempty(pvpairs) + [l,c,m,tmsg]=colstyle(pvpairs{1},'plot'); + if isempty(tmsg) + pvpairs = pvpairs(2:end); + if ~isempty(l) + pvpairs = {'LineStyle',l,pvpairs{:}}; + end + if ~isempty(c) + pvpairs = {'Color',c,pvpairs{:}}; + end + if ~isempty(m) + pvpairs = {'Marker',m,pvpairs{:}}; + end + end +end +msg = []; % checkpvpairs(pvpairs); tor disabled; private fcn +nargs = length(args); + +function str=id(str) +str = ['MATLAB:errorbar:' str]; + +function [l,c,m] = nextstyle(ax,autoColor,autoStyle,firsttime) +%NEXTSTYLE Get next plot linespec +% [L,C,M] = NEXTSTYLE(AX) gets the next line style, color +% and marker for plotting from the ColorOrder and LineStyleOrder +% of axes AX. +% +% See also PLOT, HOLD + +% [L,C,M] = NEXTSTYLE(AX,COLOR,STYLE,FIRST) gets the next line +% style and color and increments the color index if COLOR is true +% and the line style index if STYLE is true. If FIRST is true +% then start the cycling from the start of the order unless HOLD +% ALL is active. + +if nargin == 1 + autoColor = true; + autoStyle = true; + firsttime = false; +end + +co = get(ax,'ColorOrder'); +lo = get(ax,'LineStyleOrder'); + +ci = [1 1]; +if (isappdata(ax,'PlotHoldStyle') && getappdata(ax,'PlotHoldStyle')) || ... + ~firsttime + if isappdata(ax,'PlotColorIndex') + ci(1) = getappdata(ax,'PlotColorIndex'); + end + if isappdata(ax,'PlotLineStyleIndex') + ci(2) = getappdata(ax,'PlotLineStyleIndex'); + end +end + +cm = size(co,1); +lm = size(lo,1); + +if isa(lo,'cell') + [l,c,m] = colstyle(lo{mod(ci(2)-1,lm)+1}); +else + [l,c,m] = colstyle(lo(mod(ci(2)-1,lm)+1)); +end +c = co(mod(ci(1)-1,cm)+1,:); + +if autoStyle && (ci(1) == cm) + ci(2) = mod(ci(2),lm) + 1; +end +if autoColor + ci(1) = mod(ci(1),cm) + 1; +end +setappdata(ax,'PlotColorIndex',ci(1)); +setappdata(ax,'PlotLineStyleIndex',ci(2)); + +if isempty(l) && ~isempty(m) + l = 'none'; +end +if ~isempty(l) && isempty(m) + m = 'none'; +end + + +function h = torDrawHorizErrBars(y, x, l, u, cax) + +npt = size(y,1); + +tee = (max(y(:))-min(y(:)))/100; % make tee .02 y-distance for error bars +yl = y - tee; +yr = y + tee; + +xtop = x + u; +xbot = x - l; +n = size(x,2); + + +% build up nan-separated vector for bars +yb = zeros(npt*9,n); +yb(1:9:end,:) = y; +yb(2:9:end,:) = y; +yb(3:9:end,:) = NaN; +yb(4:9:end,:) = yl; +yb(5:9:end,:) = yr; +yb(6:9:end,:) = NaN; +yb(7:9:end,:) = yl; +yb(8:9:end,:) = yr; +yb(9:9:end,:) = NaN; + +xb = zeros(npt*9,n); +xb(1:9:end,:) = xtop; +xb(2:9:end,:) = xbot; +xb(3:9:end,:) = NaN; +xb(4:9:end,:) = xtop; +xb(5:9:end,:) = xtop; +xb(6:9:end,:) = NaN; +xb(7:9:end,:) = xbot; +xb(8:9:end,:) = xbot; +xb(9:9:end,:) = NaN; + +[ls,col,mark,msg] = colstyle('-'); +esymbol = ['-' col]; % Make sure bars are solid + +h = plot(xb,yb,esymbol,'parent',cax); hold(cax,'on') + diff --git a/Visualization_functions/errorbar_width.m b/Visualization_functions/errorbar_width.m new file mode 100644 index 00000000..5d00915f --- /dev/null +++ b/Visualization_functions/errorbar_width.m @@ -0,0 +1,53 @@ +function errorbar_width(h, x, interval) + +% Work with errorbar.m: Adjust the width of errorbar +% +% Usage: +% ------------------------------------------------------------------------- +% errorbar_width(h, x, interval) +% +% Inputs: +% ------------------------------------------------------------------------- +% h errorbar graphic handle +% x vector x, which is used in errorbar +% interval e.g., [-.1 .1] or [0 0] +% +% Examples: you can see this output in +% http://wagerlab.colorado.edu/wiki/doku.php/help/core/figure_gallery +% ------------------------------------------------------------------------- +% +% x = 1:5; % x values +% y = [32 40 55 84 130]; % mean +% e = [6 6 6 6 6]; % standard error of the mean +% +% create_figure(y_axis); +% set(gcf, 'Position', [1 512 268 194]); +% col = [0.3333 0.6588 1.0000]; +% markercol = col-.2; +% +% h = errorbar(x, y, e, 'o', 'color', 'k', 'linewidth', 1.5, 'markersize', 7, 'markerfacecolor', col); +% hold on; +% sepplot(x, y, .75, 'color', col, 'linewidth', 2); +% errorbar_width(h, x, [0 0]); % here +% +% set(gca, 'xlim', [.5 5.5], 'linewidth', 1.5); +% +% try +% pagesetup(gcf); +% saveas(gcf, 'example.pdf'); +% catch +% pagesetup(gcf); +% saveas(gcf, 'example.pdf'); +% end +% +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo + +xdata = []; +for i = x + xdata = [xdata repmat(i,1,2) NaN (repmat(i,1,2) + interval) NaN (repmat(i,1,2) + interval) NaN]; +end + +hh = get(h, 'children'); set(hh(2), 'XData', xdata); + +end diff --git a/Visualization_functions/fill_area_around_points.m b/Visualization_functions/fill_area_around_points.m new file mode 100644 index 00000000..f11ea7d3 --- /dev/null +++ b/Visualization_functions/fill_area_around_points.m @@ -0,0 +1,111 @@ +function h = fill_area_around_points(x, y, borderscale, color) + % h = fill_area_around_points(x, y, borderscale, color) + % + % tor wager, feb 07 + % fills area around list of coordinates in a color + % using spline interpolation and other stuff. + % designed for cluster imaging in nmdsfig figures. + % + % tor recommends .2 for borderscale... +% +% Example: +% x = randn(3,1); y = randn(3,1); +% figure; plot(x,y,'k.'); +% h = fill_area_around_points(x, y, .2, 'r'); + +if length(x) == 1 & length(y) == 1 + % only one point; exit + h = []; + return +end + + % bootstrap means of x and y to get values in area + % --------------------------------------------------- + c = [x y]; + + cm = bootstrp(2000,@mean,c); + + border = borderscale * mean(range(c)); % x% of range + + % add random noise to get coverage + c1 = c(:,1) + border .* (rand(size(c,1),1) - .5); + c2 = c(:,2) + border .* (rand(size(c,1),1) - .5); + c = [c; [c1 c2]]; + + cm = [cm; bootstrp(500,@mean,c)]; + cm = [cm; c]; + + % plot(cm(:,1),cm(:,2),'b.'); + + sampleres = border ./ 5; + r1 = min(cm(:,1)) - 2 * border:sampleres:max(cm(:,1)) + 2 * border; + r2 = min(cm(:,2)) - 2 * border:sampleres:max(cm(:,2)) + 2 * border; + + % make grid + % --------------------------------------------------- + [X,Y] = meshgrid(r1, r2); + + xy = combvec(r1,r2); % across x and then up y + + len = length(cm); + + Z = zeros(size(X)); % values for sig. + + % Find values within border + % --------------------------------------------------- + b2 = border ^ 2; + for i = 1:len % for each point, find xy combos that are within radius + dsquared = (cm(i,1) - X) .^2 + (cm(i,2) - Y) .^2; + Z(dsquared <= b2) = 1; + end + + + + % Plot initial contours (rough) + % --------------------------------------------------- + hold on; + % % [cout,hand] = contourf(X,Y,Z,[0 1]); + % % delete(hand); + % % cout = cout(:, 5:end); + + cout = contourc(r1,r2,Z,1); + cout = cout(:,2:end); + + % % hand = hand(2); + % % set(hand,'FaceColor','y') + + % Reduce and use spline interpolation + % --------------------------------------------------- + desiredpts = 10; + + wh = round(linspace(1,size(cout,2),desiredpts)); % which to save for spline + + cout = cout(:,wh); + +% % skipevery = ceil((size(cout,2) - 4) ./ desiredpts); +% % +% % % % cout = cout(:,all(~isnan(cout))); +% % cout = cout(:,1:skipevery:end); + + % pad + % cool effects but artsy + % cout = [cout(:,[end-3:end]) cout cout(:,1:3)]; cout = [cout cout(:,1)]; + + % pad + cout = [cout(:,end-2:end-1) cout cout(:,[2 3])]; + + + n = size(cout,2); + t = 1:n; + ts = 3:1/desiredpts:n-2; % get rid of padding + xs = spline(t,cout(1,:),ts); + ys = spline(t,cout(2,:),ts); + hold on + hh = plot(xs,ys,'k'); + hold on + + h = fill(xs,ys,color,'FaceAlpha',.2); + h = [h hh]; + + % figure; imagesc(Z); +end diff --git a/Visualization_functions/getVertexColors.m b/Visualization_functions/getVertexColors.m new file mode 100755 index 00000000..433adee4 --- /dev/null +++ b/Visualization_functions/getVertexColors.m @@ -0,0 +1,402 @@ +%function [c, alld] = getVertexColors(xyz, v, actcolor, [basecolor], [mind], 'vert', [xyz2], [actcolor2], 'vert', [xyz3], [actcolor3]) +% +% by Tor Wager August 25, 2002 +% +% given a point list of XYZ mm coordinates (3 columns) +% and a list of vertices in an isosurface, +% returns FaceVertexCData color values for brain near points and brain not near points. +% c is vertex color specification, 3 columns indicating RGB values +% +% Inputs: +% xyz a 3-vol list of vertices to color +% v can be a matrix of vertices +% or a handle to a patch object containing vertices +% if it's a handle, this function sets the color to interp +% and the FaceVertexCData to the color matrix c +% actcolor +% [r g b] activation color +% basecolor +% [r g b] baseline color - optional. +% mind optional - min distance to color vertex +% Vertices within mind of an xyz coordinate will be colored +% colorscale +% optional. followed by vector of values by which to multiply input +% color +% these are scaled to be between .3 and one. +% if entered, this will make the colors vary by, for example, Z score +% so Z-scores are an acceptable input. +% cscale should be in the same coordinate order as xyz +% for ADDITIONAL clusters, repeat the 'colorscale', Z argument pair in the function call +% +% YOU CAN ALSO pass true RGB values for each xyz coordinate in: 'colorscale', rgblist, +% IF cscale is a 3-vector, it specifies the ACTUAL colors, and is not scaled to .3 - 1 +% +% +% following basecolor and mind: +% additional xyz coordinate lists, with syntax: +% 'vert', xyz2 [your xyz input], [r g b] color for xyz plot +% +% also, you can enter 'ovlcolor' followed by [r g b] for overlaps between xyz sets +% colors will ONLY appear in the overlap color if they share actual coordinates in common, +% not necessarily if surface vertices are within the specified distance from both sets of coords. +% +% to get a good brain surface, try this: +%figure +%p = patch('Faces', faces, 'Vertices', vertices, 'FaceColor', [.5 .5 .5], ... +% 'EdgeColor', 'none', 'SpecularStrength', .2, 'FaceAlpha', 1, 'SpecularExponent', 200); +%lighting gouraud;camlight right +%axis image; myLight = camlight(0, 0);set(myLight, 'Tag', 'myLight'); +%set(gcf, 'WindowButtonUpFcn', 'lightFollowView');lightfollowview +%drawnow + +function [c, alld] = getVertexColors(xyz, v, actcolor, varargin) + + mind = 3; + basecolor = [.5 .5 .5]; + alld = []; + xyza = xyz; + cscale = []; + allda = []; + vv = []; + + % ----------------------------------------------------------------------- + % * set up input arguments + % ----------------------------------------------------------------------- + doalph = 0; cscale = []; + if ~isempty(varargin), basecolor = varargin{1}; end + if length(varargin) > 1, mind = varargin{2}; end + ind = 1; + for i = 3:length(varargin) + if strcmp(varargin{i}, 'vert') + vv{ind} = varargin{i+1}; + + % intersections + xyzb{ind, 1} = intersect(xyz, vv{ind}, 'rows'); + for j = ind-1:-1:1 + xyzb{ind, j} = intersect(vv{j}, vv{ind}, 'rows'); + xyza = intersect(xyza, xyzb{ind, j}, 'rows'); + end + + cc{ind} = varargin{i+2}; + ind = ind+1; + elseif strcmp(varargin{i}, 'ovlcolor') + ocol = varargin{i+1}; + elseif strcmp(varargin{i}, 'alphaone') + doalph = 1; + elseif strcmp(varargin{i}, 'allcolor') + acol = varargin{i+1}; + elseif strcmp(varargin{i}, 'colorscale') + cscale{end+1} = varargin{i+1}; + if min(size(cscale{end})) == 1 + % scale colors (may be necessary) - only if single vector, not RGB values + cscale{end} = cscale{end} ./ max(cscale{end}); + end + if any(cscale{end} < 0), error('Some color scale values are less than zero.'), end + + end + end + + if isempty(cscale) + cscale{1} = ones(size(xyz, 1), 1); + for i = 1:length(vv) % additional vertices + cscale{end+1} = ones(size(vv{i}, 1), 1); + end + end + + %if ~isempty(cscale) don't need this. + % if length(cscale) < length(vv) + % cscale{length(vv)} = []; + % end + %end + + + if ishandle(v) + p = v; + v = get(p, 'Vertices'); + c = get(p, 'FaceVertexCData'); % get existing colors from surface + else + % uh-oh, not a handle! + warning('Figure handle missing: Figure was closed?') + p = findobj('Type', 'patch'); + v = get(p(1), 'Vertices'); + c = get(p, 'FaceVertexCData'); % get existing colors from surface + end + + bad = any(size(c) - size(v)); + if bad + c = repmat(basecolor, size(v, 1), 1); + end + + + + + % ----------------------------------------------------------------------- + % * main xyz color change + % ----------------------------------------------------------------------- + + t1 = clock; + fprintf('Main color vertices: ') + + c = change_colors(c, xyz, v, mind, cscale, actcolor, p); + drawnow(); + + + + % ----------------------------------------------------------------------- + % * additional optional vertices + % ----------------------------------------------------------------------- + if exist('vv', 'var') + for j = 1:length(vv) + fprintf('\nAdditional vertices: ') + + % cscale: + % pass in vector of ones length coords for solid color + % or scalar vals for color mapping + % pass in cell array + + % cc{j} is color, cscale{j+1} is scaling vals for coords + c = change_colors(c, vv{j}, v, mind, cscale(j+1), cc{j}, p); + end + drawnow(); + end + + + % ----------------------------------------------------------------------- + % * figure out which vertices should be colored with ocol (overlap color) + % ----------------------------------------------------------------------- + + if exist('ocol', 'var') && exist('xyzb', 'var') && ~isempty(cat(1, xyzb{:})) + + alld = zeros(size(v, 1), 1); % keeps track of overlap vertices + xyzb = cat(1, xyzb{:}); + + t1 = clock; + fprintf('\nOverlap vertices: ') + + cscaletmp = {ones(size(xyzb, 1), 1)}; + c = change_colors(c, xyzb, v, mind, cscaletmp, ocol, p); + drawnow(); + + fprintf('%3.0f.done in %3.0f s\n', i, etime(clock, t1)) + end + + + % ----------------------------------------------------------------------- + % * figure out which vertices should be colored with acol (all color) + % ----------------------------------------------------------------------- + if exist('acol', 'var') && ~isempty(xyza) && length(vv)>1 + + xyzall = xyza; + for i = 2:length(vv) + xyzall = intersect(xyzall, vv{i}, 'rows'); + end + + t1 = clock; + fprintf('\nAll overlap vertices: ') + + cscaletmp = {ones(size(xyzall, 1), 1)}; + c = change_colors(c, xyzall, v, mind, cscaletmp, acol, p); + drawnow(); + + fprintf('%3.0f.done in %3.0f s\n', i, etime(clock, t1)) + + end + + + % ----------------------------------------------------------------------- + % * final color change + % ----------------------------------------------------------------------- + + + if exist('p', 'var') + set(p, 'FaceColor', 'interp') + set(p, 'FaceVertexCData', c) + drawnow() + end + + %lightFollowView +end + + + + + + +% ----------------------------------------------------------------------- +% * SUB-FUNCTIONS +% ----------------------------------------------------------------------- + + + +function c = change_colors(c, coords, v, mind, cscale, actcolor, p) + + if isempty(coords), disp('Coords is empty. Nothing to plot.'), return, end + + % select vertices that are even close + cmax = max(coords, [], 1); + cmin = min(coords, [], 1); + fprintf('%3.0f vertices. selecting: ', size(v, 1)); + wh = any(v - repmat(cmax, size(v, 1), 1) > mind, 2); + wh2 = any(repmat(cmin, size(v, 1), 1) - v > mind, 2); + + % list vertices to test and possibly change color + whverts = (1:size(v, 1))'; + whverts(wh | wh2) = []; % vertex indices in big list + smallv = v; + smallv(wh | wh2,:) = []; % vertices--restricted list + + fprintf('%3.0f\n', size(whverts, 1)); + + if isempty(smallv), return, end + + % select coords that are even close + % ---------------------------------- + cmax = max(smallv, [], 1); + cmin = min(smallv, [], 1); + fprintf('%3.0f coords. selecting: ', size(coords, 1)); + wh = any(coords - repmat(cmax, size(coords, 1), 1) > mind, 2); + wh2 = any(repmat(cmin, size(coords, 1), 1) - coords > mind, 2); + + + % if cscale is matrix, must select these values of cscale as well! + if size(cscale{1}, 1) == size(coords, 1) + cscale{1}(wh | wh2,:) = []; + end + + coords(wh | wh2,:) = []; + + + if isempty(coords), return, end + + nc = size(coords, 1); + fprintf('%3.0f\n', nc); + + % break up coords into list and run + % ---------------------------------- + + % break up coords into list + xyz2 = {}; indx = 1; + for kk = 1:1000:nc + setwh{indx} = (kk:min(nc, kk + 1000 - 1))'; + xyz2{indx} = coords(setwh{indx},:); + + indx = indx + 1; + end + + fprintf('Running %3.0f sets of coordinates: 000', length(xyz2)); + + indxval = 1; + wh_coords_near_surface = false(size(coords, 1), 1); + + for setno = 1:length(xyz2) + fprintf('\b\b\b%03d', setno); + + for i = 1:size(xyz2{setno}, 1) + % find vertices that are within range of point i in set setno + vertex_indices = find_in_radius(xyz2, setno, i, smallv, mind, whverts); + + % two modes: if cscale{1} is a matrix, treats as rgb values, and put in + % color stored in cscale. if cscale{1} is a vector, treat it as a scaling value for actcolor + % In either case, indxval should index location of coordinate in FULL + % list (corresponding to full list in cscale) + + c = color_change_vertices(c, mind, indxval, cscale, actcolor, vertex_indices); + + if ~isempty(vertex_indices), wh_coords_near_surface(indxval) = 1; end + + indxval = indxval + 1; + end + + if exist('p', 'var') + set(p, 'FaceColor', 'interp') + set(p, 'FaceVertexCData', c) + end + end +end + + + + + + +function z = dist_tmp(w, p) + + % + % if isstr(w) + % switch (w) + % case 'deriv', + % z = ''; + % otherwise + % error('Unrecognized code.') + % end + % return + % end + + % CALCULATION + if nargin == 1 + p = w; + w = w'; + end + + [S, R] = size(w); + [R2, Q] = size(p); + if (R ~= R2), error('Inner matrix dimensions do not match.'), end + + z = zeros(S, Q); + if (Q 1 % if we have rgb values rather than scaling values + % this occurs if heatmap = yes and colorscale = no, we pass in rgb values + c(vertex_indices,:) = repmat(cscale{1}(i,:), n, 1); + else + c(vertex_indices,:) = repmat(actcolor.*cscale{1}(i,:), n, 1); + end + end + +end + diff --git a/Visualization_functions/image_histogram.m b/Visualization_functions/image_histogram.m new file mode 100755 index 00000000..46b926d2 --- /dev/null +++ b/Visualization_functions/image_histogram.m @@ -0,0 +1,82 @@ +function h = image_histogram(P,varargin) +% h = image_histogram(P,[method(string)],[range]) +% +% tor wager +% eliminates 0, NaN voxels from either image +% red then blue +% +% P is one or two images in string array +% +% Methods: +% 'def' +% +% range: optional 3rd input, range of values to include in histogram +% +% h = image_histogram('p_Omnibus.img','def',[0 1-eps]); + +V = spm_vol(P); %v = spm_read_vols(V); + +v = iimg_read_vols(V); + + +if length(varargin) > 0, meth = varargin{1}; else, meth = 'def'; end +if length(varargin) > 1, range = varargin{2}; else, range = [-Inf Inf]; end + +for f = 1:size(P,1), [dum,ff{f}] = fileparts(P(f,:)); end + + +%p2 = which('/Users/tor/Documents/tor_scripts/3DheadUtility/canonical_brains/scalped_avg152T1_graymatter.img') +%[p2,p2new] = reslice_imgs(P(1,:),p2,1); +%maskV = spm_read_vols(spm_vol(p2new)); maskV = maskV(:); + +switch meth + case 'def' + + disp('Plotting histograms') + + v1 = squeeze(v(:,:,:,1)); + if size(v,4) > 1 + v2 = squeeze(v(:,:,:,2)); + end + + v1 = v1(:); + if size(v,4) > 1, v2 = v2(:); ,end + + if size(v,4) > 1 + wh = isnan(v1) | isnan(v2) | v1 == 0 | v2 == 0 ... + | v1 < range(1) | v1 > range(2) | v2 < range(1) | v2 > range(2); %| ~maskV; + else + wh = isnan(v1) | v1 == 0 | v1 < range(1) | v1 > range(2); + end + + v1(wh) = []; + if size(v,4) > 1 + v2(wh) = []; + else + v2 = []; + end + + [h,xx] = hist([v1;v2],max(10,length(v1)./300)); + h1 = hist(v1,xx); + + if size(v,4) > 1, h2 = hist(v2,xx); end + + otherwise + error('unknown action string') +end + + + +figure('Color','w'); + +if size(v,4) > 1, plot(xx,h1,'r',xx,h2,'b'); +else + plot(xx,h1,'r'); +end + +set(gca,'FontSize',16) + +legend(ff) + + +return diff --git a/Visualization_functions/line_plot_multisubject.m b/Visualization_functions/line_plot_multisubject.m new file mode 100644 index 00000000..06848e20 --- /dev/null +++ b/Visualization_functions/line_plot_multisubject.m @@ -0,0 +1,157 @@ +function [han, X, Y] = line_plot_multisubject(X, Y, varargin) +% Plots a scatterplot with multi-subject data, with one line per subject +% in a unique color. +% +% [han, X, Y] = line_plot_multisubject(X, Y, varargin) +% +% Inputs: +% % X and Y are cell arrays, one cell per upper level unit (subject) +% +% varargin: +% 'n_bins' = pass in the number of point "bins". Will divide each subj's trials +% into bins, get the avg X and Y per bin, and plot those +% points. +% 'noind' - suppress points +% +% 'subjid' - followed by integer vector of subject ID numbers. Use when +% passing in vectors (with subjects concatenated) rather than cell arrays in X and Y +% +% 'center' - subtract means of each subject before plotting +% 'colors' - followed by array size N of desired colors. if not passed +% in, will use scn_standard_colors +% 'MarkerTypes' - followed by char string. if not passed in, uses +% 'osvd^<>ph' by default +% +% 'group_avg_ref_line' - will make a reference line for the group avg +% +% Outputs: +% han = handles to points and lines +% X, Y = new variables (binned if bins requested) +% +% Examples: +% ------------------------------------------------------------------------- +% for i = 1:20, X{i} = randn(4, 1); Y{i} = X{i} + .3*randn(4, 1) + randn(1); end +% han = line_plot_multisubject(X, Y) +% +% Center within subjects and bin, then calculate correlation of +% within-subject variables: +% create_figure('lines'); [han, Xbin, Ybin] = line_plot_multisubject(stats.Y, stats.yfit, 'n_bins', 7, 'center'); +% corr(cat(1, Xbin{:}), cat(1, Ybin{:})) + +docenter = 0; +doind = 1; +group_avg_ref_line = 0; + +for i=1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'n_bins' + n_bins = varargin{i+1}; + bin_size = length(X{1}) / n_bins; + if rem(length(X{1}), n_bins) ~= 0, error('Num trials must be divisible by num bins'), end; + + case 'subjid' + subjid = varargin{i + 1}; + if iscell(X) || iscell(Y) + error('X and Y should be vectors when using subjid, not cell arrays.'); + end + u = unique(subjid); + for i = 1:length(u) + XX{i} = X(subjid == u(i)); + YY{i} = Y(subjid == u(i)); + end + X = XX; + Y = YY; + + case 'center' + docenter = 1; + case 'colors' + colors = varargin{i+1}; + case 'MarkerTypes' + mtypes = varargin{i+1}; + case 'noind' + doind=0; + case 'group_avg_ref_line' + group_avg_ref_line = 1; + end + end +end + +if ~iscell(X) || ~iscell(Y) + error('X and Y should be cell arrays, one cell per line to plot.'); +end + + +N = length(X); + +if ~exist('colors', 'var'), colors = scn_standard_colors(N); end +if ~exist('mtypes', 'var'), mtypes = 'osvd^<>ph'; end + +hold on + +for i = 1:N + + % choose marker + whm = min(i, mod(i, length(mtypes)) + 1); + + if length(X{i}) == 0 || all(isnan(X{i})) || all(isnan(Y{i})) + % empty + continue + + elseif length(X{i}) ~= length(Y{i}) + error(['Subject ' num2str(i) ' has unequal elements in X{i} and Y{i}. Check data.']) + end + + % centere, if asked for + if docenter + X{i} = scale(X{i}, 1); + Y{i} = scale(Y{i}, 1); + end + + + % plot points in bins + if exist('n_bins', 'var') + if n_bins ~= 0 + points = zeros(n_bins,2); + t = sortrows([X{i} Y{i}],1); + for j=1:n_bins %make the bins + points(j,:) = nanmean(t( bin_size*(j-1)+1 : bin_size*j ,:)); + end + + X{i} = points(:, 1); + Y{i} = points(:, 2); + + %han.point_handles(i) = plot(points(:,1), points(:,2), ['k' mtypes(whm(1))], 'MarkerFaceColor', colors{i}, 'Color', max([0 0 0; colors{i}.*.7])); + end + end + + % plot ref line + b(i,:) = glmfit(X{i}, Y{i}); + + han.line_handles(i) = plot([min(X{i}) max(X{i})], [b(i,1)+b(i,2)*min(X{i}) b(i,1)+b(i,2)*max(X{i})], 'Color', colors{i}, 'LineWidth', 1); + + % plot all the points + if doind + han.point_handles(i) = plot(X{i}, Y{i}, ['k' mtypes(whm(1))], 'MarkerFaceColor', colors{i}, 'Color', max([0 0 0; colors{i}])); + end +end % subject loop + +% get rid of bad handles for missing subjects +if doind + han.point_handles = han.point_handles(ishandle(han.point_handles) & han.point_handles ~= 0); +end +han.line_handles = han.line_handles(ishandle(han.line_handles) & han.line_handles ~= 0); + +%the correlation +%r=corr(cat(1,detrend(X{:},'constant')), cat(1,detrend(Y{:}, 'constant')), 'rows', 'complete') + +% plot the average ref line +if group_avg_ref_line + avg_b = mean(b); + Xs = cat(2,X{:}); + minX = prctile(Xs(1,:),5); + maxX = prctile(Xs(end,:),95); + plot([minX maxX], [b(i,1)+b(i,2)*minX b(i,1)+b(i,2)*maxX], 'Color', 'k', 'LineWidth', 6); +end + +end % function diff --git a/Visualization_functions/lineplot_columns.m b/Visualization_functions/lineplot_columns.m new file mode 100644 index 00000000..41d57cfa --- /dev/null +++ b/Visualization_functions/lineplot_columns.m @@ -0,0 +1,118 @@ +function out = lineplot_columns(dat_matrix, varargin) +% out = lineplot_columns(dat_matrix, varargin) +% +% +% w = 3; % width +% color = 'k'; % color +% wh = true(size(dat_matrix)); % which observations +% x = 1:size(dat_matrix, 2); % x values +% marker = 'o'; % marker style +% linestyle = '-'; % line +% markersize = 8; % markersize +% markerfacecolor = [.5 .5 .5]; % face color +% dowithinste = 0; % enter 'within' to get within-ss ste +% atleast = 1; % 'atleast' followed by n for at least n valid obs to plot +% doshading = 0; % shaded vs. error-bar plots +% +% Inputs: +% dat_matrix is usually a rectangular matrix with rows = observations, +% columns = variables. +% It can also be a cell array with column vectors in each cell, for unequal +% numbers of observations, but then the rows will not be the same +% observations across variables. +% +% Examples: +% out = lineplot_columns(dat_matrix, 'w', 3, 'color', 'r', 'markerfacecolor', [1 .5 0], 'wh', ispain); +% out = lineplot_columns(dat_matrix, 'w', 3, 'color', [0 1 0], 'markerfacecolor', [1 .5 0], 'within'); +% out = lineplot_columns(dat_matrix, 'w', 3, 'color', 'b', 'markerfacecolor', [0 .5 1], 'within'); +% +% shaded error regions +% out = lineplot_columns(hotopen, 'color', 'r', 'marker', 'none', 'w', 1, 'shade'); + +w = 3; % width +color = 'k'; % color +wh = true(size(dat_matrix)); % which observations +x = 1:size(dat_matrix, 2); % x values +marker = 'o'; % marker style +linestyle = '-'; % line +markersize = 8; % markersize +markerfacecolor = [.5 .5 .5]; % face color +dowithinste = 0; % enter 'within' to get within-ss ste +atleast = 1; +doshading = 0; % shaded vs. error-bar plots + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'w', 'color', 'x', 'marker', 'linestyle', 'markersize', 'markerfacecolor', 'wh'} + str = [varargin{i} ' = varargin{i + 1};']; + eval(str) + varargin{i} = []; + varargin{i + 1} = []; + + case {'within', 'dowithinste'} + dowithinste = 1; + + case 'atleast' + atleast = varargin{i + 1}; + + case 'shade' + doshading = 1; + + otherwise + error('Unknown input.'); + end + end +end + +% If cells, pad with NaNs and make matrix +if iscell(dat_matrix) + fprintf('Converting from cell array, padding with NaNs if necessary.\n'); + + Csize = cellfun(@size, dat_matrix, 'UniformOutput', false); + Csize = cat(1, Csize{:}); + [mx, wh] = max(Csize(:, 1)); + + for i = 1:length(dat_matrix) + dat_matrix{i} = padwithnan(dat_matrix{i}, dat_matrix{wh}, 1); + end + + dat_matrix = cat(2, dat_matrix{:}); + +end + +%% + +dat_matrix(~wh) = NaN; + +wh_omit = sum(~isnan(dat_matrix)) < atleast; + +dat_matrix(:, wh_omit) = []; +x(wh_omit) = []; + +if dowithinste + + [out.ste, stats] = barplot_get_within_ste(dat_matrix); + out.ste = repmat(out.ste, 1, size(dat_matrix, 2)); + +else + out.ste = ste(dat_matrix); +end + +out.m = nanmean(dat_matrix); + +hold on; + +if doshading + out.err_han = fill_around_line(out.m, out.ste, color, x); +else + out.err_han = errorbar(x, out.m, out.ste, 'Color', color, 'LineWidth', w); + set(out.err_han, 'LineStyle', 'none') +end + +out.line_han = plot(x, out.m, 'Color', color, 'Marker', marker, 'LineStyle', linestyle, ... + 'MarkerSize', markersize, 'MarkerFaceColor', markerfacecolor, 'LineWidth', w); + +end % function + diff --git a/Visualization_functions/logitScatter.m b/Visualization_functions/logitScatter.m new file mode 100644 index 00000000..22c178be --- /dev/null +++ b/Visualization_functions/logitScatter.m @@ -0,0 +1,55 @@ +function [h]=scatter(x,y,stats,varargin) + +% Usage: [h]=scatter(x,y,stats,varargin) +% +% scatterplots x and y and draws the logistic function specified by the +% parameters in the stats structure (output from glmfit). +% h is the handle to the figure axis. + +Title = []; +Xlabel = []; +Ylabel = []; +fontsize = 18; +xfontsize = 20; +yfontsize = 20; +tfontsize = 24; +param=1; + + + +for k = 1:length(varargin) + if ischar(varargin{k}) + switch(varargin{k}) + case 'Title' + Title=varargin{k+1}; + case 'XLabel' + Xlabel=varargin{k+1}; + case 'Ylabel' + Ylabel=varargin{k+1}; + case 'FontSize' + fontsize=varargin{k+1}; + case 'xFontSize' + xfontsize=varargin{k+1}; + case 'yFontSize' + yfontsize=varargin{k+1}; + case 'tFontSize' + beep,disp('Title Font size control has not yet been implemented') + tfontsize=varargin{k+1}; + case 'param' + param=varargin{k+1}; + end + end +end + + +figure; +h=scatter(x,y,'filled','MarkerEdgeColor','k','MarkerFaceColor','k'); +h=get(h,'Parent'); +xlabel(Xlabel,'FontSize',xfontsize) +ylabel(Ylabel,'FontSize',yfontsize) +hold on +lim=get(h,'XLim'); +x=lim(1):(lim(2)-lim(1))/1000:lim(2); +y=exp(stats.beta(1)+stats.beta(param+1)*x)./(1+exp(stats.beta(1)+stats.beta(param+1)*x)); +plot(x,y,'k') +hold off \ No newline at end of file diff --git a/Visualization_functions/make_figure_into_orthviews.m b/Visualization_functions/make_figure_into_orthviews.m new file mode 100755 index 00000000..adf31622 --- /dev/null +++ b/Visualization_functions/make_figure_into_orthviews.m @@ -0,0 +1,39 @@ +function [hh1,hh2,hh3,hl,a1,a2,a3] = make_figure_into_orthviews +% [hh1,hh2,hh3,hl,a1,a2,a3] = make_figure_into_orthviews +% +% Copies a surface rendering or glass brain into three separate view +% panels, one saggital, one axial, and one coronal +% +% tor wager +% returns handles to objects in each view and hl light handles +% +% example: +% [hh1,hh2,hh3,hl,a1,a2,a3] = make_figure_into_orthviews; +% axes(a1) +% text(-55,60,70,'L','FontSize',24);text(55,60,70,'R','FontSize',24); +% axes(a3) +% text(-55,60,70,'L','FontSize',24);text(55,60,70,'R','FontSize',24); + +fo = gcf; +f = figure('Color','w'); a1=subplot(1,3,1); set(gca,'FontSize',16); +a2=subplot(1,3,2); set(gca,'FontSize',16); +a3=subplot(1,3,3);set(gca,'FontSize',16); + +figure(fo); +o1=get(gca,'Children'); +hh1=copyobj(o1,a1); +hh2=copyobj(o1,a2); +hh3=copyobj(o1,a3); + +axes(a1); , axis image, +axes(a2); view(270,0), axis image +hl = lightangle(270,0); +axes(a3); view(0,0), axis image +hl(2) = lightangle(0,0); + +axes(a1) +text(-55,60,70,'L','FontSize',24);text(55,60,70,'R','FontSize',24); +axes(a3) +text(-55,60,70,'L','FontSize',24);text(55,60,70,'R','FontSize',24); + +return \ No newline at end of file diff --git a/Visualization_functions/makelegend.m b/Visualization_functions/makelegend.m new file mode 100644 index 00000000..7c0a7dbd --- /dev/null +++ b/Visualization_functions/makelegend.m @@ -0,0 +1,76 @@ +function han = makelegend(names,colors,makefig) +% makelegend(names,colors,[decimal places if numeric entries for names]) +% +% names must be cell array of names OR a vector of numbers (i.e., thresholds) that will be converted to +% text +% +% colors can be cell array of text or rgb values, or matrix of [r g b] +% values +% +% Examples: +% han = makelegend({'red' 'green' 'blue'}, {'r' 'g' 'b'}); +% +% han = makelegend([.001 .005 .01], {[1 1 0] [1 .5 0] [.7 .3 .3]}); + + +%if nargin < 3, makefig = 1; end +%if makefig, tor_fig; end + +if nargin < 3, num_decimals = 3; end + +num_entries = length(names); + +create_figure('Legend'); + +if num_entries < 4 +set(gcf, 'Position', [200 200 200 200]); +set(gca, 'FontSize', 36); +else + set(gcf, 'Position', [200 200 200 320]); +set(gca, 'FontSize', 36); +end + +% convert numbers to text +if ~iscell(names) && ~ischar(names) + + myfmt = ['%3.' num2str(num_decimals) 'f']; + + for i = 1:num_entries + names2{i} = sprintf(myfmt, names(i)); + end + + names = names2; +end + + +for i = 1:num_entries + + if iscell(colors) + mycolor = colors{i}; + else + mycolor = colors(i,:); + end + + if isstr(mycolor) + + h(i) = plot(0,0, 'ks', 'MarkerFaceColor',mycolor(1), 'MarkerSize', 48); %'LineWidth',3); + + else + h(i) = plot(0,0, 'ks', 'MarkerFaceColor',mycolor, 'MarkerSize', 48); %'LineWidth',12); + + end +end + +han = legend(h,names); + +if num_entries < 4 +set(han, 'Position', [.25 .35 .45 .45]); +else + set(han, 'Position', [.25 .30 .45 .45]); +end + +axis off + +scn_export_papersetup(200); + +return \ No newline at end of file diff --git a/Visualization_functions/map_data_to_colormap.m b/Visualization_functions/map_data_to_colormap.m new file mode 100644 index 00000000..66481d2d --- /dev/null +++ b/Visualization_functions/map_data_to_colormap.m @@ -0,0 +1,101 @@ +function actcolor = map_data_to_colormap(datavaluesets, poscm, negcm, varargin) + %function actcolor = map_data_to_colormap(datavaluesets, poscm, negcm, varargin) +% + % Given sets of data values (each cell is a row vector of data values, + % e.g., z-scores) and color maps for positive and negative values, + % returns mapped colors for each data value in order. + % These colors can be used for direct plotting. + % + % input 1: data values (e.g., z-scores). k data value sets, in cells. Each cell contains row vector of data values + % input 2/3: color maps [n x 3] for positive and negative values + % input 4: optional: fixed range of data defining max and min colors + % + % Tor Wager, Sept. 2007 + % + % e.g., + % + % poscm = colormap_tor([0 0 0], [1 1 0]); + % negcm = colormap_tor([0 0 1], [0 0 0]); + % Z = randn(40, 1)'; + % actcolors = map_data_to_colormap({Z}, poscm, negcm) + % [Z' actcolors{1}] + + nposcolors = size(poscm, 1); + nnegcolors = size(negcm, 1); + + % ------------------------------------------------------------- + % determine overall data range + % ------------------------------------------------------------- + + % k data value sets, in cells. Each cell contains row vector of data + % values + datavalues = cat(2,datavaluesets{:}); + + % exactly zero values will give wrong length, so make pos. small number + datavalues(datavalues == 0) = 100 * eps; + + posvalues = datavalues(datavalues > 0); + negvalues = datavalues(datavalues < 0); + + if ~isempty(posvalues) + + zrange = [min(posvalues) max(posvalues)]; + + % input fixed data range for max and min colors + if ~isempty(varargin) && ~isempty(varargin{1}) + zrange = varargin{1}(1:2); + + end + + zh = linspace(zrange(1),zrange(2),nposcolors); + + if isempty(zh), zh = [1 1 0]; end % only one element? + end + + if ~isempty(negvalues) + zrangec = [min(negvalues) max(negvalues)]; + + % input fixed data range for max and min colors + if ~isempty(varargin) && ~isempty(varargin{1}) + zrangec = varargin{1}(3:4); + end + + zhc = linspace(zrangec(1),zrangec(2),nnegcolors); + + if isempty(zhc), zhc = [0 0 1]; end % only one element? + end + + % ------------------------------------------------------------- + % find color for each xyz + % ------------------------------------------------------------- + + if sum(isnan(datavalues)) + disp('Warning! NaNs in data values mapped to colors. These will be mapped to black [0 0 0].'); + end + + for k = 1:length(datavaluesets) + datavalues = datavaluesets{k}; + + actcolor{k} = zeros(length(datavalues), 3); + + for i = 1:length(datavalues) + + dv = datavalues(i); + + if dv < 0 + [mydistance, wh] = min(abs(zhc - dv), [], 2); + actcolor{k}(i,:) = negcm(wh, :); + + elseif dv >= 0 + + [mydistance, wh] = min(abs(zh - dv), [], 2); + actcolor{k}(i,:) = poscm(wh, :); + + else + % could be NaN; leave as 0 + + end + end + end + +end diff --git a/Visualization_functions/montage_clusters.m b/Visualization_functions/montage_clusters.m new file mode 100755 index 00000000..5977adc3 --- /dev/null +++ b/Visualization_functions/montage_clusters.m @@ -0,0 +1,722 @@ +% fig_handle = montage_clusters(ovl, clusters, varargin) +% +% varargin (in any order) = +% a) additional clusters structures +% b) cell array of colors (text format), must be ROW vector {'r' 'g'} etc... +% if length of color string is longer than number of clusters inputs, +% additional colors are interpreted as '2-intersection' and 'all-intersection' +% colors, in that order. This overrides single string argument (c, below) for +% color input +% c) single string color argument for overlaps (intersections) +% plots intersections of ANY TWO clusters right now. +% also color for plotting points, if entered. +% use 'nooverlap' as an input argument to suppress this. +% d) [n x 3] matrix of points to plot on map +% e) text labels for points, must be cell array in COLUMN vector +% f) single number, 1/0 for whether to plot overlapping coordinates in overlap colors +% default is 1. +% g) color limit vector [min max] indicates color mapping for blobs +% rather than +% solid colors. This will do hot/cool mapping +% +% Intersections of 2 colors are magenta, and ALL colors are yellow +% unless otherwise specified +% +% by Tor Wager +% Edited: Jan 2010, to add functionality to display underlay image only if +% no clusters are entered (with 9 mm slice spacing) + +function mch = montage_clusters(ovl, clusters, varargin) + % try: CLU = clusters2clu(clusters); + % spm_orthviews('AddColouredBlobs', 1, CLU.XYZ, CLU.Z, CLU.M, [1 0 0]) + + % ---------------------------------------- + % * defaults + % ---------------------------------------- + + if ischar(ovl) + [path, name, ext] = fileparts(ovl); + if(isempty(path)) + ovl = which(ovl); + end + end + + if isempty(ovl), ovl = which('spm2_single_subj_T1_scalped.img'); end + mch = []; + myc = {'r' 'b' 'g' 'c' 'm' 'w'}; + customcolors = 0; + bcolor = 'm'; + acolor = 'y'; + XYZmm_both = []; + clindx = 1; + XYZpts = []; + myplab = []; + cl = []; + plotovl = 1; + doverb = 1; + colorlimits = []; + + % recursive operation if clusters is a cell array + % indicating multiple clusters structures + %if iscell(clusters), + % for i = 1:length(clusters), + % montage_clusters(ovl, clusters{i}, varargin) + % end + % return + %end + + if nargin < 2, clusters = []; end + + if isempty(clusters) + cl{1} = []; + disp('montage_clusters: no activation blobs; displaying underlay image.'); + XYZmm = zeros(3, 16); + XYZmm(3, :) = -60:9:80; + else + % we have clusters + + cl{1} = clusters; + clindx = length(cl)+1; + + XYZmm = cat(2, clusters.XYZmm); + XYZmm_all = XYZmm; + end + + % ---------------------------------------- + % * process input arguments + % ---------------------------------------- + + %why not turn this into a switch statement? - SS 5/20/10 + if length(varargin) > 0 + for i = 1:length(varargin) + if isempty(varargin{i}) + % ignore it + elseif isstruct(varargin{i}) + % struct inputs interpreted as clusters variables + cl{clindx} = varargin{i}; + clXYZmm = cat(2, cl{clindx}.XYZmm); + clindx = clindx + 1; + a = XYZmm'; b = clXYZmm'; + XYZmm_both = [XYZmm_both intersect(a, b, 'rows')']; + if ~isempty(XYZmm_both), XYZmm_all = intersect(XYZmm_all', b, 'rows')'; else XYZmm_all = []; end + XYZmm = [XYZmm clXYZmm]; + + elseif ischar(varargin{i}) + % string arguments interpreted as overlap colors + if strcmp(varargin{i}, 'nooverlap'), plotovl = 0; + else + bcolor = varargin{i}; + end + elseif iscell(varargin{i}) + % cell array vectors with one row interpreted as color inputs + if size(varargin{i}, 1) == 1 + customcolors = 1; + myc = varargin{i}; + elseif size(varargin{i}, 2) == 1 + % cell array vectors with one column interpreted as point coordinates to plot + myplab = varargin{i}; + else + error('cell array must be row (for colors) or column (for text labels) vector.') + end + elseif prod(size(varargin{i})) == 1 + % single integers interpreted as 'do overlap plot' flag, can be 1 or 0 + % default is 1 + plotovl = varargin{i}; + elseif any(size(varargin{i}) == 3) + % any 3-vector matrix interpreted as list of points to plot on figure + XYZpts = varargin{i}; + %XYZmm = [XYZmm XYZpts]; + % then it's a list of points to plot + elseif size(varargin{i}, 1) == 1 && size(varargin{i}, 2) == 2 + % 1 x 2 double vector, means we want height-mapped colors instead of solid + colorlimits = varargin{i}; + else + error('Unknown input argument type.') + end + end + end + + % get rid of empty clusters + allempty = 0; + for i =1:length(cl), wh(i) = isempty(cl{i}); end; wh = find(wh); + if length(wh) == length(cl), allempty = 1; plotovl = 0; end + cl(wh) = []; myc(wh) = []; + + % do not plot overlaps if only one clusters struct entered + if length(cl) == 1, plotovl = 0; end + + if customcolors + if length(myc) > length(cl)+1, acolor = myc{length(cl) + 2}; end + if length(myc) > length(cl), bcolor = myc{length(cl) + 1}; end + end + + if doverb + disp([num2str(length(cl)) ' Clusters found.']) + if plotovl + disp(['Two cluster overlap: ' bcolor ', found ' num2str(length(XYZmm_both)) ' voxels.']) + disp(['All cluster overlap: ' acolor ', found ' num2str(length(XYZmm_all)) ' voxels.']) + else + disp('No overlap plotting.') + end + end + + % ---------------------------------------- + % * overlay image + % ---------------------------------------- + try + V = spm_vol(ovl); + oimg = spm_read_vols(V); + catch + fprintf(1, 'Could not use spm_vol or spm_read_vols. Either image "%s" is missing or you don''t have SPM.', ovl); + [oimg, hdr, h] = readim2(ovl); + V.mat = diag([hdr.xsize hdr.ysize hdr.zsize 1]); V.mat(:, 4) = [hdr.origin(1:3); hdr.SPM_scale]; + orig = (hdr.origin(1:3) - [hdr.xdim hdr.ydim hdr.zdim]') .* [hdr.xsize hdr.ysize hdr.zsize]'; + V.mat(1:3, 4) = orig; + V.mat = [ + 2 0 0 -92 + 0 2 0 -128 + 0 0 2 -74 + 0 0 0 1]; + end + + %V.mat = scn_mat_conform(V.mat); + V.M = V.mat; + + textx = size(oimg, 1) - 50; + texty = 6; %size(oimg, 2) - 6; + + %[array, hdr, h, whichslices, rows, cols, figh] = readim2(ovl, 'p'); + + + % how many slices, which ones + + XYZ = mm2voxel(XYZmm, V, 1)'; % 1 no re-ordering, allows repeats %2 THIS RE-ORDERS VOXELS, BUT IS FAST, AND ORDER SHOULDN'T MATTER HERE. + whsl = unique(XYZ(3, :)); + + whsl(whsl <=0) = []; + whsl(whsl >= size(oimg, 3)) = []; + + nsl = length(whsl) + 1; + rc = ceil(sqrt(nsl)); + h = []; + + mch = create_figure('SCNlab_Montage'); + colormap gray; + set(mch, 'Color', [1 1 1], 'MenuBar', 'none'); +% ssize = get(0, 'screensize'); +% set(mch, 'Position', ssize ./ [0.0303 0.0128 2.0447 1.2577]); + +% colormap works to make white background for continuous-valued images, but +% does not work well for binary images +cc = colormap(gray); cc(1:10, :) = repmat([1 1 1], 10, 1); % [0 0 .3] for dark blue +colormap(cc) + +if length(unique(oimg(:))) < 10 % binary or other strange image type + cm = colormap(gray); cm = cm(length(cm):-1:1, :); % invert colormap + colormap(cm) +end + + display_underlay(mch, oimg, whsl, rc, V, textx, texty), drawnow + + if allempty, return, end + + % ---------------------------------------- + % * plot first cluster structure + % ---------------------------------------- + + index = plot_cluster(mch, cl{1}, oimg, rc, V, whsl, myc{1}, textx, texty, 1, size(oimg), colorlimits); + if ~isempty(XYZpts), ph = plot_points(mch, XYZpts, rc, V, whsl, bcolor, myplab); end + % plot points for single-voxel clusters + if isempty(colorlimits) + for i = 1:length(clusters) + try + if clusters(i).numVox == 1 + plot_points(mch, clusters(i).XYZmm, rc, V, whsl, myc{1}, myplab); + end + catch + end + end + end + + % ---------------------------------------- + % * plot additional cluster structures + % ---------------------------------------- + + if length(cl) > 1 + for i = 2:length(cl) + index = plot_cluster(mch, cl{i}, [], rc, V, whsl, myc{i}, textx, texty, i, size(oimg), colorlimits); + if isempty(colorlimits) + for j = 1:length(cl{i}), + try + if cl{i}(j).numVox == 1, plot_points(mch, cl{i}(j).XYZmm, rc, V, whsl, myc{i}, myplab);end + catch + end + end + end + end + end + + + % ---------------------------------------- + % * plot overlap areas + % ---------------------------------------- + + if plotovl + + if ~isempty(XYZmm_both) + bcl.XYZmm = XYZmm_both; bcl.M = cl{1}(1).M; + plot_cluster(mch, bcl, [], rc, V, whsl, bcolor, textx, texty, i+1, size(oimg), colorlimits, 1); + end + + if ~isempty(XYZmm_all) + bcl.XYZmm = XYZmm_all; bcl.M = cl{1}(1).M; + plot_cluster(mch, bcl, [], rc, V, whsl, acolor, textx, texty, i+1, size(oimg), colorlimits, 1); + end + + end + + % fix bug at end - replot XYZ slice text + %for z = whsl + % subplot(rc, rc, index); + % zmm = voxel2mm([1 1 z]', V.mat); + % text(textx, texty, ['z = ' num2str(zmm(3))], 'Color', 'w') + % zi(z) = zmm(3); + %end + %zi(zi > 0) + + try + enlarge_axes(mch) + catch + disp('Error enlarging axes. Skipping.') + end + +end + + + + +% ---------------------------------------- +% +% * Sub-functions +% +% ---------------------------------------- + +function index = plot_cluster(mch, clusters, oimg, rc, V, whsl, myc, textx, texty, clind, odims, colorlimits, varargin) + % varargin suppresses end text + + if ~isfield(clusters, 'Z'), for i = 1:length(clusters), clusters(i).Z = ones(1, size(clusters(i).XYZmm, 2));end, end + + % take only first row of Z scores + for i = 1:length(clusters), clusters(i).Z = clusters(i).Z(1, :);end + + + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + % Set up + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + XYZmm = cat(2, clusters.XYZmm); + XYZ = mm2voxel(XYZmm, V, 1)'; % no unique suppression + + % get relative voxel sizes for fill + xs = diag(clusters(1).M(1:3, 1:3)); + xs = xs ./ diag(V.mat(1:3, 1:3)); + % add 25% to make sure no gaps in plot + xs = xs + .25*xs; + + if ~isempty(colorlimits) + + if (colorlimits(1) < 1 && colorlimits(2) < 1) || all(colorlimits - mean(colorlimits)) < eps + + % ------------------------------------------------------------- + % define 4 split color maps - red/yellow, green/blue + % ------------------------------------------------------------- + colorlimits = 0; % define maps within function; otherwise, use limits entered + end + + + % ------------------------------------------------------------- + % define color maps - biscale hot/cool + % ------------------------------------------------------------- + + % color map - hot + % -------------------------------------------- + h1 = linspace(.7, 1, 250)'; + h2 = linspace(0, .8, 175)'; + h2 = [h2; linspace(.8, 1, 75)']; + + h3 = zeros(size(h1)); + h = [h1 h2 h3]; + + % color map - winter + % -------------------------------------------- + h1 = linspace(0, .5, 250)'; + h2 = linspace(1, .7, 250)'; + h3 = zeros(size(h1)); + hc = [h3 h1 h2]; + + % % color map - hot + % % -------------------------------------------- + % h1 = (0:1/99:1)'; + % h2 = ones(size(h1)); + % h3 = zeros(size(h1)); + % h = [h1 h3 h3; h2 h1 h3; h2 h2 h1]; + % h(1:50, :) = []; % take only red values to start + % % in new matlab: h = colormap(hot(300)); + % + % % color map - winter + % % -------------------------------------------- + % h1 = (0:1/249:1)'; + % h2 = (1:-1/(249*2):.5)'; + % h3 = zeros(size(h1)); + % hc = [h3 h1 h2]; + + % ------------------------------------------------------------- + % determine overall z-score range + % ------------------------------------------------------------- + + allz = cat(2, clusters.Z); + zrange = allz; + tmp = zrange(zrange > 0); + tmpc = zrange(zrange < 0); + + if ~isempty(tmp) + if colorlimits + zrange = colorlimits; + else + zrange = [min(tmp) max(tmp)]; + end + zh = zrange(1):(zrange(2)-zrange(1))./249:zrange(2); + zh = round(zh*100); + if isempty(zh) % we probably have all same values + zh = ones(1, 250); + end + else + zh = []; + end + + if ~isempty(tmpc) + if colorlimits + zrangec = -colorlimits; + else + zrangec = [min(tmpc) max(tmpc)]; + end + zhc = zrangec(1):(zrangec(2)-zrangec(1))./249:zrangec(2); + if isempty(zhc), zhc = 1:length(hc);end + zhc = round(zhc*100); + if isempty(zhc) % we probably have all same values + zhc = ones(1, 250); + end + else + zhc = []; + end + + if isempty(zh) && isempty(zhc), error('No Z-scores in cluster'), end + + else + + % surface patch method + % ---------------------------------------------------------------------------------------- + vol = voxel2mask(XYZ', odims); + vol = smooth3(vol); + + end + + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + % Loop through slices + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if length(whsl) > 12, fsz = 14; else fsz = 18;end + index = 1; + for z = whsl + set(0, 'CurrentFigure', mch); + subplot(rc, rc, index); + +% This is now done earlier, in display_underlay +% if ~isempty(oimg) +% imagesc(oimg(:, :, z)') +% set(gca, 'YDir', 'normal', 'Color', [1 1 1]); +% hold on; axis image; axis off +% zmm = voxel2mm([1 1 z]', V.mat); +% text(textx, texty, ['z = ' num2str(zmm(3))], 'Color', 'k', 'FontSize', fsz) +% else +% hold on +% end + + doimgpatch = 0; + + if doimgpatch %isempty(colorlimits) + + % -------------------------------------------------------------------------------- + % solid color patches + % -------------------------------------------------------------------------------- + if z>1 + mvol = vol(:, :, z-1:z); for i = 1:size(mvol, 3), myvol(:, :, i) = mvol(:, :, i)';end + %FVC = isocaps(vol(:, :, z-1:z)', 0, 'zmax'); + else + mvol = vol(:, :, z:z+1); for i = 1:size(mvol, 3), myvol(:, :, i) = mvol(:, :, i)';end + %FVC = isocaps(vol(:, :, z:z+1)', 0, 'zmax'); + end + FVC = isocaps(myvol, 0, 'zmax'); + + try + patch(FVC, 'EdgeColor', 'none', 'FaceColor', myc, 'FaceAlpha', 1) + catch + patch(FVC, 'EdgeColor', 'none', 'FaceColor', myc) + end + + else + % -------------------------------------------------------------------------------- + % color-mapped points + % -------------------------------------------------------------------------------- + + myxyz = XYZ(1:2, XYZ(3, :) == z); + + if ~isempty(colorlimits) + myz = allz(XYZ(3, :) == z); + clear h2, clear wh + end + + %plot(myXYZ(2, :), myXYZ(1, :), [myc 's'], 'MarkerFaceColor', myc, 'MarkerSize', 3) + hold on + for j = 1:size(myxyz, 2) + + % added to replace image patch, solid color point fill for pos Z values only + if isempty(colorlimits) + Xfill = [myxyz(1, j) myxyz(1, j) myxyz(1, j)+xs(1) myxyz(1, j)+xs(1)]; + Yfill = [myxyz(2, j) myxyz(2, j)+xs(2) myxyz(2, j)+xs(2) myxyz(2, j)]; + h2(j) = patch(Xfill, Yfill, myc, 'EdgeColor', 'none'); + + else + + if myz(j) < 0 + tmp = find((zhc-round(myz(j)*100)).^2 == min((zhc-round(myz(j)*100)).^2)); + wh(j) = tmp(1); + %h2(j) = plot(myxyz(1, j), myxyz(2, j), 'Color', hc(wh(j), :), 'MarkerSize', 3, 'MarkerFaceColor', hc(wh(j), :)); + + + h2(j) = fill([myxyz(1, j) myxyz(1, j) myxyz(1, j)+xs(1) myxyz(1, j)+xs(1)], [myxyz(2, j) myxyz(2, j)+xs(2) myxyz(2, j)+xs(2) myxyz(2, j)], hc(wh(j), :), 'EdgeColor', 'none'); + else + tmp = find((zh-round(myz(j)*100)).^2 == min((zh-round(myz(j)*100)).^2)); + wh(j) = tmp(1); + %h2(j) = plot(myxyz(1, j), myxyz(2, j), 'Color', h(wh(j), :), 'MarkerSize', 3, 'MarkerFaceColor', h(wh(j), :)); + h2(j) = patch([myxyz(1, j) myxyz(1, j) myxyz(1, j)+xs(1) myxyz(1, j)+xs(1)], [myxyz(2, j) myxyz(2, j)+xs(2) myxyz(2, j)+xs(2) myxyz(2, j)], h(wh(j), :), 'EdgeColor', 'none'); + end + %if exist('h2') == 1, set(h2, 'Marker', 'square'), end + + end %if isempty colorlimits + end + + end % if solid or color-mapped + + + % text numbers, if plotting text at xyz coords of centers (varargin) + %if length(varargin) > 0 + % cencooz = cencoo(:, cencoo(3, :) == z); + % for i = 1:size(cencooz, 2) + % text(cencooz(2, i), cencooz(1, i), num2str(cenind), 'Color', 'k') + % cenind = cenind + 1; + %end + %end + + index = index + 1; + %drawnow + + end % slice loop + + + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + % Text and color bars + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + if isempty(varargin) + set(0, 'CurrentFigure', mch); + subplot(rc, rc, index) + a = pwd; a = a(end-6:end); + + if isfield(clusters(1), 'threshold') && isnumeric(clusters(1).threshold) + b = num2str(clusters(1).threshold); + else + b = 'none'; + end + + axis off + c = num2str(length(clusters)); + if ~isfield(clusters, 'title') || isempty(clusters(1).title) + clusters(1).title = 'Clusters'; + end + + text(0, clind-1, [myc ': ' clusters(1).title ' ' a ' u = ' b ', ' c ' clusters']) + axis([0 1 -1 clind]) + + if ~isempty(colorlimits) + if ~isempty(tmp) + % make color bar + %bfig = figure('Color', 'w'); + bax = axes('Position', [0.100 0.0800 0.40 0.02]); + hold on; + + if any(allz > 0) + zh2 = zh./100; + for i = 2:size(h, 1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)], [0 1 1 0], h(i, :), 'EdgeColor', 'none'); end + plot([min(zh2) max(zh2)], [0 0], 'k-') + set(gca, 'YTickLabel', ''); + %xlabel('Z-score', 'FontSize', 14) + end + end + + if any(allz < 0) + %if ~exist('bfig'), + %bfig = figure('Color', 'w'); hold on; + %end + zh2 = zhc./100; + for i = 2:size(hc, 1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)], [0 1 1 0], hc(i, :), 'EdgeColor', 'none'); end + plot([min(zh2) max(zh2)], [0 0], 'k-') + set(gca, 'YTickLabel', ''); + %xlabel('Z-score', 'FontSize', 14) + end + + end + + %set(gca, 'Position', [0.1300 0.1100 0.7750 0.8150]) + set(gca, 'FontSize', 16, 'Color', [1 1 1]) + %xlabel('Z-score', 'FontSize', 16) + %set(gca, 'Position', [0.1000 0.4500 0.80 0.50]) + %ssize = get(0, 'ScreenSize'); + %set(mch, 'Position', ssize./[0.0370 0.0014 2.7948 15.2500]) + end + + drawnow + +end + + + +function display_underlay(mch, oimg, whsl, rc, V, textx, texty) +% mch: figure handle +% oimg: underlay image matrix 3-D +% whsl: which slices +% rc: rows/cols for subplots (integer number) +% V: volume structure with .mat field for underlay image +% textx, texty: x- and y-axis positions for text labels on plot + +% tor: sept 2010: try to make consistent with spm flipping based on x +% coordinate +if sign(V.mat(1)) < 0 + for z = 1:size(oimg, 3) + oimg(:, :, z) = flipud(oimg(:, :, z)); + end +end + +if length(whsl) > 12, fsz = 14; else fsz = 18; end + +% whsl(whsl <=0) = []; +% whsl(whsl >= size(oimg, 3)) = []; + + index = 1; + for z = whsl + set(0, 'CurrentFigure', mch); + subplot(rc, rc, index); + + if ~isempty(oimg) + imagesc(oimg(:, :, z)') + set(gca, 'YDir', 'normal', 'Color', [1 1 1]); + hold on; axis image; axis off + zmm = voxel2mm([1 1 z]', V.mat); + text(textx, texty, ['z = ' sprintf('%3.0f', zmm(3))], 'Color', 'k', 'FontSize', fsz) + else + hold on + end + + index = index + 1; + end + +end + + + +function ph = plot_points(mch, XYZmm, rc, V, whsl, myc, myplab) + + XYZ = mm2voxel(XYZmm, V, 1)'; % suppress unique voxel output + index = 1; + phind = 1; + + for z = whsl + set(0, 'CurrentFigure', mch); + subplot(rc, rc, index); + hold on + myXYZ = XYZ(:, XYZ(3, :) == z); + if ~isempty(myplab), myplz = myplab(XYZ(3, :) == z); end + + for i = 1:size(myXYZ, 2) + ph(phind) = plot3(myXYZ(1, i), myXYZ(2, i), 100, [myc(1) '.'], 'MarkerFaceColor', myc(1), 'MarkerSize', 8); + + if ~isempty(myplab) + text(myXYZ(1, i), myXYZ(2, i), 100, myplz{i}, 'Color', myc(1)) + end + + phind = phind + 1; + end + + index = index + 1; + %view(0, 90) + %plot(0, 0, 'kd') + end +end + + + + + + +function in = scn_mat_conform(in) + %function in = scn_mat_conform(in) + % + % sets flipping to 0 (no flip) in SPM2 and adjusts mat file accordingly + % input in spm-style mat file or struct with .mat or .M fields + % + + global defaults + spm_defaults(); + + if isstruct(in) + if isfield(in,'mat') + in.mat = scn_mat_conform(in.mat); + end + + if isfield(in,'M') + in.M = scn_mat_conform(in.M); + end + + return + end + + + + if in(1) < 0 + disp('Orientation: Image has negative x voxel size, indicating radiological orientation.'); %''flipped'' in SPM.') + disp('The x voxel size will be changed to positive for display. Montage_clusters program does not flip images for display.') + + in(1) = abs(in(1)); % voxel size + in(1,4) = -(in(1,4)); % origin offset + else + disp('Orientation: Image has positive x voxel size, indicating neurological orientation, and will not be flipped for display.'); %''flipped'' in SPM.') + end + + if isempty(defaults) || ~isfield(defaults, 'analyze') + spm_defaults + end + + switch spm('Ver') + case 'SPM8' + % no analyze field; flip is always 1 + + case {'SPM5', 'SPM2', 'SPM99'} + if defaults.analyze.flip + disp('Warning: Setting defaults.analyze.flip to 0. No flipping.') + defaults.analyze.flip = 0; + end + otherwise + warning('Unknown version of SPM. Results may be erratic.') + end + +end + \ No newline at end of file diff --git a/Visualization_functions/montage_clusters_maxslice.m b/Visualization_functions/montage_clusters_maxslice.m new file mode 100755 index 00000000..983c13eb --- /dev/null +++ b/Visualization_functions/montage_clusters_maxslice.m @@ -0,0 +1,512 @@ +function montage_clusters_maxslice(ovl,clusters,varargin) +% montage_clusters(ovl,clusters,varargin) +% +% by Tor Wager last edit 12/11/02 +% +% varargin (in any order) = +% a) additional clusters structures +% b) cell array of colors (text format), must be ROW vector {'r' 'g'} etc... +% if length of color string is longer than number of clusters inputs, +% additional colors are interpreted as '2-intersection' and 'all-intersection' +% colors, in that order. This overrides single string argument (c, below) for +% color input +% c) single string color argument for overlaps (intersections) +% plots intersections of ANY TWO clusters right now. +% also color for plotting points, if entered. +% use 'nooverlap' as an input argument to suppress this. +% d) [n x 3] matrix of points to plot on map +% e) text labels for points, must be cell array in COLUMN vector +% f) single number, 1/0 for whether to plot overlapping coordinates in overlap colors +% default is 1. +% g) color limit vector [min max] indicates color mapping for blobs rather than +% solid colors. This will do hot/cool mapping +% +% Intersections of 2 colors are magenta, and ALL colors are yellow +% unless otherwise specified +% +% This maxslice version does 1) Not create a new figure, and 2) finds max +% slice through clusters to create single slice image. +% Useful for displaying next to timecourse plots (for example) + +% try: CLU = clusters2clu(clusters); +% spm_orthviews('AddColouredBlobs',1,CLU.XYZ,CLU.Z,CLU.M,[1 0 0]) + +% ---------------------------------------- +% * defaults +% ---------------------------------------- + +if isempty(ovl), ovl = which('scalped_single_subj_T1.img');, end +myc = {'b' 'r' 'g' 'c' 'm' 'w'}; +customcolors = 0; +bcolor = 'm'; +acolor = 'y'; +XYZmm_both = []; +clindx = 1; +XYZpts = []; +myplab = []; +cl = []; +plotovl = 1; +doverb = 0; +colorlimits = []; + +% recursive operation if clusters is a cell array +% indicating multiple clusters structures +%if iscell(clusters), +% for i = 1:length(clusters), +% montage_clusters(ovl,clusters{i},varargin) +% end +% return +%end + +XYZmm = cat(2,clusters.XYZmm); + +% Single slice stuff: Find most frequent XYZmm +tmp = unique(XYZmm(3,:)); for i = 1:length(tmp), m(i) = sum(XYZmm(3,:) == tmp(i));,end +wh = tmp(find(m==max(m))); wh = wh(1); +XYZmm = XYZmm(:,find(XYZmm(3,:) == wh)); + + +XYZmm_all = XYZmm; + + +% ---------------------------------------- +% * process input arguments +% ---------------------------------------- + +if length(varargin) > 0 + for i = 1:length(varargin) + if isstruct(varargin{i}) + % struct inputs interpreted as clusters variables + cl{clindx} = varargin{i}; + clXYZmm = cat(2,cl{clindx}.XYZmm); + clindx = clindx + 1; + a = XYZmm'; b = clXYZmm'; + XYZmm_both = [XYZmm_both intersect(a,b,'rows')']; + if ~isempty(XYZmm_both), XYZmm_all = intersect(XYZmm_all',b,'rows')';, else, XYZmm_all = [];, end + XYZmm = [XYZmm clXYZmm]; + + elseif isstr(varargin{i}) + % string arguments interpreted as overlap colors + if strcmp(varargin{i},'nooverlap'),plotovl = 0;, + else + bcolor = varargin{i}; + end + elseif iscell(varargin{i}) + % cell array vectors with one row interpreted as color inputs + if size(varargin{i},1) == 1 + customcolors = 1; + myc = varargin{i}; + elseif size(varargin{i},2) == 1 + % cell array vectors with one column interpreted as point coordinates to plot + myplab = varargin{i}; + else + error('cell array must be row (for colors) or column (for text labels) vector.') + end + elseif prod(size(varargin{i})) == 1 + % single integers interpreted as 'do overlap plot' flag, can be 1 or 0 + % default is 1 + plotovl = varargin{i}; + elseif any(size(varargin{i}) == 3) + % any 3-vector matrix interpreted as list of points to plot on + XYZpts = varargin{i}; + %XYZmm = [XYZmm XYZpts]; + % then it's a list of points to plot + elseif size(varargin{i},1) == 1 & size(varargin{i},2) == 2 + % 1 x 2 double vector, means we want height-mapped colors instead of solid + colorlimits = varargin{i}; + else + error('Unknown input argument type.') + end + end +end + +if length(cl) < 1, plotovl = 0;, end + +if customcolors + if length(myc) > length(cl)+2, acolor = myc{length(cl) + 3};, end + if length(myc) > length(cl)+1, bcolor = myc{length(cl) + 2};, end +end + +if doverb + disp([num2str(length(cl) + 1) ' Clusters found.']) + if plotovl + disp(['Two cluster overlap: ' bcolor ', found ' num2str(length(XYZmm_both)) ' voxels.']) + disp(['All cluster overlap: ' acolor ', found ' num2str(length(XYZmm_all)) ' voxels.']) + else + disp(['No overlap plotting.']) + end +end + +% ---------------------------------------- +% * overlay image +% ---------------------------------------- +try + V = spm_vol(ovl); + oimg = spm_read_vols(V); + V.M = V.mat; +catch + disp('Cannot use spm_vol: No SPM?') + [oimg,hdr,h] = readim2(ovl); + V.mat = diag([hdr.xsize hdr.ysize hdr.zsize 1]); V.mat(:,4) = [hdr.origin(1:3); hdr.SPM_scale]; + orig = (hdr.origin(1:3) - [hdr.xdim hdr.ydim hdr.zdim]') .* [hdr.xsize hdr.ysize hdr.zsize]'; + V.mat(1:3,4) = orig; + V.mat = [ + 2 0 0 -92 + 0 2 0 -128 + 0 0 2 -74 + 0 0 0 1]; + + V.M = V.mat; +end + +textx = size(oimg,1) - 50; +texty = 6; %size(oimg,2) - 6; + +%[array,hdr,h,whichslices,rows,cols,figh] = readim2(ovl,'p'); + +% how many slices, which ones + +XYZ = mm2voxel(XYZmm,V,1)'; % 1 no re-ordering, allows repeats %2 THIS RE-ORDERS VOXELS, BUT IS FAST, AND ORDER SHOULDN'T MATTER HERE. +whsl = unique(XYZ(3,:)); +nsl = length(whsl) + 1; +rc = ceil(sqrt(nsl)); +h = []; + +colormap gray; set(gcf,'Color','w') +cc = colormap(gray); cc(1:10,:) = repmat([1 1 1],10,1); % [0 0 .3] for dark blue +colormap(cc) + + +% ---------------------------------------- +% * plot first cluster structure +% ---------------------------------------- + +index = plot_cluster(clusters,oimg,rc,V,whsl,myc{1},textx,texty,1,size(oimg),colorlimits,1); +if ~isempty(XYZpts), ph = plot_points(XYZpts,rc,V,whsl,bcolor,myplab);, end +% plot points for single-voxel clusters +if isempty(colorlimits) + for i = 1:length(clusters), + try,if clusters(i).numVox == 1, plot_points(clusters(i).XYZmm,rc,V,whsl,myc{1},myplab);, end,catch,end + end +end + +% ---------------------------------------- +% * plot additional cluster structures +% ---------------------------------------- + +if length(cl) > 0 + for i = 1:length(cl) + index = plot_cluster(cl{i},[],rc,V,whsl,myc{i+1},textx,texty,i+1,size(oimg),colorlimits,1); + if isempty(colorlimits) + for j = 1:length(cl{i}), + try,if cl{i}(j).numVox == 1, plot_points(cl{i}(j).XYZmm,rc,V,whsl,myc{i+1},myplab);,end,catch,end + end + end + end +end + + +% ---------------------------------------- +% * plot overlap areas +% ---------------------------------------- + +if plotovl + + if ~isempty(XYZmm_both) + bcl.XYZmm = XYZmm_both; bcl.M = cl{1}(1).M; + plot_cluster(bcl,[],rc,V,whsl,bcolor,textx,texty,i+1,size(oimg),colorlimits,1); + end + + if ~isempty(XYZmm_all) + bcl.XYZmm = XYZmm_all; bcl.M = cl{1}(1).M; + plot_cluster(bcl,[],rc,V,whsl,acolor,textx,texty,i+1,size(oimg),colorlimits,1); + end + +end + +% fix bug at end - replot XYZ slice text +%for z = whsl +% subplot(rc,rc,index); +% zmm = voxel2mm([1 1 z]',V.mat); +% text(textx,texty,['z = ' num2str(zmm(3))],'Color','w') +% zi(z) = zmm(3); +%end +%zi(zi > 0) + +%try +% enlarge_axes(gcf) +%catch +% disp('Error enlarging axes. Skipping.') +%end + +return + + + + +% ---------------------------------------- +% +% * Sub-functions +% +% ---------------------------------------- + +function index = plot_cluster(clusters,oimg,rc,V,whsl,myc,textx,texty,clind,odims,colorlimits,varargin) +% varargin suppresses end text + +% take only first row of Z scores +for i = 1:length(clusters), clusters(i).Z = clusters(i).Z(1,:);,end + + +% * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +% Set up +% * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +XYZmm = cat(2,clusters.XYZmm); +XYZ = mm2voxel(XYZmm,V,1)'; % no unique suppression + +% get relative voxel sizes for fill +xs = diag(clusters(1).M(1:3,1:3)); +xs = xs ./ diag(V.mat(1:3,1:3)); +% add 25% to make sure no gaps in plot +xs = xs + .25*xs; + +if ~isempty(colorlimits) + + if colorlimits(1) < 1 & colorlimits(2) < 1 + + % ------------------------------------------------------------- + % define 4 split color maps - red/yellow, green/blue + % ------------------------------------------------------------- + + end + + + % ------------------------------------------------------------- + % define color maps - biscale hot/cool + % ------------------------------------------------------------- + + % color map - hot + % -------------------------------------------- + h1 = (0:1/99:1)'; + h2 = ones(size(h1)); + h3 = zeros(size(h1)); + h = [h1 h3 h3; h2 h1 h3; h2 h2 h1]; + h(1:50,:) = []; % take only red values to start + % in new matlab: h = colormap(hot(300)); + + % color map - winter + % -------------------------------------------- + h1 = (0:1/249:1)'; + h2 = (1:-1/(249*2):.5)'; + h3 = zeros(size(h1)); + hc = [h3 h1 h2]; + + % ------------------------------------------------------------- + % determine overall z-score range + % ------------------------------------------------------------- + + allz = cat(2,clusters.Z); + zrange = allz; + tmp = zrange(zrange > 0); + tmpc = zrange(zrange < 0); + + if ~isempty(tmp) + zrange = [min(tmp) max(tmp)]; + zh = zrange(1):(zrange(2)-zrange(1))./249:zrange(2); + zh = round(zh*100); + else + zh = []; + end + + if ~isempty(tmpc) + zrangec = [min(tmpc) max(tmpc)]; + zhc = zrangec(1):(zrangec(2)-zrangec(1))./249:zrangec(2); + if isempty(zhc), zhc = 1:length(hc);,end + zhc = round(zhc*100); + else, + zhc = []; + end + + if isempty(zh) & isempty(zhc), error('No Z-scores in cluster'),end + +else + + % surface patch method + % ---------------------------------------------------------------------------------------- + vol = voxel2mask(XYZ',odims); + vol = smooth3(vol); + +end + +% * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +% Loop through slices +% * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +index = 1; +for z = whsl + + %subplot(rc,rc,index); + + + if ~isempty(oimg) + imagesc(oimg(:,:,z)') + set(gca,'YDir','normal'); + hold on; axis image; axis off + zmm = voxel2mm([1 1 z]',V.mat); + text(textx,texty,['z = ' num2str(zmm(3))],'Color','k','FontSize',18) + else + hold on + end + + doimgpatch = 0; + + if doimgpatch %isempty(colorlimits) + + % -------------------------------------------------------------------------------- + % solid color patches + % -------------------------------------------------------------------------------- + if z>1, + mvol = vol(:,:,z-1:z); for i = 1:size(mvol,3),myvol(:,:,i) = mvol(:,:,i)';,end + %FVC = isocaps(vol(:,:,z-1:z)',0,'zmax'); + else + mvol = vol(:,:,z:z+1); for i = 1:size(mvol,3),myvol(:,:,i) = mvol(:,:,i)';,end + %FVC = isocaps(vol(:,:,z:z+1)',0,'zmax'); + end + FVC = isocaps(myvol,0,'zmax'); + + try + patch(FVC,'EdgeColor','none','FaceColor',myc,'FaceAlpha',1) + catch + patch(FVC,'EdgeColor','none','FaceColor',myc) + end + + else + % -------------------------------------------------------------------------------- + % color-mapped points + % -------------------------------------------------------------------------------- + + myxyz = XYZ(1:2,XYZ(3,:) == z); + + if ~isempty(colorlimits) + myz = allz(XYZ(3,:) == z); + clear h2, clear wh + end + + %plot(myXYZ(2,:),myXYZ(1,:),[myc 's'],'MarkerFaceColor',myc,'MarkerSize',3) + hold on + for j = 1:size(myxyz,2) + + % added to replace image patch, solid color point fill for pos Z values only + if isempty(colorlimits) + h2(j) = fill([myxyz(1,j) myxyz(1,j) myxyz(1,j)+xs(1) myxyz(1,j)+xs(1)], ... + [myxyz(2,j) myxyz(2,j)+xs(2) myxyz(2,j)+xs(2) myxyz(2,j)],myc, ... + 'EdgeColor','none'); + + else + + if myz(j) < 0 + tmp = find((zhc-round(myz(j)*100)).^2 == min((zhc-round(myz(j)*100)).^2)); + wh(j) = tmp(1); + %h2(j) = plot(myxyz(1,j),myxyz(2,j),'Color',hc(wh(j),:),'MarkerSize',3,'MarkerFaceColor',hc(wh(j),:)); + + + h2(j) = fill([myxyz(1,j) myxyz(1,j) myxyz(1,j)+xs(1) myxyz(1,j)+xs(1)],[myxyz(2,j) myxyz(2,j)+xs(2) myxyz(2,j)+xs(2) myxyz(2,j)],hc(wh(j),:),'EdgeColor','none'); + else + tmp = find((zh-round(myz(j)*100)).^2 == min((zh-round(myz(j)*100)).^2)); + wh(j) = tmp(1); + %h2(j) = plot(myxyz(1,j),myxyz(2,j),'Color',h(wh(j),:),'MarkerSize',3,'MarkerFaceColor',h(wh(j),:)); + h2(j) = fill([myxyz(1,j) myxyz(1,j) myxyz(1,j)+xs(1) myxyz(1,j)+xs(1)],[myxyz(2,j) myxyz(2,j)+xs(2) myxyz(2,j)+xs(2) myxyz(2,j)],h(wh(j),:),'EdgeColor','none'); + end + %if exist('h2') == 1, set(h2,'Marker','square'),end + + end %if isempty colorlimits + end + + end % if solid or color-mapped + + index = index + 1; + drawnow + +end % slice loop + + +% * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +% Text and color bars +% * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +if length(varargin) == 0 + subplot(rc,rc,index) + a = pwd; a = a(end-6:end); + b = num2str(clusters(1).threshold); + axis off + mfig = gcf; + c = num2str(length(clusters)); + text(0,clind-1,[myc ': ' clusters(1).title ' ' a ' u = ' b ', ' c ' clusters']) + axis([0 1 -1 clind]) + + if ~isempty(colorlimits) + if ~isempty(tmp) + bfig = figure('Color','w'); + hold on; + + if any(allz > 0) + zh2 = zh./100; + for i = 2:size(h,1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)],[0 1 1 0],h(i,:),'EdgeColor','none');, end + plot([min(zh2) max(zh2)],[0 0],'k-') + set(gca,'YTickLabel',''); + xlabel('Z-score','FontSize',14) + end + end + + if any(allz < 0) + %if ~exist('bfig'), + %bfig = figure('Color','w'); hold on;, + %end + zh2 = zhc./100; + for i = 2:size(hc,1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)],[0 1 1 0],hc(i,:),'EdgeColor','none');, end + plot([min(zh2) max(zh2)],[0 0],'k-') + set(gca,'YTickLabel',''); + xlabel('Z-score','FontSize',14) + end + + end + + %set(gca,'Position',[0.1300 0.1100 0.7750 0.8150]) + set(gca,'FontSize',18) + xlabel('Z-score','FontSize',18) + %set(gca,'Position',[0.1300 0.1100 0.7750 0.8150]) +end + + +return + + + + +function ph = plot_points(XYZmm,rc,V,whsl,myc,myplab) + +XYZ = mm2voxel(XYZmm,V,1)'; % suppress unique voxel output +index = 1; +phind = 1; + +for z = whsl + + subplot(rc,rc,index); + hold on + myXYZ = XYZ(:,XYZ(3,:) == z); + if ~isempty(myplab), myplz = myplab(XYZ(3,:) == z);, end + + for i = 1:size(myXYZ,2) + ph(phind) = plot3(myXYZ(1,i),myXYZ(2,i),100,[myc(1) '.'],'MarkerFaceColor',myc(1),'MarkerSize',8); + + if ~isempty(myplab) + text(myXYZ(1,i),myXYZ(2,i),100,myplz{i},'Color',myc(1)) + end + + phind = phind + 1; + end + + index = index + 1; + %view(0,90) + %plot(0,0,'kd') +end \ No newline at end of file diff --git a/Visualization_functions/montage_clusters_medial.m b/Visualization_functions/montage_clusters_medial.m new file mode 100644 index 00000000..20727c1d --- /dev/null +++ b/Visualization_functions/montage_clusters_medial.m @@ -0,0 +1,657 @@ +% montage_clusters_medial(ovl,clusters,varargin) +% +% by Tor Wager last edit 12/11/02 +% +% varargin (in any order) = +% a) additional clusters structures +% b) cell array of colors (text format), must be ROW vector {'r' 'g'} etc... +% if length of color string is longer than number of clusters inputs, +% additional colors are interpreted as '2-intersection' and 'all-intersection' +% colors, in that order. This overrides single string argument (c, below) for +% color input +% c) single string color argument for overlaps (intersections) +% plots intersections of ANY TWO clusters right now. +% also color for plotting points, if entered. +% use 'nooverlap' as an input argument to suppress this. +% d) [n x 3] matrix of points to plot on map +% e) text labels for points, must be cell array in COLUMN vector +% f) single number, 1/0 for whether to plot overlapping coordinates in overlap colors +% default is 1. +% g) color limit vector [min max] indicates color mapping for blobs rather than +% solid colors. This will do hot/cool mapping +% +% Intersections of 2 colors are magenta, and ALL colors are yellow +% unless otherwise specified + +function mcmh = montage_clusters_medial(ovl,clusters,varargin) + % try: CLU = clusters2clu(clusters); + % spm_orthviews('AddColouredBlobs',1,CLU.XYZ,CLU.Z,CLU.M,[1 0 0]) + + % ---------------------------------------- + % * defaults + % ---------------------------------------- + if ischar(ovl) + [path, name, ext] = fileparts(ovl); + if(isempty(path)) + ovl = which(ovl); + end + end + + if isempty(ovl), ovl = which('spm2_single_subj_T1_scalped.img');, end + mcmh = []; + myc = {'r' 'b' 'g' 'c' 'm' 'w'}; + customcolors = 0; + bcolor = 'm'; + acolor = 'y'; + XYZmm_both = []; + clindx = 1; + XYZpts = []; + myplab = []; + cl = []; + plotovl = 1; + doverb = 1; + colorlimits = []; + + % recursive operation if clusters is a cell array + % indicating multiple clusters structures + %if iscell(clusters), + % for i = 1:length(clusters), + % montage_clusters(ovl,clusters{i},varargin) + % end + % return + %end + + cl{1} = clusters; + clindx = length(cl)+1; + + XYZmm = cat(2,clusters.XYZmm); + XYZmm_all = XYZmm; + + + % ---------------------------------------- + % * process input arguments + % ---------------------------------------- + + if length(varargin) > 0 + for i = 1:length(varargin) + if isstruct(varargin{i}) + % struct inputs interpreted as clusters variables + cl{clindx} = varargin{i}; + clXYZmm = cat(2,cl{clindx}.XYZmm); + clindx = clindx + 1; + a = XYZmm'; b = clXYZmm'; + XYZmm_both = [XYZmm_both intersect(a,b,'rows')']; + if ~isempty(XYZmm_both), XYZmm_all = intersect(XYZmm_all',b,'rows')';, else, XYZmm_all = [];, end + XYZmm = [XYZmm clXYZmm]; + + elseif isstr(varargin{i}) + % string arguments interpreted as overlap colors + if strcmp(varargin{i},'nooverlap'),plotovl = 0;, + else + bcolor = varargin{i}; + end + elseif iscell(varargin{i}) + % cell array vectors with one row interpreted as color inputs + if size(varargin{i},1) == 1 + customcolors = 1; + myc = varargin{i}; + elseif size(varargin{i},2) == 1 + % cell array vectors with one column interpreted as point coordinates to plot + myplab = varargin{i}; + else + error('cell array must be row (for colors) or column (for text labels) vector.') + end + elseif prod(size(varargin{i})) == 1 + % single integers interpreted as 'do overlap plot' flag, can be 1 or 0 + % default is 1 + plotovl = varargin{i}; + elseif any(size(varargin{i}) == 3) + % any 3-vector matrix interpreted as list of points to plot on figure + XYZpts = varargin{i}; + %XYZmm = [XYZmm XYZpts]; + % then it's a list of points to plot + elseif size(varargin{i},1) == 1 & size(varargin{i},2) == 2 + % 1 x 2 double vector, means we want height-mapped colors instead of solid + colorlimits = varargin{i}; + else + error('Unknown input argument type.') + end + end + end + + % get rid of empty clusters; return if all empty + if isempty(cl), return, end + for i =1:length(cl),wh(i) = isempty(cl{i}); end; wh = find(wh); + if length(wh) == length(cl), return, end + cl(wh) = []; myc(wh) = []; + + % do not plot overlaps if only one clusters struct entered + if length(cl) == 1, plotovl = 0; end + + if customcolors + if length(myc) > length(cl)+1, acolor = myc{length(cl) + 2}; end + if length(myc) > length(cl), bcolor = myc{length(cl) + 1}; end + end + + if doverb + disp([num2str(length(cl)) ' Clusters found.']) + if plotovl + disp(['Two cluster overlap: ' bcolor ', found ' num2str(length(XYZmm_both)) ' voxels.']) + disp(['All cluster overlap: ' acolor ', found ' num2str(length(XYZmm_all)) ' voxels.']) + else + disp(['No overlap plotting.']) + end + end + + % ---------------------------------------- + % * overlay image + % ---------------------------------------- + try + V = spm_vol(ovl); + oimg = spm_read_vols(V); + V.M = V.mat; + catch + fprintf(1, 'Could not use spm_vol or spm_read_vols. Either image "%s" is missing or you don''t have SPM.', ovl); + [oimg,hdr,h] = readim2(ovl); + V.mat = diag([hdr.xsize hdr.ysize hdr.zsize 1]); V.mat(:,4) = [hdr.origin(1:3); hdr.SPM_scale]; + orig = (hdr.origin(1:3) - [hdr.xdim hdr.ydim hdr.zdim]') .* [hdr.xsize hdr.ysize hdr.zsize]'; + V.mat(1:3,4) = orig; + V.mat = [ + 2 0 0 -92 + 0 2 0 -128 + 0 0 2 -74 + 0 0 0 1]; + + V.M = V.mat; + end + + V.mat = scn_mat_conform(V.mat); + + textx = size(oimg,1) - 50; + texty = 6; %size(oimg,2) - 6; + + %[array,hdr,h,whichslices,rows,cols,figh] = readim2(ovl,'p'); + + % how many slices, which ones + % MODIFIED FOR MEDIAL VIEW + XYZmm(:,abs(XYZmm(1,:)) > 15) = []; % get rid of non-medial coordinates + if(isempty(XYZmm)) + disp('No medial activations to display.'); + return + end + XYZ = mm2voxel(XYZmm,V,1)'; % 1 no re-ordering, allows repeats %2 THIS RE-ORDERS VOXELS, BUT IS FAST, AND ORDER SHOULDN'T MATTER HERE. + XYZ = XYZ([2 3 1],:); % reorganize to permuted dims + oimg = permute(oimg,[2 3 1]); + + whsl = unique(XYZ(3,:)); + nsl = length(whsl) + 1; + rc = ceil(sqrt(nsl)); + h = []; + + mcmh = create_figure('SCNlab_Montage_Medial'); + colormap gray; + set(mcmh,'Color','w','MenuBar','none'); + ssize = get(0,'screensize'); + set(mcmh,'Position',ssize ./ [0.0303 0.0128 2.0447 1.2577]); + cc = colormap(gray); cc(1:10,:) = repmat([1 1 1],10,1); % [0 0 .3] for dark blue + colormap(cc) + + + % ---------------------------------------- + % * plot first cluster structure + % ---------------------------------------- + + index = plot_cluster(mcmh,cl{1},oimg,rc,V,whsl,myc{1},textx,texty,1,size(oimg),colorlimits); + if ~isempty(XYZpts), ph = plot_points(mcmh,XYZpts,rc,V,whsl,bcolor,myplab); end + % plot points for single-voxel clusters + if isempty(colorlimits) + for i = 1:length(clusters), + try + if clusters(i).numVox == 1, plot_points(mcmh,clusters(i).XYZmm,rc,V,whsl,myc{1},myplab); end + catch + end + end + end + + % ---------------------------------------- + % * plot additional cluster structures + % ---------------------------------------- + + if length(cl) > 1 + for i = 2:length(cl) + index = plot_cluster(mcmh,cl{i},[],rc,V,whsl,myc{i},textx,texty,i,size(oimg),colorlimits); + if isempty(colorlimits) + for j = 1:length(cl{i}), + try + if cl{i}(j).numVox == 1, plot_points(mcmh,cl{i}(j).XYZmm,rc,V,whsl,myc{i},myplab); end + catch + end + end + end + end + end + + + % ---------------------------------------- + % * plot overlap areas + % ---------------------------------------- + + if plotovl + + if ~isempty(XYZmm_both) + bcl.XYZmm = XYZmm_both; bcl.M = cl{1}(1).M; + plot_cluster(mcmh,bcl,[],rc,V,whsl,bcolor,textx,texty,i+1,size(oimg),colorlimits,1); + end + + if ~isempty(XYZmm_all) + bcl.XYZmm = XYZmm_all; bcl.M = cl{1}(1).M; + plot_cluster(mcmh,bcl,[],rc,V,whsl,acolor,textx,texty,i+1,size(oimg),colorlimits,1); + end + + end + + % fix bug at end - replot XYZ slice text + %for z = whsl + % subplot(rc,rc,index); + % zmm = voxel2mm([1 1 z]',V.mat); + % text(textx,texty,['z = ' num2str(zmm(3))],'Color','w') + % zi(z) = zmm(3); + %end + %zi(zi > 0) + + try + enlarge_axes(mcmh) + catch + disp('Error enlarging axes. Skipping.') + end + +end + + + + +% ---------------------------------------- +% +% * Sub-functions +% +% ---------------------------------------- + +function index = plot_cluster(mcmh,clusters,oimg,rc,V,whsl,myc,textx,texty,clind,odims,colorlimits,varargin) + % varargin suppresses end text + + if ~isfield(clusters,'Z'), for i = 1:length(clusters), clusters(i).Z = ones(1,size(clusters(i).XYZmm,2));,end,end + + % take only first row of Z scores + for i = 1:length(clusters), clusters(i).Z = clusters(i).Z(1,:);,end + + + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + % Set up + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + XYZmm = cat(2,clusters.XYZmm); + XYZ = mm2voxel(XYZmm,V,1)'; % no unique suppression + + % MODIFIED FOR MEDIAL VIEW + XYZ = XYZ([2 3 1],:); % reorganize to permuted dims + + % get relative voxel sizes for fill + xs = diag(clusters(1).M(1:3,1:3)); + xs = xs ./ diag(V.mat(1:3,1:3)); + % add 25% to make sure no gaps in plot + xs = xs + .33*xs; + + xs = xs([2 3 1]); % medial! + + if ~isempty(colorlimits) + + if (colorlimits(1) < 1 & colorlimits(2) < 1) | all(colorlimits-mean(colorlimits)) 0); + tmpc = zrange(zrange < 0); + + if ~isempty(tmp) + if colorlimits + zrange = colorlimits; + else + zrange = [min(tmp) max(tmp)]; + end + zh = zrange(1):(zrange(2)-zrange(1))./249:zrange(2); + zh = round(zh*100); + if isempty(zh) % we probably have all same values + zh = ones(1,250); + end + else + zh = []; + end + + if ~isempty(tmpc) + if colorlimits + zrangec = -colorlimits; + else + zrangec = [min(tmpc) max(tmpc)]; + end + zhc = zrangec(1):(zrangec(2)-zrangec(1))./249:zrangec(2); + if isempty(zhc), zhc = 1:length(hc);,end + zhc = round(zhc*100); + if isempty(zhc) % we probably have all same values + zhc = ones(1,250); + end + else, + zhc = []; + end + + if isempty(zh) & isempty(zhc), error('No Z-scores in cluster'),end + + else + + % surface patch method + % ---------------------------------------------------------------------------------------- + vol = voxel2mask(XYZ',odims); + vol = smooth3(vol); + + end + + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + % Loop through slices + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if length(whsl) > 12, fsz = 14;, else, fsz = 18;,end + index = 1; + for z = whsl + set(0,'CurrentFigure',mcmh); + subplot(rc,rc,index); + + + if ~isempty(oimg) + imagesc(oimg(:,:,z)') + set(gca,'YDir','normal'); + hold on; axis image; axis off + xmm = voxel2mm([z 1 1]',V.mat); + text(textx,texty,['x = ' num2str(round(xmm(1)))],'Color','k','FontSize',fsz) + else + hold on + end + + doimgpatch = 0; + + if doimgpatch %isempty(colorlimits) + + % -------------------------------------------------------------------------------- + % solid color patches + % -------------------------------------------------------------------------------- + if z>1, + mvol = vol(:,:,z-1:z); for i = 1:size(mvol,3),myvol(:,:,i) = mvol(:,:,i)';,end + %FVC = isocaps(vol(:,:,z-1:z)',0,'zmax'); + else + mvol = vol(:,:,z:z+1); for i = 1:size(mvol,3),myvol(:,:,i) = mvol(:,:,i)';,end + %FVC = isocaps(vol(:,:,z:z+1)',0,'zmax'); + end + FVC = isocaps(myvol,0,'zmax'); + + try + patch(FVC,'EdgeColor','none','FaceColor',myc,'FaceAlpha',1) + catch + patch(FVC,'EdgeColor','none','FaceColor',myc) + end + + else + % -------------------------------------------------------------------------------- + % color-mapped points + % -------------------------------------------------------------------------------- + + myxyz = XYZ(1:2,XYZ(3,:) == z); + + if ~isempty(colorlimits) + myz = allz(XYZ(3,:) == z); + clear h2, clear wh + end + + %plot(myXYZ(2,:),myXYZ(1,:),[myc 's'],'MarkerFaceColor',myc,'MarkerSize',3) + hold on + for j = 1:size(myxyz,2) + + % added to replace image patch, solid color point fill for pos Z values only + if isempty(colorlimits) + h2(j) = patch([myxyz(1,j) myxyz(1,j) myxyz(1,j)+xs(1) myxyz(1,j)+xs(1)], ... + [myxyz(2,j) myxyz(2,j)+xs(2) myxyz(2,j)+xs(2) myxyz(2,j)],myc, ... + 'EdgeColor','none'); + + else + + if myz(j) < 0 + tmp = find((zhc-round(myz(j)*100)).^2 == min((zhc-round(myz(j)*100)).^2)); + wh(j) = tmp(1); + %h2(j) = plot(myxyz(1,j),myxyz(2,j),'Color',hc(wh(j),:),'MarkerSize',3,'MarkerFaceColor',hc(wh(j),:)); + + + h2(j) = fill([myxyz(1,j) myxyz(1,j) myxyz(1,j)+xs(1) myxyz(1,j)+xs(1)],[myxyz(2,j) myxyz(2,j)+xs(2) myxyz(2,j)+xs(2) myxyz(2,j)],hc(wh(j),:),'EdgeColor','none'); + else + tmp = find((zh-round(myz(j)*100)).^2 == min((zh-round(myz(j)*100)).^2)); + wh(j) = tmp(1); + %h2(j) = plot(myxyz(1,j),myxyz(2,j),'Color',h(wh(j),:),'MarkerSize',3,'MarkerFaceColor',h(wh(j),:)); + h2(j) = fill([myxyz(1,j) myxyz(1,j) myxyz(1,j)+xs(1) myxyz(1,j)+xs(1)],[myxyz(2,j) myxyz(2,j)+xs(2) myxyz(2,j)+xs(2) myxyz(2,j)],h(wh(j),:),'EdgeColor','none'); + end + %if exist('h2') == 1, set(h2,'Marker','square'),end + + end %if isempty colorlimits + end + + end % if solid or color-mapped + + + % text numbers, if plotting text at xyz coords of centers (varargin) + %if length(varargin) > 0 + % cencooz = cencoo(:,cencoo(3,:) == z); + % for i = 1:size(cencooz,2) + % text(cencooz(2,i),cencooz(1,i),num2str(cenind),'Color','k') + % cenind = cenind + 1; + %end + %end + + index = index + 1; + %drawnow + + end % slice loop + + + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + % Text and color bars + % * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + if length(varargin) == 0 + set(0,'CurrentFigure',mcmh); + subplot(rc,rc,index) + a = pwd; a = a(end-6:end); + + try + b = num2str(clusters(1).threshold); + catch + b = 'none'; + end + + axis off + mfig = mcmh; + c = num2str(length(clusters)); + if ~isfield(clusters, 'title') || isempty(clusters(1).title) + clusters(1).title = 'Clusters'; + end + text(0,clind-1,[myc ': ' clusters(1).title ' ' a ' u = ' b ', ' c ' clusters']) + axis([0 1 -1 clind]) + + if ~isempty(colorlimits) + if ~isempty(tmp) + % make color bar + %bfig = figure('Color','w'); + bax = axes('Position',[0.100 0.0800 0.40 0.02]); + hold on; + + if any(allz > 0) + zh2 = zh./100; + for i = 2:size(h,1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)],[0 1 1 0],h(i,:),'EdgeColor','none');, end + plot([min(zh2) max(zh2)],[0 0],'k-') + set(gca,'YTickLabel',''); + %xlabel('Z-score','FontSize',14) + end + end + + if any(allz < 0) + %if ~exist('bfig'), + %bfig = figure('Color','w'); hold on;, + %end + zh2 = zhc./100; + for i = 2:size(hc,1), fill([zh2(i-1) zh2(i-1) zh2(i) zh2(i)],[0 1 1 0],hc(i,:),'EdgeColor','none');, end + plot([min(zh2) max(zh2)],[0 0],'k-') + set(gca,'YTickLabel',''); + %xlabel('Z-score','FontSize',14) + end + + end + + %set(gca,'Position',[0.1300 0.1100 0.7750 0.8150]) + set(gca,'FontSize',16); + axis auto + %xlabel('Z-score','FontSize',16) + %set(gca,'Position',[0.1000 0.4500 0.80 0.50]) + %ssize = get(0,'ScreenSize'); + %set(mcmh,'Position',ssize./[0.0370 0.0014 2.7948 15.2500]) + end + + drawnow +end + + + + +function ph = plot_points(mcmh,XYZmm,rc,V,whsl,myc,myplab) + + XYZ = mm2voxel(XYZmm,V,1)'; % suppress unique voxel output + index = 1; + phind = 1; + + for z = whsl + set(0,'CurrentFigure',mcmh); + subplot(rc,rc,index); + hold on + myXYZ = XYZ(:,XYZ(3,:) == z); + if ~isempty(myplab), myplz = myplab(XYZ(3,:) == z);, end + + for i = 1:size(myXYZ,2) + ph(phind) = plot3(myXYZ(1,i),myXYZ(2,i),100,[myc(1) '.'],'MarkerFaceColor',myc(1),'MarkerSize',8); + + if ~isempty(myplab) + text(myXYZ(1,i),myXYZ(2,i),100,myplz{i},'Color',myc(1)) + end + + phind = phind + 1; + end + + index = index + 1; + %view(0,90) + %plot(0,0,'kd') + end +end + + + + + +function in = scn_mat_conform(in) + %function in = scn_mat_conform(in) + % + % sets flipping to 0 (no flip) in SPM2 and adjusts mat file accordingly + % input in spm-style mat file or struct with .mat or .M fields + % + + global defaults + + if isstruct(in) + if isfield(in,'mat') + in.mat = scn_mat_conform(in.mat); + end + + if isfield(in,'M') + in.M = scn_mat_conform(in.M); + end + + return + end + + + + if in(1) < 0 + disp('Warning: Image has negative x voxel size, indicating ''flipped'' in SPM.') + disp('This will be changed to positive. This program does not do any image flipping.') + + in(1) = abs(in(1)); % voxel size + in(1,4) = -(in(1,4)); % origin offset + end + + if isempty(defaults) || ~isfield(defaults, 'analyze') + spm_defaults + end + + switch spm('Ver') + case 'SPM8' + % no analyze field; flip is always 1 + + case {'SPM5', 'SPM2', 'SPM99'} + if defaults.analyze.flip + disp('Warning: Setting defaults.analyze.flip to 0. No flipping.') + defaults.analyze.flip = 0; + end + otherwise + warning('Unknown version of SPM. Results may be erratic.') + end + +end + \ No newline at end of file diff --git a/Visualization_functions/montage_clusters_points.m b/Visualization_functions/montage_clusters_points.m new file mode 100755 index 00000000..ebcb552b --- /dev/null +++ b/Visualization_functions/montage_clusters_points.m @@ -0,0 +1,152 @@ +function ph = montage_clusters(ovl,clusters,XYZpts,varargin) +% ph = montage_clusters(ovl,clusters,XYZpts,varargin) +% +% Tor Wager +% varargin = additional clusters structures +% XYZpts = XYZ mm coordinates of points to plot +% ph = point handles + +if isempty(ovl), ovl = which('scalped_single_subj_T1.img');, end +myc = {'b' 'r' 'y' 'g' 'm' 'c'}; + +if size(XYZpts,1) ~= 3, XYZpts = XYZpts';,end + +XYZmm = cat(2,clusters.XYZmm); +XYZmm = [XYZmm XYZpts]; + +if length(varargin) > 0 + for i = 1:length(varargin) + cl = varargin{i}; + clXYZmm = cat(2,cl.XYZmm); + XYZmm = [XYZmm clXYZmm]; + end +end + +% ---------------------------------------- +% * overlay image +% ---------------------------------------- +V = spm_vol(ovl); +oimg = spm_read_vols(V); +V.M = V.mat; + +textx = size(oimg,2) - 50; +texty = size(oimg,1) - 6; + +%[array,hdr,h,whichslices,rows,cols,figh] = readim2(ovl,'p'); + +% how many slices, which ones + +XYZ = mm2voxel(XYZmm,V)'; +whsl = unique(XYZ(3,:)); +nsl = length(whsl) + 1; +rc = ceil(sqrt(nsl)); +h = []; + + +figure; colormap gray; set(gcf,'Color','w') + + +% plot first cluster structure + +index = plot_cluster(clusters,oimg,rc,V,whsl,myc{1},textx,texty,1,size(oimg)); +ph = plot_points(XYZpts,rc,V,whsl,myc{2}); + +% plot additional cluster structures + +if length(varargin) > 0 + for i = 1:length(varargin) + cl = varargin{i}; + index = plot_cluster(cl,[],rc,V,whsl,myc{i+1},textx,texty,i+1,size(oimg)); + + end +end + + + +return + + + +% sub-functions +% + +function index = plot_cluster(clusters,oimg,rc,V,whsl,myc,textx,texty,clind,odims) + +XYZmm = cat(2,clusters.XYZmm); +XYZ = mm2voxel(XYZmm,V)'; + +% surface patch method +% ---------------------------------------------------------------------------------------- +vol = voxel2mask(XYZ',odims); +vol = smooth3(vol); + + +index = 1; +for z = whsl + + subplot(rc,rc,index); + + if ~isempty(oimg) + set(gca,'YDir','reverse'); + imagesc(oimg(:,:,z)) + hold on; axis image; axis off + zmm = voxel2mm([1 1 z]',V.mat); + text(textx,texty,['z = ' num2str(zmm(3))],'Color','w') + else + hold on + end + + + if z>1,FVC = isocaps(vol(:,:,z-1:z),0,'zmax'); + else FVC = isocaps(vol(:,:,z:z+1),0,'zmax'); + end + try + patch(FVC,'EdgeColor','none','FaceColor',myc,'FaceAlpha',.7) + catch + patch(FVC,'EdgeColor','none','FaceColor',myc) + end + + % plot method + + %myXYZ = XYZ(1:2,XYZ(3,:) == z); + %plot(myXYZ(2,:),myXYZ(1,:),[myc 's'],'MarkerFaceColor',myc,'MarkerSize',3) + + index = index + 1; + drawnow + +end + +subplot(rc,rc,index) +a = pwd; a = a(end-6:end); +b = num2str(clusters(1).threshold); + +c = num2str(length(clusters)); +text(0,clind-1,[myc ': ' clusters(1).title ' ' a ' u = ' b ', ' c ' clusters']) +axis off +axis([0 1 -1 clind]) + +return + + + +function ph = plot_points(XYZmm,rc,V,whsl,myc) + +XYZ = mm2voxel(XYZmm,V)'; +index = 1; +phind = 1; +for z = whsl + + subplot(rc,rc,index); + hold on + myXYZ = XYZ(:,XYZ(3,:) == z); + + for i = 1:size(myXYZ,2) + ph(phind) = plot3(myXYZ(2,i),myXYZ(1,i),myXYZ(3,i),[myc(1) '.'],'MarkerFaceColor',myc(1),'MarkerSize',8); + phind = phind + 1; + end + + index = index + 1; + +end + +return diff --git a/Visualization_functions/montage_clusters_text.m b/Visualization_functions/montage_clusters_text.m new file mode 100755 index 00000000..a6ee89f9 --- /dev/null +++ b/Visualization_functions/montage_clusters_text.m @@ -0,0 +1,159 @@ +function montage_clusters_text(ovl,clusters,varargin) +% montage_clusters_text(ovl,clusters,varargin) +% +% Tor Wager +% varargin = additional clusters structures +% this function puts text cluster numbers on cluster centers + +% color = cell array of text strings indicating colors {'r' 'g'} etc... + + +if isempty(ovl), ovl = which('scalped_single_subj_T1.img');, end + +myc = {'b' 'r' 'y' 'g' 'm' 'c'}; + +XYZmm = cat(2,clusters.XYZmm); + +if length(varargin) > 0 + for i = 1:length(varargin) + cl = varargin{i}; + clXYZmm = cat(2,cl.XYZmm); + XYZmm = [XYZmm clXYZmm]; + end +end + +% ---------------------------------------- +% * overlay image +% ---------------------------------------- +V = spm_vol(ovl); +oimg = spm_read_vols(V); +V.M = V.mat; + +textx = size(oimg,2) - 50; +texty = size(oimg,1) - 6; + +%[array,hdr,h,whichslices,rows,cols,figh] = readim2(ovl,'p'); + +% how many slices, which ones + +XYZ = mm2voxel(XYZmm,V,2)'; +whsl = unique(XYZ(3,:)); +nsl = length(whsl) + 1; +rc = ceil(sqrt(nsl)); +h = []; + +% get centers for text plotting +cen = getcenters(clusters); + +figure; colormap gray; set(gcf,'Color','w') + + +% plot first cluster structure +for i = 1:length(clusters) + index = plot_cluster(clusters(i),oimg,rc,V,whsl,myc{1},textx,texty,1,size(oimg),cen(i,:),num2str(i)); + drawnow +end + +% plot additional cluster structures + +if length(varargin) > 0 + for i = 1:length(varargin) + cl = varargin{i}; + cen = getcenters(cl); + index = plot_cluster(cl,[],rc,V,whsl,myc{i+1},textx,texty,i+1,size(oimg),cen); + + end +end + + + +return + + + +% sub-functions +% + +function index = plot_cluster(clusters,oimg,rc,V,whsl,myc,textx,texty,clind,odims,varargin) +% var argument is list of centers, x y z are rows, cols are coords + +XYZmm = cat(2,clusters.XYZmm); +XYZ = mm2voxel(XYZmm,V)'; + +% text numbers, if plotting text at xyz coords of centers (varargin) +if length(varargin) > 0 + cencoo = varargin{1}; + cencoo = mm2voxel(cencoo,V)'; + centext = varargin{2}; + %cenind = 1; +end + +% surface patch method +% ---------------------------------------------------------------------------------------- +vol = voxel2mask(XYZ',odims); +vol = smooth3(vol); + + +index = 1; +for z = whsl + + subplot(rc,rc,index); + + if ~isempty(oimg) + set(gca,'YDir','reverse'); + imagesc(oimg(:,:,z)) + hold on; axis image; axis off + zmm = voxel2mm([1 1 z]',V.mat); + text(textx,texty,['z = ' num2str(zmm(3))],'Color','w') + else + hold on + end + + + if z>1,FVC = isocaps(vol(:,:,z-1:z),0,'zmax'); + else FVC = isocaps(vol(:,:,z:z+1),0,'zmax'); + end + try + patch(FVC,'EdgeColor','none','FaceColor',myc,'FaceAlpha',.7) + catch + patch(FVC,'EdgeColor','none','FaceColor',myc) + end + + % text numbers, if plotting text at xyz coords of centers (varargin) + if length(varargin) > 0 + cencooz = cencoo(:,cencoo(3,:) == z); + for i = 1:size(cencooz,2) + text(cencooz(2,i),cencooz(1,i),centext,'Color','k'); %num2str(cenind),'Color','k') + %cenind = cenind + 1; + end + end + + % plot method + + %myXYZ = XYZ(1:2,XYZ(3,:) == z); + %plot(myXYZ(2,:),myXYZ(1,:),[myc 's'],'MarkerFaceColor',myc,'MarkerSize',3) + + index = index + 1; + drawnow + +end + +subplot(rc,rc,index) +a = pwd; a = a(end-6:end); +b = num2str(clusters(1).threshold); + +c = num2str(length(clusters)); +text(0,clind-1,[myc ': ' clusters(1).title ' ' a ' u = ' b ', ' c ' clusters']) +axis off +axis([0 1 -1 clind]) + +return + + +function cen = getcenters(clusters) + for i = 1:length(clusters) + cen(i,:) = clusters(i).mm_center; + end + cen = cen'; +return + diff --git a/Visualization_functions/montage_clusters_text2.m b/Visualization_functions/montage_clusters_text2.m new file mode 100755 index 00000000..3cb6d0eb --- /dev/null +++ b/Visualization_functions/montage_clusters_text2.m @@ -0,0 +1,19 @@ +function montage_clusters_text2(cl) +% montage_clusters_text2(cl) +% tor wager + + +% how many slices, which ones +rc = ceil(sqrt(length(cl))); + + +for i = 1:length(cl) + + subplot(rc,rc,i); + montage_clusters_maxslice([],cl(i),{'r'}); + +end + + +return + diff --git a/Visualization_functions/montage_image_Worsley.m b/Visualization_functions/montage_image_Worsley.m new file mode 100644 index 00000000..5c25f25a --- /dev/null +++ b/Visualization_functions/montage_image_Worsley.m @@ -0,0 +1,163 @@ +function data = montage_image_Worsley(image_name, varargin) +% +% data = montage_image_Worsley(3D or 4D image name) +% +% Make a compact montage of some images +% Designed by Keith Worsley for pca_image.m +% Adapted by Tor Wager, Feb 2008 +% +% Limits and Colormap are designed for component loadings between [-1 1] +% +% Example: +% create_figure('Montage'); +% montage_image_Worsley('test_run1_pca.img'); +% +% data = montage_image_Worsley(imgname, 'pcacov') % changes scaling and colormap +% data = montage_image_Worsley(imgname, 'pcacov', [1 3 5]) % show only +% volumes 1, 3, 5 in image(s) + +%############################################################################ +% COPYRIGHT: Copyright 2002 K.J. Worsley, +% Department of Mathematics and Statistics, +% McConnell Brain Imaging Center, +% Montreal Neurological Institute, +% McGill University, Montreal, Quebec, Canada. +% worsley@math.mcgill.ca +% +% Permission to use, copy, modify, and distribute this +% software and its documentation for any purpose and without +% fee is hereby granted, provided that this copyright +% notice appears in all copies. The author and McGill University +% make no representations about the suitability of this +% software for any purpose. It is provided "as is" without +% express or implied warranty. +%############################################################################ + +% optional inputs + +scaletype = 'pcacor'; % Scale image and colors for PCA component viewing +whichvols = []; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + case 'noscale', scaletype = 'none'; + + case 'pcacov', scaletype = 'pcacov'; + + case 'whichvols', whichvols = varargin{i + 1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +% Get data +% ------------------------------------------------------------------------- +image_name = expand_4d_filenames(image_name); % for SPM2 compatibility +V = spm_vol(image_name); + +if isempty(whichvols) + wh_vols = 1:length(V); + +else + % select only certain vols + V = V(wh_vols); +end + +p = length(V); +go_ok = 1; +if p > 10 + go_ok = input('More than 10 images! Are you sure you want to continue?'); +end +if ~go_ok, return, end + +data = spm_read_vols(V); + + +% Setup stuff +% ------------------------------------------------------------------------- + +numslices = V(1).dim(3); +% p = num comps +numys=V(1).dim(2); +numxs=V(1).dim(1); + xr=1:numxs; + yr=1:numys; + %N=prod(V(1).dim(1:3)); + +nrow=round(sqrt(numslices/3.25/p)); +nrow=max(nrow,1); + +ncol=ceil(numslices/nrow); + +numxr=length(xr); +numyr=length(yr); +bigmat=zeros(p*nrow*numyr,ncol*numxr); + +% Create big mat +% ------------------------------------------------------------------------- +r=0; +for k=1:p + c=0; + for i=1:numslices + bigmat((1:numyr)+r*numyr,(1:numxr)+c*numxr)=flipud(data(xr,yr,i,k)'); + c=c+1; + if c>=ncol && i 0, ovl = varargin{1};,end + +% --------------------------------------------------- + +% --------------------------------------------------- + +% read image +V = spm_vol(P); v = spm_read_vols(V); + +% mask image, if necessary +if ~isempty(maskimg) + disp(['Masking with: ' maskimg]) + VV = spm_vol(maskimg); v2 = spm_read_vols(VV); + v = v .* (v2 > 0); +end + +% convert to p-values +switch type + case 'F' + p = 1 - fcdf(v(:),df(1),df(2)); + direction = 'pos'; + + case 'T' + v(isnan(v)) = 0; + p = 1 - tcdf(v(:),df); + + case 'none' + direction = 'pos'; % for p-image + % do nothing + p = v(:); + %thr = [3 2 1.2]; + colors={[1 1 0] [.9 .5 0] [.7 0 0]}; % pos + + otherwise +end + +cumwh = logical(zeros(size(p))); % define cumulative significant voxels +cumwh2 = logical(zeros(size(p))); % define cumulative significant voxels + +for i = 1:length(thr) + + % find significant voxels - only those sig at this thresh, but not + % higher ones tested previously + + if isinf(thr(i)), + tmp = p; tmp(v==0 | isnan(v))=[]; % eliminate out-of-analysis voxels + pt = FDR(tmp,.05); + + switch type + case 'F', + disp(['Height thresh: F = ' num2str(finv(1-pt,df(1),df(2))) ', p = ' num2str(pt)]) + case 'T', + disp(['Height thresh: T = ' num2str(tinv(1-pt,df(1))) ', p = ' num2str(pt)]) + end + + + else + pt = thr(i); + end + if isempty(pt), pt = -Inf;,end + + switch direction % applies only for t-values!! + case 'pos', + wh = logical(p <= pt); doboth = 0; + case 'neg' + wh = logical(p >= 1-pt); doboth = 0; + case 'both' + wh = logical(p <= pt); + wh2 = logical(p >= 1-pt); doboth = 1; + end + + % special for "none" - just threshold -- this would be for t-image, not + % p-image! + %if strcmp(type,'none'), wh = logical(p >= pt); doboth = 0;,end + + % positive response, or neg only response + mask{i} = zeros(V.dim(1:3)); + mask{i}(wh) = 1; + mask{i}(cumwh) = 0; % voxels previously sig get 0 + + cumwh = cumwh | wh; % update cumulative which sig + + % write an image of it + warning off + V.fname = 'tmp_mask.img'; + spm_write_vol(V,mask{i}); + warning on + + % get clusters + cl{i} = mask2clusters(V.fname); + + + % negative response (do both) + if doboth + mask2{i} = zeros(V.dim(1:3)); + mask2{i}(wh2) = 1; + mask2{i}(cumwh2) = 0; % voxels previously sig get 0 + + cumwh2 = cumwh2 | wh2; % update cumulative which sig + + + % write an image of it + warning off + V.fname = 'tmp_mask.img'; + spm_write_vol(V,mask2{i}); + warning on + + % get clusters + cl2{i} = mask2clusters(V.fname); + end + + !rm tmp_mask.img + !rm tmp_mask.hdr +end + + +% size threshold +for i = 1:length(cl) + if ~isempty(cl{i}), wh = find(cat(1,cl{i}.numVox) < sizes(i));,else, wh = [];,end + if ~isempty(wh), cl{i}(wh) = [];,end +end + +if doboth + for i = 1:length(cl2) + if ~isempty(cl2{i}), wh = find(cat(1,cl2{i}.numVox) < sizes(i));,else, wh = [];,end + if ~isempty(wh), cl2{i}(wh) = [];,end + end +end + + +% add together, if both +if doboth + for i = 1:length(thr) % for legend + if isempty(cl{i}), saveme(i) = 0; else, saveme(i) = 1;,end + end + len = sum(saveme); % for legend + + thr = [thr thr]; + colors = [colors colors2]; + cl = [cl cl2]; +end + +% remove empties +for i = 1:length(thr) + if isempty(cl{i}), saveme(i) = 0; else, saveme(i) = 1;,end +end +saveme = logical(saveme); +cl = cl(saveme); +colors = colors(saveme); +thr = thr(saveme); + + + +% temporary for placebo!!! +%load('/Users/tor/Documents/Tor_Documents/PublishedProjects/2004_Wager_Placebo_science/Current_UM_Study/fmri_results/RESULTS/model5_manip/eight_vs_two_clusters.mat') +%cluster_orthviews(clusters,{[1 0 0]}) + +if isempty(cl), return, end + +% now image clusters +if ~isempty(cl{1}), cluster_orthviews(cl{1},colors(1),'overlay',ovl);, end +%if ~isempty(cl{1}), cluster_orthviews(cl{1},colors(1),'add');, end + +for i = 2:length(cl) + if ~isempty(cl{i}),cluster_orthviews(cl{i},colors(i),'add');, end +end + + + + +% make legend string and legend +for i = 1:length(cl) + lstr = []; + if doboth, + if i <= len, lstr = ['+ '];, + else, lstr = ['- '];, + end + end + if isinf(thr(i)), + lstr = [lstr 'p < .05 FDR'];, + else, + lstr = [lstr 'p < ' num2str(thr(i))]; + end + legstr{i} = lstr; +end + +h = axes('Position',[.55 .2 .25 .2]); hold on; +set(gca,'FontSize',24) +for i = 1:length(colors),hh(i)=plot(0,0,'Color',colors{i},'LineWidth',10);,end +axis off +legend(legstr) + + +% check for cases in which we DO NOT want montage + +if strcmp(type,'none'), return, end + +tmp =[]; +for i = 1:length(cl), tmp = [tmp cat(2,cl{i}.XYZ)];,end +tmp = unique(tmp(3,:)); +if length(tmp) > 30, stopme = input('More than 30 slices in montage. Make montage figure? (1/0) ');,if ~stopme, return,end, end + + +% montage +if length(cl) == 6 + montage_clusters(ovl,cl{1},cl{2},cl{3},cl{4},cl{5},cl{6},colors); +elseif length(cl) == 5 + montage_clusters(ovl,cl{1},cl{2},cl{3},cl{4},cl{5},colors); +elseif length(cl) == 4 + montage_clusters(ovl,cl{1},cl{2},cl{3},cl{4},colors); +elseif length(cl) == 3 + montage_clusters(ovl,cl{1},cl{2},cl{3},colors); +elseif length(cl) == 2 + montage_clusters(ovl,cl{1},cl{2},colors); +elseif length(cl) == 1 + montage_clusters(ovl,cl{1},colors); +end + +cl{1}(1).thr = thr; +cl{1}(1).colors = colors; +%set(gcf,'Position',[ 1722 -19 715 756]) + + + +% montage medial slices + + +tmp = unique(tmp(1,:)); +if length(tmp) > 30, stopme = input('More than 30 slices in medial montage. Make montage figure? (1/0) ');,if ~stopme, return,end, end + +if length(cl) == 6 + montage_clusters_medial(ovl,cl{1},cl{2},cl{3},cl{4},cl{5},cl{6},colors); +elseif length(cl) == 5 + montage_clusters_medial(ovl,cl{1},cl{2},cl{3},cl{4},cl{5},colors); +elseif length(cl) == 4 + montage_clusters_medial(ovl,cl{1},cl{2},cl{3},cl{4},colors); +elseif length(cl) == 3 + montage_clusters_medial(ovl,cl{1},cl{2},cl{3},colors); +elseif length(cl) == 2 + montage_clusters_medial(ovl,cl{1},cl{2},colors); +elseif length(cl) == 1 + montage_clusters_medial(ovl,cl{1},colors); +end + +cl{1}(1).thr = thr; +cl{1}(1).colors = colors; +%set(gcf,'Position',[ 1722 -19 715 756]) + +return + + + \ No newline at end of file diff --git a/Visualization_functions/multi_threshold2.m b/Visualization_functions/multi_threshold2.m new file mode 100644 index 00000000..ba5cbdd7 --- /dev/null +++ b/Visualization_functions/multi_threshold2.m @@ -0,0 +1,331 @@ +% cl = multi_threshold(P, type, df, ['overlay', overlay file name], ['thresholds', thrs], ['large montage prompt', 0|1], ['title', titlestring], ['save images', 0|1]) +% +% F contrast: df = xSPM.df; +% type = 'F', 'T' or 'none' +% + + +function cl = multi_threshold2(P, type, df, varargin) + + % --------------------------------------------------- + % defaults + % --------------------------------------------------- + % Colors + + red = [1 0 0]; + yellow = [1 1 0]; + orange = [.9 .5 0]; + orange2 = [1 .6 .1]; + orange3 = [1 .7 .3]; + buff = [.7 .6 .4]; + dkred = [.7 .3 .1]; + + blue = [0 0 1]; + ltblue = [0 .5 .9]; + ltblue2 = [.2 .7 1]; + aqua = [0 .5 .5]; + dkblue = [0 .1 .7]; + dkblue2 = [0 .2 .5]; + + % low thresh colors + colors = {yellow orange3 orange2}; + colors2 = {blue ltblue ltblue2}; + + + thr = [Inf .001 .005]; % Inf = FDR, thr should be increasing p-values + sizes = [3 5 10]; % minimum sizes + direction = 'both'; % 'pos', 'neg', 'both' + ovl = []; + prompt_for_large_montages = 0; + saving_images = 0; + maskimg = []; % empty, or specify image name + + + for i=1:length(varargin) + if(ischar(varargin{i})) + switch(varargin{i}) + case {'ovl', 'overlay'} + ovl = varargin{i+1}; + case 'large montage prompt' + prompt_for_large_montages = varargin{i+1}; + case 'title' + fig_title = varargin{i+1}; + case {'saveimages' 'save images'} + saving_images = varargin{i+1}; + case 'thresholds' + thr = varargin{i+1}; + end + end + end + + + + % read image + V = spm_vol(P); + v = spm_read_vols(V); + + % mask image, if necessary + if ~isempty(maskimg) + disp(['Masking with: ' maskimg]) + + % sample mask data in space of input images + vmask = scn_map_image(maskimg, P(1,:)); + + % make sure mask is 1's or 0's + vmask = double(vm > 0); + + v = v .* vmask; + + end + + % convert to p-values + switch type + case 'F' + p = 1 - fcdf(v(:), df(1), df(2)); + direction = 'pos'; + case 'T' + v(isnan(v)) = 0; + p = 1 - tcdf(v(:), df); + case {'P' 'p' 'none'} + direction = 'pos'; % for p-image + % do nothing + p = v(:); + %thr = [3 2 1.2]; + colors = {[1 1 0] [.9 .5 0] [.7 0 0]}; % pos + end + + cumwh = false(size(p)); % define cumulative significant voxels + cumwh2 = false(size(p)); % define cumulative significant voxels + + for i = 1:length(thr) + % find significant voxels - only those sig at this thresh, but not + % higher ones tested previously + + if isinf(thr(i)) + tmp = p; + tmp(v == 0 | isnan(v)) = []; % eliminate out-of-analysis voxels + pt = FDR(tmp, .05); + + switch type + case 'F' + disp(['Height thresh FDR: F = ' num2str(finv(1-pt, df(1), df(2))) ', p = ' num2str(pt)]) + case 'T' + disp(['Height thresh FDR: T = ' num2str(tinv(1-pt, df(1))) ', p = ' num2str(pt)]) + end + else + pt = thr(i); + end + + if isempty(pt), pt = -Inf; end + + switch direction % applies only for t-values!! + case 'pos', + wh = logical(p <= pt); + doboth = 0; + case 'neg' + wh = logical(p >= 1-pt); + doboth = 0; + case 'both' + wh = logical(p <= pt); + wh2 = logical(p >= 1-pt); + doboth = 1; + end + + % special for "none" - just threshold -- this would be for t-image, not + % p-image! + %if strcmp(type, 'none'), wh = logical(p >= pt); doboth = 0;, end + + % positive response, or neg only response + mask{i} = zeros(V.dim(1:3)); + mask{i}(wh) = 1; + mask{i}(cumwh) = 0; % voxels previously sig get 0 + + cumwh = cumwh | wh; % update cumulative which sig + + % write an image of it + V.fname = 'tmp_mask.img'; + spm_write_vol(V, mask{i}); + + % get clusters + cl{i} = mask2clusters(V.fname); + + + % negative response (do both) + if doboth + mask2{i} = zeros(V.dim(1:3)); + mask2{i}(wh2) = 1; + mask2{i}(cumwh2) = 0; % voxels previously sig get 0 + + cumwh2 = cumwh2 | wh2; % update cumulative which sig + + + % write an image of it + V.fname = 'tmp_mask.img'; + spm_write_vol(V, mask2{i}); + + % get clusters + cl2{i} = mask2clusters(V.fname); + end + + % delete is slower, but cross-platform + delete('tmp_mask.img'); + delete('tmp_mask.hdr'); + end + + + % size threshold + for i = 1:length(cl) + if ~isempty(cl{i}) + wh = find(cat(1, cl{i}.numVox) < sizes(i)); + else + wh = []; + end + + if ~isempty(wh) + cl{i}(wh) = []; + end + end + + if doboth + for i = 1:length(cl2) + if ~isempty(cl2{i}) + wh = find(cat(1, cl2{i}.numVox) < sizes(i)); + else + wh = []; + end + + if ~isempty(wh) + cl2{i}(wh) = []; + end + end + end + + + % add together, if both + if doboth + num_thr = length(thr); + for i = 1:num_thr % for legend + if isempty(cl{i}), saveme(i) = 0; else saveme(i) = 1; end + end + len = sum(saveme); % for legend + + thr = [thr thr]; + colors = [colors(1:min(num_thr, length(colors))) colors2(1:min(num_thr, length(colors)))]; + cl = [cl cl2]; + end + + % remove empties + for i = 1:length(thr) + if isempty(cl{i}), saveme(i) = 0; else saveme(i) = 1; end + end + saveme = logical(saveme); + cl = cl(saveme); + colors = colors(saveme); + thr = thr(saveme); + + + if isempty(cl), return; end + + % save clusters + disp(['Saving clusters at multiple thresholds in multi_clusters.mat']); + fprintf(1,'To re-create orthviews, load the file and execute the lines below:\n'); + fprintf(1,'e.g.,\nif ~isempty(cl{1}), cluster_orthviews(cl{1}, colors(1), ''overlay'', ovl); end\n'); + fprintf(1,'for i = 2:length(cl), if ~isempty(cl{i}), cluster_orthviews(cl{i}, colors(i), ''add''); end, end\n'); + + save multi_clusters cl colors ovl + + % now display image clusters + if ~isempty(cl{1}), cluster_orthviews(cl{1}, colors(1), 'overlay', ovl); end + for i = 2:length(cl) + if ~isempty(cl{i}), cluster_orthviews(cl{i}, colors(i), 'add'); end + end + + + % make legend string and legend + for i = 1:length(cl) + lstr = []; + if doboth + if i <= len + lstr = '+ '; + else + lstr = '- '; + end + end + if isinf(thr(i)), + lstr = [lstr 'p < .05 FDR']; + else + lstr = [lstr 'p < ' num2str(thr(i))]; + end + legstr{i} = lstr; + end + + h = axes('Position', [.55 .2 .25 .2]); hold on; + set(gca, 'FontSize', 24) + for i = 1:length(colors) + hh(i)=plot(0, 0, 'Color', colors{i}, 'LineWidth', 10); + end + axis off + legend(legstr); + + + % check for cases in which we DO NOT want montage + + if strcmp(type, 'none'), return; end + + tmp = []; + for i = 1:length(cl) + tmp = [tmp cat(2, cl{i}.XYZ)]; + end + tmp = unique(tmp(3, :)); + + if(prompt_for_large_montages && length(tmp) > 30) + cont = input('More than 30 slices in montage. Make montage figure? (1/0) '); + if(~cont) + return + end + + end + + % montage + mch = montage_clusters(ovl, cl{:}, colors); + + if(~isempty(mch)) + axial_fig_title = sprintf('%s - axial', fig_title); + set(mch, 'Name', axial_fig_title); + uicontrol(mch, 'Style', 'text', 'String', axial_fig_title, 'Units', 'normalized', 'Position', [0 .97 1, .03], ... + 'BackgroundColor', 'white', 'HorizontalAlignment', 'center', 'FontUnits', 'normalized', 'FontSize', .9); + if(saving_images) + saveas(mch, [strrep(axial_fig_title, '/', '') '.png']); + end + end + cl{1}(1).thr = thr; + cl{1}(1).colors = colors; + + + + % montage medial slices + tmp = unique(tmp(1, :)); + if(prompt_for_large_montages && length(tmp) > 30) + cont = input('More than 30 slices in medial montage. Make montage figure? (1/0) '); + if ~cont + return + end + end + + + mcmh = montage_clusters_medial(ovl, cl{:}, colors); + + if(~isempty(mcmh)) + medial_fig_title = sprintf('%s - medial', fig_title); + set(mcmh, 'Name', medial_fig_title); + uicontrol(mcmh, 'Style', 'text', 'String', medial_fig_title, 'Units', 'normalized', 'Position', [0 .97 1, .03], ... + 'BackgroundColor', 'white', 'HorizontalAlignment', 'center', 'FontUnits', 'normalized', 'FontSize', .9); + if(saving_images) + saveas(mcmh, [strrep(medial_fig_title, '/', '') '.png']); + end + end + cl{1}(1).thr = thr; + cl{1}(1).colors = colors; +end + + diff --git a/Visualization_functions/nmdsfig.m b/Visualization_functions/nmdsfig.m new file mode 100755 index 00000000..9f2fb809 --- /dev/null +++ b/Visualization_functions/nmdsfig.m @@ -0,0 +1,419 @@ +function f1 = nmdsfig(pc,varargin) +% f1 = nmdsfig(pc,[opt. inputs in any order]) +% +%------------------------------------------------------------------------ +% +% Create a 1-D or 2-D plot with stimulus coordinates +% +%------------------------------------------------------------------------ +% +% % reserved keywords, each followed by appropriate input: +% case 'classes', clus = varargin{i+1}; +% case 'names', names = varargin{i+1}; +% case 'sig', sigmat = varargin{i+1}; +% case 'thr', thr = varargin{i+1}; +% case 'legend', legmat = varargin{i+1}; +% case 'sig2', sigmat2 = varargin{i+1};a +% sigmat2 can be thresholded at multiple values in thr +% case 'colors', colors = varargin{i+1}; +% case 'sizes', sizes = varargin{i+1}; +% case 'sigonly' plot regions with significant connections only +% case 'nolines', do not plot lines (lines plotted by default, but only if +% sigmat is entered) +% 'linethickness', followed by matrix of line thickness values +% NOTE: can enter sig matrix with non-zero values equal to line +% thickness and use for both sig and linethickness inputs +% % but thickness values should be scaled to integers for line thickness +% Creates a figure only if f1 output is requested +% 'fill', fill in areas around groups +% +% pc is objects x dimensions +% clus is a vector of object classes +% names is cell array of names for rows of pc (objects), or empty ([]) +% +% sig is optional matrix of 1, -1, and 0 entries +% signifies which pairs to connect with lines +% positive elements are solid lines, negative elements are dashed +% -- Can be a series of t-maps in 3-D array +% +% [opt] threshold vector of critical t-values, e.g., [2.2 5.4] +% If used, enter t-maps in sigmat +% +% [opt] a 2nd sigmat, if entered, will plot dashed lines instead of solid +% ones. this is used by cluster_nmdsfig to plot interactions between +% covariance and behavioral scores +% +% % Figure creation: +% If existing fig with tag 'nmdsfig', activates +% Otherwise, if fig handle requested as output, creates +% or if not, uses current figure. +% +% New examples: c is output of cluster_nmdsfig +% ---------------------------------------------- +% sizes = sum(c.STATS.sigmat); +% f1 = nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',p_vs_c_heat.sig,'legend',{'Pos' 'Neg'},'sizes',sizes,'sizescale',[4 12]); +% +% add length legend +% f1 = +% nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',p_vs_c_heat.sig,'legend',{'Pos' 'Neg'}, ... +% 'sizes',sizes,'sizescale',[4 12],'lengthlegend',c.r); +% +% Auto size scaling based on number of connections: +% f1 = +% nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',c.STATS.sigmat,'legend',{'Pos' 'Neg'},'sizescale',[4 12],'lengthlegend',c.r); +% +% f1 = +% nmdsfig(c.GroupSpace,'classes',c.ClusterSolution.classes,'names',c.names,'sig',p_vs_c_heat.sig,'legend',{'Pos' 'Neg'},'sizescale',[4 16],'sigonly'); +% +% SEE ALSO: cluster_nmdsfig + +% -------------------------------------- +% set up defaults +% -------------------------------------- +pc = double(pc); + +nobj = size(pc,1); +clus = ones(1,nobj); +for i = 1:size(pc,1), names{i} = ['V' num2str(i)]; end +sigmat = []; +sigmat2 = []; +thr = 0; +dolines = 1; + + +linecolor(1,:) = [0 0 0]; % first line color, 'positive' +linecolor(2,:) = [0 .3 1]; % second line color, 'negative' + +linestyle{1} = '-'; % first line style, positive +linestyle{2} = '-'; % second line style, negative + + +legmat={'positive','negative'}; +colors = {'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; +sizes = 6 * ones(nobj,1); +dosizescale = 0; +dolengthlegend = 0; +selectpoints = 0; +dofill = 0; +dolinescale = 0; + +for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % reserved keywords + case 'classes', clus = varargin{i+1}; + case 'names', names = varargin{i+1}; varargin{i + 1} = []; + case 'sig', sigmat = varargin{i+1}; + case 'thr', thr = varargin{i+1}; + case 'legend', legmat = varargin{i+1}; + case 'sig2', sigmat2 = varargin{i+1}; + case 'colors', colors = varargin{i+1}; + case {'size', 'sizes'}, sizes = varargin{i+1}; + case 'sizescale', dosizescale = 1; minmax = varargin{i+1}; + case 'lengthlegend', dolengthlegend = 1; rmatx = varargin{i+1}; + case 'sigonly', selectpoints = 1; + case 'nolines', dolines = 0; + case 'fill', dofill = 1; + + case {'linescale', 'linethickness'}, dolinescale = 1; thicknessvals = varargin{i + 1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +thr = sort(thr); % thresholds in ascending order, dark to light + +% color stuff +while max(clus) > length(colors), colors = [colors colors]; end +if length(colors{1}) == 2 + colormode = 'text'; +elseif length(colors{1}) == 3 + colormode = 'vector'; +else + error('I don''t understand the format of the colors input.'); +end + +% size stuff: sigmoid scaling +%sizes = sum(c.STATS.sigmat); +if length(sizes) == 1 + sizes = repmat(sizes, nobj, 1); +end + +if dosizescale + % if we haven't entered sizes but we want to scale, assume we want to + % use sig. connections + if ~isempty(sigmat) + sizes = sum(abs(sigmat)); + end + if ~isempty(sigmat2) % sig2 connections count for 1/2, remove already sig ones + sizes = sizes + .5 * sum(abs(sigmat2 .* ~sigmat)); + end + rg = abs(diff(minmax)); + zsz = zscore(sizes); + sizes = min(minmax) + rg * (1 ./ (1+exp(-3*zsz))); +end + +% -------------------------------------- +%%% remove extra dimensions for plotting +% -------------------------------------- +if size(pc,2) > 2 + disp('Using only first 2 dimensions of pc for plotting'); +elseif size(pc,2)==1; + disp('switching to 1D configuration plot'); + nmdsfig1D(pc,clus,names,varargin); + return +end +pc=pc(:,1:2); + + +% -------------------------------------- +%%% draw figure and set tag +% -------------------------------------- +% Figure creation: +% If existing fig with tag 'nmdsfig', activates +% Otherwise, if fig handle requested as output, creates +% or if not, uses current figure. + +f1 = findobj('Tag', 'nmdsfig'); % look for existing +if ~isempty(f1) && strcmp(get(f1,'Type'), 'figure') + % we have one already, just clear + figure(f1); +elseif nargout > 0 + f1 = create_figure; +else f1 = gcf; +end +set(f1,'Tag','nmdsfig'); + + +% -------------------------------------- +%%% fill areas, if requested +% -------------------------------------- +if dofill + areaHandles = nmdsfig_fill(clus, pc, colors); +end + + + +% -------------------------------------- +% set up colour and line thickness parameters +% -------------------------------------- + + +% % % if length(thr) > 1, +% % % sigmat = repmat(sigmat,[1 1 length(thr)]); +% % % % choose colors +% % % sigcol = [.5 0]; %.5:-.5./length(thr):0; +% % % else +% % % sigcol = 0; % black, width = 3 +% % % end + +% -------------------------------------- +% if multiple thr are entered, treats sigmat as a series of t-value maps +% replicate sigmat and threshold. +% draw AFTER 2ND THRESHOLD +% -------------------------------------- +if ~isempty(sigmat) && dolines + + sigmat = double(sigmat); + + for i = 1:length(thr) + wh = sign(sigmat(:,:,i)) .* (abs(sigmat(:,:,i)) >= thr(i)); % preserve pos or neg t-values + + if i == 1, sigfirstthr = wh; end + + line_handles = nmdsfig_tools('drawlines',pc, wh, linecolor, linestyle); + + hh1 = line_handles.hhp; + hh2 = line_handles.hhn; + + %[hh1,hh2] = drawlines(pc,wh,sigcol(i),legmat); + end + + + if dolinescale + pindx = 1; + nindx = 1; + + for i = 1 : size(pc,1) + for j = i+1 : size(pc,1) + + if wh(i,j) > 0 + set(hh1(pindx), 'LineWidth', thicknessvals(i, j)); + pindx = pindx + 1; + + elseif wh(i,j) < 0 + set(hh2(nindx), 'LineWidth', thicknessvals(i, j)); + nindx = nindx + 1; + + end + + end + end + end + +else + hh1 = []; hh2 = []; +end + + +% -------------------------------------- +% if a 2nd sigmat is entered, plot dotted lines +% +% -------------------------------------- +if ~isempty(sigmat2) && dolines + + sigmat2 = double(sigmat2); + + for i = 1:length(thr) + wh = sign(sigmat2(:,:,i)) .* (abs(sigmat2(:,:,i)) >= thr(i)); % preserve pos or neg t-values + wh = wh .* ~(logical(sigfirstthr)); % omit sig in first sigmat, first thresh + + line_handles = nmdsfig_tools('drawlines',pc, wh, linecolor, [{':'} {':'}]); + hh3 = line_handles.hhp; + hh4 = line_handles.hhn; + + %[hh3,hh4] = drawlines(pc,wh,sigcol(i),legmat,[0 0 0; 0 .5 1],[{':'} {':'}]); + end + + legmat = [legmat legmat]; + hh = [hh1 hh2 hh3 hh4]; + +end + +% -------------------------------------- +% if multiple thr are entered, treats sigmat as a series of t-value maps +% replicate sigmat and threshold, then draw +% -------------------------------------- +if ~isempty(sigmat) && dolines + sigmat = double(sigmat); + for i = 1:length(thr) + wh = sign(sigmat(:,:,i)) .* (abs(sigmat(:,:,i)) >= thr(i)); % preserve pos or neg t-values + + line_handles = nmdsfig_tools('drawlines',pc,wh, linecolor, linestyle); + hh1 = line_handles.hhp; + hh2 = line_handles.hhn; + %[hh1,hh2] = drawlines(pc,wh,sigcol(i),legmat); + end +else + hh1 = []; hh2 = []; +end + +% do legend for 2nd +if ~isempty(sigmat2) && dolines + wh = zeros(1,4); + if isempty(hh1), wh(1) = 1; end + if isempty(hh2), wh(2) = 1; end + if isempty(hh3), wh(3) = 1; end + if isempty(hh4), wh(4) = 1; end + legmat(find(wh)) = []; + legend(hh,legmat); +end + +% -------------------------------------- +% plot points +% -------------------------------------- +plotthispoint = ones(nobj,1); +if selectpoints + plotthispoint = (sum(abs(sigmat)) | sum(abs(sigmat2))); +end + +hold on +for j = 1:nobj + if plotthispoint(j) + switch colormode + case 'text' + plot(pc(j,1),pc(j,2),colors{clus(j)},'MarkerFaceColor',colors{clus(j)}(1),'LineWidth',2,'MarkerSize' ,sizes(j)) + case 'vector' + plot(pc(j,1),pc(j,2),'o','Color',colors{clus(j)},'MarkerFaceColor',colors{clus(j)},'LineWidth',2,'MarkerSize' ,sizes(j)) + end + end +end +if ~isempty(names) + for i = 1:nobj + if plotthispoint(i) + text(pc(i,1) + .025 * [max(pc(:,1))-min(pc(:,1))],pc(i,2)+.0,names{i},'Color','b','FontSize',18); + end + end +end + +xlabel('Component 1'),ylabel('Component 2') +%set(gca,'XTickLabel',{[]}); +%set(gca,'YTickLabel',{[]}); + +axis equal; +axis image; + +if dolengthlegend + nmdsfig_legend(pc,rmatx); +end + +if dofill + set(findobj(f1, 'Type', 'Text'),'Color','k','FontWeight','b') +end + +return + +% NOW IN nmdsfig_tools('drawlines') +% +% % % function [hhp,hhn] = drawlines(pc,sigmat,sigcol,legmat,varargin) +% % % % function [hhp,hhn] = drawlines(pc,sigmat,sigcol,legmat,[color],[style]) +% % % % +% % % % pc is stim coords +% % % % sigmat is matrix of which lines to draw +% % % % sigcol is multiplier to scale line width (0 is fixed width) +% % % % sigcol is a scalar between zero and one, lower is 'more salient' +% % % % legmat is not used now (for legend stuff) +% % % % +% % % % Example: +% % % % [hh3,hh4] = drawlines(pc,wh,sigcol(i),legmat,[.5 .5 .5; 0 1 1],[{':'} +% % % % {':'}]); +% % % +% % % % sigcol is a scalar between zero and one, lower is 'more salient' +% % % hold on +% % % +% % % % linew +% % % lw = 2; +% % % +% % % color(1,:) = [0 0 0]; % first line color, 'positive' +% % % color(2,:) = [0 .5 1]; % second line color, 'negative' +% % % style{1} = '-'; % first line style +% % % style{2} = '-'; % second line style +% % % +% % % if nargin < 3, sigcol = 0; end +% % % if length(varargin) > 0, color = varargin{1}; end +% % % if length(varargin) > 1, style = varargin{2}; sigcol = 0; end +% % % +% % % hhp=[];hhn=[]; +% % % for i=1:size(pc,1); +% % % for j=1:size(pc,1); +% % % if sigmat(i,j) > 0 +% % % hhp(end+1) = line([pc(i,1) pc(j,1)],[pc(i,2) pc(j,2)]); +% % % set(hhp,'Color',color(1,:) + [1 1 1] * abs(sigcol),'LineStyle',style{1},'LineWidth',lw - (lw*sigcol)-1) +% % % elseif sigmat(i,j) < 0 +% % % hhn(end+1) = line([pc(i,1) pc(j,1)],[pc(i,2) pc(j,2)]); +% % % set(hhn,'Color',color(2,:) + abs([sigcol sigcol 0]),'LineStyle',style{2},'LineWidth',lw - (lw*sigcol)-1) +% % % end +% % % end +% % % end +% % % %legend ([hhp hhn],legmat); +% % % +% % % % store in gui data for later deletion, etc. +% % % figh = gcf; +% % % linehandles = [hhp hhn]; +% % % data = guidata(figh); +% % % if ~isfield(data,'linehandles') +% % % data.linehandles = linehandles; +% % % else +% % % data.linehandles = [data.linehandles linehandles]; +% % % end +% % % guidata(figh,data); +% % % +% % % return + + + +function f1 = create_figure +scnsize = get(0,'ScreenSize'); +f1 = figure('position',round([50 50 scnsize(3)./2 scnsize(3)./2]),'color','white'); +return diff --git a/Visualization_functions/nmdsfig_fill.m b/Visualization_functions/nmdsfig_fill.m new file mode 100644 index 00000000..bbc2132a --- /dev/null +++ b/Visualization_functions/nmdsfig_fill.m @@ -0,0 +1,77 @@ +function hh = nmdsfig_fill(varargin) + % nmdsfig_fill + % Purpose: to take information about objects in multidimensional space + % and draw colored contours around them. + % Used within nmdsfig.m + % + % Usage: + % 1) If c is a structure from cluster_nmdsfig, which is compatible with + % nmdsfig_tools, then: + % hh = nmdsfig_fill(c) + % Fields used are classes, groupSpace, and colors (see code for + % details) + % + % 2) Pass in arguments directly: + % hh = nmdsfig_fill(classes, positions, colors) + % classes is n x 1 vector of group assignments (or all ones for one + % group) + % positions is n x 2 matrix of x, y coordinates + % colors is a cell containing as many colors as classes, {'r' 'g' 'b' ...} + % or {[1 0 0] [0 1 0] [0 0 1] ...} + % + % by tor wager, feb 07 + % + % Other notes: + % fills area around coordinates of regions in a class (group) in a color + % using spline interpolation and other stuff. + % designed for cluster imaging in nmdsfig figures. + % Uses fill_area_around_points, within which + % Many choices can be set that control how fills are done. + % tor recommends .2 for borderscale... +% +% Example: +% load nmdsfig_output +% hh = nmdsfig_fill(c) +% set(findobj('Type','Line'), 'Color', 'k') + +hh = []; +if nargin == 0, help nmdsfig_fill, return, end + +if isstruct(varargin{1}) + c = varargin{1}; + clas = c.ClusterSolution.classes; + positions = c.GroupSpace; + + if isfield(c, 'colors') + colors = c.colors; + else + colors = {'ro' 'go' 'bo' 'yo' 'co' 'mo' 'ko' 'r^' 'g^' 'b^' 'y^' 'c^' 'm^' 'k^'}; + end +else + clas = varargin{1}; + positions = varargin{2}; + colors = varargin{3}; +end + + + for i = 1:max(clas) + + coords = positions(clas == i, 1:2); + + if ischar(colors{i}) + h = fill_area_around_points(coords(:,1), coords(:,2), .2, colors{i}(1)); + else + h = fill_area_around_points(coords(:,1), coords(:,2), .2, colors{i}); + end + + if ~isempty(h) && all(ishandle(h)) + delete(h(2)) % line + + hh(i) = h(1); + end + + end + + drawnow + +end diff --git a/Visualization_functions/nmdsfig_legend.m b/Visualization_functions/nmdsfig_legend.m new file mode 100644 index 00000000..fc6f7465 --- /dev/null +++ b/Visualization_functions/nmdsfig_legend.m @@ -0,0 +1,44 @@ +function nmdsfig_legend(X,r) +% nmdsfig_legend(X,r) +% +% X is stim coords, r is correlation coefficient matrix +% nmds figure should be current fig. +% +% nmdsfig_legend(c.ClusterSolution.X,c.r) + +% get x limit and dims of current nmds fig. +xsz = get(gcf,'Position'); xfig = xsz(1); xsz = xsz(3); +xlim = get(gca,'XLim'); +xlim = [-.05 diff(xlim)-.05]; % shift it over + +d=pdist(X)'; +r=r-eye(size(r)); r=squareform(r)'; +b = pinv([r ones(size(r,1),1)]) * d; + +x = [.9 .7 .5]; %0 -.5 -.7 -.9]; +n = length(x); +y = b(1) * x + b(2); + +figure('Color','w'); +hold on; + +shiftx = .05; + +for i = 1:n + if x(i) < 0, mycol = 'c'; else mycol = 'k'; end + + plot([0 y(i)],[i i],mycol,'LineWidth',2); + text(y(i) + shiftx,i,['r = ' num2str(x(i))],'FontSize',14) +end + +po = get(gcf,'Position'); +po(1) = xfig; +po(3) = xsz; +po(4) = 80; % fix yaxis to be small +set(gcf,'Position',po); +set(gca,'XLim',xlim,'YLim',[-.5 n+.5]); + +axis off +scn_export_papersetup(100) + +return diff --git a/Visualization_functions/plotDesign.m b/Visualization_functions/plotDesign.m new file mode 100755 index 00000000..9e9e9dba --- /dev/null +++ b/Visualization_functions/plotDesign.m @@ -0,0 +1,172 @@ +function [X,d,out,handles] = plotDesign(ons,rt,TR,varargin) +% [X,d,out,handles] = plotDesign(ons,rt,TR,varargin) +% +% simple function to plot a design +% plots regressors and color-coded onset times as little sticks, with RT represented as height of the stick +% +% ons is a cell array of onset times in s OR a delta indicator function matrix +% rt is a cell array of rts for each onset event +% TR is the repetition time for sampling, in s +% optional argument is the y offset for plotting rts, default = 2 +% +% returns the model matrix (X) and the delta function d +% +% optional arguments +% 1 yoffset: default is 2 +% 2 vector of epoch durations in sec for each trial type, default is events +% +% examples: +% plot epochs of different lengths stored in conditions(*).stimlength +% [X3,d] = plotDesign(evtonsets,[],1,2,cat(2,conditions.stimlength)); + +rtin = rt; % original rt, to tell if rt is values or empty +yoffset = 2; +durs = []; out = []; +colors = {'r' 'g' 'b' 'c' 'm' 'y'}; +samefig = 0; +basisset = 'hrf'; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + % functional commands + case 'yoffset', yoffset = varargin{i+1}; + case 'durs', durs = varargin{i+1}; durs = durs ./ TR; + + case {'color', 'colors'}, colors = varargin{i+1}; + case 'samefig', samefig = 1; + case 'basisset', basisset = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +% build models + +if iscell(ons) + if ~isempty(durs) + for i = 1:length(ons) + if length(durs) == 1 + xons{i} = [ons{i} repmat(durs, size(ons{i}, 1), 1)]; + end + end + else + xons = ons; % xons is what we need to pass in to handle dur option + end + + [X,d] = onsets2fmridesign(xons,TR, [], basisset); +else + % note: will not convolve properly with durs + if ~isempty(durs) + warning('Must enter cell array of onsets if using dirs'); + end + X = getPredictors(ons,spm_hrf(TR)./max(spm_hrf(TR))); + d = ons; clear ons + for i = 1:size(d,2) + ons{i} = (find(d(:,i)) - 1) .* TR; + end +end + +if ~isempty(rtin), + [X2,d2,out] = rt2delta(ons,rt,TR); +else + % placeholder for plotting only + for i = 1:length(ons), rt{i} = 1000 * ones(size(ons{i})); end +end + +while size(X,2) > length(colors), colors = [colors colors]; end + + +% make figure + +if ~samefig, figure('Color','w'); end + +if ~isempty(rtin), subplot(4,1,1); end +set(gca,'FontSize',16); hold on; +handles = []; + +for i = 1:length(ons) + + if ischar(colors{i}) + handles(i) = plot(X(:,i),colors{i}); + h = plot([ons{i} ons{i}]'./(TR),[repmat(-yoffset,length(rt{i}),1) rt{i}]'./(1000.*TR) - yoffset,colors{i}); + + else + handles(i) = plot(X(:,i),'Color', colors{i}); + h = plot([ons{i} ons{i}]'./(TR),[repmat(-yoffset,length(rt{i}),1) rt{i}]'./(1000.*TR) - yoffset,'Color', colors{i}); + + end + + + if ~isempty(durs) + for j = 1:length(ons{i}) + hh = drawbox(ons{i}(j)./TR,durs(i),colors{i},yoffset); + end + end + +end + +title('Predicted activity') + + +% plot 2 + +if ~isempty(rtin) + + subplot(4,1,2); set(gca,'FontSize',16); hold on; + for i = 1:length(ons) + + plot(out.rtlinearX(:,i),colors{i}) + h = plot([ons{i} ons{i}]'./(TR),[repmat(-yoffset,length(rt{i}),1) rt{i}]'./(1000.*TR) - yoffset,colors{i}); + + end + title('Activity x reaction time (linear)') + + subplot(4,1,3); set(gca,'FontSize',16); hold on; + for i = 1:length(ons) + + plot(out.rtquadX(:,i),colors{i}) + h = plot([ons{i} ons{i}]'./(TR),[repmat(-yoffset,length(rt{i}),1) rt{i}]'./(1000.*TR) - yoffset,colors{i}); + + end + title('Activity x reaction time (quadratic)') + + subplot(4,1,4); set(gca,'FontSize',16); hold on; + ind = 1; + for i = 1:length(ons) + + hh(1) = plot(out.rtclassX(:,ind),colors{i},'LineStyle','-'); ind = ind + 1; + hh(2) = plot(out.rtclassX(:,ind),colors{i},'LineStyle','--'); ind = ind + 1; + hh(3) = plot(out.rtclassX(:,ind),colors{i},'LineStyle',':'); ind = ind + 1; + + %tmp = find(out.rtclass(:,ind)); + %h = plot([tmp tmp]'./(TR),[repmat(-yoffset,length(rt{i}),1) rt{i}]'./(1000.*TR) - yoffset,colors{i}); + + end + + legend(hh,{'Fast' 'Medium' 'Slow'}) + xlabel('Time (TRs)') + title('Activity for trials classified by RT') + +else + % single plot, add x label + if TR == 1, xlabel('Time (s)'), else xlabel('Time (TRs)'),end +end + + +return + + + +function h1 = drawbox(time,dur,color,yoffset) + +x = [0 1 1 0]; x = x * dur + time; +y = [0 0 1 1] - yoffset; + +h1 = fill(x,y,color,'FaceAlpha',.5,'EdgeColor','none'); + +return + diff --git a/Visualization_functions/plot_correlation.m b/Visualization_functions/plot_correlation.m new file mode 100644 index 00000000..9a729208 --- /dev/null +++ b/Visualization_functions/plot_correlation.m @@ -0,0 +1,190 @@ +function h = plot_correlation(X,Y,varargin) +% handles = plot_correlation(X,Y,varargin) +% +% plots robust or OLS simple or partial correlations +% replaces prplot and plot_correlation_samefig +% +% +% X is matrix of columns of interest plus nuisance +% default is to plot partial effect of 1st column +% Y is one or more columns of data +% +% Optional inputs: +% 'robust', robust IRLS plot +% 'noprint', suppress text output +% 'doquad', quadratic term; not tested, may not work +% +% % 'col', followed by column of interest +% 'labels', followed by cell array of text labels for each obs. +% 'colors', followed by cell array of colors for each column of Y +% 'ylabel', followed by y-axis label string +% 'xlabel', followed by x-axis label string +% 'weights', followed by weights that override any computed ones +% +% tor wager, august 2006 +% +% Examples: +% Plot robust partial corr. 2 of X against col. 17 of Y, controlling for other X +% figure; h = plot_correlation(X,Y(:,17),'col',2,'robust','ylabel','Brain +% data','xlabel','Order effect'); +% +% Plot Col. 1 of X vs. Y in red squares +% figure; h = plot_correlation(X,Y(:,17),'robust','colors',{'rs'}); + +%tor_fig; + +% --------------------------------------------- +% default behaviors +% --------------------------------------------- +dorobust = 0; +doprint = 1; +wh_interest = 1; +doquad = 0; +mylabels = []; +mycol = {'ko' 'rv' 'bs' 'gd' 'y^' 'cv' 'mx'}; +ylabelstr = 'Contrast beta value'; +xlabelstr = 'Behavioral score'; + + +% --------------------------------------------- +% Optional inputs +% --------------------------------------------- + +for i = 1:length(varargin) + if isstr(varargin{i}) + switch varargin{i} + % reserved keywords + case 'robust', dorobust = 1; + case 'noprint', doprint = 0; + case 'doquad', doquad = 1; + + % functional commands + case 'labels', labels = varargin{i+1}; + case 'colors', mycol = varargin{i+1}; + case 'ylabel', ylabelstr = varargin{i+1}; varargin{i+1} = []; + case 'xlabel', xlabelstr = varargin{i+1}; varargin{i+1} = []; + case 'col', wh_interest = varargin{i+1}; + case 'weights', myweights = varargin{i+1}; + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + +% --------------------------------------------- +% intercept +% --------------------------------------------- +wh_intercept = find(all(diff(X) < eps)); +if isempty(wh_intercept) + wh_intercept = size(X,2) + 1; + X(:,wh_intercept) = 1; +end + +ny = size(Y,2); +while length(mycol) < ny, mycol = [mycol mycol]; end + +for i = 1:ny + [x,y,r,p,se,meany,stats] = partialcor(X,Y(:,i),wh_interest,doprint,dorobust); + + % b(1) is param, b(2) is intercept for partial plot + b = stats.b([wh_interest wh_intercept]); + + % weights + w = stats.w; + if exist('myweights','var'), w = myweights; end + + h{i} = makefigure(x,y,mycol{i},mylabels,doquad,b,w,xlabelstr,ylabelstr); + + text(min(X(:,1)),max(y),sprintf('r = %3.2f',r),'FontSize',16,'Color',mycol{i}(1)); + +end + + +return + + + + + + + + + + +function h = makefigure(xvec,yvec,mycol,mylabels,doquad,b,varargin) +% h = makefigure(xvec,yvec,mycol,mylabels,doquad,b,[weights],[xlabel],[ylabel]) + +ylabelstr = 'Contrast beta value'; +xlabelstr = 'Behavioral score'; + +hold on; grid on; set(gca,'FontSize',18) + +if length(varargin) > 0 + % ROBUST (or we just have weights) + w = varargin{1}; + for i = 1:length(xvec) + h(i) = plot(xvec(i),yvec(i),mycol,'LineWidth',.5,'MarkerSize',8, ... + 'MarkerFaceColor',mycol(1)); + set(h(i),'MarkerFaceColor',[repmat(1-w(i),1,3)] ) + end + + % set axis + xlims = [min(xvec) max(xvec)]; + ylims = [min(yvec) max(yvec)]; + xrange = (xlims(2) - xlims(1)) *.1; % % margin + yrange = (ylims(2) - ylims(1)) *.1; + xlims = xlims + [-xrange xrange]; + ylims = ylims + [-yrange yrange]; + + set(gca,'Xlim',xlims,'YLim',ylims); + + % plot regression line by hand + xl = get(gca,'Xlim'); yl = get(gca,'Ylim'); + x = xl(1)-5:xl(2)+5; + h2 = plot(x,b(1) * x + b(2),'-','Color',mycol(1),'LineWidth',2); + h = [h h2]; + set(gca,'XLim',xl,'YLim',yl) + + +else + % not robust + + h = plot(xvec,yvec,mycol,'LineWidth',3,'MarkerSize',6,'MarkerFaceColor',mycol(1)); + + if doquad + %refcurve(doquad) + else + try + refline + catch + tmp = get(gca,'XLim'); + x = min(xvec)-std(xvec):min(std(xvec),.01):max(xvec)+std(xvec); + plot(x,b(1) * x + b(2),[mycol(1) '-'],'LineWidth',.5) + set(gca,'XLim',tmp); + + end + end + +end + + +drawnow + +if length(varargin) > 1 + xlabelstr = varargin{2}; +end + +if length(varargin) > 2 + ylabelstr = varargin{3}; +end + +if ~isempty(mylabels) + for j = 1:length(xvec) + text(xvec(j),yvec(j),mylabels{j},'FontWeight','b') + end +end + +ylabel(ylabelstr) +xlabel(xlabelstr) + +return \ No newline at end of file diff --git a/Visualization_functions/plot_correlation_samefig.m b/Visualization_functions/plot_correlation_samefig.m new file mode 100755 index 00000000..2b78e262 --- /dev/null +++ b/Visualization_functions/plot_correlation_samefig.m @@ -0,0 +1,249 @@ +function [r,str,sig,ploth] = plot_correlation_samefig(xvec,yvec,varargin) +% [r,infostring,sig,h] = plot_correlation_samefig(xvec,yvec,[textlabs],[color],[doquad],[dorobust]) +% varargin is string of text labels +% +% for text labels only, try: +% plot_correlation(beh1,mri1,highlow,'w.'); +% +% doquad: flag for quadratic correlations as well! +% dorobust: remove n outliers from data, using Min Cov Determinant (MCD) +% Rousseeuw, P.J. (1984), "Least Median of Squares Regression," +% Journal of the American Statistical Association, Vol. 79, pp. 871-88 +% outliers calculated using IRLS (robustfit.m) do not work well. +% you enter n +% +% empty variable arguments are OK, defaults will be used +% +% figure; [r, infos] = plot_correlation_samefig(x, y, [], 'ko', 0, 1); +% +% tor wager + +robopt = 'IRLS'; %'IRLS' or 'MCD' + +xvec = double(xvec); +yvec = double(yvec); + +r = [];, str=[];sig=[]; hh=[]; + +if length(varargin) > 0, mylabels = varargin{1}; else mylabels = []; end +if length(varargin) > 1, mycol = varargin{2}; else mycol = 'ko'; end +if length(varargin) > 2, doquad = varargin{3}; else doquad = 0; end +if length(varargin) > 3, dorobust = varargin{4}; else dorobust = 0; end +if isempty(mycol), mycol = 'ko'; end + + if size(xvec,2) > size(xvec,1) && size(xvec,1)==1, xvec = xvec'; end + if size(yvec,2) > size(yvec,1) && size(yvec,1)==1, yvec = yvec'; end + + + wh = find(isnan(xvec) | isnan(yvec)); + xvec(wh,:) = []; yvec(wh) = []; + + + X = [xvec ones(length(xvec),1)]; + y = (yvec); + + % robust regression - remove outliers + if dorobust & strcmp(robopt,'MCD') + + %if doquad + %tmp = xvec .^2; tmp = tmp-mean(tmp); + %[res]=fastmcd_noplot([tmp X(:,1) y]); + %else + [res]=fastmcd_noplot([X(:,1) y]); + %end + + % remove n most extreme outliers and recompute correlation + + wh = res.flag==0; nout = sum(res.flag==0); + y(wh) = []; X(wh,:) = []; xvec(wh) = []; yvec(wh) = []; + + %tmp = [(1:length(y))' stats.w]; tmp=sortrows(tmp,2); wh=tmp(1:dorobust,1); + + %keyboard + %for i = 1:length(y),lab{i}=num2str(stats.w(i)); end + % makefigure(xvec,yvec,'wo',lab,doquad) + % hold on; plot(xvec(wh),yvec(wh),'ro') + %end + ploth = makefigure(X(:,1),y,mycol,mylabels,doquad,b); + + + elseif dorobust & strcmp(robopt,'IRLS') + + % to get b and stats in original scale + [b,stats]=robustfit(X,y,'bisquare',[],'off'); + rZ = stats.t(1); rp = stats.p(1); + if rp < .05, sig=1; else,sig=0; end + + % correlation (r) is NOT the beta of standardized variables, as + % with OLS, because the standardized beta doesn't account for the + % change in covariance due to the weights. + [r] = weighted_corrcoef([X(:,1) y],stats.w); + r = r(1,2); + + ploth = makefigure(X(:,1),y,mycol,mylabels,doquad,b,stats.w); + + else + % ols + b = X \ y; + ploth = makefigure(X(:,1),y,mycol,mylabels,doquad,b); + end + + + + + % text and stats + + + + + if dorobust & strcmp(robopt,'IRLS') + + str = sprintf('Sig. of B0: u=%3.2f, t=%3.2f, p=%3.4f\n C: u=%3.2f, t=%3.2f, p=%3.4f R: r=%3.2f, t=%3.2f, p=%3.4f', ... + mean(y),stats.t(end),stats.p(end),b(1), stats.t(1), stats.p(1), r, rZ, rp); + + %text(min(X(:,1)),max(y),sprintf('r = %3.2f',r),'FontSize',16) + + + else + % not robust IRLS + + % in regression, test significance of intercept parameter + y2 = (y - X*b); % subtract b1, the regression fit + y2 = y2 + mean(y); % add intercept back in + % Old, not necessary to do it this way... + + try + [h,b0p,ci,b0stats] = ttest(y2); + catch + disp('No ttest.m: No stats toolbox?') + end + + + % separate correlation and t-test + try + [h,p,ci,stats] = ttest(y); + catch + disp('No ttest.m: No stats toolbox?') + end + + r = corrcoef(y,X(:,1)); r= r(1,2); + + try + [rci,sig,rZ,rp] = r2z(r,length(y),.05); + catch + disp('Error in or missing r2z.m: No stats toolbox?') + end + + text(min(X(:,1)),max(y),sprintf('r = %3.2f',r),'FontSize',16) + + try + str = sprintf('Sig. of B0: u=%3.2f, t=%3.2f, p=%3.4f\n C: u=%3.2f, t=%3.2f, p=%3.4f R: r=%3.2f, Z=%3.2f, p=%3.4f', ... + mean(y2),b0stats.tstat,b0p,mean(y), stats.tstat, p, r, rZ, rp); + catch + str = ['Missing stats']; + sig = NaN; + end + if dorobust && strcmp(robopt,'MCD'), str=[str sprintf(' N_o_u_t=%3.0f',nout)]; end + + end % Robust or not + + title(str,'FontSize',12) + + + if doquad + tmp = xvec .^ 2; tmp = tmp - mean(tmp); + X = [tmp xvec ones(length(xvec),1)]; + X(isnan(xvec),:) = []; + y = (yvec); + y(isnan(xvec)) = []; + + [b,bint,dummy,rint,stats] = regress(y,X,.05); + + r0 = r; + r = sqrt(stats(1)); + + mysig = sum(sign(bint),2); + for i = 1:size(b,1),if mysig(i),sigstr{i}='*'; else sigstr{i}=''; end, end + + str = sprintf('y = %3.2fX^2%s + %3.2fX%s + %3.2f%s, r_0=%3.2f, Mult. r=%3.2f, Omni F=%3.2f, p=%3.2f', ... + b(1),sigstr{1},b(2),sigstr{2},b(3),sigstr{3},r0, r, stats(2),stats(3)); + + sig = stats(3) < .05; + if dorobust & strcmp(robopt,'MCD'), str=[str sprintf(' N_o_u_t=%3.0f',nout)]; end + + title(str,'FontSize',12) + refcurve(b) + + end + + return + + + + + + function h = makefigure(xvec,yvec,mycol,mylabels,doquad,b,varargin) + + hold on; grid on; set(gca,'FontSize',18) + + if length(varargin) > 0 + % ROBUST + w = varargin{1}; + for i = 1:length(xvec) + h = plot(xvec(i),yvec(i),mycol,'LineWidth',.5,'MarkerSize',8, ... + 'MarkerFaceColor',mycol(1)); + set(h,'MarkerFaceColor',[repmat(1-w(i),1,3)] ) + set(h, 'Color', [.2 .2 .2], 'LineWidth',1) + end + + % set axis + xlims = [min(xvec) max(xvec)]; + ylims = [min(yvec) max(yvec)]; + xlims = xlims + xlims .* .2; + ylims = ylims + ylims .* .2; + set(gca,'Xlim',xlims,'YLim',ylims); + + % plot regline by hand + xl = get(gca,'Xlim'); yl = get(gca,'Ylim'); + x = xl(1)-5:xl(2)+5; + plot(x,b(1) * x + b(2),'k-','LineWidth',.5) + set(gca,'XLim',xl,'YLim',yl) + + + else + % not robust + + h = plot(xvec,yvec,mycol,'LineWidth',3,'MarkerSize',6,'MarkerFaceColor',mycol(1)); + set(h, 'Color', [.2 .2 .2], 'LineWidth',1) + + if doquad + %refcurve(doquad) + else + try + refline + catch + tmp = get(gca,'XLim'); + x = min(xvec)-std(xvec):min(std(xvec),.01):max(xvec)+std(xvec); + plot(x,b(1) * x + b(2),[mycol(1) '-'],'LineWidth',.5) + set(gca,'XLim',tmp); + + end + end + + end + + + drawnow + + + + if ~isempty(mylabels) + for j = 1:length(xvec) + text(xvec(j),yvec(j),mylabels{j},'FontWeight','b') + end + end + + ylabel('Contrast beta value') + xlabel('Behavioral score') + + return \ No newline at end of file diff --git a/Visualization_functions/plot_dx_hrfs.m b/Visualization_functions/plot_dx_hrfs.m new file mode 100755 index 00000000..c200b3c0 --- /dev/null +++ b/Visualization_functions/plot_dx_hrfs.m @@ -0,0 +1,295 @@ +function EXPT = plot_dx_hrfs(EXPT,clusters,varargin) +% EXPT = plot_dx_hrfs(EXPT,clusters,[dolegend],[dosave],[dosmooth],[doindiv]) +% +% uses EXPT.FIR and clusters +% +% If not found, creates: +% EXPT.FIR.regsofinterest = trial types +% EXPT.FIR.mcol = colors +% +% Seems to be a problem with showing the brain slice when it makes the +% legend as well! Weird bug. Optional argument turns legend off. +% +% Optional inputs (all defaults are zero): +% dolegend 1 on, 0 off +% dosave 1 saves tiff files in timecourse_plots subdir, 0 does not +% dosmooth n smooths hrfs and re-calculates st. errors, 0 plots saved +% values stored in clusters.HRF.HRF and .STE +% doindiff plot low vs. high groups of individuals (indiv diffs) +% +% see extract_dxbeta_data.m +% plot_dx_hrfs_indiffs.m +% +% Examples: +% plot_dx_hrfs(EXPT,cl(1),0,1,3); % has some smoothing (0 weight @ 3 time +% pts) + +if length(varargin) > 0, dolegend = varargin{1}; else, dolegend = 0;, end +if length(varargin) > 1, dosave = varargin{2}; else, dosave = 0;, end +if length(varargin) > 2, dosmooth = varargin{3}; else, dosmooth = 0;, end +if length(varargin) > 3, doindiv = varargin{4}; else, doindiv = 0;, end + +if ~isfield(EXPT.FIR,'mcol') + disp(EXPT.FIR.dxnames) + wh = input(['Enter vector of conditions to plot: ']); + mcol = input('Enter vector of colors, e.g. {''ro-'' ''gd'' etc.}: '); + EXPT.FIR.regsofinterest = wh; + EXPT.FIR.mcol = mcol; +else + wh = EXPT.FIR.regsofinterest; + mcol = EXPT.FIR.mcol; + mcol = mcol(wh); +end + +if ~isfield(EXPT.FIR,'TR') + EXPT.FIR.TR = input('Enter TR in s: '); +end + +% smoothing, if specified +if dosmooth + fprintf(1,'Re-averaging cluster HRF and STE: Smoothing = %3.0f\n',dosmooth); + clusters = dosmoothing(dosmooth,clusters,EXPT); +else + disp('Using existing HRFs in clusters.HRF.HRF and .STE . No change in baseline subtraction will take effect.'); +end + + for j = 1:length(clusters) + + + if ~isempty(wh) + + if doindiv + h = do_indiff_plot(clusters,j,wh,EXPT.FIR.TR,mcol); + else + h = do_group_plot(clusters,j,wh,EXPT.FIR.TR,mcol); + end + + if dolegend + legend(h,EXPT.FIR.dxnames(wh)); + end + + drawnow + + if dosave, savetiff(j), end + + + end + end + + +return + + +% ------------------------------------------------ +% MAIN FUNCTION TO MAKE GROUP PLOTS +% ------------------------------------------------ + + +function h = do_group_plot(clusters,j,wh,TR,mcol) + + + + + + figure('Color','w'); + %h = axes('Position',[0.1100 0.1100 0.3347 0.8150]); + subplot(1,2,1) + montage_clusters_maxslice([],clusters(j),{'r'}) + + %h = axes('Position',[0.6300 0.1100 0.3347 0.8150]); % + subplot(1,2,2) + hold on;set(gca,'FontSize',18),xlabel(['Time (s)']) + + for k = 1:length(wh) + %eval(['tmp = clusters(j).HRF.HRF' wh{k} ';']) + str = (['tmp = clusters(j).HRF.HRF{' num2str(wh(k)) '};']); + eval(str) + + x = 0:TR:length(tmp)*TR-1; + + h(k) = plot(x,tmp,mcol{k},'LineWidth',2); + + % to plot peak error bar + %ww = find(tmp==max(tmp)); + %tor_bar_steplot(tmp(ww),mean(clusters(j).HRF.STE{wh(k)}),mcol(wh(k)),x(ww)-1); + + % to plot transparent fill + fill_around_line(tmp,clusters(j).HRF.STE{wh(k)},mcol{k},x); + + + end + %title(num2str(round(clusters(j).mm_center))) + + %subplot(1,3,3) + %h = bar(clusters(j).HRF.BASE); set(h,'FaceColor',[.8 .8 .8]); + %tor_bar_steplot(clusters(j).HRF.BASE,clusters(j).HRF.BASESTE,{'k'}); + %title('Baseline estimates') + %set(gcf,'Position',[109 215 1099 501]) + + return + + +% ------------------------------------------------ +% SAVE A TIFF FILE OF CLUSTER IN APPROPRIATE DIR +% ------------------------------------------------ + + function savetiff(clnum) + + + if ~(exist('timecourse_plots') == 2) + mkdir timecourse_plots + end + + clnum = num2str(clnum); + + % get unique name for this file + n = 1; + name = ['timecourse_cl' clnum '_' num2str(n) '.tiff']; + while exist([pwd filesep 'timecourse_plots' filesep name]) == 2 + n = n + 1; + name = ['timecourse_cl' clnum '_' num2str(n) '.tiff']; + end + + saveas(gcf,['timecourse_plots' filesep name],'tiff'); + + return + + +% ------------------------------------------------ +% SMOOTHING AND RE-AVERAGING ON BETAS +% ------------------------------------------------ + + function clusters = dosmoothing(smlen,clusters,EXPT) + % dosmooth(smoothing length) + % with exponential function, as in smooth_timeseries + + st = cumsum([1 EXPT.FIR.numframes]); + en = st(2:end) - 1; % ending values + st = st(1:end-1); % starting values + + % set up baseline + if isfield(EXPT.FIR,'baseline') + base = EXPT.FIR.baseline; + else + base = 0; + end + if isempty(base), base = 0;, end + + + for j = 1:length(clusters) + + for i = 1:length(st) + + % get individual hrfs for THIS condition + dat = clusters(j).HRF.indiv(st(i):en(i),:); + + [dat,V] = smooth_timeseries(dat,smlen); + + + % subtract baseline, if specified + if base + bvals = nanmean(dat(base,:)); + bvals = repmat(bvals,size(dat,1),1); + dat = dat - bvals; + end + + clusters(j).HRF.HRF{i} = nanmean(dat'); + clusters(j).HRF.STE{i} = ste(dat'); + + + % hi/low individual diffs, if saved + if isfield(clusters(j).HRF,'hihrf') + % high group + dat = clusters(j).HRF.hihrf{i}; + [dat] = smooth_timeseries(dat',smlen)'; + clusters(j).HRF.hiHRF{i} = nanmean(dat); + clusters(j).HRF.hiSTE{i} = ste(dat); + end + if isfield(clusters(j).HRF,'lowhrf') + % high group + dat = clusters(j).HRF.lowhrf{i}; + [dat] = smooth_timeseries(dat',smlen)'; + clusters(j).HRF.lowHRF{i} = nanmean(dat); + clusters(j).HRF.lowSTE{i} = ste(dat); + end + + end + end + + return + + +% ------------------------------------------------ +% MAIN FUNCTION TO MAKE INDIV DIFF PLOTS +% ------------------------------------------------ + + +function h = do_indiff_plot(clusters,j,wh,TR,mcol) + + % LOW + + if ~isempty(wh) + figure('Color','w'); subplot(1,3,1) + title(num2str(round(clusters(j).mm_center))) + montage_clusters_maxslice([],clusters(j),{'r'}) + + ah = subplot(1,3,2); + hold on;set(gca,'FontSize',18),xlabel(['Time (s)']) + + for k = 1:length(wh) + str = (['tmp = clusters(j).HRF.lowHRF{' num2str(wh(k)) '};']); + eval(str) + + x = 0:TR:length(tmp)*TR-1; + + h(k) = plot(x,tmp,mcol{k},'LineWidth',2); + + % to plot peak error bar + %ww = find(tmp==max(tmp)); + %tor_bar_steplot(tmp(ww),mean(clusters(j).HRF.STE{wh(k)}),mcol(wh(k)),x(ww)-1); + + % to plot transparent fill + fill_around_line(tmp,clusters(j).HRF.lowSTE{wh(k)},mcol{k},x); + + + end + title(['Low participants']) + %legend(h,EXPT.FIR.dxnames(wh)); + + + % HIGH + + ah(2) = subplot(1,3,3); + + hold on;set(gca,'FontSize',18),xlabel(['Time (s)']) + + for k = 1:length(wh) + str = (['tmp = clusters(j).HRF.hiHRF{' num2str(wh(k)) '};']); + eval(str) + + x = 0:TR:length(tmp)*TR-1; + + h(k) = plot(x,tmp,mcol{k},'LineWidth',2); + + % to plot peak error bar + %ww = find(tmp==max(tmp)); + %tor_bar_steplot(tmp(ww),mean(clusters(j).HRF.STE{wh(k)}),mcol(wh(k)),x(ww)-1); + + % to plot transparent fill + fill_around_line(tmp,clusters(j).HRF.hiSTE{wh(k)},mcol{k},x); + + + end + title(['High participants']) + %legend(h,EXPT.FIR.dxnames(wh)); + + + equalize_axes(ah); + set(gcf,'Position',[109 215 1099 501]) + + drawnow + end + + return + + \ No newline at end of file diff --git a/Visualization_functions/plot_error.m b/Visualization_functions/plot_error.m new file mode 100644 index 00000000..8e21ea67 --- /dev/null +++ b/Visualization_functions/plot_error.m @@ -0,0 +1,119 @@ +% PLOT_ERROR Plot a matrix with shaded error area around it +% PLOT_ERROR(X, Y) plots matrix Y against x-values in X, where Y is a matrix with each row representing a signal. +% The shaded area represents the standard error across the columns of Y. +% +% PLOT_ERROR(Y) plots the data matrix Y versus its index. +% +% PLOT_ERROR(..., 'errorData', errorData) uses external data for the error areas. In this case, Y is assumed to be a mean +% timeseries already. Y and errorData must be vectors of the same length. +% +% PLOT_ERROR(..., 'allowNaNs', [0|1]) indicates whether to handle NaNs in the data. If set, plot_error will use nanmean, +% nanstd, etc. Off by default. +% +% PLOT_ERROR(..., colorSpecString) plots the line according to the designated ColorSpec string, and shades the error area +% by the color of the line +% +% PLOT_ERROR(AX, ...) plots into the axes designated by the AX axes handle. +% +% [line_handle patch_handle] = PLOT_ERROR return the handle of the main line object + +function [line_handle patch_handle] = plot_error(varargin) + using_external_error_data = 0; + plotArgs = {}; + meanFun = @mean; + stdFun = @std; + skip_next_arg_for_plot = 0; + + if(isempty(varargin)) + error('No arguments to %s', mfilename); + end + + args = varargin; + if(ishandle(args{1})) + h = args{1}; + if(~strcmp(get(h, 'Type'), 'axes')) + error('Handle %d must be an axes handle.', h); + end + args(1) = []; + end + + if(isempty(args{1}) || (~isvector(args{1}) && ~ismatrix(args{1}))) + error('First argument (if not an axes handle) must be a matrix or vector'); + elseif(length(args) > 1 && isvector(args{1}) && ismatrix(args{2}) && length(args{1}) == size(args{2}, 2)) + x = args{1}; + y = args{2}; + args(1:2) = []; + else + y = args{1}; + x = 1:size(y, 2); + args(1) = []; + end + x = x(:); + + for i=1:length(args) + if(ischar(args{i})) + switch(args{i}) + case 'errorData' + if(~isvector(y) || ~isvector(args{i+1}) || (length(y) ~= length(args{i+1}))) + error('If passing in external error data, Y and the error data must be vectors of equal length'); + end + meanData = y; + errorData = args{i+1}; + using_external_error_data = 1; + skip_next_arg_for_plot = 1; + case 'allowNaNs' + meanFun = @nanmean; + stdFun = @nanstd; + skip_next_arg_for_plot = 1; + otherwise + if(skip_next_arg_for_plot) + skip_next_arg_for_plot = 0; + else + plotArgs{end+1} = args{i}; + end + end + else + if(skip_next_arg_for_plot) + skip_next_arg_for_plot = 0; + else + plotArgs{end+1} = args{i}; + end + end + end + + if(~using_external_error_data) + if(isvector(y)) + error('Y is a vector. Nothing meaningful can be drawn. Either pass in a matrix, or pass in external error data to plot around Y.'); + end + meanData = meanFun(y); + errorData = stdFun(y) / sqrt(size(y, 1)); + end + + if(~exist('h', 'var') || isempty(h)) + h = gca(); + end + line_handle = plot(h, x, meanData, plotArgs{:}); + hold(h, 'on'); + xVertices = [x; flipud(x)]; + yVertices = [meanData+errorData meanData(end:-1:1)-errorData(end:-1:1)]; + patch_handle = fill(xVertices, yVertices, get(line_handle, 'Color'), 'EdgeColor', 'none', 'FaceAlpha', .25, 'Parent', h); + hold(h, 'off'); +end + + +% ISMATRIX: Returns 1 if the input matrix is 2+ dimensional, 0 if it is a scalar +% or vector. +% +% Usage ismat = ismatrix(X) +% +% RE Strauss, 5/19/00 + +function ismat = ismatrix(X) + [r,c] = size(X); + if (r>1 && c>1) + ismat = 1; + else + ismat = 0; + end + +end diff --git a/Visualization_functions/plot_horizontal_line.m b/Visualization_functions/plot_horizontal_line.m new file mode 100644 index 00000000..c309fc2a --- /dev/null +++ b/Visualization_functions/plot_horizontal_line.m @@ -0,0 +1,9 @@ +function han = plot_horizontal_line(y,color) + % function han = plot_horizontal_line(y,color) + + if nargin < 2, color = 'k'; end + xlim = get(gca,'XLim'); + han = plot(xlim, [y y], color); + + return + \ No newline at end of file diff --git a/Visualization_functions/plot_joint_hist_contour.m b/Visualization_functions/plot_joint_hist_contour.m new file mode 100644 index 00000000..196ee2de --- /dev/null +++ b/Visualization_functions/plot_joint_hist_contour.m @@ -0,0 +1,71 @@ +function [h, z] = plot_joint_hist_contour(z, xbins, ybins, color, varargin) +% plot a 95% 2-D density region for a 2-D histogram +% +% h = plot_joint_hist_contour(z, xbins, ybins, color, ['confval', confval], ['maxalpha', maxalpha]) +% +% Inputs: +% z = 2-D histogram values, counts in bins. See joint_hist.m +% confval = optional input; [0 - 1], retain this proportion of values in confidence region +% maxalpha = optional; [0 - 1], maximum transparency +% +% Outputs: +% h = handle to graphical contour object +% z = thresholded z matrix of counts +% +% Examples: +% ------------------------------------------------------------------- +% z = joint_hist(nnmfscores{i}{j}(:, 1),nnmfscores{i}{j}(:, 2), 50, 'noplot'); +% h = plot_joint_hist_contour(z, [0 0 1]); + +confval = .95; +maxalpha = 1; + +%if ~isempty(varargin), confval = varargin{1}; end + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'confval', 'alpha'}, confval = varargin{i+1}; + case 'maxalpha', maxalpha = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + +clear cnt +for i = 1:max(z(:)) + cnt(i) = sum(z(z <= i)); +end + +crit = find(cnt <= (1 - confval) * nansum(z(:))); + +%% +z(z <= max(crit)) = NaN; + +[~, h] = contourf(xbins, ybins, z); + +set(h, 'LineColor', 'none') + +hh = get(h, 'Children'); + +%% +vals = get(hh, 'CData'); +vals = cat(1, vals{:}); +vals = maxalpha .* vals ./ max(vals); + +%set(hh, 'FaceColor', 'b') +%set(hh, 'FaceAlpha', 'interp') + +for i = 1:length(hh) + + if ~isnan(vals(i)) + set(hh(i), 'FaceColor', color, 'FaceAlpha', vals(i)) + end + %set(hh(i), 'AlphaDataMapping', 'scaled', 'AlphaData', get(hh(i), 'CData'), 'FaceAlpha', 'interp') + %set(hh(i), 'AlphaDataMapping', 'scaled', 'AlphaData', get(hh(i), 'CData')) + %set(hh(i), 'FaceAlpha', 'interp') +end + +end diff --git a/Visualization_functions/plot_matrix_cols.m b/Visualization_functions/plot_matrix_cols.m new file mode 100644 index 00000000..38b41ad9 --- /dev/null +++ b/Visualization_functions/plot_matrix_cols.m @@ -0,0 +1,60 @@ +function han = plot_matrix_cols(X, varargin) +% +% Plot line plots showing each column of a matrix as a vertical or +% horizontal line +% +% han = plot_matrix_cols(X) +% plot_matrix_cols(X, 'horiz') +% plot_matrix_cols(X, 'vertical') + +% within denoising, plot_matrix_cols(X, 'denoising') to make red plots +% +% tor wager, feb 07 + +meth = 'horiz'; +if ~isempty(varargin), meth = varargin{1}; end + +[t, k] = size(X); + +% scale vals in each col of X to range = 1 +X = scale(X, 1); +rng = range(X); +X = X ./ rng(ones(t, 1), :); + +% plot +hold on; +set(gca, 'YLim', [0 k+1]); + +for i = 1:k + + switch meth + case {'horiz', 'horizontal', 'h'} + plot(1:t, i, 'Color', [.5 .5 .5]); + % negative b/c y dir is reversed + han(i) = plot(1:t, -X(:, i) + i, 'k'); + + + + case {'vert', 'vertical', 'v'} + + plot(i, 1:t, 'Color', [.5 .5 .5]); + + han(i) = plot(X(:, i) + i, 1:t, 'k'); + + + case {'denoising'} + plot(1:t, i, 'Color', [1 0 0]); + % negative b/c y dir is reversed + han(i) = plot(1:t, -X(:, i) + i, 'r'); + + otherwise + error('Unknown method.'); + end + + drawnow + +end + +set(gca, 'YDir', 'Reverse'); + +end \ No newline at end of file diff --git a/Visualization_functions/plot_model_fit.m b/Visualization_functions/plot_model_fit.m new file mode 100644 index 00000000..9b760c00 --- /dev/null +++ b/Visualization_functions/plot_model_fit.m @@ -0,0 +1,82 @@ +function plot_hrf_model_fit(m,TR,bf,varargin) +% plot_hrf_model_fit(m,TR,bf,[stim input function for epoch, in 1 s resolution]) +% +% if m is an hrf curve sampled at 1 s, +% plot_model_fit(m,1,'fir'); +% plot_model_fit(m,1,'hrf'); + + +% interpolate hrf to 1 s +len = length(m); + +m = interp1((0:TR:TR*len-TR)',m',(0:1:ceil(len.*TR))'); +m = m ./ max(m); + +m(isnan(m)) = []; +len = length(m); + + + + +switch bf + case 'hrf' + hrf = spm_hrf(1)./max(spm_hrf(1)); + + case 'derivatives' + + xbf = spm_get_bf(struct('dt',1,'name','hrf (with time and dispersion derivatives)')); + %tor_fig + %x = 0:.1:32; plot(x(1:length(hrf)),xbf.bf(:,1),'k','LineWidth',2); + %hold on; + %plot(x(1:length(hrf)),xbf.bf(:,2),'k--','LineWidth',2); + %plot(x(1:length(hrf)),xbf.bf(:,3),'k.-','LineWidth',2); + %legend({'HRF' 'Derivative (Time)' 'Derivative (Dispersion)'}) + %set(gca,'YColor',[1 1 1],'FontSize',16); + + hrf = xbf.bf; + + case 'fir' + xbf = spm_get_bf(struct('dt',1,'name','Finite Impulse Response')); + hrf = xbf.bf; + + otherwise + error('Enter name of basis set: ''hrf'' ''derivatives'' or ''fir''') + + +end + +if size(hrf,1) < len + hrf = [hrf; zeros(len - size(hrf,1),size(hrf,2))]; +end + +% convolve, if stim function entered +if length(varargin)>0 + stim = varargin{1}; + for i = 1:size(hrf,2) + h = conv(stim,hrf(:,i)); + hrf(:,i) = h(1:size(hrf,1)); + end +end + + +X = hrf; X(:,end+1) = 1; X = X(1:len,:); + +b = pinv(X) * m; +f = X*b; + + +tor_fig;plot(m,'k','LineWidth',2) + +hold on; plot(f,'.--','Color',[.3 .3 .3],'LineWidth',2); +set(gca,'YColor',[1 1 1],'FontSize',24); +legend({'Actual' 'Fitted'}) + +if length(varargin) > 0 + delta = stim; + d = find(delta); + z = zeros(size(d)); + + plot([d d]',[z-.2 z-.1]','k','LineWidth',1); +end + + diff --git a/Visualization_functions/plot_parallel_coords.m b/Visualization_functions/plot_parallel_coords.m new file mode 100644 index 00000000..ab00b5c6 --- /dev/null +++ b/Visualization_functions/plot_parallel_coords.m @@ -0,0 +1,88 @@ +function plot_parallel_coords(dat) + + select_on = 'crossproducts'; % 'crossproducts' or 'variables' + mcolor = [.2 .2 .2]; + facecolor = [.5 .5 .5]; + mtype = 'o'; + cutoff_percentile = 0; + select_color_on_column = 1; + + % init figure and sizes + % --------------------------------------------- + create_figure('Parallel coords plot'); + + [nobs, nvars] = size(dat); + set(gca,'XLim',[0.5 nvars + .5], 'XTick', 1:nvars); + + + % cross-products, if needed + % --------------------------------------------- + if strcmp(select_on, 'crossproducts') + for i = 1:nvars - 1 + xp(:,i) = scale(dat(:,i)) .* scale(dat(:,i + 1)); + end + end + + + % initial plot + % --------------------------------------------- + + run_plot; + + % color initial high values or xproducts + % --------------------------------------------- + + cutoff_percentile = 66; + facecolor = 'r'; + + run_plot; + + % color last var high values or xproducts + % --------------------------------------------- + switch select_on + case 'variables' + select_color_on_column = nvars; + xlabel('Red: high on v(1) Blue: high on v(end)') + case 'crossproducts' + select_color_on_column = nvars - 1; + xlabel('Red: high on xp(1,2) Blue: high on xp(end-1:end)') + otherwise + error('Unknown select_on!') + end + facecolor = 'b'; + + run_plot; + + + + + % + % + % + % inline + % + % + % + + function run_plot + + wh = find(dat(:, select_color_on_column) > prctile(dat(:, select_color_on_column), cutoff_percentile)); + + if strcmp(select_on, 'crossproducts') + wh = find(xp(:, select_color_on_column) > prctile(xp(:, select_color_on_column), cutoff_percentile)); + end + + for i = 1:nvars + plot(i, dat(wh, i), mtype, 'Color', mcolor, 'MarkerFaceColor', facecolor) + end + + for i = 1:length(wh) + plot(dat(wh(i), :), '-', 'Color', facecolor); + end + + drawnow + + end + + +end diff --git a/Visualization_functions/plot_vertical_line.m b/Visualization_functions/plot_vertical_line.m new file mode 100644 index 00000000..b39fa080 --- /dev/null +++ b/Visualization_functions/plot_vertical_line.m @@ -0,0 +1,16 @@ +function han = plot_vertical_line(x,color) +% function han = plot_vertical_line(x,color) +% +% Edited by Tor, Feb 08, to plot multiple lines + +if nargin < 2, color = 'k'; end + + +ylim = get(gca,'YLim'); + +for i = 1:length(x) + han = plot([x(i) x(i)], ylim, color); + +end + +end diff --git a/Visualization_functions/prplot.m b/Visualization_functions/prplot.m new file mode 100755 index 00000000..95558164 --- /dev/null +++ b/Visualization_functions/prplot.m @@ -0,0 +1,159 @@ +function [r,str,sig,ry,rx,h,rr] = prplot(yy,X,k,varargin) +% [r,str,sig,ry,rx,h,rr] = prplot(y,X,col,[dorobust],[colors]) +% Partial residual plot of one column of X against y. +% Uses IRLS estimation to downweight outliers +% if you enter a 4th argument +% +% Partial residual plot of y ~ X for column k +% tor wager +% +% if y contains multiple columns, different colors +% and symbols will be used, with a separate regression +% for each. +% +% colors: e.g., {'ro' 'bs' 'gd' 'y^' 'cv' 'mx'} + +mycols = {'ko'}; +robopt = 'IRLS'; %'IRLS' or 'MCD' + +if size(yy,2) > 1, + + legstr = cell(1); + mycols = {'ro' 'bs' 'gd' 'y^' 'cv' 'mx'}; + +end + +dorobust = 0; +if length(varargin) > 0, dorobust = varargin{1}; end +if length(varargin) > 1, mycols = varargin{2}; end + +% ---------------------------------------- +% do this separately for every column of y +% ---------------------------------------- + +for i = 1:size(yy,2) + + y = yy(:,i); + + Xm = X; + + if dorobust && strcmp(robopt,'MCD') + [res]=fastmcd_noplot([Xm y]); + % remove n most extreme outliers and recompute correlation + wh = res.flag==0; nout = sum(res.flag==0); + + %figure;plot_correlation_samefig(X(:,2),y,[],'ro'); + + y(wh,:) = []; Xm(wh,:) = []; + + %plot_correlation_samefig(X(:,2),y,[],'kx'); + %nout + end + + sel = Xm(:,k); + xmean = mean(sel); % save mean to add back in later + + X2 = Xm; X2(:,k) = []; + + + % problem if cols are duplicated (as in script i'm running now) + % just a fix - changes nothing if no duplicate columns + % temporary fix. + %X2 = unique(X2','rows')'; + %for j = 1:size(X2,2), tmp=corrcoef(X2(:,j),sel); tmpa(j)=tmp(1,2);,end + %X2(:,tmpa > .99) = []; + + % ---------------------------------------- + % regress out columns X2 from selected X(:,k) + % leave rx, the final adjusted x predictor + % ---------------------------------------- + + % Xm is full design matrix + % X2 is design matrix without column k + % sel is column k of design matrix + + if dorobust && strcmp(robopt,'IRLS') + + % fit model and save overall residuals + if nargout > 6 + [b,stat] = robustfit(Xm,y); + rr = y - [ones(size(Xm,1),1) Xm] * b; + w = stat.w; % weights + end + + % regress out (control for) columns of no interest (x2) from sel + [b, stat] = robustfit(X2,sel); + rx = sel - [ones(size(X2,1),1) X2] * b; % residual X component of interest + + else + % non-robust method + + Xm(:,end+1) = 1; + + % save overall residuals + if nargout > 6 + try rr(:,i) = y - Xm * pinv(Xm) * y; catch end + end + + X2(:,end+1) = 1; % add intercept + b = pinv(X2) * sel; + rx = sel - X2 * b; + end + + % y done in next lines + + + + % ---------------------------------------- + % regress out columns X2 from y + % leave ry, the final adjusted y data + % ---------------------------------------- + + if dorobust && strcmp(robopt,'IRLS') + b = robustfit(X2,y); % no intercept above; added by robustfit + b(end+1) = b(1); % move intercept to end, for compatibility with OLS + b = b(2:end); + X2(:,end+1) = 1; % add intercept to end, for resid getting later + else + b = pinv(X2) * y; % intercept added above + end + + if size(yy,2) > 1 + + ry{i} = y - X2 * b; + ry{i} = ry{i} + b(end); % add intercept back in + rx = rx + xmean; + + [r(i),str{i},sig(i),hh] = plot_correlation_samefig(rx,ry{i},[],mycols{i},0,dorobust); + h(i) = hh(1); + legstr{i} = ['DV ' num2str(i)]; + if exist('nout') == 1, legstr{i} = [legstr{i} ' nout=' num2str(nout)]; end + + else + + ry = y - X2 * b; + ry = ry + b(end); % add intercept back in + rx = rx + xmean; + + if length(unique(rx)) > 2 + [r,str,sig,hh] = plot_correlation_samefig(rx,ry,[],mycols{1},0,dorobust); + + else + % t-test: bar plot, adjusting for covariates + rx = scale(rx, 1); %mediansplit(rx); + [H,p,ci,stats] = ttest2_printout(ry(rx>0),ry(rx<0),1); + r = stats.tstat; str = 't-test'; sig = H; hh = []; + end + + + h = hh; + + end + +end % loop through yy + +if size(yy,2) > 1 + legend(h,legstr); +end + +return diff --git a/Visualization_functions/roi_contour_map.m b/Visualization_functions/roi_contour_map.m new file mode 100644 index 00000000..5c21d402 --- /dev/null +++ b/Visualization_functions/roi_contour_map.m @@ -0,0 +1,418 @@ +function info = roi_contour_map(dat, varargin) + +% Draw a pattern map of one slice (either saggital, axial, or coronal) that +% shows the most voxels, or the slice that you specify (e.g., x = #). +% You can also draw outlines for the significant voxels from a statistical test. +% +% Usage: +% ------------------------------------------------------------------------- +% info = roi_contour_map(dat, varargin) +% +% +% Inputs: +% ------------------------------------------------------------------------- +% dat dat can be fmri_data, statistic_image (to mark significant +% voxels), and region objects. If your dat is the "region" +% object, please add 'cluster' as an optional input. +% You can display two pattern maps for the purpose of +% comparison by putting additional columns of data in cell array. +% (an example of displaying two pattern maps: +% dat{1} = region('img1.nii'); +% dat{2} = region('img2.nii'); ) +% +% Some optional inputs +% ------------------------------------------------------------------------- +% 'cluster' When the data is a region object, you need this option. +% 'sig' This option outlines significant voxels. To use this +% option, data in a format of statistic_image with a "sig" +% field should be given. +% 'colorbar' display colorbar under the plot. +% 'use_same_range' When you display two pattern maps, this option uses the +% same color range for the two maps. +% 'surf' surface plot rather than voxel-by-voxel mapping. +% 'xyz' When you want a specific view and slice, you can use this +% option with 'coord'. (1:x - saggital view, 2:y - coronal +% view, 3:z - axial view) +% 'coord' With 'xyz' option, this specifies the slice displayed. +% 'notfill' Default is to fill in the blank voxels using a black +% color. With this option, you can color the blank voxels +% with the white color. +% 'whole' Default is dividing the data into contiguous regions and +% show only one region that has the most voxels. This option +% makes this function not to divide into contiguous regions. +% 'colors' or 'color' you can specify your own colormap. +% 'contour' Not fully implemented yet. +% +% +% Outputs: +% ------------------------------------------------------------------------- +% info information about the display with the following fields. +% +% info.dat: [2x30 double] (xyz mesh) +% Z: [1x30 double] (z values) +% xyz: 3 (1:x-saggital, 2:y-coronal, 3:z-axial) +% xyz_coord: -2 (slice coordinate; in this case, z = 3) +% region_idx: 1 +% +% +% Examples: you can also see the same example and output in +% http://wagerlab.colorado.edu/wiki/doku.php/help/core/figure_gallery +% ------------------------------------------------------------------------- +% +% mask{1} = 'dACC_hw_pattern_sl6mm.nii'; +% mask{2} = 'dACC_rf_pattern_sl6mm.nii'; +% for i = 1:2, cl{i} = region(mask{i}); end +% info = roi_contour_map([cl{1} cl{2}], 'cluster', 'use_same_range', 'colorbar'); +% +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo + +% Programmers' notes: + + +% parsing optional inputs +docolor = 0; +dosig = 0; +docolorbar = 0; +usesamerange = 0; +usecluster = 0; +dosurf = 0; +docontour = 0; % needs to be implemented. +doxyz = 0; +docoord = 0; +donotfill = 0; +dowhole = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % functional commands + case {'colors', 'color'} + docolor = 1; colors = varargin{i+1}; + case {'sig'} + dosig = 1; + case {'colorbar'} + docolorbar = 1; + case {'use_same_range'} + usesamerange = 1; + case {'cluster'} + usecluster = 1; + case {'surf'} + dosurf = 1; + case {'contour'} + docontour = 1; + case {'xyz'} + doxyz = 1; + xyz_idx = varargin{i+1}; + case {'coord'} + docoord = 1; + coord_idx = varargin{i+1}; + case {'notfill'} + donotfill = 1; + case {'whole'} + dowhole = 1; + end + end +end + +% data inputs +if ~usecluster + if ~iscell(dat) + dat_temp{1} = dat; + dat = dat_temp; + end +end + +rnum = numel(dat); + +for jj = 1:rnum + if ~usecluster + if dosig + if any(strcmp(fields(dat{jj}), 'sig')) + datsig{jj} = dat{jj}; + datsig{jj}.dat = dat{jj}.sig; + datsig{jj}.dat = dat{jj}.dat.*~dat{jj}.sig*10^-6 + dat{jj}.sig; + dat{jj}.sig = []; datsig{jj}.sig = []; + else + error('There is no sig data. Please check your data.'); + end + else + if any(strcmp(fields(dat{jj}), 'sig')) + dat{jj}.sig = []; + end + end + + ri = region(dat{jj}); + if dosig, risig = region(datsig{jj}); end + if dowhole, ri = combine_region(ri); risig = combine_region(risig); end + else + ri = dat(jj); + end + clear max_vox idx idxidx; + + for i = 1:numel(ri) + + if ~docoord + mm_c{i} = center_of_mass(ri(i).XYZmm, ones(1,size(ri(i).XYZmm, 2))); + elseif docoord && ~doxyz + warning('''coord'' option should be used with ''xyz''. This will use center of mass.'); + mm_c{i} = center_of_mass(ri(i).XYZmm, ones(1,size(ri(i).XYZmm, 2))); + else + mm_c{i} = center_of_mass(ri(i).XYZmm, ones(1,size(ri(i).XYZmm, 2))); + [~, idx_coord_idx] = min(abs(ri(i).XYZmm(xyz_idx,:)-coord_idx)); + new_coord_idx = ri(i).XYZmm(xyz_idx,idx_coord_idx); + mm_c{i}(xyz_idx) = new_coord_idx; + end + + if ~doxyz + [max_vox(i), idx(i)] = max([sum(ri(i).XYZmm(1,:) == mm_c{i}(1)), sum(ri(i).XYZmm(2,:) == mm_c{i}(2)), sum(ri(i).XYZmm(3,:) == mm_c{i}(3))]); + else + voxs = [sum(ri(i).XYZmm(1,:) == mm_c{i}(1)), sum(ri(i).XYZmm(2,:) == mm_c{i}(2)), sum(ri(i).XYZmm(3,:) == mm_c{i}(3))]; + idx(i) = xyz_idx; + max_vox(i) = voxs(xyz_idx); + end + + end + + [~, idxidx] = max(max_vox); + + xyz = 1:3; + xyz(idx(idxidx)) = []; + + coord{jj}.dat = ri(idxidx).XYZmm(xyz,ri(idxidx).XYZmm(idx(idxidx),:) == mm_c{idxidx}(idx(idxidx))); + coord{jj}.Z = ri(idxidx).Z(ri(idxidx).XYZmm(idx(idxidx),:) == mm_c{idxidx}(idx(idxidx))); + if dosig + coord{jj}.sig = risig(idxidx).Z(risig(idxidx).XYZmm(idx(idxidx),:) == mm_c{idxidx}(idx(idxidx))); + end + coord{jj}.xyz = idx(idxidx); + coord{jj}.xyz_coord = mm_c{idxidx}(idx(idxidx)); + coord{jj}.region_idx = idxidx; + +end + +info = coord; + +%% basic settingfor drawing + +close all; +clear xyz; +figure; +if docolorbar + set(gcf, 'color', 'w', 'position', [ 6 221 500*rnum 485]); % with colorbar +else + set(gcf, 'color', 'w', 'position', [ 6 322 500*rnum 384]); +end + +col = [0.0461 0.3833 0.5912 + 0.2461 0.5833 0.7912 + 0.4000 0.7608 0.6471 + 0.6706 0.8667 0.6431 + 0.9020 0.9608 0.5961 + 1.0000 1.0000 0.7490 + 0.9961 0.8784 0.5451 + 0.9922 0.6824 0.3804 + 0.9569 0.4275 0.2627 + 0.8353 0.2431 0.3098 + 0.6196 0.0039 0.2588]; + +colormap(col); +if docolor, colormap(colors); end + +for jj = 1:rnum + + if dosig + xyz = [coord{jj}.dat(1,:)', coord{jj}.dat(2,:)', coord{jj}.Z', coord{jj}.sig']; + else + xyz = [coord{jj}.dat(1,:)', coord{jj}.dat(2,:)', coord{jj}.Z']; + end + + [vX, vY] = meshgrid(unique(xyz(:,1)), unique(xyz(:,2))); + + vZ{jj} = NaN(size(vX)); + + for i = 1:size(vX,2) + for j = 1:size(vY,1) + for k = 1:length(xyz) + if isequal([vX(1,i) vY(j,1)], xyz(k,1:2)) + vZ{jj}(j,i) = xyz(k,3); + if dosig + vsig{jj}(j,i) = xyz(k,4); + end + end + end + end + end + + vZ{jj} = flipud(vZ{jj}); + if dosig + vsig{jj} = flipud(vsig{jj}); + end + if docontour + vZ{jj} = interp2(vZ{jj},5); + end + + vox_size = vX(1,2)-vX(1,1); + newX = vX/vox_size-vX(1,1)/vox_size+1; + newY = vY/vox_size-vY(1,1)/vox_size+1; + + if docontour + newX = repmat(1:size(vZ{jj},2), size(vZ{jj},1),1); + newY = repmat((1:size(vZ{jj},1))', 1, size(vZ{jj},2)); + end + + %newX = (vX+2)/3-(vX(1,1)+2)/3+1; + %newY = (vY+2)/3-(vY(1,1)+2)/3+1; + + xx{jj} = [newX(isnan(vZ{jj}))-.5 newX(isnan(vZ{jj}))-.5 newX(isnan(vZ{jj}))+.5 newX(isnan(vZ{jj}))+.5]; + yy{jj} = [newY(isnan(vZ{jj}))-.5 newY(isnan(vZ{jj}))+.5 newY(isnan(vZ{jj}))+.5 newY(isnan(vZ{jj}))-.5]; + + if donotfill + xx_outline{jj} = [newX(~isnan(vZ{jj}))-.5 newX(~isnan(vZ{jj}))-.5 newX(~isnan(vZ{jj}))+.5 newX(~isnan(vZ{jj}))+.5 newX(~isnan(vZ{jj}))-.5]; + yy_outline{jj} = [newY(~isnan(vZ{jj}))-.5 newY(~isnan(vZ{jj}))+.5 newY(~isnan(vZ{jj}))+.5 newY(~isnan(vZ{jj}))-.5 newY(~isnan(vZ{jj}))-.5]; + end + + if dosig + x_out{jj} = [newX(vsig{jj}==1)-.5 newX(vsig{jj}==1)-.5 newX(vsig{jj}==1)+.5 newX(vsig{jj}==1)+.5 newX(vsig{jj}==1)-.5]; + y_out{jj} = [newY(vsig{jj}==1)-.5 newY(vsig{jj}==1)+.5 newY(vsig{jj}==1)+.5 newY(vsig{jj}==1)-.5 newY(vsig{jj}==1)-.5]; + end + + lim_max(jj) = max(max(vZ{jj})); + lim_min(jj) = min(min(vZ{jj})); +end + +if usesamerange + lim_max = max(lim_max); lim_min = min(lim_min); +end + +for jj = 1:rnum + + subplot(1,rnum,jj); + + if dosurf % not work that well yet, but kind of work + % surf(newX, newY, vZ{jj}, 'EdgeColor','k'); + surf(interp2(vZ{jj}, 5), 'EdgeColor','none'); + set(gca, 'view', [-21.5 70]) + % axis off; + % camlight left; lighting phong; + set(gca, 'linewidth', 1, 'xtick', [], 'ytick', [], 'ztick', [], 'fontsize', 15); + if usesamerange + set(gca, 'zlim', [lim_min lim_max]); + end + else + if ~usesamerange + if docontour + imagesc(vZ{jj}, [lim_min(jj) lim_max(jj)]); + else + imagesc(vZ{jj}, [lim_min(jj) lim_max(jj)]); + if ~donotfill + set(gca, 'linewidth', 5, 'xtick', [], 'ytick', []); + else + set(gca, 'linewidth', 0.0001, 'xtick', [], 'ytick', [], 'xcolor', 'w', 'ycolor', 'w', 'zcolor', 'w'); + end + end + else + if docontour + imagesc(vZ{jj}, [lim_min lim_max]); + set(gca, 'linewidth', 0.0001, 'xtick', [], 'ytick', [], 'xcolor', 'w', 'ycolor', 'w', 'zcolor', 'w'); + else + imagesc(vZ{jj}, [lim_min lim_max]); + if ~donotfill + set(gca, 'linewidth', 5, 'xtick', [], 'ytick', []); + else + set(gca, 'linewidth', 0.0001, 'xtick', [], 'ytick', [], 'xcolor', 'w', 'ycolor', 'w', 'zcolor', 'w'); + end + end + end + + if ~docontour + y = size(vZ{jj},1); x = size(vZ{jj},2); + if ~donotfill + %for i = 1:x, line([i+.5,i+.5], [0 y+.5], 'color', [.2 .2 .2], 'linewidth', 1.5, 'linestyle', '-'); end + %for i = 1:y, line([0 x+.5], [i+.5,i+.5], 'color', [.2 .2 .2], 'linewidth', 1.5, 'linestyle', '-'); end + for i = 1:x, line([i+.5,i+.5], [0 y+.5], 'color', 'k', 'linewidth', 1, 'linestyle', '-'); end + for i = 1:y, line([0 x+.5], [i+.5,i+.5], 'color', 'k', 'linewidth', 1, 'linestyle', '-'); end + else + for i = 1:x, line([i+.5,i+.5], [0 y+.5], 'color', repmat(.2, 1, 3), 'linewidth', 1.5, 'linestyle', '-'); end + for i = 1:y, line([0 x+.5], [i+.5,i+.5], 'color', repmat(.2, 1, 3), 'linewidth', 1.5, 'linestyle', '-'); end + end + end + + hold on; + if docontour + for i = 1:size(xx{jj},1), fill(xx{jj}(i,:), yy{jj}(i,:), 'w'); end % this is wrong. + else + if ~donotfill + for i = 1:size(xx{jj},1), fill(xx{jj}(i,:), yy{jj}(i,:), 'k'); end + else + for i = 1:size(xx{jj},1), fill(xx{jj}(i,:), yy{jj}(i,:), 'w', 'edgecolor', 'w'); end + xy_outline_idx{jj} = outline(xx_outline{jj}, yy_outline{jj}); + draw_outline(xx_outline{jj},yy_outline{jj},xy_outline_idx{jj},'k',5); + end + end + + if dosig + xy_idx{jj} = outline(x_out{jj}, y_out{jj}); + draw_outline(x_out{jj},y_out{jj},xy_idx{jj},'r',5) +% for i = 1:size(x_out{jj},1) +% for i2 = 1:4 +% if xy_idx{jj}(i,i2) +% line([x_out{jj}(i,i2) x_out{jj}(i,i2+1)], [y_out{jj}(i,i2) y_out{jj}(i,i2+1)], 'color', 'y', 'linewidth', 5); +% end +% end +% end + end + end + if docolorbar + hh = colorbar('southoutside'); + set(hh, 'fontSize', 25, 'lineWidth', 3); + end + +end + +end + +% subfunctions +function xy_idx = outline(x, y) + +xy_idx = true(size(x,1),4); + +for i = 1:size(x,1) + for ii = 1:4 + for j = i+1:size(x,1) + for jj = 1:4 + if sum(abs([sort(x(i,(ii:ii+1))) sort(y(i,(ii:ii+1)))] - [sort(x(j,(jj:jj+1))), sort(y(j,(jj:jj+1)))]) < .0001) == 4 + xy_idx(i,ii) = false; xy_idx(j,jj) = false; + end + end + end + end +end + +end + +function draw_outline(x,y,idx,color, linewidth) +for i = 1:size(x,1) + for j = 1:4 + if idx(i,j) + line([x(i,j) x(i,j+1)], [y(i,j) y(i,j+1)], 'color', color, 'linewidth', linewidth); + end + end +end + +end + +function r = combine_region(r) +rr = r(1); + +if numel(r) > 1 + for i = 2:numel(r) + rr.XYZ = [rr.XYZ r(i).XYZ]; + rr.XYZmm = [rr.XYZmm r(i).XYZmm]; + rr.val = [rr.val; r(i).val]; + rr.Z = [rr.Z r(i).Z]; + end +end + +r = rr; + +end diff --git a/Visualization_functions/scn_export_papersetup.m b/Visualization_functions/scn_export_papersetup.m new file mode 100644 index 00000000..f73e3e51 --- /dev/null +++ b/Visualization_functions/scn_export_papersetup.m @@ -0,0 +1,22 @@ +function scn_export_papersetup(minsize) +% scn_export_papersetup([opt: min size in pixels, default = 400]) +% +% set paper size for current figure so that print to png or tiff looks as +% it should (as it does on-screen) +% +% tor wager, aug. 06 + +if nargin < 1, minsize = 400; end + +% make sure that min size of fig. is 400 pixels +sz = get(gcf,'Position'); +sz = sz(3:4); % width and height +szratio = max(sz) ./ min(sz); % max to min size +wh = find(sz == min(sz)); +sz(wh) = minsize; +wh = find(sz == max(sz)); +sz(wh) = minsize * szratio; + +% set paper size using current screen +set(gcf,'PaperUnits','points','PaperPosition',[0 0 sz],'PaperType','usletter'); +return \ No newline at end of file diff --git a/Visualization_functions/scn_export_spm_window.m b/Visualization_functions/scn_export_spm_window.m new file mode 100644 index 00000000..9d5c3e42 --- /dev/null +++ b/Visualization_functions/scn_export_spm_window.m @@ -0,0 +1,86 @@ +% scn_export_spm_window(meth, [savefilename] or [overlay]) +% +% Modes: +% scn_export_spm_window('setup') +% scn_export_spm_window('setup', overlayimgname) +% Set up SPM figure window paper size, etc. for saving +% The optional argument sets the spm_orthviews display range based on your +% image +% +% scn_export_spm_window('save', 'myfile') +% saves SPM window to myfile.png +% +% Run setup first, then save. +% +% tor wager +% aug 3, 06 + +function scn_export_spm_window(meth, savefilename) + fh = findobj('Tag', 'Graphics'); + if isempty(fh) || ~ishandle(fh) + disp('No SPM figure window. Using current figure.'); + fh = gcf; + end + + switch meth + case 'setup' + if nargin > 1 && ~isempty(savefilename) + % set display window on orthviews based on input image + v = spm_read_vols(spm_vol(savefilename)); + v = v(:); + ub = mean(v)+4*std(v); + %spm_orthviews('Window', 1, [0 ub]) + end + + % set colors + set(fh, 'InvertHardCopy', 'off'); + set(fh, 'Color', 'black'); + + scn_export_papersetup; + + % turn off crosshairs + spm_orthviews('Xhairs', 'off'); + + + % set axis color and ticks of colorbars + global st + for i = 1:length(st.vols) + + % set color bar colors, if color bar exists + if ~isempty(st.vols{i}) && ~isempty(st.vols{i}.blobs) && isfield(st.vols{i}.blobs{1}, 'cbar') && ~isempty(st.vols{i}.blobs{1}.cbar) + % colors + set(st.vols{i}.blobs{1}.cbar, 'YColor', 'g', 'XColor', 'g', 'FontSize', 16) + + % set axis ticks of colorbars + minmax = get(st.vols{i}.blobs{1}.cbar, 'YLim'); + set(st.vols{i}.blobs{1}.cbar, 'YTick', linspace(minmax(1), minmax(2), 5)); + + end + end + + case 'save' + % now we're ready to save + saveas(fh, savefilename, 'png'); + + otherwise + error('Meth should be ''setup'' or ''save''.') + end +end + + + + +function scn_export_papersetup + % make sure that min size of fig. is 400 pixels + sz = get(gcf, 'Position'); + sz = sz(3:4); % width and height + szratio = max(sz) ./ min(sz); % max to min size + wh = find(sz == min(sz)); + sz(wh) = 400; + wh = find(sz == max(sz)); + sz(wh) = 400 * szratio; + + % set paper size using current screen + set(gcf, 'PaperUnits', 'points', 'PaperPosition', [0 0 sz], 'PaperType', 'usletter'); +end + diff --git a/Visualization_functions/scn_standard_colors.m b/Visualization_functions/scn_standard_colors.m new file mode 100644 index 00000000..0459755d --- /dev/null +++ b/Visualization_functions/scn_standard_colors.m @@ -0,0 +1,37 @@ +function colors = scn_standard_colors(varargin) +% Create a set of unique colors in a standardized order. +% +% colors = scn_standard_colors(varargin) +% colors = scn_standard_colors(100) +% +% Optional input: minimum number of colors to generate +% Repeats after 36 colors + + % unique colors for each blob + colors = {[1 0 0] [0 1 0] [0 0 1] [1 1 0] [1 0 1] [0 1 1]}; + + colors = [colors {[1 .5 0] [.5 1 0] [.5 0 1] [1 0 .5] [0 1 .5] [0 .5 1]}]; + + % next 6 colors, make pastel + for i = 1:length(colors), colors = [colors {colors{i} .* .5}]; end + + % next 6, add blue + for i = 7:12, colors = [colors colors(i)]; colors{end}(3) = colors{end}(3) + .5; end + + % next 6, add red + for i = 7:12, colors = [colors colors(i)]; colors{end}(1) = colors{end}(1) + .5; end + + if ~isempty(varargin) + n = varargin{1}; + + while length(colors) < n, colors = [colors colors]; end + + colors = colors(1:n); + end + + for i = 1:length(colors) + colors{i}(colors{i} < 0) = 0; + colors{i}(colors{i} > 1) = 1; + end + +end diff --git a/Visualization_functions/selective_average_interactive_view_init.m b/Visualization_functions/selective_average_interactive_view_init.m new file mode 100644 index 00000000..5b776339 --- /dev/null +++ b/Visualization_functions/selective_average_interactive_view_init.m @@ -0,0 +1,221 @@ +function selective_average_interactive_view_init(imgs, onsets, scans_per_sess, TR, hp_length, t, basepts, plotstes) + % + % selective_average_interactive_view_init(imgs, onsets, t, basepts, plotstes) + % + % + % e.g., + % selective_average_interactive_view_init(imgs, onsets, 20, 1:2, 1); + % + % Initialize point-and-click data extraction and plotting of selective + % averages + % + % Input: + % imgs, cell array of image files, one cell per subject + % imgs is cell array of time series image names for each subject + % + % onsets, cell array of onsets + % Each cell contains its own cell array, with one cell per + % condition. + % + % t time points in average to estimate + % basepts indices of baseline points to subtract from individual averages + % plotstes plot standard errors: 1 or 0 + + % need to do the stuff below only once + + disp('Initializing Selective Average interactive viewer') + disp('-------------------------------------------------') + + % disp('Loading mediation_SETUP.mat') + % [SETUP, imgs, wh_is_image, name] = load_mediation_setup; + + + N = length(imgs); + + + + % Setting up filtering + % ------------------------------------------------------ + fprintf('Setting up high-pass filtering (dummy params for first 2 time points per run): '); + % % I = cell(1, N); + % % S = cell(1, N); + tic + + for i = 1:N + [tmp, I, S] = hpfilter(rand(sum(scans_per_sess), 1), TR, hp_length, scans_per_sess, [], 1:2); + end + + fprintf(' %3.0f s\n', toc) + + % ------------------------------------------------------ + V = cell(1, N); + fprintf('Mapping image volumes to memory: '); + tic + for i = 1:N + fprintf('%3.0f ', i); + V{i} = spm_vol(imgs{i}); + end + + fprintf(' %3.0f s\n', toc) + + + + + % ------------------------------------------------------ + + disp('Registering graphics callback with SPM registry') + + callback_handle = @(str, pos, reg, hReg) selavg_interactive_callback_wrapper(str, pos, reg, hReg); + + hSpmFig = spm_figure('GetWin', 'Graphics'); + + hReg = uicontrol(hSpmFig, 'Style', 'Text', 'String', 'InteractiveViewer hReg', ... + 'Position', [100 200 100 025], 'Visible', 'Off', ... + 'FontName', 'Times', 'FontSize', 14, 'FontWeight', 'Bold', ... + 'HorizontalAlignment', 'Center'); + + hReg = spm_XYZreg('InitReg', hReg, V{1}(1).mat, V{1}(1).dim(1:3)'); + spm_XYZreg('Add2Reg', hReg, 0, callback_handle); + spm_orthviews('Register', hReg); + + + %fh = findobj('Tag', 'Graphics'); + %set(fh, 'WindowButtonUpFcn', callback_handle); + + disp('Ready!') + + + % inline + + function selavg_interactive_callback_wrapper(str, pos, reg, hReg) + + switch str + case 'SetCoords' + selavg_interactive_callback(pos, onsets, V, t, basepts, plotstes, S, scans_per_sess, I); + + otherwise + disp('Unknown callback command from spm_XYZreg'); + end + + end + +end + + + + + +function selavg_interactive_callback(pos, onsets, V, t, basepts, plotstes, S, scans_per_sess, I) + %% Done each time you click + + % % %pos = spm_orthviews('Pos'); + % % vox = (mm2voxel(pos', V{1}(1)))'; + % % vox(4) = 1; + % % + % % fprintf('Position: %3.0f %3.0f %3.0f\n', pos(1), pos(2), pos(3)); + % % go = input('Plot selective averages for this voxel? (1 or 0) : '); + + % % if go + % % % % Load data for this voxel + % % % % ----------------------------------------------------- + % % % N = length(V); + % % % + % % % fprintf('Loading voxel data for all subjects: '); + % % % for i = 1:N, fprintf('%3.0f ', i); braindata{i} = spm_get_data(V{i}, vox); end + + % Load data and get selective averages for each subject + % ----------------------------------------------------- + [group_avgs, group_stes, subject_avgs, subject_stes, braindata] = selective_average_group( ... + V, onsets, pos, 't', t, 'basepts', basepts, 'plotstes', plotstes, 'S', S, 'scans', scans_per_sess, 'I', I); + + + if ~isempty(group_avgs) + + disp('-------------------------------------------------------'); + disp('Assigning group_avgs and stes in base workspace.'); + disp('(and subject_avgs and subject_stes)'); + disp('Assigning braindata to base workspace: Timeseries for each subject'); + disp('-------------------------------------------------------'); + disp(' ') + + assignin('base', 'group_avgs', group_avgs); + assignin('base', 'group_stes', group_stes); + assignin('base', 'subject_avgs', subject_avgs); + assignin('base', 'subject_stes', subject_stes); + assignin('base', 'braindata', braindata); + + end + % % end +end + + + +function [SETUP, imgs, wh_is_image, name] = load_mediation_setup + + SETUP = []; + imgs = []; + + fname = [pwd filesep 'mediation_SETUP.mat']; + if exist(fname,'file') + load(fname); + + % try to find names (single level) + if exist('SETUP','var') && isfield(SETUP, 'M') && ischar(SETUP.M) + imgs = SETUP.M; + name = 'From mediation_SETUP SETUP.M'; + wh_is_image = 'M'; + + elseif exist('SETUP','var') && isfield(SETUP, 'X') && ischar(SETUP.X) + imgs = SETUP.X; + name = 'From mediation_SETUP SETUP.X'; + wh_is_image = 'X'; + + elseif exist('SETUP','var') && isfield(SETUP, 'Y') && ischar(SETUP.Y) + imgs = SETUP.Y; + name = 'From mediation_SETUP SETUP.Y'; + wh_is_image = 'Y'; + + end + + % try to find names: multi-level + if isfield(SETUP, 'data') + switch SETUP.cmdstring + case 'Search for mediators' + imgs = SETUP.data.M; + name = 'Multilevel, from SETUP.data.M'; + wh_is_image = 'M'; + + case 'Search for indirect influences' + imgs = SETUP.data.X; + name = 'Multilevel, from SETUP.data.X'; + wh_is_image = 'X'; + + case 'Search for mediated outcomes' + imgs = SETUP.data.Y; + name = 'Multilevel, from SETUP.data.Y'; + wh_is_image = 'Y'; + + otherwise + error('Unknown cmdstring: "%s".', cmdstring); + end + + if ~iscell(imgs) || ~ischar(imgs{1}) + imgs = []; % invalid data here + end + + + + end + + + + if isempty(imgs) + fprintf(1,'Could not find image list.\n'); + end + + else + fprintf(1,'Go to valid mediation directory with SETUP.mat to use interactive plotting.\n'); + end + +end + diff --git a/Visualization_functions/sepplot.m b/Visualization_functions/sepplot.m new file mode 100644 index 00000000..6c8699e7 --- /dev/null +++ b/Visualization_functions/sepplot.m @@ -0,0 +1,84 @@ +function h = sepplot(x, y, prop, varargin) + +% Draw a shorter line plots between points. To see a figure example, please visit +% http://wagerlab.colorado.edu/wiki/doku.php/help/core/figure_gallery. +% +% Usage: +% ------------------------------------------------------------------------- +% h = sepplot(x, y, prop, varargin) +% +% Inputs: +% ------------------------------------------------------------------------- +% x, y The function plots vector Y against vector X +% prop The proportion of the lines: prop can be between 0 and 1 +% +% Optional inputs: Enter keyword followed by variable with values +% 'color' followed by color (e.g., 'color', [.5 .5 .5]) (default = black) +% 'linewidth' followed by a number for linewidth (e.g., 'linewidth', 2) (default = .5) +% 'linestyle' linestyle, e.g., followed by '-', '--', ':' (default = '-') +% +% Outputs: +% ------------------------------------------------------------------------- +% h graphic handles for lines +% +% Examples: you can see the output in +% http://wagerlab.colorado.edu/wiki/doku.php/help/core/figure_gallery +% ------------------------------------------------------------------------- +% +% x = 1:5; % x values +% y = [32 40 55 84 130]; % mean +% e = [6 6 6 6 6]; % standard error of the mean +% +% create_figure(y_axis); +% set(gcf, 'Position', [1 512 268 194]); +% col = [0.3333 0.6588 1.0000]; +% markercol = col-.2; +% +% h = errorbar(x, y, e, 'o', 'color', 'k', 'linewidth', 1.5, 'markersize', 7, 'markerfacecolor', col); +% hold on; +% sepplot(x, y, .75, 'color', col, 'linewidth', 2); +% errorbar_width(h, x, [0 0]); +% +% set(gca, 'xlim', [.5 5.5], 'linewidth', 1.5); +% +% try +% pagesetup(gcf); +% saveas(gcf, 'example.pdf'); +% catch +% pagesetup(gcf); +% saveas(gcf, 'example.pdf'); +% end +% +% ------------------------------------------------------------------------- +% Copyright (C) 2014 Wani Woo + +% Programmers' notes: + +col = [0 0 0]; % color default (black) +linew = .5; % linewidth default +lines = '-'; % linestyle default + +for i = 1:numel(varargin) + if ischar(varargin{i}) + switch lower(varargin{i}) + case 'color' + col = varargin{i+1}; + case 'linewidth' + linew = varargin{i+1}; + case 'linestyle' + lines = varargin{i+1}; + end + end +end + +for i = 1:(numel(x)-1) + xstep = (x(i+1)-x(i)).*((1-prop)/2); + newx = [x(i)+xstep x(i+1)-xstep]; + + ystep = (y(i+1)-y(i)).*((1-prop)/2); + newy = [y(i)+ystep y(i+1)-ystep]; + + h(i) = plot(newx, newy, 'color', col, 'linewidth', linew, 'linestyle', lines); +end + +end diff --git a/Visualization_functions/spm_orthviews_change_colormap.m b/Visualization_functions/spm_orthviews_change_colormap.m new file mode 100644 index 00000000..7e3974e2 --- /dev/null +++ b/Visualization_functions/spm_orthviews_change_colormap.m @@ -0,0 +1,48 @@ +function cm = spm_orthviews_change_colormap(lowcolor, hicolor, varargin) +% cm = spm_orthviews_change_colormap(lowcolor, hicolor, midcolor1, midcolor2, etc.) +% +% Create a new split colormap of your choosing and apply it to the +% spm_orthviews figure. +% +% spm_orthviews_change_colormap([.2 .2 .6], [1 1 0]); % slate to yellow +% spm_orthviews_change_colormap([.9 .5 .2], [1 1 0]); % orange to yellow +% spm_orthviews_change_colormap([.8 .1 .1], [1 1 0], [.9 .6 .1]); %red to orange to yellow +% spm_orthviews_change_colormap([.2 .2 .4], [1 1 0], [.9 .6 .1]); %slate to orange to yellow +% cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [0 .5 1], [0 .5 .5], ... +% [0 1 .5], [0 1 0], [.5 1 0]); +% cm = spm_orthviews_change_colormap([0 0 1], [1 1 0], [.5 0 1], [.5 .5 1], ... +% [1 .5 1], [1 .5 .5], [1 .5 0]); +% tor wager, sept. 2007 + +% colormap +myfig = findobj('Tag','Graphics'); +axishandles = findobj(myfig, 'Type', 'Axes'); + +newcm = colormap_tor(lowcolor, hicolor, varargin{:}); +wh = round(linspace(1, size(newcm, 1), 64)); +cm = [gray(64) ; newcm(wh, :)]; + + +% specify split color-map of 128 values; first 64 are gray, last 64 have +% colors for activations; this is specified by spm_orthviews + +for i = 1:length(axishandles) + colormap(axishandles(i), cm); +end + +end + +% +% newcm = NaN .* zeros(64, 3); %initialize +% n = 64; +% +% % if nargin < 3 +% % % no mid-color +% % newcm = [linspace(lowcolor(1), hicolor(1), n)' linspace(lowcolor(2), hicolor(2), n)' linspace(lowcolor(3), hicolor(3), n)' ]; +% % else +% % % we have a mid-color +% % newcm1 = [linspace(lowcolor(1), midcolor(1), n/2)' linspace(lowcolor(2), midcolor(2), n/2)' linspace(lowcolor(3), midcolor(3), n/2)' ]; +% % newcm2 = [linspace(midcolor(1), hicolor(1), n/2)' linspace(midcolor(2), hicolor(2), n/2)' linspace(midcolor(3), hicolor(3), n/2)' ]; +% % +% % newcm = [newcm1; newcm2]; +% % end diff --git a/Visualization_functions/spm_orthviews_hotcool_colormap.m b/Visualization_functions/spm_orthviews_hotcool_colormap.m new file mode 100644 index 00000000..0a1df936 --- /dev/null +++ b/Visualization_functions/spm_orthviews_hotcool_colormap.m @@ -0,0 +1,117 @@ +function cm = spm_orthviews_hotcool_colormap(t, thr) + % cm = spm_orthviews_hotcool_colormap(t, thr) + % + % Create split-level colormap with hot colors for positive values and + % cool colors for negative ones + % Apply this colormap to the spm Graphics window (or any figure with the + % tag 'Graphics') + % + % Inputs: + % t: range of input statistic values (doesn't have to be t-values) + % thr: threshold more extreme than which (+/-) colors should be used + % enter a positive value + % + % Designed to work on blobs added using spm_orthviews 'addblobs' feature + % + % Tor Wager, April 2007 + % + % Examples for using 'clusters' in torlab format: + % ---------------------------------------------------------------------- + % Threshold an image to get clusters: + % [dat, volInfo, cl] = iimg_threshold('test_statistic.img', 'thr', 3.1440, 'k', 20); + % + % Display the clusters using spm_orthviews and apply the new color map: + % cluster_orthviews(cl); cm = spm_orthviews_hotcool_colormap(cat(2,cl.Z), 3); + + + myfig = findobj('Tag','Graphics'); + axishandles = findobj(myfig, 'Type', 'Axes'); + %cm = get(myfig,'Colormap'); + + + % range of input statistic values (called 't' here, but doesn't have to be) + mx = max([eps max(t)]); + mn = min([0 min(t)]); + + % indices in color map for range of data (specified in spm_orthviews) + cvals = [1:64]' + 64; + + % t-vals corresponding to c-vals + tvals = linspace(mn, mx, length(cvals)); + + + % make new split colormap: hot colors for pos, cool colors for neg + + newcm = NaN .* zeros(64, 3); %initialize + + + pos = find(tvals > thr); + n = length(pos); + + if n > 0 + hotcm = hotmap(n); + + % gray-yellow + %hotcm = [linspace(0.5, 1, n)' linspace(0.5, 1, n)' linspace(.5, 0, n)' ]; + + + newcm(end - n + 1 : end,:) = hotcm; + end + + neg = find(tvals < -thr); + n = length(neg); + + %coolcm = cool(n); + + if n > 0 + coolcm = [linspace(0, 0.5, n)' linspace(0, 0.7, n)' linspace(1, .5, n)' ]; + + newcm(1:n,:) = coolcm; + end + + + % specify split color-map of 128 values; first 64 are gray, last 64 have + % colors for activations; this is specified by spm_orthviews + + cm = [gray(64) ; newcm]; + + for i = 1:length(axishandles) + colormap(axishandles(i), cm); + end + +end + + + +function hotcm = hotmap(n) + + offset = 3; + hotcm = hot(n + offset); + hotcm = hotcm(offset+1:end,:); + + hotcm = hotcm + greyfade(n, offset+2); % blend hot map with fade-out grayscale map + hotcm(hotcm > 1) = 1; + +end + + +function grayfadeout = greyfade(n, grayels) + + % div = 5; + % grayels = floor(n ./ div); + % + % grayels = 3; + + grayfadeout = linspace(.5, 0, grayels)'; + grayfadeout = repmat(grayfadeout, 1, 3); + grayfadeout = [grayfadeout; zeros(n - grayels, 3)]; + + grayfadeout = grayfadeout(1:n, :); +end + + + + +% coolcm = [linspace(0, 0, n)' linspace(0, 0, n)' linspace(1, 0, n)' ]; +% coolcm = coolcm + flipud(greyfade(n)); % blend hot map with fade-out grayscale map +% coolcm(coolcm > 1) = 1; diff --git a/Visualization_functions/spm_orthviews_name_axis.m b/Visualization_functions/spm_orthviews_name_axis.m new file mode 100644 index 00000000..b2fb0aff --- /dev/null +++ b/Visualization_functions/spm_orthviews_name_axis.m @@ -0,0 +1,15 @@ +function spm_orthviews_name_axis(name, axisnum) + % spm_orthviews_name_axis(name, axis#) + % put names on spm_orthviews axes + + global st + + axes(st.vols{axisnum}.ax{3}.ax) + + name(name == '_') = ' '; + + title(name,'FontSize',24,'Color','k') + + + +end \ No newline at end of file diff --git a/Visualization_functions/spm_orthviews_showposition.m b/Visualization_functions/spm_orthviews_showposition.m new file mode 100644 index 00000000..7939e85e --- /dev/null +++ b/Visualization_functions/spm_orthviews_showposition.m @@ -0,0 +1,104 @@ +function h = spm_orthviews_showposition + % handles = spm_orthviews_showposition; + % + % plots x, y, and z coordinates on SPM orthviews figure + % + % tor wager, august 2006 + % updated: june 2007 + + % bring context structure variable into this script. + % contains handles, etc. + + global st + + + % get current position in mm + pos = round(spm_orthviews('Pos')); % x y z + + for i = 1:length(st.vols) + + if ~isempty(st.vols{i}) && ~isempty(st.vols{i}.ax) && ~isempty(st.vols{i}.ax{1}) + + axish(1) = st.vols{i}.ax{1}.ax; + axish(2) = st.vols{i}.ax{2}.ax; + axish(3) = st.vols{i}.ax{3}.ax; + + % axial + h(1) = put_text(axish(1),['z = ' num2str(pos(3))]); + + % coronal + h(2) = put_text(axish(2),['y = ' num2str(pos(2))]); + + % saggital + h(3) = put_text(axish(3),['x = ' num2str(pos(1))]); + + end + + % % + % % % Get figure and axis handles + % % fh = findobj('Tag','Graphics'); + % % ch = get(fh,'Children'); + % % for i= 1:length(ch),mytype = get(ch(i),'Type'); wh(i)=strcmp(mytype,'axes'); end + % % + % % if ~exist('wh','var') + % % disp('spm_orthviews_showposition: No axis handles found. Exiting.'); + % % return + % % end + % % + % % axish = ch(find(wh)); + % % + % % if isempty(axish), return, end + + % % % get which axis is which + % % for i = 1:length(axish) + % % poss(i,:) = get(axish(i),'Position'); + % % end + % % + % % % get rid of extra axes we may have created in the 4th quadrant + % % other_axes = find(any(poss(:,1) > .45 & poss(:,2) < .2,2)); + % % axish(other_axes) = []; + % % poss(other_axes,:) = []; + % % + % % % sort into order: axial, coronal, saggital + % % ssum = sum(poss(:,1:2),2); + % % [ssum,ind] = sort(ssum); + % % axish = axish(ind); + % % + % % + % % % axial + % % h(1) = put_text(axish(1),['z = ' num2str(pos(3))]); + % % + % % % coronal + % % h(2) = put_text(axish(2),['y = ' num2str(pos(2))]); + % % + % % % saggital + % % h(3) = put_text(axish(3),['x = ' num2str(pos(1))]); + + + + end % end loop thru vols + + + + return + + + +function h = put_text(axish,str) + + axes(axish) + + % remove old text + oldh = findobj(gca,'Type','text'); + delete(oldh) + + xdelta = diff(get(axish,'XLim')); + ydelta = diff(get(axish,'YLim')); + + % add new text + xpos = xdelta .* .42; + ypos = ydelta .* .1; + h1 = text(xpos - xdelta * .01,ypos + ydelta * .005,str,'Color','k','FontSize',24, 'FontWeight', 'b'); + h = text(xpos,ypos,str,'Color','g','FontSize',24, 'FontWeight', 'demi'); + + return diff --git a/Visualization_functions/surf_plot_tor.m b/Visualization_functions/surf_plot_tor.m new file mode 100644 index 00000000..39809ad4 --- /dev/null +++ b/Visualization_functions/surf_plot_tor.m @@ -0,0 +1,128 @@ +function [data_matrix12, xvals2, yvals2, data_matrix22, bestx, besty, bestz] = surf_plot_tor(data_matrix1, xvals, yvals, xname, yname, zname, varargin) + % + % Stylized surface plot of one or two surfaces + % Tor Wager, Sept. 07 + % + % [data_matrix12, xvals2, yvals2, data_matrix22] = surf_plot_tor(data_matrix1, xvals, yvals, xname, yname, zname, [data_matrix2]) + % + % e.g., + % See classify_search_script3...in meta-analysis classification: + % + % surf_plot_tor(corrc_mean, mya, mys, 'Activation feature cutoff', 'Sensitivity feature cutoff', 'Classification accuracy', worstcat) +% +% xvals are columns, yvals are rows! + + data_matrix22 = []; + + ystep = 1/5 .* range(yvals) ./ length(yvals); + xstep = 1/5 .* range(xvals) ./ length(xvals); + + [X,Y] = meshgrid(xvals,yvals); + +% if size(X, 1) == size(data_matrix1, 2) +% error('Wrong sizes...are the x and y inputs flipped?'); +% end + + yvals2 = 0:ystep:max(yvals); + xvals2 = 0:xstep:max(xvals); + + [X2,Y2] = meshgrid(xvals2,yvals2); + data_matrix12 = interp2(X,Y,data_matrix1,X2,Y2,'cubic'); + + create_figure('Surface plot'); + + han = surf(X2,Y2,data_matrix12); + + xlabel('Activation feature cutoff') + ylabel('Sensitivity feature cutoff') + zlabel('Classification accuracy'); + + xlabel(xname); + ylabel(yname); + zlabel(zname); + + set(han,'EdgeColor','none') + grid on + + % lines + plot3(X2(end,:),Y2(end,:),data_matrix12(end,:),'r','LineWidth',2); + plot3(X2(end,:),Y2(end,:),data_matrix12(end,:),'r','LineWidth',3); + + for i = 10:10:size(X2,1) + plot3(X2(i,:),Y2(i,:),data_matrix12(i,:),'Color',[.5 .5 .5],'LineWidth',1); + end + + view(135, 30) + drawnow + + scn_export_papersetup(600); + + if length(varargin) > 0 + data_matrix2 = varargin{1}; + + data_matrix22 = interp2(X,Y,data_matrix2,X2,Y2,'cubic'); + hold on + han2 = surf(X2,Y2,data_matrix22); + set(han2,'EdgeColor','none') + set(han,'FaceAlpha',.7) + + % lines + plot3(X2(end,:),Y2(end,:),data_matrix22(end,:),'b','LineWidth',3); + plot3(X2(1,:),Y2(1,:),data_matrix22(1,:),'k','LineWidth',1); + + + for i = 10:10:size(X2,1) + plot3(X2(i,:),Y2(i,:),data_matrix22(i,:),'Color',[.5 .5 .5],'LineWidth',1); + end + + + end + + % calc max and put max point on map + % -------------------------------------------------- + if length(varargin) > 0 + % average two maps + mymap = .5 * (data_matrix12 + data_matrix22); + fprintf('Using average of two maps to calculate max.'); + else + mymap = data_matrix12; + end + + [bestsum] = max(mymap(:)); + [row, col] = find(mymap == bestsum); +bestx = xvals2(col); +besty = yvals2(row); +bestz = mymap(row, col); + +fprintf('Maximum: x = %3.3f, y = %3.3f, z = %3.3f\n', bestx, besty, bestz); + +plotz = bestz; +if length(varargin) > 0 + max1 = data_matrix12(row, col); + max2 = data_matrix22(row, col); + plotz = max([max1 max2]); + + bestz = [bestz max1 max2]; + fprintf('Height at overall max: Map 1: %3.3f, Map 2: %3.3f\n', max1, max2); +end + +% plot +plot3(bestx, besty, plotz, 'ko', 'MarkerSize', 10, 'MarkerFaceColor', 'k') + +mylowerbound = get(gca,'ZLim'); mylowerbound = mylowerbound(1); +plot3([bestx bestx], [besty besty], [plotz mylowerbound], 'k-', 'LineWidth', 2); + +myxlowerbound = get(gca,'XLim'); %myxlowerbound = myxlowerbound(1); +plot3([myxlowerbound], [besty besty], [mylowerbound mylowerbound], 'k-', 'LineWidth', 2); + +myylowerbound = get(gca,'YLim'); %myylowerbound = myylowerbound(1); +plot3([bestx bestx], [myylowerbound], [mylowerbound mylowerbound], 'k-', 'LineWidth', 2); + +set(gca, 'ZLim', get(gca, 'ZLim')); +axis vis3d + + + + +end + diff --git a/Visualization_functions/surface_cutaway.m b/Visualization_functions/surface_cutaway.m new file mode 100644 index 00000000..011fdc19 --- /dev/null +++ b/Visualization_functions/surface_cutaway.m @@ -0,0 +1,256 @@ +function surface_handles = surface_cutaway(varargin) +% Make a specialized cutaway surface and add blobs if entered +% +% Usage: +% ------------------------------------------------------------------------- +% surface_handles = surface_cutaway(varargin) +% +% Author and copyright information: +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Tor Wager +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Inputs (all are optional): +% ------------------------------------------------------------------------- +% With no inputs, this function creates a vector of surface handles and +% returns them in surface_handles +% +% 'cl' followed by clusters structure or region object with blob info +% 'surface_handles' followed by vector of existing surface handles to plot blobs on +% 'ycut_mm' followed by y-cutoff in mm for coronal section. +% - if absent, shows entire medial surface +% 'existingfig' Use existing figure axis; do not create new one +% - if 'handles' are passed in, this is irrelevant +% 'pos_colormap' followed by colormap for positive-going values +% - n x 3 vector of rgb colors +% - see colormap_tor or matlab's colormap +% 'neg_colormap' followed by colormap for negative-going values +% +% Outputs: +% ------------------------------------------------------------------------- +% surface_handles vector of surface handles for all surface objects +% +% Examples: +% ------------------------------------------------------------------------- +% Create new surfaces: +% r is a region object made from a thresholded image (see region.m) +% surface_handles = surface_cutaway('cl', r, 'ycut_mm', -30); +% +% Image blobs on existing handles: +% surface_handles = surface_cutaway('cl', r, 'handles', surface_handles); +% +% Do it step by step: +% p = surface_cutaway(); +% p = surface_cutaway('cl', r, 'surface_handles', p); +% +% Use custom colormaps (you can define 'pos_colormap' and 'neg_colormap'): +% poscm = colormap_tor([.5 0 .5], [1 0 0]); % purple to red +% p = surface_cutaway('cl', r, 'surface_handles', p, 'pos_colormap', poscm); +% p = surface_cutaway('cl', r, 'ycut_mm', -30, 'pos_colormap', poscm); +% +% Programmers' notes: +% List dates and changes here, and author of changes + + +% ------------------------------------------------------------------------- +% DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + +% Defaults +% ----------------------------------- +% Set color maps for + / - values +poscm = colormap_tor([1 0 .5], [1 1 0], [.9 .6 .1]); %reddish-purple to orange to yellow +negcm = colormap_tor([0 0 1], [0 1 1], [.5 0 1]); % cyan to purple to dark blue + +% optional inputs with default values +% ----------------------------------- +% - allowable_args is a cell array of argument names +% - avoid spaces, special characters, and names of existing functions +% - variables will be assigned based on these names +% i.e., if you use an arg named 'cl', a variable called cl will be +% created in the workspace + +allowable_args = {'cl', 'ycut_mm', 'pos_colormap', 'neg_colormap', ... + 'surface_handles', 'existingfig'}; + +default_values = {[], [], poscm, negcm, ... + [], 0}; + +% define actions for each input +% ----------------------------------- +% - cell array with one cell for each allowable argument +% - these have special meanings in the code below +% - allowable actions for inputs in the code below are: 'assign_next_input' or 'flag_on' + +actions = {'assign_next_input', 'assign_next_input', 'assign_next_input', 'assign_next_input', ... + 'assign_next_input', 'flag_on'}; + +% logical vector and indices of which inputs are text +textargs = cellfun(@ischar, varargin); +whtextargs = find(textargs); + +for i = 1:length(allowable_args) + + % assign default + % ------------------------------------------------------------------------- + + eval([allowable_args{i} ' = default_values{i};']); + + wh = strcmp(allowable_args{i}, varargin(textargs)); + + if any(wh) + % Optional argument has been entered + % ------------------------------------------------------------------------- + + wh = whtextargs(wh); + if length(wh) > 1, warning(['input ' allowable_args{i} ' is duplicated.']); end + + switch actions{i} + case 'assign_next_input' + eval([allowable_args{i} ' = varargin{wh(1) + 1};']); + + case 'flag_on' + eval([allowable_args{i} ' = 1;']); + + otherwise + error(['Coding bug: Illegal action for argument ' allowable_args{i}]) + end + + end % argument is input +end + +% END DEFAULTS AND INPUTS +% ------------------------------------------------------------------------- + + + +% Build brain surface +% ------------------------------------------------------------------------- + +if ~existingfig + create_figure('brain surface'); +end + +if isempty(surface_handles) + surface_handles = build_brain_surface(ycut_mm); +else + whbad = ~ishandle(surface_handles); + if any(whbad) + warning('Some handles are not valid. Using those that are.'); + surface_handles(whbad) = []; + end +end + +if isempty(surface_handles) + return +end + +% quit if no cluster/region input + +if isempty(cl), return, end + +% add blobs +% ------------------------------------------------------------------------- + +% Set scale for colormap based on .Z field +% These are not necessarily Z-scores, but they are assumed to be intensity values for the voxels + +clZ = cat(2,cl.Z); +refZ = [min(clZ(clZ > 0)) max(clZ) min(clZ(clZ < 0)) min(clZ)]; +refZ = [0 prctile(clZ(clZ > 0), 80) prctile(clZ(clZ < 0), 20) 0]; + +% Add blobs to all surfaces +% - Map color scale to custom colormaps using 'heatmap' and refZ, +% - and also map intensity to transparency using 'colorscale' + +% make bigger to avoid cross-hatching bug/problem +%specialp = false(size(surface_handles)); +%specialp([end end-2]) = true; +cluster_surf(cl, 4, 'heatmap', 'colormaps', pos_colormap, neg_colormap, surface_handles, refZ, 'colorscale'); + +%cluster_surf(cl, 4, 'heatmap', 'colormaps', pos_colormap, neg_colormap, surface_handles(~specialp), refZ, 'colorscale'); + + +end % main function + + +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- +% +% Sub-functions +% +% ------------------------------------------------------------------------- +% ------------------------------------------------------------------------- + + +function surface_handles = build_brain_surface(ycut_mm) + +% BUILD BRAIN SURFACE + +% handles +surface_handles = []; +p2 = []; +p3 = []; + +surface_handles = addbrain('limbic'); +delete(surface_handles(end)); surface_handles(end) = []; + +set(surface_handles, 'FaceAlpha', .7) + +surface_handles = [surface_handles addbrain('brainstem')]; + +set(gca, 'ZLim', [-85 82]); + +set(surface_handles, 'FaceColor', [.5 .5 .5]); + +hh = findobj(gcf, 'Tag', 'thalamus'); +set(hh, 'FaceAlpha', .8); + +hh = get(surface_handles, 'Tag'); +wh = strcmp(hh, 'hippocampus'); +set(surface_handles(wh), 'FaceAlpha', .6); + +wh = strcmp(hh, 'caudate'); +set(surface_handles(wh), 'FaceAlpha', .6); + +overlay = which('SPM8_colin27T1_seg.img'); +ovlname = 'SPM8_colin27T1_seg'; + +if ~isempty(ycut_mm); + [D,Ds,hdr,p2,bestCoords] = tor_3d('whichcuts','y','coords',[0 ycut_mm 0], 'topmm', 90, 'filename', ovlname, 'intensity_threshold', 70); + set(p2(1),'FaceColor',[.5 .5 .5]); +end + +[D,Ds,hdr,p3,bestCoords] = tor_3d('whichcuts','x','coords',[-4 0 0], 'topmm', 90, 'filename', ovlname, 'intensity_threshold', 60, 'bottommm', -75); +set(p3(1),'FaceColor',[.5 .5 .5]); + +colormap gray; +material dull; +axis off + +view(133, 10); + +lightRestoreSingle + +lighting gouraud + +surface_handles = [surface_handles p2 p3]; + +end + + + + + diff --git a/Visualization_functions/timeseries_prplot.m b/Visualization_functions/timeseries_prplot.m new file mode 100755 index 00000000..d4bd7357 --- /dev/null +++ b/Visualization_functions/timeseries_prplot.m @@ -0,0 +1,58 @@ +function [r,bb,f] = timeseries_prplot(y,X,cols,varargin) +% function [r,bb,f] = timeseries_prplot(y,X,cols,varargin) +% +% Plots timeseries data (y') against fitted response (X) +% y is n x 1 data points, X is n x k model matrix of predictors +% +% y is adjusted to remove all effects OTHER THAN those columns of X +% specified in cols, creating a partial residual vector y'. +% +% The fitted response to X(:,cols) is plotted against y' to graphically assess the +% effect of particular columns of X on y. +% +% varargin includes two optional arguments: +% 1) a vector of trial onsets (to shade in plot) +% 2) length of elements to shade after trial onset. +% +% outputs: +% r = partial residuals +% bb = betas +% f = partial fitted response +% +% example: +% timeseries_prplot(Yfla,X,[2 4],x2,18); +% +% to average across sessions 1 and 2: +% +% +% tor wager + + +% get betas + +b = pinv(X) * y; +bb = b; + +% get fits + +f = X(:,cols) * b(cols); + +% get partial residuals + +X(:,cols) = []; b(cols) = []; +r = y - X * b; + +% plot +tor_fig; +plot(r,'k','LineWidth',2); hold on; +plot(f,'k--'); + +if length(varargin) > 0 + x2 = varargin{1}; len = varargin{2}; + sc = 2 * max(abs(r)); + for i = 1:length(x2), hh(i) = fill([x2(i) x2(i)+len x2(i)+len, x2(i)],sc*([0 0 1 1]-.5),[.5 .5 .5]);,end + set(hh,'EdgeColor','none','FaceAlpha',.5) +end + +return + diff --git a/Visualization_functions/tor_fill_steplot.m b/Visualization_functions/tor_fill_steplot.m new file mode 100755 index 00000000..70e27b9e --- /dev/null +++ b/Visualization_functions/tor_fill_steplot.m @@ -0,0 +1,132 @@ +function [h,t,dat,d,m1,m2,sterr] = tor_fill_steplot(dat,color,varargin) +% [h,t] = tor_fill_steplot(dat,color,[robust flag],[p-thresh],[x vector],[covs no interest]) +% +% Plots a mean vector (mean of each column of dat) +% surrounded by a fill with standard err bars +% +% If dat has 3 dimensions, then +% the diff between dat(:,:,1) and dat(:,:,2) is +% used as the difference for computing standard err +% (as in repeated measures) +% +% if behavior is entered as optional argument, removes it before plotting +% lines. Also returns adjusted output in d, dat +% +% Optional: robust flag (1/0), robust IRLS +% +% tor_fig; tor_fill_steplot(dat,{'b' 'r'},0,.05,secs); +% + +dorobust = 0; pthresh = 0; x = 1:size(dat,2); +t = []; m1 = []; m2 = []; X = []; + +if length(varargin) > 0, dorobust = varargin{1};,end +if length(varargin) > 1, pthresh = varargin{2};,end +if length(varargin) > 2, x = varargin{3};,end % small x, time +if length(varargin) > 3, X = varargin{4};,end % big X, model matrix of covs + +if isempty(x), x = 1:size(dat,2);, end + + + + +if length(size(dat)) > 2 + + % remove covariates of no interest, if any + % center covs and fit without intercept to preserve mean in data + if ~isempty(X) + disp('Adjusting for covariates.'); + X = scale(X,1); % center + W = X * pinv(X); + y = squeeze(dat(:,:,1)); + dat(:,:,1) = y - W * y; + + y = squeeze(dat(:,:,2)); + dat(:,:,2) = y - W * y; + end + + d = dat(:,:,1) - dat(:,:,2); + + if dorobust + [m1] = robust_mean(dat(:,:,1)); + [m2] = robust_mean(dat(:,:,2)); + [dummy,t,p,sterr] = robust_mean(d); + else + + m1 = nanmean(dat(:,:,1)); + m2 = nanmean(dat(:,:,2)); + md = nanmean(d); + sterr = ste(d); + t = md ./ sterr; + [h,p,ci,stat] = ttest(d); + end + + if ~iscell(color), error('For two groups, color should be cell, e.g., {''r'' ''b''})');,end + + hold on; + h(1) = plot(x,m1,color{1},'LineWidth',2); + h(2) = plot(x,m2,color{2},'LineWidth',2); + + drawnow + + fill_around_line(m1,sterr,color{1},x); + fill_around_line(m2,sterr,color{2},x); + + drawnow + + if pthresh + % significance markers at top of plot. + yval = max([m1 m2]) + .04 * max([m1 m2]); + k = 1; + df = size(d,1) - k; + %tthr = tinv(1 - pthresh,df); + tsig = (p < pthresh) .* t; + + wh = yval * (tsig > 0);%double((t > tthr)); wh(wh==0) = NaN; wh(wh>0) = yval; + %plot(x,wh,color{1},'LineWidth',3); + for i = 1:length(wh) + if wh(i) + text(x(i),wh(i),'*','Color',color{1}(1),'FontSize',24); + end + end + + %wh = yval(find(tsig < 0)); % double((t < -tthr)); wh(wh==0) = NaN; wh(wh>0) = yval; + %plot(x,wh,color{2},'LineWidth',3); + + wh = yval * (tsig < 0);%double((t > tthr)); wh(wh==0) = NaN; wh(wh>0) = yval; + %plot(x,wh,color{1},'LineWidth',3); + for i = 1:length(wh) + if wh(i) + text(x(i),wh(i),'*','Color',color{2}(1),'FontSize',24); + end + end + + + text(x(5),yval+.04 * max([m1 m2]),'Significant','FontSize',16); + end + + drawnow + +else + + if dorobust + [m1,t,p,sterr] = robust_mean(dat(:,:,1)); + else + m1 = nanmean(dat(:,:,1), 1); + sterr = ste(dat); + end + + if ~iscell(color), tmp = color; color = []; color{1} = tmp; end + + hold on; + if ischar(color{1}) + h = plot(x,m1,color{1},'LineWidth',2); + else + h = plot(x,m1,'o-', 'Color', color{1},'LineWidth',2); + end + + fill_around_line(m1,sterr,color{1},x); + +end + +return \ No newline at end of file diff --git a/Visualization_functions/tor_polar_plot.m b/Visualization_functions/tor_polar_plot.m new file mode 100644 index 00000000..a414b23a --- /dev/null +++ b/Visualization_functions/tor_polar_plot.m @@ -0,0 +1,99 @@ +function tor_polar_plot(vals, colors, names, varargin) +% Make polar line plot(s) +% +% Usage: +% tor_polar_plot(vals, colors, names, ['nofigure']) +% +% vals = cell array, one cell per plot +% in each cell, matrix of observations x variables +% plots one line for each variable. +% +% names is cell array, one cell per plot +% contains cell array of names for each condition +% +% e.g., +% tor_polar_plot({w+1}, {'r' 'b'}, setnames(1)) + +dofigure = 1; + +p = length(vals); % plots + +nr = 1; nc = p; % rows/cols in subplots +if p > 4, nr = 2; nc = ceil(p./2); end + +if any(strcmp(varargin, 'nofigure')) + % suppress figure + dofigure = 0; +else + create_figure('tor_polar', nr, nc) +end + +for s = 1:p + + if dofigure, subplot(nr, nc, s);, end + + set(gca, 'FontSize', 18) + + k = size(vals{s}, 2); % variables to plot (each gets a line) + + % make non-negative + % this changes the interpretation of the origin of the plot + if any(vals{s}(:) < 0) + vals{s} = vals{s} - min(vals{s}(:)); + end + + for i = 1:k + if i == 1, hold on, end % must turn off to get polar text automatically. we now do our own. + + v = vals{s}(:, i)'; + + ang = linspace(0, 2*pi, length(v) + 1); + hh = polar(ang, [v v(1)]); + + set(hh, 'LineWidth', 2, 'Color', colors{i}) + hold on + + end + + % lines and text + + % Circles + maxval = max(max(vals{s})); + h = circle([0 0], maxval); + set(h, 'Color', 'k'); + + h = circle([0 0], maxval ./ 2); + set(h, 'Color', 'k', 'LineStyle', ':'); + + % spokes + [X, Y] = pol2cart(ang, maxval); + z = zeros(size(X)); + lineh = line([z; X], [z; Y]); + set(lineh, 'Color', 'k', 'LineStyle', ':'); + + % names + + xlim = get(gca, 'XLim'); + ylim = get(gca, 'YLim'); + + if ~isempty(names) && ~isempty(names{s}) + + for j = 1:length(names{s}) + x = X(j); + if x < 0, x = x - range(xlim)*.02*length(names{s}{j}); end + + y = Y(j); + y = y + sign(y) * range(ylim)* .04; + + texth(j) = text(x, y, names{s}{j}, 'FontSize', 14); + end + end + + axis equal + axis off + + +end % subplot + +end % function + diff --git a/Visualization_functions/xval_lasso_brain_permutation_histogram.m b/Visualization_functions/xval_lasso_brain_permutation_histogram.m new file mode 100644 index 00000000..8198cf84 --- /dev/null +++ b/Visualization_functions/xval_lasso_brain_permutation_histogram.m @@ -0,0 +1,71 @@ +function xval_lasso_brain_permutation_histogram(stats) +% Plot histograms and get permutation test-based p-value for xval_lasso_brain output structure +% +% xval_lasso_brain_permutation_histogram(stats) + +create_figure('null', 2, 2); + +for i = 1:4 + + h(i) = subplot(2, 2, i); + set(h(i), 'FontSize', 24); + +end + +% copy key info +pe = stats.full_model.pred_err; +r = stats.full_model.r_each_subject; + +penull = stats.full_model.permuted.pe_values; + +% permutations and p-value +nperms = length(penull); +nbins = ceil(nperms ./ 15); + +pval = sum(penull <= pe) ./ nperms; +pval = max(pval, 1./nperms); + +% for text on graph +xloc = mean(stats.full_model.permuted.v1000_pe_values) + std(stats.full_model.permuted.v1000_pe_values); +hi = hist(stats.full_model.permuted.v1000_pe_values, nbins); +yloc = max(hi); + +% plots + +subplot(2, 2, 1); +hist(stats.full_model.permuted.v1000_pe_values, nbins); +title('Perm 1000vox'); ylabel('PE'); +lh = plot_vertical_line(pe, 'r'); +set(lh, 'LineWidth', 3); +axis tight + +text(xloc, yloc, sprintf('p < %3.6f', pval), 'FontSize', 18); + +subplot(2, 2, 2); +hist(stats.full_model.permuted.v1000_r_values, nbins); +title('Perm 1000vox'); ylabel('Pred-outcome r'); +lh = plot_vertical_line(r, 'r'); +set(lh, 'LineWidth', 3); +axis tight + +subplot(2, 2, 3); +hist(penull, nbins); +title('Perm test'); ylabel('PE'); +lh = plot_vertical_line(pe, 'r'); +set(lh, 'LineWidth', 3); +axis tight + +subplot(2, 2, 4); +hist(stats.full_model.permuted.r_values, nbins); +title('Perm test'); ylabel('Pred-outcome r'); +lh = plot_vertical_line(r, 'r'); +set(lh, 'LineWidth', 3); +axis tight + +equalize_axes(h([1 3])) +equalize_axes(h([2 4])) + +hh = findobj(gcf, 'Type', 'patch'); set(hh, 'FaceColor', [.5 .5 .5]); + +end + diff --git a/fmridisplay/@fmridisplay/addblobs.m b/fmridisplay/@fmridisplay/addblobs.m new file mode 100644 index 00000000..2f2c5878 --- /dev/null +++ b/fmridisplay/@fmridisplay/addblobs.m @@ -0,0 +1,134 @@ +function obj = addblobs(obj, cl, varargin) +% obj = addblobs(obj, cl, varargin) +% +% Add blobs to montage and other surface plot(s) +% +% See render_blobs for options +% +% Examples: +% +% obj = addblobs(obj, cl, 'color', [0 1 1]); +% obj = addblobs(obj, cl, 'color', [0 0 1], 'outline'); +% obj = addblobs(obj, cl, 'color', [0 1 0], 'outline', 'linewidth', 1, 'smooth'); +% obj = addblobs(obj, cl, 'color', [1 0 0], 'smooth', 'cmaprange', [0 40]); +% obj = addblobs(obj, cl, ... 'wh_montages', 1); +% obj = addblobs(obj, cl, 'splitcolor', ... 'cmaprange', ... 'trans'); +% +% add only to montage 2 in vector of montages in obj.montage +% obj = addblobs(obj, cl, 'which_montages', 2); +% +% % Map values to the colormap red->yellow. +% % This uses the default percentile-based mapping so that 20% of voxels +% will have the low color and 20% will have the high color, and the rest will be in between: +% obj = addblobs(obj, cl, 'maxcolor', [1 1 0], 'mincolor', [1 0 0]); +% +% % Same, but now Map a specific range of values in image ([0 to .05]) +% obj = addblobs(obj, cl, 'maxcolor', [1 1 0], 'mincolor', [1 0 0], 'cmaprange', [0 .05]); +% +% Separate positive and negative activations and map to a split colormap; +% see render_blobs +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}, 'wh_montages', 1); +% +% It is possible to transparency-map values in a statistic image so you +% can show 'unthresholded' statistic values. e.g.: +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'trans'); +% +% Copyright Tor Wager, 2011 + +% Add the volume to activation maps in fmridisplay object +% ------------------------------------------------------------------------- + +% turn clusters into mask and volume info +[dummy, mask] = clusters2mask2011(cl); + +if sum(mask(:)) == 0, warning('No voxels in cl! Empty/zero cl.Z field? Bad cl?'); end + +XYZ = cat(2, cl(:).XYZ); + +dim = max(XYZ, [], 2); + +V = struct('mat', cl(1).M, 'dim', dim'); + +SPACE = map_to_world_space(V); + +obj.activation_maps{end + 1} = struct('mapdata', mask, 'V', V, 'SPACE', SPACE, 'blobhandles', [], 'cmaprange', [], ... + 'mincolor', [0 0 1], 'maxcolor', [1 0 0], 'color', []); + +% select which montages; default = all +wh_montage = 1:length(obj.montage); + +whm = strcmp(varargin, 'wh_montages') | strcmp(varargin, 'wh_montage') | strcmp(varargin, 'which_montages') | strcmp(varargin, 'which montages'); +if any(whm) + whm = find(whm); + wh_montage = varargin{whm(1) + 1}; +end + +% Resampling whole map seems to be too slow: do this in render slice... +% fprintf('Resampling map data to underlay space.'); +% +% [resampled_dat, SPACEto] = resample_space(mask, V, obj.SPACE); +% +% fprintf(' Done.\n'); +% +% obj.activation_maps{end + 1} = struct('mapdata', resampled_dat, 'V_original', V, 'blobhandles', []); + +wh_to_display = length(obj.activation_maps); + + +% Find valid handles and render blobs on them +% ------------------------------------------------------------------------- + +currentmap = obj.activation_maps{wh_to_display}; + +if isempty(currentmap) || ~isfield(currentmap, 'mapdata') || isempty(currentmap.mapdata) + + error('Map to display is not a valid activation map on fmridisplay object.'); + +end + +% Montages +% ------------------------------------------------------------------------- + +for i = wh_montage + + if length(obj.montage) < i + error('Requested montage does not exist! Check input montage indices.'); + end + + % render, and return color ranges (splitcolor will change these) + [blobhan, cmaprange, mincolor, maxcolor] = render_blobs(currentmap, obj.montage{i}, obj.SPACE, varargin{:}); + + % Register blob handles and colormap-defining range in object + if ~isempty(blobhan) + obj.activation_maps{wh_to_display}.blobhandles = [obj.activation_maps{wh_to_display}.blobhandles; blobhan]; + + obj.activation_maps{wh_to_display}.cmaprange = cmaprange; + + % update color info, for legend (or use defaults..set in + % render_blobs)_ + for Arg = {'color'} + + whmax = find(strcmp(varargin, Arg{1})); + if ~isempty(whmax) + obj.activation_maps{wh_to_display}.(Arg{1}) = [obj.activation_maps{wh_to_display}.(Arg{1}) varargin{whmax(1) + 1}]; + end + + end + + for Arg = {'mincolor' 'maxcolor'} + + str = (['obj.activation_maps{wh_to_display}.(Arg{1}) = ' Arg{1} ';']); + eval(str); + + end + + end +end + + +% Could add surfaces, etc. here +% ------------------------------------------------------------------------- + + +end % main function + diff --git a/fmridisplay/@fmridisplay/addpoints.m b/fmridisplay/@fmridisplay/addpoints.m new file mode 100644 index 00000000..e41a2896 --- /dev/null +++ b/fmridisplay/@fmridisplay/addpoints.m @@ -0,0 +1,242 @@ +function obj = addpoints(obj, xyz, varargin) +% newax = addpoints(obj, xyz, varargin) +% +% Plots points on fmridisplay objects (e.g., montages of slices) +% Registers handles with the object (referred to as obj) +% +% - enter xyz as n x 3 list of coordinates in mm to plot (world space) +% - Points or text labels or both +% - Flexible slice spacing, colors, marker sizes/styles, axis layout (one row/standard square) +% - axial, saggital, or coronal orientation handled automatically +% - Multiple different sets of points can be plotted in different colors/text labels +% +% Optional inputs: +% +% Takes all inputs of plot_points_on_slice. See help for additional +% documentation of options. +% +% {'text', 'textcodes'} % cell array of text values corresponding to points +% {'condf' 'colorcond'}, % vector of integers to define color conditions +% 'close_enough', % mm within which to plot; defined automatically based on slice distance if not entered +% 'color' % string, 'b', or vector, [1 0 0], to define colors; cell if condf is used, e.g., {'b' 'g'} +% {'marker', 'MarkerStyle'}, e.g., 'o', 'v', 's' +% {'MarkerSize', 'markersize'} +% {'MarkerFaceColor', 'markerfacecolor'} see color above +% % +% Examples: +% +% Plot points (i.e., coordinate locations) for xyz coords: +% o2 = addpoints(o2, DB.xyz, 'MarkerFaceColor', 'b', 'Marker', 'o', 'MarkerSize', 4); +% o2 = addpoints(o2, DB.xyz, 'text', DB.textcodes, 'condf', DB.condf, 'color', {'b' 'g'}); +% o2 = removepoints(o2); + +% select which montages; default = all +wh_montage = 1:length(obj.montage); + +whm = strcmp(varargin, 'wh_montages') | strcmp(varargin, 'wh_montage') | strcmp(varargin, 'which_montages') | strcmp(varargin, 'which montages'); +if any(whm) + whm = find(whm); + wh_montage = varargin{whm(1) + 1}; +end + +% Montages +% ------------------------------------------------------------------------- + +for i = wh_montage + + if ~isfield(obj.montage{i}, 'plotted_point_handles') || isempty(obj.montage{i}.plotted_point_handles) + + obj.montage{i}.plotted_point_handles = []; + + end + + % Plot it + pointhan = plotpoints(xyz, obj.montage{i}, varargin{:}); + + obj.montage{i}.plotted_point_handles = [obj.montage{i}.plotted_point_handles pointhan]; + +end + + + +end % main function + + + + + +function pointhan = plotpoints(xyz, montagestruct, varargin) + + +% fixed fields + +myview = montagestruct.orientation; +slicemm = montagestruct.slice_mm_coords; +axhan = montagestruct.axis_handles; + +% optional inputs + +textcodes = []; +texthandles = []; +condf = []; + +close_enough = min(diff(slicemm)) ./ 2; +if isempty(close_enough), close_enough = 8; end + +color = 'k'; +marker = 'o'; +markersize = 12; +markerfacecolor = 'k'; + +% ------------------------------------------------------ +% parse inputs +% ------------------------------------------------------ + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + + case {'text', 'textcodes'} % do not pass on to slice plot... + textcodes = varargin{i + 1}; + varargin{i+1} = []; + varargin{i} = []; + + case {'condf' 'colorcond'}, condf = varargin{i + 1}; + + case 'close_enough', close_enough = varargin{i + 1}; + + case 'color' % do not pass on... + color = varargin{i+1}; + varargin{i+1} = []; + varargin{i} = []; + + case {'marker', 'MarkerStyle'}, marker = varargin{i+1}; + + case {'MarkerSize', 'markersize'}, markersize = varargin{i+1}; + + case {'MarkerFaceColor', 'markerfacecolor'}, markerfacecolor = varargin{i+1}; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +% SETUP +% ----------------------------------------------- + +fprintf('Plotting points within %3.2f mm of slices\n', close_enough); + +if isempty(condf) + condf = ones(size(xyz, 1), 1); + + if iscell(color) + error('Color should not be a cell array.'); + end + color = {color}; + +else + if ~iscell(color) + error('When entering condf, enter colors cell with same number of entries.'); + end +end + +u = unique(condf); +n = length(u); + +% Do the work for each slice +% ----------------------------------------------- + +pointhan = []; + +for i = 1:length(axhan) + axes(axhan(i)); + + for j = 1:n % For each color code + + % Select coordinates (2D) for plot + slicexyz = xyz; + + switch myview + case 'axial' + whcol = 3; + + case {'sagg', 'sagittal', 'saggital'} + + whcol = 1; + + case {'cor', 'coronal'} + + whcol = 2; + + otherwise + error('Unknown slice orientation.') + end + + + + wh_to_plot = condf == u(j) & abs(slicexyz(:, whcol) - slicemm(i)) <= close_enough; + + slicexyz = slicexyz(wh_to_plot, :); + + slicexyz(:, whcol) = []; + + if ~isempty(slicexyz) + + if isempty(textcodes) + + pointhan(end + 1) = plot(slicexyz(:, 1), slicexyz(:, 2), '.', 'color', color{j}, 'Marker', marker, 'MarkerSize', markersize, 'MarkerFaceColor', markerfacecolor); + + else + my_text = textcodes(wh_to_plot); + + pointhan = [pointhan plottext(slicexyz(:, 1), slicexyz(:, 2), my_text, color{j}, myview)]; + + end + + end + + + + end % condition color codes + +end % slices + +end % function + + + + + + + +function texthandles = plottext(x, y, textcodes, color, orientation) + +% adjust for font placement +switch orientation + case 'sagittal' + xshift = 6; + case 'axial' + xshift = -6; + +end + + +texthandles = []; +for j = 1:length(textcodes) + % text labels + + + if ischar(color) + + texthandles(j) = text(x(j) + xshift, y(j), textcodes{j},'Color',color,'FontSize',12,'FontWeight','bold'); + + else + texthandles(j) = text(x(j) + xshift, y(j), textcodes{j},'Color',color,'FontSize',12,'FontWeight','bold'); + + end + +end + +end % plottext + diff --git a/fmridisplay/@fmridisplay/fmridisplay.m b/fmridisplay/@fmridisplay/fmridisplay.m new file mode 100644 index 00000000..915d7b54 --- /dev/null +++ b/fmridisplay/@fmridisplay/fmridisplay.m @@ -0,0 +1,179 @@ +% fmridisplay: Data class for storing data matrices and information +% +% 'fmridisplay' is a data class containing information about an underlay +% and activation map(s) for creating montage plots and other types of +% plots. +% +% Creating class instances +% ----------------------------------------------------------------------- +% +% +% Examples +% ----------------------------------------------------------------------- +% obj = fmridisplay; Create object with canonical underlay +% obj = montage(obj); Show axial montage of underlay +% obj = addblobs(obj, cl); Add blobs from cl clusters +% +% obj = fmridisplay('montage', 'addblobs', cl); Do all of the above +% +% Add colored green blobs, with smoothed edges +% obj = addblobs(obj, cl, 'trans', 'color', [0 1 0], 'smooth'); +% +% Create a second montage with 4 mm spacing and add blue blobs to both: +% obj = montage(obj, 'spacing', 4); +% obj = addblobs(obj, cl, 'trans', 'color', [0 0 1], 'smooth'); +% +% Add axial and parasaggital montages to the same figure: +% o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); +% axh = axes('Position', [0.03 0.45 .1 .5]); +% o2 = montage(o2, 'saggital', 'wh_slice', [0 0 0], 'existing_axes', axh); +% +% Add blue outlines only: +% obj = addblobs(obj, cl, 'outline', 'color', [0 0 1]); +% +% Sagittal images and blobs: +% o2 = fmridisplay; +% o2 = montage(o2, 'saggital', 'slice_range', [-20 20], 'onerow'); +% o2 = addblobs(o2, cl); +% legend(o2, 'figure') +% o2 = addblobs(o2, cl, 'contour', 'color', [0 1 0]); +% +% Display over your custom anatomical image: +% overlay = 'mean_T1_FSL1.nii'; +% o2 = fmridisplay('overlay', overlay); +% +% Overlapping sagittal and axial images with outlines +% o2 = fmridisplay; +% o2 = montage(o2, 'saggital', 'slice_range', [-10 10], 'onerow'); +% enlarge_axes(gcf, 1.2); +% o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 4); +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}); +% o2 = addblobs(o2, cl, 'maxcolor', [1 0 0], 'mincolor', [1 .3 0], 'cmaprange', [0 .01], 'outline'); +% +% Add transparent blobs, either mapped with color scale or constant +% opacity value: +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'trans'); +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'transvalue', .85); +% +% o2 = montage(o2, 'saggital', 'slice_range', [-6 6], 'onerow'); +% o2 = montage(o2, 'axial', 'slice_range', [-20 30], 'onerow', 'spacing', 8); +% xyz = a list of [x y z] coordinates, one coord per row +% o2 = montage(o2, 'axial', 'wh_slice', xyz, 'onerow'); +% squeeze_axes(o2.montage{1}.axis_handles, 20) +% squeeze_axes(o2.montage{2}.axis_handles, 10) +% +% Plot points (i.e., coordinate locations) rather than blobs: +% o2 = addpoints(o2, DB.xyz, 'MarkerFaceColor', 'b', 'Marker', 'o', 'MarkerSize', 4); +% o2 = addpoints(o2, DB.xyz, 'text', DB.textcodes, 'condf', DB.condf, 'color', {'b' 'g'}); +% o2 = removepoints(o2); +% +% Methods +% ----------------------------------------------------------------------- +% See methods(fmridisplay) +% +% See fmridisplay.montage for list of all slice display options +% See fmridisplay.render_blobs for list of all rendering options +% +% Copyright 2011 - Tor Wager + +classdef fmridisplay + + properties + + overlay + SPACE + activation_maps + + montage + surface + orthviews + + history + history_descrip = 'Cell array: names of methods applied to this data, in order'; + additional_info = struct(''); + + end % properties + + methods + + % Class constructor + function obj = fmridisplay(varargin) + + + % --------------------------------- + % Create empty object, and return if no additional + % arguments + % --------------------------------- + + obj.overlay = which('SPM8_colin27T1_seg.img'); % spm8 seg cleaned up + + if any(strcmp(varargin, 'overlay')) + wh = find(strcmp(varargin, 'overlay')); + wh = wh(1); + obj.overlay = varargin{wh + 1}; + varargin([wh:wh+1]) = []; + end + + obj.SPACE = []; + + if ~isempty(obj.overlay) + V = spm_vol(obj.overlay); + end + + obj.SPACE = define_sampling_space(V); + obj.activation_maps = {}; + + obj.montage = {}; + obj.surface = {}; + obj.orthviews = {}; + +% obj.axis_handles = struct('montage', [], 'surface', [], 'orthviews', []); +% obj.axis_handles.montage = struct('axial', [], 'sagittal', [], 'coronal', []); +% + obj.history = {}; + obj.history_descrip = []; + obj.additional_info = ''; + + if nargin == 0 + return + end + + + % Now parse inputs and run methods depending on what is entered + + if any(strcmp(varargin, 'montage')) + wh = strcmp(varargin, 'montage'); + varargin(wh) = []; + obj = montage(obj, varargin{:}); + end + + for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'addblobs' + + cl = varargin{i + 1}; + if ~isstruct(cl) && ~isa(cl, 'region') + disp('addblobs arg should be followed by clusters/region structure.'); + end + + obj = addblobs(obj, cl, varargin); + + case {'slice_range', 'smooth', 'spacing', 'onerow'} + % other inputs that we should ignore here + % but are used in subfunctions like montage, + % etc. + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end + end + + + end % constructor function + + end % methods + + +end \ No newline at end of file diff --git a/fmridisplay/@fmridisplay/legend.m b/fmridisplay/@fmridisplay/legend.m new file mode 100644 index 00000000..fa40a4da --- /dev/null +++ b/fmridisplay/@fmridisplay/legend.m @@ -0,0 +1,142 @@ +function obj = legend(obj, varargin) +% Creates legend for fmridisplay object +% Adds legend axis handles to obj.activation_maps{:} +% +% obj = legend(obj, varargin) +% obj = legend(obj, 'figure') % new figure +% +% Tor Wager + +donewfig = 0; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case {'figure' 'newfig'}, donewfig = 1; + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +if donewfig + create_figure('legend'); axis off + + mypositions = {[0.100 0.1200 0.80 0.04] ... + [0.100 0.2800 0.80 0.04] ... + [0.100 0.46 0.80 0.04] ... + [0.100 0.64 0.80 0.04] ... + }; + + myfontsize = 18; + +else + mypositions = {[0.100 0.1200 0.20 0.04] ... + [0.100 0.2800 0.20 0.04] ... + [0.400 0.1200 0.20 0.04] ... + [0.400 0.2800 0.20 0.04] ... + }; + myfontsize = 14; + +end + +for c = 1:length(obj.activation_maps) + + currentmap = obj.activation_maps{c}; + + scaleanchors = currentmap.cmaprange; %for text labels + + if ~diff(scaleanchors) + disp('No variability in mapped values. Not plotting legend.'); + continue + end + + if c > length(mypositions) + disp('Maximum number of legends exceeded. Not plotting remaining legends.'); + break + end + + obj.activation_maps{c}.legendhandle = axes('Position', mypositions{c}); + hold on; + + % mapd = currentmap.mapdata(:); mapd = mapd(mapd ~= 0 & ~isnan(mapd)); + % scaleanchors = [min(mapd) max(mapd)]; + + % This info should already be in currentmap + % cmaprange = [prctile(mapd, 20) prctile(mapd, 80)]; + % currentmap.cmaprange = cmaprange; + % currentmap.maxcolor = [1 1 0]; + % currentmap.mincolor = [1 .3 0]; + + % fix for multiple blobs + if size(currentmap.mincolor, 2) > 3 || size(currentmap.maxcolor, 2) > 3 + fprintf('Warning! Extra colors in map...legend will not display correctly with multiple blobs'); + + currentmap.mincolor = currentmap.mincolor(:, 1:3); + currentmap.maxcolor = currentmap.maxcolor(:, 1:3); + end + + nsteps = 100; + + legvals = linspace(currentmap.cmaprange(1), currentmap.cmaprange(2), nsteps); + wvals = linspace(0, 1, nsteps); + + % separate plot for split colormap + issplitmap = size(currentmap.mincolor, 1) - 1; % zero for one row, 1 for 2+ + + for i = 2:nsteps + + if ~issplitmap || (issplitmap && legvals(i) > 0) + + fcolor = (1 - wvals(i)) * currentmap.mincolor(1, :) + wvals(i) * currentmap.maxcolor(1, :); + + elseif issplitmap && legvals(i) < 0 + + fcolor = (1 - wvals(i)) * currentmap.mincolor(2, :) + wvals(i) * currentmap.maxcolor(2, :); + + else + error('This should not happen...debug me.') + end + + fcolor(fcolor > 1) = 1; + fcolor(fcolor < 0) = 0; + + try + fill([legvals(i-1) legvals(i-1) legvals(i) legvals(i)], [0 1 1 0], fcolor, 'EdgeColor', 'none'); + + catch + disp('problem with legend fill.') + keyboard + end + + end + + myxtick = linspace(scaleanchors(1), scaleanchors(2), 5); + + % kludgy fix for sig digits + for i = 1:5 + mylabels{i} = sprintf('%3.2f', myxtick(i)); + end + + if sum(strcmp(mylabels, mylabels{1})) > 1 + for i = 1:5 + mylabels{i} = sprintf('%3.3f', myxtick(i)); + end + end + + + if sum(strcmp(mylabels, mylabels{1})) > 1 + for i = 1:5 + mylabels{i} = sprintf('%3.4f', myxtick(i)); + end + end + + + set(gca, 'YTickLabel', '', 'YColor', 'w', 'FontSize', myfontsize); + set(gca, 'XLim', scaleanchors, 'XTick', myxtick, 'XTickLabel', mylabels); + axis tight + +end + + +end % function \ No newline at end of file diff --git a/fmridisplay/@fmridisplay/montage.m b/fmridisplay/@fmridisplay/montage.m new file mode 100644 index 00000000..a76e0b9b --- /dev/null +++ b/fmridisplay/@fmridisplay/montage.m @@ -0,0 +1,264 @@ +function obj = montage(obj, varargin) +% obj = montage(obj, varargin) +% +% Creates montage of slices +% - Solid brain slices or contour outlines +% - Points or text labels or both +% - Flexible slice spacing, colors, marker sizes/styles, axis layout (one row/standard square) +% - axial or saggital orientation +% +% Takes all inputs of plot_points_on_slice. Optional inputs: +% {'noslice', 'nodraw'}, drawslice = 0; +% 'color', color = varargin{i+1}; varargin{i+1} = []; +% 'marker', marker = varargin{i+1}; varargin{i + 1} = []; +% {'wh_slice'}, wh_slice = varargin{i+1}; +% {'close', 'closeenough', 'close_enough'}, close_enough = varargin{i+1}; +% {'sagg','saggital','sagittal'}, orientation = 'sagittal'; +% {'MarkerSize', 'markersize'}, markersize = varargin{i+1}; +% {'MarkerFaceColor', 'markerfacecolor'}, facecolor = varargin{i+1}; +% 'solid', disptype = 'solid'; +% 'overlay', ovl = varargin{i + 1}; NOTE! DO NOT ENTER THIS HERE; ENTER +% WHEN YOU INITIALIZE FMRIDISPLAY OBJECT +% {'text', 'textcodes'}, textcodes = varargin{i + 1}; +% {'condf' 'colorcond'}, condf = varargin{i + 1}; +% +% In addition: +% 'onerow' : arrange axes in one row +% 'slice_range' : [min max] values in mm for slices to plot +% 'wh_slice' or 'custom_coords' : followed my mm values for slices desired +% e.g., for cluster/region centers, xyz = cat(1, cl.mm_center) +% o2 = montage(o2, 'axial', 'wh_slice', xyz, 'onerow'); +% o2 = montage(o2, 'saggital', 'wh_slice', xyz, 'onerow'); +% 'spacing' : followed by inter-slice spacing in mm +% +% Define new axes in existing figure, and use those for montage: +% axh = axes('Position', [0.05 0.4 .1 .5]); +% o2 = montage(o2, 'saggital', 'wh_slice', xyz(1,:), 'existing_axes', axh); +% +% Returns: +% obj, an fmridisplay object +% obj = +% +% fmridisplay +% +% Properties: +% overlay: [1x105 char] +% SPACE: [1x1 struct] +% activation_maps: {[1x1 struct]} +% montage: {[1x1 struct]} +% surface: {} +% orthviews: {} +% history: {} +% history_descrip: [] +% additional_info: '' +% +% Examples: +% See help fmridisplay + +% Programmers' notes: +% 3/2012 : Fixed bug in slices displayed when choosing exactly 3 custom +% slices. Transposed coordinates for voxel2mm. (Tor) + + +% +% initialize, if nothing passed in; but you would have to call overloaded +% method, fmridisplay.montage, to invoke this. +if nargin == 0 || isempty(obj) || ~isa(obj, 'fmridisplay') + obj = fmridisplay; +end + +if isempty(obj.SPACE) || ~isstruct(obj.SPACE.V) + error('fmridisplay is not initialized correctly. run obj = fmridisplay; first and then pass in to this method.') + +end + +donewaxes = 1; % default - new figure/axes +lightenstr = 'lighten'; +myview = 'axial'; +disptype = 'solid'; % or contour +doonerow = 0; +spacing = 6; % slice spacing, in mm +ovl = which('SPM8_colin27T1_seg.img'); % which('scalped_avg152T1.img'); +textcodes = []; +texthandles = []; +slice_range = 'auto'; +custom_coords = 0; +color = 'k'; + +% ------------------------------------------------------ +% parse inputs +% ------------------------------------------------------ + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + % reserved keywords + + case {'onerow'}, doonerow = 1; + + case 'slice_range', slice_range = varargin{i + 1}; + + case {'spacing'}, spacing = varargin{i+1}; + + case {'text', 'textcodes'} % do not pass on to slice plot... + textcodes = varargin{i + 1}; + varargin{i+1} = []; + varargin{i} = []; + + case {'sag', 'sagg','saggital','sagittal'}, myview = 'sagittal'; + + case {'cor', 'coronal'}, myview = 'coronal'; + + case {'condf' 'colorcond'}, condf = varargin{i + 1}; + + case 'overlay', ovl = varargin{i + 1}; + + case {'wh_slice', 'custom_coords', 'custom_slices'} + custom_coords = 1; + custom_slice_mm = varargin{i + 1}; + + case 'existing_axes' + donewaxes = 0; + newax = varargin{i + 1}; + + %otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + +% SETUP +% ----------------------------------------------- +% Volume-level: Set up underlay + +fprintf('Load underlay. ') +dat = spm_read_vols(obj.SPACE.V); + +fprintf('Define axes. ') +setup_axes(); + +% fprintf('Lighten edges. ') +% dat = lighten_underlay_edges(dat, 10); + +fprintf('Ready. \n') + + +% Do the work for each slice +% ----------------------------------------------- + +figure(slices_fig_h) + +for i = 1:length(slice_vox_coords) + + axes(newax(i)); + + wh_slice = slice_vox_coords(i); + + Z = display_slice(dat, wh_slice, obj.SPACE, myview, lightenstr); + + hold on + + axis off +end + +% register info in fmridisplay object + +obj.montage{end + 1} = struct('axis_handles', newax, 'orientation', myview, 'slice_mm_coords', slice_mm_coords); + + +% ------------------------------------------------------ +% INLINE FUNCTIONS +% ------------------------------------------------------ + + function setup_axes + + myviews = {'axial' 'coronal' 'sagittal'}; % for selecting SPM window + whview = find(strcmp(myviews, myview)); + if isempty(whview), error('myview must be axial, coronal, or sagittal.'); end + + switch whview + case 1 + if strcmp(slice_range, 'auto') + slice_range = [-45 70]; + end + + cen = [slice_range(1):spacing:slice_range(2)]'; + if custom_coords + cen = custom_slice_mm(:, 3); + end + slice_mm_coords = cen; + cen = [zeros(length(cen), 2) cen]; + xyzvox = mm2voxel(cen', obj.SPACE.V.mat, 1); + slice_vox_coords = xyzvox(:, 3); + + case 2 + if strcmp(slice_range, 'auto') + slice_range = [-100 65]; + end + + cen = [slice_range(1):spacing:slice_range(2)]'; + if custom_coords + cen = custom_slice_mm(:, 2); + end + slice_mm_coords = cen; + cen = [zeros(length(cen),1) cen zeros(length(cen),1)]; + xyzvox = mm2voxel(cen', obj.SPACE.V.mat, 1); + slice_vox_coords = xyzvox(:, 2); + + case 3 + if strcmp(slice_range, 'auto') + slice_range = [-70 70]; + end + + cen = [slice_range(1):spacing:slice_range(2)]'; + if custom_coords + cen = custom_slice_mm(:, 1); + end + slice_mm_coords = cen; + cen = [ cen zeros(length(cen), 2)]; + xyzvox = mm2voxel(cen', obj.SPACE.V.mat, 1); + slice_vox_coords = xyzvox(:, 1); + end + + + myviews2 = {'sagittal' 'coronal' 'axial' }; % for selecting coord + whcoord = strmatch(myview, myviews2) ; + + % get text string base + mystr = {'x = ' 'y = ' 'z = '}; + textbase = mystr{whcoord}; + + % get optimal number of axes + num_axes = size(cen, 1); + rc = ceil(sqrt(num_axes)); + + if ~donewaxes + % Break and return here if we are using existing axes. + slices_fig_h = get(newax(1), 'Parent'); + return + end + + slices_fig_h = figure; %create_figure(myview); + set(slices_fig_h, 'Color', 'w'); + + if doonerow + ss = get(0, 'ScreenSize'); + set(gcf, 'Position', [round(ss(3)/12) round(ss(4)*.9) round(ss(3)*.9) round(ss(4)/7) ]) + end + + for i = 1:num_axes + + if doonerow + newax(i) = subplot(1, num_axes, i); + else + newax(i) = subplot(rc, rc, i); + end + + axis off; + end + + enlarge_axes(gcf, 1.4); + + end % setup axes + +end % main function + diff --git a/fmridisplay/@fmridisplay/removeblobs.m b/fmridisplay/@fmridisplay/removeblobs.m new file mode 100644 index 00000000..75855a2f --- /dev/null +++ b/fmridisplay/@fmridisplay/removeblobs.m @@ -0,0 +1,33 @@ +function obj = removeblobs(obj) + +to_remove = []; + +for i = 1:length(obj.activation_maps) + + if isfield(obj.activation_maps{i}, 'blobhandles') + + wh = ishandle(obj.activation_maps{i}.blobhandles); + + if any(wh) + delete(obj.activation_maps{i}.blobhandles(wh)) + + to_remove(end+1) = i; + end + + if isfield(obj.activation_maps{i}, 'legendhandle') + + wh = ishandle(obj.activation_maps{i}.legendhandle); + + if any(wh) + delete(obj.activation_maps{i}.legendhandle(wh)) + end + + end + end + +end + + +obj.activation_maps(to_remove) = []; + +end diff --git a/fmridisplay/@fmridisplay/removepoints.m b/fmridisplay/@fmridisplay/removepoints.m new file mode 100644 index 00000000..5b344769 --- /dev/null +++ b/fmridisplay/@fmridisplay/removepoints.m @@ -0,0 +1,21 @@ +function obj = removepoints(obj) + + +for i = 1:length(obj.montage) + + if isfield(obj.montage{i}, 'plotted_point_handles') + + wh = ishandle(obj.montage{i}.plotted_point_handles); + + if any(wh) + delete(obj.montage{i}.plotted_point_handles(wh)) + end + + end + +end + + + +end + diff --git a/fmridisplay/@fmridisplay/transparency_change.m b/fmridisplay/@fmridisplay/transparency_change.m new file mode 100644 index 00000000..94f732dc --- /dev/null +++ b/fmridisplay/@fmridisplay/transparency_change.m @@ -0,0 +1,27 @@ +function transparency_change(o2, multval) +% transparency_change(o2, multval) +% +% Change the transparency of blobs in an fmridisplay object +% +% multval: multiply transparency values by this. +% values < 1 makes blobs more transparent, > 1 makes blobs more opaque + +for m = 1:length(o2.activation_maps) + + h = o2.activation_maps{m}.blobhandles; + + for i = 1:length(h) + + d1 = get(h(i), 'AlphaData'); + + d1 = d1 .* multval; + + d1(d1 > 1) = 1; + + set(h(i), 'AlphaData', d1); + + end + +end + +end \ No newline at end of file diff --git a/fmridisplay/clusters2mask2011.m b/fmridisplay/clusters2mask2011.m new file mode 100644 index 00000000..18b4eadf --- /dev/null +++ b/fmridisplay/clusters2mask2011.m @@ -0,0 +1,85 @@ +function [mask, mask2] = clusters2mask2011(cl, varargin) +% [mask, mask2] = clusters2mask2011(cl, [dim]) +% +% Returns 3-D mask of voxels included in a cl structure. +% Mask values are coded with the cluster index. That is, the non-zero entries +% in mask are integers that reflect the index of the unique contiguous cluster to +% which each voxel belongs. +% +% Any non-zero value indicates membership in a cluster +% Uses VOXEL values from cl, so define cl in the space you wish to have for +% mask first! +% +% Optional: +% - A 2nd argument will be treated as 'dim', dimensions in voxels of the +% new mask image. If empty, uses max value in cluster to determine +% automatically, but then the mask may not match image dimensions desired +% for .img/.nii reading/writing purposes. +% +% - If a second output is requested, the second output (maskz) is a mask +% like the first, but the values in the mask reflect the numeric value +% stored in the cl.Z field (whether Z-scores or other values, depending on +% how cl is constructed.) +% +% Copyright 2011 Tor Wager + + +mask = []; +mask2 = []; + +if isempty(cl), return, end + +if isa(cl, 'region') && ~any(cat(1, cl.numVox)) + [mask, mask2] = deal(zeros(cl(1).dim)); + return +end + +if ~isfield(cl(1), 'M') || isempty(cl(1).M) + %error('cl(1).M must have spm-style mat info, i.e., as returned in spm_vol.m V.mat'); + % do nothing; use voxel values alone + % if we have .mat file entered from some other source, could use that + % to reconstruct. but probably better to recon in voxel dims anyway, + % and then resample to the grid defined by the new .mat information. + % Then we will have voxels filled in appropriately when upsampling. + % So the .M field is really not necessary at all. +end + +XYZ = cat(2, cl(:).XYZ); + +if length(varargin) == 0 + % get dim automatically + dim = max(XYZ, [], 2); + +else + % we have dim entered + dim = varargin{1}; + + if any(size(dim) - [3 1]) + dim = dim'; + end + + if any(size(dim) - [3 1]) + error('dim argument must be 3 x 1 vector of image dimensions, in voxels'); + end + + +end + + +for i = 1:length(cl) + wh_cl{i} = repmat(i, size(cl(i).XYZ, 2), 1); +end +wh_cl = cat(1, wh_cl{:}); + + +ind = sub2ind(dim', XYZ(1, :), XYZ(2, :), XYZ(3, :)); + +[mask, mask2] = deal(zeros(dim')); +mask(ind) = wh_cl; + +if nargout > 1 + mask2(ind) = cat(2, cl(:).Z); +end + +end + diff --git a/fmridisplay/define_sampling_space.m b/fmridisplay/define_sampling_space.m new file mode 100644 index 00000000..1417d23a --- /dev/null +++ b/fmridisplay/define_sampling_space.m @@ -0,0 +1,71 @@ +function SPACE = define_sampling_space(V, varargin) +% SPACE = define_sampling_space(V, [upsamplefactor]) +% +% Define the sampling space of an image, with an upsampled space to 0.5 mm +% resolution +% +% Inputs: +% V: spm-style .mat structure, e.g., from spm_vol +% V.mat : 4 x 4 matrix of voxel sizes and mm coords for the bottom +% back left vox +% V.dim : dimensions of image +% Outputs: +% Xo, Yo : Meshgrid for original voxel space +% X, Y : Meshgrid for upsampled voxel space at 0.5 mm resolution +% Xmm, Ymm : Meshgrid for upsampled space in mm +% xcoords, ycoords : mm coordinates for rows and cols for slice locations +% new_voxSize : new voxel size in mm for upsampled space +% usfactor : Upsampling factor for new sampleing space +% +% Examples: +% overlay = which('SPM8_colin27T1_seg.img'); % spm8 seg cleaned up +% V = spm_vol(overlay); +% SPACE = define_sampling_space(V) +% +% Define mm sampling space in original voxel coord resolution +% SPACE = define_sampling_space(V, 1) +% +% original (o) and new (X, Y) grid space +% xcoords, ycoords: mm coords centered on origin + +voxSize = abs(diag(V.mat(1:3, 1:3))); % in mm + +%Zo = vol3(:, :, 32); % x by y mm + +% nx = size(vol3, 2); +% ny = size(vol3, 1); + +nx = V.dim(1); +ny = V.dim(2); +nz = V.dim(3); + +[Xo, Yo, Zo] = meshgrid(1:ny, 1:nx, 1:nz); + +if length(varargin) > 0 && ~isempty(varargin{1}) + usfactor = varargin{1}; +else + usfactor = ceil(max(voxSize) ./ .5); % upsample factor for 0.5 mm res +end + +new_voxSize = voxSize ./ usfactor; + +x = linspace(1, nx, nx * usfactor); +y = linspace(1, ny, ny * usfactor); +z = linspace(1, nz, nz * usfactor); + +[X, Y, Z] = meshgrid(y, x, z); + +bottomleft_mm = V.mat(1:3, 4); +topright_mm = voxel2mm([V.dim(1:3)]', V.mat); + +xcoords = linspace(bottomleft_mm(1,1), topright_mm(1), nx * usfactor); +ycoords = linspace(bottomleft_mm(2,1), topright_mm(2), ny * usfactor); +zcoords = linspace(bottomleft_mm(3,1), topright_mm(3), nz * usfactor); + +[Xmm, Ymm, Zmm] = meshgrid(ycoords, xcoords, zcoords); + +SPACE = struct('V', V, 'Xo', Xo, 'Yo', Yo, 'Zo', Zo, 'X', X, 'Y', Y, 'Z', Z, ... + 'usfactor', usfactor, 'new_voxSize', new_voxSize, ... + 'xcoords', xcoords, 'ycoords', ycoords, 'zcoords', zcoords, 'Xmm', Xmm, 'Ymm', Ymm, 'Zmm', Zmm); + +end \ No newline at end of file diff --git a/fmridisplay/display_slice.m b/fmridisplay/display_slice.m new file mode 100644 index 00000000..c68ddd8b --- /dev/null +++ b/fmridisplay/display_slice.m @@ -0,0 +1,106 @@ +function Z = display_slice(dat, wh_slice, SPACE, varargin) +% Z = display_slice(dat, wh_slice, SPACE, varargin) +% +% Resample slice data in dat to SPACE and display + +dolighten = 0; +myview = 'axial'; + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + + case 'lighten', dolighten = 1; + case 'nolighten', dolighten = 0; + + case 'sagittal', myview = 'sagittal'; %disp('Warning! NOT implemented correctly yet!!!'), %pause(5) + case 'coronal', myview = 'coronal'; %disp('Warning! NOT implemented correctly yet!!!'), pause(5) + case 'axial', myview = 'axial'; + + otherwise, warning('displayslice:Badinput', ['Unknown input string option:' varargin{i}]); + end + end +end + + +switch myview + + case 'axial' + + + Zo = dat(:, :, wh_slice); + + Z = interp2(SPACE.Xo(:, :, wh_slice), SPACE.Yo(:, :, wh_slice), Zo, SPACE.X(:, :, wh_slice), SPACE.Y(:, :, wh_slice)); + + % set transparent value for clear axes + myAlphaData = double(abs(Z') > 0); + + % done at slice level now + Z = lighten_underlay_edges(Z, 32); + + h = imagesc(SPACE.xcoords, SPACE.ycoords, Z'); + + % If we set alphadata to clear for BG and axis color to none, we get clear + % axes + set(h, 'AlphaDataMapping', 'scaled', 'AlphaData', myAlphaData) + set(gca, 'Color', 'none') + + case 'coronal' + Zo = squeeze(dat(:, wh_slice, :)); + + yo = squeeze(SPACE.Yo(:, wh_slice, :)); + zo = squeeze(SPACE.Zo(:, wh_slice, :)); + yi = squeeze(SPACE.Y(:, wh_slice, :)); + zi = squeeze(SPACE.Z(:, wh_slice, :)); + + Z = interp2(yo', zo', Zo', yi', zi'); + + % set transparent value for clear axes + myAlphaData = double(abs(Z) > 0); + + % done at slice level now + Z = lighten_underlay_edges(Z, 32); + + h = imagesc(SPACE.xcoords', SPACE.zcoords', Z); % Wani modified ycoords -> xcoords + + % If we set alphadata to clear for BG and axis color to none, we get clear + % axes + set(h, 'AlphaDataMapping', 'scaled', 'AlphaData', myAlphaData) + set(gca, 'Color', 'none') + + case 'sagittal' + Zo = squeeze(dat(wh_slice, :, :)); + + xo = squeeze(SPACE.Xo(wh_slice, :, :)); + zo = squeeze(SPACE.Zo(wh_slice, :, :)); + xi = squeeze(SPACE.X(wh_slice, :, :)); + zi = squeeze(SPACE.Z(wh_slice, :, :)); + + Z = interp2(xo', zo', Zo', xi', zi'); + + % set transparent value for clear axes + myAlphaData = double(abs(Z) > 0); + + % done at slice level now + Z = lighten_underlay_edges(Z, 32); + + h = imagesc(SPACE.ycoords', SPACE.zcoords', Z); + + % If we set alphadata to clear for BG and axis color to none, we get clear + % axes + set(h, 'AlphaDataMapping', 'scaled', 'AlphaData', myAlphaData) + set(gca, 'XDir', 'reverse', 'Color', 'none') + + otherwise + + error ('Unknown view. Check inputs...view must be axial, sagittal.'); + +end + +colormap(gray); +set(gca,'YDir', 'Normal') +axis image + +end + + diff --git a/fmridisplay/lighten_underlay_edges.m b/fmridisplay/lighten_underlay_edges.m new file mode 100644 index 00000000..e4ba9142 --- /dev/null +++ b/fmridisplay/lighten_underlay_edges.m @@ -0,0 +1,26 @@ +%% lighten underlay volume - for white background +function Z = lighten_underlay_edges(Z, maxpercent) + +Zi = Z(:); + +mindat = min(Zi); +maxdat = max(Zi); + +Z(Z == mindat) = maxdat; + +% higher ending values of k will be more 'softening'; lower = +% more dark edges +for k = 1:maxpercent % for each of the darkest/lowest-value percentiles + + w = 1 - k/100; % weights, for wtd average of orig and inverted, 1 is all inverted + % (bilinear weighting) + + isdark = Zi < prctile(Zi, k) & Zi >= prctile(Zi, k - 1); + + Z(isdark) = maxdat; + + %Z(isdark) = w .* (maxdat) + (1 - w) .* Z(isdark); + +end % the weight value loop + +end \ No newline at end of file diff --git a/fmridisplay/map_to_world_space.m b/fmridisplay/map_to_world_space.m new file mode 100644 index 00000000..ce69bde8 --- /dev/null +++ b/fmridisplay/map_to_world_space.m @@ -0,0 +1,34 @@ +function SPACE = map_to_world_space(V) +% SPACE = map_to_world_space(V) +% +% Inputs: +% ------------------------------------------------------------------------- +% V: spm-style .mat structure, e.g., from spm_vol +% V.mat : 4 x 4 matrix of voxel sizes and mm coords for the bottom +% back left vox +% V.dim : dimensions of image +% +% Outputs: SPACE structure, with fields: +% ------------------------------------------------------------------------- +% Xmm, Ymm, Zmm : Meshgrid for voxel volume in mm space +% xcoords, ycoords, zcoords : mm coordinates for rows, cols, slices +% +% Tor Wager, Feb 2011 + +bottomleft_mm = V.mat(1:3, 4); +topright_mm = voxel2mm([V.dim(1:3)]', V.mat); + +xcoords = linspace(bottomleft_mm(1), topright_mm(1), V.dim(1)); +ycoords = linspace(bottomleft_mm(2), topright_mm(2), V.dim(2)); +zcoords = linspace(bottomleft_mm(3), topright_mm(3), V.dim(3)); + +[Xmm, Ymm, Zmm] = meshgrid(ycoords, xcoords, zcoords); + + + +SPACE = struct('V', V, 'Xmm', Xmm, 'Ymm', Ymm, 'Zmm', Zmm, ... + 'xcoords', xcoords, 'ycoords', ycoords, 'zcoords', zcoords); + + +end + diff --git a/fmridisplay/removepoints.m b/fmridisplay/removepoints.m new file mode 100644 index 00000000..5b344769 --- /dev/null +++ b/fmridisplay/removepoints.m @@ -0,0 +1,21 @@ +function obj = removepoints(obj) + + +for i = 1:length(obj.montage) + + if isfield(obj.montage{i}, 'plotted_point_handles') + + wh = ishandle(obj.montage{i}.plotted_point_handles); + + if any(wh) + delete(obj.montage{i}.plotted_point_handles(wh)) + end + + end + +end + + + +end + diff --git a/fmridisplay/render_blobs.m b/fmridisplay/render_blobs.m new file mode 100644 index 00000000..253871df --- /dev/null +++ b/fmridisplay/render_blobs.m @@ -0,0 +1,474 @@ +function [blobhan, cmaprange, mincolor, maxcolor] = render_blobs(currentmap, mymontage, SPACE, varargin) +% [blobhan, cmaprange, mincolor, maxcolor] = render_blobs(currentmap, mymontage, SPACE, varargin) +% +% Renders blobs on brain slces with addblobs.m +% +% See addblobs.m method in fmridisplay for technical details. +% +% currentmap : see addblobs method +% mymontage : ditto +% SPACE: space of map to sample to (object display SPACE) +% +% Options: +% 'smooth', dosmooth = 1; +% +% {'maxcolor', 'color'}, color = varargin{i + 1}; % color for single-color solid or value-mapped +% 'mincolor', mincolor = varargin{i + 1}; % minimum color for value-mapped colors +% +% 'onecolor', docolormap = 0; % solid-color blobs +% 'splitcolor' +% docolormap = 1; dosplitcolor = 1; +% splitcolors = varargin{i + 1}; +% if ~iscell(splitcolors) || length(splitcolors) ~= 4 +% error('Enter splitcolor followed by a 4-element cell vector of 4 colors\n{Min_neg} {Max_neg} {Min_pos} {Max_pos}'); +% end +% color = splitcolors{4}; % max pos +% mincolor = splitcolors{3}; % min pos +% minnegcolor = splitcolors{1}; % min neg +% maxnegcolor = splitcolors{2}; % max neg +% +% % do not use other entries for colors +% % varargin{strcmp(varargin, 'color')} = deal(0); +% % varargin{strcmp(varargin, 'maxcolor')} = deal(0); +% % varargin{strcmp(varargin, 'mincolor')} = deal(0); +% +% 'cmaprange', cmaprange = varargin{i + 1}; % enter specific values mapped to min and max colors +% {'trans', 'transparent','constanttrans', [val], 'transvalue', [val]}, dotrans = 1; % transparent blobs +% (default: map transparency to cmaprange, unless you +% enter contanttrans followed by a transparency value +% +% % contour options +% 'contour', docontour = 1; +% 'outline', docontour = 1; outline = 1; +% 'linewidth', mylinewidth = varargin{i + 1}; +% +% % orientation options +% 'sagittal', myview = 'sagittal'; %disp('Warning! NOT implemented correctly yet!!!'), %pause(5) +% 'coronal', myview = 'coronal'; %disp('Warning! NOT implemented correctly yet!!!'), pause(5) +% 'axial', myview = 'axial'; + + +myview = mymontage.orientation; + +dosmooth = 0; +color = [1 0 0]; + +% contour and outline options +docontour = 0; +dotrans = 0; +transvalue = []; % default: map with clim +contourmin = 100*eps; +outline = 0; +mylinewidth = 2; + +% color-mapped blobs options +docolormap = 1; +mincolor = [0 0 1]; +maxcolor = [1 1 0]; +dosplitcolor = 0; +%cmaprange = [min(currentmap.mapdata(:)) max(currentmap.mapdata(:))]; +mapd = currentmap.mapdata(:); mapd = mapd(mapd ~= 0 & ~isnan(mapd)); +cmaprange = double([prctile(mapd, 10) prctile(mapd, 90)]); + +% adjust defaults +if any(strcmp(varargin, 'splitcolor')) && ~any(strcmp(varargin, 'cmaprange')) + cmaprange = double([prctile(mapd(mapd < 0), 25) prctile(mapd(mapd < 0), 75) prctile(mapd(mapd > 0), 25) prctile(mapd(mapd > 0), 75) ]); +end + +for i = 1:length(varargin) + if ischar(varargin{i}) + switch varargin{i} + case 'smooth', dosmooth = 1; + + case 'color', docolormap = 0; color = varargin{i + 1}; % single color, turn off color mapping + + case {'maxcolor'}, color = varargin{i + 1}; % color for single-color solid or value-mapped + case 'mincolor', mincolor = varargin{i + 1}; % minimum color for value-mapped colors + + case 'onecolor', docolormap = 0; % solid-color blobs + case 'splitcolor' + docolormap = 1; dosplitcolor = 1; + splitcolors = varargin{i + 1}; + if ~iscell(splitcolors) || length(splitcolors) ~= 4 + error('Enter splitcolor followed by a 4-element cell vector of 4 colors\n{Min_neg} {Max_neg} {Min_pos} {Max_pos}'); + end + color = splitcolors{4}; % max pos + mincolor = splitcolors{3}; % min pos + minnegcolor = splitcolors{1}; % min neg + maxnegcolor = splitcolors{2}; % max neg + + + % do not use other entries for colors + % varargin{strcmp(varargin, 'color')} = deal(0); + % varargin{strcmp(varargin, 'maxcolor')} = deal(0); + % varargin{strcmp(varargin, 'mincolor')} = deal(0); + + case 'cmaprange', cmaprange = double(varargin{i + 1}); % enter specific values mapped to min and max colors + case {'trans', 'transparent'}, dotrans = 1; % transparent blobs + + case {'constanttrans', 'transvalue'} + dotrans = 1; transvalue = double(varargin{i + 1}); + % (default: map transparency to cmaprange, unless you + % enter contanttrans followed by a transparency value + + % contour options + case 'contour', docontour = 1; + case 'outline', docontour = 1; outline = 1; + case 'linewidth', mylinewidth = varargin{i + 1}; + + % orientation options + case 'sagittal', myview = 'sagittal'; %disp('Warning! NOT implemented correctly yet!!!'), %pause(5) + case 'coronal', myview = 'coronal'; %disp('Warning! NOT implemented correctly yet!!!'), pause(5) + case 'axial', myview = 'axial'; + + case 'wh_montages' + % not functional, avoid warning + + otherwise, warning(['Unknown input string option:' varargin{i}]); + end + end +end + + + +blobhan = []; + +isvalid = ~isempty(mymontage) && isfield(mymontage, 'axis_handles') && all(ishandle(mymontage.axis_handles)); + +if ~isvalid, return, end + + +handles = mymontage.axis_handles; +n = length(handles); + +% for each slice... + +% Summarize voxels shown and not shown + +% find closest slice to each one in original 'blob' image to map +for j = 1:n + + switch myview % may be same as mymontage.orientation... + case 'axial' + + my_slice_coord = mymontage.slice_mm_coords(j); + + [dummy, wh_slice(j)] = min(abs(currentmap.SPACE.zcoords - my_slice_coord)); + + % Wani: The previous line gets the closest slice from the montages + % you're displaying by calculating the distance between them. + % However, if the distance measure is larger than a half of the + % voxel size (i.e., vox_size/2), it means there is no proper montage + % that can display the data. So I needed to add the following + % line for each view. Actually, I tried to use V.mat(:,3) for + % the axial view (because the voxel sizes for x,y,z could be + % different), but for some reasons, it doesn't work well. So + % I'm using V.mat(:,1) for all views (and take absolute values). + % I thought this could be a possible bug in the future, so I made this comment. 9/9/12 + + % if dummy > max(abs(currentmap.V.mat(:,1)))/2, wh_slice(j) = 0; end % Wani added this line 8/11/12, then see the next line. + if (dummy - (range(currentmap.SPACE.zcoords)/(length(currentmap.SPACE.zcoords)-1))/2) > .01, wh_slice(j) = 0; end % wani modified this line to fix a weird bug 3/5/13 + numvox = cat(1, squeeze(sum(sum(abs(currentmap.mapdata) > 0)))); + + case 'sagittal' + my_slice_coord = mymontage.slice_mm_coords(j); + + [dummy, wh_slice(j)] = min(abs(currentmap.SPACE.xcoords - my_slice_coord)); + + if (dummy - (range(currentmap.SPACE.xcoords)/(length(currentmap.SPACE.xcoords)-1))/2) > .01, wh_slice(j) = 0; end % Wani modified this line 3/5/13 + + numvox = cat(1, squeeze(sum(sum(abs(currentmap.mapdata) > 0, 2), 3))); + + case 'coronal' + my_slice_coord = mymontage.slice_mm_coords(j); + + [dummy, wh_slice(j)] = min(abs(currentmap.SPACE.ycoords - my_slice_coord)); + + if (dummy - (range(currentmap.SPACE.ycoords)/(length(currentmap.SPACE.ycoords)-1))/2) > .01, wh_slice(j) = 0; end % Wani modified this line 3/5/13 + + numvox = cat(1, squeeze(sum(sum(abs(currentmap.mapdata) > 0, 1), 3))); + + end +end + +% Wani decommented this again because the previous part already fixed the +% problem. 9/9/12 +% k = []; % added and modified by Wani 8/11/12 (from here) +% for i = 1:length(wh_slice)-1 +% if wh_slice(i) == wh_slice(i+1) +% k(end+1) = i+1; +% end +% end +% wh_slice(k) = 0; + +k = unique(wh_slice); k(k==0) = []; +voxshown = sum(numvox(k)); %voxshown = sum(numvox(unique(wh_slice))); % (to here) + +fprintf('%s montage: %3.0f voxels displayed, %3.0f not displayed on these slices\n', myview, voxshown, sum(numvox) - voxshown); + +% SETUP smoothing, contours +% ------------------------------------------------------- +if dosmooth + % for smoothing + PSF = fspecial('gaussian',10, 4); + contourmin = .2; +end + +if ~docontour + % surface + sz = size(SPACE.Z); % dims + + switch myview + case 'axial' + sz = sz([1 2]); % contour slice size in UPSAMPLED space + case 'sagittal' + sz = sz([2 3]); + case 'coronal' + sz = sz([1 3]); % contour slice size + end + + cdat = define_cdat(sz, color); + + if docolormap % colors are linear mixture of color and mincolor + + cdat2 = define_cdat(sz, mincolor); + + if dosplitcolor + % cdat and cdat2 are for positive values + % cdatminneg and maxneg are for negative values + cdatminneg = define_cdat(sz, minnegcolor); + cdatmaxneg = define_cdat(sz, maxnegcolor); + end + + end + +end % end if docontour + +% ----------------------------------------------------------- +% Loop through slices to plot +% ----------------------------------------------------------- + +for j = 1:length(wh_slice) % for j = 1:n - modified by Wani 7/28/12 + if wh_slice(j) ~= 0 % Wani added this if and end 8/11/12 + switch myview + case 'axial' + slicedat = currentmap.mapdata(:, :, wh_slice(j)); + case 'sagittal' + slicedat = squeeze(currentmap.mapdata(wh_slice(j), :, :)); + case 'coronal' + slicedat = squeeze(currentmap.mapdata(:, wh_slice(j), :)); + end + + if any(slicedat(:)) + + if docontour + % slow, but do for contours; otherwise render directly + axes(mymontage.axis_handles(j)); + end + + % Z is upsampled slice data; any orientation, not just axial + switch myview + case 'axial' % X x Y + myx = currentmap.SPACE.Xmm(:, :, wh_slice(j)); + myy = currentmap.SPACE.Ymm(:, :, wh_slice(j)); + mynewx = SPACE.Xmm(:, :, 1); + mynewy = SPACE.Ymm(:, :, 1); + Z = interp2(myx, myy, slicedat, mynewx, mynewy); + + case 'sagittal' % Y x Z; Xmm is for some reason Y mm coords + % myx should have all rows the same, myy should have all + % cols the same, always always for interp2 + myy = squeeze(currentmap.SPACE.Xmm(wh_slice(j), :, :)); + myx = squeeze(currentmap.SPACE.Zmm(wh_slice(j), :, :)); + mynewy = squeeze(SPACE.Xmm(wh_slice(j), :, :)); + mynewx = squeeze(SPACE.Zmm(wh_slice(j), :, :)); + Z = interp2(myx, myy, slicedat, mynewx, mynewy); + + case 'coronal' % X x Z + myy = squeeze(currentmap.SPACE.Ymm(:, wh_slice(j), :)); + myx = squeeze(currentmap.SPACE.Zmm(:, wh_slice(j), :)); + mynewy = squeeze(SPACE.Ymm(:, wh_slice(j), :)); + mynewx = squeeze(SPACE.Zmm(:, wh_slice(j), :)); + Z = interp2(myx, myy, slicedat, mynewx, mynewy); % Wani modified this line. 08/11/12 + + end + + if dosmooth + % SMOOTH + Z = imfilter(Z, PSF, 'replicate', 'conv'); + end + + %if j == 8, keyboard, end + + if docontour + % contour outline or plot + % ----------------------------------------------------------- + + [c, h] = contourf(mynewy, mynewx, abs(Z), [contourmin, contourmin]); + + ch = get(h, 'Children'); + + whiteh = findobj(ch, 'FaceColor', [1 1 1]); + %set(whiteh, 'FaceAlpha', 0); + set(whiteh, 'FaceColor', [.8 .8 .8]); + + % white for bg (center-fills), 'flat' for colored + colorh = findobj(ch, 'FaceColor', 'flat'); + set(colorh, 'FaceColor', color); %, 'FaceAlpha', .8); + + if dotrans + if ~isempty(transvalue) + set(colorh, 'FaceAlpha', transvalue); + else + set(colorh, 'FaceAlpha', .7); % backward compatible... + end + end + + if outline + set(colorh, 'FaceAlpha', 0, 'LineWidth', mylinewidth, 'EdgeColor', color); + end + + else + % surface map method + % ----------------------------------------------------------- + + if ~docolormap + % single-color map + slicecdat = cdat .* repmat(double(abs(Z) > 0), [1 1 3]); + + elseif ~dosplitcolor + % color-mapped + Zscaled = Z; + Zscaled(Zscaled ~= 0 & Zscaled > max(cmaprange)) = max(cmaprange); + Zscaled(Zscaled ~= 0 & Zscaled < min(cmaprange)) = min(cmaprange); + Zscaled = (Zscaled - min(cmaprange)) ./ (max(cmaprange) - min(cmaprange)); + + w = repmat(Zscaled, [1 1 3]); + + slicecdat = (w .* cdat) + (1 - w) .* cdat2; + + elseif dosplitcolor + % split colormap around zero + + if max(cmaprange) < 0, [dummy, wh] = max(cmaprange); cmaprange(wh) = abs(min(cmaprange)); end + if min(cmaprange) > 0, [dummy, wh] = min(cmaprange); cmaprange(wh) = -(max(cmaprange)); end + + % make into 4-element: min neg, max neg, min pos, max pos + if length(cmaprange) == 2 + cmaprange = [min(cmaprange) 0 0 max(cmaprange)]; % just like before = all the way to 0 + end + + Zscaled = double(Z); % cast as double to avoid weird bug + + % Zscaled must be transformed to weights from -1 to 1 + + % Zscaled(Zscaled > 0 & Zscaled > max(cmaprange)) = max(cmaprange); + % Zscaled(Zscaled < 0 & Zscaled < min(cmaprange)) = min(cmaprange); + + %Zscaled = Zscaled ./ max(abs(cmaprange)); % keep scale equal + %Zscaled(Zscaled > 0) = Zscaled(Zscaled > 0) ./ max(cmaprange); + %Zscaled(Zscaled < 0) = Zscaled(Zscaled < 0) ./ abs(min(cmaprange)); + + % linear scaling into pos and neg range, respectively + % allows for 4-element threshold input; e.g., [-6 -3 3 6] to display between 3 and 6 + + Zscaled(Zscaled > 0 & Zscaled < cmaprange(3)) = cmaprange(3) + 100*eps; + Zscaled(Zscaled < 0 & Zscaled > cmaprange(2)) = cmaprange(2) - 100*eps; + + Zscaled(Zscaled > 0) = (Zscaled(Zscaled > 0) - cmaprange(3)) ./ (cmaprange(4) - cmaprange(3)); + Zscaled(Zscaled < 0) = (Zscaled(Zscaled < 0) - cmaprange(2)) ./ abs(cmaprange(1) - cmaprange(2)); + + + + % pos only + w = repmat(Zscaled, [1 1 3]); + + slicecdat = (w .* cdat) + (1 - w) .* cdat2; + + to_keep = double(repmat(Zscaled > 0, [1 1 3])); + slicecdat = slicecdat .* to_keep; + + % now do neg part, then add them + %w = repmat(Zscaled, [1 1 3]); + w = abs(w); % now values we care about are neg; care about magnitude + slicecdat2 = (w .* cdatminneg) + (1 - w) .* cdatmaxneg; + + to_keep = double(repmat(Zscaled < 0, [1 1 3])); + slicecdat2 = slicecdat2 .* to_keep; + + slicecdat = slicecdat + slicecdat2; + end + + % switch myview + % case 'axial' + % h = surf(mymontage.axis_handles(j), SPACE.Ymm(:, :, wh_slice(j)), SPACE.Xmm(:, :, wh_slice(j)), Z, 'FaceColor', 'interp', 'edgecolor', 'none'); + % + %case 'sagittal' + + if ~isa(slicecdat, 'double') + keyboard + end + + % h = surf(mymontage.axis_handles(j), mynewy, mynewx, Z, 'FaceColor', 'interp', 'edgecolor', 'none', 'FaceAlpha', 'interp'); + % Wani: -ones(size(Z)) is helpful for boundaries for some reasons. + h = surf(mymontage.axis_handles(j), mynewy, mynewx, -ones(size(Z)), 'FaceColor', 'interp', 'edgecolor', 'none', 'FaceAlpha', 'interp'); + + %case 'coronal' + + %end + + if dotrans + + if ~docolormap, Zscaled = abs(Z); end + Zscaled(isnan(Zscaled)) = 0; + Zscaled = abs(Zscaled); + + if ~isempty(transvalue) + % constant transparency value + set(h, 'AlphaDataMapping', 'scaled', 'AlphaData', transvalue .* double(abs(Z) > 0), 'FaceAlpha', 'interp') + + else + % map transparency with colormap + set(h, 'AlphaDataMapping', 'scaled', 'AlphaData', Zscaled, 'FaceAlpha', 'interp') + end + + else + set(h, 'AlphaDataMapping', 'scaled', 'AlphaData', double(abs(Z) > 0), 'FaceAlpha', 'interp') + + end + + set(h, 'CData', slicecdat) + + end % do contour + + blobhan{j} = h; + + end + end +end + +if ~isempty(blobhan) + blobhan = cat(1, blobhan{:}); +end + +% for legend +if dosplitcolor + mincolor = [mincolor; minnegcolor]; + maxcolor = [color; maxnegcolor]; +end + + +end % main function + + + + +function cdat = define_cdat(sz, color) +% needs a size and a 3-element color vector + +cdat = ones([sz 3]); +for ii = 1:3 + cdat(:, :, ii) = color(ii) .* cdat(:, :, ii); +end + +end \ No newline at end of file diff --git a/fmridisplay/resample_space.m b/fmridisplay/resample_space.m new file mode 100644 index 00000000..56b5dccf --- /dev/null +++ b/fmridisplay/resample_space.m @@ -0,0 +1,41 @@ +function [resampled_dat, SPACEto] = resample_space(dat, V, targetsp) +% [resampled_dat, SPACEto] = resample_space(dat, V, [target V or target SPACE]) +% +% Inputs: +% ------------------------------------------------------------------------- +% dat : 3-D volume data +% V : spm-style .mat structure for dat, e.g., from spm_vol +% V.mat : 4 x 4 matrix of voxel sizes and mm coords for the bottom +% back left vox +% V.dim : dimensions of image +% +% targetsp +% target V : spm-style .mat structure defining space to transform to +% - OR - +% target SPACE: target SPACE, with Xmm, Ymm, Zmm; see map_to_world_space.m +% +% Outputs: +% ------------------------------------------------------------------------- +% resampled_dat : data sampled in new space +% +% SPACE structure, with fields: +% Xmm, Ymm, Zmm : Meshgrid for voxel volume in mm space +% xcoords, ycoords, zcoords : mm coordinates for rows, cols, slices +% V : Vol info structure for image in new space + +SPACE = map_to_world_space(V); + +if isfield(targetsp, 'Xmm') && isfield(targetsp, 'Ymm') && isfield(targetsp, 'Zmm') + SPACEto = targetsp; + +elseif isfield(targetsp, 'mat') && isfield(targetsp, 'dim') + SPACEto = map_to_world_space(Vto); + +else + error('targetsp is not valid; use spm_vol.m or map_to_world_space.m to define this.'); +end + +resampled_dat = interp3(SPACE.Xmm, SPACE.Ymm, SPACE.Zmm, dat, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm); + +end + diff --git a/peak_coordinates/cluster_manova_within.m b/peak_coordinates/cluster_manova_within.m new file mode 100755 index 00000000..95cce587 --- /dev/null +++ b/peak_coordinates/cluster_manova_within.m @@ -0,0 +1,206 @@ +function cluster_manova(clusters,fnames,varargin) +% function cluster_manova(clusters,fnames,verbose) +% tor wager +% +% clusters is output of clusters2database, with all fields +% from database +% fnames is cell array of strings with names to test +% e.g., {'Rule' 'Task'} +% +% uses stats toolbox +% example: cluster_manova(clusters,fnames) +% +% verbose: optional, produces more output and tests + +if length(varargin) > 0, verb = 1; else, verb = 0;, end + +mycol = {'bo' 'ys' 'g^' 'cv' 'rd' 'ys'}; +warning off % avoid openGL warnings on my machine +fprintf(1,'\nNote: df will not equal sums of individual class memberships if classes are fuzzy; \nMANOVA run separately on each class') +fprintf(1,'\n-------------------------------------------------\n* Cluster_Manova.m :: Table of Clusters \n-------------------------------------------------\n') +clusters = cluster_table(clusters); + +con = [1 -1]; + + +for i = 1:length(clusters) + + %x = [clusters(i).x clusters(i).y clusters(i).z]; + fprintf(1,'\n-------------------------------------------------\n* Cluster %3.0f :: %s\n-------------------------------------------------\n',i,clusters(i).BAstring) + + im = {};, clear allcent + for j = 1:length(fnames) + % get centers and distances + eval(['im{j} = clusters(i).' fnames{j} ';']) + cent = mean(im{j}); + allcent(j,:) = cent; + if isempty(cent), cent = [NaN NaN NaN];,end + end + + % differences + for j = 1:length(con), im{j} = im{j} .* con(j);,end + x = cat(3,im{:}); x = sum(x,3); % differences + + % distances between group centers + fprintf(1,'\nDistances between class centers\n') + for j = 1:length(fnames),fprintf(1,'%s\t',fnames{j}),end,fprintf(1,'\n') + mydist = squareform(pdist(allcent,'euclid')); + + + %fprintf(1,'%s\t%3.0f\t%3.0f\t%3.2f\t%3.4f\t%3.0f%\t',fnames{j},stats.dfB,stats.dfW,stats.lambda,p,round(100*missrate)); + fprintf(1,'Name\tnum_peaks\tdfB\tdfW\tWilk''s\tp\tmissclass. rate\t\n') + + + for j = 1:length(fnames) + + d = 0; + + eval(['group = clusters(i).' fnames{j} ';']) + if max(group) == 1 | any(group==0), group = group+1;,end + + % run manova if there are enough observations + % print output line + nvox = eval(['sum(clusters(i).' fnames{j} ');']); + [d,stats] = manova(x,group,fnames{j},nvox); + + % print centers and distances + + fprintf(1,'%s\t%3.2f\t%3.2f\t%3.2f\t',fnames{j},allcent(j,1),allcent(j,2),allcent(j,3)) + fprintf(1,[repmat('%3.2f\t',1,size(allcent,1)) '\n'],mydist(j,:)'); + + + if d > 0 & verb + % stuff to print if it's significant + + figure('Color','w'); hold on; + %fprintf(1,'\n%s Dimension Weights: %3.2f\t%3.2f\t%3.2f\t', ... + % fnames{j},stats.eigenvec(1,1),stats.eigenvec(2,1),stats.eigenvec(3,1)) + + fprintf(1,'\n%s Group Centers',fnames{j}) + fprintf(1,'\nGroup\tx\ty\tz\t') + for k = 1:max(group), + tmp = mean(x(group==k,:)); + fprintf(1,'\n%3.0f\t%3.0f\t%3.0f\t%3.0f\t',k,tmp(1),tmp(2),tmp(3)) + plot3(x(group==k,1),x(group==k,2),x(group==k,3),mycol{k}) + end + addbrain + camzoom(.7) + title(['Cluster ' num2str(i) ' ' fnames{j} ': blue o = no, yellow square = yes']) + set(gca,'FontSize',18) + + end + + if d > 0 % confidence volume stuff + + if sum(group==2) > 12, + results = confidence_volume(x(group==2,:),mycol{j}(1)); + end + + hold on; + plot3(x(group==2,1),x(group==2,2),x(group==2,3),mycol{j},'LineWidth',2); + + end + + end + + if verb + % add ALL cluster centers and pairwise manovas + all_pairwise(clusters(i),fnames) + end + + fprintf(1,'\n') + +end + +warning on + +return + + + +function [missrate,c] = get_missclassrate(group,class) + +% misclassification rate with confusion matrix +for i = 1:max(group) + for j = 1:max(class) + c(i,j) = sum(group==i & class==j); + end +end +missrate = 1 - trace(c) ./ sum(c(:)); + +return + + + + +function all_pairwise(clusters,fnames) + +xyz = [clusters.x clusters.y clusters.z]; + +fprintf(1,'\n\nCenters for each class\n') + +for i = 1:length(fnames), + eval(['im(:,i) = clusters.' fnames{i} ';']) + cent = mean(xyz(find(im(:,i)),:),1); + + if isempty(cent), fprintf(1,'No peaks of type %s in cluster\n', fnames{i}),cent = [NaN NaN NaN]; + else, fprintf(1,'%s\t%3.2f\t%3.2f\t%3.2f\t\n',fnames{i},cent(1),cent(2),cent(3)) + end + allcent(i,:) = cent; + +end + +% distances between group centers +fprintf(1,'\nDistances between class centers\n') +for i = 1:length(fnames),fprintf(1,'%s\t',fnames{i}),end,fprintf(1,'\n') +mydist = squareform(pdist(allcent,'euclid')); +fprintf(1,[repmat('%3.2f\t',1,size(allcent,1)) '\n'],mydist'); + +% now do pairwise manovas +fprintf(1,'\nPairwise MANOVAs\n') +fprintf(1,'\nName\tdfB\tdfW\tWilk''s\tp\tmissclass. rate\t\n') + +for i = 1:size(im,2) + for j = (i+1):size(im,2) + + % build group identities, eliminate points in both groups + group = im(:,i); group(find(im(:,j))) = 2; group(im(:,i) & im(:,j)) = 0; + xyztmp = xyz; xyztmp(group == 0,:) = []; group(group == 0) = []; + + [d,stats] = manova(xyztmp,group,[fnames{i} ' vs. ' fnames{j}]); + + end +end + + +return + + + + +function [d,stats,missrate] = manova(x,group,myname,nvox) + +d = 0; + + for k = 1:max(group), numg(k) = sum(group==k);,end + + if any(numg < 3), fprintf(1,'%s\t%3.0f\t Too few in cell to classify\t\t\t\t\t\t\t\t',myname,nvox);, + elseif numg(1) == length(group), fprintf(1,'%s\t%3.0f\t No activations for one group in cluster\t\t\t\t\t\t\t\t',myname,nvox);, + + else + class = classify(x,x,group); + [d,p,stats] = manova1(x,group); + + %whos group, whos x, sum(group==1),sum(group==2), stats + + [missrate,c] = get_missclassrate(group,class); + + fprintf(1,'\n%s\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.4f\t%3.0f%%\t',myname,nvox,stats.dfB,stats.dfW,stats.lambda,p,round(100*missrate)); + fprintf(1,'%3.2f\t%3.2f\t%3.2f\t', ... + stats.eigenvec(1,1),stats.eigenvec(2,1),stats.eigenvec(3,1)) + end + + return + + + \ No newline at end of file diff --git a/peak_coordinates/extract_ind_peak.m b/peak_coordinates/extract_ind_peak.m new file mode 100755 index 00000000..4639a2c9 --- /dev/null +++ b/peak_coordinates/extract_ind_peak.m @@ -0,0 +1,86 @@ +function [cl] = extract_ind_peak(imnames,cl,varargin) +% function [clusters] = extract_ind_peak(imnames [can be empty],clusters,[vols]) +% +% this function gets individual spatial peaks from clusters. +% input: +% imnames: a matrix of image names, in spm_list_files output format +% if imnames is empty, enter full data (vols) as 3rd argument +% or else this program will use cl.all_data to get data +% clusters: see tor_extract_rois +% +% 11/2/03 by Tor Wager +% +% NOTE (WARNING): WORKS ON XYZ VOXEL COORDINATES - NO TRANSFORMATION TO DIFFERENT SPACES +% see transform_coordinates.m for this. +% +% Functions called +% C:\matlabR12\toolbox\matlab\datatypes\squeeze.m +% c:\tor_scripts\voistatutility\nanmean.m +% (calls other spm functions) +% +% see also cluster_manova + +verbose = 1; + + + % ---------------------------------------------------------------------------------- + % load image files, if possible + % ---------------------------------------------------------------------------------- + if ~isempty(imnames), + if size(imnames,1) < 60, + % Image loading a la SPM, and manual extraction. + V = spm_vol(imnames); + vols = spm_read_vols(V); + end + elseif length(varargin) > 0 + vols = varargin{1}; + end + + +for i = 1:length(cl) + + % ---------------------------------------------------------------------------------- + % get the data for the cluster + % ---------------------------------------------------------------------------------- + O.coords = cl(i).XYZ'; + mm = cl(i).XYZmm'; + all_data = []; + + if exist('vols') == 1 + for co = 1:size(O.coords,1) + all_data(:,co) = squeeze(vols(O.coords(co,1),O.coords(co,2),O.coords(co,3),:)); + end + else + if i == 1,disp('Using data from clusters.all_data'),end + all_data = cl(i).all_data; + end + + for subj = 1:size(all_data,1) + wh = find(abs(all_data(subj,:)) == max(abs(all_data(subj,:)))); + + % randomly select if multiple peaks + if length(wh) > 1 + warning('Multiple matches to max!') + tmp = randn(size(wh)); wh = wh(tmp==max(tmp)); + end + + if isempty(wh) + warning(['Cluster ' num2str(i) ': No individual peak data for subject ' num2str(subj) '?']) + cl(i).peakXYZ(subj,:) = [NaN NaN NaN]; + cl(i).peakXYZmm(subj,:) = [NaN NaN NaN]; + cl(i).peakdata(subj,1) = NaN; + else + cl(i).peakXYZ(subj,:) = O.coords(wh,:); + cl(i).peakXYZmm(subj,:) = mm(wh,:); + cl(i).peakdata(subj,1) = all_data(subj,wh); + end + + end + + end + + + + +return + diff --git a/peak_coordinates/image2coordinates.m b/peak_coordinates/image2coordinates.m new file mode 100644 index 00000000..ae6957f6 --- /dev/null +++ b/peak_coordinates/image2coordinates.m @@ -0,0 +1,62 @@ +function XYZmm = image2coordinates(img) +% Threshold a statistic image and turn it into a list of x, y, z +% coordinates (in mm "world space") with SPM +% +% Usage: +% XYZmm = image2coordinates(img) +% +% Example: +% img = 'h25_aerger.img'; +% XYZmm = image2coordinates(img) +% figure; plot3(XYZmm{1}(1, :)', XYZmm{1}(2, :)', XYZmm{1}(3,:)', 'ko'); +% addbrain +% axis image +% +% Example with multiple images: +% % list all images in dir +% imgs = filenames(fullfile(pwd, '*img'), 'char', 'absolute') +% XYZmm = image2coordinates(imgs); +% % save images names and coordinates +% % imgs2 has cell array of image names, without full paths +% imgs2 = filenames(fullfile(pwd, '*img')); +% save silke_senders_xyz_coordinates imgs imgs2 XYZmm + +dat = fmri_data(img); + +% arbitrary threshold of 1 or greater, based on histogram +%dat = threshold(dat, [1 Inf], 'raw-between'); + +% threshold is xth (e.g., 99th) percentile across all the images +thr = prctile(double(dat.dat(:)), 99); +dat = threshold(dat, [thr Inf], 'raw-between'); + +dat = replace_empty(dat); + +n = size(dat.dat, 2); % number of images + +XYZmm = cell(1, n); + +for i = 1:n + + % values in image(s) - need for spm_max + X = double(dat.dat(:, i)); + wh = X ~= 0; + + % remove empty voxels + X(X == 0) = []; + + % skip if empty (no voxels) + if isempty(X), continue, end + + % otherwise + xyz = dat.volInfo.xyzlist(wh, :)'; + + % core SPM function. M is maxima in voxel coords + [N Z M] = spm_max(X, xyz); + + % put into mm: + XYZmm{i} = voxel2mm(M, dat.volInfo.mat); + +end + +end % function \ No newline at end of file diff --git a/peak_coordinates/spatial_contrast.m b/peak_coordinates/spatial_contrast.m new file mode 100755 index 00000000..b38b365d --- /dev/null +++ b/peak_coordinates/spatial_contrast.m @@ -0,0 +1,71 @@ +function [str,dcon] = spatial_contrast(XYZ1,XYZ2) +% function [str,dcon] = spatial_contrast(XYZ1,XYZ2) +% +% this function tests relative locations of individual +% spatial peaks from clusters. +% +% plots position of XYZ2 relative to XYZ1 +% thus, an 'anterior' group position means that XYZ2 +% peaks are anterior to XYZ1 peaks +% +% input: +% XYZ1,2: n x 3 coordinates (in mm) +% con: contrast vector, e.g., [1 -1] +% +% 11/2/03 by Tor Wager +% +% Minor modification/documentation by Tor Wager, Aug 2010 +% Now correctly indicates that permutation test is used for p-values. +% See help conf_region for details of the test. + + +dcon = XYZ2 - XYZ1; +m = mean(dcon); +mmax = max(abs(max(dcon))) * 2; + +mm = mean([XYZ1;XYZ2]); +xx1 = XYZ1 - repmat(mm,size(XYZ1,1),1); +xx2 = XYZ2 - repmat(mm,size(XYZ2,1),1); + +figure('Color','w'); subplot(1,2,1);hold on; set(gca,'FontSize',16) +% points +plot3(xx1(:,1),xx1(:,2),xx1(:,3),'rs'); +plot3(xx2(:,1),xx2(:,2),xx2(:,3),'go'); + +hold on; plot3([-mmax mmax],[0 0],[0 0],'k','LineWidth',2); text(-mmax,0,-1,'Left');text(mmax,0,-1,'Right'); +hold on; plot3([0 0],[-mmax mmax],[0 0],'k','LineWidth',2); text(0,-mmax,-1,'Posterior');text(0,mmax,-1,'Anterior'); +hold on; plot3([0 0],[0 0],[-mmax mmax],'k','LineWidth',2); text(1,0,-mmax,'Inferior');text(1,0,mmax,'Superior'); +view(26,34) +xlabel('x (mm)');ylabel('y (mm)'),zlabel('z (mm)') +%camzoom(1.25); +axis vis3d + + +subplot(1,2,2); hold on; set(gca,'FontSize',16) + +% individual ones +for i = 1:size(dcon,1) + plot3(dcon(i,1),dcon(i,2),dcon(i,3),'b^','MarkerFaceColor','b','MarkerSize',2); + hh(i) = plot3([0 dcon(i,1)],[0 dcon(i,2)],[0 dcon(i,3)],'b'); +end + +% confidence sphere +results = confidence_volume(dcon,'r',1); +str=sprintf('%3.0f pts, [%3.1f %3.1f %3.1f], Mean Euclidean Distance from origin = %3.2f, p = %3.4f',size(dcon,1),m(1),m(2),m(3),results.msb, results.pval); +disp(str) + +% average vector +hold on; plot3(m(1),m(2),m(3),'b^','MarkerFaceColor','r','MarkerSize',8); +h = plot3([0 m(1)],[0 m(2)],[0 m(3)],'r','LineWidth',4); + +% axes +hold on; plot3([-mmax mmax],[0 0],[0 0],'k','LineWidth',2); text(-mmax,0,-1,'Left');text(mmax,0,-1,'Right'); +hold on; plot3([0 0],[-mmax mmax],[0 0],'k','LineWidth',2); text(0,-mmax,-1,'Posterior');text(0,mmax,-1,'Anterior'); +hold on; plot3([0 0],[0 0],[-mmax mmax],'k','LineWidth',2); text(1,0,-mmax,'Inferior');text(1,0,mmax,'Superior'); +view(26,34) +xlabel('x (mm)');ylabel('y (mm)'),zlabel('z (mm)') +%camzoom(1.25); +axis vis3d +title(str) + +return