Exemple #1
1
    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
Exemple #2
0
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
Exemple #3
0
 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")
Exemple #4
0
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
Exemple #5
0
 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
Exemple #6
0
 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
Exemple #9
0
    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()
Exemple #10
0
    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
Exemple #11
0
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
Exemple #12
0
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))
Exemple #13
0
    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)
Exemple #14
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
Exemple #15
0
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
Exemple #16
0
    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))
Exemple #17
0
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))
Exemple #19
0
    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()
Exemple #20
0
    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
Exemple #21
0
    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))
Exemple #22
0
    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)
Exemple #23
0
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)
Exemple #25
0
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
Exemple #26
0
    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)
Exemple #27
0
 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()
Exemple #28
0
 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)'
Exemple #29
0
    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
Exemple #30
0
 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)
Exemple #31
0
 def _error(self, msg):
     self.OK = False
     logging.error(msg)
Exemple #32
0
        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 
Exemple #33
0
    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)
Exemple #34
0
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
Exemple #35
0
    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)
Exemple #36
0
            # 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')
Exemple #37
0
    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
Exemple #38
0
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([])
Exemple #39
0
    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
Exemple #40
0
    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))
Exemple #41
0
 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.")
Exemple #42
0
 def __init__(self, port, meterType="PR650", verbose=True):
     logging.error(self.__doc__)
     sys.exit()
Exemple #43
0
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__
Exemple #44
0
    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))
Exemple #45
0
    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)
Exemple #46
0
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")
Exemple #47
0
    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
Exemple #48
0
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
Exemple #49
0
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)
Exemple #51
0
    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")
Exemple #52
0
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
Exemple #53
0
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)
Exemple #54
0
 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.")
Exemple #55
0
        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')
Exemple #56
0
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
Exemple #57
0
    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))
Exemple #58
0
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
Exemple #59
0
    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
Exemple #60
0
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()