classdef LogisticRegression < handle
    %LOGISTICREGRESSION  Logistic Regression classifier
    %
    % ## Logistic Regression
    %
    % ML implements logistic regression, which is a probabilistic
    % classification technique. Logistic Regression is a binary classification
    % algorithm which is closely related to Support Vector Machines (SVM).
    % Like SVM, Logistic Regression can be extended to work on multi-class
    % classification problems like digit recognition (i.e. recognizing digitis
    % like 0,1 2, 3,... from the given images). This version of Logistic
    % Regression supports both binary and multi-class classifications (for
    % multi-class it creates a multiple 2-class classifiers). In order to
    % train the logistic regression classifier, Batch Gradient Descent and
    % Mini-Batch Gradient Descent algorithms are used (see [BatchDesWiki]).
    % Logistic Regression is a discriminative classifier (see [LogRegTomMitch]
    % for more details). Logistic  Regression is implemented as a C++ class in
    % cv.LogisticRegression.
    %
    % In Logistic Regression, we try to optimize the training parameter
    % `theta` such that the hypothesis `0 <= h_theta(x) <= 1` is achieved. We
    % have `h_theta(x) = g(h_theta(x))` and `g(z)=1/(1+e^(-z))` as the
    % logistic or sigmoid function. The term "Logistic" in Logistic Regression
    % refers to this function. For given data of a binary classification
    % problem of classes 0 and 1, one can determine that the given data
    % instance belongs to class 1 if `h_theta(x) >= 0.5` or class 0 if
    % `h_theta(x) < 0.5`.
    %
    % In Logistic Regression, choosing the right parameters is of utmost
    % importance for reducing the training error and ensuring high training
    % accuracy:
    %
    % * The learning rate can be set with `LearningRate` property. It
    %   determines how fast we approach the solution. It is a positive real
    %   number.
    % * Optimization algorithms like Batch Gradient Descent and Mini-Batch
    %   Gradient Descent are supported in cv.LogisticRegression. It is
    %   important that we mention the number of iterations these optimization
    %   algorithms have to run. The number of iterations can be set with
    %   `Iterations` property. This parameter can be thought as number of
    %   steps taken and learning rate specifies if it is a long step or a
    %   short step. This and previous parameter define how fast we arrive at a
    %   possible solution.
    % * In order to compensate for overfitting regularization is performed,
    %   which can be enabled with `Regularization` property. One can specify
    %   what kind of regularization has to be performed by passing one of
    %   regularization kinds to this property.
    % * Logistic regression implementation provides a choice of 2 training
    %   methods with Batch Gradient Descent or the MiniBatch Gradient Descent.
    %   To specify this, set `TrainMethod` property as either `Batch` or
    %   `MiniBatch`. If training method is set to `MiniBatch`, the size of the
    %   mini batch has to be to a positive integer set with `MiniBatchSize`
    %   property.
    %
    % ## Example
    % A sample set of training parameters for the Logistic Regression
    % classifier can be initialized as follows:
    %
    %     lr = cv.LogisticRegression();
    %     lr.LearningRate = 0.05;
    %     lr.Iterations = 1000;
    %     lr.Regularization = 'L2';
    %     lr.TrainMethod = 'MiniBatch';
    %     lr.MiniBatchSize = 10;
    %
    % ## References
    % [LogRegWiki]:
    % > [Logistic regression](https://en.wikipedia.org/wiki/Logistic_regression)
    %
    % [BatchDesWiki]:
    % > [Gradient descent optimization](https://en.wikipedia.org/wiki/Gradient_descent_optimization)
    %
    % [LogRegTomMitch]:
    % > "Generative and Discriminative Classifiers: Naive Bayes and Logistic
    % > Regression" in Machine Learning, Tom Mitchell.
    % > [Machine Learning](http://www.cs.cmu.edu/~tom/NewChapters.html)
    %
    % [RenMalik2003]:
    % > "Learning a Classification Model for Segmentation". Proc. CVPR, Nice,
    % > France (2003).
    %
    % See also: cv.LogisticRegression.LogisticRegression, fitglm
    %

    properties (SetAccess = private)
        % Object ID
        id
    end

    properties (Dependent)
        % The learning rate of the optimization algorithm.
        %
        % The higher the value, faster the rate and vice versa. If the value
        % is too high, the learning algorithm may overshoot the optimal
        % parameters and result in lower training accuracy. If the value is
        % too low, the learning algorithm converges towards the optimal
        % parameters very slowly. The value must a be a positive real number.
        % You can experiment with different values with small increments as in
        % 0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, ... and select
        % the learning rate with less training error. Default 0.001
        LearningRate
        % Number of iterations.
        %
        % The number of iterations required for the learing algorithm
        % (Gradient Descent or Mini Batch Gradient Descent). It has to be a
        % positive integer. You can try different number of iterations like in
        % 100, 1000, 2000, 3000, 5000, 10000, .. so on. Default 1000
        Iterations
        % Kind of regularization to be applied.
        %
        % Default 'L2'. Possible values:
        %
        % * __Disable__ Regularization disabled.
        % * __L1__ L1 norm.
        % * __L2__ L2 norm.
        Regularization
        % Kind of training method used to train the classifier.
        %
        % Default 'Batch'. Possible values:
        %
        % * __Batch__ batch gradient descent.
        % * __MiniBatch__ Mini-Batch Gradient Descent. Set `MiniBatchSize` to
        %   a positive integer when using this method.
        TrainMethod
        % Number of training samples taken in each step of Mini-Batch Gradient
        % Descent.
        %
        % Will only be used if using `MiniBatch` training algorithm. It has to
        % take values less than the total number of training samples.
        % Default 1
        MiniBatchSize
        % Termination criteria of the training algorithm.
        %
        % A struct with the following fields is accepted:
        %
        % * __type__ one of 'Count', 'EPS', 'Count+EPS'. default 'Count+EPS'
        % * __maxCount__ maximum number of iterations. default `Iterations`
        % * __epsilon__ tolerance value. default `LearningRate`
        TermCriteria
    end

    %% Constructor/destructor
    methods
        function this = LogisticRegression(varargin)
            %LOGISTICREGRESSION  Creates/trains a logistic regression model
            %
            %     model = cv.LogisticRegression()
            %     model = cv.LogisticRegression(...)
            %
            % The first variant creates Logistic Regression model with default
            % parameters.
            %
            % The second variant accepts the same parameters as the train
            % method, in which case it forwards the call after construction.
            %
            % See also: cv.LogisticRegression, cv.LogisticRegression.train
            %
            this.id = LogisticRegression_(0, 'new');
            if nargin > 0
                this.train(varargin{:});
            end
        end

        function delete(this)
            %DELETE  Destructor
            %
            %     model.delete()
            %
            % See also: cv.LogisticRegression
            %
            if isempty(this.id), return; end
            LogisticRegression_(this.id, 'delete');
        end
    end

    %% Algorithm
    methods
        function clear(this)
            %CLEAR  Clears the algorithm state
            %
            %     model.clear()
            %
            % The method clear does the same job as the destructor: it
            % deallocates all the memory occupied by the class members. But
            % the object itself is not destructed and can be reused further.
            % This method is called from the destructor, from the `train` and
            % `load` methods, or even explicitly by the user.
            %
            % See also: cv.LogisticRegression.empty, cv.LogisticRegression.load
            %
            LogisticRegression_(this.id, 'clear');
        end

        function b = empty(this)
            %EMPTY  Returns true if the algorithm is empty
            %
            %     b = model.empty()
            %
            % ## Output
            % * __b__ Returns true if the algorithm is empty (e.g. in the very
            %   beginning or after unsuccessful read).
            %
            % See also: cv.LogisticRegression.clear, cv.LogisticRegression.load
            %
            b = LogisticRegression_(this.id, 'empty');
        end

        function varargout = save(this, filename)
            %SAVE  Saves the algorithm parameters to a file or a string
            %
            %     model.save(filename)
            %     str = model.save(filename)
            %
            % ## Input
            % * __filename__ Name of the file to save to. In case of string
            %   output, only the filename extension is used to determine the
            %   output format (XML or YAML).
            %
            % ## Output
            % * __str__ optional output. If requested, the model is persisted
            %   to a string in memory instead of writing to disk.
            %
            % This method stores the complete model state to the specified
            % XML or YAML file (or to a string in memory, based on the number
            % of output arguments).
            %
            % See also: cv.LogisticRegression.load
            %
            [varargout{1:nargout}] = LogisticRegression_(this.id, 'save', filename);
        end

        function load(this, fname_or_str, varargin)
            %LOAD  Loads algorithm from a file or a string
            %
            %     model.load(filename)
            %     model.load(str, 'FromString',true)
            %     model.load(..., 'OptionName',optionValue, ...)
            %
            % ## Input
            % * __filename__ Name of the file to read.
            % * __str__ String containing the serialized model you want to
            %   load.
            %
            % ## Options
            % * __ObjName__ The optional name of the node to read (if empty,
            %   the first top-level node will be used). default empty
            % * __FromString__ Logical flag to indicate whether the input is a
            %   filename or a string containing the serialized model (switches
            %   between `Algorithm<T>::load()` and
            %   `Algorithm<T>::loadFromString()` C++ methods). default false
            %
            % This method loads the complete model state from the specified
            % XML or YAML file (either from disk or serialized string). The
            % previous model state is cleared.
            %
            % See also: cv.LogisticRegression.save
            %
            LogisticRegression_(this.id, 'load', fname_or_str, varargin{:});
        end

        function name = getDefaultName(this)
            %GETDEFAULTNAME  Returns the algorithm string identifier
            %
            %     name = model.getDefaultName()
            %
            % ## Output
            % * __name__ This string is used as top level XML/YML node tag
            %   when the object is saved to a file or string.
            %
            % See also: cv.LogisticRegression.save, cv.LogisticRegression.load
            %
            name = LogisticRegression_(this.id, 'getDefaultName');
        end
    end

    %% StatModel
    methods
        function count = getVarCount(this)
            %GETVARCOUNT  Returns the number of variables in training samples
            %
            %     count = model.getVarCount()
            %
            % ## Output
            % * __count__ number of variables in training samples (plus one to
            %   account for the implicitly prepended bias/intercept term).
            %
            % See also: cv.LogisticRegression.train
            %
            count = LogisticRegression_(this.id, 'getVarCount');
        end

        function b = isTrained(this)
            %ISTRAINED  Returns true if the model is trained
            %
            %     b = model.isTrained()
            %
            % ## Output
            % * __b__ Returns true if the model is trained, false otherwise.
            %
            % See also: cv.LogisticRegression.empty, cv.LogisticRegression.train
            %
            b = LogisticRegression_(this.id, 'isTrained');
        end

        function b = isClassifier(this)
            %ISCLASSIFIER  Returns true if the model is a classifier
            %
            %     b = model.isClassifier()
            %
            % ## Output
            % * __b__ Returns true if the model is a classifier, false if the
            %   model is a regressor.
            %
            % Always true for logistic regression.
            %
            % See also: cv.LogisticRegression.isTrained
            %
            b = LogisticRegression_(this.id, 'isClassifier');
        end

        function status = train(this, samples, responses, varargin)
            %TRAIN  Trains the statistical model
            %
            %     status = model.train(samples, responses)
            %     status = model.train(csvFilename, [])
            %     [...] = model.train(..., 'OptionName', optionValue, ...)
            %
            % ## Input
            % * __samples__ matrix of training samples. It should have
            %   `single` type. By default, each row represents a sample (see
            %   the `Layout` option).
            % * __responses__ matrix of associated responses. A vector of
            %   categorical labels, stored in an array of type `single`.
            % * __csvFilename__ The input CSV file name from which to load
            %   dataset. In this variant, you should set the second argument
            %   to an empty array.
            %
            % ## Output
            % * __status__ Success flag.
            %
            % ## Options
            % * __Data__ Training data options, specified as a cell array of
            %   key/value pairs of the form `{'key',val, ...}`. See below.
            % * __Flags__ The optional training flags, model-dependent.
            %   Not used. default 0
            %
            % ### Options for `Data` (first variant with samples and reponses)
            % * __Layout__ Sample types. Default 'Row'. One of:
            %   * __Row__ each training sample is a row of samples.
            %   * __Col__ each training sample occupies a column of samples.
            % * __VarIdx__ vector specifying which variables to use for
            %   training. It can be an integer vector (`int32`) containing
            %   0-based variable indices or logical vector (`uint8` or
            %   `logical`) containing a mask of active variables. Not set by
            %   default, which uses all variables in the input data.
            % * __SampleIdx__ vector specifying which samples to use for
            %   training. It can be an integer vector (`int32`) containing
            %   0-based sample indices or logical vector (`uint8` or
            %   `logical`) containing a mask of training samples of interest.
            %   Not set by default, which uses all samples in the input data.
            % * __SampleWeights__ optional floating-point vector with weights
            %   for each sample. Some samples may be more important than
            %   others for training. You may want to raise the weight of
            %   certain classes to find the right balance between hit-rate and
            %   false-alarm rate, and so on. Not set by default, which
            %   effectively assigns an equal weight of 1 for all samples.
            % * __VarType__ optional vector of type `uint8` and size
            %   `<num_of_vars_in_samples> + <num_of_vars_in_responses>`,
            %   containing types of each input and output variable. By default
            %   considers all variables as numerical (both input and output
            %   variables). In case there is only one output variable of
            %   integer type, it is considered categorical. You can also
            %   specify a cell-array of strings (or as one string of single
            %   characters, e.g 'NNNC'). Possible values:
            %   * __Numerical__, __N__ same as 'Ordered'
            %   * __Ordered__, __O__ ordered variables
            %   * __Categorical__, __C__ categorical variables
            % * __MissingMask__ Indicator mask for missing observation (not
            %   currently implemented). Not set by default
            % * __TrainTestSplitCount__ divides the dataset into train/test
            %   sets, by specifying number of samples to use for the test set.
            %   By default all samples are used for the training set.
            % * __TrainTestSplitRatio__ divides the dataset into train/test
            %   sets, by specifying ratio of samples to use for the test set.
            %   By default all samples are used for the training set.
            % * __TrainTestSplitShuffle__ when splitting dataset into
            %   train/test sets, specify whether to shuffle the samples.
            %   Otherwise samples are assigned sequentially (first train then
            %   test). default true
            %
            % ### Options for `Data` (second variant for loading CSV file)
            % * __HeaderLineCount__ The number of lines in the beginning to
            %   skip; besides the header, the function also skips empty lines
            %   and lines staring with '#'. default 1
            % * __ResponseStartIdx__ Index of the first output variable. If
            %   -1, the function considers the last variable as the response.
            %   If the dataset only contains input variables and no responses,
            %   use `ResponseStartIdx = -2` and `ResponseEndIdx = 0`, then the
            %   output variables vector will just contain zeros. default -1
            % * __ResponseEndIdx__ Index of the last output variable + 1. If
            %   -1, then there is single response variable at
            %   `ResponseStartIdx`. default -1
            % * __VarTypeSpec__ The optional text string that specifies the
            %   variables' types. It has the format
            %   `ord[n1-n2,n3,n4-n5,...]cat[n6,n7-n8,...]`. That is, variables
            %   from `n1` to `n2` (inclusive range), `n3`, `n4` to `n5` ...
            %   are considered ordered and `n6`, `n7` to `n8` ... are
            %   considered as categorical. The range
            %   `[n1..n2] + [n3] + [n4..n5] + ... + [n6] + [n7..n8]` should
            %   cover all the variables. If `VarTypeSpec` is not specified,
            %   then algorithm uses the following rules:
            %   * all input variables are considered ordered by default. If
            %     some column contains has non- numerical values, e.g.
            %     'apple', 'pear', 'apple', 'apple', 'mango', the
            %     corresponding variable is considered categorical.
            %   * if there are several output variables, they are all
            %     considered as ordered. Errors are reported when
            %     non-numerical values are used.
            %   * if there is a single output variable, then if its values are
            %     non-numerical or are all integers, then it's considered
            %     categorical. Otherwise, it's considered ordered.
            % * __Delimiter__ The character used to separate values in each
            %   line. default ','
            % * __Missing__ The character used to specify missing
            %   measurements. It should not be a digit. Although it's a
            %   non-numerical value, it surely does not affect the decision of
            %   of whether the variable ordered or categorical. default '?'
            % * __TrainTestSplitCount__ same as above.
            % * __TrainTestSplitRatio__ same as above.
            % * __TrainTestSplitShuffle__ same as above.
            %
            % The training algorithm uses one-vs-rest scheme for multiclass
            % case (i.e a binary problem is fit for each label).
            %
            % See also: cv.LogisticRegression.predict, cv.LogisticRegression.calcError
            %
            status = LogisticRegression_(this.id, 'train', samples, responses, varargin{:});
        end

        function [err,resp] = calcError(this, samples, responses, varargin)
            %CALCERROR  Computes error on the training or test dataset
            %
            %     err = model.calcError(samples, responses)
            %     err = model.calcError(csvFilename, [])
            %     [err,resp] = model.calcError(...)
            %     [...] = model.calcError(..., 'OptionName', optionValue, ...)
            %
            % ## Input
            % * __samples__ See the train method.
            % * __responses__ See the train method.
            % * __csvFilename__ See the train method.
            %
            % ## Output
            % * __err__ computed error.
            % * __resp__ the optional output responses.
            %
            % ## Options
            % * __Data__ See the train method.
            % * __TestError__ if true, the error is computed over the test
            %   subset of the data, otherwise it's computed over the training
            %   subset of the data. Please note that if you loaded a
            %   completely different dataset to evaluate an already trained
            %   classifier, you will probably want not to set the test subset
            %   at all with `TrainTestSplitRatio` and specify
            %   `TestError=false`, so that the error is computed for the whole
            %   new set. Yes, this sounds a bit confusing. default false
            %
            % The method uses the predict method to compute the error. For
            % regression models the error is computed as RMS, for classifiers
            % as a percent of missclassified samples (0%-100%).
            %
            % See also: cv.LogisticRegression.train, cv.LogisticRegression.predict
            %
            [err,resp] = LogisticRegression_(this.id, 'calcError', samples, responses, varargin{:});
        end

        function [results,f] = predict(this, samples, varargin)
            %PREDICT  Predicts responses for input samples
            %
            %     [results,f] = model.predict(samples)
            %     [...] = model.predict(..., 'OptionName', optionValue, ...)
            %
            % ## Input
            % * __samples__ The input data for the prediction algorithm. `MxN`
            %   matrix, where each row contains variables (features) of one
            %   object being classified. Should have `single` data type.
            %
            % ## Output
            % * __results__ Predicted labels as a column vector of `int32`
            %   type. In case `RawOutput` was set, it returns a `single`
            %   matrix of size `size(samples,1)-by-size(thetas,1)` which
            %   contains the raw output of the sigmoid function(s).
            % * __f__ The same as the response of the first sample, i.e
            %   `results(1)`.
            %
            % ## Options
            % * __Flags__ The optional predict flags, model-dependent. For
            %   convenience, you can set the individual flag options below,
            %   instead of directly setting bits here. default 0
            % * __RawOutput__ makes the method return the raw results (the
            %   value of the sigmoid function), not the class label.
            %   default false
            %
            % See also: cv.LogisticRegression.train, cv.LogisticRegression.calcError
            %
            [results,f] = LogisticRegression_(this.id, 'predict', samples, varargin{:});
        end
    end

    %% LogisticRegression
    methods
        function thetas = getLearntThetas(this)
            %GETLEARNTTHETAS  Returns the trained parameters
            %
            %     thetas = model.getLearntThetas()
            %
            % ## Output
            % * __thetas__ It returns learnt parameters of the Logistic
            %   Regression as a matrix of type `single` arranged across rows.
            %   For a two-class classifcation problem, it returns a single row
            %   matrix.
            %
            % `thetas` is a matrix of size `nclasses-by-model.getVarCount()`
            % if `nclasses>2`, otherwise `1-by-model.getVarCount()` if
            % `nclasses=2`.
            %
            % See also: cv.LogisticRegression.train
            %
            thetas = LogisticRegression_(this.id, 'get_learnt_thetas');
        end
    end

    %% Getters/Setters
    methods
        function value = get.Iterations(this)
            value = LogisticRegression_(this.id, 'get', 'Iterations');
        end
        function set.Iterations(this, value)
            LogisticRegression_(this.id, 'set', 'Iterations', value);
        end

        function value = get.LearningRate(this)
            value = LogisticRegression_(this.id, 'get', 'LearningRate');
        end
        function set.LearningRate(this, value)
            LogisticRegression_(this.id, 'set', 'LearningRate', value);
        end

        function value = get.MiniBatchSize(this)
            value = LogisticRegression_(this.id, 'get', 'MiniBatchSize');
        end
        function set.MiniBatchSize(this, value)
            LogisticRegression_(this.id, 'set', 'MiniBatchSize', value);
        end

        function value = get.Regularization(this)
            value = LogisticRegression_(this.id, 'get', 'Regularization');
        end
        function set.Regularization(this, value)
            LogisticRegression_(this.id, 'set', 'Regularization', value);
        end

        function value = get.TermCriteria(this)
            value = LogisticRegression_(this.id, 'get', 'TermCriteria');
        end
        function set.TermCriteria(this, value)
            LogisticRegression_(this.id, 'set', 'TermCriteria', value);
        end

        function value = get.TrainMethod(this)
            value = LogisticRegression_(this.id, 'get', 'TrainMethod');
        end
        function set.TrainMethod(this, value)
            LogisticRegression_(this.id, 'set', 'TrainMethod', value);
        end
    end

end


function [nlogL,nlogLvar,exitflag,output] = ibslike(fun,params,respMat,designMat,options,varargin)
%IBSLIKE Unbiased negative log-likelihood via inverse binomial sampling.
%   NLOGL = IBLLIKE(FUN,PARAMS,RESPMAT,DESIGNMAT) returns unbiased estimate
%   NLOGL of the negative of the log-likelihood for the simulated model
%   and data, calculated using inverse binomial sampling (IBS).
%   FUN is a function handle to a function that simulates the model's
%   responses (see below).
%   PARAMS is the parameter vector used to simulate the model's responses.
%   RESPMAT is a "response" data matrix, where each row correspond to one
%   observation or "trial" (e.g., a trial of a psychophysical experiment),
%   and each column represents a different response feature (e.g., the
%   subject's response and reported confidence level). Responses need to
%   belong to a finite set.
%   DESIGNMAT is an optional experimental design matrix, where each row
%   corresponds to one trial, and each column corresponds to a different
%   trial feature (such as condition, stimulus value, etc.).
%
%   FUN takes as input a vector of parameters PARAMS and an experimental
%   design matrix DMAT (one row per trial), and generates a matrix of
%   simulated model responses (one row per trial, corresponding to rows of
%   DMAT). DMAT is built by the algorithm out of rows of DESIGNMAT.
%
%   DESIGNMAT can be omitted or left empty, in which case FUN needs to
%   accept a parameter vector PARAMS and an array of trial numbers T, and
%   returns a matrix of simulated responses, where the i-th row contains
%   the simulated response for trial T(i) (the indices in T may repeat).
%
%   NLOGL = IBSLIKE(FUN,PARAMS,RESPMAT,DESIGNMAT,OPTIONS) uses options in
%   structure OPTIONS to replace default values. (To be explained...)
%
%   NLOGL = IBSLIKE(...,VARARGIN) additional arguments are passed to FUN.
%
%   [NLOGL,NLOGLVAR] = IBSLIKE(...) also returns an estimate NLOGVAR of the
%   variance of the log likelihood.
%
%   [NLOGL,NLOGLVAR,EXITFLAG] = IBSLIKE(...) returns an EXITFLAG that
%   describes the exit condition. Possible values of EXITFLAG and the
%   corresponding exit conditions are
%
%    2  IBS terminated after reaching the maximum runtime specified by the
%       user (the estimate can be arbitrarily biased).
%    1  IBS terminated after reaching the negative log-likelihood
%       threshold specified by the user (the estimate is biased).
%    0  Correct run of IBS; the estimate is unbiased.
%
%   [NLOGL,NLOGLVAR,EXITFLAG,OUTPUT] = IBSLIKE(...) returns a structure
%   OUTPUT with additional information about the sampling.
%
%   OPTIONS = IBSLIKE('defaults') returns a basic default OPTIONS structure.
%
%   EXITFLAG = IBSLIKE('test') runs some tests. Here EXITFLAG is 0 if
%   everything works correctly.
%
%   Test code on binomial sampling:
%      p = 0.7; Ntrials = 100;                  % Define binomial probability
%      fun = @(x,dmat) rand(size(dmat)) < x;    % Simulating function
%      rmat = fun(p,NaN(Ntrials,1));            % Generate responses
%      [nlogL,nlogLvar,pc,output] = ibslike(fun,p,rmat);
%      nlogL_true = -log(p)*sum(rmat == 1) - log(1-p)*sum(rmat == 0);
%      fprintf('Ground truth: %.4g, Estimate: %.4g ± %.4g.\n',nlogL_true,nlogL,sqrt(nlogLvar));
%
%   Reference:
%   van Opheusden*, B., Acerbi*, L. & Ma, W. J. (2020), "Unbiased and
%   efficient log-likelihood estimation with inverse binomial sampling".
%   (* equal contribution), PLoS Computational Biology 16(12): e1008483.
%   Link: https://doi.org/10.1371/journal.pcbi.1008483
%
%   See also @.

%--------------------------------------------------------------------------
% IBS: Inverse Binomial Sampling for unbiased log-likelihood estimation
% To be used under the terms of the MIT License
% (https://opensource.org/licenses/MIT).
%
%   Authors (copyright): Luigi Acerbi and Bas van Opheusden, 2020-2022
%   e-mail: luigi.acerbi@helsinki.fi, svo@princeton.edu
%   URL: http://luigiacerbi.com
%   Version: 0.96
%   Release date: Jan 21, 2021
%   Code repository: https://github.com/acerbilab/ibs
%--------------------------------------------------------------------------

if nargin < 4; designMat = []; end
if nargin < 5; options = []; end

t0 = tic;

% Default options
% defopts.Display       = 'off';        % Level of display on screen
defopts.Nreps           = 10;           % # independent log-likelihood estimates per trial
defopts.NegLogLikeThreshold = Inf;      % Stop sampling if estimated nLL is above threshold (incompatible with vectorized sampling)
defopts.Vectorized      = 'auto';       % Use vectorized sampling algorithm with acceleration
defopts.Acceleration    = 1.5;          % Acceleration factor for vectorized sampling
defopts.NsamplesPerCall = 0;            % # starting samples per trial per function call (0 = choose automatically)
defopts.MaxIter         = 1e5;          % Maximum number of iterations (per trial and estimate)
defopts.ReturnPositive  = false;        % If true, the first returned output is the *positive* log-likelihood
defopts.ReturnStd       = false;        % If true, the second returned output is the standard deviation of the estimate
defopts.MaxTime         = Inf;          % Maximum time for a IBS call (in seconds)
defopts.TrialWeights    = [];           % Vector of per-trial weights to yield a weighted sum of log-likelihood

%% If called with no arguments or with 'defaults', return default options
if nargout <= 1 && (nargin == 0 || (nargin == 1 && ischar(fun) && strcmpi(fun,'defaults')))
    if nargin < 1
        fprintf('Basic default options returned (type "help ibslike" for help).\n');
    end
    nlogL = defopts;
    return;
end

%% If called with the first argument as 'test', run test
if ischar(fun) && strcmpi(fun,'test')
    if nargin < 2; options = []; else; options = params; end
    figure;
    subplot(1,3,1);
    exitflag(1) = runtest1(options);
    subplot(1,3,2);
    exitflag(2) = runtest2(options);
    subplot(1,3,3);
    exitflag(3) = runtest3(options);
    nlogL = any(exitflag);
    return;
end

for f = fields(defopts)'
    if ~isfield(options,f{:}) || isempty(options.(f{:}))
        options.(f{:}) = defopts.(f{:});
    end
end

Ntrials = size(respMat,1);

% Add hard-coded options
options.MaxSamples = 1e4;                       % Maximum # of samples per function call
options.AccelerationThreshold = 0.1;            % Keep accelerating until threshold is passed (in s)
options.VectorizedThreshold  = 0.1;             % Max threshold for using vectorized algorithm (in s)
options.MaxMem = 1e6;                           % Maximum number of samples for vectorized implementation
options.MaxMem = max(min(Ntrials,1e4),10)*100;  % Maximum number of samples for vectorized implementation

% NSAMPLESPERCALL should be a scalar integer
if ~isnumeric(options.NsamplesPerCall) || ~isscalar(options.NsamplesPerCall)
    error('ibslike:NsamplesPerCall','OPTIONS.NsamplesPerCall should be a scalar integer.');
end

% ACCELERATION should be a scalar equal or greater than 1
if ~isnumeric(options.Acceleration) || ~isscalar(options.Acceleration) || ...
        options.Acceleration < 1
    error('ibslike:Acceleration','OPTIONS.Acceleration should be a scalar equal or greater than one.');
end

% NEGLOGLIKETHRESHOLD should be a scalar greater than 0 (or Inf)
if ~isnumeric(options.NegLogLikeThreshold) || ~isscalar(options.NegLogLikeThreshold) || ...
        options.NegLogLikeThreshold <= 0
    error('ibslike:NegLogLikeThreshold','OPTIONS.NegLogLikeThreshold should be a positive scalar (including Inf).');
end

% MAXTIME should be a positive scalar (including Inf)
if ~isnumeric(options.MaxTime) || ~isscalar(options.MaxTime) || ...
        options.MaxTime <= 0
    error('ibslike:MaxTime','OPTIONS.MaxTime should be a positive scalar (or Inf).');
end

% WEIGHT vector should be a scalar or same as number of trials
weights = options.TrialWeights(:);
if isempty(weights); weights = 1; end

if numel(weights) ~= 1 && numel(weights) ~= Ntrials
    error('ibslike:NumWeights','OPTIONS.TrialWeights should be empty, a scalar or an array of weights with as many elements as the number of trials.');
end

Trials = (1:Ntrials)';
funcCount = 0;

simdata = []; elapsed_time = [];

% Use vectorized or loop version?
if ischar(options.Vectorized) && options.Vectorized(1) == 'a'
    if options.Nreps == 1
        vectorized_flag = false;
    else
        % First full simulation to determine computation time
        fun_clock = tic;
        if isempty(designMat)    % Pass only trial indices
            simdata = fun(params,Trials(:),varargin{:});
        else                    % Pass full design matrix per trial
            simdata = fun(params,designMat(Trials(:),:),varargin{:});
        end
        elapsed_time = toc(fun_clock);
        vectorized_flag = elapsed_time < options.VectorizedThreshold;
        funcCount = 1;
    end
else
    vectorized_flag = logical(options.Vectorized);
    if options.Nreps == 1 && vectorized_flag
        vectorized_flag = false;
        warning('Vectorized IBS requires OPTIONS.Nreps > 1. Switching to non-vectorized algorithm.');
    end
end

if vectorized_flag
    [nlogL,K,Nreps,Ns,fc,exitflag] = ...
        vectorized_ibs_sampling(fun,params,respMat,designMat,simdata,elapsed_time,t0,options,varargin{:});
else
    [nlogL,K,Nreps,Ns,fc,exitflag] = ...
        loop_ibs_sampling(fun,params,respMat,designMat,simdata,elapsed_time,t0,options,varargin{:});
end
funcCount = funcCount + fc;

% Variance of estimate per trial
if nargout > 1
    K_max = max(max(K(:),1));
    Ktab = -(psi(1,1:K_max)' - psi(1,1));
    LLvar = Ktab(max(K,1));
    nlogLvar = sum(LLvar,2)./Nreps.^2;
end

% OUTPUT structure with additional information
if nargout > 2
    output.funcCount = funcCount;
    output.NsamplesPerTrial = Ns/Ntrials;
    output.nlogL_trials = nlogL;
    output.nlogLvar_trials = nlogLvar;
end

% Return negative log-likelihood and variance summed over trials
nlogL = sum(nlogL.*weights);
if options.ReturnPositive; nlogL = -nlogL; end
if nargout > 1
    nlogLvar = sum(nlogLvar.*(weights.^2));
    if options.ReturnStd    % Return standard deviation instead of variance
        nlogLvar = sqrt(nlogLvar);
    end
end

end

%--------------------------------------------------------------------------
function [nlogL,K,Nreps,Ns,fc,exitflag] = vectorized_ibs_sampling(fun,params,respMat,designMat,simdata0,elapsed_time0,t0,options,varargin)

Ntrials = size(respMat,1);
Trials = (1:Ntrials)';
Ns = 0;
fc = 0;
exitflag = 0;

Psi_tab = [];   % Empty PSI table

% Empty matrix of K values (samples-to-hit) for each repeat for each trial
K_mat = zeros([max(options.Nreps),Ntrials]);

% Matrix of rep counts
K_place0 = repmat((1:size(K_mat,1))',[1,Ntrials]);

% Current rep being sampled for each trial
Ridx = ones(1,Ntrials);

% Current vector of "open" K values per trial (not reached a "hit" yet)
K_open = zeros(1,Ntrials);

targetHits = options.Nreps(:)'.*ones(1,Ntrials);
MaxIter = options.MaxIter*max(options.Nreps);

% Starting samples
if options.NsamplesPerCall == 0
    samples_level = options.Nreps;
else
    samples_level = options.NsamplesPerCall;
end

for iter = 1:MaxIter
    % Pick trials that need more hits, sample multiple times
    T = Trials(Ridx <= targetHits);
    if isfinite(options.MaxTime) && toc(t0) > options.MaxTime
        T = [];
        exitflag = 2;
    end
    if isempty(T); break; end

    Ttrials = numel(T);    % Number of trials under consideration

    % With accelerated sampling, might request multiple samples at once
    Nsamples = min(options.MaxSamples,max(1,round(samples_level)));
    MaxSamples = ceil(options.MaxMem / Ttrials);
    Nsamples = min(Nsamples, MaxSamples);
    Tmat = repmat(T,[1,Nsamples]);

    % Simulate trials
    if iter == 1 && Nsamples == 1 && ~isempty(simdata0)
        simdata = simdata0;
        elapsed_time = elapsed_time0;
    else
        fun_clock = tic;
        if isempty(designMat)    % Pass only trial indices
            simdata = fun(params,Tmat(:),varargin{:});
            fc = fc + 1;
        else                    % Pass full design matrix per trial
            simdata = fun(params,designMat(Tmat(:),:),varargin{:});
            fc = fc + 1;
        end
        elapsed_time = toc(fun_clock);
    end

    % Check that the returned simulated data have the right size
    if size(simdata,1) ~= numel(Tmat)
        error('ibslike:SizeMismatch', ...
            'Number of rows of returned simulated data does not match the number of requested trials.');
    end

    Ns = Ns + Ttrials;

    % Accelerated sampling
    if options.Acceleration > 0 && elapsed_time < options.AccelerationThreshold
        samples_level = samples_level*options.Acceleration;
    end

    % Check new "hits"
    hits_temp = all(respMat(Tmat(:),:) == simdata,2);

    % Build matrix of new hits (sandwich with buffer of hits, then removed)
    hits_new = [ones(1,Ttrials);reshape(hits_temp,size(Tmat))';ones(1,Ttrials)];

    % Warning: from now on it's going to be incomprehensible
    % (all vectorized for speed)

    % Extract matrix of Ks from matrix of hits for this iteration
    h = size(hits_new,1);
    list = find(hits_new(:) == 1)-1;
    row = floor(list/h)+1;
    col = mod(list,h)+1;
    delta = diff([col;1]);
    remidx = delta <= 0;
    delta(remidx) = [];
    row(remidx) = [];
    indexcol = find(diff([0;row]));
    col = 1 + (1:numel(row))' - indexcol(row);
    K_iter = zeros(size(T,1),max(col));
    K_iter(row + (col-1)*size(K_iter,1)) = delta;

    % This is the comprehensible version that we want to get to:
    %
    %   for iTrial = 1:Ntrials
    %       index = find(hits_new(iTrial,:),targetHits(iTrial));
    %       K = diff([0 index]);
    %       logL(iTrial) = sum(Ktab(K))/numel(index);
    %   end

    % Add still-open K to first column
    K_iter(:,1) = K_open(T)' + K_iter(:,1);

    % Find last K position for each trial
    [~,idx_last] = min([K_iter,zeros(Ttrials,1)],[],2);
    idx_last = idx_last - 1;
    ii = sub2ind(size(K_iter),(1:Ttrials)',idx_last);

    % Subtract one hit from last K (it was added)
    K_iter(ii) = K_iter(ii) - 1;
    K_open(T) = K_iter(ii)';

    % For each trial, ignore entries of K_iter past max # of reps
    idx_mat = bsxfun(@plus,Ridx(T)',repmat(0:size(K_iter,2)-1,[Ttrials,1]));
    K_iter(idx_mat > (options.Nreps)) = 0;

    % Find last K position for each trial again
    [~,idx_last2] = min([K_iter,zeros(Ttrials,1)],[],2);
    idx_last2 = idx_last2 - 1;

    % Add current K to full K matrix
    K_iter_place = bsxfun(@ge,K_place0(:,1:Ttrials),Ridx(T)) & bsxfun(@le,K_place0(:,1:Ttrials),Ridx(T) + idx_last2'- 1);
    K_place = false(size(K_place0));
    K_place(:,T) = K_iter_place;
    Kt = K_iter';
    K_mat(K_place) = Kt(Kt > 0);
    Ridx(T) = Ridx(T) + idx_last' - 1;

    % Compute log-likelihood only if requested for thresholding
    if isfinite(options.NegLogLikeThreshold)
        Rmin = min(Ridx(T));    % Find repeat still ongoing
        if Rmin > size(K_mat,1); continue; end
        [LL_temp,Psi_tab] = get_LL_from_K(Psi_tab,K_mat(Rmin,:));
        nLL_temp = -sum(LL_temp,1);
        if nLL_temp > options.NegLogLikeThreshold
            idx_move = Ridx == Rmin;
            Ridx(idx_move) = Rmin+1;
            K_open(idx_move) = 0;
            exitflag = 1;
        end
    end
end

if ~isempty(T)
    error('ibslike:ConvergenceFail', ...
        'Maximum number of iterations or time limit reached and algorithm did not converge. Check FUN and DATA.');
end

% Log likelihood estimate per trial and run lengths K for each repetition
Nreps = sum(K_mat > 0,1)';
[LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K_mat);
nlogL = sum(-LL_mat',2)./Nreps;
K = K_mat';

end

%--------------------------------------------------------------------------
function [nlogL,K,Nreps,Ns,fc,exitflag] = loop_ibs_sampling(fun,params,respMat,designMat,simdata0,elapsed_time0,t0,options,varargin)

Ntrials = size(respMat,1);
Trials = (1:Ntrials)';
MaxIter = options.MaxIter;
exitflag = 0;

K = zeros(Ntrials,options.Nreps);
Ns = 0;
fc = 0;
Psi_tab = [];

for iRep = 1:options.Nreps

    offset = 1;
    hits = zeros(Ntrials,1);
    if isfinite(options.MaxTime) && toc(t0) > options.MaxTime
        exitflag = 2;
        break;
    end

    for iter = 1:MaxIter
        % Pick trials that need more hits, sample multiple times
        T = Trials(hits < 1);
        if isempty(T); break; end

        % Simulate trials
        if iter == 1 && iRep == 1 && ~isempty(simdata0)
            simdata = simdata0;
        elseif isempty(designMat)    % Pass only trial indices
            simdata = fun(params,T(:),varargin{:});
            fc = fc + 1;
        else                    % Pass full design matrix per trial
            simdata = fun(params,designMat(T(:),:),varargin{:});
            fc = fc + 1;
        end

        % Check that the returned simulated data have the right size
        if size(simdata,1) ~= numel(T)
            error('ibslike:SizeMismatch', ...
                'Number of rows of returned simulated data does not match the number of requested trials.');
        end

        Ns = Ns + numel(T); % Count samples
        hits_new = all(respMat(T(:),:) == simdata,2);
        hits(T) = hits(T) + hits_new;

        K(T(hits_new),iRep) = offset;
        offset = offset + 1;

        % Terminate if negative log likelihood is above a given threshold
        if isfinite(options.NegLogLikeThreshold)
            K(hits < 1,iRep) = offset;
            [LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K(:,iRep));
            nlogL_sum = -sum(LL_mat,1);
            if nlogL_sum > options.NegLogLikeThreshold
                T = [];
                exitflag = 1;
                break;
            end
        end

        % Terminate if above maximum allowed runtime
        if isfinite(options.MaxTime) && toc(t0) > options.MaxTime
            T = [];
            exitflag = 2;
            break;
        end

    end

    if ~isempty(T)
        error('ibslike:ConvergenceFail', ...
            'Maximum number of iterations reached and algorithm did not converge. Check FUN and DATA.');
    end
end

Nreps = sum(K > 0,2);
[LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K);
nlogL = sum(-LL_mat,2)./Nreps;

end

%--------------------------------------------------------------------------
function [LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K_mat)
%GET_LL_FROM_K Convert matrix of K values into log-likelihoods.

K_max = max(1,max(K_mat(:)));
if K_max > numel(Psi_tab)   % Fill digamma function table
    Psi_tab = [Psi_tab; (psi(1) - psi(numel(Psi_tab)+1:K_max)')];
end
LL_mat = Psi_tab(max(1,K_mat));

end

%--------------------------------------------------------------------------
function exitflag = runtest1(options)

Nreps = 1e3;
RMSE_tol = 2/sqrt(Nreps);

% Binomial probability model
p_model = exp(linspace(log(1e-3),log(1),10));
fun = @(x,dmat) rand(size(dmat)) < x;   % Simulating function
rmat = fun(1,NaN);

fprintf('\n');
fprintf('TEST 1: Using IBS to compute log(p) of Bernoulli distributions with %d repeats.\n',Nreps);
fprintf('We consider p = %s.\n',mat2str(p_model,3));

options.Nreps = Nreps;
options.NegLogLikeThreshold = Inf;

nlogL = zeros(1,numel(p_model));
nlogLvar = zeros(1,numel(p_model));
for iter = 1:numel(p_model)
    [nlogL(iter),nlogLvar(iter)] = ibslike(fun,p_model(iter),rmat,[],options);
end

% We expect the true value to be almost certainly (> 99.99%) in this range
LL_min = (-nlogL - 4*sqrt(nlogLvar));
LL_max = (-nlogL + 4*sqrt(nlogLvar));

exitflag = any(log(p_model) < LL_min) | any(log(p_model) > LL_max);

rmse = sqrt(mean((-nlogL - log(p_model)).^2));
fprintf('Average RMSE of log(p) estimates across p: %.4f.\n',rmse);

exitflag = exitflag | (rmse > RMSE_tol);

if exitflag
    fprintf('Test FAILED. Something might be wrong.\n');
else
    fprintf('Test PASSED. IBS estimates are calibrated and close to ground truth.\n');
end

% Plot figure
xx = log(p_model);
h(1) = plot(xx,xx,'k-','LineWidth',2); hold on;

yy = -nlogL;
xxerr = [xx, fliplr(xx)];
yyerr_down = yy - 1.96*sqrt(nlogLvar);
yyerr_up = yy + 1.96*sqrt(nlogLvar);
yyerr = [yyerr_down, fliplr(yyerr_up)];
fill(xxerr, yyerr,'b','FaceAlpha',0.5,'LineStyle','none'); hold on;
h(2) = plot(xx,yy,'b-','LineWidth',2); hold on;

box off;
set(gca,'TickDir','out');
set(gcf,'Color','w');
xlabel('True log(p)');
ylabel('Estimated log(p)')
%xlim([-5 5]);
hl = legend(h,'True log(p)','IBS estimate (95% CI)');
set(hl,'Location','NorthWest','Box','off');
title('IBS estimation test');

end

%--------------------------------------------------------------------------
function exitflag = runtest2(options)

% Binomial probability model
Ntrials = 100;
p_true = 0.9*rand() + 0.05;             % True probability
p_model = 0.9*rand() + 0.05;            % Model probability
fun = @(x,dmat) rand(size(dmat)) < x;   % Simulating function
Nexps = 2e3;

options.NegLogLikeThreshold = Inf;

fprintf('\n');
fprintf('TEST 2: Using IBS to compute the log-likelihood of a binomial distribution.\n');
fprintf('Parameters: p_true=%.2g, p_model=%.2g, %d trials per experiment.\n',p_true,p_model,Ntrials);
fprintf('The distribution of z-scores should approximate a standard normal distribution (mean 0, SD 1).\n');

zscores = zeros(1,Nexps);
for iter = 1:Nexps
    rmat = fun(p_true,NaN(Ntrials,1));            % Generate data
    [nlogL,nlogLvar] = ibslike(fun,p_model,rmat,[],options);
    nlogL_exact = -log(p_model)*sum(rmat == 1) - log(1-p_model)*sum(rmat == 0);
    zscores(iter) = (nlogL_exact - nlogL)/sqrt(nlogLvar);
end

edges = -4.75:0.5:4.75;
nz = histc(zscores,edges);
h(1) = bar(edges,nz,'histc');
hold on;
xx = linspace(-5,5,1e4);
h(2) = plot(xx,Nexps*exp(-xx.^2/2)/sqrt(2*pi)/2,'k-','LineWidth',2);

box off;
set(gca,'TickDir','out');
set(gcf,'Color','w');
xlabel('z-score');
ylabel('pdf')
xlim([-5 5]);
hl = legend(h,'z-scores histogram','expected pdf');
set(hl,'Location','NorthEast','Box','off');
title('Calibration test');

exitflag = abs(mean(zscores)) > 0.15 || abs(std(zscores) - 1) > 0.1;

fprintf('Distribution of z-scores (%d experiments). Mean: %.4g. Standard deviation: %.4g.\n',Nexps,mean(zscores),std(zscores));
if exitflag
    fprintf('Test FAILED. Something might be wrong.\n');
else
    fprintf('Test PASSED. We verified that IBS is unbiased (~zero mean) and calibrated (SD ~1).\n');
end

end

%--------------------------------------------------------------------------
function exitflag = runtest3(options)

Nreps = 100;
RMSE_tol = 4/sqrt(Nreps);

% Binomial probability model
p_model = exp(linspace(log(1e-3),log(0.1),10));
fun = @(x,dmat) rand(size(dmat)) < x;   % Simulating function
rmat = fun(1,NaN);
thresh = -log(0.01);
p_target = max(p_model,exp(-thresh));

fprintf('\n');
fprintf('TEST 3: Log-likelihood thresholding at log(p) = %.3f.\n',-thresh);
fprintf('Using IBS to compute thresholded log(p) of Bernoulli distributions with %d repeats.\n',Nreps);
fprintf('We consider p = %s.\n',mat2str(p_model,3));

options.Nreps = Nreps;
options.NegLogLikeThreshold = thresh;
options.Acceleration = 1;

nlogL = zeros(1,numel(p_model));
nlogLvar = zeros(1,numel(p_model));
for iter = 1:numel(p_model)
    [nlogL(iter),nlogLvar(iter)] = ibslike(fun,p_model(iter),rmat,[],options);
end

% We expect the true value to be almost certainly (> 99.99%) in this range
LL_min = (-nlogL - 4*sqrt(nlogLvar));
LL_max = (-nlogL + 4*sqrt(nlogLvar));

% We expect the estimates to be (almost) correct away from the threshold
idx = log(p_model) > -thresh*0.75;
exitflag = any(log(p_model(idx)) < LL_min(idx)) | any(log(p_model(idx)) > LL_max(idx));

% We expect the estimates to be above the true value below the threshold
LL_thresh = (-nlogL - sqrt(nlogLvar));
idx_below = log(p_model) < -thresh;
exitflag = exitflag | any(log(p_model(idx_below)) > LL_thresh(idx_below));
% exitflag = any(log(p_target) < LL_min) | any(log(p_target) > LL_max);

rmse = sqrt(mean((-nlogL(idx) - log(p_target(idx))).^2));
fprintf('Average RMSE of log(p) estimates across p: %.4f.\n',rmse);

exitflag = exitflag | (rmse > RMSE_tol);

if exitflag
    fprintf('Test FAILED. Something might be wrong.\n');
else
    fprintf('Test PASSED. IBS estimates are calibrated and close to (thresholded) ground truth.\n');
end

% Plot figure
xx = log(p_model);
h(1) = plot(xx,xx,'k-','LineWidth',2); hold on;

yy = -nlogL;
xxerr = [xx, fliplr(xx)];
yyerr_down = yy - 1.96*sqrt(nlogLvar);
yyerr_up = yy + 1.96*sqrt(nlogLvar);
yyerr = [yyerr_down, fliplr(yyerr_up)];
fill(xxerr, yyerr,'b','FaceAlpha',0.5,'LineStyle','none'); hold on;

h(2) = plot(xx,yy,'b-','LineWidth',2); hold on;
h(3) = plot([xx(1),xx(end)],-thresh*[1 1],'k:','LineWidth',2);

box off;
set(gca,'TickDir','out');
set(gcf,'Color','w');
xlabel('True log(p)');
ylabel('Estimated log(p) with thresholding')
%xlim([-5 5]);
hl = legend(h,'True log(p)','IBS estimate (95% CI)','Threshold');
set(hl,'Location','NorthWest','Box','off');
title('Thresholded IBS test');

end

%   TODO:
%   - Fix help and documentation
%   - Optimal allocation of estimates?


function path = imGeodesicPath(img, source, target, varargin)
%IMGEODESICPATH Compute a geodesic path between two markers in an image
%
%   PATH = imGeodesicPath(MASK, SOURCE, TARGET)
%   Computea minimal geodesic-distance path between the two markers SOURCE
%   and TARGET. Both SOURCE and TARGET can be either a binary image the
%   same size as MASK, or a N-by-2 array containing a list of coordinates
%   (x first, y second).
%   The result PATH is a P-by-2 array containing coordinates of a polyline
%   with minimal geodesic length starting from one of the points specified
%   by SOURCE, and terminating at one of the points specified by TARGET.
%   Note that the result is not uniquely defined, and the returned solution
%   is one of the possible solutions.
%
%   PATH = imGeodesicPath(..., WEIGHTS)
%   Specify the weights to use for propagating chamfer distance. Default is
%   [3 4], as suggested by Borgefors.
%
%   PATH = imGeodesicPath(..., 'verbose', V)
%   Specify the verbosity. V can be either TRUE or FALSE.
%
%
%   Example
%     % read circle image, and create 2 markers
%     img = imread('circles.png');
%     imshow(img); hold on;
%     p1 = [130 130]; % (x1,y1)
%     p2 = [170 170]; % (x2,y2)
%     plot(p1(1), p1(2), 'bo');
%     plot(p2(1), p2(2), 'ro');
%     % Compute and display the path as a polyline
%     path = imGeodesicPath(img, p1, p2);
%     plot(path(:,1), path(:,2), 'color', 'm', 'linewidth', 2);
%
%   See also
%   imGeodesics, imGeodesicDistanceMap, imMaxGeodesicPath

% ------
% Author: David Legland
% e-mail: david.legland@inra.fr
% Created: 2011-04-04,    using Matlab 7.9.0.529 (R2009b)
% Copyright 2011 INRA - Cepia Software Platform.

%% Default values

% weights for computing geodesic lengths
ws = [3 4];

% no verbosity by default
verbose = 0;

%% process input arguments

% extract weights if present
if ~isempty(varargin)
    if isnumeric(varargin{1})
        ws = varargin{1};
        varargin(1) = [];
    end
end

% Extract options
while ~isempty(varargin)
    paramName = varargin{1};
    if strcmpi(paramName, 'verbose')
        verbose = varargin{2};
    else
        error(['Unkown option in imGeodesicLength: ' paramName]);
    end
    varargin(1:2) = [];
end

% forces input image to binary
img = img > 0;

%% Propagate distance from destination markers

% initialize marker image
if sum(size(target) == size(img)) == 2
    % initialize from binary mask
    markers = target > 0;

else
    % initialize from set of points
    markers = false(size(img));
    for i = 1:size(target, 1)
        markers(target(i,2), target(i,1)) = true;
    end
end

% compute distance map from marker
dist = imGeodesicDistanceMap(img, markers, ws, 'verbose', verbose);

%% Find position of first source point

% find position of closest point belonging to the source
if sum(size(source) == size(img)) == 2
    % compute in a binary mask
    [minDist, ind] = min(dist(source));
    [ys, xs] = ind2sub(size(source), ind);

else
    % initialize from set of points
    minDist = inf;
    for i = 1:size(source, 1)
        value = dist(source(i,2), source(i,1));
        if value < minDist
            xs = source(i,1);
            ys = source(i,2);
            minDist = value;
        end
    end
end

% check existence of path
if isempty(minDist)
    warning([mfilename ':NoPathFound'], ...
        'No path could be found between the two markers');
    path = [];
    return;
end

%% Create the path by returning to destination marker

% add a 1-pixel wide border around image, initialized with very high value
tmp = dist;
dist = zeros(size(dist)+2, class(dist));
dist(:) = max(tmp(:)) + 2;
dist(2:end-1, 2:end-1) = tmp;

% keep coordinates of geodesic extremity
x = xs + 1;
y = ys + 1;

% initialize path
path = [x y];

while true
    % check around current point
    neigh = dist(y-1:y+1, x-1:x+1);

    % look for the minimum
    [mini, ind] = min(neigh(:));

    % if minimum is the same as current value, minima is found
    if mini == dist(y, x)
        break;
    end

    % convert index to sub
    [iy, ix] = ind2sub([3 3], ind(1));

    % update coord
    x = x + ix - 2;
    y = y + iy - 2;

    % stores result
    path = [path; x y]; %#ok<AGROW>
end

% subtract 1, because of border
path = path - 1;


function varargout = clipPolygonHP(poly, line, varargin)
%CLIPPOLYGONHP Clip a polygon with a half-plane defined by a directed line.
%
%   POLY2 = clipPolygonHP(POLY, LINE)
%   POLY is a [Nx2] array of points, and LINE is given as [x0 y0 dx dy].
%   The result POLY2 is also an array of points, sometimes smaller than
%   poly, and that can be [0x2] (empty polygon). POLY2 contains the part of
%   POLY on the left side of the directed line.
%
%   [POLY_L, POLY_R] = clipPolygonHP(POLY, LINE, 'method', 'polyshape')
%   Uses MATLAB polyshape objects and functions to clip the polygon by the
%   line. Returns the right part POLY_R in addition to the left part
%   POLY_L in the polygon cell format.
%
%   Example
%     line = [0.4 0 1 1];
%     r = [2.5, 2, 1];
%     poly = flipud(circleToPolygon([0 0 r(1)], round(2*pi*r(1))));
%     poly2 = clipPolygonHP(poly, line);
%     figure('color','w','numbertitle','off','name','Method: legland')
%     axis equal tight; hold on; xlabel('x'); ylabel('y')
%     fillPolygon(poly2)
%     poly2_centroid = polygonCentroid(poly2);
%     drawLabels(poly2_centroid,'L ','HorizontalAlignment','Right')
%     scatter(poly2_centroid(1), poly2_centroid(2),[],'k','filled')
%     midCircle = circleToPolygon([0 0 r(2)], round(2*pi*r(2)));
%     innerCircle = flipud(circleToPolygon([0 0 r(3)], round(2*pi*r(3))));
%     poly = {poly, midCircle, innerCircle};
%     clipPolygonHP(poly, line, 'method','polyshape','debug',1);
%
%   See also
%   clipPolygon

% ------
% Author: David Legland, oqilipo
% E-mail: david.legland@inrae.fr
% Created: 2005-07-31
% Copyright 2005-2022 INRA - Cepia Software Platform

% Parsing
p = inputParser;
logParValidFunc=@(x) (islogical(x) || isequal(x,1) || isequal(x,0));
addParameter(p,'method','legland',@(x) any(validatestring(x,{'legland','polyshape'})));
addParameter(p,'debugVisu',false,logParValidFunc);
parse(p,varargin{:});

method = p.Results.method;
debugVisu = p.Results.debugVisu;

if iscell(poly) && numel(poly)==1
    poly = poly{1};
elseif iscell(poly) && length(poly) > 1
    method = 'polyshape';
end

if ~iscell(poly) && any(isnan(poly(:)))
    poly = splitPolygons(poly);
    method = 'polyshape';
end

switch method
    case 'legland'
        % avoid to process empty polygons
        if size(poly, 1)<3
            varargout{1} = zeros([0 2]);
            return;
        end

        % ensure the last point is the same as the first one
        if sum(poly(end, :)==poly(1,:))~=2
            poly = [poly; poly(1,:)];
        end

        N = size(poly, 1);
        edges = [poly([N 1:N-1], :) poly];

        b = isLeftOriented(poly, line);

        % case of totally clipped polygon
        if sum(b)==0
            varargout{1} = zeros(0, 2);
            return;
        end

        poly2 = zeros(0, 2);

        i=1;
        while i<=N
            if isLeftOriented(poly(i,:), line)
                % keep all points located on the right side of line
                poly2 = [poly2; poly(i,:)]; %#ok<AGROW>
            else
                % compute of preceeding edge with line
                if i>1
                    poly2 = [poly2; intersectLineEdge(line, edges(i, :))]; %#ok<AGROW>
                end
                % go to the next point on the left side
                i=i+1;
                while i<=N

                    % find the next point on the right side
                    if isLeftOriented(poly(i,:), line)
                        % add intersection of previous edge
                        poly2 = [poly2; intersectLineEdge(line, edges(i, :))]; %#ok<AGROW>

                        % add current point
                        poly2 = [poly2; poly(i,:)]; %#ok<AGROW>

                        % exit the second loop
                        break;
                    end
                    i=i+1;
                end
            end
            i=i+1;
        end

        % remove last point if it is the same as the first one
        if sum(poly2(end, :)==poly(1,:))==2
            poly2 = poly2(1:end-1, :);
        end

        if debugVisu
            figure('color','w','numbertitle','off', ...
                'name', ['Debug Figure: ' mfilename ...
                '.m, Method: ' method]);
            axis equal tight; hold on; xlabel('x'); ylabel('y')
            fillPolygon(poly2,'g')
            poly2_centroid = polygonCentroid(poly2);
            drawLabels(poly2_centroid,'L ','HorizontalAlignment','Right')
            scatter(poly2_centroid(1), poly2_centroid(2),[],'k','filled')
        end

        varargout{1} = poly2;

    case 'polyshape'
        warning('off','MATLAB:polyshape:repairedBySimplify')
        polyShape = polygonToPolyshape(poly, 'debugVisu',0);
        warning('on','MATLAB:polyshape:repairedBySimplify')
        [bblim(1:2), bblim(3:4)] = boundingbox(polyShape);
        bbXdist = bblim(2)-bblim(1);
        bbYdist = bblim(4)-bblim(3);
        % Increase the bounding box a little bit
        bblim(1) = bblim(1)-0.01*bbXdist;
        bblim(2) = bblim(2)+0.01*bbXdist;
        bblim(3) = bblim(3)-0.01*bbYdist;
        bblim(4) = bblim(4)+0.01*bbYdist;
        % Bounding box
        BB = [bblim(1) bblim(3); bblim(2) bblim(3); ...
            bblim(2) bblim(4); bblim(1) bblim(4)];
        % Clip the bounding box by the line
        BB_L = clipPolygonHP(BB, line);
        PS_R = subtract(polyShape, polyshape(BB_L));
        lineRev = [line(1:2) -line(3:4)];
        BB_R = clipPolygonHP(BB, lineRev);
        PS_L = subtract(polyShape, polyshape(BB_R));
        lineSeg = clipLine(line, bblim);
        lineSeg = [lineSeg(1:2); lineSeg(3:4)];
        % Intersection edges
        itsEdges = intersect(polyShape,lineSeg);
        itsEdges(2:end+1, 3:4) = itsEdges;
        itsEdges(1,3:4) = nan;
        itsEdges(end,1:2) = nan;
        itsEdges(any(isnan(itsEdges),2),:) = [];

        if debugVisu
            figure('color','w','numbertitle','off', ...
                'name', ['Debug Figure: ' mfilename ...
                '.m, Method: ' method]);
            axis equal tight; hold on; xlabel('x'); ylabel('y')
            plot(PS_R,'FaceColor','g','EdgeColor','k')
            plot(PS_L,'FaceColor','b','EdgeColor','k')
            [PS_R_centroid(1), PS_R_centroid(2)] = centroid(PS_R);
            drawLabels(PS_R_centroid,' R','HorizontalAlignment','Left')
            scatter(PS_R_centroid(1), PS_R_centroid(2),[],'g','filled')
            [PS_L_centroid(1), PS_L_centroid(2)] = centroid(PS_L);
            drawLabels(PS_L_centroid,'L ','HorizontalAlignment','Right')
            scatter(PS_L_centroid(1), PS_L_centroid(2),[],'b','filled')
            drawEdge(itsEdges,'LineStyle','-','LineWidth',2,'Color','k')
        end

        varargout{1} = splitPolygons(PS_L.Vertices);
        varargout{2} = splitPolygons(PS_R.Vertices);
        varargout{3} = itsEdges;

end

end


% MatrixUser, a multi-dimensional matrix analysis software package
% https://sourceforge.net/projects/matrixuser/
%
% The MatrixUser is a matrix analysis software package developed under Matlab
% Graphical User Interface Developing Environment (GUIDE). It features
% functions that are designed and optimized for working with multi-dimensional
% matrix under Matlab. These functions typically includes functions for
% multi-dimensional matrix display, matrix (image stack) analysis and matrix
% processing.
%
% Author:
%   Fang Liu <leoliuf@gmail.com>
%   University of Wisconsin-Madison
%   Aug-30-2014

function MU_funcAxis(Temp,Event,handles)
handles = guidata(handles.MU_matrix_display);

AType={'on','off','auto','manual','tight','fill','ij','xy','equal','image','square','vis3d','normal'};
[Type,ok] = listdlg('ListString',AType, ...
                         'SelectionMode','single',...
                         'PromptString','Axis Appearance',...
                         'Name','Axis');
if ok==0
    warndlg('Changing axis is cancelled.');
    return;
end
handles.V.Axis=AType(Type);

handles=MU_update_image(handles.Matrix_display_axes,{handles.TMatrix,handles.Mask},handles,0);
guidata(handles.MU_matrix_display, handles);

end


function y = erfc(x)
%ERFC         Implements  erfc(x)  for real intervals
%
%   y = erfc(x)
%
%interval standard function implementation
%

% written  05/30/13     S.M. Rump
%

  if x.complex
    error('Error function only for real arguments')
  end

  if issparse(x)
    index = ( x==0 );
    if any(index(:))                    % treat zero indices
      y = intval(ones(size(x)));
      index = ~index;
      %VVVV  y(index) = erfc(full(x(index)));
      s.type = '()'; s.subs = {index}; y = subsasgn(y,s,erfc(full(subsref(x,s))));
      %AAAA  Matlab bug fix
    else
      y = erfc(full(x));
    end
    return
  end

  e = 1e-30;
  if 1+e==1-e                           % fast check for rounding to nearest
    rndold = 0;
  else
    rndold = getround;
    setround(0)
  end

  y = x;

  if all( x.inf(:)==x.sup(:) );         % thin input

    [y.inf,y.sup] = erfc_rnd(x.inf(:),[]);

  else                                  % thick input

    y.inf = erfc_rnd(x.sup(:),-1);
    y.sup = erfc_rnd(x.inf(:),1);

  end

  y.inf = reshape(y.inf,size(x.inf));
  y.sup = reshape(y.sup,size(x.sup));

  setround(rndold)


function res = le(a,b)
%LE           Implements  a <= b  for Taylor, compares only a.x and b.x
%

% written  05/21/09     S.M. Rump
%

  if ~isa(a,'taylor')
    res = ( a<=reshape(b.t(1,:),b.size) );
  elseif ~isa(b,'taylor')
    res = ( reshape(a.t(1,:),a.size)<=b );
  else
    res = ( reshape(a.t(1,:),a.size)<=reshape(b.t(1,:),b.size) );
  end


function varargout = plot_spline(P,C)
  % PLOT_SPLINE Plot a cubic Bezier spline.
  %
  % [pe,p] = plot_spline(P,C)
  %
  % Inputs:
  %   P  #P by dim list of control point locations
  %   C  #C by 4 list of indices into P of cubic Bezier curves
  % Outputs:
  %   pe  plot handles for UI
  %   p  plot handle for curves
  % Example:
  %   cellfun(@(pe) arrayfun(@(p) set(p,'Color','r'),pe),plot_spline(P,C))
  assert(max(C(:))<=size(P,1));
  assert(min(C(:))>=1);
  p = {};
  pe = {};
  ish = ishold;
  for c = 1:size(C,1)
    [pe{c},p{c}] = plot_cubic(P(C(c,:),:));
    hold on;
  end
  hold off;
  if ish
    hold on
  end
  if nargout>=1
    varargout{1} = pe;
    if nargout >= 2
      varargout{2} = p;
    end
  end
end


% OP_GRADSYMU_GRADSYMV_MP: assemble the stiffness matrix A = [a(i,j)], a(i,j) = (epsilon gradsym u_j, gradsym v_i), in a multipatch domain.
%
%   mat = op_gradsymu_gradsymv_mp (spu, spv, msh, [epsilon], [patches]);
%
% INPUT:
%
%   spu:     object representing the space of trial functions (see sp_multipatch)
%   spv:     object representing the space of test functions (see sp_multipatch)
%   msh:     object defining the domain partition and the quadrature rule (see msh_multipatch)
%   epsilon: function handle to compute the diffusion coefficient. Equal to one if left empty.
%   patches: list of patches where the integrals have to be computed. By default, all patches are selected.
%
% OUTPUT:
%
%   mat:    assembled stiffness matrix
%
% Copyright (C) 2015, 2016, 2020 Rafael Vazquez
%
%    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 A = op_gradsymu_gradsymv_mp (spu, spv, msh, coeff, patch_list)

  if (nargin < 5)
    patch_list = 1:msh.npatch;
  end

  if ((spu.npatch ~= spv.npatch) || (spu.npatch ~= msh.npatch))
    error ('op_gradsymu_gradsymv_mp: the number of patches does not coincide')
  end

  ncounter = 0;
  for iptc = patch_list
    if (nargin < 4 || isempty (coeff))
      [rs, cs, vs] = op_gradsymu_gradsymv_tp (spu.sp_patch{iptc}, spv.sp_patch{iptc}, msh.msh_patch{iptc});
    else
      [rs, cs, vs] = op_gradsymu_gradsymv_tp (spu.sp_patch{iptc}, spv.sp_patch{iptc}, msh.msh_patch{iptc}, coeff);
    end
    rows(ncounter+(1:numel (rs))) = spv.gnum{iptc}(rs);
    cols(ncounter+(1:numel (rs))) = spu.gnum{iptc}(cs);

    if (~isempty (spv.dofs_ornt))
      vs = spv.dofs_ornt{iptc}(rs)' .* vs;
    end
    if (~isempty (spu.dofs_ornt))
      vs = vs .* spu.dofs_ornt{iptc}(cs)';
    end

    vals(ncounter+(1:numel (rs))) = vs;
    ncounter = ncounter + numel (rs);
  end

  A = sparse (rows, cols, vals, spv.ndof, spu.ndof);
  clear rows cols vals rs cs vs

end


function n = kpu_order ( l )

%*****************************************************************************80
%
%% KPU_ORDER computes the order of a KPU rule from the level.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    10 May 2012
%
%  Author:
%
%    John Burkardt.
%
%  Parameters:
%
%    Input, integer L, the level of the rule.
%    1 <= L <= 25
%
%    Output, integer N, the order of the rule.
%
  if ( l < 1 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'KPU_ORDER - Fatal error!\n' );
    fprintf ( 1, '  1 <= L <= 25 required.\n' );
    fprintf ( 1, '  Input L = %d\n', l );
    error ( 'KPU_ORDER - Fatal error!' );
  elseif ( l == 1 )
    n = 1;
  elseif ( l <= 3 )
    n = 3;
  elseif ( l <= 6 )
    n = 7;
  elseif ( l <= 12 )
    n = 15;
  elseif ( l <= 24 )
    n = 31;
  elseif ( l <= 25 )
    n = 63;
  else
    fprintf ( 1, '\n' );
    fprintf ( 1, 'KPU_ORDER - Fatal error!\n' );
    fprintf ( 1, '  1 <= L <= 25 required.\n' );
    fprintf ( 1, '  Input L = %d\n', l );
    error ( 'KPU_ORDER - Fatal error!' );
  end

  return
end


function res = lt(a,b)
%GE           Implements  a < b  for Taylor, compares only a.x and b.x
%

% written  05/21/09     S.M. Rump
%

  if ~isa(a,'taylor')
    res = ( a<reshape(b.t(1,:),b.size) );
  elseif ~isa(b,'taylor')
    res = ( reshape(a.t(1,:),a.size)<b );
  else
    res = ( reshape(a.t(1,:),a.size)<reshape(b.t(1,:),b.size) );
  end


function [f]= spm_fx_adem_pursuit(x,v,a,P)
% returns the flow for occulomotor pursuit (with action)
% FORMAT [f]= spm_fx_adem_pursuit(x,v,a,P)
%
% x    - hidden states:
%   x.o(1) - oculomotor angle
%   x.o(2) - oculomotor angle
%   x.x(1) - target location (visual) - extrinsic coordinates (Cartesian)
%   x.x(2) - target location (visual) - extrinsic coordinates (Cartesian)
%   x.a(:) - attractor (SHC) states
%
% v    - hidden cause (speed)
% P    - parameters
%__________________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_fx_adem_pursuit.m 4625 2012-01-24 20:53:10Z karl $

% intisaise flow (to ensure fields are aligned)
%--------------------------------------------------------------------------
f    = x;

% motion of attractor states
%==========================================================================
f.a  = spm_lotka_volterra(x.a,v);

% motion of target states
%==========================================================================

% target location is determined by the attractor state x.a
%--------------------------------------------------------------------------
t    = P*spm_softmax(x.a,1/2);
f.x  = (t - x.x)/2;

% motion of oculomotor angles (driven by bounded action)
%==========================================================================
f.o  = tanh(a) - x.o/16;


function Fitting_from_bb_vis( Image, DepthImage, bounding_box, PDM, patchExperts, clmParams, out_dir, varargin)
%FITTING Summary of this function goes here
%   Detailed explanation goes here

    % the bounding box format is [minX, minY, maxX, maxY];

    % the mean model shape
    M = PDM.M;

    num_points = numel(M) / 3;

    if(any(strcmp(varargin,'orientation')))
        orientation = varargin{find(strcmp(varargin, 'orientation'))+1};
        rot = Euler2Rot(orientation);
    else
        rot = eye(3);
        orientation = [0;0;0];
    end

    rot_m = rot * reshape(M, num_points, 3)';
    width_model = max(rot_m(1,:)) - min(rot_m(1,:));
    height_model = max(rot_m(2,:)) - min(rot_m(2,:));

    a = (((bounding_box(3) - bounding_box(1)) / width_model) + ((bounding_box(4) - bounding_box(2))/ height_model)) / 2;

    tx = (bounding_box(3) + bounding_box(1))/2;
    ty = (bounding_box(4) + bounding_box(2))/2;

    % correct it so that the bounding box is just around the minimum
    % and maximum point in the initialised face
    tx = tx - a*(min(rot_m(1,:)) + max(rot_m(1,:)))/2;
    ty = ty - a*(min(rot_m(2,:)) + max(rot_m(2,:)))/2;

    % visualisation of the initial state
    %hold off;imshow(Image);hold on;plot(a*rot_m(1,:)+tx, a*rot_m(2,:)+ty,'.r');hold on;rectangle('Position', [bounding_box(1), bounding_box(2), bounding_box(3)-bounding_box(1), bounding_box(4)-bounding_box(2)]);
    global_params = [a, 0, 0, 0, tx, ty]';
    global_params(2:4) = orientation;

    local_params = zeros(numel(PDM.E), 1);

    if(any(strcmp(varargin,'gparam')))
        global_params = varargin{find(strcmp(varargin, 'gparam'))+1};
    end

    if(any(strcmp(varargin,'lparam')))
        local_params = varargin{find(strcmp(varargin, 'lparam'))+1};
    end

    scale = clmParams.startScale;

    if(size(Image, 3) == 1)
        GrayImage = Image;
    else
        GrayImage = rgb2gray(Image);
    end

    [heightImg, widthImg] = size(GrayImage);

    % Some predefinitions for faster patch extraction
    [xi, yi] = meshgrid(0:widthImg-1,0:heightImg-1);
    xi = double(xi);
    yi = double(yi);

    GrayImageDb = double(GrayImage);

    % multi iteration refinement using NU-RLMS in each one
    i=1;

    current_patch_scaling = patchExperts(scale).trainingScale;
    visibilities = patchExperts(scale).visibilities;

    view = GetView(patchExperts(scale).centers, global_params(2:4));

    % The shape fitting is performed in the reference frame of the
    % patch training scale
    refGlobal = [current_patch_scaling, 0, 0, 0, 0, 0]';

    % the reference shape
    refShape = GetShapeOrtho(M, PDM.V, local_params, refGlobal);

    % shape around which the patch experts will be evaluated in the original image
    [shape2D] = GetShapeOrtho(M, PDM.V, local_params, global_params);
    shape2D_img = shape2D(:,1:2);

    % Create transform using a slightly modified version of Kabsch that
    % takes scaling into account as well, in essence we get a
    % similarity transform from current estimate to reference shape
    [A_img2ref, T_img2ref, ~, ~] = AlignShapesWithScale(shape2D_img(:,1:2),refShape(:,1:2));

    % Create a transform, from shape in image to reference shape
    T = maketform('affine', [A_img2ref;T_img2ref]);

    shape_2D_ref = tformfwd(T, shape2D_img);

    % transform the current shape to the reference one, so we can
    % interpolate
    shape2D_in_ref = (A_img2ref * shape2D_img')';

    sideSizeX = (clmParams.window_size(i,1) - 1)/2;
    sideSizeY = (clmParams.window_size(i,2) - 1)/2;

    patches = zeros(size(shape2D_in_ref,1), clmParams.window_size(i,1) * clmParams.window_size(i,2));

    Ainv = inv(A_img2ref);

    % extract patches on which patch experts will be evaluted
    for l=1:size(shape2D_in_ref,1)
        if(visibilities(view,l))

            xs = (shape2D_in_ref(l,1)-sideSizeX):(shape2D_in_ref(l,1)+sideSizeX);
            ys = (shape2D_in_ref(l,2)-sideSizeY):(shape2D_in_ref(l,2)+sideSizeY);

            [xs, ys] = meshgrid(xs, ys);

            pairs = [xs(:), ys(:)];

            actualLocs = (Ainv * pairs')';

            actualLocs(actualLocs(:,1) < 0,1) = 0;
            actualLocs(actualLocs(:,2) < 0,2) = 0;
            actualLocs(actualLocs(:,1) > widthImg - 1,1) = widthImg - 1;
            actualLocs(actualLocs(:,2) > heightImg - 1,2) = heightImg - 1;

            [t_patch] = interp2_mine(xi, yi, GrayImageDb, actualLocs(:,1), actualLocs(:,2), 'bilinear');
            t_patch = reshape(t_patch, size(xs));

            patches(l,:) = t_patch(:);

        end
    end

    % Calculate patch responses, either SVR or CCNF
    if(strcmp(patchExperts(scale).type, 'SVR'))
        responses = PatchResponseSVM_multi_modal( patches, patchExperts(scale).patch_experts(view,:), visibilities(view,:), patchExperts(scale).normalisationOptionsCol, clmParams, clmParams.window_size(i,:));

        for r=1:numel(responses)
            out_patch = reshape(patches(r,:)/255, size(xs));
            imwrite(out_patch, [out_dir, '/', num2str(r), '_a.png']);
           imwrite(responses{r}/max(responses{r}(:)), [out_dir, '/', num2str(r), '_svr.png']);
        end

    elseif(strcmp(patchExperts(scale).type, 'CCNF'))
        responses = PatchResponseCCNF( patches, patchExperts(scale).patch_experts(view,:), visibilities(view,:), patchExperts(scale), clmParams.window_size(i,:));
        for r=1:numel(responses)
           imwrite(responses{r}/max(responses{r}(:)), [out_dir, '/', num2str(r), '_lnf.png']);
        end
    elseif(strcmp(patchExperts(scale).type, 'DNN'))
        responses = PatchResponseDNN( patches, patchExperts(scale).patch_experts(view,:), visibilities(view,:), patchExperts(scale), clmParams.window_size(i,:));
        for r=1:numel(responses)
           imwrite(responses{r}/max(responses{r}(:)), [out_dir, '/', num2str(r), '_dnn.png']);
        end
    end

end

function [id] = GetView(centers, rotation)

    [~,id] = min(sum((centers * pi/180 - repmat(rotation', size(centers,1), 1)).^2,2));

end


function [on,rt,ac] = spm_ADEM_cue_rt(DEM)
% returns reaction times and accuracy for ADEM_cued_response demo
% FORMAT [on,rt,ac] = spm_ADEM_cue_rt(DEM)
%
% DEM - DEM structure from ADEM_cued_response.m
%
% on  - cue onset
% ac  - accuracy
% rt  - reaction time
%__________________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_ADEM_cue_rt.m 4231 2011-03-07 21:00:02Z karl $

% distance from target and cue contrast
%--------------------------------------------------------------------------
n   = length(DEM.M(1).x.a);                   % number of targets
F   = DEM.pU.v{1}((1:2) + 2,:);               % location of finger
L   = DEM.pP.P{1};                            % location of targets
for i = 1:n
    D(i,:) = (F(1,:) - L(1,i)).^2;
    D(i,:) = (F(2,:) - L(2,i)).^2 + D(i,:);   % distance from targets
end
C   = DEM.pU.v{1}((1:n) + 4,:);               % contrast of targets

r   = 1/32;                                   % radius of proximity
c   = diff(C > 1,1,2) > 0;                    % target onset
on  = {};                                     % cue onset
ac  = {};                                     % accuracy
rt  = {};                                     % reaction time

% get performance
%--------------------------------------------------------------------------
for i = 1:n

    on{i} = find(c(i,:));
    for j = 1:length(on{i})
        try

            % minimum distance
            %--------------------------------------------------------------
            d        = D(i,(1:8) + on{i}(j))';
            ac{i}(j) = sqrt(min(d));

            % estimated reaction time
            %--------------------------------------------------------------
            X        = (1:length(d))' - 1;
            B        = pinv([X.^0 X.^1])*log(d);
            rt{i}(j) = (log(r) - B(1))/B(2);

        catch
            ac{i}(j) = NaN;
            rt{i}(j) = NaN;
        end
    end
end

% sort trials (over all targets
%--------------------------------------------------------------------------
on = spm_vec(on); [i j] = sort(on,1,'ascend'); on = on(j);
ac = spm_vec(ac); ac = ac(j);
rt = spm_vec(rt); rt = rt(j);

% remove first trial
%--------------------------------------------------------------------------
on(1) = [];
rt(1) = [];
ac(1) = [];

% convert spatial error to accuracy
%--------------------------------------------------------------------------
ac    = 1./ac;

% convert reaction time to ms
%--------------------------------------------------------------------------
dt    = 64/1000;
rt    = rt*1000*dt;
on    = on*dt;


%compute the xcoordinate of the center in the tails coordinate system

function [data,units]=compute_xtailcentral_mm(trx,n)

larvae=trx.exp2flies{n};
numlarvae=numel(larvae);
xtailcentral_mm=cell(1,numlarvae);

for i=1:numlarvae
    larva=larvae(i);
    xtailcentral_mm{1,i}=trx(larva).xcentral_mm-trx(larva).xtail_mm;
end

units=parseunits('mm');
data=xtailcentral_mm;


function [C] = collide(map, points)
% COLLIDE Test whether points collide with an obstacle in an environment.
%   C = collide(map, points).  points is an M-by-3 matrix where each
%   row is an (x, y, z) point.  C in an M-by-1 logical vector;
%   C(i) = 1 if M(i, :) touches an obstacle and is 0 otherwise.
map3d = map.map3d_collision;
sz = size(map3d); nx = sz(1); ny = sz(2); nz = sz(3);
% vectorized
points = points_to_idx(map, points);
idx = (points(:,3)-1)*nx*ny + (points(:,2)-1)*nx + points(:,1);
C = map3d(idx) ~= 255 | map3d(nx*ny*nz+idx) ~= 255 | map3d(nx*ny*nz*2+idx) ~= 255;
end


function [x,y,z,info]=csdp(At,b,c,K,pars)
% CSDP Uses CSDP to solve a problem in SeDuMi format
%
% [x,y,z,info]=csdp(At,b,c,K,pars)
%
% Uses CSDP to solve a problem in SeDuMi format.
%
% Input:
%        At, b, c, K      SDP problem in SeDuMi format.
%        pars             CSDP parameters (optional parameter.)
%
% Output:
%
%        x, y, z          solution.
%        info             CSDP return code.
%                         info=100 indicates a failure in the MATLAB
%                         interface, such as inability to write to
%                         a temporary file.
%
% Note: This interface makes use of temporary files with names given by the
% tempname function.  This will fail if there is no working temporary
% directory or there isn't enough space available in this directory.
%
% Note: This code writes its own param.csdp file in the current working
% directory.  Any param.csdp file already in the directory will be deleted.
%
% Note: It is assumed that csdp is the search path made available through
% the "system" or "dos" command.  Typically, having the csdp executable in
% current working directory will work, although some paranoid system
% administrators keep . out of the path.  In that case, you'll need to
% install csdp in one of the directories that is in the search path.
% A simple test is to run csdp from a command line prompt.
%
%

% This file is part of the Matlab Toolbox for Dimensionality Reduction.
% The toolbox can be obtained from http://homepage.tudelft.nl/19j49
% You are free to use, change, or redistribute this code in any way you
% want for non-commercial purposes. However, it is appreciated if you
% maintain the name of the original author.
%
% (C) Laurens van der Maaten, Delft University of Technology

    %
    % First, put a dummy pars in place if no argument was given.  Also
    % set pars.printlevel if not given.
    %
    if (nargin<5)
        pars.printlevel=1;
    else
        if (isfield(pars,'printlevel'))
            pars.printlevel=pars.printlevel;
        else
            pars.printlevel=1;
        end
    end
    %
    % Write out the param.csdp file.
    %
    fid=fopen('param.csdp','w');
    if (fid==-1)
        if (pars.printlevel ~= 0)
            fprintf('Could not open param.csdp\n');
        end
        info=100;
        return
    end
    %
    % Now, go through the parameters.
    %

    if (isfield(pars,'axtol'))
        fprintf(fid,'axtol= %e\n',pars.axtol);
    else
        fprintf(fid,'axtol=%e\n',1.0e-8);
    end

    if (isfield(pars,'atytol'))
        fprintf(fid,'atytol= %e\n',pars.atytol);
    else
        fprintf(fid,'atytol=%e\n',1.0e-8);
    end

    if (isfield(pars,'objtol'))
        fprintf(fid,'objtol= %e\n',pars.objtol);
    else
        fprintf(fid,'objtol=%e\n',1.0e-8);
    end

    if (isfield(pars,'pinftol'))
        fprintf(fid,'pinftol= %e\n',pars.pinftol);
    else
        fprintf(fid,'pinftol=%e\n',1.0e8);
    end

    if (isfield(pars,'dinftol'))
        fprintf(fid,'dinftol= %e\n',pars.dinftol);
    else
        fprintf(fid,'dinftol=%e\n',1.0e8);
    end

    if (isfield(pars,'maxiter'))
        fprintf(fid,'maxiter= %d\n',pars.maxiter);
    else
        fprintf(fid,'maxiter=%d\n',100);
    end
    if (isfield(pars,'minstepfrac'))
        fprintf(fid,'minstepfrac= %e\n',pars.minstepfrac);
    else
        fprintf(fid,'minstepfrac=%e\n',0.90);
    end

    if (isfield(pars,'maxstepfrac'))
        fprintf(fid,'maxstepfrac= %e\n',pars.maxstepfrac);
    else
        fprintf(fid,'maxstepfrac=%e\n',0.97);
    end

    if (isfield(pars,'minstepp'))
        fprintf(fid,'minstepp= %e\n',pars.minstepp);
    else
        fprintf(fid,'minstepp=%e\n',1.0e-8);
    end

    if (isfield(pars,'minstepd'))
        fprintf(fid,'minstepd= %e\n',pars.minstepd);
    else
        fprintf(fid,'minstepd=%e\n',1.0e-8);
    end

    if (isfield(pars,'usexzgap'))
        fprintf(fid,'usexzgap= %d\n',pars.usexzgap);
    else
        fprintf(fid,'usexzgap=%d\n',1);
    end

    if (isfield(pars,'tweakgap'))
        fprintf(fid,'tweakgap= %d\n',pars.tweakgap);
    else
        fprintf(fid,'tweakgap=%d\n',0);
    end

    if (isfield(pars,'affine'))
        fprintf(fid,'affine= %d\n',pars.affine);
    else
        fprintf(fid,'affine=%d\n',0);
    end

    if (isfield(pars,'printlevel'))
        fprintf(fid,'printlevel= %d\n',pars.printlevel);
    else
        fprintf(fid,'printlevel=%d\n',1);
    end

    if (isfield(pars,'perturbobj'))
        fprintf(fid,'printlevel= %d\n',pars.perturbobj);
    else
        fprintf(fid,'printlevel=%d\n',1);
    end

    if (isfield(pars,'fastmode'))
        fprintf(fid,'printlevel= %d\n',pars.fastmode);
    else
        fprintf(fid,'printlevel=%d\n',0);
    end

    %
    % close the parameter file.
    %
    fclose(fid);
    %
    % Write the problem out.
    %
    fname=tempname;
    ret=writesdpa([fname '.dat-s'],At,b,c,K,pars);
    if (ret==1)
        info=100;
        x=[];
        y=[];
        z=[];
        return
    end
    %
    % Solve the problem.
    %
    directory = fileparts(which('csdp'));
    if any(strcmp(computer, {'PCWIN', 'PCWIN64'}))
        % Call for Windows
        info = dos(['"' directory '\csdp.exe" ' fname '.dat-s' ' ' fname '.sol'],'-echo');
    elseif any(strcmp(computer, {'GLNX86', 'GLNXA64'})) || strcmp(computer, 'GLNXA64')
        % Call for Linux
        info = system([directory '/csdplinux ' fname '.dat-s' ' ' fname '.sol']);
    elseif any(strcmp(computer, {'MACI', 'MACI64'}))
        % Call for Intel Mac
        info = system([directory '/csdpmaci ' fname '.dat-s' ' ' fname '.sol']);
    elseif strcmp(computer, 'MAC')
        % Call for Power PC Mac
        info = system([directory '/csdpmac ' fname '.dat-s' ' ' fname '.sol']);
    else
        error('There is no binary of CSDP available for your platform. You should obtain this binary yourself and edit techniques/csdp.m to solve this problem.');
    end
    %
    % Read back the solution.
    %
    [x,y,z]=readsol([fname '.sol'],K,length(b));
    %
    % Delete the temporary files, including param.csdp if we wrote one!
    %
    try
        delete([fname '.dat-s']);
        delete([fname '.sol']);
        delete('param.csdp');
    catch
        warning('Some temporary files may not be deleted.');
    end


function [maskedSlices, ROIRows, ROICols, ROIVoxels] = ROIMaskedSlices(inputSlices3M,maskM)
%  This function loads a 3D image array for masking with a single mask.
%  This could be an array of DCE images of one anatomic slice over time
%  or an array of varied flip angle images at one slice for T1 calculation
%  The calling program supplies the slice mask from the 3D mask array.
%  This function generates a masked version of the 3D array and
%  it also returns the coordinates of each voxel in the ROI
%  and the total number of voxels in the ROI
%
%  Kristen Zakian
%

% Get coordinates of in-ROI voxels
[ROIRows , ROICols] = find(maskM);
% Get no. voxels in ROI
ROIVoxels = nnz(maskM);

% Mask input array
%maskedSlices = bsxfun(@times, int16(maskM),int16(inputSlices));
maskedSlices = bsxfun(@times,maskM,double(inputSlices3M)); %CHANGED AI 2/7/17
end


function p = sup(p);
%SUP          Polynomial of upper bounds for (interval) polynomial (same as p.sup)
%
%   r = sup(p)
%

% written  08/28/00     S.M. Rump
% modified 04/04/04     S.M. Rump  set round to nearest for safety
% modified 04/06/05     S.M. Rump  rounding unchanged
%

  p.c = sup(p.c);
  p = normalize(p);


function issym=issymmetric(X)
%ISSYMMETRIC Check if variable is symmetric

n = X.dim(1);
m = X.dim(2);
if (n==m)
    % What are the linar indicies to the transposed matrices
    if isa(X.basis,'lazybasis')
        issym = 1;
    else
        ind = reshape(reshape(1:n^2,n,n)',n^2,1);
        issym = norm(X.basis-X.basis(ind,:),1)<1e-10;
    end
else
    issym = 0;
end


function [in, on] = getDataFromClickedPointsOnAImage(h, num_click, data, display)
    [xv, yv] = ginput(num_click);
    xq = data(1,:);
    yq = data(2,:);
    [in, on] = inpolygon(xq, yq, xv, yv);
    if checkDisplay(display)
        numel(xq(in))
        numel(xq(on))
        current_img_handle = h;
        hold(current_img_handle, 'on');
        plot(xv,yv) % polygon
        hold on
        plot(xq(in),yq(in),'r+') % points inside
        set(get(current_img_handle, 'parent'),'visible','on');% show the current axes
        axis equal
        hold(current_img_handle, 'off');
    end
end


function [node,face]=extrudesurf(no,fc,vec)
%
% [node,face]=extrudesurf(no,fc,vec)
%
% create a enclosed surface mesh by extruding an open surface
%
% author: Qianqian Fang, <q.fang at neu.edu>
%
% input:
%
% output:
%      node: 3D node coordinates for the generated surface mesh
%      face: triangular face patches of the generated surface mesh, each
%           row represents a triangle denoted by the indices of the 3 nodes
%
% -- this function is part of iso2mesh toolbox (http://iso2mesh.sf.net)
%

nlen=size(no,1);
if(length(vec)>1)
   node=[no; no+repmat(vec(:)', nlen,1)];
else
   node=[no; no+vec*nodesurfnorm(no, fc)];
end

face=[fc; fc+nlen];

edge=surfedge(fc);
sideface=[edge edge(:,1)+nlen; edge+nlen edge(:,2)];
face=[face; sideface];


function plot_StressVarBeta(vVariance,vBeta)
% function plot_StressVarBeta(vVariance,vBeta)
% --------------------------------------------
% Plot linearly the variance of the stress tensor versus the angle beta,
% representing the difference between the assumed direction of traction and the inverted
%
% Incoming variables:
% vVariance : Vector of Variance values
% vBeta     : Vector of Beta values
%
% j.woessner@sed.ethz.ch
% last update: 24.02.2005

% Deselect nans
vSel = (~isnan(vVariance) & ~isnan(vBeta));
vVariance = vVariance(vSel,:);
vBeta = vBeta.subset(vSel);

% Plot figure
figure
plot(vBeta,vVariance,'k^')
xlabel('\beta [degree]','FontSize',12,'Fontweight','bold')
ylabel('Variance','FontSize',12,'Fontweight','bold')
set(gca,'Linewidth',2,'FontSize',12,'Fontweight','bold')


classdef TestConvertTo
    %TestConvertTo

    methods (Static)
        function test_1
            A = magic(5);
            B = cv.convertTo(A, 'RType','single', 'Alpha',2, 'Beta',3);
            validateattributes(B, {'single'}, {'size',size(A)});
            assert(isequal(B, single(A*2+3)));
        end

        function test_2
            img = imread(fullfile(mexopencv.root(),'test','cat.jpg'));
            dst = cv.convertTo(img, 'RType','double', 'Alpha',1/255);
            validateattributes(dst, {'double'}, {'size',size(img)});
            assert(norm(dst(:) - double(img(:))/255) < 1e-6);
        end

        function test_error_argnum
            try
                cv.convertTo();
                throw('UnitTest:Fail');
            catch e
                assert(strcmp(e.identifier,'mexopencv:error'));
            end
        end
    end

end


function [coeff,latent] = pca_of_hog(sbin, n)

globals;
pascal_init;

try
  load([cachedir 'pca']);
catch
  ids = textread(sprintf(VOCopts.imgsetpath, 'trainval'), '%s');
  num = length(ids);
  if nargin > 1
    num = min(n, num);
  end
  X = zeros(31, 31);
  n = 0;
  for i = 1:num
    fprintf('pca: %d/%d\n', i, num);
    rec = PASreadrecord(sprintf(VOCopts.annopath, ids{i}));
    name = [VOCopts.datadir rec.imgname];
    im = color(imread(name));
    feat = features(resize(double(im), 0.25), sbin);
    % remove occlusion feature
    feat(:,:,32) = [];
    for x = 1:size(feat,2)
      for y = 1:size(feat,1);
        v = feat(y,x,:);
        X = X + v(:) * v(:)';
        n = n+1;
      end
    end
    feat = features(resize(double(im), 0.5), sbin);
    feat(:,:,32) = [];
    for x = 1:size(feat,2)
      for y = 1:size(feat,1);
        v = feat(y,x,:);
        X = X + v(:) * v(:)';
        n = n+1;
      end
    end
    feat = features(resize(double(im), 0.75), sbin);
    feat(:,:,32) = [];
    for x = 1:size(feat,2)
      for y = 1:size(feat,1);
        v = feat(y,x,:);
        X = X + v(:) * v(:)';
        n = n+1;
      end
    end
  end

  X = X/n;
  [coeff, latent] = pcacov(X);
  save([cachedir 'pca'], 'coeff', 'latent');
end