from psychopy import core, visual
from haptic import Experiment, HapticDevice
class ExampleExperiment(Experiment):
"""The Experiment class is used as a parent
class for all individual experiments. This
allows more automization while allowing
customization. This is generally done
through overwriting particular functions
defined in the Experiment class.
For example, the member function `makeStims`
will always be called on initialization
without the end user needing to remember
to add that call. Simply creating a member
function with that name will cause it
to be run.
Others are optional callbacks where certain
member functions will be called at specific
times. If an end user does not define their
own `calibrateRumbleDisplay` then nothing
will be shown but the calibration will still
fire. Rather than passing a callback function
class inheretence allows these callbacks to
be defined in the class.
In the case where you simply want to extend
the function of a base class method, you
can override the function here and call
`super().functionName(args)` when you
want the parent event to fire.
"""
def __init__(self):
"""Create an experiment.
The `__init__` function is where any
startup information goes. It is a
good place to define configuration
variables like `invert_y_axis`.
Remember to call the parent `__init__`
function using `super().__init__(window)`
or else some functions will not work.
"""
win = visual.Window([400, 400]) # Create the window
super().__init__(win) # Initialize the parent class
self.invert_y_axis = True # Set config variable
self.calibrate() # Defined in the parent class
self.run(2000, self.moveLoop)
# Callback functions used in `run` calls should
# generally be member functions as this allows
# them to access the internal attributes and
# class methods.
def calibrateRumbleDisplay(self):
"""`Experiment.calibrate` looks for this method
and will run its contents on every window flip.
"""
self.stims["calibrateRumbleText"].draw()
self.stims["buttons"]["south"].draw()
self.stims["buttons"]["east"].draw()
def calibrateStickDisplay(self):
"""`Experiment.calibrate` looks for this method
and will run its contents on every window flip.
"""
self.stims["calibrateStickText"].draw()
self.stims["buttons"]["west"].draw()
def makeStims(self):
"""`Experiment.__init__` looks for this method
and will run its contents on initialization.
"""
cursor = visual.Circle(self.window, fillColor="blue", radius=0.01)
buttonImage = {
"north": "img/48px-PlayStation_button_T.png",
"south": "img/48px-PlayStation_button_X.png",
"east": "img/48px-PlayStation_button_C.png",
"west": "img/48px-PlayStation_button_S.png",
}
buttonRet = {}
for k, v in buttonImage.items():
i = visual.ImageStim(
self.window, v, name=k.rstrip(".png").replace("_", " ")
)
buttonRet[k] = i
calibrateRumbleText = visual.TextStim(
self.window,
text="When you feel a vibration, please press\n\n\n\nOtherwise press",
)
calibrateStickText = visual.TextStim(
self.window,
text="Please move the right stick in a circle, then release it and press",
)
calibrateStickText.pos += (0, 0.2)
buttonRet["east"].pos -= (0, 0.6)
buttonRet["south"].pos -= (0, 0.1)
buttonRet["west"].pos -= (0, 0.2)
self.stims = {
"cursor": cursor,
"buttons": buttonRet,
"calibrateRumbleText": calibrateRumbleText,
"calibrateStickText": calibrateStickText,
}
def setJoystick(self):
"""`Experiment.__init__` looks for this method
and will run its contents on initialization.
"""
self.joystick = HapticDevice()
def moveLoop(self, frameN, *args, **kwargs):
"""This is a callback used in the `__init__`
method above.
See also:
Experiment.run
"""
self.stims["cursor"].pos += self.stickPos()
return [self.stims["cursor"]]
try:
ExampleExperiment()
except KeyboardInterrupt:
pass