function imageCha = recon_main(obj, iRep, iAvg)
% main reconstruction function
% prepare constraints and select algorithm depending on image dimensionality
%
% input:
% obj       CS reconstruction object (holding all parameters)
% iRep      current repetition
% iAvg      current average
%
% output:
% imageCha  individual reconstructed channel images
%
% (c) Thomas Kuestner
% -------------------------------------------------------------------------

nCha = obj.measPara.dim(5);
nSlices = size(obj.kSpace,1);
nTime = obj.measPara.dim(4);
imageCha = cell(nSlices,nCha);

if(strcmp(obj.measPara.dimension,'2D'))

elseif(strcmp(obj.measPara.dimension,'3D'))

    % prepare k-space
    % => k_y - k_z - k_x - cha
    kSpaceL = cell2mat(shiftdim(obj.kSpace(:,:,iRep,iAvg),-2));
    kSpaceL = permute(kSpaceL,[1 3 2 4]);
    if(strcmp(obj.measPara.precision,'double'))
        kSpaceL = double(kSpaceL);
    end
    % same undersampling for all coils and fully sampled in k_x direction
    obj.fullMask = abs(kSpaceL) > 0; % k_y - k_z - k_x
    if(obj.measPara.oversampling{2,1})
        kSpaceL = ifftnshift(kSpaceL, 3); % -> k_y - k_z - x - cha
        kSpaceL = kSpaceL(:, :, obj.measPara.oversampling{1,1}, :); % anti-aliasing
        obj.fullMask = obj.fullMask(:,:, obj.measPara.oversampling{1,1}, :);
        kSpaceL = fftnshift(kSpaceL,3); % -> k_y - k_z - k_x - cha
        kSpaceL = kSpaceL .* obj.fullMask;
        obj.measPara.dim(2) = size(kSpaceL,3);
    end
    [nPha, nZ, nFreq, nCha] = size(kSpaceL);

    if(~isempty(obj.calibSize))
        % adjust calibSize due to anti-aliasing
        obj.calibSize(3) = nFreq;
    end

    % create a 3D kernel fomr the calibration region
    fprintf('Performing calibration\n');
    if(isempty(obj.calibSize))
        % automatically determine calibration region
        obj.calibSize = obj.calibrationSize(obj.fullMask(:,:,:,1)); % k_y - k_z - k_x
        if(size(kSpaceL,3) == 1), obj.calibSize = [obj.calibSize, 1]; obj.kernelSize = obj.kernelSize([1 3 2]); end; % k_y - k_x sparse!
        if(~obj.flagToolbox)
            obj.kCalib = crop(kSpaceL, [obj.calibSize, nCha]); % k_y - k_z - k_x - cha
        end
    else
        if(~obj.flagToolbox)
            % fixed calibration region
            plonk = [nPha, nZ, nFreq];
            idx = cell(1,length(plonk));
            for iInner=1:length(plonk)
                if(mod(obj.calibSize(iInner),2) == 0)
                    idx{iInner} = floor(plonk(iInner)/2)+1+ceil(-obj.calibSize(iInner)/2):floor(plonk(iInner)/2)+ceil(obj.calibSize(iInner)/2);
                else
                    idx{iInner} = floor(plonk(iInner)/2)+ceil(-obj.calibSize(iInner)/2):floor(plonk(iInner)/2)+ceil(obj.calibSize(iInner)/2)-1;
                end

                tmp = [idx{iInner}(1) <= 0, idx{iInner}(end) > plonk(iInner)];
                if(any(tmp))
                    if(all(tmp)), error('crop(): Both index out of bounds'); end;
                    hShift = [abs(idx{iInner}(1)) + 1, idx{iInner}(end) - plonk(iInner)];
                    op = {idx{iInner}(1), idx{iInner}(end); '+', '-'; '<', '>'; plonk(iInner) + 1, 0};
                    eval(sprintf('if(op{1,~helper} %s hShift(helper) %s %d), idx{iInner} = idx{iInner} %s hShift(helper); else idx{iInner} = idx{iInner}(idx{iInner} %s %d);end;',op{2,helper}, op{3,helper}, op{4,helper}, op{2,helper}, op{3,~helper}, op{4,~helper}));
                end
            end
            obj.kCalib = kSpaceL(idx{1},idx{2},idx{3},:); % k_y - k_z - k_x - cha
        end
    end

    if(~obj.flagToolbox)
        % windowing for cut-out region
    %     [~,w1] = window2D(prop.window.type,size(obj.kCalib,1),prop.window.windowOpt{1},prop.window.windowOpt{2});
    %     [~,w2] = window2D(prop.window.type,size(obj.kCalib,2),prop.window.windowOpt{1},prop.window.windowOpt{2});
    %     window =  w1(:) * w2(:).';
        if(obj.window.windowingOn)
            window = windowND(obj.window.type,[size(obj.kCalib,1), size(obj.kCalib,2)],obj.window.windowOpt{1},obj.window.windowOpt{2});
            obj.kCalib = obj.kCalib .* repmat(window,[1 1 size(obj.kCalib,3) nCha]);
        end

        % compute eigen-value maps
        [hybridKernel,S] = dat3Kernel(obj.kCalib,obj.kernelSize);
        hybridKernel = ifftnshift(zpad(hybridKernel(:,:,end:-1:1,:,:),[obj.kernelSize(1),obj.kernelSize(2),nFreq + obj.kernelSize(3)-1,nCha,size(hybridKernel,5)]),3);
        idx = max(find(S >= S(1)*obj.eigThresh_k));

        % compute inverse fourier transform along readout direction
        kSpaceL = ifftnshift(kSpaceL,3); % k_y - k_z - x - cha

        % convolution with GRAPPA kernel in k-space -> multiplication in image
        % domain
        obj.method = 'fft';

        % prepare wavelet transformation
        if(strcmp(obj.solver,'L1'))
            if(strcmp(obj.trafo.trafoType,'wavelet_lab'))
                ssx = 2^ceil(log2(nPha));
                ssy = 2^ceil(log2(nZ));
                ss = max(ssx, ssy);
                XOP = Wavelet(obj.trafo.waveletFilter,obj.trafo.waveletFilterSize,log2(ss/2^(obj.trafo.waveletStages)));
            end
        end

        image = zeros(nPha,nFreq,nZ,obj.n_maps);
    %     AtA_sub = zeros(prod(obj.kernelSize)*nCha); % subtract this matrix from AtA_mask, due to accumulation of previous samples (A'*A multiplication)
    %     A_sub = zeros((size(obj.kCalib,1)-obj.kernelSize(1)+1) * (size(obj.kCalib,2)-obj.kernelSize(2)+1) * (size(obj.kCalib,3)-obj.kernelSize(3)+1),prod(obj.kernelSize)*nCha);

        fprintf('ESPIRiT reconstruction\n');
        dispProgress('Frequency',0,nFreq);
        for iFreq=1:nFreq

            % extract 2D kernel from complete 3D kernel
            obj.kernel = squeeze(hybridKernel(:,:,iFreq,:,:));

            % crop kernels and compute eigen-value decomposition in image space to get maps
            [M,W] = kernelEig(obj.kernel(:,:,:,1:idx),[nPha,nZ]);

            % Compute Soft-SENSE ESPIRiT Maps
            % crop sensitivity maps according to eigenvalues == 1
            % weigth with n maps of eigen-values

            maps = M(:,:,:,end-(obj.n_maps-1):end);

            % Weight the eigenvectors with soft-senses eigen-values
            weights = W(:,:,end-(obj.n_maps-1):end);
            weights = (weights - obj.eigThresh_im)./(1-obj.eigThresh_im).* (W(:,:,end-(obj.n_maps-1):end) > obj.eigThresh_im);
            weights = -cos(pi*weights)/2 + 1/2;

            % create an ESPIRiT operator
            ESP = ESPIRiT(maps,weights);

            if(strcmp(obj.solver,'L1'))
                FT = p2DFT(squeeze(obj.fullMask(:,:,iFreq,:)),[nPha,nZ,nCha]);
                if(strcmp(obj.trafo.trafoType,'fft'))
                    XOP = FT;
                end
                image(:,iFreq,:,:) = permute(obj.cgL1ESPIRiT(squeeze(kSpaceL(:,:,iFreq,:)), zeros([size(squeeze(kSpaceL(:,:,iFreq,1))),obj.n_maps]), FT, ESP, obj.iNINNER, XOP, obj.trafo.wavWeight, obj.splitWeight, obj.iNIterSplit),[1 4 2 3]); % => y-x-z-cha
            else
                [tmpK,tmp] = obj.cgESPIRiT(squeeze(kSpaceL(:,:,iFreq,:)), ESP, obj.iNINNER, obj.calibTyk, zeros(size(squeeze(kSpaceL(:,:,iFreq,:)))));
                image(:,iFreq,:,:) = permute(tmp,[1 4 2 3]);
            end
            dispProgress('Frequency', iFreq/nFreq);
        end
        dispProgress('Frequency','Close');
    %     image = ifftnshift(image,[1,3]);
        imageCha(:,1:obj.n_maps) = shiftdim(mat2cell(image,nPha,nFreq,nZ,ones(1,obj.n_maps)),2);
        [imageCha{:,obj.n_maps+1:end}] = deal(zeros(size(imageCha{1,1})));

    else
        fprintf('ESPIRiT reconstruction\n');
        if(ispc || ismac)
            eval(sprintf('ESP = bart(''ecalib -r %d:%d -k %d:%d -m %d'',kSpaceL);', obj.calibSize(1), obj.calibSize(2), obj.kernelSize(1), obj.kernelSize(2), obj.n_maps ));
            if(strcmp(obj.solver,'L1'))
                eval(sprintf('image = permute(squeeze(bart(''pics -l1 -r %f'', kSpaceL, ESP)),[1 3 2 4]);', obj.trafo.wavWeight));
            else
                image = permute(squeeze(bart('pics -l2', kSpaceL, ESP)),[1 3 2 4]);
            end
        else
            % create temporary dir if not yet existing
            if(~exist([obj.path,filesep,'tmpESPIRiT'],'dir'))
                mkdir([obj.path,filesep,'tmpESPIRiT']);
            end
            fftdims = size(kSpaceL);
            fftdims = fftdims(1:3);
            if(any(mod(fftdims,2) ~= 0))
                writecfl([obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], zpad(kSpaceL,size(kSpaceL) + [mod(fftdims,2),0])); % k_y - k_z - k_x - cha
            else
                writecfl([obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], kSpaceL); % k_y - k_z - k_x - cha
            end
            obj.calibSize = obj.calibSize - mod(obj.calibSize,2);
            eval(sprintf('! ecalib -r %d:%d:%d -k %d:%d:%d -m %d %s %s', obj.calibSize(1), obj.calibSize(2), obj.calibSize(3), obj.kernelSize(1), obj.kernelSize(2), obj.kernelSize(3), obj.n_maps, [obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], [obj.path,filesep,'tmpESPIRiT',filesep,'espiritmaps']));
            ESP = [obj.path,filesep,'tmpESPIRiT',filesep,'espiritmaps'];

            if(strcmp(obj.solver,'L1'))
                eval(sprintf('! sense -l1 -r %f %s %s %s', obj.trafo.wavWeight, [obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], ESP, [obj.path,filesep,'tmpESPIRiT',filesep,'imgOut']));
            else
                eval(sprintf('! sense -l2 %s %s %s', [obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], ESP, [obj.path,filesep,'tmpESPIRiT',filesep,'imgOut']));
            end
            image = permute(squeeze(readcfl([obj.path,filesep,'tmpESPIRiT',filesep,'imgOut'])),[1 3 2 4]);
        end
%         % OR:
%         kSpaceL = permute(ifftnshift(kSpaceL,3), [3 1 2 4]); % x - k_y - k_z - cha
%         writecfl([obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], kSpaceL); % x - k_y - k_z - cha
%         eval(sprintf('! ecalib -r %d:%d:%d -k %d:%d:%d -m %d %s %s', obj.calibSize(3), obj.calibSize(1), obj.calibSize(2), obj.kernelSize(3), obj.kernelSize(1), obj.kernelSize(2), obj.n_maps, [obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], [obj.path,filesep,'tmpESPIRiT',filesep,'espiritmaps']));
%         ESP = [obj.path,filesep,'tmpESPIRiT',filesep,'espiritmaps'];
%          if(strcmp(obj.solver,'L1'))
%             eval(sprintf('! rsense -l1 -r %f %s %s %s', obj.trafo.wavWeight, [obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], ESP, [obj.path,filesep,'tmpESPIRiT',filesep,'imgOut']));
%         else
%             eval(sprintf('! rsense -l2 %s %s %s', [obj.path,filesep,'tmpESPIRiT',filesep,'kspace'], ESP, [obj.path,filesep,'tmpESPIRiT',filesep,'imgOut']));
%         end
%         image = permute(squeeze(readcfl([obj.path,filesep,'tmpESPIRiT',filesep,'imgOut'])), [2 1 3 4]);

        image = crop(image,[size(kSpaceL,1),size(kSpaceL,3),size(kSpaceL,2),obj.n_maps]);
        imageCha(:,1:obj.n_maps) = shiftdim(mat2cell(image,nPha,nFreq,nZ,ones(1,obj.n_maps)),2);
        [imageCha{:,obj.n_maps+1:end}] = deal(zeros(size(imageCha{1,1})));
    end

elseif(strcmp(obj.measPara.dimension,'4D'))

    % prepare k-space
    % => k_x - k_y - k_z - cha - ... - t
    kSpaceL = cell2mat(shiftdim(obj.kSpace(:,:,iRep,iAvg),-3));
    kSpaceL = permute(kSpaceL,[2 1 3 5 4]);
    if(strcmp(obj.measPara.precision,'double'))
        kSpaceL = double(kSpaceL);
    end
    % same undersampling for all coils and fully sampled in k_x direction
    obj.fullMask = abs(kSpaceL) > 0; % k_x - k_y - k_z - cha - t
    if(obj.measPara.oversampling{2,1})
        kSpaceL = ifftnshift(kSpaceL, 1);
        kSpaceL = kSpaceL(obj.measPara.oversampling{1,1}, :, :, :, :); % anti-aliasing
        obj.fullMask = obj.fullMask(obj.measPara.oversampling{1,1}, :, :, :, :);
        kSpaceL = fftnshift(kSpaceL,1);
        kSpaceL = kSpaceL .* obj.fullMask;
        obj.measPara.dim(2) = size(kSpaceL,1);
    end

    if(~isempty(obj.calibSize))
        % adjust calibSize due to anti-aliasing
        obj.calibSize(3) = size(kSpaceL,1);
    end

    lEnsureCenter = false;
    calibSize = zeros(size(obj.fullMask,5),3);
    for iTime = 1:size(obj.fullMask,5)
        calibSize(iTime,:) = obj.calibrationSize(obj.fullMask(:,:,:,1,iTime));
        if(any(calibSize(iTime,:) <= 4)) % or <= 2
            lEnsureCenter = true;
        end
    end

    if(lEnsureCenter) % ensure a center region is found!
        dPercentage = [0.065,0.2];
        m = [size(obj.fullMask,1), size(obj.fullMask,2), size(obj.fullMask,3)];
        s = [size(obj.fullMask,1)/2, round(dPercentage(1)*size(obj.fullMask,2)), round(dPercentage(2)*size(obj.fullMask,3))];
        idx = cell(1,length(s));
        for n=1:length(s)
            if(mod(s(n),2) == 0)
                idx{n} = floor(m(n)/2)+1+ceil(-s(n)/2) : floor(m(n)/2)+ceil(s(n)/2);
            else
                idx{n} = floor(m(n)/2)+ceil(-s(n)/2) : floor(m(n)/2)+ceil(s(n)/2)-1;
            end
        end
        lMaskNew = false(size(obj.fullMask));
        lMaskNew(idx{1},idx{2},idx{3},:,:) = true;
        lMaskNew = lMaskNew & ~obj.fullMask;
        kSpaceL(lMaskNew) = eps;
    end

    if(obj.mc.jointMCCS)
        % coarse motion estimation (in lowres image)
%         kSpaceL = squeeze(kSpaceL); % x-y-z-cha-t
        nTime = size(kSpaceL,5);
%         % get SENSE map
%         pos = 'idx{1},idx{2},idx{3},:,t';
%         m = size(kSpaceL);
%         kSpaceCenter = false(size(kSpaceL));
%         for t=1:nTime
%             s = calibSize(t,:);
%             idx = cell(1,length(s));
%             for n=1:length(s)
%                 if(mod(s(n),2) == 0)
%                     idx{n} = floor(m(n)/2)+1+ceil(-s(n)/2) : floor(m(n)/2)+ceil(s(n)/2);
%                 else
%                     idx{n} = floor(m(n)/2)+ceil(-s(n)/2) : floor(m(n)/2)+ceil(s(n)/2)-1;
%                 end
%
%                 tmp = [idx{n}(1) <= 0, idx{n}(end) > m(n)];
%                 if(any(tmp))
%                     if(all(tmp)), error('crop(): Both index out of bounds'); end;
%                     hShift = [abs(idx{n}(1)) + 1, idx{n}(end) - m(n)];
%                     op = {idx{n}(1), idx{n}(end); '+', '-'; '<', '>'; m(n) + 1, 0};
%                     eval(sprintf('if(op{1,~tmp} %s hShift(tmp) %s %d), idx{n} = idx{n} %s hShift(tmp); else idx{n} = idx{n}(idx{n} %s %d);end;',op{2,tmp}, op{3,tmp}, op{4,tmp}, op{2,tmp}, op{3,~tmp}, op{4,~tmp}));
%                 end
%             end
%             eval(sprintf('kSpaceCenter(%s) = true;',pos));
%         end
%
%         kSpaceLow = zeros(size(kSpaceL)); kSpaceLow(kSpaceCenter) = kSpaceL(kSpaceCenter);
%         dImgCenter = ifftnshift(kSpaceLow,1:3); % x-y-z-cha-t
%         clear 'kSpaceLow';
%         dImgCenterSOS = sqrt(sum(abs(dImgCenter).^2,4)); % x-y-z-1-t
%         dSensemap = dImgCenter./(repmat(dImgCenterSOS,[1 1 1 size(dImgCenter,4) 1]));
%         clear 'dImgCenterSOS' 'dImgCenter';
%         dWindow = windowND(@hamming, [size(dSensemap,1), size(dSensemap,2), size(dSensemap,3)]);
%         dSensemap = fftnshift(dSensemap,1:3);
%         dSensemap = dSensemap .* repmat(dWindow, [1 1 1 size(dSensemap,4) size(dSensemap,5)]);
%         dSensemap = ifftnshift(dSensemap,1:3);
%         dSensemapFctr = sqrt(sum(conj(dSensemap).*dSensemap,4)).^(-1);
%         dSensemapFctr(isinf(dSensemapFctr)) = 0;
%         dSensemap = conj(repmat(dSensemapFctr,[1 1 1 size(dSensemap,4) 1]) .* dSensemap);
%         clear 'dSensemapFctr';

%         dSensemap = complex(zeros(size(kSpaceL),'single'),zeros(size(kSpaceL),'single'));
%         for t=1:nTime
%             dSensemap(:,:,:,:,t) = bart('ecalib -c0. -I -S -m1', kSpaceL(:,:,:,:,t)); % y-x-z-cha-t
%         end

        lSumMask = logical(sum(obj.fullMask,5));
%         dImgIn = ifftnshift(kSpaceL,1:3) .* dSensemap;

        % registration pairs
%         nPairs = nchoosek(nTime,2);
        [p, q] = meshgrid(1:nTime,1:nTime);
        mask   = triu(ones(nTime), 1) > 0.5;
        pairs = [p(mask) q(mask)];

        % multiresolution approach
        nRes = 5;
        alpha = 0.5;
        beta = [linspace(0.1,0.65,nRes-1),1];
        smallestRes = 64; % in readout direction
        iSize = size(kSpaceL); % y-x-z-cha-t
        iResolutions = [linspace(smallestRes, iSize(1), nRes).', linspace(round(iSize(2)/iSize(1) * smallestRes), iSize(2), nRes).',  linspace(round(iSize(3)/iSize(1) * smallestRes), iSize(3), nRes).'];
        % init
        u = cell(size(pairs,1),2);
        for iI=1:size(u,1)
            u{iI,1} = cell(1,3);
            [u{iI,1}{:}] = deal(zeros(iResolutions(1,:),'single'));
            u{iI,2} = pairs(iI,:);
        end
        matlabpool('open', min([12, size(pairs,1)]));

        kSpaceRes = kSpaceL;
        for iRes=1:nRes
            % first iteration -> downsample from full
            % remaining -> upsample from low_res (with linear interpolation)
            % last iteration -> no downsampling
            kSpaceRes = updownsample(kSpaceRes, false, iResolutions(iRes,:));
            dSenseRes = ones(size(kSpaceRes));
%             dSenseRes = updownsample(dSensemap, false, iResolutions(iRes,:)); % always downsampling from full
            lSumMaskRes = updownsample(lSumMask, false, iResolutions(iRes,:), false); % always downsampling from full, no AA filter
            dImgRes = ifftnshift(kSpaceRes,1:3) .* conj(dSenseRes);
%             dImgDiff = ifftnshift(fftnshift(dImgRes .* conj(dSenseRes),1:3).*obj.fullMask - kSpaceL,1:3) .* dSensemap;
            dImgDiff = ifftnshift(updownsample(updownsample(kSpaceRes,false, size(kSpaceL)).*obj.fullMask - kSpaceL,false,iResolutions(iRes,:)),1:3) .* conj(dSenseRes);

            % LAP registration
            if(iRes > 1) % upsample motion field from last iteration
                [X,Y,Z] = ndgrid(linspace(1,iResolutions(iRes,1),iResolutions(iRes-1,1)), linspace(1,iResolutions(iRes,2),iResolutions(iRes-1,2)), linspace(1,iResolutions(iRes,3),iResolutions(iRes-1,3)));
                [Xq,Yq,Zq] = ndgrid(1:iResolutions(iRes,1),1:iResolutions(iRes,2),   1:iResolutions(iRes,3));
                for iI=1:size(u,1)
                    for iInner=1:3
                        u{iI,1}{iInner} = interpn(X,Y,Z,u{iI,1}{iInner},Xq,Yq,Zq);
                    end
                end
            end
            u = fJointMCCS(squeeze(abs(sum(dImgRes,4))), u, pairs);

            % image update
            for t=1:nTime
                fprintf('Image %d/%d update', t, nTime);
                dSumImg = complex(zeros(size(dImgRes,1),size(dImgRes,2),size(dImgRes,3),'single'),zeros(size(dImgRes,1),size(dImgRes,2),size(dImgRes,3),'single'));
                tmap = 1:nTime; tmap = tmap(~ismember(tmap,t));
                for tI=1:length(tmap)
                    fprintf('.');
                    tInner = tmap(tI);
                    lInd = [tInner, t];
                    iRow = find(ismember(pairs,lInd, 'rows'));
                    if(~isempty(iRow))
                        uDeform = u{iRow,1};
                    else
                        iRow = find(ismember(pairs,lInd(end:-1:1), 'rows'));
                        uDeform = u{iRow,1}; %#ok<FNDSB>
                        uDeform = cellfun(@(x) -x, uDeform, 'UniformOutput', false);
                    end
                    dSumImg = dSumImg + imshift_3D(sum(dImgRes(:,:,:,:,tInner),4),uDeform, 'cubicOMOMS');
                end
                dImgRes(:,:,:,:,t) = dImgRes(:,:,:,:,t) + alpha .* ((1-beta(iRes)) .* dImgDiff(:,:,:,:,t) + beta(iRes)./nTime .* repmat(dSumImg,[1 1 1 size(dSenseRes,4)]) .* dSenseRes(:,:,:,:,t));
                fprintf('\n');
            end

            if(iRes == nRes)
                % CS recon
                kSpaceRes = permute(fftnshift(dImgRes,1:3).* repmat(lSumMaskRes,[1 1 1 1 nTime]),[1 2 3 4 6 7 8 9 10 11 5]) ;
                maps = bart(sprintf('ecalib -c0. -m%d',obj.n_maps), kSpaceRes);
                bartcmd = sprintf('pics -d5 -S -R W:7:0:%f',obj.lambda);
                if(obj.lambdaMC > 0)
                    bartcmd = [bartcmd, sprintf(' -R T:1024:0:%f', obj.lambdaMC)];
                %         bartcmd = [bartcmd, sprintf(' -R L:1024:0:%f', obj.lambdaMC)];
                end
                if(obj.lambdaTV > 0)
                    bartcmd = [bartcmd, sprintf(' -R T:7:0:%f', obj.lambdaTV)];
                end
                imgOut = bart(bartcmd, kSpaceRes, maps);
%                 kSpaceRes = fftnshift(squeeze(repmat(imgOut,[1 1 1 size(dSenseRes,4) 1 1 1 1 1 1 1])) .* dSenseRes,1:3); % y-x-z-cha-t
            else
                kSpaceRes = fftnshift(dImgRes .* dSenseRes,1:3);
            end
        end
        matlabpool('close');
    else
        kSpaceL = permute(kSpaceL,[1 2 3 4 6 7 8 9 10 11 5]);

        maps = bart(sprintf('ecalib -c0. -m%d',obj.n_maps), kSpaceL);

        bartcmd = sprintf('pics -d 5 -R W:7:0:%f',obj.lambda);
        if(obj.lambdaMC > 0)
            bartcmd = [bartcmd, sprintf(' -R T:1024:1024:%f -u 0.1', obj.lambdaMC)];
    %         bartcmd = [bartcmd, sprintf(' -R L:1024:1024:%f', obj.lambdaMC)];
        end
        if(obj.lambdaTV > 0)
            bartcmd = [bartcmd, sprintf(' -R T:7:0:%f -u 0.25', obj.lambdaTV)];
        end
        imgOut = bart(bartcmd, kSpaceL, maps);
    end
    imgOut = permute(imgOut,[2 1 3 11 5 4 6 7 8 9 10]);

    imageCha{1} = imgOut;
%     imageCha = shiftdim(mat2cell(imgOut,size(imgOut,1),size(imgOut,2),size(imgOut,3),size(imgOut,4),ones(1,size(imgOut,5))),3);
end

end


function plotTestSuiteResults(testResultsFolder,reconVersion)

% This function prints and summarizes the results of testAllReconstructionFunctions.

%% plot the computed reconstruction features
currentDir = pwd;
cd(testResultsFolder)

%% load all test results
fields = {
    'Carbon_sources_TruePositives'
    'Carbon_sources_FalseNegatives'
    'Fermentation_products_TruePositives'
    'Fermentation_products_FalseNegatives'
    'growsOnDefinedMedium'
    'Metabolite_uptake_TruePositives'
    'Metabolite_uptake_FalseNegatives'
    'Secretion_products_TruePositives'
    'Secretion_products_FalseNegatives'
    'Bile_acid_biosynthesis_TruePositives'
    'Bile_acid_biosynthesis_FalseNegatives'
    'Drug_metabolism_TruePositives'
    'Drug_metabolism_FalseNegatives'
    'PutrefactionPathways_TruePositives'
    'PutrefactionPathways_FalseNegatives'
    'AromaticAminoAcidDegradation_TruePositives'
    'AromaticAminoAcidDegradation_FalseNegatives'
    'Mass_imbalanced'
    'Charge_imbalanced'
    'Leaking_metabolites'
    };

Results=struct;

for i=1:length(fields)
    if isfile([testResultsFolder filesep fields{i} '_' reconVersion '.txt'])
        savedResults = readtable([testResultsFolder filesep fields{i} '_' reconVersion '.txt'], 'ReadVariableNames', false, 'Delimiter','tab', 'format', 'auto');
        Results.(fields{i}) = table2cell(savedResults);
        numberRecons=size(Results.(fields{i}),1);
    else
        Results.(fields{i}) = {};
    end
end

for j=1:length(fields)
    data=Results.(fields{j});
    if size(data,2)>1
        plotdata=[];
        % if there are no entries for data
        if size(data,2)==1
            for k=1:length(data)
                plotdata(k,1)=0;
            end
            label='Number of data points';
        else
            if strcmp(fields{j},'growsOnDefinedMedium')
                plotdata=data(:,2);
                plotdata(find(strcmp(plotdata(:,1),'NA')),:)=[];
                plotdata=str2double(plotdata);
            else
                if isnumeric(data{1,2}) && ~isempty(data{1,2})
                    % if the data is fluxes-plot the values
                    plotdata=cell2mat(data(:,2));
                    if ~any(strcmp(fields{j},{'Number_genes', 'Number_reactions', 'Number_metabolites'}))
                        label='Flux (mmol*gDW-1*hr-1)';
                    else
                        label='Number of data points';
                    end
                else
                    % count the non-empty data entries
                    for k=1:size(data,1)
                        plotdata(k,1)=length(find(~cellfun(@isempty,data(k,2:end))));
                    end
                    label='Number of data points';
                end
            end
        end
        figure;
        hold on
        % need a workaround if all data is zero
        if sum(plotdata)==0
            plotdata(1,1)=0.00001;
        end
        % does not work if all values are equal or there are too few values
        try
            violinplot(plotdata, {reconVersion});
            ylabel(label)
            if contains(pwd,'_refined')
                h=title([strrep(fields{j},'_',' ') ', refined reconstructions']);
            elseif contains(pwd,'_draft')
                h=title([strrep(fields{j},'_',' ') ', draft reconstructions']);
            end
            ylim([0 max(plotdata)+1])
            set(h,'interpreter','none')
            set(gca,'TickLabelInterpreter','none')
            set(gca, 'FontSize', 12)
            print(fields{j},'-dpng','-r300')
        end
    end
end

% calculate specificity and sensitivity
features=fields;
features(cellfun(@isempty,regexp(features,strjoin({'True','False'},'|'))),:)=[];
features=strrep(features,'_FalseNegatives','');
features=strrep(features,'_FalsePositives','');
features=strrep(features,'_TruePositives','');
features=strrep(features,'_TrueNegatives','');
features=unique(features);
Table{1,1}='Feature';
Table{1,2}=strcat('Sensitivity ',reconVersion);
cnt=2;
plotdata=[];
for i=1:length(features)
    TPs=zeros(numberRecons,1);
    FNs=zeros(numberRecons,1);
    Table{cnt,1}=features{i};
    if size(Results.(strcat(features{i},'_TruePositives')),2)==1
        for j=1:size(Results.(strcat(features{i},'_TruePositives')),1)
            TPs(j,1)=0;
        end
    else
        for j=1:size(Results.(strcat(features{i},'_TruePositives')),1)
            TPs(j,1)=length(find(~cellfun(@isempty,Results.(strcat(features{i},'_TruePositives'))(j,2:end))));
        end
    end
    if size(Results.(strcat(features{i},'_FalseNegatives')),2)==1
        for j=1:size(Results.(strcat(features{i},'_FalseNegatives')),1)
            FNs(j,1)=0;
        end
    else
        for j=1:size(Results.(strcat(features{i},'_FalseNegatives')),1)
            FNs(j,1)=length(find(~cellfun(@isempty,Results.(strcat(features{i},'_FalseNegatives'))(j,2:end))));
        end
    end

    Sensitivity=sum(TPs)/(sum(TPs)+sum(FNs));
    if isnan(Sensitivity)
        Table{cnt,2}='N/A';
    else
        Table{cnt,2}=Sensitivity;
    end
    % to plot all results
    plotdata(i,1)=sum(FNs);
    plotdata(i,2)=sum(TPs);
    labels{i,1}=strrep(features{i},'_',' ');
    labels{i,1}=strrep(labels{i,1},'AromaticAminoAcidDegradation','Amino acid degradation');
    labels{i,1}=strrep(labels{i,1},'PutrefactionPathways','Putrefaction pathways');
    cnt=cnt+1;
end
% remove drug metabolism-better plot separately
[C,IA]=intersect(labels,{'Drug metabolism'});
labels(IA)=[];
plotdata(IA,:)=[];

% remove entries with no experimental data
delInd=find(sum(plotdata,2)==0);
labels(delInd)=[];
plotdata(delInd,:)=[];

% worksround if there is only one row in plotdata
if size(plotdata,1) == 1
    plotdata(2,:) = NaN;
end

figure;
hold on
h=bar(plotdata);
barvalues(h)
h(1).FaceColor = [1 0 0];
h(2).FaceColor = [0 0 1];
set(findall(gcf,'-property','FontSize'),'FontSize',12)
xticklabels(labels);
set(gca,'XTick',1:numel(plotdata))
xtickangle(45)
set(gca,'YTickLabel',[])
ylabel('Total number of model predictions')
legend('Number of false negatives','Number of true positives')
set(gca,'TickLabelInterpreter','none')
set(gca,'FontSize',10)
if contains(pwd,'_refined')
    title('Features succesfully and unsuccessfully captured by refined reconstructions')
elseif contains(pwd,'_draft')
    title('Features succesfully and unsuccessfully captured by draft reconstructions')
end
print('FN_vs_TPs','-dpng','-r300')

close all

Table=cell2table(Table);
writetable(Table,[reconVersion '_Properties'],'FileType','spreadsheet','WriteVariableNames',false);

%% summarize more features
% report all unbalanced reactions
if size(Results.Mass_imbalanced,2)>1 || size(Results.Charge_imbalanced,2)>1
    Mass_imbalanced=Results.Mass_imbalanced(:,2:end);
%     Mass_imbalanced(cellfun(@isempty,Mass_imbalanced)==1)=[];
    Charge_imbalanced=Results.Charge_imbalanced(:,2:end);
%     Charge_imbalanced(cellfun(@isempty,Charge_imbalanced)==1)=[];
    Unbalanced_reactions=unique([Mass_imbalanced,Charge_imbalanced]);
    Unbalanced_reactions=cell2table(Unbalanced_reactions);
    writetable(Unbalanced_reactions,'Unbalanced_reactions','FileType','text','WriteVariableNames',false);
end
% report all leaking metabolites
if size(Results.Leaking_metabolites,2)>1
    Leaking_metabolites=Results.Leaking_metabolites(:,2:end);
    Leaking_metabolites(cellfun(@isempty,Leaking_metabolites)==1)=[];
    Leaking_metabolites=cell2table(Leaking_metabolites);
    writetable(Leaking_metabolites,'Leaking_metabolites','FileType','text','WriteVariableNames',false);
end

%% print percentage of reconstructions agreeing with data
Percentages={'Feature','StrainsWithData','PercentAgreeing'};
features={
    'Carbon_sources'
    'Fermentation_products'
    'PutrefactionPathways'
    'Secretion_products'
    'Metabolite_uptake'
    'AromaticAminoAcidDegradation'
    'Bile_acid_biosynthesis'
    'Drug_metabolism'
    'growsOnDefinedMedium'
    };
for i=1:length(features)
    Percentages{i+1,1} = features{i};
    cntData=0;
    cntAgreeing=0;
    if i < length(features)
        for j=1:size(Results.(strcat(features{i},'_TruePositives')),1)
            if size(Results.(strcat(features{i},'_TruePositives')),2) > 1
                if ~isempty(Results.(strcat(features{i},'_TruePositives')){j,2})
                    cntData=cntData+1;
                else
                    if size(Results.(strcat(features{i},'_FalseNegatives')),2) > 1
                        if ~isempty(Results.(strcat(features{i},'_FalseNegatives')){j,2})
                            cntData=cntData+1;
                        end
                    end
                end
            else
                if size(Results.(strcat(features{i},'_FalseNegatives')),2) > 1
                    if ~isempty(Results.(strcat(features{i},'_FalseNegatives')){j,2})
                        cntData=cntData+1;
                    end
                end
            end
            TP=length(find(~cellfun(@isempty,Results.(strcat(features{i},'_TruePositives'))(j,2:end))));
            if size(Results.(strcat(features{i},'_FalseNegatives')),2) > 1
                FN=length(find(~cellfun(@isempty,Results.(strcat(features{i},'_FalseNegatives'))(j,2:end))));
            else
                FN = 0;
            end
            if TP > 0 && FN == 0
                cntAgreeing=cntAgreeing+1;
            end
        end
    else
        growth=length(find(str2double(Results.(features{i})(:,2))==1));
        nogrowth=length(find(str2double(Results.(features{i})(:,2))==0));
        cntData=growth+nogrowth;
        cntAgreeing=growth;
    end
    Percentages{i+1,2} = cntData;
    Percentages{i+1,3} = cntAgreeing/cntData;
end
Percentages=cell2table(Percentages);
writetable(Percentages,[reconVersion '_PercentagesAgreement'],'FileType','spreadsheet','WriteVariableNames',false);

cd(currentDir)
end


function optionsDyn = vargplvmOptionsDyn(optionsDyn, X)
% VARGPLVMOPTIONSDYN Fill in an options structure with default parameters
% FORMAT
% DESC takes an VARGPLVM options structure amends it with default values
% wherever the field is empty.
% ARG optionsDyn : the VARGPLVM structure to fill in with default values.
% ARG X : a latent space initialised for the basic model (only used if
% times t are not given)
%
% COPYRIGHT : Michalis K. Titsias, 2011
% COPYRIGHT : Neil D. Lawrence, 2011
% COPYRIGHT : Andreas C. Damianou, 2011
%
% SEEALSO : vargplvmInitDynamics, vargplvmAddDynamics

% VARGPLVM

if isfield(optionsDyn, 'type') && ~isempty(optionsDyn.type)
    optionsDyn.type = 'vargpTime';
end

if strcmp(optionsDyn.type, 'vargpTime')
    if ~isfield(optionsDyn, 'initX')
        optionsDyn.initX = 'ppca';
    end

    if  ~isfield(optionsDyn, 'constrainType') || isempty(optionsDyn.constrainType)
        % If the dynamics kernel is of type invcmpnd, this cell will
        % explain what type of constraints each element will impose (if
        % other kernel is used, then this cell will only have one element).
        % Possible values (so far) include: "time" and "data".
        optionsDyn.constrainType = {'time'};
    end

    % Create time vector for each dimension; if t is not given, assume an
    % equally spaced time vector.
    if  ~isfield(optionsDyn, 't') || isempty(optionsDyn.t)
        fprintf(1, '# Time vector unknown; creating random, equally spaced time vector\n');
        t = linspace(0, 2*pi, size(X, 1)+1)';
        t = t(1:end-1, 1);
        optionsDyn.t = t;
    else
        t=optionsDyn.t;
    end

    if ~isfield(optionsDyn, 'kernType') || isempty(optionsDyn.kernType)
        optionsDyn.kernType = {'rbf','white', 'bias'};
    end

    % A better way to initialize the  kernel hyperparameter,
    % especially lengthscale, should be to learn it by running few iterations of
    % GP regression marginal likelihood maximization given as data the PCA output
    % (the default value is jsut reasonable and it sets the inverse lenthscale to quite
    % small value so the GP dynamic prior is weaker (less smoothed) at
    % initializations
    if  ~isfield(optionsDyn, 'kern') || isempty(optionsDyn.kern)
       % kern = kernCreate(t, {'rbf','white'});
        kern = kernCreate(t, optionsDyn.kernType);
        kern.comp{2}.variance = 1e-3; % 1e-1

        if  isfield(optionsDyn, 'inverseWidth')
            invWidth=optionsDyn.inverseWidth;
        else
            invWidth=5;
        end

        % The following is related to the expected number of zero-crossings.
        if kern.inputDimension == 1
            kern.comp{1}.inverseWidth = invWidth./(((max(t)-min(t))).^2);
        end
        kern.comp{1}.variance = 1;

        optionsDyn.kern = kern;
    end

    if ~isfield(optionsDyn,'seq')
        optionsDyn.seq=[];
    end

    % Set to 1 to reoptimise all inducing points during test time
     if ~isfield(optionsDyn, 'testReoptimise')
     	optionsDyn.testReoptimise = 1;  % !!! SET THE DEFAULT TO 1
     end

     % Set to 0 so that if you use a kernel from the exp. family for the
     % dynamics, then its variance is not learned and the lenghtscale
     % compensates this (we have one less free parameter then).
    if ~isfield(optionsDyn, 'learnVariance')
        optionsDyn.learnVariance = 0; % !!! SET THE DEFAULT TO 0
    end

    % If set to 1 (not recommended) then the initial means have some
    % smoothing in the first and last values (i.e. closer to zero). Useful
    % mostly for periodic kernels.
    if ~isfield(optionsDyn, 'regularizeMeans')
        optionsDyn.regularizeMeans = 0;
    end

end


function train_full_net(varargin)
% load data & label
data = load('./data/train/SDR_youtube_80.mat') ;
label = load('./data/train/HDR_youtube_80.mat') ;
imdb.images.data = data.SDR_data;
imdb.images.label = label.HDR_data;
imdb.images.set = cat(2, ones(1, size(data.SDR_data, 4)-500), 2*ones(1, 500));

% set CNN model
net = net_full();
netstruct = load('./net/net_base/net-epoch-200.mat');
net_init = dagnn.DagNN.loadobj(netstruct.net);

for i = 1:148
    % learning rate and weight decay for biases
    if mod(i, 2) == 0
        net.params(i).learningRate = 0.1;
        net.params(i).weightDecay = 0;
    end
    % initialize net_full with pre-trained values of net_base
    if i>6 && i< 17
        net.params(i).value = net_init.params(i-6).value;
    elseif i>20 && i<29
        net.params(i).value = net_init.params(i-10).value;
    elseif i>32 && i<41
        net.params(i).value = net_init.params(i-14).value;
    elseif i>50 && i<63
        net.params(i).value = net_init.params(i-24).value;
    elseif i>66 && i<79
        net.params(i).value = net_init.params(i-28).value;
    elseif i>82 && i<95
        net.params(i).value = net_init.params(i-32).value;
    elseif i>98
        net.params(i).value = net_init.params(i-36).value;
    end
end
net.conserveMemory = true;

% options
opts.solver = @adam;
opts.train.batchSize = 16;
opts.train.continue = false;
opts.train.gpus = 1;
opts.train.prefetch = false ;
opts.train.expDir = './net/net_full' ;
opts.train.learningRate = [1e-7*ones(1, 250) 1e-8*ones(1, 10) 1e-9*ones(1, 10)];
opts.train.weightDecay = 0.0005;
opts.train.numEpochs = numel(opts.train.learningRate) ;
opts.train.derOutputs = {'objective', 1} ;
[opts, ~] = vl_argparse(opts.train, varargin) ;

% record
if(~isdir(opts.expDir))
    mkdir(opts.expDir);
end

% call training function
[net,info] = cnn_train_dag(net, imdb, @getBatch, opts) ;

function inputs = getBatch(imdb, batch, opts)
image = imdb.images.data(:, :, :, batch) ;
label = imdb.images.label(:, :, :, batch) ;

image = single(image)/255;
label = single(label)/1023;
inputs = {'input', gpuArray(image), 'label', gpuArray(label)};


function autoContour40SUVMax(structNum)
% autoContour40SUVMax
%
% This function creates a contour at 40% threshold level of Max scan value.
% structNum is the structure index in planC.
%
% Created DK 11/21/06
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
% CERR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
% without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% See the GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with CERR.  If not, see <http://www.gnu.org/licenses/>.

global planC stateS

indexS = planC{end};

scanSet = getStructureAssociatedScan(structNum);

[struct3D slices] = getStruct3DMaskedData(structNum);

siz = size(struct3D);

newStructNum = length(planC{indexS.structures})+ 1;
C = max(struct3D(:));

for i = 1:length(slices)
    slice = struct3D(:,:,i);

    [c I] = max(slice(:));

    if c > (C*4/10)
        [x y]=ind2sub([siz(1) siz(2)],I);

        BW = roicolor(slice,C*4/10,C);

        L = bwlabel(BW, 4);

        region = L(x,y);

        ROI = L == region;

        if isempty(find(ROI))
            continue
        end

        [contour, sliceValues] = maskToPoly(ROI, slices(i), scanSet,planC);

        if(length(contour.segments) > 1)
            longestDist = 0;
            longestSeg =  [];

            for j = 1:length(contour.segments)
                segmentV = contour.segments(j).points(:,1:2);
                curveLength = 0;

                for k = 1:size(segmentV,1) - 1
                    curveLength = curveLength + sepsq(segmentV(k,:)', segmentV(k+1,:)');
                end

                if curveLength > longestDist
                    longestDist = curveLength;
                    longestSeg = j;
                end

            end
            tmp = contour.segments(longestSeg).points;

        else
            if isempty(contour.segments.points)
                continue;
            else
                tmp = contour.segments.points;
            end

        end

        planC{indexS.structures}(newStructNum).contour(slices(i)).segments.points = tmp;
    end

end

for l = slices(end)+1 : length(planC{indexS.scan}(scanSet).scanInfo)
    planC{indexS.structures}(newStructNum).contour(l).segments.points = [];
end
stateS.structsChanged = 1;

planC{indexS.structures}(newStructNum).strUID = createUID('structure');
planC{indexS.structures}(newStructNum).assocScanUID = planC{indexS.structures}(structNum).assocScanUID;
planC{indexS.structures}(newStructNum).structureName = [planC{indexS.structures}(structNum).structureName '_40_SUVMax'];

planC = getRasterSegs(planC, newStructNum);

planC = setUniformizedData(planC);


function v = cvx_vexity( x )

global cvx___
s = x.size_;
if any( s == 0 ),
    v = cvx_zeros( s );
    return
end
p  = cvx___.vexity;
b  = x.basis_;
n  = length( p );
nb = size( b, 1 );
if nb < n,
    p = p( 1 : nb, 1 );
elseif n < nb,
    p( nb, 1 ) = 0;
end
if ~any( p ),
    v = cvx_zeros( x.size_ );
    if x.slow_,
        v( isnan( x.basis_( 1, : ) ) ) = NaN;
    end
    return
end
b = b( p ~= 0, : );
p = nonzeros(p).';
if cvx___.nan_used,
    b = sparse( b );
end
v = full( p * b );
tt = abs( v ) ~= abs( p ) * abs( b );
if x.slow_,
    v( tt | isnan( x.basis_( 1, : ) ) ) = NaN;
else
    v( tt ) = NaN;
end
if ~isreal( x ),
    v( any( imag( x.basis_ ), 1 ) & v ) = NaN;
end
v = sign( v );
v = reshape( v, x.size_ );

% 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.


% SB2_FULLSTATISTICS  Compute all relevant statistics in full for SPARSEBAYES
%
% [SIGMA,MU,S_IN,Q_IN,S_OUT,Q_OUT,FACTOR,LOGML,GAMMA,...
%	BETABASIS_PHI,BETA] = ...
%  SB2_FULLSTATISTICS(LIKELIHOOD,BASIS,PHI,TARGETS,USED,ALPHA,BETA,...
%       MU,BASIS_PHI,BASIS_TARGETS,OPTIONS)
%
% OUTPUT ARGUMENTS:
%
%	SIGMA			Posterior covariance matrix for relevant bases
%	MU				Posterior mean
%	S_IN			S-factors for in-model (relevant) basis vectors
%	Q_IN			Q-factors for in-model (relevant) basis vectors
%	S_OUT			S-factors for all basis vectors
%	Q_OUT			Q-factors for all basis vectors
%	FACTOR			Q^2-S relevance factors
%	LOGML			Log-marginal-likelihood
%	GAMMA			"Well-determinedness" factors
%	BETABASIS_PHI	Cached value of BASIS'*B*PHI matrix
%	BETA			Inverse noise variance (vector of beta
%					approximations in non-Gaussian case)
%
% INPUT ARGUMENTS:
%
%	LIKELIHOOD		LIKELIHOOD structure
%	BASIS			Full NxM basis matrix
%	PHI				Current relevant basis matrix
%	TARGETS			N-vector with target output values
%	USED			Relevant basis vector indices
%	ALPHA			Hyperparameters
%	BETA			Inverse noise variance (ignored in non-Gauss case)
%	MU				Current posterior mean (for non-Gauss)
%	BASIS_PHI		Cached value of BASIS'*PHI (for Gauss only)
%	BASIS_TARGETS	Cached value of BASIS'*TARGETS (for Gauss only)
%	OPTIONS			Standard OPTIONS structure (see SB2_USEROPTIONS)
%
% NOTES:
%
% This function computes the posterior, and other, statistics for the
% SPARSEBAYES algorithm in "long-hand" fashion. i.e. it does not use the
% more effecient "iterative" updates.
%
% It is required on every iteration (unless approximating) in the
% non-Gaussian case, and on iterations in the Gaussian case where the noise
% estimate (BETA) is updated.
%
% This function is intended for internal use by SPARSEBAYES only.
%

%
% Copyright 2009, Vector Anomaly Ltd
%
% This file is part of the SPARSEBAYES library for Matlab (V2.0).
%
% SPARSEBAYES is free software; you can redistribute it and/or modify it
% under the terms of the GNU General Public License as published by the Free
% Software Foundation; either version 2 of the License, or (at your option)
% any later version.
%
% SPARSEBAYES is distributed in the hope that it will be useful, but WITHOUT
% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
% FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
% more details.
%
% You should have received a copy of the GNU General Public License along
% with SPARSEBAYES in the accompanying file "licence.txt"; if not, write to
% the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
% MA 02110-1301 USA
%
% Contact the author: m a i l [at] m i k e t i p p i n g . c o m
%
function [SIGMA,Mu,S_in,Q_in,S_out,Q_out,Factor,logML,Gamma,...
	  betaBASIS_PHI,beta] = ...
    SB2_FullStatistics(LIKELIHOOD,BASIS,PHI,Targets,Used,Alpha,beta,...
		       Mu,BASIS_PHI,BASIS_Targets,OPTIONS)

% Mu is only required for non-Gauss
% BASIS_PHI and BASIS_Targets are only required for Gauss

% beta (a vector for non-Gauss) is returned only for non-Gauss

MAX_POSTMODE_ITS	= 25; % More than enough

[N M_full]	= size(BASIS);
M			= size(PHI,2);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% COMPUTE POSTERIOR

%
% Compute full relevant statistics
%
if LIKELIHOOD.InUse==LIKELIHOOD.Gaussian
  %
  % Posterior is analytic: use linear algebra here
  %
  % Compute posterior covariance SIGMA (inverse Hessian)
  % via Cholesky decomposition
  %
  U		= chol(PHI'*PHI*beta + diag(Alpha));
  Ui	= inv(U);
  SIGMA	= Ui * Ui';
  % Posterior mean Mu
  Mu	= (SIGMA * (PHI'*Targets)) * beta;
  % Data error and likelihood
  y				= PHI * Mu;
  e				= (Targets - y);
  ED			= e'*e;
  %
  dataLikely	= (N*log(beta) - beta*ED)/2;
  %
else
  %
  % Posterior must be approximated: find the posterior mode as the basis
  % for the Laplace approximation
  %
  [Mu U beta dataLikely] = ...
      SB2_PosteriorMode(LIKELIHOOD,PHI,Targets,Alpha,Mu, ...
						MAX_POSTMODE_ITS,OPTIONS);
  % Compute covariance approximation
  %
  Ui	= inv(U);
  SIGMA	= Ui * Ui';
  % Compute posterior-mean-based outputs and errors
  %
  switch LIKELIHOOD.InUse
   case LIKELIHOOD.Bernoulli,
    y	= SB2_Sigmoid(PHI * Mu);
   case LIKELIHOOD.Poisson
    y	= exp(PHI*Mu);
  end
  e		= (Targets-y);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% COMPUTE LOG MARGINAL LIKELIHOOD

logdetHOver2	= sum(log(diag(U)));
logML			= dataLikely - (Mu.^2)'*Alpha/2 + ...
    sum(log(Alpha))/2 - logdetHOver2;
% Well-determinedness factors
DiagC	= sum(Ui.^2,2);
Gamma	= 1 - Alpha.*DiagC;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% COMPUTE THE Q & S VALUES

%
% Q: "quality" factor - related to how well the basis function contributes
% to reducing the error
%
% S: "sparsity factor" - related to how orthogonal a given basis function
% is to the currently used set of basis functions
%
if LIKELIHOOD.InUse==LIKELIHOOD.Gaussian
  %
  % Gaussian case simple: beta a scalar
  %
  betaBASIS_PHI	= beta*BASIS_PHI;
  %
  % The S_in calculation exploits the fact that BASIS is normalised,
  %  i.e. sum(BASIS.^2,1)==ones(1,M_full)
  %
  S_in		= beta - sum((betaBASIS_PHI*Ui).^2,2);
  Q_in		= beta*(BASIS_Targets - BASIS_PHI*Mu);
else
  %
  % Non-Gaussian case: beta an N-vector
  %
  betaBASIS_PHI	= BASIS'*(PHI.* (beta*ones(1,M)));
  S_in			= (beta'*(BASIS.^2))' - sum((betaBASIS_PHI*Ui).^2,2);
  Q_in			= BASIS'*e;
end
%
S_out		= S_in;
Q_out		= Q_in;
%
% S,Q with that basis excluded: equations (23)
%
S_out(Used)	= (Alpha .* S_in(Used)) ./ (Alpha - S_in(Used));
Q_out(Used)	= (Alpha .* Q_in(Used)) ./ (Alpha - S_in(Used));
%
% Pre-compute the "relevance factor" for ongoing convenience
%
Factor		= (Q_out.*Q_out - S_out);


function right_impact_constraints(nlp, src, tar, bounds, varargin)

    plant = nlp.Plant;

    % fist call the class method
    plant.rigidImpactConstraint(nlp, src, tar, bounds, varargin{:});

    % Don't need time continuity constraint
    removeConstraint(nlp,'tContDomain');

    % the relabeling of joint coordiante is no longer valid
    removeConstraint(nlp,'xDiscreteMapRightImpact');

    % the configuration only depends on the relabeling matrix
    R = plant.R;
    x = plant.States.x;
    xn = plant.States.xn;
    x_diff = R*x-xn;
    x_map = SymFunction(['xDiscreteMap' plant.Name],x_diff(3:end),{x,xn});

    addNodeConstraint(nlp, x_map, {'x','xn'}, 'first', 0, 0, 'Linear');

end


function results = startSimulation(t0,tf,initialState,input_density,startParameters)
%	startSimulation  Starts the simulation of the Li-ion battery.
%
%   results = startSimulation(t0,tf,initialStates,input_density,startParameters)
%   Starts the simulation of the Li-ion Cell with LIONSIMBA.
%
%   Input:
%       - t0 : initial integration time
%       - tf : final integration time
%       - initialStates : structure containing data for initializing the
%       states of the battery.
%       - input_density  : Applied current/power density. If negative, the battery gets
%       discharged. If positive, the battery gets charged.
%       - startParameters : if provided, it defines the cell array containing the parameters
%       structures to be used in the simulation. Every single structure has to be obtained through the Parameters_init script.
%		If a cell array of N parameters structures is used, the simulator will perform a simulation with N cells in series.
%		If a cell of 1 parameters structure is used, then a single cell will be simulated.
%
%   Output:
%       - results : structure containing the solution of the dependent
%       variables. The stored results have as many rows as time instants
%       and columns as number of discrete volumes. If multiple cells are
%       simulated, the index i is used to access to the data of the i-th
%       cell.
%
%     results.Phis{i}:                      Solid Phase potential
%     results.Phie{i}:                      Liquid Phase potential
%     results.ce{i}:                        Electrolyte concentration
%     results.cs_surface{i}:                Electrode surface concentration
%     results.cs_average{i}:                Electrode average concentration
%     results.time{i}:                      Interpolated simulation time
%     results.int_internal_time{i}:         Integrator time steps
%     results.ionic_flux{i}:                Ionic flux
%     results.side_reaction_flux{i}:        Side reaction flux
%     results.SOC{i}:                       State of Charge
%     results.SOC_estimated{i}:             State of Charge estimate according to the
%                                           user-defined function
%     results.Voltage{i}:                   Cell voltage
%     results.Temperature{i}:               Cell Temperature
%     results.Qrev{i}:                      Reversible heat generation rate
%     results.Qrxn{i}:                      Reaction heat generation rate
%     results.Qohm{i}:                      Ohmic heat generation rate
%     results.film{i}:                      Side reaction film resistance
%     results.R_int{i}:                     Internal resistance
%     results.Up{i}:                        Cathode open circuit potential
%     results.Un{i}:                        Anode open circuit potential
%     results.etap{i}:                      Cathode overpotential
%     results.etan{i}:                      Anode overpotential
%     results.parameters{i}:                Parameters used for the simulation
%     results.JacobianFun:                  If evaluated, contains the
%                                           Jacobian matrix

%   This file is part of the LIONSIMBA Toolbox
%
%	Official web-site: 	http://sisdin.unipv.it/labsisdin/lionsimba.php
% 	Official GitHUB: 	https://github.com/lionsimbatoolbox/LIONSIMBA
%
%   LIONSIMBA: A Matlab framework based on a finite volume model suitable for Li-ion battery design, simulation, and control
%   Copyright (C) 2016-2018 :Marcello Torchio, Lalo Magni, Davide Raimondo,
%                            University of Pavia, 27100, Pavia, Italy
%                            Bhushan Gopaluni, Univ. of British Columbia,
%                            Vancouver, BC V6T 1Z3, Canada
%                            Richard D. Braatz,
%                            Massachusetts Institute of Technology,
%                            Cambridge, Massachusetts 02142, USA
%
%   Main code contributors to LIONSIMBA 2.0:
%                           Ian Campbell, Krishnakumar Gopalakrishnan,
%                           Imperial college London, London, UK
%
%   LIONSIMBA is a free Matlab-based software distributed with an MIT
%   license.

try
    test_lionsimba_folder
catch
    error('It seems that you did not add to the Matlab path the battery_model_files directory and the folders therein. Please fix this problem and restart the simulation.')
end

% Version of LIONSIMBA
version       = '2.1';

if(isempty(startParameters))
    % Load battery's parameters if not provided by the user
    param{1} = Parameters_init;
else
    % Use user provided parameters
    param = startParameters;
end

% Validate the input current density/power_density values
if(param{1}.OperatingMode==1 || param{1}.OperatingMode==2)
    if(~isreal(input_density) || isnan(input_density) || isinf(input_density) || isempty(input_density))
        error('The input current/power densities provided by user is complex valued or is NaN. Please check the values and restart simulation.')
    end
end

% Check for environmental tool availability
checkEnvironment(param,nargin);

% If enabled, print the header information
if(param{1}.PrintHeaderInfo==1)
    headerInfo(version)
end

% If everything is ok, let's start to simulate.
results = mainCore(t0,tf,initialState,input_density,param);
end

function results = mainCore(t0,tf,initialState,input_density,param)

% Store the original parameters structure in order to return it at the
% end of simulations.
param_original  = param;

% Get the total number of cells that have to be simulated.
n_cells         = length(param);

% Check if more cells are simulated when potentiostatic conditions are required.
if(param{1}.OperatingMode==3 && n_cells~=1)
    clc;
    error('!!!ERROR!!! -- Potentiostatic simulations are only possible with single cell packs -- !!!ERROR!!!')
end

% Check if the initial state structure is given
[Y0_existence,YP0_existence,Y0,YP0] = checkInitialStates(initialState);

% Switch among the selected operating modes for defining a suitable getCurrentDensity/getPowerDensity
% function. If multiple cells are being simulated, the variable current/power profile
% or its constant value are retreived from the first element of the
% parameters structures. This is valid because in a series string, all cells carry the same current.
switch(param{1}.OperatingMode)
    case 1
        param{1}.getCurrentDensity = @(t,t0,tf,x,param,extra)input_density;
    case 2
        param{1}.getPowerDensity = @(t,t0,tf,x,param,extra)input_density;
    case 3
        param{1}.getCurrentDensity  = @(t,t0,tf,x,param,extra)0; % dummy values, will be over-written by solver
        param{1}.getPowerDensity    = @(t,t0,tf,x,param,extra)0; % unsure of this?
    case 4
        param{1}.getCurrentDensity = param{1}.CurrentDensityFunction; % external, i.e. user-supplied function values from external files loaded in parameterisation file
    case 5
        param{1}.getPowerDensity = param{1}.PowerDensityFunction; % external, i.e. user-supplied function values from external files loaded in parameterisation file
    otherwise
        error('Operating mode not supported');
end

% Define absolute and relative tolerances. If more cells are required,
% these values are taken from the first parameters structure.
opt.AbsTol      = param{1}.AbsTol;
opt.RelTol      = param{1}.RelTol;

n_diff          = zeros(n_cells,1);
n_alg           = zeros(n_cells,1);
start_x_index   = 1;
start_xp_index  = 1;

% For each cell, allcoate memory to store external functions used to
% estimate the SOC.
SOC_estimate    = cell(n_cells,1);

% Perform several checks over the cells
for i=1:n_cells
    % Check the daeFormulation flag in case startSimulation is called
    if(param{i}.daeFormulation~=1)
        error(['Make sure that the daeFormulation flag is set to 1 for each cell parameters structure. Cell ', num2str(i),' does not respect this constraint.'])
    end

    % When Fick's law of diffusion is used, at least 10 discretization
    % points are required. Raise an error if this condition is not met.
    if((param{i}.Nr_p<10 || param{i}.Nr_n<10) && param{i}.SolidPhaseDiffusion==3)
        error('The number of discrete points for the particles must be at least 10 in both cathode and anode.')
    end
    % Check if the SOC estimation function handle have been set. In case that
    % the function handle has not been defined or it does not have the right
    % number of input arguments, then return empty values.
    if(isempty(param{i}.SOC_estimation_function) || nargin(param{i}.SOC_estimation_function)~=6)
        SOC_estimate{i} = @(a,b,c,d,e,f,g,h,i,j,k)[];
    else
        SOC_estimate{i} = @SOCestimation;
    end
    param{i}.Nsum      = param{i}.Np + param{i}.Ns + param{i}.Nn;
    param{i}.Nsum_nos  = param{i}.Np + param{i}.Nn;

    % Define the discretization steps.
    param{i}.deltax_al     = 1 / param{i}.Nal;
    param{i}.deltax_p      = 1 / param{i}.Np;
    param{i}.deltax_s      = 1 / param{i}.Ns;
    param{i}.deltax_n      = 1 / param{i}.Nn;
    param{i}.deltax_cu     = 1 / param{i}.Ncu;

    % Compute the indices used to store the positions of the differential
    % and algebraic variables.
    param{i} = computeVariablesIndices(param{i});

    % Preallocate the differentiation matrices used for the solid phase
    % potential. This can be done here because such matrices are considered
    % to be time invariant.
    param{i} = solidPhaseDifferentiationMatrices(param{i});

    % Initialize Param.I_density or Param.P_density, using the value of the current density/power density (set in lines of code a few lines above
    if param{1}.OperatingMode==1 || param{1}.OperatingMode==4
        param{i}.I_density = 0;%param{1}.getCurrentDensity(0,t0,tf,x,param,param{1}.extraData);
    elseif param{1}.OperatingMode==2 || param{1}.OperatingMode==5
        param{i}.P_density = 0;%param{1}.getPowerDensity(0,t0,tf,x,param,param{1}.extraData);
    else
        param{i}.I_density = 0; % this is a dummy value
        param{i}.P_density = 0;
    end

    % Preallocate the differentiation matrices used for the solid phase
    % diffusion in case of Fick's law.
    param{i} = solidPhaseDiffusionDifferentiationMatrices(param{i});

    % Get the number of unknowns for the differential states
    [~, ~, ~, ~, ~, n_diff(i)] =   differentialInitialConditions(param{i});
    % Get the number of unknowns for the algebraic states
    [~, n_alg(i), ~] = initialise_model(param{i});

    % Store the number of differential and algebraic variables for each cell.
    param{i}.ndiff = n_diff(i);
    param{i}.nalg  = n_alg(i);

    % The x_index variable will be used in the battery model file
    % for indexing purposes.
    param{i}.x_index    = (start_x_index:n_diff(i)+n_alg(i)+start_x_index-1);

    param{i}.xp_index   = (start_xp_index:n_diff(i)+start_xp_index-1);

    % Update the starting x_index value for the (possible) next cell
    start_x_index       = n_diff(i)+n_alg(i)+start_x_index;

    start_xp_index      = n_diff(i)+n_alg(i)+start_xp_index;
end

for i=1:n_cells
    if((Y0_existence==0) && (YP0_existence==0))
        % Get the initial conditions for the differential states of the i-th cell
        [cs_average_init, ce_init, T_init, film_init, Q_init] =   differentialInitialConditions(param{i});
        % Solve the algebraic equations to find a set of semi-consistent initial
        % conditions for the algebraic equations. This will help the DAE solver as a warm startup.
        init_point = initialise_model(param{i});

        % Build the initial values array for the integrator
        Yt0 = [ce_init;cs_average_init;T_init;film_init;Q_init;init_point];
        Y0  = [Y0;Yt0];
        YP0 = [YP0;zeros(size(Yt0))];
    end
end

if(n_cells==1)
    nc = ' cell';
else
    nc = ' cells';
end

% Empty the used arrays
ce_t            = cell(n_cells,1);
cs_bar_t        = cell(n_cells,1);
T_t             = cell(n_cells,1);
jflux_t         = cell(n_cells,1);
Phis_t          = cell(n_cells,1);
Phie_t          = cell(n_cells,1);
cs_star_t       = cell(n_cells,1);
t_tot           = cell(n_cells,1);
Qrev_t          = cell(n_cells,1);
Qrxn_t          = cell(n_cells,1);
Qohm_t          = cell(n_cells,1);
SOC_t           = cell(n_cells,1);
Voltage_t       = cell(n_cells,1);
SOC_estimated_t = cell(n_cells,1);
film_t          = cell(n_cells,1);
js_t            = cell(n_cells,1);
R_int_t         = cell(n_cells,1);
curr_density_t  = cell(n_cells,1);
Up_t            = cell(n_cells,1);
Un_t            = cell(n_cells,1);
etap_t          = cell(n_cells,1);
etan_t          = cell(n_cells,1);
dudtp_t         = cell(n_cells,1);
dudtn_t         = cell(n_cells,1);
Q_t             = cell(n_cells,1);
yp_original     = YP0';

% This flag is used to notify the reason of the simulation stop. If 0
% everything went well.
exit_reason     = 0;

% Define the structure to be passed to the residual function
ida_user_data.param  = param;
ida_user_data.t0     = t0;
ida_user_data.tf     = tf;

% Define algebraic and differential variables.
% id:1-> differential variables,
% id:0-> algebraic variables.
id = [];
constraint_vector=[];
for i=1:n_cells
    id                                              = [id;ones(n_diff(i),1);zeros(n_alg(i),1)];
    temp_constraint_vector                          = zeros(n_diff(i)+n_alg(i),1);
    temp_constraint_vector(param{i}.Phis_indices)   = 1; % enforce positivity on solid phase potential in all nodes
    constraint_vector                               = [constraint_vector;temp_constraint_vector];
end

JacFun = [];

% This statement checks if the user wants to make use of the Jacobian
% matrix and (if yes) it has been already provided or not as part of the parameters structure.
if(param{1}.UseJacobian==1 && isempty(param{1}.JacobianFunction))
    % If the user wants to make use of the Jacobian, but it was not
    % provided in the parameters structure, then evaluate a new Jacobian.
    disp('Evaluating the analytical form of the Jacobian matrix. Please wait...')
    % Import casadi framework
    import casadi.*
    % Define the symbolic variables.
    xsym    = SX.sym('x',[sum(n_diff)+sum(n_alg),1]);
    xpsym   = SX.sym('xp',[sum(n_diff)+sum(n_alg),1]);
    cj      = SX.sym('cj',1);

    % Get the model equations written in an implicit form in a symbolic way.
    [dx_tot, ~, ~] = batteryModel(0,xsym,xpsym,ida_user_data);

    % Evaluate the Jacobian matrix. (Please refer to the Sundials guide for
    % further information about the Jacobian structure).
    J = jacobian(dx_tot,xsym) + cj*jacobian(dx_tot,xpsym);

    % Define a function for the Jacobian evaluation for a given set of
    % differential and algebraic variables.
    JacFun = Function('fJ',{xsym,cj},{J});

    % Store the function into a structure such that IDA will use it for the
    % evaluation of the Jacobian matrix (see jacobianFunction.m in simulator_tools).
    ida_user_data.fJ = JacFun;

    % Define the options for Sundials
    options = IDASetOptions('RelTol', opt.RelTol,...
        'AbsTol'        , opt.AbsTol,...
        'MaxNumSteps'   , 1500,...
        'VariableTypes' , id,...
        'UserData'      , ida_user_data,...
        'JacobianFn'    , @jacobianFunction,...
        'ConstraintTypes', constraint_vector);

elseif(param{1}.UseJacobian==1 && ~isempty(param{1}.JacobianFunction))
    % If the Jacobian needs to be used and it has also been provided in the
    % parameters structure, use it directly.

    disp('Analytical function of the Jacobian matrix provided by the user.')
    % If the Jacobian has been provided from the user, use it.
    JacFun = param{1}.JacobianFunction;

    % Pass this function pointer to the routine that IDA will call for the
    % evaluation of the Jacobian values.
    ida_user_data.fJ = JacFun;

    % Define the options for Sundials
    options = IDASetOptions('RelTol', opt.RelTol,...
        'AbsTol'        , opt.AbsTol,...
        'MaxNumSteps'   , 1500,...
        'VariableTypes' , id,...
        'UserData'      , ida_user_data,...
        'JacobianFn'    , @jacobianFunction,...
        'ConstraintTypes', constraint_vector);
else
    % In this case the user does not want to make use of the Jacobian
    % matrix. A numerical approximation will be calculated instead.

    % Define the options for Sundials
    options = IDASetOptions('RelTol', opt.RelTol,...
        'AbsTol'        , opt.AbsTol,...
        'MaxNumSteps'   , 1500,...
        'VariableTypes' , id,...
        'UserData'      , ida_user_data,...
        'ConstraintTypes', constraint_vector);
end

% Enable the capability to deal with piecewise inputs only when custom
% current profiles are used
if(param{1}.OperatingMode == 4)
    % Add to IDA options structure the pointer to the function used to
    % identify the presence of events (in this particular case
    % discontinuities in the applied input currents)
    options.RootsFn     = @rootFinder;
    options.NumRoots    = 1;
end

% Initialise solver
IDAInit(@batteryModel,t0,Y0,YP0,options);

if param{i}.Scope == 1
    disp(['Finding a set of consistent ICs for ',num2str(n_cells),nc,' battery pack. Please wait..'])
end
% Find consistent initial conditions
[~, yy, ~] = IDACalcIC(t0+10,'FindAlgebraic');

% Init the starting integration time
t = t0;

% Store in the results the initial states values.
y = yy';

% Store the total number of complete set of states
y_tot = y;

[ce_t,cs_bar_t,T_t,jflux_t,Phis_t, Phie_t, cs_star_t, SOC_t, film_t, js_t,Up_t,Un_t,R_int_t,curr_density_t,Voltage_t,SOC_estimated_t,Qrev_t,Qrxn_t,Qohm_t,Q_t,~,dudtp_t, dudtn_t,t_tot] =...
    storeSimulationResults(n_cells,ce_t,cs_bar_t,T_t,jflux_t,Phis_t, Phie_t, cs_star_t, SOC_t, film_t, js_t,curr_density_t,Voltage_t,SOC_estimated_t,Up_t,Un_t,R_int_t,Qrev_t,Qrxn_t,Qohm_t,Q_t,dudtp_t, dudtn_t, t_tot, y, t,SOC_estimate,t0,tf, param);

sim_time = 0;  % Simulation time (i.e. wall) for reporting purposes only. Not used in controlling solver or time-loop
% Loop until the integration time reaches tf.
while(t<tf)

    % Check if simulation stop conditions are met or not
    exit_reason = checkSimulationStopConditions(n_cells, Phis_t, cs_bar_t, T_t, param);
    % If a stop condition is reached, stop the simulation
    if(exit_reason~=0)
        break;
    end

    %% Solve the set of DAEs
    % The solver IDA is used to solve the resulting set of DAEs. Please
    % refer to IDA manual for more information about syntax and its usage.
    tic
    [status, t, y]   = IDASolve(tf,'OneStep');

    % If status > 0 it means that roots have been found during the
    % resolution of the equations
    if(status>0)
        % In case of custom current profiles, the roots are determined by
        % the presence of a discontinuity in the applied current density
        if(param{1}.OperatingMode==4)
            % Define a new time instant at which re-initialize the solver
            % using the last known values of the states
            t = t*(1+1e-5);
            IDAReInit(t,y,0*y,options);

            % Find consistent initial conditions starting from the new
            % point and keep on integrating
            [~, y, yp] = IDACalcIC(t+10,'FindAlgebraic');
        else
            error(e.message);
        end
    end
    sim_time    = sim_time+toc;
    y           = y';
        % Store the matrix containing all the states at all the integration
    % time steps.
    y_tot       = [y_tot;y];
    if(status==0)
        % Store derivative info at each time step
        yp_original = [yp_original;IDAGet('DerivSolution',t,1)'];
    else
        yp_original = [yp_original;yp'];
    end

    [ce_t,cs_bar_t,T_t,jflux_t,Phis_t, Phie_t, cs_star_t, SOC_t, film_t, js_t,Up_t,Un_t,R_int_t,curr_density_t,Voltage_t,SOC_estimated_t,Qrev_t,Qrxn_t,Qohm_t,Q_t,tot_voltage,dudtp_t, dudtn_t,t_tot] =...
        storeSimulationResults(n_cells,ce_t,cs_bar_t,T_t,jflux_t,Phis_t, Phie_t, cs_star_t, SOC_t, film_t, js_t,curr_density_t,Voltage_t,SOC_estimated_t,Up_t,Un_t,R_int_t,Qrev_t,Qrxn_t,Qohm_t,Q_t,dudtp_t, dudtn_t, t_tot, y, t,SOC_estimate,t0,tf, param);

    % If the output scope is active, show additional information to the user
    if(param{1}.Scope==1)
        if(n_cells==1)
            temperature     = T_t{1}(end,end);
            % If Fick's law of diffusion is used, before to evaluate the
            % SOC, it is necessary to compute the average solid
            % concentration in each particle.
            Sout = internalSOCestimate(cs_bar_t,param,1);
            clc
            fprintf(['No. of cells in the pack \t',num2str(n_cells),'\n']);
            fprintf(['Time \t\t\t\t\t',num2str(t),' s\n']);
            % If potentiostatic mode is running, applied current comes as
            % solution of DAEs. Otherwise it is provided by the user.
            if(param{1}.OperatingMode==1) || (param{1}.OperatingMode==4)
                fprintf(['Applied current density \t\t',num2str(y(end)),' A/m^2\n']);
            elseif (param{1}.OperatingMode==2) || (param{1}.OperatingMode==5)
                fprintf(['Applied power density \t\t',num2str(param{1}.getPowerDensity(t,t0,tf,param{1}.extraData)),' W/m^2\n']);
            end

			switch param{1}.edge_values
				% Linear interpolation
				case 2
					output_voltage = (1.5*Phis_t{1}(end,1)-0.5*Phis_t{1}(end,2)) - (1.5*Phis_t{1}(end,end) - 0.5*Phis_t{1}(end,end-1));
				% Value at the centroid
				otherwise
					output_voltage = Phis_t{1}(end,1) - Phis_t{1}(end,end);
			end
            fprintf(['Voltage \t\t\t\t',          num2str(output_voltage),   ' V\n']);
            fprintf(['Temperature \t\t\t',        num2str(temperature),                           ' K\n']);
            fprintf(['SOC \t\t\t\t\t',            num2str(Sout),                                  ' %% \n']);
            fprintf(['Cutoff Voltage \t\t\t',     num2str(param{1}.CutoffVoltage),                ' V\n']);
            fprintf(['Cutover Voltage \t\t',      num2str(param{1}.CutoverVoltage),               ' V\n']);
            fprintf(['Internal Resistance \t',    num2str(R_int_t{1}(end)),                       ' Ohm m^2\n']);
            fprintf(['Absolute tolerance \t\t',   num2str(param{1}.AbsTol),                       '\n']);
            fprintf(['Relative tolerance \t\t',   num2str(param{1}.RelTol),                       '\n']);
            fprintf(['Initial int. time \t\t',    num2str(t0),                                    ' s\n']);
            fprintf(['Final int. time \t\t',      num2str(tf),                                    ' s\n']);
            fprintf(['N. of unknowns \t\t\t',     num2str(length(y)),                             ' \n']);
        else
            clc
            fprintf(['No. of cells in the pack \t',num2str(n_cells),'\n']);
            fprintf(['Time \t\t\t\t\t',num2str(t),' s\n']);
            % If potentiostatic mode is running, applied current comes as
            % solution of DAEs. Otherwise it is provided by the user.
            if(param{1}.OperatingMode==1) || (param{1}.OperatingMode==4)
                fprintf(['Applied current density \t\t',num2str(y(end)),' A/m^2\n']);
            elseif (param{1}.OperatingMode==2) || (param{1}.OperatingMode==5)
                fprintf(['Applied power density \t\t',num2str(param{1}.getPowerDensity(t,t0,tf,param{1}.extraData)),' W/m^2\n']);
            end

            fprintf(['Voltage \t\t\t\t',          num2str(tot_voltage),       ' V\n']);
            fprintf(['Absolute tolerance \t\t',   num2str(param{1}.AbsTol),   ' \n']);
            fprintf(['Relative tolerance \t\t',   num2str(param{1}.RelTol),   ' \n']);
            fprintf(['Initial int. time \t\t',    num2str(t0),                ' s\n']);
            fprintf(['Final int. time \t\t',      num2str(tf),                ' s\n']);
            fprintf(['N. of unknowns \t\t\t',     num2str(length(y)),         ' \n']);
        end
    end
end

disp(['Elasped time: ',num2str(sim_time),' s']);

% Interpolate for fixed time step values
t_tot_original = t_tot;

% Build the time vector used for interpolation
time_vector = (t0:param{i}.sim_datalog_interval:tf);

% In case of the simulation has stopped before the final time set by the
% user, change the tf variable in order to interpolate only available values.

if(t<tf)
    % Set final time equal to the last integration step
    tf          = t;
    % Redefine the time vector used for interpolation
    time_vector = (t0:param{i}.sim_datalog_interval:tf);
end

% If at least one integration step has been done, retreive the first order
% time derivative information. Otherwise use the initial data.
if(time_vector(end)>t0)
    % Retreive derivative information at the last time step
    yp          = interp1(t_tot{i},yp_original,time_vector(end))';
else
    % If the integration step carried out by SUNDIALS is less than the
    % parametrized step size, then return the initial data as set of initial states.
    yp          = YP0;
    yp_original = YP0;
end

% Free memory allocated by IDA solver
IDAFree

% These variables will be used to store the original results of the integration process.
Phis_t_o            = cell(n_cells,1);
Phie_t_o            = cell(n_cells,1);
ce_t_o              = cell(n_cells,1);
cs_star_t_o         = cell(n_cells,1);
cs_average_t_o      = cell(n_cells,1);
jflux_t_o           = cell(n_cells,1);
SOC_t_o             = cell(n_cells,1);
T_t_o               = cell(n_cells,1);
Voltage_t_o         = cell(n_cells,1);
SOC_estimated_t_o   = cell(n_cells,1);
film_t_o            = cell(n_cells,1);
js_t_o              = cell(n_cells,1);
R_int_t_o           = cell(n_cells,1);
Up_t_o              = cell(n_cells,1);
Un_t_o              = cell(n_cells,1);
dudtp_t_o           = cell(n_cells,1);
dudtn_t_o           = cell(n_cells,1);
Qrev_t_o            = cell(n_cells,1);
Qrxn_t_o            = cell(n_cells,1);
Qohm_t_o            = cell(n_cells,1);
etap_t_o            = cell(n_cells,1);
etan_t_o            = cell(n_cells,1);
app_current_t_o     = cell(n_cells,1);
Q_t_o               = cell(n_cells,1);
y                   = [];
y_original          = [];
for i=1:n_cells
    % Save the overpotentials
    etap_t{i} = Phis_t{i}(:,1:param{i}.Np)-Phie_t{i}(:,1:param{i}.Np)-Up_t{i};
    if(param{i}.EnableAgeing==1)
        etan_t{i} = Phis_t{i}(:,param{i}.Np+1:end)-Phie_t{i}(:,param{i}.Np+param{i}.Ns+1:end)-Un_t{i} - param{1}.F*jflux_t{i}(:,param{i}.Np+1:end).*(param{i}.R_SEI+film_t{i}./param{i}.k_n_aging);
    else
        etan_t{i} = Phis_t{i}(:,param{i}.Np+1:end)-Phie_t{i}(:,param{i}.Np+param{i}.Ns+1:end)-Un_t{i};
    end
    if(param{i}.sim_datalog_interval>0)
        % Store original results
        Phis_t_o{i}            = Phis_t{i};
        Phie_t_o{i}            = Phie_t{i};
        ce_t_o{i}              = ce_t{i};
        cs_star_t_o{i}         = cs_star_t{i};
        cs_average_t_o{i}      = cs_bar_t{i};
        jflux_t_o{i}           = jflux_t{i};
        SOC_t_o{i}             = SOC_t{i};
        T_t_o{i}               = T_t{i};
        Voltage_t_o{i}         = Voltage_t{i};
        SOC_estimated_t_o{i}   = SOC_estimated_t{i};
        film_t_o{i}            = film_t{i};
        js_t_o{i}              = js_t{i};
        R_int_t_o{i}           = R_int_t{i};
        app_current_t_o{i}     = curr_density_t{i};
        Up_t_o{i}              = Up_t{i};
        Un_t_o{i}              = Un_t{i};
        dudtp_t_o{i}           = dudtp_t{i};
        dudtn_t_o{i}           = dudtn_t{i};
        etap_t_o{i}            = etap_t{i};
        etan_t_o{i}            = etan_t{i};
        Qrev_t_o{i}            = Qrev_t{i};
        Qrxn_t_o{i}            = Qrxn_t{i};
        Qohm_t_o{i}            = Qohm_t{i};
        Q_t_o{i}               = Q_t{i};

        if(time_vector(end)>t0)
            % Interpolate the results
            Phis_t{i}          = interp1(t_tot{i},Phis_t{i},time_vector');
            Phie_t{i}          = interp1(t_tot{i},Phie_t{i},time_vector');
            ce_t{i}            = interp1(t_tot{i},ce_t{i},time_vector');
            cs_star_t{i}       = interp1(t_tot{i},cs_star_t{i},time_vector');
            cs_bar_t{i}        = interp1(t_tot{i},cs_bar_t{i},time_vector');
            jflux_t{i}         = interp1(t_tot{i},jflux_t{i},time_vector');
            SOC_t{i}           = interp1(t_tot{i},SOC_t{i},time_vector');
            SOC_estimated_t{i} = interp1(t_tot{i},SOC_estimated_t{i},time_vector');
            Voltage_t{i}       = interp1(t_tot{i},Voltage_t{i},time_vector');
            film_t{i}          = interp1(t_tot{i},film_t{i},time_vector');
            js_t{i}            = interp1(t_tot{i},js_t{i},time_vector');
            R_int_t{i}         = interp1(t_tot{i},R_int_t{i},time_vector');
            T_t{i}             = interp1(t_tot{i},T_t{i},time_vector');
            curr_density_t{i}  = interp1(t_tot{i},curr_density_t{i},time_vector');
            Up_t{i}            = interp1(t_tot{i},Up_t{i},time_vector');
            Un_t{i}            = interp1(t_tot{i},Un_t{i},time_vector');
            Qrev_t{i}          = interp1(t_tot{i},Qrev_t{i},time_vector');
            Qrxn_t{i}          = interp1(t_tot{i},Qrxn_t{i},time_vector');
            Qohm_t{i}          = interp1(t_tot{i},Qohm_t{i},time_vector');
            etap_t{i}          = interp1(t_tot{i},etap_t{i},time_vector');
            etan_t{i}          = interp1(t_tot{i},etan_t{i},time_vector');
            dudtp_t{i}         = interp1(t_tot{i},dudtp_t{i},time_vector');
            dudtn_t{i}         = interp1(t_tot{i},dudtn_t{i},time_vector');
            Q_t{i}             = interp1(t_tot{i},Q_t{i},time_vector');
            t_tot{i}           = time_vector';
        end
    end
    % Store results. If integration steps are enabled, store the interpolated data.
    results.Phis{i}                        = Phis_t{i};
    results.Phie{i}                        = Phie_t{i};
    results.ce{i}                          = ce_t{i};
    results.cs_surface{i}                  = cs_star_t{i};
    results.cs_average{i}                  = cs_bar_t{i};
    results.time{i}                        = t_tot{i};
    results.int_internal_time{i}           = t_tot_original{i};
    results.ionic_flux{i}                  = jflux_t{i};
    results.side_reaction_flux{i}          = js_t{i};
    results.SOC{i}                         = SOC_t{i};
    results.SOC_estimated{i}               = SOC_estimated_t{i};
    results.Voltage{i}                     = Voltage_t{i};
    results.Temperature{i}                 = T_t{i};
    results.Qrev{i}                        = Qrev_t{i};
    results.Qrxn{i}                        = Qrxn_t{i};
    results.Qohm{i}                        = Qohm_t{i};
    results.film{i}                        = film_t{i};
    results.R_int{i}                       = R_int_t{i};
    results.Up{i}                          = Up_t{i};
    results.Un{i}                          = Un_t{i};
    results.etap{i}                        = etap_t{i};
    results.etan{i}                        = etan_t{i};
    results.dudtp{i}                       = dudtp_t{i};
    results.dudtn{i}                       = dudtn_t{i};
    results.Q{i}                           = Q_t{i};
    results.parameters{i}                  = param{i};
    results.JacobianFun                    = JacFun;

    % Store original data.
    results.original.Phis{i}               = Phis_t_o{i};
    results.original.Phie{i}               = Phie_t_o{i};
    results.original.ce{i}                 = ce_t_o{i};
    results.original.cs_surface{i}         = cs_star_t_o{i};
    results.original.cs_average{i}         = cs_average_t_o{i};
    results.original.ionic_flux{i}         = jflux_t_o{i};
    results.original.side_reaction_flux{i} = js_t_o{i};
    results.original.SOC{i}                = SOC_t_o{i};
    results.original.SOC_estimated{i}      = SOC_estimated_t_o{i};
    results.original.Voltage{i}            = Voltage_t_o{i};
    results.original.Temperature{i}        = T_t_o{i};
    results.original.film{i}               = film_t_o{i};
    results.original.R_int{i}              = R_int_t_o{i};
    results.original.Up{i}                 = Up_t_o{i};
    results.original.Un{i}                 = Un_t_o{i};
    results.original.etap{i}               = etap_t_o{i};
    results.original.etan{i}               = etan_t_o{i};
    results.original.Q{i}                  = Q_t_o{i};
    results.original.parameters{i}         = param_original{i};

    % Store initial states data
    y           = [y;ce_t{i}(end,:)';cs_bar_t{i}(end,:)';T_t{i}(end,:)';film_t{i}(end,:)';Q_t{i}(end,:)';jflux_t{i}(end,:)';Phis_t{i}(end,:)';Phie_t{i}(end,:)';js_t{i}(end,:)';curr_density_t{i}(end)];
    % Store initial states original data
    y_original  = [y_original;ce_t_o{i}(end,:)';cs_average_t_o{i}(end,:)';T_t_o{i}(end,:)';film_t_o{i}(end,:)';Q_t_o{i}(end,:)';jflux_t_o{i}(end,:)';Phis_t_o{i}(end,:)';Phie_t_o{i}(end,:)';js_t_o{i}(end,:)';app_current_t_o{i}(end)];
end

% Store the array of last results
results.Y                           = y;
results.YP                          = yp;

results.original.Y                  = y_tot;
results.original.YP                 = yp_original;

results.original.initialState.Y     = y_original;
results.original.initialState.YP    = yp_original(end,:);

results.initialState.Y              = y;
results.initialState.YP             = yp;

% Store simulation time
results.simulation_time             = sim_time;

% Exit reason
results.exit_reason                 = exit_reason;

% Check the operating mode and store the results accordingly.
if(param{1}.OperatingMode==2)
    results.power_density          = param{1}.P_density * ones(size(t_tot{1},1),1);
    results.original.power_density = param{1}.P_density * ones(size(t_tot_original{1},1),1);
    % Variable current profile
elseif(param{1}.OperatingMode==5)
    results.power_density          = param{1}.getPowerDensity(t_tot{1},t0,tf,param{1}.extraData);
    results.original.power_density = param{1}.getPowerDensity(t_tot_original{1},t0,tf,param{1}.extraData);
end

results.curr_density          = curr_density_t{1};
results.original.curr_density = app_current_t_o{1};
end

function estimate = SOCestimation(t,t0,tf,param,ce_t,cs_bar_t,cs_star_t,Phie_t,Phis_t,jflux_t,T_t)
% Build the states struct which will be passed to the function
states.ce           = ce_t(end,:);
states.cs_average   = cs_bar_t(end,:);
states.cs_surface   = cs_star_t(end,:);
states.Phie         = Phie_t(end,:);
states.Phis         = Phis_t(end,:);
states.ionic_flux   = jflux_t(end,:);
states.Temperature  = T_t(end,:);
% Call the estimation procedure
estimate = param.SOC_estimation_function(t,t0,tf,states,param.extraData,param);
end


classdef BIF < handle
    %BIF  Implementation of bio-inspired features (BIF)
    %
    % An implementation of the bio-inspired features (BIF) approach for
    % computing image descriptors, applicable for human age estimation. For
    % more details we refer to [BIF09] and [BIF2015].
    %
    % ## References
    % [BIF09]:
    % > Guo, Guodong, et al. "Human age estimation using bio-inspired
    % > features". Computer Vision and Pattern Recognition, 2009. CVPR 2009.
    %
    % [BIF2015]:
    % > Spizhevoi, A. S., and A. V. Bovyrin. "Estimating human age using
    % > bio-inspired features and the ranking method." Pattern Recognition and
    % > Image Analysis 25.3 (2015): 547-552.
    %
    % See also: cv.BIF.BIF, cv.BIF.compute
    %

    properties (SetAccess = private)
        % Object ID
        id
    end

    properties (Dependent, SetAccess = private)
        % The number of filter bands used for computing BIF
        NumBands
        % The number of image rotations
        NumRotations
    end

    %% Constructor/destructor
    methods
        function this = BIF(varargin)
            %BIF  Constructor
            %
            %     obj = cv.BIF()
            %     obj = cv.BIF('OptionName',optionValue, ...)
            %
            % ## Options
            % * __NumBands__ The number of filter bands (`<= 8`) used for
            %   computing BIF. default 8
            % * __NumRotations__ The number of image rotations for computing
            %   BIF. default 12
            %
            % See also: cv.BIF.compute
            %
            this.id = BIF_(0, 'new', varargin{:});
        end

        function delete(this)
            %DELETE  Destructor
            %
            %     obj.delete()
            %
            % See also: cv.BIF
            %
            if isempty(this.id), return; end
            BIF_(this.id, 'delete');
        end
    end

    %% BIF
    methods
        function features = compute(this, img)
            %COMPUTE  Computes features sby input image
            %
            %     features = model.compute(img)
            %
            % ## Input
            % * __image__ Input image (1-channel `single`).
            %
            % ## Output
            % * __features__ Feature vector (`single` type).
            %
            % See also: cv.BIF.BIF
            %
            features = BIF_(this.id, 'compute', img);
        end
    end

    %% Algorithm
    methods (Hidden)
        function clear(this)
            %CLEAR  Clears the algorithm state
            %
            %     obj.clear()
            %
            % See also: cv.BIF.empty, cv.BIF.load
            %
            BIF_(this.id, 'clear');
        end

        function b = empty(this)
            %EMPTY  Checks if detector object is empty.
            %
            %     b = obj.empty()
            %
            % ## Output
            % * __b__ Returns true if the detector object is empty (e.g in the
            %   very beginning or after unsuccessful read).
            %
            % See also: cv.BIF.clear, cv.BIF.load
            %
            b = BIF_(this.id, 'empty');
        end

        function save(this, filename)
            %SAVE  Saves the algorithm parameters to a file
            %
            %     obj.save(filename)
            %
            % ## Input
            % * __filename__ Name of the file to save to.
            %
            % This method stores the algorithm parameters in the specified
            % XML or YAML file.
            %
            % See also: cv.BIF.load
            %
            BIF_(this.id, 'save', filename);
        end

        function load(this, fname_or_str, varargin)
            %LOAD  Loads algorithm from a file or a string
            %
            %     obj.load(fname)
            %     obj.load(str, 'FromString',true)
            %     obj.load(..., 'OptionName',optionValue, ...)
            %
            % ## Input
            % * __fname__ Name of the file to read.
            % * __str__ String containing the serialized model you want to
            %   load.
            %
            % ## Options
            % * __ObjName__ The optional name of the node to read (if empty,
            %   the first top-level node will be used). default empty
            % * __FromString__ Logical flag to indicate whether the input is a
            %   filename or a string containing the serialized model.
            %   default false
            %
            % This method reads algorithm parameters from the specified XML or
            % YAML file (either from disk or serialized string). The previous
            % algorithm state is discarded.
            %
            % See also: cv.BIF.save
            %
            BIF_(this.id, 'load', fname_or_str, varargin{:});
        end

        function name = getDefaultName(this)
            %GETDEFAULTNAME  Returns the algorithm string identifier
            %
            %     name = obj.getDefaultName()
            %
            % ## Output
            % * __name__ This string is used as top level XML/YML node tag
            %   when the object is saved to a file or string.
            %
            % See also: cv.BIF.save, cv.BIF.load
            %
            name = BIF_(this.id, 'getDefaultName');
        end
    end

    %% Getters/Setters
    methods
        function value = get.NumBands(this)
            value = BIF_(this.id, 'get', 'NumBands');
        end

        function value = get.NumRotations(this)
            value = BIF_(this.id, 'get', 'NumRotations');
        end
    end

end


function I = ni(I)

I = double(I);

I = I - min(I(:));

I = I./(max(I(:))+eps);

end


function [var, cut, children, catsplit] = tree_getParameters(dt)

if iscell(dt.cut)
    var = dt.var;
    children = dt.children;

    cut = zeros(size(dt.cut));
    catind = var<0;
    ncat = sum(catind);
    catsplit = cell(ncat, 2);
    ncat = 0;
    for k = 1:numel(cut)
        if var(k)>=0
            cut(k) = dt.cut{k};
        else
            ncat = ncat+1;
            cut(k) = ncat;
            catsplit(ncat, :) = dt.cut{k};
        end
    end
else
    var = dt.var;
    cut = dt.cut;
    children = dt.children;
    catsplit = dt.catsplit;
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Spherical Harmonic Modeling and Analysis Toolkit (SPHARM-MAT) is a 3D
% shape modeling and analysis toolkit.
% It is a software package developed at Shenlab in Center for Neuroimaging,
% Indiana University (SpharmMat@gmail.com, http://www.iupui.edu/~shenlab/)
% It is available to the scientific community as copyright freeware
% under the terms of the GNU General Public Licence.
%
% Copyright 2009, 2010, ShenLab, Center for Neuroimaging, Indiana University
%
% This file is part of SPHARM-MAT.
%
% SPHARM-MAT is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% SPHARM-MAT is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with SPHARM-MAT. If not, see <http://www.gnu.org/licenses/>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

disp('Ex0702: Average Objects')

SpharmMatDir = '../';
CodeDir = [SpharmMatDir 'code'];
addpath(CodeDir);

DataFolder = 'data';

%% (1) Input data directory
InDataDir = [SpharmMatDir DataFolder '/Ex0702/hip05_prm'];

%% (2) List input file names
inFiles = dir([InDataDir '/*_prm.mat']); inNames={};
for i=1:length(inFiles)
    inNames{end+1} = [InDataDir '/' inFiles(i).name];
end

%% (3) Display input objects (optional)
    %Available values for Space- 'object';'param';'both'
dispConfs.Space = 'object';
    % Available values for Mesh- 'orig';'quad32';'quad64';'quad128';'quad256';'quad512';'icosa1';'icosa2'; ...
    %    'icosa3';'icosa4';'icosa5';'icosa6'
dispConfs.Mesh = 'orig';
    % Available values for Shape- 'solid';'mesh';'both'
dispConfs.Shade = 'both';
    % Available values for Overlay- 'none';'adc_paramap'
dispConfs.Overlay = 'none';
    % Available values for Export- 'screen';'png';'both'
dispConfs.Export = 'png';
dispConfs.Degree = [];
dispConfs.Template = '';

SpharmMatUtilDisplayObjs(dispConfs, inNames, CodeDir);

%% (4) Average objects
confs.OutputName='atlas.mat';
confs.OutDirectory = [SpharmMatDir DataFolder '/Ex0702/hip06_reg'];

if ~exist(confs.OutDirectory,'dir')
    mkdir(confs.OutDirectory);
end

SpharmMatUtilAverageObjs(confs, inNames);

%% (5) Display output objects (optional)
    %Available values for Space- 'object';'param';'both'
dispConfs.Space = 'object';
    % Available values for Mesh- 'orig';'quad32';'quad64';'quad128';'quad256';'quad512';'icosa1';'icosa2'; ...
    %    'icosa3';'icosa4';'icosa5';'icosa6'
dispConfs.Mesh = 'icosa4';
    % Available values for Shape- 'solid';'mesh';'both'
dispConfs.Shade = 'both';
    % Available values for Overlay- 'none';'adc_paramap'
dispConfs.Overlay = 'adc_paramap';
    % Available values for Export- 'screen';'png';'both'
dispConfs.Export = 'png';
dispConfs.Degree = [];
dispConfs.Template = '';

inFiles = dir([confs.OutDirectory '/*_als.mat']); inNames={};
for i=1:length(inFiles)
    inNames{end+1} = [confs.OutDirectory '/' inFiles(i).name];
end

SpharmMatUtilDisplayObjs(dispConfs, inNames, CodeDir);

inNames={}; inNames{1} = [confs.OutDirectory '/atlas.mat'];

SpharmMatUtilDisplayObjs(dispConfs, inNames, CodeDir);

rmpath(CodeDir);

clear;


function st = type( x, usegeo )

df  = x.dof_;
if nargin > 1 && usegeo,
    geo = any( df < 0 );
else
    geo = false;
end
df  = abs( df );
s   = x.size_;
len = prod( s );

if len == 1,
    isstruct = 0;
    nzs = len;
    st = 'scalar';
else
    isstruct = ~isempty( df ) & ( sum( df ) < ( 2 - isreal( x.basis_ ) ) * len );
    if ~isstruct,
        nzs = nnz( any( x.basis_, 1 ) );
    end
    nd = length( s );
    st = sprintf( '%dx', s );
    st = st( 1 : end - 1 );
    if nd > 2,
        st = [ st, ' array' ];
    elseif any( s == 1 ),
        st = [ st, ' vector' ];
    else
        st = [ st, ' matrix' ];
    end
end

if isstruct,
    st = sprintf( '%s, %d d.o.f.', st, df );
elseif nzs < len,
    if nzs > 1,
        st = sprintf( '%s, %d nonzeros', st, nzs );
    elseif nzs == 1,
        st = sprintf( '%s, 1 nonzero', st );
    end
end
if geo,
    st = [ st, ', geometric' ];
end

% 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:         PlotIt(FVr_temp,iter,S_struct)
% Author:           Rainer Storn
% Description:      PlotIt can be used for special purpose plots
%                   used in deopt.m.
% Parameters:       FVr_temp     (I)    Paramter vector
%                   iter         (I)    counter for optimization iterations
%                   S_Struct     (I)    Contains a variety of parameters.
%                                       For details see Rundeopt.m
% Return value:     -
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function PlotIt(FVr_temp,iter,S_struct)

figure(1);
plot(S_struct.FM_pop(:,1),S_struct.FM_pop(:,2),'ro');
xlabel('x');
ylabel('y');
xlim([-10;10]);
ylim([-10;10]);
grid on;

ff = vertcat(S_struct.S_val.FVr_oa);
f1 = ff(:,1);
f2 = ff(:,2);
figure(2);
plot(f1,f2,'bo');
xlabel('f_1');
ylabel('f_2');
xlim([-100;100]);
ylim([-100;100]);
grid on;

drawnow;
pause(0.5); %wait for one second to allow convenient viewing
return


function writeWIRE(filename, V,E)
  % WRITEWIRE writes an .wire file with vertex/edge information (see PyMesh
  % documentation).
  %
  % writeWIRE(filename,V,F,UV,N)
  %
  % Input:
  %  filename  path to .wire file
  %  V  #V by 3 list of vertices
  %  E  #E by 2 list of edge indices into E
  %

  f = fopen( filename, 'w' );
  if size(V,2) == 2
    warning('Appending 0s as z-coordinate');
    V(:,end+1:3) = 0;
  else
    assert(size(V,2) == 3);
  end

  fprintf( f, 'v %0.17g %0.17g %0.17g\n', V');
  assert(size(E,2) == 2,'Edge list should have two columns');
  fprintf( f, 'l %d %d\n', E');

  fclose(f);
end


function test_suite = test_cosmo_fmri_dataset
% tests for cosmo_fmri_dataset
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions=localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_init()
    ds=cosmo_synthetic_dataset();

    assertTrue(numel(setxor(fieldnames(ds),...
               {'samples','a','sa','fa'}))==0);

function test_nifti()
    ni=generate_test_nifti_struct();
    g=cosmo_synthetic_dataset('size','normal');

    tmpfn='__tmp1_.nii';
    cleaner=onCleanup(@()delete(tmpfn));
    save_nii(ni,tmpfn);
    ds=cosmo_fmri_dataset(tmpfn,'targets',g.sa.targets,...
                                'chunks',g.sa.chunks);

    assertTrue(numel(setxor(fieldnames(ds),...
               fieldnames(g)))==0);

    assertElementsAlmostEqual(ds.samples, g.samples,'relative',1e-6);
    assertEqual(ds.sa.targets, g.sa.targets)
    assertEqual(ds.sa.chunks, g.sa.chunks)

function test_io()
    ds=cosmo_synthetic_dataset('size','normal');
    tmpfn='__tmp2_.nii';
    cleaner=onCleanup(@()delete(tmpfn));

    cosmo_map2fmri(ds,tmpfn);

    es=cosmo_fmri_dataset(tmpfn);
    assertElementsAlmostEqual(ds.samples,es.samples,'relative',1e-6);
    assertEqual(ds.a.fdim,es.a.fdim);

function ni=generate_test_nifti_struct()
% Generates a struct that behaves like a NIFTI struct
    ni=struct();

    ds=cosmo_synthetic_dataset('size','normal');
    ni.img=shiftdim(cosmo_unflatten(ds),1);

    hdr=struct();

    dime=struct();
    dime.datatype=16; %single
    dime.dim=[4 3 2 5 6 1 1 1];
    dime.pixdim=[0 2 2 2 0 0 0 0];
    fns={'intent_p1','intent_p2','intent_p3','intent_code',...
        'slice_start','slice_duration','slice_end',...
        'scl_slope','scl_inter','slice_code','cal_max',...
        'cal_min','toffset'};

    dime=nifti_helper_set_all(dime,fns);
    dime.xyzt_units=10;
    hdr.dime=dime;

    hk=struct();
    hk.sizeof_hdr=348;
    hk.data_type='';
    hk.db_name='';
    hk.extents=0;
    hk.session_error=0;
    hk.regular='r';
    hk.dim_info=0;
    hdr.hk=hk;

    hist=struct();
    hist.sform_code=1;
    hist.originator=[2 1 3 3];
    hist=nifti_helper_set_all(hist,{'descrip','aux_file'},'');
    hist=nifti_helper_set_all(hist,{'qform_code','quatern_b',...
                        'quatern_d',...
                        'qoffset_x','qoffset_y','qoffset_z'});
    hist=nifti_helper_set_all(hist,{'intent_name'},'');

    hist.srow_x=[2 0 0 -1];
    hist.srow_y=[0 2 0 -1];
    hist.srow_z=[0 0 2 -1];

    hist.quatern_c=0;
    hdr.hist=hist;

    ni.hdr=hdr;

function s=nifti_helper_set_all(s, fns, v)
    % sets all fields in fns in struct s to v
    % if v is omitted it is set to 0.
    if nargin<3, v=0; end
    n=numel(fns);
    for k=1:n
        fn=fns{k};
        s.(fn)=v;
    end


function imout=readspe(filename)
% imout=readspe(filename)
% Reads .spe file from the CSSTORM evaluation
fid = fopen(filename,'r','l');
header = fread(fid,2050,'*uint16');
imgs = fread(fid, inf, 'float32');
fclose(fid);
z_dim = double(header(724));
x_dim = double(header(22));
im=reshape(imgs,x_dim,x_dim,z_dim);
imnan=isnan(im);
imn=zeros(size(im));
imn(~imnan)=im(~imnan);
imout=rot90stack(fliplrstack(imn));


function [ind_Mp, ind_Md] = create_masks(ge, gridnd)
% Generating the indices to mask is better than generating a diagonal mask
% matrix, because multiplying the mask matrix cannot make NaN or Inf elements
% zeros.

chkarg(istypesizeof(ge, 'GT'), '"ge" should be instance of GT.');  % ge: grid type for the E-field
chkarg(istypesizeof(gridnd, 'Grid2d') || istypesizeof(gridnd, 'Grid3d'), '"gridnd" should be instance of Grid2d or Grid3d.');

v = Axis.x;
if istypesizeof(gridnd, 'Grid2d')
	v = Dir.h;
end

%% Get the shape.
N = gridnd.N;
bc = gridnd.bc;
ind = cell(1, v.count);

% Mask matrices
mask_p = cell(1, v.count);
for w = v.elems  % mask w-component of field on primary grid
	mask = false(N);
	us = setdiff(v.elems, w);

	for u = us
		if (ge == GT.prim && bc(u) == BC.e) || (ge == GT.dual && bc(u) == BC.m)
			[ind{:}] = deal(':');  % ind = {':', ':', ':'} for grid3d
			ind{u} = 1;
			mask(ind{:}) = true;
		end
	end
	mask_p{w} = mask(:);
end

ind_Mp = cell2mat(mask_p);
ind_Mp = ind_Mp(:);

mask_d = cell(1, v.count);
for w = v.elems  % mask w-component of field on dual grid
	mask = false(N);

	if (ge == GT.prim && bc(w) == BC.e) || (ge == GT.dual && bc(w) == BC.m)
		[ind{:}] = deal(':');  % ind = {':', ':', ':'} for grid3d
		ind{w} = 1;
		mask(ind{:}) = true;
	end
	mask_d{w} = mask(:);
end

ind_Md = cell2mat(mask_d);
ind_Md = ind_Md(:);