Package: toon
toonDescriptionAdditional 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.InstallCurrent release:pip install toonDevelopment version:pip install -i https://test.pypi.org/simple/ toon --preOr for the latest commit (requires compilation):pip install git+https://github.com/aforren1/toonSee thedemos/folder for usage examples (note: some require additional packages).OverviewInputtoonprovides 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 (seetoon/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-inmultiprocessingmodule to control a separate process that hosts the device, and, in concert withnumpy, to move data to the main process via shared memory. It seems that under typical conditions, we can expect singleread()operations to take less than 500 microseconds (and more often < 100 us). Seedemos/bench_plot.pyfor an example of measuring user-side read performance.Typical use looks like this:fromtoon.inputimportMpDevicefrommymouseimportMousefromtimeitimportdefault_timerdevice=MpDevice(Mouse())withdevice:t1=default_timer()+10whiledefault_timer()<t1:res=device.read()# alternatively, unpack immediately# time, data = device.read()ifres: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 timesprint(time)Creating a custom device is relatively straightforward, though there are a few boxes to check.fromctypesimportc_doubleclassMyDevice(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.Structuresctype=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 communicatingdefenter(self):pass# optional: clean up resources, close devicedefexit(self):pass# requireddefread(self):# See demos/ for examples of sharing a time source between the processestime=self.clock()# store new data with a timestampdata=get_data()returntime,dataThis device can then be passed to atoon.input.MpDevice, which preallocates the shared memory and handles other details.A few things to be aware of for data returned byMpDevice:If there's no data for a givenread,Noneis returned.The returned data is acopyof the local copy of the data. If you don't need copies, setuse_views=Truewhen instantiating theMpDevice.If receiving batches of data when reading from the device, you can return a list of (time, data) tuples.You can optionally usedevice.start()/device.stop()instead of a context manager.You can check for remote errors at any point usingdevice.check_error(), though this automatically happens after entering the context manager and when reading.In addition to python types/dtypes/ctypes, devices can returnctypes.Structures (see input tests or theexample_devicesfolder for examples).AnimationThis is still a work in progress, though I think it has some utility as-is. It's a port of the animation component in theMagnumframework, though lacking some of the features (e.g. Track extrapolation, proper handling of time scaling).Example:frommathimportsin,pifromtimeimportsleepfromtimeitimportdefault_timerimportmatplotlib.pyplotaspltfromtoon.animimportTrack,Player# see toon/anim/easing.py for all available easingsfromtoon.anim.easingimportLINEAR,ELASTIC_INclassCircle(object):x=0y=0circle=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 keyframesy_track=Track(keyframes,easing=ELASTIC_IN)player=Player(repeats=3)# directly modify an attributeplayer.add(x_track,'x',obj=circle)defy_cb(val,obj):obj.y=val# modify via callbackplayer.add(y_track,y_cb,obj=circle)t0=default_timer()player.start(t0)vals=[]times=[]whileplayer.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).Multiple objects can be modified simultaneously by feeding a list of objects intoplayer.add().UtilitiesTheutilmodule includes high-resolution clocks/timers viaQueryPerformanceCounter/Frequencyon Windows,mach_absolute_timeon MacOS, andclock_gettime(CLOCK_MONOTONIC)on Linux. The class is calledMonoClock, and an instantiation calledmono_clockis created upon import. Usage:fromtoon.utilimportmono_clock,MonoClockclk=mono_clock# re-use pre-instantiated clockclk2=MonoClock(relative=False)# time relative to whenever the system's clock startedt0=clk.get_time()Another utility currently included is apriorityfunction, which tries to improve the determinism of the calling script. This is derived from Psychtoolbox'sPriority(dochere). General usage is:fromtoon.utilimportpriorityifnotpriority(1):raiseRuntimeError('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 returnFalse. The exact implementational details depend on the host operating system. All implementations disable garbage collection.WindowsUsesSetPriorityClassandSetThreadPriority/AvSetMmMaxThreadCharacteristics.level = 2only seems to work if running Python as administrator.MacOSOnly disables/enables garbage collection; I don't have a Mac to test on.LinuxSets the scheduler policy and parameterssched_setscheduler.Iflevel == 2, locks the calling process's virtual address space into RAM viamlockall.Anylevel > 0seems 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's99-psychtoolboxlimits.confand added myself to the psychtoolbox group.Your mileage may vary on whether theseactuallyimprove latency/determinism. When in doubt, measure! Read the warningshere.Notes about checking whether parts are working:WindowsIn 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.LinuxCheckmlockallwithcat /proc/{python pid}/status | grep VmLckCheck priority withtop -c -p $(pgrep -d',' -f python)