% AstarMOO A*-MOO navigation class
%
% A concrete subclass of the Navigation class that implements the A*
% navigation algorithm for multiobjective optimization (MOO) - i.e.
% optimizes over several objectives/criteria.
%
% Methods:
%     plan              Compute the cost map given a goal and map
%     path              Compute a path to the goal
%     visualize         Display the obstacle map (deprecated)
%     plot              Display the obstacle map
%     costmap_modify    Modify the costmap
%     costmap_get       Return the current costmap
%     costmap_set       Set the current costmap
%     distancemap_get   Set the current distance map
%     heuristic_get     Get the current heuristic map
%     display           Print the parameters in human readable form
%     char              Convert to string
%
% Properties:
% TBD
%
% Example::
%
%         load map1           % load map
%         goal = [50;30];
%         start = [20;10];
%         as = AstarMOO(map);    % create Navigation object
%         as.plan(goal,2);       % setup costmap for specified goal
%         as.path(start);        % plan solution path star-goal, animate
%         P = as.path(start);    % plan solution path star-goal, return path
%
% Example 2:
%         goal = [100;100];
%         start = [1;1];
%         as = AstarMOO(0);   % create Navigation object with random occupancy grid
%         as.addCost(1,L);    % add 1st add'l cost layer L
%         as.plan(goal,3);    % setup costmap for specified goal
%         as.path(start);     % plan solution path start-goal, animate
%         P = as.path(start);    % plan solution path start-goal, return path
%
% Notes::
% - Obstacles are represented by Inf in the costmap.
%
% References::
% - A Pareto Optimal D* Search Algorithm for Multiobjective Path Planning, A. Lavin.
% - A Pareto Front-Based Multiobjective Path Planning Algorithm, A. Lavin.
% - Robotics, Vision & Control, Sec 5.2.2, Peter Corke, Springer, 2011.
%
% Author::
% Alexander Lavin
%
% See also Navigation, Astar, AstarPO.

% Copyright (C) 1993-2015, by Peter I. Corke, Alexander Lavin
%
% This file is part of The Robotics Toolbox for MATLAB (RTB).
%
% RTB is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% RTB is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU Lesser General Public License for more details.
%
% You should have received a copy of the GNU Leser General Public License
% along with RTB.  If not, see <http://www.gnu.org/licenses/>.
%
% http://www.petercorke.com
%
% The RTB implementation of this algorithm is done by Alexander Lavin.
% http://alexanderlavin.com

% Implementation notes:
%
% All the state is kept in the structure called a
% X is an index into the array of states.
% state pointers are kept as matlab array index rather than row,col format

classdef AstarMOO < Navigation

    properties (SetAccess=private, GetAccess=private)

        % essential world info
        costmap   % world cost map: obstacle = Inf
        G         % index of goal point
        N         % number of objectives

        % info kept per cell (state):
        b            % backpointer (0 means not set)
        t            % tag: NEW OPEN CLOSED

        cost_g       % path distance summation
        cost_h       % path heuristic (state to goal) cost
        cost_01      % add'l cost layer 01 (unused)
        cost_02      % add'l cost layer 02 (unused)
        cost_03      % add'l cost layer 03 (unused)
                     % add more cost layers if needed...

        priority
        tie

        % list of open states: 2xN matrix
        %   each open point is a column, row 1 = index of cell, row 2 = k
        openlist
        niter

        changed

        openlist_maxlen     % keep track of maximum length
        quiet

        % tag state values
        NEW = 0;
        OPEN = 1;
        CLOSED = 2;
    end

    methods % start of public methods
        % AstarMOO class constructor:
        function as = AstarMOO(world, varargin)
            %AstarMOO.AstarMOO A*-MOO constructor
            %
            % AS = AstarMOO(MAP, OPTIONS) is a A* navigation object, and MAP is an
            % occupancy grid, a representation of a planar world as a
            % matrix whose elements are 0 (free space) or 1 (occupied).
            % The occupancy grid is coverted to a costmap with a unit cost
            % for traversing a cell.
            %
            % Options::
            % 'goal',G      Specify the goal point (2x1)
            % 'metric',M    Specify the distance metric as 'euclidean' (default)
            %               or 'cityblock'.
            % 'inflate',K   Inflate all obstacles by K cells.
            % 'quiet'       Don't display the progress spinner
            %
            % Other options are supported by the Navigation superclass.
            %
            % Notes::
            % - If MAP == 0 a random map is created.
            %
            % See also Navigation.Navigation.

            % invoke the superclass constructor
            as = as@Navigation(world, varargin{:}); % includes the occgrid

            % options
            opt.quiet = false;
            opt = tb_optparse(opt, varargin);
            as.quiet = opt.quiet;

            as.occgrid2costmap(as.occgrid);

            % initialize the A* state variables
            as.reset();
            if ~isempty(as.goal)
                as.goal_change();
            end
            as.changed = false;
        end

        function reset(as)
            %AstarMOO.reset Reset the planner
            %
            % AS.reset() resets the A* planner.  The next instantiation
            % of AS.plan() will perform a global replan.

            % build the matrices required to hold the state of each cell for A*
            as.b = zeros(size(as.occgrid), 'uint32');     % backpointers
            as.t = zeros(size(as.occgrid), 'uint8');      % tags, all NEW=0
            as.cost_g = Inf*ones(size(as.costmap));       % path cost estimate
            as.openlist = zeros(2,0);                     % the open list, one column per point

            as.openlist_maxlen = -Inf;
        end

        %AstarMOO.goal_change Changes to costlayers due to new goal
        %position
        function goal_change(as)

            if isempty(as.b)
                return;
            end
            goal = as.goal;

            % keep goal in index rather than row,col format
            as.G = sub2ind(size(as.occgrid), goal(2), goal(1));
            as.INSERT(as.G, as.projectCost(as.G), 'goalset');
            as.cost_g(as.G) = 0;

            % new goal changes cost layers:
            as.calcHeuristic(as.occgrid, as.goal);
        end

        function s = char(as)
            %AstarMOO.char Convert navigation object to string
            %
            % AS.char() is a string representing the state of the Astar
            % object in human-readable form.
            %
            % See also AstarMOO.display, Navigation.char.

            % most of the work is done by the superclass
            s = char@Navigation(as);
        end

        function plot(as, varargin)
            %AstarMOO.plot Visualize navigation environment
            %
            % AS.plot() displays the occupancy grid and the goal distance
            % in a new figure.  The goal distance is shown by intensity which
            % increases with distance from the goal.  Obstacles are overlaid
            % and shown in red.
            %
            % AS.plot(P) as above but also overlays a path given by the set
            % of points P (Mx2).
            %
            % See also Navigation.plot.

            plot@Navigation(as, 'distance', as.cost_h, varargin{:});
        end

       % invoked by Navigation.step
        function n = next(as, current)
            % Backpropagate from goal to start
            % Return [col;row] of previous step
            if as.changed
                error('Cost map has changed, replan');
            end
            X = sub2ind(size(as.occgrid), current(2), current(1));
            X = as.b(X); % set X as the backpointer of X
            if X == 0
                n = []; % goal (no further backpointer)
            else
                [r,c] = ind2sub(size(as.costmap), X);
                n = [c;r];
            end
        end

        function plan(as, goal, N)
            %AstarMOO.plan Prep the grid for planning.
            %
            % AS.plan() updates AS with a costmap of distance to the
            % goal from every non-obstacle point in the map.  The goal is
            % as specified to the constructor.
            %
            % Inputs:
            %   goal: goal state coordinates
            %   N: number of optimization objectives; standard A* is 2
            %   (i.e. distance and heuristic)

            as.N = N; % number of optimization objectives
            as.openlist = zeros(as.N+1,0);

            % Setup cost layers. If a
            % cost layer is goal-dependent, it's setup function needs to
            % also be called in AS.goal_change(). If more cost layers are
            % needed, add similar to AS.cost_01.

            % initializations first:
            as.cost_g = zeros(size(as.occgrid));
            as.cost_h = zeros(size(as.occgrid)); % filled after setting goal below
            % if add'l costs haven't been added with addCost()
            if isempty(as.cost_01)
                as.cost_01 = zeros(size(as.occgrid));
            end
            if isempty(as.cost_02)
                as.cost_02 = zeros(size(as.occgrid));
            end
            if isempty(as.cost_03)
                as.cost_03 = zeros(size(as.occgrid));
            end

            if nargin > 1
                as.goal = goal; % invokes superclass method set.goal()
            end
            if isempty(as.goal)
                error('must specify a goal point');
            end

            % Setup cost layers AS.cost_g and AS.cost_h.
            % assign values to the distance cost layer, set as AS.costmap
            as.occgrid2costmap(as.occgrid);
            % assign values to the heuristic cost layer, set as AS.cost_h
            as.calcHeuristic(as.occgrid, as.goal);
            % Additional cost layers are added by the user with the
            % AS.addCost() method

            % Cost priority/tiebreaker: cost_g (distance to node)
            as.priority = as.cost_g;
            as.tie = 1; % first cost: cost_g
        end

        function P = path(as, start)
        %AstarMOO.path Find a path between two points
        %
        % AS.path(START) finds and displays a path from START to GOAL
        % which is overlaid on the occupancy grid.
        %
        % P = AS.path(START) returns the path (2xM) from START to GOAL.
            if nargin < 1
                error('must specify start state');
            end

            % invoke the superclass path function, which iterates on our
            % next method
            start = [start; 1]; % Specifies backpropogation for NAV.path()
            if nargout == 0
                path@Navigation(as, start);
            else
                P = path@Navigation(as, start);
            end
        end

        % Handler invoked by Navigation.path() to start the navigation process
        % Calculate the solution path
        % Line comments LN reference A* pseudocode in Lavin's "A Pareto
        % Front-Based Multiobjective Path Planning Algorithm" where n is
        % the line number.
        function navigate_init(as, start)
            as.openlist = zeros(as.N+1,0); % make sure openlist is empty
            % begin with the start node
            start = sub2ind(size(as.costmap), start(2), start(1));
            as.openlist(1,1) = start;
            as.t(start) = as.OPEN;
            as.niter = 0; flag = 0; % init while loop
            % Plan the A* path
            while ~isempty(as.openlist) % L4
                % Normalize costs on the open list, choose expansion state:
                queue = normc(as.openlist(2:size(as.openlist,1),:)');
                [~,ind]=min(sum(queue,2));
                X = as.openlist(1,ind); % L5; minimum state on the open list
                as.DELETE(X); % L6; remove state from the openlist

                as.niter = as.niter + 1;

                if ~as.quiet && mod(as.niter, 20) == 0 % i.e. spinner every 20 iterations
                    as.spinner();
                end

                % Populate the openlist:
                for Y=as.neighbors(X) % L7,8; check node's 8 neighbors
                    if(Y==as.G) % L9; check for goal
                        as.b(Y) = X;
                        as.updateCosts(Y,X,as.N)
                        flag = 1; % flag for goal
                        break;
                    end
                    if as.t(Y)==as.NEW && as.costmap(Y)~=Inf % hasn't been checked and isn't an obstacle
                        as.b(Y) = X;
                        as.updateCosts(Y,X,as.N); % as.N = 2 -> base case w/ only distance cost parameters (g and h)
                        % project node's costs into objective space:
                        objspace = as.projectCost(Y,X);
                        as.INSERT(Y, objspace, '');
                    end
                end
                if as.verbose
                    disp(' ')
                end
                if flag==1 % goal found
                    break;
                end
            end
            if ~as.quiet
                fprintf('\r');
            end
            as.changed = false;
        end

        function layer = cost_get(as)
        %AstarMOO.cost_get Get the specified cost layer
            layer = as.cost_02;
        end

        function c = heuristic_get(as)
        %AstarMOO.heuristic_get Get the current heuristic map
        %
        % C = AS.heuristic_get() is the current heuristic map.  This map is the same size
        % as the occupancy grid and the value of each element is the shortest distance
        % from the corresponding point in the map to the current goal.  It is computed
        % by Astar.plan.
        %
        % See also Astar.plan.
            c = as.cost_h;
        end

        function c = costmap_get(as)
        %AstarMOO.costmap_get Get the current costmap
        %
        % C = AS.costmap_get() is the current costmap.  The cost map is the same size
        % as the occupancy grid and the value of each element represents the cost
        % of traversing the cell.  It is autogenerated by the class constructor from
        % the occupancy grid such that:
        % - free cell (occupancy 0) has a cost of 1
        % - occupied cell (occupancy >0) has a cost of Inf
        %
        % See also Astar.costmap_set, Astar.costmap_modify.

            c = as.costmap;
        end

        function costmap_set(as, costmap)
        %AstarMOO.costmap_set Set the current costmap
        %
        % AS.costmap_set(C) sets the current costmap.  The cost map is the same size
        % as the occupancy grid and the value of each element represents the cost
        % of traversing the cell.  A high value indicates that the cell is more costly
        % (difficult) to traverese.  A value of Inf indicates an obstacle.
        %
        % Notes::
        % - After the cost map is changed the path should be replanned by
        %   calling AS.plan().
        %
        % See also Astar.costmap_get, Astar.costmap_modify.
            if ~all(size(costmap) == size(as.occgrid))
                error('costmap must be same size as occupancy grid');
            end
            as.costmap = costmap;
            as.changed = true;
            end

        function costmap_modify(as, point, newcost)
        %AstarMOO.costmap_modify Modify cost map
        %
        % AS.costmap_modify(P, NEW) modifies the cost map at P=[X,Y] to
        % have the value NEW.  If P (2xM) and NEW (1xM) then the cost of
        % the points defined by the columns of P are set to the corresponding
        % elements of NEW.
        %
        % Notes::
        % - After one or more point costs have been updated the path
        %   should be replanned by calling AS.plan().
        %
        % See also AstarMOO.costmap_set, AstarMOO.costmap_get.

            if numel(point) == 2
                % for case of single point ensure it is a column vector
                point = point(:);
            end
            if numcols(point) ~= numcols(newcost)
                error('number of columns in point must match columns in newcost');
            end
            for i=1:numcols(point)
                X = sub2ind(size(as.costmap), point(2,i), point(1,i));
                as.costmap(X) = newcost(i);
            end
            if as.t(X) == as.CLOSED
                as.INSERT(X, as.h(X), 'modifycost');
            end
            as.changed = true;
        end

        function addCost(as, layer, values)
        %AstarMOO.addCost Add an additional cost layer
        %
        % AS.addCost(LAYER, VALUES) adds the matrix specified by values as a
        % cost layer.  The layer number is given by LAYER, and VALUES has the same
        % size as the original occupancy grid.

            if size(values)~=size(as.occgrid)
                display('Layer size does not match the environment')
                return
            end
            if max(max(values))~=1 || min(min(values))~=0
                display('Layer values are not normalized [0:1]')
                return
            end

            if layer==1
                as.cost_01 = values;
            elseif layer==2
                as.cost_02 = values;
            elseif layer==3
                as.cost_03 = values;
            else
                display('Layer index out of range')
            end
            % If more cost layers needed, add additional elseif statements
            % as above.
        end

    end % end of public methods

    methods (Access=protected) % start of private methods

        function occgrid2costmap(as, og, cost)
            if nargin < 3
                cost = 1; % cost to traverse each cell
            end
            as.costmap = og;
            as.costmap(as.costmap==1) = Inf; % occupied cells -> infinite path cost
            as.costmap(as.costmap==0) = cost; % unoccupied cells -> path cost
        end

        function calcHeuristic(as, grid, goal)
            as.cost_h=zeros(size(grid));
            for ii=1:size(grid,1)
                for jj=1:size(grid,2)
                    as.cost_h(ii,jj)=sqrt((ii-goal(1))^2+(jj-goal(2))^2);
                end
            end
        end

             % NOTE: Only for costs that accumulate (i.e. sum) over the
            % path, and for dynamic costs.
            % E.g. the heuristic parameter AS.cost_h only needs updating
            % when the goal state changes; it's values are stored for each
            % cell.
            %
            % Location moving from state b to a.

        function k_new = updateCosts(as, a, b, obj)

            if nargout > 0
                k_new = as.cost_g(b) + as.dc(b,a);
                return
            end
            if obj == 0                        % just return what the new priority cost would be (k_new)
                return
            end
            if obj > 1                         % base case
                as.cost_g(a) = as.cost_g(b) + as.dc(b,a);
                % (no heuristic update needed)
            end
            if obj > 2                         % w/ cost_01: elevation
                % (no elevation update needed)
            end
            if obj > 3                         % w/ cost_02: solar
                sV = [cos(as.niter/100);sin(as.niter/100)]; % rotates 1rad per 100 steps
                as.cost_02(a) = dot(sV,as.vc(b,a));
            end
            if obj > 4                         % w/ cost_03: risk
                % (no risk update needed)
            end
        end

        % Returns the projection of state a into objective space. If
        % specified, location is moving from b to a.
        function pt = projectCost(as, a, b)
            switch nargin
                case 2
                    pt = [as.cost_g(a);
                          as.cost_h(a);
                          as.cost_01(a);
                          as.cost_02(a);
                          as.cost_03(a);
                          ];
                case 3
                    pt = [as.cost_g(b) + as.dc(a,b);
                          as.cost_h(a);
                          as.cost_01(a);
                          as.cost_02(a);
                          as.cost_03(a);
                          ];
                otherwise
                    return
            end
        end

        % X is new node with costs in array pt

        function INSERT(as, X, pt, where)

            if nargin>2
                as.message('insert (%s) %d = %f\n', where, X, pt);
            end

            i = find(as.openlist(1,:) == X);
            if length(i) > 1
                error('A*:INSERT: state in open list %d times', X);
            end

            if as.t(X) == as.NEW
                % add a new column to the open list
                as.openlist = [as.openlist [X; pt]];
            elseif as.t(X) == as.OPEN
                % L13: If a node with same position as successor is in the
                % OPEN list & has a lower f than successor, then skip this
                % successor.
                if pt(as.tie) < as.priority(X) % break tie with the sum of path costs
                    % add a new column to the open list
                    as.openlist = [as.openlist [X; pt]];
                end
            elseif as.t(X) == as.CLOSED
                % L14: If a node with same position as successor is in the
                % CLOSED list & has a lower f than successor, then skip this
                % successor.
                if pt(as.tie) < as.priority(X) % break tie with the sum of path costs
                    % add a new column to the open list
                    as.openlist = [as.openlist [X; pt]];
                end
            end

            % keep track of the max length of the openlist:
            if numcols(as.openlist) > as.openlist_maxlen
                as.openlist_maxlen = numcols(as.openlist);
            end

            as.t(X) = as.OPEN; % tag X as open
        end

        function DELETE(as, X)
            as.message('delete %d\n', X);
            i = find(as.openlist(1,:) == X);
            if length(i) ~= 1
                error('D*:DELETE: state %d doesnt exist', X);
            end
            as.openlist(:,i) = []; % remove the column
            as.t(X) = as.CLOSED;
        end

        % return the distance cost of moving from state X to state Y
        function cost = dc(as, X, Y)
            [r,c] = ind2sub(size(as.costmap), [X; Y]);
            dist = sqrt(sum(diff([r c]).^2));
            dcost = (as.costmap(X) + as.costmap(Y))/2;

            cost = dist * dcost;
        end

        % return the robot unit vector; direction of moving from state X to state Y
        function vector = vc(as, X, Y)
            [Xi,Xj] = ind2sub(size(as.occgrid),X);
            [Yi,Yj] = ind2sub(size(as.occgrid),Y);
            vector = [Yi-Xi;Yj-Xj];
            vector = vector/norm(vector);
%             slope = vector(2) / vector(1);
%             theta = dot([0,1],[vector])/(norm([0,1])*norm(vector));
        end

        % return index of neighbor states (max 8) as a row vector
        function Y = neighbors(as, X)
            dims = size(as.costmap);
            [r,c] = ind2sub(dims, X);

            % list of 8-way neighbors:
            Y = [r-1 r-1 r-1 r r  r+1 r+1 r+1; c-1 c c+1 c-1 c+1 c-1 c c+1];
            % only use neighbors w/in grid bounds...
            k = (min(Y)>0) & (Y(1,:)<=dims(1)) & (Y(2,:)<=dims(2));
            Y = Y(:,k);
            Y = sub2ind(dims, Y(1,:)', Y(2,:)')';
        end

    end % end of private methods
end


function obj = GridData(geodata,obj,varargin)
% obj = GridData(geodata,obj,varargin);
% GridData: Uses the cell-averaged approach to interpolate the nodes
%           on the unstructured grid to the DEM.
%       Input : geodata - either a geodata class or a filename of the netcdf DEM
%                   obj - A msh class object containing:
%                     p - A vector of the locations of all the vertices
%                         in the mesh, length in number of nodes, nn x 2
%                     t - Matrix size ne x 3 of the element table of the mesh
%
%          K (optional) - vector of relevant nodes to search. This can
%                         significantly speed up the calculation by either
%                         only interpolating part of the mesh or
%                         intepolating whole mesh but in segments. Function
%                         automatically removes uncessary portions of DEM
%                         Example to call:
%                         K = find( obj.p(:,1) >= lon_min & ...
%                                   obj.p(:,2) <= lon_max);
%                         obj = GridData(fname,obj,'K',K);
%       type (optional) - type is either 'depth', 'slope' or 'all'
%                         'all' is the default (both slope and depth).
%                         'slope' to gets the gradients of DEM
%
%     interp (optional) - interp is either the normal griddedInterpolant
%                         options in MATLAB or is 'CA' (default). Note: 'CA'
%                         applies linear griddedInterpolant when the DEM
%                         and grid sizes are similar.
%
%          N (optional) - enlarge cell-averaging stencil by factor N (only
%                         relevant for CA interpolation method).
%                         default value N=1.
%
%        nan (optional) - 'fill' to fill in any NaNs everywhere
%                       - 'fillinside' to fill NaNs only in DEM extents
%
%   mindepth (optional) - ensure the minimum depth is bounded in the
%                         interpolated region
%
%   maxdepth (optional) - ensure the maximum depth is bounded in the
%                         interpolated region
%
%   ignoreOL (optional) - NaN overland data for more accurate seabed interpolation
%
%   lut (optional)      - A look up table (lut). See nlcd and ccap in
%                         datasets/ for examples
%
% slope_calc (optional) - 'rms' [default]: Compute cell-averaged slope based on root-mean-square
%                       - 'abs'          : Compute cell-averaged slope based on mean of absolute value
%
%      Output : obj     - A mesh class object with the following updated:
%               b       - The depth or vertical difference from the datum
%                         in the mesh if type is 'depth' or 'all'
%               bx      - The x direction gradient if type is 'slope' or
%                         'all'
%               by      - The y direction gradient if type is 'slope' or
%                         'all'
%
%	Author: William Pringle, and Keith Roberts CHL, Notre Dame University
%	Created: 2018-03-12
%
%   Edits by Keith Roberts, 2018-02-22 to avoid FillValue contaimation and
%   to only interpolate data inside extent of DEM.
%
%   Edits by Keith Roberts, 2018-10-18 to bound depth in interpolated
%   regions
%
%   Edits by Keith Roberts, 2019-4-4 to interpolate the floodplain
%
%% Test optional arguments
% default
nn = length(obj.p);
K  = (1:nn)';        % K is all of the grid
type = 'all';
interp = 'CA';
NaNs = 'ignore'; nanfill = false;
ignoreOL = 0 ;
N    = 1 ;
mindepth = -inf ;
maxdepth = +inf ;
rms_slope_calc = true;
slope_calc = 'rms';
lut = [] ;
if ~isempty(varargin)
    varargin=varargin{1} ;
    names = {'K','type','interp','nan','N','mindepth','maxdepth','ignoreOL','slope_calc','lut'};
    for ii = 1:length(names)
        ind = find(~cellfun(@isempty,strfind(varargin(1:2:end),names{ii})));
        if ~isempty(ind)
            if ii == 1
                K = varargin{ind*2};
                nn = length(K);
                if nn == 0
                   error('K index entry is empty!')
                end
            elseif ii == 2
                type = varargin{ind*2};
            elseif ii == 3
                interp = varargin{ind*2};
            elseif ii == 4
                NaNs = varargin{ind*2};
            elseif ii == 5
                N = varargin{ind*2} ;
            elseif ii ==6
                mindepth = varargin{ind*2} ;
            elseif ii ==7
                maxdepth = varargin{ind*2}  ;
            elseif ii ==8
                ignoreOL = varargin{ind*2} ;
            elseif ii ==9
                slope_calc = varargin{ind*2};
                if strcmp(slope_calc,'rms')
                   rms_slope_calc = true;
                elseif strcmp(slope_calc,'abs')
                   rms_slope_calc = false;
                else
                   error(['Invalid entry, ' temp ', for slope_calc'])
                end
            elseif ii == 10
                lut = varargin{ind*2};
            end
        end
    end
end

if ignoreOL
    disp('NaNing overland data before interpolating')
end
if N > 1
    disp(['Enlarging CA stencil by factor ',num2str(N)]) ;
end
if mindepth > -inf
    disp(['Bounding minimum depth to ',num2str(mindepth), ' meters.']) ;
end
if maxdepth < inf
    disp(['Bounding maximum depth to ',num2str(maxdepth), ' meters.']) ;
end

if strcmp(type,'slope')
    disp(['Computing cell-averaged slope using the ' slope_calc ' method'])
    if isempty(obj.b) || all(obj.b == 0)
        error(['You must have the bathymetry on the mesh before ' ...
               'calculating the slopes'])
    elseif sum(isnan(obj.b)) > 0
        warning(['There are some NaNs in the bathymetry that could ' ...
                 'affect the computation of the slopes'])
    end
end
if length(K) == 1
    if strcmp(type,'slope') || strcmp(type,'all')
        error('Cannot compute slopes on bathymetry with K of length 1')
    end
end

if strcmp(NaNs,'fill') || strcmp(NaNs,'fillinside')
    disp('Fill in NaNs using nearest neighbour interpolation.')
    nanfill = true;
end
if strcmp(NaNs,'fill')
   warning(['Note that nan "fill" option will try and put values everywhere ' ...
            'on mesh even outside of gdat/dem extents unless K logical is ' ...
            'set. Change nan to "fillinside" to avoid this.'])
end

if ~isempty(lut)
    disp('Using look up table...');
end

%% Let's read the LON LAT of DEM if not already geodata
flipUD = 0;
if ~isa(geodata,'geodata')
    [xvn, yvn, zvn] = getdemvarnames(geodata);
    DEM_XA = double(ncread(geodata,xvn));
    DEM_YA = double(ncread(geodata,yvn));
    DELTA_X = mean(diff(DEM_XA));
    DELTA_Y = mean(diff(DEM_YA));
    if DELTA_Y < 0
       flipUD = 1;
       DELTA_Y = -DELTA_Y;
    end
else
    DEM_XA = geodata.Fb.GridVectors{1};
    DEM_YA = geodata.Fb.GridVectors{2};
    DEM_Z  = -geodata.Fb.Values;
    if ignoreOL
       DEM_Z(DEM_Z <= 0) = NaN ;
    end
    DELTA_X = mean(diff(DEM_XA));
    DELTA_Y = mean(diff(DEM_YA));
    [DEM_X,DEM_Y] = ndgrid(DEM_XA,DEM_YA);
end

if max(DEM_XA) > 180
   lon_change = obj.p(:,1) < 0; lon_dir = 1;
elseif max(obj.p(:,1)) > 180
   lon_change = obj.p(:,1) > 180; lon_dir = -1;
else
   lon_change = false(length(obj.p),1); lon_dir = 0;
end
obj.p(lon_change,1) = obj.p(lon_change,1) + lon_dir*360;

% kjr edit 20180320
if length(K) == length(obj.p)
    % msh class may not have populated b, if this case populate with NaN to
    % detect issues
    if isempty(obj.b)
        obj.b = obj.p(:,1)*NaN;
    end
    if ~strcmp(NaNs,'fill')
        outside = obj.p(:,1) < min(DEM_XA)-DELTA_X | ...
                  obj.p(:,1) > max(DEM_XA)+DELTA_X | ...
                  obj.p(:,2) < min(DEM_YA)-DELTA_Y | ...
                  obj.p(:,2) > max(DEM_YA)+DELTA_Y;
        K(outside) = [];
        if isempty(K)
            warning('no mesh vertices contained within DEM bounds'); return;
        end
    end
end

% If the length of DEM_X and DEM_Y is too large then let's break it up by
% using K
% test
lon_min = min(obj.p(K,1));
lon_max = max(obj.p(K,1));
lat_min = min(obj.p(K,2));
lat_max = max(obj.p(K,2));

I = find(DEM_XA >= lon_min & DEM_XA <= lon_max);
J = find(DEM_YA >= lat_min & DEM_YA <= lat_max);
% single or integer array
L = length(I)*length(J)*4e-9;
% larger than 2 GB
if L > 2
    % divide by latitude
    times = ceil(L/2); lat_l = lat_min; dl = (lat_max - lat_min)/times;
    for t = 1:times
        lat_r = lat_l + dl;
        if t == times
            lat_r = lat_max;
        end
        KK{t} = K(obj.p(K,2) >= lat_l & obj.p(K,2) <= lat_r);
        lat_l = lat_r;
    end
else
    times = 1;
    KK{1} = K;
end
% This deletes any elements straddling the -180/180 boundary
xt = [obj.p(obj.t(:,1),1) obj.p(obj.t(:,2),1) ...
      obj.p(obj.t(:,3),1) obj.p(obj.t(:,1),1)];
dxt = diff(xt,[],2);
tt = obj.t;
tt(abs(dxt(:,1)) > 180 | abs(dxt(:,2)) > 180 |  abs(dxt(:,3)) > 180,:) = [];

% Do this once;
vtoe_o = VertToEle(tt); %find connecting elements to each node
pmid = squeeze(mean(reshape(obj.p(tt,:),[],3,2),2)); % get mid points of elements
pmid(end+1,:) = NaN;

K_o = K; % save the original K
disp(['Looping over the DEM ' num2str(times) ' time(s)'])
for t = 1:times
K = KK{t};
%% Get the grid size for each node
% Get the subset
vtoe = vtoe_o(:,K);
% make sure when vtoe is zero we just return a NaN
vtoe(vtoe == 0) = length(tt) + 1;
% the connecting element centers
pcon = reshape(pmid(vtoe,:),size(vtoe,1),[],2);

% delta is max and min bounds of the connecting element centers (or if only
% one connecting element then do difference with the vertex itself
if length(K) == 1
    pcon_max = max(max(squeeze(pcon)),2*obj.p(K,:)-max(squeeze(pcon)));
    pcon_min = min(min(squeeze(pcon)),2*obj.p(K,:)-min(squeeze(pcon)));
else
    pcon_max = max(squeeze(max(pcon,[],1)),2*obj.p(K,:)-squeeze(min(pcon,[],1)));
    pcon_min = min(squeeze(min(pcon,[],1)),2*obj.p(K,:)-squeeze(max(pcon,[],1)));
end
delta = pcon_max - pcon_min;

% kjr 10/17/2018 enlarge the cell-averaging stencil by a factor N
pcon_max = N*pcon_max+(1-N)*obj.p(K,:) ;
pcon_min = N*pcon_min+(1-N)*obj.p(K,:) ;

%% Now read the bathy (only what we need)
if ~isa(geodata,'geodata')
    BufferL = max(delta,[],1);
    lon_min = min(obj.p(K,1)) - BufferL(1);
    lon_max = max(obj.p(K,1)) + BufferL(1);
    lat_min = min(obj.p(K,2)) - BufferL(2);
    lat_max = max(obj.p(K,2)) + BufferL(2);
    I = find(DEM_XA > lon_min & DEM_XA < lon_max);
    J = find(DEM_YA > lat_min & DEM_YA < lat_max);
    if exist('DEM_X','var')
        clear DEM_X DEM_Y DEM_Z
    end
    [DEM_X,DEM_Y] = ndgrid(DEM_XA(I),DEM_YA(J));
    DEM_Z = single(ncread(geodata,zvn,...
                   [I(1) J(1)],[length(I) length(J)]));
    if flipUD
       % handle DEMS from packed starting from the bottom left
       DEM_YA = flipud(DEM_YA) ;
       DEM_Y = fliplr(DEM_Y);
       DEM_Z = fliplr(DEM_Z);
    end

    % make into depths (ADCIRC compliant)
    DEM_Z = -DEM_Z;

    if ignoreOL
       DEM_Z(DEM_Z <= 0) = NaN ;
    end
end

% bound all depths below mindepth
DEM_Z(DEM_Z < mindepth) = mindepth ;

% bound all depths above maxdepth
DEM_Z(DEM_Z > maxdepth) = maxdepth ;

% if look up table is passed
if ~isempty(lut)
    DEM_Z(isnan(DEM_Z) | DEM_Z == 0)=length(lut); % <--default value to NaN
    % Convert to Mannings
    DEM_Z = lut(abs(round(DEM_Z)));
end
%% Make the new bx, by, b and calculate gradients if necessary
if strcmp(type,'slope') || strcmp(type,'all')
    bx = NaN(length(K),1);
    by = NaN(length(K),1);
    % Get the dx and dy of the dem in meters
    DELTA_X1 = DELTA_X*111e3;
    DELTA_X1 = DELTA_X1*cosd(DEM_Y(1,:)); % for gradient function
    DELTA_Y1 = DELTA_Y*111e3;
    if exist('DEM_ZY','var')
        clear DEM_ZY DEM_ZX
    end
    % Calculate the gradient of the distance function.
    [DEM_ZY,DEM_ZX] = EarthGradient(DEM_Z,DELTA_Y1,DELTA_X1);
    % New method of averaging the absolute values
    DEM_ZY = abs(DEM_ZY); DEM_ZX = abs(DEM_ZX);
end
if strcmp(type,'depth') || strcmp(type,'all')
    b = NaN(length(K),1);
end

%% Calculate N - number of DEM grids to average - for each node
if strcmp(interp,'CA')
    [~,IDXX,IDXY] = FindLinearIdx(obj.p(K,1),obj.p(K,2),DEM_X,DEM_Y);
    [~,IDXR,IDXT] = FindLinearIdx(pcon_max(:,1),pcon_max(:,2),DEM_X,DEM_Y);
    [~,IDXL,IDXB] = FindLinearIdx(pcon_min(:,1),pcon_min(:,2),DEM_X,DEM_Y);
    % make sure inside box
    % y
    high = DEM_Y(IDXR(1),IDXT)' > pcon_max(:,2);
    IDXT(high) = max(1,IDXT(high) - 1);
    low = DEM_Y(IDXL(1),IDXB)' < pcon_min(:,2);
    IDXB(low) = min(size(DEM_Y,2),IDXB(low) + 1);
    % x
    high = DEM_X(IDXR,IDXT(1)) > pcon_max(:,1);
    IDXR(high) = max(1,IDXR(high) - 1);
    low = DEM_X(IDXL,IDXB(1)) < pcon_min(:,1);
    IDXL(low) = min(size(DEM_X,1),IDXL(low) + 1);
    % Make sure no negative or positive Nx, Ny
    I = IDXL > IDXR;
    IDXL(I) = IDXX(I); IDXR(I) = IDXX(I);
    I = IDXB > IDXT;
    IDXB(I) = IDXY(I); IDXT(I) = IDXY(I);
    % The span of x and y
    Nx = IDXR - IDXL;
    Ny = IDXT - IDXB;

    disp(['Performing cell-averaging with Ny(max) ' num2str(max(Ny)) ...
          ' and Nx(max)' num2str(max(Nx))])

    % Average for the depths
    if strcmp(type,'depth') || strcmp(type,'all')
        for ii = 1:length(K)
            pts = reshape(DEM_Z(IDXL(ii):IDXR(ii),...
                                IDXB(ii):IDXT(ii)),[],1);
            b(ii) = mean(pts,'omitnan');
        end
        if sum(~isnan(b)) == 0
            warning('All depths were NaNs. Doing nothing and returning');
            return
        end
        % Try and fill in the NaNs
        if nanfill
            if sum(isnan(b)) > 0
                localcoord = obj.p(K,:);
                [KI, ~] = ourKNNsearch(localcoord(~isnan(b),:),',localcoord(isnan(b)',1);
                %KI = knnsearch(localcoord(~isnan(b),:),localcoord(isnan(b),:));
                bb = b(~isnan(b));
                b(isnan(b)) = bb(KI); clear bb localcoord
            end
        end
    end

    % RMS for the slopes
    if strcmp(type,'slope') || strcmp(type,'all')
        for ii = 1:length(K)
            pts = reshape(DEM_ZX(IDXL(ii):IDXR(ii),...
                                 IDXB(ii):IDXT(ii)),[],1);
            if rms_slope_calc
               bx(ii) = sqrt(mean(pts.^2,'omitnan'));
            else
               bx(ii) = mean(abs(pts),'omitnan');
            end
            pts = reshape(DEM_ZY(IDXL(ii):IDXR(ii),...
                                 IDXB(ii):IDXT(ii)),[],1);
            if rms_slope_calc
               by(ii) = sqrt(mean(pts.^2,'omitnan'));
            else
               by(ii) = mean(abs(pts),'omitnan');
            end
        end
        if nanfill
            % Try and fill in the NaNs
            if ~isempty(find(isnan(bx),1))
                localcoord = obj.p(K,:);
                [KI, ~] = ourKNNsearch(localcoord(~isnan(bx),:)',localcoord(isnan(bx),:)',1);
                %KI = knnsearch(localcoord(~isnan(bx),:),localcoord(isnan(bx),:));
                bb = bx(~isnan(bx));
                bx(isnan(bx)) = bb(KI); clear bb localcoord
            end
            if ~isempty(find(isnan(by),1))
                localcoord = obj.p(K,:);
                [KI, ~] = ourKNNsearch(localcoord(~isnan(by),:)',localcoord(isnan(by),:)',1);
                %KI = knnsearch(localcoord(~isnan(by),:),localcoord(isnan(by),:));
                bb = by(~isnan(by));
                by(isnan(by)) = bb(KI); clear bb localcoord
            end
        end
    end
else
    %% Get gridded interpolant for non-CA interp
    disp(['Using griddedInterpolant with ' interp 'interp option'])
    if strcmp(type,'slope') || strcmp(type,'all')
        Fx = griddedInterpolant(DEM_X,DEM_Y,DEM_ZX,interp);
        Fy = griddedInterpolant(DEM_X,DEM_Y,DEM_ZY,interp);
        bx = Fx(obj.p(K,1),obj.p(K,2));
        by = Fy(obj.p(K,1),obj.p(K,2));
    end
    if strcmp(type,'depth') || strcmp(type,'all')
        F = griddedInterpolant(DEM_X,DEM_Y,DEM_Z,interp,'none');
        b = F(obj.p(K,1),obj.p(K,2));
    end
end
%% Put in the global msh class
if strcmp(type,'depth') || strcmp(type,'all')
    if isempty(obj.b)
        obj.b = zeros(length(obj.p),1);
    end
    % Only overwrite non-nan values
    obj.b(K(~isnan(b))) = b(~isnan(b));
end
if strcmp(type,'slope') || strcmp(type,'all')
    if isempty(obj.bx)
        obj.bx = zeros(length(obj.p),1);
    end
    obj.bx(K(~isnan(bx))) = bx(~isnan(bx));
    % Put in the global array
    if isempty(obj.by)
        obj.by = zeros(length(obj.p),1);
    end
    obj.by(K(~isnan(by))) = by(~isnan(by));
end
end
% New method of averaging asbolute values of slope before multiplying
% by the sign of the slope on unstructured grid
if strcmp(type,'slope') || strcmp(type,'all')
    [Hx,Hy] = Unstruc_Bath_Slope( tt,obj.p(:,1),obj.p(:,2),obj.b);
    obj.bx(K_o) = sign(Hx(K_o)).*obj.bx(K_o);
    obj.by(K_o) = sign(Hy(K_o)).*obj.by(K_o);
end
obj.p(lon_change,1) = obj.p(lon_change,1) - lon_dir*360;
%EOF
end

function [xvn, yvn, zvn] = getdemvarnames(fname)
    % Define well-known variables for longitude and latitude
    % coordinates in Digital Elevation Model NetCDF file (CF
    % compliant).
    xvn = []; yvn = []; zvn = [];
    wkv_x = {'x','Longitude','longitude','lon','lon_z'} ;
    wkv_y = {'y','Latitude', 'latitude','lat','lat_z'} ;
    finfo = ncinfo(fname);
    for ii = 1:length(finfo.Variables)
        if ~isempty(xvn) && ~isempty(yvn) && ~isempty(zvn); break; end
        if length(finfo.Variables(ii).Size) == 1
            if isempty(xvn) && ...
                    any(strcmp(finfo.Variables(ii).Name,wkv_x))
                xvn = finfo.Variables(ii).Name;
            end
            if isempty(yvn) && ...
                    any(strcmp(finfo.Variables(ii).Name,wkv_y))
                yvn = finfo.Variables(ii).Name;
            end
        elseif length(finfo.Variables(ii).Size) == 2
            if isempty(zvn)
                zvn = finfo.Variables(ii).Name;
            end
        end
    end
    if isempty(xvn)
        error('Could not locate x coordinate in DEM') ;
    end
    if isempty(yvn)
        error('Could not locate y coordinate in DEM') ;
    end
    if isempty(zvn)
        error('Could not locate z coordinate in DEM') ;
    end
end


function [] = plot_results(setup_file)

    width = 24;
    height = 12;
    fontsize = 9;

    setup = ReadYaml(setup_file)

    algorithm = setup.algorithm;
    configuration_name = setup.configuration_name;
    experiment_name = setup.experiment_name;
    granularity = setup.granularity;
    dataset = setup.dataset;
    household = setup.household;
    setup_name = setup.setup_name;

    source_folder = ['results/details/', algorithm, '/', configuration_name, '/', experiment_name, '/', setup_name];
    plot_folder = ['results/plots/', algorithm, '/', configuration_name, '/', experiment_name, '/', setup_name, '/'];

    load([source_folder, '/result1.mat']);

    if ~exist(plot_folder)
        mkdir(plot_folder)
    end

    evaluation_days = result.evaluation_and_training_days{1};
    first_day = evaluation_days(1,:);
    num_days = size(evaluation_days, 1);

    total_consumption = read_smartmeter_data(dataset, household, evaluation_days, granularity, 'powerallphases');

    %%%%%%%%%%%%%%
    %% Plot events
    %%%%%%%%%%%%%%
    if isfield(result, 'events')
        i = 1;
        for appliance_name_cell = result.appliance_names
            appliance_name = cell2mat(appliance_name_cell);
            appliance_id = getApplianceID(appliance_name);
            fprintf(appliance_name);
            plug_consumption = read_plug_data(dataset, household, appliance_id, evaluation_days, granularity);

            classified_events = struct;

            % events inferred from algorithm
            % col1: event time
            % col2: appliance id
            % col3: delta
            events_inferred = result.events(result.events(:,2) == i,:);

            % get ground truth from plug-level data
            filter = struct;
            filter.p_edgeThreshold = setup.p_edgeThreshold;
            filter.p_filtering = setup.p_filtering;
            filter.p_filtLength = setup.p_filtLength;
            % col1: time start
            % col2: time stop
            % col3: consumption delta
            events_from_plug_data = getEventsFromPlugData(dataset, household, appliance_id, evaluation_days, granularity, filter);

            % class one events: correctly inferred
            % class two events: missed events
            plug_events_detected = arrayfun(@(idx) any(events_inferred(:,1) > events_from_plug_data(idx,1) - 20 & ...
                    events_inferred(:,1) < events_from_plug_data(idx,1) + 20), [1:size(events_from_plug_data, 1)]');
            avg_deviation_of_plug_data_event = mean(events_from_plug_data(:,1) - events_from_plug_data(:,2));
            classified_events.tp_events = [events_from_plug_data(plug_events_detected,1) + avg_deviation_of_plug_data_event, events_from_plug_data(plug_events_detected,3)];
            classified_events.fn_events = [events_from_plug_data(~plug_events_detected,1) + avg_deviation_of_plug_data_event, events_from_plug_data(~plug_events_detected,3)];

            % class three events: incorrectly inferred
            which_detected_events_have_ground_truth_events = arrayfun(@(t) any(events_from_plug_data(:,1) - 20 < t & t < events_from_plug_data(:,2) + 20), ...
                    events_inferred(:, 1));
            plug_data_exists = arrayfun(@(t) ~any(plug_consumption(max(1,t-5):min(t+5,length(plug_consumption))) == -1), events_inferred(:, 1));
            tmp_idx = ~which_detected_events_have_ground_truth_events & plug_data_exists;
            classified_events.fp_events = events_inferred(tmp_idx, [1,3]);

            for d = 1:num_days
                day = evaluation_days(d,:);
                idx_start = 86400 / granularity * (d-1) + 1;
                idx_stop = 86400 / granularity * d;
                date_range = datenum(day)+granularity/86400 : granularity/86400 : datenum(day)+1;

                % plot smart meter consumption
                fig = figure;
                hold on;
                plot(date_range, total_consumption(idx_start:idx_stop));
                max_cons = max(total_consumption(idx_start:idx_stop));

                % get events for current day
                tp_events = classified_events.tp_events(:,1) >= idx_start & classified_events.tp_events(:,1) <= idx_stop;
                tp_events_on = classified_events.tp_events(classified_events.tp_events(:,2) > 0 & tp_events, 1);
                tp_events_off = classified_events.tp_events(classified_events.tp_events(:,2) < 0 & tp_events, 1);
                fn_events = classified_events.fn_events(:,1) >= idx_start & classified_events.fn_events(:,1) <= idx_stop;
                fn_events_on = classified_events.fn_events(classified_events.fn_events(:,2) > 0 & fn_events, 1);
                fn_events_off = classified_events.fn_events(classified_events.fn_events(:,2) < 0 & fn_events, 1);
                fp_events = classified_events.fp_events(:,1) >= idx_start & classified_events.fp_events(:,1) <= idx_stop;
                fp_events_on = classified_events.fp_events(classified_events.fp_events(:,2) > 0 & fp_events, 1);
                fp_events_off = classified_events.fp_events(classified_events.fp_events(:,2) < 0 & fp_events, 1);

                for ev = 1:size(tp_events_on, 1)
                    time = tp_events_on(ev, 1);
                    xval = datenum(day) + time/86400*granularity - d + 1;
                    plot([xval,xval],[0,max_cons], 'g-');
                end

                for ev = 1:size(tp_events_off, 1)
                    time = tp_events_off(ev, 1);
                    xval = datenum(day) + time/86400*granularity - d + 1;
                    plot([xval,xval],[0,max_cons], 'g--');
                end

                for ev = 1:size(fn_events_on, 1)
                    time = fn_events_on(ev, 1);
                    xval = datenum(day) + time/86400*granularity - d + 1;
                    plot([xval,xval],[0,max_cons], '-', 'Color', [0.5 0.5 0.5]);
                end

                for ev = 1:size(fn_events_off, 1)
                    time = fn_events_off(ev, 1);
                    xval = datenum(day) + time/86400*granularity - d + 1;
                    plot([xval,xval],[0,max_cons], '--', 'Color', [0.5 0.5 0.5]);
                end

                for ev = 1:size(fp_events_on, 1)
                    time = fp_events_on(ev, 1);
                    xval = datenum(day) + time/86400*granularity - d + 1;
                    plot([xval,xval],[0,max_cons], 'r-');
                end

                for ev = 1:size(fp_events_off, 1)
                    time = fp_events_off(ev, 1);
                    xval = datenum(day) + time/86400*granularity - d + 1;
                    plot([xval,xval],[0,max_cons], 'r--');
                end

                ylim([0 max_cons]);
                datetick('x','HHPM')
                % legend('Actual', 'Inferred'); % does not work
                title([appliance_name, ' - ', evaluation_days(d,:)])
                fig = make_report_ready(fig, 'size', [width, height], 'fontsize', fontsize);
                filename = ['Events_', appliance_name, '_', evaluation_days(d,:)];
                % print('-depsc2', '-cmyk', '-r600', [plot_folder, filename, '.eps']); % if eps is needed
                saveas(fig, [plot_folder, filename, '.png'], 'png');
                close(fig);
            end
            i = i+1;
        end
    end

    %%%%%%%%%%%%%
    %% Plot inferred consumption and pie chart
    %%%%%%%%%%%%%
    if isfield(result, 'consumption')
        %% plot inferred plug data vs ground truth plug data
        i = 1;
        for appliance_name_cell = result.appliance_names
            appliance_name = cell2mat(appliance_name_cell);
            appliance_id = getApplianceID(appliance_name);
            plug_consumption = read_plug_data(dataset, household, appliance_id, evaluation_days, granularity);
            inferred_consumption = result.consumption(i,:);
            for d = 1:num_days
                day = evaluation_days(d,:);
                idx_start = 86400 / granularity * (d-1) + 1;
                idx_stop = 86400 / granularity * d;
                date_range = datenum(day)+granularity/86400 : granularity/86400 : datenum(day)+1;

                fig = figure;
                hold on;
                plot(date_range, plug_consumption(idx_start:idx_stop));
                plot(date_range, inferred_consumption(idx_start:idx_stop), 'r');
                if strcmp(appliance_name, 'Fridge') == 1 || strcmp(appliance_name, 'Freezer') == 1
                    ylim([0 500]);
                end
                datetick('x','HHPM')
                % legend('Actual', 'Inferred'); % does not work
                title([appliance_name, ' - ', evaluation_days(d,:)])
                fig = make_report_ready(fig, 'size', [width, height], 'fontsize', fontsize);
                filename = ['Consumption_', appliance_name, '_', evaluation_days(d,:)];
                % print('-depsc2', '-cmyk', '-r600', [plot_folder, filename, '.eps']); % if eps is needed
                saveas(fig, [plot_folder, filename, '.png'], 'png');
                close(fig);
            end
            i = i+1;
        end

        %% plot ground truth smart meter data vs ground truth plug data and vs. ground truth inferred consumption
        i = 1;
        plug_consumption = {};
        inferred_consumption = {};
        for appliance_name_cell = result.appliance_names
            appliance_name = cell2mat(appliance_name_cell);
            appliance_id = getApplianceID(appliance_name);
            plug_consumption{i} = read_plug_data(dataset, household, appliance_id, evaluation_days, granularity);
            inferred_consumption{i} = result.consumption(i,:);
            i = i+1;
        end
        for d = 1:num_days
            day = evaluation_days(d,:);
            idx_start = 86400 / granularity * (d-1) + 1;
            idx_stop = 86400 / granularity * d;
            date_range = datenum(day)+granularity/86400 : granularity/86400 : datenum(day)+1;

            fig = figure;
            hold on;
            plot(date_range, total_consumption(idx_start:idx_stop));
            for j = 1:length(plug_consumption)
                 plot(date_range, plug_consumption{j}(idx_start:idx_stop));
            end
            datetick('x','HHPM')
            % legend('Actual', 'Inferred'); % does not work
            title(['Plug and Smart Meter - ', evaluation_days(d,:)])
            fig = make_report_ready(fig, 'size', [width, height], 'fontsize', fontsize);
            filename = ['plug-and-smartmeter_', evaluation_days(d,:)];
            % print('-depsc2', '-cmyk', '-r600', [plot_folder, filename, '.eps']); % if eps is needed
            saveas(fig, [plot_folder, filename, '.png'], 'png');
            close(fig);
            fig = figure;
            hold on;
            plot(date_range, total_consumption(idx_start:idx_stop));
            for j = 1:length(plug_consumption)
                plot(date_range, inferred_consumption{j}(idx_start:idx_stop));
            end
            datetick('x','HHPM')
            % legend('Actual', 'Inferred'); % does not work
            title(['Inferred and Smart Meter -  - ', evaluation_days(d,:)])
            fig = make_report_ready(fig, 'size', [width, height], 'fontsize', fontsize);
            filename = ['Smartmeter_', evaluation_days(d,:)];
            % print('-depsc2', '-cmyk', '-r600', [plot_folder, filename, '.eps']); % if eps is needed
            saveas(fig, [plot_folder, filename, '.png'], 'png');
            close(fig);
        end

        %% plot pie charts
        i = 1;
        plug_consumption = {};
        inferred_consumption = {};
        for appliance_name_cell = result.appliance_names
            appliance_name = cell2mat(appliance_name_cell);
            appliance_id = getApplianceID(appliance_name);
            tmp = read_plug_data(dataset, household, appliance_id, evaluation_days, granularity);
            valid_time = tmp ~= -1;
            plug_consumption{i} = mean(tmp(valid_time)) * num_days * 24;
            cons = result.consumption(i,:);
            inferred_consumption{i} = mean(cons(valid_time)) * num_days * 24;
            i = i+1;
        end
        other_plugs = mean(total_consumption) * num_days * 24 - sum(cell2mat(plug_consumption));
        other_inferred = mean(total_consumption) * num_days * 24 - sum(cell2mat(inferred_consumption));
        fig = figure;
        hold on;
        subplot(1,2,1);
        pie([cell2mat(plug_consumption), other_plugs]);
        title('Ground truth');
        subplot(1,2,2);
        pie([cell2mat(inferred_consumption), other_inferred]);
        title('Inferred');
        fig = make_report_ready(fig, 'size', [width, height], 'fontsize', fontsize);
        filename = 'Pie_Chart';
        % print('-depsc2', '-cmyk', '-r600', [plot_folder, filename, '.eps']); % if eps is needed
        saveas(fig, [plot_folder, filename, '.png'], 'png');
        close(fig);
    end
end


function Y = prtUtilY(varargin)
% prtUtilY     Quickly generate class labels for the PRT.
%
% Syntax: Y = prtUtilY(nH0,nH1,nH2,...)
%
% Inputs:
%   nH0 - The number of H0 samples
%   nH1 - The number of H1 samples
%       ...
%
% Outputs:
%   Y - A PRT compliant labeled vector
%
% Example:
%   Y = prtUtilY(100,100);
%   Y = prtUtilY([],100,100);
%   Y = prtUtilY(100,0,100);
%
% See also: prtDataGen*

Y = [];

if nargin == 1
    if length(varargin{1}) > 1

        temp = mat2cell(varargin{1}(:),ones(length(varargin{1}),1),1);
        Y = prtUtilY(temp{:});
    else
        Y = zeros(varargin{1},1);
    end
    return
end

for iInput = 1:nargin
    nHi = varargin{iInput};
    if ~isempty(nHi)
        if nHi > 0
            Y(end+1:end+nHi,1) = iInput-1;
        end
    end
end


% pdfbtrainimagethmt.m
% written by: Duncan Po
% Date: August 24, 2002
% Using the EM algorithm, train models for the specified image
% Usage: [model, stateprob] = pdfbtrainimagethmt(imname, imformat, initmodel, mD)
% Inputs:   imname      - name of the image file
%           imformat    - format of the image file (e.g. 'gif')
%           initmodel   - optional. Give an intial model to speed up the training
%                         process. Input '' if not providing initial model.
%           mD          - convergence value
% Output:   model       - the model generated
%           stateprob   - state probabilities

function [model, stateprob] = pdfbtrainimagethmt(imname, imformat, initmodel, mD)

pyrfilter = '9-7';
dirfilter = 'pkva';
levndir = [2 2 3 3];
ns = 2;
zeromean = 'yes';

coef = contourlet(pyrfilter, dirfilter, levndir, imname, imformat);

for dir = 1:2.^levndir(1)
    [tree, scaling] = contourlet2tree(coef, dir);
    if isempty(initmodel)
        [tempmodel, tempstateprob] = pdfbtrainthmt(tree, levndir, mD, ns,zeromean);
    else
        [tempmodel, tempstateprob] = pdfbtrainthmt(tree, levndir, mD,...
            initmodel{dir});
    end;
    model{dir} = tempmodel;
    stateprob{dir} = tempstateprob;
end;


%-------------------------------------------------------------------------------------------------------------------%
%
% IB2d is an Immersed Boundary Code (IB) for solving fully coupled non-linear
% 	fluid-structure interaction models. This version of the code is based off of
%	Peskin's Immersed Boundary Method Paper in Acta Numerica, 2002.
%
% Author: Nicholas A. Battista
% Email:  nickabattista@gmail.com
% Date Created: May 27th, 2015
% Institution: UNC-CH
%
% This code is capable of creating Lagrangian Structures using:
% 	1. Springs
% 	2. Beams (*torsional springs)
% 	3. Target Points
%	4. Muscle-Model (combined Force-Length-Velocity model, "Hill+(Length-Tension)")
%
% One is able to update those Lagrangian Structure parameters, e.g., spring constants, resting lengths, etc
%
% There are a number of built in Examples, mostly used for teaching purposes.
%
% If you would like us to add a specific muscle model, please let Nick (nickabattista@gmail.com) know.
%
%--------------------------------------------------------------------------------------------------------------------%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION: updates the target point positions
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function targets = update_Target_Point_Positions(dt,current_time,targets)

%IDs = targets(:,1);                 % Stores Lag-Pt IDs in col vector
%xPts= targets(:,2);                 % Original x-Values of x-Target Pts.
%yPts= targets(:,3);                 % Original y-Values of y-Target Pts.
%kStiffs = targets(:,4);             % Stores Target Stiffnesses
%N_target = length(targets(:,1));    % Gives total number of target pts!

tR1 = 0.25;                          % Period A->B (and B->A) for RIGHT
tR2 = 0.25;                          % Period B->C (abd C->B) for RIGHT
periodR = (tR1+tR2);                % Time it takes to move to the right

off_Left = 0;%0.10*periodR;             % OFFSET FOR LEFT ARM
tL1 = 0.25;                          % Period A->B (and B->A) for LEFT
tL2 = 0.25;                          % Period B->C (abd C->B) for LEFT
periodL = (tL1+tL2);        % Time it takes to move to the LEFT

tR = rem(current_time,periodR);      % "Time" (moded out) according to right arm

tL = rem(current_time,periodL);      % "Time" (moded out) according to left arm

% Cubic Interpolation Information
p1 = 0.125;
p2 = 0.875;

% a COEFFICIENTS
a0 = 0;
a1 = 0;
a2 = 0;
a3 = 9.1429;

% b COEFFICIENTS
b0 = 0.0238;
b1 = -0.5714;
b2 = 4.5714;
b3 = -3.0476;

% c COEFFICIENTS
c0 = -8.1429;
c1 = 27.4286;
c2 = -27.4286;
c3 = 9.1429;

%
% JELLYFISH STATES:
%   XY(:,1) = 1st X-positions
%   XY(2:150,2) = 2nd X-positions (RIGHT)
%   XY(151:end,2) = 2nd X-positions (LEFT)
%
%   XY(:,3) = 1st Y-positions
%   XY(2:150,4) = 2nd Y-positions (RIGHT)
%   XY(151:end,4) = 2nd Y-positions (LEFT)
%
XY = read_In_Phase_Positions('XY_2Pos.txt');

% Coming from give_Me_Jelly:
NArm = 174;

% 1st and 2nd position of RIGHT arm
xR1 = XY(2:NArm+1,1); yR1 = XY(2:NArm+1,3);
xR2 = XY(2:NArm+1,2); yR2 = XY(2:NArm+1,4);

% 1st and 2nd position of LEFT arm
xL1 = XY(NArm+2:end,1); yL1 = XY(NArm+2:end,3);
xL2 = XY(NArm+2:end,2); yL2 = XY(NArm+2:end,4);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% START THE INTERPOLATING BETWEEN STATES!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%
%
% RIGHT ARM!
%
%%%%%%%%%%%%%

if tR <= tR1+off_Left % STATE A -> STATE B

    % Scaling time for appropriate use in interp. function so tRTilde\in[0,1]
    tRTilde = ( tR / tR1 );

    % Evaluate Piecewise Cubic Interpolation Poly
    if tRTilde<=p1
        gFUNC = a0 + a1*tRTilde + a2*tRTilde^2 + a3*tRTilde^3;
    elseif tRTilde<=p2
        gFUNC = b0 + b1*tRTilde + b2*tRTilde^2 + b3*tRTilde^3;
    else
        gFUNC = c0 + c1*tRTilde + c2*tRTilde^2 + c3*tRTilde^3;
    end

    % MOVING X-POSITIONS
    targets(2:NArm+1,2) = xR1 + gFUNC*( xR2 - xR1 );

    % MOVING Y-POSITIONS
    targets(2:NArm+1,3) = yR1 + gFUNC*( yR2 - yR1 );

elseif tR <= (tR1+tR2) % STATE B -> A

    % Scaling time for appropriate use in interp. function so tRTilde\in[0,1]
    tRTilde = (tR-tR1)/( tR2 );

    % Evaluate Piecewise Cubic Interpolation Poly
    if tRTilde<=p1
        gFUNC = a0 + a1*tRTilde + a2*tRTilde^2 + a3*tRTilde^3;
    elseif tRTilde<=p2
        gFUNC = b0 + b1*tRTilde + b2*tRTilde^2 + b3*tRTilde^3;
    else
        gFUNC = c0 + c1*tRTilde + c2*tRTilde^2 + c3*tRTilde^3;
    end

    % MOVING X-POSITIONS
    targets(2:NArm+1,2) = xR2 + gFUNC*( xR1 - xR2 );

    % MOVING Y-POSITIONS
    targets(2:NArm+1,3) = yR2 + gFUNC*( yR1 - yR2 );

end

%%%%%%%%%%%%%
%
% LEFT ARM!
%
%%%%%%%%%%%%%

if current_time >= off_Left

    if tL <= tL1 % STATE A -> STATE B

        % Scaling time for appropriate use in interp. function so tRTilde\in[0,1]
        tLTilde = (tL/tL1);

        % Evaluate Piecewise Cubic Interpolation Poly
        if tLTilde<=p1
            gFUNC = a0 + a1*tLTilde + a2*tLTilde^2 + a3*tLTilde^3;
        elseif tRTilde<=p2
            gFUNC = b0 + b1*tLTilde + b2*tLTilde^2 + b3*tLTilde^3;
        else
            gFUNC = c0 + c1*tLTilde + c2*tLTilde^2 + c3*tLTilde^3;
        end

        % MOVING X-POSITIONS
        targets(NArm+2:end,2) = xL1 + gFUNC*( xL2 - xL1 );

        % MOVING Y-POSITIONS
        targets(NArm+2:end,3) = yL1 + gFUNC*( yL2 - yL1 );

    elseif tL <= (tL1+tL2) % STATE B -> A

        % Scaling time for appropriate use in interp. function so tRTilde\in[0,1]
        tLTilde = (tL-tL1)/(tL2);

        % Evaluate Piecewise Cubic Interpolation Poly
        if tLTilde<=p1
            gFUNC = a0 + a1*tLTilde + a2*tLTilde^2 + a3*tLTilde^3;
        elseif tLTilde<=p2
            gFUNC = b0 + b1*tLTilde + b2*tLTilde^2 + b3*tLTilde^3;
        else
            gFUNC = c0 + c1*tLTilde + c2*tLTilde^2 + c3*tLTilde^3;
        end

        % MOVING X-POSITIONS
        targets(NArm+2:end,2) = xL2 + gFUNC*( xL1 - xL2 );

        % MOVING Y-POSITIONS
        targets(NArm+2:end,3) = yL2 + gFUNC*( yL1 - yL2 );

    end

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION: Reads in the # of vertex pts and all the vertex pts from the
%           .vertex file.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function PTS = read_In_Phase_Positions(struct_name)

filename = struct_name;  %Name of file to read in
fileID = fopen(filename);

% Read in the file, use 'CollectOutput' to gather all similar data together
% and 'CommentStyle' to to end and be able to skip lines in file.
C = textscan(fileID,'%f %f %f %f','CollectOutput',1);

fclose(fileID);     %Close the data file.

vertices = C{1};    %Stores all read in data in vertices (N+1,2) array

PTS = vertices(1:end,1:4);


function a=recor2(bsol,para)
time=bsol(5)+bsol(6);
kk=trajt(para,bsol);
tt=torque(kk);
ft=ftorque(tt);
ffq=kk(1:2,:);
fq=sum(sum(abs(diff(ffq'))));
pos=forkin(kk(1:2,:));
xx=pos(1,:);yy=pos(2,:);
x=diff(xx);
y=diff(yy);
fdis=sum(sqrt(x.^2+y.^2));
a=[time,fq,fdis,ft];


function [C, grps, G] = grp2cell(X, G, dim)
%GRP2CELL Splits an array into a cell using a grouping variable.
% Usage:
%   C = grp2cell(X, G)
%   C = grp2cell(X, G, dim)
%   [C, grps] = grp2cell(_)
%
% Args:
%   X: array with at least one dimension of the same size as G
%   G: grouping variable vector
%   dim: dimension along which to slice X (default: [])
%        if empty, first dimension of the same size as G is used
%
% Returns:
%   C: cell array with as many cells as unique elements in G
%   grps: values in G that correspond to the grouping in C
%
% Example:
%   >> C = grp2cell(X,G,dim);
%   >> isequal(cellcat(C, dim), X)
%   ans =
%     logical
%      1
%
% See also: arr2cell, cellcat, celljoin

if nargin < 3; dim = []; end
if isscalar(G)
    % Default to first non-singleton dimension
    dim = find(size(X) > 1, 1);
    if isempty(dim); dim = 1; end % or 1

    % Create grouping vector
    G = ceil((1:size(X,dim)) / G);
end

if isempty(dim)
    dim = find(size(X) == numel(G),1);
end

subs = af(@(x) 1:x, size(X));

grps = unique(G);
C = cell1(numel(grps),dim);
for i = 1:numel(grps)
    subs_i = subs;
    subs_i{dim} = find(G == grps(i));

    C{i} = X(subs_i{:});
end

end


classdef LevelSetFactory < handle

    methods (Access = public, Static)

        function obj = create(d)
            switch d.type
                case 'circle'
                    obj = LevelSetCircle(d);
                case 'circleInclusion'
                    obj = LevelSetWithCircleInclusion(d);
                case 'sphere'
                    obj = LevelSetSphere(d);
                case 'sphereInclusion'
                    obj = LevelSetWithSphereInclusion(d);
                case 'cylinder'
                    obj = LevelSetCylinder(d);
                case 'horizontal'
                    obj = LevelSetHorizontalInclusion(d);
                case {'squareInclusion'}
                    obj = LevelSetSquareInclusion(d);
                case 'smoothSquare'
                    obj = LevelSetSmoothSquareInclusion(d);
                case 'rectangle'
                    obj = LevelSetRectangle(d);
                case 'rectangleInclusion'
                    obj = LevelSetRectangleInclusion(d);
                case 'smoothRectangle'
                    obj = LevelSetSmoothRectangleInclusion(d);
                case 'feasible'
                    obj = LevelSetFeasible(d);
                case 'rand'
                    obj = LevelSetRandom(d);
                case 'holes'
                    obj = LevelSetWithSeveralHoles(d);
                case 'full'
                    obj = LevelSetFull(d);
                case 'horizontalFibers'
                    obj = LevelSetHorizontalFibers(d);
                case 'given'
                    obj = LevelSetGiven(d);
                case 'Vigdergauz'
                    obj = LevelSetVigdergauz(d);
                case 'periodicAndOriented'
                    obj = LevelSetPeriodicAndOriented(d);
                otherwise
                    error('Invalid initial value of design variable.');
            end

        end
    end

end


function varargout = process_test_parametric1( varargin )
% PROCESS_TEST_PARAMETRIC1: Parametric one-sample tests (test vs zero).

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, Dimitrios Pantazis, 2015-2016

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Parametric test against zero';
    sProcess.Category    = 'Stat1';
    sProcess.SubGroup    = 'Test';
    sProcess.Index       = 701;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Statistics';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'data',  'results',  'timefreq',  'matrix'};
    sProcess.OutputTypes = {'pdata', 'presults', 'ptimefreq', 'pmatrix'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 2;

    % === GENERIC EXTRACT OPTIONS
    % Label
    sProcess.options.extract_title.Comment    = 'Select data to test:';
    sProcess.options.extract_title.Type       = 'label';
    sProcess.options.extract_title.InputTypes = {'data', 'results', 'timefreq', 'matrix'};
    % Options
    sProcess = process_extract_values('DefineExtractOptions', sProcess);
    % DISABLE ABSOLUTE VALUE
    sProcess.options.isabs.Value = 0;
    sProcess.options.isnorm.Value = 0;
    sProcess.options.isabs.Hidden = 1;
    sProcess.options.isnorm.Hidden = 1;

    % === OUTPUT COMMENT
    sProcess.options.Comment.Comment = 'Comment (empty=default): ';
    sProcess.options.Comment.Type    = 'text';
    sProcess.options.Comment.Value   = '';

    % === TEST: title
    sProcess.options.test_title.Comment    = '
Test statistic:';
    sProcess.options.test_title.Type       = 'label';
    % === TEST: type
    sProcess.options.test_type.Comment = {['One-sample Student''s t-test    X~N(m,s)
' ...
                                           't = mean(X) ./ std(X) .* sqrt(n)      df=n-1'], ...
                                          ['One-sample Chi2 test     Zi~N(0,1), i=1..n
' ...
                                           'Q = sum(|Zi|^2)     Q~Chi2(n)'], ...
                                          ['One-sample Chi2 test (unconstrained sources)     Zix,Ziy,Ziz~N(0,1), i=1..n
' ...
                                           'Q = sum(|Zix|^2 + |Ziy|^2 + |Ziz|^2)     Q~Chi2(3*n)']; ...
                                          'ttest_onesample', 'chi2_onesample', 'chi2_onesample_unconstr'};
    sProcess.options.test_type.Type    = 'radio_label';
    sProcess.options.test_type.Value   = 'ttest_onesample';
    % === TAIL FOR THE TEST STATISTIC
    sProcess.options.tail.Comment  = {'One-tailed (-)', 'Two-tailed', 'One-tailed (+)', ''; ...
                                      'one-', 'two', 'one+', ''};
    sProcess.options.tail.Type     = 'radio_linelabel';
    sProcess.options.tail.Value    = 'two';
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    Comment = process_test_parametric2('FormatComment', sProcess);
end

%% ===== RUN =====
function sOutput = Run(sProcess, sInputs) %#ok<DEFNU>
    sOutput = process_test_parametric2('Run', sProcess, sInputs, []);
end


function len = length(f)
%LENGTH   Length of a SINGFUN.
%   LENGTH(F) is the length of the smooth part contained in F.
%
% See also SIZE.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

% The length of a SINGFUN object is the length of its smooth part:
len = length(f.smoothPart);

end


function outn=gt(x,y)

precAndSize

for ii=1:max(ex,ey)
 imag=false;
 if ex==1
  [xrval,xival]=getVals(x,1);
  [yrval,yival]=getVals(y,ii);
 elseif ey==1
  [xrval,xival]=getVals(x,ii);
  [yrval,yival]=getVals(y,1);
 else
  [xrval,xival]=getVals(x,ii);
  [yrval,yival]=getVals(y,ii);
 end
  outn(ii)=mpfr_gt(precision,xrval,yrval);
  if outn(ii)~=0, outn(ii)=1; end
end % for ii=1:max(ex,


function varargout = tsvread( varargin )
%[data, header, raw] = tsvread( file ) reads in text file with tab-seperated variables. default value for data is nan.
%alternative input/output option is suppluying header strings
%[col1, col2, col3, ..., header, raw] = tsvread( file, header1, header2, header3, ... )
%header is the first row (assumed to have header names) and raw is the imported text
%if a vector is supplied, this specifies the number rows to be imported.
%examples:
%[col1, col2,col3,header,raw] = tsvread( 'example.tsv', 'header1', 'header2', 'header3', 1:5 )
%will import data from example.tsv, and cols corresponding to header1,
%header2, header3, cols 1 t0 5.
%if no outputs are requested, then a portion of the rquested table is
%displayed -- good idea to see how the import and header requests are
%working!
%If there is a tsv file in local directory, then just running "tsvread" at
%the command line will read the newest tsv file and display first ten rows to screen.
%sak 9/2/11
if nargin == 0
    disp( 'importing most recent tsv file in local directory' );
    d = dir( '*.tsv' );
    [~,i] = sort( [d.datenum], 'descend' );
    fprintf( 'tsvread( ''%s'', 1:10 )\n', d(i(1)).name );
    eval( sprintf( 'tsvread( ''%s'', 1:10 )', d(i(1)).name ) );
    return;
end;

fid = fopen( varargin{1}, 'r' );
if nargout == 0
    fprintf( 'loading %s, and displaying sideways\n', varargin{1} );
end
varargin(1) = [];
stuff = textscan( fid, '%s', 'delimiter', '\n');
stuff = stuff{1};
fclose(fid);

numrows = 1:size(stuff,1);
ind = cellfun( 'isclass', varargin, 'double' );
if any( ind )
    numrows = varargin{ind};
    varargin(ind) = [];
    if numel(numrows) == 1
        numrows = 1:min(numrows, size( stuff, 1) );
    end
end
numrows = intersect( numrows, 1:size( stuff, 1 ) );
header = regexprep( regexp( stuff{1}, '[^\t]*\t', 'match' ), '\t', '' );
raw = repmat( {}, numel(numrows), numel(header) );
for i=numrows
    stuff{i}(end+1) = 9;
    tmp = regexprep( regexp( stuff{i}, '[^\t]*\t', 'match' ), '\t', '');
    raw(i,1:numel(tmp)) = tmp;
end
header = raw(1,:);
data = nan(size(raw));
for i=numrows
    for j=1:size( raw, 2 )
        if ~isempty( raw{i,j} )
            [a, count, errmsg] = sscanf( raw{i,j}, '%f' );
            if ~isempty( a )
                data(i,j) = a;
            end
        end
    end
end
%%
if numel( varargin ) == 0
    j=1:size(data, 2);
else
    j = [];
    for i=1:numel(varargin)
        j = [j, find( strncmp( header, varargin{i}, numel(varargin{i})) )];
    end
end
if nargout == 0
    disp( [ strvcat( header(j)), num2str( data(numrows,j)' ) ] );
    return;
end
if numel(varargin)==0
    varargout = {data, header, raw};
    varargout = varargout(1:nargout);
    return;
end
varargout = {};
for i=j
    varargout{end+1} = data( :, i );
end
varargout{end+1} = header(:,j);
varargout{end+1} = raw(:,j);


function mask = inplaneOverlayMask(view, img, hImg);
%
% mask = inplaneOverlayMask(view, img, <hImg>);
%
% Compute a binary mask indicating which pixels in the current
% overlay image for a view exceed the thresholds set by the UI.
%
% img is a data matrix containing the overlay data.
%
% hImg is an optional handle to an overlay image. If this is passed,
% will update the image's Alpha map such that the pixels exceeding
% threshold are opaque and everything else is transparent.
%
% ras, 05/2006.
if nargin<1, view = getCurView; end

if ~exist('img', 'var') | isempty(img),
    if checkfields(view, 'ui', 'image')
        img = view.ui.image;
    else
        error('Need an overlay data argument (img).')
    end
end

% Select pixels that satisfy cothresh, phWindow, and mapWindow
cothresh = getCothresh(view);
phWindow = getPhWindow(view);
mapWindow = getMapWindow(view);

pts = [];
% if ~isempty(img)
%     pts = ones(size(img));
%     curCo = cropCurSlice(view,'co',slice);
%     curPh = cropCurSlice(view,'ph',slice);
%     curMap = cropCurSlice(view,'map',slice);
%
%     if ~isempty(curCo) & cothresh>0
%         ptsCo = curCo > cothresh;
%         pts = pts & ptsCo;
%     end
%
%     if ~isempty(curPh)
%         if diff(phWindow) > 0
%             ptsPh = (curPh>=phWindow(1) & curPh<=phWindow(2));
%         else
%             ptsPh = (curPh>=phWindow(1) | curPh<=phWindow(2));
%         end
%         pts = pts & ptsPh;
%     end
%
%     if ~isempty(curMap)
%         ptsMap = (curMap>=mapWindow(1) & curMap<=mapWindow(2));
%         pts = pts & ptsMap;
%     end
% end

switch view.ui.displayMode
    case 'anat', mask = logical(zeros(size(img)));
    case 'map',
        mask = (img >= mapWindow(1)) & (img <= mapWindow(2));
    case 'amp',
        % tough ...
        mask = (img >= mapWindow(1)) & (img <= mapWindow(2));
    case 'ph',
        mask = (img >= mapWindow(1)) & (img <= mapWindow(2));
    case 'co',
        mask = (img >= mapWindow(1)) & (img <= mapWindow(2));
end

return


classdef prtRegressRvm < prtRegress
    % prtRegressRvm  Relevance vector machine regression object
    %
    %   REGRESS = prtRegressRvm returns a prtRegressRvm object
    %
    %    REGRESS = prtRegressRVM(PROPERTY1, VALUE1, ...) constructs a
    %    prtRegressRvm object REGRESS with properties as specified by
    %    PROPERTY/VALUE pairs.
    %
    %    A prtRegressRvm object inherits all properties from the prtRegress
    %    class. In addition, it has the following properties:
    %
    %   SetAccess = public:
    %    kernels            - A cell array of prtKernel objects specifying
    %                         the kernels to use
    %    verbosePlot        - Flag indicating whether or not to plot during
    %                         training
    %    verboseText        - Flag indicating whether or not to display
    %                         a message during training
    %
    %   SetAccess = private/protected:
    %    learningConverged  - Flag indicating if the training converged
    %    learningResults    - Struct with information about the convergence
    %    beta               - The weights on each of the kernel elements;
    %                         learned during training
    %    Sigma              - The learned covariance
    %    sparseBeta         - The weights on the retained kernel elements;
    %                         learned durning training
    %    sparseKernels      - The retained kernels
    %
    %   This code is based on:
    %       Michael E Tipping, Sparse bayesian learning and the relevance
    %   vector machine, The Journal of Machine Learning Research, Vol 1.
    %
    %   Also see http://en.wikipedia.org/wiki/Relevance_vector_machine
    %
    %   A prtRegressionRvm object inherits the PLOT method from the
    %   prtRegress object, and the TRAIN, RUN, CROSSVALIDATE and KFOLDS
    %   methods from the prtAction object.
    %
    %   Example:
    %
    %   dataSet = prtDataGenNoisySinc;           % Load a prtDataRegress
    %   dataSet.plot;                    % Display data
    %   reg = prtRegressRvm;             % Create a prtRegressRvm object
    %   reg = reg.train(dataSet);        % Train the prtRegressRvm object
    %   reg.plot();                      % Plot the resulting curve
    %   dataSetOut = reg.run(dataSet);   % Run the regressor on the data
    %   hold on;
    %   plot(dataSet.getX,dataSetOut.getX,'c.') % Plot, overlaying the
    %                                           % fitted points with the
    %                                           % curve and original data
    %   legend('Regression curve','Original Points','Kernel Locations Used',0)
    %
    %
    %   See also prtRegress, prtRegressGP, prtRegressLslr

    properties (SetAccess=private)
        name = 'Relevance Vector Machine'  % Relevance Vector Machine
        nameAbbreviation = 'RVM'           % RVM
    end

    properties
        kernels = prtKernelDc & prtKernelRbfNdimensionScale;

        verbosePlot = false;   % Whether or not to plot during training
        verboseText = false;   % Whether or not to plot during training
    end

    properties (Hidden = true)
        learningMaxIterations = 1000;  % Maximum number of iteratoins
        learningConvergedTolerance = 1e-6;
        learningRelevantTolerance = 1e-3;
    end
    properties (SetAccess = 'protected',GetAccess = 'public')
        learningConverged = [];% Whether or not the training converged

        beta = [];      % Estimated in training
        Sigma = [];     % Estimated in training
        sigma2 = [];    % Estimated in training

        sparseBeta = [];% Estimated in training
        sparseKernels = {};% Estimated in training
    end
    methods
         % Allow for string, value pairs
        function Obj = prtRegressRvm(varargin)
            Obj = prtUtilAssignStringValuePairs(Obj,varargin{:});
        end

        function Obj = set.kernels(Obj,val)
            if ~isa(val,'prtKernel')
                error('prt:prtRegressRvm:kernels','kernels must be a prtKernel');
            end

            Obj.kernels = val;
        end

        function Obj = set.verbosePlot(Obj,val)
            if ~prtUtilIsLogicalScalar(val)
                error('prt:prtRegressRvm:verbosePlot','verbosePlot must be a logical value or a positive integer');
            end
            Obj.verbosePlot = val;
        end

        function Obj = set.verboseText(Obj,val)
            if ~prtUtilIsLogicalScalar(val)
                error('prt:prtRegressRvm:verboseText','verboseText must be a logical value or a positive integer');
            end
            Obj.verboseText = val;
        end
    end

    methods (Access = protected, Hidden = true)

        function Obj = trainAction(Obj,DataSet)
            %Rvm = trainAction(Rvm,DataSet) (Private; see prtClass\train)
            %   Implements Jefferey's prior based training of a relevance
            %   vector machine.  The Rvm output from this function contains
            %   fields "sparseBeta" and "sparseKernels"

            warningState = warning;

            if DataSet.nTargetDimensions ~= 1
                error('prt:prtRegressRvm:tooManyTargets','prtRegressRvm can only operate on single target data.');
            end

            y = DataSet.getTargets(1:DataSet.nObservations,1);

            localKernels = Obj.kernels.train(DataSet);
            gram = localKernels.run_OutputDoubleArray(DataSet);
            nBasis = size(gram,2);

            if Obj.verboseText
                fprintf('RVM training with %d possible vectors.\n', nBasis);
            end

            Obj.beta = zeros(nBasis,1);

            relevantIndices = true(nBasis,1); % Everybody!

            alpha = ones(nBasis,1); % Initialize

            Obj.sigma2 = var(y); % A descent guess to start with

            for iteration = 1:Obj.learningMaxIterations
                % Given currenet relevant stuff find the weight mean and
                % covariance
                if ~any(relevantIndices)
                    break;
                end
                cPhi = gram(:,relevantIndices);
                A = diag(alpha(relevantIndices));

                sigma2Inv = (Obj.sigma2^-1);

                SigmaInv = A + sigma2Inv*(cPhi'*cPhi);
                Obj.Sigma = inv(SigmaInv);
                mu = sigma2Inv*(SigmaInv\(cPhi'*y));

                % Find the current prediction
                yHat = cPhi*mu;

                % Update A
                logAlphaOld = log(alpha(relevantIndices));

                cG = 1 - alpha(relevantIndices).*diag(Obj.Sigma);
                alpha(relevantIndices) = cG./(mu.^2);

                % Update sigma2
                Obj.sigma2 = norm(y-yHat)./(length(yHat) - sum(cG));

                Obj.beta = zeros(nBasis,1);
                Obj.beta(relevantIndices) = mu;

                %check tolerance for basis removal
                TOL = abs(log(alpha(relevantIndices))-logAlphaOld);
                TOL(isnan(TOL)) = 0; % inf-inf = nan

                if Obj.verboseText
                    fprintf('\t Iteration %d: %d RV''s, Convergence tolerance: %g \n',iteration, sum(relevantIndices), max(TOL));
                end

                if all(TOL < Obj.learningConvergedTolerance) && iteration > 1
                    Obj.learningConverged = true;

                    if Obj.verboseText
                        fprintf('Convergence reached. Exiting...\n\n');
                    end

                    break;
                end
                % We didn't break so we can contiue on
                % Select relevant stuff
                newRelevantIndices = alpha < 1./Obj.learningRelevantTolerance;

                if ~mod(iteration,Obj.verbosePlot)
                    if DataSet.nFeatures == 1
                        Obj.verboseIterationPlot(DataSet,relevantIndices);
                    elseif iteration == 1
                        warning('prt:prtRegressRvm','Learning iteration plot can only be produced for training Datasets with 1 feature.');
                    end
                end

                relevantIndices = newRelevantIndices;
            end

            if Obj.verboseText && iteration == Obj.learningMaxIterations
                fprintf('Exiting...Convergence not reached before the maximum allowed iterations was reached.\n\n');
            end

            % Make sparse represenation
            Obj.sparseBeta = Obj.beta(relevantIndices,1);
            Obj.sparseKernels = localKernels.retainKernelDimensions(relevantIndices);

            % Very bad training
            if isempty(Obj.sparseBeta)
                warning('prt:prtClassRvm:NoRelevantFeatures','No relevant features were found during training.');
            end

            % Reset warning
            warning(warningState);

        end

        function DataSetOut = runAction(Obj,DataSet)

            if isempty(Obj.sparseBeta)
                DataSetOut = DataSet.setObservations(zeros(DataSet.nObservations,Obj.dataSetSummary.nTargetDimensions));
                return
            end

            memChunkSize = 1000; % Should this be moved somewhere?
            n = DataSet.nObservations;

            DataSetOut = prtDataSetRegress(zeros(n,1));
            for i = 1:memChunkSize:n;
                cI = i:min(i+memChunkSize,n);
                cDataSet = prtDataSetRegress(DataSet.X(cI,:));
                gram = Obj.sparseKernels.run_OutputDoubleArray(cDataSet);

                DataSetOut = DataSetOut.setObservations(gram*Obj.sparseBeta, cI);
            end
        end
    end

    methods
        function varargout = plot(Obj)

            HandleStructure = plot@prtRegress(Obj);

            holdState = get(gca,'nextPlot');

            % Plot the kernels
            hold on
            %This only plots the x-coordinate... which is weird, but it's
            %all we can do right now b/c kernels don't know about
            %targets...
            Obj.sparseKernels.plot();
            set(gca, 'nextPlot', holdState);

            varargout = {};
            if nargout > 0
                varargout = {HandleStructure};
            end
        end
    end

    methods (Access=protected,Hidden=true)
        function Obj = verboseIterationPlot(Obj,DataSet,relevantIndices)
            DsSummary = DataSet.summarize;

            [linGrid, gridSize] = prtPlotUtilGenerateGrid(DsSummary.lowerBounds, DsSummary.upperBounds, Obj.plotOptions);

            trainedKernel = train(Obj.kernels, DataSet);
            trainedKernel = trainedKernel.retainKernelDimensions(relevantIndices);
            cPhi = trainedKernel.run_OutputDoubleArray(prtDataSetClass(linGrid));

            yHat = reshape(cPhi*Obj.beta(relevantIndices),gridSize);

            colors = Obj.plotOptions.colorsFunction(Obj.dataSetSummary.nTargetDimensions);
            lineWidth = Obj.plotOptions.lineWidth;
            plot(linGrid,yHat,'color',colors(1,:),'lineWidth',lineWidth);
            hold on
            plot(DataSet);
            plot(trainedKernel);
            hold off
            drawnow;
        end
    end
end


%% MEEG dataset operations
%
% This example shows MVPA analyses performed on MEEG data.
%
% The input dataset involved a paradigm with electrical median nerve
% stimulation for durations of 2s at 20Hz.
%
%
% The code presented here can be adapted for other MEEG analyses, but
% there are a few potential caveats:
% * assignment of targets (labels of conditions) is based here on
%   stimulation periods versus pre-stimulation periods. In typical
%   analyses the targets should be based on different trial conditions, for
%   example as set a FieldTrip .trialinfo field.
% * assignment of chunks (parts of the data that are assumed to be
%   independent) is done randomly. In typical analyses they can be based on
%   different runs, with one chunk value per run.
% * the time window used for analyses is rather small. This means that in
%   particular for time-freq analysis a lot of data is missing, especially
%   for early and late timepoints in the lower frequency bands. For typical
%   analyses it may be preferred to use a wider time window.
% * the current examples do not perform baseline corrections or signal
%   normalizations, which may reduce discriminatory power.
%
% Note: running this code requires FieldTrip.
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

%% get timelock data in CoSMoMVPA format

% set configuration
config=cosmo_config();
data_path=fullfile(config.tutorial_data_path,'meg_20hz');

% show dataset information
readme_fn=fullfile(data_path,'README');
cosmo_type(readme_fn);

% reset citation list
cosmo_check_external('-tic');

% load data
data_fn=fullfile(data_path,'subj102_B01_20Hz_timelock.mat');
data_tl=load(data_fn);

% convert to cosmomvpa struct
ds_tl=cosmo_meeg_dataset(data_tl);

% set the target (trial condition)
ds_tl.sa.targets=ds_tl.sa.trialinfo(:,1); % 1=pre, 2=post

% in addition give a label to each trial
index2label={'pre','post'}; % 1=pre, 2=peri/post
ds_tl.sa.labels=cellfun(@(x)index2label(x),num2cell(ds_tl.sa.targets));

% just to check everything is ok
cosmo_check_dataset(ds_tl);

%% Print some information about dataset

% print the size. Note that the number of features is the number of time
% points times the number of channels.
[ns,nf]=size(ds_tl.samples);
fprintf('There are %d samples and %d features\n', ns, nf);

fdim_labels=ds_tl.a.fdim.labels;
fdim_values=ds_tl.a.fdim.values;
ndim=numel(fdim_labels);
for dim=1:ndim
    dim_label=fdim_labels{dim};
    nvalues=numel(fdim_values{dim});
    fprintf('Dimension %d (%s) has %d values\n', dim, dim_label, nvalues);
end

%% Simple data manipulations
% select five trials from post-stim period
post_mask=cosmo_match(ds_tl.sa.labels,'post');
ds_tl_post=cosmo_slice(ds_tl,post_mask);
select_trials=2:2:18;

% the last argument indicates to slice along the first dimension.
% The value 1 is the default and can be omitted
ds_trial=cosmo_slice(ds_tl_post, select_trials, 1);

% select a single channel (multiple channels are also possible but
% makes plotting the samples directly a bit more tricky)
chan={'MEG1042'};
chan_msk=cosmo_dim_match(ds_trial,'chan', chan);

% slice again, this time along the feature dimension (dimension 2),
% to get a new dataset with just one channel
ds_trial_chan=cosmo_slice(ds_trial, chan_msk, 2);

% plot five individual trials
figure();
subplot(2,1,1);
plot(data_tl.time, ds_trial_chan.samples');
title(sprintf('%d trials of sensor %s', numel(select_trials), chan{1}));

% use original full data to select data in just one channel
chan_msk=cosmo_dim_match(ds_tl,'chan', chan);
ds_chan=cosmo_slice(ds_tl, chan_msk, 2);

ds_mean=cosmo_fx(ds_chan,@(x)mean(x,1),'targets');
% plot signal time course averaged over all samples
% note: the 'pre' data has been shifted in time to match the 'post' data
subplot(2,1,2);
[time_dim, time_index]=cosmo_dim_find(ds_mean,'time');
assert(time_dim==2); % it is a feature dimension
plot(ds_mean.a.fdim.values{time_index},ds_mean.samples);
title(sprintf('average of sensor %s', chan{1}));
legend(ds_mean.sa.labels);

% Show citation information
cosmo_check_external('-cite');


function [noise_samp, n_noise_samp, noise_seg, D, Dsmth, snre_vad, e]=snre_highenergy(dfdata, nfr10, flen, fsh10, ENERGYFLOOR, pv01)

% Ref:
%   Z-H Tan and B Lindberg
%   Low-Complexity Variable Frame Rate Analysis for Speech Recognition and Voice Activity Detection
%   IEEE Journal of Selected Topics in Signal Processing, 4(5), Oct. 2010.

% square root of a posteriori SNR weighted engergy difference, block based
%
% Modified 01 Mar 2013

Dexpl=18;
Dexpr=18;
segThres = 0.25;
e=zeros(nfr10,1);
for i=1:nfr10
    for j=1:flen
        e(i)=e(i)+dfdata((i-1)*fsh10+j)*dfdata((i-1)*fsh10+j);
    end
    if e(i) <= ENERGYFLOOR
        e(i)=ENERGYFLOOR;
    end
end

emin=ones(nfr10,1);
NESEG = 200;
if nfr10 < NESEG; NESEG=nfr10; end
for i=1:floor(nfr10/NESEG)
	[eY,eI]=sort(e((i-1)*NESEG+1:i*NESEG));
	emin((i-1)*NESEG+1:i*NESEG)=eY(floor(NESEG*0.1));
    if i~=1
        emin((i-1)*NESEG+1:i*NESEG)=0.9*emin((i-1)*NESEG)+0.1*emin((i-1)*NESEG+1);
    end
end
if i*NESEG~=nfr10
    [eY,eI]=sort(e((i-1)*NESEG+1:nfr10));
    emin(i*NESEG+1:nfr10)=eY(floor((nfr10-(i-1)*NESEG)*0.1));
    emin(i*NESEG+1:nfr10)=0.9*emin(i*NESEG)+0.1*emin(i*NESEG+1);
end

D=zeros(nfr10,1);
postsnr=zeros(nfr10,1);
for i=2:nfr10
    postsnr(i) =log10(e(i))-log10(emin(i));
    if postsnr(i)<0
        postsnr(i)=0;
    end
    D(i)=sqrt(abs(e(i)-e(i-1))*postsnr(i));
end
D(1)=D(2);

Dexp = vertcat(ones(Dexpl,1)*D(1), D, ones(Dexpr,1)*D(nfr10));
Dsmth = zeros(nfr10,1);
for i=1:nfr10
    Dsmth(i)=sum(Dexp(i:i+Dexpl+Dexpr));
end

for i=1:floor(nfr10/NESEG)
    Dsmth_max((i-1)*NESEG+1:i*NESEG)=max(e((i-1)*NESEG+1:i*NESEG));
end
if i*NESEG~=nfr10
    Dsmth_max(i*NESEG+1:nfr10)=max(e((i-1)*NESEG+1:nfr10));
end

snre_vad = zeros(nfr10,1);
for i=1:nfr10
   if Dsmth(i)>Dsmth_max(i)*segThres; snre_vad(i)=1; end
end

% block based processing to remove noise part by using snre_vad1.
sign_vad = 0;
noise_seg=zeros(floor(nfr10/1.6),1);
noise_samp=zeros(nfr10,2);
n_noise_samp=0;
for i=1:nfr10
    if snre_vad(i) == 1 && sign_vad == 0 % start of a segment
        sign_vad = 1;
        nstart=i;
    elseif (snre_vad(i) ==0 || i==nfr10) && sign_vad == 1 % end of a segment
        sign_vad = 0;
        nstop=i-1;
        if sum(pv01(nstart:nstop))==0
            noise_seg(round(nstart/1.6):floor(nstop/1.6)) = 1;
            n_noise_samp=n_noise_samp+1;
            noise_samp(n_noise_samp,:)=[(nstart-1)*fsh10+1 nstop*fsh10];
        end
    end
end
noise_samp(n_noise_samp+1:nfr10,:)=[];


% this code is revised based on ILSVRC 2013 (http://www.image-net.org/challenges/LSVRC/2013/)

function  top_recall  = top_recall_Relationship(Nre, tuple_confs_cell, tuple_labels_cell, sub_bboxes_cell, obj_bboxes_cell)

load('gt.mat','gt_tuple_label','gt_obj_bboxes','gt_sub_bboxes');

%num_imgs = length(gt_tuple_label);
num_imgs = 1000;
for i=1:num_imgs
    [tuple_confs_cell{i}, ind] = sort(tuple_confs_cell{i},'descend');
    if length(ind) >= Nre
        tuple_confs_cell{i} = tuple_confs_cell{i}(1:Nre);
        tuple_labels_cell{i} = tuple_labels_cell{i}(ind(1:Nre),:);
        obj_bboxes_cell{i} = obj_bboxes_cell{i}(ind(1:Nre),:);
        sub_bboxes_cell{i} = sub_bboxes_cell{i}(ind(1:Nre),:);
    else
        tuple_labels_cell{i} = tuple_labels_cell{i}(ind,:);
        obj_bboxes_cell{i} = obj_bboxes_cell{i}(ind,:);
        sub_bboxes_cell{i} = sub_bboxes_cell{i}(ind,:);
    end
end

num_pos_tuple = 0;
for ii = 1 : num_imgs
    num_pos_tuple = num_pos_tuple + size(gt_tuple_label{ii},1);
end

tp_cell = cell(1,num_imgs);
fp_cell = cell(1,num_imgs);

gt_thr = 0.5;
iterc = 0;

% iterate over images
for i=1:num_imgs

    gt_tupLabel = gt_tuple_label{i};
    gt_objBox = gt_obj_bboxes{i};
    gt_subBox = gt_sub_bboxes{i};

    num_gt_tuple = size(gt_tupLabel,1);
    gt_detected = zeros(1,num_gt_tuple);

    labels = tuple_labels_cell{i};
    boxObj = obj_bboxes_cell{i};
    boxSub = sub_bboxes_cell{i};

    num_obj = size(labels,1);
    tp = zeros(1,num_obj);
    fp = zeros(1,num_obj);
    for j=1:num_obj

        bbO = boxObj(j,:);
        bbS = boxSub(j,:);
        ovmax = -inf;
        kmax = -1;

        for k=1:num_gt_tuple
            if norm(labels(j,:) - gt_tupLabel(k,:),2) ~= 0
                continue;
            end
            if gt_detected(k) > 0
                continue;
            end

            bbgtO = gt_objBox(k,:);
            bbgtS = gt_subBox(k,:);

            biO=[max(bbO(1),bbgtO(1)) ; max(bbO(2),bbgtO(2)) ; min(bbO(3),bbgtO(3)) ; min(bbO(4),bbgtO(4))];
            iwO=biO(3)-biO(1)+1;
            ihO=biO(4)-biO(2)+1;

            biS=[max(bbS(1),bbgtS(1)) ; max(bbS(2),bbgtS(2)) ; min(bbS(3),bbgtS(3)) ; min(bbS(4),bbgtS(4))];
            iwS=biS(3)-biS(1)+1;
            ihS=biS(4)-biS(2)+1;

            if iwO>0 & ihO>0 &  iwS>0 & ihS>0
                % compute overlap as area of intersection / area of union
                uaO=(bbO(3)-bbO(1)+1)*(bbO(4)-bbO(2)+1)+...
                   (bbgtO(3)-bbgtO(1)+1)*(bbgtO(4)-bbgtO(2)+1)-...
                   iwO*ihO;
                ovO =iwO*ihO/uaO;

                uaS=(bbS(3)-bbS(1)+1)*(bbS(4)-bbS(2)+1)+...
                   (bbgtS(3)-bbgtS(1)+1)*(bbgtS(4)-bbgtS(2)+1)-...
                   iwS*ihS;
                ovS =iwS*ihS/uaS;
                ov = min([ovO,ovS]);

                % makes sure that this object is detected according
                % to its individual threshold
                if ov >= gt_thr && ov > ovmax
                    ovmax=ov;
                    kmax=k;
                end
            end
        end

        if kmax > 0
            tp(j) = 1;
            gt_detected(kmax) = 1;

        else
            fp(j) = 1;
        end
    end

    % put back into global vector
    tp_cell{i} = tp;
    fp_cell{i} = fp;

end
t = tic;
tp_all = [];
fp_all = [];
confs = [];
for ii = 1 : num_imgs
tp_all = [tp_all; tp_cell{ii}(:) ];
fp_all = [fp_all; fp_cell{ii}(:) ];
confs = [confs; tuple_confs_cell{ii}(:)];
end

[confs, ind] = sort(confs,'descend');
tp_all = tp_all(ind);
fp_all = fp_all(ind);

tp = cumsum(tp_all );
fp = cumsum(fp_all );
recall =(tp/num_pos_tuple);
top_recall = recall(end);

end