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);
Related
I have code that uses Wolff's Algorithm to simulate the XY Model in MATLAB and I want to implement a pcolor/color map to demonstrate each spin according to their angles across the system. But I want it to be live and changing as the angles change.
Any idea how to do this?
This is an example of how I want it to look https://i.stack.imgur.com/aSp7s.png
If you save each snapshot of the lattice in a cell array A{t}, you can use the following function to view and save it as a video (if fileName is not empty, the function saves an mp4 video).
Another option is to adapt the function view_lattice to run your simulation (which, honestly, I wouldn't recommend, for performance issues). I will mark where you should edit for doing a "live" simulation
This is at least MATLAB R2019b (although it may be compatible with earlier versions, but no guarantee).
File view_lattice.m
function view_lattice(A,fileName)
% for a 'live' simulation, you will have to remove A from the input
% parameters and add the ones you need for the XY Wolff algorithm,
% which will be used to calculate each configuration A in the time loop below
% you will also need to remove the assert statements for 'live' simulation
%
% otherwise, you save snapshots from your simulation
% and use this function as is
%
% A -> A{k}[m,n] snapshot k containing the angles of spins in lattice site at row m and col n
% fileName -> if contains string, then records a video with the snapshots and name it with this string
assert(iscell(A) && all(cellfun(#(a)isnumeric(a) && ismatrix(a),A)),'A must be cell of numeric matrices');
assert(ischar(fileName),'fileName must be either an empty char or contain a file name');
recordVideo = ~isempty(fileName);
if recordVideo
vw = setup_video(fileName);
else
vw = [];
end
% setting some default axis properties to speed-up plotting
set(0,'DefaultAxesPlotBoxAspectRatio',[1 1 1],'DefaultAxesDataAspectRatioMode','manual','DefaultAxesDataAspectRatio',[1,1,1],'DefaultAxesNextPlot','replace');
fh = figure;
ax=axes;
for t = 1:numel(A) % for 'live' simulation, this loop should be the time loop
% here you calculate the new configuration A
% and call the function below with A instead of A{t}
vw = record_frame(vw,fh,ax,A{t},t,recordVideo);
end
% any video to close?
if recordVideo
vw.close();
end
end
function vw = record_frame(vw,fh,ax,A,t,recordVideo)
imagesc(ax,A);
title(ax,sprintf('snapshot %g',t)); % if you want, y
axis(ax,'square');
daspect(ax,[1,1,1]);
pause(0.01);
if recordVideo
vframe = getframe(fh);
vw.writeVideo(vframe);
end
end
function vw = setup_video(fileName)
vid_id = num2str(rand,'%.16g');
vid_id = vid_id(3:6);
vid_id = [fileName,'_',vid_id];
% Initialize video
vw = VideoWriter([vid_id,'.mp4'], 'MPEG-4'); %open video file
vw.Quality = 100;
vw.FrameRate = 16;
vw.open();
end
Test script: test.m
clearvars
close all
A = cell(1,30);
for t = 1:numel(A)
% creating a sequence of random snapshots only for illustration
A{t} = rand(20,20);
end
% viewing the animation and saving it as a video with name test
view_lattice(A,'test');
Output
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"
I am working in image processing task using matlab,I have made a slider inside a dialog to apply and update gaussian blur filter on an image which is shown in axes
but it can not apply it,it shows that error
Error in ==> MatlabTestProject>name at 392
axes(handles.axes4);
??? Error while evaluating uicontrol Callback
this is my code
function mygui()
out = dialog('WindowStyle', 'normal', 'Name', 'My Dialog','Resize','off');
hSlider = uicontrol('Style','slider','Min',3,'Max',15,'Value',3,'Callback',#gaussian_blur);
%hListener = addlistener(hSlider,'Value','PostSet',#(s,e) disp('hi'));
function gaussian_blur(s,e,handles)
global imag;
slider_value = get(s,'Value');
slider_value=round(slider_value);
%display(slider_value);
%disp('hello')
%create filter
%sliderValueTxt=num2str(slider_value);
%set(handles.kSizeValueText ,'String',sliderValueTxt);
h = fspecial('gaussian', slider_value,0.5);
imag=imfilter(imag,h,'conv');
axes(handles.axes4);
imshow(imag)
% --------------------------------------------------------------------
function gaussianBlur_Callback(hObject, eventdata, handles)
% hObject handle to gaussianBlur (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
gaussian_dialog_Gui();
You're actually really close! All you need to do is include handles as an argument to the function gaussian_blur when defining the slider's listener object callback.
i.e replace this line: (it looks like a test line though)
hListener = addlistener(hSlider,'Value','PostSet',#(s,e) disp('hi'));
with something like this:
hListener = addlistener(hslider,'Value','PostSet',#(s,e) gaussian_blur(handles));
Just to be sure it works I created a test GUI programmatically using an axes and a slider and it works very well! Actually I changed a bit the filter to see an effect on my test image, but it should work as well in your case:
function GaussianSlider()
clear
clc
close all
handles.Image = imread('peppers.png');
handles.fig = figure('Position',[500 500 600 600],'Units','pixels');
handles.axes1 = axes('Units','pixels','Position',[50 100 400 400]);
handles.slider = uicontrol('Style','slider','Position',[50 50 400 20],'Min',3,'Max',15,'Value',3);%// I commented this for the purpose of demonstration. 'Callback',#gaussian_blur(handles));
%// That's the important part: add 'handles' as input argument to
%// gaussian_blur.
handles.Listener = addlistener(handles.slider,'Value','PostSet',#(s,e) gaussian_blur(handles));
imshow(handles.Image,'Parent',handles.axes1);
guidata(handles.fig);
function gaussian_blur(handles)
slider_value = round(get(handles.slider,'Value'));
%// I modified a bit the filter to see the effect
h = fspecial('gaussian',slider_value,slider_value);
handles.Image=imfilter(handles.Image,h,'conv');
axes(handles.axes1);
imshow(handles.Image)
end
end
If we look at 2 screenshots (i.e. 2 different slider positions):
and after moving the slider:
That's it! Hope that helps! If something is unclear please tell me.
Oh and in case you do not know: The actual callback of a slider is only executed when you release the button or press either arrow. As long as you hold the slider and move it, it's only the listener's callback that will be executed.
I would like to know if some of you know of a way to automatically title plots with the name of the object plotted
so that for intance when I plot supermatrix(5:10,:,2:3)
the title (or the legend ..) on the plot says "supermatrix(5:10,:,2:3)"
thanks
Is this for debugging purposes? If not then I suggest you tell us your overall motivation because someone might be able to suggest a more robust method, but this might get you started:
vname = #(x)inputname(1); %//from here: https://www.mathworks.com/matlabcentral/newsreader/view_thread/251347
plot(supermatrix(5:10,:,2:3))
title(vname(supermatrix))
Although to be honest I cannot imagine why this would ever be useful
I think this does what you want and remains pretty flexible:
function h = plotwithtitle( plotstring, varargin )
argstoplot = evalin('caller', ['{', plotstring, '}']);
h = plot( argstoplot{:}, varargin{:} );
title(plotstring);
end
The following examples all work for me:
supermatrix=rand(10,10);
x=1:10;
y=rand(1,10);
plotwithtitle('supermatrix');
plotwithtitle('supermatrix(5:10,:)');
plotwithtitle('x, y');
plotwithtitle('x, y', '--r');
plotwithtitle('1:10', 'r');
plotwithtitle('rand(1,10)');
I've modified the function dfig originally created by F.Moisy for creating docked figures in order to have the command used for plotting show up in the figure name.
The idea is to read the last command in the command history and use that to generate the figure title.
function hh = dfig(varargin)
%DFIG Create docked figure window
% DFIG, by itself, creates a new docked figure window, and returns its
% handle.
%
% DFIG(H) makes H the current figure and docks it. If Figure H does not
% exist, and H is an integer, a new figure is created with handle H.
%
% DFIG(H, name, value,...) reads additional name-value pairs. See
% doc(figure) for available otions.
%
% DFIG will parse the command line input and use the text following dfig
% as figure name. E.g. calling dfig,plot(x(1:3),y(2:2:end)) results in
% the name "plot(x(1:3),y(2:2:end))"
% F. Moisy, moisy_at_fast.u-psud.fr
% Revision: 1.00, Date: 2007/09/11
% Modified (a lot) by Jonas
if nargin==0
h=figure; % create a new figure
else
% call figure with varargin
figure(varargin{:})
h = gcf;
end
if ~any(strcmp('name',varargin(1:2:end)))
% if no name has been supplied: try to use function call
javaHistory=com.mathworks.mlservices.MLCommandHistoryServices.getSessionHistory;
if ~isempty(javaHistory)
lastCommand = javaHistory(end).toCharArray';
funCall = regexp(lastCommand,'dfig\s*[,;]\s*(.*)$','tokens','once');
else
funCall = [];
end
if ~isempty(funCall)
if isnumeric(h)
set(h,'Name',[num2str(h),': ',funCall{1}],'NumberTitle','off')
else % HG2
h.Name = sprintf('%i: %s',h.Number,funCall{1});
h.NumberTitle = 'off';
end
end
end
set(h,'WindowStyle','docked'); % dock the figure
if nargout~=0 % returns the handle if requested
hh=h;
end
When working with Matlab GUIs, is there a way to place a "hint" in an Edit Text box? That is, text that will disappear once the user starts typing? I've used similar functionalities in Android, but I'm not as familiar with other GUIs, so I'm not sure how widespread this functionality is.
This is possible in Matlab, but you have to define a custom MouseClickCallback, which is only accessible using findjobj by Yair Altman (download it from the Matlab File Exchange using the link and save it somewhere on your Matlab path).
Since I like the idea, I have written a function that conveniently takes care of all this. It creates a grayed-out, italic help text that disappears once you click the edit box.
function setInitialHelp(hEditbox,helpText)
%SETINITIALHELP adds a help text to edit boxes that disappears when the box is clicked
%
% SYNOPSIS: setInitialHelp(hEditbox,helpText)
%
% INPUT hEditbox: handle to edit box. The parent figure cannot be docked, the edit box cannot be part of a panel.
% helpText: string that should initially appear as help. Optional. If empty, current string is considered the help.
%
% SEE ALSO uicontrol, findjobj
%
% EXAMPLE
% fh = figure;
% % define uicontrol. Set foregroundColor, fontAngle, before
% % calling setInitialHelp
% hEditbox = uicontrol('style','edit','parent',fh,...
% 'units','normalized','position',[0.3 0.45 0.4 0.15],...
% 'foregroundColor','r');
% setInitialHelp(hEditbox,'click here to edit')
%
% check input
if nargin < 1 || ~ishandle(hEditbox) || ~strcmp(get(hEditbox,'style'),'edit')
error('please supply a valid edit box handle to setInitialHelp')
end
if nargin < 2 || isempty(helpText)
helpText = get(hEditbox,'string');
end
% try to get java handle
jEditbox = findjobj(hEditbox,'nomenu');
if isempty(jEditbox)
error('unable to find java handle. Figure may be docked or edit box may part of panel')
end
% get current settings for everything we'll change
color = get(hEditbox,'foregroundColor');
fontAngle = get(hEditbox,'fontangle');
% define new settings (can be made optional input in the future)
newColor = [0.5 0.5 0.5];
newAngle = 'italic';
% set the help text in the new style
set(hEditbox,'string',helpText,'foregroundColor',newColor,'fontAngle',newAngle)
% add the mouse-click callback
set(jEditbox,'MouseClickedCallback',#(u,v)clearBox());
% define the callback "clearBox" as nested function for convenience
function clearBox
%CLEARBOX clears the current edit box if it contains help text
currentText = get(hEditbox,'string');
currentColor = get(hEditbox,'foregroundColor');
if strcmp(currentText,helpText) && all(currentColor == newColor)
% delete text, reset color/angle
set(hEditbox,'string','','foregroundColor',color,'fontAngle',fontAngle)
else
% this is not help text anymore - don't do anything
end
end % nested function
end % main fcn