% NCUGRID Extention of dataset object class for unstructured grid datasets.
% Usage:
%            >> dataset = ncugrid(uri);
%
% NCTOOLBOX (http://code.google.com/p/nctoolbox)
classdef ncugrid < handle

    properties (SetAccess = private)
        dataset % ncdataset obj (may not even be needed) since I am reproducing essential functions in
        % this ncugrid class...
        netcdfugrid % Kyle's netcdf u grid dataset
        numberofgrids % total number of meshes
        meshes % list of mesh objs
        cells % number of cells
        nodes % number of nodes
        edges % number of edges
        faces % number of faces
        variables % list of variable names
    end

    methods

        function obj = ncugrid(nc)
            echo off
            %             import java.io.IOException;
            import java.util.Formatter;
            %             import java.util.List;
            % %             import ucar.*
            %             import ucar.nc2.Variable;
            import ucar.nc2.constants.FeatureType;
            %             import ucar.nc2.dt.UGridDataset;
            %             import ucar.nc2.dt.ugrid.Cell;
            %             import ucar.nc2.dt.ugrid.Entity;
            %             import ucar.nc2.dt.ugrid.Mesh;
            % %             import ucar.nc2.dt.ugrid.topology.*;
            %             import ucar.nc2.dt.ugrid.geom.LatLonPoint2D;
            import ucar.nc2.ft.FeatureDatasetFactoryManager;
            import ucar.nc2.util.CancelTask;
            %             import ucar.unidata.geoloc.LatLonPoint;
            %             import ucar.unidata.geoloc.LatLonPointImpl;
            %             import ucar.unidata.geoloc.LatLonRect;
            import java.io.IOException;
            import java.util.ArrayList;
            import java.util.List;
            import ucar.ma2.Array;
            import ucar.ma2.DataType;
            import ucar.nc2.Attribute;
            import ucar.nc2.Dimension;
            import ucar.nc2.Variable;
            import ucar.nc2.dataset.CoordinateSystem;
            import ucar.nc2.dataset.NetcdfDataset;
            import ucar.nc2.dataset.VariableDS;
            import ucar.nc2.dt.ugrid.Cell;
            import ucar.nc2.dt.ugrid.Edge;
            import ucar.nc2.dt.ugrid.Face;
            import ucar.nc2.dt.ugrid.Node;
            import ucar.nc2.dt.ugrid.UGridDataset;
            import ucar.nc2.dt.ugrid.geom.LatLonPoint2D;

            if ischar(nc)
                try
                    obj.dataset = ncdataset(nc);  % src is a string URL/File
                catch % if it doesnt work maybe try dods instead of http
                    nc(1:4) = 'dods';
                    obj.dataset = ncdataset(nc);  % src is a string URL/File
                end
                cancelTask = [];
                %                 try
                obj.netcdfugrid = FeatureDatasetFactoryManager.open(FeatureType.UGRID, nc, cancelTask, Formatter());
                %                 catch me
                %                     me.throw
                %                 end

            elseif isa(nc, 'ncdataset')
                obj.dataset = nc;             % src is a ncdataset
                cancelTask = [];
                obj.netcdfugrid = FeatureDatasetFactoryManager.open(FeatureType.UGRID, nc.link, cancelTask, Formatter());
            elseif isa(nc,'ucar.nc2.dt.ugrid.UGridDataset')
                obj.dataset = nc;             % src is a raw ugriddataset in memory
                cancelTask = [];
                obj.netcdfugrid = FeatureDatasetFactoryManager.open(FeatureType.UGRID, nc, cancelTask, Formatter());
            else
                ex = MException('NCUGRID:ncugrid', 'Invalid dataset was specified');
                ex.throw;
            end

            %set properties
            u = obj.netcdfugrid;

%             vars = u.getMeshVariables();
            vars = obj.dataset.variables;
%             n = size(vars);
%             obj.variables = cell(n, 1);
%             for i = 1:(n)
%                 obj.variables{i, 1} = char(vars.get(i - 1).getName());
%             end
           obj.variables = vars;

            meshsets = u.getMeshsets();
            %       UGridDataset.getMeshsets().get(0).getMesh()
            obj.meshes = meshsets;
            obj.numberofgrids = meshsets.size;
            for i = 1:(obj.numberofgrids)
                obj.cells(i, 1) = meshsets.get(i - 1).getMesh.getSize();
                obj.nodes(i, 1) = meshsets.get(i - 1).getMesh.getNodeSize();
                obj.edges(i, 1) = meshsets.get(i - 1).getMesh.getEdgeSize();
                obj.faces(i, 1) = meshsets.get(i - 1).getMesh.getFaceSize();
            end

        end % end constructor

        function uvar = uvariable(obj, varName)
            % NCUGRID.UVARIABLE - Instantiates a ugrid variable object from the variable 'varName'.
            % Usage:
            %             uvar_obj = nc.uvariable(varName);
            %
            uvar = ncuvariable(obj, varName);
        end % uvariable end

        function ss = variableSubset(obj, varName, struct) % input subset structure
                    % NCUGRID.unstructuredLatLonSubset  - Function to subset an unstructured model grid by lat/lon
                    % bounding box using subsetting methods in the ugrid-java.
                    %
                    % Usage:
                    %              d = nc.unstructuredLatLonSubset(variableName, subsetstructure)
                    %
                    % Subset-structure:
                    %              subsetstructure.lat = [minlat maxlat];
                    %              subsetstructure.lon = [minlon maxlon];
                    %
                    import ucar.nc2.dt.ugrid.geom.LatLonPolygon2D;
                    import ucar.nc2.dt.ugrid.geom.LatLonRectangle2D;
                    import ucar.unidata.geoloc.LatLonPoint;
                    import ucar.unidata.geoloc.LatLonPointImpl;
                    import ucar.unidata.geoloc.LatLonRect;
%                     if nargin ==3
%                         struct = varargin{2};
%                         varName = varargin{1};
%                         % Find the mesh that contains the variable of interest.
%                         mesh_ident = obj.attribute('mesh', varName);
%                         mesh_vars = regexp(mesh_ident, ' ', 'split');
%                         mesh_ind = str2double(mesh_vars{1}(5:end));
%                     elseif nargin == 2
%                         struct = varargin{1};
%                     end
                    % Need to construct lat/lon rect:
                    maxlat  = max(struct.lat);
                    minlon = min(struct.lon);
                    minlat = min(struct.lat);
                    maxlon = max(struct.lon);
                    bbox = LatLonRect(LatLonPointImpl(maxlat, minlon), LatLonPointImpl(minlat, maxlon));

%                     if nargin == 3
%                         % Get all the meshes, access the correct mesh for the variable, then call subset method on mesh.
%                         meshes = obj.meshes;
%                         mesh = meshes.get(mesh_ind-1).getMesh();
%                         if mesh.getTreeSize() == 0
%                             mesh.buildRTree();
%                         end
%         %                 v = obj.netcdfugrid.getMeshVariableByName(varName);
%         %                 subsat = v.subsetToDataset(bbox); % Subsat is ugriddataset
%                         v = obj.netcdfugrid;
%                         subsat = v.subset(bbox); % Subsat is ugriddataset
%                     elseif nargin == 2
%                         v = obj.netcdfugrid;
%                         subsat = v.subset(bbox); % Subsat is ugriddataset
%                     end
                    mesh  = obj.netcdfugrid.getMeshVariableByName(varName).getMeshset().getMesh();
%                     mesh = meshvariable.getMeshset.getMesh();
                    if mesh.getTreeSize() == 0
                        mesh.buildRTree();
                    else
                        % pass
                    end
%                     clear meshvariable
%                     meshvariable = obj.netcdfugrid.getMeshVariableByName(varName);
                    subsatdataset = obj.netcdfugrid.getMeshVariableByName(varName).subsetToSelf(bbox);
%                     disp(subsatdataset.getNetcdfDataset.toString())
                    % Get netcdfj variable class and read all (since we already subset)
                    submeshv = subsatdataset.getMeshVariableByName(varName);
                    connect_meshset = submeshv.getMeshset();
%                     temp = connect_meshset.getMesh();
%                     temp.toString()

                    % Get variable values from subsat meshVariable
                    submeshv = submeshv.getVariable();
                    array = submeshv.read();
                    values = array.copyToNDJavaArray();
                    ss.data = values;
                    % Related coordinates and connectivity
                    ss.grid = obj.getSomeCoordinateData(varName, subsatdataset, connect_meshset);
        %         end

        end % end subset

        function sub = geosubset(obj, struct)
            import ucar.nc2.dt.ugrid.geom.LatLonPolygon2D;
            import ucar.nc2.dt.ugrid.geom.LatLonRectangle2D;
            import ucar.unidata.geoloc.LatLonPoint;
            import ucar.unidata.geoloc.LatLonPointImpl;
            import ucar.unidata.geoloc.LatLonRect;
            %                     if nargin ==3
            %                         struct = varargin{2};
            %                         varName = varargin{1};
            %                         % Find the mesh that contains the variable of interest.
            %                         mesh_ident = obj.attribute('mesh', varName);
            %                         mesh_vars = regexp(mesh_ident, ' ', 'split');
            %                         mesh_ind = str2double(mesh_vars{1}(5:end));
            %                     elseif nargin == 2
            %                         struct = varargin{1};
            %                     end
            % Need to construct lat/lon rect:
            maxlat  = max(struct.lat);
            minlon = min(struct.lon);
            minlat = min(struct.lat);
            maxlon = max(struct.lon);
            bbox = LatLonRect(LatLonPointImpl(maxlat, minlon), LatLonPointImpl(minlat, maxlon));
            subsatdataset = obj.netcdfugrid.subset(bbox);
%             disp(subsatdataset.getNetcdfDataset.toString())
        end % end geosubset (datasetSubset)

        function a = attributes(obj, variable)
            % NCUGRID.ATTRIBUTES returns the attributes of the variable as an
            % n x 2 cell array.
            %
            % Use as:
            %   a = ncdataset.attributes
            %   a = ncdataset.attributes(variableName)
            %
            % Inputs:
            %   variableName = The name of the variable whos attributes you want
            %       to retrieve. If no argument is specified then the
            %       global attributes are returned.
            %
            % Return:
            %   An (n, 2) cell array. The 1st column contains the attribute name
            %   The 2nd column contains the attribute value. Each row is essentially
            %   a key-value pair.
            %
            % Hints:
            %   Here's how to obtain cell arrays of the keys and corresponding values
            %   and search for a particular key (or attribute name)
            %     ds = ncugrid('http://somewhere/data.nc');
            %     attr = ds.attributes('time');
            %     keys = attr(:, 1);    % Cell array of keys
            %     values = attr(:, 2);  % Cell array of values
            %     i = find(ismember(keys, 'units')); % search for units attribute
            %     units = values{i};  % Retrieve the units value
            %             if (nargin < 2)
            %                 % Get global attributes
            %                 aa = obj.netcdfugrid.getGlobalAttributes();
            %             else
            %                 % Get attributes for the variable
            %                 variable = ucar.nc2.NetcdfFile.escapeName(variable);
            %                 v = obj.netcdfugrid.getNetcdfFile().findVariable(variable);
            %                 if isempty(v)
            %                     warning('NCTOOLBOX:ncugrid:attributes', ['Could not find the variable ']);
            %                 end
            %                 aa = v.getAttributes();
            %             end
            %
            %             if ~isempty(aa)
            %                 n = aa.size();
            %                 a = cell(n, 2);
            %                 for i = 1:n
            %                     at = aa.get(i - 1);
            %                     a{i, 1} = char(at.getName());
            %                     if (at.isString())
            %                         a{i, 2} = char(at.getStringValue());
            %                     else
            %                         a{i, 2} = at.getValues().copyToNDJavaArray();
            %                     end
            %                 end
            %             else
            %                 % Show warning, return empty cell array
            %                 warning('NCTOOLBOX:ncugrid:attributes', 'No attributes were found');
            %                 a = cell(1, 2);
            %             end
            if nargin == 2
                a = obj.dataset.attributes(variable);
            elseif nargin == 1

                a = obj.dataset.attributes();
            else
                % pass
            end
        end % end attributes

        function val = attribute(obj, varargin)
            % NCUGRID.ATTRIBUTE returns the value a global attribute specified by its key or the
            % variable attribute specified by key and variable.
            %
            % Use as:
            %   a = ncdataset.attribute('title')
            %   a = ncdataset.attribute(key)
            %
            %   a = ncdataset.attribute('temp', 'title')
            %   a = ncdataset.attribute(variableName, key)
            %
            % Inputs:
            %   key = The name of the attribute field like 'title' or 'units'...
            %   variableName = The name of the variable whos attributes you want
            %       to retrieve. If no argument is specified then the
            %       global attributes are returned.
            %
            % Return:
            %   The value associated with the attribute field corresponding to key (and optionally
            %   variableName)
            if nargin < 3
                atlist = obj.attributes;
                val = value4key(atlist, varargin{1});
            elseif nargin == 3
                atlist = obj.attributes(varargin{1});
                val = value4key(atlist, varargin{2});
            else
                warning('NCTOOLBOX:ncugrid:attribute', 'No key or variable specified.');
            end
        end % end attribute

        function gs = getSomeCoordinateData(obj, varName, dataset, meshset)
%             mesh_ident = obj.attribute(varName, 'mesh');
%             mesh_vars = regexp(mesh_ident, ' ', 'split');
%             if nargin < 3
%                 meshset  = obj.meshes.get(str2double(mesh_vars{1}(5:end)-1));
%             end
            connectivity = meshset.getMesh().getTopology().getFaceNodeConnectivityVariable();
            coordinates = obj.attribute(varName, 'coordinates');
            coordinate_vars = regexp(coordinates, ' ', 'split');

             location = obj.attribute(varName, 'location');
%             connectivity_var = obj.attribute([location, '_connectivity'], mesh_vars{1});

            % Get coordinates (time, lat, lon,...)
            for i = 1:length(coordinate_vars)
                switch coordinate_vars{i}
                    case 'time'
                        v = meshset.getMeshVariableByName(coordinate_vars{i});
                        if isempty(v)
                            v = dataset.getNetcdfFile().findVariable(coordinate_vars{i});
                        else
                            %
                        end
                        array = v.read();
                        values = array.copyToNDJavaArray();
                        gs.(coordinate_vars{i}) = values;
                        clear values
                    otherwise
                        v = meshset.getMeshVariableByName(coordinate_vars{i});
                        if isempty(v)
                            v = dataset.getNetcdfFile().findVariable(coordinate_vars{i});
                        else
                            %
                        end
                        array = v.read();
                        values = array.copyToNDJavaArray();
                        gs.(coordinate_vars{i}) = values;
                        clear values
                end
            end % end for

%             switch location
%                 case 'node'
%                     locs = meshset.getMesh().getUniqueNodes();
%                     for i = 1:locs.size();
%                         %                         point = locs.get(i-1).getGeoPoint();
%                         %                 gs.lat(i,1) = point.getLatitude();
%                         %                 gs.lon(i,1) = point.getLongitude();
%                         gs.index(i,1) = locs.get(i-1).getDataIndex();
%                     end
%                 case 'face'
%                     locs = meshset.getMesh().getUniqueFaces();
%                     for i = 1:locs.size();
%                         gs.index(i,1) = locs.get(i-1).getDataIndex();
%                     end
%                     %                 case 'edge'
%                     %                     locs = meshset.getMesh().getUniqueEdges();
%             end
            % Kyle made a method to do this automatically mesh.getNodeLatLons() and getNodeIndexes()
            %             for i = 1:locs.size();
            %                 point = locs.get(i-1).getGeoPoint();
            % %                 gs.lat(i,1) = point.getLatitude();
            % %                 gs.lon(i,1) = point.getLongitude();
            %                 gs.index(i,1) = locs.get(i-1).getDataIndex();
            %             end

            try
                % Get connectivity
                %                     v = obj.netcdfugrid.getNetcdfFile().findVariable(connectivity_var);
%                 v = meshset.getMesh();
%                 v = v.getConnectivityVariable();
%                 v = connectivity.getVariable();

                array = connectivity.read();
                values = array.copyToNDJavaArray();
                gs.connectivity = values';
                switch location
                    case 'node'
                        gs.index = unique(gs.connectivity);
                end
            catch me
                me.throw();
            end

        end % end

        function d = data(obj, variable, first, last, stride)
            % NCUGRID.DATA - Get variable data based on vector of elements (nodes, faces, etc.) representation of the unstructured data.
            %
            % Usage:
            %              d = nc.data(variableName)
            %              d = nc.data(variableName, first)
            %              d = nc.data(variableName, first, last)
            %              d = nc.data(variableName, first, last, stride)
            %

            variable = ucar.nc2.NetcdfFile.escapeName(variable);
            v = obj.netcdfugrid.getNetcdfFile().findVariable(variable);

            if (nargin == 2)
                array = v.read();
                try
                    d = array.copyToNDJavaArray(); % this fails if the variable has no java shape/no dimension was assigned
                catch me1
                    try
                        % TODO (Alex added this code) Where is a file where
                        % this code section gets called?
                        d = array.toString;  % different way to get single value out of java array
                        d = d.toCharArray';  % must transpose
                        d = str2double(d);   % matlab string to matlab numeric
                    catch me2
                        ex = MException('NCTOOLBOX:ncugrid:data', ['Failed to open "' variable '" in ' url]);
                        ex = ex.addCause(me2);
                        ex.throw;
                    end
                end
                d = d;
            else
                s = obj.size(variable);

                % Fill in missing arguments
                % default stride is 1
                if (nargin < 5)
                    stride = ones(1, length(s));
                end

                % Default last is the end
                if (nargin < 4)
                    last = s;
                end

                % Construct the range objects needed to subset the data
                n = max(size(obj.size(variable)));
                ranges = java.util.ArrayList(n);
                for i = 1:n
                    ranges.add(ucar.ma2.Range(first(i) - 1, last(i) - 1, stride(i)));
                end

                array = v.read(ranges);
                d = array.copyToNDJavaArray();
            end
        end % end data

        function ig = grid_interop(src, varName, first, last, stride)
            % NCUGRID.GRID_INTEROP - Method to get the coordinate variables and their data as a
            % a structure with standardized field names for lat, lon, time, and z. Other coordiante variables
            % that are not recognized by netcdf-java as the previous four types have field names directly
            % taken from their variable names.
            % Usage:
            %          >> gridstruct = geovar.grid_interop(1,:,:,1:2:50);
            %
            g = src.grid(varName, first, last, stride);
            names = fieldnames(g);

            for i = 1:length(names); % loop through fields returned by grid
                tempname = names{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                try
                    type = char(javaaxisvar.getAxisType);
                    if isempty(type)
                        ig.(tempname) = g.(tempname);
                    else
                        switch type
                            case 'Height'
                                pos_z = char(javaaxisvar.getPositive());
                                if strcmp(pos_z, 'POSITIVE_DOWN')
                                    tmp = g.(tempname);
                                    ig.z = tmp.*-1; %adjust for positive direction
                                else
                                    ig.z = g.(tempname);
                                end

                            case 'GeoZ'
                                pos_z = char(javaaxisvar.getPositive());
                                if strcmp(pos_z, 'POSITIVE_DOWN')
                                    tmp = g.(tempname);
                                    ig.z = tmp.*-1; %adjust for positive direction
                                else
                                    ig.z = g.(tempname);
                                end

                            case 'Time'
                                tmp = g.(tempname);
                                t_converted = src.dataset.time(tempname, tmp);
                                ig.time = t_converted;

                                %                     case 'RunTime'
                                %                       tmp = obj.dataset.data(name, vFirst, vLast, vStride);
                                %                       t_converted = obj.dataset.time(name, tmp);
                                %                       data.(type) = t_converted;

                            case 'Lon'
                                tmp = g.(tempname);
                                ind = find(tmp > 180); % convert 0-360 convention to -180-180
                                tmp(ind) = tmp(ind)-360;
                                ig.lon = tmp;

                            case 'Lat'
                                ig.lat = g.(tempname);

                            otherwise
                                ig.(tempname) = g.(tempname);

                        end % end switch on type
                    end % end is type empty or not if statement
                catch
                    ig.(tempname) = g.(tempname);
                end
            end % end loop through field names

        end % grid_interop end

        function gr = grid(obj, varName, first, last, stride)
            % NCUGRID.GRID - Return a matlab structure of the coordinate variables of 'variableName',
            % subsetted using first, last, and stride index arguments in relation to the variable of interest
            % 'variableName'.
            %
            % Usage:
            %              d = nc.grid(variableName)
            %              d = nc.grid(variableName, first)
            %              d = nc.grid(variableName, first, last)
            %              d = nc.grid(variableName, first, last, stride)
            %
            mesh_ident = obj.attribute(varName, 'mesh');
            mesh_vars = regexp(mesh_ident, ' ', 'split');
            if nargin < 3
                mesh  = obj.meshes.get(str2double(mesh_vars{1}(5:end)-1));
            end

            coordinates = obj.attribute(varName, 'coordinates');
            coordinate_vars = regexp(coordinates, ' ', 'split');

            location = obj.attribute(varName, 'location');
            if strcmp(location, 'node')
                connectivity_var = obj.attribute(mesh_vars{1}, ['face_', location, '_connectivity']);
            else

            end
            % Get coordinates (time, lat, lon,...)
            for i = 1:length(coordinate_vars)
                v = obj.netcdfugrid.getNetcdfFile().findVariable(coordinate_vars{i});
                vs = obj.size(v.getName);
                s = obj.size(varName);
                %         if numel(vs) > 0 % Added to solve work around somedata calls that involve variables with
                %           % no netcdf dim. (This will be frequent in some OOI files.)
                if (length(vs) == length(s))
                    %% case: sizes are the same
                    if isequal(vs, s)
                        vFirst = first;
                        vLast = last;
                        vStride = stride;
                    else
                        me = MException('NCTOOLBOX:ncvariable:somedata', ...
                            ['The data size of the coordinate variable,' ...
                            name ', does not fit the size of ' obj.name]);
                        me.throw;
                    end

                elseif length(vs) == 1
                    %% case: singleton dimension. Find side of data with
                    % the same length

                    % TODO: the following line  will give bogus results if
                    % the data has 2 dimensions of the same length
                    dim = find(s == vs, 1);
                    if ~isempty(dim)
                        vFirst = first(dim);
                        vLast = last(dim);
                        vStride = stride(dim);
                    else
                        me = MException('NCTOOLBOX:ncvariable:somedata', ...
                            ['The data size of the coordinate variable,' ...
                            name ', does not fit the size of ' obj.name]);
                        me.throw;
                    end

                else
                    %% case: variable is coordiantes. Look for size
                    % TODO this is a lame implementation.
                    dim = find(s == vs(1), 1);
                    if ~isempty(dim)
                        for j = 2:length(vs)
                            if vs(j) ~= s(dim + j - 1)
                                me = MException('NCTOOLBOX:ncvariable:somedata', ...
                                    ['The data size of the coordinate variable,' ...
                                    name ', does not fit the size of ' obj.name]);
                                me.throw;
                            end
                        end
                        k = dim:dim + length(vs) - 1;
                        vFirst = first(k);
                        vLast = last(k);
                        vStride = stride(k);
                    end
                end

                n = max(size(obj.size(v.getName)));
                ranges = java.util.ArrayList(n);
                for j = 1:n
                    ranges.add(ucar.ma2.Range(vFirst(j) - 1, vLast(j) - 1, vStride(j)));
                end

                array = v.read(ranges);
                values = array.copyToNDJavaArray();
                gr.(coordinate_vars{i}) = values;
                clear values

            end
            %         else
            % not needed i dont think...

            % Get connectivity
            v = obj.netcdfugrid.getNetcdfFile().findVariable(connectivity_var);
            array = v.read();
            values = array.copyToNDJavaArray();
            gr.connectivity = values;

        end % end grid

        function vs = size(obj, varName)
            % NCUGRID.SIZE - Return the dimensional sizes of the variable of interest.
            %
            % Usage:
            %              d = nc.size(variableName)
            %
            v = obj.dataset.netcdf.findVariable(varName);
            vs = v.getShape;
            vs = vs';
        end

        function d = timewindowij(src, varargin)
            % NCGEOVARIABLE.TIMEWINDOWIJ - Function to get indices from start and stop times for sub-
            % setting. TODO: There must be a better/fast way to do this using the java library.
            % Useage: >> timestruct = nc.timewindowij([2004 1 1 0 0 0], [2005 12 31 0 0 0]);
            %              >> timestruct = nc.timewindowij(731947, 732677);
            % Result: time.time = time data
            %            time.index = time indices
            s = src.size(varargin{1});
            first = ones(1, length(s));
            last = s;
            stride = first;
            g = src.grid_interop(first, last, stride);

            if isfield(g, 'time') % are any of the fields recognized as time explictly
                starttime = datenum(varargin{2});
                stoptime = datenum(varargin{3});
                if isempty(starttime)
                    starttime = g.time(1);
                end
                if isempty(stoptime)
                    stoptime = g.time(end);
                end

                t_index1 = g.time >= starttime;
                t_index2 = g.time <= stoptime;
                d.index = find(t_index1==t_index2);
                d.time = g.time(d.index);
            else
                me = MException(['NCTOOLBOX:ncgeovariable:timewindowij'], ...
                    'No grid variable returned as time.');
                me.throw;
            end
        end % end timewindowij

    end % end methods

end % end class


%run this test case with the command
%results = runtests('modelSortingTests.m')
function tests = modelSortingTests
tests = functiontests(localfunctions);
end

function sortIdentifirs_and_permuteModelTest(testCase)

%Load the expected (i.e. sorted) model
sourceDir = fileparts(which(mfilename));
load([sourceDir,'/test_data/ecoli_textbook.mat'], 'model');
expModel = model;

%Create the actual model that will be permuted and sorted
actModel = expModel;

%Randomly permutate model, do not use RAVEN functions
rndIdx = randperm(numel(actModel.rxns));
fieldsToChange = {'rxns','lb','ub','rev','c','rxnNames','grRules','eccodes'};
for i=1:numel(fieldsToChange)
    actModel.(fieldsToChange{i}) = actModel.(fieldsToChange{i})(rndIdx);
end
actModel.S          = actModel.S(:,rndIdx);
actModel.rxnGeneMat = actModel.rxnGeneMat(rndIdx,:);

rndIdx = randperm(numel(actModel.mets));
fieldsToChange = {'mets','metNames','metComps','metFormulas','metMiriams'};
for i=1:numel(fieldsToChange)
    actModel.(fieldsToChange{i}) = actModel.(fieldsToChange{i})(rndIdx);
end
actModel.S     = actModel.S(rndIdx,:);

rndIdx = randperm(numel(actModel.genes));
fieldsToChange = {'genes','geneShortNames'};
for i=1:numel(fieldsToChange)
    actModel.(fieldsToChange{i}) = actModel.(fieldsToChange{i})(rndIdx);
end
actModel.rxnGeneMat = actModel.rxnGeneMat(:,rndIdx);

rndIdx = randperm(numel(actModel.comps));
fieldsToChange = {'comps','compNames'};
for i=1:numel(fieldsToChange)
    actModel.(fieldsToChange{i}) = actModel.(fieldsToChange{i})(rndIdx);
end
[~,J]=sort(rndIdx);
[toreplace, bywhat] = ismember(actModel.metComps,1:length(J));
actModel.metComps(toreplace) = J(bywhat(toreplace));

%Sort randomly permutated model
actModel = sortIdentifiers(actModel);

%Check that the actual model is the same as the expected model
verifyEqual(testCase,actModel,expModel)
end

function expandModel_and_contractModelTest(testCase)
%Load the expected model
sourceDir = fileparts(which(mfilename));
load([sourceDir,'/test_data/ecoli_textbook.mat'], 'model');

% Note that this does not work any model, as grRules might not be properly
% sorted (but should still be valid)
evalc('modelNew = expandModel(model);'); % Suppress warnings about complex grRules
modelNew = contractModel(modelNew);

verifyEqual(testCase,model,modelNew)
end


% LTFAT - Signals
%
%  Signal generators
%    CTESTFUN       -  Complex valued test function.
%    NOISE          -  Stochastic noise generator.
%    PINKNOISE      -  Pink noise.
%    EXPCHIRP       -  Exponential chirp.
%
%  Sound signals.
%    BAT            -  Bat chirp.
%    BATMASK        -  Mask for bat signal.
%    GREASY         -  Woman speaking the word 'greasy'
%    COCKTAILPARTY  -  Sentence by male, native English speaker.
%    GSPI           -  Glockenspiel test signal.
%    LINUS          -  Linus pronouncing Linux.
%    LTFATLOGO      -  Synthetic sound from spectrogram reconstruction.
%    OTOCLICK       -  Click-evoked otoacoustic emmision.
%    TRAINDOPPLER   -  Sound of passing train.
%
%  Images.
%    CAMERAMAN      -  Greyscale image of the cameraman.
%    LICHTENSTEIN   -  Color image of the Lichtenstein castle.
%    LTFATTEXT      -  Black and white word: 'LTFAT'.
%
%  For help, bug reports, suggestions etc. please visit
%  http://github.com/ltfat/ltfat/issues
%


function n = mep2dep(n)
   % convert a matlab date to an epoch date
   n = (n - 719529) * 86400;
end


function Set = intersect_elements(Set1,Set2,False1,False2)

% Determines the intersection of Set1 and Set2.

Set = unique_elements([Set1; Set2],False1);
False1(Set1) = true;
False2(Set2) = true;
I = False1(Set)&False2(Set);
Set = Set(I);


function varargout = spm_graph_ui(action, varargin)
% Graphical display of adjusted data
% FORMAT [Y,y,beta,Bcov] = spm_graph_ui(xSPM,SPM,hReg)
%
% xSPM   - structure containing SPM, distributional & filtering details
%          about the excursion set
% SPM    - structure containing generic details about the analysis
% hReg   - handle of MIP register or [x y z] coordinates
%
% Y      - fitted   data for the selected voxel
% y      - adjusted data for the selected voxel
% beta   - parameter estimates (ML or MAP)
% Bcov   - Covariance of parameter estimates (ML or conditional)
%
% See spm_getSPM for details.
%__________________________________________________________________________
%
% spm_graph is a Callback function that uses the structures above to:
% (1) send adjusted (y) and fitted data (Y), for the selected voxel, to the
% workspace and (ii) provide graphics for:
%
% a) Contrasts of parameter estimates (e.g. activations) and their
% standard error.
%
% b) Fitted and adjusted responses that can be plotted against time, scan,
% or an indicator variable in the design matrix.
%
% c) (fMRI only).  Evoked responses using the basis functions to give
% impulse responses that would have been seen in the absence of other
% effects. The PSTH (peristimulus-time histogram) option provides a finite
% impulse response (FIR) estimate of the trial-specific evoked response as
% a function of peristimulus time.  This is estimated by refitting a
% convolution model to the selected voxel using an FIR basis set.  This is
% simply a set of small boxes covering successive time bins after trial
% onset.  The width of each bin is usually the TR.  This option provides a
% more time-resolved quantitative characterisation of the evoked
% hemodynamic response.  However, it should not be over-interpreted because
% inference is usually made using a simpler and more efficient basis set
% (e.g., canonical hrf, or canonical plus time derivative).
%
% Getting adjusted data:
% Ensuring the data are adjusted properly can be important (e.g. in
% constructing explanatory variables such as in a psychophysiological
% interaction). To remove or correct for specific effects, specify an
% appropriate F contrast and simply plot the fitted (and adjusted)
% responses after selecting that F contrast. The vectors Y (fitted) and y
% (adjusted) in the workspace will now be corrected for the effects in the
% reduced design matrix (X0) specified in the contrast manager with the
% column indices (iX0) of the confounds in this adjustment.
%
% Plotting data:
% All data and graphics use filtered/whitened data and residuals. In PET
% studies the parameter estimates and the fitted data are often the same
% because the explanatory variables are simply indicator variables taking
% the value of one.  Only contrasts previously defined can be plotted. This
% ensures that the parameters plotted are meaningful even when there is
% collinearity among the design matrix subpartitions.
%
% Selecting contrasts used for PPMs will automatically give plots
% based on conditonal estimates.
%
% The structure     contrast.contrast      = cbeta;
%                   contrast.standarderror = SE;
%                   contrast.interval      = 2*CI;
%
% is assigned in base workspace for plots of contrasts and their error.
%__________________________________________________________________________
% Copyright (C) 1996-2013 Wellcome Trust Centre for Neuroimaging

% Karl Friston, Guillaume Flandin
% $Id: spm_graph_ui.m 6985 2017-01-11 11:51:49Z guillaume $

if nargin && ~ischar(action)
    varargin = {action varargin{:}};
    action   = 'Graph';
end

%==========================================================================
switch lower(action), case 'graph'                                  %-Graph
%==========================================================================
% FORMAT [Y,y,beta,Bcov] = spm_graph_ui('Graph',xSPM,SPM,hReg)

    if nargin < 3, error('Not enough input arguments.'); end
    [xSPM,SPM,hReg] = deal(varargin{:});

    [xG, XYZ] = spm_graph_ui('UI',xSPM,SPM,hReg);
    if isempty(xG), varargout = {[],[],[],[]}; return; end

    G = spm_graph_ui('Compute',SPM,XYZ,xG);

    XYZmm = SPM.xVol.M(1:3,:) * [XYZ(:);1];
    spm_graph_ui('Plot',SPM,XYZmm,xG,G,hReg);

    varargout = { G.Y, G.y, G.beta, G.Bcov };

%==========================================================================
case 'ui'                                                              %-UI
%==========================================================================
% FORMAT [xG, XYZ] = spm_graph_ui('UI',xSPM,SPM,hReg)

    if nargin < 3, error('Not enough input arguments.'); end
    [xSPM,SPM,hReg] = deal(varargin{:});

    %-Delete previous axis and their pagination controls (if any)
    %----------------------------------------------------------------------
    Fgraph = spm_figure('GetWin','Graphics');
    spm_results_ui('Clear',Fgraph,2);

    %-Find nearest voxel [Euclidean distance] in point list & update GUI
    %----------------------------------------------------------------------
    if isempty(xSPM.XYZmm)
        spm('alert!','No suprathreshold voxels!',mfilename,0);
        varargout = {[],[]};
        return
    end

    if numel(hReg) == 1
        XYZmm = spm_XYZreg('GetCoords',hReg);
    else
        XYZmm = hReg;
    end
    [XYZmm,i] = spm_XYZreg('NearestXYZ',XYZmm,xSPM.XYZmm);
    if numel(hReg) == 1, spm_XYZreg('SetCoords',XYZmm,hReg); end
    XYZ       = xSPM.XYZ(:,i);

    %-Find out what to plot
    %======================================================================
    Cplot   = {'Contrast estimates and 90% C.I.',...
               'Fitted responses',...
               'Event-related responses',...
               'Parametric responses',...
               'Volterra Kernels'};

    % ensure options are appropriate
    %----------------------------------------------------------------------
    if ~isfield(SPM,'Sess'), Cplot = Cplot(1:2); end
    i       = spm_input('Plot',-1,'m',Cplot);
    xG.def  = Cplot{i};

    switch xG.def

        % select contrast if
        %------------------------------------------------------------------
        case {'Contrast estimates and 90% C.I.'}

            % determine which contrast
            %--------------------------------------------------------------
            xG.spec.Ic = spm_input('Which contrast?','!+1','m',...
                {SPM.xCon.name,'Specify a temporary contrast...'});
            if xG.spec.Ic > numel(SPM.xCon)
                xG.spec.Ic = spm_input('Contrast weights','!+1','x','',Inf,SPM.xX.X);
            end

        % select contrast if
        %------------------------------------------------------------------
        case {'Fitted responses'}

            % determine which contrast
            %--------------------------------------------------------------
            xG.spec.Ic = spm_input('Which contrast?','!+1','m',{SPM.xCon.name});

        % select session and trial if
        %------------------------------------------------------------------
        case {'Event-related responses','Parametric responses','Volterra Kernels'}

            % get session
            %--------------------------------------------------------------
            s     = length(SPM.Sess);
            if  s > 1
                s = spm_input('which session','+1','n1',1,s);
            end
            xG.spec.Sess = s;

            % effect names
            %--------------------------------------------------------------
            switch xG.def
                case 'Volterra Kernels'
                    u = length(SPM.Sess(s).Fc);
                otherwise
                    u = length(SPM.Sess(s).U);
            end
            Uname = {};
            for i = 1:u
                Uname{i} = SPM.Sess(s).Fc(i).name;
            end
            if isempty(Uname), error('No conditions found.'); end

            % get effect
            %--------------------------------------------------------------
            str   = 'which effect';
            u     = spm_input(str,'+1','m',Uname);
            xG.spec.u = u;

    end

    switch xG.def
        case 'Fitted responses'

            % predicted or adjusted response
            %--------------------------------------------------------------
            str   = 'predicted or adjusted response?';
            xG.spec.predicted = spm_input(str,'!+1','b',{'predicted','adjusted'},[1 0]);

            % get ordinates
            %--------------------------------------------------------------
            Xplot = {'an explanatory variable',...
                     'scan or time',...
                     'a user specified ordinate'};
            Cx    = spm_input('plot against','!+1','m',Xplot);
            if     Cx == 1
                str  = 'Which explanatory variable?';
                xG.spec.x.i = spm_input(str,'!+1','m',SPM.xX.name);
            elseif Cx == 2
                xG.spec.x.scan = true;
            elseif Cx == 3
                xG.spec.x.x = spm_input('enter ordinate','!+1','e','',numel(SPM.xY.VY));
            end

        %-Modeling evoked responses based on Sess
        %==================================================================
        case 'Event-related responses'

            % get plot type
            %--------------------------------------------------------------
            Rplot = {'fitted response and PSTH',...
                     'fitted response and 90% C.I.',...
                     'fitted response and adjusted data'};

            Rplot = Rplot{spm_input('plot in terms of','+1','m',Rplot)};
            xG.spec.Rplot = Rplot;

        %-Modeling evoked responses based on Sess
        %==================================================================
        case 'Parametric responses'

            % return gracefully if no parameters
            %--------------------------------------------------------------
            if ~SPM.Sess(s).U(u).P(1).h
                warning('No parametric modulation in condition "%s".',...
                    SPM.Sess(s).Fc(u).name);
                varargout = {[],[]};
                return
            end

            str   = 'which parameter';
            p     = spm_input(str,'+1','m',{SPM.Sess(s).U(u).P.name});
            xG.spec.p = p;
    end

    varargout = { xG, XYZ };

%==========================================================================
case 'compute'                                                    %-Compute
%==========================================================================
% FORMAT [Y,y,beta,Bcov,G] = spm_graph_ui('Compute',SPM,XYZ,xG)

spm('Pointer','Watch');
[Y,y,beta,Bcov,G] = spm_graph(varargin{:});
spm('Pointer','Arrow');

G.Y = Y; G.y = y; G.beta = beta; G.Bcov = Bcov;
varargout = { G };

%==========================================================================
case 'plot'                                                          %-Plot
%==========================================================================
% FORMAT spm_graph_ui('Plot',SPM,XYZmm,xG,G,hReg)

    [SPM,XYZmm,xG,G,hReg] = deal(varargin{:});

    %-Get Graphics figure handle
    %----------------------------------------------------------------------
    Fgraph = spm_figure('GetWin','Graphics');

    %-Scaled font sizes
    %----------------------------------------------------------------------
    FS     = spm('FontSizes');

    %-Voxel coordinates
    %----------------------------------------------------------------------
    XYZstr = sprintf(' at [%g, %g, %g]',XYZmm);

    %-Colour specification
    %----------------------------------------------------------------------
    Col    = [0 0 0; .8 .8 .8; 1 .5 .5];

    switch xG.def

        %-Plot parameter estimates
        %==================================================================
        case 'Contrast estimates and 90% C.I.'

            % bar chart
            %--------------------------------------------------------------
            figure(Fgraph)
            ax = subplot(2,1,2,'Parent',Fgraph);
            cla(ax)
            hold(ax,'on')

            % estimates
            %--------------------------------------------------------------
            cbeta = G.contrast;
            h     = bar(ax,cbeta);
            set(h,'FaceColor',Col(2,:))

            % standard error
            %--------------------------------------------------------------
            CI    = G.interval / 2;
            for j = 1:length(cbeta)
                line([j j],([CI(j) -CI(j)] + cbeta(j)),...
                    'LineWidth',6,'Color',Col(3,:),'Parent',ax)
            end

            TTLstr = {xG.def};
            if numel(xG.spec.Ic) == 1
                TTLstr = [TTLstr {SPM.xCon(xG.spec.Ic).name}];
            else
                TTLstr = [TTLstr {'user-defined contrast'}];
            end
            if isfield(SPM,'VCbeta')
                TTLstr = [TTLstr {'(conditional estimates)'}];
            end
            title(ax,TTLstr,'FontSize',FS(12))
            xlabel(ax,'contrast','FontSize',FS(12))
            ylabel(ax,['contrast estimate',XYZstr],'FontSize',FS(12))
            set(ax,'XLim',[0.4 (length(cbeta) + 0.6)])
            hold(ax,'off')

            % for compatibility with earlier versions of SPM
            %--------------------------------------------------------------
            assignin('base','contrast',G)

        %-All fitted effects or selected effects
        %==================================================================
        case 'Fitted responses'

            % plot
            %--------------------------------------------------------------
            figure(Fgraph)
            ax = subplot(2,1,2,'Parent',Fgraph);
            cla(ax)
            hold(ax,'on')

            x = G.x;
            y = G.y;
            Y = G.Y;
            [p,q] = sort(x);
            if all(diff(x(q)))
                plot(x(q),Y(q),'LineWidth',4,'Color',Col(2,:));
                plot(x(q),y(q),':','Color',Col(1,:));
                plot(x(q),y(q),'.','MarkerSize',8, 'Color',Col(3,:));
            else
                plot(x(q),Y(q),'.','MarkerSize',16,'Color',Col(1,:));
                plot(x(q),y(q),'.','MarkerSize',8, 'Color',Col(2,:));
                xlim = get(ax,'XLim');
                xlim = [-1 1]*diff(xlim)/4 + xlim;
                set(ax,'XLim',xlim)
            end

            TTLstr = {xG.def SPM.xCon(xG.spec.Ic).name};
            if isfield(SPM,'VCbeta')
                TTLstr = [TTLstr {'(conditional estimates)'}];
            end
            title(ax,TTLstr,'FontSize',FS(12))
            switch char(fieldnames(xG.spec.x))
                case 'i' % an explanatory variable
                    XLAB = SPM.xX.name{xG.spec.x.i};
                case 'scan' % scan or time
                    if isfield(SPM.xY,'RT')
                        XLAB = 'time {seconds}';
                    else
                        XLAB = 'scan number';
                    end
                case 'x' % user specified
                    XLAB = 'ordinate';
            end
            xlabel(ax,XLAB,'FontSize',FS(12))
            ylabel(ax,['response',XYZstr],'FontSize',FS(12))
            legend(ax,'fitted','plus error')
            hold(ax,'off')

        %-Modeling evoked responses based on Sess
        %==================================================================
        case 'Event-related responses'

            % plot
            %--------------------------------------------------------------
            figure(Fgraph)
            ax = subplot(2,1,2,'Parent',Fgraph);
            hold(ax,'on')

            x = G.x;
            y = G.y;
            Y = G.Y;
            s = xG.spec.Sess;
            u = xG.spec.u;
            Rplot = xG.spec.Rplot;
            if strcmp(Rplot,'fitted response and PSTH') && ~isfield(G,'PSTH')
                % Data not available
                Rplot = 'fitted response and 90% C.I.';
            end
            switch Rplot

                case 'fitted response and PSTH'
                    %------------------------------------------------------
                    PST  = G.PST;
                    PSTH = G.PSTH;
                    PCI  = G.PCI;
                    errorbar(ax,PST,PSTH,PCI)
                    plot(ax,PST,PSTH,'LineWidth',4,'Color',Col(2,:))
                    plot(ax,x,Y,'-.','Color',Col(3,:))

                case 'fitted response and 90% C.I.'
                    %------------------------------------------------------
                    CI   = G.CI;
                    plot(ax,x,Y,'Color',Col(2,:),'LineWidth',4)
                    plot(ax,x,Y + CI,'-.',x,Y - CI,'-.','Color',Col(1,:))

                case 'fitted response and adjusted data'
                    %------------------------------------------------------
                    pst  = G.pst;
                    plot(ax,x,Y,'Color',Col(2,:),'LineWidth',4)
                    plot(ax,pst,y,'.','Color',Col(3,:))

            end

            % label
            %--------------------------------------------------------------
            [i,j] = max(Y);
            text(ceil(1.1*x(j)),i,SPM.Sess(s).Fc(u).name,'FontSize',FS(8),'Parent',ax);
            title(ax,Rplot,'FontSize',FS(12))
            xlabel(ax,'peristimulus time {secs}','FontSize',FS(12))
            ylabel(ax,['response',XYZstr],'FontSize',FS(12))
            hold(ax,'off')

        %-Parametric responses
        %==================================================================
        case 'Parametric responses'

            % plot
            %--------------------------------------------------------------
            figure(Fgraph)

            pst = G.pst;
            P   = G.P;
            Y   = G.Y;
            s   = xG.spec.Sess;
            u   = xG.spec.u;
            p   = xG.spec.p;

            ax  = subplot(2,2,3,'Parent',Fgraph);
            surf(ax,pst,P,Y')
            shading(ax,'flat')
            title(ax,SPM.Sess(s).U(u).name{1},'FontSize',FS(12))
            xlabel(ax,'PST {secs}','FontSize',FS(12))
            ylabel(ax,SPM.Sess(s).U(u).P(p).name,'FontSize',FS(12))
            zlabel(ax,['responses',XYZstr],'FontSize',FS(12))
            axis(ax,'square')

            ax = subplot(2,2,4,'Parent',Fgraph);
            [j,i] = max(mean(Y,2));
            plot(ax,P,Y(i,:),'LineWidth',4,'Color',Col(2,:))
            str   = sprintf('response at %0.1fs',i*SPM.xBF.dt);
            title(ax,str,'FontSize',FS(12))
            xlabel(ax,SPM.Sess(s).U(u).P(p).name,'FontSize',FS(12))
            axis(ax,'square')
            grid(ax,'on')

        %-Volterra Kernels
        %==================================================================
        case 'Volterra Kernels'

            pst = G.pst;
            Y   = G.Y;
            s   = xG.spec.Sess;
            u   = xG.spec.u;

            % second order kernel
            %--------------------------------------------------------------
            if u > length(SPM.Sess(s).U)

                % plot
                %----------------------------------------------------------
                figure(Fgraph)

                ax = subplot(2,2,3,'Parent',Fgraph);
                imagesc(pst,pst,Y,'Parent',ax)
                axis(ax,'xy')
                axis(ax,'image')
                title(ax,'2nd order Kernel','FontSize',FS(12))
                xlabel(ax,'peristimulus time {secs}','FontSize',FS(12))
                ylabel(ax,'peristimulus time {secs}','FontSize',FS(12))

                ax = subplot(2,2,4,'Parent',Fgraph);
                plot(ax,pst,Y)
                axis(ax,'square')
                grid(ax,'on')
                title(ax,SPM.Sess(s).Fc(u).name,'FontSize',FS(12))
                xlabel(ax,'peristimulus time {secs}','FontSize',FS(12))

            % first  order kernel
            %--------------------------------------------------------------
            else

                % plot
                %----------------------------------------------------------
                figure(Fgraph)
                ax = subplot(2,1,2,'Parent',Fgraph);
                plot(ax,pst,Y)
                grid(ax,'on')
                axis(ax,'square')
                title(ax,{'1st order Volterra Kernel' SPM.Sess(s).Fc(u).name},...
                    'FontSize',FS(12))
                xlabel(ax,'peristimulus time {secs}','FontSize',FS(12))
                ylabel(ax,['impulse response',XYZstr],'FontSize',FS(12))

            end

    end

    %-Turn hold button off - this will alert the user to press it again
    %----------------------------------------------------------------------
    try, set(findobj('Tag','holdButton'),'Value',0); end

    %-Call Plot UI
    %----------------------------------------------------------------------
    spm_results_ui('PlotUi',ax);

    %-Setup registry
    %----------------------------------------------------------------------
    hPB = findobj('Tag','plotButton');
    if ~isempty(hPB)
        setappdata(hPB,'Reg',struct('xG',xG,'Fgraph',Fgraph));
        spm_XYZreg('Add2Reg',hReg,hPB,'spm_graph_ui');
        set(ax,'DeleteFcn',[get(ax,'DeleteFcn') ';' ...
            'spm_XYZreg(''Del2Reg'',hReg,findobj(''Tag'',''plotButton''));']);
    end

%==========================================================================
case 'setcoords'                                        %-Coordinate change
%==========================================================================
% FORMAT spm_graph_ui('SetCoords',XYZmm,h,hReg)
    if nargin<3, error('Not enough input arguments.'); end
    XYZmm = varargin{1};
    h     = varargin{2};
    hReg  = varargin{3};
    UD    = getappdata(h,'Reg');

    SPM   = evalin('base','SPM');
    XYZ   = SPM.xVol.iM(1:3,:) * [XYZmm(:);1];
    G     = spm_graph_ui('Compute',SPM,XYZ,UD.xG);

    spm_results_ui('Clear',UD.Fgraph,2);

    spm_graph_ui('Plot',SPM,XYZmm,UD.xG,G,hReg);

%==========================================================================
otherwise                                           %-Unknown action string
%==========================================================================
    error('Unknown action string')

end


function D = deleteObjectsOutsideFrame(D, HOMEIMAGES)
%
% mark as deleted objects with a center of mass outside the image
%

for j = 1:length(D)
    %info = imfinfo(strrep(strrep(fullfile(HOMEIMAGES, D(j).annotation.folder, D(j).annotation.filename), '\', '/'), ' ', '%20'));
    info = imfinfo(strrep(fullfile(HOMEIMAGES, D(j).annotation.folder, D(j).annotation.filename), '\', '/'));
    nrows = info.Height;
    ncols = info.Width;

    % Change the size of the polygon coordinates
    if isfield(D(j).annotation, 'object')
        Nobjects = length(D(j).annotation.object);
        for i = 1:Nobjects
            [x,y] = getLMpolygon(D(j).annotation.object(i).polygon);

            xm = mean(x);
            ym = mean(y);

            if xm<1 || ym<1 || xm>ncols || ym>nrows
                D(j).annotation.object(i).deleted = '1';
            end
        end
    end
end


function [images images2 images3 view]= RawToCommonAtlas(images, corners, safirData, safirResult, view, flip_lr)
% From the results of atlas fitting, get the data morphed into an individual
% atlas space (images2), and that morphed into a common atlas space (images3)
%
% [images images2 images3 view]= RawToCommonAtlas(images, corners, safirData, safirResult, [view], [flip_lr])
%
% INPUT
%  images: a structure including raw data (M1,M2,M3) and inital atlas (A1,A2)
%  corners: corners of the initial atlas
%  safirData: data structure for the atlas fitting GUI
%  safirResult: morphing matrix
%  view: has to be specified if you want to get ROIs
%  flip_lr: flip
%
% OUTPUT
%  images: raw data with the fitted atlas
%  images2: the data morphed into an individual atlas space
%  images3: the data morphed into a common atlas space
%  view: has to be specified if you want to get ROIs
% Example
%  [images images2 images3]= RawToCommonAtlas(images, corners, 'safirData', 'safirResult');
%
% 07/03 KA wrote it

% Check arguments
if notDefined('images'), error('images should be defined'); end
if notDefined('corners'), error('corners should be defined'); end
if notDefined('safirData'), safirData = 'safirData'; end
if notDefined('safirResult'), safirData = 'safirResult'; end
if notDefined('flip_lr'), flip_lr=0; end

load(safirResult)
load(safirData,'maxLevel')

if size(corners,2)==6 % LO,TO
    area_num=4;
elseif size(corners,2)==5 % V1-3
    area_num=5;
end

yOpt_inv = inverse_yOpt(yOpt,maxLevel);
if isfield(images,'A3')
    [dM1m dM2m dM3m Edges dEdges A1m A2m M1m M2m M3m W1m W2m COm dW1m dW2m dCOm dAreasImgm dA1m dA2m dA3m] = showResults2(yOpt,yOpt_inv,corners, safirData,images);
else
    [dM1m dM2m dM3m Edges dEdges A1m A2m M1m M2m M3m W1m W2m COm dW1m dW2m dCOm dAreasImgm dA1m dA2m] = showResults2(yOpt,yOpt_inv,corners, safirData,images);
end

images_size = size(images.A1,1);
if strfind(safirData,'left')>0
    curSlice = 1;
elseif  strfind(safirData,'right')>0
    curSlice = 2;
end
if notDefined('view')==0
    view = transformAtlasToROIs(round(dAreasImgm), images_size, curSlice, view);
end

% raw data spcae
images.M1 = M1m;
images.M2 = M2m;
images.M3 = M3m;
images.W1 = W1m;
images.W2 = W2m;
images.CO = COm;
images.dA1 = dA1m;
images.dA2 = dA2m;
if isfield(images,'A3')
    images.dA3 = dA3m;
end
images.dEdges = dEdges;
images.dareasImg = round(dAreasImgm);

% individual atlas space
images2.A1 = A1m;
images2.A2 = A2m;
images2.M1 = dM1m;
images2.M2 = dM2m;
images2.M3 = dM3m;
images2.W1 = dW1m;
images2.W2 = dW2m;
images2.CO = dCOm;
images2.areasImg = round(dAreasImgm);

% For flipped image (since sometimes flat view itself is flipped)
if flip_lr
    images2.M1 = fliplr(images2.M1);
    images2.M2 = fliplr(images2.M2);
    images2.M3 = fliplr(images2.M3);
    images2.A1 = fliplr(images2.A1);
    images2.A2 = fliplr(images2.A2);
    images2.W1 = fliplr(images2.W1);
    images2.W2 = fliplr(images2.W2);
    images2.CO = fliplr(images2.CO);
    images2.areasImg = fliplr(images2.areasImg);

    for i=1:size(corners,2)
        corners{i}(:,1) = size(images.A1,2)+1-corners{i}(:,1);
    end
end

% scale corner coordinates
for i=1:size(corners,2)
    corners_scaled{i} = corners{i}*(size(images.A1,1)-1)/(size(images.A1,1)-1);
%     corners_scaled{i} = corners{i};
end

if size(corners,2)==6 % LO-1/2, TO-1/2
    images3.A1 = transform_image_LO_TO(images2.A1,corners,'bicubic');
    images3.A2 = transform_image_LO_TO(images2.A2,corners,'bicubic');
    images3.M1 = transform_image_LO_TO(images2.M1,corners,'bicubic');
    images3.M2 = transform_image_LO_TO(images2.M2,corners,'bicubic');
    images3.M3 = transform_image_LO_TO(images2.M3,corners,'bicubic');
    images3.W1 = transform_image_LO_TO(images2.W1,corners,'bicubic');
    images3.W2 = transform_image_LO_TO(images2.W2,corners,'bicubic');
    images3.CO = transform_image_LO_TO(images2.CO,corners,'bicubic');
    images3.CO2=images3.CO;
    images3.CO2(find(images3.CO2<0.1))=0;
    images3.COmask = double(images3.CO>=0.1);
    images3.areasImg = round(transform_image_LO_TO(images2.areasImg,corners,'nearest'));
elseif size(corners,2)==5 % V1-3
    images3.A1 = transform_image_V1_V3(images2.A1,corners,'bicubic');
    images3.A2 = transform_image_V1_V3(images2.A2,corners,'bicubic');
    images3.M1 = transform_image_V1_V3(images2.M1,corners,'bicubic');
    images3.M2 = transform_image_V1_V3(images2.M2,corners,'bicubic');
    images3.M3 = transform_image_V1_V3(images2.M3,corners,'bicubic');
    images3.W1 = transform_image_V1_V3(images2.W1,corners,'bicubic');
    images3.W2 = transform_image_V1_V3(images2.W2,corners,'bicubic');
    images3.CO = transform_image_V1_V3(images2.CO,corners,'bicubic');
    images3.CO2=images3.CO;
    images3.CO2(find(images3.CO2<0.1))=0;
    images3.COmask = double(images3.CO>=0.1);
    images3.areasImg = round(transform_image_V1_V3(images2.areasImg,corners,'nearest'));
end

if ~notDefined('images3')
    images3.M1(find(isnan(images3.CO)))=0;
    images3.M2(find(isnan(images3.CO)))=0;
    images3.M3(find(isnan(images3.CO)))=0;
    images3.CO(find(isnan(images3.CO)))=0;

    images3.M1(find(images3.CO>1))=0;
    images3.M2(find(images3.CO>1))=0;
    images3.M3(find(images3.CO>1))=0;
    images3.CO(find(images3.CO>1))=0;
end

if size(corners,2)==6 % LO-1/2, TO-1/2
%     corners2 =  [30 40; 90 40; 90 80; 30 80; 30 40; 45 40; 45 80; 60 80; 60 40; 75 40; 75 80];
    corners2 =  [30 40; 105 40; 105 80; 105 40; 90 40; 90 80; 30 80; 30 40; 45 40; 45 80; 60 80; 60 40; 75 40; 75 80; 90 80; 105 80];
else % V1-3
    corners2 =  [15 40; 105 40; 105 80; 15 80; 15 40; 30 40; 30 80; 45 80; 45 40; 75 40; 75 80; 90 80; 90 40];
end

tmp=load('WedgeMapLeft');
figure
subplot(331)
imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images.M2.*double(images.CO>0.1),dEdges,[tmp.modeInformation.cmap(119:128,:);tmp.modeInformation.cmap(65:118,:)],2*pi))
% contourf(images.M2.*double(images.CO>0.1))
set(gca,'YDir','reverse');
axis equal
axis([1 images_size 1 images_size])
title('Angle map with fitted atlas')
set(gca,'XTickLabel','')
set(gca,'YTickLabel','')
axis off

subplot(332)
hold on
imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images2.M2.*double(images2.CO>0.1),[],[tmp.modeInformation.cmap(119:128,:);tmp.modeInformation.cmap(65:118,:)],2*pi))
for ii=1:size(corners,2)
    plot([corners{ii}(:,1);corners{ii}(1,1)],[corners{ii}(:,2);corners{ii}(1,2)],'k','LineWidth',2)
end
if size(corners,2)==5
    plot([corners{5}(:,1);corners{5}(1,1)],[corners{5}(:,2);corners{5}(1,2)],'k','LineWidth',2)
end
% plot([corners{7}(:,1);corners{7}(1,1)],[corners{7}(:,2);corners{7}(1,2)],'k','LineWidth',2)
set(gca,'YDir','reverse');
axis equal
axis([1 images_size 1 images_size])
title('Angle (raw data space)')
title('Deformed angle map with initial atlas')
set(gca,'XTickLabel','')
set(gca,'YTickLabel','')
axis off

if ~notDefined('images3')
    subplot(333)
    hold on
    imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images3.M2.*double(images3.CO>0.1),[],[tmp.modeInformation.cmap(119:128,:);tmp.modeInformation.cmap(65:118,:)],2*pi))
    plot(corners2(:,1),corners2(:,2),'k','LineWidth',2)
    set(gca,'YDir','reverse');
    axis equal
    axis([1 120 1 120])
    title('Deformed angle map with common atlas')
    set(gca,'XTickLabel','')
    set(gca,'YTickLabel','')
    axis off
end

subplot(334)
imagesc(0:images_size/127:images_size,0:images_size/127:images_size,mergedImage(images.M1.*double(images.CO>0.1),dEdges,hsvTbCmap(0,256), 15))
set(gca,'YDir','reverse');
axis equal
axis([1 images_size 1 images_size])
title('Eccentricity map with fitted atlas')
set(gca,'XTickLabel','')
set(gca,'YTickLabel','')
axis off

subplot(335)
hold on
imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images2.M1.*double(images2.CO>0.1),[],hsvTbCmap(0,256),15))
for ii=1:size(corners,2)
    plot([corners{ii}(:,1);corners{ii}(1,1)],[corners{ii}(:,2);corners{ii}(1,2)],'k','LineWidth',2)
end
set(gca,'YDir','reverse');
axis equal
axis([1 images_size 1 images_size])
title('Deformed eccentricity map with initial atlas')
set(gca,'XTickLabel','')
set(gca,'YTickLabel','')
axis off

if ~notDefined('images3')
    subplot(336)
    hold on
    imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images3.M1.*double(images3.CO>0.1),[],hsvTbCmap(0,256),15))
    plot(corners2(:,1),corners2(:,2),'k','LineWidth',2)
    set(gca,'YDir','reverse');
    axis equal
    axis([1 120 1 120])
    title('Deformed eccentricity map with common atlas')
    set(gca,'XTickLabel','')
    set(gca,'YTickLabel','')
    axis off
end

tmp=cool_springCmap;
subplot(337)
imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images.M3.*double(images.CO>0.1),dEdges,tmp(129:224,:), 15))
set(gca,'YDir','reverse');
axis equal
axis([1 images_size 1 images_size])
title('pRF size map with fitted atlas')
set(gca,'XTickLabel','')
set(gca,'YTickLabel','')
axis off

subplot(338)
hold on
imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images2.M3.*double(images2.CO>0.1),[],tmp(129:224,:),15))
for ii=1:size(corners,2)
    plot([corners{ii}(:,1);corners{ii}(1,1)],[corners{ii}(:,2);corners{ii}(1,2)],'k','LineWidth',2)
end
set(gca,'YDir','reverse');
axis equal
axis([1 images_size 1 images_size])
title('Deformed pRF size map with initial atlas')
set(gca,'XTickLabel','')
set(gca,'YTickLabel','')
axis off

if ~notDefined('images3')
    subplot(339)
    hold on
    imagesc(1:(images_size-1)/(2^maxLevel-1):images_size,1:(images_size-1)/(2^maxLevel-1):images_size,mergedImage(images3.M3.*double(images3.CO>0.1),[],tmp(129:224,:),15))
    plot(corners2(:,1),corners2(:,2),'k','LineWidth',2)
    set(gca,'YDir','reverse');
    axis equal
    axis([1 120 1 120])
    title('Deformed pRF size map with common atlas')
    set(gca,'XTickLabel','')
    set(gca,'YTickLabel','')
    axis off

    % mask = (images3.CO>0.1).*(images3.M2>pi/2-pi/4).*(images3.M2<pi/2*3+pi/4);
    mask = double(images3.CO>0.1).*double(double(images3.CO<=1));
    mask_nan = mask;
    mask_nan(find(mask==0))=nan;

    % Angle modulation averaged across iso-ecc lines
    polar_ave=nan(3,size(round(10/(images_size-1)*(2^maxLevel-1)):round(110/(images_size-1)*(2^maxLevel-1)),2));
    polar_min=nan(3,size(round(10/(images_size-1)*(2^maxLevel-1)):round(110/(images_size-1)*(2^maxLevel-1)),2));
    polar_max=nan(3,size(round(10/(images_size-1)*(2^maxLevel-1)):round(110/(images_size-1)*(2^maxLevel-1)),2));
    for i=round(10/(images_size-1)*(2^maxLevel-1)):round(110/(images_size-1)*(2^maxLevel-1))
        for k=1:3
            tmp=[];
            for j=round((80-k*10)/(images_size-1)*(2^maxLevel-1)):round((90-k*10)/(images_size-1)*(2^maxLevel-1))
                % select the data for the averaging
                if mask(j,i)==1
                    tmp = [tmp [images3.M2(j,i);images3.CO(j,i)]];
                end
            end
            if sum(size(tmp))>0
                polar_ave(k,i-round(10/(images_size-1)*(2^maxLevel-1))+1)=sum(tmp(1,:).*tmp(2,:))/sum(tmp(2,:));
                polar_min(k,i-round(10/(images_size-1)*(2^maxLevel-1))+1)=min(tmp(1,:));
                polar_max(k,i-round(10/(images_size-1)*(2^maxLevel-1))+1)=max(tmp(1,:));
            end
        end
    end

    % Ecc modulation averaged across iso-polar lines
    ecc_ave=nan(area_num,44);
    for j=round(40/(images_size-1)*(2^maxLevel-1)):round(80/(images_size-1)*(2^maxLevel-1))
        for k=1:area_num
            tmp=[];
            for i=round((15+k*15)/(images_size-1)*(2^maxLevel-1)):round((30+k*15)/(images_size-1)*(2^maxLevel-1))
                if mask(j,i)==1
                    tmp = [tmp [images3.M1(j,i);images3.CO(j,i)]];
                end
            end
            if sum(size(tmp))>0
                ecc_ave(k,round(80/(images_size-1)*(2^maxLevel-1))+1-j)=sum(tmp(1,:).*tmp(2,:))/sum(tmp(2,:));
            end
        end
    end

    upper_model = [pi:pi/2/16:pi*1.5 pi*1.5-pi/2/16:-pi/2/16:pi pi+pi/2/16:pi/2/16:pi*1.5 pi*1.5-pi/2/16:-pi/2/16:pi];
    lower_model = [pi:pi/2/16:pi*1.5 pi*1.5-pi/2/16:-pi/2/16:pi pi+pi/2/16:pi/2/16:pi*1.5 pi*1.5-pi/2/16:-pi/2/16:pi]-pi*0.5;
    hemi_model = [pi*0.5:pi/16:pi*1.5 pi*1.5-pi/16:-pi/16:pi*0.5 pi*0.5+pi/16:pi/16:pi*1.5 pi*1.5-pi/16:-pi/16:pi*0.5];

    % Phase plot along iso-ecc lines and ecc plot along iso-polar lines
    figure
    for k=1:3
        subplot(2,3,k)
        hold on
        plot(-20:100/(round(110/(images_size-1)*(2^maxLevel-1))-round(10/(images_size-1)*(2^maxLevel-1))):80, ...
            images3.M2(round((80-k*10)/(images_size-1)*(2^maxLevel-1)):round((90-k*10)/(images_size-1)*(2^maxLevel-1)),round(10/(images_size-1)*(2^maxLevel-1)):round(110/(images_size-1)*(2^maxLevel-1)))' ...
            .*mask_nan(round((80-k*10)/(images_size-1)*(2^maxLevel-1)):round((90-k*10)/(images_size-1)*(2^maxLevel-1)),round(10/(images_size-1)*(2^maxLevel-1)):round(110/(images_size-1)*(2^maxLevel-1)))'-pi,'k','LineWidth',1)
        plot(-20:100/(round(110/(images_size-1)*(2^maxLevel-1))-round(10/(images_size-1)*(2^maxLevel-1))):80, polar_ave(k,:)-pi,'r','LineWidth',2)
        %     plot(0:70/74:70, polar_min(k,:)-pi,'r:','LineWidth',2)
        %     plot(0:70/74:70, polar_max(k,:)-pi,'r:','LineWidth',2)
        %     plot(0:80/85:80, upper_model-pi,'b','LineWidth',2);
        %     plot(0:80/85:80, lower_model-pi,'g','LineWidth',2);
        %     plot(0:80/85:80, hemi_model-pi,'m','LineWidth',2);
        xlabel('Atlas distance')
        ylabel('Angle from HM')
        if size(corners,2)==6 % LO,TO
            xlim([-10 70])
        else
            xlim([-20 80])
        end
        ylim([-2 2])
        if size(corners,2)==6 % LO,TO
            text(3,1.5,'LO1')
            text(18,1.5,'LO2')
            text(33,1.5,'TO1')
            text(48,1.5,'TO2')
        else
            text(-10,1.8,'V3v')
            text(5,1.8,'V2v')
            text(28,1.8,'V1')
            text(50,1.8,'V2d')
            text(65,1.8,'V3d')
        end

        if k==1
            title('Ecc = 0-4 deg')
        elseif k==2
            title('Ecc = 4-8 deg')
        else
            title('Ecc = 8-12 deg')
        end
    end

    for k=1:area_num
        subplot(2,area_num,area_num+k)
        hold on
        plot(40:-40/(round(80/(images_size-1)*(2^maxLevel-1))-round(40/(images_size-1)*(2^maxLevel-1))):0, ...
            images3.M1(round(40/(images_size-1)*(2^maxLevel-1)):round(80/(images_size-1)*(2^maxLevel-1)),round((15+15*k)/(images_size-1)*(2^maxLevel-1)):round((30+15*k)/(images_size-1)*(2^maxLevel-1))) ...
            .*mask_nan(round(40/(images_size-1)*(2^maxLevel-1)):round(80/(images_size-1)*(2^maxLevel-1)),round((15+15*k)/(images_size-1)*(2^maxLevel-1)):round((30+15*k)/(images_size-1)*(2^maxLevel-1))),'k','LineWidth',1)
        plot(0:(round(80/(images_size-1)*(2^maxLevel-1))-round(40/(images_size-1)*(2^maxLevel-1))):40, ecc_ave(k,:),'r','LineWidth',2)
        xlabel('Atlas distance')
        ylabel('Eccentricity (deg)')
        xlim([0 40])
        ylim([0 15])
        if size(corners,2)==6 % LO,TO
            if k==1
                title('LO1')
            elseif k==2
                title('LO2')
            elseif k==3
                title('TO1')
            else
                title('TO2')
            end
        elseif size(corners,2)==5 % V1-3
            if k==1
                title('V3v')
            elseif k==2
                title('V2v')
            elseif k==3
                title('V1')
            elseif k==4
                title('V2d')
            else
                title('V3d')
            end
        end
    end
end
return;


function [past_track_plot, future_track_plot, other_eddy_plots] = ...
    plot_tracks( hdls, past_tracks, future_tracks, past_track_color, future_track_color )
%PLOT_TRACKS Plot past tracks, future tracks and eddies in the past and future (not the current ones)

if isempty(past_tracks)
    past_tracks = [NaN NaN NaN]; % Plotm requires the input to be not empty
end
past_track_plot =  plotm(past_tracks(:, 1:2), 'Color', past_track_color, 'Parent', hdls.mapAx);

if isempty(future_tracks)
    future_tracks = [NaN NaN NaN]; % Plotm requires the input to be not empty
end
future_track_plot =  plotm(future_tracks(:, 1:2), 'Color', future_track_color, 'Parent', hdls.mapAx);

% Plot other eddies by types
other_eddy_plots = plot_eddy_by_type(hdls, [past_tracks;future_tracks], hdls.constants.eddy_types, ...
    hdls.constants.eddy_markers, hdls.constants.eddy_colors, hdls.constants.OTHER_EDDY_SIZE, false);

end


function [cropsResized, preparationTime, resizeTime] = cropImagePatches(curImage, boundingBoxes, cropPad, outputSize, maxGpuImages, cropMode, jitterStd)
%cropImagePatches crops patches from an image and resizes them to the standard size
%
% cropsResized = cropImagePatches(curImage, boundingBoxes, cropPad, outputSize);
% [cropsResized, cropTime, resizeTime] = cropImagePatches(curImage, boundingBoxes, cropPad, outputSize, maxGpuImages, cropMode, jitterStd);
%
% Input:
%   cropImage - image to crop from (has to be single)
%   boundingBoxes - bounding boxes in format [y1 x1 y2 x2]. x is col, y is row
%   cropPad - padding of the bounding boxes (padding is measure after crop and resize), format: left, top, right, bottom
%   outputSize - target size of the crops
%   maxGpuImages - maximal number of patches to crop on a GPU at the same time (default : 256)
%   cropMode - 'square' or 'warp' (mimicing the R-CNN cropping code) (default: 'warp')
%   jitterStd - ammount of jittering to do: std of a gaussian distribution of a patch-border shift (default: 0)
%
% Output:
%   cropsResized - the cropped patches
%   preparationTime - time on the computations of the crop parameters
%   resizeTime - time spent on cropping and resizing
%
% This function depends on cropRectanglesMex

tStart = tic;

if ~exist( 'maxGpuImages', 'var') || isempty(maxGpuImages)
    maxGpuImages = 256;
end
if ~exist( 'cropMode', 'var') || isempty(cropMode)
    cropMode = 'warp';
end
if ~exist('jitterStd', 'var') || isempty(jitterStd)
    jitterStd = 0;
end
useSquare = false;
if isequal( cropMode, 'square')
    useSquare = true;
end

numChannels = size(curImage, 3);
numBoxes = size( boundingBoxes, 1 );
if numBoxes <= maxGpuImages
    resultOnGpuGlag = true;
else
    resultOnGpuGlag = false;
end

cropBoxes = nan(numBoxes, 4);
for iBox = 1 : numBoxes
    %% get crop position: take padding into account
    leftBorder = boundingBoxes(iBox, 2);
    rightBorder = boundingBoxes(iBox, 4);

    topBorder = boundingBoxes(iBox, 1);
    bottomBorder = boundingBoxes(iBox, 3);

    % add the square mode
    if useSquare
        halfWidth = (rightBorder - leftBorder + 1 ) / 2;
        halfHeight = (bottomBorder - topBorder + 1 ) / 2;

        centerX = leftBorder + halfWidth;
        centerY = topBorder + halfHeight;

        if halfHeight > halfWidth
            halfWidth = halfHeight;
        else
            halfHeight = halfWidth;
        end

        topBorder = centerY - halfHeight;
        bottomBorder = centerY + halfHeight;
        leftBorder = centerX - halfWidth;
        rightBorder = centerX + halfWidth;
    end

    % compute the transformation
    posOld = [  topBorder, leftBorder, 1; ... % top left corner
        topBorder, rightBorder, 1; ... % top right corner
        bottomBorder, leftBorder, 1; ... % bottom left corner
        bottomBorder, rightBorder, 1; ... % bottom right corner
        ]';

    % crop corners after padding
    posNew = [  1 + cropPad(2), 1 + cropPad(1), 1; ... % top left corner
        1 + cropPad(2), outputSize(2) - cropPad(3), 1; ... % top right corner
        outputSize(1) - cropPad(4), 1 + cropPad(1), 1; ... % bottom left corner
        outputSize(1) - cropPad(4), outputSize(2)- cropPad(3), 1; ... % bottom right corner
        ]';

    % solve linear system
    M = posOld / posNew;

    % actual crop corners
    posNew = [  1, 1, 1; ... % top left corner
        1, outputSize(2), 1; ... % top right corner
        outputSize(1), 1, 1; ... % bottom left corner
        outputSize(1), outputSize(2), 1; ... % bottom right corner
        ]';

    cropPos = (M * posNew)';

    leftBorder = (cropPos(1, 2) + cropPos(3, 2)) / 2;
    rightBorder = (cropPos(2, 2) + cropPos(4, 2)) / 2;

    topBorder = (cropPos(1, 1) + cropPos(2, 1)) / 2;
    bottomBorder = (cropPos(3, 1) + cropPos(4, 1)) / 2;

    cropBoxes(iBox, 1) = topBorder;
    cropBoxes(iBox, 2) = leftBorder;
    cropBoxes(iBox, 3) = bottomBorder;
    cropBoxes(iBox, 4) = rightBorder;

end

%% apply jittering
borderNoise = randn( size( cropBoxes) ) * jitterStd;
cropBoxes = cropBoxes + borderNoise;
% check if the bouding boxes are still valid. If not (might happen if the size of the box is too small) than remove jittering
badMask = cropBoxes(:, 1) > cropBoxes(iBox, 3) | cropBoxes(:, 2) > cropBoxes(iBox, 4);
cropBoxes( badMask, : ) = cropBoxes( badMask, : ) - borderNoise( badMask, : );

preparationTime = toc(tStart);

%% crop the image
if ~isequal( class( curImage ), 'single')
    curImage = single(curImage);
end
if resultOnGpuGlag
    cropsResized = cropRectanglesMex( curImage, cropBoxes, outputSize );
    cropsResized = single(cropsResized);
else
    cropsResized = zeros(outputSize(1), outputSize(2), numChannels, numBoxes, 'single');
    for iBatchStart = 1 : maxGpuImages : numBoxes
        curIds = iBatchStart : 1 : min( numBoxes, iBatchStart + maxGpuImages - 1);
        curBoxes = cropBoxes( curIds, : );
        curCrops = cropRectanglesMex( curImage, curBoxes, outputSize );
        cropsResized(:,:,:,curIds) = gather(curCrops);
    end
    cropsResized = single(cropsResized);
end

resizeTime = toc(tStart) - preparationTime;

end


function [muX, varX, model]  = vargplvmDynamicsUpdateModelTestVar(model, x, varx, y)
% VARGPLVMDYNAMICSUPDATEMODELTESTVAR return the original variational means and
% variances for the test data and update the model to be prepared for
% prediction
% DESC in the dynamical vargplvm the original variational parameters are
% reparametrized so that the optimiser optimises the new free ones and then
% these are mapped back to the original ones. This function takes the new
% free parameters which are optimised and returns the original ones. Also,
% because there is coupling in the variational parameters (because the
% original ones are obtained via a nonlinear operation on the free ones
% also involving Kt) after introducing test points (which have their own
% latent positions) the whole variational distribution is changed.
% Therefore, the updated model is also returned.
% ARG model : The vargplvm model which contains dynamics.
% ARG x : the optimised variational means (the free parameters, not the ones) of the test points
% ARG varx : the optimised variational variances (the free parameters, not the
% ones) of the test points
% ARG y: the test points
% RETURN muX : the original,true variational means corresponding to x
% RETURN varX : the original,true variational variances corresponding to varx
% RETURN model : the updated model, prepared for prediction

%
% COPYRIGHT : Michalis K. Titsias, 2009-2011
% COPYRIGHT : Neil D. Lawrence, 2009-2011
%
% VARGPLVM

Nstar = size(x,1);
N = model.N;

if isfield(model.dynamics, 'seq') && ~isempty(model.dynamics.seq)
   mask = sum(isnan(y),2);
   indexObservedData = find(mask==0)';
   yOb = y(indexObservedData, :);

   modelTest = model;
   modelTest.dynamics.t = model.dynamics.t_star;
   modelTest.dynamics.vardist.means = x;
   modelTest.dynamics.vardist.covars = varx;
   modelTest.dynamics.N = size(modelTest.dynamics.vardist.means, 1);
   modelTest.vardist.numData = modelTest.dynamics.N;
   modelTest.vardist.nParams = 2*prod(size(modelTest.dynamics.vardist.means));
   Kt = kernCompute(model.dynamics.kern, model.dynamics.t_star);
   modelTest.dynamics.Kt = Kt;
   modelTest.dynamics.fixedKt = 1;
   % This enables the computation of the variational means and covars
   modelTest.dynamics.seq = [];
   modelTest = vargpTimeDynamicsUpdateStats(modelTest);

   % Variational means and covars for the test block data
   % latent variables
   muX = modelTest.vardist.means;
   varX = modelTest.vardist.covars;

   if ~isempty(yOb)
      % Since the test block also might have a fully observed subblock, you
      % need to update the model structure (this for the subseqeucent prediction
      % using the PostMeanVar function)
      vardistOb = modelTest.vardist;
      vardistOb.means = modelTest.vardist.means(indexObservedData, :);
      vardistOb.covars = modelTest.vardist.covars(indexObservedData, :);
      vardistOb.nParams = 2*prod(size(vardistOb.means));
      vardistOb.numData = size(vardistOb.means,1);
      % Psi statistics for the data of the new block/sequence which have a fully
      % observed features/dimensions
      obsPsi0 = kernVardistPsi0Compute(model.kern, vardistOb);
      obsPsi1 = kernVardistPsi1Compute(model.kern, vardistOb, model.X_u);
      obsPsi2 = kernVardistPsi2Compute(model.kern, vardistOb, model.X_u);
      model.N = model.N + size(yOb,1);
      myOb = yOb - repmat(model.bias,size(yOb,1),1);
      myOb = myOb./repmat(model.scale,size(yOb,1),1);
      model.m = [model.m; myOb];
      model.dynamics.t = [model.dynamics.t; model.dynamics.t_star(indexObservedData)];
      Kt = zeros(N+size(indexObservedData,2),N+size(indexObservedData,2));
      Kt(1:N,1:N) = model.dynamics.Kt;
      tmpKt = modelTest.dynamics.Kt(indexObservedData,:);
      tmpKt = tmpKt(:,indexObservedData);
      Kt(N+1:end, N+1:end) = tmpKt;
      model.dynamics.Kt = Kt;
      model.dynamics.seq = [model.dynamics.seq, (N+size(indexObservedData,2))];

      model.Psi1 = [model.Psi1; obsPsi1];
      model.Psi2 = model.Psi2 + obsPsi2;
      model.Psi0 = model.Psi0 + obsPsi0;

      % dynamics (reparametrized) variational parameters (barmu and lambdas)
      model.dynamics.vardist.means = [model.dynamics.vardist.means; x(indexObservedData,:)];
      model.dynamics.vardist.covars = [model.dynamics.vardist.covars; varx(indexObservedData,:)];
      model.dynamics.N = size(model.dynamics.vardist.means, 1);
      model.dynamics.vardist.transforms.index = (size(model.dynamics.vardist.means,2)*model.N+1):size(model.dynamics.vardist.means,2)*model.N*2;
      model.dynamics.vardist.numData =  size(model.dynamics.vardist.means, 1);
      model.dynamics.vardist.nParams = 2*prod(size(model.dynamics.vardist.means));

      % the actual variational distrbution
      model.vardist.means = [model.vardist.means; vardistOb.means];
      model.vardist.covars = [model.vardist.covars; vardistOb.covars];
      model.vardist.transforms.type = model.vardist.transforms.type;
      model.vardist.transforms.index = (size(model.vardist.means,2)*model.N+1):size(model.vardist.means,2)*model.N*2;
      model.vardist.numData =  size(model.vardist.means, 1);
      model.vardist.nParams = 2*prod(size(model.vardist.means));

      % update in the model structure useful for prediction
      model.C = model.invLm * model.Psi2 * model.invLmT;
      model.TrC = sum(diag(model.C)); % Tr(C)
      model.At = (1/model.beta) * eye(size(model.C,1)) + model.C; % At = beta^{-1} I + C
      model.Lat = jitChol(model.At)';
      model.invLat = model.Lat\eye(size(model.Lat,1));
      model.invLatT = model.invLat';
      model.logDetAt = 2*(sum(log(diag(model.Lat)))); % log |At|
      model.P1 = model.invLat * model.invLm; % M x M
      model.P = model.P1 * (model.Psi1' * model.m);
      model.TrPP = sum(sum(model.P .* model.P));
   end
else
   % Augment the time vector to include the timestamp of the new point
   model.dynamics.t = [model.dynamics.t; model.dynamics.t_star];
   % Augment the reparametrized variational parameters mubar and lambda
   model.dynamics.vardist.means = [model.dynamics.vardist.means; x];
   model.dynamics.vardist.covars = [model.dynamics.vardist.covars; varx];
   model.dynamics.N =  model.dynamics.N + Nstar;
   model.vardist.numData = model.dynamics.N;
   model.dynamics.vardist.numData = model.dynamics.N;
   model.vardist.nParams = 2*prod(size(model.dynamics.vardist.means));
   model.dynamics.vardist.nParams = 2*prod(size(model.dynamics.vardist.means));
   model.dynamics.seq = model.dynamics.N; %%%%%% Chec
   model = vargpTimeDynamicsUpdateStats(model);
   vardist2 = model.vardist;
   vardist2.means = model.vardist.means(1:end-Nstar,:);
   vardist2.covars = model.vardist.covars(1:end-Nstar,:);
   vardist2.nParams = 2*prod(size(vardist2.means));
   vardist2.numData = size(vardist2.means,1);
   model.Psi0 = kernVardistPsi0Compute(model.kern, vardist2);
   model.Psi1 = kernVardistPsi1Compute(model.kern, vardist2, model.X_u);
   model.Psi2 = kernVardistPsi2Compute(model.kern, vardist2, model.X_u);
   model.C = model.invLm * model.Psi2 * model.invLmT;
   model.TrC = sum(diag(model.C)); % Tr(C)
   model.At = (1/model.beta) * eye(size(model.C,1)) + model.C; % At = beta^{-1} I + C
   model.Lat = jitChol(model.At)';
   model.invLat = model.Lat\eye(size(model.Lat,1));
   model.invLatT = model.invLat';
   model.logDetAt = 2*(sum(log(diag(model.Lat)))); % log |At|
   model.P1 = model.invLat * model.invLm; % M x M
   model.P = model.P1 * (model.Psi1' * model.m);
   model.TrPP = sum(sum(model.P .* model.P));

   % Variational means and covars for the test block data
   % latent variables
   muX = model.vardist.means(N+1:end,:);
   varX = model.vardist.covars(N+1:end,:);

end

model.X = model.vardist.means;


function dataNdCombined = combine(this, dataNdArray, combineDims, tolerance)
% combines multiple n-dim. datasets into a single one along specified
% dimensions. Makes sure data is sorted into right place according to
% different dimInfos
%
% NOTE: inverse operation of MrDataNd.split
%
%   Y = MrDataNd()
%   dataNdCombined = Y.combine(dataNdArray, combineDims, tolerance)
%
% This is a method of class MrDataNd.
%
% IN
%   dataNdArray     cell(nDatasets,1) of MrDataNd to be combined
%   combineDims     [1, nCombineDims] vector of dim indices to be combined
%                       OR
%                   cell(1, nCombineDims) of dimLabels to be combined
%                   NOTE: If specified dimLabels do not exist, new
%                   dimensions are created with these names and default
%                   samplingPoints (1:nDatasets)
%                   default: all singleton dimensions (i.e. dims with one
%                   sample only within each individual dimInfo)
%                   NOTE: if a non-singleton dimension is given, images are
%                         concatenated along this dimension
%
%   tolerance                   dimInfos are only combined, if their
%                               information is equal for all but the
%                               combineDims (because only one
%                               representation is retained for those,
%                               usually from the first of the dimInfos).
%                               However, sometimes numerical precision,
%                               e.g., rounding errors, preclude the
%                               combination. Then you can increase this
%                               tolerance;
%                               default: single precision (eps('single')
%                               ~1.2e-7)
% OUT
%
% EXAMPLE
%   combine
%
%   See also MrDataNd MrDimInfo.combine MrDataNd.split

% Author:   Lars Kasper
% Created:  2018-05-16
% Copyright (C) 2018 Institute for Biomedical Engineering
%                    University of Zurich and ETH Zurich
%
% This file is part of the TAPAS UniQC Toolbox, which is released
% under the terms of the GNU General Public License (GPL), version 3.
% You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version).
% For further details, see the file COPYING or
%  <http://www.gnu.org/licenses/>.

if nargin < 4
    tolerance = eps('single');
end

% create new
dataNdCombined = this.copyobj();

%% 1) dimInfoCombined = Y.combine(dimInfoArray, combineDims)
doCombineSingletonDims = nargin < 3;
if doCombineSingletonDims
    indSplitDims = this.dimInfo.get_singleton_dimensions();
    combineDims = this.dimInfo.dimLabels(indSplitDims);
else
    % for 1-dim case, make cell
    if ~iscell(combineDims) && isstr(combineDims)
        combineDims = {combineDims};
    end
end

%% Create dimInfoArray from all data and combine it first
dimInfoArray = cellfun(@(x) x.dimInfo.copyobj(), dataNdArray, 'UniformOutput', false);

% if dimLabels not existing previously, add as new dimensions
for iDim = 1:numel(combineDims)
    combineDim = combineDims{iDim};
    % new dimension that did not exist in dimInfo
    if isempty(this.dimInfo.get_dim_index(combineDim))
        cellfun(@(x,y) x.add_dims(combineDim, 'units', 'nil', ...
            'samplingPoints', y), dimInfoArray, ...
            num2cell(1:size(dataNdArray, iDim))', ...
            'UniformOutput', false);
    end
end

[dimInfoCombined, indSamplingPointCombined] = dimInfoArray{1}.combine(...
    dimInfoArray, combineDims, tolerance);

%% Loop over all splits dataNd and put data into right place, as defined by combined DimInfo
if ~isempty(combineDims) % otherwise, nothing to do hear
    % dimInfo sampling points
    indSplitDims        = dimInfoArray{1}.get_dim_index(combineDims);
    nSplits             = numel(dataNdArray);
    dataMatrixCombined  = nan(dimInfoCombined.nSamples);
    for iSplit = 1:nSplits
        % write out indices to be filled in final array, e.g. tempData(:,:,sli, dyn)
        % would be {':', ':', sli, dyn}
        index = repmat({':'}, 1, dimInfoCombined.nDims);
        index(indSplitDims) = indSamplingPointCombined(iSplit,:);
        dataMatrixCombined(index{:}) = dataNdArray{iSplit}.data;
    end

    %% assemble the output object
    dataNdCombined.dimInfo = dimInfoCombined;
    dataNdCombined.data = dataMatrixCombined;
end
end