function [cfg] = ft_sourceplot(cfg, functional, anatomical)

% FT_SOURCEPLOT plots functional source reconstruction data on slices or on a surface,
% optionally as an overlay on anatomical MRI data, where statistical data can be used to
% determine the opacity of the mask. Input data comes from FT_SOURCEANALYSIS,
% FT_SOURCEGRANDAVERAGE or statistical values from FT_SOURCESTATISTICS.
%
% Use as
%   ft_sourceplot(cfg, anatomical)
%   ft_sourceplot(cfg, functional)
%   ft_sourceplot(cfg, functional, anatomical)
% where the input data can contain either anatomical, functional or statistical data,
% or a combination of them.
%
% The input data can be in a 3-D volumetric representation or in a 2-D cortical sheet
% representation.  If both anatomical and functional/statistical data is provided as input,
% they should be represented in the same coordinate system or interpolated on the same
% geometrical representation, e.g. using FT_SOURCEINTERPOLATE.
%
% The slice and ortho visualization plot the data in the input data voxel arrangement, i.e.
% the three ortho views are the 1st, 2nd and 3rd dimension of the 3-D data matrix, not of
% the head coordinate system. The specification of the coordinate for slice intersection
% is specified in head coordinates, i.e. relative to anatomical landmarks or fiducials and
% expressed in mm or cm. If you want the visualisation to be consistent with the head
% coordinate system, you can reslice the data using FT_VOLUMERESLICE. See http://bit.ly/1OkDlVF
%
% The configuration should contain:
%   cfg.method        = 'ortho',      plots the data on three orthogonal slices
%                       'slice',      plots the data on a number of slices in the same plane
%                       'surface',    plots the data on a 3D brain surface
%                       'glassbrain', plots a max-projection through the brain
%                       'vertex',     plots the grid points or vertices scaled according to the functional value
%                       'cloud',      plot the data as clouds, spheres, or points scaled according to the functional value
% and
%   cfg.anaparameter  = string, field in data with the anatomical data (default = 'anatomy' if present in data)
%   cfg.funparameter  = string, field in data with the functional parameter of interest (default = [])
%   cfg.maskparameter = string, field in the data to be used for opacity masking of fun data (default = [])
%                        If values are between 0 and 1, zero is fully transparant and one is fully opaque.
%                        If values in the field are not between 0 and 1 they will be scaled depending on the values
%                        of cfg.opacitymap and cfg.opacitylim (see below)
%                        You can use masking in several ways, f.i.
%                        - use outcome of statistics to show only the significant values and mask the insignificant
%                          NB see also cfg.opacitymap and cfg.opacitylim below
%                        - use the functional data itself as mask, the highest value (and/or lowest when negative)
%                          will be opaque and the value closest to zero transparent
%                        - Make your own field in the data with values between 0 and 1 to control opacity directly
%
% The following parameters can be used in all methods:
%   cfg.downsample    = downsampling for resolution reduction, integer value (default = 1) (orig: from surface)
%   cfg.atlas         = string, filename of atlas to use (default = []) see FT_READ_ATLAS
%                        for ROI masking (see 'masking' below) or for orthogonal plots (see method='ortho' below)
%   cfg.visible       = string, 'on' or 'off' whether figure will be visible (default = 'on')
%   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. The OpenGL renderer is required when using opacity (default = 'opengl')
%
% The following parameters can be used for the functional data:
%   cfg.funcolormap   = colormap for functional data, see COLORMAP (default = 'auto')
%                       'auto', depends structure funparameter, or on funcolorlim
%                         - funparameter: only positive values, or funcolorlim:'zeromax' -> 'hot'
%                         - funparameter: only negative values, or funcolorlim:'minzero' -> 'cool'
%                         - funparameter: both pos and neg values, or funcolorlim:'maxabs' -> 'default'
%                         - funcolorlim: [min max] if min & max pos-> 'hot', neg-> 'cool', both-> 'default'
%   cfg.funcolorlim   = color range of the functional data (default = 'auto')
%                        [min max]
%                        'maxabs', from -max(abs(funparameter)) to +max(abs(funparameter))
%                        'zeromax', from 0 to max(funparameter)
%                        'minzero', from min(funparameter) to 0
%                        'auto', if funparameter values are all positive: 'zeromax',
%                          all negative: 'minzero', both possitive and negative: 'maxabs'
%   cfg.colorbar      = 'yes' or 'no' (default = 'yes')
%   cfg.colorbartext  =  string indicating the text next to colorbar
%
% The 'ortho' method can also plot time and/or frequency, the other methods can not.
% If your functional data has a time and/or frequency dimension, you can use
%   cfg.latency       = scalar or string, can be 'all', 'prestim', 'poststim', or [beg end], specify time range in seconds
%   cfg.avgovertime   = string, can be 'yes' or 'no' (default = 'no')
%   cfg.frequency     = scalar or string, can be 'all', or [beg end], specify frequency range in Hz
%   cfg.avgoverfreq   = string, can be 'yes' or 'no' (default = 'no')
%
% The following parameters can be used for the masking data:
%   cfg.maskstyle     = 'opacity', or 'colormix'. If 'opacity', low-level
%                         graphics opacity masking is applied, if
%                         'colormix', the color data is explicitly
%                         expressed as a single RGB value, incorporating
%                         the opacitymask. Yields faster and more robust
%                         rendering in general.
%   cfg.opacitymap    = opacitymap for mask data, see ALPHAMAP (default = 'auto')
%                       'auto', depends structure maskparameter, or on opacitylim
%                         - maskparameter: only positive values, or opacitylim:'zeromax' -> 'rampup'
%                         - maskparameter: only negative values, or opacitylim:'minzero' -> 'rampdown'
%                         - maskparameter: both pos and neg values, or opacitylim:'maxabs' -> 'vdown'
%                         - opacitylim: [min max] if min & max pos-> 'rampup', neg-> 'rampdown', both-> 'vdown'
%                         - NB. to use p-values use 'rampdown' to get lowest p-values opaque and highest transparent
%   cfg.opacitylim    = range of mask values to which opacitymap is scaled (default = 'auto')
%                        [min max]
%                        'maxabs', from -max(abs(maskparameter)) to +max(abs(maskparameter))
%                        'zeromax', from 0 to max(abs(maskparameter))
%                        'minzero', from min(abs(maskparameter)) to 0
%                        'auto', if maskparameter values are all positive: 'zeromax',
%                          all negative: 'minzero', both positive and negative: 'maxabs'
%   cfg.roi           = string or cell of strings, region(s) of interest from anatomical atlas (see cfg.atlas above)
%                        everything is masked except for ROI
%
% When cfg.method='ortho', three orthogonal slices will be rendered. You can click in any
% of the slices to update the display in the other two. You can also use the arrow keys on
% your keyboard to navigate in one-voxel steps. Note that the slices are along the first,
% second and third voxel dimension, which do not neccessarily correspond to the axes of the
% head coordinate system. See http://bit.ly/1OkDlVF
%
% The following parameters apply when cfg.method='ortho'
%   cfg.location      = location of cut, (default = 'auto')
%                        'auto', 'center' if only anatomy, 'max' if functional data
%                        'min' and 'max' position of min/max funparameter
%                        'center' of the brain
%                        [x y z], coordinates in voxels or head, see cfg.locationcoordinates
%   cfg.locationcoordinates = coordinate system used in cfg.location, 'head' or 'voxel' (default = 'head')
%                              'head', headcoordinates as mm or cm
%                              'voxel', voxelcoordinates as indices
%   cfg.crosshair     = 'yes' or 'no' (default = 'yes')
%   cfg.axis          = 'on' or 'off' (default = 'on')
%   cfg.queryrange    = number, in atlas voxels (default 3)
%   cfg.clim          = lower and upper anatomical MRI limits (default = [0 1])
%
% When cfg.method='slice', a NxM montage with a large number of slices will be rendered.
% All slices are evenly spaced and along the same dimension.
%
% The following parameters apply for cfg.method='slice'
%   cfg.nslices       = number of slices, (default = 20)
%   cfg.slicerange    = range of slices in data, (default = 'auto')
%                       'auto', full range of data
%                       [min max], coordinates of first and last slice in voxels
%   cfg.slicedim      = dimension to slice 1 (x-axis) 2(y-axis) 3(z-axis) (default = 3)
%   cfg.title         = string, title of the plot
%   cfg.figurename    = string, title of the figure window
%
% When cfg.method='surface', the functional data will be rendered onto a cortical mesh
% (can be an inflated mesh). If the input source data contains a tri-field (i.e. a
% description of a mesh), no interpolation is needed. If the input source data does not
% contain a tri-field, an interpolation is performed onto a specified surface. Note that
% the coordinate system in which the surface is defined should be the same as the coordinate
% system that is represented in the pos-field.
%
% The following parameters apply to cfg.method='surface' when an interpolation is required
%   cfg.surffile       = string, file that contains the surface (default = 'surface_white_both.mat')
%                        'surface_white_both.mat' contains a triangulation that corresponds with the
%                         SPM anatomical template in MNI coordinates
%   cfg.surfinflated   = string, file that contains the inflated surface (default = [])
%                        may require specifying a point-matching (uninflated) surffile
%   cfg.surfdownsample = number (default = 1, i.e. no downsampling)
%   cfg.projmethod     = projection method, how functional volume data is projected onto surface
%                        'nearest', 'project', 'sphere_avg', 'sphere_weighteddistance'
%   cfg.projvec        = vector (in mm) to allow different projections that
%                        are combined with the method specified in cfg.projcomb
%   cfg.projcomb       = 'mean', 'max', method to combine the different projections
%   cfg.projweight     = vector of weights for the different projections (default = 1)
%   cfg.projthresh     = implements thresholding on the surface level
%                        for example, 0.7 means 70% of maximum
%   cfg.sphereradius   = maximum distance from each voxel to the surface to be
%                        included in the sphere projection methods, expressed in mm
%   cfg.distmat        = precomputed distance matrix (default = [])
%
% The following parameters apply to cfg.method='surface' irrespective of whether an interpolation is required
%   cfg.camlight       = 'yes' or 'no' (default = 'yes')
%   cfg.facecolor      = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r',
%                        or an Nx3 or Nx1 array where N is the number of faces
%   cfg.vertexcolor    = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r',
%                        or an Nx3 or Nx1 array where N is the number of vertices
%   cfg.edgecolor      = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r'
%
% When cfg.method = 'cloud', the functional data will be rendered as as clouds (groups of points), spheres, or
% single points at each sensor position. These spheres or point clouds can either
% be viewed in 3D or as 2D slices. The 'anatomical' input may also consist of
% a single or multiple triangulated surface mesh(es) in an Nx1 cell-array
% to be plotted with the interpolated functional data (see FT_PLOT_CLOUD)
%
% The following parameters apply to cfg.method='cloud'
%   cfg.cloudtype       = 'point' plots a single point at each sensor position
%                         'cloud' (default) plots each a group of spherically arranged points at each sensor position
%                         'surf' plots a single spherical surface mesh at each sensor position
%   cfg.radius          = scalar, maximum radius of cloud (default = 4)
%   cfg.colorgrad       = 'white' or a scalar (e.g. 1), degree to which color of points in cloud
%                         changes from its center
%   cfg.slice           = requires 'anatomical' as input (default = 'none')
%                         '2d', plots 2D slices through the cloud with an outline of the mesh
%                         '3d', draws an outline around the mesh at a particular slice
%   cfg.ori             = 'x', 'y', or 'z', specifies the orthogonal plane which will be plotted (default = 'y')
%   cfg.slicepos        = 'auto' or Nx1 vector specifying the position of the
%                         slice plane along the orientation axis (default = 'auto': chooses slice(s) with
%                         the most data)
%   cfg.nslices         = scalar, number of slices to plot if 'slicepos' = 'auto (default = 1)
%   cfg.minspace        = scalar, minimum spacing between slices if nslices>1 (default = 1)
%
% 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 corresponding to the
% input structure.
%
% See also FT_SOURCEMOVIE, FT_SOURCEANALYSIS, FT_SOURCEGRANDAVERAGE, FT_SOURCESTATISTICS,
% FT_VOLUMELOOKUP, FT_READ_ATLAS, FT_READ_MRI

% TODO have to be built in:
%   cfg.marker        = [Nx3] array defining N marker positions to display (orig: from sliceinterp)
%   cfg.markersize    = radius of markers (default = 5)
%   cfg.markercolor   = [1x3] marker color in RGB (default = [1 1 1], i.e. white) (orig: from sliceinterp)
%   white background option

% undocumented TODO
%   slice in all directions
%   surface also optimal when inside present
%   come up with a good glass brain projection
%
% undocumented option
%   cfg.intersectmesh = cell-array of mesh(es) to be plotted along with the
%                       anatomy, useful for evaluating coregistration. Does
%                       at present not check for coordinate system

% Copyright (C) 2007-2022, Robert Oostenveld, Ingrid Nieuwenhuis, J.M.
% Schoffelen
%
% 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$

% 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 functional anatomical
ft_preamble provenance functional anatomical

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

% this is not supported any more as of 26/10/2011
if ischar(functional)
  ft_error('please use cfg.inputfile instead of specifying the input variable as a sting');
end

% ensure that old and unsupported options are not being relied on by the end-user's script
cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.pow', 'pow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.coh', 'coh'});
cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.mom', 'mom'});
cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.pow', 'pow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.coh', 'coh'});
cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.mom', 'mom'});
cfg = ft_checkconfig(cfg, 'renamedval', {'location', 'interactive', 'auto'});
% instead of specifying cfg.coordsys, the user should specify the coordsys in the data
cfg = ft_checkconfig(cfg, 'forbidden', {'units', 'coordsys', 'inputcoord', 'inputcoordsys', 'coordinates'});
% see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=2837
cfg = ft_checkconfig(cfg, 'renamed', {'viewdim', 'axisratio'});
cfg = ft_checkconfig(cfg, 'renamed', {'newfigure', 'figure'});

if isfield(cfg, 'atlas') && ~isempty(cfg.atlas)
  % the atlas lookup requires the specification of the coordsys
  functional     = ft_checkdata(functional, 'datatype', {'source', 'volume'}, 'feedback', 'yes', 'hasunit', 'yes', 'hascoordsys', 'yes');
else
  % check if the input functional is valid for this function, a coordsys is not directly needed
  functional     = ft_checkdata(functional, 'datatype', {'source', 'volume'}, 'feedback', 'yes', 'hasunit', 'yes');
end

% set the defaults for all methods
cfg.method        = ft_getopt(cfg, 'method',        'ortho');
cfg.funparameter  = ft_getopt(cfg, 'funparameter',  []);
cfg.maskparameter = ft_getopt(cfg, 'maskparameter', []);
cfg.maskstyle     = ft_getopt(cfg, 'maskstyle',     'opacity');
cfg.downsample    = ft_getopt(cfg, 'downsample',    1);
cfg.flip          = ft_getopt(cfg, 'flip',          []); % the default is set below
cfg.title         = ft_getopt(cfg, 'title',         []);
cfg.figurename    = ft_getopt(cfg, 'figurename',    []);
cfg.atlas         = ft_getopt(cfg, 'atlas',         []);
cfg.marker        = ft_getopt(cfg, 'marker',        []);
cfg.markersize    = ft_getopt(cfg, 'markersize',    5);
cfg.markercolor   = ft_getopt(cfg, 'markercolor',   [1 1 1]);
cfg.colorbar      = ft_getopt(cfg, 'colorbar',      'yes');
cfg.colorbartext  = ft_getopt(cfg, 'colorbartext',  '');
cfg.voxelratio    = ft_getopt(cfg, 'voxelratio',    'data'); % display size of the voxel, 'data' or 'square'
cfg.axisratio     = ft_getopt(cfg, 'axisratio',     'data'); % size of the axes of the three orthoplots, 'square', 'voxel', or 'data'
cfg.visible       = ft_getopt(cfg, 'visible',       'on');
cfg.clim          = ft_getopt(cfg, 'clim',          [0 1]); % this is used to scale the orthoplot
cfg.intersectmesh = ft_getopt(cfg, 'intersectmesh');
cfg.renderer      = ft_getopt(cfg, 'renderer',      'opengl');
cfg.interactive   = ft_getopt(cfg, 'interactive',   'yes'); % used to disable interaction for method=glassbrain

if isempty(cfg.flip)
  if strcmp(cfg.method, 'ortho')
    cfg.flip = 'yes';
  else
    cfg.flip = 'no';
  end
end

if ~isfield(cfg, 'anaparameter')
  if isfield(functional, 'anatomy')
    cfg.anaparameter = 'anatomy';
  else
    cfg.anaparameter = [];
  end
end

% set the common defaults for the functional data
cfg.funcolormap   = ft_getopt(cfg, 'funcolormap',   'auto');
cfg.funcolorlim   = ft_getopt(cfg, 'funcolorlim',   'auto');

% set the common defaults for the statistical data
cfg.opacitymap    = ft_getopt(cfg, 'opacitymap',    'auto');
cfg.opacitylim    = ft_getopt(cfg, 'opacitylim',    'auto');
cfg.roi           = ft_getopt(cfg, 'roi',           []);
cfg.maskstyle     = ft_getopt(cfg, 'maskstyle',     'opacity');

% select the functional and the mask parameter
cfg.funparameter  = parameterselection(cfg.funparameter, functional);
cfg.maskparameter = parameterselection(cfg.maskparameter, functional);
% only a single parameter should be selected
try, cfg.funparameter  = cfg.funparameter{1};  end
try, cfg.maskparameter = cfg.maskparameter{1}; end

if isfield(functional, 'time') || isfield(functional, 'freq')
  % make a selection of the time and/or frequency dimension
  tmpcfg = keepfields(cfg, {'frequency', 'avgoverfreq', 'keepfreqdim', 'latency', 'avgovertime', 'keeptimedim', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
  functional = ft_selectdata(tmpcfg, functional);
  % restore the provenance information
  [cfg, functional] = rollback_provenance(cfg, functional);
end

% the data can be passed as input argument or can be read from disk
hasanatomical = exist('anatomical', 'var');

if hasanatomical && ~strcmp(cfg.method, 'cloud') % cloud method should be able to take multiple surfaces and does not require interpolation
  % interpolate on the fly, this also does the downsampling if requested
  tmpcfg = keepfields(cfg, {'downsample', 'interpmethod', 'sphereradius', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
  tmpcfg.parameter = cfg.funparameter;
  functional = ft_sourceinterpolate(tmpcfg, functional, anatomical);
  [cfg, functional] = rollback_provenance(cfg, functional);
  cfg.anaparameter = 'anatomy';
elseif ~hasanatomical && cfg.downsample~=1
  % optionally downsample the functional volume
  tmpcfg = keepfields(cfg, {'downsample', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
  tmpcfg.parameter = {cfg.funparameter, cfg.maskparameter, cfg.anaparameter};
  functional = ft_volumedownsample(tmpcfg, functional);
  [cfg, functional] = rollback_provenance(cfg, functional);
end

if isfield(functional, 'dim') && isfield(functional, 'transform')
  % this is a regular 3D functional volume
  isUnstructuredFun = false;

elseif isfield(functional, 'dim') && isfield(functional, 'pos')
  % these are positions that can be mapped onto a 3D regular grid
  isUnstructuredFun  = false;
  % construct the transformation matrix from the positions
  functional.transform = pos2transform(functional.pos, functional.dim);
else
  % this is functional data on irregular positions, such as a cortical sheet
  isUnstructuredFun = true;
end

if ~isUnstructuredFun && strcmp(cfg.flip, 'yes')
  % align the anatomical volume approximately to coordinate system, this puts it upright
  origmethod = cfg.method;
  tmpcfg = [];
  tmpcfg.method = 'flip';
  tmpcfg.trackcallinfo = 'no';
  tmpcfg.showcallinfo = 'no';
  functional = ft_volumereslice(tmpcfg, functional);
  [cfg, functional] = rollback_provenance(cfg, functional);
  cfg.method = origmethod;
end

% this only relates to the dimensions of the geometry, which is npos*1 or nx*ny*nz
if isUnstructuredFun
  dim = [size(functional.pos,1) 1];
else
  dim = functional.dim;
end

%% get the elements that will be plotted
hasatlas = ~isempty(cfg.atlas);
if hasatlas
  [atlas, functional] = handle_atlas_input(cfg.atlas, functional);
end

hasroi = ~isempty(cfg.roi);
if hasroi
  if ~hasatlas
    ft_error('specify cfg.atlas which specifies cfg.roi')
  else
    % get the mask
    tmpcfg = keepfields(cfg, {'roi', 'atlas'});
    roi = ft_volumelookup(tmpcfg, functional);
  end
end

hasana = isfield(functional, cfg.anaparameter);
if hasana
  ana = getsubfield(functional, cfg.anaparameter);
  if isa(ana, 'uint8') || isa(ana, 'uint16') || isa(ana, 'int8') || isa(ana, 'int16')
    ana = double(ana);
  end
  fprintf('scaling anatomy to [0 1]\n');
  dmin = min(ana(:));
  dmax = max(ana(:));
  ana  = (ana-dmin)./(dmax-dmin);
  ana  = reshape(ana, dim);
end

%%% funparameter
hasfun = isfield(functional, cfg.funparameter);
if hasfun
  fun = getsubfield(functional, cfg.funparameter);

  dimord = getdimord(functional, cfg.funparameter);
  dimtok = tokenize(dimord, '_');

  % replace the cell-array functional with a normal array
  if strcmp(dimtok{1}, '{pos}')
    tmpdim = getdimsiz(functional, cfg.funparameter);
    tmpfun = nan(tmpdim);
    insideindx = find(functional.inside);
    for i=insideindx(:)'
      tmpfun(i,:) = fun{i};
    end
    fun = tmpfun;
    clear tmpfun
    dimtok{1} = 'pos';  % update the description of the dimensions
    dimord([1 5]) = []; % remove the { and }
  end

  % ensure that the functional data is real
  if ~isreal(fun)
    ft_warning('functional data is complex, taking absolute value');
    fun = abs(fun);
  end

  if ~isa(fun, 'double')
    ft_warning('converting functional data to double precision');
    fun = double(fun);
  end

  if strcmp(dimord, 'pos_rgb') || (ndims(fun)>3 && size(fun,4)==3)
    % treat functional data as rgb values
    if any(fun(:)>1 | fun(:)<0)
      % scale
      tmpdim = size(fun);
      nvox   = prod(tmpdim(1:end-1));
      tmpfun = reshape(fun,[nvox tmpdim(end)]);
      m1     = max(tmpfun,[],1);
      m2     = min(tmpfun,[],1);
      tmpfun = (tmpfun-m2(ones(nvox,1),:))./(m1(ones(nvox,1),:)-m2(ones(nvox,1),:));
      fun    = reshape(tmpfun, tmpdim);
      clear tmpfun
    end
    qi      = 1;
    hasfreq = 0;
    hastime = 0;

    doimage = 1;
    fcolmin = 0;
    fcolmax = 1;

    cfg.funcolormap = 'rgb';
  else
    % determine scaling min and max (fcolmin fcolmax) and funcolormap
    if ~isa(fun, 'logical')
      funmin = min(fun(:));
      funmax = max(fun(:));
    else
      funmin = 0;
      funmax = 1;
    end
    % smart automatic limits
    if isequal(cfg.funcolorlim, 'auto')
      if sign(funmin)>-1 && sign(funmax)>-1
        cfg.funcolorlim = 'zeromax';
      elseif sign(funmin)<1 && sign(funmax)<1
        cfg.funcolorlim = 'minzero';
      else
        cfg.funcolorlim = 'maxabs';
      end
    end
    if ischar(cfg.funcolorlim)
      % limits are given as string
      if isequal(cfg.funcolorlim, 'maxabs')
        fcolmin = -max(abs([funmin,funmax]));
        fcolmax =  max(abs([funmin,funmax]));
        if isequal(cfg.funcolormap, 'auto'); cfg.funcolormap = 'default'; end
      elseif isequal(cfg.funcolorlim, 'zeromax')
        fcolmin = 0;
        fcolmax = funmax;
        if isequal(cfg.funcolormap, 'auto'); cfg.funcolormap = 'hot'; end
      elseif isequal(cfg.funcolorlim, 'minzero')
        fcolmin = funmin;
        fcolmax = 0;
        if isequal(cfg.funcolormap, 'auto'); cfg.funcolormap = 'cool'; end
      else
        ft_error('do not understand cfg.funcolorlim');
      end
    else
      % limits are numeric
      fcolmin = cfg.funcolorlim(1);
      fcolmax = cfg.funcolorlim(2);
      % smart colormap
      if isequal(cfg.funcolormap, 'auto')
        if sign(fcolmin) == -1 && sign(fcolmax) == 1
          cfg.funcolormap = 'default';
        else
          if fcolmin < 0
            cfg.funcolormap = 'cool';
          else
            cfg.funcolormap = 'hot';
          end
        end
      end
    end % if ischar
    clear funmin funmax

    % what if fun is 4D?
    if ndims(fun)>3 || prod(dim)==size(fun,1)
      if strcmp(dimord, 'pos_freq_time') || strcmp(dimord, 'dim1_dim2_dim3_freq_time')
        % functional contains time-frequency representation
        qi      = [1 1];
        hasfreq = numel(functional.freq)>1;
        hastime = numel(functional.time)>1;
        fun     = reshape(fun, [dim numel(functional.freq) numel(functional.time)]);
      elseif strcmp(dimord, 'pos_time') || strcmp(dimord, 'dim1_dim2_dim3_time')
        % functional contains evoked field
        qi      = 1;
        hasfreq = 0;
        hastime = numel(functional.time)>1;
        fun     = reshape(fun, [dim numel(functional.time)]);
      elseif strcmp(dimord, 'pos_ori_time') || strcmp(dimord, 'dim1_dim2_dim3_ori_time')
        % functional contains evoked field
        qi      = 1;
        hasfreq = 0;
        hastime = numel(functional.time)>1;
        % the following will fail if the number of orientations is larger than 1
        fun     = reshape(fun, [dim numel(functional.time)]);
      elseif strcmp(dimord, 'pos_freq') || strcmp(dimord, 'dim1_dim2_dim3_freq')
        % functional contains frequency spectra
        qi      = 1;
        hasfreq = numel(functional.freq)>1;
        hastime = 0;
        fun     = reshape(fun, [dim numel(functional.freq)]);
      else
        % functional contains scalar value for each position
        qi      = 1;
        hasfreq = 0;
        hastime = 0;
        fun     = reshape(fun, dim);
      end
    else
      % do nothing
      qi      = 1;
      hasfreq = 0;
      hastime = 0;
    end

    doimage = 0;
  end % if dimord has rgb or something else

else
  % there is no functional data
  qi      = 1;
  hasfreq = 0;
  hastime = 0;
  doimage = 0;
  fcolmin = 0; % needs to be defined for callback
  fcolmax = 1;
end

hasmsk = issubfield(functional, cfg.maskparameter);
if hasmsk
  if ~hasfun
    ft_error('you can not have a mask without functional data')
  else
    msk = getsubfield(functional, cfg.maskparameter);
    if islogical(msk) % otherwise sign() not posible
      msk = double(msk);
    end
  end
  % reshape to match fun
  if strcmp(dimord, 'pos_freq_time')
    % functional contains timefrequency representation
    msk     = reshape(msk, [dim numel(functional.freq) numel(functional.time)]);
  elseif strcmp(dimord, 'pos_time')
    % functional contains evoked field
    msk     = reshape(msk, [dim numel(functional.time)]);
  elseif strcmp(dimord, 'pos_freq')
    % functional contains frequency spectra
    msk     = reshape(msk, [dim numel(functional.freq)]);
  else
    msk     = reshape(msk, dim);
  end

  % determine scaling and opacitymap
  mskmin = min(msk(:));
  mskmax = max(msk(:));
  % determine the opacity limits and the opacity map
  % smart limits: make from auto other string, or equal to funcolorlim if funparameter == maskparameter
  if isequal(cfg.opacitylim, 'auto')
    if isequal(cfg.funparameter,cfg.maskparameter)
      cfg.opacitylim = cfg.funcolorlim;
    else
      if sign(mskmin)>-1 && sign(mskmax)>-1
        cfg.opacitylim = 'zeromax';
      elseif sign(mskmin)<1 && sign(mskmax)<1
        cfg.opacitylim = 'minzero';
      else
        cfg.opacitylim = 'maxabs';
      end
    end
  end
  if ischar(cfg.opacitylim)
    % limits are given as string
    switch cfg.opacitylim
      case 'zeromax'
        opacmin = 0;
        opacmax = mskmax;
        if isequal(cfg.opacitymap, 'auto'), cfg.opacitymap = 'rampup'; end
      case 'minzero'
        opacmin = mskmin;
        opacmax = 0;
        if isequal(cfg.opacitymap, 'auto'), cfg.opacitymap = 'rampdown'; end
      case 'maxabs'
        opacmin = -max(abs([mskmin, mskmax]));
        opacmax =  max(abs([mskmin, mskmax]));
        if isequal(cfg.opacitymap, 'auto'), cfg.opacitymap = 'vdown'; end
      otherwise
        ft_error('incorrect specification of cfg.opacitylim');
    end % switch opacitylim
  else
    % limits are numeric
    opacmin = cfg.opacitylim(1);
    opacmax = cfg.opacitylim(2);
    if isequal(cfg.opacitymap, 'auto')
      if sign(opacmin)>-1 && sign(opacmax)>-1
        cfg.opacitymap = 'rampup';
      elseif sign(opacmin)<1 && sign(opacmax)<1
        cfg.opacitymap = 'rampdown';
      else
        cfg.opacitymap = 'vdown';
      end
    end
  end % handling opacitylim and opacitymap
  clear mskmin mskmax
else
  opacmin = [];
  opacmax = [];
end

% prevent outside fun from being plotted
if hasfun && ~hasmsk && isfield(functional, 'inside')
  hasmsk = 1;
  msk = zeros(dim);
  cfg.opacitymap = 'rampup';
  opacmin = 0;
  opacmax = 1;
  % make intelligent mask
  if isequal(cfg.method, 'surface')
    msk(functional.inside&isfinite(functional.(cfg.funparameter))) = 1;
    if any(functional.inside&~isfinite(functional.(cfg.funparameter)))
      ft_warning('functional data contains %d NaNs labeled as inside', sum(functional.inside&~isfinite(functional.(cfg.funparameter))));
    end
  else
    if hasana
      msk(functional.inside) = 0.5; % so anatomy is visible
    else
      msk(functional.inside) = 1;
    end
  end
end

% if region of interest is specified, mask everything besides roi
if hasfun && hasroi && ~hasmsk
  hasmsk = 1;
  msk = roi;
  cfg.opacitymap = 'rampup';
  opacmin = 0;
  opacmax = 1;
elseif hasfun && hasroi && hasmsk
  msk = roi .* msk;
  opacmin = [];
  opacmax = []; % has to be defined
elseif hasroi
  ft_error('you can not have a roi without functional data')
end

%% give some feedback
if ~hasfun && ~hasana
  % this seems to be a problem that people often have due to incorrect specification of the cfg
  ft_error('no anatomy is present and no functional data is selected, please check your cfg.funparameter');
end
if ~hasana
  fprintf('not plotting anatomy\n');
end
if ~hasfun
  fprintf('not plotting functional data\n');
end
if ~hasmsk
  fprintf('not applying a mask on the functional data\n');
end
if ~hasatlas
  fprintf('not using an atlas\n');
end
if ~hasroi
  fprintf('not using a region-of-interest\n');
end

%% start building the figure

% open a new figure with the specified settings
h = open_figure(keepfields(cfg, {'figure', 'position', 'visible', 'renderer', 'figurename', 'title'}));
set(h, 'color', [1 1 1]);

%% set color and opacity mapping for this figure
if hasfun
  if ischar(cfg.funcolormap) && ~strcmp(cfg.funcolormap, 'rgb')
    cfg.funcolormap = ft_colormap(cfg.funcolormap);
  elseif iscell(cfg.funcolormap)
    cfg.funcolormap = ft_colormap(cfg.funcolormap{:});
  end
end
if hasmsk
  cfg.opacitymap = alphamap(cfg.opacitymap);
  alphamap(cfg.opacitymap);
  if ndims(fun)>3 && ndims(msk)==3 && ~isequal(cfg.funcolormap, 'rgb')
    siz = size(fun);
    msk = repmat(msk, [1 1 1 siz(4:end)]);
  end
end

switch cfg.method
  case 'slice'
    assert(~hastime, 'method "%s" does not support time', cfg.method);
    assert(~hasfreq, 'method "%s" does not support freq', cfg.method);

    % set the defaults for method=slice
    cfg.nslices    = ft_getopt(cfg, 'nslices',    20);
    cfg.slicedim   = ft_getopt(cfg, 'slicedim',   3);
    cfg.slicerange = ft_getopt(cfg, 'slicerange', 'auto');

    % ADDED BY JM: allow for slicedim different than 3
    switch cfg.slicedim
      case 1
        if hasana, ana = permute(ana,[2 3 1]); end
        if hasfun, fun = permute(fun,[2 3 1]); end
        if hasmsk, msk = permute(msk,[2 3 1]); end
        cfg.slicedim=3;
      case 2
        if hasana, ana = permute(ana,[3 1 2]); end
        if hasfun, fun = permute(fun,[3 1 2]); end
        if hasmsk, msk = permute(msk,[3 1 2]); end
        cfg.slicedim=3;
      otherwise
        % nothing needed
    end

    %%%%% select slices
    if ~ischar(cfg.slicerange)
      ind_fslice = cfg.slicerange(1);
      ind_lslice = cfg.slicerange(2);
    elseif isequal(cfg.slicerange, 'auto')
      if hasfun % default
        if isfield(functional, 'inside')

          insideMask = false(size(fun));
          insideMask(functional.inside) = true;

          ind_fslice = find(max(max(insideMask,[],1),[],2), 1, 'first');
          ind_lslice = find(max(max(insideMask,[],1),[],2), 1, 'last');
        else
          ind_fslice = find(~isnan(max(max(fun,[],1),[],2)), 1, 'first');
          ind_lslice = find(~isnan(max(max(fun,[],1),[],2)), 1, 'last');
        end
      elseif hasana % if only ana, no fun
        ind_fslice = find(max(max(ana,[],1),[],2), 1, 'first');
        ind_lslice = find(max(max(ana,[],1),[],2), 1, 'last');
      else
        ft_error('no functional parameter and no anatomical parameter, can not plot');
      end
    else
      ft_error('do not understand cfg.slicerange');
    end
    ind_allslice = linspace(ind_fslice,ind_lslice,cfg.nslices);
    ind_allslice = round(ind_allslice);
    % make new ana, fun, msk, mskana with only the slices that will be plotted (slice dim is always third dimension)
    if hasana; new_ana = ana(:,:,ind_allslice); clear ana; ana=new_ana; clear new_ana; end
    if hasfun; new_fun = fun(:,:,ind_allslice); clear fun; fun=new_fun; clear new_fun; end
    if hasmsk; new_msk = msk(:,:,ind_allslice); clear msk; msk=new_msk; clear new_msk; end

    % update the dimensions of the volume
    if hasana
      dim=size(ana);
    else
      dim=size(fun);
    end

    %%%%% make a "quilt", that contain all slices on 2D patched sheet
    % Number of patches along sides of Quilt (M and N)
    % Size (in voxels) of side of patches of Quilt (m and n)

    % take care of a potential singleton 3rd dimension
    if numel(dim)<3
      dim(end+1:3) = 1;
    end

    m = dim(1);
    n = dim(2);
    M = ceil(sqrt(dim(3)));
    N = ceil(sqrt(dim(3)));

    num_patch = N*M;
    num_slice = (dim(cfg.slicedim));
    % put empty slides on ana, fun, msk, mskana to fill Quilt up
    if hasana; ana(:,:,end+1:num_patch)=0; end
    if hasfun; fun(:,:,end+1:num_patch)=0; end
    if hasmsk; msk(:,:,end+1:num_patch)=0; end
    % if hasmskana; mskana(:,:,end:num_patch)=0; end
    % put the slices in the quilt
    for iSlice = 1:num_slice
      xbeg = floor((iSlice-1)./M);
      ybeg = mod(iSlice-1, M);
      if hasana
        quilt_ana(ybeg*m+1:(ybeg+1)*m, xbeg*n+1:(xbeg+1)*n)=ana(:,:,iSlice);
      end
      if hasfun
        quilt_fun(ybeg*m+1:(ybeg+1)*m, xbeg*n+1:(xbeg+1)*n)=fun(:,:,iSlice);
      end
      if hasmsk
        quilt_msk(ybeg*m+1:(ybeg+1)*m, xbeg*n+1:(xbeg+1)*n)=msk(:,:,iSlice);
      end
    end
    % make vols and scales, containes volumes to be plotted (fun, ana, msk), added by ingnie
    if hasana; vols2D{1} = quilt_ana; scales{1} = []; end % needed when only plotting ana
    if hasfun; vols2D{2} = quilt_fun; scales{2} = [fcolmin fcolmax]; end
    if hasmsk; vols2D{3} = quilt_msk; scales{3} = [opacmin opacmax]; end

    % the transpose is needed for displaying the matrix using the MATLAB image() function
    if hasana;             ana = vols2D{1}'; end
    if hasfun && ~doimage; fun = vols2D{2}'; end
    if hasfun &&  doimage; fun = permute(vols2D{2},[2 1 3]); end
    if hasmsk;             msk = vols2D{3}'; end

    if hasana
      % scale anatomy between 0 and 1
      fprintf('scaling anatomy\n');
      amin = min(ana(:));
      amax = max(ana(:));
      ana = (ana-amin)./(amax-amin);
      clear amin amax;
      % convert anatomy into RGB values
      ana = cat(3, ana, ana, ana);
      ha = imagesc(ana);
    end
    hold on

    if hasfun

      if doimage
        hf = image(fun);
      else
        hf = imagesc(fun);
        try
          caxis(scales{2});
        end
        % apply the opacity mask to the functional data
        if hasmsk
          % set the opacity
          set(hf, 'AlphaData', msk)
          set(hf, 'AlphaDataMapping', 'scaled')
          try
            alim(scales{3});
          end
        elseif hasana
          set(hf, 'AlphaData', 0.5)
        end

      end
    end

    axis equal
    axis tight
    axis xy
    axis off

    if istrue(cfg.colorbar)
      if hasfun
        % use a normal MATLAB colorbar
        hc = colorbar;
        set(hc, 'YLim', [fcolmin fcolmax]);
        ylabel(hc, cfg.colorbartext);
      else
        ft_warning('no colorbar possible without functional data');
        cfg.colorbar = 'no'; % prevent the warning from apearing twice
      end
    end

  case 'ortho'
    % set the defaults for method=ortho
    cfg.location            = ft_getopt(cfg, 'location',            'auto');
    cfg.locationcoordinates = ft_getopt(cfg, 'locationcoordinates', 'head');
    cfg.crosshair           = ft_getopt(cfg, 'crosshair',           'yes');
    cfg.axis                = ft_getopt(cfg, 'axis',                'on');
    cfg.queryrange          = ft_getopt(cfg, 'queryrange',          3);

    if ~ischar(cfg.location)
      if strcmp(cfg.locationcoordinates, 'head')
        % convert the headcoordinates location into voxel coordinates
        loc = inv(functional.transform) * [cfg.location(:); 1];
        loc = round(loc(1:3));
      elseif strcmp(cfg.locationcoordinates, 'voxel')
        % the location is already in voxel coordinates
        loc = round(cfg.location(1:3));
      else
        ft_error('you should specify cfg.locationcoordinates');
      end
    else
      if isequal(cfg.location, 'auto')
        if hasfun
          if isequal(cfg.funcolorlim, 'maxabs')
            loc = 'max';
          elseif isequal(cfg.funcolorlim, 'zeromax')
            loc = 'max';
          elseif isequal(cfg.funcolorlim, 'minzero')
            loc = 'min';
          else % if numerical
            loc = 'max';
          end
        else
          loc = 'center';
        end
      else
        loc = cfg.location;
      end
    end

    % determine the initial intersection of the cursor (xi yi zi)
    if ischar(loc) && strcmp(loc, 'min')
      if isempty(cfg.funparameter)
        ft_error('cfg.location is min, but no functional parameter specified');
      end
      [dummy, minindx] = min(fun(:));
      [xi, yi, zi] = ind2sub(dim, minindx);
    elseif ischar(loc) && strcmp(loc, 'max')
      if isempty(cfg.funparameter)
        ft_error('cfg.location is max, but no functional parameter specified');
      end
      [dummy, maxindx] = max(fun(:));
      [xi, yi, zi] = ind2sub(dim, maxindx);
    elseif ischar(loc) && strcmp(loc, 'center')
      xi = round(dim(1)/2);
      yi = round(dim(2)/2);
      zi = round(dim(3)/2);
    elseif ~ischar(loc)
      % using nearest instead of round ensures that the position remains within the volume
      xi = nearest(1:dim(1), loc(1));
      yi = nearest(1:dim(2), loc(2));
      zi = nearest(1:dim(3), loc(3));
    end

    if numel(dim)<3
      ft_error('the input source structure cannot be reshaped into a volumetric 3D representation');
    end

    xi = round(xi); xi = max(xi, 1); xi = min(xi, dim(1));
    yi = round(yi); yi = max(yi, 1); yi = min(yi, dim(2));
    zi = round(zi); zi = max(zi, 1); zi = min(zi, dim(3));

    % axes settings
    if strcmp(cfg.axisratio, 'voxel')
      % determine the number of voxels to be plotted along each axis
      axlen1 = dim(1);
      axlen2 = dim(2);
      axlen3 = dim(3);
    elseif strcmp(cfg.axisratio, 'data')
      % determine the length of the edges along each axis
      [cp_voxel, cp_head] = cornerpoints(dim, functional.transform);
      axlen1 = norm(cp_head(2,:)-cp_head(1,:));
      axlen2 = norm(cp_head(4,:)-cp_head(1,:));
      axlen3 = norm(cp_head(5,:)-cp_head(1,:));
    elseif strcmp(cfg.axisratio, 'square')
      % the length of the axes should be equal
      axlen1 = 1;
      axlen2 = 1;
      axlen3 = 1;
    end

    % this is the size reserved for subplot h1, h2 and h3
    h1size(1) = 0.82*axlen1/(axlen1 + axlen2);
    h1size(2) = 0.82*axlen3/(axlen2 + axlen3);
    h2size(1) = 0.82*axlen2/(axlen1 + axlen2);
    h2size(2) = 0.82*axlen3/(axlen2 + axlen3);
    h3size(1) = 0.82*axlen1/(axlen1 + axlen2);
    h3size(2) = 0.82*axlen2/(axlen2 + axlen3);

    if strcmp(cfg.voxelratio, 'square')
      voxlen1 = 1;
      voxlen2 = 1;
      voxlen3 = 1;
    elseif strcmp(cfg.voxelratio, 'data')
      % the size of the voxel is scaled with the data
      [cp_voxel, cp_head] = cornerpoints(dim, functional.transform);
      voxlen1 = norm(cp_head(2,:)-cp_head(1,:))/norm(cp_voxel(2,:)-cp_voxel(1,:));
      voxlen2 = norm(cp_head(4,:)-cp_head(1,:))/norm(cp_voxel(4,:)-cp_voxel(1,:));
      voxlen3 = norm(cp_head(5,:)-cp_head(1,:))/norm(cp_voxel(5,:)-cp_voxel(1,:));
    end

    %% the figure is interactive, add callbacks
    set(h, 'windowbuttondownfcn', @cb_buttonpress);
    set(h, 'windowbuttonupfcn',   @cb_buttonrelease);
    set(h, 'windowkeypressfcn',   @cb_keyboard);
    set(h, 'CloseRequestFcn',     @cb_quit);

    %% create figure handles

    % axis handles will hold the anatomical functional if present, along with labels etc.
    h1 = axes('position',[0.06                0.06+0.06+h3size(2) h1size(1) h1size(2)]);
    h2 = axes('position',[0.06+0.06+h1size(1) 0.06+0.06+h3size(2) h2size(1) h2size(2)]);
    h3 = axes('position',[0.06                0.06                h3size(1) h3size(2)]);

    set(h1, 'Tag', 'ik', 'Visible', cfg.axis, 'XAxisLocation', 'top');
    set(h2, 'Tag', 'jk', 'Visible', cfg.axis, 'YAxisLocation', 'right'); % after rotating in ft_plot_ortho this becomes top
    set(h3, 'Tag', 'ij', 'Visible', cfg.axis);

    set(h1, 'DataAspectRatio',1./[voxlen1 voxlen2 voxlen3]);
    set(h2, 'DataAspectRatio',1./[voxlen1 voxlen2 voxlen3]);
    set(h3, 'DataAspectRatio',1./[voxlen1 voxlen2 voxlen3]);

    % create structure to be passed to gui
    opt               = [];
    opt.dim           = dim;
    opt.zoom          = 0;
    opt.ijk           = [xi yi zi];
    opt.h1size        = h1size;
    opt.h2size        = h2size;
    opt.h3size        = h3size;
    opt.handlesaxes   = [h1 h2 h3];
    opt.handlesfigure = h;
    opt.showaxis      = cfg.axis;
    if hasatlas
      opt.atlas = atlas;
    end
    if hasana && ~strcmp(cfg.maskstyle, 'colormix')
      opt.ana = ana;
    elseif hasana && strcmp(cfg.maskstyle, 'colormix')
      opt.background = ana;
    elseif ~hasana && ~strcmp(cfg.maskstyle, 'colormix')
      % nothing needed
    elseif ~hasana && strcmp(cfg.maskstyle, 'colormix')
      opt.background = zeros(size(fun));
    end
    if hasfun
      opt.fun = fun;
    end
    if hasmsk
      opt.msk = msk;
    end
    opt.update        = [1 1 1];
    opt.init          = true;
    opt.usedim        = (isUnstructuredFun==false);
    opt.usepos        = (isUnstructuredFun==true);
    opt.hasatlas      = hasatlas;
    opt.hasfreq       = hasfreq;
    opt.hastime       = hastime;
    opt.hasmsk        = hasmsk;
    opt.hasfun        = hasfun;
    opt.hasana        = isfield(opt, 'ana');
    opt.hasbackground = isfield(opt, 'background');
    opt.qi            = qi;
    opt.tag           = 'ik';
    opt.functional    = functional;
    opt.fcolmin       = fcolmin;
    opt.fcolmax       = fcolmax;
    opt.opacmin       = opacmin;
    opt.opacmax       = opacmax;
    opt.clim          = cfg.clim; % contrast limits for the anatomy, see ft_volumenormalise
    opt.colorbar      = cfg.colorbar;
    opt.colorbartext  = cfg.colorbartext;
    opt.queryrange    = cfg.queryrange;
    opt.funcolormap   = cfg.funcolormap;
    opt.crosshair     = istrue(cfg.crosshair);
    opt.interactive   = istrue(cfg.interactive);
    if ~isempty(cfg.intersectmesh)
      if isstruct(cfg.intersectmesh)
        tmp = cfg.intersectmesh;
        cfg.intersectmesh = cell(size(tmp));
        for i=1:numel(tmp)
          cfg.intersectmesh{i} = tmp(i);
        end
      end
      % the data will be plotted in voxel space, so transform the meshes
      % accordingly, assuming the same coordinate system as the anatomical
      for m = 1:numel(cfg.intersectmesh)
        opt.intersectmesh{m} = ft_transform_geometry(inv(functional.transform), cfg.intersectmesh{m});
      end
    else
      opt.intersectmesh = [];
    end

    %% do the actual plotting
    setappdata(h, 'opt', opt);
    cb_redraw(h);
    cb_help(h);

  case 'surface'
    assert(~hastime, 'method "%s" does not support time', cfg.method);
    assert(~hasfreq, 'method "%s" does not support freq', cfg.method);

    % set the defaults for method=surface
    cfg.downsample     = ft_getopt(cfg, 'downsample',     1);
    cfg.surfdownsample = ft_getopt(cfg, 'surfdownsample', 1);
    cfg.surffile       = ft_getopt(cfg, 'surffile', 'surface_white_both.mat'); % use a triangulation that corresponds with the collin27 anatomical template in MNI coordinates
    cfg.surfinflated   = ft_getopt(cfg, 'surfinflated',  []);
    cfg.sphereradius   = ft_getopt(cfg, 'sphereradius',  []);
    cfg.projvec        = ft_getopt(cfg, 'projvec',       1);
    cfg.projweight     = ft_getopt(cfg, 'projweight',    ones(size(cfg.projvec)));
    cfg.projcomb       = ft_getopt(cfg, 'projcomb',      'mean'); % or max
    cfg.projthresh     = ft_getopt(cfg, 'projthresh',    []);
    cfg.projmethod     = ft_getopt(cfg, 'projmethod',    'nearest');
    cfg.distmat        = ft_getopt(cfg, 'distmat',       []);
    cfg.camlight       = ft_getopt(cfg, 'camlight',      'yes');
    cfg.facecolor      = ft_getopt(cfg, 'facecolor',    []);
    cfg.vertexcolor    = ft_getopt(cfg, 'vertexcolor',   'curv'); % curvature-dependent mix of cortex_light and cortex_dark
    cfg.edgecolor      = ft_getopt(cfg, 'edgecolor',     'none');

    % determine whether the source functional already contains a triangulation
    interpolate2surf = 0;
    if ~isUnstructuredFun
      % no triangulation present: interpolation should be performed
      fprintf('The source functional is defined on a 3D grid, interpolation to a surface mesh will be performed\n');
      interpolate2surf = 1;
    elseif isUnstructuredFun && isfield(functional, 'tri')
      fprintf('The source functional is defined on a triangulated surface, using the surface mesh description in the functional\n');
    elseif isUnstructuredFun
      % add a transform field to the functional
      fprintf('The source functional does not contain a triangulated surface, we may need to interpolate to a surface mesh\n');
      functional.transform = pos2transform(functional.pos);
      interpolate2surf = 1;
    end

    if interpolate2surf
      % deal with the interpolation
      % FIXME this should be dealt with by ft_sourceinterpolate

      % read the triangulated cortical surface from file
      surf = ft_read_headshape(cfg.surffile);
      if isfield(surf, 'transform')
        % compute the vertex positions in head coordinates
        surf.pos = ft_warp_apply(surf.transform, surf.pos);
      end
      % ensure that the surface is formatted properly
      surf = ft_checkdata(surf, 'hasunit', isfield(functional, 'unit'), 'hascoordsys', isfield(functional, 'coordsys'));
      if isfield(functional, 'unit')
        % ensure that the units are consistent, convert the units if required
        surf = ft_convert_units(surf, functional.unit);
      end
      if isfield(functional, 'coordsys') && isfield(surf, 'coordsys')
        % ensure that the coordinate systems match
        functional = fixcoordsys(functional);
        surf       = fixcoordsys(surf);
        assert(isequal(functional.coordsys, surf.coordsys), 'coordinate systems do not match');
      else
        ft_notice('assuming that the coordinate systems match');
      end

      % downsample the cortical surface
      if cfg.surfdownsample > 1
        if ~isempty(cfg.surfinflated)
          ft_error('downsampling the surface is not possible in combination with an inflated surface');
        end
        fprintf('downsampling surface from %d vertices\n', size(surf.pos,1));
        [temp.tri, temp.pos] = reducepatch(surf.tri, surf.pos, 1/cfg.surfdownsample);
        % find indices of retained patch faces
        [dummy, idx] = ismember(temp.pos, surf.pos, 'rows');
        idx(idx==0)  = [];
        surf.tri = temp.tri;
        surf.pos = temp.pos;
        clear temp
        % downsample other fields
        if isfield(surf, 'curv'),       surf.curv       = surf.curv(idx);       end
        if isfield(surf, 'sulc'),       surf.sulc       = surf.sulc(idx);       end
        if isfield(surf, 'hemisphere'), surf.hemisphere = surf.hemisphere(idx); end
        if isfield(surf, 'inside'),     surf.inside     = surf.inside(idx);     end
      end

      % these are required
      if ~isfield(functional, 'inside')
        functional.inside = true(dim);
      end

      fprintf('%d voxels in functional data\n', prod(dim));
      fprintf('%d vertices in cortical surface\n', size(surf.pos,1));

      tmpcfg = [];
      tmpcfg.parameter = {cfg.funparameter};
      if ~isempty(cfg.maskparameter)
        % it was specified by the user
        tmpcfg.parameter = [tmpcfg.parameter {cfg.maskparameter}];
        maskparameter    = cfg.maskparameter;
      elseif hasmsk
        % it was constructed on the fly
        functional.mask  = msk;
        tmpcfg.parameter = [tmpcfg.parameter {'mask'}];
        maskparameter    = 'mask'; % temporary variable
      end
      tmpcfg.interpmethod = cfg.projmethod;
      tmpcfg.distmat      = cfg.distmat;
      tmpcfg.sphereradius = cfg.sphereradius;
      tmpcfg.projvec      = cfg.projvec;
      tmpcfg.projcomb     = cfg.projcomb;
      tmpcfg.projweight   = cfg.projweight;
      tmpcfg.projthresh   = cfg.projthresh;
      tmpdata             = ft_sourceinterpolate(tmpcfg, functional, surf);

      if hasfun, val      = getsubfield(tmpdata, cfg.funparameter);  val     = val(:);     end
      if hasmsk, maskval  = getsubfield(tmpdata, maskparameter);     maskval = maskval(:); end

      if ~isempty(cfg.projthresh) && hasmsk
        maskval(abs(val) < cfg.projthresh*max(abs(val(:)))) = 0;
      end

    else
      surf     = [];
      surf.pos = functional.pos;
      surf.tri = functional.tri;

      % if hasfun, val     = fun(functional.inside(:)); end
      % if hasmsk, maskval = msk(functional.inside(:)); end
      if hasfun, val     = fun(:); end
      if hasmsk, maskval = msk(:); end

    end

    if ~isempty(cfg.surfinflated)
      if ~isstruct(cfg.surfinflated)
        % read the inflated triangulated cortical surface from file
        surf = ft_read_headshape(cfg.surfinflated);
      else
        surf = cfg.surfinflated;
        if isfield(surf, 'transform')
          % compute the surface vertices in head coordinates
          surf.pos = ft_warp_apply(surf.transform, surf.pos);
        end
      end
    end

    %------do the plotting
    if ~hasfun
      ft_plot_mesh(surf,'edgecolor', cfg.edgecolor, 'facecolor', cfg.facecolor, 'vertexcolor', cfg.vertexcolor);

    elseif hasfun
      if ~hasmsk || all(maskval(:)==1)
        ft_plot_mesh(surf,'edgecolor', cfg.edgecolor, 'facecolor', cfg.facecolor, 'vertexcolor', val, 'clim', [fcolmin fcolmax], 'colormap', cfg.funcolormap);

      elseif hasmsk
        switch cfg.maskstyle
          case 'opacity'
            % this needs to be handled here, rather than in ft_plot_mesh,
            % because the latter function needs to be called twice: once
            % for drawing the background, with possibly a user-specified
            % background color, and the second time for the functional data
            ft_plot_mesh(surf, 'edgecolor', cfg.edgecolor, 'facecolor', cfg.facecolor, 'vertexcolor', cfg.vertexcolor);
            ft_plot_mesh(surf, 'edgecolor', cfg.edgecolor, 'facecolor', cfg.facecolor, 'vertexcolor', val, 'facealpha', maskval, 'clim', [fcolmin fcolmax], 'alphalim', [opacmin opacmax], 'alphamap', cfg.opacitymap, 'colormap', cfg.funcolormap, 'maskstyle', 'opacity');

          case 'colormix'
            % convert the specification of the background color + functional
            % color + opacity into a single rgb value to speed up the rendering
            ft_plot_mesh(surf, 'edgecolor', cfg.edgecolor, 'facecolor', cfg.facecolor, 'vertexcolor', val, 'facealpha', maskval, 'clim', [fcolmin fcolmax], 'alphalim', [opacmin opacmax], 'alphamap', cfg.opacitymap, 'colormap', cfg.funcolormap, 'maskstyle', 'colormix');

        end
      end
    end

    lighting gouraud

    if istrue(cfg.camlight)
      camlight
    end

    if istrue(cfg.colorbar)
      if hasfun
        % use a normal MATLAB colorbar
        hc = colorbar;
        if strcmp(cfg.maskstyle, 'opacity')
          % functional values are according to original input values
          set(hc, 'YLim', [fcolmin fcolmax]);
          ylabel(hc, cfg.colorbartext);
        else
          % functional values have been transformed to be scaled
        end
      else
        ft_warning('no colorbar possible without functional data');
        cfg.colorbar = 'no'; % prevent the warning from apearing twice
      end
    end

  case 'glassbrain'
    assert(~hastime, 'method "%s" does not support time', cfg.method);
    assert(~hasfreq, 'method "%s" does not support freq', cfg.method);

    % This is implemented using a recursive call with an updated functional data
    % structure. The functional volume is replaced by a volume in which the maxima
    % are projected to the "edge" of the volume.

    tmpfunctional = keepfields(functional, {'dim', 'transform', 'unit', 'coordsys'});

    if hasfun
      if isfield(functional, 'inside')
        fun(~functional.inside) = nan;
      end
      fun(1,:,:) = max(fun, [], 1); % get the projection along the 1st dimension
      fun(:,1,:) = max(fun, [], 2); % get the projection along the 2nd dimension
      fun(:,:,1) = max(fun, [], 3); % get the projection along the 3rd dimension
      tmpfunctional.(cfg.funparameter) = fun;
    end

    if hasana
      if isfield(functional, 'inside')
        % ana(~functional.inside) = nan;
      end
      ana(1,:,:) = max(ana, [], 1); % get the projection along the 1st dimension
      ana(:,1,:) = max(ana, [], 2); % get the projection along the 2nd dimension
      ana(:,:,1) = max(ana, [], 3); % get the projection along the 3rd dimension
      tmpfunctional.(cfg.anaparameter) = ana;
    end

    tmpcfg                      = keepfields(cfg, {'anaparameter', 'funparameter', 'funcolorlim', 'funcolormap', 'opacitylim', 'axis', 'renderer', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
    tmpcfg.method               = 'ortho';
    tmpcfg.location             = [1 1 1];
    tmpcfg.locationcoordinates  = 'voxel';
    tmpcfg.interactive          = 'no';
    ft_sourceplot(tmpcfg, tmpfunctional);

  case 'vertex'
    assert(~hastime, 'method "%s" does not support time', cfg.method);
    assert(~hasfreq, 'method "%s" does not support freq', cfg.method);

    if isUnstructuredFun
      pos = functional.pos;
    else
      [X, Y, Z] = ndgrid(1:dim(1), 1:dim(2), 1:dim(3));
      pos = ft_warp_apply(functional.transform, [X(:) Y(:) Z(:)]);
    end

    if isfield(functional, 'inside')
      pos = pos(functional.inside,:);
      if hasfun
        fun = fun(functional.inside);
      end
    end

    % scale the functional data between -30 and 30
    fun = 30*fun/max(abs(fun(:)));
    if any(fun<=0)
      ft_warning('using red for positive and blue for negative functional values')
      col = zeros(numel(fun), 3); % RGB
      col(fun>0,1) = 1;  % red
      col(fun<0,3) = 1;  % blue
      fun(fun==0) = eps; % these will be black
      ft_plot_mesh(pos, 'vertexsize', abs(fun), 'vertexcolor', col);
    else
      ft_plot_mesh(pos, 'vertexsize', fun, 'vertexcolor', 'k');
    end

    % ensure that the axes don't change if you rotate
    axis vis3d

  case 'cloud'
    assert(~hastime, 'method "%s" does not support time', cfg.method);
    assert(~hasfreq, 'method "%s" does not support freq', cfg.method);

    % some defaults depend on the geometrical units
    scale = ft_scalingfactor('mm', functional.unit);

    % set the defaults for method=cloud
    cfg.cloudtype          = ft_getopt(cfg, 'cloudtype', 'cloud');
    cfg.scalerad           = ft_getopt(cfg, 'scalerad', 'yes');
    cfg.ptsize             = ft_getopt(cfg, 'ptsize', 1);
    cfg.ptdensity          = ft_getopt(cfg, 'ptdensity', 20);
    cfg.ptgradient         = ft_getopt(cfg, 'ptgradient', .5);
    cfg.colorgrad          = ft_getopt(cfg, 'colorgrad', 'white');
    cfg.marker             = ft_getopt(cfg, 'marker', '.');
    cfg.slice              = ft_getopt(cfg, 'slice', 'none');
    cfg.ori                = ft_getopt(cfg, 'ori', 'y');
    cfg.slicepos           = ft_getopt(cfg, 'slicepos', 'auto');
    cfg.nslices            = ft_getopt(cfg, 'nslices', 1);
    cfg.minspace           = ft_getopt(cfg, 'minspace', 1);
    cfg.intersectcolor     = ft_getopt(cfg, 'intersectcolor', {'k'});
    cfg.intersectlinestyle = ft_getopt(cfg, 'intersectlinestyle', {'-'});
    cfg.intersectlinewidth = ft_getopt(cfg, 'intersectlinewidth', 2);
    cfg.ncirc              = ft_getopt(cfg, 'ncirc', 15);
    cfg.scalealpha         = ft_getopt(cfg, 'scalealpha', 'no');
    cfg.facecolor          = ft_getopt(cfg, 'facecolor', [0.781 0.762 0.664]);
    cfg.edgecolor          = ft_getopt(cfg, 'edgecolor', 'none');
    cfg.facealpha          = ft_getopt(cfg, 'facealpha', 1);
    cfg.edgealpha          = ft_getopt(cfg, 'edgealpha', 0);
    cfg.vertexcolor        = ft_getopt(cfg, 'vertexcolor', 'curv'); % curvature-dependent mix of cortex_light and cortex_dark
    if ~hasanatomical; anatomical = {}; end

    if isUnstructuredFun
      pos = functional.pos;
    else
      [X, Y, Z] = ndgrid(1:dim(1), 1:dim(2), 1:dim(3));
      pos = ft_warp_apply(functional.transform, [X(:) Y(:) Z(:)]);
    end

    if hasmsk
      pos = pos(logical(msk),:);
      if hasfun
        fun = fun(logical(msk));
      end
    end

    if strcmp(cfg.cloudtype, 'cloud') || strcmp(cfg.cloudtype, 'surf')
      % set the defaults for cloudtype=cloud & cloudtype=surf
      cfg.radius = ft_getopt(cfg, 'radius', 4*scale);
      cfg.rmin   = ft_getopt(cfg, 'rmin', 1*scale);

      ft_plot_cloud(pos, fun, 'mesh', anatomical,...
        'radius', cfg.radius, 'rmin', cfg.rmin, 'scalerad', cfg.scalerad, ...
        'ptsize', cfg.ptsize, 'ptdensity', cfg.ptdensity, 'ptgradient', cfg.ptgradient,...
        'colorgrad', cfg.colorgrad, 'colormap', cfg.funcolormap, 'clim', [fcolmin fcolmax], ...
        'unit', functional.unit, 'slice', cfg.slice, 'cloudtype', cfg.cloudtype, ...
        'ori', cfg.ori, 'slicepos', cfg.slicepos, 'nslices', cfg.nslices, 'minspace', cfg.minspace,...
        'intersectcolor', cfg.intersectcolor, 'intersectlinestyle', cfg.intersectlinestyle, ...
        'intersectlinewidth', cfg.intersectlinewidth, 'ncirc', cfg.ncirc, ...
        'scalealpha', cfg.scalealpha, 'facecolor', cfg.facecolor, 'edgecolor', cfg.edgecolor,...
        'facealpha', cfg.facealpha, 'edgealpha', cfg.edgealpha, 'marker', cfg.marker,...
        'vertexcolor', cfg.vertexcolor);

    elseif strcmp(cfg.cloudtype, 'point')
      if strcmp(cfg.slice, '2d') || strcmp(cfg.slice, '3d')
        error('slices are not supported for cloudtype=''point''')
      end

      % set the defaults for cloudtype=point
      cfg.radius = ft_getopt(cfg, 'radius', 40*scale);
      cfg.rmin   = ft_getopt(cfg, 'rmin', 10*scale);

      % functional scaling
      cmap    = cfg.funcolormap;
      cmid    = size(cmap,1)/2;                            % colorbar middle
      clim    = [fcolmin fcolmax];                         % color limits
      colscf  = 2*( (fun-clim(1)) / (clim(2)-clim(1)) )-1; % color between -1 and 1, bottom vs. top colorbar
      colscf(colscf>1)=1; colscf(colscf<-1)=-1;            % clip values outside the [-1 1] range
      radscf  = fun-(min(abs(fun)) * sign(max(fun)));      % radius between 0 and 1, small vs. large pos/neg effect
      radscf  = abs( radscf / max(abs(radscf)) );

      if strcmp(cfg.scalerad, 'yes')
        rmax = cfg.rmin+(cfg.radius-cfg.rmin)*radscf; % maximum radius of the clouds
      else
        rmax = ones(length(pos), 1)*cfg.radius; % each cloud has the same radius
      end

      % plot functional
      for n = 1:size(pos,1) % sensor loop
        indx  = ceil(cmid) + sign(colscf(n))*floor(abs(colscf(n)*cmid));
        indx  = max(min(indx,size(cmap,1)),1);  % index should fall within the colormap
        fcol  = cmap(indx,:);                   % color [Nx3]
        hold on; plot3(pos(n,1), pos(n,2), pos(n,3), 'Marker', cfg.marker, 'MarkerSize', rmax(n), 'Color', fcol, 'Linestyle', 'none');
      end

      % plot anatomical
      if hasanatomical
        ft_plot_mesh(anatomical, 'facecolor', cfg.facecolor, 'EdgeColor', cfg.edgecolor, 'facealpha', cfg.facealpha, 'edgealpha', cfg.edgealpha, 'vertexcolor', cfg.vertexcolor);
        material dull
      end

      % color settings
      ft_colormap(cmap);
      if ~isempty(clim) && clim(2)>clim(1)
        caxis(gca, clim);
      end
    end

    if istrue(cfg.colorbar)
      if ~strcmp(cfg.slice, '2d')
        c = colorbar;
      else % position the colorbar so that it does not change the axis of the last subplot
        subplotpos = get(subplot(cfg.nslices,1,cfg.nslices), 'Position'); % position of the bottom or rightmost subplot
        c = colorbar('Position', [subplotpos(1)+subplotpos(3)+0.01 subplotpos(2) .03 subplotpos(2)+subplotpos(4)*(cfg.nslices+.1)]);
      end
      ylabel(c, cfg.colorbartext);
    end

  otherwise
    ft_error('unsupported method "%s"', cfg.method);
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

% 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');

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

% add a menu to the figure, the subplots are well-controlled in this case
menu_fieldtrip(gcf, cfg, true);

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

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_redraw(h, eventdata)

h   = getparent(h);
opt = getappdata(h, 'opt');

if ~opt.interactive && ~opt.init
  % this applies with method=glassbrain when calling cb_redraw for the 2nd time
  return
end

curr_ax = get(h, 'currentaxes');
tag = get(curr_ax, 'tag');

functional = opt.functional;

h1 = opt.handlesaxes(1);
h2 = opt.handlesaxes(2);
h3 = opt.handlesaxes(3);

xi = opt.ijk(1);
yi = opt.ijk(2);
zi = opt.ijk(3);
qi = opt.qi;

if any([xi yi zi] > functional.dim) || any([xi yi zi] <= 0)
  return;
end

opt.ijk = [xi yi zi 1]';
if opt.usedim
  xyz = functional.transform * opt.ijk;
elseif opt.usepos
  ix  = sub2ind(opt.dim,xi,yi,zi);
  xyz = functional.pos(ix,:);
end
opt.ijk = opt.ijk(1:3);

% construct a number of strings that will be shown in the lower right panel
str0 = sprintf('voxel number %d', sub2ind(functional.dim(1:3), xi, yi, zi));
str1 = sprintf('voxel indices [%d %d %d]', opt.ijk);

if isfield(functional, 'unit')
  if isfield(functional, 'coordsys') && ~isempty(functional.coordsys) && ~isequal(functional.coordsys, 'unknown')
    switch functional.unit
      case 'm'
        str2 = sprintf('location in %s coordinates : [%.3f %.3f %.3f] m', functional.coordsys, xyz(1:3));
      case 'cm'
        str2 = sprintf('location in %s coordinates : [%.1f %.1f %.1f] cm', functional.coordsys, xyz(1:3));
      case 'mm'
        str2 = sprintf('location in %s coordinates : [%.0f %.0f %.0f] mm', functional.coordsys, xyz(1:3));
      otherwise
        str2 = sprintf('location in %s coordinates : [%f %f %f] %s', functional.coordsys, xyz(1:3), functional.unit);
    end
  else
    switch functional.unit
      case 'm'
        str2 = sprintf('location : [%.3f %.3f %.3f] m', xyz(1:3));
      case 'cm'
        str2 = sprintf('location : [%.1f %.1f %.1f] cm', xyz(1:3));
      case 'mm'
        str2 = sprintf('location : [%.0f %.0f %.0f] mm', xyz(1:3));
      otherwise
        str2 = sprintf('location : [%f %f %f] %s', xyz(1:3), functional.unit);
    end
  end
else
  if isfield(functional, 'coordsys') && ~isempty(functional.coordsys) && ~isequal(functional.coordsys, 'unknown')
    str2 = sprintf('location in %s coordinates : [%f %f %f]', functional.coordsys, xyz(1:3));
  else
    str2 = sprintf('location : [%f %f %f]', xyz(1:3));
  end
end

if opt.hasfun
  if ~opt.hasfreq && ~opt.hastime
    val = opt.fun(xi, yi, zi);
  elseif ~opt.hasfreq && opt.hastime
    val = opt.fun(xi, yi, zi, opt.qi);
  elseif opt.hasfreq && ~opt.hastime
    val = opt.fun(xi, yi, zi, opt.qi);
  elseif opt.hasfreq && opt.hastime
    val = opt.fun(xi, yi, zi, opt.qi(1), opt.qi(2));
  end
  str3 = sprintf('value %f', val);
else
  str3 = '';
end

if opt.hasfreq && opt.hastime
  str4 = sprintf('%.1f s, %.1f Hz', functional.time(opt.qi(2)), functional.freq(opt.qi(1)));
elseif ~opt.hasfreq && opt.hastime
  str4 = sprintf('%.1f s', functional.time(opt.qi(1)));
elseif opt.hasfreq && ~opt.hastime
  str4 = sprintf('%.1f Hz', functional.freq(opt.qi(1)));
else
  str4 = '';
end

if opt.hasatlas
  % determine the anatomical label of the current position
  lab = atlas_lookup(opt.atlas, (xyz(1:3)), 'coordsys', functional.coordsys, 'queryrange', opt.queryrange);
  if isempty(lab)
    str5 = sprintf('atlas label: n/a');
  else
    lab = unique(lab);
    tmp = sprintf('%s', strrep(lab{1}, '_', ' '));
    for i=2:length(lab)
      tmp = [tmp sprintf(', %s', strrep(lab{i}, '_', ' '))];
    end
    str5 = sprintf('atlas label: %s', tmp);
  end
else
  str5 = '';
end

if opt.hasana
  plotopt = {};
  plotopt = ft_setopt(plotopt, 'transform', eye(4)); % use an identity transform
  plotopt = ft_setopt(plotopt, 'location', opt.ijk); % the position is expressed in voxels
  plotopt = ft_setopt(plotopt, 'style', 'subplot');
  plotopt = ft_setopt(plotopt, 'update', opt.update);
  plotopt = ft_setopt(plotopt, 'doscale', false);
  plotopt = ft_setopt(plotopt, 'clim', opt.clim);
  plotopt = ft_setopt(plotopt, 'intersectmesh', opt.intersectmesh);

  if isfield(functional, 'coordsys')
    % determine the coordsys that matches the volume with an (approximate) identity transform
    dum = align_xyz2ijk(functional);
    plotopt = ft_setopt(plotopt, 'coordsys', dum.coordsys);
  end

  if opt.init
    plotopt = ft_setopt(plotopt, 'parents', [h1 h2 h3]);
    ft_plot_ortho(opt.ana, plotopt{:});

    opt.anahandles = findobj(opt.handlesfigure, 'type', 'surface')';
    for i=1:length(opt.anahandles)
      opt.parenttag{i} = get(get(opt.anahandles(i), 'parent'), 'tag');
    end
    [i1,i2,i3] = intersect(opt.parenttag, {'ik' 'jk' 'ij'});
    opt.anahandles = opt.anahandles(i3(i2)); % seems like swapping the order
    opt.anahandles = opt.anahandles(:)';
    set(opt.anahandles, 'tag', 'ana');
    if ~isempty(opt.intersectmesh)
      opt.patchhandles = findobj(opt.handlesfigure, 'type', 'patch');
      opt.patchhandles = opt.patchhandles(i3(i2));
      opt.patchhandles = opt.patchhandles(:)';
      set(opt.patchhandles, 'tag', 'patch');
    end
  else
    plotopt = cat(2, plotopt, {'surfhandle', opt.anahandles});
    if ~isempty(opt.intersectmesh)
      plotopt = cat(2, plotopt, {'patchhandle', opt.patchhandles});
    end
    ft_plot_ortho(opt.ana, plotopt{:});
  end
end

if opt.hasfun
  plotopt = {};
  plotopt = ft_setopt(plotopt, 'transform', eye(4));
  plotopt = ft_setopt(plotopt, 'location', opt.ijk);
  plotopt = ft_setopt(plotopt, 'style', 'subplot');
  plotopt = ft_setopt(plotopt, 'update', opt.update);
  plotopt = ft_setopt(plotopt, 'clim', [opt.fcolmin opt.fcolmax]);
  plotopt = ft_setopt(plotopt, 'colormap', opt.funcolormap);
  plotopt = ft_setopt(plotopt, 'intersectmesh', opt.intersectmesh);

  if opt.init
    if isequal(opt.funcolormap, 'rgb')
      tmpfun = opt.fun;
      if opt.hasmsk
        tmpmask = opt.msk;
      end
    else
      tmpqi  = [opt.qi 1];
      tmpfun = opt.fun(:,:,:,tmpqi(1),tmpqi(2));
      if opt.hasmsk
        tmpmask = opt.msk(:,:,:,tmpqi(1),tmpqi(2));
      end
    end

    if opt.hasmsk
      plotopt = ft_setopt(plotopt, 'datmask', tmpmask);
      plotopt = ft_setopt(plotopt, 'opacitylim', [opt.opacmin opt.opacmax]);
    elseif opt.hasbackground
      % there's a background, in the absence of the mask, the fun should be plotted with an opacity value of 0.5
      plotopt = ft_setopt(plotopt, 'datmask', ones(size(tmpfun))./2);
      plotopt = ft_setopt(plotopt, 'opacitylim', [0 1]);
    end
    if opt.hasbackground
      % the background should always be added, independent of the mask
      plotopt = ft_setopt(plotopt, 'background', opt.background);
      plotopt = ft_setopt(plotopt, 'maskstyle', 'colormix');
    end

    plotopt = ft_setopt(plotopt, 'parents', [h1 h2 h3]);
    ft_plot_ortho(tmpfun, plotopt{:});

    % After the first call, the handles to the functional surfaces exist.
    % Create a variable containing these, and sort according to the parents.
    opt.funhandles = findobj(opt.handlesfigure, 'type', 'surface');
    opt.funtag     = get(opt.funhandles, 'tag');
    opt.funhandles = opt.funhandles(~strcmp('ana', opt.funtag));
    for i=1:length(opt.funhandles)
      opt.parenttag{i} = get(get(opt.funhandles(i), 'parent'), 'tag');
    end
    [i1,i2,i3] = intersect(opt.parenttag, {'ik' 'jk' 'ij'});
    opt.funhandles = opt.funhandles(i3(i2)); % seems like swapping the order
    opt.funhandles = opt.funhandles(:)';
    set(opt.funhandles, 'tag', 'fun');

    if ~opt.hasmsk && opt.hasfun && opt.hasana
      set(opt.funhandles(1), 'facealpha', 0.5);
      set(opt.funhandles(2), 'facealpha', 0.5);
      set(opt.funhandles(3), 'facealpha', 0.5);
    end

  else
    if isequal(opt.funcolormap, 'rgb')
      tmpfun = opt.fun;
      if opt.hasmsk
        tmpmask = opt.msk;
      end
    else
      tmpqi  = [opt.qi 1];
      tmpfun = opt.fun(:,:,:,tmpqi(1),tmpqi(2));
      if opt.hasmsk
        tmpmask = opt.msk(:,:,:,tmpqi(1),tmpqi(2));
      end
    end

    if opt.hasmsk
      plotopt = ft_setopt(plotopt, 'datmask', tmpmask);
      plotopt = ft_setopt(plotopt, 'opacitylim', [opt.opacmin opt.opacmax]);
    elseif opt.hasbackground
      % there's a background, in the absence of the mask, the fun should be plotted with an opacity value of 0.5
      plotopt = ft_setopt(plotopt, 'datmask', ones(size(tmpfun))./2);
      plotopt = ft_setopt(plotopt, 'opacitylim', [0 1]);
    end
    if opt.hasbackground
      % the background should always be added, independent of the mask
      plotopt = ft_setopt(plotopt, 'background', opt.background);
      plotopt = ft_setopt(plotopt, 'maskstyle', 'colormix');
    end

    plotopt = ft_setopt(plotopt, 'surfhandle', opt.funhandles);
    ft_plot_ortho(tmpfun, plotopt{:});
  end
end

% zoom
limits = [0.5 opt.dim(1)+0.5 0.5 opt.dim(2)+0.5 0.5 opt.dim(3)+0.5];
xloadj = ((xi-limits(1))-(xi-limits(1))*opt.zoom);
xhiadj = ((limits(2)-xi)-(limits(2)-xi)*opt.zoom);
yloadj = ((yi-limits(3))-(yi-limits(3))*opt.zoom);
yhiadj = ((limits(4)-yi)-(limits(4)-yi)*opt.zoom);
zloadj = ((zi-limits(5))-(zi-limits(5))*opt.zoom);
zhiadj = ((limits(6)-zi)-(limits(6)-zi)*opt.zoom);
axis(h1, [xi-xloadj xi+xhiadj yi-yloadj yi+yhiadj zi-zloadj zi+zhiadj]);
axis(h2, [xi-xloadj xi+xhiadj yi-yloadj yi+yhiadj zi-zloadj zi+zhiadj]);
axis(h3, [xi-xloadj xi+xhiadj yi-yloadj yi+yhiadj]);

if opt.zoom>0
  % the coordsys labels fall outside the subplots when zoomed in
  delete(findall(h, 'Type', 'text', 'Tag', 'coordsys_label_100'));
  delete(findall(h, 'Type', 'text', 'Tag', 'coordsys_label_010'));
  delete(findall(h, 'Type', 'text', 'Tag', 'coordsys_label_001'));
end

if opt.crosshair
  if opt.init
    hch1 = ft_plot_crosshair([xi        yi-yloadj zi], 'parent', h1); % was [xi 1 zi], now corrected for zoom
    hch2 = ft_plot_crosshair([xi+xhiadj yi        zi], 'parent', h2); % was [opt.dim(1) yi zi], now corrected for zoom
    hch3 = ft_plot_crosshair([xi        yi        zi], 'parent', h3); % was [xi yi opt.dim(3)], now corrected for zoom
    opt.handlescross  = [hch1(:)';hch2(:)';hch3(:)'];
  else
  ft_plot_crosshair([xi        yi-yloadj zi], 'handle', opt.handlescross(1, :));
  ft_plot_crosshair([xi+xhiadj yi        zi], 'handle', opt.handlescross(2, :));
  ft_plot_crosshair([xi        yi        zi], 'handle', opt.handlescross(3, :));
  end
end

set(opt.handlesaxes(1), 'Visible', opt.showaxis);
set(opt.handlesaxes(2), 'Visible', opt.showaxis);
set(opt.handlesaxes(3), 'Visible', opt.showaxis);

if opt.hasfreq && opt.hastime && opt.hasfun
  h4 = subplot(2,2,4);
  tmpdat = double(shiftdim(opt.fun(xi,yi,zi,:,:),3));
  % uimagesc is in external/fileexchange
  ft_hastoolbox('fileexchange', 1);
  uimagesc(double(functional.time), double(functional.freq), tmpdat); axis xy;
  xlabel('time'); ylabel('freq');
  set(h4, 'tag', 'TF1');
  caxis([opt.fcolmin opt.fcolmax]);
elseif opt.hasfreq && opt.hasfun
  h4 = subplot(2,2,4);
  plot(functional.freq, shiftdim(opt.fun(xi,yi,zi,:),3)); xlabel('freq');
  axis([functional.freq(1) functional.freq(end) opt.fcolmin opt.fcolmax]);
  set(h4, 'tag', 'TF2');
elseif opt.hastime && opt.hasfun
  h4 = subplot(2,2,4);
  plot(functional.time, shiftdim(opt.fun(xi,yi,zi,:),3)); xlabel('time');
  set(h4, 'tag', 'TF3', 'xlim',functional.time([1 end]), 'ylim',[opt.fcolmin opt.fcolmax], 'layer', 'top');
elseif strcmp(opt.colorbar,  'yes') && ~isfield(opt, 'hc')
  if opt.hasfun
    % vectorcolorbar = linspace(fscolmin, fcolmax,length(cfg.funcolormap));
    % imagesc(vectorcolorbar,1,vectorcolorbar);ft_colormap(cfg.funcolormap);
    % use a normal MATLAB colorbar, attach it to the invisible 4th subplot
    try
      caxis([opt.fcolmin opt.fcolmax]);
    end

    opt.hc = colorbar;
    set(opt.hc, 'location', 'southoutside');
    set(opt.hc, 'position',[0.06+0.06+opt.h1size(1) 0.06-0.06+opt.h3size(2) opt.h2size(1) 0.06]);
    ylabel(opt.hc, opt.colorbartext);
    try
      set(opt.hc, 'XLim', [opt.fcolmin opt.fcolmax]);
    end
  else
    ft_warning('no colorbar possible without functional data');
    opt.colorbar = 'no'; % prevent the warning from apearing twice
  end
end

if ~((opt.hasfreq && numel(functional.freq)>1) || opt.hastime)
  ht = subplot('position',[0.06+0.06+opt.h1size(1) 0.06 opt.h2size(1) opt.h3size(2)]);
  set(ht, 'visible', 'off');
  if opt.init
    opt.ht0 = text(ht, 0, 0.7, str0);
    opt.ht1 = text(ht, 0, 0.6, str1);
    opt.ht2 = text(ht, 0, 0.5, str2);
    opt.ht3 = text(ht, 0, 0.4, str3);
    opt.ht4 = text(ht, 0, 0.3, str4);
    opt.ht5 = text(ht, 0, 0.2, str5);
  else
    set(opt.ht0, 'String', str0);
    set(opt.ht1, 'String', str1);
    set(opt.ht2, 'String', str2);
    set(opt.ht3, 'String', str3);
    set(opt.ht4, 'String', str4);
    set(opt.ht5, 'String', str5);
  end
end

% make the last current axes current again
sel = findobj('type', 'axes', 'tag',tag);
if ~isempty(sel)
  set(opt.handlesfigure, 'currentaxes', sel(1));
end

if opt.init
  opt.init = false;
  setappdata(h, 'opt', opt);
end

set(h, 'currentaxes', curr_ax);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_help(h, eventdata)

h   = getparent(h);
opt = getappdata(h, 'opt');

if opt.interactive
  fprintf('\n');
  fprintf('==================================================================================\n');
  fprintf('Press "h" to show this help.\n');
  fprintf('Press "1", "2", or "3" to switch to the corresponding subplot.\n');
  fprintf('Use the arrow keys to navigate in the current axis.\n');
  fprintf('Click left mouse button to reposition the cursor.\n');
  fprintf('Click and hold right mouse button to update the position while moving the mouse.\n');
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_keyboard(h, eventdata)

if isempty(eventdata)
  % determine the key that corresponds to the uicontrol element that was activated
  key = get(h, 'userdata');
else
  % determine the key that was pressed on the keyboard
  key = parsekeyboardevent(eventdata);
end

% get focus back to figure
if ~strcmp(get(h, 'type'), 'figure')
  set(h, 'enable', 'off');
  drawnow;
  set(h, 'enable', 'on');
end

h   = getparent(h);
opt = getappdata(h, 'opt');

curr_ax = get(h, 'currentaxes');
tag     = get(curr_ax, 'tag');

if isempty(key)
  % this happens if you press the apple key
  key = '';
end

% the following code is largely shared by FT_SOURCEPLOT, FT_VOLUMEREALIGN, FT_INTERACTIVEREALIGN, FT_MESHREALIGN, FT_ELECTRODEPLACEMENT
switch key
  case {'' 'shift+shift' 'alt-alt' 'control+control' 'command-0'}
    % do nothing

  case '1'
    subplot(opt.handlesaxes(1));

  case '2'
    subplot(opt.handlesaxes(2));

  case '3'
    subplot(opt.handlesaxes(3));

  case 'h'
    cb_help(h);

  case 'q'
    setappdata(h, 'opt', opt);
    cb_quit(h);

  case {'i' 'j' 'k' 'm' 28 29 30 31 'leftarrow' 'rightarrow' 'uparrow' 'downarrow'} % TODO FIXME use leftarrow rightarrow uparrow downarrow
    % update the view to a new position
    if     strcmp(tag, 'ik') && (strcmp(key, 'i') || strcmp(key, 'uparrow')    || isequal(key, 30)), opt.ijk(3) = opt.ijk(3)+1; opt.update = [0 0 1];
    elseif strcmp(tag, 'ik') && (strcmp(key, 'j') || strcmp(key, 'leftarrow')  || isequal(key, 28)), opt.ijk(1) = opt.ijk(1)-1; opt.update = [0 1 0];
    elseif strcmp(tag, 'ik') && (strcmp(key, 'k') || strcmp(key, 'rightarrow') || isequal(key, 29)), opt.ijk(1) = opt.ijk(1)+1; opt.update = [0 1 0];
    elseif strcmp(tag, 'ik') && (strcmp(key, 'm') || strcmp(key, 'downarrow')  || isequal(key, 31)), opt.ijk(3) = opt.ijk(3)-1; opt.update = [0 0 1];
    elseif strcmp(tag, 'ij') && (strcmp(key, 'i') || strcmp(key, 'uparrow')    || isequal(key, 30)), opt.ijk(2) = opt.ijk(2)+1; opt.update = [1 0 0];
    elseif strcmp(tag, 'ij') && (strcmp(key, 'j') || strcmp(key, 'leftarrow')  || isequal(key, 28)), opt.ijk(1) = opt.ijk(1)-1; opt.update = [0 1 0];
    elseif strcmp(tag, 'ij') && (strcmp(key, 'k') || strcmp(key, 'rightarrow') || isequal(key, 29)), opt.ijk(1) = opt.ijk(1)+1; opt.update = [0 1 0];
    elseif strcmp(tag, 'ij') && (strcmp(key, 'm') || strcmp(key, 'downarrow')  || isequal(key, 31)), opt.ijk(2) = opt.ijk(2)-1; opt.update = [1 0 0];
    elseif strcmp(tag, 'jk') && (strcmp(key, 'i') || strcmp(key, 'uparrow')    || isequal(key, 30)), opt.ijk(3) = opt.ijk(3)+1; opt.update = [0 0 1];
    elseif strcmp(tag, 'jk') && (strcmp(key, 'j') || strcmp(key, 'leftarrow')  || isequal(key, 28)), opt.ijk(2) = opt.ijk(2)-1; opt.update = [1 0 0];
    elseif strcmp(tag, 'jk') && (strcmp(key, 'k') || strcmp(key, 'rightarrow') || isequal(key, 29)), opt.ijk(2) = opt.ijk(2)+1; opt.update = [1 0 0];
    elseif strcmp(tag, 'jk') && (strcmp(key, 'm') || strcmp(key, 'downarrow')  || isequal(key, 31)), opt.ijk(3) = opt.ijk(3)-1; opt.update = [0 0 1];
    else
      % do nothing
    end
    setappdata(h, 'opt', opt);
    cb_redraw(h);

  case {43 'add' 'shift+equal'}  % + or numpad +
    % contrast scaling
    if isempty(opt.clim)
      opt.clim = [min(opt.ana(:)) max(opt.ana(:))];
    end
    % reduce color scale range by 5%
    cscalefactor = (opt.clim(2)-opt.clim(1))/10;
    %opt.clim(1) = opt.clim(1)+cscalefactor;
    opt.clim(2) = opt.clim(2)-cscalefactor;
    setappdata(h, 'opt', opt);
    cb_redraw(h);

  case {45 'subtract' 'hyphen' 'shift+hyphen'} % - or numpad -
    % contrast scaling
    if isempty(opt.clim)
      opt.clim = [min(opt.ana(:)) max(opt.ana(:))];
    end
    % increase color scale range by 5%
    cscalefactor = (opt.clim(2)-opt.clim(1))/10;
    %opt.clim(1) = opt.clim(1)-cscalefactor;
    opt.clim(2) = opt.clim(2)+cscalefactor;
    setappdata(h, 'opt', opt);
    cb_redraw(h);

  case 'pageup'
    opt.zoom = opt.zoom + 0.1;
    opt.zoom = min(opt.zoom, 0.9); % between 0 and 0.9
    fprintf('increasing zoom to %f\n', opt.zoom);
    setappdata(h, 'opt', opt);
    cb_redraw(h);

  case 'pagedown'
    opt.zoom = opt.zoom - 0.1;
    opt.zoom = max(opt.zoom, 0); % between 0 and 0.9
    fprintf('decreasing zoom to %f\n', opt.zoom);
    setappdata(h, 'opt', opt);
    cb_redraw(h);

  otherwise
    % do nothing

end % switch key

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_buttonpress(h, eventdata)

h   = getparent(h);
cb_getposition(h);

switch get(h, 'selectiontype')
  case 'normal'
    % just update to new position, nothing else to be done here
    cb_redraw(h);
  case 'alt'
    set(h, 'windowbuttonmotionfcn', @cb_tracemouse);
    cb_redraw(h);
  otherwise
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_buttonrelease(h, eventdata)

set(h, 'windowbuttonmotionfcn', '');

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_tracemouse(h, eventdata)

h   = getparent(h);
cb_getposition(h);
cb_redraw(h);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_getposition(h, eventdata)

h   = getparent(h);
opt = getappdata(h, 'opt');

curr_ax = get(h,       'currentaxes');
pos     = mean(get(curr_ax, 'currentpoint'));

tag = get(curr_ax, 'tag');

if ~isempty(tag) && ~opt.init
  if strcmp(tag, 'ik')
    opt.ijk([1 3])  = round(pos([1 3]));
    opt.update = [1 1 1];
  elseif strcmp(tag, 'ij')
    opt.ijk([1 2])  = round(pos([1 2]));
    opt.update = [1 1 1];
  elseif strcmp(tag, 'jk')
    opt.ijk([2 3])  = round(pos([2 3]));
    opt.update = [1 1 1];
  elseif strcmp(tag, 'TF1')
    % timefreq
    opt.qi(2) = nearest(opt.functional.time, pos(1));
    opt.qi(1) = nearest(opt.functional.freq, pos(2));
    opt.update = [1 1 1];
  elseif strcmp(tag, 'TF2')
    % freq only
    opt.qi  = nearest(opt.functional.freq, pos(1));
    opt.update = [1 1 1];
  elseif strcmp(tag, 'TF3')
    % time only
    opt.qi  = nearest(opt.functional.time, pos(1));
    opt.update = [1 1 1];
  end
end
opt.ijk = min(opt.ijk(:)', opt.dim);
opt.ijk = max(opt.ijk(:)', [1 1 1]);

setappdata(h, 'opt', opt);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cb_quit(h, eventdata)

% opt = getappdata(h, 'opt');
% opt.quit = true;
% setappdata(h, 'opt', opt);
% uiresume
delete(h);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function h = getparent(h)
p = h;
while p~=0
  h = p;
  p = get(h, 'parent');
end


classdef PTKLungROI < PTKPlugin
    % PTKLungROI. Plugin for finding the lung region of interest.
    %
    %     This is a plugin for the Pulmonary Toolkit. Plugins can be run using
    %     the gui, or through the interfaces provided by the Pulmonary Toolkit.
    %     See PTKPlugin.m for more information on how to run plugins.
    %
    %     Plugins should not be run directly from your code.
    %
    %     PTKLungROI runs the library function PTKGetLungROI to find the region of
    %     interest for the lung image.
    %
    %
    %     Licence
    %     -------
    %     Part of the TD Pulmonary Toolkit. https://github.com/tomdoel/pulmonarytoolkit
    %     Author: Tom Doel, 2012.  www.tomdoel.com
    %     Distributed under the GNU GPL v3 licence. Please see website for details.
    %

    properties
        ButtonText = 'Lungs & Airways<BR>ROI'
        ToolTip = 'Change the context to display the lungs and airways'
        Category = 'Context'

        AllowResultsToBeCached = true
        AlwaysRunPlugin = false
        PluginType = 'ReplaceImage'
        HidePluginInDisplay = false
        FlattenPreviewImage = false
        PTKVersion = '1'
        ButtonWidth = 6
        ButtonHeight = 2
        GeneratePreview = true
        Visibility = 'Developer'
        Version = 3

        MemoryCachePolicy = 'Temporary'
        DiskCachePolicy = 'Permanent'
    end

    methods (Static)
        function results = RunPlugin(dataset, reporting)
            if dataset.IsGasMRI
                results = PTKGetLungROIForGasMRI(dataset.GetResult('PTKOriginalImage', PTKContext.OriginalImage), reporting);
            elseif strcmp(dataset.GetImageInfo.Modality, 'MR')
                lung_threshold = dataset.GetResult('PTKMRILungThreshold', PTKContext.OriginalImage);
                results = dataset.GetResult('PTKOriginalImage', PTKContext.OriginalImage);
                results.ResizeToMatch(lung_threshold.LungMask);
            else
                results = PTKGetLungROIForCT(dataset.GetResult('PTKOriginalImage', PTKContext.OriginalImage), reporting);
            end
        end
    end
end


function rs_imwrite_bands(varargin)
% rs_imwrite - Write an ENVI image with differnt bands with names (Copy Right - Zhe Zhu, Sept. 4th 2018)
% Examples
% rs_imwrite(image,'filename',info,n_names);
% Input
%   image: image matrix
%   filename: File names
%   info: Image properties and cartographic information for ENVI or GeoTIFF file
%   n_names   : Band names 1985:2015 or 'disturb type';

if nargin < 4
    fprintf('Failed! Less Than Three Variables! \n');
    return
elseif nargin == 4
    % Create input variables
    image = varargin{1};
    fname = varargin{2};
    info = varargin{3};
    n_names = varargin{4};

    im_size=size(image);
    im_size(3)=size(image,3);

    % ENVI data format numbers
    d = [1 2 3 4 5 12];

    % Check user input
    if ~ischar(fname)
        error('fname should be a char string');
    end

    cl=class(image);
    switch cl
        case 'uint8'
            t = d(1);
        case 'int16'
            t = d(2);
        case 'int32'
            t = d(3);
        case 'single'
            t = d(4);
        case 'double'
            t = d(5);
        case 'uint16'
            t = d(6);
        otherwise
            error('Data type not recognized');
    end

    % update info
    info.samples = im_size(2);
    info.lines = im_size(1);
    info.bands = im_size(3);
    info.data_type = t;

    try multibandwrite(image,fname,info.interleave);
        fprintf('Write ENVI Images %s',fname);
        fprintf((' . '));
    catch
        fprintf('Images Failed! Not ENVI Variables! \n');
        return
    end

    % Write header file
    fprintf((' . '));
    fid = fopen(strcat(fname,'.hdr'),'w');

    fprintf(fid,'%s \n','ENVI');
    n_field = fieldnames(info);
    n_value = struct2cell(info);

    for i = 1:length(n_field)-1
        n_field{i} = strrep(n_field{i},'_',' ');
        if ischar(n_value{i})
            fprintf(fid,'%s = %s \n',n_field{i},n_value{i});
        else
            fprintf(fid,'%s = %d \n',n_field{i},n_value{i});
        end
    end

    % band names
    fprintf(fid,'band names = {');
    if ischar(n_names)
        fprintf(fid,'%s',n_names);
    else
        for i=1:length(n_names)
            str_n = num2str(n_names(i));
            fprintf(fid,'%s',str_n);
            if i < length(n_names)
                fprintf(fid,',');
            end
        end
    end
    fprintf(fid,'}\n');

        elements={'samples =','lines   =','bands   =',...
        'header offset =','file type =', 'data type =',...
        'interleave =','sensor type =','byte order =',...
        'map info =','coordinate system string =','wavelength units =',...
        'band names ='};

    fprintf(fid,'%s \n','ENVI');

    try
        fprintf(fid,'%s %s \n','description =',info.description);
    catch
        fprintf(fid,'%s %s \n','description =',info.descirption); % to fix the inccorect info made by the old data-preparesion funciton.
    end
    fprintf(fid,'%s %d \n',elements{1,1},im_size(2));
    fprintf(fid,'%s %d \n',elements{1,2},im_size(1));
    fprintf(fid,'%s %d \n',elements{1,3},im_size(3));
    fprintf(fid,'%s %d \n',elements{1,4},info.header_offset);
    fprintf(fid,'%s %s \n',elements{1,5},info.file_type);
    fprintf(fid,'%s %d \n',elements{1,6},t);
    fprintf(fid,'%s %s \n',elements{1,7},info.interleave);
    fprintf(fid,'%s %s \n',elements{1,8},info.sensor_type);
    fprintf(fid,'%s %d \n',elements{1,9},info.byte_order);
    fprintf(fid,'%s %s \n',elements{1,10},info.map_info);
% % %     fprintf(fid,'%s %s \n',elements{1,11},info.coordinate_system_string);
% % %     fprintf(fid,'%s %s \n',elements{1,12},info.wavelength_units);
% % %     fprintf(fid,'%s %s \n',elements{1,13},info.band_names);

    fclose(fid);
    fprintf((' . \n'));
else
    fprintf('Failed! Too Many Variables! \n');
    return
end


function classifier= bbci_apply_evalClassifier(fv, bbci_classifier)
%BBCI_APPLY_EVALCLASSIFIER - Apply classifier to feature vector
%
%Synopsis:
%  CLASSIFIER= bbci_apply_evalClassifier(FV, BBCI_CLASSIFIER)

% 02-2011 Benjamin Blankertz

classifier.x= bbci_classifier.fcn(bbci_classifier.C, fv);
classifier.x= classifier.x(:);


function [gKern1, gKern2, gVarmeans, gVarcovars, gInd] = kernkernVardistPsi2Gradient(kern1, kern2, vardist, Z, covGrad, learnInducing)

% KERNKERNVARDISTPSI2GRADIENT Description

% VARGPLVM

if nargin < 6
    learnInducing = true;
end

if strcmp(kern2.type, kern1.type)
    error('Kern1 and Kern2 cannot be the same')
    return;
elseif ~strcmp(kern1.type, 'white') &  ~strcmp(kern2.type, 'white')
    %
    swap = 0;
    if strcmp(kern2.type, 'rbfard2')
       swap = 1;
    elseif strcmp(kern2.type, 'linard2') & ~strcmp(kern1.type, 'rbfard2')
       swap = 1;
    end
    %
    if swap == 0
       fhandle = str2func([kern1.type kern2.type 'VardistPsi2Gradient']);
       [gKern1, gKern2, gVarmeans, gVarcovars, gInd] = fhandle(kern1, kern2, vardist, Z, covGrad, learnInducing);
    else
       fhandle = str2func([kern2.type kern1.type 'VardistPsi2Gradient']);
       [gKern2, gKern1, gVarmeans, gVarcovars, gInd] = fhandle(kern2, kern1, vardist, Z, covGrad, learnInducing);
    end
    % !!! Do not take transformation here: they applied in the kernkernVardistPsi2Gradient
    %gKern1 = paramTransformPsi2(kern1, gKern1);
    %gKern2 = paramTransformPsi2(kern2, gKern2);
    % variational variances are positive
    %gVarcovars = (gVarcovars(:).*vardist.covars(:))';
   %
else
   %
    % the white kernel gives zeros
    gKern1 = zeros(1,kern1.nParams);
    gKern2 = zeros(1,kern2.nParams);
    gVarmeans = zeros(1,prod(size(vardist.means)));
    gInd = zeros(1,prod(size(Z)));
    gVarcovars = zeros(1,prod(size(vardist.covars)));
   %
end

%%
%-----------------------------------------------------
% This applies transformations
% This must be done similar to kernGradient at some point
function gKern = paramTransformPsi2(kern, gKern)
%
%

if strcmp(kern.type,'rbfard2')
   gKern(1) = gKern(1)*kern.variance;
   gKern(2:end) = gKern(2:end).*kern.inputScales;
elseif strcmp(kern.type,'linard2')
   gKern(1:end) = gKern(1:end).*kern.inputScales;
elseif strcmp(kern.type,'bias')
   gKern = gKern*kern.variance;
end