function selectors = sequence_tag_selectors(experiment, sequences, tags) %#ok<INUSL>
% sequence_tag_selectors Create per-tag selector
%
% Create a set of selectors for a set of tags upon a set of sequences.
%
% Input:
% - experiment (structure): A valid experiment descriptor.
% - sequences (cell): A cell array of valid sequence descriptors.
% - tags (cell): A cell array of tag names.
%
% Output:
% - selectors (cell): A cell array of selector structures, one for each tag.
%

    selectors = cellfun(@(tag) struct('name', sprintf('tag_%s', tag), ...
        'title', tag, ...
        'groundtruth', @(sequences) groundtruth_for_tag(sequences, tag), ...
        'groundtruth_values', @(sequences, value) groundtruth_value_for_tag(sequences, tag, value), ...
        'results', @(experiment, tracker, sequences) results_for_tag(experiment, tracker, sequences, tag), ...
        'results_values', @(experiment, tracker, sequences, value) ...
        result_values_for_tag(experiment, tracker, sequences, tag, value), ...
        'length', @(sequences) count_for_tag(sequences, tag)), ...
        tags, 'UniformOutput', false);

end

function [groundtruth] = groundtruth_for_tag(sequences, tag)

    groundtruth = cell(numel(sequences), 1);

    for s = 1:length(sequences)

        filter = sequence_query_tag(sequences{s}, tag);

        if isempty(filter) || ~any(filter)
            continue;
        end;

        groundtruth{s} = sequences{s}.groundtruth(filter);

    end

end

function [results] = results_for_tag(experiment, tracker, sequences, tag)

    repeat = experiment.parameters.repetitions;

    results = cell(numel(sequences), repeat);

    if ~exist(fullfile(tracker.directory, experiment.name), 'dir')
        print_debug('Warning: Results not available %s', tracker.identifier);
        return;
    end;

    for s = 1:length(sequences)

        filter = sequence_query_tag(sequences{s}, tag);

        if isempty(filter) || ~any(filter)
            continue;
        end;

        groundtruth = sequences{s}.groundtruth;

        directory = fullfile(tracker.directory, experiment.name, sequences{s}.name);

        for j = 1:repeat

            result_file = fullfile(directory, sprintf('%s_%03d.txt', sequences{s}.name, j));

            try
                trajectory = read_trajectory(result_file);

                if (size(results{s, j}, 1) < size(groundtruth, 1))
                    %print_debug('Warning: Trajectory too short. Expanding with empty frames.');
                    trajectory(end+1:length(groundtruth)) = {0};
                end;

                results{s, j} = trajectory(filter);

            catch
                continue;
            end;

        end;

    end

end

function [results] = result_values_for_tag(experiment, tracker, sequences, tag, value)

    repeat = experiment.parameters.repetitions;

    results = cell(numel(sequences), repeat);

    if ~exist(fullfile(tracker.directory, experiment.name), 'dir')
        print_debug('Warning: Results not available %s', tracker.identifier);
        return;
    end;

    for s = 1:length(sequences)

        filter = sequence_query_tag(sequences{s}, tag);

        if isempty(filter) || ~any(filter)
            continue;
        end;

        directory = fullfile(tracker.directory, experiment.name, sequences{s}.name);

        for j = 1:repeat

            values_file = fullfile(directory, sprintf('%s_%03d_%s.value', sequences{s}.name, j, value));

            data = nan(sequences{s}.length, 1);

            i = 0;

            try
                fp = fopen(values_file, 'r');

                while true
                     line = fgets(fp);

                     if line == -1
                         break;
                     end

                     [v, numeric] = str2num(line(1:end-1)); %#ok<ST2NM>

                     if ~numeric
                        v = line(1:end-1);
                     end

                     i = i + 1;

                     if isempty(v)
                         continue;
                     end;

                     data(i) = v;

                end;

                fclose(fp);

                results{s, j} = data(filter);

            catch
                results{s, j} = nan(numel(filter), 1);
                continue;
            end;

        end;

    end

end

function [count, partial] = count_for_tag(sequences, tag)

	count = 0;

    partial = zeros(1, length(sequences));

    for s = 1:length(sequences)

        filter = sequence_query_tag(sequences{s}, tag);

        if isempty(filter)
            continue;
        end;

		count = count + numel(filter);

        partial(s) = numel(filter);

    end
end

function values = groundtruth_value_for_tag(sequences, tag, value)

    values = cell(numel(sequences), 1);

    for s = 1:length(sequences)

        filter = sequence_query_tag(sequences{s}, tag);

        if isempty(filter)
            continue;
        end;

        values{s} = sequence_get_frame_value(sequences{s}, value, filter);

    end;

end


clear all;
close all;
path(path, './code');


function hFig = view_scouts(ResultsFiles, ScoutsArg, hFig)
% VIEW_SCOUTS: Display time series for all the scouts selected in the JList.
%
% USAGE:  view_scouts()                               : Display selected sources file time series for selected scouts
%         view_scouts(ResultsFiles, 'SelectedScouts') : Display input sources file time series for selected scouts
%         view_scouts(ResultsFiles, iScouts)          : Display input sources file time series for input scouts

% @=============================================================================
% 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-2023

global GlobalData;
%% ===== PARSE INPUTS =====
if (nargin < 3) || isempty(hFig)
    hFig = [];
end
% If no parameters were given
if (nargin == 0)
    % === GET SURFACES LIST ===
    hFigures = bst_figures('GetFiguresByType', '3DViz');
    % Process all figures : and keep them that have a ResultsFile defined
    ResultsFiles = {};
    for i = 1:length(hFigures)
        resFile = getappdata(hFigures(i), 'ResultsFile');
        if ~isempty(resFile)
            ResultsFiles{end+1} = resFile;
        else
            TfInfo = getappdata(hFigures(i), 'Timefreq');
            if ~isempty(TfInfo) && ~isempty(TfInfo.FileName)
                ResultsFiles{end+1} = TfInfo.FileName;
            end
        end
    end
    ResultsFiles = unique(ResultsFiles);
    ScoutsArg = 'SelectedScouts';
    clear hFigures resFile;
end
if isempty(ResultsFiles)
    % Try displaying scouts from tree selection
    tree_view_scouts();
    return
end

%% ===== GET SCOUTS LIST =====
isVolumeAtlas = 0;
% No scout
if isempty(ScoutsArg)
    return
% Use the scouts selected in the "Scouts" panel
elseif ischar(ScoutsArg) && strcmpi(ScoutsArg, 'SelectedScouts')
    % Get current atlas
    [sAtlas, iAtlas, sSurf] = panel_scout('GetAtlas');
    if isempty(sAtlas)
        return;
    end
    % Cannot display scouts from the "Source model" atlas (different regions)
    if ~isempty(strfind(sAtlas.Name, 'Source model'))
        bst_error('Cannot use the atlas "Source model" to calculate scouts time series.', 'Display scouts', 0);
        return;
    end
    % Volume scout: Get number of vertices of the atlas
    [isVolumeAtlas, nAtlasGrid] = panel_scout('ParseVolumeAtlas', sAtlas.Name);
    % Get selected scouts
    [sScouts, iScouts, sSurf] = panel_scout('GetSelectedScouts');
% Else: use directly the scout indices in argument
else
    iScouts = ScoutsArg;
    [sScouts, sSurf] = panel_scout('GetScouts', iScouts);
end
if isempty(sScouts)
    return
end
clear ScoutsArg;

%% ===== CHECK CORRESPONDANCE SCOUTS/SURFACES =====
iDroppedRes = [];
FileType = {};
% Check each results file
for i = 1:length(ResultsFiles)
    FileType{i} = file_gettype(ResultsFiles{i});
    % Load surface file from sources file
    switch (FileType{i})
        case {'link', 'results', 'presults'}
            ResMat = in_bst_results(ResultsFiles{i}, 0, 'SurfaceFile', 'HeadModelType', 'GridLoc');
            if isVolumeAtlas && (size(ResMat.GridLoc,1) ~= nAtlasGrid)
                disp(['BST> Error: The number of grid points in this atlas (' num2str(nAtlasGrid) ') does not match the loaded source file (' num2str(size(ResMat.GridLoc,1)) ').']);
                iDroppedRes(end+1) = i;
                continue;
            elseif ~isVolumeAtlas && strcmpi(ResMat.HeadModelType, 'volume')
                disp(['BST> Error: Cannot use a surface scout to extract value from a volume grid.']);
                iDroppedRes(end+1) = i;
                continue;
            end
        case {'timefreq', 'ptimefreq'}
            ResMat = in_bst_timefreq(ResultsFiles{i}, 0, 'SurfaceFile', 'HeadModelType', 'DataFile');
            if isempty(ResMat.SurfaceFile) && ~isempty(ResMat.DataFile)
                ResMat = in_bst_results(ResMat.DataFile, 0, 'SurfaceFile', 'HeadModelType');
            else
                ResMat.SurfaceFile   = sSurf.FileName;
                ResMat.HeadModelType = 'surface';
            end
    end
    % If surface is not the same as scouts' one, drop this results file
    if ~isempty(ResMat.SurfaceFile) && ~file_compare(ResMat.SurfaceFile, sSurf.FileName)    % || ismember(ResMat.HeadModelType, {'volume', 'mixed'})
        iDroppedRes = [iDroppedRes, i];
    end
end
if ~isempty(iDroppedRes)
    ResultsFiles(iDroppedRes) = [];
    FileType(iDroppedRes) = [];
end
% Check number of results files
if isempty(ResultsFiles)
    java_dialog('warning', 'No available source files.', 'Display scouts');
    return
end

%% ===== PREPARE DATA TO DISPLAY =====
% Initialize common descriptors (between all files)
SubjectFile    = '*';
StudyFile      = '*';

% Get display options
ScoutsOptions = panel_scout('GetScoutsOptions');
% if (length(iScouts) == 1)
%     ScoutsOptions.overlayScouts = 0;
% end
if (length(ResultsFiles) == 1)
    ScoutsOptions.overlayConditions = 0;
end
% Initialize data to display
scoutsActivity = cell(length(ResultsFiles), length(iScouts));
scoutsStd      = cell(length(ResultsFiles), length(iScouts));
scoutsLabels   = cell(length(ResultsFiles), length(iScouts));
scoutsColors   = cell(length(ResultsFiles), length(iScouts));
axesLabels     = cell(length(ResultsFiles), length(iScouts));
allComponents  = [];
TimeVector     = [];
issloreta      = 0;
DisplayUnits   = [];
Freqs          = [];
% Process each Results file
for iResFile = 1:length(ResultsFiles)
    % Is stat
    isStat = ismember(FileType{iResFile}, {'presults', 'ptimefreq'});
    isTimefreq = ismember(FileType{iResFile}, {'timefreq', 'ptimefreq'});
    isPsd = isTimefreq && (~isempty(strfind(ResultsFiles{iResFile}, '_psd')) || ~isempty(strfind(ResultsFiles{iResFile}, '_fft')));

    % ===== GET/CREATE RESULTS DATASET =====
    bst_progress('start', 'Display scouts time series', ['Loading results file: "' ResultsFiles{iResFile} '"...']);
    % Load results
    if ~isTimefreq
        [iDS, iResult] = bst_memory('LoadResultsFileFull', ResultsFiles{iResFile});
        if isempty(iDS)
            disp(['BST> Cannot load file : "', ResultsFiles{iResFile}, '"']);
            bst_progress('stop');
            return;
        end
        if ~isempty(strfind(lower(ResultsFiles{iResFile}), 'sloreta')) || ~isempty(strfind(lower(GlobalData.DataSet(iDS).Results(iResult).Comment), 'sloreta'))
            issloreta = 1;
        end
        fileUnits = GlobalData.DataSet(iDS).Results(iResult).DisplayUnits;
    else
        [iDS, iTimefreq, iResult] = bst_memory('LoadTimefreqFile', ResultsFiles{iResFile}, 1, 0);
        if isempty(iDS)
            disp(['BST> Cannot load file : "', ResultsFiles{iResFile}, '"']);
            bst_progress('stop');
            return;
        end
        fileUnits = GlobalData.DataSet(iDS).Timefreq(iTimefreq).DisplayUnits;
        % Check frequencies
        if isempty(Freqs)
            Freqs = GlobalData.DataSet(iDS).Timefreq(iTimefreq).Freqs;
        elseif length(Freqs) ~= length(GlobalData.DataSet(iDS).Timefreq(iTimefreq).Freqs)
            error('The frequencies must match across all files.');
        end
    end
    % Make sure that units are the same for all the files
    if ~isempty(fileUnits)
        if isempty(DisplayUnits)
            DisplayUnits = fileUnits;
        elseif ~strcmpi(fileUnits, DisplayUnits)
            DisplayUnits = 'Mixed units';
        end
    end

    % Get study
    [sStudy,   iStudy]   = bst_get('Study',   GlobalData.DataSet(iDS).StudyFile);
    if isempty(sStudy)
        error(['No study registered for file: ' strrep(ResultsFiles{iResFile},'\\','\')]);
    end
    % Get results subjectName/condition/#solInverse (FOR TITLE ONLY)
    [sSubject, iSubject] = bst_get('Subject', sStudy.BrainStormSubject);
    isInterSubject = (iStudy == -2);
    % Get identification of figure
    if ~isempty(SubjectFile)
        if (SubjectFile(1) == '*')
            SubjectFile = sStudy.BrainStormSubject;
        elseif ~file_compare(SubjectFile, sStudy.BrainStormSubject)
            SubjectFile = [];
        end
    end
    if ~isempty(StudyFile)
        if (StudyFile(1) == '*')
            StudyFile = GlobalData.DataSet(iDS).StudyFile;
        elseif ~file_compare(StudyFile, GlobalData.DataSet(iDS).StudyFile)
            StudyFile = [];
        end
    end
    DataFile = GlobalData.DataSet(iDS).DataFile;

    % ===== Prepare cell array containing time series to display =====
    TfFunction = [];
    for k = 1:length(sScouts)
        % ===== Get data to display =====
        % Get list of useful vertices in ImageGridAmp
        if ~isempty(iResult) && ~isempty(GlobalData.DataSet(iDS).Results(iResult).Atlas)
            % Atlas-based source file: use only the seed of the scout, and find it in the file atlas
            iVertices = panel_scout('GetScoutForVertex', GlobalData.DataSet(iDS).Results(iResult).Atlas, sScouts(k).Seed);
            % Error management
            if isempty(iVertices)
                disp('BST> Error: No data to display.');
                bst_progress('stop');
                return;
            end
        else
            % SORT and select unique vertices
            iVertices = sort(unique(sScouts(k).Vertices));
        end
        % Fix errors in color definition
        if isequal(size(sScouts(k).Color), [3,1])
            sScouts(k).Color = sScouts(k).Color';
        end
        % Get data (over current time window)
        if ~isTimefreq
            [DataToPlot, nComponents, DataStd] = bst_memory('GetResultsValues', iDS, iResult, iVertices, 'UserTimeWindow', 0, isVolumeAtlas);
        else
            % PSD: Get all the frequencies, display as a spectrum
            if isPsd
                iFreqs = [];
            % Other time-frequency files: Get only the selected time, display as time series
            else
                iFreqs = GlobalData.UserFrequencies.iCurrentFreq;
            end
            [DataToPlot, iTimeBands, iRow, nComponents] = bst_memory('GetTimefreqValues', iDS, iTimefreq, iVertices, iFreqs, 'UserTimeWindow', []);
            DataStd = [];
            % If the values are complex: try to get the measure from the Display tab
            if ~all(isreal(DataToPlot(:)))
                % If time-frequency function not found yet
                if isempty(TfFunction)
                    % Get current panel options
                    sOptions = panel_display('GetDisplayOptions');
                    TfFunction = sOptions.Function;
                    % Display warning
                    disp(['BST> Warning: We don''t know how to convert these complex values to real values. Trying function "' TfFunction '".']);
                end
                % Apply function
                [DataToPlot, isError] = process_tf_measure('Compute', DataToPlot, 'none', TfFunction);
                if isError
                    bst_error(['Invalid measure conversion: none => ' TfFunction], 'Display scouts', 0);
                    return;
                end
            end
        end

        % Get the time vector
        if isTimefreq && ~isempty(iTimeBands)
            TimeBands = process_tf_bands('GetBounds', GlobalData.DataSet(iDS).Timefreq(iTimefreq).TimeBands(iTimeBands,:));
            TimeVectorTmp = mean(TimeBands,2)';
        else
            [TimeVectorTmp, iTime] = bst_memory('GetTimeVector', iDS, [], 'UserTimeWindow');
            TimeVectorTmp = TimeVectorTmp(iTime);
        end
        if ~isempty(TimeVector) && (length(TimeVectorTmp) ~= length(TimeVector))
            bst_error('The time definition between the different files do not match.', 'Display scouts', 0);
            return;
        elseif isempty(TimeVector)
            TimeVector = TimeVectorTmp;
        end

        % Compute the scout values
        if ~isStat || strcmpi(sScouts(k).Function, 'All')
            ScoutFunction = sScouts(k).Function;
        else
            ScoutFunction = 'stat';
        end

        % Mixed headmodel results
        if (nComponents == 0)
            % Get atlas "Source model"
            GridAtlas = GlobalData.DataSet(iDS).Results(iResult).GridAtlas;
            % Get the vertex indices of the scout in GridLoc
            [iRows, iRegionScouts, iVertices] = bst_convert_indices(iVertices, nComponents, GridAtlas, ~isVolumeAtlas);
            % Do not accept scouts that span over multiple regions
            if isempty(iRegionScouts)
                bst_error(['Scout "' sScouts(k).Label '" is not included in the source model.' 10 'If you use this region as a volume, create a volume scout instead (menu Atlas > New atlas > Volume scouts).'], 'Display scouts', 0);
                return;
            elseif (length(iRegionScouts) > 1)
                bst_error(['Scout "' sScouts(k).Label '" spans over multiple regions of the "Source model" atlas.'], 'Display scouts', 0);
                return;
            end
            % Do not accept volume atlases with non-volume head models
            if ~isVolumeAtlas && strcmpi(GridAtlas.Scouts(iRegionScouts).Region(2), 'V')
                bst_error(['Scout "' sScouts(k).Label '" is a surface scout but region "' GridAtlas.Scouts(iRegionScouts).Label '" is a volume region.'], 'Display scouts', 0);
                return;
            elseif isVolumeAtlas && strcmpi(GridAtlas.Scouts(iRegionScouts).Region(2), 'S')
                bst_error(['Scout "' sScouts(k).Label '" is a volume scout but region "' GridAtlas.Scouts(iRegionScouts).Label '" is a surface region.'], 'Display scouts', 0);
                return;
            end
            % Set the scout computation properties based on the information in the "Source model" atlas
            if strcmpi(GridAtlas.Scouts(iRegionScouts).Region(3), 'C')
                nComponents = 1;
                VertNormals = GlobalData.DataSet(iDS).Results(iResult).GridOrient(iVertices,:);
            else
                nComponents = 3;
                VertNormals = [];
            end
        % Volume scouts
        elseif (nComponents == 3)
            VertNormals = [];
        else
            % Get vertex normals
            VertNormals = sSurf.VertNormals(iVertices,:);
        end
        % Keep track of how many components are available
        allComponents(end+1) = nComponents;

        % Only one component
        if (nComponents == 1)
            % Get meaningful tags in the results file name (without folders)
            TestResFile = file_resolve_link(ResultsFiles{iResFile});
            [tmp, TestTags] = bst_fileparts(TestResFile);
            % Do not flip sign for statistics, norms or NIRS source maps
            isFlipSign = ~isStat && ~isTimefreq && ...
                         strcmpi(GlobalData.DataSet(iDS).Results(iResult).DataType, 'results') && ...
                         isempty(strfind(TestTags, '_abs')) && ...
                         isempty(strfind(TestTags, '_norm')) && ...
                         isempty(strfind(TestTags, 'NIRS')) && ...
                         isempty(strfind(TestTags, 'Summed_sensitivities')) && ...
                         (isempty(GlobalData.DataSet(iDS).Channel) || isempty(GlobalData.DataSet(iDS).Results(iResult).GoodChannel) || ...
                          ~ismember('NIRS', {GlobalData.DataSet(iDS).Channel(GlobalData.DataSet(iDS).Results(iResult).GoodChannel).Type}));
            iTrace = k;
            scoutsActivity{iResFile,iTrace} = bst_scout_value(DataToPlot, ScoutFunction, VertNormals, nComponents, 'none', isFlipSign);
            if ~isempty(DataStd)
                scoutsStd{iResFile,iTrace} = bst_scout_value(DataStd, ScoutFunction, VertNormals, nComponents, 'none', 0);
            else
                scoutsStd{iResFile,iTrace} = [];
            end
            if ScoutsOptions.displayAbsolute
                scoutsActivity{iResFile,iTrace} = abs(scoutsActivity{iResFile,iTrace});
            end
        % More than one component & Absolute: Display the norm
        elseif ScoutsOptions.displayAbsolute
            iTrace = k;
            scoutsActivity{iResFile,iTrace} = bst_scout_value(DataToPlot, ScoutFunction, VertNormals, nComponents, 'norm');
            if ~isempty(DataStd)
                scoutsStd{iResFile,iTrace} = bst_scout_value(DataStd, ScoutFunction, VertNormals, nComponents, 'norm');
            else
                scoutsStd{iResFile,iTrace} = [];
            end
        % More than one component & Relative: Display the three components
        else
            iTrace = nComponents * (k-1) + (1:nComponents);
            tmp = bst_scout_value(DataToPlot, ScoutFunction, VertNormals, nComponents, 'none');
            for iComp = 1:nComponents
                scoutsActivity{iResFile,iTrace(iComp)} = tmp(iComp:nComponents:end,:);
            end
            % Std
            if ~isempty(DataStd)
                tmp = bst_scout_value(DataStd, ScoutFunction, VertNormals, nComponents, 'none');
                for iComp = 1:nComponents
                    scoutsStd{iResFile,iTrace(iComp)} = tmp(iComp:nComponents:end,:);
                end
            else
                scoutsStd(iResFile,iTrace) = repmat({[]}, 1, nComponents);
            end
        end

        % === AXES LABELS ===
        % === SUBJECT NAME ===
        % Format: SubjectName/CondName/(DataComment)/(Sol#iResult)/(ScoutName)
        strAxes = '';
        if ~isempty(sSubject) && (iSubject > 0)
            strAxes = sSubject.Name;
        end
        % === CONDITION ===
        % If inter-subject node
        if isInterSubject
            strAxes = [strAxes, 'Inter'];
        % Else: display conditions name
        else
            for i = 1:length(sStudy.Condition)
                strAxes = [strAxes, '/', sStudy.Condition{i}];
            end
        end
        % === DATA COMMENT ===
        % If more than one data file in this study : display current data comments
        if ~isempty(DataFile) && ((length(sStudy.Data) > 1) || isInterSubject)
            % Find DataFile in current study
            iData = find(file_compare({sStudy.Data.FileName}, DataFile));
            % Add Data comment
            if ~isempty(iData)
                strAxes = [strAxes, '/', sStudy.Data(iData).Comment];
            end
        end
        % === RESULTS COMMENT ===
        % If more than one results file in study : display indice
        if ~isempty(DataFile) && isStat && (length(sStudy.Stat) > 1)   % Stat
            % Get list of results files for current data file
            [tmp__, tmp__, iStatInStudy] = bst_get('StatForDataFile', DataFile, iStudy);
            % More than one results file for this data file
            if (length(iStatInStudy) > 1)
                strAxes = [strAxes, '/', GlobalData.DataSet(iDS).Results(iResult).Comment];
            end
        elseif ~isempty(DataFile) && isTimefreq && (length(sStudy.Timefreq) > 1)    % Time-frequency
            % Get list of results files for current data file
            [tmp__, tmp__, iTfInStudy] = bst_get('TimefreqForFile', DataFile, iStudy);
            % More than one results file for this data file
            if (length(iTfInStudy) > 1)
                strAxes = [strAxes, '/', GlobalData.DataSet(iDS).Timefreq(iTimefreq).Comment];
            end
        elseif ~isempty(DataFile) && ~isTimefreq && (length(sStudy.Result) > 1)    % Source maps
            % Get list of results files for current data file
            [tmp__, tmp__, iResInStudy] = bst_get('ResultsForDataFile', DataFile, iStudy);
            % More than one results file for this data file
            if (length(iResInStudy) > 1)
                strAxes = [strAxes, '/', GlobalData.DataSet(iDS).Results(iResult).Comment];
            end
        % Inter-subject: always display whole
        elseif (isempty(DataFile) || isInterSubject)
           strAxes = [strAxes, '/', GlobalData.DataSet(iDS).Results(iResult).Comment];
        end
        [axesLabels{iResFile,iTrace}] = deal(strAxes);

        % === SCOUTS LABELS/COLORS ===
        if (length(iTrace) == 1)
            scoutsLabels{iResFile,iTrace} = sScouts(k).Label;
            [scoutsColors{iResFile,iTrace}] = deal(sScouts(k).Color);
        else
            for iComp = 1:length(iTrace)
                scoutsLabels{iResFile,iTrace(iComp)} = sprintf('%s%d', sScouts(k).Label, iComp);
                scoutsColors{iResFile,iTrace(iComp)} = sScouts(k).Color .* (1 - .25 * (iComp-1));
            end
        end
    end
end

% ===== DISPLAY STATIC VALUES =====
% Get the number of time samples for all the scouts
nbTimes = cellfun(@(c)size(c,2), scoutsActivity(:));
% If both real scouts and static values
if ((max(nbTimes) > 2) && any(nbTimes == 2))
    iCellToExtend = find(nbTimes == 2);
    for i = 1:length(iCellToExtend)
        scoutsActivity{iCellToExtend(i)} = repmat(scoutsActivity{iCellToExtend(i)}(:,1), [1,max(nbTimes)]);
        if ~isempty(scoutsStd)
            scoutsStd{iCellToExtend(i)} = repmat(scoutsStd{iCellToExtend(i)}(:,1), [1,max(nbTimes)]);
        end
    end
end

%% ===== LEGENDS =====
% Get common beginning part in all the axesLabels
[ strAxesCommon, axesLabels ] = str_common_path( axesLabels );
% Only one time series: no overlay scout
if (size(scoutsActivity,2) == 1)
    ScoutsOptions.overlayScouts = 0;
end
% Cannot mix number of components
isMixedComponents = ~ScoutsOptions.displayAbsolute && (length(allComponents) > 1) && any(allComponents ~= allComponents(1));
if isMixedComponents
    bst_error(['Cannot display scouts with mixed numbers of components at the same time ' 10 'when the option "Values: Relative" is selected.'], 'Display scouts', 0);
    return;
end
% If at least one of the scouts functions is "All", ignore the overlay checkboxes
if ~isempty(sScouts) && any(strcmpi({sScouts.Function}, 'All'))
    ScoutsOptions.overlayScouts     = 0;
    ScoutsOptions.overlayConditions = 0;
end

% === NO OVERLAY ===
% Display all timeseries (ResultsFiles/Scouts) in different axes on the same figure
if (~ScoutsOptions.overlayScouts && ~ScoutsOptions.overlayConditions)
    % One graph for each line => Ngraph, Nscouts*Ncond
    % Scouts activity = cell-array {1, Ngraph} of doubles [1,Nt]
    scoutsActivity = scoutsActivity(:)';
    % Axes labels (subj/cond) = cell-array of strings {1, Ngraph}
    axesLabels = axesLabels(:)';
    % Scouts labels = cell-array of strings {1, Ngraph}
    scoutsLabels = scoutsLabels(:)';
    scoutsColors = scoutsColors(:)';
    % Eliminate empty entries
    iEmpty = find(cellfun(@isempty, scoutsActivity));
    if ~isempty(iEmpty)
        scoutsActivity(iEmpty) = [];
        scoutsLabels(iEmpty) = [];
        axesLabels(iEmpty) = [];
        if ~isempty(scoutsColors)
            scoutsColors(iEmpty) = [];
        end
    end
    % Std
    if ~isempty(scoutsStd)
        scoutsStd = scoutsStd(:)';
        if ~isempty(iEmpty)
            scoutsStd(iEmpty) = [];
        end
    end

% === OVERLAY SCOUTS AND CONDITIONS ===
% Only one graph with Nlines = Nscouts*Ncond
elseif (ScoutsOptions.overlayScouts && ScoutsOptions.overlayConditions)
    % Linearize entries
    scoutsActivity = scoutsActivity(:);
    scoutsLabels = scoutsLabels(:);
    axesLabels = axesLabels(:);
    % Eliminate empty entries
    iEmpty = find(cellfun(@isempty, scoutsActivity));
    if ~isempty(iEmpty)
        scoutsActivity(iEmpty) = [];
        scoutsLabels(iEmpty) = [];
        axesLabels(iEmpty) = [];
    end
    % Scouts activity = double [Nlines, Nt]
    scoutsActivity = cat(1, scoutsActivity{:});
    % Scouts labels = cell-array {Nlines}
    % => "scoutName @ subject/condition"
    for iTrace = 1:size(scoutsActivity,1)
        if ~isempty(axesLabels{iTrace})
            scoutsLabels{iTrace} = [scoutsLabels{iTrace} ' @ ' axesLabels{iTrace}];
        end
    end
    scoutsColors = [];
    % Only one graph => legend is common scouts parts
    if ~isempty(strAxesCommon)
        axesLabels = {strAxesCommon};
    else
        axesLabels = {'Mixed subjects'};
    end
    % Std
    if ~isempty(scoutsStd)
        scoutsStd = scoutsStd(:);
        if ~isempty(iEmpty)
            scoutsStd(iEmpty) = [];
        end
        scoutsStd = cat(1, scoutsStd{:});
    end

% === OVERLAY SCOUTS ONLY ===
elseif ScoutsOptions.overlayScouts
    % One graph per condition => Ngraph = Ncond
    % Scouts activity = cell-array {1, Ncond} of doubles [Nscout, Nt]
    for i = 1:size(scoutsActivity,1)
        scoutsActivityTmp(i) = {cat(1, scoutsActivity{i,:})};
    end
    scoutsActivity = scoutsActivityTmp;
    % Axes labels (subj/cond) = cell-array of strings {1, Ncond}
    axesLabels   = axesLabels(:,1)';
    % Scouts labels = cell-array of strings {Nscout, Ncond}
    scoutsLabels = scoutsLabels';
    scoutsColors = scoutsColors';
    % Std
    if ~isempty(scoutsStd)
        for i = 1:size(scoutsStd,1)
            scoutsStdTmp(i) = {cat(1, scoutsStd{i,:})};
        end
        scoutsStd = scoutsStdTmp;
    end

% === OVERLAY CONDITIONS ONLY ===
elseif ScoutsOptions.overlayConditions
    % One graph per scout => Ngraph = Nscout
    % Scouts activity = cell-array {1, Nscout} of doubles [Ncond, Nt]
    for j = 1:size(scoutsActivity,2)
        scoutsActivityTmp(j) = {cat(1, scoutsActivity{:,j})};
    end
    scoutsActivity = scoutsActivityTmp;
    % Axes labels (scout names @ common_subject/cond part) = cell-array of strings {1, Nscout}
    tmpAxesLabels = axesLabels;
    axesLabels = scoutsLabels(1,:);
    % Lines labels (subject/condition) = cell-array of strings {Ncond, Nscout}
    scoutsLabels = tmpAxesLabels;
    scoutsColors = [];
    % Std
    if ~isempty(scoutsStd)
        for j = 1:size(scoutsStd,2)
            scoutsStdTmp(j) = {cat(1, scoutsStd{:,j})};
        end
        scoutsStd = scoutsStdTmp;
    end
end
% Close progress bar
bst_progress('stop');

%% ===== CALL DISPLAY FUNCTION ====
% Get type of the first file
switch (file_gettype(ResultsFiles{1}))
    case {'results', 'link'}
        if issloreta
            Modality = 'sloreta';
        else
            Modality = 'results';
        end
    case 'timefreq'
        Modality = 'timefreq';
    case 'presults'
        Modality = 'stat';
    otherwise
        Modality = 'none';
end
% Plot spectrum
if isPsd
    if (length(scoutsActivity) > 1)
        error(['Scouts on PSD plots can only show one graph at a time.' 10 ...
               'Open only one file at a time and enable the "overlay" options.']);
    end
    hFig = view_spectrum_matrix(ResultsFiles, 'Spectrum', scoutsActivity{1}, scoutsLabels, TimeVector, Freqs, 'Scout', DisplayUnits);
% Plot time series
else
    hFig = view_timeseries_matrix(ResultsFiles, scoutsActivity, TimeVector, Modality, axesLabels, scoutsLabels, scoutsColors, hFig, scoutsStd, DisplayUnits);
end
% Associate the file with one specific result file
setappdata(hFig, 'ResultsFiles', ResultsFiles);
if (length(ResultsFiles) == 1)
    setappdata(hFig, 'ResultsFile', ResultsFiles);
else
    setappdata(hFig, 'ResultsFile', []);
end
% Update figure name
bst_figures('UpdateFigureName', hFig);
% Set the time label visible
figure_timeseries('SetTimeVisible', hFig, 1);


function should_evaluate = exp_beginfun(setting, varargin)
% Begin the definition of a function with lazy evaluation behavior.
% Evaluate = exp_beginfun(AttributeSetting, Attributes...)
%
% General
% =======
% If a function uses exp_beginfun/exp_endfun, MATLAB code that uses these functions does not "call"
% them in an imperative manner, but instead builds symbolic expressions that can be stored,
% transmitted, or manipulated before/if they are being evaluated. Evaluation is generally done via
% exp_eval() (or exp_eval_optimized()). Almost all uses of exp_beginfun by functions look like this:
%
%   function [res1,res2,...] = my_function(arg1,arg2, ...)
%   if ~exp_beginfun('somesetting') return; end
%
%   ... <actual implementation> ...
%
%   exp_endfun;
%
% ... where 'somesetting' specifies the behavioral setting for the function. Generally, all
% functions that operate on data sets and modify the signal content in a way that needs to be
% replicated online have the setting 'filter', and all functions that have no equivalent during
% online processing (e.g., marker transcription) have the setting 'editing'. Functions that do
% processing that cannot be reproduced online have the setting 'offline'. See filters/flt_* and
% dataset_ops/set_* for some examples. A setting is a shortcut name for an assignment of attributes
% associated with the function declaration (attributes can also be directly specified to
% exp_beginfun).
%
%
% Filter and Editing Functions
% ============================
% Functions of type 'filter' should be written just like regular EEGLAB signal processing functions
% (i.e., they receive a data set and some options, and produce a transformed version of the data
% set), but functions that are supposed to be usable online should be "chunkable", i.e. they should
% produce the same sequence of output samples when they are called for consecutive segments (chunks)
% of the data set. Thus, the function should dump its internal state in an additional output (the
% 'state' variable) and be able to resume from this state when called on the next chunk. Chunks may
% well be just a single sample long. During online processing, state will be passed in automatically.
%
% In some cases, this strict behavior would be prohibitively inefficient - in this case, the
% function may refrain to non-adaptive behavior if the chunks are too short. In particular, in
% BCILAB, it may be assumed that each filter is first called on a complete calibration data set in a
% single call, and is then either called on a complete test data set in one call (the next 'chunk'),
% or piecemeal on small blocks of an incoming online stream. ICA, for example, adapts itself only on
% the calibration data set (i.e. the first chunk) and uses that decomposition for all remaining ones
% (i.e. during online use).
%
% Functions of type 'editing' should be written just like regular EEGLAB data set editing functions
% (e.g. pop_chanedit), BUT they must not change the data or srate fields.
%
% Rationale/Background
% ====================
% For the purpose of data processing function declarations, the exp_beginfun/exp_endfun contract
% handles the book-keeping necessary to be able to reproduce the filter (and entire filter chain)
% 1:1 online, and offers additional functionality, such as transparent caching of results (for later
% near-instantaneuous retrieval) and the annotation of all data sets with an executable expression
% which, when invoked, reproduces the data set 1:1, as well as checks to guarantee that no invalid
% "tampering" is done by the user on data sets (such as running a forward-backward filter in EEGLAB
% or manually scaling the data).
%
% In its general form, the functions exp_beginfun/exp_endfun are part of the machinery that enables
% symbolic computation in MATLAB (in the style of Mathematica), and the
% 'filter'/'editing'/'symbolic'/'annotate'/'default' argument is a short-cut which selects default
% attributes of the function being defined (which can be overridden). Some of these attributes are
% customizable plugin "slots" (plugins to be applied to each argument or all inputs or all outputs
% of the function being defined). 'Filter'/'editing' imply such hooks as default attributes, which
% in turn implement the tamper-proofing checks and some post-processing on the emitted data sets.
% The post-processing adds fields (subfields of the .tracking field) to the (otherwise arbitrary)
% data set structs that are produced by the function being defined, most importantly the
% .tracking.expression, which is an executable (recursive) expression representation which mirrors
% each data set in symbolic form, and .tracking.online_expression, in the case of filters, which
% defines the arguments to be used for the respective filter during online processing. These
% operations are usually completely transparent to the user (and locked away in the .tracking
% field), and make it exceptionally simple to author components for the toolbox.
%
% The symbolic-computation functionality implemented by the expressions/exp_* functions is largely
% non-overlapping with what the MATLAB Symbolic toolbox offers: the Symbolic toolbox exclusively
% implements mathematics (e.g. derivatives, integrals, simplifications), while the expression system
% of BCILAB exclusively implements expression manipulation, such as substitution, pattern matching,
% hashing, memoization, and reflection. These facilities allow to implement very high-level behavior
% in functions such as bci_train, such as generic programming (e.g. utl_crossval, utl_searchmodel,
% utl_gridsearch being non-intrusively adapted to EEG sets, STUDY sets, etc.), optimizations (e.g.
% in-memory caching and on-disk caching of intermediate results, computational shortcuts, and more
% in the future), with relatively little coding effort.
%
% While, by default, a function is invoked online with the same parameters that were used for
% offline processing (aside from a 'state' parameter for stateful functions), it is is possible to
% specify that certain parameters should have different values during online processing. This is
% enabled by the attribute 'append_online' (list of arguments to be appended in the online case),
% which can be specified either to exp_beginfun or exp_endfun. Alternatively, the entire expression
% can be subsituted by setting the 'set_online' attribute (expression to use during online
% processing).
%
% In:
%  AttributeSetting: baseline attribute assignment for the function; must be specified. Below are
%                    typical scenarios:
%                    * defining a function that should not evaluate immediately, but only after
%                      evaluation by an exp_eval() clause:
%                      'default' : delayed execution, input fingerprint checks
%
%                    * defining a function that processes data sets and should have
%                      delayed-evaluation semantics:
%                      'filter'  : functions that do non-trivial processing of data sets, and which
%                                  should support online replication of some offline processing;
%                                  attributes: delayed (1), add_impure (1), set_online
%                                  ('reproduce'), also performs EEGLAB-aware input data set checks &
%                                  transforms note: when an offline processing is replicated online,
%                                  the function is called successively on short EEGLAB data sets,
%                                  with inputs identical to the input parameters as they were
%                                  snapshot after execution of the function's body (by exp_endfun)
%                      'editing' : functions that do processing of data sets which has no equivalent
%                                  in online processing, i.e. which pass the input data through
%                                  unmodified; attributes: delayed (1), add_impure (1), set_online
%                                  ('passthrough'), also performs EEGLAB-aware input data set checks
%                      'offline' : functions that do processing of data sets which cannot be
%                                  reproduced online; these generate an error, when used within
%                                  online BCIss; attributes: delayed (1), add_impure (1), set_online
%                                  ('inapplicable'), also performs EEGLAB-aware input data set
%                                  checks
%
%                    * definining a function that imports or constructs data sets from scratch:
%                      'import'  : functions that construct a data set from scratch based on
%                                  arguments that are not data sets
%
%                    * defining a function that performs purely symbolic manipulations of
%                      expressions:
%                      'symbolic': all inputs are held unevaluated, associated (impure) values are
%                                  ignored (thus no fingerprinting and no memoization); attributes:
%                                  delayed (1), hold ('all'), fingerprints unchecked & passed
%                                  through, memoize (0)
%                                  note: symbolic functions cannot be used in online code.
%
%                    * defining a function that performs immediate (non-lazy) processing of data
%                      structures, and which shall have their results annotated with an executable
%                      (repeatable) description of the processing performed:
%                      'annotate': delayed (0), add_impure (1)
%
%
%   Attributes... : optional name-value pairs to refine attributes of the function being defined:
%
%                     'delayed'   : whether the function has delayed-evaluation semantics, i.e. it
%                                   doesn't evaluate unless passed to exp_eval (default: 1)
%
%                     'hold'      : []/'all'/'first'/'rest'; whether to leave all/the first/the
%                                   remaining arguments or no arguments of the function unevaluated
%                                   before executing the function body (useful for higher-order
%                                   functions), (default: [])
%
%                     'symbolic'  : whether the function being defined should be evaluated even if it
%                                   receives unevaluated expressions as arguments (ignoring those
%                                   arguments that fall under hold) - this is for symbol-manipulating
%                                   functions that can process unevaluated expressions (0/1, default: 0)
%
%                     'add_impure': integrate the original expression into the function value (when
%                                   evaluated), creating an 'impure expression', 0/1
%
%                     'set_online': Relevant only when the settings attribute is 'filter', 'editing'
%                                   or 'offline', may also be specified in exp_endfun. Introduces an
%                                   'online expression' into the function value (when evaluated),
%                                   which is a record of how the resulting data (usually a signal)
%                                   should be calculated online. Usually, this is identical to how
%                                   the filter is applied offline.
%                                   * 'passthrough': the function is skipped in the online
%                                                    processing
%                                   * 'reproduce': the function is called online with the same arguments
%                                                  as in offline processing
%                                   * 'inapplicable': the function gives an error when used in
%                                                     online processing
%                                   * {arg1, arg2, arg3, ...}: the given arguments will be used as
%                                                              function arguments during online use
%                                   * expression: the given expression will be used during online
%                                                 reproduction of the filter
%
%                     'append_online' : {arg1, arg2, arg3, ...}: the given arguments will be
%                                       appended to the function arguments during online use
%                                       (default: [])
%
%                     'argsteps', 'presteps', 'poststeps' : advanced attributes, see also "Advanced
%                                                           Functionality" section.
%
%                     'delayed_online' : whether this function has delayed-evaluation semantics
%                                        during online processing (default: false)
%
% Out:
%   Evaluate     : whether the body of the function being defined shall be evaluated or skipped
%
%
% Advanced functionality:
%   The allowed settings are customizable/extensible via the function exp_settings, which maps a
%   setting string onto a cell array of default attributes (name-value pairs).
%
%   During evaluation, each input argument is sent though a customizable sequence of functions,
%   which can be specified via the 'argsteps' attribute (as a cell array). Further, the attriutes
%   'presteps' and 'poststeps' are cell arrays of functions that should be invoked before and after
%   the function body runs, respectively; they operate on a context structure, which is defined
%   further below. These built-in attributes are the primary mechanism by which custom exp_settings
%   add functionality to exp_beginfun/exp_endfun (e.g., annotating a dataset with an expression or
%   checking fingerprints).
%
%   The function's behavior can be controlled via dynamically scoped variables, as set by exp_block,
%   exp_set and exp_setdelayed, or correspondent attributes in exp_beginfun.
%     These variables can be assigned values, such as 0/1, but may be arbitrary expressions
%      * fingerprint_check: toggle per-argument fingerprint checking (default: true) - may use
%        @expression to refer to the argument expression, to implement expression-dependent checking
%      * fingerprint_create: toggle fingerprint creation (default: true) - may use @expression to
%        refer to the unevaluated complete expression, to implement expression-dependent checking
%      * memoize: selectively enable memoization; this is a cell array of the form
%        {location,expression,location,expression, ...}. location can be either 'disk' or 'memory',
%        expression should evaluate to true/false, may use @expression to refer to the unevaluated
%        complete expression, to implement expression-dependent memoization.
%
% See also:
%   exp_endfun, exp_eval, exp_settings
%
% Examples:
%   % a typical function declaration using the expression system
%   function result = myfunc(myarg,myotherarg,yetanotherarg)
%   if ~exp_beginfun('somesetting'), return; end
%   ...
%   exp_endfun;
%
%
%   % define a filter function using the expression system
%   % (note that, for filters to work with all BCILAB facilities, the varargin arguments should also
%   %  be processed via arg_define)
%   function signal = flt_myfilter(varargin)
%   if ~exp_beginfun('filter'), return; end
%   ...
%   exp_endfun;
%
%
%   % as before, but define that the filter, when applied during online processing, should be passed
%   % a particular list of additional arguments
%   function signal = flt_myfilter(varargin)
%   if ~exp_beginfun('filter'), return; end
%   ...
%   exp_endfun('add_online',{'myinternalarg',10, 'my_reserved_argument','test'})
%
%
%   % like before, but this time replace the entire set of arguments; the assumption here is that
%   % the variable called signal contains the input signal, and that it can be passed to flt_myfilter
%   % by name
%   function signal = flt_myfilter(varargin)
%   if ~exp_beginfun('filter'), return; end
%   ...
%   exp_endfun('set_online',{'signal',signal, 'myparam',10,'myotherparam',200})
%
%
%   % like before, but this time define that the filter, when applied online, should go through an
%   % entirely different function (here assumed to be defined using exp_beginfun itself) with specific
%   % arguments
%   function signal = flt_myfilter(varargin)
%   if ~exp_beginfun('filter'), return; end
%   ...
%   exp_endfun('set_online',flt_myfilter_online('signal',signal, 'specialparam1',10))
%
%
%   % define a dataset editing function
%   function signal = set_myoperation(varargin)
%   if ~exp_beginfun('editing'), return; end
%   ...
%   exp_endfun;
%
%
%   % define a function that operates on expressions symbolically
%   function result = my_operation(varargin)
%   if ~exp_beginfun('symbolic'), return; end
%   ...
%   exp_endfun;
%
%
%   % define a signal processing function that can only be applied in offline processing
%   function signal = set_myoperation(varargin)
%   if ~exp_beginfun('offline'), return; end
%   ...
%   exp_endfun;
%
%
%                                Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                                2010-04-14

% --- obtain & interpret calling context ---

% obtain execution context
ctx.stack = dbstack('-completenames');

% debug print
dp(['in ' ctx.stack(2).name]);

% early check of whether the expression system is disabled
if  ~strcmp(setting,'symbolic') && hlp_resolve('disable_expressions',false,ctx) && ~isequal(varargin,{'delayed_online',true})
    % and exit exp_beginfun, if so
    assignin('caller','exp_internal_context',struct('enabled',0, 'exec_ctx',ctx));
    % the function should evaluate
    should_evaluate = 1;
    return;
end

% if we're being called by exp_eval, the function should be immediately evaluated - otherwise it may
% evaluate in a delayed fashion (i.e., only the expression itself is returned)
should_evaluate = any(strncmp('exp_eval',{ctx.stack(3:min(4,length(ctx.stack))).name},8));

% identify the calling function (i.e., the one using exp_beginfun)
callname = ctx.stack(2).name;
callfile = ctx.stack(2).file;
callfunc = str2func(callname);
[codehash,inargs,outargs] = hlp_fileinfo(callname,callfile);

% --- read the caller's function arguments (and unfold varargin, if any) ---
try
    parts = {};
    for i=1:length(inargs)
        val = evalin('caller',inargs{i});
        if ~strcmp(inargs{i},'varargin')
            val = {val}; end
        parts = [parts val]; %#ok<AGROW>
    end
catch, end

% --- parse the attribute settings ---
if isempty(varargin) && strcmp(setting,'symbolic')
    % fast path: symbolic operations with no settings
    if ~should_evaluate
        % -> delayed evaluation: compose & return a new expression from transformed
        % arguments
        assignin('caller',outargs{1},struct('head',{callfunc},'parts',{parts}));
        % also assign [] to all other output arguments
        for k=2:length(outargs)
            assignin('caller',outargs{k},[]); end
    else
        % -> immediate evaluation: opt out of exp_endfun() processing (nothing to do for
        % symbolic functions)
        assignin('caller','exp_internal_context',struct('enabled',{0}, 'exec_ctx',{ctx}));
    end
    return;
else
    % regular path: every other case

    % define general attributes
    attribs = {'delayed',1,'add_impure',1,'hold',[],'symbolic',0,'set_online',[],'append_online',[],'delayed_online',false,'argsteps',[],'presteps',[],'poststeps',[],'fingerprint_check',[],'fingerprint_create',[],'memoize',[]};

    % set defaults according to the chosen setting
    switch setting
        case 'symbolic'
            defaults = {'add_impure',0, 'symbolic',1, 'hold','all', 'fingerprint_check',0, 'fingerprint_create','passthrough', 'memoize',0};
        case 'default'
            defaults = {'add_impure',0, 'argsteps',{@utl_check_fingerprint}};
        case 'annotate'
            defaults = {'delayed',0};
        otherwise
            defaults = exp_settings(setting);
    end

    % concatenate general attribs, per-setting defaults and user overrides into one list of
    % name-value pairs (NVPs)
    if ~iscellstr(varargin(1:2:end))
        error('The given attributes must be name-value pairs.'); end
    nvps = [attribs defaults varargin];
    % retain only the last assignment for each name
    [s,indices] = sort(nvps(1:2:end));
    indices(strcmp(s((1:end-1)'),s((2:end)'))) = [];
    % and turn them into an options struct
    opts = cell2struct(nvps(2*indices),nvps(2*indices-1),2);
end

% if delayed is false, it enforces immediate evaluation
if ~opts.delayed
    should_evaluate = true; end

if should_evaluate

    % apply per-argument transformation steps (and pass the full options)
    expression = struct('head',{callfunc},'parts',{parts},'codehash',{codehash});
    for step = opts.argsteps
        for p=1:length(parts)
            parts{p} = step{1}(parts{p},opts,ctx,expression); end
    end

    % --- check if the result of evaluating this expression is already in the cache ---

    % purify the expression
    expression = utl_purify_expression(struct('head',{callfunc},'parts',{parts},'codehash',{codehash}));

    % quick check if memoization is disabled
    if ~isequal(opts.memoize,0)
        % do a cache lookup
        [action,result] = utl_memoize_lookup(expression,opts.memoize,ctx);
        % act according to outcome
        if strcmp(action,'return')
            % assign the retrieved data to the output argument(s)
            for k=1:length(result)
                assignin('caller',outargs{k},result{k}); end
            % and make sure that the function does not evaluate further
            should_evaluate = 0;
            return;
        elseif strcmp(action,'memoize')
            % remember to memoize the expression later, under the given id(s)
            memoize_id = result;
        else
            % do not memoize the expression
            memoize_id = [];
        end
    else
        memoize_id = [];
    end

    % --- not in cache: evaluate arguments  ---

    % evaluate the function's arguments (recursively), if they are not held
    if ~isempty(parts) && ~any(strcmp(opts.hold,{'all','first'}));
        parts{1} = exp_eval(parts{1},inf);
        if ~opts.symbolic && ~is_evaluated(parts{1})
            % one of the inputs could not be evaluated (e.g. using an undefined symbol): return the
            % expression
            assignin('caller',outargs{1},expression);
            % also assign [] to all other output arguments
            for k=2:length(outargs)
                assignin('caller',outargs{k},[]); end
            should_evaluate = false;
            return;
        end
    end
    if ~any(strcmp(opts.hold,{'all','rest'}))
        for i=2:length(parts)
            parts{i} = exp_eval(parts{i},inf); %#ok<AGROW>
            if ~opts.symbolic && ~is_evaluated(parts{i})
                % one of the inputs could not be evaluated (e.g. using an undefined symbol): return
                % the expression
                assignin('caller',outargs{1},expression);
                % also assign [] to all other output arguments
                for k=2:length(outargs)
                    assignin('caller',outargs{k},[]); end
                should_evaluate = false;
                return;
            end
        end
    end

    % --- prepare function execution ---

    % begin measuring the evaluation time of the expression ...
    eval_time = tic;
    % ... and measure the size of the inputs (both used to determine the best cache location)
    stats = whos('parts');
    input_size = stats.bytes;

    % create a context struct to keep track of the inputs, outputs, and attributes of the function
    % throughout its evaluation
    context = struct( ...
        'inargs',{inargs}, 'outargs',{outargs}, ...                     % input/output argument names
        'ws_input_pre',{struct()}, 'ws_output_post',{struct()}, ...     % input/output function workspace before/after evaluation of the function's body
        'expression_preeval',{expression}, 'expression_posteval', struct('head',{callfunc},'parts',{parts}), ... % expression arguments (original and evaluated)
        'opts',{opts}, 'eval_time',{eval_time}, 'input_size',{input_size}, 'memoize_id',{memoize_id}, 'exec_ctx',{ctx}, 'enabled',{1});    % misc fields

    % re-derive the function's input & output workspace from the now evaluated parts (folding
    % varargs again)
    for i=1:length(parts)
        if strcmp(inargs{i},'varargin')
            context.ws_input_pre.(inargs{i}) = parts(i:end);
            break;
        else
            context.ws_input_pre.(inargs{i}) = parts{i};
        end
    end

    % execute the prestep(s) on the function workspace
    for step = opts.presteps'
        context = step{1}(context); end

    % store the context object...
    assignin('caller','exp_internal_context',context);

    % assign all input for the function body's execution
    for fn=fieldnames(context.ws_input_pre)'
        assignin('caller',fn{1},context.ws_input_pre.(fn{1})); end

    % (from here on, the function body will execute)
else

    % --- delayed evaluation: return the expression ---

    expression = struct('head',{callfunc},'parts',{parts},'codehash',{codehash});
    assignin('caller',outargs{1},expression);
    % also assign [] to all other output arguments
    for k=2:length(outargs)
        assignin('caller',outargs{k},[]); end

    % (the function body will return immediately, as the should_evaluate output is false)
end


function [states] = tapas_sem_hier_init_states(data, model, inference)
%% Generates the data structure to store the samples.
%
% Input
%
%
% Output
%   data        -- Cell array with nx1 cells, where n = niter
%

% aponteeduardo@gmail.com
% copyright (C) 2016
%

states = cell(inference.niter, 1);

end


function iNewFiles = import_video(iStudy, VideoFiles)
% IMPORT_VIDEO Link video files to the database.
%
% USAGE:  iNewFiles = import_dipoles(iStudy, VideoFiles=[ask])
%
% INPUT:
%    - iStudy       : Index of the study where to import the DipolesFiles
%    - DipolesFiles : Full filename, or cell array of filenames, of video files to import

% @=============================================================================
% 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, 2015-2019

%% ===== PARSE INPUTS =====
if (nargin < 2)
    VideoFiles = [];
elseif ~iscell(VideoFiles)
    VideoFiles = {VideoFiles};
end
% Returned variables
iNewFiles = [];

%% ===== SELECT FILES =====
% If file to load was not defined : open a dialog box to select it
if isempty(VideoFiles)
    % Get default import directory and formats
    LastUsedDirs   = bst_get('LastUsedDirs');
    % Get Dipoles file
    [VideoFiles, FileFormat] = java_getfile('open', ...
            'Import dipoles...', ...   % Window title
            LastUsedDirs.ImportData, ...    % Last used directory
            'multiple', 'files', ...        % Selection mode
            {{'.avi','.mpg','.mpeg','.mp4','.mp2','.mkv','.wmv','.divx','.mov','.asf'}, 'Video files (*.avi;*.mpg;*.mpeg;*.mp4;*.mp2;*.mkv;*.wmv;*.divx;*.mov;.asf)', 'VIDEO'}, []);
    % If no file was selected: exit
    if isempty(VideoFiles)
        return
    % Make sure it's an array of files
    elseif ~iscell(VideoFiles)
        VideoFiles = {VideoFiles};
    end
    % Save default import directory
    LastUsedDirs.ImportData = bst_fileparts(VideoFiles{1});
    bst_set('LastUsedDirs', LastUsedDirs);
end

%% ===== GET RECORDINGS SYNCHRO =====
RecStart = 0;
% Get study structure
sStudy = bst_get('Study', iStudy);
% Look for the first RAW file in the folder
if ~isempty(sStudy.Data)
    iDataRaw = find(strcmpi({sStudy.Data.DataType}, 'raw'),1);
else
    iDataRaw = [];
end
% Read the syncro information from the RAW link
if ~isempty(iDataRaw)
    % Read raw link
    DataMat = in_bst_data(sStudy.Data(iDataRaw).FileName, 'F');
    sFile = DataMat.F;
    % Get the start time of the recordings
    if isfield(sFile, 'header') && isfield(sFile.header, 'FirstTimeStamp') && ~isempty(sFile.header.FirstTimeStamp)
        RecStart = double(sFile.header.FirstTimeStamp) * 1e-6;
    end
end

%% ===== CREATE LINKS =====
% Get protocol folders
ProtocolInfo = bst_get('ProtocolInfo');
% Get output study
sStudy = bst_get('Study', iStudy);
% Loop on each file
for iFile = 1:length(VideoFiles)
    % === SYNCHRO WITH SUBTITLES ===
    VideoStart = [];
    % Get file name
    [fPath, fBase, fExt] = bst_fileparts(VideoFiles{iFile});
    % Try to find an .smi file corresponding to the video and use it for the synchronization
    SmiFile = bst_fullfile(fPath, [fBase, '.smi']);
    if file_exist(SmiFile)
        % Open the file
        fid = fopen(SmiFile, 'r');
        if (fid < 0)
            disp(['BST> Error: Cannot open file "' SmiFile '"']);
            continue;
        end
        % Read the file line by line to get the first syncro tag
        while ~feof(fid)
            % Read one line
            newLine = fgetl(fid);
            if ~ischar(newLine)
                break;
            end
            % Look for line: "<SYNC Start=0><P Class=ENUSCC>XXXXX</SYNC>"
            if (length(newLine) > 21) && strcmpi(newLine(1:14), '<SYNC Start=0>')
                VideoStart = str2num(str_striptag(newLine)) .* 1e-6;
                break;
            end
        end
        % Close the file
        fclose(fid);
    end

    % === CREATE STRUCTURE ===
    % Create new empty videolink structure
    VideoLinkMat = struct();
    VideoLinkMat.LinkTo     = VideoFiles{iFile};
    VideoLinkMat.Comment    = fBase;
    VideoLinkMat.VideoStart = VideoStart - RecStart;
    % Add History field
    VideoLinkMat = bst_history('add', VideoLinkMat, 'import', ['Import from: ' VideoFiles{iFile}]);

    % === SAVE NEW FILE ===
    % Create output filename
    OutputFile = bst_fullfile(ProtocolInfo.STUDIES, bst_fileparts(sStudy.FileName), ['videolink_', file_standardize(fBase), '.mat']);
    OutputFile = file_unique(OutputFile);
    % Save new file in Brainstorm format
    bst_save(OutputFile, VideoLinkMat, 'v7');

    % === UPDATE DATABASE ===
    % Create structure
    sImage = db_template('image');
    sImage.FileName = file_short(OutputFile);
    sImage.Comment  = VideoLinkMat.Comment;
    % Add to study
    iImage = length(sStudy.Image) + 1;
    sStudy.Image(iImage) = sImage;
    iNewFiles = [iNewFiles, iImage];
end

% Save study
bst_set('Study', iStudy, sStudy);
% Update tree
panel_protocols('UpdateNode', 'Study', iStudy);
% Save database
db_save();


function spm_eeg_inv_vbecd_disp(action,varargin)
% Display the dipoles as obtained from VB-ECD
%
% FORMAT spm_eeg_inv_vbecd_disp('Init',D)
% Display the latest VB-ECD solution saved in the .inv{} field of the
% data structure D.
%
% FORMAT spm_eeg_inv_vbecd_disp('Init',D, ind)
% Display the ind^th .inv{} cell element, if it is actually a VB-ECD
% solution.
%__________________________________________________________________________
% Copyright (C) 2008-2015 Wellcome Trust Centre for Neuroimaging

% Christophe Phillips
% $Id: spm_eeg_inv_vbecd_disp.m 6501 2015-07-17 14:32:09Z spm $

% Note:
% unfortunately I cannot see how to ensure that when zooming in the image
% the dipole location stays in place...

global st

Fig     = spm_figure('GetWin','Graphics');
colors  = {'y','b','g','r','c','m'};              % 6 possible colors
marker  = {'o','x','+','*','s','d','v','p','h'};  % 9 possible markers
Ncolors = length(colors);
Nmarker = length(marker);

if nargin == 0, action = 'Init'; end

switch lower(action)

%==========================================================================
case 'init'
%==========================================================================
% FORMAT spm_eeg_inv_vbecd_disp('init',D,ind)
% Initialise the variables with GUI
%--------------------------------------------------------------------------

if nargin<2
    D = spm_eeg_load;
else
    D = varargin{1};
end
if nargin<3
    % find the latest inverse produced with vbecd
    Ninv = length(D.inv);
    lind = [];
    for ii=1:Ninv
        if isfield(D.inv{ii},'method') && strcmp(D.inv{ii}.method,'vbecd')
            lind = [lind ii];
        end
    end
    ind = max(lind);
    if ~ind,
        spm('alert*','No VB-ECD solution found with this data file!',...
                'VB-ECD display')
        return
    end
else
    ind = varargin{3};
end

% Stash dipole(s) information in sdip structure
sdip = D.inv{ind}.inverse;

% if the exit flag is not in the structure, assume everything went ok.
if ~isfield(sdip,'exitflag')
    sdip.exitflag = ones(1,sdip.n_seeds);
end

%try
%    Pimg = spm_vol(D.inv{ind}.mesh.sMRI);
%catch
    Pimg = spm_vol(fullfile(spm('dir'), 'canonical', 'single_subj_T1.nii'));
%end

spm_orthviews('Reset');
spm_orthviews('Image', Pimg, [0.0 0.45 1 0.55]);
spm_orthviews('MaxBB');
spm_orthviews('AddContext')
st.callback = 'spm_image(''shopos'');';
% remove clicking in image
for ii=1:3,
    set(st.vols{1}.ax{ii}.ax,'ButtonDownFcn',';');
end

WS = spm('WinScale');

% Build GUI
%==========================================================================
% Location:
%--------------------------------------------------------------------------
uicontrol(Fig,'Style','Frame','Position',[60 25 200 325].*WS, ...
    'DeleteFcn','spm_image(''reset'');');
uicontrol(Fig,'Style','Frame','Position',[70 250 180 90].*WS);
uicontrol(Fig,'Style','Text', 'Position',[75 320 170 016].*WS, ...
    'String','Current Position');
uicontrol(Fig,'Style','Text', 'Position',[75 295 35 020].*WS,'String','mm:');
uicontrol(Fig,'Style','Text', 'Position',[75 275 35 020].*WS,'String','vx:');
uicontrol(Fig,'Style','Text', 'Position',[75 255 75 020].*WS,'String','Img Intens.:');

st.mp = uicontrol(Fig,'Style','Text', 'Position',[110 295 135 020].*WS,'String','');
st.vp = uicontrol(Fig,'Style','Text', 'Position',[110 275 135 020].*WS,'String','');
st.in = uicontrol(Fig,'Style','Text', 'Position',[150 255  85 020].*WS,'String','');

c = 'if get(gco,''Value'')==1, spm_orthviews(''Xhairs'',''off''), else, spm_orthviews(''Xhairs'',''on''); end;';
uicontrol(Fig,'Style','togglebutton','Position',[95 220 125 20].*WS,...
    'String','Hide Crosshairs','Callback',c,'ToolTipString','show/hide crosshairs');

% Dipoles/seeds selection:
%--------------------------------------------------------------------------
uicontrol(Fig,'Style','Frame','Position',[300 25 180 325].*WS);
sdip.hdl.hcl = uicontrol(Fig,'Style','pushbutton','Position',[310 320 100 20].*WS, ...
        'String','Clear all','CallBack','spm_eeg_inv_vbecd_disp(''ClearAll'')');

sdip.hdl.hseed=zeros(sdip.n_seeds,1);
for ii=1:sdip.n_seeds
    if sdip.exitflag(ii)==1
        sdip.hdl.hseed(ii) = uicontrol(Fig,'Style','togglebutton','String',num2str(ii),...
            'Position',[310+rem(ii-1,8)*20 295-fix((ii-1)/8)*20 20 20].*WS,...
            'CallBack','spm_eeg_inv_vbecd_disp(''ChgSeed'')');
    else
        sdip.hdl.hseed(ii) = uicontrol(Fig,'Style','Text','String',num2str(ii), ...
            'Position',[310+rem(ii-1,8)*20 293-fix((ii-1)/8)*20 20 20].*WS) ;
    end
end

uicontrol(Fig,'Style','text','String','Select dipole # :', ...
        'Position',[310 255-fix((sdip.n_seeds-1)/8)*20 110 20].*WS);

txt_box = cell(sdip.n_dip,1);
for ii=1:sdip.n_dip, txt_box{ii} = num2str(ii); end
txt_box{sdip.n_dip+1} = 'all';
sdip.hdl.hdip = uicontrol(Fig,'Style','popup','String',txt_box, ...
        'Position',[420 258-fix((sdip.n_seeds-1)/8)*20 40 20].*WS, ...
        'Callback','spm_eeg_inv_vbecd_disp(''ChgDip'')');

% Dipoles orientation and strength:
%--------------------------------------------------------------------------
uicontrol(Fig,'Style','Frame','Position',[70 120 180 90].*WS);
uicontrol(Fig,'Style','Text', 'Position',[75 190 170 016].*WS, ...
                'String','Dipole orientation & strength');
uicontrol(Fig,'Style','Text', 'Position',[75 165 65 020].*WS, ...
                'String','x-y-z or.:');
uicontrol(Fig,'Style','Text', 'Position',[75 145 75 020].*WS, ...
                'String','theta-phi or.:');
uicontrol(Fig,'Style','Text', 'Position',[75 125 75 020].*WS, ...
                'String','Dip. intens.:');

sdip.hdl.hor1 = uicontrol(Fig,'Style','Text', 'Position', ...
                            [140 165  105 020].*WS,'String','a');
sdip.hdl.hor2 = uicontrol(Fig,'Style','Text', 'Position', ...
                            [150 145  85 020].*WS,'String','b');
sdip.hdl.int  = uicontrol(Fig,'Style','Text', 'Position', ...
                            [150 125  85 020].*WS,'String','c');

st.vols{1}.sdip = sdip;

% First plot = all the seeds that converged !
l_conv = find(sdip.exitflag==1);
if isempty(l_conv)
    error('No seed converged towards a stable solution, nothing to be displayed !')
else
    spm_eeg_inv_vbecd_disp('DrawDip',l_conv,1)
    set(sdip.hdl.hseed(l_conv),'Value',1); % toggle all buttons
end

%==========================================================================
case 'drawdip'
%==========================================================================
% FORMAT spm_eeg_inv_vbecd_disp('DrawDip',i_seed,i_dip,sdip)
% e.g. spm_eeg_inv_vbecd_disp('DrawDip',1,1,sdip)
% e.g. spm_eeg_inv_vbecd_disp('DrawDip',[1:5],1,sdip)
%--------------------------------------------------------------------------

if nargin < 2
    i_seed = 1;
else
    i_seed = varargin{1};
end
if nargin<3
    i_dip = 1;
else
    i_dip = varargin{2};
end

if nargin<4
    if isfield(st.vols{1},'sdip')
        sdip = st.vols{1}.sdip;
    else
        error('I can''t find sdip structure');
    end
else
    sdip = varargin{3};
    st.vols{1}.sdip = sdip;
end

if any(i_seed>sdip.n_seeds) || i_dip>(sdip.n_dip+1)
    error('Wrong i_seed or i_dip index in spm_eeg_inv_vbecd_disp');
end

% Note if i_dip==(sdip.n_dip+1) all dipoles are displayed simultaneously,
% The 3D cut will then be at the mean location of all sources !!!
if i_dip == (sdip.n_dip+1)
    i_dip = 1:sdip.n_dip;
end

% if seed indexes passed is wrong (no convergence) remove the wrong ones
i_seed(sdip.exitflag(i_seed)~=1) = [];
if isempty(i_seed)
    error('You passed the wrong seed indexes...')
end
if size(i_seed,2)==1, i_seed=i_seed'; end

% Display business
%--------------------------------------------------------------------------
loc_mm = sdip.mniloc{i_seed(1)}(:,i_dip);
if length(i_seed)>1
%     unit = ones(1,sdip.n_dip);
    for ii = i_seed(2:end)
        loc_mm = loc_mm + sdip.mniloc{ii}(:,i_dip);
    end
    loc_mm = loc_mm/length(i_seed);
end
if length(i_dip)>1
    loc_mm = mean(loc_mm,2);
end

% Place the underlying image at right cuts

% GRB FIX- removing the line below July 2013
%spm_orthviews('Reposition',loc_mm);

if length(i_dip)>1
    tabl_seed_dip = [kron(ones(length(i_dip),1),i_seed') ...
                        kron(i_dip',ones(length(i_seed),1))];
else
    tabl_seed_dip = [i_seed' ones(length(i_seed),1)*i_dip];
end

% Scaling, according to all dipoles in the selected seed sets.
% The time displayed is the one corresponding to the maximum EEG power !
Mn_j = -1;
l3 = -2:0;
for ii = 1:length(i_seed)
    for jj = 1:sdip.n_dip
        Mn_j = max([Mn_j sqrt(sum(sdip.jmni{ii}(jj*3+l3,sdip.Mtb).^2))]);
    end
end
st.vols{1}.sdip.tabl_seed_dip = tabl_seed_dip;

% Display all dipoles, the 1st one + the ones close enough.
% Run through the 6 colors and 9 markers to differentiate the dipoles.
% NOTA: 2 dipoles coming from the same set will have same colour/marker
ind = 1 ;
dip_h = zeros(9,size(tabl_seed_dip,1),1);
    % each dipole displayed has 9 handles:
    %   3 per view (2*3): for the line, for the circle & for the error
js_m = zeros(3,1);

% Deal with case of multiple i_seed and i_dip displayed.
% make sure dipole from same i_seed have same colour but different marker.

pi_dip = find(diff(tabl_seed_dip(:,2)));
if isempty(pi_dip)
    % i.e. only one dip displayed per seed, use old fashion
    for ii=1:size(tabl_seed_dip,1)
        if ii>1
            if tabl_seed_dip(ii,1)~=tabl_seed_dip(ii-1,1)
                ind = ind+1;
            end
        end
        ic = mod(ind-1,Ncolors)+1;
        im = fix(ind/Ncolors)+1;

        loc_pl = sdip.mniloc{tabl_seed_dip(ii,1)}(:,tabl_seed_dip(ii,2));
        js = sdip.jmni{tabl_seed_dip(ii,1)}(tabl_seed_dip(ii,2)*3+l3,sdip.Mtb);

        vloc = sdip.cov_loc{tabl_seed_dip(ii,1)}(tabl_seed_dip(ii,2)*3+l3,tabl_seed_dip(ii,2)*3+l3);
        dip_h(:,ii) = add1dip(loc_pl,js/Mn_j*20,vloc, ...
                            marker{im},colors{ic},st.vols{1}.ax,Fig,st.bb);
        js_m = js_m+js;
    end
else
    for ii=1:pi_dip(1)
        if ii>1
            if tabl_seed_dip(ii,1)~=tabl_seed_dip(ii-1,1)
                ind = ind+1;
            end
        end
        ic = mod(ind-1,Ncolors)+1;
        for jj=1:sdip.n_dip
            im = mod(jj-1,Nmarker)+1;

            loc_pl = sdip.mniloc{tabl_seed_dip(ii,1)}(:,jj);
            js = sdip.jmni{tabl_seed_dip(ii,1)}(jj*3+l3,sdip.Mtb);
            vloc = sdip.cov_loc{tabl_seed_dip(ii,1)}(jj*3+l3,jj*3+l3);
            js_m = js_m+js;
            dip_h(:,ii+(jj-1)*pi_dip(1)) = ...
                add1dip(loc_pl,js/Mn_j*20,vloc, ...
                        marker{im},colors{ic},st.vols{1}.ax,Fig,st.bb);
        end
    end
end
st.vols{1}.sdip.ax = dip_h;

% Display dipoles orientation and strength
js_m = js_m/size(tabl_seed_dip,1);
[th,phi,Ijs_m] = cart2sph(js_m(1),js_m(2),js_m(3));
Njs_m = round(js_m'/Ijs_m*100)/100;
Angle = round([th phi]*1800/pi)/10;

set(sdip.hdl.hor1,'String',[num2str(Njs_m(1)),' ',num2str(Njs_m(2)), ...
        ' ',num2str(Njs_m(3))]);
set(sdip.hdl.hor2,'String',[num2str(Angle(1)),'  ',num2str(Angle(2)),' ']);
set(sdip.hdl.int,'String',Ijs_m);

% Change the colour of toggle button of dipoles actually displayed
for ii=tabl_seed_dip(:,1)
    set(sdip.hdl.hseed(ii),'BackgroundColor',[.7 1 .7]);
end

%==========================================================================
case 'clearall'
%==========================================================================
% Clears all dipoles, and reset the toggle buttons
%--------------------------------------------------------------------------

if isfield(st.vols{1},'sdip')
    sdip = st.vols{1}.sdip;
else
    error('I can''t find sdip structure');
end

disp('Clears all dipoles')
spm_eeg_inv_vbecd_disp('ClearDip');
for ii=1:st.vols{1}.sdip.n_seeds
    if sdip.exitflag(ii)==1
        set(st.vols{1}.sdip.hdl.hseed(ii),'Value',0);
    end
end
set(st.vols{1}.sdip.hdl.hdip,'Value',1);

%==========================================================================
case 'chgseed'
%==========================================================================
% Changes the seeds displayed
%--------------------------------------------------------------------------

% disp('Change seed')

sdip = st.vols{1}.sdip;
if isfield(sdip,'tabl_seed_dip')
    prev_seeds = p_seed(sdip.tabl_seed_dip);
else
    prev_seeds = [];
end

l_seed = zeros(sdip.n_seeds,1);
for ii=1:sdip.n_seeds
    if sdip.exitflag(ii)==1
        l_seed(ii) = get(sdip.hdl.hseed(ii),'Value');
    end
end
l_seed = find(l_seed);

% Modify the list of seeds displayed
if isempty(l_seed)
    % Nothing left displayed
    i_seed=[];
elseif isempty(prev_seeds)
    % Just one dipole added, nothing before
    i_seed=l_seed;
elseif length(prev_seeds)>length(l_seed)
    % One seed removed
    i_seed = prev_seeds;
    for ii=1:length(l_seed)
        p = find(prev_seeds==l_seed(ii));
        if ~isempty(p)
            prev_seeds(p) = [];
        end % prev_seeds is left with the index of the one removed
    end
    i_seed(i_seed==prev_seeds) = [];
    % Remove the dipole & change the button colour
    spm_eeg_inv_vbecd_disp('ClearDip',prev_seeds);
    set(sdip.hdl.hseed(prev_seeds),'BackgroundColor',[.7 .7 .7]);
else
    % One dipole added
    i_seed = prev_seeds;
    for ii=1:length(prev_seeds)
        p = find(prev_seeds(ii)==l_seed);
        if ~isempty(p)
            l_seed(p) = [];
        end % l_seed is left with the index of the one added
    end
    i_seed = [i_seed ; l_seed];
end

i_dip = get(sdip.hdl.hdip,'Value');
spm_eeg_inv_vbecd_disp('ClearDip');
if ~isempty(i_seed)
    spm_eeg_inv_vbecd_disp('DrawDip',i_seed,i_dip);
end

%==========================================================================
case 'chgdip'
%==========================================================================
% Changes the dipole index for the first seed displayed
%--------------------------------------------------------------------------

disp('Change dipole')
sdip = st.vols{1}.sdip;

i_dip = get(sdip.hdl.hdip,'Value');
if isfield(sdip,'tabl_seed_dip')
    i_seed = p_seed(sdip.tabl_seed_dip);
else
    i_seed = [];
end

if ~isempty(i_seed)
    spm_eeg_inv_vbecd_disp('ClearDip')
    spm_eeg_inv_vbecd_disp('DrawDip',i_seed,i_dip);
end

%==========================================================================
case 'cleardip'
%==========================================================================
% FORMAT spm_eeg_inv_vbecd_disp('ClearDip',seed_i)
% e.g. spm_eeg_inv_vbecd_disp('ClearDip')
%       clears all displayed dipoles
% e.g. spm_eeg_inv_vbecd_disp('ClearDip',1)
%       clears the first dipole displayed
%--------------------------------------------------------------------------

if nargin>2
    seed_i = varargin{1};
else
    seed_i = 0;
end
if isfield(st.vols{1},'sdip')
        sdip = st.vols{1}.sdip;
    else
        return; % I don't do anything, as I can't find sdip strucure
end

if isfield(sdip,'ax')
    Nax = size(sdip.ax,2);
    else
        return; % I don't do anything, as I can't find axes info
end

if seed_i==0 % removes everything
    for ii=1:Nax
        for jj=1:9
            delete(sdip.ax(jj,ii));
        end
    end
    for ii=sdip.tabl_seed_dip(:,1)
        set(sdip.hdl.hseed(ii),'BackgroundColor',[.7 .7 .7]);
    end
    sdip = rmfield(sdip,'tabl_seed_dip');
    sdip = rmfield(sdip,'ax');
elseif seed_i<=Nax % remove one seed only
    l_seed = find(sdip.tabl_seed_dip(:,1)==seed_i);
    for ii=l_seed
        for jj=1:9
            delete(sdip.ax(jj,ii));
        end
    end
    sdip.ax(:,l_seed) = [];
    sdip.tabl_seed_dip(l_seed,:) = [];
else
    error('Trying to clear unspecified dipole');
end
st.vols{1}.sdip = sdip;

%==========================================================================
case 'redrawdip'
%==========================================================================
% spm_eeg_inv_vbecd_disp('RedrawDip')
% redraw everything, useful when zooming into image
%--------------------------------------------------------------------------

% spm_eeg_inv_vbecd_disp('ClearDip')
% spm_eeg_inv_vbecd_disp('ChgDip')

% disp('Change dipole')
sdip = st.vols{1}.sdip;

i_dip = get(sdip.hdl.hdip,'Value');
if isfield(sdip,'tabl_seed_dip')
    i_seed = p_seed(sdip.tabl_seed_dip);
else
    i_seed = [];
end

if ~isempty(i_seed)
    spm_eeg_inv_vbecd_disp('ClearDip')
    spm_eeg_inv_vbecd_disp('DrawDip',i_seed,i_dip);
end

%==========================================================================
otherwise
%==========================================================================
    warning('Unknown action string');
end
% warning(sw);
return

%==========================================================================
% dh = add1dip(loc,js,vloc,mark,col,ax,Fig,bb)
%==========================================================================
function dh = add1dip(loc,js,vloc,mark,col,ax,Fig,bb)
% Plots the dipoles on the 3 views, with an error ellipse for location
% Then returns the handle to the plots

global st

is = inv(st.Space);
loc = is(1:3,1:3)*loc(:) + is(1:3,4);
% taking into account the zooming/scaling only for the location
% NOT for the dipole's amplitude.
% Amplitude plotting is quite arbitrary anyway and up to some scaling
% defined for better viewing...

loc(1,:) = loc(1,:) - bb(1,1)+1;
loc(2,:) = loc(2,:) - bb(1,2)+1;
loc(3,:) = loc(3,:) - bb(1,3)+1;
% +1 added to be like John's orthview code

% prepare error ellipse
vloc = is(1:3,1:3)*vloc*is(1:3,1:3);
[V,E] = eig(vloc);
VE = V*diag(sqrt(diag(E))); % use std
% VE = V*E;   % or use variance ???

dh = zeros(9,1);
figure(Fig)
% Transverse slice, # 1
%----------------------
set(Fig,'CurrentAxes',ax{1}.ax)
set(ax{1}.ax,'NextPlot','add')
dh(1) = plot(loc(1),loc(2),[mark,col],'LineWidth',1);
dh(2) = plot(loc(1)+[0 js(1)],loc(2)+[0 js(2)],col,'LineWidth',2);
% add error ellipse
[uu,ss,vv] = svd(VE([1 2],:));
[phi] = cart2pol(uu(1,1),uu(2,1));
e = diag(ss);
t = (-1:.02:1)*pi;
x = e(1)*cos(t)*cos(phi)-e(2)*sin(t)*sin(phi)+loc(1);
y = e(2)*sin(t)*cos(phi)+e(1)*cos(t)*sin(phi)+loc(2);
dh(3) = plot(x,y,[':',col],'LineWidth',.5);
set(ax{1}.ax,'NextPlot','replace')

% Coronal slice, # 2
%----------------------
set(Fig,'CurrentAxes',ax{2}.ax)
set(ax{2}.ax,'NextPlot','add')
dh(4) = plot(loc(1),loc(3),[mark,col],'LineWidth',1);
dh(5) = plot(loc(1)+[0 js(1)],loc(3)+[0 js(3)],col,'LineWidth',2);
% add error ellipse
[uu,ss,vv] = svd(VE([1 3],:));
[phi] = cart2pol(uu(1,1),uu(2,1));
e = diag(ss);
t = (-1:.02:1)*pi;
x = e(1)*cos(t)*cos(phi)-e(2)*sin(t)*sin(phi)+loc(1);
y = e(2)*sin(t)*cos(phi)+e(1)*cos(t)*sin(phi)+loc(3);
dh(6) = plot(x,y,[':',col],'LineWidth',.5);
set(ax{2}.ax,'NextPlot','replace')

% Sagital slice, # 3
%----------------------
set(Fig,'CurrentAxes',ax{3}.ax)
set(ax{3}.ax,'NextPlot','add')
% dh(5) = plot(dim(2)-loc(2),loc(3),[mark,col],'LineWidth',2);
% dh(6) = plot(dim(2)-loc(2)+[0 -js(2)],loc(3)+[0 js(3)],col,'LineWidth',2);
dh(7) = plot(bb(2,2)-bb(1,2)-loc(2),loc(3),[mark,col],'LineWidth',1);
dh(8) = plot(bb(2,2)-bb(1,2)-loc(2)+[0 -js(2)],loc(3)+[0 js(3)],col,'LineWidth',2);
% add error ellipse
[uu,ss,vv] = svd(VE([2 3],:));
[phi] = cart2pol(uu(1,1),uu(2,1));
e = diag(ss);
t = (-1:.02:1)*pi;
x = -(e(1)*cos(t)*cos(phi)-e(2)*sin(t)*sin(phi))+bb(2,2)-bb(1,2)-loc(2);
y = e(2)*sin(t)*cos(phi)+e(1)*cos(t)*sin(phi)+loc(3);
dh(9) = plot(x,y,[':',col],'LineWidth',.5);
set(ax{3}.ax,'NextPlot','replace')

return

%==========================================================================
% pr_seed = p_seed(tabl_seed_dip)
%==========================================================================
function pr_seed = p_seed(tabl_seed_dip)
% Gets the list of seeds used in the previous display

ls = sort(tabl_seed_dip(:,1));
if length(ls)==1
    pr_seed = ls;
else
    pr_seed = ls([find(diff(ls)) ; length(ls)]);
end

%

% OLD STUFF

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Use it with arguments or not:
% - spm_eeg_inv_vbecd_disp('Init')
%        The routine asks for the dipoles file and image to display
% - spm_eeg_inv_vbecd_disp('Init',sdip)
%        The routine will use the avg152T1 canonical image
% - spm_eeg_inv_vbecd_disp('Init',sdip,P)
%        The routines dispays the dipoles on image P.
%
% If multiple seeds have been used, you can select the seeds to display
% by pressing their index.
% Given that the sources could have different locations, the slices
% displayed will be the 3D view at the *average* or *mean* locations of
% selected sources.
% If more than 1 dipole was fitted at a time, then selection of source 1
% to N is possible through the pull-down selector.
%
% The location of the source/cut is displayed in mm and voxel, as well as
% the underlying image intensity at that location.
% The cross hair position can be hidden by clicking on its button.
%
% Nota_1: If the cross hair is manually moved by clicking in the image or
%       changing its coordinates, the dipole displayed will NOT be at
%       the right displayed location. That's something that needs to be improved...
%
% Nota_2: Some seeds may have not converged within the limits fixed,
%       these dipoles are not displayed...
%
% Fields needed in sdip structure to plot on an image:
%       + n_seeds: nr of seeds set used, i.e. nr of solutions calculated
%       + n_dip: nr of fitted dipoles on the EEG time series
%       + loc: location of fitted dipoles, cell{1,n_seeds}(3 x n_dip)
%               remember that loc is fixed over the time window.
%       + j: sources amplitude over the time window,
%            cell{1,n_seeds}(3*n_dip x Ntimebins)
%       + Mtb: index of maximum power in EEG time series used

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% % First point to consider
% loc_mm = sdip.loc{i_seed(1)}(:,i_dip);
%
% % PLace the underlying image at right cuts
% spm_orthviews('Reposition',loc_mm);
% % spm_orthviews('Reposition',loc_vx);
% % spm_orthviews('Xhairs','off')
%
% % if i_seed = set, Are there other dipoles close enough ?
% tabl_seed_dip=[i_seed(1) i_dip]; % table summarising which set & dip to use.
% if length(i_seed)>1
%   unit = ones(1,sdip.n_dip);
%   for ii = i_seed(2:end)'
%         d2 = sqrt(sum((sdip.loc{ii}-loc_mm*unit).^2));
%         l_cl = find(d2<=lim_cl);
%         if ~isempty(l_cl)
%             for jj=l_cl
%                 tabl_seed_dip = [tabl_seed_dip ; [ii jj]];
%             end
%         end
%   end
% end
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% get(sdip.hdl.hseed(1),'Value')
% for ii=1:sdip.n_seeds, delete(hseed(ii)); end
% h1 = uicontrol(Fig,'Style','togglebutton','Position',[600 25 10 10].*WS)
% h2 = uicontrol(Fig,'Style','togglebutton','Position',[620 100 20 20].*WS,'String','1')
% h2 = uicontrol(Fig,'Style','checkbox','Position',[600 100 10 10].*WS)
% h3 = uicontrol(Fig,'Style','radiobutton','Position',[600 150 20 20].*WS)
% h4 = uicontrol(Fig,'Style','radiobutton','Position',[700 150 20 20].*WS)
% delete(h2),delete(h3),delete(h4),
% delete(hdip)


classdef(Abstract) AbstractEventAction < matlab.mixin.SetGet & matlab.mixin.Heterogeneous
    %AbstractEventAction Summary of this class goes here
    %   Detailed explanation goes here

    properties(Abstract=false)
        id(1,1) double
        event LaunchVehicleEvent
    end

    methods
        newStateLogEntry = executeAction(obj, stateLogEntry)

        initAction(obj, initialStateLogEntry)

        name = getName(obj)

        tf = usesStage(obj, stage)

        tf = usesEngine(obj, engine)

        tf = usesTank(obj, tank)

        tf = usesEngineToTankConn(obj, engineToTank)

        tf = usesStopwatch(obj, stopwatch)

        tf = usesExtremum(obj, extremum)

        tf = usesTankToTankConn(obj, tankToTank)

        function tf = usesCalculusCalc(obj, calculusCalc)
            tf = false;
        end

        function tf = usesEvent(obj, event)
            tf = false;
        end

        function tf = usesPwrSink(obj, powerSink)
            tf = false;
        end

        function tf = usesPwrSrc(obj, powerSrc)
            tf = false;
        end

        function tf = usesPwrStorage(obj, powerStorage)
            tf = false;
        end

        function tf = usesSensor(obj, sensor)
            tf = false;
        end

        [tf,vars] = hasActiveOptimVar(obj)
    end

    methods(Sealed)
        function tf = eq(A,B)
            tf = [A.id] == [B.id];
        end
    end

    methods(Static)
        addActionTf = openEditActionUI(action, lv);
    end
end


data_filename = ['generated_scenes.mat'];

%% params to be set
data_folder = ['..', filesep, '0-data'];
input_folder = [data_folder, filesep, '4-pydata']
labelset_folder = [data_folder, filesep, 'labelsets'];
output_folder = [data_folder, filesep, '5-generated_scenes'];
output_obj_folder = [output_folder, filesep, 'recon'];
output_img_folder = [output_folder, filesep, 'images'];
objfolder_path = [data_folder, filesep, 'SUNCG', filesep, 'object'];
labelset_filename = [labelset_folder,filesep,'bedroomlabelset.mat'];
objperlabel_filename = [data_folder,filesep,'object_per_label',filesep,'bedroom_objset_per_label.mat'];

addpath(genpath('..\3-datapreparation'));

if ~exist(output_folder,'dir')
   mkdir(output_folder);
end
if ~exist(output_obj_folder, 'dir')
	mkdir(output_obj_folder);
end
if ~exist(output_img_folder, 'dir')
	mkdir(output_img_folder);
end


function save_model_path = rfcn_train(conf, imdb_train, roidb_train, varargin)
% save_model_path = rfcn_train(conf, imdb_train, roidb_train, varargin)
% --------------------------------------------------------
% R-FCN implementation
% Modified from MATLAB Faster R-CNN (https://github.com/shaoqingren/faster_rcnn)
% Copyright (c) 2016, Jifeng Dai
% Licensed under The MIT License [see LICENSE for details]
% --------------------------------------------------------

%% inputs
    ip = inputParser;
    ip.addRequired('conf',                              @isstruct);
    ip.addRequired('imdb_train',                        @iscell);
    ip.addRequired('roidb_train',                       @iscell);
    ip.addParamValue('do_val',          false,          @isscalar);
    ip.addParamValue('imdb_val',        struct(),       @isstruct);
    ip.addParamValue('roidb_val',       struct(),       @isstruct);
    ip.addParamValue('val_iters',       500,            @isscalar);
    ip.addParamValue('val_interval',    5000,           @isscalar);
    ip.addParamValue('snapshot_interval',...
                                        10000,          @isscalar);
    ip.addParamValue('solver_def_file', fullfile(pwd, 'models', 'rfcn_prototxts', 'ResNet-50L_res3a', 'solver_80k120k_lr1_3.prototxt'), ...
                                                        @isstr);
    ip.addParamValue('net_file',        fullfile(pwd, 'models', 'pre_trained_models', 'ResNet-50L', 'ResNet-50-model.caffemodel'), ...
                                                        @isstr);
    ip.addParamValue('cache_name',      'ResNet-50L_res3a', ...
                                                        @isstr);
    ip.addParamValue('caffe_version',   'Unkonwn',      @isstr);

    ip.parse(conf, imdb_train, roidb_train, varargin{:});
    opts = ip.Results;

%% try to find trained model
    imdbs_name = cell2mat(cellfun(@(x) x.name, imdb_train, 'UniformOutput', false));
    cache_dir = fullfile(pwd, 'output', 'rfcn_cachedir', opts.cache_name, imdbs_name);
    save_model_path = fullfile(cache_dir, 'final');
    if exist(save_model_path, 'file')
        return;
    end

%% init
    % set random seed
    prev_rng = seed_rand(conf.rng_seed);
    caffe.set_random_seed(conf.rng_seed);

    % init caffe solver
    mkdir_if_missing(cache_dir);
    caffe_log_file_base = fullfile(cache_dir, 'caffe_log');
    caffe.init_log(caffe_log_file_base);
    caffe_solver = caffe.Solver(opts.solver_def_file);
    caffe_solver.net.copy_from(opts.net_file);

    % init log
    timestamp = datestr(datevec(now()), 'yyyymmdd_HHMMSS');
    mkdir_if_missing(fullfile(cache_dir, 'log'));
    log_file = fullfile(cache_dir, 'log', ['train_', timestamp, '.txt']);
    diary(log_file);

    % set gpu/cpu
    if conf.use_gpu
        caffe.set_mode_gpu();
    else
        caffe.set_mode_cpu();
    end

    disp('conf:');
    disp(conf);
    disp('opts:');
    disp(opts);

%% making tran/val data
    fprintf('Preparing training data...');
    [image_roidb_train, bbox_means, bbox_stds] = rfcn_prepare_image_roidb(conf, opts.imdb_train, opts.roidb_train);
    fprintf('Done.\n');

    if opts.do_val
        fprintf('Preparing validation data...');
        [image_roidb_val] = rfcn_prepare_image_roidb(conf, opts.imdb_val, opts.roidb_val, bbox_means, bbox_stds);
        fprintf('Done.\n');

        % fix validation data
        shuffled_inds_val = generate_random_minibatch([], image_roidb_val, conf.ims_per_batch);
        shuffled_inds_val = shuffled_inds_val(randperm(length(shuffled_inds_val), opts.val_iters));
    end

%% training
    shuffled_inds = [];
    train_results = [];
    val_results = [];
    iter_ = caffe_solver.iter();
    max_iter = caffe_solver.max_iter();

    p = new_parpool(1);
    parfor i=1:1
        seed_rand(conf.rng_seed);
    end
    [shuffled_inds, sub_db_inds] = generate_random_minibatch(shuffled_inds, image_roidb_train, conf.ims_per_batch);
    parHandle = parfeval(p, @rfcn_get_minibatch, 1, conf, image_roidb_train(sub_db_inds));
    tic
    while (iter_ < max_iter)
        caffe_solver.net.set_phase('train');

        % generate minibatch training data
        % gather date
        [~, net_inputs] = fetchNext(parHandle);

        % generate minibatch training data
        % generate data asynchronously
        [shuffled_inds, sub_db_inds] = generate_random_minibatch(shuffled_inds, image_roidb_train, conf.ims_per_batch);
        parHandle = parfeval(p, @rfcn_get_minibatch, 1, conf, image_roidb_train(sub_db_inds));

        caffe_solver.net.reshape_as_input(net_inputs);

        % one iter SGD update
        caffe_solver.net.set_input_data(net_inputs);
        caffe_solver.step(1);

        rst = caffe_solver.net.get_output();
        train_results = parse_rst(train_results, rst);

        % do valdiation per val_interval iterations
        if ~mod(iter_, opts.val_interval)
            if opts.do_val
                caffe_solver.net.set_phase('test');
                for i = 1:length(shuffled_inds_val)
                    sub_db_inds = shuffled_inds_val{i};
                    net_inputs = rfcn_get_minibatch(conf, image_roidb_val(sub_db_inds));
                    caffe_solver.net.reshape_as_input(net_inputs);

                    caffe_solver.net.forward(net_inputs);

                    rst = caffe_solver.net.get_output();
                    val_results = parse_rst(val_results, rst);
                end
            end

            show_state(iter_, train_results, val_results);
            toc;tic;
            train_results = [];
            val_results = [];
            diary; diary; % flush diary
        end

        % snapshot
        if ~mod(iter_, opts.snapshot_interval)
            snapshot(caffe_solver, bbox_means, bbox_stds, cache_dir, sprintf('iter_%d', iter_));
        end

        iter_ = caffe_solver.iter();
    end

    % final snapshot
    snapshot(caffe_solver, bbox_means, bbox_stds, cache_dir, sprintf('iter_%d', iter_));
    save_model_path = snapshot(caffe_solver, bbox_means, bbox_stds, cache_dir, 'final');

    diary off;
    caffe.reset_all();
    rng(prev_rng);
end

function [shuffled_inds, sub_inds] = generate_random_minibatch(shuffled_inds, image_roidb_train, ims_per_batch)

    % shuffle training data per batch
    if isempty(shuffled_inds)
        % make sure each minibatch, only has horizontal images or vertical
        % images, to save gpu memory

        hori_image_inds = arrayfun(@(x) x.im_size(2) >= x.im_size(1), image_roidb_train, 'UniformOutput', true);
        vert_image_inds = ~hori_image_inds;
        hori_image_inds = find(hori_image_inds);
        vert_image_inds = find(vert_image_inds);

        % random perm
        lim = floor(length(hori_image_inds) / ims_per_batch) * ims_per_batch;
        hori_image_inds = hori_image_inds(randperm(length(hori_image_inds), lim));
        lim = floor(length(vert_image_inds) / ims_per_batch) * ims_per_batch;
        vert_image_inds = vert_image_inds(randperm(length(vert_image_inds), lim));

        % combine sample for each ims_per_batch
        hori_image_inds = reshape(hori_image_inds, ims_per_batch, []);
        vert_image_inds = reshape(vert_image_inds, ims_per_batch, []);

        shuffled_inds = [hori_image_inds, vert_image_inds];
        shuffled_inds = shuffled_inds(:, randperm(size(shuffled_inds, 2)));

        shuffled_inds = num2cell(shuffled_inds, 1);
    end

    if nargout > 1
        % generate minibatch training data
        sub_inds = shuffled_inds{1};
        assert(length(sub_inds) == ims_per_batch);
        shuffled_inds(1) = [];
    end
end

function model_path = snapshot(caffe_solver, bbox_means, bbox_stds, cache_dir, file_name)
    bbox_pred_layer_name = 'rfcn_bbox';
    weights = caffe_solver.net.params(bbox_pred_layer_name, 1).get_data();
    biase = caffe_solver.net.params(bbox_pred_layer_name, 2).get_data();
    weights_back = weights;
    biase_back = biase;

    rep_time = size(weights, 4)/length(bbox_means(:));

    bbox_stds_flatten = bbox_stds';
    bbox_stds_flatten = bbox_stds_flatten(:);
    bbox_stds_flatten = repmat(bbox_stds_flatten, [1,rep_time])';
    bbox_stds_flatten = bbox_stds_flatten(:);
    bbox_stds_flatten = permute(bbox_stds_flatten, [4,3,2,1]);

    bbox_means_flatten = bbox_means';
    bbox_means_flatten = bbox_means_flatten(:);
    bbox_means_flatten = repmat(bbox_means_flatten, [1,rep_time])';
    bbox_means_flatten = bbox_means_flatten(:);
    bbox_means_flatten = permute(bbox_means_flatten, [4,3,2,1]);

    % merge bbox_means, bbox_stds into the model
    weights = bsxfun(@times, weights, bbox_stds_flatten); % weights = weights * stds;
    biase = biase .* bbox_stds_flatten(:) + bbox_means_flatten(:); % bias = bias * stds + means;

    caffe_solver.net.set_params_data(bbox_pred_layer_name, 1, weights);
    caffe_solver.net.set_params_data(bbox_pred_layer_name, 2, biase);

    model_path = fullfile(cache_dir, file_name);
    caffe_solver.net.save(model_path);
    fprintf('Saved as %s\n', model_path);

    % restore net to original state
    caffe_solver.net.set_params_data(bbox_pred_layer_name, 1, weights_back);
    caffe_solver.net.set_params_data(bbox_pred_layer_name, 2, biase_back);
end

function show_state(iter, train_results, val_results)
    fprintf('\n------------------------- Iteration %d -------------------------\n', iter);
    fprintf('Training : accuracy %.3g, loss (cls %.3g, reg %.3g)\n', ...
        mean(train_results.accuarcy.data), ...
        mean(train_results.loss_cls.data), ...
        mean(train_results.loss_bbox.data));
    if exist('val_results', 'var') && ~isempty(val_results)
        fprintf('Testing  : accuracy %.3g, loss (cls %.3g, reg %.3g)\n', ...
            mean(val_results.accuarcy.data), ...
            mean(val_results.loss_cls.data), ...
            mean(val_results.loss_bbox.data));
    end
end