MATLAB - AppDesigner: Interrupt a loop with GUI - matlab

I have created a GUI which computes a trajectory based on an external .m file.
When the user press the 'Calculate' button, the external .m function file gets called via the callback function of the button:
% calculateBtn button pushed function
function calculate(app)
numSteps = app.stepSlider.Value;
app.omega = app.omegaSpin.Value;
app.phid = app.phi.Value;
app.x0 = app.x0Spin.Value;
app.y0 = app.y0Spin.Value;
app.u0 = app.u0Spin.Value;
app.v0 = app.v0Spin.Value;
set(app.calculateBtn, 'Enable', 'off')
set(app.showBtn, 'Enable', 'off')
[app.Xc, app.Xi, app.C, T, f]=coriolis_traj(app.x0, app.y0, app.u0, app.v0, app.phid, numSteps);
app.fEdit.Value = num2str(f);
app.tEdit.Value = num2str(T);
set(app.calculateBtn, 'Enable', 'on')
if length(app.Xc)>1
set(app.showBtn, 'Enable', 'on')
else
set(app.showBtn, 'Enable', 'off')
end
end
The external file consists of the main loop of the computations.
while 1
% Counters
i = i + 1;
t = t + Dt;
theta = theta + Omega * Dt;
% Parcel's position
% on the inertial frame
x1 = x0 + Dt*u0;
y1 = y0 + Dt*v0;
% Parcel's position translated to the
% rotating frame
xc1 = x1*cos(theta)+y1*sin(theta);
yc1 = x1*sin(theta)+y1*cos(theta);
x(i) = x1 ; y(i) = y1;
xc(i) = xc1 ; yc(i) = yc1;
x0 = x1 ; y0 = y1;
[in] = inpolygon(xc,yc,xv,yv);
if ~in(i) > 0
break;
end
end
I want to stop the computation and clear the computed arrays when the user changes any of the values in 'Controls' panel, or when the button 'Break' is pushed.
How could I code this?

The best solution I can figure out is to bring your while loop inside your GUI callback. The inner code of your while loop can be kept on a separate, external file, but bringing in the loop will give you full control over it and will make it easier to interrupt. The only constraint is that it must be make less "tight"... it must contain a small pause (better if followed by a drawnow() call) so that the main GUI thread can have the time to process the application messages.
And here is the code of your callbacks:
% Computation Callback
function Button1_Callback(obj,evd,handles)
obj.Enable = 'off'; % disable this button
handles.Button2.Enable = 'on'; % enable the interruption button
handles.stop = false; % the control variable for interruption
while (true)
% call your external function for running a computational cycle
res = myExternalFunction(...);
% refresh the application handles
handles = guidata(obj);
% interrupt the loop if the variable has changed
if (handles.stop)
break;
end
% this allows the loop to be interrupted
pause(0.01);
drawnow();
end
obj.Enable = 'on';
end
% Interruption Callback
function Button2_Callback(obj,evd,handles)
obj.Enable = 'off'; % disable this button
handles = guidata(obj);
handles.stop = true;
end

There is no way in MATLAB to interrupt a function by another function, e.g. say programmatically injecting a CTRL-C into another running function, without modifying the to-be-interrupted function.
The closest you can get is by modifying the simulator code to regularly perform a callback. This is how I integrated by simulation code into a GUI. IMO it is a clean solution and also works with MEX files.
Think of it as a progress callback for your simulator code.
It could be regularly (e.g. every second, or at completion of the i-th step) called by the simulator with some percentage parameter to indicate the degree of completion.
If you modify the simulator code to stop simulating in case the callback returns false, you achieve what you want. At the same time this is minimum invasive in your simulator code. Supply a dummy callback and it will run stand alone.
GUI pseudocode:
function button_cancel_Callback(hObject, eventdata, handles)
global cancel_pressed;
cancel_pressed = true;
function dostop = callback( progress, handles )
<show progress>( handles.<XXX>, progress );
global cancel_pressed;
if cancel_pressed
dostop = true;
else
dostop = false;
end
function button_run_simulation_Callback(hObject, eventdata, handles)
global cancel_pressed;
cancel_pressed = false;
<simulator function>( ..., #callback, handles )
SIMULATOR pseudocode:
function <simulator function>( ..., callback, callback_param )
while ... % main loop
if ~isempty(callback) && ~callback( progress, callback_param )
error("canceled-by-user") % could also be something more elaborate
end
return "simulation completed"

Related

Operating on while loop outside the loop matlab

i've got problems with my Nucleo.
Im using Matlab to cooperate with my Nucleo board.
I want to build programmatically GUI with buttons, figures etc. I'm going to put whole functionality in while loop. And now there's my question.
Is there any posibility to put whole code in while loop and operate it through callback functions outside the loop?
For example: Im my loop i want to send some data to Nucleo on btn1 press, and i want to stop it on btn2 press(Of course if statements for btns). Is there possibility to do it by changing the button values or something like that(Flags etc.)?
I don't want use global variables.
Yes it's possible...
It's not the best programming pattern, but it's very convenient approach for small software projects.
I recommend you to use MATLAB App Designer.
App Designer uses OOP programming model, that makes it simpler to pass data (without using global variables, and without complicated solutions that are used with GUIDE).
Here is a sample instructions:
Start App Designer - execute appdesigner from command line.
Add two buttons, and two labels (add anything else later).
Add callbacks: for start button set flag to true and in stop button to false.
The syntax is app.is_sending = true (when is_sending is a property of app).
Change from Design View to Code View.
Add private properties: loop_counter = 0;, sending_counter = 0;, is_sending = false;.
Add callback - select startupFcn callback function.
Put your while loop in the startupFcn callback.
Use while isvalid(app)
Place your while loop functionality inside the while loop.
Place your sending code with if (app.is_sending)...
Very important: at the end of the loop call pause function.
You must execute pause or drawnow to allow callbacks to be responsive.
When using App Designer, some code is generated automatically, and cannot be modified in Code View.
The following code sample includes both generated code, and customized code:
classdef app1 < matlab.apps.AppBase
% Properties that correspond to app components
properties (Access = public)
UIFigure matlab.ui.Figure
StartSendingButton matlab.ui.control.Button
StopSendingButton matlab.ui.control.Button
Sending0Label matlab.ui.control.Label
LoopCounter0Label matlab.ui.control.Label
end
properties (Access = private)
loop_counter = 0 % Count while loop iteration
sending_counter = 0; %Count only while "sending data"
is_sending = false; %Flag: if value is true, assume "sending"
end
% Callbacks that handle component events
methods (Access = private)
% Code that executes after component creation
function startupFcn(app)
%While loop: loop until app is not valid.
while isvalid(app)
%Place your while loop functionality here:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
app.loop_counter = app.loop_counter + 1; %Increase loop counter
app.LoopCounter0Label.Text = ['Loop Counter: ', num2str(app.loop_counter)]; %Update loop coouner text lable.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Place your sending code here:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if (app.is_sending)
app.sending_counter = app.sending_counter + 1; %Increase sending counter
app.Sending0Label.Text = ['Sending: ', num2str(app.sending_counter)]; %Update sending coouner text lable.
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%% Important %%%%
%Short pause must be used in order to allow buttons callbacks to be responsive (you can also call drawnow).
pause(0.01)
%%%% Important %%%%
end
end
% Button pushed function: StartSendingButton
function StartSendingButtonPushed(app, event)
app.is_sending = true; %Set flag to true when button is pressed.
end
% Button pushed function: StopSendingButton
function StopSendingButtonPushed(app, event)
app.is_sending = false; %Set flag to false when button is pressed.
end
end
% Component initialization
methods (Access = private)
% Create UIFigure and components
function createComponents(app)
% Create UIFigure and hide until all components are created
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100 100 440 304];
app.UIFigure.Name = 'UI Figure';
% Create StartSendingButton
app.StartSendingButton = uibutton(app.UIFigure, 'push');
app.StartSendingButton.ButtonPushedFcn = createCallbackFcn(app, #StartSendingButtonPushed, true);
app.StartSendingButton.BackgroundColor = [0.3922 0.8314 0.0745];
app.StartSendingButton.FontSize = 16;
app.StartSendingButton.Position = [33 197 130 49];
app.StartSendingButton.Text = 'Start Sending';
% Create StopSendingButton
app.StopSendingButton = uibutton(app.UIFigure, 'push');
app.StopSendingButton.ButtonPushedFcn = createCallbackFcn(app, #StopSendingButtonPushed, true);
app.StopSendingButton.BackgroundColor = [0.851 0.3255 0.098];
app.StopSendingButton.FontSize = 16;
app.StopSendingButton.Position = [33 129 130 49];
app.StopSendingButton.Text = 'Stop Sending';
% Create Sending0Label
app.Sending0Label = uilabel(app.UIFigure);
app.Sending0Label.FontSize = 16;
app.Sending0Label.Position = [229 203 151 37];
app.Sending0Label.Text = 'Sending: 0';
% Create LoopCounter0Label
app.LoopCounter0Label = uilabel(app.UIFigure);
app.LoopCounter0Label.FontSize = 16;
app.LoopCounter0Label.Position = [37 44 193 37];
app.LoopCounter0Label.Text = 'Loop Counter: 0';
% Show the figure after all components are created
app.UIFigure.Visible = 'on';
end
end
% App creation and deletion
methods (Access = public)
% Construct app
function app = app1
% Create UIFigure and components
createComponents(app)
% Register the app with App Designer
registerApp(app, app.UIFigure)
% Execute the startup function
runStartupFcn(app, #startupFcn)
if nargout == 0
clear app
end
end
% Code that executes before app deletion
function delete(app)
% Delete UIFigure when app is deleted
delete(app.UIFigure)
end
end
end
You can copy and paste the entire code into an m file, to see how it works.
The code sample is not sending any data (and not related to Nucleo board), it's just advancing counters.
Here is how the sample application interface looks like:

Performance loss using Matlab parfeval?

I have a relatively big file and I would like to make an interactive plot using GUIDE which plots a segment of the file and upon scrollEvent the window gets updated. (the data is appropriately replotted).
To this end a buffer of size 4 times that of the window is fetched, and when the window's center reaches 75% of the buffer, the buffer is refetched so that the window is at the center of the new buffer.
The problem with this is that when using fread it of course blocks until done.(which is visually disturbing)
What I tried is to create a separate readData function which gets called with f = parfeval(gcp(),#readData,1,datafile) and on [~,buffer]=fetchNext(f) the buffer is persisted on handles.datafile.
Problem: Even though in my example (see below) readData only gets called once, every subsequent plotting is incredibly slow (10x the runtime of when not using parfeval).
Example:
./test.dat was generated by dd if=/dev/urandom of=test.dat bs=100000 count=1024
**This is the synchronous code to get the asynchronous one, comment out the parfeval and fetchNext function definitions shadowing the parallel ones.
function test()
f = figure('Toolbar','none','Menubar','none');
ax = axes(f);
data = struct( 'file','./test.dat',...
'buffer',[],...
'window',[0,100000]);
p = gcp();
data.fileReader = parfeval(p,#readData,1,data);
handles = struct('axes',ax,'figure',f,'data',data);
guidata(f,handles);
set(f,'WindowScrollWheelFcn',#scrollHandler);
end
function f = parfeval(~,fun,~,in1)
f = struct('output',fun(in1));
end
function [id,out] = fetchNext(f,varargin)
id = 1;
out = f.output;
end
function buffer = readData(data)
file = fopen(data.file,'r');
buffer = fread(file,[128,400000],'int16');
fclose(file);
end
function scrollHandler(hObject, eventdata, ~)
handles = guidata(hObject);
ax = handles.axes;
C = get (ax, 'CurrentPoint');
XLim = get(ax, 'XLim');
YLim = get(ax, 'YLim');
if XLim(1)<=C(1,1) && XLim(2)>=C(1,1) && ...
YLim(1)<=C(1,2) && YLim(2)>=C(1,2)
tic;
window = handles.data.window;
if isstruct(handles.data.fileReader) || handles.data.fileReader ~= -1
fprintf('Reading from file\n');
[~,handles.data.buffer] = fetchNext(handles.data.fileReader);
handles.data.fileReader = -1;
end
if eventdata.VerticalScrollCount > 0 && window(2) < 399000
window = window + 1000;
else
if window(1) > 1000
window = window - 1000;
end
end
handles.data.window = window;
guidata(hObject, handles);
plot(ax,handles.data.buffer(65,window(1)+1:window(2)));
toc
end
end
This is indeed very mysterious. The problem seems to be some sort of interaction with guidata and the parallel.FevalFuture returned by parfeval. As a workaround, you can simply avoid overwriting that element of the struct. In other words, remove the line
handles.data.fileReader = -1;
You'll also need to check handles.data.fileReader.Read to make sure you don't attempt to call fetchNext again on an already-completed future.

How can I check mouse events as a background process in Matlab?

I am using Matlab to develop an application which performs several mathematical operations, whose parameters can be changed when the mouse is clicked, as in the example below.
while time<endtime
calculate_manythings;
if ~mod(time,checkmouse)
mouseinput_timeout(timemouse, gca);
change_calculation_parameters;
end
time=time+1;
end
At the moment, I am pausing the operations periodically to check for mouse events, but this is slow and unpractical. How can I monitor these continually and run the code at the same time? Could I make the mouse event checks a background process using parfeval, for example?
Many thanks,
Marta
you can use callback functions. here I used 'ButtonDownFcn':
timeinterval = 1; % seconds between mouse clicks
% generate axes with callback function
h = plot(rand(1,2),'LineWidth',6);
set(gca,'ButtonDownFcn',#callback);
% reset Tag and time
h.Tag = '';
tic;
while true
drawnow;
if strcmp(h.Tag,'Pressed') % if pressed
t = toc; % check time passed
if t >= timeinterval
% change parameters
disp('Pressed');
h.Color = rand(1,3);
% reset timer
tic;
end
% reset Tag
h.Tag = '';
end
end
and the callback function is:
function callback(src,~)
src.Children(1).Tag = 'Pressed';
end

Update pushbutton string using counter when another pushbutton is active

I am setting the handles in the opening function:
function Select_A_B_OpeningFcn(hObject, eventdata, handles, varargin)
handles.output = hObject;
handles.string = '';
new_count = 1;
set(handles.counter,'String',num2str(new_count));
if isempty(varargin)
varargin{1} = 1;
varargin{2} = 1;
end
A = {'Apple';'Orange';'Bag';'Cowboy'};
handles.pushbutton1text = A;
new_count = str2double(handles.counter.String);
handles.pushbutton1 = handles.pushbutton1text(new_count);
guidata(hObject, handles);
Then I am trying to change the handles to pushbutton1 when the pushbutton tagged as 'Next' is pushed:
function next_Callback(hObject, eventdata, handles)
current_count = str2double(get(handles.counter, 'String'));
new_count = current_count+1;
set(handles.counter,'String',new_count);
set(handles.pushbutton1,'String',get(handles.counter,'string');
guidata(hObject, handles);
I get the following error when I try to set the handles to pushbutton1:
Error using set
Conversion to double from cell is not possible.
I have tried several ways to fix the error, but no success yet. What am I doing wrong?
Here is a programmatic GUI which does what you want and is, in my opinion, much simpler to understand/debug. You can easily implement this using GUIDE; everything that comes before the pushbutton's callback can be put into the GUI Opening_Fcn.
I added comments in the code; if something is unclear please tell me.
function DisplayFruits
clear
clc
hFig = figure('Position',[200 200 300 300]);
handles.A = {'Apple';'Orange';'Bag';'Cowboy'};
handles.counter = 1;
%// Counter text
handles.CounterTitle = uicontrol('Style','text','Position',[50 200 60 20],'String','Counter');
handles.CounterBox = uicontrol('Style','text','Position',[130 200 60 20],'String','1');
%// Content of A
handles.TextTitle = uicontrol('Style','text','Position',[50 170 60 20],'String','Content');
handles.TextBox = uicontrol('Style','text','Position',[130 170 60 20],'String',handles.A{handles.counter});
%// Pushbutton to increment counter/content
handles.PushButton = uicontrol('Style','push','Position',[130 100 80 40],'String','Update counter','Callback',#(s,e) UpdateCallback);
guidata(hFig,handles);
%// Pushbutton callback
function UpdateCallback
%// Update counter
handles.counter = handles.counter + 1;
%// If maximum value possible, set back to 1.
if handles.counter == numel(handles.A)+1;
handles.counter = 1;
end
%// Update text boxes
set(handles.CounterBox,'String',num2str(handles.counter));
set(handles.TextBox,'String',handles.A{handles.counter});
guidata(hFig,handles);
end
end
Sample screenshot of the GUI:
As the user presses the button, the counter is incremented and the "content box" is updated.
Hope this helps!
As a side note, I think the error you got above was due to this command:
handles.pushbutton1 = handles.pushbutton1text(new_count);
here you assigned a string to the pushbutton but later you try to modify its String property and Matlab does not like it.

Updating MATLAB waitbar fails after first update

I am having a problem updating a waitbar in a MATLAB GUI.
I created a simple example that works as expected.
steps = 5;
hWaitBar = waitbar(0, 'Testing...');
for i = 1:steps
waitbar(i/steps, hWaitBar);
pause(1);
end
close(hWaitBar);
However when I use this construction in the GUI...
numSteps = %calculated
hWaitBar = waitbar(0, 'Processing...');
if %conditional
for i = 1:numSteps
waitbar(i/numSteps, hWaitBar)
% additional processing
end
else %conditional
% additional processing
end
close(hWaitBar);
...the waitbar only displays correctly for the first for loop iteration.
The second interation fails with the execption:
Error using waitbar(109)
Improper arguments for waitbar.
I have verified that the waitbar progress value does not exceed 1.
I have verified that the waitbar is not being closed until outside the if/else loop.
I found the solution -- I was misuing handle graphics.
In my original code:
numSteps = %calculated
hWaitBar = waitbar(0, 'Processing...');
if %conditional
for i = 1:numSteps
waitbar(i/numSteps, hWaitBar)
% additional processing
% *** call to imagesc caused the error
end
else %conditional
% additional processing
end
close(hWaitBar);
Using the debugger, I saw that the waitbar became the current figure and imagesc tried to plot to it instead of the axis on the main form. Setting the appropriate figure as current immediately before the referencing calls yielded proper behavior.
Corrected code:
numSteps = %calculated
hForm = gcf; % save the handle of the main form
hWaitBar = waitbar(0, 'Processing...');
if %conditional
for i = 1:numSteps
% set the waitbar to be the current figure before it is updated
% note: this syntax will ensure window order will be preserved
% with waitbar on top
set(0, 'CurrentFigure', hWaitBar);
waitbar(i/numSteps, hWaitBar);
% additional processing
set(0, 'CurrentFigure', hForm);
imagesc(...);
% more processing
end
else %conditional
% additional processing
end
close(hWaitBar);