classdef RoaThreshold < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        RootFigure                      matlab.ui.Figure
        ROAOverlayRedPanel              matlab.ui.container.Panel
        ThresholdsigmaLabel             matlab.ui.control.Label
        ThresholdsigmaSlider            matlab.ui.control.Slider
        OpacitySliderLabel              matlab.ui.control.Label
        OpacitySlider                   matlab.ui.control.Slider
        MinimumAreapixelsSliderLabel    matlab.ui.control.Label
        MinimumAreapixelsSlider         matlab.ui.control.Slider
        EditField                       matlab.ui.control.NumericEditField
        EditField_2                     matlab.ui.control.NumericEditField
        EditField_3                     matlab.ui.control.NumericEditField
        ROAIgnoredRegionsBluePanel      matlab.ui.container.Panel
        BrushRadiuspixelsLabel          matlab.ui.control.Label
        BrushRadiuspixelsSlider         matlab.ui.control.Slider
        OpacitySlider_2Label            matlab.ui.control.Label
        OpacitySlider_2                 matlab.ui.control.Slider
        DrawToolButtonGroup             matlab.ui.container.ButtonGroup
        OffButton                       matlab.ui.control.RadioButton
        OnAddButton                     matlab.ui.control.RadioButton
        OnSubtractButton                matlab.ui.control.RadioButton
        ClearMaskButton                 matlab.ui.control.Button
        EditField_4                     matlab.ui.control.NumericEditField
        EditField_5                     matlab.ui.control.NumericEditField
        IgnoreborderpixelsSpinnerLabel  matlab.ui.control.Label
        IgnoreborderpixelsSpinner       matlab.ui.control.Spinner
        DisplayingButtonGroup           matlab.ui.container.ButtonGroup
        PreviewButton                   matlab.ui.control.RadioButton
        FinalResultsButton              matlab.ui.control.RadioButton
        ProcessPanel                    matlab.ui.container.Panel
        ProcessButton                   matlab.ui.control.Button
        MinimumDurationframesSliderLabel  matlab.ui.control.Label
        MinimumDurationframesSlider     matlab.ui.control.Slider
        SaveConfigurationButton         matlab.ui.control.Button
        LoadConfigurationButton         matlab.ui.control.Button
        EditField_6                     matlab.ui.control.NumericEditField
    end

    
    properties (Access = private)
        manager % Description
        application % Description
        view
        
        % event listeners that needs to be refreshed when the view changes:
        data_change_listener % Description
        view_data_change_listener
        
        force_update_flag
    end
    
    methods (Access = private)
        
        function handle_data_changed(app)
            
            if ~isvalid(app) || isempty(app.manager) || ~isvalid(app.manager) || isempty(app.view)
                return;
            end
            
            [m_has, ~, m_read,m_flagged] = app.manager.data.shorts();
            [v_has, ~, v_read,v_flagged] = app.view.data.shorts();
            
            if ~v_has("channel") || ~m_has("roa_param")
                return;
            end
            
            if ~m_flagged("roa_tool_flag") && ~v_flagged("roa_tool_flag") && ~app.force_update_flag
                return;
            end
            app.force_update_flag = false;
            
            chan = v_read("channel");
            
            app.OpacitySlider.Value = v_read("roa_alpha");
            app.OpacitySlider_2.Value = v_read("roa_ignore_alpha");
            app.BrushRadiuspixelsSlider.Value = m_read("roa_ignore_brush_size");
                
            app.EditField_3.Value = v_read("roa_alpha");
            app.EditField_4.Value = v_read("roa_ignore_alpha");
            app.EditField_5.Value = m_read("roa_ignore_brush_size");
            
            roa_param_processed = m_read("roa_param_processed");
            if isempty(roa_param_processed)
                app.FinalResultsButton.Enable = false;
            else
                app.FinalResultsButton.Enable = true;
            end
                
            mode = m_read("mode");
            if mode.name == "IDLE"
                app.DrawToolButtonGroup.SelectedObject = app.OffButton;
                app.BrushRadiuspixelsSlider.Enable = 'Off';
            elseif mode.name == "ROA:IGNORE"
                app.BrushRadiuspixelsSlider.Enable = 'On';
                
                roa_ignore_operation = m_read("roa_ignore_operation");
                if roa_ignore_operation == "add"
                    app.DrawToolButtonGroup.SelectedObject = app.OnAddButton;
                elseif roa_ignore_operation == "subtract"
                    app.DrawToolButtonGroup.SelectedObject = app.OnSubtractButton;
                end
            end
            
            % Radio buttons displaying which ROA overlay is shown.
            roa_overlay_mode = v_read("roa_overlay_mode");
            if roa_overlay_mode == "preview"
                app.DisplayingButtonGroup.SelectedObject = app.PreviewButton;
                
                app.MinimumAreapixelsSlider.Enable = 'On';
                app.ThresholdsigmaSlider.Enable = 'On';
                app.MinimumDurationframesSlider.Enable = 'On';
                app.IgnoreborderpixelsSpinner.Enable = 'On';
                
                app.EditField.Enable = 'On';
                app.EditField_2.Enable = 'On';
                app.EditField_6.Enable = 'On';
                
                roa_param = m_read("roa_param");
                
                app.ThresholdsigmaSlider.Value = roa_param(chan).roa_threshold;
                app.MinimumDurationframesSlider.Value = roa_param(chan).roa_min_duration;
                app.MinimumAreapixelsSlider.Value = roa_param(chan).roa_min_size;
                
                app.EditField.Value = roa_param(chan).roa_threshold;
                app.EditField_2.Value = roa_param(chan).roa_min_size;
                app.EditField_6.Value = roa_param(chan).roa_min_duration;
                
                app.IgnoreborderpixelsSpinner.Value = roa_param(chan).roa_ignore_border;
                
                app.SaveConfigurationButton.Enable = 'On';
                
                app.ProcessButton.Enable = 'On';
                
                app.ClearMaskButton.Enable = 'On';
                
                app.OffButton.Enable = 'On';
                app.OnAddButton.Enable = 'On';
                app.OnSubtractButton.Enable = 'On';
                
                roa_param_saved = m_read("roa_param_saved");
                roa_param_processed = m_read("roa_param_processed");
                
                % Only enable the save config button if the saved configs are different from
                % the GUI settings.
                app.SaveConfigurationButton.Enable = 'Off';
                if ~isequal(roa_param,roa_param_saved)
                    app.SaveConfigurationButton.Enable = 'On';
                end

                % Only enable the process button if the saved configs are
                % different from the parameters of the previous run. 
                app.ProcessButton.Enable = 'Off';
                if app.SaveConfigurationButton.Enable == "off" ...
                        && ~isequal(roa_param_processed,roa_param)
                    app.ProcessButton.Enable = 'On';
                end
                
                % Only enable the load config button if the current
                % settings are different from the saved ones and saved
                % settings are available.
                if isequal(roa_param,roa_param_saved) || isempty(roa_param_saved)
                    app.LoadConfigurationButton.Enable = 'Off';
                else
                    app.LoadConfigurationButton.Enable = 'On';
                end
            elseif roa_overlay_mode == "final_results"
                app.DisplayingButtonGroup.SelectedObject = app.FinalResultsButton;
                
                % Sliders. Display the parameters of the processed ROA.
                roa_param_processed = m_read("roa_param_processed");
                if ~isempty(roa_param_processed)
                    app.ThresholdsigmaSlider.Value = roa_param_processed(chan).roa_threshold;
                    app.MinimumDurationframesSlider.Value = roa_param_processed(chan).roa_min_duration;
                    app.MinimumAreapixelsSlider.Value = roa_param_processed(chan).roa_min_size;
                
                    app.IgnoreborderpixelsSpinner.Value = roa_param_processed(chan).roa_ignore_border;
                end
                
                app.MinimumAreapixelsSlider.Enable = 'Off';
                app.ThresholdsigmaSlider.Enable = 'Off';
                app.MinimumDurationframesSlider.Enable = 'Off';
                app.IgnoreborderpixelsSpinner.Enable = 'Off';
                
                app.EditField.Enable = 'Off';
                app.EditField_2.Enable = 'Off';
                app.EditField_6.Enable = 'Off';
                
                app.EditField_5.Enable = 'Off';
                
                app.SaveConfigurationButton.Enable = 'Off';
                app.LoadConfigurationButton.Enable = 'Off';
                
                app.ClearMaskButton.Enable = 'Off';
                
                app.BrushRadiuspixelsSlider.Enable = 'Off';
                
                app.ProcessButton.Enable = 'Off';
                
                app.OffButton.Enable = 'Off';
                app.OnAddButton.Enable = 'Off';
                app.OnSubtractButton.Enable = 'Off';
                
            end
        end
        
        function handle_view_changed(app,sr,ev)
            if ~isvalid(app)
                return;
            end
            % clear old event listeners:
            if ~isempty(app.data_change_listener)
                delete(app.data_change_listener);
            end
            if ~isempty(app.view_data_change_listener)
                delete(app.view_data_change_listener);
            end
    
            app.manager = ev.viewmanager;
            app.force_update_flag = true;

            % the new items could be empty - in whcih case we simply hide
            % outselves:
            if isempty(ev.viewmanager) || isempty(ev.to) || ~ev.viewmanager.data.has("roa_param")
                app.RootFigure.Visible = false;
                app.view = [];
                return
            else
                app.RootFigure.Visible = true;
                % this steams focus, so we must focus top view again:
                ev.viewmanager.focus_top_view();
            
                app.manager.data.assign_changeflag("roa_param","roa_tool_flag");
                app.manager.data.assign_changeflag("roa_ignore_brush_size","roa_tool_flag");
                app.manager.data.assign_changeflag("mode","roa_tool_flag");
                app.manager.data.assign_changeflag("roa_param_processed","roa_tool_flag");
                app.manager.data.assign_changeflag("roa_param_saved","roa_tool_flag");
                
                app.data_change_listener = addlistener(app.manager.data, ...
                   'on_data_changed', @(~, ~) app.handle_data_changed());
            
                app.view = ev.to;
                
                app.view.data.assign_changeflag("roa_alpha","roa_tool_flag");
                app.view.data.assign_changeflag("roa_ignore_alpha","roa_tool_flag");
                app.view.data.assign_changeflag("roa_overlay_mode","roa_tool_flag");
                app.view.data.assign_changeflag("channel","roa_tool_flag");
                
                app.view_data_change_listener = addlistener(ev.to.data, ...
                   'on_data_changed', @(~, ~) app.handle_data_changed());
                
                app.handle_data_changed();
            end
            
        end
    end
    
    methods (Access = public)
        
        function process_roa(app,ts)
        
            begonia.processing.roa.filter_roa(ts);
            
            if isvalid(app)
                % Load the new, lazy roa_mask.
                for ch = 1:ts.channels
                    roa_mask = ts.load_var("roa_mask_ch"+ch,[]);
                    app.manager.data.write("roa_mask_ch"+ch,roa_mask);
                end

                % Transfer the processed parameters to the gui. 
                roa_param_processed = ts.load_var("roa_param_hidden");
                app.manager.data.write("roa_param_processed",roa_param_processed);
                
                roa_traces = ts.load_var("roa_traces");
                app.manager.data.write("roa_traces",roa_traces);
                
                % Change the view to processed
                app.view.data.write("roa_overlay_mode","final_results");
            end
            
        end
    end
    

    % Callbacks that handle component events
    methods (Access = private)

        % Code that executes after component creation
        function startupFcn(app, application)
            addlistener(application, 'on_view_changed',...
                @app.handle_view_changed);
            
            app.application = application;
            
        end

        % Value changing function: ThresholdsigmaSlider
        function ThresholdsigmaSliderValueChanging(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_threshold = event.Value;
            app.manager.data.write("roa_param",roa_param);
        end

        % Value changing function: OpacitySlider
        function OpacitySliderValueChanging(app, event)
            app.view.data.write("roa_alpha",event.Value);
        end

        % Value changing function: MinimumAreapixelsSlider
        function MinimumAreapixelsSliderValueChanging(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_min_size = round(event.Value);
            app.manager.data.write("roa_param",roa_param);
        end

        % Button pushed function: SaveConfigurationButton
        function SaveConfigurationButtonPushed(app, event)
            
            ts = app.manager.data.read("tseries");
            
            % Save the pre-processing and processing parameters in the same
            % struct. 
            roa_pre_param = ts.load_var('roa_pre_param_hidden');
            roa_param = app.manager.data.read("roa_param");
            f = fieldnames(roa_pre_param);
            for ch = 1:ts.channels
                for i = 1:length(f)
                    roa_param(ch).(f{i}) = roa_pre_param(ch).(f{i});
                end
            end
            begonia.logging.log(1,"Saving parameters");
            ts.save_var(roa_param);
            
            app.manager.data.write("roa_param",roa_param);
            app.manager.data.write("roa_param_saved",roa_param);
        end

        % Selection changed function: DisplayingButtonGroup
        function DisplayingButtonGroupSelectionChanged(app, event)
            selectedButton = app.DisplayingButtonGroup.SelectedObject;
            if selectedButton == app.FinalResultsButton
                app.view.data.write("roa_overlay_mode","final_results");
                app.manager.set_mode("IDLE");
            elseif selectedButton == app.PreviewButton
                app.view.data.write("roa_overlay_mode","preview");
            else
                error("oh No")
            end
        end

        % Value changing function: MinimumDurationframesSlider
        function MinimumDurationframesSliderValueChanging(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_min_duration = round(event.Value);
            app.manager.data.write("roa_param",roa_param);
        end

        % Button pushed function: ProcessButton
        function ProcessButtonPushed(app, event)
            
            editor = app.manager.data.read("editor");
            
            ts = app.manager.data.read("tseries");
            
            if isempty(editor) || ts.frame_count < 2000
                app.process_roa(ts);
            else
                disp("The recording is above 2000 frames and is expected to take a while to process. Run the action queue to start the processing.")
                action = xylobium.dledit.Action("Process ROA", @(~,~,~)app.process_roa(ts));
                action.accept_multiple_dlocs = false;
                action.can_execute_without_dloc = true;
                editor.handleQueueRequested(action);
                editor.jobview.show(true);
            end
            
            
        end

        % Selection changed function: DrawToolButtonGroup
        function DrawToolButtonGroupSelectionChanged(app, event)
            selectedButton = app.DrawToolButtonGroup.SelectedObject;
            if selectedButton == app.OffButton
                app.manager.set_mode("IDLE");
            elseif selectedButton == app.OnAddButton
                app.manager.data.write("roa_ignore_operation","add");
                app.manager.set_mode("ROA:IGNORE");
            elseif selectedButton == app.OnSubtractButton
                app.manager.data.write("roa_ignore_operation","subtract");
                app.manager.set_mode("ROA:IGNORE");
            end
        end

        % Value changing function: OpacitySlider_2
        function OpacitySlider_2ValueChanging(app, event)
            app.view.data.write("roa_ignore_alpha",event.Value);
        end

        % Value changing function: BrushRadiuspixelsSlider
        function BrushRadiuspixelsSliderValueChanging(app, event)
            roiman.modes.RoaIgnore.set_brush(app.manager,max(1,round(event.Value)),"set");
        end

        % Button pushed function: ClearMaskButton
        function ClearMaskButtonPushed(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_ignore_mask(:) = false;
            app.manager.data.write("roa_param",roa_param);
        end

        % Button pushed function: LoadConfigurationButton
        function LoadConfigurationButtonPushed(app, event)
            roa_param = app.manager.data.read("roa_param_saved");
            
            % Rewrite the pre-processing parameters. 
            % This is to avoid load previously processed and maybe
            % invalid pre-processing parameters. 
            ts = app.manager.data.read("tseries");
            roa_pre_param = ts.load_var('roa_pre_param_hidden');
            f = fieldnames(roa_pre_param);
            for ch = 1:ts.channels
                for i = 1:length(f)
                    roa_param(ch).(f{i}) = roa_pre_param(ch).(f{i});
                end
            end
            
            app.manager.data.write("roa_param",roa_param);
        end

        % Value changed function: EditField
        function EditFieldValueChanged(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_threshold = event.Value;
            app.manager.data.write("roa_param",roa_param);
        end

        % Value changed function: EditField_3
        function EditField_3ValueChanged(app, event)
            app.view.data.write("roa_alpha",event.Value);
        end

        % Value changed function: EditField_2
        function EditField_2ValueChanged(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_min_size = round(event.Value);
            app.manager.data.write("roa_param",roa_param);
        end

        % Value changed function: EditField_4
        function EditField_4ValueChanged(app, event)
            app.view.data.write("roa_ignore_alpha",event.Value);
        end

        % Value changed function: EditField_5
        function EditField_5ValueChanged(app, event)
            roiman.modes.RoaIgnore.set_brush(app.manager,max(1,round(event.Value)),"set");
        end

        % Value changed function: EditField_6
        function EditField_6ValueChanged(app, event)
            chan = app.view.data.read("channel");
            roa_param = app.manager.data.read("roa_param");
            roa_param(chan).roa_min_duration = event.Value;
            app.manager.data.write("roa_param",roa_param);
        end

        % Value changing function: IgnoreborderpixelsSpinner
        function IgnoreborderpixelsSpinnerValueChanging(app, event)
            roa_param = app.manager.data.read("roa_param");
            channel = app.view.data.read("channel");
            roa_param(channel).roa_ignore_border = event.Value;
            app.manager.data.write("roa_param",roa_param);
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create RootFigure and hide until all components are created
            app.RootFigure = uifigure('Visible', 'off');
            app.RootFigure.Position = [100 100 501 631];
            app.RootFigure.Name = 'ROA Thresholds';

            % Create ROAOverlayRedPanel
            app.ROAOverlayRedPanel = uipanel(app.RootFigure);
            app.ROAOverlayRedPanel.Tooltip = {'Online preview of ROAs given the 2 parameters: threshold and minimum size. '};
            app.ROAOverlayRedPanel.Title = 'ROA Overlay (Red)';
            app.ROAOverlayRedPanel.Position = [13 390 475 172];

            % Create ThresholdsigmaLabel
            app.ThresholdsigmaLabel = uilabel(app.ROAOverlayRedPanel);
            app.ThresholdsigmaLabel.HorizontalAlignment = 'center';
            app.ThresholdsigmaLabel.FontWeight = 'bold';
            app.ThresholdsigmaLabel.Position = [25 67 63 30];
            app.ThresholdsigmaLabel.Text = {'Threshold'; '(sigma)'};

            % Create ThresholdsigmaSlider
            app.ThresholdsigmaSlider = uislider(app.ROAOverlayRedPanel);
            app.ThresholdsigmaSlider.Limits = [0 10];
            app.ThresholdsigmaSlider.ValueChangingFcn = createCallbackFcn(app, @ThresholdsigmaSliderValueChanging, true);
            app.ThresholdsigmaSlider.Tooltip = {'Number of standard deviations to threshold. '};
            app.ThresholdsigmaSlider.FontWeight = 'bold';
            app.ThresholdsigmaSlider.Position = [124 88 272 3];

            % Create OpacitySliderLabel
            app.OpacitySliderLabel = uilabel(app.ROAOverlayRedPanel);
            app.OpacitySliderLabel.HorizontalAlignment = 'center';
            app.OpacitySliderLabel.Position = [32 116 47 22];
            app.OpacitySliderLabel.Text = 'Opacity';

            % Create OpacitySlider
            app.OpacitySlider = uislider(app.ROAOverlayRedPanel);
            app.OpacitySlider.Limits = [0 1];
            app.OpacitySlider.ValueChangingFcn = createCallbackFcn(app, @OpacitySliderValueChanging, true);
            app.OpacitySlider.Position = [123 135 272 3];
            app.OpacitySlider.Value = 0.7;

            % Create MinimumAreapixelsSliderLabel
            app.MinimumAreapixelsSliderLabel = uilabel(app.ROAOverlayRedPanel);
            app.MinimumAreapixelsSliderLabel.HorizontalAlignment = 'center';
            app.MinimumAreapixelsSliderLabel.FontWeight = 'bold';
            app.MinimumAreapixelsSliderLabel.Position = [14 30 88 30];
            app.MinimumAreapixelsSliderLabel.Text = {'Minimum Area'; '(pixels)'};

            % Create MinimumAreapixelsSlider
            app.MinimumAreapixelsSlider = uislider(app.ROAOverlayRedPanel);
            app.MinimumAreapixelsSlider.ValueChangingFcn = createCallbackFcn(app, @MinimumAreapixelsSliderValueChanging, true);
            app.MinimumAreapixelsSlider.Tooltip = {'Minimum area of ROA in a given frame. '};
            app.MinimumAreapixelsSlider.FontWeight = 'bold';
            app.MinimumAreapixelsSlider.Position = [123 45 272 3];

            % Create EditField
            app.EditField = uieditfield(app.ROAOverlayRedPanel, 'numeric');
            app.EditField.ValueChangedFcn = createCallbackFcn(app, @EditFieldValueChanged, true);
            app.EditField.Position = [411 72 49 22];
            app.EditField.Value = 3;

            % Create EditField_2
            app.EditField_2 = uieditfield(app.ROAOverlayRedPanel, 'numeric');
            app.EditField_2.ValueChangedFcn = createCallbackFcn(app, @EditField_2ValueChanged, true);
            app.EditField_2.Position = [411 26 49 22];
            app.EditField_2.Value = 3;

            % Create EditField_3
            app.EditField_3 = uieditfield(app.ROAOverlayRedPanel, 'numeric');
            app.EditField_3.ValueChangedFcn = createCallbackFcn(app, @EditField_3ValueChanged, true);
            app.EditField_3.Position = [411 116 49 22];
            app.EditField_3.Value = 3;

            % Create ROAIgnoredRegionsBluePanel
            app.ROAIgnoredRegionsBluePanel = uipanel(app.RootFigure);
            app.ROAIgnoredRegionsBluePanel.Tooltip = {'Tool to mark regions that will be completely ignored by the analysis. '};
            app.ROAIgnoredRegionsBluePanel.Title = 'ROA Ignored Regions (Blue)';
            app.ROAIgnoredRegionsBluePanel.FontWeight = 'bold';
            app.ROAIgnoredRegionsBluePanel.Position = [14 156 474 224];

            % Create BrushRadiuspixelsLabel
            app.BrushRadiuspixelsLabel = uilabel(app.ROAIgnoredRegionsBluePanel);
            app.BrushRadiuspixelsLabel.HorizontalAlignment = 'center';
            app.BrushRadiuspixelsLabel.Position = [15 117 78 28];
            app.BrushRadiuspixelsLabel.Text = {'Brush Radius'; '(pixels)'};

            % Create BrushRadiuspixelsSlider
            app.BrushRadiuspixelsSlider = uislider(app.ROAIgnoredRegionsBluePanel);
            app.BrushRadiuspixelsSlider.ValueChangingFcn = createCallbackFcn(app, @BrushRadiuspixelsSliderValueChanging, true);
            app.BrushRadiuspixelsSlider.Position = [125 132 272 3];
            app.BrushRadiuspixelsSlider.Value = 1;

            % Create OpacitySlider_2Label
            app.OpacitySlider_2Label = uilabel(app.ROAIgnoredRegionsBluePanel);
            app.OpacitySlider_2Label.HorizontalAlignment = 'center';
            app.OpacitySlider_2Label.Position = [31 162 47 22];
            app.OpacitySlider_2Label.Text = 'Opacity';

            % Create OpacitySlider_2
            app.OpacitySlider_2 = uislider(app.ROAIgnoredRegionsBluePanel);
            app.OpacitySlider_2.Limits = [0 1];
            app.OpacitySlider_2.ValueChangingFcn = createCallbackFcn(app, @OpacitySlider_2ValueChanging, true);
            app.OpacitySlider_2.Position = [123 181 272 3];
            app.OpacitySlider_2.Value = 0.7;

            % Create DrawToolButtonGroup
            app.DrawToolButtonGroup = uibuttongroup(app.ROAIgnoredRegionsBluePanel);
            app.DrawToolButtonGroup.SelectionChangedFcn = createCallbackFcn(app, @DrawToolButtonGroupSelectionChanged, true);
            app.DrawToolButtonGroup.Tooltip = {'The brush will either add to or subtract from the mask of ignored regions when the draw tool is on. '};
            app.DrawToolButtonGroup.Title = 'Draw Tool';
            app.DrawToolButtonGroup.Position = [15 44 444 45];

            % Create OffButton
            app.OffButton = uiradiobutton(app.DrawToolButtonGroup);
            app.OffButton.Text = 'Off';
            app.OffButton.Position = [11 1 38 22];
            app.OffButton.Value = true;

            % Create OnAddButton
            app.OnAddButton = uiradiobutton(app.DrawToolButtonGroup);
            app.OnAddButton.Text = 'On : Add';
            app.OnAddButton.Position = [186 3 70 18];

            % Create OnSubtractButton
            app.OnSubtractButton = uiradiobutton(app.DrawToolButtonGroup);
            app.OnSubtractButton.Text = 'On : Subtract';
            app.OnSubtractButton.Position = [324 1 94 22];

            % Create ClearMaskButton
            app.ClearMaskButton = uibutton(app.ROAIgnoredRegionsBluePanel, 'push');
            app.ClearMaskButton.ButtonPushedFcn = createCallbackFcn(app, @ClearMaskButtonPushed, true);
            app.ClearMaskButton.Position = [348 11 111 22];
            app.ClearMaskButton.Text = 'Clear Mask';

            % Create EditField_4
            app.EditField_4 = uieditfield(app.ROAIgnoredRegionsBluePanel, 'numeric');
            app.EditField_4.ValueChangedFcn = createCallbackFcn(app, @EditField_4ValueChanged, true);
            app.EditField_4.Position = [410 162 49 22];
            app.EditField_4.Value = 3;

            % Create EditField_5
            app.EditField_5 = uieditfield(app.ROAIgnoredRegionsBluePanel, 'numeric');
            app.EditField_5.ValueChangedFcn = createCallbackFcn(app, @EditField_5ValueChanged, true);
            app.EditField_5.Position = [410 113 49 22];
            app.EditField_5.Value = 3;

            % Create IgnoreborderpixelsSpinnerLabel
            app.IgnoreborderpixelsSpinnerLabel = uilabel(app.ROAIgnoredRegionsBluePanel);
            app.IgnoreborderpixelsSpinnerLabel.HorizontalAlignment = 'right';
            app.IgnoreborderpixelsSpinnerLabel.Position = [15 11 119 22];
            app.IgnoreborderpixelsSpinnerLabel.Text = 'Ignore border (pixels)';

            % Create IgnoreborderpixelsSpinner
            app.IgnoreborderpixelsSpinner = uispinner(app.ROAIgnoredRegionsBluePanel);
            app.IgnoreborderpixelsSpinner.ValueChangingFcn = createCallbackFcn(app, @IgnoreborderpixelsSpinnerValueChanging, true);
            app.IgnoreborderpixelsSpinner.Position = [149 11 100 22];

            % Create DisplayingButtonGroup
            app.DisplayingButtonGroup = uibuttongroup(app.RootFigure);
            app.DisplayingButtonGroup.SelectionChangedFcn = createCallbackFcn(app, @DisplayingButtonGroupSelectionChanged, true);
            app.DisplayingButtonGroup.Tooltip = {'Switch between showing a preview and processed results. The preview supports an online adjustment of the parameters. '};
            app.DisplayingButtonGroup.TitlePosition = 'centertop';
            app.DisplayingButtonGroup.Title = 'Displaying:';
            app.DisplayingButtonGroup.Position = [132 572 232 49];

            % Create PreviewButton
            app.PreviewButton = uiradiobutton(app.DisplayingButtonGroup);
            app.PreviewButton.Text = 'Preview';
            app.PreviewButton.Position = [11 3 64 22];
            app.PreviewButton.Value = true;

            % Create FinalResultsButton
            app.FinalResultsButton = uiradiobutton(app.DisplayingButtonGroup);
            app.FinalResultsButton.Text = 'Final Results';
            app.FinalResultsButton.Position = [133 3 90 22];

            % Create ProcessPanel
            app.ProcessPanel = uipanel(app.RootFigure);
            app.ProcessPanel.Title = 'Process';
            app.ProcessPanel.Position = [13 12 475 134];

            % Create ProcessButton
            app.ProcessButton = uibutton(app.ProcessPanel, 'push');
            app.ProcessButton.ButtonPushedFcn = createCallbackFcn(app, @ProcessButtonPushed, true);
            app.ProcessButton.Tooltip = {'Process ROAs which enables viewing the final results. Disabled when the saved parameters match the processed parameters. '};
            app.ProcessButton.Position = [333 12 107 22];
            app.ProcessButton.Text = 'Process';

            % Create MinimumDurationframesSliderLabel
            app.MinimumDurationframesSliderLabel = uilabel(app.ProcessPanel);
            app.MinimumDurationframesSliderLabel.HorizontalAlignment = 'center';
            app.MinimumDurationframesSliderLabel.FontWeight = 'bold';
            app.MinimumDurationframesSliderLabel.Position = [13 69 111 30];
            app.MinimumDurationframesSliderLabel.Text = {'Minimum Duration'; '(frames)'};

            % Create MinimumDurationframesSlider
            app.MinimumDurationframesSlider = uislider(app.ProcessPanel);
            app.MinimumDurationframesSlider.ValueChangingFcn = createCallbackFcn(app, @MinimumDurationframesSliderValueChanging, true);
            app.MinimumDurationframesSlider.Tooltip = {'Minimum duration of ROAs. The result of this parameters are only visible when viewing the final results. '};
            app.MinimumDurationframesSlider.FontWeight = 'bold';
            app.MinimumDurationframesSlider.Position = [129 86 272 3];

            % Create SaveConfigurationButton
            app.SaveConfigurationButton = uibutton(app.ProcessPanel, 'push');
            app.SaveConfigurationButton.ButtonPushedFcn = createCallbackFcn(app, @SaveConfigurationButtonPushed, true);
            app.SaveConfigurationButton.Tooltip = {'Save the currently displayed parameters. Only enabled when the current parameters are different from the saved parameters.'};
            app.SaveConfigurationButton.Position = [179 12 118 22];
            app.SaveConfigurationButton.Text = 'Save Configuration';

            % Create LoadConfigurationButton
            app.LoadConfigurationButton = uibutton(app.ProcessPanel, 'push');
            app.LoadConfigurationButton.ButtonPushedFcn = createCallbackFcn(app, @LoadConfigurationButtonPushed, true);
            app.LoadConfigurationButton.Tooltip = {'Load previously saved parameters. Disabled when current parameters match the saved parameters. '};
            app.LoadConfigurationButton.Position = [16 12 118 22];
            app.LoadConfigurationButton.Text = 'Load Configuration';

            % Create EditField_6
            app.EditField_6 = uieditfield(app.ProcessPanel, 'numeric');
            app.EditField_6.ValueChangedFcn = createCallbackFcn(app, @EditField_6ValueChanged, true);
            app.EditField_6.Position = [411 67 49 22];
            app.EditField_6.Value = 3;

            % Show the figure after all components are created
            app.RootFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = RoaThreshold(varargin)

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.RootFigure)

            % Execute the startup function
            runStartupFcn(app, @(app)startupFcn(app, varargin{:}))

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.RootFigure)
        end
    end
end