function [imo,rois] = fast_rcnn_eval_get_batch(images, imdb, batch, opts)
% FAST_RCNN_GET_BATCH_EVAL Load, preprocess, and pack images for CNN
% evaluation

% opts.numFgRoisPerImg = 128;
% opts.numRoisPerImg = 64;
% opts.maxScale = 1000;
% opts.bgLabel = 21;
% opts.visualize = 1;
% opts.scale = 600;
% opts.interpolation = 'bicubic';
% opts.averageImage = [];
% opts.numThreads = 2;
%
% Copyright (C) 2016 Hakan Bilen.
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).

if isempty(images)
  imo = [] ;
  rois = [] ;
  return ;
end

% fetch is true if images is a list of filenames (instead of
% a cell array of images)
fetch = ischar(images{1}) ;

% prefetch is used to load images in a separate thread
prefetch = fetch & opts.prefetch ;

if prefetch
  vl_imreadjpeg(images, 'numThreads',opts.numThreads,'prefetch') ;
  imo = [] ;
  rois = [] ;
  return ;
end

if fetch
  ims = vl_imreadjpeg(images,'numThreads',opts.numThreads) ;
else
  ims = images ;
end

imre = cell(1,numel(batch));
maxW = 0;
maxH = 0;

pboxes  = cell(1,numel(batch));

% get fg and bg rois
for b=1:numel(batch)
  pbox   = imdb.boxes.pbox{batch(b)};

  if size(pbox,2)~=4
    error('wrong box size');
  end

  pboxes{b} = pbox;
end

% rescale images and rois
rois = [];
for b=1:numel(batch)
  imSize = size(ims{b});

  h = imSize(1);
  w = imSize(2);

  factor = max(opts.scale(1)/h,opts.scale(1)/w);

  if any([h*factor,w*factor]>opts.maxScale)
    factor = min(opts.maxScale/h,opts.maxScale/w);
  end

  if abs(factor-1)>1e-3
    imre{b} = imresize(ims{b},factor,'Method',opts.interpolation,...
        'antialiasing', false);
  else
    imre{b} = ims{b};
  end

  if imdb.boxes.flip(batch(b))
    im = imre{b};
    imre{b} = im(:,end:-1:1,:);
  end

  imreSize = size(imre{b});

  maxH = max(imreSize(1),maxH);
  maxW = max(imreSize(2),maxW);

  % adapt bounding boxes into new coord
  bbox = pboxes{b};
  if any(bbox(:)<=0)
    error('bbox error');
  end

  nB = size(bbox,1);
  tbbox = bbox_scale(bbox,factor,[imreSize(2) imreSize(1)]);
  if any(tbbox(:)<=0)
    error('tbbox error');
  end

  rois = [rois [b*ones(1,nB); tbbox' ] ];
end

% rois = single(rois);
imo = zeros(maxH,maxW,size(imre{1},3),numel(batch),'single');
for b=1:numel(batch)
  if ~isempty(opts.averageImage)
    imre{b} = single(bsxfun(@minus,imre{b},opts.averageImage));
  end
  sz = size(imre{b});
  imo(1:sz(1),1:sz(2),:,b) = single(imre{b});
end


function F = in_fread_micromed(sFile, sfid, SamplesBounds, ChannelsRange)
% IN_FREAD_MICROMED:  Read a block of recordings from a Micromed TRC file
%
% USAGE:  F = in_fread_micromed(sFile, sfid, SamplesBounds=[], ChannelsRange=[])

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2017

% Parse inputs
if (nargin < 4) || isempty(ChannelsRange)
    ChannelsRange = [1, sFile.header.num_channels];
end
if (nargin < 3) || isempty(SamplesBounds)
    SamplesBounds = round(sFile.prop.times .* sFile.prop.sfreq);
end

% ===== COMPUTE OFFSETS =====
nChannels     = double(sFile.header.num_channels);
nReadTimes    = SamplesBounds(2) - SamplesBounds(1) + 1;
nReadChannels = double(ChannelsRange(2) - ChannelsRange(1) + 1);
% Data type
bytesPerVal = sFile.header.num_bytes;
switch bytesPerVal
    case 1, dataClass = 'uint8';
    case 2, dataClass = 'uint16';
    case 4, dataClass = 'uint32';
end

% Sample offset of the first block, for reduced files
if isfield(sFile.header, 'smp_offset') && ~isempty(sFile.header.smp_offset)
    smp_offset = sFile.header.smp_offset;
else
    smp_offset = 0;
end
% Time offset
offsetTime = round((SamplesBounds(1) - smp_offset) * nChannels * bytesPerVal);
% Channel offset at the beginning and end of each channel block
offsetChannelStart = round((ChannelsRange(1)-1) * bytesPerVal);
offsetChannelEnd   = (nChannels - ChannelsRange(2)) * bytesPerVal;
% Start reading at this point
offsetStart = sFile.header.data_offset + offsetTime + offsetChannelStart;
% Number of time samples to skip after each channel
offsetSkip = offsetChannelStart + offsetChannelEnd;

% ===== READ DATA BLOCK =====
% Position file at the beginning of the trial
fseek(sfid, offsetStart, 'bof');
% Read trial data
% => WARNING: CALL TO FREAD WITH SKIP=0 DOES NOT WORK PROPERLY
if (offsetSkip == 0)
    F = fread(sfid, [nReadChannels, nReadTimes], dataClass);
else
    precision = sprintf('%d*%s', nReadChannels, dataClass);
    F = fread(sfid, [nReadChannels, nReadTimes], precision, offsetSkip);
end
% Check that data block was fully read
if (numel(F) < nReadTimes * nReadChannels)
    % Error message
    disp(sprintf('BST> ERROR: File is truncated (%d values were read instead of %d)...', numel(F), nReadTimes * nReadChannels));
    % Pad with zeros
    Ftmp = zeros(nReadChannels, nReadTimes);
    Ftmp(1:numel(F)) = F(:);
    F = Ftmp;
end
% Apply offset and gains
chan = sFile.header.electrode(ChannelsRange(1):ChannelsRange(2));
F = bst_bsxfun(@minus, F, [chan.logicGround]');
F = bst_bsxfun(@times, F, ([chan.physicalMin]' - [chan.physicalMax]') ./ ([chan.logicMax]' - [chan.logicMin]' + 1) .* [chan.unit_gain]');


function [Path,Xi] = hmmdecode(data,T,hmm,type,residuals,preproc)
%
% State time course and Viterbi decoding for hmm
% The algorithm is run for the whole data set, including those whose class
% was fixed. This means that the assignment for those can be different.
%
% INPUT
% data          observations, either a struct with X (time series) and C (classes, optional)
%                             or just a matrix containing the time series
% T             length of series
% hmm           hmm data structure
% type          0, state time courses (default); 1, viterbi path
% residuals     in case we train on residuals, the value of those (optional)
% preproc       whether we should perform the preprocessing options with
%               which the hmm model was trained; 1 by default.
%
% OUTPUT
% Path         (T x 1) maximum likelihood state sequence (type=1) OR
% Path         (T x K) state time courses
% Xi            joint probability of past and future states conditioned on data
%                   (empty if Viterbi path is computed)
%
% Author: Diego Vidaurre, OHBA, University of Oxford

% to fix potential compatibility issues with previous versions
hmm = versCompatibilityFix(hmm);

if nargin<4 || isempty(type), type = 0; end
if nargin<5, residuals = []; end
if nargin<6 || isempty(preproc), preproc = 1; end
% if nargin<7 || isempty(grouping)
%     if isfield(hmm.train,'grouping')
%         grouping = hmm.train.grouping;
%     else
%         grouping = ones(length(T),1);
%     end
%     if size(grouping,1)==1,  grouping = grouping'; end
% end

% if length(size(hmm.Dir_alpha))==3 && isempty(grouping)
%     error('You must specify the grouping argument if the HMM was trained on different groups')
% elseif ~isempty(grouping)
%     Q = length(unique(grouping));
% else
%     Q = 1;
% end

stochastic_learn = isfield(hmm.train,'BIGNbatch') && hmm.train.BIGNbatch < length(T);
mixture_model = isfield(hmm.train,'id_mixture') && hmm.train.id_mixture;
p = hmm.train.lowrank; do_HMM_pca = (p > 0);

if mixture_model && type==1
   error('Viterbi path not implemented for mixture model')
end

if xor(iscell(data),iscell(T)), error('data and T must be cells, either both or none of them.'); end
if stochastic_learn
    N = length(T);
    if ~iscell(data)
       dat = cell(N,1); TT = cell(N,1);
       for i = 1:N
          t = 1:T(i);
          dat{i} = data(t,:); TT{i} = T(i);
          try data(t,:) = [];
          catch, error('The dimension of data does not correspond to T');
          end
       end
       if ~isempty(data)
           error('The dimension of data does not correspond to T');
       end
       data = dat; T = TT; clear dat TT
    end
    if nargin<2
        Path = hmmsdecode(data,T,hmm,type);
    else
        [Path,Xi] = hmmsdecode(data,T,hmm,type);
    end
    return
else % data can be a cell or a matrix
    if iscell(T)
        for i = 1:length(T)
            if size(T{i},1)==1, T{i} = T{i}'; end
        end
        if size(T,1)==1, T = T'; end
        T = cell2mat(T);
    end
    checkdatacell;
    N = length(T);
end

if preproc % Adjust the data if necessary
    train = hmm.train;
    checkdatacell;
    data = data2struct(data,T,train);
    % Standardise data and control for ackward trials
    data = standardisedata(data,T,train.standardise);
    % Filtering
    if ~isempty(train.filter)
        data = filterdata(data,T,train.Fs,train.filter);
    end
    % Detrend data
    if train.detrend
        data = detrenddata(data,T);
    end
    % Leakage correction
    if train.leakagecorr ~= 0
        data = leakcorr(data,T,train.leakagecorr);
    end
    % Hilbert envelope
    if train.onpower
        data = rawsignal2power(data,T);
    end
    % Leading Phase Eigenvectors
    if train.leida
        data = leadingPhEigenvector(data,T);
    end
    % pre-embedded  PCA transform
    if length(train.pca_spatial) > 1 || train.pca_spatial > 0
        if isfield(train,'As')
            data.X = bsxfun(@minus,data.X,mean(data.X));
            data.X = data.X * train.As;
        else
            [train.As,data.X] = highdim_pca(data.X,T,train.pca_spatial);
        end
    end
    % Embedding
    if length(train.embeddedlags) > 1
        [data,T] = embeddata(data,T,train.embeddedlags);
    end
    % PCA transform
    if length(train.pca) > 1 || train.pca > 0
        if isfield(train,'A')
            data.X = bsxfun(@minus,data.X,mean(data.X));
            data.X = data.X * train.A;
        else
            [train.A,data.X] = highdim_pca(data.X,T,train.pca,0,0,0,train.varimax);
        end
        % Standardise principal components and control for ackward trials
        data = standardisedata(data,T,train.standardise_pc);
        train.ndim = size(train.A,2);
        train.S = ones(train.ndim);
        orders = formorders(train.order,train.orderoffset,train.timelag,train.exptimelag);
        train.Sind = formindexes(orders,train.S) == 1;
    end
    % Downsampling
    if train.downsample > 0
        [data,T] = downsampledata(data,T,train.downsample,train.Fs);
    end
end

if type==0
   if nargout == 1, Path = hsinference(data,T,hmm,residuals);
   else, [Path,~,Xi] = hsinference(data,T,hmm,residuals);
   end
   return
end

if isstruct(data)
    if isfield(data,'C') && ~all(isnan(data.C(:)))
        warning('Pre-specified state time courses will be ignored for Viterbi path calculation')
    end
    data = data.X;
end

Xi = [];

K = length(hmm.state);

if isempty(residuals) && ~do_HMM_pca
    if ~isfield(hmm.train,'Sind')
        orders = formorders(hmm.train.order,hmm.train.orderoffset,hmm.train.timelag,hmm.train.exptimelag);
        hmm.train.Sind = formindexes(orders,hmm.train.S) == 1;
    end
    residuals =  getresiduals(data,T,hmm.train.S,hmm.train.maxorder,hmm.train.order,...
        hmm.train.orderoffset,hmm.train.timelag,hmm.train.exptimelag,hmm.train.zeromean);
end

if ~isfield(hmm,'P')
    hmm = hmmhsinit(hmm);
end

order = hmm.train.maxorder;

if hmm.train.useParallel==1 && N>1

    % to duplicate this code is really ugly but there doesn't seem to be
    % any other way - more Matlab's fault than mine

    Path = cell(N,1);

    parfor n = 1:N

        %if Q > 1
        %    i = grouping(n);
        %    P = hmm.P(:,:,i); Pi = hmm.Pi(:,i)';
        %else
        %    P = hmm.P; Pi = hmm.Pi;
        %end
        % This causes error with the Parallel toolbox
        P = hmm.P; Pi = hmm.Pi;

        q_star = ones(T(n)-order,1);

        scale=zeros(T(n),1);
        alpha=zeros(T(n),K);
        beta=zeros(T(n),K);

        % Initialise Viterbi bits
        delta=zeros(T(n),K);
        psi=zeros(T(n),K);

        if n==1, t0 = 0; s0 = 0;
        else t0 = sum(T(1:n-1)); s0 = t0 - order*(n-1);
        end

        if do_HMM_pca
            B = obslike(data(t0+1:t0+T(n),:),hmm,[]);
        else
            B = obslike(data(t0+1:t0+T(n),:),hmm,residuals(s0+1:s0+T(n)-order,:));
        end
        B(B<realmin) = realmin;

        % Scaling for delta
        dscale=zeros(T(n),1);

        alpha(1+order,:)=Pi(:)'.*B(1+order,:);
        scale(1+order)=sum(alpha(1+order,:));
        alpha(1+order,:)=alpha(1+order,:)/(scale(1+order));

        delta(1+order,:) = alpha(1+order,:);    % Eq. 32(a) Rabiner (1989)
        % Eq. 32(b) Psi already zero
        for i=2+order:T(n)
            alpha(i,:)=(alpha(i-1,:)*P).*B(i,:);
            scale(i)=sum(alpha(i,:));
            if scale(i)<realmin, scale(i) = realmin; end
            alpha(i,:)=alpha(i,:)/(scale(i));

            for k=1:K
                v=delta(i-1,:).*P(:,k)';
                mv=max(v);
                delta(i,k)=mv*B(i,k);  % Eq 33a Rabiner (1989)
                fmv = find(v==mv);
                if length(fmv) > 1
                    % no unique maximum - so pick one at random
                    tmp1=fmv;
                    tmp2=rand(length(tmp1),1);
                    [~,tmp4]=max(tmp2);
                    psi(i,k)=tmp4;
                else
                    psi(i,k)=fmv;  % ARGMAX; Eq 33b Rabiner (1989)
                end
            end

            % SCALING FOR DELTA ????
            dscale(i)=sum(delta(i,:));
            if dscale(i)<realmin, dscale(i) = realmin; end
            delta(i,:)=delta(i,:)/(dscale(i));
        end

        % Get beta values for single state decoding
        beta(T(n),:)=ones(1,K)/scale(T(n));
        for i=T(n)-1:-1:1+order
            beta(i,:)=(beta(i+1,:).*B(i+1,:))*(P')/scale(i);
        end

        xi=zeros(T(n)-1-order,K*K);
        for i=1+order:T(n)-1
            t=P.*( alpha(i,:)' * (beta(i+1,:).*B(i+1,:)));
            xi(i-order,:)=t(:)'/sum(t(:));
        end

        delta=delta(1+order:T(n),:);
        psi=psi(1+order:T(n),:);

        % Backtracking for Viterbi decoding
        id = find(delta(T(n)-order,:)==max(delta(T(n)-order,:)));% Eq 34b Rabiner;
        q_star(T(n)-order) = id(1);
        for i=T(n)-1-order:-1:1
            q_star(i) = psi(i+1,q_star(i+1));
        end

        Path{n} = single(q_star);

    end

    Path = cell2mat(Path);

else

    Path = zeros(sum(T)-length(T)*order,1,'single');
    tacc = 0;

    for n=1:N

        %if Q > 1
        %    i = grouping(n);
        %    P = hmm.P(:,:,i); Pi = hmm.Pi(:,i)';
        %else
        %    P = hmm.P; Pi = hmm.Pi;
        %end
        P = hmm.P; Pi = hmm.Pi;

        q_star = ones(T(n)-order,1);

        alpha=zeros(T(n),K);
        beta=zeros(T(n),K);

        % Initialise Viterbi bits
        delta=zeros(T(n),K);
        psi=zeros(T(n),K);

        if n==1, t0 = 0; s0 = 0;
        else t0 = sum(T(1:n-1)); s0 = t0 - order*(n-1);
        end

        if do_HMM_pca
            B = obslike(data(t0+1:t0+T(n),:),hmm,[]);
        else
            %B = obslike(data(t0+1:t0+T(n),:),hmm,residuals(s0+1:s0+T(n)-order,:));
            if ~isfield(hmm.train,'distribution') || strcmp(hmm.train.distribution,'Gaussian')
                B = obslike(data(t0+1:t0+T(n),:),hmm,residuals(s0+1:s0+T(n)-order,:));
            elseif strcmp(hmm.train.distribution ,'bernoulli')
                B = obslikebernoulli(data(t0+1:t0+T(n),:),hmm);
            elseif strcmp(hmm.train.distribution ,'poisson')
                B = obslikepoisson(data(t0+1:t0+T(n),:),hmm);
            elseif strcmp(hmm.train.distribution ,'logistic')
                B = obslikelogistic([],hmm,residuals(s0+1:s0+T(n)-order,:),XX);
            end
        end
        B(B<realmin) = realmin;

        scale=zeros(T(n),1);
        % Scaling for delta
        dscale=zeros(T(n),1);

        alpha(1+order,:)=Pi(:)'.*B(1+order,:);
        scale(1+order)=sum(alpha(1+order,:));

        alpha(1+order,:)=alpha(1+order,:)/(scale(1+order)+realmin);

        delta(1+order,:) = alpha(1+order,:);    % Eq. 32(a) Rabiner (1989)
        % Eq. 32(b) Psi already zero
        for i=2+order:T(n)
            alpha(i,:)=(alpha(i-1,:)*P).*B(i,:);
            scale(i)=sum(alpha(i,:));
            alpha(i,:)=alpha(i,:)/(scale(i)+realmin);

            for k=1:K
                v=delta(i-1,:).*P(:,k)';
                mv=max(v);
                delta(i,k)=mv*B(i,k);  % Eq 33a Rabiner (1989)
                if length(find(v==mv)) > 1
                    % no unique maximum - so pick one at random
                    tmp1=find(v==mv);
                    tmp2=rand(length(tmp1),1);
                    [~,tmp4]=max(tmp2);
                    psi(i,k)=tmp4;
                else
                    psi(i,k)=find(v==mv);  % ARGMAX; Eq 33b Rabiner (1989)
                end
            end

            % SCALING FOR DELTA ????
            dscale(i)=sum(delta(i,:));
            delta(i,:)=delta(i,:)/(dscale(i)+realmin);
        end

        % Get beta values for single state decoding
        beta(T(n),:)=ones(1,K)/scale(T(n));
        for i=T(n)-1:-1:1+order
            beta(i,:)=(beta(i+1,:).*B(i+1,:))*(P')/scale(i);
        end

        xi=zeros(T(n)-1-order,K*K);
        for i=1+order:T(n)-1
            t=P.*( alpha(i,:)' * (beta(i+1,:).*B(i+1,:)));
            xi(i-order,:)=t(:)'/sum(t(:));
        end

        delta=delta(1+order:T(n),:);
        psi=psi(1+order:T(n),:);

        % Backtracking for Viterbi decoding
        id = find(delta(T(n)-order,:)==max(delta(T(n)-order,:)));% Eq 34b Rabiner;
        q_star(T(n)-order) = id(1);
        for i=T(n)-1-order:-1:1
            q_star(i) = psi(i+1,q_star(i+1));
        end

        Path( (1:(T(n)-order)) + tacc ) = q_star;
        tacc = tacc + T(n)-order;

    end

end

end


function varargout = exp(varargin)
%EXP (overloaded)

switch class(varargin{1})

    case 'double' % What is the numerical value of this argument (needed for displays etc)
        % SHOULD NEVER HAPPEN, THIS SHOULD BE CAUGHT BY BUILT-IN
        error('Overloaded SDPVAR/NORM CALLED WITH DOUBLE. Report error')

    case 'sdpvar' % Overloaded operator for SDPVAR objects. Pass on args and save them.
        if length(varargin{1}) == 1
            varargout{1} = yalmip('addEvalVariable',mfilename,varargin{1});
        else
            y = [];
            for i = 1:length(varargin{1})
                y = [y;yalmip('addEvalVariable',mfilename,extsubsref(varargin{1},i))];
            end
            varargout{1} = y;
        end

    case 'char' % YALMIP sends 'model' when it wants the epigraph or hypograph
        switch varargin{1}
            case 'graph'
                t = varargin{2};
                X = varargin{3};

                % This is different from so called extended operators
                % Just do it!
                F = SetupEvaluationVariable(varargin{:});

                % Now add your own code, such as domain constraints.
                % Exponential does not need any domain constraint.

                % Let YALMIP know about convexity etc
                varargout{1} = F;
                varargout{2} = struct('convexity','convex','monotonicity','increasing','definiteness','positive');
                varargout{3} = X;

            case 'milp'
                    varargout{1} = [];
                    varargout{2} = [];
                    varargout{3} = [];
            otherwise
                error('SDPVAR/EXP called with CHAR argument?');
        end
    otherwise
        error('SDPVAR/EXP called with CHAR argument?');
end


%% Filename and Format string
% This code loads in the contents of the data file with textscan a block at a
% time and converts the cellarray from textscan into a double array

filename='waferdata.csv';
FormatString=['%*f%*f%*f%*f' repmat('%u8',1,9) repmat('%*f',1,7)];

%% Parameters
Headers=3;
NumLines=1e6; % Total number of lines to read
BlockLines=10000; % Size of block
NumBlocks=NumLines/BlockLines; % Number of blocks

%% Open file
fid = fopen(filename); % Open file

%% Preallocate data
data=zeros(NumLines,9,'uint8'); % Pre-allocate space for data

%% Get Headers
cellchunk=textscan(fid,'%s',3,'delimiter','\n'); % Get first 3 lines

%% Read in blocks
for Block=1:NumBlocks
    data((Block-1)*BlockLines+(1:BlockLines),:) = ...
	cell2mat(textscan(fid,FormatString,BlockLines,'delimiter',','));
    disp(['Block ' num2str(Block) ]); % Display current block to show progress
end

%% Close file
fclose(fid);


function [m, pot] = marginal_family(engine, i)
% MARGINAL_FAMILY Compute the marginal on i's family (global_inf_engine)
% [m, pot] = marginal_family(engine, i)
%

bnet = bnet_from_engine(engine);
[m, pot] = marginal_nodes(engine, family(bnet.dag, i));


 function ob = fatrix2_setup_dim(ob)
%function ob = fatrix2_setup_dim(ob)
%|
%| Set up idim and odim etc.
%|
%| Copyright 2010-12-17, Jeff Fessler, University of Michigan

persistent warned
if ~isvar('warned') || isempty(warned)
	warned = false;
end

arg = ob.arg; % this shall not be modified

if isempty(ob.imask) && isfield(arg, 'mask')
	ob.imask = arg.mask;
end

% input mask (rhs)

if isempty(ob.idim)
	if ~isempty(ob.imask)
		ob.idim = size(ob.imask);
	elseif isfield(arg, 'idim') && ~isempty(arg.idim)
		ob.idim = arg.idim;
	else
		fail('idim or imask or arg.idim required')
	end
end

if ~isempty(ob.imask)
	if ~islogical(ob.imask)
		fail('imask must be logical')
	end
	fatrix2_check_dim1(ob.imask, ob.idim)
	if isfield(arg, 'mask')
		if ~isequal(ob.imask, arg.mask)
			warn('mask inconsistent!?')
		end
	end
end

% output mask (lhs)

if isempty(ob.odim)
	if ~isempty(ob.omask)
		ob.odim = size(ob.omask);
	elseif isfield(arg, 'odim') && ~isempty(arg.odim)
		ob.odim = arg.odim;
	else
		fail('odim or omask or arg.odim required')
	end
end

if ~isempty(ob.omask)
	if ~islogical(ob.omask)
		fail('omask must be logical')
	end
	fatrix2_check_dim1(ob.omask, ob.odim)
end

ob.size = [prod(ob.odim) prod(ob.idim)];
if ~isempty(ob.imask)
	ob.size(2) = sum(ob.imask(:));
end
if ~isempty(ob.omask)
	ob.size(1) = sum(ob.omask(:));
end

% 1D input fully masked with >1D output is ambiguous
if ~ob.accept1d
	if numel(ob.idim) == 1 && numel(ob.odim) > 1 && ob.size(2) == prod(ob.idim)
%		fail 'use 1d odim for 1d idim case with full mask'
		warn '1d idim with full imask and >1d odim may not work'
	end

	if numel(ob.odim) == 1 && numel(ob.idim) > 1 && ob.size(1) == prod(ob.odim)
        if ~warned
            warn '1d odim full omask, >1d idim: transpose may not work'
            warned = true;
        end
	end
end


function segmentation = convert_segmentationstyle(segmentation, fn, dim, style)

% CONVERT_SEGMENTATIONSTYLE is a helper function for converting between probabilistic
% and indexed representations. It is used by ft_datatype_segmentation and
% ft_datatype_parcellation.
%
% See also FIXSEGMENTATION, DETERMINE_SEGMENTATIONSTYLE

switch style
  case 'indexed'

    % convert the probabilistic representation to an indexed representation
    threshold = 0.5;
    seg       = zeros(dim);
    seglabel  = cell(1,numel(fn));

    for i=1:length(fn)
      tmp = segmentation.(fn{i})>threshold;
      if any(seg(tmp(:)))
        ft_error('overlapping tissue probability maps cannot be converted to an indexed representation');
        % FIXME in principle it is possible to represent two tissue types at one voxel
      else
        seg(tmp) = i;
        seglabel{i} = fn{i};
      end
    end
    segmentation          = rmfield(segmentation, fn);
    segmentation.seg      = seg;
    segmentation.seglabel = seglabel;

  case 'probabilistic'
    % convert the indexed representation to a probabilistic one
    for i=1:length(fn)
      fprintf('converting %s\n', fn{i});
      seg      = segmentation.(fn{i});
      seglabel = segmentation.([fn{i} 'label']);
      for j=i:length(seglabel)
        fprintf('creating probabilistic representation for %s\n', seglabel{j});
        tmp.(fixname(seglabel{j})) = (seg==j);  % avoid overwriting the existing segmentation
      end % for j
      segmentation = rmfield(segmentation,  fn{i}         );
      segmentation = rmfield(segmentation, [fn{i} 'label']);
      for j=i:length(seglabel)
        segmentation.(fixname(seglabel{j})) = tmp.(fixname(seglabel{j})); % avoid overwriting the existing segmentation
      end
    end % for i

  otherwise
    ft_error('unsupported style "%s"', style);
end


function  plot_shcks_dcmp_(pplotvar,ex_names_,leg,input,BVAR,options)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Filippo Ferroni, 6/1/2015
% Revised, 2/15/2017
% Revised, 3/21/2018

% pplotvar:     cell with the variable names for the decomp
% ex_names_:    shocks grouping
% leg:          shocks grouping names
% input:        outpu tof 'histdecomposition.m'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

nshocks = BVAR.N;

for v = 1 : nshocks
    eval(['namesshock{' num2str(v) '} =  ''Shck ' num2str(v) ''';'])
end

TT   = 1:1:size(input,1);
Tlim   = [TT(1) TT(end)];

[~,positions] = ismember(pplotvar,BVAR.varnames);
% Setting the time
% i1     = length(TT);
% nplots = [1 1];
% T      = TT;

pplotvarname   = pplotvar;
dcmp_group_yes = 0;
dmcp_type      = 'stacked';
tags           = [];
colors_decomp_yes = 0;
savefig_yes = 0;
% initial_state_dcmp = 0;
% addplot_=0;
% addplot0_=0;

if nargin > 5
    if isfield(options,'time') ==1
        TT = options.time;
        if isfield(options,'Tlim') ==1;
            Tlim=options.Tlim;
            if Tlim(1) < TT(1)
                warning('You have set a intitial date that starts earlier than the first obs');
                warning('I change it with the frist obs');
                Tlim(1) = TT(1);
            end
            if Tlim(2) > TT(end)
                warning('You have set a final date that exceeds the forecast horizon');
                warning('I change it with the endo of forecast');
                Tlim(2) = TT(end);
            end
        end

    end

    if isfield(options,'tags') ==1
        tags = options.tags;
    end
    if isfield(options,'dcmp_grouped') ==1 && options.dcmp_grouped ==1
        dcmp_group_yes = 1;
        dmcp_type      = 'grouped';
    end
    if isfield(options,'plotvarnames') ==1
        pplotvarname = options.plotvarnames;
        if length(pplotvarname)~=length(pplotvar)
            error('The number of plot titles (pplotvarname) and of plot variables needs to coincide')
        end
    end
    if isfield(options,'colors_decomp') ==1 && options.colors_decomp==1
        colors_decomp_yes =1;
    end
    if isfield(options,'colors_decomp') ==1 && options.colors_decomp==2
        colors_decomp_yes =2;
    end
    if isfield(options,'colors_decomp') ==1 && options.colors_decomp==3
        colors_decomp_yes =3;
    end
    if isfield(options,'saveas_dir') ==1
        savefig_yes = 1;
        %   setting the folder where to save the figure
        fnam_dir = options.saveas_dir;
        if exist(fnam_dir,'dir') == 0
            mkdir(fnam_dir)
        end
    end

    %     if isfield(options,'addplots_yes') ==1  && options.addplots_yes==1,
    %         tags = [ tags '_memo'];
    %         if isfield(input,'frcsts') ==1
    %             addplot_=1;
    %             frcsts = input.frcsts.states.Mean(:,positions);
    %             s_s = input.frcsts.states.steady(positions);
    %         end
    %         if isfield(input,'frcsts0') ==1
    %             addplot0_=1;
    %             frcsts0 = input.frcsts0.states.Mean(:,positions);
    %
    %         end
    %     end
end

deco  = input;

% setting the names of the figure to save
fnam_suffix = [tags '_shcks_dcmp'];

ngroups0 = size(ex_names_,1);
% ngroups  = ngroups0+1+no_initial_effect;
if colors_decomp_yes == 0
    func = @(x) colorspace('RGB->Lab',x);
    MAP = distinguishable_colors(ngroups0+1,'w',func);
    % MAP = CreateColorMap(ngroups0+1);
    MAP(end,:) = [0.7 0.7 0.7];
elseif colors_decomp_yes == 1
    MAP = zeros(4,3);
    MAP(end,:) = [0.7 0.7 0.7]; % gray
    MAP(end-1,:) = [0.2 0.5 0.99];      % blue
    MAP(end-2,:) =  [1 1 0];        % yellow
    MAP(end-3,:) = [0.8 0 0.8] ;            % purple
    %     MAP(end-4,:) = [0 0.7 0];          % green
    %     MAP(end-5,:) = [1 0 0];             % red
    %     MAP(end-6,:) = [0 0 1];             % blue
    %     MAP(end-7,:) = [.5 .5 0];           % light green
elseif colors_decomp_yes == 2
    MAP = zeros(10,3);
    MAP(end,:) = [0.7 0.7 0.7]; % gray
    MAP(end-1,:) = [0.2 0.5 0.99];       % blue
    MAP(end-2,:) = [0.8 0 0.8] ;        % purple
    MAP(end-3,:) = [1 1 0] ;            % yellow
    MAP(end-4,:) = [0 0.7 0];          % green
    MAP(end-5,:) = [1 0 0];             % red
    MAP(end-6,:) = [0 0 1];             % blue
    MAP(end-7,:) = [.5 .5 0];          % light green
    MAP(end-8,:) = [0 0.25 0]; %brown
    MAP(end-9,:) = [0 0.9 0.9]; % violet
elseif colors_decomp_yes == 3
    MAP = zeros(11,3);
    MAP(end,:) = [0.7 0.7 0.7]; % gray
    MAP(end-1,:) = [0.2 0.5 0.99];       % blue
    MAP(end-2,:) = [0.8 0 0.8] ;        % purple
    MAP(end-3,:) = [1 1 0] ;            % yellow
    MAP(end-4,:) = [0 0.7 0];          % green
    MAP(end-5,:) = [1 0 0];             % red
    MAP(end-6,:) = [0 0 1];             % blue
    MAP(end-7,:) = [.5 .5 0];          % light green
    MAP(end-8,:) = [0 0.25 0]; %brown
    MAP(end-9,:) = [0 0.9 0.9]; % violet
    MAP(end-10,:) = [0.5 0.1 0.1]; % violet
end

st = find(Tlim(1)==TT);
en = find(Tlim(2)==TT);
if savefig_yes == 1
    fidTxt = fopen([fnam_dir '\legenda_' fnam_suffix '_plots.txt'],'w');
    fprintf(fidTxt,['LEGENDA ' tags ' SHOCK DECOMPOSITION PLOTS\n']);
    fprintf(fidTxt,['\n']);
end

for j = 1 : size(pplotvar,2)
    clear sdec sdec_tot,
    indx = positions(j);
    sdec0 = squeeze(deco(:,indx,:));
    for i=1:ngroups0
        clear index,
        for ii=1:size(ex_names_{i},2)
            indbuf = strmatch(ex_names_{i}{ii},namesshock,'exact');
            if ~isempty(indbuf)
                index(ii) = indbuf;
            elseif ~isempty(ex_names_{i}{ii})
                error(['Shock name ',ex_names_{i}{ii}, ' not found.' ]);
            end
        end
        sdec(:,i)=sum(sdec0(:,index),2);
        sdec0(:,index)=0;
    end

    h= figure('Name',['Shocks Decomposition for ' pplotvarname{j}]);
    sdec_tot=[sdec,  sum(sdec0,2)];
    if dcmp_group_yes == 0
        ind_pos = (sdec_tot>0);
        ind_neg = (sdec_tot<0);
        temp_neg  = cumsum(sdec_tot.*ind_neg ,2).*ind_neg;
        temp_pos  = cumsum(sdec_tot.*ind_pos ,2).*ind_pos;
        temp      = temp_neg + temp_pos;
        for kk = size(temp,2) : -1 : 1
            hold on
            bbar = bar(TT(st:en),temp(st:en,kk),dmcp_type,'EdgeColor',[0 0 0]);
            set(bbar, 'FaceColor', MAP(kk,:))
            shading faceted; hold on;
        end
        leg0 = leg(end:-1:1);

    else
        temp      = sdec_tot;
        bbar = bar(TT(st:en),temp(st:en,:),dmcp_type,'EdgeColor',[0 0 0]); colormap(MAP);
        %         hleg=legend(leg(1:1:end),'interpreter','none','location','Best');
        %         shading faceted;
        %
    end
    %     set(hleg,'position',[0.5 0.15 0.4 0.2],'units','normalized')
    hold on
    axis tight
    fillips = sum(sdec_tot,2);
    hold on, h1=plot(TT(st:en),fillips(st:en),'k-d');
    set(h1,'MarkerFaceColor', 'k')

    %     if addplot_ ==1
    %         hold on, h1=plot(TT(st:en),frcsts(st:en,j),'k-*','LineWidth',2);
    %         hold on, h1=plot(TT(st:en),s_s(j)*ones(length(TT(st:en)),1),'k-.','LineWidth',2);
    %         leg0{end+1} = 'Current Forecast';
    %         leg0{end+1} = 'steady state';
    %     end
    %
    %     if addplot0_ ==1
    %         hold on, h1=plot(TT(st:en),frcsts0(st:en,j),'r-o','LineWidth',2);
    %         leg0{end+1} = 'Previous Forecast';
    %     end

    set(gcf,'position' ,[50 50 800 650])
    hleg=legend(leg0,'interpreter','none','location','Best');
    shading faceted;

    %     define_.timestart + (define_.nobs-1)/4
    %plot the last in-+sample obs
    %     hold on; plot([TT(define_.nobs) TT(define_.nobs)],[low up],'color',[1 0 0],'LineWidth',2)
    %     h=vline(define_.timestart + (define_.nobs-1)/4, 'k', 'Last Obs');
    %     set(h,'Linewidth',2)

    title(pplotvarname{j})
    %     set(gca,'Xtick',TT(st:6:en))
    %     tmp_str= sample2date(TT(st:6:en));
    set(gca,'Xtick',TT(st:6:en))
    %     tmp_str= sample2date(TT(st:2:en));
    %     set(gca,'Xticklabel',tmp_str)
    %     tmp_str= sample2date(TT);
    %     STR_RECAP = [ 'model_' fnam_suffix '_' tmp_str{st} '_'  tmp_str{end} '_' int2str(j)];

    if savefig_yes == 1
        STR_RECAP = [ fnam_dir '\svar_' fnam_suffix '_' int2str(j)];

        saveas(gcf,STR_RECAP,'fig');
        if strcmp(version('-release'),'2022b') == 0
            saveas(gcf,STR_RECAP,'pdf');
        end
        fprintf(fidTxt,['The figure sdcmp_' fnam_suffix '_' int2str(j) ' contains the following variable:\n' ]);
        tmp = strrep(char(pplotvarname{j} ),'\',' ');
        fprintf(fidTxt,[tmp '; ']);

        fprintf(fidTxt,['\n' ]);
        fprintf(fidTxt,['\n']);
        %close all
    end

end

if savefig_yes == 1
    fclose(fidTxt);
end

end


function rd = gsp_resistance_distance(G,param)
%GSP_RESISTANCE_DISTANCE see gsp_resistance_distances

if nargin<2
    param = struct;
end

rd = gsp_resistance_distances(G,param);

end


classdef InverterFactory < handle

    properties (Access = private)
        tensor
        inverter
    end

    methods (Access = public)

        function inverter = create(obj,tensor)
            obj.init(tensor);
            obj.createInverter()
            inverter = obj.getInverter();
        end
    end

    methods (Access = private)

        function init(obj,tensor)
            obj.tensor = tensor;
        end

        function createInverter(obj)
            if obj.isVoigt()
                obj.inverter = VoigtTensorInverter(obj.tensor);
            elseif obj.isFourthOrderTensor() && ~obj.isSymmetric()
                obj.inverter = TensorInverter(obj.tensor);
            elseif obj.isFourthOrderTensor() && obj.isSymmetric()
                obj.inverter = SymmetricTensorInverter(obj.tensor);
            else
             error('Not admitted object to be Inverted')
            end
        end

        function inv = getInverter(obj)
            inv = obj.inverter;
        end

        function isVoigt = isVoigt(obj)
            isVoigt   = strcmp(obj.tensor.getRepresentation(),'voigt');
        end

        function itIs = isFourthOrderTensor(obj)
            itIs   = strcmp(obj.tensor.getOrder(),'fourth');
        end

        function isSymmetric = isSymmetric(obj)
            symmetrizer = FourthOrderSymmetrizer();
            isSymmetric = symmetrizer.isSymmetric(obj.tensor.getValue());
        end

    end

end


function y = var( varargin )

%STD    Internal cvx version.

y = square_pos( std( varargin{:} ) );

% Copyright 2005-2016 CVX Research, Inc.
% See the file LICENSE.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.


function output = callqsopt(interfacedata)

% Retrieve needed data
options = interfacedata.options;
F_struc = interfacedata.F_struc;
c       = interfacedata.c;
K       = interfacedata.K;
ub      = interfacedata.ub;
lb      = interfacedata.lb;

n = length(c);
% Bounded variables converted to constraints
if ~isempty(ub)
    % TODO : MAKE SURE SIZES ARE CORRECT
    lb(lb==-inf) = -1e30;
    ub(ub==inf) = 1e30;
else
    lb=repmat(-1e30,n,1);    % just for sure
    ub=repmat(1e30,n,1);
end

if options.showprogress;showprogress('Calling QSOPT',options.showprogress);end

options.qsopt.verbose = options.verbose;

% Call mex-interface
if options.savedebug
    save qsoptdebug
end
solvertime = tic;
[x,lambda,STATUS] = qsopt(c,-F_struc(1+K.f:end,2:end),F_struc(1+K.f:end,1),-F_struc(1:K.f,2:end),F_struc(1:K.f,1),lb,ub,options.qsopt);
solvertime = toc(solvertime);
problem = 0;

if options.saveduals
    D_struc = -lambda;
else
    D_struc = [];
end

% Check, currently not exhaustive...
switch STATUS
    case 1
        problem = 0;
    case 2
        problem = 1;
    case 3
        problem = 2;
    case 4
        problem = 3;
    case {5,6}
        problem = 4;
    otherwise
        problem = -1;
end
infostr = yalmiperror(problem,'QSOPT');

% Save all data sent to solver?
if options.savesolverinput
	solverinput = [];
else
	solverinput = [];
end

% Save all data from the solver?
if options.savesolveroutput
	solveroutput = [];
else
	solveroutput = [];
end

% Standard interface
Primal      = solution.x(:);
Dual        = solution.D_struc;
problem     = solution.problem;
infostr     = yalmiperror(solution.problem,interfacedata.solver.tag);
if ~options.savesolverinput
    solverinput = [];
else
    solverinput = model;
end
if ~options.savesolveroutput
    solveroutput = [];
else
    solveroutput = solveroutput;
end
% Standard interface
Primal      = solution.x(:);
Dual        = solution.D_struc;
problem     = solution.problem;
infostr     = yalmiperror(solution.problem,interfacedata.solver.tag);
if ~options.savesolverinput
    solverinput = [];
else
    solverinput = model;
end
if ~options.savesolveroutput
    solveroutput = [];
else
    solveroutput = solveroutput;
end
% Standard interface
output = createOutputStructure(Primal,Dual,[],problem,infostr,solverinput,solveroutput,solvertime);


% [INPUT]
% ds = A structure representing the dataset.
% sn = A string representing the serial number of the result file.
% temp = A string representing the full path to the Excel spreadsheet used as template for the result file.
% out = A string representing the full path to the Excel spreadsheet to which the results are written, eventually replacing the previous ones.
% bw = An integer [21,252] representing the dimension of each rolling window (optional, default=252).
% sst = A float (0.0,0.1] representing the statistical significance threshold for the linear Granger-causality test (optional, default=0.05).
% rp = A boolean indicating whether to use robust p-values for the linear Granger-causality test (optional, default=false).
% k = A float (0.00,0.20] representing the Granger-causality threshold for non-causal relationships (optional, default=0.06).
% analyze = A boolean that indicates whether to analyse the results and display plots (optional, default=false).
%
% [OUTPUT]
% result = A structure representing the original dataset inclusive of intermediate and final calculations.
% stopped = A boolean that indicates whether the process has been stopped through user input.

function [result,stopped] = run_connectedness(varargin)

    persistent ip;

    if (isempty(ip))
        ip = inputParser();
        ip.addRequired('ds',@(x)validateattributes(x,{'struct'},{'nonempty'}));
        ip.addRequired('sn',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addRequired('temp',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addRequired('out',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addOptional('bw',252,@(x)validateattributes(x,{'double'},{'real' 'finite' 'integer' '>=' 21 '<=' 252 'scalar'}));
        ip.addOptional('sst',0.05,@(x)validateattributes(x,{'double'},{'real' 'finite' '>' 0 '<=' 0.1 'scalar'}));
        ip.addOptional('rp',false,@(x)validateattributes(x,{'logical'},{'scalar'}));
        ip.addOptional('k',0.06,@(x)validateattributes(x,{'double'},{'real' 'finite' '>' 0 '<=' 0.20 'scalar'}));
        ip.addOptional('analyze',false,@(x)validateattributes(x,{'logical'},{'scalar'}));
    end

    ip.parse(varargin{:});

    ipr = ip.Results;
    ds = validate_dataset(ipr.ds,'Connectedness');
    sn = ipr.sn;
    temp = validate_template(ipr.temp);
    out = validate_output(ipr.out);
    bw = ipr.bw;
    sst = ipr.sst;
    rp = ipr.rp;
    k = ipr.k;
    analyze = ipr.analyze;

    nargoutchk(1,2);

    [result,stopped] = run_connectedness_internal(ds,sn,temp,out,bw,sst,rp,k,analyze);

end

function [result,stopped] = run_connectedness_internal(ds,sn,temp,out,bw,sst,rp,k,analyze)

    result = [];
    stopped = false;
    e = [];

    ds = initialize(ds,sn,bw,sst,rp,k);
    t = ds.T;

    bar = waitbar(0,'Initializing connectedness measures...','CreateCancelBtn',@(src,event)setappdata(gcbf(),'Stop',true));
    setappdata(bar,'Stop',false);
    cleanup = onCleanup(@()delete(bar));

    pause(1);
    waitbar(0,bar,'Calculating connectedness measures...');
    pause(1);

    try

        windows = extract_rolling_windows(ds.Returns,ds.BW);

        futures(1:t) = parallel.FevalFuture;
        futures_max = 0;
        futures_results = cell(t,1);

        for i = 1:t
            futures(i) = parfeval(@main_loop,1,windows{i},ds.SST,ds.RP,ds.GroupDelimiters);
        end

        for i = 1:t
            if (getappdata(bar,'Stop'))
                stopped = true;
                break;
            end

            [future_index,value] = fetchNext(futures);
            futures_results{future_index} = value;

            futures_max = max([future_index futures_max]);
            waitbar((futures_max - 1) / t,bar);

            if (getappdata(bar,'Stop'))
                stopped = true;
                break;
            end
        end

    catch e
    end

    try
        cancel(futures);
    catch
    end

    if (~isempty(e))
        delete(bar);
        rethrow(e);
    end

    if (stopped)
        delete(bar);
        return;
    end

    pause(1);
    waitbar(1,bar,'Finalizing connectedness measures...');
    pause(1);

    try
        ds = finalize(ds,futures_results);
    catch e
        delete(bar);
        rethrow(e);
    end

    pause(1);
    waitbar(1,bar,'Writing connectedness measures...');
    pause(1);

    try
        write_results(ds,temp,out);
        delete(bar);
    catch e
        delete(bar);
        rethrow(e);
    end

    if (analyze)
        analyse_result(ds);
    end

    result = ds;

end

%% PROCESS

function ds = initialize(ds,sn,bw,sst,rp,k)

    n = ds.N;
    t = ds.T;

    ds.Result = 'Connectedness';
    ds.ResultDate = now(); %#ok<TNOW1>
    ds.ResultAnalysis = @(ds)analyse_result(ds);
    ds.ResultSerial = sn;

    ds.BW = bw;
    ds.K = k;
    ds.RP = rp;
    ds.SST = sst;

    if (ds.RP)
        all_label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ', R)'];
    else
        all_label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ')'];
    end

    ds.LabelsCentralities = {'Betweenness Centrality' 'Closeness Centrality' 'Degree Centrality' 'Eigenvector Centrality' 'Katz Centrality' 'Clustering Coefficient'};

    ds.LabelsIndicatorsSimple = {'DCI' 'CIO' 'CIOO'};
    ds.LabelsIndicators = {['DCI' all_label] ['CIO' all_label] ['CIOO' all_label]};

    ds.LabelsSheetsSimple = {'Indicators' 'Average Adjacency Matrix' 'Average Centrality Measures'};
    ds.LabelsSheets = {['Indicators' all_label] 'Average Adjacency Matrix' 'Average Centrality Measures'};

    ds.AdjacencyMatrices = cell(t,1);
    ds.BetweennessCentralities = NaN(t,n);
    ds.ClosenessCentralities = NaN(t,n);
    ds.DegreeCentralities = NaN(t,n);
    ds.EigenvectorCentralities = NaN(t,n);
    ds.KatzCentralities = NaN(t,n);
    ds.ClusteringCoefficients = NaN(t,n);
    ds.Degrees = NaN(t,n);
    ds.DegreesIn = NaN(t,n);
    ds.DegreesOut = NaN(t,n);

    ds.Indicators = NaN(t,numel(ds.LabelsIndicators));

    ds.AverageAdjacencyMatrix = NaN(n);
    ds.AverageBetweennessCentralities = NaN(1,n);
    ds.AverageClosenessCentralities = NaN(1,n);
    ds.AverageDegreeCentralities = NaN(1,n);
    ds.AverageEigenvectorCentralities = NaN(1,n);
    ds.AverageKatzCentralities = NaN(1,n);
    ds.AverageClusteringCoefficients = NaN(1,n);
    ds.AverageDegreesIn = NaN(1,n);
    ds.AverageDegreesOut = NaN(1,n);
    ds.AverageDegrees = NaN(1,n);

    if (ds.Groups == 0)
        ds.ComparisonReferences = {'Indicators' 1:2 strcat({'CO-'},ds.LabelsIndicatorsSimple)};
    else
        ds.ComparisonReferences = {'Indicators' 1:3 strcat({'CO-'},ds.LabelsIndicatorsSimple)};
    end

end

function window_results = main_loop(r,sst,rp,gd)

    window_results = struct();

    am = causal_adjacency(r,sst,rp);
    window_results.AdjacencyMatrix = am;

    [dci,cio,cioo] = connectedness_metrics(am,gd);
    window_results.DCI = dci;
    window_results.ConnectionsInOut = cio;
    window_results.ConnectionsInOutOther = cioo;

    [bc,cc,dc,ec,kc,clc,deg,deg_in,deg_out] = network_centralities(am);
    window_results.BetweennessCentralities = bc;
    window_results.ClosenessCentralities = cc;
    window_results.DegreeCentralities = dc;
    window_results.EigenvectorCentralities = ec;
    window_results.KatzCentralities = kc;
    window_results.ClusteringCoefficients = clc;
    window_results.Degrees = deg;
    window_results.DegreesIn = deg_in;
    window_results.DegreesOut = deg_out;

end

function ds = finalize(ds,results)

    t = ds.T;

    for i = 1:t
        result = results{i};

        ds.AdjacencyMatrices{i} = result.AdjacencyMatrix;
        ds.BetweennessCentralities(i,:) = result.BetweennessCentralities;
        ds.ClosenessCentralities(i,:) = result.ClosenessCentralities;
        ds.DegreeCentralities(i,:) = result.DegreeCentralities;
        ds.EigenvectorCentralities(i,:) = result.EigenvectorCentralities;
        ds.KatzCentralities(i,:) = result.KatzCentralities;
        ds.ClusteringCoefficients(i,:) = result.ClusteringCoefficients;
        ds.Degrees(i,:) = result.Degrees;
        ds.DegreesIn(i,:) = result.DegreesIn;
        ds.DegreesOut(i,:) = result.DegreesOut;

        ds.Indicators(i,:) = [result.DCI result.ConnectionsInOut result.ConnectionsInOutOther];
    end

    am = sum(cat(3,ds.AdjacencyMatrices{:}),3) ./ numel(ds.AdjacencyMatrices);
    am_threshold = mean(mean(am));
    am(am < am_threshold) = 0;
    am(am >= am_threshold) = 1;
    ds.AverageAdjacencyMatrix = am;

    [bc,cc,dc,ec,kc,clc,deg,deg_in,deg_out] = network_centralities(am);
    ds.AverageBetweennessCentralities = bc;
    ds.AverageClosenessCentralities = cc;
    ds.AverageDegreeCentralities = dc;
    ds.AverageEigenvectorCentralities = ec;
    ds.AverageKatzCentralities = kc;
    ds.AverageClusteringCoefficients = clc;
    ds.AverageDegrees = deg;
    ds.AverageDegreesIn = deg_in;
    ds.AverageDegreesOut = deg_out;

end

function write_results(ds,temp,out)

    [out_path,~,~] = fileparts(out);

    try
        if (exist(out_path,'dir') ~= 7)
            mkdir(out_path);
        end

        if (exist(out,'file') == 2)
            delete(out);
        end
    catch
        error('A system I/O error occurred while writing the results.');
    end

    copy_result = copyfile(temp,out,'f');

    if (copy_result == 0)
        error('The output file could not be created from the template file.');
    end

    firm_names = ds.FirmNames';

    vars = [ds.DatesStr num2cell(ds.Indicators)];
    labels = [{'Date'} ds.LabelsIndicatorsSimple];
    tab = cell2table(vars,'VariableNames',labels);
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{1},'WriteRowNames',true);

    vars = [firm_names num2cell(ds.AverageAdjacencyMatrix)];
    labels = {'Firms' ds.FirmNames{:,:}};
    tab = cell2table(vars,'VariableNames',labels);
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{2},'WriteRowNames',true);

    vars = [firm_names num2cell(ds.AverageBetweennessCentralities') num2cell(ds.AverageClosenessCentralities') num2cell(ds.AverageDegreeCentralities') num2cell(ds.AverageEigenvectorCentralities') num2cell(ds.AverageKatzCentralities') num2cell(ds.AverageClusteringCoefficients')];
    labels = [{'Firms'} strrep(ds.LabelsCentralities,' ','')];
    tab = cell2table(vars,'VariableNames',labels);
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{3},'WriteRowNames',true);

    worksheets_batch(out,ds.LabelsSheetsSimple,ds.LabelsSheets);

end

%% PLOTTING

function analyse_result(ds)

    safe_plot(@(id)plot_indicators(ds,id));
    safe_plot(@(id)plot_network(ds,id));
    safe_plot(@(id)plot_adjacency_matrix(ds,id));
    safe_plot(@(id)plot_centralities(ds,id));

end

function plot_indicators(ds,id)

    dci = smooth_data(ds.Indicators(:,1));
    cio = smooth_data(ds.Indicators(:,2));
    cioo = smooth_data(ds.Indicators(:,3));

    connections_max = max(max([cio cioo])) * 1.1;

    threshold_indices = dci >= ds.K;
    threshold = NaN(ds.T,1);
    threshold(threshold_indices) = connections_max;

    if (ds.RP)
        label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ', R)'];
    else
        label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ')'];
    end

    f = figure('Name','Connectedness Measures > Indicators','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub_1 = subplot(2,1,1);
    p1 = plot(sub_1,ds.DatesNum,dci);
    hold on;
        p2 = plot(sub_1,ds.DatesNum,repmat(ds.K,[ds.T 1]),'Color',[1.000 0.400 0.400]);
    hold off;
    set(sub_1,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45);
    set(sub_1,'XGrid','on','YGrid','on');
    legend(sub_1,[p1 p2],'Indicator','Threshold','Location','eastoutside');
    title(sub_1,['DCI' label]);

    sub_2 = subplot(2,1,2);
    a1 = area(sub_2,ds.DatesNum,threshold,'EdgeColor','none','FaceColor',[1.000 0.400 0.400]);
    hold on;
        a2 = area(sub_2,ds.DatesNum,cio,'EdgeColor','none','FaceColor','b');
        if (ds.Groups == 0)
            a3 = area(sub_2,ds.DatesNum,NaN(ds.T,1),'EdgeColor','none','FaceColor',[0.678 0.922 1]);
        else
            a3 = area(sub_2,ds.DatesNum,cioo,'EdgeColor','none','FaceColor',[0.678 0.922 1]);
        end
    hold off;
    set(sub_2,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45,'YLim',[0 connections_max]);
    legend(sub_2,[a2 a3 a1],'CIO','CIOO','Threshold Exceeded','Location','eastoutside');
    title(sub_2,['Connections' label]);

    if (ds.MonthlyTicks)
        date_ticks([sub_1 sub_2],'x','mm/yyyy','KeepLimits','KeepTicks');
    else
        date_ticks([sub_1 sub_2],'x','yyyy','KeepLimits');
    end

    sub_1_position = get(sub_1,'Position');
    sub_2_position = get(sub_2,'Position');
    set(sub_1,'Position',[sub_2_position(1) sub_1_position(2) sub_2_position(3) sub_2_position(4)]);

    figure_title(f,'Indicators');

    maximize_figure(f);

end

function plot_network(ds,id)

    if (ds.Groups == 0)
        group_colors = repmat(lines(1),ds.N,1);
    else
        group_colors = zeros(ds.N,3);
        group_delimiters_len = length(ds.GroupDelimiters);
        group_lines = lines(ds.Groups);

        for i = 1:group_delimiters_len
            group_delimiter = ds.GroupDelimiters(i);

            if (i == 1)
                group_colors(1:group_delimiter,:) = repmat(group_lines(i,:),group_delimiter,1);
            else
                group_delimiter_prev = ds.GroupDelimiters(i-1) + 1;
                group_colors(group_delimiter_prev:group_delimiter,:) = repmat(group_lines(i,:),group_delimiter - group_delimiter_prev + 1,1);
            end

            if (i == group_delimiters_len)
                group_colors(group_delimiter+1:end,:) = repmat(group_lines(i+1,:),ds.N - group_delimiter,1);
            end
        end
    end

    weights = mean(ds.Degrees,1,'omitnan');
    weights = weights ./ mean(weights);
    weights = (weights - min(weights)) ./ (max(weights) - min(weights));
    weights = (weights .* 3.75) + 0.25;

    theta = linspace(0,(2 * pi),(ds.N + 1)).';
    theta(end) = [];
    xy = [cos(theta) sin(theta)];
    [i,j] = find(ds.AverageAdjacencyMatrix);
    [~,order] = sort(max(i,j));
    i = i(order);
    j = j(order);
    x = [xy(i,1) xy(j,1)].';
    y = [xy(i,2) xy(j,2)].';

    f = figure('Name','Connectedness Measures > Network Graph','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub = subplot(100,1,10:100);

    hold on;
        for i = 1:size(x,2)
            index = ismember(xy,[x(1,i) y(1,i)],'rows');
            plot(sub,x(:,i),y(:,i),'Color',group_colors(index,:));
        end
    hold off;

    if (ds.Groups == 0)
        hold on;
            for i = 1:size(xy,1)
                line(xy(i,1),xy(i,2),'Color',group_colors(i,:),'LineStyle','none','Marker','.','MarkerSize',(35 + (15 * weights(i))));
            end
        hold off;
    else
        d_inc = ds.GroupDelimiters + 1;

        lines_ref = NaN(ds.Groups,1);
        lines_off = 1;

        hold on;
            for i = 1:size(xy,1)
                group_color = group_colors(i,:);
                line(xy(i,1),xy(i,2),'Color',group_color,'LineStyle','none','Marker','.','MarkerSize',(35 + (15 * weights(i))));

                if ((i == 1) || any(d_inc == i))
                    lines_ref(lines_off) = line(xy(i,1),xy(i,2),'Color',group_color,'LineStyle','none','Marker','.','MarkerSize',35);
                    lines_off = lines_off + 1;
                end
            end
        hold off;

        legend(sub,lines_ref,ds.GroupShortNames,'Units','normalized','Position',[0.725 0.131 0.040 0.076]);
    end

    axis(sub,[-1 1 -1 1]);
    axis('equal','off');

    labels = text((xy(:,1) .* 1.075), (xy(:,2) .* 1.075),ds.FirmNames,'FontSize',10);
    set(labels,{'Rotation'},num2cell(theta * (180 / pi())));

    figure_title(f,'Network Graph');

    maximize_figure(f);

end

function plot_adjacency_matrix(ds,id)

    am = ds.AverageAdjacencyMatrix;
    am(logical(eye(ds.N))) = 0.5;
    am = padarray(am,[1 1],'post');

    off = ds.N + 0.5;

    f = figure('Name','Connectedness Measures > Average Adjacency Matrix','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    pcolor(am);
    colormap([1 1 1; 0.65 0.65 0.65; 0.749 0.862 0.933]);
    axis image;

    ax = gca();
    set(ax,'TickLength',[0 0]);
    set(ax,'XAxisLocation','top','XTick',1.5:off,'XTickLabels',ds.FirmNames,'XTickLabelRotation',45);
    set(ax,'YDir','reverse','YTick',1.5:off,'YTickLabels',ds.FirmNames,'YTickLabelRotation',45);

    figure_title(f,'Average Adjacency Matrix');

    maximize_figure(f);

end

function plot_centralities(ds,id)

    seq = 1:ds.N;

    [bc,order] = sort(ds.AverageBetweennessCentralities);
    bc_names = ds.FirmNames(order);
    [cc,order] = sort(ds.AverageClosenessCentralities);
    cc_names = ds.FirmNames(order);
    [dc,order] = sort(ds.AverageDegreeCentralities);
    dc_names = ds.FirmNames(order);
    [ec,order] = sort(ds.AverageEigenvectorCentralities);
    ec_names = ds.FirmNames(order);
    [kc,order] = sort(ds.AverageKatzCentralities);
    kc_names = ds.FirmNames(order);
    [clc,order] = sort(ds.AverageClusteringCoefficients);
    clc_names = ds.FirmNames(order);

    f = figure('Name','Connectedness Measures > Average Centrality Measures','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub_1 = subplot(2,3,1);
    bar(sub_1,seq,bc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_1,'XTickLabel',bc_names);
    title(ds.LabelsCentralities{1});

    sub_2 = subplot(2,3,2);
    bar(sub_2,seq,cc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_2,'XTickLabel',cc_names);
    title(ds.LabelsCentralities{2});

    sub_3 = subplot(2,3,3);
    bar(sub_3,seq,dc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_3,'XTickLabel',dc_names);
    title(ds.LabelsCentralities{3});

    sub_4 = subplot(2,3,4);
    bar(sub_4,seq,ec,'FaceColor',[0.749 0.862 0.933]);
    set(sub_4,'XTickLabel',ec_names);
    title(ds.LabelsCentralities{4});

    sub_5 = subplot(2,3,5);
    bar(sub_5,seq,kc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_5,'XTickLabel',kc_names);
    title(ds.LabelsCentralities{5});

    sub_6 = subplot(2,3,6);
    bar(sub_6,seq,clc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_6,'XTickLabel',clc_names);
    title(ds.LabelsCentralities{6});

    set([sub_1 sub_2 sub_3 sub_4 sub_5 sub_6],'XLim',[0 (ds.N + 1)],'XTick',seq,'XTickLabelRotation',90);
    set([sub_1 sub_2 sub_3 sub_4 sub_5 sub_6],'YGrid','on');

    figure_title(f,'Average Centrality Measures');

    maximize_figure(f);

end

%% VALIDATION

function out = validate_output(out)

    [path,name,extension] = fileparts(out);

    if (~strcmpi(extension,'.xlsx'))
        out = fullfile(path,[name extension '.xlsx']);
    end

end

function temp = validate_template(temp)

    sheets = {'Indicators' 'Average Adjacency Matrix' 'Average Centrality Measures'};
    file_sheets = validate_xls(temp,'T');

    if (~all(ismember(sheets,file_sheets)))
        error(['The template must contain the following sheets: ' sheets{1} sprintf(', %s',sheets{2:end}) '.']);
    end

    worksheets_batch(temp,sheets);

end


% fmri_data: Data class for storing and analyzing neuroimaging datasets and associated information
%
% 'fmri_data' is a data class containing information about generic fmri
% datasets stored in a structure-like object.  Using this has the
% advantages that the properties and methods are standardized and controlled.
% It also keeps track of the history of what was done to the dataset.
%
% -------------------------------------------------------------------------
% Features and philosophy:
% -------------------------------------------------------------------------
%
% - Store image data in a flat (2-d), space-efficient voxels x images matrix
% - Save space: Tools to remove out-of-image voxels and empty (zero/NaN) voxels, store data in single-precision format
% - Analysis-friendly: 2-d matrices can be read and analyzed in multiple packages/algorithms
% - Meta-data included to convert back to 3-d image volume space,
%   with easy tools (methods) to reconstruct and visualize images
%   Built-in resampling makes it easy to compare/combine datasets with different voxel sizes and image bounding boxes
%   Reduces overhead for statisticians/data scientists unfamiliar with neuroimaging to apply their algorithms.
% - Multiple images can be stored in a single object
% - Methods have short, intuitive names, and perform high-level functions specialized for neuroimaging:
% - Visualization (plot, orthviews, surface, montage, histogram, isosurface methods)
% - Image manipulation (apply_mask, get_wh_image, resample_space, compare_space, flip, threshold methods)
% - Data extraction (apply_atlas, apply_parcellation, extract_gray_white_csf, extract_roi_averages)
% - Analysis (ica, mahal, image_math, and many more in the fmri_data subclass)
% - Provenance: Ability to track and update history of changes to objects
%
% fmri_data is a subclass of the image_vector object and inherits its
% properties and methods.
%
% -------------------------------------------------------------------------
% To construct/create a new instance of an object:
% -------------------------------------------------------------------------
%
% Basic usage:
% obj = fmri_data(image_names, [maskinput], [other optional inputs])
%
% maskinput     :   [optional] name of mask image to use.  Default: 'brainmask_canlab.nii', a
%                   brain mask that is distributed with SPM software and in
%                   the CANlab Core Tools
%                   Alternative in CANlab tools: which('gray_matter_mask.img')
% 'noverbose'   :   Suppress verbose output during image loading
% 'sample2mask' :   Sample images to mask space. Default: Sample mask to
%                   image space, use native image space
%
% Creating class instances
% -----------------------------------------------------------------------
% You can create an empty object by using:
% fmri_dat = fmri_data
% - fmri_dat is the object.
% - It will be created with a standard brain mask, brainmask_canlab.nii
% - This image should be placed on your Matlab path
% - The space information is stored in fmri_dat.volInfo
% - Data is stored in fmri_dat.dat, in a [voxels x images] matrix
% - You can replace or append data to the fmri_dat.dat field.
%
% You can create an object by assembling an image_vector object from parts
% (entering fields) and converting using fmri_obj = fmri_data(image_vec_obj)
% This can also be a structure with field names that match properties of
% the new intended fmri_data object. e.g.,
%   new_obj.volInfo = MC_Setup.volInfo;
%   new_obj.dat = MC_Setup.unweighted_study_data(:, sym_cons);
%
% You can create an fmri_data object with extacted image data.
% - Let "imgs" be a string array or cell array of image names
% - This command creates an object with your (4-D) image data:
% - fmri_dat = fmri_data(imgs);
% - Images can be zipped (.gz) as well. fmri_data() will unpack them.
% - Only values in the standard brain mask, brainmask_canlab.nii, will be included.
% - This saves memory by reducing the number of voxels saved dramatically.
%
% You can specify any mask you'd like to extract data from.
% - Let "maskimagename" be a string array with a mask image name.
% - this command creates the object with data saved in the mask:
% - fmri_dat = fmri_data(imgs, maskimagename);
% - The mask information is saved in fmri_dat.mask
%
% e.g., this extracts data from images within the standard brain mask:
% dat = fmri_data(imgs, which('brainmask_canlab.nii'));
%
% Defining the space of the extracted data
% -----------------------------------------------------------------------
% Note: There are two options for defining the space (i.e., coordinates/voxels)
% that the data is mapped to.
% By default, the mask is resliced to the same space as the first image in the
% input image name set (not coregistered; just resliced to the same voxel sizes.
% The images are assumed to be in register.)
%
% Reampling to mask space: YOU CAN ALSO map the image data to the space of the mask, by entering
% 'sample2mask' as in input argument.
% For loading images in different spaces together in one object, use the 'sample2mask' option.
%
% Attaching additional data
% -----------------------------------------------------------------------
% The fmri_data object has a number of fields for appending specific types of data.
%
% - You can replace or append data to the fmri_dat.dat field.
% - The fmri_data object will also store predictor data (.X) also outcome data (.Y)
% - There are many fields for descriptions, notes, etc., like "dat_descrip" and "source_notes"
% - Attach custom descriptions in these fields to document your object.
% - The "history" field stores a cell array of strings with the processing
% history of the object. Some methods add to this history automatically.
%
% -----------------------------------------------------------------------
% Properties and methods
% -----------------------------------------------------------------------
% Properties are data fields associated with an object.
% Type the name of an object (class instance) you create to see its
% properties, and a link to its methods (things you can run specifically
% with this object type). For example: After creating an fmri_data object
% called fmri_dat, as above, type fmri_dat to see its properties.
%
% There are many other methods that you can apply to fmri_data objects to
% do different things.
% - Try typing methods(fmri_data) for a list.
% - You always pass in an fmri_data object as the first argument.
% - Methods include utilities for many functions - e.g.,:
% - resample_space(fmri_dat) resamples the voxels
% - write(fmri_dat) writes an image file to disk (careful not to overwrite by accident!)
% - regress(fmri_dat) runs multiple regression
% - predict(fmri_dat) runs cross-validated machine learning/prediction algorithms
%
% Key properties and methods (a partial list; type doc fmri_data for more):
% -------------------------------------------------------------------------
% fmri_data Properties (a partial list; type doc fmri_data for more):
%   dat                     - Image data, a [voxels x images] matrix, single-format
%   fullpath                - List of image names loaded into object with full paths
%   history                 - History of object processing, for provenance
%   image_names             - List of image names loaded into object, no paths
%   removed_images          - Vector of images that have been removed (saves space; see remove_empty.m, replace_empty.m)
%   removed_voxels          - Vector of empty in-mask voxels that have been removed (saves space; see remove_empty.m, replace_empty.m)
%   volInfo                 - Structure with info on brain mask (saves space) and mapping voxels to brain space
%
% fmri_data Methods (a partial list; type doc fmri_data for more):
%   General:
% . 	descriptives        -  Get descriptives for an fmri_data or other image_vector object
%       enforce_variable_types	-  Re-casts variables in objects into standard data types, which can save
%       flip                - Flips images stored in an object left-to-right
%     	history             - Display history for image_vector object
%       write               - Write an image_vector object to hard drive as an Analyze image (uses .fullpath field for image names)
%   Data extraction:
%       apply_atlas         - Computes the mean value or pattern expression for each reference region specified in an atlas object
%       apply_mask          - Apply a mask image (image filename or fmri_mask_image object) to an image_vector object
%       apply_parcellation  - Computes the mean value or pattern expression for each parcel specified in a data object
%       extract_gray_white_csf	- Extracts mean values (values) and top 5 component scores (components)
%       extract_roi_averages	- This image_vector method a extracts and averages data stored in an fmri_data object
%
%   Handling brain space and image selection:
%       compare_space           - Compare spaces of two image_vector objects
%       get_wh_image            - For an image_vector with multiple images (cases, contrasts, etc.), select a subset.
%       reconstruct_image       - Reconstruct a 3-D or 4-D image from image_vector object obj
%       remove_empty            - remove vox: logical vector of custom voxels to remove, VOX x 1
%       reparse_contiguous      - Re-construct list of contiguous voxels in an image based on in-image
%       replace_empty           - Replace empty/missing values in an image data object
%       resample_space          - Resample the images in an fmri_data object (obj) to the space of another
%
%   Display and visualization:
%   	display_slices      - Creates 3 separate montage views - ax, cor, sagg in a special figure window
%       histogram           - Create a histogram of image values or a series of histograms for each
%       image_similarity_plot - Associations between images in object and set of 'spatial basis function' images (e.g., 'signatures' or pre-defined maps)
%       isosurface          - Create and visualize an isosurface created from the boundaries in an image object.
%       montage             - Create a montage of an image_vector (or statistic_image or fmri_data)
%       orthviews               - display SPM orthviews for CANlab image_vector (or fmri_data, statistic_image) object
%       pattern_surf_plot_mip	- axial maximum intensity projection pattern surface plot
%   	sagg_slice_movie	- Movie of successive differences (sagittal slice)
%       slices              - Create a montage of single-slice results for every image in an image_vector object
%       surface             - Render image data on brain surfaces; options for cutaways and canonical surfaces
%       wedge_plot_by_atlas	- Plot a data object or 'signature' pattern divided into local regions
%
%   Data processing and analysis:
%   	ica                 - Spatial ICA of an fmri_data object
%       image_math          - Perform simple mathematical and boolean operations on image objects (see also plus, minus, power)
%       mahal               - Mahalanobis distance for each image in a set compared to others in the set
%       mean                - Mean across a set of images. Returns a new image_vector object.
%       preprocess          - Preprocesses data in an image_vector (e.g., fmri_data) object; many options for filtering and outlier id
%       qc_metrics_second_level	- Quality metrics for a 2nd-level analysis (set of images from different subjects)
%       searchlight         - Run searchlight multivariate prediction/classification on an image_vector
%       threshold           - Threshold image_vector (or fmri_data or fmri_obj_image) object based on raw threshold values
%       union               - ...and intersection masks for two image_vector objects
%
% -------------------------------------------------------------------------
% Examples and help:
% -------------------------------------------------------------------------
%
% To list properties and methods for this object, type:
% doc fmri_data, methods(fmri_data)
%
% Example 1: Load images (and run a simple analysis)
% % --------------------------------------------------------------------
%
% Load a sample dataset into an fmri_data object (subclass of image_vector)
% This loads one of a set of named image collections used in demos/help:
% data_obj = load_image_set('emotionreg');
%
% You can load the same images manually, by locating the files, listing
% their names in a character array (or 1 x n cell array of strings), and
% then passing those into fmri_data:
%
% data_obj = fmri_data(which('Wager_2008_emo_reg_vs_look_neg_contrast_images.nii.gz'));
%
% filedir = what(fullfile('CanlabCore', 'Sample_datasets', 'Wager_et_al_2008_Neuron_EmotionReg'));
% image_names = filenames(fullfile(filedir.path, '*img'));
% data_obj = fmri_data(image_names);
%
% Now you can interact with the object.  Try, e.g.,:
% methods(data_obj)                               % List methods for object type
% descriptives(data_obj);                         % Print summary of descriptive statistics for the dataset
% plot(data_obj)                                  % Custom fmri_data specific plots
% t = ttest(data_obj);                            % Perform a voxel-wise one-sample t-test across images
% t = threshold(t, .005, 'unc', 'k', 10);         % Re-threshold with extent threshold of 10 contiguous voxels
% r = region(t);                                  % Turn t-map into a region object with one element per contig region
%
% Example 2: Extract data averaged over regions of interest:
% % --------------------------------------------------------------------
%
% First run Example 1.  Now you have a thresholded t-statistic map.
% Extract averages (across voxels) for each subject in each contiguous
% region by typing:
%
% r = extract_roi_averages(data_obj, t);
%
% This returns r, which is another object--a "region"-class object.
% Region objects contain vectors, one element per pre-defined region in the
% image (in this case, significant blobs from our analysis).
% Each element contains info that describes the region, including the voxels included,
% and average and voxel-by-voxel data if extracted from images and attached.
% Type "doc region" for more info.
% r has properties that hold multiple types of data:
% - .dat holds generic extracted data
% - .all_data holds voxel-by-voxel extracted data
%
% % --------------------------------------------------------------------
% % Group one-sample t-test:
% % A simple, complete example of a group analysis
% % For more, see walkthroughs on canlab.github.io
% % --------------------------------------------------------------------
% % Load sample images, creating and fmri_data class object with 30 images
%     imgs = load_image_set('emotionreg');
%
% % Display a slice from each image in a montage:
%     slices(imgs);
%
% % Display some useful summary plots of the dataset:
%     plot(imgs);
%
% % Perform a t-test on each voxel, returning a statistic_image object
% % containing t-stats and p-values:
%     t = ttest(imgs);
%
% % Display the unthresholded results in a quick-render montage of image
% % values only:
%     display_slices(t, 'axial'); colormap summer; colorbar;
%
% % Display the unthresholded results over an anatomical underlay,
% % on a combination of slices and surfaces,
% % returning an fmridisplay class object with registered handles
%     o2 = canlab_results_fmridisplay(t, 'full');
%
% % Remove colors from slices and surfaces registered in the o2 object:
%     o2 = removeblobs(o2);
%
% % Threshold the t-statistic_image object at p < 0.005
%     t = threshold(t, .005, 'unc');
%
% % Re-display the thresholded images on slices/surfaces registered in o2:
%     o2 = addblobs(o2, region(t), 'nolegend');
%
% % Display the thresholded t-map with orthviews:
%     orthviews(t);
%
% % display on a slice montage:
%    create_figure('slices'); axis off;
%    montage(t);
%
% % Re-threshold at q < 0.05 FDR, and re-display on orthviews:
%     t = threshold(t, .05, 'fdr');
%     orthviews(t);
%
% % Print a table of results, and return a region-class object r with labels
% % from a default atlas (an atlas-class object):
%     r = table(t);
%
% % Display a slice montage showing each activation blob, with labels:
%   montage(r, 'regioncenters', 'colormap');
%
% --------------------------------------------------------------------
% For more examples and walkthroughs, see
% walkthroughs on canlab.github.io
% These are also found in the CANlab_help_examples
% repository at https://github.com/canlab/CANlab_help_examples
%
% Some example tutorials:
% canlab_help_1_installing_tools
% canlab_help_2_load_a_sample_dataset
% canlab_help_3_voxelwise_t_test_walkthrough
% canlab_help_4_write_data_to_image_file_format
% canlab_help_5_regression_walkthrough
% ... and more
% % --------------------------------------------------------------------

% Programmers' notes:
% Tor Wager, 1/14/17 : Previously, if you stack names of images in different spaces and load them,
% fmri_data did not return an error, but it returned distorted/incorrectly
% loaded images.  Now, it returns an error. For loading images in different
% spaces together, use the 'sample2mask' option.
%
% Stephan Geuter, 5/16/17: Processing of masks in line 270 had been
% changed, masks were ignored. modified check in line 270 to include
% masking again

classdef fmri_data < image_vector

    properties
        % also inherits the properties of image_vector.

        source_notes = 'Source notes...';

        X % legacy; temporary, so we can load old objects

        mask = fmri_mask_image;
        mask_descrip = 'mask is an fmri_mask_image object that defines the mask.';

        images_per_session

        Y = [];
        Y_names;
        Y_descrip = 'Behavioral or outcome data matrix.';

        covariates;
        covariate_names = {''};
        covariates_descrip = 'Nuisance covariates associated with data';

        history_descrip = 'Cell array: names of methods applied to this data, in order';

        additional_info = struct('');

        metadata_table = table(); % a table for storing image-level metadata. Numbers of rows should be the number of images in .dat

    end % properties

    methods

        % Class constructor
        function obj = fmri_data(image_names, maskinput, varargin)
            %
            % [obj, cl_with_averages] = fmri_data(image_names, mask_image, varargin)
            %
            % Reads a set of image files and a mask image, and returns
            % an fmri_data object with data for all in-mask voxels.

            % ---------------------------------
            % Create empty fmri_data object, and return if no additional
            % arguments
            % ---------------------------------

            obj.source_notes = 'Info about image source here';
            obj.mask = fmri_mask_image;
            obj.mask_descrip = 'Volume and in-area mask info from iimg_read_img';

            obj.X = []; % legacy; temporary, so we can load old objects
            obj.Y = [];
            obj.Y_names;
            obj.Y_descrip = 'Behavioral or outcome data matrix.';
            obj.covariates;
            obj.covariate_names = {''};
            obj.covariates_descrip = 'Nuisance covariates associated with data';

            obj.images_per_session = [];

            obj.history = {''};
            obj.history_descrip = 'Cell array of names of methods applied to this data, in order';
            obj.additional_info = struct('');

            % DEFAULT INPUTS
            % -----------------------------------

            verbose = 1;
            verbosestr = 'verbose';
            sample2mask = 0;

            % SET UP OPTIONAL INPUTS
            % -----------------------------------

            for i = 1:length(varargin)
                if ischar(varargin{i})
                    switch varargin{i}

                        case 'verbose', varargin{i} = []; % nothing else needed
                        case 'noverbose', verbose = 0; verbosestr = 'noverbose'; varargin{i} = [];
                        case 'sample2mask', sample2mask = 1; varargin{i} = [];

                        case 'native_image_space' % do nothing, for convenience in calling scripts

                        otherwise, warning(['Unknown input string option:' varargin{i}]);
                    end
                end
            end

            if nargin == 0
                % SPECIAL DEFAULT METHOD: Standard empty mask
                % -----------------------------------

                % Empty: Define with standard default mask
                [image_names, maskinput] = deal(which('brainmask_canlab.nii'));

                if isempty(maskinput)
                    disp('Warning: Cannot find brainmask_canlab.nii, creating without mask info.');
                    return
                end

                verbose = 0;
                verbosestr = 'noverbose';

            elseif isempty(image_names)
               % We must have image names to run
               disp('fmridata: image_names is empty. No images to load.');
               return

            end

            % ---------------------------------
            % Special: if existing image_vector
            % map into space of fmri_data and return
            % ---------------------------------
            if iscell(image_names), image_names = char(image_names{:}); end

            if isstruct(image_names) || isa(image_names, 'image_vector')
                % Map fields of input object into fmri_data structure

                warning off
                obj2 = struct(image_names); %image_names;  % tor: struct not needed i think. nope, is needed.
                warning on

                N = fieldnames(obj);
                for i = 1:length(N)
                    if isfield(obj2, (N{i}))
                        obj.(N{i}) = obj2.(N{i});
                    end
                end

                obj.mask.volInfo = obj2.volInfo;

                obj = run_checks_and_fixes(obj, verbosestr);

                return

            else
                % It's a file. Unzip if needed and check that image_names exist

                % Handle .gz by unzipping if needed
                [image_names, was_gzipped] = gunzip_image_names_if_gz(image_names);

            end

            % ---------------------------------
            % define mask object
            % ---------------------------------

            % if all keywords, like 'noverbose', then varargin will be empty
            % SG changed check, maskinput can be defined and varargin empty.
            % maskinput is not part of varargin. 5/16/17

            % 5/16/17 Tor: if all varargin cells are empty, varargin may not
            % be... also, deal with special case where 2nd input is not
            % mask, but keyword.

            % Special case: 2nd argument is keyword, not mask.  This is
            % improper usage, but allow it for legacy reasons and
            % usability.
            if exist('maskinput', 'var') && ~isempty(maskinput) && ischar(maskinput)

                switch maskinput

                    case 'verbose', maskinput = []; % nothing else needed
                    case 'noverbose', verbose = 0; verbosestr = 'noverbose'; maskinput = [];
                    case 'sample2mask', sample2mask = 1; maskinput = [];
                end
            end

            % Empty mask: use default
            if (nargin < 2 || isempty(maskinput)) % && isempty(varargin)

                maskinput = which('brainmask_canlab.nii');
                if verbose, fprintf('Using default mask: %s\n', maskinput); end
                if isempty(maskinput), error('Cannot find mask image!'); end

            end

            switch class(maskinput)
                case 'char' % string file name
                    maskobj = fmri_mask_image(maskinput);

                case {'fmri_mask_image'} % , 'fmri_data', 'statistic_image'} % others will not work with resample_to_space_defining... below

                    maskobj = maskinput;

                otherwise
                    error('region class constructor: unknown mask input type.')
            end
            clear maskinput

            % Either extract the image data in the space of the mask, or
            % vice versa
            %sample2mask = strmatch('sample2mask', varargin);

            if sample2mask
                % Read data in mask space; map images to mask
                % ------------------------------------------------

                if verbose, fprintf('Expanding image filenames if necessary\n'); end

                for i = 1:size(image_names, 1)

                    iinames{i} = expand_4d_filenames(image_names(i, :));
                end
                iinames = char(iinames{:});
                imgdat = zeros(length(maskobj.volInfo.wh_inmask), size(iinames, 1), 'single');

                if isempty(iinames)
                    disp('Images do not exist!'); disp(image_names); error('Exiting');
                end

                % Now extract the actual data from the mask
                if verbose
                    fprintf('Sampling %3.0f images to mask space: %04d', size(iinames, 1), 0)
                end

                for i = 1:size(iinames, 1)

                    if verbose, fprintf('\b\b\b\b%04d', i); end
                    idat = scn_map_image(iinames(i, :), maskobj);
                    imgdat(:, i) = idat(maskobj.volInfo.wh_inmask);

                end

                if verbose, fprintf('\n'); end

                [dd, ff, ee] = fileparts(maskobj.volInfo.fname);
                maskobj.space_defining_image_name = [ff ee];

                obj.volInfo = maskobj.volInfo;

            else % Not sample2mask, so resample mask to data image space

                % Read data in image space; map mask to images
                % ------------------------------------------------

                % resample to image space if necessary
                space_defining_image = deblank(image_names(1, :));
                maskobj = resample_to_image_space(maskobj, space_defining_image);

                % Now extract the actual data from the mask
                switch spm('Ver')

                    case {'SPM12','SPM8', 'SPM5'}
                        imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand');

                    case {'SPM2', 'SPM99'}
                        % legacy, for old SPM
                        imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr);

                    otherwise
                        error('Unknown version of SPM! Update code, check path, etc.');
                end

                imgdat = imgdat';

            end % read data, depending on mask sampling

            % re-zip images if they were originally zipped
            % add .gz back to file names.
            if any(was_gzipped)

                image_names = re_zip_images(image_names, was_gzipped);

            end

            % add mask object to fmri_data object
            obj = create(obj, 'mask', maskobj, verbosestr);

            % append data
            obj = create(obj, 'dat', imgdat, 'image_names', image_names, verbosestr);
            clear imgdat

            % append description info
            [~, ff, ee] = fileparts(maskobj.volInfo.fname);
            mask_image_name = [ff ee];

            %obj = create(obj, 'dat_descrip', sprintf('Data from %s: %s', mask_image_name, obj.dat_descrip));
            obj = create(obj, 'mask_descrip', mask_image_name, verbosestr);

            obj.volInfo = maskobj.volInfo;

            obj.history(end+1) = {sprintf('Sampled to space of %s', maskobj.space_defining_image_name)};
            obj.history(end+1) = {['Masked with ' mask_image_name]};

            % Checks and fixes
            % -------------------------------------------------------------

            obj = run_checks_and_fixes(obj, verbosestr);

        end % constructor function

    end % methods

end

% -------------------------------------------------------------
% -------------------------------------------------------------

% Sub-functions

% -------------------------------------------------------------
% -------------------------------------------------------------

function obj = run_checks_and_fixes(obj, verbosestr)

obj = check_image_filenames(obj, verbosestr);

if isempty(obj.volInfo)
    error('obj.volInfo cannot be empty');
end

if isempty(obj.mask) && ~isempty(obj)  % isempty is an object method here
    % fix/create mask

    obj.mask.dat = any(obj.dat, 2);
    obj.mask.removed_voxels = obj.removed_voxels;
    obj.mask.volInfo_descrip = 'Generic mask built from .dat. Any voxel with a value in .dat is in-mask';

end

if issparse(obj.dat)
    obj.dat = full(obj.dat);
end

if ~isfield(obj.volInfo, 'cluster')
    obj = reparse_contiguous(obj);
end

if strcmp(verbosestr, 'verbose')

    % Check data bit rate
    % ---------------------------------------------------------------------

    databitrate = length(unique(obj.dat(:)));

    fprintf('Number of unique values in dataset: %d  Bit rate: %3.2f bits\n', databitrate, log2(databitrate));

    if databitrate < 2^10
        fprintf('Warning: Number of unique values in dataset is low, indicating possible restriction of bit rate. For comparison, Int16 has 65,536 unique values\n');
    end

end

end % function

% -------------------------------------------------------------

function image_names_out = re_zip_images(image_names, was_gzipped)
% in : char, out: char

image_names_out = cellstr(image_names);

for i = 1:size(image_names, 1)

    if was_gzipped(i)

        % Use system to remove unzipped version after zipping.
        % Will wait for input, and not overwrite, if images exist
        %         [status, result] = system(['gzip ' image_names(i, :)]);

        try
            gzip(deblank(image_names(i, :)));
            image_names_out{i} = [deblank(image_names_out{i}) '.gz'];

        catch
            warning('Error writing .gz images. Check permissions (or maybe using Git Annex?');

        end
    end

end

image_names_out = char(image_names_out{:});

end


% this function is dedicated for crossover operation used by MATLAB ga
% function.
function xoverKids  = SP_crossover(parents,options,NVARS, ...
    FitnessFcn,thisScore,thisPopulation)

nKids = length(parents)/2;
xoverKids = cell(nKids,1); % Normally zeros(nKids,NVARS);
index = 1;
for i=1:nKids
    parent = thisPopulation{parents(index)};
    index = index + 2;
    % Flip a section of parent1.
    p1 = ceil((length(parent) -1) * rand);
    p2 = p1 + ceil((length(parent) - p1- 1) * rand);
    child = parent;
    child(p1:p2) = fliplr(child(p1:p2));
    xoverKids{i} = child; % Normally, xoverKids(i,:);
end


function [model] = integrate_cVector_into_model(model)
% sintegrate_cVector_into_model incorporates the c vector directly into the
% model as the reaction 'obj_fun_rxn'.

% USAGE:
%
%    [model] = integrate_cVector_into_model(model)
%
% INPUTS:
%   model:  COBRA model structure with minimal fields:

% OUTPUT:
%   model:   Augmented COBRA model
%
%
% .. Authors: - Bronson R. Weston   2022

model=addMetabolite(model, 'obj_fun_met');
model.S(end,:)=model.c;
model = addMultipleReactions(model, {'obj_fun_rxn'}, {'obj_fun_met'}, [-1], 'lb', [-1e7], 'ub', [1e7]);

model.c=zeros(length(model.c),1);
model.c(end)=1;

end