function [cfg] = ft_multiplotTFR(cfg, data)

% FT_MULTIPLOTTFR plots the time-frequency representations of power or coherence
% in a topographical layout. The plots of the indivual sensors are arranged
% according to their location specified in the layout.
%
% Use as
%   ft_multiplotTFR(cfg, data)
%
% The data can be a time-frequency representation of power or coherence
% that was computed using the FT_FREQANALYSIS or FT_FREQDESCRIPTIVES
% functions.
%
% The configuration can have the following parameters:
%   cfg.parameter        = field to be represented as color, for example 'powspctrm' or 'cohspctrm' (default depends on data.dimord)
%   cfg.maskparameter    = field in the data to be used for masking of data, can be logical (e.g. significant data points) or numerical (e.g. t-values).
%                        (not possible for mean over multiple channels, or when input contains multiple subjects
%                        or trials)
%   cfg.maskstyle        = style used to masking, 'opacity', 'saturation', or 'outline' (default = 'opacity')
%                        'outline' can only be used with a logical cfg.maskparameter
%                        use 'saturation' or 'outline' when saving to vector-format (like *.eps) to avoid all sorts of image-problems
%   cfg.maskalpha        = alpha value between 0 (transparent) and 1 (opaque) used for masking areas dictated by cfg.maskparameter (default = 1)
%                        (will be ignored in case of numeric cfg.maskparameter or if cfg.maskstyle = 'outline')
%   cfg.masknans         = 'yes' or 'no' (default = 'yes')
%   cfg.xlim             = 'maxmin', 'maxabs', 'zeromax', 'minzero', or [xmin xmax] (default = 'maxmin')
%   cfg.ylim             = 'maxmin', 'maxabs', 'zeromax', 'minzero', or [ymin ymax] (default = 'maxmin')
%   cfg.zlim             = plotting limits for color dimension, 'maxmin', 'maxabs', 'zeromax', 'minzero', or [zmin zmax] (default = 'maxmin')
%   cfg.gradscale        = number, scaling to apply to the MEG gradiometer channels prior to display
%   cfg.magscale         = number, scaling to apply to the MEG magnetometer channels prior to display
%   cfg.channel          = Nx1 cell-array with selection of channels (default = 'all'), see FT_CHANNELSELECTION for details
%   cfg.refchannel       = name of reference channel for visualising connectivity, can be 'gui'
%   cfg.baseline         = 'yes', 'no' or [time1 time2] (default = 'no'), see FT_FREQBASELINE
%   cfg.baselinetype     = 'absolute', 'relative', 'relchange', 'normchange', 'db' or 'zscore' (default = 'absolute')
%   cfg.trials           = 'all' or a selection given as a 1xN vector (default = 'all')
%   cfg.box              = 'yes', 'no', whether to draw a box around each graph (default = 'no', if maskparameter is given default = 'yes')
%   cfg.hotkeys          = enables hotkeys (up/down arrows) for dynamic colorbar adjustment
%   cfg.colorbar         = 'yes', 'no' (default = 'no')
%   cfg.colorbartext     = string indicating the text next to colorbar
%   cfg.colormap         = string, or Nx3 matrix, see FT_COLORMAP
%   cfg.showlabels       = 'yes', 'no' (default = 'no')
%   cfg.showoutline      = 'yes', 'no' (default = 'no')
%   cfg.showscale        = 'yes', 'no' (default = 'yes')
%   cfg.showcomment      = 'yes', 'no' (default = 'yes')
%   cfg.comment          = string of text (default = date + limits)
%                          Add 'comment' to graph (according to COMNT in the layout)
%   cfg.limittext        = add user-defined text instead of cfg.comment, (default = cfg.comment)
%   cfg.fontsize         = font size of comment and labels (if present) (default = 8)
%   cfg.fontweight       = font weight of comment and labels (if present)
%   cfg.interactive      = Interactive plot 'yes' or 'no' (default = 'yes')
%                          In a interactive plot you can select areas and produce a new
%                          interactive plot when a selected area is clicked. Multiple areas
%                          can be selected by holding down the SHIFT key.
%   cfg.figure           = 'yes' or 'no', whether to open a new figure. You can also specify a figure handle from FIGURE, GCF or SUBPLOT. (default = 'yes')
%   cfg.position         = location and size of the figure, specified as [left bottom width height] (default is automatic)
%   cfg.renderer         = string, 'opengl', 'zbuffer', 'painters', see RENDERERINFO (default is automatic, try 'painters' when it crashes)
%   cfg.directionality   = '', 'inflow' or 'outflow' specifies for
%                          connectivity measures whether the inflow into a
%                          node, or the outflow from a node is plotted. The
%                          (default) behavior of this option depends on the dimor
%                          of the input data (see below).
%   cfg.layout           = specify the channel layout for plotting using one of
%                          the supported ways (see below).
%
% The following options for the scaling of the EEG, EOG, ECG, EMG, MEG and NIRS channels
% is optional and can be used to bring the absolute numbers of the different
% channel types in the same range (e.g. fT and uV). The channel types are determined
% from the input data using FT_CHANNELSELECTION.
%   cfg.eegscale         = number, scaling to apply to the EEG channels prior to display
%   cfg.eogscale         = number, scaling to apply to the EOG channels prior to display
%   cfg.ecgscale         = number, scaling to apply to the ECG channels prior to display
%   cfg.emgscale         = number, scaling to apply to the EMG channels prior to display
%   cfg.megscale         = number, scaling to apply to the MEG channels prior to display
%   cfg.gradscale        = number, scaling to apply to the MEG gradiometer channels prior to display (in addition to the cfg.megscale factor)
%   cfg.magscale         = number, scaling to apply to the MEG magnetometer channels prior to display (in addition to the cfg.megscale factor)
%   cfg.nirsscale        = number, scaling to apply to the NIRS channels prior to display
%   cfg.mychanscale      = number, scaling to apply to the channels specified in cfg.mychan
%   cfg.mychan           = Nx1 cell-array with selection of channels
%   cfg.chanscale        = Nx1 vector with scaling factors, one per channel specified in cfg.channel
%
% For the plotting of directional connectivity data the cfg.directionality
% option determines what is plotted. The default value and the supported
% functionality depend on the dimord of the input data. If the input data
% is of dimord 'chan_chan_XXX', the value of directionality determines
% whether, given the reference channel(s), the columns (inflow), or rows
% (outflow) are selected for plotting. In this situation the default is
% 'inflow'. Note that for undirected measures, inflow and outflow should
% give the same output. If the input data is of dimord 'chancmb_XXX', the
% value of directionality determines whether the rows in data.labelcmb are
% selected. With 'inflow' the rows are selected if the refchannel(s) occur in
% the right column, with 'outflow' the rows are selected if the
% refchannel(s) occur in the left column of the labelcmb-field. Default in
% this case is '', which means that all rows are selected in which the
% refchannel(s) occur. This is to robustly support linearly indexed
% undirected connectivity metrics. In the situation where undirected
% connectivity measures are linearly indexed, specifying 'inflow' or
% 'outflow' can result in unexpected behavior.
%
% The layout defines how the channels are arranged and what the size of each
% subplot is. You can specify the layout in a variety of ways:
%  - you can provide a pre-computed layout structure, see FT_PREPARE_LAYOUT
%  - you can give the name of an ASCII layout file with extension *.lay
%  - you can give the name of an electrode file
%  - you can give an electrode definition, i.e. "elec" structure
%  - you can give a gradiometer definition, i.e. "grad" structure
% If you do not specify any of these and the data structure contains an
% electrode or gradiometer structure (common for MEG data, since the header
% of the MEG datafile contains the gradiometer information), that will be
% used for creating a layout. If you want to have more fine-grained control
% over the layout of the subplots, you should create your own layout file.
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
% If you specify this option the input data will be read from a *.mat
% file on disk. This mat files should contain only a single variable named 'data',
% corresponding to the input structure. For this particular function, the
% data should be provided as a cell-array.
%
% See also:
%   FT_MULTIPLOTER, FT_SINGLEPLOTER, FT_SINGLEPLOTTFR, FT_TOPOPLOTER, FT_TOPOPLOTTFR,
%   FT_PREPARE_LAYOUT

% Undocumented local options:
% cfg.channel
% cfg.layoutname
% cfg.orient = landscape/portrait

% Copyright (C) 2003-2006, Ole Jensen
% Copyright (C) 2007-2011, Roemer van der Meij & Jan-Mathijs Schoffelen
% Copyright (C) 2012-2017, F.C. Donders Centre
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip 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.
%
%    FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% DEVELOPERS NOTE: This code is organized in a similar fashion for multiplot/singleplot/topoplot
% and for ER/TFR and should remain consistent over those 6 functions.
% Section 1: general cfg handling that is independent from the data
% Section 2: data handling, this also includes converting bivariate (chan_chan and chancmb) into univariate data
% Section 3: select the data to be plotted and determine min/max range
% Section 4: do the actual plotting
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Section 1: general cfg handling that is independent from the data

% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin   = nargin;
ft_nargout  = nargout;

% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar data
ft_preamble provenance data

% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
  return
end

% check if the input data is valid for this function
data = ft_checkdata(data, 'datatype', 'freq');

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden',  {'channels', 'trial'}); % prevent accidental typos, see issue 1729
cfg = ft_checkconfig(cfg, 'renamed',    {'cohrefchannel', 'refchannel'});
cfg = ft_checkconfig(cfg, 'renamed',    {'matrixside', 'directionality'});
cfg = ft_checkconfig(cfg, 'renamed',    {'zparam', 'parameter'});
cfg = ft_checkconfig(cfg, 'renamedval', {'directionality', 'feedback', 'inflow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'directionality', 'feedforward', 'outflow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'zlim', 'absmax', 'maxabs'});
cfg = ft_checkconfig(cfg, 'unused',     {'cohtargetchannel'});
cfg = ft_checkconfig(cfg, 'renamed',    {'newfigure', 'figure'});

% set the defaults
cfg.parameter      = ft_getopt(cfg, 'parameter',    'powspctrm');
cfg.baseline       = ft_getopt(cfg, 'baseline',     'no');
cfg.baselinetype   = ft_getopt(cfg, 'baselinetype', 'absolute');
cfg.trials         = ft_getopt(cfg, 'trials', 'all', 1);
cfg.xlim           = ft_getopt(cfg, 'xlim',         'maxmin');
cfg.ylim           = ft_getopt(cfg, 'ylim',         'maxmin');
cfg.zlim           = ft_getopt(cfg, 'zlim',         'maxmin');
cfg.colorbar       = ft_getopt(cfg, 'colorbar',     'no');
cfg.colormap       = ft_getopt(cfg, 'colormap',      'default');
cfg.colorbartext   = ft_getopt(cfg, 'colorbartext', '');
cfg.comment        = ft_getopt(cfg, 'comment',       date);
cfg.limittext      = ft_getopt(cfg, 'limittext',    'default');
cfg.showlabels     = ft_getopt(cfg, 'showlabels',   'no');
cfg.showoutline    = ft_getopt(cfg, 'showoutline',  'no');
cfg.showscale      = ft_getopt(cfg, 'showscale',    'yes');
cfg.showcomment    = ft_getopt(cfg, 'showcomment',  'yes');
cfg.channel        = ft_getopt(cfg, 'channel',      'all');
cfg.fontsize       = ft_getopt(cfg, 'fontsize',      8);
cfg.fontweight     = ft_getopt(cfg, 'fontweight');
cfg.interactive    = ft_getopt(cfg, 'interactive',  'yes');
cfg.hotkeys        = ft_getopt(cfg, 'hotkeys',      'yes');
cfg.orient         = ft_getopt(cfg, 'orient',       'landscape');
cfg.maskalpha      = ft_getopt(cfg, 'maskalpha',     1);
cfg.masknans       = ft_getopt(cfg, 'masknans',     'yes');
cfg.maskparameter  = ft_getopt(cfg, 'maskparameter');
cfg.maskstyle      = ft_getopt(cfg, 'maskstyle',    'opacity');
cfg.directionality = ft_getopt(cfg, 'directionality', '');
cfg.figurename     = ft_getopt(cfg, 'figurename');
cfg.commentpos     = ft_getopt(cfg, 'commentpos',   'layout');
cfg.scalepos       = ft_getopt(cfg, 'scalepos',     'layout');
cfg.renderer       = ft_getopt(cfg, 'renderer'); % let MATLAB decide on the default

if ~isfield(cfg, 'box')
  if ~isempty(cfg.maskparameter)
    cfg.box = 'yes';
  else
    cfg.box = 'no';
  end
end

% check if the colormap is in the proper format
if ~isequal(cfg.colormap, 'default')
  if ischar(cfg.colormap)
    cfg.colormap = ft_colormap(cfg.colormap);
  elseif iscell(cfg.colormap)
    cfg.colormap = ft_colormap(cfg.colormap{:});
  elseif isnumeric(cfg.colormap) && size(cfg.colormap,2)~=3
    ft_error('cfg.colormap must be Nx3');
  end
  % the actual colormap will be set below
end

% this is needed for the figure title
if isfield(cfg, 'dataname') && ~isempty(cfg.dataname)
  dataname = cfg.dataname;
elseif isfield(cfg, 'inputfile') && ~isempty(cfg.inputfile)
  dataname = cfg.inputfile;
elseif nargin>1
  dataname = arrayfun(@inputname, 2:nargin, 'UniformOutput', false);
else
  dataname = {};
end

%% Section 2: data handling, this also includes converting bivariate (chan_chan and chancmb) into univariate data

hastime = isfield(data, 'time');
hasfreq = isfield(data, 'freq');

assert((hastime && hasfreq), 'please use ft_multiplotTFR for time-frequency data only');

xparam = ft_getopt(cfg, 'xparam', 'time');
yparam = ft_getopt(cfg, 'yparam', 'freq');

% check whether rpt/subj is present and remove if necessary
dimord = getdimord(data, cfg.parameter);
dimtok = tokenize(dimord, '_');
hasrpt = any(ismember(dimtok, {'rpt' 'subj'}));

if ~hasrpt
  assert(isequal(cfg.trials, 'all') || isequal(cfg.trials, 1), 'incorrect specification of cfg.trials for data without repetitions');
else
  assert(~isempty(cfg.trials), 'empty specification of cfg.trials for data with repetitions');
end

% parse cfg.channel
if isfield(cfg, 'channel') && isfield(data, 'label')
  cfg.channel = ft_channelselection(cfg.channel, data.label);
elseif isfield(cfg, 'channel') && isfield(data, 'labelcmb')
  cfg.channel = ft_channelselection(cfg.channel, unique(data.labelcmb(:)));
end

% Apply baseline correction:
if ~strcmp(cfg.baseline, 'no')
  tmpcfg = removefields(cfg, {'inputfile', 'reproducescript'});
  % keep mask-parameter if it is set
  if ~isempty(cfg.maskparameter)
    tempmask = data.(cfg.maskparameter);
  end
  data = ft_freqbaseline(tmpcfg, data);
  % put mask-parameter back if it is set
  if ~isempty(cfg.maskparameter)
    data.(cfg.maskparameter) = tempmask;
  end
end

% channels SHOULD be selected here, as no interactive action produces a new multiplot
tmpcfg = keepfields(cfg, {'channel', 'trials', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
if hasrpt
  tmpcfg.avgoverrpt = 'yes';
else
  tmpcfg.avgoverrpt = 'no';
end
tmpvar = data;
[data] = ft_selectdata(tmpcfg, data);
% restore the provenance information
[cfg, data] = rollback_provenance(cfg, data);

if isfield(tmpvar, cfg.maskparameter) && ~isfield(data, cfg.maskparameter)
  % the mask parameter is not present after ft_selectdata, because it is
  % not included in all input arguments. Make the same selection and copy
  % it over
  tmpvar = ft_selectdata(tmpcfg, tmpvar);
  data.(cfg.maskparameter) = tmpvar.(cfg.maskparameter);
end

clear tmpvar tmpcfg dimord dimtok hastime hasfreq hasrpt

% ensure that the preproc specific options are located in the cfg.preproc
% substructure, but also ensure that the field 'refchannel' remains at the
% highest level in the structure. This is a little hack by JM because the field
% refchannel can relate to connectivity or to an EEG reference.

if isfield(cfg, 'refchannel'), refchannelincfg = cfg.refchannel; cfg = rmfield(cfg, 'refchannel'); end
cfg = ft_checkconfig(cfg, 'createsubcfg',  {'preproc'});
if exist('refchannelincfg', 'var'), cfg.refchannel  = refchannelincfg; end

if ~isempty(cfg.preproc)
  % preprocess the data, i.e. apply filtering, baselinecorrection, etc.
  fprintf('applying preprocessing options\n');
  if ~isfield(cfg.preproc, 'feedback')
    cfg.preproc.feedback = cfg.interactive;
  end
  data = ft_preprocessing(cfg.preproc, data);
end

% Handle the bivariate case
dimord = getdimord(data, cfg.parameter);
if startsWith(dimord, 'chan_chan_') || startsWith(dimord, 'chancmb_')
  % convert the bivariate data to univariate and call this plotting function with univariate input
  cfg.originalfunction = 'ft_multiplotTFR';
  cfg.trials = 'all'; % trial selection has been taken care off
  bivariate_common(cfg, data);
  return
end

% Apply channel-type specific scaling
fn = fieldnames(cfg);
fn = setdiff(fn, {'skipscale', 'showscale', 'gridscale'}); % these are for the layout and plotting, not for CHANSCALE_COMMON
fn = fn(endsWith(fn, 'scale') | startsWith(fn, 'mychan') | strcmp(fn, 'channel') | strcmp(fn, 'parameter'));
tmpcfg = keepfields(cfg, fn);
if ~isempty(tmpcfg)
  data = chanscale_common(tmpcfg, data);
  % remove the scaling fields from the configuration, to prevent them from being called again in interactive mode
  % but keep the parameter and channel field
  cfg = removefields(cfg, setdiff(fn, {'parameter', 'channel'}));
else
  % do nothing
end

%% Section 3: select the data to be plotted and determine min/max range

% Read or create the layout that will be used for plotting
tmpcfg = keepfields(cfg, {'layout', 'channel', 'rows', 'columns', 'commentpos', 'skipcomnt', 'scalepos', 'skipscale', 'projection', 'viewpoint', 'rotate', 'width', 'height', 'elec', 'grad', 'opto', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
cfg.layout = ft_prepare_layout(tmpcfg, data);

% Take the subselection of channels that is contained in the layout, this is the same in all datasets
[selchan, sellay] = match_str(data.label, cfg.layout.label);

% Get physical min/max range of x, i.e. time
if ~isnumeric(cfg.xlim)
  xmin = nanmin(data.(xparam));
  xmax = nanmax(data.(xparam));
  switch cfg.xlim
    case 'maxmin'
      % keep them as they are
    case 'maxabs'
      xmax = max(abs(xmax), abs(xmin));
      xmin = -xmax;
    case 'zeromax'
      xmin = 0;
    case 'minzero'
      xmax = 0;
    otherwise
      ft_error('invalid specification of cfg.xlim');
  end % switch
else
  xmin = cfg.xlim(1);
  xmax = cfg.xlim(2);
end

% Get the index of the nearest bin, this is the same in all datasets
xminindx = nearest(data.(xparam), xmin);
xmaxindx = nearest(data.(xparam), xmax);
xmin = data.(xparam)(xminindx);
xmax = data.(xparam)(xmaxindx);
selx = xminindx:xmaxindx;
xval = data.(xparam)(selx);

% Get physical min/max range of y, i.e. freq
if ~isnumeric(cfg.ylim)
  ymin = nanmin(data.(yparam));
  ymax = nanmax(data.(yparam));
  switch cfg.ylim
    case 'maxmin'
      % keep them as they are
    case 'maxabs'
      ymax = max(abs(ymax), abs(ymin));
      ymin = -ymax;
    case 'zeromax'
      ymin = 0;
    case 'minzero'
      ymax = 0;
    otherwise
      ft_error('invalid specification of cfg.ylim');
  end % switch
else
  ymin = cfg.ylim(1);
  ymax = cfg.ylim(2);
end

% Get the index of the nearest bin, this is the same in all datasets
yminindx = nearest(data.(yparam), ymin);
ymaxindx = nearest(data.(yparam), ymax);
ymin = data.(yparam)(yminindx);
ymax = data.(yparam)(ymaxindx);
sely = yminindx:ymaxindx;
yval = data.(yparam)(sely);

% test if X and Y are linearly spaced (to within 10^-12): % FROM UIMAGE
dx = min(diff(xval));  % smallest interval for X
dy = min(diff(yval));  % smallest interval for Y
evenx = all(abs(diff(xval)/dx-1)<1e-12);     % true if X is linearly spaced
eveny = all(abs(diff(yval)/dy-1)<1e-12);     % true if Y is linearly spaced

if ~evenx || ~eveny
  ft_warning('(one of the) axis is/are not evenly spaced, but plots are made as if axis are linear')
end

% masking is only possible for evenly spaced axis
if strcmp(cfg.masknans, 'yes') && (~evenx || ~eveny)
  ft_warning('(one of the) axis are not evenly spaced -> nans cannot be masked out -> cfg.masknans is set to ''no'';')
  cfg.masknans = 'no';
end

% the usual data is chan_freq_time, but other dimords should also work
dimtok = tokenize(dimord, '_');
datamatrix = data.(cfg.parameter);
[c, ia, ib] = intersect({'chan', yparam, xparam}, dimtok, 'stable');
datamatrix = permute(datamatrix, ib);
datamatrix = datamatrix(selchan, sely, selx);

if ~isempty(cfg.maskparameter)
  % one value for each channel-freq-time point
  maskmatrix = data.(cfg.maskparameter)(selchan, sely, selx);
  if islogical(maskmatrix) && any(strcmp(cfg.maskstyle, {'saturation', 'opacity'}))
    maskmatrix = double(maskmatrix);
    maskmatrix(~maskmatrix) = cfg.maskalpha;
  elseif isnumeric(maskmatrix)
    if strcmp(cfg.maskstyle, 'outline')
      error('Outline masking with a numeric cfg.maskparameter is not supported. Please use a logical mask instead.')
    end
    if cfg.maskalpha ~= 1
      ft_warning('Using field "%s" for masking, cfg.maskalpha is ignored.', cfg.maskparameter)
    end
    % scale mask between 0 and 1
    minval = min(maskmatrix(:));
    maxval = max(maskmatrix(:));
    maskmatrix = (maskmatrix - minval) / (maxval-minval);
  end
else
  % create an Nx0x0 matrix
  maskmatrix = zeros(length(selchan), 0, 0);
end

chanX      = cfg.layout.pos(sellay, 1);
chanY      = cfg.layout.pos(sellay, 2);
chanWidth  = cfg.layout.width(sellay);
chanHeight = cfg.layout.height(sellay);

%% Section 4: do the actual plotting

% open a new figure, or add it to the existing one
% note that in general adding a TFR to an existing one does not make sense, since they will overlap
open_figure(keepfields(cfg, {'figure', 'position', 'visible', 'renderer', 'figurename', 'title'}));
hold on

% Get physical z-axis range (color axis):
if strcmp(cfg.zlim, 'maxmin')
  zmin = nanmin(datamatrix(:));
  zmax = nanmax(datamatrix(:));
elseif strcmp(cfg.zlim, 'maxabs')
  zmin = -nanmax(abs(datamatrix(:)));
  zmax =  nanmax(abs(datamatrix(:)));
elseif strcmp(cfg.zlim, 'zeromax')
  zmin = 0;
  zmax = nanmax(datamatrix(:));
elseif strcmp(cfg.zlim, 'minzero')
  zmin = nanmin(datamatrix(:));
  zmax = 0;
else
  zmin = cfg.zlim(1);
  zmax = cfg.zlim(2);
end

% Plot channels
for k=1:length(selchan)
  cdata = shiftdim(datamatrix(k, :, :));
  if ~isempty(cfg.maskparameter)
    mdata = shiftdim(maskmatrix(k, :, :));
  end

  % Draw plot (and mask Nan's with maskfield if requested)
  if isequal(cfg.masknans, 'yes') && isempty(cfg.maskparameter)
    nans_mask = ~isnan(cdata);
    mask = double(nans_mask);
    ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'highlightstyle', cfg.maskstyle, 'highlight', mask, 'hpos', chanX(k), 'vpos', chanY(k), 'width', chanWidth(k), 'height', chanHeight(k))
  elseif isequal(cfg.masknans, 'yes') && ~isempty(cfg.maskparameter)
    nans_mask = ~isnan(cdata);
    mask = nans_mask .* mdata;
    mask = double(mask);
    ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'highlightstyle', cfg.maskstyle, 'highlight', mask, 'hpos', chanX(k), 'vpos', chanY(k), 'width', chanWidth(k), 'height', chanHeight(k))
  elseif isequal(cfg.masknans, 'no') && ~isempty(cfg.maskparameter)
    mask = mdata;
    mask = double(mask);
    ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'highlightstyle', cfg.maskstyle, 'highlight', mask, 'hpos', chanX(k), 'vpos', chanY(k), 'width', chanWidth(k), 'height', chanHeight(k))
  else
    ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'hpos', chanX(k), 'vpos', chanY(k), 'width', chanWidth(k), 'height', chanHeight(k))
  end

  % Currently the handle isn't being used below, this is here for possible use in the future
  h = findobj('tag', 'cip');
end % plot channels

% plot the layout, labels and outline
ft_plot_layout(cfg.layout, 'box', cfg.box, 'label', cfg.showlabels, 'outline', cfg.showoutline, 'point', 'no', 'mask', 'no', 'fontsize', cfg.fontsize, 'labelyoffset', 1.4*median(cfg.layout.height/2), 'labelalignh', 'center', 'chanindx', find(~ismember(cfg.layout.label, {'COMNT', 'SCALE'})) );

% apply the colormap
if ~isempty(cfg.colormap)
  set(gcf,  'colormap', cfg.colormap);
end

% Construct comment
if ~strcmp(cfg.comment, 'no')
  if ~strcmp(cfg.limittext, 'default')
    comment = cfg.limittext;
  else
    comment = cfg.comment;
    comment = sprintf('%0s\nxlim=[%.3g %.3g]', comment, xmin, xmax);
    comment = sprintf('%0s\nylim=[%.3g %.3g]', comment, ymin, ymax);
    comment = sprintf('%0s\nzlim=[%.3g %.3g]', comment, zmin, zmax);
  end
end

% Write comment
if strcmp(cfg.comment, 'no')
  comment_handle = [];
elseif strcmp(cfg.commentpos, 'title')
  comment_handle = title(comment, 'FontSize', cfg.fontsize);
elseif ~isempty(strcmp(cfg.layout.label, 'COMNT'))
  x_comment = cfg.layout.pos(strcmp(cfg.layout.label, 'COMNT'), 1);
  y_comment = cfg.layout.pos(strcmp(cfg.layout.label, 'COMNT'), 2);
  % 'HorizontalAlignment', 'left', 'VerticalAlignment', 'bottom',
  comment_handle = ft_plot_text(x_comment, y_comment, comment, 'FontSize', cfg.fontsize, 'FontWeight', cfg.fontweight);
else
  comment_handle = [];
end

% show scale
if istrue(cfg.showscale)
  k = cellstrmatch('SCALE', cfg.layout.label);
  if ~isempty(k)
    % Get average cdata across channels:
    cdata = shiftdim(mean(datamatrix, 1));

    % Draw plot (and mask Nan's with maskfield if requested)
    if isequal(cfg.masknans, 'yes') && isempty(cfg.maskparameter)
      mask = ~isnan(cdata);
      mask = double(mask);
      ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'highlightstyle', cfg.maskstyle, 'highlight', mask, 'hpos', cfg.layout.pos(k, 1), 'vpos', cfg.layout.pos(k, 2), 'width', cfg.layout.width(k), 'height', cfg.layout.height(k))
    elseif isequal(cfg.masknans, 'yes') && ~isempty(cfg.maskparameter)
      mask = ~isnan(cdata);
      mask = mask .* mdata;
      mask = double(mask);
      ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'highlightstyle', cfg.maskstyle, 'highlight', mask, 'hpos', cfg.layout.pos(k, 1), 'vpos', cfg.layout.pos(k, 2), 'width', cfg.layout.width(k), 'height', cfg.layout.height(k))
    elseif isequal(cfg.masknans, 'no') && ~isempty(cfg.maskparameter)
      mask = mdata;
      mask = double(mask);
      ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'highlightstyle', cfg.maskstyle, 'highlight', mask, 'hpos', cfg.layout.pos(k, 1), 'vpos', cfg.layout.pos(k, 2), 'width', cfg.layout.width(k), 'height', cfg.layout.height(k))
    else
      ft_plot_matrix(cdata, 'clim', [zmin zmax], 'tag', 'cip', 'hpos', cfg.layout.pos(k, 1), 'vpos', cfg.layout.pos(k, 2), 'width', cfg.layout.width(k), 'height', cfg.layout.height(k))
    end
  end
end % show scale

% show colorbar
if isfield(cfg, 'colorbar') && (strcmp(cfg.colorbar, 'yes'))
  c = colorbar;
  ylabel(c, cfg.colorbartext);
end

% Set colour axis
caxis([zmin zmax]);
if strcmp('yes', cfg.hotkeys)
  %  Attach data and cfg to figure and attach a key listener to the figure
  set(gcf, 'KeyPressFcn', {@key_sub, zmin, zmax})
end

axis tight
axis off

% Make the axis a little wider when boxes are shown
if strcmp(cfg.box, 'yes')
  abc = axis;
  axis(abc + [-1 +1 -1 +1]*mean(abs(abc))/10)
end

% Set orientation for printing if specified
if ~isempty(cfg.orient)
  orient(gcf, cfg.orient);
end

% set the figure window title
if ~isempty(dataname)
  set(gcf, 'Name', sprintf('%d: %s: %s', double(gcf), mfilename, join_str(', ', dataname)));
else
  set(gcf, 'Name', sprintf('%d: %s', double(gcf), mfilename));
end
set(gcf, 'NumberTitle', 'off');

% Make the figure interactive:
if strcmp(cfg.interactive, 'yes')
  % add the cfg/data/channel information to the figure under identifier linked to this axis
  ident                 = ['axh' num2str(round(sum(clock.*1e6)))]; % unique identifier for this axis
  set(gca,'tag',ident);
  info                  = guidata(gcf);
  info.(ident).x        = cfg.layout.pos(:, 1);
  info.(ident).y        = cfg.layout.pos(:, 2);
  info.(ident).label    = cfg.layout.label;
  info.(ident).dataname = dataname;
  info.(ident).cfg      = cfg;
  info.(ident).data     = data;
  info.(ident).commenth = comment_handle;
  guidata(gcf, info);
  set(gcf, 'WindowButtonUpFcn', {@ft_select_channel, 'multiple', true, 'callback', {@select_singleplotTFR}, 'event', 'WindowButtonUpFcn'});
  set(gcf, 'WindowButtonDownFcn', {@ft_select_channel, 'multiple', true, 'callback', {@select_singleplotTFR}, 'event', 'WindowButtonDownFcn'});
  set(gcf, 'WindowButtonMotionFcn', {@ft_select_channel, 'multiple', true, 'callback', {@select_singleplotTFR}, 'event', 'WindowButtonMotionFcn'});
end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous data
ft_postamble provenance
ft_postamble savefig

% add a menu to the figure, but only if the current figure does not have subplots
menu_fieldtrip(gcf, cfg, false);

if ~ft_nargout
  % don't return anything
  clear cfg
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function l = cellstrmatch(str, strlist)
l = [];
for k=1:length(strlist)
  if strcmp(char(str), char(strlist(k)))
    l = [l k];
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION which is called after selecting channels in case of cfg.interactive='yes'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function select_singleplotTFR(label, varargin)
% fetch cfg/data based on axis indentifier given as tag
ident = get(gca,'tag');
info  = guidata(gcf);
cfg   = info.(ident).cfg;
data  = info.(ident).data;
if ~isempty(label)
  cfg = removefields(cfg, 'inputfile');   % the reading has already been done and varargin contains the data
  cfg.baseline = 'no';                    % make sure the next function does not apply a baseline correction again
  cfg.channel = label;
  cfg.dataname = info.(ident).dataname;   % put data name in here, this cannot be resolved by other means
  cfg.trials = 'all';                     % trial selection has already been taken care of
  fprintf('selected cfg.channel = {%s}\n', join_str(', ', cfg.channel));
  % ensure that the new figure appears at the same position
  cfg.figure = 'yes';
  cfg.position = get(gcf, 'Position');
  ft_singleplotTFR(cfg, data);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION which handles hot keys in the current plot
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function key_sub(handle, eventdata, varargin)
ident       = get(gca, 'tag');
info        = guidata(gcf);

climits = caxis;
incr_c  = abs(climits(2) - climits(1)) /10;

newz = climits;
if length(eventdata.Modifier) == 1 && strcmp(eventdata.Modifier{:}, 'control')
  % TRANSLATE by 10%
  switch eventdata.Key
    case 'pageup'
      newz = [climits(1)+incr_c climits(2)+incr_c];
    case 'pagedown'
      newz = [climits(1)-incr_c climits(2)-incr_c];
  end % switch
else
  % ZOOM by 10%
  switch eventdata.Key
    case 'pageup'
      newz = [climits(1)-incr_c climits(2)+incr_c];
    case 'pagedown'
      newz = [climits(1)+incr_c climits(2)-incr_c];
    case 'm'
      newz = [varargin{1} varargin{2}];
  end % switch
end % if

% update the color axis
caxis(newz);

if ~isempty(ident) && isfield(info.(ident), 'commenth') && ~isempty(info.(ident).commenth)
  commentstr = get(info.(ident).commenth, 'string');
  sel        = contains(commentstr, 'zlim');
  if any(sel)
    commentstr{sel} = sprintf('%0s=[%.3g %.3g]', 'zlim', newz(1), newz(2));
    set(info.(ident).commenth, 'string', commentstr);
  end
end


function varargout = drawSquareMesh(nodes, edges, faces, varargin) %#ok<INUSL>
%DRAWSQUAREMESH Draw a 3D square mesh given as a graph.
%
%   drawSquareMesh(NODES, EDGES, FACES)
%   Draw the mesh defined by NODES, EDGES and FACES. FACES must be a N-by-4
%   array of vertex indices.
%
%   See also
%   boundaryGraph, drawGraph

% ------
% Author: David Legland
% E-mail: david.legland@inrae.fr
% Created: 2004-06-28
% Copyright 2004-2022 INRA - TPV URPOI - BIA IMASTE

% input size check up
if size(faces, 2) ~= 4
    error('Requires a face array with 4 columns');
end

% number of faces
Nf = size(faces, 1);

% allocate memory for vertex coordinates
px = zeros(4, Nf);
py = zeros(4, Nf);
pz = zeros(4, Nf);

% initialize vertex coordinates of each face
for f = 1:Nf
    face = faces(f, 1:4);
    px(1:4, f) = nodes(face, 1);
    py(1:4, f) = nodes(face, 2);
    pz(1:4, f) = nodes(face, 3);
end

p = patch(px, py, pz, 'r');

if nargout > 0
    varargout = {p};
end


function data = read_tck(filename)

% READ_TCK reads tractography information from an mrtrix-generated .tck
% file. Requires the matlab functions from mrtrix.

tmp  = read_mrtrix_tracks(filename);
npos = cellfun(@numel, tmp.data)./3;
maxnpos = max(npos);
lines   = zeros(2*maxnpos, numel(npos))+nan;
offset  = 0;
for k = 1:numel(npos)
  indx1 = 1:npos(k)*2;
  indx2 = [1:npos(k) npos(k):-1:1]+offset;
  lines(indx1,k) = indx2;
  offset = max(indx2);
end

data.pos  = cat(1, tmp.data{:});
data.line = lines.';
data.hdr  = rmfield(tmp, 'data');


function [sel1, sel2] = match_str(a, b);

% MATCH_STR looks for matching labels in two listst of strings
% and returns the indices into both the 1st and 2nd list of the matches.
% They will be ordered according to the first input argument.
%
% [sel1, sel2] = match_str(strlist1, strlist2)
%
% The strings can be stored as a char matrix or as an vertical array of
% cells, the matching is done for each row.

% Copyright (C) 2000, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
% for the documentation and details.
%
%    FieldTrip 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.
%
%    FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id: match_str.m 2865 2011-02-12 19:24:57Z roboos $

% ensure that both are cell-arrays
if isempty(a)
  a = {};
elseif ~iscell(a)
  a = cellstr(a);
end
if isempty(b)
  b = {};
elseif ~iscell(b)
  b = cellstr(b);
end

% regardless of what optimizations are implemented, the code should remain
% functionally compatible to the original, which is
% for i=1:length(a)
%   for j=1:length(b)
%     if strcmp(a(i),b(j))
%       sel1 = [sel1; i];
%       sel2 = [sel2; j];
%     end
%   end
% end

% ensure that both are column vectors
a = a(:);
b = b(:);
Na = numel(a);
Nb = numel(b);

% replace all unique strings by a unique number and use the fact that
% numeric comparisons are much faster than string comparisons
[dum1, dum2, c] = unique([a; b]);
a = c(1:Na);
b = c((Na+1):end);

sel1 = [];
sel2 = [];
for i=1:length(a)
  % s = find(strcmp(a(i), b));  % for string comparison
  s = find(a(i)==b);            % for numeric comparison
  sel2 = [sel2; s];
  s(:) = i;
  sel1 = [sel1; s];
end


%io_loadlcmdetail.m
%Jamie Near, McGill University 2014.
%
% USAGE:
% [metabs,corrMatrix]=io_loadlcmdetail(filename);
%
% DESCRIPTION:
% This function loads in the "detailed output" of LCModel and returns the
% matrix of metabolite correlation coefficients.
%
% INPUTS:
% filename   = Filename of the lcmodel detailed output file.
%
% OUTPUTS:
% metabs     = A listing of the metabolites included in the correlation
%               coefficients table.
% corrMatrix = A matrix of correlation coefficients between metabolites,
%               with indices specified by the 'metabs' variable.

function [metabs,corrMatrix]=io_loadlcmdetail(filename);

%First open the detailed output file and search for the beginning of the
%Correlation Coefficients table
fid=fopen(filename);
line=fgets(fid);
search_index=findstr(line,'Correlation coefficients');

while isempty(search_index)
    line=fgets(fid);
    search_index=findstr(line,'Correlation coefficients');
end

%Now that we've found the top of the correlation coefficients table, we
%need to record the names of all of the metabolites that are listed in the
%table.  Start by going down two lines to where the metabolite names are
%listed:
line=fgets(fid);
line=fgets(fid);

%Read the first line of metabolite names and then go down one line.  The
%table of correlation coefficients can only fit 17 metabolites per row.
%Therefore if there are more than 17 metabolites to read, then we will have
%to go down another row and read again.  A simple trick to know whether to
%go down another row is to check and see if the first token in the next row
%is equal to the second token from the first row.  If so, then we're done
%reading metabolite names.  If not, then we need to keep reading.
metabstr=line;
[temp,remain]=strtok(line);
secondMetabName=strtok(remain);
line=fgets(fid);

while ~strcmp(strtok(line),secondMetabName)
    metabstr=[metabstr line];
    line=fgets(fid);
end

%We are now finished reading all of the metabolite names.  Now make a cell
%array containing the names of all of the meatbolites.  Note that at this
%point in the code, the final element in metabs will be a blank space,
%since the last metabolite name is missing from the top row of the correlation
%coefficient table in the LCModel detailed output.
remain=metabstr;
n=1;
while ~isempty(remain)
    [metabs{n},remain]=strtok(remain);
    n=n+1;
end

%Now calculate the total number of metabolites, and initialize the
%correlation coefficient matrix:
numMetabs=numel(metabs);
corrMatrix=zeros(numMetabs);
fillLine=1;

%Now start populating the correlation coefficient matrix.  The first token
%of each line is a metabolite name, so we have to remove it.  The rest of
%the line is a row in the correlation matrix.  The first 17 lines are
%simple to read becuase they all have 17 elements or less, so they don't
%wrap around the page (remember there are only 17 columns in the page).  NOTE:
%The correlation matrix is symmetric about the diagonal, but we start by only
%constructing the lower half of the diagonal (The upper half will be all
%zeros).  The diagonal elements themselves will be set to 0.5.  Then later,
%the full matrix, will be completed by adding the matrix to it's transpose,
%thus completing the symmetry, and making all of the diagonals ones.  We
%will also rename "metabs" as we go, becuase the full names of each
%metabolite are given with greater precision on the left column of the
%table in the lcmodel detailed output (compared to the top row of the table).
corrMatrix(1,1)=0.5;
if numMetabs>17
    upto=18;
else
    upto=numMetabs;
end
while fillLine<upto
    [name,corrs]=strtok(line);
    metabs{fillLine+1}=name;
    corrs=[corrs(1:end-1) ' 0.5'];
    corrMatrix(fillLine+1,1:length(str2num(corrs)))=str2num(corrs);
    line=fgets(fid);
    fillLine=fillLine+1;
end

line=fgets(fid);

%For the 18th to 34th lines, there is one line of 'wrap around'.
if numMetabs>34
    upto=35;
else
    upto=numMetabs;
end
while fillLine<upto
    [name,corrs]=strtok(line);
    metabs{fillLine+1}=name;
    line=fgets(fid);
    corrs=[corrs(1:end-1) ' ' line(1:end-1) ' 0.5'];
    corrMatrix(fillLine+1,1:length(str2num(corrs)))=str2num(corrs);
    line=fgets(fid);
    fillLine=fillLine+1;
end

%For the 35th to 52nd lines, there are two lines of 'wrap around'.  Assume
%that there will be no more than 52 elemens in the correlation matrix.
while fillLine<numMetabs
    [name,corrs]=strtok(line);
    metabs{fillLine+1}=name;
    line1=fgets(fid);
    line2=fgets(fid);
    corrs=[corrs(1:end-1) ' ' line1(1:end-1) ' ' line2(1:end-1) ' 0.5'];
    size(str2num(corrs));
    corrMatrix(fillLine+1,1:length(str2num(corrs)))=str2num(corrs);
    line=fgets(fid);
    fillLine=fillLine+1;
end
metabs{end}=name;

%Now add the correlation matrix (the lower have of which is currently only
%populated) to its transpose to complete the symmetry.
corrMatrix=corrMatrix+corrMatrix';

fclose(fid);

% imagesc(corrMatrix);
% impixelinfo;


function test_bug1409

% MEM 2gb
% WALLTIME 00:10:00
% DEPENDENCY ft_selectdata

% the issue is that ft_selectdata_new removes the dimord

data = [];
data.trial{1} = randn(2,100);
data.time{1}  = [0:99]./100;
data.label    = {'chan1';'chan2'};

data = ft_checkdata(data, 'datatype', 'timelock');

cfg = [];
cfg.channel = 'chan1';
data2 = ft_selectdata(cfg, data);

assert(isfield(data2,'dimord'));


function g
%here is how one creates a function ("callback") which does something
%(prints the node label) when you click on the node's text in Matlab figure.
%
% Leon Peshkin  http://www.ai.mit.edu/~pesha
%
%draw_graph(...)

     % "gca" is the current "axes" object, parent of all objects in figure
     % "gcbo" is the handle of the object whose callback is being executed
     % "findall" gives handles to all elements of a given type in the figure
text_elms = findall(gca,'Type','text');
for ndx = 1:length(text_elms)
  callbk = 'my_call(str2num(get(gcbo,''String'')))';
  set(text_elms(ndx), 'ButtonDownFcn', callbk);  % assume the node label is a number
end


% pop_plotdata() - Plot average of EEG channels or independent components in
%                  a rectangular array. Else, (over)plot single trials.
% Usage:
%   >> avg = pop_plotdata(EEG, typeplot, indices, trials, title, singletrials, ydir, ylimits);
%
% Inputs:
%   EEG        - Input dataset
%   typeplot   - Type data to plot (1=channels, 0=components) {Default:1}
%   indices    - Array of channels (or component) indices to plot
%                     {Default: all}
%   trials     - Array of trial indices. sum specific trials in the average
%                     {Default: all}
%   title      - Plot title. {Default: []}.
%   singletrials - [0|1], Plot average or overplot single trials
%                      0 plot average, 1 plot single trials {Default: 0}
%   ydir       - [1|-1] y-axis polarity (pos-up = 1; neg-up = -1)
%                {def command line-> pos-up; def GUI-> neg-up}
%   ylimits    - [ymin ymax] plotting limits {default [0 0] -> data range}
%
% Outputs:
%   avg        - [matrix] Data average
%
% Author: Arnaud Delorme, CNL / Salk Institute, 2001
%
% See also: plotdata(), eeglab()

% Copyright (C) 2001 Arnaud Delorme, Salk Institute, arno@salk.edu
%
% 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 2 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, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

% 01-25-02 reformated help & license -ad
% 03-08-02 add eeglab options -ad
% 03-18-02 added title -ad & sm
% 03-30-02 added single trial capacities -ad

function [sigtmp, com] = pop_plotdata(EEG, typeplot, indices, trials, plottitle, singletrials, ydir,ylimits);

ylimits = [0 0];% default ylimits = data range

% warning signal must be in a 3D form before averaging

sigtmp = [];

com = '';
if nargin < 1
   help pop_plotdata;
   return;
end;
if nargin < 2
	typeplot = 1; % 1=signal; 0=component
end;
if exist('plottitle') ~= 1
	plottitle = '';
end;

if nargin <3
	if typeplot % plot signal channels
		result = inputdlg2({  'Channel number(s):'  ...
                                      'Plot title:'  ...
                                      'Vertical limits ([0 0]-> data range):' ...
                                   }, ...
                                  'Channel ERPs in rect. array -- pop_plotdata()', 1, ...
                                   {['1:' int2str(EEG.nbchan)] [fastif(isempty(EEG.setname), '',[EEG.setname ' ERP'])] ['0 0'] }, ...
                                   'pop_plotdata' );
	else % plot components
		result = inputdlg2({  'Component number(s):' ...
                                      'Plot title:' ...
                                      'Vertical limits ([0 0]-> data range):' ...
                                   }, ...
                                   'Component ERPs in rect. array -- pop_plotdata()', 1, ...
                                   {   ['1:' int2str(size(EEG.icawinv,2))] [fastif(isempty(EEG.setname), '',[EEG.setname ' ERP'])] ['0 0'] }, ...
                                   'pop_plotdata' );
	end;
	if length(result) == 0 return; end;

	indices   	 = eval( [ '[' result{1} ']' ] );

	plottitle    = result{2};

	singletrials = 0;

	ylimits   	 = eval( [ '[' result{3} ']' ] );
        if length(ylimits) ~= 2
            ylimits = [0 0]; % use default if 2 values not given
        end
end;
if ~(exist('trials') == 1)
	trials = 1:EEG.trials;
end;
if exist('plottitle') ~= 1
    plottitle = '';
end;
if exist('singletrials') ~= 1
    singletrials = 0;
end;

if EEG.trials > 1 & singletrials == 0
    fprintf('Selecting trials and components...\n');
	if typeplot == 1
	   sigtmp = nan_mean(EEG.data(indices,:,trials),3);
 	else
	   if isempty(EEG.icasphere)
	      error('no ICA data for this set, first run ICA');
	   end;
       tmpdata = eeg_getdatact(EEG, 'component', indices, 'trialindices', trials);
	   fprintf('Averaging...\n');
	   sigtmp = nan_mean(tmpdata,3);
	end;
else
	if typeplot == 1
	   sigtmp = EEG.data(indices,:,trials);
	else
	   if isempty(EEG.icasphere)
	      error('no ICA data for this set, first run ICA');
	   end;
       sigtmp = eeg_getdatact(EEG, 'component', indices, 'trialindices', trials);
	end;
end;
figure;
try, icadefs; set(gcf, 'color', BACKCOLOR); catch, end;
if exist('YDIR') ~= 1
     ydir = 1;
else
     ydir = YDIR;
end

%
%%%%%%%%%%%%%%%%%%%%%%%%%% make the plot %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
sigtmp = reshape( sigtmp, size(sigtmp,1),  size(sigtmp,2)*size(sigtmp,3));
ymin = ylimits(1);
ymax = ylimits(2);

if ischar(ymin)
   ymin = str2num(ymin);
   ymax = str2num(ymax);
end

% com = sprintf('pop_plotdata(%s, %d, %s, [1:%d], ''%s'', %d, %d, [%f %f]);', ...
% inputname(1), typeplot, vararg2str(indices), EEG.trials, plottitle, singletrials,ydir,ymin,ymax);
% fprintf([com '\n']);
if ~isempty(EEG.chanlocs) && typeplot == 1
    tmpchanlocs = EEG.chanlocs;
    chanlabels = strvcat({ tmpchanlocs(indices).labels });
else
    chanlabels = num2str(indices(:));
end;
plottopo( sigtmp, 'frames', EEG.pnts, 'limits', [EEG.xmin*1000 EEG.xmax*1000 ymin ymax], 'title', plottitle, 'chans', 1:size(sigtmp,1), 'ydir', ydir, 'channames', chanlabels);
%plotdata(sigtmp, EEG.pnts, [EEG.xmin*1000 EEG.xmax*1000 ymin ymax], plottitle, indices,0,0,ydir);

%
%%%%%%%%%%%%%%%%%%%%%%%%%% add figure title %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
if typeplot == 1
	set(gcf, 'name', 'Plot > Channel ERPs > In rect. array -- plotdata()');
else
	set(gcf, 'name', 'Plot > Component ERPs > In rect. array -- plotdata()');
end;
%
%%%%%%%%%%%%%%%%%%%%%%%%%% set y-axis direction %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
if exist('ydir') ~= 1
  if exist('YDIR') ~= 1
    ydir = 1;  % default positive up
  else
    ydir = YDIR; % icadefs.m system-wide default
  end
end

if ydir==1
   set(gca,'ydir','normal');
else % ydir == -1
   set(gca,'ydir','reverse');
end

switch nargin
	case {0, 1, 2, 3}, com = sprintf('pop_plotdata(%s, %d, %s, [1:%d], ''%s'', %d, %d, [%g %g]);', inputname(1), typeplot, vararg2str(indices), EEG.trials, plottitle, singletrials,ydir,ymin,ymax);
	case 4, com = sprintf('pop_plotdata(%s, %d, %s, %s, ''%s'', %d, %d, [%g %g]);', inputname(1), typeplot, vararg2str(indices), vararg2str(trials), plottitle, singletrials,ydir,ymin,ymax);
end;

fprintf([com '\n']);
return;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function out = nan_mean(in, dim)
    tmpin = in;
    tmpin(find(isnan(in(:)))) = 0;
    out = sum(tmpin, dim) ./ sum(~isnan(in),dim);


% BIOSIG2EEGLAB - convert BIOSIG structure to EEGLAB structure
%
% Usage:
%   >> OUTEEG = pop_biosig2eeglab(hdr, data, interval);
%
% Inputs:
%   hdr   - BIOSIG header
%   data  - BIOSIG data array
%
% Optional input:
%   interval - BIOSIG does not remove event which are outside of
%              the data range when importing data range subsets. This
%              parameter helps fix this problem.
%
% Outputs:
%   OUTEEG   - EEGLAB data structure
%
% Author: Arnaud Delorme, SCCN, INC, UCSD, Oct. 29, 2009-
%
% Note: BIOSIG toolbox must be installed. Download BIOSIG at
%       http://biosig.sourceforge.net
%       Contact a.schloegl@ieee.org for troubleshooting using BIOSIG.

% Copyright (C) 2003 Arnaud Delorme, SCCN, INC, UCSD, arno@salk.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function EEG = biosig2eeglab(dat, DAT, interval, channels, importevent, importannot)

if nargin < 2
    help biosig2eeglab;
    return;
end
if nargin < 3
    interval = [];
end
if nargin < 4
    channels = [];
end
if nargin < 5
    importevent = 0;
end
if nargin < 6
    importannot = 0;
end

% import data
% -----------
if abs(dat.NS - size(DAT,1)) > 1
    DAT = DAT';
end
EEG = eeg_emptyset;

% convert to seconds for sread
% ----------------------------
if ~isfield(dat, 'InChanSelect') && max(dat.InChanSelect) > size(DAT,1)
    dat.InChanSelect = [1:size(DAT,1)];
end
EEG.nbchan          = length(dat.InChanSelect); %= size(DAT,1);
EEG.srate           = dat.SampleRate(1);
EEG.data            = DAT(dat.InChanSelect,:);  %DAT
clear DAT;
% try  % why would you do the following???????  JO
%     EEG.data            = EEG.data';
% catch,
%     pack;
%     EEG.data            = EEG.data';
% end;
EEG.setname 		= sprintf('%s file', dat.TYPE);
EEG.comments        = [ 'Original file: ' dat.FileName ];
EEG.xmin            = 0;
nepoch              = dat.NRec;
EEG.trials   = nepoch;
EEG.pnts     = size(EEG.data,2)/nepoch;

if isfield(dat,'T0')
    EEG.etc.T0 = dat.T0; % added sjo
end

if isfield(dat, 'Label') && ~isempty(dat.Label)
    if ischar(dat.Label)
        EEG.chanlocs = struct('labels', cellstr(char(dat.Label(dat.InChanSelect)))); % 5/8/2104 insert (dat.InChanSelect) Ramon
    else
        % EEG.chanlocs = struct('labels', dat.Label(1:min(length(dat.Label), size(EEG.data,1))));
        EEG.chanlocs = struct('labels', dat.Label(dat.InChanSelect)); % sjo added 120907 to avoid error below % 5/8/2104 insert (dat.InChanSelect) Ramon
    end
    if length(EEG.chanlocs) > EEG.nbchan, EEG.chanlocs = EEG.chanlocs(1:EEG.nbchan); end

    % check BIOSEMI channel types
    if strcmpi(dat.TYPE, 'BDF')
        bdftype = { 'EXG1'  'EXG2'  'EXG3'  'EXG4'  'EXG5'  'EXG6'  'EXG7'  'EXG8'  'GSR1'  'GSR2'  'Erg1'  'Erg2'  'Resp'  'Plet'  'Temp';
                    'MISC'  'MISC'  'MISC'  'MISC'  'MISC'  'MISC'  'MISC'  'MISC'  'GSR'   'GSR'   'MISC'  'MISC'  'RESP'  'PPG'   'TEMP' };
        for iChan = 1:length(EEG.chanlocs)
            matchChan = strmatch(EEG.chanlocs(iChan).labels, bdftype(1,:), 'exact');
            if ~isempty(matchChan)
                EEG.chanlocs(iChan).type = bdftype{2,matchChan};
            else
                EEG.chanlocs(iChan).type = 'EEG';
            end
        end
    end
end
EEG = eeg_checkset(EEG);

% extract events % this part I totally revamped to work...  JO
% --------------
EEG.event = [];

% startval = mode(EEG.data(end,:)); % my code
% for p = 2:size(EEG.data,2)-1
%     [codeout] = code(EEG.data(end,p));
%     if EEG.data(end,p) > EEG.data(end,p-1) & EEG.data(end,p) >= EEG.data(end,p+1)
%         EEG.event(end+1).latency =  p;
%         EEG.event(end).type = bitand(double(EEG.data(end,p)-startval),255);
%     end
% end

% lastout = mod(EEG.data(end,1),256);newevs = []; % andrey's code 8 bits
% codeout = mod(EEG.data(end,2),256);
% for p = 2:size(EEG.data,2)-1
%     nextcode = mod(EEG.data(end,p+1),256);
%     if codeout > lastout & codeout >= nextcode
%         newevs = [newevs codeout];
%         EEG.event(end+1).latency =  p;
%         EEG.event(end).type = codeout;
%     end
%     lastout = codeout;
%     codeout = nextcode;
% end

%lastout = mod(EEG.data(end,1),256*256);newevs = []; % andrey's code 16 bits
%codeout = mod(EEG.data(end,2),256*256);
%for p = 2:size(EEG.data,2)-1
%    nextcode = mod(EEG.data(end,p+1),256*256);
%    if (codeout > lastout) & (codeout >= nextcode)
%        newevs = [newevs codeout];
%        EEG.event(end+1).latency =  p;
%        EEG.event(end).type = codeout;
%    end
%    lastout = codeout;
%    codeout = nextcode;
%end

% if strcmp(dat.TYPE,'EDF') % sjo added 120907
%     disp('filetype EDF does not support events');
%     importevent = 0;
% end

if importevent
    if isfield(dat, 'BDF')
        if dat.BDF.Status.Channel <= size(EEG.data,1)
            EEG.data(dat.BDF.Status.Channel,:) = [];
        end
        EEG.nbchan = size(EEG.data,1);
        if ~isempty(EEG.chanlocs) && dat.BDF.Status.Channel <= length(EEG.chanlocs)
            EEG.chanlocs(dat.BDF.Status.Channel,:) = [];
        end
    elseif isempty(dat.EVENT.POS)
        disp('Extracting events from last EEG channel...');
        %Modifieded by Andrey (Aug.5,2008) to detect all non-zero codes:
        if length(unique(EEG.data(end, 1:100))) > 20
            disp('Warning: event extraction failure, the last channel contains data');
        elseif length(unique(EEG.data(end, :))) > 1000
            disp('Warning: event extraction failure, the last channel contains data');
        else
            thiscode = 0;
            for p = 1:size(EEG.data,2)*size(EEG.data,3)-1
                prevcode = thiscode;
                thiscode = mod(EEG.data(end,p),256*256);   % andrey's code - 16 bits
                if (thiscode ~= 0) && (thiscode~=prevcode)
                    EEG.event(end+1).latency =  p;
                    EEG.event(end).type = thiscode;
                end
            end
            EEG.data(end,:) = [];
            EEG.chanlocs(end) = [];
        end
        % recreate the epoch field if necessary
        % -------------------------------------
        if EEG.trials > 1
            for i = 1:length(EEG.event)
                EEG.event(i).epoch = ceil(EEG.event(i).latency/EEG.pnts);
            end
        end
        EEG = eeg_checkset(EEG, 'eventconsistency');
    end

    if ~isempty(dat.EVENT.POS)
        if isfield(dat, 'out') % Alois fix for event interval does not work
            if isfield(dat.out, 'EVENT')
                dat.EVENT = dat.out.EVENT;
            end
        end
        EEG.event = biosig2eeglabevent(dat.EVENT, interval, importannot); % Toby's fix

        % recreate the epoch field if necessary
        % -------------------------------------
        if EEG.trials > 1
            for i = 1:length(EEG.event)
                EEG.event(i).epoch = ceil(EEG.event(i).latency/EEG.pnts);
            end
        end

        EEG = eeg_checkset(EEG, 'eventconsistency');
    elseif isempty(EEG.event)
        disp('Warning: no event found. Events might be embedded in a data channel.');
        disp('         To extract events, use menu File > Import Event Info > From data channel');
    end
end

% convert data to single if necessary
% -----------------------------------
EEG = eeg_checkset(EEG,'makeur');   % Make EEG.urevent field
EEG = eeg_checkset(EEG);


function [trl, event] = ft_trialfun_hed(cfg)

% FT_TRIALFUN_HED is a trial function that can be used with HED tags. It demonstrates
% some basic functionality for selecting specific events, but mainly serves as an
% example or template for your own conditial trial definitions. For that you would
% copy this function and giuve it your own name, e.g. FT_TRIALFUN_MYEXPERIMENT.
%
% Use this function by calling
%   [cfg] = ft_definetrial(cfg)
% where the configuration structure should contain
%   cfg.dataset           = string with the filename
%   cfg.trialfun          = 'ft_trialfun_hed' % or your own copy
%
% The selection of events and timing of the epochs is specified with
%   cfg.trialdef.regexp     = regular expression that is applied to the HED tags
%   cfg.trialdef.prestim    = number, in seconds
%   cfg.trialdef.poststim   = number, in seconds
%
% See also FT_DEFINETRIAL, FT_TRIALFUN_GENERAL, FT_TRIALFUN_EXAMPLE1,
% FT_TRIALFUN_EXAMPLE2

% read the header information
hdr = ft_read_header(cfg.dataset);

% determine the number of samples before and after the event
prestim  = -round(cfg.trialdef.prestim  * hdr.Fs);
poststim =  round(cfg.trialdef.poststim * hdr.Fs);

% rather than using FT_READ_EVENT which has a limited representation, we assume that
% the data is formatted according to BIDS and includes HED tags

tsvfile  = bids_sidecar(cfg.dataset, 'events', 'tsv');
jsonfile = bids_sidecar(cfg.dataset, 'events', 'json');

tsv = ft_read_tsv(tsvfile);
if ~isempty(jsonfile)
  json = ft_read_json(jsonfile);
else
  json = [];
end

% this will be returned as the complete set of events without selection
event = tsv;

% make a selection based on the HED tags
if any(strcmp(fieldnames(tsv), 'HED'))
  hedtags = tsv.HED;
elseif ~isempty(jsonfile)
  warning('assembling the HED tags on the fly');
  % this requires functions from an external toolbox, see https://hed-examples.readthedocs.io/en/latest/HedMatlabTools.html
  ft_hastoolbox('hedtools', 1);
  tsv = hed_assemble(tsvfile, jsonfile);
  hedtags = tsv.HED;
else
  error('there are no HED tags')
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the following code uses a regular expression to select events
% you may want to change this and use another mechanism to define the trials/epochs
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% find the HED tags that match the regular expression
sel = find(~cellfun(@isempty, regexp(hedtags, cfg.trialdef.regexp)));

% these are the first three required columnss
trl = table;
trl.begsample = tsv.sample(sel) + prestim;
trl.endsample = tsv.sample(sel) + poststim - 1;
trl.offset = repmat(prestim, size(trl.begsample));

% concatenate all other details to the table
trl = cat(2, trl, tsv(sel,:));


function [C, log] = SOFAmerge(A,B)
%SOFAmerge
%   [C, log] = SOFAmerge(A,B) merges the SOFA objects A and B to a single one, C.
%
%   A and B are structs containing the data and meta. A and B
%   must be of the same SOFA conventions.
%
%   C is a struct containing merged data
%   log contains log data

% SOFA API - function SOFAupdateDimensions
% Copyright (C) 2012-2013 Acoustics Research Institute - Austrian Academy of Sciences
% Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "License")
% You may not use this work except in compliance with the License.
% You may obtain a copy of the License at: http://joinup.ec.europa.eu/software/page/eupl
% Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing  permissions and limitations under the License.

%% Initial check
if ~strcmp(A.GLOBAL_SOFAConventions,B.GLOBAL_SOFAConventions)
	error('Both SOFA objects must use the same SOFA conventions');
end
if A.API.N~=B.API.N
	error('Data size of both SOFA objects must be the same');
end
Def = SOFAdefinitions;
log={''};
OC = SOFAgetConventions(A.GLOBAL_SOFAConventions,'a');

%% Update dimensions
A=SOFAupdateDimensions(A);
B=SOFAupdateDimensions(B);

%% create field names which have to be checked
C=rmfield(B,{'API','Data'});
if isfield(C,'PRIVATE'), C=rmfield(C,'PRIVATE'); end
Bf=fieldnames(C);

%% Copy and merge metadata variables
C=A;
for ii=1:size(Bf,1)
	if ~isfield(A,Bf{ii})
		C.(Bf{ii}) = B.(Bf{ii});	% field in B but not in A. Simple copy.
	else	% here we have a potential conflict and have to merge
		if strfind(Bf{ii},'_')	% is it an attribute?
			if strcmp(A.(Bf{ii}),B.(Bf{ii})),
				C.(Bf{ii}) = B.(Bf{ii});	% content the same, no conflict
			else
				switch Bf{ii}
					case 'GLOBAL_TimeCreated'	% use the oldest date
						dateNew=datenum(A.GLOBAL_TimeCreated,Def.dateFormat);
						if datenum(B.GLOBAL_TimeCreated,Def.dateFormat)<dateNew, dateNew=datenum(B.GLOBAL_TimeCreated,Def.dateFormat); end;
						C.(Bf{ii}) = datestr(dateNew,Def.dateFormat);
						log{end+1}=[Bf{ii} ' set to ' C.(Bf{ii})];
					case 'GLOBAL_TimeModified' % now
						C.(Bf{ii}) = datestr(now,Def.dateFormat);
						log{end+1}=[Bf{ii} ' updated'];
					otherwise
						C.(Bf{ii}) = [A.(Bf{ii}) '; ' B.(Bf{ii})]; % concatate [A; B]
						log{end+1}=[Bf{ii} ' merged'];
				end
			end
		else	% a variable
			if isfield(OC.API.Dimensions, Bf{ii})	% is a known variable?
				AExp=SOFAexpand(A,Bf{ii});
				BExp=SOFAexpand(B,Bf{ii});
				dim=strfind(AExp.API.Dimensions.(Bf{ii}),'M');
				if isempty(dim),
					error([Bf{ii} ' can not be merged because it does not depend on M']);
				end
				C.(Bf{ii})=cat(dim,AExp.(Bf{ii}),BExp.(Bf{ii}));
				log{end+1}=[Bf{ii} ' expanded and merged'];
			else	% user-defined variable, dimensions must be stated
				if ~isfield(A.API.Dimensions, Bf{ii})
					error(['Dimension missing for ' Bf{ii} ' in A.']); end
				if ~isfield(B.API.Dimensions, Bf{ii})
					error(['Dimension missing for ' Bf{ii} ' in B.']); end
				dim=strfind(A.API.Dimensions.(Bf{ii}),'M');
				if ~isempty(dim),
          C.(Bf{ii})=cat(dim,A.(Bf{ii}),B.(Bf{ii})); % depends on M, merge
          log{end+1}=[Bf{ii} ' merged'];
        else  % not M-dependend, must be identical in A and B
          if prod(A.(Bf{ii})==B.(Bf{ii}))==1,
            C.(Bf{ii})=A.(Bf{ii});
            log{end+1}=[Bf{ii} ' identical'];
          else
            error([Bf{ii} ' must depend on M or be equal in both objects.']);
          end
        end
			end
		end
	end
end

%% Copy and merge Data variables
Bf=fieldnames(B.Data);
for ii=1:size(Bf,1)
	if ~isfield(A.Data,Bf{ii})
		C.Data.(Bf{ii}) = B.Data.(Bf{ii});	% field in B but not in A. Simple copy.
	else	% here we have a potential conflict and have to merge
		if strfind(Bf{ii},'_')	% is it an attribute?
			if strcmp(A.Data.(Bf{ii}),B.Data.(Bf{ii})),
				C.Data.(Bf{ii}) = B.Data.(Bf{ii});	% content the same, no conflict
			else
				C.Data.(Bf{ii}) = [A.Data.(Bf{ii}) '; ' B.Data.(Bf{ii})]; % concatate [A; B]
				log{end+1}=['Data.' Bf{ii} ' merged'];
			end
		else	% a variable in Data
			if isfield(OC.API.Dimensions.Data,Bf{ii})	% is a known variable?
				dim=strfind(A.API.Dimensions.Data.(Bf{ii}),'M'); % is a matrix
				if ~isempty(dim),
          C.Data.(Bf{ii})=cat(dim,A.Data.(Bf{ii}),B.Data.(Bf{ii})); % depends on M, cat
          log{end+1}=['Data.' Bf{ii} ' merged'];
        else  % not M-dependend, must be identical in A and B
          if all(A.Data.(Bf{ii})==B.Data.(Bf{ii}))==1,
            C.Data.(Bf{ii})=A.Data.(Bf{ii});
            log{end+1}=['Data.' Bf{ii} ' identical'];
          else
            error(['Data.' Bf{ii} ' must depend on M or be equal in both objects.']);
          end
        end
			else	% user-defined variable, dimensions must be stated
				if ~isfield(A.API.Dimensions.Data, Bf{ii})
					error(['Dimension missing for Data.' Bf{ii} ' in A.']); end
				if ~isfield(B.API.Dimensions.Data, Bf{ii})
					error(['Dimension missing for Data.' Bf{ii} ' in B.']); end
				dim=strfind(A.API.Dimensions.Data.(Bf{ii}),'M');
				if ~isempty(dim),
          C.Data.(Bf{ii})=cat(dim,A.Data.(Bf{ii}),B.Data.(Bf{ii})); % depends on M, cat
          log{end+1}=['Data.' Bf{ii} ' merged'];
        else  % not M-dependend, must be identical in A and B
          if prod(A.Data.(Bf{ii})==B.Data.(Bf{ii}))==1,
            C.Data.(Bf{ii})=A.Data.(Bf{ii});
            log{end+1}=['Data.' Bf{ii} ' identical'];
          else
            error(['Data.' Bf{ii} ' must depend on M or be equal in both objects.']);
          end
        end
			end
		end
	end
end

%% Update the new dimensions and finish
C=SOFAcompact(C);
C=SOFAupdateDimensions(C);
if length(log)>1, log=log(2:end); else log={}; end;

%% Get the sizes of the dimension variables according the dimension variables in str
function vec=getdim(Obj,str)
	vec=arrayfun(@(f)(Obj.API.(f)),upper(str));


%% QUAD_FSU uses the fsuClusterMatlab command to run the QUAD computation.
%
%  Discussion:
%
%    The arguments to fsuClusterMatlab mean:
%
%    [] allows us to specify an output directory;
%    [] allows us to specify switches to the MOAB queue handler, such as more time.
%    'm" indicates this is an "MPI-like" job;
%    'w' indicates that the MATLAB session should wait for completion;
%    4 is the number of processors requested;
%    @quad_fun indicates the function we want to execute;
%    {} would contain input arguments if there were any.
%
%    The function "quad_fun" must correspond to a MATLAB M-file "quad_fun.m"
%    and that file must be in MATLAB's path.
%
%    The easiest way to ensure that the file is in MATLAB's path is to
%    create a subdirectory called "matlab" immediately below your home directory,
%    and place the function file there (as well as any other files needed by the job).
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    26 March 2010
%
%  Author:
%
%    John Burkardt
%
  fsuClusterMatlab ( [], [], 'm', 'w', 4, @quad_fun, { 100000 } )


function [tParam, regParam] = histology_intraframe_reg(pathstr, files, opts)
% histology_intraframe_reg  Registration diffusion of histology slices.
%
% histology_intraframe_reg computes the registration of each slices to the
% two adjacent slices. The two resulting transforms are combined to
% produce a compromise transform. The process is applied iteratively.
%
% [TPARAM, REGPARAM] = histology_intraframe_reg(PATHSTR, FILES)
%
%   PATHSTR is the full path to the files. If empty, PATHSTR='.'.
%
%   FILES is the result of a dir() command, e.g. dir('*.png'). The
%   function expects a list of histology images.
%
%   TPARAM is an array of structs with the transforms for each image, where
%   TPARAM(I) is the nested transform for the I-th image. Note that if
%   ta is a series of nested transforms, e.g.
%
%     ta.Transform = 'EulerTransform';
%     ta.InitialTransformParametersFileName = tb;
%
%     tb.Transform = 'BSplineTransform';
%     tb.InitialTransformParametersFileName = 'NoInitialTransform';
%
%   tb is the "initial transform" of ta, but because ITK and elastix define
%   the transform of an image in the inverse direction, in practice what
%   happens is that ta is applied *first* to the image, and then tb is
%   applied to the result.
%
% ... = histology_intraframe_reg(..., OPTS)
%
%   OPTS is a struct with algorithm parameters:
%
%     'MaxIter':   (def 10) Maximum number of diffusion iterations.
%
%     'DiffusionCoefficient': (def 0.4) Value of diffusion coefficient.
%
%     'verbose':   (default 0) Show elastix output on the screen.
%
%     't0':        (default []) Initial transform in elastix format for the
%                  images (typically rigid transforms to provide an initial
%                  rough alignment).
%
% See also: histology_preprocessing.

% Author: Ramon Casero <rcasero@gmail.com>
% Copyright © 2014 University of Oxford
% Version: 0.2.2
%
% 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
narginchk(2, 3);
nargoutchk(0, 2);

if (length(files) < 3)
    error('At least three files are needed for registration')
end

% defaults
if (nargin < 3 || isempty(opts) || ~isfield(opts, 'MaxIter'))
    opts.MaxIter = 10;
end
if (isempty(opts) || ~isfield(opts, 'DiffusionCoefficient'))
    opts.DiffusionCoefficient = 0.4;
end
if (isempty(opts) || ~isfield(opts, 'verbose'))
    opts.verbose = 0;
end
if (isempty(opts) || ~isfield(opts, 't0'))
    opts.t0 = [];
end
if (isempty(pathstr))
    pathstr = '.';
end

if (~isempty(opts.t0) && (length(opts.t0) ~= length(files)))
    error('If opts.t0 initial transforms are provided, there must be one per image')
end

% create a struct with the registration parameters
regParam = generate_registration_parameters();

% create a duplicate of the images that we will deform repeteadly in the
% diffusion process
pathtemp = tempname;
mkdir(pathtemp);
for I = 1:length(files)
    copyfile([pathstr filesep files(I).name], ...
        [pathtemp filesep files(I).name]);
end

% diffusion computation loop
for S = 1:opts.MaxIter

    % initial transform to apply to images before registration in this
    % iteration
    if (S==1)

        t0 = opts.t0;

    else

        t0 = tParam;

    end

    % compute registration diffusion for the first slice of the histology
    % volume
    tic
    disp(['S = ' num2str(S) '/' num2str(opts.MaxIter) ...
        ', I = ' num2str(1) '/' num2str(length(files))])
    tParam(1) = process_extreme_slices(...
        [pathstr filesep files(2).name], ... % fixed
        [pathstr filesep files(1).name], ... % moving
        t0(2), t0(1), regParam, opts);
    disp(['time = ' num2str(toc) 'sec'])

    % loop for all the other slices
    for I = 2:length(files)-1

        tic
        disp(['S = ' num2str(S) '/' num2str(opts.MaxIter) ...
            ', I = ' num2str(I) '/' num2str(length(files))])

        tParam(I) = process_intermediate_slices(...
            [pathstr filesep files(I-1).name], ... % fixed a
            [pathstr filesep files(I).name], ...   % moving
            [pathstr filesep files(I+1).name], ... % fixed b
            t0(I-1), t0(I), t0(I+1), regParam, opts);
        disp(['time = ' num2str(toc) 'sec'])

    end

    % compute registration diffusion for the last slice of the histology
    % volume
    N = length(files);
    tic
    disp(['S = ' num2str(S) '/' num2str(opts.MaxIter) ...
        ', I = ' num2str(length(files)) '/' num2str(length(files))])
    tParam(N) = process_extreme_slices(...
        [pathstr filesep files(N-1).name], ... % fixed
        [pathstr filesep files(N).name], ...   % moving
        t0(N-1), t0(N), regParam, opts);
    disp(['time = ' num2str(toc) 'sec'])

end

% delete temp files
rmdir(pathtemp, 's')

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% generate_registration_parameters()
%
%   Create a struct with the parameters necessary for registration
function regParam = generate_registration_parameters()

regParam.FixedInternalImagePixelType = 'float';
regParam.FixedImageDimension = 2;
regParam.MovingInternalImagePixelType = 'float';
regParam.MovingImageDimension = 2;
regParam.Registration = 'MultiMetricMultiResolutionRegistration';
regParam.FixedImagePyramid = {'FixedSmoothingImagePyramid', ...
    'FixedSmoothingImagePyramid', 'FixedSmoothingImagePyramid'};
regParam.MovingImagePyramid = {'MovingSmoothingImagePyramid', ...
    'MovingSmoothingImagePyramid', 'MovingSmoothingImagePyramid'};
regParam.Interpolator = {'BSplineInterpolator', 'BSplineInterpolator', ...
    'BSplineInterpolator'};
regParam.Metric = {'AdvancedMeanSquares', 'AdvancedMeanSquares', ...
    'AdvancedMeanSquares'};
regParam.Optimizer = 'RegularStepGradientDescent';
regParam.ResampleInterpolator = 'FinalBSplineInterpolator';
regParam.Resampler = 'DefaultResampler';
regParam.Transform = 'BSplineTransform';
regParam.BSplineTransformSplineOrder = 3;
regParam.UseCyclicTransform = 'false';
regParam.AutomaticTransformInitialization = 'false';
regParam.AutomaticTransformInitializationMethod = 'GeometricalCenter';
regParam.UseFastAndLowMemoryVersion = 'true';
regParam.UseJacobianPreconditioning = 'false';
regParam.FiniteDifferenceDerivative = 'false';
regParam.MaximumNumberOfSamplingAttempts = 10;
regParam.ErodeMask = 'false';
regParam.NumberOfResolutions = 3;
regParam.ImagePyramidSchedule = [16 16 4 4 1 1];
regParam.FinalGridSpacingInVoxels = 32;
regParam.GridSpacingSchedule = [16 4 1];
regParam.HowToCombineTransforms = 'Compose';
regParam.UseDirectionCosines = 'true';
regParam.MaximumNumberOfIterations = 400;
regParam.MaximumStepLength = 50;
regParam.MinimumStepLength = 0.500;
regParam.MinimumGradientMagnitude = 1e-3;
regParam.NumberOfFixedHistogramBins = 32;
regParam.NumberOfMovingHistogramBins = 32;
regParam.NumberOfHistogramBins = 32;
regParam.FixedLimitRangeRatio = 0;
regParam.MovingLimitRangeRatio = 0;
regParam.FixedKernelBSplineOrder = 3;
regParam.MovingKernelBSplineOrder = 3;
% regParam.ImageSampler = {'RandomSparseMask' 'RandomSparseMask' 'RandomSparseMask'};
regParam.ImageSampler = {'RandomCoordinate' 'RandomCoordinate' 'RandomCoordinate'};
regParam.NewSamplesEveryIteration = 'true';
regParam.NumberOfSpatialSamples = [0 0 0]; % to be set for each slice
regParam.FixedImageBSplineInterpolationOrder = 1;
regParam.UseRandomSampleRegion = 'false';
regParam.BSplineInterpolationOrder = 1;
regParam.FinalBSplineInterpolationOrder = 1;
regParam.DefaultPixelValue = 0;
regParam.WriteResultImage = 'true';
regParam.WriteResultImageAfterEachResolution = 'false';
regParam.WriteTransformParametersEachIteration = 'false';
regParam.WriteTransformParametersEachResolution = 'true';
regParam.ResultImagePixelType = 'unsigned char';
regParam.ResultImageFormat = 'png';
regParam.CompressResultImage = 'true';

end

% process_extreme_slices()
%
%   The first and last slices of the histology volume don't have slices on
%   both sides. This function deals with those two cases (it has to be run
%   twice, once for the first slice, and once for the last slice).
%
%   *M refers to the moving slice, i.e. the first or last histology slice.
%   *F refers to the fixed adjacent slice.
%
%   FILEM, FILEF is the path and filename to the histology images.
%
%   T0M, T0F is the nested (accumulated) transform to apply to each image
%   before registering them.
%
%   REGPARAM is the struct with the registration parameters for elastix.
%
%   OPTS is a struct with optional algorithm parameters.
function t0m = process_extreme_slices(filef, filem, t0f, t0m, regParam, opts)

DEBUG = 0;

% load images
imm = imread(filem);
imf = imread(filef);

% preprocess histograms
[imm, imf] = histology_preprocessing(imm, imf);

% apply accumulated initial transform (make sure that added voxels are
% black, so that they blend in with the background)
t0m.DefaultPixelValue = 0;
t0f.DefaultPixelValue = 0;
imm = transformix(t0m, imm);
imf = transformix(t0f, imf);

% create masks
opts.mMask = uint8(sum(imm, 3) > 0);
opts.fMask = uint8(sum(imf, 3) > 0);

% remove some background noise in the mask
se = strel('disk', 1);
opts.mMask = imerode(opts.mMask, se);
opts.fMask = imerode(opts.fMask, se);

% dilate mask to cover some background
se = strel('disk', 10);
opts.mMask = imdilate(opts.mMask, se);
opts.fMask = imdilate(opts.fMask, se);

% we use 10% of the voxels within the mask
nSamples = nnz(imf) / 10 * regParam.GridSpacingSchedule(end);
regParam.NumberOfSpatialSamples = round(nSamples ./ regParam.GridSpacingSchedule);

% register slices
[tma, immreg, iterinfo] = elastix(regParam, imf, imm, opts);

if (DEBUG)
    subplot(2, 1, 1)
    hold off
    imshowpair(imf, imm)
    subplot(2, 1, 2)
    hold off
    imshowpair(imf, immreg)
end

% create a new combined transform as a linear combination of the two
% computed transforms, and apply diffusion coefficient (the new transform
% has to go at the back of the list so that it is applied last)
tma.TransformParameters ...
    = 2 * tma.TransformParameters * opts.DiffusionCoefficient;
str = 't0m';
while (isstruct(eval([str '.InitialTransformParametersFileName'])))
    str = [str '.InitialTransformParametersFileName'];
end
eval([str '.InitialTransformParametersFileName = tma;']);

end

% process_intermediate_slices()
%
%   All slices of the histology volume except the first and last have
%   slices on both sides. This function computes the difussion registration
%   of a slice to its two adjacent slices.
%
%   *M refers to the moving slice, i.e. the first or last histology slice.
%   *FA, *FB refer to the two fixed adjacent slices.
%
%   FILEM, FILEFA, FILEFB are the path and filename to the histology
%   images.
%
%   T0M, T0FA, T0FB are the nested (accumulated) transforms to apply to
%   each image before registering them.
%
%   REGPARAM is the struct with the registration parameters for elastix.
%
%   OPTS is a struct with optional algorithm parameters.
function t0m = process_intermediate_slices(filefa, filem, filefb, t0fa, t0m, t0fb, regParam, opts)

DEBUG = 0;

% load images
imm = imread(filem);
imfa = imread(filefa);
imfb = imread(filefb);

% preprocess histograms
[~, imfa] = histology_preprocessing(imm, imfa);
[imm, imfb] = histology_preprocessing(imm, imfb);

% apply accumulated initial transform (make sure that added voxels are
% black, so that they blend in with the background)
t0m.DefaultPixelValue = 0;
t0fa.DefaultPixelValue = 0;
t0fb.DefaultPixelValue = 0;
imm = transformix(t0m, imm);
imfa = transformix(t0fa, imfa);
imfb = transformix(t0fb, imfb);

% create masks
opts.mMask = create_mask(imm);
fMaska = create_mask(imfa);
fMaskb = create_mask(imfb);

% register slices
opts.fMask = fMaska;
regParam.NumberOfSpatialSamples = round(nnz(fMaska) / 10); % use 10% of the voxels within the mask
[tma, immrega, iterinfoa] = elastix(regParam, imfa, imm, opts);
opts.fMask = fMaskb;
regParam.NumberOfSpatialSamples = round(nnz(fMaskb) / 10); % use 10% of the voxels within the mask
[tmb, immregb, iterinfob] = elastix(regParam, imfb, imm, opts);

% visualize registered images
if (DEBUG)
    subplot(2, 2, 1)
    hold off
    imshowpair(imfa, imm)
    subplot(2, 2, 3)
    hold off
    imshowpair(imfa, immrega)
    subplot(2, 2, 2)
    hold off
    imshowpair(imfb, imm)
    subplot(2, 2, 4)
    hold off
    imshowpair(imfb, immregb)
end

% create a new combined transform as a linear combination of the two
% computed transforms, and apply diffusion coefficient (the new transform
% has to go at the back of the list so that it is applied last)
tma.TransformParameters ...
    = (tma.TransformParameters + tmb.TransformParameters) ...
    * opts.DiffusionCoefficient;
str = 't0m';
while (isstruct(eval([str '.InitialTransformParametersFileName'])))
    str = [str '.InitialTransformParametersFileName'];
end
eval([str '.InitialTransformParametersFileName = tma;']);

% visualize deformation field
if (DEBUG)

    % create grid image
    aux = zeros(size(imm), 'uint8');
    for I = 1:5
        aux(I:100:end, :, :) = 255;
        aux(:, I:100:end, :) = 255;
    end

    % deform grid according to both transforms independently, and the
    % combined one
    auxrega = transformix(tma, aux);
    auxregb = transformix(tmb, aux);
    auxregm = transformix(tm, aux);

    % display 3 deformation fields
    subplot(3, 1, 1)
    imagesc(auxrega)
    axis equal
    subplot(3, 1, 2)
    imagesc(auxregb)
    axis equal
    subplot(3, 1, 3)
    imagesc(auxregm)
    axis equal

end

end

function mask = create_mask(im)

% threshold image
mask = uint8(sum(im, 3) > 0);

% detect connected components of the mask
cc = bwconncomp(mask);

% remove small components
stats = regionprops(cc, 'Area');
idx = [stats.Area] < 5000;
mask(cat(1, cc.PixelIdxList{idx})) = 0;

% smooth mask and dilate a bit
se = strel('disk', 15);
mask = imdilate(mask, se);
se = strel('disk', 5);
mask = imerode(mask, se);

% fill in the holes
mask = imfill(mask, 'holes');

% make sure we only have one connected components
mask = bwrmsmallcomp(mask);

% fit ellipse to the segmentation mask
stats = regionprops(mask, 'MajorAxisLength', 'MinorAxisLength', ...
    'Centroid', 'Orientation');

% simplify notation
xc = stats.Centroid(1);
yc = stats.Centroid(2);
a = stats.MajorAxisLength / 2;
b = stats.MinorAxisLength / 2;
alpha = stats.Orientation;

% create elliptical segmentation mask
[x, y] = meshgrid(1:size(mask, 2), 1:size(mask, 1));
xy = [x(:) - xc, y(:) - yc] ...
    * [cosd(alpha) sind(alpha); -sind(alpha) cosd(alpha)];

mask = (b^2 * xy(:, 1).^2 + a^2 * xy(:, 2).^2 <= (a * b)^2);
mask = reshape(mask, size(im, 1), size(im, 2));

end


function varargout = isempty(varargin)
%ISEMPTY   True for empty CHEBFUN2.
%   ISEMPTY(F) returns 1 if F is an empty CHEBFUN2 object and 0 otherwise.

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

[varargout{1:nargout}] = isempty@separableApprox(varargin{:});

end


function [y,deriv] = identity_trans(w)
% This is an MV2DF . See MV2DF_API_DEFINITION.readme.
%
%  w --> w
%

if nargin==0
    test_this();
    return;
end

if isempty(w)
    map = @(w) w;
    y = linTrans(w,map,map);
    return;
end

if isa(w,'function_handle')
    f = identity_trans([]);
    y = compose_mv(f,w,[]);
    return;
end

f = identity_trans([]);
if nargout==1
    y = f(w);
else
    [y,deriv] = f(w);
end

function test_this()

f = identity_trans([]);
test_MV2DF(f,randn(5,1));


function []=panel5cfdisp(N,n,T,Units,endo,Ymat,stringdates2,decimaldates2,Fstartlocation,Fendlocation,cforecast_estimates,pref)

% preliminary task: reshape Ymat
Ymat=reshape(Ymat,T,n,N);
% preliminary task: gather in a cell the values to be plotted
% initiate the cell
plotdata={};
% because forecasts have to be computed for each unit, loop over units
for ii=1:N
% each cell entry is a matrix of actual and forecast values
% this matrix comprises 4 rows: the first row is actual data, while the three other rows are the estimates (point estimates and confidence bands) for the forecasts
% also, this matrix has a number of rows equal to the dimension of decimaldates2, which comprises the total period sample+forecasts
   % loop over variables
   for jj=1:n
      plotdata{jj,1,ii}=nan(4,size(decimaldates2,1));
      % record actual sample values
      plotdata{jj,1,ii}(1,1:T)=Ymat(:,jj,ii)';
      % copy the last point of the actual sample for the forecast part of the matrix (required to have a clean plot)
      plotdata{jj,1,ii}(:,Fstartlocation-1)=repmat(Ymat(Fstartlocation-1,jj,ii),4,1);
      % record forecast, lower bound
      plotdata{jj,1,ii}(2,Fstartlocation:Fendlocation)=cforecast_estimates{jj,1,ii}(1,:);
      % record forecast, point estimate
      plotdata{jj,1,ii}(3,Fstartlocation:Fendlocation)=cforecast_estimates{jj,1,ii}(2,:);
      % record forecast, upper bound
      plotdata{jj,1,ii}(4,Fstartlocation:Fendlocation)=cforecast_estimates{jj,1,ii}(3,:);
   end
end

% then plot the figure
if pref.plot
forecast=figure('Tag','BEARresults');
set(forecast,'Color',[0.9 0.9 0.9]);
set(forecast,'name','conditional forecasts');
% initiate the count
count=0;
% loop over units
for ii=1:N
   % loop over endogenous variables
   for jj=1:n
   % increment count
   count=count+1;
   % then plot
   subplot(N,n,count)
   hold on
   Xpatch=[decimaldates2(Fstartlocation-1:Fendlocation,1)' fliplr((decimaldates2(Fstartlocation-1:Fendlocation,1))')];
   Ypatch=[plotdata{jj,1,ii}(2,Fstartlocation-1:Fendlocation) fliplr(plotdata{jj,1,ii}(4,Fstartlocation-1:Fendlocation))];
   Fpatch=patch(Xpatch,Ypatch,[0.7 0.78 1]);
   set(Fpatch,'facealpha',0.6);
   set(Fpatch,'edgecolor','none');
   plot(decimaldates2,plotdata{jj,1,ii}(3,:),'Color',[0.4 0.4 1],'LineWidth',2);
   plot(decimaldates2,plotdata{jj,1,ii}(1,:),'Color',[0 0 0],'LineWidth',2);
   hold off
   set(gca,'XLim',[decimaldates2(1,1) decimaldates2(end,1)],'FontName','Times New Roman');
   set(gca,'XGrid','on');
   set(gca,'YGrid','on');
      % top labels
      if count<=n
      title(endo{count,1},'FontWeight','normal');
      end
      % side labels
      if jj==1
      ylabel(Units{ii,1},'FontWeight','normal');
      end
   end
end
end

% save on Excel
% create the cell that will be saved on excel
cforecastcell={};
% build preliminary elements: space between the tables
vertspace=repmat({''},size(stringdates2,1)+3,1);
horzspace=repmat({''},3,6*n);
% loop over units
for ii=1:N
% initiate the cell of results
unitcell={};
   % loop over endogenous variables (horizontal dimension)
   for jj=1:n
   % create a header
   header=[{[Units{ii,1} ': ' endo{jj,1}]} {''} {''} {''} {''};{''} {''} {''} {''} {''};{''} {'actual'} {'lower bound'} {'median'} {'upper bound'}];
   % complete the cell
   endocell=[[header;stringdates2 num2cell((plotdata{jj,1,ii})')] vertspace];
   % concatenate to the previous parts of unitcell
   unitcell=[unitcell endocell];
   end
% concatenate to the previous parts of afcell
cforecastcell=[cforecastcell;horzspace;unitcell];
end
% trim
cforecastcell=cforecastcell(4:end,1:end-1);
% write in excel
if pref.results==1
    bear.xlswritegeneral(fullfile(pref.results_path, [pref.results_sub '.xlsx']),cforecastcell,'cond forecasts','B2');
end


function [iSel, iEcg, chTags, chInd] = ImaGIN_select_channels(chNames, isSEEG)
% IMAGIN_SELECT_CHANNELS Keep only channels of interest.
%
% USAGE:  [iSel, iEcg, chTags, chInd] = ImaGIN_select_channels(chNames, isSEEG=1)
%
% INPUT:
%    - chNames : Cell-array of strings
%    - isSEEG  : 1 if the data is SEEG, 0 if regular EEG
%
% OUTPUT:
%    - iSel   : Array of indices of the channels that are considered as valid
%    - iEcg   : Array of indices of the channels that are identified as ECG
%    - chTags : Array of electrode names for the selected sEEG channels
%    - chInd  : Array of indices of the selected contact on the corresponding sEEG electrode

% -=============================================================================
% This function is part of the ImaGIN software:
% https://f-tract.eu/
%
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE AUTHORS
% DO NOT ASSUME ANY LIABILITY OR RESPONSIBILITY FOR ITS USE IN ANY CONTEXT.
%
% Copyright (c) 2000-2019 Inserm U1216
% =============================================================================-
%
% Authors: Francois Tadel, 2017-2019

% Parse inputs
if (nargin < 2) || isempty(isSEEG)
    isSEEG = 1;
end
% Make sure the names are in one row
chNames = chNames(:)';

% Get all names: remove special characters
AllNames = cellfun(@(c)c(~ismember(c, ' .,?!-_@#$%^&*+*=()[]{}|/')), chNames, 'UniformOutput', 0);
AllTags  = cell(size(AllNames));
AllInd   = cell(size(AllNames));
isNoInd  = zeros(size(AllNames));
% Separate characters and numbers in the names
for i = 1:length(AllNames)
    % Find the last letter in the name
    iLastLetter = find(~ismember(AllNames{i}, '0123456789'), 1, 'last');
    if isempty(iLastLetter)
        iLastLetter = 0;
    end
    AllTags{i} = AllNames{i}(1:iLastLetter);
    % For EEG: The separate name/index does not make sense
    if ~isSEEG
        AllInd{i} = sprintf('%d',i);
        isNoInd(i) = 0;
    % If there are digits at the end of the name: use them as the index of the contact
    elseif (iLastLetter < length(AllNames{i}))
        AllInd{i} = AllNames{i}(iLastLetter+1:end);
    else
        isNoInd(i) = 1;
        AllInd{i} = '0';
    end
end
% Convert indices to double values
AllInd = cellfun(@str2num, AllInd);

% Remove all channels with indices > 18 (this is not SEEG)
% Tolerance for indices 101-118 and 201-218 (examples: electrode "T1" contact "11" => "T11",  electrode "T1" contact "2" => "T12" or "T02")
MAX_CONTACTS = 18;
if isSEEG
    uniqueTags = unique(upper(AllTags));
    for iTag = 1:length(uniqueTags)
        % Get channels of this tag
        iChTag = find(strcmpi(uniqueTags{iTag}, AllTags));
        contactInd = AllInd(iChTag);
        % Reject channels "E" without rejecting "E1"
        if any(contactInd == 0) && any(contactInd > 0)
            % Remove unwanted channels
            iChTagDel = iChTag(contactInd == 0);
            AllNames(iChTagDel) = {'XXXXX'};
            AllTags(iChTagDel) = {'XXXXX'};
            % Remove from the list, so we can process the rest of the channels
            iChTag(contactInd == 0) = [];
            contactInd(contactInd == 0) = [];
        end
        % If the tag is "X": Used by Nihon Kohden for technical tracks, but can be a SEEG electrode too. Keep only indices <= 18
        if isequal(uniqueTags{iTag}, 'X')
            iChTagExtra = iChTag(contactInd > MAX_CONTACTS);
            AllNames(iChTagExtra) = {'XXXXX'};
            AllTags(iChTagExtra) = {'XXXXX'};
        % Accept if all indices < 18
        elseif all(((contactInd >= 1) & (contactInd <= MAX_CONTACTS)))
            % OK
        % Electrode names ending in "1" or "2" (two possible nameing conventions, with or without zeros)
        elseif all(((contactInd >= 11)  & (contactInd <= 19))  | ...                  % Ending in 1, without zeros:  T11..T19, T110..T118
                   ((contactInd >= 110) & (contactInd <= 100 + MAX_CONTACTS)) | ...
                   ((contactInd >= 21)  & (contactInd <= 29))  | ...                  % Ending in 2, without zeros:  T21..T29, T210..T218
                   ((contactInd >= 210) & (contactInd <= 200 + MAX_CONTACTS))) || ...
               all(((contactInd >= 101)  & (contactInd <= 100 + MAX_CONTACTS)) | ...  % Ending in 1, with zeros:  T101..T118
                   ((contactInd >= 201)  & (contactInd <= 200 + MAX_CONTACTS)))       % Ending in 2, with zeros:  T201..T218
            % All channel indices starting with 1 => Add "1" to electrode name
            iElec1 = iChTag(((contactInd >= 11) & (contactInd <= 19))  |  (contactInd >= 101) & (contactInd <= 100 + MAX_CONTACTS));
            for i = 1:length(iElec1)
                AllTags{iElec1(i)} = [AllTags{iElec1(i)}, '1'];
                if (AllInd(iElec1(i)) < 100)
                    AllInd(iElec1(i)) = AllInd(iElec1(i)) - 10;
                elseif (AllInd(iElec1(i)) > 100)
                    AllInd(iElec1(i)) = AllInd(iElec1(i)) - 100;
                end
            end
            % All channel indices starting with 2 => Add "2" to electrode name
            iElec2 = iChTag(((contactInd >= 21) & (contactInd <= 29))  |  (contactInd >= 201) & (contactInd <= 200 + MAX_CONTACTS));
            for i = 1:length(iElec2)
                AllTags{iElec2(i)} = [AllTags{iElec2(i)}, '2'];
                if (AllInd(iElec2(i)) < 200)
                    AllInd(iElec2(i)) = AllInd(iElec2(i)) - 20;
                elseif (AllInd(iElec2(i)) > 200)
                    AllInd(iElec2(i)) = AllInd(iElec2(i)) - 200;
                end
            end
        % Mark as bad all the other channels with Label+Index (but not the channels without labels, because we want to keep the ECG)
        elseif all(contactInd > 0)
            AllNames(iChTag) = {'XXXXX'};
            AllTags(iChTag) = {'XXXXX'};
        end
        % Removed: ((length(iChTag) < 2) && ~any(chNames{iChTag(1)} == '-'))   % less than 2 (but only if not designating a bipolar montage, with '-' in the name)
        % Keep electrodes with one contact only
    end
end

% Process all the channels
iSel = [];
iEcg = [];
for i = 1:length(AllNames)
    % ECG: Accept (should be labelled as such)
    if ismember(lower(AllTags{i}), {'ecg', 'ekg'}) || ~isempty(strfind(lower(AllNames{i}), 'ecg')) || ~isempty(strfind(lower(AllNames{i}), 'ekg'))
        iEcg(end+1) = i;
    % No index or does not end with a digit
    elseif isSEEG && (isNoInd(i) || ~ismember(AllNames{i}(end), '0123456789'))
        continue;
    % Does not contain at least a letter
    elseif isSEEG && ~any(ismember(lower(AllNames{i}), 'abcdefghijklmnopqrstuvwxyz'))
        continue;
    % Contains a /: exclude
    elseif isSEEG && any(ismember(lower(chNames{i}), '/'))
        continue;
    % Unwanted labels (strict)
    elseif ismember(lower(AllTags{i}), {'xxxxx', 'mark', 'dc', 'veo', 'heo', 'dd', 'dg', 'el', 'pulse', 'spo2', 'lpar', 'rpar', 'tib'})
        % MAYBE ADD 'oc' ??
        continue;
    % Unwanted labels (including variants): EOG, EMG, MYO, MAST, REF
    elseif ~isempty(strfind(lower(AllTags{i}), 'eog')) ...
        || ~isempty(strfind(lower(AllTags{i}), 'emg')) ...
        || ~isempty(strfind(lower(AllTags{i}), 'myo')) ...
        || ~isempty(strfind(lower(AllTags{i}), 'mast')) ...
        || ~isempty(strfind(lower(AllTags{i}), 'ref'))
        continue;
    % Unwanted EEG labels
    elseif isSEEG && ismember(lower(AllTags{i}), {'cz', 'fz', 'pz', 'oz', 'nz', 'fpz'})
        continue;
    % Unwanted electrodes that are explicitely scalp electrodes (case sensitive)
    elseif isSEEG && ismember(AllNames{i}, {'sFp1','sFp2','sF4','sF3','sC3','sC4','sP4','sP3','sO2','sO1','sF8','sF7','sT8/T4','sT7/T3','sP8/T6','sP7/T5','sPz','sFz','sIO1','sIO2','sAF9','sAF10','sF9','sF10','sCB1','sCB2','sTP7','sTP9','sTP10','sTP8','sOz','sIz','sPO4','sPO3','sCP5','sCP6','sCP1','sCP2','sFT9','sFT10','sFC2','sFC1','sAF3','sAF4','sFC6','sFC5','sCPz','sP1','sPOz','sP2','sP6','sC6','sP5','sC1','sC2','sC5','sF2','sF6','sF1','sAF8','sF5','sAF7','sFpz','sFCz','sCz'})
        continue;
    % Unwanted FPx/Fx/Cx/Tx/Pz/Ox labels
    elseif isSEEG && ismember(lower(AllNames{i}), {'fp1','fp2'}) && ~any(ismember({'fp3','fp4','fp5','fp6','fp9','fp10','fp11','fp12','fp13','fp14','fp15','fp16','fp17','fp18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'f3','f4','f7','f8'}) && ~any(ismember({'f1','f2','f5','f6','f9','f10','f11','f12','f13','f14','f15','f16','f17','f18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'ft1','ft2','ft9','ft10'}) && ~any(ismember({'ft5','ft6','ft11','ft12','ft13','ft14','ft15','ft16','ft17','ft18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'tp9','tp10'}) && ~any(ismember({'tp1','tp2','tp5','tp6','tp11','tp12','tp13','tp14','tp15','tp16','tp17','tp18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'c3','c4'}) && ~any(ismember({'c1','c2','c5','c6','c7','c8','c9','c10','c11','c12','c13','c14','c15','c16','c17','c18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'t3','t4','t5','t6','t9','t10'}) && ~any(ismember({'t1','t2','t7','t8','t11','t12','t13','t14','t15','t16','t17','t18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'p3','p4'}) && ~any(ismember({'p1','p2','p5','p6','p7','p8','p9','p10','p11','p12','p13','p14','p15','p16','p17','p18'}, lower(AllNames)))
        continue;
    elseif isSEEG && ismember(lower(AllNames{i}), {'o1','o2'}) && ~any(ismember({'o3','o4','o4','o5','o6','o7','o8','o9','o10','o11','o12','o13','o14','o15','o16','o17','o18'}, lower(AllNames)))
        continue;
    % Otherwise: accept
    else
        iSel(end+1) = i;
    end
end

% Sort channels by tag and index
AllTags = AllTags(iSel);
AllInd = AllInd(iSel);
uniqueTags = unique(upper(AllTags));
iOrderSel = [];
for iTag = 1:length(uniqueTags)
    iChTag = find(strcmpi(AllTags, uniqueTags{iTag}));
    [tmp, iOrderInd] = sort(AllInd(iChTag));
    iOrderSel = [iOrderSel, iChTag(iOrderInd)];
end

% Add ECG channels at the end of the file
iSel = [iSel(iOrderSel), iEcg];

% Return lists of selected channels and electrode names
chTags = AllTags;
chInd = AllInd;