function varargout = process_ssp2( varargin )
% PROCESS_SSP2: Artifact rejection for a group of recordings file (calculates SSP from FilesA and applies them to FilesB)
%
% USAGE:  OutputFiles = process_ssp2('Run', sProcess, sInputsA, sInputsB)
%                proj = process_ssp2('Compute', F, chanmask)
%           Projector = process_ssp2('BuildProjector', ListSsp, ProjStatus)
%           Projector = process_ssp2('ConvertOldFormat', OldProj)

% @=============================================================================
% 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, 2011-2022
%          Elizabeth Bock, 2011-2018

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription()
    % Description the process
    sProcess.Comment     = 'SSP: Generic';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = 'Artifacts';
    sProcess.Index       = 302;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/ArtifactsSsp?highlight=%28Process2%29#Troubleshooting';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'raw', 'data'};
    sProcess.OutputTypes = {'raw', 'raw'};
    sProcess.nInputs     = 2;
    sProcess.nMinFiles   = 1;
    sProcess.isSeparator = 0;

    % Notice
    sProcess.options.label1.Comment = 'Files A = Artifacts samples (raw or epoched)
 Files B = Files to clean (raw)

';
    sProcess.options.label1.Type    = 'label';
    % Time window
    sProcess.options.timewindow.Comment   = 'Time window: ';
    sProcess.options.timewindow.Type      = 'timewindow';
    sProcess.options.timewindow.Value     = [];
    sProcess.options.timewindow.InputTypes = {'raw'};
    % Event name
    sProcess.options.eventname.Comment = 'Event name (empty=continuous): ';
    sProcess.options.eventname.Type    = 'text';
    sProcess.options.eventname.Value   = 'blink';
    sProcess.options.eventname.InputTypes = {'raw'};
    % Event window
    sProcess.options.eventtime.Comment = 'Event window (ignore if no event): ';
    sProcess.options.eventtime.Type    = 'range';
    sProcess.options.eventtime.Value   = {[-.200, .200], 'ms', []};
    sProcess.options.eventtime.InputTypes = {'raw'};
    % Filter
    sProcess.options.bandpass.Comment = 'Frequency band: ';
    sProcess.options.bandpass.Type    = 'range';
    sProcess.options.bandpass.Value   = {[1.5, 15], 'Hz', 2};
    %  Number of components
    sProcess.options.nicacomp.Comment = 'Number of ICA components (0=default): ';
    sProcess.options.nicacomp.Type    = 'value';
    sProcess.options.nicacomp.Value   = {0, '', 0};
    sProcess.options.nicacomp.Hidden  = 1;
    % Sensor types
    sProcess.options.sensortypes.Comment = 'Sensor types or names (empty=all): ';
    sProcess.options.sensortypes.Type    = 'text';
    sProcess.options.sensortypes.Value   = 'MEG, EEG';
    % Use existing SSPs
    sProcess.options.usessp.Comment = 'Compute using existing SSP/ICA projectors';
    sProcess.options.usessp.Type    = 'checkbox';
    sProcess.options.usessp.Value   = 1;
    sProcess.options.usessp.InputTypes = {'raw'};
    % Ignore bad segments
    sProcess.options.ignorebad.Comment = 'Ignore bad segments';
    sProcess.options.ignorebad.Type    = 'checkbox';
    sProcess.options.ignorebad.Value   = 1;
    sProcess.options.ignorebad.InputTypes = {'raw'};
    sProcess.options.ignorebad.Hidden  = 1;
    % Save ERP
    sProcess.options.saveerp.Comment = 'Save averaged artifact in the database';
    sProcess.options.saveerp.Type    = 'checkbox';
    sProcess.options.saveerp.Value   = 0;
    % Method: Average or PCA
    sProcess.options.label2.Comment = '
Method to calculate the projectors:';
    sProcess.options.label2.Type    = 'label';
    sProcess.options.method.Comment = {'PCA: One component per sensor', 'Average: One component only'};
    sProcess.options.method.Type    = 'radio';
    sProcess.options.method.Value   = 1;
    % Examples: EOG, ECG
    sProcess.options.example.Comment = ['
Examples:
' ...
                                        '   - EOG: [-200,+200] ms, [1.5-15] Hz
' ...
                                        '   - ECG: [-40,+40] ms, [13-40] Hz

'];
    sProcess.options.example.Type    = 'label';
    % Default selection of components
    sProcess.options.select.Comment = 'Selected components:';
    sProcess.options.select.Type    = 'value';
    sProcess.options.select.Value   = {1, 'list', 0};
    sProcess.options.select.Hidden  = 1;
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess)
    if isfield(sProcess.options, 'eventname') && ~isempty(sProcess.options.eventname.Value)
        Comment = ['SSP: ' sProcess.options.eventname.Value];
    else
        Comment = 'SSP';
    end
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputsA, sInputsB)
    OutputFiles = {};
    strOptions = '=> ';

    % ===== GET OPTIONS =====
    % Get time window
    if isfield(sProcess.options, 'timewindow') && isfield(sProcess.options.timewindow, 'Value') && iscell(sProcess.options.timewindow.Value) && ~isempty(sProcess.options.timewindow.Value)
        TimeWindow = sProcess.options.timewindow.Value{1};
    else
        TimeWindow = [];
    end
    % Get options values
    if isfield(sProcess.options, 'eventname') && isfield(sProcess.options.eventname, 'Value') && ~isempty(sProcess.options.eventname.Value)
        % Event name
        evtName = strtrim(sProcess.options.eventname.Value);
        if isempty(evtName)
            bst_report('Warning', sProcess, [], 'Event name is not specified: starting from the beginning of the file.');
        end
        strOptions = [strOptions, 'Event=' evtName ', '];
    else
        evtName = [];
    end
    % Event time window (only used if event is a point in time, not a window)
    if isfield(sProcess.options, 'eventtime') && isfield(sProcess.options.eventtime, 'Value') && ~isempty(sProcess.options.eventtime.Value) && ~isempty(sProcess.options.eventtime.Value{1})
        evtTimeWindow = sProcess.options.eventtime.Value{1};
        strOptions = [strOptions, sprintf('Epoch=[%1.3f,%1.3f]s, ', evtTimeWindow(1), evtTimeWindow(2))];
    else
        evtTimeWindow = [];
    end
    % Get downsampling value
    if isfield(sProcess.options, 'resample') && isfield(sProcess.options.resample, 'Value') && ~isempty(sProcess.options.resample.Value)
        resample = sProcess.options.resample.Value{1};
        strOptions = [strOptions, 'resample=' num2str(resample) ', '];
    else
        resample = 0;
    end
    % Get bandpass range
    BandPass = sProcess.options.bandpass.Value{1};
    if isempty(BandPass) || all(BandPass <= 0)
        bst_report('Info', sProcess, sInputsA, 'The bandpass filter was disabled.');
        BandPass = [];
        strOptions = [strOptions, 'NoBandPass, '];
    else
        strOptions = [strOptions, 'BandPass=[' num2str(BandPass(1)) ',' num2str(BandPass(2)), ']Hz, '];
    end
    % WARNING: Channel file should be the same for all the files
    if ~all(strcmpi(sInputsA(1).ChannelFile, {sInputsA.ChannelFile, sInputsB.ChannelFile}))
        bst_report('Warning', sProcess, sInputsA, [...
            'All the files should relate to the same channel file. ' ...
            'If the head position is different between two files, the values recorded for one channel do not mean ' ...
            'the same thing, therefore the calculation or the application of spatial filters (SSP) cannot be reliable.' 10 ...
            'If you decide to use the results of this process, you should understand this problem and check ' ...
            'your analysis for possible errors.']);
    end
    % Check input file types
    isRawA = strcmpi(sInputsA(1).FileType, 'raw');
    isRawB = strcmpi(sInputsB(1).FileType, 'raw');
    if ~isRawB
        bst_report('Error', sProcess, sInputsB, ['You can apply the calculated projectors only to a continuous file (Link to raw file).' 10 'Files B cannot contain any imported data blocks.']);
        return;
    end
    % Use existing SSPs
    if isfield(sProcess.options, 'usessp') && isfield(sProcess.options.usessp, 'Value') && ~isempty(sProcess.options.usessp.Value)
        UseSsp = sProcess.options.usessp.Value;
    else
        UseSsp = 0;
    end
    strOptions = [strOptions, 'UseSsp=' num2str(UseSsp) ', '];
    % Force selection of specific components
    if isfield(sProcess.options, 'select') && isfield(sProcess.options.select, 'Value') && ~isempty(sProcess.options.select.Value)
        if iscell(sProcess.options.select.Value)
            SelectComp = sProcess.options.select.Value{1};
        elseif isnumeric(sProcess.options.select.Value)
            SelectComp = sProcess.options.select.Value;
        else
            error('Invalid value for option "select".');
        end
    else
        SelectComp = [];
    end
    % Force selection of specific components
    if isfield(sProcess.options, 'nicacomp') && isfield(sProcess.options.nicacomp, 'Value') && ~isempty(sProcess.options.nicacomp.Value)
        nIcaComp = sProcess.options.nicacomp.Value{1};
        strOptions = [strOptions, 'nIcaComp=' num2str(nIcaComp) ', '];
    else
        nIcaComp = 0;
    end
    % Find components correlated to reference signals
    if isfield(sProcess.options, 'icasort')
        icaSort = sProcess.options.icasort.Value;
    else
        icaSort = [];
    end
    % Ignore bad segments
    if panel_record('IsEventBad', evtName)
        % If the event name contains the tag "bad", we need to include the bad segments
        bst_report('Info', sProcess, sInputsA, 'The selected event contains the tag "bad": including all the bad segments...');
        isIgnoreBad = 0;
    elseif isfield(sProcess.options, 'ignorebad') && isfield(sProcess.options.ignorebad, 'Value') && ~isempty(sProcess.options.ignorebad.Value)
        isIgnoreBad = sProcess.options.ignorebad.Value;
    else
        isIgnoreBad = 1;
    end
    strOptions = [strOptions, 'IgnoreBadSegments=' num2str(isIgnoreBad) 10];
    % Save ERP
    if isfield(sProcess.options, 'saveerp') && isfield(sProcess.options.saveerp, 'Value') && ~isempty(sProcess.options.saveerp.Value)
        SaveErp = sProcess.options.saveerp.Value;
    else
        SaveErp = 0;
    end
    % Method
    if isfield(sProcess.options, 'method') && isfield(sProcess.options.method, 'Value') && ~isempty(sProcess.options.method.Value)
        if ischar(sProcess.options.method.Value)
            Method = sProcess.options.method.Value;
        else
            switch (sProcess.options.method.Value)
                case 1,   Method = 'SSP_pca';
                case 2,   Method = 'SSP_mean';
                otherwise, error('Invalid method.');
            end
        end
    else
        Method = 'SSP_pca';
    end
    strOptions = [strOptions, '=> Method=' Method ', '];
    isICA = ~isempty(strfind(Method, 'ICA'));
    % Check method/inputs incompabilities
    if strcmpi(Method, 'SSP_mean') && isRawA && isempty(evtName)
        bst_report('Error', sProcess, sInputsA, 'The method SSP_mean cannot be applied to continuous recordings.');
        return;
    end

    % ===== COMPUTE BANDPASS FILTER =====
    % Get the time vector for the first file
    DataMat = in_bst_data(sInputsA(1).FileName, 'Time');
    sfreq = 1 ./ (DataMat.Time(2) - DataMat.Time(1));
    % Design band-pass filter
    if ~isempty(BandPass) && ~all(BandPass == 0)
        % If we need to resample the recordings
        if (resample > 0)
            filterFreq = resample;
        % Use the original sampling frequency
        else
            filterFreq = sfreq;
        end
        % Design the filter
        isMirror = 0;
        [tmp, FiltSpec] = process_bandpass('Compute', [], filterFreq, BandPass(1), BandPass(2), 'bst-hfilter-2019', isMirror);
        % Estimate transient period (before and after resampling)
        nTransientLoad    = round(FiltSpec.transient * sfreq);
        nTransientDiscard = round(FiltSpec.transient * filterFreq);
        % Show warning when computing epoched files
        if ~isRawA
            bst_report('Warning', sProcess, sInputsA, sprintf('Removing %1.3fs at the beginning and the end of each input for filtering.', FiltSpec.transient));
        end
    else
        nTransientLoad    = [];
        nTransientDiscard = [];
    end

    % ===== READ ARTIFACTS (FILES A) =====
    % Initialize progress bar
    bst_progress('text', 'Reading recordings...');
    progressPos = bst_progress('get');
    % Initialize concatenated data matrix
    F = {};
    Fref = {}; % for holding reference signals (EOG, ECG)
    iBad = [];
    iTimeZero = [];
    nSamples = 0;
    nMaxSamples = 100000;
    projChan = [];
    projChanNames = [];
    % Read together all the files in group A
    for iFile = 1:length(sInputsA)
        % ===== GET CHANNEL FILE =====
        if isempty(projChan) || ~isequal(projChan, sInputsA(iFile).ChannelFile)
            % Load channel file
            ChannelMat = in_bst_channel(sInputsA(iFile).ChannelFile);
            % Check for different channel structures in FilesA
            if isempty(projChanNames)
                projChanNames = {ChannelMat.Channel.Name};
            elseif ~isequal(projChanNames, {ChannelMat.Channel.Name})
                bst_report('Error', sProcess, sInputsA(iFile), 'All the files A must have the exact same list of channels.');
                return;
            end
            % Get channels to process
            iChannels = channel_find(ChannelMat.Channel, sProcess.options.sensortypes.Value);
            if isempty(iChannels)
                bst_report('Error', sProcess, sInputsA(iFile), 'No channels to process.');
                return;
            end
            % Warning if more than one channel type
            allTypes = unique({ChannelMat.Channel(iChannels).Type});
            if (length(allTypes) > 1) && ~isequal(allTypes, {'MEG GRAD', 'MEG MAG'})
                for iType = 1:length(allTypes)-1
                    allTypes{iType} = [allTypes{iType}, ', '];
                end
                bst_report('Warning', sProcess, sInputsA, ...
                    ['Mixing different channel types to compute the projector: ' [allTypes{:}], '.' 10 ...
                     'You should compute projectors separately for each sensor type.']);
            end
            if ~isempty(icaSort)
                iRef = channel_find(ChannelMat.Channel, icaSort);
                if isempty(iRef)
                    bst_report('Error', sProcess, sInputsA(iFile), sprintf('Channels %s not found.', icaSort));
                end
                iSensors = iChannels;
                iChannels = [iSensors iRef];
            end
        end

        % ===== GET DATA =====
        % === RAW: EVENTS ===
        if isRawA && ~isempty(evtName)
            % Load the raw file descriptor
            DataMat = in_bst_data(sInputsA(iFile).FileName, 'F', 'ChannelFlag');
            sFile = DataMat.F;
            sfreq_file = sFile.prop.sfreq;
            if isempty(sFile.events)
                bst_report('Error', sProcess, sInputsA(iFile), 'No events in the input file.');
                return;
            end
            % Get list of bad segments in file
            [badSeg, badEpochs] = panel_record('GetBadSegments', sFile);
            % Get the event to process
            events = sFile.events;
            iEvt = find(strcmpi({events.label}, evtName));
            nOcc = size(events(iEvt).times, 2);
            if isempty(iEvt) || (nOcc == 0)
                bst_report('Error', sProcess, sInputsA(iFile), ['Event type "' evtName '" not found, or has no occurrence.']);
                return;
            end
            % Extended / simple event
            isExtended = (size(events(iEvt).times, 1) == 2);
            % Simple events: get the samples to read around each event
            if ~isExtended
                evtSmpRange = round(evtTimeWindow .* sFile.prop.sfreq);
            % Extended: cannot work with "mean" option
            elseif strcmpi(Method, 'SSP_mean')
                bst_report('Error', sProcess, sInputsA, 'Method "SSP_mean" cannot be used for extended events.');
                return;
            % Extended: read the full block
            else
                evtSmpRange = [0 0];
            end
            % Add transients for bandpass
            if ~isempty(nTransientLoad)
                evtSmpRange = evtSmpRange + [-1 1] .* nTransientLoad;
            end
            % Reading options
            % NOTE: FORCE READING CLEAN DATA (Baseline correction + CTF compensators + Previous SSP)
            ImportOptions = db_template('ImportOptions');
            ImportOptions.ImportMode      = 'Event';
            ImportOptions.EventsTimeRange = evtSmpRange ./ sFile.prop.sfreq;
            ImportOptions.UseCtfComp      = 1;
            ImportOptions.UseSsp          = UseSsp;    % ADDED OPTION (FT: 27-Jun-2014) - Before we were always applying the previous SSPs
            ImportOptions.RemoveBaseline  = 'all';
            ImportOptions.DisplayMessages = 0;
            nInfoBad = 0;
            % Loop on each occurrence of the event
            for iOcc = 1:nOcc
                % Progress bar
                bst_progress('set', progressPos + round(iOcc / nOcc * 50));
                % Simple event: read a time window around the marker
                if ~isExtended
                    SamplesBounds = round(events(iEvt).times(1,iOcc) .* sFile.prop.sfreq) + evtSmpRange;
                % Extended event: read the full event
                else
                    SamplesBounds = round(events(iEvt).times(:,iOcc)' .* sFile.prop.sfreq) + evtSmpRange;
                end
                % Check that this epoch is within the segment of file to consider
                TimeBounds = SamplesBounds ./ sFile.prop.sfreq;
                if ~isempty(TimeWindow) && ((TimeBounds(1) < TimeWindow(1)) || (TimeBounds(2) > TimeWindow(2)))
                    continue;
                end
                % Check if this segment is outside of ALL the bad segments (either entirely before or entirely after)
                if isIgnoreBad && ~isempty(badSeg) && (~all((SamplesBounds(2) < badSeg(1,:)) | (SamplesBounds(1) > badSeg(2,:))))
                    nInfoBad = nInfoBad + 1;
                    continue;
                % Check if this this segment is  outside of the file bounds
                elseif (TimeBounds(1) < sFile.prop.times(1)) || (TimeBounds(2) > sFile.prop.times(2))
                    bst_report('Info', sProcess, sInputsA(iFile), sprintf('Event %s #%d is too close to the beginning or end of the file: ignored...', evtName, iOcc));
                    continue;
                end
                % Read block
                [Fevt, TimeVector] = in_fread(sFile, ChannelMat, events(iEvt).epochs(iOcc), SamplesBounds, [], ImportOptions);
                % SSP_mean: Check that we can get a time zero
                if strcmpi(Method, 'SSP_mean')
                    if ((TimeVector(1) > 0) || (TimeVector(end) < 0))
                        bst_report('Warning', sProcess, sInputsA(iFile), ['File #"' num2str(iFile) '" does not have a time t=0, it cannot be used for the method "mean".']);
                        continue;
                    end
                    % Get the sample where t=0
                    iTimeZero{end+1} = bst_closest(0, TimeVector);
                end
                % Concatenate to final matrix
                F{end+1} = Fevt;
                nSamples = nSamples + size(Fevt,2);
                % Check whether we read already all the samples we need
                if ~isICA && (nSamples >= nMaxSamples)
                    bst_report('Info', sProcess, sInputsA(iFile), sprintf('Reached the maximum number of samples at event %d / %d', iOcc, nOcc));
                    break;
                end
            end
            % Display message with number of events ignored
            if (nInfoBad > 0)
                bst_report('Info', sProcess, sInputsA(iFile), sprintf('Event %s: %d/%d events were in bad segments and ignored.', evtName, nInfoBad, nOcc));
            end

        % === RAW: CONTINUOUS ===
        elseif isRawA && isempty(evtName)
            % Load the raw file descriptor
            DataMat = in_bst_data(sInputsA(iFile).FileName, 'F', 'ChannelFlag');
            sFile = DataMat.F;
            sfreq_file = sFile.prop.sfreq;
            % Options for LoadInputFile()
            LoadOptions.IgnoreBad      = isIgnoreBad;  % From raw files: ignore the bad segments
            LoadOptions.ProcessName    = func2str(sProcess.Function);
            LoadOptions.RemoveBaseline = 'all';
            LoadOptions.UseSsp         = UseSsp;
            % Add transients for bandpass filter
            if ~isempty(nTransientLoad) && ~isempty(TimeWindow)
                rawTime = TimeWindow + [-1 1] .* (nTransientLoad / sfreq_file);
            else
                rawTime = TimeWindow;
            end
            % Load input signals
            [sMat, nSignals, iRows] = bst_process('LoadInputFile', sInputsA(iFile).FileName, [], rawTime, LoadOptions);
            if isempty(sMat.Data)
                bst_report('Error', sProcess, [], 'No data could be read from the input file.');
                return
            end
            % Use the loaded block
            F{end+1} = zeros(length(ChannelMat.Channel), size(sMat.Data,2));
            F{end}(iRows,:) = sMat.Data;
            TimeVector = sMat.Time;

        % === IMPORTED DATA ===
        else
            % Progress bar
            bst_progress('set', progressPos + round(iFile / length(sInputsA) * 50));
            % Load file
            DataMat = in_bst_data(sInputsA(iFile).FileName, 'F', 'ChannelFlag', 'Time');
            % Sampling frquency
            TimeVector = DataMat.Time;
            sfreq_file = 1 ./ (TimeVector(2) - TimeVector(1));
            % SSP_mean: Check that we can get a time zero
            if strcmpi(Method, 'SSP_mean')
                if ((TimeVector(1) > 0) || (TimeVector(end) < 0))
                    bst_report('Warning', sProcess, sInputsA(iFile), ['File #"' num2str(iFile) '" does not have a time t=0, it cannot be used for the method "mean".']);
                    continue;
                end
                % Get the sample where t=0
                iTimeZero{end+1} = bst_closest(0, TimeVector);
            end
            % Concatenate to final matrix
            F{end+1} = DataMat.F;
            nSamples = nSamples + size(DataMat.F,2);
            % Check whether we read already all the samples we need
            if ~isICA && (nSamples >= nMaxSamples)
                bst_report('Info', sProcess, sInputsA(iFile), sprintf('Reached the maximum number of samples at file %d / %d', iFile, length(sInputsA)));
                break;
            end
        end
        % Check that the sampling frequency is the same
        if (abs(sfreq_file - sfreq) > 1e-3)
            bst_report('Error', sProcess, sInputsA(iFile), 'Input files have different sampling rates.');
            break;
        end
        % Add bad channels in this file to the global list
        iBad = union(iBad, find(DataMat.ChannelFlag == -1));
    end
    % No data was read...
    if isempty(F)
        bst_report('Error', sProcess, sInputsA, 'No data could be read from the input file.');
        return;
    end

    % ===== DOWNSAMPLE =====
    % Downsample data to specified rate.
    if (resample > 0)
        bst_progress('text', 'Downsampling recordings...');
        bst_progress('set', progressPos + 25);
        for iFile = 1:length(F)
            [F{iFile}, TimeResample] = process_resample('Compute', F{iFile}, TimeVector, resample);
        end
        sfreq = resample;
        TimeVector = TimeResample;
    end

    % ===== COMPUTE AVERAGE =====
    % Compute the average of the data blocks (unfiltered)
    if SaveErp
        % Check that the size is the same for all the blocks
        if ~all(cellfun(@(c)isequal(size(c), size(F{1})), F))
            bst_report('Warning', sProcess, [], 'Input files have different sizes, cannot compute average');
            SaveErp = 0;
        else
            Favg = mean(cat(3, F{:}), 3);
        end
    end

    % ===== PROCESS DATA =====
    % Set the progress bar to 50%
    bst_progress('text', 'Processing recordings...');
    bst_progress('set', progressPos + 50);
    % Initializations for this loop
    Fzero = zeros(size(F{1},1), 1);
    nAvg = length(F);
    % Apply montages and filters
    for iBlock = 1:length(F)
        % Progress bar
        bst_progress('set', progressPos + 50 + round(iBlock / length(F) * 50));
        % Remove the bad channels from the matrix
        [iBad, iChanRemove] = intersect(iChannels, iBad);
        if ~isempty(iBad) && ~isempty(F{iBlock})
            iChannels(iChanRemove) = [];
        end
        % Filter recordings
        if ~isempty(BandPass) && ~all(BandPass == 0)
            F{iBlock}(iChannels,:) = process_bandpass('Compute', F{iBlock}(iChannels,:), sfreq, FiltSpec);
        end
        % Compute the average at time zero (filtered)
        if strcmpi(Method, 'SSP_mean')
            Fzero = Fzero + F{iBlock}(:,iTimeZero{iBlock});
        end
        % Filter recordings: Remove transients
        if ~isempty(BandPass) && ~all(BandPass == 0)
            F{iBlock} = F{iBlock}(:, (nTransientDiscard+1):(end-nTransientDiscard));
        end
        % Keep only the needed channels
        if ~isempty(icaSort)
            Fref{iBlock} = F{iBlock}(iRef,:);
            F{iBlock} = F{iBlock}(iSensors,:);
        else
            F{iBlock} = F{iBlock}(iChannels,:);
        end
    end
    % Comment
    nSamplesFinal = sum(cellfun(@(c)size(c,2), F));
    strOptions = [strOptions, 'Nsamples=' num2str(nSamplesFinal) ' from ' num2str(length(F)) ' blocks'];
    % Concatenate all the loaded data
    F = [F{:}];
    if ~isempty(icaSort)
        Fref = [Fref{:}];
        iChannels = iSensors;
    end
    % Error if nothing was loaded
    if isempty(F)
        bst_report('Error', sProcess, sInputsA, 'No data could be read from the input files.');
        return;
    elseif (size(F,2) < size(F,1))
        bst_report('Error', sProcess, sInputsA, 'Not enough data could be read from the input files.');
        return;
    elseif ~isICA && (size(F,1) < 32)
        bst_report('Warning', sProcess, sInputsA, ['You selected only ' num2str(size(F,1)) ' channels to compute the projectors. ' 10 ...
            'The SSP approach may not work correctly with a low number of sensors. ' ...
            'Make sure you selected correctly all the channels of data you have in your file.']);
    end
    % ICA: Check that the number of time samples is sufficient
    % Recommended number of time samples (from S Makeig):  Ntime/Nchan^2 >> 10
    nTime = size(F,2);
    if (nIcaComp > 0)
        nChan = nIcaComp;
    else
        nChan = size(F,1);
    end
    if isICA && (nTime / nChan^2 < 10)
        % Warning message
        strWarning = ['There is probably not enough data for a correct ICA decomposition.' 10 ...
            'Number time samples in input: '    num2str(nTime)      ' (' num2str(round(nTime / sfreq)) 's)' 10 ...
            'Recommend number of time samples:' num2str(10*nChan^2) ' (' num2str(round(10*nChan^2 / sfreq)) 's)'];
        % Add to the report
        bst_report('Warning', sProcess, [], strWarning);
        % Display on the console
        disp([10 'WARNING: ' strWarning]);
    end

    % ===== COMPUTE PROJECTORS =====
    Y = [];
    bst_progress('text', 'Computing projector...');
    switch (Method)

        % === SSP: PCA ===
        case 'SSP_pca'
            % === CHECK NUMBER OF SAMPLES ===
            % Minimum number of time samples required to estimate covariance
            nMinSmp = 10 * size(F,1);
            if (size(F,2) < nMinSmp)
                nTimePerBlock = length(TimeVector);
                nBlock = ceil(size(F,2) / nTimePerBlock);
                nBlockTotal = ceil(nMinSmp / nTimePerBlock);
                if isRawA && ~isempty(evtName)
                    errMsg = sprintf(' - Add %d events (Total: %d)', nBlockTotal - nBlock, nBlockTotal);
                    if ~isExtended
                        nAddTime = ceil((nMinSmp - size(F,2)) / nBlock / 2);
                        newTimeWin = round([evtSmpRange(1) - nAddTime, evtSmpRange(2) + nAddTime] ./ sFile.prop.sfreq .* 1000);
                        errMsg = sprintf([errMsg, 10, ' - Increase the time window around each event to [%d,%d] ms'], newTimeWin(1), newTimeWin(2));
                    end
                elseif isRawA && strcmpi(sFile.format, 'CTF') && length(sFile.epochs) > 1
                    errMsg = ' - Convert the input files to continuous.';
                else
                    errMsg = sprintf(' - Add %d files in the process list (Total: %d)', nBlockTotal - nBlock, nBlockTotal);
                end
                bst_report('Error', sProcess, sInputsA, ['Not enough time samples to compute projectors. You may:' 10 errMsg]);
                return;
            end

            % === COMPUTE PROJECTOR ===
            % Create channel mask matrix
            chanmask = zeros(length(ChannelMat.Channel), 1);
            chanmask(iChannels) = 1;
            % Call computation function
            proj = Compute(F, chanmask);
%             % Select the components with a singular value > threshold
%             if isempty(ForceSelect)
%                 singThresh = 0.12;
%                 proj.CompMask = double(proj.SingVal ./ sum(proj.SingVal) > singThresh);
%             % Force selection of specific components
%             else
%                 proj.CompMask(ForceSelect) = 1;
%             end
            % By default: first component selected
            if isempty(SelectComp)
                SelectComp = 1;
            end
            % Apply component selection (default: first component only)
            proj.CompMask(SelectComp) = 1;
            % Set the category as active if there is one selected component
            proj.Status = any(proj.CompMask);

        % === SSP: MEAN ===
        case 'SSP_mean'
            % Check number of samples
            if isempty(Fzero)
                bst_report('Error', sProcess, sInputsA, 'No data could be read from the input files.');
                return;
            else
                bst_report('Info', sProcess, sInputsA, sprintf('Computing projectors based on an average of %d events.', nAvg));
            end
            % Get the average at t=0
            Components = zeros(length(ChannelMat.Channel), 1);
            Components(iChannels) = Fzero(iChannels,:) ./ nAvg;
            % Normalize columns of the components
            Components = Components ./ sqrt(sum(Components .^2));
            % Build projector structure
            proj = db_template('projector');
            proj.Components = Components;
            proj.CompMask   = 1;
            proj.Status     = 1;
            proj.SingVal    = [];

        % === ICA: JADE ===
        case 'ICA_jade'
            bst_progress('text', 'Calling external function: jadeR()...');
            % Run decomposition
            if ~isempty(nIcaComp) && (nIcaComp ~= 0)
                W = jadeR(F, nIcaComp);
            else
                W = jadeR(F);
            end
            % Error handling
            if isempty(W)
                bst_report('Error', sProcess, sInputsA, 'Function "jadeR" did not return any results.');
                return;
            end

        % === INFOMAX ===
        case 'ICA_infomax'
            bst_progress('text', 'Calling external function: EEGLAB''s runica()...');
            % Remove the mean
            F = bst_bsxfun(@minus, F, mean(F,2));
            % Run EEGLAB ICA function
            if ~isempty(nIcaComp) && (nIcaComp ~= 0)
                [icaweights, icasphere] = runica(F, 'pca', nIcaComp, 'lrate', 0.001, 'extended', 1, 'interupt', 'off');
            else
                [icaweights, icasphere] = runica(F, 'lrate', 0.001, 'extended', 1, 'interupt', 'off');
            end
            % Error handling
            if isempty(icaweights) || isempty(icasphere)
                bst_report('Error', sProcess, sInputsA, 'Function "runica" did not return any results.');
                return;
            end
            % Reconstruct unmixing matrix
            W = icaweights * icasphere;

        % === ICA: PICARD ===
        case 'ICA_picard'
            bst_progress('text', 'Calling external function: picard()...');
            % Install picard plugin
            [isInstalled, errMsg] = bst_plugin('Install', 'picard');
            if ~isInstalled
                error(errMsg);
            end
            % Run decomposition
            if ~isempty(nIcaComp) && (nIcaComp ~= 0)
                [Y,W] = picard(F, 'pca', nIcaComp);
            else
                [Y,W] = picard(F);
            end

        % === ICA: FASTICA ===
        case 'ICA_fastica'
            bst_progress('text', 'Calling external function: fastica()...');
            % Install fastica plugin
            [isInstalled, errMsg] = bst_plugin('Install', 'fastica');
            if ~isInstalled
                error(errMsg);
            end
            % Scale the values to higher values, so that the pcamat function doesn't complain for small eigenvalues
            F = F ./ mean(abs(F(:)));
            % Run decomposition
            if ~isempty(nIcaComp) && (nIcaComp ~= 0)
                [M,W] = fastica(F, 'numOfIC', nIcaComp);
            else
                [M,W] = fastica(F);
            end

        otherwise
            bst_report('Error', sProcess, sInputsA, ['Invalid method: "' Method '".']);
            return;
    end

    % Finish assembling ICA results
    if ismember(Method, {'ICA_jade', 'ICA_infomax', 'ICA_picard', 'ICA_fastica'})
        % Fill with the missing channels with zeros
        Wall = zeros(length(ChannelMat.Channel), size(W,1));
        Wall(iChannels,:) = W';
        % Build projector structure
        proj = db_template('projector');
        proj.Components = Wall;
        proj.CompMask   = zeros(size(Wall,2), 1);   % No component selected by default
        proj.Status     = 1;
        proj.SingVal    = 'ICA';
        % Apply component selection (if set explicitly)
        if ~isempty(SelectComp)
            proj.CompMask(SelectComp) = 1;
        end
        % Compute IC
        if isempty(Y)
            Y = W * F;
        end
        % Sort ICA components
        if ~isempty(icaSort)
            % By correlation with reference channel
            C = bst_corrn(Fref, Y);
            [corrs, iSort] = sort(max(abs(C),[],1), 'descend');
            proj.Components = proj.Components(:,iSort);
        elseif ismember(Method, {'ICA_picard', 'ICA_fastica'})
            % By explained variance
            if diff(size(W)) == 0
                M = inv(W);
            else
                M = pinv(W);
            end
            var = sum(M.^2, 1) .* sum(Y.^2, 2)';
            [var, iSort] = sort(var, 'descend');
            proj.Components = proj.Components(:,iSort);
        end
    end

    % Modality used in the end
    AllMod = unique({ChannelMat.Channel(iChannels).Type});
    strMod = '';
    for iMod = 1:length(AllMod)
        strMod = [strMod, AllMod{iMod} '+'];
    end
    strMod = strMod(1:end-1);
    % Comment
    if ~isempty(evtName)
        proj.Comment = [evtName ': ' Method ', ' strMod ', ' datestr(clock)];
    else
        proj.Comment = [Method ': ' strMod  ', ' datestr(clock)];
    end

    % ===== APPLY PROJECTORS (FILES B) =====
    bst_progress('text', 'Applying projector to the recordings...');
    % Get all the channel files
    ChannelFiles = unique({sInputsB.ChannelFile});
    % Apply projectors to all the files in input
    for iFile = 1:length(ChannelFiles)
        % Load destination channel file
        ChannelMatB = in_bst_channel(ChannelFiles{iFile});
        destChanNames = {ChannelMatB.Channel.Name};
        % If channel names in projector and destination files do not match at all: ERROR
        [commonNames,I,J] = intersect(projChanNames, destChanNames);
        if isempty(commonNames)
            bst_report('Error', sProcess, sInputsB(iFile), 'List of channels are too different in the source and destination files.');
            return;
        end
        % If the channels list is not the same: re-order them to match the destination channel file
        if ~isequal(projChanNames, destChanNames)
            fixComponents = zeros(length(destChanNames), size(proj.Components,2));
            fixComponents(J,:) = proj.Components(I,:);
            proj.Components = fixComponents;
            bst_report('Warning', sProcess, sInputsB(iFile), sprintf('List of channels differ in FilesA (%d) and FilesB (%d). The common channels (%d) have been re-ordered to match the channels in FilesB.', length(projChanNames), length(destChanNames), length(commonNames)));
        end
        % Add projector to channel file
        [newproj, errMsg] = import_ssp(ChannelFiles{iFile}, proj, 1, 1, strOptions);
        if ~isempty(errMsg)
            bst_report('Error', sProcess, sInputsB(iFile), errMsg);
            return;
        end
    end

    % ===== SAVE THE AVERAGE =====
    if SaveErp && ~isempty(Favg)
        % Divide by number of average
        ChannelFlag = ones(size(Favg,1), 1);
        ChannelFlag(iBad) = -1;
        % Remove transients
        if ~isempty(nTransientDiscard)
            Favg = Favg(:, (nTransientDiscard+1):(end-nTransientDiscard));
            TimeVector = TimeVector((nTransientDiscard+1):(end-nTransientDiscard));
        end

        % === BEFORE ===
        % Create new output structure
        sOutput = db_template('datamat');
        sOutput.F           = Favg;
        sOutput.Comment     = [proj.Comment ' (before)'];
        sOutput.ChannelFlag = ChannelFlag;
        sOutput.Time        = TimeVector;
        sOutput.DataType    = 'recordings';
        sOutput.Device      = 'ArtifactERP';
        sOutput.nAvg        = nAvg;
        sOutput.Leff        = nAvg;
        % Get output study
        [tmp, iOutputStudy] = bst_process('GetOutputStudy', sProcess, sInputsB);
        sOutputStudy = bst_get('Study', iOutputStudy);
        % Output filename
        OutputFileERP = bst_process('GetNewFilename', bst_fileparts(sOutputStudy.FileName), 'data_artifact_before');
        % Save file
        bst_save(OutputFileERP, sOutput, 'v6');
        % Add file to database structure
        db_add_data(iOutputStudy, OutputFileERP, sOutput);

        % === SSP COMPONENTS ===
        if ~isICA
            % Force the first component to be used (allow multiple components by default)
            projErp = proj;
            projErp.Status(1) = 1;
            %projErp.CompMask = 0 * projErp.CompMask;
            projErp.CompMask(1) = 1;
            % Build projector
            Projector = BuildProjector(projErp, 1);
            % Apply to average data
            sOutput.F       = Projector * Favg;
            sOutput.Comment = [proj.Comment ' (after)'];
            % Output filename
            OutputFileERP = bst_process('GetNewFilename', bst_fileparts(sOutputStudy.FileName), 'data_artifact_after');
            % Save file
            bst_save(OutputFileERP, sOutput, 'v6');
            % Add file to database structure
            db_add_data(iOutputStudy, OutputFileERP, sOutput);
        % === ICA COMPONENTS ===
        else
            % Create file structure
            sOutput = db_template('matrixmat');
            sOutput.Value       = proj.Components' * Favg;
            sOutput.Comment     = [proj.Comment ' (ICA)'];
            sOutput.Time        = TimeVector;
            sOutput.ChannelFlag = [];
            sOutput.nAvg        = nAvg;
            sOutput.Leff        = nAvg;
            % Description of the signals: IC*
            sOutput.Description = cell(size(proj.Components,1),1);
            for i = 1:size(proj.Components,1)
                sOutput.Description{i} = sprintf('IC%d', i);
            end
            % Output filename
            OutputFileIC = bst_process('GetNewFilename', bst_fileparts(sOutputStudy.FileName), 'matrix_artifact_ica');
            % Save file
            bst_save(OutputFileIC, sOutput, 'v6');
            % Add file to database structure
            db_add_data(iOutputStudy, OutputFileIC, sOutput);
        end
    end
    % Return all the input files
    OutputFiles = {sInputsB.FileName};
end

%% ===== COMPUTE PROJECTOR =====
function proj = Compute(F, chanmask)
    % SVD decomposition
    [U,S,V] = svd(F, 'econ');
    % Create projector structure
    proj = db_template('projector');
    % Keep all the dimensions
    nChannel = length(chanmask);
    nProj    = size(U,2);
    proj.Components = zeros(nChannel, nProj);
    proj.Components(chanmask == 1,:) = U;
    % Other fields
    proj.SingVal  = diag(S)';
    proj.CompMask = zeros(1,nProj);
    proj.Status   = 1;
end

%% ===== BUILD PROJECTOR ====
% Combine all the projectors in decomposed form to create a [nChan x nChan] matrix
%
% USAGE: Projector = process_ssp2('BuildProjector', ListProj, ProjStatus)
%
% INPUT:
%    - ListProj   : Array of db_template('projector')
%    - ProjStatus : List of the projector status to include
%                   0 = not applied, not used
%                   1 = have to be used, but still have to be applied on the fly
%                   2 = already applied, saved in the file, not revertible
% OUTPUT:
%    - Projector  : [nChannels x nChannels] matrix, projector in the condensed form (I-UUt)
%
% COMMENTS:
%    There are 5 categories of projectors:
%    - SSP_pca:   CompMask=[Ncomp x 1],   SingVal=[Ncomp x 1],   Components=[Nchan x Ncomp]=U
%    - SSP_mean:  CompMask=1,             SingVal=[],            Components=[Nchan x 1]=U
%    - ICA:       CompMask=[Ncomp x 1],   SingVal='ICA',         Components=[Nchan x Ncomp]=W'
%    - REF:       CompMask=[],            SingVal='REF',         Components=[Nchan x Ncomp]=Wmontage
%    - Other:     CompMask=[],            SingVal=[],            Components=[Nchan x Nchan]=Projector=I-UUt
%
%    Description of the notations used here:
%    - W: Unmixing matrix  [Nelectrodes x Ncomponents]
%    - Winv = pinv(W) = [Ncomponents x Nelectrodes]
%    - In EEGLAB:  W = icaweights * icasphere;
%    - Activations_IC = W * Data
%    - CleanData = Winv(:,iComp) * Activations(iComp,:)
%                = Winv(:,iComp) * W(iComp,:) * Data
%
function Projector = BuildProjector(ListProj, ProjStatus) %#ok<*DEFNU>
    % Call on an old form of Projector (I-UUt)
    if ~isstruct(ListProj)
        Projector = ListProj;
        return
    end
    % Initialize matrices
    nChannel = size(ListProj(1).Components,1);

    % === SORT PROJECTORS ===
    % Find projectors to group or remove
    iProjDel = [];
    iProjSsp = [];
    U = [];
    for i = 1:length(ListProj)
        % Is entry not selected: skip
        if ~ismember(ListProj(i).Status, ProjStatus) || (~isempty(ListProj(i).CompMask) && all(ListProj(i).CompMask == 0))
            iProjDel(end+1) = i;
        % New SSP: Stack selected vectors all together
        elseif ~isempty(ListProj(i).CompMask) && ~isequal(ListProj(i).SingVal, 'ICA') && ~isequal(ListProj(i).SingVal, 'REF')
            iProjSsp(end+1) = i;
            U = [U, ListProj(i).Components(:,ListProj(i).CompMask == 1)];
        end
    end

    % === ORTHOGONALIZE SSP VECTORS ===
    if ~isempty(U) && ~all(U(:) == 0)
        % Reorthogonalize the vectors
        [U,S,V] = svd(U,0);
        S = diag(S);
        % Enforce strict zero values (because Matlab SVD function can randomly return small values instead of zeros !!!)
        % If not, sometimes it adds small contributions of some channels that have nothing to do with the projectors (<1e-16).
        % If this added channel contains high values (eg. Stim channel in Volts) it can corrupt significantly the MEG values.
        iZero = find((abs(U) < 1e-15) & (U ~= 0));
        if ~isempty(iZero)
            U(iZero) = 0;
        end
        % Throw away the linearly dependent guys (threshold on singular values: 0.01 * the first one)
        iThresh = find(S < 0.01 * S(1),1);
        if ~isempty(iThresh)
            disp(sprintf('SSP> %d linearly dependent vectors removed...', size(U,2)-iThresh+1));
            U = U(:, 1:iThresh-1);
        end
        % Compute projector in the form: I-UUt
        SspProj = eye(nChannel) - U*U';
        % Copy the projector in the Components field of the first SSP entry
        ListProj(iProjSsp(1)).Components = SspProj;
        % Remove all the other SSP entries
        if (length(iProjSsp) >= 2)
            iProjDel = [iProjDel, iProjSsp(2:end)];
        end
    % Remove all the SSP projectors
    elseif ~isempty(iProjSsp)
        iProjDel = [iProjDel, iProjSsp];
    end

    % === CLEANING LIST ===
    % Remove unwanted projectors
    ListProj(iProjDel) = [];
    % Nothing left: return
    if isempty(ListProj)
        Projector = [];
        return;
    end

    % === BUILD FINAL PROJECTOR ===
    % Initialize returned projector as an identity matrix
    Projector = eye(nChannel);
    % Add the projectors in the order of appearance
    for i = 1:length(ListProj)
        % ICA
        if isequal(ListProj(i).SingVal, 'ICA')
            % Get selected channels (find the non-zero channels)
            iChan = find(any(ListProj(i).Components ~= 0, 2));
            % Get selected components
            iComp = find(ListProj(i).CompMask == 1);
            % Initialize projector
            P = eye(size(ListProj(i).Components,1));
            % Compute projector
            W = ListProj(i).Components(iChan,:)';
            Winv = pinv(W);
            P(iChan,iChan) = eye(size(W,2)) - Winv(:,iComp) * W(iComp,:);
            % Check if there are any complex values in the projector
            if any(~isreal(P))
                warning('WARNING: ICA components contain complex values. Something went wrong in their computation.');
                P = real(P);
            end
        % Other projectors
        else
            P = ListProj(i).Components;
        end
        % Add to the final operator
        Projector = P * Projector;
    end
    % Make sure it is not an identity matrix
    if isequal(Projector, eye(nChannel))
        Projector = [];
    end
end

%% ===== CONVERT OLD FORMAT =====
% Old format: I - UUt
% New format: Structure with decomposed form (U, maskU...)
function proj = ConvertOldFormat(OldProj)
    if isempty(OldProj)
        proj = [];
    elseif ~isstruct(OldProj)
        proj = db_template('projector');
        proj.Components = OldProj;
        proj.Comment    = 'Unnamed';
        proj.Status     = 1;
    else
        proj = OldProj;
    end
end


function [headinfo,nav,obs,tobs,fid]=decode_obsh(headinfo,nav,obs,fid)

global glc gls
tobs(1:glc.MAXOBSTYPE,1:3,glc.NSYS)=' '; del=zeros(3,1);
sta=gls.sta;

while ~feof(fid)
    line=fgets(fid);label=line(61:end);
    if size(line,2)<=60
        continue;
    elseif ~isempty(strfind(label,'MARKER NAME'))
        str=line(1:60);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.name=str;
        end
    elseif ~isempty(strfind(label,'MARKER NUMBER'))
        str=line(1:20);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.maker=str;
        end
    elseif ~isempty(strfind(label,'MAKER TYPE'))
    elseif ~isempty(strfind(label,'OBSERVER / AGENCY'))
    elseif ~isempty(strfind(label,'REC # / TYPE / VERS'))
        str=line(1:20);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.recsno=str;
        end
        str=line(21:40);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.rectype=str;
        end
        str=line(41:60);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.recver=str;
        end
    elseif ~isempty(strfind(label,'ANT # / TYPE'))
        str=line(1:20);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.antno=str;
        end
        str=line(21:40);idx=find(str~=' ');
        if any(idx)
            str(idx(end)+1:end)=[];
            sta.antdes=str;
        end
    elseif ~isempty(strfind(label,'APPROX POSITION XYZ'))
        j=1;
        for i=1:3
            sta.pos(i)=str2double(line(j:j+14));
            j=j+14;
        end
    elseif ~isempty(strfind(label,'ANTENNA: DELTA H/E/N'))
        j=1;
        for i=1:3
            del(i)=str2double(line(j:j+14));
            j=j+14;
        end
        sta.del(1)=del(2);sta.del(2)=del(3);sta.del(3)=del(1);
    elseif ~isempty(strfind(label,'ANTENNA: DELTA X/Y/Z'))
    elseif ~isempty(strfind(label,'ANTENNA: PHASECENTER'))
    elseif ~isempty(strfind(label,'ANTENNA: B.SIGHT XYZ'))
    elseif ~isempty(strfind(label,'ANTENNA: ZERODIR AZI'))
    elseif ~isempty(strfind(label,'ANTENNA: ZERODIR XYZ'))
    elseif ~isempty(strfind(label,'CENTER OF MASS: XYZ'))
    elseif ~isempty(strfind(label,'SYS / # / OBS TYPES' ))%ver 3.0
        syscode=line(1);
        switch syscode
            case 'G',i=glc.SYS_GPS;
            case 'R',i=glc.SYS_GLO;
            case 'E',i=glc.SYS_GAL;
            case 'C',i=glc.SYS_BDS;
            case 'J',i=glc.SYS_QZS;
            otherwise,continue;
        end
        n=str2double(line(4:7));
        k=7;nt=1;
        for j=1:n
            if k>58
                line=fgetl(fid);
                k=7;
            end
            str=line(k+1:k+3);%idx=find(tmp~=' ');tmp(idx(end)+1:end)=[];
            tobs(nt,:,i)=str;
            nt=nt+1;
            k=k+4;
        end
        if i==4&&(headinfo.ver-3.02)<1e-3
            for j=1:nt
                if tobs(j,2,i)=='1'
                    tobs(j,2,i)='2';
                end
            end
        end
    elseif ~isempty(strfind(label,'WAVELENGTH FACT L1/2'))
    elseif ~isempty(strfind(label,'# / TYPES OF OBSERV'))%ver 2.0
        n=str2double(line(1:7));% number of obs type
        j=10;nt=1;
        for i=1:n
            if j>58
                line=fgetl(fid);
                j=10;
            end
            if headinfo.ver<=2.99
                str=line(j+1:j+2);idx=find(str~=' ');str(idx(end)+1:end)=[];
                tobs(nt,:,1)=convcode(headinfo.ver,str,glc.SYS_GPS);
                tobs(nt,:,2)=convcode(headinfo.ver,str,glc.SYS_GLO);
                tobs(nt,:,3)=convcode(headinfo.ver,str,glc.SYS_GAL);
                tobs(nt,:,4)=convcode(headinfo.ver,str,glc.SYS_BDS);
                tobs(nt,:,5)=convcode(headinfo.ver,str,glc.SYS_QZS);
            end
            nt=nt+1;
            j=j+6;
        end
    elseif ~isempty(strfind(label,'SIGNAL STRENGTH UNIT'))
    elseif ~isempty(strfind(label,'INTERVAL'))
    elseif ~isempty(strfind(label,'TIME OF FIRST OBS'))
        if     strcmp(line(49:51),'GPS'), headinfo.tsys=glc.TSYS_GPS;
        elseif strcmp(line(49:51),'GLO'), headinfo.tsys=glc.TSYS_GLO;
        elseif strcmp(line(49:51),'GAL'), headinfo.tsys=glc.TSYS_GAL;
        elseif strcmp(line(49:51),'BDT'), headinfo.tsys=glc.TSYS_BDS;
        elseif strcmp(line(49:51),'QZS'), headinfo.tsys=glc.TSYS_QZS;
        end
    elseif ~isempty(strfind(label,'TIME OF LAST OBS'))
    elseif ~isempty(strfind(label,'RCV CLOCK OFFS APPL'))
    elseif ~isempty(strfind(label,'SYS / DCBS APPLIED'))
    elseif ~isempty(strfind(label,'SYS / PCVS APPLIED'))
    elseif ~isempty(strfind(label,'SYS / SCALE FACTOR'))
    elseif ~isempty(strfind(label,'SYS / PHASE SHIFTS'))
    elseif ~isempty(strfind(label,'GLONASS SLOT / FRQ #'))
        p=5;
        for i=1:8
            prn=str2double(line(p+1:p+2));
            fcn=str2double(line(p+4:p+5));
            if prn>=1&&prn<=glc.MAXPRNGLO
                nav.glo_fcn(prn)=fcn+8;
                p=p+7;
            end
        end
    elseif ~isempty(strfind(label,'GLONASS COD/PHS/BIS'))
        p=1;
        for i=1:4
            if     strcmp(line(p+1:p+3),'C1C'),nav.glo_cpbias(1)=str2double(line(p+4:p+4+8));
            elseif strcmp(line(p+1:p+3),'C1P'),nav.glo_cpbias(2)=str2double(line(p+4:p+4+8));
            elseif strcmp(line(p+1:p+3),'C2C'),nav.glo_cpbias(3)=str2double(line(p+4:p+4+8));
            elseif strcmp(line(p+1:p+3),'C2P'),nav.glo_cpbias(4)=str2double(line(p+4:p+4+8));
            end
            p=p+13;
        end
    elseif ~isempty(strfind(label,'LEAP SECONDS'))
        nav.leaps=str2double(line(1:6));
    elseif ~isempty(strfind(label,'# OF SALTELLITES'))
    elseif ~isempty(strfind(label,'PRN / # OF OBS'))
    elseif ~isempty(strfind(label,'END OF HEADER'))
        break;
    end
end
obs.sta=sta;

return


function snail_trail( filename, callback_function )
%SNAIL_TRAIL Allows user to select AOIs repeatedly and remember/display them
%
% Example:
% snail_trail(filename, @disp)
%
% This example just prints bounds of selected areas, but you could do
% processing in the callback as well.
%
% TODO: Make this work for multi-image files.
%
% Author: Wade Schwartzkopf, NGA/R
%
% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////

% Setup layout
fig_hand = figure('MenuBar','none','Toolbar','none');
uip_hand = uipanel('Parent',fig_hand,'Position',[0 0 1 1],'BorderType','none');
mitm_hand = hg_mitm_viewer(uip_hand);
aoiButton = javaObjectEDT('javax.swing.JButton','Select AOI');
set(handle(aoiButton,'callbackproperties'), 'ActionPerformedCallback', ...
    @(obj,eventdata) process_aoi());

% Open file
mitm_hand.openFile(filename, true);
% Might be cleaner if we fit figure shape to image aspect ratio first like in TASER.
mitm_hand.Zoom = 'fit';
mitm_hand.PreChangeViewFcn = @() saveShapesNativeCoords();
mitm_hand.PostChangeViewFcn = @() restoreShapesLocalCoords();

% Add buttons to toolbar
main_toolbar = mitm_hand.main_toolbar;
main_toolbar.addSeparator;
main_toolbar.add(aoiButton);
main_toolbar.repaint;
main_toolbar.revalidate;

% Setup draggable rectangle for AOI selection
datasize = double([mitm_hand.Metadata{mitm_hand.Frame}.ImageData.NumCols ...
            mitm_hand.Metadata{mitm_hand.Frame}.ImageData.NumRows]);
native_coords = [datasize(1)/4 datasize(2)/4 datasize(1)/2-1 datasize(2)/2-1];
init_coords = mitm_hand.native2axescoords(native_coords(1:2));
init_coords(3:4) = native_coords(3:4)/max(mitm_hand.Zoom,1);
aoi_imrect = imrect(mitm_hand.AxesHandle, init_coords);
init_constraint = mitm_hand.native2axescoords(datasize+1);
% Force draggable rectangle to be within image, rather than entire figure/axes
setPositionConstraintFcn(aoi_imrect, makeConstrainToRectFcn('imrect', ...
    [1 init_constraint(1)], [1 init_constraint(2)]));
processed_areas = {};

    % This function shades all processed areas as reddish.
    function data_out = shade_processed_areas(data_in)
        % Doesn't currently handle muli-frame datasets, in which the masks
        % of processed area are almost certainly different for each frame.
        data_out = repmat(data_in, [1 1 3]);
        mask = zeros(size(data_in));
        for i = 1:numel(processed_areas)
            ul = mitm_hand.native2axescoords(processed_areas{i}(1:2));
            lr = ul + processed_areas{i}(3:4)/max(mitm_hand.Zoom,1) - 1;
            mask = mask | poly2mask([ul(1), lr(1), lr(1), ul(1)], ...
                [ul(2), ul(2), lr(2), lr(2)], size(data_in,1), size(data_in,2));
        end
        % Shade processed areas red
        data_out(:,:,2) = data_out(:,:,2) .* ~mask;
        data_out(:,:,3) = data_out(:,:,3) .* ~mask;
    end

    % This function uses the draggable rectangle to start AOI processing.
    function process_aoi
        % Get AOI
        aoi_screen = aoi_imrect.getPosition();
        % Convert to image coordinate space
        ulcorner = round(mitm_hand.axescoords2native(aoi_screen(1:2)));
        lrcorner = round(mitm_hand.axescoords2native(aoi_screen(1:2) + aoi_screen(3:4) - 1));
        % Return values (UL corner and size) to calling function
        callback_args{1}(1:2) = ulcorner;
        callback_args{1}(3:4) = lrcorner-ulcorner+1;
        callback_args{2} = mitm_hand.FrameSubFileIndices{mitm_hand.Frame}; % Which frame was AOI selected in?
        try
            callback_function(callback_args{1});
            processed_areas{end+1} = callback_args{1};  % Do we need to store frame as well?
            % This function never changes (only the processed_areas
            % variable it references), but setting it here, even if to the
            % same function it was before, causes the viewer to actually
            % apply it.
            mitm_hand.DataTransformFcn = @shade_processed_areas;
        end
    end

    % Rember draggable rectangle before zoom/pan
    function saveShapesNativeCoords()
        local_coords = aoi_imrect.getPosition();
        native_coords = mitm_hand.axescoords2native(local_coords(1:2));
        native_coords(3:4) = local_coords(3:4)*max(mitm_hand.Zoom,1);
    end

    % Restore draggable rectangle before zoom/pan
    % This is messier than one would hope because of the MATLAB quirks.
    function restoreShapesLocalCoords()
        oldzoom = get(zoom(fig_hand),'Enable');
        oldpan = get(pan(fig_hand),'Enable');
        delete(aoi_imrect);
        local_coords = mitm_hand.native2axescoords(native_coords(1:2));
        local_coords(3:4) = native_coords(3:4)/max(mitm_hand.Zoom,1);
        aoi_imrect = imrect(mitm_hand.AxesHandle, local_coords);
        ds = double([mitm_hand.Metadata{mitm_hand.Frame}.ImageData.NumCols ...
            mitm_hand.Metadata{mitm_hand.Frame}.ImageData.NumRows]);
        constraint = mitm_hand.native2axescoords(ds+1);
        setPositionConstraintFcn(aoi_imrect, makeConstrainToRectFcn('imrect', ...
            [1 constraint(1)], [1 constraint(2)]));
        % Restore old state which was changed when we redrew the shapes
        try % Doesn't work when initiated from zoom callback.
            zoom(fig_hand,oldzoom);
            pan(fig_hand, oldpan);
        end
    end
end

% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////


function fields = TmodelFields()
% Returns the field names of the `Tmodel` structure. This is
% to help alleviate control between different scripts.
% Called by `cleanTmodel`, `readCbTmodel`, `verifyModel`, `organizeModelCool`, `TmodelStats`, `orderModelFieldsBorg`.
%
% USAGE:
%
%    fields = TmodelFields()
%
% OUTPUTS:
%    Fields:       A column cell array that contains the following in order:
%
%                    * rxnFields
%                    * rNumField
%                    * metFields
%                    * mNumFields
%                    * allFields
%
% Please cite:
% `Sauls, J. T., & Buescher, J. M. (2014). Assimilating genome-scale
% metabolic reconstructions with modelBorgifier. Bioinformatics
% (Oxford, England), 30(7), 1036?8`. http://doi.org/10.1093/bioinformatics/btt747
%
% ..
%    Edit the above text to modify the response to help addMetInfo
%    Last Modified by GUIDE v2.5 06-Dec-2013 14:19:28
%    This file is published under Creative Commons BY-NC-SA.
%
%    Correspondance:
%    johntsauls@gmail.com
%
%    Developed at:
%    BRAIN Aktiengesellschaft
%    Microbial Production Technologies Unit
%    Quantitative Biology and Sequencing Platform
%    Darmstaeter Str. 34-36
%    64673 Zwingenberg, Germany
%    www.brain-biotech.de

fields = cell(4,1) ; % Declare fields

% Reaction related fields that are cell arrays.
rxnFields = {'rxns' 'rxnID' 'rxnNames' 'subSystems' 'rxnECNumbers' ...
             'rxnKEGGID' 'rxnSEEDID' 'rxnEquations' ...
             'rxnReferences' 'rxnNotes' 'grRules'}' ;
fields{1,1} = rxnFields ;

% Reaction related fields that are numeric arrays.
rNumFields = {'lb' 'ub' 'c'}' ;
fields{2,1} = rNumFields ;

% Metabolite related fields that are cell arrays
metFields = {'mets' 'metID' 'metNames' 'metFormulas' ...
             'metKEGGID' 'metSEEDID' ...
             'metChEBIID' 'metPubChemID' 'metInChIString'}' ;
fields{3,1} = metFields ;

% Metabolite related fields that are numeric arrays.
mNumFields = {'metCharge'} ;
fields{4,1} = mNumFields ;

% All field names in correct order (27 fields).
allFields = {'rxns' 'mets' 'S' 'lb' 'ub' 'c' ...
             'rxnID' 'rxnNames' 'subSystems' 'rxnEquations' ...
             'rxnECNumbers' 'rxnKEGGID' 'rxnSEEDID' ...
             'rxnReferences' 'rxnNotes' 'grRules' ...
             'metID' 'metNames' 'metFormulas' 'metCharge' ...
             'metKEGGID' 'metSEEDID' 'metChEBIID' 'metPubChemID' ...
             'metInChIString' ...
             'genes' 'description'}' ;
fields{5,1} = allFields ;


function plot_miss_rate_min(plot_obj,plot_args,legend_string)
% Plots the contribution of the misses to the minimum dcf curve.
% Inputs:
%   plot_args: A cell array of arguments to be passed to 'plot' that control
%     the appearance of the curve. See Matlab's help on 'plot' for information.
%   legend_string: Optional. A string to describe this curve in the legend.
%     This string will be prepended with the system name.

if ~exist('legend_string','var')
    legend_string = '';
end

plot_obj.plot_miss_rate(plot_obj.minPmiss,plot_args,legend_string);
end


function vw = setMapWindow(vw, mapWindow)
%
% setMapWindow(vw,mapWindow)
%
% Sets mapWindow slider values
% ras, 01/07 -- deals w/ hidden views
% ras, 07/09 -- forces slider range to conform to the new window.
% Previously this required you to run UpdateMapWindow. This logic is not
% flexible enough for many analyses.
% jw: 08/2011 -- keep mapWinMin and mapWinMax ranges the same. if we extend
%                   one, then we extend the other.

if ~exist('vw', 'var') || isempty(vw),  vw = getCurView;  end

if checkfields(vw, 'ui', 'mapWinMin', 'sliderHandle')
	% ensure that the slider ranges allow for this map window
	sliderMin = get(vw.ui.mapWinMin.sliderHandle, 'Value');
	if sliderMin > min(mapWindow)
		set(vw.ui.mapWinMin.sliderHandle, 'Min', min(mapWindow));
        set(vw.ui.mapWinMax.sliderHandle, 'Min', min(mapWindow));
	end

	sliderMax = get(vw.ui.mapWinMax.sliderHandle, 'Value');
	if sliderMax < max(mapWindow)
		set(vw.ui.mapWinMax.sliderHandle, 'Max', max(mapWindow));
		set(vw.ui.mapWinMin.sliderHandle, 'Max', max(mapWindow));
	end

	% now set the slider values.
    setSlider(vw, vw.ui.mapWinMin, mapWindow(1));
    setSlider(vw, vw.ui.mapWinMax, mapWindow(2));

else    % hidden view
    vw.settings.mapWin = mapWindow;

end

return


function [model, hasEffect, constrRxnNames, deletedGenes] = deleteModelGenes(model, geneList, downRegFraction)
% Deletes one or more genes and constrain the reactions
% affected to zero and appends '_deleted' to the gene(s)
%
% USAGE:
%
%    [model, hasEffect, constrRxnNames, deletedGenes] = deleteModelGenes(model, geneList, downRegFraction)
%
% INPUT:
%    model:             COBRA model with the appropriate constrains for a
%                       particular condition
%
% OPTIONAL INPUTS:
%    geneList:          List of genes to be deleted (Default =  all genes in model)
%    downRegFraction:   Fraction of the original bounds that the reactions
%                       corresponding to downregulated genes will be assigned
%                       (Default = 0 corresponding to a full deletion)
%
% OUTPUTS:
%    model:             COBRA model with the selected genes deleted
%    hasEffect:         True if the gene deletion has an effect on the model
%    constrRxnNames:    Reactions that are associated to the genes in `deletedGenes`
%    deletedGenes:      The list of genes removed from the model.
%
% .. Authors:
%       - Markus Herrgard 8/28/06
%       - Josh Lerman and Richard Que 04/21/10 - Added an error if non-existent gene.
%       - Richard Que (04/22/2010) - '_deleted' is appended to deleted gene names

if (nargin < 2)
    geneList = model.genes;
end

if (nargin < 3)
    downRegFraction = 0;
end

if (~iscell(geneList))
    geneName = geneList;
    clear geneList;
    geneList{1} = geneName;
end

if (~isfield(model,'genes'))
    error('Gene-reaction associations not included with the model');
end

%RxnGeneMat is required for this function, so we will have to build it if
%it does not exist
if ~isfield(model,'rxnGeneMat')
    model = buildRxnGeneMat(model);
end

hasEffect = false;
constrRxnNames = {};
%deletedGenes is a cell array for returning the genes that are
%eliminated from the model.
deletedGenes = {};

% Find gene indices in model
[isInModel,geneInd] = ismember(geneList,regexprep(model.genes,'_deleted',''));

if (all(isInModel))

  %If there are any zero elements in geneInd remove them from the
  %geneList and the geneInd because they correspond to genes that
  %are not in the model.
  deletedGenes = geneList( find( geneInd ) );
  geneInd = geneInd( find( geneInd ) );

  %mark genes for deletion
  model.genes(geneInd) = strcat(model.genes(geneInd),'_deleted');

    % Find rxns associated with this gene
    rxnInd = find(any(model.rxnGeneMat(:,geneInd),2));
    if (~isempty(rxnInd))
        x = true(size(model.genes));
        % set genes marked "_deleted" to false
        x(~cellfun('isempty',(regexp(model.genes,'_deleted')))) = false;
        constrainRxn = false(length(rxnInd),1);
        % Figure out if any of the reaction states is changed
        for j = 1:length(rxnInd)
            if (isfield(model, 'rules') && ~isempty(model.rules{rxnInd(j)})) %To avoid errors if the rule is empty
                if (~eval(model.rules{rxnInd(j)}))
                    constrainRxn(j) = true;
                end
            end
        end
        % Constrain flux through the reactions associated with these genes
        if (any(constrainRxn))
            constrRxnNames = model.rxns(rxnInd(constrainRxn));
            if (nargin > 2)
                model = changeRxnBounds(model,constrRxnNames,downRegFraction*model.lb(findRxnIDs(model,constrRxnNames)),'l');
                model = changeRxnBounds(model,constrRxnNames,downRegFraction*model.ub(findRxnIDs(model,constrRxnNames)),'u');
            else
                % Full deletion
                model = changeRxnBounds(model,constrRxnNames,0,'b');
            end
            hasEffect = true;
        end
    end
else
    error(['Gene',' ',geneList{~isInModel}, ' not in model!']);
end


function MRSIStruct = op_CSICombineTime(MRSIStruct, dimensionLabel)
    newTimeLength = prod(getSizeFromDimensions(MRSIStruct, {'t', dimensionLabel}));
    [MRSIStruct, prevPermute, prevSize]= reshapeDimensions(MRSIStruct, {'t', dimensionLabel});
    data = getData(MRSIStruct);

    data = reshape(data, newTimeLength, []);
    MRSIStruct = setData(MRSIStruct, data);
    prevSize(2) = [];
    prevSize(1) = newTimeLength;

    prevPermute = removeDimPrevPermute(prevPermute, 2);
    MRSIStruct = reshapeBack(MRSIStruct, prevPermute, prevSize);
end


% GetSegyTraceData : Get Segy trace data if filehandle
%
% Call :
%
%   tracedata=GetSegyTraceData(segyid,ns,SegyHeader)
%

%
% (C) 2001-2004 Thomas Mejer Hansen, tmh@gfy.ku.dk/thomas@cultpenguin.com
%
%    This program is free software; you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation; either version 2 of the License, or
%    (at your option) any later version.
%
%    This program is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with this program; if not, write to the Free Software
%    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
%
function tracedata=GetSegyTraceData(segyid,ns,SegyHeader,SkipData,it)
%
% Get Segy Trace Data.
%
%

SkipData=[];

if nargin==0,
    SegymatVerbose(exist('segyid'))
    SegymatVerbose([mfilename,' : SEGYID not specified - exiting'])
    tracedata=[];
    return
end
if nargin==1
  SegymatVerbose([mfilename,' : NS not specified - exiting'])
  tracedata=[];
  return
end
if nargin==2
    SegyHeader.DataFormat='float32';
    SegyHeader.BytesPerSample=32;
    SegyHeader.DataSampleFormat=5; % IEEE
    SegymatVerbose(['Dataformat not specified -  dataformat->',SegyHeader.DataFormat])
end;

if isempty(SkipData)==1, SkipData=0; end

Revision=SegyHeader.SegyFormatRevisionNumber;
if Revision>0, Revision=1; end
Format=SegyHeader.Rev(Revision+1).DataSampleFormat(SegyHeader.DataSampleFormat).format;

BPS=SegyHeader.Rev(Revision+1).DataSampleFormat(SegyHeader.DataSampleFormat).bps;
if (SkipData==1)
    SkipBytes=ns*BPS/8;
    fseek(segyid,SkipBytes,'cof');
    tracedata=[];
else
    % SegymatVerbose([mfilename,' : ',Format,'. ns=',num2str(ns)])
    try

       if nargin>4

           % Skip to the it'th sample
           SkipBytes=(it-1)*BPS/8;
           fseek(segyid,SkipBytes,'cof');

           tracedata=fread(segyid,1,Format);

           SkipBytes=(ns-it)*BPS/8;
           fseek(segyid,SkipBytes,'cof');

       else
         tracedata=fread(segyid,ns,Format);
       end
    catch
      SegymatVerbose([mfilename,' : Error using fread - Possibly ''ns'' is negative -' ...
	    ' check byteorder-'])
      tracedata=[];
    end

    if (strcmp(Format,'uint32')==1), % IBM FLOATING POINT
        % CONVERT FROM FLOATING POINT
        verbose=1;
        if verbose>1, SegymatVerbose([mfilename,'Converting from IBM, DataFormat :',SegyHeader.DataFormat]); end
        try
          tracedata=ibm2num(uint32(tracedata));
        catch
          % SegymatVerbose([mfilename,' : SOMETHING BAD HAPPENED WHEN CONVERTING FROM IBM FLOATS TO IEEE. ARE YOU SURE DATA ARE IBM FLOAT FORMATTED ?' ])
          % tracedata=0.*tracedata;
          % return

        end
    end;

end


function result = op_selective_search_boxes(first_el, last_el, imdb)
% result = op_selective_search_boxes(first_el, last_el, imdb)
%
% Loop slice operation for distributed computation of selective search
% boxes.
%
% This function depends on simple-cluster-lib, which is specific to
% the Berkeley cluster and not useful to the general public (and
% hence not available). This file exists because it's convenient for
% me to keep it in the repository.
%
% AUTORIGHTS
% ---------------------------------------------------------
% Copyright (c) 2014, Ross Girshick
%
% This file is part of the R-CNN code and is available
% under the terms of the Simplified BSD License provided in
% LICENSE. Please retain this notice and LICENSE if you use
% this file (or any portion of it) in your project.
% ---------------------------------------------------------

fast_mode = true;
mean_num = 0;
mean_time = 0;

result = cell(last_el-first_el+1, 1);
for i = first_el:last_el
  i_ = i-first_el+1;

  fprintf('%d/%d (%s) ...', i, last_el, imdb.image_ids{i});
  im = imread(imdb.image_at(i));
  th = tic();
  result{i_} = selective_search_boxes(im, fast_mode);
  t = toc(th);

  mean_num = (mean_num * (i_-1) + size(result{i_}, 1))/i_;
  mean_time = (mean_time * (i_-1) + t)/i_;
  fprintf('%.2fs...%d boxes (means: %.2fs %.1f boxes)\n', t, ...
      size(result{i_}, 1), mean_time, mean_num);
end


function plotQualityMeasuresForCompressedDatasets(srResults, qualityMeasure, binningFactors, compression, srMethods, normalized, outputDir)

    figure_bin2 = plotQualityMeasuresAsBarplot(srResults, qualityMeasure, binningFactors(1), srMethods, compression, normalized);
    figure_bin3 = plotQualityMeasuresAsBarplot(srResults, qualityMeasure, binningFactors(2), srMethods, compression, normalized);
    figure_bin4 = plotQualityMeasuresAsBarplot(srResults, qualityMeasure, binningFactors(3), srMethods, compression, normalized);
    LinkFigures([figure_bin2 figure_bin3 figure_bin4]);

    if normalized
        qualityMeasure = [qualityMeasure, '_norm'];
    end

    % Save to Tikz.
    figure(figure_bin2);
    matlab2tikz([outputDir, '/', 'compressionDatasets_bin2_', qualityMeasure, '_bars.tikz'], ...
        'height', '\figureheight', 'width', '\figurewidth', 'showInfo', false, ...
        'extraaxisoptions', ['xlabel near ticks,', ...
        'ylabel near ticks,', ...
        'scaled y ticks=false,', ...
        'yticklabel style={/pgf/number format/fixed, /pgf/number format/precision=2},']);

    figure(figure_bin3);
    matlab2tikz([outputDir, '/', 'compressionDatasets_bin3_', qualityMeasure, '_bars.tikz'], ...
        'height', '\figureheight', 'width', '\figurewidth', 'showInfo', false, ...
        'extraaxisoptions', ['xlabel near ticks,', ...
        'ylabel near ticks,', ...
        'scaled y ticks=false,', ...
        'yticklabel style={/pgf/number format/fixed, /pgf/number format/precision=2},']);

    figure(figure_bin4);
    matlab2tikz([outputDir, '/', 'compressionDatasets_bin4_', qualityMeasure, '_bars.tikz'], ...
        'height', '\figureheight', 'width', '\figurewidth', 'showInfo', false, ...
        'extraaxisoptions', ['xlabel near ticks,', ...
        'ylabel near ticks,', ...
        'scaled y ticks=false,', ...
        'yticklabel style={/pgf/number format/fixed, /pgf/number format/precision=2},']);

function figureNumber = plotQualityMeasuresAsBarplot(srResults, qualityMeasure, binningFactor, srMethods, compression, normalized)

    % Compute mean of the quality measure for different SR methods and
    % binning factors.
    barMeans = zeros(length(compression), length(srMethods));
    for compressionLevelIdx = 1:length(compression)
        for srMethodIdx = 1:length(srMethods)
            % Get quality measures for given SR method and binning factor.
            qualityMeasureVals = [srResults([ strcmp({srResults.compressionLevel}, compression{compressionLevelIdx}) ] ...
                & [srResults(:).binningFactor] == binningFactor ...
                &  strcmp(srMethods{srMethodIdx}, {srResults(:).srMethod}) > 0).(qualityMeasure)];

            if normalized
                % Normalize the quality measure with the input
                % low-resolution image (after NN interpolation).
                qualityMeasureVals_nn = [srResults([ strcmp({srResults.compressionLevel}, compression{compressionLevelIdx}) ] ...
                    & [srResults(:).binningFactor] == binningFactor &  strcmp('nn', {srResults(:).srMethod}) > 0).(qualityMeasure)];
                qualityMeasureVals = (qualityMeasureVals - qualityMeasureVals_nn) ./ abs(qualityMeasureVals_nn);
            end

            % Compute the mean of the quality measure.
            barMeans(compressionLevelIdx, srMethodIdx) = mean(qualityMeasureVals);
        end
    end

    % Plot mean quality measures as bar plot.
    figure;
    figureNumber = get(gcf, 'Number');
    bar_handle = bar(barMeans);
    for k = 1:length(srMethods)
        set(bar_handle(k), 'FaceColor', getFaceColorForSRMethod(srMethods{k}));
    end
    hold off;
    pbaspect([4 1.25 1]);
    set(gca, 'XTickLabel', {'Uncoded', 'QP10', 'QP20', 'QP30', 'QP40'}, 'XTick', 1:(5*length(srMethods)));
    ymax = max(1.05*max(barMeans(:)));
    ymin = max(0.99*min(barMeans(:)), -0.25*ymax);
    ylim([ymin ymax]);
    ylabelStr = upper(qualityMeasure);
    if normalized
        ylabelStr = ['Norm. ', ylabelStr]; %#ok<AGROW>
    end
    ylabel(ylabelStr);
    grid on;


function s=get_name(a)

s=[get_name(a.algorithm) ' (' get_name(a.child) ')'];  %% print name of algorithm
eval_name                   %% print values of hyperparameters


function test_suite = test_svi_classific

%   Run specific demo, save values and compare the results to the expected.
%   Works for both xUnit Test Framework package by Steve Eddins and for
%   the built-in Unit Testing Framework (as of Matlab version 2013b).
%
%   See also
%     TEST_ALL, DEMO_SVI_CLASSIFIC
%
% Copyright (c) 2011-2012 Ville Tolvanen
% Copyright (c) 2014 Tuomas Sivula

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

  % Check which package to use
  if exist('initTestSuite', 'file')
    initTestSuite_path = which('initTestSuite');
  else
    initTestSuite_path = '';
  end
  if ~isempty(initTestSuite_path) && ...
     exist([initTestSuite_path(1:end-15) 'runtests'], 'file')
    % xUnit package
    initTestSuite;
  else
    % Built-in package
    % Use all functions except the @setup
    tests = localfunctions;
    tests = tests(~cellfun(@(x)strcmp(func2str(x),'setup'),tests));
    test_suite = functiontests(tests);
  end
end

% -------------
%     Tests
% -------------

function testRunDemo(testCase)
  % Run the correspondin demo and save the values. Note this test has to
  % be run at lest once before the other test may succeed.
  rundemo(getName(), {'lpyt', {'w', @(x)gp_pak(x), 'gp'} })
end

function testPrediction(testCase)
  verifyVarsEqual(testCase, getName(), {'lpyt'}, ...
    'RelTolElement', 0.05, 'RelTolRange', 0.01)
end

function testParameters(testCase)
  verifyVarsEqual(testCase, getName(), {'w'}, ...
    'RelTolElement', 0.05, 'RelTolRange', 0.01)
end

% ------------------------
%     Helper functions
% ------------------------

function testCase = setup
  % Helper function to suply empty array into variable testCase as an
  % argument for each test function, if using xUnit package. Not to be
  % used with built-in test framework.
  testCase = [];
end

function name = getName
  % Helperfunction that returns the name of the demo, e.g. 'binomial1'.
  name = mfilename;
  name = name(6:end);
end


function tagS = general_study_module_tags_subset
%"general_study_module_tags_subset"
%   Return a subset of the tags specified in C.7.2.1 in PS3.3 of 2006 DICOM
%   specification required to distinguish different studies.
%
%   Tags are returned in a struct array with 3 fields:
%
%   Tag: String containing hex DICOM tag of a field.
%  Type: String describing type of field, with 5 options:
%         '1' Field must exist, data must exist and be valid.
%         '2' Field must exist, data can exist and be valid, or be NULL.
%         '3' Field is optional, if the field exists data can exist and be
%             valid or be NULL.
%         '1C' Field must exist under certain conditions and contain valid
%             data.
%         '2C' Field must exist under certain conditions and can contain
%             valid data or be NULL.
%Children: For sequences, a tagS with the same format as this struct array.
%
%AI 11/20/2020
%
%Usage:
%   tagS = general_study_module_tags_subset
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR 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.
%
% CERR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
% without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% See the GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with CERR.  If not, see <http://www.gnu.org/licenses/>.

tagC = {'0020000D','00080020','00080030','00200010','00080050'};
tagDecC = {2097165, 524320, 524336, 2097168, 524368};
tagTypeC = {'1','2','2','2','2'};
childC = cell(1,length(tagC));

tagS = struct('tag',tagC,'tagdec',tagDecC,'type',tagTypeC,'children',childC);


function [IFF]=openiff(fid,LEN)
% OPENIFF is an auxillary function to SOPEN for
% opening of IFF files
%
% Use SOPEN instead of OPENIFF
%
% See also: fopen, SOPEN
%
% References:

% 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.

%	$Id: openiff.m 2205 2009-10-27 12:18:15Z schloegl $
%	Copyright (C) 2004,2005,2007 by Alois Schloegl <a.schloegl@ieee.org>
%    	This is part of the BIOSIG-toolbox http://biosig.sf.net/

FLAG.CLOSE = 0;
if ischar(fid)
        fid = fopen(fid,'r');
        FLAG.CLOSE = 1;
end;

if nargin<2,
        LEN = 8;
end;

IFF = [];
K   = 0;
K1  = 0;

[tmp,c] = fread(fid,[1,4],'uint8');
while ((LEN>0) | isnan(LEN)) & (c>0),
        tag     = char(tmp);
        tagsize = fread(fid,1,'uint32');        % which size
        tagsize0= tagsize + rem(tagsize,2);
        filepos = ftell(fid);
        LEN = LEN - 8;

%       fprintf(1,'tag: %6s\tpos: %8i\tsize: %8i\n',tag,filepos,tagsize);

        if 0,
                VAL = openiff(fid,tagsize);
        elseif strcmp(tag,'FORM')
                [tmp,c] = fread(fid,[1,4],'uint8');tag,
                VAL = setfield([],char(tmp),openiff(fid,tagsize-4));
        elseif 0,strcmp(tag,'RIFF')
                VAL = openiff(fid,tagsize);
        elseif strcmp(tag,'RIFF')
                [tmp,c] = fread(fid,[1,4],'uint8');
                %val = fread(fid,tagsize-4,'uint8');
                val = openiff(fid,tagsize-4);
                VAL = setfield([],char(tmp),val);
        elseif strcmp(tag,'MThd')
                VAL.MThd = fread(fid,tagsize,'uchar');
                VAL.MIDI = openiff(fid,NaN);
                %LEN = NaN;
                %VAL.MIDI = tmp;

        elseif strcmp(tag,'LIST');	% CNT_RIFF (EEP 3.1)
                [tmp,c] = fread(fid,[1,4],'uint8');
                VAL = setfield([],char(tmp),openiff(fid,tagsize-4));

        elseif strcmp(tag,'chan')	% CNT_RIFF (EEP 3.1)
                VAL = fread(fid,[1,tagsize/2],'uint16');
        elseif strcmp(tag,'info')	% CNT_RIFF (EEP 3.1)
                VAL = fread(fid,[1,tagsize],'*char');
        elseif strcmp(tag,'eeph')	% CNT_RIFF (EEP 3.1)
                VAL = fread(fid,[1,tagsize],'*char');
        elseif strncmp(tag,'ep  ',4),	% CNT_RIFF (EEP 3.1)
                VAL = fread(fid,[1,tagsize/4],'uint32');
        elseif strcmp(tag,'hdrl')
                [tmp,c] = fread(fid,[1,4],'uint8');
                VAL = setfield([],char(tmp),openiff(fid,tagsize-4));

        elseif strcmp(tag,'CAT ')
                VAL = openiff(fid,tagsize);

        elseif strcmp(tag,'data')
        %        LEN = fread(fid,1,'uint32');
                VAL = fread(fid,[1,LEN],'*uint8');
                %[tmp,c] = fread(fid,[1,4],'*char');
                %LEN = fread(fid,1,'int32');
                %VAL.data = fread(fid,LEN,'uint8');
                %VAL = openiff(fid,tagsize);
        elseif strncmp(tag,'(c)',3)
                tag = 'Copyright';
                VAL = char(fread(fid,tagsize,'uchar')');
                %VAL.CopyRight = char(VAL);
        else
                if 0,tagsize<1024*2,
                        VAL = fread(fid,tagsize,'uchar');
                else
			VAL = [];
                        VAL.handle = ftell(fid);
                        VAL.size = tagsize;
                        status = fseek(fid,tagsize,'cof');
                        if status, return; end;
                end
        end;

        if strcmp(tag(3:4),'dc')
                K = K+1;
                ix = (tag(1:2)-48)*[16;1]+1;
                IFF.dc{K,ix} = VAL;
        elseif strcmp(tag(3:4),'wb')
                K1 = K1+1;
                ix = (tag(1:2)-48)*[16;1]+1;
                IFF.wb{K1,ix} = VAL;
        else
                %try,
	                IFF = setfield(IFF,deblank(tag),VAL);
                %catch,
                %end;
        end;
        status = fseek(fid,filepos+tagsize0,'bof');
        LEN = LEN - tagsize0;
        [tmp,c] = fread(fid,[1,4],'uint8');
end;

if ~isfield(IFF,'MThd'), % do not check MIDI files
        if ((LEN ~= 0) & c),
                fprintf(2,'Warning OPENIFF: LEN=%i %i %i %i %s  %i\n',LEN,filepos,tagsize0,ftell(fid),char(tmp),c);
        end;
end;

if FLAG.CLOSE,
        fclose(fid);
end;


function [list,dif]=locDepFun(Fun)
%LOCDEPFUN determines the local m-files that a function depends upon
%    [LIST,DIF]=LOCDEPFUN(FUN)
%    FUN is a string with the function name
%    LIST is a cell array of strings with the local m-files that FUN
%    depends upon
%    DIF is a cell array of strings with the local m-files that FUN does
%    not depend upon
%
%    Example
%        [list,dif]=locDepFun('foo');
%    Determines which local m-files foo.m ooes and does not depend upon
%
%    See also DEPFUN.

%R.F. Tap, May 2006

list = depfun(Fun,'-quiet');
d = cd;
lcd = length(d);
I = [];
for i=1:length(list)
    if length(list{i})>=lcd
        if strcmp(d,list{i}(1:lcd))
            I=[I i];
        end
    end
end

dm=dir('*.m');
names = {dm.name};

ieq = [];
for i=1:length(names)
    LN = length(names{i});
    for j=1:length(I)
        if strcmp(names{i},list{I(j)}(end-LN+1:end))
            ieq = [ieq i];
            break
        end
    end
end

idif = setdiff(1:length(names),ieq);
dif = names(idif)';
list = names(ieq)';


% Copyright 2013 The MathWorks, Inc.
function y=playControls()

persistent state;
persistent hpause;
persistent hexit; %#ok<PUSE>
persistent isPaused;

if isempty(state)
    initializeUI;
end

% deal with the state
if state == 2 % exit
    y = 0; % terminate the loop
    state = [];
else
    % block the return until pause is turned off
    while(isPaused)
        drawnow; % keep refreshing the UI
        pause(0.01); % don't hog the CPU
    end

    y = 1;
end

    function exit_callback(~, ~)
        state = 2; % 2 signifies exit
        isPaused = false;
        close gcf;
    end

    function pause_callback(obj, ~)
        % Handle button string toggling
        if get(hpause,'value')
            isPaused = true;
            set(obj, 'string', 'PLAY');
        else
            isPaused = false;
            set(obj, 'string', 'PAUSE');
        end
    end

    function initializeUI
        state = 0;

        scrsz = get(0,'ScreenSize');
        % [left, bottom, width, height]
        %figure('Position',[1 scrsz(4)-50 80 50]);
        h = figure('Position',[scrsz(3)*.25 scrsz(4)*.75 80 50], 'Name', 'Loop controls', ...
            'NumberTitle','off','MenuBar','none');

        hpause = uicontrol('unit','pixel','style','togglebutton','string','PAUSE',...
            'position',[10 10 50 25],'callback',@pause_callback);
        hexit = uicontrol('unit','pixel','style','pushbutton','string','EXIT',...
            'position',[60 10 50 25],'callback',@exit_callback);

        set(h,'HandleVisibility','callback');

        isPaused = false;
    end

end


% The COBRAToolbox: testShowprogress.m
%
% Purpose:
%     - testShowprogress tests the showprogress function (0: silent, 1: textprogressbar, 2: waitbar)
%
% Author:
%     - Laurent Heirendt - March 2017

% save the current path
currentDir = pwd;

% initialize the test
fileDir = fileparts(which('testShowprogress'));
cd(fileDir);

global WAITBAR_TYPE

for m = 0:2
    WAITBAR_TYPE = m;
    fprintf('Testing show progress, mode = %i:\n', m)

    % run multiple tests with the textprogressbar (WAITBAR_TYPE = 1)
    if WAITBAR_TYPE == 1
        kend = 3;
    else
        kend = 1;
    end

    for k = 1:kend
        showprogress(0,'Testing showprogress ...');
        for i = 1:k * 10
            showprogress(i/(k * 10));
            pause(0.02)
        end
    end
    if WAITBAR_TYPE == 1
        fprintf('\n')
    end
end

% update with 1 input argument
WAITBAR_TYPE = 1;
showprogress(0);

% update with WAITBAR_TYPE=[]
WAITBAR_TYPE = [];
showprogress(0,'Testing showprogress ...');

% reset the default WAITBAR_TYPE
WAITBAR_TYPE = 1;
if ~isempty(strfind(getenv('HOME'), 'jenkins'))
    WAITBAR_TYPE = 0;
end

% return to original directory
cd(currentDir);


function PrepareTestData()

global param;

sceneFolder = param.testScenes;
outputFolder = param.testData;

[sceneNames, scenePaths, numScenes] = GetFolderContent(sceneFolder, [], true);

MakeDir(outputFolder);

for i = 1 : numScenes

    count = fprintf('Started working on scene %d of %d', i, numScenes);

    %%% reading input data
    curExpo = ReadExpoTimes(scenePaths{i});
    [curImgsLDR, curLabel] = ReadImages(scenePaths{i});

    %%% processing data
    [inputs, label] = ComputeTestExamples(curImgsLDR, curExpo, curLabel);

    %%% writing data
%     label=[];
%     inputs = ((curImgsLDR{1}));
%     inputs = cat(3, inputs, (curImgsLDR{2}));
%     inputs = cat(3, inputs, (curImgsLDR{3}));
    WriteTestExamples(inputs, label, [outputFolder, '/', sceneNames{i}, '.h5']);
%     WriteTestExamples(inputs, label, [outputFolder, '/', 'TrainSequence', num2str(i) '.h5']);

    fprintf(repmat('\b', [1, count]));
end

fprintf('Done\n\n');


function res = units(this, varargin)
% Method for setting/getting all units, over channels
% FORMAT res = units(this, ind)
% _______________________________________________________________________
% Copyright (C) 2008-2012 Wellcome Trust Centre for Neuroimaging

% Stefan Kiebel
% $Id: units.m 5933 2014-03-28 13:22:28Z vladimir $

if this.montage.Mind == 0
    res = getset(this, 'channels', 'units', varargin{:});
else
    if nargin == 3
        this.montage.M(this.montage.Mind) = getset(this.montage.M(this.montage.Mind), 'channels', 'units', varargin{:});
        res = this;
    else
        res = getset(this.montage.M(this.montage.Mind), 'channels', 'units', varargin{:});
    end
end


function display(t)
%DISPLAY Command window display of a sptenmat.
%
%   DISPLAY(T) displays the tensor with its name.
%
%   See also SPTENMAT/DISP.
%
%MATLAB Tensor Toolbox.
%Copyright 2015, Sandia Corporation.

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

disp(t,inputname(1));


% The COBRAToolbox: testdynamicRFBA.m
%
% Purpose:
%     - testrFBA tests the dynamicRFBA function and its different outputs
%
% Author:
%     - Marouen BEN GUEBILA - 31/01/2017

% save the current path
currentDir = pwd;

% initialize the test
fileDir = fileparts(which('testdynamicRFBA'));
cd(fileDir);

%load model and test data
modelReg = getDistributedModel('modelReg.mat');
load('refData_dynamicRFBA.mat');

%Solver packages
solverPkgs = {'tomlab_cplex','ibm_cplex'};
%QP solvers
QPsolverPkgs = {'tomlab_cplex','ibm_cplex'};

for k =1:length(solverPkgs)

    solverLPOK = changeCobraSolver(solverPkgs{k},'LP', 0);

    for j=1:length(QPsolverPkgs)%QP solvers

        solverQPOK = changeCobraSolver(QPsolverPkgs{j}, 'QP', 0);

        if solverLPOK && solverQPOK
            substrateRxns ={'EX_glc(e)' 'EX_ac(e)'};
            initConcentrations = [10.8; 1];
            initBiomass = 0.001;
            timeStep = 1/100;
            nSteps = 200;

             %Assert
            [concentrationMatrixtest,excRxnNamestest,timeVectest,biomassVectest,drGenestest,constrainedRxnstest,statestest] = ...
                dynamicRFBA(modelReg,substrateRxns,initConcentrations,initBiomass,timeStep,nSteps);

            tol = eps(0.5);%set tolerance
            %assertions
            assert(any(any(abs(concentrationMatrixtest-concentrationMatrix) < tol)))
            assert(isequal(excRxnNamestest,excRxnNames))
            assert(isequal(timeVectest,timeVec))
            assert(any(abs(biomassVectest-biomassVec) < tol))
            assert(isequal(drGenestest,drGenes))
            assert(isequal(constrainedRxnstest,constrainedRxns))
            assert(isequal(statestest,states))
        end
    end

    fprintf('Done.\n');
end

 % change the directory
 cd(currentDir)


function [trx,fieldnames,data,units] = ReadStructSVMTrxFile(trxfile)

MAXSTRINGLENGTH = 10000;
fid = fopen(trxfile,'rb');
version = fread(fid,1,'double'); %#ok<NASGU>
moviename_length = fread(fid,1,'int');
if moviename_length > MAXSTRINGLENGTH,
  error('Movie name length = %d > %d\n',moviename_length,MAXSTRINGLENGTH);
end
moviename = fread(fid,moviename_length,'char=>char')';
matname_length = fread(fid,1,'int');
if matname_length > MAXSTRINGLENGTH,
  error('Mat name length = %d > %d\n',matname_length,MAXSTRINGLENGTH);
end
matname = fread(fid,matname_length,'char=>char')'; %#ok<NASGU>
nflies = fread(fid,1,'int'); %#ok<NASGU>
fly_ids = fread(fid,1,'int');
firstframe = fread(fid,1,'int');
firstframe = firstframe + 1;
lastframe = fread(fid,1,'int');
lastframe = lastframe + 1;
sex = fread(fid,1,'char=>char');
fps = fread(fid,1,'double');
nfields = fread(fid,1,'int');

fieldnames = cell(1,nfields);
data = cell(1,nfields);
units = cell(1,nfields);
nframes = lastframe-firstframe+1;

for i = 1:nfields,
  fieldname_length = fread(fid,1,'int');
  if fieldname_length > MAXSTRINGLENGTH,
    error('Field name length = %d > %d\n',fieldname_length,MAXSTRINGLENGTH);
  end
  fieldname = fread(fid,fieldname_length,'char=>char')';
  fieldnames{i} = fieldname;
  units_numerator_length = fread(fid,1,'int');
  if units_numerator_length > MAXSTRINGLENGTH,
    error('Units numerator length = %d > %d\n',units_numerator_length,MAXSTRINGLENGTH);
  end
  units_numerator = fread(fid,units_numerator_length,'char=>char')';
  units_numerator = regexp(strtrim(units_numerator),'\s','split');
  idx = strcmp(units_numerator,'1');
  units_numerator(idx) = [];

  units_denominator_length = fread(fid,1,'int');
  if units_denominator_length > MAXSTRINGLENGTH,
    error('Units denominator length = %d > %d\n',units_denominator_length,MAXSTRINGLENGTH);
  end
  units_denominator = fread(fid,units_denominator_length,'char=>char')';
  units_denominator = regexp(strtrim(units_denominator),'\s','split');
  idx = strcmp(units_denominator,'1');
  units_denominator(idx) = [];

  units{i} = struct('numerator',{units_numerator},'denominator',{units_denominator});
  datacurr = fread(fid,nframes,'double');
  data{i} = datacurr(:)';
end

fclose(fid);

trxfns = {'x','y','a','b','theta','x_mm','y_mm','a_mm','b_mm','theta_mm','timestamps'};

trx = struct;
for i = 1:numel(trxfns),
  fn = trxfns{i};
  j = find(strcmp(fn,fieldnames),1);
  if isempty(j), continue; end
  trx.(fn) = data{j};
end

if isfield(trx,'theta') && ~isfield(trx,'theta_mm') && isfield(trx,'x_mm'),
  trx.theta_mm = trx.theta;
end

j = find(strcmp('timestamp',fieldnames),1);
if ~isfield(trx,'timestamps') && ~isempty(j),
  trx.timestamps = data{j};
end
if ~isfield(trx,'timestamps'),
  trx.dt = diff(trx.timestamps);
end
if ~isfield(trx,'dt'),
  trx.dt = repmat(1/fps,[1,nframes-1]);
end
if ~isfield(trx,'timestamps'),
  trx.timestamps = cumsum([(firstframe-1)/fps,trx.dt]);
end

trx.firstframe = firstframe;
trx.off = 1-trx.firstframe;
trx.endframe = lastframe;
trx.nframes = nframes;
trx.id = fly_ids;
trx.moviename = moviename;
trx.sex = sex;
trx.fps = fps;


function [XS] = dataclean_XS(XS)

for i=1:size(XS,1)
  string = XS(i,3);
  string = string{1};
  if(strcmp(string,'Utilities ')==1),
    XS(i,3) = {'Utilities'};
  elseif(strcmp(string,'Services ')==1),
    XS(i,3) = {'Services'};
  elseif(strcmp(string,'#Technology')==1),
    XS(i,3) = {'Technology'};
  end
end

end


function kern = invcmpndKernExpandParam(kern, params)

% INVCMPNDKERNEXPANDPARAM Create kernel structure from INVCMPND kernel's parameters.
% FORMAT
% DESC returns a inv-compound kernel structure filled with the
% parameters in the given vector. This is used as a helper function to
% enable parameters to be optimised in, for example, the NETLAB
% optimisation functions.
% ARG kern : the kernel structure in which the parameters are to be
% placed.
% ARG param : vector of parameters which are to be placed in the
% kernel structure.
% RETURN kern : kernel structure with the given parameters in the
% relevant locations.
%
% SEEALSO : invcmpndKernParamInit, invcmpndKernExtractParam, cmpndkernExpandParam
%
% COPYRIGHT : Andreas C. Damianou, 2012

% KERN

kern = cmpndKernExpandParam(kern, params);


function p = addInequality(p,row)
if ~anyCones(p.K)
    % Just append in the end
    p.F_struc = [p.F_struc;row];
else
    % Insert before conics
    if p.K.f==0 % a bit faster, append on top
        p.F_struc = [row;
                     p.F_struc];
    else
        p.F_struc = [p.F_struc(1:(p.K.f+p.K.l),:);
                     row;
                     p.F_struc(1+p.K.f+p.K.l:end,:)];
    end
end
p.K.l = p.K.l + size(row,1);


function F = subsref(F,Y)
%subsref           Overloaded indexing

F = subsref(lmi(F),Y);