function [radius,subsettrunc,outstate] = ncorr_gui_setstrainradius(reference,current,rois_ref,rois_cur,plots_u_ref,plots_v_ref,plots_u_cur,plots_v_cur,spacing,pixtounits,units,pos_parent,params_init)
% This is a GUI for setting the strain radius.
%
% Inputs -----------------------------------------------------------------%
%   reference - ncorr_class_img; used for displaying the background image.
%   current - ncorr_class_img; used for displaying the background image.
%   rois_ref - ncorr_class_roi; contains ROIs WRT the reference image. Note
%   that rois_ref has already been reduced.
%   rois_cur - ncorr_class_roi; contains ROIs WRT the current image. Note
%   that rois_ref has already been reduced.
%   plots_u_ref - cell; contains displacement data WRT the reference image.
%   Uses real units. Note that displacement plots are reduced by default.
%   plots_v_ref - cell; contains displacement data WRT the reference image.
%   Uses real units. Note that displacement plots are reduced bym default.
%   plots_u_cur - cell; contains displacement data WRT the current image.
%   Uses real units. Note that displacement plots are reduced by default.
%   plots_v_cur - cell; contains displacement data WRT the current image.
%   Uses real units. Note that displacement plots are reduced by default.
%   spacing - double; spacing parameter
%   pixtounits - double; units/pixels conversion factor
%   units - string; this a string containing the units
%   pos_parent - integer array; this is the position of the parent figure
%   which determines where to position this figure
%   params_init - integer; contains [radius subsettrunc] if they have been set before.
%
% Outputs ----------------------------------------------------------------%
%   radius - integer; strain radius
%   subsettrunc - logical; true if subset truncation is set
%   outstate - integer; returns either out.cancelled, out.failed, or
%   out.success.
%
% Note that some regions for the input ROIs can be empty. This is handled
% properly.

    % Data ---------------------------------------------------------------%
    % Initialize outputs
    outstate = out.cancelled;
    radius = [];
    subsettrunc = [];
    % Get GUI handles
    handles_gui = init_gui();
    % Run c-tor
    feval(ncorr_util_wrapcallbacktrycatch(@constructor,handles_gui.figure));

    % Callbacks and functions --------------------------------------------%
    function constructor()
        % Set zoom and pan
        handle_zoom = zoom(handles_gui.figure);
        handle_pan = pan(handles_gui.figure);

        % Allow rotations - For some reason when rotations are enabled, the
        % impoint is no longer draggable, so only allow rotations when the
        % cursor is over the least squares plane fit axes.
        handle_rotate3d = rotate3d(handles_gui.axes_leastsquares);
        set(handles_gui.figure,'WindowButtonMotionFcn',ncorr_util_wrapcallbacktrycatch(@callback_moveplot,handles_gui.figure));

        % Set buffers
        % Min radius and spacing
        min_radius = 1;
        max_radius = 100;

        % Create radius function this will take the input from the slider
        % and return a radius
        get_radius = @(x)round(x*50+min_radius);

        % Initialize strain radius and slider
        if (isempty(params_init))
            radius_prelim = 15;
            radius_slider_prelim = min(1,max(0,fzero(@(x) get_radius(x)-radius_prelim,0)));
            subsettrunc_prelim = false;
        else
            radius_prelim = params_init(1);
            radius_slider_prelim = min(1,max(0,fzero(@(x) get_radius(x)-radius_prelim,0)));
            subsettrunc_prelim = params_init(2);
        end

        % Set data
        setappdata(handles_gui.figure,'min_radius',min_radius);
        setappdata(handles_gui.figure,'max_radius',max_radius);
        setappdata(handles_gui.figure,'radius_prelim',radius_prelim);
        setappdata(handles_gui.figure,'radius_slider_prelim',radius_slider_prelim);
        setappdata(handles_gui.figure,'subsettrunc_prelim',subsettrunc_prelim);
        setappdata(handles_gui.figure,'get_radius',get_radius);
        setappdata(handles_gui.figure,'num_cur',length(current)-1);
        setappdata(handles_gui.figure,'handle_points',[]);
        setappdata(handles_gui.figure,'handle_preview',[]);
        setappdata(handles_gui.figure,'handle_rotate3d',handle_rotate3d);
        setappdata(handles_gui.figure,'val_popupmenu_disp',1);
        setappdata(handles_gui.figure,'val_popupmenu_lore',1);
        setappdata(handles_gui.figure,'pos_points',[]);
        setappdata(handles_gui.figure,'num_region',0);
        setappdata(handles_gui.figure,'handle_zoom',handle_zoom);
        setappdata(handles_gui.figure,'handle_pan',handle_pan);

        % Update
        update_axes('set');
        update_sidemenu();

        % Set Visible
        set(handles_gui.figure,'Visible','on');
    end

    function callback_popupmenu_lore(hObject,eventdata) %#ok<INUSD>
        % Get data
        val_popupmenu_lore = get(handles_gui.popupmenu_lore,'value');

        % Clear position of points
        pos_points = [];

        % Set data
        setappdata(handles_gui.figure,'pos_points',pos_points);
        setappdata(handles_gui.figure,'val_popupmenu_lore',val_popupmenu_lore);

        % Update
        update_axes('set');
        update_sidemenu();
    end

    function callback_popupmenu_disp(hObject,eventdata) %#ok<INUSD>
        % Get data
        val_popupmenu_disp = get(handles_gui.popupmenu_disp,'value');

        % Set data
        setappdata(handles_gui.figure,'val_popupmenu_disp',val_popupmenu_disp);

        % Update
        update_axes('set');
        update_sidemenu();
    end

    function callback_slider_radius(hObject,eventdata) %#ok<INUSD>
        % Get data
        get_radius = getappdata(handles_gui.figure,'get_radius');

        % Get slider value
        radius_slider_prelim = get(handles_gui.slider_radius,'value');

        % Get radius_prelim
        radius_prelim = get_radius(radius_slider_prelim);

        % Set data
        setappdata(handles_gui.figure,'radius_prelim',radius_prelim);
        setappdata(handles_gui.figure,'radius_slider_prelim',radius_slider_prelim);

        % Update
        update_axes('update');
        update_sidemenu();
    end

    function callback_checkbox_subsettrunc(hObject,eventdata) %#ok<INUSD>
        % Get value
        subsettrunc_prelim = get(handles_gui.checkbox_subsettrunc,'Value');

        % Set data
        setappdata(handles_gui.figure,'subsettrunc_prelim',subsettrunc_prelim);

        % Update
        update_axes('update');
        update_sidemenu();
    end

    function callback_edit_radius(hObject,eventdata) %#ok<INUSD>
        min_radius = getappdata(handles_gui.figure,'min_radius');
        max_radius = getappdata(handles_gui.figure,'max_radius');
        radius_prelim = getappdata(handles_gui.figure,'radius_prelim');
        radius_slider_prelim = getappdata(handles_gui.figure,'radius_slider_prelim');
        get_radius = getappdata(handles_gui.figure,'get_radius');

        % Get radius
        radius_buffer = str2double(get(handles_gui.edit_radius,'string'));
        if (ncorr_util_isintbb(radius_buffer,min_radius,max_radius,'Radius') == out.success)
            % Set radius
            radius_prelim = radius_buffer;
            radius_slider_prelim = min(1,max(0,fzero(@(x)get_radius(x)-radius_prelim,0)));
        end

        % Set data
        setappdata(handles_gui.figure,'radius_prelim',radius_prelim);
        setappdata(handles_gui.figure,'radius_slider_prelim',radius_slider_prelim);

        % Update
        update_axes('update');
        update_sidemenu();
    end

    function callback_edit_imgnum(hObject,eventdata) %#ok<INUSD>
        % Get Data
        num_cur = getappdata(handles_gui.figure,'num_cur');
        pos_points = getappdata(handles_gui.figure,'pos_points');

        % Get img num
        num_cur_prelim = str2double(get(handles_gui.edit_imgnum,'string'));
        if (ncorr_util_isintbb(num_cur_prelim,1,length(current),'Current image number') == out.success)
            % Clear position of points
            pos_points = [];

            % num_cur_prelim is zero based indexed
            num_cur = num_cur_prelim-1;
        end

        % Set Data
        setappdata(handles_gui.figure,'num_cur',num_cur);
        setappdata(handles_gui.figure,'pos_points',pos_points);

        % Update
        update_axes('set');
    end

    function callback_button_zoom(hObject,eventdata) %#ok<INUSD>
        % Get data
        handle_zoom = getappdata(handles_gui.figure,'handle_zoom');

        if (strcmp(get(handle_zoom,'Enable'),'on'))
            % Zoom is already enabled; disable it
            set(handle_zoom,'Enable','off');
        else
            % Zoom not enabled; enable it
            set(handle_zoom,'Enable','on');
        end

        % Set data
        setappdata(handles_gui.figure,'handle_zoom',handle_zoom);

        % Update
        update_sidemenu();
    end

    function callback_button_pan(hObject,eventdata) %#ok<INUSD>
        % Get data
        handle_pan = getappdata(handles_gui.figure,'handle_pan');

        if (strcmp(get(handle_pan,'Enable'),'on'))
            % Pan is already enabled; disable it
            set(handle_pan,'Enable','off');
        else
            % Pan not enabled; enable it
            set(handle_pan,'Enable','on');
        end

        % Set data
        setappdata(handles_gui.figure,'handle_pan',handle_pan);

        % Update
        update_sidemenu();
    end

    function callback_button_finish(hObject,eventdata) %#ok<INUSD>
        % Get data
        radius_prelim = getappdata(handles_gui.figure,'radius_prelim');
        subsettrunc_prelim = getappdata(handles_gui.figure,'subsettrunc_prelim');

        if (subsettrunc_prelim)
            text_st = 'Enabled';
        else
            text_st = 'Disabled';
        end
        contbutton = questdlg({['Strain radius is set to: ' num2str(radius_prelim) '.'], ...
                               ['Subset Truncation is: ' text_st '.'], ...
                               'Is this correct?'}, ...
                               'Continue Operation','Yes','No','No');
        if (strcmp(contbutton,'Yes'))
            % Set Outputs
            radius = radius_prelim;
            subsettrunc = subsettrunc_prelim;
            outstate = out.success;

            % Exit
            close(handles_gui.figure);
        end
    end

    function callback_button_cancel(hObject,eventdata) %#ok<INUSD>
        close(handles_gui.figure);
    end

    function callback_button_left(hObject,eventdata) %#ok<INUSD>
        % Get Data
        num_cur = getappdata(handles_gui.figure,'num_cur');
        pos_points = getappdata(handles_gui.figure,'pos_points');

        % Check for overshoot
        if (num_cur > 0)
            % Clear position of points
            pos_points = [];

            num_cur = num_cur-1;
        end

        % Set Data
        setappdata(handles_gui.figure,'num_cur',num_cur);
        setappdata(handles_gui.figure,'pos_points',pos_points);

        % Update
        update_axes('set');
        update_sidemenu();
    end

    function callback_button_right(hObject,eventdata) %#ok<INUSD>
        % Get Data
        num_cur = getappdata(handles_gui.figure,'num_cur');
        pos_points = getappdata(handles_gui.figure,'pos_points');

        % Check for overshoot
        if (num_cur < length(current)-1)
            % Clear position of points
            pos_points = [];

            num_cur = num_cur+1;
        end

        % Set Data
        setappdata(handles_gui.figure,'num_cur',num_cur);
        setappdata(handles_gui.figure,'pos_points',pos_points);

        % Update
        update_axes('set');
        update_sidemenu();
    end

    function callback_impoint(pos,num_region)
        % Get data
        pos_points = getappdata(handles_gui.figure,'pos_points');

        % Store position point
        pos_points(num_region+1,:) = round(pos)-1;

        % Set Data
        setappdata(handles_gui.figure,'pos_points',pos_points);
        setappdata(handles_gui.figure,'num_region',num_region);

        % Update
        update_axes('update');
    end

    function callback_moveplot(hObject,eventdata) %#ok<INUSD>
        % Get data
        handle_rotate3d = getappdata(handles_gui.figure,'handle_rotate3d');
        handle_zoom = getappdata(handles_gui.figure,'handle_zoom');
        handle_pan = getappdata(handles_gui.figure,'handle_pan');

        % Enable rotation if zoom and pan are disabled
        pos_mouse = get(handles_gui.figure,'CurrentPoint');
        pos_group = get(handles_gui.group_preview,'Position');
        pos_axes = get(handles_gui.axes_leastsquares,'Position');
        inleastsquares = (pos_mouse(1) > pos_group(1)+pos_axes(1) && pos_mouse(1) < pos_group(1)+pos_axes(1)+pos_axes(3) && pos_mouse(2) > pos_group(2)+pos_axes(2) && pos_mouse(2) < pos_group(2)+pos_axes(2)+pos_axes(4));
        if (inleastsquares)
            set(handle_rotate3d,'Enable','on');
        end

        % If pan and zoom are both disabled and cursor is outside of least
        % square axes, then disable rotation
        if (strcmp(get(handle_zoom,'Enable'),'off') && strcmp(get(handle_pan,'Enable'),'off') && ~inleastsquares)
            set(handle_rotate3d,'Enable','off');
        end

        % Update
        update_sidemenu();
    end

    function update_sidemenu()
        % Get data
        radius_prelim = getappdata(handles_gui.figure,'radius_prelim');
        radius_slider_prelim = getappdata(handles_gui.figure,'radius_slider_prelim');
        val_popupmenu_disp = getappdata(handles_gui.figure,'val_popupmenu_disp');
        val_popupmenu_lore = getappdata(handles_gui.figure,'val_popupmenu_lore');
        handle_zoom = getappdata(handles_gui.figure,'handle_zoom');
        handle_pan = getappdata(handles_gui.figure,'handle_pan');
        subsettrunc_prelim = getappdata(handles_gui.figure,'subsettrunc_prelim');

        % Update slider
        set(handles_gui.slider_radius,'value',radius_slider_prelim);

        % Update edit field
        set(handles_gui.edit_radius,'String',num2str(radius_prelim))

        % Update popupmenus
        set(handles_gui.popupmenu_disp,'value',val_popupmenu_disp);
        set(handles_gui.popupmenu_lore,'value',val_popupmenu_lore);

        if (strcmp(get(handle_pan,'Enable'),'on'))
            set(handles_gui.button_pan,'FontWeight','bold');
        else
            set(handles_gui.button_pan,'FontWeight','normal');
        end

        if (strcmp(get(handle_zoom,'Enable'),'on'))
            set(handles_gui.button_zoom,'FontWeight','bold');
        else
            set(handles_gui.button_zoom,'FontWeight','normal');
        end

        set(handles_gui.checkbox_subsettrunc,'Value',subsettrunc_prelim);
    end

    function update_axes(action)
        % Get data
        radius_prelim = getappdata(handles_gui.figure,'radius_prelim');
        num_cur = getappdata(handles_gui.figure,'num_cur');
        handle_points = getappdata(handles_gui.figure,'handle_points');
        handle_preview = getappdata(handles_gui.figure,'handle_preview');
        val_popupmenu_disp = getappdata(handles_gui.figure,'val_popupmenu_disp');
        val_popupmenu_lore = getappdata(handles_gui.figure,'val_popupmenu_lore');
        pos_points = getappdata(handles_gui.figure,'pos_points');
        num_region = getappdata(handles_gui.figure,'num_region');
        subsettrunc_prelim = getappdata(handles_gui.figure,'subsettrunc_prelim');

        % Get img, data plot, and ROI
        if (val_popupmenu_disp == 1 && val_popupmenu_lore == 1)
            img_bg = reference;
            plot_data = plots_u_ref{num_cur+1};
            roi_data = rois_ref(num_cur+1);
        elseif (val_popupmenu_disp == 2 && val_popupmenu_lore == 1)
            img_bg = reference;
            plot_data = plots_v_ref{num_cur+1};
            roi_data = rois_ref(num_cur+1);
        elseif (val_popupmenu_disp == 1 && val_popupmenu_lore == 2)
            img_bg = current(num_cur+1);
            plot_data = plots_u_cur{num_cur+1};
            roi_data = rois_cur(num_cur+1);
        else
            img_bg = current(num_cur+1);
            plot_data = plots_v_cur{num_cur+1};
            roi_data = rois_cur(num_cur+1);
        end

        if (strcmp(action,'set'))
            % Get reduced img
            img_reduced = img_bg.reduce(spacing);

            % Initialize displacements
            imshow(img_reduced.get_img(),[img_reduced.min_gs img_reduced.max_gs],'Parent',handles_gui.axes_preview);
            hold(handles_gui.axes_preview,'on');

            % Get bounds
            cmin = prctile(plot_data(roi_data.mask),5)-1e-5;
            cmax = prctile(plot_data(roi_data.mask),95)+1e-5;
            if (abs(cmin-cmax)<1e-4)
                cmax = cmax + 1e-5; % add a small increment to insure they are not the same number
                cmin = cmin - 1e-5;
            end

            handle_preview = imshow(plot_data,'Parent',handles_gui.axes_preview);
            set(handles_gui.axes_preview,'CLim',[cmin cmax]);
            hold(handles_gui.axes_preview,'off');

            % Set colorbar
            handle_colorbar = colorbar('peer',handles_gui.axes_preview);
            set(handle_colorbar,'UIContextMenu','');
            set(get(handle_colorbar,'child'),'YData',[cmin cmax]);
            set(handle_colorbar,'YLim',[cmin cmax]);
            set(handle_colorbar,'Units','Pixels');

            % Set Impoints - only set impoints in regions which are full - use
            % a cell array here since it is not gauranteed that regions are
            % full.
            if (isempty(pos_points))
                setnewpos = true;
            else
                setnewpos = false;
            end

            handle_points = cell(1,length(roi_data.region));
            for i = 0:length(roi_data.region)-1
                if (roi_data.region(i+1).totalpoints > 0)
                    % Form point
                    handle_points{i+1} = impoint(handles_gui.axes_preview,round(size(roi_data.mask,2)/2),round(size(roi_data.mask,1)/2));
                    setColor(handle_points{i+1},'g');
                    set(handle_points{i+1},'UIContextMenu','');

                    % Set constraint function
                    api_point = iptgetapi(handle_points{i+1});
                    constrainfcn = ncorr_util_formregionconstraint(roi_data.region(i+1));
                    api_point.setPositionConstraintFcn(constrainfcn);

                    % Initialize point location - use 1 based indexing for
                    % input to constrainfcn
                    x_center = round(roi_data.region(i+1).leftbound+(roi_data.region(i+1).rightbound-roi_data.region(i+1).leftbound)/2)+1;
                    y_center = round(roi_data.region(i+1).upperbound+(roi_data.region(i+1).lowerbound-roi_data.region(i+1).upperbound)/2)+1;

                    % See if points already exist
                    if (setnewpos)
                        % constrainfcn returns points based on 1 based
                        % indexing, so convert it to zero based.
                        pos_points(i+1,1:2) = constrainfcn([x_center y_center])-1;
                    end
                    % Set position based on 1 based indexing
                    setPosition(handle_points{i+1},pos_points(i+1,1:2)+1);

                    % Assign Position callback
                    addNewPositionCallback(handle_points{i+1},ncorr_util_wrapcallbacktrycatch(@(pos)callback_impoint(pos,i),handles_gui.figure));

                    % Set num_region
                    if (setnewpos)
                        num_region = i;
                    end
                end
            end

            % Set colormap
            ncorr_util_colormap(handles_gui.figure);

            % Update buttons
            set(handles_gui.edit_imgnum,'String',num2str(num_cur+1));
            if (length(current) == 1)
                set(handles_gui.button_left,'Enable','off');
                set(handles_gui.button_right,'Enable','off');
                set(handles_gui.edit_imgnum,'Enable','off');
            elseif (num_cur == 0)
                set(handles_gui.button_left,'Enable','off');
                set(handles_gui.button_right,'Enable','on');
                set(handles_gui.edit_imgnum,'Enable','on');
            elseif (num_cur == length(current)-1)
                set(handles_gui.button_left,'Enable','on');
                set(handles_gui.button_right,'Enable','off');
                set(handles_gui.edit_imgnum,'Enable','on');
            else
                set(handles_gui.button_left,'Enable','on');
                set(handles_gui.button_right,'Enable','on');
                set(handles_gui.edit_imgnum,'Enable','on');
            end
        end

        if (strcmp(action,'set') || strcmp(action,'update'))
            % Get cirroi
            cirroi = roi_data.get_cirroi(pos_points(num_region+1,1),pos_points(num_region+1,2),radius_prelim,subsettrunc_prelim);

            % Initialize matrix and vector to zeros
            mat_LS = zeros(3);
            z_vec_LS = zeros(3,1);

            % Initialize alphamask
            alpha_preview = 0.5*roi_data.mask;

            % Preallocate arrays
            plotpoints_x = zeros(cirroi.region.totalpoints,1);
            plotpoints_y = zeros(cirroi.region.totalpoints,1);
            plotpoints_z = zeros(cirroi.region.totalpoints,1);

            % Initialize counter
            counter = 0;
            for i = 0:size(cirroi.region.noderange,1)-1
                x = i+(cirroi.x-cirroi.radius);
                for j = 0:2:cirroi.region.noderange(i+1)-1
                    y_vec = cirroi.region.nodelist(i+1,j+1):cirroi.region.nodelist(i+1,j+2);

                    % Form cirroi.x,cirroi.y,z vectors
                    plotpoints_x(counter+1:counter+length(y_vec)) = x*ones(size(y_vec))*(pixtounits*(spacing+1));
                    plotpoints_y(counter+1:counter+length(y_vec)) = y_vec*(pixtounits*(spacing+1));
                    plotpoints_z(counter+1:counter+length(y_vec)) = plot_data(y_vec+1,x+1)';

                    % Do least squares analysis to find plane
                    x_LS = (x*ones(size(y_vec))-cirroi.x)*(pixtounits*(spacing+1));
                    y_LS = (y_vec-cirroi.y)*(pixtounits*(spacing+1));
                    z_LS = plot_data(y_vec+1,x+1)';

                    % Do the matrix first
                    mat_LS(1,1) = mat_LS(1,1)+sum(x_LS.^2);
                    mat_LS(2,1) = mat_LS(2,1)+sum(x_LS.*y_LS);
                    mat_LS(3,1) = mat_LS(3,1)+sum(x_LS);
                    mat_LS(1,2) = mat_LS(1,2)+sum(x_LS.*y_LS);
                    mat_LS(2,2) = mat_LS(2,2)+sum(y_LS.^2);
                    mat_LS(3,2) = mat_LS(3,2)+sum(y_LS);
                    mat_LS(1,3) = mat_LS(1,3)+sum(x_LS);
                    mat_LS(2,3) = mat_LS(2,3)+sum(y_LS);
                    mat_LS(3,3) = mat_LS(3,3)+sum(ones(size(y_vec)));

                    % Do vec next
                    z_vec_LS(1) = z_vec_LS(1)+sum(x_LS.*z_LS);
                    z_vec_LS(2) = z_vec_LS(2)+sum(y_LS.*z_LS);
                    z_vec_LS(3) = z_vec_LS(3)+sum(z_LS);

                    % Update Preview Mask
                    alpha_preview(y_vec+1,x+1) = 1;

                    % Update counter
                    counter = counter + length(y_vec);
                end
            end

            % Set Alpha Mask
            set(handle_preview,'AlphaData',alpha_preview);

            if (abs(det(mat_LS)) > 10^-10) % Make sure matrix is not singular
                % Find dp/dx dp/cirroi.y
                du_vec = linsolve(mat_LS,z_vec_LS);

                % Plot points first
                plot3(plotpoints_x,plotpoints_y,plotpoints_z,'o','Parent',handles_gui.axes_leastsquares);
                zlabel(handles_gui.axes_leastsquares,['Displacement - ' units]);
                hold(handles_gui.axes_leastsquares,'on');

                % Plot the plane next
                [plane_x,plane_y] = meshgrid((-cirroi.radius:cirroi.radius)*(pixtounits*(spacing+1)),(-cirroi.radius:cirroi.radius)*(pixtounits*(spacing+1)));
                plane_z = du_vec(3)+du_vec(2)*plane_y+du_vec(1)*plane_x;
                mesh(plane_x+cirroi.x*(pixtounits*(spacing+1)),plane_y+cirroi.y*(pixtounits*(spacing+1)),plane_z,'Parent',handles_gui.axes_leastsquares);

                hold(handles_gui.axes_leastsquares,'off');
            else
                cla(handles_gui.axes_leastsquares);
            end

            % Set Name
            set(handles_gui.text_ref,'String',['Reference Name: ' reference.name(1:min(end,22))]);
            set(handles_gui.text_cur,'String',['Current Name: ' current(num_cur+1).name(1:min(end,22))]);
        end

        % Set data
        setappdata(handles_gui.figure,'handle_points',handle_points);
        setappdata(handles_gui.figure,'handle_preview',handle_preview);
        setappdata(handles_gui.figure,'pos_points',pos_points);
        setappdata(handles_gui.figure,'num_region',num_region);
    end

    function handles_gui = init_gui()
    % GUI controls -------------------------------------------------------%
        % Figure
        handles_gui.figure = figure( ...
            'Tag', 'figure', ...
            'Units', 'characters', ...
            'Position', ncorr_util_figpos(pos_parent,[32.5 167]), ...
            'Name', 'Set Strain Parameters', ...
            'MenuBar', 'none', ...
            'NumberTitle', 'off', ...
            'Color', get(0,'DefaultUicontrolBackgroundColor'), ...
            'handlevisibility','off', ...
            'DockControls','off', ...
            'Resize','off', ...
            'WindowStyle','modal', ...
            'Visible','off', ...
            'IntegerHandle','off', ...
            'Interruptible','off');

        % Panels
        handles_gui.group_strainoptions = uipanel( ...
            'Parent', handles_gui.figure, ...
            'Tag', 'group_strainoptions', ...
            'Units', 'characters', ...
            'Position', [2 25.5 35 6.4], ...
            'Title', 'Strain Options', ...
            'Interruptible','off');

        handles_gui.group_viewoptions = uipanel( ...
            'Parent', handles_gui.figure, ...
            'Tag', 'group_viewoptions', ...
            'Units', 'characters', ...
            'Position', [2 17.8 35 7.2], ...
            'Title', 'View Options', ...
            'Interruptible','off');

        handles_gui.group_subsettrunc = uipanel( ...
            'Parent', handles_gui.figure, ...
            'Tag', 'group_subsettrunc', ...
            'Units', 'characters', ...
            'Position', [2 12.8 35 4.3], ...
            'Title', 'Discontinuous Analysis', ...
            'Interruptible','off');

        handles_gui.group_zoompan = uibuttongroup( ...
            'Parent', handles_gui.figure, ...
            'Tag', 'group_zoompan', ...
            'Units', 'characters', ...
            'Position', [2 8.0 35 4.5], ...
            'Title', 'Zoom/Pan', ...
            'Interruptible','off');

        handles_gui.group_menu = uipanel( ...
            'Parent', handles_gui.figure, ...
            'Tag', 'group_menu', ...
            'Units', 'characters', ...
            'Position', [2 1 35 6.7], ...
            'Title', 'Menu', ...
            'Interruptible','off');

        handles_gui.group_preview = uipanel( ...
            'Parent', handles_gui.figure, ...
            'Tag', 'group_preview', ...
            'Units', 'characters', ...
            'Position', [39 0.75 126 31.2], ...
            'Title', 'Preview', ...
            'Interruptible','off');

        % Axes
        handles_gui.axes_preview = axes( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'axes_preview', ...
            'Units', 'characters', ...
            'Visible','off', ...
            'Position', [2 4 57 23.5], ...
            'Interruptible','off');

        handles_gui.axes_leastsquares = axes( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'axes_leastsquares', ...
            'Units', 'characters', ...
            'Visible', 'off', ...
            'Position', [74.8 6 38 19.5], ...
            'Interruptible','off');

        % Drop-down Menu
        handles_gui.popupmenu_lore = uicontrol( ...
            'Parent', handles_gui.group_viewoptions, ...
            'Tag', 'popupmenu_lore', ...
            'Style', 'popupmenu', ...
            'Units', 'characters', ...
            'Position', [2.4 3.6 29.1 1.3], ...
            'BackgroundColor', [1 1 1], ...
            'String', {'Lagrangian','Eulerian'}, ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_popupmenu_lore,handles_gui.figure), ...
            'Interruptible','off');

        handles_gui.popupmenu_disp = uicontrol( ...
            'Parent', handles_gui.group_viewoptions, ...
            'Tag', 'popupmenu_disp', ...
            'Style', 'popupmenu', ...
            'Units', 'characters', ...
            'Position', [2.4 1.5 29.1 1.3], ...
            'BackgroundColor', [1 1 1], ...
            'String', {'U-Displacement','V-Displacement'}, ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_popupmenu_disp,handles_gui.figure), ...
            'Interruptible','off');

        % Static Texts
        handles_gui.text_radius = uicontrol( ...
            'Parent', handles_gui.group_strainoptions, ...
            'Tag', 'text_radius', ...
            'Style', 'text', ...
            'Units', 'characters', ...
            'Position', [2.5 2.6 16 1.3], ...
            'String', 'Strain Radius: ', ...
            'HorizontalAlignment', 'left', ...
            'Interruptible','off');

        handles_gui.text_ref = uicontrol( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'text_ref', ...
            'Style', 'text', ...
            'Units', 'characters', ...
            'Position', [3.0 1.8 56.9 1.3], ...
            'String', 'Reference Name: ', ...
            'HorizontalAlignment', 'left', ...
            'Interruptible','off');

        handles_gui.text_cur = uicontrol( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'text_cur', ...
            'Style', 'text', ...
            'Units', 'characters', ...
            'Position', [3.0 0.6 56.9 1.3], ...
            'String', 'Current Name: ', ...
            'HorizontalAlignment', 'left', ...
            'Interruptible','off');

        % Sliders
        handles_gui.slider_radius = uicontrol( ...
            'Parent', handles_gui.group_strainoptions, ...
            'Tag', 'slider_radius', ...
            'Style', 'slider', ...
            'Units', 'characters', ...
            'Position', [2.5 1.2 28.9 1.3], ...
            'BackgroundColor', [0.9 0.9 0.9], ...
            'String', {'Slider'}, ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_slider_radius,handles_gui.figure), ...
            'Interruptible','off');

        % Checkbox
        handles_gui.checkbox_subsettrunc = uicontrol( ...
            'Parent', handles_gui.group_subsettrunc, ...
            'Tag', 'checkbox_subsettrunc', ...
            'Style', 'checkbox', ...
            'Units', 'characters', ...
            'Position', [2.3 1.0 26.7 1.3], ...
            'String', 'Subset Truncation', ...
            'Value', 0, ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_checkbox_subsettrunc,handles_gui.figure), ...
            'Interruptible','off');

        % Edit texts
        handles_gui.edit_radius = uicontrol( ...
            'Parent', handles_gui.group_strainoptions, ...
            'Tag', 'edit_radius', ...
            'Style', 'edit', ...
            'Units', 'characters', ...
            'Position', [20.2 2.8 11.1 1.3], ...
            'String', '', ...
            'HorizontalAlignment', 'left', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_edit_radius,handles_gui.figure), ...
            'Interruptible','off');

        handles_gui.edit_imgnum = uicontrol( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'edit_imgnum', ...
            'Style', 'edit', ...
            'Units', 'characters', ...
            'Position', [108.2 1.3 7 1.6], ...
            'String', '', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_edit_imgnum,handles_gui.figure), ...
            'Enable', 'off', ...
            'Interruptible','off');

        % Pushbuttons
        handles_gui.button_zoom = uicontrol( ...
            'Parent', handles_gui.group_zoompan, ...
            'Tag', 'button_zoom', ...
            'Style', 'pushbutton', ...
            'Units', 'characters', ...
            'Position', [2.1 1 14.4 1.7], ...
            'String', 'Zoom', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_button_zoom,handles_gui.figure), ...
            'Interruptible','off');

        handles_gui.button_pan = uicontrol( ...
            'Parent', handles_gui.group_zoompan, ...
            'Tag', 'button_pan', ...
            'Style', 'pushbutton', ...
            'Units', 'characters', ...
            'Position', [17.4 1 14.4 1.7], ...
            'String', 'Pan', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_button_pan,handles_gui.figure), ...
            'Interruptible','off');

        handles_gui.button_finish = uicontrol( ...
            'Parent', handles_gui.group_menu, ...
            'Tag', 'button_finish', ...
            'Style', 'pushbutton', ...
            'Units', 'characters', ...
            'Position', [2.1 3 29.7 1.7], ...
            'String', 'Finish', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_button_finish,handles_gui.figure), ...
            'Interruptible','off');

        handles_gui.button_cancel = uicontrol( ...
            'Parent', handles_gui.group_menu, ...
            'Tag', 'button_cancel', ...
            'Style', 'pushbutton', ...
            'Units', 'characters', ...
            'Position', [2.1 1 29.7 1.7], ...
            'String', 'Cancel', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_button_cancel,handles_gui.figure), ...
            'Interruptible','off');

        handles_gui.button_left = uicontrol( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'button_left', ...
            'Style', 'pushbutton', ...
            'Units', 'characters', ...
            'Position', [101.2 1.2 6 1.8], ...
            'String', '<', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_button_left,handles_gui.figure), ...
            'Interruptible','off', ...
            'Enable', 'off');

        handles_gui.button_right = uicontrol( ...
            'Parent', handles_gui.group_preview, ...
            'Tag', 'button_right', ...
            'Style', 'pushbutton', ...
            'Units', 'characters', ...
            'Position', [116.2 1.2 6 1.8], ...
            'String', '>', ...
            'Callback', ncorr_util_wrapcallbacktrycatch(@callback_button_right,handles_gui.figure), ...
            'Interruptible','off', ...
            'Enable', 'off');
    end

    % Pause until figure is closed ---------------------------------------%
    waitfor(handles_gui.figure);
end


classdef prtBrvVbMembershipModel < prtBrvMembershipModel

    methods (Abstract)

        y = conjugateVariationalAverageLogLikelihood(self, x)
    end
end


function varargout=tach(varargin)
%
% [hr]=tach(recordName,annotator,N,N0,ouputSize)
%
%    Wrapper to WFDB TACH:
%         http://www.physionet.org/physiotools/wag/tach-1.htm
%
%    Reads a WFDB record and Annotation file to return:
%
%
% hr
%       Nx1 vector of doubles representing a uniformly sampled and
%       smoothed instantaneous heart rate signal. The output are samples
%       of the instantaneous heart rate signal in units of beats per minute.
%
% Required Parameters:
%
% recorName
%       String specifying the name of the record in the WFDB path or
%       in the current directory.
%
% annotator  -
%       String specifying the name of the annotation file in the WFDB path or
%       in the current directory.
%
% Optional Parameters are:
%
% N
%       A 1x1 integer specifying the sample number at which to stop reading the
%       annotation file (default read all = N).
% N0
%       A 1x1 integer specifying the sample number at which to start reading the
%       annotation file (default 1 = begining of the record).
%
% outputSize
%
%    A 1x1 integer specifying the number of output samples (ie estimated
%    heart rate intervals) such that the output 'hr' is a vector of
%    size (outputSize-1) x 1.
%
%
% Written by Ikaro Silva, 2013
% Last Modified: January 24, 2014
% Version 1.1
%
% Since 0.0.1
%
% %Example 1- Read a signal and annotaion from PhysioNet's Remote server:
%[hr]=tach('challenge/2013/set-a/a01','fqrs');
%plot(hr);grid on;hold on

%endOfHelp

[javaWfdbExec,config]=getWfdbClass('tach');

%Set default pararamter values
inputs={'recordName','annotator','N','N0','ouputSize'};
outputs={'data(:,1)'};
N=[];
N0=1;
ouputSize=[];
for n=1:nargin
    if(~isempty(varargin{n}))
        eval([inputs{n} '=varargin{n};']);
    end
end

N0=num2str(N0-1); %-1 is necessary because WFDB is 0 based indexed.
wfdb_argument={'-r',recordName,'-a',annotator,'-f',['s' N0]};

if(~isempty(N))
    wfdb_argument{end+1}='-t';
    %-1 is necessary because WFDB is 0 based indexed.
    wfdb_argument{end+1}=['s' num2str(N-1)];
end

if(~isempty(ouputSize))
    wfdb_argument{end+1}='-n';
    wfdb_argument{end+1}=[num2str(ouputSize)];
end

data=javaWfdbExec.execToDoubleArray(wfdb_argument);
data=wfdbjava2mat(data);
for n=1:nargout
        eval(['varargout{n}=' outputs{n} ';']);
end


function data = xdf2fieldtrip(filename, varargin)

% XDF2FIELDTRIP reads data from a XDF file with multiple streams. It upsamples the
% data of all streams to the highest sampling rate and concatenates all channels in
% all streams into a raw data structure that is compatible with the output of
% FT_PREPROCESSING.
%
% Use as
%   data = xdf2fieldtrip(filename, ...)
%
% Optional arguments should come in key-value pairs and can include
%   streamindx  = list, indices of the streams to read (default is all)
%
% You can also use the standard procedure with FT_DEFINETRIAL and FT_PREPROCESSING
% for XDF files. This will return (only) the stream with the highest sampling rate,
% which is typically the EEG.
%
% You can use FT_READ_EVENT to read the events from the non-continuous data streams.
% To get them aligned with the samples in one of the specific data streams, you
% should specify the corresponding header structure.
%
% See also FT_PREPROCESSING, FT_DEFINETRIAL, FT_REDEFINETRIAL

% Copyright (C) 2019, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

% process the options
streamindx = ft_getopt(varargin, 'streamindx');

% ensure this is on the path
ft_hastoolbox('xdf', 1);

% read all streams
streams = load_xdf(filename);

iscontinuous = false(size(streams));
% figure out which streams contain continuous/regular and discrete/irregular data
for i=1:numel(streams)
  iscontinuous(i) = isfield(streams{i}.info, 'effective_srate');
end

% give some feedback
for i=1:numel(streams)
  if iscontinuous(i)
    ft_info('stream %d contains continuous %s data\n', i, streams{i}.info.name);
  else
    ft_info('stream %d contains non-continuous %s data\n', i, streams{i}.info.name);
  end
end

% select the streams to continue working with
if isempty(streamindx)
  selected = true(size(streams));
else
  selected = false(size(streams));
  selected(streamindx) = true;
end

% discard the non-continuous streams
streams = streams(iscontinuous & selected);

if isempty(streams)
  ft_error('no continuous streams were selected');
end

% convert each continuous stream into a FieldTrip raw data structure
data = cell(size(streams));
for i=1:numel(streams)

  % make a copy for convenience
  stream = streams{i};

  % this section of code is shared with fileio/private/sccn_xdf
  hdr             = [];
  hdr.Fs          = stream.info.effective_srate;
  hdr.nChans      = numel(stream.info.desc.channels.channel);
  hdr.nSamplesPre = 0;
  hdr.nSamples    = length(stream.time_stamps);
  hdr.nTrials     = 1;
  hdr.label       = cell(hdr.nChans, 1);
  hdr.chantype    = cell(hdr.nChans, 1);
  hdr.chanunit    = cell(hdr.nChans, 1);

  prefix = stream.info.name;

  for j=1:hdr.nChans
    hdr.label{j} = [prefix '_' stream.info.desc.channels.channel{j}.label];
    hdr.chantype{j} = stream.info.desc.channels.channel{j}.type;
    hdr.chanunit{j} = stream.info.desc.channels.channel{j}.unit;
  end

  hdr.FirstTimeStamp     = stream.time_stamps(1);
  hdr.TimeStampPerSample = (stream.time_stamps(end)-stream.time_stamps(1)) / (length(stream.time_stamps) - 1);

  % keep the original header details
  hdr.orig = stream.info;

  data{i}.hdr = hdr;
  data{i}.label = hdr.label;
  data{i}.time = {streams{i}.time_stamps};
  data{i}.trial = {streams{i}.time_series};

end % for all continuous streams

% determine the continuous stream with the highest sampling rate
srate = nan(size(streams));
for i=1:numel(streams)
  srate(i) = streams{i}.info.effective_srate;
end
[dum, indx] = max(srate);

if numel(data)>1
  % resample all data structures, except the one with the max sampling rate
  % this will also align the time axes
  for i=1:numel(data)
    if i==indx
      continue
    end

    ft_notice('resampling %s', streams{i}.info.name);
    cfg = [];
    cfg.time = data{indx}.time;
    data{i} = ft_resampledata(cfg, data{i});
  end

  % append all data structures
  data = ft_appenddata([], data{:});
else
  % simply return the first and only one
  data = data{1};
end


classdef ImageFolder < datasets.Dataset
%IMAGEFOLDER Dataset created from a directory of JPEG images
%   Encapsulates a directory of JPEG images for training. The MatConvNet
%   function VL_IMREADJPEG is used, which supports fast multi-threaded
%   reading from disk.
%
%   D = datasets.ImageFolder('dataDir', '/folder', 'imageSize', [H, W])
%   loads a dataset from the directory '/folder', resizing images to have
%   height H and width W. Any subfolders are recursively scanned for JPEG
%   files. The resulting list of image files will be stored in D.filenames.
%
%   D.train() returns a cell array with mini-batches, from the shuffled
%   training set. Each mini-batch consists of a set of indexes drawn from
%   the vector D.trainSet.
%
%   D.val() returns a cell array with mini-batches, from the validation set
%   (without shuffling). Each mini-batch consists of a set of indexes drawn
%   from the vector D.valSet.
%
%   IMAGES = D.get(BATCH) returns a tensor of images for the given
%   mini-batch BATCH.
%
%   datasets.ImageFolder(..., 'option', value, ...) sets the following
%   properties, in addition to 'dataDir' and 'imageSize':
%
%   `batchSize`:: 128
%     The batch size.
%
%   `numThreads`:: 1
%     Number of image reading threads. If > 1, multi-threading is enabled.
%
%   `useGpu`:: false
%     Whether the IMAGES tensor is returned as a gpuArray.
%
%   `keepAspect`:: true
%     Whether aspect ratio is maintained when resizing images. If true,
%     images that do not conform to the aspect ratio of D.imageSize will be
%     cropped.
%
%   `removeMean`:: true
%     Whether the dataset's mean is subtracted from every pixel.
%
%   `whiten`:: true
%     Whether the dataset's standard deviation is removed from every pixel.
%
%   `augmentation`:: none
%     Struct that specifies data augmentation options. The valid fields
%     are: flip, location, aspect, scale, brightness, contrast, saturation,
%     crop. They correspond to similarly-named arguments of VL_IMREADJPEG,
%     with the exception of 'aspect' which corresponds to VL_IMREADJPEG's
%     'CropAnisotropy'. See 'help vl_imreadjpeg' for more details.
%
%   `partialBatches`:: false
%     Whether partial batches are returned (which can happen for the last
%     batch in a set, if the batch size does not divide the set size).
%
%   `trainSet`:: []
%     The training set. Note that subclasses (e.g. datasets.ImageNet)
%     usually override this property with the default training set.
%
%   `valSet`:: []
%     The validation set. Note that subclasses (e.g. datasets.ImageNet)
%     usually override this property with the default validation set.
%
%   `augmentImage`:: training only
%     Whether data augmentation is applied to each image in the dataset
%     (logical vector).
%
%   See 'autonn/examples/cnn/image_folder_example.m' for a full example.

% Copyright (C) 2018 Joao F. Henriques, Andrea Vedaldi.
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).

  properties
    dataDir
    filenames

    numThreads = 1  % higher to enable multithreading/prefetching
    useGpu = false
    removeMean = true
    whiten = true

    imageSize
    keepAspect = true

    augmentation = struct('flip', false, 'location', false, 'aspect', 1, ...
      'scale', 1, 'brightness', 0, 'contrast', 0, 'saturation', 0, 'crop', 1)

    augmentImage  % vector, for each image, false disables augmentation (e.g. val images)

    % pixel statistics over dataset (computed automatically)
    rgbMean
    rgbCovariance
    rgbDeviation  % independent standard deviation of each channel (3x1)
    rgbDeviationFull  % standard deviation as a 3x3 matrix
    initialized = false
  end

  methods
    function o = ImageFolder(varargin)
      % parse generic ImageFolder arguments on non-subclassed
      % construction. allows use as a stand-alone class.
      varargin = o.parseGenericArgs(varargin) ;

      opts.skipInitialization = false ;
      opts = vl_argparse(opts, varargin) ;

      if ~opts.skipInitialization
        o.initialize() ;
      end
    end

    function initialize(o)
      % finishes initializing the dataset, must be called by subclasses
      assert(~o.initialized) ;
      assert(~isempty(o.dataDir), 'Must specify dataset.dataDir.') ;
      assert(~isempty(o.imageSize), 'Must specify dataset.imageSize.') ;
      o.initialized = true ;

      % list image files if they weren't set
      if isempty(o.filenames)
        o.filenames = o.listImages(o.dataDir) ;
      end

      % apply data augmentation to training images only by default
      if isempty(o.augmentImage)
        o.augmentImage = true(size(o.filenames)) ;
        o.augmentImage(o.valSet) = false ;
      end

      % load or recompute RGB statistics over all images
      if o.removeMean || o.whiten
        cache = [o.dataDir '/datasetstats.mat'] ;
        if exist(cache, 'file')
          s = load(cache) ;
          o.rgbMean = s.rgbMean ;
          o.rgbCovariance = s.rgbCovariance ;
        else
          o.computeImageStats() ;
          rgbMean = o.rgbMean ;  %#ok<*PROP,NASGU>
          rgbCovariance = o.rgbCovariance ;  %#ok<NASGU>
          save(cache, 'rgbMean', 'rgbCovariance') ;
        end

        % standard deviation
        o.rgbDeviation = sqrt(diag(o.rgbCovariance)) ;
        [v, d] = eig(o.rgbCovariance) ;
        o.rgbDeviationFull = v * sqrt(d) ;
      end

      % reset MEX file state
      clear('vl_imreadjpeg') ;
    end

    function files = listImages(o, folder, prefix)
      % returns all JPEG image files recursively from the given directory

      if nargin < 3  % no leading slash on top-level prefix/partial folder
        prefix = '' ;
      else
        prefix = [prefix '/'] ;
      end
      folder = [folder '/'] ;

      % list folder contents
      s = dir([folder '*']) ;

      % iterate files/folders
      files = cell(1, numel(s)) ;
      for i = 1:numel(s)
        name = s(i).name ;
        if strcmpi(name(max(1,end-3) : end), '.jpg') || ...
           strcmpi(name(max(1,end-4) : end), '.jpeg')
          % a file, store it (wrapped around a scalar cell)
          files{i} = {[prefix name]} ;

        elseif s(i).isdir && ~any(strcmp(name, {'.', '..'}))
          % a folder, recurse (stored as a cell array)
          files{i} = o.listImages([folder name], [prefix name]) ;
        end
      end

      % flatten nested cell arrays. this also removes empty entries, which
      % were not assigned in the For loop (e.g. invalid files/folders).
      files = [files{:}] ;
    end

    function args = parseGenericArgs(o, args)
      % called by subclasses to parse generic Dataset arguments.
      % start by parsing parent class's arguments
      args = o.parseGenericArgs@datasets.Dataset(args) ;

      % now parse ImageFolder arguments
      args = vl_parseprop(o, args, {'dataDir', 'filenames', 'removeMean', ...
        'whiten', 'numThreads', 'useGpu', 'imageSize', 'keepAspect', ...
        'augmentation', 'augmentImage'}) ;
    end

    function batches = partition(o, idx, batchSz)
      % partition indexes into batches (stored in a cell array).
      % if IDX is a matrix, each column is a distinct sample.
      if nargin < 3  % allow overriding batch size
        batchSz = o.batchSize ;
      end
      batches = o.partition@datasets.Dataset(idx, batchSz) ;  % call parent class

      % for prefetching, append an extra row with the next batch for each
      % batch. this way, get() can know which batch comes next to prefetch.
      if o.numThreads > 1
        batches = [batches; batches(1,2:end), {[]}] ;
      end
    end

    function [images, idx] = get(o, batch)
      % return a single batch of images, with optional prefetching.
      % also returns the image indexes.
      if ~iscell(batch)
        batch = {batch};
      end
      assert(~isempty(batch) && numel(batch) <= 2, ...
        'Malformed batch; use dataset.partition/train/val.');

      % get current batch
      idx = batch{1} ;
      images = o.getImageBatch(o.filenames(idx), o.augmentImage(idx(1))) ;

      % start prefetching next batch of images
      if o.numThreads > 1 && numel(batch) == 2 && ~isempty(batch{2})
        o.getImageBatch(o.filenames(batch{2}), o.augmentImage(batch{2}(1))) ;
      end
    end

    function data = getImageBatch(o, images, augmentImages)
      % load image batch, with data augmentation and whitening
      assert(o.initialized, 'Subclass did not call ImageFolder.initialize.') ;

      imagePaths = strcat([o.dataDir filesep], images) ;

      % for validation, disable all augmentation except any fixed cropping
      if augmentImages
        augment = o.augmentation ;  % full augmentation
      else
        augment = struct('crop', o.augmentation.crop) ;  % crop only
      end

      % fill in missing augmentation options with defaults
      defaults = struct('flip', false, 'location', false, 'aspect', 1, ...
        'scale', 1, 'brightness', 0, 'contrast', 0, 'saturation', 0, 'crop', 1) ;
      augment = vl_argparse(defaults, augment) ;

      % make brightness jitter proportional to dataset's color deviation,
      % unless the data is whitened
      if ~isequal(augment.brightness, 0) && ~o.whiten
        if ~isempty(o.rgbDeviationFull)
          augment.brightness = double(augment.brightness * o.rgbDeviationFull)  ;
        else  % no color deviation to use
          augment.brightness = 0 ;
        end
      end

      args{1} = {imagePaths, ...
                 'NumThreads', o.numThreads, ...
                 'Pack', ...
                 'Interpolation', 'bicubic', ...
                 'Resize', o.imageSize(1:2), ...
                 'CropSize', augment.crop * augment.scale, ...
                 'CropAnisotropy', augment.aspect, ...
                 'Brightness', augment.brightness, ...
                 'Contrast', augment.contrast, ...
                 'Saturation', augment.saturation} ;

      if ~o.keepAspect  % Squashing effect
        args{end+1} = {'CropAnisotropy', 0} ;
      end

      if augment.flip
        args{end+1} = {'Flip'} ;
      end

      if augment.location
        args{end+1} = {'CropLocation', 'random'} ;
      else
        args{end+1} = {'CropLocation', 'center'} ;
      end

      if o.useGpu
        args{end+1} = {'Gpu'} ;
      end

      if o.removeMean && ~isempty(o.rgbMean)
        args{end+1} = {'SubtractAverage', o.rgbMean} ;
      end

      args = horzcat(args{:}) ;

      if o.numThreads > 1 && nargout == 0
        vl_imreadjpeg(args{:}, 'prefetch') ;
      else
        data = vl_imreadjpeg(args{:}) ;
        data = data{1} ;

        if o.whiten && ~isempty(o.rgbDeviation)
          data = data ./ reshape(o.rgbDeviation, 1, 1, 3) ;
        end
      end
    end

    function computeImageStats(o)
      % computes RGB mean/covariance over whole dataset
      bs = o.batchSize ;
      images = o.filenames ;
      [avg, rgbm1, rgbm2] = deal({}) ;

      maxImages = 10000 ;  % max number of images, should be enough for pixel-wise mean
      skip = floor(max(1, numel(images) / maxImages)) ;  % number of batches to skip
      for t = 1 : skip * bs : numel(images)
        time = tic ;
        batch = t : min(t+bs-1, numel(images)) ;
        fprintf('collecting image stats: batch starting with image %d ...', batch(1)) ;

        data = o.getImageBatch(images(batch), false) ;

        z = reshape(shiftdim(data,2),3,[]) ;
        rgbm1{end+1} = mean(z,2) ;
        rgbm2{end+1} = z*z'/size(z,2) ;
        avg{end+1} = mean(data, 4) ;
        time = toc(time) ;
        fprintf(' %.1f Hz\n', numel(batch) / time) ;
      end

      % averageImage = gather(mean(cat(4,avg{:}),4)) ;
      rgbm1 = gather(mean(cat(2,rgbm1{:}),2)) ;
      rgbm2 = gather(mean(cat(3,rgbm2{:}),3)) ;
      o.rgbMean = rgbm1 ;
      o.rgbCovariance = rgbm2 - rgbm1*rgbm1' ;

      fprintf('computeImageStats: all done\n') ;
    end
  end
end


function inspect_issue2138

% WALLTIME 00:10:00
% MEM 2gb

%%

bidsroot = dccnpath('/home/common/matlab/fieldtrip/data/test/issue2138');

% this is the original
filename1 = fullfile(bidsroot, 'sub-001/ses-01/eeg/sub-001_ses-01_task-WorkingMemory_run-1_eeg.set');

% this is the one with enriched HED tags
filename2 = fullfile(bidsroot, 'sub-002/ses-01/eeg/sub-002_ses-01_task-WorkingMemory_run-1_eeg.set');

%%
% just to check that this works

hdr1 = ft_read_header(filename1, 'readbids', false);
event1 = ft_read_event(filename1, 'readbids', false);

hdr2 = ft_read_header(filename2, 'readbids', false);
event2 = ft_read_event(filename2, 'readbids', false);

%%
% default is readbids=true, which causes the sidecars to be read

hdr1 = ft_read_header(filename1);
event1 = ft_read_event(filename1);

hdr2 = ft_read_header(filename2);
event2 = ft_read_event(filename2);

%%
% show the events

cfg = [];
cfg.dataset = filename1;
cfg.trialdef.eventtype = '?';
ft_definetrial(cfg);

cfg = [];
cfg.dataset = filename2;
cfg.trialdef.eventtype = '?';
ft_definetrial(cfg);

%%
% this is some random ERP analysis using "normal" events

cfg = [];
cfg.dataset = filename1;
cfg.trialdef.eventtype = 'show_letter';
cfg.trialdef.eventvalue = {'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z'};
cfg.trialdef.prestim = 0.3;
cfg.trialdef.poststim = 0.7;
cfg = ft_definetrial(cfg);

cfg.baselinewindow = [-inf 0];
cfg.demean = 'yes';
rawdata1 = ft_preprocessing(cfg);

% make a selective average
cfg = [];
cfg.trials = ismember(rawdata1.trialinfo.eventvalue, {'A' 'B' 'C'});
erp1 = ft_timelockanalysis(cfg, rawdata1);

assert(sum(erp1.cfg.trials)==9);

%%
% this is exactly the same analysis for a dataset that also contains HED tags
% but it does not use those HED tags

cfg = [];
cfg.dataset = filename2;
cfg.trialdef.eventtype = 'show_letter';
cfg.trialdef.eventvalue = {'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z'};
cfg.trialdef.prestim = 0.3;
cfg.trialdef.poststim = 0.7;
cfg = ft_definetrial(cfg);

cfg.baselinewindow = [-inf 0];
cfg.demean = 'yes';
rawdata2 = ft_preprocessing(cfg);

% make a selective average
cfg = [];
cfg.trials = ismember(rawdata2.trialinfo.eventvalue, {'A' 'B' 'C'});
erp2 = ft_timelockanalysis(cfg, rawdata2);

assert(sum(erp2.cfg.trials)==9);

%%
% this is the same analysis, now using the HED tags

cfg = [];
cfg.dataset = filename2;
cfg.trialdef.eventtype = 'HED';
cfg.trialdef.eventvalue = 'Sensory-event,Visual-presentation,(Character,(Center-of,Computer-screen)),Target,*';
cfg.trialdef.prestim = 0.3;
cfg.trialdef.poststim = 0.7;
cfg = ft_definetrial(cfg);

cfg.baselinewindow = [-inf 0];
cfg.demean = 'yes';
rawdata3 = ft_preprocessing(cfg);

% make a selective average
cfg = [];
cfg.trials = ~cellfun(@isempty, regexp(rawdata3.trialinfo.eventvalue, '(Character,Label/[ABC])'));
erp3 = ft_timelockanalysis(cfg, rawdata3);

assert(sum(erp3.cfg.trials)==9);

%%
% now using a trialfun that is specialized in HED tags

cfg = [];
cfg.dataset = filename2; % this has complete tags
cfg.trialfun = 'ft_trialfun_hed';
cfg.trialdef.regexp = '.*,Target,\(Character,Label/[ABC]\),.*';
cfg.trialdef.prestim = 0.3;
cfg.trialdef.poststim = 0.7;
cfg = ft_definetrial(cfg);

cfg.baselinewindow = [-inf 0];
cfg.demean = 'yes';
rawdata4 = ft_preprocessing(cfg);

% make an average, it would be possible to make a selection using rawdata4.trialinfo
cfg = [];
erp4 = ft_timelockanalysis(cfg, rawdata4);

%%
% assemble the HED tags when not present in the file

cfg = [];
cfg.dataset = filename1; % HED tags need to be assembled
cfg.trialfun = 'ft_trialfun_hed';
cfg.trialdef.regexp = '.*,Target,\(Character,Label/[ABC]\),.*';
cfg.trialdef.prestim = 0.3;
cfg.trialdef.poststim = 0.7;
cfg = ft_definetrial(cfg);

cfg.baselinewindow = [-inf 0];
cfg.demean = 'yes';
rawdata5 = ft_preprocessing(cfg);

% make an average, it would be possible to make a selection using rawdata4.trialinfo
cfg = [];
erp5 = ft_timelockanalysis(cfg, rawdata5);

%%
% just to demonstrate that reading+filtering and segmenting can also be done the other way around

cfg = [];
cfg.dataset = filename2;
cfg.hpfreq = 0.2;
cfg.hpfilter = 'yes';
rawdata6_continuous = ft_preprocessing(cfg);

% determine the selection of epochs
cfg = [];
cfg.dataset = filename2;
cfg.trialfun = 'ft_trialfun_hed';
cfg.trialdef.regexp = '.*,Target,\(Character,Label/[ABC]\),.*';
cfg.trialdef.prestim = 0.3;
cfg.trialdef.poststim = 0.7;
cfg = ft_definetrial(cfg);

% make the selection of epochs
rawdata6_epoched = ft_redefinetrial(cfg, rawdata6_continuous);

% make an average
cfg = [];
erp5 = ft_timelockanalysis(cfg, rawdata5_epoched);

%%
% all ERPs should be the same

assert(isequal(erp1.avg, erp2.avg));
assert(isequal(erp1.avg, erp3.avg));
assert(isequal(erp1.avg, erp4.avg));
assert(isequal(erp1.avg, erp5.avg));
% assert(isequal(erp1.avg, erp6.avg)); % this has been preprocessed differently


function dim = size(ob)
%function dim = size(ob)
%       "size" method for "mridft" class

dim = [size(ob.t,1) size(ob.we,1)];


%kckhuemap 'creates a colormap for displaying Hue images with color'
% This MatLab function was automatically generated by a converter (KhorosToMatLab) from the Khoros ckhuemap.pane file
%
% Parameters:
% InputFile: i 'Input ', required: 'First Input data object'
% Double: saturation 'Saturation', default: 100: 'saturation for hsv->rgb conversion'
% Double: value 'Value ', default: 100: 'value for hsv->rgb conversion'
% OutputFile: o 'Output', required: 'Resulting output data object'
%
% Example: o = kckhuemap(i, {'i','';'saturation',100;'value',100;'o',''})
%
% Khoros helpfile follows below:
%
%  PROGRAM
% ckhuemap - creates a colormap for displaying Hue images with color
%
%  DESCRIPTION
% Creates a colormap (map segment) for an hue image in order to display correctly the image with colors. User can enter value and saturation values.
% The user can also choose a hue model so the generated values will be displayed closer to the expected.
% For other hue models than HSV (so far only IHS) the colors will be approximated, (not correct and in most cases, plain wrong), because the other models cannot give correct hues for fixed saturation and values. If you're looking for a correct way to display only the hues of an image, HSV model will be better.
% If you want to use IHS, better results can be obtained by setting saturation < 60 and intensity < 90. Even so the results will not be correct.
%
%
%
%  EXAMPLES
% $COLOR/workspaces/HSV.wk
%
%  "SEE ALSO"
% iRGBtoHSV, iRGBtoHSI, iRGBtoHLS, iRGBtoSOWstar
%
%  RESTRICTIONS
% Value segment in output object is forced to KUINT for correct assignment of map values.
%
%  REFERENCES
% All references for the toolbox COLOR are on the $COLOR/Readme.html file.
%
%  COPYRIGHT
% Copyright (C) 1997 Rafael Santos. Khoros (C) Khoral Research, Inc.
%

function varargout = kckhuemap(varargin)
if nargin ==0
  Inputs={};arglist={'',''};
elseif nargin ==1
  Inputs=varargin{1};arglist={'',''};
elseif nargin ==2
  Inputs=varargin{1}; arglist=varargin{2};
else error('Usage: [out1,..] = kckhuemap(Inputs,arglist).');
end
if size(arglist,2)~=2
  error('arglist must be of form {''ParameterTag1'',value1;''ParameterTag2'',value2}')
 end
narglist={'i', '__input';'saturation', 100;'value', 100;'o', '__output'};
maxval={0,100,100,0};
minval={0,0,0,0};
istoggle=[0,0,0,0];
was_set=istoggle * 0;
paramtype={'InputFile','Double','Double','OutputFile'};
% identify the input arrays and assign them to the arguments as stated by the user
if ~iscell(Inputs)
Inputs = {Inputs};
end
NumReqOutputs=1; nextinput=1; nextoutput=1;
  for ii=1:size(arglist,1)
  wasmatched=0;
  for jj=1:size(narglist,1)
   if strcmp(arglist{ii,1},narglist{jj,1})  % a given argument was matched to the possible arguments
     wasmatched = 1;
     was_set(jj) = 1;
     if strcmp(narglist{jj,2}, '__input')
      if (nextinput > length(Inputs))
        error(['Input ' narglist{jj,1} ' has no corresponding input!']);
      end
      narglist{jj,2} = 'OK_in';
      nextinput = nextinput + 1;
     elseif strcmp(narglist{jj,2}, '__output')
      if (nextoutput > nargout)
        error(['Output nr. ' narglist{jj,1} ' is not present in the assignment list of outputs !']);
      end
      if (isempty(arglist{ii,2}))
        narglist{jj,2} = 'OK_out';
      else
        narglist{jj,2} = arglist{ii,2};
      end

      nextoutput = nextoutput + 1;
      if (minval{jj} == 0)
         NumReqOutputs = NumReqOutputs - 1;
      end
     elseif isstr(arglist{ii,2})
      narglist{jj,2} = arglist{ii,2};
     else
        if strcmp(paramtype{jj}, 'Integer') & (round(arglist{ii,2}) ~= arglist{ii,2})
            error(['Argument ' arglist{ii,1} ' is of integer type but non-integer number ' arglist{ii,2} ' was supplied']);
        end
        if (minval{jj} ~= 0 | maxval{jj} ~= 0)
          if (minval{jj} == 1 & maxval{jj} == 1 & arglist{ii,2} < 0)
            error(['Argument ' arglist{ii,1} ' must be bigger or equal to zero!']);
          elseif (minval{jj} == -1 & maxval{jj} == -1 & arglist{ii,2} > 0)
            error(['Argument ' arglist{ii,1} ' must be smaller or equal to zero!']);
          elseif (minval{jj} == 2 & maxval{jj} == 2 & arglist{ii,2} <= 0)
            error(['Argument ' arglist{ii,1} ' must be bigger than zero!']);
          elseif (minval{jj} == -2 & maxval{jj} == -2 & arglist{ii,2} >= 0)
            error(['Argument ' arglist{ii,1} ' must be smaller than zero!']);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} < minval{jj})
            error(['Argument ' arglist{ii,1} ' must be bigger than ' num2str(minval{jj})]);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} > maxval{jj})
            error(['Argument ' arglist{ii,1} ' must be smaller than ' num2str(maxval{jj})]);
          end
        end
     end
     if ~strcmp(narglist{jj,2},'OK_out') &  ~strcmp(narglist{jj,2},'OK_in')
       narglist{jj,2} = arglist{ii,2};
     end
   end
   end
   if (wasmatched == 0 & ~strcmp(arglist{ii,1},''))
        error(['Argument ' arglist{ii,1} ' is not a valid argument for this function']);
   end
end
% match the remaining inputs/outputs to the unused arguments and test for missing required inputs
 for jj=1:size(narglist,1)
     if  strcmp(paramtype{jj}, 'Toggle')
        if (narglist{jj,2} ==0)
          narglist{jj,1} = '';
        end;
        narglist{jj,2} = '';
     end;
     if  ~strcmp(narglist{jj,2},'__input') && ~strcmp(narglist{jj,2},'__output') && istoggle(jj) && ~ was_set(jj)
          narglist{jj,1} = '';
          narglist{jj,2} = '';
     end;
     if strcmp(narglist{jj,2}, '__input')
      if (minval{jj} == 0)  % meaning this input is required
        if (nextinput > size(Inputs))
           error(['Required input ' narglist{jj,1} ' has no corresponding input in the list!']);
        else
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        end
      else  % this is an optional input
        if (nextinput <= length(Inputs))
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end;
     else
     if strcmp(narglist{jj,2}, '__output')
      if (minval{jj} == 0) % this is a required output
        if (nextoutput > nargout & nargout > 1)
           error(['Required output ' narglist{jj,1} ' is not stated in the assignment list!']);
        else
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
          NumReqOutputs = NumReqOutputs-1;
        end
      else % this is an optional output
        if (nargout - nextoutput >= NumReqOutputs)
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end
     end
  end
end
if nargout
   varargout = cell(1,nargout);
else
  varargout = cell(1,1);
end
global KhorosRoot
if exist('KhorosRoot') && ~isempty(KhorosRoot)
w=['"' KhorosRoot];
else
if ispc
  w='"C:\Program Files\dip\khorosBin\';
else
[s,w] = system('which cantata');
w=['"' w(1:end-8)];
end
end
[varargout{:}]=callKhoros([w 'ckhuemap"  '],Inputs,narglist);


function f = sym2func(fun,args)
%SYM2FUNC  Convert a symbolic variable equation to function handle
%
%   f = sym2func(fun) converts the symbolic equation into a string, then
%   replaces any x1 to x(1), x2 to x(2), etc, and returns it as a function
%   handle suitable for standard evaluation.

%   Copyright (C) 2013 Jonathan Currie (IPL)

if(nargin < 2), args = 'x'; end

%Extract symbolic variables in the expression
sv = symvar(fun);
%For each symbolic var, convert x1 to x(1) as cell of strings
cv = cell(size(sv));
for i = 1:length(sv)
    cv{i} = symVar2MVar(sv(i));
end
%Substitute our indexed variables into the symbolic expression
sfun = subs(fun,sv,cv);

%Convert to string
str = char(sfun);
%Remove 'matrix' if it is found
k = strfind(str,'matrix');
if(k)
    str = str(k+7:end-1);
end
%Ensure we have right size
[r,c] = size(fun);
if(c==1 && r > 1)
    str = regexprep(str,',',';'); %force column
elseif(c>1 && r>1)
    str = regexprep(str,'],','];'); %keep matrix
end

if(iscell(args))
    a = args{1};
    for i = 2:length(args)
        a = sprintf('%s,%s',a,args{i});
    end
    args = a;
end

%Build function handle
f = eval(['@(' args ') ' str]);

function str = symVar2MVar(svar)
s = char(svar);
for i = 2:length(s)
    if(~isalpha(s(i)))
        str = [s(1:i-1) '(' s(i:end) ')'];
        return;
    end
end
%No index?, oh well... could be for e.g. just t
str = s;

function is = isalpha(str)
if((str>='a' && str<='z') || (str>='A' && str<='Z'))
    is = true;
else
    is = false;
end


function newstr = m2tstrjoin(cellstr, delimiter, floatFormat)
% This function joins a cell of strings to a single string (with a
% given delimiter in between two strings, if desired).
    if ~exist('delimiter','var') || isempty(delimiter)
        delimiter = '';
    end
    if ~exist('floatFormat','var') || isempty(floatFormat)
        floatFormat = '%g';
    end
    if isempty(cellstr)
        newstr = '';
        return
    end

    % convert all values to strings first
    nElem = numel(cellstr);
    for k = 1:nElem
        if isnumeric(cellstr{k})
            cellstr{k} = sprintf(floatFormat, cellstr{k});
        elseif iscell(cellstr{k})
            cellstr{k} = m2tstrjoin(cellstr{k}, delimiter, floatFormat);
            % this will fail for heavily nested cells
        elseif ~ischar(cellstr{k})
            error('matlab2tikz:join:NotCellstrOrNumeric',...
                'Expected cellstr or numeric.');
        end
    end

    % inspired by strjoin of recent versions of MATLAB
    newstr = cell(2,nElem);
    newstr(1,:)         = reshape(cellstr, 1, nElem);
    newstr(2,1:nElem-1) = {delimiter}; % put delimiters in-between the elements
    newstr(2,end)       = {''}; % for Octave 4 compatibility
    newstr = [newstr{:}];
end


function sMat = in_bst_matrix(MatFile, varargin)
% IN_BST_MATRIX: Read a "matrix" file in Brainstorm format.
%
% USAGE:  sMat = in_bst_matrix(MatFile, FieldsList) : Read the specified fields
%         sMat = in_bst_matrix(MatFile)             : Read all the fields
%
% INPUT:
%    - MatFile    : Absolute or relative path to the file to read
%    - FieldsList : List of fields to read from the file
% OUTPUT:
%    - sMat : Brainstorm matrix file structure

% @=============================================================================
% 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, 2010-2019

%% ===== PARSE INPUTS =====
% Get protocol folders
ProtocolInfo = bst_get('ProtocolInfo');
% Filename: Relative / absolute
if ~file_exist(MatFile)
    MatFile = bst_fullfile(ProtocolInfo.STUDIES, MatFile);
    if ~file_exist(MatFile)
        error('File not found.');
    end
end

% Specific fields
if (nargin < 2)
    % Read all fields
    sMat = load(MatFile);
    % Get all the fields
    FieldsToRead = fieldnames(sMat);
    FieldsToRead{end+1} = 'ZScore';
else
    % Get fields to read
    FieldsToRead = varargin;
    % Add other necessary fields
    if ismember('Value', FieldsToRead)
        FieldsToRead{end+1} = 'ZScore';
    end
    % When reading Leff, make sure nAvg is read as well
    if ismember('Leff', FieldsToRead) && ~ismember('nAvg', FieldsToRead)
        FieldsToRead{end+1} = 'nAvg';
    end
    % Read each field only once
    FieldsToRead = unique(FieldsToRead);
    % Read specified files only
    warning off MATLAB:load:variableNotFound
    sMat = load(MatFile, FieldsToRead{:});
    warning on MATLAB:load:variableNotFound
end

%% ===== FILL OTHER MISSING FIELDS =====
for i = 1:length(FieldsToRead)
    if ~isfield(sMat, FieldsToRead{i}) || isempty(sMat.(FieldsToRead{i}))
        switch(FieldsToRead{i})
            case 'nAvg'
                sMat.(FieldsToRead{i}) = 1;
            case 'Leff'
                if isfield(sMat, 'nAvg') && ~isempty(sMat.nAvg)
                    sMat.Leff = sMat.nAvg;
                else
                    sMat.Leff = 1;
                end
            otherwise
                sMat.(FieldsToRead{i}) = [];
        end
    end
end

%% ===== APPLY DYNAMIC ZSCORE =====
% DEPRECATED
% Check for structure integrity
if ismember('ZScore', FieldsToRead) && ~isempty(sMat.ZScore) && (~isfield(sMat.ZScore, 'mean') || ~isfield(sMat.ZScore, 'std') || ~isfield(sMat.ZScore, 'abs') || ~isfield(sMat.ZScore, 'baseline') || isempty(sMat.ZScore.abs))
    sMat.ZScore = [];
end
% Apply to file values
if ismember('ZScore', FieldsToRead) && ismember('Value', FieldsToRead) && ~isempty(sMat.ZScore) && ~isempty(sMat.Value)
    sMat.Value = process_zscore_dynamic('Compute', sMat.Value, sMat.ZScore);
    sMat = rmfield(sMat, 'ZScore');
end

% ===== FIX TRANSPOSED TIME VECTOR =====
if isfield(sMat, 'Time') && (size(sMat.Time,1) > 1)
    sMat.Time = sMat.Time';
end


function [net,stats] = cnn_train_autonn(net, imdb, getBatch, varargin)
%CNN_TRAIN_AUTONN Demonstrates training a CNN using the AutoNN wrapper
%   CNN_TRAIN_AUTONN is similar to CNN_TRAIN, but works with the AutoNN
%   wrapper instead of the SimpleNN wrapper.

% Copyright (C) 2014-18 Andrea Vedaldi and Joao F. Henriques.
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).
addpath(fullfile(vl_rootnn, 'examples'));

warning('AutoNN:cnn_train_autonn', ['The function cnn_train_autonn is ' ...
  'deprecated. It will still be supported, however switching to the new, ' ...
  'more flexible training classes (Solver, Dataset, Stats) is recommended. ' ...
  'See the examples for more information. This warning will now be disabled.']) ;
warning('off', 'AutoNN:cnn_train_autonn') ;

% this is needed to harmonize the behavior of two versions of vl_nnloss:
% the legacy behavior which *sums* the loss over the batch, and the new
% behavior that takes the *average* over the batch.
% first, detect if the new behavior ('normalise' option) is present.
old = false ;
try
  vl_nnloss([], [], 'normalise', true)  ;
catch  % unrecognized option, must be the old vl_nnloss
  old = true ;
end
if old
  warning('AutoNN:normalizedLoss', ['The most recent version of ' ...
   'vl_nnloss normalizes the loss by the batch size. The current version ' ...
   'does not. A workaround is being used, but consider updating MatConvNet.']) ;
end
% consider the loss normalized (workaround in Layer.vl_nnloss). this can
% be disabled if detrimental (e.g. custom loss without normalization).
opts.normalizedLoss = true ;

opts.expDir = fullfile('data','exp') ;
opts.continue = true ;
opts.batchSize = 256 ;
opts.numSubBatches = 1 ;
opts.train = [] ;
opts.val = [] ;
opts.gpus = [] ;
opts.prefetch = false ;
opts.numEpochs = 300 ;
opts.learningRate = 0.001 ;
opts.weightDecay = 0.0005 ;

opts.solver = [] ;  % Empty array means use the default SGD solver
[opts, varargin] = vl_argparse(opts, varargin) ;
if ~isempty(opts.solver)
  assert(isa(opts.solver, 'function_handle') && nargout(opts.solver) == 2,...
    'Invalid solver; expected a function handle with two outputs.') ;
  % Call without input arguments, to get default options
  opts.solverOpts = opts.solver() ;
end

opts.onNanInf = [] ;  % Behavior when variables with NaN/Inf are found: 'error', 'warning' or 'debug' (default: nothing).

opts.momentum = 0.9 ;
opts.saveSolverState = true ;
opts.nesterovUpdate = false ;
opts.randomSeed = 0 ;
opts.profile = false ;
opts.parameterServer.method = 'mmap' ;
opts.parameterServer.prefix = 'mcn' ;

if isa(net, 'dagnn.DagNN')
  opts.derOutputs = {'objective', 1} ;
else
  opts.derOutputs = 1 ;
end
opts.stats = 'auto' ;  % list of layers to aggregate stats from (e.g. loss, error), or 'auto' for automatic
opts.extractStatsFn = [] ;
opts.plotStatistics = true;
opts.plotDiagnostics = false ;
opts.postEpochFn = [] ;  % postEpochFn(net,params,state) called after each epoch; can return a new learning rate, 0 to stop, [] for no change
opts = vl_argparse(opts, varargin) ;

if ~exist(opts.expDir, 'dir'), mkdir(opts.expDir) ; end
if isempty(opts.train), opts.train = find(imdb.images.set==1) ; end
if isempty(opts.val), opts.val = find(imdb.images.set==2) ; end
if isscalar(opts.train) && isnumeric(opts.train) && isnan(opts.train)
  opts.train = [] ;
end
if isscalar(opts.val) && isnumeric(opts.val) && isnan(opts.val)
  opts.val = [] ;
end

% -------------------------------------------------------------------------
%                                                            Initialization
% -------------------------------------------------------------------------

evaluateMode = isempty(opts.train) ;
if ~evaluateMode
  if isempty(opts.derOutputs)
    error('DEROUTPUTS must be specified when training.\n') ;
  end
end

% find loss layers now instead of at every extractStatsFn call
if isa(net, 'dagnn.DagNN')
  if isempty(opts.extractStatsFn)
    opts.extractStatsFn = @extractStats ;
  end

  if isequal(opts.stats, 'auto')  % all losses
    sel = find(cellfun(@(x) isa(x,'dagnn.Loss'), {net.layers.block})) ;
  else
    sel = zeros(size(opts.stats)) ;
    for i = 1:numel(sel)  % find layers with the given names
      sel(i) = net.getLayerIndex(opts.stats{i}) ;
    end
  end
else  % AutoNN
  if isempty(opts.extractStatsFn)
    opts.extractStatsFn = @extractStatsAutoNN ;
  end

  if isequal(opts.stats, 'auto')  % all losses
    sel = find(cellfun(@(f) isequal(f, @vl_nnloss) || ...
      isequal(f, @vl_nnsoftmaxloss), {net.forward.func})) ;
  else
    sel = zeros(size(opts.stats)) ;
    for i = 1:numel(sel)  % find layers with the given names
      sel(i) = find(strcmp({net.forward.name}, opts.stats{i})) ;
    end
  end
end
fn = opts.extractStatsFn ;
normalizedLoss = opts.normalizedLoss ;
opts.extractStatsFn = @(stats, net, batchSize) fn(stats, net, sel, batchSize, normalizedLoss) ;

% -------------------------------------------------------------------------
%                                                        Train and validate
% -------------------------------------------------------------------------

modelPath = @(ep) fullfile(opts.expDir, sprintf('net-epoch-%d.mat', ep));
modelFigPath = fullfile(opts.expDir, 'net-train.pdf') ;

start = opts.continue * findLastCheckpoint(opts.expDir) ;
if start >= 1
  fprintf('%s: resuming by loading epoch %d\n', mfilename, start) ;
  [net, state, stats] = loadState(modelPath(start)) ;
else
  state = [] ;
end

for epoch=start+1:opts.numEpochs

  % Set the random seed based on the epoch and opts.randomSeed.
  % This is important for reproducibility, including when training
  % is restarted from a checkpoint.

  rng(epoch + opts.randomSeed) ;
  prepareGPUs(opts, epoch == start+1) ;

  % Train for one epoch.
  params = opts ;
  params.epoch = epoch ;
  params.learningRate = opts.learningRate(min(epoch, numel(opts.learningRate))) ;
  params.train = opts.train(randperm(numel(opts.train))) ; % shuffle
  params.val = opts.val(randperm(numel(opts.val))) ;
  params.imdb = imdb ;
  params.getBatch = getBatch ;

  if numel(opts.gpus) <= 1
    [net, state] = processEpoch(net, state, params, 'train') ;
    [net, state] = processEpoch(net, state, params, 'val') ;
    if ~evaluateMode
      saveState(modelPath(epoch), net, state) ;
    end
    lastStats = state.stats ;
  else
    spmd
      [net, state] = processEpoch(net, state, params, 'train') ;
      [net, state] = processEpoch(net, state, params, 'val') ;
      if labindex == 1 && ~evaluateMode
        saveState(modelPath(epoch), net, state) ;
      end
      lastStats = state.stats ;
    end
    lastStats = accumulateStats(lastStats) ;
  end

  if ~isempty(opts.postEpochFn)
    state.stats = lastStats ;  % allow the func. to read accumulated stats
    if nargout(opts.postEpochFn) <= 0
      opts.postEpochFn(net, params, state) ;
    else  % returned an updated learning rate
      if nargout(opts.postEpochFn) == 1
        lr = opts.postEpochFn(net, params, state) ;
      else  % returned updated/custom stats
        [lr, lastStats] = opts.postEpochFn(net, params, state) ;
      end
      if ~isempty(lr), opts.learningRate = lr; end
      if opts.learningRate == 0, break; end
    end
  end

  stats.train(epoch) = lastStats.train ;
  stats.val(epoch) = lastStats.val ;
  clear lastStats ;
  saveStats(modelPath(epoch), stats) ;

  if opts.plotStatistics
    switchFigure(1) ; clf ;
    plots = setdiff(...
      cat(2,...
      fieldnames(stats.train)', ...
      fieldnames(stats.val)'), {'num', 'time'}) ;
    for p = plots
      p = char(p) ;
      values = zeros(0, epoch) ;
      leg = {} ;
      for f = {'train', 'val'}
        f = char(f) ;
        if isfield(stats.(f), p)
          tmp = [stats.(f).(p)] ;
          values(end+1,:) = tmp(1,:)' ;
          leg{end+1} = f ;
        end
      end
      subplot(1,numel(plots),find(strcmp(p,plots))) ;
      plot(1:epoch, values','o-') ;
      xlabel('epoch') ;
      title(p) ;
      legend(leg{:}) ;
      grid on ;
    end
    drawnow ;
    print(1, modelFigPath, '-dpdf') ;
  end
end

% With multiple GPUs, return one copy
if isa(net, 'Composite'), net = net{1} ; end

% -------------------------------------------------------------------------
function [net, state] = processEpoch(net, state, params, mode)
% -------------------------------------------------------------------------
% Note that net is not strictly needed as an output argument as net
% is a handle class. However, this fixes some aliasing issue in the
% spmd caller.

% initialize with momentum 0
if isempty(state) || isempty(state.solverState)
  state.solverState = cell(1, numel(net.params)) ;
  state.solverState(:) = {0} ;
end

% move CNN  to GPU as needed
numGpus = numel(params.gpus) ;
if numGpus >= 1
  net.move('gpu') ;
  for i = 1:numel(state.solverState)
    if ~isa(net, 'dagnn.DagNN') && ~net.isGpuVar(net.params(i).var)
      continue
    end
    s = state.solverState{i} ;
    if isnumeric(s)
      state.solverState{i} = gpuArray(s) ;
    elseif isstruct(s)
      state.solverState{i} = structfun(@gpuArray, s, 'UniformOutput', false) ;
    end
  end
end
if numGpus > 1
  parserv = ParameterServer(params.parameterServer) ;

  if ~isa(net, 'dagnn.DagNN')
    % some layers have implicit Param sizes: the Params are scalar, until
    % the first derivative computation, when they're expanded to full size.
    % this allows initializing layers without knowing their input/output
    % sizes, making them less verbose. e.g. batch-norm: y = vl_nnbnorm(x);
    % for multi-GPU we need their sizes, so compute their derivatives once.
    subset = params.(mode) ;
    inputs = params.getBatch(params.imdb, subset(1)) ;  % one sample

    net.eval(inputs, 'normal', params.derOutputs) ;

    paramDer = net.getDer([net.params.var]) ;
    if ~iscell(paramDer), paramDer = {paramDer} ; end

    net.setParameterServer(parserv, paramDer) ;
  else
    net.setParameterServer(parserv) ;
  end
else
  parserv = [] ;
end

% profile
if params.profile
  if numGpus <= 1
    profile clear ;
    profile on ;
  else
    mpiprofile reset ;
    mpiprofile on ;
  end
end

num = 0 ;
epoch = params.epoch ;
subset = params.(mode) ;
adjustTime = 0 ;

stats.num = 0 ; % return something even if subset = []
stats.time = 0 ;

start = tic ;
for t=1:params.batchSize:numel(subset)
  fprintf('%s: epoch %02d: %3d/%3d:', mode, epoch, ...
          fix((t-1)/params.batchSize)+1, ceil(numel(subset)/params.batchSize)) ;
  batchSize = min(params.batchSize, numel(subset) - t + 1) ;

  for s=1:params.numSubBatches
    % get this image batch and prefetch the next
    batchStart = t + (labindex-1) + (s-1) * numlabs ;
    batchEnd = min(t+params.batchSize-1, numel(subset)) ;
    batch = subset(batchStart : params.numSubBatches * numlabs : batchEnd) ;
    num = num + numel(batch) ;
    if numel(batch) == 0, continue ; end

    inputs = params.getBatch(params.imdb, batch) ;

    if params.prefetch
      if s == params.numSubBatches
        batchStart = t + (labindex-1) + params.batchSize ;
        batchEnd = min(t+2*params.batchSize-1, numel(subset)) ;
      else
        batchStart = batchStart + numlabs ;
      end
      nextBatch = subset(batchStart : params.numSubBatches * numlabs : batchEnd) ;
      params.getBatch(params.imdb, nextBatch) ;
    end

    if isa(net, 'dagnn.DagNN')
      if strcmp(mode, 'train')
        net.mode = 'normal' ;
        net.accumulateParamDers = (s ~= 1) ;
        net.eval(inputs, params.derOutputs, 'holdOn', s < params.numSubBatches) ;
      else
        net.mode = 'test' ;
        net.eval(inputs) ;
      end
    else  % AutoNN
      if strcmp(mode, 'train')
        net.eval(inputs, 'normal', params.derOutputs, s ~= 1) ;
      else
        net.eval(inputs, 'test') ;
      end
    end
  end

  % Check if any vars contain NaN or Inf
  if ~isempty(params.onNanInf) && ~isa(net, 'dagnn.DagNN') && ...
    ~all(cellfun(@(x) ~isnumeric(x) || gather(all(isfinite(x(:)))), net.vars))
    switch params.onNanInf
    case 'error'
      error('The network contains NaN or Inf values.') ;
    case 'warning'
      warning('The network contains NaN or Inf values.') ;
    otherwise  % 'debug'/'break'
      net.displayVars() ;
      warning('The network contains NaN or Inf values.') ;
      keyboard ;
    end
  end

  % Accumulate gradient.
  if strcmp(mode, 'train')
    if ~isempty(parserv), parserv.sync() ; end
    if isa(net, 'dagnn.DagNN')
      state = accumulateGradients(net, state, params, batchSize, parserv) ;
    else
      state = accumulateGradientsAutoNN(net, state, params, batchSize, parserv) ;
    end
  end

  % Get statistics.
  time = toc(start) + adjustTime ;
  batchTime = time - stats.time ;
  stats.num = num ;
  stats.time = time ;
  stats = params.extractStatsFn(stats, net, batchSize / max(1, numGpus)) ;
  currentSpeed = batchSize / batchTime ;
  averageSpeed = (t + batchSize - 1) / time ;
  if t == 3*params.batchSize + 1
    % compensate for the first three iterations, which are outliers
    adjustTime = 4*batchTime - time ;
    stats.time = time + adjustTime ;
  end

  fprintf(' %.1f (%.1f) Hz', averageSpeed, currentSpeed) ;
  for f = setdiff(fieldnames(stats)', {'num', 'time'})
    f = char(f) ;
    fprintf(' %s: %.3f', f, stats.(f)) ;
  end
  fprintf('\n') ;

  if params.plotDiagnostics && ~isa(net, 'dagnn.DagNN') && mod(t-1, params.batchSize * 5) == 0
    net.plotDiagnostics(200) ;
  end
end

% Save back to state.
state.stats.(mode) = stats ;
if params.profile
  if numGpus <= 1
    state.prof.(mode) = profile('info') ;
    profile off ;
  else
    state.prof.(mode) = mpiprofile('info');
    mpiprofile off ;
  end
end
if ~params.saveSolverState
  state.solverState = [] ;
else
  for i = 1:numel(state.solverState)
    s = state.solverState{i} ;
    if isnumeric(s)
      state.solverState{i} = gather(s) ;
    elseif isstruct(s)
      state.solverState{i} = structfun(@gather, s, 'UniformOutput', false) ;
    end
  end
end

net.reset() ;
net.move('cpu') ;

% -------------------------------------------------------------------------
function state = accumulateGradients(net, state, params, batchSize, parserv)
% -------------------------------------------------------------------------
numGpus = numel(params.gpus) ;
otherGpus = setdiff(1:numGpus, labindex) ;
if params.normalizedLoss  % assume loss is normalized by batch size
  batchSize = max(1,numGpus) * params.numSubBatches ;
end

for p=1:numel(net.params)

  if ~isempty(parserv)
    parDer = parserv.pullWithIndex(p) ;
  else
    parDer = net.params(p).der ;
  end

  switch net.params(p).trainMethod

    case 'average' % mainly for batch normalization
      thisLR = net.params(p).learningRate ;
      net.params(p).value = vl_taccum(...
          1 - thisLR, net.params(p).value, ...
          (thisLR/batchSize/net.params(p).fanout),  parDer) ;

    case 'gradient'
      thisDecay = params.weightDecay * net.params(p).weightDecay ;
      thisLR = params.learningRate * net.params(p).learningRate ;

      if thisLR>0 || thisDecay>0
        % Normalize gradient and incorporate weight decay.
        parDer = vl_taccum(1/batchSize, parDer, ...
                           thisDecay, net.params(p).value) ;

        if isempty(params.solver)
          % Default solver is the optimised SGD.
          % Update momentum.
          state.solverState{p} = vl_taccum(...
            params.momentum, state.solverState{p}, ...
            -1, parDer) ;

          % Nesterov update (aka one step ahead).
          if params.nesterovUpdate
            delta = params.momentum * state.solverState{p} - parDer ;
          else
            delta = state.solverState{p} ;
          end

          % Update parameters.
          net.params(p).value = vl_taccum(...
            1,  net.params(p).value, thisLR, delta) ;

        else
          % call solver function to update weights
          [net.params(p).value, state.solverState{p}] = ...
            params.solver(net.params(p).value, state.solverState{p}, ...
            parDer, params.solverOpts, thisLR) ;
        end
      end
    otherwise
      error('Unknown training method ''%s'' for parameter ''%s''.', ...
        net.params(p).trainMethod, ...
        net.params(p).name) ;
  end
end

% -------------------------------------------------------------------------
function state = accumulateGradientsAutoNN(net, state, params, batchSize, parserv)
% -------------------------------------------------------------------------

% ensure supported training methods are ordered as expected
assert(isequal(Param.trainMethods, {'gradient', 'average', 'none'})) ;

numGpus = numel(params.gpus) ;
if params.normalizedLoss  % assume loss is normalized by batch size
  batchSize = max(1,numGpus) * params.numSubBatches ;
end

paramVars = [net.params.var] ;
w = net.getValue(paramVars) ;
dw = net.getDer(paramVars) ;
if isscalar(paramVars), w = {w} ; dw = {dw} ; end

if ~params.plotDiagnostics
  % allow memory to be released, for parameters and their derivatives
  net.setValue([paramVars, paramVars + 1], cell(1, 2 * numel(paramVars))) ;
else
  % free only parameter memory, as we still need the gradients for plotting the diagnostics
  net.setValue(paramVars, cell(size(paramVars))) ;
end

for p=1:numel(net.params)
  if ~isempty(parserv)
    parDer = parserv.pullWithIndex(p) ;
  else
    parDer = dw{p} ;
  end

  switch net.params(p).trainMethod
    case 1
      thisDecay = params.weightDecay * net.params(p).weightDecay ;
      thisLR = params.learningRate * net.params(p).learningRate ;

      if thisLR>0 || thisDecay>0
        % Normalize gradient and incorporate weight decay.
        parDer = vl_taccum(1/batchSize, parDer, ...
                           thisDecay, w{p}) ;

        if isempty(params.solver)
          % Default solver is the optimised SGD.
          % Update momentum.
          state.solverState{p} = vl_taccum(...
            params.momentum, state.solverState{p}, ...
            -1, parDer) ;

          % Nesterov update (aka one step ahead).
          if params.nesterovUpdate
            delta = params.momentum * state.solverState{p} - parDer ;
          else
            delta = state.solverState{p} ;
          end

          % Update parameters.
          w{p} = vl_taccum(1, w{p}, thisLR, delta) ;

        else
          % call solver function to update weights
          [w{p}, state.solverState{p}] = ...
            params.solver(w{p}, state.solverState{p}, ...
            parDer, params.solverOpts, thisLR) ;
        end
      end

    case 2 % mainly for batch normalization
      thisLR = net.params(p).learningRate ;
      w{p} = vl_taccum(...
          1 - thisLR, w{p}, ...
          (thisLR/batchSize/net.params(p).fanout),  parDer) ;

    case 3  % none
    otherwise
      error('Unknown training method ''%i'' for parameter ''%s''.', ...
        net.params(p).trainMethod, ...
        net.params(p).name) ;
  end
end

if isscalar(paramVars), w = w{1} ; end
net.setValue(paramVars, w) ;

% -------------------------------------------------------------------------
function stats = accumulateStats(stats_)
% -------------------------------------------------------------------------

for s = {'train', 'val'}
  s = char(s) ;
  total = 0 ;

  % initialize stats stucture with same fields and same order as
  % stats_{1}
  stats__ = stats_{1} ;
  names = fieldnames(stats__.(s))' ;
  values = zeros(1, numel(names)) ;
  fields = cat(1, names, num2cell(values)) ;
  stats.(s) = struct(fields{:}) ;

  for g = 1:numel(stats_)
    stats__ = stats_{g} ;
    num__ = stats__.(s).num ;
    total = total + num__ ;

    for f = setdiff(fieldnames(stats__.(s))', 'num')
      f = char(f) ;
      stats.(s).(f) = stats.(s).(f) + stats__.(s).(f) * num__ ;

      if g == numel(stats_)
        stats.(s).(f) = stats.(s).(f) / total ;
      end
    end
  end
  stats.(s).num = total ;
end

% -------------------------------------------------------------------------
function stats = extractStats(stats, net, sel, ~, ~)
% -------------------------------------------------------------------------
for i = 1:numel(sel)
  stats.(net.layers(sel(i)).outputs{1}) = net.layers(sel(i)).block.average ;
end

% -------------------------------------------------------------------------
function stats = extractStatsAutoNN(stats, net, sel, batchSize, normalizedLoss)
% -------------------------------------------------------------------------
for i = 1:numel(sel)
  name = net.forward(sel(i)).name ;
  if ~isfield(stats, name)
    stats.(name) = 0 ;
  end
  newValue = gather(sum(net.vars{net.forward(sel(i)).outputVar(1)}(:))) ;
  if normalizedLoss  % Undo normalization for following code
    newValue = newValue * batchSize ;
  end
  % Update running average (same work as dagnn.Loss)
  stats.(name) = ((stats.num - batchSize) * stats.(name) + newValue) / stats.num ;
end

% -------------------------------------------------------------------------
function saveState(fileName, net_, state)
% -------------------------------------------------------------------------
net = net_.saveobj() ;
save(fileName, 'net', 'state') ;

% -------------------------------------------------------------------------
function saveStats(fileName, stats)
% -------------------------------------------------------------------------
if exist(fileName)
  save(fileName, 'stats', '-append') ;
else
  save(fileName, 'stats') ;
end

% -------------------------------------------------------------------------
function [net, state, stats] = loadState(fileName)
% -------------------------------------------------------------------------
load(fileName, 'net', 'state', 'stats') ;
if isfield(net, 'layers')
%   net = dagnn.DagNN.loadobj(net) ;
  error('The last checkpoint does not contain a valid AutoNN model.') ;
else
  net = Net(net) ;
end
if isempty(whos('stats'))
  error('Epoch ''%s'' was only partially saved. Delete this file and try again.', ...
        fileName) ;
end

% -------------------------------------------------------------------------
function epoch = findLastCheckpoint(modelDir)
% -------------------------------------------------------------------------
list = dir(fullfile(modelDir, 'net-epoch-*.mat')) ;
tokens = regexp({list.name}, 'net-epoch-([\d]+).mat', 'tokens') ;
epoch = cellfun(@(x) sscanf(x{1}{1}, '%d'), tokens) ;
epoch = max([epoch 0]) ;

% -------------------------------------------------------------------------
function switchFigure(n)
% -------------------------------------------------------------------------
if get(0,'CurrentFigure') ~= n
  try
    set(0,'CurrentFigure',n) ;
  catch
    figure(n) ;
  end
end

% -------------------------------------------------------------------------
function clearMex()
% -------------------------------------------------------------------------
clear vl_tmove vl_imreadjpeg ;

% -------------------------------------------------------------------------
function prepareGPUs(opts, cold)
% -------------------------------------------------------------------------
numGpus = numel(opts.gpus) ;
if numGpus > 1
  % check parallel pool integrity as it could have timed out
  pool = gcp('nocreate') ;
  if ~isempty(pool) && pool.NumWorkers ~= numGpus
    delete(pool) ;
  end
  pool = gcp('nocreate') ;
  if isempty(pool)
    parpool('local', numGpus) ;
    cold = true ;
  end

end
if numGpus >= 1 && cold
  fprintf('%s: resetting GPU\n', mfilename)
  clearMex() ;
  if numGpus == 1
    gpuDevice(opts.gpus)
  else
    spmd
      clearMex() ;
      gpuDevice(opts.gpus(labindex))
    end
  end
end


function varargout = solver_apply( ndxs, func, varargin )
global tfocs_count___

%SOLVER_APPLY	Internal TFOCS routine.
%	A wrapper function used to facilitate the counting of function calls, and
%   to standardize the number of output arguments for the algorithms.

[ varargout{1:max(nargout,1)} ] = func( varargin{:} );
tfocs_count___(ndxs) = tfocs_count___(ndxs) + 1;

% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant.
% Copyright 2013 California Institute of Technology and CVX Research.
% See the file LICENSE for full license information.


function lab_nodes_local = extract(nodes_local, lab_begin, lab_end)

% Special routine for locating the nodes in node_local that correspond to
% columns on the lab. We rely on the fact that lab_cols are consecutive
% integers from lab_begin to lab_end (inclusive).

  lab_nodes_local = [];
  for kk = 1:length(nodes_local)
      if nodes_local(kk) >= lab_begin && nodes_local(kk) <= lab_end
          lab_nodes_local = [lab_nodes_local; kk nodes_local(kk)];
      end
  end

  return
end


function h = fast_viewvol(vol,volres,frameres)
% h = fast_viewvol(vol,volres,frameres)

h = [];

if(nargin < 1 | nargin > 3)
  fprintf('USAGE: h = fast_viewvol(vol,volres,frameres)\n');
  return;
end

if(nargin < 2)      volres = [1 1 1]; end
if(isempty(volres)) volres = [1 1 1]; end

if(nargin < 3)        frameres = 1; end
if(isempty(frameres)) frameres = 1; end