Mouse click callback in MATLAB for line plot - matlab

If I want to read location of the mose click on some image I can do it using callback action.
function[]=FooBar
Img=imshow(FooMatrix,'callback',#(s,e)ImageClickCallback());
function ImageClickCallback(objectHandle,~)
axesHandle = get(objectHandle,'Parent');
coordinates = get(axesHandle,'CurrentPoint');
coordinates = round(coordinates(1,1:2))
end
end
It works well for images but now I have axes with lines only. I tried to set the callback routine to the appropriate axes or the line but I got the error message
Error while evaluating uicontrol Callback
Error using hg.figure/set
The name 'callback' is not an accessible property for an instance of class
'figure'.
Background:
I am trying to create GUI with line plot, say y=f(x) enabling user to select points on the line. Idea is to read the [x,y] coordinates of the mouse click and highlight the point [f'(y),y] or [x,f(x)], where f' is inverse function to f. Something like Data Cursor function does.

callback isn't a valid property of a line object. You'll want to set the ButtonDownFcn property of the line object.
h = plot(1:3, 'ButtonDownFcn', #(s,e)ImageClickCallback()
The ButtonDownFcn property is also available for most of UI elements (including axes)
set(gca, 'ButtonDownFcn', #mycallback)

Related

UIAxes' YLim property cannot be listened to

MATLAB provides the addlistener function.
Listeners allow us to keep track of changes of an object's properties and act upon them. For example, we can create a very simple listener that will display a message in the command window when the 'YLim' property of an axes object is changed:
% Example using axes
ax = axes();
addlistener(ax, 'YLim', 'PostSet', #(src, evnt)disp('YLim changed'));
Try panning the axes or zooming in/out and see what happens. This works fine.
I need to do the same but using an uiaxes instead.
Unfortunately, it looks like we are not allowed to do so. Try running the following example:
% Example using uiaxes
ax = uiaxes();
addlistener(ax, 'YLim', 'PostSet', #(src, evnt)disp('YLim changed'));
It throws this error:
Error using matlab.ui.control.UIAxes/addlistener While adding a
PostSet listener, property 'YLim' in class 'matlab.ui.control.UIAxes'
is not defined to be SetObservable.
I've read the articles Listen for Changes to Property Values and Observe Changes to Property Values and I learned that a property must be declared as SetObservable to allow being listened:
classdef PropLis < handle
properties (SetObservable)
ObservedProp = 1 % <-- Observable Property
end
end
I've tried taking a look at the UIAxes class definition via edit matlab.ui.control.UIAxes but it's not possible because it's a P-file.
If 'YLim' is not observable then how can I keep track of changes in this property?
I'm using App Designer in MATLAB R2018b.
We should attach the listener to the internal Axes object, and not the UIAxes itself. Try this:
hFig = uifigure();
hAx = uiaxes(hFig);
addlistener(struct(hAx).Axes, 'YLim', 'PostSet', #(src, evnt)disp("YLim changed"));
hAx.YLim = [0 2];
In case anybody is wondering, I found this via trial and error.
Tested on R2018a & R2018b.
Thank you so much for this solution! I was having a real problem with zooming in on 3D data on a UIAxes. The 3D axes contained a .png background raster map at z=0 (plotted as a surface) and the 3D position of a UAV flight in x-y-x. When I would zoom in, the z would zoom as well and the new z limits would exclude the map I wanted always displayed. What was odd, is that setting
app.UIAxes2.Interactions = [zoomInteraction('Dimensions','xy')];
would correct the problem when zooming with the scroll wheel on my mouse, but if I selected the zoom toolbar button (clicking to zoom), it would still zoom in z. Really frustrating.
To get around this, I used your example, but added the listener to the 'ZLim' and made a callback function that would look at all the elements of the plot, and reset ZLim to include all the data whenever the ZLim changed.
warning('off','MATLAB:structOnObject');
addlistener(struct(app.UIAxes2).Axes, 'ZLim', 'PostSet', #(src,evnt)mapholdaltlims(app,app.UIAxes2));
function [inclusivezlim] = mapholdaltlims(app,ax)
objwithz = findobj(app.UIAxes2.Children,'-property','ZData');
currmin_z = 0;
currmax_z = 0;
for i = 1:length(objwithz)
currmin_z = min([min(min(objwithz(i).ZData)), currmin_z]);%Need double mins because data could be 2d surface
currmax_z = max([max(max(objwithz(i).ZData)), currmax_z]);
end
inclusivezlim = [currmin_z currmax_z];
ax.ZLim = inclusivezlim;
%disp('Updated the limits')
end
Man, what a pain this was. I am glad it now works. Thanks again.

How to use "ButtonDownFcn" in a populated GUI axes?

I have a very simple GUI made in guide where i have a plot function initiated by a pushbutton which plots a scatter plot in axes (called Method1axes1):
handles.plot = scatter(X,Y, 'parent', handles.Method1axes1);
Now I want the user to be able to click the axes (plot) to get en new larger figure. I tried the below code which is working if i DON'T plot in the axes first. As soon as I run the plot function the scatterplot appears in Method1axes1 but I can no longer click the figure.
% --- Executes on mouse press over axes background.
function Method1axes1_ButtonDownFcn(hObject, eventdata, handles)
figure
scatter(X,Y);
What am I doing wrong?
This is a kind of special case for MATLAB, and it is not extremely well documented.
There are 2 things you need to consider:
1) The most obvious part. When you plot something in your axes, the plot is on the foreground. So when you click on your axes, the top plot intercept that click and tries to process it. You need to disable the mouse click capture from the plot/scatter/image objects you have in your axes. For that, you have to set the HitTest property of your scatter object to 'off'. (recent MATLAB version have changed the name of this property, it is now called PickableParts).
2) Much less obvious and documented. It used to be in the doc for the axes ButtonDownFcn callback but it is not explained anymore (although the behaviour persist). This is what I could find on old forums:
When you call PLOT, if the axes NextPlot property is set to 'replace'
(which it is by default) most of the properties of the axes (including
ButtonDownFcn) are reset to their default values.
Change the axes NextPlot property to 'replacechildren' to avoid this,
or set the ButtonDownFcn after calling PLOT, or use the low-level LINE
function instead of the higher-level PLOT function.
This is also discussed and explained here: Why does the ButtonDownFcn callback of my axes object stop working after plotting something?
For your case, I tried set(axe_handle,'NextPlot','replacechildren') and it works ok to let the mouse click reach the ButtonDownFcn, but unfortunately it creates havoc with the axes limits and LimitModes ... so I opted for the second solution, which is to redefine the callback for ButtonDownFcn after every plot in the axes.
So in summary, your code for the pushbutton1_Callback should be:
function pushbutton1_Callback(hObject, eventdata, handles)
% Whatever stuff you do before plotting
% ...
% Plot your data
handles.plot = scatter(X,Y, 'parent', handles.Method1axes1);
% Disable mouse click events for the "scatterplot" object
set(handles.plot,'HitTest','off') ;
% re-set the "ButtonDownFcn" callback
set(handles.Method1axes1,'ButtonDownFcn',#(s,e) Method1axes1_ButtonDownFcn(s,e,handles) )
And for your axes mouse click event, you might as well keep the handle of the new generated objects:
function Method1axes1_ButtonDownFcn(hObject, eventdata, handles)
handles.newfig = figure ;
handles.axes1copy = copyobj( handles.Method1axes1 , handles.newfig ) ;
Note that instead of plotting a new set, I simply use the copyobj function, very handy when you need to reproduce a plot.
Illustration:
If you want to set the figure/graph to enlarge and shrink on mouse
scroll/click, then just set the zoom property of the required axes in
OpeningFcn within the m file.
For example within the OpeningFcn in the GUI's m file, put the below code. Please ensure that you put the below code within the OpeningFcn function.
h1 = zoom(handles.Method1axes1);
h1.Enable = 'on';
Now, on each mouse scroll/click, you would be able to zoom in/out the graphs.
A sample screenshot of openingFcn for a GUI named ZoomAxesDemo is given below.

What's the 'axes' field in matlab?

What's the field axes in the last line of the following code?
function drawBox2D(h,object)
% draw regular objects
if ~strcmp(object.type,'DontCare')
rectangle('Position',pos,'EdgeColor',occ_col{object.occlusion+1},...
'LineWidth',3,'LineStyle',trun_style{trc},'parent',h(1).axes)
rectangle('Position',pos,'EdgeColor','b', 'parent', h(1).axes)
See the h(1).axes in the last line? I don't know what is the h, don't know what to pass to that function.
I speculate it to be a graph handle. So I tried
h = figure(2);
drawBox2D(h, obj);
However the handle itself is an integer so there's no field called 'axes'.
Could anyone tell me who does the field axes belong to? And what should I pass as h into this function?
If you want to get all axes handles anywhere in Matlab, you can do the following things:
allAxes = findall(0,'type','axes');
To select only axes handles not legends, you need to cleanup the list of axes
axNoLegendsOrColorbars= ax(~ismember(get(ax,'Tag'),{'legend','Colobar'}));

In Matlab, own function which dynamically load/display images into Axes from a Gui in a loop?

With Guide I made a Matlab Gui that have 10 Axes in which i want to display images, in all of them at once, after i press a Button.
I made a separate .m file with function Load_Write_Img_Results(img_index) that i call from the Button Callback which have the following code:
for i = 1 : 10
handles_imgOut = findobj('Tag', ['imgOut' num2str(i)]);
set(handles_imgOut, 'HandleVisibility', 'ON');
axes(handles_imgOut);
image(imgs_data{img_index(i)});
...
end
Every time when i run the main Gui and press the button for the first time, images are displayed in all axes, so everything works ok.
The problem appear when i press the button for the second time and i get this error:
Error using axes
Invalid object handle
on this line:
axes(handles_imgOut);
When debugging, i saw that after handles_imgOut = findobj('Tag', ['imgOut' num2str(i)]); the handles_imgOut is not getting any value and is empty, so it's obvious that error.
Is there any chance i can't get handles for axes at the second press of the button?
Also, i want to know how can i solve this warning from Matlab:
Calling AXES(h) in a loop can be slow. Consider moving the call to AXES outside the loop.
Thanks in advance, any suggestions are welcome!
[SOLUTION]:
for i = 1 : 10
handles_imgOut = findobj('Tag', ['imgOut' num2str(i)]);
set(handles_imgOut, 'HandleVisibility', 'ON');
axes(handles_imgOut);
image(imgs_data{img_index(i)});
set(gca, 'Tag', ['imgOut' num2str(i)]); //! renew the tag
...
end
I'm fairly new to GUIDE and I've encountered similar problems, with the graph not being updated more than once / axes not found.
In the end, I used the following approach, which I hope can be useful to you too:
% I get my axes directly from the handles object, instead of using findObj:
graph1 = handles.axes1;
graph2 = handles.axes2;
% clear axes
cla(graph1);
cla(graph2);
% showTrial is my function that draws the graphs -
%notice that I used handles to store other variables as well (.data, .trials)
showTrial(handles.data, handles.trials(1), graph1, graph2)
To sum it up:
don't use findObj, get your axes from handles (it should automatically contain them as imgOut1,imgOut2, etc.)
pass the axes to your drawing function or pass directly the handles variable e.g. Load_Write_Img_Results(img_index, handles)
The fact that it works on the first button press, but not thereafter hints that
image(imgs_data{img_index(i)});
opens new axes instead of drawing into the existing ones. Since the new axes are not initialized with your tags, findobj will not find them. Either
(1) make sure to hold the original axes by placing a hold on command right after their creation,
or
(2) renew the tag right after the image command by
set(gca, 'Tag', ['imgOut' num2str(i)]);

MATLAB ButtonDownFcn

I have a project of "Optical Character Recognition" in MATLAB and i need your help:
how do i recognize when the user press the mouse on an image?
i trying to do this with ButtonDownFcn but even when i just printing message
the message is not printed.
i want to allow the user to select the license plate from the image.
how can i do this and save the Pixels of the selected area?
thanks in advance.
Addressing your two questions:
I'm guessing that you are trying to set the 'ButtonDownFcn' of the figure window, which won't work how you expect it to. If you want to do something when the user clicks on an image, you should make sure that you're setting the 'ButtonDownFcn' of the image, and not the figure window or the axes object. Note this line in the figure property documentation (emphasis added by me):
Executes whenever you press a mouse button while the pointer is in the figure window, but not over a child object (i.e., uicontrol, uipanel, axes, or axes child).
This is why you have to set a 'ButtonDownFcn' for each object you want it to work for. Setting it for the figure window won't make it work automatically for each object in the figure. Here's an example that sets the 'ButtonDownFcn' for the figure and an image object:
img = imread('peppers.png'); %# Load a sample image
hFigure = figure; %# Create a figure window
hImage = image(img); %# Plot an image
set(hFigure,'ButtonDownFcn',... %# Set the ButtonDownFcn for the figure
#(s,e) disp('hello'));
set(hImage,'ButtonDownFcn',... %# Set the ButtonDownFcn for the image
#(s,e) disp('world'));
Notice how clicking inside and outside the image displays a different message, since each calls the 'ButtonDownFcn' for a different object. Notice also that if you click on the tick mark label of one of the axes, nothing is displayed. This is because the axes object has its own 'ButtonDownFcn', which wasn't set to anything.
If you have access to the Image Processing Toolbox you can use the function IMFREEHAND to have the user draw an ROI (region of interest) in the image. Then you can use the createMask method to create a binary mask of the image with ones for pixels inside the ROI and zeroes for pixels outside the ROI.