function varargout = process_evt_detect_threshold( varargin )
% PROCESS_EVT_DETECT_THRESHOLD: Event detection based on a set threshold for a group of recordings file
%
% USAGE:  OutputFiles = process_evt_detect_threshold('Run', sProcess, sInputs)
%                 evt = process_evt_detect_threshold('Compute', F, TimeVector, OPTIONS)
%             OPTIONS = process_evt_detect_threshold('Compute')                         % Get the default options structure

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Elizabeth Bock, Francois Tadel, 2015-2016
%          Raymundo Cassani, 2021

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Detect events above threshold';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = 'Events';
    sProcess.Index       = 45;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/VisualSingle?highlight=%28Detect+events+above+threshold%29#Artifact_correction_with_SSP';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'raw', 'data'};
    sProcess.OutputTypes = {'raw', 'data'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 1;
	% Notice
    sProcess.options.notice.Comment = ['This process creates extended events to indicate all the time
' ...
                                       'samples where the signal is above a given amplitude threshold.
' ...
                                       'For creating simple events, use one of the following processes:
' ...
                                       '"Detect custom events" or "Detect analog triggers".

'];
    sProcess.options.notice.Type    = 'label';
    % Event name
    sProcess.options.eventname.Comment = 'Event name: ';
    sProcess.options.eventname.Type    = 'text';
    sProcess.options.eventname.Value   = 'artifact';
    % Separator
    sProcess.options.separator.Type = 'separator';
    sProcess.options.separator.Comment = ' ';
    % Channel name
    sProcess.options.channelname.Comment = 'Channel name: ';
    sProcess.options.channelname.Type    = 'channelname';
    sProcess.options.channelname.Value   = '';
    % Channel name comment
    sProcess.options.channelhelp.Comment = 'You can use the montage syntax here: "ch1, -ch2"';
    sProcess.options.channelhelp.Type    = 'label';
    % Time window
    sProcess.options.timewindow.Comment = 'Time window:';
    sProcess.options.timewindow.Type    = 'timewindow';
    sProcess.options.timewindow.Value   = [];
    % Blanking period
    sProcess.options.blanking.Comment = 'Min duration between two events: ';
    sProcess.options.blanking.Type    = 'value';
    sProcess.options.blanking.Value   = {0.02, 'ms', []};
    % Threshold maximum
    sProcess.options.thresholdMAX.Comment = 'Maximum threshold: ';
    sProcess.options.thresholdMAX.Type    = 'value';
    sProcess.options.thresholdMAX.Value   = {0.00, '', 2};
    % units
    sProcess.options.label1.Comment = '
Threshold Units:';
    sProcess.options.label1.Type    = 'label';
    sProcess.options.units.Comment = {'None: 100', 'mV: 10-3', 'uV: 10-6', 'fT: 10-15', ''};
    sProcess.options.units.Type    = 'radio_line';
    sProcess.options.units.Value   = 1;
    % Filter
    sProcess.options.label2.Comment = '
Filter signal before detection:';
    sProcess.options.label2.Type    = 'label';
    sProcess.options.bandpass.Comment = 'Frequency band: ';
    sProcess.options.bandpass.Type    = 'range';
    sProcess.options.bandpass.Value   = {[], 'Hz', 2};
    % Use absolute value
    sProcess.options.isAbsolute.Comment = 'Use absolute value of signal';
    sProcess.options.isAbsolute.Type    = 'checkbox';
    sProcess.options.isAbsolute.Value   = 0;
    % Remove DC
    sProcess.options.isDCremove.Comment = 'Remove DC offset';
    sProcess.options.isDCremove.Type    = 'checkbox';
    sProcess.options.isDCremove.Value   = 0;
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    Comment = ['Detect: ', sProcess.options.eventname.Value];
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs) %#ok<DEFNU>
    % ===== GET OPTIONS =====
    % Event name
    evtName = strtrim(sProcess.options.eventname.Value);
    chanName = strtrim(sProcess.options.channelname.Value);
    if isempty(evtName) || isempty(chanName)
        bst_report('Error', sProcess, [], 'Event and channel names must be specified.');
        OutputFiles = {};
        return;
    end
    % Ignore bad segments
    % => Consider that if the event name contains "bad", we need to include the bad segments. If not, we ignore them.
    isIgnoreBad = panel_record('IsEventBad', evtName);
    % Prepare options structure for the detection function
    OPTIONS = Compute();
    OPTIONS.thresholdMAX = sProcess.options.thresholdMAX.Value{1};
    OPTIONS.isAbsolute   = sProcess.options.isAbsolute.Value;
    OPTIONS.isDCremove   = sProcess.options.isDCremove.Value;
    OPTIONS.bandpass     = sProcess.options.bandpass.Value{1};
    iUnits = sProcess.options.units.Value;
    switch iUnits
        case 1
            OPTIONS.thresholdMAX = OPTIONS.thresholdMAX * 1;
        case 2
            OPTIONS.thresholdMAX = OPTIONS.thresholdMAX * 1e-3;
        case 3
            OPTIONS.thresholdMAX = OPTIONS.thresholdMAX * 1e-6;
        case 4
            OPTIONS.thresholdMAX = OPTIONS.thresholdMAX * 1e-15;
    end

    % Time window to process
    if isfield(sProcess.options, 'timewindow') && isfield(sProcess.options.timewindow, 'Value') && iscell(sProcess.options.timewindow.Value) && ~isempty(sProcess.options.timewindow.Value)
        TimeWindow = sProcess.options.timewindow.Value{1};
    else
        TimeWindow = [];
    end

    % Blanking period between two events
    if isfield(sProcess.options,'blanking')
        OPTIONS.blanking = sProcess.options.blanking.Value{1};
    end

    % Option structure for function in_fread()
    ImportOptions = db_template('ImportOptions');
    ImportOptions.ImportMode      = 'Time';
    ImportOptions.UseCtfComp      = 1;
    ImportOptions.UseSsp          = 1;
    ImportOptions.EventsMode      = 'ignore';
    ImportOptions.DisplayMessages = 0;
    ImportOptions.RemoveBaseline  = 'no';

    % Get current progressbar position
    progressPos = bst_progress('get');
    nEvents = 0;
    nTotalOcc = 0;

    % For each file
    iOk = false(1,length(sInputs));
    for iFile = 1:length(sInputs)
        % ===== GET DATA =====
        % Progress bar
        bst_progress('text', 'Reading channel to process...');
        bst_progress('set', progressPos + round(iFile / length(sInputs) / 3 * 100));
        % Load the raw file descriptor
        isRaw = strcmpi(sInputs(iFile).FileType, 'raw');
        if isRaw
            DataMat = in_bst_data(sInputs(iFile).FileName, 'F', 'Time');
            sFile = DataMat.F;
        else
            DataMat = in_bst_data(sInputs(iFile).FileName, 'Time');
            sFile = in_fopen(sInputs(iFile).FileName, 'BST-DATA');
        end
        % Load channel file
        ChannelMat = in_bst_channel(sInputs(iFile).ChannelFile);
        % Process only continuous files
        if ~isempty(sFile.epochs)
            bst_report('Error', sProcess, sInputs(iFile), 'This function can only process continuous recordings (no epochs).');
            continue;
        end

        % Get channel to process: multiple channels
        if any(chanName == ',')
            [iChannels, iChanWeights] = process_evt_detect('ParseChannelMontage', chanName, {ChannelMat.Channel.Name});
            if isempty(iChannels)
                bst_report('Error', sProcess, sInputs(iFile), ['Montage "' chanName '" could not be interpreted. Please check channel names.']);
                continue;
            end
        % One channel
        else
            iChannels = find(strcmpi({ChannelMat.Channel.Name}, chanName));
            if isempty(iChannels)
                bst_report('Error', sProcess, sInputs(iFile), ['Channel "' chanName '" not found in the channel file.']);
                continue;
            elseif (length(iChannels) > 1)
                bst_report('Error', sProcess, sInputs(iFile), ['Found more than one channel with name "' chanName '" in the channel file.']);
                continue;
            end
            iChanWeights = 1;
        end

        % Read channel to process
        if ~isempty(TimeWindow)
            SamplesBounds = round(sFile.prop.times(1) .* sFile.prop.sfreq) + bst_closest(TimeWindow, DataMat.Time) - 1;
        else
            SamplesBounds = [];
        end
        [F, TimeVector] = in_fread(sFile, ChannelMat, 1, SamplesBounds, iChannels, ImportOptions);
        % Apply weights if reading multiple channels
        if (length(iChannels) > 1)
            F = iChanWeights * F;
        end
        % If nothing was read
        if isempty(F) || (length(TimeVector) < 2)
            bst_report('Error', sProcess, sInputs(iFile), 'Time window is not valid.');
            continue;
        end

        % ===== DETECT PEAKS =====
        % Progress bar
        bst_progress('text', 'Detecting peaks...');
        bst_progress('set', progressPos + round(2 * iFile / length(sInputs) / 3 * 100));
        % Perform detection
        detectedEvt = Compute(F, TimeVector, OPTIONS);

        % ===== CREATE EVENTS =====
        sEvent = [];
        % Basic events structure
        if ~isfield(sFile, 'events') || isempty(sFile.events)
            sFile.events = repmat(db_template('event'), 0);
        end
        % Process each event type separately
        for i = 1:length(detectedEvt)
            % Event name
            if (i > 1)
                newName = sprintf('%s%d', evtName, i);
            else
                newName = evtName;
            end
            % Get the event to create
            iEvt = find(strcmpi({sFile.events.label}, newName));
            % Existing event: reset it
            if ~isempty(iEvt)
                sEvent = sFile.events(iEvt);
                sEvent.epochs  = [];
                sEvent.times   = [];
                sEvent.reactTimes = [];
            % Else: create new event
            else
                % Initialize new event
                iEvt = length(sFile.events) + 1;
                sEvent = db_template('event');
                sEvent.label = newName;
                % Get the default color for this new event
                sEvent.color = panel_record('GetNewEventColor', iEvt, sFile.events);
            end
            % Times, samples, epochs
            sEvent.times    = detectedEvt{i};
            sEvent.epochs   = ones(1, size(sEvent.times,2));
            sEvent.channels = [];
            sEvent.notes    = [];
            % Add to events structure
            sFile.events(iEvt) = sEvent;
            nEvents = nEvents + 1;
            nTotalOcc = nTotalOcc + size(sEvent.times, 2);
        end

        % ===== SAVE RESULT =====
        % Progress bar
        bst_progress('text', 'Saving results...');
        bst_progress('set', progressPos + round(3 * iFile / length(sInputs) / 3 * 100));
        % Only save changes if something was detected
        if ~isempty(sEvent)
            % Report changes in .mat structure
            if isRaw
                DataMat.F = sFile;
            else
                DataMat.Events = sFile.events;
            end
            DataMat = rmfield(DataMat, 'Time');
            % Save file definition
            bst_save(file_fullpath(sInputs(iFile).FileName), DataMat, 'v6', 1);
            % Report number of detected events
            bst_report('Info', sProcess, sInputs(iFile), sprintf('%s: %d events detected in %d categories', chanName, nTotalOcc, nEvents));
        else
            bst_report('Warning', sProcess, sInputs(iFile), ['No event detected on channel "' chanName '". Please check the signal quality.']);
        end
        iOk(iFile) = true;
    end
    % Return all the input files
    OutputFiles = {sInputs(iOk).FileName};
end

%% ===== PERFORM DETECTION =====
% USAGE:      evt = Compute(F, TimeVector, OPTIONS=[])
%         OPTIONS = Compute()                            % Get the default options structure
function evt = Compute(F, TimeVector, OPTIONS)
    % Options structure
    defOptions = struct('thresholdMAX',  0, ...     % Maximum threshold value
                        'bandpass', [], ...         % bandpass filter data before detection
                        'isAbsolute', 0, ...        % If 1, the max point defines the event, else, first thresh crossing defines the event
                        'isDCremove', 0, ...        % If 1, remove the DC offset of the signal before detection
                        'blanking', 0.02);          % Min duration between two events. Default 0.02 s
    % Parse inputs
    if (nargin == 0)
        evt = defOptions;
        return;
    end
    % Copy the missing parameters
    OPTIONS = struct_copy_fields(OPTIONS, defOptions, 0);
    % Sampling frequency
    sFreq = 1 ./ (TimeVector(2) - TimeVector(1));
    % Initialize output
    evt = {};

    %% ===== DETECT EVENTS ABOVE THRESHOLD =====
     % Filter recordings
    if ~isempty(OPTIONS.bandpass)
        F = process_bandpass('Compute', F, sFreq, OPTIONS.bandpass(1), OPTIONS.bandpass(2), 'bst-hfilter-2019', 1);
    end

    % Remove DC offset with detrending
    if OPTIONS.isDCremove
        % Remove the linear trend
        nTime = length(F);
        iTime = 1:nTime;
        % Basis functions
        x = [ones(1,nTime); 0:nTime-1];
        % Estimate the contribution of the basis functions
        % beta = dat(:,iTime)/x(:,iTime); <-this leads to numerical issues, even in simple examples
        invxcov = inv(x(:,iTime) * x(:,iTime)');
        beta    = F(:,iTime) * x(:,iTime)' * invxcov;
        % Remove the estimated basis functions
        F = F - beta*x;
    end

    % Absolute valuse
    if OPTIONS.isAbsolute
        F = abs(F);
    end

    % Threshold mask
    threshMask = zeros(size(F));
    % Find all the indices that are above the threshold
    threshMask(F > OPTIONS.thresholdMAX) = 1;

    % Nothing detected: exit
    if sum(threshMask) < 1
        return;
    end

    % Group time points into extended events
    % group events that occur withing OPTIONS.blanking seconds of each other
    minNewEvent = round(sFreq * OPTIONS.blanking);
    % If minNewEvent samples is longer than the signal to process: exit
    if (minNewEvent >= length(F))
        bst_report('Warning', 'process_evt_detect_threshold', [], 'The minimun period between two events is longer than the signal. Cannot perform detection.');
        return;
    end
    diffThreshMask = diff([0 threshMask 0]);
    startEve = find(diffThreshMask == 1);
    endEve = find(diffThreshMask == -1) -1;
    iEve = 1;
    while iEve < length(startEve)-1
        if startEve(iEve + 1) - endEve(iEve) < minNewEvent
            % combine the two events
            endEve(iEve) = endEve(iEve + 1);
            startEve(iEve+1) = [];
            endEve(iEve+1) = [];
        else
            iEve = iEve + 1;
        end
    end

    % Find times of the events
    evt = {[TimeVector(startEve); TimeVector(endEve)]};

end


%  create auxiliary parameters to be used in the perturbation approach by
%  Foerster, Rubio-Ramirez, Waggoner and Zha(2016)
%


function needed = isneeded(curmethod,allmethods)
% check whether curmethod will need to be calculated

if iscell(curmethod) && length(curmethod)>1
    for i=1:length(curmethod)
        needed(i) = isneeded(curmethod{i},allmethods);
    end
    return;
end

% depedency graph -- each of the measures on the right depend on the
% measure on the left (dependency arrow goes from left to right
% e.g., 'Coh',{'iCoh'} means iCoh depends on Coh (Coh-->iCoh)
% If a new measure is added, make sure to update this depedency table
% (add the method descriptor to the appropriate RHS and make a LHS entry
% for the method)
dependencies = {...
                'dDTF',     {};
                'ffDTF2',   {'dDTF'};
                'ffDTF',    {};
                'nDTF',     {};
                'GGC',      {};
                'iCoh',     {};
                'Coh',      {'iCoh'};
                'S',        {'Coh','GGC'};
                'pCoh',     {'dDTF'};
                'mCoh',     {};
                'RPDC',     {};
                'GPDC',     {};
                'nPDC',     {};
                'DTF',      {'nDTF','ffDTF','ffDTF2'};
                'G',        {'mCoh','pCoh'};
                'PDC',      {'DTF','G'};
                'Rinv',     {'RPDC'}};

if isempty(curmethod)
    needed =0;
    return;
elseif ismember_bc(curmethod,allmethods)
    needed = 1;
    return;
else
    % recursively determine whether any of the other methods the user
    % wants depend on curmethod
    idx=strmatch(curmethod,dependencies(:,1),'exact');
    if isempty(idx) || isempty(dependencies(idx,2)), needed=0; return; end
    children=dependencies(idx,2);
    if any(isneeded(children{1},allmethods))
        needed = 1;
        return;
    end
end

% if we get here, this particular curmethod is not needed
needed = 0;


function [axis] = extractHeatmapAxisData(obj,axis_data,axisName)
%extract information related to each axis
%   axis_data is the data extrated from the figure, axisName take the
%   values 'x' 'y' or 'z'

%-------------------------------------------------------------------------%

%-axis-side-%
% axis.side = eval(['axis_data.' axisName 'AxisLocation;']);

%-------------------------------------------------------------------------%

%-axis zeroline-%
axis.zeroline = false;

%-------------------------------------------------------------------------%

%-axis autorange-%
axis.autorange = false;

%-------------------------------------------------------------------------%

%-axis exponent format-%
axis.exponentformat = obj.PlotlyDefaults.ExponentFormat;

%-------------------------------------------------------------------------%

%-axis tick font size-%
axis.tickfont.size = axis_data.FontSize;

%-------------------------------------------------------------------------%

%-axis tick font family-%
axis.tickfont.family = matlab2plotlyfont(axis_data.FontName);

%-------------------------------------------------------------------------%

tl = eval(['axis_data.' axisName 'Data;']);
tl = length(tl);

w = axis_data.Position(4);
h = axis_data.Position(3);

ticklength = min(obj.PlotlyDefaults.MaxTickLength,...
    max(tl*w*obj.layout.width,tl*h*obj.layout.height));

%-axis ticklen-%
axis.ticklen = 0.1; %ticklength;

%-------------------------------------------------------------------------%

% col = eval(['255*axis_data.' axisName 'Color;']);
axiscol = 'rgb(150, 150, 150)';

%-axis linecolor-%
axis.linecolor = axiscol;
%-axis tickcolor-%
axis.tickcolor = axiscol;
%-axis tickfont-%
axis.tickfont.color = 'black';
%-axis grid color-%
axis.gridcolor = 'rgb(0, 0, 0)';

%-------------------------------------------------------------------------%

axis.showgrid = true;

%-------------------------------------------------------------------------%

lw = 0.5;
linewidth = max(1,lw*obj.PlotlyDefaults.AxisLineIncreaseFactor);

%-axis line width-%
axis.linewidth = linewidth;
%-axis tick width-%
axis.tickwidth = linewidth;
%-axis grid width-%
axis.gridwidth = linewidth*1.2;

%-------------------------------------------------------------------------%

%-setting ticks-%
axis.ticks = 'inside';
axis.mirror = true;

labels = eval(['axis_data.' axisName 'DisplayLabels;']);
vals = eval(['axis_data.' axisName 'DisplayData;']);

axis.showticklabels = true;
axis.type = 'category';
axis.autorange = true;
axis.ticktext = labels;
axis.tickvals = vals;
axis.autotick = false;
axis.tickson = 'boundaries';

%-------------------------------LABELS------------------------------------%

label = eval(['axis_data.' axisName 'Label;']);

%-------------------------------------------------------------------------%

%-title-%
axis.title = label;

%-------------------------------------------------------------------------%

%-axis title font color-%
axis.titlefont.color = 'black';

%-------------------------------------------------------------------------%

%-axis title font size-%
axis.titlefont.size = axis_data.FontSize*1.3;
axis.tickfont.size = axis_data.FontSize*1.15;

%-------------------------------------------------------------------------%

%-axis title font family-%
axis.titlefont.family = matlab2plotlyfont(axis_data.FontName);
axis.tickfont.family = matlab2plotlyfont(axis_data.FontName);

%-------------------------------------------------------------------------%

if strcmp(axis_data.Visible,'on')
    %-axis showline-%
    axis.showline = true;
else
    %-axis showline-%
    axis.showline = false;
    %-axis showticklabels-%
    axis.showticklabels = false;
    %-axis ticks-%
    axis.ticks = '';
end

%-------------------------------------------------------------------------%
end


function comp_next_tests ( )

%****************************************************************************80
%
%% COMP_NEXT_TESTS calls COMP_NEXT_TEST with various arguments.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    03 March 2011
%
%  Author:
%
%    John Burkardt
%
  addpath ( '../sandia_rules' );

  test_num = 12;

  dim_num_array(1:test_num) = [ ...
    2, 2, 2, 2, 2, ...
    3, 3, 3, 3, 3, ...
    4, 4 ];
  level_max_array(1:test_num) = [ ...
    0, 1, 2, 3, 4, ...
    0, 1, 2, 3, 4, ...
    2, 3 ];

  fprintf ( 1, '\n' );
  timestamp ( );
  fprintf ( 1, '\n' );
  fprintf ( 1, 'COMP_NEXT_TESTS\n' );
  fprintf ( 1, '  MATLAB version\n' );
  fprintf ( 1, '  Call COMP_NEXT_TEST with various arguments.\n' );

  for test = 1 : test_num

    dim_num = dim_num_array(test);
    level_max = level_max_array(test);
    comp_next_test ( dim_num, level_max );

  end
%
%  Terminate.
%
  fprintf ( 1, '\n' );
  fprintf ( 1, 'COMP__NEXT_TESTS\n' );
  fprintf ( 1, '  Normal end of execution.\n' );
  fprintf ( 1, '\n' );
  timestamp ( );

  rmpath ( '../sandia_rules' );

  return
end


%% Discretisation of Orientation Space
% represents a set of orientations
%%
%


function B = mirror_shrink(A)
    [m,n] = size(A);
    yi = 2:m-1;
    xi = 2:n-1;
    B = A(yi,xi);
end


function msi_file_get_index(fid, keyword, names)
%	MSI_FILE_GET_INDEX(fid, keyword)  Skip to a keyword in a file FID
%	and return the single valuea one based index array from a zero
%	based index array
%

frewind(fid);
test_line = sprintf('%s\t%%d', keyword);
index=0;
while feof(fid) == 0
    	line = fgetl(fid);
	matches = findstr(line, keyword);
	num = length(matches);
	if num > 0
	    % eat up keyword
	    [chopped,remainder] = strtok(line);
	    while(any(remainder));
		[chopped,remainder] = strtok(remainder, ',');
		names(index) = sscanf(chopped, '%s');
                index = index + 1;
	    end
	end
end


function disp(X,name)
%DISP Command window display of a sparse tensor.
%
%   DISP(X) displays the tensor without printing its name.
%
%   DISP(X,NAME) displays the tensor with the given name.
%
%   See also SPTENSOR, SPTENSOR/DISPLAY.
%
%MATLAB Tensor Toolbox.
%Copyright 2012, Sandia Corporation.

% This is the MATLAB Tensor Toolbox by T. Kolda, B. Bader, and others.
% http://www.sandia.gov/~tgkolda/TensorToolbox.
% Copyright (2012) Sandia Corporation. Under the terms of Contract
% DE-AC04-94AL85000, there is a non-exclusive license for use of this
% work by or on behalf of the U.S. Government. Export of this data may
% require a license from the United States Government.
% The full license terms can be found in the file LICENSE.txt

% Extract the number of nonzeros and number of dimensions
nz = nnz(X);

if ~exist('name','var')
    name = 'ans';
end

if (nz == 0)
    fprintf('%s is an all-zero sparse tensor of size %s\n',...
        name, tt_size2str(X.size));
    return;
else
    fprintf('%s is a sparse tensor of size %s with %d nonzeros\n',...
        name, tt_size2str(X.size), nz);
end

% Stop insane printouts
if (nz > 10000)
    r = input('Are you sure you want to print all nonzeros? (Y/N) ','s');
    if upper(r) ~= 'Y', return, end;
end

% preallocate
output = cell(nz,1);
%%
spc = floor(log10(max(double(X.subs),[],1)))+1;
if numel(spc) == 1
    fmt = ['\t(%' num2str(spc(1)) 'd)%s'];
else
    fmt = ['\t(%' num2str(spc(1)) 'd,'];
    for i = 2:numel(spc)-1
        fmt = [fmt '%' num2str(spc(i)) 'd,'];
    end
    fmt = [fmt '%' num2str(spc(end)) 'd)%s'];
end
%%
% Get values out so that they look nice
savefmt = get(0,'FormatSpacing');
format compact
S = evalc('disp(X.vals)');
set(0,'FormatSpacing',savefmt)
S = textscan(S,'%s','delimiter','\n','whitespace','');
S = S{1};
if ~isempty(strfind(S{1},'*'))
    fprintf('%s\n',S{1});
    S = S(2:end);
end
%%
for i = 1:nz
    output{i} = sprintf(fmt,X.subs(i,:),S{i});
end
fprintf('%s\n',output{:});

%     function y = fmt(s,v)
%         % nested function has access to n from disp function workspace
%         if n > 1
%             y = [sprintf('\t(') sprintf('%d,',s(1:n-1))...
%                 sprintf('%d) ',s(n)) sprintf('\t%g',v)];
%         else
%             y = sprintf('\t(%d) \t%f',s,v);
%         end
%     end

end


function plot_branch_segmentation(P,cover,segment,Color,fig,ms,segind,BO)

% ---------------------------------------------------------------------
% PLOT_BRANCH_SEGMENTATION.M   Plots branch-segmented point cloud, coloring
%                               based on branching order or branches
%
% Version 1.0.0
% Latest update     13 July 2020
%
% Copyright (C) 2013-2020 Pasi Raumonen
% ---------------------------------------------------------------------
%
% If the coloring is based on branches (Color = 'branch'), then each segment
% is colored with unique color. If the coloring is based on branching order
% (Color = 'order'), then Blue = trunk, Green = 1st-order branches,
% Red = 2nd-order branches, etc.
%
% If segind = 1 and BO = 0, then plots the stem. If segind = 1 and BO = 1,
% then plots the stem and the 1st-order branches. If segind = 1 and
% BO >= maximum branching order or BO input is not given, then plots the
% whole tree. If segind = 2 and BO is not given or it is high enough, then
% plots the branch whose index is 2 and all its sub-branches.
%
% Inputs
% P         Point cloud
% cover     Cover sets structure
% segment   Segments structure
% Color     Color option, 'order' or 'branch'
% fig       Figure number
% ms        Marker size
% segind    Index of the segment where the plotting of tree structure starts.
% BO        How many branching orders are plotted. 0 = stem, 1 = 1st order, etc

n = nargin;
if n < 8
    BO = 1000;
    if n < 7
        segind = 1;
        if n < 6
            ms = 1;
            if n < 5
                fig = 1;
                if n == 3
                    Color = 'order';
                end
            end
        end
    end
end

Bal = cover.ball;
Segs = segment.segments;
SChi = segment.ChildSegment;
SPar = segment.ParentSegment;
ns = max(size(Segs));

if iscell(Segs{1})
    Seg = cell(ns,1);
    for i = 1:ns
        m = size(Segs{i},1);
        S = zeros(0);
        for j = 1:m
            s = Segs{i}(j);
            s = s{:};
            S = [S; s];
        end
        Seg{i} = S;
    end
else
    Seg = Segs;
end

if strcmp(Color,'branch')
    Color = 1;
    % Color the segments with unique colors
    col = rand(ns,3);
    for i = 2:ns
        C = col(SPar(i),:);
        c = col(i,:);
        while sum(abs(C-c)) < 0.2
            c = rand(1,3);
        end
        col(i,:) = c;
    end
elseif strcmp(Color,'order')
    Color = 0;
    % Color the cylinders in branches based on the branch order
    col = [
        0.00  0.00  1.00
        0.00  0.50  0.00
        1.00  0.00  0.00
        0.00  0.75  0.75
        0.75  0.00  0.75
        0.75  0.75  0.00
        0.25  0.25  0.25
        0.75  0.25  0.25
        0.95  0.95  0.00
        0.25  0.25  0.75
        0.75  0.75  0.75
        0.00  1.00  0.00
        0.76  0.57  0.17
        0.54  0.63  0.22
        0.34  0.57  0.92
        1.00  0.10  0.60
        0.88  0.75  0.73
        0.10  0.49  0.47
        0.66  0.34  0.65
        0.99  0.41  0.23];
    col = repmat(col,[10,1]);
end

segments = segind;
C = SChi{segind};
b = 1;
order = 1;
while ~isempty(C) && b <= BO
    b = b+1;
    segments = [segments; C];
    order = [order; b*ones(length(C),1)];
    C = vertcat(SChi{C});
end

ns = length(segments);
figure(fig)
for i = 1:ns
    if i == 2
        hold on
    end
    S = vertcat(Bal{Seg{segments(i)}});
    if Color
    % Coloring based on branch
    plot3(P(S,1),P(S,2),P(S,3),'.','Color',col(segments(i),:),'Markersize',ms)
    else
    % Coloring based on branch order
    plot3(P(S,1),P(S,2),P(S,3),'.','Color',col(order(i),:),'Markersize',ms)
    end
end
hold off
axis equal


function om = display_soln(om, set_type, name, idx)
%DISPLAY_SOLN  Display solution values.
%   OM.DISPLAY_SOLN()
%   OM.DISPLAY_SOLN(SET_TYPE)
%   OM.DISPLAY_SOLN(SET_TYPE, NAME)
%   OM.DISPLAY_SOLN(SET_TYPE, NAME, IDX)
%
%   Displays the model solution, including values, bounds and shadow
%   prices for variables and linear constraints, values and shadow
%   prices for nonlinear constraints, and individual cost components.
%
%   Results are displayed for each SET_TYPE or specified SET_TYPE and
%   for each named/indexed set or a specified NAME/IDX.
%
%   Inputs:
%       SET_TYPE - one of the following, specifying the type of set:
%           'var' - variables
%           'lin' - linear constraints
%           'nle' - nonlinear equality constraints
%           'nli' - nonlinear inequality constraints
%           'nlc' - nonlinear costs
%           'qdc' - quadratic costs
%         or
%           a cell array of one or more of the above
%         or
%           '' or 'all' - indicating to display all
%       NAME - (optional) char array specifying the name of the set
%       IDX  - (optional) cell array specifying the indices of the set
%
%   Examples:
%       om.display_soln('var');
%       om.display_soln({'nle', 'nli'});
%       om.display_soln('var', 'P');
%       om.display_soln('lin', 'lin_con_1');
%       om.display_soln('nle', 'nle_con_b', {2,3});
%
%   See also GET_SOLN, PARSE_SOLN.

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

%% input arg handling
if nargin < 4
    idx = [];
    if nargin < 3
        name = [];
        if nargin < 2
            set_type = 'all';
        end
    end
end

fid = 1;

mu_thresh = 1e-7;
num_inf = 1e10;

%% print header
if om.is_solved()
    s = om.soln;
    if strcmp(set_type, 'all')
        set_types = fieldnames(om.set_types);   %% all set types
    elseif ~iscell(set_type)
        set_types = {set_type}; %% make set_type a cell array of char arrays
    else
        set_types = set_type;
    end

    for ss = 1:length(set_types)
        st = set_types{ss};
        om_st = om.(st);

        if om.(st).N
            if isempty(name)            %% all indices for set type
                idxs = (1:om_st.N);
            elseif isempty(idx)         %% all indices for set type & name
                idxs = [];
                for k = 1:length(om_st.order)
                    if strcmp(om_st.order(k).name, name)
                        i1 = om_st.idx.i1.(name)(om_st.order(k).idx{:});
                        iN = om_st.idx.iN.(name)(om_st.order(k).idx{:});
                        idxs = [idxs (i1:iN)];
                    end
                end
            else                        %% indices for set type, name, idx
                idxs = (om_st.idx.i1.(name)(idx{:}):om_st.idx.iN.(name)(idx{:}));
            end

            %% set type headers
            fprintf(fid, '=====  %s  =====\n', om.set_types.(st));
            %% common columns
            hdr1 = {'  idx    description                ', ...
                    '------- ----------------------------' };
            %% type specific columns
            switch st
            case {'var', 'lin'}
                hdr2 = {'   mu_lb     lb       val      ub      mu_ub', ...
                        ' -------- -------- -------- -------- --------' };
                switch st
                case 'var'
                    [v0, vl, vu] = om.params_var();
                    v = s.x;
                    mu_l = s.lambda.lower;
                    mu_u = s.lambda.upper;
                case 'lin'
                    [A, vl, vu] = om.params_lin_constraint();
                    v = A * s.x;
                    mu_l = s.lambda.mu_l;
                    mu_u = s.lambda.mu_u;
                end
            case 'nle'
                hdr2 = {'    val    lambda', ...
                        ' -------- --------' };
                lam = s.lambda.eqnonlin;
                v = om.eval_nln_constraint(s.x, 1);
            case 'nli'
                hdr2 = {'    val      ub      mu_ub', ...
                        ' -------- -------- --------' };
                mu_u = s.lambda.ineqnonlin;
                v = om.eval_nln_constraint(s.x, 0);
            case 'qdc'
                hdr2 = {'   cost  =  quad    linear  constant  average', ...
                        ' -------- -------- -------- -------- --------' };
                c = [];
                c_k = [];
                c_lin = [];
                c_avg = [];
                vv = om.get_idx();
                for k = 1:length(om_st.order)
                    n = om_st.order(k).name;
                    i = om_st.order(k).idx;
                    [QQ, cc, kk, vs] = om.params_quad_cost(n, i);
                    xx = om.varsets_x(s.x, vs, 'vector');
                    c_total = om.eval_quad_cost(s.x, n, i);
                    len = length(c_total);
                    if len == 1
                        c_constant = kk;
                        c_linear = cc' * xx;
                        c_average = c_total / sum(xx);
                    else
                        c_linear = cc .* xx;
                        c_average = c_total ./ xx;
                        if isscalar(kk)
                            c_constant = ones(len, 1)*kk/len;
                        else
                            c_constant = kk;
                        end
                    end
                    if sum(sum(QQ)) == 0 && sum(kk) == 0 && len == length(cc)
                        c_average = cc;
                    end
                    c = [c; c_total];
                    c_lin = [c_lin; c_linear];
                    c_k = [c_k; c_constant];
                    c_avg = [c_avg; c_average];
                end
                c_quad = c - c_lin - c_k;
            case 'nlc'
                c = [];
                for k = 1:length(om_st.order)
                    n = om_st.order(k).name;
                    i = om_st.order(k).idx;
                    c_total = om.eval_nln_cost(s.x, n, i);
                    c = [c; c_total];
                end
                hdr2 = {'   cost', ...
                        ' --------' };
            otherwise
                hdr2 = {'', ...
                        '' };
            end

            %% print headers
            for h = 1:length(hdr1)
                fprintf(fid, '%s\n', [hdr1{h} hdr2{h}]);
            end

            %% print data
            none = '- ';
            % none = '';
            for k = 1:length(idxs)
                ii = sprintf('%d', idxs(k));
                fmt = sprintf('%%-%ds', length(ii)+ceil((7-length(ii))/2));
                fprintf(fid, '%7s %-28s', sprintf(fmt, ii), om.describe_idx(st, idxs(k)));

                switch st
                case {'var', 'lin'}
                    if abs(mu_l(idxs(k))) < mu_thresh
                        mu_lb = sprintf(none);
                    else
                        mu_lb = sprintf_num(8, mu_l(idxs(k)));
                    end
                    if abs(mu_u(idxs(k))) < mu_thresh
                        mu_ub = sprintf(none);
                    else
                        mu_ub = sprintf_num(8, mu_u(idxs(k)));
                    end
                    if vl(idxs(k)) < -num_inf
                        lb = sprintf(none);
                    else
                        lb = sprintf_num(8, vl(idxs(k)));
                    end
                    if vu(idxs(k)) > num_inf
                        ub = sprintf(none);
                    else
                        ub = sprintf_num(8, vu(idxs(k)));
                    end
                    fprintf(fid, '%9s%9s%9s%9s%9s\n', ...
                        mu_lb, lb, sprintf_num(8, v(idxs(k))), ub, mu_ub);
                case 'nle'
                    if abs(lam(idxs(k))) < mu_thresh
                        mu_ub = sprintf(none);
                    else
                        mu_ub = sprintf_num(8, lam(idxs(k)));
                    end
                    fprintf(fid, '%9s%9s\n', sprintf_num(8, v(idxs(k))), ...
                        mu_ub);
                case 'nli'
                    if abs(mu_u(idxs(k))) < mu_thresh
                        mu_ub = sprintf(none);
                    else
                        mu_ub = sprintf_num(8, mu_u(idxs(k)));
                    end
                    fprintf(fid, '%9s%9s%9s\n', sprintf_num(8, v(idxs(k))), ...
                        '0', mu_ub);
                case 'qdc'
                    if isnan(c_avg(idxs(k)))
                        cc_avg = '- ';
                    else
                        cc_avg = sprintf_num(8, c_avg(idxs(k)));
                    end
                    fprintf(fid, '%9s%9s%9s%9s%9s\n', ...
                        sprintf_num(8, c(idxs(k))), ...
                        sprintf_num(8, c_quad(idxs(k))), ...
                        sprintf_num(8, c_lin(idxs(k))), ...
                        sprintf_num(8, c_k(idxs(k))), ...
                        cc_avg);
                case 'nlc'
                    fprintf(fid, '%9s\n', sprintf_num(8, c(idxs(k))));
                otherwise
                    fprintf(fid, '\n');
                end
            end     %% loop over idxs

            %% section footer
            switch st
            case {'var', 'lin'}
                fprintf(fid, '%s\n', [hdr1{2} hdr2{2}]);
                fprintf(fid, '%7s %-28s%9s%9s%9s%9s%9s\n', '', 'Min', ...
                    sprintf_num(8, min(mu_l)), sprintf_num(8, min(vl)), ...
                    sprintf_num(8, min(v)), ...
                    sprintf_num(8, min(vu)), sprintf_num(8, min(mu_u)));
                fprintf(fid, '%7s %-28s%9s%9s%9s%9s%9s\n', '', 'Max', ...
                    sprintf_num(8, max(mu_l)), sprintf_num(8, max(vl)), ...
                    sprintf_num(8, max(v)), ...
                    sprintf_num(8, max(vu)), sprintf_num(8, max(mu_u)));
                fprintf(fid, '\n');
            case 'nle'
                fprintf(fid, '%s\n', [hdr1{2} hdr2{2}]);
                fprintf(fid, '%7s %-28s%9s%9s\n', '', 'Min', ...
                    sprintf_num(8, min(v)), ...
                    sprintf_num(8, min(lam)));
                fprintf(fid, '%7s %-28s%9s%9s\n', '', 'Max', ...
                    sprintf_num(8, max(v)), ...
                    sprintf_num(8, max(lam)));
                fprintf(fid, '\n');
            case 'nli'
                fprintf(fid, '%s\n', [hdr1{2} hdr2{2}]);
                fprintf(fid, '%7s %-28s%9s%9s%9s\n', '', 'Max', ...
                    sprintf_num(8, max(v)), '0', ...
                    sprintf_num(8, max(mu_u)));
                fprintf(fid, '\n');
            case 'qdc'
                fprintf(fid, '%s\n', [hdr1{2} hdr2{2}]);
                fprintf(fid, '%7s %-28s%9s%9s%9s%9s%9s\n', '', ...
                    'Sum of Displayed Costs', ...
                    sprintf_num(8, sum(c(idxs))), ...
                    sprintf_num(8, sum(c_quad(idxs))), ...
                    sprintf_num(8, sum(c_lin(idxs))), ...
                    sprintf_num(8, sum(c_k(idxs))), '');
                fprintf(fid, '\n');
            case 'nlc'
                if length(c) > 1
                    fprintf(fid, '%s\n', [hdr1{2} hdr2{2}]);
                    fprintf(fid, '%7s %-28s%9s%9s%9s%9s%9s\n', '', ...
                        'Sum of Displayed Costs', ...
                        sprintf_num(8, sum(c(idxs))));
                end
                fprintf(fid, '\n');
            otherwise
                fprintf(fid, '\n');
            end
        end
    end             %% loop over set types
else
    fprintf(fid, 'Not a solved model.\n');
end

function str = sprintf_num(width, val)
val = full(val);
fmt = sprintf('%%%dg', width);
str = sprintf(fmt, val);
if length(str) > width
    fmt = sprintf('%%%d.*g', width);
    for p = width-2:-1:0
        str = sprintf(fmt, p, val);
        if length(str) <= width
            break;
        end
    end
end
assert(length(str) <= width)


function Obs = createObservations(Sen,Opt)

% CREATEOBSERVATIONS Create Obs structure array.
%   Rob = CREATEOBSERVATIONS(Sen,Lmk) creates the Obs() structure array to
%   be used as SLAM data, from the information contained in Sen() and
%   Rob(). See the toolbox documentation for details on this structure.

%   Copyright 2008-2009 Joan Sola @ LAAS-CNRS.

for sen = 1:numel(Sen)

    for lmk = 1:Opt.map.numLmks

        S = Sen(sen);
%         L = Lmk(lmk);

        O.sen       = sen;        % sensor index
        O.lmk       = lmk;        % landmark index.
        O.sid       = S.id;       % sensor id
        O.lid       = [];         % lmk id
        O.stype     = S.type;     % sensor type
        O.ltype     = '';         % lmk type
        O.meas.y    = [];         % observation
        O.meas.R = S.par.cov; % observation cov
        O.nom.n     = [];         % expected non-observable
        O.nom.N     = [];         % expected non-observable cov
        O.exp.e     = [];         % expectation mean
        O.exp.E     = [];         % expectation cov
        O.exp.um    = [];         % expectation uncertainty measure
        O.inn.z     = [];         % innovation
        O.inn.Z     = [];         % innovation cov
        O.inn.iZ    = [];         % inverse inn. cov
        O.inn.MD2   = 0;          % Mahalanobis distance squared
        O.app.pred  = [];         % predicted appearence
        O.app.curr  = [];         % current appearence
        O.app.sc    = 0;          % match score
        O.par       = [];         % other params
        O.vis       = false;      % lmk is visible?
        O.measured  = false;      % lmk has been measured?
        O.matched   = false;      % lmk has been matched?
        O.updated   = false;      % lmk has been updated?
        O.Jac.E_r   = [];         % Jac of expectation wrt robot state - Obs function
        O.Jac.E_s   = [];         % Jac of expectation wrt sensor
        O.Jac.E_l   = [];         % Jac of expectation wrt landmark
        O.Jac.Z_r   = [];         % Jac of innovation wrt robot state - Inn function
        O.Jac.Z_s   = [];         % Jac of innovation wrt sensor
        O.Jac.Z_l   = [];         % Jac of innovation wrt landmark

        Obs(sen,lmk) = O;

    end
end

% ========== End of function - Start GPL license ==========

%   # START GPL LICENSE

%---------------------------------------------------------------------
%
%   This file is part of SLAMTB, a SLAM toolbox for Matlab.
%
%   SLAMTB 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.
%
%   SLAMTB 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 SLAMTB.  If not, see <http://www.gnu.org/licenses/>.
%
%---------------------------------------------------------------------

%   SLAMTB is Copyright:
%   Copyright (c) 2008-2010, Joan Sola @ LAAS-CNRS,
%   Copyright (c) 2010-2013, Joan Sola,
%   Copyright (c) 2014-2015, Joan Sola @ IRI-UPC-CSIC,
%   SLAMTB is Copyright 2009
%   by Joan Sola, Teresa Vidal-Calleja, David Marquez and Jean Marie Codol
%   @ LAAS-CNRS.
%   See on top of this file for its particular copyright.

%   # END GPL LICENSE


function r4lib_test ( )

%*****************************************************************************80
%
%% R4LIB_TEST tests the R4LIB library.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    28 September 2014
%
%  Author:
%
%    John Burkardt
%
  timestamp ( );
  fprintf ( 1, '\n' );
  fprintf ( 1, 'R4LIB_TEST\n' );
  fprintf ( 1, '  MATLAB version\n' );
  fprintf ( 1, '  Test the R4LIB library.\n' );

  r4lib_test001 ( );
  r4lib_test023 ( );
%
%  Terminate.
%
  fprintf ( 1, '\n' );
  fprintf ( 1, 'R4LIB_TEST\n' );
  fprintf ( 1, '  Normal end of execution.\n' );
  fprintf ( 1, '\n' );
  timestamp ( );

  return
end


function m = size(t,idx)
%SIZE Sparse tensor dimensions.
%
%   D = SIZE(T) returns the size of the tensor.
%
%   I = size(T,DIM) returns the sizes of the dimensions specified by DIM.
%
%   See also SPTENSOR, SPTENSOR/NDIMS.
%
%MATLAB Tensor Toolbox.
%Copyright 2015, Sandia Corporation.

% This is the MATLAB Tensor Toolbox by T. Kolda, B. Bader, and others.
% http://www.sandia.gov/~tgkolda/TensorToolbox.
% Copyright (2015) Sandia Corporation. Under the terms of Contract
% DE-AC04-94AL85000, there is a non-exclusive license for use of this
% work by or on behalf of the U.S. Government. Export of this data may
% require a license from the United States Government.
% The full license terms can be found in the file LICENSE.txt

if exist('idx','var')
    m = t.size(idx);
else
    m = t.size;
end


function out = iszero(f)
%ISZERO    True for zero CHEBTECH objects.
%   ISZERO(F) returns logical TRUE is F.COEFFS has only zero entries and logical
%   FALSE otherwise.

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

out = ~any(f.coeffs, 1);

if ( any(out) )
    % We need this as any(NaN) = 0, which will pass the test above.
    out = out & ~any(isnan(f.coeffs), 1);
    return
end

end


function hFig = view_spheres(HeadModelFile, ChannelFile, sSubject)
% VIEW_SPHERES: Show all the spheres from a "overlapping spheres" forward model.
%
% USAGE:  hFig = view_spheres(HeadModelFile, ChannelFile, sSubject)

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2008-2011

%% ===== PARSE INPUTS =====
% Get reference surface
if ~isempty(sSubject.iScalp)
    sScalp = in_tess_bst(sSubject.Surface(sSubject.iScalp).FileName);
else
    sScalp = [];
end
if ~isempty(sSubject.iInnerSkull)
    sInnerSkull = in_tess_bst(sSubject.Surface(sSubject.iInnerSkull).FileName);
elseif ~isempty(sSubject.iCortex)
    CortexFile = sSubject.Surface(sSubject.iCortex).FileName;
    sInnerSkull = tess_envelope(CortexFile, 'convhull', 1082, .003);
else
    sInnerSkull = [];
end
hFig = [];

%% ===== LOAD DATA =====
% Load HeadModel ('Param' field only)
HeadModelMat = in_bst_headmodel(HeadModelFile, 0, 'Param', 'MEGMethod', 'EEGMethod');
Spheres = HeadModelMat.Param;
% Load Channels
ChannelMat = in_bst_channel(ChannelFile);
Channel = ChannelMat.Channel;

%% ===== SELECT SPHERES TO DISPLAY =====
iSel = [];
if ~isempty(HeadModelMat.MEGMethod)
    iMeg = good_channel(Channel, [], 'MEG');
    if ismember(HeadModelMat.MEGMethod, {'meg_sphere', 'singlesphere'})
        iSel = [iSel, iMeg(1)];
    elseif ismember(HeadModelMat.MEGMethod, {'os_meg', 'localspheres'})
        iSel = [iSel, iMeg];
    end
end
if ismember(HeadModelMat.EEGMethod, {'eeg_3sphereberg', 'singlesphere', 'concentricspheres'})
    iEeg = good_channel(Channel, [], 'EEG');
    iSel = [iSel, iEeg(1)];
    Channel(iEeg(1)).Name = 'EEG';
end
Spheres = Spheres(iSel);
Channel = Channel(iSel);

%% ===== DISPLAY SURFACES =====
hFig = [];
% Display scalp
if ~isempty(sScalp)
    hFig = view_surface_matrix(sScalp.Vertices, sScalp.Faces, .9, []);
end
% Display inner skull (or inflated cortex envelope)
if ~isempty(sInnerSkull)
    if isempty(hFig)
        hFig = view_surface_matrix(sInnerSkull.Vertices, sInnerSkull.Faces, .2, []);
    else
        view_surface_matrix(sInnerSkull.Vertices, sInnerSkull.Faces, .2, [], hFig);
    end
end
% No figure create: error
if isempty(hFig)
    error('No reference surface available');
end
% Set orientation: left
figure_3d('SetStandardView', hFig, 'left');
% Update figure name
set(hFig, 'Name', ['Check spheres: ' HeadModelFile]);
% Get axes handles
hAxes = findobj(hFig, '-depth', 1, 'Tag', 'Axes3D');

%% ===== DISPLAY SPHERES+SENSORS =====
% Hack keyboard callback
KeyPressFcn_bak = get(hFig, 'KeyPressFcn');
set(hFig, 'KeyPressFcn', @KeyPress_Callback);
% Create legend (Sphere)
hLabel = uicontrol('Style',               'text', ...
                   'String',              '...', ...
                   'Units',               'Pixels', ...
                   'Position',            [6 0 400 18], ...
                   'HorizontalAlignment', 'left', ...
                   'FontUnits',           'points', ...
                   'FontSize',            bst_get('FigFont'), ...
                   'ForegroundColor',     [.3 1 .3], ...
                   'BackgroundColor',     [0 0 0], ...
                   'Parent',              hFig);
% Plot sensors
if length(Channel) > 10
    markersLocs   = cell2mat(cellfun(@(c)c(:,1), {Channel.Loc}, 'UniformOutput', 0))';
    hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0);
    set(hSensors, 'LineWidth', 1, 'MarkerSize', 2);
end

% Base of spheres
[X,Y,Z] = sphere(20);
XYZ = tess_sphere(812);
[TH,PHI,R] = cart2sph(X,Y,Z);

% Current sphere
iSphere = 1;
% Draw first sphere
DrawSphere();

%% ===== KEYBOARD CALLBACK =====
    function KeyPress_Callback(h, keyEvent)
        switch (keyEvent.Key)
            % === LEFT, RIGHT, PAGEUP, PAGEDOWN : Processed by TimeWindow  ===
            case {'leftarrow', 'space', 'uparrow'}
                iSphere = iSphere - 1;
            case 'pagedown'
                iSphere = iSphere - 10;
            case {'rightarrow', 'downarrow'}
                iSphere = iSphere + 1;
            case 'pageup'
                iSphere = iSphere + 10;
            otherwise
                KeyPressFcn_bak(h, keyEvent);
                return;
        end
        % Redraw sphere
        if (iSphere <= 0)
            iSphere = length(Spheres);
        end
        if (iSphere > length(Spheres))
            iSphere = 1;
        end
        DrawSphere();
    end

%% ===== DRAW CURRENT SPHERE =====
    function DrawSphere()
        % Delete previous spheres and sensors
        delete(findobj(hAxes, '-depth', 1, 'Tag', 'OverlapSphere'));
        % Draw current sphere
        [X,Y,Z] = sph2cart(TH, PHI, R * Spheres(iSphere).Radii(end));
        hSphere = patch(surf2patch(X + Spheres(iSphere).Center(1),...
                                   Y + Spheres(iSphere).Center(2),...
                                   Z + Spheres(iSphere).Center(3),...
                                   Z + Spheres(iSphere).Center(3)));
        set(hSphere, 'Parent',    hAxes, ...
                     'Facecolor', 'none', ...
                     'EdgeColor', [.8 .8 .8], ...
                     'EdgeAlpha', 1, ...
                     'LineWidth', 1, ...
                     'Tag',       'OverlapSphere');

        % Remove previous selected sensor
        delete(findobj(hAxes, '-depth', 1, 'Tag', 'SelSphereChannel'));
        % Plot selected sensor
        if ~isempty(Channel(iSphere).Loc) && ~ismember(Channel(iSphere).Name, {'EEG','MEG','MEG MAG', 'MEG GRAD'})
            line(Channel(iSphere).Loc(1,1), Channel(iSphere).Loc(2,1), Channel(iSphere).Loc(3,1), ...
                 'Parent',          hAxes, ...
                 'LineWidth',       2, ...
                 'LineStyle',       'none', ...
                 'Marker',          'o', ...
                 'MarkerFaceColor', [1 0 0], ...
                 'MarkerEdgeColor', [.4 .4 .4], ...
                 'MarkerSize',      8, ...
                 'Tag',             'SelSphereChannel');
        end

         % Update legend
        newLegend = sprintf('Sphere #%d/%d  (%s)', iSphere, length(Spheres), Channel(iSphere).Name);
        if (iSphere == 1) && (length(Spheres) > 1)
            newLegend = [newLegend, '       [Press arrows for next/previous sphere...]'];
        end
        set(hLabel, 'String', newLegend);
    end

end


% pop_importegimat() - import EGI Matlab segmented file
%
% Usage:
%   >> EEG = pop_importegimat(filename, srate, latpoint0);
%
% Inputs:
%   filename    - Matlab file name
%   srate       - sampling rate
%   latpoint0   - latency in sample ms of stimulus presentation.
%                 When data files are exported using Netstation, the user specify
%                 a time range (-100 ms to 500 ms for instance). In this
%                 case, the latency of the stimulus is 100 (ms). Default is 0 (ms)
%
% Output:
%   EEG        - EEGLAB dataset structure
%
% Authors: Arnaud Delorme, CERCO/UCSD, Jan 2010

% Copyright (C) Arnaud Delorme, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

function [EEG com] = pop_importegimat(filename, srate, latpoint0);

    if nargin < 3, latpoint0 = 0; end;
    if nargin < 4, lookupchanfile = 0; end;
    if nargin < 1
        % ask user
        [filename, filepath] = uigetfile('*.mat', 'Choose a Matlab file from Netstation -- pop_importegimat()');
        if filename == 0 return; end;
        filename = fullfile(filepath, filename);

        promptstr    = { { 'style' 'text'       'string' 'Sampling rate (Hz)' } ...
                         { 'style' 'edit'       'string' '250' } ...
                         { 'style' 'text'       'string' 'Sample latency for stimulus (ms)' } ...
                         { 'style' 'edit'       'string' '0' } };
        geometry = { [2 1] [2 1] };
        result       = inputgui( 'geometry', geometry, 'uilist', promptstr, ...
                                 'helpcom', 'pophelp(''pop_importegimat'')', ...
                                 'title', 'Import a Matlab file from Netstation -- pop_runica()');

        if length(result) == 0 return; end;
        srate = str2num(result{1});
        latpoint0 = str2num(result{2});
        if isempty(latpoint0), latpoint0 = 0; end;
    end;

    EEG = eeg_emptyset;
    fprintf('Reading EGI Matlab file %s\n', filename);
    tmpdata = load('-mat', filename);

    % get data types
    % --------------
    allfields = fieldnames(tmpdata);
    for index = 1:length(allfields)
        allfields{index} = allfields{index}(1:findstr(allfields{index}, 'Segment')-2);
    end;
    datatypes = unique(allfields);
    datatypes(cellfun(@isempty, datatypes)) = [];

    % read all data
    % -------------
    counttrial = 1;
    EEG.srate  = srate;
    latency = (latpoint0/1000)*EEG.srate+1;
    for index = 1:length(datatypes)
        tindex = 1;
        for tindex = 1:length(allfields)
            if isfield(tmpdata, sprintf('%s_Segment%d', datatypes{index}, tindex))
                datatrial = getfield(tmpdata, sprintf('%s_Segment%d', datatypes{index}, tindex));
                if counttrial == 1
                    EEG.pnts = size(datatrial,2);
                    EEG.data = repmat(single(0), [size(datatrial,1), size(datatrial,2), 1000]);
                end;
                EEG.data(:,:,counttrial) = datatrial;

                EEG.event(counttrial).type    = datatypes{index};
                EEG.event(counttrial).latency = latency;
                EEG.event(counttrial).epoch   = counttrial;

                counttrial = counttrial+1;
                latency = latency + EEG.pnts;
            end;
        end;
    end;
    fprintf('%d trials read\n', counttrial-1);
    EEG.data(:,:,counttrial:end) = [];

    EEG.setname  = filename(1:end-4);
    EEG.nbchan   = size(EEG.data,1);
    EEG.trials   = counttrial-1;
    if latpoint0 ~= 1
        EEG.xmin     = -latpoint0/1000;
    end;
    EEG = eeg_checkset(EEG);

    % channel location
    % ----------------
    if all(EEG.data(end,1:10) == 0)
        disp('Deleting empty data reference channel (reference channel location is retained)');
        EEG.data(end,:)   = [];
        EEG.nbchan        = size(EEG.data,1);
        EEG = eeg_checkset(EEG);
    end;
    EEG = readegilocs(EEG);

    com = sprintf('EEG = pop_importegimat(''%s'', %3.2f, %3.2f, %d);', filename, srate, latpoint0, lookupchanfile);


function [ax1 ax2] = popout(h, xmin, xmax, extraprops)
% POPOUT create popout plot
%   [ax1 ax2] = popout(h, xmin, xmax)
%      creates a popout plot (e.g. outset plot) from axis contained in the
%      figure specfied by handle h, from limits specified by xmin and xmax.
%
%   [ax1 ax2] = popout(h, xmin, xmax, props)
%      creates a popout plot (e.g. outset plot) from axis contained in the
%      figure specfied by handle h, from limits specified by xmin and xmax
%      with axes properties specified by the structure props (see example below).
%
%      popout plot inherits all graphics properties of the axis in figure h
%      (e.g. linewidth, labels).
%
%      However, extra axes properties (for each axis) may be specfied to override inherited
%      properties, such as:
%
%      props.axes1.position = [0.1 0.15 0.7 0.8];
%      props.axes2.position = [0.5 0.1 0.4 0.6];
%      props.axes2.fontsize = 20;
%
%      Any valid axis property (fontsize, xtick, yticklabel, etc...) may be
%      provided for each axis.
%
%
%   example:
%
%       x=0:0.1:100;
%       y = bessel(1,x);
%       plot(x,y);
%       popout(gcf, 10, 20);
%
%   more complex popout plot example (with axes properties):
%
%       x=0:0.1:100;
%       y1 = bessel(1,x);
%       y2 = bessel(1,x)*2;
%
%       plot(x,y1, x, y2, 'linewidth', 2); grid on;
%       xlabel('time'); ylabel('amplitude');
%       title('Bessel functions');
%       set(gca, 'fontsize', 12, 'linewidth', 2);
%       ylim([-10 2]);
%
%       props.axes1.position = [0.1 0.15 0.7 0.8];
%       props.axes2.position = [0.5 0.1 0.4 0.6];
%       props.axes1.fontsize = 10;
%       props.axes2.fontsize = 18;
%       props.axes2.linewidth = 2;
%       props.xlabel = 'poput x-label';
%       props.ylabel = 'poput y-label';
%
%       [ax1 ax2] = popout(gcf, 10, 20, props);
%
%
%       Copyright 2011, Simon Henin <shenin@gc.cuny.edu>

if nargin < 4,
   extraprops.axes1.position = [0.1 0.55 0.65 0.35];
   extraprops.axes2.position = [0.3 0.1 0.6 0.55];
end

type = get(h, 'Type');
if strcmp(type, 'figure'),
    ax1 = get(h, 'children');
else
    ax1 = h;
end
padding = 10;

ax2 = copyobj(ax1, h);
set(ax2, 'position', extraprops.axes2.position);

children = get(ax1, 'children');
ylimit = get(ax1, 'Ylim');
set(ax1, 'position',extraprops.axes1.position);
ylim([ylimit(1)+ylimit(1)*(padding/100) ylimit(2)+ylimit(2)*(padding/100)]);

ymax = 0;
ymin = 0;
for i=1:length(children),
    xd = get(children(i), 'xdata');
    index = find(xd >= xmin & xd <= xmax);
    yd =  get(children(i), 'ydata');
    ymax = max(ymax, max(yd(index)));
    ymin = min(ymin, min(yd(index)));
end

axes(ax1);
rectangle('Position',[xmin,ymin,xmax-xmin,ymax-ymin], 'linewidth', 2);

axes(ax2);
title('');
xlim([xmin xmax]);
ylim([ymin ymax]);

% add special pop-out properties
if isfield(extraprops, 'xlabel'),
   xlabel(extraprops.xlabel);
end
if isfield(extraprops, 'ylabel'),
   ylabel(extraprops.ylabel);
end

% add axes properties
fields = fieldnames(extraprops.axes1);
for i=1:length(fields),
   set(ax1, fields{i}, getfield(extraprops.axes1, fields{i}));
end
fields = fieldnames(extraprops.axes2);
for i=1:length(fields),
   set(ax2, fields{i}, getfield(extraprops.axes2, fields{i}));
end

% add pop-out lines
[xf1 yf1] = popout_getcoords(ax1, xmin, ymin);
[xf2 yf2] = popout_getcoords(ax2, xmin, ymin);
annotation('line',[xf1 xf2],[yf1 yf2]);

[xf1 yf1] = popout_getcoords(ax1, xmin, ymax);
[xf2 yf2] = popout_getcoords(ax2, xmin, ymax);
annotation('line',[xf1 xf2],[yf1 yf2]);

[xf1 yf1] = popout_getcoords(ax1, xmax, ymin);
[xf2 yf2] = popout_getcoords(ax2, xmax, ymin);
annotation('line',[xf1 xf2],[yf1 yf2]);

[xf1 yf1] = popout_getcoords(ax1, xmax, ymax);
[xf2 yf2] = popout_getcoords(ax2, xmax, ymax);
annotation('line',[xf1 xf2],[yf1 yf2]);

axes(ax2);

function [x y] = popout_getcoords(ax, x, y)

%% Get limits
axun = get(ax,'Units');
set(ax,'Units','normalized');
axpos = get(ax,'Position');
axlim = axis(ax);
axwidth = diff(axlim(1:2));
axheight = diff(axlim(3:4));

x = (x-axlim(1))*axpos(3)/axwidth + axpos(1);
y = (y-axlim(3))*axpos(4)/axheight + axpos(2);

%% Restore axes units
set(ax,'Units',axun)