function handles = distributionPlot(varargin)
%DISTRIBUTIONPLOT creates violin plots for convenient visualization of multiple distributions
% 2019.03.28 [13:29:26] - Biafra fix % # issue
% SYNOPSIS: handles = distributionPlot(data,propertyName,propertyValue,...)
%           handles = distributionPlot(ah,...)
%
% INPUT data : m-by-nData array of values, or vector of grouped data (use
%           the 'groups' property to specify the grouping variable), or
%           cell array of length nData.
%           The cell array can either contain vectors with values, or
%           m-by-2 arrays with [bins,counts] if you want to determine the
%           histograms by yourself (m can be different between cell
%           elements). Note that arrays inside cells with any
%           other shape than m-by-2 are reshaped to vector an a warning is
%           thrown (DISTRIBUTIONPLOT:AUTORESHAPE).
%
%       DISTRIBUTIONPLOT accepts the following propertyName/propertyValue
%           pairs (all are optional):
%
%       distWidth :  width of distributions; ideally between 0 and 1.
%           1 means that adjacent distributions might touch. Default: 0.9
%       variableWidth : If true, the width of the distribution changes,
%           reflecting the shape of the histogram of the data. If false,
%           the distribution is only encoded by color levels. Default: true
%       color : uniform coloring of histograms. Supply either a color
%           string ('r'), or a truecolor vector ([1 0 0]). Use a
%           cell array of length nData to specify one color per
%           distribution. Default: 'k'
%           If variableWidth is set to false, a colormap is generated that
%           goes from white to the chose color (or from black, if
%           invert==true).
%           If both 'color', and 'colormap' are specified, 'colormap' takes
%           precedence.
%       colormap : colormap used to describe the distribution (first row
%           corresponds to bins with least data, last row corresponds to
%           bins with most data (invert the grayscale colormap to have
%           black indicate the most data).
%           Supply a cell array of length nData to color distributions
%           individually. Note that using multiple colormaps means that
%           the colorbar doesn't contain much useful information.
%           Default: []
%           Colormap will index into the figure colormap, which will be
%           modified by distributionPlot. This is done to allow editing the
%           distributions in e.g. Adobe Illustrator.
%           If both 'color', and 'colormap' are specified, 'colormap' takes
%           precedence.
%       globalNorm : normalization for bin width (x-direction)
%           0 : every histogram is normalized individually so that the
%               maximum bin width is equal to distWidth. This is best
%               suited to comparing distribution shapes. Default.
%           1 : histograms are normalized such that equal bin width
%               reports equal numbers of counts per bin.
%           2 : histograms are normalized so that the relative areas
%               covered by the histograms reflect the relative total number
%               of data points.
%           3 : histograms areas are normalized so that relative densities
%               are the same across histograms. Thus, if
%               data = {rand(100,1),rand(500,1)},
%               then
%               distributionPlot(data,'globalNorm',2,'histOpt',0,'divFactor',10)
%               shows the left histogram 5x as wide as the right, while
%               distributionPlot(data,'globalNorm',3,'histOpt',0,'divFactor',10)
%               displays both histograms equally wide, since each bin
%               contains ~10% of the data.
%           Options 1 and 2 produce similar results if the bins are spaced
%           equally for the distributions. Options 0 and 3 produce similar
%           results if the data are drawn from the same distributions.
%           Note that colormaps currently always report the number of data
%           points per bin; 'globalNorm' only applies to the distribution
%           shape.
%
%       groups : grouping variable for grouped data. Grouping will be
%                   resolved by calling grp2idx, and unless xNames have
%                   been supplied, group names determine the x-labels.
%                   If the grouping variable is numeric, group labels also
%                   determine x-values, unless the parameter xValues has
%                   been specified.
%       histOpt : histogram type to plot
%                   0 : use hist command (no smoothing, fixed number of
%                       bins)
%                   1 : smoothened histogram using ksdensity with
%                       Normal kernel. Default.
%                   1.1: smoothened histogram using ksdensity where the
%                       kernel is robustly estimated via histogram.m.
%                       Normal kernel.
%                   2 : histogram command (no smoothing, automatic
%                       determination of thickness (y-direction) of bins)
%       divFactor : Parameter dependent on histOpt. If...
%                   histOpt == 0: divFactor = # of bins. Default: 25.
%                       Alternatively, pass a vector which will be
%                       interpreted as bin centers.
%                   histOpt == 1: divFactor decides by how much the default
%                       kernel-width is multiplied in order to avoid an
%                       overly smooth histogram. Default: 1/2
%                   histOpt == 2: divFactor decides by how much the
%                       automatic bin width is multiplied in order to have
%                       more (<1) or less (>1) detail. Default: 1
%       addSpread : if 1, data points are plotted with plotSpread.
%                   distWidth is ideally set to 0.95
%                   This option is not available if the data is supplied as
%                   histograms.
%                   Please download plotSpread.m separately from the File
%                   Exchange using the link in the remarks
%       showMM : if 1, mean and median are shown as red crosses and
%                green squares, respectively. This is the default
%                2: only mean
%                3: only median
%                4: mean +/- standard error of the mean (no median)
%                5: mean +/- standard deviation (no median)
%                6: draw lines at the 25,50,75 percentiles (no mean)
%                0: plot neither mean nor median
%       xValues: x-coordinate where the data should be plotted.
%                If xValues are given, "distWidth" is scaled by the median
%                difference between adjacent (sorted) x-values. Note that
%                this may lead to overlapping distributions. Default:
%                1:nData
%       xNames : cell array of length nData containing x-tick names
%               (instead of the default '1,2,3')
%       xMode  : if 'auto', x-ticks are spaced automatically. If 'manual',
%                there is a tick for each distribution. If xNames is
%                provided as input, xMode is forced to 'manual'. Default:
%                'manual'.
%          NOTE: SPECIFYING XNAMES OR XVALUES OR XMODE WILL ERASE PREVIOUS
%                LABELS IF PLOTTING INTO EXISTING AXES
%       yLabel : string with label for y-axis. Default : ''
%                If empty and data is histograms, ylabel is set to 'counts'
%       invert : if 1, axes color is changed to black, and colormap is
%                   inverted.
%       histOri: Orientation of histogram. Either 'center', 'left', or
%                'right'. With 'left' or 'right', the left or right half of
%                the standard violin plot is shown. Has no effect if
%                variableWidth is false. Default: center
%       xyOri  : orientation of axes. Either 'normal' (=default), or
%                'flipped'. If 'flipped', the x-and y-axes are switched, so
%                that violin plots are horizontal. Consequently,
%                axes-specific properties, such as 'yLabel' are applied to
%                the other axis.
%       widthDiv : 1-by-2 array with [numberOfDivisions,currentDivision]
%                widthDiv allows cutting the stripe dedicated to a single
%                distribution into multible bands, which can be filled with
%                sequential calls to distributionPlot. This is one way
%                to compare two (or more) sequences of distributions. See
%                example below.
%       ah : axes handle to plot the distributions. Default: gca
%
% OUTPUT handles : 1-by-4 cell array with patch-handles for the
%                  distributions, plot handles for mean/median, the
%                  axes handle, and the plotSpread-points handle
%
%
% EXAMPLES
%         %--Distributions contain more information than boxplot can capture
%         r = rand(1000,1);
%         rn = randn(1000,1)*0.38+0.5;
%         rn2 = [randn(500,1)*0.1+0.27;randn(500,1)*0.1+0.73];
%         rn2=min(rn2,1);rn2=max(rn2,0);
%         figure
%         ah(1)=subplot(3,4,1:2);
%         boxplot([r,rn,rn2])
%         ah(2)=subplot(3,4,3:4);
%         distributionPlot([r,rn,rn2],'histOpt',2); % histOpt=2 works better for uniform distributions than the default
%         set(ah,'ylim',[-1 2])
%
%         %--- additional options
%
%         data = [randn(100,1);randn(50,1)+4;randn(25,1)+8];
%         subplot(3,4,5)
%
%         %--- defaults
%         distributionPlot(data);
%         subplot(3,4,6)
%
%         %--- show density via custom colormap only, show mean/std,
%         distributionPlot(data,'colormap',copper,'showMM',5,'variableWidth',false)
%         subplot(3,4,7:8)
%
%         %--- auto-binwidth depends on # of datapoints; for small n, plotting the data is useful
%         % note that this option requires the additional installation
%         % of plotSpread from the File Exchange (link below)
%         distributionPlot({data(1:5:end),repmat(data,2,1)},'addSpread',true,'showMM',false,'histOpt',2)
%
%         %--- show quantiles
%         subplot(3,4,9),distributionPlot(randn(100,1),'showMM',6)
%
%         %--- horizontal orientation
%         subplot(3,4,10:11),
%         distributionPlot({chi2rnd(3,1000,1),chi2rnd(5,1000,1)},'xyOri','flipped','histOri','right','showMM',0),
%         xlim([-3 13])
%
%         %--- compare distributions side-by-side (see also example below)
%         % plotting into specified axes will throw a warning that you can
%         % turn off using " warning off DISTRIBUTIONPLOT:ERASINGLABELS "
%         ah = subplot(3,4,12);
%         subplot(3,4,12),distributionPlot(chi2rnd(3,1000,1),'histOri','right','color','r','widthDiv',[2 2],'showMM',0)
%         subplot(3,4,12),distributionPlot(chi2rnd(5,1000,1),'histOri','left','color','b','widthDiv',[2 1],'showMM',0)
%
%         %--Use globalNorm to generate meaningful colorbar
%         data = {randn(100,1),randn(500,1)};
%         figure
%         distributionPlot(data,'globalNorm',true,'colormap',1-gray(64),'histOpt',0,'divFactor',[-5:0.5:5])
%         colorbar
%
%         %--Use widthDiv to compare two series of distributions
%         data1 = randn(500,5);
%         data2 = bsxfun(@plus,randn(500,5),0:0.1:0.4);
%         figure
%         distributionPlot(data1,'widthDiv',[2 1],'histOri','left','color','b','showMM',4)
%         distributionPlot(gca,data2,'widthDiv',[2 2],'histOri','right','color','k','showMM',4)
%
%         %--Christmas trees!
%           x=meshgrid(1:10,1:10);
%           xx = tril(x);
%           xx = xx(xx>0);
%           figure
%           hh=distributionPlot({xx,xx,xx},'color','g','addSpread',1,'histOpt',2,'showMM',0);
%           set(hh{4}{1},'color','r','marker','o')
% END
%
% REMARKS To show distributions as clouds of points (~beeswarm plot),
%         and/or to use the option "addSpread", please download the
%         additional function plotSpread.m from the File Exchange
%         http://www.mathworks.com/matlabcentral/fileexchange/37105-plot-spread-points-beeswarm-plot
%
%         I used to run ksdensity with the Epanechnikov kernel. However,
%         for integer data, the shape of the kernel can produce peaks
%         between the integers, which is not ideal (use histOpt=2 for
%         integer valued data).
%
%         A previous iteration of distributionPlot used the input
%         specifications below. They still work to ensure backward
%         compatibility, but are no longer supported or updated.
%           handles = distributionPlot(data,distWidth,showMM,xNames,histOpt,divFactor,invert,addSpread,globalNorm)
%           where distWidth of 1 means that the maxima
%           of  two adjacent distributions might touch. Negative numbers
%           indicate that the distributions should have constant width, i.e
%           the density is only expressed through greylevels.
%           Values between 1 and 2 are like values between 0 and 1, except
%           that densities are not expressed via graylevels. Default: 1.9
%
%
% SEE ALSO histogram, ksdensity, plotSpread, boxplot, grp2idx
%

% created with MATLAB ver.: 7.6.0.324 (R2008a) on Windows_NT
%
% created by: Jonas Dorn; jonas.dorn@gmail.com
% DATE: 08-Jul-2008
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%====================================
%% TEST INPUT
%====================================

% set defaults
def.xNames = [];
def.showMM = 1;
def.distWidth = 0.9;
def.histOpt = 1;
def.divFactor = [25,2,1];
def.invert = false;
def.colormap = [];
def.color = 'k';
def.addSpread = false;
def.globalNorm = false;
def.variableWidth = true;
def.groups = [];
def.yLabel = '';
def.xValues = '';
def.xMode = 'manual';
def.histOri = 'center';
def.xyOri = 'normal';
def.widthDiv = [1 1];
isHistogram = false; % # this parameter is not set by input

if nargin == 0 || isempty(varargin{1})
    error('not enough input arguments')
end

% check for axes handle
if ~iscell(varargin{1}) && isscalar(varargin{1}) == 1 && ...
        ishandle(varargin{1}) && strcmp(get(varargin{1},'Type'),'axes')
    ah = varargin{1};
    data = varargin{2};
    varargin(1:2) = [];
    newAx = false;

else
    ah = gca;
    data = varargin{1};
    varargin(1) = [];
    newAx = true;
end

% check for current axes limits. Set NaN if the axes have no children
% yet - we need that in case we're building a complicated set of
% distributions
if ~isempty(get(ah,'children'))
    xAxLim = xlim;
    yAxLim = ylim;
else
    [xAxLim,yAxLim] = deal([NaN NaN]);
end

fh = get(ah,'Parent');

% check data. If not cell, convert
if ~iscell(data)
    [nPoints,nData] = size(data);
    data = mat2cell(data,nPoints,ones(nData,1));
else
    % get nData
    data = data(:);
    nData = length(data);
    % make sure all are vectors
    badCol = ~cellfun(@isvector,data) & ~cellfun(@isempty,data);
    if any(badCol)
        nCols = cellfun(@(x)(size(x,2)),data(badCol));
        if all(nCols==2)
            % bins,counts
            isHistogram = true;
        else
            warning('DISTRIBUTIONPLOT:AUTORESHAPE',...
                'Elements %s of the cell array are not vectors. They will be reshaped automatically',...
                num2str(find(badCol)'));
            data(badCol) = cellfun(@(x)(x(:)),data(badCol),'UniformOutput',false);
        end
    end
end

parserObj = inputParser;
parserObj.FunctionName = 'distributionPlot';
stdWidth = 1; % scaling parameter for variableWidth with uneven x-values
% check whether we're dealing with pN/pV or straight arguments
if ~isempty(varargin) && ~ischar(varargin{1}) && ~isstruct(varargin{1})
    % use old format
    % distWidth,showMM,xNames,histOpt,divFactor,invert,addSpread,globalNorm
    def.distWidth = 1.9;
    parserObj.addOptional('distWidth',def.distWidth);
    parserObj.addOptional('showMM',def.showMM);
    parserObj.addOptional('xNames',def.xNames);
    parserObj.addOptional('histOpt',def.histOpt);
    parserObj.addOptional('divFactor',def.divFactor);
    parserObj.addOptional('invert',def.invert);
    parserObj.addOptional('addSpread',def.addSpread);
    parserObj.addOptional('globalNorm',def.globalNorm);
    parserObj.addOptional('groups',def.groups);
    parserObj.addOptional('yLabel',def.yLabel);
    parserObj.addOptional('color',def.color);

    parserObj.parse(varargin{:});
    opt = parserObj.Results;
    % fill in defaults that are not supported in the old version of the
    % code
    opt.colormap = [];
    opt.variableWidth = true;
    opt.histOri = 'center';
    opt.xValues = [];
    opt.xMode = 'auto';
    opt.xyOri = 'normal';
    opt.widthDiv = [1 1];

    % overwrite empties with defaults - inputParser considers empty to be a
    % valid input.
    fnList = fieldnames(opt);
    for fn = fnList'
        if isempty(opt.(fn{1}))
            opt.(fn{1}) = def.(fn{1});
        end
    end

    % fix a few parameters
    if opt.distWidth > 1
        opt.distWidth = opt.distWidth - 1;
    else
        opt.colormap = 1-gray(128);
    end
    if opt.distWidth < 0
        opt.variableWidth = false;
        opt.distWidth = abs(opt.distWidth);
    end

    if ~isempty(opt.xNames)
        opt.xMode = 'manual';
    end

else
    defNames = fieldnames(def);
    for dn = defNames(:)'
        parserObj.addParamValue(dn{1},def.(dn{1}));
    end

    parserObj.parse(varargin{:});
    opt = parserObj.Results;

    % if groups: deal with data
    if ~isempty(opt.groups)
        [idx,labels,vals] = grp2idx(opt.groups);
        % convert data to cell array
        data = accumarray(idx,data{1},[],@(x){x});
        nData = length(data);
        % if not otherwise provided, use group labels for xnames
        if isempty(opt.xNames)
            opt.xNames = labels;
            if ~iscell(opt.xNames)
                opt.xNames = num2cell(opt.xNames);
            end
        end
        if isnumeric(vals) && isempty(opt.xValues)
            opt.xValues = vals;
        end

    end

    if ~ischar(opt.xyOri) || ~any(ismember(opt.xyOri,{'normal','flipped'}))
        error('option xyOri must be either ''normal'' or ''flipped'' (is ''%s'')',opt.xyOri);
    end

end
% common checks

% default x-values: 1:n
if isempty(opt.xValues)
    opt.xValues = 1:nData;
elseif length(opt.xValues) ~= nData
    error('please supply as many x-data values as there are data entries')
elseif length(opt.xValues) > 1 % only check for scale if more than 1 value
    % scale width
    stdWidth = median(diff(sort(opt.xValues)));
    opt.distWidth = opt.distWidth * stdWidth;
end

if ~isscalar(opt.divFactor) && length(opt.divFactor) == 3 && all(opt.divFactor==def.divFactor)
    opt.divFactor = opt.divFactor(floor(opt.histOpt)+1);
end
if isHistogram
    opt.histOpt = 99;
    if isempty(opt.yLabel)
        opt.yLabel = 'counts';
    end
end

% check colors/colormaps: do we need to expand colormap?
if ~iscell(opt.colormap)
    opt.colormap = {opt.colormap};
end
if ~iscell(opt.color)
    opt.color = {opt.color};
end
for iColor = 1:length(opt.color)
    if ischar(opt.color{iColor})
        opt.color{iColor} = colorCode2rgb(opt.color{iColor});
    end
end

% expand - if only single colormap specified, we expand only once
if ~opt.variableWidth
    missingColormaps = find(cellfun(@isempty,opt.colormap));
    for iMissing = missingColormaps(:)'

        endColor = opt.color{max(iMissing,length(opt.color))};
        % normally, we go from white to color
        cmap = zeros(128,3);
        for rgb = 1:3
            cmap(:,rgb) = linspace(1,endColor(rgb),128);
        end
        opt.colormap{iMissing} = cmap;

    end
end

% if we have colormaps, we need to create a master which we add to the
% figure. Invert if necessary, and expand the cell array to nData
colormapLength = cellfun(@(x)size(x,1),opt.colormap);
if any(colormapLength>0)

    colormap = cat(1,opt.colormap{:});
    if opt.invert
        colormap = 1-colormap;
    end
    set(fh,'Colormap',colormap)
    if length(opt.colormap) == 1
        opt.colormap = repmat(opt.colormap,nData,1);
        colormapLength = repmat(colormapLength,nData,1);
        colormapOffset = zeros(nData,1);
        singleMap = true;
    else
        colormapOffset = [0;cumsum(colormapLength(1:end-1))];
        singleMap = false;
    end

else

    colormapLength = zeros(nData,1);
    if length(opt.color) == 1
        opt.color = repmat(opt.color,nData,1);
    end
    if opt.invert
        opt.color = cellfun(@(x)1-x,opt.color,'uniformOutput',false);
    end
end

% set hold on
holdState = get(ah,'NextPlot');
set(ah,'NextPlot','add');

% if new axes: invert
if newAx && opt.invert
    set(ah,'Color','k')
end

%===================================

%===================================
%% PLOT DISTRIBUTIONS
%===================================

% assign output
hh = NaN(nData,1);
[m,md,sem,sd] = deal(nan(nData,1));
if opt.showMM == 6
    md = nan(nData,3,3); % md/q1/q3, third dim is y/xmin/xmax
end

% get base x-array
% widthDiv is a 1-by-2 array with
% #ofDivs, whichDiv
% The full width (distWidth) is split into
% #ofDivs; whichDiv says which "stripe" is active
xWidth = opt.distWidth/opt.widthDiv(1);
xMin = -opt.distWidth/2;
xLow = xMin + xWidth * (opt.widthDiv(2)-1);
xBase = [-xWidth;xWidth;xWidth;-xWidth]/2;
xOffset = xLow + xWidth/2;

% b/c of global norm: loop twice
plotData = cell(nData,2);

% loop through data. Prepare patch input, then draw patch into gca
for iData = 1:nData
    currentData = data{iData};
    % only plot if there is some finite data
    if ~isempty(currentData(:)) && any(isfinite(currentData(:)))

        switch floor(opt.histOpt)
            case 0
                % use hist
                [xHist,yHist] = hist(currentData,opt.divFactor);

            case 1
                % use ksdensity

                if opt.histOpt == 1.1
                    % use histogram to estimate kernel
                    [dummy,x] = histogramDistributionPlot(currentData); %#ok<ASGLU>
                    if length(x) == 1
                        % only one value. Make fixed distribution
                        dx = 0.1;
                        yHist = x;
                        xHist = sum(isfinite(currentData));
                    else
                        dx = x(2) - x(1);

                    % make sure we sample frequently enough
                    x = min(x)-dx:dx/3:max(x)+dx;
                    [xHist,yHist] = ksdensity(currentData,x,'kernel','normal','width',dx/(1.5*opt.divFactor));
                    end
                else

                    % x,y are switched relative to normal histogram
                    [xHist,yHist,u] = ksdensity(currentData,'kernel','normal');
                    % take smaller kernel to avoid over-smoothing
                    if opt.divFactor ~= 1
                        [xHist,yHist] = ksdensity(currentData,'kernel','normal','width',u/opt.divFactor);
                    end
                end

                % modify histogram such that the sum of bins (not the
                % integral under the curve!) equals the total number of
                % observations, in order to be comparable to hist
                xHist = xHist/sum(xHist)*sum(isfinite(currentData));

            case 2
                % use histogram - bar heights are counts as in hist
                [xHist,yHist] = histogramDistributionPlot(currentData,opt.divFactor,0);
            case 99
                % bins,counts already supplied
                xHist = currentData(:,2)';
                yHist = currentData(:,1)';
        end
        plotData{iData,1} = xHist;
        plotData{iData,2} = yHist;
    end
end

goodData = find(~cellfun(@isempty,plotData(:,1)));
% get norm
switch opt.globalNorm
    case 3
        % #3 normalizes relative densities
        xNorm(goodData) = cellfun(@(x)min(diff(x)),plotData(goodData,2));
        xNorm(goodData) = xNorm(goodData) .* cellfun(@sum,plotData(goodData,1))';
        maxNorm(goodData) = cellfun(@max,plotData(goodData,1));
        xNorm(goodData) = xNorm(goodData)*max(maxNorm(goodData)./xNorm(goodData));

    case 2
        % #2 should normalize so that the integral of the
        % different histograms (i.e. area covered) scale with the
        % respective sum of counts across all bins. Requires evenly spaced
        % histograms at the moment
        xNorm(goodData) = cellfun(@(x)min(diff(x)),plotData(goodData,2));
        maxNorm(goodData) = cellfun(@max,plotData(goodData,1));
        xNorm(goodData) = xNorm(goodData)*max(maxNorm(goodData)./xNorm(goodData));
    case 1
        xNorm(goodData) = max(cat(2,plotData{:,1}));
    case 0
        xNorm(goodData) = cellfun(@max,plotData(goodData,1));
end

for iData = goodData'

    % find current data again
    currentData = data{iData};

    xHist = plotData{iData,1};
    yHist = plotData{iData,2};

    % find y-step
    dy = min(diff(yHist));
    if isempty(dy)
        dy = 0.1;
    end

    % create x,y arrays
    nPoints = length(xHist);
    xArray = repmat(xBase,1,nPoints);
    yArray = repmat([-0.5;-0.5;0.5;0.5],1,nPoints);

    % x is iData +/- almost 0.5, multiplied with the height of the
    % histogram
    if opt.variableWidth

        tmp = xArray.*repmat(xHist,4,1)./xNorm(iData);

        switch opt.histOri
            case 'center'
                % we can simply use xArray
                xArray = tmp;
            case 'right'
                % shift everything to the left
                delta = tmp(1,:) - xArray(1,:);
                xArray = bsxfun(@minus,tmp,delta);
            case 'left'
                % shift everything to the right
                delta = tmp(1,:) - xArray(1,:);
                xArray = bsxfun(@plus,tmp,delta);
        end

        xArray = xArray + opt.xValues(iData);

    else
        xArray = xArray + iData;
    end

    % add offset (in case we have multiple widthDiv)
    xArray = xArray + xOffset;

    % yData is simply the bin locations
    yArray = repmat(yHist,4,1) + dy*yArray;

    % add patch
    vertices = [xArray(:),yArray(:)];
    faces = reshape(1:numel(yArray),4,[])';

    if colormapLength(iData) == 0
        colorOpt = {'FaceColor',opt.color{iData}};
    else
        % calculate index into colormap
        if singleMap
            % use scaled mapping so that colorbar is meaningful
            if opt.globalNorm > 0
                colorOpt = {'FaceVertexCData',xHist','CDataMapping','scaled','FaceColor','flat'};
            else
                colorOpt = {'FaceVertexCData',xHist'/xNorm(iData),'CDataMapping','scaled','FaceColor','flat'};
            end

        else
            idx = round((xHist/xNorm(iData))*(colormapLength(iData)-1))+1;
            colorOpt = {'FaceVertexCData',idx'+colormapOffset(iData),'CDataMapping','direct','FaceColor','flat'};
        end
    end

    switch opt.xyOri
        case 'normal'
            hh(iData)= patch('Vertices',vertices,'Faces',faces,'Parent',ah,colorOpt{:},'EdgeColor','none');
        case 'flipped'
            hh(iData)= patch('Vertices',vertices(:,[2,1]),'Faces',faces,'Parent',ah,colorOpt{:},'EdgeColor','none');
    end

    if opt.showMM > 0
        if isHistogram
            [m(iData),sem(iData)] = weightedStats(currentData(:,1),currentData(:,2),'w');
            sd(iData) = sem(iData) * sqrt(sum(currentData(:,2)));
            % weighted median: where we're at middle weight
            % may need some tweaking
            goodCurrentData = sortrows(currentData(all(isfinite(currentData),2),:),1);
            weightList = cumsum(goodCurrentData(:,2));
            weightList = weightList / weightList(end);
            md(iData) = goodCurrentData(find(weightList>0.5,1,'first'),1);
        else
            m(iData) = nanmean(currentData);
            md(iData) = nanmedian(currentData);
            sd(iData) = nanstd(currentData);
            sem(iData) = sd(iData)/sqrt(sum(isfinite(currentData)));
        end

        if opt.showMM == 6
            % read quantiles - "y"-value, plus x-start-stop
            % re-use md array which allows using a loop below instead of
            % lots of copy-paste
            % md array is md/q1/q3, with third dimension y/xmin/xmax

            md(iData,2,1) = prctile(currentData,25);
            md(iData,3,1) = prctile(currentData,75);

            for qq = 1:3
                % find corresponding y-bin
                yLoc =  repmat(...
                    any(yArray>md(iData,qq,1),1) & any(yArray<=md(iData,qq,1),1),...
                    [4 1]);
                % look up corresponding x-values. Note that there is a bit
                % of a risk that the line will be exactly between two very
                % different bins - but if we make the line longer, it will
                % be ugly almost all the time
                md(iData,qq,2) = min( xArray( yLoc ) );
                md(iData,qq,3) = max( xArray( yLoc ) );
            end

        end
    end
end % loop

sh = [];
if opt.addSpread
    if isHistogram
        disp('Option addSpread is unavailable if data is supplied as histograms. Call plotSpread separately')
    else
        % add spread
        try
            sh = plotSpread(ah,data,'xValues',opt.xValues,'xyOri',opt.xyOri);
            set(sh{1},'color',[0,128,255]/255);
        catch me
            if strcmp(me.identifier,'MATLAB:UndefinedFunction')
                error('plotSpread not found. Please download it from the Matlab File Exchange')
            else
                rethrow(me)
            end
        end
    end
end

mh = [];mdh=[];
if opt.showMM
    % plot mean, median. Mean is filled red circle, median is green square
    % I don't know of a very clever way to flip xy and keep everything
    % readable, thus it'll be copy-paste
    switch opt.xyOri
        case 'normal'
            if any(opt.showMM==[1,2])
                mh = plot(ah,opt.xValues+xOffset,m,'+r','Color','r','MarkerSize',12);
            end
            if any(opt.showMM==[1,3])
                mdh = plot(ah,opt.xValues+xOffset,md,'sg','MarkerSize',12);
            end
            if opt.showMM == 4
                mh = plot(ah,opt.xValues+xOffset,m,'+r','Color','r','MarkerSize',12);
                mdh = myErrorbar(ah,opt.xValues+xOffset,m,sem);
            end
            if opt.showMM == 5
                mh = plot(ah,opt.xValues+xOffset,m,'+r','Color','r','MarkerSize',12);
                mdh = myErrorbar(ah,opt.xValues+xOffset,m,sd);
            end
            if opt.showMM == 6
                mdh(1,:) = plot(ah,squeeze(md(:,1,2:3))',repmat(md(:,1,1)',2,1),'color','r','lineWidth',2);%,'lineStyle','--');
                mdh(2,:) = plot(ah,squeeze(md(:,2,2:3))',repmat(md(:,2,1)',2,1),'color','r','lineWidth',1);%,'lineStyle','--');
                mdh(3,:) = plot(ah,squeeze(md(:,3,2:3))',repmat(md(:,3,1)',2,1),'color','r','lineWidth',1);%,'lineStyle','--');
            end
        case 'flipped'
            if any(opt.showMM==[1,2])
                mh = plot(ah,m,opt.xValues+xOffset,'+r','Color','r','MarkerSize',12);
            end
            if any(opt.showMM==[1,3])
                mdh = plot(ah,md,opt.xValues+xOffset,'sg','MarkerSize',12);
            end
            if opt.showMM == 4
                mh = plot(ah,m,opt.xValues+xOffset,'+r','Color','r','MarkerSize',12);
                mdh = myErrorbar(ah,m,opt.xValues+xOffset,[sem,NaN(size(sem))]);
            end
            if opt.showMM == 5
                mh = plot(ah,m,opt.xValues+xOffset,'+r','Color','r','MarkerSize',12);
                mdh = myErrorbar(ah,m,opt.xValues+xOffset,[sd,NaN(size(sd))]);
            end
            if opt.showMM == 6
                mdh(1,:) = plot(ah,repmat(md(:,1,1)',2,1),squeeze(md(:,1,2:3))','color','r','lineWidth',2);%,'lineStyle','--');
                mdh(2,:) = plot(ah,repmat(md(:,2,1)',2,1),squeeze(md(:,2,2:3))','color','r','lineWidth',1);%,'lineStyle','--');
                mdh(3,:) = plot(ah,repmat(md(:,3,1)',2,1),squeeze(md(:,3,2:3))','color','r','lineWidth',1);%,'lineStyle','--');
            end
    end
end

% find extents of x-axis (or y-axis, if flipped)
minX = min(opt.xValues)-stdWidth;
maxX = max(opt.xValues)+stdWidth;

if ~isnan(xAxLim(1))
    % we have previous limits
    switch opt.xyOri
        case 'normal'
            minX = min(minX,xAxLim(1));
            maxX = max(maxX,xAxLim(2));
        case 'flipped'
            minX = min(minX,yAxLim(1));
            maxX = max(maxX,yAxLim(2));
    end
end

% if ~empty, use xNames
switch opt.xyOri
    case 'normal'
        switch opt.xMode
            case 'manual'
                if newAx == false
                    warning('DISTRIBUTIONPLOT:ERASINGLABELS','Plotting into an existing axes and specifying labels will erase previous labels')
                end
                set(ah,'XTick',opt.xValues);
                if ~isempty(opt.xNames)
                    set(ah,'XTickLabel',opt.xNames)
                end
            case 'auto'
                % no need to do anything
        end
        if ~isempty(opt.yLabel)
            ylabel(ah,opt.yLabel);
        end
        % have plot start/end properly
        xlim([minX,maxX])
    case 'flipped'
        switch opt.xMode
            case 'manual'
                if newAx == false
                    warning('DISTRIBUTIONPLOT:ERASINGLABELS','Plotting into an existing axes and specifying labels will erase previous labels')
                end
                set(ah,'YTick',opt.xValues);
                if ~isempty(opt.xNames)
                    set(ah,'YTickLabel',opt.xNames)
                end
            case 'auto'
                % no need to do anything
        end
        if ~isempty(opt.yLabel)
            xlabel(ah,opt.yLabel);
        end
        % have plot start/end properly
        ylim([minX,maxX])
end

%==========================

%==========================
%% CLEANUP & ASSIGN OUTPUT
%==========================

if nargout > 0
    handles{1} = hh;
    handles{2} = [mh;mdh];
    handles{3} = ah;
    handles{4} = sh;
end

set(ah,'NextPlot',holdState);


function mv = mv_exportMap(mv, param, saveFlag, varargin);
%
% mv = mv_exportMap(mv, [param], [saveFlag], [options]);
%
% For Multi-Voxel UI,  export a set of voxel parameter values
% as a map. [more]
%
%
% ras 08/05.
if notDefined('mv')
    mv = get(gcf, 'UserData');
end

if notDefined('param')
    % put up a dialog
    paramList = {'D-prime' 'D-prime-refcond' 'Preferred Condition' ...
                 'Selectivity Index' 'Voxel Reliability'  ...
                 'Mutual Information' 'Omnibus Reliability' ...
                 'Mean Voxel Amplitudes' 'Amplitudes for Each Condition' ...
                 'Contrast'};

    dlg.fieldName = 'param';
    dlg.style = 'popup';
    dlg.string = 'Export which parameter?';
    dlg.list = paramList;
    dlg.value = 1;

    dlg(2).fieldName = 'saveFlag';
    dlg(2).style = 'checkbox';
    dlg(2).string = 'Save Parameter To File';
    dlg(2).value = 1;

    resp = generalDialog(dlg, 'Export to Map...');
    if isempty(resp),  disp('mv_exportMap: User Aborted'); return; end
    param = resp.param;
    saveFlag = resp.saveFlag;
end

% Get the map name,  and values for the map
switch lower(param)
    case 'd-prime',
        mapName = 'd-prime';
        vals = mv_dprime(mv);
    case 'd-prime-refcond',
        mapName = 'd-prime-refcond';
        [vals mapName] = mv_dprime_cond(mv);
    case 'preferred condition',
        mapName = 'Preferred Condition';
        [dprime vals] = mv_dprime(mv);

    case 'voxel reliability',
        if ~isfield(mv, 'voxRSorting'),  mv = mv_sortByVoxR(mv);  end
        mapName = 'Voxel Reliability';
        vals = mv.voxRSorting.metric;

    case 'mutual information',
        if ~isfield(mv, 'wta'),   mv = mv_reliability(mv, 'plotFlag', 0);  end
        mv = mv_mutualInformation(mv, [], 'auto', 1);
        mapName = 'Mutual Information';
        vals = mv.mutualInf.Im;

    case 'omnibus reliability',
        if ~isfield(mv, 'MISorting'),  mv=mv_sortByOmniR(mv); end
        mapName = 'Omnibus Reliability';
        vals = mv.MISorting.metric;

    case 'selectivity index',
        % special function call for high-res / lo-res comparisons
        mv = mv_exportSelectivity(mv, saveFlag);
        return

    case 'mean voxel amplitudes',
        % ask user what sort of amplitudes to export
        ampNames = {'Peak-Baseline Amplitude' 'Beta Weights' ...
                    'Dot-product Amplitudes'};
        n = cellfind({'difference' 'betas' 'relamps'}, mv.params.ampType);
        mapName = ampNames{n};
        vals = mean(mv_amps(mv), 2)';

    case 'amplitudes for each condition',
        % special function call: multiple maps
        mv = mv_exportAmplitudeMap(mv);
        return

    case 'contrast',
        % contrast map values: conditions specified in varargin
        if length(varargin) < 2
            % get from dialog
           prompt = {'Active Conditions' 'Control Conditions' 'Map Name'};
           name = 'Input for Peaks function';
           resp = inputdlg(prompt, mfilename, 1, {'1' '0' ''});
           active = str2num(resp{1});
           control = str2num(resp{2});
		   mapName = resp{3};

        else
            active = varargin{1};
            control = varargin{2};
			if length(varargin) > 2
				mapName = varargin{3};
			end

        end
        if ~isfield(mv, 'glm'), mv = mv_applyGlm(mv); end
        vals = glm_contrast(mv.glm, active, control);

        activeName = [mv.trials.condNames{active+1}];
        ctrlName = [mv.trials.condNames{control+1}];
	   if isempty(mapName)
			mapName = sprintf('%sV%s', activeName, ctrlName);
	   end

	otherwise,
		myErrorDlg(sprintf('Parameter %s is not defined.', param));

end

% initialize a view to get parameters about the
% map volume
fn = sprintf('getSelected%s', mv.roi.viewType);
view = eval(fn);
if isempty(view)
    % no selected view of the proper type -- make a hidden one
    mrGlobals; loadSession; saveFlag = 1;
    fn = sprintf('initHidden%s', roi.viewType); view = eval(fn);
end
mapdims = viewGet(view, 'dataSize');
nScans = viewGet(view, 'numScans');
scan = mv.params.scans(1);

% plug in the values to the map volume:
mapvol = zeros(mapdims);
ind = roiIndices(view,mv.coords);
if ~exist('vals','var')
	fprintf('error: %s map does not exist\n',lower(param));
else
	mapvol(ind) = vals;
end

% plug the map volume into the view
fprintf('Assigning %s data to param map for scan %i\n', mapName, scan);
map = cell(1, nScans);
map{scan} = mapvol;
if ~isequal(view.name, 'hidden')
    view = setParameterMap(view, map, mapName);
    refreshScreen(view);
end

% evaluate this in the workspace,  so the view
% itself is updated
assignin('base', 'map', map);
evalin('base', sprintf('%s=setParameterMap(%s, map, ''%s'');', ...
    view.name, view.name, mapName));

% save if selected
if saveFlag==1
    mapName = sprintf('%s_%s', mapName, mv.roi.name);
    mapPath = fullfile(dataDir(view), mapName);
    if exist(mapPath, 'dir')
        load(mapPath, 'map', 'mapName');
        map{scan} = mapvol;
    end
    save(mapPath, 'map', 'mapName');
    fprintf('Saved map %s.\n', mapPath);
end

% this is a check for mrVista 2: if we're running a mrVista2 session GUI,
% update it to show the new map
global GUI
if ~isempty(GUI)
    sessionGUI_selectDataType;
end

return


% Test file for trigtech/isnan.m

function pass = test_isnan(pref)

% Get preferences.
if ( nargin < 1 )
    pref = trigtech.techPref();
end

testclass = trigtech();

% Test a scalar-valued function.
f = testclass.make(@(x) cos(pi*x), [], pref);
pass(1) = ~isnan(f);

% Test an array-valued function.
f = testclass.make(@(x) [cos(pi*x), cos(pi*x).^2], [], pref);
pass(2) = ~isnan(f);

% Artificially construct and test a NaN-valued function.
f = testclass.make(NaN);
pass(3) = isnan(f);

% Test a NaN scalar-valued function.
try
    f = testclass.make(@(x) cos(pi*x) + NaN);
    pass(4) = isnan(f);
catch ME
    pass(4) = strcmpi(ME.message, 'Cannot handle functions that evaluate to Inf or NaN.');
end

% Test a NaN array-valued function.
try
    f = testclass.make(@(x) [cos(pi*x) + NaN, cos(pi*x)]);
    pass(5) = isnan(f);
catch ME
    pass(5) = strcmpi(ME.message, 'Cannot handle functions that evaluate to Inf or NaN.');
end

end


function img = imresizecrop(img, M, METHOD)
%
% img = imresizecrop(img, M, METHOD);
%
% Output an image of size M(1) x M(2).

if nargin < 3
    METHOD = 'bilinear';
end

if length(M) == 1
    M = [M(1) M(1)];
end

scaling = max([M(1)/size(img,1) M(2)/size(img,2)]);

%scaling = M/min([size(img,1) size(img,2)]);

newsize = round([size(img,1) size(img,2)]*scaling);
img = imresize(img, newsize, METHOD);

[nr nc cc] = size(img);

sr = floor((nr-M(1))/2);
sc = floor((nc-M(2))/2);

img = img(sr+1:sr+M(1), sc+1:sc+M(2),:);


function [fea_train, train_targets, fea_test, test_chunk_id] = features(data, prediction_offset)

time_back = 8;

fea_train = zeros(40000, 3 + 89*time_back);
fea_test = zeros(500, 3 + 89*time_back);

train_targets = zeros(40000, 39);

test_chunk_id = [];

fea_cnt = 0;
test_cnt = 0;
for i=1:size(data,1)-time_back-prediction_offset+1
    if data(i,2)==data(i+time_back+prediction_offset-1,2)
        fea_cnt = fea_cnt + 1;
        fea_train(fea_cnt,1:3) = data(i, 4:6);
        this_fea = data(i:i+time_back-1,7:95);
        fea_train(fea_cnt,4:end) = this_fea(:)';

        train_targets(fea_cnt, :) = data(i+time_back+prediction_offset-1, 95-39+1:95);
    end

    if data(i,2) ~= data(i+1,2)
        test_cnt = test_cnt + 1;
        i_back = i - time_back + 1;
        fea_test(test_cnt,1:3) = data(i_back, 4:6);

        this_fea = data(i_back:i_back+time_back-1,7:95);
        fea_test(test_cnt,4:end) = this_fea(:)';
        test_chunk_id(end+1) = data(i_back,2);
    end
end

test_cnt = test_cnt + 1;
i_back = size(data,1) - time_back + 1;
fea_test(test_cnt,1:3) = data(i_back, 4:6);

this_fea = data(i_back:i_back+time_back-1,7:95);
fea_test(test_cnt,4:end) = this_fea(:)';
test_chunk_id(end+1) = data(i_back,2);

train_targets = train_targets(1:fea_cnt,:);
fea_train = fea_train(1:fea_cnt,:);
fea_test = fea_test(1:test_cnt, :);


function ord = BF_LinkageOrdering(distMat,links)
% BF_LinkageOrdering attempts to use optimalleaforder for dendrogram orderings

% ------------------------------------------------------------------------------
% Copyright (C) 2020, Ben D. Fulcher <ben.d.fulcher@gmail.com>,
% <http://www.benfulcher.com>
%
% If you use this code for your research, please cite the following two papers:
%
% (1) B.D. Fulcher and N.S. Jones, "hctsa: A Computational Framework for Automated
% Time-Series Phenotyping Using Massive Feature Extraction, Cell Systems 5: 527 (2017).
% DOI: 10.1016/j.cels.2017.10.001
%
% (2) B.D. Fulcher, M.A. Little, N.S. Jones, "Highly comparative time-series
% analysis: the empirical structure of time series and their methods",
% J. Roy. Soc. Interface 10(83) 20130048 (2013).
% DOI: 10.1098/rsif.2013.0048
%
% This function 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/>.
% ------------------------------------------------------------------------------

%-------------------------------------------------------------------------------
% Find the size threshold:
if any(size(distMat)==1)
    % A vector of pairwise distances
    numItems = (1+sqrt(1+8*length(distMat)))/2;
else
    % A square matrix of pairwise distances
    numItems = length(distMat);
end

%-------------------------------------------------------------------------------
% Do either optimalleaforder, or normal dendrogram ordering, applying a threhold
% on the number of items for which it is efficient to apply the optimalleaforder
% algorithm. 2000 is the number

if numItems < 2000 % small enough to try optimalleaforder
    try
        ord = optimalleaforder(links,distMat);
        % [~,~,ord] = dendrogram(links,0,'r',ord);
        fprintf(1,'Using optimalleaforder reordering!\n');
    catch
        fprintf(1,'Using dendrogram reordering.\n');
        [~,~,ord] = dendrogram(links,0);
    end
else
    fprintf(1,'Too many objects to reorder using optimalleaforder, using dendrogram instead.\n');
    [~,~,ord] = dendrogram(links,0);
end

end


function Z=conv(X,Y)
%CONV (overloaded)

if isnumeric(X)
    temp = X;
    X = Y;
    Y = temp;
end
if isa(X,'sdpvar') & isa(Y,'sdpvar')
    error('nonlinear CONV not supported yet. Make feature request');
end
x_lmi_variables = X.lmi_variables;
n = X.dim(1);
m = X.dim(2);
Z=X;
ii = [];
jj = [];
ss = [];
for i = 1:length(x_lmi_variables)+1
    x=reshape(X.basis(:,i),n,m);
    z=conv(full(x),full(Y));
    [iiz,jjz,ssz] = find(z(:));
    ii = [ii iiz(:)'];
    jj = [jj jjz(:)'+i-1];
    ss = [ss ssz(:)'];
end
Z.basis = sparse(ii,jj,ss);
Z.dim(1) = size(z,1);
Z.dim(2) = size(z,2);
Z = clean(Z);
% Reset info about conic terms
if isa(Z,'sdpvar')
    Z.conicinfo = [0 0];
end


function ehmm = updateAlpha_ehmm(ehmm,rangeK)
if nargin < 2 || isempty(rangeK), rangeK = 1:ehmm.K; end % K+1
ehmm = updateAlpha(ehmm,rangeK);
% setstateoptions;
% for k = 1:ehmm.K
%     for n = 1:ehmm.train.ndim
%         if ~regressed(n), continue; end
%         indr = S(n,:)==1;
%         for i = 1:length(orders)
%             index = (i-1)*ehmm.train.ndim + n + ~train.zeromean;
%             if ehmm.train.ndim > 1
%                 sigmaprior = (ehmm.state(k).sigma.Gam_shape(n,indr) ./ ...
%                     ehmm.state(k).sigma.Gam_rate(n,indr));
%             else
%                 sigmaprior = zeros(1,sum(indr));
%             end
%             ehmm.state(k).alpha.Gam_rate(i) = ehmm.state(k).alpha.Gam_rate(i) + ...
%                 0.5 * ( (ehmm.state(k).W.Mu_W(index,indr) .* ...
%                 sigmaprior ) * ...
%                 ehmm.state(k).W.Mu_W(index,indr)' + sum( ...
%                 sigmaprior .* ...
%                 ehmm.state(k).W.S_W(indr,index,index)'));
%         end
%         ehmm.state(k).alpha.Gam_shape = ehmm.state(k).alpha.Gam_shape + 0.5 * sum(indr);
%     end
% end
end


function [tColor] = hyperTruecolor(rflFile, height, width, bands, rgbBands, strechtype)
% HYPERTRUECOLOR Returns a truecolor composite image, given a reflectance file
%   hyperTruecolor returns a truecolor composite image, given an AVIRIS reflectance file,
%  with an optional enhancement of the image using one of two contrast streches.
%
% Usage
%   [tColor] = hyperTruecolor(rflFile, height, width, bands)
%   [tColor] = hyperTruecolor(rflFile, height, width, bands, rgbBands)
%   [tColor] = hyperTruecolor(rflFile, height, width, bands, rgbBands, strechtype)
% Inputs
%   rflFile    - Path and filename of AVIRIS reflectance measurements
%   height     - Height in pixels
%   width      - Width in pixels
%   bands      - Number of bands
%   rgbBands   - [Red, green, blue] bands (1x3 vector)
%                [31,20,12] is the default if no argument is given
%   strechtype - truecolor enhancement contrast strech (string):
%                'none'         : default if no argument is given
%                'stretchlim'   : linear contrast stretch
%                'decorrstretch': decorrelation stretch
%                   (enhanced color separation across highly correlated channels)
% Outputs
%   tColor - Truecolor composite image

if nargin < 4 || nargin > 6
    help hyperTruecolor
    error('Incorrect usage, see function description above.')
end

if nargin == 4
    % RGB bands: [665.73, 557.07, 478.17] nm
    rgbBands   = [31,20,12];
    strechtype = 'none';
elseif nargin == 5
    strechtype = 'none';
end

h = height;
w = width;
p = bands;

% Read the 3 RGB bands
tColor = multibandread(rflFile, [h w p], 'int16', 0, 'bip', 'ieee-be', ...
            {'Row', 'Range', [1 h]}, {'Column', 'Range', [1 w]}, ...
            {'Band', 'Direct', rgbBands} );

% Normalize to proper reflectance units.
tColor = tColor ./ 1e4;

switch lower(strechtype)
    case 'none'
        return
    case 'stretchlim'
        % Apply a linear contrast stretch to the truecolor composite image
        tColor = imadjust(tColor, stretchlim(tColor));
    case 'decorrstretch'
        % Apply a decorrelation stretch to the truecolor composite image
        tColor = decorrstretch(tColor, 'Tol', 0.01);
    otherwise
        help hyperTruecolor
        error(sprintf('Unknown method %s. See function description above.', strechtype))
end


function test_bug950

% MEM 2gb
% WALLTIME 00:10:00
% DEPENDENCY ft_megrealign test_bug950

% the issue explored here is a reputed crash in megrealign due to a problem
% in the channelposition function.
% in addition balancing is not properly taken into account when creating
% the headmodel for the inverse/forward steps

% load in some data
load(dccnpath('/home/common/matlab/fieldtrip/data/test/latest/raw/meg/preproc_ctf151.mat'));

cfg = [];
cfg.gradient = 'G3BR';
data = ft_denoise_synthetic(cfg, data);

template = data.grad;
template.chanpos(:,3) = template.chanpos(:,3)+1;
template.coilpos(:,3) = template.coilpos(:,3)+1;

cfg = [];
cfg.template{1}    = template;
cfg.inwardshift    = 1;
cfg.headmodel.o    = [0 0 4];
cfg.headmodel.r    = 8;
cfg.headmodel.unit = 'cm';
data2 = ft_megrealign(cfg, data);


% This script takes in a floating point complex IQ recording containing DroneID bursts and demodulates each burst
% The steps are:
%   - Find all bursts in the file using the first ZC sequence
%   - Low pass filter each burst
%   - Adjust for frequency offset based on the offset found using the first OFDM symbol's cyclic prefix
%   - Extract each OFDM symbol
%   - Quantize/Demodulate all data carriers
%   - Validate that the first symbol XOR's to all zeros
%   - Pass XOR'd bits from all other data symbols to a C++ program that removes the LTE and rate matching
%   - Print out each frame in hex

%% Path Info
if (is_octave)
  this_script_path = fileparts(mfilename('fullpath'));
else
  this_script_path = fileparts(matlab.desktop.editor.getActiveFilename);
end

% Create a directory to store the constellation plots for debugging
% THIS CAN BE COMMENTED OUT IF NEEDED!!!  JUST MAKE SURE TO COMMENT OUT THE `saveas` CALL LATER AS WELL
mkdir(fullfile(this_script_path, "images"));

turbo_decoder_path = fullfile(this_script_path, filesep, '..', filesep, '..', filesep, 'cpp', filesep, 'remove_turbo');
if (~ isfile(turbo_decoder_path))
    error("Could not find Turbo decoder application at '%s'.  Check that the program has been compiled",...
        turbo_decoder_path);
end

%% File Parameters
enable_plots = true;        % Set to false to prevent the plots from popping up
correlation_threshold = 0.7; % The SNR is pretty good, so using a high correlation score (must be between 0.0 and 1.0)
chunk_size = 10e6;           % Number of samples to process at a time
enable_equalizer = true;     % Enable/disable the frequency domain equalizer

%% Paramters that the user must change
sample_type = 'single';
file_path = 'YOUR_FILE_NAME_HERE';
file_sample_rate = YOUR_SAMPLE_RATE_HERE;
file_freq_offset = 0e6;

%% Low Pass Filter Setup
signal_bandwidth = 10e6; % The actual occupied bandwidth of the DroneID signal
filter_tap_count = 50;   % Number of filter taps to use for the low pass filter
filter_taps = fir1(filter_tap_count, signal_bandwidth/file_sample_rate); % Create the low pass filter taps

%% Burst Extraction
[long_cp_len, short_cp_len] = get_cyclic_prefix_lengths(file_sample_rate);
cyclic_prefix_schedule = [
    long_cp_len, ...
    short_cp_len, ...
    short_cp_len, ...
    short_cp_len, ...
    short_cp_len, ...
    short_cp_len, ...
    short_cp_len, ...
    short_cp_len, ...
    long_cp_len];
fft_size = get_fft_size(file_sample_rate);

% A correlation figure number of -1 will prevent plotting by the find_zc_indices_by_file function
correlation_fig_number = -1;
if (enable_plots)
    correlation_fig_number = 456;
end

% Making sure that the bursts that are extracted have enough padding for the low pass filter to start up and terminate
bursts = extract_bursts_from_file(file_path, file_sample_rate, file_freq_offset, correlation_threshold, chunk_size,...
    filter_tap_count, 'SampleType', sample_type, 'CorrelationFigNum', correlation_fig_number);

assert(~isempty(bursts), "Did not find any bursts");

frames = {};

% Get a list of the indices from the shifted FFT outputs that contain data carriers
data_carrier_indices = get_data_carrier_indices(file_sample_rate);

% Initial value for the second LFSR in the scrambler
scrambler_x2_init = fliplr([0 0 1, 0 0 1 0, 0 0 1 1, 0 1 0 0, 0 1 0 1, 0 1 1 0, 0 1 1 1, 1 0 0 0]);

% This determines which OFDM symbol's cyclic prefix is used to determine the coarse frequency offset.  Some drones use 9
% OFDM symbols, and some use 8.  It seems that those drones that use 8 OFDM symbols have a short cyclic prefix in the
% first symbol.  Skipping the first symbol for those drones that have 9 OFDM symbols results in the new "first" symbol
% having a short cyclic prefix as well.  So, since the burst extractor always assumes that there are 9 symbols, the
% first symbol is skipped for the purposes of coarse CFO.  The second symbol is assumed to have a short cyclic prefix
cfo_estimation_symbol_idx = 2;

%% Burst Processing
for burst_idx=1:size(bursts, 1)
    % Get the next burst
    burst = bursts(burst_idx,:);

    if (enable_plots)
        figure(43);
        subplot(2, 1, 1);
        plot(10 * log10(abs(burst).^2));
        title('Time domain abs^2 10log10 (original)');

        % Plot the FFT, but average it with a single pole IIR filter to make it smoother
        figure(1000);
        fft_bins = 10 * log10(abs(fftshift(fft(burst))).^2);
        running = fft_bins(1);
        beta = 0.06;
        for idx = 2:length(fft_bins)
            running = (running * (1 - beta)) + (fft_bins(idx) * beta);
            fft_bins(idx) = running;
        end
        x_axis = file_sample_rate / 2 * linspace(-1, 1, length(burst));
        plot(x_axis, fft_bins);
        title('Frequency Spectrum (averaged)');
        grid on;
    end

    %% Find Integer Frequency Offset

    % Exploiting the fact that during the first ZC sequence the DC carrier will be much lower in amplitude than the
    % surrounding samples.  Steps:
    %   1. Extract just the time domain samples used in the first ZC sequence
    %   2. Interpolate those time domain samples to increase the frequency resolution of the measurement
    %   3. Get the power spectrum (abs squared of the FFT)
    %   4. Look N elements around the center of the FFT for the lowest point (this is the center of the signal)
    %   5. Calculate how far off from 0 Hz the lowest bin was, and frequency shift the upsampled signal by that value
    %   6. Decimate the samples back to the original sample rate for further processing

    % Calculate the first sample index for the first ZC sequence (skipping the cyclic prefix)
    offset = sum(cyclic_prefix_schedule(1:4)) + (fft_size * 3) + filter_tap_count;

    % Upsample (interpolate and filter) the ZC sequence samples
    interp_rate = 10;
    burst = resample(burst, interp_rate, 1);

    % Extract out just the samples for the first ZC sequence
    zc_samples = burst((offset * interp_rate):(offset * interp_rate) + (fft_size * interp_rate) - 1);

    % Convert the time domain ZC sequence samples to the frequency domain
    fft_bins = 10 * log10(abs(fftshift(fft(zc_samples))).^2);

    % Loop for the lowest bin in the middle of the frequency domain spectrum
    bin_count = 15; % How far left and right to look for the lowest carrier

    % Set all of the FFT bins on the outside to infinity so they can't possibly be the minimum value
    fft_bins(1:(fft_size * interp_rate / 2) - bin_count) = Inf;
    fft_bins((fft_size * interp_rate / 2) + bin_count - 1:end) = Inf;

    % Find the index of the FFT bin with the lowest amplitude
    [~, center_offset] = min(fft_bins);

    % Calculate the frequency needed to correct the integer offset, then conver that to radians
    integer_offset = ((fft_size * interp_rate / 2) - center_offset + 1) * 15e3;
    radians = 2 * pi * integer_offset / (file_sample_rate * interp_rate);

    % Apply a frequency adjustment
    burst = burst .* exp(1j * radians * [0:length(burst) - 1]);

    % Downsample (filter and decimate) the burst samples
    burst = resample(burst, 1, interp_rate);

    %% Apply low pass filter
    burst = filter(filter_taps, 1, burst);

    if (enable_plots)
        figure(43);
        subplot(2, 1, 2);
        plot(10 * log10(abs(burst).^2));
        title('Time domain abs^2 10log10 (filtered)')
    end

    %% Interpolate and find the true starting sample offset
    interp_factor = 1;
    burst = resample(burst, interp_factor, 1);
    true_start_index = find_sto_cp(burst, file_sample_rate * interp_factor);
    burst = resample(burst(true_start_index:end), 1, interp_factor);

    % Plot cyclic prefixes overlayed with the replica from the end of the OFDM symbol
    if (enable_plots)
        offset = 1;
        figure(7777);
        for cp_idx=1:length(cyclic_prefix_schedule)
            subplot(3, 3, cp_idx);
            symbol = burst(offset:offset + cyclic_prefix_schedule(cp_idx) + fft_size - 1);
            left = symbol(1:cyclic_prefix_schedule(cp_idx));
            right = symbol(end - cyclic_prefix_schedule(cp_idx) + 1:end);
            plot(abs(left));
            hold on
            plot(abs(right));
            hold off;
            title(['Cyclic Prefix Overlay ', mat2str(cp_idx)]);

            offset = offset + length(symbol);
        end
    end

    %% Coarse frequency offset adjustment using one of the OFDM symbols (see coarse_cfo_symbol_sample_offset definition)

    % Get the expected starting index of the symbol to be used for CFO estimation
    zc_start = long_cp_len + (fft_size * 3) + (short_cp_len * 3);

    % Extract out the full OFDM symbol (cyclic prefix included)
    cfo_est_symbol = burst(zc_start - short_cp_len:zc_start + fft_size - 1);

    % Get the cyclic prefix, and then the copy of the cyclic prefix that exists at the end of the OFDM symbol
    cyclic_prefix = cfo_est_symbol(1:short_cp_len);
    symbol_tail = cfo_est_symbol(end - short_cp_len + 1:end);

    skip = 0;
    cyclic_prefix = cyclic_prefix(skip+1:end-skip);
    symbol_tail = symbol_tail(skip+1:end-skip);

    % Calculate the frequency offset by taking the dot product of the two copies of the cyclic prefix and dividing out
    % the number of samples in between each cyclic prefix sample (the FFT size)
    offset_radians = angle(dot(cyclic_prefix, symbol_tail)) / fft_size;
    offset_hz = offset_radians * file_sample_rate / (2 * pi);

    if (enable_plots)
        figure(999);
        plot(abs(cyclic_prefix).^2);
        hold on;
        plot(abs(symbol_tail).^2, '*-', 'Color', 'red');
        hold off;
        title('Cyclic Prefix Overlay - CFO Estimate')
    end

    % Apply the inverse of the estimated frequency offset back to the signal
    burst = burst .* exp(1j * -offset_radians * [1:length(burst)]);

    %% OFDM Symbol Processing

    % Extract the individual OFDM symbols without the cyclic prefix for both time and frequency domains
    [time_domain_symbols, freq_domain_symbols] = extract_ofdm_symbol_samples(burst, file_sample_rate);

    % Calculate the channel for both of the ZC sequnces
    channel1 = calculate_channel(freq_domain_symbols(4,:), file_sample_rate, 4);
    channel2 = calculate_channel(freq_domain_symbols(6,:), file_sample_rate, 6);

    % Only select the data carriers from each channel estimate
    channel1 = channel1(data_carrier_indices);
    channel2 = channel2(data_carrier_indices);

    % Calculate the average phase offset of each channel estimate
    channel1_phase = sum(angle(channel1)) / length(data_carrier_indices);
    channel2_phase = sum(angle(channel2)) / length(data_carrier_indices);

    % This doesn't seem right, but taking the difference of the two channels and dividing by two yields the average
    % walking phase offset between the two.  That value can be used to correct for the phase offsets caused by not being
    % exactly spot on with the true first sample
    channel_phase_adj = (channel1_phase - channel2_phase) / 2;

    if (enable_plots)
        figure(441);
        subplot(2, 1, 1);
        plot(abs(channel1).^2, '-');
        title('ZC Sequence 1 Channel')
        subplot(2, 1, 2);
        plot(abs(channel2).^2, '-');
        title('ZC Sequence 2 Channel')
    end

    % Only use the fisrt ZC sequence to do the initial equaliztion.  Trying to use the average of both ends up with
    % strange outliers in the constellation plot
    channel = channel1;

    % Place to store the demodulated bits
    bits = zeros(9, 1200);

    % Walk through each OFDM symbol and extract the data carriers and demodulate the QPSK inside
    % This is done for symbols 4 and 6 even though they contain ZC sequences.  It's just to keep the logic clean

    for idx=1:size(bits, 1)
        data_carriers = freq_domain_symbols(idx,data_carrier_indices);

        if (enable_equalizer)
            % Equalize just the data carriers
            data_carriers = data_carriers .* channel;
        end

        % Demodulate/quantize the QPSK to bits
        bits(idx,:) = quantize_qpsk(data_carriers);

        if (enable_plots)
            figure(1);
            subplot(3, 3, idx);
            plot(data_carriers, 'o');
            title(['Symbol ', mat2str(idx), ' IQ']);

            figure(111);
            subplot(3, 3, idx);
            plot(10 * log10(abs(time_domain_symbols(idx,:)).^2), '-');
            title(['Symbol ', mat2str(idx), ' Time Domain']);

            figure(112);
            subplot(3, 3, idx);
            plot(10 * log10(abs(freq_domain_symbols(idx,:)).^2));
            title(['Symbol ', mat2str(idx), ' Freq Domain']);
        end
    end

    if (enable_plots)
        % Save the constellation plots to disk for debugging
        % THIS CAN BE COMMENTED OUT IF NEEDED
        png_path = sprintf('%s/images/ofdm_symbol_%d.png', this_script_path, burst_idx);

        try
            saveas(gcf, png_path);
        catch
            error('Could not write out PNG file to "%s"', png_path);
        end
    end

    % The remaining bits are descrambled using the same initial value, but more bits
    second_scrambler = generate_scrambler_seq(7200, scrambler_x2_init);

    % Only descramble the remaining data symbols (ignoring the ZC sequences in 4 and 6, and the first data symbol)
    bits = bits([2,3,5,7,8,9],:);

    % Just converting the bits matrix into a vector to make XOR'ing easier
    bits = reshape(bits.', 1, []);

    % Run the actual XOR
    bits = bitxor(bits, second_scrambler);

    % Write the descrambled bits to disk as 8-bit integers
    handle = fopen("/tmp/bits", "wb");
    fwrite(handle, bits, 'int8');
    fclose(handle);

    % Run the Turbo decoder and rate matcher
    [retcode, out] = system(sprintf("%s %s", turbo_decoder_path, "/tmp/bits"));
    if (retcode ~= 0)
        warning("Failed to run the final processing step");
    end

    % Save off the hex values for the frame
    frames{burst_idx} = out;
end

% Print out all frames in hex
for idx=1:size(bursts, 1)
    frame = frames{idx};

    fprintf('FRAME: %s', frame);
end


% The COBRAToolbox: testmultiProductionEnvelope.m
%
% Purpose:
%     - test the multiProductionEnvelope function
%
% Authors:
%     - Jacek Wachowiak
global CBTDIR
% save the current path
currentDir = pwd;

% initialize the test
fileDir = fileparts(which('testMultiProductionEnvelope'));
cd(fileDir);

% test variables
model = getDistributedModel('ecoli_core_model.mat');
model.lb(36) = 0; % setting the model to anaerobic conditions
model.ub(36) = 0; % setting the model to anaerobic conditions
biomassRxn = model.rxns(13);
target = model.rxns(13); % objective = biomass reaction
deletions = model.rxns(21);
deletions2 = model.genes(21);
targetreactionsBasic = {'EX_ac(e)','EX_for(e)','EX_ethoh(e)'};

%reference data
refData_x=(0:(0.2117/19):0.2117)';
refData_lowerbound = zeros(20, 1); % EX_ac(e)
refData_lowerbound(18) = 0.0736;
refData_lowerbound(19) = 3.9862;
refData_lowerbound(20) = 8.5036;
 refData_upperBound = (10:(-1.4964/19):8.5036)';

% function outputs
% each column of targetValues corresponds to one reaction results, while biomassValues is the x axis
% for this test: 1	EX_ac(e), 2	EX_acald(e), 3	EX_akg(e), 4	EX_etoh(e), 5	EX_for(e), after that the results do not correspond to the graph,
% additionally there are always 2 curves, the data corresponds to the lower one
[biomassValues, targetLowerBounds, targetUpperBounds, targetReactions] = multiProductionEnvelope(model);
[biomassValues2, targetLowerBounds2, targetUpperBounds2, targetReactions2] = multiProductionEnvelope(model, deletions, biomassRxn);
%gene not reaction removal
[biomassValues3, targetLowerBounds3, targetUpperBounds3,  targetReactions3] = multiProductionEnvelope(model, deletions2, biomassRxn, 1, 20);

% tests - not all results are possible to be tested, the suitable were chosen
% x axis comparison
assert(isequal((abs(refData_x-biomassValues) < 1e-4), true(20, 1)));
assert(isequal((abs(refData_lowerbound-targetLowerBounds(:,ismember(targetReactions,'EX_ac(e)'))) < 1e-4), true(20, 1)));
assert(isequal((abs(refData_upperBound-targetUpperBounds(:, ismember(targetReactions,'EX_ac(e)'))) < 1e-4), true(20, 1)));

close all hidden force

% change to old directory
cd(currentDir);


function [ t_stop, y_stop ] = p17_stop ( neqn )

%*****************************************************************************80
%
%% P17_STOP returns the stopping point for problem p17.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    23 February 2013
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer NEQN, the number of equations.
%
%    Output, real T_STOP, Y_STOP(NEQN), the final data.
%
  y_stop = zeros ( neqn, 1 );

  t_stop = 20.0;
  y_stop = [ ...
   -1.777027357140412e-01; ...
    9.467784719905892e-01; ...
   -1.030294163192969; ...
    1.211074890053952e-01 ];

  return
end


function kern = ggwhiteKernExpandParam(kern, params)

% GGWHITEKERNEXPANDPARAM Create kernel structure from GG white kernel's parameters.
% FORMAT
% DESC returns a gaussian gaussian white
%	kernel structure filled with the parameters in the given vector.
%	This is used as a helper function to enable parameters to be
%	optimised in, for example, the NETLAB optimisation functions.
% RETURN kern : kernel structure with the given parameters in the relevant
%	   locations.
% ARG kern : the kernel structure in which the parameters are to be
%	   placed.
% ARG param : vector of parameters which are to be placed in the kernel
%	   structure.
%
% SEEALSO : ggwhiteKernParamINit, ggwhiteKernExtractParam, kernExpandParam
%
% COPYRIGHT : Mauricio A. Alvarez and Neil D. Lawrence, 2008
%
% MODIFICATIONS : Mauricio A Alvarez, 2009

% KERN

kern.precisionG = params(1:end-2)';
kern.sigma2Noise = params(end-1);
kern.variance = params(end);


function obj = addParamVariable(obj, bounds)
    % Adds parameters as the NLP decision variables to the problem
    %
    % Parameters:
    %  bounds: a structed data stores the boundary information of the
    %  NLP variables @type struct

    % get basic information of the variables
    params = obj.Plant.Params;

    param_names = fieldnames(params);

    for j=1:length(param_names)

        p_name = param_names{j};

        var = struct();
        var.Name = p_name;
        siz = size(params.(p_name));
        var.Dimension = prod(siz); %#ok<PSIZE>
        if isfield(bounds,p_name)
            if isfield(bounds.(p_name),'lb')
                var.lb = bounds.(p_name).lb;
            end
            if isfield(bounds.(p_name),'ub')
                var.ub = bounds.(p_name).ub;
            end
            if isfield(bounds.(p_name),'x0')
                var.x0 = bounds.(p_name).x0;
            end
        end
        % determines the nodes at which the variables to be defined.
        if obj.Options.DistributeParameters
            % time variables are defined at all nodes if distributes the weightes
            obj = addVariable(obj, p_name, 'all', var);

            % add an equality constraint between the time variable at
            % neighboring nodes to make sure they are same
            p   = flatten(params.(p_name));
            siz = size(p);
            pn  = SymVariable([p_name 'n'],siz);
            p_cont = SymFunction([p_name 'Cont_' obj.Plant.Name],transpose(p-pn),{p,pn});
            % create an array of constraints structure
            p_cstr = struct();
            p_cstr(obj.NumNode-1) = struct();
            [p_cstr.Name] = deal(p_cont.Name);
            [p_cstr.Dimension] = deal(var.Dimension);
            [p_cstr.lb] = deal(0);
            [p_cstr.ub] = deal(0);
            [p_cstr.Type] = deal('Linear');
            [p_cstr.SymFun] = deal(p_cont);
            for i=1:obj.NumNode-1
                p_cstr(i).DepVariables = [obj.OptVarTable.(p_name)(i);obj.OptVarTable.(p_name)(i+1)];
            end

            % add to the NLP constraints table
            obj = addConstraint(obj,[p_name 'Cont'],'except-last',p_cstr);

        else
            % otherwise only define at the first node
            obj = addVariable(obj, p_name, 'first', var);
        end

    end

end


function rx = rxSetXform(rx, xform, aboutCenter)
%
% rx = rxSetXform(rx, xform, [aboutCenter]);
%
% Sets the xform field of a mrRx struct,
% and also sets UI controls of the GUI
% to agree with this transform.
%
% The aboutCenter flag is an optional flag
% specifying whether the rotations in the
% new xform are intended to rotate about the
% center of prescription or not. (E.g., mrVista
% alignments rotate about the corner, b/c the
% math is more straightforward). If 0 [default],
% this means that further adjustment is needed to
% ensure the rotations act about the center, so the
% xform is adjusted to compensate (the mapping of
% points is the same though).
%
% ras 03/05.
if notDefined('rx')
    cfig = findobj('Tag','rxControlFig');
    rx = get(cfig,'UserData');
end

if notDefined('aboutCenter')
    aboutCenter = 0;
end

if aboutCenter==0
    % we center the rx at 0,0,0 to rotate about the
	% center -- this compensatory translation ensures
	% that zero settings return an unchanged matrix.
	% Because of this, the UI settings reflect a diff't
	% set of rotations from that in the mrVista alignment.
	% Compute the modified xform (after a similar set of
	% changes in rxRefresh, it will return the original
	% mrVista xform matrix):
	shift = [eye(3) -rx.rxDims([2 1 3])'./2; 0 0 0 1];
	xform = shift*xform/shift;
end

% set prev xform field
rx.prevXform = rx.xform;

% set xform field
rx.xform = xform;

% set ui controls to agree w/ the xform
[trans rot flip] = affineDecompose(rx.xform);
rot = rad2deg(rot+pi) - 180;
if ishandle(rx.ui.controlFig)
    rxSetSlider(rx.ui.corTrans, trans(1));
    rxSetSlider(rx.ui.axiTrans, trans(2));
    rxSetSlider(rx.ui.sagTrans, trans(3));
    rxSetSlider(rx.ui.corRot, rot(1));
    rxSetSlider(rx.ui.axiRot, rot(2));
    rxSetSlider(rx.ui.sagRot, rot(3));
    set(rx.ui.corFlip, 'Value', (flip(1)<0));
    set(rx.ui.axiFlip, 'Value', (flip(2)<0));
    set(rx.ui.sagFlip, 'Value', (flip(3)<0));

    % set the control fig w/ the new rx:
    set(rx.ui.controlFig, 'UserData', rx);
    rxRefresh(rx, 0);
end

return


function idx= cspselect_equalPerClass(score, ~, nComponents)
%CSPSELECT_EQUALPERCLASS - Select equally many CSP components for each class
%
%Synopsis:
% CI = select_fixedNumber(SCORE, ~, NCOMP)
%
%Arguments:
% SCORE  - score of components
% NCOMPS - number of components (total number depends on MODE)
%
%Returns:
% CI     - index of components
%
%See also processing/proc_csp

idx= [1:nComponents, length(score)-nComponents+1:length(score)]';


function out= bbci_calibrate_evalFeature(signal, bbci_feature, mrk)
%BBCI_CALIBRATE_EVALFEATURE - Perform feature extration
%
%Synopsis:
%  FV= bbci_calibrate_evalFeature(EPO, BBCI_FEATURE)
%  FV= bbci_calibrate_evalFeature(CNT, BBCI_FEATURE, MRK)
%
%Arguments:
%  EPO - Structure of epoched data.
%  BBCI_FEATURE - Struct (array) specifying the feature extraction,
%      subfield of 'bbci' structure of bbci_apply.
%  CNT - Structure of continuous data.
%  MRK - Structure of markers.
%
%Output:
%  FV - Structure holding the extracted feature vector

% 11-2011 Benjamin Blankertz

bbci_feature= bbciutil_transformProc2FcnParam(bbci_feature);
bbci_feature= opt_setDefaults(bbci_feature, {'signal', 1});

feature= repmat(struct, [1 length(bbci_feature)]);
for f= 1:length(bbci_feature),
  BF= bbci_feature(f);
  fv= signal(BF.signal);
  if nargin>=3,
    fv= proc_segmentation(fv, mrk, BF.ival);
  end

  if f==1 && isfield(fv, 'y'),
    out.y= fv.y;
  end

  for k= 1:length(BF.fcn),
    fv= BF.fcn{k}(fv, BF.param{k}{:});
  end
  % clash all feature dimensions (but preserve sample dimension)
  sz= size(fv.x);
  feature(f).x= reshape(fv.x, [prod(sz(1:end-1)) sz(end)]);
end
out.x= cat(1, feature.x);


function [g1, g2, g3] = sdlfmaXsdlfmaKernGradientBlockIGJ(lfmKern1, lfmKern2, ...
    t1, t2,  i, j, generalConst, generalConstGrad, covGrad, g1Mean, ...
    g2Mean, gsp1Mean, gsp2Mean, typeMeanParam, typeMeanSwitching, ...
    typeKernParam1, typeKernParam2, typeKernSwitching1, typeKernSwitching2)

% SDLFMAXSDLFMAKERNGRADIENTBLOCKIGJ
% FORMAT
% DESC computes the gradients of the parameters for system 1 and system 2
% when i is greater than j. It assummes both systems represent
% accelerations.
% ARG lfmKern1 : structure containing parameters for the system 1
% ARG lfmKern2 : structure containing parameters for the system 2
% ARG t1 : times at which the system 1 is evaluated
% ARG t2 : times at which the system 2 is evaluated
% ARG covGrad : partial derivatives of the objective function wrt portion
% of the corresponding kernel matrix
% ARG g1Mean : gradients of the parameter of the system 1 obtained from the
% part of the function that uses the funcitons accommpanying the initial
% conditions.
% ARG g2Mean : gradients of the parameter of the system 2 obtained from the
% part of the function that uses the funcitons accommpanying the initial
% conditions.
% ARG gsp1Mean : gradient of the switching point of the system 1 obtained
% from the part of the function that uses the funcitons accommpanying the
% initial conditions.
% ARG gsp2Mean : gradient of the switching point of the system 2 obtained
% from the part of the function that uses the funcitons accommpanying the
% initial conditions.
% ARG typeMeanParam : specify the mean functions used to compute this part
% of the kernel
% ARG typeMeanSwitching : specify the functions used to compute the
% gradients of the swicthing points in part thst uses mean functions
% ARG typeKernParam1 : specify the first kernel function used to compute
% this part of the kernel
% ARG typeKernParam2 : specify the second kernel function used to compute
% this part of the kernel
% ARG typeKernSwitching1 : specify the functions used to compute the
% gradients of the swicthing points in both sides of the kernel function 1
% ARG typeKernSwitching2 : specify the functions used to compute the
% gradients of the swicthing points in both sides of the kernel function 2
% RETURN g1 : gradients of parameters for the system 1
% RETURN g2 : gradients of parameters for the system 2
% RETURN g3 : gradients of switching points
%
% COPYRIGHT : Mauricio A. Alvarez, 2010.

% KERN

if nargin < 14
    typeMeanParam = 'sdlfm';
    typeMeanSwitching = 'sdlfmv';
    typeKernParam1 = 'lfmXlfm';
    typeKernParam2 = 'lfmvXlfm';
    typeKernSwitching1= {'lfmvXlfm', 'lfmvXlfm'};
    typeKernSwitching2 ={'lfmaXlfm', 'lfmvXlfmv'};
end

g3 = [];

fhandleMeanParam = str2func([typeMeanParam 'MeanCompute']);
fhandleMeanGradParam = str2func([typeMeanParam 'MeanGradient']);
fhandleMeanGradSwitching = str2func([typeMeanSwitching 'MeanCompute']);
fhandleKernPosPos = str2func([typeKernParam1 'KernCompute']);
fhandleKernGradPosPos = str2func([typeKernParam1 'KernGradient']);
fhandleKernGradSwitchingPosPos1 = str2func([typeKernSwitching1{1} 'KernCompute']);
fhandleKernGradSwitchingPosPos2 = str2func([typeKernSwitching1{2} 'KernCompute']);
fhandleKernVelPos = str2func([typeKernParam2 'KernCompute']);
fhandleKernGradVelPos = str2func([typeKernParam2 'KernGradient']);
fhandleKernGradSwitchingVelPos1 = str2func([typeKernSwitching2{1} 'KernCompute']);
fhandleKernGradSwitchingVelPos2 = str2func([typeKernSwitching2{2} 'KernCompute']);

c1 = fhandleMeanParam(lfmKern1(1), t1, 'Pos');
e1 = fhandleMeanParam(lfmKern1(1), t1, 'Vel');

if isempty(generalConst{i,j})
    coeffPosPos = c1;
    coeffVelPos = e1;
else
    coeffPosPos = generalConst{i,j}(1,1)*c1 + generalConst{i,j}(2,1)*e1;
    coeffVelPos = generalConst{i,j}(1,2)*c1 + generalConst{i,j}(2,2)*e1;
end
g1Kern = zeros(length(lfmKern1), 5);
g2Kern = zeros(length(lfmKern1), 5);
PosPos = zeros(1,length(t2));
VelPos = zeros(1,length(t2));

for k=1:length(lfmKern1)
    PosPos = PosPos + fhandleKernPosPos(lfmKern2(k), lfmKern1(k), t2, ...
        lfmKern2(k).limit).';
    [g2KLocal, g1KLocal] = fhandleKernGradPosPos(lfmKern2(k), lfmKern1(k), ...
        t2, lfmKern2(k).limit, covGrad.', coeffPosPos.');
    g1Kern(k,:) = g1Kern(k, :) + g1KLocal;
    g2Kern(k,:) = g2Kern(k, :) + g2KLocal;
    VelPos = VelPos + fhandleKernVelPos(lfmKern2(k), lfmKern1(k), ...
        t2, lfmKern2(k).limit).';
    [g2KLocal, g1KLocal] = fhandleKernGradVelPos(lfmKern2(k), lfmKern1(k), ...
        t2, lfmKern2(k).limit, covGrad.', coeffVelPos.');
    g1Kern(k,:) = g1Kern(k, :) + g1KLocal;
    g2Kern(k,:) = g2Kern(k, :) + g2KLocal;
end
[gcAlpha, gcOmega] = fhandleMeanGradParam(lfmKern1(1), t1, 'Pos');
[geAlpha, geOmega] = fhandleMeanGradParam(lfmKern1(1), t1, 'Vel');
[gradAlpha, gradOmega] = getLocalGradAlphaOmega(lfmKern1);
g1Pos = fhandleMeanGradSwitching(lfmKern1(1), t1, 'Pos');
h1Vel = fhandleMeanGradSwitching(lfmKern1(1), t1, 'Vel');
gsp1Kern = zeros(1, length(lfmKern1));
gsp2Kern = zeros(1, length(lfmKern1));
% This means that are only two switching points involved, t1
% and t0 appear in k, like k_{ff}(t1-t0,t'-t0) and
% k_{mf}(t1-t0,t'-t0). The other points appear in c1(t - t1)
% and e1(t - t1)
for k=1:length(lfmKern1)
    temp = fhandleKernGradSwitchingPosPos1(lfmKern2(k), lfmKern1(k), t2, lfmKern2(k).limit).';
    gsp2Kern(k) = gsp2Kern(k) - sum(sum((coeffPosPos*temp).*covGrad));
    gsp1Kern(k) = gsp1Kern(k) + sum(sum((coeffPosPos*temp).*covGrad));
    temp = fhandleKernGradSwitchingPosPos2(lfmKern2(k), lfmKern1(k), t2, lfmKern2(k).limit).';
    gsp2Kern(k) = gsp2Kern(k) - sum(sum((coeffPosPos*temp).*covGrad));
    temp = fhandleKernGradSwitchingVelPos1(lfmKern1(k), lfmKern2(k), lfmKern2(k).limit, t2);
    gsp2Kern(k) = gsp2Kern(k) - sum(sum((coeffVelPos*temp).*covGrad));
    gsp1Kern(k) = gsp1Kern(k) + sum(sum((coeffVelPos*temp).*covGrad));
    temp = fhandleKernGradSwitchingVelPos2(lfmKern2(k), lfmKern1(k), t2, lfmKern2(k).limit).';
    gsp2Kern(k) = gsp2Kern(k) - sum(sum((coeffVelPos*temp).*covGrad));
end
if isempty(generalConst{i,j})
    matGradAlpha = gcAlpha*PosPos + geAlpha*VelPos;
    matGradOmega = gcOmega*PosPos + geOmega*VelPos;
    gCoeff = gradAlpha*sum(sum(matGradAlpha.*covGrad)) + ...
        gradOmega*(sum(sum(matGradOmega.*covGrad)));
    matGradSp1 = g1Pos*PosPos + h1Vel*VelPos;
    gsp1 = - sum(sum(matGradSp1.*covGrad));
    g3(i) = gsp1Mean + sum(gsp1Kern) + gsp1; % switching point 1
    g3(j) = gsp2Mean + sum(gsp2Kern);        % switching point 2
else
    constGradAlpha = generalConstGrad{1}{i,j};
    constGradOmega = generalConstGrad{2}{i,j};
    constVal = generalConst{i,j};
    % Derivative wrt parameters
    matGradAlpha = (constVal(1,1)*gcAlpha + constGradAlpha(1,1)*c1 ...
        + constVal(2,1)*geAlpha + constGradAlpha(2,1)*e1)*PosPos ...
        + (constVal(1,2)*gcAlpha + constGradAlpha(1,2)*c1 ...
        + constVal(2,2)*geAlpha + constGradAlpha(2,2)*e1)*VelPos;
    matGradOmega = (constVal(1,1)*gcOmega + constGradOmega(1,1)*c1 ...
        + constVal(2,1)*geOmega + constGradOmega(2,1)*e1)*PosPos ...
        + (constVal(1,2)*gcOmega + constGradOmega(1,2)*c1 ...
        + constVal(2,2)*geOmega + constGradOmega(2,2)*e1)*VelPos;
    gCoeff = gradAlpha*sum(sum(matGradAlpha.*covGrad)) + ...
        gradOmega*(sum(sum(matGradOmega.*covGrad)));
    % Derivative wrt switching points
    % Firts, notice that gsp1Kern doesn't correspond to the switching point
    % of the interval i (or say 1). It's just the derivative of the inner
    % switching point that lead to the actual switching point for the
    % interval 1. So we rename it first.
    gspInt = sum(gsp1Kern);
    % Compute the derivative of the swicthing point i, not appearing in the
    % constant
    matGradSp1 = (constVal(1,1)*g1Pos + constVal(2,1)*h1Vel)*PosPos + ...
        (constVal(1,2)*g1Pos + constVal(2,2)*h1Vel)*VelPos;
    gsp1 = - sum(sum(matGradSp1.*covGrad));
    % Compute the derivatives for all the switching points apearing in the
    % constant
    constGradSPoint = generalConstGrad{3}{i,j};
    numberSP = size(constGradSPoint,2);
    gspInBetween = zeros(1, numberSP);
    for k=1:numberSP
        temp = (constGradSPoint(1,k)*c1 + constGradSPoint(3,k)*e1)*PosPos + ...
            (constGradSPoint(2,k)*c1 + constGradSPoint(4,k)*e1)*VelPos;
        gspInBetween(k) = sum(sum(temp.*covGrad));
    end
    % Assign derivatives wrt all other switching points, with a correction
    % for the derivative of the innermost switching point in the constant
    gspInBetween(end) =  gspInBetween(end) + gspInt;
    g3(i) = gsp1Mean + gsp1 + gspInBetween(1);
    g3(j) = gsp2Mean + sum(gsp2Kern);
    g3(j+1:i-1) = fliplr(gspInBetween(2:end));
end
% Assign derivatives wrt first system
g1{1} = g1Mean + sum(g1Kern(:,1:3), 1) + gCoeff; % mass 1, spring 1, damper 1
g1{2} = g1Kern(:,4).';                            % inverse widths
g1{3} = g1Kern(:,5).';                            % seinsitivities 1
% Assign derivatives wrt second system
g2{1} = g2Mean + sum(g2Kern(:,1:3), 1); % mass 2, spring 2, damper 2
g2{2} = g2Kern(:,4).';                   % inverse widths
g2{3} = g2Kern(:,5).';


function r = ldivide(a,b)
% o .\ v
%
% Syntax
%   h = o .\ r
%
% Input
%  o - @orientation
%  r - @vector3d
%
% Output
%  h - @Miller indice
%
% See also
%

r = inv(a) .* b;


function deri = derivative_sigm_j(a)
    % derivative of tanh(x) is 1-tanh(x).^2
    %tmp = gones(size(a));
    %tmp = gpuArray(ones(size(a)));
    %deri = (tmp - (a / 1.7159).^2) * 1.7159 * 0.6667;
    %deri = tmp - (a.^2);
    %deri = gsingle(not(not(max(0, a))));
    deri = gpuArray(not(not(max(0, a))));
end


tic;
ssub = neuron_raw.options.ssub;
tsub = neuron_raw.options.tsub;

sframe = str2double(get(edit_begin, 'string'));
eframe = str2double(get(edit_end, 'string'));
num2read = str2double(get(edit_total, 'string'));

if and(ssub==1, tsub==1)
    neuron = neuron_raw;
    Y = double(data.Y(:, :, sframe+(1:num2read)-1));
    [d1s,d2s, T] = size(Y);
    fprintf('\nThe data has been loaded into RAM. It has %d X %d pixels X %d frames. \nLoading all data requires %.2f GB RAM\n\n', d1s, d2s, T, d1s*d2s*T*8/(2^30));
else
    [Y, neuron] = neuron_raw.load_data(nam_mat, sframe, num2read);
    [d1s,d2s, T] = size(Y);
    fprintf('\nThe data has been downsampled and loaded into RAM. It has %d X %d pixels X %d frames. \nLoading all data requires %.2f GB RAM\n\n', d1s, d2s, T, d1s*d2s*T*8/(2^30));
end
Y = neuron.reshape(Y, 1);
neuron_raw.P.p = 2;      %order of AR model

fprintf('Time cost in downsapling data:     %.2f seconds\n', toc);


function tips = correct_rv_crescent_tips(tips, valve, dthr)
% CORRECT_RV_CRESCENT_TIPS  Correct the segmentation of the Right
% Ventricle's crescent tips using the segmentation of the tricuspid and
% pulmonary annula
%
% TIPS = CORRECT_RV_CRESCENT_TIPS(TIPS, VALVE)
%
%   TIPS is a 3-column matrix with the coordinates of all the points in the
%   Right Ventricle crest, that corresponds to the crescent shape tips when
%   you cut the ventricle by an axial plane.
%
%   VALVE is a 3-colum matrix with the coordinates of either the tricuspid
%   valve or pulmonary valve segmetations.
%
%   The points in the segmentations are assumed to be consecutive.
%
% TIPS = CORRECT_RV_CRESCENT_TIPS(TIPS, VALVE, DTHR)
%
%   DTHR is the threshold distance value between the crest line and the
%   valve.

% Author: Ramon Casero <rcasero@gmail.com>
% Copyright © 2010 University of Oxford
% Version: 0.1.0
%
% University of Oxford means the Chancellor, Masters and Scholars of
% the University of Oxford, having an administrative office at
% Wellington Square, Oxford OX1 2JD, UK.
%
% This file is part of Gerardus.
%
% 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. The offer of this
% program under the terms of the License is subject to the License
% being interpreted in accordance with English Law and subject to any
% action against the University of Oxford being under the jurisdiction
% of the English Courts.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.

% check arguments
error( nargchk( 2, 3, nargin, 'struct' ) );
error( nargoutchk( 0, 1, nargout, 'struct' ) );

% number of crest points
P = size(tips, 1);

% compute distance from each crest point to the valve
d = inf(P, 1);
for I = find(~isnan(sum(tips, 2)));
    d(I) = min(dmatrix(valve', tips(I,:)'));
end

% default value of dthr
if (nargin < 3 || isempty(dthr))
    dthr = min(d) * 5;
end

% find the first and last times the crest gets within a small distance of
% the valve
idx = find(d <= dthr);
if isempty(idx)
    error('The crest does not get close enough to the valve')
end
t1 = idx(1);
t2 = idx(end);

% % find one of the intersection points of the valve with the crest (crest
% % point index)
% [aux, t1] = min(d);
%
% % we are going to ignore a bunch of points either side of the intersection
% % point
% L = round(size(valve, 1)/4);
% d2 = d;
% d2(t1-L:t1+L) = Inf;
%
% % find second intersection point (crest point index)
% [aux, t2] = min(d2);
% aux = sort([t1 t2]);
% t1 = aux(1);
% t2 = aux(2);

% find intersection points (valve point index)
[aux, v1] = min(dmatrix(valve', tips(t1,:)'));
[aux, v2] = min(dmatrix(valve', tips(t2,:)'));

% find which half of the valve is closer to the centroid
aux = sort([v1 v2]);
v1 = aux(1);
v2 = aux(2);
if (mean(dmatrix(valve(v1:v2, :)', [0;0;0])) < ...
        mean(dmatrix(valve([v2:end 1:v1], :)', [0;0;0])))
    valveseg = valve(v1:v2, :);
else
    valveseg = valve([v2:end 1:v1], :);
end

% if the first point we find in the valve segment is closer to the _last_
% point we find in the crest gap, then we have to invert the segment's
% order
if (dmatrix(valveseg(1, :)', tips(t1,:)') > ...
        dmatrix(valveseg(end, :)', tips(t1,:)'))
    valveseg = valveseg(end:-1:1, :);
end

% replace the incorrect crest segment by the valve segment
tips = [tips(1:t1, :); valveseg; tips(t2:end, :)];


function varargout = get_soln(om, set_type, tags, name, idx)
%GET_SOLN  Fetch solution values for specific named/indexed sets.
%   VALS = OM.GET_SOLN(SET_TYPE, NAME)
%   VALS = OM.GET_SOLN(SET_TYPE, NAME, IDX)
%   VALS = OM.GET_SOLN(SET_TYPE, TAGS, NAME)
%   VALS = OM.GET_SOLN(SET_TYPE, TAGS, NAME, IDX)
%
%   Returns named/indexed results for a solved model, evaluated at
%   the solution found.
%
%   Inputs:
%       SET_TYPE - one of the following, specifying the type of set:
%           'var' - variables
%           'lin' - linear constraints
%           'nle' - nonlinear equality constraints
%           'nli' - nonlinear inequality constraints
%           'nlc' - nonlinear costs
%           'qdc' - quadratic costs
%       TAGS - char array or cell array of char arrays specifying the
%           desired output(s). Valid tags vary by SET_TYPE as follows:
%           'var' - default is {'x', 'mu_l', 'mu_u'}
%               'x' - value of solution variable
%               'mu_l' - shadow price on variable lower bound
%               'mu_u' - shadow price on variable upper bound
%           'lin' - default is {'g', 'mu_l', 'mu_u'}
%               'g' - 1 x 2 cell array of upper and lower constraint
%                   values, {A*x - u, l - A*x}
%               'Ax_u' - upper constraint value, A*x - u
%               'l_Ax' - lower constraint value, l - A*x
%               'mu_l' - shadow price on constraint lower bound
%               'mu_u' - shadow price on constraint upper bound
%           'nle' - default is {'g', 'lam', 'dg'}
%               'g' - constraint value g(x)
%               'lam' - shadow price on constraint
%               'dg' - Jacobian of constraint
%           'nli' - default is {'h', 'mu', 'dh'}
%               'h' - constraint value h(x)
%               'mu' - shadow price on constraint
%               'dh' - Jacobian of constraint
%           'nlc' and 'qdc' - default is {'f', 'df', 'd2f'}
%               'f' - cost function value f(x) (for 'qdc' can return a vector)
%               'df' - gradient of cost function
%               'd2f' - Hessian of cost function
%       NAME - char array specifying the name of the set
%       IDX  - cell array specifying the indices of the set
%
%   Outputs:
%       Variable number of outputs corresponding to TAGS input. If TAGS
%       is empty or not specified, the calling context will define the
%       number of outputs, returned in order of default tags for the
%       specified SET_TYPE.
%
%   Examples:
%       [P, muPmin, muPmax] = om.get_soln('var', 'P');
%       [mu_u, mu_l] = om.get_soln('lin', {'mu_u', 'mu_l'}, 'lin_con_1');
%       dg_b_2_3 = om.get_soln('nle', 'dg', 'nle_con_b', {2,3});
%
%   For a complete set of solution vector values and shadow prices, using
%   the PARSE_SOLN method may be more efficient.
%
%   See also PARSE_SOLN.

%   MP-Opt-Model
%   Copyright (c) 2020, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MP-Opt-Model.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://github.com/MATPOWER/mp-opt-model for more info.

%% input arg handling
if nargin == 3              %% om.get_soln(set_type, name)
    idx = [];
    name = tags;
    tags = {};
elseif nargin == 4
    if ischar(name)         %% om.get_soln(set_type, tags, name)
        idx = [];
    else                    %% om.get_soln(set_type, name, idx)
        idx = name;
        name = tags;
        tags = {};
    end
end

%% set up tags for default outputs
if isempty(tags)
    switch set_type
        case 'var'
            tags = {'x', 'mu_l', 'mu_u'};
        case 'lin'
            if strcmp(om.problem_type(), 'LEQ')
                tags = {'f'};   %% 'Ax_u', 'l_Ax' are also options
            else
                tags = {'g', 'mu_l', 'mu_u'};   %% 'Ax_u', 'l_Ax' are also options
            end
        case 'nle'
            tags = {'g', 'lam', 'dg'};
        case 'nli'
            tags = {'h', 'mu', 'dh'};
        case 'nlc'
            tags = {'f', 'df', 'd2f'};
        case 'qdc'
            tags = {'f', 'df', 'd2f'};
    end
elseif ~iscell(tags)
    tags = { tags };
end

%% set up indexing
om_ff = om.(set_type);
if isempty(idx)         %% simple named set
    N = om_ff.idx.N.(name);
    i1 = om_ff.idx.i1.(name);           %% starting row index
    iN = om_ff.idx.iN.(name);           %% ending row index
else                    %% indexed named set
    %% calls to substruct() are relatively expensive, so we pre-build the
    %% structs for addressing cell and numeric array fields, updating only
    %% the subscripts before use
    sc = struct('type', {'.', '{}'}, 'subs', {name, idx});  %% cell array field
    sn = sc; sn(2).type = '()';                         %% num array field
    N = subsref(om_ff.idx.N, sn);
    i1 = subsref(om_ff.idx.i1, sn);     %% starting row index
    iN = subsref(om_ff.idx.iN, sn);     %% ending row index
end

%% get outputs
varargout = cell(1, nargout);
s = om.soln;
if N && ~isempty(s.eflag)
    switch set_type
        case 'var'
            for k = 1:nargout
                switch tags{k}
                    case 'x'
                        varargout{k} = s.x(i1:iN);
                    case 'mu_l'
                        varargout{k} = s.lambda.lower(i1:iN);
                    case 'mu_u'
                        varargout{k} = s.lambda.upper(i1:iN);
                    otherwise
                        error('opt_model/get_soln: unknown tag ''%s''', tags{k});
                end
            end
        case 'lin'
            if strcmp(om.problem_type(), 'LEQ') %% tag must be 'f'
                varargout{1} = s.f(i1:iN);
            else
                if any(ismember({'g', 'Ax_u', 'l_Ax'}, tags(1:nargout)))
                    g = cell(1,2);
                    [g{:}] = om.eval_lin_constraint(s.x, name, idx);
                end
                for k = 1:nargout
                    switch tags{k}
                        case 'g'
                            varargout{k} = g;
                        case 'Ax_u'
                            varargout{k} = g{1};
                        case 'l_Ax'
                            varargout{k} = g{2};
                        case 'mu_l'
                            varargout{k} = s.lambda.mu_l(i1:iN);
                        case 'mu_u'
                            varargout{k} = s.lambda.mu_u(i1:iN);
                        otherwise
                            error('opt_model/get_soln: unknown tag ''%s''', tags{k});
                    end
                end
            end
        case 'nle'
            if ismember('dg', tags(1:nargout))
                [g, dg] = om.eval_nln_constraint(s.x, 1, name, idx);
            elseif ismember('g', tags(1:nargout))
                g = om.eval_nln_constraint(s.x, 1, name, idx);
            end
            for k = 1:nargout
                switch tags{k}
                    case 'g'
                        varargout{k} = g;
                    case 'dg'
                        varargout{k} = dg;
                    case 'lam'
                        varargout{k} = s.lambda.eqnonlin(i1:iN);
                    otherwise
                        error('opt_model/get_soln: unknown tag ''%s''', tags{k});
                end
            end
        case 'nli'
            if ismember('dh', tags(1:nargout))
                [h, dh] = om.eval_nln_constraint(s.x, 0, name, idx);
            elseif ismember('h', tags(1:nargout))
                h = om.eval_nln_constraint(s.x, 0, name, idx);
            end
            for k = 1:nargout
                switch tags{k}
                    case 'h'
                        varargout{k} = h;
                    case 'dh'
                        varargout{k} = dh;
                    case 'mu'
                        varargout{k} = s.lambda.ineqnonlin(i1:iN);
                    otherwise
                        error('opt_model/get_soln: unknown tag ''%s''', tags{k});
                end
            end
        case 'nlc'
            if ismember('d2f', tags(1:nargout))
                [f, df, d2f] = om.eval_nln_cost(s.x, name, idx);
            elseif ismember('df', tags(1:nargout))
                [f, df] = om.eval_nln_cost(s.x, name, idx);
            else
                f = om.eval_nln_cost(s.x, name, idx);
            end
            for k = 1:nargout
                switch tags{k}
                    case 'f'
                        varargout{k} = f;
                    case 'df'
                        varargout{k} = df;
                    case 'd2f'
                        varargout{k} = d2f;
                    otherwise
                        error('opt_model/get_soln: unknown tag ''%s''', tags{k});
                end
            end
        case 'qdc'
            if ismember('d2f', tags(1:nargout))
                [f, df, d2f] = om.eval_quad_cost(s.x, name, idx);
            elseif ismember('df', tags(1:nargout))
                [f, df] = om.eval_quad_cost(s.x, name, idx);
            else
                f = om.eval_quad_cost(s.x, name, idx);
            end
            for k = 1:nargout
                switch tags{k}
                    case 'f'
                        varargout{k} = f;
                    case 'df'
                        varargout{k} = df;
                    case 'd2f'
                        varargout{k} = d2f;
                    otherwise
                        error('opt_model/get_soln: unknown tag ''%s''', tags{k});
                end
            end
        otherwise
            error('opt_model/get_soln: unknown set_type ''%s''', set_type);
    end     %% switch set_type
    end
end     %% if N


function output = apply_net_with_mask(input, mask)
    global config mem;
    fake_output_for_test = config.NEW_MEM(zeros(config.output_size(1), config.output_size(2), config.output_size(3), config.batch_size));
    input_size = size(input);
    output = config.NEW_MEM((zeros(size(input, 1), size(input, 2), config.chs)));
    p_size = config.MEM.p_size;
    size_differ = config.size_differ;
    count = zeros(size(input, 1), size(input, 2));
    for v = 1 : length(config.MEM.start_rows)
        for h = 1 : length(config.MEM.start_cols)
            v_start = config.MEM.start_rows(v);
            v_end = v_start + p_size - 1;
            h_start = config.MEM.start_cols(h);
            h_end = h_start + p_size - 1;
            if(v_end > input_size(1))
                v_end = input_size(1);
                v_start = v_end - p_size + 1;
            end
            if(h_end > input_size(2))
                h_end = input_size(2);
                h_start = h_end - p_size + 1;
            end
            input_piece = input(v_start:v_end, h_start:h_end,:);
            m = mask(v_start:v_end, h_start:h_end,:);

            % make the mask list
            mask_li = {};
            mask_li{1} = m;

            %op_test_pipe(input_piece, fake_output_for_test);
            op_test_pipe_with_mask(input_piece, mask_li, fake_output_for_test);
            output_piece = mem.output;

            output(v_start+(size_differ(1)/2):v_end-(size_differ(1)/2), h_start+(size_differ(2)/2):h_end-(size_differ(2)/2),:) = ...
                    output(v_start+(size_differ(1)/2):v_end-(size_differ(1)/2), h_start+(size_differ(2)/2):h_end-(size_differ(2)/2),:) + output_piece;
            count(v_start+(size_differ(1)/2):v_end-(size_differ(1)/2), h_start+(size_differ(2)/2):h_end-(size_differ(2)/2)) = ...
                    count(v_start+(size_differ(1)/2):v_end-(size_differ(1)/2), h_start+(size_differ(2)/2):h_end-(size_differ(2)/2)) + 1;
        end
    end
    count = max(count, 1);
    output = bsxfun(@rdivide, output, count);
end