I’ve seen a few posts with questions about macbooks with retina screens & external displays (see Understanding the retina screen scaling of stimuli, Stimuli on mac retina display are in bottom left corner, Problem when extending monitor (retina display and windows creation). I thought it might be useful to share how I fixed my particular problem.
I needed to get an older experiment running on a new macbook. After fixing some exec calls (see Problem with exec('{} = thisTrial[paramName]'.format(paramName))), the experiment ran, but the window on the external display (screen 1) was in the wrong place, and the stimuli were two big and off-center. This is a known problem with retina displays and pyglet (see some of the above posts), but I needed a quick solution rather than waiting for updates to pyglet or psychopy. Fixing the window was easy enough: I set pos = [0,0], and for some reason I also had to set allowGUI=True. Not exactly what I wanted but I could live with it. Next I had to reposition and rescale all the stimuli, but I didn’t want to edit 70 different function calls.
Decorators to the rescue! Python decorators let you redefine python functions. In this case, I needed to “intercept” TextStim and ImageStim, and change the height, size, pos, and wrapWidth arguments. That looked like this:
# Decorator to fix retina problems (JCS 20191212)
# calculate correct offset in degrees of visual angle (the units used by our window)
from psychopy.tools import monitorunittools
pos_offset_0 = -0.25*monitorunittools.pix2deg(resolution[0], mon)
pos_offset_1 = 0.25*monitorunittools.pix2deg(resolution[1], mon)
def StimDecorator(func):
def wrapper(*args, **kwargs):
if kwargs['win'].screen == 1:
if 'height' in kwargs:
kwargs['height'] = 0.5*kwargs['height']
if 'pos' in kwargs:
kwargs['pos'] = (0.5*kwargs['pos'][0] + pos_offset_0,
0.5*kwargs['pos'][1] + pos_offset_1)
if 'size' in kwargs:
kwargs['size'] = (0.5*kwargs['size'][0], 0.5*kwargs['size'][1])
if 'wrapWidth' in kwargs and kwargs['wrapWidth']:
kwargs['wrapWidth'] = 0.5*kwargs['wrapWidth']
return func(*args, **kwargs)
return wrapper
visual.TextStim = StimDecorator(visual.TextStim)
visual.ImageStim = StimDecorator(visual.ImageStim)
Voila! Things look okay, I didn’t have to change much code, and I can easily change it again or remove it in the future.