% STD_CHECKSET - check STUDY set consistency
%
% Usage: >> [STUDY, ALLEEG] = std_checkset(STUDY, ALLEEG);
%
% Input:
%   STUDY      - EEGLAB STUDY set
%   ALLEEG     - vector of EEG datasets included in the STUDY structure
%
% Output:
%   STUDY      - a new STUDY set containing some or all of the datasets in ALLEEG,
%                plus additional information from the optional inputs above.
%   ALLEEG     - an EEGLAB vector of EEG sets included in the STUDY structure
%
% Authors:  Arnaud Delorme & Hilit Serby, SCCN, INC, UCSD, November 2005

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

function [STUDY, ALLEEG, command] = std_checkset(STUDY, ALLEEG, options)

if nargin < 2
    help std_checkset;
    return;
end
command  = '';

if isempty(STUDY), return; end
studywasempty = 0;

modif = 0;
if ~isfield(STUDY, 'name'),  STUDY.name  = ''; modif = 1; end
if ~isfield(STUDY, 'task'),  STUDY.task  = ''; modif = 1; end
if ~isfield(STUDY, 'notes'), STUDY.notes = ''; modif = 1; end
if ~isfield(STUDY, 'filename'),  STUDY.filename  = ''; modif = 1; end
if ~isfield(STUDY, 'filepath'),  STUDY.filepath  = ''; modif = 1; end
if ~isfield(STUDY, 'history'),   STUDY.history   = ''; modif = 1; end
if ~isfield(STUDY, 'subject'),   STUDY.subject   = {}; modif = 1; end
if ~isfield(STUDY, 'group'),     STUDY.group     = {}; modif = 1; end
if ~isfield(STUDY, 'session'),   STUDY.session   = {}; modif = 1; end
if ~isfield(STUDY, 'run'),       STUDY.run       = {}; modif = 1; end
if ~isfield(STUDY, 'condition'), STUDY.condition = {}; modif = 1; end
if ~isfield(STUDY, 'etc'),       STUDY.etc       = []; modif = 1; end
if ~isfield(STUDY, 'cache'),     STUDY.cache     = []; modif = 1; end
if ~isfield(STUDY, 'etc.warnmemory'), STUDY.etc.warnmemory = 1; modif = 1; end
if ~isfield(STUDY, 'preclust'),  STUDY.preclust  = []; modif = 1; end
if ~isfield(STUDY, 'datasetinfo'), STUDY.datasetinfo = []; modif = 1; end
if ~isfield(STUDY.etc, 'version'), STUDY.etc.version = []; modif = 1; end
if ~isfield(STUDY.preclust, 'erpclusttimes' ),  STUDY.preclust.erpclusttimes = []; modif = 1; end
if ~isfield(STUDY.preclust, 'specclustfreqs' ), STUDY.preclust.specclustfreqs = []; modif = 1; end
if ~isfield(STUDY.preclust, 'erspclustfreqs' ), STUDY.preclust.erspclustfreqs = []; modif = 1; end
if ~isfield(STUDY.preclust, 'erspclusttimes' ), STUDY.preclust.erspclusttimes = []; modif = 1; end
if ~isfield(STUDY.datasetinfo, 'comps') && ~isempty(STUDY.datasetinfo), STUDY.datasetinfo(1).comps = []; modif = 1; end
if ~isfield(STUDY.datasetinfo, 'index') && ~isempty(STUDY.datasetinfo), STUDY.datasetinfo(1).index = []; modif = 1; end
if ~isfield(STUDY.datasetinfo, 'run') && ~isempty(STUDY.datasetinfo), STUDY.datasetinfo(1).run = []; modif = 1; end

% all summary fields
% ------------------
try, subject = unique_bc({ STUDY.datasetinfo.subject });
catch,
    subject = '';
    disp('Important warning: some datasets do not have subject codes; some functions may crash!');
end
try, group = unique_bc({ STUDY.datasetinfo.group });
catch,
    group = {};
    % disp('Important warning: some datasets do not have group codes; some functions may crash!');
end
try, condition = unique_bc({ STUDY.datasetinfo.condition });
catch,
    condition = {};
    disp('Important warning: some datasets do not have condition codes; some functions may crash!');
end
try, session = unique_bc([STUDY.datasetinfo.session]);
catch,
    session = '';
    % disp('Important warning: some datasets do not have session numbers; some functions may crash!');
end
try, run = unique_bc([STUDY.datasetinfo.run]);
catch,
    run = '';
    % disp('Important warning: some datasets do not have session numbers; some functions may crash!');
end
if ~isequal(STUDY.subject,   subject  ), STUDY.subject   = subject;   modif = 1; end
if ~isequal(STUDY.group,     group    ), STUDY.group     = group;     modif = 1; end
if ~isequal(STUDY.condition, condition), STUDY.condition = condition; modif = 1; end
if ~isequal(STUDY.session,   session  ), STUDY.session   = session;   modif = 1; end
if ~isequal(STUDY.run,       run      ), STUDY.run       = run;       modif = 1; end

% check dataset info consistency
% ------------------------------
for k = 1:length(STUDY.datasetinfo)
    if ~strcmpi(STUDY.datasetinfo(k).filename, ALLEEG(k).filename)
        STUDY.datasetinfo(k).filename = ALLEEG(k).filename; modif = 1;
        fprintf('Warning: file name has changed for dataset %d and the study has been updated\n', k);
        fprintf('         to discard this change in the study, reload it from disk\n');
    end
end

% recompute setind array (setind is deprecated but we keep it anyway)
% -------------------------------------------------------------------
setind  = [];
sameica = std_findsameica(ALLEEG);
for index = 1:length(sameica)
    setind(length(sameica{index}):-1:1,index) = sameica{index}';
end
setind(find(setind == 0)) = NaN;
if any(isnan(setind))
    warndlg('Warning: non-uniform set of dataset, some function might not work');
end

% check that dipfit is present in all datasets
% --------------------------------------------
for cc = 1:length(sameica)
    idat = [];
    for tmpi = 1:length(sameica{cc})
        if isfield(ALLEEG(sameica{cc}(tmpi)).dipfit, 'model')
            idat = sameica{cc}(tmpi);
        end
    end
    if ~isempty(idat)
        for tmpi = 1:length(sameica{cc})
            if ~isfield(ALLEEG(sameica{cc}(tmpi)).dipfit, 'model')
                ALLEEG(sameica{cc}(tmpi)).dipfit = ALLEEG(idat).dipfit;
                ALLEEG(sameica{cc}(tmpi)).saved  = 'no';
                fprintf('Warning: no ICA dipoles for dataset %d, using dipoles from dataset %d (same ICA)\n', sameica{cc}(tmpi), idat);
            end
        end
    end
end

% put in fake channels if channel labels are missing
% --------------------------------------------------
chanlabels = { ALLEEG.chanlocs };
if any(cellfun(@isempty, chanlabels))
    if any(~cellfun(@isempty, chanlabels))
        disp('********************************************************************');
        disp(' IMPORTANT WARNING: SOME DATASETS DO NOT HAVE CHANNEL LABELS AND ');
        disp(' SOME OTHERs HAVE CHANNEL LABELS. GENERATING CHANNEL LABELS FOR ');
        disp(' THE FORMER DATASETS (THIS SHOULD PROBABLY BE FIXED BY THE USER).');
        disp('********************************************************************');
    end
    disp('Generating channel labels for all datasets...');
    for currentind = 1:length(ALLEEG)
        for ind = 1:ALLEEG(currentind).nbchan
            ALLEEG(currentind).chanlocs(ind).labels = int2str(ind);
        end
    end
    ALLEEG(currentind).saved = 'no';
end

if length( unique( [ ALLEEG.srate ] )) > 1
    disp('********************************************************************');
    disp(' IMPORTANT WARNING: SOME DATASETS DO NOT HAVE THE SAME SAMPLING ');
    disp(' RATE AND THIS WILL MAKE MOST OF THE STUDY FUNCTIONS CRASH. THIS');
    disp(' SHOULD PROBABLY BE FIXED BY THE USER.');
    disp('********************************************************************');
end

% create STUDY design if it is not present
% ----------------------------------------
rebuild_design = 0;
if ~isfield(STUDY, 'cluster'), STUDY.cluster = []; modif = 1; end
if ~isfield(STUDY, 'changrp'), STUDY.changrp = []; modif = 1; end
if ~studywasempty
    if isfield(STUDY.datasetinfo, 'trialinfo')
        alltrialinfo = { STUDY.datasetinfo.trialinfo };
        if any(cellfun(@isempty, alltrialinfo)) && any(~cellfun(@isempty, alltrialinfo))
            disp('Rebuilding trial information structure for STUDY');
            STUDY  = std_maketrialinfo(STUDY, ALLEEG); % some dataset do not have trialinfo and
            % some other have it, remake it for everybody
        end
    elseif ALLEEG(1).trials > 1
        disp('Rebuilding trial information structure for STUDY');
        STUDY  = std_maketrialinfo(STUDY, ALLEEG); % some dataset do not have trialinfo and
    end
    if ~isfield(STUDY, 'design') || isempty(STUDY.design) || ~isfield(STUDY.design, 'name')
        STUDY  = std_maketrialinfo(STUDY, ALLEEG);
        STUDY  = std_makedesign(STUDY, ALLEEG);
        STUDY  = std_selectdesign(STUDY, ALLEEG,1);
        rebuild_design = 0;
    else
        if isfield(STUDY.design, 'indvar1')
            STUDY  = std_convertdesign(STUDY, ALLEEG);
        end

        % check independent variable
        setFields   = fieldnames(STUDY.datasetinfo);
        if isempty(ALLEEG(1).event)
             trialFields = [];
        else trialFields = fieldnames(ALLEEG(1).event);
        end
        rmInd       = [];
        for indDes = 1:length(STUDY.design)
            for indVar = 1:length(STUDY.design(indDes).variable)
                if isempty(strmatch( STUDY.design(indDes).variable(indVar).label, setFields)) && ...
                    ~isempty(trialFields) && ~isempty(STUDY.design(indDes).variable(indVar).label) && ...
                        isempty(strmatch( STUDY.design(indDes).variable(indVar).label, trialFields))
                        % Missing independent variable

                        if isempty(rmInd)
                            textVal1 = sprintf('The variable ''%s'' defined in the STUDY design is not found in ', STUDY.design(indDes).variable(indVar).label);
                            textVal2 = 'some of the EEG datasets. Some STUDY features will not be functional.';
                            if nargin > 2 && strcmpi(options, 'popup')
                                textVal = [ textVal1 10 textVal2 10 10 'Do you want to remove all designs with missing variables?' ];
                                res = questdlg2(  textVal, 'Issue with studies', 'No', 'Yes', 'Yes');
                                if strcmpi(res, 'Yes')
                                    rmInd = [ rmInd indDes ];
                                end
                            else
                                disp([textVal1 textVal2]);
                            end
                        else
                            rmInd = [ rmInd indDes ];
                        end
                end
            end
        end
        if ~isempty(rmInd)
            STUDY.design(rmInd) = [];
        end

        % convert combined independent variable values
        % between dash to cell array of strings
        % -------------------------------------
        for inddes = 1:length(STUDY.design)
            rmVar = [];
            for indvar = 1:length(STUDY.design(inddes).variable)

                % add pairing info in case it is missing
                if isempty(STUDY.design(inddes).variable(indvar).label)
                    rmVar = [ rmVar indvar ];
                end
                if ~isfield(STUDY.design(inddes).variable, 'pairing') || isempty(STUDY.design(inddes).variable(indvar).pairing)
                    if strcmpi(STUDY.design(inddes).variable(indvar).label, 'group')
                        STUDY.design(inddes).variable(indvar).pairing = 'off';
                    else
                        STUDY.design(inddes).variable(indvar).pairing = 'on';
                    end
                end

                for indval = 1:length(STUDY.design(inddes).variable(indvar).value)
                    STUDY.design(inddes).variable(indvar).value{indval} = convertindvarval(STUDY.design(inddes).variable(indvar).value{indval});
                end
            end
            STUDY.design(inddes).variable(rmVar) = [];
            for indinclude = 1:length(STUDY.design(inddes).include)
                if iscell(STUDY.design(inddes).include{indinclude})
                    for indval = 1:length(STUDY.design(inddes).include{indinclude})
                        STUDY.design(inddes).include{indinclude}{indval} = convertindvarval(STUDY.design(inddes).include{indinclude}{indval});
                    end
                end
            end
        end
    end

    if rebuild_design % in case datasets have been added or removed
        STUDY = std_rebuilddesign(STUDY, ALLEEG);
    end

    % scan design to fix old pairing format
    % ------------------------------------
    for design = 1:length(STUDY.design)
        for var = 1:length(STUDY.design(design).variable)
            if ischar(STUDY.design(design).variable(var).pairing)
                if strcmpi(STUDY.design(design).variable(var).pairing, 'paired')
                    STUDY.design(design).variable(var).pairing = 'on';
                elseif strcmpi(STUDY.design(design).variable(var).pairing, 'unpaired')
                    STUDY.design(design).variable(var).pairing = 'off';
                end
            end
        end
    end

    % add filepath field if absent
    for ind = 1:length(STUDY.design)
        if ~isfield(STUDY.design, 'filepath') || (isnumeric(STUDY.design(ind).filepath) && isempty(STUDY.design(ind).filepath))
            STUDY.design(ind).filepath = '';
            STUDY.saved = 'no';
            modif = 1;
        end
    end

    % check for level field and add it if it not present
    if length(STUDY.design) > 0 && isfield(STUDY.design, 'variable')
        if ~isfield(STUDY.design(1).variable, 'level')
            STUDY = std_addvarlevel(STUDY);
        end
    end

    % check that ICA is present and if it is update STUDY.datasetinfo
    recreateclusters = 0;
    for index = 1:length(STUDY.datasetinfo)
        if max(STUDY.datasetinfo(index).comps) > size(ALLEEG(index).icaweights,1)
            STUDY.datasetinfo(index).comps = [1:size(ALLEEG(index).icaweights,1)];
            recreateclusters = 1;
            modif = 1;
        end
    end
    if recreateclusters
        fprintf('Recreating empty clusters because ICA has changed for some datasets\n');
        STUDY.cluster = [];
    end

    % make channel groups
    % -------------------
    STUDYTMP = std_changroup(STUDY, ALLEEG);
    if length(STUDYTMP.changrp) ~= length(STUDY.changrp)
        STUDY.changrp = STUDYTMP.changrp;
        modif = 1;
    end
    clear STUDYTMP;
end

% check cluster array
% -------------------
if isempty(STUDY.changrp) && isempty(STUDY.cluster)
    rebuild_design = 1;
end
if isfield(STUDY.cluster, 'sets'),
    if max(STUDY.cluster(1).sets(:)) > length(STUDY.datasetinfo)
        disp('Warning: Some datasets had been removed from the STUDY, clusters have been reinitialized');
        STUDY.cluster = []; modif = 1;
    else
    	if isempty(STUDY.cluster(1).sets)
            STUDY.cluster = []; modif = 1;
        end
    end
end
if ~studywasempty
    if isempty(STUDY.cluster)
        modif = 1;
        [STUDY] = std_createclust(STUDY, ALLEEG, 'parentcluster', 'on');
    end
    if length(STUDY.cluster(1).child) == length(STUDY.cluster)-1 && length(STUDY.cluster) > 1
        newchild = { STUDY.cluster(2:end).name };
        if ~isequal(STUDY.cluster(1).child, newchild)
            STUDY.cluster(1).child = newchild;
        end
    end
end

% adapt STUDY design to new framework
% -----------------------------------
if ~isempty(STUDY.design) && ~isfield(STUDY.design(1).variable, 'vartype')
    for inddes = 1:length(STUDY.design)
        for indvar = 1:length(STUDY.design(inddes).variable)
            STUDY.design(inddes).variable(indvar).vartype = 'categorical';
        end
    end
end
if ~isempty(STUDY.design) && isfield(STUDY.design(1), 'cell')
    STUDY.design = rmfield(STUDY.design, 'cell');
end

% determine if there has been any change
% --------------------------------------
if modif
    STUDY.saved = 'no';
    command = '[STUDY ALLEEG] = std_checkset(STUDY, ALLEEG);';
    addToHistory = true;
    % check duplicate
    if length(STUDY.history) >= length(command) && strcmpi(STUDY.history(end-length(command)+1:end), command)
        addToHistory = false;
    end
    if addToHistory
        STUDY.history =  sprintf('%s\n%s',  STUDY.history, command);
    end
end

% convert combined independent variables
% --------------------------------------
function val = convertindvarval(val)
if ischar(val)
    inddash = findstr(' - ', val);
    if isempty(inddash), return; end
    inddash = [ -2 inddash length(val)+1];
    for ind = 1:length(inddash)-1
        newval{ind} = val(inddash(ind)+3:inddash(ind+1)-1);
    end
    val = newval;
end


function zipfilename = exporttozip(funcname,abspath,zipfilename)
%% EXPORTTOZIP - Creates a ZIP file containing dependencies of a function
%
% zipfilename = exporttozip(funcname);
% zipfilename = exporttozip(funcname,abspath);
% zipfilename = exporttozip(funcname,abspath,zipfilename);
%
% Finds and Zips all M-files which are required by the specified function(s)
% and which are NOT inside MATLAB's "toolbox" directory.
% Note: this does not include functions called by eval() statements
%
% funcname      is one or more function names, as a string or cell array of strings
% abspath       flag for absolute (rather than relative) paths
%               1 = use absolute paths, write the zipfile to the local directory
%               0 = use relative paths, write the zipfile to the commom rootpath
% zipfilename   the zipfile to export the functions to
%
% abspath is useful when you are using networked drives where a common
% rootpath may not exist or be useful
%
% If no zipfilename is specified, the ZIP file is created in the current
% working directory with name X.zip where X is the first function name in
% the supplied list
%
% The file names in the ZIP file are relative to the
% common root directory directory of all the required files.  If no
% common root directory can be established (e.g. if files are on
% different drives) an error is thrown (sometimes).
%

%% Version
% Copyright 2006-2010 The MathWorks, Inc.
% Original: Malcolm Wood    11 Apr 2006
% Modified: ?               15 Mar 2010     ? on Matlab FileExchange
%           Mark Morehead      Mar 2011     added abspath ability
%                                             relative paths do not work well
%                                             across multiple network drives
%                                           changed strcmp to strcmpi to
%                                             avoid problems with mixing cases
%                                             recommend by Mut Ante
%           Mark Morehead   25 Feb 2013     Added nargin<2 test
%                                           recommended by Ioannis Filippidis
%

%% Start

if ~iscell(funcname)
    funcname = {funcname};
end

if isempty(funcname)
    error('No function names specified');
end

if ~iscellstr(funcname)
    error('Function names must be strings');
end

if nargin>1 && ~isnumeric(abspath)
    error('abspath must be a numeric value');
end

if nargin < 2
    abspath = 0;
end

if nargin<3
    zipfilename = fullfile(pwd,[ funcname{1} '.zip' ]);
end

req = cell(size(funcname));
for i=1:numel(funcname)
    req{i} = mydepfun(funcname{i},1); % recursive
end
req = vertcat(req{:}); % cell arrays of full file names
req = unique(req);

% Use relative rootpath
if ~abspath
    % Find the common root directory
    d = i_root_directory(req);
    % Calculate relative paths for all required files.
    n = numel(d);
    for i=1:numel(req)
        % This is the bit that can't be vectorised
        req{i} = req{i}(n+1:end); % step over last character (which is the separator)
    end
    zip(zipfilename,req,d);
else
    zip(zipfilename,req);
end

fprintf(1,'Created %s \n',zipfilename);
fprintf(1,' with %d entries (exludes functions called by eval())\n',numel(req));

%%%%%%%%%%%%%%%%%%%%%
% Identifies the common root directory of all files in cell array "req"
function d = i_root_directory(req)

d = i_parent(req{1});
for i=1:numel(req)
    t = i_parent(req{i});
    if strncmpi(t,d,numel(d))
        % req{i} is in directory d.  Next file.
        continue;
    end
    % req{i} is not in directory d.  Up one directory.
    count = 1;
    while true
        % Remove trailing separator before calling fileparts.  Add it
        % again afterwards.
        tempd = i_parent(d(1:end-1));
        if strcmp(d,tempd)
            % fileparts didn't find us a higher directory
            error('Failed to find common root directory for %s and %s',req{1},req{i});
        end
        d = tempd;
        if strncmpi(t,d,numel(d))
            % req{i} is in directory d.  Next file.
            break;
        end
        % Safety measure for untested platform.
        count = count+1;
        if count>1000
            error('Bug in i_root_directory.');
        end
    end
end

%%%%%%%%%%%%%%%%%%%
function d = i_parent(d)
% Identifies the parent directory, including a trailing separator

% Include trailing separator in all comparisons so we don't assume that
% file C:\tempX\file.txt is in directory C:\temp
d = fileparts(d);
if d(end)~=filesep
    d = [d filesep];
end


%Contents of the AMD sparse matrix ordering package:
%
% amd2          - p = amd2 (A), the approximate minimum degree ordering of A
% amd_demo      - a demo of amd2, using the can_24 matrix
% amd_make      - to compile amd2 for use in MATLAB
% amd_install   - compile and install amd2 for use in MATLAB
%
% See also:  amd, amd2, colamd, symamd, colmmd, symmmd, umfpack
%
% Note that amd2 and the built-in amd function in MATLAB 7.3 and later are one
% and the same.
%
% Example:
%   p = amd2 (A) ;

% Copyright 1994-2007, Tim Davis, University of Florida,
% Patrick R. Amestoy, and Iain S. Duff.

help Contents


function res = indtrial(this, label, flag)
% Method for getting trial indices based on condition labels
% FORMAT res = indtrial(this, label)
% this       - MEEG object
% label      - string or cell array of labels, 'GOOD' and 'BAD'
%              can be added to list of labels to select only
%              good or bad trials respectively
% flag       - 'GOOD' or 'BAD' to include only good or bad trials
%              respectively (all are selected by default)
%
% res        - vector of trial indices matching condition labels
%__________________________________________________________________________
% Copyright (C) 2008-2012 Wellcome Trust Centre for Neuroimaging

% Vladimir Litvak
% $Id: indtrial.m 5059 2012-11-15 13:48:35Z vladimir $

if ischar(label)
    label = {label};
end

[dum, res] = match_str(label, conditions(this));

if nargin > 2
    if strcmpi(flag, 'GOOD')
        res = setdiff(res, badtrials(this));
    elseif strcmpi(flag, 'BAD')
        res = intersect(res, badtrials(this));
    end
end


function a = dbgetarrivals(databasePath, subset_expr)
    %DBGETARRIVALS Load arrivals from an Antelope CSS3.0 database
    %
    % arrivals = DBGETORIGINS(databasePath) opens the arrival table belonging to
    % the database specified by databasePath. The arrival table will be joined to
    % the assoc, origin and event tables too, if these are present. If all
    % of these is present, only arrivals corresponding to preferred origins
    % will be loaded. If assoc is present, but not event or origin, only
    % arrivals referenced in the assoc table are loaded.
    %
    % The output is a structure containing the fields:
    %   * arid
    %   * sta
    %   * chan
    %   * time (arrival date/time converted from epoch to MATLAB datenum)
    %   * iphase
    %   * amp
    %   * snr
    %   * seaz
    %   * deltim
    %   * iphase
    %   * delta
    %   * otime (origin date/time converted from epoch to MATLAB datenum)
    %   * orid
    %   * evid
    %   * timeres
    %   * traveltime (= time - otime)
    %
    % All these fields are vectors of numbers, or cell arrays of strings.
    % If you want more fields adding, please email Glenn
    %
    % arrivals = DBGETARRIVALS(databasePath, subset_expression) evaluates the
    % subset specified before reading the arrivals. subset_expression must
    % be a valid expression accepted by dbe/dbeval.

    % Author: Glenn Thompson
    %
    %% To do:
    %   test

    %% Load the earthquake catalogue

    % Initialize output so we don't crash if return early
    a = struct('sta', [], 'chan',[], 'time', [], 'arid',[], 'amp',[], ...
        'snr',[], 'seaz', [], 'deltim', [], 'iphase', {}, 'delta', [], 'otime', [], ...
        'orid', [], 'evid', [], 'timeres', [], 'traveltime', []);

    % Check the database descriptor exists - if not abort
    if ~exist(databasePath, 'file')
        warning(sprintf('Database %s does not exist, please type in a different one',databasePath));
        return
    end
    debug.print_debug(0, sprintf('Loading data from %s',databasePath));
    ARRIVAL_TABLE_PRESENT = antelope.dbtable_present(databasePath, 'arrival');
    if (ARRIVAL_TABLE_PRESENT) % Open the arrival table, subset if expr exists
        db = dbopen(databasePath, 'r');
        db = dblookup_table(db, 'arrival');
        numarrivals = dbquery(db,'dbRECORD_COUNT');
        debug.print_debug(1,sprintf('Got %d records from %s.arrival',numarrivals,databasePath));
        if numarrivals > 0
            ASSOC_TABLE_PRESENT = antelope.dbtable_present(databasePath, 'assoc');
            ORIGIN_TABLE_PRESENT = antelope.dbtable_present(databasePath, 'origin');
            EVENT_TABLE_PRESENT = antelope.dbtable_present(databasePath, 'event');

            if (ASSOC_TABLE_PRESENT)
                % open and join the assoc table
                db2 = dblookup_table( db , 'assoc');
                db = dbjoin(db, db2);

                if dbnrecs(db) > 0 & (ORIGIN_TABLE_PRESENT)
                    % open and join the origin table
                    db3=dblookup_table( db, 'origin');
                    db= dbjoin(db, db3);

                    if dbnrecs(db) > 0 & (EVENT_TABLE_PRESENT)
                        % open and join the event table and subset for prefor
                        db4=dblookup_table( db, 'event');
                        db= dbjoin(db, db4);
                        %dbunjoin(db, 'dbview2')
                        db = dbsubset( db, 'origin.orid == event.prefor');
                    end
                end
            end
            if exist('subset_expr','var')
                db = dbsubset(db, subset_expr);
            end

            if dbnrecs(db)>0

                % read (some) fields & close db
                dbsave_view(db)
                [a.sta, a.chan, a.time, a.phase, a.arid, a.amp, a.snr, a.deltim] = dbgetv(db, 'sta', 'chan', 'arrival.time', 'iphase', 'arid', 'amp', 'snr', 'deltim');
                if (ASSOC_TABLE_PRESENT)
                    [a.delta, a.seaz, a.esaz, a.timeres] = dbgetv(db, 'assoc.delta', 'assoc.seaz', 'assoc.esaz', 'assoc.timeres');
                end
                if (ORIGIN_TABLE_PRESENT)
                    [a.otime, a.orid, a.evid] = dbgetv(db, 'origin.time', 'origin.orid', 'origin.evid');
                    a.otime
                    a.traveltime = a.time - a.otime;
                end

                dbclose(db);

                % Cell arrays don't get created if only 1 row
                if strcmp(class(a.sta),'char')
                   a.sta = {a.sta};
                   a.chan = {a.chan};
                   a.iphase = {a.iphase};
                end

                % Times are all in epoch
                a.time = epoch2datenum(a.time);
                a.otime = epoch2datenum(a.otime);

                % Display counts
                fprintf('\n%d arrivals\n',numel(a.time));
            end

        end
    end

end


function scores = JAABADetect(expdir,varargin)
% Run classifiers trained from JAABA on experiments
% scores = JAABADetect(expdir,'classifierfiles',classifierfiles,'configfiles',configfiles)
% scores = JAABADetect(expdir,'classifierparamsfile',classifierparamsfile)
% expdir (String) -- is the experiment directory.
% classifierfiles (Cell array of Strings) -- locations of the classifiers
% for the behaviors that you want to detect.
% configfiles (Cell array of strings) -- locations of the project files
% that were used to train the classifiers.
% Example usage:
% JAABADetect('testExp','classifierfiles',{'ChaseClassifier.mat'},'configfiles',{'ChaseProject.mat'});

[blockSize,classifierfiles,configfiles,classifierparamsfile,configparams,forcecompute,DEBUG,isrelativepath,fnsrelative] = ...
  myparse(varargin,'blockSize',10000,...
  'classifierfiles',{},'configfiles',{},...
  'classifierparamsfile',0,...
  'configparams',[],...
  'forcecompute',false,...
  'debug',false,...
  'isrelativepath',false,...
  'fnsrelative',{'featureparamfilename'});

if ~isdeployed,
  SetUpJAABAPath;
end

if ischar(forcecompute),
  forcecompute = str2double(forcecompute) ~= 0;
end
if ischar(DEBUG),
  DEBUG = str2double(DEBUG);
end

if ischar(classifierparamsfile),
  configparams = ReadClassifierParamsFile(classifierparamsfile,'isrelativepath',isrelativepath,'fnsrelative',fnsrelative);
  classifierfiles = {configparams.classifierfile};
  configfiles = {configparams.configfile};
%{
%   if ~exist(classifierparamsfile,'file'),
%     error('File %s does not exist',classifierparamsfile);
%   end
%   fid = fopen(classifierparamsfile,'r');
%   classifierfiles = {};
%   configfiles = {};
%   while true,
%     l = fgetl(fid);
%     if ~ischar(l),
%       break;
%     end
%     if isempty(l),
%       continue;
%     end
%     if strcmp(l(1),'%'), continue; end
%     ws = regexp(l,',','split');
%     classifierfiles{end+1} = ws{1}; %#ok<AGROW>
%     configfiles{end+1} = ws{2}; %#ok<AGROW>
%   end
%   fclose(fid);
%}
end

nclassifiers = numel(classifierfiles);
if nclassifiers == 0,
  error('No classifiers input');
end
% read parameters
fprintf('Reading classifier parameters...\n');
if isempty(configparams),
  if nclassifiers ~= numel(configfiles),
    error('Number of classifier files and number of config files do not match');
  end
  configparams = cell(1,nclassifiers);
  for i = 1:nclassifiers,
    [~,~,ext] = fileparts(configfiles{i});
    if strcmpi(ext,'.xml');
      configparams{i} = ReadXMLConfigParams(configfiles{i});
    elseif strcmpi(ext,'.jab')
      tmp = load(configfiles{i},'-mat');
      configparams{i} = tmp.x;
    else
      configparams{i} = load(configfiles{i});
    end
  end
else
  if nclassifiers ~= numel(configparams),
    error('Number of classifier files and number of config params do not match');
  end
  if ~iscell(configparams),
    configparams = num2cell(configparams);
  end
end
classifiers = cell(1,nclassifiers);
for i = 1:nclassifiers,
  [~,~,ext] = fileparts(classifierfiles{i});
  if strcmpi(ext,'.jab'),
    tmp = load(classifierfiles{i},'-mat');
    classifiers{i} = tmp.x;
  else
    classifiers{i} = load(classifierfiles{i});
  end
end

% scores file name
scorefilenames = cell(1,nclassifiers);
for i = 1:nclassifiers,
  % obsolete, ReadXMLConfigParams does this
%   if ~isfield(configparams{i}.file,'scorefilename'),
%     if ~isfield(configparams{i},'behaviors'),
%       error('configparams %d does not have field behaviors',i);
%     elseif ~isfield(configparams{i}.behaviors,'names'),
%       error('configparams{%d}.behaviors does not have field names',i);
%     else
%       scorefilenames{i} = ['scores_',configparams{i}.behaviors.names,'.mat'];
%     end
%   else
    scorefilenames{i} = configparams{i}.file.scorefilename;
%  end
end

% find classifiers to run together
[classifiersets,featureconfigperset] = GroupClassifiers(configparams,scorefilenames);

% find classifiers that are done
docompute = true(1,nclassifiers);
if forcecompute == 0,
  for i = 1:nclassifiers,
    scorefile = fullfile(expdir,scorefilenames{i});
    if exist(scorefile,'file'),

      % check timestamp
      try
        sd = load(scorefile,'timestamp');
        if sd.timestamp == classifiers{i}.classifierTS,
          docompute(i) = false;
          behaviorname = configparams{i}.behaviors.names;
          if iscell(behaviorname),
            behaviorname = sprintf('%s_',behaviorname{:});
            behaviorname = behaviorname(1:end-1);
          end
          fprintf('Skipping classifier %s(%s) for experiment %s, scores already computed\n',...
            behaviorname,datestr(classifiers{i}.classifierTS,'yyyymmddTHHMMSS'),...
            expdir); %#ok<PFCEL>
        end
      catch ME,
        fprintf('Could not check timestamps: %s\n',getReport(ME));
        continue;
      end
    end

  end

end

scores = cell(1,nclassifiers);
if nargout >= 1,
  for i = find(~docompute),
    scorefile = fullfile(expdir,scorefilenames{i});
    try
      sd = load(scorefile,'allScores');
      scores{i} = sd.allScores.scores;
    catch ME,
      warning('Could not load in scores from %s: %s',scorefile,getReport(ME));
      docompute(i) = true;
    end
  end
end

if ~any(docompute),
  fprintf('All classifiers have been run on experiment %s. Exiting.\n',expdir);
  return;
end

for seti = 1:numel(classifiersets),

  classifierset = intersect(find(docompute),classifiersets{seti});

  if isempty(classifierset),
    continue;
  end

  nclassifiers_curr = numel(classifierset);

  fprintf('Processing %d classifiers together:\n',nclassifiers_curr);
  fprintf('%s\n',classifierfiles{classifierset});

  % get a list of all window features
  wfidx_per_classifier = cell(1,nclassifiers_curr);
  wfs_per_classifier = cell(1,nclassifiers_curr);
  wfs = {};
  fprintf('Getting list of window features...\n');
  for ii = 1:numel(classifierset),

    i = classifierset(ii);

    fprintf('Classifier %d\n',i);

    % check trans_types
    nfixtranstypes = 0;
    fns = fieldnames(classifiers{i}.windowfeaturescellparams);
    for j = 1:numel(fns),
      fn = fns{j};
      k = find(strcmp(classifiers{i}.windowfeaturescellparams.(fn),'trans_types'));
      if ~isempty(k),
        k = k+1;
        if ~iscell(classifiers{i}.windowfeaturescellparams.(fn){k}),
          classifiers{i}.windowfeaturescellparams.(fn){k} = {classifiers{i}.windowfeaturescellparams.(fn){k}}; %#ok<CCAT1>
          nfixtranstypes = nfixtranstypes+1;
        end
      end
      for k = 2:2:numel(classifiers{i}.windowfeaturescellparams.(fn)),
        if ~iscell(classifiers{i}.windowfeaturescellparams.(fn){k}),
          continue;
        end
        l = find(strcmp(classifiers{i}.windowfeaturescellparams.(fn){k},'trans_types'),1);
        if isempty(l),
          continue;
        end
        l = l+1;
        if ~iscell(classifiers{i}.windowfeaturescellparams.(fn){k}{l}),
          classifiers{i}.windowfeaturescellparams.(fn){k}{l} = {classifiers{i}.windowfeaturescellparams.(fn){k}{l}}; %#ok<CCAT1>
          nfixtranstypes = nfixtranstypes+1;
        end
      end
    end
    [~,name] = fileparts(classifierfiles{i});
    if nfixtranstypes > 0,
      warning('Fixed %d trans_types issues for classifier %s',nfixtranstypes,name);
    end

    if ~isfield(classifiers{i},'featurenames'),

      warning('featurenames field not found in classifier %s (old classifier format), computing featurenames using ComputeWindowFeatures',name);

      % all per-frame features for this classifier
      pffs_curr = fieldnames(classifiers{i}.windowfeaturesparams);

      % list of all window features for this classifier
      feature_names = {};
      for j = 1:numel(pffs_curr),
        fn = pffs_curr{j};
        [~,feature_names_curr] = ComputeWindowFeatures([0,0],...
          classifiers{i}.windowfeaturescellparams.(fn){:});
        feature_names_curr = cellfun(@(x) [{fn},x],feature_names_curr,'UniformOutput',false);
        feature_names = [feature_names,feature_names_curr]; %#ok<AGROW>
      end

      % which features are actually used
      dims = [classifiers{i}.classifier.dim];
      feature_names = feature_names(dims);

      wfs_per_classifier{ii} = feature_names;

    else
      dims = [classifiers{i}.classifier.dim];
      feature_names =  classifiers{i}.featurenames(dims);
      wfs_per_classifier{ii} = feature_names;

    end

    % put these in with the rest of the classifiers' window features
    wfidx_per_classifier{i} = nan(1,numel(dims));
    for j = 1:numel(feature_names),
      idxcurr = find(WindowFeatureNameCompare(feature_names{j},wfs),1);
      if isempty(idxcurr),
        idxcurr = numel(wfs)+1;
        wfs{idxcurr} = feature_names{j}; %#ok<AGROW>
      end
      wfidx_per_classifier{i}(j) = idxcurr;
    end

  end
  wf2pff = cellfun(@(x)x{1},wfs,'UniformOutput',false);
  [pffs,~,wf2pffidx] = unique(wf2pff);
  pfidx_per_classifier = cell(1,nclassifiers_curr);
  for ii = 1:numel(classifierset),
    pfidx_per_classifier{ii} = wf2pffidx(wfidx_per_classifier{ii});
  end

  % create parameters for ComputeWindowFeatures which will compute all these
  % features
  fprintf('Converting window feature names to ComputeWindowFeatures parameters...\n');
  windowfeaturescellparams = struct;
  for pfi = 1:numel(pffs),
    pf = pffs{pfi};
    wfidx = wf2pffidx==pfi;
    windowfeaturescellparams.(pf) = WindowFeatureName2Params(wfs(wfidx));
  end

  % make a temporary config file containing all features
  configparams_global = configparams{classifierset(1)};
  configparams_global.behaviors.names = 'JAABADetect_DummyBehavior';
  configparams_global.featureparamlist = struct;
  for i = 1:numel(pffs),
    configparams_global.featureparamlist.(pffs{i}) = struct;
  end
  configfile_global = tempname();
  % SaveXMLParams(configparams_global,configfile_global,'params');
  save(configfile_global,'-struct','configparams_global');

  % initialize with temporary configfile
  data = JLabelData(configfile_global,'openmovie',false);
  [success,msg] = data.AddExpDirNoPreload(expdir);
  if ~success,
    error(msg);
  end
  expi = 1;
  % check if all the data inecessary is there
  if ~data.filesfixable,
    fprintf('Experiment %s is missing required files that cannot be generated within this interface.',expdir);
    return;
  end

  % generate missing files if possible
  if data.filesfixable && ~data.allfilesexist,
    [success,msg] = data.GenerateMissingFiles(data.nexps,false);
    if ~success,
      fprintf('Error generating missing required files for experiment %s: %s. Removing...',expdir,msg);
      return;
    end
  end

  isfirst = true;
  for i = classifierset,
    scores{i} = cell(1,data.nflies_per_exp(expi));
  end

  for flies = 1:data.nflies_per_exp(expi),

    fprintf('Classifying flies %s...\n',mat2str(flies));

    file = data.GetPerframeFiles(expi);
    for j = 1:numel(file),
      if ~exist(file{j},'file'),
        error('Per-frame data file %s does not exist',file{j});
      end
      tmp = load(file{j});
      ndx = find(strcmp(data.allperframefns{j},pffs));
      perframedata{ndx} = tmp.data{flies(1)};
    end

    % frames for this fly
    tStart = 1;
    off = 1-data.firstframes_per_exp{expi}(flies);
    tEnd = data.endframes_per_exp{expi}(flies)+off;
    for i = classifierset,
      scores{i}{flies} = nan(1,tEnd-off);
    end

    if tEnd < 2,
      fprintf('Trajectory length too short. Not classifying\n');
      continue;
    end

    % one block of frames at a time
    for t0 = tStart:blockSize:tEnd,

      t1 = min(t0+blockSize-1,tEnd);

      % compute all window features
      X = single([]);
      feature_names = {};
      % loop through per-frame features
      for j = 1:numel(pffs),
        fn = pffs{j};

        t11 = min(t1,numel(perframedata{j}));
        [x_curr,feature_names_curr] = ...
          ComputeWindowFeatures(perframedata{j},windowfeaturescellparams.(fn){:},'t0',t0,'t1',t11);

        if t11 < t1,
          x_curr(:,end+1:end+t1-t11) = nan;
        end

        % add the window data for this per-frame feature to X
        nold = size(X,1);
        nnew = size(x_curr,2);
        if nold > nnew,
          warning('Number of examples for per-frame feature %s does not match number of examples for previous features',fn);
          x_curr(:,end+1:end+nold-nnew) = nan;
        elseif nnew > nold && ~isempty(X),
          warning('Number of examples for per-frame feature %s does not match number of examples for previous features',fn);
          X(end+1:end+nnew-nold,:) = nan;
        end
        X = [X,single(x_curr)']; %#ok<AGROW>
        % add the feature names
        feature_names = [feature_names,cellfun(@(s) [{fn},s],feature_names_curr,'UniformOutput',false)]; %#ok<AGROW>
      end

      % find indexes for each classifier into feature_names
      if isfirst,

        classifiers_indexed = cell(1,nclassifiers_curr);

        for ii = 1:nclassifiers_curr,
          i = classifierset(ii);
          wfidx_per_classifier{ii} = nan(1,numel(wfs_per_classifier{ii}));
          for j = 1:numel(wfs_per_classifier{ii}),
            idxcurr = find(WindowFeatureNameCompare(wfs_per_classifier{ii}{j},feature_names));
            if numel(idxcurr) ~= 1,
              error('Error matching wfs for classifier %d with window features computed',i);
            end
            wfidx_per_classifier{ii}(j) = idxcurr;
          end
          classifiers_indexed{ii} = classifiers{i}.classifier;
          for j = 1:numel(classifiers_indexed{ii}),
            classifiers_indexed{ii}(j).dim = j;
          end
        end

        isfirst = false;

      end

      % apply the classifiers
      for ii = 1:nclassifiers_curr,
        i = classifierset(ii);
        Xcurr = X(:,wfidx_per_classifier{ii});
        % there's some weird offseting in the stored scores files
        scores{i}{flies}(t0-off:t1-off) = myBoostClassify(Xcurr,classifiers_indexed{ii});
      end

      fprintf('%d%% done.\n',round(100*(t1-tStart)/(tEnd-tStart)));

    end

  end

  % save scores
  for ii = 1:nclassifiers_curr,
    i = classifierset(ii);
    data.SetPostprocessingParams(classifiers{i}.postprocessparams);
    allScores = struct;
    allScores.scores = scores{i};
    allScores.tStart = data.firstframes_per_exp{expi};
    allScores.tEnd = data.endframes_per_exp{expi};
    for flies = 1:data.nflies_per_exp(expi),
      [i0s i1s] = get_interval_ends(allScores.scores{flies}>0);
      allScores.t0s{flies} = i0s;
      allScores.t1s{flies} = i1s;
    end

    postprocessedscoresA = cell(1,data.nflies_per_exp(expi));
    tStartAll = data.firstframes_per_exp{expi};
    tEndAll = data.endframes_per_exp{expi};
    for flies = 1:data.nflies_per_exp(expi)
      data.windowdata.scoreNorm = classifiers{i}.scoreNorm;
      postprocessedscoresA{flies} = nan(1,tEndAll(flies));
      postprocessedscoresA{flies}(tStartAll(flies):tEndAll(flies)) = ...
        data.Postprocess(scores{i}{flies}(tStartAll(flies):tEndAll(flies)));
    end
    allScores.postprocessed = postprocessedscoresA;
    allScores.postprocessparams = data.postprocessparams;
    allScores.scoreNorm = classifiers{i}.scoreNorm; %#ok<STRNU>
    scorefilename = scorefilenames{i};
    %scorefilename = ['test_',scorefilename];
    sfn = fullfile(expdir,scorefilename);

    if exist(sfn,'file'),
      [didbak,msg] = copyfile(sfn,[sfn,'~']);
      if ~didbak,
        warning('Could not create backup of %s: %s',sfn,msg);
      end
    end
    timestamp = classifiers{i}.classifierTS; %#ok<NASGU>
    classifierfilename = classifierfiles{i}; %#ok<NASGU>
    if DEBUG == 0,
      save(sfn,'allScores','timestamp','classifierfilename');
    end
  end

end


function model=combineMetaCycKEGGModels(metacycModel,keggModel)
% combineMetaCycKEGGModels
%	Combine MetaCyc and KEGG draft models into one model structure.
%
%	Input:
%	metacycModel	the reconstructed model from MetaCyc
%	keggModel       the reconstructed model from KEGG
%
%	Output:
%	model           a model structure generated by integrating information
%                   from draft models reconstructed using MetaCyc and KEGG
%                   databases. The 'rxnFrom/metFrom/geneFrom' fields are
%                   included to indicate the source.
%
%	Usage: model=combineMetaCycKEGGModels(metacycModel,keggModel)

%Just return the model
if nargin<2
    disp('Missing input model');
    return;
end

%Add MetaCyc model as template
model=metacycModel;
model.id='COMBINED';
model.name='Combined model from MetaCyc and KEGG draft models';

%Use MetaCyc model as template
model.rxnFrom=cell(numel(model.rxns),1);
model.rxnFrom(:)={'MetaCyc'};
model.metFrom=cell(numel(model.mets),1);
model.metFrom(:)={'MetaCyc'};
model.geneFrom=cell(numel(model.genes),1);
model.geneFrom(:)={'MetaCyc'};
model.grRulesKEGG=cell(numel(model.rxns),1);
model.grRulesKEGG(:)={''};

%Remove the S and rxnGeneMat matrix
if isfield(model,'S')
    model=rmfield(model,'S');
end
if isfield(model,'rxnGeneMat')
    model=rmfield(model,'rxnGeneMat');
end

%Arrange the genes related fields

%Identify the shared genes between KEGG and MetaCyc
sharedGenes=intersect(keggModel.genes,model.genes);
[~, b]=ismember(sharedGenes,model.genes);
model.geneFrom(b)={'Both'};

%Add unique genes from KEGG and update geneFrom
C=setdiff(keggModel.genes,model.genes);
model.genes=[model.genes;C];
geneFrom=cell(numel(C),1);
geneFrom(:)={'KEGG'};
model.geneFrom=[model.geneFrom;geneFrom];

%Prepare for matching grRules
if isfield(keggModel,'grRules')
    keggModel.grRules=strrep(keggModel.grRules,'(','');
    keggModel.grRules=strrep(keggModel.grRules,')','');
end

%Replace rxns in KEGG model with the corresponding ones in MetaCyc by
%updating values rxns field to MetaCyc version whenever possible

%Collect the ones found in MetaCyc as mappedRxns
mappedRxns=[];
rxnsToMove=[];
grRulesToMove={}; %Storing the grRules to be moved to MetaCyc model

%Read in dbLinks, note that linkMetaCycKEGGRxns need to be run in advance
load('metaCycRxns.mat');
num=0;
numToMove=0;
%Loop through the KEGG model and replace the rxn id from KEGG to MetaCyc
%version
for i=1:numel(keggModel.rxns)
    [~, b]=ismember(rxnLinks.kegg,keggModel.rxns{i});
    I=find(b);

    if numel(I)==1
        %Find out the corresponding MetaCyc reactions
        num=num+1;
        %Record the mapped rxn index in KEGG model and remove them later
        mappedRxns=[mappedRxns,i];
        %keggModel.rxns{i}=rxnLinks.metacyc{I(1)};

        [c, d]=ismember(rxnLinks.metacyc{I(1)},model.rxns);
        if c
            model.rxnFrom{d}='Both';
            %Combine the grRule info Check if KEGG grRules equals or a
            %subset of MetaCyc grRules If not, save the KEGG grRules to a
            %new field grRulesKEGG for manual curation
            k=strfind(model.grRules{d},keggModel.grRules{i});
            if isempty(k)
                if isempty(model.grRulesKEGG{d})
                    model.grRulesKEGG{d}=keggModel.grRules{i};
                else
                    model.grRulesKEGG{d}=strcat(model.grRulesKEGG{d},{' or '},keggModel.grRules{i});
                end
            end
        else
            %For the KEGG rxns have MetaCyc version but not in MetaCyc
            %draft model
            [~, y]=ismember(rxnLinks.metacyc{I(1)},metaCycRxns.rxns);

            %Check if this reaction is repitetive and save the grRules
            [Repeat, Index]=ismember(y,rxnsToMove);
            if ~Repeat
                rxnsToMove=[rxnsToMove;y];  %Record rxns to be moved from KEGG to MetaCyc model
                if isempty(keggModel.grRules{i})
                    %Rxns may have empty grRules (e.g. spontaneous) that
                    %are identified and added back here
                    grRulesToMove=[grRulesToMove;{''}];
                else
                    grRulesToMove=[grRulesToMove;keggModel.grRules{i}];
                end
                numToMove=numToMove+1;
            else
                %If this reaction has been recorded, append the grRules if
                %it is different
                k=strfind(grRulesToMove{Index},keggModel.grRules{i});
                if isempty(k)
                    grRulesToMove{Index}=strcat(grRulesToMove{Index},{' or '},keggModel.grRules{i});
                end
            end
        end
    else
        %Here are the case of one KEGG rxn id link to several MetaCyc ids
        %for j=2:numel(I) Ignore this issue for now, and resolve it later,
        %because it never happened This case can be solved by adding above
        %section into a loop below disp(I(j)); end
    end
end
fprintf('NOTE: A total of %d reactions in the KEGG model were mapped to MetaCyc.\n',num);
fprintf('NOTE: %d reactions already in MetaCyc model, %d will be combined.\n',num-numToMove,numToMove);

%Append mapped MetaCyc rxns in KEGG model that are absent from MetaCyc
%model
rxnFrom=cell(numel(rxnsToMove),1);
rxnFrom(:)={'KEGG'};
model.rxnFrom=[model.rxnFrom;rxnFrom];
model.rxns=[model.rxns;metaCycRxns.rxns(rxnsToMove)];

rxnNames=metaCycRxns.rxnNames(rxnsToMove);
%Replace rxnNames with those from metaCycEnzymes
load('metaCycEnzymes.mat');
for i=1:numel(rxnsToMove)
    [a, b]=ismember(metaCycRxns.rxns{rxnsToMove(i)},metaCycEnzymes.rxns);
    if a
        rxnNames{i}=metaCycEnzymes.rxnNames{b};
    end
end
model.rxnNames=[model.rxnNames;rxnNames];

model.eccodes=[model.eccodes;metaCycRxns.eccodes(rxnsToMove)];
model.subSystems=[model.subSystems;metaCycRxns.subSystems(rxnsToMove)];
model.rxnMiriams=[model.rxnMiriams;metaCycRxns.rxnMiriams(rxnsToMove)];
model.rxnReferences=[model.rxnReferences;metaCycRxns.rxnReferences(rxnsToMove)];
model.rev=[model.rev;metaCycRxns.rev(rxnsToMove)];
model.equations=[model.equations;metaCycRxns.equations(rxnsToMove)];
model.lb=[model.lb;metaCycRxns.lb(rxnsToMove)];
model.ub=[model.ub;metaCycRxns.ub(rxnsToMove)];
model.c=[model.c;metaCycRxns.c(rxnsToMove)];
model.grRules=[model.grRules;grRulesToMove];
%model.grRules=[model.grRules;keggModel.grRules(rxnsToMove(:,2))];
%model.grRulesKEGG=[model.grRulesKEGG;keggModel.grRules(rxnsToMove(:,2))];
rxnFields=cell(numel(rxnsToMove),1);
rxnFields(:)={''};
%model.rxnNotes=[model.rxnNotes;rxnFields];
model.grRulesKEGG=[model.grRulesKEGG;rxnFields];

%Remove all mapped reactions from KEGG model
pureKeggModel=removeReactions(keggModel,mappedRxns,true,true);
fprintf('NOTE: A global KEGG model with %d reactions and %d metabolites was obtained.\n',numel(pureKeggModel.rxns),numel(pureKeggModel.mets));

%Replace mets information in KEGG model with the corresponding ones in
%MetaCyc This includes updating all metabolite-related fields, except S
%matrix.
load('metaCycMets.mat');

if ~isfield(pureKeggModel,'metCharges')
    pureKeggModel.metCharges=NaN(numel(pureKeggModel.mets),1);
end

num=0;
for i=1:numel(pureKeggModel.mets)
    [a, b]=ismember(pureKeggModel.mets{i},metaCycMets.keggid);
    if a
        %For now only replace the met ids
        num=num+1;
        pureKeggModel.mets{i}=metaCycMets.mets{b};
        %Record the replaced mets into a vector for later
    end
end
fprintf('NOTE: A total of %d metabolites from the global KEGG model will be re-mapped to MetaCyc.\n',num);

%Generate equations for pureKeggModel
equationStrings=constructEquations(pureKeggModel,'',false,false,false,true);

%Add the pure KEGG reactions to MetaCyc model
rxnFrom=cell(numel(pureKeggModel.rxns),1);
rxnFrom(:)={'KEGG'};
model.rxnFrom=[model.rxnFrom;rxnFrom];
model.rxns=[model.rxns;pureKeggModel.rxns];
model.equations=[model.equations;equationStrings];
model.rxnNames=[model.rxnNames;pureKeggModel.rxnNames];
model.eccodes=[model.eccodes;pureKeggModel.eccodes];
model.subSystems=[model.subSystems;pureKeggModel.subSystems];
model.rxnMiriams=[model.rxnMiriams;pureKeggModel.rxnMiriams];
model.rev=[model.rev;pureKeggModel.rev];
model.lb=[model.lb;pureKeggModel.lb];
model.ub=[model.ub;pureKeggModel.ub];
model.c=[model.c;pureKeggModel.c];
model.grRules=[model.grRules;pureKeggModel.grRules];
rxnFields=cell(numel(pureKeggModel.rxns),1);
rxnFields(:)={''};
model.grRulesKEGG=[model.grRulesKEGG;rxnFields];
model.rxnReferences=[model.rxnReferences;rxnFields];
%model.rxnNotes=[model.rxnNotes;rxnFields];

%It could also be that the metabolite and reaction names are empty for some
%reasons. In that case, use the ID instead
%I=cellfun(@isempty,model.metNames); model.metNames(I)=model.mets(I);
I=cellfun(@isempty,model.rxnNames);
model.rxnNames(I)=model.rxns(I);

%Generate S matrix and mets
[S, mets, ~]=constructS(model.equations);
model.S=S;
model.mets=mets;

%Remap metabolite information
if isfield(model,'metFrom')
    model=rmfield(model,'metFrom');
end
model.metFrom=cell(numel(model.mets),1);
model.metFrom(:)={''};

if isfield(model,'metNames')
    model=rmfield(model,'metNames');
end
model.metNames=cell(numel(model.mets),1);
model.metNames(:)={''};

if isfield(model,'metFormulas')
    model=rmfield(model,'metFormulas');
end
model.metFormulas=cell(numel(model.mets),1);
model.metFormulas(:)={''};

if ~isfield(model,'metCharges')
    model=rmfield(model,'metCharges');
end
model.metCharges=NaN(numel(model.mets),1);

if ~isfield(model,'inchis')
    model=rmfield(model,'inchis');
end
model.inchis=cell(numel(model.mets),1);
model.inchis(:)={''};

if ~isfield(model,'metMiriams')
    model=rmfield(model,'metMiriams');
end
model.metMiriams=cell(numel(model.mets),1);
model.metMiriams(:)={''};

keggMets=getMetsFromKEGG();
for i=1:numel(model.mets)
    [a, b]=ismember(model.mets{i},metaCycMets.mets);
    if a
        %Replace the met related info
        model.mets{i}=metaCycMets.mets{b};
        model.metNames{i}=metaCycMets.metNames{b};
        model.metFormulas{i}=metaCycMets.metFormulas{b};
        model.metMiriams{i}=metaCycMets.metMiriams{b};
        model.inchis{i}=metaCycMets.inchis{b};
        model.metCharges(i,1)=metaCycMets.metCharges(b,1);
        num=num+1;
    else
        [c, d]=ismember(model.mets{i},keggMets.mets);
        if c
            model.mets{i}=keggMets.mets{d};
            model.metNames{i}=keggMets.metNames{d};
            model.metFormulas{i}=keggMets.metFormulas{d};
            model.metMiriams{i}=keggMets.metMiriams{d};
            model.inchis{i}=keggMets.inchis{d};
            num=num+1;
        end
    end

end

%It could also be that the metabolite and reaction names are empty for some
%reasons. In that case, use the ID instead
I=cellfun(@isempty,model.metNames);
model.metNames(I)=model.mets(I);

%Add confidenceScores to model in a rough way
if isfield(model,'rxnConfidenceScores')
    model=rmfield(model,'rxnConfidenceScores');
end
model.rxnConfidenceScores=NaN(numel(model.rxns),1);
model.rxnConfidenceScores(:)=2;

%Put all metabolites in one compartment called 's' (for system). This is
%done just to be more compatible with the rest of the code
model.comps={'s'};
model.compNames={'System'};
model.metComps=ones(numel(model.mets),1);
model.b=zeros(numel(model.mets),1);

%Create the new rxnGene matrix, get back to this later
model.rxnGeneMat=sparse(numel(model.rxns),numel(model.genes));

end


function output = GetCustomDefaults(current,property,value)

%GetCustomDefaults - Get custom default value for a given function property.
%
%  This is an 'internal' function used by FMAToolbox. You should not need
%  to use it, unless you are developping new functions for this toolbox.
%
%  See Example below.
%
%  WARNING
%
%    Custom defaults should be allowed with great care, because they *implicitly*
%    change the default behavior of functions: because the actual function call
%    does not make it explicit that default values are being overriden, the same
%    code can yield different results for different users, or even for the same
%    user before and after she/he changes the custom defaults.
%
%  USAGE
%
%    output = GetCustomDefaults(current,property,value)
%
%    current        current value
%    property       property name
%    value          default value if custom default value is undefined
%
%  EXAMPLE
%
%    The function GetPositions has optional property-value pairs, including:
%
%     - 'mode' (default value 'clean')
%     - 'coordinates' (default value 'normalized')
%     - 'pixel' (no default value)
%     - 'distances' (default value [0 Inf])
%
%    Users may want to change default values, e.g. have 'coordinates'
%    set to 'video' whenever they call GetPositions without an explicit
%    value. GetPositions therefore includes the following code:
%
%      % Default values (customizable defaults must be empty at this point)
%      c = '';
%
%      % Parse parameters
%      (possibly set a value for c, if supplied among optional input parameters)
%
%      % Customizable defaults
%      c = GetCustomDefaults(c,'coordinates','normalized');
%
%    GetCustomDefaults will first test if c was already set (non-empty), in which
%    case c will remain unaltered. Otherwise, it will test if there is a user-defined
%    default value for 'coordinates', and issue a warning and set c to this value if
%    it exists, or set it to 'normalized' otherwise.
%
%    To define a custom default value, users would have to e.g. add the following
%    lines to their startup.m file:
%
%     global SETTINGS;
%     SETTINGS.GetPositions.coordinates = 'video';
%
%  NOTE
%
%    Do not forget to document when the default value for a property can be customized.
%    See the code for GetPositions for an example.

% Copyright (C) 2010-2014 by Michaël Zugaro
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 3 of the License, or
% (at your option) any later version.

if nargin == 2,
	% Backward compatibility for former usage: output = GetCustomDefaults(property,value)
	value = property;
	property = current;
	warning('Deprecated usage (type ''help GetCustomDefaults'' for details).');
else
	% Non-empty input
	if ~isempty(current),
		output = current;
		return
	end
end

% Default value
output = value;

% Find out calling function name
stack = dbstack;
if length(stack) < 2, error('Stack error (sorry, cannot be more explicit)'); end
functionName = stack(2).name;

% Look for custom default
global SETTINGS;
if ~exist('SETTINGS'), return; end
if ~isfield(SETTINGS,functionName), return; end
p = getfield(SETTINGS,functionName);
if ~isfield(p,property), return; end
output = getfield(p,property);

% Warn user
if isdvector(output),
	d =  sprintf('%g ',output);
	d = d(1:end-1);
	if length(output) ~= 1,
		d = ['[' d '] '];
	else
		d = [d ' '];
	end
elseif isstring_FMAT(output),
	d = ['''' output ''' '];
else
	d = '';
end
warning(['Using custom default value ' d 'for ''' property ''' in function ''' functionName '''.']);


function [ image_paths, image_set_name ] = get_image_paths_from_voc( voc_path, image_set, voc_year )

VOCopts         = initVOCOpts( voc_path, voc_year );
VOCopts.testset = image_set;
image_set_name  = ['voc_', voc_year, '_' image_set];

image_ext       = '.jpg';
image_dir       = fileparts(VOCopts.imgpath);
image_ids       = textread(sprintf(VOCopts.imgsetpath, image_set), '%s');
image_paths     = strcat([image_dir, filesep], image_ids, image_ext);
end


function res = pyff(command, varargin)

% PYFF -  wrapper function used to initialize and communicate with Pyff
%         (http://bbci.de/pyff) from Matlab
%
%Usage:
% res = pyff(command, <OPT>)
%
%IN:
% 'command' -  command to be issued: 'startup', 'init',
%              'setdir','set', 'get', 'refresh','play', 'stop','quit',
%              'saveSettings', 'loadSettings'
%
% res = pyff('startup',<OPT>): startup pyff in a separate console window.
% res contains the command string that is executed. For
% this command, OPT is a property/value list or struct of options with
% the following (optional) fields/properties:
%  .Dir      - pyff source directory (default for win: D:\svn\pyff\src;
%              default for unix: ~/svn/pyff/src)
%  .Parport     - port number for parallel port. Default is dec2hex(BTB.Acq.IoAddr),
%              if BTB.Acq.IoAddr exists, otherwise [].
%  .A        - additional feedback directory (default [])
%  .Gui      - if 1, pyff gui is started along with the feedback controller (default 0)
%  .L        - loglevel (default 'debug')
%
% pyff('init',feedback): load and initialize pyff feedback
%  feedback  - strint containing the name of the feedback
%
% pyff('setdir',<OPT>): set directory and filename for EEG recording.
%  .TodayDir - directory for saving EEG data (default BTB.Tp.Dir)
%  .VpCode   - the code of the VP (Versuchsperson) (default BTB.Tp.Code)
%  .Basename  - the basename of the EEG file (default '')
% Special cases: If OPT=='' all entries are set to '', ensuring that no EEG is
% recorded. If OPT is not given at all, all entries are set to default.
%
% pyff('set',OPT): set feedback variables after the feedback has been
% initialized. OPT is a property/value list or struct of with
% fields/properties referring to names and values of variables. The type of
% the variable (string, integer, or float) is automatically checked.
% NOTE: Unlike Python, Matlab automatically turns integers into floats!
% For example, var1 = 1.0 and var1 = 1 both yield a float. This should not be a problem
% in most cases, but if you definitely need an integer, you should provide the
% type along with the value, e.g. pyff('set','var1',int16(1)).
% Alternatively, you can use the 'setint' command (below).
%
% pyff('setint',OPT): use this command to set feedback variables
% explicitly to integers. If you do this, each provided variable is
% cast to an integer.
%
% res = pyff('get',...): get the value of feedback variables.
% Give a list of variables to be inspected. res is a cell array containing
% the respective values of the variables. [wishful thinking, I think it's
% not possible]
%
% pyff('play'): start playing currently initialized feedback
% pyff('play', 'Basename', BASENAME, <PARAM>): start acquisition in BV Recorder, then start playing
%    currently initialized feedback. PARAM can be 'impedances', 0 to avoid
%    impedance measurement.
% pyff('stop'):  stop current feedback
% pyff('quit'):  quit current feedback (and stop acquisition in BV
%    Recorder).
%
% pyff('saveSettings', FILENAME): load or save the parameters of the feedback
%   to FILENAME. The appendix '.json' is automatically appended.
%   If FILENAME does not contain file separators '\' or '/',
%   BTB.Tp.Dir is prepended.
%
%
% General OPT:
% 'Os'       - operating system 'win' or 'unix' (usually you do not need to
%              set this since the script figures out your OS)
% 'ReplaceHtmlEntities' - replaces symbols like '<' in string to be set via
%                    XML by their according HTML (XML compatible) entities
%OUT:
% res        - final command as a string
%
% ISSUES:
% *command 'getvariables' (aka 'refresh') does not make variables
% appear in the GUI

% Matthias Treder 2010

global BTB
%% TODO: get rid of these (change bvr_* functions to more general ones)
global acquire_func general_port_fields
persistent ACQ_STARTED

props = {'Os',                    'win',                  '!CHAR(win unix)';
         'PyffDir'                BTB.PyffDir             'CHAR';
         'ReplaceHtmlEntities',   1,                      '!BOOL';
         'Parport',               dec2hex(BTB.Acq.IoAddr),'CHAR';
         'A',                     [],                     'CHAR';
         'Gui',                   0,                      'BOOL';
         'L',                     'debug',                'CHAR';
         'TodayDir',              BTB.Tp.Dir,             'CHAR';
         'VpCode',                BTB.Tp.Code,            'CHAR';
         'Basename',              '',                     'CHAR';
         'OutputProtocol',        [],                     'CHAR';
         'Host',                 'localhost',             'CHAR';
         'Port',                  12345,                  'INT';
         'Impedances'             1                       'BOOL';

};

if nargin==0,
  dat= props; return
end

misc_checkType(command,'CHAR(startup init set setint setdir play refresh stop quit saveSettings loadSettings)');

%% Case-dependent check of parameters
switch(command)

  case {'stop','quit'}
    nargchk(1,1,nargin);
    opt=[];
  case 'startup'
    opt = opt_proplistToStruct(varargin{:});

  case 'init'
    nargchk(2,2,nargin);
    feedback = varargin{1};
    opt=[];
    misc_checkType(feedback,'!CHAR');
    ACQ_STARTED= 0;

  case 'setdir'
    nargchk(2,inf,nargin);
    if nargin==2
      opt=[];
      opt.TodayDir = '';
      opt.VpCode = '';
    else
      opt = opt_proplistToStruct(varargin{:});
    end

  case {'set','setint'}
    if nargin==2 && isstruct(varargin{1})
      vars= varargin{1};
    else
      vars = opt_proplistToStruct(varargin{:});
    end
    opt = [];
    if isfield(vars,'ReplaceHtmlEntities')
      warning 'Found variable ''ReplaceHtmlEntities'', assuming its a Pyff parameter, not a variable'
      opt.ReplaceHtmlEntities = vars.ReplaceHtmlEntities;
      vars = rmfield(vars,'ReplaceHtmlEntities');
    end

  case 'play'
    nargchk(1,5,nargin);
    opt = opt_proplistToStruct(varargin{:});

  case {'saveSettings','loadSettings'}
    nargchk(2,2,nargin);
    opt=[];
    filename = varargin{1};
    misc_checkType(filename,'!CHAR');
    settings_file= [filename '.json'];
    if ~any(ismember('/\', settings_file,'legacy')),
      settings_file= [BTB.Tp.Dir settings_file];
  %   % Avoid overwriting? - Maybe it is intended, so we don't.
  %    if strcmp(command,'saveSettings') && exists(settings_file, 'file'),
  %      new_str= datestr(now, 'yyyy-mm-dd_HH:MM:SS.FFF');
  %      settings_file= [settings_file, '_', now_str];
  %    end
    end
end

[opt,isdefault] = opt_setDefaults(opt, props);
opt_checkProplist(opt, props);

% Figure out os (unless it was set manually)
if isdefault.Os
  if isunix || ismac
    opt.Os = 'unix';
  else
    opt.Os = 'win';
  end
end

%% Execute command
res = [];
switch(command)

  case 'startup',

    if strcmp(opt.Os,'win')
      % get system path
      curr_path = getenv('PATH');
      % also sets the path back to the normal system variable. Matlab adds a
      % reference to itself to the beginning of the system path which
      % breaks PyQT.QtCore (possibly also other imports that require dll)
      comstr = ['set PATH=' curr_path '& cmd /C "cd ' opt.PyffDir ' & python FeedbackController.py'];
      if ~isempty(opt.A)
        opt.A= strrep(opt.A, '/', filesep);
      end
    elseif strcmp(opt.Os,'unix')
      comstr = ['xterm -e python ' opt.PyffDir  '/FeedbackController.py'];
%       opt.A= strrep(opt.A, '\', filesep); % probably unneccesary
    end

    if ~isempty(opt.Parport)
      comstr = [comstr ' --port=0x' num2str(opt.Parport)];
    end
    if ~isempty(opt.A)
      comstr = [comstr ' -a "' opt.A '"'];
    end
    if opt.Gui==0
      comstr = [comstr ' --nogui'];
    end
    if ~isempty(opt.L)
      comstr = [comstr ' -l ' opt.L];
    end
    if ~isempty(opt.OutputProtocol)
      comstr = [comstr ' --protocol ' opt.OutputProtocol];
    end

    if strcmp(opt.Os,'win')
      comstr = [comstr '" &'];
    elseif strcmp(opt.Os,'unix')
      comstr = [comstr ' &'];
    end
    system(comstr);
    res = comstr;
    pyff_sendUdp('init', opt.Host, opt.Port);
    general_port_fields.feedback_receiver= 'pyff';

  case 'init'
    pyff_sendUdp('interaction-signal', 's:_feedback', feedback,'command','sendinit');

  case 'setdir'
    pyff_sendUdp('interaction-signal', 's:BTB.Tp.Dir',opt.TodayDir, ...
      's:BTB.Tp.Code',opt.VpCode, 's:BASENAME',opt.Basename);

  case 'set'
    settings= {};
    fn= fieldnames(vars);
    for ii= 1:numel(fn)
      val = vars.(fn{ii});
      % Take value or (if it is a cell) the value of its first element
      if isempty(val) && ~ischar(val)
        typ= '';
      elseif ischar(val) || (iscell(val) && ischar(val{1})) || (iscell(val) && iscell(val{1}) && numel(val{1}) && ischar(val{1}{1}))
        % string or nested string
        typ= 's:';
        if opt.ReplaceHtmlEntities
          val = ReplaceHtmlEntities(val);
        end
      elseif isinteger(val) || (iscell(val) && isinteger(val{1}))
        typ= 'i:';
      elseif islogical(val)
        typ= 'b:';
        if val
          val= 'True';
        else
          val= 'False';
        end
      else
        typ= '';
      end
      settings= cat(2, settings, {[typ fn{ii}], val});
    end
    pyff_sendUdp('interaction-signal', settings{:});

  case 'setint'
    settings= {};
    fn= fieldnames(vars);
    typ= 'i:';
    for ii= 1:numel(fn)
      settings= cat(2, settings, {[typ fn{ii}], vars.(fn{ii})});
    end
    pyff_sendUdp('interaction-signal', settings{:});

  case 'play'
    if isempty(varargin),
      ACQ_STARTED= 0;
    else
      ACQ_STARTED= 1;
      bvr_startrecording(varargin{2}, 'AppendTpCode',1, varargin{3:end});
      pause(0.01);
    end
    pyff_sendUdp('interaction-signal', 'command', 'play');

  case 'stop'
    pyff_sendUdp('interaction-signal', 'command', 'stop');

  case 'quit'
    pyff_sendUdp('interaction-signal', 'command', 'quit');
%     if strcmp(func2str(acquire_func), 'acquire_bv') && ACQ_STARTED,
    if ACQ_STARTED
       bvr_sendcommand('stoprecording');
       ACQ_STARTED= 0;
    end

  case 'loadSettings'
    pyff_sendUdp('interaction-signal', 'loadvariables', settings_file);

  case 'saveSettings'
    pyff_sendUdp('interaction-signal', 'savevariables', settings_file);

end

%% HELP FUNCTIONS
function str = ReplaceHtmlEntities(str,direction)

  % REPLACE_HTML_ENTITIES -  replaces special symbols such as '<' by their
  %             according HTML entities for correct display in HTML or XML.
  %             Can also perform the backward substitution (replacing HTML
  %             entities by symbols).
  %
  %Usage:
  % str = pyff(str,<direction>)
  %
  %IN:
  % str - input string or cell array of strings
  % direction - direction of substitution, 'forward' (default) replaces
  %             symbols by HTML entities, 'backward' does the opposite
  %
  %OUT:
  % str - string with special symbols replaced by HTML entities
  %
  %EXAMPLE:
  % replace_html_entities('If A < B then C')

  % Matthias Treder 2010

  if nargin<2, direction = 'forward'; end

  % TO DO ---- REPLACE literal special letters by their ASCII code !!!

  % Specify symbols (ie their ASCII code) and their corresponding entities in matched order
  source = {'´'            'ä'      'Ä'      '<'    '>' ...
    'ö'      'Ö'     };
  target = {'´' 'ä' 'Ä' '<' '>' ...
    'ö' 'Ö'};
%     '^'   'ˆ'   % <- this is not necessary?

  % For backward substitution switch source and target
  if strcmp(direction,'backward')
    dummy = source;
    source = target;
    target = dummy;
  end

  % Replace
  if ischar(str)
    for kk=1:numel(source)
        str = strrep(str,source{kk},target{kk});
    end
  elseif iscell(str)
    for jj=1:numel(str)
      str{jj} = ReplaceHtmlEntities(str{jj});
    end
  end
end

end


function test_bug1427

% MEM 3gb
% WALLTIME 00:10:00
% DEPENDENCY dataset2files ft_read_header ft_read_data

% the Long64ChannelWithEvents fails because it consists of muliple segments
% filepath = dccnpath('/home/common/matlab/fieldtrip/data/test/bug1427/Long64ChannelWithEvents.mff');

filepath = dccnpath('/home/common/matlab/fieldtrip/data/test/bug1427/NS500Sine6Hz.mff');

filename = dir(filepath);
filename = {filename(~[filename.isdir]).name};

% determine the type for any of the files
typ = cell(size(filename));
for i=1:length(filename)
  typ{i} = ft_filetype(fullfile(filepath, filename{i}));
end

sel = find(strcmp(typ, 'unknown'));
if ~isempty(sel)
  error('the filetype detection failed on %s',  fullfile(filepath, filename{sel(1)}))
end

% read the header for any of the files
hdr = cell(size(filename));
for i=1:length(filename)
  disp(filename{i})
  hdr{i} = ft_read_header(fullfile(filepath, filename{i}));
end

% read some data for any of the files
dat = cell(size(filename));
for i=1:length(filename)
  disp(filename{i})
  dat{i} = ft_read_data(fullfile(filepath, filename{i}), 'begsample', 1, 'endsample', 1000);
end


function face = meshFace(faces, index)
%MESHFACE Return the vertex indices of a face in a mesh
%
%   FACE = meshFace(FACES, INDEX)
%   Return the vertex indices of the i-th face in the face array. This is
%   mainly an utilitary function that manages faces stored either as int
%   array (when all faces have same number of sides) or cell array (when
%   faces may have different number of edges).
%
%   Example
%   meshFace
%
%   See also
%
%
% ------
% Author: David Legland
% e-mail: david.legland@grignon.inra.fr
% Created: 2010-10-06,    using Matlab 7.9.0.529 (R2009b)
% Copyright 2010 INRA - Cepia Software Platform.

% process mesh given as structure
if isstruct(faces)
    if isfield(faces, 'faces')
        faces = faces.faces;
    else
        error('Mesh structure should contains a field ''faces''');
    end
end

% switch between numeric or cell array
if isnumeric(faces)
    face = faces(index, :);
else
    face = faces{index};
end


function [item, inputs] = fillvals(item, inputs, infcn)

% function [item, inputs] = fillvals(item, inputs, infcn)
% If ~all_set_item, try to set item.val{1} to inputs{1}. Validity checks
% are performed through subsasgn. If inputs{1} is not suitable for this
% item, it is discarded. If infcn is a function handle,
% [val sts] = infcn(item)
% will be called to obtain a value for this item. This call will be
% repeated until either val can be assigned to item or sts is true. val
% should be a cell array with 1 item and val{1} the value that is to be
% assigned to item.val{1}.
% This function is suitable for all cfg_leaf items.
%
% This code is part of a batch job configuration system for MATLAB. See
%      help matlabbatch
% for a general overview.
%_______________________________________________________________________
% Copyright (C) 2007 Freiburg Brain Imaging

% Volkmar Glauche
% $Id: fillvals.m 5678 2013-10-11 14:58:04Z volkmar $

rev = '$Rev: 5678 $'; %#ok

if ~all_set_item(item)
    if ~isempty(inputs)
        item = setval(item, inputs{1}, false);
        inputs = inputs(2:end);
    end;
    if ~all_set_item(item) && ~isempty(infcn) && subsasgn_check_funhandle(infcn)
        sts = false;
        while ~sts && ~all_set_item(item)
            [val, sts] = feval(infcn, item);
            if sts
                item = setval(item, val, false);
            end;
        end;
    end;
end;


%% SO3Fun


function varargout = wave_clus_aux2(varargin)
% WAVE_CLUS_AUX1 M-file for wave_clus_aux.fig
%      WAVE_CLUS_AUX, by itself, creates a new WAVE_CLUS_AUX or raises the existing
%      singleton*.
%
%      H = WAVE_CLUS_AUX returns the handle to a new WAVE_CLUS_AUX or the handle to
%      the existing singleton*.
%
%      WAVE_CLUS_AUX('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in WAVE_CLUS_AUX.M with the given input arguments.
%
%      WAVE_CLUS_AUX('Property','Value',...) creates a new WAVE_CLUS_AUX or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before wave_clus_aux_OpeningFunction gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to wave_clus_aux_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help wave_clus_aux

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @wave_clus_aux2_OpeningFcn, ...
                   'gui_OutputFcn',  @wave_clus_aux2_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && isstr(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before wave_clus_aux is made visible.
function wave_clus_aux2_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to wave_clus_aux (see VARARGIN)

% Choose default command line output for wave_clus_aux
handles.output = hObject;

set(handles.isi14_accept_button,'value',1);
set(handles.isi15_accept_button,'value',1);
set(handles.isi16_accept_button,'value',1);
set(handles.isi17_accept_button,'value',1);
set(handles.isi18_accept_button,'value',1);
set(handles.fix14_button,'value',0);
set(handles.fix15_button,'value',0);
set(handles.fix16_button,'value',0);
set(handles.fix17_button,'value',0);
set(handles.fix18_button,'value',0);

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes wave_clus_aux wait for user response (see UIRESUME)
% uiwait(handles.wave_clus_aux);

% --- Outputs from this function are returned to the command line.
function varargout = wave_clus_aux2_OutputFcn(hObject, eventdata, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;

h_figs=get(0,'children');
h_fig = findobj(h_figs,'tag','wave_clus_figure');
USER_DATA = get(h_fig,'UserData');
par = USER_DATA{1};

for i = 14:18
	si = num2str(i);
	set(eval(['handles.isi' si '_accept_button']),'value',1);
	set(eval(['handles.isi' si '_reject_button']),'value',0);
	set(eval(['handles.fix' si '_button']),'value',0);

	eval(['set(handles.isi' si '_nbins,''string'',par.nbins' si ');']);
	eval(['set(handles.isi' si '_bin_step,''string'',par.bin_step' si ');']);
end

plot_spikes_aux(handles,USER_DATA,2)