def _get_psychopy_pos(self, p): """Convert Tobii ADCS coordinates to PsychoPy coordinates. Args: p: Gaze position (x, y) in Tobii ADCS. Returns: Gaze position in PsychoPy coordinate systems. """ if self.win.units == 'norm': return (2 * p[0] - 1, -2 * p[1] + 1) elif self.win.units == 'height': return ((p[0] - 0.5) * (self.win.size[0] / self.win.size[1]), -p[1] + 0.5) elif self.win.units in ['pix', 'cm', 'deg', 'degFlat', 'degFlatPos']: p_pix = ((p[0] - 0.5) * self.win.size[0], (-p[1] + 0.5) * self.win.size[1]) if self.win.units == 'pix': return p_pix elif self.win.units == 'cm': return (pix2cm(p_pix[0], self.win.monitor), pix2cm(p_pix[1], self.win.monitor)) elif self.win.units == 'deg': return (pix2deg(p_pix[0], self.win.monitor), pix2deg(p_pix[1], self.win.monitor)) else: return (pix2deg( np.array(p_pix), self.win.monitor, correctFlat=True)) else: raise ValueError('unit ({}) is not supported.'.format( self.win.units))
def get_psychopy_pos_from_trackbox(self, p, units=None): """ Convert Tobii TBCS coordinates to PsychoPy coordinates. :param p: Position (x, y) """ if units is None: units = self.win.units if units == 'norm': return (-2 * p[0] + 1, -2 * p[1] + 1) elif units == 'height': return ((-p[0] + 0.5) * (self.win.size[0] / self.win.size[1]), -p[1] + 0.5) elif units in ['pix', 'cm', 'deg', 'degFlat', 'degFlatPos']: p_pix = ((-2 * p[0] + 1) * self.win.size[0] / 2, (-2 * p[1] + 1) * self.win.size[1] / 2) if units == 'pix': return p_pix elif units == 'cm': return (pix2cm(p_pix[0], self.win.monitor), pix2cm(p_pix[1], self.win.monitor)) elif units == 'deg': return (pix2deg(p_pix[0], self.win.monitor), pix2deg(p_pix[1], self.win.monitor)) else: return (pix2deg(np.array(p_pix), self.win.monitor, correctFlat=True)) else: raise ValueError('unit ({}) is not supported.'.format( self.win.units))
def get_psychopy_pos(self, p): """ Convert PsychoPy position to Tobii coordinate system. :param p: Position (x, y) """ p = (p[0], 1 - p[1]) #flip vert if self.win.units == 'norm': return (2 * p[0] - 1, 2 * p[1] - 1) elif self.win.units == 'height': return ((p[0] - 0.5) * self.win.size[0] / self.win.size[1], p[1] - 0.5) p_pix = ((p[0] - 0.5) * self.win.size[0], (p[1] - 0.5) * self.win.size[1]) if self.win.units == 'pix': return p_pix elif self.win.units == 'cm': return (pix2cm(p_pix[0], self.win.monitor), pix2cm(p_pix[1], self.win.monitor)) elif self.win.units == 'deg': return (pix2deg(p_pix[0], self.win.monitor), pix2deg(p_pix[1], self.win.monitor)) elif self.win.units in ['degFlat', 'degFlatPos']: return (pix2deg(np.array(p_pix), self.win.monitor, correctFlat=True)) else: raise ValueError('unit ({}) is not supported.'.format( self.win.units))
def plot_category_exemplars_2(x, y, xpos, ypos, contrast=None): '''plot sine-wave gratings at x CPD and at y degrees orientation in stimulus space --- cat, x, and y should be lists of the same length.''' # NOTE: Screen coordinate system has origin in the middle of the screen with # positive numbers going right and negative number going left screen_size = 600 x_max = np.max(xpos) y_max = np.max(ypos) x_min = np.min(xpos) y_min = np.min(ypos) # NOTE: convert stimulus coordinates into screen coordinates xp = ((xpos - np.min(xpos)) / np.max(xpos - np.min(xpos))) * (screen_size) - screen_size / 2.0 yp = ((ypos - np.min(ypos)) / np.max(ypos - np.min(ypos))) * (screen_size) - screen_size / 2.0 win = visual.Window(size=(screen_size + 150, screen_size + 150), fullscr=False, screen=0, allowGUI=False, allowStencil=False, monitor='testMonitor', color=[0, 0, 0], colorSpace='rgb', blendMode='avg', useFBO=False) mon = Monitor('testMonitor') mon.setWidth(29.0) mon.setSizePix((1440, 900)) cm_per_pix = pix2cm(1, mon) n = x.shape[0] if contrast is None: cont = [1] * n else: cont = contrast stim = [] for i in range(n): grating = visual.GratingStim(win, units='pix', mask='circle', sf=x[i] * cm_per_pix, ori=y[i], pos=(xp[i], yp[i]), contrast=cont[i], size=(2 / cm_per_pix, 2 / cm_per_pix)) stim.append(grating) [i.draw() for i in stim] win.flip() event.waitKeys(keyList=['escape'])
def _pix2windowUnits(self, win_handle, pos): win = self._iohub_server._psychopy_windows.get(win_handle) win_units = win['units'] monitor = win['monitor'] pos = np.asarray(pos) if win_units == 'pix': return pos elif win_units == 'norm': return pos * 2.0 / win['size'] elif win_units == 'cm': if monitor: return pix2cm(pos, monitor['monitor']) else: # should raise exception? print2err( "iohub Mouse error: Window is using units %s but has no Monitor definition." % win_units) elif win_units == 'deg': if monitor: return pix2deg(pos, monitor['monitor']) else: # should raise exception? print2err( "iohub Mouse error: Window is using units %s but has no Monitor definition." % win_units) elif win_units == 'height': return pos / float(win['size'][1])
def _pixToWindowUnits(self, pos): """Conversion from 'pix' units to window units. The mouse class stores mouse positions in 'pix' units. This function is used by getter and setter methods to convert position values to the units specified by the window. Parameters ---------- pos : ArrayLike Position `(x, y)` in 'pix' coordinates to convert. Returns ------- ndarray Position `(x, y)` in window units. """ pos = np.asarray(pos, dtype=np.float32) if self.win is None: return pos if self.win.units == 'pix': if self.win.useRetina: pos /= 2.0 return pos elif self.win.units == 'norm': return pos * 2.0 / self.win.size elif self.win.units == 'cm': return pix2cm(pos, self.win.monitor) elif self.win.units == 'deg': return pix2deg(pos, self.win.monitor) elif self.win.units == 'height': return pos / float(self.win.size[1])
def size(self, value): """ :ref:`x,y-pair <attrib-xy>`, :ref:`scalar <attrib-scalar>` or None (resets to default). Supports :ref:`operations <attrib-operations>`. Units are inherited from the stimulus. Sizes can be negative and can extend beyond the window. Example:: stim.size = 0.8 # Set size to (xsize, ysize) = (0.8, 0.8), quadratic. print stim.size # Outputs array([0.8, 0.8]) stim.size += (0,5, -0.5) # make wider and flatter. Is now (1.3, 0.3) Tip: if you can see the actual pixel range this corresponds to by looking at stim._sizeRendered """ value = val2array(value) # Check correct user input self._requestedSize = value #to track whether we're just using a default # None --> set to default if value == None: """Set the size to default (e.g. to the size of the loaded image etc)""" #calculate new size if self._origSize is None: #not an image from a file value = numpy.array([0.5, 0.5]) #this was PsychoPy's original default else: #we have an image - calculate the size in `units` that matches original pixel size if self.units == 'pix': value = numpy.array(self._origSize) elif self.units == 'deg': value = pix2deg(numpy.array(self._origSize, float), self.win.monitor) elif self.units == 'cm': value = pix2cm(numpy.array(self._origSize, float), self.win.monitor) elif self.units == 'norm': value = 2 * numpy.array(self._origSize, float) / self.win.size elif self.units == 'height': value = numpy.array(self._origSize, float) / self.win.size[1] self.__dict__['size'] = value self._calcSizeRendered() if hasattr(self, '_calcCyclesPerStim'): self._calcCyclesPerStim() self._needUpdate = True
def _get_psychopy_pos(self, p, units=None): """Convert Tobii ADCS coordinates to PsychoPy coordinates. Args: p: Gaze position (x, y) in Tobii ADCS. units: The PsychoPy coordinate system to use. Returns: Gaze position in PsychoPy coordinate systems. For example: (0,0). """ if units is None: units = self.win.units if units == "norm": return (2 * p[0] - 1, -2 * p[1] + 1) elif units == "height": return ((p[0] - 0.5) * (self.win.size[0] / self.win.size[1]), -p[1] + 0.5) elif units in ["pix", "cm", "deg", "degFlat", "degFlatPos"]: p_pix = self._tobii2pix(p) if units == "pix": return p_pix elif units == "cm": return tuple(pix2cm(pos, self.win.monitor) for pos in p_pix) elif units == "deg": tuple(pix2deg(pos, self.win.monitor) for pos in p_pix) else: return tuple( pix2deg(np.array(p_pix), self.win.monitor, correctFlat=True)) else: raise ValueError("unit ({}) is not supported.".format(units))
def _pix2windowUnits(self, pos): if self.win.units == "pix": return pos elif self.win.units == "norm": return pos * 2.0 / self.win.size elif self.win.units == "cm": return pix2cm(pos, self.win.monitor) elif self.win.units == "deg": return pix2deg(pos, self.win.monitor) elif self.win.units == "height": return pos / float(self.win.size[1])
def _pix2windowUnits(self, pos): if self.win.units == 'pix': return pos elif self.win.units == 'norm': return pos * 2.0 / self.win.size elif self.win.units == 'cm': return pix2cm(pos, self.win.monitor) elif self.win.units == 'deg': return pix2deg(pos, self.win.monitor) elif self.win.units == 'height': return pos / float(self.win.size[1])
def size(self, value): """The size (w,h) of the stimulus in the stimulus :ref:`units <units>` Value should be :ref:`x,y-pair <attrib-xy>`, :ref:`scalar <attrib-scalar>` (applies to both dimensions) or None (resets to default). :ref:`Operations <attrib-operations>` are supported. Sizes can be negative (causes a flip) and can extend beyond the window. Example:: stim.size = 0.8 # Set size to (xsize, ysize) = (0.8, 0.8), quadratic. print stim.size # Outputs array([0.8, 0.8]) stim.size += (0,5, -0.5) # make wider and flatter. Is now (1.3, 0.3) Tip: if you can see the actual pixel range this corresponds to by looking at `stim._sizeRendered` """ value = val2array(value) # Check correct user input self._requestedSize = value #to track whether we're just using a default # None --> set to default if value == None: """Set the size to default (e.g. to the size of the loaded image etc)""" #calculate new size if self._origSize is None: #not an image from a file value = numpy.array([0.5, 0.5 ]) #this was PsychoPy's original default else: #we have an image - calculate the size in `units` that matches original pixel size if self.units == 'pix': value = numpy.array(self._origSize) elif self.units in ['deg', 'degFlatPos', 'degFlat']: #NB when no size has been set (assume to use orig size in pix) this should not #be corrected for flat anyway, so degFlat==degFlatPos value = pix2deg(numpy.array(self._origSize, float), self.win.monitor) elif self.units == 'norm': value = 2 * numpy.array(self._origSize, float) / self.win.size elif self.units == 'height': value = numpy.array(self._origSize, float) / self.win.size[1] elif self.units == 'cm': value = pix2cm(numpy.array(self._origSize, float), self.win.monitor) else: raise AttributeError, "Failed to create default size for ImageStim. Unsupported unit, %s" % ( repr(self.units)) self.__dict__['size'] = value self._needVertexUpdate = True self._needUpdate = True if hasattr(self, '_calcCyclesPerStim'): self._calcCyclesPerStim()
def size(self, value): """The size (w,h) of the stimulus in the stimulus :ref:`units <units>` Value should be :ref:`x,y-pair <attrib-xy>`, :ref:`scalar <attrib-scalar>` (applies to both dimensions) or None (resets to default). :ref:`Operations <attrib-operations>` are supported. Sizes can be negative (causing a mirror-image reversal) and can extend beyond the window. Example:: stim.size = 0.8 # Set size to (xsize, ysize) = (0.8, 0.8), quadratic. print stim.size # Outputs array([0.8, 0.8]) stim.size += (0.5, -0.5) # make wider and flatter. Is now (1.3, 0.3) Tip: if you can see the actual pixel range this corresponds to by looking at `stim._sizeRendered` """ value = val2array(value) # Check correct user input self._requestedSize = value #to track whether we're just using a default # None --> set to default if value == None: """Set the size to default (e.g. to the size of the loaded image etc)""" #calculate new size if self._origSize is None: #not an image from a file value = numpy.array([0.5, 0.5]) #this was PsychoPy's original default else: #we have an image - calculate the size in `units` that matches original pixel size if self.units == 'pix': value = numpy.array(self._origSize) elif self.units in ['deg', 'degFlatPos', 'degFlat']: #NB when no size has been set (assume to use orig size in pix) this should not #be corrected for flat anyway, so degFlat==degFlatPos value = pix2deg(numpy.array(self._origSize, float), self.win.monitor) elif self.units == 'norm': value = 2 * numpy.array(self._origSize, float) / self.win.size elif self.units == 'height': value = numpy.array(self._origSize, float) / self.win.size[1] elif self.units == 'cm': value = pix2cm(numpy.array(self._origSize, float), self.win.monitor) else: raise AttributeError, "Failed to create default size for ImageStim. Unsupported unit, %s" %(repr(self.units)) self.__dict__['size'] = value self._needVertexUpdate=True self._needUpdate = True if hasattr(self, '_calcCyclesPerStim'): self._calcCyclesPerStim()
def save_stim(x, y, contrast, name): screen_size = 100 win = visual.Window(size=(screen_size, screen_size), fullscr=False, screen=0, allowGUI=False, allowStencil=False, monitor='testMonitor', color=[0, 0, 0], colorSpace='rgb', blendMode='avg', useFBO=False) mon = Monitor('testMonitor') mon.setWidth(29.0) mon.setSizePix((1440, 900)) cm_per_pix = pix2cm(1, mon) cont = contrast grating = visual.GratingStim(win, units='pix', mask='circle', sf=x * cm_per_pix, ori=y, pos=(0, 0), contrast=cont, size=(2 / cm_per_pix, 2 / cm_per_pix), interpolate=True, texRes=2048) grating.draw() win.flip() win.getMovieFrame() win.saveMovieFrames(name)
def __init__(self, params=None, reportobj=None, subroutines=[]): logging.LogFile(params['cwd'] + os.sep + params['logfile'], level=logging.INFO, filemode='w') logging.LogFile(level=logging.ERROR) logging.info('Using Python ' + sys.version) self.args = sys.argv self.params = self.update_params(self.args, params) self.win = None self.model = None self.routines = subroutines self.thisExp = None self.name = params['name'] self.cwd = params['cwd'] self.reportobj = reportobj if params['monitor'] not in monitors.getAllMonitors(): logging.error('monitor not found: ' + params.monitor) logging.info('available monitors: ' + ' '.join(monitors.getAllMonitors())) logging.info('Define monitor in monitor center.') logging.info('To launch monitor center, type:\n' 'python -m psychopy.monitors.MonitorCenter') core.quit() else: self.monitor = monitors.Monitor( '', width=None, distance=self.params['viewing_distance'], gamma=None, notes=None, useBits=None, verbose=True, currentCalib=None, autoLog=True) self.monitor.setSizePix((1920, 1280)) self.monitor.setWidth(54.15) logging.exp('using monitor: ' + self.params['monitor'] + ' with viewing_distance=' + str(self.params['viewing_distance']) + ' cm\n' + ' resolution (pixel/deg): ' + str(deg2pix(1, self.monitor))) # TODO: change screen_pixel_size back to calculation from monitor # TODO: change the monitor definition width so that screen_pixel_size=0.282 if 'screen_pixel_size' not in self.params['model']: self.params['model']['screen_pixel_size'] = pix2cm( 1, self.monitor) * 10.0 #self.params['model']['screen_pixel_size'] = 0.282 self.params['model'][ 'viewing_distance'] = self.params['viewing_distance'] / 2.54 self.expInfo = params['exp_info'] self.expInfo['date'] = data.getDateStr() # Ensure that relative paths start from the same directory as this script _thisDir = os.path.dirname(os.path.abspath(__file__)) os.chdir(_thisDir) # Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc runsubject = self.params['runsubject'] if runsubject: self.filename = self.cwd + os.sep + u'data' + os.sep + '%s_%s_%s' % ( self.expInfo['participant'], params['name'], self.expInfo['date']) else: self.filename = self.cwd + os.sep + u'data' + os.sep + 'model-' + params[ 'expName']
def TransformStim(xin, yin): #initialize variable, transfer labels trans_y = np.zeros(np.shape(xin)[0]) # Convert x values; as long as input's x-values are in 0-100 space, this # line linearly transforms those values to -1:2 space; to choose # a different range, simply change the linear scaling, but be sure to # change the scaling for the y transformation as well so the ratio of the # axes remains the same. trans_x = (xin / 100) * 3 - 0.5 # trans_x = (xin / 100) * 3 - 1 # Nonlinear conversion of x values: trans_x exponentiated, resulting in a # range of .5-4 for CPD. DO NOT CHANGE. trans_x = 2**trans_x # Y values should also be in 0-100; negative values in particular cause # problems. if np.any(xin < 0) or np.any(yin < 0): print('Negative value for input!') # Linear conversion of y values to pi/11:(3*pi/8+pi/11) space. Again, # different ranges and bounds can be chosen at this step. # y = (yin / 100) * ((3 * np.pi / 8) + (np.pi / 11)) y = (yin / 100) * ((3 * np.pi / 8) + (np.pi / 9)) # The remainder of the code performs the nonlinear transformation on the y # values, which remain in the same space, but warped. DO NOT CHANGE. ind = np.argsort(y) sort_y = y[ind] z = 4.7 * np.sin(sort_y)**2 trans_y[0] = np.sqrt(sort_y[0]**2 + z[0]**2) for i in range(1, np.shape(sort_y)[0]): trans_y[i] = trans_y[i - 1] + np.sqrt( np.power(sort_y[i] - sort_y[i - 1], 2) + np.power(z[i] - z[i - 1], 2)) range_trans_y = np.amax(trans_y) - np.amin(trans_y) range_sort_y = np.amax(sort_y) - np.amin(sort_y) trans_y = trans_y / range_trans_y * range_sort_y trans_y = trans_y - np.min(trans_y) + np.min(sort_y) # NOTE: Convert radians to degrees trans_y = trans_y * 180 / np.pi xout = trans_x yout = np.zeros(np.shape(xin)[0]) for i in range(0, len(ind)): yout[ind[i]] = trans_y[i] # NOTE: Convert Cycles per degree to cycles per cm mon = Monitor('testMonitor') mon.distance = 20.0 # viewing distance in cm xout = xout * (pix2deg(1, mon) / pix2cm(1, mon)) # xout = (xin / 100) * 5 + 1 # yout = (yin / 100) * 90 return ([xout, yout])
def pix2cmcoord(self, x, y, display_index=None): # print2err('Display {0} bounds: {1}'.format(display_index,self.getBounds())) if display_index == self.getIndex(): ppx, ppy = display2psychopyPix(x, y) return pix2cm(ppx, self._psychopy_monitor), pix2cm(ppy, self._psychopy_monitor) return x, y