Source code for psychopy.hardware.forp

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Part of the PsychoPy library
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2021 Open Science Tools Ltd.
# Distributed under the terms of the GNU General Public License (GPL).

"""fORP fibre optic (MR-compatible) response devices by CurrentDesigns:
http://www.curdes.com/
This class is only useful when the fORP is connected via the serial port.

If you're connecting via USB, just treat it like a standard keyboard.
E.g., use a Keyboard component, and typically listen for Allowed keys
``'1', '2', '3', '4', '5'``. Or use ``event.getKeys()``.
"""

# Jeremy Gray and Dan Grupe developed the asKeys and baud parameters

from __future__ import absolute_import, print_function

from builtins import object
from psychopy import logging, event
import sys
from collections import defaultdict

try:
    import serial
except ImportError:
    serial = False

BUTTON_BLUE = 1
BUTTON_YELLOW = 2
BUTTON_GREEN = 3
BUTTON_RED = 4
BUTTON_TRIGGER = 5
# Maps bit patterns to character codes
BUTTON_MAP = [
    (0x01, BUTTON_BLUE),
    (0x02, BUTTON_YELLOW),
    (0x04, BUTTON_GREEN),
    (0x08, BUTTON_RED),
    (0x10, BUTTON_TRIGGER)]


[docs]class ButtonBox(object): """Serial line interface to the fORP MRI response box. To use this object class, select the box use setting `serialPort`, and connect the serial line. To emulate key presses with a serial connection, use `getEvents(asKeys=True)` (e.g., to be able to use a RatingScale object during scanning). Alternatively connect the USB cable and use fORP to emulate a keyboard. fORP sends characters at 800Hz, so you should check the buffer frequently. Also note that the trigger event numpy the fORP is typically extremely short (occurs for a single 800Hz epoch). """ def __init__(self, serialPort=1, baudrate=19200): """ :Parameters: `serialPort` : should be a number (where 1=COM1, ...) `baud` : the communication rate (baud), eg, 57600 """ super(ButtonBox, self).__init__() if not serial: raise ImportError("The module serial is needed to connect to " "fORP. On most systems this can be installed " "with\n\t easy_install pyserial") self.port = serial.Serial(serialPort - 1, baudrate=baudrate, bytesize=8, parity='N', stopbits=1, timeout=0.001) if not self.port.isOpen(): self.port.open() self.buttonStatus = defaultdict(bool) # Defaults to False self.rawEvts = [] self.pressEvents = []
[docs] def clearBuffer(self): """Empty the input buffer of all characters""" self.port.flushInput()
[docs] def clearStatus(self): """ Resets the pressed statuses, so getEvents will return pressed buttons, even if they were already pressed in the last call. """ for k in self.buttonStatus: self.buttonStatus[k] = False
[docs] def getEvents(self, returnRaw=False, asKeys=False, allowRepeats=False): """Returns a list of unique events (one event per button pressed) and also stores a copy of the full list of events since last getEvents() (stored as ForpBox.rawEvts) `returnRaw` : return (not just store) the full event list `asKeys` : If True, will also emulate pyglet keyboard events, so that button 1 will register as a keyboard event with value "1", and as such will be detectable using `event.getKeys()` `allowRepeats` : If True, this will return pressed buttons even if they were held down between calls to getEvents(). If the fORP is on the "Eprime" setting, you will get a stream of button presses while a button is held down. On the "Bitwise" setting, you will get a set of all currently pressed buttons every time a button is pressed or released. This option might be useful if you think your participant may be holding the button down before you start checking for presses. """ nToGet = self.port.inWaiting() evtStr = self.port.read(nToGet) self.rawEvts = [] self.pressEvents = [] if allowRepeats: self.clearStatus() # for each character convert to an ordinal int value (numpy the ascii # chr) for thisChr in evtStr: pressCode = ord(thisChr) self.rawEvts.append(pressCode) decodedEvents = self._generateEvents(pressCode) self.pressEvents += decodedEvents if asKeys: for code in decodedEvents: event._onPygletKey(symbol=code, modifiers=0) # better as: emulated='fORP_bbox_asKey', but need to # adjust event._onPygletKey and the symbol conversion # pyglet.window.key.symbol_string(symbol).lower() # return the abbreviated list if necessary if returnRaw: return self.rawEvts else: return self.getUniqueEvents()
[docs] def _generateEvents(self, pressCode): """For a given button press, returns a list buttons that went from unpressed to pressed. Also flags any unpressed buttons as unpressed. `pressCode` : a number with a bit set for every button currently pressed. """ curStatuses = self.__class__._decodePress(pressCode) pressEvents = [] for button, pressed in curStatuses: if pressed and not self.buttonStatus[button]: # We're transitioning to pressed... pressEvents.append(button) self.buttonStatus[button] = True if not pressed: self.buttonStatus[button] = False return pressEvents
[docs] @classmethod def _decodePress(kls, pressCode): """Returns a list of buttons and whether they're pressed, given a character code. `pressCode` : A number with a bit set for every button currently pressed. Will be between 0 and 31. """ return [(mapping[1], bool(mapping[0] & pressCode)) for mapping in BUTTON_MAP]
[docs] def getUniqueEvents(self, fullEvts=False): """Returns a Python set of the unique (unordered) events of either a list given or the current rawEvts buffer """ if fullEvts: return set(self.rawEvts) return set(self.pressEvents)

Back to top