diff --git a/Analysis/Build_Connectivity_Matrix.m b/Analysis/Build_Connectivity_Matrix.m new file mode 100755 index 0000000..50f03c3 --- /dev/null +++ b/Analysis/Build_Connectivity_Matrix.m @@ -0,0 +1,23 @@ +%% This function constructs a connectivity matrix from the indices of +% cluster assignment +function [M] = Build_Connectivity_Matrix(IDX,tmp_ss,type,n_items) + + IDX_full = zeros(n_items,1); + + switch type + case 'items' + IDX_full(tmp_ss) = IDX; + case 'dims' + IDX_full = IDX; + end + + M = zeros(n_items,n_items); + + for i = 1:length(IDX_full) + for j = 1:length(IDX_full) + if (IDX_full(i) == IDX_full(j)) && (IDX_full(i) > 0) + M(i,j) = 1; + end + end + end +end \ No newline at end of file diff --git a/Analysis/CAPToNIFTI.m b/Analysis/CAPToNIFTI.m new file mode 100755 index 0000000..51622ba --- /dev/null +++ b/Analysis/CAPToNIFTI.m @@ -0,0 +1,29 @@ +%% Converts CAPs (matlab matrix) into NIFTI files +% CAP must have size n_CAPs x n_voxels +function [] = CAPToNIFTI(CAP,mask,brain_info,savedir,savename) + + % Number of CAPs + n_CAPs = size(CAP,1); + + % Voxel size + voxel_size = diag(brain_info.mat); + voxel_size = voxel_size(1:end-1)'; + + voxel_shift = brain_info.mat(:,4); + voxel_shift = voxel_shift(1:end-1)'; + + % Converts each CAP into a 3D volume + for i = 1:n_CAPs + + tmp = CAP(i,:); + V = zeros(brain_info.dim); + V(mask) = tmp; + + tmp_NIFTI = make_nii(V,voxel_size,-voxel_shift./voxel_size); + + tmp_NIFTI.hdr.dime.datatype=64; + tmp_NIFTI.hdr.dime.bitpix=64; + + save_nii(tmp_NIFTI,fullfile(savedir,[savename,'_CAP',num2str(i),'.nii'])); + end +end \ No newline at end of file diff --git a/Analysis/CAP_AddToLog.m b/Analysis/CAP_AddToLog.m new file mode 100755 index 0000000..c2e3e2f --- /dev/null +++ b/Analysis/CAP_AddToLog.m @@ -0,0 +1,19 @@ +% Displays a message specifying what command is currently being run, and +% saves the same command and relevant information into a text file +function [Log] = CAP_AddToLog(Log,new_s,Params,Params_name) + + n = length(Log); + Log{n+1}{1} = [' ']; + %Log{n+1}{1} = [date,'/',num2str(round(hour(now))),'/',num2str(round(minute(now))),'/',num2str(round(second(now)))]; + Log{n+1}{2} = new_s; + + disp(new_s); + + % If we also want to save parameters, we do so + if nargin > 2 + for n2 = 1:length(Params) + Log{n+1}{n2+2} = [Params_name{n2},': ',num2str(Params{n2})]; + disp(Log{n+1}{n2+2}); + end + end +end \ No newline at end of file diff --git a/Analysis/CAP_AssignFrames.m b/Analysis/CAP_AssignFrames.m new file mode 100755 index 0000000..812f777 --- /dev/null +++ b/Analysis/CAP_AssignFrames.m @@ -0,0 +1,14 @@ +function [i] = CAP_AssignFrames(Cp,XON,d,T) + + % Value of correlation below which 5% of all values lie (so gives an + % estimate of a 'bad correlation for a trace belonging to a cluster' + CT = prctile(d,T); + + % cluster assignment in young + r = corr(Cp',XON); + [c,i] = max(r); + + % If the correlation value is too low (below threshold), the index is set + % to a new non-existing group + i(c... + 3 && sum(cell2mat(cellfun(@(x) size(x,1) == size(TC{1},1) && ... + size(x,2) == size(TC{1},2),TC,'un',0))) == size(TC,2) + + % If the dimensions of FD fit the dimensions of TC... + if size(FD,1) == size(TC{1},1) && size(FD,2) == size(TC,2) + + % If the dimensions of the mask fit the rest... + if size(mask,2) == 1 && sum(mask,1) == size(TC{1},2) + + % If the dimensions of the brain info are consistant... + if isfield(BI,'dim') && isfield(BI,'mat') && size(BI.mat,1) == 4 && size(BI.mat,2) == 4 && size(BI.dim,1) == 1 && size(BI.dim,2) == 3 + + % Then, everything is OK ! + IsOK = 1; + Problems = 'No problem !!'; + else + Problems = 'Inconsistant brain information dimensions'; + end + else + Problems = 'Inconsistant mask dimensions compared to time courses'; + end + else + Problems = 'Inconsistant dimensions between time courses and motion file'; + end + + else + Problems = 'Inconsistant time courses dimensions'; + end + + +end \ No newline at end of file diff --git a/Analysis/CAP_V2V.m b/Analysis/CAP_V2V.m new file mode 100755 index 0000000..5cabfac --- /dev/null +++ b/Analysis/CAP_V2V.m @@ -0,0 +1,41 @@ +function Out=CAP_V2V(In,In_dim,In_mat,Out_dim,Out_mat) +% map voxels in the space of input volume to voxels in the space +% of output volumes +% Out_fn: uses only header info +% +% v1.0 Jonas Richiardi +% - initial release, based on code by Dimitri Van De Ville and Jonas +% Richiardi + +% Out is filled with zeros and has the size of the output file +Out=zeros(Out_dim); + +% generate all coordinates in output space +[x1,x2,x3]=ndgrid(1:Out_dim(1),1:Out_dim(2),1:Out_dim(3)); +idx=1:numel(Out); % map all voxels + +% take every voxel in the volume spanned by the output images, +% compute its real-world position in mm, then map input image + +oobList=zeros(0,4); % list of out-of-bound input voxels + +for iter=1:length(idx), + oob=false; + % recover world-space position of this voxel in mm from affine + % transform matrix + mm=Out_mat*[x1(idx(iter)) x2(idx(iter)) x3(idx(iter)) 1]'; + % convert this position into index of the closest structural voxel + vx=round(In_mat\[mm(1) mm(2) mm(3) 1]'); + vx(vx<=0)=1; + vxOri=vx; + % remap out-of-bounds voxels to last + if vx(1)>In_dim(1), vx(1)=In_dim(1); oob=true; end + if vx(2)>In_dim(2), vx(2)=In_dim(2); oob=true; end + if vx(3)>In_dim(3), vx(3)=In_dim(3); oob=true; end + if (oob==true), oobList(end+1,:)=vxOri; end + % idx(iter): current voxel + Out(idx(iter))=In(vx(1),vx(2),vx(3)); + if any(Out(idx(iter))<0) %Out + warning('mapV2V:negativeVal',['Negative voxel values at ' num2str(iter)]); + end +end; \ No newline at end of file diff --git a/Analysis/CAP_find_activity.m b/Analysis/CAP_find_activity.m new file mode 100755 index 0000000..744299d --- /dev/null +++ b/Analysis/CAP_find_activity.m @@ -0,0 +1,156 @@ +%% Finds the moments of (de)activation in a group of fMRI subjects +% Inputs +% tcvox: cell aray with a seed signal in each cell (time points x masked voxels) +% seed: masks for the seeds used (masked voxels x n_seed) +% T: threshold for the retention of active or inactive frames +% FDall: framewise displacement traces for all subjects (time points x n_subjects) +% Mot_thresh: threshold (in mm) to use for scrubbing +% +% Outputs +% Xonp: cell array (each cell dimension masked voxels x n_retained_frames) +% for active frames brain patterns +% p: 5xn_subject matrix with percentages of scrubbed frames, scrubbed and +% active frames, scrubbed and inactive frames, retained frames for active +% analysis and retained frames for inactive analysis +function [Xonp,p,Indices,idx_sep_seeds] = CAP_find_activity(tcvox,seed,T,FDall,Mot_thresh,SelMode,SeedType,SignMat,is_SS) + + % Contains the indices (logical) of the selected frames for each seed + % subcase + idx_sep_seeds = nan(size(FDall,1),size(FDall,2),size(seed,2)); + + % Computes the indices of the peak values that must be excluded. If we + % go above threshold for motion, we censor the current frame, but also + % the one just before and the one just after + flag = FDall>Mot_thresh; + flag2 = circshift(flag,[1 0]); + flag3 = circshift(flag,[-1 0]); + flag = flag+flag2+flag3; + flag(flag>1)=1; + + % We want to store the indices of scrubbed frames and return it later + Indices.scrubbed = logical(flag); + + % Same for the retained frames + switch SeedType + case 'Intersection' + Indices.kept.active = logical(zeros(size(FDall,1),size(FDall,2))); + otherwise + Indices.kept.active = logical(ones(size(FDall,1),size(FDall,2))); + end + + + % Each cell of flag will contain a time x 1 vector of logicals (1 if + % the frame is to be censored, 0 otherwise) + flag = num2cell(flag,1); + clear flag2 + clear flag3 + + % 1 x n_subject vector containing the percentage of scrubbed frames (throughout the whole scan) + p_scrubbed = cell2mat(cellfun(@(x) sum(x)/length(x)*100, flag,'un',0)); + + % If we wanted an average seed, we can either be in the + % subject-specific seed case (we know there is only one seed type), or + % in the case with several different seeds (up to three) to average + % together + if strcmp(SeedType,'Average') + + % According to the two options, we compute the seed time courses + % for each subject. + if ~is_SS + S = CAP_get_seed_traces(tcvox,logical(sum(seed,2)),SignMat(1,:),is_SS); + else + S = CAP_get_seed_traces(tcvox,seed,SignMat(1,:),is_SS); + end + + % Selects the indices appropriately (threshold or percentage + % selection cases) + if strcmp(SelMode,'Threshold') + + xindp = cellfun(@(x) x>T, S, 'un', 0); + + elseif strcmp(SelMode,'Percentage') + + T_per_act = cellfun(@(x) prctile(x,100-T), S, 'un', 0); + xindp = cellfun(@(x,y) x>y, S,T_per_act, 'un', 0); + + else + errordlg('Should never happen...'); + end + + % flag now contains the traces with high activity AND high motion + flag_active = cellfun(@(x,y) x & y,xindp, flag,'un',0); + + % Vector (1xn_subj) with the percentage of traces removed because of too high + % motion and being selected as active + p_scrubactive = cell2mat(cellfun(@(x) sum(x)/length(x)*100, flag_active,'un',0)); + + % My indices of active/inactive frames now contain only the non + % corrupted frames + xindp = cellfun(@(x,y) x & ~y, xindp,flag_active,'un',0); + + Indices.kept.active = cell2mat(xindp); + idx_sep_seeds(:,:,1) = cell2mat(xindp); + + % If Union or Intersection was wanted instead, then computations for + % different seeds need to be carried independently + else + for idx = 1:size(seed,2) + + S = CAP_get_seed_traces(tcvox,logical(seed(:,idx)),SignMat(idx,:),is_SS); + + if strcmp(SelMode,'Threshold') + + % Computes the indexes at which we have a seed activity of interest + xindp = cellfun(@(x) x>T, S, 'un', 0); + + elseif strcmp(SelMode,'Percentage') + + % Computes the threshold that corresponds to P percent frames for + % each subject + T_per_act = cellfun(@(x) prctile(x,100-T), S, 'un', 0); + + % And then uses this to select frames + xindp = cellfun(@(x,y) x>y, S,T_per_act, 'un', 0); + + else + errordlg('Should never happen...'); + end + + % flag now contains the traces with high activity AND high motion + flag_active = cellfun(@(x,y) x & y,xindp, flag,'un',0); + + % Vector (1xn_subj) with the percentage of traces removed because of too high + % motion and being selected as active + p_scrubactive = cell2mat(cellfun(@(x) sum(x)/length(x)*100, flag_active,'un',0)); + + % My indices of active/inactive frames now contain only the non + % corrupted frames + xindp = cellfun(@(x,y) x & ~y, xindp,flag_active,'un',0); + + % Updates the indices of the frames to keep + switch SeedType + case 'Union' + + Indices.kept.active = (Indices.kept.active) & cell2mat(xindp); + + case 'Intersection' + + Indices.kept.active = (Indices.kept.active) | cell2mat(xindp); + end + + idx_sep_seeds(:,:,idx) = cell2mat(xindp); + + end + end + + % Each cell contains the frames selected as active or as inactive (if + % inactive, the sign is reversed, i.e. inactivation is a positive + % signal). Size: masked voxels x n_retained_frames + Xonp = cellfun(@(x,y) x(y,:)',tcvox,num2cell(Indices.kept.active,1),'un',0); + + % Percentage of active and inactive frames retained per subject + p_active = cell2mat(cellfun(@(x) size(x,2)/size(FDall,1)*100, Xonp,'un',0)); + + % Matrix containing all the interesting probabilities + p = [p_scrubbed; p_scrubactive; p_active]; +end \ No newline at end of file diff --git a/Analysis/CAP_get_seed_traces.m b/Analysis/CAP_get_seed_traces.m new file mode 100755 index 0000000..62ddd93 --- /dev/null +++ b/Analysis/CAP_get_seed_traces.m @@ -0,0 +1,40 @@ +%% From the raw voxelwise data, computes a spatially averaged seed signal +% tcvox is a cell array (each cell has time x masked voxels dimension) +% seed is the seed mask (masked voxel x 1) +% S is a cell array (each cell is a time x 1 vector) +function [S] = CAP_get_seed_traces(tcvox,seed,SignMat,is_SS) + + % In the case when we have the same seed across subjects, we use it + % throughout for the computations + if ~is_SS + if SignMat(1) + % Computation of the spatially averaged seed signal + S = cellfun(@(x) mean(x(:,seed),2), tcvox, 'un', 0); + elseif SignMat(2) + % Computation of the spatially averaged seed signal + S = cellfun(@(x) (-1)*mean(x(:,seed),2), tcvox, 'un', 0); + else + errordlg('PROBLEM WITH SIGN MAT !!!'); + end + + % In the case when we have subject-specific seeds, we will compute the + % seed signal separately for each subject + else + + seed_cell = {}; + + for s = 1:size(seed,2) + seed_cell{s} = seed(:,s); + end + + if SignMat(1) + % Computation of the spatially averaged seed signal + S = cellfun(@(x,y) mean(x(:,y),2), tcvox, seed_cell, 'un', 0); + elseif SignMat(2) + % Computation of the spatially averaged seed signal + S = cellfun(@(x,y) (-1)*mean(x(:,y),2), tcvox, seed_cell, 'un', 0); + else + errordlg('PROBLEM WITH SIGN MAT !!!'); + end + end +end \ No newline at end of file diff --git a/Analysis/CAP_mask4kmeans.m b/Analysis/CAP_mask4kmeans.m new file mode 100755 index 0000000..5e9b035 --- /dev/null +++ b/Analysis/CAP_mask4kmeans.m @@ -0,0 +1,53 @@ +%% This function sets to zero the value of the voxels that are judged noise +% X is the data to mask (n_vox x n_frames) +% topP and bottomP are the percentages of data retained (top and lowest +% activity ones respectively) by the masking procedure +% n_cv is the number of voxels in a group that must be reached for that +% group not to be masked +function [Y] = CAP_mask4kmeans(X,topP,bottomP,n_cv,mask,ai) + + % We want to exit with a matrix of the same size as X, for which each + % frame has had its noise-related elements set to zero + Y = zeros(size(X)); + + % For all frames... + for i = 1:size(X,2) + % We sort in descending and ascending order to get the indexes of + % the considered frame matching the top percentage of high or low + % activity + [~,Isorted] = sort(X(:,i),'descend'); + [~,Isortedrev] = sort(X(:,i),'ascend'); + + % Contains the indexes of all the points with activity of interest, + % both high and low + I = [Isorted(1:round(topP/100*length(Isorted)))',Isortedrev(1:round(bottomP/100*length(Isortedrev)))']; + + % If the elements of the frame belong to the indexes, X_binary has + % the corresponding entry set to 1. Else, it is set to 0 + X_binary = ismember(1:length(X(:,i)),I); + + % To perform the opening operation, we must convert X_binary to a + % 3D volume + temp = nan(size(mask)); + temp(mask) = X_binary; + temp(isnan(temp)) = 0; + temp = reshape(temp,ai.dim); + + % At this state, temp is a binary image on which the small clusters + % of neigboring voxels retained (less than n_cv neighbours) have + % been removed + temp = bwareaopen(temp,n_cv); + + % We convert temp back to a 1D vector, and conserve only the + % elements of interest (70700 voxels) with 0 and 1 respectively + % denoting 'not to consider for k-means' and 'to consider for + % k-means' + temp = temp(:); + Y(:,i) = temp(mask); + end + + % Y is a binary matrix of same size as X; multiplying element by + % element, we set to zero the elements of X that we want to neglect for + % clustering + Y = Y.*X; +end \ No newline at end of file diff --git a/Analysis/Compute_Metrics.m b/Analysis/Compute_Metrics.m new file mode 100755 index 0000000..fe93dc0 --- /dev/null +++ b/Analysis/Compute_Metrics.m @@ -0,0 +1,120 @@ +%% This function computes all the metrics of the CAP analysis framework +% Inputs: +% +% - idx depicts the indices of the frames that have been clustered (the +% cluster to which they belong) +% - xindp1 depicts the indices of the frames that have been selected as +% activation moments +% - xindn1 is the same for deactivation time points +% - sind depicts scrubbed frames +% - n_clusters is the number of clusters used for state disentanglement +% - TR is the TR of the experiment +% - CAPType denotes the type of clustering that has been chosen (only +% activation, only deactivation, or both) +% +% Outputs: +% +% - TPM (n_subjects x n_frames) is the state sequence matrix +% - Counts (-> raw/frac -> scrubbed/baseline/state: n_subj x n_states) +% contains the counts (raw and normalized) +% - Number (n_subj x n_seqtype) with sequence type: scrub, act/deact/-, +% baseline, then states +% - Avg_Duration (n_subj x n_seqtype) contains the average duration of a +% state for a subject +% - Duration (n_subj array of size 1 x n_sequences) contains the duration +% of the state sequences for each subject +% - TM (n_seqtype x n_seqtype) encompasses the transitions that exist +function [TPM,Counts,Number,Avg_Duration,Duration,TM] = Compute_Metrics(idx,xindp1,sind,n_clusters,TR) + + % Number of subjects + n = size(xindp1,2); + + % Number of frames + n_frames = size(xindp1,1); + + % This will contain our transitions + TM = zeros(n_clusters+2,n_clusters+2,n); + + % Cumulative frame counts across subjects (1xn_subjects) for frames + dd2p = cumsum([1,sum(xindp1,1)]); + + % TPM will contain the sequence of states for all subjects + TPM = zeros(n,n_frames); + + % Computation of the state matrix + + % Filling of TP for each subject + for i = 1:n + % We set scrubbed time points at -1 + TPM(i,xindp1(:,i)) = idx(dd2p(i):dd2p(i+1)-1)'; + TPM(i,sind(:,i)) = -1; + end + + % Computation of the counts + + % We first compute the amount and percentage of scrubbed frames + % Raw counts + Counts.raw.scrubbed = sum(TPM == -1,2); + Counts.raw.notpicked = sum(TPM == 0,2); + + % Normalized counts + Counts.frac.scrubbed = 100*sum(TPM == -1,2)./sum(TPM,2); + Counts.frac.notpicked = 100*sum(TPM == 0,2)./sum(TPM,2); + + for idx_state = 1:n_clusters + Counts.raw.state(:,idx_state) = sum(TPM == idx_state,2); + Counts.frac.state(:,idx_state) = 100*sum(TPM == idx_state,2)./sum(TPM > 0,2); + end + + % Computation of state number and duration + + % We now want to compute dynamical metrics for each subject + for j = 1:n + + % Marks the indices when we switch state (last element is idx_max+1) + TS = find(diff([-1337 TPM(j,:) -1337])~=0); + + % Length of each period (in s.) + Length = diff(TS)*TR; + + % Starting index of each period (in s., first element at 0 s.) + StartTime = (TS(1:end-1)-1)*TR; + + % Type of each period + SeqType = TPM(j,TS(1:end-1)); + + % Number of different states that have been entered + n_Periods = length(Length); + + % We now want to actually count how many times we enter each state, + % and how long we stay in each state + n_Sequences = zeros(1,n_clusters+2); + length_Sequences = cell(1,n_clusters+2); + + % We go through all sequences + for i = 1:n_Periods + + % We increase the appropriate counter + n_Sequences(SeqType(i)+2) = n_Sequences(SeqType(i)+2)+1; + length_Sequences{SeqType(i)+2} = [length_Sequences{SeqType(i)+2},Length(i)]; + end + + for ns = 1:n_clusters+2 + % Average duration of states + avg_length_Sequences(ns) = mean(length_Sequences{ns}); + end + + Duration{j} = length_Sequences; + Number(j,:) = n_Sequences; + Avg_Duration(j,:) = avg_length_Sequences; + + % For each state transition, we increment properly the matrix of + % transitions + for k = 1:n_frames-1 + TM(TPM(j,k)+2,TPM(j,k+1)+2,j) = TM(TPM(j,k)+2,TPM(j,k+1)+2,j) + 1; + end + end + + % We normalize the matrix by all the transitions + TM = TM/(n_frames-1); +end \ No newline at end of file diff --git a/Analysis/ConsensusClustering.m b/Analysis/ConsensusClustering.m new file mode 100755 index 0000000..67195f7 --- /dev/null +++ b/Analysis/ConsensusClustering.m @@ -0,0 +1,106 @@ +%% This function performs consensus clustering over a range of K values +% The goal is to provide a measure of how good each value of K is +% +% Inputs: +% - X is the data matrix (n_DP x n_DIM) +% - K_range is the range of K values to examine +% - Subsample_type defines how subsampling is done: across items (data +% points) if 'items', and across dimensions if 'dimensions' +% - Subsample_fraction is the fraction of the original data points, or +% dimensions, to keep for a given fold +% - n_folds is the number of folds over which to run +function [Consensus_ordered] = ConsensusClustering(X,K_range,Subsample_type,Subsample_fraction,n_folds,DistType) + + % Number of data points + n_items = size(X,1); + + % Number of dimensions + n_dims = size(X,2); + + Consensus = zeros(n_items,n_items,length(K_range)); + Consensus_ordered = zeros(n_items,n_items,length(K_range)); + + % Loop over all K values to assess + for k = 1:length(K_range) + + disp(['Running consensus clustering for K = ',num2str(K_range(k)),'...']); + + % Connectivity matrix that will contain 0s or 1s depending on whether + % elements are clustered together or not + M = zeros(n_items,n_items,n_folds); + I = zeros(n_items,n_items,n_folds); + + disp('before h loop'); + + % Loops over all the folds to perform clustering for + for h = 1:n_folds + + switch Subsample_type + case 'items' + + % Number of items to subsample + n_items_ss = floor(Subsample_fraction*n_items); + + % Does the subsampling + [X_ss,tmp_ss] = datasample(X,n_items_ss,1,'Replace',false); + + % Vector + I_vec = zeros(n_items,1); + I_vec(tmp_ss) = 1; + + % Constructs the indicator matrix + for i = 1:length(I_vec) + for j = 1:length(I_vec) + if (I_vec(i) == I_vec(j)) && (I_vec(i) > 0) + I(i,j,h) = 1; + end + end + end + + case 'dims' + + % Number of dimensions to subsample + n_dims_ss = floor(Subsample_fraction*n_dims); + + % Does the subsampling + [X_ss,tmp_ss] = datasample(X,n_dims_ss,2,'Replace',false); + + % Constructs the indicator matrix + I(:,:,h) = ones(n_items,n_items); + + otherwise + errordlg('PROBLEM IN TYPE OF SUBSAMPLING'); + end + + % Does the clustering (for now, only with k-means), so that IDX + % contains the indices for each datapoint + IDX = kmeans(X_ss,K_range(k),'Distance',DistType,'Replicates',1,'Start','uniform'); + + % Builds the connectivity matrix + M(:,:,h) = Build_Connectivity_Matrix(IDX,tmp_ss,Subsample_type,n_items); + + clear I_vec + clear X_ss + clear tmp_ss + clear IDX + end + + % Constructs the consensus matrix for the considered K + Consensus(:,:,k) = sum(M,3)./sum(I,3); + + tree = linkage(squeeze(1-Consensus(:,:,k)),'average'); + + % Leaf ordering to create a nicely looking matrix + leafOrder = optimalleaforder(tree,squeeze(1-Consensus(:,:,k))); + + % Ordered consensus matrix + Consensus_ordered(:,:,k) = Consensus(leafOrder,leafOrder,k); + + clear leafOrder + clear Dist_vec + clear test + clear IDX + clear M + clear I + end +end \ No newline at end of file diff --git a/Analysis/Run_Clustering.m b/Analysis/Run_Clustering.m new file mode 100755 index 0000000..e812dff --- /dev/null +++ b/Analysis/Run_Clustering.m @@ -0,0 +1,121 @@ +function [CP2,Disp,Std_Clusters,idx,d,sfrac] = Run_Clustering(XONn,n_clusters,mask,brain_info,maskP,maskN,n_rep,idx_sep_seeds,SeedType) + + % Number of seeds + n_seeds = size(idx_sep_seeds,3); + + % Number of subjects + n_subjects = size(idx_sep_seeds,2); + + % Number of possible seed combinations + switch n_seeds + case 1 + n_combos = 1; + combvec = [1]; + case 2 + n_combos = 3; + combvec = [1 0; 0 1; 1 1]; + case 3 + n_combos = 7; + combvec = [1 0 0; 0 1 0; 0 0 1; 1 1 0; 0 1 1; 1 0 1; 1 1 1]; + otherwise + errordlg('PROBLEM AT SEED FRACTIONS'); + end + + % Will contain the fraction of frames linked to a given seed (if using + % the 'Intersection' method) + sfrac = zeros(n_subjects,n_clusters,n_combos); + + % 'Filtering so that we only use the largest activation and deactivation + % spots for clustering + XONn_filtered = CAP_mask4kmeans(XONn,maskP,maskN,6,mask,brain_info); + + % Rows datapoints, columns variable (so here every 70700 activation is + % a datapoint) + % idx will contain 1462 elements (the index of the cluster to which the + % considered datapoint belongs + [idx,CP] = kmeans(XONn_filtered',n_clusters,'distance','correlation','replicates',n_rep,'empty','drop','maxiter',100,'Display','iter'); + + % idx2counts is of size K (number of clusters) and has the number of + % datapoints classified within a given cluster) + + % disp('idx2counts:'); + idx2counts = histc(idx, 1:max(idx)); + + % Output = Input(IX) + [~,IX] = sort(idx2counts,'descend'); + + % Size Kx70700 (location of each cluster); clusters are put with 'the + % most prominent one first' + CP = CP(IX,:); idx2 = idx; % order by occurrence + + % Changes the datapoint indexes so that they fit the new clusters order + for l=1:max(idx), idx2(idx==IX(l))=l; end + idx=idx2; + + CP2 = zeros(n_clusters,size(CP,2)); + Disp = zeros(1,n_clusters); + Std_Clusters = zeros(size(CP,2),n_clusters); + + % For each cluster index + for l=1:max(idx) + % Averages all data points belonging to one specific cluster, and + % stores the obtained pattern as a cell in CP2 + CP2(l,:) = mean(XONn(:,idx==l),2); %./ ( std(XON(:,idx==l),[],2) ) * sqrt(length(idx==l)); % Liu&Duyn + + % Measure of dispersion within the cluster considered + Disp(l) = mean(corr(CP2(l,:)',XONn(:,idx==l))); + + Std_Clusters(:,l) = std(XONn(:,idx==l),[],2); + end + + % d contains the correlation values of all frames to the CAPs + r = corr(CP2',XONn); + + d = zeros(1,length(idx)); + for k=1:max(idx) + d(idx==k) = r(k,idx==k); + end + + % Added part to compute the fraction of frames assigned to a given seed + % if using the intersection option (in which a data point is retained + % as long as at least one seed region becomes significantly (de)active) + if strcmp(SeedType,'Intersection') + + % idx_all will contain the clustering indices as put on a whole + % temporal scale (time x subjects) + idx_all = zeros(size(idx_sep_seeds,1),n_subjects); + + % Index to properly fill in the matrix by adding up the number of + % frames per subject every time + tmp_loc = 1; + + + for s = 1:n_subjects + tmp = sum(squeeze(idx_sep_seeds(:,s,:)),2); + tmp(tmp >= 1) = 1; + tmp = logical(tmp); + idx_all(tmp,s) = idx(tmp_loc:(tmp_loc+sum(tmp)-1)); + tmp_loc = tmp_loc + sum(tmp); + end + + + + % I will compute my fractions for each seed combination of + % interest; for example, 3 seeds would yield 7 possible + % combinations + for s = 1:n_subjects + for t = 1:size(idx_sep_seeds,1) + + tmp = squeeze(idx_sep_seeds(t,s,:)); + + % If there is at least one seed active or deactive at + % the point of interest, we update the sfrac count at the + % appropriate CAP + if sum(tmp) > 0 + sfrac(s,idx_all(t,s),find(ismember(combvec,tmp','rows'))) = ... + sfrac(s,idx_all(t,s),find(ismember(combvec,tmp','rows'))) + 1; + end + end + end + end +end \ No newline at end of file diff --git a/Analysis/circular_arrow.m b/Analysis/circular_arrow.m new file mode 100644 index 0000000..ae40811 --- /dev/null +++ b/Analysis/circular_arrow.m @@ -0,0 +1,137 @@ +function circular_arrow(figHandle, radius, centre, arrow_angle, angle, direction, colour, head_size, head_style) +% This is a function designed to draw a circular arrow onto the current +% figure. It is required that "hold on" must be called before calling this +% function. +% +% The correct calling syntax is: +% circular_arrow(height, centre, angle, direction, colour, head_size) +% where: +% figHandle - the handle of the figure to be drawn on. +% radius - the radius of the arrow. +% centre - a vector containing the desired centre of the circular +% arrow. +% arrow_angle - the desired orientation angle of the circular arrow. +% This is measured in degrees counter-clockwise +% angle - the angle between starting and end point of the arrow in +% degrees. +% direction - variable set to determine format of arrow head. Use 1 +% to get a clockwise arrow, -1 to get a counter clockwise +% arrow, 2 to get a double headed arrow and 0 to get just +% an arc. +% colour (optional) - the desired colour of the arrow, using Matlab's +% Color Specification. +% head_size (optional) - the size of the arrow head. +% head_style (optional) - the style of the arrow head. +% For more information, see Annotation Arrow Properties. + +%Ensure proper number of arguments +if (nargin < 6)||(nargin > 9) + error(['Wrong number of parameters '... + 'Enter "help circular_arrow" for more information']); +end + +% arguments 7, 8 and 9 are optional, +if nargin < 9 + head_style = 'vback2'; +end +if nargin < 8 + head_size = 10; +end +if nargin < 7 + colour = 'k'; +end + +% display a warning if the headstyle has been specified, but direction has +% been set to no heads +if nargin == 9 && direction == 0 + warning(['Head style specified, but direction set to 0! '... + 'This will result in no arrow head being displayed.']); +end + + +% Check centre is vector with two points +[m,n] = size(centre); +if m*n ~= 2 + error('Centre must be a two element vector'); +end + +arrow_angle = deg2rad(arrow_angle); % Convert angle to rad +angle = deg2rad(angle); % Convert angle to rad +xc = centre(1); +yc = centre(2); + +% Creating (x, y) values that are in the positive direction along the x +% axis and the same height as the centre +x_temp = centre(1) + radius; +y_temp = centre(2); + +% Creating x & y values for the start and end points of arc +x1 = (x_temp-xc)*cos(arrow_angle+angle/2) - ... + (y_temp-yc)*sin(arrow_angle+angle/2) + xc; +x2 = (x_temp-xc)*cos(arrow_angle-angle/2) - ... + (y_temp-yc)*sin(arrow_angle-angle/2) + xc; +x0 = (x_temp-xc)*cos(arrow_angle) - ... + (y_temp-yc)*sin(arrow_angle) + xc; +y1 = (x_temp-xc)*sin(arrow_angle+angle/2) + ... + (y_temp-yc)*cos(arrow_angle+angle/2) + yc; +y2 = (x_temp-xc)*sin(arrow_angle-angle/2) + ... + (y_temp-yc)*cos(arrow_angle-angle/2) + yc; +y0 = (x_temp-xc)*sin(arrow_angle) + ... + (y_temp-yc)*cos(arrow_angle) + yc; + +% Plotting twice to get angles greater than 180 +i = 1; + +% Creating points +P1 = struct([]); +P2 = struct([]); +P1{1} = [x1;y1]; % Point 1 - 1 +P1{2} = [x2;y2]; % Point 1 - 2 +P2{1} = [x0;y0]; % Point 2 - 1 +P2{2} = [x0;y0]; % Point 2 - 1 +centre = [xc;yc]; % guarenteeing centre is the right dimension +n = 1000; % The number of points in the arc +v = struct([]); + +while i < 3 + + v1 = P1{i}-centre; + v2 = P2{i}-centre; + c = det([v1,v2]); % "cross product" of v1 and v2 + a = linspace(0,atan2(abs(c),dot(v1,v2)),n); % Angle range + v3 = [0,-c;c,0]*v1; % v3 lies in plane of v1 and v2 and is orthog. to v1 + v{i} = v1*cos(a)+((norm(v1)/norm(v3))*v3)*sin(a); % Arc, center at (0,0) + plot(v{i}(1,:)+xc,v{i}(2,:)+yc,'Color', colour) % Plot arc, centered at P0 + + i = i + 1; + +end + +position = struct([]); + +% Setting x and y for CW and CCW arrows +if direction == 1 + position{1} = [x2 y2 x2-(v{2}(1,2)+xc) y2-(v{2}(2,2)+yc)]; +elseif direction == -1 + position{1} = [x1 y1 x1-(v{1}(1,2)+xc) y1-(v{1}(2,2)+yc)]; +elseif direction == 2 + position{1} = [x2 y2 x2-(v{2}(1,2)+xc) y2-(v{2}(2,2)+yc)]; + position{2} = [x1 y1 x1-(v{1}(1,2)+xc) y1-(v{1}(2,2)+yc)]; +elseif direction == 0 + % Do nothing +else + error('direction flag not 1, -1, 2 or 0.'); +end + +% Loop for each arrow head +i = 1; +while i < abs(direction) + 1 + h=annotation('arrow'); % arrow head + set(h,'parent', gca, 'position', position{i}, ... + 'HeadLength', head_size, 'HeadWidth', head_size,... + 'HeadStyle', head_style, 'linestyle','none','Color', colour); + + i = i + 1; +end \ No newline at end of file diff --git a/Analysis/jUpperTriMatToVec.m b/Analysis/jUpperTriMatToVec.m new file mode 100755 index 0000000..693b942 --- /dev/null +++ b/Analysis/jUpperTriMatToVec.m @@ -0,0 +1,32 @@ +function v=jUpperTriMatToVec(m,varargin) +% converts the upper-triangular part of a matrix to a vector +% +% IN: +% m: matrix +% offset: offset above leading diagonal, fed to triu function +% OUT: +% v: vector of upper-triangular values +% +% v1.0 Oct 2009 Jonas Richiardi +% - initial release +% v1.1 Feb 2015 Dimitri Van De Ville +% - faster version by single index + +switch nargin + case 1 + offset=1; + case 2 + offset=varargin{1}; +end + +% get indices of upper triangular part (Peter Acklam's trick) +%[m_i m_j] = find(triu(ones(size(m)), offset)); +idx = find(triu(ones(size(m)), offset)); + +v=m(idx); + +% copy to vector +%v=zeros(numel(m_i),1); +%for v_idx=1:numel(m_i) +% v(v_idx)=m(m_i(v_idx),m_j(v_idx)); +%end \ No newline at end of file diff --git a/Build_Connectivity_Matrix.m b/Build_Connectivity_Matrix.m new file mode 100755 index 0000000..4befae4 --- /dev/null +++ b/Build_Connectivity_Matrix.m @@ -0,0 +1,25 @@ +%% This function constructs a connectivity matrix from the indices of +% cluster assignment +function [M] = Build_Connectivity_Matrix(IDX,tmp_ss,type,n_items) + + IDX_full = zeros(n_items,1); + + switch type + case 'items' + IDX_full(tmp_ss) = IDX; + case 'dims' + IDX_full = IDX; + case 'subjects' + IDX_full(tmp_ss) = IDX; + end + + M = zeros(n_items,n_items); + + for i = 1:length(IDX_full) + for j = 1:length(IDX_full) + if (IDX_full(i) == IDX_full(j)) && (IDX_full(i) > 0) + M(i,j) = 1; + end + end + end +end \ No newline at end of file diff --git a/CAP_ConsensusClustering.m b/CAP_ConsensusClustering.m new file mode 100755 index 0000000..e017aa2 --- /dev/null +++ b/CAP_ConsensusClustering.m @@ -0,0 +1,137 @@ +%% This function performs consensus clustering over a range of K values +% The goal is to provide a measure of how good each value of K is +% +% Inputs: +% - X is the data matrix (cell array with each cell n_DP x n_DIM) +% - K_range is the range of K values to examine +% - Subsample_type defines how subsampling is done: across items (data +% points) if 'items', and across dimensions if 'dimensions' +% - Subsample_fraction is the fraction of the original data points, or +% dimensions, to keep for a given fold +% - n_folds is the number of folds over which to run +function [Consensus_ordered] = CAP_ConsensusClustering(X,K_range,Subsample_type,Subsample_fraction,n_folds,DistType) + + % Number of subjects + n_subjects = length(X); + + n_items = 0; + + frames_index = cell(1,n_subjects); + + for s = 1:n_subjects + frames_index{s} = n_items + (1:size(X{s},2)); + n_items = n_items + size(X{s},2); + end + + % Number of dimensions + n_dims = size(X{1},1); + + Consensus = zeros(n_items,n_items,length(K_range)); + Consensus_ordered = zeros(n_items,n_items,length(K_range)); + + % Loop over all K values to assess + for k = 1:length(K_range) + + disp(['Running consensus clustering for K = ',num2str(K_range(k)),'...']); + + % Connectivity matrix that will contain 0s or 1s depending on whether + % elements are clustered together or not + M = zeros(n_items,n_items,n_folds); + I = zeros(n_items,n_items,n_folds); + + % Loops over all the folds to perform clustering for + for h = 1:n_folds + + switch Subsample_type + case 'items' + + % Number of items to subsample + n_items_ss = floor(Subsample_fraction*n_items); + + % Does the subsampling + [X_ss,tmp_ss] = datasample((cell2mat(X))',n_items_ss,1,'Replace',false); + + % Vector + I_vec = zeros(n_items,1); + I_vec(tmp_ss) = 1; + + % Constructs the indicator matrix + for i = 1:length(I_vec) + for j = 1:length(I_vec) + if (I_vec(i) == I_vec(j)) && (I_vec(i) > 0) + I(i,j,h) = 1; + end + end + end + + case 'dims' + + % Number of dimensions to subsample + n_dims_ss = floor(Subsample_fraction*n_dims); + + % Does the subsampling + [X_ss,tmp_ss] = datasample((cell2mat(X))',n_dims_ss,2,'Replace',false); + + % Constructs the indicator matrix + I(:,:,h) = ones(n_items,n_items); + + case 'subjects' + + % Number of subjects to use in the subsampling + n_subjects_ss = floor(Subsample_fraction*n_subjects); + + tmp = datasample(1:n_subjects,n_subjects_ss,'Replace',false); + + % n_frames x n_voxels + X_ss = (cell2mat(X(tmp)))'; + tmp_ss = cell2mat(frames_index(tmp)); + + % Vector + I_vec = zeros(n_items,1); + I_vec(tmp_ss) = 1; + + % Constructs the indicator matrix + for i = 1:length(I_vec) + for j = 1:length(I_vec) + if (I_vec(i) == I_vec(j)) && (I_vec(i) > 0) + I(i,j,h) = 1; + end + end + end + + otherwise + errordlg('PROBLEM IN TYPE OF SUBSAMPLING'); + end + + % Does the clustering (for now, only with k-means), so that IDX + % contains the indices for each datapoint + IDX = kmeans(X_ss,K_range(k),'Distance',DistType,'Replicates',1,'Start','uniform'); + + % Builds the connectivity matrix + M(:,:,h) = Build_Connectivity_Matrix(IDX,tmp_ss,Subsample_type,n_items); + + clear I_vec + clear X_ss + clear tmp_ss + clear IDX + end + + % Constructs the consensus matrix for the considered K + Consensus(:,:,k) = sum(M,3)./sum(I,3); + + tree = linkage(squeeze(1-Consensus(:,:,k)),'average'); + + % Leaf ordering to create a nicely looking matrix + leafOrder = optimalleaforder(tree,squeeze(1-Consensus(:,:,k))); + + % Ordered consensus matrix + Consensus_ordered(:,:,k) = Consensus(leafOrder,leafOrder,k); + + clear leafOrder + clear Dist_vec + clear test + clear IDX + clear M + clear I + end +end \ No newline at end of file diff --git a/CAP_TB.m b/CAP_TB.m new file mode 100755 index 0000000..d608cf0 --- /dev/null +++ b/CAP_TB.m @@ -0,0 +1,3413 @@ +%% This is the main script containing the routines necessary for the use +% of the co-activation pattern analysis (CAP) toolbox +% +% Implemented and written by Thomas Bolton, Medical Image Processing +% Laboratory (MIP:Lab) +% +% Version 1.0: November 9th 2018: fixing the last remaining issues +% +function varargout = CAP_TB(varargin) + +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @CAP_TB_OpeningFcn, ... + 'gui_OutputFcn', @CAP_TB_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 + + + + + +%% Executes when the window opens +function CAP_TB_OpeningFcn(hObject, eventdata, handles, varargin) + +%%%%%%%%%%%%%%%%%%%% +% Path and other miscellaneous settings + +% Adds the paths to the subfolders of the toolbox that will be important +% for the plotting and the analysis +addpath(genpath('./Plotting')); +addpath(genpath('./Analysis')); +addpath(genpath('./DefaultData')); + +% Sets warnings off +warning('off'); + +% Choose default command line output for CAP_TB +handles.output = hObject; + + +%%%%%%%%%%%%%%%%%%%% +% Data loading + +% TC will contain the time courses of the subjects from the different +% populations (cell array, each cell with size n_TP x n_masked_voxels) +handles.TC = {}; + +% FD contains the traces of framewise displacement for the subjects (n_TP x +% n_subj per cell of the array, one cell per dataset) +handles.FD = {}; + +% Information on the NIFTI files from which the data originate +handles.brain_info = {}; + +% Mask used prior to CAP analysis +handles.mask = {}; + +% Number of datasets added to the interface. A dataset is defined as a +% population of subjects from the same experimental group (e.g., an +% ensemble of subjects suffering from the same disorder) +handles.n_datasets = 0; + +% Stores the number of subjects that have been loaded +handles.n_subjects = {}; + +% SubjNames contains the names of the files from which subject data have +% been sampled (full paths) +handles.SubjNames = {}; + +% MotName contains the name(s) of the file(s) loaded as motion ones +handles.MotName = {}; + +% TP and VOX contain the number of time points (of frames) and of brain +% voxels that are present in the loaded datasets. Those values are +% initialized at -inf, and then take the values of the first file that is +% being loaded if that file looks reasonable dimensionally speaking. In the +% scripts below, it is assumed that all subject populations loaded have a +% similar number of time points and of voxels +handles.SubjSize.TP = -inf; +handles.SubjSize.VOX = -inf; + +% By default, the reference population from which CAPs will be extracted +% will be the first uploaded one +% Note: for the moment, this parameter is fixed to 1 (no functionality for +% modifying it yet) +handles.ReferencePopulation = 1; + +% Loads and sets the brain underlay used for plotting purposes +Underlay = load_nii('Underlay.nii'); +Underlay_mat = [Underlay.hdr.hist.srow_x; Underlay.hdr.hist.srow_y; Underlay.hdr.hist.srow_z; 0 0 0 1]; +Underlay_dim = Underlay.hdr.dime.dim; +Underlay_dim = Underlay_dim(2:4); +handles.Underlay_info.dim = Underlay_dim; +handles.Underlay_info.mat = Underlay_mat; +clear Underlay +clear Underlay_dim +clear Underlay_mat +load('brain.mat'); +assignin('base','brain', brain); +handles.brain = brain; +clear brain + +% Handles for the TR and whether it is a reasonable value +handles.TR = -inf; +handles.isTROK = false; + + +%%%%%%%%%%%%%%%%%%%% +% Seed selection and seed maps + +% Seed used for the analysis +handles.seed = []; + +% Because there can be more than one seed, we create a vector that will +% encompass the multiple information in several colors +handles.seed_display = []; + +% Handle to verify whether the amount of seeds has been entered well, how +% many seeds there are, and the type +handles.isSeedOK = false; + +% Option to have a subject-specific seed +handles.isSeedSubjectSpecific = 0; + +% Number of different seeds +handles.n_seed = 1; + +handles.SeedType = 'Average'; + +% One average map throughout subjects +handles.AvgSeedMap = []; + +%%%%%%%%%%%%%%%%%%%% +% Time points selection + +% Motion threshold for scrubbing +handles.Tmot = 0.5; + +% Threshold for frame selection in the analysis +handles.T = 0.5; + +% Sets the right text header in front of the frame selection threshold box +% (threshold or retention percentage) +if get(handles.TRadio,'Value') + set(handles.TText,'String','T [-]'); + handles.SelMode = 'Threshold'; +else + set(handles.TText,'String','P [%]'); + handles.SelMode = 'Percentage'; +end + +% Denotes the type of frames (activation, deactivation or both) to use for +% selecting time points +handles.SignMatrix = [1 0; 1 0]; + +% Activation and deactivation frames kept for all datasets +handles.Xonp = {}; +handles.Xonn = {}; + +% Percentage of frames retained for CAP analysis (discarding both the +% baseline time points and the scrubbed time points) +handles.RetainedPercentage = {}; + +% Indices of the frames that have been retained (i.e. when do they occur in +% the full time course), of baseline frames, and of scrubbed frames +handles.FrameIndices = {}; + +handles.idx_sep_seeds = {}; + +%%%%%%%%%%%%%%%%%%%% +% CAP analysis + +% Max number of clusters to verify with consensus clustering +handles.Kmax = 12; + +% Percentage of items to use for the consensus clustering folds +handles.PCC = 80; + +% Number of times that clustering is run +handles.n_rep = 20; + +% Percentage voxels to keep for clustering (positive - Pp - and negative - +% Pn - ones) +handles.Pp = 100; +handles.Pn = 100; + +% Number of clusters to use in the analysis +handles.K = 5; + +% Indices of the CAP to which frames from the reference population and from +% the other populations are assigned +handles.idx = {}; + +% Value of correlation of the control group frame that is the Tper-th least +% close to its CAP +handles.CorrDist = []; + +% Contains the CAPs +handles.CAP = []; + +% Contains the standard deviation for the CAPs +handles.STDCAP = []; + +% Percentile threshold used in frame assignment +handles.percentile = 5; + +%%%%%%%%%%%%%%%%%%%% +% Metrics + +% Will contain the metrics +% State matrix (n_subjects x n_time points) +handles.TPM = {}; + +% State counts (raw and frac) +handles.Counts = {}; + +% Number of times entering a state +handles.Number = {}; + +% Average duration within a state +handles.Avg_Duration = {}; + +% Duration of all the excursions within a state +handles.Duration = {}; + +% Transition probabilities +handles.TM = {}; + +% Cumulative sum of states +handles.TPMCum = {}; + +% Seed fractions +handles.sfrac = []; + +%%%%%%%%%%%%%%%%%%%% +% General utilities + +% Log containing the different events summoned from the toolbox +handles.Log = {}; + +% Colors used in plotting of all populations +handles.PopColor{1} = [255,255,180; 219,224,252; 188,252,188; 230,230,230]/255; +handles.PopColor{2} = [130,48,48; 51,75,163; 59,113,86; 0, 0, 0]/255; + +% Project title, by default 'Untitled' +handles.project_title = 'Untitled'; + +% Directory to which data is to be saved (initially loaded as ./SavedData) +handles.savedir = fullfile(pwd,'SavedData'); +set(handles.SaveFolderText,'String',handles.savedir); + +% Update handles structure +guidata(hObject, handles); + +% --- Outputs from this function are returned to the command line. +function varargout = CAP_TB_OutputFcn(hObject, eventdata, handles) + +% Get default command line output from handles structure +varargout{1} = handles.output; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%% SECTION 1: LOADING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Data Button Click + +% Executes when adding a subject population (clicking on 'A. Load data') +function DataButton_Callback(hObject, eventdata, handles) + % Opens up a menu to choose the required files for the analysis; the user + % must select four files: + % 1. Data file + % 2. Mask file + % 3. Info file (header of NIFTI) + % 4. Motion file + + % He can select them in the order he likes + [filename1,pathname1]=uigetfile({'*.*','All Files'},... + 'Select data, motion, mask and brain info files...','MultiSelect','on'); + + % If the user has indeed entered files + if ~isequal(filename1,0) || ~isequal(pathname1,0) + % There should be four selected files. In this switch, we test + % for the amount of entered files + if length(filename1) == 4 + + % The files are loaded sequentially + for i = 1:length(filename1) + File{i} = fullfile(pathname1, filename1{i}); + tmp = load(File{i}); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); + + % Finds what type of file DataType is between the four + % possibilities + DataType = CAP_FindDataType(tmp); + + % Accordingly, fill the right handle with the information + switch DataType + case 'Data' + % We store the data into handles.TC and the file name that goes + % with it + handles.TC{handles.n_datasets+1} = tmp; + + % Takes only the last two parts of the file name and + % puts them in tmp_file + [tmp_file,n_delim] = strsplit(File{i},'/'); + + if isempty(n_delim) + tmp_file = strsplit(File{i},'\'); + end + + tmp_file = tmp_file(end-1:end); + + % This is what is saved and displayed in the main + % window then + handles.SubjNames{handles.n_datasets+1} = fullfile(tmp_file{1},tmp_file{2}); + handles.n_subjects{handles.n_datasets+1} = size(handles.TC{handles.n_datasets+1},2); + + % Some commands are run only for the first dataset that we add + if handles.n_datasets == 0 + % We compute and store the number of voxels and the number of time + % points, as well as the number of subjects + handles.SubjSize.VOX = size(handles.TC{1}{1},2); + handles.SubjSize.TP = size(handles.TC{1}{1},1); + end + + % Sets the text label about data dimensions + set(handles.Dimensionality_Text, 'String', [num2str(handles.SubjSize.TP),... + ' frames x ',num2str(handles.SubjSize.VOX),' voxels (',... + strjoin(arrayfun(@(x) num2str(x),cell2mat(handles.n_subjects),... + 'UniformOutput',false),'+'),')']); + + case 'Motion' + % We store the path of the motion file added + handles.MotName{handles.n_datasets+1} = File{i}; + % If the dimensions hold, we store the file into the FD variable + % and then plot the FD ribbon graph + handles.FD{handles.n_datasets+1} = tmp; + + case 'Mask' + handles.mask{handles.n_datasets+1} = tmp; + case 'Info' + % If so, we store the value and we validate the choice + handles.brain_info{handles.n_datasets+1} = tmp; + + % If the data file is unknown, then we return an error and + % the user must enter files again + case 'Unknown' + errordlg('At least one of the selected files is not recognized; please try again !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + end + + % Check if the dimensionality of the entered data holds between + % the file types. It may be that the user entered four files of + % the same type (e.g. four data files), rather than one of each + % type as required + [is_DataOK,Data_problems] = CAP_IsDataOK(handles.TC{handles.n_datasets+1},handles.FD{handles.n_datasets+1},... + handles.mask{handles.n_datasets+1},handles.brain_info{handles.n_datasets+1}); + if is_DataOK + + % We increment handles.n_datasets + handles.n_datasets = handles.n_datasets + 1; + + % We can now enable the seed selection + set(handles.SeedButton,'Enable','on'); + + % Also, we can now color the button in green + set(hObject,'BackgroundColor', [101,140,196]/255); + + % If we are loading the first dataset, we convert the underlay + % to the resolution of the functional data for plotting + if handles.n_datasets == 1 + + % If we are loading the first dataset, we can get rid + % of anything else that we have so far + handles = ClearSection3(eventdata,handles); + handles = ClearSection4(eventdata,handles); + + % The brain variable now contains a good resolution + % underlay that can directly be overlapped with the + % functional data + handles.brain = CAP_V2V(handles.brain,handles.Underlay_info.dim,... + handles.Underlay_info.mat,handles.brain_info{1}.dim,handles.brain_info{1}.mat); + + elseif handles.n_datasets > 1 && handles.n_datasets < 5 && ~isempty(handles.CAP) + + % If we are loading one more dataset on top of the + % first one, then we rather want to solely clear the + % Metrics content; but we may want to keep the rest + handles = ClearSection4(eventdata,handles); + + set(handles.AssignButton,'Enable','on'); + + set(handles.CAP_TP,'Visible','on'); + set(handles.Percentile_Edit,'Visible','on'); + + elseif handles.n_datasets > 4 + errordlg('Please enter at most four different populations in the interface !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + handles.Log = CAP_AddToLog(handles.Log,'Data correctly loaded'); + + % If it doesn't hold, then we return an error + else + errordlg(['There is a dimensionality problem in your data: ',Data_problems]); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + % If a different number of files is entered, then there is a problem, + % and everything is reset + else + errordlg('You did not enter the correct number of files !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + % Else, an error is displayed and the user is prompted to enter files + else + errordlg('Cancelling data entry will not solve your problems !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + +% Update handles structure +guidata(hObject, handles); + + + + + +%% TR Textbox Interaction + +% Executes when we go to the TR field to add the TR of the experiment +function TR_Entry_Callback(hObject, eventdata, handles) + + % If the TR takes a reasonable value, then we validate it; we enable values + % between 0.5 and 5s + if (~isempty(str2double(get(hObject,'String')))) && ... + (str2double(get(hObject,'String')) > 0.5) && ... + (str2double(get(hObject,'String')) <= 5) + + handles.TR = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + handles.isTROK = true; + + handles.Log = CAP_AddToLog(handles.Log,'Correct value of TR entered',{handles.TR},{'TR'}); + + % Else, the TR value is not accepted + else + set(hObject,'BackgroundColor', [204,146,146]/255); + handles.isTROK = false; + end + +guidata(hObject, handles); + + + +% Executes during creation of the TR textbox +function handles = TR_Entry_CreateFcn(hObject, eventdata, handles) + + set(hObject,'Enable','off'); + set(hObject,'String','Click to enter...'); + set(hObject,'FontAngle','italic'); + + if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','r'); + end + +guidata(hObject, handles); + + + +% Executes when clicking on the TR text space +function TR_Entry_ButtonDownFcn(hObject, eventdata, handles) + + set(hObject,'Enable','on'); + set(hObject,'String',''); + set(hObject,'FontAngle','normal'); + uicontrol(hObject); + +guidata(hObject, handles); + + + + + +%% Seed Button Controls +% We want to define what happens when loading data (SeedButton) or when +% attempting to plot them (PlotSeedButton) + +% --- Executes on button press in CheckS1POS. +function CheckS1POS_Callback(hObject, eventdata, handles) + +if get(hObject,'Value') + set(handles.CheckS1NEG,'Value',0); +else + set(handles.CheckS1POS,'Value',1); +end + +handles.SignMatrix(2,:) = [1 0]; + +rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +guidata(hObject,handles); + + + +% --- Executes on button press in CheckS1NEG. +function CheckS1NEG_Callback(hObject, eventdata, handles) + +if get(hObject,'Value') + handles.SignMatrix(1,:) = [0 1]; + set(handles.CheckS1POS,'Value',0); + rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[51,75,163]/255,'EdgeColor','none','Parent',handles.FancyCircles); +else + handles.SignMatrix(1,:) = [1 0]; + set(handles.CheckS1POS,'Value',1); + rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); +end + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +guidata(hObject,handles); + + +% --- Executes on button press in CheckS2POS. +function CheckS2POS_Callback(hObject, eventdata, handles) + +if get(hObject,'Value') + set(handles.CheckS2NEG,'Value',0); +else + set(handles.CheckS2POS,'Value',1); +end + +handles.SignMatrix(2,:) = [1 0]; +rectangle('Position',[20 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +guidata(hObject,handles); + + +% --- Executes on button press in CheckS2NEG. +function CheckS2NEG_Callback(hObject, eventdata, handles) + +if get(hObject,'Value') + handles.SignMatrix(2,:) = [0 1]; + set(handles.CheckS2POS,'Value',0); + rectangle('Position',[20 0 10 10],'Curvature',[1 1],'FaceColor',[51,75,163]/255,'EdgeColor','none','Parent',handles.FancyCircles); +else + handles.SignMatrix(2,:) = [1 0]; + set(handles.CheckS2POS,'Value',1); + rectangle('Position',[20 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); +end + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +guidata(hObject,handles); + + + +% --- Executes on button press in CheckS3POS. +function CheckS3POS_Callback(hObject, eventdata, handles) + +if get(hObject,'Value') + set(handles.CheckS3NEG,'Value',0); +else + set(handles.CheckS3POS,'Value',1); +end + +handles.SignMatrix(3,:) = [1 0]; + +rectangle('Position',[40 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +guidata(hObject,handles); + +% --- Executes on button press in CheckS3NEG. +function CheckS3NEG_Callback(hObject, eventdata, handles) + +if get(hObject,'Value') + handles.SignMatrix(3,:) = [0 1]; + set(handles.CheckS3POS,'Value',0); + rectangle('Position',[40 0 10 10],'Curvature',[1 1],'FaceColor',[51,75,163]/255,'EdgeColor','none','Parent',handles.FancyCircles); +else + handles.SignMatrix(3,:) = [1 0]; + set(handles.CheckS3POS,'Value',1); + rectangle('Position',[40 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); +end + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +guidata(hObject,handles); + + + +% Executes when the user changes the type of seed relationship desired +% (Average, Intersection, Union of seed signals) +function SeedPopup_Callback(hObject, eventdata, handles) + +% Clears and makes the circles display reflecting seed types unvisible +handles = ResetGraphDisplay(handles.FancyCircles,handles); + +% We consider the value selected by the user for the first seed between +% '+' or '-', and update the FancyCircles plot accordingly +if get(handles.CheckS1POS,'Value') + rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); +elseif get(handles.CheckS1NEG,'Value') + rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[51,75,163]/255,'EdgeColor','none','Parent',handles.FancyCircles); +end + +% The same is done for the other seeds, if more than one has been loaded +% (or if more than two have been loaded) +if handles.n_seed > 1 + + if get(handles.CheckS2POS,'Value') + rectangle('Position',[20 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + elseif get(handles.CheckS2NEG,'Value') + rectangle('Position',[20 0 10 10],'Curvature',[1 1],'FaceColor',[51,75,163]/255,'EdgeColor','none','Parent',handles.FancyCircles); + end + + if handles.n_seed > 2 + + if get(handles.CheckS3POS,'Value') + rectangle('Position',[40 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + elseif get(handles.CheckS3NEG,'Value') + rectangle('Position',[40 0 10 10],'Curvature',[1 1],'FaceColor',[51,75,163]/255,'EdgeColor','none','Parent',handles.FancyCircles); + end + end +end + +% The second step in the plotting is to add the "arrows" that link circles +% differently depending on whether we consider a Union or an Intersection +switch get(hObject,'Value') + + % If we have the "average" pick + case 1 + % If the user selects "average" but the number of seeds entered + % exceeds 1, then by default we revert back to the "union" case, + % since the user should enter one averaged seed file to have an + % averaged seed output + if handles.n_seed > 1 + set(hObject,'Value',2); + handles.SeedType = 'Union'; + rectangle('Position',[12 -10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[11 -5 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + if handles.n_seed > 2 + rectangle('Position',[32 -10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[31 -5 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + end + else + handles.SeedType = 'Average'; + end + + % If we have the union pick + case 2 + handles.SeedType = 'Union'; + rectangle('Position',[12 -10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[11 -5 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + if handles.n_seed > 2 + rectangle('Position',[32 -10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[31 -5 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + end + + % If we have the intersection pick + case 3 + handles.SeedType = 'Intersection'; + rectangle('Position',[12 10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[11 9 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + if handles.n_seed > 2 + rectangle('Position',[32 10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[31 9 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + end +end + +% Modifies the axis parameters to have a big enough seed display +axis(handles.FancyCircles,'square'); + +switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); +end + +handles.Log = CAP_AddToLog(handles.Log,'Seed union status changed',{handles.SeedType},{'Status'}); + +guidata(hObject, handles); + + + +% Executes during object creation, after setting all properties. +function SeedPopup_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +guidata(hObject, handles); + + + +% Executes when clicking on 'B. Select a seed' +function SeedButton_Callback(hObject, eventdata, handles) + +% We want to clear everything from and after this stage, since we will +% compute results with a new seed +handles = ClearSection3(eventdata,handles); +handles = ClearSection4(eventdata,handles); + +% Multiselection is on, so that as many as three files can be picked +[filename_seed,pathname_seed]=uigetfile({'*.*','All Files'},... + 'Select Seed File...','MultiSelect','on'); + +% Stores the type of frames to retain for each seed +handles.SignMatrix = [1 0; 1 0; 1 0]; + +% Resets the graph displays as well as the seed-related parameters +handles = ResetGraphDisplay(handles.FancyCircles,handles); + +set(handles.Seed1Text,'Visible','off'); +set(handles.Seed2Text,'Visible','off'); +set(handles.Seed3Text,'Visible','off'); + +set(handles.SeedPlusText,'Visible','off'); +set(handles.SeedMinusText,'Visible','off'); + +set(handles.CheckS1POS,'Visible','off'); +set(handles.CheckS1NEG,'Visible','off'); + +set(handles.CheckS1POS,'Value',1); +set(handles.CheckS1NEG,'Value',0); + +set(handles.CheckS2POS,'Visible','off'); +set(handles.CheckS2NEG,'Visible','off'); + +set(handles.CheckS2POS,'Value',1); +set(handles.CheckS2NEG,'Value',0); + +set(handles.CheckS3POS,'Visible','off'); +set(handles.CheckS3NEG,'Visible','off'); + +set(handles.CheckS3POS,'Value',1); +set(handles.CheckS3NEG,'Value',0); + +handles = ResetGraphDisplay(handles.SeedGraphX,handles); +handles = ResetGraphDisplay(handles.SeedGraphZ,handles); +handles = ResetGraphDisplay(handles.SeedMapX,handles); +handles = ResetGraphDisplay(handles.SeedMapZ,handles); + +% If the user has indeed entered files +if ~isequal(filename_seed,0) || ~isequal(pathname_seed,0) + % There should be three or less selected files. In this switch, we test + % for the amount of entered files + + % In the case in which only one seed file is entered ('char' type), + % we convert into an array + if strcmp(class(filename_seed),'char') + filename_seed = {filename_seed}; + end + + % If we enter that statement, it means that we have only one seed type + % across subjects (that is, no subject-specific data) + if length(filename_seed) <= 3 + + % Number of entered seeds + handles.n_seed = length(filename_seed); + + for myindex = 1:length(filename_seed) + File_seed = fullfile(pathname_seed, filename_seed{myindex}); + tmp = load(File_seed); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); + + % If the file is of suitable dimensions + if islogical(tmp) && size(tmp,2) == 1 && size(tmp,1) == sum(handles.mask{1}) + + % Then we put it in the handles, enable the plotting button, and + % make the seed selection button green + handles.seed(:,myindex) = tmp; + + handles.Log = CAP_AddToLog(handles.Log,'Seed chosen',{File_seed},{'Seed file'}); + else + errordlg('The file you entered appears to be of wrong dimensions...'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + end + + % If we survive all the above, we can see that the seed files are + % good + handles.isSeedOK = true; + + % We also enable to plot the seed results + set(handles.PlotSeedButton,'Enable','on'); + set(handles.PlotSeedButton,'Visible','on'); + + % We make the text legends visible + set(handles.S_SEED1,'Visible','on'); + + set(handles.SeedButton,'BackgroundColor', [101,140,196]/255); + + % We can now go through the next parts of the analysis, so we + % enable the related buttons + set(handles.TPSelectionButton,'Enable','on'); + set(handles.SeedMapPushButton,'Enable','on'); + + set(handles.TPSelectionButton,'Visible','on'); + set(handles.SeedMapPushButton,'Visible','on'); + + % Makes other TP selection utilities visible + set(handles.PRadio,'Visible','on'); + set(handles.TRadio,'Visible','on'); + set(handles.uibuttongroup7,'Visible','on'); + set(handles.TText,'Visible','on'); + set(handles.TMotText,'Visible','on'); + set(handles.TEdit,'Visible','on'); + set(handles.TMotEdit,'Visible','on'); + + % We also see the displays for entering seed specifications + set(handles.SeedPopup,'Visible','on'); + + handles.Log = CAP_AddToLog(handles.Log,'Correct amount of seeds entered',{handles.n_seed},{'Seed amount'}); + + handles.seed_display = zeros(length(handles.seed(:,1)),1); + + set(handles.CheckS1POS,'Visible','on'); + set(handles.CheckS1NEG,'Visible','on'); + CheckS1POS_Callback(handles.CheckS1POS,eventdata,handles); + + set(handles.Seed1Text,'Visible','on'); + + set(handles.SeedPlusText,'Visible','on'); + set(handles.SeedMinusText,'Visible','on'); + + % If there are more than one seed, then we allow the popup button to be + % changed for a more complex seed use choice + if handles.n_seed > 1 + set(handles.SeedPopup,'Enable','on'); + set(handles.SeedPopup,'Value',2); + handles.SeedType = 'Union'; + + % We also update the circles in the seed illustration + rectangle('Position',[12 -10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[11 -5 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + + % We fill seed_display with one scalar value across seed voxels + % per seed (to have different colors plotted in the seed choice + % graph + useless_vector = [0.25,0.75,1]; + + for se = 1:handles.n_seed + handles.seed_display = handles.seed_display + useless_vector(se)*handles.seed(:,se); + end + + set(handles.S_SEED2,'Visible','on'); + + set(handles.CheckS2POS,'Visible','on'); + set(handles.CheckS2NEG,'Visible','on'); + CheckS2POS_Callback(handles.CheckS2POS,eventdata,handles); + + set(handles.Seed2Text,'Visible','on'); + + % Same for 3 seeds + if handles.n_seed > 2 + set(handles.CheckS3POS,'Visible','on'); + set(handles.CheckS3NEG,'Visible','on'); + CheckS3POS_Callback(handles.CheckS3POS,eventdata,handles); + + set(handles.Seed3Text,'Visible','on'); + + rectangle('Position',[32 -10 6 8],'Curvature',[0.8 0.8],'Parent',handles.FancyCircles); + rectangle('Position',[31 -5 8 4],'Curvature',[0.8 0.8],'EdgeColor','none','FaceColor','w','Parent',handles.FancyCircles); + + set(handles.S_SEED3,'Visible','on'); + end + + % Entered if we have just one seed + else + set(handles.SeedPopup,'Enable','off'); + set(handles.SeedPopup,'Value',1); + handles.SeedType = 'Average'; + + handles.seed_display = handles.seed; + + set(handles.CheckS1POS,'Visible','on'); + set(handles.CheckS1NEG,'Visible','on'); + end + + rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + + if handles.n_seed > 1 + rectangle('Position',[20 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + + if handles.n_seed > 2 + rectangle('Position',[40 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + end + end + + % If the seed file is of length n_subjects, then we want a + % subject-specific seed scheme to run + elseif length(filename_seed) == handles.n_subjects{1} + + handles.isSeedSubjectSpecific = 1; + + % Then, we only allow one seed to be considered + handles.n_seed = 1; + + % For each entry (i.e., each subject data), we load the seed + for myindex = 1:length(filename_seed) + File_seed = fullfile(pathname_seed, filename_seed{myindex}); + tmp = load(File_seed); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); + + % If the file is of suitable dimensions + if islogical(tmp) && size(tmp,2) == 1 && size(tmp,1) == sum(handles.mask{1}) + + % Then we put it in the handles, enable the plotting button, and + % make the seed selection button green + handles.seed(:,myindex) = tmp; + + handles.Log = CAP_AddToLog(handles.Log,'Seed chosen',{File_seed},{'Seed file'}); + else + errordlg('The file you entered appears to be of wrong dimensions...'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + end + + handles.seed_display = zeros(length(handles.seed(:,1)),1); + + % If we survive all the above, we can see that the seed files are + % good + handles.isSeedOK = true; + + % We also enable to plot the seed results + set(handles.PlotSeedButton,'Enable','on'); + set(handles.PlotSeedButton,'Visible','on'); + + set(handles.S_SEED1,'Visible','on'); + + set(handles.SeedButton,'BackgroundColor', [101,140,196]/255); + + set(handles.SeedPopup,'Enable','off'); + set(handles.SeedPopup,'Value',1); + handles.SeedType = 'Average'; + set(handles.CheckS1POS,'Visible','on'); + set(handles.CheckS1NEG,'Visible','on'); + + % Creates the seed information to plot in the subject-specific case + for idx_seed = 1:size(handles.seed,2) + + handles.seed_display = handles.seed_display + handles.seed(:,idx_seed); + end + + handles.seed_display = handles.seed_display/handles.n_subjects{1}; + + % We can now go through the next parts of the analysis, so we + % enable the related buttons + set(handles.TPSelectionButton,'Enable','on'); + set(handles.SeedMapPushButton,'Enable','on'); + + set(handles.TPSelectionButton,'Visible','on'); + set(handles.SeedMapPushButton,'Visible','on'); + + set(handles.PRadio,'Visible','on'); + set(handles.TRadio,'Visible','on'); + set(handles.uibuttongroup7,'Visible','on'); + set(handles.TText,'Visible','on'); + set(handles.TMotText,'Visible','on'); + set(handles.TEdit,'Visible','on'); + set(handles.TMotEdit,'Visible','on'); + + % We also see the displays for entering seed specifications + set(handles.SeedPopup,'Visible','on'); + + handles.Log = CAP_AddToLog(handles.Log,'Correct subject-specific seed data entered',{handles.n_seed},{'Seed amount'}); + + handles.seed_display = zeros(length(handles.seed(:,1)),1); + + set(handles.CheckS1POS,'Visible','on'); + set(handles.CheckS1NEG,'Visible','on'); + CheckS1POS_Callback(handles.CheckS1POS,eventdata,handles); + + set(handles.Seed1Text,'Visible','on'); + + set(handles.SeedPlusText,'Visible','on'); + set(handles.SeedMinusText,'Visible','on'); + + rectangle('Position',[0 0 10 10],'Curvature',[1 1],'FaceColor',[150,48,48]/255,'EdgeColor','none','Parent',handles.FancyCircles); + + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + + else + errordlg('Problem with the amount of seed files entered !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + % Updates the limits of the plot + switch handles.n_seed + case 1 + set(handles.FancyCircles,'xlim',[-10 10]); + set(handles.FancyCircles,'ylim',[-10 10]); + case 2 + set(handles.FancyCircles,'xlim',[-10 30]); + set(handles.FancyCircles,'ylim',[-10 30]); + case 3 + set(handles.FancyCircles,'xlim',[-10 50]); + set(handles.FancyCircles,'ylim',[-10 50]); + end + +else + errordlg('You did not enter a seed file !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); +end + +guidata(hObject, handles); + + + +% Executes when clicking on 'Plot Seed' +function PlotSeedButton_Callback(hObject, eventdata, handles) + +% Clears the present graph content +cla(handles.SeedGraphX); +cla(handles.SeedGraphZ); + +% Plots the slices within the graph windows +handles.SeedGraphX = plot_slice(handles.seed_display,get(handles.TVIS_Slider,... + 'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(handles.SliderX,... + 'Value'),handles.SeedGraphX); + +handles.SeedGraphZ = plot_slice(handles.seed_display,get(handles.TVIS_Slider,... + 'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SliderZ,... + 'Value'),handles.SeedGraphZ); + +% Sets the sliders to visible +set(handles.SliderX,'Visible','on'); +set(handles.SliderZ,'Visible','on'); + +% Sets the text values at the ones of the sliders +set(handles.XCoordText,'String',['X: ',sprintf('%.2f',get(handles.SliderX,'Value'))]); +set(handles.ZCoordText,'String',['Z: ',sprintf('%.2f',get(handles.SliderZ,'Value'))]); + +% Sets the visibility of the slider texts to on +set(handles.XCoordText,'Visible','on'); +set(handles.ZCoordText,'Visible','on'); + +handles.Log = CAP_AddToLog(handles.Log,'Seed plots activated'); + +guidata(hObject, handles); + + + + + +%% Seed sliders interactions +% For the below functions, the goal is to change the value of the slider +% textboxes when the sliders are moved, and to update the graph display +% accordingly. For this purpose, cla is used to clear graph content prior +% to a new display + +% Executes on slider movement. +function SliderX_Callback(hObject, eventdata, handles) + +% Clears the content of the graph +cla(handles.SeedGraphX); + +% Gets the MNI slice coordinate value associated to the new display +set(handles.XCoordText,'String',['X: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Slice plotting itself; 1.5 is the color past which the display saturates +handles.SeedGraphX = plot_slice(handles.seed_display,get(handles.TVIS_Slider,... + 'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),... + handles.SeedGraphX); + +guidata(hObject, handles); + + + +% Executes during object creation, after setting all properties. +function SliderX_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +guidata(hObject, handles); + + + +% Executes on slider movement. +function SliderZ_Callback(hObject, eventdata, handles) + +cla(handles.SeedGraphZ); +set(handles.ZCoordText,'String',['Z: ',sprintf('%.2f',get(hObject,'Value'))]); +handles.SeedGraphZ = plot_slice(handles.seed_display,get(handles.TVIS_Slider,... + 'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),... + handles.SeedGraphZ); + +guidata(hObject, handles); + + + +% Executes during object creation, after setting all properties. +function SliderZ_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +guidata(hObject, handles); + + + + + +%% Seed Map Computation Button +% When pressing on this button, classical seed maps are computed for the +% population of subjects chosen as the reference one in the loading part. +% The last entered seed is used + +function SeedMapPushButton_Callback(hObject, eventdata, handles) + +% Computes seed maps for each subject and for the population, using the +% data from the chosen reference population +[~,handles.AvgSeedMap] = CAP_Compute_SeedMap(handles.TC{handles.ReferencePopulation},handles.seed,handles.isSeedSubjectSpecific); + +% Graphical displays + +% Making the plots, texts and sliders visible +set(handles.SeedMapX,'Visible','on'); +set(handles.SeedMapZ,'Visible','on'); + +set(handles.SeedMap_SliderX,'Visible','on'); +set(handles.SeedMap_SliderZ,'Visible','on'); +set(handles.SeedMapSliderX,'Visible','on'); +set(handles.SeedMapSliderZ,'Visible','on'); + +set(handles.TSeed_Slider,'Visible','on'); +set(handles.TSeed,'Visible','on'); +set(handles.ColorbarSeed,'Visible','on'); + +% Writing down the text with current MNI coordinates +set(handles.SeedMap_SliderX,'String',['X: ',sprintf('%.2f',get(handles.SeedMapSliderX,'Value'))]); +set(handles.SeedMap_SliderZ,'String',['Z: ',sprintf('%.2f',get(handles.SeedMapSliderZ,'Value'))]); + +% Clears previous plot contents (in case we want to re-plot after changing +% the seed) +cla(handles.SeedMapX); +cla(handles.SeedMapZ); + +% Plots new slices +handles.SeedMapX = plot_slice(handles.AvgSeedMap,0.25,1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(handles.SeedMapSliderX,'Value'),handles.SeedMapX); +handles.SeedMapZ = plot_slice(handles.AvgSeedMap,0.25,1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SeedMapSliderZ,'Value'),handles.SeedMapZ); + +% Adds the colorbar for the seed maps (between -1 and 1) +handles.ColorbarSeed = Create_CAP_colorbar(-1,1,0.5,get(handles.TSeed_Slider,'Value'),'',handles.ColorbarSeed,'Horizontal','div','RdBu',1000); + +handles.Log = CAP_AddToLog(handles.Log,'Seed maps displayed'); + +guidata(hObject,handles); + + + + + +%% Slider Controls (MNI coordinates) +% We want to reload the seed images with the new parameters when changing a +% slider, so we clear the previous display, change the text summarizing the +% MNI coordinate where we stand, and plot the new image + +function SeedMapSliderX_Callback(hObject, eventdata, handles) + +% Clears graphs +cla(handles.SeedMapX); + +% Changes slider texts +set(handles.SeedMap_SliderX,'String',['X: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Plots new slices +handles.SeedMapX = plot_slice(handles.AvgSeedMap,get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),handles.SeedMapX); + +guidata(hObject, handles); + + + +function SeedMapSliderX_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + + +function SeedMapSliderZ_Callback(hObject, eventdata, handles) + +cla(handles.SeedMapZ); + +% Changes slider texts +set(handles.SeedMap_SliderZ,'String',['Z: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Plots new slices +handles.SeedMapZ = plot_slice(handles.AvgSeedMap,get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),handles.SeedMapZ); + +guidata(hObject, handles); + + + +% --- Executes during object creation, after setting all properties. +function SeedMapSliderZ_CreateFcn(hObject, eventdata, handles) +% hObject handle to SeedMapSliderZ (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + + + + +%% Slider controls (visualization threshold) +% We want to replot and modify the colorbar according to the visualization +% threshold that we select (and also change the text) + +function TSeed_Slider_Callback(hObject, eventdata, handles) + +% Clears previous plot contents +cla(handles.SeedMapX); +cla(handles.SeedMapZ); + +% Plots new slices (average seed maps) +handles.SeedMapX = plot_slice(handles.AvgSeedMap,get(hObject,'Value'),1,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',... + get(handles.SeedMapSliderX,'Value'),handles.SeedMapX); + +handles.SeedMapZ = plot_slice(handles.AvgSeedMap,get(hObject,'Value'),1,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',... + get(handles.SeedMapSliderZ,'Value'),handles.SeedMapZ); + +% Modifies the text +set(handles.TSeed,'String',['Tv: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Clears and replots the colorbar +cla(handles.ColorbarSeed); +handles.ColorbarSeed = Create_CAP_colorbar(-1,1,0.5,get(hObject,'Value'),'',handles.ColorbarSeed,'Horizontal','div','RdBu',1000); + +guidata(hObject,handles); + + + +function TSeed_Slider_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + + + + +%% Motion parameter entry +% In the following, we have the functions controling the motion threshold +% (Power's FD) value + +function TMotEdit_Callback(hObject, eventdata, handles) + +% If we enter a reasonable value, it is taken as a new threshold +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 0.5) + handles.Tmot = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid motion threshold value entered',{handles.Tmot},{'Motion threshold value'}); + +% If we set something wrong again, we set the threshold value back to the +% default of 0.5 +else + set(hObject,'BackgroundColor', [203,146,146]/255); + handles.Tmot = 0.5; +end + +guidata(hObject, handles); + + + +% When clicking on the motion button +function handles = TMotEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject, handles); + + + +% When the object is created +function handles = TMotEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','r'); +end + +guidata(hObject, handles); + + + + + +%% Frame selection parameter entry +% What comes below describes the control of the frame selection threshold +% value entered by the user + +% Threshold for selection of the frames to keep +function TEdit_Callback(hObject, eventdata, handles) + +% If we enter a reasonable value, it is taken as the new threshold +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.T = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid (de)activation threshold entered',{handles.T},{'Threshold value'}); + +% If we set something wrong again, we set the threshold value back to the +% default of 0.5 +else + set(hObject,'BackgroundColor',[203,146,146]/255); + handles.T = 0.5; +end + +guidata(hObject, handles); + + + +% When the object is created +function handles = TEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +guidata(hObject, handles); + + + +% When clicking on it +function TEdit_ButtonDownFcn(hObject,eventdata,handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + + + +%% Frame selection mode control buttons +% The below code is run when the user wishes to change between frame +% selection modes + +% If we select threshold, we update the Selmode accordingly +function TRadio_Callback(hObject, eventdata, handles) + +set(handles.TText,'String','T [-]'); +handles.SelMode = 'Threshold'; + +handles.Log = CAP_AddToLog(handles.Log,'Changed time points selection scheme',{handles.SelMode},{'Selected mode'}); + +guidata(hObject,handles); + + + +% Same for percentage +function PRadio_Callback(hObject, eventdata, handles) + +set(handles.TText,'String','P [%]'); +handles.SelMode = 'Percentage'; + +handles.Log = CAP_AddToLog(handles.Log,'Changed time points selection scheme',{handles.SelMode},{'Selected mode'}); + +guidata(hObject,handles); + + + + + +%% Time points selection control +% When clicking on the select time points button, the frames matching the +% provided thresholds for scrubbing and for frame retention are selected. +% Both activation and deactivation frames are selected. This is performed +% on all the loaded populations of subjects + +% Upon clicking on the 'Select time points' button +function TPSelectionButton_Callback(hObject, eventdata, handles) + +% Clears the current plot display (for the case of having already computed +% something before with other parameters) +cla(handles.TPViolin); + +% Performs the analysis to extract frames of activity for all loaded +% populations (done for each dataset) +for n_ds = 1:handles.n_datasets + % Xonp and Xonn contain the frames (deactivation frames have been + % switched in sign, so that deactivation is positive) + [handles.Xonp{n_ds},p,Indices,... + handles.idx_sep_seeds{n_ds}] = CAP_find_activity(handles.TC{n_ds},... + handles.seed,handles.T,handles.FD{n_ds},handles.Tmot,... + handles.SelMode,handles.SeedType,handles.SignMatrix,handles.isSeedSubjectSpecific); + + % Percentage of retained frames across subjects + handles.RetainedPercentage{n_ds} = p(3,:); + + % Indices of the frames that have been retained (used later for metrics + % computations) + handles.FrameIndices{n_ds} = Indices; +end + +% Enables to go to the next step of the analysis and cluster the extracted +% frames +set(handles.ClusterButton,'Enable','on'); +set(handles.CCButton,'Enable','on'); + +set(handles.ClusterButton,'Visible','on'); +set(handles.CCButton,'Visible','on'); + +% Sets related displays to visible +set(handles.CAP_Kmax,'Visible','on'); +set(handles.CAP_PPC,'Visible','on'); +set(handles.CAP_N,'Visible','on'); +set(handles.CAP_K,'Visible','on'); +set(handles.CAP_PP,'Visible','on'); +set(handles.CAP_PN,'Visible','on'); + +set(handles.KRange_Edit,'Visible','on'); +set(handles.PCC_Edit,'Visible','on'); +set(handles.ClusterEdit,'Visible','on'); +set(handles.ClusterRepEdit,'Visible','on'); +set(handles.ClusterPpEdit,'Visible','on'); +set(handles.ClusterPnEdit,'Visible','on'); + +tmp_toplot = ConcatMat(handles.RetainedPercentage,handles.n_datasets,1,handles.n_subjects,'FD'); + +% Displays the violin plot of subject scrubbing percentage for the +% reference population +[~,~,handles.TPViolin] = MakeViolin(tmp_toplot,handles.TPViolin,{' '},'Frames ret. [%]',handles.PopColor,handles.n_datasets,1); +set(handles.TPViolin,'Visible','on'); + +clear tmp_toplot + +handles.Log = CAP_AddToLog(handles.Log,'Time points selected',{['1 to ',num2str(handles.n_datasets)],handles.SelMode,handles.T,handles.Tmot},{'Datasets indices','Selection mode','Activation threshold','Motion threshold'}); + +guidata(hObject, handles); + + + + + +%% Data saving and loading +% The functions below are summoned when the user wishes to save his/her +% data into a MATLAB structure, or to load previously processed data (until +% the end of spatio-temporal selection) and attempt clustering computations +% and CAP generation + +% Save folder change function +function SaveFolderButton_Callback(hObject, eventdata, handles) + +% Selection of a directory +[dirname]=uigetdir('*.*','Please select a save directory'); +handles.savedir = dirname; + +% If the user has indeed chosen a directory, we set it as the new save +% folder +if ~isequal(dirname,0) + set(handles.SaveFolderText,'String',handles.savedir); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Save folder changed',... + {handles.savedir},... + {'Save folder'}); +else + errordlg('Please select a directory !'); +end + +guidata(hObject,handles); + +% Upon clicking on the 'SAVE' button, the data will be saved entirely under +% a file name partly chosen by the user and partly depending on the present +% date and time +function SaveButton_Callback(hObject, eventdata, handles) + +% Upon pressing the save button, we want to save all the important +% information into a big matlab structure +SAVED = []; + +% General information on the project +SAVED.ProjectInfo.title = handles.project_title; +SAVED.ProjectInfo.date = date; + +% Name of the files that were loaded +SAVED.SubjData.SubjFileNames = handles.SubjNames; +SAVED.SubjData.MotFileNames = handles.MotName; + +% Dimension over time and voxels of the files analyzed +SAVED.SubjData.Dimensions.TP = handles.SubjSize.TP; +SAVED.SubjData.Dimensions.VOX = handles.SubjSize.VOX; + +% Number of subjects considered +SAVED.SubjData.n_subjects = handles.n_subjects; + +% TR of the experiment +SAVED.SubjData.TR = handles.TR; + +% Information about the NIFTI files used (dimensions, mapping between real +% world and index) +SAVED.BrainData.brain_info = handles.brain_info; + +% Mask that was used on the considered data +SAVED.BrainData.mask = handles.mask; + +% Seed used for the analysis +SAVED.BrainData.seed = handles.seed; + +% Motion threshold and activation threshold used in time points selection +SAVED.TPSelData.Tmot = handles.Tmot; +SAVED.TPSelData.T = handles.T; + +% Type of frame selection used +SAVED.TPSelData.SelMode = handles.SelMode; + +% Frames that were considered in the clustering process +% SAVED.TPSelData.Act = handles.Xonp; + +% Type of seed computation chosen ('Avg','Union','Intersection') +SAVED.TPSelData.SeedType = handles.SeedType; + +% Indices, for each seed used, of the retained frames across subjects +SAVED.TPSelData.idx_sep_seeds = handles.idx_sep_seeds; + +% Percentage frames retained for the clustering +SAVED.TPSelData.PercRetained = handles.RetainedPercentage; + +% Computed seed maps (average and subject-wise) +SAVED.SeedMap.AvgMap = handles.AvgSeedMap; + +% Parameters used for clustering +SAVED.ClusterData.N = handles.n_rep; +SAVED.ClusterData.K = handles.K; +SAVED.ClusterData.Pp = handles.Pp; +SAVED.ClusterData.Pn = handles.Pn; + +% CAP data +SAVED.ClusterData.CAPs = handles.CAP; +SAVED.ClusterData.StdCAPs = handles.STDCAP; +SAVED.ClusterData.idx = handles.idx; + +% Computed metrics +SAVED.Metrics.TPM = handles.TPM; +SAVED.Metrics.Counts = handles.Counts; +SAVED.Metrics.Number = handles.Number; +SAVED.Metrics.Avg_Duration = handles.Avg_Duration; +SAVED.Metrics.Duration = handles.Duration; +SAVED.Metrics.TM = handles.TM; +SAVED.Metrics.SeedFrac = handles.sfrac; + +%[tmp_date,tmp_date2] = strtok(date,'-'); +%[tmp_date2,tmp_date3] = strtok(tmp_date2,'-'); +%tmp_date3 = strtok(tmp_date3,'-'); + +% Name that will be given to the saved files +% fancy_name = [handles.project_title,'_',tmp_date,'_',tmp_date2,'_',tmp_date3,'_',... +% num2str(hour(now)),'_',num2str(minute(now)),'_',... +% num2str(round(second(now)))]; + +fancy_name = [handles.project_title]; + +% Saves NIFTI files storing the CAPs (already 'normalized'), in MNI space +CAPToNIFTI(handles.CAP./handles.STDCAP',... + handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.savedir,['CAP_NIFTI_',fancy_name]); + +% Saves the different variables from the program +save(fullfile(handles.savedir,fancy_name),'SAVED','-v7.3'); + +% Adds the save process to the log +handles.Log = CAP_AddToLog(handles.Log,'Data saved'); + +% Writes a log .txt file with what has been done so far +file_ID = fopen(fullfile(handles.savedir,[fancy_name,'.txt']),'wt'); + +for i = 1:length(handles.Log) + for j = 1:length(handles.Log{i}) + fprintf(file_ID,[handles.Log{i}{j},'\n']); + end + fprintf(file_ID,'\n'); +end + +fclose(file_ID); + +% Clears the structure now that it has been saved +clear SAVED + +guidata(hObject,handles); + + + +% This function is summoned when the user wishes to load previously +% computed data and pursue an analysis +function LoadButton_Callback(hObject, eventdata, handles) + +% We want to verify +[filename1,pathname1]=uigetfile({'*.*','All Files'},... + 'Select struct file to load...','MultiSelect','on'); + + % If the user has indeed entered a file + if ~isequal(filename1,0) || ~isequal(pathname1,0) + + Loaded_File = fullfile(pathname1, filename1); + tmp = load(Loaded_File); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); + end + + % Checks that the right fields do exist + try + if tmp.SubjData.n_subjects{1} > 3 &&... + ~isempty(tmp.BrainData.brain_info) &&... + sum(tmp.BrainData.mask{1}) == size(tmp.BrainData.seed,1) && ... + length(tmp.TPSelData.Act{1}) == tmp.SubjData.n_subjects{1} &&... + size(tmp.TPSelData.Act{1}{1},1) == sum(tmp.BrainData.mask{1}) + + handles.brain_info = tmp.BrainData.brain_info; + handles.Xonp = tmp.TPSelData.Act{1}; + handles.mask = tmp.BrainData.mask; + handles.SeedType = tmp.TPSelData.SeedType; + handles.idx_sep_seeds = tmp.TPSelData.idx_sep_seeds; + + % Makes the LOAD button green to indicate that loading worked + set(handles.LoadButton,'BackgroundColor',[59 113 86]/255); + + % Enables to go to the next step of the analysis and cluster the extracted + % frames + set(handles.ClusterButton,'Enable','on'); + set(handles.CCButton,'Enable','on'); + + set(handles.ClusterButton,'Visible','on'); + set(handles.CCButton,'Visible','on'); + + % Sets related displays to visible + set(handles.CAP_Kmax,'Visible','on'); + set(handles.CAP_PPC,'Visible','on'); + set(handles.CAP_N,'Visible','on'); + set(handles.CAP_K,'Visible','on'); + set(handles.CAP_PP,'Visible','on'); + set(handles.CAP_PN,'Visible','on'); + + set(handles.KRange_Edit,'Visible','on'); + set(handles.PCC_Edit,'Visible','on'); + set(handles.ClusterEdit,'Visible','on'); + set(handles.ClusterRepEdit,'Visible','on'); + set(handles.ClusterPpEdit,'Visible','on'); + set(handles.ClusterPnEdit,'Visible','on'); + end + catch + errordlg('Problem in loading the data...'); + end + +guidata(hObject,handles); + + + +% Executes when pressing on the 'CLEAR' button for data loading; supposed +% to set everything back to normal (when the window opened) +function handles = ClearDataButton_Callback(hObject, eventdata, handles) + + handles = ClearSection1(eventdata,handles); + handles = ClearSection2(eventdata,handles); + handles = ClearSection3(eventdata,handles); + handles = ClearSection4(eventdata,handles); + + % Loads and sets the brain underlay used for plotting purposes + Underlay = load_nii('Underlay.nii'); + Underlay_mat = [Underlay.hdr.hist.srow_x; Underlay.hdr.hist.srow_y; Underlay.hdr.hist.srow_z; 0 0 0 1]; + Underlay_dim = Underlay.hdr.dime.dim; + Underlay_dim = Underlay_dim(2:4); + handles.Underlay_info.dim = Underlay_dim; + handles.Underlay_info.mat = Underlay_mat; + clear Underlay + clear Underlay_dim + clear Underlay_mat + load('brain.mat'); + assignin('base','brain', brain); + handles.brain = brain; + clear brain + + handles.Log = CAP_AddToLog(handles.Log,'Data cleared'); + +guidata(hObject, handles); + + + +% Clears the content of section 1 only +function handles = ClearSection1(eventdata, handles) + +% Makes 'A. Load data' red again +set(handles.DataButton,'BackgroundColor',[204,146,146]/255); + +% Same for Save folder button +set(handles.SaveFolderButton,'BackgroundColor',[204,146,146]/255); + +% Resets the time point and voxel parameters +handles.SubjSize.TP = -inf; +handles.SubjSize.VOX = -inf; + +% Resets the TR +handles.TR = -inf; +handles.isTROK = false; + +% Resets the reference population +handles.ReferencePopulation = 1; + +handles = ProjectTitleText_CreateFcn(handles.ProjectTitleText,eventdata,handles); + +% Also resets the number of subjects variable and associated text +set(handles.Dimensionality_Text, 'String','_ frames x _ voxels (_)'); +handles.n_subjects = {}; + +% Resets the number of datasets entered to 0 +handles.n_datasets = 0; + +% Empties the data, motion, brain information and mask variables +handles.TC = {}; +handles.FD = {}; +handles.mask = {}; +handles.brain_info = {}; + +% Resets the text related to motion and data files +handles.SubjNames = {}; +handles.MotName = {}; + +% Resets the title and save folder information +handles.Log = {}; + +% Project title, by default 'Untitled' +handles.project_title = 'Untitled'; + +set(handles.LoadButton,'BackgroundColor',[51 75 163]/255); + +% Directory to which data is to be saved (initially loaded as ./SavedData) +handles.savedir = fullfile(pwd,'SavedData'); +set(handles.SaveFolderText,'String',handles.savedir); + +%%%%%%%%%% Putting the loading part (bottom) back to normal %%%%%%%%%%% + +% We also want to set the TR textbox back to its initial state +handles = TR_Entry_CreateFcn(handles.TR_Entry, eventdata, handles); + + + +% Clears the content of section 1 only +function handles = ClearSection2(eventdata, handles) + +% Puts back the seed buttons information to original state +handles.seed = []; +set(handles.SeedButton,'BackgroundColor',[204,146,146]/255); +set(handles.SeedButton,'Enable','off'); +set(handles.PlotSeedButton,'Enable','off'); +set(handles.PlotSeedButton,'Visible','off'); + +% Seed label entries set invisible +set(handles.S_SEED1,'Visible','off'); +set(handles.S_SEED2,'Visible','off'); +set(handles.S_SEED3,'Visible','off'); + +% Puts back the logical defining the type of seed information entered +handles.isSeedSubjectSpecific = 0; + +% Removes graph display for the seed +cla(handles.SeedGraphX); +cla(handles.SeedGraphZ); +set(handles.SeedGraphX,'Visible','off'); +set(handles.SeedGraphZ,'Visible','off'); +set(handles.SliderX,'Visible','off'); +set(handles.SliderZ,'Visible','off'); +set(handles.XCoordText,'Visible','off'); +set(handles.ZCoordText,'Visible','off'); + +%%%%%%%%%%%% Putting the seed map part back to normal %%%%%%%%%%%%%%%%%%% + +% Resets the variable containing the seed maps of the subjects +handles.AvgSeedMap = []; + +% Not clickable anymore +set(handles.SeedMapPushButton,'Enable','off'); +set(handles.SeedMapPushButton,'Visible','off'); + +% Resets colorbar display +handles = ResetGraphDisplay(handles.ColorbarSeed,handles); + +% Makes the slider and the text linked to slider of the seed map threshold +% back to invisible +set(handles.TSeed_Slider,'Visible','off'); +set(handles.TSeed,'Visible','off'); + +% Resets graphs with seed map plots +handles = ResetGraphDisplay(handles.SeedMapX,handles); +handles = ResetGraphDisplay(handles.SeedMapZ,handles); + +% Resets associated sliders +set(handles.SeedMapSliderX,'Visible','off'); +set(handles.SeedMapSliderZ,'Visible','off'); + +% Resets associated slider texts +set(handles.SeedMap_SliderX,'Visible','off'); +set(handles.SeedMap_SliderZ,'Visible','off'); + +% Resets the circles plot +handles = ResetGraphDisplay(handles.FancyCircles,handles); + +% Sets the associated text back to invisible +set(handles.SeedPlusText,'Visible','off'); +set(handles.SeedMinusText,'Visible','off'); +set(handles.Seed1Text,'Visible','off'); +set(handles.Seed2Text,'Visible','off'); +set(handles.Seed3Text,'Visible','off'); + +% Puts the seed boxes back to not visible +set(handles.CheckS1POS,'Visible','off'); +set(handles.CheckS2POS,'Visible','off'); +set(handles.CheckS3POS,'Visible','off'); +set(handles.CheckS1NEG,'Visible','off'); +set(handles.CheckS2NEG,'Visible','off'); +set(handles.CheckS3NEG,'Visible','off'); + +set(handles.TPSelectionButton,'Enable','off'); +set(handles.TPSelectionButton,'Visible','off'); + +set(handles.PRadio,'Visible','off'); +set(handles.TRadio,'Visible','off'); +set(handles.uibuttongroup7,'Visible','off'); +set(handles.TText,'Visible','off'); +set(handles.TMotText,'Visible','off'); +set(handles.TEdit,'Visible','off'); +set(handles.TMotEdit,'Visible','off'); + +% Resets the frame selection mode +handles.SelMode = 'Threshold'; + +% Invisible Seed type list +set(handles.SeedPopup,'Visible','off'); + +% Reinitializes motion and the motion box +handles.Tmot = 0.5; +handles = TMotEdit_CreateFcn(handles.TMotEdit,eventdata,handles); + +% Reinitializes frame selection threshold and the linked box +handles.T = 0.5; +handles = TEdit_CreateFcn(handles.TEdit,eventdata,handles); + +% Resets the frame and percentage retention variables +handles.Xonp = {}; +handles.Xonn = {}; +handles.RetainedPercentage = {}; +handles.FrameIndices = {}; + +% Resets the variables indexing seed selection time points retained +handles.idx_sep_seeds = {}; +handles.sfrac = []; + +% Resets the violin plot with percentage retained frames +handles = ResetGraphDisplay(handles.TPViolin,handles); + + + +% Clears the content of section 3 only +function handles = ClearSection3(eventdata, handles) + +set(handles.ClusterButton,'Enable','off'); +set(handles.CCButton,'Enable','off'); +set(handles.AssignButton,'Enable','off'); +set(handles.AssignButton,'Visible','off'); + +set(handles.CCPlot,'Visible','off'); +cla(handles.CCPlot); + +set(handles.CAP_TP,'Visible','off'); +set(handles.Percentile_Edit,'Visible','off'); + +set(handles.CAP_Kmax,'Visible','off'); +set(handles.CAP_PPC,'Visible','off'); +set(handles.CAP_N,'Visible','off'); +set(handles.CAP_K,'Visible','off'); +set(handles.CAP_PP,'Visible','off'); +set(handles.CAP_PN,'Visible','off'); + +set(handles.KRange_Edit,'Visible','off'); +set(handles.PCC_Edit,'Visible','off'); +set(handles.ClusterEdit,'Visible','off'); +set(handles.ClusterRepEdit,'Visible','off'); +set(handles.ClusterPpEdit,'Visible','off'); +set(handles.ClusterPnEdit,'Visible','off'); + +% Resets the consensus clustering parameter input boxes +handles = KRange_Edit_CreateFcn(handles.KRange_Edit,eventdata,handles); +handles = PCC_Edit_CreateFcn(handles.PCC_Edit,eventdata,handles); + +% Resets the parameter input boxes +handles = ClusterEdit_CreateFcn(handles.ClusterEdit,eventdata,handles); +handles = ClusterRepEdit_CreateFcn(handles.ClusterRepEdit,eventdata,handles); +handles = ClusterPpEdit_CreateFcn(handles.ClusterPpEdit,eventdata,handles); +handles = ClusterPnEdit_CreateFcn(handles.ClusterPnEdit,eventdata,handles); +handles = Percentile_Edit_CreateFcn(handles.Percentile_Edit,eventdata,handles); + +% Resets the consensus clustering parameters themselves +handles.Kmax = 12; +handles.PCC = 80; + +set(handles.CCButton,'Visible','off'); +set(handles.ClusterButton,'Visible','off'); + +set(handles.PIE_S1,'Visible','off'); +set(handles.PIE_S2,'Visible','off'); +set(handles.PIE_S3,'Visible','off'); +set(handles.PIE_S1S2,'Visible','off'); +set(handles.PIE_S2S3,'Visible','off'); +set(handles.PIE_S1S3,'Visible','off'); +set(handles.PIE_S1S2S3,'Visible','off'); + +% Resets the parameters themselves +handles.K = 5; +handles.n_rep = 20; +handles.Pp = 100; +handles.Pn = 100; +handles.percentile = 5; + +% Resets the CAP parameters (CAPs, standard deviation within CAPs and +% indices of the CAPs to which all retained frames were assigned) +handles.CAP = []; +handles.STDCAP = []; +handles.idx = {}; + +% Resets the graph display of the CAP colorbar +handles = ResetGraphDisplay(handles.ColorbarCAP,handles); + +% Reset all graph displays for the CAPs +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z}; +tmpF = {handles.CAP1_Frames,handles.CAP2_Frames,handles.CAP3_Frames,handles.CAP4_Frames,handles.CAP5_Frames}; + + +for i_CAP = 1:5 + set(tmpF{i_CAP},'Visible','off'); + handles = ResetGraphDisplay(tmpX{i_CAP},handles); + handles = ResetGraphDisplay(tmpY{i_CAP},handles); + handles = ResetGraphDisplay(tmpZ{i_CAP},handles); +end + +% Resets the sliders and the textboxes for the CAPs +set(handles.CAP_SliderX,'Visible','off'); +set(handles.CAP_SliderY,'Visible','off'); +set(handles.CAP_SliderZ,'Visible','off'); +set(handles.CAP_XC,'Visible','off'); +set(handles.CAP_YC,'Visible','off'); +set(handles.CAP_ZC,'Visible','off'); + +% Resets the slider and textbox for the CAPs visualization threshold +set(handles.TVIS_Slider,'Visible','off'); +set(handles.TVIS,'Visible','off'); + +% Resets the pie charts +handles = ResetGraphDisplay(handles.pie1,handles); +handles = ResetGraphDisplay(handles.pie2,handles); +handles = ResetGraphDisplay(handles.pie3,handles); +handles = ResetGraphDisplay(handles.pie4,handles); +handles = ResetGraphDisplay(handles.pie5,handles); + +handles = ResetGraphDisplay(handles.ColorbarSimMat,handles); +handles = ResetGraphDisplay(handles.CAP_Mat,handles); + + + +% Clears the content of section 4 only +function handles = ClearSection4(eventdata, handles) + +set(handles.MetricsButton,'Enable','off'); +set(handles.MetricsButton,'Visible','off'); + +% Resets the metrics variables +handles.TPM = {}; +handles.Counts = {}; +handles.Number = {}; +handles.Avg_Duration = {}; +handles.Duration = {}; +handles.TM = {}; +handles.TPMCum = {}; + +% Set the sliding lists of subjects invisible again +set(handles.SubjectMenuMetrics,'Visible','off'); +set(handles.StateMenu,'Visible','off'); + +% Resets the colorbars from the metrics part +handles = ResetGraphDisplay(handles.ColorbarTransMat,handles); + +% Resets all the graphs from the metrics part +handles = ResetGraphDisplay(handles.TMGraph,handles); +handles = ResetGraphDisplay(handles.TM_Subject,handles); +handles = ResetGraphDisplay(handles.DynStates,handles); +handles = ResetGraphDisplay(handles.CumStates,handles); +handles = ResetGraphDisplay(handles.ViolinCounts,handles); +handles = ResetGraphDisplay(handles.ViolinCountsFrac,handles); +handles = ResetGraphDisplay(handles.ViolinNumber,handles); +handles = ResetGraphDisplay(handles.ViolinDuration,handles); + +% Removes all the labels linked to Metrics displays +set(handles.DS_Scrubbed,'Visible','off'); +set(handles.DS_NotSelected,'Visible','off'); +set(handles.DS_Unassigned,'Visible','off'); + +tmp = {handles.DS_CAP1,handles.DS_CAP2,handles.DS_CAP3,handles.DS_CAP4,... + handles.DS_CAP5,handles.DS_CAP6,handles.DS_CAP7,handles.DS_CAP8,... + handles.DS_CAP9,handles.DS_CAP10,handles.DS_CAP11,handles.DS_CAP12}; + +for i = 1:length(tmp) + set(tmp{i},'Visible','off'); +end + +clear tmp + +tmp = {handles.V_POP1,handles.V_POP2,handles.V_POP3,handles.V_POP4}; + +for i = 1:length(tmp) + set(tmp{i},'Visible','off'); +end + +clear tmp + + +% The following functions enable to modify the text of the project title +function ProjectTitleText_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + +function handles = ProjectTitleText_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function ProjectTitleText_Callback(hObject, eventdata, handles) + +% If we have entered a valid string, then we name the project as such +if ~isempty((get(hObject,'String'))) + handles.project_title = get(hObject,'String'); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid project title entered',{handles.project_title},{'New project title'}); + +% If we haven't entered anything, the project is just named 'untitled' +else + handles.project_title = 'Untitled'; + set(hObject,'BackgroundColor',[204,146,146]/255); +end + +guidata(hObject, handles); + + + + + +%% Consensus clustering functions to compute optimal K +% The functions below enable to determine the optimal values of cluster +% number into which to disentangle the CAPs data + +% Executes when pressing on the button to run consensus clustering +function CCButton_Callback(hObject, eventdata, handles) + + % Computes the consensus results + [Consensus] = CAP_ConsensusClustering(handles.Xonp{handles.ReferencePopulation},2:handles.Kmax,'items',handles.PCC/100,handles.n_rep,'correlation'); + + % Calculates the quality metrics + [~,Lorena] = ComputeClusteringQuality(Consensus,2:handles.Kmax); + + set(handles.CCPlot,'Visible','on'); + tmp_plot = bar(2:handles.Kmax,1-Lorena,'Parent',handles.CCPlot); + xlabel(get(tmp_plot(1),'Parent'),'Cluster number K'); + ylabel(get(tmp_plot(1),'Parent'),'Stability'); + xlim(get(tmp_plot(1),'Parent'),[2-0.6,handles.Kmax+0.6]); + ylim(get(tmp_plot(1),'Parent'),[0,1]); + custom_cm = cbrewer('seq','Reds',25); + colormap(handles.CCPlot,custom_cm(6:25,:)); + +guidata(hObject,handles); + + + +function KRange_Edit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 1) && (str2double(get(hObject,'String')) <= 12) + handles.Kmax = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid number of Kmax chosen',{handles.K},{'Max cluster number'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.Kmax = 12; +end + +guidata(hObject, handles); + + + +function handles = KRange_Edit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function KRange_Edit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + +function PCC_Edit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 50) && (str2double(get(hObject,'String')) <= 100) + handles.PCC = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentage of items chosen',{handles.K},{'Percentage items to cluster'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.PCC = 12; +end + +guidata(hObject, handles); + + + +function handles = PCC_Edit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function PCC_Edit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + + + +%% Editing of all the CAP generation parameters + +% Number of clusters +function ClusterEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 1) && (str2double(get(hObject,'String')) <= 12) + handles.K = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid number of clusters chosen',{handles.K},{'Number of clusters'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.K = 5; +end + +guidata(hObject, handles); + + + +function handles = ClusterEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function ClusterEdit_ButtonDownFcn(hObject,eventdata,handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + +% Number of k-means repetitions +function ClusterRepEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 50) + handles.n_rep = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid number of replicates chosen',{handles.n_rep},{'Number of replicates'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.n_rep = 20; +end + +guidata(hObject, handles); + + + +function handles = ClusterRepEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function ClusterRepEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + +% Percentage of positive-valued voxels to keep +function ClusterPpEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.Pp = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentage positive voxels chosen',{handles.Pp},{'Percentage positive voxels'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.Pp = 20; +end + +guidata(hObject, handles); + + + +function handles = ClusterPpEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function ClusterPpEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + +% Percentage of negative-valued voxels to keep +function ClusterPnEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.Pn = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentage negative voxels chosen',{handles.Pn},{'Percentage negative voxels'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.Pn = 20; +end + +guidata(hObject, handles); + + + +function handles = ClusterPnEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + + + +function ClusterPnEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + + + + + +%% Clustering control +% When pressing on the 'Cluster' button, we want to run clustering for the +% specified mode (Activation frames, Deactivation frames, or both types of +% frames together), using the previously declared parameters + +% Upon clicking on 'Cluster' +function ClusterButton_Callback(hObject, eventdata, handles) + +% We perform clustering +[handles.CAP,~,handles.STDCAP,handles.idx{handles.ReferencePopulation},... + handles.CorrDist,handles.sfrac] = Run_Clustering(cell2mat(handles.Xonp{handles.ReferencePopulation}),... + handles.K,handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn,handles.n_rep,handles.idx_sep_seeds{handles.ReferencePopulation},handles.SeedType); + +% Makes the sliders visible, and the related text too (CAP MNI coordinates) +set(handles.CAP_SliderX,'Visible','on'); +set(handles.CAP_SliderY,'Visible','on'); +set(handles.CAP_SliderZ,'Visible','on'); +set(handles.CAP_XC,'Visible','on'); +set(handles.CAP_YC,'Visible','on'); +set(handles.CAP_ZC,'Visible','on'); +set(handles.CAP_XC,'String',['X: ',sprintf('%.2f',get(handles.CAP_SliderX,'Value'))]); +set(handles.CAP_YC,'String',['Y: ',sprintf('%.2f',get(handles.CAP_SliderY,'Value'))]); +set(handles.CAP_ZC,'String',['Z: ',sprintf('%.2f',get(handles.CAP_SliderZ,'Value'))]); + +% Computation of the similarity +SimMat = corr(handles.CAP',handles.CAP'); +SimMat(isnan(SimMat))=0; + +% Graph set visible, and plotting +handles = ResetGraphDisplay(handles.CAP_Mat,handles); +set(handles.CAP_Mat,'Visible','on'); +imagesc(SimMat,'Parent',handles.CAP_Mat); + +tmp_cb2 = cbrewer('div','RdBu',1000); + +colormap(handles.CAP_Mat,flipud(tmp_cb2)); + +% Correlation ranges from -1 to 1, so this is what we make the graph +% colorbar vary within. We also make the graph square and remove the axes +caxis(handles.CAP_Mat,[-1 1]); +axis(handles.CAP_Mat,'square','on'); +axis(handles.CAP_Mat,'off'); + +% Addition of the colorbar just below +set(handles.ColorbarSimMat,'Visible','on'); +handles.ColorbarSimMat = Create_CAP_colorbar(-1,1,0.5,0,'',... + handles.ColorbarSimMat,'Vertical','div','RdBu',1000); + +% If using the 'Intersection' option... +if strcmp(handles.SeedType,'Intersection') + + % Custom colormap + custom_cm = 1/255*[211,36,36;11,170,65;51,75,163;255,255,180;58,221,221;186,59,204;242,242,242]; + + % Graph displays are stored in a common tmp_sfrac cell array + tmp_sfrac = {handles.pie1,handles.pie2,handles.pie3,handles.pie4,... + handles.pie5}; + + % The pie charts for each cluster are created + for cc = 1:min([handles.K,5]) + + % Pie charts + set(tmp_sfrac{cc},'Visible','on'); + for tt = 1:size(handles.sfrac,3) + lab{tt} = ''; + end + + pie(tmp_sfrac{cc},realmin*ones(size(handles.sfrac,3),1)+squeeze(mean(handles.sfrac(:,cc,:),1)),lab); + + switch handles.n_seed + case 1 + errordlg('You managed the impossible, congratulations!'); + case 2 + colormap(tmp_sfrac{cc},flipud(custom_cm)); + set(handles.PIE_S1,'Visible','on'); + set(handles.PIE_S2,'Visible','on'); + set(handles.PIE_S3,'Visible','on'); + case 3 + colormap(tmp_sfrac{cc},flipud(custom_cm)); + set(handles.PIE_S1,'Visible','on'); + set(handles.PIE_S2,'Visible','on'); + set(handles.PIE_S3,'Visible','on'); + set(handles.PIE_S2S3,'Visible','on'); + set(handles.PIE_S1S2,'Visible','on'); + set(handles.PIE_S1S3,'Visible','on'); + set(handles.PIE_S1S2S3,'Visible','on'); + end + end +end + +% Same for the slider for the visualization threshold +set(handles.TVIS,'Visible','on'); +set(handles.TVIS_Slider,'Visible','on'); +set(handles.TVIS,'String',['Tv: ',sprintf('%.2f',get(handles.TVIS_Slider,'Value'))]); + +% Makes the colorbar for the CAPs visible +handles.ColorbarCAP = Create_CAP_colorbar(-1.5,1.5,0.5,get(handles.TVIS_Slider,'Value'),'',handles.ColorbarCAP,'Horizontal','div','RdBu',1000); +set(handles.ColorbarCAP,'Visible','on'); + +% Concatenates all CAP information into metavariables for easier subsequent +% changes +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z}; +tmpF = {handles.CAP1_Frames,handles.CAP2_Frames,handles.CAP3_Frames,handles.CAP4_Frames,handles.CAP5_Frames}; + +% For each CAP... +for i_CAP = 1:min([handles.K,5]) + + % Clears the display for each dimension + cla(tmpX{i_CAP}); + cla(tmpY{i_CAP}); + cla(tmpZ{i_CAP}); + + % Plots the new slice for each dimension + tmpX{i_CAP} = plot_slice(handles.CAP(i_CAP,:),... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'X',get(handles.CAP_SliderX,'Value'),tmpX{i_CAP}); + + tmpY{i_CAP} = plot_slice(handles.CAP(i_CAP,:),... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'Y',get(handles.CAP_SliderY,'Value'),tmpY{i_CAP}); + + tmpZ{i_CAP} = plot_slice(handles.CAP(i_CAP,:),... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'Z',get(handles.CAP_SliderZ,'Value'),tmpZ{i_CAP}); + + % Sets the frame percentage text visible and at the right value (number + % of frames from a CAP/total frame number, and then percentage that it + % stands for) + set(tmpF{i_CAP},'Visible','on'); + set(tmpF{i_CAP},'String',{[num2str(sum(handles.idx{handles.ReferencePopulation}==i_CAP)),'/',... + num2str(size(handles.idx{handles.ReferencePopulation},1))],[sprintf('%.2f',... + sum(handles.idx{handles.ReferencePopulation}==i_CAP)/size(handles.idx{handles.ReferencePopulation},1)*100),' [%]']}); +end + +% Fills that subject menu with the subjects from the reference population +handles = FillSubjectList(handles.SubjectMenuMetrics,handles); + +% Also enables and fills the state menu +handles = FillStateList(handles.StateMenu,handles); + +% Enables the Metrics button for the next part of the analysis if we +% only deal with one dataset +if handles.n_datasets == 1 + set(handles.MetricsButton,'Enable','on'); + set(handles.MetricsButton,'Visible','on'); + +% Else, we enable the assignment before enabling the metrics computation +elseif handles.n_datasets > 1 + set(handles.AssignButton,'Enable','on'); + set(handles.AssignButton,'Visible','on'); + + set(handles.CAP_TP,'Visible','on'); + set(handles.Percentile_Edit,'Visible','on'); +end + +handles.Log = CAP_AddToLog(handles.Log,'Clustering performed',... + {handles.ReferencePopulation,handles.K,handles.n_rep,handles.Pp,... + handles.Pn},{'Reference group index',... + 'Number of clusters','Number of replicates',... + 'Percentage positive voxels','Percentage negative voxels'}); + +guidata(hObject, handles); + + + + + +%% Frame assignment control +% This button is only enabled after clustering has been performed on the +% reference population. It assigns frames from the other populations to the +% computed CAPs + +% Happens upon clicking on the 'Assign' buttons +function AssignButton_Callback(hObject, eventdata, handles) + +tmp_notref = []; +tmp_computedTPsel = []; + +% For each non-reference dataset... +for n_ds = 1:handles.n_datasets + if n_ds ~= handles.ReferencePopulation + + tmp_notref = [tmp_notref,n_ds]; + + % Attempts to access the frames for a given dataset; if it fails, it + % means we must compute activity. If it works, we do nothing because + % activity has already been computed + try + justtotest = handles.Xonp{n_ds}; + catch + + [handles.Xonp{n_ds},handles.Xonn{n_ds},p,handles.FrameIndices{n_ds},handles.idx_sep_seeds{n_ds}] = ... + CAP_find_activity(handles.TC{n_ds},handles.seed,handles.T,handles.FD{n_ds},handles.Tmot,handles.SelMode,handles.SeedType); + + handles.RetainedPercentage{n_ds} = p(4:5,:); + + tmp_computedTPsel = [tmp_computedTPsel,n_ds]; + end + + try + handles.idx{n_ds} = CAP_AssignFrames(handles.CAP,cell2mat(handles.Xonp{n_ds}),handles.CorrDist,handles.percentile)'; + catch + errordlg('You computed CAPs with a different CAP type compared to the one used now; please use the same CAP type !'); + end + end +end + +% We then enable the computation of metrics +set(handles.MetricsButton,'Enable','on'); +set(handles.MetricsButton,'Visible','on'); + +handles.Log = CAP_AddToLog(handles.Log,'Frame assignment performed',... + {handles.ReferencePopulation,num2str(tmp_computedTPsel),... + num2str(tmp_notref)},{'Reference group index','Group indices for which frames were computed',... + 'Group indices for which frames were assigned'}); + +guidata(hObject, handles); + + + + + +%% Parameter control: percentile to use for frame assignment +% This asks for the percentile to use in frame assignment (i.e. the +% threshold of correlation below which frames are left unassigned) + +function Percentile_Edit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.percentile = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [101,140,196]/255); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentile chosen',{handles.percentile},{'Percentile'}); + +else + set(hObject,'BackgroundColor',[204,146,146]/255); + handles.percentile = 5; +end + +guidata(hObject, handles); + + + +function handles = Percentile_Edit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + +function Percentile_Edit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject, handles); + + + + + +%% Sliders for CAP visualization (MNI coordinates) +% When changing along a slider, we want to update the graphs and the text of +% the MNI coordinate below the slider + +% X dimension slider +function CAP_SliderX_Callback(hObject, eventdata, handles) + +set(handles.CAP_XC,'String',['X: ',sprintf('%.2f',get(hObject,'Value'))]); +tmp_struct = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X}; + +for i_CAP = 1:min([handles.K,5]) + cla(tmp_struct{i_CAP}); + tmp_struct{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(handles.TVIS_Slider,'Value'),... + 1.5,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),tmp_struct{i_CAP}); +end + +guidata(hObject, handles); + + + +function CAP_SliderX_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +guidata(hObject,handles); + + + +% Y dimension slider +function CAP_SliderY_Callback(hObject, eventdata, handles) + +set(handles.CAP_YC,'String',['Y: ',sprintf('%.2f',get(hObject,'Value'))]); +tmp_struct = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y}; + +for i_CAP = 1:min([handles.K,5]) + cla(tmp_struct{i_CAP}); + tmp_struct{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(handles.TVIS_Slider,'Value'),... + 1.5,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Y',get(hObject,'Value'),tmp_struct{i_CAP}); +end + +guidata(hObject,handles); + + + +function CAP_SliderY_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +guidata(hObject,handles); + + + +% Z dimension slider +function CAP_SliderZ_Callback(hObject, eventdata, handles) + +set(handles.CAP_ZC,'String',['Z: ',sprintf('%.2f',get(hObject,'Value'))]); +tmp_struct = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z}; + +for i_CAP = 1:min([handles.K,5]) + + cla(tmp_struct{i_CAP}); + tmp_struct{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(handles.TVIS_Slider,'Value'),... + 1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),tmp_struct{i_CAP}); +end + +guidata(hObject,handles); + + + +function CAP_SliderZ_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +guidata(hObject,handles); + + + + + +%% Sliders for threshold visualization (CAP analysis) +% Again, we want to update the slices and the text if we change those +% sliders + +function TVIS_Slider_Callback(hObject, eventdata, handles) + +% The text is changed +set(handles.TVIS,'String',['Tv: ',sprintf('%.2f',get(hObject,'Value'))]); + +% The colorbar graph is modified to suit the new threshold value +cla(handles.ColorbarCAP); +handles.ColorbarCAP = Create_CAP_colorbar(-1.5,1.5,0.5,get(hObject,'Value'),'',handles.ColorbarCAP,'Horizontal','div','RdBu',1000); + +% The brain slices are replotted +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z}; + +for i_CAP = 1:min([handles.K,5]) + + cla(tmpX{i_CAP}); + cla(tmpY{i_CAP}); + cla(tmpZ{i_CAP}); + + tmpX{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(hObject,'Value'),1.5,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(handles.CAP_SliderX,'Value'),tmpX{i_CAP}); + + tmpY{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(hObject,'Value'),1.5,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Y',get(handles.CAP_SliderY,'Value'),tmpY{i_CAP}); + + tmpZ{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(hObject,'Value'),1.5,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',get(handles.CAP_SliderZ,'Value'),tmpZ{i_CAP}); +end + +guidata(hObject,handles); + + + +function TVIS_Slider_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + + + + +%% Metrics computation control +% When pressing on the 'Compute metrics' button, the different metrics for +% CAP analysis are computed, including: +% - Similarity between CAPs +% - Transition probabilities from a state to the other (average + subject) +% - Sequence of states for each subject +% - Cumulative state sequence for all subjects +% - Counts (number of frames in a state) +% - Number of times entering a state, and duration spent in a state + +function MetricsButton_Callback(hObject, eventdata, handles) + +% All the metrics are computed for all the datasets +for n_ds = 1:handles.n_datasets + if n_ds == handles.ReferencePopulation + tmp_nclust = handles.K; + else + tmp_nclust = handles.K+1; + end + + try + [handles.TPM{n_ds},handles.Counts{n_ds},... + handles.Number{n_ds},handles.Avg_Duration{n_ds},... + handles.Duration{n_ds},handles.TM{n_ds}] =... + Compute_Metrics(handles.idx{n_ds},handles.FrameIndices{n_ds}.kept.active,... + handles.FrameIndices{n_ds}.scrubbed,... + tmp_nclust,handles.TR); + catch + + errordlg('You tried computing metrics using parameter values different from the ones that were employed to generate CAPs; please check !'); + end +end + +handles.Log = CAP_AddToLog(handles.Log,'Metrics computed',... + {handles.n_datasets,handles.K,handles.TR},... + {'Number of datasets','Number of clusters','TR'}); + +tmp_cb = cbrewer('seq','Greys',1000); + +% 2. Transition matrix for all subjects together + +tmp_toplot = squeeze(mean(handles.TM{handles.ReferencePopulation},3)); +tmp_toplot = tmp_toplot(3:end,3:end); + +% Make graph visible and plotting +handles = ResetGraphDisplay(handles.TMGraph,handles); +set(handles.TMGraph,'Visible','on'); +imagesc(tmp_toplot,'Parent',handles.TMGraph); + +colormap(handles.TMGraph,flipud(tmp_cb)); + +clear tmp_toplot + +% Arbitrary setting of probability scale from 0 to 0.03 +caxis(handles.TMGraph,[0 0.03]); +axis(handles.TMGraph,'square','on'); +axis(handles.TMGraph,'off'); + +% 3. Transition matrix for one subject + +tmp_toplot = squeeze(handles.TM{handles.ReferencePopulation}(:,:,get(handles.SubjectMenuMetrics,'Value'))); +tmp_toplot = tmp_toplot(3:end,3:end); + +% makes graph visible and plots the information given by the Subject popup +handles = ResetGraphDisplay(handles.TM_Subject,handles); +set(handles.TM_Subject,'Visible','on'); +imagesc(tmp_toplot,... + 'Parent',handles.TM_Subject); + +colormap(handles.TM_Subject,flipud(tmp_cb)); + +clear tmp_toplot + +% Same setting for the axes as for the average graph +caxis(handles.TM_Subject,[0 0.03]); +axis(handles.TM_Subject,'square','on'); +axis(handles.TM_Subject,'off'); + +% We then create the colorbar for both cases +set(handles.ColorbarTransMat,'Visible','on'); +handles.ColorbarTransMat = Create_CAP_colorbar(0,0.03,0.01,0,'',... + handles.ColorbarTransMat,'Vertical','seq','Greys',1000); + +% Makes the subject menu visible +set(handles.SubjectMenuMetrics,'Visible','on'); + +% 4. Dynamic state plotting + +% Makes the graph visible +handles = ResetGraphDisplay(handles.DynStates,handles); +set(handles.DynStates,'Visible','on'); + +% Concatenates information from the different datasets +tmp_toplot = []; + +for i = 1:handles.n_datasets + tmp_toplot = [tmp_toplot; handles.TPM{i}; 0*ones(5,handles.SubjSize.TP)]; +end +tmp_toplot = tmp_toplot(1:end-5,:); + +custom_cm = cbrewer('qual','Set1',handles.K+1); +custom_cm = [0.05,0.05,0.05;1,1,1;custom_cm]; + +% If the TR has been properly entered, the x-axis is time; else, it depicts +% time index. In any case, we plot the states +if handles.isTROK + imagesc(tmp_toplot,'Parent',handles.DynStates); + colormap(handles.DynStates,(custom_cm)); + xlabel(handles.DynStates,'Time [s]'); +else + imagesc(tmp_toplot,'Parent',handles.DynStates); + colormap(handles.DynStates,(custom_cm)); + xlabel(handles.DynStates,'Time index [-]'); +end + +ylabel(handles.DynStates,'Subjects [-]'); +axis(handles.DynStates,'off'); +caxis(handles.DynStates,[-1,handles.K+1]); + +clear tmp_toplot + +% 5. Cumulative state distributions + +% Makes the graph visible +handles = ResetGraphDisplay(handles.CumStates,handles); +set(handles.CumStates,'Visible','on'); + +for i = 1:handles.n_datasets + % Cumulative distribution for the state that we want to be displayed (i.e. + % the state from the popup menu) + handles.TPMCum{i} = cumsum(handles.TPM{i} == get(handles.StateMenu,'Value'),2); + + % Average of the considered state across subjects + tmp_TPMCum{i} = mean(handles.TPMCum{i},1); +end + +% Similarly as above, we plot time if we have a valid TR; else, we plot +% 'time index' +if handles.isTROK == false + + for i = 1:handles.n_datasets + % We first plot each subject curve + for j = 1:size(handles.TPMCum{i},1) + plot(1:size(handles.TPM{i},2),handles.TPMCum{i}(j,:),'Color',handles.PopColor{1}(i,:),... + 'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + % Then, we plot a bold average across subjects + plot(1:size(handles.TPM{i},2),tmp_TPMCum{i},'Color',handles.PopColor{2}(i,:),... + 'LineWidth',2,'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time index [-]','FontSize',10); + xlim(handles.CumStates,[1,size(handles.TPM{i},2)]); + end +else + for i = 1:handles.n_datasets + for j = 1:size(handles.TPMCum{i},1) + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + handles.TPMCum{i}(j,:),... + 'Color',handles.PopColor{1}(i,:),'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + tmp_TPMCum{i},... + 'LineWidth',2,'Color',handles.PopColor{2}(i,:),'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time [s]','FontSize',10); + xlim(handles.CumStates,[0,(size(handles.TPM{i},2)-1)*handles.TR]); + end +end + + +ylabel(handles.CumStates,'Cumul. sum [-]','FontSize',10); +set(handles.CumStates,'Box','off'); + +% Makes the state menu visible +set(handles.StateMenu,'Visible','on'); + +% 6. Violin plots +% Below, we plot violin plots depicting: +% - Raw counts of state excursions +% - Fractional counts of state excursions +% - Number of times entering a state +% - Duration of state excursions + +% We build the legend used to plot the violins +% leg_viol = cell(handles.K); +for i = 1:handles.K + leg_viol{i} = num2str(i); +end + +% Makes graphs ready +handles = ResetGraphDisplay(handles.ViolinCounts,handles); +set(handles.ViolinCounts,'Visible','on'); + +handles = ResetGraphDisplay(handles.ViolinCountsFrac,handles); +set(handles.ViolinCountsFrac,'Visible','on'); + +handles = ResetGraphDisplay(handles.ViolinNumber,handles); +set(handles.ViolinNumber,'Visible','on'); + +handles = ResetGraphDisplay(handles.ViolinDuration,handles); +set(handles.ViolinDuration,'Visible','on'); + +% Concatenates the values from the different populations +tmp_toplot = ConcatMat(handles.Counts,handles.n_datasets,handles.K,handles.n_subjects,'Raw counts'); + +% Plots the raw count values +[~,~,handles.ViolinCounts] = MakeViolin(tmp_toplot,... + handles.ViolinCounts,leg_viol,'Raw counts [-]',handles.PopColor,handles.n_datasets,handles.K); + +clear tmp_toplot + +tmp_toplot = ConcatMat(handles.Counts,handles.n_datasets,handles.K,handles.n_subjects,'Normalized counts'); + +% Plots the normalized count values +[~,~,handles.ViolinCountsFrac] = MakeViolin(tmp_toplot,... + handles.ViolinCountsFrac,leg_viol,'Norm counts [-]',handles.PopColor,handles.n_datasets,handles.K); + +clear tmp_toplot + +tmp_toplot = ConcatMat(handles.Number,handles.n_datasets,handles.K,handles.n_subjects,'Number'); + +% Plots the number of times a state is entered +[~,~,handles.ViolinNumber] = MakeViolin(tmp_toplot,... + handles.ViolinNumber,leg_viol,'Number [-]',handles.PopColor,handles.n_datasets,handles.K); + +clear tmp_toplot + +% Plots the duration graph +if handles.isTROK + + tmp_toplot = ConcatMat(handles.Avg_Duration,handles.n_datasets,handles.K,handles.n_subjects,'Duration'); + + [~,~,handles.ViolinDuration] = MakeViolin(tmp_toplot,... + handles.ViolinDuration,leg_viol,'Dur. [s]',handles.PopColor,handles.n_datasets,handles.K); + + clear tmp_toplot + +else + errordlg('Not showing duration violin plot as TR was not entered !'); + set(handles.ViolinDuration,'Visible','off'); +end + +% Makes the displays visible +set(handles.DS_Scrubbed,'Visible','on'); +set(handles.DS_Scrubbed,'ForegroundColor',custom_cm(1,:)); + +set(handles.DS_NotSelected,'Visible','on'); +set(handles.DS_NotSelected,'ForegroundColor',[0.9,0.9,0.9]); + +set(handles.DS_Unassigned,'Visible','on'); +set(handles.DS_Unassigned,'ForegroundColor',custom_cm(handles.K+3,:)); + +tmp = {handles.DS_CAP1,handles.DS_CAP2,handles.DS_CAP3,handles.DS_CAP4,... + handles.DS_CAP5,handles.DS_CAP6,handles.DS_CAP7,handles.DS_CAP8,... + handles.DS_CAP9,handles.DS_CAP10,handles.DS_CAP11,handles.DS_CAP12}; + +for i = 1:handles.K + set(tmp{i},'Visible','on'); + set(tmp{i},'ForegroundColor',custom_cm(2+i,:)); +end + +clear tmp + +tmp = {handles.V_POP1,handles.V_POP2,handles.V_POP3,handles.V_POP4}; + +for i = 1:handles.n_datasets + set(tmp{i},'Visible','on'); +end + +clear tmp + +guidata(hObject,handles); + + + + + +%% Subject popup menu control (metrics computation) +% When a new subject is chosen, the display of the transition matrix graph +% is changed + +function SubjectMenuMetrics_Callback(hObject, eventdata, handles) + +% In the case when we have something to plot... +try + % ... we reset the graph display, make the graph visible, and plot + % again + handles = ResetGraphDisplay(handles.TM_Subject,handles); + set(handles.TM_Subject,'Visible','on'); + + tmp_toplot = squeeze(handles.TM{handles.ReferencePopulation}(:,:,get(handles.SubjectMenuMetrics,'Value'))); + tmp_toplot = tmp_toplot(3:end,3:end); + + imagesc(tmp_toplot,'Parent',handles.TM_Subject); + caxis(handles.TM_Subject,[0 0.03]); + axis(handles.TM_Subject,'square','on'); + axis(handles.TM_Subject,'off'); + + tmp_cb = cbrewer('seq','Greys',1000); + colormap(handles.TM_Subject,flipud(tmp_cb)); + + clear tmp_toplot + + handles.Log = CAP_AddToLog(handles.Log,'Subject index changed (metrics)',... + {get(hObject,'Value')},... + {'Subject index'}); + +catch + errordlg('Please recompute metrics for the presently considered population !'); +end + +guidata(hObject,handles); + + + +function SubjectMenuMetrics_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + + +%% State menu popup control +% When we change our state of interest, we will change the display of the +% cumulative state being displayed + +function StateMenu_Callback(hObject, eventdata, handles) + +handles = ResetGraphDisplay(handles.CumStates,handles); +set(handles.CumStates,'Visible','on'); + +% In the case of a non-null matrix... +if ~isempty(handles.TPM) + + for i = 1:handles.n_datasets + % Cumulative distribution for the state that we want to be displayed (i.e. + % the state from the popup menu) + handles.TPMCum{i} = cumsum(handles.TPM{i} == get(handles.StateMenu,'Value'),2); + + % Average of the considered state across subjects + tmp_TPMCum{i} = mean(handles.TPMCum{i},1); + end + + % Similarly as above, we plot time if we have a valid TR; else, we plot + % 'time index' + if handles.isTROK == false + + for i = 1:handles.n_datasets + % We first plot each subject curve + for j = 1:size(handles.TPMCum{i},1) + plot(handles.TPMCum{i}(j,:),'Color',handles.PopColor{1}(i,:),... + 'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + % Then, we plot a bold average across subjects + plot(tmp_TPMCum{i},'Color',handles.PopColor{2}(i,:),... + 'LineWidth',2,'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time index [-]','FontSize',8); + end + else + for i = 1:handles.n_datasets + for j = 1:size(handles.TPMCum{i},1) + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + handles.TPMCum{i}(j,:),... + 'Color',handles.PopColor{1}(i,:),'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + tmp_TPMCum{i},... + 'LineWidth',2,'Color',handles.PopColor{2}(i,:),'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time [s]','FontSize',8); + end + end + + +ylabel(handles.CumStates,'Cumul. sum [-]','FontSize',8); +set(handles.CumStates,'Box','off'); + +end + +guidata(hObject,handles); + + + +function StateMenu_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + + +%% General utilities + +% Resets the display of a graph object +function handles = ResetGraphDisplay(Graph,handles) + +cla(Graph); +set(Graph,'Visible','off'); + + + +% Fills the entries of a pop-up menu with 'Subject _' entries from the +% reference population +function handles = FillSubjectList(ToFill,handles) + +tmp_string = {}; + +for ns = 1:handles.n_subjects{handles.ReferencePopulation} + tmp_string{ns} = ['Subject ',num2str(ns)]; +end + +set(ToFill,'String',tmp_string); + +clear tmp_string + + + +% Fills the entries of a pop-up menu with the different population entries +function handles = FillPopulationList(ToFill,handles) + +tmp_string = {}; + +for ns = 1:handles.n_datasets + tmp_string{ns} = [handles.SubjNames{ns}]; +end + +set(ToFill,'String',tmp_string); + +clear tmp_string + + + +% Fills the entries of a pop-up menu with the different population entries +function handles = FillStateList(ToFill,handles) + +tmp_string = {}; + +for ns = 1:handles.K + tmp_string{ns} = ['State ',num2str(ns)]; +end + +set(ToFill,'String',tmp_string); + +clear tmp_string + + + +% Removes NaN-containing lines from a matrix (used for the plotting of +% duration violin plots) +function M2 = DiscardNaN(M) + +% We initialize the output matrix as a void one +M2 = []; + +% For each row, we count the amount of NaN entries; if not equal to zero, +% then we discard the line +for i = 1:size(M,1) + if sum(isnan(M(i,:))) > 0 + else + M2 = [M2;M(i,:)]; + end +end + + + +% Concatenates populations appropriately for Violin plotting +function M2 = ConcatMat(M,n_pop,n_states,n_subjects,type) + +% Creates the data matrix (nan values are used to have the same amount of +% data for each group) +M2 = nan(n_pop*n_states,max(cell2mat(n_subjects))); + +for i = 1:n_pop + + switch type + case 'Raw counts' + + tmp = M{i}.raw.state(:,1:n_states)'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + + case 'Normalized counts' + + tmp = M{i}.frac.state(:,1:n_states)'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + + case 'Number' + + tmp = M{i}(:,3:3+n_states-1)'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + + case 'Duration' + tmp = DiscardNaN(M{i}(:,3:3+n_states-1))'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + case 'FD' + tmp = M{i}; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + end +end diff --git a/CAP_TB_Validation.m b/CAP_TB_Validation.m new file mode 100755 index 0000000..9a97c75 --- /dev/null +++ b/CAP_TB_Validation.m @@ -0,0 +1,144 @@ +%% This script intends to validate the CAP toolbox proper functioning with +% simulated data of different types + +%% 1. Determination of ground truth data parameters + +% Number of states that we define +K = 4; + +% Number of voxels (10x10x10) +V = [10 10 10]; + +% For now, the mask keeps all the voxels +mask = logical(ones(prod(V),1)); + +% Number of time points +T = 100; + +% Number of subjects +S = 4; + +% FD is said to be null for now +FD = zeros(T,S); + +% We try a simple brain_info +brain_info.dim = [10 10 10]; +brain_info.mat = [20 0 0 -90; 0 20 0 -90; 0 0 20 -90; 0 0 0 1]; + + +%% 2. Determination of the ground truth co-activation patterns +% For the moment, we select four partially spatially overlapping states: a +% cylinder along Z, a cube along Z, and two stripe patterns + + +% We must first define the four states +Circle = zeros(10,10); +for i = -4.5:4.5 + for j = -4.5:4.5 + if sqrt((i*i+j*j))<3 + Circle(i+5.5,j+5.5)=1; + else + Circle(i+5.5,j+5.5)=0; + end + end +end + +for z = 1:10 + State1(:,:,z) = Circle; +end + +Square = zeros(10,10); +Square(1:5,1:5) = ones(5,5); + +for z = 1:10 + State2(:,:,z) = Square; +end + +Vertical = zeros(10,10); +for j = 1:10 + + if mod(j,2)==0 + Vertical(:,j)=ones(10,1); + end +end + +for z = 1:10 + State3(:,:,z) = Vertical; +end + +Horizontal = zeros(10,10); +for i = 1:10 + + if mod(i,2)==0 + Horizontal(i,:)=ones(1,10); + end +end + +for z = 1:10 + State4(:,:,z) = Horizontal; +end + +State1 = State1(:); +State2 = State2(:); +State3 = State3(:); +State4 = State4(:); + +%% 3. Determination of seed parameters + +% The seeda are created spatially +seed1 = zeros(10,10,10); +seed1(9,9,5) = 1; +seed1 = seed1(:); +seed1 = seed1(mask); +seed1 = logical(seed1); +seed2 = zeros(10,10,10); +seed2(1,5,8) = 1; +seed2 = seed2(:); +seed2 = seed2(mask); +seed2 = logical(seed2); +seed3 = zeros(10,10,10); +seed3(5,5,5) = 1; +seed3 = seed3(:); +seed3 = seed3(mask); +seed3 = logical(seed3); + +State1(seed1) = 1; +State1(seed2) = 0; +State1(seed3) = 1; +State2(seed1) = 0; +State2(seed2) = 1; +State2(seed3) = 0; +State3(seed1) = 1; +State3(seed2) = 1; +State3(seed3) = 1; +State4(seed1) = 0; +State4(seed2) = 1; +State4(seed3) = 1; + +States = {State1,State2,State3,State4}; + +% Now that we defined the four states, we must define time courses for the +% different subjects. We assume that at each time point, only one state can +% be entered +idx = [ones(1,10),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10),3*ones(1,5),4*ones(1,5),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10);... + ones(1,10),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10),3*ones(1,5),4*ones(1,5),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10);... + ones(1,10),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10),3*ones(1,5),4*ones(1,5),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10);... + ones(1,10),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10),3*ones(1,5),4*ones(1,5),3*ones(1,5),4*ones(1,5),ones(1,10),2*ones(1,10),ones(1,10)]; + + +% TC contains the above time courses +TC = cell(1,4); + +for s = 1:S + for t = 1:T + TC{s}(t,:) = States{idx(s,t)}(:)';% + 0.1*rand(1,prod(V)); + end +end + +save('TC','TC'); +save('FD','FD'); +save('mask','mask'); +save('seed1','seed1'); +save('seed2','seed2'); +save('seed3','seed3'); +save('brain_info','brain_info'); \ No newline at end of file diff --git a/CAP_TBsmall.m b/CAP_TBsmall.m new file mode 100644 index 0000000..1d8b5d3 --- /dev/null +++ b/CAP_TBsmall.m @@ -0,0 +1,2683 @@ +%% This is the script of what is supposed to be a friendly user interface +% for application of co-activation pattern analysis. + +function varargout = CAP_TBsmall(varargin) +% CAP_TBsmall MATLAB code for CAP_TBsmall.fig +% CAP_TBsmall, by itself, creates a new CAP_TBsmall or raises the existing +% singleton*. +% +% H = CAP_TBsmall returns the handle to a new CAP_TBsmall or the handle to +% the existing singleton*. +% +% CAP_TBsmall('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in CAP_TBsmall.M with the given input arguments. +% +% CAP_TBsmall('Property','Value',...) creates a new CAP_TBsmall or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before CAP_TBsmall_OpeningFcn gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to CAP_TBsmall_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 CAP_TBsmall + +% Last Modified by GUIDE v2.5 03-Mar-2017 13:41:05 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @CAP_TBsmall_OpeningFcn, ... + 'gui_OutputFcn', @CAP_TBsmall_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 when the window opens; all handle variables are created at this +% time +function CAP_TBsmall_OpeningFcn(hObject, eventdata, handles, varargin) + +%%%%%%%%%%%%%%%%%%%% +% Path and other miscellaneous settings + +% Adds the paths to the subfolders of the toolbox that will be important +% for the plotting and the analysis +addpath(genpath('./Plotting')); +addpath(genpath('./Analysis')); +addpath(genpath('./DefaultData')); + +% Sets warnings off +warning('off'); + +% Choose default command line output for CAP_TBsmall +handles.output = hObject; + + + +%%%%%%%%%%%%%%%%%%%% +% Data loading + +% TC will contain the time courses of the subjects from the different +% populations +handles.TC = {}; + +% FD contains the traces of framewise displacement for the subjects (n_TP x +% n_subj per cell of the array, one cell per dataset) +handles.FD = {}; + +% Information on the NIFTI files from which the data originate +handles.brain_info = {}; + +% Mask used prior to CAP analysis +handles.mask = {}; + +% Number of datasets added to the interface. A dataset is defined as a +% population of subjects from the same experimental group (e.g. an ensemble +% of subjects suffering from the same disorder) +handles.n_datasets = 0; + +% Stores the number of subjects that have been loaded +handles.n_subjects = {}; + +% SubjNames contains the names of the files from which subject data have +% been sampled (full paths) +handles.SubjNames = {}; + +% MotName contains the name(s) of the file(s) loaded as motion ones +handles.MotName = {}; + +% TP and VOX contain the number of time points (of frames) and of brain +% voxels that are present in the loaded datasets. Those values are +% initialized at -inf, and then take the values of the first file that is +% being loaded if that file looks reasonable dimensionally speaking. In the +% scripts below, it is assumed that all subject populations loaded have a +% similar number of time points and of voxels +handles.SubjSize.TP = -inf; +handles.SubjSize.VOX = -inf; + +% By default, the reference population from which CAPs will be extracted +% will be the first uploaded one +handles.ReferencePopulation = 1; + +% Loads and sets the brain underlay used for plotting purposes +Underlay = load_nii('Underlay.nii'); +Underlay_mat = [Underlay.hdr.hist.srow_x; Underlay.hdr.hist.srow_y; Underlay.hdr.hist.srow_z; 0 0 0 1]; +Underlay_dim = Underlay.hdr.dime.dim; +Underlay_dim = Underlay_dim(2:4); +handles.Underlay_info.dim = Underlay_dim; +handles.Underlay_info.mat = Underlay_mat; +clear Underlay +clear Underlay_dim +clear Underlay_mat +load('brain.mat'); +assignin('base','brain', brain); +handles.brain = brain; +clear brain + +% Handles for the TR +handles.TR = -inf; +handles.isTROK = false; + +%%%%%%%%%%%%%%%%%%%% +% Seed selection and seed maps + +% Seed used for the analysis +handles.seed = []; +handles.seed2 = []; + +% One map per subject +handles.SeedMaps = {}; + +% One average map throughout subjects +handles.AvgSeedMap = []; + +%%%%%%%%%%%%%%%%%%%% +% Time points selection + +% Motion threshold for scrubbing +handles.Tmot = 0.5; + +% Threshold for frame selection in the analysis +handles.T = 0.5; + +% Sets the right text header in front of the frame selection threshold box +% (threshold or retention percentage) +if get(handles.TRadio,'Value') + set(handles.TText,'String','T [-]'); + handles.SelMode = 'Threshold'; +else + set(handles.TText,'String','P [%]'); + handles.SelMode = 'Percentage'; +end + + + +% Activation and deactivation frames kept for all datasets +handles.Xonp = {}; +handles.Xonn = {}; + +% Percentage of frames retained for CAP analysis (discarding both the +% baseline time points and the scrubbed time points) +handles.RetainedPercentage = {}; + +% Indices of the frames that have been retained (i.e. when do they occur in +% the full time course), of baseline frames, and of scrubbed frames +handles.FrameIndices = {}; + +%%%%%%%%%%%%%%%%%%%% +% CAP analysis + +% Number of times that clustering is run +handles.n_rep = 20; + +% Percentage voxels to keep for clustering (positive - Pp - and negative - +% Pn - ones) +handles.Pp = 100; +handles.Pn = 100; + +% Number of clusters to use in the analysis +handles.K = 5; + +% Type of CAPs computed: can take a value of 'Act','Deact' or 'Both' +handles.CAPType = ''; + +% Indices of the CAP to which frames from the reference population and from +% the other populations are assigned +handles.idx = {}; + +% Value of correlation of the control group frame that is the Tper-th least +% close to its CAP +handles.CorrDist = []; + +% Contains the CAPs +handles.CAP = []; + +% Contains the standard deviation for the CAPs +handles.STDCAP = []; + +% Parameters for the GMM +handles.Gamma_GMM = []; +handles.Priors_GMM = []; +handles.Mu_GMM = []; +% This one must be put into a structure to have several sparse matrices +% filling it +handles.Sigma_GMM = {}; + +% Percentile threshold used in frame assignment +handles.percentile = 5; + +%%%%%%%%%%%%%%%%%%%% +% Metrics + +% Will contain the metrics +% State matrix (n_subjects x n_time points) +handles.TPM = {}; + +% State counts (raw and frac) +handles.Counts = {}; + +% Number of times entering a state +handles.Number = {}; + +% Average duration within a state +handles.Avg_Duration = {}; + +% Duration of all the excursions within a state +handles.Duration = {}; + +% Transition probabilities +handles.TM = {}; + +% Cumulative sum of states +handles.TPMCum = {}; + +%%%%%%%%%%%%%%%%%%%% +% General utilities + +% Log containing the different events summoned from the toolbox +handles.Log = {}; + +% Colors used in plotting of all populations +handles.PopColor{1} = [1,0.9,0.4; 0.8, 1.0, 1.0; 0.2, 1, 0.2; 0.9, 0.9, 0.9]; +handles.PopColor{2} = [1, 0, 0.2; 0.2, 0.2, 0.8; 0, 0.4, 0; 0, 0, 0]; + +% Project title, by default 'Untitled' +handles.project_title = 'Untitled'; + +% Directory to which data is to be saved (initially loaded as ./SavedData) +handles.savedir = fullfile(pwd,'SavedData'); +set(handles.SaveFolderText,'String',handles.savedir); + +% Update handles structure +guidata(hObject, handles); + +% --- Outputs from this function are returned to the command line. +function varargout = CAP_TBsmall_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; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%% SECTION 1: LOADING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Data Button Click + +% Executes when adding a subject population (clicking on 'A. Load data') +function DataButton_Callback(hObject, eventdata, handles) +% Opens up a menu to choose the required files for the analysis; the user +% must select four files: +% 1. Data file +% 2. Mask file +% 3. Info file (header of NIFTI) +% 4. Motion file +% +% He can select them in the order he likes +[filename1,pathname1]=uigetfile({'*.*','All Files'},... + 'Select data, motion, mask and brain info files...','MultiSelect','on'); + +% If the user has indeed entered files +if ~isequal(filename1,0) || ~isequal(pathname1,0) + % There should be four selected files. In this switch, we test + % for the amount of entered files + if length(filename1) == 4 + + % The files are loaded sequentially + for i = 1:length(filename1) + File{i} = fullfile(pathname1, filename1{i}); + tmp = load(File{i}); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); + + % Finds what type of file DataType is between the four + % possibilities + DataType = CAP_FindDataType(tmp); + + % Accordingly, fill the right handle with the information + switch DataType + case 'Data' + % We store the data into handles.TC and the file name that goes + % with it + handles.TC{handles.n_datasets+1} = tmp; + + % Takes only the last two parts of the file name and + % puts them in tmp_file + [tmp_file,n_delim] = strsplit(File{i},'/'); + + if isempty(n_delim) + tmp_file = strsplit(File{i},'\'); + end + + tmp_file = tmp_file(end-1:end); + + % This is what is saved and displayed in the main + % window then + handles.SubjNames{handles.n_datasets+1} = fullfile(tmp_file{1},tmp_file{2}); + handles.n_subjects{handles.n_datasets+1} = size(handles.TC{handles.n_datasets+1},2); + + % Some commands are run only for the first dataset that we add + if handles.n_datasets == 0 + % We compute and store the number of voxels and the number of time + % points, as well as the number of subjects + handles.SubjSize.VOX = size(handles.TC{1}{1},2); + handles.SubjSize.TP = size(handles.TC{1}{1},1); + end + + % Sets the text label about data dimensions + set(handles.Dimensionality_Text, 'String', [num2str(handles.SubjSize.TP),... + ' frames x ',num2str(handles.SubjSize.VOX),' voxels (',... + strjoin(arrayfun(@(x) num2str(x),cell2mat(handles.n_subjects),... + 'UniformOutput',false),'+'),')']); + + case 'Motion' + % We store the path of the motion file added + handles.MotName{handles.n_datasets+1} = File{i}; + % If the dimensions hold, we store the file into the FD variable + % and then plot the FD ribbon graph + handles.FD{handles.n_datasets+1} = tmp; + + case 'Mask' + handles.mask{handles.n_datasets+1} = tmp; + case 'Info' + % If so, we store the value and we validate the choice + handles.brain_info{handles.n_datasets+1} = tmp; + + % If the data file is unknown, then we return an error and + % the user must enter files again + case 'Unknown' + errordlg('At least one of the selected files is not recognized; please try again !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + end + + % Check if the dimensionality of the entered data holds between + % the file types. It may be that the user entered four files of + % the same type (e.g. four data files), rather than one of each + % type as required + [is_DataOK,Data_problems] = CAP_IsDataOK(handles.TC{handles.n_datasets+1},handles.FD{handles.n_datasets+1},... + handles.mask{handles.n_datasets+1},handles.brain_info{handles.n_datasets+1}); + if is_DataOK + + % We increment handles.n_datasets + handles.n_datasets = handles.n_datasets + 1; + + % We fill the list of loaded populations, and make it visible + handles = FillPopulationList(handles.RefPop,handles); + set(handles.RefPop,'Visible','on'); + + % We also fill the list of subjects from the seed menu, but do + % not make it visible yet + handles = FillSubjectList(handles.SubjectMenu,handles); + + % We can now enable the seed selection + set(handles.SeedButton,'Enable','on'); + + % Also, we can now color the button in green + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + % If we are loading the first dataset, we convert the underlay + % to the resolution of the functional data for plotting + if handles.n_datasets == 1 + + % The brain variable now contains a good resolution + % underlay that can directly be overlapped with the + % functional data + handles.brain = CAP_V2V(handles.brain,handles.Underlay_info.dim,... + handles.Underlay_info.mat,handles.brain_info{1}.dim,handles.brain_info{1}.mat); + + elseif handles.n_datasets > 1 && handles.n_datasets < 5 && ~isempty(handles.CAP) + set(handles.AssignButton,'Enable','on'); + elseif handles.n_datasets > 4 + errordlg('Please enter at most four different populations in the interface !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + handles.Log = CAP_AddToLog(handles.Log,'Data correctly loaded'); + + % If it doesn't hold, then we return an error + else + errordlg(['There is a dimensionality problem in your data: ',Data_problems]); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end + + % If a different number of files is entered, then there is a problem, + % and everything is reset + else + errordlg('You did not enter the correct number of files !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); + end +% Else, an error is displayed and the user is prompted to enter files +else + errordlg('Cancelling data entry will not solve your problems !'); + handles = ClearDataButton_Callback(handles.ClearDataButton, eventdata, handles); +end + + +% Update handles structure +guidata(hObject, handles); + +%% TR Textbox Interaction + +% Executes when we go to the TR field to add the TR of the experiment +function TR_Entry_Callback(hObject, eventdata, handles) + +% If the TR takes a reasonable value, then we validate it +if (~isempty(str2double(get(hObject,'String')))) && ... + (str2double(get(hObject,'String')) > 0.5) && ... + (str2double(get(hObject,'String')) <= 5) + + handles.TR = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + handles.isTROK = true; + + handles.Log = CAP_AddToLog(handles.Log,'Correct value of TR entered',{handles.TR},{'TR'}); + +% Else, the TR value is not accepted +else + set(hObject,'BackgroundColor', [0.93 0.84 0.84]); + handles.isTROK = false; +end + +guidata(hObject, handles); + +% Executes during creation of the TR textbox +function handles = TR_Entry_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +% 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','r'); +end + +guidata(hObject, handles); + +% Executes when clicking on the TR text space +function TR_Entry_ButtonDownFcn(hObject, eventdata, handles) +% hObject handle to TMotEdit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); +guidata(hObject, handles); + +%% Seed sliders interactions +% For the below functions, the goal is to change the value of the slider +% textboxes when the sliders are moved, and to update the graph display +% accordingly. For this purpose, cla is used to clear graph content prior +% to a new display + +% --- Executes on slider movement. +function SliderX_Callback(hObject, eventdata, handles) + +cla(handles.SeedGraphX); +set(handles.XCoordText,'String',['X: ',sprintf('%.2f',get(hObject,'Value'))]); +handles.SeedGraphX = plot_slice(handles.seed,get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),handles.SeedGraphX); +guidata(hObject, handles); + +% --- Executes during object creation, after setting all properties. +function SliderX_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end +guidata(hObject, handles); + +% --- Executes on slider movement. +function SliderY_Callback(hObject, eventdata, handles) + +cla(handles.SeedGraphY); +set(handles.YCoordText,'String',['Y: ',sprintf('%.2f',get(hObject,'Value'))]); +handles.SeedGraphY = plot_slice(handles.seed,get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(hObject,'Value'),handles.SeedGraphY); +guidata(hObject, handles); + +% --- Executes during object creation, after setting all properties. +function SliderY_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end +guidata(hObject, handles); + +% --- Executes on slider movement. +function SliderZ_Callback(hObject, eventdata, handles) + +cla(handles.SeedGraphZ); +set(handles.ZCoordText,'String',['Z: ',sprintf('%.2f',get(hObject,'Value'))]); +handles.SeedGraphZ = plot_slice(handles.seed,get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),handles.SeedGraphZ); +guidata(hObject, handles); + +% --- Executes during object creation, after setting all properties. +function SliderZ_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end +guidata(hObject, handles); + + +%% Seed Button Controls +% We want to define what happens when loading data (SeedButton) or when +% attempting to plot them (PlotSeedButton) + +% Executes when clicking on 'B. Select a seed' +function SeedButton_Callback(hObject, eventdata, handles) + +% No seed union asked +if ~get(handles.Union_Checkbox,'Value') + + [filename_seed,pathname_seed]=uigetfile({'*.*','All Files'},... + 'Select Seed File...'); + + File_seed = fullfile(pathname_seed, filename_seed); + tmp = load(File_seed); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); +% Seed union asked +else + + [filename_seed,pathname_seed]=uigetfile({'*.*','All Files'},... + 'Select Seed File...','MultiSelect','on'); + + File_seed = fullfile(pathname_seed, filename_seed{1}); + tmp = load(File_seed); + assignin('base','tmp', tmp); + tmp = struct2array(tmp); + handles.seed = tmp; + + File_seed = fullfile(pathname_seed, filename_seed{2}); + tmp2 = load(File_seed); + assignin('base','tmp2', tmp2); + tmp2 = struct2array(tmp2); + handles.seed2 = tmp2; +end + +% If the user has indeed entered files +if ~isequal(filename_seed,0) || ~isequal(pathname_seed,0) + + % If the file is of suitable dimensions + if islogical(tmp) && size(tmp,2) == 1 && size(tmp,1) == sum(handles.mask{1}) + + % Then we put it in the handles, enable the plotting button, and + % make the seed selection button green + handles.seed = tmp; + set(handles.PlotSeedButton,'Enable','on'); + set(handles.SeedButton,'BackgroundColor', [0.4 0.6 0.4]); + + % We can now go through the next parts of the analysis, so we + % enable the related buttons + set(handles.TPSelectionButton,'Enable','on'); + set(handles.SeedMapPushButton,'Enable','on'); + + handles.Log = CAP_AddToLog(handles.Log,'Seed chosen',{File_seed},{'Seed file'}); + else + errordlg('The file you entered appears to be of wrong dimensions...'); + end + +else + errordlg('Please enter a seed file !'); +end + +guidata(hObject, handles); + +% Change in the status of the checkbox for seed union +function Union_Checkbox_Callback(hObject, eventdata, handles) + +handles.Log = CAP_AddToLog(handles.Log,'Seed union status changed',{get(hObject,'Value')},{'Status'}); + +guidata(hObject, handles); + +% Executes when clicking on 'Plot Seed' +function PlotSeedButton_Callback(hObject, eventdata, handles) + +% Clears the present graph content +cla(handles.SeedGraphX); +cla(handles.SeedGraphX); +cla(handles.SeedGraphX); + +% Plots the slices within the graph windows +handles.SeedGraphX = plot_slice(handles.seed,get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(handles.SliderX,'Value'),handles.SeedGraphX); +handles.SeedGraphY = plot_slice(handles.seed,get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(handles.SliderY,'Value'),handles.SeedGraphY); +handles.SeedGraphZ = plot_slice(handles.seed,get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SliderZ,'Value'),handles.SeedGraphZ); + +% Sets the sliders to visible +set(handles.SliderX,'Visible','on'); +set(handles.SliderY,'Visible','on'); +set(handles.SliderZ,'Visible','on'); + +% Sets the text values at the ones of the sliders +set(handles.XCoordText,'String',['X: ',sprintf('%.2f',get(handles.SliderX,'Value'))]); +set(handles.YCoordText,'String',['Y: ',sprintf('%.2f',get(handles.SliderY,'Value'))]); +set(handles.ZCoordText,'String',['Z: ',sprintf('%.2f',get(handles.SliderZ,'Value'))]); + +% Sets the visibility of the slider texts to on +set(handles.XCoordText,'Visible','on'); +set(handles.YCoordText,'Visible','on'); +set(handles.ZCoordText,'Visible','on'); + +handles.Log = CAP_AddToLog(handles.Log,'Seed plots activated'); + +guidata(hObject, handles); + +%% Reference group List Control +% If we change the entry from this list, we change the reference group on +% which seed maps will be computed, and CAPs calculated + +% Executes on selection change in RefPop +function RefPop_Callback(hObject, eventdata, handles) + +% We take the actual value of the population as the reference group +handles.ReferencePopulation = get(hObject,'Value'); + +% We want to fill the subject lists again, according to how many subjects +% now lie in the considered reference population +handles = FillSubjectList(handles.SubjectMenu,handles); +handles = FillSubjectList(handles.SubjectMenuMetrics,handles); + +% We want to reset the graph displays and only enable what can be computed + +% Resetting the seed map section +set(handles.SubjectMenu,'Visible','off'); +set(handles.SubjectMenu,'Value',1); + +handles = ResetGraphDisplay(handles.SeedMapX,handles); +handles = ResetGraphDisplay(handles.SeedMapY,handles); +handles = ResetGraphDisplay(handles.SeedMapZ,handles); + +handles = ResetGraphDisplay(handles.SubjSeedMapX,handles); +handles = ResetGraphDisplay(handles.SubjSeedMapY,handles); +handles = ResetGraphDisplay(handles.SubjSeedMapZ,handles); + +set(handles.SeedMap_SliderX,'Visible','off'); +set(handles.SeedMapSliderX,'Visible','off'); +set(handles.SeedMap_SliderY,'Visible','off'); +set(handles.SeedMapSliderY,'Visible','off'); +set(handles.SeedMap_SliderZ,'Visible','off'); +set(handles.SeedMapSliderZ,'Visible','off'); + +handles = ResetGraphDisplay(handles.ColorbarSeed,handles); +set(handles.TSeed,'Visible','off'); +set(handles.TSeed_Slider,'Visible','off'); + +% Resetting the time points selection section +handles = ResetGraphDisplay(handles.TPViolin,handles); + +% Resetting the CAP analysis section +set(handles.ClusterButton,'Enable','off'); +set(handles.GMM_Button,'Enable','off'); + +set(handles.TVIS,'Visible','off'); +set(handles.TVIS_Slider,'Visible','off'); + +handles = ResetGraphDisplay(handles.ColorbarCAP,handles); + +set(handles.CAP1_Frames,'Visible','off'); +set(handles.CAP2_Frames,'Visible','off'); +set(handles.CAP3_Frames,'Visible','off'); +set(handles.CAP4_Frames,'Visible','off'); +set(handles.CAP5_Frames,'Visible','off'); +set(handles.CAP6_Frames,'Visible','off'); + +tmp_CAPX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X,handles.CAP6X}; +tmp_CAPY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y,handles.CAP6Y}; +tmp_CAPZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z,handles.CAP6Z}; + +for i = 1:6 + handles = ResetGraphDisplay(tmp_CAPX{i},handles); + handles = ResetGraphDisplay(tmp_CAPY{i},handles); + handles = ResetGraphDisplay(tmp_CAPZ{i},handles); +end + +set(handles.CAP_XC,'Visible','off'); +set(handles.CAP_YC,'Visible','off'); +set(handles.CAP_ZC,'Visible','off'); + +set(handles.CAP_SliderX,'Visible','off'); +set(handles.CAP_SliderY,'Visible','off'); +set(handles.CAP_SliderZ,'Visible','off'); + +% Resetting the metrics section +set(handles.MetricsButton,'Enable','off'); + +handles = ResetGraphDisplay(handles.CAP_Mat,handles); +handles = ResetGraphDisplay(handles.TMGraph,handles); +handles = ResetGraphDisplay(handles.TM_Subject,handles); +handles = ResetGraphDisplay(handles.ColorbarSimMat,handles); +handles = ResetGraphDisplay(handles.ColorbarTransMat,handles); +handles = ResetGraphDisplay(handles.DynStates,handles); +handles = ResetGraphDisplay(handles.CumStates,handles); +handles = ResetGraphDisplay(handles.ViolinCounts,handles); +handles = ResetGraphDisplay(handles.ViolinCountsFrac,handles); +handles = ResetGraphDisplay(handles.ViolinNumber,handles); +handles = ResetGraphDisplay(handles.ViolinDuration,handles); + +set(handles.SubjectMenuMetrics,'Visible','off'); +set(handles.SubjectMenuMetrics,'Value',1); + +set(handles.StateMenu,'Visible','off'); + +handles.Log = CAP_AddToLog(handles.Log,'Reference population change',{handles.ReferencePopulation},{'New reference population index'}); + +guidata(hObject,handles); + +% --- Executes during object creation, after setting all properties. +function RefPop_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%SECTION 2: SEED MAPS COMPUTATION%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Seed Map Computation Button +% When pressing on this button, classical seed maps are computed for the +% population of subjects chosen as the reference one in the loading part. +% The last entered seed is used + +function SeedMapPushButton_Callback(hObject, eventdata, handles) + +% Computes seed maps for each subject and for the population, using the +% data from the chosen reference population +[handles.SeedMaps,handles.AvgSeedMap] = CAP_Compute_SeedMap(handles.TC{handles.ReferencePopulation},handles.seed); + +% Graphical displays + +% Making the plots, texts and sliders visible +set(handles.SubjectMenu,'Visible','on'); +set(handles.SeedMapX,'Visible','on'); +set(handles.SeedMapY,'Visible','on'); +set(handles.SeedMapZ,'Visible','on'); +set(handles.SubjSeedMapX,'Visible','on'); +set(handles.SubjSeedMapY,'Visible','on'); +set(handles.SubjSeedMapZ,'Visible','on'); +set(handles.SeedMap_SliderX,'Visible','on'); +set(handles.SeedMap_SliderY,'Visible','on'); +set(handles.SeedMap_SliderZ,'Visible','on'); +set(handles.SeedMapSliderX,'Visible','on'); +set(handles.SeedMapSliderY,'Visible','on'); +set(handles.SeedMapSliderZ,'Visible','on'); +set(handles.TSeed_Slider,'Visible','on'); +set(handles.TSeed,'Visible','on'); +set(handles.ColorbarSeed,'Visible','on'); + +% Writing down the text with current MNI coordinates +set(handles.SeedMap_SliderX,'String',['X: ',sprintf('%.2f',get(handles.SeedMapSliderX,'Value'))]); +set(handles.SeedMap_SliderY,'String',['Y: ',sprintf('%.2f',get(handles.SeedMapSliderY,'Value'))]); +set(handles.SeedMap_SliderZ,'String',['Z: ',sprintf('%.2f',get(handles.SeedMapSliderZ,'Value'))]); + +% Clears previous plot contents (in case we want to re-plot after changing +% the seed) +cla(handles.SeedMapX); +cla(handles.SeedMapY); +cla(handles.SeedMapZ); +cla(handles.SubjSeedMapX); +cla(handles.SubjSeedMapY); +cla(handles.SubjSeedMapZ); + +% Plots new slices +handles.SeedMapX = plot_slice(handles.AvgSeedMap,0.25,1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(handles.SeedMapSliderX,'Value'),handles.SeedMapX); +handles.SeedMapY = plot_slice(handles.AvgSeedMap,0.25,1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(handles.SeedMapSliderY,'Value'),handles.SeedMapY); +handles.SeedMapZ = plot_slice(handles.AvgSeedMap,0.25,1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SeedMapSliderZ,'Value'),handles.SeedMapZ); + +% Plots subject specific slices after having selected the subject +handles.SubjSeedMapX = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(handles.SeedMapSliderX,'Value'),handles.SubjSeedMapX); +handles.SubjSeedMapY = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(handles.SeedMapSliderY,'Value'),handles.SubjSeedMapY); +handles.SubjSeedMapZ = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SeedMapSliderZ,'Value'),handles.SubjSeedMapZ); + +% Adds the colorbar for the seed maps (between -1 and 1) +handles.ColorbarSeed = Create_CAP_colorbar(-1,1,0.5,get(handles.TSeed_Slider,'Value'),'',handles.ColorbarSeed,'Horizontal'); + +handles.Log = CAP_AddToLog(handles.Log,'Seed maps displayed'); + +guidata(hObject,handles); + +%% Slider Controls (MNI coordinates) +% We want to reload the seed images with the new parameters when changing a +% slider, so we clear the previous display, change the text summarizing the +% MNI coordinate where we stand, and plot the new image + +function SeedMapSliderX_Callback(hObject, eventdata, handles) + +% Clears graphs +cla(handles.SeedMapX); +cla(handles.SubjSeedMapX); + +% Changes slider texts +set(handles.SeedMap_SliderX,'String',['X: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Plots new slices +handles.SeedMapX = plot_slice(handles.AvgSeedMap,get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),handles.SeedMapX); +handles.SubjSeedMapX = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),handles.SubjSeedMapX); +guidata(hObject, handles); + +function SeedMapSliderX_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +function SeedMapSliderY_Callback(hObject, eventdata, handles) + +% Clears graphs +cla(handles.SeedMapY); +cla(handles.SubjSeedMapY); + +% Changes slider texts +set(handles.SeedMap_SliderY,'String',['Y: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Plots new slices +handles.SeedMapY = plot_slice(handles.AvgSeedMap,get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(hObject,'Value'),handles.SeedMapY); +handles.SubjSeedMapY = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(hObject,'Value'),handles.SubjSeedMapY); +guidata(hObject, handles); + +function SeedMapSliderY_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +function SeedMapSliderZ_Callback(hObject, eventdata, handles) + +cla(handles.SeedMapZ); +cla(handles.SubjSeedMapZ); + +% Changes slider texts +set(handles.SeedMap_SliderZ,'String',['Z: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Plots new slices +handles.SeedMapZ = plot_slice(handles.AvgSeedMap,get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),handles.SeedMapZ); +handles.SubjSeedMapZ = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),handles.SubjSeedMapZ); +guidata(hObject, handles); + +% --- Executes during object creation, after setting all properties. +function SeedMapSliderZ_CreateFcn(hObject, eventdata, handles) +% hObject handle to SeedMapSliderZ (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +%% Slider controls (visualization threshold) +% We want to replot and modify the colorbar according to the visualization +% threshold that we select (and also change the text) + +function TSeed_Slider_Callback(hObject, eventdata, handles) + +% Clears previous plot contents +cla(handles.SeedMapX); +cla(handles.SeedMapY); +cla(handles.SeedMapZ); +cla(handles.SubjSeedMapX); +cla(handles.SubjSeedMapY); +cla(handles.SubjSeedMapZ); + +% Plots new slices (average seed maps) +handles.SeedMapX = plot_slice(handles.AvgSeedMap,get(hObject,'Value'),1,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',... + get(handles.SeedMapSliderX,'Value'),handles.SeedMapX); + +handles.SeedMapY = plot_slice(handles.AvgSeedMap,get(hObject,'Value'),1,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Y',... + get(handles.SeedMapSliderY,'Value'),handles.SeedMapY); + +handles.SeedMapZ = plot_slice(handles.AvgSeedMap,get(hObject,'Value'),1,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',... + get(handles.SeedMapSliderZ,'Value'),handles.SeedMapZ); + +% Plots subject specific slices after having selected the subject +handles.SubjSeedMapX = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},... + get(hObject,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(handles.SeedMapSliderX,... + 'Value'),handles.SubjSeedMapX); + +handles.SubjSeedMapY = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},... + get(hObject,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Y',get(handles.SeedMapSliderY,... + 'Value'),handles.SubjSeedMapY); + +handles.SubjSeedMapZ = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},... + get(hObject,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SeedMapSliderZ,... + 'Value'),handles.SubjSeedMapZ); + +% Modifies the text +set(handles.TSeed,'String',['Tv: ',sprintf('%.2f',get(hObject,'Value'))]); + +% Clears and replots the colorbar +cla(handles.ColorbarSeed); +handles.ColorbarSeed = Create_CAP_colorbar(-1,1,0.5,get(hObject,'Value'),'',handles.ColorbarSeed,'Horizontal'); + +guidata(hObject,handles); + +function TSeed_Slider_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +%% Subject Menu Control +% We want that when we change the value of the subject menu, the displays +% from the graphs change too (if the graphs already contain something) + +function SubjectMenu_Callback(hObject, eventdata, handles) + +% If the seed maps have already been computed (i.e. if we have already +% pressed on the computation button), then we adjust the displays of the +% subject-specific maps +try + cla(handles.SubjSeedMapX); + cla(handles.SubjSeedMapY); + cla(handles.SubjSeedMapZ); + handles.SubjSeedMapX = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'X',get(handles.SeedMapSliderX,'Value'),handles.SubjSeedMapX); + handles.SubjSeedMapY = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Y',get(handles.SeedMapSliderY,'Value'),handles.SubjSeedMapY); + handles.SubjSeedMapZ = plot_slice(handles.SeedMaps{get(handles.SubjectMenu,'Value')},get(handles.TSeed_Slider,'Value'),1,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(handles.SeedMapSliderZ,'Value'),handles.SubjSeedMapZ); +catch + errordlg('Please recompute seed maps for the presently considered population !'); +end + +handles.Log = CAP_AddToLog(handles.Log,'Changed displayed subjectwise seed map',{get(handles.SubjectMenu,'Value')},{'New subject index'}); + +guidata(hObject, handles); + +function SubjectMenu_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%PART 3: CAP ANALYSIS%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Motion parameter entry + +function TMotEdit_Callback(hObject, eventdata, handles) + +% If we enter a reasonable value, it is taken as a new threshold +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 0.5) + handles.Tmot = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid motion threshold value entered',{handles.Tmot},{'Motion threshold value'}); + +% If we set something wrong again, we set the threshold value back to the +% default of 0.5 +else + set(hObject,'BackgroundColor', [0.93 0.84 0.84]); + handles.Tmot = 0.5; +end + +guidata(hObject, handles); + +% When clicking on the motion button +function handles = TMotEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject, handles); + +% When the object is created +function handles = TMotEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','r'); +end +guidata(hObject, handles); + +%% Frame selection parameter entry + +function TEdit_Callback(hObject, eventdata, handles) + +% If we enter a reasonable value, it is taken as the new threshold +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.T = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid (de)activation threshold entered',{handles.T},{'Threshold value'}); + +% If we set something wrong again, we set the threshold value back to the +% default of 0.5 +else + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); + handles.T = 0.5; +end + +guidata(hObject, handles); + +% When the object is created +function handles = TEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +guidata(hObject, handles); + +% When clicking on it +function TEdit_ButtonDownFcn(hObject,eventdata,handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + +%% Frame selection mode control buttons + +% If we select threshold, we update the Selmode accordingly +function TRadio_Callback(hObject, eventdata, handles) + +set(handles.TText,'String','T [-]'); +handles.SelMode = 'Threshold'; + +handles.Log = CAP_AddToLog(handles.Log,'Changed time points selection scheme',{handles.SelMode},{'Selected mode'}); + +guidata(hObject,handles); + +% Same for percentage +function PRadio_Callback(hObject, eventdata, handles) + +set(handles.TText,'String','P [%]'); +handles.SelMode = 'Percentage'; + +handles.Log = CAP_AddToLog(handles.Log,'Changed time points selection scheme',{handles.SelMode},{'Selected mode'}); + +guidata(hObject,handles); + +%% Time points selection control +% When clicking on the select time points button, the frames matching the +% provided thresholds for scrubbing and for frame retention are selected. +% Both activation and deactivation frames are selected. This is performed +% on all the loaded populations of subjects + +% Upon clicking on the 'Select time points' button +function TPSelectionButton_Callback(hObject, eventdata, handles) + +% Clears the current plot display (for the case of having already computed +% something before with other parameters) +cla(handles.TPViolin); + +% Performs the analysis to extract frames of activity for all loaded +% populations (done for each dataset) +for n_ds = 1:handles.n_datasets + % Xonp and Xonn contain the frames (deactivation frames have been + % switched in sign, so that deactivation is positive) + if ~get(handles.Union_Checkbox,'Value') + [handles.Xonp{n_ds},handles.Xonn{n_ds},p,Indices] = CAP_find_activity(handles.TC{n_ds},handles.seed,handles.T,handles.FD{n_ds},handles.Tmot,handles.SelMode); + else + [handles.Xonp{n_ds},handles.Xonn{n_ds},p,Indices] = CAP_find_activity_twoseed(handles.TC{n_ds},handles.seed,handles.seed2,handles.T,handles.FD{n_ds},handles.Tmot); + end + % Percentage of retained frames for both activation and deactivation + % cases across subjects + handles.RetainedPercentage{n_ds} = p(4:5,:); + + % Indices of the frames that have been retained (used later for metrics + % computations) + handles.FrameIndices{n_ds} = Indices; +end + +% Enables to go to the next step of the analysis and cluster the extracted +% frames +set(handles.ClusterButton,'Enable','on'); + +tmp_toplot = ConcatMat(handles.RetainedPercentage,handles.n_datasets,2,handles.n_subjects,'FD'); + +% Displays the violin plot of subject scrubbing percentage for the +% reference population +[~,~,handles.TPViolin] = MakeViolin(tmp_toplot,handles.TPViolin,{'A','D'},'Frames ret. [%]',handles.PopColor,handles.n_datasets,2); +set(handles.TPViolin,'Visible','on'); + +handles.Log = CAP_AddToLog(handles.Log,'Time points selected',{['1 to ',num2str(handles.n_datasets)],handles.SelMode,handles.T,handles.Tmot},{'Datasets indices','Selection mode','Activation threshold','Motion threshold'}); + +guidata(hObject, handles); + + +%% Parameter control: number of clusters to use + +function ClusterEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 1) && (str2double(get(hObject,'String')) <= 12) + handles.K = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid number of clusters chosen',{handles.K},{'Number of clusters'}); + +else + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); + handles.K = 5; +end + +guidata(hObject, handles); + +function handles = ClusterEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + +function ClusterEdit_ButtonDownFcn(hObject,eventdata,handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + +%% Parameter control: number of replicates of the k-means algorithm + +function ClusterRepEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 50) + handles.n_rep = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid number of replicates chosen',{handles.n_rep},{'Number of replicates'}); + +else + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); + handles.n_rep = 20; +end + +guidata(hObject, handles); + +function handles = ClusterRepEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + +function ClusterRepEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + +%% Parameter control: percentage of positive voxels on which to cluster + +function ClusterPpEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.Pp = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentage positive voxels chosen',{handles.Pp},{'Percentage positive voxels'}); + +else + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); + handles.Pp = 20; +end + +guidata(hObject, handles); + +function handles = ClusterPpEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + +function ClusterPpEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + +%% Parameter control: percentage of negative voxels on which to cluster + +function ClusterPnEdit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.Pn = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentage negative voxels chosen',{handles.Pn},{'Percentage negative voxels'}); + +else + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); + handles.Pn = 20; +end + +guidata(hObject, handles); + +function handles = ClusterPnEdit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + +function ClusterPnEdit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject,handles); + +%% Clustering control +% When pressing on the 'Cluster' button, we want to run clustering for the +% specified mode (Activation frames, Deactivation frames, or both types of +% frames together), using the previously declared parameters + +% Upon clicking on 'Cluster' +function ClusterButton_Callback(hObject, eventdata, handles) + +% Determines which type of clustering we want to perform, and runs +% clustering accordingly + +% Clustering activation frames +if get(handles.ActRadio,'Value') + + handles.CAPType = 'Act'; + [handles.CAP,~,handles.STDCAP,handles.idx{handles.ReferencePopulation},... + handles.CorrDist] = Run_Clustering(cell2mat(handles.Xonp{handles.ReferencePopulation}),... + handles.K,handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn,handles.n_rep); + +% Clustering deactivation frames +elseif get(handles.DeactRadio,'Value') + + handles.CAPType = 'Deact'; + [handles.CAP,~,handles.STDCAP,handles.idx{handles.ReferencePopulation},... + handles.CorrDist] = Run_Clustering(cell2mat(handles.Xonn{handles.ReferencePopulation}),... + handles.K,handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn,handles.n_rep); + +% Clustering both activation and deactivation frames +elseif get(handles.BothFramesRadio,'Value') + + handles.CAPType = 'Both'; + + % In this last case, we want the activation and deactivation frames to + % be of opposite sign, so we invert the sign of Xonn + [handles.CAP,~,handles.STDCAP,handles.idx{handles.ReferencePopulation},... + handles.CorrDist] = Run_Clustering([cell2mat(handles.Xonp{handles.ReferencePopulation}),... + (-1)*cell2mat(handles.Xonn{handles.ReferencePopulation})],... + handles.K,handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn,handles.n_rep); + +% Normally we should NEVER enter this +else + errordlg('I have no idea how you got there, but yes, you made everything crash in a way I do not get... Congrats...'); +end + +% Makes the sliders visible, and the related text too (CAP MNI coordinates) +set(handles.CAP_SliderX,'Visible','on'); +set(handles.CAP_SliderY,'Visible','on'); +set(handles.CAP_SliderZ,'Visible','on'); +set(handles.CAP_XC,'Visible','on'); +set(handles.CAP_YC,'Visible','on'); +set(handles.CAP_ZC,'Visible','on'); +set(handles.CAP_XC,'String',['X: ',sprintf('%.2f',get(handles.CAP_SliderX,'Value'))]); +set(handles.CAP_YC,'String',['Y: ',sprintf('%.2f',get(handles.CAP_SliderY,'Value'))]); +set(handles.CAP_ZC,'String',['Z: ',sprintf('%.2f',get(handles.CAP_SliderZ,'Value'))]); + +% Same for the slider for the visualization threshold +set(handles.TVIS,'Visible','on'); +set(handles.TVIS_Slider,'Visible','on'); +set(handles.TVIS,'String',['Tv: ',sprintf('%.2f',get(handles.TVIS_Slider,'Value'))]); + +% Makes the colorbar for the CAPs visible +handles.ColorbarCAP = Create_CAP_colorbar(-1.5,1.5,0.5,get(handles.TVIS_Slider,'Value'),'',handles.ColorbarCAP,'Horizontal'); +set(handles.ColorbarCAP,'Visible','on'); + +% Concatenates all CAP information into metavariables for easier subsequent +% changes +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X,handles.CAP6X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y,handles.CAP6Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z,handles.CAP6Z}; +tmpF = {handles.CAP1_Frames,handles.CAP2_Frames,handles.CAP3_Frames,handles.CAP4_Frames,handles.CAP5_Frames,handles.CAP6_Frames}; + +% For each CAP... +for i_CAP = 1:min([handles.K,6]) + + % Clears the display for each dimension + cla(tmpX{i_CAP}); + cla(tmpY{i_CAP}); + cla(tmpZ{i_CAP}); + + % Plots the new slice for each dimension + tmpX{i_CAP} = plot_slice(handles.CAP(i_CAP,:)./handles.STDCAP(:,i_CAP)',... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'X',get(handles.CAP_SliderX,'Value'),tmpX{i_CAP}); + + tmpY{i_CAP} = plot_slice(handles.CAP(i_CAP,:)./handles.STDCAP(:,i_CAP)',... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'Y',get(handles.CAP_SliderY,'Value'),tmpY{i_CAP}); + + tmpZ{i_CAP} = plot_slice(handles.CAP(i_CAP,:)./handles.STDCAP(:,i_CAP)',... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'Z',get(handles.CAP_SliderZ,'Value'),tmpZ{i_CAP}); + + % Sets the frame percentage text visible and at the right value (number + % of frames from a CAP/total frame number, and then percentage that it + % stands for) + set(tmpF{i_CAP},'Visible','on'); + set(tmpF{i_CAP},'String',{[num2str(sum(handles.idx{handles.ReferencePopulation}==i_CAP)),'/',... + num2str(size(handles.idx{handles.ReferencePopulation},1))],[sprintf('%.2f',... + sum(handles.idx{handles.ReferencePopulation}==i_CAP)/size(handles.idx{handles.ReferencePopulation},1)*100),' [%]']}); +end + +% Fills that subject menu with the subjects from the reference population +handles = FillSubjectList(handles.SubjectMenuMetrics,handles); + +% Also enables and fills the state menu +handles = FillStateList(handles.StateMenu,handles); + + +% Enables the Metrics button for the next part of the analysis if we +% only deal with one dataset +if handles.n_datasets == 1 + set(handles.MetricsButton,'Enable','on'); +% Else, we enable the assignment before enabling the metrics computation +elseif handles.n_datasets > 1 + set(handles.AssignButton,'Enable','on'); +end + +handles.Log = CAP_AddToLog(handles.Log,'Clustering performed',... + {handles.ReferencePopulation,handles.K,handles.n_rep,handles.Pp,... + handles.Pn,handles.CAPType},{'Reference group index',... + 'Number of clusters','Number of replicates',... + 'Percentage positive voxels','Percentage negative voxels','Type of CAPs'}); + +% When we have done one k-means clustering run and thus have 'meaningful' +% brain states, we are allowed to go for the GMM option +set(handles.GMM_Button,'Enable','on'); + +guidata(hObject, handles); + +%% Clustering with the more elaborate GMM (Gaussian Mixture Model) method +function GMM_Button_Callback(hObject, eventdata, handles) + +if handles.K ~= size(handles.CAP,1) + errordlg('Please choose a matching value of K with respect to the latest run k-means trial!'); +end + +% Initializes GMM parameters with the results from k-means +for i = 1:handles.K + handles.Mu_GMM(:,i) = handles.CAP(i,:)'; + handles.Sigma_GMM{i} = sparse(1:handles.SubjSize.VOX,1:handles.SubjSize.VOX,handles.STDCAP(:,i)); + handles.Priors_GMM(i) = 1/handles.K; +end + +% handles.Mu_GMM = 2*rand(1000,4)-ones(1000,4); +% handles.Sigma_GMM = rand(1000,1000,4); + +% Clustering activation frames +if get(handles.ActRadio,'Value') + + handles.CAPType = 'Act'; + + [handles.Gamma_GMM,handles.Priors_GMM,handles.Mu_GMM,handles.Sigma_GMM,handles.CorrDist,handles.idx{handles.ReferencePopulation}] =... + CAP_GMM(cell2mat(handles.Xonp{handles.ReferencePopulation}),handles.K,... + handles.Priors_GMM,... + handles.Mu_GMM,... + handles.Sigma_GMM,... + handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn); + +% Clustering deactivation frames +elseif get(handles.DeactRadio,'Value') + + handles.CAPType = 'Deact'; + + [handles.Gamma_GMM,handles.Priors_GMM,handles.Mu_GMM,handles.Sigma_GMM,handles.CorrDist,handles.idx{handles.ReferencePopulation}] =... + CAP_GMM(cell2mat(handles.Xonn{handles.ReferencePopulation}),handles.K,... + handles.Priors_GMM,... + handles.Mu_GMM,... + handles.Sigma_GMM,... + handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn); + +% Clustering both activation and deactivation frames +elseif get(handles.BothFramesRadio,'Value') + + handles.CAPType = 'Both'; + + % In this last case, we want the activation and deactivation frames to + % be of opposite sign, so we invert the sign of Xonn + [handles.Gamma_GMM,handles.Priors_GMM,handles.Mu_GMM,handles.Sigma_GMM,handles.CorrDist,handles.idx{handles.ReferencePopulation}] =... + CAP_GMM([cell2mat(handles.Xonp{handles.ReferencePopulation}),... + (-1)*cell2mat(handles.Xonn{handles.ReferencePopulation})],handles.K,... + handles.Priors_GMM,... + handles.Mu_GMM,... + handles.Sigma_GMM,... + handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.Pp,handles.Pn); + +% Normally we should NEVER enter this +else + errordlg('I have no idea how you got there, but yes, you made everything crash in a way I do not get... Congrats...'); +end + +% Calculates 'CAP equivalents' from the GMM outcomes +handles.CAP = handles.Mu_GMM'; + +for i = 1:handles.K + handles.STDCAP(:,i) = sqrt(diag(handles.Sigma_GMM{i})); +end + +% Makes the sliders visible, and the related text too (CAP MNI coordinates) +set(handles.CAP_SliderX,'Visible','on'); +set(handles.CAP_SliderY,'Visible','on'); +set(handles.CAP_SliderZ,'Visible','on'); +set(handles.CAP_XC,'Visible','on'); +set(handles.CAP_YC,'Visible','on'); +set(handles.CAP_ZC,'Visible','on'); +set(handles.CAP_XC,'String',['X: ',sprintf('%.2f',get(handles.CAP_SliderX,'Value'))]); +set(handles.CAP_YC,'String',['Y: ',sprintf('%.2f',get(handles.CAP_SliderY,'Value'))]); +set(handles.CAP_ZC,'String',['Z: ',sprintf('%.2f',get(handles.CAP_SliderZ,'Value'))]); + +% Same for the slider for the visualization threshold +set(handles.TVIS,'Visible','on'); +set(handles.TVIS_Slider,'Visible','on'); +set(handles.TVIS,'String',['Tv: ',sprintf('%.2f',get(handles.TVIS_Slider,'Value'))]); + +% Makes the colorbar for the CAPs visible +handles.ColorbarCAP = Create_CAP_colorbar(-1.5,1.5,0.5,get(handles.TVIS_Slider,'Value'),'',handles.ColorbarCAP,'Horizontal'); +set(handles.ColorbarCAP,'Visible','on'); + +% Concatenates all CAP information into metavariables for easier subsequent +% changes +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X,handles.CAP6X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y,handles.CAP6Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z,handles.CAP6Z}; +tmpF = {handles.CAP1_Frames,handles.CAP2_Frames,handles.CAP3_Frames,handles.CAP4_Frames,handles.CAP5_Frames,handles.CAP6_Frames}; + +% For each CAP... +for i_CAP = 1:min([handles.K,6]) + + % Clears the display for each dimension + cla(tmpX{i_CAP}); + cla(tmpY{i_CAP}); + cla(tmpZ{i_CAP}); + + % Plots the new slice for each dimension + tmpX{i_CAP} = plot_slice(handles.CAP(i_CAP,:)./handles.STDCAP(:,i_CAP)',... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'X',get(handles.CAP_SliderX,'Value'),tmpX{i_CAP}); + + tmpY{i_CAP} = plot_slice(handles.CAP(i_CAP,:)./handles.STDCAP(:,i_CAP)',... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'Y',get(handles.CAP_SliderY,'Value'),tmpY{i_CAP}); + + tmpZ{i_CAP} = plot_slice(handles.CAP(i_CAP,:)./handles.STDCAP(:,i_CAP)',... + get(handles.TVIS_Slider,'Value'),1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},... + 'Z',get(handles.CAP_SliderZ,'Value'),tmpZ{i_CAP}); + + % Sets the frame percentage text visible and at the right value (number + % of frames from a CAP/total frame number, and then percentage that it + % stands for) + set(tmpF{i_CAP},'Visible','on'); + set(tmpF{i_CAP},'String',{[num2str(sum(handles.idx{handles.ReferencePopulation}==i_CAP)),'/',... + num2str(size(handles.idx{handles.ReferencePopulation},1))],[sprintf('%.2f',... + sum(handles.idx{handles.ReferencePopulation}==i_CAP)/size(handles.idx{handles.ReferencePopulation},1)*100),' [%]']}); +end + +% Fills that subject menu with the subjects from the reference population +handles = FillSubjectList(handles.SubjectMenuMetrics,handles); + +% Also enables and fills the state menu +handles = FillStateList(handles.StateMenu,handles); + +% Enables the Metrics button for the next part of the analysis if we +% only deal with one dataset +if handles.n_datasets == 1 + set(handles.MetricsButton,'Enable','on'); +% Else, we enable the assignment before enabling the metrics computation +elseif handles.n_datasets > 1 + set(handles.AssignButton,'Enable','on'); +end + +handles.Log = CAP_AddToLog(handles.Log,'Clustering performed',... + {handles.ReferencePopulation,handles.K,handles.n_rep,handles.Pp,... + handles.Pn,handles.CAPType},{'Reference group index',... + 'Number of clusters','Number of replicates',... + 'Percentage positive voxels','Percentage negative voxels','Type of CAPs'}); + +guidata(hObject, handles); + + + +%% Frame assignment control +% This button is only enabled after clustering has been performed on the +% reference population. It assigns frames from the other populations to the +% computed CAPs + +% Happens upon clicking on the 'Assign' buttons +function AssignButton_Callback(hObject, eventdata, handles) + +tmp_notref = []; +tmp_computedTPsel = []; + +% For each non-reference dataset... +for n_ds = 1:handles.n_datasets + if n_ds ~= handles.ReferencePopulation + + tmp_notref = [tmp_notref,n_ds]; + + % Attempts to access the frames for a given dataset; if it fails, it + % means we must compute activity. If it works, we do nothing because + % activity has already been computed + try + justtotest = handles.Xonp{n_ds}; + catch + if get(handles.Union_Checkbox,'Value') + [handles.Xonp{n_ds},handles.Xonn{n_ds},p,handles.FrameIndices{n_ds}] = ... + CAP_find_activity_twoseed(handles.TC{n_ds},handles.seed,handles.seed2,handles.T,handles.FD{n_ds},handles.Tmot); + else + [handles.Xonp{n_ds},handles.Xonn{n_ds},p,handles.FrameIndices{n_ds}] = ... + CAP_find_activity(handles.TC{n_ds},handles.seed,handles.T,handles.FD{n_ds},handles.Tmot,handles.SelMode); + end + handles.RetainedPercentage{n_ds} = p(4:5,:); + + tmp_computedTPsel = [tmp_computedTPsel,n_ds]; + end + + try + % Assigning activation frames + if strcmp(handles.CAPType,'Act') && get(handles.ActRadio,'Value') + handles.idx{n_ds} = CAP_AssignFrames(handles.CAP,cell2mat(handles.Xonp{n_ds}),handles.CorrDist,handles.percentile)'; + + % Assigning deactivation frames + elseif strcmp(handles.CAPType,'Deact') && get(handles.DeactRadio,'Value') + handles.idx{n_ds} = CAP_AssignFrames(handles.CAP,cell2mat(handles.Xonn{n_ds}),handles.CorrDist,handles.percentile)'; + + % Assigning both activation and deactivation frames + elseif strcmp(handles.CAPType,'Both') && get(handles.BothFramesRadio,'Value') + handles.idx{n_ds} = CAP_AssignFrames(handles.CAP,[cell2mat(handles.Xonp{n_ds}),cell2mat((-1)*handles.Xonn{n_ds})],handles.CorrDist,handles.percentile)'; + + % Normally we should NEVER enter this + else + errordlg('I have no idea how you got there, but yes, you made everything crash in a way I do not get... Congrats...'); + end + catch + errordlg('You computed CAPs with a different CAP type compared to the one used now; please use the same CAP type !'); + end + end +end + +% We then enable the computation of metrics +set(handles.MetricsButton,'Enable','on'); + +handles.Log = CAP_AddToLog(handles.Log,'Frame assignment performed',... + {handles.ReferencePopulation,num2str(tmp_computedTPsel),num2str(tmp_notref),... + handles.CAPType},{'Reference group index','Group indices for which frames were computed',... + 'Group indices for which frames were assigned','Type of CAPs'}); + +guidata(hObject, handles); + +%% Parameter control: percentile to use for frame assignment +% This asks for the percentile to use in frame assignment (i.e. the +% threshold of correlation below which frames are left unassigned) + +function Percentile_Edit_Callback(hObject, eventdata, handles) + +if ~isempty(str2double(get(hObject,'String'))) && (str2double(get(hObject,'String')) > 0) && (str2double(get(hObject,'String')) <= 100) + handles.percentile = str2double(get(hObject,'String')); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid percentile chosen',{handles.percentile},{'Percentile'}); + +else + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); + handles.percentile = 5; +end + +guidata(hObject, handles); + +function handles = Percentile_Edit_CreateFcn(hObject, eventdata, handles) + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +function Percentile_Edit_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +guidata(hObject, handles); + + +%% Sliders for CAP visualization (MNI coordinates) +% When changing along a slider, we want to update the graphs and the text of +% the MNI coordinate below the slider + +% X dimension slider +function CAP_SliderX_Callback(hObject, eventdata, handles) + +set(handles.CAP_XC,'String',['X: ',sprintf('%.2f',get(hObject,'Value'))]); +tmp_struct = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X,handles.CAP6X}; + +for i_CAP = 1:min([handles.K,6]) + cla(tmp_struct{i_CAP}); + tmp_struct{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(handles.TVIS_Slider,'Value'),... + 1.5,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(hObject,'Value'),tmp_struct{i_CAP}); +end + +guidata(hObject, handles); + +function CAP_SliderX_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +% Y dimension slider +function CAP_SliderY_Callback(hObject, eventdata, handles) + +set(handles.CAP_YC,'String',['Y: ',sprintf('%.2f',get(hObject,'Value'))]); +tmp_struct = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y,handles.CAP6Y}; + +for i_CAP = 1:min([handles.K,6]) + cla(tmp_struct{i_CAP}); + tmp_struct{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(handles.TVIS_Slider,'Value'),... + 1.5,handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Y',get(hObject,'Value'),tmp_struct{i_CAP}); +end + +guidata(hObject,handles); + +function CAP_SliderY_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +% Z dimension slider +function CAP_SliderZ_Callback(hObject, eventdata, handles) + +set(handles.CAP_ZC,'String',['Z: ',sprintf('%.2f',get(hObject,'Value'))]); +tmp_struct = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z,handles.CAP6Z}; + +for i_CAP = 1:min([handles.K,6]) + + cla(tmp_struct{i_CAP}); + tmp_struct{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(handles.TVIS_Slider,'Value'),... + 1.5,handles.mask{handles.ReferencePopulation},handles.brain,handles.brain_info{handles.ReferencePopulation},'Z',get(hObject,'Value'),tmp_struct{i_CAP}); +end + +guidata(hObject,handles); + +function CAP_SliderZ_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + +%% Sliders for threshold visualization (CAP analysis) +% Again, we want to update the slices and the text if we change those +% sliders + +function TVIS_Slider_Callback(hObject, eventdata, handles) + +% The text is changed +set(handles.TVIS,'String',['Tv: ',sprintf('%.2f',get(hObject,'Value'))]); + +% The colorbar graph is modified to suit the new threshold value +cla(handles.ColorbarCAP); +handles.ColorbarCAP = Create_CAP_colorbar(-1.5,1.5,0.5,get(hObject,'Value'),'',handles.ColorbarCAP,'Horizontal'); + +% The brain slices are replotted +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X,handles.CAP6X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y,handles.CAP6Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z,handles.CAP6Z}; + +for i_CAP = 1:min([handles.K,6]) + + cla(tmpX{i_CAP}); + cla(tmpY{i_CAP}); + cla(tmpZ{i_CAP}); + + tmpX{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(hObject,'Value'),1.5,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'X',get(handles.CAP_SliderX,'Value'),tmpX{i_CAP}); + + tmpY{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(hObject,'Value'),1.5,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Y',get(handles.CAP_SliderY,'Value'),tmpY{i_CAP}); + + tmpZ{i_CAP} = plot_slice(handles.CAP(i_CAP,:),get(hObject,'Value'),1.5,... + handles.mask{handles.ReferencePopulation},handles.brain,... + handles.brain_info{handles.ReferencePopulation},'Z',get(handles.CAP_SliderZ,'Value'),tmpZ{i_CAP}); +end + +guidata(hObject,handles); + +function TVIS_Slider_CreateFcn(hObject, eventdata, handles) + +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%% PART 4: METRICS COMPUTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Metrics computation control +% When pressing on the 'Compute metrics' button, the different metrics for +% CAP analysis are computed, including: +% - Similarity between CAPs +% - Transition probabilities from a state to the other (average + subject) +% - Sequence of states for each subject +% - Cumulative state sequence for all subjects +% - Counts (number of frames in a state) +% - Number of times entering a state, and duration spent in a state + +function MetricsButton_Callback(hObject, eventdata, handles) + +% All the metrics are computed for all the datasets +for n_ds = 1:handles.n_datasets + if n_ds == handles.ReferencePopulation + tmp_nclust = handles.K; + else + tmp_nclust = handles.K+1; + end + + try + [handles.TPM{n_ds},handles.Counts{n_ds},... + handles.Number{n_ds},handles.Avg_Duration{n_ds},... + handles.Duration{n_ds},handles.TM{n_ds}] =... + Compute_Metrics(handles.idx{n_ds},handles.FrameIndices{n_ds}.kept.active,... + handles.FrameIndices{n_ds}.kept.deactive,handles.FrameIndices{n_ds}.scrubbed,... + tmp_nclust,handles.TR,handles.CAPType); + catch + + errordlg('You tried computing metrics using parameter values different from the ones that were employed to generate CAPs; please check !'); + end +end + +handles.Log = CAP_AddToLog(handles.Log,'Metrics computed',... + {handles.n_datasets,handles.K,handles.TR,handles.CAPType},... + {'Number of datasets','Number of clusters','TR','CAP type'}); + +% 1. Display of the similarity matrix between CAPs + +% Computation of the similarity +SimMat = corr(handles.CAP',handles.CAP'); + +% Graph set visible, and plotting +handles = ResetGraphDisplay(handles.CAP_Mat,handles); +set(handles.CAP_Mat,'Visible','on'); +imagesc(SimMat,'Parent',handles.CAP_Mat); + +% Correlation ranges from -1 to 1, so this is what we make the graph +% colorbar vary within. We also make the graph square and remove the axes +caxis(handles.CAP_Mat,[-1 1]); +axis(handles.CAP_Mat,'square','on'); +axis(handles.CAP_Mat,'off'); + +% Addition of the colorbar just below +set(handles.ColorbarSimMat,'Visible','on'); +handles.ColorbarSimMat = Create_CAP_colorbar(-1,1,0.5,0,'',... + handles.ColorbarSimMat,'Horizontal'); + + +% 2. Transition matrix for all subjects together + +tmp_toplot = squeeze(mean(handles.TM{handles.ReferencePopulation},3)); +tmp_toplot = tmp_toplot(4:end,4:end); + +% Make graph visible and plotting +handles = ResetGraphDisplay(handles.TMGraph,handles); +set(handles.TMGraph,'Visible','on'); +imagesc(tmp_toplot,'Parent',handles.TMGraph); + +clear tmp_toplot + +% Arbitrary setting of probability scale from 0 to 0.03 +caxis(handles.TMGraph,[0 0.03]); +axis(handles.TMGraph,'square','on'); +axis(handles.TMGraph,'off'); + +% 3. Transition matrix for one subject + +tmp_toplot = squeeze(handles.TM{handles.ReferencePopulation}(:,:,get(handles.SubjectMenuMetrics,'Value'))); +tmp_toplot = tmp_toplot(4:end,4:end); + +% makes graph visible and plots the information given by the Subject popup +handles = ResetGraphDisplay(handles.TM_Subject,handles); +set(handles.TM_Subject,'Visible','on'); +imagesc(tmp_toplot,... + 'Parent',handles.TM_Subject); + +clear tmp_toplot + +% Same setting for the axes as for the average graph +caxis(handles.TM_Subject,[0 0.03]); +axis(handles.TM_Subject,'square','on'); +axis(handles.TM_Subject,'off'); + +% We then create the colorbar for both cases +set(handles.ColorbarTransMat,'Visible','on'); +handles.ColorbarTransMat = Create_CAP_colorbar(0,0.03,0.01,0,'',... + handles.ColorbarTransMat,'Horizontal'); + +% Makes the subject menu visible +set(handles.SubjectMenuMetrics,'Visible','on'); + +% 4. Dynamic state plotting + +%%%%%%%% TO SOLVE SOMEHOW: for the moment, the states are displayed with +%%%%%%%% the classical colorbar. The problem is that using another colorbar +%%%%%%%% instead will make all the figures use that colorbar. For now, I +%%%%%%%% could not find a solution to have only that specific figure be +%%%%%%%% plotted with different colors + +% Makes the graph visible +handles = ResetGraphDisplay(handles.DynStates,handles); +set(handles.DynStates,'Visible','on'); + +% Concatenates information from the different datasets +tmp_toplot = []; + +for i = 1:handles.n_datasets + tmp_toplot = [tmp_toplot; handles.TPM{i}; -2*ones(5,handles.SubjSize.TP)]; +end +tmp_toplot = tmp_toplot(1:end-5,:); + +% If the TR has been properly entered, the x-axis is time; else, it depicts +% time index. In any case, we plot the states +if handles.isTROK + imagesc(tmp_toplot,'Parent',handles.DynStates); + xlabel(handles.DynStates,'Time [s]'); +else + imagesc(tmp_toplot,'Parent',handles.DynStates); + xlabel(handles.DynStates,'Time index [-]'); +end + +ylabel(handles.DynStates,'Subjects [-]'); +axis(handles.DynStates,'off'); +caxis(handles.DynStates,[-2.5,handles.K+0.5]); + +clear tmp_toplot + +% 5. Cumulative state distributions + +% Makes the graph visible +handles = ResetGraphDisplay(handles.CumStates,handles); +set(handles.CumStates,'Visible','on'); + +for i = 1:handles.n_datasets + % Cumulative distribution for the state that we want to be displayed (i.e. + % the state from the popup menu) + handles.TPMCum{i} = cumsum(handles.TPM{i} == get(handles.StateMenu,'Value'),2); + + % Average of the considered state across subjects + tmp_TPMCum{i} = mean(handles.TPMCum{i},1); +end + +% Similarly as above, we plot time if we have a valid TR; else, we plot +% 'time index' +if handles.isTROK == false + + for i = 1:handles.n_datasets + % We first plot each subject curve + for j = 1:size(handles.TPMCum{i},1) + plot(handles.TPMCum{i}(j,:),'Color',handles.PopColor{1}(i,:),... + 'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + % Then, we plot a bold average across subjects + plot(tmp_TPMCum{i},'Color',handles.PopColor{2}(i,:),... + 'LineWidth',2,'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time index [-]','FontSize',8); + end +else + for i = 1:handles.n_datasets + for j = 1:size(handles.TPMCum{i},1) + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + handles.TPMCum{i}(j,:),... + 'Color',handles.PopColor{1}(i,:),'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + tmp_TPMCum{i},... + 'LineWidth',2,'Color',handles.PopColor{2}(i,:),'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time [s]','FontSize',8); + end +end + + +ylabel(handles.CumStates,'Cumul. sum [-]','FontSize',8); +set(handles.CumStates,'Box','off'); + +% Makes the state menu visible +set(handles.StateMenu,'Visible','on'); + +% 6. Violin plots +% Below, we plot violin plots depicting: +% - Raw counts of state excursions +% - Fractional counts of state excursions +% - Number of times entering a state +% - Duration of state excursions + +% We build the legend used to plot the violins +% leg_viol = cell(handles.K); +for i = 1:handles.K + leg_viol{i} = num2str(i); +end + +% Makes graphs ready +handles = ResetGraphDisplay(handles.ViolinCounts,handles); +set(handles.ViolinCounts,'Visible','on'); + +handles = ResetGraphDisplay(handles.ViolinCountsFrac,handles); +set(handles.ViolinCountsFrac,'Visible','on'); + +handles = ResetGraphDisplay(handles.ViolinNumber,handles); +set(handles.ViolinNumber,'Visible','on'); + +handles = ResetGraphDisplay(handles.ViolinDuration,handles); +set(handles.ViolinDuration,'Visible','on'); + +% Concatenates the values from the different populations +tmp_toplot = ConcatMat(handles.Counts,handles.n_datasets,handles.K,handles.n_subjects,'Raw counts'); + +% Plots the raw count values +[~,~,handles.ViolinCounts] = MakeViolin(tmp_toplot,... + handles.ViolinCounts,leg_viol,'Raw counts [-]',handles.PopColor,handles.n_datasets,handles.K); + +clear tmp_toplot + +tmp_toplot = ConcatMat(handles.Counts,handles.n_datasets,handles.K,handles.n_subjects,'Normalized counts'); + +% Plots the normalized count values +[~,~,handles.ViolinCountsFrac] = MakeViolin(tmp_toplot,... + handles.ViolinCountsFrac,leg_viol,'Norm counts [-]',handles.PopColor,handles.n_datasets,handles.K); + +clear tmp_toplot + +tmp_toplot = ConcatMat(handles.Number,handles.n_datasets,handles.K,handles.n_subjects,'Number'); + +% Plots the number of times a state is entered +[~,~,handles.ViolinNumber] = MakeViolin(tmp_toplot,... + handles.ViolinNumber,leg_viol,'Number [-]',handles.PopColor,handles.n_datasets,handles.K); + +clear tmp_toplot + + +% Plots the duration graph +if handles.isTROK + + tmp_toplot = ConcatMat(handles.Avg_Duration,handles.n_datasets,handles.K,handles.n_subjects,'Duration'); + + [~,~,handles.ViolinDuration] = MakeViolin(tmp_toplot,... + handles.ViolinDuration,leg_viol,'Dur. [s]',handles.PopColor,handles.n_datasets,handles.K); + + clear tmp_toplot + +else + errordlg('Not showing duration violin plot as TR was not entered !'); + set(handles.ViolinDuration,'Visible','off'); +end + +guidata(hObject,handles); + +%% Subject popup menu control (metrics computation) +% When a new subject is chosen, the display of the transition matrix graph +% is changed + +function SubjectMenuMetrics_Callback(hObject, eventdata, handles) + +% In the case when we have something to plot... +try + % ... we reset the graph display, make the graph visible, and plot + % again + handles = ResetGraphDisplay(handles.TM_Subject,handles); + set(handles.TM_Subject,'Visible','on'); + + tmp_toplot = squeeze(handles.TM{handles.ReferencePopulation}(:,:,get(handles.SubjectMenuMetrics,'Value'))); + tmp_toplot = tmp_toplot(4:end,4:end); + + imagesc(tmp_toplot,'Parent',handles.TM_Subject); + caxis(handles.TM_Subject,[0 0.06]); + axis(handles.TM_Subject,'square','on'); + axis(handles.TM_Subject,'off'); + + clear tmp_toplot + + handles.Log = CAP_AddToLog(handles.Log,'Subject index changed (metrics)',... + {get(hObject,'Value')},... + {'Subject index'}); + +catch + errordlg('Please recompute metrics for the presently considered population !'); +end + +guidata(hObject,handles); + +function SubjectMenuMetrics_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +%% State menu popup control +% When we change our state of interest, we will change the display of the +% cumulative state being displayed + +function StateMenu_Callback(hObject, eventdata, handles) + +handles = ResetGraphDisplay(handles.CumStates,handles); +set(handles.CumStates,'Visible','on'); + +% In the case of a non-null matrix... +if ~isempty(handles.TPM) + + for i = 1:handles.n_datasets + % Cumulative distribution for the state that we want to be displayed (i.e. + % the state from the popup menu) + handles.TPMCum{i} = cumsum(handles.TPM{i} == get(handles.StateMenu,'Value'),2); + + % Average of the considered state across subjects + tmp_TPMCum{i} = mean(handles.TPMCum{i},1); + end + + % Similarly as above, we plot time if we have a valid TR; else, we plot + % 'time index' + if handles.isTROK == false + + for i = 1:handles.n_datasets + % We first plot each subject curve + for j = 1:size(handles.TPMCum{i},1) + plot(handles.TPMCum{i}(j,:),'Color',handles.PopColor{1}(i,:),... + 'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + % Then, we plot a bold average across subjects + plot(tmp_TPMCum{i},'Color',handles.PopColor{2}(i,:),... + 'LineWidth',2,'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time index [-]','FontSize',8); + end + else + for i = 1:handles.n_datasets + for j = 1:size(handles.TPMCum{i},1) + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + handles.TPMCum{i}(j,:),... + 'Color',handles.PopColor{1}(i,:),'Parent',handles.CumStates); + hold(handles.CumStates,'on'); + end + end + + for i = 1:handles.n_datasets + plot(((1:size(handles.TPM{i},2))-1)*handles.TR,... + tmp_TPMCum{i},... + 'LineWidth',2,'Color',handles.PopColor{2}(i,:),'Parent',handles.CumStates); + xlabel(handles.CumStates,'Time [s]','FontSize',8); + end + end + + +ylabel(handles.CumStates,'Cumul. sum [-]','FontSize',8); +set(handles.CumStates,'Box','off'); + +end + +guidata(hObject,handles); + +function StateMenu_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%% GENERAL UTILITIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Save Folder Control +% Upon clicking on the 'select save folder' button, the user can choose the +% directory where data will be saved. + +function SaveFolderButton_Callback(hObject, eventdata, handles) + +% Selection of a directory +[dirname]=uigetdir('*.*','Please select a save directory'); +handles.savedir = dirname; + +% If the user has indeed chosen a directory, we set it as the new save +% folder +if ~isequal(dirname,0) + set(handles.SaveFolderText,'String',handles.savedir); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Save folder changed',... + {handles.savedir},... + {'Save folder'}); +else + errordlg('Please select a directory !'); +end + + +%% Save Button Control +% Upon clicking on the 'SAVE' button, the data will be saved entirely under +% a file name partly chosen by the user and partly depending on the present +% date and time + +function SaveButton_Callback(hObject, eventdata, handles) + +% Upon pressing the save button, we want to save all the important +% information into a big matlab structure +SAVED = []; + +% General information on the project +SAVED.ProjectInfo.title = handles.project_title; +SAVED.ProjectInfo.date = date; + +% Name of the files that were loaded +SAVED.SubjData.SubjFileNames = handles.SubjNames; +SAVED.SubjData.MotFileNames = handles.MotName; + +% Dimension over time and voxels of the files analyzed +SAVED.SubjData.Dimensions.TP = handles.SubjSize.TP; +SAVED.SubjData.Dimensions.VOX = handles.SubjSize.VOX; + +% Number of subjects considered +SAVED.SubjData.n_subjects = handles.n_subjects; + +% TR of the experiment +SAVED.SubjData.TR = handles.TR; + +% Information about the NIFTI files used (dimensions, mapping between real +% world and index) +SAVED.BrainData.brain_info = handles.brain_info; + +% Mask that was used on the considered data +SAVED.BrainData.mask = handles.mask; + +% Seed used for the analysis +SAVED.BrainData.seed = handles.seed; +SAVED.BrainData.seed2 = handles.seed2; + +% Motion threshold and activation threshold used in time points selection +SAVED.TPSelData.Tmot = handles.Tmot; +SAVED.TPSelData.T = handles.T; + +% Type of frame selection used +SAVED.TPSelData.SelMode = handles.SelMode; + +% Frames that were considered in the clustering process +SAVED.TPSelData.Act = handles.Xonp; +SAVED.TPSelData.Deact = handles.Xonn; + +% Percentage frames retained for the clustering +SAVED.TPSelData.PercRetained = handles.RetainedPercentage; + +% Computed seed maps (average and subject-wise) +SAVED.SeedMap.AvgMap = handles.AvgSeedMap; +SAVED.SeedMap.SubjMaps = handles.SeedMaps; + +% Parameters used for clustering +SAVED.ClusterData.N = handles.n_rep; +SAVED.ClusterData.K = handles.K; +SAVED.ClusterData.Pp = handles.Pp; +SAVED.ClusterData.Pn = handles.Pn; +SAVED.ClusterData.CAPType = handles.CAPType; + +% CAP data +SAVED.ClusterData.CAPs = handles.CAP; +SAVED.ClusterData.StdCAPs = handles.STDCAP; +SAVED.ClusterData.idx = handles.idx; + +% GMM data +SAVED.GMMData.Mu = handles.Mu_GMM; +SAVED.GMMData.Sigma = handles.Sigma_GMM; +SAVED.GMMData.Priors = handles.Priors_GMM; +SAVED.GMMData.Gamma = handles.Gamma_GMM; + +% Computed metrics +SAVED.Metrics.TPM = handles.TPM; +SAVED.Metrics.Counts = handles.Counts; +SAVED.Metrics.Number = handles.Number; +SAVED.Metrics.Avg_Duration = handles.Avg_Duration; +SAVED.Metrics.Duration = handles.Duration; +SAVED.Metrics.TM = handles.TM; + +[tmp_date,tmp_date2] = strtok(date,'-'); +[tmp_date2,tmp_date3] = strtok(tmp_date2,'-'); +tmp_date3 = strtok(tmp_date3,'-'); + +% Name that will be given to the saved files +fancy_name = [handles.project_title,'_',tmp_date,'_',tmp_date2,'_',tmp_date3,'_',... + num2str(hour(now)),'_',num2str(minute(now)),'_',... + num2str(round(second(now)))]; + +% Saves NIFTI files storing the CAPs (already 'normalized'), in MNI space +CAPToNIFTI(handles.CAP./handles.STDCAP',... + handles.mask{handles.ReferencePopulation},handles.brain_info{handles.ReferencePopulation},... + handles.savedir,['CAP_NIFTI_',fancy_name]); + +% Saves the different variables from the program +save(fullfile(handles.savedir,fancy_name),'SAVED','-v7.3'); + +% Adds the save process to the log +handles.Log = CAP_AddToLog(handles.Log,'Data saved'); + +% Writes a log .txt file with what has been done so far +file_ID = fopen(fullfile(handles.savedir,[fancy_name,'.txt']),'wt'); + +for i = 1:length(handles.Log) + for j = 1:length(handles.Log{i}) + fprintf(file_ID,[handles.Log{i}{j},'\n']); + end + fprintf(file_ID,'\n'); +end + +fclose(file_ID); + +% Clears the structure now that it has been saved +clear SAVED + + +%% Clear functions +% Executes when pressing on the 'CLEAR' button for data loading; supposed +% to set everything back to normal (when the window opened) + +function handles = ClearDataButton_Callback(hObject, eventdata, handles) + +%%%%%%%%%% Putting the loading part (top) back to normal %%%%%%%%%%% + +% Makes 'A. Load data' red again +set(handles.DataButton,'BackgroundColor',[0.93 0.84 0.84]); + +% Resets the time point and voxel parameters +handles.SubjSize.TP = -inf; +handles.SubjSize.VOX = -inf; + +% Resets the TR +handles.TR = -inf; +handles.isTROK = false; + +% Resets the reference population +handles.ReferencePopulation = 1; +set(handles.RefPop_Text,'String','Reference group'); + +% Also resets the number of subjects variable and associated text +set(handles.Dimensionality_Text, 'String','_ frames x _ voxels (_)'); +handles.n_subjects = {}; + +% Resets the number of datasets entered to 0 +handles.n_datasets = 0; + +% Makes the reference population list invisible again +set(handles.RefPop,'Visible','off'); + +% Empties the data, motion, brain information and mask variables +handles.TC = {}; +handles.FD = {}; +handles.mask = {}; +handles.brain_info = {}; + +%%%%%%%%%% Putting the loading part (bottom) back to normal %%%%%%%%%%% + +% We also want to set the TR textbox back to its initial state +handles = TR_Entry_CreateFcn(handles.TR_Entry, eventdata, handles); + +% Puts back the seed buttons information to original state +handles.seed = []; +handles.seed2 = []; +set(handles.SeedButton,'BackgroundColor',[0.93 0.84 0.84]); +set(handles.SeedButton,'Enable','off'); +set(handles.PlotSeedButton,'Enable','off'); + +% Removes graph display for the seed +cla(handles.SeedGraphX); +cla(handles.SeedGraphY); +cla(handles.SeedGraphZ); +set(handles.SeedGraphX,'Visible','off'); +set(handles.SeedGraphY,'Visible','off'); +set(handles.SeedGraphZ,'Visible','off'); +set(handles.SliderX,'Visible','off'); +set(handles.SliderY,'Visible','off'); +set(handles.SliderZ,'Visible','off'); +set(handles.XCoordText,'Visible','off'); +set(handles.YCoordText,'Visible','off'); +set(handles.ZCoordText,'Visible','off'); + +%%%%%%%%%%%% Putting the seed map part back to normal %%%%%%%%%%%%%%%%%%% + +% Resets the variable containing the seed maps of the subjects +handles.SeedMaps = {}; +handles.AvgSeedMap = []; + +% Not clickable anymore +set(handles.SeedMapPushButton,'Enable','off'); + +% No more subject list visible +set(handles.SubjectMenu,'Visible','off'); + +% Resets colorbar display +handles = ResetGraphDisplay(handles.ColorbarSeed,handles); + +% Makes the slider and the text linked to slider of the seed map threshold +% back to invisible +set(handles.TSeed_Slider,'Visible','off'); +set(handles.TSeed,'Visible','off'); + +% Resets graphs with seed map plots +handles = ResetGraphDisplay(handles.SeedMapX,handles); +handles = ResetGraphDisplay(handles.SeedMapY,handles); +handles = ResetGraphDisplay(handles.SeedMapZ,handles); +handles = ResetGraphDisplay(handles.SubjSeedMapX,handles); +handles = ResetGraphDisplay(handles.SubjSeedMapY,handles); +handles = ResetGraphDisplay(handles.SubjSeedMapZ,handles); + +% Resets associated sliders +set(handles.SeedMapSliderX,'Visible','off'); +set(handles.SeedMapSliderY,'Visible','off'); +set(handles.SeedMapSliderZ,'Visible','off'); + +% Resets associated slider texts +set(handles.SeedMap_SliderX,'Visible','off'); +set(handles.SeedMap_SliderY,'Visible','off'); +set(handles.SeedMap_SliderZ,'Visible','off'); + +%%%%%%%%%% Putting the CAP analysis part back to normal %%%%%%%%%%%%%%%%%% + +% Buttons not clickable anymore (frame selection and clustering) +set(handles.TPSelectionButton,'Enable','off'); +set(handles.ClusterButton,'Enable','off'); +set(handles.GMM_Button,'Enable','off'); +set(handles.AssignButton,'Enable','off'); + +% Reinitializes motion and the motion box +handles.Tmot = 0.5; +handles = TMotEdit_CreateFcn(handles.TMotEdit,eventdata,handles); + +% Reinitializes frame selection threshold and the linked box +handles.T = 0.5; +handles = TEdit_CreateFcn(handles.TEdit,eventdata,handles); + +% Resets the frame and percentage retention variables +handles.Xonp = {}; +handles.Xonn = {}; +handles.RetainedPercentage = {}; +handles.FrameIndices = {}; + +% Resets the violin plot with percentage retained frames +handles = ResetGraphDisplay(handles.TPViolin,handles); + +% Resets the parameter input boxes +handles = ClusterEdit_CreateFcn(handles.ClusterEdit,eventdata,handles); +handles = ClusterRepEdit_CreateFcn(handles.ClusterRepEdit,eventdata,handles); +handles = ClusterPpEdit_CreateFcn(handles.ClusterPpEdit,eventdata,handles); +handles = ClusterPnEdit_CreateFcn(handles.ClusterPnEdit,eventdata,handles); +handles = Percentile_Edit_CreateFcn(handles.Percentile_Edit,eventdata,handles); + +% Resets the parameters themselves +handles.K = 5; +handles.n_rep = 20; +handles.Pp = 100; +handles.Pn = 100; +handles.percentile = 5; + +% Resets the type of performed clustering +handles.CAPType = ''; + +% Resets the CAP parameters (CAPs, standard deviation within CAPs and +% indices of the CAPs to which all retained frames were assigned) +handles.CAP = []; +handles.STDCAP = []; +handles.idx = {}; + +% Resets the graph display of the CAP colorbar +handles = ResetGraphDisplay(handles.ColorbarCAP,handles); + +% Reset all graph displays for the CAPs +tmpX = {handles.CAP1X,handles.CAP2X,handles.CAP3X,handles.CAP4X,handles.CAP5X,handles.CAP6X}; +tmpY = {handles.CAP1Y,handles.CAP2Y,handles.CAP3Y,handles.CAP4Y,handles.CAP5Y,handles.CAP6Y}; +tmpZ = {handles.CAP1Z,handles.CAP2Z,handles.CAP3Z,handles.CAP4Z,handles.CAP5Z,handles.CAP6Z}; +tmpF = {handles.CAP1_Frames,handles.CAP2_Frames,handles.CAP3_Frames,handles.CAP4_Frames,handles.CAP5_Frames,handles.CAP6_Frames}; + + +for i_CAP = 1:6 + set(tmpF{i_CAP},'Visible','off'); + handles = ResetGraphDisplay(tmpX{i_CAP},handles); + handles = ResetGraphDisplay(tmpY{i_CAP},handles); + handles = ResetGraphDisplay(tmpZ{i_CAP},handles); +end + +% Resets the sliders and the textboxes for the CAPs +set(handles.CAP_SliderX,'Visible','off'); +set(handles.CAP_SliderY,'Visible','off'); +set(handles.CAP_SliderZ,'Visible','off'); +set(handles.CAP_XC,'Visible','off'); +set(handles.CAP_YC,'Visible','off'); +set(handles.CAP_ZC,'Visible','off'); + +% Resets the slider and textbox for the CAPs visualization threshold +set(handles.TVIS_Slider,'Visible','off'); +set(handles.TVIS,'Visible','off'); + +%%%%%%%%%%% Putting the metrics part back to normal %%%%%%%%%%%%%%%%%%%%%% + +set(handles.MetricsButton,'Enable','off'); + +% Resets the metrics variables +handles.TPM = {}; +handles.Counts = {}; +handles.Number = {}; +handles.Avg_Duration = {}; +handles.Duration = {}; +handles.TM = {}; +handles.TPMCum = {}; + +% Set the sliding lists of subjects invisible again +set(handles.SubjectMenuMetrics,'Visible','off'); +set(handles.StateMenu,'Visible','off'); + +% Resets the colorbars from the metrics part +handles = ResetGraphDisplay(handles.ColorbarSimMat,handles); +handles = ResetGraphDisplay(handles.ColorbarTransMat,handles); + +% Resets all the graphs from the metrics part +handles = ResetGraphDisplay(handles.CAP_Mat,handles); +handles = ResetGraphDisplay(handles.TMGraph,handles); +handles = ResetGraphDisplay(handles.TM_Subject,handles); +handles = ResetGraphDisplay(handles.DynStates,handles); +handles = ResetGraphDisplay(handles.CumStates,handles); +handles = ResetGraphDisplay(handles.ViolinCounts,handles); +handles = ResetGraphDisplay(handles.ViolinCountsFrac,handles); +handles = ResetGraphDisplay(handles.ViolinNumber,handles); +handles = ResetGraphDisplay(handles.ViolinDuration,handles); + +handles.Log = CAP_AddToLog(handles.Log,'Data cleared'); + +guidata(hObject, handles); + +% Resets the display of a graph object (called within the clear function) +function handles = ResetGraphDisplay(Graph,handles) +cla(Graph); +set(Graph,'Visible','off'); + +%% Popup Filling Utilities + +% Fills the entries of a pop-up menu with 'Subject _' entries from the +% reference population +function handles = FillSubjectList(ToFill,handles) + +tmp_string = {}; + +for ns = 1:handles.n_subjects{handles.ReferencePopulation} + tmp_string{ns} = ['Subject ',num2str(ns)]; +end + +set(ToFill,'String',tmp_string); + +clear tmp_string + +% Fills the entries of a pop-up menu with the different population entries +function handles = FillPopulationList(ToFill,handles) + +tmp_string = {}; + +for ns = 1:handles.n_datasets + tmp_string{ns} = [handles.SubjNames{ns}]; +end + +set(ToFill,'String',tmp_string); + +clear tmp_string + +% Fills the entries of a pop-up menu with the different population entries +function handles = FillStateList(ToFill,handles) + +tmp_string = {}; + +for ns = 1:handles.K + tmp_string{ns} = ['State ',num2str(ns)]; +end + +set(ToFill,'String',tmp_string); + +clear tmp_string + +%% Project Title Controls +% Selects the title to give to the project + +function ProjectTitleText_ButtonDownFcn(hObject, eventdata, handles) + +set(hObject,'Enable','on'); +set(hObject,'String',''); +set(hObject,'FontAngle','normal'); +uicontrol(hObject); + +function ProjectTitleText_CreateFcn(hObject, eventdata, handles) + +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + +set(hObject,'Enable','off'); +set(hObject,'String','Click to enter...'); +set(hObject,'FontAngle','italic'); + +guidata(hObject, handles); + +function ProjectTitleText_Callback(hObject, eventdata, handles) + +% If we have entered a valid string, then we name the project as such +if ~isempty((get(hObject,'String'))) + handles.project_title = get(hObject,'String'); + set(hObject,'BackgroundColor', [0.4 0.6 0.4]); + + handles.Log = CAP_AddToLog(handles.Log,'Valid project title entered',{handles.project_title},{'New project title'}); + +% If we haven't entered anything, the project is just named 'untitled' +else + handles.project_title = 'Untitled'; + set(hObject,'BackgroundColor',[0.93 0.84 0.84]); +end + +guidata(hObject, handles); + +%% Matrix modulation utilities + +% Removes NaN-containing lines from a matrix (used for the plotting of +% duration violin plots) +function M2 = DiscardNaN(M) + +% We initialize the output matrix as a void one +M2 = []; + +% For each row, we count the amount of NaN entries; if not equal to zero, +% then we discard the line +for i = 1:size(M,1) + if sum(isnan(M(i,:))) > 0 + else + M2 = [M2;M(i,:)]; + end +end + +% Concatenates populations appropriately for Violin plotting +function M2 = ConcatMat(M,n_pop,n_states,n_subjects,type) + +% Creates the data matrix (nan values are used to have the same amount of +% data for each group) +M2 = nan(n_pop*n_states,max(cell2mat(n_subjects))); + +for i = 1:n_pop + + switch type + case 'Raw counts' + + tmp = M{i}.raw.state(:,1:n_states)'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + + case 'Normalized counts' + + tmp = M{i}.frac.state(:,1:n_states)'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + + case 'Number' + + tmp = M{i}(:,4:4+n_states-1)'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + + case 'Duration' + tmp = DiscardNaN(M{i}(:,4:4+n_states-1))'; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + + clear tmp + case 'FD' + tmp = M{i}; + + for j = 1:n_states + M2(i+(j-1)*n_pop,1:size(tmp,2)) = tmp(j,:); + end + end +end diff --git a/ComputeClusteringQuality.m b/ComputeClusteringQuality.m new file mode 100755 index 0000000..4259d93 --- /dev/null +++ b/ComputeClusteringQuality.m @@ -0,0 +1,57 @@ +function [CDF,Lorena] = ComputeClusteringQuality(Consensus,K_range) + + % Number of K values to check + K = size(Consensus,3); + + % Number of pairs of frames + n_items = size(Consensus,1); + + % Creates the CDF range + c = 0:0.005:1; + + % Quality criterion computations are run for each explored K value... + for k = 1:K + + % Sorted consensus entries + Cons_val = sort(jUpperTriMatToVec(squeeze(Consensus(:,:,k))),'ascend'); + + % Computation of CDF + for i = 1:length(c) + CDF(k,i) = sum(Cons_val <= c(i))/(n_items*(n_items-1)/2); + end + + + idx_dp = 1; + + for delta_perc = 0.5:0.5:10 + + Lorena(k,idx_dp) = prctile(CDF(k,:),100-delta_perc) - prctile(CDF(k,:),delta_perc); + idx_dp = idx_dp + 1; + end + + + + + % Computation of the AUC +% AUC(k) = 0; +% +% for i = 2:(n_items*(n_items-1)/2) +% AUC(k) = AUC(k) + (Cons_val(i)-Cons_val(i-1))* interp1q(c',CDF(k,:)',Cons_val(i)); +% end + + clear Cons_val + end + +% for k = 2:K +% +% % Computation of Delta +% +% tmp_max_AUC = max(AUC(1:k-1)); +% +% Delta(k) = (AUC(k) - tmp_max_AUC)/tmp_max_AUC; +% +% end +% +% Delta(1) = AUC(1); + +end \ No newline at end of file diff --git a/DefaultData/Underlay.nii b/DefaultData/Underlay.nii new file mode 100755 index 0000000..23d0901 Binary files /dev/null and b/DefaultData/Underlay.nii differ diff --git a/DefaultData/brain.mat b/DefaultData/brain.mat new file mode 100755 index 0000000..4023a14 Binary files /dev/null and b/DefaultData/brain.mat differ diff --git a/DefaultData/load_nii.m b/DefaultData/load_nii.m new file mode 100755 index 0000000..42415a5 --- /dev/null +++ b/DefaultData/load_nii.m @@ -0,0 +1,138 @@ +% Load NIFTI or ANALYZE dataset. Support both *.nii and *.hdr/*.img +% file extension. If file extension is not provided, *.hdr/*.img will +% be used as default. +% +% A subset of NIFTI transform is included. For non-orthogonal rotation, +% shearing etc., please use 'reslice_nii.m' to reslice the NIFTI file. +% It will not cause negative effect, as long as you remember not to do +% slice time correction after reslicing the NIFTI file. Output variable +% nii will be in RAS orientation, i.e. X axis from Left to Right, +% Y axis from Posterior to Anterior, and Z axis from Inferior to +% Superior. +% +% Usage: nii = load_nii(filename, [img_idx], [dim5_idx], [dim6_idx], ... +% [dim7_idx], [old_RGB], [tolerance], [preferredForm]) +% +% filename - NIFTI or ANALYZE file name. +% +% img_idx (optional) - a numerical array of 4th dimension indices, +% which is the indices of image scan volume. The number of images +% scan volumes can be obtained from get_nii_frame.m, or simply +% hdr.dime.dim(5). Only the specified volumes will be loaded. +% All available image volumes will be loaded, if it is default or +% empty. +% +% dim5_idx (optional) - a numerical array of 5th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim6_idx (optional) - a numerical array of 6th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim7_idx (optional) - a numerical array of 7th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% old_RGB (optional) - a scale number to tell difference of new RGB24 +% from old RGB24. New RGB24 uses RGB triple sequentially for each +% voxel, like [R1 G1 B1 R2 G2 B2 ...]. Analyze 6.0 from AnalyzeDirect +% uses old RGB24, in a way like [R1 R2 ... G1 G2 ... B1 B2 ...] for +% each slices. If the image that you view is garbled, try to set +% old_RGB variable to 1 and try again, because it could be in +% old RGB24. It will be set to 0, if it is default or empty. +% +% tolerance (optional) - distortion allowed in the loaded image for any +% non-orthogonal rotation or shearing of NIfTI affine matrix. If +% you set 'tolerance' to 0, it means that you do not allow any +% distortion. If you set 'tolerance' to 1, it means that you do +% not care any distortion. The image will fail to be loaded if it +% can not be tolerated. The tolerance will be set to 0.1 (10%), if +% it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% Returned values: +% +% nii structure: +% +% hdr - struct with NIFTI header fields. +% +% filetype - Analyze format .hdr/.img (0); +% NIFTI .hdr/.img (1); +% NIFTI .nii (2) +% +% fileprefix - NIFTI filename without extension. +% +% machine - machine string variable. +% +% img - 3D (or 4D) matrix of NIFTI data. +% +% original - the original header before any affine transform. +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = load_nii(filename, img_idx, dim5_idx, dim6_idx, dim7_idx, ... + old_RGB, tolerance, preferredForm) + + if ~exist('filename','var') + error('Usage: nii = load_nii(filename, [img_idx], [dim5_idx], [dim6_idx], [dim7_idx], [old_RGB], [tolerance], [preferredForm])'); + end + + if ~exist('img_idx','var') | isempty(img_idx) + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; % 10 percent + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + % Read the dataset header + % + [nii.hdr,nii.filetype,nii.fileprefix,nii.machine] = load_nii_hdr(filename); + + % Read the header extension + % +% nii.ext = load_nii_ext(filename); + + % Read the dataset body + % + [nii.img,nii.hdr] = load_nii_img(nii.hdr,nii.filetype,nii.fileprefix, ... + nii.machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB); + + % Perform some of sform/qform transform + % + nii = xform_nii(nii, tolerance, preferredForm); + + return % load_nii + diff --git a/DefaultData/load_nii_hdr.m b/DefaultData/load_nii_hdr.m new file mode 100755 index 0000000..a915a74 --- /dev/null +++ b/DefaultData/load_nii_hdr.m @@ -0,0 +1,320 @@ +% Load NIFTI dataset header. Support both *.nii and *.hdr/*.img file +% extension. If file extension is not provided, *.hdr/*.img will be +% used as default. +% +% Usage: [hdr, filetype, fileprefix, machine] = load_nii_hdr(filename) +% +% filename - NIFTI file name. +% +% Returned values: +% +% hdr - struct with NIFTI header fields. +% +% filetype - 0 for Analyze format (*.hdr/*.img); +% 1 for NIFTI format in 2 files (*.hdr/*.img); +% 2 for NIFTI format in 1 file (*.nii). +% +% fileprefix - NIFTI file name without extension. +% +% machine - a string, see below for details. The default here is 'ieee-le'. +% +% 'native' or 'n' - local machine format - the default +% 'ieee-le' or 'l' - IEEE floating point with little-endian +% byte ordering +% 'ieee-be' or 'b' - IEEE floating point with big-endian +% byte ordering +% 'vaxd' or 'd' - VAX D floating point and VAX ordering +% 'vaxg' or 'g' - VAX G floating point and VAX ordering +% 'cray' or 'c' - Cray floating point with big-endian +% byte ordering +% 'ieee-le.l64' or 'a' - IEEE floating point with little-endian +% byte ordering and 64 bit long data type +% 'ieee-be.l64' or 's' - IEEE floating point with big-endian byte +% ordering and 64 bit long data type. +% +% Number of scanned images in the file can be obtained by: +% num_scan = hdr.dime.dim(5) +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [hdr, filetype, fileprefix, machine] = load_nii_hdr(fileprefix) + + if ~exist('fileprefix','var'), + error('Usage: [hdr, filetype, fileprefix, machine] = load_nii_hdr(filename)'); + end + + machine = 'ieee-le'; + new_ext = 0; + + if findstr('.nii',fileprefix) + new_ext = 1; + fileprefix = strrep(fileprefix,'.nii',''); + end + + if findstr('.hdr',fileprefix) + fileprefix = strrep(fileprefix,'.hdr',''); + end + + if findstr('.img',fileprefix) + fileprefix = strrep(fileprefix,'.img',''); + end + + if new_ext + fn = sprintf('%s.nii',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.nii".', fileprefix); + error(msg); + end + else + fn = sprintf('%s.hdr',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.hdr".', fileprefix); + error(msg); + end + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') == 348 + hdr = read_header(fid); + fclose(fid); + else + fclose(fid); + + % first try reading the opposite endian to 'machine' + % + switch machine, + case 'ieee-le', machine = 'ieee-be'; + case 'ieee-be', machine = 'ieee-le'; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') ~= 348 + + % Now throw an error + % + msg = sprintf('File "%s" is corrupted.',fn); + error(msg); + end + + hdr = read_header(fid); + fclose(fid); + end + end + end + + if strcmp(hdr.hist.magic, 'n+1') + filetype = 2; + elseif strcmp(hdr.hist.magic, 'ni1') + filetype = 1; + else + filetype = 0; + end + + return % load_nii_hdr + + +%--------------------------------------------------------------------- +function [ dsr ] = read_header(fid) + + % Original header structures + % struct dsr + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + dsr.hk = header_key(fid); + dsr.dime = image_dimension(fid); + dsr.hist = data_history(fid); + + % For Analyze data format + % + if ~strcmp(dsr.hist.magic, 'n+1') & ~strcmp(dsr.hist.magic, 'ni1') + dsr.hist.qform_code = 0; + dsr.hist.sform_code = 0; + end + + return % read_header + + +%--------------------------------------------------------------------- +function [ hk ] = header_key(fid) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + % + % int sizeof_header Should be 348. + % char regular Must be 'r' to indicate that all images and + % volumes are the same size. + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hk.sizeof_hdr = fread(fid, 1,'int32')'; % should be 348! + hk.data_type = deblank(fread(fid,10,directchar)'); + hk.db_name = deblank(fread(fid,18,directchar)'); + hk.extents = fread(fid, 1,'int32')'; + hk.session_error = fread(fid, 1,'int16')'; + hk.regular = fread(fid, 1,directchar)'; + hk.dim_info = fread(fid, 1,'uchar')'; + + return % header_key + + +%--------------------------------------------------------------------- +function [ dime ] = image_dimension(fid) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % /* + % dim[0] Number of dimensions in database; usually 4. + % dim[1] Image X dimension; number of *pixels* in an image row. + % dim[2] Image Y dimension; number of *pixel rows* in slice. + % dim[3] Volume Z dimension; number of *slices* in a volume. + % dim[4] Time points; number of volumes in database + % */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width, mm + % pixdim[2] - voxel height, mm + % pixdim[3] - slice thickness, mm + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + dime.dim = fread(fid,8,'int16')'; + dime.intent_p1 = fread(fid,1,'float32')'; + dime.intent_p2 = fread(fid,1,'float32')'; + dime.intent_p3 = fread(fid,1,'float32')'; + dime.intent_code = fread(fid,1,'int16')'; + dime.datatype = fread(fid,1,'int16')'; + dime.bitpix = fread(fid,1,'int16')'; + dime.slice_start = fread(fid,1,'int16')'; + dime.pixdim = fread(fid,8,'float32')'; + dime.vox_offset = fread(fid,1,'float32')'; + dime.scl_slope = fread(fid,1,'float32')'; + dime.scl_inter = fread(fid,1,'float32')'; + dime.slice_end = fread(fid,1,'int16')'; + dime.slice_code = fread(fid,1,'uchar')'; + dime.xyzt_units = fread(fid,1,'uchar')'; + dime.cal_max = fread(fid,1,'float32')'; + dime.cal_min = fread(fid,1,'float32')'; + dime.slice_duration = fread(fid,1,'float32')'; + dime.toffset = fread(fid,1,'float32')'; + dime.glmax = fread(fid,1,'int32')'; + dime.glmin = fread(fid,1,'int32')'; + + return % image_dimension + + +%--------------------------------------------------------------------- +function [ hist ] = data_history(fid) + + % Original header structures + % struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hist.descrip = deblank(fread(fid,80,directchar)'); + hist.aux_file = deblank(fread(fid,24,directchar)'); + hist.qform_code = fread(fid,1,'int16')'; + hist.sform_code = fread(fid,1,'int16')'; + hist.quatern_b = fread(fid,1,'float32')'; + hist.quatern_c = fread(fid,1,'float32')'; + hist.quatern_d = fread(fid,1,'float32')'; + hist.qoffset_x = fread(fid,1,'float32')'; + hist.qoffset_y = fread(fid,1,'float32')'; + hist.qoffset_z = fread(fid,1,'float32')'; + hist.srow_x = fread(fid,4,'float32')'; + hist.srow_y = fread(fid,4,'float32')'; + hist.srow_z = fread(fid,4,'float32')'; + hist.intent_name = deblank(fread(fid,16,directchar)'); + hist.magic = deblank(fread(fid,4,directchar)'); + + fseek(fid,253,'bof'); + hist.originator = fread(fid, 5,'int16')'; + + return % data_history + diff --git a/DefaultData/load_nii_img.m b/DefaultData/load_nii_img.m new file mode 100755 index 0000000..e8d6e5f --- /dev/null +++ b/DefaultData/load_nii_img.m @@ -0,0 +1,386 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB) + + if ~exist('hdr','var') | ~exist('filetype','var') | ~exist('fileprefix','var') | ~exist('machine','var') + error('Usage: [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,[img_idx],[dim5_idx],[dim6_idx],[dim7_idx],[old_RGB]);'); + end + + if ~exist('img_idx','var') | isempty(img_idx) | hdr.dime.dim(5)<1 + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) | hdr.dime.dim(6)<1 + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) | hdr.dime.dim(7)<1 + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) | hdr.dime.dim(8)<1 + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + % check img_idx + % + if ~isempty(img_idx) & ~isnumeric(img_idx) + error('"img_idx" should be a numerical array.'); + end + + if length(unique(img_idx)) ~= length(img_idx) + error('Duplicate image index in "img_idx"'); + end + + if ~isempty(img_idx) & (min(img_idx) < 1 | max(img_idx) > hdr.dime.dim(5)) + max_range = hdr.dime.dim(5); + + if max_range == 1 + error(['"img_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"img_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim5_idx + % + if ~isempty(dim5_idx) & ~isnumeric(dim5_idx) + error('"dim5_idx" should be a numerical array.'); + end + + if length(unique(dim5_idx)) ~= length(dim5_idx) + error('Duplicate index in "dim5_idx"'); + end + + if ~isempty(dim5_idx) & (min(dim5_idx) < 1 | max(dim5_idx) > hdr.dime.dim(6)) + max_range = hdr.dime.dim(6); + + if max_range == 1 + error(['"dim5_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim5_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim6_idx + % + if ~isempty(dim6_idx) & ~isnumeric(dim6_idx) + error('"dim6_idx" should be a numerical array.'); + end + + if length(unique(dim6_idx)) ~= length(dim6_idx) + error('Duplicate index in "dim6_idx"'); + end + + if ~isempty(dim6_idx) & (min(dim6_idx) < 1 | max(dim6_idx) > hdr.dime.dim(7)) + max_range = hdr.dime.dim(7); + + if max_range == 1 + error(['"dim6_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim6_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim7_idx + % + if ~isempty(dim7_idx) & ~isnumeric(dim7_idx) + error('"dim7_idx" should be a numerical array.'); + end + + if length(unique(dim7_idx)) ~= length(dim7_idx) + error('Duplicate index in "dim7_idx"'); + end + + if ~isempty(dim7_idx) & (min(dim7_idx) < 1 | max(dim7_idx) > hdr.dime.dim(8)) + max_range = hdr.dime.dim(8); + + if max_range == 1 + error(['"dim7_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim7_idx" should be an integer within the range of [' range '].']); + end + end + + [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB); + + return % load_nii_img + + +%--------------------------------------------------------------------- +function [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB) + + switch filetype + case {0, 1} + fn = [fileprefix '.img']; + case 2 + fn = [fileprefix '.nii']; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + end + + % Set bitpix according to datatype + % + % /*Acceptable values for datatype are*/ + % + % 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN + % 1 Binary (ubit1, bitpix=1) % DT_BINARY + % 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 + % 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 + % 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 + % 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 + % 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 + % 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 + % 128 uint8 RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 + % 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 + % 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 + % 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 + % 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 + % 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 + % 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 + % 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 + % 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % + switch hdr.dime.datatype + case 1, + hdr.dime.bitpix = 1; precision = 'ubit1'; + case 2, + hdr.dime.bitpix = 8; precision = 'uint8'; + case 4, + hdr.dime.bitpix = 16; precision = 'int16'; + case 8, + hdr.dime.bitpix = 32; precision = 'int32'; + case 16, + hdr.dime.bitpix = 32; precision = 'float32'; + case 32, + hdr.dime.bitpix = 64; precision = 'float32'; + case 64, + hdr.dime.bitpix = 64; precision = 'float64'; + case 128, + hdr.dime.bitpix = 24; precision = 'uint8'; + case 256 + hdr.dime.bitpix = 8; precision = 'int8'; + case 511 + hdr.dime.bitpix = 96; precision = 'float32'; + case 512 + hdr.dime.bitpix = 16; precision = 'uint16'; + case 768 + hdr.dime.bitpix = 32; precision = 'uint32'; + case 1024 + hdr.dime.bitpix = 64; precision = 'int64'; + case 1280 + hdr.dime.bitpix = 64; precision = 'uint64'; + case 1792, + hdr.dime.bitpix = 128; precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.dim(find(hdr.dime.dim < 1)) = 1; + + % move pointer to the start of image block + % + switch filetype + case {0, 1} + fseek(fid, 0, 'bof'); + case 2 + fseek(fid, hdr.dime.vox_offset, 'bof'); + end + + % Load whole image block for old Analyze format or binary image; + % otherwise, load images that are specified in img_idx, dim5_idx, + % dim6_idx, and dim7_idx + % + % For binary image, we have to read all because pos can not be + % seeked in bit and can not be calculated the way below. + % + if hdr.dime.datatype == 1 | isequal(hdr.dime.dim(5:8),ones(1,4)) | ... + (isempty(img_idx) & isempty(dim5_idx) & isempty(dim6_idx) & isempty(dim7_idx)) + + % For each frame, precision of value will be read + % in img_siz times, where img_siz is only the + % dimension size of an image, not the byte storage + % size of an image. + % + img_siz = prod(hdr.dime.dim(2:8)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + img = fread(fid, img_siz, sprintf('*%s',precision)); + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + else + + img = []; + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + + for i7=1:length(dim7_idx) + for i6=1:length(dim6_idx) + for i5=1:length(dim5_idx) + for t=1:length(img_idx) + + % Position is seeked in bytes. To convert dimension size + % to byte storage size, hdr.dime.bitpix/8 will be + % applied. + % + pos = sub2ind([d1 d2 d3 d4 d5 d6 d7], 1, 1, 1, ... + img_idx(t), dim5_idx(i5),dim6_idx(i6),dim7_idx(i7)) -1; + pos = pos * hdr.dime.bitpix/8; + + img_siz = prod(hdr.dime.dim(2:4)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + if filetype == 2 + fseek(fid, pos + hdr.dime.vox_offset, 'bof'); + else + fseek(fid, pos, 'bof'); + end + + % For each frame, fread will read precision of value + % in img_siz times + % + img = [img fread(fid, img_siz, sprintf('*%s',precision))]; + end + end + end + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img = reshape(img, [2, length(img)/2]); + img = complex(img(1,:)', img(2,:)'); + end + + fclose(fid); + + % Update the global min and max values + % + hdr.dime.glmax = double(max(img(:))); + hdr.dime.glmin = double(min(img(:))); + + % old_RGB treat RGB slice by slice, now it is treated voxel by voxel + % + if old_RGB & hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:3) 3 hdr.dime.dim(4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [1 2 4 3 5 6 7 8]); + elseif hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + elseif hdr.dime.datatype == 511 & hdr.dime.bitpix == 96 + img = double(img(:)); + img = (img - min(img))/(max(img) - min(img)); + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + else + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + end + + if ~isempty(img_idx) + hdr.dime.dim(5) = length(img_idx); + end + + if ~isempty(dim5_idx) + hdr.dime.dim(6) = length(dim5_idx); + end + + if ~isempty(dim6_idx) + hdr.dime.dim(7) = length(dim6_idx); + end + + if ~isempty(dim7_idx) + hdr.dime.dim(8) = length(dim7_idx); + end + + return % read_image + diff --git a/DefaultData/make_nii.m b/DefaultData/make_nii.m new file mode 100755 index 0000000..b89fe66 --- /dev/null +++ b/DefaultData/make_nii.m @@ -0,0 +1,243 @@ +% Make NIfTI structure specified by an N-D matrix. Usually, N is 3 for +% 3D matrix [x y z], or 4 for 4D matrix with time series [x y z t]. +% However, NIfTI allows a maximum of 7D matrix. For RGB24 datatype, an +% extra dimension for RGB should be inserted immediately after [x y z]. +% Optional parameters can also be included, such as: voxel_size, +% origin, datatype, and description. +% +% Once the NIfTI structure is made, it can be saved into NIfTI file +% using "save_nii" command (for more detail, type: help save_nii). +% +% Usage: nii = make_nii(img, [voxel_size], [origin], [datatype], ... +% [description]) +% +% Where: +% +% img: Usually, img is a 3D matrix [x y z], or a 4D +% matrix with time series [x y z t]. However, +% NIfTI allows a maximum of 7D matrix. For RGB +% datatype, an extra dimension for RGB should +% be inserted immediately after [x y z]. +% +% voxel_size (optional): Voxel size in millimeter for each +% dimension. Default is [1 1 1]. +% +% origin (optional): The AC origin. Default is [0 0 0]. +% +% datatype (optional): Storage data type: +% 2 - uint8, 4 - int16, 8 - int32, 16 - float32, +% 32 - complex64, 64 - float64, 128 - RGB24, +% 256 - int8, 512 - uint16, 768 - uint32, +% 1792 - complex128 +% Default will use the data type of 'img' matrix +% +% description (optional): Description of data. Default is ''. +% +% e.g.: +% origin = [33 44 13]; datatype = 64; +% nii = make_nii(img, [], origin, datatype); % default voxel_size +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = make_nii(varargin) + + nii.img = varargin{1}; + dims = size(nii.img); + dims = [length(dims) dims ones(1,8)]; + dims = dims(1:8); + + voxel_size = [0 ones(1,7)]; + origin = zeros(1,5); + descrip = ''; + + switch class(nii.img) + case 'uint8' + datatype = 2; + case 'int16' + datatype = 4; + case 'int32' + datatype = 8; + case 'single' + datatype = 16; + case 'double' + datatype = 64; + case 'int8' + datatype = 256; + case 'uint16' + datatype = 512; + case 'uint32' + datatype = 768; + otherwise + error('Datatype is not supported by make_nii.'); + end + + if nargin > 1 & ~isempty(varargin{2}) + voxel_size(2:4) = double(varargin{2}); + end + + if nargin > 2 & ~isempty(varargin{3}) + origin(1:3) = double(varargin{3}); + end + + if nargin > 3 & ~isempty(varargin{4}) + datatype = double(varargin{4}); + end + + if nargin > 4 & ~isempty(varargin{5}) + descrip = varargin{5}; + end + + if datatype == 128 + if ndims(nii.img) > 8 + error('NIfTI only allows a maximum of 7 Dimension matrix.'); + end + + dims(1) = dims(1)-1; + dims(5:8) = [dims(6:8) 1]; + + else + if ndims(nii.img) > 7 + error('NIfTI only allows a maximum of 7 Dimension matrix.'); + end + end + + maxval = round(double(max(nii.img(:)))); + minval = round(double(min(nii.img(:)))); + + nii.hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval); + + switch nii.hdr.dime.datatype + case 2 + nii.img = uint8(nii.img); + case 4 + nii.img = int16(nii.img); + case 8 + nii.img = int32(nii.img); + case 16 + nii.img = single(nii.img); + case 32 + nii.img = single(nii.img); + case 64 + nii.img = double(nii.img); + case 128 + nii.img = uint8(nii.img); + case 256 + nii.img = int8(nii.img); + case 512 + nii.img = uint16(nii.img); + case 768 + nii.img = uint32(nii.img); + case 1792 + nii.img = double(nii.img); + otherwise + error('Datatype is not supported by make_nii.'); + end + + return; % make_nii + + +%--------------------------------------------------------------------- +function hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval) + + hdr.hk = header_key; + hdr.dime = image_dimension(dims, voxel_size, datatype, maxval, minval); + hdr.hist = data_history(origin, descrip); + + return; % make_header + + +%--------------------------------------------------------------------- +function hk = header_key + + hk.sizeof_hdr = 348; % must be 348! + hk.data_type = ''; + hk.db_name = ''; + hk.extents = 0; + hk.session_error = 0; + hk.regular = 'r'; + hk.dim_info = 0; + + return; % header_key + + +%--------------------------------------------------------------------- +function dime = image_dimension(dims, voxel_size, datatype, maxval, minval) + + dime.dim = dims; + dime.intent_p1 = 0; + dime.intent_p2 = 0; + dime.intent_p3 = 0; + dime.intent_code = 0; + dime.datatype = datatype; + + switch dime.datatype + case 2, + dime.bitpix = 8; precision = 'uint8'; + case 4, + dime.bitpix = 16; precision = 'int16'; + case 8, + dime.bitpix = 32; precision = 'int32'; + case 16, + dime.bitpix = 32; precision = 'float32'; + case 32, + dime.bitpix = 64; precision = 'float32'; + case 64, + dime.bitpix = 64; precision = 'float64'; + case 128, + dime.bitpix = 24; precision = 'uint8'; + case 256 + dime.bitpix = 8; precision = 'int8'; + case 512 + dime.bitpix = 16; precision = 'uint16'; + case 768 + dime.bitpix = 32; precision = 'uint32'; + case 1792, + dime.bitpix = 128; precision = 'float64'; + otherwise + error('Datatype is not supported by make_nii.'); + end + + dime.slice_start = 0; + dime.pixdim = voxel_size; + dime.vox_offset = 0; + dime.scl_slope = 0; + dime.scl_inter = 0; + dime.slice_end = 0; + dime.slice_code = 0; + dime.xyzt_units = 0; + dime.cal_max = 0; + dime.cal_min = 0; + dime.slice_duration = 0; + dime.toffset = 0; + dime.glmax = maxval; + dime.glmin = minval; + + return; % image_dimension + + +%--------------------------------------------------------------------- +function hist = data_history(origin, descrip) + + hist.descrip = descrip; + hist.aux_file = 'none'; + hist.qform_code = 0; + hist.sform_code = 0; + hist.quatern_b = 0; + hist.quatern_c = 0; + hist.quatern_d = 0; + hist.qoffset_x = 0; + hist.qoffset_y = 0; + hist.qoffset_z = 0; + hist.srow_x = zeros(1,4); + hist.srow_y = zeros(1,4); + hist.srow_z = zeros(1,4); + hist.intent_name = ''; + hist.magic = ''; + hist.originator = origin; + + return; % data_history + diff --git a/DefaultData/save_nii.m b/DefaultData/save_nii.m new file mode 100755 index 0000000..c96ee57 --- /dev/null +++ b/DefaultData/save_nii.m @@ -0,0 +1,233 @@ +% Save NIFTI dataset. Support both *.nii and *.hdr/*.img file extension. +% If file extension is not provided, *.hdr/*.img will be used as default. +% +% Usage: save_nii(nii, filename, [old_RGB]) +% +% nii.hdr - struct with NIFTI header fields (from load_nii.m or make_nii.m) +% +% nii.img - 3D (or 4D) matrix of NIFTI data. +% +% filename - NIFTI file name. +% +% old_RGB - an optional boolean variable to handle special RGB data +% sequence [R1 R2 ... G1 G2 ... B1 B2 ...] that is used only by +% AnalyzeDirect (Analyze Software). Since both NIfTI and Analyze +% file format use RGB triple [R1 G1 B1 R2 G2 B2 ...] sequentially +% for each voxel, this variable is set to FALSE by default. If you +% would like the saved image only to be opened by AnalyzeDirect +% Software, set old_RGB to TRUE (or 1). It will be set to 0, if it +% is default or empty. +% +% Tip: to change the data type, set nii.hdr.dime.datatype, +% and nii.hdr.dime.bitpix to: +% +% 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN +% 1 Binary (ubit1, bitpix=1) % DT_BINARY +% 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 +% 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 +% 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 +% 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 +% 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 +% 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 +% 128 Red-Green-Blue (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 +% 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 +% 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 +% 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 +% 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 +% 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 +% 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 +% 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 +% 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% - "old_RGB" related codes in "save_nii.m" are added by Mike Harms (2006.06.28) +% +function save_nii(nii, fileprefix, old_RGB) + + if ~exist('nii','var') | isempty(nii) | ~isfield(nii,'hdr') | ... + ~isfield(nii,'img') | ~exist('fileprefix','var') | isempty(fileprefix) + + error('Usage: save_nii(nii, filename, [old_RGB])'); + end + + if isfield(nii,'untouch') & nii.untouch == 1 + error('Usage: please use ''save_untouch_nii.m'' for the untouched structure.'); + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + filetype = 1; + + % Note: fileprefix is actually the filename you want to save + % + if findstr('.nii',fileprefix) + filetype = 2; + fileprefix = strrep(fileprefix,'.nii',''); + end + + if findstr('.hdr',fileprefix) + fileprefix = strrep(fileprefix,'.hdr',''); + end + + if findstr('.img',fileprefix) + fileprefix = strrep(fileprefix,'.img',''); + end + + write_nii(nii, filetype, fileprefix, old_RGB); + + if filetype == 1 + + % So earlier versions of SPM can also open it with correct originator + % + M=[[diag(nii.hdr.dime.pixdim(2:4)) -[nii.hdr.hist.originator(1:3).*nii.hdr.dime.pixdim(2:4)]'];[0 0 0 1]]; + save([fileprefix '.mat'], 'M'); + end + + return % save_nii + + +%----------------------------------------------------------------------------------- +function write_nii(nii, filetype, fileprefix, old_RGB) + + hdr = nii.hdr; + + if isfield(nii,'ext') & ~isempty(nii.ext) + ext = nii.ext; + [ext, esize_total] = verify_nii_ext(ext); + else + ext = []; + end + + switch double(hdr.dime.datatype), + case 1, + hdr.dime.bitpix = int16(1 ); precision = 'ubit1'; + case 2, + hdr.dime.bitpix = int16(8 ); precision = 'uint8'; + case 4, + hdr.dime.bitpix = int16(16); precision = 'int16'; + case 8, + hdr.dime.bitpix = int16(32); precision = 'int32'; + case 16, + hdr.dime.bitpix = int16(32); precision = 'float32'; + case 32, + hdr.dime.bitpix = int16(64); precision = 'float32'; + case 64, + hdr.dime.bitpix = int16(64); precision = 'float64'; + case 128, + hdr.dime.bitpix = int16(24); precision = 'uint8'; + case 256 + hdr.dime.bitpix = int16(8 ); precision = 'int8'; + case 512 + hdr.dime.bitpix = int16(16); precision = 'uint16'; + case 768 + hdr.dime.bitpix = int16(32); precision = 'uint32'; + case 1024 + hdr.dime.bitpix = int16(64); precision = 'int64'; + case 1280 + hdr.dime.bitpix = int16(64); precision = 'uint64'; + case 1792, + hdr.dime.bitpix = int16(128); precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.glmax = round(double(max(nii.img(:)))); + hdr.dime.glmin = round(double(min(nii.img(:)))); + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 352; + + if ~isempty(ext) + hdr.dime.vox_offset = hdr.dime.vox_offset + esize_total; + end + + hdr.hist.magic = 'n+1'; + save_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + else + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 0; + hdr.hist.magic = 'ni1'; + save_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + end + + ScanDim = double(hdr.dime.dim(5)); % t + SliceDim = double(hdr.dime.dim(4)); % z + RowDim = double(hdr.dime.dim(3)); % y + PixelDim = double(hdr.dime.dim(2)); % x + SliceSz = double(hdr.dime.pixdim(4)); + RowSz = double(hdr.dime.pixdim(3)); + PixelSz = double(hdr.dime.pixdim(2)); + + x = 1:PixelDim; + + if filetype == 2 & isempty(ext) + skip_bytes = double(hdr.dime.vox_offset) - 348; + else + skip_bytes = 0; + end + + if double(hdr.dime.datatype) == 128 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + if old_RGB + nii.img = permute(nii.img, [1 2 4 3 5 6 7 8]); + else + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + real_img = real(nii.img(:))'; + nii.img = imag(nii.img(:))'; + nii.img = [real_img; nii.img]; + end + + if skip_bytes + fwrite(fid, ones(1,skip_bytes), 'uint8'); + end + + fwrite(fid, nii.img, precision); +% fwrite(fid, nii.img, precision, skip_bytes); % error using skip + fclose(fid); + + return; % write_nii + diff --git a/DefaultData/save_nii_ext.m b/DefaultData/save_nii_ext.m new file mode 100755 index 0000000..4788649 --- /dev/null +++ b/DefaultData/save_nii_ext.m @@ -0,0 +1,38 @@ +% Save NIFTI header extension. +% +% Usage: save_nii_ext(ext, fid) +% +% ext - struct with NIFTI header extension fields. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_nii_ext(ext, fid) + + if ~exist('ext','var') | ~exist('fid','var') + error('Usage: save_nii_ext(ext, fid)'); + end + + if ~isfield(ext,'extension') | ~isfield(ext,'section') | ~isfield(ext,'num_ext') + error('Wrong header extension'); + end + + write_ext(ext, fid); + + return; % save_nii_ext + + +%--------------------------------------------------------------------- +function write_ext(ext, fid) + + fwrite(fid, ext.extension, 'uchar'); + + for i=1:ext.num_ext + fwrite(fid, ext.section(i).esize, 'int32'); + fwrite(fid, ext.section(i).ecode, 'int32'); + fwrite(fid, ext.section(i).edata, 'uchar'); + end + + return; % write_ext + diff --git a/DefaultData/save_nii_hdr.m b/DefaultData/save_nii_hdr.m new file mode 100755 index 0000000..6cc34bb --- /dev/null +++ b/DefaultData/save_nii_hdr.m @@ -0,0 +1,239 @@ +% Save NIFTI dataset header. Support both *.nii and *.hdr/*.img file +% extension. +% +% Usage: save_nii_hdr(hdr, fid) +% +% hdr - struct with NIFTI header fields. +% +% fileprefix - NIFTI file name without extension. +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_nii_hdr(hdr, fid) + + if ~exist('hdr','var') | ~exist('fid','var') + error('Usage: save_nii_hdr(hdr, fid)'); + end + + if ~isequal(hdr.hk.sizeof_hdr,348), + error('hdr.hk.sizeof_hdr must be 348.'); + end + + if hdr.hist.qform_code == 0 & hdr.hist.sform_code == 0 + hdr.hist.sform_code = 1; + hdr.hist.srow_x(1) = hdr.dime.pixdim(2); + hdr.hist.srow_x(2) = 0; + hdr.hist.srow_x(3) = 0; + hdr.hist.srow_y(1) = 0; + hdr.hist.srow_y(2) = hdr.dime.pixdim(3); + hdr.hist.srow_y(3) = 0; + hdr.hist.srow_z(1) = 0; + hdr.hist.srow_z(2) = 0; + hdr.hist.srow_z(3) = hdr.dime.pixdim(4); + hdr.hist.srow_x(4) = (1-hdr.hist.originator(1))*hdr.dime.pixdim(2); + hdr.hist.srow_y(4) = (1-hdr.hist.originator(2))*hdr.dime.pixdim(3); + hdr.hist.srow_z(4) = (1-hdr.hist.originator(3))*hdr.dime.pixdim(4); + end + + write_header(hdr, fid); + + return; % save_nii_hdr + + +%--------------------------------------------------------------------- +function write_header(hdr, fid) + + % Original header structures + % struct dsr /* dsr = hdr */ + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + header_key(fid, hdr.hk); + image_dimension(fid, hdr.dime); + data_history(fid, hdr.hist); + + % check the file size is 348 bytes + % + fbytes = ftell(fid); + + if ~isequal(fbytes,348), + msg = sprintf('Header size is not 348 bytes.'); + warning(msg); + end + + return; % write_header + + +%--------------------------------------------------------------------- +function header_key(fid, hk) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + + fwrite(fid, hk.sizeof_hdr(1), 'int32'); % must be 348. + + % data_type = sprintf('%-10s',hk.data_type); % ensure it is 10 chars from left + % fwrite(fid, data_type(1:10), 'uchar'); + pad = zeros(1, 10-length(hk.data_type)); + hk.data_type = [hk.data_type char(pad)]; + fwrite(fid, hk.data_type(1:10), 'uchar'); + + % db_name = sprintf('%-18s', hk.db_name); % ensure it is 18 chars from left + % fwrite(fid, db_name(1:18), 'uchar'); + pad = zeros(1, 18-length(hk.db_name)); + hk.db_name = [hk.db_name char(pad)]; + fwrite(fid, hk.db_name(1:18), 'uchar'); + + fwrite(fid, hk.extents(1), 'int32'); + fwrite(fid, hk.session_error(1), 'int16'); + fwrite(fid, hk.regular(1), 'uchar'); % might be uint8 + + % fwrite(fid, hk.hkey_un0(1), 'uchar'); + % fwrite(fid, hk.hkey_un0(1), 'uint8'); + fwrite(fid, hk.dim_info(1), 'uchar'); + + return; % header_key + + +%--------------------------------------------------------------------- +function image_dimension(fid, dime) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width + % pixdim[2] - voxel height + % pixdim[3] - interslice distance + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + fwrite(fid, dime.dim(1:8), 'int16'); + fwrite(fid, dime.intent_p1(1), 'float32'); + fwrite(fid, dime.intent_p2(1), 'float32'); + fwrite(fid, dime.intent_p3(1), 'float32'); + fwrite(fid, dime.intent_code(1), 'int16'); + fwrite(fid, dime.datatype(1), 'int16'); + fwrite(fid, dime.bitpix(1), 'int16'); + fwrite(fid, dime.slice_start(1), 'int16'); + fwrite(fid, dime.pixdim(1:8), 'float32'); + fwrite(fid, dime.vox_offset(1), 'float32'); + fwrite(fid, dime.scl_slope(1), 'float32'); + fwrite(fid, dime.scl_inter(1), 'float32'); + fwrite(fid, dime.slice_end(1), 'int16'); + fwrite(fid, dime.slice_code(1), 'uchar'); + fwrite(fid, dime.xyzt_units(1), 'uchar'); + fwrite(fid, dime.cal_max(1), 'float32'); + fwrite(fid, dime.cal_min(1), 'float32'); + fwrite(fid, dime.slice_duration(1), 'float32'); + fwrite(fid, dime.toffset(1), 'float32'); + fwrite(fid, dime.glmax(1), 'int32'); + fwrite(fid, dime.glmin(1), 'int32'); + + return; % image_dimension + + +%--------------------------------------------------------------------- +function data_history(fid, hist) + + % Original header structures + %struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + % descrip = sprintf('%-80s', hist.descrip); % 80 chars from left + % fwrite(fid, descrip(1:80), 'uchar'); + pad = zeros(1, 80-length(hist.descrip)); + hist.descrip = [hist.descrip char(pad)]; + fwrite(fid, hist.descrip(1:80), 'uchar'); + + % aux_file = sprintf('%-24s', hist.aux_file); % 24 chars from left + % fwrite(fid, aux_file(1:24), 'uchar'); + pad = zeros(1, 24-length(hist.aux_file)); + hist.aux_file = [hist.aux_file char(pad)]; + fwrite(fid, hist.aux_file(1:24), 'uchar'); + + fwrite(fid, hist.qform_code, 'int16'); + fwrite(fid, hist.sform_code, 'int16'); + fwrite(fid, hist.quatern_b, 'float32'); + fwrite(fid, hist.quatern_c, 'float32'); + fwrite(fid, hist.quatern_d, 'float32'); + fwrite(fid, hist.qoffset_x, 'float32'); + fwrite(fid, hist.qoffset_y, 'float32'); + fwrite(fid, hist.qoffset_z, 'float32'); + fwrite(fid, hist.srow_x(1:4), 'float32'); + fwrite(fid, hist.srow_y(1:4), 'float32'); + fwrite(fid, hist.srow_z(1:4), 'float32'); + + % intent_name = sprintf('%-16s', hist.intent_name); % 16 chars from left + % fwrite(fid, intent_name(1:16), 'uchar'); + pad = zeros(1, 16-length(hist.intent_name)); + hist.intent_name = [hist.intent_name char(pad)]; + fwrite(fid, hist.intent_name(1:16), 'uchar'); + + % magic = sprintf('%-4s', hist.magic); % 4 chars from left + % fwrite(fid, magic(1:4), 'uchar'); + pad = zeros(1, 4-length(hist.magic)); + hist.magic = [hist.magic char(pad)]; + fwrite(fid, hist.magic(1:4), 'uchar'); + + return; % data_history + diff --git a/DefaultData/xform_nii.m b/DefaultData/xform_nii.m new file mode 100755 index 0000000..21d82cd --- /dev/null +++ b/DefaultData/xform_nii.m @@ -0,0 +1,521 @@ +% internal function + +% 'xform_nii.m' is an internal function called by "load_nii.m", so +% you do not need run this program by yourself. It does simplified +% NIfTI sform/qform affine transform, and supports some of the +% affine transforms, including translation, reflection, and +% orthogonal rotation (N*90 degree). +% +% For other affine transforms, e.g. any degree rotation, shearing +% etc. you will have to use the included 'reslice_nii.m' program +% to reslice the image volume. 'reslice_nii.m' is not called by +% any other program, and you have to run 'reslice_nii.m' explicitly +% for those NIfTI files that you want to reslice them. +% +% Since 'xform_nii.m' does not involve any interpolation or any +% slice change, the original image volume is supposed to be +% untouched, although it is translated, reflected, or even +% orthogonally rotated, based on the affine matrix in the +% NIfTI header. +% +% However, the affine matrix in the header of a lot NIfTI files +% contain slightly non-orthogonal rotation. Therefore, optional +% input parameter 'tolerance' is used to allow some distortion +% in the loaded image for any non-orthogonal rotation or shearing +% of NIfTI affine matrix. If you set 'tolerance' to 0, it means +% that you do not allow any distortion. If you set 'tolerance' to +% 1, it means that you do not care any distortion. The image will +% fail to be loaded if it can not be tolerated. The tolerance will +% be set to 0.1 (10%), if it is default or empty. +% +% Because 'reslice_nii.m' has to perform 3D interpolation, it can +% be slow depending on image size and affine matrix in the header. +% +% After you perform the affine transform, the 'nii' structure +% generated from 'xform_nii.m' or new NIfTI file created from +% 'reslice_nii.m' will be in RAS orientation, i.e. X axis from +% Left to Right, Y axis from Posterior to Anterior, and Z axis +% from Inferior to Superior. +% +% NOTE: This function should be called immediately after load_nii. +% +% Usage: [ nii ] = xform_nii(nii, [tolerance], [preferredForm]) +% +% nii - NIFTI structure (returned from load_nii) +% +% tolerance (optional) - distortion allowed for non-orthogonal rotation +% or shearing in NIfTI affine matrix. It will be set to 0.1 (10%), +% if it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = xform_nii(nii, tolerance, preferredForm) + + % save a copy of the header as it was loaded. This is the + % header before any sform, qform manipulation is done. + % + nii.original.hdr = nii.hdr; + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; + elseif(tolerance<=0) + tolerance = eps; + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + % if scl_slope field is nonzero, then each voxel value in the + % dataset should be scaled as: y = scl_slope * x + scl_inter + % I bring it here because hdr will be modified by change_hdr. + % + if nii.hdr.dime.scl_slope ~= 0 & ... + ismember(nii.hdr.dime.datatype, [2,4,8,16,64,256,512,768]) & ... + (nii.hdr.dime.scl_slope ~= 1 | nii.hdr.dime.scl_inter ~= 0) + + nii.img = ... + nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter; + + if nii.hdr.dime.datatype == 64 + + nii.hdr.dime.datatype = 64; + nii.hdr.dime.bitpix = 64; + else + nii.img = single(nii.img); + + nii.hdr.dime.datatype = 16; + nii.hdr.dime.bitpix = 32; + end + + nii.hdr.dime.glmax = max(double(nii.img(:))); + nii.hdr.dime.glmin = min(double(nii.img(:))); + + % set scale to non-use, because it is applied in xform_nii + % + nii.hdr.dime.scl_slope = 0; + + end + + % However, the scaling is to be ignored if datatype is DT_RGB24. + + % If datatype is a complex type, then the scaling is to be applied + % to both the real and imaginary parts. + % + if nii.hdr.dime.scl_slope ~= 0 & ... + ismember(nii.hdr.dime.datatype, [32,1792]) + + nii.img = ... + nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter; + + if nii.hdr.dime.datatype == 32 + nii.img = single(nii.img); + end + + nii.hdr.dime.glmax = max(double(nii.img(:))); + nii.hdr.dime.glmin = min(double(nii.img(:))); + + % set scale to non-use, because it is applied in xform_nii + % + nii.hdr.dime.scl_slope = 0; + + end + + % There is no need for this program to transform Analyze data + % + if nii.filetype == 0 & exist([nii.fileprefix '.mat'],'file') + load([nii.fileprefix '.mat']); % old SPM affine matrix + R=M(1:3,1:3); + T=M(1:3,4); + T=R*ones(3,1)+T; + M(1:3,4)=T; + nii.hdr.hist.qform_code=0; + nii.hdr.hist.sform_code=1; + nii.hdr.hist.srow_x=M(1,:); + nii.hdr.hist.srow_y=M(2,:); + nii.hdr.hist.srow_z=M(3,:); + elseif nii.filetype == 0 + nii.hdr.hist.rot_orient = []; + nii.hdr.hist.flip_orient = []; + return; % no sform/qform for Analyze format + end + + hdr = nii.hdr; + + [hdr,orient]=change_hdr(hdr,tolerance,preferredForm); + + % flip and/or rotate image data + % + if ~isequal(orient, [1 2 3]) + + old_dim = hdr.dime.dim([2:4]); + + % More than 1 time frame + % + if ndims(nii.img) > 3 + pattern = 1:prod(old_dim); + else + pattern = []; + end + + if ~isempty(pattern) + pattern = reshape(pattern, old_dim); + end + + % calculate for rotation after flip + % + rot_orient = mod(orient + 2, 3) + 1; + + % do flip: + % + flip_orient = orient - rot_orient; + + for i = 1:3 + if flip_orient(i) + if ~isempty(pattern) + pattern = flipdim(pattern, i); + else + nii.img = flipdim(nii.img, i); + end + end + end + + % get index of orient (rotate inversely) + % + [tmp rot_orient] = sort(rot_orient); + + new_dim = old_dim; + new_dim = new_dim(rot_orient); + hdr.dime.dim([2:4]) = new_dim; + + new_pixdim = hdr.dime.pixdim([2:4]); + new_pixdim = new_pixdim(rot_orient); + hdr.dime.pixdim([2:4]) = new_pixdim; + + % re-calculate originator + % + tmp = hdr.hist.originator([1:3]); + tmp = tmp(rot_orient); + flip_orient = flip_orient(rot_orient); + + for i = 1:3 + if flip_orient(i) & ~isequal(tmp(i), 0) + tmp(i) = new_dim(i) - tmp(i) + 1; + end + end + + hdr.hist.originator([1:3]) = tmp; + hdr.hist.rot_orient = rot_orient; + hdr.hist.flip_orient = flip_orient; + + % do rotation: + % + if ~isempty(pattern) + pattern = permute(pattern, rot_orient); + pattern = pattern(:); + + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ... + hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + + tmp = reshape(nii.img(:,:,:,1), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,1) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + + tmp = reshape(nii.img(:,:,:,2), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,2) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + tmp = reshape(nii.img(:,:,:,3), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,3) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + end + + else + nii.img = reshape(nii.img, [prod(new_dim) hdr.dime.dim(5:8)]); + nii.img = nii.img(pattern, :); + nii.img = reshape(nii.img, [new_dim hdr.dime.dim(5:8)]); + end + else + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ... + hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + + nii.img(:,:,:,1) = permute(nii.img(:,:,:,1), rot_orient); + nii.img(:,:,:,2) = permute(nii.img(:,:,:,2), rot_orient); + + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + nii.img(:,:,:,3) = permute(nii.img(:,:,:,3), rot_orient); + end + else + nii.img = permute(nii.img, rot_orient); + end + end + else + hdr.hist.rot_orient = []; + hdr.hist.flip_orient = []; + end + + nii.hdr = hdr; + + return; % xform_nii + + +%----------------------------------------------------------------------- +function [hdr, orient] = change_hdr(hdr, tolerance, preferredForm) + + orient = [1 2 3]; + affine_transform = 1; + + % NIFTI can have both sform and qform transform. This program + % will check sform_code prior to qform_code by default. + % + % If user specifys "preferredForm", user can then choose the + % priority. - Jeff + % + useForm=[]; % Jeff + + if isequal(preferredForm,'S') + if isequal(hdr.hist.sform_code,0) + error('User requires sform, sform not set in header'); + else + useForm='s'; + end + end % Jeff + + if isequal(preferredForm,'Q') + if isequal(hdr.hist.qform_code,0) + error('User requires qform, qform not set in header'); + else + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'s') + if hdr.hist.sform_code > 0 + useForm='s'; + elseif hdr.hist.qform_code > 0 + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'q') + if hdr.hist.qform_code > 0 + useForm='q'; + elseif hdr.hist.sform_code > 0 + useForm='s'; + end + end % Jeff + + if isequal(useForm,'s') + R = [hdr.hist.srow_x(1:3) + hdr.hist.srow_y(1:3) + hdr.hist.srow_z(1:3)]; + + T = [hdr.hist.srow_x(4) + hdr.hist.srow_y(4) + hdr.hist.srow_z(4)]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + hdr.hist.old_affine = [ [R;[0 0 0]] [T;1] ]; + R_sort = sort(abs(R(:))); + R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0; + hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + msg = [char(10) char(10) ' Non-orthogonal rotation or shearing ']; + msg = [msg 'found inside the affine matrix' char(10)]; + msg = [msg ' in this NIfTI file. You have 3 options:' char(10) char(10)]; + msg = [msg ' 1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)]; + msg = [msg ' file. I strongly recommand this, because it will not cause' char(10)]; + msg = [msg ' negative effect, as long as you remember not to do slice' char(10)]; + msg = [msg ' time correction after using ''reslice_nii.m''.' char(10) char(10)]; + msg = [msg ' 2. Using included ''load_untouch_nii.m'' program to load image' char(10)]; + msg = [msg ' without applying any affine geometric transformation or' char(10)]; + msg = [msg ' voxel intensity scaling. This is only for people who want' char(10)]; + msg = [msg ' to do some image processing regardless of image orientation' char(10)]; + msg = [msg ' and to save data back with the same NIfTI header.' char(10) char(10)]; + msg = [msg ' 3. Increasing the tolerance to allow more distortion in loaded' char(10)]; + msg = [msg ' image, but I don''t suggest this.' char(10) char(10)]; + msg = [msg ' To get help, please type:' char(10) char(10) ' help reslice_nii.m' char(10)]; + msg = [msg ' help load_untouch_nii.m' char(10) ' help load_nii.m']; + error(msg); + end + end + + elseif isequal(useForm,'q') + b = hdr.hist.quatern_b; + c = hdr.hist.quatern_c; + d = hdr.hist.quatern_d; + + if 1.0-(b*b+c*c+d*d) < 0 + if abs(1.0-(b*b+c*c+d*d)) < 1e-5 + a = 0; + else + error('Incorrect quaternion values in this NIFTI data.'); + end + else + a = sqrt(1.0-(b*b+c*c+d*d)); + end + + qfac = hdr.dime.pixdim(1); + if qfac==0, qfac = 1; end + i = hdr.dime.pixdim(2); + j = hdr.dime.pixdim(3); + k = qfac * hdr.dime.pixdim(4); + + R = [a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c + 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b + 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b]; + + T = [hdr.hist.qoffset_x + hdr.hist.qoffset_y + hdr.hist.qoffset_z]; + + % qforms are expected to generate rotation matrices R which are + % det(R) = 1; we'll make sure that happens. + % + % now we make the same checks as were done above for sform data + % BUT we do it on a transform that is in terms of voxels not mm; + % after we figure out the angles and squash them to closest + % rectilinear direction. After that, the voxel sizes are then + % added. + % + % This part is modified by Jeff Gunter. + % + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + + % det(R) == 0 is not a common trigger for this --- + % R(find(R)) is a list of non-zero elements in R; if that + % is straight (not oblique) then it should be the same as + % columnwise summation. Could just as well have checked the + % lengths of R(find(R)) and sum(R)' (which should be 3) + % + hdr.hist.old_affine = [ [R * diag([i j k]);[0 0 0]] [T;1] ]; + R_sort = sort(abs(R(:))); + R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0; + R = R * diag([i j k]); + hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + msg = [char(10) char(10) ' Non-orthogonal rotation or shearing ']; + msg = [msg 'found inside the affine matrix' char(10)]; + msg = [msg ' in this NIfTI file. You have 3 options:' char(10) char(10)]; + msg = [msg ' 1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)]; + msg = [msg ' file. I strongly recommand this, because it will not cause' char(10)]; + msg = [msg ' negative effect, as long as you remember not to do slice' char(10)]; + msg = [msg ' time correction after using ''reslice_nii.m''.' char(10) char(10)]; + msg = [msg ' 2. Using included ''load_untouch_nii.m'' program to load image' char(10)]; + msg = [msg ' without applying any affine geometric transformation or' char(10)]; + msg = [msg ' voxel intensity scaling. This is only for people who want' char(10)]; + msg = [msg ' to do some image processing regardless of image orientation' char(10)]; + msg = [msg ' and to save data back with the same NIfTI header.' char(10) char(10)]; + msg = [msg ' 3. Increasing the tolerance to allow more distortion in loaded' char(10)]; + msg = [msg ' image, but I don''t suggest this.' char(10) char(10)]; + msg = [msg ' To get help, please type:' char(10) char(10) ' help reslice_nii.m' char(10)]; + msg = [msg ' help load_untouch_nii.m' char(10) ' help load_nii.m']; + error(msg); + end + + else + R = R * diag([i j k]); + end % 1st det(R) + + else + affine_transform = 0; % no sform or qform transform + end + + if affine_transform == 1 + voxel_size = abs(sum(R,1)); + inv_R = inv(R); + originator = inv_R*(-T)+1; + orient = get_orient(inv_R); + + % modify pixdim and originator + % + hdr.dime.pixdim(2:4) = voxel_size; + hdr.hist.originator(1:3) = originator; + + % set sform or qform to non-use, because they have been + % applied in xform_nii + % + hdr.hist.qform_code = 0; + hdr.hist.sform_code = 0; + end + + % apply space_unit to pixdim if not 1 (mm) + % + space_unit = get_units(hdr); + + if space_unit ~= 1 + hdr.dime.pixdim(2:4) = hdr.dime.pixdim(2:4) * space_unit; + + % set space_unit of xyzt_units to millimeter, because + % voxel_size has been re-scaled + % + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,1,0)); + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,2,1)); + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,3,0)); + end + + hdr.dime.pixdim = abs(hdr.dime.pixdim); + + return; % change_hdr + + +%----------------------------------------------------------------------- +function orient = get_orient(R) + + orient = []; + + for i = 1:3 + switch find(R(i,:)) * sign(sum(R(i,:))) + case 1 + orient = [orient 1]; % Left to Right + case 2 + orient = [orient 2]; % Posterior to Anterior + case 3 + orient = [orient 3]; % Inferior to Superior + case -1 + orient = [orient 4]; % Right to Left + case -2 + orient = [orient 5]; % Anterior to Posterior + case -3 + orient = [orient 6]; % Superior to Inferior + end + end + + return; % get_orient + + +%----------------------------------------------------------------------- +function [space_unit, time_unit] = get_units(hdr) + + switch bitand(hdr.dime.xyzt_units, 7) % mask with 0x07 + case 1 + space_unit = 1e+3; % meter, m + case 3 + space_unit = 1e-3; % micrometer, um + otherwise + space_unit = 1; % millimeter, mm + end + + switch bitand(hdr.dime.xyzt_units, 56) % mask with 0x38 + case 16 + time_unit = 1e-3; % millisecond, ms + case 24 + time_unit = 1e-6; % microsecond, us + otherwise + time_unit = 1; % second, s + end + + return; % get_units + diff --git a/Plotting/Create_CAP_colorbar.m b/Plotting/Create_CAP_colorbar.m new file mode 100755 index 0000000..be85c38 --- /dev/null +++ b/Plotting/Create_CAP_colorbar.m @@ -0,0 +1,56 @@ +%% Creates a nice looking colorbar for the CAP display +function [Ah,h] = Create_CAP_colorbar(absmin,absmax,absstep,colT,lab,Ah,Ori,CB1,CB2,n_steps) + + H_range = [absmin absmax]; % The colormap is symmetric around zero + + % Set the Min/Max T-values for alpha coding + A_range = [0 1]; + + % Set the labels for the colorbar + hue_label = lab; + + colrange = linspace(absmin,absmax,256); + + switch Ori + case 'Horizontal' + + y = linspace(A_range(1), A_range(2), 256); + % x represents the range in alpha (abs(t-stats)) + x = linspace(H_range(1), H_range(2), 256); + % y represents the range in hue (beta weight difference) + [X,Y] = meshgrid(x,y); % Transform into a 2D matrix + + h=imagesc(x,y,X,'Parent',Ah); + axis(Ah,'xy'); % Plot the colorbar + set(Ah, 'Xcolor', 'k', 'Ycolor', 'k','YTickLabel','','YTick',[],'XTick',absmin:absstep:absmax,'FontSize',8); + set(Ah, 'XAxisLocation', 'bottom'); + xlabel(Ah,hue_label,'FontSize',8); + + A = ones(size(X)); + A(abs(X) < colT) = 0; + A = reshape(A,256,256); + + case 'Vertical' + + x = linspace(A_range(1), A_range(2), 256); + % x represents the range in alpha (abs(t-stats)) + y = linspace(H_range(1), H_range(2), 256); + % y represents the range in hue (beta weight difference) + [X,Y] = meshgrid(x,y); % Transform into a 2D matrix + + h=imagesc(x,y,Y,'Parent',Ah); + axis(Ah,'xy'); % Plot the colorbar + set(Ah, 'Xcolor', 'k', 'Ycolor', 'k','XTickLabel','','XTick',[],'YTick',absmin:absstep:absmax,'FontSize',8); + set(Ah, 'YAxisLocation', 'right'); + ylabel(Ah,hue_label,'FontSize',8); + + A = ones(size(Y)); + A(abs(Y) < colT) = 0; + A = reshape(A,256,256); + end + + tmp_cmap = cbrewer(CB1,CB2,n_steps); + colormap(Ah,flipud(tmp_cmap)); + + set(h,'AlphaData',A); +end \ No newline at end of file diff --git a/Plotting/MakeViolin.m b/Plotting/MakeViolin.m new file mode 100755 index 0000000..d7adf60 --- /dev/null +++ b/Plotting/MakeViolin.m @@ -0,0 +1,55 @@ +%% Makes a violin plot with overlapped boxplot, tuning colors +% Y and O are vectors containing the data to plot for the two conditions +% Color has light colors for background (first cell) and dark colors for +% average (second cell), for maximum 4 populations +function [box,h_viol,ah] = MakeViolin(Y,ah,Lab,YLabel,Color,n_pop,n_states) + + % Range of the plot + Max = max(max((Y))); + Min = min(min((Y))); + Max = Max + 0.15*max(abs(Min),Max); + Min = Min - 0.15*max(abs(Min),Max); + + % Plots the distribution + ylim(ah,[Min,Max]); + + % Colors for the background + Col_final = num2cell(Color{1},2); + Col_final = Col_final(1:n_pop); + Col_final = repmat(Col_final,n_states,1); + + % Colors for the foreground + Col_final2 = num2cell(Color{2},2); + Col_final2 = Col_final2(1:n_pop); + Col_final2 = repmat(Col_final2,n_states,1); + + Lab_final = {}; + + for i = 1:n_states + Lab_final = [Lab_final, repmat({Lab{i}},1,n_pop)]; + end + + h_viol = distributionPlot(ah,Y','showMM',0,'color',Col_final,'yLabel',YLabel,'xNames',Lab_final); + hold(ah,'on'); + box = boxplot(Y','Parent',ah,'Labels',Lab_final); + + h_box = findobj(box,'Tag','Box'); + set(h_box,'color','k','LineWidth',2); + + h_median = findobj(box,'Tag','Median'); + h_outliers = findobj(box,'Tag','Outliers'); + + for i = 1:n_pop + for j = 1:n_states + + idx_oi = i+(j-1)*n_pop; + + set(h_median(idx_oi),'color',Col_final2{idx_oi},'LineWidth',2); + set(h_outliers(idx_oi),'Marker','o','MarkerFaceColor',Col_final2{n_pop*n_states-idx_oi+1},... + 'MarkerEdgeColor',Col_final2{idx_oi},'MarkerSize',3); + + end + end + + set(ah,'Box','off'); +end \ No newline at end of file diff --git a/Plotting/cbrewer/cbrewer.m b/Plotting/cbrewer/cbrewer.m new file mode 100644 index 0000000..26be891 --- /dev/null +++ b/Plotting/cbrewer/cbrewer.m @@ -0,0 +1,128 @@ +function [colormap]=cbrewer(ctype, cname, ncol, interp_method) +% +% CBREWER - This function produces a colorbrewer table (rgb data) for a +% given type, name and number of colors of the colorbrewer tables. +% For more information on 'colorbrewer', please visit +% http://colorbrewer2.org/ +% +% The tables were generated from an MS-Excel file provided on the website +% http://www.personal.psu.edu/cab38/ColorBrewer/ColorBrewer_updates.html +% +% +% [colormap]=cbrewer(ctype, cname, ncol, interp_method) +% +% INPUT: +% - ctype: type of color table 'seq' (sequential), 'div' (diverging), 'qual' (qualitative) +% - cname: name of colortable. It changes depending on ctype. +% - ncol: number of color in the table. It changes according to ctype and +% cname +% - interp_method: interpolation method (see interp1.m). Default is "cubic" ) +% +% A note on the number of colors: Based on the original data, there is +% only a certain number of colors available for each type and name of +% colortable. When 'ncol' is larger then the maximum number of colors +% originally given, an interpolation routine is called (interp1) to produce +% the "extended" colormaps. +% +% Example: To produce a colortable CT of ncol X 3 entries (RGB) of +% sequential type and named 'Blues' with 8 colors: +% CT=cbrewer('seq', 'Blues', 8); +% To use this colortable as colormap, simply call: +% colormap(CT) +% +% To see the various colormaps available according to their types and +% names, simply call: cbrewer() +% +% This product includes color specifications and designs developed by +% Cynthia Brewer (http://colorbrewer.org/). +% +% Author: Charles Robert +% email: tannoudji@hotmail.com +% Date: 06.12.2011 +% ------------------------------ +% 18.09.2015 Minor fixes, fixed a bug where the 'spectral' color table did not appear in the preview + + +% load colorbrewer data +load('colorbrewer.mat') +% initialise the colormap is there are any problems +colormap=[]; +if (~exist('interp_method', 'var')) + interp_method='cubic'; +end + +% If no arguments +if (~exist('ctype', 'var') | ~exist('cname', 'var') | ~exist('ncol', 'var')) + disp(' ') + disp('[colormap] = cbrewer(ctype, cname, ncol [, interp_method])') + disp(' ') + disp('INPUT:') + disp(' - ctype: type of color table *seq* (sequential), *div* (divergent), *qual* (qualitative)') + disp(' - cname: name of colortable. It changes depending on ctype.') + disp(' - ncol: number of color in the table. It changes according to ctype and cname') + disp(' - interp_method: interpolation method (see interp1.m). Default is "cubic" )') + + disp(' ') + disp('Sequential tables:') + z={'Blues','BuGn','BuPu','GnBu','Greens','Greys','Oranges','OrRd','PuBu','PuBuGn','PuRd',... + 'Purples','RdPu', 'Reds', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd', 'Spectral'}; + disp(z') + + disp('Divergent tables:') + z={'BrBG', 'PiYG', 'PRGn', 'PuOr', 'RdBu', 'RdGy', 'RdYlBu', 'RdYlGn'}; + disp(z') + + disp(' ') + disp('Qualitative tables:') + %getfield(colorbrewer, 'qual') + z={'Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3'}; + disp(z') + + plot_brewer_cmap + return +end + +% Verify that the input is appropriate +ctype_names={'div', 'seq', 'qual'}; +if (~ismember(ctype,ctype_names)) + disp('ctype must be either: *div*, *seq* or *qual*') + colormap=[]; + return +end + +if (~isfield(colorbrewer.(ctype),cname)) + disp(['The name of the colortable of type *' ctype '* must be one of the following:']) + getfield(colorbrewer, ctype) + colormap=[]; + return +end + +if (ncol>length(colorbrewer.(ctype).(cname))) +% disp(' ') +% disp('----------------------------------------------------------------------') +% disp(['The maximum number of colors for table *' cname '* is ' num2str(length(colorbrewer.(ctype).(cname)))]) +% disp(['The new colormap will be extrapolated from these ' num2str(length(colorbrewer.(ctype).(cname))) ' values']) +% disp('----------------------------------------------------------------------') +% disp(' ') + cbrew_init=colorbrewer.(ctype).(cname){length(colorbrewer.(ctype).(cname))}; + colormap=interpolate_cbrewer(cbrew_init, interp_method, ncol); + colormap=colormap./255; + return +end + +if (isempty(colorbrewer.(ctype).(cname){ncol})) + + while(isempty(colorbrewer.(ctype).(cname){ncol})) + ncol=ncol+1; + end + disp(' ') + disp('----------------------------------------------------------------------') + disp(['The minimum number of colors for table *' cname '* is ' num2str(ncol)]) + disp('This minimum value shall be defined as ncol instead') + disp('----------------------------------------------------------------------') + disp(' ') +end + +colormap=(colorbrewer.(ctype).(cname){ncol})./255; + +end \ No newline at end of file diff --git a/Plotting/cbrewer/cbrewer_preview.jpg b/Plotting/cbrewer/cbrewer_preview.jpg new file mode 100644 index 0000000..bd2830a Binary files /dev/null and b/Plotting/cbrewer/cbrewer_preview.jpg differ diff --git a/Plotting/cbrewer/change_jet.m b/Plotting/cbrewer/change_jet.m new file mode 100644 index 0000000..b8d4ecb --- /dev/null +++ b/Plotting/cbrewer/change_jet.m @@ -0,0 +1,64 @@ +% This script help produce a new 'jet'-like colormap based on other RGB reference colors + +% ------- I WAS ASKED --------------- +% "is there a chance that you could add a diverging map going from blue to green to red as in jet, +% but using the red and blue from your RdBu map and the third darkest green from your RdYlGn map?"" +% +% ANSWER: +% You should construct the new colormap based on the existing RGB values of 'jet' +% but projecting these RGB values on your new RGB basis. +% ----------------------------------- + +% load colormaps +jet=colormap('jet'); +RdBu=cbrewer('div', 'RdBu', 11); +RdYlGn=cbrewer('div', 'RdYlGn', 11); + +% Define the new R, G, B references (p stands for prime) +Rp=RdBu(1,:); +Bp=RdBu(end, :); +Gp=RdYlGn(end-2, :); +RGBp=[Rp;Gp;Bp]; + +% construct the new colormap based on the existing RGB values of jet +% Project the RGB values on your new basis +newjet = jet*RGBp; + +% store data in a strcuture, easier to handle +cmap.jet=jet; +cmap.newjet=newjet; +cnames={'jet', 'newjet'}; + +% plot the RGB values +fh=figure(); +colors={'r', 'g', 'b'}; +for iname=1:length(cnames) + subplot(length(cnames),1,iname) + dat=cmap.(cnames{end-iname+1}); + for icol=1:size(dat,2) + plot(dat(:,icol), 'color', colors{icol}, 'linewidth', 2);hold on; + end % icol + title([' "' cnames{end-iname+1} '" in RGB plot']) +end + +% plot the colormaps +fh=figure(); +for iname=1:length(cnames) + F=cmap.(cnames{iname}); + ncol=length(F); + fg=1./ncol; % geometrical factor + X=fg.*[0 0 1 1]; + Y=0.1.*[1 0 0 1]+(2*iname-1)*0.1; + + for icol=1:ncol + X2=X+fg.*(icol-1); + fill(X2,Y,F(icol, :), 'linestyle', 'none') + hold all + end % icol + text(-0.1, mean(Y), cnames{iname}, 'HorizontalAlignment', 'right', 'FontWeight', 'bold', 'FontSize', 10, 'FontName' , 'AvantGarde') + xlim([-0.4, 1]) + axis off + set(gcf, 'color', [1 1 1]) + ylim([0.1 1.05.*max(Y)]); + end % iname + diff --git a/Plotting/cbrewer/colorbrewer.mat b/Plotting/cbrewer/colorbrewer.mat new file mode 100644 index 0000000..ec59ef4 Binary files /dev/null and b/Plotting/cbrewer/colorbrewer.mat differ diff --git a/Plotting/cbrewer/interpolate_cbrewer.m b/Plotting/cbrewer/interpolate_cbrewer.m new file mode 100644 index 0000000..e8b5e21 --- /dev/null +++ b/Plotting/cbrewer/interpolate_cbrewer.m @@ -0,0 +1,36 @@ +function [interp_cmap]=interpolate_cbrewer(cbrew_init, interp_method, ncolors) +% +% INTERPOLATE_CBREWER - interpolate a colorbrewer map to ncolors levels +% +% INPUT: +% - cbrew_init: the initial colormap with format N*3 +% - interp_method: interpolation method, which can be the following: +% 'nearest' - nearest neighbor interpolation +% 'linear' - bilinear interpolation +% 'spline' - spline interpolation +% 'cubic' - bicubic interpolation as long as the data is +% uniformly spaced, otherwise the same as 'spline' +% - ncolors=desired number of colors +% +% Author: Charles Robert +% email: tannoudji@hotmail.com +% Date: 14.10.2011 + + +% just to make sure, in case someone puts in a decimal +ncolors=round(ncolors); + +% How many data points of the colormap available +nmax=size(cbrew_init,1); + +% create the associated X axis (using round to get rid of decimals) +a=(ncolors-1)./(nmax-1); +X=round([0 a:a:(ncolors-1)]); +X2=0:ncolors-1; + +z=interp1(X,cbrew_init(:,1),X2,interp_method); +z2=interp1(X,cbrew_init(:,2),X2,interp_method); +z3=interp1(X,cbrew_init(:,3),X2, interp_method); +interp_cmap=round([z' z2' z3']); + +end \ No newline at end of file diff --git a/Plotting/cbrewer/plot_brewer_cmap.m b/Plotting/cbrewer/plot_brewer_cmap.m new file mode 100644 index 0000000..a5cab9e --- /dev/null +++ b/Plotting/cbrewer/plot_brewer_cmap.m @@ -0,0 +1,50 @@ +% Plots and identifies the various colorbrewer tables available. +% Is called by cbrewer.m when no arguments are given. +% +% Author: Charles Robert +% email: tannoudji@hotmail.com +% Date: 14.10.2011 + + + +load('colorbrewer.mat') + +ctypes={'div', 'seq', 'qual'}; +ctypes_title={'Diverging', 'Sequential', 'Qualitative'}; +cnames{1,:}={'BrBG', 'PiYG', 'PRGn', 'PuOr', 'RdBu', 'RdGy', 'RdYlBu', 'RdYlGn', 'Spectral'}; +cnames{2,:}={'Blues','BuGn','BuPu','GnBu','Greens','Greys','Oranges','OrRd','PuBu','PuBuGn','PuRd',... + 'Purples','RdPu', 'Reds', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd'}; +cnames{3,:}={'Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3'}; + +figure('position', [314 327 807 420]) +for itype=1:3 + + %fh(itype)=figure(); + subplot(1,3,itype) + + for iname=1:length(cnames{itype,:}) + + ncol=length(colorbrewer.(ctypes{itype}).(cnames{itype}{iname})); + fg=1./ncol; % geometrical factor + + X=fg.*[0 0 1 1]; + Y=0.1.*[1 0 0 1]+(2*iname-1)*0.1; + F=cbrewer(ctypes{itype}, cnames{itype}{iname}, ncol); + + for icol=1:ncol + X2=X+fg.*(icol-1); + fill(X2,Y,F(icol, :), 'linestyle', 'none') + text(-0.1, mean(Y), cnames{itype}{iname}, 'HorizontalAlignment', 'right', 'FontWeight', 'bold', 'FontSize', 10, 'FontName' , 'AvantGarde') + xlim([-0.4, 1]) + hold all + end % icol + %set(gca, 'box', 'off') + title(ctypes_title{itype}, 'FontWeight', 'bold', 'FontSize', 16, 'FontName' , 'AvantGarde') + axis off + set(gcf, 'color', [1 1 1]) + end % iname + ylim([0.1 1.05.*max(Y)]); +end %itype + +set(gcf, 'MenuBar', 'none') +set(gcf, 'Name', 'ColorBrewer Color maps') \ No newline at end of file diff --git a/Plotting/distributionPlot.m b/Plotting/distributionPlot.m new file mode 100755 index 0000000..352cc69 --- /dev/null +++ b/Plotting/distributionPlot.m @@ -0,0 +1,902 @@ +function handles = distributionPlot(varargin) +%DISTRIBUTIONPLOT creates violin plots for convenient visualization of multiple distributions +% +% SYNOPSIS: handles = distributionPlot(data,propertyName,propertyValue,...) +% handles = distributionPlot(ah,...) +% +% INPUT data : m-by-nData array of values, or vector of grouped data (use +% the 'groups' property to specify the grouping variable), or +% cell array of length nData. +% The cell array can either contain vectors with values, or +% m-by-2 arrays with [bins,counts] if you want to determine the +% histograms by yourself (m can be different between cell +% elements). Note that arrays inside cells with any +% other shape than m-by-2 are reshaped to vector an a warning is +% thrown (DISTRIBUTIONPLOT:AUTORESHAPE). +% +% DISTRIBUTIONPLOT accepts the following propertyName/propertyValue +% pairs (all are optional): +% +% distWidth : width of distributions; ideally between 0 and 1. +% 1 means that adjacent distributions might touch. Default: 0.9 +% variableWidth : If true, the width of the distribution changes, +% reflecting the shape of the histogram of the data. If false, +% the distribution is only encoded by color levels. Default: true +% color : uniform coloring of histograms. Supply either a color +% string ('r'), or a truecolor vector ([1 0 0]). Use a +% cell array of length nData to specify one color per +% distribution. Default: 'k' +% If variableWidth is set to false, a colormap is generated that +% goes from white to the chose color (or from black, if +% invert==true). +% If both 'color', and 'colormap' are specified, 'colormap' takes +% precedence. +% colormap : colormap used to describe the distribution (first row +% corresponds to bins with least data, last row corresponds to +% bins with most data (invert the grayscale colormap to have +% black indicate the most data). +% Supply a cell array of length nData to color distributions +% individually. Note that using multiple colormaps means that +% the colorbar doesn't contain much useful information. +% Default: [] +% Colormap will index into the figure colormap, which will be +% modified by distributionPlot. This is done to allow editing the +% distributions in e.g. Adobe Illustrator. +% If both 'color', and 'colormap' are specified, 'colormap' takes +% precedence. +% globalNorm : normalization for bin width (x-direction) +% 0 : every histogram is normalized individually so that the +% maximum bin width is equal to distWidth. This is best +% suited to comparing distribution shapes. Default. +% 1 : histograms are normalized such that equal bin width +% reports equal numbers of counts per bin. +% 2 : histograms are normalized so that the relative areas +% covered by the histograms reflect the relative total number +% of data points. +% 3 : histograms areas are normalized so that relative densities +% are the same across histograms. Thus, if +% data = {rand(100,1),rand(500,1)}, +% then +% distributionPlot(data,'globalNorm',2,'histOpt',0,'divFactor',10) +% shows the left histogram 5x as wide as the right, while +% distributionPlot(data,'globalNorm',3,'histOpt',0,'divFactor',10) +% displays both histograms equally wide, since each bin +% contains ~10% of the data. +% Options 1 and 2 produce similar results if the bins are spaced +% equally for the distributions. Options 0 and 3 produce similar +% results if the data are drawn from the same distributions. +% Note that colormaps currently always report the number of data +% points per bin; 'globalNorm' only applies to the distribution +% shape. +% +% groups : grouping variable for grouped data. Grouping will be +% resolved by calling grp2idx, and unless xNames have +% been supplied, group names determine the x-labels. +% If the grouping variable is numeric, group labels also +% determine x-values, unless the parameter xValues has +% been specified. +% histOpt : histogram type to plot +% 0 : use hist command (no smoothing, fixed number of +% bins) +% 1 : smoothened histogram using ksdensity with +% Normal kernel. Default. +% 1.1: smoothened histogram using ksdensity where the +% kernel is robustly estimated via histogram.m. +% Normal kernel. +% 2 : histogram command (no smoothing, automatic +% determination of thickness (y-direction) of bins) +% divFactor : Parameter dependent on histOpt. If... +% histOpt == 0: divFactor = # of bins. Default: 25. +% Alternatively, pass a vector which will be +% interpreted as bin centers. +% histOpt == 1: divFactor decides by how much the default +% kernel-width is multiplied in order to avoid an +% overly smooth histogram. Default: 1/2 +% histOpt == 2: divFactor decides by how much the +% automatic bin width is multiplied in order to have +% more (<1) or less (>1) detail. Default: 1 +% addSpread : if 1, data points are plotted with plotSpread. +% distWidth is ideally set to 0.95 +% This option is not available if the data is supplied as +% histograms. +% Please download plotSpread.m separately from the File +% Exchange using the link in the remarks +% showMM : if 1, mean and median are shown as red crosses and +% green squares, respectively. This is the default +% 2: only mean +% 3: only median +% 4: mean +/- standard error of the mean (no median) +% 5: mean +/- standard deviation (no median) +% 6: draw lines at the 25,50,75 percentiles (no mean) +% 0: plot neither mean nor median +% xValues: x-coordinate where the data should be plotted. +% If xValues are given, "distWidth" is scaled by the median +% difference between adjacent (sorted) x-values. Note that +% this may lead to overlapping distributions. Default: +% 1:nData +% xNames : cell array of length nData containing x-tick names +% (instead of the default '1,2,3') +% xMode : if 'auto', x-ticks are spaced automatically. If 'manual', +% there is a tick for each distribution. If xNames is +% provided as input, xMode is forced to 'manual'. Default: +% 'manual'. +% NOTE: SPECIFYING XNAMES OR XVALUES OR XMODE WILL ERASE PREVIOUS +% LABELS IF PLOTTING INTO EXISTING AXES +% yLabel : string with label for y-axis. Default : '' +% If empty and data is histograms, ylabel is set to 'counts' +% invert : if 1, axes color is changed to black, and colormap is +% inverted. +% histOri: Orientation of histogram. Either 'center', 'left', or +% 'right'. With 'left' or 'right', the left or right half of +% the standard violin plot is shown. Has no effect if +% variableWidth is false. Default: center +% xyOri : orientation of axes. Either 'normal' (=default), or +% 'flipped'. If 'flipped', the x-and y-axes are switched, so +% that violin plots are horizontal. Consequently, +% axes-specific properties, such as 'yLabel' are applied to +% the other axis. +% widthDiv : 1-by-2 array with [numberOfDivisions,currentDivision] +% widthDiv allows cutting the stripe dedicated to a single +% distribution into multible bands, which can be filled with +% sequential calls to distributionPlot. This is one way +% to compare two (or more) sequences of distributions. See +% example below. +% ah : axes handle to plot the distributions. Default: gca +% +% OUTPUT handles : 1-by-4 cell array with patch-handles for the +% distributions, plot handles for mean/median, the +% axes handle, and the plotSpread-points handle +% +% +% EXAMPLES +% %--Distributions contain more information than boxplot can capture +% r = rand(1000,1); +% rn = randn(1000,1)*0.38+0.5; +% rn2 = [randn(500,1)*0.1+0.27;randn(500,1)*0.1+0.73]; +% rn2=min(rn2,1);rn2=max(rn2,0); +% figure +% ah(1)=subplot(3,4,1:2); +% boxplot([r,rn,rn2]) +% ah(2)=subplot(3,4,3:4); +% distributionPlot([r,rn,rn2],'histOpt',2); % histOpt=2 works better for uniform distributions than the default +% set(ah,'ylim',[-1 2]) +% +% %--- additional options +% +% data = [randn(100,1);randn(50,1)+4;randn(25,1)+8]; +% subplot(3,4,5) +% +% %--- defaults +% distributionPlot(data); +% subplot(3,4,6) +% +% %--- show density via custom colormap only, show mean/std, +% distributionPlot(data,'colormap',copper,'showMM',5,'variableWidth',false) +% subplot(3,4,7:8) +% +% %--- auto-binwidth depends on # of datapoints; for small n, plotting the data is useful +% % note that this option requires the additional installation +% % of plotSpread from the File Exchange (link below) +% distributionPlot({data(1:5:end),repmat(data,2,1)},'addSpread',true,'showMM',false,'histOpt',2) +% +% %--- show quantiles +% subplot(3,4,9),distributionPlot(randn(100,1),'showMM',6) +% +% %--- horizontal orientation +% subplot(3,4,10:11), +% distributionPlot({chi2rnd(3,1000,1),chi2rnd(5,1000,1)},'xyOri','flipped','histOri','right','showMM',0), +% xlim([-3 13]) +% +% %--- compare distributions side-by-side (see also example below) +% % plotting into specified axes will throw a warning that you can +% % turn off using " warning off DISTRIBUTIONPLOT:ERASINGLABELS " +% ah = subplot(3,4,12); +% subplot(3,4,12),distributionPlot(chi2rnd(3,1000,1),'histOri','right','color','r','widthDiv',[2 2],'showMM',0) +% subplot(3,4,12),distributionPlot(chi2rnd(5,1000,1),'histOri','left','color','b','widthDiv',[2 1],'showMM',0) +% +% %--Use globalNorm to generate meaningful colorbar +% data = {randn(100,1),randn(500,1)}; +% figure +% distributionPlot(data,'globalNorm',true,'colormap',1-gray(64),'histOpt',0,'divFactor',[-5:0.5:5]) +% colorbar +% +% %--Use widthDiv to compare two series of distributions +% data1 = randn(500,5); +% data2 = bsxfun(@plus,randn(500,5),0:0.1:0.4); +% figure +% distributionPlot(data1,'widthDiv',[2 1],'histOri','left','color','b','showMM',4) +% distributionPlot(gca,data2,'widthDiv',[2 2],'histOri','right','color','k','showMM',4) +% +% %--Christmas trees! +% x=meshgrid(1:10,1:10); +% xx = tril(x); +% xx = xx(xx>0); +% figure +% hh=distributionPlot({xx,xx,xx},'color','g','addSpread',1,'histOpt',2,'showMM',0); +% set(hh{4}{1},'color','r','marker','o') +% END +% +% REMARKS To show distributions as clouds of points (~beeswarm plot), +% and/or to use the option "addSpread", please download the +% additional function plotSpread.m from the File Exchange +% http://www.mathworks.com/matlabcentral/fileexchange/37105-plot-spread-points-beeswarm-plot +% +% I used to run ksdensity with the Epanechnikov kernel. However, +% for integer data, the shape of the kernel can produce peaks +% between the integers, which is not ideal (use histOpt=2 for +% integer valued data). +% +% A previous iteration of distributionPlot used the input +% specifications below. They still work to ensure backward +% compatibility, but are no longer supported or updated. +% handles = distributionPlot(data,distWidth,showMM,xNames,histOpt,divFactor,invert,addSpread,globalNorm) +% where distWidth of 1 means that the maxima +% of two adjacent distributions might touch. Negative numbers +% indicate that the distributions should have constant width, i.e +% the density is only expressed through greylevels. +% Values between 1 and 2 are like values between 0 and 1, except +% that densities are not expressed via graylevels. Default: 1.9 +% +% +% SEE ALSO histogram, ksdensity, plotSpread, boxplot, grp2idx +% + +% created with MATLAB ver.: 7.6.0.324 (R2008a) on Windows_NT +% +% created by: Jonas Dorn; jonas.dorn@gmail.com +% DATE: 08-Jul-2008 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%==================================== +%% TEST INPUT +%==================================== + +% set defaults +def.xNames = []; +def.showMM = 1; +def.distWidth = 0.9; +def.histOpt = 1; +def.divFactor = [25,2,1]; +def.invert = false; +def.colormap = []; +def.color = 'k'; +def.addSpread = false; +def.globalNorm = false; +def.variableWidth = true; +def.groups = []; +def.yLabel = ''; +def.xValues = ''; +def.xMode = 'manual'; +def.histOri = 'center'; +def.xyOri = 'normal'; +def.widthDiv = [1 1]; +isHistogram = false; %# this parameter is not set by input + + +if nargin == 0 || isempty(varargin{1}) + error('not enough input arguments') +end + +% check for axes handle +if ~iscell(varargin{1}) && isscalar(varargin{1}) == 1 && ... + ishandle(varargin{1}) && strcmp(get(varargin{1},'Type'),'axes') + ah = varargin{1}; + data = varargin{2}; + varargin(1:2) = []; + newAx = false; + + +else + ah = gca; + data = varargin{1}; + varargin(1) = []; + newAx = true; +end + +% check for current axes limits. Set NaN if the axes have no children +% yet - we need that in case we're building a complicated set of +% distributions +if ~isempty(get(ah,'children')) + xAxLim = xlim; + yAxLim = ylim; +else + [xAxLim,yAxLim] = deal([NaN NaN]); +end + +fh = get(ah,'Parent'); + +% check data. If not cell, convert +if ~iscell(data) + [nPoints,nData] = size(data); + data = mat2cell(data,nPoints,ones(nData,1)); +else + % get nData + data = data(:); + nData = length(data); + % make sure all are vectors + badCol = ~cellfun(@isvector,data) & ~cellfun(@isempty,data); + if any(badCol) + nCols = cellfun(@(x)(size(x,2)),data(badCol)); + if all(nCols==2) + % bins,counts + isHistogram = true; + else + warning('DISTRIBUTIONPLOT:AUTORESHAPE',... + 'Elements %s of the cell array are not vectors. They will be reshaped automatically',... + num2str(find(badCol)')); + data(badCol) = cellfun(@(x)(x(:)),data(badCol),'UniformOutput',false); + end + end +end + +parserObj = inputParser; +parserObj.FunctionName = 'distributionPlot'; +stdWidth = 1; % scaling parameter for variableWidth with uneven x-values +% check whether we're dealing with pN/pV or straight arguments +if ~isempty(varargin) && ~ischar(varargin{1}) && ~isstruct(varargin{1}) + % use old format + % distWidth,showMM,xNames,histOpt,divFactor,invert,addSpread,globalNorm + def.distWidth = 1.9; + parserObj.addOptional('distWidth',def.distWidth); + parserObj.addOptional('showMM',def.showMM); + parserObj.addOptional('xNames',def.xNames); + parserObj.addOptional('histOpt',def.histOpt); + parserObj.addOptional('divFactor',def.divFactor); + parserObj.addOptional('invert',def.invert); + parserObj.addOptional('addSpread',def.addSpread); + parserObj.addOptional('globalNorm',def.globalNorm); + parserObj.addOptional('groups',def.groups); + parserObj.addOptional('yLabel',def.yLabel); + parserObj.addOptional('color',def.color); + + + parserObj.parse(varargin{:}); + opt = parserObj.Results; + % fill in defaults that are not supported in the old version of the + % code + opt.colormap = []; + opt.variableWidth = true; + opt.histOri = 'center'; + opt.xValues = []; + opt.xMode = 'auto'; + opt.xyOri = 'normal'; + opt.widthDiv = [1 1]; + + % overwrite empties with defaults - inputParser considers empty to be a + % valid input. + fnList = fieldnames(opt); + for fn = fnList' + if isempty(opt.(fn{1})) + opt.(fn{1}) = def.(fn{1}); + end + end + + + % fix a few parameters + if opt.distWidth > 1 + opt.distWidth = opt.distWidth - 1; + else + opt.colormap = 1-gray(128); + end + if opt.distWidth < 0 + opt.variableWidth = false; + opt.distWidth = abs(opt.distWidth); + end + + if ~isempty(opt.xNames) + opt.xMode = 'manual'; + end + + +else + defNames = fieldnames(def); + for dn = defNames(:)' + parserObj.addParamValue(dn{1},def.(dn{1})); + end + + + parserObj.parse(varargin{:}); + opt = parserObj.Results; + + % if groups: deal with data + if ~isempty(opt.groups) + [idx,labels,vals] = grp2idx(opt.groups); + % convert data to cell array + data = accumarray(idx,data{1},[],@(x){x}); + nData = length(data); + % if not otherwise provided, use group labels for xnames + if isempty(opt.xNames) + opt.xNames = labels; + if ~iscell(opt.xNames) + opt.xNames = num2cell(opt.xNames); + end + end + if isnumeric(vals) && isempty(opt.xValues) + opt.xValues = vals; + end + + end + + if ~ischar(opt.xyOri) || ~any(ismember(opt.xyOri,{'normal','flipped'})) + error('option xyOri must be either ''normal'' or ''flipped'' (is ''%s'')',opt.xyOri); + end + + + + +end +% common checks + +% default x-values: 1:n +if isempty(opt.xValues) + opt.xValues = 1:nData; +elseif length(opt.xValues) ~= nData + error('please supply as many x-data values as there are data entries') +elseif length(opt.xValues) > 1 % only check for scale if more than 1 value + % scale width + stdWidth = median(diff(sort(opt.xValues))); + opt.distWidth = opt.distWidth * stdWidth; +end + +if ~isscalar(opt.divFactor) && length(opt.divFactor) == 3 && all(opt.divFactor==def.divFactor) + opt.divFactor = opt.divFactor(floor(opt.histOpt)+1); +end +if isHistogram + opt.histOpt = 99; + if isempty(opt.yLabel) + opt.yLabel = 'counts'; + end +end + + + +% check colors/colormaps: do we need to expand colormap? +if ~iscell(opt.colormap) + opt.colormap = {opt.colormap}; +end +if ~iscell(opt.color) + opt.color = {opt.color}; +end +for iColor = 1:length(opt.color) + if ischar(opt.color{iColor}) + opt.color{iColor} = colorCode2rgb(opt.color{iColor}); + end +end + +% expand - if only single colormap specified, we expand only once +if ~opt.variableWidth + missingColormaps = find(cellfun(@isempty,opt.colormap)); + for iMissing = missingColormaps(:)' + + endColor = opt.color{max(iMissing,length(opt.color))}; + % normally, we go from white to color + cmap = zeros(128,3); + for rgb = 1:3 + cmap(:,rgb) = linspace(1,endColor(rgb),128); + end + opt.colormap{iMissing} = cmap; + + end +end + +% if we have colormaps, we need to create a master which we add to the +% figure. Invert if necessary, and expand the cell array to nData +colormapLength = cellfun(@(x)size(x,1),opt.colormap); +if any(colormapLength>0) + + colormap = cat(1,opt.colormap{:}); + if opt.invert + colormap = 1-colormap; + end + set(fh,'Colormap',colormap) + if length(opt.colormap) == 1 + opt.colormap = repmat(opt.colormap,nData,1); + colormapLength = repmat(colormapLength,nData,1); + colormapOffset = zeros(nData,1); + singleMap = true; + else + colormapOffset = [0;cumsum(colormapLength(1:end-1))]; + singleMap = false; + end + +else + + colormapLength = zeros(nData,1); + if length(opt.color) == 1 + opt.color = repmat(opt.color,nData,1); + end + if opt.invert + opt.color = cellfun(@(x)1-x,opt.color,'uniformOutput',false); + end +end + + +% set hold on +holdState = get(ah,'NextPlot'); +set(ah,'NextPlot','add'); + +% if new axes: invert +if newAx && opt.invert + set(ah,'Color','k') +end + +%=================================== + + + +%=================================== +%% PLOT DISTRIBUTIONS +%=================================== + +% assign output +hh = NaN(nData,1); +[m,md,sem,sd] = deal(nan(nData,1)); +if opt.showMM == 6 + md = nan(nData,3,3); % md/q1/q3, third dim is y/xmin/xmax +end + +% get base x-array +% widthDiv is a 1-by-2 array with +% #ofDivs, whichDiv +% The full width (distWidth) is split into +% #ofDivs; whichDiv says which "stripe" is active +xWidth = opt.distWidth/opt.widthDiv(1); +xMin = -opt.distWidth/2; +xLow = xMin + xWidth * (opt.widthDiv(2)-1); +xBase = [-xWidth;xWidth;xWidth;-xWidth]/2; +xOffset = xLow + xWidth/2; + +% b/c of global norm: loop twice +plotData = cell(nData,2); + +% loop through data. Prepare patch input, then draw patch into gca +for iData = 1:nData + currentData = data{iData}; + % only plot if there is some finite data + if ~isempty(currentData(:)) && any(isfinite(currentData(:))) + + switch floor(opt.histOpt) + case 0 + % use hist + [xHist,yHist] = hist(currentData,opt.divFactor); + + case 1 + % use ksdensity + + if opt.histOpt == 1.1 + % use histogram to estimate kernel + [dummy,x] = histogram(currentData); %#ok + if length(x) == 1 + % only one value. Make fixed distribution + dx = 0.1; + yHist = x; + xHist = sum(isfinite(currentData)); + else + dx = x(2) - x(1); + + % make sure we sample frequently enough + x = min(x)-dx:dx/3:max(x)+dx; + [xHist,yHist] = ksdensity(currentData,x,'kernel','normal','width',dx/(1.5*opt.divFactor)); + end + else + + % x,y are switched relative to normal histogram + [xHist,yHist,u] = ksdensity(currentData,'kernel','normal'); + % take smaller kernel to avoid over-smoothing + if opt.divFactor ~= 1 + [xHist,yHist] = ksdensity(currentData,'kernel','normal','width',u/opt.divFactor); + end + end + + % modify histogram such that the sum of bins (not the + % integral under the curve!) equals the total number of + % observations, in order to be comparable to hist + xHist = xHist/sum(xHist)*sum(isfinite(currentData)); + + case 2 + % use histogram - bar heights are counts as in hist + [xHist,yHist] = histogram(currentData,opt.divFactor,0); + case 99 + % bins,counts already supplied + xHist = currentData(:,2)'; + yHist = currentData(:,1)'; + end + plotData{iData,1} = xHist; + plotData{iData,2} = yHist; + end +end + +goodData = find(~cellfun(@isempty,plotData(:,1))); +% get norm +switch opt.globalNorm + case 3 + % #3 normalizes relative densities + xNorm(goodData) = cellfun(@(x)min(diff(x)),plotData(goodData,2)); + xNorm(goodData) = xNorm(goodData) .* cellfun(@sum,plotData(goodData,1))'; + maxNorm(goodData) = cellfun(@max,plotData(goodData,1)); + xNorm(goodData) = xNorm(goodData)*max(maxNorm(goodData)./xNorm(goodData)); + + case 2 + % #2 should normalize so that the integral of the + % different histograms (i.e. area covered) scale with the + % respective sum of counts across all bins. Requires evenly spaced + % histograms at the moment + xNorm(goodData) = cellfun(@(x)min(diff(x)),plotData(goodData,2)); + maxNorm(goodData) = cellfun(@max,plotData(goodData,1)); + xNorm(goodData) = xNorm(goodData)*max(maxNorm(goodData)./xNorm(goodData)); + case 1 + xNorm(goodData) = max(cat(2,plotData{:,1})); + case 0 + xNorm(goodData) = cellfun(@max,plotData(goodData,1)); +end + + +for iData = goodData' + + % find current data again + currentData = data{iData}; + + xHist = plotData{iData,1}; + yHist = plotData{iData,2}; + + % find y-step + dy = min(diff(yHist)); + if isempty(dy) + dy = 0.1; + end + + % create x,y arrays + nPoints = length(xHist); + xArray = repmat(xBase,1,nPoints); + yArray = repmat([-0.5;-0.5;0.5;0.5],1,nPoints); + + + % x is iData +/- almost 0.5, multiplied with the height of the + % histogram + if opt.variableWidth + + + tmp = xArray.*repmat(xHist,4,1)./xNorm(iData); + + switch opt.histOri + case 'center' + % we can simply use xArray + xArray = tmp; + case 'right' + % shift everything to the left + delta = tmp(1,:) - xArray(1,:); + xArray = bsxfun(@minus,tmp,delta); + case 'left' + % shift everything to the right + delta = tmp(1,:) - xArray(1,:); + xArray = bsxfun(@plus,tmp,delta); + end + + xArray = xArray + opt.xValues(iData); + + else + xArray = xArray + iData; + end + + % add offset (in case we have multiple widthDiv) + xArray = xArray + xOffset; + + + % yData is simply the bin locations + yArray = repmat(yHist,4,1) + dy*yArray; + + % add patch + vertices = [xArray(:),yArray(:)]; + faces = reshape(1:numel(yArray),4,[])'; + + if colormapLength(iData) == 0 + colorOpt = {'FaceColor',opt.color{iData}}; + else + % calculate index into colormap + if singleMap + % use scaled mapping so that colorbar is meaningful + if opt.globalNorm > 0 + colorOpt = {'FaceVertexCData',xHist','CDataMapping','scaled','FaceColor','flat'}; + else + colorOpt = {'FaceVertexCData',xHist'/xNorm(iData),'CDataMapping','scaled','FaceColor','flat'}; + end + + else + idx = round((xHist/xNorm(iData))*(colormapLength(iData)-1))+1; + colorOpt = {'FaceVertexCData',idx'+colormapOffset(iData),'CDataMapping','direct','FaceColor','flat'}; + end + end + + + switch opt.xyOri + case 'normal' + hh(iData)= patch('Vertices',vertices,'Faces',faces,'Parent',ah,colorOpt{:},'EdgeColor','none'); + case 'flipped' + hh(iData)= patch('Vertices',vertices(:,[2,1]),'Faces',faces,'Parent',ah,colorOpt{:},'EdgeColor','none'); + end + + if opt.showMM > 0 + if isHistogram + [m(iData),sem(iData)] = weightedStats(currentData(:,1),currentData(:,2),'w'); + sd(iData) = sem(iData) * sqrt(sum(currentData(:,2))); + % weighted median: where we're at middle weight + % may need some tweaking + goodCurrentData = sortrows(currentData(all(isfinite(currentData),2),:),1); + weightList = cumsum(goodCurrentData(:,2)); + weightList = weightList / weightList(end); + md(iData) = goodCurrentData(find(weightList>0.5,1,'first'),1); + else + m(iData) = nanmean(currentData); + md(iData) = nanmedian(currentData); + sd(iData) = nanstd(currentData); + sem(iData) = sd(iData)/sqrt(sum(isfinite(currentData))); + end + + if opt.showMM == 6 + % read quantiles - "y"-value, plus x-start-stop + % re-use md array which allows using a loop below instead of + % lots of copy-paste + % md array is md/q1/q3, with third dimension y/xmin/xmax + + md(iData,2,1) = prctile(currentData,25); + md(iData,3,1) = prctile(currentData,75); + + for qq = 1:3 + % find corresponding y-bin + yLoc = repmat(... + any(yArray>md(iData,qq,1),1) & any(yArray<=md(iData,qq,1),1),... + [4 1]); + % look up corresponding x-values. Note that there is a bit + % of a risk that the line will be exactly between two very + % different bins - but if we make the line longer, it will + % be ugly almost all the time + md(iData,qq,2) = min( xArray( yLoc ) ); + md(iData,qq,3) = max( xArray( yLoc ) ); + end + + end + end +end % loop + +sh = []; +if opt.addSpread + if isHistogram + disp('Option addSpread is unavailable if data is supplied as histograms. Call plotSpread separately') + else + % add spread + try + sh = plotSpread(ah,data,'xValues',opt.xValues,'xyOri',opt.xyOri); + set(sh{1},'color',[0,128,255]/255); + catch me + if strcmp(me.identifier,'MATLAB:UndefinedFunction') + error('plotSpread not found. Please download it from the Matlab File Exchange') + else + rethrow(me) + end + end + end +end + +mh = [];mdh=[]; +if opt.showMM + % plot mean, median. Mean is filled red circle, median is green square + % I don't know of a very clever way to flip xy and keep everything + % readable, thus it'll be copy-paste + switch opt.xyOri + case 'normal' + if any(opt.showMM==[1,2]) + mh = plot(ah,opt.xValues+xOffset,m,'+r','Color','r','MarkerSize',12); + end + if any(opt.showMM==[1,3]) + mdh = plot(ah,opt.xValues+xOffset,md,'sg','MarkerSize',12); + end + if opt.showMM == 4 + mh = plot(ah,opt.xValues+xOffset,m,'+r','Color','r','MarkerSize',12); + mdh = myErrorbar(ah,opt.xValues+xOffset,m,sem); + end + if opt.showMM == 5 + mh = plot(ah,opt.xValues+xOffset,m,'+r','Color','r','MarkerSize',12); + mdh = myErrorbar(ah,opt.xValues+xOffset,m,sd); + end + if opt.showMM == 6 + mdh(1,:) = plot(ah,squeeze(md(:,1,2:3))',repmat(md(:,1,1)',2,1),'color','r','lineWidth',2);%,'lineStyle','--'); + mdh(2,:) = plot(ah,squeeze(md(:,2,2:3))',repmat(md(:,2,1)',2,1),'color','r','lineWidth',1);%,'lineStyle','--'); + mdh(3,:) = plot(ah,squeeze(md(:,3,2:3))',repmat(md(:,3,1)',2,1),'color','r','lineWidth',1);%,'lineStyle','--'); + end + case 'flipped' + if any(opt.showMM==[1,2]) + mh = plot(ah,m,opt.xValues+xOffset,'+r','Color','r','MarkerSize',12); + end + if any(opt.showMM==[1,3]) + mdh = plot(ah,md,opt.xValues+xOffset,'sg','MarkerSize',12); + end + if opt.showMM == 4 + mh = plot(ah,m,opt.xValues+xOffset,'+r','Color','r','MarkerSize',12); + mdh = myErrorbar(ah,m,opt.xValues+xOffset,[sem,NaN(size(sem))]); + end + if opt.showMM == 5 + mh = plot(ah,m,opt.xValues+xOffset,'+r','Color','r','MarkerSize',12); + mdh = myErrorbar(ah,m,opt.xValues+xOffset,[sd,NaN(size(sd))]); + end + if opt.showMM == 6 + mdh(1,:) = plot(ah,repmat(md(:,1,1)',2,1),squeeze(md(:,1,2:3))','color','r','lineWidth',2);%,'lineStyle','--'); + mdh(2,:) = plot(ah,repmat(md(:,2,1)',2,1),squeeze(md(:,2,2:3))','color','r','lineWidth',1);%,'lineStyle','--'); + mdh(3,:) = plot(ah,repmat(md(:,3,1)',2,1),squeeze(md(:,3,2:3))','color','r','lineWidth',1);%,'lineStyle','--'); + end + end +end + +% find extents of x-axis (or y-axis, if flipped) +minX = min(opt.xValues)-stdWidth; +maxX = max(opt.xValues)+stdWidth; + +if ~isnan(xAxLim(1)) + % we have previous limits + switch opt.xyOri + case 'normal' + minX = min(minX,xAxLim(1)); + maxX = max(maxX,xAxLim(2)); + case 'flipped' + minX = min(minX,yAxLim(1)); + maxX = max(maxX,yAxLim(2)); + end +end + + +% if ~empty, use xNames +switch opt.xyOri + case 'normal' + switch opt.xMode + case 'manual' + if newAx == false + warning('DISTRIBUTIONPLOT:ERASINGLABELS','Plotting into an existing axes and specifying labels will erase previous labels') + end + set(ah,'XTick',opt.xValues); + if ~isempty(opt.xNames) + set(ah,'XTickLabel',opt.xNames) + end + case 'auto' + % no need to do anything + end + if ~isempty(opt.yLabel) + ylabel(ah,opt.yLabel); + end + % have plot start/end properly + xlim(ah,[minX,maxX]) + case 'flipped' + switch opt.xMode + case 'manual' + if newAx == false + warning('DISTRIBUTIONPLOT:ERASINGLABELS','Plotting into an existing axes and specifying labels will erase previous labels') + end + set(ah,'YTick',opt.xValues); + if ~isempty(opt.xNames) + set(ah,'YTickLabel',opt.xNames) + end + case 'auto' + % no need to do anything + end + if ~isempty(opt.yLabel) + xlabel(ah,opt.yLabel); + end + % have plot start/end properly + ylim(ah,[minX,maxX]) +end + + +%========================== + + +%========================== +%% CLEANUP & ASSIGN OUTPUT +%========================== + +if nargout > 0 + handles{1} = hh; + handles{2} = [mh;mdh]; + handles{3} = ah; + handles{4} = sh; +end + +set(ah,'NextPlot',holdState); \ No newline at end of file diff --git a/Plotting/plot_slice.m b/Plotting/plot_slice.m new file mode 100755 index 0000000..cb4f2c8 --- /dev/null +++ b/Plotting/plot_slice.m @@ -0,0 +1,88 @@ +%% This function plots the CAPs obtained for a given case +% Inputs: +% - Cp: a matrix of size n_clusters x n_voxels with the CAPs (or patterns) +% to plot +% - T: the threshold below which voxels will not be colored +% - maxC: the maximal value at which the color display will saturate +% - mask: a very long vector of logicals symbolizing the regions of the +% brain that are actually to be considered (in-brain voxels typically) +% - brain_final: a 3D matrix used to plot the greyscale brain template on which +% to overlay activity patterns +% - ai: the nii data related to the considered seed, including information +% on the scale differences between Cp indexes ('distance between voxels of +% the matrix') and actual MNI space +% - Dimension: the type of slice to plot ('X', 'Y' or 'Z') +% - MNI: the MNI coordinate of the slice to plot +% - Handle: the handle of the graph to update +function [Handle] = plot_slice(Cp,T,maxC,mask,brain_final,ai,Dimension,MNI,Handle) + + % Computes the matrix index matching the MNI coordinates of interest. + Map = inv(ai.mat); + + switch Dimension + case 'X' + ctr = round(Map(1,1)*MNI+Map(1,4)); + case 'Y' + ctr = round(Map(2,2)*MNI+Map(2,4)); + case 'Z' + ctr = round(Map(3,3)*MNI+Map(3,4)); + end + + % temp contains the volume values (within mask), and is a 3D volume after + % those lines + temp = nan(size(mask)); + temp(mask) = Cp; + temp(isnan(temp)) = 0; + temp = reshape(temp,ai.dim); + + % ho contains the structural underlay slice to plot at right slice, + % while tmpp contains the values to plot on top + switch Dimension + case {'X'} + tmpp = squeeze(temp(ctr,:,:)); + ho = squeeze(brain_final(ctr,:,:)); + case {'Y'} + tmpp = squeeze(temp(:,ctr,:)); + ho = squeeze(brain_final(:,ctr,:)); + case {'Z'} + tmpp = squeeze(temp(:,:,ctr)); + ho = squeeze(brain_final(:,:,ctr)); + end + + % I is an image with values from 0 to 1 (because the original + % image had no negative value) + I = double(ho)'/max(double(ho(:))); + I(I==0) = 1; + + % Creates an 'image with three values per pixel' + Irgb = cat(3,I,I,I); + + % Actual plotting + imagesc(Irgb,'Parent',Handle); + hold(Handle,'on'); + + % Plots the slice of interest on top of the brain template + h=imagesc(tmpp','Parent',Handle); + set(Handle,'YDir','normal'); + + % At this stage, ddd contains a set of colors, with white if the values + % are too low. We ask the axes of interest to be using this colormap + % colormap(Handle,ddd); + tmp_cm = cbrewer('div','RdBu',1000); + colormap(Handle,flipud(tmp_cm)); + + % Defines that the topmost and bottommost elements of the + % colormap will map maxC and -maxC respectively + caxis(Handle,[-1 1]*maxC); + + % Opacity: sets data points below the value of interest as + % transparent (they are white, but transparent); note that we do this + % specifically for the h imagesc (the one to plot on top) + A = ones(size(tmpp)); + A(abs(tmpp)0.7)=Inf; +[a,b]=munkres(A); +%} +% Example 4: an example of partial assignment +%{ +A = [1 3 Inf; Inf Inf 5; Inf Inf 0.5]; +[a,b]=munkres(A) +%} +% a = [1 0 3] +% b = 1.5 +% Reference: +% "Munkres' Assignment Algorithm, Modified for Rectangular Matrices", +% http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html + +% version 2.3 by Yi Cao at Cranfield University on 11th September 2011 + +assignment = zeros(1,size(costMat,1)); +cost = 0; + +validMat = costMat == costMat & costMat < Inf; +bigM = 10^(ceil(log10(sum(costMat(validMat))))+1); +costMat(~validMat) = bigM; + +% costMat(costMat~=costMat)=Inf; +% validMat = costMat0) + break + end + coverColumn = false(1,n); + coverColumn(starZ(starZ>0))=true; + coverRow = false(n,1); + primeZ = zeros(n,1); + [rIdx, cIdx] = find(dMat(~coverRow,~coverColumn)==bsxfun(@plus,minR(~coverRow),minC(~coverColumn))); + while 1 + %************************************************************************** + % STEP 4: Find a noncovered zero and prime it. If there is no starred + % zero in the row containing this primed zero, Go to Step 5. + % Otherwise, cover this row and uncover the column containing + % the starred zero. Continue in this manner until there are no + % uncovered zeros left. Save the smallest uncovered value and + % Go to Step 6. + %************************************************************************** + cR = find(~coverRow); + cC = find(~coverColumn); + rIdx = cR(rIdx); + cIdx = cC(cIdx); + Step = 6; + while ~isempty(cIdx) + uZr = rIdx(1); + uZc = cIdx(1); + primeZ(uZr) = uZc; + stz = starZ(uZr); + if ~stz + Step = 5; + break; + end + coverRow(uZr) = true; + coverColumn(stz) = false; + z = rIdx==uZr; + rIdx(z) = []; + cIdx(z) = []; + cR = find(~coverRow); + z = dMat(~coverRow,stz) == minR(~coverRow) + minC(stz); + rIdx = [rIdx(:);cR(z)]; + cIdx = [cIdx(:);stz(ones(sum(z),1))]; + end + if Step == 6 + % ************************************************************************* + % STEP 6: Add the minimum uncovered value to every element of each covered + % row, and subtract it from every element of each uncovered column. + % Return to Step 4 without altering any stars, primes, or covered lines. + %************************************************************************** + [minval,rIdx,cIdx]=outerplus(dMat(~coverRow,~coverColumn),minR(~coverRow),minC(~coverColumn)); + minC(~coverColumn) = minC(~coverColumn) + minval; + minR(coverRow) = minR(coverRow) - minval; + else + break + end + end + %************************************************************************** + % STEP 5: + % Construct a series of alternating primed and starred zeros as + % follows: + % Let Z0 represent the uncovered primed zero found in Step 4. + % Let Z1 denote the starred zero in the column of Z0 (if any). + % Let Z2 denote the primed zero in the row of Z1 (there will always + % be one). Continue until the series terminates at a primed zero + % that has no starred zero in its column. Unstar each starred + % zero of the series, star each primed zero of the series, erase + % all primes and uncover every line in the matrix. Return to Step 3. + %************************************************************************** + rowZ1 = find(starZ==uZc); + starZ(uZr)=uZc; + while rowZ1>0 + starZ(rowZ1)=0; + uZc = primeZ(rowZ1); + uZr = rowZ1; + rowZ1 = find(starZ==uZc); + starZ(uZr)=uZc; + end +end + +% Cost of assignment +rowIdx = find(validRow); +colIdx = find(validCol); +starZ = starZ(1:nRows); +vIdx = starZ <= nCols; +assignment(rowIdx(vIdx)) = colIdx(starZ(vIdx)); +pass = assignment(assignment>0); +pass(~diag(validMat(assignment>0,pass))) = 0; +assignment(assignment>0) = pass; +cost = trace(costMat(assignment>0,assignment(assignment>0))); + +function [minval,rIdx,cIdx]=outerplus(M,x,y) +ny=size(M,2); +minval=inf; +for c=1:ny + M(:,c)=M(:,c)-(x+y(c)); + minval = min(minval,min(M(:,c))); +end +[rIdx,cIdx]=find(M==minval);