function [signal, state] = flt_sourceLocalize(varargin)
% Localize sources for a given head model.
% Estimated sources and inverse operator will be stored in
% signal.CSD and signal.invOperator
%
% Author: Tim Mullen, Jan 2013, SCCN/INC/UCSD
%         Alejandro Ojeda, Jan 2013, SCCN/INC/UCSD
%         Christian Kothe, Jan 2013, SCCN/INC/UCSD

% FIXME: Need to add a for loop over the calls to Loreta (or add it in the
% flt_loreta function) which loops over sub-partitions of the data block,
% estimating CSD for each partition (i.e. assuming local stationarity of
% the inverse operator within each partition). The invOperator results are
% stored in a 3D array and the mean invOperator is then returned in state
% obj as well as in signal obj.
% FIXME: Implement option to only update inverse operatior infrequently,
% otherwise using the inv op stored in state object (i.e. mean over last
% block).

if ~exp_beginfun('filter'), return; end

declare_properties('name','SourceLocalization', 'experimental',true, 'follows',{'flt_ica','flt_fir','flt_iir'}, 'cannot_precede',{'flt_clean_settings'},'independent_channels',false, 'independent_trials',false);

headmodel_default = 'resources:/headmodels/standard-Colin27-385ch.mat';

% try to get ROI names supported by head model (for use in arg definition)
ROINames = {};
if ~onl_isonline
    hmObj = arg_extract(varargin,{'hmObj','HeadModelObject'},[],headmodel_default);
    if isstruct(hmObj) || (ischar(hmObj) && ~isempty(hmObj))
        hmObj = hlp_validateHeadModelObject(hmObj);
        if ~isempty(hmObj)
            % get the unique ROI names
            [tmp, idx] = unique(hmObj.atlas.label); %#ok<ASGLU>
            ROINames = hmObj.atlas.label(sort(idx))';
        end
    end
end

arg_define(varargin, ...
    arg_norep({'signal','Signal'},[],[],'Signal structure. Must contain .data field with channel data'), ...
    arg({'hmObj','HeadModelObject'},headmodel_default,[],'Head model object generated by MOBILAB. Usually a filename, or a cell array of the form {query,file,query,file,...} where a query is a wildcard pattern that, if matching the sourcedata filename, determines that the subsequent file in the list shall be used  See MOBILAB''s headModel class.','type','expression'), ...
    arg({'channels','Channels'},[],[],'Cell array of channel labels to retain. If empty, all channels in signal.chanlocs.labels will be used. Channels must be a subset of hmObj.getChannelLabels()','type','cellstr','shape','row'), ...
    arg_subswitch({'invMethod','InverseMethod'},{'LORETA'}, ...
    [{...
      'LORETA', @flt_loreta, ...
      'Evidence Approximation', @flt_evidence_approx_tikhonov_reg, ...
      'Variational LORETA', @flt_vbloreta, ...
      'Beamforming', @flt_beamforming}, ...
      quickif(exist('flt_champagne','file'),{'Champagne',@flt_champagne},{})], ...
     'Source Localization Algorithm'), ...
    arg({'sourceAtlasLabels','SourceAtlasLabels','SourceAtlasROI'},ROINames,ROINames,'Source regions of interest (atlas labels). This is a cell array of strings corresponding to a subset of the labels stored in hmObj.atlas.label. Current source density will be estimated only for these ROIs.','type','logical','rangecheck',true), ...
    arg({'colRoiCsd','CollapseRoiCsd'},'sum',{'none','mean','sum','max','maxmag','median'},'Method for computing ROI CSD. Return the (mean, integral, max) current source density within each ROI. signal.srcpot matrix will be reduced to [num_rois x num_samples].'), ...
    arg({'keepFullCsd','KeepFullCsd'},true,[],'Keep a copy of the csd for each vertex. This is stored in srcpot_all'), ...
    arg({'roiAtlasLabels','ROIAtlasLabels','AtlasROI','ROI'},ROINames,ROINames,'Regions of interest atlas labels. This is a cell array of strings corresponding to a subset of the labels stored in hmObj.atlas.label. If CollapseRoiCsd is set, current source density will be integrated over these ROIs. If empty the all ROIs in SourceAtlasLabels are selected.','type','logical','rangecheck',true), ...
    arg({'combineROIs','CombineROI'},[],[],'Combine ROIs in ROIAtlasLabels. This is a cell array of (name, value) pairs. The name entry is a string denoting the name of a new ROI. The value part is a cell array of strings indicating which of the ROIs in ROIAtlasLabels should be combined to form a new ROI. Any ROIs that were merged into a new ROI are removed from the original set of ROIs. To preserve an ROI that was merged into a new ROI, specify a new ROI containing only this ROI. All new ROIs are then appended to the list of existing (unmerged) ROIs in ROIAtlasLabels. If CollapseRoiCsd is set, current source density will be integrated over all ROIs.','type','expression','shape','row'), ...
    arg({'combineMode','CombineMode'},'merge',{'replace','append','merge'},'Mode for combining MetaROIs with original ROIs'), ...
    arg_nogui({'appendROI','AppendROI'},false,[],'Append superROIs or replace'), ...
    arg({'reference','Reference'},[],[],'Reference scheme. This is a cell array of channel labels defining the reference. If more than one channel, average reference is assumed','type','cellstr','shape','row'), ...
    arg_nogui({'roiVertices','ROIVertices'},[],[],'Regions of interest (vertices). This is supplementary to the ''RegionsOfInterest'' option. ''roiVertices'' is a cell array where each cell contains the indices of all vertices within a region of interest. The vertex indices correspond to respective columns of the lead field matrix. These ROIs will be added to the set of ROIs defined in the ''RegionsOfInterest'' option.'), ...
    arg_nogui({'roiVerticesLabels','ROIVerticesLabels'},[],[],'Labels for the supplementary ROIs. This is a cell array of same dimension as ''roiVertices'' with labels for each ROI','type','cellstr'), ...
    arg({'normalCSDWeight','NormalCSDWeight'},0.7,[],'Weight of normal direction in CSD. When calculating the CSD at a given vertex from a lead-field matrix that is not restricted to the surface normal this is the weighting of the current normal to the surface (and tangential current will be weighted by 1 minus this value).','guru',true),...
    arg({'leadfieldOrientation','LeadfieldOrientation'},'axisparallel',{'axisparallel','tangentspace'},'Orientation of local leadfield components. This is for a given source vertex (if orientations are flexible). If axisparallel, the projections will point in X/Y/Z directions, if tangentspace, the projections will point in surface normal, tangent and binormal directions.','guru',true),...
    arg({'makeDipfitStruct','MakeDipfitStruct'},true,[],'Make dipfit structure. If selected, a dipfit structure will be created in signal.dipfit containing the locations (.posxyz) and moments (.mom) of each dipole or center of mass of ROI.'), ...
    arg({'do_transform','TransformData','transform'},false,[],'Transform the data rather than annotate. By default, source reconstructions are added as annotations (.srcpot) to the data set.'),...
    arg({'usecar','CAR'},true,[],'Rereference to common average. This should always be enabled, unless you are really sure about it'), ...
    arg({'verb','Verbosity'},0,[],'Verbosity level.','typecheck',false), ...
    arg_nogui({'state','State'}));

if isempty(roiAtlasLabels) %#ok
    roiAtlasLabels = ROINames;
end
if isempty(sourceAtlasLabels) %#ok
    sourceAtlasLabels = ROINames;
end

% Intialization block
% -------------------------------------------------------------------------
if isempty(state)
    % || (isfield(state,'roiAtlasLabels') && ~isequal(state.roiAtlasLabels,roiAtlasLabels))

    if iscellstr(hmObj)
        % per-subject headmodels given as a cell array of query-model pairsl resolve which one to use
        loader = utl_find_filter(signal,'io_loadset');
        filename = loader.parts{1};
        for k=1:2:length(hmObj)
            pattern = regexptranslate('wildcard',hmObj{k});
            if regexp(filename,pattern)
                hmObj = hmObj{k+1};
                break;
            end
        end
        if iscellstr(hmObj)
            error('No matching headmodel found for the given data filename: %s (patterns: %s)',filename,hlp_tostring(hmObj(1:2:end))); end
    end

    % Either we are initializing our adaptive estimator or user-specified
    % ROIs have changed.
    state = hlp_microcache('sourcespace',@build_state,hmObj,channels,sourceAtlasLabels,roiAtlasLabels,roiVertices,roiVerticesLabels,makeDipfitStruct,verb,signal.chanlocs,combineROIs,combineMode,usecar,normalCSDWeight,leadfieldOrientation);
end

% Estimate Current Source Density
% -------------------------------------------------------------------------

% Current source density is returned in signal.srcpot
% [num_vertices x num_samples]
% Inverse operator is returned in signal.srcweights
% [num_vertices x num_channels]

if usecar
    % apply common average reference
    signal.data = bsxfun(@minus,signal.data,mean(signal.data));
end
switch lower(invMethod.arg_selection)
    case 'loreta'
        [signal, state.solverState] = hlp_scope({'disable_expressions',true}, ...
                                    @flt_loreta,                    ...
                                    'signal',signal,                ...
                                    invMethod,                      ...
                                    'state',state.solverState,      ...
                                    'K',state.leadFieldMatrix,      ...
                                    'L',state.laplacianOperator,    ...
                                    'verb',max(0,verb-1),'arg_direct',true);
        signal.srcweights = state.solverState.srcweights;
    case 'evidence approximation'
        [signal, state.solverState] = hlp_scope({'disable_expressions',true}, ...
                                    @flt_evidence_approx_tikhonov_reg,                  ...
                                    'signal',signal,                ...
                                    invMethod,                      ...
                                    'state',state.solverState,      ...
                                    'K',state.leadFieldMatrix,      ...
                                    'L',state.laplacianOperator,    ...
                                    'verb',verb,'arg_direct',true);
        signal.srcweights = state.solverState.srcweights;
    case 'variational loreta'
        [signal, state.solverState] = hlp_scope({'disable_expressions',true}, ...
                                    @flt_vbloreta,                  ...
                                    'signal',signal,                ...
                                    invMethod,                      ...
                                    'state',state.solverState,      ...
                                    'K',state.leadFieldMatrix,      ...
                                    'L',state.laplacianOperator,    ...
                                    'verb',max(0,verb-1),'arg_direct',true);
        signal.srcweights = state.solverState.srcweights;
    case 'dynamic loreta'
        [signal, state.solverState] = hlp_scope({'disable_expressions',true}, ...
                                    @flt_dynamicloreta,             ...
                                    'signal',signal,                ...
                                    invMethod,                      ...
                                    'state',state.solverState,      ...
                                    'K',state.leadFieldMatrix,      ...
                                    'L',state.laplacianOperator,    ...
                                    'verb',max(0,verb-1),'arg_direct',true);
        signal.srcweights = state.solverState.srcweights;
    case 'beamforming'
        [signal, state.solverState] = hlp_scope({'disable_expressions',true}, ...
                                    @flt_beamforming,                  ...
                                    'signal',signal,                ...
                                    invMethod, ...
                                    'lead_field', state.leadFieldMatrix, ...
                                    'state',state.solverState,      ...
                                    'arg_direct',true);
    case 'champagne'
        [signal, state.solverState] = hlp_scope({'disable_expressions',true}, ...
                                    @flt_champagne,                  ...
                                    'signal',signal,                ...
                                    invMethod,                      ...
                                    'state',state.solverState,      ...
                                    'head_file',struct('leadfield',reshape(state.leadFieldMatrix,state.nChannels,state.nVertices,state.orientationDegrees), ...
                                                       'chanlabels',{{signal.chanlocs.labels}}), ...
                                    'arg_direct',true);

    otherwise
        error('flt_sourceLocalize:badInverseMethod','Invalid inverse method %s',invMethod.arg_selection);
end

% collapse srcpot from 3d to 1d based on state.srcweightCollapseDoFWeights
if state.orientationDegrees == 3
    siz = size(signal.srcpot);
    signal.srcpot = reshape(sum(reshape(bsxfun(@times,signal.srcpot,state.srcweightCollapseDoFWeights),[3 siz(1)/3 siz(2:end)]),1),[siz(1)/3 siz(2:end)]);
end

% Collapse CSD within each ROI
% -------------------------------------------------------------------------
% FIXME: We may wish to replace avg with the surface integral over the ROI
% We can use numerical double integration i.e. quad2d() or more crudely:
% sum(signal.CSD(x,:)) * dx * dy;
% where dx, dy are the voxel x,y dimensions.
% If the csd is not constrained to the surface of a mesh, then we need to
% use triple integration i.e. triplequad(). Or
% sum(signal.CSD(x,:)) * dx * dy * dz

if ~strcmp(colRoiCsd,'none') && ~isempty(signal.srcpot)
    if verb
        fprintf('Computing %s CSD for each ROI \n', colRoiCsd);
    end

    if keepFullCsd
        % store backup with all vertices
        % also, register this as a time-series field
        signal = utl_register_field(signal,'timeseries','srcpot_all',signal.srcpot);
        signal.srcweights_all = signal.srcweights;
    end

    % collapse current density
    signal.srcpot = hlp_colsrc(signal.srcpot,state.roiVerticesReduced,colRoiCsd);
    % collapse weights (note: if weights are time-varying, then whatever snapshot this is isn't
    % going to be too useful)
    if isfield(signal,'srcweights')
        signal.srcweights = hlp_colsrc(signal.srcweights,state.roiVerticesReduced,colRoiCsd); end
else
    signal.srcpot_all       = [];
    signal.srcweights_all   = [];
end

% Store additional outputs in signal
% -------------------------------------------------------------------------
if makeDipfitStruct
    signal.dipfit   = state.dipfit;
end
signal.roiLabels          = state.roiLabels;
signal.roiVertices        = state.roiVertices;
signal.roiVerticesReduced = state.roiVerticesReduced;
signal.rmIndices          = state.rmIndices;

if do_transform
    signal.data = signal.srcpot;
    signal.nbchan = size(signal.data,1);
    signal.chanlocs = struct('labels',cellfun(@num2str,num2cell(1:signal.nbchan,1),'UniformOutput',false));
end
signal.hmObj = hmObj;
signal.leadFieldMatrix = state.leadFieldMatrix;

exp_endfun;

function state = build_state(hmObj,channels,sourceAtlasLabels,roiAtlasLabels,roiVertices,roiVerticesLabels,makeDipfitStruct,verb,chanlocs,combineROIs,combineMode,usecar,normalCSDWeight,leadfieldOrientation)
% validate the head model and construct the source space

orilen = length(roiAtlasLabels);
[res ia] = intersect(roiAtlasLabels,sourceAtlasLabels);
roiAtlasLabels = roiAtlasLabels(sort(ia));  % FIXME: double-check this order
if length(roiAtlasLabels)~=orilen
    warn_once('BCILAB:AtlasLabelMismatch','Some elements of ROIAtlasLabels were not contained in SourceAtlasLabels. These were removed.');
end

% Validate Inputs
hmObj     = hlp_validateHeadModelObject(hmObj);
if isempty(hmObj)
    error('HeadModelObject was improperly defined. Exiting');
end
if ~isempty(roiVerticesLabels) && ~isempty(roiVertices) ...
        && length(roiVertices) ~= length(roiVerticesLabels)
    fprintf('WARNING: The number of ROI labels in ''roiVerticesLabels'' does not match the number of ROIs in ''roiVertices''. Reverting to generic labeling scheme {''ROI1'' ''ROI2'' ... ''ROIN''}.');
    roiVerticesLabels = [];
end

if verb
    fprintf('Initializing source reconstructor \n');
end

% Prune lead field matrix and laplacian operator
% to contain only regions of interest
% If there are no specific ROIs selected, this will "open" the
% surface mesh by removing a few vertices
% (the surface mesh cannot be a closed surface)
if verb
    fprintf('Constructing source space \n');
end

% remove undesired brain structures
% note: we assume that the leadFieldMatrix produced by this step is 2d, but
% contain stacked pages for X, Y and Z dipole components (so that a reshape
% to [#ch,#vox,3] would yield the correct result.
brainStructsToRemove = setdiff(unique(hmObj.atlas.label),sourceAtlasLabels);
[   reducedSpace,                      ...
    state.leadFieldMatrix,             ...
    state.laplacianOperator            ...
    state.rmIndices                    ...
    ] = getSourceSpace4PEB(hmObj,brainStructsToRemove,roiVertices);

state.nVertices = size(reducedSpace.vertices,1);
state.nChannels = length(chanlocs);

% determine the number of orientation degrees of freedom
if size(state.leadFieldMatrix,2) == state.nVertices
    state.orientationDegrees = 1;
elseif size(state.leadFieldMatrix,2) == 3*state.nVertices
    state.orientationDegrees = 3;
else
    error('Your lead-field matrix has a # of columns that does not correspond to the # of source vertices.');
end

% project leadfield matrix onto tangent space
if state.orientationDegrees==3
    % get tangent space at each vertex
    if isprop(hmObj,'surfNormal')
        state.surfNormals = blk_diag(hmObj.surfNormal,1)';
        % prune removed indices
        state.surfNormals(state.rmIndices,:)=[];
    else
        % FIXME: these data contains NaN's (presumably near deleted triangles)
        state.surfNormals = geometricTools.getSurfaceNormals(reducedSpace.vertices,reducedSpace.faces,true);
    end
    state.surfTangents = cross(state.surfNormals,ones(length(state.surfNormals),1)*[1 0 0],2);
    state.surfBinormals = cross(state.surfNormals,state.surfTangents,2);

    % also calc final projection operator
    if strcmp(leadfieldOrientation,'axisparallel')
        state.srcweightCollapseDoFWeights = vec((state.surfNormals*normalCSDWeight + (state.surfTangents+state.surfBinormals)*(1-normalCSDWeight))'); % does assume axis-parallel lead-field
    elseif strcmp(leadfieldOrientation,'tangentspace')
        state.srcweightCollapseDoFWeights = vec((ones(length(state.surfNormals),1)*[normalCSDWeight (1-normalCSDWeight)/2 (1-normalCSDWeight)/2])');  % assumes rotated lead-field
        % turn into triplet order
        tmp = permute(reshape(state.leadFieldMatrix,state.nChannels,state.nVertices,state.orientationDegrees),[1 3 2]);
        % transpose for recombination
        tmp = tmp(:,:)'; siz = size(tmp);
        % recombine into tangent space in pages
        new_matrix = permute(cat(3,reshape(sum(reshape(bsxfun(@times,tmp,vec(state.surfNormals')),[3 siz(1)/3 siz(2:end)]),1),[siz(1)/3 siz(2:end)]), ...
            reshape(sum(reshape(bsxfun(@times,tmp,vec(state.surfTangents')),[3 siz(1)/3 siz(2:end)]),1),[siz(1)/3 siz(2:end)]), ...
            reshape(sum(reshape(bsxfun(@times,tmp,vec(state.surfBinormals')),[3 siz(1)/3 siz(2:end)]),1),[siz(1)/3 siz(2:end)])),[2 1 3]);
        % flatten again
        state.leadFieldMatrix = new_matrix(:,:);
    else
        error('Unsupported LeadfieldOrientation specified: %s',leadfieldOrientation);
    end
end

% We also store the indices of the vertices of each ROI (in the full
% source space) in a cell array. This allows us to obtain dipole
% centroids for each ROI
for k=1:length(roiAtlasLabels)
    state.roiVertices{k} = find(indices4Structure(hmObj,roiAtlasLabels{k}))';
    %         [~,state.roiVertices{k}] = removeStructureFromSourceSpace(hmObj,state.brainStructsToRemove{k});
end
% append any additional ROI defined by 'roiVertices' input argument
state.roiVertices = [state.roiVertices roiVertices];

% get ROI indices into reduced source space. These are used for
% integration over current source density (CSD) within each ROI
LFMcols = 1:length(hmObj.atlas.colorTable);
LFMcols(state.rmIndices) = [];
for k=1:length(state.roiVertices)
    state.roiVerticesReduced{k} = find(ismember(LFMcols,state.roiVertices{k}));
end

% set up the labeling for any vertex-index-defined ROIs
if isempty(roiVerticesLabels) && ~isempty(roiVertices)
    % use generic labeling scheme {'ROI1' 'ROI2' ... 'ROIN'}
    roiVerticesLabels = cellstr(num2str((1:length(roiVertices))'))';
    roiVerticesLabels = cellfun(@(x)['ROI' x],roiVerticesLabels,'UniformOutput',false);
end
state.roiLabels = [roiAtlasLabels roiVerticesLabels];

% if needed, combine ROIs according to rules in combineROIs
if ~isempty(combineROIs)
    if mod(length(combineROIs),2)
        error('BCILAB:BadNameValuePair','CombineROI must be a cell array of name,value pairs');
    end
    newRoiLabels = combineROIs(1:2:end-1);
    roiSubLabels = combineROIs(2:2:end);
    numroi = length(newRoiLabels);
    newRoiVerticesReduced = cell(1,numroi);
    newRoiVertices        = cell(1,numroi);
    roiIdxInAtlas  = cell(1,numroi);
    for k=1:numroi
        matchedLabels = ismember(roiSubLabels{k},roiAtlasLabels);
        if ~all(matchedLabels)
            error('BCILAB:BadROINames', ...
                  'The following ROIs in ''CombineROIs'' are not in the ROIAtlasLabels list: %s', ...
                          hlp_tostring(roiSubLabels{k}(~matchedLabels)));
        end
        roiIdxInAtlas{k}  = find(ismember(roiAtlasLabels,roiSubLabels{k}));
        A = cell2mat(state.roiVerticesReduced(roiIdxInAtlas{k}));
        [tmp, idx] = unique(A);
        newRoiVerticesReduced{k} = A(sort(idx));
        % do the same for roiVertices
        A = cell2mat(state.roiVertices(roiIdxInAtlas{k}));
        [tmp, idx] = unique(A);
        newRoiVertices{k} = A(sort(idx));
    end

    switch combineMode
        case 'merge'
            % remove all merged ROIs ...
            mergedROI = unique(cell2mat(roiIdxInAtlas));
            state.roiVerticesReduced(mergedROI) = [];
            state.roiVertices(mergedROI)        = [];
            state.roiLabels(mergedROI)          = [];

            % ... and append the new ROIs
            state.roiVerticesReduced = [state.roiVerticesReduced newRoiVerticesReduced];
            state.roiVertices        = [state.roiVertices newRoiVertices];
            state.roiLabels          = [state.roiLabels newRoiLabels];
        case 'replace'
            % keep only the new rois
            state.roiVerticesReduced = newRoiVerticesReduced;
            state.roiVertices        = newRoiVertices;
            state.roiLabels          = newRoiLabels;
        case 'append'
            % append the new ROIs
            state.roiVerticesReduced = [state.roiVerticesReduced newRoiVerticesReduced];
            state.roiVertices        = [state.roiVertices newRoiVertices];
            state.roiLabels          = [state.roiLabels newRoiLabels];
    end
end

% prune the lead field matrix to contain only desired channels
hmChanlabels  = lower(hmObj.channelLabel);
if isempty(channels)
    channels = lower({chanlocs.labels});
end

% use only selected channels that are in the head model
% chaninds is also an integer permutation vector so that rows of
% LFM match the ordering of 'channels' variable
[~, ia, ib] = intersect(lower(hmChanlabels),lower(channels),'stable');
if max(ib)>length(ia)
    error('The following channels could not be matched to the headmodel: %s', ...
          hlp_tostring(channels(ib>length(ia))));
end
chaninds = ia; %ia(ib);
% if nnz(chaninds)~=length(channels)
%     error('Some channels could not be matched to the headmodel');
% end
state.leadFieldMatrix = state.leadFieldMatrix(chaninds,:,:);

if usecar
    % apply common-average rereference
    state.leadFieldMatrix = bsxfun(@minus,state.leadFieldMatrix,mean(state.leadFieldMatrix));
end

% load the original source space (non-reduced)
tmp = load(hmObj.surfaces);
fn  = fieldnames(tmp);
state.sourceSpace = tmp.(fn{1})(end);  % dim end = cortical surface

% make dipfit structure containing centroids (dipfit.model.posxyz) and
% surface mesh (dipfit.model.surfmesh) for each ROI as well as complete
% surface mesh (dipfit.surfmesh). We also store the indices of each ROI
% into the complete surface mesh (dipfit.model.meshVertices)
if makeDipfitStruct
    state.dipfit = hlp_makeDipfitStruct(state.sourceSpace,state.roiVertices,reducedSpace);
end

% initialize the state of the solver
state.solverState = [];


function epoch = findLastCheckpoint(modelDir)
% epoch = findLastCheckpoint(modelDir)
%
% Find the last checkpoint to continue previous trainings.
%
% Copyright by Holger Caesar, 2015

list = dir(fullfile(modelDir, 'net-epoch-*.mat'));
tokens = regexp({list.name}, 'net-epoch-([\d]+).mat', 'tokens');
epoch = cellfun(@(x) sscanf(x{1}{1}, '%d'), tokens);
if isempty(epoch)
    epoch = NaN;
else
    epoch = max(epoch);
end


function out_signal_dim = bci_Preflight(in_signal_dim)
% Check bci_Parameters variable (stored by BCI2000) for consistency.
% Out-Signal-Dim = bci_Preflight(In-Signal-Dim)
%
% In:
%  In-Signal-Dim  : a two-element vector of the form [#Channels, #Samples] which describes the input signal's dimensions
%
% Out:
%  Out-Signal-Dim : a two-element vector of the form [#Channels, #Samples] which describes the input signal's dimensions
%
%                                Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                                2010-09-08

global bci_Parameters;

% these variables are shared with Initialize and Process
global model meta_data outrate maxcpu outdims outform;

quicklog('bcilab-log.txt','called with in_signal_dim = [%i, %i]',in_signal_dim(1),in_signal_dim(2));

if in_signal_dim(1) <= 2
    quicklog('bcilab-log.txt','Haha, apparently bci_Preflight is being called with its own output by BCI2000... ignoring that for now.');
    out_signal_dim = in_signal_dim;
    return;
end

% --- model checks ---

quicklog('bcilab-log.txt','checking if model file (%s) is present ...',bci_Parameters.Model{1});

% check if the bci model is present
if ~exist(bci_Parameters.Model{1},'file')
    error('BCILAB:BCI2000_plugin:model_not_found',['Could not find the bci model file specified as ' bci_Parameters.Model '.']); end

% --- stream meta-data checks ---

quicklog('bcilab-log.txt','checking if stream parameters are present...');
quicklog('bcilab-log.txt','declared parameters: %s',hlp_tostring(fieldnames(bci_Parameters)'));

% try to get the sampling rate
try
    srate = sscanf(bci_Parameters.SamplingRate{1},'%i');
    if ~isscalar(srate)
        quicklog('bcilab-log.txt','SamplingRate is ill-formatted: %s...',hlp_tostring(srate));
        error('Sampling rate has the wrong size...');
    end
    quicklog('bcilab-log.txt','SamplingRate is %.1f...',srate);
catch
    quicklog('bcilab-log.txt','SamplingRate is unspecified...');
    srate = [];
end

% try to get the channel names
try
    channels = bci_Parameters.ChannelNames;
    quicklog('bcilab-log.txt','ChannelNames are %s...',hlp_tostring(channels));
catch
    quicklog('bcilab-log.txt','ChannelNames are unspecified...');
    channels = [];
end

quicklog('bcilab-log.txt','checking if the meta-data file (%s) is present...',bci_Parameters.MetaFile{1});

% check if the meta-data file exists
if exist(bci_Parameters.MetaFile{1},'file')
    quicklog('bcilab-log.txt','found meata-data file, now loading...');
    % yes: use it
    meta_data = utl_parse_metadata('datasource',bci_Parameters.MetaFile{1},'srate',srate,'chanlocs',channels);
    quicklog('bcilab-log.txt','success.');
else
    % no: check if the BCI2000 parameters are sufficient to determine the stream format
    quicklog('bcilab-log.txt','Found no meata-data file.');
    if ~isempty(channels) && ~isempty(srate)
        quicklog('bcilab-log.txt','But ChannelNames and SamplingRate are fine, proceeding...');
        % we're fine...
        meta_data = utl_parse_metadata('srate',srate,'chanlocs',channels);
    else
        % we're NOT fine -- take a look at the model to find out the original (source) data set
        quicklog('bcilab-log.txt','ChannelNames or SamplingRate parameters are not specified correctly!');
        quicklog('bcilab-log.txt','Now checking if the original training set was specified in the model and can be retrieved for reference.');

        % try to load the model (jumping through a few hoops, if necessary....)
        try
            quicklog('bcilab-log.txt','Trying to load the model file...');
            model = io_load(bci_Parameters.Model{1});
            quicklog('bcilab-log.txt','success.');
            if ~isfield(model,'tracking') || ~isfield(model.tracking,'prediction_function')
                quicklog('bcilab-log.txt','But the model file is lacking the correct properties - apparently the file contains a variable that is the model.');
                % the loaded model is lacking the appropriate fields; check if there are variables
                % in the loaded data which are valid models
                candidates = {};
                for f = fieldnames(model)'
                    fname = f{1};
                    if isfield(model.(fname),'tracking') && isfield(model.(fname).tracking,'prediction_function')
                        candidates{end+1} = fname; end %#ok<AGROW>
                end
                if length(candidates) > 1
                    quicklog('bcilab-log.txt','Model file contains multiple candidate model variables; choice is ambiguous - aborting...');
                    error('BCILAB:bci_Preflight:ambiguous',['The file given as the model contains multiple candiate variables:\n' ...
                        hlp_tostring(candidates) '; please pass a file name which contains only one model variable.']);
                elseif isempty(candidates)
                    quicklog('bcilab-log.txt','Model file contains no candidate model variable - aborting...');
                    error('BCILAB:bci_Preflight:load_error','The given file contains no valid model.');
                else
                    quicklog('bcilab-log.txt','Model was successfully identified.');
                    model = model.(candidates{1});
                end
            end
        catch le
            quicklog('bcilab-log.txt','Error loading the model; traceback: %s',le.message);
            error('BCILAB:bci_Preflight:load_error',['The given model string could not be parsed; traceback: ' le.message]);
        end

        % now try to load the source data...
        if ~isfield(model,'source_data')
            quicklog('bcilab-log.txt','Model contains no source_data field, thus the source data set cannot be identified - aborting.');
            error('BCILAB:bci_Preflight:load_error','The given model is lacking a source_data field (which should contain the name of the training set.');
        end
        if ~all(isfield(model.source_data,{'head','parts'}))
            quicklog('bcilab-log.txt','Model has a source_data field, but it does not refer to a single data set - aborting.');
            error('BCILAB:bci_Preflight:load_error','The source_data field of the given model does not refer to a single data set (but some structure that is unrecognized by this plugin). You will have to specify the stream meta-data either via the MetaFile field or via SamplingRate and ChannelNames parameters.');
        end

        quicklog('bcilab-log.txt','Successfully extracted the meta-data from the source file.');
        meta_data = exp_eval(model.source_data);

        if isfield(meta_data,'tracking')
            meta_data = rmfield(meta_data,'tracking');
            quicklog('bcilab-log.txt','Stripped ''tracking'' field from meta-data.');
        end
        if isfield(meta_data,'data')
            meta_data = rmfield(meta_data,'data');
            quicklog('bcilab-log.txt','Stripped ''data'' field from meta-data.');
        end

        if ~isempty(srate)
            quicklog('bcilab-log.txt','Overriding the sampling rate with BCI2000''s %.1f',srate);
            meta_data.srate = srate;
        end
    end
end

quicklog('bcilab-log.txt','Determined sampling rate is: %.1f',meta_data.srate);
quicklog('bcilab-log.txt','Determined channel count: %i',length(meta_data.chanlocs));
quicklog('bcilab-log.txt','Determined channel names are: %s',hlp_tostring({meta_data.chanlocs.labels}));

% check that the input signal dimension matches the number of channel names
if length(meta_data.chanlocs) ~= in_signal_dim(1)
    quicklog('bcilab-log.txt','Number of channel names (%i) does not match the input signal channel # supplied by BCI2000 (%i) - aborting...',length(meta_data.chanlocs),in_signal_dim(1));
    error('BCILAB:BCI2000_plugin:metadata_inconsistent',['The number of channel names (' num2str(length(meta_data.chanlocs)) ', specified in the ChannelNames parameter or the MetaFile) does not match the signal''s channel count (' num2str(in_signal_dim(1)) ').']);
end

quicklog('bcilab-log.txt','successfully matched specified and supplied channel numbers.');

% --- get remaining parameters ---
outforms = {'expectation','distribution','mode'};
outform = outforms{str2num(bci_Parameters.OutputForm{1})};
outrate = str2num(bci_Parameters.OutputRate{1});
maxcpu = str2num(bci_Parameters.MaxCPU{1});

quicklog('bcilab-log.txt','OutputForm is %s',outform);
quicklog('bcilab-log.txt','OutputRate is %.1f',outrate);
quicklog('bcilab-log.txt','MaxCPU is %.1f',maxcpu);

outdims = str2num(bci_Parameters.OutputChannels{1});
if outdims == 0
    quicklog('bcilab-log.txt','No number of output channels was specified; assuming univariate regression or classification...');
    if strcmp(outform,'distribution')
        quicklog('bcilab-log.txt','Since the OutputForm is ''distribution'', assuming that the output signal has 2 channels.');
        outdims = 2;
    else
        quicklog('bcilab-log.txt','Since the OutputForm is either ''mode'' or ''expectation'', assuming that the output signal has 1 channel.');
        outdims = 1;
    end
    % no output channel # specified; assume that we're doing univariate regression or classification
    disp(['Number of BCI output channels was not specified; assuming it is ' num2str(outdims)]);
end

out_signal_dim = [outdims, in_signal_dim(2)];

quicklog('bcilab-log.txt','Returing out_signal_dim = [%i, %i]',out_signal_dim(1),out_signal_dim(2));

if ~isempty(meta_data)
    quicklog('bcilab-log.txt','global meta_data variable is properly initialized.'); end

quicklog('bcilab-log.txt','finished successfully...');


function actdistr()
    % activate deviation input editors in random parameters window
    %
    % This code is responsable for the activation (enable, on) of the std. deviation input
    % editors in the random parameters input window. It is called from randomcat.m,
    % Francesco Pacchiani 6/2000
    %
    % turned into function by Celso G Reyes 2017

    ZG=ZmapGlobal.Data; % used by get_zmap_globals
    if distr == 4

        set(input9, 'enable', 'on');
        set(input10, 'enable', 'on');
        set(input11, 'enable', 'on');
        set(tx9, 'color', 'k');
        set(tx10, 'color', 'k');
        set(tx11, 'color', 'k');

    elseif distr == 6

        set(input3, 'enable', 'off');
        set(input4, 'enable', 'off');
        set(input5, 'enable', 'off');
        set(input6, 'enable', 'off');
        set(input7, 'enable', 'off');
        set(input8, 'enable', 'off');
        set(input9, 'enable', 'off');
        set(input10, 'enable', 'off');
        set(input11, 'enable', 'off');
        set(tx3, 'color', 'w');
        set(tx4, 'color', 'w');
        set(tx5, 'color', 'w');
        set(tx6, 'color', 'w');
        set(tx7, 'color', 'w');
        set(tx8, 'color', 'w');
        set(tx9, 'color', 'w');
        set(tx10, 'color', 'w');
        set(tx11, 'color', 'w');

    elseif distr == 1  ||  distr == 2  ||  distr == 3  ||  distr == 5

        set(input3, 'enable', 'on');
        set(input4, 'enable', 'on');
        set(input5, 'enable', 'on');
        set(input6, 'enable', 'on');
        set(input7, 'enable', 'on');
        set(input8, 'enable', 'on');
        set(input9, 'enable', 'off');
        set(input10, 'enable', 'off');
        set(input11, 'enable', 'off');
        set(tx3, 'color', 'k');
        set(tx4, 'color', 'k');
        set(tx5, 'color', 'k');
        set(tx6, 'color', 'k');
        set(tx7, 'color', 'k');
        set(tx8, 'color', 'k');
        set(tx9, 'color', 'w');
        set(tx10, 'color', 'w');
        set(tx11, 'color', 'w');

    end

end


% DESCRIPTION:
%       subscript reorder the sensor points if a binary sensor mask was
%       used for Cartesian sensor mask nearest neighbour interpolation
%
% ABOUT:
%       author      - Bradley Treeby
%       date        - 2nd September 2012
%       last update - 18th August 2014
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave is free software: you can
% redistribute it and/or modify it under the terms of the GNU Lesser
% General Public License as published by the Free Software Foundation,
% either version 3 of the License, or (at your option) any later version.
%
% k-Wave 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 Lesser General Public License for
% more details.
%
% You should have received a copy of the GNU Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

% update command line status
disp('  reordering Cartesian measurement data...');

if record.p
    % calculate the position for reordering data
    new_col_pos = length(sensor_data.p(1,:)) + 1;

    % append the reordering data
    sensor_data.p(:, new_col_pos) = reorder_index;

    % reorder based on the order_index
    sensor_data.p = sortrows(sensor_data.p, new_col_pos);

    % remove the reordering data
    sensor_data.p = sensor_data.p(:, 1:new_col_pos - 1);
end

if record.p_max
    % append the reordering data
    sensor_data.p_max(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.p_max = sortrows(sensor_data.p_max, 2);

    % remove the reordering data
    sensor_data.p_max = sensor_data.p_max(:, 1);
end

if record.p_min
    % append the reordering data
    sensor_data.p_min(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.p_min = sortrows(sensor_data.p_min, 2);

    % remove the reordering data
    sensor_data.p_min = sensor_data.p_min(:, 1);
end

if record.p_rms
    % append the reordering data
    sensor_data.p_rms(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.p_rms = sortrows(sensor_data.p_rms, 2);

    % remove the reordering data
    sensor_data.p_rms = sensor_data.p_rms(:, 1);
end

if record.u
    % calculate the position for reordering data
    new_col_pos = length(sensor_data.ux(1,:)) + 1;

    % append the reordering data
    sensor_data.ux(:, new_col_pos) = reorder_index;

    % reorder based on the order_index
    sensor_data.ux = sortrows(sensor_data.ux, new_col_pos);

    % remove the reordering data
    sensor_data.ux = sensor_data.ux(:, 1:new_col_pos - 1);

    % repeat for y-dimension if 2D or 3D
    if kgrid.dim > 1
        sensor_data.uy(:, new_col_pos) = reorder_index;
        sensor_data.uy = sortrows(sensor_data.uy, new_col_pos);
        sensor_data.uy = sensor_data.uy(:, 1:new_col_pos - 1);
    end

    % repeat for z-dimension if 3D
    if kgrid.dim > 2
        sensor_data.uz(:, new_col_pos) = reorder_index;
        sensor_data.uz = sortrows(sensor_data.uz, new_col_pos);
        sensor_data.uz = sensor_data.uz(:, 1:new_col_pos - 1);
    end
end

if record.u_max
    % append the reordering data
    sensor_data.ux_max(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.ux_max = sortrows(sensor_data.ux_max, 2);

    % remove the reordering data
    sensor_data.ux_max = sensor_data.ux_max(:, 1);

    % repeat for y-dimension if 2D or 3D
    if kgrid.dim > 1
        sensor_data.uy_max(:, 2) = reorder_index;
        sensor_data.uy_max = sortrows(sensor_data.uy_max, 2);
        sensor_data.uy_max = sensor_data.uy_max(:, 1);
    end

    % repeat for z-dimension if 3D
    if kgrid.dim > 2
        sensor_data.uz_max(:, 2) = reorder_index;
        sensor_data.uz_max = sortrows(sensor_data.uz_max, 2);
        sensor_data.uz_max = sensor_data.uz_max(:, 1);
    end
end

if record.u_min
    % append the reordering data
    sensor_data.ux_min(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.ux_min = sortrows(sensor_data.ux_min, 2);

    % remove the reordering data
    sensor_data.ux_min = sensor_data.ux_min(:, 1);

    % repeat for y-dimension if 2D or 3D
    if kgrid.dim > 1
        sensor_data.uy_min(:, 2) = reorder_index;
        sensor_data.uy_min = sortrows(sensor_data.uy_min, 2);
        sensor_data.uy_min = sensor_data.uy_min(:, 1);
    end

    % repeat for z-dimension if 3D
    if kgrid.dim > 2
        sensor_data.uz_min(:, 2) = reorder_index;
        sensor_data.uz_min = sortrows(sensor_data.uz_min, 2);
        sensor_data.uz_min = sensor_data.uz_min(:, 1);
    end
end

if record.u_rms
    % append the reordering data
    sensor_data.ux_rms(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.ux_rms = sortrows(sensor_data.ux_rms, 2);

    % remove the reordering data
    sensor_data.ux_rms = sensor_data.ux_rms(:, 1);

    % repeat for y-dimension if 2D or 3D
    if kgrid.dim > 1
        sensor_data.uy_rms(:, 2) = reorder_index;
        sensor_data.uy_rms = sortrows(sensor_data.uy_rms, 2);
        sensor_data.uy_rms = sensor_data.uy_rms(:, 1);
    end

    % repeat for z-dimension if 3D
    if kgrid.dim > 2
        sensor_data.uz_rms(:, 2) = reorder_index;
        sensor_data.uz_rms = sortrows(sensor_data.uz_rms, 2);
        sensor_data.uz_rms = sensor_data.uz_rms(:, 1);
    end
end

if record.I
    % calculate the position for reordering data
    new_col_pos = length(sensor_data.Ix(1,:)) + 1;

    % append the reordering data
    sensor_data.Ix(:, new_col_pos) = reorder_index;

    % reorder based on the order_index
    sensor_data.Ix = sortrows(sensor_data.Ix, new_col_pos);

    % remove the reordering data
    sensor_data.Ix = sensor_data.Ix(:, 1:new_col_pos - 1);

    % repeat for y-dimension if 2D or 3D
    if kgrid.dim > 1
        sensor_data.Iy(:, new_col_pos) = reorder_index;
        sensor_data.Iy = sortrows(sensor_data.Iy, new_col_pos);
        sensor_data.Iy = sensor_data.Iy(:, 1:new_col_pos - 1);
    end

    % repeat for z-dimension if 3D
    if kgrid.dim > 2
        sensor_data.Iz(:, new_col_pos) = reorder_index;
        sensor_data.Iz = sortrows(sensor_data.Iz, new_col_pos);
        sensor_data.Iz = sensor_data.Iz(:, 1:new_col_pos - 1);
    end
end

if record.I_avg
    % append the reordering data
    sensor_data.Ix_avg(:, 2) = reorder_index;

    % reorder based on the order_index
    sensor_data.Ix_avg = sortrows(sensor_data.Ix_avg, 2);

    % remove the reordering data
    sensor_data.Ix_avg = sensor_data.Ix_avg(:, 1);

    % repeat for y-dimension if 2D or 3D
    if kgrid.dim > 1
        sensor_data.Iy_avg(:, 2) = reorder_index;
        sensor_data.Iy_avg = sortrows(sensor_data.Iy_avg, 2);
        sensor_data.Iy_avg = sensor_data.Iy_avg(:, 1);
    end

    % repeat for z-dimension if 3D
    if kgrid.dim > 2
        sensor_data.Iz_avg(:, 2) = reorder_index;
        sensor_data.Iz_avg = sortrows(sensor_data.Iz_avg, 2);
        sensor_data.Iz_avg = sensor_data.Iz_avg(:, 1);
    end
end


%CURLABLIST Get current label list
%
% [LABLISTNUMBER,LABLISTNAME,T0,T1] = CURLABLIST(A)
%
% INPUT
%   A             - Dataset
%
% OUTPUT
%   LABLISTNUMBER - Number of current lablist
%   LABLISTNAME   - Name of current label list
%		T0            - Start column current targets
%   T1            - End column current targets
%
% DESCRIPTION
% The number and name of the current label list are returned.
% If T1<T0: no tergets (or soft labels) set for current label list.
%
% SEE ALSO (PRTools Guide)
% DATASETS, MULTI_LABELING, ADDLABLIST, CHANGELABLIST


function display_regions(bndinfo, regions, type, varargin)

sps = region_to_sp(bndinfo, regions, type, varargin{:});
mask = sps(bndinfo.wseg);

imagesc(mask);
axis image


function list = find(varargin)
% XMLTREE/FIND Find elements in a tree with specified characteristics
% FORMAT list = find(varargin)
%
% tree  - XMLTree object
% xpath - string path with specific grammar (XPath)
% uid   - lists of root uid's
% parameter/value - pair of pattern
% list  - list of uid's of matched elements
%
%      list = find(tree,xpath)
%      list = find(tree,parameter,value[,parameter,value])
%      list = find(tree,uid,parameter,value[,parameter,value])
%
% Grammar for addressing parts of an XML document:
%        XML Path Language XPath (http://www.w3.org/TR/xpath)
% Example: /element1//element2[1]/element3[5]/element4
%__________________________________________________________________________
%
% Find elements in an XML tree with specified characteristics or given
% a path (using a subset of XPath language).
%__________________________________________________________________________
% Copyright (C) 2002-2011  http://www.artefact.tk/

% Guillaume Flandin
% $Id: find.m 4460 2011-09-05 14:52:16Z guillaume $

% TODO:
%   - clean up subroutines
%   - find should only be documented using XPath (other use is internal)
%   - handle '*', 'last()' in [] and '@'
%   - if a key is invalid, should rather return [] than error ?

if nargin==0
    error('[XMLTree] A tree must be provided');
elseif nargin==1
    list = 1:length(tree.tree);
    return
elseif mod(nargin,2)
    list = sub_find_subtree1(varargin{1}.tree,root(varargin{1}),varargin{2:end});
elseif isa(varargin{2},'double') && ...
       ndims(varargin{2}) == 2 && ...
       min(size(varargin{2})) == 1
    list = unique(sub_find_subtree1(varargin{1}.tree,varargin{2:end}));
elseif nargin==2 && ischar(varargin{2})
    list = sub_pathfinder(varargin{:});
else
   error('[XMLTree] Arguments must be parameter/value pairs.');
end

%==========================================================================
function list = sub_find_subtree1(varargin)
    list = [];
    for i=1:length(varargin{2})
        res = sub_find_subtree2(varargin{1},...
                varargin{2}(i),varargin{3:end});
        list = [list res];
    end

%==========================================================================
function list = sub_find_subtree2(varargin)
    uid = varargin{2};
    list = [];
    if sub_comp_element(varargin{1}{uid},varargin{3:end})
        list = [list varargin{1}{uid}.uid];
    end
    if isfield(varargin{1}{uid},'contents')
        list = [list sub_find_subtree1(varargin{1},...
                varargin{1}{uid}.contents,varargin{3:end})];
    end

%==========================================================================
function match = sub_comp_element(varargin)
match = 0;
try
    % v = getfield(varargin{1}, varargin{2}); % slow...
    for i=1:floor(nargin/2)
        v = subsref(varargin{1}, struct('type','.','subs',varargin{i+1}));
        if strcmp(v,varargin{i+2})
            match = 1;
        end
    end
catch
end

% More powerful but much slower
%match = 0;
%for i=1:length(floor(nargin/2)) % bug: remove length !!!
%   if isfield(varargin{1},varargin{i+1})
%     if ischar(getfield(varargin{1},varargin{i+1})) & ischar(varargin{i+2})
%      if strcmp(getfield(varargin{1},varargin{i+1}),char(varargin{i+2}))
%           match = 1;
%        end
%     elseif isa(getfield(varargin{1},varargin{i+1}),'double') & ...
%           isa(varargin{i+2},'double')
%        if getfield(varargin{1},varargin{i+1}) == varargin{i+2}
%           match = 1;
%       end
%    else
%       warning('Cannot compare different objects');
%     end
%   end
%end

%==========================================================================
function list = sub_pathfinder(tree,pth)
    %- Search for the delimiter '/' in the path
    i = strfind(pth,'/');
    %- Begin search by root
    list = root(tree);
    %- Walk through the tree
    j = 1;
    while j <= length(i)
        %- Look for recursion '//'
        if j<length(i) && i(j+1)==i(j)+1
            recursive = 1;
            j = j + 1;
        else
            recursive = 0;
        end
        %- Catch the current tag 'element[x]'
        if j ~= length(i)
            element = pth(i(j)+1:i(j+1)-1);
        else
            element = pth(i(j)+1:end);
        end
        %- Search for [] brackets
        k = xml_findstr(element,'[',1,1);
        %- If brackets are present in current element
        if ~isempty(k)
            l   = xml_findstr(element,']',1,1);
            val = str2num(element(k+1:l-1));
            element = element(1:k-1);
        end
        %- Use recursivity
        if recursive
            list = find(tree,list,'name',element);
        %- Just look at children
        else
            if i(j)==1 % if '/root/...' (list = root(tree) in that case)
                if sub_comp_element(tree.tree{list},'name',element)
                    % list = 1; % list still contains root(tree)
                else
                    list = [];
                    return;
                end
            else
                list = sub_findchild(tree,list,element);
            end
        end
        % If an element is specified using a key
        if ~isempty(k)
            if val < 1 || val > length(list)+1
                error('[XMLTree] Bad key in the path.');
            elseif val == length(list)+1
                list = [];
                return;
            end
            list = list(val);
        end
        if isempty(list), return; end
        j = j + 1;
    end

%==========================================================================
function list = sub_findchild(tree,listt,elmt)
    list = [];
    for a=1:length(listt)
        for b=1:length(tree.tree{listt(a)}.contents)
            if sub_comp_element(tree.tree{tree.tree{listt(a)}.contents(b)},'name',elmt)
                list = [list tree.tree{tree.tree{listt(a)}.contents(b)}.uid];
            end
        end
    end


%% patchExtrude
% Below is a demonstration of the features of the |patchExtrude| function

%%
clear; close all; clc;

%% Syntax
% |[Et,Vt]=patchExtrude(F,V,zRange);|

%% Description
% UNDOCUMENTED
%% Examples
%
%%
%
% <<gibbVerySmall.gif>>
%
% _*GIBBON*_
% <www.gibboncode.org>
%
% _Kevin Mattheus Moerman_, <gibbon.toolbox@gmail.com>
%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% 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.
%
% 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, see <http://www.gnu.org/licenses/>.


function VLMplay(varargin)
% Visualizes the polygons from a video and the tracks if available inside the annotation.
%
% To play the annotations only:
% VLMplay(annotation)
%
% To get video for selected frames
% VLMplay(D, ndx, HOMEVIDEOS, frameSkipRate)

switch length(varargin)
    case 1
        v = varargin{1};
        annotation = v.annotation;
        videoFramesPath = '';

    case 3
        D = varargin{1};
        ndx = varargin{2};

        if ndx>length(D)
            error('Index outside database range')
        end

        HOMEVIDEOS = varargin{3};

        annotation = D(ndx).annotation;
        videoFramesPath = fullfile(HOMEVIDEOS, annotation.folder, annotation.filename(1:end-4));

    case 4
        D = varargin{1};
        ndx = varargin{2};
        if ndx>length(D)
            error('Index outside database range')
        end

        HOMEVIDEOS = varargin{3};

        annotation = D(ndx).annotation;
        videoFramesPath = fullfile(HOMEVIDEOS, annotation.folder, annotation.filename(1:end-4));
        frameSkipRate = varargin{4};

    otherwise
        error('wrong number of input arguments.')
end

numFrames = str2num(annotation.numFrames);
% Define colors
colors = hsv(15);

if ~isempty(videoFramesPath)
    % Open the directory containing the frames
    videoDir = dir(fullfile(videoFramesPath, '*.jpg'));
end

%default if frameIdxs is not given
if(~exist('frameSkipRate', 'var'))
    frameSkipRate = 1;
end

% Draw image
figure(1);
% if we want to resize, uncomment the next line:
%set(gcf, 'position', [183   712   202   167]);
rootName = annotation.filename(1:end-4);

for f = 1:frameSkipRate:numFrames
    [annotation, frame] = VLMgetvideoframe(D(ndx).annotation, f, HOMEVIDEOS);
    [nrows ncols cc] = size(frame);
    hold off;
    LMplot(annotation, frame)

    hold on
    plot([annotation.tracks.track.x], [annotation.tracks.track.y], 'o', 'MarkerFaceColor','r', 'MarkerEdgeColor','y', 'MarkerSize', 5)

    axis([1 ncols 1 nrows])
    drawnow
end


%Arrival the blueprint for Arrival objects in GISMO
% An Arrival object is a container for phase arrival metadata
% See also Catalog
classdef Arrival
    properties(Dependent)
    %properties
        channelinfo
        time
        %arid
        %jdate
        iphase
        %deltim
        %azimuth
        %delaz
        %slow
        %delslo
        %ema
        %rect
        amp
        per
        %clip
        %fm
        signal2noise
        %qual
        %auth
    end
    properties
        waveforms
    end
    properties(Hidden)
        table
    end
    methods
        function obj = Arrival(sta, chan, time, iphase, varargin)
            % Parse required, optional and param-value pair arguments,
            % set default values, and add validation conditions
            p = inputParser;
            p.addRequired('sta', @iscell);
            p.addRequired('chan', @iscell);
            %p.addRequired('time', @(t) t>0 & t<now+1);
            p.addRequired('time', @isnumeric);
            p.addRequired('iphase', @iscell);
            p.addParameter('amp', NaN, @isnumeric);
            p.addParameter('per', NaN, @isnumeric);
            p.addParameter('signal2noise', NaN, @isnumeric);

            % Missed several properties out here just because of laziness.
            % Add them as needed.
            p.parse(sta, chan, time, iphase, varargin{:});
            fields = fieldnames(p.Results);
            for i=1:length(fields)
                field=fields{i};
                val = p.Results.(field);
                eval(sprintf('%s = val;',field));
            end
            ctag = ChannelTag.array('',sta,'',chan)';
            obj.table = table(time, datestr(time,26), datestr(time,'HH:MM'), datestr(time,'SS.FFF'), ctag.string(), iphase, amp, per, signal2noise, ...
                'VariableNames', {'time' 'date' 'hour_minute' 'second' 'channelinfo' 'iphase' 'amp' 'per' 'signal2noise'})
            obj.table = sortrows(obj.table, 'time', 'ascend');
            fprintf('\nGot %d arrivals\n',height(obj.table));edit
            misc_fields = struct;

        end

        function val = get.time(obj)
            val = obj.table.time;
        end

        function val = get.channelinfo(obj)
            val = obj.table.channelinfo;
        end

        function val = get.iphase(obj)
            val = obj.table.iphase;
        end

        function val = get.amp(obj)
            val = obj.table.amp;
        end

        function val = get.signal2noise(obj)
            val = obj.table.signal2noise;
        end

        function obj = set.amp(obj, amp)
            obj.table.amp = amp;
        end

        function summary(obj, showall)
        % ARRIVAL.SUMMARY Summarise Arrival object
            for c=1:numel(obj)
                obj(c)
                numarrs = height(obj(c).table)
                fprintf('Number of arrivals: %d\n',numarrs);
                if numarrs > 0
                    if ~exist('showall','var')
                            showall = false;
                    end
                    if numel(obj) == 1
                        if height(obj.table) <= 50 || showall
                            disp(obj.table)
                        else
                            disp(obj.table([1:50],:))

                            disp('* Only showing first 50 rows/arrivals - to see all rows/arrivals use:')
                            disp('*      arrivalObject.disp(true)')
                        end
                    end
                end
            end
        end

        function self2 = subset(self, columnname, findval)
            self2 = self;
            N = numel(self.time);
            indexes = [];
            if ~exist('findval','var')
                % assume columnname is actually row numbers
                indexes = columnname;
            else

                for c=1:N
                    gotval = eval(sprintf('self.%s(c);',columnname));
                    if isa(gotval,'cell')
                        gotval = cell2mat(gotval);
                    end
                    if isnumeric(gotval)
                        if gotval==findval
                            indexes = [indexes c];
                        end
                    else
                        if strcmp(gotval,findval)
                            indexes = [indexes c];
                        end
                    end
                end
            end
            self2.table = self.table(indexes,:);

%             % now go into misc_fields and apply same index subset to
%             % anything with N elements
%             fields = fieldnames(self.misc_fields);
%             for fieldnum=1:numel(fields)
%                 fieldval = getfield(self.misc_fields, fields{fieldnum});
%                 if numel(fieldval)==N
%                     self2.misc_fields = setfield(self2.misc_fields, fields{fieldnum}, fieldval(indexes));
%                 end
%             end

            self2.waveforms = self.waveforms(indexes);

        end

        % prototypes
        catalogobj = associate(self, maxTimeDiff)
        %arrivalobj = setminman(self, w, pretrig, posttrig, maxtimediff)
        arrivalobj = addmetrics(self, maxtimediff)
        arrivalobj = addwaveforms(self, datasourceobj, pretrigsecs, posttrigsecs);
    end
    methods(Static)
        function self = retrieve(dataformat, varargin)
        %ARRIVAL.RETRIEVE Read arrivals from common file formats & data sources.
        % retrieve can read phase arrivals from different earthquake catalog file
        % formats (e.g. Seisan, Antelope) and data sources (e.g. IRIS DMC) into a
        % GISMO Catalog object.
        %
        % Usage:
        %       arrivalObject = ARRIVAL.RETRIEVE(dataformat, 'param1', _value1_, ...
        %                                                   'paramN', _valueN_)
        %
        % dataformat may be:
        %
        %   * 'iris' (for IRIS DMC, using irisFetch.m),
        %   * 'antelope' (for a CSS3.0 Antelope/Datascope database)
        %   * 'seisan' (for a Seisan database with a REA/YYYY/MM/ directory structure)
        %   * 'zmap' (converts a Zmap data strcture to a Catalog object)
        %
        % See also CATALOG, IRISFETCH, CATALOG_COOKBOOK

        % Author: Glenn Thompson (glennthompson1971@gmail.com)

        %% To do:
        % Implement name-value parameter pairs for all methods
        % Test the Antelope method still works after factoring out db_load_origins
        % Test the Seisan method more
        % Add in support for 'get_arrivals'

            debug.printfunctionstack('>')
            self = [];
            switch lower(dataformat)
                case {'css3.0','antelope', 'datascope'}
                    if admin.antelope_exists()
                        try
                            self = Arrival.read_arrivals.antelope(varargin{:});
                        catch
                            % no arrivals
                        end
                    else
                        warning('Antelope toolbox for MATLAB not found')
                    end
                case 'hypoellipse'
                    self = read_hypoellipse(varargin{:});
                otherwise
                    self = NaN;
                    fprintf('format %s unknown\n\n',dataformat);
            end

            debug.printfunctionstack('<')
        end

        %cookbook()
    end
end


%kvsupgen 'Sup-Generating Operator (Hit-Miss)'
% This MatLab function was automatically generated by a converter (KhorosToMatLab) from the Khoros vsupgen.pane file
%
% Parameters:
% InputFile: i 'Input Image', required: 'input image'
% InputFile: str1 'Struct Element 1', required: 'Structuring Element'
% InputFile: str2 'Struct Element 2', required: 'Structuring Element'
% OutputFile: o 'Output Image ', required: 'resulting output image'
%
% Example: o = kvsupgen({i, str1, str2}, {'i','';'str1','';'str2','';'o',''})
%
% Khoros helpfile follows below:
%
%  PROGRAM
% vsupgen - Sup-Generating Operator (Hit-Miss)
%
%  DESCRIPTION
% .I vsupgen
% will do the sup-generating of an input image by two set structuring elements.
%
% The output image data type will be the same as that of the highest data type
% of the input image.
%
%
%
%  EXAMPLES
% vsupgen -i ball.xv -str1 b272.str -str2 b273.str -o outimage.xv
%
% Will do the sup-generating of image "ball.xv" by the structuring set b272.str and b273.str, with the resulting image written to "outimage.xv".
%
%  "SEE ALSO"
% vcast1(1)
%
%  RESTRICTIONS
% .I vsupgen
% can be defined for all data types supported by Khoros, but at the moment it has been implemented just for the bit and unsigned char types.
% The structuring elements are subsets of the 3x3 matrix and the origin is always at the center of this matrix.
%
%  REFERENCES
%
%  COPYRIGHT
% Copyright (C) 1993-1997 Junior Barrera, Roberto Lotufo.  All rights reserved.
%

function varargout = kvsupgen(varargin)
if nargin ==0
  Inputs={};arglist={'',''};
elseif nargin ==1
  Inputs=varargin{1};arglist={'',''};
elseif nargin ==2
  Inputs=varargin{1}; arglist=varargin{2};
else error('Usage: [out1,..] = kvsupgen(Inputs,arglist).');
end
if size(arglist,2)~=2
  error('arglist must be of form {''ParameterTag1'',value1;''ParameterTag2'',value2}')
 end
narglist={'i', '__input';'str1', '__input';'str2', '__input';'o', '__output'};
maxval={0,0,0,0};
minval={0,0,0,0};
istoggle=[0,0,0,0];
was_set=istoggle * 0;
paramtype={'InputFile','InputFile','InputFile','OutputFile'};
% identify the input arrays and assign them to the arguments as stated by the user
if ~iscell(Inputs)
Inputs = {Inputs};
end
NumReqOutputs=1; nextinput=1; nextoutput=1;
  for ii=1:size(arglist,1)
  wasmatched=0;
  for jj=1:size(narglist,1)
   if strcmp(arglist{ii,1},narglist{jj,1})  % a given argument was matched to the possible arguments
     wasmatched = 1;
     was_set(jj) = 1;
     if strcmp(narglist{jj,2}, '__input')
      if (nextinput > length(Inputs))
        error(['Input ' narglist{jj,1} ' has no corresponding input!']);
      end
      narglist{jj,2} = 'OK_in';
      nextinput = nextinput + 1;
     elseif strcmp(narglist{jj,2}, '__output')
      if (nextoutput > nargout)
        error(['Output nr. ' narglist{jj,1} ' is not present in the assignment list of outputs !']);
      end
      if (isempty(arglist{ii,2}))
        narglist{jj,2} = 'OK_out';
      else
        narglist{jj,2} = arglist{ii,2};
      end

      nextoutput = nextoutput + 1;
      if (minval{jj} == 0)
         NumReqOutputs = NumReqOutputs - 1;
      end
     elseif isstr(arglist{ii,2})
      narglist{jj,2} = arglist{ii,2};
     else
        if strcmp(paramtype{jj}, 'Integer') & (round(arglist{ii,2}) ~= arglist{ii,2})
            error(['Argument ' arglist{ii,1} ' is of integer type but non-integer number ' arglist{ii,2} ' was supplied']);
        end
        if (minval{jj} ~= 0 | maxval{jj} ~= 0)
          if (minval{jj} == 1 & maxval{jj} == 1 & arglist{ii,2} < 0)
            error(['Argument ' arglist{ii,1} ' must be bigger or equal to zero!']);
          elseif (minval{jj} == -1 & maxval{jj} == -1 & arglist{ii,2} > 0)
            error(['Argument ' arglist{ii,1} ' must be smaller or equal to zero!']);
          elseif (minval{jj} == 2 & maxval{jj} == 2 & arglist{ii,2} <= 0)
            error(['Argument ' arglist{ii,1} ' must be bigger than zero!']);
          elseif (minval{jj} == -2 & maxval{jj} == -2 & arglist{ii,2} >= 0)
            error(['Argument ' arglist{ii,1} ' must be smaller than zero!']);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} < minval{jj})
            error(['Argument ' arglist{ii,1} ' must be bigger than ' num2str(minval{jj})]);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} > maxval{jj})
            error(['Argument ' arglist{ii,1} ' must be smaller than ' num2str(maxval{jj})]);
          end
        end
     end
     if ~strcmp(narglist{jj,2},'OK_out') &  ~strcmp(narglist{jj,2},'OK_in')
       narglist{jj,2} = arglist{ii,2};
     end
   end
   end
   if (wasmatched == 0 & ~strcmp(arglist{ii,1},''))
        error(['Argument ' arglist{ii,1} ' is not a valid argument for this function']);
   end
end
% match the remaining inputs/outputs to the unused arguments and test for missing required inputs
 for jj=1:size(narglist,1)
     if  strcmp(paramtype{jj}, 'Toggle')
        if (narglist{jj,2} ==0)
          narglist{jj,1} = '';
        end;
        narglist{jj,2} = '';
     end;
     if  ~strcmp(narglist{jj,2},'__input') && ~strcmp(narglist{jj,2},'__output') && istoggle(jj) && ~ was_set(jj)
          narglist{jj,1} = '';
          narglist{jj,2} = '';
     end;
     if strcmp(narglist{jj,2}, '__input')
      if (minval{jj} == 0)  % meaning this input is required
        if (nextinput > size(Inputs))
           error(['Required input ' narglist{jj,1} ' has no corresponding input in the list!']);
        else
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        end
      else  % this is an optional input
        if (nextinput <= length(Inputs))
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end;
     else
     if strcmp(narglist{jj,2}, '__output')
      if (minval{jj} == 0) % this is a required output
        if (nextoutput > nargout & nargout > 1)
           error(['Required output ' narglist{jj,1} ' is not stated in the assignment list!']);
        else
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
          NumReqOutputs = NumReqOutputs-1;
        end
      else % this is an optional output
        if (nargout - nextoutput >= NumReqOutputs)
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end
     end
  end
end
if nargout
   varargout = cell(1,nargout);
else
  varargout = cell(1,1);
end
global KhorosRoot
if exist('KhorosRoot') && ~isempty(KhorosRoot)
w=['"' KhorosRoot];
else
if ispc
  w='"C:\Program Files\dip\khorosBin\';
else
[s,w] = system('which cantata');
w=['"' w(1:end-8)];
end
end
[varargout{:}]=callKhoros([w 'vsupgen"  '],Inputs,narglist);


function imgs = read_neurofinder(foldername,output_name)
%% import neurofinder tiff multifile format

format compact
opts_tiff.append = false;
opts_tiff.big = true;
files = subdir(fullfile(foldername,'*.tif*'));
numFiles = length(files)
data_type = class(imread(files(1).name));
imgs = zeros([size(imread(files(1).name)),numFiles],data_type);
for i = 1:length(files)
    fname = files(i).name;
	imgs(:,:,i) = imread(fname);
    if mod(i,100)==0
        disp(i)
    end
end
if ~isempty(output_name)
    disp('saving to tif')
    saveastiff(cast(imgs,data_type),output_name,opts_tiff);
end


function slwritearray(A, filename)
%SLWRITEARRAY Writes an array to an array file
%
% $ Syntax $
%   - slwritearray(A, filename)
%
% $ Arguments $
%   - A:            The array to be written
%   - filename:     The filename of the array file
%
% $ Description $
%   - slwritearray(A, filename) writes an array A to the array file.
%
% $ History $
%   - Created by Dahua Lin, on Jul 26th, 2006
%

value_types = { ...
    'double', ...
    'single', ...
    'logical', ...
    'char', ...
    'int8', ...
    'uint8', ...
    'int16', ...
    'uint16', ...
    'int32', ...
    'uint32', ...
    'int64', ...
    'uint64'};

%% open file

fid = fopen(filename, 'w');
if fid <= 0
    error('sltoolbox:filefail', ...
        'Fail to open file %s', filename);
end

%% write header

% write tag
fwrite(fid, ['arr', 0], 'char');

% write value type and dimension number
[tf, typeidx] = ismember(class(A), value_types);
if ~tf
    error('Unknown type for A: %s', class(A));
end
d = ndims(A);
info = uint8([typeidx, d, 0, 0]);
fwrite(fid, info, 'uint8');

%% write size

siz = size(A);
fwrite(fid, uint32(siz), 'uint32');

%% write data

fwrite(fid, A, class(A));

%% close file

fclose(fid);


%{
    Copyright (C) Cambridge Electronic Design Limited 2014
    Author: James Thompson
    Web: www.ced.co.uk email: james@ced.co.uk, softhelp@ced.co.uk

    This file is part of CEDS64ML, a MATLAB interface to the SON64 library.

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

    CEDS64ML 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 CEDS64ML.  If not, see <http://www.gnu.org/licenses/>.
%}

function [ iMaxChans ] = CEDS64MaxChan( fhand )
%CEDS64MAXCHAN Returns the maximum number of channels that can stored in this
%file
%   [ iMaxChans ] = CEDS64MaxChan( fhand )
%   Inputs
%   fhand - Integer file handle
%   Outputs
%   iMaxChans - The maximum number of channels, otherwise a negative error
%   code.

if (nargin ~= 1)
    iMaxChans = -22;
    return;
end

iMaxChans = calllib('ceds64int', 'S64MaxChans', fhand);
end


function data= bbci_load(bbci)
%BBCI_LOAD - Load data set
%
%Synopsis:
%  DATA= bbci_load(BBCI);

bbci= bbci_calibrate_setDefaults(bbci);
BC= bbci.calibrate;

% fullfile does not work for CELL, so we use strcat here:
if ~isempty(BC.folder) && ~fileutil_isAbsolutePath(BC.file),
    data.filename= strcat(BC.folder, filesep, BC.file);
else
    data.filename = BC.file;
end
filespec= strcat(data.filename,'.*');
if iscell(filespec),
    data.fileinfo= cellfun(@dir, filespec, 'UniformOutput',false);
else
    data.fileinfo= {dir(filespec)};
end
if isempty(BC.montage_fcn),
    [data.cnt, data.mrk, data.mnt]= ...
        BC.read_fcn(data.filename, BC.read_param{:});
else
    [data.cnt, data.mrk]= BC.read_fcn(data.filename, BC.read_param{:});
    data.mnt= BC.montage_fcn(data.cnt.clab, BC.montage_param{:});
end
if ~isempty(BC.marker_fcn),
    data.mrk= BC.marker_fcn(data.mrk, BC.marker_param{:});
end
data.isnew= 1;


%% tri_area
% Below is a demonstration of the features of the |tri_area| function

%%
clear; close all; clc;

%% Syntax
% |A=tri_area(F,V);|

%% Description
% UNDOCUMENTED
%% Examples
%
%%
%
% <<gibbVerySmall.gif>>
%
% _*GIBBON*_
% <www.gibboncode.org>
%
% _Kevin Mattheus Moerman_, <gibbon.toolbox@gmail.com>
%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% 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.
%
% 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, see <http://www.gnu.org/licenses/>.


%kktranspose 'Transpose Data on Dimensions'
% This MatLab function was automatically generated by a converter (KhorosToMatLab) from the Khoros ktranspose.pane file
%
% Parameters:
% InputFile: i 'Input ', required: 'input data object'
% OutputFile: o 'Output', required: 'resulting data object'
% Toggle: w ' ', default: 0: 'swap width and one other dimension'
% Toggle: h ' ', default: 0: 'swap height with one other dimension'
% Toggle: e ' ', default: 0: 'swap elements with one other dimension'
% Toggle: d ' ', default: 0: 'swap depth with one other dimension'
% Toggle: t ' ', default: 0: 'swap time with one other dimension'
%
% Example: o = kktranspose(i, {'i','';'o','';'w',0;'h',0;'e',0;'d',0;'t',0})
%
% Khoros helpfile follows below:
%
%  PROGRAM
% ktranspose - Transpose Data across Dimensions
%
%  DESCRIPTION
% ktranspose will swap data across two dimensions.  Any of the polymorphic
% dimensions of WIDTH (W), HEIGHT (H), ELEMENTS (E), DEPTH (D),
% and TIME (T), can be switched with another.
%
% \f(CW
%
%                  'depth' planes of value data
%                ____/   ____       ____
%               +---+|  +---+|     +---+|  The value segment is a time
%     'height'--|   ||  |   || ... |   ||  series of volumes.
%               +---+   +-|-+      +---+
%                 |       |
%              'width'   each element in the value value
%                        segment is a vector of size
%                        'elements'.
%
%
% The dimensions which should be transposed are specified.
% On the graphical user interface accessible via
% cantata, these flags are represented as a series of buttons, with one
% for each dimension.  On the command line, these flags are specified
% as a letter : w,h,d,t,e.   Only two flags should be specified. The
% dimensions indicated by these two flags will be transposed. For example,
%
% 	ktranspose -i infile -o outfile -w -h
%
% will cause the height dimension and the width dimension to be
% switched. If the input file has width = 4, height = 2,
% the output dimension will have width = 2, height = 4.
%
%
%  "Map Data"
% If the source object has map data, the map data cannot
% have depth, time, or elements dimensions of greater than
% one.  The map width and height dimensions can be any size.
%
%  "Location & Time Data"
% If the source object has rectilinear or curvilinear
% location or time data, the orientation operator will fail.  If the
% source object has uniform location data, orientation will
% continue, but the location data will not be modified
% to reflect the reorientation.
%
%  "Map Data"
% If the object has mask data, it will be transposed along with the value data.
%
%  "Failure Modes"
% The transpose operator will fail if the source object does not contain
% value data, or if it contains rectilinear or curvilinear location or time
% data.
%
%
%
%  EXAMPLES
%
%  "SEE ALSO"
% kaxis, korient
%
%  RESTRICTIONS
% This program has not yet been updated to completely support the
% polymorphic data model.
%
%  REFERENCES
%
%  COPYRIGHT
% Copyright (C) 1993 - 1997, Khoral Research, Inc. ("KRI")  All rights reserved.
%

function varargout = kktranspose(varargin)
if nargin ==0
  Inputs={};arglist={'',''};
elseif nargin ==1
  Inputs=varargin{1};arglist={'',''};
elseif nargin ==2
  Inputs=varargin{1}; arglist=varargin{2};
else error('Usage: [out1,..] = kktranspose(Inputs,arglist).');
end
if size(arglist,2)~=2
  error('arglist must be of form {''ParameterTag1'',value1;''ParameterTag2'',value2}')
 end
narglist={'i', '__input';'o', '__output';'w', 0;'h', 0;'e', 0;'d', 0;'t', 0};
maxval={0,0,0,0,0,0,0};
minval={0,0,0,0,0,0,0};
istoggle=[0,0,1,1,1,1,1];
was_set=istoggle * 0;
paramtype={'InputFile','OutputFile','Toggle','Toggle','Toggle','Toggle','Toggle'};
% identify the input arrays and assign them to the arguments as stated by the user
if ~iscell(Inputs)
Inputs = {Inputs};
end
NumReqOutputs=1; nextinput=1; nextoutput=1;
  for ii=1:size(arglist,1)
  wasmatched=0;
  for jj=1:size(narglist,1)
   if strcmp(arglist{ii,1},narglist{jj,1})  % a given argument was matched to the possible arguments
     wasmatched = 1;
     was_set(jj) = 1;
     if strcmp(narglist{jj,2}, '__input')
      if (nextinput > length(Inputs))
        error(['Input ' narglist{jj,1} ' has no corresponding input!']);
      end
      narglist{jj,2} = 'OK_in';
      nextinput = nextinput + 1;
     elseif strcmp(narglist{jj,2}, '__output')
      if (nextoutput > nargout)
        error(['Output nr. ' narglist{jj,1} ' is not present in the assignment list of outputs !']);
      end
      if (isempty(arglist{ii,2}))
        narglist{jj,2} = 'OK_out';
      else
        narglist{jj,2} = arglist{ii,2};
      end

      nextoutput = nextoutput + 1;
      if (minval{jj} == 0)
         NumReqOutputs = NumReqOutputs - 1;
      end
     elseif isstr(arglist{ii,2})
      narglist{jj,2} = arglist{ii,2};
     else
        if strcmp(paramtype{jj}, 'Integer') & (round(arglist{ii,2}) ~= arglist{ii,2})
            error(['Argument ' arglist{ii,1} ' is of integer type but non-integer number ' arglist{ii,2} ' was supplied']);
        end
        if (minval{jj} ~= 0 | maxval{jj} ~= 0)
          if (minval{jj} == 1 & maxval{jj} == 1 & arglist{ii,2} < 0)
            error(['Argument ' arglist{ii,1} ' must be bigger or equal to zero!']);
          elseif (minval{jj} == -1 & maxval{jj} == -1 & arglist{ii,2} > 0)
            error(['Argument ' arglist{ii,1} ' must be smaller or equal to zero!']);
          elseif (minval{jj} == 2 & maxval{jj} == 2 & arglist{ii,2} <= 0)
            error(['Argument ' arglist{ii,1} ' must be bigger than zero!']);
          elseif (minval{jj} == -2 & maxval{jj} == -2 & arglist{ii,2} >= 0)
            error(['Argument ' arglist{ii,1} ' must be smaller than zero!']);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} < minval{jj})
            error(['Argument ' arglist{ii,1} ' must be bigger than ' num2str(minval{jj})]);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} > maxval{jj})
            error(['Argument ' arglist{ii,1} ' must be smaller than ' num2str(maxval{jj})]);
          end
        end
     end
     if ~strcmp(narglist{jj,2},'OK_out') &  ~strcmp(narglist{jj,2},'OK_in')
       narglist{jj,2} = arglist{ii,2};
     end
   end
   end
   if (wasmatched == 0 & ~strcmp(arglist{ii,1},''))
        error(['Argument ' arglist{ii,1} ' is not a valid argument for this function']);
   end
end
% match the remaining inputs/outputs to the unused arguments and test for missing required inputs
 for jj=1:size(narglist,1)
     if  strcmp(paramtype{jj}, 'Toggle')
        if (narglist{jj,2} ==0)
          narglist{jj,1} = '';
        end;
        narglist{jj,2} = '';
     end;
     if  ~strcmp(narglist{jj,2},'__input') && ~strcmp(narglist{jj,2},'__output') && istoggle(jj) && ~ was_set(jj)
          narglist{jj,1} = '';
          narglist{jj,2} = '';
     end;
     if strcmp(narglist{jj,2}, '__input')
      if (minval{jj} == 0)  % meaning this input is required
        if (nextinput > size(Inputs))
           error(['Required input ' narglist{jj,1} ' has no corresponding input in the list!']);
        else
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        end
      else  % this is an optional input
        if (nextinput <= length(Inputs))
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end;
     else
     if strcmp(narglist{jj,2}, '__output')
      if (minval{jj} == 0) % this is a required output
        if (nextoutput > nargout & nargout > 1)
           error(['Required output ' narglist{jj,1} ' is not stated in the assignment list!']);
        else
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
          NumReqOutputs = NumReqOutputs-1;
        end
      else % this is an optional output
        if (nargout - nextoutput >= NumReqOutputs)
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end
     end
  end
end
if nargout
   varargout = cell(1,nargout);
else
  varargout = cell(1,1);
end
global KhorosRoot
if exist('KhorosRoot') && ~isempty(KhorosRoot)
w=['"' KhorosRoot];
else
if ispc
  w='"C:\Program Files\dip\khorosBin\';
else
[s,w] = system('which cantata');
w=['"' w(1:end-8)];
end
end
[varargout{:}]=callKhoros([w 'ktranspose"  '],Inputs,narglist);


function test_suite = test_regression_additive1

%   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_REGRESSION_ADDITIVE1
%
% 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())
end

function testPredictionsFIC(testCase)
  verifyVarsEqual(testCase, getName(), ...
    {'Eft_fic', 'Varft_fic'}, {[], @mean}, ...
    'AbsTol', 0.1)
end

function testPredictionsPIC(testCase)
  verifyVarsEqual(testCase, getName(), ...
    {'Eft_pic', 'Varft_pic'}, ...
    'AbsTol', 0.1)
end

function testPredictionsSparse(testCase)
  verifyVarsEqual(testCase, getName(), ...
    {'Eft_csfic', 'Varft_csfic'}, ...
    'AbsTol', 0.1)
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 varargout=fxpefac(varargin)
%
% For calling details please see v_fxpefac.m
%
% This dummy routine is included for backward compatibility only
% and will be removed in a future release of voicebox. Please use
% v_fxpefac.m in future and/or update with v_voicebox_update.m
%
%      Copyright (C) Mike Brookes 2018
%      Version: $Id: fxpefac.m 10863 2018-09-21 15:39:23Z dmb $
%
if nargout
    varargout=cell(1,nargout);
    [varargout{:}]=v_fxpefac(varargin{:});
else
    v_fxpefac(varargin{:});
end


function p = p25_p_get ( )

%*****************************************************************************80
%
%% P25_P_GET gets the value of a parameter for problem 25.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    22 January 2001
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Output, real P, the value of the parameter that multiplies the
%    Gaussian noise.
%
  p = 0.0;
  p = p25_p_val ( 'GET', p );

  return
end


function printMetrics(metrics, metricsInfo, dispHeader,dispMetrics,padChar)
% print metrics
%
% ...
%

% default names
if nargin==1
    metricsInfo.names.long = {'Recall','Precision','False Alarm Rate', ...
        'GT Tracks','Mostly Tracked','Partially Tracked','Mostly Lost', ...
        'False Positives', 'False Negatives', 'ID Switches', 'Fragmentations', ...
        'MOTA','MOTP', 'MOTA Log'};

    metricsInfo.names.short = {'Rcll','Prcn','FAR', ...
        'GT','MT','PT','ML', ...
        'FP', 'FN', 'IDs', 'FM', ...
        'MOTA','MOTP', 'MOTAL'};

    metricsInfo.widths.long = [6 9 16 9 14 17 11 15 15 11 14 5 5 8];
    metricsInfo.widths.short = [5 5 5 3 3 3 3 5 5 4 4 5 5 5];

    metricsInfo.format.long = {'.1f','.1f','.2f', ...
        'i','i','i','i', ...
        'i','i','i','i', ...
        '.1f','.1f','.1f'};

    metricsInfo.format.short=metricsInfo.format.long;
end

namesToDisplay=metricsInfo.names.long;
widthsToDisplay=metricsInfo.widths.long;
formatToDisplay=metricsInfo.format.long;

namesToDisplay=metricsInfo.names.short;
widthsToDisplay=metricsInfo.widths.short;
formatToDisplay=metricsInfo.format.short;

if nargin<3, dispHeader=1; end
if nargin<4
    dispMetrics=1:length(metrics);
end
if nargin<5
    padChar={' ',' ','|',' ',' ',' ','|',' ',' ',' ','| ',' ',' ',' '};
end

if dispHeader
    for m=dispMetrics
        printString=sprintf('fprintf(''%%%is%s'',char(namesToDisplay(m)))',widthsToDisplay(m),char(padChar(m)));
        eval(printString)
    end
    fprintf('\n');
end

for m=dispMetrics
    printString=sprintf('fprintf(''%%%i%s%s'',metrics(m))',widthsToDisplay(m),char(formatToDisplay(m)),char(padChar(m)));
    eval(printString)
end

% if standard, new line
if nargin<4
    fprintf('\n');
end


%mdd is a class.
%    obj = mdd(theta_draws, log_post_kern, lb, ub, maximization)
%
%    Reference page in Doc Center
%       doc mdd
%
%


%  Imbed a file menu to any figure. If file menu exist, it will append
%  to the existing file menu. This file menu includes: Copy to clipboard,
%  print, save, close etc.
%
%  Usage: rri_file_menu(fig);
%
%         rri_file_menu(fig,0) means no 'Close' menu.
%
%  - Jimmy Shen (jimmy@rotman-baycrest.on.ca)
%
%--------------------------------------------------------------------

function rri_file_menu(action, varargin)

   if isnumeric(action)
      fig = action;
      action = 'init';
   end

   %  clear the message line,
   %
   h = findobj(gcf,'Tag','MessageLine');
   set(h,'String','');

   if ~strcmp(action, 'init')
      set(gcbf, 'InvertHardcopy','off');
%      set(gcbf, 'PaperPositionMode','auto');
   end

   switch action
      case {'init'}
         if nargin > 1
            init(fig, 1);		% no 'close' menu
         else
            init(fig, 0);
         end
      case {'print_fig'}
         printdlg(gcbf);
      case {'copy_fig'}
         copy_fig;
      case {'export_fig'}
         export_fig;
   end

   return					% rri_file_menu

%------------------------------------------------
%
%  Create (or append) File menu
%
function init(fig, no_close)

   %  search for file menu
   %
   h_file = [];
   menuitems = findobj(fig, 'type', 'uimenu');

   for i=1:length(menuitems)
      filelabel = get(menuitems(i),'label');

      if strcmpi(strrep(filelabel, '&', ''), 'file')
         h_file = menuitems(i);
         break;
      end
   end

   set(fig, 'menubar', 'none');

   if isempty(h_file)
      if isempty(menuitems)
         h_file = uimenu('parent', fig, 'label', 'File');
      else
         h_file = uimenu('parent', fig, 'label', 'Copy Figure');
      end

      h1 = uimenu('parent', h_file, ...
         'callback','rri_file_menu(''copy_fig'');', ...
         'label','Copy to Clipboard');
   else
      h1 = uimenu('parent', h_file, ...
         'callback','rri_file_menu(''copy_fig'');', ...
         'separator','on', ...
         'label','Copy to Clipboard');
   end

   h2 = uimenu(h_file, ...
      'callback','pagesetupdlg(gcbf);', ...
      'label','Page Setup...');

   h2 = uimenu(h_file, ...
      'callback','printpreview(gcbf);', ...
      'label','Print Preview...');

   h2 = uimenu('parent', h_file, ...
      'callback','printdlg(gcbf);', ...
      'label','Print Figure ...');

   h2 = uimenu('parent', h_file, ...
      'callback','rri_file_menu(''export_fig'');', ...
      'label','Save Figure ...');

   arch = computer;
   if ~strcmpi(arch(1:2),'PC')
      set(h1, 'enable', 'off');
   end

   if ~no_close
      h1 = uimenu('parent', h_file, ...
         'callback','close(gcbf);', ...
         'separator','on', ...
         'label','Close');
   end

   return;					% init

%------------------------------------------------
%
%  Copy to clipboard
%
function copy_fig

   arch = computer;
   if(~strcmpi(arch(1:2),'PC'))
      error('copy to clipboard can only be used under MS Windows');
      return;
   end

   print -noui -dbitmap;

   return					% copy_fig

%------------------------------------------------
%
%  Save as an image file
%
function export_fig

   curr = pwd;
   if isempty(curr)
      curr = filesep;
   end

   [selected_file, selected_path] = rri_select_file(curr,'Save As');

   if isempty(selected_file) | isempty(selected_path)
      return;
   end

   filename = [selected_path selected_file];

   if(exist(filename,'file')==2)		% file exist

      dlg_title = 'Confirm File Overwrite';
      msg = ['File ',filename,' exist. Are you sure you want to overwrite it?'];
      response = questdlg(msg,dlg_title,'Yes','No','Yes');

      if(strcmp(response,'No'))
         return;
      end

   end

   old_pointer = get(gcbf,'pointer');
   set(gcbf,'pointer','watch');

   try
      saveas(gcbf,filename);
   catch
      msg = 'ERROR: Cannot save file';
      set(findobj(gcf,'Tag','MessageLine'),'String',msg);
   end

   set(gcbf,'pointer',old_pointer);

   return;					% export_fig


function s = prtUtilSimpleStruct(varargin)
%s = prtUtilSimpleStruct(varargin)
%s = prtUtilSimpleStruct('asdf',randn(100,1),'fff',randn(100,1))
%s = prtUtilSimpleStruct('asdf',randn(100,1),'fff',randn(100,1),'asfsadf',prtUtilCellPrintf('%s',num2cell(1:100)'))
%s = prtUtilSimpleStruct(origStruct,...)

% fields = varargin(1:2:end);
% vals = varargin(2:2:end);

if mod(nargin,2) %odd # inputs
    origStruct = varargin{1};

    if isempty(origStruct)
        origStruct = struct;
    end

    if ~isa(origStruct,'struct')
        error('prtUtilSimpleStruct:firstInputStruct','prtUtilSimpleStruct with odd # of inputs expects first input to be a structure');
    end
    varargin = varargin(2:end);
else
    origStruct = struct;
end
structInput = cell(1,length(varargin));

for i = 1:2:length(varargin)
    cField = varargin{i};
    cVal = varargin{i+1};
    assert(isvarname(cField),'can not create struct with a field named %s',cField);

    if iscellstr(cVal)
        %ok
    else
        cVal = mat2cell(cVal,ones(size(cVal,1),1),size(cVal,2));
    end

    structInput(i:i+1) = {cField,cVal};
end
newStruct = struct(structInput{:});

f1 = fieldnames(origStruct);
f2 = fieldnames(newStruct);

%If the new structure data specifies a pre-existing field in origStruct,
%remove the field from origStruct before trying to merge the structures
uNames = unique(cat(1,f1(:),f2(:)));
for i = 1:length(uNames)
    if any(strcmpi(f2,uNames{i})) && any(strcmpi(f1,uNames{i}))
        origStruct = rmfield(origStruct,uNames{i});
    end
end

s = mergestruct(origStruct,newStruct);

function sout = mergestruct(varargin)
%MERGESTRUCT Merge structures with unique fields.
% This is from http://blogs.mathworks.com/loren/2009/10/15/concatenating-structs/
%   Copyright 2009 The MathWorks, Inc.

% Start with collecting fieldnames, checking implicitly
% that inputs are structures.
fn = [];
for k = 1:nargin
    try
        fn = [fn ; fieldnames(varargin{k})];
    catch MEstruct
        throw(MEstruct)
    end
end

% Make sure the field names are unique.
if length(fn) ~= length(unique(fn))
    error('mergestruct:FieldsNotUnique',...
        'Field names must be unique');
end

% Now concatenate the data from each struct.  Can't use
% structfun since input structs may not be scalar.
c = [];
for k = 1:nargin
    try
        c = [c ; struct2cell(varargin{k})];
    catch MEdata
        throw(MEdata);
    end
end

% Construct the output.
sout = cell2struct(c, fn, 1);


function scanList = chooseScans(view)
% scanList = schooseScans(view);
%
%   Query the user for a sub-selection of scans.

%   This function uses numScans(view) to determine the number of scans to choose from.
%   An alternate function, selectScans, actually checks for the existence of the tSeries files.
%   Use selectScans if you will be analyzing the tSeries.
%   Use chooseScans if your code does not depend on the presence/absence of the tSeries files.
%
% Output:
%  scanList: list of selected scans.
%
% djh, 3/2001, modified from selectScans

nScans = numScans(view);
scanList = 1:nScans;

for i=scanList
    scanNames{i} = ['Scan',num2str(i)];
end

%Check for zero:
if nScans == 0
  myErrorDlg('No scans!');
  return
end

% Which scans to analyze?
iSel = buttondlg('Choose scans', scanNames);
scanList = scanList(find(iSel));

return;