%KWAVEGRID   Class definition for k-Wave grid structure.
%
% DESCRIPTION:
%       See makeGrid for function arguments.
%
% ABOUT:
%       author      - Bradley Treeby
%       date        - 22nd July 2010
%       last update - 29th August 2012
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave is free software: you can
% redistribute it and/or modify it under the terms of the GNU Lesser
% General Public License as published by the Free Software Foundation,
% either version 3 of the License, or (at your option) any later version.
%
% k-Wave 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 Lesser General Public License for
% more details.
%
% You should have received a copy of the GNU Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

classdef kWaveGrid

    % define the private properties (these parameters are stored and cannot
    % be altered by the user after the object is created, but they can be
    % accessed)
    properties (GetAccess = public, SetAccess = private)

        % grid dimensions in grid points
        Nx = 0;
        Ny = 0;
        Nz = 0;

        % grid point spacing in metres
      	dx = 0;
        dy = 0;
        dz = 0;

        % wavenumber grids
        kx_vec = 0;
        ky_vec = 0;
        kz_vec = 0;
        k = 0;

        % maximum supported spatial frequency
        kx_max = 0;
        ky_max = 0;
        kz_max = 0;
        k_max = 0;

        % number of dimensions
        dim = 0;

        % non-uniform grid
        nonuniform = false;

    end

    % define the public properties (these parameters are stored and can be
    % both accessed and altered by the user after the object is created)
    properties (GetAccess = public, SetAccess = public)

        % time array
        t_array = 'auto';

    end

    % define the dependent properties (these parameters are not stored but
    % re-computed each time they are needed)
    properties(Dependent = true, GetAccess = public, SetAccess = private)

        % 3D plaid Cartesian spatial grids
        x;
        y;
        z;

        % 3D plaid wavenumber grids
        kx;
        ky;
        kz;

        % 1D spatial grids
        x_vec;
        y_vec;
        z_vec;

        % Cartesian grid size
        x_size;
        z_size;
        y_size;

        % time step and number of time points
        dt;
        Nt;

        % total number of grid points
        total_grid_points;

    end

    % define the staggered grid properties (these can be accessed but not
    % altered by the user after the object is created, the parameters are
    % also hidden from view)
    properties (Hidden = true, GetAccess = public, SetAccess = private)

        % position vectors for the staggered grid points in [0, 1]
        xn_vec = 0;
        yn_vec = 0;
        zn_vec = 0;
        xn_vec_sgx = 0;
        yn_vec_sgy = 0;
        zn_vec_sgz = 0;

        % transformation gradients between uniform and staggered grids
        dxudxn = 0;
        dyudyn = 0;
        dzudzn = 0;
        dxudxn_sgx = 0;
        dyudyn_sgy = 0;
        dzudzn_sgz = 0;

    end

    % define the dependent staggered grid properties (these parameters are
    % not stored but re-computed each time they are needed, the parameters
    % are also hidden from view)
    properties(Dependent = true, Hidden = true, GetAccess = public, SetAccess = private)

        % 3D plaid non-uniform spatial grids
        xn;
        yn;
        zn;

    end

    % constructor function
    methods
        function kgrid = kWaveGrid(varargin)

            % assign the input values to the grid object
            switch nargin

                case 6

                    % 3D uniform grid
                    kgrid.Nx = varargin{1};
                    kgrid.dx = varargin{2};
                    kgrid.Ny = varargin{3};
                    kgrid.dy = varargin{4};
                    kgrid.Nz = varargin{5};
                    kgrid.dz = varargin{6};

                    % set the number of dimensions
                    kgrid.dim = 3;

                case 4

                    % 2D uniform grid
                    kgrid.Nx = varargin{1};
                    kgrid.dx = varargin{2};
                    kgrid.Ny = varargin{3};
                    kgrid.dy = varargin{4};

                    % set the number of dimensions
                    kgrid.dim = 2;

                case 2

                    % 1D uniform grid
                    kgrid.Nx = varargin{1};
                    kgrid.dx = varargin{2};

                    % set the number of dimensions
                    kgrid.dim = 1;

                otherwise
                    error('Incorrect number of input arguments');
            end

            switch kgrid.dim
                case 1

                    % assign the grid parameters for the x spatial direction
                    kgrid.kx_vec = kgrid.makeDim(kgrid.Nx, kgrid.dx);

                    % define the scalar wavenumber based on the wavenumber components
                    kgrid.k = abs(kgrid.kx_vec);

                    % define maximum supported frequency
                    kgrid.kx_max = max(abs(kgrid.kx_vec(:)));
                    kgrid.k_max = kgrid.kx_max;

                case 2

                    % assign the grid parameters for the x and z spatial directions
                    kgrid.kx_vec = kgrid.makeDim(kgrid.Nx, kgrid.dx);
                    kgrid.ky_vec = kgrid.makeDim(kgrid.Ny, kgrid.dy);

                    % define the wavenumber based on the wavenumber components
                    % (using bsxfun saves memory by avoiding ndgrid)
                    kgrid.k = zeros(kgrid.Nx, kgrid.Ny);
                    kgrid.k = bsxfun(@plus, (reshape(kgrid.kx_vec, [], 1, 1).^2), kgrid.k);
                    kgrid.k = bsxfun(@plus, (reshape(kgrid.ky_vec, 1, [], 1).^2), kgrid.k);
                    kgrid.k = sqrt(kgrid.k);

                    % define maximum supported frequency
                    kgrid.kx_max = max(abs(kgrid.kx_vec(:)));
                    kgrid.ky_max = max(abs(kgrid.ky_vec(:)));
                    kgrid.k_max = min([kgrid.kx_max, kgrid.ky_max]);

                case 3

                    % assign the grid parameters for the x ,y and z spatial directions
                    kgrid.kx_vec = kgrid.makeDim(kgrid.Nx, kgrid.dx);
                    kgrid.ky_vec = kgrid.makeDim(kgrid.Ny, kgrid.dy);
                    kgrid.kz_vec = kgrid.makeDim(kgrid.Nz, kgrid.dz);

                    % define the wavenumber based on the wavenumber components
                    % (using bsxfun saves memory by avoiding ndgrid)
                    kgrid.k = zeros(kgrid.Nx, kgrid.Ny, kgrid.Nz);
                    kgrid.k = bsxfun(@plus, (reshape(kgrid.kx_vec, [], 1, 1).^2), kgrid.k);
                    kgrid.k = bsxfun(@plus, (reshape(kgrid.ky_vec, 1, [], 1).^2), kgrid.k);
                    kgrid.k = bsxfun(@plus, (reshape(kgrid.kz_vec, 1, 1, []).^2), kgrid.k);
                    kgrid.k = sqrt(kgrid.k);

                    % define maximum supported frequency
                    kgrid.kx_max = max(abs(kgrid.kx_vec(:)));
                    kgrid.ky_max = max(abs(kgrid.ky_vec(:)));
                    kgrid.kz_max = max(abs(kgrid.kz_vec(:)));
                    kgrid.k_max = min([kgrid.kx_max, kgrid.ky_max, kgrid.kz_max]);

            end
        end
    end

    % functions for dependent variables that only run when queried
    methods

        function x = get.x(obj)
            % calculate x based on kx
            x = obj.x_size*obj.kx*obj.dx/(2*pi);
        end

        function y = get.y(obj)
            % calculate y based on ky
            y = obj.y_size*obj.ky*obj.dy/(2*pi);
        end

        function z = get.z(obj)
            % calculate z based on kz
            z = obj.z_size*obj.kz*obj.dz/(2*pi);
        end

        function kx = get.kx(obj)
            % duplicate kx vector
            switch obj.dim
                case 1
                    kx = obj.kx_vec;
                case 2
                    kx = repmat(obj.kx_vec, [1, obj.Ny]);
                case 3
                    kx = repmat(obj.kx_vec, [1, obj.Ny, obj.Nz]);
            end
        end

        function ky = get.ky(obj)
            % rotate ky vector to y direction then duplicate
            switch obj.dim
                case 1
                    ky = 0;
                case 2
                    ky = repmat(obj.ky_vec.', [obj.Nx, 1]);
                case 3
                    ky = repmat(obj.ky_vec.', [obj.Nx, 1, obj.Nz]);
            end
        end

        function kz = get.kz(obj)
            % permute kz vector to z direction then duplicate
            switch obj.dim
                case 1
                    kz = 0;
                case 2
                    kz = 0;
                case 3
                    kz = repmat(permute(obj.kz_vec, [2 3 1]), [obj.Nx, obj.Ny, 1]);
            end
        end

        function x_vec = get.x_vec(obj)
            % calculate x_vec based on kx_vec
            x_vec = obj.x_size*obj.kx_vec*obj.dx/(2*pi);
        end

        function y_vec = get.y_vec(obj)
            % calculate y_vec based on ky_vec
            y_vec = obj.y_size*obj.ky_vec*obj.dy/(2*pi);
        end

        function z_vec = get.z_vec(obj)
            % calculate z_vec based on kz_vec
            z_vec = obj.z_size*obj.kz_vec*obj.dz/(2*pi);
        end

        function x_size = get.x_size(obj)
            % calculate x_size based on Nx and dx
            x_size = obj.Nx*obj.dx;
        end

        function y_size = get.y_size(obj)
            % calculate y_size based on Ny and dy
            y_size = obj.Ny*obj.dy;
        end

        function z_size = get.z_size(obj)
            % calculate z_size based on Nz and dz
            z_size = obj.Nz*obj.dz;
        end

        function dt = get.dt(obj)
            % extract dt from the time array
            if strcmp(obj.t_array, 'auto')
                dt = 'auto';
            else
                dt = obj.t_array(2) - obj.t_array(1);
            end
        end

        function Nt = get.Nt(obj)
            % extract dt from the time array
            if strcmp(obj.t_array, 'auto')
                Nt = 'auto';
            else
                Nt = length(obj.t_array);
            end
        end

        function N = get.total_grid_points(obj)
            % calculate the total number of points in the grid
            switch obj.dim
                case 1
                    N = obj.Nx;
                case 2
                    N = obj.Nx*obj.Ny;
                case 3
                    N = obj.Nx*obj.Ny*obj.Nz;
            end
        end
    end

    % functions that can only be accessed by class members
    methods (Access = 'protected', Static = true)

        % subfunction to create the grid parameters for a single spatial direction
        function kx_vec = makeDim(Nx, dx)

            % define the discretisation of the spatial dimension such that there is
            % always a DC component
            if rem(Nx, 2) == 0
                % grid dimension has an even number of points
                nx = ((-Nx/2:Nx/2-1)/Nx).';
            else
                % grid dimension has an odd number of points
                nx = ((-(Nx-1)/2:(Nx-1)/2)/Nx).';
            end

            % force middle value to be zero in case 1/Nx is a recurring
            % number and the series doesn't give exactly zero
            nx(floor(Nx/2) + 1) = 0;

            % define the wavenumber vector components
            kx_vec = (2*pi/dx).*nx;
        end

    end

    % functions for non-uniform grids
    methods

        % subfunction to return plaid xn matrix
        function xn = get.xn(obj)
            if obj.nonuniform
                % duplicate xn vector
                switch obj.dim
                    case 1
                        xn = obj.xn_vec;
                    case 2
                        xn = repmat(obj.xn_vec, [1, obj.Ny]);
                    case 3
                        xn = repmat(obj.xn_vec, [1, obj.Ny, obj.Nz]);
                end
            else
                xn = 0;
            end
        end

        % subfunction to return plaid yn matrix
        function yn = get.yn(obj)
            if obj.nonuniform
                % rotate yn vector to y direction then duplicate
                switch obj.dim
                    case 1
                        yn = 0;
                    case 2
                        yn = repmat(obj.yn_vec.', [obj.Nx, 1]);
                    case 3
                        yn = repmat(obj.yn_vec.', [obj.Nx, 1, obj.Nz]);
                end
            else
                yn = 0;
            end
        end

        % subfunction to return plaid zn matrix
        function zn = get.zn(obj)
            if obj.nonuniform
                % permute kz vector to z direction then duplicate
                switch obj.dim
                    case 1
                        zn = 0;
                    case 2
                        zn = 0;
                    case 3
                        zn = repmat(permute(obj.zn_vec, [2 3 1]), [obj.Nx, obj.Ny, 1]);
                end
            else
                zn = 0;
            end
        end

        % subfunction to set non-uniform grid parameters in specified
        % dimension
        function obj = setNUGrid(obj, dim, xn_vec, dxudxn, xn_vec_sgx, dxudxn_sgx)

            % check the dimension to set the nonuniform grid is appropriate
            if dim > obj.dim
                error(['Cannot set nonuniform parameters for dimension ' num2str(dim) ' of ' num2str(obj.dim) '-dimensional grid']);
            end

            % force non-uniform grid spacing to be column vectors, and the
            % gradients to be in the correct direction for use with bsxfun
            switch dim
                case 1
                    obj.xn_vec = reshape(xn_vec, [], 1);
                    obj.dxudxn = reshape(dxudxn, [], 1);
                    obj.xn_vec_sgx = reshape(xn_vec_sgx, [], 1);
                    obj.dxudxn_sgx = reshape(dxudxn_sgx, [], 1);
                case 2
                    obj.yn_vec = reshape(xn_vec, [], 1);
                    obj.dyudyn = reshape(dxudxn, 1, []);
                    obj.yn_vec_sgy = reshape(xn_vec_sgx, [], 1);
                    obj.dyudyn_sgy = reshape(dxudxn_sgx, 1, []);
                case 3
                    obj.zn_vec = reshape(xn_vec, [], 1);
                    obj.dzudzn = reshape(dxudxn, 1, 1, []);
                    obj.zn_vec_sgz = reshape(xn_vec_sgx, [], 1);
                    obj.dzdzn_sgz = reshape(dxudxn_sgx, 1, 1, []);
            end

            % set non-uniform flag
            obj.nonuniform = true;

        end
    end
end


function [group_metrics individual_metrics values gwcsf gwcsfmean gwcsf_l2norm] = qc_metrics_second_level(obj, varargin)
%
% Quality metrics for a 2nd-level analysis (set of images from different subjects)
% The goal is to obtain measures that are simple and scale invariant, and
% so can be compared across image datasets
%
% :Usage:
% ::
%
%     [group_metrics individual_metrics values gwcsf gwcsfmean gwcsf_l2norm] = qc_metrics_second_level(list inputs here, [optional inputs])
%
% For objects: Type methods(object_name) for a list of special commands
%              Type help object_name.method_name for help on specific
%              methods.
%
% ..
%     Author and copyright information:
%
%     Copyright (C) 2016 Tor Wager
%
%     This program is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
%     This program is distributed in the hope that it will be useful,
%     but WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%     GNU General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see <http://www.gnu.org/licenses/>.
% ..
%
% :Inputs:
%
%   **obj:**
%        An fmri_data object with a set of images
%        Intended to be individual-participant beta or contrast images from
%        first-level analyses.
%
% :Optional Inputs:
%   **noverbose:**
%        Turn off text display reporting of warnings and metrics
%
%   **param2:**
%        description of param2
%
% :Outputs:
%
%   **group_metrics:**
%        Quality metrics indicating potential problems, as described below
%
%   **individual_metrics:**
%        Individual image-level values for quality metrics
%        These can be used to identify individuals with problematic images
%        or as covariates in analyses.
%
%   **values:**
%        Grand Mean Gray, white, and CSF values - one number per metric per
%        image
%
%   **gwcsf:**
%        Gray, white, and CSF image objects with values for all images
%
%   **gwcsfmean:**
%        Gray, white, and CSF image objects mean values across images
%
%   **gwcsfl2norm:**
%        L2 norm of Gray, white, and CSF components for each image
%
% :References:
%   None.
%
%
% --------------------------------------------------------------------------------
% Coverage:  Percentage of standard MNI gray-matter mask with non-zero/non-nan values
%       NOT IMPLEMENTED YET
%       Range: [0 100],  100 is good (full coverage). Low values indicate
%       dropout due to artifacts or space mis-match
%
% Template match:  Dice coefficient (point-biserial r?) with standard MNI gray-matter mask
%       NOT IMPLEMENTED YET
%       Range: [0 100],  100 is good (full coverage). Low values indicate
%       dropout due to artifacts or space mis-match
%
% Bright-edge bias:  "brightedge"
%       NOT IMPLEMENTED YET
%       High inter-image variability around edge of brain
%       median absolute deviation (MAD) in brain edge-mask / MAD in center
%       of brain.
%       Range: [0 Inf],  1 is good (homogeneous). High values may indicate
%       head movement artifacts
%
% Signal in ventricles: "csf_to_gm_signal_ratio"
%       In most contrast maps, there should be less non-zero signal in the CSF space (e.g., ventricles)
%       than in gray matter.  This measures the ratio of mean absolute signal in CSF / GM.
%       Range: [0 Inf]. Values < 1 indicate low ventricle signal, which is good.
%       Higher values are worse.
%       Apply to: Individual or group. Contrast images (differences across conditions) for a series of individual participants
%
% Effect size in ventricles: "global_d_ventricles"
%       d = Mean / standard error (STD) of whole-ventricle average signal
%       Indicates a shift towards global activation or deactivation of the
%       ventricles in the group.
%       Range: [-Inf Inf], farther from zero is bad.
%       Apply to: Group. Contrast images (differences across conditions) for group of participants
%
% Significant global activation of ventricles: "global_logp_ventricles"
%       -log(p-value) in ventricle global mean across subjects, divided by -log(0.05)
%       Any value > 1 indicates a significant global activation in the
%       ventricles.
%       Range: [0 Inf], higher is worse. Values > 1 are bad.
%       Apply to: Group. Contrast images (differences across conditions) for group of participants
%
% Effect size in white matter: "global_d_wm"
%       d = Mean / standard error (STD) of whole-WM average signal
%       Indicates a shift towards global activation or deactivation of the
%       WM in the group. This is not completely redundant with CSF, because
%       some artifacts can affect one tissue compartment but not another.
%       WM values are, however, more likely to be influenced by true
%       signal.
%       Range: [-Inf Inf], farther from zero is bad.
%
% Significant global activation of WM: "global_logp_wm"
%       -log(p-value) in WM global mean across subjects, divided by -log(0.05)
%       Any value > 1 indicates a significant global activation in WM.
%       Range: [0 Inf], higher is worse. Values > 1 are bad.
%
% Non-spatially specific signal contamination: "r2_explained_by_csf"
%       Variance in individual differences in whole-gray-matter activity
%       Range: [-1 1], usually [0 1].  Farther from zero is bad.
%       Apply to: Group. Contrast images (differences across conditions) for group of participants
%
% Scale inhomogeneity across participants: "csf_scale_inhom"
%       Assumption: Subjects should be on the same scale (e.g., if you look
%       at histograms of contrast images for each subject). We assume that
%       the signal in gray matter may vary for meaningful reasons, but the
%       scale of the signal in CSF (and maybe WM?) should be similar across
%       participants.
%       Measure: The coefficient of variation of the L1-norm across CSF voxels for each participant.
%       The L1-norm is a measure of scale (across voxels, for each observation/image)
%       that is more robust than the variance.
%       High variability in these individual scale parameters is a sign of inhomogeneity.
%       The coefficient of variation is the standard dev. of this whole-image scale value divided by its mean.
%       This is a scale-invariant measure of inhomogeneity across images.
%       It also avoids the instability that occurs by dividing by values that may
%       be near zero, such as the mean.
%       Range: [0 Inf].  Lower is better.
%       Apply to: Group. Condition images (betas) for group of participants
%
% Scale inhomogeneity across participants in gray matter: "gm_scale_inhom"
%       The coefficient of variation of the L1-norm across GM voxels for each participant.
%       The L1-norm is a measure of scale (across voxels, for each observation/image)
%       that is more robust than the variance.
%       High variability in these individual scale parameters is a sign of inhomogeneity.
%       The coefficient of variation is the standard dev. of this whole-image scale value divided by its mean.
%       This is a scale-invariant measure of inhomogeneity across images.
%       It also avoids the instability that occurs by dividing by values that may
%       be near zero, such as the mean.
%       Range: [0 Inf].  Lower is better, but GM values may also be influenced by large differences in true signal.
%
% :Examples:
% ::
%    % Load sample images
%    obj = load_image_set('emotionreg');
%
%    % Run QC metrics
%    [group_metrics individual_metrics] = qc_metrics_second_level(obj);
%
%    % Plot histograms of gray, white, CSF values
%    hist_han = histogram(obj, 'byimage', 'by_tissue_type');
%
% :See also:
%   - extract_gray_white_csf, histogram
%

% ..
%    DEFAULTS AND INPUTS
% ..

doverbose = 1;

% optional inputs with default values
for i = 1:length(varargin)
    if ischar(varargin{i})
        switch varargin{i}

            case 'noverbose', doverbose = 0;

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

[values, components, gwcsf, gwcsf_l2norm] = extract_gray_white_csf(obj);

% Get mean signal in each tissue compartment for visualization, if desired
% -------------------------------------------------------------------------
gwcsfmean = mean(gwcsf{1});
gwcsfmean = replace_empty(gwcsfmean);
for i = 2:3
    m = mean(gwcsf{i});
    m = replace_empty(m);
    gwcsfmean.dat(:, i) = m.dat;
end

% -------------------------------------------------------------------------
% METRICS
% -------------------------------------------------------------------------

% Get coverage of gray-matter a priori mask space (do we have data in all vox?)
% -------------------------------------------------------------------------

objmasked = apply_mask(obj, mean(gwcsf{1}));

wh = isnan(objmasked.dat) | objmasked.dat == 0;
individual_metrics.gray_matter_coverage = sum(~wh) ./ size(objmasked.dat, 1);
group_metrics.mean_gray_matter_coverage = mean(individual_metrics.gray_matter_coverage);

%brightedge
% NOT DONE YET

% Global activation/deactivation in CSF and white matter
% -------------------------------------------------------------------------

individual_metrics.global_ventricle_values = mean(values(:, 3));

group_metrics.global_d_ventricles = mean(values(:, 3)) ./ std(values(:, 3));

n = size(values, 1);
t = group_metrics.global_d_ventricles .* sqrt(n);   % t-value
p = 2 .* min(tcdf(t, n - 1), tcdf(-t, n - 1));      % two-tailed

group_metrics.global_logp_ventricles = -log(p) ./ (-log(.05));

% --- wm ---
individual_metrics.global_white_matter_values = mean(values(:, 2));

group_metrics.global_d_wm = mean(values(:, 2)) ./ std(values(:, 2));

t = group_metrics.global_d_wm .* sqrt(n);   % t-value
p = 2 .* min(tcdf(t, n - 1), tcdf(-t, n - 1));      % two-tailed

group_metrics.global_logp_wm = -log(p) ./ (-log(.05));

% Gray-matter signal explained by CSF
% -------------------------------------------------------------------------

[r, p] = corr(values(:, 1), values(:, 3));

group_metrics.gm_explained_by_csf_pvalue = p;
group_metrics.r2_explained_by_csf = r .^ 2;

% Gray-matter scale (l2 norm) explained by CSF scale
% -------------------------------------------------------------------------

[r, p] = corr(gwcsf_l2norm(:, 1), gwcsf_l2norm(:, 3));

group_metrics.gm_l2norm_explained_by_csf_pvalue = p;
group_metrics.r2_l2norm_explained_by_csf = r .^ 2;

% Signal in ventricles
% -------------------------------------------------------------------------

csfsignal = mean(abs(gwcsf{3}.dat)) ./ mean(abs(gwcsf{1}.dat));
group_metrics.csf_to_gm_signal_ratio = mean(csfsignal);
individual_metrics.csf_to_gm_signal_ratio = csfsignal;

% Scale Inhomogeneity
% -------------------------------------------------------------------------

L1norm = sum(abs(gwcsf{1}.dat))';
group_metrics.gm_scale_inhom = std(L1norm) ./ mean(L1norm);
individual_metrics.gm_L1norm = L1norm;

L1norm = sum(abs(gwcsf{3}.dat))';
group_metrics.csf_scale_inhom = std(L1norm) ./ mean(L1norm);
individual_metrics.csf_L1norm = L1norm;

% Add warnings
% -------------------------------------------------------------------------
group_metrics.warnings = {};

if any(individual_metrics.gray_matter_coverage < .9)
    group_metrics.warnings{end + 1} = sprintf('Warning: Incomplete coverage, missing gray-matter data in some images.\n   - Mean coverage is d = %3.2f%%\n', group_metrics.mean_gray_matter_coverage .* 100);
end

if group_metrics.global_logp_ventricles > 1
    group_metrics.warnings{end + 1} = sprintf('Warning: Significant global activation in CSF space/ventricles.\n   - Effect size is d = %3.2f\n', group_metrics.global_d_ventricles);
end

if group_metrics.global_logp_wm > 1
    group_metrics.warnings{end + 1} = sprintf('Warning: Significant global activation in white matter.\n   - Effect size is d = %3.2f\n', group_metrics.global_d_wm);
end

if group_metrics.gm_explained_by_csf_pvalue < .05
    group_metrics.warnings{end + 1} = sprintf('Warning: Gray-matter individual diffs significantly correlated with mean CSF value.\n   - Var explained (r^2) = %3.2f%%', group_metrics.r2_explained_by_csf .* 100);
end

if group_metrics.gm_l2norm_explained_by_csf_pvalue < .05
    group_metrics.warnings{end + 1} = sprintf('Warning: Gray-matter scale (L2 norm) significantly correlated with mean CSF L2 norm.\n   - Var explained (r^2) = %3.2f%%', group_metrics.r2_l2norm_explained_by_csf .* 100);
end

if group_metrics.csf_to_gm_signal_ratio > 1
    group_metrics.warnings{end + 1} = sprintf('Warning: Strong non-zero signal in CSF relative to gray matter.\n   - Ratio is = %3.2f', group_metrics.csf_to_gm_signal_ratio);
end

if group_metrics.csf_scale_inhom > .3
    group_metrics.warnings{end + 1} = sprintf('Warning: High individual diffs in image scaling estimated from CSF.\n   - CV is = %3.2f', group_metrics.csf_scale_inhom);
end

% Display warnings
% -------------------------------------------------------------------------

if doverbose

    disp(' ');
    disp(group_metrics)
    disp(char(group_metrics.warnings{:}))

end

end % function


function [names, ids] = getMultiSpeciesModelId(modelJoint, nameTagsModels, nameTagHost, metTagRe, rxnTagRe, compCom, compHost)
% Get names and IDs for metabolites and exchange reactions in the [u] and [b] space
%
% USAGE:
%
%    [names, ids] = getMultiSpeciesModelId(modelJoint, nameTagsModels, nameTagHost, metTagRe, rxnTagRe, compCom, compHost)
%
% INPUTS:
%    modelJoint:       COBRA multi-organism model
%    nameTagsModels:   cell array of tags for species to identify the respective
%                      reactions and metabolites from `modelJoint.rxns` and `.mets`
%
% OPTIONAL INPUTS:
%    nameTagHost:      string of tag for the host model if exist. Input [] if no host is present
%    metTagRe:         a regular expression to identify the tag in nameTagsModels from `modelJoint.mets`.
%                      Use '%s' for the tag in `nameTagsModels` for each species.
%                      Default '^%s', the beginning of the string. In this case, if the tag for species 1 is
%                      'SP1', then mets with id 'SP1glc-D[e]' will be identified as belonging to species 1.
%                      E.g., 'met[compartment_SP1]' where 'SP1' is the tag for species 1, input '\[[^_]+_%s\]$'.
%    rxnTagRe:         a regular expression to idenify the tag in `nameTagsModels` from `modelJoint.rxns`.
%                      Default '^%s', the beginning of the string
%    compCom:          compartment Id for the community exchange compartment. Default 'u'
%    compHost:         compartment Id for the exchange compartment specific to the host. Default 'b'
%
% OUTPUTS:
%    names:            structure of reaction/metabolite name (.rxns/.mets) with the following fields:
%
%                        * spAbbr - species abbreviation (= `nameTags`). Host put at the end
%                        * EXcom - #met[u]-by-1 cell. Exchange reactions for community metabolites `met[u]`
%                        * EXhost - #met[b]-by-1 cell. Exchange reactions for host-specific exchange metabolites `met[b]`
%                        * EXsp - #met[u]-by-#species cell. `EXsp(i,j)` is the species-community exchange reactions
%                          for the i-th met[u] and the j-th species
%                        * Mcom - #met[u]-by-1 cell. All community metabolites `met[u]`
%                        * Mhost - #met[u]-by-1 cell. Host-specific exchange metabolites `met[b]`
%                        * Msp - #met[u]-by-#species cell. `Msp(i,j)` is the metabolite in `met[e]` of the `j`-th species
%                          participating in reaction `EXsp(i,j)`
%                        * rxnSps - #rxns-by-1 cell. `rxnSps(i)` would be `spAbbr(j)` if the `i`-th reaction belongs to the `j`-th species
%                        * metSps - #mets-by-1 cell. `metSps(i)` would be `spAbbr(j)` if the i-th metabolite belongs to the `j`-th species
%
%    ids:              structure of reaction/metabolite index with the same fields as names except without `spAbbr`

if nargin < 7 || isempty(compHost)
    compHost = 'b';
end
if nargin < 6 || isempty(compCom)
    compCom = 'u';
end
compHost = regexprep(compHost, '^\[(.*)\]$', '$1');
compCom = regexprep(compCom, '^\[(.*)\]$', '$1');
% regular expression for identifying species
if nargin < 5 || isempty(rxnTagRe)
    rxnTagRe = '^%s';
end
if nargin < 4 || isempty(metTagRe)
    metTagRe = '^%s';
end
metCompartment = getCompartment(modelJoint.mets);
% mets in the common exchange space ([u]) or the biomass (use biomass[c] to be consistent with createMultipleSpeciesModel)
metCom = strcmp(metCompartment, compCom) | strcmp(modelJoint.mets, 'biomass[c]');
% mets in exchange space accessible only by the host ([b])
metHost = strcmp(metCompartment, compHost);
hostExist = any(metHost) && nargin >= 3 && ~isempty(nameTagHost);   % use && to avoid error if nameTagHost not given
if hostExist && iscell(nameTagHost)
    nameTagHost = nameTagHost{1};
end
if ischar(nameTagsModels)
    nameTagsModels = {nameTagsModels};
end
% get the regular expression of identifiers for species-specific mets and rxns
nSp = numel(nameTagsModels);
[metTagReSp, rxnTagReSp] = deal(repmat({''}, nSp + hostExist, 1));
for jSp = 1:nSp
    metTagReSp{jSp} = strrep(metTagRe, '%s', nameTagsModels{jSp});
    rxnTagReSp{jSp} = strrep(rxnTagRe, '%s', nameTagsModels{jSp});
end
metHost = find(metHost);
if hostExist
    % add regular expression for host
    metTagReSp{nSp + 1} = strrep(metTagRe, '%s', nameTagHost);
    rxnTagReSp{nSp + 1} = strrep(rxnTagRe, '%s', nameTagHost);
    % try to sort metHost to have the same order as metCom by comparing met names
    metHostName = regexprep(modelJoint.mets(metHost), metTagReSp{nSp + 1}, '');
    metHostName = strrep(metHostName, ['[' compHost ']'], '');
    metComName = strrep(modelJoint.mets(metCom), ['[' compCom ']'], '');
    [yn, id] = ismember(metComName, metHostName);
    if all(yn) && numel(union(metComName, metHostName)) == numel(metComName)
        % one-to-one mapping exists. Reorder
        metHost = metHost(id);
    end
end

% get system exchange rxns
rxnEX = find(sum(modelJoint.S ~= 0, 1) == 1);
% exchange rxns for mets in [u], in the same order as metCom
[EXcom, ~] = find(modelJoint.S(metCom, rxnEX)');
EXcom = rxnEX(EXcom);
% exchange rxns for mets in [b], in the same order as metHost
[EXhost, ~] = find(modelJoint.S(metHost, rxnEX)');
EXhost = rxnEX(EXhost);

% Get the species number for each met and rxn. 0 for those in [u]
[nMets, nRxns] = size(modelJoint.S);
metSps = zeros(nMets, 1);
rxnSps = zeros(nRxns, 1);
for jSp = 1:nSp
    metSps(~cellfun(@isempty, regexp(modelJoint.mets, metTagReSp{jSp}, 'once')) & ~metCom) = jSp;
    rxnSps(~cellfun(@isempty, regexp(modelJoint.rxns, rxnTagReSp{jSp}, 'once'))) = jSp;
    %     metSps(strncmp(modelJoint.mets, nameTagsModels{j}, numel(nameTagsModels{j})) & ~metCom) = j;
    %     rxnSps(strncmp(modelJoint.rxns, nameTagsModels{j}, numel(nameTagsModels{j}))) = j;
    rxnSps(EXcom) = 0;  % in case some mets or exchange rxns in [u] have prefix equal to the name tags
    % check if the model is properly compartmentalized.
    if nnz(modelJoint.S(metSps == jSp, rxnSps ~= jSp)) > 0 ...
            || nnz(modelJoint.S(metSps ~= jSp & metSps > 0, rxnSps == jSp)) > 0
        error('The model for species %s is not correctly compartmentalized. Check the name tags.');
    end
end
if hostExist
    metSps(~cellfun(@isempty, regexp(modelJoint.mets, metTagReSp{nSp + 1}, 'once')) & ~metCom) = nSp + 1;
    rxnSps(~cellfun(@isempty, regexp(modelJoint.rxns, rxnTagReSp{nSp + 1}, 'once'))) = nSp + 1;
    %     metSps(strncmp(modelJoint.mets, nameTagHost, numel(nameTagHost))) = nSp + 1;
    %     rxnSps(strncmp(modelJoint.rxns, nameTagHost, numel(nameTagHost))) = nSp + 1;
end

% species-community exchange rxns (between members' [e] and [u])
EXeu = any(modelJoint.S(metCom, :), 1);
EXeu(EXcom) = false;  % exclude system input/output exchange rxns
EXeu = find(EXeu);
% EXsp is a #met[u] x #species matrix with (i,j)-entry being the index for exchange reactionn
% for the i-th met in [u] between [u] and [e] of the j-th organism.
% Msp similar but the index for the exchanged metabolite in [e] of the organism
[EXsp, Msp] = deal(zeros(sum(metCom), nSp + hostExist));
for j = 1:numel(EXeu)
    mJcom = find(modelJoint.S(:, EXeu(j)) ~= 0 & metCom);  % met in [u]
    mJsp = find(modelJoint.S(:, EXeu(j)) ~= 0 & ~metCom);  % met in [e]
    EXsp(sum(metCom(1:mJcom)), rxnSps(EXeu(j))) = EXeu(j);
    Msp(sum(metCom(1:mJcom)), rxnSps(EXeu(j))) = mJsp;
end
% store all rxns/mets and their ids
names.spAbbr = nameTagsModels(:);  % species abbreviation
if hostExist
    names.spAbbr = [names.spAbbr; {nameTagHost}];
end
names.spName = names.spAbbr;
names.EXcom = modelJoint.rxns(EXcom);
ids.EXcom = EXcom(:);
names.EXhost = modelJoint.rxns(EXhost);
ids.EXhost = EXhost(:);
names.EXsp = repmat({''}, size(EXsp, 1), size(EXsp, 2));
names.EXsp(EXsp ~= 0) = modelJoint.rxns(EXsp(EXsp ~= 0));
ids.EXsp = EXsp;
names.Mcom = modelJoint.mets(metCom);
ids.Mcom = find(metCom);
names.Mhost = modelJoint.mets(metHost);
ids.Mhost = metHost;
names.Msp = repmat({''}, size(EXsp, 1), size(EXsp, 2));
names.Msp(Msp ~= 0) = modelJoint.mets(Msp(Msp ~= 0));
ids.Msp = Msp;
names.rxnSps = cell(nRxns, 1);
names.rxnSps(rxnSps > 0) = names.spAbbr(rxnSps(rxnSps > 0));
names.rxnSps(rxnSps == 0) = {'com'};
ids.rxnSps = rxnSps;
names.metSps = cell(nMets, 1);
names.metSps(metSps > 0) = names.spAbbr(metSps(metSps > 0));
names.metSps(metSps == 0) = {'com'};
ids.metSps = metSps;


function xf = compact_fourier_coeff(xf)

% Creates a compact fourier series representation by removing the strict right
% half plane.

if iscell(xf)
    xf = cellfun(@(xf) xf(:,1:(size(xf,2)+1)/2,:), xf, 'uniformoutput', false);
else
    xf = xf(:,1:(size(xf,2)+1)/2,:);
end


function [tissueModel, cRes] = pruningModel(model, rankNonCore, coreRxn, zeroExpRxns, precursorMets, eta, tol)
% Prunes the reactions of the model based on their expression,
% connectivity to core and confidence score.
%
% USAGE:
%   [tissueModel, cRes] = pruningModel(model, rankNonCore, coreRxn, zeroExpRxns, precursorMets, eta, tol)
%
% INPUTS:
%   model:               input model (COBRA model structure)
%   rankNonCore:         order for reaction removal
%	coreRxn:             core reactions in model
%	zeroExpRxns:         reactions with zero expression (i.e., measured zero, not just
%                        missing from expression data)
%   precursorMets:       list of metabolites involved in the
%                        reactions defined in protected reactions
%   eta:                 tradeoff between removing core and zero-expression
%                        reactions (default value: 1/3)
%   tol:                 minimum flux threshold for "expressed" reactions
%                        (default 1e-8)
%
% OUTPUTS:
%	tissueModel:         pruned, context-specific model
%	cRes:                result of model checks (consistency/function)
%                           - vs. +: reaction r removed from generic model or
%                           not
%                           1 vs. 2: reaction r had zero or non-zero expression evidence
%                           -x.y: removal of reaction r corresponded with removal of y (num.) total
%                           core reactions
%                           +x.1 vs. x.0: precursor production possible after removal of
%                           reaction r or not
%                           3: removal of reaction r by itself prevented production of required
%                           metabolites (therefore was not removed)
%
%
% Authors: - This script is an adapted version of the implementation from
%            https://github.com/jaeddy/mcadre.
%          - Modified and commented by S. Opdam and A. Richelle,May 2017

    tissueModel = model;
    R_P = model.rxns;
    nonCoreRemoved = 0;
    coreRemoved = 0;
    cRes = zeros(3000, 1);
    count = 1;

    paramConsistency.epsilon=tol;
    paramConsistency.modeFlag=0;
    paramConsistency.method='fastcc';

    while numel(rankNonCore) > 0

        display(['Reaction no. ', num2str(count)])
        r = rankNonCore(1);
        display(['Attempting to remove reaction ', r{:}, '...'])
        modelR = removeRxns(tissueModel, r);

        % First check precursor production; if this test fails, no need to
        % check model consistency with FVA (time-saving step)
        rStatus = checkModelFunction(modelR, precursorMets);

        if rStatus

            % Check for inactive reactions after removal of r
            if numel(r)
                % Remove reaction r from the model
                model_rem = removeRxns(tissueModel, r);
            end
            % Check for inactive reactions after removal of r
            try
                [fluxConsistentMetBool,fluxConsistentRxnBool] = findFluxConsistentSubset(model_rem,paramConsistency);
                rStatus_and_not_error = true;
            catch
                rStatus_and_not_error = false;
            end
        else
            rStatus_and_not_error = false;
        end

        if rStatus_and_not_error
            inactive_G= [ r; model_rem.rxns(fluxConsistentRxnBool==0)];

            inactiveCore = intersect(inactive_G, coreRxn);
            inactiveNonCore = setdiff(inactive_G, inactiveCore);

            % Remove reactions with zero expression (previously penalized in
            % rank_reactions) and corresponding inactive core reactions, only if
            % sufficiently more non-core reactions are removed
            if ismember(r, zeroExpRxns)

                display('Zero-expression evidence for reaction...')

                % Check model function with all inactive reactions removed
                modelTmp = removeRxns(tissueModel, inactive_G);
                tmpStatus = checkModelFunction(modelTmp, precursorMets);

                if (numel(inactiveCore) / numel(inactiveNonCore) <= eta) && tmpStatus

                    R_P = setdiff(R_P, inactive_G);
                    tissueModel = removeRxns(tissueModel, inactive_G);

                    rankNonCore(ismember(rankNonCore, inactive_G)) = [];

                    nonCoreRemoved = nonCoreRemoved + numel(inactiveNonCore);
                    coreRemoved = coreRemoved + numel(inactiveCore);
                    num_removed = nonCoreRemoved + coreRemoved;

                    display('Removed all inactive reactions')

                    % result = -1.x indicates that reaction r had zero
                    % expression evidence and was removed along with any
                    % consequently inactivated reactions; x indicates the number of
                    % core reactions removed
                    if numel(inactiveCore) > 100
                        removed_C_indicator = numel(inactiveCore) / 100;
                    else
                        removed_C_indicator = numel(inactiveCore) / 10;
                    end
                    result = -1 - removed_C_indicator;

                else
                    % Note: no reactions (core or otherwise) are actually
                    % removed in this step, but it is necessary to update the
                    % total number of removed reactions to avoid errors below
                    num_removed = nonCoreRemoved + coreRemoved;
                    rankNonCore(1) = [];

                    display('No reactions removed')

                    % result = 1.x indicates that no reactions were removed
                    % because removal of r either led to a ratio of inactivated
                    % core vs. non-core reactions above the specified threshold
                    % eta (x = 1) or the removal of r and consequently
                    % inactivated reactions prevented production of required
                    % metabolites (x = 0)
                    result = 1 + tmpStatus / 10;
                end

            % If reaction has expression evidence, only attempt to remove
            % inactive non-core reactions
            else
                % Check model function with non-core inactive reactions removed
                modelTmp = removeRxns(tissueModel, inactiveNonCore);
                tmpStatus = checkModelFunction(modelTmp, precursorMets);

                if numel(inactiveCore) == 0 && tmpStatus
                    R_P = setdiff(R_P, inactiveNonCore);
                    tissueModel = removeRxns(tissueModel, inactiveNonCore);

                    rankNonCore(ismember(rankNonCore, inactiveNonCore)) = [];

                    nonCoreRemoved = nonCoreRemoved + numel(inactiveNonCore);
                    num_removed = nonCoreRemoved + coreRemoved;
                    display('Removed non-core inactive reactions')

                    % result = -2 indicates that reaction r had expression.
                    % evidence and was removed along with (only) non-core
                    % inactivated reactions; x indicates the number of
                    % core reactions removed (should be zero!)
                    if numel(inactiveCore) > 100
                        removed_C_indicator = numel(inactiveCore) / 100;
                    else
                        removed_C_indicator = numel(inactiveCore) / 10;
                    end
                    result = -2 - removed_C_indicator;

                else
                    num_removed = nonCoreRemoved + coreRemoved;
                    rankNonCore(1) = [];

                    display('No reactions removed')

                    % result = 2.x indicates that no reactions were removed
                    % because removal of r either led to inactivated core
                    % reactions (x = 1) or the removal of r and consequently
                    % inactivated reactions prevented production of required
                    % metabolites (x = 0)
                    result = 2 + tmpStatus / 10;
                end
            end
        else
            num_removed = nonCoreRemoved + coreRemoved;
            rankNonCore(1) = [];

            % result = 3 indicates that no reactions were removed because
            % removal of r by itself prevented production of required
            % metabolites
            result = 3;
        end

        cRes(count) = result;
        count = count + 1;
        display(sprintf(['Num. removed: ', num2str(num_removed), ...
            ' (', num2str(coreRemoved), ' core, ', ...
            num2str(nonCoreRemoved), ' non-core); ', ...
            'Num. remaining: ', num2str(numel(rankNonCore)), '\n']))
    end
    cRes(count:end) = [];
end


function C = mrdivide(A, B)
%/    CHEBOP right division.
%   C = A/B, where ones of a A is a CHEBOP and is B a scalar is equivalent
%   to A*(1/B), repectively.
%
%   All other instances returns an error.
%
% See also CHEBOP/MTIMES, CHEBOP/RDIVIDE.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

if ( isnumeric(B) )
    C = mtimes(A, 1./B);
else
    error('CHEBOP:MRDIVIDE:NotSupported', '%s/%s is not supported.', ...
        class(A), class(B));
end

end


function [Gamma,Gammasum,Xi,LL] = hsinference(data,T,hmm,residuals,options,XX,Gamma0,Xi0,cv)
%
% inference engine for HMMs.
%
% INPUT
%
% data      Observations - a struct with X (time series) and C (classes)
% T         Number of time points for each time series
% hmm       hmm data structure
% residuals in case we train on residuals, the value of those.
% XX        optionally, XX, as computed by setxx.m, can be supplied
% Gamma0    initial Gamma (only used for ehmm model)
% cv        is this called from a CV routine?
%
% OUTPUT
%
% Gamma     Probability of hidden state given the data
% Gammasum  sum of Gamma over t
% Xi        joint Prob. of child and parent states given the data
% LL        Log-likelihood, summed across time points
%
% Author: Diego Vidaurre, OHBA, University of Oxford

if length(hmm.train.embeddedlags_batched) > 1
    [Gamma,Gammasum,Xi,LL] = hsinference_batched(T,hmm,residuals,XX);
    return
end

N = length(T);
K = hmm.K;
p = hmm.train.lowrank; do_HMM_pca = (p > 0);
if nargin < 7, Gamma0 = []; end
if nargin < 8, Xi0 = []; end
if nargin < 9, cv = 0; end
if ~isfield(hmm,'train')
    if nargin<5 || isempty(options)
        error('You must specify the field options if hmm.train is missing');
    end
    hmm.train = checkoptions(options,data.X,T,0);
end
mixture_model = isfield(hmm.train,'id_mixture') && hmm.train.id_mixture;
if isfield(hmm.train,'episodic'), episodic = hmm.train.episodic;
else, episodic = 0;
end
if episodic, rangeK = 1:K+1; else, rangeK = 1:K; end
order = hmm.train.maxorder;

if iscell(data)
    data = cell2mat(data);
end
if ~isstruct(data)
    data = struct('X',data);
    data.C = NaN(size(data.X,1)-order*length(T),K);
end

if do_HMM_pca
    ndim = size(data.X,2);
elseif nargin<4 || isempty(residuals)
    ndim = size(data.X,2);
    if ~isfield(hmm.train,'Sind')
        orders = formorders(hmm.train.order,hmm.train.orderoffset,hmm.train.timelag,hmm.train.exptimelag);
        hmm.train.Sind = formindexes(orders,hmm.train.S) == 1;
        if ~hmm.train.zeromean, hmm.train.Sind = [true(1,ndim); hmm.train.Sind]; end
    end
    residuals =  getresiduals(data.X,T,hmm.train.S,hmm.train.maxorder,hmm.train.order,...
        hmm.train.orderoffset,hmm.train.timelag,hmm.train.exptimelag,hmm.train.zeromean);
else
    ndim = size(residuals,2);
end

if (episodic && ~isfield(hmm.state(1),'P')) || (~episodic && ~isfield(hmm,'P'))
    hmm = hmmhsinit(hmm);
end

if nargin<6 || isempty(XX)
    setxx;
end

if ~hmm.train.acrosstrial_constrained
    Gamma = cell(N,1);
    LL = cell(N,1);
end
Gammasum = zeros(N,K);
if ~mixture_model
    Xi = cell(N,1);
else
    Xi = [];
end

setstateoptions;
% Cache shared results for use in obslike
for k = rangeK
    %hmm.cache = struct();
    hmm.cache.train{k} = train;
    %hmm.cache.order{k} = order;
    %hmm.cache.orders{k} = orders;
    %hmm.cache.Sind{k} = Sind;
    %hmm.cache.S{k} = S;
    if do_HMM_pca
        W = hmm.state(k).W.Mu_W;
        v = hmm.Omega.Gam_rate / hmm.Omega.Gam_shape;
        C = W * W' + v * eye(ndim);
        ldetWishB = 0.5*logdet(C); PsiWish_alphasum = 0;
    elseif k == 1 && strcmp(train.covtype,'uniquediag') || strcmp(train.covtype,'shareddiag')
        ldetWishB = 0;
        PsiWish_alphasum = 0;
        for n = 1:ndim
            if ~regressed(n), continue; end
            ldetWishB = ldetWishB+0.5*log(hmm.Omega.Gam_rate(n));
            PsiWish_alphasum = PsiWish_alphasum+0.5*psi(hmm.Omega.Gam_shape);
        end
        C = hmm.Omega.Gam_shape ./ hmm.Omega.Gam_rate;
    elseif k == 1 && strcmp(train.covtype,'uniquefull') || strcmp(train.covtype,'sharedfull')
        ldetWishB = 0.5*logdet(hmm.Omega.Gam_rate(regressed,regressed));
        PsiWish_alphasum = 0;
        for n = 1:sum(regressed)
            PsiWish_alphasum = PsiWish_alphasum+psi(hmm.Omega.Gam_shape/2+0.5-n/2);
        end
        PsiWish_alphasum=PsiWish_alphasum*0.5;
        C = hmm.Omega.Gam_shape * hmm.Omega.Gam_irate;
        for kk = rangeK
            [hmm.cache.WishTrace{kk},hmm.cache.codevals] = computeWishTrace(hmm,regressed,XX,C,kk);
        end
    elseif strcmp(train.covtype,'diag')
        ldetWishB=0;
        PsiWish_alphasum = 0;
        for n = 1:ndim
            if ~regressed(n), continue; end
            ldetWishB = ldetWishB+0.5*log(hmm.state(k).Omega.Gam_rate(n));
            PsiWish_alphasum = PsiWish_alphasum+0.5*psi(hmm.state(k).Omega.Gam_shape);
        end
        C = hmm.state(k).Omega.Gam_shape ./ hmm.state(k).Omega.Gam_rate;
        if episodic, iC = hmm.state(k).Omega.Gam_rate / hmm.state(k).Omega.Gam_shape; end
    elseif strcmp(train.covtype,'full')
        ldetWishB = 0.5*logdet(hmm.state(k).Omega.Gam_rate(regressed,regressed));
        PsiWish_alphasum = 0;
        for n = 1:sum(regressed)
            PsiWish_alphasum = PsiWish_alphasum+0.5*psi(hmm.state(k).Omega.Gam_shape/2+0.5-n/2);
        end
        C = hmm.state(k).Omega.Gam_shape * hmm.state(k).Omega.Gam_irate;
        if episodic, iC = hmm.state(k).Omega.Gam_rate / hmm.state(k).Omega.Gam_shape; end
        [hmm.cache.WishTrace{k},hmm.cache.codevals] = computeWishTrace(hmm,regressed,XX,C,k);
    end
    % Set up cache
    if ~isfield(train,'distribution') || strcmp(train.distribution,'Gaussian')
        hmm.cache.ldetWishB{k} = ldetWishB;
        hmm.cache.PsiWish_alphasum{k} = PsiWish_alphasum;
        hmm.cache.C{k} = C;
        if episodic && (strcmp(train.covtype,'diag') || strcmp(train.covtype,'full'))
            hmm.cache.iC{k} = iC;
        end
    end
end

if hmm.train.acrosstrial_constrained % state probabilities are shared across trials

    ttrial = T(1)-order; L = zeros(N*ttrial,K);
    if episodic
        [Gammastar,Xistar,LL] = fb_Gamma_inference_ehmm(XX,hmm,residuals,Gamma0,Xi0,T);
    else
        for j = 1:N
            t = (1:ttrial) + (j-1)*ttrial;
            t2 = (1:ttrial+order) + (j-1)*(ttrial+order);
            try
                L(t2,:) = obslike([],hmm,residuals(t,:),XX(t,:),hmm.cache,cv);
            catch
                error('obslike function is giving trouble - Out of precision?')
            end
        end
        L = squeeze(sum(reshape(L,[ttrial+order,N,K]),2));
        if mixture_model
            Gammastar = id_Gamma_inference(L,hmm.Pi,order); Xistar = [];
        else
            [Gammastar,Xistar,LL] = fb_Gamma_inference_sub(L,hmm.P,hmm.Pi,T(1),...
                order,hmm.train.Gamma_constraint);
        end
    end
    Gamma = zeros(ttrial,N,K);
    if episodic
        Xi = zeros(ttrial-1,N,K,4);
    elseif mixture_model
        Xi = zeros(ttrial-1,N,K,K);
    end
    for t = 1:ttrial
        Gamma(t,:,:) = repmat(Gammastar(t,:),N,1);
        if t==ttrial, break; end
        if episodic
            Xi(t,:,:,:) = reshape(repmat(Xistar(t,:,:),N,1),[1,N,K,4]);
        elseif mixture_model
            Xi(t,:,:,:) = reshape(repmat(Xistar(t,:,:),N,1),[1,N,K,K]);
        end
    end
    Gamma = reshape(Gamma,ttrial*N,K);
    if episodic
        Xi = reshape(Xi,(ttrial-1)*N,K,2,2);
    elseif mixture_model
        Xi = reshape(Xi,(ttrial-1)*N,K,K);
    end

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

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

    % to avoid the entire data being copied entirely in each parfor loop
    XX_copy = cell(N,1); residuals_copy = cell(N,1);
    Gamma0_copy = cell(N,1); Xi0_copy = cell(N,1);
    C_copy = cell(N,1);
    for j = 1:N
        t0 = sum(T(1:j-1)); s0 = t0 - order*(j-1); s02 = t0 - (order+1)*(j-1);
        XX_copy{j} = XX(s0+1:s0+T(j)-order,:);
        if ~do_HMM_pca
            residuals_copy{j} = residuals(s0+1:s0+T(j)-order,:);
        end
        if isfield(data,'C')
            C_copy{j} = data.C(s0+1:s0+T(j)-order,:);
        end
        if ~isempty(Gamma0)
            Gamma0_copy{j} = Gamma0(s0+1:s0+T(j)-order,:);
        end
        if ~isempty(Xi0)
            Xi0_copy{j} = Xi(s02+1:s02+T(j)-order-1,:,:,:);
        end
    end; clear data; clear Gamma0; clear Xi0; clear XX; clear residuals

    parfor j = 1:N
        xit = []; llt = [];
        if order>0
            R = [zeros(order,size(residuals_copy{j},2)); residuals_copy{j}];
            if ~isempty(C_copy{j})
                C = [zeros(order,K); C_copy{j}];
            else
                C = NaN(size(R,1),K);
            end
        else
            if do_HMM_pca
                R = XX_copy{j};
            else
                R = residuals_copy{j};
            end
            if ~isempty(C_copy{j})
                C = C_copy{j};
            else
                C = NaN(size(R,1),K);
            end
        end
        % we jump over the fixed parts of the chain
        t = order+1;
        nsegments = computeNoSegments(T(j),t,C);
        xi = cell(nsegments,1); gamma = cell(nsegments,1);
        ll = cell(nsegments,1); ns = 1;
        while t <= T(j)
            if isnan(C(t,1)), no_c = find(~isnan(C(t:T(j),1)));
            else, no_c = find(isnan(C(t:T(j),1)));
            end
            if t>order+1
                if isempty(no_c), slicer = (t-1):T(j); %slice = (t-order-1):T(in);
                else, slicer = (t-1):(no_c(1)+t-2); %slice = (t-order-1):(no_c(1)+t-2);
                end
            else
                if isempty(no_c), slicer = t:T(j); %slice = (t-order):T(in);
                else, slicer = t:(no_c(1)+t-2); %slice = (t-order):(no_c(1)+t-2);
                end
            end
            slicepoints = slicer - order;
            XXt = XX_copy{j}(slicepoints,:);
            if isnan(C(t,1))
                if episodic
                    slicer2 = slicer(1:end-1);
                    Gamma0t = Gamma0_copy{j}(slicer,:);
                    Xi0t = []; if ~isempty(Xi0_copy{j}), Xi0t = Xi0_copy{j}(slicer2,:); end
                    [gammat,xit,llt] = fb_Gamma_inference_ehmm(XXt,hmm,R(slicer,:),Gamma0t,Xi0t);
                else
                    [gammat,xit,llt] = ...
                        fb_Gamma_inference(XXt,hmm,R(slicer,:),slicepoints,hmm.train.Gamma_constraint,cv);
                end
            else
                gammat = zeros(length(slicer),K);
                if t==order+1, gammat(1,:) = C(slicer(1),:); end
                if ~mixture_model
                    if episodic, xit = zeros(length(slicer)-1, K, 4);
                    else, xit = zeros(length(slicer)-1, K^2);
                    end
                end
                for i = 2:length(slicer)
                    gammat(i,:) = C(slicer(i),:);
                    if ~mixture_model
                        if episodic
                            for k = 1:K
                                gg = [gammat(i-1,k) (1-gammat(i-1,k))];
                                xitr = gg' * gg;
                                xit(i-1,k,:) = xitr(:)';
                            end
                        else
                            xitr = gammat(i-1,:)' * gammat(i,:) ;
                            xit(i-1,:) = xitr(:)';
                        end
                    end
                end
            end
            if t>order+1
                gammat = gammat(2:end,:);
            end
            if ~mixture_model
                xi{ns} = xit;
            end
            gamma{ns} = gammat;
            ll{ns} = llt;
            ns = ns + 1;
            if isempty(no_c), break;
            else, t = no_c(1)+t-1;
            end
        end
        Gamma{j} = cell2mat(gamma);
        Gammasum(j,:) = sum(Gamma{j});
        LL{j} = cell2mat(ll);
        if ~mixture_model
            if episodic
                Xi{j} = reshape(cell2mat(xi),T(j)-order-1,K,2,2);
            else
                Xi{j} = reshape(cell2mat(xi),T(j)-order-1,K,K);
            end
        end
    end

else

    for j = 1:N % this is exactly the same than the code above but changing parfor by for

        t0 = sum(T(1:j-1)); s0 = t0 - order*(j-1); s02 = t0 - (order+1)*(j-1);
        if order > 0
            R = [zeros(order,size(residuals,2)); residuals(s0+1:s0+T(j)-order,:)];
            if isfield(data,'C')
                C = [zeros(order,K); data.C(s0+1:s0+T(j)-order,:)];
            else
                C = NaN(size(R,1),K);
            end
        else %s0==t0
            if do_HMM_pca
                R = XX(s0+1:s0+T(j),:);
            else
                R = residuals(s0+1:s0+T(j),:);
            end
            if isfield(data,'C')
                C = data.C(s0+1:s0+T(j),:);
            else
                C = NaN(size(R,1),K);
            end
        end
        % we jump over the fixed parts of the chain
        t = order+1;
        nsegments = computeNoSegments(T(j),t,C);
        xi = cell(nsegments,1); gamma = cell(nsegments,1);
        ll = cell(nsegments,1); ns = 1;
        while t <= T(j)
            if isnan(C(t,1)), no_c = find(~isnan(C(t:T(j),1)));
            else, no_c = find(isnan(C(t:T(j),1)));
            end
            if t > order+1
                if isempty(no_c), slicer = (t-1):T(j); %slice = (t-order-1):T(in);
                else, slicer = (t-1):(no_c(1)+t-2); %slice = (t-order-1):(no_c(1)+t-2);
                end
            else
                if isempty(no_c), slicer = t:T(j); %slice = (t-order):T(in);
                else, slicer = t:(no_c(1)+t-2); %slice = (t-order):(no_c(1)+t-2);
                end
            end
            slicepoints = slicer + s0 - order;
            XXt = XX(slicepoints,:);
            if isnan(C(t,1))
                if episodic
                    slicepoints2 = slicer(1:end-1) + s02 - order;
                    Gamma0t = Gamma0(slicepoints,:);
                    Xi0t = []; if ~isempty(Xi0), Xi0t = Xi0(slicepoints2,:,:,:); end
                    [gammat,xit,llt] = ...
                        fb_Gamma_inference_ehmm(XXt,hmm,R(slicer,:),Gamma0t,Xi0t);
                else
                    [gammat,xit,llt] = ...
                        fb_Gamma_inference(XXt,hmm,R(slicer,:),slicepoints,hmm.train.Gamma_constraint,cv);
                end
                if any(isnan(gammat(:))) % this will never come up - we treat it within fb_Gamma_inference
                    error(['State time course inference returned NaN (out of precision). ' ...
                        'There are probably extreme events in the data'])
                end
            else
                gammat = zeros(length(slicer),K);
                llt = NaN(length(slicer),1);
                if t==order+1, gammat(1,:) = C(slicer(1),:); end
                if ~mixture_model
                    if episodic, xit = zeros(length(slicer)-1, K, 4);
                    else, xit = zeros(length(slicer)-1, K^2);
                    end
                end
                for i = 2:length(slicer)
                    gammat(i,:) = C(slicer(i),:);
                    if ~mixture_model
                        if episodic
                            for k = 1:K
                                gg = [gammat(i-1,k) (1-gammat(i-1,k))];
                                xitr = gg' * gg;
                                xit(i-1,k,:) = xitr(:)';
                            end
                        else
                            xitr = gammat(i-1,:)' * gammat(i,:) ;
                            xit(i-1,:) = xitr(:)';
                        end
                    end
                end
            end
            if t > order+1
                gammat = gammat(2:end,:);
            end
            if ~mixture_model
                xi{ns} = xit;
            end
            gamma{ns} = gammat;
            ll{ns} = llt;
            ns = ns + 1;
            if isempty(no_c), break;
            else, t = no_c(1)+t-1;
            end
        end
        Gamma{j} = cell2mat(gamma);
        Gammasum(j,:) = sum(Gamma{j});
        LL{j} = cell2mat(ll);
        if ~mixture_model
            if episodic
                Xi{j} = reshape(cell2mat(xi),T(j)-order-1,K,2,2);
            else
                Xi{j} = reshape(cell2mat(xi),T(j)-order-1,K,K);
            end
        end
    end

end

% join
if ~hmm.train.acrosstrial_constrained
    Gamma = cell2mat(Gamma);
    LL = cell2mat(LL);
    if ~mixture_model, Xi = cell2mat(Xi); end
end

% orthogonalise = 1;
% Gamma0 = Gamma;
% if orthogonalise && hmm.train.tuda
%     T = length(Gamma) / length(T) * ones(length(T),1);
%     Gamma = reshape(Gamma,[T(1) length(T) size(Gamma,2) ]);
%     Y = reshape(residuals(:,end),[T(1) length(T)]);
%     for t = 1:T(1)
%        y = Y(t,:)';
%        for k = 1:size(Gamma,3)
%            x = Gamma(t,:,k)';
%            b = y \ x;
%            Gamma(t,:,k) = Gamma(t,:,k) - (y * b)';
%        end
%     end
%     Gamma = reshape(Gamma,[T(1)*length(T) size(Gamma,3) ]);
%     Gamma = rdiv(Gamma,sum(Gamma,2));
%     Gamma = Gamma - min(Gamma(:));
%     Gamma = rdiv(Gamma,sum(Gamma,2));
% end

end

function nsegments = computeNoSegments(Tj,t0,C)
t = t0; nsegments = 0;
while t <= Tj
    if isnan(C(t,1)), no_c = find(~isnan(C(t:Tj,1)));
    else, no_c = find(isnan(C(t:Tj,1)));
    end
    nsegments = nsegments + 1;
    if isempty(no_c), break;
    else, t = no_c(1)+t-1;
    end
end
end


function exit_reason = checkSimulationStopConditions(n_cells, Phis_t, cs_bar_t, T_t, param)
% checkSimulationStopConditions checks if a particular stop condition for the simulation is met

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

exit_reason = 0;
% Check stop conditions for each cell
for i=1:n_cells
    Phis_pos_cc_t = 1.5*Phis_t{i}(end,1) - 0.5*Phis_t{i}(end,2);
    Phis_neg_cc_t = 1.5*Phis_t{i}(end,end) - 0.5*Phis_t{i}(end,end-1);
    voltage = Phis_pos_cc_t - Phis_neg_cc_t;
    Sout    = internalSOCestimate(cs_bar_t,param,i);
    max_layer_temperature = max(T_t{1}(end,:));
    % Break conditions.
    if(voltage<param{i}.CutoffVoltage)
        if param{i}.suppress_status_prints == 0
            fprintf('\nCell #%d  below its Cutoff voltage. Stopping ...\n',i);
        end
        if exit_reason==0
            exit_reason=[];
        end
        exit_reason = [exit_reason;1];
    end

    if(voltage>param{i}.CutoverVoltage)
        if param{i}.suppress_status_prints == 0
            fprintf('\nCell #%d  above its Cutover voltage. Stopping ...\n',i);
        end
        if exit_reason==0
            exit_reason=[];
        end
        exit_reason = [exit_reason;2];
    end

    if(Sout<param{i}.CutoffSOC)
        if param{i}.suppress_status_prints == 0
            fprintf('\nCell #%d  below its Cutoff SOC. Stopping ...\n',i);
        end
        if exit_reason==0
            exit_reason=[];
        end
        exit_reason = [exit_reason;3];
    end

    if(Sout>param{i}.CutoverSOC)
        if param{i}.suppress_status_prints == 0
            fprintf('\nCell #%d  above its Cutover SOC. Stopping ...\n',i);
        end
        if exit_reason==0
            exit_reason=[];
        end
        exit_reason = [exit_reason;4];
    end

    if(max_layer_temperature > 0.99*param{i}.Tmax)
        if param{i}.suppress_status_prints == 0
            fprintf('\nCell #%d  above its Maximum Permitted Temperature. Stopping ...\n',i);
        end
        if exit_reason==0
            exit_reason=[];
        end
        exit_reason = [exit_reason;5];
    end

    if(param{i}.enable_csneg_Saturation_limit == 1)
        if(param{i}.SolidPhaseDiffusion~=3)
            max_cs_surface_neg = max(cs_bar_t{i}(end,param{i}.Np+1:end));
        else
            max_cs_surface_neg = max(cs_bar_t{i}(end, (param{i}.Np*param{i}.Nr_p) +1:end));
        end

        if (max_cs_surface_neg > param{i}.cs_sat_thresh*param{i}.cs_neg_saturation)
            if param{i}.suppress_status_prints == 0
                fprintf('\n\nCell #%d  above its safe surface concentration (saturation threshold) Stopping ...\n\n',i);
            end
            if exit_reason==0
                exit_reason=[];
            end
            exit_reason = [exit_reason;6];
        end
    end
end
end


function varargout = Echo_cancel(varargin)
% ECHO_CANCEL MATLAB code for Echo_cancel.fig
%      ECHO_CANCEL, by itself, creates a new ECHO_CANCEL or raises the existing
%      singleton*.
%
%      H = ECHO_CANCEL returns the handle to a new ECHO_CANCEL or the handle to
%      the existing singleton*.
%
%      ECHO_CANCEL('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in ECHO_CANCEL.M with the given input arguments.
%
%      ECHO_CANCEL('Property','Value',...) creates a new ECHO_CANCEL or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before Echo_cancel_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to Echo_cancel_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help Echo_cancel

% Last Modified by GUIDE v2.5 30-Nov-2017 13:55:03

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @Echo_cancel_OpeningFcn, ...
                   'gui_OutputFcn',  @Echo_cancel_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before Echo_cancel is made visible.
function Echo_cancel_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to Echo_cancel (see VARARGIN)

% Choose default command line output for Echo_cancel
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);
global FilterType;
FilterType=0;
global std_rls;
global std_lms;
global std_fxlms;
global std_nlms;
global std_ap;
std_rls=0;
std_lms=0;
std_fxlms=0;
std_nlms=0;
std_ap=0;
global corr_rls;
global corr_lms;
global corr_fxlms;
global corr_nlms;
global corr_ap;
corr_rls=0;
corr_lms=0;
corr_fxlms=0;
corr_nlms=0;
corr_ap=0;

% UIWAIT makes Echo_cancel wait for user response (see UIRESUME)
% uiwait(handles.figure1);

% --- Outputs from this function are returned to the command line.
function varargout = Echo_cancel_OutputFcn(hObject, eventdata, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;

% --- Executes on button press in pushbutton1.
function pushbutton1_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
global x;
global y;
global res;
global Fs;
global std_rls;
global std_lms;
global std_fxlms;
global std_nlms;
global std_ap;
global corr_rls;
global corr_lms;
global corr_fxlms;
global corr_nlms;
global corr_ap;
[x,Fs] = audioread('handel.wav');
[y,Fs]=audioread('handel_echo.wav');
length=get(handles.edit1,'String');
length=str2num(length);
ff=get(handles.edit2,'String');
ff=str2double(ff);
mu=get(handles.edit3,'String');
mu=str2double(mu);
global FilterType;
switch FilterType
    case 0
        ha=dsp.RLSFilter('Length',length,'ForgettingFactor',ff);
        [res,e] = ha(x,y);
        std_rls=std(e,1);
        corr_rls=corr(x,res);
    case 1
        ha=dsp.LMSFilter('Length',length,'StepSize',mu);
        [res,e] = ha(x,y);
        std_lms=std(e,1);
        corr_lms=corr(x,res);
    case 2
        ha=dsp.FilteredXLMSFilter('Length',length,'StepSize',mu);
        [res,e] = ha(x,y);
        std_fxlms=std(e,1);
        corr_fxlms=corr(x,res);
    case 3
        ha=dsp.LMSFilter('Length',length,'Method','Normalized LMS','StepSize',mu);
        [res,e] = ha(x,y);
        std_nlms=std(e,1);
        corr_nlms=corr(x,res);
    case 4
        ha=dsp.AffineProjectionFilter('Length',length,'StepSize',mu);
        [res,e] = ha(x,y);
        std_ap=std(e,1);
        corr_ap=corr(x,res);
end
audiowrite('handle Regenerated.wav',res,Fs);
set(handles.pushbutton4,'Enable','on');
set(handles.radiobutton1,'Enable','on');
set(handles.radiobutton1,'Value',0);
%axes(handles.axes1);
%spectrogram(x);
plot(handles.axes1,x);
title(handles.axes1,'Original Signal');
xlabel(handles.axes1,'Time Index'); ylabel(handles.axes1,'Signal Value');
plot(handles.axes2,y);
title(handles.axes2,'Echoed Signal');
xlabel(handles.axes2,'Time Index'); ylabel(handles.axes2,'Signal Value');
plot(handles.axes3,res);
title(handles.axes3,'Regenerated Signal');
xlabel(handles.axes3,'Time Index'); ylabel(handles.axes3,'Signal Value');
std_all=[std_rls,std_lms,std_nlms,std_fxlms,std_ap];
corr_all=[corr_rls,corr_lms,corr_nlms,corr_fxlms,corr_ap];
%bar(handles.axes4,mse_all);
bar(handles.axes4,std_all,0.4);
gap1=get(handles.axes4,'YTick');
ylength1=get(handles.axes4,'YLim');
ylim(handles.axes4,[0 ylength1(1,2)*1.1]);
if std_rls>0
    text(handles.axes4,1-0.3,std_rls+gap1(1,2)*0.3,'RLS');
end
if std_lms>0
    text(handles.axes4,2-0.3,std_lms+gap1(1,2)*0.3,'LMS');
end
if std_fxlms>0
    text(handles.axes4,4-0.3,std_fxlms+gap1(1,2)*0.3,'FX');
end
if std_nlms>0
    text(handles.axes4,3-0.4,std_nlms+gap1(1,2)*0.3,'NLMS');
end
if std_ap>0
    text(handles.axes4,5-0.3,std_ap+gap1(1,2)*0.3,'Affine');
end
title(handles.axes4,{'Standard deviation';'the smaller the better'});
xlabel(handles.axes4,'Filter type'); ylabel(handles.axes4,'Value');

bar(handles.axes5,corr_all,0.4);
gap2=get(handles.axes5,'YTick');
ylength2=get(handles.axes5,'YLim');
ylim(handles.axes5,[0 ylength2(1,2)*1.1]);
if corr_rls>0
    text(handles.axes5,1-0.3,corr_rls+gap2(1,2)*0.3,'RLS');
end
if corr_lms>0
    text(handles.axes5,2-0.3,corr_lms+gap2(1,2)*0.3,'LMS');
end
if corr_fxlms>0
    text(handles.axes5,4-0.3,corr_fxlms+gap2(1,2)*0.3,'FX');
end
if corr_nlms>0
    text(handles.axes5,3-0.5,corr_nlms+gap2(1,2)*0.3,'NLMS');
end
if corr_ap>0
    text(handles.axes5,5-0.3,corr_ap+gap2(1,2)*0.3,'Affine');
end
title(handles.axes5,{'Correlation coefficient';'the bigger the better'});
xlabel(handles.axes5,'Filter type'); ylabel(handles.axes5,'Value');

% --- Executes on button press in pushbutton2.
function pushbutton2_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
global x;
global Fs;
sound(x,Fs);
pause(10);

% --- Executes on button press in pushbutton3.
function pushbutton3_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
global y;
global Fs;
sound(y,Fs);
pause(10);

% --- Executes on button press in pushbutton4.
function pushbutton4_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton4 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
global res;
global Fs;
sound(res,Fs);
pause(10);

% --- Executes on button press in radiobutton1.
function radiobutton1_Callback(hObject, eventdata, handles)
% hObject    handle to radiobutton1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of radiobutton1
global x;
global y;
global res;
if get(handles.radiobutton1,'Value')
    axes(handles.axes1);
    spectrogram(x);
    title('Original Signal');
    axes(handles.axes2);
    spectrogram(y);
    title('Echoed Signal');
    axes(handles.axes3);
    spectrogram(res);
    title('Regenerated Signal');
else
    plot(handles.axes1,x);
    title(handles.axes1,'Original Signal');
    xlabel(handles.axes1,'Time Index'); ylabel(handles.axes1,'Signal Value');
    plot(handles.axes2,y);
    title(handles.axes2,'Echoed Signal');
    xlabel(handles.axes2,'Time Index'); ylabel(handles.axes2,'Signal Value');
    plot(handles.axes3,res);
    title(handles.axes3,'Regenerated Signal');
    xlabel(handles.axes3,'Time Index'); ylabel(handles.axes3,'Signal Value');
end
guidata(hObject,handles)

% --- Executes on selection change in popupmenu1.
function popupmenu1_Callback(hObject, eventdata, handles)
% hObject    handle to popupmenu1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% Determine the selected data set.
global FilterType;
set(handles.pushbutton4,'Enable','off');
set(handles.radiobutton1,'Enable','off');
str = get(hObject, 'String');
val = get(hObject,'Value');
% Set current data to the selected data set.
switch str{val}
    case 'RLS' % User selects peaks.
        FilterType=0;
        set(handles.edit1,'String','4');
        set(handles.edit2,'String','1');
        set(handles.edit2,'Enable','on');
        set(handles.edit3,'Enable','off');
    case 'LMS' % User selects membrane.
        FilterType=1;
        set(handles.edit1,'String','2');
        set(handles.edit3,'String','0.022');
        set(handles.edit2,'Enable','off');
        set(handles.edit3,'Enable','on');
    case 'FilteredXLMS' % User selects sinc.
        FilterType=2;
        set(handles.edit1,'String','2');
        set(handles.edit3,'String','0.002');
        set(handles.edit2,'Enable','off');
        set(handles.edit3,'Enable','on');
    case 'NLMS' % User selects membrane.
        FilterType=3;
        set(handles.edit1,'String','4');
        set(handles.edit3,'String','0.022');
        set(handles.edit2,'Enable','off');
        set(handles.edit3,'Enable','on');
    case 'AffineProjectionFilter' % User selects membrane.
        FilterType=4;
        set(handles.edit1,'String','2');
        set(handles.edit3,'String','0.022');
        set(handles.edit2,'Enable','off');
        set(handles.edit3,'Enable','on');
end
% Save the handles structure.
guidata(hObject,handles)

% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu1 contents as cell array
%        contents{get(hObject,'Value')} returns selected item from popupmenu1

% --- Executes during object creation, after setting all properties.
function popupmenu1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to popupmenu1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function edit1_Callback(hObject, eventdata, handles)
% hObject    handle to edit1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of edit1 as text
%        str2double(get(hObject,'String')) returns contents of edit1 as a double
value = str2double(get(handles.edit1,'String'));
flag = value > 0
if ~flag
   set(handles.edit1,'String','');
   warndlg('Value should be positive !! Please Enter Again');
% else
%    msgbox('Got the value, On it!');
end
set(handles.pushbutton4,'Enable','off');
set(handles.radiobutton1,'Enable','off');

% --- Executes during object creation, after setting all properties.
function edit1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to edit1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function edit2_Callback(hObject, eventdata, handles)
% hObject    handle to edit2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of edit2 as text
%        str2double(get(hObject,'String')) returns contents of edit2 as a double
value = str2double(get(handles.edit2,'String'));
flag = value >= 0 && value <= 1
if ~flag
   set(handles.edit2,'String','');
   warndlg('Value should be between 0 and 1 !! Please Enter Again');
% else
%    msgbox('Got the value, On it!');
end
set(handles.pushbutton4,'Enable','off');
set(handles.radiobutton1,'Enable','off');

% --- Executes during object creation, after setting all properties.
function edit2_CreateFcn(hObject, eventdata, handles)
% hObject    handle to edit2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function edit3_Callback(hObject, eventdata, handles)
% hObject    handle to edit3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of edit3 as text
%        str2double(get(hObject,'String')) returns contents of edit3 as a double
value = str2double(get(handles.edit3,'String'));
flag = value > 0
if ~flag
   set(handles.edit3,'String','');
   warndlg('Value should be positive !! Please Enter Again');
% else
%    msgbox('Got the value, On it!');
end
set(handles.pushbutton4,'Enable','off');
set(handles.radiobutton1,'Enable','off');

% --- Executes during object creation, after setting all properties.
function edit3_CreateFcn(hObject, eventdata, handles)
% hObject    handle to edit3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes during object creation, after setting all properties.
function axes1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to axes1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: place code in OpeningFcn to populate axes1

% --- Executes on button press in pushbutton5.
function pushbutton5_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton5 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
plot(handles.axes1,5);% To override colorbar of spectrogram
cla(handles.axes1);
reset(handles.axes1);
plot(handles.axes2,5);
cla(handles.axes2);
reset(handles.axes2);
plot(handles.axes3,5);
cla(handles.axes3);
reset(handles.axes3);
cla(handles.axes4);
reset(handles.axes4);
cla(handles.axes5);
reset(handles.axes5);
set(handles.popupmenu1,'Value',1);
set(handles.edit2,'Enable','on');
set(handles.edit3,'Enable','off');
set(handles.pushbutton4,'Enable','off');
set(handles.radiobutton1,'Enable','off');
global FilterType;
FilterType=0;
global std_rls;
global std_lms;
global std_fxlms;
global std_nlms;
global std_ap;
std_rls=0;
std_lms=0;
std_fxlms=0;
std_nlms=0;
std_ap=0;
global corr_rls;
global corr_lms;
global corr_fxlms;
global corr_nlms;
global corr_ap;
corr_rls=0;
corr_lms=0;
corr_fxlms=0;
corr_nlms=0;
corr_ap=0;
set(handles.edit1,'String','4');
set(handles.edit2,'String','1');
set(handles.edit3,'String','0.022');

% --- Executes during object deletion, before destroying properties.
function axes3_DeleteFcn(hObject, eventdata, handles)
% hObject    handle to axes3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


function [isrcs, idets, measures, measure_type] = nst_unformat_channels(channel_labels, warn_bad_channels)
% NST_UNFORMAT_CHANNELS extract sources, dectectors and measures information
% from channel labels with *homogeneous* type (eg wavelength or Hb).
%
%   [ISRCS, IDETS, MEAS, CHAN_TYPE] = NST_UNFORMAT_CHANNELS(CHANNEL_LABELS)
%       CHANNEL_LABELS (cell array of str):
%           each str is formatted as 'SxDyWLz' or 'SxDyHbt', where:
%               x: source index
%               y: detector index
%               z: wavelength
%               t: Hb type (O, R, T).
%           Consistency is asserted so that:
%               - channels are unique
%               - channel type is homogeneous
%           Warning is issued if:
%               - the number of measures per pair is not homogeneous
%                 eg one pair has only one wavelength
%
%           Examples: S1D2WL685, S01D7WL830, S3D01HbR
%
%
%        ISRCS (array of int): extracted source indexes
%        IDETS (array of int): extracted detector indexes
%        MEAS (array of int | cell array of str): extracted measure values
%        CHAN_TYPE (array of int): channel type (see NST_CHANNEL_TYPES for enum).
%
%   See also NST_UNFORMAT_CHANNEL, NST_CHANNEL_TYPES, NST_FORMAT_CHANNEL

assert(iscellstr(channel_labels));

if nargin < 2
    warn_bad_channels = 0;
end

nb_channels = length(channel_labels);
isrcs = zeros(1, nb_channels);
idets = zeros(1, nb_channels);
measures = cell(1, nb_channels);
mtypes = zeros(1, nb_channels);
reformated_channels = cell(1, nb_channels);
for ichan=1:nb_channels
    [isrc, idet, meas, mtype] = nst_unformat_channel(channel_labels{ichan}, warn_bad_channels);
    isrcs(ichan) = isrc;
    idets(ichan) = idet;
    measures{ichan} = meas;
    mtypes(ichan) = mtype;
    if ~isnan(isrc) && ~isnan(idet)
        % reformat channels to make sure formatting is consistent
        % -> allow proper duplicate detection after
        reformated_channels{ichan} = nst_format_channel(isrc, idet, meas);
    else
        reformated_channels{ichan} = '';
    end
end

% check uniqueness
[~, i_unique] = unique(reformated_channels);
duplicates = reformated_channels;
duplicates(i_unique) = [];
duplicates(strcmp(duplicates, '')) = []; %remove unrecognized channels
i_duplicates = ismember(reformated_channels, unique(duplicates));
if ~isempty(duplicates)
    msg = sprintf('Duplicated channels: "%s". Indexes: %s', ...
                  strjoin(channel_labels(i_duplicates), ', '), num2str(i_duplicates));
    throw(MException('NIRSTORM:NonUniqueChannels', msg));
end

% check homogeneity of channel type
mtypes = mtypes(~isnan(mtypes));
if length(unique(mtypes)) > 1
    throw(MException('NIRSTORM:NonHomogeneousMeasure', 'Measure type is not homogeneous.'));
end

measure_types = nst_measure_types();
measure_type = mtypes(1);
if measure_type == measure_types.WAVELENGTH
   measures = cell2mat(measures);
end

%% Check number of measures per pair
max_idet = (max(idets)+1);
pairs_hash = isrcs(~isnan(isrcs)) * max_idet + idets(~isnan(isrcs));
unique_pairs_hash = unique(pairs_hash);
mcounts = sparse(ones(1, length(unique_pairs_hash)), unique_pairs_hash, ...
                      ones(1, length(unique_pairs_hash)), 1, max(unique_pairs_hash), ...
                      nb_channels);
if ~iscell(measures)
    all_measures = unique(measures(~isnan(measures)));
else
    all_measures = unique(measures(cellfun(@(e) ischar(e) || ~isnan(e), measures)));
end
for ichan=1:nb_channels
    if ~isnan(isrcs(ichan))
        if measure_type == measure_types.WAVELENGTH
            measure_hash = find(measures(ichan)==all_measures);
        elseif measure_type == measure_types.HB
            measure_hash = find(strcmp(measures(ichan),all_measures));
        end
        h_idx = isrcs(ichan) * max_idet + idets(ichan);
        mcounts(h_idx) = mcounts(h_idx) + measure_hash;
    end
end

% pairs with too many measures:
i_inconsistant_measures = (mcounts ~= sum(1:length(all_measures)) + 1) & (mcounts ~= 0);

if nnz(i_inconsistant_measures) > 0
    [isrc_incon,idet_incon,counts] = find(i_inconsistant_measures);
    inconsistent_pairs = cell(1, length(isrc_incon));
    for ipair=1:length(isrc_incon)
        inconsistent_pairs{ipair} = sprintf('S%dD%d', isrc_incon(ipair), idet_incon(ipair));
    end
    warning('Inconsistent measure(s) for pair(s): %s', strjoin(inconsistent_pairs, ', '));
end

end