Tools for neuroscience experiments
Project description
toon
Description
Additional tools for neuroscience experiments, including:
- A framework for polling input devices on a separate process.
- A framework for keyframe-based animation.
- High-resolution clocks.
Everything should work on Windows/Mac/Linux.
Install
Current release:
pip install toon
Development version (requires compilation-- C++11 (and if using MSVC, >= 2015 for proper std::chrono
implementation)):
pip install git+https://github.com/aforren1/toon
See the demos/ folder for usage examples (note: some require additional packages).
Overview
Input
toon
provides a framework for polling from input devices, including common peripherals like mice and keyboards, with the flexibility to handle less-common devices like eyetrackers, motion trackers, and custom devices (see toon/input/
for examples). The goal is to make it easier to use a wide variety of devices, including those with sampling rates >1kHz, with minimal performance impact on the main process.
We use the built-in multiprocessing
module to control a separate process that hosts the device, and, in concert with numpy
, to move data to the main process via shared memory. It seems that under typical conditions, we can expect single read()
operations to take less than 500 microseconds (and more often < 100 us). See demos/bench_plot.py for an example of measuring user-side read performance.
Typical use looks like this:
from toon.input import MpDevice
from mydevice.mouse import Mouse
from timeit import default_timer
device = MpDevice(Mouse())
with device:
t1 = default_timer() + 10
while default_timer() < t1:
res = device.read()
# alternatively, unpack immediately
# time, data = device.read()
if res:
time, data = res # unpack (or access via res.time, res.data)
# N-D array of data (0th dim is time)
print(data)
# 1D array of times
print(time)
Creating a custom device is relatively straightforward, though there are a few boxes to check.
from ctypes import c_double
class MyDevice(BaseDevice):
# optional: give a hint for the buffer size (we'll allocate 1 sec worth of this)
sampling_frequency = 500
# this can either be introduced at the class level, or during __init__
shape = (3, 3)
# ctype can be a python type, numpy dtype, or ctype
# including ctypes.Structures
ctype = c_double
# optional. Do not start device communication here, wait until `enter`
def __init__(self):
pass
## Use `enter` and `exit`, rather than `__enter__` and `__exit__`
# optional: configure the device, start communicating
def enter(self):
pass
# optional: clean up resources, close device
def exit(self):
pass
# required
def read(self):
# See demos/ for examples of sharing a time source between the processes
time = self.clock()
# store new data with a timestamp
data = get_data()
return time, data
This device can then be passed to a toon.input.MpDevice
, which preallocates the shared memory and handles other details.
A few things to be aware of for data returned by MpDevice
:
- If there's no data for a given
read
,None
is returned. - The returned data is a copy of the local copy of the data. If you don't need copies, set
use_views=True
when instantiating theMpDevice
. - If receiving batches of data when reading from the device, you can return a list of (time, data) tuples.
- You can optionally use
device.start()
/device.stop()
instead of a context manager. - You can check for remote errors at any point using
device.check_error()
, though this automatically happens after entering the context manager and when reading. - In addition to python types/dtypes/ctypes, devices can return
ctypes.Structure
s (see input tests or the example_devices folder for examples).
Animation
This is still a work in progress, though I think it has some utility as-is. It's a port of the animation component in the Magnum framework, though lacking some of the features (e.g. Track extrapolation, proper handling of time scaling).
Example:
from math import sin, pi
from time import sleep
from timeit import default_timer
import matplotlib.pyplot as plt
from toon.anim import Track, Player
# see toon/anim/easing.py for all available easings
from toon.anim.easing import LINEAR, ELASTIC_IN
class Circle(object):
x = 0
y = 0
circle = Circle()
# list of (time, value)
keyframes = [(0.2, -0.5), (0.5, 0), (3, 0.5)]
x_track = Track(keyframes, easing=LINEAR)
# we can reuse keyframes
y_track = Track(keyframes, easing=ELASTIC_IN)
player = Player(repeats=3)
# directly modify an attribute
player.add(x_track, 'x', obj=circle)
def y_cb(val, obj):
obj.y = val
# modify via callback
player.add(y_track, y_cb, obj=circle)
t0 = default_timer()
player.start(t0)
vals = []
times = []
while player.is_playing:
t = default_timer()
player.advance(t)
times.append(t)
vals.append([circle.x, circle.y])
# sleep(1/60)
plt.plot(times, vals)
plt.show()
Other notes:
- Non-numeric attributes, like color strings, can also be modified in this framework (easing is ignored).
- The
Player
can also be used as a mixin, in which case theobj
argument can be omitted fromplayer.add()
(see the demos/ folder for examples). - Multiple objects can be modified simultaneously by feeding a list of objects into
player.add()
.
Utilities
The util
module includes high-resolution clocks/timers via std::chrono::steady_clock
. The class is called MonoClock
, and an instantiation called mono_clock
is created upon import. Usage:
from toon.util import mono_clock, MonoClock
clk = mono_clock # re-use pre-instantiated clock
clk2 = MonoClock(relative=False) # time relative to whenever the system's clock started
t0 = clk.get_time()
Another utility currently included is a priority
function, which tries to improve the determinism of the calling script. This is derived from Psychtoolbox's Priority
(doc here). General usage is:
from toon.util import priority
res = priority(1)
if not res:
raise ValueError('Failed to raise priority.')
# ...do stuff...
priority(0)
The input should be a 0 (no priority/cancel), 1 (higher priority), or 2 (realtime). If the requested level is rejected, the function will return False
. The exact implementational details depend on the host operating system. All implementations disable garbage collection.
Windows
- Uses
SetPriorityClass
andSetThreadPriority
/AvSetMmMaxThreadCharacteristics
. level = 2
only seems to work if running Python as administrator.
MacOS
- Only disables/enables garbage collection; I don't have a Mac to test on.
Linux
- Sets the scheduler policy and parameters
sched_setscheduler
. - If
level == 2
, locks the calling process's virtual address space into RAM viamlockall
. - Any
level > 0
seems to fail unless the user is either superuser, or has the right capability. I've used setcap:sudo setcap cap_sys_nice=eip <path to python>
(disable by passingsudo setcap cap_sys_nice= <path>
). For memory locking, I've used Psychtoolbox's 99-psychtoolboxlimits.conf and added myself to the psychtoolbox group.
Your mileage may vary on whether these actually improve latency/determinism. When in doubt, measure! Read the warnings here.
Notes about checking whether parts are working:
Windows
- In the task manager under details, right-clicking on python and mousing over "Set priority" will show the current priority level. I haven't figured out how to verify the Avrt threading parts are working.
Linux
- Check
mlockall
withcat /proc/{python pid}/status | grep VmLck
- Check priority with
top -c -p $(pgrep -d',' -f python)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for toon-0.15.0-cp38-cp38-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 99ab0a1c165a661469b948c38902725dcc23f3959fbdfbcc44675bdc17ba45b6 |
|
MD5 | 64aad99b8ac16d70fb0d4f342d157ae6 |
|
BLAKE2b-256 | 76c63c931701d98e3a0a106d7142f8ef35b44e5038a2059011bc8f42b2799e3e |
Hashes for toon-0.15.0-cp38-cp38-manylinux2010_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 40c251c5f7c47af3dabc4a0e5611acf7da8285e7ac7bd9ccb77bfb71c39582fb |
|
MD5 | f828c2fd8fdd2334106f4f785f91e398 |
|
BLAKE2b-256 | 6b2c954625734c3d9d483cd77a737ace7d14acf1b6df5c9cf47305b98028d04e |
Hashes for toon-0.15.0-cp38-cp38-manylinux1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | cdc5e25c3cfb067c3d1f2f8ba5e69eebc291e55b57d3b97bc0c8ea3ab5426379 |
|
MD5 | 360a4eba2bb0cc7e3389e86367c8315c |
|
BLAKE2b-256 | 558336f1d1d100688ac5b778d027aefb0c1f8278779e7f02e0ccf3b49c3b52dd |
Hashes for toon-0.15.0-cp38-cp38-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 96a0d2b6323e2d849cacbd4ce27efd35c94a2c8445bd024ebed6edbbfc32f317 |
|
MD5 | 502cfb9ebb65cbfd90d0d65a1510991e |
|
BLAKE2b-256 | 91785b9e58323adb6667a941f7e1f7eb43f2af002a1d2c0ffd3c424d80f0ce6c |
Hashes for toon-0.15.0-cp37-cp37m-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | be41933af20e895ba8c46e57129fc47f4b86127a1c346f3618a2e48851e9987d |
|
MD5 | 6763b78af455513e0c4aefa9ef28afd5 |
|
BLAKE2b-256 | 640c91feb7572f58755709ee5f26e13bbb48dbc724cfa275a0dfba35c597eaa2 |
Hashes for toon-0.15.0-cp37-cp37m-manylinux2010_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0f6d3114211a306b18122464a2391578b2540dd3c99f70889db8e510217f2675 |
|
MD5 | 657bf7bb575bdf3e33f28cb61364d64d |
|
BLAKE2b-256 | c0c7382848b13ed8ef6095b71d0057e5dcae6ec5b135f03019093a73b016ec1b |
Hashes for toon-0.15.0-cp37-cp37m-manylinux1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c1a47535b1c65733de8cc118010ae3cd15ad868de6ad29a60cdc2c356c84ce84 |
|
MD5 | dde998dcf84389633ef4f04e70695818 |
|
BLAKE2b-256 | d54ff5f9612954eedbe311c8a833c311e9bfb676ea56b5b72e6658b41f760354 |
Hashes for toon-0.15.0-cp37-cp37m-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 29e5f15f6e8da5b17cd1569864cdc1c6c196a21636553601bb66b70faa3f438b |
|
MD5 | 834660f130217e34f9cec3309f3742dd |
|
BLAKE2b-256 | 22a2ecce22b1a3da0e8be86921f47e166eaea5698e1ba95ef80f716ccfa90984 |
Hashes for toon-0.15.0-cp36-cp36m-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2e29854953005c27cfab53b5672564e168693c25d85c1f2b36ebc65788e6fc70 |
|
MD5 | 641be81c1b255f9ccf6dcdc4965ce6bf |
|
BLAKE2b-256 | 17af7bbbb3b5bf37e713e62c3764cc4d7f4f251bc20737c7f3d43f60ade656ff |
Hashes for toon-0.15.0-cp36-cp36m-manylinux2010_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c4d234965e9189bef9e52cf089bcfd67928e65accb47e6672769c436bdcd4c33 |
|
MD5 | f6c1c2ebf2c4b6c1575c64bc28abc3c3 |
|
BLAKE2b-256 | 70f59ab01731e407534d4afda38297f096d3c7f7e0704337582dae91119b2659 |
Hashes for toon-0.15.0-cp36-cp36m-manylinux1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 766f8d66a0137d83561f3b4e1d0864710cc3b43f934393d355374657d2557d53 |
|
MD5 | 15d907afdaac82d2f92c3c685191e3a5 |
|
BLAKE2b-256 | 5b14cbaa70b3eb5933b28cc662fc0b815e34344196e7a8592cadc1e017d2b6d9 |
Hashes for toon-0.15.0-cp36-cp36m-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 81842717305f4a9eb636edd6c392b5e919986434ff535b751b4e21f00f5480b9 |
|
MD5 | af465d2dfece1c2a16fa02c18b723d81 |
|
BLAKE2b-256 | 1bfa6d8a58fe53235ba0f9023d924e8d202febfad1b4ba6648c7ab675e3fbca4 |