% op_CSIoverlayMRI.m
% Brenden Kadota, Sunnybrook 2022
%
% DESCRIPTION:
% Overlay MRSI structure on spm_image graphic. Displays the MRSI specs onto
% the axial plane of the brain.
%
% USAGE:
% op_CSIoverlayMRI('/path/to/nifit/mri.nii', MRSIStruct, 1);
%
%
% INPUT:
% mriFileName           = string path to nifti file
% in                    = MRSI structure to overlay. In FID-A format
% coilIndex             = coilIndex to plot if coils have not been combined (default value = 1)
% averageIndex          = average index to plot
%
% OUTPUT:
%

function op_CSIoverlayMRI(MRSIStruct, mriFileName, coilIndex, averageIndex)
    arguments
        MRSIStruct (1, 1) struct
        mriFileName (:, 1) string
        coilIndex (1, 1) double = 1
        averageIndex (1, 1) double = 1
    end

    checkArguments(MRSIStruct);
    %call spm global variable
    global st
    global voxels
    global showVoxelOutlineFlag

    showVoxelOutlineFlag = false;

    % ensure filename is a character array not string
    mriFileName = char(mriFileName);
    if(contains(mriFileName, '~'))
        mriFileName = fullfile(getuserdir, mriFileName(2:end));
    end
    % display image using spm
    spm_image('display', mriFileName)

    % adds MRSI overlay function to SPM callback function
    st = addMRSIOverlayToSPMCallback(st, 'overlaySpectraOnMRI()');

    spmFigure = st.fig;
    buildMouseClickDropdown(spmFigure);
    buildPPMBoundTextBox(spmFigure);
    buildPlotTypeDropdown(spmFigure);
    buildShowVoxelButton(spmFigure);

    voxels = buildArrayOfVoxels(MRSIStruct);
    %plot the MRSI onto the spm mri graphic along the axial dimension.
    overlaySpectraOnMRI('real', min(MRSIStruct.ppm), max(MRSIStruct.ppm), MRSIStruct, coilIndex, averageIndex);
end

function change_plot(src,~)
    overlaySpectraOnMRI(src.String{src.Value});
end

function set_min(src, ~)
    max_obj = findobj('Tag', 'max');
    max_obj.String;
    ppm_min = str2double(src.String);
    ppm_max = str2double(max_obj.String);
    if(~isempty(ppm_min) && ~isempty(ppm_max))
        if(ppm_min <= ppm_max)
            plot_obj = findobj('Tag', 'plot_type');
            cur_type = plot_obj.String{plot_obj.Value};
            overlaySpectraOnMRI(cur_type, ppm_min, ppm_max);
        end
    end
end

function set_max(src, ~)
    min_obj = findobj('Tag', 'min');
    min_obj.String;
    ppm_max = str2double(src.String);
    ppm_min = str2double(min_obj.String);
    if(~isempty(ppm_min) && ~isempty(ppm_max))
        if(ppm_min <= ppm_max)
            plot_obj = findobj('Tag', 'plot_type');
            cur_type = plot_obj.String{plot_obj.Value};
            overlaySpectraOnMRI(cur_type, ppm_min, ppm_max);
        end
    end
end

function setMouseClickOption(src, ~)
    global st

    %get the axis object for the axial plane
    axis = st.vols{1}.ax{1}.ax;

    if(src.Value == 2)
        %If the popup menu is set to select_spec
        %set the callback function of click on axis to the method
        %select_spec.m
        set(axis, 'ButtonDownFcn', @select_spec);
        %position new axis here with properties
        axes('Visible','off', ...
             'Parent', st.fig, ...
             'YDir','normal', ...
             'Units','Pixels', ...
             'Box','on', ...
             'Position', [380 445 250 220], ...
             'Units','normalized', ...
             'Visible','on', ...
             'Tag', 'csi_axis');
    else
        %If the popup menu is set to reposition
        %set click on axial plane to reposition
        set(axis, 'ButtonDownFcn', @repos_start);
        %find the axis used to plot MRSI specs
        ax = findobj(gcf, 'Tag', 'csi_axis');
        %delete the specs
        delete(ax);
    end
end

%callback method used for repositioning
function repos_start(~, ~)
    currentFigure = gcbf;
    %checking if the click is a right click
    if ~strcmpi(get(currentFigure, 'SelectionType'),'alt')
        %set callback functions for movement of mouse and finished
        %clicking. spm_orthviews('reposition') is for movement of mouse
        % and repos_end for end of click.
        set(currentFigure, 'windowbuttonmotionfcn',@()(spm_orthviews('reposition')), 'windowbuttonupfcn',@repos_end);
        %call reposition in spm_orthviews
        spm_orthviews('reposition');
    end
end

function repos_end(~, ~)
    %remove callback functions for movement of mouse and wiindows
    %button up.
    set(gcbf,'windowbuttonmotionfcn','', 'windowbuttonupfcn','');
end

function showVoxelCallback(src, ~)
    global show_vox
    show_vox = src.Value;
    overlaySpectraOnMRI();
end

function checkArguments(in)
    if ~exist('spm_image', 'file')
        error('please add spm12 to your path before continuing');
    end
    checkSpatialFT(in)
end

function buildMouseClickDropdown(spmFigure)
    %Labels for dropdown
    labels={'Reposition', 'Select Spec'};
    %putting the dropdown menu in the figure to reposition or select spec
    mouseDropdown = uicontrol('Parent', spmFigure, ...
        'Units', 'Pixels', ...
        'Position', [320 400 100 20], ...
        'Style', 'Popupmenu', ...
        'String', labels);
    %selector callback function
    mouseDropdown.Callback = @setMouseClickOption;
end

function [maxPPMTextBox, minPPMTextBox] = buildPPMBoundTextBox(spmFigure)
    maxPPMTextBox = uicontrol('Parent', spmFigure, ...
        'Units', 'Pixels', ...
        'Position', [100, 405, 50, 20],...
        'Style', 'edit', ...
        'String', 'ppmmax', ...
        'Tag', 'max', ...
        'Callback', @set_max);

    minPPMTextBox = uicontrol('Parent', spmFigure, ...
        'Units', 'Pixels', ...
        'Position', [50, 405, 50, 20],...
        'Style', 'edit', ...
        'String', 'ppmmin', ...
        'Tag', 'min', ...
        'Callback', @set_min);
end

%
function buildPlotTypeDropdown(spmFigure)
    %labels for the dropdown menu
    plotLabels = {'real', 'imaginary', 'magnitude'};
    %creates a dropdown menu in the figure to decide to plot real, imaginary,
    %or magnitud
    plot_type_selector = uicontrol('Parent', spmFigure, ...
        'Units', 'Pixels', ...
        'Position', [200 400 100 20], ...
        'Style', 'Popupmenu', ...
        'String', plotLabels, ...
        'Tag', 'plot_type', ...
        'Callback', @change_plot);
end

function buildShowVoxelButton(spmFigure)
    uicontrol('Parent', spmFigure, ...
        'Units', 'Pixels', ...
        'Position', [500 405 60 20],...
        'Style', 'togglebutton', ...
        'String', 'Show Voxels', ...
        'Tag', 'show_voxels', ...
        'Callback', @showVoxelCallback);
end


function mvuInfo = mvuDeconstruct(model)

% MVUDECONSTRUCT break MVU in pieces for saving.
% FORMAT
% DESC takes an MVU model structure and breaks it into component
% parts for saving.
% ARG model : the model that needs to be saved.
% RETURN mvuInfo : a structure containing the other information
% from the MVU: what the sparse approximation is, what the inducing
% variables are.
%
% SEEALSO : mvuReconstruct
%
% COPYRIGHT : Neil D. Lawrence, 2009

% MLTOOLS

mvuInfo = model;
removeFields = {'Y'};

for i = 1:length(removeFields)
  if isfield(mvuInfo, removeFields{i})
    mvuInfo = rmfield(mvuInfo, removeFields{i});
  end
end


 function out = cpu(arg, varargin)
%function out = cpu(arg, varargin)
%|
%| cpu etic
%| cpu etoc printarg
%|	tic/toc elapsed time
%|
%| cpu etic
%| cpu tic
%| cpu toc
%| cpu toc printarg
%| work like tic/toc except they use cpu time instead of wall time.
%|
%| also:
%| If printarg begins with a ':' then caller_name preceeds when printing
%|
%| for multiple levels of clock use:
%|	c = cpu('etic');
%|	(run some slow commands)
%|	cpu(c, printarg)

if nargin < 1, ir_usage, end
if streq(arg, 'test'), cpu_test, return, end

% todo: add default behaviour

persistent t
if ~isvar('t') || isempty(t)
	t.tic = [];
	t.clock = [];
end

if isnumeric(arg) && numel(arg) == 6
	out = etime(clock, arg);

elseif streq(arg, 'tic')
	t.tic = cputime;
	if length(varargin), error 'tic takes no option', end
	return

elseif streq(arg, 'etic')
	if length(varargin), error 'tic takes no option', end
	if nargout == 1
		out = clock;
	else
		t.clock = clock;
	end
	return

elseif streq(arg, 'toc')
	if isempty(t.tic)
		error 'must initialize cpu with tic first'
	end
	out = cputime - t.tic;

elseif streq(arg, 'etoc')
	if isempty(t.clock)
		error 'must initialize cpu with etic first'
	end
	out = etime(clock, t.clock);

else
	error 'bad argument'
end

if length(varargin) == 1
	arg = varargin{1};
	if ischar(arg)
		if arg(1) == ':'
			[name line] = caller_name;
			if ~isempty(line) && line ~= 0
				name = [name sprintf(' %d', line)];
			end
			printf('%s%s %g', name, arg, out)
		else
			printf('%s %g', arg, out)
		end
	end

	if ~nargout
		clear out
	end
elseif length(varargin) > 1
	error 'too many arguments'
end

function cpu_test
cpu tic
cpu('toc');
cpu toc cpu_test:toc
cpu etic
cpu('etoc');
cpu etoc cpu_test:etoc
out = cpu('etoc', 'cpu_test:out');

tmp = cpu('etic');
pause(0.1)
cpu(tmp, 'ok')


function image = APPgetLabeledImage2(image, imsegs, pv, ph)
% image = APPgetLabeledImage2(image, imsegs, pv, ph)
%
% Copyright(C) Derek Hoiem, Carnegie Mellon University, 2006
% Current Version: 2.0  04/22/2006
%image = image / double(max(image(:)));

%grayim = rgb2gray(image);
image = rgb2hsv(image);
image(:, :, 2) = 0.0*image(:, :, 2);
image = hsv2rgb(image);

rad = round(max(size(image))/20);
markW = ceil(rad/10);
right_arrow = struct('x', 0, 'y', 0, 'angle', 0, 'radius', rad, 'head_length', rad/2, 'head_base_angle', 30);
up_arrow = struct('x', 0, 'y', 0, 'angle', 90, 'radius', rad, 'head_length', rad/2, 'head_base_angle', 30);
up_left_arrow = struct('x', 0, 'y', 0, 'angle', 135, 'radius', rad, 'head_length', rad/2, 'head_base_angle', 30);
up_right_arrow = struct('x', 0, 'y', 0, 'angle', 45, 'radius', rad, 'head_length', rad/2, 'head_base_angle', 30);
left_arrow = struct('x', 0, 'y', 0, 'angle', 180, 'radius', rad, 'head_length', rad/2, 'head_base_angle', 30);
down_arrow = struct('x', 0, 'y', 0, 'angle', 270, 'radius', rad, 'head_length', rad/2, 'head_base_angle', 30);
toward_arrow = struct('x', 0, 'y', 0, 'angle', 250, 'radius', rad*2/3, 'head_length', rad/3, 'head_base_angle', 60);

vhue = [112 0 177]/255;

if ndims(pv)==2
    [vconf, vlab] = max(pv, [], 2);
    [hconf, hlab] = max(ph, [], 2);

    vlabim = vlab(imsegs.segimage);
    hlabim = hlab(imsegs.segimage);
    hconfim = hconf(imsegs.segimage);
    %vconf = max((vconf - 0.5)*2, 0);
    %vconf = max(vconf, 1);

    hueim = vhue(vlabim);
    satim = vconf(imsegs.segimage);
    valim = ones(size(hueim));
else
    [satim, vlabim] = max(pv, [], 3);
    [hconfim, hlabim] = max(ph, [], 3);
    hueim = vhue(vlabim);
    valim = ones(size(hueim));
end

% set color
image = image*0.5 + 0.5*hsv2rgb(hueim, satim, valim);

% draw boundaries of main classes
% [gx, gy] = gradient(vlabim);
% g = gx.^2 + gy.^2;
% g = conv2(g, ones(2), 'same');
% edgepix = find(g>0);
% npix = numel(hueim);
% for b = 1:3
%     image((b-1)*npix+edgepix) = 0;
% end

if 1 % set do draw subclass marks

%hconfim = max((hconf(imsegs.segimage)-0.5)*2, 0);

height = size(image, 1);  width = size(image, 2);

for x = rad:round(rad*1.51):(width-rad)
    for y = rad:round(rad*1.51):(height-rad)

        %val = 1-hconfim(y, x);
        val = [0 0 0];

        arrows = [];
        if vlabim(y, x)==2 % vertical
            if hlabim(y, x)==1 % left
                arrows = left_arrow;
            elseif hlabim(y, x)==2 % center
                arrows = up_arrow;
            elseif hlabim(y, x)==3 % right
                arrows = right_arrow;
            elseif hlabim(y, x)==4 % porous
                image = draw_circle_image(image, x, y, rad/2, val, markW);
            elseif hlabim(y, x)==5 % solid
                image = draw_x_image(image, x, y, rad, val, markW);
            end

        end

        % draw arrows
        for a = 1:length(arrows)
            arrows(a).x = x;
            arrows(a).y = y;
        end

        image = draw_arrow_image(image, arrows, val, markW);

    end

end
end


function net_inputs = rfcn_get_minibatch(conf, image_roidb)
% net_inputs = rfcn_get_minibatch(conf, image_roidb)
% --------------------------------------------------------
% R-FCN implementation
% Modified from MATLAB Faster R-CNN (https://github.com/shaoqingren/faster_rcnn)
% Copyright (c) 2016, Jifeng Dai
% Licensed under The MIT License [see LICENSE for details]
% --------------------------------------------------------

    num_images = length(image_roidb);
    if conf.bbox_class_agnostic
        num_classes = 1;
    else
        % Infer number of classes from the number of columns in gt_overlaps
        num_classes = size(image_roidb(1).overlap, 2);
    end
    % Sample random scales to use for each image in this batch
    random_scale_inds = randi(length(conf.scales), num_images, 1);

    if conf.batch_size > 0
        assert(mod(conf.batch_size, num_images) == 0, ...
            sprintf('num_images %d must divide BATCH_SIZE %d', num_images, conf.batch_size));

        rois_per_image = conf.batch_size / num_images;
        fg_rois_per_image = round(rois_per_image * conf.fg_fraction);
    else
        rois_per_image = inf;
        fg_rois_per_image = inf;
    end

    % Get the input image blob
    [im_blob, im_scales] = get_image_blob(conf, image_roidb, random_scale_inds);

    % build the region of interest and label blobs
    rois_blob = zeros(0, 5, 'single');
    labels_blob = zeros(0, 1, 'single');
    bbox_targets_blob = zeros(0, 4 * (num_classes+1), 'single');
    bbox_loss_blob = zeros(size(bbox_targets_blob), 'single');

    for i = 1:num_images
        [labels, ~, im_rois, bbox_targets, bbox_loss] = ...
            sample_rois(conf, image_roidb(i), fg_rois_per_image, rois_per_image);

        % Add to ROIs blob
        feat_rois = rfcn_map_im_rois_to_feat_rois(conf, im_rois, im_scales(i));
        batch_ind = i * ones(size(feat_rois, 1), 1);
        rois_blob_this_image = [batch_ind, feat_rois];
        rois_blob = [rois_blob; rois_blob_this_image];

        % Add to labels, bbox targets, and bbox loss blobs
        labels_blob = [labels_blob; labels];
        bbox_targets_blob = [bbox_targets_blob; bbox_targets];
        bbox_loss_blob = [bbox_loss_blob; bbox_loss];
    end

    % permute data into caffe c++ memory, thus [num, channels, height, width]
    im_blob = im_blob(:, :, [3, 2, 1], :); % from rgb to brg
    im_blob = single(permute(im_blob, [2, 1, 3, 4]));
    rois_blob = rois_blob - 1; % to c's index (start from 0)
    rois_blob = single(permute(rois_blob, [3, 4, 2, 1]));
    labels_blob = single(permute(labels_blob, [3, 4, 2, 1]));
    bbox_targets_blob = single(permute(bbox_targets_blob, [3, 4, 2, 1]));
    bbox_loss_blob = single(permute(bbox_loss_blob, [3, 4, 2, 1]));

    assert(~isempty(im_blob));
    assert(~isempty(rois_blob));
    assert(~isempty(labels_blob));
    assert(~isempty(bbox_targets_blob));
    assert(~isempty(bbox_loss_blob));

    net_inputs = {im_blob, rois_blob, labels_blob, bbox_targets_blob, bbox_loss_blob};
end

%% Build an input blob from the images in the roidb at the specified scales.
function [im_blob, im_scales] = get_image_blob(conf, images, random_scale_inds)

    num_images = length(images);
    processed_ims = cell(num_images, 1);
    im_scales = nan(num_images, 1);
    for i = 1:num_images
        im = imread(images(i).image_path);
        target_size = conf.scales(random_scale_inds(i));

        [im, im_scale] = prep_im_for_blob(im, conf.image_means, target_size, conf.max_size);

        im_scales(i) = im_scale;
        processed_ims{i} = im;
    end

    im_blob = im_list_to_blob(processed_ims);
end

%% Generate a random sample of ROIs comprising foreground and background examples.
function [labels, overlaps, rois, bbox_targets, bbox_loss_weights] = sample_rois(conf, image_roidb, fg_rois_per_image, rois_per_image)

    [overlaps, labels] = max(image_roidb(1).overlap, [], 2);
%     labels = image_roidb(1).max_classes;
%     overlaps = image_roidb(1).max_overlaps;
    rois = image_roidb(1).boxes;

    % Select foreground ROIs as those with >= FG_THRESH overlap
    fg_inds = find(overlaps >= conf.fg_thresh);
    % Guard against the case when an image has fewer than fg_rois_per_image
    % foreground ROIs
    fg_rois_per_this_image = min(fg_rois_per_image, length(fg_inds));
    % Sample foreground regions without replacement
    if ~isempty(fg_inds)
       fg_inds = fg_inds(randperm(length(fg_inds), fg_rois_per_this_image));
    end

    % Select background ROIs as those within [BG_THRESH_LO, BG_THRESH_HI)
    bg_inds = find(overlaps < conf.bg_thresh_hi & overlaps >= conf.bg_thresh_lo);
    % Compute number of background ROIs to take from this image (guarding
    % against there being fewer than desired)
    bg_rois_per_this_image = rois_per_image - fg_rois_per_this_image;
    bg_rois_per_this_image = min(bg_rois_per_this_image, length(bg_inds));
    % Sample foreground regions without replacement
    if ~isempty(bg_inds)
       bg_inds = bg_inds(randperm(length(bg_inds), bg_rois_per_this_image));
    end
    % The indices that we're selecting (both fg and bg)
    keep_inds = [fg_inds; bg_inds];
    % Select sampled values from various arrays
    labels = labels(keep_inds);
    % Clamp labels for the background ROIs to 0
    labels((fg_rois_per_this_image+1):end) = 0;
    overlaps = overlaps(keep_inds);
    rois = rois(keep_inds, :);

    if conf.bbox_class_agnostic
        assert(all((labels>0) == image_roidb.bbox_targets(keep_inds, 1)));
    else
        assert(all(labels == image_roidb.bbox_targets(keep_inds, 1)));
    end

    % Infer number of classes from the number of columns in gt_overlaps
    num_classes = size(image_roidb(1).overlap, 2);

    [bbox_targets, bbox_loss_weights] = get_bbox_regression_labels(conf, ...
        image_roidb.bbox_targets(keep_inds, :), num_classes);

end

function [bbox_targets, bbox_loss_weights] = get_bbox_regression_labels(conf, bbox_target_data, num_classes)
%% Bounding-box regression targets are stored in a compact form in the roidb.
 % This function expands those targets into the 4-of-4*(num_classes+1) representation used
 % by the network (i.e. only one class has non-zero targets).
 % The loss weights are similarly expanded.
% Return (N, (num_classes+1) * 4, 1, 1) blob of regression targets
% Return (N, (num_classes+1 * 4, 1, 1) blob of loss weights
    if conf.bbox_class_agnostic
        num_classes = 1;
    end

    clss = bbox_target_data(:, 1);
    bbox_targets = zeros(length(clss), 4 * (num_classes+1), 'single');
    bbox_loss_weights = zeros(size(bbox_targets), 'single');
    inds = find(clss > 0);
    for i = 1:length(inds)
       ind = inds(i);
       cls = clss(ind);
       targets_inds = (1+cls*4):((cls+1)*4);
       bbox_targets(ind, targets_inds) = bbox_target_data(ind, 2:end);
       bbox_loss_weights(ind, targets_inds) = 1;
    end
end


function header = sofa_get_header(sofa)
%SOFA_GET_HEADER returns the header of a SOFA file or struct
%
%   Usage: header = sofa_get_header(sofa)
%
%   Input parameters:
%       sofa    - impulse response data set (SOFA struct/file)
%
%   Output parameters:
%       header  - SOFA header
%
%   SOFA_GET_HEADER(sofa) returns the header of the given SOFA file or struct.
%   For the struct the SOFA file has to loaded before with SOFAload().
%   For a description of the SOFA file format see: https://sofaconventions.org
%
%   See also: sofa_get_data, sofa_is_file, get_ir, SOFAload

%*****************************************************************************
% The MIT License (MIT)                                                      *
%                                                                            *
% Copyright (c) 2010-2019 SFS Toolbox Developers                             *
%                                                                            *
% Permission is hereby granted,  free of charge,  to any person  obtaining a *
% copy of this software and associated documentation files (the "Software"), *
% to deal in the Software without  restriction, including without limitation *
% the rights  to use, copy, modify, merge,  publish, distribute, sublicense, *
% and/or  sell copies of  the Software,  and to permit  persons to whom  the *
% Software is furnished to do so, subject to the following conditions:       *
%                                                                            *
% The above copyright notice and this permission notice shall be included in *
% all copies or substantial portions of the Software.                        *
%                                                                            *
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
% IMPLIED, INCLUDING BUT  NOT LIMITED TO THE  WARRANTIES OF MERCHANTABILITY, *
% FITNESS  FOR A PARTICULAR  PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL *
% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
% LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT, TORT  OR OTHERWISE, ARISING *
% FROM,  OUT OF  OR IN  CONNECTION  WITH THE  SOFTWARE OR  THE USE  OR OTHER *
% DEALINGS IN THE SOFTWARE.                                                  *
%                                                                            *
% The SFS Toolbox  allows to simulate and  investigate sound field synthesis *
% methods like wave field synthesis or higher order ambisonics.              *
%                                                                            *
% https://sfs.readthedocs.io                            sfstoolbox@gmail.com *
%*****************************************************************************

%% ===== Checking of input  parameters ==================================
nargmin = 1;
nargmax = 1;
narginchk(nargmin,nargmax)

%% ===== Computation ====================================================
% Get only the metadata of the SOFA data set
if sofa_is_file(sofa)
    header = SOFAload(sofa,'nodata');
else
    header = sofa;
    if isfield(sofa.Data,'IR')
        header.Data = rmfield(sofa.Data,'IR');
    end
end


function [stateLogCol] = ma_resNumToStateLogCol(resNum)
%ma_resNumToStateLogCol Summary of this function goes here
%   Detailed explanation goes here

    switch resNum
        case 1
            stateLogCol = 10;
        case 2
            stateLogCol = 11;
        case 3
            stateLogCol = 12;
        otherwise
            error('Resource number %u has no mapping to a state log column number!', resNum);
    end

end


contactList{1,1} = 'title 1'; contactList{1,2} = 'author name 1'; contactList{1,3} = 'spam@email.com';
contactList{2,1} = 'title 2'; contactList{2,2} = 'author name 2'; contactList{2,3} = 'spam@email.com';

subjectLine = 'title of the emails';
bodyLine = ['Dear %s,\n',...
'\n',...
'We will be organizing XXX\n',...
'Sincerely,\n',...
'The Organizers'];
cmdLine = 'mail -s "%s" %s < email.txt';

for itemID =1:size(contactList,1)
    paperTitle = contactList{itemID,1};
    authorName = contactList{itemID,2};
    emailAddress = contactList{itemID,3};

    % write the file
    fp = fopen('email.txt','w');
    fprintf(fp,bodyLine,authorName, paperTitle);
    fclose(fp);

    % run command
    cmd = sprintf(cmdLine,subjectLine,emailAddress);
    system(cmd);

    % delete file
    delete('email.txt');
end


function varargout = process_mni_normalize( varargin )
% PROCESS_MNI_NORMALIZE: Compute deformation fields to the MNI ICBM152 space.
%
% USAGE:     OutputFiles = process_mni_normalize('Run', sProcess, sInputs)
%         [isOk, errMsg] = process_mni_normalize('Compute',            T1File, Method)
%                          process_mni_normalize('ComputeInteractive', T1File, Method=[ask], isUnload=1)

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

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription()
    % Description the process
    sProcess.Comment     = 'MNI normalization';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = {'Import', 'Import anatomy'};
    sProcess.Index       = 10;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/ImportAnatomy#MNI_transformation';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'import'};
    sProcess.OutputTypes = {'import'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 0;
    sProcess.isSeparator = 1;
    % Option: Subject name
    sProcess.options.subjectname.Comment = 'Subject name:';
    sProcess.options.subjectname.Type    = 'subjectname';
    sProcess.options.subjectname.Value   = 'NewSubject';
    % Method
    sProcess.options.method.Comment = {...
         'maff8:
Affine registration using SPM mutual information algorithm.
Estimates a simple 4x4 linear transformation to the MNI space.
Included in Brainstorm.', ...
         'segment:
Non-linear normalization and tissue classification with SPM12.
Estimates forward and inverse deformation fields to MNI space (IXI549).
Requires the installation of SPM12.'; ...
         'maff8', 'segment'};
    sProcess.options.method.Type    = 'radio_label';
    sProcess.options.method.Value   = 'maff8';
    % Use T2 when available
    sProcess.options.uset2.Comment = 'Use T2 when available ("segment" only)';
    sProcess.options.uset2.Type    = 'checkbox';
    sProcess.options.uset2.Value   = 0;
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess)
    Comment = sProcess.Comment;
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs)
    OutputFiles = {};

    % ===== GET OPTIONS =====
    % Get subject name
    SubjectName = file_standardize(sProcess.options.subjectname.Value);
    if isempty(SubjectName)
        bst_report('Error', sProcess, [], 'Subject name is empty.');
        return;
    end
    % Get method
    Method = sProcess.options.method.Value;
    % Use T2
    UseT2 = sProcess.options.uset2.Value;

    % ===== GET SUBJECT =====
    % Get subject
    [sSubject, iSubject] = bst_get('Subject', SubjectName);
    if isempty(iSubject)
        bst_report('Error', sProcess, [], ['Subject "' SubjectName '" does not exist.']);
        return
    end
    % Check if a MRI is available for the subject
    if isempty(sSubject.Anatomy) || isempty(sSubject.iAnatomy)
        bst_report('Error', sProcess, [], ['No MRI available for subject "' SubjectName '".']);
        return
    end
    % Get default MRI as T1
    T1File = sSubject.Anatomy(sSubject.iAnatomy).FileName;
    % Look for T2 MRI
    T2File = [];
    if UseT2 && (length(sSubject.Anatomy) > 1)
        iT2 = find(~cellfun(@(c)isempty(strfind(c,'t2')), lower({sSubject.Anatomy.Comment})));
        % Warning when multiple T2 images
        if (length(iT2) > 1)
            bst_report('Warning', sProcess, [], ['Subject "' sSubject.Name '" has multiple anatomy volumes with tag "T2": not using T2 to avoid confusion.' 10 ...
                'To use a T2 MRI for better volume segmentation, rename or delete the extra T2-labelled files from the subject anatomy.']);
            iT2 = [];
        end
        % Get T2 filename
        if ~isempty(iT2)
            T2File = sSubject.Anatomy(iT2).FileName;
        end
    end

    % ===== COMPUTE MNI TRANSFORMATION =====
    % Call normalize function
    [sMriT1, errMsg] = Compute(T1File, Method, T2File);
    % Error handling
    if ~isempty(errMsg)
        bst_report('Error', sProcess, [], errMsg);
    end

    OutputFiles = {'import'};
end

%% ===== COMPUTE =====
function [sMriT1, errMsg] = Compute(T1File, Method, T2File)
    % Parse inputs
    if (nargin < 3) || isempty(T2File)
        T2File = [];
    end
    % Compute normalization
    [sMriT1, errMsg] = bst_normalize_mni(T1File, Method, T2File);
end

%% ===== COMPUTE/INTERACTIVE =====
function sMriT1 = ComputeInteractive(T1File, Method, isUnload, T2File)
    % Parse inputs
    if (nargin < 4) || isempty(T2File)
        T2File = [];
    end
    if (nargin < 3) || isempty(isUnload)
        isUnload = 1;
    end
    if (nargin < 2) || isempty(Method)
        Method = [];
    end
    % Unloading everything
    if isUnload
        bst_memory('UnloadAll', 'Forced');
    end
    % Ask method
    if isempty(Method)
        sProcess = GetDescription();
        Method = java_dialog('question', ['' sprintf('%s

', sProcess.options.method.Comment{1,:})], ...
            'MNI normalization method', [], {sProcess.options.method.Comment{2,:}, 'Cancel'}, sProcess.options.method.Comment{2,1});
        % Cancel
        if isempty(Method) || strcmpi(Method, 'Cancel')
            sMriT1 = [];
            return;
        end
    end
    % For Segment method: look for T2 MRI in the subject
    if strcmpi(Method, 'segment') && isempty(T2File)
        % Get subject info
        sSubject = bst_get('MriFile', T1File);
        % Find any possible T2
        iT2 = [];
        if (length(sSubject.Anatomy) > 1)
            iT2 = find(~cellfun(@(c)isempty(strfind(c,'t2')), lower({sSubject.Anatomy.Comment})));
            % Warning when multiple T2 images
            if (length(iT2) > 1)
                res = java_dialog('question', ...
                    ['Subject "' sSubject.Name '" has multiple anatomy volumes with tag "T2".' 10 10 ...
                     'If you want to use a T2 MRI for better volume segmentation, ' 10 ...
                     'rename or delete the extra T2-labelled files from the subject anatomy.' 10 10 ...
                     'If you click OK, no T2 image will be used in the segmentation.' 10 10], 'MNI normalization', [], {'OK', 'Cancel'}, 'OK');
                if isempty(res) || isequal(res, 'Cancel')
                    return;
                end
                iT2 = [];
            % Confirmation for using T2 image
            elseif (length(iT2) == 1)
                isT2 = java_dialog('confirm', ...
                    ['Subject "' sSubject.Name '" seems to include one T2 MRI, named "' sSubject.Anatomy(iT2).Comment '".' 10 10 ...
                     'Use this T2 image to refine the volume segmentation?' 10 10], 'MNI normalization');
                if ~isT2
                    iT2 = [];
                end
            end
        end
        % Get T2 filename
        if ~isempty(iT2)
            T2File = sSubject.Anatomy(iT2).FileName;
        end
    end
    % Open progress bar
    bst_progress('start', 'MNI normalization', 'Initialization...');
    % Call non-interactive function
    [sMriT1, errMsg] = Compute(T1File, Method, T2File);
    % Error handling
    if ~isempty(errMsg)
        sMriT1 = [];
        bst_error(errMsg, 'MNI normalization', 0);
    end
    % Close progress bar
    bst_progress('stop');
end


function prtPlotUtilAlgorithmGui(connectivityMatrix, actionCell, algo)
% Internal function, for PRT use only, makes use of GraphViz
% xxx NEED HELP xxx

% Given the algorithms action cell extract the names of each of the blocks
% Also add in Input and Output blocks
algoStr = cellfun(@(c)c.nameAbbreviation,actionCell,'uniformoutput',false);
algoStr = cat(1,{'Input'}, algoStr(:), {'Output'});
actionCell = cat(1,{[]},actionCell(:),{[]});

% Call GraphViz to get a good initial layout
GraphLayoutInfo = prtPlotUtilGraphVizRun(connectivityMatrix');

% Scale the GraphViz layout to get something reasonable
nodePosMat = cat(1,GraphLayoutInfo.Nodes.pos);
nodePosMat = bsxfun(@minus,nodePosMat,min(nodePosMat));
nodePosMat = bsxfun(@rdivide,nodePosMat,max(nodePosMat));
nodePosMat(isnan(nodePosMat))=0;

% Decide how big the blocks can reasonably be given the number of blocks
% we have to put in the unit square
minDistBetweenBlocks = min(min(prtDistanceLNorm(nodePosMat,nodePosMat,2) + realmax*eye(size(connectivityMatrix))));
blockSize = minDistBetweenBlocks*2/3;

% Because nodePosMat gives us the left corner we have to modify the block
% size a little
nodePosMat = bsxfun(@rdivide,nodePosMat,max(nodePosMat)+[blockSize blockSize]);

strLengths = cellfun(@(s)length(s),algoStr);

textSizes = blockSize./(strLengths+1)/0.7;
textSizes = ones(size(textSizes))*min(textSizes);
originalTextSizes = textSizes;
% Add one character for the sides
% 0.8 is an approximate to the aspect ratio of fonts.

% Get the block drawing options
Options = localGetOptions();

Handles = localMakeFigure();

% Ready the outputs
BlankBlock = struct('handle',[],'textHandle',[], 'Object',[]);

Layout.nBlocks = size(connectivityMatrix,1);
Layout.Blocks = BlankBlock;
Layout.connectivity = connectivityMatrix;
Layout.edges = zeros(sum(connectivityMatrix(:)),1);

Gui.status = '';
Gui.previous_point = [];

for iBlockOuter = 1:Layout.nBlocks
    placeBlockFunction([], [], actionCell{iBlockOuter}, nodePosMat(iBlockOuter,:), iBlockOuter, algoStr{iBlockOuter}, textSizes(iBlockOuter));
end

for iEdge = 1:length(GraphLayoutInfo.Edges)
    startLoc = cat(2,nodePosMat(GraphLayoutInfo.Edges(iEdge).startIndex,1)+blockSize,nodePosMat(GraphLayoutInfo.Edges(iEdge).startIndex,2)+blockSize/2);
    stopLoc = cat(2,nodePosMat(GraphLayoutInfo.Edges(iEdge).stopIndex,1),nodePosMat(GraphLayoutInfo.Edges(iEdge).stopIndex,2)+blockSize/2);

    %Layout.edges(iEdge) = prtPlotUtilPlotArrow(cat(1,startLoc(1),stopLoc(1)),cat(1,startLoc(2),stopLoc(2)),[20 4]);
    %set(Layout.edges(iEdge),'facecolor',[0 0 0],'edgecolor',[0 0 0])

    Layout.edges(iEdge) = prtPlotUtilPlotArrow(cat(1,startLoc(1),stopLoc(1)),cat(1,startLoc(2),stopLoc(2)),[],'headWidth',0.01,'realHeadLength',0.01);

end

centerBlocks();

set(gcf,'NextPlot','new')

%% Begin Functions

    function patchWindowButtonMotion(hObject, eventData, blockIndex, offSet) %#ok
        cp = get(Handles.AxesPanel.mainAxes,'currentPoint');

        oldBlockPosition = get(Layout.Blocks(blockIndex).handle,'position');

        cp = cp - blockSize/2 + repmat(cat(2, offSet, 0),2,1);

        Layout.Blocks(blockIndex).polygonPosition = cp(1,1:2);

        set(Layout.Blocks(blockIndex).handle,'position',cat(2,cp(1,1:2),oldBlockPosition(3:4)));
        set(Layout.Blocks(blockIndex).textHandle,'Position',[cp(1,1)+Options.blockSize(2)/2 cp(1,2)+Options.blockSize(2)/2 0]);

        %Move Edges
        for jEdge = 1:length(GraphLayoutInfo.Edges)
            if blockIndex == GraphLayoutInfo.Edges(jEdge).startIndex || blockIndex == GraphLayoutInfo.Edges(jEdge).stopIndex

                startInd = GraphLayoutInfo.Edges(jEdge).startIndex;
                stopInd = GraphLayoutInfo.Edges(jEdge).stopIndex;

                cStart = get(Layout.Blocks(startInd).handle,'position');
                cStop = get(Layout.Blocks(stopInd).handle,'position');

                startLoc = cStart(1:2) + [blockSize blockSize/2];
                stopLoc = cStop(1:2) + [0 blockSize/2];

%                 try  %#ok<TRYNC>
%                     delete(Layout.edges(jEdge));
%                 end
%                 Layout.edges(jEdge) = prtPlotUtilPlotArrow(cat(1,startLoc(1),stopLoc(1)),cat(1,startLoc(2),stopLoc(2)));

                %Layout.edges(jEdge) = prtPlotUtilPlotArrow(cat(1,startLoc(1),stopLoc(1)),cat(1,startLoc(2),stopLoc(2)),[20 4]);
                %set(Layout.edges(jEdge),'facecolor',[0 0 0],'edgecolor',[0 0 0])

                Layout.edges(jEdge) = prtPlotUtilPlotArrow(cat(1,startLoc(1),stopLoc(1)),cat(1,startLoc(2),stopLoc(2)),Layout.edges(jEdge));
            end
        end
    end

    function patchButtonDownFunction(hObject, eventData, blockIndex) %#ok<INUSL>

        switch lower(get(Handles.handle, 'selectiontype'))
            case 'open'
                % Nothing for now
                BlockObject = Layout.Blocks(blockIndex).Object;
                if ~isempty(BlockObject) && BlockObject.isTrained && prtUtilIsMethodIncludeHidden(BlockObject,'plot') && BlockObject.dataSetSummary.nFeatures < 4
                    plot(BlockObject);
                elseif (blockIndex == length(Layout.Blocks)) && algo.isPlottableAsClassifier
                    plotAsClassifier(algo);
                elseif (blockIndex == length(Layout.Blocks)) && algo.isPlottableAsRegressor
                    plotAsRegressor(algo);
                end

            otherwise % case 'normal'

                % Get block clock offset
                cp = get(Handles.AxesPanel.mainAxes,'currentPoint');

                cPolyCorner = Layout.Blocks(blockIndex).polygonPosition;

                offSet = cPolyCorner + blockSize/2 - cp(1,1:2);

                set(Handles.handle,'WindowButtonMotionFcn',@(h,E)patchWindowButtonMotion(h,E, blockIndex, offSet));
                set(Handles.handle,'WindowButtonUpFcn',@(h,E)patchButtonUpFunction(h,E, blockIndex));
                set(Layout.Blocks(blockIndex).handle,'Selected','off')
        end
    end

    function patchButtonUpFunction(hObject, eventData, blockIndex)   %#ok<INUSL>
        set(Handles.handle,'WindowButtonMotionFcn',@mouseControlWindowButtonMotionFcn,...
                           'WindowButtonDownFcn'  , @mouseControlWindowButtonDownFcn,...
                           'WindowButtonUpFcn'    , @mouseControlWindowButtonUpFcn);
        Gui.status = '';

        set(Layout.Blocks(blockIndex).handle,'Selected','off')
    end

    function placeBlockFunction(hObject, eventData, BlockObject, position, iBlock, blockStr, textSize)  %#ok<INUSL>

        % Extract stuff from the options so that the GUI can use it
        if isempty(BlockObject)
            blockColor = Options.BlockColors.dataSet;
            textColor = Options.BlockTextColors.dataSet;
        elseif isa(BlockObject,'prtPreProc')
            blockColor = Options.BlockColors.preProcessor;
            textColor = Options.BlockTextColors.preProcessor;
        elseif isa(BlockObject,'prtFeatSel')
            blockColor = Options.BlockColors.featureSelector;
            textColor = Options.BlockTextColors.featureSelector;
        elseif isa(BlockObject,'prtClass')
            blockColor = Options.BlockColors.classifier;
            textColor = Options.BlockTextColors.classifier;
        elseif isa(BlockObject,'prtDecision')
            blockColor = Options.BlockColors.decision;
            textColor = Options.BlockTextColors.decision;
        elseif isa(BlockObject,'prtDecision')
            blockColor = Options.BlockColors.decision;
            textColor = Options.BlockTextColors.decision;
        elseif isa(BlockObject,'prtOutlierRemoval')
            blockColor = Options.BlockColors.outlierRemoval ;
            textColor = Options.BlockTextColors.outlierRemoval ;
        elseif isa(BlockObject,'prtCluster')
            blockColor = Options.BlockColors.cluster ;
            textColor = Options.BlockTextColors.cluster ;
        elseif isa(BlockObject,'prtRv')
            blockColor = Options.BlockColors.rv ;
            textColor = Options.BlockTextColors.rv ;
        else
            blockColor = [1 1 1];
            textColor = [0 0 0];
        end

        Layout.Blocks(iBlock).polygonPosition = position;
        Layout.Blocks(iBlock).handle = rectangle('Position',[position,blockSize,blockSize],'Curvature',[0.25, 0.25],'FaceColor',blockColor,'LineWidth',2);

        if (~isempty(BlockObject) && BlockObject.isTrained && prtUtilIsMethodIncludeHidden(BlockObject,'plot') && BlockObject.dataSetSummary.nFeatures < 4)
            %set(Layout.Blocks(iBlock).handle,'lineWidth',2,'EdgeColor',[1 1 0.1]);
            set(Layout.Blocks(iBlock).handle,'lineWidth',4);
        end

        if (iBlock == Layout.nBlocks) && (algo.isPlottableAsClassifier || algo.isPlottableAsRegressor)
            set(Layout.Blocks(iBlock).handle,'lineWidth',4);
        end

        Layout.Blocks(iBlock).textHandle = text(position(1)+blockSize/2,position(2)+blockSize/2,blockStr,'VerticalAlignment','Middle','HorizontalAlignment','Center','Color',textColor,'FontUnits','Normalized','FontSize',textSize,'FontWeight','Bold');

        set(Layout.Blocks(iBlock).handle,'ButtonDownFcn',@(h,E)patchButtonDownFunction(h,E,iBlock));
        set(Layout.Blocks(iBlock).textHandle,'ButtonDownFcn',@(h,E)patchButtonDownFunction(h,E,iBlock));

        Layout.Blocks(iBlock).Object = BlockObject;
    end

    function mouseControlWindowScrollWheelZoomFcn(varargin)
        scrollAmountFactor = 50;

        if ~ishandle(Handles.handle)
            return
        end

        % current axes?
        if get(Handles.handle, 'currentaxes') ~= Handles.AxesPanel.mainAxes
            return
        end

        % get the amount of scolls
        scrolls = varargin{2}.VerticalScrollCount;

        % Get the axes' x- and y-limits
        xlim = get(Handles.AxesPanel.mainAxes, 'xlim');
        ylim = get(Handles.AxesPanel.mainAxes, 'ylim');

        % get the current camera position, and save the [z]-value
        cam_pos_Z = get(Handles.AxesPanel.mainAxes, 'cameraposition');
        cam_pos_Z = cam_pos_Z(3);

        % get the current point
        old_position = get(Handles.AxesPanel.mainAxes, 'CurrentPoint');
        old_position(1,3) = cam_pos_Z;

        % calculate zoom factor
        zoomfactor = 1 - scrolls/scrollAmountFactor;

        % adjust camera position
        set(Handles.AxesPanel.mainAxes,...
            'cameratarget', [old_position(1, 1:2), 0],...
            'cameraposition', old_position(1, 1:3));

        % adjust the camera view angle (equal to zooming in)
        camzoom(zoomfactor);

        % zooming with the camera has the side-effect of
        % NOT adjusting the axes limits. We have to correct for this:
        x_lim1 = (old_position(1,1) - min(xlim))/zoomfactor;
        x_lim2 = (max(xlim) - old_position(1,1))/zoomfactor;
        xlim   = [old_position(1,1) - x_lim1, old_position(1,1) + x_lim2];
        y_lim1 = (old_position(1,2) - min(ylim))/zoomfactor;
        y_lim2 = (max(ylim) - old_position(1,2))/zoomfactor;
        ylim   = [old_position(1,2) - y_lim1, old_position(1,2) + y_lim2];
        set(Handles.AxesPanel.mainAxes, 'xlim', xlim)
        set(Handles.AxesPanel.mainAxes, 'ylim', ylim)

        % set new camera position
        new_position = get(Handles.AxesPanel.mainAxes, 'CurrentPoint');
        old_camera_target =  get(Handles.AxesPanel.mainAxes, 'CameraTarget');
        old_camera_target(3) = cam_pos_Z;
        new_camera_position = old_camera_target - ...
            (new_position(1,1:3) - old_camera_target(1,1:3));

        % adjust camera target and position
        set(Handles.AxesPanel.mainAxes, 'cameraposition', new_camera_position(1, 1:3),...
            'cameratarget', [new_camera_position(1, 1:2), 0]);

        % we also have to re-set the axes to stretch-to-fill mode
        set(Handles.AxesPanel.mainAxes, 'cameraviewanglemode', 'auto',...
            'camerapositionmode', 'auto',...
            'cameratargetmode', 'auto');

        % Resize the text since the block size has changed
        xRange = xlim(2)-xlim(1);
        yRange = ylim(2)-ylim(1);

        zoomFactorFromOriginal = max(xRange,yRange) ./ (Options.initialCanvasLimits(2)-Options.initialCanvasLimits(1));
        textSizes = 1./zoomFactorFromOriginal.*originalTextSizes;

        resizeText();
    end

    function mouseControlWindowButtonDownFcn(varargin)
        % If we are not a handle get out of here
        if ~ishandle(Handles.handle)
            return
        end

        % current axes?
        if get(Handles.handle, 'currentaxes') ~= Handles.AxesPanel.mainAxes
            return
        end

        % perform appropriate action
        switch lower(get(Handles.handle, 'selectiontype'))
            % start panning on left click
            case 'normal'
                cp = get(Handles.AxesPanel.mainAxes, 'CurrentPoint');

                deselectEverything();
                % Clicking on back canvas Allow dragging around
                Gui.status = 'down';
                Gui.previous_point = cp;

            case 'open' % double click (left or right)
                centerBlocks();
                Gui.status = '';
        end
    end

    function mouseControlWindowButtonUpFcn(varargin)
        % double check if these axes are indeed the current axes
        if get(Handles.handle, 'currentaxes') ~= Handles.AxesPanel.mainAxes
            return
        end

        %deselectEverything();
        % reset Gui.status
        Gui.status = '';
    end

    % The primary mouse button controler
    function mouseControlWindowButtonMotionFcn(varargin)
        if ~ishandle(Handles.handle)
            return
        end

        % double check if these axes are indeed the current axes
        if get(Handles.handle, 'currentaxes') ~= Handles.AxesPanel.mainAxes
            return
        end
        % return if there isn't a previous point
        if isempty(Gui.previous_point)
            return
        end
        % return if mouse hasn't been clicked
        if isempty(Gui.status)
            return
        end
        % get current location (in pixels)
        current_point = get(Handles.AxesPanel.mainAxes, 'CurrentPoint');

        switch lower(Gui.status)
            case 'down'
                % get current XY-limits
                xlim = get(Handles.AxesPanel.mainAxes, 'xlim');
                ylim = get(Handles.AxesPanel.mainAxes, 'ylim');

                % find change in position
                delta_points = current_point - Gui.previous_point;
                % adjust limits
                new_xlim = xlim - delta_points(1);
                new_ylim = ylim - delta_points(3);
                % set new limits
                set(Handles.AxesPanel.mainAxes, 'Xlim', new_xlim); set(Handles.AxesPanel.mainAxes, 'Ylim', new_ylim);
                % save new position
                Gui.previous_point = get(Handles.AxesPanel.mainAxes, 'CurrentPoint');
            otherwise % Window motion without anything going on
        end
    end

    function deselectEverything()
        for iBlock = 1:Layout.nBlocks
            set(Layout.Blocks(iBlock).handle,'Selected','off');
        end
    end

    function centerBlocks()
        if Layout.nBlocks == 0
            xLims = Options.initialCanvasLimits(1:2);
            yLims = Options.initialCanvasLimits(3:4);
            Options.blockFontSizeNormalized = Options.initialBlockFontSizeNormalized;

            xlim(xLims)
            ylim(yLims);

            return
        end

        xLims = [inf -inf];
        yLims = [inf -inf];
        for iBlock = 1:Layout.nBlocks
            maxPos = Layout.Blocks(iBlock).polygonPosition + blockSize;
            minPos = Layout.Blocks(iBlock).polygonPosition;
            xLims(1) = min(xLims(1),minPos(1));
            xLims(2) = max(xLims(2),maxPos(1));
            yLims(1) = min(yLims(1),minPos(2));
            yLims(2) = max(yLims(2),maxPos(2));
        end
        % Add some percentage to the edges
        edgePadPercent = 0.05;
        xLimsPadded = [-(xLims(2)-xLims(1)) (xLims(2)-xLims(1))]*edgePadPercent + xLims;
        yLimsPadded = [-(yLims(2)-yLims(1)) (yLims(2)-yLims(1))]*edgePadPercent + yLims;

        xRange = xLimsPadded(2)-xLimsPadded(1);
        yRange = yLimsPadded(2)-yLimsPadded(1);

        if xRange > yRange
            % We need to add some to the yRange to make equal aspect ratio
            xLims = xLimsPadded;
            yLims = yLimsPadded + (xRange-yRange)/2*[-1 1];
            yRange = xRange;
        else
            xLims = xLimsPadded + (yRange-xRange)/2*[-1 1];
            yLims = yLimsPadded;
            xRange = yRange;
        end

        zoomFactorFromOriginal = max(xRange,yRange) ./ (Options.initialCanvasLimits(2)-Options.initialCanvasLimits(1));
        textSizes = 1./zoomFactorFromOriginal.*originalTextSizes;
        resizeText();

        xlim(xLims)
        ylim(yLims);
    end

    function resizeText
        for iBlock = 1:Layout.nBlocks
            set(Layout.Blocks(iBlock).textHandle,'FontSize',textSizes(iBlock));
        end
    end

%     function blockInd = findBlockClick(cp)
%         blockInd = [];
%         for iBlock = 1:Layout.nBlocks
%             isInThisBlock = all(cp(1,1:2) < max(Layout.Blocks(iBlock).polygonNodes) & cp(1,1:2) > min(Layout.Blocks(iBlock).polygonNodes));
%             if isInThisBlock
%                 blockInd = iBlock;
%
%                 return
%             end
%         end
%     end

    function Options = localGetOptions()

        colors = prtPlotUtilClassColors(7);

        Options.initialCanvasLimits = [-0.1 1.1 -0.1 1.1];
        Options.blockSize = blockSize*[1 1];
        Options.initialBlockFontSizeNormalized = 0.03; %This is the default relative to the initialCanvas limits
        Options.blockFontSizeNormalized = Options.initialBlockFontSizeNormalized; %It can change as a function of the zoom level.

        Options.BlockColors.dataSet = [0.8 0.8 0.8];
        Options.BlockColors.preProcessor = colors(1,:);
        Options.BlockColors.featureSelector = colors(2,:);
        Options.BlockColors.classifier = colors(3,:);
        Options.BlockColors.decision = colors(4,:);
        Options.BlockColors.outlierRemoval = colors(5,:);
        Options.BlockColors.cluster = colors(6,:);
        Options.BlockColors.rv = colors(7,:);

        Options.BlockTextColors.dataSet = [0 0 0];
        Options.BlockTextColors.preProcessor = [1 1 1];
        Options.BlockTextColors.featureSelector = [1 1 1];
        Options.BlockTextColors.classifier = [1 1 1];
        Options.BlockTextColors.decision = [0 0 0];
        Options.BlockTextColors.rv = [0 0 0];
        Options.BlockTextColors.outlierRemoval = [1 1 1];
        Options.BlockTextColors.cluster = [1 1 1];

    end

    function Handles = localMakeFigure()

        ss = get(0,'screensize');

        windowSize = [754 600];

        % Center the window
        sizePads = round((ss(3:4)-windowSize));
        sizePads(1) = sizePads(1)/2; % We should use 2 right?
        sizePads(2) = sizePads(2)/2;
        pos = cat(2,sizePads,windowSize);

        % Create the figure an UIControls
        MainFigure.style = 'figure';
        MainFigure.units = 'pixels';
        MainFigure.position = pos;
        MainFigure.name = 'PRT Algorithm';
        MainFigure.NumberTitle = 'Off';
        MainFigure.Menu = 'none';
        MainFigure.toolbar = 'none';
        MainFigure.DockControls = 'off';

        MainFigure.Children.AxesPanel.style = 'panel';
        MainFigure.Children.AxesPanel.units = 'normalized';
        MainFigure.Children.AxesPanel.position = [0 0 1 1];
        MainFigure.Children.AxesPanel.Children.mainAxes.style = 'axes';
        MainFigure.Children.AxesPanel.Children.mainAxes.units = 'Normalized';
        MainFigure.Children.AxesPanel.Children.mainAxes.position = [0 0 1 1];
        MainFigure.Children.AxesPanel.Children.mainAxes.xtick = [];
        MainFigure.Children.AxesPanel.Children.mainAxes.ytick = [];
        MainFigure.Children.AxesPanel.Children.mainAxes.xlim = [0 1];
        MainFigure.Children.AxesPanel.Children.mainAxes.ylim = [0 1];
        MainFigure.Children.AxesPanel.Children.mainAxes.nextPlot = 'add';

        Handles = prtUtilSuicontrol(MainFigure);

        % define zooming with scrollwheel, and panning with mouseclicks
        set(Handles.handle, 'WindowScrollWheelFcn' , @mouseControlWindowScrollWheelZoomFcn,...
            'WindowButtonDownFcn'  , @mouseControlWindowButtonDownFcn,...
            'WindowButtonUpFcn'    , @mouseControlWindowButtonUpFcn,...
            'WindowButtonMotionFcn', @mouseControlWindowButtonMotionFcn);

    end
end


    camera = AxisWebCamera('http://wc2.dartmouth.edu');
    im = camera.grab();
    idisp(im)
    clear camera


function res = is_symbolic_lambda(x)
% internal: a symbolic lambda function is one which generates expressions when invoked with arguments (this is what exp_symbol generates)
res = isa(x,'function_handle') && ~isempty(regexp(char(x),'@\(varargin\)struct\(''head'',\{.*\},''parts'',\{varargin\}\)','once'));


function bands = extract_subbands(coeffs,pos)
pos=round(pos);
for i=1:length(pos)
    if i>1
        bands{i} = coeffs(pos(i):pos(i-1)-1);
    else
        bands{1} = coeffs(pos(1):end);
    end
end


%
% Set the default parameters for each model
%
function AMICO_SetModel( model )

    global CONFIG

    CONFIG.model   = [];

    CONFIG.OPTIMIZATION = [];
    CONFIG.OPTIMIZATION.LS_param    = optimset('TolX',1e-4);
    CONFIG.OPTIMIZATION.SPAMS_param = [];

    % Call the specific model constructor
    modelClass = str2func( ['AMICO_' upper(model)] );
    if exist([func2str(modelClass) '.m'],'file')
        CONFIG.model = modelClass();
    else
        error( '[AMICO_SetModel] Model "%s" not recognized', model )
    end

end


%SYMEXPR2SLBLOCK Create symbolic embedded MATLAB Function block
%
% symexpr2slblock(VARARGIN) creates an Embedded MATLAB Function block
% from a symbolic expression. The input arguments are just as used with
% the functions emlBlock or matlabFunctionBlock.
%
% Notes::
% - In Symbolic Toolbox versions prior to V5.7 (2011b) the function to
%   create Embedded Matlab Function blocks from symbolic expressions is
%   'emlBlock'.
% - Since V5.7 (2011b) there is another function named
%   'matlabFunctionBlock' which replaces the old function.
% - symexpr2slblock is a wrapper around both functions, which
%   checks for the installed Symbolic Toolbox version and calls the
%   required function accordingly.
%
% Authors::
%  Joern Malzahn, (joern.malzahn@tu-dortmund.de)
%
% See also emlBlock, matlabFunctionBlock.

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

function [] = symexpr2slblock(varargin)

% V5.8 (R2012a)
if verLessThan('symbolic','5.7')
    emlBlock(varargin{:});
elseif verLessThan('symbolic','5.11')
    matlabFunctionBlock(varargin{:});
else
    % Work around a bug in matlabFunctionBlock.m

    %% Read orignal file
	fid = fopen(which('matlabFunctionBlock.m'),'r');
    funStr = fscanf(fid, '%c',inf);
    fclose(fid);

    %% Create Temporary Workaround File
    tmpFName = fullfile(pwd,'tmp_workaround_matlabFunctionBlock.m');

    % perform necessary modifications
    funStr = strrep(funStr,...
        'if isa(b,''Stateflow.EMChart'')',...   Buggy expression to be replaced
        'if ~strcmp(class(b),''Stateflow.EMChart'')'); % Working expression from previous version.
    funStr = strrep(funStr,...
        'matlabFunctionBlock',...   Buggy expression to be replaced
        'tmp_workaround_matlabFunctionBlock'); % Working expression from previous version.

    % write modified function to temporary file
    fid = fopen(tmpFName, 'w');
    fprintf(fid,'%s\n','% This is a temporary file used as workaround for a bug in matlabFunctionBlock.m (R2013b).');
    fprintf(fid,'%s\n','% It should have been deleted automatically by the Robotics Toolbox. If not, feel free to do it manually.');
    fprintf(fid,'%s',funStr);
    fclose(fid);

    % update function database
    rehash

    % perform the actual block generation
    tmp_workaround_matlabFunctionBlock(varargin{:});

    % clean up
    delete(tmpFName);
end


function [EventsFUN,EventsValue]=sdeeventsfun(solver,tspan,y0,options)
%SDEEVENTSFUN  Process events function arguments for SDE solvers and functions.
%
%   See also:
%       SDEARGUMENTS, SDEARGUMENTS_PROCESS, SDEOUTPUTFUN, SDERANDFUN, SDEGET,
%       FUNCTION_HANDLE

%   Andrew D. Horchler, horchler @ gmail . com, Created 5-2-13
%   Revision: 1.2, 5-2-13

EventsFUN = sdeget(options,'EventsFUN',[],'flag');
if ~isempty(EventsFUN)
    if ~isa(EventsFUN,'function_handle')
        error('SDETools:sdeeventsfun:EventsFUNNotAFunctionHandle',...
              'EventsFUN, if specified, must be a function handle.  See %s.',...
              solver);
    end

    % Check output of EventsFUN at initial condition and save value
    try
        [EventsValue,isterminal,direction] = EventsFUN(tspan(1),y0);
    catch err
        switch err.identifier
            case 'MATLAB:TooManyInputs'
                error('SDETools:sdeeventsfun:EventsFUNTooFewInputs',...
                      'EventsFUN must have at least two inputs.  See %s.',...
                      solver);
            case 'MATLAB:TooManyOutputs'
                error('SDETools:sdeeventsfun:EventsFUNNoOutput',...
                     ['The output of EventsFUN was not specified. EventsFUN '...
                      'must return three non-empty vectors.  See %s.'],solver);
            case 'MATLAB:unassignedOutputs'
                error('SDETools:sdeeventsfun:EventsFUNUnassignedOutput',...
                     ['The first output of EventsFUN was not assigned.  '...
                      'See %s.'],solver);
            case 'MATLAB:minrhs'
                error('SDETools:sdeeventsfun:EventsFUNTooManyInputs',...
                     ['EventsFUN requires one or more input arguments '...
                      '(parameters) that were not supplied.  See %s.'],solver);
            otherwise
                rethrow(err);
        end
    end
    if ~isvector(EventsValue) || isempty(EventsValue)...
            || ~all(isfinite(EventsValue))
        error('SDETools:sdeeventsfun:InvalidEventsValue',...
             ['The first output of EventsFUN, ''Value'', must be a '...
              'non-empty finite vector.  See %s.'],solver);
    end
    if ~isvector(isterminal)...
            || ~any(length(isterminal) == [length(EventsValue) 1])
        error('SDETools:sdeeventsfun:EventsIsterminalDimensionMismatch',...
             ['The second output of EventsFUN, ''IsTerminal'', must be a '...
              'scalar or a vector the same length as the first output.  '...
              'See %s.'],solver);
    end
    if ~all(isterminal == 0 | isterminal == 1)
        error('SDETools:sdeeventsfun:InvalidEventsIsterminal',...
             ['The elements of the second output of EventsFUN, '...
              '''IsTerminal'', must be equal to 0 (false) or 1 (true).  '...
              'See %s.'],solver);
    end
    if ~isempty(direction)
        if ~isvector(direction)...
                || ~any(length(direction) == [length(EventsValue) 1])
            error('SDETools:sdeeventsfun:EventsDirectionDimensionMismatch',...
                 ['If the third output of EventsFUN, ''Direction'', is not '...
                  'specified as the empty matrix, [], it must be a scalar '...
                  'or a vector the same length as first output.  See %s.'],...
                  solver);
        end
        if ~all(direction == 0 | direction == 1 | direction == -1)
            error('SDETools:sdeeventsfun:InvalidEventsDirection',....
                 ['The third output of EventsFUN, ''Direction'', must be '...
                  'equal to 0 (default), 1, or -1.  See %s.'],solver);
        end
    end
else
    EventsValue = [];
end


function paramval = fl_inputparser(parseobj,paramname,default,validator)
% if default == [], then parameter is required

%% if no input return default
if isempty(parseobj)
    paramval = default;
    return
end

%% find parameter value
if isstruct(parseobj{1}) %if input is given as a structure

    if isfield(parseobj{1},paramname)
        paramval = parseobj{1}.(paramname);
    end

elseif iscell(parseobj) %if input is given as name/value pairs

    %check whether input is in pairs
    if mod(length(parseobj),2)
        error('Optional input parameters should come in name/value pairs.');
    end

    %separate parameters and values
    paramnames = parseobj(1:2:end);
    paramvalues = parseobj(2:2:end);

    %check if parameter name is invalid
    if isempty(paramname)
        for i = 1:length(paramnames)
            if ischar(paramnames{i}) & ~nnz(ismember(validator,paramnames{i})) %if parameter name not valid
                error(['Input ''' paramnames{i} ''' not valid.']);
            end
        end
        paramval = [];
        return
    end

    ndx = find(strcmp(paramnames,paramname));

    if length(ndx)>1
        error('Optional input parameters should only be defined once; multiple definitions detected.');
    end
    if ndx
        paramval = paramvalues{ndx};
    end

end

%% check if paramval was not defined
if ~exist('paramval')
    if ~exist('default') | isempty('default') %check if parameter is required
        error(['Input parameter '''  paramname ''' is required.']);
    else
        paramval = default;
    end
end

%% validate input
if exist('validator') %validator is optional
    if isa(validator,'function_handle') %if validator is a function handle
        if ~validator(paramval) %if not valid
            error(['Optional input parameter '''  paramname ''' has invalid value.']);
        end
    elseif iscell(validator) %directly provide cell; makes it easy to check strings

        paramval = validatestring(paramval,validator);

    end
end


clear;
%list of images
imglist=importdata('imglist.txt');
%minimum size of face
minsize=20;

%path of toolbox
caffe_path='E:/matlab';
pdollar_toolbox_path='E:/matlab/edges-master/toolbox-master'
caffe_model_path='./model'
addpath(genpath(caffe_path));
addpath(genpath(pdollar_toolbox_path));

%use cpu
%caffe.set_mode_cpu();
gpu_id=0;
caffe.set_mode_gpu();
caffe.set_device(gpu_id);

%three steps's threshold
threshold=[0.6 0.7 0.7]

%scale factor
factor=0.709;

%load caffe models
prototxt_dir =strcat(caffe_model_path,'/det1.prototxt');
model_dir = strcat(caffe_model_path,'/det1.caffemodel');
PNet=caffe.Net(prototxt_dir,model_dir,'test');
prototxt_dir = strcat(caffe_model_path,'/det2.prototxt');
model_dir = strcat(caffe_model_path,'/det2.caffemodel');
RNet=caffe.Net(prototxt_dir,model_dir,'test');
prototxt_dir = strcat(caffe_model_path,'/det3.prototxt');
model_dir = strcat(caffe_model_path,'/det3.caffemodel');
ONet=caffe.Net(prototxt_dir,model_dir,'test');
prototxt_dir =  strcat(caffe_model_path,'/det4.prototxt');
model_dir =  strcat(caffe_model_path,'/det4.caffemodel');
LNet=caffe.Net(prototxt_dir,model_dir,'test');
faces=cell(0);
for i=1:length(imglist)
    i
	img=imread(imglist{i});
	%we recommend you to set minsize as x * short side
	%minl=min([size(img,1) size(img,2)]);
	%minsize=fix(minl*0.1)
    tic
    [boudingboxes points]=detect_face(img,minsize,PNet,RNet,ONet,LNet,threshold,false,factor);
	toc
    faces{i,1}={boudingboxes};
	faces{i,2}={points'};
	%show detection result
	numbox=size(boudingboxes,1);
	imshow(img)
	hold on;
	for j=1:numbox
		plot(points(1:5,j),points(6:10,j),'g.','MarkerSize',10);
		r=rectangle('Position',[boudingboxes(j,1:2) boudingboxes(j,3:4)-boudingboxes(j,1:2)],'Edgecolor','g','LineWidth',3);
    end
    hold off;
	pause
end
save result box landmark


function plot_recall_ILSVRC2013()
  % Plot the recall of ILSVRC2013 validation set ground truth for all methods.
  %
  % This function requires the proposals to already be saved to disk. It will
  % compute a matching between ground truth and proposals (if the result is not
  % yet found on disk) and then plot recall curves. The plots are saved to
  % figures/.

  val = load('data/ILSVRC2013_val_annotations.mat');
  methods = get_method_configs();
  methods([14 16 19:24]) = [];

  valid_methods = compute_best_candidates(val, methods);
  methods = methods(valid_methods);

  plot_legend(methods);
  printpdf('figures/imagenet_recall_legend.pdf');

  fprintf('\n\n');
  for i = 1:numel(methods)
    fprintf('Valid data for plots: %s\n', methods(i).name);
  end

  fh = figure;
  plot_overlap_recall_curve({methods.best_imagenet_candidates_file}, methods, 100, fh, true, 'none');
  hei = 10;
  wid = 10;
  set(gcf, 'Units','centimeters', 'Position',[0 0 wid hei]);
  set(gcf, 'PaperPositionMode','auto');
  printpdf('figures/imagenet_recall_100.pdf')

  fh = figure;
  plot_overlap_recall_curve({methods.best_imagenet_candidates_file}, methods, 1000, fh, false, 'none');
  hei = 10;
  wid = 10;
  set(gcf, 'Units','centimeters', 'Position',[0 0 wid hei]);
  set(gcf, 'PaperPositionMode','auto');
  printpdf('figures/imagenet_recall_1000.pdf')

  fh = figure;
  plot_overlap_recall_curve({methods.best_imagenet_candidates_file}, methods, 10000, fh, false, 'none');
  hei = 10;
  wid = 10;
  set(gcf, 'Units','centimeters', 'Position',[0 0 wid hei]);
  set(gcf, 'PaperPositionMode','auto');
  printpdf('figures/imagenet_recall_10000.pdf')

  plot_num_candidates_auc({methods.best_imagenet_candidates_file}, methods, 'imagenet_');
end

function valid_methods = compute_best_candidates(testset, methods)
  num_annotations = numel(testset.pos);
  candidates_thresholds = round(10 .^ (0:0.5:4));
  num_candidates_thresholds = numel(candidates_thresholds);

  valid_methods = false(numel(methods), 1);
  for method_idx = 1:numel(methods)
    method = methods(method_idx);
    fprintf('%s\n', method.name);
    try
      load(method.best_imagenet_candidates_file, 'best_candidates');
      valid_methods(method_idx) = true;
      continue;
    catch
    end

    % preallocate
    best_candidates = [];
    best_candidates(num_candidates_thresholds).candidates_threshold = [];
    best_candidates(num_candidates_thresholds).best_candidates = [];
    for i = 1:num_candidates_thresholds
      best_candidates(i).candidates_threshold = candidates_thresholds(i);
      best_candidates(i).best_candidates.candidates = zeros(num_annotations, 4);
      best_candidates(i).best_candidates.iou = zeros(num_annotations, 1);
      best_candidates(i).image_statistics(numel(testset.impos)).num_candidates = 0;
    end

    files_missing = false;
    pos_range_start = 1;
    for j = 1:numel(testset.impos)
      tic_toc_print('evalutating %s: %d/%d\n', method.name, j, numel(testset.impos));
      pos_range_end = pos_range_start + size(testset.impos(j).boxes, 1) - 1;
      assert(pos_range_end <= num_annotations);

%       tic_toc_print('sampling candidates for image %d/%d\n', j, numel(testset.impos));
      [~,img_id,~] = fileparts(testset.impos(j).im);

      for i = 1:num_candidates_thresholds
        try
          [candidates, scores] = get_candidates(method, img_id, ...
            candidates_thresholds(i));
        catch
          fprintf('%s candidates for %s missing\n', method.name, img_id);
          files_missing = true;
          break;
        end
        if isempty(candidates)
          impos_best_ious = zeros(size(testset.impos(j).boxes,1),1);
          impos_best_boxes = zeros(size(testset.impos(j).boxes,1),4);
        else
          [impos_best_ious, impos_best_boxes] = closest_candidates(...
            testset.impos(j).boxes, candidates);
        end

        best_candidates(i).best_candidates.candidates(pos_range_start:pos_range_end,:) = impos_best_boxes;
        best_candidates(i).best_candidates.iou(pos_range_start:pos_range_end) = impos_best_ious;
        best_candidates(i).image_statistics(j).num_candidates = size(candidates, 1);
      end
      if files_missing
        break;
      end

      pos_range_start = pos_range_end + 1;
    end
    valid_methods(method_idx) = ~files_missing;
    if ~files_missing
      save(method.best_imagenet_candidates_file, 'best_candidates');
    end

  end
end


function finetuned_model_path = train_net_bbox_reg(...
    image_db_train, image_db_test, pooler, opts)

data_param                  = opts.data_param;
solver_param.test_iter      = data_param.test_iter;
solver_param.test_interval  = data_param.test_interval;
solver_param.max_iter       = opts.max_iter;

% ------------------------------------------------

work_dir  = opts.finetune_rst_dir;
timestamp = datestr(datevec(now()), 'yyyymmdd_HHMMSS');
log_file  = fullfile(work_dir, 'output', ['mrcnn_regression_' timestamp, '.txt']);

mkdir_if_missing(work_dir);
mkdir_if_missing(fileparts(log_file));
diary(log_file);

fprintf('data_param:\n');
disp(data_param);
fprintf('solver_param:\n');
disp(solver_param);
fprintf('opts_param:\n');
disp(opts);
fprintf('pooler:\n');
disp(pooler);

data.feature_paths_train = image_db_train.feature_paths;
data.feature_paths_test  = image_db_test.feature_paths;

[data.fg_windows_train] = setup_data(image_db_train, data_param);
[data.fg_windows_test]  = setup_data(image_db_test,  data_param);

% data.fg_windows_train = setup_data(image_db_train, data_param);
% data.fg_windows_test  = setup_data(image_db_test,  data_param);

data.fg_windows_num_train = cell2mat(cellfun(@(x) size(x, 1), data.fg_windows_train, 'UniformOutput', false));
data.fg_windows_num_test  = cell2mat(cellfun(@(x) size(x, 1), data.fg_windows_test,  'UniformOutput', false));

fprintf('total fg_windows_num_train : %d\n', sum(data.fg_windows_num_train));
fprintf('total fg_windows_num_test  : %d\n', sum(data.fg_windows_num_test));

model.pooler = pooler;

current_dir = pwd;
cd(fileparts(opts.finetune_net_def_file))
% init caffe solver
[solver, iter_] = InitializeSolver(opts);

fprintf('Starting iteration %d\n', iter_);

% fix the random seed for repeatability
data_param_test                = data_param;
data_param_test.iter_per_batch = solver_param.test_iter;
data_param_test.random_scale   = 0; % for test use the best scale
data_param_test.nTimesMoreData = 10;

data.save_data_test_file = fullfile(work_dir, ...
    sprintf('data_test_file_batch%d_test_iter%d.mat', ...
    data_param_test.img_num_per_iter, data_param_test.iter_per_batch));

%% finetune
prev_rng = seed_rand();
mean_test_error = TestNet(solver, model, data_param_test, solver_param, data);

%min_test_error = TestNet(model, data_param_test, solver_param, data);

if isfield(opts, 'test_only') && opts.test_only
    finetuned_model_path = '';
    return;
end
last_finetuned_model_prev_path = '';

rng(prev_rng);
while(iter_ < solver_param.max_iter)
    mean_train_error = TrainNet(solver, model, data_param,      solver_param, data);
    mean_test_error  = TestNet( solver, model, data_param_test, solver_param, data);
    iter_            = solver.iter();
    ShowState(iter_, mean_train_error, mean_test_error);

    last_finetuned_model_path = fullfile(opts.finetune_rst_dir, sprintf('%s_iter_%d.caffemodel', opts.snapshot_prefix, iter_));
    assert(exist(last_finetuned_model_path,'file')>0);
    fprintf('last save as %s\n', last_finetuned_model_path);

    delete_prev_model_if_exist(last_finetuned_model_prev_path);
    last_finetuned_model_prev_path = last_finetuned_model_path;

    diary; diary; % flush diary
end
cd(current_dir);
finetuned_model_path = last_finetuned_model_path;
fprintf('Optimization is finished\n');
end

function [solver, iter_] = InitializeSolver(opts)
solver = caffe.Solver(opts.finetune_net_def_file);
iter_   = 1;

if isfield(opts,'solver_state_file') && ~isempty(opts.solver_state_file)
    solver.restore(opts.solver_state_file);
    [directory,filename] = fileparts(opts.solver_state_file);
    model_file = [directory,filesep, filename, '.caffemodel'];
    solver.net.copy_from(model_file);
    solver.test_nets(1).copy_from(model_file);
    iter_   = solver.iter();
    rng('shuffle')
else
    solver.net.copy_from(opts.net_file);
    solver.test_nets(1).copy_from(opts.net_file);
end
end

function ShowState(iter, train_error, test_error)
fprintf('\n------------------------- Iteration %d -------------------------\n', iter);
fprintf('Error - Training : %.4f - Testing : %.4f\n', train_error, test_error);
end

function delete_prev_model_if_exist(finetuned_model_prev_path)
if exist(finetuned_model_prev_path, 'file')
	delete(finetuned_model_prev_path);
end
[directory,filename] = fileparts(finetuned_model_prev_path);
finetuned_model_prev_path_solver_state = [directory,filesep, filename,'.solverstate'];
if exist(finetuned_model_prev_path_solver_state, 'file')
    delete(finetuned_model_prev_path_solver_state);
end
end

function fg_windows = setup_data(image_db, data_param)

image_paths = image_db.image_paths;
all_regions = image_db.all_regions;
all_bbox_gt = image_db.all_bbox_gt;

num_elems  = length(image_paths);
fg_windows = cell(num_elems, 1);

num_classes  = data_param.num_classes;
fg_threshold = data_param.fg_threshold;

for i = 1:num_elems
    fg_windows_this = setup_img_data(image_paths{i}, all_bbox_gt{i}, all_regions{i}, num_classes, fg_threshold);
    fg_windows{i}   = [ones(size(fg_windows_this,1),1,'single')*i, fg_windows_this];
end

end

function bbox_out = setup_img_data(image_file, bbox_gt, bbox_proposals, num_classes, low_ov_threshold)

bbox_gt_label = bbox_gt(:,5);

bbox_all    = [bbox_gt(:,1:4); bbox_proposals(:,1:4)];
overlap     = zeros(size(bbox_all,1), num_classes,     'single');
bbox_target = zeros(size(bbox_all,1), num_classes * 4, 'single');

for class_idx = 1:num_classes
    if any(bbox_gt_label == class_idx)
        bbox_gt_class = bbox_gt(bbox_gt_label == class_idx,1:4);
        overlap_class = boxoverlap(bbox_all(:,1:4), bbox_gt_class(:,1:4));
        [overlap(:, class_idx), gt_index] = max(overlap_class, [], 2);
        does_overlap = overlap(:, class_idx) > 0.001;
        bbox_target(does_overlap, (class_idx-1)*4 + (1:4)) = bbox_gt_class(gt_index(does_overlap), 1:4);
    end
end

[max_overlap, label] = max(overlap, [], 2);
keep = max_overlap >= low_ov_threshold;

bbox_all         = bbox_all(   keep,:);
overlap          = overlap(    keep,:);
bbox_target      = bbox_target(keep,:);
bbox_target_vals = encode_bbox_targets_to_reg_vals(bbox_all, bbox_target);

max_overlap      = max_overlap(keep);
label            = label(keep);

bbox_out         = single([label, max_overlap, bbox_all, overlap, bbox_target_vals, bbox_target]);
end

function [data_test, label_test, windows_test] = create_test_batch(model, data_param_test, data )
try
    ld = load(data.save_data_test_file);
    data_test    = ld.data_test;
    label_test   = ld.label_test;
    windows_test = ld.windows_test;
catch
    windows_test = sample_window_data(data.fg_windows_test, data.fg_windows_num_test, data_param_test);
    data_test    = prepare_batch_feat_input_data(model.pooler, data.feature_paths_test, windows_test, data_param_test);
    label_test   = prepare_batch_target_data(windows_test, data_param_test);

    save(data.save_data_test_file, 'data_test', 'label_test', 'windows_test', '-v7.3');
end
end

function [windows, select_img_idx] = sample_window_data(fg_windows, fg_windows_num, data_param)

nTimesMoreData = 10;

if isfield(data_param,'nTimesMoreData')
    nTimesMoreData = data_param.nTimesMoreData;
end

fg_num_total = data_param.img_num_per_iter * data_param.iter_per_batch;
num_img      = length(fg_windows);

% random perm image and get top n image with total more than patch_num
% windows
img_idx           = randperm(num_img);
fg_windows_num    = fg_windows_num(img_idx);
fg_windows_cumsum = cumsum(fg_windows_num);
img_idx_end       = find(fg_windows_cumsum > (fg_num_total * nTimesMoreData), 1, 'first');

if isempty( img_idx_end ), img_idx_end = length(img_idx); end

select_img_idx = img_idx(1:img_idx_end);
fg_windows_sel = cell2mat(fg_windows(select_img_idx));

% random perm all windows, and drop redundant data
window_idx     = randperm(size(fg_windows_sel, 1));
fg_indices     = mod((1:fg_num_total) - 1, length(window_idx)) + 1;
fg_windows_sel = fg_windows_sel(window_idx(fg_indices), :);

random_seed = randi(10^6, length(select_img_idx),1);
for i = 1:length(select_img_idx), rng(random_seed(i), 'twister'); end

windows     = fg_windows_sel(randperm(size(fg_windows_sel, 1)),:);
end

function label = prepare_batch_target_data(windows, data_param)
num_classes  = data_param.num_classes;
num_reg_vals = num_classes * 4;
fg_threshold = data_param.fg_threshold;
mask         = windows(:,7+(1:num_classes)) >= fg_threshold;
mask         = repmat( mask, [4, 1]);
mask         = reshape(mask, [size(windows,1), num_classes*4]);
label        = [single(mask), windows(:,(7+num_classes)+(1:num_reg_vals))]';
end

function [mean_ave_recall, mean_test_error] = TestNet(solver, model, data_param, solver_param, data)
fprintf('Testing: ');
th = tic;

[data_test, label_test, windows_test] = create_test_batch(model, data_param, data );

assert(isnumeric(data_test));
assert(isnumeric(label_test));
assert(size(data_test,2)    == solver_param.test_iter * data_param.img_num_per_iter);
assert(size(data_test,1)    == data_param.feat_dim);
assert(size(label_test,2)   == solver_param.test_iter * data_param.img_num_per_iter);
assert(size(windows_test,1) == solver_param.test_iter * data_param.img_num_per_iter);

% windows_test = [fg_img_idx, fg_label, fg_ov, fg_window, fg_overlap_all, fg_bbox_target_vals, fg_bbox_target];
mini_batch_size = data_param.img_num_per_iter;

fprintf('elapsed time %.2f + ', toc(th));
th = tic;
test_error = nan(solver_param.test_iter, 1);
test_pred  = cell(solver_param.test_iter,1);
% fprintf('Testing: ');
for it = 1:solver_param.test_iter
    start_idx = (it-1) * mini_batch_size + 1;
    stop_idx  = it * mini_batch_size;
    assert(stop_idx <= size(data_test,2));

    mini_batch = prepare_mini_batch(data_test( :,start_idx:stop_idx), ...
                               label_test(:,start_idx:stop_idx), data_param);
    caffe_set_input(solver.test_nets(1), mini_batch);
    forward_prefilled(solver.test_nets(1));

    test_error(it) = solver.test_nets(1).blobs('error').get_data();
    test_pred{it}  = solver.test_nets(1).blobs('predictions').get_data();
end

mean_test_error = mean(test_error);

num_classes      = data_param.num_classes;
bbox_init        = windows_test(:,4:7);
all_bbox_pred    = repmat(bbox_init, [1, num_classes]);
all_bbox_targets = windows_test(:, (7+num_classes+num_classes*4) + (1:(num_classes*4)));
all_mask         = label_test(1:(num_classes*4),:)';

fprintf('Initial Candidate Boxes');
mean_ave_recall_init  = compute_mAR_of_bbox_reg(bbox_init, all_bbox_pred, all_bbox_targets, all_mask, num_classes);

all_reg_values  = cell2mat(test_pred(:)')';
all_bbox_pred   = decode_reg_vals_to_bbox_targets(bbox_init, all_reg_values);
fprintf('After Regression Candidate Boxes');
mean_ave_recall = compute_mAR_of_bbox_reg(bbox_init, all_bbox_pred, all_bbox_targets, all_mask, num_classes);
fprintf('%.2f sec --- error %.4f mAR %.5f --> %.5f\n', toc(th), mean_test_error, mean_ave_recall_init, mean_ave_recall);
mean_ave_recall = 1 - mean_ave_recall;

end

function mean_train_error = TrainNet(solver, model, data_param, solver_param, data)

test_interval   = solver_param.test_interval;
num_batches     = data_param.iter_per_batch;
num_epochs      = test_interval / num_batches;
mini_batch_size = data_param.img_num_per_iter;

train_count = 0;
train_error_sum = 0;
for epoch = 1:num_epochs
    fprintf('Training: ');
    th = tic;

    windows_train = sample_window_data(data.fg_windows_train,   data.fg_windows_num_train, data_param);
    data_train    = prepare_batch_feat_input_data(model.pooler, data.feature_paths_train, windows_train, data_param);
    label_train   = prepare_batch_target_data(windows_train, data_param);

    assert(isnumeric(data_train));
    assert(isnumeric(label_train));
    assert(size(data_train,2)  == num_batches * data_param.img_num_per_iter);
    assert(size(data_train,1)  == data_param.feat_dim);
    assert(size(label_train,2) == num_batches * data_param.img_num_per_iter);

    fprintf('elapsed time %.2f + ', toc(th));

    th = tic;
    train_error_sum_this = 0;
    for it = 1:num_batches
        start_idx = (it-1) * mini_batch_size + 1;
        stop_idx  = it * mini_batch_size;
        assert(stop_idx <= size(data_train,2));
        caffe_set_input(solver.net, ...
            prepare_mini_batch(data_train( :,start_idx:stop_idx), ...
                               label_train(:,start_idx:stop_idx), data_param));
        solver.step(1);

        results = caffe_get_output(solver.net);

        train_error_sum_this = train_error_sum_this + results{1}(1);
    end
    train_error_sum = train_error_sum + train_error_sum_this;
    train_count = train_count + num_batches;
    data_train = []; label_train = [];
    fprintf('%.2f sec --- error[total] %.4f - error[this] %.4f\n', toc(th), train_error_sum/train_count, train_error_sum_this/num_batches);
end
mean_train_error = train_error_sum/train_count;
end

function mini_batch = prepare_mini_batch(data, labels, data_param)
data   = prepare_data(    data, data_param);
labels = prepare_labels(labels, data_param);
mini_batch = [data, labels];
end

function labels = prepare_labels(labels, data_param)

num_samples = size(labels,2);

if ~isfield(data_param, 'labels_split_div')
    labels_split_div = size(labels,1);
else
    labels_split_div = data_param.labels_split_div;
    assert(sum(labels_split_div) == size(labels,1));
end

labels = mat2cell(labels, labels_split_div, size(labels,2))';

if isfield(data_param, 'reshape_labels')
    reshape_labels = data_param.reshape_labels;
    num_labels_types = length(labels);
    assert(length(labels) == length(reshape_labels));
    for i = 1:num_labels_types
        assert(length(reshape_labels{i}) == 4 || length(reshape_labels{i}) == 3);
        if length(reshape_labels{i}) == 3
            reshape_labels{i}(4) = num_samples;
        end
        labels{i} = reshape(labels{i}, reshape_labels{i});
    end
end

end

function data = prepare_data(data, data_param)
num_samples = size(data,2);

if ~isfield(data_param, 'data_split_div')
    data_split_div = size(data,1);
else
    data_split_div = data_param.data_split_div;
    assert(sum(data_split_div) == size(data,1));
end

data = mat2cell(data, data_split_div, size(data,2))';

if isfield(data_param, 'reshape_data')
    reshape_data = data_param.reshape_data;
    num_data_types = length(data);
    assert(length(data) == length(reshape_data));
    for i = 1:num_data_types
        assert(length(reshape_data{i}) == 4 || length(reshape_data{i}) == 3);
        if length(reshape_data{i}) == 3
            reshape_data{i}(4) = num_samples;
        end
        data{i} = reshape(data{i}, reshape_data{i});
    end
end
end

function [mAR, AR] = compute_mAR_of_bbox_reg(bbox_init, all_bbox_pred, all_bbox_targets, all_mask, num_classes)

AR = zeros(1,num_classes);
fprintf('\nAveRecall: ')
for c = 1:num_classes
    bbox_pred    = all_bbox_pred(:,    (c-1)*4 + (1:4));
    bbox_targets = all_bbox_targets(:, (c-1)*4 + (1:4));
    mask         = all_mask(:,         (c-1)*4 + (1:4));
    is_not_gt    = ~all(bbox_init == bbox_targets, 2);
    bbox_pred    = bbox_pred(   is_not_gt, :);
    bbox_targets = bbox_targets(is_not_gt, :);
    mask         = mask(        is_not_gt, :);
    mask         = all(mask,2);
    bbox_pred    = bbox_pred(   mask,:);
    bbox_targets = bbox_targets(mask,:);
    AR(c)        = compute_ave_recall_of_bbox( bbox_pred, bbox_targets );
    fprintf('[%d]=%.2f ',c, AR(c));
end

fprintf('\n')
mAR = mean(AR);

end


function model = mapMetsToAltModel(refModel,mapModel,metFields,mapMethod)
%mapMetsToAltModel  Map metabolites from one model to another.
%
% USAGE:
%
%   model = mapMetsToAltModel(refModel,mapModel,metFields,mapMethod);
%
%
% INPUTS:
%
%   refModel   Model structure containing metabolites that are to be mapped
%              to mapModel metabolite identifiers.
%
%   mapModel   Model structure containing metabolites to which refModel
%              will be mapped.
%
%   metFields  One or more metabolite-related fields that will be compared
%              between the two models to map metabolites from one model to
%              the other. If "metNames" is included in metFields,
%              metabolite names will be processed in three steps:
%               1) Map refModel metNames to mapModel metNames and
%                  metNamesAlt
%               2) Map refModel metNames and metNamesAlt to mapModel
%                  metNames and metNamesAlt
%               3) Make specific modifications to met names based on known
%                  differences (unique to situations where Recon3D and HMR
%                  are the input models)
%
%   mapMethod  (Optional) Specify the method used to handle metabolites in
%              refModel that map to multiple IDs in mapModel across
%              multiple metFields.
%                 'all'  (Default) For each metabolite, met IDs will be
%                        returned for all mets in mapModel that were mapped
%                        along any of the specified metFields.
%               'order'  The order in which metFields are listed will
%                        determine their priority in mapping met IDs (where
%                        first is most important/confident, and last is the
%                        least). Once a met is mapped via one of the fields
%                        listed in metFields, there will be no further
%                        attempts to map that metabolite via all subsequent
%                        fields in metFields.
%               'score'  Each met will be mapped along all fields listed in
%                        metFields. The mapped met ID(s) with the highest
%                        score for each metabolite will be kept, whereas
%                        all other mapped IDs with lower scores will be
%                        removed as potential matches. The score is
%                        calculated based on the number of fields in
%                        metFields by which the refModel met is mapped to
%                        the mapModel met. If metFields includes a second
%                        column of field weights, then the score will be
%                        weighted by these values, where mets mapped along
%                        fields with greater weights will receive higher
%                        scores than those mapped along fields with lower
%                        weights.
%
% OUTPUTS:
%
%   model     Model structure with added field "metAltModelID", which
%             contains the mapModel metIDs that were matched to the
%             refModel metabolites.
%

% handle input arguments
if nargin < 4
    mapMethod = 'all';
end

if ischar(metFields)
    % convert char to cell
    metFields = {metFields};
    metFieldWeights = 1;  % weight is irrelevant if only one metField is provided
elseif isvector(metFields)
    % ensure that metFields is a column vector (probably unnecessary)
    metFields = metFields(:);
    metFieldWeights = ones(size(metFields));  % weight all metFields equally
else
    % if metFields contains a second column, it is treated as the vector of
    % weights corresponding to the list of met fields, and is extracted
    metFieldWeights = cell2mat(metFields(:,2));
    metFields = metFields(:,1);
end

if strcmpi(mapMethod,'order')
    % if mapMethod is "order", then weight fields based on their order of appearance in metFields
    metFieldWeights = (length(metFields):-1:1)';
end

% verify that all metFields are present in both models
if any(~isfield(refModel,metFields)) || any(~isfield(mapModel,metFields))
    error('One or more of the specified metFields is not present in one or both models.');
end

% remove metNamesAlt if present in metFields - this field is automatically
% included when metNames is specified
if ismember(metFields,'metNamesAlt')
    fprintf('Note: the "metNamesAlt" entry is ignored, because it is included in the "metNames" processing step.\n');
    metFields(ismember(metFields,'metNamesAlt')) = [];
    if ~ismember('metNames',metFields)
        % warn user if they include metNamesAlt, but not metNames, in metFields
        fprintf('WARNING: comparison by "metNames" has not been specified - mets will not be compared by names.\n');
    end
end

% Add empty metNamesAlt field to either of the models if they are missing
% the field. This just makes things easier later on.
if ~isfield(refModel,'metNamesAlt')
    refModel.metNamesAlt = repmat({''},size(refModel.mets));
end
if ~isfield(mapModel,'metNamesAlt')
    mapModel.metNamesAlt = repmat({''},size(mapModel.mets));
end

% initialize output model
model = refModel;

% strip compartments from mapModel met IDs to obtain compartment-free
% met-to-name (or met-to-ID) pairs
if strcmp(mapModel.mets{1}(end),']')
    % compartment information is formatted in brackets at end of ID
    map_mets = regexprep(mapModel.mets,'\[.\]$','');
elseif length(unique(mapModel.mets)) == length(mapModel.mets)
    % If the met IDs are not all unique, assume the compartment has already
    % been removed; otherwise, assume that the last character of the ID is
    % the compartment abbreviation, and remove it.
    map_mets = regexprep(mapModel.mets,'.$','');
end

% obtain unique list of compartment-free met IDs, and only keep field
% entries associated with this unique set of metabolites
[~,uniq_met_ind] = unique(map_mets);
map_mets = map_mets(uniq_met_ind);
mapModel.metNamesAlt = mapModel.metNamesAlt(uniq_met_ind,:);
for i = 1:length(metFields)
    % also remove rows correponding to non-unique mets from metFields
    mapModel.(metFields{i}) = mapModel.(metFields{i})(uniq_met_ind,:);
end

% initialize variables
matchScores = [];
name_stage = 1;
f = 1;

% map metabolites along each field in metFields
while f <= length(metFields)

    fprintf('Mapping metabolites via %s... ',metFields{f});

    % extract IDs from model field
    switch metFields{f}

        case 'mets'

            % If comparing the "mets" field, remove the compartment
            if strcmp(refModel.mets{1}(end),']')
                ref_mets = regexprep(refModel.mets,'\[.\]$','');
            elseif length(unique(refModel.mets)) == length(refModel.mets)
                ref_mets = regexprep(refModel.mets,'.$','');
            end
            ref_ids = lower(ref_mets);
            map_ids = lower(map_mets);
            is_name = false;
            ids = repmat({''},size(refModel.mets));  % initialize matches

        case 'metNames'

            if name_stage == 1

                % METNAMES STAGE 1: Map mets via metNames field
                fprintf('(stage 1) ');
                is_name = true;
                ref_ids = refModel.metNames;
                map_ids = [mapModel.metNames,mapModel.metNamesAlt];
                ids = repmat({''},size(refModel.mets));  % initialize matches

            elseif name_stage == 2

                % METNAMES STAGE 2: Map remaining mets via metNamesAlt field
                fprintf('(stage 2) ');
                is_name = true;
                ref_ids = [refModel.metNames,refModel.metNamesAlt];
                map_ids = [mapModel.metNames,mapModel.metNamesAlt];

            elseif name_stage == 3

                % METNAMES STAGE 3: Make manual changes to metNames, and map.
                % These changes are specific to the mapping between HMR2 and
                % Recon3D, and are based on observations in naming differences.
                fprintf('(stage 3) ');
                is_name = true;
                ref_ids = [refModel.metNames,refModel.metNamesAlt];
                map_ids = [mapModel.metNames,mapModel.metNamesAlt];

                % make name adjustments to Recon3D model if present
                if isfield(refModel,'modelID') && strcmpi(refModel.modelID,'Recon3D')
                    ref_ids = applyMetNameChanges(ref_ids);
                elseif isfield(mapModel,'modelID') && strcmpi(mapModel.modelID,'Recon3D')
                    map_ids = applyMetNameChanges(map_ids);
                end

            end

        otherwise

            % comparing by field other than "mets" or "metNames"
            is_name = false;  % not a met name
            ref_ids = refModel.(metFields{f});
            map_ids = mapModel.(metFields{f});
            ids = repmat({''},size(refModel.mets));  % initialize matches

    end

    % if map_ids contains multiple columns, flatten into a single
    % column, and repeat entries of mapModel mets to maintain alignment
    non_empty = ~cellfun(@isempty,map_ids);
    if size(map_ids,2) > 1
        repMets = arrayfun(@(i) repmat(map_mets(i),sum(non_empty(i,:),2),1),(1:length(map_mets))','UniformOutput',false);
        repMets = vertcat(repMets{:});
        map_ids = map_ids';
        map_ids = map_ids(non_empty');
    else
        map_ids = map_ids(non_empty);
        repMets = map_mets(non_empty);
    end

    % assemble mapping array
    met2id = [repMets,map_ids];

    % map metabolites
    ignore_mets = ~cellfun(@isempty,ids);  % for the name-matching process, ignore mets that have been mapped in previous name-matching stages
    ids(~ignore_mets) = matchIDs(ref_ids(~ignore_mets,:),met2id,is_name);
    fprintf('Done.\n');

    if strcmpi(metFields{f},'metNames') && name_stage < 3
        % if processing metNames, move to next stage
        name_stage = name_stage + 1;
    else

        % get indices of mapped mets in each of the respective models
        ref_met_inds = arrayfun(@(i) repmat(i,numel(ids{i}),1),(1:length(ids))','UniformOutput',false);
        ref_met_inds = vertcat(ref_met_inds{:});
        [~,map_met_inds] = ismember(vertcat(ids{:}),map_mets);

        % append matches to match score matrix
        matchScores(:,:,f) = accumarray(unique([ref_met_inds,map_met_inds],'rows'),metFieldWeights(f),[length(refModel.mets),length(map_mets)]);
        f = f + 1;  % proceed to next metField

    end

end

if strcmpi(mapMethod,'order')
    % only use the match from the top-scoring (earliest-listed) metField
    matchScores(matchScores < max(matchScores,[],3)) = 0;
end

% determine the total score for each match by adding scores among all fields
matchScores = sum(matchScores,3);

if ~strcmpi(mapMethod,'all')
    % For each metabolite, only keep the mapped IDs with the highest score.
    % If there is a tie, keep all IDs that are tied.
    matchScores(matchScores < max(matchScores,[],2)) = 0;
end

% retrieve mapModel met IDs and add to output model structure
metAltModelID = arrayfun(@(i) map_mets(matchScores(i,:) > 0),(1:length(refModel.mets))','UniformOutput',false);
model.metAltModelID = flattenCell(metAltModelID,true);

end

function chgNames = applyMetNameChanges(names)

% met name changes specific to Recon3D-HMR2 metabolite mapping
chgNames = regexprep(names,'Coenzyme A','CoA');
chgNames = regexprep(chgNames,'\(.*-Density Lipoprotein\)','');
chgNames = regexprep(chgNames,'13-Cis-Retinoyl Glucuronide','13-Cis-Retinoyl-beta-D-Glucuronide');
chgNames = regexprep(chgNames,"Cytidine-5'-Diphosphate",'CDP');
chgNames = regexprep(chgNames,'Human Liver Homolog','');
chgNames = regexprep(chgNames,'Glutathionyl-Leuc4','glutathionyl-leukotriene C4');
chgNames = regexprep(chgNames,'Omega-Cooh-Tetranor-Leukotriene E3','Omega-Cooh-Tetranor-LTE3');

end

function metID = matchIDs(ids,met2id,is_name)

if is_name
    % make all met names lowercase, and remove special characters (non-word or
    % digit characters, e.g., dashes, parentheses, spaces, etc.)
    met2id(:,2) = lower(regexprep(met2id(:,2),'[^a-zA-Z0-9]',''));
    ids = lower(regexprep(ids,'[^a-zA-Z0-9]',''));
end

% compress refModel met IDs into single column of nested entries
if size(ids,2) > 1
    ids = nestCell(ids,true);
end

% find empty met ID indices to ignore
ignore_ind = cellfun(@isempty,ids);

% map refModel met IDs to mapModel met IDs
metID = repmat({''},size(ids,1),1);
metID(~ignore_ind) = cellfun(@(x) unique(met2id(ismember(met2id(:,2),x),1)),ids(~ignore_ind),'UniformOutput',false);

end