Psychopy useful functions
The following document contains a collection of functions created to run experiments in Psychopy.
Creating coloured gratings (pseudo-Gabor).
from psychopy import visual, event, monitors, tools
from psychopy.visual import filters
from psychopy.tools import monitorunittools as mut
import numpy as np
import math
import os
###############################################################################
# VARIABLES
###############################################################################
SAVE_IMAGE = True #Set to true if you want to output an image
DIFFERENT_BRIGHTNESS = True #Should the stimuli have different luminance?
SWITCH_SIDE = True #Invert the left and right grating
WHAT_TO_DRAW = "right" #Either "both", "left" or "rigth"
# Parameters for the gratings
param_stim = {
"resolution": 5, #Size of the stimulus grating in deg (this will be coverted in pix later)
"mask_resolution": 2**11, #Resolution of the mask used to render the gratings as circles (must be a power of 2)
"ori_left": 45, #Orientation of the first grating
"ori_right": -45, #Orientation of the second grating
"pos_left": (0, 0), #Position of the first grating (0,0 is the center of the screen)
"pos_right": (0, 0), #Position of the second grating (0,0 is the center of the screen)
"cycles": 4*5, #Spatial frequency of the gratings. This should be resolution X cycles per deg
"vergence_cycles": 5, #Spatial frequency of the gratings used to create the vergence patterns
"vergence_sf": 0.03, #This value controls the number of gratings used in the vergence patterns (use values < 0.5)
"alpha_left": 1,
"max_value_first": -0.3 #Red: Psychopy [-0.0030, -1,-1], RGB [89,0,0], HSV[0,100,35]
}
# Screen and window parameters - for Psychopy
param_pc = {
"resolution": (1920, 1080),
"width": 34.2}
# Directories and file names for the image output
this_dir = os.path.dirname(os.path.abspath(__file__))
image_name = "red_single_03-1-1.png" #Name of the file to output at the end
###############################################################################
# WINDOW
###############################################################################
# Create monitor and windows
mon = monitors.Monitor(
name="desk_monitor",
width=param_pc["width"],
distance=57
)
mon.setSizePix = param_pc["resolution"]
win = visual.Window(
size=param_pc["resolution"],
monitor=mon,
units="pix",
allowGUI=False,
screen=1,
fullscr=False,
color=(-1, -1, -1),
colorSpace='rgb',
blendMode='avg',
winType='pyglet',
useFBO=True)
###############################################################################
# FROM DEG TO PIX
###############################################################################
# Convert to pix
param_stim["resolution"] = int(mut.deg2pix(param_stim["resolution"], mon))
# Round pix to the closest power of 2. NOTE this works for "low" values but
# cannot be generalized to high values (eg. 100000). However, here we work with
# values in the 100 range (eg. 256 pix).
param_stim["resolution"] = 2**round(math.log2(param_stim["resolution"]))
###############################################################################
# STIMULI
# To create the gratings we start by creating a black texture defined as a
# matrix of dimension [dim1, dim2, 3], where the three layers represent the RGB
# colours. Then, we will replace the layer of the colour we are interested in
# with a grating, which is a [dim1, dim2] array, conatining values from -1 to 1
# representing the intensity of the colour. Doing this will create a grating
# stimulus of the desired colour.
# For the red stimulusg, we are interested in manipulating its brightness. To do
# so, we define a colour in HSV space and convert it into RGB (use tool online)
# Then we modify the grating range, so that it goes from -1 (black) to N, where
# N is the values obtained online
###############################################################################
# Switch side if requested
if SWITCH_SIDE:
pos_left = param_stim["pos_left"]
pos_right = param_stim["pos_right"]
param_stim["pos_left"] = pos_right
param_stim["pos_right"] = pos_left
# ---Coloured Gabors---#
# Create a black texture for both stimuli...
grating_left = np.ones((param_stim["resolution"], param_stim["resolution"], 3)) * -1
grating_right = np.ones((param_stim["resolution"], param_stim["resolution"], 3)) * -1
# GREEN --> For the green stimulus we simply overaly the grating to the G channel
grating_right[:, :, 1] = filters.makeGrating(res=param_stim["resolution"],
ori=param_stim["ori_right"],
cycles=param_stim["cycles"],
gratType="sin")
# RED --> For the red stimulus we need to do some more work...
# Create a grating
sin_mask = filters.makeGrating(res=param_stim["resolution"],
ori=param_stim["ori_left"],
cycles=param_stim["cycles"],
gratType="sin")
# If different luminance is requested
if DIFFERENT_BRIGHTNESS:
# Scale only positive values to change the colour (RED) but not the black through the Rohan's transform
# NOTE: it's not a real transform...it was a tip from a friend
scale_factor = 0.5*(param_stim["max_value_first"]+1)
sin_mask_scaled = scale_factor * (sin_mask + 1) - 1
grating_left[:,:,0] = sin_mask_scaled
# If no difference in brightness is required, apply the grating as above
else:
grating_left[:, :, 0] = sin_mask
#---Vergence Gratings---#
# Create a gray texture (Psychopy [0,0,0] is gray)...
grating_vergence = np.zeros((param_stim["resolution"], param_stim["resolution"], 3))
#...then overimpose a grid on all the three RGB channels
grating_vergence[:, :, 0] = filters.makeGrating(res=param_stim["resolution"],
cycles=param_stim["vergence_cycles"],
ori=45,
gratType='sin')
grating_vergence[:, :, 1] = filters.makeGrating(res=param_stim["resolution"],
cycles=param_stim["vergence_cycles"],
ori=45,
gratType='sin')
grating_vergence[:, :, 2] = filters.makeGrating(res=param_stim["resolution"],
cycles=param_stim["vergence_cycles"],
ori=45,
gratType='sin')
#---Circle Mask---#
# Generate a nice smooth (at least almost) circle mask
mask = filters.makeMask(matrixSize=param_stim["mask_resolution"],
shape="circle")
#---Generate Stimuli with Psychopy---#
# left grating stimulus
stim_left = visual.GratingStim(
name="stimL",
win=win,
size=(param_stim["resolution"], param_stim["resolution"]),
pos=param_stim["pos_left"],
tex=grating_left,
mask=mask,
units="pix")
# Right grating stimulus
stim_right = visual.GratingStim(
name="stimR",
win=win,
size=(param_stim["resolution"], param_stim["resolution"]),
pos=param_stim["pos_right"],
tex=grating_right,
mask=mask,
units="pix")
# Left vergence pattern
vergence_left = visual.GratingStim(
name="vergL",
win=win,
size=(param_stim["resolution"]+50, param_stim["resolution"]+50),
pos=param_stim["pos_left"],
tex=grating_vergence,
mask=mask,
units="pix",
sf=param_stim["vergence_sf"])
# Right vergence pattern
vergence_right = visual.GratingStim(
name="vergR",
win=win,
size=(param_stim["resolution"]+50, param_stim["resolution"]+50),
pos=param_stim["pos_right"],
tex=grating_vergence,
mask=mask,
units="pix",
sf=param_stim["vergence_sf"])
# Left fixation dot
fixation_left = visual.ShapeStim(
win=win,
name='polygon',
size=(param_stim["resolution"]/50, param_stim["resolution"]/50),
vertices='circle',
ori=0.0,
pos=param_stim["pos_left"],
anchor='center',
lineWidth=1.0,
colorSpace='rgb',
lineColor='white',
fillColor='white',
opacity=None,
depth=0.0,
interpolate=True)
fixation_right = visual.ShapeStim(
win=win,
name='polygon',
size=(param_stim["resolution"]/50, param_stim["resolution"]/50),
vertices='circle',
ori=0.0,
pos=param_stim["pos_right"],
anchor='center',
lineWidth=1.0,
colorSpace='rgb',
lineColor='white',
fillColor='white',
opacity=None,
depth=0.0,
interpolate=True)
###############################################################################
# DRAW
###############################################################################
if WHAT_TO_DRAW == "both":
# Draw stimuli on buffer
vergence_left.draw()
vergence_right.draw()
stim_left.draw()
stim_right.draw()
fixation_left.draw()
fixation_right.draw()
elif WHAT_TO_DRAW == "left":
vergence_left.draw()
stim_left.draw()
fixation_left.draw()
else:
vergence_right.draw()
stim_right.draw()
fixation_right.draw()
# Present stimuli on the window
win.flip()
# Save stimuli if requested
if SAVE_IMAGE:
frame = win.getMovieFrame()
frame.save(os.path.join(this_dir, image_name))
# Terminate when a key is pressed
event.waitKeys()
# Close the Psychopy window
win.close()