classdef PTKPoint
    % PTKPoint. A class for storing points
    %
    %
    %     Licence
    %     -------
    %     Part of the TD Pulmonary Toolkit. https://github.com/tomdoel/pulmonarytoolkit
    %     Author: Tom Doel, 2012.  www.tomdoel.com
    %     Distributed under the GNU GPL v3 licence. Please see website for details.
    %

    properties
        CoordX
        CoordY
        CoordZ
    end

    methods
        function obj = PTKPoint(c_x, c_y, c_z)
            if nargin > 0
                obj.CoordX = c_x;
                obj.CoordY = c_y;
                obj.CoordZ = c_z;
            end
        end
    end

    methods (Static)
        function diff = Difference(point_1, point_2)
            diff = PTKPoint(point_1.CoordX - point_2.CoordX, point_1.CoordY - point_2.CoordY, point_1.CoordZ - point_2.CoordZ);
        end

        function mag = Magnitude(point)
            mag = norm([point.CoordX, point.CoordY, point.CoordZ]);
        end

        function point = SetCoordinate(point, dimension, value)
            switch dimension
                case PTKImageOrientation.Coronal
                    point.CoordY = value;
                case PTKImageOrientation.Sagittal
                    point.CoordX = value;
                case PTKImageOrientation.Axial
                    point.CoordZ = value;
                otherwise
                    error('Unknown dimension');
            end
        end
    end
end


function [ifeat,testStat,testStat_rand,featureClassifier] = TS_TopFeatures(whatData,whatTestStat,cfnParams,varargin)
% TS_TopFeatures  Top individual features for discriminating labeled time series
%
% This function compares each feature in an hctsa dataset individually for its
% ability to separate the labeled classes of time series according to a given
% test statistic.
%
% Can also compare this performance to a set of randomized null features to
% evaluate the statistical significance of the result (pooled permutation testing).
%
% Note that it can be more interpretable to run this function on filtered but
% un-normalized data (e.g., from TS_Normalize('none')), so that feature values
% correspond to raw feature outputs.
%
%---INPUTS:
%
% whatData, the hctsa data to use (input to TS_LoadData, default: 'raw')
% whatTestStat, the test statistic to quantify the goodness of each feature
%               (e.g., 'tstat','ustat'; or set 'classification' to use classifier
%                   described in cfnParams)
% cfnParams, the classification settings if using 'classification'-based selection
%
%---OPTIONAL extra inputs:
%
% 'whatPlots', can specify what output plots to produce (cell of strings), e.g.,
%               specify {'histogram','distributions','cluster','datamatrix'} to
%               produce all four possible output plots (this is the default).
% 'numTopFeatures' [40], specifies the number of top features to analyze, both in
%                   terms of the list of outputs, the histogram plots, and the
%                   cluster plot.
% 'numFeaturesDistr' [16], sets a custom number of distributions to display (can
%                   set this lower to avoid producing large numbers of figures).
% 'numNulls' [0], the number of shuffled nulls to generate (e.g., 10 shuffles pools
%               shuffles for all M features, for a total of 10*M elements in the
%               null distribution)
%
%---EXAMPLE USAGE:
%
% TS_TopFeatures('norm','ustat',struct(),'whatPlots',{'histogram','distributions',...
%           'cluster','datamatrix'},'numTopFeatures',40,'numFeaturesDistr',10,'numNulls',10);
%
% TS_TopFeatures('norm','classification',cfnParams,'whatPlots',{'histogram','distributions',...
%           'cluster','datamatrix'},'numTopFeatures',40,'numFeaturesDistr',10,'numNulls',10);
%
%---OUTPUTS:
%
% ifeat, the ordering of operations by their performance
% testStat, the test statistic (whatTestStat) for each operation
% testStat_rand, test statistics making up the null distributions
% featureClassifier, the individual-feature classifier (if using single-feature
%               classification)

% ------------------------------------------------------------------------------
% Copyright (C) 2020, Ben D. Fulcher <ben.d.fulcher@gmail.com>,
% <http://www.benfulcher.com>
%
% If you use this code for your research, please cite the following two papers:
%
% (1) B.D. Fulcher and N.S. Jones, "hctsa: A Computational Framework for Automated
% Time-Series Phenotyping Using Massive Feature Extraction, Cell Systems 5: 527 (2017).
% DOI: 10.1016/j.cels.2017.10.001
%
% (2) B.D. Fulcher, M.A. Little, N.S. Jones, "Highly comparative time-series
% analysis: the empirical structure of time series and their methods",
% J. Roy. Soc. Interface 10(83) 20130048 (2013).
% DOI: 10.1098/rsif.2013.0048
%
% This work is licensed under the Creative Commons
% Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of
% this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or send
% a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View,
% California, 94041, USA.
% ------------------------------------------------------------------------------

%-------------------------------------------------------------------------------
%% Check inputs and set defaults
%-------------------------------------------------------------------------------
if nargin < 1 || isempty(whatData)
    whatData = 'norm';
end
% Set other defaults later when we know what we're dealing with

% Use an inputParser to control additional options as parameters:
inputP = inputParser;
% whatPlots
default_whatPlots = {'histogram','distributions','dendrogram','cluster'}; % 'datamatrix'
check_whatPlots = @(x) iscell(x) || ischar(x);
addParameter(inputP,'whatPlots',default_whatPlots,check_whatPlots);
% numTopFeatures
default_numTopFeatures = 40;
addParameter(inputP,'numTopFeatures',default_numTopFeatures,@isnumeric);
% numFeaturesDistr
default_numFeaturesDistr = 16;
addParameter(inputP,'numFeaturesDistr',default_numFeaturesDistr,@isnumeric);
% numNulls
default_numNulls = 0; % by default, don't compute an empirical null distribution
                      % by randomizing class labels
addParameter(inputP,'numNulls',default_numNulls,@isnumeric);

parse(inputP,varargin{:});

whatPlots = inputP.Results.whatPlots;
if ischar(whatPlots)
    whatPlots = {whatPlots};
end
numTopFeatures = inputP.Results.numTopFeatures;
numFeaturesDistr = inputP.Results.numFeaturesDistr;
numNulls = inputP.Results.numNulls;
clear('inputP');

%-------------------------------------------------------------------------------
%% Load the data
%-------------------------------------------------------------------------------
[TS_DataMat,TimeSeries,Operations,whatDataFile] = TS_LoadData(whatData);

%-------------------------------------------------------------------------------
%% Checks
%-------------------------------------------------------------------------------
% Does the grouping information exist?:
% Assign group labels (removing unlabeled data):
[TS_DataMat,TimeSeries] = FilterLabeledTimeSeries(TS_DataMat,TimeSeries);
[groupLabels,classLabels,groupLabelsInteger,numClasses] = TS_ExtractGroupLabels(TimeSeries);
% Give basic info about the represented classes:
TellMeAboutLabeling(TimeSeries);

%-------------------------------------------------------------------------------
% Set defaults for the test statistic:
if nargin < 2 || isempty(whatTestStat)
    if numClasses == 2
        % ustat or ttest are way faster than proper prediction models
        whatTestStat = 'ustat';
    else
        whatTestStat = 'classification';
    end
    fprintf(1,'Using ''%s'' test statistic by default.\n', whatTestStat);
end
% Set cfnParams default (if using 'classification') later after loading the time series
if strcmp(whatTestStat,'classification')
    if nargin < 3 || isempty(fieldnames(cfnParams))
        cfnParams = GiveMeDefaultClassificationParams(TimeSeries);
        cfnParams.whatClassifier = 'fast-linear';
        cfnParams = UpdateClassifierText(cfnParams);
    end
else
    cfnParams = struct();
end

%-------------------------------------------------------------------------------
% Filter down to reduced features if specified/required:
[TS_DataMat,Operations] = FilterFeatures(TS_DataMat,Operations,cfnParams);
numFeatures = size(TS_DataMat,2);
numTopFeatures = min(numTopFeatures,numFeatures);

%-------------------------------------------------------------------------------
%% Define the train/test classification rate function, fn_testStat
%-------------------------------------------------------------------------------
% chanceLevel -- what you'd expect by chance

% Check that simple stats are being applied just for pairs:
if ismember(whatTestStat,{'ustat','ranksum','ustatExact','ustatP','ranksumExact',...
                            'ttest','ttestP','tstat','svmBeta'})
    if numClasses~=2
        error(['There are %u labeled classes.\nStatistics like ''%s'' are only valid',...
                    ' for problems with 2 labeled classes'],numClasses,whatTestStat);
    end
end

% Set up the test statistic computation and relevant description text:
switch whatTestStat
    case 'classification'
        % Set up the loss function for a classifier-based metric:
        fn_testStat = GiveMeFunctionHandle(cfnParams);
        chanceLevel = 100/numClasses; % (could be a bad assumption: accuracy for equiprobable groups...)
        testStatText = sprintf('%s %s',cfnParams.classifierText,cfnParams.whatLoss);
        statUnit = cfnParams.whatLossUnits;
        whatIsBetter = 'high';
    case 'svmBeta'
        % Feature weight from a linear (in-sample) SVM
        fn_testStat = [];
        chanceLevel = NaN;
        testStatText = '(multivariate) Linear SVM (in-sample) feature weight';
        statUnit = '';
        whatIsBetter = 'abs';
    case {'ustatP','ranksumP'}
        % Approximate p-value from ranksum test
        fn_testStat = @(XTrain,yTrain,Xtest,yTest) ...
                fn_uStat(XTrain(yTrain==classLabels{1}),XTrain(yTrain==classLabels{2}),false,true,false);
        chanceLevel = NaN;
        testStatText = 'Mann-Whitney approx p-value';
        statUnit = ' (-log10(p))';
        whatIsBetter = 'high';
    case {'ustatPsign','ranksumPsign'}
        % Approximate p-value from ranksum test, signed by the direction of difference
        fn_testStat = @(XTrain,yTrain,Xtest,yTest) ...
                fn_uStat(XTrain(yTrain==classLabels{1}),XTrain(yTrain==classLabels{2}),false,true,true);
        chanceLevel = NaN;
        testStatText = 'Mann-Whitney approx p-value (signed)';
        statUnit = ' signed(-log10(p))';
        whatIsBetter = 'abs';
    case {'ustatExactP','ranksumExactP'}
        % Exact p-value from ranksum test
        fn_testStat = @(XTrain,yTrain,Xtest,yTest) ...
                    fn_uStat(XTrain(yTrain==classLabels{1}),XTrain(yTrain==classLabels{2}),true,true,false);
        chanceLevel = NaN;
        testStatText = 'Mann-Whitney exact p-value';
        statUnit = ' (-log10(p))';
        whatIsBetter = 'high';
    case {'ustat','ranksum'}
        % Approximate test statistic from ranksum test
        fn_testStat = @(XTrain,yTrain,Xtest,yTest) ...
                fn_uStat(XTrain(yTrain==classLabels{1}),XTrain(yTrain==classLabels{2}),false,false,false);
        chanceLevel = NaN; % (check if you can compute the chance level stat?)
        testStatText = 'Mann-Whitney approx test statistic';
        statUnit = '';
        whatIsBetter = 'high';
    case {'ttest','tstat'}
        fn_testStat = @(XTrain,yTrain,Xtest,yTest) ...
                        fn_tStat(XTrain(yTrain==classLabels{1}),XTrain(yTrain==classLabels{2}),false);
        chanceLevel = 0; % chance-level t statistic is zero
        testStatText = 'Welch''s t-stat';
        statUnit = '';
        whatIsBetter = 'abs';
    case {'ttestP','tstatP'}
        fn_testStat = @(XTrain,yTrain,Xtest,yTest) ...
                        fn_tStat(XTrain(yTrain==classLabels{1}),XTrain(yTrain==classLabels{2}),true);
        chanceLevel = NaN;
        testStatText = 'Welch''s t-test p-value';
        statUnit = ' (-log10(p))';
        whatIsBetter = 'high';
    otherwise
        error('Unknown test statistic: ''%s''',whatTestStat);
end

%-------------------------------------------------------------------------------
%% Loop over all features
%-------------------------------------------------------------------------------
% Use the same data for training and testing:
fprintf(1,['Computing the performance of %u individual features to differentiate',...
                ' %u classes using a %s...\n'],...
                        height(Operations),numClasses,testStatText);
timer = tic;
testStat = giveMeStats(TS_DataMat,TimeSeries.Group,true);
fprintf(1,' Done in %s.\n',BF_TheTime(toc(timer)));

if all(isnan(testStat))
    error('Error computing test statistics for %s (maybe there are missing data?)',...
                testStatText);
end

%-------------------------------------------------------------------------------
% Give mean and that expected from random classifier (there may be a little overfitting)
if ~isnan(chanceLevel)
    fprintf(1,['Mean %s across %u features = %4.2f%s\n' ...
            '(Random guessing for %u equiprobable classes = %4.2f%s)\n'], ...
        testStatText,numFeatures,nanmean(testStat),statUnit,numClasses,chanceLevel,statUnit);
else
    fprintf(1,'Mean %s across %u features = %4.2f%s\n',...
        testStatText,numFeatures,nanmean(testStat),statUnit);
end

%-------------------------------------------------------------------------------
%% Display information about the top features (numTopFeatures)
%-------------------------------------------------------------------------------
switch whatIsBetter
case 'high'
    % Bigger numbers indicate better features:
    [testStat_sort,ifeat] = sort(testStat,'descend');
case 'abs'
    % Bigger magnitudes indicate better features:
    [~,ifeat] = sort(abs(testStat),'descend');
    testStat_sort = testStat(ifeat);
otherwise
    error('Unknown ''whatIsBetter'' option: %s',whatIsBetter)
end

isNaN = isnan(testStat_sort);
testStat_sort = testStat_sort(~isNaN);
ifeat = ifeat(~isNaN);

% List the top features:
for i = 1:numTopFeatures
    ind = ifeat(i);
    fprintf(1,'[%u] %s (%s) -- %4.2f%s\n',Operations.ID(ind),...
            Operations.Name{ind},Operations.Keywords{ind},...
            testStat_sort(i),statUnit);
end

%-------------------------------------------------------------------------------
%% Define the indices of features we will go on to visualize
% (and construct text labels for them)
%-------------------------------------------------------------------------------
featInd = ifeat(1:numTopFeatures);
makeLabelMinimal = @(x) sprintf('[%u] %s (%4.2f%s)',Operations.ID(x),Operations.Name{x},...
                        testStat(x),statUnit);
makeLabelFull = @(x) sprintf('[%u] %s (%s) (%4.2f%s)',Operations.ID(x),Operations.Name{x},...
                    Operations.Keywords{x}, testStat(x),statUnit);
topFeatureLabelsMinimal = arrayfun(@(x) makeLabelMinimal(x),featInd,'UniformOutput',false);
topFeatureLabelsFull = arrayfun(@(x) makeLabelFull(x),featInd,'UniformOutput',false);

%-------------------------------------------------------------------------------
%-------------------------------------------------------------------------------
%% Plot outputs
%-------------------------------------------------------------------------------
%-------------------------------------------------------------------------------

%-------------------------------------------------------------------------------
%% Histogram of distribution of test statistics for labeled and null data
%-------------------------------------------------------------------------------
if ismember('histogram',whatPlots)
    % Plot the distribution of test statistics across all features:

    %-------------------------------------------------------------------------------
    % Compute null distribution
    %-------------------------------------------------------------------------------
    testStat_rand = zeros(numFeatures,numNulls);
    if numNulls > 0
        fprintf(1,'Now for %u nulls... ',numNulls);
        tic
        for j = 1:numNulls
            if j < numNulls
                fprintf(1,'%u,',j);
            else
                fprintf(1,'%u.',j);
            end
            shuffledLabels = TimeSeries.Group(randperm(height(TimeSeries)));
            testStat_rand(:,j) = giveMeStats(TS_DataMat,shuffledLabels,false);
        end
        fprintf(1,'\n%u %s statistics computed in %s.\n',numFeatures*numNulls,...
                                        testStatText,BF_TheTime(toc(timer)));

        % Pool nulls to estimate p-values
        pooledNulls = testStat_rand(:);
        pVals = arrayfun(@(x) mean(testStat(x) < pooledNulls),1:length(testStat));
        % FDR-corrected q-values:
        FDR_qVals = mafdr(pVals,'BHFDR','true');
        fprintf(1,'Estimating FDR-corrected p-values across all features by pooling across %u nulls.\n',numNulls);
        fprintf(1,'(Given strong dependences across %u features, will produce conservative p-values)\n',numFeatures);

        % Give summary:
        sigThreshold = 0.05;
        if any(FDR_qVals < sigThreshold)
            fprintf(1,['%u/%u features show better performance using %s than the null distribution\n' ...
                        'at the magical 0.05 threshold (FDR corrected).\n'],...
                            sum(FDR_qVals < 0.05),length(FDR_qVals),testStatText);
        else
            fprintf(1,['Tough day at the office, hey? No features show statistically better performance than ' ...
                    'the null distribution at a FDR of 0.05.\nDon''t you go p-hacking now, will you?\n']);
        end
    end

    %---------------------------------------------------------------------------
    % Plot histogram
    %---------------------------------------------------------------------------
    f = figure('color','w'); hold('on')
    f.Position(3:4) = [559,278];
    colors = BF_GetColorMap('spectral',5,1);
    if numNulls == 0
        % Just plot the real distribution of test statistics across all features
        h_real = histogram(testStat,'Normalization','probability',...
                    'BinMethod','auto','FaceColor',colors{4},'EdgeColor','k');
        maxH = max(h_real.Values);
    else
        % Plot both real distribution and null distribution:
        numBins = 20;
        allTestStat = [testStat(:); testStat_rand(:)];
        minMax = [min(allTestStat),max(allTestStat)];
        histEdges = linspace(minMax(1),minMax(2),numBins+1);
        h_null = histogram(testStat_rand(:),histEdges,'Normalization','probability','FaceColor',colors{1});
        h_real = histogram(testStat,histEdges,'Normalization','probability',...
                                'FaceColor',colors{5},'EdgeColor','k');
        maxH = max([max(h_real.Values),max(h_null.Values)]);
        l_meannull = plot(nanmean(testStat_rand(:))*ones(2,1),[0,maxH],'--','color',colors{1},'LineWidth',2);
    end

    % Add chance line:
    if ~isnan(chanceLevel)
        l_chance = plot(chanceLevel*ones(2,1),[0,maxH],'--','color',colors{1},'LineWidth',2);
    end

    % Add mean of real distribution:
    l_mean = plot(nanmean(testStat)*ones(2,1),[0,maxH],'--','color',colors{5},'LineWidth',2);

    % Labels:
    xlabel(sprintf('Individual %s %s across %u features',testStatText,statUnit,numFeatures))
    ylabel('Probability')

    % Legend:
    if numNulls > 0
        if ~isnan(chanceLevel)
            legend([h_null,h_real,l_chance,l_meannull,l_mean],'null','real','chance','null mean','mean')
        else
            legend([h_null,h_real,l_meannull,l_mean],'null','real','null mean','mean')
        end
        title(sprintf(['%u features significantly informative of groups',...
                        ' (FDR-corrected at 0.05)'],sum(FDR_qVals < 0.05)))
    else
        if ~isnan(chanceLevel)
            legend([h_real,l_chance,l_mean],{'real','chance','mean'});
        else
            legend([h_real,l_mean],{'real','mean'});
        end
    end
else
    testStat_rand = [];
end

%-------------------------------------------------------------------------------
%% Distributions across classes for top features
%-------------------------------------------------------------------------------
if ismember('distributions',whatPlots)
    subPerFig = 16; % subplots per figure

    % Space the figures out properly:
    numFigs = ceil(numFeaturesDistr/subPerFig);

    % Make data structure for TS_SingleFeature
    data = struct('TS_DataMat',TS_DataMat,'TimeSeries',TimeSeries,...
                'Operations',Operations);

    for figi = 1:numFigs
        if figi*subPerFig > length(ifeat)
            break % We've exceeded number of features
        end
        % Get the indices of features to plot
        r = ((figi-1)*subPerFig+1:figi*subPerFig);
        if figi==numFigs % filter down for last one
            r = r(r <= numFeaturesDistr);
        end
        featHere = ifeat(r); % features to plot on this figure
        % Make the figure
        f = figure('color','w');
        f.Position(3:4) = [1353, 857];
        % Loop through features
        for opi = 1:length(featHere)
            subplot(ceil(length(featHere)/4),4,opi);
            TS_SingleFeature(data,Operations.ID(featHere(opi)),true,false,...
                                                testStat(featHere(opi)),false);
        end
    end
end

%-------------------------------------------------------------------------------
%% Data matrix containing top features
%-------------------------------------------------------------------------------
if ismember('datamatrix',whatPlots)
    ixFeat = BF_ClusterReorder(TS_DataMat(:,featInd)','corr','average');
    dataLocal = struct('TS_DataMat',BF_NormalizeMatrix(TS_DataMat(:,featInd(ixFeat)),'maxmin'),...
                    'TimeSeries',TimeSeries,...
                    'Operations',Operations(featInd(ixFeat),:));
    TS_PlotDataMatrix(dataLocal,'colorGroups',true,'groupReorder',true);
end

%-------------------------------------------------------------------------------
%% Pre-computing pairwise dependencies
% (for annotated dendrogram or feature–feature 'cluster' plot)
%-------------------------------------------------------------------------------
if ismember('dendrogram',whatPlots) || ismember('cluster',whatPlots)
    % 1. Compute pairwise Spearman correlations between all pairs of top features

    % (If it exists already, use that; otherwise compute it on the fly)
    % clustStruct = TS_GetFromData(whatData,'op_clust');
    % if isempty(clustStruct) % doesn't exist
    %     clustStruct = struct();
    % end
    % if isfield(clustStruct,'Dij') && ~isempty(clustStruct.Dij)
    %     % pairwise distances already computed, stored in the HCTSA .mat file
    %     fprintf(1,'Loaded %s distances from %s\n',clustStruct.distanceMetric,whatDataFile);
    %     Dij = squareform(clustStruct.Dij);
    %     Dij = Dij(featInd,featInd);
    %     distanceMetric = clustStruct.distanceMetric;
    % else
    %     % Compute correlations on the fly
    %     Dij = BF_pdist(TS_DataMat(:,featInd)','abscorr');
    %     distanceMetric = 'abscorr';
    % end

    % spearmanCorrs = corr(TS_DataMat(:,featInd)','abscorr');
    distanceMetric = 'abscorr'; % absolute Spearman correlation distances
    Dij = BF_pdist(TS_DataMat(:,featInd)',distanceMetric);
end

%-------------------------------------------------------------------------------
%% Dendrogram
% An annotated dendrogram showing the similarity between the set of top features
% (like the feature–feature correlation matrix but without the matrix)
%-------------------------------------------------------------------------------
if ismember('dendrogram',whatPlots)
    colorThreshold = 0.25;
    doVertical = false;
    f = figure('color','w');

    if doVertical
        f.Position(3:4) = [950,750];
    else
        f.Position(3:4) = [750,950];
    end

    [distVec,links] = BF_DoLinkage(Dij,distanceMetric,'average');
    % Get the dendrogram reordering:
    ord = BF_LinkageOrdering(distVec,links);
    if doVertical
        h_dend = dendrogram(links,0,'Orientation','right','Reorder',ord,...
                    'ColorThreshold',colorThreshold);
    else
        h_dend = dendrogram(links,0,'Orientation','top','Reorder',ord,...
                    'ColorThreshold',colorThreshold);
    end

    % Thicken lines:
    set(h_dend,'LineWidth',2)

    % Fix colors:
    BF_RecolorDendrogram(h_dend);

    % Label:
    box('on')
    ax = gca();
    ax.TickLabelInterpreter = 'none';
    if doVertical
        ax.YDir = 'reverse'; % needs to be reversed to match the imagesc plot (if 'cluster' is selected)
        ax.YTick = 1:numTopFeatures;
        ax.YTickLabel = topFeatureLabelsFull(ord);
        xlabel(sprintf('%s distance',distanceMetric));
    else
        ax.XTick = 1:numTopFeatures;
        ax.XTickLabel = topFeatureLabelsFull(ord);
        ylabel(sprintf('%s distance',distanceMetric));
        ax.XTickLabelRotation = 40;
    end
end

%-------------------------------------------------------------------------------
%% Inter-dependence of top features
%-------------------------------------------------------------------------------
% Plot the pairwise feature–feature correlation matrix (using BF_ClusterDown)
if ismember('cluster',whatPlots)
    clusterThreshold = 0.25; % threshold at which split into clusters
    [~,cluster_Groupi] = BF_ClusterDown(Dij,'clusterThreshold',clusterThreshold,...
                        'whatDistance',distanceMetric,...
                        'objectLabels',topFeatureLabelsMinimal);
    title(sprintf('Dependencies between %u top features (organized into %u clusters)',...
                            numTopFeatures,length(cluster_Groupi)))
end

%-------------------------------------------------------------------------------
%% Save a classifier (for the single best feature) to file
%-------------------------------------------------------------------------------
featureClassifier = struct();
if isfield(cfnParams,'classifierFilename') && ~isempty(cfnParams.classifierFilename)
    theTopFeatureIndex = ifeat(1);
    topFeatureValues = TS_DataMat(:,theTopFeatureIndex);

    % Build in-sample classifier (no CV):
    numFoldsAbove = cfnParams.numFolds;
    cfnParams.numFolds = 0;

    % Train a classification model on the top feature:
    GiveMeCfn(XTrain,yTrain,XTest,yTest,cfnParams,beVerbose)
    [bestTestStat,bestMdl,whatTestStat] = GiveMeCfn(topFeatureValues,TimeSeries.Group,...
                                                    topFeatureValues,TimeSeries.Group,...
                                                    cfnParams);

    % Prepare the featureClassifier structure:
    featureClassifier.Operation.ID = Operations.ID(theTopFeatureIndex); % Sorted list of top operations
    featureClassifier.Operation.Name = Operations.Name{theTopFeatureIndex}; % Sorted list of top operations
    featureClassifier.Mdl = bestMdl; % sorted feature models
    if numFoldsAbove > 0
        % From sorted accuracy of models
        featureClassifier.CVAccuracy = testStat_sort(1);
    else
        % CV was never done
        featureClassifier.CVAccuracy = NaN;
    end
    featureClassifier.Accuracy = bestTestStat;
    featureClassifier.whatTestStat = whatTestStat;
    featureClassifier.normalizationInfo = TS_GetFromData(whatData,'normalizationInfo');
    classes = cfnParams.classLabels;

    % Save to file
    if exist(cfnParams.classifierFilename,'file')~=0
        save(cfnParams.classifierFilename,'featureClassifier','classes','-append');
        fprintf(1,'Appended individual-feature classifiers to %s\n',cfnParams.classifierFilename);
    else
        save(cfnParams.classifierFilename,'featureClassifier','classes','-v7.3');
        fprintf(1,'Saved individual-feature classifiers to %s\n',cfnParams.classifierFilename);
    end
end

%-------------------------------------------------------------------------------
% Don't display unused outputs to screen:
if nargout == 0
    clear('ifeat','testStat','testStat_rand','featureClassifier');
end

%-------------------------------------------------------------------------------
%-------------------------------------------------------------------------------
function [theStatistic,stats] = fn_uStat(d1,d2,doExact,doP,doSigned)
    % Return a statistic from a Mann-Whitney U-test
    if doExact
        % use the exact method (slower)
        [p,~,stats] = ranksum(d1,d2,'method','exact');
    else
        % use the approximate method (faster)
        [p,~,stats] = ranksum(d1,d2,'method','approx');
    end
    if doP
        % -log10(p) from a Mann-Whitney U-test
        theStatistic = -log10(p);
    else
        % ranksum test statistic from a Mann-Whitney U-test
        theStatistic = stats.ranksum;
    end
    if doSigned
        % Gives the statistic the sign of the z statistic
        % (tracks wether higher or lower in the class)
        theStatistic = sign(stats.zval)*theStatistic;
    end
    % theStatistic = sign(stats.ranksum)*(-log10(p)); % (signed p-value)
end
%-------------------------------------------------------------------------------
function [theStatistic,stats] = fn_tStat(d1,d2,doP)
    % Return a statistic from a 2-sample Welch's t-test
    [~,p,~,stats] = ttest2(d1,d2,'Vartype','unequal');
    if doP
        % -log10(p) from the 2-sample Welch's t-test
        theStatistic = -log10(p);
    else
        % t-statistic from the 2-sample Welch's t-test
        theStatistic = stats.tstat;
    end
end
%-------------------------------------------------------------------------------
function [testStat,Mdl] = giveMeStats(dataMatrix,groupLabels,beVerbose)
    % Return a test statistic for each feature:
    testStat = zeros(numFeatures,1);
    if nargout==2
        Mdl = cell(numFeatures,1);
    end

    if strcmp(whatTestStat,'svmBeta')
        % Fit a linear SVM (multivariate) and return feature weights:
        % Mdl = fitcsvm(dataMatrix,dataLabels,'KernelFunction','linear');
        Mdl = fitcsvm(dataMatrix,groupLabels,'KernelFunction','linear','Weights',InverseProbWeight(groupLabels));
        testStat = Mdl.Beta; % (code assumes bigger is better)
        testStat = testStat/max(abs(testStat)); % rescale by max-abs
    else
        loopTimer = tic;
        BF_ProgressBar('new')
        for k = 1:numFeatures
            try
                if nargout == 2
                    % This is slower for the fast-linear classifier (but returns a model)
                    [testStat(k),Mdl{k}] = fn_testStat(dataMatrix(:,k),groupLabels,dataMatrix(:,k),groupLabels);
                else
                    testStat(k) = fn_testStat(dataMatrix(:,k),groupLabels,dataMatrix(:,k),groupLabels);
                end
            catch
                warning('Error computing %s test statistic for feature %u.',whatTestStat,k);
            end
            BF_ProgressBar(k/numFeatures)
        end
        BF_ProgressBar('close')
    end
end
%-------------------------------------------------------------------------------

end


function kern = rbfperiodicKernExpandParam(kern, params)

% RBFPERIODICKERNEXPANDPARAM Create kernel structure from RBFPERIODIC kernel's parameters.
% FORMAT
% DESC returns a RBF derived periodic kernel structure filled with the
% parameters in the given vector. This is used as a helper function to
% enable parameters to be optimised in, for example, the NETLAB
% optimisation functions.
% ARG kern : the kernel structure in which the parameters are to be
% placed.
% ARG param : vector of parameters which are to be placed in the
% kernel structure.
% RETURN kern : kernel structure with the given parameters in the
% relevant locations.
%
% SEEALSO : rbfperiodicKernParamInit, rbfperiodicKernExtractParam, kernExpandParam
%
% COPYRIGHT : Neil D. Lawrence, 2007

% KERN

kern.inverseWidth = params(1);
kern.variance = params(2);


function test_pull1663

% MEM 14gb
% WALLTIME 00:60:00
% DEPENDENCY ft_prepare_sourcemodel headsurface ft_prepare_leadfield ft_freqanalysis ft_sourceanalysis

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% This function creates a set of input-structures to be used for testing
% the DUNEuro MEG forward solution.
% The structure of this script is more or less
% 1. create input data (dull segmentation, hex and tet meshes, dipoles, sensors)
% 2. compute and compare MEG leadfield for hex and tet meshes
%   a. create volume conductor (ft_prepare_headmodel)
%   b. create source grid (ft_prepare_sourcemodel)
%   c. compute leadfield (ft_prepare_leadfield)
%   d. compare hex leadfield with tet leadfield

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 1. create input data
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% create mesh

% from test_pull1427.m
% dull segmentation
segprob = [];
segprob.brain = false(10,10,10); segprob.brain(4:7,4:7,4:7) = true;
segprob.skull = false(10,10,10); segprob.skull(3:8,3:8,3:8) = true;
segprob.scalp = false(10,10,10); segprob.scalp(2:9,2:9,2:9) = true;
%segprob.air = true(10,10,10);
%segprob.air(2:9,2:9,2:9) = false;
%segprob.brain = false(11,11,11); segprob.brain(4:8,4:8,4:8) = true;
%segprob.skull = false(11,11,11); segprob.skull(3:9,3:9,3:9) = true;
%segprob.scalp = false(11,11,11); segprob.scalp(2:10,2:10,2:10) = true;
segprob.dim = size(segprob.brain);
segprob.unit = 'cm';
segprob.coordsys = 'ctf';
segprob.transform = eye(4);
segprob.transform(1,4) = -0.5;
segprob.transform(2,4) = -0.5;
segprob.transform(3,4) = -0.5;

% visualize the segmentation
% it is more difficult to visualize a probabilistic segmentation than an indexed one
segindx = ft_datatype_segmentation(segprob, 'segmentationstyle', 'indexed');

cfg = [];
cfg.funparameter = 'tissue';
cfg.method = 'ortho';
cfg.location = [5 5 5]; % this is the center of the volume, in this plot it will be rounded off to the nearest voxel
ft_sourceplot(cfg, segindx);

% hexa mesh
cfg = [];
cfg.shift = 0.3;
cfg.method = 'hexahedral';
mesh_vol_hex = ft_prepare_mesh(cfg, segprob);

figure
ft_plot_ortho(segindx.tissue, 'transform', segindx.transform, 'location', [5 5 5], 'style', 'intersect');
hold on
ft_plot_mesh(mesh_vol_hex, 'surfaceonly', false, 'facecolor', 'none', 'edgecolor', 'm');
view(120, 30)

% tetra mesh
cfg = [];
cfg.method = 'tetrahedral';
mesh_vol_tet = ft_prepare_mesh(cfg, segprob);

figure
ft_plot_ortho(segindx.tissue, 'transform', segindx.transform, 'location', [5 5 5], 'style', 'intersect');
hold on
ft_plot_mesh(mesh_vol_tet, 'surfaceonly', false, 'facecolor', 'none', 'edgecolor', 'm');
view(120, 30)

%% define sensors

% i manually passed 5 coils and fixed projections
% or maybe take some from a ctf file? something like this (from test_pull1377.m)

% for MEG data + sensor info
%
% load(dccnpath('/home/common/matlab/fieldtrip/data/test/latest/raw/meg/preproc_ctf151.mat'), 'data');
% datameg = data;
% clear data

coils = [5 5 12; 5 12 5; -2 5 5; 12 5 5; 5 -2 5];
projections = [0 0 1 ; 0 1 0; -1 0 0; 1 0 0; 0 -1 0 ];

figure
ft_plot_ortho(segindx.tissue, 'transform', segindx.transform, 'location', [5 5 5], 'style', 'intersect');
hold on
quiver3(coils(:,1),coils(:,2),coils(:,3),projections(:,1),projections(:,2),projections(:,3),'bo')

meg_labels = cellstr(strings(1,size(coils,1)));
for i=1:size(coils,1)
  meg_labels(i) = cellstr(strcat('meg',num2str(i)));
end

sens = [];
sens.coilpos = coils;
sens.coilori = projections;
sens.chanpos = coils;
sens.chanori = projections;
sens.label = meg_labels;
sens.type = 'meg';
sens.unit = 'mm';
sens.tra = eye(5);
sens = ft_convert_units(sens,'mm');

%% define dipoles
dip_pos = [5.5 6.5 6.5; 5.5 5.5 3.5; 3.5 5.5 5.5];
dip_mom = [0 1 0; 0 0 -1; -1 0 0 ];

figure
ft_plot_ortho(segindx.tissue, 'transform', segindx.transform, 'location', [5 5 5], 'style', 'intersect');
hold on
quiver3(dip_pos(:,1),dip_pos(:,2),dip_pos(:,3),dip_mom(:,1),dip_mom(:,2),dip_mom(:,3),'bo')
view(-200, 15)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 2. compute the leadfield
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% prepare headmodel

cfg              = [];
cfg.method       = 'duneuro';
cfg.conductivity = [0.33, 0.43, 0.53];  % vector, conductivity values for tissues: check the order here
vol_duneuro_hex  = ft_prepare_headmodel(cfg, mesh_vol_hex);
vol_duneuro_tet  = ft_prepare_headmodel(cfg, mesh_vol_tet);

%% prepare sourcemodel

% hex
cfg = [];
cfg.sourcemodel.pos = dip_pos';
cfg.sourcemodel.inside = ones(size(dip_pos,2),1);
cfg.grad = sens;
cfg.headmodel = vol_duneuro_hex;
sm_duneuro_hex = ft_prepare_sourcemodel(cfg);

% tet
cfg = [];
cfg.sourcemodel.pos = dip_pos';
cfg.sourcemodel.inside = ones(size(dip_pos,2),1);
cfg.grad = sens;
cfg.headmodel = vol_duneuro_tet;
sm_duneuro_tet = ft_prepare_sourcemodel(cfg);

%% prepare leadfield

% hex
cfg                 = [];
cfg.sourcemodel     = sm_duneuro_hex;
cfg.sourcemodel.mom = dip_mom;
cfg.headmodel       = vol_duneuro_hex;
cfg.grad            = sens;
cfg.reducerank      = 3;
out_hex = ft_prepare_leadfield(cfg);
lf_hex = cell2mat(out_hex.leadfield);

% tet
cfg                 = [];
cfg.sourcemodel     = sm_duneuro_tet;
cfg.sourcemodel.mom = dip_mom;
cfg.headmodel       = vol_duneuro_tet;
cfg.grad            = sens;
cfg.reducerank      = 3;
out_tet = ft_prepare_leadfield(cfg);
lf_tet = cell2mat(out_tet.leadfield);

%% compare leadfields

figure, plot(lf_hex,'r'), hold on, plot(lf_tet,'b')
% set a limit for an error
rel_err_perc = 100*(abs(lf_hex-lf_tet)./norm(lf_hex));
assert(max(max(rel_err_perc))<10)

end % main function


function ADA = getada2(ADA, DAt,Aord, K) %#ok
% ADA = getada2(ADA, DAt,Aord, K)
%
% GETADA2  Compute ADA += DAt.q'*DAt.q
%   IMPORTANT: Updated ADA only on triu(ADA(Aord.qperm,Aord.qperm)).
%     Remaining entries are not affected.
%
% ******************** INTERNAL FUNCTION OF SEDUMI ********************
%
% See also sedumi, getada1, getada3

% This file is part of SeDuMi 1.1 by Imre Polik and Oleksandr Romanko
% Copyright (C) 2005 McMaster University, Hamilton, CANADA  (since 1.1)
%
% Copyright (C) 2001 Jos F. Sturm (up to 1.05R5)
%   Dept. Econometrics & O.R., Tilburg University, the Netherlands.
%   Supported by the Netherlands Organization for Scientific Research (NWO).
%
% Affiliation SeDuMi 1.03 and 1.04Beta (2000):
%   Dept. Quantitative Economics, Maastricht University, the Netherlands.
%
% Affiliations up to SeDuMi 1.02 (AUG1998):
%   CRL, McMaster University, Canada.
%   Supported by the Netherlands Organization for Scientific Research (NWO).
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
% 02110-1301, USA

%Indicate to the user Matlab cannot find the SeDuMi binaries
sedumi_binary_error();


function grad = B_realImag2complex(future_layers, curr_layer)

future_grad = GetFutureGrad(future_layers, curr_layer);

real_grad = real(future_grad);
imag_grad = -imag(future_grad);
grad = [real_grad; imag_grad];
end


function [patchHandles textHandles] = hlp_vrect(x, varargin)
% Tim Mullen, 2011, SCCN/INC/UCSD

g = finputcheck(varargin, ...
    {'yscale'           'real'      []           1; ...         % vertical scaling (relative to ylimits)
    'dock'              'string'    {'bottom','top','none'} 'top'; ...    % whether to 'dock' the patch at the top or bottom of the axis. If 'none' then scale is location on yaxis of [bottom top] of rect
    'label'             ''          []              {}; ...
    'axesHandle'        'real'      []              gca; ...
    'textPosition'      'real'      [0 1]           [0.5 0.02]; ...
    'textProperties'    'cell'      []              {}; ...
    'patchProperties'   'cell'      []              {'FaceColor',[0.7 0.7 1],'FaceAlpha',0.5,'EdgeColor',[0.2 0.2 0.2],'EdgeAlpha',0.5} ...
    },'hlp_vrect','error','quiet');

if ischar(g)
    help hlp_vrect;
    error(g);
end

pp = hlp_varargin2struct(g.patchProperties);
if ~isfield(pp,'FaceColor');
    FaceColor = [0.7 0.7 1];
else
    FaceColor = pp.FaceColor;
end
clear pp;

xsize = size(x);
if( xsize(2)~=2 )
    error('x must have two columns');
end

if ~iscell(g.label)
    g.label = {g.label};
end

if length(g.label)~=xsize(1)
    g.label = repmat(g.label,xsize(1),1);
end

if size(g.textPosition,2) ~= 2
    error('textPosition must have two columns [ (x,y) position ]');
end

if size(g.textPosition,1)~=xsize(1)
    g.textPosition=repmat(g.textPosition,xsize(1),1);
end

textPositionX = g.textPosition(:,1);
textPositionY = g.textPosition(:,2);

holdState = ishold(g.axesHandle);
hold(g.axesHandle, 'on');

if strcmpi(g.dock,'none')
    yLimits = g.yscale;
else
    yl = get(g.axesHandle,'ylim');             % Row vector
    yLimits = yl.*g.yscale;
end

if any(g.yscale<1)
    switch g.dock
        case 'bottom'
            yLimits = yLimits-(yLimits(1)-yl(1))+0.001*diff(yLimits);
        case 'top'
            yLimits = yLimits+(yl(2)-yLimits(2))-0.001*diff(yLimits);
    end
end

% bring desired axes into focus
set(gcf,'CurrentAxes',g.axesHandle);

patchHandles = zeros(1,xsize(1));
textHandles = [];

for k=1:xsize(1)
    % for each patch
    patchHandles(k) = patch([x(k,1) x(k,1) x(k,2) x(k,2)], [yLimits yLimits(end:-1:1)],FaceColor,g.patchProperties{:});

    if( ~isempty(g.label) )
%         xLimits = get(g.axesHandle,'xlim');
%         xLowerLimit = xLimits(1);
%         xUpperLimit = xLimits(2);
%         xRange      = xUpperLimit - xLowerLimit;
        xPosition   = x(k,1) + textPositionX(k,:)*(x(k,2)-x(k,1));

        yUpperLimit = yLimits(2);
        yLowerLimit = yLimits(1);
        yRange      = yUpperLimit - yLowerLimit;
        Yposition   = yLowerLimit + textPositionY(k,:)*yRange;
%         Yposition   = repmat(yPosition, length(x), 1);

        % textHandle is a vector correspond to the line
        textHandles(k) = text(xPosition, Yposition, g.label{k}, 'Parent', g.axesHandle,g.textProperties{:});
        % center text
        extent = get(textHandles(k),'Extent');
        set(textHandles(k),'Position',[xPosition-extent(3)/2 Yposition]);

    end
end

if( holdState==false )
    hold(g.axesHandle, 'off');
end


% make movie of whole-brain activity, with stimulus and motor icons.

%% (manually run twice) 2 stimulus types: Phototaxis and OMR
isPTnotOMR = 0; % 1
choose_xyz = 'x';

%% load files
if isPTnotOMR
    filedir = 'C:\Users\Xiu\Downloads\chuckFish8\phototaxis\max_z';
    load('C:\Users\Xiu\Downloads\chuckFish8\phototaxis\PT_frame.mat');
    swim = PT_frame;
else
    filedir = ['C:\Users\Xiu\Downloads\chuckFish8\omr\max_',choose_xyz];
    load('C:\Users\Xiu\Downloads\chuckFish8\omr\OMR_swim_frame.mat');
    swim = OMR_swim_frame;
end

% load images
dir1 = dir(fullfile(filedir, '*.tif'));
nFrames = length(dir1);

% load first image
im1 = imread(fullfile(filedir,dir1(1).name));
[s1,s2] = size(im1);

% load all images
IM = zeros(s1,s2,nFrames);
for i_frame = 1:nFrames
    IM(:,:,i_frame) = imread(fullfile(filedir,dir1(i_frame).name));
end

%% subtract average (dFF) for this chunk of data (output in correct scale)
imavr = mean(IM,3);

IMavr = repmat(imavr,1,1,nFrames);
dIM0 = (IM-IMavr)./IMavr;

%% add fish outline
if choose_xyz == 'x'
    AI_fishoutline = imread('C:\Users\Xiu\Dropbox (Personal)\!Proj FishExplorer\presentation\Media\AI_fishoutline.tif');
    imfish = AI_fishoutline(:,:,1);
    IM_fish = double(repmat(imfish,1,1,nFrames));
    dIM = dIM0 + IM_fish;
else
    dIM = dIM0;

end
%% draw movie
if false
    %%
    figure; colormap(hot);
    clim = [0.1,1];
    for i_frame = 1:nFrames
        imagesc(dIM(:,:,i_frame),clim);axis equal; axis off
        drawnow;
        F(i_frame) = getframe;%im2frame(C);
        %    cdata = print('-RGBImage','-r300');
    end
    colorbar
    %%
    im = dIM(:,:,i_frame);
    k_zres_ratio = 20; % 19.7
    im = imresize(im, [s1*k_zres_ratio,s2],'nearest');
    figure;
    imagesc(im)
end

%% add stimulus
% manual
if isPTnotOMR % 0:white. -1:right; 1:left
    stim_1rep = horzcat(0*ones(1,20),-1*ones(1,40),0*ones(1,20),1*ones(1,40));
    stim = repmat(stim_1rep,1,4);
else % -2:baseline. 0:forward. -1:right. 1:left.
    stim_1rep = horzcat(-2*ones(1,20),0*ones(1,30),-2*ones(1,20),-1*ones(1,30),-2*ones(1,20),1*ones(1,30));
    stim = repmat(stim_1rep,1,3);
end

figure;hold on;
plot(stim,'k');
plot(swim(:,1),'r');

turns = swim(:,1);
turns = turns/max(abs(turns));
turns = smooth(turns,10);
fw = swim(:,2); % not used...

%% draw movie with stim icon
% pad with extra space for stim icon
IM2 = horzcat(zeros(568,20,nFrames),dIM);
[s1,s2,~] = size(IM2);

r1 = 20;
r2 = 40;
circlemaskIX = MakeCircularMask(r1,[s1,s2]);
center_ix = sub2ind([s1,s2],s1-r2,r2);
stimIX_left = circlemaskIX+center_ix;
center_ix = sub2ind([s1,s2],r2,r2);
stimIX_right = circlemaskIX+center_ix;
stimIX_both = union(stimIX_left,stimIX_right);

figure; colormap(hot);
clim = [0.1,1];

for i_frame = 1:nFrames
    im = IM2(:,:,i_frame);
    % add stim
    switch stim(i_frame)
        case 1 % left
            im(stimIX_left) = 0.5;
        case 0 % forward
            if ~isPTnotOMR
                im(stimIX_both) = 0.5;
            end
        case -1 % right
            im(stimIX_right) = 0.5;
    end

    % add motor
    if turns(i_frame)>0
        % (left)
        radius = round(sqrt(abs(turns(i_frame)))*r2);
        circlemaskIX = MakeCircularMask(radius,[s1,s2]);
        center_ix = sub2ind([s1,s2],s1-r2,s2-r2);
        motor_left = circlemaskIX+center_ix;
        im(motor_left) = 1;
    else
        % (right)
        radius = round(sqrt(abs(turns(i_frame)))*r2);
        circlemaskIX = MakeCircularMask(radius,[s1,s2]);
        center_ix = sub2ind([s1,s2],r2,s2-r2);
        motor_right = circlemaskIX+center_ix;
        im(motor_right) = 1;
    end

    % draw
    imagesc(im,clim);axis equal; axis off
%     drawnow;
    F(i_frame) = getframe;
end

%% show movie again, if desired
figure
nloops = 3;
fps = 20;
movie(F,nloops,fps);
axis off

%% write video to file
if isPTnotOMR
    videoname = 'PT_dFF_video_Fish8.avi';
else
    videoname = 'OMR_dFF_video_Fish8.avi';
end
myVideo = VideoWriter(videoname);
myVideo.FrameRate = 20;  % Default 30
myVideo.Quality = 50;    % Default 75
open(myVideo);
writeVideo(myVideo, F);
close(myVideo);


function varargout = recover(lmi_variables)
%RECOVER Create NCVAR object using variable indicies

if isempty(lmi_variables)
    varargout{1} = [];
else
    if isa(lmi_variables,'ncvar') | isa(lmi_variables,'lmi') | isa(lmi_variables,'constraint')
        varargout{1} = flush(recover(depends(lmi_variables)));
    else
        n = length(lmi_variables);
        i=1:n;
        if nargout <= 1

            lmi_variables = lmi_variables(:)';
            basis = sparse(i,i+1,ones(n,1),n,n+1);
            if any(diff(lmi_variables)<0)
                [i,j]=sort(lmi_variables);
                basis = [basis(:,1) basis(:,j+1)];
                lmi_variables = lmi_variables(j);
            end

            [un_Z_vars2] = uniquestripped(lmi_variables);
            if length(un_Z_vars2) < length(lmi_variables)
                [un_Z_vars,hh,jj] = unique(lmi_variables);
                if length(lmi_variables) ~= length(un_Z_vars)
                    basis = basis*sparse([1 1+jj(:)'],[1 1+(1:length(jj))],ones(1,1+length(jj)))';
                    lmi_variables = un_Z_vars;
                end
            end

            varargout{1} = ncvar(n,1,[],lmi_variables(:)',basis,0);

        else
            x = ncvar(n,1,[],lmi_variables(:)',sparse(i,i+1,ones(n,1),n,n+1),0);
            for i = 1:length(lmi_variables)
                varargout{i} = flush(x(i));
            end
        end
    end
end


function [clusteredEdges, clusterInfo] = clusterCandidatePairs_3logFeatures_normalized_kmeans(imdb, imageSet, varargin)
%clusterCandidatePairs_3logFeatures_normalized_kmeans assigns all pairs of candidates to clusters, k-means clustering is performed if necessary

if ~exist('varargin', 'var')
    varargin = {};
end
%% parameters
opts = struct;
opts.numClusters = 10;
opts.clusterInfo = struct('type', [], 'mean', [], 'std', [], 'numClusters', [], 'clusterCenters', [] );
opts.maxPointsToCluster = 10^6;
opts.labelIouThreshold = 0.4;
opts.scoreThreshold = 0;
opts.dataPath = '';
opts.candidateIds = [];
opts.randomSeed = 1;
opts = vl_argparse(opts, varargin);

if ~exist('imageSet', 'var') || isempty( imageSet )
    error( 'clusterCandidatePairs_3logFeatures_normalized_kmeans:emptyImageSet', 'There are not images to do clustering' );
end

%% get the candidate data
dataset = struct;
dataset.boundingBoxes = cell(length(imageSet), 1);
dataset.numCandidates = zeros(length(imageSet), 1);
dataset.numCandidatesInitial = zeros(length(imageSet), 1);
dataset.groundTruth = cell(length(imageSet), 1);
dataset.scores = cell(length(imageSet), 1);
dataset.imageSize = cell(length(imageSet), 1);

numBoxPairs = 0;

selectedCandidatesIds = cell(length(imageSet), 1);
for iImageId = 1 : length(imageSet)
    if mod( iImageId, 1000 ) == 0
        fprintf('Reading data for image %d of %d\n', iImageId, length(imageSet));
    end

    iImage = imageSet( iImageId );

    curCandidates = load( fullfile( opts.dataPath, imdb.candidateFiles{ iImage } ) );

    dataset.numCandidatesInitial(iImageId) = size( curCandidates.boxes, 1);

    selectedCandidatesIds{iImageId} = [];
    if isempty( opts.candidateIds )
        selectedCandidatesIds{iImageId} = imdb.candidateIds{ iImage };
    else
        selectedCandidatesIds{iImageId} = opts.candidateIds{iImageId};
    end
    curCandidates = curCandidates.boxes( selectedCandidatesIds{iImageId}, :);

    % fix the Bb format: SelectiveSearch format [y1 x1 y2 x2] to format [x y w h]
    curBb = convertBb_Y1X1Y2X2_to_X1Y1WH( curCandidates(:, 1 : 4) );

    % fix the ground-truth format
    curGt = convertBb_X1Y1X2Y2_to_X1Y1WH( imdb.groundTruth{iImage} );

    dataset.imageSize{iImageId} = imdb.images.size{iImage};
    dataset.numCandidates(iImageId) = size(curBb, 1);
    dataset.boundingBoxes{iImageId} = curBb;
    dataset.groundTruth{iImageId} = curGt;
    dataset.scores{iImageId} = imdb.scores{iImage};

    numBoxPairs = numBoxPairs + size(curBb, 1) * (size(curBb, 1) - 1) / 2;
end

%% get info
numObjects = length(dataset.numCandidates);
numBoxFeatures = 8;
bBoxData = nan(numBoxPairs, numBoxFeatures);
bBoxPairLabels = nan(numBoxPairs, 2);
bBoxPairScores = nan(numBoxPairs, 2);
image_w = nan(numBoxPairs, 1);
image_h = nan(numBoxPairs, 1);
imageId = nan(numBoxPairs, 1);

clusteredEdges = cell(numObjects, 1);
startObjectId = zeros(numObjects + 1, 1);

%% collect all pairs
iPair = 0;
for iObject = 1 : numObjects
    startObjectId(iObject) = iPair + 1;
    curBbNum = dataset.numCandidates(iObject);
    curPairNum = curBbNum * (curBbNum - 1) / 2;
    curIds = iPair + 1 : iPair + curPairNum;
    iPair = iPair + curPairNum;

    [bb1, bb2] = meshgrid(1 : curBbNum, 1 : curBbNum);

    curMask = bb1 < bb2;
    bb1 = bb1(curMask);
    bb1 = bb1(:);
    bb2 = bb2(curMask);
    bb2= bb2(:);

    curBBoxes = dataset.boundingBoxes{ iObject };

    curGtIoU = zeros( size(curBBoxes, 1), 1 );
    for iGt = 1 : size( dataset.groundTruth{iObject}, 1 )
        IoU = bbIntersectionOverUnion( curBBoxes, dataset.groundTruth{iObject}(iGt, :) );
        curGtIoU = max( curGtIoU, IoU );
    end
    curLabels = curGtIoU > opts.labelIouThreshold;

    bBoxData(curIds, :) = [ curBBoxes( bb1, : ), curBBoxes( bb2, : ) ];
    bBoxPairLabels(curIds, :) = [ curLabels(bb1), curLabels(bb2) ];
    bBoxPairScores(curIds, :) = [ dataset.scores{iObject}(bb1), dataset.scores{iObject}(bb2) ];

    image_w( curIds ) = dataset.imageSize{iObject}( 2 );
    image_h( curIds ) = dataset.imageSize{iObject}( 1 );
    imageId( curIds ) = iObject;

    clusteredEdges{iObject} = struct;
    clusteredEdges{iObject}.bbIds = [bb1, bb2];
    clusteredEdges{iObject}.clusterId = nan(size(bb1, 1), 1);
end
startObjectId( numObjects + 1 ) = iPair + 1;
bBoxData(iPair + 1 : end, :) = [];
image_w(iPair + 1 : end, :) = [];
image_h(iPair + 1 : end, :) = [];
bBoxPairLabels(iPair + 1 : end, :) = [];
bBoxPairScores(iPair + 1 : end, :) = [];
imageId(iPair + 1 : end, :) = [];

%% sort bouding boxes (left to right)
changeOrder = ( bBoxData(:, 1) > bBoxData(:, 5) ) | ( (bBoxData(:, 1) == bBoxData(:, 5)) & (bBoxData(:, 2) > bBoxData(:, 6)) );
tmp = bBoxData(changeOrder, 1 : 4);
bBoxData(changeOrder, 1 : 4) = bBoxData(changeOrder, 5 : 8);
bBoxData(changeOrder, 5 : 8) = tmp;

tmp = bBoxPairLabels(changeOrder, 1);
bBoxPairLabels(changeOrder, 1) = bBoxPairLabels(changeOrder, 2);
bBoxPairLabels(changeOrder, 2) = tmp;

tmp = bBoxPairScores(changeOrder, 1);
bBoxPairScores(changeOrder, 1) = bBoxPairScores(changeOrder, 2);
bBoxPairScores(changeOrder, 2) = tmp;

%% get BB features
cx1 = bBoxData(:, 1) + bBoxData(:, 3) / 2;
cx2 = bBoxData(:, 5) + bBoxData(:, 7) / 2;
cy1 = bBoxData(:, 2) + bBoxData(:, 4) / 2;
cy2 = bBoxData(:, 6) + bBoxData(:, 8) / 2;
w1 = bBoxData(:, 3);
w2 = bBoxData(:, 7);
h1 = bBoxData(:, 4);
h2 = bBoxData(:, 8);
s1 = (w1 + h1) / 2;
s2 = (w2 + h2) / 2;

f1 = s1 ./ s2;
f2 = (cx2 - cx1) ./ s1;
f3 = (cy2 - cy1) ./ s1;
f1 = log(f1);
f2 = log( abs(f2) + 1 ) .* sign(f2);
f3 = log( abs(f3) + 1 ) .* sign(f3);
features = [f1, f2, f3];

%% clustering
if isempty( opts.clusterInfo.type )
    %% Run kmeans
    rng(opts.randomSeed , 'twister');

    % normalize the features
    meanFeatures = mean( features, 1 );
    stdFeatures = std( features, 0, 1 );

    featuresNormalized = bsxfun( @minus, features, meanFeatures );
    featuresNormalized = bsxfun( @rdivide, featuresNormalized, stdFeatures );

    clusterInfo = struct;
    clusterInfo.type = '3logFeatures_normalized';
    clusterInfo.mean = meanFeatures;
    clusterInfo.std = stdFeatures;
    clusterInfo.numClusters = opts.numClusters;
    clusterInfo.clusterCenters = [];

    curMask = ( ( bBoxPairScores(:, 1) > opts.scoreThreshold ) | ( bBoxPairLabels(:, 1) == 1) ) ...
        & ( ( bBoxPairScores(:, 2) > opts.scoreThreshold ) | ( bBoxPairLabels(:, 2) == 1) );

    curIds = find( curMask );
    if numel( curIds ) > opts.maxPointsToCluster
        curIds = curIds( randperm( numel( curIds ), opts.maxPointsToCluster ) );
    end

    [~,  clusterInfo.clusterCenters] = kmeans( featuresNormalized(curIds, :), opts.numClusters );

else
    clusterInfo = opts.clusterInfo;
    featuresNormalized = bsxfun( @minus, features, clusterInfo.mean );
    featuresNormalized = bsxfun( @rdivide, featuresNormalized, clusterInfo.std );
end
clusterIds = assignPointsToClusters( clusterInfo.clusterCenters, featuresNormalized );

%% change order within some pairs bounding boxes, revert to original indices
for iObject = 1 : numObjects
    curIds = startObjectId(iObject) : 1 : startObjectId(iObject + 1) - 1;
    changeFlags = changeOrder( curIds );
    curClusterIds = clusterIds( curIds );

    tmp = clusteredEdges{iObject}.bbIds(changeFlags, 1);
    clusteredEdges{iObject}.bbIds(changeFlags, 1) = clusteredEdges{iObject}.bbIds(changeFlags, 2);
    clusteredEdges{iObject}.bbIds(changeFlags, 2) = tmp;
    clusteredEdges{iObject}.clusterId = curClusterIds;

    clusteredEdges{iObject}.initialBbIds = selectedCandidatesIds{ iObject }( clusteredEdges{iObject}.bbIds );
end

end


%%***************************************************
%% qprod:
%%
%% Input: A = [A1 A2 ... An]
%%        x = [x1; x2; ...; xn]
%% Output: [A1*x1 A2*x2 ... An*xn]
%%
%% SDPT3: version 3.1
%% Copyright (c) 1997 by
%% K.C. Toh, M.J. Todd, R.H. Tutuncu
%% Last Modified: 16 Sep 2004
%%***************************************************

  function Ax = qprod(pblk,A,x);

  if (size(pblk,1) > 1)
     error('qprod: pblk can only have 1 row');
  end
  if issparse(x); x = full(x); end; %% for spconvert
  n = length(x);
  ii = [1:n]';
  jj = mexexpand(pblk{2},[1:length(pblk{2})]');
  X = spconvert([ii, jj, x]);
  Ax = A*X;
%%***************************************************


% Copyright (C) Daphne Koller, Stanford Univerity, 2012
%
% Author: Huayan Wang

function VisualizeDataset(Dataset)

f = figure;
for i=1:size(Dataset,1)
    img = ShowPose(reshape(Dataset(i,:,:), [10 3]));
    imshow(img);
    pause(0.3)
    if (~ishandle(f)) break; end; % quit loop when user closes the figure
end


function pt_start(mCatalog, hFigure, bMap, rContainer, sName)
    % Starts the probabilistic forecast testing.
    %
    % pt_start(mCatalog, hFigure, bMap, rContainer)
    %
    % The function invokes the parameter dialog, calls the
    %   calculation function and invokes the dialog for displaying the results
    %
    % Input parameters:
    %   mCatalog      Earthquake catalog to use for the testing
    %   hFigure       Handle of figure where the user should select the grid (i.e. the seismicity map)
    %   bMap          Map/cross-section switch. If the testing is carried out on a map set bMap = true,
    %                 on a cross-section set bMap = false
    %   rContainer    Just a container (structure proposed) to store any variables that should be available in the
    %                 probabilistic forecast testing code
    %
    % Danijel Schorlemmer
    % June 4, 2002

    report_this_filefun();

    % Launch GUI
    hMenuFig = pt_options(bMap);

    % Set up parameter struct and store input parameters
    params.mCatalog = mCatalog;
    params.sCatalogName = sName;
    params.bMap = bMap;
    params.rContainer = rContainer;

    % Analyze Output
    if ~ishandle(hMenuFig)
        answer = 0;
    else
        handles = guidata(hMenuFig);
        answer = handles.answer;
        % OK pressed
        if answer == 1
            % Get parameter values from the gui
            % ---------------------------------
            % Hypothesis
            if get(handles.radHNumber, 'Value') == 1
                rOptionsH.nGriddingMode = 0;
                rOptionsH.nNumberEvents = str2double(get(handles.txtHNumber, 'String'));
                rOptionsH.fRadius = 0;
                rOptionsH.fSizeRectX = 0;
                rOptionsH.fSizeRectY = 0;
                rOptionsH.nMinimumNumber = 0;
                rOptionsH.fMaximumRadius = str2double(get(handles.txtHMaximumRadius, 'String'));
            elseif get(handles.radHRadius, 'Value') == 1
                rOptionsH.nGriddingMode = 1;
                rOptionsH.nNumberEvents = 0;
                rOptionsH.fRadius = str2double(get(handles.txtHRadius, 'String'));
                rOptionsH.fSizeRectX = 0;
                rOptionsH.fSizeRectY = 0;
                rOptionsH.nMinimumNumber = str2double(get(handles.txtHMinimumNumber, 'String'));
                rOptionsH.fMaximumRadius = 0;
            else % rectangle
                rOptionsH.nGriddingMode = 2;
                rOptionsH.nNumberEvents = 0;
                rOptionsH.fRadius = 0;
                rOptionsH.fSizeRectX = str2double(get(handles.txtHSizeRectX, 'String'));
                rOptionsH.fSizeRectY = str2double(get(handles.txtHSizeRectY, 'String'));
                rOptionsH.nMinimumNumber = str2double(get(handles.txtHMinimumNumber, 'String'));
                rOptionsH.fMaximumRadius = 0;
            end
            rOptionsH.nCalculateMC = get(handles.cboHCalculateMC, 'Value');
            if get(handles.radHSpatialB, 'Value') == 1
                rOptionsH.nCalcMode = 0;  % Spatial b-values
            else
                rOptionsH.nCalcMode = 1;  % Overall b-values
            end
            rOptionsH.bCalcBothPeriods = get(handles.chkHCalcBothPeriods, 'Value');

            % Null hypothesis
            if get(handles.radNNumber, 'Value') == 1
                rOptionsN.nGriddingMode = 0;  % Constant number
                rOptionsN.nNumberEvents = str2double(get(handles.txtNNumber, 'String'));
                rOptionsN.fRadius = 0;
                rOptionsN.fSizeRectX = 0;
                rOptionsN.fSizeRectY = 0;
                rOptionsN.nMinimumNumber = 0;
                rOptionsN.fMaximumRadius = str2double(get(handles.txtNMaximumRadius, 'String'));
            elseif get(handles.radNRadius, 'Value') == 1
                rOptionsN.nGriddingMode = 1;  % Constant Radius
                rOptionsN.nNumberEvents = 0;
                rOptionsN.fRadius = str2double(get(handles.txtNRadius, 'String'));
                rOptionsN.fSizeRectX = 0;
                rOptionsN.fSizeRectY = 0;
                rOptionsN.nMinimumNumber = str2double(get(handles.txtNMinimumNumber, 'String'));
                rOptionsN.fMaximumRadius = 0;
            else % rectangle
                rOptionsN.nGriddingMode = 2;  % Rectangles
                rOptionsN.nNumberEvents = 0;
                rOptionsN.fRadius = 0;
                rOptionsN.fSizeRectX = str2double(get(handles.txtNSizeRectX, 'String'));
                rOptionsN.fSizeRectY = str2double(get(handles.txtNSizeRectY, 'String'));
                rOptionsN.nMinimumNumber = str2double(get(handles.txtNMinimumNumber, 'String'));
                rOptionsN.fMaximumRadius = 0;
            end
            rOptionsN.nCalculateMC = get(handles.cboNCalculateMC, 'Value');
            if get(handles.radNSpatialB, 'Value') == 1
                rOptionsN.nCalcMode = 0;  % Spatial b-values
            else
                rOptionsN.nCalcMode = 1;  % Overall b-values
            end
            rOptionsN.bCalcBothPeriods = get(handles.chkNCalcBothPeriods, 'Value');

            % Testing
            if get(handles.radTNumber, 'Value') == 1
                rOptionsT.nGriddingMode = 0;  % Constant number
                rOptionsT.nNumberEvents = str2double(get(handles.txtTNumber, 'String'));
                rOptionsT.fRadius = 0;
                rOptionsT.fSizeRectX = 0;
                rOptionsT.fSizeRectY = 0;
                rOptionsT.nMinimumNumber = 0;
                rOptionsT.fMaximumRadius = str2double(get(handles.txtTMaximumRadius, 'String'));
            elseif get(handles.radTRadius, 'Value') == 1
                rOptionsT.nGriddingMode = 1;  % Constant Radius
                rOptionsT.nNumberEvents = 0;
                rOptionsT.fRadius = str2double(get(handles.txtTRadius, 'String'));
                rOptionsT.fSizeRectX = 0;
                rOptionsT.fSizeRectY = 0;
                rOptionsT.nMinimumNumber = str2double(get(handles.txtTMinimumNumber, 'String'));
                rOptionsT.fMaximumRadius = 0;
            else % rectangle
                rOptionsT.nGriddingMode = 2;  % Rectangles
                rOptionsT.nNumberEvents = 0;
                rOptionsT.fRadius = 0;
                rOptionsT.fSizeRectX = str2double(get(handles.txtTSizeRectX, 'String'));
                rOptionsT.fSizeRectY = str2double(get(handles.txtTSizeRectY, 'String'));
                rOptionsT.nMinimumNumber = str2double(get(handles.txtTMinimumNumber, 'String'));
                rOptionsT.fMaximumRadius = 0;
            end
            rOptionsT.nCalculateMC = [];      % Fill the unused fields
            rOptionsT.nCalcMode = [];         % Fill the unused fields
            rOptionsT.bCalcBothPeriods = false;   % Fill the unused fields
            bSaveParameter = get(handles.chkSaveParameter, 'Value');
            if bSaveParameter
                sSaveParameter = get(handles.lblSaveParameter, 'String');
            end

            % Set up the options array
            params.rOptions = [rOptionsH; rOptionsN; rOptionsT];

            % Options section
            params.bGridEntireArea = get(handles.chkGridEntireArea, 'Value');
            params.fSpacingX = str2double(get(handles.txtSpacingX, 'String'));
            params.fSpacingY = str2double(get(handles.txtSpacingY, 'String'));
            params.fSplitTime = str2double(get(handles.txtSplitTime, 'String'));
            params.bForecastPeriod = get(handles.chkForecastPeriod, 'Value');
            if params.bForecastPeriod
                params.fForecastPeriod = str2double(get(handles.txtForecastPeriod, 'String'));
            else
                params.fForecastPeriod = 0;
            end
            params.bLearningPeriod = get(handles.chkLearningPeriod, 'Value');
            if params.bLearningPeriod
                params.fLearningPeriod = str2double(get(handles.txtLearningPeriod, 'String'));
            else
                params.fLearningPeriod = 0;
            end
            params.bRandomNode = get(handles.chkRandomNode, 'Value');
            if params.bRandomNode
                params.nNumberCalculationNode = str2double(get(handles.txtNumberCalculationNode, 'String'));
            end
            params.bMinMagMc = get(handles.chkMinMag, 'Value');
            if ~params.bMinMagMc
                params.fMinMag = str2double(get(handles.txtMinMag, 'String'));
            else
                params.fMinMag = [];
            end
            params.fMaxMag = str2double(get(handles.txtMaxMag, 'String'));

            % Additional stuff
            params.nTestMethod = get(handles.cboTestMethod, 'Value');
            params.bForceRandomCalculation = false;
            params.sComment = 'First test';

            % Dave Jackson
            params.bSaveRates = true;
            params.sSaveRatesAsciiFilenameH = '/home/danijel/pro/parkfield/Runs/ratesH.txt';
            params.sSaveRatesAsciiFilenameN = '/home/danijel/pro/parkfield/Runs/ratesN.txt';
            params.sSaveRatesMatFilenameH = '/home/danijel/pro/parkfield/Runs/ratesH.mat';
            params.sSaveRatesMatFilenameN = '/home/danijel/pro/parkfield/Runs/ratesN.mat';
            %     delete(params.sSaveRatesAsciiFilenameH);
            %     delete(params.sSaveRatesAsciiFilenameN);
            %     delete(params.sSaveRatesMatFilenameH);
            %     delete(params.sSaveRatesMatFilenameN);
            params.vRatesH = [];
            params.vRatesN = [];

            % Remove figure from memory
            delete(hMenuFig);

            % Select grid
            [params.mPolygon, params.vX, params.vY, params.vUsedNodes] = ex_selectgrid(hFigure, params.fSpacingX, params.fSpacingY, params.bGridEntireArea);

            % Validate polygonsize
            if length(params.vX) < 2  ||  length(params.vY) < 2
                errordlg('Selection is too small. Please select a larger polygon.');
                return;
            end

            % General calculations
            % --------------------

            % Split the catalog
            [params.mLearningCatalog, params.mObservedCatalog, ~, ~, params.fLearningPeriodUsed, params.fObservedPeriodUsed] ...
                = ex_SplitCatalog(params.mCatalog, params.fSplitTime, ...
                params.bLearningPeriod, params.fLearningPeriod, params.bForecastPeriod, params.fForecastPeriod);
            % Create Indices to catalogs
            for nCnt_ = 1:3
                % Create Indices to learning catalogs
                params.rOptions(nCnt_).caLearningNodeIndices = ex_CreateIndexCatalog(params.mLearningCatalog, params.mPolygon, params.bMap, ...
                    params.rOptions(nCnt_).nGriddingMode, params.rOptions(nCnt_).nNumberEvents, params.rOptions(nCnt_).fRadius, ...
                    params.rOptions(nCnt_).fSizeRectX, params.rOptions(nCnt_).fSizeRectY);
                % Create Indices to learning catalogs
                params.rOptions(nCnt_).caObservedNodeIndices = ex_CreateIndexCatalog(params.mObservedCatalog, params.mPolygon, params.bMap, ...
                    params.rOptions(nCnt_).nGriddingMode, params.rOptions(nCnt_).nNumberEvents, params.rOptions(nCnt_).fRadius, ...
                    params.rOptions(nCnt_).fSizeRectX, params.rOptions(nCnt_).fSizeRectY);
            end

            if bSaveParameter
                % Save parameters
                save(sSaveParameter, 'params');
            else
                %       % Determine the overall magnitude of completeness and overall b-value
                %       params.fMcOverall = calc_Mc(params.mCatalog, params.nCalculateMC);
                %       vSel = params.mCatalog.Magnitude >= params.fMcOverall;
                %       [params.fBValueOverall params.fStdDevOverall] = calc_bmemag(params.mCatalog.Magnitude(vSel), 0.1);

                %       % Determine the overall standard deviation of the b-value for the bayesian approach (if used)
                %       if params.nTestMethod == 3
                %         disp(['Calculating overall mean standard deviation']);
                %         params.fStdDevOverall = kj_CalcOverallStdDev(params);
                %         disp(['Standard deviation calculated']);
                %       end

                % Perform the calculation
                [params] = pt_calc(params);

                if params.bSaveRates
                    vRatesH = params.vRatesH;
                    vRatesN = params.vRatesN;
                    save(params.sSaveRatesAsciiFilenameH, 'vRatesH', '-ascii');
                    save(params.sSaveRatesAsciiFilenameN, 'vRatesN', '-ascii');
                    save(params.sSaveRatesMatFilenameH, 'vRatesH');
                    save(params.sSaveRatesMatFilenameN, 'vRatesN');
                end

                % Display the results
                gui_result(params);
            end
        else
            delete(hMenuFig);
        end
    end
end


function cuboidhyp = cuboid_pts2hyp(pts, vp)

junc3dummy = struct('type',[],'pt',[],'regionid',[]);
cuboidhyp.junc3(8) = junc3dummy; % initialize

for i = 1:8
    cuboidhyp.junc3(i).type = i;
    cuboidhyp.junc3(i).pt = pts(i,:);
    cuboidhyp.junc3(i).regionid = getregionid(pts(i,1), pts(i,2), vp);
end


function [grandavg] = ft_timelockgrandaverage(cfg, varargin)

% FT_TIMELOCKGRANDAVERAGE computes ERF/ERP average and variance
% over multiple subjects or over blocks within one subject
%
% Use as
%   [grandavg] = ft_timelockgrandaverage(cfg, avg1, avg2, avg3, ...)
%
% where
%   avg1..N are the ERF/ERP averages as obtained from FT_TIMELOCKANALYSIS
%
% and cfg is a configuration structure with
%   cfg.method         = string, 'across' or 'within' (default = 'across'), see below for details
%   cfg.parameter      = string, which parameter to average (default = 'avg')
%   cfg.channel        = Nx1 cell-array with selection of channels (default = 'all'), see FT_CHANNELSELECTION for details
%   cfg.latency        = [begin end] in seconds or 'all' (default = 'all')
%   cfg.keepindividual = string, 'yes' or 'no' (default = 'no')
%   cfg.nanmean        = string, can be 'yes' or 'no' (default = 'yes')
%   cfg.normalizevar   = string, 'N' or 'N-1' (default = 'N-1')
%
% If cfg.method = 'across', a plain average is performed, i.e. the requested
% parameter in each input argument is weighted equally in the average. This is useful
% when averaging across subjects. The variance-field will contain the variance across
% the parameter of interest, and the output dof-field will contain the number of
% input arguments.
%
% If cfg.method = 'within', a weighted average is performed, i.e. the requested
% parameter in each input argument is weighted according to the degrees of freedom in
% the dof-field. This is useful when averaging within subjects across blocks, e.g.
% when each block was recorded in a separate file. The variance-field will contain
% the variance across all input observations, and the output dof-field will contain
% the total number of observations.
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
%   cfg.outputfile  =  ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure. For this particular function, the input should be
% structured as a cell-array.
%
% See also FT_TIMELOCKANALYSIS, FT_TIMELOCKSTATISTICS, FT_TIMELOCKBASELINE

% Copyright (C) 2003-2006, Jens Schwarzbach
% Copyright (C) 2013, Burkhard Maess
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip 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.
%
%    FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin   = nargin;
ft_nargout  = nargout;

% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar varargin
ft_preamble provenance varargin

% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
  return
end

% return immediately after distributed execution
if ~isempty(ft_getopt(cfg, 'distribute'))
  return
end

% check if the input data is valid for this function
for i=1:length(varargin)
  if isfield(varargin{i},'trial') && isfield(varargin{i},'avg') % see bug2372 (dieloz)
    varargin{i} = rmfield(varargin{i}, 'trial');
    varargin{i}.dimord = 'chan_time';
    ft_warning('not using the trials, using the single-subject average to compute the grand average');
  else
    if isfield(varargin{i},'trial') && ~isfield(varargin{i},'avg')
      ft_error('input structure %d does not contain an average, use FT_TIMELOCKANALYSIS first', i);
    end
  end
  varargin{i} = ft_checkdata(varargin{i}, 'datatype', 'timelock', 'feedback', 'no');
end

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden',  {'channels'}); % prevent accidental typos, see issue 1729

% set the defaults
cfg.method         = ft_getopt(cfg, 'method'        , 'across');
cfg.parameter      = ft_getopt(cfg, 'parameter'     , 'avg');
cfg.channel        = ft_getopt(cfg, 'channel'       , 'all');
cfg.latency        = ft_getopt(cfg, 'latency'       , 'all');
cfg.keepindividual = ft_getopt(cfg, 'keepindividual', 'no');
cfg.nanmean        = ft_getopt(cfg, 'nanmean'       , 'yes');
cfg.normalizevar   = ft_getopt(cfg, 'normalizevar'  , 'N-1');

if iscell(cfg.parameter)
  if numel(cfg.parameter)>1
    ft_error('only a single parameter can be specified to be averaged');
  else
    cfg.parameter = cfg.parameter{1};
  end
end

if strcmp(cfg.parameter, 'trial')
  ft_error('not supporting averaging over the repetition dimension, please use FT_TIMELOCKANALYSIS');
end

% select trials and channels of interest
orgcfg = cfg;
tmpcfg = keepfields(cfg, {'parameter', 'channel', 'tolerance', 'latency', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
[varargin{:}] = ft_selectdata(tmpcfg, varargin{:});
% restore the provenance information
[cfg, varargin{:}] = rollback_provenance(cfg, varargin{:});
% do not use the default option returned by FT_SELECTDATA, but the original one for this function
cfg.nanmean = orgcfg.nanmean;

% determine the size of the data to be averaged
datsiz = size(varargin{1}.(cfg.parameter));
dimord = getdimord(varargin{1}, cfg.parameter);
nsubj  = length(varargin);

% whether to normalize the variance with N or N-1, see VAR
normalizewithN = strcmpi(cfg.normalizevar, 'N');

% whether nans should persist in the output or be treated as missing values
if istrue(cfg.nanmean)
  mymean = @nanmean;
  myvar  = @nanvar;
  mysum  = @nansum;
else
  mymean = @mean;
  myvar  = @var;
  mysum  = @sum;
end

if istrue(cfg.keepindividual)
  fprintf('not computing average, but keeping individual %s for %d subjects\n', cfg.parameter, nsubj);

  % allocate memory to hold the data and collect it
  dat = zeros([nsubj, datsiz]);
  for s=1:nsubj
    dat(s, :, :, :) = varargin{s}.(cfg.parameter);
  end
  grandavg.individual = dat; % Nsubj x Nchan x Nsamples

else
  dat = nan([nsubj, datsiz]);
  dof = nan([nsubj, datsiz]);

  switch cfg.method
    case 'across'
      fprintf('computing average of %s across %d subjects\n', cfg.parameter, nsubj);
      for s=1:nsubj
        dat(s, :, :, :) = varargin{s}.(cfg.parameter);
      end

      % compute the mean and variance across subjects
      grandavg.avg = reshape(mymean(dat, 1),                 datsiz);
      grandavg.var = reshape(myvar(dat, normalizewithN, 1),  datsiz);
      grandavg.dof = reshape(sum(isfinite(dat), 1),          datsiz);

      if normalizewithN
        % just to be sure
        grandavg.var(grandavg.dof<=0) = NaN;
      else
        % see https://stats.stackexchange.com/questions/4068/how-should-one-define-the-sample-variance-for-scalar-input
        % the fieldtrip/external/stats/nanvar implementation behaves differently here than Mathworks VAR and NANVAR implementations
        grandavg.var(grandavg.dof<=1) = NaN;
      end

    case 'within'
      fprintf('computing average of %s within subjects over %d blocks\n', cfg.parameter, nsubj);

      for s=1:nsubj
        dat(s, :, :, :) = varargin{s}.(cfg.parameter);
        dof(s, :, :, :) = varargin{s}.dof;
      end % for nsub

      % compute the weighted mean across input arguments
      grandavg.avg = mysum(dat .* dof, 1) ./ sum(dof, 1);
      grandavg.avg = reshape(grandavg.avg, datsiz);
      grandavg.dof = sum(dof, 1);
      grandavg.dof = reshape(grandavg.dof, datsiz);

      % this follows https://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance with frequency weights
      if normalizewithN
        grandavg.var = mysum(dof .* (dat - repmat(reshape(grandavg.avg, [1 datsiz]), [nsubj 1 1])).^2, 1) ./  sum(dof, 1);
        grandavg.var = reshape(grandavg.var, datsiz);
      else
        grandavg.var = mysum(dof .* (dat - repmat(reshape(grandavg.avg, [1 datsiz]), [nsubj 1 1])).^2, 1) ./ (sum(dof, 1) - 1);
        grandavg.var = reshape(grandavg.var, datsiz);
      end

    otherwise
      ft_error('unsupported value for cfg.method')

  end % switch
end % if keepindividual

% collect the output data
grandavg = copyfields(varargin{1}, grandavg, {'time', 'freq', 'label', 'labelcmb'});

% sensor positions should only be present in the output when they are identical in all inputs
hasgrad = cellfun(@(x) isfield(x, 'grad'), varargin(:));
if all(hasgrad) % check if positions are different between subjects
  samegrad = cellfun(@(x) isequal(varargin{1}.grad, x.grad), varargin(2:end));
  if all(samegrad)
    grandavg.grad = varargin{1}.grad;
  else
    ft_warning('discarding gradiometer information because it is not identical in all inputs');
  end
end

haselec = cellfun(@(x) isfield(x, 'elec'), varargin(:));
if all(haselec) % check if positions are different between subjects
  sameelec = cellfun(@(x) isequal(varargin{1}.elec, x.elec), varargin(2:end));
  if all(sameelec)
    grandavg.elec = varargin{1}.elec;
  else
    ft_warning('discarding electrode information because it is not identical in all inputs');
  end
end

hasopto = cellfun(@(x) isfield(x, 'opto'), varargin(:));
if all(hasopto) % check if positions are different between subjects
  sameopto = cellfun(@(x) isequal(varargin{1}.opto, x.opto), varargin(2:end));
  if all(sameopto)
    grandavg.opto = varargin{1}.opto;
  else
    ft_warning('discarding optode information because it is not identical in all inputs');
  end
end

% set dimord
if strcmp(cfg.keepindividual, 'yes')
  grandavg.dimord = ['subj_', dimord];
elseif strcmp(cfg.keepindividual, 'no')
  grandavg.dimord =  dimord;
end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous   varargin
ft_postamble provenance grandavg
ft_postamble history    grandavg
ft_postamble savevar    grandavg


function [updatedStates, rho_i_pj_i_est] = updateStateStruct( currentStates, lmIds, rho_i_pj_i_est, dx)
%UPDATESTATESTRUCT Updates states by applying a step dx

updatedStates = currentStates;
numStates = length(currentStates);
for stIdx = 1:numStates-1
    dr = dx(1+(stIdx-1)*6:3+(stIdx-1)*6);
    phi = dx(4+(stIdx-1)*6:6+(stIdx-1)*6);

    updatedStates{stIdx+1}.r_vi_i = currentStates{stIdx+1}.r_vi_i + dr;
    updatedStates{stIdx+1}.C_vi = Cfrompsi(phi)*currentStates{stIdx+1}.C_vi;
end

initialIdx = (numStates-1)*6 + 1;
for lm_i = 1:length(lmIds)
    idx = initialIdx + (lm_i-1)*3;
    dp = dx(idx:idx+2);
    rho_i_pj_i_est(:, lmIds(lm_i)) = rho_i_pj_i_est(:, lmIds(lm_i)) + dp;
end

end


% params
totalNum = 1502861;
bValue = 3000;
lamda = 0.1;
qFile = '/home/yinhuan/Data/mapModel/park/weightVector.txt';
visFilesDir = '/home/yinhuan/Data/mapModel/park/visMatrix/';
maxQ = 110;
minQ = 1;

splitLength_0 = 100;
saveResultsDir_0 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/0/';
saveReIndexFile_0 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/index/0.txt';
saveNewQFile_0 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/weight/0.txt';

splitLength_1 = 200;
saveResultsDir_1 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/1/';
saveReIndexFile_1 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/index/1.txt';
saveNewQFile_1 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/weight/1.txt';

splitLength_2 = 400;
saveResultsDir_2 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/2/';
saveReIndexFile_2 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/index/2.txt';
saveNewQFile_2 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/weight/2.txt';

splitLength_3 = 800;
saveResultsDir_3 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/3/';
saveReIndexFile_3 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/index/3.txt';
saveNewQFile_3 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/weight/3.txt';

splitLength_4 = 800;
saveResultsDir_4 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/4/';
saveReIndexFile_4 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/index/4.txt';
saveNewQFile_4 = '/home/yinhuan/Data/mapModel/park/iter_b_3000/weight/4.txt';

%% iteration of optimization

visCells_origin = fromVisDirToCells(visFilesDir);

[ epsilon_soft_0, time_sum_0  ] = Uniform_loopCompress_Cells( lamda, qFile, visCells_origin, splitLength_0, totalNum, bValue, saveResultsDir_0 );
min_cost_0 = get_min_cost(minQ, maxQ, saveResultsDir_0, qFile, lamda, epsilon_soft_0);
[totalNum_0, visCells_0] = deleteZeroPoints_Cells( qFile, visCells_origin, saveResultsDir_0, saveReIndexFile_0, saveNewQFile_0 );

[ epsilon_soft_1, time_sum_1  ] = Uniform_loopCompress_Cells( lamda, saveNewQFile_0, visCells_0, splitLength_1, totalNum_0, bValue, saveResultsDir_1 );
min_cost_1 = get_min_cost(minQ, maxQ, saveResultsDir_1, saveNewQFile_0, lamda, epsilon_soft_1);
[totalNum_1, visCells_1] = deleteZeroPoints_Cells( saveNewQFile_0, visCells_0, saveResultsDir_1, saveReIndexFile_1, saveNewQFile_1 );

[ epsilon_soft_2, time_sum_2  ] = Uniform_loopCompress_Cells( lamda, saveNewQFile_1, visCells_1, splitLength_2, totalNum_1, bValue, saveResultsDir_2 );
min_cost_2 = get_min_cost(minQ, maxQ, saveResultsDir_2, saveNewQFile_1, lamda, epsilon_soft_2);
[totalNum_2, visCells_2] = deleteZeroPoints_Cells( saveNewQFile_1, visCells_1, saveResultsDir_2, saveReIndexFile_2, saveNewQFile_2 );

[ epsilon_soft_3, time_sum_3  ] = Uniform_loopCompress_Cells( lamda, saveNewQFile_2, visCells_2, splitLength_3, totalNum_2, bValue, saveResultsDir_3 );
min_cost_3 = get_min_cost(minQ, maxQ, saveResultsDir_3, saveNewQFile_2, lamda, epsilon_soft_3);
[totalNum_3, visCells_3] = deleteZeroPoints_Cells( saveNewQFile_2, visCells_2, saveResultsDir_3, saveReIndexFile_3, saveNewQFile_3 );

[ epsilon_soft_4, time_sum_4  ] = Uniform_loopCompress_Cells( lamda, saveNewQFile_3, visCells_3, splitLength_4, totalNum_3, bValue, saveResultsDir_4 );
min_cost_4 = get_min_cost(minQ, maxQ, saveResultsDir_4, saveNewQFile_3, lamda, epsilon_soft_4);
[totalNum_4, visCells_4] = deleteZeroPoints_Cells( saveNewQFile_3, visCells_3, saveResultsDir_4, saveReIndexFile_4, saveNewQFile_4 );

%% save the results

compressIndex_3 = anal_reindex_last(saveResultsDir_4, saveReIndexFile_3);
compressIndex_2 = anal_reindex_middle(compressIndex_3, saveReIndexFile_2);
compressIndex_1 = anal_reindex_middle(compressIndex_2, saveReIndexFile_1);
compressIndex_0 = anal_reindex_middle(compressIndex_1, saveReIndexFile_0);

dlmwrite('/home/yinhuan/Data/mapModel/park/iter_b_3000/compressIndex_final.txt', compressIndex_0, 'precision', '%d');


classdef SetPolyThrottleModelActionOptimVar < AbstractOptimizationVariable
    %SetRPYSteeringModelActionOptimVar Summary of this class goes here
    %   Detailed explanation goes here

    properties
        varObj = ThrottlePolyModel.getDefaultThrottleModel()

        lb(1,4) double
        ub(1,4) double

        varConst(1,1) logical = false;
        varLin(1,1) logical   = false;
        varAccel(1,1) logical = false;

        varTimeOffset(1,1) logical = false;
    end

    methods
        function obj = SetPolyThrottleModelActionOptimVar(varObj)
            obj.varObj = varObj;
            obj.varObj.optVar = obj;

            obj.id = rand();
        end

        function x = getXsForVariable(obj)
            x = NaN(1,4);

            if(obj.varConst)
                x(1) = obj.varObj.throttleModel.constTerm;
            end
            if(obj.varLin)
                x(2) = obj.varObj.throttleModel.linearTerm;
            end
            if(obj.varAccel)
                x(3) = obj.varObj.throttleModel.accelTerm;
            end

            if(obj.varTimeOffset)
                x(4) = obj.varObj.throttleModel.tOffset;
            end

            x(isnan(x)) = [];
        end

        function [lb, ub] = getBndsForVariable(obj)
            useTf = obj.getUseTfForVariable();

            lb = obj.lb(useTf);
            ub = obj.ub(useTf);
        end

        function [lb, ub] = getAllBndsForVariable(obj)
            lb = obj.lb;
            ub = obj.lb;
        end

        function setBndsForVariable(obj, lb, ub)
            if(length(lb) == 4 && length(ub) == 4)
                obj.lb = lb;
                obj.ub = ub;
            else
                useTf = obj.getUseTfForVariable();

                obj.lb(useTf) = lb;
                obj.ub(useTf) = ub;
            end
        end

        function useTf = getUseTfForVariable(obj)
            useTf = [obj.varConst obj.varLin obj.varAccel ...
                     obj.varTimeOffset];
        end

        function setUseTfForVariable(obj, useTf)
            obj.varConst = useTf(1);
            obj.varLin = useTf(2);
            obj.varAccel = useTf(3);
            obj.varTimeOffset = useTf(4);
        end

        function updateObjWithVarValue(obj, x)
            ind = 1;

            if(obj.varConst)
                obj.varObj.throttleModel.constTerm = x(ind);
                ind = ind + 1;
            end
            if(obj.varLin)
                obj.varObj.throttleModel.linearTerm = x(ind);
                ind = ind + 1;
            end
            if(obj.varAccel)
                obj.varObj.throttleModel.accelTerm = x(ind);
                ind = ind + 1;
            end

            if(obj.varTimeOffset)
                obj.varObj.throttleModel.tOffset = x(ind);
                ind = ind + 1; %#ok<NASGU>
            end
        end

        function nameStrs = getStrNamesOfVars(obj, evtNum, varLocType)
           if(evtNum > 0)
                subStr = sprintf('Event %i',evtNum);
            else
                subStr = varLocType;
            end

            nameStrs = {sprintf('%s Throttle Constant', subStr), ...
                        sprintf('%s Throttle Rate', subStr), ...
                        sprintf('%s Throttle Acceleration', subStr), ...
                        sprintf('%s Throttle Time Offset', subStr)};

            nameStrs = nameStrs(obj.getUseTfForVariable());
        end
    end
end