function eddyscan_compiled_script(file_name, file_path, save_path, varargin)%#codegen
if ~strcmp(file_path(end), '/')
    file_path = strcat(file_path, '/');
end
if ~strcmp(save_path(end), '/')
    save_path = strcat(save_path, '/');
end
vars = load('area_map.mat');
area_map = vars.area_map;
[dir, rem] = strtok(file_name, '/');
filename = strtok(rem, '/');
eddy_dir = [save_path, dir];
indices = regexp(filename, '[0-9]');
nums = filename(indices);
date = nums(1:8);
eddy_file = ['anticyc_', date, '.mat'];
if exist(eddy_dir, 'dir')
    cd(eddy_dir);
    if exist(eddy_file, 'file')
        disp('Eddy file detected, quitting.');
        %quit;
    end
end
cd([file_path, dir]);
if exist([file_path, dir, '/', filename], 'file') && ~exist([save_path, dir, '/', eddy_file], 'file')
    ssh = ncread(filename, 'sla')';
    lat = double(ncread(filename, 'lat'));
    lon = double(ncread(filename, 'lon'));
    cd(save_path);
    ant_eddies = scan_single(ssh, lat, lon, date, 'anticyc', 'v2', area_map, varargin{:});%#ok
    cyc_eddies = scan_single(ssh, lat, lon, date, 'cyclonic', 'v2', area_map, varargin{:});%#ok
    if ~exist([save_path, dir], 'dir')
        mkdir([save_path, dir]);
    end
    cd([save_path, dir]);
    save(['anticyc_', date, '.mat'], 'ant_eddies');
    save(['cyclonic_', date, '.mat'], 'cyc_eddies');
else

end
%quit;
end

function [ eddies ] = scan_single( ssh, lat, lon, date, cyc, scan_type, areamap, varargin )
%SCAN_SINGLE Wrapper function to do scanning
% ssh: ssh slice with nans for land, size should be [length(lat) length(lon)]
% lat: 1D array of the latitudes of ssh grid
% lon: 1D array of the longitudes of ssh grid
% cyc: 'anticyc' or 'cyclonic'
% scan_type: 'v1', 'v2', 'hybrid'
%         v1: Will run top-down scanning (only works with full data of 0.25 x 0.25 ssh grid)
%         v2: Will run bottom-up scanning from the minima of the field
%     hybrid: Will run v2 and v1 scanning and will take the union of the
%             two sets where, for common features, v2 bodies will be used
% areamap: A 2D array that refer to the area of each pixel in SSH data (should have same size as ssh), or 1D array
%   that refer to area of each pixel for a specific lat in a regular grid (pixeld have same area for the same
%   latitude)
% Optional parameters (only applicable for v2 eddyscan):
%   'minimumArea': minimum number of pixels for an eddy, used for validating eddies, default value is 9
%   'thresholdStep': the minimum step for thresholding, the unit is SSH's unit, default value is 0.05
%   'isPadding': whether or not to pad SSH data, should be true when scanning SSH data with the longitudes expanding the
%   whole world dmap. Set to false if only partial SSH data is used. Default value is true
    if ~any(isnan(ssh(:)))
        error('Invalid ssh data, must contain NaNs for land values');
    end

    if ~all(size(ssh) == [length(lat) length(lon)])
        error('Invalid ssh data size, should be [length(lat) length(lon]');
    end

    if ~all(size(areamap) == size(ssh))
        % Not a 2d array with same size as ssh
        if ~any(size(areamap) == [1 1]) || length(areamap) ~= length(lat)
            disp('Invalid areamap, using NaN for eddy surface area');
        end
    end

    stype = get_stype(scan_type);
    ctype = get_ctype(cyc);
    disp('About to start scanning eddies');

    %oldpath = addpath('lib');
    switch stype
        case 1
            eddies = top_down_single(ssh, lat, lon, areamap, ctype, varargin{:});
        case 2
            eddies = bottom_up_single(ssh, lat, lon, areamap, ctype, varargin{:});
%        case 0
%            scanners = {@top_down_single, @bottom_up_single};
%            eddies_out = {[], []};
%            parfor i = 1:2
%                eddies_out{i} = scanners{i}(ssh, lat, lon, areamap, ctype, varargin{:});
%            end
%            eddies = get_combined_eddy_frames(eddies_out{2}, eddies_out{1}, ssh);
    end
    [eddies.Date] = deal(date);

    %path(oldpath);

end
%end

function cyc_t = get_ctype(cyc)
    switch cyc
        case 'anticyc'
            cyc_t = 1;
        case 'cyclonic'
            cyc_t = -1;
        otherwise
            error('cyc must be anticyc or cyclonic');
    end
end

function scan_t = get_stype(scan_type)
    switch scan_type
        case 'v1'
            scan_t = 1;
        case 'v2'
            scan_t = 2;
        case 'hybrid'
            scan_t = 0;
        otherwise
            error('scan_type must be v1, v2, or hybrid');
    end
end

function [ eddies ] = bottom_up_single(ssh_data, lat, lon, areamap, cyc, varargin)
%BOTTOM_UP_SINGLE Finds eddies using the Bottom Up method
%   Will return an array of struct's that contain the eddy data.
%   ssh_data: A 2D array of double's that contain the sea surface heights (latsxlons)
%   lat: A 1D array of double's that gives the latitude for a given index (dimension should match
%         that of ssh_data)
%   lon: A 1D array of double's that gives the longitude for a given index (dimension should match
%         that of ssh_data)
%   areamap: A 2D array that refer to the area of each pixel in SSH data (should have same size as ssh), or 1D array
%   that refer to area of each pixel for a specific lat in a regular grid (pixeld have same area for the same
%   latitude)
%   cyc: Pass 1 to output anticyclonic eddies or -1 to output cyclonic eddies
%   Optional parameters:
%   'minimumArea': minimum number of pixels for an eddy, used for validating eddies
%   'thresholdStep': the minimum step for thresholding, the unit is SSH's unit
%   'isPadding': whether or not to pad SSH data, should be true when scanning SSH data of the whole map. Set to false if
%   only partial SSH data is used.
%   'sshUnits': The units the SSH data is in. bottom_up_single is built to work natively on centimeter SSH data.
%   Valid parameters are 'meters' and 'centimeters'. If the paramater passed in is 'meters', the SSH data will
%   be multiplied by 100. No changes will be made if the paramater passed in is 'centimeters'.
%   The default value of 'sshUnits' is centimeters.
    p = inputParser;
    defaultMinPixelSize = 9;
    defaultThresholdStep = 0.05;
    defaultSSHUnits = 'centimeters';
    defaultPaddingFlag = true;
    addRequired(p, 'ssh_data');
    addRequired(p, 'lat');
    addRequired(p, 'lon');
    addRequired(p, 'areamap');
    addRequired(p, 'cyc');
    addParameter(p, 'minimumArea', defaultMinPixelSize);%, @isnumeric);
    addParameter(p, 'thresholdStep', defaultThresholdStep);%, @isnumeric);
    addParameter(p, 'isPadding', defaultPaddingFlag);
    addParameter(p, 'sshUnits', defaultSSHUnits);
    parse(p, ssh_data, lat, lon, areamap, cyc, varargin{:});
    minimumArea = p.Results.minimumArea;
    thresholdStep = p.Results.thresholdStep;
    isPadding = p.Results.isPadding;
    SSH_Units = p.Results.sshUnits;
    disp(minimumArea);
    if isa(minimumArea, 'char')
        disp('Minimum area was a string, converting to double.');
        minimumArea = str2double(minimumArea);
        disp(minimumArea);
    end
    if isa(thresholdStep, 'char')
        disp('Threshold step was a string, converting to double.');
        thresholdStep = str2double(thresholdStep);
        disp(thresholdStep);
    end
    if strcmp(SSH_Units, 'meters')
        ssh_data = ssh_data * 100;
    elseif strcmp(SSH_Units, 'centimeters')
        max_val = max(ssh_data(:));
        min_val = max(ssh_data(:));
        if max_val < 1 && min_val > -1
            ssh_data = ssh_data * 100;
        elseif max_val < 100 && min_val > -100

        else
            error('Could not figure out what units the SSH data provided is in. Please specify it as an additional parameter: sshUnits');
        end
    end

    %Check if the grid is regular (differences between lats and lons are equal)
    lat_diffs = lat(2:end) - lat(1:end-1);
    lat_diffs2 = lat_diffs(2:end) - lat_diffs(1:end-1);
    lon_diffs = lon(2:end) - lon(1:end-1);
    lon_diffs(lon_diffs <= -180) = lon_diffs(lon_diffs <= -180) + 360;
    lon_diffs(lon_diffs >= 180) = lon_diffs(lon_diffs >= 180) - 360;
    lon_diffs = abs(lon_diffs);
    lon_diffs2 = lon_diffs(2:end) - lon_diffs(1:end-1);
    if all(lat_diffs2 == 0) && all(lon_diffs2 == 0)
        % Regular grid, create a georasterref object to get eddy's centroid
        geo_raster_lat_limit = [lat(1) lat(end)];
        if lon(1) > lon(end)
            geo_raster_lon_limit = [lon(1) (360 + lon(end))];
        else
            geo_raster_lon_limit = [lon(1) lon(end)];
        end

        R = georasterref('LatLim', geo_raster_lat_limit, 'LonLim', geo_raster_lon_limit, 'RasterSize', ...
         size(ssh_data), 'ColumnsStartFrom', 'south', 'RowsStartFrom', 'west');
    else
        % Use normal indexing to get eddy's centroid
        R = [];
    end
    disp('About to get extrema');

    extrema = get_extrema(ssh_data, cyc);
    disp('Got extrema');
    if isPadding
        origExtrema = extrema;
        extrema = [zeros(size(extrema, 1), 200), extrema, zeros(size(extrema, 1), 200)];
        sshExtended = [ssh_data(:, end-199:end), ssh_data(:, :), ssh_data(:, 1:200)];
        [extrema_lat_indexes, extrema_lon_indexes] = ind2sub(size(extrema), find(extrema == 1));

        extrema(:, 1:200) = origExtrema(:, end-199:end);
        extrema(:, end-199:end) = origExtrema(:, 1:200);
    else
        [extrema_lat_indexes, extrema_lon_indexes] = ind2sub(size(extrema), find(extrema == 1));
        sshExtended = ssh_data;
    end

    disp('Scanning');
    eddies = new_eddy();
    eddies(length(extrema_lat_indexes)).Date = NaN;
    cyc_sshExtended = sshExtended * cyc;
    parfor i = 1:length(extrema_lat_indexes) % Normally a parfor. Modified to a for loop solely for itasca testing
        curr_lat_index = extrema_lat_indexes(i); curr_lon_index = extrema_lon_indexes(i);
        e = thresholdBU(cyc, curr_lat_index-5, curr_lat_index+5, curr_lon_index-5, curr_lon_index+5, ...
            sshExtended, extrema, curr_lat_index, curr_lon_index, sshExtended(curr_lat_index, curr_lon_index), ...
            thresholdStep, NaN, ...
            zeros(size(sshExtended)), lat, lon, R, areamap, minimumArea, isPadding, cyc_sshExtended);
        %disp(e);
        if ~isempty(e.Stats)
            %disp(['Adding eddy at index: ', num2str(i)]);
            %disp(e);
            eddies(i) = e;
        end
    end
    %lat_array = [eddies.Lat];
    %disp(lat_array);
    mask = false(1, length(eddies));
    for i = 1:length(eddies)
        if isempty(eddies(i).Lat)
    %        disp(['Empty lat array index at ', num2str(i)]);
            mask(i) = true;
        end
    end
    disp('Scanned');
    %mask = cellfun('isempty', {eddies.Lat});
    eddies = eddies(~mask);
    %lat_array = [eddies.Lat];
    %disp(lat_array);
end

function [eddy] = thresholdBU(cyc, block_bottom_index, block_top_index, block_left_index, block_right_index, ...
        ssh, extrema, extrema_lat_index, extrema_lon_index, thresh, threshold_step, last_step, previous, ...
        lat, lon, R, areamap, min_pixel_size, is_padding, cyc_ssh)
% THRESHOLDBU Get an eddy by bottom up method
%   cyc: 1 for anticyclonic and -1 for cyclonic
%   block_bottom(top/left/right)_index: index of the bottom/top/left/right of the block that will be used for
%   thresholding
%   ssh: ssh data(extended if is_padding is true)
%   extrema: logical index of ssh extrema
%   extrema_lat/lon_index: lat/lon index of the extremum that is being used to find an eddy
%   thresh: current threshold that is being used to find an eddy
%   threshold_step: the step to increase/decrease threshold value, based on eddy type
%   last_step: the last step was used for thresholding
%   previous: 2d logical array of the connected component that contains the extremum in the last thresholdBU call
%   lat: 1d array of latitudes of the ssh grid
%   lon: 1d array of longitudes of the ssh grid
%   R: the georasterref object for SSH grid
%   areamap: 1D or 2D array of reference to area of each pixel in SSH grid
%   min_pixel_size: minimum number of pixels for an eddy
%   is_padding: whether or not the SSH data is padded

    switch cyc
        case 1
            intensity = 'MaxIntensity';
        case -1
            intensity = 'MinIntensity';
        otherwise
            error('Invalid cyc');
    end

    if block_bottom_index < 1 || block_top_index > size(ssh, 1) || ...
            block_left_index < 1 || block_right_index > size(ssh, 2)
        edgeOfWorld = true;

        while block_bottom_index < 1 || block_top_index > size(ssh, 1) || ...
            block_left_index < 1 || block_right_index > size(ssh, 2)
            % Make sure that the block is inside the grid

            block_bottom_index = block_bottom_index + 1;
            block_top_index = block_top_index - 1;
            block_left_index = block_left_index + 1;
            block_right_index = block_right_index - 1;
            if block_top_index <= block_bottom_index + 2 || block_right_index <= block_left_index + 2
                % If the block is too small, just return an empty eddy
                eddy = new_eddy();
                return;
            end
        end

        block = ssh(block_bottom_index:block_top_index, block_left_index:block_right_index);

        extremaBlock = extrema(block_bottom_index:block_top_index, block_left_index:block_right_index);

    else
        edgeOfWorld = false;
        block = ssh(block_bottom_index:block_top_index, block_left_index:block_right_index);
        extremaBlock = extrema(block_bottom_index:block_top_index, block_left_index:block_right_index);
    end

    if isnan(last_step)
        step = threshold_step;
    else
        step = last_step;
    end

    iter = 1;
    while true
        iter = iter+1;
        if iter > 5000

            perim = imdilate(logical(current), ones(3)) & ~logical(current);
            if all(isnan(block(perim)))
                eddy = new_eddy();
                return;
            end
            disp('potential infinite loop')
        end

        bw = cyc .* block >= cyc .* thresh;
        labels = bwlabel(bw);

        extrema_label = labels(extrema_lat_index - block_bottom_index + 1, extrema_lon_index - block_left_index + 1);
        current = labels == extrema_label;
        currentExtrema = extremaBlock(current);

        existing_pixel_at_box_edge = outterRing(labels, extrema_label);

        if sum(currentExtrema) > 1 || ( edgeOfWorld && existing_pixel_at_box_edge)

            if step ~= threshold_step
                % Go back to last threshold
                thresh = thresh + cyc*step;
                step = threshold_step;
                thresh = thresh - cyc*step;
                continue;
            end

            if size(block, 1) ~= size(previous(block_bottom_index:block_top_index, block_left_index:block_right_index), 1)
                prevBlock = block(2:end-1, 2:end-1);
            else
                prevBlock = block;
            end

            perim = imdilate(logical(previous(block_bottom_index:block_top_index, block_left_index:block_right_index)), ones(3)) ...
                & ~logical(previous(block_bottom_index:block_top_index, block_left_index:block_right_index));
            nan = isnan(prevBlock(perim));
            if sum(nan) / length(nan) > .3
                %if more than half of your perimeter is land, then throw it out.
                eddy = new_eddy();
                return;
            end

            if sum(previous(:)) < min_pixel_size
                    eddy = new_eddy();
                    return;
            end

            perim = bwperim(previous);
            meanPerim = mean(ssh(logical(perim)));
            amp = cyc * (ssh(extrema_lat_index, extrema_lon_index)-meanPerim);

            stats = regionprops(previous, ssh, 'Area', 'Extrema',...
                'PixelIdxList', intensity, 'ConvexImage', 'PixelList', ...
                'Solidity', 'Extent', 'Orientation', 'MajorAxisLength', ...
                'MinorAxisLength');

            stats.Intensity = stats.(intensity);
            stats = rmfield(stats, intensity);

            if is_padding
                [idx, r, c] = extidx2original(stats.PixelIdxList, [length(lat) length(lon)], size(ssh));
                stats.PixelIdxList = idx;
            else
                [r, c] = ind2sub(size(ssh), stats.PixelIdxList);
            end

            stats.PixelList = [r, c];

            % Getting geodesic speed
            if is_padding
                geoSpeed = mean_geo_speed(ssh(:, 201:end-200), stats.PixelIdxList, lat, lon);
                if ~isempty(R)
                    [elat, elon] = weighted_centroid(cyc_ssh(:, 201:end-200), stats.PixelList, stats.PixelIdxList, R);
                else
                    [elat, elon] = weighted_centroid_irregular_grid(cyc_ssh(:, 201:end-200), stats.PixelList, stats.PixelIdxList, lat, lon);
                end
            else
                geoSpeed = mean_geo_speed(ssh, stats.PixelIdxList, lat, lon);
                if ~isempty(R)
                    [elat, elon] = weighted_centroid(cyc_ssh, stats.PixelList, stats.PixelIdxList, R);
                else
                    [elat, elon] = weighted_centroid_irregular_grid(cyc_ssh, stats.PixelList, stats.PixelIdxList, lat, lon);
                end
            end

            % weighted_centroid returns lon from 0-360, fix this
            % TODO: should we also fix lat lon -270 to 80?
            elon = (elon > 180).*(elon - 360) + (elon <= 180).*elon;

            % Getting surface area of the eddy
            if all(size(areamap) == [length(lat) length(lon)])
                % area is 2D array for areas of pixels at [lat, lon]
                sarea = sum(areamap(stats.PixelIdxList));
            elseif any(size(areamap) == [1 1]) && length(areamap) == length(lat)
                % Area is 1D array for areas of pixels at a specific latitude
                sarea = sum(areamap(stats.PixelList(:, 1)));
            else
                % Invalid areamap
                sarea = NaN;
            end

            eddy = new_eddy(rmfield(stats, 'PixelList'), amp, elat, elon, thresh, sarea, cyc, geoSpeed, 'ESv2');

            return
        end

        if existing_pixel_at_box_edge
            %disp('expanding size');
            eddy = thresholdBU(cyc, block_bottom_index-1, block_top_index+1, block_left_index-1,...
                block_right_index+1, ssh, extrema, extrema_lat_index, extrema_lon_index, thresh, threshold_step, ...
                step, previous, lat, lon, R, areamap, min_pixel_size, ...
                is_padding, cyc_ssh);
            return
        end

        previous(block_bottom_index:block_top_index, block_left_index:block_right_index) = current;

        step = step * 2; % double the step for less number of iterations
        thresh = thresh - cyc*step;
    end

end

function [ extrema ] = get_extrema( ssh, cyc )
%GET_EXTREMA Returns a matrix containing all of the minima or maxima
%(depending on the value of cyc) in a 5x5 matrix within the 2D ssh field.
% ssh: ssh slice containing NaNs for land
% cyc: 1 for anticyclonic, -1 for cyclonic

    padded = [ssh(:,end-1:end) ssh(:,:) ssh(:,1:2)];
    padded(isnan(padded)) = cyc*-Inf;
    padded = padarray(padded, [1, 1], cyc*-Inf);
    n = ones(5); n(3, 3) = 0;
    padded = cyc .* padded; % Want to find right extrema for cyclonic and anticyc eddies

    extrema = padded > imdilate(padded, n);
    extrema = extrema(2:end-1, 4:end-3);
end

function [res] = outterRing(box, val)
    ring = [box(1, 1:end)'; box(end, 1:end)'; box(1:end, 1); box(1:end, end)];
    res = any(ring == val);
end

function [idx, row, col] = extidx2original(idx, original_size, extended_size)
%EXTIDX2ORIGINAL Convert from extended indexes to original indexes
    [row, col] = ind2sub(extended_size,idx);

    offright = col > (extended_size(2) + original_size(2)) / 2;
    offleft = col < (extended_size(2) - original_size(2)) / 2 + 1;
    notoff = ~(offleft | offright);
    col(offright)=col(offright) - (extended_size(2) + original_size(2)) / 2;
    col(offleft) = col(offleft) + original_size(2) - (extended_size(2) - original_size(2)) / 2;
    col(notoff)=col(notoff) - (extended_size(2) - original_size(2)) / 2;

    idx = sub2ind(original_size,row,col);
end

function mean_speed = mean_geo_speed(ssh, pixels, lat, lon)
%MEAN_GEO_SPEED Returns the mean geostrophic speed for pixels.
% lat and lon should yield the correct values for indices of ssh.

    g = 980.665; % cm/s
    omega = 7.2921e-5;
    [x, y] = ind2sub(size(ssh), pixels);
    lats = lat(x);
    f = 2*omega*sin(lats);
    f(f == 0) = 2*omega*sin(0.25); % f is coriolis frequency

    dSSH_y = ssh(sub2ind(size(ssh), x, mod(y, size(ssh, 2))+1)) - ...
        ssh(sub2ind(size(ssh), x, mod(y-2, size(ssh, 2))+1));
    dSSH_x = ssh(sub2ind(size(ssh), min(x+1, zeros(size(x)) + size(ssh, 1)), y))...
        - ssh(sub2ind(size(ssh), max(x-1, ones(size(x))), y));

    dy = deg2km(distance(lat(x), lon(mod(y, size(ssh, 1))+1), lat(x), lon(mod(y-2, size(ssh, 1))+1))) * 100000;
    dx = (min(x+1, size(ssh, 1)) - max(1, x-1)) .* 111.12 .* 100000 .* 180 ./ (length(lats) - 1);

    vs = -g .* (dSSH_y) ./ (2.*f .* dy);
    us = g .* (dSSH_x) ./ (2 .* f .* dx );
    speeds = sqrt(us .^2 + vs .^2);
    mean_speed = nanmean(speeds);
end

function [lat, lon] = weighted_centroid(cyc_ssh, pixellist, pixelidxlist, R)
%WEIGHTED_CENTROID Returns the location of the weighted centroid for the
%pixels provided
    %ssh = cyc * ssh;
    shift = min(cyc_ssh(pixelidxlist));

    x = pixellist(:, 1);
    y = pixellist(:, 2);

    if min(y) == 1 && max(y) == size(cyc_ssh,2)
        y(y > size(cyc_ssh,2)/2) = y(y > size(cyc_ssh,2)/2) - size(cyc_ssh,2);
    end

    mask = ~isnan(cyc_ssh(pixelidxlist));
    intensities = cyc_ssh(pixelidxlist)+shift;
    intensities = intensities - min(intensities); % should start from 0

    xbar = sum(x(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);
    ybar = sum(y(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);

    if ybar <= 0
        ybar = ybar + size(cyc_ssh,2);
    end

    [lat, lon] = pix2latlon(R, xbar, ybar);

end

function [lat, lon] = weighted_centroid_irregular_grid(cyc_ssh, pixellist, pixelidxlist, lats, lons)
%WEIGHTED_CENTROID_IRREGULAR_GRID Returns the location of the weighted centroid for the
%pixels provided
    %ssh = cyc * ssh;
    shift = min(cyc_ssh(pixelidxlist));

    x = pixellist(:, 1);
    y = pixellist(:, 2);

    if min(y) == 1 && max(y) == size(cyc_ssh,2)
        y(y > size(cyc_ssh,2)/2) = y(y > size(cyc_ssh,2)/2) - size(cyc_ssh,2);
    end

    mask = ~isnan(cyc_ssh(pixelidxlist));
    intensities = cyc_ssh(pixelidxlist)+shift;
    intensities = intensities - min(intensities); % should start from 0
    xbar = sum(x(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);
    ybar = sum(y(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);

    if ybar <= 0
        ybar = ybar + size(cyc_ssh,2);
    end

    x_lower = floor(xbar);
    x_upper = ceil(xbar);
    if x_lower == 0
        lat = lats(1);
    elseif x_upper == length(lats) + 1
        lat = lats(end);
    else
        lat_lower = lats(x_lower);
        lat_upper = lats(x_upper);
        lat = lat_lower + (lat_upper - lat_lower) * (xbar - x_lower) / (x_upper - x_lower);
        if isnan(lat)
            lat = 90;
        end
    end
    y_lower = floor(ybar);
    y_upper = ceil(ybar);
    if y_lower == 0
        lon = lons(1);
    elseif y_upper == length(lons) + 1
        lon = lons(end);
    else
        lon_lower = lons(y_lower);
        lon_upper = lons(y_upper);
        if lon_upper - lon_lower > 180
            lon_upper = lon_upper - 360;
        elseif lon_lower - lon_upper > 180
            lon_lower = lon_lower - 360;
        end
        lon = lon_lower + (lon_upper - lon_lower) * (ybar - y_lower) / (y_upper - y_lower);
    end

end

function eddy = new_eddy(STATS, amplitude, lat, lon, thresh, sa, cyc, geospeed, detect)
%NEW_EDDY Initializes new eddy objects, run with no arguments to create an
%empty matrix
    if nargin
        eddy = struct('Stats', STATS, ...
            'Lat', lat, ...
            'Lon', lon, ...
            'Amplitude', amplitude, ...
            'ThreshFound', thresh, ...
            'SurfaceArea', sa, ...
            'Date', NaN, ...
            'Cyc', cyc, ...
            'MeanGeoSpeed', geospeed, ...
            'DetectedBy', detect);
    else
        eddy = struct('Stats', [], ...
            'Lat', [], ...
            'Lon', [], ...
            'Amplitude', [], ...
            'ThreshFound', [], ...
            'SurfaceArea', [], ...
            'Date', [], ...
            'Cyc', [], ...
            'MeanGeoSpeed', [], ...
            'DetectedBy', []);
    end
end


function edgesEvalPlot( algs, nms, cols )
% Plot edge precision/recall results for directory of edge images.
%
% Enhanced replacement for plot_eval() from BSDS500 code:
%  http://www.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/
% Uses same format and is fully compatible with plot_eval. Use this
% function to plot the edge results created using edgesEvalDir.
%
% USAGE
%  edgesEvalPlot( algs, [nms], [cols] )
%
% INPUTS
%  algs       - {nx1} algorithm result directories
%  nms        - [{nx1}] algorithm names (for legend)
%  cols       - [{nx1}] algorithm colors
%
% OUTPUTS
%
% EXAMPLE
%
% See also edgesEvalDir
%
% Structured Edge Detection Toolbox      Version 3.0
% Copyright 2014 Piotr Dollar.  [pdollar-at-microsoft.com]
% Please email me if you find bugs, or have suggestions or questions!
% Licensed under the MSR-LA Full Rights License [see license.txt]

% parse inputs
if(nargin<2||isempty(nms)), nms={}; end; if(~iscell(nms)), nms={nms}; end
if(nargin<3||isempty(cols)), cols=repmat({'r','g','b','k','m'},1,100); end
if(~iscell(algs)), algs={algs}; end; if(~iscell(cols)), cols={cols}; end

% setup basic plot (isometric contour lines and human performance)
clf; box on; grid on; hold on;
line([0 1],[.5 .5],'LineWidth',2,'Color',.7*[1 1 1]);
for f=0.1:0.1:0.9, r=f:0.01:1; p=f.*r./(2.*r-f); %f=2./(1./p+1./r)
  plot(r,p,'Color',[0 1 0]); plot(p,r,'Color',[0 1 0]); end
if(1), h=plot(0.7235,0.9014,'o','MarkerSize',8,'Color',[0 .5 0],...
    'MarkerFaceColor',[0 .5 0],'MarkerEdgeColor',[0 .5 0]); end
set(gca,'XTick',0:0.1:1,'YTick',0:0.1:1);
grid on; xlabel('Recall'); ylabel('Precision');
axis equal; axis([0 1 0 1]);

% load results for every algorithm (pr=[T,R,P,F])
n=length(algs); hs=zeros(1,n); res=zeros(n,9); prs=cell(1,n);
for i=1:n, a=[algs{i} '-eval'];
  pr=dlmread(fullfile(a,'eval_bdry_thr.txt')); pr=pr(pr(:,2)>=1e-3,:);
  [~,o]=unique(pr(:,3)); R50=interp1(pr(o,3),pr(o,2),max(pr(o(1),3),.5));
  res(i,1:8)=dlmread(fullfile(a,'eval_bdry.txt')); res(i,9)=R50; prs{i}=pr;
end;

% sort algorithms by ODS score
[~,o]=sort(res(:,4),'descend'); res=res(o,:); prs=prs(o);
cols=cols(o); if(~isempty(nms)), nms=nms(o); end

% plot results for every algorithm (plot best last)
for i=n:-1:1
  hs(i)=plot(prs{i}(:,2),prs{i}(:,3),'-','LineWidth',3,'Color',cols{i});
  fprintf('ODS=%.3f OIS=%.3f AP=%.3f R50=%.3f',res(i,[4 7:9]));
  if(~isempty(nms)), fprintf(' - %s',nms{i}); end; fprintf('\n');
end

% show legend if nms provided (report best first)
hold off; if(isempty(nms)), return; end
for i=1:n, nms{i}=sprintf('[F=.%i] %s',round(res(i,4)*100),nms{i}); end
if(1), hs=[h hs]; nms=['[F=.80] Human'; nms(:)]; end
legend(hs,nms,'Location','sw');

end


function computeAssociations_Textures_Old(pathUnivariate,pathFeatures,cohorts,outcomes,timeToEvent,featType,textType,textName,nPatient,scale_mat,algo_cell,Ng_mat)

% THIS FUNCTION WAS MODIFIED ON SEPTEMBER 29 TO COMPUTE FUSED TEXTURE
% RESULTS. PREVIOUS VERSION (WITHOUT FUSED) IS WITH COMMENTED LINES.

startpath = pwd;
cd(pathUnivariate)
fid = fopen('Texture_SignificanceProportion.txt','w');

nameOutcomes = fieldnames(outcomes.(cohorts{1})); nOutcomes = numel(nameOutcomes); nCohort = numel(cohorts);
nFeatType = numel(featType); nTextType = numel(textType);
nText = 0;
for t = 1:nTextType
    nText = nText + numel(textName{t});
end

% featureNames = {'Best PET texture - GTVp'; 'Best PET texture - GTVtot'; 'Best CT texture - GTVp'; 'Best CT texture - GTVtot'}; nFeature = numel(featureNames);
featureNames = {'Best PET texture - GTVp'; 'Best PET texture - GTVtot'; 'Best CT texture - GTVp'; 'Best CT texture - GTVtot'; 'Best PET/CT texture - GTVp'; 'Best PET/CT texture - GTVtot'}; nFeature = numel(featureNames);
rsMat_Binary = zeros(nFeature,nOutcomes); pMat_Binary = zeros(nFeature,nOutcomes); pCell_Binary = cell(nFeature,nOutcomes); stringCell_Binary = cell(nFeature,nOutcomes); nameTextCell_Binary = cell(nFeature,nOutcomes);
rsMat_Time = zeros(nFeature,nOutcomes); pMat_Time = zeros(nFeature,nOutcomes); pCell_Time = cell(nFeature,nOutcomes); stringCell_Time = cell(nFeature,nOutcomes); nameTextCell_Time = cell(nFeature,nOutcomes);
% scans = {'PT','CT'}; nScans = numel(scans); nParams = [numel(scale_mat)*numel(algo_cell)*numel(Ng_mat),numel(scale_mat)*numel(algo_cell)*numel(Ng_mat)]; paramSizes = {[numel(scale_mat),numel(algo_cell),numel(Ng_mat)],[numel(scale_mat),numel(algo_cell),numel(Ng_mat)]};
scans = {'PT','CT','PTCT'}; nScans = numel(scans); nParams = [numel(scale_mat)*numel(algo_cell)*numel(Ng_mat),numel(scale_mat)*numel(algo_cell)*numel(Ng_mat),numel(CTweight_mat)*numel(scale_mat)*numel(algo_cell)*numel(Ng_mat)]; paramSizes = {[numel(scale_mat),numel(algo_cell),numel(Ng_mat)],[numel(scale_mat),numel(algo_cell),numel(Ng_mat)],[numel(CTweight_mat),numel(scale_mat),numel(algo_cell),numel(Ng_mat)]};
nPat = 0;
for c = 1:nCohort
    nPat = nPat + nPatient.(cohorts{c});
end
i = 0;
for scan = 1:nScans
    nParam = nParams(scan);
    paramSize = paramSizes{scan};
    textParamName = cell(1,nText*nParam); count = 0;
    for t = 1:numel(textType)
        for tStr = 1:numel(textName{t})
            for p = 1:nParam
                count = count + 1;
                if numel(paramSize) == 3
                    [aa,bb,cc] = ind2sub(paramSize,p);
                    textParamName{count} = [textType{t},'/',textName{t}{tStr},' -- ','Scale=',num2str(scale_mat(aa)),',Quant.algo=',algo_cell{bb},',Ng=',num2str(Ng_mat(cc))];
                elseif numel(paramSize) == 4 % Remove this if fused is not used.
                    [aa,bb,cc,dd] = ind2sub(paramSize,p);
                    textParamName{count} = [textType{t},'/',textName{t}{tStr},' -- ','CTweight=',num2str(CTweight_mat(aa),'%.2f'),',Scale=',num2str(scale_mat(bb)),',Quant.algo=',algo_cell{cc},',Ng=',num2str(Ng_mat(dd))];
                end
            end
        end
    end
    for type = 1:nFeatType
        i = i + 1; textMat = zeros(nPat,nText*nParam); countPat = 0;
        outcomeMat = zeros(nPat,nOutcomes);
        timeMat = zeros(nPat,nOutcomes);
        cd(pathFeatures)
        for c = 1:nCohort
            cohort = cohorts{c};
            text = load(['text_',cohort,'_',scans{scan},'_',featType{type}]); text = struct2cell(text); text = text{1};
            patientVect = (countPat+1):(countPat+nPatient.(cohort)); countPat = countPat + nPatient.(cohort);
            count = 0;
            for t = 1:numel(textType)
                for tStr = 1:numel(textName{t})
                    for p = 1:nParam
                        count = count + 1;
                        if numel(paramSize) == 3
                            [aa,bb,cc] = ind2sub(paramSize,p);
                            textMat(patientVect,count) = text{aa,bb,cc}.(textType{t}).(textName{t}{tStr}).Data;
                        elseif numel(paramSize) == 4 % Remove this if fused is not used.
                            [aa,bb,cc,dd] = ind2sub(paramSize,p);
                            textMat(patientVect,count) = text{aa,bb,cc,dd}.(textType{t}).(textName{t}{tStr}).Data;
                        end
                    end
                end
            end
            for j = 1:nOutcomes
                outcomeMat(patientVect,j) = outcomes.(cohort).(nameOutcomes{j});
                timeMat(patientVect,j) = timeToEvent.(cohort).(nameOutcomes{j});
            end
        end
        cd(pathUnivariate)
        for j = 1:nOutcomes
            outcome = outcomeMat(:,j);
            time = timeMat(:,j);
            [rsTemp,pTemp] = corr(textMat,outcome,'type','Spearman','rows','pairwise'); temp = abs(rsTemp);
            [significance] = benjamini_hochberg(pTemp,0.10); proportion = sum(significance)/numel(significance);
            if proportion > 0
                starB = '*';
            else
                starB = '';
            end
            fprintf(fid,['Proportion of significant textures: ',scans{scan},', ',featType{type},', ',nameOutcomes{j},', Binary: ',num2str(proportion),'\n']);
            [~,indMax] = max(temp);
            rsMat_Binary(i,j) = rsTemp(indMax); pMat_Binary(i,j) = pTemp(indMax); nameTextCell_Binary{i,j} = textParamName{indMax};
            pCell_Binary{i,j} = pTemp;
            [rsTemp,pTemp] = corr(textMat,time,'type','Spearman','rows','pairwise'); temp = abs(rsTemp);
            [significance] = benjamini_hochberg(pTemp,0.10); proportion = sum(significance)/numel(significance);
            if proportion > 0
                starT = '*';
            else
                starT = '';
            end
            fprintf(fid,['Proportion of significant textures: ',scans{scan},', ',featType{type},', ',nameOutcomes{j},', Time: ',num2str(proportion),'\n']);
            [~,indMax] = max(temp);
            rsMat_Time(i,j) = rsTemp(indMax); pMat_Time(i,j) = pTemp(indMax); nameTextCell_Time{i,j} = textParamName{indMax};
            pCell_Time{i,j} = pTemp;
            if pMat_Binary(i,j) < 0.01
                stringCell_Binary{i,j} = [starB,'rs = ',num2str(rsMat_Binary(i,j),'%.2f'),', p = ',num2str(pMat_Binary(i,j),'%.2i'),starB];
            else
                stringCell_Binary{i,j} = [starB,'rs = ',num2str(rsMat_Binary(i,j),'%.2f'),', p = ',num2str(pMat_Binary(i,j),'%.2f'),starB];
            end
            if pMat_Time(i,j) < 0.01
                stringCell_Time{i,j} = [starT,'rs = ',num2str(rsMat_Time(i,j),'%.2f'),', p = ',num2str(pMat_Time(i,j),'%.2i'),starT];
            else
                stringCell_Time{i,j} = [starT,'rs = ',num2str(rsMat_Time(i,j),'%.2f'),', p = ',num2str(pMat_Time(i,j),'%.2f'),starT];
            end
        end
    end
end
cd(pathUnivariate), fclose(fid);
Locoregional = stringCell_Binary(:,1); Distant = stringCell_Binary(:,2); Death = stringCell_Binary(:,3);
results.tableCorr = table(Locoregional,Distant,Death,'RowNames',featureNames);
Locoregional = nameTextCell_Binary(:,1); Distant = nameTextCell_Binary(:,2); Death = nameTextCell_Binary(:,3);
results.tableTextName = table(Locoregional,Distant,Death,'RowNames',featureNames);
results.rsMat = rsMat_Binary; results.pMat = pMat_Binary; results.pCell = pCell_Binary;
save('texturesBest_UniV_Binary','results'), clear results
Locoregional = stringCell_Time(:,1); Distant = stringCell_Time(:,2); Death = stringCell_Time(:,3);
results.tableCorr = table(Locoregional,Distant,Death,'RowNames',featureNames);
Locoregional = nameTextCell_Time(:,1); Distant = nameTextCell_Time(:,2); Death = nameTextCell_Time(:,3);
results.tableTextName = table(Locoregional,Distant,Death,'RowNames',featureNames);
results.rsMat = rsMat_Time; results.pMat = pMat_Time; results.pCell = pCell_Time;
save('texturesBest_UniV_Time','results'), clear results

cd(startpath)
end


% This file is part of the project NILM-Eval (https://github.com/beckel/nilm-eval).
% Licence: GPL 2.0 (http://www.gnu.org/licenses/gpl-2.0.html)
% Copyright: ETH Zurich, 2014
% Author: Romano Cicchetti

function [result] = weiss(evaluation_and_training_days, setup, fid)

    % load parameters of algorithm
    dataset = setup.dataset;
    household = setup.household;
    granularity = setup.granularity;
    filteringMethod = setup.filtering;
    filtLength = setup.filtLength;
    plevelMinLength = setup.plevelMinLength;
    maxEventDuration = setup.maxEventDuration;
    eventPowerStepThreshold = setup.eventThreshold;
    r = setup.r;
    osc = setup.osc;
    evaluation_days = evaluation_and_training_days{1};
    training_days = evaluation_and_training_days{2};
    appliance = setup.appliance;

    % set variables
    edgeThreshold = 2;
    num_measurements = size(evaluation_days,1)*86400;

    % build signature database
    global caching;
    if caching == 1
        if exist('cache_sigdat.mat') == 2
            load('cache_sigdat');
        else
            signature_database = buildSignatureDatabase(setup, training_days);
            save('cache_sigdat', 'signature_database');
        end
    else
        signature_database = buildSignatureDatabase(setup, training_days);
    end

    signatures = signature_database.signatures;
    names_of_signatures = signature_database.names;
    phases_of_signatures = signature_database.phases;
    % calculate length (2-norm) of each signature
    numOfSignatures = size(signatures,1);
    signatureLength = zeros(numOfSignatures,1);
    for j = 1:numOfSignatures
        signatureLength(j,1) = norm(signatures(j,:));
    end
    signature_database.signatureLength = signatureLength;

    % write signatures to text file
    fprintf(fid,'%20s %13s %16s %9s\n', 'appliance:', 'true power:', 'reactive power:', 'phase:');
    for i = 1:size(signatures,1)
        fprintf(fid,'%20s %13.2f %16.2f %9s\n', cell2mat(names_of_signatures(i)), signatures(i,1),...
             signatures(i,2), num2str(phases_of_signatures(i,1)));
    end
    fprintf(fid, '\n\n');

    result = struct;
    result.events = [];
    result.appliance_names = {};

    input_params = struct;
    input_params.dataset = dataset;
    input_params.household = household;
    input_params.evaluation_days = evaluation_days;
    input_params.granularity = granularity;
    input_params.filtering_method = filteringMethod;
    input_params.filtLength = filtLength;
    input_params.edgeThreshold = filtLength;
    input_params.plevelMinLength = plevelMinLength;
    input_params.eventPowerStepThreshold = eventPowerStepThreshold;
    input_params.maxEventDuration = maxEventDuration;

    % Disaggregate all appliances or individual appliance?
    if strcmp(appliance, 'Stove') == 1
        [events, times] = get_events_of_multi_phase_appliance(input_params);
        matching_ids = find(strcmp(signature_database.names, 'Stove'));
        stove_sig = signatures(matching_ids,:);
        result = infer_events_stove(result, events, times, min(stove_sig(stove_sig > 0))-200);
        [result.usage, result.usage_times_start] = infer_usage(appliance, result.events(:,3), result.events(:,1), num_measurements, setup.usage_duration);
        result.consumption = infer_consumption_stove(result.events(:,3), result.events(:,1), num_measurements, setup.usage_duration);

    elseif strcmp(appliance, 'Dishwasher') == 1
        applianceID = getApplianceID(appliance);
        phase = getPhase(household, applianceID, dataset);
        [events, times] = get_events_of_single_phase_appliance(phase, input_params);
        result = infer_events(result, events, times, signature_database, phase, setup, appliance);
        [result.usage, result.usage_times_start] = infer_usage(appliance, result.events(:,3), result.events(:,1), num_measurements, setup.usage_duration);
        result.consumption = infer_consumption(result, evaluation_and_training_days, setup.usage_duration, appliance, setup);

    elseif strcmp(appliance, 'Water kettle') == 1 || strcmp(appliance, 'TV') == 1 || strcmp(appliance, 'Stereo') == 1
            applianceID = getApplianceID(appliance);
            phase = getPhase(household, applianceID, dataset);
            [events, times] = get_events_of_single_phase_appliance(phase, input_params);
            result = infer_events(result, events, times, signature_database, phase, setup, appliance);
            [result.usage, result.usage_times_start] = infer_usage(appliance, result.events(:,3), result.events(:,1), num_measurements, setup.usage_duration);
            result.consumption = infer_consumption(result, evaluation_and_training_days, setup.usage_duration, appliance, setup);
    elseif strcmp(appliance, 'Fridge') == 1 || strcmp(appliance, 'Freezer') == 1
            applianceID = getApplianceID(appliance);
            phase = getPhase(household, applianceID, dataset);
            [events, times] = get_events_of_single_phase_appliance(phase, input_params);
            result = infer_events(result, events, times, signature_database, phase, setup, appliance);
            result.consumption = infer_consumption(result, evaluation_and_training_days, setup.usage_duration, appliance, setup);
    elseif strcmp(appliance, 'Laptop') == 1
            applianceID = getApplianceID(appliance);
            phase = getPhase(household, applianceID, dataset);
            [events, times] = get_events_of_single_phase_appliance(phase, input_params);
            result = infer_events(result, events, times, signature_database, phase, setup, appliance);
%             result.consumption = infer_consumption(result, evaluation_and_traini
    elseif strcmp(appliance, 'All')

        %% "Old": all appliances
        signatures_in_previous_phases = 0;
        for phase = 1:3
            if caching == 1
                error('Does not work with caching - run without caching');
            end
           [event_vecs, timeOfEvents] = get_events_of_single_phase_appliance(phase, input_params);
            % assign each event to its best match in the signature database
            [signatureIDs, dist] = knnsearch(signatures(phases_of_signatures == phase, 1:2), event_vecs(:,1:2));
            if ~isempty(signatureIDs)
                dist_threshold = r*signatureLength(signatureIDs,1) + event_vecs(:,4);
                matching_valid = dist < dist_threshold;
                %% should te next line not be earlier???
                signatureIDs = signatureIDs + signatures_in_previous_phases;
                result.events = [result.events; timeOfEvents(matching_valid), signatureIDs(matching_valid), event_vecs(matching_valid, 1:3)];
                signatures_in_previous_phases = signatures_in_previous_phases + nnz(phases_of_signatures == phase);
            end
        end

    else
        error('Appliance not supported');
    end

    % store labeled events
    for i = 1:length(names_of_signatures)
        if ismember(cell2mat(names_of_signatures(i)), result.appliance_names)
            result.events(result.events(:,2) == i,2) = find(ismember(result.appliance_names, names_of_signatures{i}));
        else
            % do nothing if signature is not in signature database
            % result.appliance_names{end+1} = cell2mat(names_of_signatures(i));
            % result.events(result.events(:,2) == i,2) = length(result.appliance_names);
        end
    end
end


function model = yalmip2nonlinearsolver(model)

global newmodel

newmodel = 1;

model.dense = 0;
if ~model.equalitypresolved
    model = propagate_bounds_from_equalities(model);
end

K = model.K;
lb = model.lb;
ub = model.ub;
x0 = model.x0;
c = model.c;

% Pick out the positive conditions from cones ||Ax+b|| <= c'*x+d which will
% be treated as (Ax+b)'*(ax+b) <= (c'*x+d)^2,  c'*x+d >= 0
if any(K.q)
    aux = [];
    top = 1 + K.f + K.l;
    for i = 1:length(K.q)
        row = model.F_struc(top,:);
        if any(row(2:end))
            aux = [aux;row];
        end
        top = top + model.K.q(i);
    end
    model.F_struc = [model.F_struc(1:K.f+K.l,:);aux;model.F_struc(K.f+K.l+1:end,:)];
    model.K.l = model.K.l + size(aux,1);
end

if isempty(model.evaluation_scheme)
    model = build_recursive_scheme(model);
end
model = compress_evaluation_scheme(model);

% Do some pre-calc to be used in calls from fmincon
nonlinearindicies = union(find(model.variabletype~=0),model.evalVariables);
linearindicies    = setdiff(find(model.variabletype==0),nonlinearindicies);
model.nonlinearindicies = nonlinearindicies;
model.linearindicies    = linearindicies;

model.Anonlinineq = [];
model.bnonlinineq = [];
model.Anonlineq = [];
model.bnonlineq = [];

% Extract linear and nonlinear equality constraints
if K.f>0
    Aeq = -model.F_struc(1:1:K.f,2:end);
    beq = model.F_struc(1:1:model.K.f,1);

    nonlinear_equalities_indicies = find(any(Aeq(:,nonlinearindicies),2));
    model.Anonlineq = Aeq(nonlinear_equalities_indicies,:);
    model.bnonlineq = beq(nonlinear_equalities_indicies);

    Aeq(nonlinear_equalities_indicies,:) = [];
    beq(nonlinear_equalities_indicies,:) = [];
    Aeq(:,nonlinearindicies) = [];
    model.F_struc(1:model.K.f,:) = [];
    model.K.f = 0;
else
    Aeq = [];
    beq = [];
end

% Find nonlinear eualities implied by lower and upper bounds
if ~isempty(ub) && ~isempty(lb)
    nonlinearequality = find(lb(nonlinearindicies) == ub(nonlinearindicies));
    if ~isempty(nonlinearequality)
        for i = 1:length(nonlinearequality)
          %  model.Anonlineq = [model.Anonlineq;eyev(length(c),nonlinearindicies(nonlinearequality(i)))'];
            model.Anonlineq = [model.Anonlineq;sparse(1,nonlinearindicies(nonlinearequality(i)),1,1,length(c))];
            model.bnonlineq = [model.bnonlineq;lb(nonlinearindicies(nonlinearequality(i)))];
        end
    end
end

% Extract linear and nonlinear inequality constraints
if model.K.l>0
    A = -model.F_struc(1:model.K.l,2:end);
    b = model.F_struc(1:model.K.l,1);

    nonlinear_inequalities_indicies = find(any(A(:,nonlinearindicies),2));

    model.Anonlinineq = A(nonlinear_inequalities_indicies,:);
    model.bnonlinineq = b(nonlinear_inequalities_indicies);

    A(nonlinear_inequalities_indicies,:) = [];
    b(nonlinear_inequalities_indicies,:) = [];
    A(:,nonlinearindicies) = [];

    model.F_struc(1:model.K.l,:) = [];
    model.K.l = 0;
else
    A = [];
    b = [];
end

% This helps with robustness in bnb in some cases
x0candidate = zeros(length(c),1);
if ~isempty(lb) && ~isempty(ub)
    bounded = find(~isinf(lb) & ~isinf(ub));
    x0candidate(bounded) = (lb(bounded) + ub(bounded))/2;
    bounded_below = find(~isinf(lb) & isinf(ub));
    x0candidate(bounded_below) = lb(bounded_below) + 0.5;
    bounded_above = find(~isinf(lb) & isinf(ub));
    x0candidate(bounded_above) = lb(bounded_above) + 0.5;
end

if isempty(x0)
    x0 = x0candidate(linearindicies);
else
    if ~isempty(lb) && ~isempty(ub)
        x0((x0 < lb) | (x0 > ub)) = x0candidate((x0 < lb) | (x0 > ub));
    end
    x0 = x0(linearindicies);
end

if ~isempty(lb)
    lb = lb(linearindicies);
end
if ~isempty(ub)
    ub = ub(linearindicies);
end

lb_old = lb;
ub_old = ub;
[lb,ub,A,b] = remove_bounds_from_Ab(A,b,lb,ub);
[lb,ub,Aeq,beq] = remove_bounds_from_Aeqbeq(Aeq,beq,lb,ub);

if any(model.variabletype == 4)
    problematic = find(any(model.monomtable(:,linearindicies) < 0 ,1));
    if ~isempty(problematic)
        problematic = problematic(find(x0(problematic)==0));
        Oneisfeas = problematic(find(ub(problematic) > 1));
        x0(Oneisfeas) = 1;
    end

    problematic = find(any(model.monomtable(:,linearindicies)~=fix(model.monomtable(:,linearindicies)) ,1));
    lb(problematic) = max(lb(problematic),0);
end
x0(find(lb==ub)) = lb(find(lb==ub));

if size(A,1) == 0
    A = [];
end

if size(b,1) == 0
    b = [];
end

if size(Aeq,1) == 0
    Aeq = [];
end

if size(beq,1) == 0
    beq = [];
end

if model.presolveequalities
    if ~isempty(beq) &  (~model.equalitypresolved | ~(isequal(lb,lb_old) & isequal(ub,ub_old)))
        % This helps when there are artificial variables introduced to model
        % nonlinear operators such as log(2*x+1)
        p.F_struc = [beq -Aeq];
        p.K.f = size(beq,1);
        p.lb = lb;
        p.ub = ub;
        p.variabletype = zeros(1,length(lb));
        p.binary_variables = [];
        p.integer_variables = [];
        p = propagate_bounds_from_equalities(p);
        lb = p.lb;
        ub = p.ub;
    end
end

model.A = A;
model.b = b;
model.Aeq = Aeq;
model.beq = beq;
model.lb = lb;
model.ub = ub;
model.x0 = x0;

model = setup_fmincon_params(model);

% Check if all derivatives are available
model.derivative_available = 1;
for i = 1:length(model.evalMap)
    if isempty(model.evalMap{i}.properties.derivative)
        model.derivative_available = 0;
        break
    end
end

% Some precomputation of computational scheme for Jacobian
allA = [model.Anonlineq;model.Anonlinineq];
if any(model.K.q)
    allA = [allA;model.F_struc(1+model.K.f + model.K.f:end,2:end)];
end
requested = any(allA',2);
[i,j] = find((model.deppattern(find(requested),:)));
requested(j) = 1;
if ~isempty(model.evalMap)
    % Recursive stuff is only possible if we have evaluation-based
    % operators
    model.Crecursivederivativeprecompute = precomputeDerivative(model,requested);
end

% Some precomputation of computational scheme for gradient
requested = model.c | any(model.Q,2);
[i,j,k] = find((model.deppattern(find(requested),:)));
requested(j) = 1;
model.frecursivederivativeprecompute = precomputeDerivative(model,requested);

% Precomputed list of bilinear expressions, used in
% apply_recursive_differentiation
model = compile_bilinearslist(model);
model = compile_quadraticslist(model);

model.binary_variables  = find(ismember(linearindicies,model.binary_variables));
model.integer_variables  = find(ismember(linearindicies,model.integer_variables));
model.semicont_variables  = find(ismember(linearindicies,model.semicont_variables));


%% housekeeping
clc

%% rise the model

m=rise('pdk','rise_flags',{'Sektors',{'C','I'}},...
    'steady_state_file','sstate_model',...
    'saveas',true);

%% get the parameters

[p,priors]=create_parameters(~true);

m=set(m,'parameters',p);

%% get the data

data=create_data();

%% estimate model
clc

ms=estimate(m,'data',data,'estim_priors',priors,'optimizer','fminunc');


% Author: Guosheng Lin (guosheng.lin@gmail.com)

% this is a simpler demo file for testing on your own images.

function demo_test_simple_voc()

rng('shuffle');
addpath('./my_utils');
dir_matConvNet='../libs/matconvnet/matlab';
run(fullfile(dir_matConvNet, 'vl_setupnn.m'));

run_config=[];

run_config.use_gpu=true;
% run_config.use_gpu=false;
run_config.gpu_idx=1;

% result dir:
result_name=['runner_result_dir' datestr(now, 'YYYYmmDDHHMMSS')];
result_dir=fullfile('../cache_data', 'test_examples_city', result_name);
mkdir_notexist(result_dir);

% the folder that contains testing images:
img_data_dir='../datasets/example_imgs_cityscapes';

% using a trained model which is trained on VOC 2012
run_config.trained_model_path='../model_trained/refinenet_res101_cityscapes.mat';
run_config.class_info=gen_class_info_cityscapes();

% for trained model, control the size of input images
run_config.input_img_short_edge_min=600;
run_config.input_img_short_edge_max=1100;

runner_info=prepare_runner_test_simple(run_config);

img_filenames=my_list_file(img_data_dir);
img_num=length(img_filenames);
for img_idx=1:img_num
    task_info=[];
    task_info.img_dir=img_data_dir;
    task_info.img_filename=img_filenames{img_idx};
    task_result=runner_info.run_task_fn(runner_info, task_info);

    [~, img_name]=fileparts(task_info.img_filename);
    one_cache_file=fullfile(result_dir, [img_name '.png']);
    fprintf('save prediction mask:%s\n', one_cache_file);
    imwrite(task_result.mask_data, run_config.class_info.mask_cmap, one_cache_file);
end

end


function pcc=pro(pc);

test(1:100)=0;
l=round(100*pc);
test(1:l)=1;
n=round(rand*99)+1;
pcc=test(n);


% ----------------------------------------------------------------------------
% function hfssAddMaterial(fid, Name, Er, bSigma, tanDelta)
%
% Description :
% -------------
% Creates VB Script necessary to add a new material to the HFSS Materials
% Manager.
%
% Parameters :
% ------------
% fid      - file identifier of the HFSS script file.
% Name     - name of the material to be added.
% Er       - dielectric constant of the material to be added.
% bSigma   - bulk conductivity of the material to be added (Siemens)
% tanDelta - loss tangent of the material.
%
% Note :
% ------
% If a material with the given name already exists in HFSS, this code will
% have no effect and the script will continue running. A warning message will
% appear in the HFSS message log.
%
% Example :
% ---------
% fid = fopen('Dipole.vbs', 'wt');
% ...
% hfssAddMaterial(fid, 'CoaxDielectric', 2.07, 0, 0);
% ----------------------------------------------------------------------------

% ----------------------------------------------------------------------------
% This file is part of HFSS-MATLAB-API.
%
% HFSS-MATLAB-API is free software; you can redistribute it and/or modify it
% under the terms of the GNU General Public License as published by the Free
% Software Foundation; either version 2 of the License, or (at your option)
% any later version.
%
% HFSS-MATLAB-API is distributed in the hope that it will be useful, but
% WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
% or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
% for more details.
%
% You should have received a copy of the GNU General Public License along with
% Foobar; if not, write to the Free Software Foundation, Inc., 59 Temple
% Place, Suite 330, Boston, MA  02111-1307  USA
%
% Copyright 2004, Vijay Ramasami (rvc@ku.edu)
% ----------------------------------------------------------------------------
function hfssAddMaterial(fid, Name, Er, bSigma, tanDelta)

% Preamble.
fprintf(fid, '\n');
fprintf(fid, 'oProject.AddMaterial _\n');

% Name.
fprintf(fid, 'Array("NAME:%s", _\n', Name);

% Dielectric Properties.
fprintf(fid, '"permittivity:=", "%f", _\n', Er);
fprintf(fid, '"conductivity:=", "%f", _\n', bSigma);
fprintf(fid, '"dielectric_loss_tangent:=", "%f")\n', tanDelta);


function [bbox3M, planC] = crop_for_constrictor(planC,paramS,varargin)
% Custom crop function for segmentation model
% AI 10/04/19

%% Limits of bounding box using chewing structures
indexS = planC{end};
strC = {planC{indexS.structures}.structureName};
bboxIdx = [];

%% Return bounding box if it exists
if isfield(paramS,'saveStrToPlanCFlag') && paramS.saveStrToPlanCFlag
    if isfield(paramS,'outStrName')
        outStrName = paramS.outStrName;
    else
        outStrName = 'crop_for_constrictor';
    end
    bboxIdx = getMatchingIndex(outStrName,strC,'EXACT');
end
if ~isempty(bboxIdx)
    bbox3M = getStrMask(bboxIdx,planC);
else
    bboxName = paramS.structureName.cropStructure;
    idx1 = getMatchingIndex(bboxName,strC,'EXACT');
    scanNum = getStructureAssociatedScan(idx1,planC);
    [mask3M, planC]  = getStrMask(idx1,planC);
    if sum(mask3M(:))>0
        [minr,~,minc,maxc,mins,~] = compute_boundingbox(mask3M);
    else
        minr = 1;
        minc = 1;
        maxc = size(mask3M,2);
        mins = 1;
    end
    %% Limits of bbox around larynx
    larynxStrName = paramS.structureName.larynx;
    idx2 = getMatchingIndex(larynxStrName,strC,'EXACT');
    [mask3M, planC]  = getStrMask(idx2,planC);
    if sum(mask3M(:))>0
        [~,maxr,~,~,~,maxs] = compute_boundingbox(mask3M);
    else
        maxr = size(mask3M,1);
        maxs = size(mask3M,3);
    end

    %% Get bounding box for constrictors
    tol_r = 30;
    tol_s = 15;
    maxr = min(maxr+tol_r,size(mask3M,1));
    maxs = min(maxs+tol_s,size(mask3M,3));

    bbox3M = false(size(getScanArray(scanNum,planC)));
    bbox3M(minr:maxr,minc:maxc,mins:maxs) = true;
end

end


classdef clusteringGUI < handle

    properties
        currentCluster = 1
        page = 1
        thumbnail_size = [200 200]
        clustAssign
        clusters
        rejected
        ClusteringData
        minfreq
        maxfreq
        fig
        image_axes = gobjects()
        handle_image = gobjects()
        ColorData
        totalCount
        count
        clusterName
        pagenumber
        finished
        call_id_text
        txtbox
    end

    methods
        function [obj, NewclusterName, NewRejected, NewFinished, NewClustAssign] = clusteringGUI(clustAssign, ClusteringData)

            obj.clustAssign = clustAssign;
            %Image, Lower freq, delta time, Time points, Freq points, File path, Call ID in file, power, RelBox
            obj.ClusteringData = ClusteringData;
            obj.rejected = zeros(1,length(obj.clustAssign));

            obj.minfreq = prctile(ClusteringData.MinFreq, 5);
            obj.maxfreq = prctile(ClusteringData.MinFreq + ClusteringData.Bandwidth, 95);
            obj.ColorData = jet(256); % Color by mean frequency
            % obj.ColorData = HSLuv_to_RGB(256, 'H',  [270 0], 'S', 100, 'L', 75, 'type', 'HSL'); % Make a color map for each category
            obj.ColorData = reshape(obj.ColorData,size(obj.ColorData,1),1,size(obj.ColorData,2));

            if iscategorical(obj.clustAssign)
                obj.clusterName =unique(obj.clustAssign);
                obj.clusters = unique(obj.clustAssign);
            else
                obj.clusterName = categorical(unique(obj.clustAssign(~isnan(obj.clustAssign))));
                obj.clusters = (unique(obj.clustAssign(~isnan(obj.clustAssign))));
            end

            obj.fig = dialog('Visible','off','Position',[360,500,600,600],'WindowStyle','Normal','resize', 'on','WindowState','maximized' );
            obj.fig.CloseRequestFcn = @(src,event) finished_Callback(obj, src, event);
            set(obj.fig,'color',[.1, .1, .1]);

            movegui(obj.fig,'center');
            %             set(obj.fig,'WindowButtonMotionFcn', @(hObject, eventdata) mouse_over_Callback(obj, hObject, eventdata));

            txt = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.1 .1 .1],...
                'ForegroundColor','w',...
                'Style','text',...
                'Position',[120 565 80 30],...
                'String','Name:');

            obj.txtbox = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.149 .251 .251],...
                'ForegroundColor','w',...
                'Style','edit',...
                'String','',...
                'Position',[120 550 80 30],...
                'Callback',@(src,event) txtbox_Callback(obj,src,event));

            obj.totalCount = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.1 .1 .1],...
                'ForegroundColor','w',...
                'Style','text',...
                'String','',...
                'Position',[330 542.5 200 30],...
                'HorizontalAlignment','left');

            back = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.149 .251 .251],...
                'ForegroundColor','w',...
                'Position',[20 550 80 30],...
                'String','Back',...
                'Callback',@(src,event) back_Callback(obj, src, event));

            next = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.149 .251 .251],...
                'ForegroundColor','w',...
                'Position',[220 550 80 30],...
                'String','Next',...
                'Callback',@(src,event) next_Callback(obj, src, event));

            apply = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.149 .251 .251],...
                'ForegroundColor','w',...
                'Position',[440 550 60 30],...
                'String','Save',...
                'Callback',@(src,event)  finished_Callback(obj, src, event));

            if nargin == 2
                redo = uicontrol('Parent',obj.fig,...
                    'BackgroundColor',[.149 .251 .251],...
                    'ForegroundColor','w',...
                    'Position',[510 550 60 30],...
                    'String','Redo',...
                    'Callback',@(src,event) finished_Callback(obj, src, event));
            else
                redo = uicontrol('Parent',obj.fig,...
                    'BackgroundColor',[.149 .251 .251],...
                    'ForegroundColor','w',...
                    'Position',[510 550 60 30],...
                    'String','Cancel',...
                    'Callback',@(src,event) finished_Callback(obj, src, event));
            end
            %% Paging
            nextpage = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.149 .251 .251],...
                'ForegroundColor','w',...
                'Position',[220 517 80 30],...
                'String','Next Page',...
                'Callback',@(src,event) nextpage_Callback(obj, src, event));

            backpage = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.149 .251 .251],...
                'ForegroundColor','w',...
                'Position',[20 517 80 30],...
                'String','Previous Page',...
                'Callback',@(src,event, h) backpage_Callback(obj, src, event));

            obj.pagenumber = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.1 .1 .1],...
                'ForegroundColor','w',...
                'Style','text',...
                'String','',...
                'Position',[118 509 80 30],...
                'HorizontalAlignment','center');

            obj.call_id_text = uicontrol('Parent',obj.fig,...
                'BackgroundColor',[.1 .1 .1],...
                'ForegroundColor','w',...
                'Style','text',...
                'String','',...
                'FontSize',12,...
                'Position',[100 470 400 30],...
                'HorizontalAlignment','center');

            obj.render_GUI();

            % Wait for d to close before running to completion
            set( findall(obj.fig, '-property', 'Units' ), 'Units', 'Normalized');
            obj.fig.Visible = 'on';

            % Enable pointer management for the figure for mouse hover over
            iptPointerManager(obj.fig, 'enable');

            uiwait(obj.fig);
            NewclusterName = obj.clusterName;
            NewRejected = obj.rejected;
            NewFinished = obj.finished;
            NewClustAssign = obj.clustAssign;

        end

        function render_GUI(obj)

            %% Colormap
            xdata = obj.minfreq:obj.maxfreq;
            caxis = axes(obj.fig,'Units','Normalized','Position',[.88 .05 .04 .8]);
            image(1,xdata,obj.ColorData,'parent',caxis)
            caxis.YDir = 'normal';
            set(caxis,'YColor','w','box','off','YAxisLocation','right');
            ylabel(caxis, 'Frequency (kHz)')

            %% Make the axes
            aspectRatio = median(cellfun(@(im) size(im,1) ./ size(im,2), obj.ClusteringData.Spectrogram));

            % Choose a number of rows and columns to fill the space with
            % the average call aspect ratio
            % nFrames = 10;
            % figureAspectRatio = 1;
            % x_grids = sqrt(aspectRatio * figureAspectRatio * nFrames);
            % x_grids = ceil(x_grids);
            % y_grids = ceil(nFrames / x_grids);

            obj.thumbnail_size = round(sqrt(20000 .* [aspectRatio, 1/aspectRatio]));

            axes_spacing = .70; % Relative width of each image
            y_range = [.05, .75]; % [Start, End] of the grid
            x_range = [.05, .85];
            x_grids = 9; % Number of x grids
            y_grids = 3; % Number of y grids

            ypos = linspace(y_range(1), y_range(2) - axes_spacing * range(y_range) / y_grids, y_grids );
            xpos = linspace(x_range(1), x_range(2) - axes_spacing * range(x_range) / x_grids, x_grids );
            xpos = fliplr(xpos);

            pos = [];
            for i = 1:length(ypos)
                for j = 1:length(xpos)
                    pos(end+1,:) = [xpos(j), ypos(i), (xpos(1)-xpos(2)) * axes_spacing, (ypos(2)-ypos(1)) * axes_spacing];
                end
            end
            pos = flipud(pos);
            for i = 1 : length(ypos) * length(xpos)
                    im = zeros([obj.thumbnail_size, 3]);
                    obj.image_axes(i) = axes(obj.fig,'Units','Normalized','Position',pos(i,:));
                    obj.handle_image(i) = image(im,'parent',obj.image_axes(i));
                    set(obj.image_axes(i),'Visible','off')
                    set(get(obj.image_axes(i),'children'),'Visible','off');
            end
            plotimages(obj);
        end

        function [colorIM, rel_x, rel_y] = create_thumbnail(obj, ClusteringData,clustIndex,callID)
            % Resize the image while maintaining the aspect ratio by
            % padding with zeros
            im_size = size(ClusteringData.Spectrogram{clustIndex(callID)}) ;
            new_size = floor(im_size .* min(obj.thumbnail_size ./ im_size));
            im = double(imresize(ClusteringData.Spectrogram{clustIndex(callID)}, new_size));
            pad = (obj.thumbnail_size - size(im)) / 2;
            im = padarray(im, floor(pad), 'pre');
            im = padarray(im, ceil(pad), 'post');

            % Relative offsets for setting the tick values
            rel_size = pad ./ obj.thumbnail_size;
            rel_x = [rel_size(2), 1-rel_size(2)];
            rel_y = [rel_size(1), 1-rel_size(1)];

            % Apply color to the greyscale images
            freqRange = [ClusteringData.MinFreq(clustIndex(callID)),...
                ClusteringData.MinFreq(clustIndex(callID)) + ClusteringData.Bandwidth(clustIndex(callID))];
            % Account for any padding on the y axis
            freqRange = freqRange + range(freqRange) .* rel_y(1) .* [-1, 1];

            freqdata = linspace(freqRange(2) ,freqRange(1), obj.thumbnail_size(1));
            colorMask = interp1(linspace(obj.minfreq, obj.maxfreq, size(obj.ColorData,1)), obj.ColorData, freqdata, 'nearest', 'extrap');
            colorIM = im .* colorMask ./ 255;
        end

        function obj = config_axis(obj, axis_handles,i, rel_x, rel_y)
            set(axis_handles,'xcolor','w');
            set(axis_handles,'ycolor','w');

            x_lim = xlim(axis_handles);
            x_span = x_lim(2) - x_lim(1);
            xtick_positions = linspace(x_span*rel_x(1)+x_lim(1), x_span*rel_x(2)+x_lim(1),4);
            x_ticks = linspace(0,obj.ClusteringData.Duration(i),4);
            x_ticks = arrayfun(@(x) sprintf('%.3f',x),x_ticks(2:end),'UniformOutput',false);

            y_lim = ylim(axis_handles);
            y_span = y_lim(2) - y_lim(1);
            ytick_positions = linspace(y_span*rel_y(1)+y_lim(1), y_span*rel_y(2)+y_lim(1),3);

            y_ticks = linspace(obj.ClusteringData.MinFreq(i),obj.ClusteringData.MinFreq(i)+obj.ClusteringData.Bandwidth(i),3);
            y_ticks = arrayfun(@(x) sprintf('%.1f',x),y_ticks(1:end),'UniformOutput',false);
            y_ticks = flip(y_ticks);

            yticks(axis_handles,ytick_positions);
            xticks(axis_handles,xtick_positions(2:end));
            xticklabels(axis_handles,x_ticks);
            yticklabels(axis_handles,y_ticks);
            xlabel(axis_handles,'Time (s)');
            ylabel(axis_handles,'Frequency (kHz)');
        end

        function obj = plotimages(obj)
            % Number of calls in each cluster
            for cl = 1:length(obj.clusterName)
                obj.count(cl) = sum(obj.clustAssign==obj.clusters(cl));
            end

            clustIndex = find(obj.clustAssign==obj.clusters(obj.currentCluster));

            for i=1:length(obj.image_axes)
                if i <= length(clustIndex) - (obj.page - 1)*length(obj.image_axes)
                    % set(image_axes(i),'Visible','off')

                    set(get(obj.image_axes(i),'children'),'Visible','on');

                    callID = i + (obj.page - 1)*length(obj.image_axes);
                    [colorIM, rel_x, rel_y] = obj.create_thumbnail(obj.ClusteringData,clustIndex,callID);
                    set(obj.handle_image(i), 'ButtonDownFcn',@(src,event) clicked(obj,src,event,clustIndex(callID),i,callID));
                    obj.add_cluster_context_menu(obj.handle_image(i),clustIndex(callID));

                    % Display the file ID and call number on mouse hover
                    [~,call_file,~] = fileparts(obj.ClusteringData.Filename(clustIndex(callID)));
                    call_id = sprintf('Call: %u', obj.ClusteringData.callID(clustIndex(callID)));
                    pointerBehavior.enterFcn = @(~,~) set(obj.call_id_text, 'string', {call_id, call_file});
                    pointerBehavior.traverseFcn = [];
                    pointerBehavior.exitFcn = @(~,~) set(obj.call_id_text, 'string', '');
                    iptSetPointerBehavior(obj.handle_image(i), pointerBehavior);

                    % Make the image red if the call is rejected
                    if obj.rejected(clustIndex(callID))
                        colorIM(:,:,1) = colorIM(:,:,1) + .5;
                    end

                    set(obj.handle_image(i),'CData',colorIM, 'XData', []);

                    obj.config_axis(obj.image_axes(i),clustIndex(callID), rel_x, rel_y);

                    set(obj.image_axes(i),'Visible','on')

                else
                    set(obj.image_axes(i),'Visible','off')
                    set(get(obj.image_axes(i),'children'),'Visible','off');
                end

            end

            % Update text
            obj.pagenumber.String = sprintf('Page %u of %u', obj.page, ceil(obj.count(obj.currentCluster) / length(obj.image_axes)));
            obj.txtbox.String = string(obj.clusterName(obj.currentCluster));
            obj.totalCount.String = sprintf('total count: %u', obj.count(obj.currentCluster));
            obj.fig.Name = sprintf('Cluster %u of %u', obj.currentCluster, length(obj.count));

        end

        function obj = add_cluster_context_menu(obj, hObject, i)
            unique_clusters = unique(obj.clusterName);

            c = uicontextmenu(obj.fig);
            for ci=1:length(unique_clusters)
                uimenu(c,'text',string(obj.clusterName(ci)),'Callback',@(src,event) assign_cluster(obj, src, event,i,unique_clusters(ci)));
            end

            set(hObject, 'UIContextMenu',c);
        end

        function obj = assign_cluster(obj, hObject,eventdata,i, clusterLabel)
            obj.clustAssign(i) = clusterLabel;
            obj.plotimages();
        end

        function obj = clicked(obj, hObject,eventdata,i,plotI,callID)
            if( eventdata.Button ~= 1 ) % Return if not left clicked
                return
            end

            clustIndex = find(obj.clustAssign == obj.clusters(obj.currentCluster));

            obj.rejected(i) = ~obj.rejected(i);

            [colorIM, ~, ~] = obj.create_thumbnail(obj.ClusteringData,clustIndex,callID);

            if obj.rejected(i)
                colorIM(:,:,1) = colorIM(:,:,1) + .5;
            end
            set(obj.handle_image(plotI),'CData',colorIM);
        end

        function obj = next_Callback(obj, hObject, eventdata)
            obj.clusterName(obj.currentCluster) = get(obj.txtbox,'String');
            if obj.currentCluster < length(obj.clusterName)
                obj.currentCluster = obj.currentCluster + 1;
                obj.page = 1;
                obj.plotimages();
            end
        end

        function obj = back_Callback(obj, hObject, eventdata)
            obj.clusterName(obj.currentCluster) = get(obj.txtbox,'String');
            if obj.currentCluster > 1
                obj.currentCluster = obj.currentCluster-1;
                obj.page = 1;
                obj.plotimages();
            end
        end

        function obj = nextpage_Callback(obj, hObject, eventdata)
            if obj.page < ceil(obj.count(obj.currentCluster) / length(obj.image_axes))
                obj.page = obj.page + 1;
                obj.plotimages();
            end
        end

        function obj = backpage_Callback(obj, hObject, eventdata)
            if obj.page > 1
                obj.page = obj.page - 1;
                obj.plotimages();
            end
        end

        function obj = txtbox_Callback(obj, hObject, eventdata)
            obj.clusterName(obj.currentCluster) = get(hObject,'String');
        end

        function obj = finished_Callback(obj, hObject, eventdata)
            % If window is closed, finished = 2
            % If clicked apply, finished = 1
            % If clicked redo, finished = 0
            switch eventdata.EventName
                case 'Close'
                    obj.finished = 2;
                otherwise
                    switch hObject.String
                        case 'Save'
                            obj.finished = 1;
                        case 'Redo'
                            obj.finished = 0;
                    end
            end
            set(obj.fig,  'closerequestfcn', '');
            delete(obj.fig);
            obj.fig = [];
        end

    end
end


function rcnn_cache_pool5_features(imdb, varargin)
% rcnn_cache_pool5_features(imdb, varargin)
%   Computes pool5 features and saves them to disk. We compute
%   pool5 features because we can easily compute fc6 and fc7
%   features from them on-the-fly and they tend to compress better
%   than fc6 or fc7 features due to greater sparsity.
%
%   Keys that can be passed in:
%
%   start             Index of the first image in imdb to process
%   end               Index of the last image in imdb to process
%   crop_mode         Crop mode (either 'warp' or 'square')
%   crop_padding      Amount of padding in crop
%   net_file          Path to the Caffe CNN to use
%   cache_name        Path to the precomputed feature cache

% AUTORIGHTS
% ---------------------------------------------------------
% Copyright (c) 2014, Ross Girshick
%
% This file is part of the R-CNN code and is available
% under the terms of the Simplified BSD License provided in
% LICENSE. Please retain this notice and LICENSE if you use
% this file (or any portion of it) in your project.
% ---------------------------------------------------------

ip = inputParser;
ip.addRequired('imdb', @isstruct);
ip.addOptional('start', 1, @isscalar);
ip.addOptional('end', 0, @isscalar);
ip.addOptional('crop_mode', 'warp', @isstr);
ip.addOptional('crop_padding', 16, @isscalar);
ip.addOptional('net_file', ...
    './data/caffe_nets/finetune_voc_2007_trainval_iter_70k', ...
    @isstr);
ip.addOptional('cache_name', ...
    'v1_finetune_voc_2007_trainval_iter_70000', @isstr);

ip.parse(imdb, varargin{:});
opts = ip.Results;
opts.net_def_file = './model-defs/rcnn_batch_256_output_pool5.prototxt';

image_ids = imdb.image_ids;
if opts.end == 0
  opts.end = length(image_ids);
end

% Where to save feature cache
opts.output_dir = ['./feat_cache/' opts.cache_name '/' imdb.name '/'];
mkdir_if_missing(opts.output_dir);

% Log feature extraction
timestamp = datestr(datevec(now()), 'dd.mmm.yyyy:HH.MM.SS');
diary_file = [opts.output_dir 'rcnn_cache_pool5_features_' timestamp '.txt'];
diary(diary_file);
fprintf('Logging output in %s\n', diary_file);

fprintf('\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n');
fprintf('Feature caching options:\n');
disp(opts);
fprintf('~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n');

% load the region of interest database
roidb = imdb.roidb_func(imdb);

rcnn_model = rcnn_create_model(opts.net_def_file, opts.net_file);
rcnn_model = rcnn_load_model(rcnn_model);
rcnn_model.detectors.crop_mode = opts.crop_mode;
rcnn_model.detectors.crop_padding = opts.crop_padding;

total_time = 0;
count = 0;
for i = opts.start:opts.end
  fprintf('%s: cache features: %d/%d\n', procid(), i, opts.end);

  save_file = [opts.output_dir image_ids{i} '.mat'];
  if exist(save_file, 'file') ~= 0
    fprintf(' [already exists]\n');
    continue;
  end
  count = count + 1;

  tot_th = tic;

  d = roidb.rois(i);
  im = imread(imdb.image_at(i));

  th = tic;
  d.feat = rcnn_features(im, d.boxes, rcnn_model);
  fprintf(' [features: %.3fs]\n', toc(th));

  th = tic;
  save(save_file, '-struct', 'd');
  fprintf(' [saving:   %.3fs]\n', toc(th));

  total_time = total_time + toc(tot_th);
  fprintf(' [avg time: %.3fs (total: %.3fs)]\n', ...
      total_time/count, total_time);
end


% TEST_CNNIMAGERETRIEVAL  Code to evaluate (not train) the methods presented in the papers:
% F. Radenovic, G. Tolias, O. Chum, Fine-tuning CNN Image Retrieval with No Human Annotation, TPAMI 2018
% F. Radenovic, G. Tolias, O. Chum, CNN Image Retrieval Learns from BoW: Unsupervised Fine-Tuning with Hard Examples, ECCV 2016
%
% Authors: F. Radenovic, G. Tolias, O. Chum. 2017.

clear;

%---------------------------------------------------------------------
% Set data folder and testing parameters
%---------------------------------------------------------------------

% Set data folder, change if you have downloaded the data somewhere else
data_root = fullfile(get_root_cnnimageretrieval(), 'data');
% Check, and, if necessary, download train data (for whiten)
download_train(data_root);
% Check, and, if necessary, download test data (Oxf5k and Par6k), and fine-tuned networks
download_test(data_root);

% Set test options
test_datasets = {'oxford5k', 'paris6k', 'roxford5k', 'rparis6k'};  % list of datasets to evaluate on
test_imdim = 1024;  % choose test image dimensionality
use_ms = 1; % use multi-scale representation, otherwise use single-scale
use_rvec = 0;  % use regional representation (R-MAC, R-GeM), otherwise use global (MAC, GeM)
use_gpu = [1];  % use GPUs (array of GPUIDs), if empty use CPU

% Choose ECCV16 fine-tuned CNN network
% network_file = fullfile(data_root, 'networks', 'retrieval-SfM-30k', 'retrievalSfM30k-siamac-alex.mat');
% network_file = fullfile(data_root, 'networks', 'retrieval-SfM-30k', 'retrievalSfM30k-siamac-vgg.mat');

% Choose TPAMI18 fine-tuned CNN network
% network_file = fullfile(data_root, 'networks', 'retrieval-SfM-30k', 'retrievalSfM30k-gem-alex.mat');
% network_file = fullfile(data_root, 'networks', 'retrieval-SfM-120k', 'retrievalSfM120k-gem-vgg.mat');
network_file = fullfile(data_root, 'networks', 'retrieval-SfM-120k', 'retrievalSfM120k-gem-resnet101.mat');

% After running the training script train_cnnimageretrieval.m you can evaluate fine-tuned network
% network_file = fullfile(data_root, 'networks', 'exp', 'resnet101_gem_test', 'net-epoch-30');

%---------------------------------------------------------------------
% Set dependent variables
%---------------------------------------------------------------------

% Choose training data for whitening and set up data folder
% train_whiten_file = fullfile(data_root, 'train', 'dbs', 'retrieval-SfM-30k-whiten.mat'); % less images, faster
train_whiten_file = fullfile(data_root, 'train', 'dbs', 'retrieval-SfM-120k-whiten.mat'); % more images, better results but slower

% Set folder where original training images are stored, whitening learned on them
ims_whiten_dir = fullfile(data_root, 'train', 'ims');

% Prepare function for desc extraction
if ~use_rvec
	if ~use_ms
		descfun = @(x, y) cnn_vecms (x, y, 1);
	else
		descfun = @(x, y) cnn_vecms (x, y, [1, 1/sqrt(2), 1/2]);
	end
else
	if ~use_ms
		descfun = @(x, y) cnn_vecrms (x, y, 3, 1);
	else
		descfun = @(x, y) cnn_vecrms (x, y, 3, [1, 1/sqrt(2), 1/2]);
	end
end

%---------------------------------------------------------------------
% Testing
%---------------------------------------------------------------------
[~, network_name, ~] = fileparts(network_file);
fprintf('>> %s: Evaluating CNN image retrieval...\n', network_name);

% Load pre-trained CNN network
load(network_file);
net = dagnn.DagNN.loadobj(net);

% prepare GPUs if necessary
numGpus = numel(use_gpu);
if numGpus, fprintf('>> Preparing GPU(s)...\n'); end
if numGpus > 1
	% check parallel pool integrity as it could have timed out
	pool = gcp('nocreate');
	if ~isempty(pool) && pool.NumWorkers ~= numGpus
		delete(pool);
	end
	pool = gcp('nocreate');
	if isempty(pool)
		parpool('local', numGpus);
	end
end
if numGpus >= 1
	if numGpus == 1
		gpuinfo = gpuDevice(use_gpu);
		net.move('gpu');
		fprintf('>>>> Running on GPU %s with Index %d\n', gpuinfo.Name, gpuinfo.Index);
	else
		spmd
			gpuinfo = gpuDevice(use_gpu(labindex));
			fprintf('>>>> Running on GPU %s with Index %d\n', gpuinfo.Name, gpuinfo.Index);
		end
	end
end

% Load training data filenames and pairs for whitening
train_whiten = load(train_whiten_file);
if isfield(train_whiten, 'train') && isfield(train_whiten, 'val')
	cids  = [train_whiten.train.cids train_whiten.val.cids];
	qidxs = [train_whiten.train.qidxs train_whiten.val.qidxs+numel(train_whiten.train.cids)]; % query indexes
	pidxs = [train_whiten.train.pidxs train_whiten.val.pidxs+numel(train_whiten.train.cids)]; % positive indexes
else
	cids  = train_whiten.cids;
	qidxs = train_whiten.qidxs; % query indexes
	pidxs = train_whiten.pidxs; % positive indexes
end

% learn whitening
fprintf('>> whitening: Extracting CNN descriptors for training images...\n');
vecs_whiten = cell(1, numel(cids));
if numGpus <= 1
	progressbar(0);
	for i=1:numel(cids)
		vecs_whiten{i} = descfun(imresizemaxd(imread(cid2filename(cids{i}, ims_whiten_dir)), test_imdim, 0), net);
		progressbar(i/numel(cids));
	end
else
	time = tic;
	parfor i=1:numel(cids)
		if strcmp(net.device, 'cpu'), net.move('gpu'); end
		vecs_whiten{i} = descfun(imresizemaxd(imread(cid2filename(cids{i}, ims_whiten_dir)), test_imdim, 0), net);
	end
	fprintf('>>>> done in %s\n', htime(toc(time)));
end
vecs_whiten = cell2mat(vecs_whiten);
fprintf('>> whitening: Learning...\n');
Lw = whitenlearn(vecs_whiten, qidxs, pidxs);

% extract and evaluate
for d = 1:numel(test_datasets)
	fprintf('>> %s: Processing test dataset...\n', test_datasets{d});
	cfg = configdataset (test_datasets{d}, fullfile(data_root, 'test/')); % config file for the dataset

	fprintf('>> %s: Extracting CNN descriptors for db images...\n', test_datasets{d});
	vecs = cell(1, cfg.n);
	if numGpus <= 1
		progressbar(0);
		for i = 1:cfg.n
			vecs{i} = descfun(imresizemaxd(imread(cfg.im_fname(cfg, i)), test_imdim, 0), net);
			progressbar(i/cfg.n);
		end
	else
		time = tic;
		parfor i = 1:cfg.n
			if strcmp(net.device, 'cpu'), net.move('gpu'); end
			vecs{i} = descfun(imresizemaxd(imread(cfg.im_fname(cfg, i)), test_imdim, 0), net);
		end
		fprintf('>>>> done in %s\n', htime(toc(time)));
	end
	vecs = cell2mat(vecs);

	fprintf('>> %s: Extracting CNN descriptors for query images...\n', test_datasets{d});
	qvecs = cell(1, cfg.nq);
	if numGpus <= 1
		progressbar(0);
		for i = 1:cfg.nq
			qvecs{i} = descfun(crop_qim(imread(cfg.qim_fname(cfg, i)), cfg.gnd(i).bbx, test_imdim), net);
			progressbar(i/cfg.nq);
		end
	else
		time = tic;
		parfor i = 1:cfg.nq
			if strcmp(net.device, 'cpu'), net.move('gpu'); end
			qvecs{i} = descfun(crop_qim(imread(cfg.qim_fname(cfg, i)), cfg.gnd(i).bbx, test_imdim), net);
		end
		fprintf('>>>> done in %s\n', htime(toc(time)));
	end
	qvecs = cell2mat(qvecs);

	vecsLw = whitenapply(vecs, Lw.m, Lw.P); % apply whitening on database descriptors
	qvecsLw = whitenapply(qvecs, Lw.m, Lw.P); % apply whitening on query descriptors

	fprintf('>> %s: Retrieval...\n', test_datasets{d});
	if strcmp(test_datasets{d}, 'oxford5k') || strcmp(test_datasets{d}, 'paris6k')
		% % raw descriptors
		% sim = vecs'*qvecs;
		% [sim, ranks] = sort(sim, 'descend');
		% map = compute_map (ranks, cfg.gnd);
		% fprintf('>> %s: mAP = %.4f, without whiten\n', test_datasets{d}, map);
		% with learned whitening
		sim = vecsLw'*qvecsLw;
		[sim, ranks] = sort(sim, 'descend');
		map = compute_map (ranks, cfg.gnd);
		fprintf('>> %s: mAP = %.4f\n', test_datasets{d}, map);
	elseif strcmp(test_datasets{d}, 'roxford5k') || strcmp(test_datasets{d}, 'rparis6k')
		sim = vecsLw'*qvecsLw;
		[sim, ranks] = sort(sim, 'descend');
		% evaluate ranks
		ks = [1, 5, 10];
		% search for easy (E setup)
		for i = 1:numel(cfg.gnd), gnd(i).ok = [cfg.gnd(i).easy]; gnd(i).junk = [cfg.gnd(i).junk, cfg.gnd(i).hard]; end
		[mapE, apsE, mprE, prsE] = compute_map (ranks, gnd, ks);
		% search for easy & hard (M setup)
		for i = 1:numel(cfg.gnd), gnd(i).ok = [cfg.gnd(i).easy, cfg.gnd(i).hard]; gnd(i).junk = cfg.gnd(i).junk; end
		[mapM, apsM, mprM, prsM] = compute_map (ranks, gnd, ks);
		% search for hard (H setup)
		for i = 1:numel(cfg.gnd), gnd(i).ok = [cfg.gnd(i).hard]; gnd(i).junk = [cfg.gnd(i).junk, cfg.gnd(i).easy]; end
		[mapH, apsH, mprH, prsH] = compute_map (ranks, gnd, ks);
		fprintf('>> %s: mAP E: %.2f, M: %.2f, H: %.2f\n', test_datasets{d}, 100*mapE, 100*mapM, 100*mapH);
		fprintf('>> %s: mP@k[%d %d %d] E: [%.2f %.2f %.2f], M: [%.2f %.2f %.2f], H: [%.2f %.2f %.2f]\n', test_datasets{d}, ks(1), ks(2), ks(3), 100*mprE, 100*mprM, 100*mprH);
	end
end