aforren1 / toon

TOOls for experiments in psychophysics and Neuroscience
MIT License
0 stars 0 forks source link

Input #1

Closed aforren1 closed 6 years ago

aforren1 commented 7 years ago

The dream (for me, at least) would be to have a single common API for input devices.

import abc
from time import time

"""
Base class
"""

class BaseInput(object):
    """Input should inherit from this"""
    __metaclass__ = abc.ABCMeta

    @abc_abstractmethod
    def __init__(self, clock_source=None):
        self._buffer = list()
        self.clock = time if not clock_source else clock_source

    @abc.abstractmethod
    def start(self):
        """Start reading from device"""
        pass

    @abc.abstractmethod
    def stop(self):
        """Stop device"""
        pass

    @abc.abstractmethod
    def close(self):
        """Close the device"""
        pass

    @abc.abstractmethod
    def read(self):
        """
        Read from device (which lives on separate thread/process or is nonblocking)
        This ends up calling _world_to_raw (which does the actual read) and then
        _raw_to_exp (which munges the data into a usable format).
        Reading clears the temporary buffer.
        """
        pass

    @abc.abstractmethod
    def write(self, values):
        """Write to device (very device-specific -- bytes, strings, ...)"""
        pass

    @abc.abstractmethod
    def clear(self):
        """Clear any buffered input"""
        pass

    @abc.abstractmethod
    def _world_to_raw(self):
        """Take input from the world (only needs to be defined once per device)"""
        pass

    @abc.abstractmethod
    def _raw_to_exp(self, values):
        """Convert raw to experiment-specific (implement once per experiment)"""
        pass

Then inherit:

from baseinput import BaseInput

class Keyboard(BaseInput):
    ...

We could probably even do a factory pattern for device instantiation, e.g.:

device = DeviceFactory.create('keyboard')

so that the particular device can be chosen at runtime (or from a config file, etc.).

aforren1 commented 7 years ago

Another (orthogonal) issue is how to handle polling of high-frequency input devices. I have a few examples floating around using the multiprocessing module, but there's also ioHub, which covers the whole polling-on-another-process angle.

Writing our own pros:

  1. Understand exactly how it works

cons:

  1. More time/effort

ioHub pros:

  1. Already exists

cons:

  1. Non-trivial to add new devices? Or we need to work at least one example (e.g. kinereach)
  2. Heavy dependencies (though that doesn't matter tremendously)
aforren1 commented 7 years ago

Note re: keyboards & mice: there's https://github.com/boppreh/keyboard which provides hooks to keyboard & mice events on Windows, Mac, and linux. I was thinking of using the ioHub mice/keyboards initially, but the pyHook/pywin32 dependency is annoying (no pypi package for pyHook).

Using keyboard also makes debugging easier, as mouse/keyboard can exist without a pyglet window.

aforren1 commented 7 years ago

See https://stackoverflow.com/questions/7894791/use-numpy-array-in-shared-memory-for-multiprocessing for shared arrays.

aforren1 commented 7 years ago

Just another note re: polling - toon/input/hand.py implements polling on a separate process (and toon/examples/scratch_mp.py is an earlier, but I think simpler, example of the same concept).

aforren1 commented 7 years ago

As of 0.1.0, there's a generic input/base_input.py that takes care of the multiprocessing bits. Both the HAND and Flock of Birds have been re-implemented using this.