classdef Violin < handle
    % Violin creates violin plots for some data
    %   A violin plot is an easy to read substitute for a box plot
    %   that replaces the box shape with a kernel density estimate of
    %   the data, and optionally overlays the data points itself.
    %   It is also possible to provide two sets of data which are supposed
    %   to be compared by plotting each column of the two datasets together
    %   on each side of the violin.
    %
    %   Additional constructor parameters include the width of the
    %   plot, the bandwidth of the kernel density estimation, the
    %   X-axis position of the violin plot, and the categories.
    %
    %   Use violinplot for a
    %   boxplot-like wrapper for
    %   interactive plotting.
    %
    %   See for more information on Violin Plots:
    %   J. L. Hintze and R. D. Nelson, "Violin plots: a box
    %   plot-density trace synergism," The American Statistician, vol.
    %   52, no. 2, pp. 181-184, 1998.
    %
    % Violin Properties:
    %    ViolinColor    - Fill color of the violin area and data points.
    %                     Can be either a matrix nx3 or an array of up to two
    %                     cells containing nx3 matrices.
    %                     Defaults to the next default color cycle.
    %    ViolinAlpha    - Transparency of the violin area and data points.
    %                     Can be either a single scalar value or an array of
    %                     up to two cells containing scalar values.
    %                     Defaults to 0.3.
    %    EdgeColor      - Color of the violin area outline.
    %                     Defaults to [0.5 0.5 0.5]
    %    BoxColor       - Color of the box, whiskers, and the outlines of
    %                     the median point and the notch indicators.
    %                     Defaults to [0.5 0.5 0.5]
    %    MedianColor    - Fill color of the median and notch indicators.
    %                     Defaults to [1 1 1]
    %    ShowData       - Whether to show data points.
    %                     Defaults to true
    %    ShowNotches    - Whether to show notch indicators.
    %                     Defaults to false
    %    ShowMean       - Whether to show mean indicator.
    %                     Defaults to false
    %    ShowBox        - Whether to show the box.
    %                     Defaults to true
    %    ShowMedian     - Whether to show the median indicator.
    %                     Defaults to true
    %    ShowWhiskers   - Whether to show the whiskers
    %                     Defaults to true
    %    HalfViolin     - Whether to do a half violin(left, right side) or
    %                     full. Defaults to full.
    %    QuartileStyle - Option on how to display quartiles, with a
    %                     boxplot, shadow or none. Defaults to boxplot.
    %    DataStyle      - Defines the style to show the data points. Opts:
    %                     'scatter', 'histogram' or 'none'. Default is 'scatter'.
    %
    %
    % Violin Children:
    %    ScatterPlot    - scatter plot of the data points
    %    ScatterPlot2   - scatter second plot of the data points
    %    ViolinPlot     - fill plot of the kernel density estimate
    %    ViolinPlot2    - fill second plot of the kernel density estimate
    %    BoxPlot        - fill plot of the box between the quartiles
    %    WhiskerPlot    - line plot between the whisker ends
    %    MedianPlot     - scatter plot of the median (one point)
    %    NotchPlots     - scatter plots for the notch indicators
    %    MeanPlot       - line plot at mean value

    % Copyright (c) 2016, Bastian Bechtold
    % This code is released under the terms of the BSD 3-clause license

    properties (Access=public)
        ScatterPlot     % scatter plot of the data points
        ScatterPlot2    % comparison scatter plot of the data points
        ViolinPlot      % fill plot of the kernel density estimate
        ViolinPlot2     % comparison fill plot of the kernel density estimate
        BoxPlot         % fill plot of the box between the quartiles
        WhiskerPlot     % line plot between the whisker ends
        MedianPlot      % scatter plot of the median (one point)
        NotchPlots      % scatter plots for the notch indicators
        MeanPlot        % line plot of the mean (horizontal line)
        HistogramPlot   % histogram of the data
        ViolinPlotQ     % fill plot of the Quartiles as shadow
    end

    properties (Dependent=true)
        ViolinColor         % fill color of the violin area and data points
        ViolinAlpha         % transparency of the violin area and data points
        MarkerSize          % marker size for the data dots
        MedianMarkerSize    % marker size for the median dot
        LineWidth           % linewidth of the median plot
        EdgeColor           % color of the violin area outline
        BoxColor            % color of box, whiskers, and median/notch edges
        BoxWidth            % width of box between the quartiles in axis space (default 10% of Violin plot width, 0.03)
        MedianColor         % fill color of median and notches
        ShowData            % whether to show data points
        ShowNotches         % whether to show notch indicators
        ShowMean            % whether to show mean indicator
        ShowBox             % whether to show the box
        ShowMedian          % whether to show the median line
        ShowWhiskers        % whether to show the whiskers
        HalfViolin          % whether to do a half violin(left, right side) or full
    end

    methods
        function obj = Violin(data, pos, varargin)
            %Violin plots a violin plot of some data at pos
            %   VIOLIN(DATA, POS) plots a violin at x-position POS for
            %   a vector of DATA points.
            %
            %   VIOLIN(..., 'PARAM1', val1, 'PARAM2', val2, ...)
            %   specifies optional name/value pairs:
            %     'Width'        Width of the violin in axis space.
            %                    Defaults to 0.3
            %     'Bandwidth'    Bandwidth of the kernel density
            %                    estimate. Should be between 10% and
            %                    40% of the data range.
            %     'ViolinColor'  Fill color of the violin area
            %                    and data points.Can be either a matrix
            %                    nx3 or an array of up to two cells
            %                    containing nx3 matrices.
            %     'ViolinAlpha'  Transparency of the violin area and data
            %                    points. Can be either a single scalar
            %                    value or an array of up to two cells
            %                    containing scalar values. Defaults to 0.3.
            %     'MarkerSize'   Size of the data points, if shown.
            %                    Defaults to 24
            % 'MedianMarkerSize' Size of the median indicator, if shown.
            %                    Defaults to 36
            %     'EdgeColor'    Color of the violin area outline.
            %                    Defaults to [0.5 0.5 0.5]
            %     'BoxColor'     Color of the box, whiskers, and the
            %                    outlines of the median point and the
            %                    notch indicators. Defaults to
            %                    [0.5 0.5 0.5]
            %     'MedianColor'  Fill color of the median and notch
            %                    indicators. Defaults to [1 1 1]
            %     'ShowData'     Whether to show data points.
            %                    Defaults to true
            %     'ShowNotches'  Whether to show notch indicators.
            %                    Defaults to false
            %     'ShowMean'     Whether to show mean indicator.
            %                    Defaults to false
            %     'ShowBox'      Whether to show the box
            %                    Defaults to true
            %     'ShowMedian'   Whether to show the median line
            %                    Defaults to true
            %     'ShowWhiskers' Whether to show the whiskers
            %                    Defaults to true
            %     'HalfViolin'   Whether to do a half violin(left, right side) or
            %                    full. Defaults to full.
            %   'QuartileStyle'  Option on how to display quartiles, with a
            %                    boxplot or as a shadow. Defaults to boxplot.
            %     'DataStyle'    Defines the style to show the data points. Opts:
            %                   'scatter', 'histogram' or 'none'. Default is 'Scatter'.

            st = dbstack; % get the calling function for reporting errors
            namefun = st.name;
            args = obj.checkInputs(data, pos, varargin{:});

            if length(data)==1
                data2 = [];
                data = data{1};

            else
                data2 = data{2};
                data = data{1};
            end

            if isempty(args.ViolinColor)
                Release= strsplit(version('-release'), {'a','b'}); %Check release
                if str2num(Release{1})> 2019 || strcmp(version('-release'), '2019b')
                     C = colororder;
                else
                     C = lines;
                end

                if pos > length(C)
                    C = lines;
                end
                args.ViolinColor = {repmat(C,ceil(size(data,2)/length(C)),1)};
            end

            data = data(not(isnan(data)));
            data2 = data2(not(isnan(data2)));
            if numel(data) == 1
                obj.MedianPlot = scatter(pos, data, 'filled');
                obj.MedianColor = args.MedianColor;
                obj.MedianPlot.MarkerEdgeColor = args.EdgeColor;
                return
            end

            hold('on');

            %% Calculate kernel density estimation for the violin
            [density, value, width] = obj.calcKernelDensity(data, args.Bandwidth, args.Width);

            % also calculate the kernel density of the comparison data if
            % provided
            if ~isempty(data2)
                [densityC, valueC, widthC] = obj.calcKernelDensity(data2, args.Bandwidth, args.Width);
            end

            %% Plot the data points within the violin area
            if length(density) > 1
                [~, unique_idx] = unique(value);
                jitterstrength = interp1(value(unique_idx), density(unique_idx)*width, data, 'linear','extrap');
            else % all data is identical:
                jitterstrength = density*width;
            end
            if isempty(data2) % if no comparison data
                jitter = 2*(rand(size(data))-0.5); % both sides
            else
                jitter = rand(size(data)); % only right side
            end
            switch args.HalfViolin % this is more modular
                case 'left'
                    jitter = -1*(rand(size(data))); %left
                case 'right'
                    jitter = 1*(rand(size(data))); %right
                case 'full'
                    jitter = 2*(rand(size(data))-0.5);
            end
            % Make scatter plot
            switch args.DataStyle
                case 'scatter'
                    if ~isempty(data2)
                        jitter = 1*(rand(size(data))); %right
                        obj.ScatterPlot = ...
                            scatter(pos + jitter.*jitterstrength, data, args.MarkerSize, 'filled');
                        % plot the data points within the violin area
                        if length(densityC) > 1
                            jitterstrength = interp1(valueC, densityC*widthC, data2);
                        else % all data is identical:
                            jitterstrength = densityC*widthC;
                        end
                        jitter = -1*rand(size(data2));% left
                        obj.ScatterPlot2 = ...
                            scatter(pos + jitter.*jitterstrength, data2, args.MarkerSize, 'filled');
                    else
                        obj.ScatterPlot = ...
                            scatter(pos + jitter.*jitterstrength, data, args.MarkerSize, 'filled');

                    end
                case 'histogram'
                    [counts,edges] = histcounts(data, size(unique(data),1));
                    switch args.HalfViolin
                        case 'right'
                            obj.HistogramPlot= plot([pos-((counts')/max(counts))*max(jitterstrength)*2, pos*ones(size(counts,2),1)]',...
                                [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2],'-','LineWidth',1, 'Color', 'k');
                        case 'left'
                            obj.HistogramPlot= plot([pos*ones(size(counts,2),1), pos+((counts')/max(counts))*max(jitterstrength)*2]',...
                                [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2],'-','LineWidth',1, 'Color', 'k');
                        otherwise
                            fprintf([namefun, ' No histogram/bar plot option available for full violins, as it would look overcrowded.\n'])
                    end
                case 'none'
            end

            %% Plot the violin
            halfViol= ones(1, size(density,2));
            if isempty(data2) % if no comparison data
                switch args.HalfViolin
                    case 'right'
                        obj.ViolinPlot =  ... % plot color will be overwritten later
                            fill([pos+density*width halfViol*pos], ...
                            [value value(end:-1:1)], [1 1 1]);
                    case 'left'
                        obj.ViolinPlot =  ... % plot color will be overwritten later
                            fill([halfViol*pos pos-density(end:-1:1)*width], ...
                            [value value(end:-1:1)], [1 1 1]);
                    case 'full'
                        obj.ViolinPlot =  ... % plot color will be overwritten later
                            fill([pos+density*width pos-density(end:-1:1)*width], ...
                            [value value(end:-1:1)], [1 1 1]);
                end
            else
                % plot right half of the violin
                obj.ViolinPlot =  ...
                    fill([pos+density*width pos-density(1)*width], ...
                    [value value(1)], [1 1 1]);
                % plot left half of the violin
                obj.ViolinPlot2 =  ...
                    fill([pos-densityC(end)*widthC pos-densityC(end:-1:1)*widthC], ...
                    [valueC(end) valueC(end:-1:1)], [1 1 1]);
            end

            %% Plot the quartiles within the violin
            quartiles = quantile(data, [0.25, 0.5, 0.75]);
            flat= [halfViol*pos halfViol*pos];
            switch args.QuartileStyle
                case 'shadow'
                    switch args.HalfViolin
                        case 'right'
                            w = [pos+density*width halfViol*pos];
                            h= [value value(end:-1:1)];
                        case 'left'
                            w = [halfViol*pos pos-density(end:-1:1)*width];
                            h= [value value(end:-1:1)];
                        case 'full'
                            w = [pos+density*width pos-density(end:-1:1)*width];
                            h= [value value(end:-1:1)];
                    end
                    w(h<quartiles(1))=flat(h<quartiles(1));
                    w(h>quartiles(3))=flat((h>quartiles(3)));
                    obj.ViolinPlotQ =  ... % plot color will be overwritten later
                        fill(w, ...
                        h, [1 1 1]);
                case 'boxplot'
                    obj.BoxPlot = ... % plot color will be overwritten later
                        fill(pos+[-1,1,1,-1]*args.BoxWidth, ...
                        [quartiles(1) quartiles(1) quartiles(3) quartiles(3)], ...
                        [1 1 1]);
                case 'none'
            end

            %% Plot the data mean
            meanValue = mean(data);
            if length(density) > 1
                [~, unique_idx] = unique(value);
                meanDensityWidth = interp1(value(unique_idx), density(unique_idx), meanValue, 'linear','extrap')*width;
            else % all data is identical:
                meanDensityWidth = density*width;
            end
            if meanDensityWidth<args.BoxWidth/2
                meanDensityWidth=args.BoxWidth/2;
            end
            switch args.HalfViolin
                case 'right'
                    obj.MeanPlot = plot(pos+[0,1].*meanDensityWidth, ...
                        [meanValue, meanValue]);
                case 'left'
                    obj.MeanPlot = plot(pos+[-1,0].*meanDensityWidth, ...
                        [meanValue, meanValue]);
                case 'full'
                    obj.MeanPlot = plot(pos+[-1,1].*meanDensityWidth, ...
                        [meanValue, meanValue]);
            end
            obj.MeanPlot.LineWidth = 1;

            %% Plot the median, notch, and whiskers
            IQR = quartiles(3) - quartiles(1);
            lowhisker = quartiles(1) - 1.5*IQR;
            lowhisker = max(lowhisker, min(data(data > lowhisker)));
            hiwhisker = quartiles(3) + 1.5*IQR;
            hiwhisker = min(hiwhisker, max(data(data < hiwhisker)));
            if ~isempty(lowhisker) && ~isempty(hiwhisker)
                obj.WhiskerPlot = plot([pos pos], [lowhisker hiwhisker]);
            end

            % Median
            obj.MedianPlot = scatter(pos, quartiles(2), args.MedianMarkerSize, [1 1 1], 'filled');

            % Notches
            obj.NotchPlots = ...
                scatter(pos, quartiles(2)-1.57*IQR/sqrt(length(data)), ...
                [], [1 1 1], 'filled', '^');
            obj.NotchPlots(2) = ...
                scatter(pos, quartiles(2)+1.57*IQR/sqrt(length(data)), ...
                [], [1 1 1], 'filled', 'v');

            %% Set graphical preferences
            obj.EdgeColor = args.EdgeColor;
            obj.MedianPlot.LineWidth = args.LineWidth;
            obj.BoxColor = args.BoxColor;
            obj.BoxWidth = args.BoxWidth;
            obj.MedianColor = args.MedianColor;
            obj.ShowData = args.ShowData;
            obj.ShowNotches = args.ShowNotches;
            obj.ShowMean = args.ShowMean;
            obj.ShowBox = args.ShowBox;
            obj.ShowMedian = args.ShowMedian;
            obj.ShowWhiskers = args.ShowWhiskers;

            if not(isempty(args.ViolinColor))
                if size(args.ViolinColor{1},1) > 1
                    ViolinColor{1} = args.ViolinColor{1}(pos,:);
                else
                    ViolinColor{1} = args.ViolinColor{1};
                end
                if length(args.ViolinColor)==2
                    if size(args.ViolinColor{2},1) > 1
                        ViolinColor{2} = args.ViolinColor{2}(pos,:);
                    else
                        ViolinColor{2} = args.ViolinColor{2};
                    end
                else
                    ViolinColor{2} = ViolinColor{1};
                end
            else
                % defaults
                if args.scpltBool
                    ViolinColor{1} = obj.ScatterPlot.CData;
                else
                    ViolinColor{1} = [0 0 0];
                end
                ViolinColor{2} = [0 0 0];
            end
            obj.ViolinColor = ViolinColor;

            if not(isempty(args.ViolinAlpha))
                if length(args.ViolinAlpha{1})>1
                    error('Only scalar values are accepted for the alpha color channel');
                else
                    ViolinAlpha{1} = args.ViolinAlpha{1};
                end
                if length(args.ViolinAlpha)==2
                    if length(args.ViolinAlpha{2})>1
                        error('Only scalar values are accepted for the alpha color channel');
                    else
                        ViolinAlpha{2} = args.ViolinAlpha{2};
                    end
                else
                    ViolinAlpha{2} = ViolinAlpha{1}/2;  % default unless specified
                end
            else
                % default
                ViolinAlpha = {1,1};
            end
            obj.ViolinAlpha = ViolinAlpha;

        end

        %% SET METHODS
        function set.EdgeColor(obj, color)
            if ~isempty(obj.ViolinPlot)
                obj.ViolinPlot.EdgeColor = color;
                obj.ViolinPlotQ.EdgeColor = color;
                if ~isempty(obj.ViolinPlot2)
                    obj.ViolinPlot2.EdgeColor = color;
                end
            end
        end

        function color = get.EdgeColor(obj)
            if ~isempty(obj.ViolinPlot)
                color = obj.ViolinPlot.EdgeColor;
            end
        end

        function set.MedianColor(obj, color)
            obj.MedianPlot.MarkerFaceColor = color;
            if ~isempty(obj.NotchPlots)
                obj.NotchPlots(1).MarkerFaceColor = color;
                obj.NotchPlots(2).MarkerFaceColor = color;
            end
        end

        function color = get.MedianColor(obj)
            color = obj.MedianPlot.MarkerFaceColor;
        end

        function set.BoxColor(obj, color)
            if ~isempty(obj.BoxPlot)
                obj.BoxPlot.FaceColor = color;
                obj.BoxPlot.EdgeColor = color;
                obj.WhiskerPlot.Color = color;
                obj.MedianPlot.MarkerEdgeColor = color;
                obj.NotchPlots(1).MarkerFaceColor = color;
                obj.NotchPlots(2).MarkerFaceColor = color;
            elseif  ~isempty(obj.ViolinPlotQ)
                obj.WhiskerPlot.Color = color;
                obj.MedianPlot.MarkerEdgeColor = color;
                obj.NotchPlots(1).MarkerFaceColor = color;
                obj.NotchPlots(2).MarkerFaceColor = color;
            end
        end

        function color = get.BoxColor(obj)
            if ~isempty(obj.BoxPlot)
                color = obj.BoxPlot.FaceColor;
            end
        end

        function set.BoxWidth(obj,width)
            if ~isempty(obj.BoxPlot)
                pos=mean(obj.BoxPlot.XData);
                obj.BoxPlot.XData=pos+[-1,1,1,-1]*width;
            end
        end

        function width = get.BoxWidth(obj)
            width=max(obj.BoxPlot.XData)-min(obj.BoxPlot.XData);
        end

        function set.ViolinColor(obj, color)
            obj.ViolinPlot.FaceColor = color{1};
            obj.ScatterPlot.MarkerFaceColor = color{1};
            obj.MeanPlot.Color = color{1};
            if ~isempty(obj.ViolinPlot2)
                obj.ViolinPlot2.FaceColor = color{2};
                obj.ScatterPlot2.MarkerFaceColor = color{2};
            end
            if  ~isempty(obj.ViolinPlotQ)
                obj.ViolinPlotQ.FaceColor = color{1};
            end
            for idx = 1: size(obj.HistogramPlot,1)
                obj.HistogramPlot(idx).Color = color{1};
            end
        end

        function color = get.ViolinColor(obj)
            color{1} = obj.ViolinPlot.FaceColor;
            if ~isempty(obj.ViolinPlot2)
                color{2} = obj.ViolinPlot2.FaceColor;
            end
        end

        function set.ViolinAlpha(obj, alpha)
            obj.ViolinPlotQ.FaceAlpha = .65;
            obj.ViolinPlot.FaceAlpha = alpha{1};
            obj.ScatterPlot.MarkerFaceAlpha = 1;
            if ~isempty(obj.ViolinPlot2)
                obj.ViolinPlot2.FaceAlpha = alpha{2};
                obj.ScatterPlot2.MarkerFaceAlpha = 1;
            end
        end

        function alpha = get.ViolinAlpha(obj)
            alpha{1} = obj.ViolinPlot.FaceAlpha;
            if ~isempty(obj.ViolinPlot2)
                alpha{2} = obj.ViolinPlot2.FaceAlpha;
            end
        end

        function set.ShowData(obj, yesno)
            if yesno
                obj.ScatterPlot.Visible = 'on';
                for idx = 1: size(obj.HistogramPlot,1)
                    obj.HistogramPlot(idx).Visible = 'on';
                end
            else
                obj.ScatterPlot.Visible = 'off';
                for idx = 1: size(obj.HistogramPlot,1)
                    obj.HistogramPlot(idx).Visible = 'off';
                end
            end
            if ~isempty(obj.ScatterPlot2)
                obj.ScatterPlot2.Visible = obj.ScatterPlot.Visible;
            end

        end

        function yesno = get.ShowData(obj)
            if ~isempty(obj.ScatterPlot)
                yesno = strcmp(obj.ScatterPlot.Visible, 'on');
            end
        end

        function set.ShowNotches(obj, yesno)
            if ~isempty(obj.NotchPlots)
                if yesno
                    obj.NotchPlots(1).Visible = 'on';
                    obj.NotchPlots(2).Visible = 'on';
                else
                    obj.NotchPlots(1).Visible = 'off';
                    obj.NotchPlots(2).Visible = 'off';
                end
            end
        end

        function yesno = get.ShowNotches(obj)
            if ~isempty(obj.NotchPlots)
                yesno = strcmp(obj.NotchPlots(1).Visible, 'on');
            end
        end

        function set.ShowMean(obj, yesno)
            if ~isempty(obj.MeanPlot)
                if yesno
                    obj.MeanPlot.Visible = 'on';
                else
                    obj.MeanPlot.Visible = 'off';
                end
            end
        end

        function yesno = get.ShowMean(obj)
            if ~isempty(obj.BoxPlot)
                yesno = strcmp(obj.BoxPlot.Visible, 'on');
            end
        end

        function set.ShowBox(obj, yesno)
            if ~isempty(obj.BoxPlot)
                if yesno
                    obj.BoxPlot.Visible = 'on';
                else
                    obj.BoxPlot.Visible = 'off';
                end
            end
        end

        function yesno = get.ShowBox(obj)
            if ~isempty(obj.BoxPlot)
                yesno = strcmp(obj.BoxPlot.Visible, 'on');
            end
        end

        function set.ShowMedian(obj, yesno)
            if ~isempty(obj.MedianPlot)
                if yesno
                    obj.MedianPlot.Visible = 'on';
                else
                    obj.MedianPlot.Visible = 'off';
                end
            end
        end

        function yesno = get.ShowMedian(obj)
            if ~isempty(obj.MedianPlot)
                yesno = strcmp(obj.MedianPlot.Visible, 'on');
            end
        end

        function set.ShowWhiskers(obj, yesno)
            if ~isempty(obj.WhiskerPlot)
                if yesno
                    obj.WhiskerPlot.Visible = 'on';
                else
                    obj.WhiskerPlot.Visible = 'off';
                end
            end
        end

        function yesno = get.ShowWhiskers(obj)
            if ~isempty(obj.WhiskerPlot)
                yesno = strcmp(obj.WhiskerPlot.Visible, 'on');
            end
        end

    end

    methods (Access=private)
        function results = checkInputs(~, data, pos, varargin)
            isscalarnumber = @(x) (isnumeric(x) & isscalar(x));
            p = inputParser();
            p.addRequired('Data', @(x)isnumeric(vertcat(x{:})));
            p.addRequired('Pos', isscalarnumber);
            p.addParameter('Width', 0.3, isscalarnumber);
            p.addParameter('Bandwidth', [], isscalarnumber);
            iscolor = @(x) (isnumeric(x) & size(x,2) == 3);
            p.addParameter('ViolinColor', [], @(x)iscolor(vertcat(x{:})));
            p.addParameter('MarkerSize', 24, @isnumeric);
            p.addParameter('MedianMarkerSize', 36, @isnumeric);
            p.addParameter('LineWidth', 0.75, @isnumeric);
            p.addParameter('BoxColor', [0.5 0.5 0.5], iscolor);
            p.addParameter('BoxWidth', 0.01, isscalarnumber);
            p.addParameter('EdgeColor', [0.5 0.5 0.5], iscolor);
            p.addParameter('MedianColor', [1 1 1], iscolor);
            p.addParameter('ViolinAlpha', {0.3,0.15}, @(x)isnumeric(vertcat(x{:})));
            isscalarlogical = @(x) (islogical(x) & isscalar(x));
            p.addParameter('ShowData', true, isscalarlogical);
            p.addParameter('ShowNotches', false, isscalarlogical);
            p.addParameter('ShowMean', false, isscalarlogical);
            p.addParameter('ShowBox', true, isscalarlogical);
            p.addParameter('ShowMedian', true, isscalarlogical);
            p.addParameter('ShowWhiskers', true, isscalarlogical);
            validSides={'full', 'right', 'left'};
            checkSide = @(x) any(validatestring(x, validSides));
            p.addParameter('HalfViolin', 'full', checkSide);
            validQuartileStyles={'boxplot', 'shadow', 'none'};
            checkQuartile = @(x)any(validatestring(x, validQuartileStyles));
            p.addParameter('QuartileStyle', 'boxplot', checkQuartile);
            validDataStyles = {'scatter', 'histogram', 'none'};
            checkStyle = @(x)any(validatestring(x, validDataStyles));
            p.addParameter('DataStyle', 'scatter', checkStyle);

            p.parse(data, pos, varargin{:});
            results = p.Results;
        end
    end

    methods (Static)
        function [density, value, width] = calcKernelDensity(data, bandwidth, width)
            if isempty(data)
                error('Empty input data');
            end
            [density, value] = ksdensity(data, 'bandwidth', bandwidth);
            density = density(value >= min(data) & value <= max(data));
            value = value(value >= min(data) & value <= max(data));
            value(1) = min(data);
            value(end) = max(data);
            value = [value(1)*(1-1E-5), value, value(end)*(1+1E-5)];
            density = [0, density, 0];

            % all data is identical
            if min(data) == max(data)
                density = 1;
                value= mean(value);
            end

            width = width/max(density);
        end
    end
end


function [outdata,outstate] = asr_process(data,srate,state,windowlen,lookahead,stepsize,maxdims,chunklen,usegpu)
% Processing function for the Artifact Subspace Reconstruction (ASR) method.
% [Data,State] = asr_process(Data,SamplingRate,State,WindowLength,LookAhead,StepSize,MaxDimensions,ChunkLength,UseGPU)
%
% This function is used to clean multi-channel signal using the ASR method. The required inputs are
% the data matrix, the sampling rate of the data, and the filter state (as initialized by
% asr_calibrate). If the data is used on successive chunks of data, the output state of the previous
% call to asr_process should be passed in.
%
% In:
%   Data : Chunk of data to process [#channels x #samples]. This is a chunk of data, assumed to be
%          a continuation of the data that was passed in during the last call to asr_process (if
%          any). The data should be *zero-mean* (e.g., high-pass filtered the same way as for
%          asr_calibrate).
%
%   SamplingRate : sampling rate of the data in Hz (e.g., 250.0)
%
%   State : initial filter state (determined by asr_calibrate or from previous call to asr_process)
%
%   WindowLength : Length of the statistcs window, in seconds (e.g., 0.5). This should not be much
%                  longer than the time scale over which artifacts persist, but the number of samples
%                  in the window should not be smaller than 1.5x the number of channels. Default: 0.5
%
%   LookAhead : Amount of look-ahead that the algorithm should use. Since the processing is causal,
%               the output signal will be delayed by this amount. This value is in seconds and should
%               be between 0 (no lookahead) and WindowLength/2 (optimal lookahead). The recommended
%               value is WindowLength/2. Default: WindowLength/2
%
%   StepSize : The statistics will be updated every this many samples. The larger this is, the faster
%              the algorithm will be. The value must not be larger than WindowLength*SamplingRate.
%              The minimum value is 1 (update for every sample) while a good value is 1/3 of a second.
%              Note that an update is always performed also on the first and last sample of the data
%              chunk. Default: 32
%
%   MaxDimensions : Maximum dimensionality of artifacts to remove. Up to this many dimensions (or up
%                   to this fraction of dimensions) can be removed for a given data segment. If the
%                   algorithm needs to tolerate extreme artifacts a higher value than the default
%                   may be used (the maximum fraction is 1.0). Default 0.66
%
%   ChunkLength : The length of the chunks to process, in samples. Larger chunks require more
%                 memory. Default: 50000
%
%   UseGPU : Whether to run on the GPU. This makes sense for offline processing if you have a a card
%            with enough memory and good double-precision performance (e.g., NVIDIA GTX Titan or
%            K20). Note that for this to work you need to have the Parallel Computing toolbox.
%            Default: false
%
% Out:
%   Data : cleaned data chunk (same length as input but delayed by LookAhead samples)
%
%   State : final filter state (can be passed in for subsequent calls)
%
%                                Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                                2012-08-31

% UC Copyright Notice
% This software is Copyright (C) 2013 The Regents of the University of California. All Rights Reserved.
%
% Permission to copy, modify, and distribute this software and its documentation for educational,
% research and non-profit purposes, without fee, and without a written agreement is hereby granted,
% provided that the above copyright notice, this paragraph and the following three paragraphs appear
% in all copies.
%
% Permission to make commercial use of this software may be obtained by contacting:
% Technology Transfer Office
% 9500 Gilman Drive, Mail Code 0910
% University of California
% La Jolla, CA 92093-0910
% (858) 534-5815
% invent@ucsd.edu
%
% This software program and documentation are copyrighted by The Regents of the University of
% California. The software program and documentation are supplied "as is", without any accompanying
% services from The Regents. The Regents does not warrant that the operation of the program will be
% uninterrupted or error-free. The end-user understands that the program was developed for research
% purposes and is advised not to rely exclusively on the program for any reason.
%
% IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
% SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF
% THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
% PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
% CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
% MODIFICATIONS.

if nargin < 4 || isempty(windowlen)
    windowlen = 0.5; end
windowlen = max(windowlen,1.5*size(data,1)/srate);
if nargin < 5 || isempty(lookahead)
    lookahead = windowlen/2; end
if nargin < 6 || isempty(stepsize)
    stepsize = 32; end
if nargin < 7 || isempty(maxdims)
    maxdims = 0.66; end
if nargin < 9 || isempty(usegpu)
    usegpu = false; end
if nargin < 8 || isempty(chunklen)
    chunklen = 50000; end
if maxdims < 1
    maxdims = round(size(data,1)*maxdims); end
if isempty(data)
    outdata = data; outstate = state; return; end

[C,S] = size(data);
N = round(windowlen*srate);
P = round(lookahead*srate);
[T,M,A,B] = deal(state.T,state.M,state.A,state.B);

% initialize prior filter state by extrapolating available data into the past (if necessary)
if isempty(state.carry)
    state.carry = repmat(2*data(:,1),1,P) - data(:,1+mod(((P+1):-1:2)-1,S)); end

data = [state.carry data];
data(~isfinite(data(:))) = 0;

% split up the total sample range into k chunks
splits = ceil(S/chunklen);
if splits > 1
    fprintf('Now cleaning data in %i blocks',splits); end

for i=1:splits
    range = 1+floor((i-1)*S/splits) : min(S,floor(i*S/splits));
    if ~isempty(range)
        % get spectrally shaped data X for statistics computation (range shifted by lookahead)
        X = double(data(:,range+P));
        if isempty(state.SOS)
            [X,state.iir] = filter(B,A,double(X),state.iir,2);
        else
            for s = 1:size(state.SOS,1)
                [X,state.Zi{s}] = filter(state.SOS(s,1:3),state.SOS(s,4:6),double(X),state.Zi{s},2); end
            X = X*state.G;
        end

        % move it to the GPU if applicable
        if usegpu && length(range) > 1000
            try X = gpuArray(X); catch,end; end
        % compute running mean covariance (assuming a zero-mean signal)
        [Xcov,state.cov] = moving_average(N,reshape(bsxfun(@times,reshape(X,1,C,[]),reshape(X,C,1,[])),C*C,[]),state.cov);
        % extract the subset of time points at which we intend to update
        update_at = min(stepsize:stepsize:(size(Xcov,2)+stepsize-1),size(Xcov,2));
        % if there is no previous R (from the end of the last chunk), we estimate it right at the first sample
        if isempty(state.last_R)
            update_at = [1 update_at];  %#ok<AGROW>
            state.last_R = eye(C);
        end
        Xcov = reshape(Xcov(:,update_at),C,C,[]);
        if usegpu
            Xcov = gather(Xcov); end
        % do the reconstruction in intervals of length stepsize (or shorter if at the end of a chunk)
        last_n = 0;
        for j=1:length(update_at)
            % do a PCA to find potential artifact components
            [V,D] = eig(Xcov(:,:,j));
            [D,order] = sort(reshape(diag(D),1,C)); V = V(:,order);
            % determine which components to keep (variance below directional threshold or not admissible for rejection)
            keep = D<sum((T*V).^2) | (1:C)<(C-maxdims);
            trivial = all(keep);
            % update the reconstruction matrix R (reconstruct artifact components using the mixing matrix)
            if ~trivial
                R = real(M*pinv(bsxfun(@times,keep',V'*M))*V');
            else
                R = eye(C);
            end
            % apply the reconstruction to intermediate samples (using raised-cosine blending)
            n = update_at(j);
            if ~trivial || ~state.last_trivial
                subrange = range((last_n+1):n);
                blend = (1-cos(pi*(1:(n-last_n))/(n-last_n)))/2;
                data(:,subrange) = bsxfun(@times,blend,R*data(:,subrange)) + bsxfun(@times,1-blend,state.last_R*data(:,subrange));
            end
            [last_n,state.last_R,state.last_trivial] = deal(n,R,trivial);
        end
    end
    if splits > 1
        fprintf('.'); end
end
if splits > 1
    fprintf('\n'); end

% carry the look-ahead portion of the data over to the state (for successive calls)
state.carry = [state.carry data(:,(end-P+1):end)];
state.carry = state.carry(:,(end-P+1):end);

% finalize outputs
outdata = data(:,1:(end-P));
if usegpu
    state.iir = gather(state.iir);
    state.cov = gather(state.cov);
end
outstate = state;

function [X,Zf] = moving_average(N,X,Zi)
% Run a moving-average filter along the second dimension of the data.
% [X,Zf] = moving_average(N,X,Zi)
%
% In:
%   N : filter length in samples
%   X : data matrix [#Channels x #Samples]
%   Zi : initial filter conditions (default: [])
%
% Out:
%   X : the filtered data
%   Zf : final filter conditions
%
%                           Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                           2012-01-10

if nargin <= 2 || isempty(Zi)
    Zi = zeros(size(X,1),N); end

% pre-pend initial state & get dimensions
Y = [Zi X]; M = size(Y,2);
% get alternating index vector (for additions & subtractions)
I = [1:M-N; 1+N:M];
% get sign vector (also alternating, and includes the scaling)
S = [-ones(1,M-N); ones(1,M-N)]/N;
% run moving average
X = cumsum(bsxfun(@times,Y(:,I(:)),S(:)'),2);
% read out result
X = X(:,2:2:end);

if nargout > 1
    Zf = [-(X(:,end)*N-Y(:,end-N+1)) Y(:,end-N+2:end)]; end


function varargout = process_pac_dynamic_sur2( varargin )
% PROCESS_PAC_DYNAMIC: Compute the Time resolved Phase-Amplitude Coupling
%
% DOCUMENTATION

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Soheila Samiee, Francois Tadel 2013
%
% Updates:
%   - 1.0.4:  Soheila
%   - 2.0.0: Soheila, based on dpac v.1.1.3 and surrogate 3, June 2016
%   - 2.1:   SS, Based on dpac v2.1.0, Nov. 2016
%   - 3.1:   SS, block resampling (N = 2)
%   - 3.3:   SS, number of blocks for resampling could be more than 2
%            (default =5 ), May 2017
eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Surrogate tPAC - block resampling - version: 3.4';
    sProcess.FileTag     = '';
%     sProcess.Category    = 'Custom';
%     sProcess.SubGroup    = 'dPAC analysis';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = {'Frequency','Time-resolved Phase-Amplitude Coupling'};
    sProcess.Index       = 660;
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'raw',      'data',     'results',  'matrix'};
    sProcess.OutputTypes = {'timefreq', 'timefreq', 'timefreq', 'timefreq'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 1;

    % === TIME WINDOW
    sProcess.options.timewindow.Comment = 'Time:';
    sProcess.options.timewindow.Type    = 'timewindow';
    sProcess.options.timewindow.Value   = [];
    % === NESTING FREQ
    sProcess.options.nesting.Comment = 'Frequency for phase band (low):';
    sProcess.options.nesting.Type    = 'range';
    sProcess.options.nesting.Value   = {[8, 12], 'Hz', 2};
    % === NESTED FREQ
    sProcess.options.nested.Comment = 'Frequency for amplitude band (high):';
    sProcess.options.nested.Type    = 'range';
    sProcess.options.nested.Value   = {[40, 150], 'Hz', 2};
        % Band for fa
%     sProcess.options.label_fa.Comment = 'F_A frequency band:';
%     sProcess.options.label_fa.Type    = 'label';
    sProcess.options.fa_type.Comment = {'   Single band', ...
                                        '   More than one center frequencies (default: 20)' };
    sProcess.options.fa_type.Type    = 'radio';
    sProcess.options.fa_type.Value   = 1;

    % === WINDOW LENGTH
    sProcess.options.winLen.Comment = 'Length of sliding time window:';
    sProcess.options.winLen.Type    = 'value';
    sProcess.options.winLen.Value   = {1.10, 'S', 2};

    % === NUMBER OF SURROGATE DATASETS
    sProcess.options.Nsurrogate.Comment    = 'Number of surrogate datasets: ';
    sProcess.options.Nsurrogate.Type       = 'value';
    sProcess.options.Nsurrogate.Value      = {100, '', 0};

    % === SOURCES
    sProcess.options.label5.Comment = 'Sensors/sources to be investigated :';
    sProcess.options.label5.Type    = 'label';

    % === CLUSTERS
    sProcess.options.clusters.Comment = '';
    sProcess.options.clusters.Type    = 'scout_confirm';
    sProcess.options.clusters.Value   = {};
    sProcess.options.clusters.InputTypes = {'results'};

    % === SENSOR SELECTION
    sProcess.options.target_data.Comment    = 'Sensor types or names (empty=all): ';
    sProcess.options.target_data.Type       = 'text';
    sProcess.options.target_data.Value      = 'MEG, EEG';
    sProcess.options.target_data.InputTypes = {'data', 'raw'};
    % === SOURCE INDICES
    sProcess.options.target_res.Comment    = 'Source indices (empty=all): ';
    sProcess.options.target_res.Type       = 'text';
    sProcess.options.target_res.Value      = '';
    sProcess.options.target_res.InputTypes = {'results'};
    sProcess.options.label6.Comment = '(The indices will only be considered if the scouts are not selected)';
    sProcess.options.label6.Type    = 'label';

    % === ROW NAMES
    sProcess.options.target_tf.Comment    = 'Row names or indices (empty=all): ';
    sProcess.options.target_tf.Type       = 'text';
    sProcess.options.target_tf.Value      = '';
    sProcess.options.target_tf.InputTypes = {'timefreq', 'matrix'};

    % === LOOP METHOD
    sProcess.options.label1.Comment = 'Processing options [expert only]:';
    sProcess.options.label1.Type    = 'label';
    % === MAX_BLOCK_SIZE
    sProcess.options.max_block_size.Comment = 'Number of signals to process at once: ';
    sProcess.options.max_block_size.Type    = 'value';
    sProcess.options.max_block_size.Value   = {20, ' ', 0};

    % sProcess.options.filter_sensor.InputTypes = {'results'};
    % === AVERAGE OUTPUT FILES
    sProcess.options.label2.Comment = 'Output options:';
    sProcess.options.label2.Type    = 'label';
    sProcess.options.avgoutput.Comment = 'Save average PAC across trials';
    sProcess.options.avgoutput.Type    = 'checkbox';
    sProcess.options.avgoutput.Value   = 1;
%     % === SAVE PAC MAPS
%     sProcess.options.savefull.Comment = 'Save the full PAC maps';
%     sProcess.options.savefull.Type    = 'checkbox';
%     sProcess.options.savefull.Value   = 1;
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    Comment = sProcess.Comment;
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputsA) %#ok<DEFNU>
tic
    % Get options
    if isfield(sProcess.options, 'timewindow') && isfield(sProcess.options.timewindow, 'Value') && iscell(sProcess.options.timewindow.Value) && ~isempty(sProcess.options.timewindow.Value)
        OPTIONS.TimeWindow = sProcess.options.timewindow.Value{1};
    else
        OPTIONS.TimeWindow = [];
    end
    OPTIONS.FunctionVersion = sProcess.Comment;

    % Clusters
    if isfield(sProcess.options, 'clusters') && ~isempty(sProcess.options.clusters) && ~isempty(sProcess.options.clusters.Value)
        OPTIONS.Clusters = sProcess.options.clusters.Value;
    else
        OPTIONS.Clusters = [];
    end
    OPTIONS.BandNesting = sProcess.options.nesting.Value{1};
    OPTIONS.BandNested  = sProcess.options.nested.Value{1};
    OPTIONS.WinLen      = sProcess.options.winLen.Value{1};
    % Get target
    if  ~isempty(OPTIONS.Clusters)         % extract cluster
        OPTIONS.Target = OPTIONS.Clusters;
    elseif ismember(sInputsA(1).FileType, {'data','raw'}) && isfield(sProcess.options, 'target_data') && ~isempty(sProcess.options.target_data.Value)
        OPTIONS.Target = sProcess.options.target_data.Value;
    elseif strcmpi(sInputsA(1).FileType, 'results') && isfield(sProcess.options, 'target_res') && ~isempty(sProcess.options.target_res.Value)
        OPTIONS.Target = sProcess.options.target_res.Value;
    elseif ismember(sInputsA(1).FileType, {'timefreq', 'matrix'}) && isfield(sProcess.options, 'target_tf') && ~isempty(sProcess.options.target_tf.Value)
        OPTIONS.Target = sProcess.options.target_tf.Value;
    else
        OPTIONS.Target = [];
    end
    % All other options
    OPTIONS.MaxSignals   = sProcess.options.max_block_size.Value{1};
    if (strcmp(sInputsA(1).FileType,'data') && isempty(sProcess.options.target_data.Value)) || ...
            (strcmp(sInputsA(1).FileType,'results') && isempty(sProcess.options.target_res.Value))
        OPTIONS.isFullMaps   = 1;
    else
        OPTIONS.isFullMaps   = 0;
    end
    OPTIONS.isAvgOutput  = sProcess.options.avgoutput.Value;
    if (length(sInputsA) == 1)
        OPTIONS.isAvgOutput = 0;
    end
    OPTIONS.HighFreqs    = sProcess.options.fa_type.Value;
    OPTIONS.Nsur = sProcess.options.Nsurrogate.Value{1};

    % ===== INITIALIZE =====
    % Initialize output variables
    OutputFiles = {};
    sPAC_avg = [];
    nAvg = 0;
    % Initialize progress bar
    if bst_progress('isVisible')
        startValue = bst_progress('get');
    else
        startValue = 0;
    end
    % Options for LoadInputFile()
    if strcmpi(sInputsA(1).FileType, 'results')
        LoadOptions.LoadFull = 0;  % Load kernel-based results as kernel+data
    else
        LoadOptions.LoadFull = 1;  % Load the full file
    end
    LoadOptions.IgnoreBad   = 1;  % From raw files: ignore the bad segments
    LoadOptions.ProcessName = func2str(sProcess.Function);
    LoadOptions.TargetFunc = 'all';
    % Start the matlabpool for parallel processing in bst_pac

    % Loop over input files
    for iFile = 1:length(sInputsA)

        % ===== LOAD SIGNALS =====
        bst_progress('text', sprintf('PAC: Loading input file (%d/%d)...', iFile, length(sInputsA)));
        bst_progress('set', round(startValue + (iFile-1) / length(sInputsA) * 100));

        % Load input signals
        [sInput, nSignals, iRows] = bst_process('LoadInputFile', sInputsA(iFile).FileName, OPTIONS.Target, OPTIONS.TimeWindow, LoadOptions);
        if isempty(sInput) || isempty(sInput.Data)
            return;
        end

        % Get sampling frequency
        sRate = 1 / (sInput.Time(2) - sInput.Time(1));
        % Check the nested frequencies
        if (OPTIONS.BandNested(2) > sRate/3)
            % Warning
            strMsg = sprintf('Higher nesting frequency is too high (%d Hz) compared with sampling frequency (%d Hz): Limiting to %d Hz', round(OPTIONS.BandNested(2)), round(sRate), round(sRate/3));
            disp([10 'process_pac> ' strMsg]);
            bst_report('Warning', sProcess, [], strMsg);
            % Fix higher frequencyy
            OPTIONS.BandNested(2) = sRate/3;
        end
        % Check the extent of bandNested band
        if (OPTIONS.BandNested(2) <= OPTIONS.BandNested(1))
            bst_report('Error', sProcess, [], sprintf('Invalid frequency range: %d-%d Hz', round(OPTIONS.BandNested(1)), round(OPTIONS.BandNested(2))));
            continue;
        end

        % ===== COMPUTE PAC MEASURE =====
        % Number of blocks of signals
        MAX_BLOCK_SIZE = OPTIONS.MaxSignals;
        nBlocks = ceil(nSignals / MAX_BLOCK_SIZE);
        sPAC = [];
        % Display processing time
        disp(sprintf('Processing %d blocks of %d signals each.', nBlocks, MAX_BLOCK_SIZE));
        % Process each block of signals
        for iBlock = 1:nBlocks
%             tic
            bst_progress('text', sprintf('PAC: File %d/%d - Block %d/%d', iFile, length(sInputsA), iBlock, nBlocks));
            bst_progress('set', round(startValue + (iFile-1)/length(sInputsA)*100 + iBlock/nBlocks*100));

            % Indices of the signals
            iSignals = (iBlock-1)*MAX_BLOCK_SIZE+1 : min(iBlock*MAX_BLOCK_SIZE, nSignals);

            % Get signals to process
            if ~isempty(sInput.ImagingKernel)
                Fblock = sInput.ImagingKernel(iSignals,:) * sInput.Data;
            else
                Fblock = sInput.Data(iSignals,:);
            end

            % Defining the options
            PACoptions.doInterpolation = 1;%0               % Applying interpolation in frequency and time domain
            PACoptions.logCenters = 0; %1                   % Choose the center frequencies for f_A with log space in faBand
            PACoptions.overlap = 0.5;                       % Time window over lap (0<= value <1)
            if OPTIONS.HighFreqs ==1
                PACoptions.nHighFreqs = 1;                  % Number of high frequency centers
                PACoptions.doInterpolation = 0;
            else
                PACoptions.nHighFreqs = 20; %4               % Number of high frequency centers
            end
            PACoptions.nSur = OPTIONS.Nsur;
            OPTIONS.PACoptions = PACoptions;

            % Estimating DPAC
            sPACblock = Compute(Fblock, sRate,  OPTIONS.BandNested, OPTIONS.BandNesting, OPTIONS.WinLen, PACoptions);

            % Check for errors
            if isempty(sPACblock)
                return;
            end
            % Initialize output structure
            nTime = length(sPACblock.TimeOut);
            if isempty(sPAC)
                sPAC.ValPAC      = [];
                sPAC.NestingFreq = [];
                sPAC.NestedFreq  = [];
                sPAC.PhasePAC    = [];
                sPAC.DynamicPAC     = zeros(nSignals, nTime, length(sPACblock.HighFreqs), OPTIONS.Nsur);
                sPAC.DynamicNesting = zeros(nSignals, nTime, length(sPACblock.HighFreqs), OPTIONS.Nsur);
                sPAC.DynamicPhase   = zeros(nSignals, nTime, length(sPACblock.HighFreqs), OPTIONS.Nsur);
                sPAC.HighFreqs = sPACblock.HighFreqs;

                meanInputTime  = OPTIONS.TimeWindow(1)+(sPACblock.TimeOut(end)+OPTIONS.WinLen/2)/2;
                meanOutputTime = (sPACblock.TimeOut(1)+sPACblock.TimeOut(end))/2;
                sPAC.TimeOut   = sPACblock.TimeOut + (meanInputTime - meanOutputTime);
            end
            % Copy block results to output structure
            sPAC.DynamicPAC(iSignals,:,:,:)     = permute(sPACblock.DynamicPAC,[3,2,1,4]);
            sPAC.DynamicNesting(iSignals,:,:,:) = permute(sPACblock.DynamicNesting,[3,2,1,4]);
            sPAC.DynamicPhase(iSignals,:,:,:)   = permute(sPACblock.DynamicPhase,[3,2,1,4]);
        end

        % ===== APPLY SOURCE ORIENTATION =====
        if strcmpi(sInput.DataType, 'results') && (sInput.nComponents > 1)
            % Number of values per vertex
            switch (sInput.nComponents)
                case 2
                    sPAC.ValPAC         = (sPAC.ValPAC(1:2:end,:,:)           + sPAC.ValPAC(2:2:end,:,:))           / 2;
                    sPAC.NestingFreq    = (sPAC.NestingFreq(1:2:end,:,:)      + sPAC.NestingFreq(2:2:end,:,:))      / 2;
                    sPAC.NestedFreq     = (sPAC.NestedFreq(1:2:end,:,:)       + sPAC.NestedFreq(2:2:end,:,:))       / 2;
                    sPAC.PhasePAC       = (sPAC.PhasePAC(1:2:end,:,:)         + sPAC.PhasePAC(2:2:end,:,:))         / 2;
                    sPAC.DynamicPAC     = (sPAC.DynamicPAC(1:2:end,:,:,:)     + sPAC.DynamicPAC(2:2:end,:,:,:))     / 2;
                    sPAC.DynamicNesting = (sPAC.DynamicNesting(1:2:end,:,:,:) + sPAC.DynamicNesting(2:2:end,:,:,:)) / 2;
                    sInput.RowNames     = sInput.RowNames(1:2:end);
                    sPAC.DynamicPhase   = (sPAC.DynamicPhase(1:2:end,:,:,:)   + sPAC.DynamicPhase(2:2:end,:,:,:))   / 2;

                case 3
                    sPAC.ValPAC         = (sPAC.ValPAC(1:3:end,:,:)           + sPAC.ValPAC(2:3:end,:,:)           + sPAC.ValPAC(3:3:end,:,:))           / 3;
                    sPAC.NestingFreq    = (sPAC.NestingFreq(1:3:end,:,:)      + sPAC.NestingFreq(2:3:end,:,:)      + sPAC.NestingFreq(3:3:end,:,:))      / 3;
                    sPAC.NestedFreq     = (sPAC.NestedFreq(1:3:end,:,:)       + sPAC.NestedFreq(2:3:end,:,:)       + sPAC.NestedFreq(3:3:end,:,:))       / 3;
                    sPAC.PhasePAC       = (sPAC.PhasePAC(1:3:end,:,:)         + sPAC.PhasePAC(2:3:end,:,:)         + sPAC.PhasePAC(3:3:end,:,:))         / 3;
                    sPAC.DynamicPAC     = (sPAC.DynamicPAC(1:3:end,:,:,:)     + sPAC.DynamicPAC(2:3:end,:,:,:)     + sPAC.DynamicPAC(3:3:end,:,:,:))     / 3;
                    sPAC.DynamicNesting = (sPAC.DynamicNesting(1:3:end,:,:,:) + sPAC.DynamicNesting(2:3:end,:,:,:) + sPAC.DynamicNesting(3:3:end,:,:,:)) / 3;
                    sInput.RowNames     = sInput.RowNames(1:3:end);
                    sPAC.DynamicPhase   = (sPAC.DynamicPhase(1:3:end,:,:,:)   + sPAC.DynamicPhase(2:3:end,:,:,:)   + sPAC.DynamicPhase(3:3:end,:,:,:))   / 3;

            end
        end

        % ===== SAVE FILE =====
        % Detect incomplete lists of sources
        isIncompleteResult = strcmpi(sInput.DataType, 'results') && (length(sInput.RowNames) * sInput.nComponents < nSignals);
        % Comment
        Comment = 'Sur. DynamicPAC';
        if iscell(sInput.RowNames)
            % Find the scout name
            scoutName = sInput.RowNames{1};
            k = strfind(scoutName,'.');
            if isempty(k)
                Comment = [Comment, ': ' scoutName];
            else
                Comment = [Comment, ': ' scoutName(1:k-1)];
            end

        elseif (length(sInput.RowNames) == 1)
            Comment = [Comment, ': #', num2str(sInput.RowNames(1))];
        elseif isIncompleteResult
            Comment = [Comment, ': ', num2str(length(sInput.RowNames)), ' sources'];
        end

        if OPTIONS.isFullMaps
            Comment = [Comment, ' (Full)'];
        end
        % Output data type: if there are not all the sources, switch the datatype to "scout"
        if isIncompleteResult
            sInput.DataType = 'scout';
            % Convert source indices to strings
            if ~iscell(sInput.RowNames)
                sInput.RowNames = cellfun(@num2str, num2cell(sInput.RowNames), 'UniformOutput', 0);
            end
        end
        % Save each as an independent file
        if ~OPTIONS.isAvgOutput
            nAvg = 1;
            OutputFiles{end+1} = SaveFile(sPAC, sInput.iStudy, sInputsA(iFile).FileName, sInput, Comment, nAvg, OPTIONS);
        else
            % Compute online average of the connectivity matrices
            if isempty(sPAC_avg)
                sPAC_avg.ValPAC      = sPAC.ValPAC       ./ length(sInputsA);
                sPAC_avg.NestingFreq = sPAC.NestingFreq  ./ length(sInputsA);
                sPAC_avg.NestedFreq  = sPAC.NestedFreq   ./ length(sInputsA);
                sPAC_avg.PhasePAC(:,:,:,nAvg+1)       =  sPAC.PhasePAC;
                sPAC_avg.DynamicPAC(:,:,:,nAvg+1)     =  sPAC.DynamicPAC;
                sPAC_avg.DynamicNesting(:,:,:,nAvg+1) =  sPAC.DynamicNesting;
                sPAC_avg.DynamicPhase(:,:,:,nAvg+1)   =  sPAC.DynamicPhase;

                sPAC_avg.TimeOut     = sPAC.TimeOut;
                sPAC_avg.HighFreqs   = sPAC.HighFreqs;
            else
                sPAC_avg.ValPAC      = sPAC_avg.ValPAC      + sPAC.ValPAC      ./ length(sInputsA);
                sPAC_avg.NestingFreq = sPAC_avg.NestingFreq + sPAC.NestingFreq ./ length(sInputsA);
                sPAC_avg.NestedFreq  = sPAC_avg.NestedFreq  + sPAC.NestedFreq  ./ length(sInputsA);
                sPAC_avg.PhasePAC(:,:,:,nAvg+1)       =  sPAC.PhasePAC;
                sPAC_avg.DynamicPAC(:,:,:,nAvg+1)     =  sPAC.DynamicPAC;
                sPAC_avg.DynamicPhase(:,:,:,nAvg+1)   =  sPAC.DynamicPhase;
                sPAC_avg.DynamicNesting(:,:,:,nAvg+1) =  sPAC.DynamicNesting;
            end
            nAvg = nAvg + 1;
        end
    end

    % ===== SAVE AVERAGE =====
    if OPTIONS.isAvgOutput
        % Output study, in case of average
        [tmp, iOutputStudy] = bst_process('GetOutputStudy', sProcess, sInputsA);
        % Save file
        OutputFiles{1} = SaveFile(sPAC_avg, iOutputStudy, [], sInput, Comment, nAvg, OPTIONS);
    end
end

%% ========================================================================
%  ===== SUPPORT FUNCTIONS ================================================
%  ========================================================================

%% ===== SAVE FILE =====
function NewFile = SaveFile(sPAC, iOuptutStudy, DataFile, sInput, Comment, nAvg, OPTIONS)
    % ===== PREPARE OUTPUT STRUCTURE =====
    % Create file structure
    FileMat = db_template('timefreqmat');
    FileMat.TF        = sPAC.ValPAC;
    FileMat.Comment   = Comment;
    FileMat.Method    = 'dpac';
    FileMat.Measure   = 'maxpac';
    FileMat.DataFile  = file_win2unix(DataFile);
    FileMat.nAvg      = nAvg;
    FileMat.Freqs     = 0;
    % All the PAC fields
    FileMat.sPAC = rmfield(sPAC, 'ValPAC');
    % Time vector
    FileMat.Time = sPAC.TimeOut;

    % Output data type and Row names
    if isempty(OPTIONS.Target)
        FileMat.DataType = sInput.DataType;
        FileMat.RowNames = sInput.RowNames;
    elseif strcmpi(sInput.DataType, 'results') && ~isempty(OPTIONS.Target)
        FileMat.DataType = 'matrix';
        if isnumeric(sInput.RowNames)
        	FileMat.RowNames = cellfun(@num2str, num2cell(sInput.RowNames), 'UniformOutput', 0);
        else
            FileMat.RowNames = sInput.RowNames;
        end
    else
        FileMat.DataType = sInput.DataType;
        FileMat.RowNames = sInput.RowNames;
    end
    % Atlas
    if ~isempty(sInput.Atlas)
        FileMat.Atlas = sInput.Atlas;
    end
    if ~isempty(sInput.SurfaceFile)
        FileMat.SurfaceFile = sInput.SurfaceFile;
    end
    % History: Computation
    FileMat = bst_history('add', FileMat, 'compute', 'PAC measure (see the field "Options" for input parameters)');
    % Save options in the file
    FileMat.Options = OPTIONS;

    % ===== SAVE FILE =====
    % Get output study
    sOutputStudy = bst_get('Study', iOuptutStudy);
    % File tag
%     if OPTIONS.isFullMaps
        fileTag = 'timefreq_dpac_fullmaps';
%     else
%         fileTag = 'timefreq_dpac';
%     end
    % Output filename
    NewFile = bst_process('GetNewFilename', bst_fileparts(sOutputStudy.FileName), fileTag);
    % Save file
    bst_save(NewFile, FileMat, 'v6');
    % Add file to database structure
    db_add_data(iOuptutStudy, NewFile, FileMat);
    toc
end

% ===== COMPUTE PAC MEASURE =====
function sPAC = Compute(Xinput, sRate, faBand, fpBand, winLen, Options)
% USAGE:
%   sPAC = dPACestimate(Xinput, sRate, faBand, fpBand, winLen, PLvector)
%
% INPUTS:
%    - Xinput:        [nChannels,nTime] signal to process
%    - sRate:         Sampling frequency (Hz)
%    - faBand:        Nested Band: Minimum and maximum frequency for extraction of frequency for amplitude
%    - fpBand:        Nesting Band: Minimum and maximum frequency for extraction of frequency for phase (Hz)
%    - winLen:        Length of each time window for coupling estimation(S) (default: 1 Sec)
%    - isFullMaps:    If 1, save the full directPAC maps
%
% OUTPUTS:   sPAC structure [for each signal]
%    - TimeOut:        Output time vector (Sec)
%    - HighFreqs:       Frequency for amplitude vector
%    - ValPAC:         [nChannels, nTimeOut] Maximum PAC strength in each  time point
%    - NestedFreq:     [nChannels, nTimeOut] Fnested corresponding to maximum synchronization index in each time point
%    - NestingFreq:    [nChannels, nTimeOut] Fnesting corresponding to maximum synchronization index in each time point
%    - phasePAC:       [nChannels, nTimeOut] Phase corresponding to maximum synchronization index in each time point
%    - DynamicNesting: [nNestedCenters,nTimeOut,nChannels] Estimated nesting frequency for all times, channels and nested intervals
%    - DynamicPAC:     [nNestedCenters,nTimeOut,nChannels] full array of PAC
%
% DESCRIPTION:
%   Estimation of Phase Amplitude Coupling (PAC) with DPAC method.
%
% Author:  Soheila Samiee, 2013
%

if (nargin < 4) || isempty(fpBand)
    fpBand = [4, 8];
end
if (nargin < 5) || isempty(winLen)
    winLen = 1;
end
if ~isfield(Options, 'overlap')
    Options.overlap = 0.5;
end

if fpBand(2)>faBand(1)
    fpBand(2) = faBand(1)/2;
    error_msg = ['Maximum of Fp should be less than half of the minimum of Fa!' 10 10 ...
        'max{Fp} modified to ', num2str(fpBand(2))];
    bst_report('Error', sProcess, [], error_msg);
    disp(['Warning: ' error_msg]);
end

if winLen < 2*1/fpBand(1)
    error_msg = ['Window length is short for extracting this minimum fp!' 10 ...
        'Either increase window length to: ',num2str(2*1/fpBand(1)), ' or increase minimum fp to; ', num2str(2/winLen)];
    bst_report('Error', sProcess, [], error_msg);
    disp(['Warning: ' error_msg]);
    winLen = 2*1/fpBand(1);
end

% ===== SETTING THE PARAMETERS =====
tStep = winLen*(1-Options.overlap);     % Time step for sliding window on time (Sec) (Overlap: 50%)
margin = 2;%1                       % Margin (in time) for filtering (Sec) --- default: 2sec -> changed to 1 sec in May12,2016
hilMar = 1/5;                       % Percentage of margin for Hilber transform
nTS = size(Xinput,2);               % Number of temporal samples of data
bandNestingLen= max(3,1/(winLen+margin));% Length of band nesting -- considering the resolution in FFT domain with available window length
isMirror = 0;                       % Mirroring the data in filtering
isRelax  = 1;                       % Attenuation of the filter in the stopband (1 => 40 dB, 0 => 60 dB)
minExtracFreq = max(1/winLen, fpBand(1));  % minimum frequency that could be extracted as nestingFreq
sProcess = 'Process_pac_dynamic_sur2';

% Mode = Options.Mode;                % Mode of estimating the coupling (line or map)
doInterpolation = Options.doInterpolation;  % Applying interpolation in frequency and time domain
logCenters = Options.logCenters;    % Choose the center frequencies for f_A with log space in faBand
nHighFreqs = Options.nHighFreqs;    % Number of high frequency centers
% Mode = 'map';                       % Mode of estimating the coupling (line or map)
% doInterpolation = 1;                % Applying interpolation in frequency and time domain
% logCenters = 0; %0                  % Choose the center frequencies for f_A with log space in faBand
% nHighFreqs = 20;%4;                 % Number of high frequency centers
missedPcount = 0;                   % Number of intervals that do not have peak in their F_A envelope PSD
mirrorEffectSample = 40;            % Number of samples that can be affected due to mirroring effect

N = 5; % Number of blocks for shuffling

% ==== ADDING MARGING TO THE DATA => AVOID EDGE ARTIFACT (FILTERS AND HILBERT TRANSFORM) ====
nMargin = fix(margin*sRate);
nHilMar = fix(nMargin*hilMar);

if (nTS/sRate < 2*winLen)
    error_msg = 'Data length should be at least twice of window length';
    bst_report('Error', 'process_pac_dynamic', [], error_msg);
    disp(['Error: ' error_msg]);
    sPAC = [];
    return
% elseif (nTS < nMargin)
%     tmp = repmat([Xinput(:,end-1:-1:2),Xinput],1,ceil(nMargin/nTS));
%     Xinput = [tmp(:,end-nMargin+1-size(Xinput,2):end-size(Xinput,2)), Xinput, tmp(:,1:nMargin)];
%     clear tmp
% else
%     Xinput = [Xinput(:,nMargin+1:-1:2), Xinput, Xinput(:,end-1:-1:end-nMargin)];
end

% Zero-padding signal for the margin
Xinput = [zeros(size(Xinput,1),nMargin), Xinput, zeros(size(Xinput,1),nMargin)];

% ==== SETTING THE PARAMETERS OF THE FILTERS ====
if nHighFreqs > 1 %strcmp(Mode,'map')
%     Fconst = (faBand(end)-faBand(1))/nHighFreqs;                     % Frequency distant between successive fAs
    if logCenters
        nestedCenters = logspace(log10(faBand(1)),log10(faBand(end)),nHighFreqs);
    else
        nestedCenters = linspace(faBand(1),faBand(end),nHighFreqs);
    end
    Fstep = diff(nestedCenters)/2;  % the range of frequency around each nested center
    Fstep = [Fstep(1),Fstep,Fstep(end)];
    Fstep = max(Fstep, fpBand(2)/2);  % Minimum band width is defined to cover the whole interval between consecutive centre frequencies and at the same time consider all coupled frequencies to it in the range of interest.
else
    nestedCenters = mean(faBand);
    Fstep    = abs(faBand-nestedCenters);
end

fArolloff = [];
fProlloff = [];%1          % roll off frequency for filtering
sPAC.HighFreqs = nestedCenters;
nFa = length(nestedCenters);
nSources = size(Xinput,1);
isources = 1:nSources;
nTime = fix((nTS-fix(winLen*sRate))/fix(tStep*sRate))+1;
TimeOut = winLen/2 : tStep : winLen/2+(nTime-1)*tStep;        % Sec
nSur = Options.nSur;                % Number of surrogate itterations
PAC = zeros(nFa,nTime,nSources,nSur);               % PAC measure
nestingFreq = zeros(nFa,nTime,nSources,nSur);
DynamicPhase= zeros(nFa,nTime,nSources);               % PAC measure

% ===== MAIN LOOP ON FA ===== %
% Filtering in Fa band before cutting into smaller time windows
% (=> Higherfrequency resolution + faster process)

for ifreq=1:nFa
    % fA band
    bandNested = [nestedCenters(ifreq)-Fstep(ifreq),nestedCenters(ifreq)+Fstep(ifreq+1)];

    % Filtering in fA band
    Xnested = bst_bandpass_hfilter(Xinput, sRate,bandNested(1), bandNested(2), isMirror, isRelax, fArolloff);    % Filtering
    Xnested = Xnested(:,nMargin-nHilMar+1:end-nMargin+nHilMar);               % Removing part of the margin

    % Hilbert transform
    Z = hilbert(Xnested')';

    % Phase and envelope detection
    nestedEnv_total = abs(Z);             % Envelope of nested frequency rhythms
    nestedEnv_total = nestedEnv_total(:,nHilMar:end-nHilMar);              % Removing the margin

    % Loop on Time
    for iTime=1:nTime
        X = Xinput(:, (iTime-1)*fix(tStep*sRate)+[1:fix((2*margin+winLen)*sRate)]);
        nestedEnv = nestedEnv_total(:, (iTime-1)*fix(tStep*sRate)+[1:fix(winLen*sRate)]);

        % Time vector and number of samples
        nSample = size(nestedEnv,2);
        nFreq = 2^ceil(log2(nSample)+1);

        % Extraction of nesting frequency
        Ffft = abs(fft(nestedEnv-repmat(mean(nestedEnv,2),1,nSample),nFreq,2)).^2/nSample;
        freq = linspace(0,sRate,nFreq);
        x1 = X(:,nMargin+1:nMargin+fix(winLen*sRate));
        FfftSig = abs(fft(x1-repmat(mean(x1,2),1,nSample),nFreq,2)).^2/nSample;
        %%%

        % Finding the corresponding frequency component
        ind = bst_closest([minExtracFreq, fpBand(2)], freq);
        if freq(ind(1))<(minExtracFreq-diff(freq(1:2)))
            ind(1) = ind(1)+1;
        end
        if ind(2)>fpBand(2)
            ind(2) = ind(2)-1;
        end

                % Add previous and next point to the interval to give the algorithm
        % to find the local peaks even if they are in the first and last
        % point of interst in the spectrum
        if ind(1)>1
            ind(1) = ind(1)-1;
        end
        ind(2) = ind(2)+1;

        if freq(ind(1))<(minExtracFreq-diff(freq(1:2)))
            ind(1) = ind(1)+1;
        end

        indm = zeros(nSources,1);
        for iSource=1:nSources

            % Extracting the peak from envelope's PSD and then confirming
            % with a peak on the original signal
            [pks_env,locs_env] = findpeaks(Ffft(iSource,ind(1):ind(2)),'SORTSTR','descend');
            [pks_orig, locs_orig] = findpeaks(FfftSig(iSource,ind(1):ind(2)),'SORTSTR','descend');  % To check if a peak close to the coupled fp is available in the original signal
%             clear pks_env pks_orig

            % Ignore small peaks
            pks_orig = pks_orig/max(pks_orig);
            locs_orig = locs_orig(pks_orig>0.1);

            % Confirming the peak
            max_dist = max(1.5/winLen,1.5);     % maximum acceptable distance between peaks in evelope and the original signal's PSD
            count = 1;
            check_pks = 1;
            fp_loc = [];
            while check_pks && count<=length(locs_env)
                index = bst_closest(freq(locs_env(count)), freq(locs_orig));
                if abs(freq(locs_orig(index))-freq(locs_env(count)))<=max_dist
                    fp_loc = locs_env(count);
                    check_pks = 0;
                else
                    count = count+1;
                end
            end
            % If peak is not approved or no peak
            if isempty(fp_loc)
                fp_loc = 0;    % arbitrary value for fp  ==> will set the pac value to zero
                missedPcount = missedPcount +1;
            end

            indm(iSource) = fp_loc(1);
            clear pks_env locs_env
        end

        nestingFreq(ifreq,iTime,isources) = freq(ind(1)+indm-1);
        bandNesting = [max([squeeze(nestingFreq(ifreq,iTime,isources))-bandNestingLen/2,zeros(size(nestingFreq,3),1)],[],2),...
            squeeze(nestingFreq(ifreq,iTime,isources))+bandNestingLen/2];
        bandNesting(bandNesting<.15)=.15;

% Filtering in fP band
        if length(unique(bandNesting(:,1)))==1 && length(unique(bandNesting(:,2)))==1
            Xnesting = bst_bandpass_hfilter(X, sRate,bandNesting(1,1), bandNesting(1,2), isMirror, isRelax, fProlloff);    % Filtering
        else
            Xnesting = zeros(size(X));
            for i=1:length(isources)
                Xnesting(i,:) = bst_bandpass_hfilter(X(i,:), sRate, bandNesting(i,1), bandNesting(i,2),isMirror, isRelax, fProlloff);    % Filtering
            end
        end
        Xnesting = Xnesting(:,nMargin-nHilMar+1:fix((margin+winLen)*sRate)+nHilMar);              % Removing part of the margin
        % Hilbert transform
        Z = hilbert(Xnesting')';
        % Phase detection
        nestingPh = angle(Z-repmat(mean(Z,2),1,size(Z,2)));    % Phase of nesting frequency
        nestingPh = nestingPh(:,nHilMar:fix(winLen*sRate)+nHilMar-1);              % Removing the margin

        for ii=1:length(isources)
            iphase = find(diff(sign(nestingPh(ii,:) - nestingPh(ii,1)))==-2 | ...
                sign(nestingPh(ii,2:end)-nestingPh(ii,1))==0 | ...
                -(diff(sign(nestingPh(ii,:) - nestingPh(ii,1)))-1).*diff(nestingPh(ii,:)-nestingPh(ii,1)) >6 )-1;
            if isempty(iphase)
                iphase = length(nestingPh(ii,:));
            end
            amplitude = nestedEnv(ii,1:max(iphase));
            phase = nestingPh(ii,1:max(iphase));

            % Block resampling N=2
            numpoints=length(amplitude); %% number of sample points in raw signal

            if N==2
                minskip=numpoints/4; %% time lag must be at least this big
                maxskip=numpoints-numpoints/4; %% time lag must be smaller than this
                skip = ceil(minskip + (maxskip-minskip-1)*rand(2*nSur,1));
                skip(skip>maxskip)=[];
                skip(skip<minskip)=[];
                skip=skip(1:nSur,1);

                for iSur=1:nSur
                    surrogate_amplitude=[amplitude(skip(iSur):end) amplitude(1:skip(iSur)-1)];
                    PAC(ifreq,iTime,isources(ii),iSur) = sum(surrogate_amplitude.*exp(1i*phase),2)./max(iphase)./sqrt(mean(amplitude.^2,2));
                    DynamicPhase(ifreq,iTime,isources(ii),iSur) = angle(PAC(ifreq,iTime,isources(ii)));

                    if indm(ii)==0 % Fp not confirmed and arbitrary value for fp
                        PAC(ifreq,iTime,isources(ii),iSur) = 0;
                    end
                end

            elseif N>2 & N<numpoints/20
                % Block resampling N>2
%                 N = 10;
                numpoints=length(amplitude); %% number of sample points in raw signal
                block_len = fix(numpoints/N);
                remaining = numpoints - block_len*N;
                blocked_amp = reshape(amplitude(1:end-remaining),[block_len,N]);

                for iSur=1:nSur
                    order = randperm(N);
                    blocked_amp = blocked_amp(:,order);
                    surrogate_amplitude = [blocked_amp(:)', amplitude(end-remaining+1:end)];
                    PAC(ifreq,iTime,isources(ii),iSur) = sum(surrogate_amplitude.*exp(1i*phase),2)./max(iphase)./sqrt(mean(amplitude.^2,2));
                    DynamicPhase(ifreq,iTime,isources(ii),iSur) = angle(PAC(ifreq,iTime,isources(ii)));

                    if indm(ii)==0 % Fp not confirmed and arbitrary value for fp
                        PAC(ifreq,iTime,isources(ii),iSur) = 0;
                    end
                end

            else
                warning('Number of blocks should be at least 2, and less than 5% of data points')
            end
        end
    end
      disp(['iFreq: ', num2str(ifreq),' / ',num2str(nFa)]);
end

% ===== EXTRACTING THE PAC RELATED VALUES ===== %
[PACmax,maxInd] = max(abs(PAC),[],1);
% Fnested  = squeeze(nestedCenters(maxInd))';
% Sind     = repmat((1:nSources), nTime, 1);           % Source indices
% Tind     = repmat((1:nTime)', 1, nSources);              % Time indices
% linInd   = sub2ind(size(PAC),maxInd(:),Tind(:),Sind(:));
% Fnesting = reshape(nestingFreq(linInd),nTime,nSources)';
% phase    = reshape(angle(PAC(linInd)),nTime,nSources)'/pi*180;
% PACmax   = squeeze(PACmax)';

% ===== Interpolation in time domain for smoothing the results ==== %
if doInterpolation
    % Interpolation of PAC
    if nSources>1
        [X,Y,Z,W] = ndgrid(nestedCenters,TimeOut,[1:nSources], 1:nSur);
        ny = linspace(TimeOut(1), TimeOut(end), 2*length(TimeOut)-1);
        if logCenters
            nx = logspace(log10(nestedCenters(1)), log10(nestedCenters(end)), 2*nFa-1);
        else
            nx = linspace(nestedCenters(1), nestedCenters(end), 2*nFa-1);
        end
        [nX,nY,nZ,nW] = ndgrid(nx,ny,[1:nSources], 1:nSur);
        PAC = interpn(X,Y,Z,W,abs(PAC),nX,nY,nZ,nW,'linear',0);
        % make the notations similar
        tmp = nx; nx = ny;ny = tmp; clear tmp;

    else
        [X,Y,Z] = meshgrid(TimeOut,nestedCenters,[1:nSur]);
        nx = linspace(TimeOut(1), TimeOut(end), 2*nTime-1);
        if logCenters
            ny = logspace(log10(nestedCenters(1)), log10(nestedCenters(end)), 2*nFa-1);
        else
            ny = linspace(nestedCenters(1), nestedCenters(end), 2*nFa-1);
        end
        [nX,nY,nZ] = meshgrid(nx,ny,[1:nSur]);
        PAC = interp3(X,Y,Z,abs(squeeze(PAC)),nX,nY,nZ,'linear',0);
        PAC = permute(PAC,[1,2,4,3]);
    end
    TimeOut = nx;
    sPAC.HighFreqs = ny;
    clear nx nX nY nZ X Y Z

    % nestingFreq
    tmp = zeros(nFa*2-1, nTime, nSources, nSur);
    tmp(1:2:end,:,:,:) = nestingFreq;
    tmp(2:2:end,:,:,:) = nestingFreq(1:end-1,:,:,:);
    tmp2 = zeros(nFa*2-1, nTime*2-1, nSources, nSur);
    tmp2(:,1:2:end,:,:) = tmp;
    tmp2(:,2:2:end,:,:) = tmp(:,1:end-1,:,:);
    nestingFreq = tmp2;

    tmp = zeros(nFa*2-1, nTime, nSources, nSur);
    tmp(1:2:end,:,:,:) = DynamicPhase;
    tmp(2:2:end,:,:,:) = DynamicPhase(1:end-1,:,:,:);
    tmp2 = zeros(nFa*2-1, nTime*2-1, nSources, nSur);
    tmp2(:,1:2:end,:,:) = tmp;
    tmp2(:,2:2:end,:,:) = tmp(:,1:end-1,:,:);
    DynamicPhase = tmp2;
    clear tmp tmp2

end

Fnesting = [];
Fnested = [];
phase = [];

if missedPcount>0
disp(['Missed Peaks:',num2str(missedPcount),'/',num2str(nFa*nTime*nSources)])
end

% ===== OUTPUTS ===== %
if nTime >1
    sPAC.ValPAC = PACmax;
    sPAC.NestingFreq = Fnesting;
    sPAC.NestedFreq  = Fnested;
    sPAC.PhasePAC = phase;
    sPAC.TimeOut  = TimeOut;
    sPAC.DynamicPAC(:,:,1:nSources,:) = abs(PAC);
    sPAC.DynamicNesting(:,:,1:nSources,:)  = nestingFreq;
    sPAC.DynamicPhase(:,:,1:nSources,:)  = DynamicPhase;

        % == Generating two time points for Brainstorm structure ==
else
    sPAC.ValPAC = [PACmax(:), PACmax(:)];
    sPAC.NestingFreq = [Fnesting(:), Fnesting(:)];
    sPAC.NestedFreq  = [Fnested(:), Fnested(:)];
    sPAC.PhasePAC = [phase(:), phase(:)];
    sPAC.TimeOut  = [TimeOut, TimeOut+0.001];
    sPAC.DynamicPAC(:,1:2,1:nSources,:) = repmat(abs(PAC),[1,2,1]);
    sPAC.DynamicNesting(:,1:2,1:nSources,:)  = repmat(abs(nestingFreq),[1,2,1]);
    sPAC.DynamicPhase(:,1:2,1:nSources,:)  = repmat(abs(DynamicPhase),[1,2,1]);
end

end


function meshI = genIVmesh(mesh)

Np = size(mesh.p,1);
node = [mesh.p;mesh.eIntP];
allCutElem = mesh.t(mesh.tLoc<0,:);
isCutElem = (mesh.tLoc<0);
vSign = [mesh.pLoc;zeros(size(mesh.eIntP,1),1)];
isInterfaceNode = false(size(node,1), 1);
isInterfaceNode(allCutElem(:)) = true; % include the vertices of allCutElem
numCutElemV1 = sum(isInterfaceNode);
isInterfaceNode(Np+1:end) = true;    % and the cut points and the aux points
allCutElemReoderTmp = zeros(size(node,1),1);
numCutElemV2 = sum(isInterfaceNode);
allCutElemReoderTmp(isInterfaceNode) = 1:numCutElemV2;
allCutElemReoder = allCutElemReoderTmp(allCutElem);
interfaceNode = node(isInterfaceNode,:);
ntI = -min(mesh.tLoc);
intID = find(mesh.tLoc<0);

tetElem = zeros(12*ntI,4);
tetElemLoc = zeros(12*ntI,1);
idx2cube = zeros(12*ntI,1);
tetcount = 0;

for i = 1:ntI
    if i == 3
        stp = 1;
    end
    tID = intID(i);
    t_e = mesh.t_e(tID,:);
    pLocK = mesh.pLoc(mesh.t(tID,:));
    vert0 = mesh.p(mesh.t(tID,:),:);
    nodeid = [allCutElemReoder(i,:),numCutElemV1-mesh.eLoc(t_e(mesh.eLoc(t_e)<0))'];
    vert2 = vert0(pLocK>0,:); % plus domain
    vert1 = vert0(pLocK<0,:); % minus domain
    intpt0 = mesh.eIntP(-mesh.eLoc(t_e(mesh.eLoc(t_e)<0)),:);
    id1 = [find(pLocK<0)', 5:4+size(intpt0,1)];
    id2 = [find(pLocK>0)', 5:4+size(intpt0,1)];
    p1 = [vert1;intpt0]; DT = delaunayTriangulation(p1); t1 = DT.ConnectivityList;
    p2 = [vert2;intpt0]; DT = delaunayTriangulation(p2); t2 = DT.ConnectivityList;
    tetElem(tetcount+1:tetcount+size(t1,1),:) = nodeid(id1(t1));
    idx2cube(tetcount+1:tetcount+size(t1,1)) = tID;
    tetElemLoc(tetcount+1:tetcount+size(t1,1)) = 1; % inside subdomain
    tetcount = tetcount+size(t1,1);
    tetElem(tetcount+1:tetcount+size(t2,1),:) = nodeid(id2(t2));
    idx2cube(tetcount+1:tetcount+size(t2,1)) = tID;
    tetElemLoc(tetcount+1:tetcount+size(t2,1)) = 2; % outside subdomain
    tetcount = tetcount+size(t2,1);
end
tetElem(tetcount+1:end,:) = [];
tetElemLoc(tetcount+1:end,:) = [];
idx2cube(tetcount+1:end,:) = [];
[tetElem, ortidx, volume] = fixorder3(interfaceNode, tetElem);

% there are some tets which are coplane, need to get rid of them
d12 = interfaceNode(tetElem(:,2),:) - interfaceNode(tetElem(:,1),:);
d13 = interfaceNode(tetElem(:,3),:) - interfaceNode(tetElem(:,1),:);
d14 = interfaceNode(tetElem(:,4),:) - interfaceNode(tetElem(:,1),:);
d12 = sum(d12.^2,2).^(1/2); d13 = sum(d13.^2,2).^(1/2); d14 = sum(d14.^2,2).^(1/2);
ld = max([d12,d13,d14],[],2);
idPlane = (volume./ld<=10^(-16));

tetElem(idPlane,:) = [];
volume(idPlane,:) = [];
tetElemLoc(idPlane,:) = [];
idx2cube(idPlane,:) = [];
localidx2globalidx = find(isInterfaceNode); % tetElem points to interfaceNode
tetElem = localidx2globalidx(tetElem);  % map to the global index of node
% there are some tets which are on the interface, but contained in the surrounding tets
% need to get rid of them (but the following algorithm may not be robust)
tetSign = vSign(tetElem);
idSliver = (sum(abs(tetSign),2)==0);
tetElem(idSliver,:) = [];
volume(idSliver,:) = [];
tetElemLoc(idSliver,:) = [];
idx2cube(idSliver,:) = [];
PolyVolume = accumarray([idx2cube,tetElemLoc],volume);
PolyVolume = PolyVolume(mesh.tLoc<0,:);

%% Get triangular faces for interrior elements and interface
localFace = [2 3 4; 1 4 3; 1 2 4; 1 3 2];
NT = size(tetElem,1);
tface = zeros(4*NT, 3);
tface2elem = zeros(4*NT, 1);
iface = zeros(4*NT, 3);
iface2elem = zeros(4*NT, 1);
% find the interior tet elements
isIntTet1 = min(vSign(tetElem),[], 2) == -1; % can not be == -1 as there is sliver
isIntTet2 = sum(abs(vSign(tetElem)),2) == 0;
isIntTet = isIntTet1 | isIntTet2;
intTet = tetElem(isIntTet,:);
% find the corresponding cube indices
intIdx2cube = idx2cube(isIntTet);
% find triangular interface
T = auxstructure3(intTet);
neighbor = T.neighbor; % if a face is on the boundary, then the neighbor element index is itself
clear T;
tmp = (1:size(intTet, 1))';
ct = 0;
ci = 0;
for i = 1:4
    face = intTet(:, localFace(i,:));
    % find the triangle faces of polyhedron
    % 1. face and its neighbor associated to different cubes, and
    % 2. face is not on the boundary of all cut elements (which are squares)
    isPolyTriFace = ((neighbor(:, i) == tmp) | (intIdx2cube ~= intIdx2cube(neighbor(:,i)))) &...
        (sum(abs(vSign(face)), 2) >= 10^(-12));% & (sum(abs(vSign(face)), 2) > 0);
    c2 = ct + sum(isPolyTriFace);
    tface((ct+1):c2,:) = face(isPolyTriFace,:);
    tface2elem((ct+1):c2,:) = intIdx2cube(isPolyTriFace); % the indices of the polyhedron
    ct = c2;
    % note that only interior elements are treated

    % find the triangle faces on interface with normal points to exterior
    % 1. face is on the boundary and
    % 2. all vertices are on the interface
    isInterface = (sum(abs(vSign(face)), 2) <= 10^(-12));

    % add to interface
    c4 = ci + sum(isInterface);
    iface((ci+1):c4,:) = face(isInterface,:); % the interface tri faces.
    iface2elem((ci+1):c4,:) = intIdx2cube(isInterface); % the indices of the polyhedron
    ci = c4;
end
iface((ci+1):end,:) = [];
iface2elem((ci+1):end,:) = [];
c2old = c2;
% plot to check
%trisurf(tface(1:c2,:),node(:,1),node(:,2),node(:,3))
%trisurf(iface(1:ci,:),node(:,1),node(:,2),node(:,3))

% Find the triangular faces for exterior elements
extTet = tetElem(~isIntTet, :);
extIdx2cube = idx2cube(~isIntTet);

T = auxstructure3(extTet);
neighbor = T.neighbor;
clear T;
tmp = (1:size(extTet,1))';
for i = 1:4
    face = extTet(:, localFace(i,:));
    % find the triangle faces of polyhedron
    % 1. face and its neighbor associated to different cubes, and
    % 2. face is not on the boundary of all cut elements (which are squares)
    isPolyTriFace = ((neighbor(:, i) == tmp) | (extIdx2cube ~= extIdx2cube(neighbor(:,i)))) &...
        (sum(abs(vSign(face)), 2) > 0);
    c2 = ct + sum(isPolyTriFace);
    tface((ct+1):c2,:) = face(isPolyTriFace,:);
    tface2elem((ct+1):c2,:) = extIdx2cube(isPolyTriFace);
    % index of exterior polyhedron is append to the end of elem
    ct = c2;

end

% plot to check
%trisurf(tface(c2old+1:c2,:),node(:,1),node(:,2),node(:,3))

tface((ct+1):end,:) = [];    % remove empty meomory
tface2elem((ct+1):end) = [];
face2elemLoc = [ones(c2old,1);2*ones(c2-c2old,1)]; % face2elemLoc is the same as face location

meshI.tface = tface;
meshI.tface2elem = tface2elem;
meshI.iface = iface;
meshI.iface2elem = iface2elem;
meshI.face2elemLoc = face2elemLoc;
meshI.PolyVolume = PolyVolume;
% meshI.vSign = vSign;
meshI.node = node;
meshI.tetElem = tetElem;
meshI.tetElemLoc = tetElemLoc;
meshI.idx2cube = idx2cube;
meshI.tetVolume = volume;
meshI.vSign = vSign;

end