def findPR650(ports=None): """DEPRECATED (as of v.1.60.01). Use :func:`psychopy.hardware.findPhotometer()` instead, which finds a wider range of devices """ log.error("DEPRECATED (as of v.1.60.01). Use psychopy.hardware.findPhotometer() instead, which "\ +"finds a wider range of devices") if ports==None: if sys.platform=='darwin': ports=[] #try some known entries in /dev/tty. used by keyspan ports.extend(glob.glob('/dev/tty.USA*'))#keyspan twin adapter is usually USA28X13P1.1 ports.extend(glob.glob('/dev/tty.Key*'))#some are Keyspan.1 or Keyserial.1 ports.extend(glob.glob('/dev/tty.modem*'))#some are Keyspan.1 or Keyserial.1 if len(ports)==0: log.error("couldn't find likely serial port in /dev/tty.* Check for \ serial port name manually, check drivers installed etc...") elif sys.platform=='win32': ports = range(11) elif type(ports) in [int,float]: ports=[ports] #so that we can iterate pr650=None log.info('scanning serial ports...\n\t') log.console.flush() for thisPort in ports: log.info(str(thisPort)); log.console.flush() pr650 = Photometer(port=thisPort, meterType="PR650", verbose=False) if pr650.OK: log.info(' ...OK\n'); log.console.flush() break else: pr650=None log.info('...Nope!\n\t'); log.console.flush() return pr650
def findPR650(ports=None): """DEPRECATED (as of v.1.60.01). Use :func:`psychopy.hardware.findPhotometer()` instead, which finds a wider range of devices """ log.error("DEPRECATED (as of v.1.60.01). Use psychopy.hardware.findPhotometer() instead, which "\ +"finds a wider range of devices") print 'here' if ports==None: if sys.platform=='darwin': ports=[] #try some known entries in /dev/tty. used by keyspan ports.extend(glob.glob('/dev/tty.USA*'))#keyspan twin adapter is usually USA28X13P1.1 ports.extend(glob.glob('/dev/tty.Key*'))#some are Keyspan.1 or Keyserial.1 ports.extend(glob.glob('/dev/tty.modem*'))#some are Keyspan.1 or Keyserial.1 if len(ports)==0: log.error("couldn't find likely serial port in /dev/tty.* Check for \ serial port name manually, check drivers installed etc...") elif sys.platform=='win32': ports = range(11) elif type(ports) in [int,float]: ports=[ports] #so that we can iterate pr650=None log.info('scanning serial ports...\n\t') log.console.flush() for thisPort in ports: log.info(str(thisPort)); log.console.flush() pr650 = Photometer(port=thisPort, meterType="PR650", verbose=False) if pr650.OK: log.info(' ...OK\n'); log.console.flush() break else: pr650=None log.info('...Nope!\n\t'); log.console.flush() return pr650
def rush(value=True): """Raise the priority of the current thread/process Win32 and OS X only so far - on linux use os.nice(niceIncrement) Set with rush(True) or rush(False) Beware and don't take priority until after debugging your code and ensuring you have a way out (e.g. an escape sequence of keys within the display loop). Otherwise you could end up locked out and having to reboot! """ if importCtypesFailed: return False if value: bus = getBusFreq() extendedPolicy=_timeConstraintThreadPolicy() extendedPolicy.period=bus/160 #number of cycles in hz (make higher than frame rate) extendedPolicy.computation=bus/320#half of that period extendedPolicy.constrain= bus/640#max period that they should be carried out in extendedPolicy.preemptible=1 extendedPolicy=getThreadPolicy(getDefault=True, flavour=THREAD_TIME_CONSTRAINT_POLICY) err=cocoa.thread_policy_set(cocoa.mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY, ctypes.byref(extendedPolicy), #send the address of the struct THREAD_TIME_CONSTRAINT_POLICY_COUNT) if err!=KERN_SUCCESS: log.error('Failed to set darwin thread policy, with thread_policy_set') else: log.info('Successfully set darwin thread to realtime') else: #revert to default policy extendedPolicy=getThreadPolicy(getDefault=True, flavour=THREAD_STANDARD_POLICY) err=cocoa.thread_policy_set(cocoa.mach_thread_self(), THREAD_STANDARD_POLICY, ctypes.byref(extendedPolicy), #send the address of the struct THREAD_STANDARD_POLICY_COUNT) return True
def makeGrating( res, ori=0.0, #in degrees cycles=1.0, phase=0.0, #in degrees gratType="sin", contr=1.0): """Make an array containing a luminance grating of the specified params :Parameters: res: integer the size of the resulting matrix on both dimensions (e.g 256) ori: float or int (default=0.0) the orientation of the grating in degrees cycles:float or int (default=1.0) the number of grating cycles within the array phase: float or int (default=0.0) the phase of the grating in degrees (NB this differs to most PsychoPy phase arguments which use units of fraction of a cycle) gratType: 'sin', 'sqr', 'ramp' or 'sinXsin' (default="sin") the type of grating to be 'drawn' contr: float (default=1.0) contrast of the grating :Returns: a square numpy array of size resXres """ tiny = 0.0000000000001 #to prevent the sinusoid ever being exactly at zero (for sqr wave) ori *= (-numpy.pi / 180) phase *= (numpy.pi / 180) xrange, yrange = numpy.mgrid[0.0:cycles * 2.0 * numpy.pi:cycles * 2.0 * numpy.pi / res, 0.0:cycles * 2.0 * numpy.pi:cycles * 2.0 * numpy.pi / res] if gratType is "none": res = 2 intensity = numpy.ones((res, res), Float) elif gratType is "sin": intensity = contr * (numpy.sin(xrange * numpy.sin(ori) + yrange * numpy.cos(ori) + phase)) elif gratType is "ramp": intensity = contr * (xrange * numpy.cos(ori) + yrange * numpy.sin(ori)) / (2 * numpy.pi) elif gratType is "sqr": #square wave (symmetric duty cycle) intensity = numpy.where( numpy.sin(xrange * numpy.sin(ori) + yrange * numpy.cos(ori) + phase + tiny) >= 0, 1, -1) elif gratType is "sinXsin": intensity = numpy.sin(xrange) * numpy.sin(yrange) else: #might be a filename of an image try: im = Image.open(gratType) except: log.error("couldn't find tex...", gratType) return return intensity
def writeStartCode(self,buff): buff.writeIndented("#store info about the experiment session\n") buff.writeIndented("expName='%s'#from the Builder filename that created this script\n" %(self.exp.name)) expInfo = self.params['Experiment info'].val.strip() if not len(expInfo): expInfo = '{}' try: eval('dict('+expInfo+')') except SyntaxError, err: log.error('Builder Expt: syntax error in "Experiment info" settings (expected a dict)') raise SyntaxError, 'Builder: error in "Experiment info" settings (expected a dict)'
def checkOK(self,msg): """Check that the message from the photometer is OK. If there's an error print it. Then return True (OK) or False. """ #also check that the reply is what was expected if msg[0:2] != 'OK': if msg=='': log.error('No reply from LS100'); sys.stdout.flush() else: log.error('Error message from LS100:' + self.codes[msg]); sys.stdout.flush() return False else: return True
def makeGrating(res, ori=0.0, #in degrees cycles=1.0, phase=0.0, #in degrees gratType="sin", contr=1.0): """Make an array containing a luminance grating of the specified params :Parameters: res: integer the size of the resulting matrix on both dimensions (e.g 256) ori: float or int (default=0.0) the orientation of the grating in degrees cycles:float or int (default=1.0) the number of grating cycles within the array phase: float or int (default=0.0) the phase of the grating in degrees (NB this differs to most PsychoPy phase arguments which use units of fraction of a cycle) gratType: 'sin', 'sqr', 'ramp' or 'sinXsin' (default="sin") the type of grating to be 'drawn' contr: float (default=1.0) contrast of the grating :Returns: a square numpy array of size resXres """ tiny=0.0000000000001#to prevent the sinusoid ever being exactly at zero (for sqr wave) ori *= (-numpy.pi/180) phase *= (numpy.pi/180) xrange, yrange = numpy.mgrid[0.0 : cycles*2.0*numpy.pi : cycles*2.0*numpy.pi/res, 0.0 : cycles*2.0*numpy.pi : cycles*2.0*numpy.pi/res] if gratType is "none": res=2 intensity = numpy.ones((res,res),Float) elif gratType is "sin": intensity= contr*(numpy.sin( xrange*numpy.sin(ori)+yrange*numpy.cos(ori) + phase)) elif gratType is "ramp": intensity= contr*( xrange*numpy.cos(ori)+yrange*numpy.sin(ori) )/(2*numpy.pi) elif gratType is "sqr":#square wave (symmetric duty cycle) intensity = numpy.where(numpy.sin( xrange*numpy.sin(ori)+yrange*numpy.cos(ori) + phase + tiny)>=0, 1, -1) elif gratType is "sinXsin": intensity = numpy.sin(xrange)*numpy.sin(yrange) else:#might be a filename of an image try: im = Image.open(gratType) except: log.error( "couldn't find tex...",gratType) return return intensity
def makeMPEG(filename, images, codec='mpeg1video', codecParams=None, verbose=False): if not havePyMedia: log.error('pymedia (www.pymedia.org) needed to make mpeg movies') return 0 fw = open(filename, 'wb') t = time.time() #set bitrate if codec == 'mpeg1video': bitrate = 2700000 else: bitrate = 9800000 #set other params (or receive params dictionary) if codecParams == None: codecParams= { \ 'type': 0, 'gop_size': 12, 'frame_rate_base': 125, 'max_b_frames': 0, 'width': images[0].size[0], 'height': images[0].size[1], 'frame_rate': 3125, 'deinterlace': 0, 'bitrate': bitrate, 'id': vcodec.getCodecID( codec ) } log.info('Setting codec to ' + str(codecParams)) encoder = vcodec.Encoder(codecParams) for im in images: # Create VFrame imStr = im.tostring() bmpFrame = vcodec.VFrame(vcodec.formats.PIX_FMT_RGB24, im.size, (imStr, None, None)) yuvFrame = bmpFrame.convert(vcodec.formats.PIX_FMT_YUV420P, im.size) d = encoder.encode(yuvFrame) try: fw.write(d.data) #this is what works! except: fw.write(d) #this is what pymedia demo recommends else: log.info('%d frames written in %.2f secs' % (len(images), time.time() - t)) i = 0 fw.close()
def init(): """initialise the bits++ box Note that, by default, bits++ will perform gamma correction that you don't want (unless you have the CRS calibration device) (Recommended that you use the BitsBox class rather than calling this directly) """ if haveBitsDLL: try: retVal = _bits.bitsInit() #returns null if fails? except: log.error('bits.init() barfed!') return 0 return 1
def writeStartCode(self, buff): buff.writeIndented("#store info about the experiment session\n") buff.writeIndented( "expName='%s'#from the Builder filename that created this script\n" % (self.exp.name)) expInfo = self.params['Experiment info'].val.strip() if not len(expInfo): expInfo = '{}' try: eval('dict(' + expInfo + ')') except SyntaxError, err: log.error( 'Builder Expt: syntax error in "Experiment info" settings (expected a dict)' ) raise SyntaxError, 'Builder: error in "Experiment info" settings (expected a dict)'
def makeEdgeMatrix(width, center, size=512): """Create a matrix of given size that switches from 0 to 1 using a linear ramp. :params: width: (float) the width of the linear ramp (blur) as fraction of the total matrix center: (float) the location of the center of the ramp as a fraction of the total matrix size: (int=256) width and height of the matrix """ center=int(center*(size-1)) width = int(width*size/2)*2 mat = np.ones([size,size], 'f') mat[:,0:(center-width/2)]=0 if (center-width/2)<0 or (center+width/2)>size: log.error('ramp extends beyond texture') mat[:,(center-width/2):(center+width/2)] = np.linspace(0,1.0,width) return mat*2.0-1
def run(self): self.running = True self.clock.reset() last_onset = 0.000 # wait until next event requested, and simulate a key press for onset, key in self.responses: core.wait(float(onset) - last_onset) if type(key) == int: #log.warning('ResponseEmulator: int converted to str') key = str(key)[0] # avoid cryptic error if int if type(key) == str: event._keyBuffer.append(key) else: log.error('ResponseEmulator: only keyboard events are supported') last_onset = onset if self.stopflag: break self.running = False
def makeMPEG(filename, images, codec='mpeg1video', codecParams = None, verbose=False): if not havePyMedia: log.error('pymedia (www.pymedia.org) needed to make mpeg movies') return 0 fw= open( filename, 'wb' ) t= time.time() #set bitrate if codec== 'mpeg1video': bitrate= 2700000 else: bitrate= 9800000 #set other params (or receive params dictionary) if codecParams == None: codecParams= { \ 'type': 0, 'gop_size': 12, 'frame_rate_base': 125, 'max_b_frames': 0, 'width': images[0].size[0], 'height': images[0].size[1], 'frame_rate': 3125, 'deinterlace': 0, 'bitrate': bitrate, 'id': vcodec.getCodecID( codec ) } log.info('Setting codec to ' + str(codecParams)) encoder= vcodec.Encoder( codecParams ) for im in images: # Create VFrame imStr = im.tostring() bmpFrame= vcodec.VFrame( vcodec.formats.PIX_FMT_RGB24, im.size, (imStr,None,None)) yuvFrame= bmpFrame.convert( vcodec.formats.PIX_FMT_YUV420P, im.size ) d = encoder.encode( yuvFrame ) try: fw.write( d.data )#this is what works! except: fw.write( d )#this is what pymedia demo recommends else: log.info('%d frames written in %.2f secs' % ( len(images), time.time()- t)) i= 0 fw.close()
def run(self): self.running = True self.clock.reset() last_onset = 0.000 # wait until next event requested, and simulate a key press for onset, key in self.responses: core.wait(float(onset) - last_onset) if type(key) == int: # log.warning('ResponseEmulator: int converted to str') key = str(key)[0] # avoid cryptic error if int if type(key) == str: event._keyBuffer.append(key) else: log.error("ResponseEmulator: only keyboard events are supported") last_onset = onset if self.stopflag: break self.running = False
def rush(value=True): """Raise the priority of the current thread/process Win32 and OS X only so far - on linux use os.nice(niceIncrement) Set with rush(True) or rush(False) Beware and don't take priority until after debugging your code and ensuring you have a way out (e.g. an escape sequence of keys within the display loop). Otherwise you could end up locked out and having to reboot! """ if importCtypesFailed: return False if value: bus = getBusFreq() extendedPolicy = _timeConstraintThreadPolicy() extendedPolicy.period = bus / 160 #number of cycles in hz (make higher than frame rate) extendedPolicy.computation = bus / 320 #half of that period extendedPolicy.constrain = bus / 640 #max period that they should be carried out in extendedPolicy.preemptible = 1 extendedPolicy = getThreadPolicy(getDefault=True, flavour=THREAD_TIME_CONSTRAINT_POLICY) err = cocoa.thread_policy_set( cocoa.mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY, ctypes.byref(extendedPolicy), #send the address of the struct THREAD_TIME_CONSTRAINT_POLICY_COUNT) if err != KERN_SUCCESS: log.error( 'Failed to set darwin thread policy, with thread_policy_set') else: log.info('Successfully set darwin thread to realtime') else: #revert to default policy extendedPolicy = getThreadPolicy(getDefault=True, flavour=THREAD_STANDARD_POLICY) err = cocoa.thread_policy_set( cocoa.mach_thread_self(), THREAD_STANDARD_POLICY, ctypes.byref(extendedPolicy), #send the address of the struct THREAD_STANDARD_POLICY_COUNT) return True
So setting to DEBUG level will include all possible messages, setting to ERROR will include only the absolutely essential messages. """ globalClock = core.Clock()#if this isn't provided the log times will reflect secs since python started log.setDefaultClock(globalClock)#use this for log.console.setLevel(log.DEBUG)#set the console to receive nearly all messges logDat = log.LogFile('logLastRun.log', filemode='w',#if you set this to 'a' it will append instead of overwriting level=log.WARNING)#errors, data and warnings will be sent to this logfile #the following will go to any files with the appropriate minimum level set log.info('Something fairly unimportant') log.data('Something about our data. Data is likely very important!') log.warning('Handy while building your experiment - highlights possible flaws in code/design') log.error("You might have done something that PsychoPy can't handle! But hopefully this gives you some idea what.") #some things should be logged timestamped on the next video frame #For instance the time of a stimulus appearing is related to the flip: win = visual.Window([400,400]) for n in range(5): win.logOnFlip('frame %i occured' %n, level=log.EXP) if n in [2,4]: win.logOnFlip('an even frame occured', level=log.EXP) win.flip() #LogFiles can also simply receive direct input from the write() method #messages using write() will be sent immediately, and are often not #in correct chronological order with logged messages logDat.write("Testing\n\n") log.flush()
def __init__(self, port, meterType="PR650", verbose=True): log.error(self.__doc__) sys.exit()
of PsychoPy as of version 1.62.01). installation: Download the package from the link above and copy egi.py into your site-packages directory. usage:: from psychopy.hardware import egi For an example see the demos menu of the PsychoPy Coder For further documentation see the pynetstation website """ # Part of the PsychoPy library # Copyright (C) 2010 Jonathan Peirce # Distributed under the terms of the GNU General Public License (GPL). from psychopy import log try: from egi import * except: msg="""Failed to import egi (pynetstation). If you're using your own copy of python (not the Standalone distribution of PsychoPy) then try installing pynetstation. See: http://code.google.com/p/pynetstation/wiki/Installation """ log.error(msg)
def lineariseLums(self, desiredLums, newInterpolators=False, overrideGamma=None): """lums should be uncalibrated luminance values (e.g. a linear ramp) ranging 0:1""" linMethod = self.getLineariseMethod() desiredLums = numpy.asarray(desiredLums) output = desiredLums*0.0 #needs same size as input #gamma interpolation if linMethod==3: lumsPre = copy(self.getLumsPre()) if self._gammaInterpolator!=None and not newInterpolators: pass #we already have an interpolator elif lumsPre != None: log.info('Creating linear interpolation for gamma') #we can make an interpolator self._gammaInterpolator, self._gammaInterpolator2 =[],[] #each of these interpolators is a function! levelsPre = self.getLevelsPre()/255.0 for gun in range(4): lumsPre[gun,:] = (lumsPre[gun,:]-lumsPre[gun,0])/(lumsPre[gun,-1]-lumsPre[gun,0])#scale to 0:1 self._gammaInterpolator.append(interp1d(lumsPre[gun,:], levelsPre,kind='linear')) #interpFunc = Interpolation.InterpolatingFunction((lumsPre[gun,:],), levelsPre) #polyFunc = interpFunc.fitPolynomial(3) #print polyFunc.coeff #print polyFunc.derivative(0) #print polyFunc.derivative(0.5) #print polyFunc.derivative(1.0) #self._gammaInterpolator2.append( [polyFunc.coeff]) else: #no way to do this! Calibrate the monitor log.error("Can't do a gamma interpolation on your monitor without calibrating!") return desiredLums #then do the actual interpolations if len(desiredLums.shape)>1: for gun in range(3): output[:,gun] = self._gammaInterpolator[gun+1](desiredLums[:,gun])#gun+1 because we don't want luminance interpolator else:#just luminance output = self._gammaInterpolator[0](desiredLums) #use a fitted gamma equation (1 or 2) elif linMethod in [1,2]: #get the min,max lums gammaGrid = self.getGammaGrid() if gammaGrid!=None: #if we have info about min and max luminance then use it minLum = gammaGrid[1,0] maxLum = gammaGrid[1:4,1] if overrideGamma is not None: gamma=overrideGamma else: gamma = gammaGrid[1:4,2] maxLumWhite = gammaGrid[0,1] gammaWhite = gammaGrid[0,2] log.debug('using gamma grid'+str(gammaGrid)) else: #just do the calculation using gamma minLum=0 maxLumR, maxLumG, maxLumB, maxLumWhite= 1,1,1, 1 gamma = self.gamma gammaWhite = num.average(self.gamma) #get the inverse gamma if len(desiredLums.shape)>1: for gun in range(3): output[:,gun] = gammaInvFun(desiredLums[:,gun], minLum, maxLum[gun], gamma[gun],eq=linMethod) #print gamma else: output = gammaInvFun(desiredLums, minLum, maxLumWhite, gammaWhite,eq=linMethod) else: log.error("Don't know how to linearise with method %i" %linMethod) output = desiredLums #if DEBUG: print 'LUT:', output[0:10,1], '...' return output
def getLumSeries(lumLevels=8, winSize=(800,600), monitor=None, gamma=1.0, allGuns = True, useBits=False, autoMode='auto', stimSize = 0.3, photometer=None): """ Automatically measures a series of gun values and measures the luminance with a photometer. :Parameters: photometer : a photometer object e.g. a :class:`~psychopy.hardware.pr.PR65` or :class:`~psychopy.hardware.minolta.LS100` from hardware.findPhotometer() lumLevels : (default=8) array of values to test or single value for n evenly spaced test values gamma : (default=1.0) the gamma value at which to test autoMode : 'auto' or 'semi'(='auto') If 'auto' the program will present the screen and automatically take a measurement before moving on. If set to 'semi' the program will wait for a keypress before moving on but will not attempt to make a measurement (use this to make a measurement with your own device). Any other value will simply move on without pausing on each screen (use this to see that the display is performing as expected). """ import psychopy.event, psychopy.visual from psychopy import core if photometer==None: havePhotom = False elif not hasattr(photometer, 'getLum'): log.error("photometer argument to monitors.getLumSeries should be a type of photometer "+\ "object, not a %s" %type(photometer)) return None else: havePhotom = True if useBits: #all gamma transforms will occur in calling the Bits++ LUT #which improves the precision (14bit not 8bit gamma) bitsMode='fast' else: bitsMode=None if gamma==1: initRGB= 0.5**(1/2.0)*2-1 else: initRGB=0.8 #setup screen and "stimuli" myWin = psychopy.visual.Window(fullscr = 0, size=winSize, gamma=gamma,units='norm',monitor=monitor,allowGUI=True,winType='pyglet', bitsMode=bitsMode) instructions="Point the photometer at the central bar. Hit a key when ready (or wait 30s)" message = psychopy.visual.TextStim(myWin, text = instructions,height=0.1, pos=(0,-0.85), rgb=[1,-1,-1]) noise = numpy.random.rand(512,512).round()*2-1 backPatch = psychopy.visual.PatchStim(myWin, tex=noise, size=2, units='norm', sf=[winSize[0]/512.0, winSize[1]/512.0]) testPatch = psychopy.visual.PatchStim(myWin, tex='sqr', size=stimSize, rgb=initRGB, units='norm') #stay like this until key press (or 30secs has passed) waitClock=core.Clock() tRemain=30 while tRemain>0: tRemain = 30-waitClock.getTime() instructions="Point the photometer at the central white bar. Hit a key when ready (or wait %iss)" %tRemain backPatch.draw() testPatch.draw() message.setText(instructions) message.draw() myWin.flip() if len(psychopy.event.getKeys()): break#we got a keypress so move on message.setText('Q to quit at any time') # if photometer.type=='LS100':#LS100 likes to take at least one bright measurement junk=photometer.getLum() #what are the test values of luminance if (type(lumLevels) is int) or (type(lumLevels) is float): toTest= DACrange(lumLevels) else: toTest= numpy.asarray(lumLevels) if allGuns: guns=[0,1,2,3]#gun=0 is the white luminance measure else: allGuns=[0] lumsList = numpy.zeros((len(guns),len(toTest)), 'd') #this will hoold the measured luminance values #for each gun, for each value run test for gun in guns: for valN, DACval in enumerate(toTest): lum = DACval/127.5-1 #get into range -1:1 #only do luminanc=-1 once if lum==-1 and gun>0: continue #set hte patch color if gun>0: rgb=[-1,-1,-1]; rgb[gun-1]=lum else: rgb = [lum,lum,lum] backPatch.draw() testPatch.setColor(rgb) testPatch.draw() message.draw() myWin.flip() time.sleep(0.2)#allowing the screen to settle (no good reason!) #check for quit request for thisKey in psychopy.event.getKeys(): if thisKey in ['q', 'Q', 'escape']: myWin.close() return numpy.array([]) #take measurement if havePhotom and autoMode=='auto': actualLum = photometer.getLum() print "At DAC value %i\t: %.2fcd/m^2" % (DACval, actualLum) if lum==-1 or not allGuns: #if the screen is black set all guns to this lum value! lumsList[:,valN] = actualLum else: #otherwise just this gun lumsList[gun,valN] = actualLum elif autoMode=='semi': print "At DAC value %i" % DACval psychopy.event.waitKeys() myWin.close() #we're done with the visual stimuli if havePhotom: return lumsList else: return numpy.array([])
def getLumSeries(lumLevels=8, winSize=(800,600), monitor=None, gamma=1.0, allGuns = True, useBits=False, autoMode='auto', stimSize = 0.3, photometer=None): """ Automatically measures a series of gun values and measures the luminance with a photometer. :Parameters: photometer : a photometer object e.g. a :class:`~psychopy.hardware.pr.PR65` or :class:`~psychopy.hardware.minolta.LS100` from hardware.findPhotometer() lumLevels : (default=8) array of values to test or single value for n evenly spaced test values gamma : (default=1.0) the gamma value at which to test autoMode : 'auto' or 'semi'(='auto') If 'auto' the program will present the screen and automatically take a measurement before moving on. If set to 'semi' the program will wait for a keypress before moving on but will not attempt to make a measurement (use this to make a measurement with your own device). Any other value will simply move on without pausing on each screen (use this to see that the display is performing as expected). """ import psychopy.event, psychopy.visual from psychopy import core if photometer==None: havePhotom = False elif not hasattr(photometer, 'getLum'): log.error("photometer argument to monitors.getLumSeries should be a type of photometer "+\ "object, not a %s" %type(photometer)) return None else: havePhotom = True if useBits: #all gamma transforms will occur in calling the Bits++ LUT #which improves the precision (14bit not 8bit gamma) bitsMode='fast' else: bitsMode=None if gamma==1: initRGB= 0.5**(1/2.0)*2-1 else: initRGB=0.8 #setup screen and "stimuli" myWin = psychopy.visual.Window(fullscr = 0, size=winSize, gamma=gamma,units='norm',monitor=monitor,allowGUI=True,winType='pyglet', bitsMode=bitsMode) instructions="Point the photometer at the central bar. Hit a key when ready (or wait 30s)" message = psychopy.visual.TextStim(myWin, text = instructions,height=0.1, pos=(0,-0.95), rgb=[1,-1,-1]) noise = numpy.random.rand(512,512).round()*2-1 backPatch = psychopy.visual.PatchStim(myWin, tex=noise, size=2, units='norm', sf=[winSize[0]/512.0, winSize[1]/512.0]) testPatch = psychopy.visual.PatchStim(myWin, tex='sqr', size=stimSize, rgb=initRGB, units='norm') #stay like this until key press (or 30secs has passed) waitClock=core.Clock() tRemain=30 while tRemain>0: tRemain = 30-waitClock.getTime() instructions="Point the photometer at the central white bar. Hit a key when ready (or wait %iss)" %tRemain backPatch.draw() testPatch.draw() message.setText(instructions) message.draw() myWin.flip() if len(psychopy.event.getKeys()): break#we got a keypress so move on # if photometer.type=='LS100':#LS100 likes to take at least one bright measurement junk=photometer.getLum() #what are the test values of luminance if (type(lumLevels) is int) or (type(lumLevels) is float): toTest= DACrange(lumLevels) else: toTest= numpy.asarray(lumLevels) if allGuns: guns=[0,1,2,3]#gun=0 is the white luminance measure else: allGuns=[0] lumsList = numpy.zeros((len(guns),len(toTest)), 'd') #this will hoold the measured luminance values #for each gun, for each value run test for gun in guns: for valN, DACval in enumerate(toTest): lum = DACval/127.5-1 #get into range -1:1 #only do luminanc=-1 once if lum==-1 and gun>0: continue #set hte patch color if gun>0: rgb=[-1,-1,-1]; rgb[gun-1]=lum else: rgb = [lum,lum,lum] backPatch.draw() testPatch.setColor(rgb) testPatch.draw() myWin.flip() time.sleep(0.2)#allowing the screen to settle (no good reason!) #check for quit request for thisKey in psychopy.event.getKeys(): if thisKey in ['q', 'Q']: myWin.close() return numpy.array([]) #take measurement if havePhotom and autoMode=='auto': actualLum = photometer.getLum() print "At DAC value %i\t: %.2fcd/m^2" % (DACval, actualLum) if lum==-1 or not allGuns: #if the screen is black set all guns to this lum value! lumsList[:,valN] = actualLum else: #otherwise just this gun lumsList[gun,valN] = actualLum elif autoMode=='semi': print "At DAC value %i" % DACval psychopy.event.waitKeys() myWin.close() #we're done with the visual stimuli if havePhotom: return lumsList else: return numpy.array([])
def lineariseLums(self, desiredLums, newInterpolators=False, overrideGamma=None): """lums should be uncalibrated luminance values (e.g. a linear ramp) ranging 0:1""" linMethod = self.getLineariseMethod() desiredLums = numpy.asarray(desiredLums) output = desiredLums*0.0 #needs same size as input #gamma interpolation if linMethod==3: lumsPre = copy(self.getLumsPre()) if self._gammaInterpolator!=None and not newInterpolators: pass #we already have an interpolator elif lumsPre != None: log.info('Creating linear interpolation for gamma') #we can make an interpolator self._gammaInterpolator, self._gammaInterpolator2 =[],[] #each of these interpolators is a function! levelsPre = self.getLevelsPre()/255.0 for gun in range(4): lumsPre[gun,:] = (lumsPre[gun,:]-lumsPre[gun,0])/(lumsPre[gun,-1]-lumsPre[gun,0])#scale to 0:1 self._gammaInterpolator.append(interp1d(lumsPre[gun,:], levelsPre,kind='linear')) #interpFunc = Interpolation.InterpolatingFunction((lumsPre[gun,:],), levelsPre) #polyFunc = interpFunc.fitPolynomial(3) #print polyFunc.coeff #print polyFunc.derivative(0) #print polyFunc.derivative(0.5) #print polyFunc.derivative(1.0) #self._gammaInterpolator2.append( [polyFunc.coeff]) else: #no way to do this! Calibrate the monitor log.error("Can't do a gamma interpolation on your monitor without calibrating!") return desiredLums #then do the actual interpolations if len(desiredLums.shape)>1: for gun in range(3): output[:,gun] = self._gammaInterpolator[gun+1](desiredLums[:,gun])#gun+1 because we don't want luminance interpolator else:#just luminance output = self._gammaInterpolator[0](desiredLums) #use a fitted gamma equation (1 or 2) elif linMethod in [1,2,4]: #get the min,max lums gammaGrid = self.getGammaGrid() if gammaGrid!=None: #if we have info about min and max luminance then use it minLum = gammaGrid[1,0] maxLum = gammaGrid[1:4,1] b = gammaGrid[1:4,4] if overrideGamma is not None: gamma=overrideGamma else: gamma = gammaGrid[1:4,2] maxLumWhite = gammaGrid[0,1] gammaWhite = gammaGrid[0,2] log.debug('using gamma grid'+str(gammaGrid)) else: #just do the calculation using gamma minLum=0 maxLumR, maxLumG, maxLumB, maxLumWhite= 1,1,1, 1 gamma = self.gamma gammaWhite = num.average(self.gamma) #get the inverse gamma if len(desiredLums.shape)>1: for gun in range(3): output[:,gun] = gammaInvFun(desiredLums[:,gun], minLum, maxLum[gun], gamma[gun],eq=linMethod, b=b[gun]) #print gamma else: output = gammaInvFun(desiredLums, minLum, maxLumWhite, gammaWhite,eq=linMethod) else: log.error("Don't know how to linearise with method %i" %linMethod) output = desiredLums #if DEBUG: print 'LUT:', output[0:10,1], '...' return output
This is currently a simple import of the `ioLab python library <http://github.com/ioLab/python-ioLabs>`_. That needs to be installed (but is included in the *Standalone* distributions of PsychoPy as of version 1.62.01). installation:: easy_install iolabs usage:: from psychopy.hardware import ioLabs for examples see the demos menu of the PsychoPy Coder or go to the URL above. """ # Part of the PsychoPy library # Copyright (C) 2011 Jonathan Peirce # Distributed under the terms of the GNU General Public License (GPL). from psychopy import log try: from ioLabs import * except: msg="""Failed to import the ioLabs library. If you're using your own copy of python (not the Standalone distribution of PsychoPy) then try installing it with: > easy_install ioLabs """ log.error(msg)
def _error(self, msg): self.OK = False log.error(msg)
from psychopy import log #create a log that gets replaced every run and stores all the details detailedLog = log.LogFile('complete.log', 'w', #'a' will append to previous file, 'w' will overwrite level=log.INFO) #set the level of the console log log.console.setLevel(log.WARNING) #set the level of the log at site-packages/psychopy/psychopy.log log.psychopyLog.setLevel(log.ERROR) log.warning('a shot across the bows') log.error('just a test error message') log.info('this will only get sent to the detailed log file')
log.warn('Requested stereo setting was not possible') def setAudioAPI(api): """Change the API used for the presentation of sounds usage: setAudioAPI(api) where: api is one of 'pygame','pyglet', pyaudio' """ global audioAPI, Sound exec('haveThis=have%s' % api.title()) if haveThis: audioAPI = api exec('init%s()' % (API.title())) exec('thisSound= Sound%s' % (API.title())) Sound = thisSound return haveThis #initialise it and keep track for API in preferredAPI: if setAudioAPI(API): audioAPI = API break #we found one so stop looking if audioAPI is None: log.error('No audio API found. Try installing pygame 1.8+')
from psychopy import log #create a log that gets replaced every run and stores all the details detailedLog = log.LogFile( 'complete.log', 'w', #'a' will append to previous file, 'w' will overwrite level=log.INFO) #set the level of the console log log.console.setLevel(log.WARNING) #set the level of the log at site-packages/psychopy/psychopy.log log.psychopyLog.setLevel(log.ERROR) log.warning('a shot across the bows') log.error('just a test error message') log.info('this will only get sent to the detailed log file')
log.warn('Requested stereo setting was not possible') def setAudioAPI(api): """Change the API used for the presentation of sounds usage: setAudioAPI(api) where: api is one of 'pygame','pyglet', pyaudio' """ global audioAPI, Sound exec('haveThis=have%s' %api.title()) if haveThis: audioAPI=api exec('init%s()' %(API.title())) exec('thisSound= Sound%s' %(API.title())) Sound= thisSound return haveThis #initialise it and keep track for API in preferredAPI: if setAudioAPI(API): audioAPI=API break#we found one so stop looking if audioAPI is None: log.error('No audio API found. Try installing pygame 1.8+')
def findPhotometer(ports=None, device=None): """Try to find a connected photometer/photospectrometer! PsychoPy will sweep a series of serial ports trying to open them. If a port successfully opens then it will try to issue a command to the device. If it responds with one of the expected values then it is assumed to be the appropriate device. :parameters: ports : a list of ports to search Each port can be a string (e.g. 'COM1', ''/dev/tty.Keyspan1.1') or a number (for win32 comports only). If none are provided then PsychoPy will sweep COM0-10 on win32 and search known likely port names on OS X and linux. device : string giving expected device (e.g. 'PR650', 'PR655', 'LS110'). If this is not given then an attempt will be made to find a device of any type, but this often fails :returns: * An object representing the first photometer found * None if the ports didn't yield a valid response * -1 if there were not even any valid ports (suggesting a driver not being installed) e.g.:: photom = findPhotometer(device='PR655') #sweeps ports 0 to 10 searching for a PR655 print photom.getLum() if hasattr(photom, 'getSpectrum'):#can retrieve spectrum (e.g. a PR650) print photom.getSpectrum() """ import minolta, pr if device.lower() in ['pr650']: photometers=[pr.PR650] elif device.lower() in ['pr655', 'pr670']: photometers=[pr.PR655] elif device.lower() in ['ls110', 'ls100']: photometers=[minolta.LS100] else:#try them all photometers=[pr.PR650, pr.PR655, minolta.LS100]#a list of photometer objects to test for #determine candidate ports if ports==None: if sys.platform=='darwin': ports=[] #try some known entries in /dev/tty. used by keyspan ports.extend(glob.glob('/dev/tty.USA*'))#keyspan twin adapter is usually USA28X13P1.1 ports.extend(glob.glob('/dev/tty.Key*'))#some are Keyspan.1 or Keyserial.1 ports.extend(glob.glob('/dev/tty.modem*'))#some are Keyspan.1 or Keyserial.1 ports.extend(glob.glob('/dev/cu.usbmodem*'))#for PR650 if len(ports)==0: log.error("PsychoPy couldn't find any likely serial port in /dev/tty.* or /dev/cs* Check for " \ +"serial port name manually, check drivers installed etc...") return None elif sys.platform.startswith('linux'): ports = glob.glob('/dev/ttyACM?')#USB CDC devices (virtual serial ports) ports.extend(glob.glob('/dev/ttyS?'))#genuine serial ports usually /dev/ttyS0 or /dev/ttyS1 elif sys.platform=='win32': ports = range(11) elif type(ports) in [int,float]: ports=[ports] #so that we can iterate #go through each port in turn photom=None log.info('scanning serial ports...') log.flush() for thisPort in ports: log.info('...'+str(thisPort)); log.flush() for Photometer in photometers: photom = Photometer(port=thisPort) if photom.OK: log.info(' ...found a %s\n' %(photom.type)); log.flush() #we're now sure that this is the correct device and that it's configured #now increase the number of attempts made to communicate for temperamental devices! if hasattr(photom,'setMaxAttempts'):photom.setMaxAttempts(10) return photom#we found one so stop looking else: if photom.com and photom.com.isOpen: log.info('closing port') photom.com.close() #If we got here we didn't find one log.info('...nope!\n\t'); log.flush() return None
def _error(self, msg): self.OK=False log.error(msg)
log.setDefaultClock(globalClock) #use this for log.console.setLevel(log.DEBUG) #set the console to receive nearly all messges logDat = log.LogFile( 'logLastRun.log', filemode='w', #if you set this to 'a' it will append instead of overwriting level=log.WARNING) #errors, data and warnings will be sent to this logfile #the following will go to any files with the appropriate minimum level set log.info('Something fairly unimportant') log.data('Something about our data. Data is likely very important!') log.warning( 'Handy while building your experiment - highlights possible flaws in code/design' ) log.error( "You might have done something that PsychoPy can't handle! But hopefully this gives you some idea what." ) #some things should be logged timestamped on the next video frame #For instance the time of a stimulus appearing is related to the flip: win = visual.Window([400, 400]) for n in range(5): win.logOnFlip('frame %i occured' % n, level=log.EXP) if n in [2, 4]: win.logOnFlip('an even frame occured', level=log.EXP) win.flip() #LogFiles can also simply receive direct input from the write() method #messages using write() will be sent immediately, and are often not #in correct chronological order with logged messages logDat.write("Test ended\n\n")
def findPhotometer(ports=None, device=None): """Try to find a connected photometer/photospectrometer! PsychoPy will sweep a series of serial ports trying to open them. If a port successfully opens then it will try to issue a command to the device. If it responds with one of the expected values then it is assumed to be the appropriate device. :parameters: ports : a list of ports to search Each port can be a string (e.g. 'COM1', ''/dev/tty.Keyspan1.1') or a number (for win32 comports only). If none are provided then PsychoPy will sweep COM0-10 on win32 and search known likely port names on OS X and linux. device : string giving expected device (e.g. 'PR650', 'PR655', 'LS110'). If this is not given then an attempt will be made to find a device of any type, but this often fails :returns: * An object representing the first photometer found * None if the ports didn't yield a valid response * -1 if there were not even any valid ports (suggesting a driver not being installed) e.g.:: photom = findPhotometer(device='PR655') #sweeps ports 0 to 10 searching for a PR655 print photom.getLum() if hasattr(photom, 'getSpectrum'):#can retrieve spectrum (e.g. a PR650) print photom.getSpectrum() """ import minolta, pr if device.lower() in ['pr650']: photometers = [pr.PR650] elif device.lower() in ['pr655', 'pr670']: photometers = [pr.PR655] elif device.lower() in ['ls110', 'ls100']: photometers = [minolta.LS100] else: #try them all photometers = [pr.PR650, pr.PR655, minolta.LS100 ] #a list of photometer objects to test for #determine candidate ports if ports == None: if sys.platform == 'darwin': ports = [] #try some known entries in /dev/tty. used by keyspan ports.extend(glob.glob('/dev/tty.USA*') ) #keyspan twin adapter is usually USA28X13P1.1 ports.extend( glob.glob('/dev/tty.Key*')) #some are Keyspan.1 or Keyserial.1 ports.extend(glob.glob( '/dev/tty.modem*')) #some are Keyspan.1 or Keyserial.1 ports.extend(glob.glob('/dev/cu.usbmodem*')) #for PR650 if len(ports) == 0: log.error("PsychoPy couldn't find any likely serial port in /dev/tty.* or /dev/cs* Check for " \ +"serial port name manually, check drivers installed etc...") return None elif sys.platform.startswith('linux'): ports = glob.glob( '/dev/ttyACM?') #USB CDC devices (virtual serial ports) ports.extend( glob.glob('/dev/ttyS?') ) #genuine serial ports usually /dev/ttyS0 or /dev/ttyS1 elif sys.platform == 'win32': ports = range(11) elif type(ports) in [int, float]: ports = [ports] #so that we can iterate #go through each port in turn photom = None log.info('scanning serial ports...') log.flush() for thisPort in ports: log.info('...' + str(thisPort)) log.flush() for Photometer in photometers: photom = Photometer(port=thisPort) if photom.OK: log.info(' ...found a %s\n' % (photom.type)) log.flush() #we're now sure that this is the correct device and that it's configured #now increase the number of attempts made to communicate for temperamental devices! if hasattr(photom, 'setMaxAttempts'): photom.setMaxAttempts(10) return photom #we found one so stop looking else: if photom.com and photom.com.isOpen: log.info('closing port') photom.com.close() #If we got here we didn't find one log.info('...nope!\n\t') log.flush() return None
To run this demo the ioLab library needs to be installed (it is included with the Standalone distributions of PsychoPy). """ __author__ = 'Jonathan Roberts' from psychopy import log #log.console.setLevel(log.CRITICAL) from psychopy import core, visual, event try: import ioLabs except RuntimeError, errMsg: log.error('Is an ioLabs button-box connected and turned on? (import failed: "'+str(errMsg)+'")') core.quit() import random def setup_bbox(): '''Initialize the button box object and disable all buttons and lights.''' global usbbox # button box object declared global so the other routines can use it usbbox=ioLabs.USBBox() usbbox.buttons.enabled = 0x00 #8 bit pattern 0=disabled 1=enabled usbbox.port2.state = 0xFF #port2 is the lights on the bbox - 8 bit pattern 0=on 1=off def enableButtons(buttonList=(0,1,2,3,4,5,6,7)): '''enable the specified buttons the argument should beone of the following: