How can I change pushbutton visibility in MATLAB? - matlab

I am trying to create a simple game.
What it should do: Create 2 pushbuttons and when user clicks each button it should disappear.
What it actually does: When I click the first button it disappears. But when I click the 2nd one nothing happens.
clear all, clc, close all
fh = figure;
n = 2;
x = ceil(rand(10)*2);
y = ceil(rand(10)*2);
bgh = uibuttongroup('Parent',fh,'Title',...
'Button Game','Position',[.1 .2 .8 .6]);
for i = 1:n
rbh1 = uicontrol(bgh,'Style','Pushbutton','String','Red',...
'Units','normalized','Position',[rand(1) rand(1) x(1,i) y(1,i)]);
set(rbh1,'CallBack','set(rbh1,''visible'',''off'')')
end
axt = axes('Parent',bgh,'Units','normalized');
axis([0.5 1 0.5 1])
axis square
axis off
How can I fix this?

The problem is that you're setting the callback for one handle only. Change the loop bit of your code to the following and it will work. Since this seems like a learning exercise, I'll leave it to you to explore it and figure out why making this change helps.
for i = 1:n
rbh(i) = uicontrol(bgh,'Style','Pushbutton','String','Red',...
'Units','normalized','Position',[rand(1) rand(1) x(1,i) y(1,i)]);
set(rbh(i),'CallBack',['set(rbh( ' num2str(i) '),''visible'',''off'')'])
end

Related

How to plot a "moving" graph for real time values along the x-axis (using psychtoolbox)?

I am writing a code for a real time experiment using psychtoolbox to present the stimulus. In my experiment, I need to show the subject a graph that indicates his performance. I have plotted the graph using this simple code:
% Draw the graph
figure('visible','off','color',[0 0 0]);
pcolor([0 Num_timepoint+2],[-10 0],ones(2,2));
hold on;
pcolor([0 Num_timepoint+2],[0 10],2*ones(2,2));
colormap([79 167 255;255 187 221]/256);
plot(1:subloop,value0,'*-',...
'color',[0,0,0],...
'LineWidth',1,...
'MarkerSize',5,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[0.5,0.5,0.5]);
axis([0,Num_timepoint+2,-10,10]);
saveas(gcf,'line_chart.png'); %save it
close(figure);
line_chart=imread('line_chart.png'); %read it
resized_plot=imresize(line_chart,0.5);
imageSize = size(resized_plot);
[imageHeight,imageWidth,colorChannels]=size(resized_plot);
bottomRect = [xCenter-imageWidth/1.5, yCenter+gapdown, xCenter+imageWidth/1.5, yCenter+gapdown+imageHeight];
imageDisplay=Screen('MakeTexture', win0, resized_plot);
Screen('DrawTexture', win0, imageDisplay, [], bottomRect);
Unfortunately, this simple code is very slow. In addition, I couldn't make the graph moving along x axis, as soon as the new value comes.
Any help would be Awesome. Thanks in advance for your efforts.
Why are you saving the figure and redisplaying as an image? Maybe I'm missing something but you should be able to accomplish what you need by updating the existing plot with the fresh data using the handles properties of the plot:
.... First time through we need the initial plot ....
% Set up figure with colormaps and such but leave as 'visible','on'
hPlot = plot(plot(1:subloop,value0,'*-',...
'color',[0,0,0],...
'LineWidth',1,...
'MarkerSize',5,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[0.5,0.5,0.5]);
hAxes = gca;
.... Loop over your real time updates ....
% Assuming value0 has subject's results from t=0 to t=now
hPlot.XData = value0; hPlot.YData = [1:subloop];
hAxes.XLim = [0 numTimePoints+2];
hAxes.YLim = [-10 10]
.... Continue test and update value0 ....
I think that should keep your plots current without having to save the figure as image to file then reopen the image to display to subject.
If you want to move your data one sample, you can use the circshift function. For example, if you want your new values to appear on the left hand side, you can shift all values 1 sample rightward, then add your new value in the first position.
For converting a MATLAB figure to a Psychtoolbox texture, you don't need to save, then load the temporary images. You can instead use the getframe function to capture the MATLAB figure data, which can then be given to MakeTexture to turn it into a Psychtoolbox texture.
I'm not sure what values you're actually using for subloop, value0, etc. but there is an example that I think might be close to what you want. In this example, 30 frames of figures are plotted, with each figure being on screen for 1 second. New data points are generated randomly and appear from the left hand side of the figure.
Depending on the details of your experiment, you may find that this approach is still too slow. You could also create the figure directly via Psychtoolbox drawing methods like DrawLines, etc. though that would require more effort.
try
win0 = Screen('OpenWindow', 0, 0);
Num_timepoint = 100;
subloop = 100;
value0 = zeros(1,100);
num_demo_frames = 30;
% Draw the graph
fig_h = figure('visible','off','color',[0 0 0]);
pcolor([0 Num_timepoint+2],[-10 0],ones(2,2));
hold on;
pcolor([0 Num_timepoint+2],[0 10],2*ones(2,2));
colormap([79 167 255;255 187 221]/256);
plot_h = plot(1:subloop,value0,'*-',...
'color',[0,0,0],...
'LineWidth',1,...
'MarkerSize',5,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[0.5,0.5,0.5]);
axis([0,Num_timepoint+2,-10,10]);
for f = 1:num_demo_frames
new_value = randn(1,1);
data_values = plot_h.YData;
data_values = circshift(data_values, 1);
data_values(1) = new_value;
plot_h.YData = data_values;
plot_values = getframe(fig_h);
imageDisplay=Screen('MakeTexture', win0, plot_values.cdata);
Screen('DrawTexture', win0, imageDisplay);
Screen('Flip', win0);
WaitSecs(1);
end
sca;
catch e
sca;
rethrow(e);
end

Scatter persists after setting X & YData to fewer points

EDIT: See end of post for code to automatically reproduce the issue without the mouse/keyboard input and for pictures describing the issue.
Please let me know if any of the following explanation or code requires further clarification.
I'm allowing a user to click on an axis and scatter points appear where they click. Left clicking results in larger scatter markers, and right clicking results in smaller scatter markers. Pressing enter adds a new set (represented by scatter markers switching between yellow and red). Pressing backspace should delete the most recent marker, and consecutively delete markers back through the set and into previous sets if continued to be pressed.
This works fine if I use only one size marker, however, if I allow the size to be determined by the left/right click, then upon clicking backspace to delete previous points, it will correctly delete the points until you arrive at the first point (the earliest point, not the first you try to delete) which had a size change. The still appear on the figure as if they were a separate scatter plot, even though they are one scatter plot, and the data the plot is set from has been deleted (and the scatter data set to that deleted data set).
Here is my example code:
function CodeToReplicateIssue()
a = get(0,'ScreenSize');
set(0,'DefaultFigurePosition',[a(3)*.3,a(4)*.1,a(3)*.4,a(4)*.8]);
mainFig = figure;
set(mainFig, 'WindowButtonDownFcn', #mouseDown);
set(mainFig,'WindowKeyPressFcn', #keyPress);
pfTitle = 'Next Set: Enter Delete Previous Point: Backspace';
colormap('gray');
imagesc(zeros(512,512));
set(gca,'XTick',[]);
set(gca,'YTick',[]);
axis image;
title(pfTitle)
hold on;
cStrs2 = {'r','y'};
pfTracks = {[]};
pfTracksRef = {[]};
pfTrackI = 1;
pfScatters{1} = scatter([],[],5,cStrs2{2});
function mouseDown(source,callbackData)
%display('mouseDown');
pos = get (gca, 'CurrentPoint');
pos = pos(1,1:2);
pfTracksRef{pfTrackI}(end+1) = ~strcmp(get(source,'SelectionType'),'alt');
pfTracks{pfTrackI}(end+1,1:2) = pos;
if(numel(pfScatters)<pfTrackI)
pfScatters{pfTrackI} = scatter(pos(1),pos(2),getSizes(pfTrackI),cStrs2{mod(pfTrackI,2)+1});
else
set(pfScatters{pfTrackI},{'XData','YData','SizeData'},{pfTracks{pfTrackI}(:,1),pfTracks{pfTrackI}(:,2),getSizes(pfTrackI)});
end
end
function keyPress(source,eventdata)
%display(eventdata.Key);
if(strcmp(eventdata.Key,'return'))
if(isempty(pfTracks{pfTrackI}))
%do nothing
else
refreshScatter('pfScatters');
pfTrackI = pfTrackI+1;
pfTracks{pfTrackI} = [];
pfTracksRef{pfTrackI} = [];
end
elseif(strcmp(eventdata.Key,'backspace'))
if(isempty(pfTracks{pfTrackI}) && numel(pfTracks)>1)
pfTracks(pfTrackI) = [];
pfTracksRef(pfTrackI) = [];
if(numel(pfScatters) >= pfTrackI)
pfScatters(pfTrackI:end) = [];
end
pfTrackI = pfTrackI -1;
end
if(numel(pfTracks{pfTrackI}))
pfTracks{pfTrackI}(end,:) = [];
pfTracksRef{pfTrackI}(end) = [];
end
refreshScatter('pfScatters');
end
end
function refreshScatter(strOption)
if(nargin<1)
strOption = '';
end
if(any(strcmp(strOption,{'','pfScatters'})))
for n = 1:numel(pfScatters)
if(numel(pfTracks{n})>0)
set(pfScatters{n},{'XData','YData','SizeData','MarkerEdgeColor'},{pfTracks{n}(:,1),pfTracks{n}(:,2),getSizes(n),cStrs2{mod(n,2)+1}});
else
set(pfScatters{n},{'XData','YData'},{[],[]});
end
end
end
end
function scatterSize = getSizes(indx)
scatterSize = 9*(pfTracksRef{indx}(:)+1)-6;
end
end
Line 32:
pfTracksRef{pfTrackI}(end+1) = ~strcmp(get(source,'SelectionType'),'alt');
This is where the left or right mouse button is recorded, later used in getSizes(), with the output used for calls to scatter (or when setting the scatter series SizeData).
I can't figure out why the points created before a size change persist after deleteing the associated data and setting the scatter values to that data. It seems like the first time a size is changed a new scatter series is created.
Any light that could be shed on this issue would be appreciated, and any fixes would be doubly appreciated.
Thanks.
EDIT: One way to see what I'm talking about is to do the following:
Left click several times (lets say 5), spaced out so you can see them all.
Push Enter
Left click 5 more times, again spaced out, note they are in red (this is a desired functionality for my final product).
Press backspace until all the points are no longer visible. This is the correct funcitonality.
Incorrect:
Left click 1 time, right click 4 times.
Press enter
Left click 1 time, right click 4 times.
Backspace repeatedly (10 times). Note that the first 2 points (1 large and 1 small) from both the red and the yellow persist, even though you have clicked backspace enough times to delete everything, which is what happened when there was no size change.
EDIT: I've added code below that replicates the issue I am seeing using some loops and doing away with requiring mouse and keyboard input.
function ReplicateIssueWithLoops()
mainFig = figure;
imshow(zeros(512,512));
hold on;
axis image;
for hh = 1:2
%two runs, the first works fine, the second fails to remove scatter
%points from the figure.
cStrs = {'r','y'};
tracks = {[]};
tracksSizeRef = {[]};
trackI = 1;
scatters{1} = scatter([],[],5,cStrs{2});
for ii = 1:2
%simulates 2 sets
for jj = 1:5
%simulates 5 left clicks each
tracks{ii}(end+1,:) = [256,(ii*6+jj)*20];
if(hh ==1) %Simulates all left clicks
tracksSizeRef{ii}(end+1) = 12;
else %Simulates 1 left click and 4 right clicks
tracksSizeRef{ii}(end+1) = ((jj==1)+1)*9-6;
end
if(numel(scatters)<ii)
scatters{ii} = scatter(tracks{ii}(:,1),tracks{ii}(:,2),tracksSizeRef{ii},cStrs{mod(ii,2)+1});
else
set(scatters{ii},{'XData','YData','SizeData'},{tracks{ii}(:,1),tracks{ii}(:,2),tracksSizeRef{ii}});
end
pause(0.25);
end
tracks{ii+1}=[];
tracksSizeRef{ii+1} = [];
end
trackI = ii;
for kk = 1:10
%simulates 10 backspaces
if(isempty(tracks{trackI}) && numel(tracks)>1)
tracks(trackI) = [];
tracksSizeRef(trackI) = [];
if(numel(scatters) >= trackI)
scatters(trackI:end) = [];
end
trackI = trackI -1;
end
if(numel(tracks{trackI}))
tracks{trackI}(end,:) = [];
tracksSizeRef{trackI}(end) = [];
end
refreshScatters();
pause(0.25);
end
end
function refreshScatters()
for n = 1:numel(scatters)
if(numel(tracks{n})>0)
set(scatters{n},{'XData','YData','SizeData','MarkerEdgeColor'},{tracks{n}(:,1),tracks{n}(:,2),tracksSizeRef{n},cStrs{mod(n,2)+1}});
else
set(scatters{n},{'XData','YData'},{[],[]});
end
end
end
end
Below are pictures demonstrating how the issue looks on my end. In left to right order the pictures show 1) at the end of the first 10 points, 2) after deleting the first 10 points , 3) after the second ten points (the set with size difference), 4) after deleting the second 10 points (4 remain, the two from each set when the size was first changed).

Switching values to plot using keyboard input

I have sets of data in a matrix. I want to plot on set and then use a keyboard input to move to another one. It's simply possible this way:
for t=1:N
plot(data(:,t))
pause
end
but I want to move forward and backward in time t (e.g. using arrows). OK, it could be done like this:
direction = input('Forward or backward?','s')
if direction=='forward'
plot(data(:,ii+1))
else
plot(data(:,ii-1))
end
but isn't there something more elegant? (On one click without getting the figure of the sight - it's a big full sreen figure.)
You can use mouse clicks combined with ginput. What you can do is put your code in a while loop and wait for the user to click somewhere on the screen. ginput pauses until some user input has taken place. This must be done on the figure screen though. When you're done, check to see which key was pushed then act accordingly. Left click would mean that you would plot the next set of data while right click would mean that you plot the previous set of data.
You'd call ginput this way:
[x,y,b] = ginput(1);
x and y denote the x and y coordinates of where an action occurred in the figure window and b is the button you pushed. You actually don't need the spatial coordinates and so you can ignore them when you're calling the function.
The value of 1 gets assigned a left click and the value of 3 gets assigned a right click. Also, escape (on my computer) gets assigned a value of 27. Therefore, you could have a while loop that keeps cycling and plotting things on mouse clicks until you push escape. When escape happens, quit the loop and stop asking for input.
However, if you want to use arrow keys, on my computer, the value of 28 means left arrow and the value of 29 means right arrow. I'll put comments in the code below if you desire to use arrow keys.
Do something like this:
%// Generate random data
clear all; close all;
rng(123);
data = randn(100,10);
%// Show first set of points
ii = 1;
figure;
plot(data(:,ii), 'b.');
title('Data set #1');
%// Until we decide to quit...
while true
%// Get a button from the user
[~,~,b] = ginput(1);
%// Left click
%// Use this for left arrow
%// if b == 28
if b == 1
%// Check to make sure we don't go out of bounds
if ii < size(data,2)
ii = ii + 1; %// Move to the right
end
%// Right click
%// Use this for right arrow
%// elseif b == 29
elseif b == 3
if ii > 1 %// Again check for out of bounds
ii = ii - 1; %// Move to the left
end
%// Check for escape
elseif b == 27
break;
end
%// Plot new data
plot(data(:, ii), 'b.');
title(['Data set #' num2str(ii)]);
end
Here's an animated GIF demonstrating its use:
This demo shows you how to use either the left and right arrows of the keyboard to switch data set or even the mouse wheel.
It uses the KeyPressFcn and/or WindowScrollWheelFcn event of the figure.
function h = change_dataset_demo
%// sample data
nDataset = 8 ;
x = linspace(0,2*pi,50).' ; %'// ignore this comment
data = sin( x*(1:nDataset) ) ;
index.max = nDataset ;
index.current = 1 ;
%// Plot the first one
h.fig = figure ;
h.plot = plot( data(:,index.current) ) ;
%// store data in figure appdata
setappdata( h.fig , 'data', data )
setappdata( h.fig , 'index', index )
%// set the figure event callbacks
set(h.fig, 'KeyPressFcn', #KeyPressFcn_callback ) ; %// Set figure KeyPressFcn function
set(h.fig, 'WindowScrollWheelFcn',#mouseWheelCallback) %// Set figure Mouse wheel function
guidata( h.fig , h )
function mouseWheelCallback(hobj,evt)
update_display( hobj , evt.VerticalScrollCount )
function KeyPressFcn_callback(hobj,evt)
if ~isempty( evt.Modifier ) ; return ; end % Bail out if there is a modifier
switch evt.Key
case 'rightarrow'
increment = +1 ;
case 'leftarrow'
increment = -1 ;
otherwise
% do nothing
return ;
end
update_display( hobj , increment )
function update_display( hobj , increment )
h = guidata( hobj ) ;
index = getappdata( h.fig , 'index' ) ;
data = getappdata( h.fig , 'data' ) ;
newindex = index.current + increment ;
%// roll over if we go out of bound
if newindex > index.max
newindex = 1 ;
elseif newindex < 1
newindex = index.max ;
end
set( h.plot , 'YData' , data(:,newindex) ) ;
index.current = newindex ;
setappdata( h.fig , 'index', index )
This will roll over when the end of the data set is reached.
done a little gif too but it's a lot less impressive because it does not show the keyboard/mouse action, only the graph updates :

MATLAB: Print contents of uipanel to PNG image

I don't know how to accomplish the following in MATLAB. I have a figure which looks like this:
In the figure, I have a panel with many subplots and a scrollbar that allows me to view a portion of the panel.
I want to save the whole contents of the panel to a PNG image file (not just the visible portion), i.e. I want to have a file which is a tall rectangle, and doesn't require scrolling.
The code for generating the figure is as follows:
function draw(obj)
figure;
panel1 = uipanel('Parent',1);
panel2 = uipanel('Parent',panel1);
panelheight = obj.nIterations / 2;
set(panel1,'Position',[0 0 0.97 1]);
set(panel2,'Position',[0 1-panelheight 1 panelheight]); %%
nPlot = 1;
for i=1:obj.nIterations
models = obj.iterations{i};
for nModel=1:length(models)
subplot(obj.nIterations,length(models)*2,nPlot);
nPlot = nPlot + 1;
drawTransitions(models{nModel});
set(gca,'Parent',panel2);
subplot(obj.nIterations,length(models)*2,nPlot);
nPlot = nPlot + 1;
drawRewards(models{nModel});
set(gca,'Parent',panel2);
end
end
s = uicontrol('Style','Slider','Parent',1,...
'Units','normalized','Position',[0.97 0 0.03 1],...
'Value',1,'Callback',{#slider_callback1,panel2,panelheight});
end
I have tried the following, without success.
The saveas funstion saves the whole figure, not just the panel. Also, it clips the invisible portion of the panel.
export_fig(panel2.'file.png') gives just a solid gray image.
Why don't you just scroll your panel and grab the frames and concatenate them all together? Here's some code that will basically do that. I would have posted am image, but I guess I don't have enough points for that. You may need to fiddle with the scrolling, and maybe making the slider invisible, but it works.
function printPanel(pnl,filename)
fig = figure(ancestor(pnl,'figure'));
pnl_units = get(pnl,'units');
fig_units = get(fig,'units');
set(pnl,'units','pixels')
set(fig,'units','pixels')
pnl_rect = getpixelposition(pnl);
fig_rect = getpixelposition(fig);
pnl_height = pnl_rect(4);
fig_height = fig_rect(4);
pnl_rect(2) = -pnl_height;
set(pnl,'position',pnl_rect)
N = ceil(pnl_height/fig_height);
CDATA = cell(N,1);
for i = 1:N
F = getframe(fig);
CDATA{i} = F.cdata;
pnl_rect(2) = pnl_rect(2)+fig_height;
set(pnl,'position',pnl_rect)
drawnow
end
set(pnl,'units',pnl_units)
set(fig,'units',fig_units)
imwrite(cat(1,CDATA{:}),filename)
end
You could get rid of the ui elements and just make a figure with all the subplots, and then export that one, using e.g. print -dpng ....
saveas takes a handle as a first argument. Maybe this does not have to be a figure or model handle, but could be a reference to the contents of the panel.

Matlab loop until keystroke

I have a loop in which I keep entering points into figure using ginput. I would like the loop to run until user presses a key, Heres what I have:
function enter_points()
f = figure();
axis([-1 1 -1 1]);
coorX = [];
coorY = [];
while 1
[x, y] = ginput(1);
coorX = [coorX x];
coorY = [coorY y];
waitforbuttonpress;
key = get(f,'CurrentCharacter');
if (key == 'e')
display('End of cycle.')
break;
else
display('Enter next point')
end
end
coor = [transpose(coorX) transpose(coorY)];
display(size(coor));
display(coor);
end
The problem with this code is, that I haveto press a key to continue entering points. Another problem is, that Matlab sometimes freezes when running this code (Im not sure it the code is the reason or something else is). And how to detect pressing "enter" instead of "e"? Thanks for any help
Why don't you use the builtin:
[X,Y] = ginput gathers an unlimited number of points until the return
key is pressed.
AFAIK, the general way to handle your problem in OOP and Event Oriented Programming is to generate a listener to a given event, in your case a keypress-like event. When defining the listener, one usually passes a callback function to be called(-back) when the event is generated.
One may define listeners e.g. in matlab GUIs (reference). Nonetheless, I am not sure one can do that when the event is generated at the console level.
bdecaf already gave you the simplest answer, but you could also try these couple of changes:
1) Change this:
[x, y] = ginput(1);
to this:
[x, y, key] = ginput(1);
ginput also returns the key you press and even which mouse button (1:LB, 2:RB or 3:MB).
2) Delete these lines:
waitforbuttonpress;
key = get(f,'CurrentCharacter');
With these changes your routine should work as intended. No pause between points, and exit when pressing [e].