Related
I did an app designer GUI with two buttons and axes. The first one (LoadimageButton) is loading the pappers image and I can mark points until I press escape. The second button is printing out the point coordinates (PositionButton).
I have noticed that after pressing the two buttons I can move points in the axes and change their positions or delete them. The issue is that when I press the delete (in the context menu) I get this error after pressing the PositionButton:
Error using images.roi.Point/get
Invalid or deleted object.
Error in tempDrwPnt1/PositionButtonPushed (line 61)
positions = cell2mat(get(app.pointhandles, 'position'))
Error while evaluating Button PrivateButtonPushedFcn.
How can I refresh the app.pointhandles after deleting a point?
Code:
function LoadimageButtonPushed(app, event)
imshow('peppers.png','Parent',app.ImageAxes);
userStopped = false;
app.pointhandles = gobjects();
while ~userStopped
roi = drawpoint(app.ImageAxes);
if ~isvalid(roi) || isempty(roi.Position)
% End the loop
userStopped = true;
else
% store point object handle
app.pointhandles(end+1) = roi;
end
end
addlistener(roi,'MovingROI',#allevents);
addlistener(roi,'ROIMoved',#allevents);
app.pointhandles(1) = [];
function allevents(src,evt)
evname = evt.EventName;
switch(evname)
case{'MovingROI'}
disp(['ROI moving previous position: ' mat2str(evt.PreviousPosition)]);
disp(['ROI moving current position: ' mat2str(evt.CurrentPosition)]);
case{'ROIMoved'}
disp(['ROI moved previous position: ' mat2str(evt.PreviousPosition)]);
disp(['ROI moved current position: ' mat2str(evt.CurrentPosition)]);
end
end
end
% Button pushed function: PositionButton
function PositionButtonPushed(app, event)
positions = cell2mat(get(app.pointhandles, 'position'))
end
You could add a check within the PositionButtonPushed function for whether or not each pointhandles element is valid, report on it if so or remove it if not
Something like
function PositionButtonPushed(app, event)
nPts = numel(app.pointhandles);
positions = NaN(nPts,2); % array to hold positions
for iPt = nPts:-1:1 % loop backwards so we can remove elements without issue
if isvalid( app.pointhandles(iPt) )
positions(iPt,:) = get(app.pointhandles(iPt),'position');
else
% No longer valid (been deleted), remove the reference
app.pointhandles(iPt) = [];
% Also remove from the positions output
positions(iPt,:) = [];
end
end
end
I haven't got a compatible MATLAB version available right now to test this, but I am assuming the in-built function isvalid works for Point objects, otherwise you will have to add your own validity check. Alternatively you could try to get the position, and do the deletion (handled by else above) within a catch, but I usually recommend against try/catch if you can check for a specific (known) issue instead.
I've often used something similar (e.g. for axes), but bundled the cleanup functionality for invalid handles into its own function. In this case it would have let you add a single function call before getting the positions of the remaining valid points.
I've also made this more condensed by using arrayfun, but under the hood it's the same approach as the above loop:
function PositionButtonPushed(app, event )
app.checkHandleValidity(); % cleanup handles just in case
positions = cell2mat(get(app.pointhandles, 'position'));
end
function checkHandleValidity( app )
bValid = arrayfun( #isvalid, app.pointhandles ); % Check validity
app.pointhandles( ~bValid ) = []; % Remove invalid elements
end
I'm building a MATLAB GUI using GUIDE for processing medical images (brain tumor segmentation on MRI scans). As a preprocessing step, the program coregisters different scans, which looks like this:
I now want to display crosshairs on both images as a visual check of the coregistration. The crosshairs should be linked to one another, such that they point to the same pixel in both images. Moreover, it should move when hovering over (or clicking on) one of the images. This is what I want to achieve:
Does a build-in MATLAB function exist that can achieve this? Or, if I have to write it myself, how would one tackle this problem?
I'll use a Toy GUI in order to show how it works :
Let's first try to make it work for the first image only. What you want to achieve is :
When the user clicks on the figure, we want to have access to the location of the mouse click.
Then we want to check if the click was located inside the first image
If it is, we want to update the position of the crossbar, which will be represented by two lines overlayed on the image and crossing at the selected point.
Step 0 : Initial settings
You want to make sure of some details first :
Make sure you add a call to hold on after both your imshow calls, otherwise the crossbars will delete your image instead of overlaying on it.
Step 1 : Getting the position of the mouse click
In your GUI opening function (here it will be SuperDuperGUI_OpeningFcn), you want to add a call to :
set(gcf, 'WindowButtonDownFcn', #getMousePositionOnImage);
This will trigger the function getMousePositionOnImage everytime the user clicks inside your GUI.
Then, you want to add and implement the function getMousePositionOnImage :
function getMousePositionOnImage(src, event)
% Fetch the current handles structure
handles = guidata(src);
% Get the coordinate IN THE AXES UNITS OF AXES1 (Here I chose to define them
% as pixels) of the point where the user clicked
cursorPoint = get(handles.axes1, 'CurrentPoint')
Step 2 : Check if the mouse click is inside the first image
Still in the getMousePositionOnImage function :
% Get the Position of the first image (We're only interested by the width
% and height of the axes1 object)
Img1Pos=get(handles.axes1,'Position')
% Check if inside
if(cursorPoint(1,1)<Img1Pos(3)&&cursorPoint(1,2)<Img1Pos(4)&&cursorPoint(1,1)>=0&&cursorPoint(1,2)>=0)
% Do stuff
end
Step 3 : If the click was inside the first image, update the position of the crossbar
% Check if inside
if(cursorPoint(1,1)<Img1Pos(3)&&cursorPoint(1,2)<Img1Pos(4)&&cursorPoint(1,1)>=0&&cursorPoint(1,2)>=0)
Lines=findobj('Type','line','Parent',handles.axes1);
if isempty(Lines)
% If Lines is empty, we need to create the line objects
line([0 Img1Pos(3)],[cursorPoint(1,2) cursorPoint(1,2)],'Color','g','Parent',handles.axes1);
line([cursorPoint(1,1) cursorPoint(1,1)],[0 Img1Pos(4)],'Color','g','Parent',handles.axes1);
else
% If not, we just update the fields XData and YData of both Lines
Lines(1).XData=[0 Img1Pos(3)]; % Unnecessary but I'll leave it there for clarity
Lines(1).YData=[cursorPoint(1,2) cursorPoint(1,2)];
Lines(2).XData=[cursorPoint(1,1) cursorPoint(1,1)];
Lines(2).YData=[0 Img1Pos(4)]; % Unnecessary but I'll leave it there for clarity
end
end
Result :
Now I'll let you do the last part, which involves linking the two crossbars. Instead of only checking if the click was on first image, you'll check both separately. Then, if it is in one of the image, you'll update both crossbar to the position of the click in the right axes object
Edit
The code below is inspired by the answer of BillBokeey, for which I'm very grateful.
First, do steps 0 and 1 of BillBokeey's solution. Then I put this function in my code:
function getMousePositionOnImage(src, event)
% Fetch the current handles structure
handles = guidata(src);
% Get the coordinate IN THE AXES UNITS OF AXES1 (Here I chose to define them
% as pixels) of the point where the user clicked
cursorPoint1 = get(handles.axes2, 'CurrentPoint');
cursorPoint2 = get(handles.axes3, 'CurrentPoint');
% Get the Position of the first image (We're only interested by the width
% and height of the axes1 object)
Img1Pos = getpixelposition(handles.axes2);
Img2Pos = getpixelposition(handles.axes3);
XLim1 = get(handles.axes2, 'XLim');
YLim1 = get(handles.axes2, 'YLim');
XLim2 = get(handles.axes3, 'XLim');
YLim2 = get(handles.axes3, 'YLim');
% Check if inside
if (cursorPoint1(1)<XLim1(2) && cursorPoint1(2)<YLim1(2) && cursorPoint1(1)>=XLim1(1) && cursorPoint1(1)>=YLim1(1))
Lines1=findobj('Type','line','Parent',handles.axes2);
Lines2=findobj('Type','line','Parent',handles.axes3);
if isempty(Lines1)
% If Lines is empty, we need to create the line objects
line(XLim1,[cursorPoint1(1,2) cursorPoint1(1,2)],'Color','g','Parent',handles.axes2);
line([cursorPoint1(1,1) cursorPoint1(1,1)],YLim1,'Color','g','Parent',handles.axes2);
line(XLim2,[cursorPoint1(1,2) cursorPoint1(1,2)],'Color','g','Parent',handles.axes3);
line([cursorPoint1(1,1) cursorPoint1(1,1)],YLim2,'Color','g','Parent',handles.axes3);
else
% If not, we just update the fields XData and YData of both Lines
Lines1(1).XData=XLim1; % Unnecessary but I'll leave it there for clarity
Lines1(1).YData=[cursorPoint1(1,2) cursorPoint1(1,2)];
Lines1(2).XData=[cursorPoint1(1,1) cursorPoint1(1,1)];
Lines1(2).YData=YLim1; % Unnecessary but I'll leave it there for clarity
Lines2(1).XData=XLim2; % Unnecessary but I'll leave it there for clarity
Lines2(1).YData=[cursorPoint1(1,2) cursorPoint1(1,2)];
Lines2(2).XData=[cursorPoint1(1,1) cursorPoint1(1,1)];
Lines2(2).YData=YLim2; % Unnecessary but I'll leave it there for clarity
end
elseif (cursorPoint2(1)<XLim2(2) && cursorPoint2(2)<YLim2(2) && cursorPoint2(1)>=XLim2(1) && cursorPoint2(1)>=YLim2(1))
Lines1=findobj('Type','line','Parent',handles.axes2);
Lines2=findobj('Type','line','Parent',handles.axes3);
if isempty(Lines2)
% If Lines is empty, we need to create the line objects
line(XLim1,[cursorPoint2(1,2) cursorPoint2(1,2)],'Color','g','Parent',handles.axes2);
line([cursorPoint2(1,1) cursorPoint2(1,1)],YLim1,'Color','g','Parent',handles.axes2);
line(XLim2,[cursorPoint2(1,2) cursorPoint2(1,2)],'Color','g','Parent',handles.axes3);
line([cursorPoint2(1,1) cursorPoint2(1,1)],YLim2,'Color','g','Parent',handles.axes3);
else
% If not, we just update the fields XData and YData of both Lines
Lines1(1).XData=XLim1; % Unnecessary but I'll leave it there for clarity
Lines1(1).YData=[cursorPoint2(1,2) cursorPoint2(1,2)];
Lines1(2).XData=[cursorPoint2(1,1) cursorPoint2(1,1)];
Lines1(2).YData=YLim1; % Unnecessary but I'll leave it there for clarity
Lines2(1).XData=XLim2; % Unnecessary but I'll leave it there for clarity
Lines2(1).YData=[cursorPoint2(1,2) cursorPoint2(1,2)];
Lines2(2).XData=[cursorPoint2(1,1) cursorPoint2(1,1)];
Lines2(2).YData=YLim2; % Unnecessary but I'll leave it there for clarity
end
end
i'm am very new to Matlab but really want improve. For my experiment i want to show a picture which the participant response yes/no to, using two different keys (f&g) and then the next picture is presented and it repeats so onward.
Presenting the picture, using the keys works for far, but i can't get it to repeat the trial. Thus my question is how can i get the program to repeat/loop my trial?
Is there something wrong in my code so far or is there additional coding i should use?
this is my code so far
function try1_6()
cleanupObj= onCleanup(#() myCleanupFxn);
% PRETEST
% Initialize screen with black background
winID = Screen('openWindow',0, [0 0 0]);
%Parameter
backcol=255;
textcol=0;
% Load image file(s)
structimages= [];
TheImagesdir = dir('theImagesdir/*.jpg');
for i=1: length(TheImagesdir);
TheImages = imread(['theImagesdir/' TheImagesdir(i).name], 'JPEG');
% Get width and height
imageX = size(TheImages,2);
imageY = size(TheImages,1);
% Convert to texture
myTexture = Screen('MakeTexture', winID, TheImages);
% Set destination rectangle
destRect = [50 100 50+imageX 100+imageY];
%save to structure
structimages(end+1).filename=TheImagesdir(i).name;
structimages(end).destRect= destRect;
structimages(end).texture= myTexture;
end
%Make triallist
numberOfItems= [5]; %list of all possible items
Nrepeats=4;
Response=0;
TrialList=HH_mkTrialList({numberOfItems Response},Nrepeats);
%PRESENTATION
for trialnum=1:size(TrialList,1)
nitems = TrialList(trialnum,1);
Screen('FillRect', winID,backcol); % makes the screen blank
%displays text
DrawFormattedText(winID,'dkjfghaslkdfglksdjgfh','center','center',textcol);
Screen('Flip', winID)
HH_waitForKeyPress({'space'}); % waits for spacebar to be pressed
Screen('FillRect',winID,backcol);
Screen('Flip',winID);
WaitSecs(1);
%display picture
whichTheImages= randi(length(TheImagesdir)); % randomly selects image for directory
Screen('FillRect',winID,backcol);
Screen('DrawTexture', winID, myTexture, [], destRect);
Screen('Flip', winID);
HH_waitForKeyPress({'f','j'},5)
if resp==-1
break
end
TrialList(trialnum,4)= response; %records response
end
end
function myCleanupFxn()
Screen('CloseAll')
end
There are a number of problems with you code that you need to address. First of all, TrialList is used before it is declared/initialized. The Make triallist block of code seems out of place in the body of the for loop, and should probably be placed before you loop TrialList.
Your second problem is the inner for loop that loads images. Right now, it loads every image found in the directory, on every trial! There is no reason for you to be doing this, and you should be placing this for loop outside the trial loop as well. Furthermore, your original code never worked as intended, because you never save the loaded texture anywhere; myTexture is overwritten by the last image in your folder and that's the only texture you're ever gonna get. So in addition to pre-loading the images before the loop, you need to save them in a data structure so that you can use them later in your trial loop. A simple struct will work nicely here:
structImages = [];
TheImagesdir = dir('theImagesdir/*.jpg');
for i = 1:length(TheImagesdir);
TheImages = imread(['theImagesdir/' TheImagesdir(i).name], 'JPEG');
% Get width and height
imageX = size(TheImages,2);
imageY = size(TheImages,1);
% Convert to texture
myTexture = Screen('MakeTexture', winID, TheImages);
% Set destination rectangle
destRect = [50 100 50+imageX 100+imageY];
%save to structure
structImages(end+1).filename = TheImagesdir(i).name;
structImages(end).destRect = destRect;
structImages(end).texture = myTexture;
end
There are other inconsistencies in your code:
whichTheIamges is defined but not used
resp is used in the comparison if resp==-1 but is not defined
response is saved into TrialList before it is defined
Finally, the biggest problem is Screen('CloseAll', winID); is inside the trial loop, so you tear down your whole presentation platform after the first trial.
FYI, as noted in my comment wrapping your entire script in a try block is really poor practice. I suspect you do this because you want to be able to Ctrl+C mid-task, but there's a better way to do this. If you make your entire script a function then you can use the onCleanup method to execute code whenever your function exits (whether normally, by error, or by interruption). The method goes like this:
function myScript()
%//make your script a function. There is an additional advantages to doing this:
%//function performance is better than script performance.
%//blah-blah-blah
%//setup the cleanup object before opening screen
cleanupObj = onCleanup(#() myCleanupFxn);
%//open the screen
winID = Screen('openWindow',0, [0 0 0]);
%//blah-blah-blah
end
function myCleanupFxn()
%//local function, not visible outside of this file
Screen('CloseAll');
end
I have added uicontextmenu to the line object. uicontextmenu includes 3 check boxes. whenever I check any of them uicontextmenu disappears. I want uicontextmenu visible for sometime so that i can check multiple boxes and see the change (same as a button group but in uicontextmenu). Is there any solution to this or some other approach?
cmenu=uicontextmenu;
set(he,'uicontextmenu',cmenu);
item1=uimenu(cmenu,'label','Data A','checked','off','callback',#func_a);
item2=uimenu(cmenu,'label','Data B','checked','off','callback',#func_b);
item3=uimenu(cmenu,'label','Data C','checked','off','callback',#func_c);
basically, he is the line object created by plot(x,y) and func_a, func_b, func_c are function to convert property 'checked' to on|off.
This example is greatly inspired by Benoit_11 solution, but a bit refined. I was also under the impression that the 3 different functions in your callback were doing different things so I made the 3 different menus change different properties of the line (instead of changing the same property with different values).
I made the uimenu callback in one single nested function. It decides what to do based on the parameter what2do supplied at the uimenu definition (but feel free to keep 3 separate functions). However, note that the function that toggle the check mark is the same for all uimenu (you don't need a separate function for each of them).
function hf = TestUiContext2
%// Extension of Benoit_11 solution
clear ; clc ; close all
hf = figure ; %// return the handle of the figure
hax = axes; %// Create axes and save handle
plot(rand(20,3)); %// Plot three lines
hcmenu = uicontextmenu; %// Define a context menu; it is not attached to anything
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu, 'Label','Bold line' , 'Callback' , {#uiCallback,'bold'} );
item2 = uimenu(hcmenu, 'Label','Dotted line' , 'Callback' , {#uiCallback,'dots'} );
item3 = uimenu(hcmenu, 'Label','Markers on' , 'Callback' , {#uiCallback,'mark'} );
hlines = findall(hax,'Type','line'); %// Locate line objects
for line = 1:length(hlines) %// Attach the context menu to each line
set(hlines(line),'uicontextmenu',hcmenu)
end
function uiCallback(obj,~,what2do)
hline = gco ;
switch what2do
case 'bold'
toggle_bold_line(hline)
case 'dots'
toggle_dotted_line(hline)
case 'mark'
toggle_markers(hline)
end
%// reposition the context menu and make it visible
set(hcmenu,'Position',get(gcf,'CurrentPoint'),'Visible','on')
toggle_checkmark(obj) %// toggle the checkmark
end
function toggle_checkmark(obj)
if strcmp(get(obj,'Checked'),'on')
set(obj,'Checked','off')
else
set(obj,'Checked','on')
end
end
function toggle_bold_line(hline)
if get(hline,'LineWidth')==0.5
set(hline,'LineWidth',2)
else
set(hline,'LineWidth',0.5)
end
end
function toggle_dotted_line(hline)
if strcmpi(get(hline,'LineStyle'),':')
set(hline,'LineStyle','-')
else
set(hline,'LineStyle',':')
end
end
function toggle_markers(hline)
if strcmpi(get(hline,'Marker'),'none')
set(hline,'Marker','o')
else
set(hline,'Marker','none')
end
end
end
Now you can enjoy ticking all your menu in one go ;)
Here is a workaround which might do the trick for you. That's not too elegant but it seems to work.
The trick is to set the menu 'Visible' property to 'on' in every callback you have (i.e. #func_a, #funct_b and #funct_c). When I run the following example (based on the demo on the Mathworks website) the menu does not disappear when the selection is changed. Notice that I created separate functions for each callback.
Here is the code:
function TestUiContext( ~)
%// Based on example from The Mathworks
%// http://www.mathworks.com/help/matlab/ref/uicontextmenu.html
clear
clc
close all
%// Create axes and save handle
hax = axes;
%// Plot three lines
plot(rand(20,3));
%// Define a context menu.
hcmenu = uicontextmenu;
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu,'Label','dashed','Callback',#(s,e) hcb1);
item2 = uimenu(hcmenu,'Label','dotted','Callback',#(s,e) hcb2);
item3 = uimenu(hcmenu,'Label','solid','Callback',#(s,e) hcb3);
%// Locate line objects
hlines = findall(hax,'Type','line');
%// Attach the context menu to each line
for line = 1:length(hlines)
set(hlines(line),'uicontextmenu',hcmenu)
end
%// In the callback of every item/option, set the menu property 'Visible' to 'on'.
function hcb1
set(gco,'LineStyle','--');
set(hcmenu,'Visible','on')
end
function hcb2
set(gco,'LineStyle',':');
set(hcmenu,'Visible','on')
end
function hcb3
set(gco,'LineStyle','-');
set(hcmenu,'Visible','on')
end
end
And 2 screenshots to show what it looks like:
And moving the cursor down:
So as I said, not perfect but hopefully it will do the job for you!
So, as I browse through google on the problem of how to create a .gif animation from series of .fig files, i stumble through one that uses .sdf file, I tried to rewrite the program to work for my .fig files
clear all;
close all;
dynam = 156;
gif_fps = 24;
video_filename = 'A.gif';
fh = figure(1);
for i = 1:dynam
F_data = getdata(['F' num2str(i,'_%03i', '.fig');
imagesc(F_data);
drawnow;
frame = getframe(fh);
im = frame2im(frame);
[imind,cm] = rgb2ind(im,256);
if a == 0;
imwrite(imind,cm,video_filename,'gif', 'Loopcount',inf);
else
imwrite(imind,cm,video_filename,'gif','WriteMode','append','DelayTime',1/gif_fps);
end
end
so it pops up an error saying
??? frame = getframe(fh);
|
Error: The expression to the left of the equals sign is not a valid target for an assignment.
which I don't understand why this is happening, and I also notice that Matlab is not drawing the figs, the figure that pop us is completely blank.
The error comes from a typo. The line
F_data = getdata(['F' num2str(i,'_%03i', '.fig'); %bracket missing at the end
should read
F_data = getdata(['F' num2str(i,'_%03i', '.fig']);
Without the bracket, Matlab sees
['F' num2str(i,'_%03i', '.fig');
imagesc(F_data);
drawnow;
frame
as a single string of letters. The code's logic is therefore a = b = c and matlab can't interpret this.
To prevent such errors, matlab and its editor have some nice coloring schemes that highlight in dark red the text following an opening string ', and turn the full string in purple when a closing ' is used. If you see some red characters spanning over several lines, that's a sign of a potential problem. Unfortunately, brackets don't have such behavior...
Additionnaly, what about opening the figures per se? You will see if each figure renders well (no blank) and will be able to capture the frame.
for i = 1:dynam
%open, get the frame from and close the figure
fh_tmp = open(['F' num2str(i,'_%03i', '.fig'])
frame = getframe(fh_tmp);
close(fh_tmp);
im = frame2im(frame);
...
I still struggle to find where the getdata is coming from.