def image(self, filename): """Filename, including relative or absolute path. The image can be any format that the Python Imaging Library can import (almost any). Can also be an image already loaded by PIL. """ filename = pathToString(filename) self.__dict__['image'] = filename if isinstance(filename, basestring): # is a string - see if it points to a file if os.path.isfile(filename): self.filename = filename im = Image.open(self.filename) im = im.transpose(Image.FLIP_TOP_BOTTOM) else: logging.error("couldn't find image...%s" % filename) core.quit() else: # not a string - have we been passed an image? try: im = filename.copy().transpose(Image.FLIP_TOP_BOTTOM) except AttributeError: # apparently not an image logging.error("couldn't find image...%s" % filename) core.quit() self.filename = repr(filename) # '<Image.Image image ...>' self.size = im.size # set correct formats for bytes/floats if im.mode == 'RGBA': self.imArray = numpy.array(im).astype(numpy.ubyte) self.internalFormat = GL.GL_RGBA else: self.imArray = numpy.array(im.convert("RGB")).astype(numpy.ubyte) self.internalFormat = GL.GL_RGB self.dataType = GL.GL_UNSIGNED_BYTE self._needStrUpdate = True
def findPR650(ports=None): """DEPRECATED (as of v.1.60.01). Use :func:`psychopy.hardware.findPhotometer()` instead, which finds a wider range of devices """ logging.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: logging.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 logging.info('scanning serial ports...\n\t') logging.console.flush() for thisPort in ports: logging.info(str(thisPort)); logging.console.flush() pr650 = Photometer(port=thisPort, meterType="PR650", verbose=False) if pr650.OK: logging.info(' ...OK\n'); logging.console.flush() break else: pr650=None logging.info('...Nope!\n\t'); logging.console.flush() return pr650
def __init__(self, win=None, portName=None, mode=''): self.OK=False if portName==None: if sys.platform == 'darwin': portNames = glob.glob('/dev/tty.usbmodemfa*') if not portNames: logging.error("Could not connect to Bits Sharp: No serial ports were found at /dev/tty.usbmodemfa*") return None else: portName = portNames[0] elif sys.platform.startswith('linux'): portName = '/dev/ttyACM0' self.portName = portName self._com = self._connect() if self._com: self.OK=True else: return None self.info = self.getInfo() self.mode = mode self.win = win if self.win is not None: if not hasattr(self.win, '_prepareFBOrender'): logging.error("BitsSharp was given an object as win argument but this is not a visual.Window") self.win._prepareFBOrender = self._prepareFBOrender self.win._finishFBOrender = self._finishFBOrender self._setupShaders() else: logging.warning("%s was not given any PsychoPy win")
def expression2js(expr): """Convert a short expression (e.g. a Component Parameter) Python to JS""" # if the code contains a tuple (anywhere), convert parenths to be list. # This now works for compounds like `(2*(4, 5))` where the inner # parenths becomes a list and the outer parens indicate priority. # This works by running an ast transformer class to swap the contents of the tuple # into a list for the number of tuples in the expression. try: syntaxTree = ast.parse(expr) except Exception as err: logging.error(err) syntaxTree = ast.parse(unicode(expr)) for node in ast.walk(syntaxTree): TupleTransformer().visit(node) # Transform tuples to list # for py2 using 'unicode_literals' we don't want if isinstance(node, ast.Str) and type(node.s)==bytes: node.s = unicode(node.s, 'utf-8') elif isinstance(node, ast.Str) and node.s.startswith("u'"): node.s = node.s[1:] if isinstance(node, ast.Name): if node.id == 'undefined': continue node.id = namesJS[node.id] jsStr = unparse(syntaxTree).strip() return jsStr
def clearMemory(self): """ """ self.sendMessage('SPIE') self.pause() reply = self.getResponse(timeout=10) # should return either FRMT or ESEC to indicate it started if reply.startswith('FRMT'): logging.info("BBTK.clearMemory(): " "Starting full format of BBTK memory") elif reply.startswith('ESEC'): logging.info("BBTK.clearMemory(): " "Starting quick erase of BBTK memory") else: logging.error("BBTK.clearMemory(): " "didn't get a reply from %s" % str(self.com)) return False # we aren't in a time-critical period so flush messages logging.flush() # now wait until we get told 'DONE' self.com.timeout = 20 retVal = self.com.readline() if retVal.startswith("DONE"): logging.info("BBTK.clearMemory(): completed") # we aren't in a time-critical period so flush messages logging.flush() return True else: logging.error("BBTK.clearMemory(): " "Stalled waiting for %s" % str(self.com)) # we aren't in a time-critical period so flush messages logging.flush() return False
def __init__(self, visible=True, newPos=None, win=None): super(Mouse, self).__init__() self.visible = visible self.lastPos = None self.prevPos = None # used for motion detection and timing if win: self.win = win else: try: # to avoid circular imports, core.openWindows is defined # by visual.py and updated in core namespace; # it's circular to "import visual" here in event self.win = psychopy.core.openWindows[0]() logging.info('Mouse: using default window') except (NameError, IndexError): logging.error('Mouse: failed to get a default visual.Window' ' (need to create one first)') self.win = None # for builder: set status to STARTED, NOT_STARTED etc self.status = None self.mouseClock = psychopy.core.Clock() self.movedistance = 0.0 # if pygame isn't initialised then we must use pyglet global usePygame if havePygame and not pygame.display.get_init(): usePygame = False if not usePygame: global mouseButtons mouseButtons = [0, 0, 0] self.setVisible(visible) if newPos is not None: self.setPos(newPos)
def flac2wav(path, keep=True): """Uncompress: convert .flac file (on disk) to .wav format (new file). If `path` is a directory name, convert all .flac files in the directory. `keep` to retain the original .flac file(s), default `True`. """ flac_path = _getFlacPath() flac_files = [] if path.endswith('.flac'): flac_files = [path] elif type(path) == str and os.path.isdir(path): flac_files = glob.glob(os.path.join(path, '*.flac')) if len(flac_files) == 0: logging.warn('failed to find .flac file(s) from %s' % path) return None wav_files = [] for flacfile in flac_files: wavname = flacfile.strip('.flac') + '.wav' flac_cmd = [flac_path, "-d", "--totally-silent", "-f", "-o", wavname, flacfile] _junk, se = core.shellCall(flac_cmd, stderr=True) if se: logging.error(se) if not keep: os.unlink(flacfile) wav_files.append(wavname) if len(wav_files) == 1: return wav_files[0] else: return wav_files
def _checkout(requestedVersion): """Look for a Maj.min.patch requested version, download (fetch) if needed. """ # Check tag of repo if currentTag() == requestedVersion: return requestedVersion # See if the tag already exists in repos if requestedVersion not in _localVersions(forceCheck=True): # Grab new tags msg = _translate("Couldn't find version {} locally. Trying github...") logging.info(msg.format(requestedVersion)) subprocess.check_output('git fetch github'.split()) # is requested here now? forceCheck to refresh cache if requestedVersion not in _localVersions(forceCheck=True): msg = _translate("{} is not currently available.") logging.error(msg.format(requestedVersion)) return '' # Checkout the requested tag cmd = ['git', 'checkout', requestedVersion] out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=VERSIONSDIR) logging.debug(out) logging.exp('Success: ' + ' '.join(cmd)) return requestedVersion
def __init__(self, parent, value=None, order=None): """value should be a list of dictionaries with identical field names order should be used to specify the order in which the fields appear (left to right) """ GlobSizer.__init__(self, hgap=2, vgap=2) self.parent = parent self.value = value or [{}] if type(value) != list or len(value) < 1: msg = 'The initial value for a ListWidget must be a list of dicts' raise AttributeError(msg) # sort fieldNames using order information where possible allNames = list(value[0].keys()) self.fieldNames = [] if order is None: order = [] for name in order: if name not in allNames: msg = ('psychopy.dialogs.ListWidget was given a field name ' '`%s` in order that was not in the dictionary') logging.error(msg % name) continue allNames.remove(name) self.fieldNames.append(name) # extend list by the remaining (no explicit order) self.fieldNames.extend(allNames) # set up controls self.createGrid()
def getInfo(self): """Rather than converting the value of params['Experiment Info'] into a dict from a string (which can lead to errors) use this function :return: expInfo as a dict """ infoStr = self.params['Experiment info'].val.strip() if len(infoStr) == 0: return {} try: d = eval(infoStr) except SyntaxError: """under Python3 {'participant':'', 'session':02} raises an error because ints can't have leading zeros. We will check for those and correct them tests = ["{'participant':'', 'session':02}", "{'participant':'', 'session':02}", "{'participant':'', 'session': 0043}", "{'participant':'', 'session':02, 'id':009}", ] """ def entryToString(match): entry = match.group(0) digits = re.split(r": *", entry)[1] return ':{}'.format(repr(digits)) # 0 or more spaces, 1-5 zeros, 0 or more digits: pattern = re.compile(r": *0{1,5}\d*") try: d = eval(re.sub(pattern, entryToString, infoStr)) except SyntaxError: # still a syntax error, possibly caused by user msg = ('Builder Expt: syntax error in ' '"Experiment info" settings (expected a dict)') logging.error(msg) raise AttributeError(msg) return d
def timingCheckAndLog(ts,trialN): #check for timing problems and log them #ts is a list of the times of the clock after each frame interframeIntervs = np.diff(ts)*1000 #print ' interframe intervs were ',around(interframeIntervs,1) #DEBUGOFF frameTimeTolerance=.3 #proportion longer than refreshRate that will not count as a miss longFrameLimit = np.round(1000/refreshRate*(1.0+frameTimeTolerance),2) idxsInterframeLong = np.where( interframeIntervs > longFrameLimit ) [0] #frames that exceeded 150% of expected duration numCasesInterframeLong = len( idxsInterframeLong ) if numCasesInterframeLong >0 and (not demo): longFramesStr = 'ERROR,'+str(numCasesInterframeLong)+' frames were longer than '+str(longFrameLimit)+' ms' if demo: longFramesStr += 'not printing them all because in demo mode' else: longFramesStr += ' apparently screen refreshes skipped, interframe durs were:'+\ str( np.around( interframeIntervs[idxsInterframeLong] ,1 ) )+ ' and was these frames: '+ str(idxsInterframeLong) if longFramesStr != None: logging.error( 'trialnum='+str(trialN)+' '+longFramesStr ) if not demo: flankingAlso=list() for idx in idxsInterframeLong: #also print timing of one before and one after long frame if idx-1>=0: flankingAlso.append(idx-1) else: flankingAlso.append(np.NaN) flankingAlso.append(idx) if idx+1<len(interframeIntervs): flankingAlso.append(idx+1) else: flankingAlso.append(np.NaN) flankingAlso = np.array(flankingAlso) flankingAlso = flankingAlso[np.negative(np.isnan(flankingAlso))] #remove nan values flankingAlso = flankingAlso.astype(np.integer) #cast as integers, so can use as subscripts logging.info( 'flankers also='+str( np.around( interframeIntervs[flankingAlso], 1) ) ) #because this is not an essential error message, as previous one already indicates error #As INFO, at least it won't fill up the console when console set to WARNING or higher return numCasesInterframeLong
def switchOn(sampleRate=44100): """Must explicitly switch on the microphone before use, can take several seconds. """ # imports from pyo, creates globals including pyoServer and pyoSamplingRate global haveMic haveMic = False t0 = time.time() try: global Server, Record, Input, Clean_objects, SfPlayer, serverCreated, serverBooted from pyo import Server, Record, Input, Clean_objects, SfPlayer, serverCreated, serverBooted global getVersion, pa_get_input_devices, pa_get_output_devices from pyo import getVersion, pa_get_input_devices, pa_get_output_devices haveMic = True except ImportError: msg = 'Microphone class not available, needs pyo; see http://code.google.com/p/pyo/' logging.error(msg) raise ImportError(msg) global pyoSamplingRate pyoSamplingRate = sampleRate global pyoServer if serverCreated(): pyoServer.setSamplingRate(sampleRate) pyoServer.boot() else: pyoServer = Server(sr=sampleRate, nchnls=2, duplex=1).boot() pyoServer.start() logging.exp('%s: switch on (%dhz) took %.3fs' % (__file__.strip('.py'), sampleRate, time.time() - t0))
def __init__(self, win, contrast=1.0, gamma=[1.0,1.0,1.0], nEntries=256, mode='bits++',): self.win = win self.contrast=contrast self.nEntries=nEntries #set standardised name for mode if mode in ['bits','bits++']: self.mode = 'bits++' elif mode in ['color','color++','colour','colour++']: self.mode = 'color++' elif mode in ['mono','mono++']: self.mode = 'mono++' else: logging.error("Unknown mode '%s' for BitsBox" %mode) if len(gamma)>2: # [Lum,R,G,B] or [R,G,B] self.gamma=gamma[-3:] else: self.gamma = [gamma, gamma, gamma] if init(): setVideoMode(NOGAMMACORRECT|VIDEOENCODEDCOMMS) self.initialised=True logging.debug('found and initialised bits++') else: self.initialised=False logging.warning("couldn't initialise bits++") if self.mode == 'bits++': #do the processing self._HEADandLUT = numpy.zeros((524,1,3),numpy.uint8) self._HEADandLUT[:12,:,0] = numpy.asarray([ 36, 63, 8, 211, 3, 112, 56, 34,0,0,0,0]).reshape([12,1])#R self._HEADandLUT[:12,:,1] = numpy.asarray([ 106, 136, 19, 25, 115, 68, 41, 159,0,0,0,0]).reshape([12,1])#G self._HEADandLUT[:12,:,2] = numpy.asarray([ 133, 163, 138, 46, 164, 9, 49, 208,0,0,0,0]).reshape([12,1])#B self.LUT=numpy.zeros((256,3),'d')#just a place holder self.setLUT()#this will set self.LUT and update self._LUTandHEAD elif haveShaders: self.monoModeShader = shaders.compileProgram(fragment=shaders.bitsMonoModeFrag, attachments=[shaders.gammaCorrectionFrag]) self.colorModeShader =shaders.compileProgram(fragment=shaders.bitsColorModeFrag, attachments=[shaders.gammaCorrectionFrag]) GL.glUseProgram(self.colorModeShader) prog = self.colorModeShader GL.glUniform1f(GL.glGetUniformLocation(prog, 'sampleSpacing'), 1.0) #Set default encoding gamma for power-law shader to (1.0, 1.0, 1.0): GL.glUniform3f(GL.glGetUniformLocation(prog, 'ICMEncodingGamma'), 1.0, 1.0, 1.0) # Default min and max luminance is 0.0 to 1.0, therefore reciprocal 1/range is also 1.0: GL.glUniform3f(GL.glGetUniformLocation(prog, 'ICMMinInLuminance'), 0.0, 0.0, 0.0) GL.glUniform3f(GL.glGetUniformLocation(prog, 'ICMMaxInLuminance'), 1.0, 1.0, 1.0) GL.glUniform3f(GL.glGetUniformLocation(prog, 'ICMReciprocalLuminanceRange'), 1.0, 1.0, 1.0) # Default gain to postmultiply is 1.0: GL.glUniform3f(GL.glGetUniformLocation(prog, 'ICMOutputGain'), 1.0, 1.0, 1.0) # Default bias to is 0.0: GL.glUniform3f(GL.glGetUniformLocation(prog, 'ICMOutputBias'), 0.0, 0.0, 0.0) GL.glUniform2f(GL.glGetUniformLocation(prog, 'ICMClampToColorRange'), 0.0, 1.0) GL.glUseProgram(0)
def _checkoutRequested(requestedVersion): """Look for a tag matching the request, return it if found or return None for the search. """ # Check tag of repo if getCurrentTag() == requestedVersion: # nothing to do! return 1 # See if the tag already exists in repos (no need for internet) cmd = 'git tag'.split() versions = subprocess.check_output(cmd, cwd=VERSIONSDIR) if requestedVersion not in versions: # Grab new tags msg = "Couldn't find version %r locally. Trying github..." logging.info(msg % requestedVersion) cmd = 'git fetch github'.split() out = subprocess.check_output(cmd) # after fetching from github check if it's there now! versions = subprocess.check_output(['git', 'tag'], cwd=VERSIONSDIR) if requestedVersion not in versions: msg = "%r is not a valid version. Please choose one of: %r" logging.error(msg % (requestedVersion, versions.split())) return 0 # Checkout the requested tag cmd = 'git checkout %s' % requestedVersion out = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT, cwd=VERSIONSDIR) logging.debug(out) return 1
def wav2flac(path, keep=True): """Lossless compression: convert .wav file (on disk) to .flac format. If `path` is a directory name, convert all .wav files in the directory. `keep` to retain the original .wav file(s), default `True`. """ flac_path = _getFlacPath() wav_files = [] if path.endswith(".wav"): wav_files = [path] elif type(path) == str and os.path.isdir(path): wav_files = glob.glob(os.path.join(path, "*.wav")) if len(wav_files) == 0: logging.warn("failed to find .wav file(s) from %s" % path) return None flac_files = [] for wavfile in wav_files: flacfile = wavfile.strip(".wav") + ".flac" flac_cmd = [flac_path, "-8", "-f", "--totally-silent", "-o", flacfile, wavfile] __, se = core.shellCall(flac_cmd, stderr=True) if se or not os.path.isfile(flacfile): # just try again # ~2% incidence when recording for 1s, 650+ trials # never got two in a row; core.wait() does not help logging.warn("Failed to convert to .flac; trying again") __, se = core.shellCall(flac_cmd, stderr=True) if se: logging.error(se) if not keep: os.unlink(wavfile) flac_files.append(flacfile) if len(wav_files) == 1: return flac_files[0] else: return flac_files
def playback(self, block=True, loops=0, stop=False, log=True): """Plays the saved .wav file, as just recorded or resampled. Execution blocks by default, but can return immediately with `block=False`. `loops` : number of extra repetitions; 0 = play once `stop` : True = immediately stop ongoing playback (if there is one), and return """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = "%s: Playback requested but no saved file" % self.loggingId logging.error(msg) raise ValueError(msg) if stop: if hasattr(self, "current_recording") and self.current_recording.status == PLAYING: self.current_recording.stop() return # play this file: name = self.name + ".current_recording" self.current_recording = sound.Sound(self.savedFile, name=name, loops=loops) self.current_recording.play() if block: core.wait(self.duration * (loops + 1)) # set during record() if log and self.autoLog: if loops: logging.exp( "%s: Playback: play %.3fs x %d (est) %s" % (self.loggingId, self.duration, loops + 1, self.savedFile) ) else: logging.exp("%s: Playback: play %.3fs (est) %s" % (self.loggingId, self.duration, self.savedFile))
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: logging.error('Failed to set darwin thread policy, with thread_policy_set') else: logging.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 _switchToVersion(requestedVersion): """Checkout (or clone then checkout) the requested version, set sys.path so that the new version will be found when import is called. Upon exit, the checked out version remains checked out, but the sys.path reverts. NB When installed with pip/easy_install PsychoPy will live in a site-packages directory, which should *not* be removed as it may contain other relevant and needed packages. """ if not os.path.exists(prefs.paths['userPrefsDir']): os.mkdir(prefs.paths['userPrefsDir']) try: if os.path.exists(VERSIONSDIR): _checkout(requestedVersion) else: _clone(requestedVersion) except CalledProcessError as e: if 'did not match any file(s) known to git' in e.output: msg = _translate("'{}' is not a valid PsychoPy version.") logging.error(msg.format(requestedVersion)) raise RuntimeError(msg) else: raise # make sure the checked-out version comes first on the python path: sys.path = [VERSIONSDIR] + sys.path logging.exp('Prepended `{}` to sys.path'.format(VERSIONSDIR))
def __init__(self, id): """An object to control a multi-axis joystick or gamepad .. note: You do need to be flipping frames (or dispatching events manually) in order for the values of the joystick to be updated. :Known issues: Currently under pyglet backends the axis values initialise to zero rather than reading the current true value. This gets fixed on the first change to each axis. """ self.id=id if backend=='pyglet': joys=pyglet_input.get_joysticks() if id>=len(joys): logging.error("You don't have that many joysticks attached (remember that the first joystick has id=0 etc...)") else: self._device=joys[id] self._device.open() self.name=self._device.device.name if len(visual.openWindows)==0: logging.error("You need to open a window before creating your joystick") else: for win in visual.openWindows: win._eventDispatchers.append(self._device.device) else: pygame.joystick.init() self._device=pygame.joystick.Joystick(id) self._device.init() self.name=self._device.get_name()
def calibrateZero(self): """Perform a calibration to zero light. For early versions of the ColorCAL this had to be called after connecting to the device. For later versions the dark calibration was performed at the factory and stored in non-volatile memory. You can check if you need to run a calibration with:: ColorCAL.getNeedsCalibrateZero() """ val = self.sendMessage(b"UZC", timeout=1.0) if val == 'OK00': pass elif val == 'ER11': logging.error( "Could not calibrate ColorCAL2. Is it properly covered?") return False else: # unlikely logging.warning( "Received surprising result from ColorCAL2: %s" % val) return False # then take a measurement to see if we are close to zero lum (ie is it # covered?) self.ok, x, y, z = self.measure() if y > 3: logging.error('There seems to be some light getting to the ' 'detector. It should be well-covered for zero ' 'calibration') return False self._zeroCalibrated = True self.calibMatrix = self.getCalibMatrix() return True
def __init__(self, win=None, portName=None, mode=''): serialdevice.SerialDevice.__init__(self, port=portName, baudrate=19200, byteSize=8, stopBits=1, parity="N", #'N'one, 'E'ven, 'O'dd, 'M'ask, eol='\n', maxAttempts=1, pauseDuration=0.1, checkAwake=True) if not self.OK: return #we have a confirmed connection. Now check details about device and system if not hasattr(self, 'info'): self.info = self.getInfo() self.config = None self.mode = mode self.win = win if self.win is not None: if not hasattr(self.win, '_prepareFBOrender'): logging.error("BitsSharp was given an object as win argument but this is not a visual.Window") self.win._prepareFBOrender = self._prepareFBOrender self.win._finishFBOrender = self._finishFBOrender self._setupShaders() #now check that we have a valid configuration of the box self.checkConfig(level=0) else: self.config = None # makes no sense if we have a window? logging.warning("%s was not given any PsychoPy win" %(self))
def mask(self, value): """The alpha mask that forms the shape of the resulting image Value should one of: + 'circle', 'gauss', 'raisedCos', **None** (resets to default) + or the name of an image file (most formats supported) + or a numpy array (1xN) ranging -1:1 Note that the mask for `RadialStim` is somewhat different to the mask for :class:`ImageStim`. For `RadialStim` it is a 1D array specifying the luminance profile extending outwards from the center of the stimulus, rather than a 2D array """ self.__dict__['mask'] = value res = self.texRes#resolution of texture - 128 is bearable step = 1.0/res rad = numpy.arange(0,1+step,step) if type(self.mask) == numpy.ndarray: #handle a numpy array intensity = 255*self.mask.astype(float) res = len(intensity) fromFile=0 elif type(self.mask) == list: #handle a numpy array intensity = 255*numpy.array(self.mask, float) res = len(intensity) fromFile=0 elif self.mask == "circle": intensity = 255.0*(rad<=1) fromFile=0 elif self.mask == "gauss": # Set SD if specified if self.maskParams == None: sigma = 1.0 / 3 else: sigma = 1.0 / self.maskParams['sd'] intensity = 255.0*numpy.exp( -rad**2.0 / (2.0*sigma**2.0) )#3sd.s by the edge of the stimulus fromFile=0 elif self.mask == "radRamp":#a radial ramp intensity = 255.0-255.0*rad intensity = numpy.where(rad<1, intensity, 0)#half wave rectify fromFile=0 elif self.mask in [None,"none","None"]: res=4 intensity = 255.0*numpy.ones(res,float) fromFile=0 else:#might be a filename of a tiff print value try: im = Image.open(self.mask) im = im.transpose(Image.FLIP_TOP_BOTTOM) im = im.resize([max(im.size), max(im.size)],Image.BILINEAR)#make it square except IOError, (details): logging.error("couldn't load mask...%s: %s" %(value,details)) return res = im.size[0] im = im.convert("L")#force to intensity (in case it was rgb) intensity = numpy.asarray(im)
def requireInternetAccess(forceCheck=False): """Checks for access to the internet, raise error if no access. """ if not haveInternetAccess(forceCheck=forceCheck): msg = 'Internet access required but not detected.' logging.error(msg) raise NoInternetAccessError(msg) return True
def _channelCheck(self, array): """Checks whether stream has fewer channels than data. If True, ValueError""" if self.channels < array.shape[1]: msg = ("The sound stream is set up incorrectly. You have fewer channels in the buffer " "than in data file ({} vs {}).\n**Ensure you have selected 'Force stereo' in " "experiment settings**".format(self.channels, array.shape[1])) logging.error(msg) raise ValueError(msg)
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 """ # to prevent the sinusoid ever being exactly at zero (for sqr wave): tiny = 0.0000000000001 ori *= (old_div(-numpy.pi, 180)) phase *= (old_div(numpy.pi, 180)) cyclesTwoPi = cycles * 2.0 * numpy.pi xrange, yrange = numpy.mgrid[0.0: cyclesTwoPi: old_div(cyclesTwoPi, res), 0.0: cyclesTwoPi: old_div(cyclesTwoPi, res)] sin, cos = numpy.sin, numpy.cos if gratType is "none": res = 2 intensity = numpy.ones((res, res), float) elif gratType is "sin": intensity = contr * sin(xrange * sin(ori) + yrange * cos(ori) + phase) elif gratType is "ramp": intensity = contr * (xrange * cos(ori) + yrange * sin(ori)) / (2 * numpy.pi) elif gratType is "sqr": # square wave (symmetric duty cycle) intensity = numpy.where(sin(xrange * sin(ori) + yrange * cos(ori) + phase + tiny) >= 0, 1, -1) elif gratType is "sinXsin": intensity = sin(xrange) * sin(yrange) else: # might be a filename of an image try: im = Image.open(gratType) except Exception: logging.error("couldn't find tex...", gratType) return # todo: opened it, now what? return intensity
def resample(self, newRate=16000, keep=True, log=True): """Re-sample the saved file to a new rate, return the full path. Can take several visual frames to resample a 2s recording. The default values for resample() are for google-speech, keeping the original (presumably recorded at 48kHz) to archive. A warning is generated if the new rate not an integer factor / multiple of the old rate. To control anti-aliasing, use pyo.downsamp() or upsamp() directly. """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = "%s: Re-sample requested but no saved file" % self.loggingId logging.error(msg) raise ValueError(msg) if newRate <= 0 or type(newRate) != int: msg = "%s: Re-sample bad new rate = %s" % (self.loggingId, repr(newRate)) logging.error(msg) raise ValueError(msg) # set-up: if self.rate >= newRate: ratio = float(self.rate) / newRate info = "-ds%i" % ratio else: ratio = float(newRate) / self.rate info = "-us%i" % ratio if ratio != int(ratio): logging.warn("%s: old rate is not an integer factor of new rate" % self.loggingId) ratio = int(ratio) newFile = info.join(os.path.splitext(self.savedFile)) # use pyo's downsamp or upsamp based on relative rates: if not ratio: logging.warn("%s: Re-sample by %sx is undefined, skipping" % (self.loggingId, str(ratio))) elif self.rate >= newRate: t0 = core.getTime() downsamp(self.savedFile, newFile, ratio) # default 128-sample anti-aliasing if log and self.autoLog: logging.exp( "%s: Down-sampled %sx in %.3fs to %s" % (self.loggingId, str(ratio), core.getTime() - t0, newFile) ) else: t0 = core.getTime() upsamp(self.savedFile, newFile, ratio) # default 128-sample anti-aliasing if log and self.autoLog: logging.exp( "%s: Up-sampled %sx in %.3fs to %s" % (self.loggingId, str(ratio), core.getTime() - t0, newFile) ) # clean-up: if not keep: os.unlink(self.savedFile) self.savedFile = newFile self.rate = newRate return os.path.abspath(newFile)
def setUseShaders(self, val=True): """Set this stimulus to use shaders if possible. """ #NB TextStim overrides this function, so changes here may need changing there too if val==True and self.win._haveShaders==False: logging.error("Shaders were requested but aren't available. Shaders need OpenGL 2.0+ drivers") if val!=self.useShaders: self.useShaders=val self.setImage()
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.params['expName'])) expInfo = self.params['Experiment info'].val.strip() if not len(expInfo): expInfo = '{}' try: eval('dict('+expInfo+')') except SyntaxError, err: logging.error('Builder Expt: syntax error in "Experiment info" settings (expected a dict)') raise SyntaxError, 'Builder: error in "Experiment info" settings (expected a dict)'
def font(self, font): """String. Set the font to be used for text rendering. font should be a string specifying the name of the font (in system resources). """ self.__dict__['font'] = None # until we find one if self.win.winType == "pyglet": self._font = pyglet.font.load(font, int(self._heightPix), dpi=72, italic=self.italic, bold=self.bold) self.__dict__['font'] = font else: if font is None or len(font) == 0: self.__dict__['font'] = pygame.font.get_default_font() elif font in pygame.font.get_fonts(): self.__dict__['font'] = font elif type(font) == str: # try to find a xxx.ttf file for it # check for possible matching filenames fontFilenames = glob.glob(font + '*') if len(fontFilenames) > 0: for thisFont in fontFilenames: if thisFont[-4:] in ['.TTF', '.ttf']: # take the first match self.__dict__['font'] = thisFont break # stop at the first one we find # trhen check if we were successful if self.font is None and font != "": # we didn't find a ttf filename msg = ("Found %s but it doesn't end .ttf. " "Using default font.") logging.warning(msg % fontFilenames[0]) self.__dict__['font'] = pygame.font.get_default_font() if self.font is not None and os.path.isfile(self.font): self._font = pygame.font.Font(self.font, int( self._heightPix), italic=self.italic, bold=self.bold) else: try: self._font = pygame.font.SysFont( self.font, int(self._heightPix), italic=self.italic, bold=self.bold) self.__dict__['font'] = font logging.info('using sysFont ' + str(font)) except Exception: self.__dict__['font'] = pygame.font.get_default_font() msg = ("Couldn't find font %s on the system. Using %s " "instead! Font names should be written as " "concatenated names all in lower case.\ne.g. " "'arial', 'monotypecorsiva', 'rockwellextra', ...") logging.error(msg % (font, self.font)) self._font = pygame.font.SysFont( self.font, int(self._heightPix), italic=self.italic, bold=self.bold) # re-render text after a font change self._needSetText = True
def loadShader(self): """Load the shader for the current Bits mode (mono++ or color++) """ self.lastShaderProg = GL.glGetIntegerv(GL.GL_CURRENT_PROGRAM) if self.mode == 'color++': GL.glUseProgram(self.colorModeShader) print 'using color shader' elif self.mode == 'mono++': GL.glUseProgram(self.monoModeShader) print 'using mono shader' else: logging.error('Bits.loadShader() called, but Bits is in %s mode' %self.mode)
def _error(self, msg): self.OK = False logging.error(msg)
except NameError: joystickCache={} if not 0 in joystickCache: joystickCache[0] = joysticklib.Joystick(0) joystick.device = joystickCache[0] if win.units == 'height': joystick.xFactor = 0.5 * win.size[0]/win.size[1] joystick.yFactor = 0.5 else: joystick.device = virtualjoysticklib.VirtualJoystick(0) logging.warning("joystick_{}: Using keyboard+mouse emulation 'ctrl' + 'Alt' + digit.".format(joystick.device_number)) except Exception: pass if not joystick.device: logging.error('No joystick/gamepad device found.') core.quit() joystick.status = None joystick.clock = core.Clock() joystick.numButtons = joystick.device.getNumButtons() joystick.getNumButtons = joystick.device.getNumButtons joystick.getAllButtons = joystick.device.getAllButtons joystick.getX = lambda: joystick.xFactor * joystick.device.getX() joystick.getY = lambda: joystick.yFactor * joystick.device.getY() # Create some handy timers globalClock = core.Clock() # to track the time since experiment started routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine
def saveAsWideText(self, fileName, delim='auto', matrixOnly=False, appendFile=None, encoding='utf-8-sig', fileCollisionMethod='rename', sortColumns=False): """Saves a long, wide-format text file, with one line representing the attributes and data for a single trial. Suitable for analysis in R and SPSS. If `appendFile=True` then the data will be added to the bottom of an existing file. Otherwise, if the file exists already it will be kept and a new file will be created with a slightly different name. If you want to overwrite the old file, pass 'overwrite' to ``fileCollisionMethod``. If `matrixOnly=True` then the file will not contain a header row, which can be handy if you want to append data to an existing file of the same format. :Parameters: fileName: if extension is not specified, '.csv' will be appended if the delimiter is ',', else '.tsv' will be appended. Can include path info. delim: allows the user to use a delimiter other than the default tab ("," is popular with file extension ".csv") matrixOnly: outputs the data with no header row. appendFile: will add this output to the end of the specified file if it already exists. encoding: The encoding to use when saving a the file. Defaults to `utf-8-sig`. fileCollisionMethod: Collision method passed to :func:`~psychopy.tools.fileerrortools.handleFileCollision` sortColumns: will sort columns alphabetically by header name if True """ # set default delimiter if none given delimOptions = {'comma': ",", 'semicolon': ";", 'tab': "\t"} if delim == 'auto': delim = genDelimiter(fileName) elif delim in delimOptions: delim = delimOptions[delim] if appendFile is None: appendFile = self.appendFiles # create the file or send to stdout fileName = genFilenameFromDelimiter(fileName, delim) f = openOutputFile(fileName, append=appendFile, fileCollisionMethod=fileCollisionMethod, encoding=encoding) names = self._getAllParamNames() names.extend(self.dataNames) # names from the extraInfo dictionary names.extend(self._getExtraInfo()[0]) if len(names) < 1: logging.error( "No data was found, so data file may not look as expected.") # sort names if requested if sortColumns: names.sort() # write a header line if not matrixOnly: for heading in names: f.write(u'%s%s' % (heading, delim)) f.write('\n') # write the data for each entry for entry in self.getAllEntries(): for name in names: if name in entry: ename = str(entry[name]) if ',' in ename or '\n' in ename: fmt = u'"%s"%s' else: fmt = u'%s%s' f.write(fmt % (entry[name], delim)) else: f.write(delim) f.write('\n') if f != sys.stdout: f.close() logging.info('saved data to %r' % f.name)
def getKeys(keyList=None, modifiers=False, timeStamped=False): """Returns a list of keys that were pressed. :Parameters: keyList : **None** or [] Allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If the keyList is `None`, all keys will be checked and the key buffer will be cleared completely. NB, pygame doesn't return timestamps (they are always 0) modifiers : **False** or True If True will return a list of tuples instead of a list of keynames. Each tuple has (keyname, modifiers). The modifiers are a dict of keyboard modifier flags keyed by the modifier name (eg. 'shift', 'ctrl'). timeStamped : **False**, True, or `Clock` If True will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time). If a `core.Clock` is given then the time will be relative to the `Clock`'s last reset. :Author: - 2003 written by Jon Peirce - 2009 keyList functionality added by Gary Strangman - 2009 timeStamped code provided by Dave Britton - 2016 modifiers code provided by 5AM Solutions """ keys = [] if havePygame and display.get_init(): # see if pygame has anything instead (if it exists) for evts in evt.get(locals.KEYDOWN): # pygame has no keytimes keys.append((pygame.key.name(evts.key), 0)) elif havePyglet: # for each (pyglet) window, dispatch its events before checking event # buffer defDisplay = pyglet.window.get_platform().get_default_display() for win in defDisplay.get_windows(): try: win.dispatch_events() # pump events on pyglet windows except ValueError as e: # pragma: no cover # Pressing special keys, such as 'volume-up', results in a # ValueError. This appears to be a bug in pyglet, and may be # specific to certain systems and versions of Python. logging.error(u'Failed to handle keypress') global _keyBuffer if len(_keyBuffer) > 0: # then pyglet is running - just use this keys = _keyBuffer # _keyBuffer = [] # DO /NOT/ CLEAR THE KEY BUFFER ENTIRELY if keyList is None: _keyBuffer = [] # clear buffer entirely targets = keys # equivalent behavior to getKeys() else: nontargets = [] targets = [] # split keys into keepers and pass-thrus for key in keys: if key[0] in keyList: targets.append(key) else: nontargets.append(key) _keyBuffer = nontargets # save these # now we have a list of tuples called targets # did the user want timestamped tuples or keynames? if modifiers == False and timeStamped == False: keyNames = [k[0] for k in targets] return keyNames elif timeStamped == False: keyNames = [(k[0], modifiers_dict(k[1])) for k in targets] return keyNames elif hasattr(timeStamped, 'getLastResetTime'): # keys were originally time-stamped with # core.monotonicClock._lastResetTime # we need to shift that by the difference between it and # our custom clock _last = timeStamped.getLastResetTime() _clockLast = psychopy.core.monotonicClock.getLastResetTime() timeBaseDiff = _last - _clockLast relTuple = [ filter(None, (k[0], modifiers and modifiers_dict(k[1]) or None, k[-1] - timeBaseDiff)) for k in targets ] return relTuple elif timeStamped is True: return [ filter(None, (k[0], modifiers and modifiers_dict(k[1]) or None, k[-1])) for k in targets ] elif isinstance(timeStamped, (float, int, long)): relTuple = [ filter(None, (k[0], modifiers and modifiers_dict(k[1]) or None, k[-1] - timeStamped)) for k in targets ] return relTuple
def __init__(self, filename, lang='en-US', timeout=10, samplingrate=16000, pro_filter=2, quiet=True): """ :Parameters: `filename` : <required> name of the speech file (.flac, .wav, or .spx) to process. wav files will be converted to flac, and for this to work you need to have flac (as an executable). spx format is speex-with-headerbyte (for google). `lang` : the presumed language of the speaker, as a locale code; default 'en-US' `timeout` : seconds to wait before giving up, default 10 `samplingrate` : the sampling rate of the speech clip in Hz, either 16000 or 8000. You can record at a higher rate, and then down-sample to 16000 for speech recognition. `file` is the down-sampled file, not the original. the sampling rate is auto-detected for .wav files. `pro_filter` : profanity filter level; default 2 (e.g., f***) `quiet` : no reporting intermediate details; default `True` (non-verbose) """ # set up some key parameters: results = 5 # how many words wanted self.timeout = timeout useragent = PSYCHOPY_USERAGENT host = "www.google.com/speech-api/v1/recognize" flac_path = _getFlacPath() # determine file type, convert wav to flac if needed: if not os.path.isfile(filename): raise IOError("Cannot find file: %s" % filename) ext = os.path.splitext(filename)[1] if ext not in ['.flac', '.spx', '.wav']: raise SoundFormatNotSupported("Unsupported filetype: %s\n" % ext) if ext == '.wav': __, samplingrate = readWavFile(filename) if samplingrate not in [16000, 8000]: raise SoundFormatNotSupported('Speech2Text sample rate must be 16000 or 8000 Hz') self.filename = filename if ext == ".flac": filetype = "x-flac" elif ext == ".spx": filetype = "x-speex-with-header-byte" elif ext == ".wav": # convert to .flac filetype = "x-flac" filename = wav2flac(filename) logging.info("Loading: %s as %s, audio/%s" % (self.filename, lang, filetype)) c = 0 # occasional error; core.wait(.1) is not always enough; better slow than fail while not os.path.isfile(filename) and c < 10: core.wait(.1, 0) c += 1 try: audio = open(filename, 'rb').read() except: msg = "Can't read file %s from %s.\n" % (filename, self.filename) logging.error(msg) raise SoundFileError(msg) finally: if ext == '.wav' and filename.endswith('.flac'): try: os.remove(filename) except: pass # urllib2 makes no attempt to validate the server certificate. here's an idea: # http://thejosephturner.com/blog/2011/03/19/https-certificate-verification-in-python-with-urllib2/ # set up the https request: url = 'https://' + host + '?xjerr=1&' +\ 'client=psychopy2&' +\ 'lang=' + lang +'&'\ 'pfilter=%d' % pro_filter + '&'\ 'maxresults=%d' % results header = {'Content-Type' : 'audio/%s; rate=%d' % (filetype, samplingrate), 'User-Agent': useragent} web.requireInternetAccess() # needed to access google's speech API try: self.request = urllib2.Request(url, audio, header) except: # try again before accepting defeat logging.info("https request failed. %s, %s. trying again..." % (filename, self.filename)) core.wait(0.2, 0) self.request = urllib2.Request(url, audio, header)
# update/draw components on each frame if len(feedback_response.keys) < 1: msg = "" else: msg = "X" # *required_response* updates if t >= 0.3 and required_response.status == NOT_STARTED: # keep track of start time/frame for later required_response.tStart = t # underestimates by a little under one frame required_response.frameNStart = frameN # exact frame index required_response.status = STARTED # AllowedKeys looks like a variable named `required_allowed` if not 'required_allowed' in locals(): logging.error( 'AllowedKeys variable `required_allowed` is not defined.' ) core.quit() if not type(required_allowed) in [list, tuple, np.ndarray]: if not isinstance(required_allowed, basestring): logging.error( 'AllowedKeys variable `required_allowed` is not string- or list-like.' ) core.quit() elif not ',' in required_allowed: required_allowed = (required_allowed, ) else: required_allowed = eval(required_allowed) # keyboard checking is just starting required_response.clock.reset() # now t=0 event.clearEvents(eventType='keyboard')
def loadFromXML(self, filename): """Loads an xml file and parses the builder Experiment from it """ self._doc.parse(filename) root = self._doc.getroot() # some error checking on the version (and report that this isn't valid # .psyexp)? filenameBase = os.path.basename(filename) if root.tag != "PsychoPy2experiment": logging.error('%s is not a valid .psyexp file, "%s"' % (filenameBase, root.tag)) # the current exp is already vaporized at this point, oops return self.psychopyVersion = root.get('version') # Parse document nodes # first make sure we're empty self.flow = Flow(exp=self) # every exp has exactly one flow self.routines = {} self.namespace = NameSpace(self) # start fresh modifiedNames = [] duplicateNames = [] # fetch exp settings settingsNode = root.find('Settings') for child in settingsNode: self._getXMLparam(params=self.settings.params, paramNode=child, componentNode=settingsNode) # name should be saved as a settings parameter (only from 1.74.00) if self.settings.params['expName'].val in ['', None, 'None']: shortName = os.path.splitext(filenameBase)[0] self.setExpName(shortName) # fetch routines routinesNode = root.find('Routines') allCompons = getAllComponents( self.prefsBuilder['componentsFolders'], fetchIcons=False) # get each routine node from the list of routines for routineNode in routinesNode: routineGoodName = self.namespace.makeValid( routineNode.get('name')) if routineGoodName != routineNode.get('name'): modifiedNames.append(routineNode.get('name')) self.namespace.user.append(routineGoodName) routine = Routine(name=routineGoodName, exp=self) # self._getXMLparam(params=routine.params, paramNode=routineNode) self.routines[routineNode.get('name')] = routine for componentNode in routineNode: componentType = componentNode.tag if componentType in allCompons: # create an actual component of that type component = allCompons[componentType]( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) else: # create UnknownComponent instead component = allCompons['UnknownComponent']( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) # check for components that were absent in older versions of # the builder and change the default behavior # (currently only the new behavior of choices for RatingScale, # HS, November 2012) # HS's modification superceded Jan 2014, removing several # RatingScale options if componentType == 'RatingScaleComponent': if (componentNode.get('choiceLabelsAboveLine') or componentNode.get('lowAnchorText') or componentNode.get('highAnchorText')): pass # if not componentNode.get('choiceLabelsAboveLine'): # # this rating scale was created using older version # component.params['choiceLabelsAboveLine'].val=True # populate the component with its various params for paramNode in componentNode: self._getXMLparam(params=component.params, paramNode=paramNode, componentNode=componentNode) compGoodName = self.namespace.makeValid( componentNode.get('name')) if compGoodName != componentNode.get('name'): modifiedNames.append(componentNode.get('name')) self.namespace.add(compGoodName) component.params['name'].val = compGoodName routine.append(component) # for each component that uses a Static for updates, we need to set # that for thisRoutine in list(self.routines.values()): for thisComp in thisRoutine: for thisParamName in thisComp.params: thisParam = thisComp.params[thisParamName] if thisParamName == 'advancedParams': continue # advanced isn't a normal param elif thisParam.updates and "during:" in thisParam.updates: # remove the part that says 'during' updates = thisParam.updates.split(': ')[1] routine, static = updates.split('.') if routine not in self.routines: msg = ("%s was set to update during %s Static " "Component, but that component no longer " "exists") logging.warning(msg % (thisParamName, static)) else: self.routines[routine].getComponentFromName( static).addComponentUpdate( thisRoutine.params['name'], thisComp.params['name'], thisParamName) # fetch flow settings flowNode = root.find('Flow') loops = {} for elementNode in flowNode: if elementNode.tag == "LoopInitiator": loopType = elementNode.get('loopType') loopName = self.namespace.makeValid(elementNode.get('name')) if loopName != elementNode.get('name'): modifiedNames.append(elementNode.get('name')) self.namespace.add(loopName) loop = eval('%s(exp=self,name="%s")' % (loopType, loopName)) loops[loopName] = loop for paramNode in elementNode: self._getXMLparam(paramNode=paramNode, params=loop.params) # for conditions convert string rep to list of dicts if paramNode.get('name') == 'conditions': param = loop.params['conditions'] # e.g. param.val=[{'ori':0},{'ori':3}] try: param.val = eval('%s' % (param.val)) except SyntaxError: # This can occur if Python2.7 conditions string # contained long ints (e.g. 8L) and these can't be # parsed by Py3. But allow the file to carry on # loading and the conditions will still be loaded # from the xlsx file pass # get condition names from within conditionsFile, if any: try: # psychophysicsstaircase demo has no such param conditionsFile = loop.params['conditionsFile'].val except Exception: conditionsFile = None if conditionsFile in ['None', '']: conditionsFile = None if conditionsFile: try: trialList, fieldNames = data.importConditions( conditionsFile, returnFieldNames=True) for fname in fieldNames: if fname != self.namespace.makeValid(fname): duplicateNames.append(fname) else: self.namespace.add(fname) except Exception: pass # couldn't load the conditions file for now self.flow.append(LoopInitiator(loop=loops[loopName])) elif elementNode.tag == "LoopTerminator": self.flow.append(LoopTerminator( loop=loops[elementNode.get('name')])) elif elementNode.tag == "Routine": if elementNode.get('name') in self.routines: self.flow.append(self.routines[elementNode.get('name')]) else: logging.error("A Routine called '{}' was on the Flow but " "could not be found (failed rename?). You " "may need to re-insert it".format( elementNode.get('name'))) logging.flush() if modifiedNames: msg = 'duplicate variable name(s) changed in loadFromXML: %s\n' logging.warning(msg % ', '.join(list(set(modifiedNames)))) if duplicateNames: msg = 'duplicate variable names: %s' logging.warning(msg % ', '.join(list(set(duplicateNames)))) # if we succeeded then save current filename to self self.filename = filename
def getLumSeries(lumLevels=8, winSize=(800, 600), monitor=None, gamma=1.0, allGuns=True, useBits=False, autoMode='auto', stimSize=0.3, photometer=None, screen=0): """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 import psychopy.visual from psychopy import core if photometer is None: havePhotom = False elif not hasattr(photometer, 'getLum'): msg = ("photometer argument to monitors.getLumSeries should be a " "type of photometer object, not a %s") logging.error(msg % 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**(old_div(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, screen=screen) 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 = np.random.rand(512, 512).round() * 2 - 1 backPatch = psychopy.visual.PatchStim( myWin, tex=noise, size=2, units='norm', sf=[old_div(winSize[0], 512.0), old_div(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() backPatch.draw() testPatch.draw() instructions = ("Point the photometer at the central white bar. " "Hit a key when ready (or wait %iss)") message.setText(instructions % tRemain, log=False) message.draw() myWin.flip() if len(psychopy.event.getKeys()): break # we got a keypress so move on if autoMode != 'semi': message.setText('Q to quit at any time') else: message.setText('Spacebar for next patch') # LS100 likes to take at least one bright measurement if havePhotom and photometer.type == 'LS100': junk = photometer.getLum() # what are the test values of luminance if type(lumLevels) in (int, float): toTest = DACrange(lumLevels) else: toTest = np.asarray(lumLevels) if allGuns: guns = [0, 1, 2, 3] # gun=0 is the white luminance measure else: allGuns = [0] # this will hold the measured luminance values lumsList = np.zeros((len(guns), len(toTest)), 'd') # for each gun, for each value run test for gun in guns: for valN, DACval in enumerate(toTest): lum = old_div(DACval, 127.5) - 1 # get into range -1:1 # only do luminanc=-1 once if lum == -1 and gun > 0: continue # set the 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() # allowing the screen to settle (no good reason!) time.sleep(0.2) # 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 # check for quit request for thisKey in psychopy.event.getKeys(): if thisKey in ('q', 'Q', 'escape'): myWin.close() return np.array([]) elif autoMode == 'semi': print("At DAC value %i" % DACval) done = False while not done: # check for quit request for thisKey in psychopy.event.getKeys(): if thisKey in ('q', 'Q', 'escape'): myWin.close() return np.array([]) elif thisKey in (' ', 'space'): done = True myWin.close() # we're done with the visual stimuli if havePhotom: return lumsList else: return np.array([])
def linearizeLums(self, desiredLums, newInterpolators=False, overrideGamma=None): """lums should be uncalibrated luminance values (e.g. a linear ramp) ranging 0:1 """ linMethod = self.getLinearizeMethod() desiredLums = np.asarray(desiredLums) output = desiredLums * 0.0 # needs same size as input # gamma interpolation if linMethod == 3: lumsPre = copy(self.getLumsPre()) if self._gammaInterpolator is not None and not newInterpolators: pass # we already have an interpolator elif lumsPre is not None: if self.autoLog: logging.info('Creating linear interpolation for gamma') # we can make an interpolator self._gammaInterpolator = [] self._gammaInterpolator2 = [] # each of these interpolators is a function! levelsPre = old_div(self.getLevelsPre(), 255.0) for gun in range(4): # scale to 0:1 lumsPre[gun, :] = (old_div( (lumsPre[gun, :] - lumsPre[gun, 0]), (lumsPre[gun, -1] - lumsPre[gun, 0]))) self._gammaInterpolator.append( interpolate.interp1d(lumsPre[gun, :], levelsPre, kind='linear')) # interpFunc = Interpolation.InterpolatingFunction( # (lumsPre[gun,:],), levelsPre) # polyFunc = interpFunc.fitPolynomial(3) # self._gammaInterpolator2.append( [polyFunc.coeff]) else: # no way to do this! Calibrate the monitor logging.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): # gun+1 because we don't want luminance interpolator _gammaIntrpGun = self._gammaInterpolator[gun + 1] output[:, gun] = _gammaIntrpGun(desiredLums[:, gun]) 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 is not 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] if self.autoLog: logging.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.currentCalib['gamma'] gammaWhite = np.average(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]) else: output = gammaInvFun(desiredLums, minLum, maxLumWhite, gammaWhite, eq=linMethod) else: msg = "Don't know how to linearise with method %i" logging.error(msg % linMethod) output = desiredLums return output
def __init__(self, win, filename="", units='pix', size=None, pos=(0.0, 0.0), ori=0.0, flipVert=False, flipHoriz=False, color=(1.0, 1.0, 1.0), colorSpace='rgb', opacity=1.0, volume=1.0, name='', loop=False, autoLog=True, depth=0.0, noAudio=False, vframe_callback=None, fps=None, interpolate=True): """ :Parameters: filename : a string giving the relative or absolute path to the movie. flipVert : True or *False* If True then the movie will be top-bottom flipped flipHoriz : True or *False* If True then the movie will be right-left flipped volume : The nominal level is 100, and 0 is silence. loop : bool, optional Whether to start the movie over from the beginning if draw is called and the movie is done. """ # what local vars are defined (these are the init params) for use # by __repr__ self._initParams = dir() self._initParams.remove('self') super(MovieStim2, self).__init__(win, units=units, name=name, autoLog=False) # check for pyglet if win.winType != 'pyglet': logging.error( 'Movie stimuli can only be used with a pyglet window') core.quit() self._retracerate = win._monitorFrameRate if self._retracerate is None: self._retracerate = win.getActualFrameRate() if self._retracerate is None: logging.warning("FrameRate could not be supplied by psychopy; " "defaulting to 60.0") self._retracerate = 60.0 self.filename = pathToString(filename) self.loop = loop self.flipVert = flipVert self.flipHoriz = flipHoriz self.pos = numpy.asarray(pos, float) self.depth = depth self.opacity = float(opacity) self.volume = volume self._av_stream_time_offset = 0.145 self._no_audio = noAudio self._vframe_callback = vframe_callback self.interpolate = interpolate self.useTexSubImage2D = True self._texID = None self._video_stream = cv2.VideoCapture() self._reset() self.loadMovie(self.filename) self.setVolume(volume) self.nDroppedFrames = 0 self.aspectRatio = self._video_width / float(self._video_height) # size if size is None: self.size = numpy.array([self._video_width, self._video_height], float) elif isinstance(size, (int, float, int)): # treat size as desired width, and calc a height # that maintains the aspect ratio of the video. self.size = numpy.array([size, size / self.aspectRatio], float) else: self.size = val2array(size) self.ori = ori self._updateVertices() # set autoLog (now that params have been initialised) self.autoLog = autoLog if autoLog: logging.exp("Created {} = {}".format(self.name, self))
def setPos(self, newPos=None, operation='', units=None, log=None): """Obsolete - users should use setFieldPos instead of setPos """ logging.error("User called DotStim.setPos(pos). " "Use DotStim.SetFieldPos(pos) instead.")
def __init__(self, port, meterType="PR650", verbose=True): logging.error(self.__doc__) sys.exit()
def useVersion(requestedVersion): """Manage paths and checkout psychopy libraries for requested versions of PsychoPy. requestedVersion : A string with the requested version of PsychoPy to be used. Can be major.minor.patch, e.g., '1.83.01', or a partial version, such as '1.81', or even '1'; uses the most recent version within that series. 'latest' means the most recent release having a tag on github. returns: Returns the current (new) version if it was successfully loaded. Raises a RuntimeError if git is needed and not present, or if other PsychoPy modules have already been loaded. Raises a subprocess CalledProcessError if an invalid git tag/version was checked out. Usage (at the top of an experiment script): from psychopy import useVersion useVersion('1.80') from psychopy import visual, event, ... See also: ensureMinimal() """ # Sanity Checks imported = _psychopyComponentsImported() if imported: msg = _translate("Please request a version before importing any " "PsychoPy modules. (Found: {})") raise RuntimeError(msg.format(imported)) # Get a proper full-version tag from a partial tag: reqdMajorMinorPatch = fullVersion(requestedVersion) logging.exp('Requested: useVersion({}) = {}'.format( requestedVersion, reqdMajorMinorPatch)) if not reqdMajorMinorPatch: msg = _translate('Unknown version `{}`') raise ValueError(msg.format(requestedVersion)) if not os.path.isdir(VERSIONSDIR): _clone(requestedVersion) # Allow the versions subdirectory to be built py3Compatible = _versionFilter(versionOptions(local=False), None) py3Compatible += _versionFilter(availableVersions(local=False), None) py3Compatible.sort(reverse=True) if reqdMajorMinorPatch not in py3Compatible: msg = _translate( "Please request a version of PsychoPy that is compatible with Python 3. " "You can choose from the following versions: {}. " "Alternatively, run a Python 2 installation of PsychoPy < v1.9.0.\n" ) logging.error(msg.format(py3Compatible)) return if psychopy.__version__ != reqdMajorMinorPatch: # Switching required, so make sure `git` is available. if not _gitPresent(): msg = _translate("Please install git; needed by useVersion()") raise RuntimeError(msg) # Setup Requested Version _switchToVersion(reqdMajorMinorPatch) # Reload! reload(psychopy) reload(preferences) reload(constants) reload(logging) reload(web) if _versionTuple(reqdMajorMinorPatch) >= (1, 80): reload(tools) # because this file is within tools # TODO check for other submodules that have already been imported logging.exp('Version now set to: {}'.format(psychopy.__version__)) return psychopy.__version__
def __init__(self, win, size=1, pos=(0, 0), anchor=None, ori=0, nVert=120, shape='circle', inverted=False, units=None, name=None, autoLog=None): # what local vars are defined (these are the init params) for use by # __repr__ self._initParams = dir() self._initParams.remove('self') super(Aperture, self).__init__(name=name, autoLog=False) # set self params self.autoLog = False # change after attribs are set self.win = win if not win.allowStencil: logging.error('Aperture has no effect in a window created ' 'without allowStencil=True') core.quit() self.__dict__['ori'] = ori self.__dict__['inverted'] = inverted self.__dict__['filename'] = False # unit conversions if units != None and len(units): self.units = units else: self.units = win.units # set vertices using shape, or default to a circle with nVerts edges if hasattr(shape, 'lower') and not os.path.isfile(shape): shape = shape.lower() if shape is None or shape == 'circle': # NB: pentagon etc point upwards by setting x,y to be y,x # (sin,cos): vertices = [(0.5 * sin(radians(theta)), 0.5 * cos(radians(theta))) for theta in numpy.linspace(0, 360, nVert, False)] elif isinstance(shape, int): # if given a number, take it as a number of vertices and behave as if shape=='circle and nVerts==shape vertices = [(0.5 * sin(radians(theta)), 0.5 * cos(radians(theta))) for theta in numpy.linspace(0, 360, shape, False)] elif shape == 'square': vertices = [[0.5, -0.5], [-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5]] elif shape == 'triangle': vertices = [[0.5, -0.5], [0, 0.5], [-0.5, -0.5]] elif type(shape) in [tuple, list, numpy.ndarray] and len(shape) > 2: vertices = shape elif isinstance(shape, str): # is a string - see if it points to a file if os.path.isfile(shape): self.__dict__['filename'] = shape else: msg = ("Unrecognized shape for aperture. Expected 'circle'," " 'square', 'triangle', vertices, filename, or None;" " got %s") logging.error(msg % repr(shape)) if self.__dict__['filename']: self._shape = ImageStim(win=self.win, image=self.__dict__['filename'], pos=pos, size=size, autoLog=False, units=self.units) else: self._shape = BaseShapeStim(win=self.win, vertices=vertices, fillColor=1, lineColor=None, colorSpace='rgb', interpolate=False, pos=pos, size=size, anchor=anchor, autoLog=False, units=self.units) self.vertices = self._shape.vertices self._needVertexUpdate = True self._needReset = True # Default when setting attributes # implicitly runs a self.enabled = True. Also sets # self._needReset = True on every call self._reset() self.size = size self.pos = pos # set autoLog now that params have been initialised wantLog = autoLog is None and self.win.autoLog self.__dict__['autoLog'] = autoLog or wantLog if self.autoLog: logging.exp("Created {} = {}".format(self.name, self))
pip install egi For examples on usage see the `example_simple` and `example_multi` files on the `egi github repository <https://github.com/gaelen/python-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) 2015 Jonathan Peirce # Distributed under the terms of the GNU General Public License (GPL). from __future__ import absolute_import, print_function from psychopy import logging try: from .egi import * # pyline: disable=W0614 except ImportError: 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 """ logging.error(msg)
logging.console.setLevel(logging.DEBUG) # receive nearly all messages logDat = logging.LogFile( 'logLastRun.log', filemode='w', # if you set this to 'a' it will append instead of overwriting level=logging.WARNING ) # errors, data and warnings will be sent to this logfile # the following will go to any files with the appropriate minimum level set logging.info('Something fairly unimportant') logging.data('Something about our data. Data is likely very important!') logging.warning( 'Handy while building your experiment - highlights possible flaws in code/design' ) logging.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=logging.EXP) if n in [2, 4]: win.logOnFlip('an even frame occured', level=logging.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")
def mask(self, value): """The alpha mask that forms the shape of the resulting image. Value should be one of: + 'circle', 'gauss', 'raisedCos', **None** (resets to default) + or the name of an image file (most formats supported) + or a numpy array (1xN) ranging -1:1 Note that the mask for `RadialStim` is somewhat different to the mask for :class:`ImageStim`. For `RadialStim` it is a 1D array specifying the luminance profile extending outwards from the center of the stimulus, rather than a 2D array """ # todo: fromFile is not used fromFile = 0 self.__dict__['mask'] = value res = self.texRes # resolution of texture - 128 is bearable step = 1.0 / res rad = numpy.arange(0, 1 + step, step) if type(self.mask) == numpy.ndarray: # handle a numpy array intensity = 255 * self.mask.astype(float) res = len(intensity) elif type(self.mask) == list: # handle a numpy array intensity = 255 * numpy.array(self.mask, float) res = len(intensity) elif self.mask == "circle": intensity = 255.0 * (rad <= 1) elif self.mask == "gauss": # Set SD if specified if self.maskParams is None: sigma = 1.0 / 3 else: sigma = 1.0 / self.maskParams['sd'] # 3sd.s by the edge of the stimulus intensity = 255.0 * numpy.exp(-rad**2.0 / (2.0 * sigma**2.0)) elif self.mask == "radRamp": # a radial ramp intensity = 255.0 - 255.0 * rad # half wave rectify: intensity = numpy.where(rad < 1, intensity, 0) elif self.mask in [None, "none", "None"]: res = 4 intensity = 255.0 * numpy.ones(res, float) else: # might be a filename of a tiff try: im = Image.open(self.mask) im = im.transpose(Image.FLIP_TOP_BOTTOM) im = im.resize([max(im.size), max(im.size)], Image.BILINEAR) # make it square except IOError as details: msg = "couldn't load mask...%s: %s" logging.error(msg % (value, details)) return res = im.size[0] im = im.convert("L") # force to intensity (in case it was rgb) intensity = numpy.asarray(im) fromFile = 1 data = intensity.astype(numpy.uint8) mask = data.tostring() # serialise # do the openGL binding if self.interpolate: smoothing = GL.GL_LINEAR else: smoothing = GL.GL_NEAREST GL.glBindTexture(GL.GL_TEXTURE_1D, self._maskID) GL.glTexImage1D(GL.GL_TEXTURE_1D, 0, GL.GL_ALPHA, res, 0, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, mask) # makes the texture map wrap (this is actually default anyway) GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT) # linear smoothing if texture is stretched GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_MAG_FILTER, smoothing) GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_MIN_FILTER, smoothing) GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE) GL.glEnable(GL.GL_TEXTURE_1D) self._needUpdate = True
def syncProject(parent, project=None, closeFrameWhenDone=False): """A function to sync the current project (if there is one) Returns ----------- 1 for success 0 for fail -1 for cancel at some point in the process """ if not pavlovia.haveGit: noGitWarning(parent) return 0 isCoder = hasattr(parent, 'currentDoc') # Test and reject sync from invalid folders if isCoder: currentPath = os.path.dirname(parent.currentDoc.filename) else: currentPath = os.path.dirname(parent.filename) currentPath = os.path.normcase(os.path.expanduser(currentPath)) invalidFolders = [ os.path.normcase(os.path.expanduser('~/Desktop')), os.path.normcase(os.path.expanduser('~/My Documents')) ] if currentPath in invalidFolders: wx.MessageBox( ("You cannot sync projects from:\n\n" " - Desktop\n" " - My Documents\n\n" "Please move your project files to another folder, and try again." ), "Project Sync Error", wx.ICON_QUESTION | wx.OK) return -1 if not project and "BuilderFrame" in repr(parent): # try getting one from the frame project = parent.project # type: pavlovia.PavloviaProject if not project: # ask the user to create one # if we're going to create a project we need user to be logged in pavSession = pavlovia.getCurrentSession() try: username = pavSession.user.username except: username = logInPavlovia(parent) if not username: return -1 # never logged in # create project dialog msg = _translate("This file doesn't belong to any existing project.") style = wx.OK | wx.CANCEL | wx.CENTER dlg = wx.MessageDialog(parent=parent, message=msg, style=style) dlg.SetOKLabel(_translate("Create a project")) if dlg.ShowModal() == wx.ID_OK: if isCoder: if parent.currentDoc: localRoot = os.path.dirname(parent.currentDoc.filename) else: localRoot = '' else: localRoot = os.path.dirname(parent.filename) # open the project editor (with no project to create one) editor = ProjectEditor(parent=parent, localRoot=localRoot) if editor.ShowModal() == wx.ID_OK: project = editor.project else: project = None else: return -1 # user pressed cancel if not project: # we did our best for them. Give up! return 0 # if project.localRoot doesn't exist, or is empty if 'localRoot' not in project or not project.localRoot: # we first need to choose a location for the repository setLocalPath(parent, project) parent.Raise() # make sure that frame is still visible #check that the project does exist remotely if not project.pavlovia: # project didn't exist at Pavlovia (deleted?) recreatorDlg = ProjectRecreator(parent=parent, project=project) ok = recreatorDlg.ShowModal() if ok > 0: project = recreatorDlg.project else: logging.error("Failed to recreate project to sync with") return 0 # a sync will be necessary so set the target to Runner stdout parent.app.showRunner() syncFrame = parent.app.runner.stdOut if project._newRemote: # new remote so this will be a first push if project.getRepo(forceRefresh=True) is None: # no local repo yet so create one project.newRepo(syncFrame) # add the local files and commit them ok = showCommitDialog(parent=parent, project=project, initMsg="First commit", infoStream=syncFrame) if ok == -1: # cancelled syncFrame.Destroy() return -1 syncFrame.setStatus("Pushing files to Pavlovia") wx.Yield() time.sleep(0.001) # git push -u origin master try: project.firstPush(infoStream=syncFrame) project._newRemote = False except Exception as e: closeFrameWhenDone = False syncFrame.statusAppend(traceback.format_exc()) else: # existing remote which we should sync (or clone) try: ok = project.getRepo(syncFrame) if not ok: closeFrameWhenDone = False except Exception as e: closeFrameWhenDone = False syncFrame.statusAppend(traceback.format_exc()) # check for anything to commit before pull/push outcome = showCommitDialog(parent, project, infoStream=syncFrame) # 0=nothing to do, 1=OK, -1=cancelled if outcome == -1: # user cancelled return -1 try: status = project.sync(syncFrame) if status == -1: syncFrame.statusAppend("Couldn't sync") except Exception: # not yet sure what errors might occur # send the error to panel syncFrame.statusAppend(traceback.format_exc()) return 0 wx.Yield() project._lastKnownSync = time.time() if closeFrameWhenDone: pass return 1
def compareTextFiles(pathToActual, pathToCorrect, delim=None, encoding='utf-8-sig', tolerance=None): """Compare the text of two files, ignoring EOL differences, and save a copy if they differ State a tolerance, or percentage of errors allowed, to account for differences in version numbers, datetime, etc """ if not os.path.isfile(pathToCorrect): logging.warning( 'There was no comparison ("correct") file available, for path "{pathToActual}"\n' '\t\t\tSaving current file as the comparison: {pathToCorrect}'. format(pathToActual=pathToActual, pathToCorrect=pathToCorrect)) shutil.copyfile(pathToActual, pathToCorrect) raise IOError( "File not found" ) # deliberately raise an error to see the warning message, but also to create file allowLines = 0 lineDiff = True if delim is None: if pathToCorrect.endswith('.csv'): delim = ',' elif pathToCorrect.endswith(('.dlm', '.tsv')): delim = '\t' try: # we have the necessary file with io.open(pathToActual, 'r', encoding='utf-8-sig', newline=None) as f: txtActual = f.readlines() with io.open(pathToCorrect, 'r', encoding='utf-8-sig', newline=None) as f: txtCorrect = f.readlines() if tolerance is not None: # Set number of lines allowed to fail allowLines = round((tolerance * len(txtCorrect)) / 100, 0) # Check number of lines per document for equality lineDiff = len(txtActual) == len(txtCorrect) assert lineDiff for lineN in range(len(txtActual)): if delim is None: lineActual = txtActual[lineN] lineCorrect = txtCorrect[lineN] # just compare the entire line if not lineActual == lineCorrect: allowLines -= 1 assert allowLines >= 0 else: # word by word instead lineActual = txtActual[lineN].split(delim) lineCorrect = txtCorrect[lineN].split(delim) for wordN in range(len(lineActual)): wordActual = lineActual[wordN] wordCorrect = lineCorrect[wordN] try: wordActual = float(wordActual.lstrip('"[').strip(']"')) wordCorrect = float( wordCorrect.lstrip('"[').strip(']"')) # its not a whole well-formed list because .split(delim) isFloat = True except Exception: #stick with simple text if not a float value isFloat = False pass if isFloat: #to a default of 8 dp? assert np.allclose(wordActual,wordCorrect), "Numeric values at (%i,%i) differ: %f != %f " \ %(lineN, wordN, wordActual, wordCorrect) else: if wordActual != wordCorrect: print('actual:') print(repr(txtActual[lineN])) print(lineActual) print('expected:') print(repr(txtCorrect[lineN])) print(lineCorrect) assert wordActual==wordCorrect, "Values at (%i,%i) differ: %s != %s " \ %(lineN, wordN, repr(wordActual), repr(wordCorrect)) except AssertionError as err: pathToLocal, ext = os.path.splitext(pathToCorrect) pathToLocal = pathToLocal + '_local' + ext # Set assertion type if not lineDiff: # Fail if number of lines not equal msg = "{} has the wrong number of lines".format(pathToActual) elif allowLines < 0: # Fail if tolerance reached msg = 'Number of differences in {failed} exceeds the {tol}% tolerance'.format( failed=pathToActual, tol=tolerance or 0) else: shutil.copyfile(pathToActual, pathToLocal) msg = "txtActual != txtCorr: Saving local copy to {}".format( pathToLocal) logging.error(msg) raise AssertionError(err)
def setColor(obj, color, colorSpace=None, operation='', rgbAttrib='rgb', # or 'fillRGB' etc colorAttrib='color', # or 'fillColor' etc colorSpaceAttrib=None, # e.g. 'colorSpace' or 'fillColorSpace' log=True): """Provides the workings needed by setColor, and can perform this for any arbitrary color type (e.g. fillColor,lineColor etc). OBS: log argument is deprecated - has no effect now. Logging should be done when setColor() is called. """ # how this works: # rather than using obj.rgb=rgb this function uses setattr(obj,'rgb',rgb) # color represents the color in the native space # colorAttrib is the name that color will be assigned using # setattr(obj,colorAttrib,color) # rgb is calculated from converting color # rgbAttrib is the attribute name that rgb is stored under, # e.g. lineRGB for obj.lineRGB # colorSpace and takes name from colorAttrib+space e.g. # obj.lineRGBSpace=colorSpace if colorSpaceAttrib is None: colorSpaceAttrib = colorAttrib + 'Space' # Handle strings and returns immediately as operations, colorspace etc. # does not apply here. if isinstance(color, basestring): if operation not in ('', None): raise TypeError('Cannot do operations on named or hex color') if color.lower() in colors.colors255: # set rgb, color and colorSpace setattr(obj, rgbAttrib, np.array(colors.colors255[color.lower()], float)) obj.__dict__[colorSpaceAttrib] = 'named' # e.g. 3rSpace='named' obj.__dict__[colorAttrib] = color # e.g. obj.color='red' setTexIfNoShaders(obj) return elif color[0] == '#' or color[0:2] == '0x': # e.g. obj.rgb=[0,0,0] setattr(obj, rgbAttrib, np.array(colors.hex2rgb255(color))) obj.__dict__[colorSpaceAttrib] = 'hex' # eg obj.colorSpace='hex' obj.__dict__[colorAttrib] = color # eg Qr='#000000' setTexIfNoShaders(obj) return else: # we got a string, but it isn't in the list of named colors and # doesn't work as a hex raise AttributeError( "PsychoPy can't interpret the color string '%s'" % color) else: # If it wasn't a string, do check and conversion of scalars, # sequences and other stuff. color = val2array(color, length=3) # enforces length 1 or 3 if color is None: setattr(obj, rgbAttrib, None) # e.g. obj.rgb=[0,0,0] obj.__dict__[colorSpaceAttrib] = None # e.g. obj.colorSpace='hex' obj.__dict__[colorAttrib] = None # e.g. obj.color='#000000' setTexIfNoShaders(obj) # at this point we have a numpy array of 3 vals # check if colorSpace is given and use obj.colorSpace if not if colorSpace is None: colorSpace = getattr(obj, colorSpaceAttrib) # using previous color space - if we got this far in the # _stColor function then we haven't been given a color name - # we don't know what color space to use. if colorSpace in ('named', 'hex'): logging.error("If you setColor with a numeric color value then" " you need to specify a color space, e.g. " "setColor([1,1,-1],'rgb'), unless you used a " "numeric value previously in which case PsychoPy " "will reuse that color space.)") return # check whether combining sensible colorSpaces (e.g. can't add things to # hex or named colors) if operation != '' and getattr(obj, colorSpaceAttrib) in ['named', 'hex']: msg = ("setColor() cannot combine ('%s') colors " "within 'named' or 'hex' color spaces") raise AttributeError(msg % operation) elif operation != '' and colorSpace != getattr(obj, colorSpaceAttrib): msg = ("setColor cannot combine ('%s') colors" " from different colorSpaces (%s,%s)") raise AttributeError(msg % (operation, obj.colorSpace, colorSpace)) else: # OK to update current color if colorSpace == 'named': # operations don't make sense for named obj.__dict__[colorAttrib] = color else: setAttribute(obj, colorAttrib, color, log=False, operation=operation, stealth=True) # get window (for color conversions) if colorSpace in ['dkl', 'lms']: # only needed for these spaces if hasattr(obj, 'dkl_rgb'): win = obj # obj is probably a Window elif hasattr(obj, 'win'): win = obj.win # obj is probably a Stimulus else: win = None logging.error("_setColor() is being applied to something" " that has no known Window object") # convert new obj.color to rgb space newColor = getattr(obj, colorAttrib) if colorSpace in ['rgb', 'rgb255']: setattr(obj, rgbAttrib, newColor) elif colorSpace == 'dkl': if (win.dkl_rgb is None or np.all(win.dkl_rgb == np.ones([3, 3]))): dkl_rgb = None else: dkl_rgb = win.dkl_rgb setattr(obj, rgbAttrib, colors.dkl2rgb( np.asarray(newColor).transpose(), dkl_rgb)) elif colorSpace == 'lms': if (win.lms_rgb is None or np.all(win.lms_rgb == np.ones([3, 3]))): lms_rgb = None elif win.monitor.getPsychopyVersion() < '1.76.00': logging.error("The LMS calibration for this monitor was carried" " out before version 1.76.00." " We would STRONGLY recommend that you repeat the " "color calibration before using this color space " "(contact Jon for further info).") lms_rgb = win.lms_rgb else: lms_rgb = win.lms_rgb setattr(obj, rgbAttrib, colors.lms2rgb(newColor, lms_rgb)) elif colorSpace == 'hsv': setattr(obj, rgbAttrib, colors.hsv2rgb(np.asarray(newColor))) elif colorSpace is None: pass # probably using named colors? else: logging.error('Unknown colorSpace: %s' % colorSpace) # store name of colorSpace for future ref and for drawing obj.__dict__[colorSpaceAttrib] = colorSpace # if needed, set the texture too setTexIfNoShaders(obj)
def writeStartCode(self, buff): code = ("# Ensure that relative paths start from the same directory " "as this script\n" "_thisDir = os.path.dirname(os.path.abspath(__file__))." "decode(sys.getfilesystemencoding())\n" "os.chdir(_thisDir)\n\n" "# Store info about the experiment session\n") buff.writeIndentedLines(code) if self.params['expName'].val in [None, '']: buff.writeIndented("expName = 'untitled.py'\n") else: code = ("expName = %s # from the Builder filename that created" " this script\n") buff.writeIndented(code % self.params['expName']) expInfo = self.params['Experiment info'].val.strip() if not len(expInfo): expInfo = '{}' try: expInfoDict = eval('dict(' + expInfo + ')') except SyntaxError: logging.error('Builder Expt: syntax error in ' '"Experiment info" settings (expected a dict)') raise AttributeError('Builder: error in "Experiment info"' ' settings (expected a dict)') buff.writeIndented("expInfo = %s\n" % expInfo) if self.params['Show info dlg'].val: buff.writeIndentedLines( "dlg = gui.DlgFromDict(dictionary=expInfo, title=expName)\n" "if dlg.OK == False:\n core.quit() # user pressed cancel\n" ) buff.writeIndentedLines( "expInfo['date'] = data.getDateStr() # add a simple timestamp\n" "expInfo['expName'] = expName\n") level = self.params['logging level'].val.upper() saveToDir = self.getSaveDataDir() buff.writeIndentedLines("\n# Data file name stem = absolute path +" " name; later add .psyexp, .csv, .log, etc\n") # deprecated code: before v1.80.00 we had 'Saved data folder' param # fairly fixed filename if 'Saved data folder' in self.params: participantField = '' for field in ('participant', 'Participant', 'Subject', 'Observer'): if field in expInfoDict: participantField = field self.params['Data filename'].val = ( repr(saveToDir) + " + os.sep + '%s_%s' % (expInfo['" + field + "'], expInfo['date'])") break if not participantField: # no participant-type field, so skip that part of filename self.params['Data filename'].val = repr( saveToDir) + " + os.path.sep + expInfo['date']" # so that we don't overwrite users changes doing this again del self.params['Saved data folder'] # now write that data file name to the script if not self.params['Data filename'].val: # i.e., the user deleted it self.params['Data filename'].val = ( repr(saveToDir) + " + os.sep + u'psychopy_data_' + data.getDateStr()") # detect if user wanted an absolute path -- else make absolute: filename = self.params['Data filename'].val.lstrip('"\'') # (filename.startswith('/') or filename[1] == ':'): if filename == os.path.abspath(filename): buff.writeIndented("filename = %s\n" % self.params['Data filename']) else: buff.writeIndented("filename = _thisDir + os.sep + %s\n" % self.params['Data filename']) # set up the ExperimentHandler code = ("\n# An ExperimentHandler isn't essential but helps with " "data saving\n" "thisExp = data.ExperimentHandler(name=expName, version='',\n" " extraInfo=expInfo, runtimeInfo=None,\n" " originPath=%s,\n") buff.writeIndentedLines(code % repr(self.exp.expPath)) code = (" savePickle=%(Save psydat file)s, saveWideText=%(Save " "wide csv file)s,\n dataFileName=filename)\n") buff.writeIndentedLines(code % self.params) if self.params['Save log file'].val: code = ("# save a log file for detail verbose info\nlogFile = " "logging.LogFile(filename+'.log', level=logging.%s)\n") buff.writeIndentedLines(code % level) buff.writeIndented("logging.console.setLevel(logging.WARNING) " "# this outputs to the screen, not a file\n") if self.exp.settings.params['Enable Escape'].val: buff.writeIndentedLines("\nendExpNow = False # flag for 'escape'" " or other condition => quit the exp\n")
if not autopilot: myDlg.addField('Subject name (default="Adi"):', 'Adi', tip='or subject code') dlgLabelsOrdered.append('subject') myDlg.addField('\tPercent noise dots=', defaultNoiseLevel, tip=str(defaultNoiseLevel)) dlgLabelsOrdered.append('defaultNoiseLevel') myDlg.addField('Trials per condition (default=' + str(trialsPerCondition) + '):', trialsPerCondition, tip=str(trialsPerCondition)) dlgLabelsOrdered.append('trialsPerCondition') pctCompletedBreak = 25 myDlg.addText(refreshMsg1, color='Black') if refreshRateWrong: myDlg.addText(refreshMsg2, color='Red') if refreshRateWrong: logging.error(refreshMsg1+refreshMsg2) else: logging.info(refreshMsg1+refreshMsg2) if checkRefreshEtc and (not demo) and (myWinRes != [widthPix, heightPix]).any(): msgWrongResolution = 'Screen apparently NOT the desired resolution of ' + \ str(widthPix)+'x'+str(heightPix) + ' pixels!!' myDlg.addText(msgWrongResolution, color='Red') logging.error(msgWrongResolution) print(msgWrongResolution) # color='DimGrey') color names stopped working along the way, for unknown reason myDlg.addText('Note: to abort press ESC at a trials response screen') myDlg.show() if myDlg.OK: # unpack information from dialogue box thisInfo = myDlg.data # this will be a list of data returned from each field added in order
def encrypt(datafile, pubkeyPem, meta=True, keep=None): """Encrypt a file using openssl, AES-256, and an RSA public-key. Returns: full path to the encrypted file (= .tgz bundle of 3 files). By default the original plaintext is deleted after encryption (see parameter `keep`). The idea is that you can have and share a public key, which anyone can use to encrypt things that only you can decrypt. Generating good keys and managing them is non-trivial, and is entirely up to you. (GPG can help a lot.) For better security, it is good to use signed public keys. No attempt is made here to verify key signatures automatically; you could do so manually using `verify()`. :Parameters: `datafile`: The path (name) of the original plaintext file to be encrypted. `pubkeyPem`: The public key to use, specified as the path to a .pem file. Example file contents (1024 bit pub.pem):: -----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALcw2C2Tyiq514Nc+Oe1TvweyzK92PSm s7KYMziTNcMy50E9KjSb7k8U/6Jaz/foeWFJqID1cmiyj1whZfZ4KycCAwEAAQ== -----END PUBLIC KEY----- `meta`: If `True`, include meta-data as plaintext in the archive:: original file name & sha256 of encrypted platform & date openssl version, padding pubkey info (to aid in key rotation) `keep`: None (default) = remove original (unencrypted) & all intermediate files (more secure) 'orig' = leave original file, delete intermediate (encrypted) files 'all' = leave all intermed files & orig (for testing purposes) """ logging.debug('encrypt (beta): start') if not datafile or not os.path.isfile(datafile): msg = 'encrypt (beta): no data to encrypt; %s not found' % datafile logging.error(msg) raise ValueError(msg) # good file names: if not pubkeyPem or not os.path.isfile(pubkeyPem): msg = 'encrypt (beta): missing public-key.pem file; %s not found' % pubkeyPem logging.error(msg) raise ValueError(msg) # can't / don't proceed without a pub key of sufficient length: pkLen = len(open(pubkeyPem).read()) # proxy for number of bits in key if pkLen < 271: # or 451 chars for 2048 bit raise PublicKeyTooShortError("public key < 1024 bits") if not keep in [None, 'orig', 'all']: raise ValueError("encrypt (beta): bad value for 'keep' parameter") # do encryption via encMethod: data.txt --> (data.aes256, data.aes256.pwd.rsa, data.meta) # the two ideas here are 1) to do the call in a way that ensures that the call is # documentatble (in meta-data), so encMethod is the name that is actually used. # and 2) with more work, this will allow a user to specify some other encryption # tool, eg, gpg or pgp, and that will be able to just drop as and arg (+ kwarg dict), while # retaining the .tgz bundle part encMethod = '_encrypt_rsa_aes256cbc' dataEncFile, pEncFile = eval(encMethod + '(datafile, pubkeyPem)') # bundle as a tarfile: (data.aes256, data.aes256.pwd.rsa) --> data.enc.tgz: savedLocation = os.path.abspath(os.path.curdir) root, _ = os.path.split(os.path.abspath(datafile)) os.chdir(root) # cd into dir containing datafile tgzFile = _uniqFileName(os.path.splitext(datafile)[0] + BUNDLE_EXT) # same name, new ext files = [dataEncFile, pEncFile] # files to be bundled in .tgz # encryption meta-data: if meta: metaDataFile = os.path.split(datafile)[1] + META_EXT f = open(metaDataFile, 'wb') # idea: append new meta after a string, e.g., old meta data in rotate if type(meta) == str: f.write(meta + '\n') f.write(_getMetaData(datafile, dataEncFile, pubkeyPem, encMethod)) f.close() files.append(metaDataFile) tar = tarfile.open(tgzFile, "w:gz") for name in files: # remove leading tmp path info from name tar.add(os.path.split(name)[1]) if keep != 'all': os.remove(name) # remove intermediate encrypted files tar.close() os.chdir(savedLocation) if not keep: # remove unencrypted original os.remove(datafile) return os.path.abspath(tgzFile)
def setPos(self, newPos=None, operation='', units=None, log=None): """Obsolete - users should use setFieldPos or instead of setPos. """ logging.error("User called ElementArrayStim.setPos(pos). " "Use ElementArrayStim.setFieldPos(pos) instead.")
backend.defaultInput = dev elif kind == 'output': backend.defaultOutput = dev else: if travisCI: # travisCI doesn't have any audio devices at all. Ignore return else: raise TypeError("`kind` should be one of [None, 'output', 'input']" "not {!r}".format(kind)) # Set the device according to user prefs (if current lib allows it) if hasattr(backend, 'defaultOutput'): pref = prefs.general['audioDevice'] # is it a list or a simple string? if type(prefs.general['audioDevice'])==list: # multiple options so use zeroth dev = prefs.general['audioDevice'][0] else: # a single option dev = prefs.general['audioDevice'] # is it simply "default" (do nothing) if dev=='default' or travisCI: pass # do nothing elif dev not in backend.getDevices(kind='output'): devNames = sorted(backend.getDevices(kind='output').keys()) logging.error(u"Requested audio device '{}' that is not available on " "this hardware. The 'audioDevice' preference should be one of " "{}".format(dev, devNames)) else: setDevice(dev, kind='output')
else: frameDur = 1.0 / 60.0 # could not measure, so guess # Initialize components for Routine "trial" trialClock = core.Clock() buttonBox = None for n in range(10): # doesn't always work first time! try: devices = pyxid.get_xid_devices() core.wait(0.1) buttonBox = devices[0] break # found a device so can break the loop except Exception: pass if not buttonBox: logging.error('could not find a Cedrus device.') core.quit() buttonBox.clock = core.Clock() # Create some handy timers globalClock = core.Clock() # to track the time since experiment started routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine # ------Prepare to start Routine "trial"------- t = 0 trialClock.reset() # clock frameN = -1 continueRoutine = True routineTimer.add(1.000000) # update component parameters for each repeat buttonBox.keys = [] # to store response values
def __init__(self, win=None, portName=None, mode='', checkConfigLevel=1, gammaCorrect='hardware', gamma=None, noComms=False): """ :Parameters: win : a PsychoPy :class:`~psychopy.visual.Window` object, required portName : the (virtual) serial port to which the device is connected. If None then PsychoPy will search available serial ports and test communication (on OSX, the first match of `/dev/tty.usbmodemfa*` will be used and on linux `/dev/ttyS0` will be used mode : 'bits++', 'color++', 'mono++', 'status' checkConfigLevel : integer Allows you to specify how much checking of the device is done to ensure a valid identity look-up table. If you specify one level and it fails then the check will be escalated to the next level (e.g. if we check level 1 and find that it fails we try to find a new LUT): - 0 don't check at all - 1 check that the graphics driver and OS version haven't changed since last LUT calibration - 2 check that the current LUT calibration still provides identity (requires switch to status mode) - 3 search for a new identity look-up table (requires switch to status mode) gammaCorrect : string governing how gamma correction is performed 'hardware': use the gamma correction file stored on the hardware 'FBO': gamma correct using shaders when rendering the FBO to back buffer 'bitsMode': in bits++ mode there is a user-controlled LUT that we can use for gamma correction noComms : bool If True then don't try to communicate with the device at all (passive mode). This can be useful if you want to debug the system without actually having a Bits# connected. """ # import pyglet.GL late so that we can import bits.py without it # initially global GL, visual from psychopy import visual import pyglet.gl as GL if noComms: self.noComms = True self.OK = True self.sendMessage = self._nullSendMessage self.getResponse = self._nullGetResponse else: self.noComms = False # look for device on valid serial ports # parity="N", # 'N'one, 'E'ven, 'O'dd, 'M'ask, serialdevice.SerialDevice.__init__(self, port=portName, baudrate=19200, byteSize=8, stopBits=1, parity="N", eol='\n', maxAttempts=1, pauseDuration=0.1, checkAwake=True) if not self.OK: return # the following are used by bits++ mode self._HEADandLUT = np.zeros((524, 1, 3), np.uint8) # R valsR = (36, 63, 8, 211, 3, 112, 56, 34, 0, 0, 0, 0) self._HEADandLUT[:12, :, 0] = np.asarray(valsR).reshape([12, 1]) # G valsG = (106, 136, 19, 25, 115, 68, 41, 159, 0, 0, 0, 0) self._HEADandLUT[:12, :, 1] = np.asarray(valsG).reshape([12, 1]) # B valsB = (133, 163, 138, 46, 164, 9, 49, 208, 0, 0, 0, 0) self._HEADandLUT[:12, :, 2] = np.asarray(valsB).reshape([12, 1]) self.LUT = np.zeros((256, 3), 'd') # just a place holder # replace window methods with our custom ones self.win = win self.win._prepareFBOrender = self._prepareFBOrender self.win._finishFBOrender = self._finishFBOrender self.win._afterFBOrender = self._afterFBOrender # Bits++ doesn't do its own correction so we need to self.gammaCorrect = gammaCorrect self.gamma = gamma # we have a confirmed connection. Now check details about device and # system if not hasattr(self, 'info'): self.info = self.getInfo() self.config = None self.mode = mode if self.win is not None: if not hasattr(self.win, '_prepareFBOrender'): logging.error("BitsSharp was given an object as win " "argument but this is not a visual.Window") self.win._prepareFBOrender = self._prepareFBOrender self.win._finishFBOrender = self._finishFBOrender self._setupShaders() # now check that we have a valid configuration of the box if checkConfigLevel: ok = self.checkConfig(level=checkConfigLevel) else: self.win.gammaRamp = self.config.identityLUT else: self.config = None # makes no sense if we have a window? logging.warning("%s was not given any PsychoPy win" % (self))
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 macOS 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 * None if there were not even any valid ports (suggesting a driver not being installed) e.g.:: # sweeps ports 0 to 10 searching for a PR655 photom = findPhotometer(device='PR655') print(photom.getLum()) if hasattr(photom, 'getSpectrum'): # can retrieve spectrum (e.g. a PR650) print(photom.getSpectrum()) """ if isinstance(device, basestring): photometers = [getPhotometerByName(device)] elif isinstance(device, collections.Iterable): # if we find a string assume it is a name, otherwise treat it like a # photometer photometers = [ getPhotometerByName(d) if isinstance(d, basestring) else d for d in device ] else: photometers = getAllPhotometers() # determine candidate ports if ports is None: ports = getSerialPorts() elif type(ports) in (int, float) or isinstance(ports, basestring): ports = [ports] # so that we can iterate # go through each port in turn photom = None logging.info('scanning serial ports...') logging.flush() for thisPort in ports: logging.info('...{}'.format(thisPort)) logging.flush() for Photometer in photometers: # Looks like we got an invalid photometer, carry on if Photometer is None: continue try: photom = Photometer(port=thisPort) except Exception as ex: msg = "Couldn't initialize photometer {0}: {1}" logging.error(msg.format(Photometer.__name__, ex)) # We threw an exception so we should just skip ahead continue if photom.OK: logging.info(' ...found a %s\n' % (photom.type)) logging.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) # we found one so stop looking return photom else: if photom.com and photom.com.isOpen: logging.info('closing port') photom.com.close() # If we got here we didn't find one logging.info('...nope!\n\t') logging.flush() return None
def installZipFile(self, zfile, v=None): """If v is provided this will be used as new version number; otherwise try and retrieve a version number from zip file name """ info = "" # return this at the end if PY3: zfileIsName = type(zfile) == str else: zfileIsName = type(zfile) in (str, unicode) if os.path.isfile(zfile) and zfileIsName: # zfile is filename not an actual file if v is None: # try and deduce it zFilename = os.path.split(zfile)[-1] searchName = re.search('[0-9]*\.[0-9]*\.[0-9]*.', zFilename) if searchName != None: v = searchName.group(0)[:-1] else: msg = "Couldn't deduce version from zip file: %s" logging.warning(msg % zFilename) f = open(zfile, 'rb') zfile = zipfile.ZipFile(f) else: # assume here that zfile is a ZipFile pass # todo: error checking - is it a zipfile? currPath = self.app.prefs.paths['psychopy'] currVer = psychopy.__version__ # any commands that are successfully executed may need to be undone if # a later one fails undoStr = "" # depending on install method, needs diff handling # if path ends with 'psychopy' then move it to 'psychopy-version' and # create a new 'psychopy' folder for new version # does the path contain any version number? versionLabelsInPath = re.findall('PsychoPy-.*/', currPath) # e.g. the mac standalone app, no need to refer to new version number onWin32 = bool(sys.platform == 'win32' and int(sys.getwindowsversion()[1]) > 5) if len(versionLabelsInPath) == 0: unzipTarget = currPath try: # to move existing PsychoPy os.rename(currPath, "%s-%s" % (currPath, currVer)) undoStr += 'os.rename("%s-%s" %(currPath, currVer),currPath)\n' except Exception: if onWin32: msg = _translate("To upgrade you need to restart the app" " as admin (Right-click the app and " "'Run as admin')") else: msg = _translate("Could not move existing PsychoPy " "installation (permissions error?)") return msg else: # setuptools-style installation # generate new target path unzipTarget = currPath for thisVersionLabel in versionLabelsInPath: # remove final slash from the re.findall pathVersion = thisVersionLabel[:-1] unzipTarget = unzipTarget.replace(pathVersion, "PsychoPy-%s" % v) # find the .pth file that specifies the python dir # create the new installation directory BEFORE changing pth # file nUpdates, newInfo = self.updatePthFile(pathVersion, "PsychoPy-%s" % v) if nUpdates == -1: # there was an error (likely permissions) undoStr += 'self.updatePthFile(unzipTarget, currPath)\n' exec(undoStr) # undo previous changes return newInfo try: # create the new installation dir AFTER renaming existing dir os.makedirs(unzipTarget) undoStr += 'os.remove(%s)\n' % unzipTarget except Exception: # revert path rename and inform user exec(undoStr) # undo previous changes if onWin32: msg = _translate( "Right-click the app and 'Run as admin'):\n%s") else: msg = _translate("Failed to create directory for new version" " (permissions error?):\n%s") return msg % unzipTarget # do the actual extraction for name in zfile.namelist(): # for each file within the zip # check that this file is part of psychopy (not metadata or docs) if name.count('/psychopy/') < 1: continue try: targetFile = os.path.join(unzipTarget, name.split('/psychopy/')[1]) targetContainer = os.path.split(targetFile)[0] if not os.path.isdir(targetContainer): os.makedirs(targetContainer) # make the containing folder if targetFile.endswith('/'): os.makedirs(targetFile) # it's a folder else: outfile = open(targetFile, 'wb') outfile.write(zfile.read(name)) outfile.close() except Exception: exec(undoStr) # undo previous changes logging.error('failed to unzip file: ' + name) logging.error(sys.exc_info()[0]) info += _translate('Success. \nChanges to PsychoPy will be completed' ' when the application is next run') self.cancelBtn.SetDefault() self.installBtn.Disable() return info
def init(rate=44100, stereo=True, buffer=128): """setup the pyo (sound) server """ global pyoSndServer, Sound, audioDriver, duplex, maxChnls Sound = SoundPyo global pyo try: assert pyo except NameError: # pragma: no cover import pyo # can be needed for microphone.switchOn(), which calls init even # if audioLib is something else # subclass the pyo.Server so that we can insert a __del__ function that # shuts it down skip coverage since the class is never used if we have # a recent version of pyo class _Server(pyo.Server): # pragma: no cover # make libs class variables so they don't get deleted first core = core logging = logging def __del__(self): self.stop() # make sure enough time passes for the server to shutdown self.core.wait(0.5) self.shutdown() # make sure enough time passes for the server to shutdown self.core.wait(0.5) # this may never get printed self.logging.debug('pyo sound server shutdown') if '.'.join(map(str, pyo.getVersion())) < '0.6.4': Server = _Server else: Server = pyo.Server # if we already have a server, just re-initialize it if 'pyoSndServer' in globals() and hasattr(pyoSndServer, 'shutdown'): pyoSndServer.stop() # make sure enough time passes for the server to shutdown core.wait(0.5) pyoSndServer.shutdown() core.wait(0.5) pyoSndServer.reinit(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) pyoSndServer.boot() else: if platform == 'win32': # check for output device/driver devNames, devIDs = get_output_devices() audioDriver, outputID = _bestDriver(devNames, devIDs) if outputID is None: # using the default output because we didn't find the one(s) # requested audioDriver = 'Windows Default Output' outputID = pyo.pa_get_default_output() if outputID is not None: logging.info(u'Using sound driver: %s (ID=%i)' % (audioDriver, outputID)) maxOutputChnls = pyo.pa_get_output_max_channels(outputID) else: logging.warning( 'No audio outputs found (no speakers connected?') return -1 # check for valid input (mic) # If no input device is available, devNames and devIDs are empty # lists. devNames, devIDs = get_input_devices() audioInputName, inputID = _bestDriver(devNames, devIDs) # Input devices were found, but requested devices were not found if len(devIDs) > 0 and inputID is None: defaultID = pyo.pa_get_default_input() if defaultID is not None and defaultID != -1: # default input is found # use the default input because we didn't find the one(s) # requested audioInputName = 'Windows Default Input' inputID = defaultID else: # default input is not available inputID = None if inputID is not None: msg = u'Using sound-input driver: %s (ID=%i)' logging.info(msg % (audioInputName, inputID)) maxInputChnls = pyo.pa_get_input_max_channels(inputID) duplex = bool(maxInputChnls > 0) else: maxInputChnls = 0 duplex = False # for other platforms set duplex to True (if microphone is available) else: audioDriver = prefs.hardware['audioDriver'][0] maxInputChnls = pyo.pa_get_input_max_channels( pyo.pa_get_default_input()) maxOutputChnls = pyo.pa_get_output_max_channels( pyo.pa_get_default_output()) duplex = bool(maxInputChnls > 0) maxChnls = min(maxInputChnls, maxOutputChnls) if maxInputChnls < 1: # pragma: no cover msg = (u'%s.init could not find microphone hardware; ' u'recording not available') logging.warning(msg % __name__) maxChnls = maxOutputChnls if maxOutputChnls < 1: # pragma: no cover msg = (u'%s.init could not find speaker hardware; ' u'sound not available') logging.error(msg % __name__) return -1 # create the instance of the server: if platform == 'darwin' or platform.startswith('linux'): # for mac/linux we set the backend using the server audio param pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) else: # with others we just use portaudio and then set the OutputDevice # below pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer) pyoSndServer.setVerbosity(1) if platform == 'win32': pyoSndServer.setOutputDevice(outputID) if inputID is not None: pyoSndServer.setInputDevice(inputID) # do other config here as needed (setDuplex? setOutputDevice?) pyoSndServer.setDuplex(duplex) pyoSndServer.boot() core.wait(0.5) # wait for server to boot before starting the sound stream pyoSndServer.start() #atexit is filo, will call stop then shutdown upon closing atexit.register(pyoSndServer.shutdown) atexit.register(pyoSndServer.stop) try: Sound() # test creation, no play except pyo.PyoServerStateException: msg = "Failed to start pyo sound Server" if platform == 'darwin' and audioDriver != 'portaudio': msg += "; maybe try prefs.general.audioDriver 'portaudio'?" logging.error(msg) core.quit() logging.debug('pyo sound server started') logging.flush()