How to create your own stimuli¶
Do you have to be able to do this to use o_ptb? NO! You can just use normal Screen()
commands for your visual stimuli and use the +o_ptb.stimuli.auditory.FromMatrix
audio stimulus.
However, the o_ptb base classes for these stimuli come with some advantages. For instance, you can automatically scale and move your visual stimuli. And it is not that hard!
Let’s create a rectangle that we can move and scale¶
So, we want to create a new class that shows a filled rectangle. It should appear at the center of the screen by default and let us define the initial size and color.
So, create a new class called “Rectangle” by doing a right-click in the “Current Folder” section and choose “New File -> Class”.
You will start out with something like this:
classdef Rectangle
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties
end
methods
end
end
The first thing we will do is to make our class inherit from the base class of all visual stimuli:
classdef Rectangle < o_ptb.stimuli.visual.Base
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties
end
methods
end
end
Very good. Now your class is officially a visual stimulus! Now we need to teach it, what it needs to do when you want it to draw something. The +o_ptb.stimuli.visual.Base
class defines a method called on_draw(obj, ptb)
. This method gets called whenever o_ptb wants the class to draw something on the screen. So, we need to implement that:
classdef Rectangle < o_ptb.stimuli.visual.Base
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties
end
methods
function on_draw(obj, ptb)
ptb.screen('FillRect', [0 0 0], CenterRect([0 0 300 300], ptb.win_rect));
end %function
end
end
You can try this out now:
my_rect = Rectangle
ptb.draw(my_rect);
ptb.flip
And you see that a black rectangle is displayed at the center of the screen.
But we want the class to be more flexible. The next step would be to get the hard coded colors out of the on_draw
methods and use properties instead:
classdef Rectangle < o_ptb.stimuli.visual.Base
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties
width = 300;
height = 300;
color = [0 0 0];
end %properties
methods
function on_draw(obj, ptb)
ptb.screen('FillRect', obj.color, CenterRect([0 0 obj.width obj.height], ptb.win_rect));
end %function
end
end
This class does the same as the old version. But we defined the width, height and color as properties.
Still, we cannot choose from outside of the class, how big we want the rectangle to be and what color we want. So, we add a constructor method:
classdef Rectangle < o_ptb.stimuli.visual.Base
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties
width;
height;
color;
end %properties
methods
function obj = Rectangle_other(width, height, color)
obj@o_ptb.stimuli.visual.Base();
obj.width = width;
obj.height = height;
obj.color = color;
end %function
function on_draw(obj, ptb)
ptb.screen('FillRect', obj.color, CenterRect([0 0 obj.width obj.height], ptb.win_rect));
end %function
end
end
You might wonder what the first line in the constructor means? (obj@o_ptb.stimuli.visual.Base();
). Remember that we inherit from another class. And that class also has a constructor that needs to be called. This line does that for you.
The rest is pretty straight forward. Our constructor takes three arguments and we assign it to the properties of the class.
We can now create and display our rectangle like this:
my_rect = Rectangle(200, 200, [0 0 0]);
ptb.draw(my_rect);
ptb.flip
Now for the last-but-one step: The +o_ptb.stimuli.visual.Base
class defines two very handy methods: scale
and move
. In order for these to work, we need to make use of the +o_ptb.stimuli.visual.Base
classe’s property called destination_rect
. This holds the destination rectangle where our stimulus should appear. The scale
and move
method recalculate its coordinates.
Here is how it works now:
classdef Rectangle < o_ptb.stimuli.visual.Base
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties
width;
height;
color;
end %properties
methods
function obj = Rectangle(width, height, color)
obj@o_ptb.stimuli.visual.Base();
ptb = o_ptb.PTB.get_instance;
obj.width = width;
obj.height = height;
obj.color = color;
obj.destination_rect = CenterRect([0 0 obj.width obj.height], ptb.win_rect);
end %function
function on_draw(obj, ptb)
ptb.screen('FillRect', obj.color, obj.destination_rect);
end %function
end
end
Now check this out:
my_rect = Rectangle(200, 150, [0 0 0]);
for i = 1:200
my_rect.move(1, 1);
ptb.draw(my_rect);
ptb.flip();
end %for
There is only one problem remaining: The three properties can be modified by anyone. This would lead to unexpected behavior, because I would expect that the width of the object changes if I change the width property. The easies solution is to just prohibit these properties to be read and modified from outside the class:
classdef Rectangle < o_ptb.stimuli.visual.Base
%RECTANGLE Summary of this class goes here
% Detailed explanation goes here
properties (Access=protected)
width;
height;
color;
end %properties
methods
function obj = Rectangle(width, height, color)
obj@o_ptb.stimuli.visual.Base();
ptb = o_ptb.PTB.get_instance;
obj.width = width;
obj.height = height;
obj.color = color;
obj.destination_rect = CenterRect([0 0 obj.width obj.height], ptb.win_rect);
end %function
function on_draw(obj, ptb)
ptb.screen('FillRect', obj.color, obj.destination_rect);
end %function
end
end
That’s it!