Getting clicked point coordinates in 3D figure Matlab - matlab

I have a 3D figure in Matlab, let's assume it's sphere. What I need, is to get the X, Y, Z values of the point on surface, that I click with the mouse.
r = 10;
[X,Y,Z] = sphere(50);
X2 = X * r;
Y2 = Y * r;
Z2 = Z * r;
figure();
props.FaceColor = 'texture';
props.EdgeColor = 'none';
props.FaceLighting = 'phong';
sphere = surf(X2,Y2,Z2,props);
axis equal
hold on
clicked_point = [?,?,?];
So in this example I want the clicked_point to be equal to [-3.445,-7.32,5.878].
I've tried with such solution:
clear all;
close all;
r = 10;
[X,Y,Z] = sphere(50);
X2 = X * r;
Y2 = Y * r;
Z2 = Z * r;
fig = figure();
props.FaceColor = 'texture';
props.EdgeColor = 'none';
props.FaceLighting = 'phong';
sphere = surf(X2,Y2,Z2,props);
axis equal
dcm_obj = datacursormode(fig);
set(dcm_obj,'DisplayStyle','datatip',...
'SnapToDataVertex','off','Enable','on')
c_info = getCursorInfo(dcm_obj);
while length(c_info) < 1
datacursormode on
c_info = getCursorInfo(dcm_obj);
end
But after that I can't even click on the sphere to display any data on figure. How can I get X, Y, Z in script? If not, how can I detect that the mouse click has already occured in Matlab?

It is not clear whether you want the clicked_point variable to reside in the base workspace or if that is going to be part of a GUI.
I'll give you a solution for the base workspace.
The trick is to just add the bit of code you need to the UpdateFcn of the datacursormode object.
Save a function getClickedPoint.m somewhere visible on your MATLAB path:
function output_txt = getClickedPoint(obj,event_obj)
% Display the position of the data cursor
% obj Currently not used (empty)
% event_obj Handle to event object
% output_txt Data cursor text string (string or cell array of strings).
pos = get(event_obj,'Position');
output_txt = {['X: ',num2str(pos(1),4)],...
['Y: ',num2str(pos(2),4)]};
% If there is a Z-coordinate in the position, display it as well
if length(pos) > 2
output_txt{end+1} = ['Z: ',num2str(pos(3),4)];
end
assignin('base','clicked_point',pos)
All this code is actually a copy of the default function used by data cursors. The only modifications are:
I changed the name (obviously you want it to be unique)
I added the last line of code
This last line of code use assignin to transfer the position of the cursor into a variable (named clicked_point) in the base workspace.
Armed with that, keep your code which generate the sphere (although I recommend you change the name of the surface object to something else than sphere as this is a built in MATLAB function), and we just have to modify the datacursormode object to instruct it to use our getClickedPoint function:
[X,Y,Z] = sphere(50);
r = 10 ; X2 = X * r; Y2 = Y * r; Z2 = Z * r;
fig = figure ;
hs = surf(X2,Y2,Z2,'FaceColor','texture','EdgeColor','none','FaceLighting','phong');
axis equal
%% Assign custom update function to dcm
dcm_obj = datacursormode(fig);
set(dcm_obj,'SnapToDataVertex','off','Enable','on','UpdateFcn',#getClickedPoint)
Now the first time you click on the sphere, the variable clicked_point will be created in the workspace with the coordinates of the point. And every time you click again on the sphere the variable will be updated:
If this is to be applied with a GUI, use the same technique but instead of assignin, I would recommend to use the setappdata function. (you can read Share Data Among Callbacks to have details about how this works.)

Related

How to cut part of the data out of a plot in Matlab

I just wanted to cut part of my data out in MATLAB, for example:
If I click on two points on the axis, it will cut the elements after the I click on with respect to the x-axis. I will post my code and a pic for further details
Thank you in advance
load sample.mat
X = sample.current;
X1 = sample.voltage;
Ts = 0.01;
Fs = 1/Ts;
Fm = Fs/2;
Fc = 2;
N =10;
d = fdesign.lowpass('N,Fc',N,Fc,Fs);
designmethods(d);
Hd = design(d);
%fvtool(Hd)
%X is a variable form csv
%X1 is a variable from csv
output = filter(Hd,X);
output1 = filter(Hd,X1);
figure;
plot(X,X1,'-g');
hold on
plot(output, output1,'r');
hold off
legend('raw signal','filtered signal')
grid on
x = output, output1;
y = output1;
figure
subplot(2,1,1)
plot(x,y,'r');
title('Original plot');
uiwait(msgbox('Select an x-value from which to crop','modal'));
[x_user ~] = ginput(1); % Let the user select an x-value from which to crop.
x(x>x_user) = [];
subplot(2,1,2);
plot(x,y,'r');
title('New plot with cropped values');
xlim([min(x(:)) max(x(:))]);
enter image description here
*Posting this as an answer to format code.
If its only one graphic you can just select the points that you want to delete using the "Brush/Select Data" (icon of a brush with a red square located at the menubar of the figure) selecting the data you want to be gone and then pressing the delete key.
If you want to do it with code you can try to find the index of the point where the signal starts to decrease over the X using something like:
% Find the index where X starts to decrease
maxIndex = find(data.x == max(data.x));
% In case of multiple indexs, ensure we get the first one
maxIndex = maxIndex(1);
% Copy data to new vector
saveData.x = data.x(1:maxIndex);
saveData.y = data.y(1:maxIndex);
If you want to use the users' click position you can use find to locate the index of the first element after the click:
% Get the coords of the first click
userFirstClick = ginput(1);
% Get the X component of the coords
xCoordInit = userFirstClick(1);
% Locate the index of the first element that is greater than
% the xCoord
firstXIndex = find(data.x >= xCoordInit);
% In case of multiple indexs, ensure we get the first one
firstXIndex = firstXIndex(1);
% Do the same to get the final index
userSecondClick = ginput(1);
xCoordFinal = userSecondClick(1);
finalXIndex = find(data.x > xCoordFinal);
finalXIndex = finalXIndex(1)-1;
% -1 because data.x(finalXIndex) is already greater than xCoordFinal
% Copy data to the new vector
saveData.x = data.x(firstXIndex:finalXIndex);
saveData.y = data.y(firstXIndex:finalXIndex);
Then just plot saveData.
Edit
There was a typo on my previous code, here you have a fully functional example where you just need to click over the two points where you want to crop.
function cropSine()
% create a period of a Sine to initialize our data
data.x = -pi*3:0.01:pi*3;
data.y = sin(data.x);
% we make it loop back just as in your picture
data.x = [data.x,data.x(end:-1:1)];
data.y = [data.y, -data.y*0.5+5];
% create a figure to show the signal we have just created
figure
% create the axes where the data will be displayed
mainAx = axes();
% Draw our fancy sine!
plot(data.x, data.y, 'b-', 'Parent', mainAx);
% Request the initial position to crop
userFirstClick = ginput(1);
% Get the index of the nearest point
initIndex = getNearest(userFirstClick, data);
% Do the same to get the final index
userSecondClick = ginput(1);
% Get the index of the nearest point
finalIndex = getNearest(userSecondClick, data);
% check if its a valid point
if isempty(initIndex) || isempty(finalIndex)
disp('No points in data vector!');
return;
end
% Ensure that final index is greater than first index
if initIndex > finalIndex
tempVal = initIndex;
initIndex = finalIndex;
finalIndex = tempVal;
end
% Copy the data that we want to save into a new variable
saveData.x = data.x(initIndex:finalIndex);
saveData.y = data.y(initIndex:finalIndex);
% Plot the cropped data in red!
hold(mainAx, 'on');
plot(saveData.x, saveData.y, 'r-', 'Parent', mainAx);
hold(mainAx, 'off');
end
function nearestIndex = getNearest(clickPos, vector)
nearestIndex = [];
numPoints = length(vector.x);
if numPoints == 0
return;
end
nearestIndex = 1;
minDist = calcDist(vector.x(1), vector.y(1), clickPos(1), clickPos(2));
for pointID = 1:numPoints
dist = calcDist(vector.x(pointID), vector.y(pointID), clickPos(1), clickPos(2));
if dist < minDist
nearestIndex = pointID;
minDist = dist;
end
end
end
function dist = calcDist(p1x, p1y, p2x, p2y)
dist = sqrt(power(p1x-p2x,2)+power(p1y-p2y,2));
end

Circular ROI is not being drawn from input

For some reason, the following code only displays the masked image with roi at 10,10 with height and width 100,100. These are the initial values. It seems the image does not update even after the getPosition function. Could anyone explain this issue?
`I = imread('/Users/imageuser/Documents/PT300.tif');
h = imshow(I);
% define circular roi by square bounding box
x = 10;
y = 10;
d1 = 100;
d2 = 100;
e = imellipse(gca, [x y d1 d2]);
% roi can be interactively moved/adjusted
% do not close figure window before createMask is called
%%% these lines are only needed if you move or resize the roi
pos = getPosition(e);
x = pos(1);
y = pos(2);
d1 = pos(3);
d2 = pos(4);
%%%
BW = createMask(e,h);
pause;
imshow(BW);`
You need to put those lines (note I inverted their order):
pause;
BW = createMask(e,h);
before calling getPosition, otherwise the new position is not updated.
Whole code:
clear
clc
close all
I = imread('coins.png');
h = imshow(I);
% define circular roi by square bounding box
x = 10;
y = 10;
d1 = 100;
d2 = 100;
e = imellipse(gca, [x y d1 d2]);
pause;
BW = createMask(e,h);
% roi can be interactively moved/adjusted
% do not close figure window before createMask is called
%%% these lines are only needed if you move or resize the roi
pos = getPosition(e)
x = pos(1);
y = pos(2);
d1 = pos(3);
d2 = pos(4);
%%%
figure
imshow(BW);
sample output after dragging the ROI:
Yay!
Note: As mentioned by juicestain, instead of using pause to halt execution of the program while the user is done creating the GUI, you can use wait to wait until the user double-clicks on the ROI object instead of having to press a key as with the pause command.
Therefore, you could replace the call to pause with a call to wait(e).

Speed up creation of impoint objects

I have to create some draggable points on an axes. However, this seems to be a very slow process, on my machine taking a bit more than a second when done like so:
x = rand(100,1);
y = rand(100,1);
tic;
for i = 1:100
h(i) = impoint(gca, x(i), y(i));
end
toc;
Any ideas on speed up would be highly appreciated.
The idea is simply to provide the user with the possibility to correct positions in a figure that have been previously calculated by Matlab, here exemplified by the random numbers.
You can use the the ginput cursor within a while loop to mark all points you want to edit. Afterwards just click outside the axes to leave the loop, move the points and accept with any key.
f = figure(1);
scatter(x,y);
ax = gca;
i = 1;
while 1
[u,v] = ginput(1);
if ~inpolygon(u,v,ax.XLim,ax.YLim); break; end;
[~, ind] = min(hypot(x-u,y-v));
h(i).handle = impoint(gca, x(ind), y(ind));
h(i).index = ind;
i = i + 1;
end
Depending on how you're updating your plot you can gain a general speedup by using functions like clf (clear figure) and cla (clear axes) instead of always opening a new figure window as explained in this answer are may useful.
Alternatively the following is a very rough idea of what I meant in the comments. It throws various errors and I don't have the time to debug it right now. But maybe it helps as a starting point.
1) Conventional plotting of data and activating of datacursormode
x = rand(100,1);
y = rand(100,1);
xlim([0 1]); ylim([0 1])
f = figure(1)
scatter(x,y)
datacursormode on
dcm = datacursormode(f);
set(dcm,'DisplayStyle','datatip','Enable','on','UpdateFcn',#customUpdateFunction)
2) Custom update function evaluating the chosen datatip and creating an impoint
function txt = customUpdateFunction(empt,event_obj)
pos = get(event_obj,'Position');
ax = get(event_obj.Target,'parent');
sc = get(ax,'children');
x = sc.XData;
y = sc.YData;
mask = x == pos(1) & y == pos(2);
x(mask) = NaN;
y(mask) = NaN;
set(sc, 'XData', x, 'YData', y);
set(datacursormode(gcf),'Enable','off')
impoint(ax, pos(1),pos(2));
delete(findall(ax,'Type','hggroup','HandleVisibility','off'));
txt = {};
It works for the, if you'd just want to move one point. Reactivating the datacursormode and setting a second point fails:
Maybe you can find the error.

User interactive animation in matlab

I am trying to make a user interactive animation in matlab. It is a square that translates and rotates across the screen and the user has to click on it. If they click on it, they will receive points and the animation will repeat. If they click on the whitespace (eg. anywhere except within the square) The animation will exit and YOU LOSE will be displayed. I have the animation almost finished using two functions. One to create the animation and another that registers the mouse click. So far I can recognize the mouse click and the animation will stop if the user clicks on the whitespace but the animation will not repeat if the user clicks within the polygon. I am unsure how to modify my code so that the animation will repeat until the user clicks the white space. I have pasted my code below. Any help would be greatly appreciated.
Animation function:
function movingPolygon
global gUserHitPolygon;
global gCurrentXVertices;
global gCurrentYVertices;
gUserHitPolygon = true;
nSides =4;
%Polar points
r=1;
theta = pi/nSides * (1:2:2*nSides-1);
%Cartesisn points
x0 = r * cos(theta);
y0 = r * sin(theta);
nFrames = 100;
xx = linspace(0,10, nFrames);
yy = xx;
rr = linspace(0, 2*pi, nFrames);
h = figure;
set(h,'WindowButtonDownFcn', #mouseDownCallback);
for i = 1:nFrames
rX = [cos(rr(i)), -sin(rr(i))];
rY = [sin(rr(i)), cos(rr(i))];
x1 = rX * [x0; y0];
y1 = rY * [x0; y0];
x2= x1 + xx(i);
y2= y1 + yy(i);
gCurrentXVertices=x2;
gCurrentYVertices=y2;
y=fill(x2, y2, 'b');
xlim([0,10]); ylim([0,10]);
hold on;
pause(0.000000003);
if ~gUserHitPolygon
clear GLOBAL gUserHitPolygon gCurrentXVertices gCurrentYVertices;
break;
end
delete(y);
end
end
Callback Function:
function mouseDownCallback(~,~)
global UserHitPolygon;
global CurrentXVertices;
global CurrentYVertices;
xVertices = gCurrentXVertices;
yVertices = gCurrentYVertices;
% if we have valid (so non-empty) sets of x- and y-vertices then...
if ~isempty(xVertices) && ~isempty(yVertices)
% get the coordinate on the current axis
coordinates = get(gca,'CurrentPoint');
coordinates = coordinates(1,1:2);
% if the coordinate is not in the polygon, then change the
% flag
if ~inpolygon(coordinates(1),coordinates(2),xVertices,yVertices)
gUserHitPolygon = false;
end
end
end
Edit: fixed some bugs in the callback function.
Short answer
include your animation in a while loop
Long Answer
No matter what a user does, the animation will only play once, because it doesn't know to repeat. The answer to this is to use a while loop, which will repeat your animation until you tell it to stop. Your main loop then becomes
done = false; % your stopping condition
while ~done
for i = 1:nFrames
rX = [cos(rr(i)), -sin(rr(i))];
rY = [sin(rr(i)), cos(rr(i))];
x1 = rX * [x0; y0];
y1 = rY * [x0; y0];
x2= x1 + xx(i);
y2= y1 + yy(i);
gCurrentXVertices=x2;
gCurrentYVertices=y2;
y=fill(x2, y2, 'b');
xlim([0,10]); ylim([0,10]);
hold on;
pause(0.000000003);
if ~gUserHitPolygon
clear GLOBAL gUserHitPolygon gCurrentXVertices gCurrentYVertices;
done = true; % set your stopping condition
break;
end
delete(y);
end
end

Matlab code to draw a tangent to a curve

I need to draw a tangent to a curve at a particular point (say the point is chosen by the user). I have written a code that allows the user to manually pick up two points and then a line is drawn between them. But I would like to automate the process. Can someone please suggest any algorithms/already implemented matlab codes to do so?
Try the function below. Of course, it needs lots of tweaking to apply to your case, but I think this is roughtly what you want.
function test
hh = figure(1); clf, hold on
grid on
x = 0:0.01:2*pi;
f = #(x) sin(x);
fprime = #(x) cos(x);
plot(x, f(x), 'r')
axis tight
D = [];
L = [];
set(hh, ...
'WindowButtonMotionFcn', #mouseMove,...
'WindowButtonDownFcn', #mouseClick);
function mouseMove(varargin)
coords = get(gca, 'currentpoint');
xC = coords(1);
if ishandle(D)
delete(D); end
D = plot(xC, f(xC), 'ko');
end
function mouseClick(obj, varargin)
switch get(obj, 'selectiontype')
% actions for left mouse button
case 'normal'
coords = get(gca, 'currentpoint');
xC = coords(1);
yC = f(xC);
a = fprime(xC);
b = yC-a*xC;
if ishandle(L)
delete(L); end
L = line([0; 2*pi], [b; a*2*pi+b]);
case 'alt'
% actions for right mouse button
case 'extend'
% actions for middle mouse button
case 'open'
% actions for double click
otherwise
% actions for some other X-mouse-whatever button
end
end
end