{ "info": { "author": "Brad Buran", "author_email": "bburan@alum.mit.edu", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering", "Topic :: System :: Hardware" ], "description": "\u03c8experiment\n======================\n\nPsiexperiment is a plugin-based experiment controller that facilitates the\nprocess of writing experiments. There are four core plugins: context,\nexperiment, controller and data.\n\nTerminology\n-----------\n\n### Context\n\nEvery experiment has a set of variables that define the behavior. These\nvariables range from the stimulus frequency and level to the intertrial\ninterval duration. Sometimes these variables need to be expressed as functions\nof other variables, or the value of the variable needs to vary in a random\nfashion.\n\nA *context item* provides information about a value that is managed by the\ncontext plugin. When defining a context item in one of your plugin manifests,\nyou will provide basic information about the item (e.g., a GUI label, compact\nGUI label and numpy dtype). This information will be used by plugins that\ninteract with the context plugin (for example, the name and dtype of the\ncontext item will be used by the HDF5Store plugin to set up the table that\nstores acquired trial data).\n\nThere are currently three specific types of context items. A *result* is a\nvalue provided by a plugin. It cannot be defined by the user. Common use cases\nmay include the values computed after a trial is acquired (e.g., one can\ncompute the `reaction_time` and provide it as a result).\n\nA *parameter* is a value that can be configured by the user before and during\nan experiment. While the value of the parameter can be modified by the user\nduring the experiment, it cannot be roved. There are some parameters that do\nnot make sense as roving parameters. For example, if we define a\n`go_probability` parameter that determines the probability that the next trial\nis a GO trial instead of a NOGO, it does not make sense to rove this value from\ntrial-to-trial. It, however, may make sense to change this during the couse of\nan experiemnt (e.g., during training).\n\nA *roving parameter* is like a parameter, except that it can be roved from\ntrial to trial. When selected for roving, the next value of the parameter is\nprovided by a selector.\n\nA *selector* maintains a sequence of expressions for one or more roving\nparameters. In some experiments, you'll only have a single selector. In other\nexperiments, you may want multiple selectors (e.g., one for go trials, one for\nremind trials and one for nogo trials). Right now, the only difference between\ndifferent types of selectors will be the GUI that's presented to the user for\nconfiguring the sequence of values. Internally, all of them maintain a list of\nvalues that should be presented on successive trials.\n\n### Controller\n\nThe controller is the central plugin of the experiment system. It's responsible\nfor managing a series of *engines* and *experiment actions*. \n\n#### Engine\n\nAn *engine* communicates with a data acquisition device (e.g., a NI-DAQmx\ncard). Each engine manages a set of analog and digital *channels*. The channel\ndeclaration contains sufficient information (e.g., expected range of values,\nsampling frequency, start trigger for acquisition, etc.) for the engine to\nconfigure the channel. Each channel has a set of *inputs* or *outputs*\nassociated with it (depending on whether it's an input or output channel). The\ninputs and outputs act as the primary interface to the hardware for various\npsiexpermient plugins. \n\nTo illustrate how an engine might be configured in an Enaml manifest::\n\n NIDAQEngine:\n AOChannel:\n channel = 'Dev1/ao0'\n fs = 200e3\n ContinuousOutput:\n name = 'background'\n EpochOutput:\n name = 'target'\n AIChannel:\n channel = 'Dev1/ai0'\n fs = 200e3\n start_trigger = 'ao/StartTrigger'\n ContinuousInput:\n name = 'microphone'\n AIChannel:\n channel = 'Dev1/ai1'\n fs = 200e3\n start_trigger = 'ao/StartTrigger'\n\n IIRFilter:\n f_lowpass = 200\n btype = 'lowpass'\n ftype = 'butter'\n N = 4\n Downsample:\n # effective Fs is 500\n name = 'nose_poke_analog'\n q = 400\n Threshold:\n threshold = 2.5\n Edges:\n debounce = 5\n name = 'nose_poke'\n\n DOChannel:\n channel = 'Dev1/port0/line0'\n fs = 0\n Trigger:\n name = 'food_dispense_trigger'\n duration = 0.1 \n\nAn input channel can have a signal processing chain associated with\nit that generates multiple inputs. For example, 'Dev/ai1' passes the input\nthrough a low-pass filter, downsamples it from 200 kHz to 0.5 kHz, then\nthresholds the filtered, downsampled signal to detect rising and falling edges\nin the analog signal. If the input has a name, then it will automatically be\nsaved in the HDF5 file. So, the low-pass filtered signal at 200 kHz is *not*\nsaved because no name is associated with that stage in the processing chain.\nHowever, the downsampled signal is saved as an array, `nose_poke_analog`.\nLikewise, the edge detection stage generates nose-poke events that are\ntimestamped and stored in the event log.\n\nTo illustrate the flexibility of this signal chain, one could consider the\nfollowing input (presumably recording from a 16-channel electrode array). This\nresults in three signals (raw signal, low-pass filtered LFP and high-pass\nfiltered spike train)::\n\n AIChannel:\n channel = 'Dev2/ai0:16'\n fs = 25e3\n start_trigger = 'Dev2/PFI0'\n\n IIRFilter:\n f_lowpass = 200\n btype = 'lowpass'\n ftype = 'butter'\n N = 4\n name = 'lfp'\n\n IIRFilter:\n f_highpass = 300\n f_lowpass = 3000\n btype = 'bandpass'\n ftype = 'butter'\n N = 4\n name = 'spikes'\n\n Input:\n name = 'raw_signal'\n\nCurrently, an output channel may have multiple outputs defined (right now we\nonly support one ContinuousOutput and one EpochOutput per channel, but this may\nbe expanded in the future). This is not as well fleshed-out but is meant to\nallow for the blending of multiple tokens into a single waveform that is sent\nto the channel.\n\n*To think about*\n\n> Right now the Engine and Channel declarations are system-specific. One computer may have a different equipment configuration. The reason this works is because the various plugins in the psiexperiment ecosystem will look for named inputs and outputs. For example, to successfully use the food dispenser plugin, an output named `food_dispense_trigger` must exist. How the trigger is generated will be left up to the hardware-specific Engine implementation (the pump dispense trigger is driven by commands via the serial port).\n\n> It seems that the inputs will need to be tied to the specific hardware system. This is particularly true for the inputs (e.g., some systems may have external hardware that converts the nose-poke signal to a binary value that is read in on a digital input channel rather than requiring that the thresholding be done in software). \n\n> In contrast, the outputs could theoretically be defined outside the hardware configuration. In this case, we would give the channels names (e.g., 'Dev1/ai0' would be named `speaker`). The output would then specify the channel name it is designed for::\n\n # Hypothetical restructuring of code to separate output and hardware configuration\n Extension:\n point = 'psi.controller.hardware'\n NIDAQEngine:\n AOChannel:\n channel = 'Dev1/ao0'\n fs = 200e3\n name = 'speaker1'\n DOChannel:\n channel = 'Dev1/port0/line0'\n fs = 0\n SerialPumpEngine:\n connection_url = 'COM3'\n Channel:\n # A fake channel, used to link the water dispense trigger. Maybe call it something else?\n name = 'water_dispense'\n\n Extension:\n point = 'psi.controller.io'\n ContinuousOutput:\n name = 'background'\n channel = 'speaker1'\n EpochOutput:\n name = 'target'\n channel = 'speaker1'\n Trigger:\n name = 'food_dispense_trigger'\n channel = 'food_dispense'\n Trigger:\n name = 'water_dispense_trigger'\n channel = 'water_dispense'\n\n\n#### ExperimentAction\n\nAn *experiment action* is a command (configured in an Enaml plugin manifest)\nthat is invoked when a particular event occurs. The controller defines the\navailable events (e.g., `experiment_start`, `trial_start`, `reward`). As each\nevent occurs, all actions associated with that event will be triggered.\n\nTo illustrate how an action might be configured in an Enaml manifest::\n\n def dispense_pellet(event):\n controller = event.workbench.get_plugin('psi.controller')\n output = controller.get_output('food_dispense_trigger')\n output.fire()\n\n\n enamldef PelletDispenserManifest(PluginManifest):\n\n id = 'pellet_dispenser'\n\n Extension:\n id = 'commands'\n point = 'enaml.workbench.core.commands'\n Command:\n id = 'dispense_pellet'\n handler = dispense_pellet\n\n Extension:\n id = 'action'\n point = 'psi.controller.actions'\n ExperimentAction:\n event = 'deliver_reward'\n command = 'dispense_pellet'\n\n\nNote how the `dispense_pellet` function will obtain the `food_dispense_trigger`\noutput from the controller. This is an example of how the plugin can remain\n\"agnostic\" with respect to the actual hardware configuration.\n\n*To think about*\n\n> Right now I define the commands (i.e., the actual implementation) along with the association between an event and the corresponding command (i.e, the experiment action) in the same plugin. However, we should be able to break this into three parts: 1) A hardware interface (possibly defined as a subclass of Engine) that knows how to talk to a very specific piece of equipment, 2) a core set of commands that one would want to use regardless of the interface and 3) a behavioral paradigm-specific mapping of events to commands.\n\n> To illustrate this, let's consider room light as an example. There are various ways we can have a room light be controlled via hardware. A room light is very simple. You either toggle it on or off. So, you would define an output:\n\n Toggle:\n name = 'room_light_toggle'\n channel = 'room_light'\n\n> This output exists regardless of hardware implementation or what type of actions we would want to take regarding the room light. The toggle has two commands, `set_high` (i.e., turn on the light) and `set_low` (turn off the light). \n\n> Now, one could envision that a NI-DAQ card controls the room light via a relay using one of it's digital outputs. That's simple to configure the hardware definition:\n\n NIDAQEngine:\n DOChannel:\n channel = 'Dev1/port0/line0'\n name = 'room_light'\n fs = 0\n\n> What if the computer somehow controls the room light via a serial port? \n\n RoomLightSerialPortEngine:\n SerialChannel:\n name = 'room_light'\n\n> The `DOChannel` or `SerialChannel` will know how to handle (in coordination with their parent engine) the `set_low` and `set_high` commands received from the toggle. We've achieved a little abstraction here. The final layer of abstraction is the mapping of events to commands. First, we would define the commands in a plugin manifest that could be loaded (the Toggle output can also be defined in this plugin manifest since it's intrinsically tied to the commands):\n\n def light_on(event):\n controller = event.workbench.get_plugin('psi.controller')\n output = controller.get_output('room_light_toggle')\n output.set_high()\n\n def light_off(event):\n controller = event.workbench.get_plugin('psi.controller')\n output = controller.get_output('room_light_toggle')\n output.set_low()\n\n Extension:\n point = 'enaml.core.workbench.commands'\n Command:\n id = 'psi.controller.room_light.turn_on'\n handler = light_on\n Command:\n id = 'psi.controller.room_light.turn_off'\n handler = light_off\n\n Extension:\n point = 'psi.controller.io'\n Toggle:\n name = 'room_light_toggle'\n channel = 'room_light'\n\n> The hardware configuration would be defined in a separate file that is specific to the system psiexperiment is running on.\n\n> Finally, we would have a paradigm-specific mapping of actions to commands. For an appetitive experiment (i.e., reward on correct responses), we would turn off the light when the animal receives a timeout. We can define this in a separate file:\n\n Extension:\n ponit = 'psi.controller.actions'\n ExperimentAction:\n event = 'timeout_start'\n command = 'psi.controller.room_light.turn_off'\n ExperimentAction:\n event = 'timeout_end'\n command = 'psi.controller.room_light.turn_on'\n\n\nPlugins\n-------\n\n### Context plugin\n\nManages the context items and selectors (see terminology above). Every\nexperiment has a set of values, or parameters, that define the course of the\nexperiment. This plugin provides a central registry of all context items,\nmanages their current values (i.e., when notified, the current values will be\ncleared and re-computed for the next trial). A subset of these context items\ncan be specified as roving parameters (i.e., their value varies from trial to\ntrial in a predefined way). Values for roving parameters are managed by one or\nmore selectors. Selectors are objects that provide a mechanism for specifying\nthe sequence of values that the context plugin draws from on each trial. \n\n### Experiment plugin\n\nProvides basic management of the experiment workspace by managing GUI elements\nprovided by the loaded plugins (e.g., the context plugin provides several GUI\nwidgets) as well as providing methods to save/restore the layout of the\nworkspace as well as save/restore the preferences for each plugin.\n\n### Data plugin\n\nProvides basic management and analysis of data acquired during an experiment.\n\n### Controller plugin\n\nTODO\n\n\nRoadmap\n-------\n\nTODO\n\nMigrating from Neurobehavior \n----------------------------\n\nThis is a significant rewrite of Neurobehavior that leverages the strengths of\nthe Atom/Enaml framework. The key change is that Psiexperiment is plugin-based.\nWriting experiments in Neurobehavior involved subclassing several classes\n(e.g., paradigm, experiment, controller, data) and incorporating various mixins\n(e.g., for the pump controller and pump data). Often this required some\ncumbersome hacks to get the GUI and experiment to behave the way you want.\nThere were also some significant limitations with the context management system\n(i.e., adding new parameters required subclassing the paradigm class and\npossibly incorporating mixins for pumps, etc.). \n\nIn contrast, setting up new experiments in psiexperiment should be simpler. You\ndecide the types of plugins you want loaded and write a manifest file that\ndefines the specific extensions you want. If you have a specific type of\nanalysis that you need done on the acquired data, you can write a new plugin\nand load it.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/bburan/psiexperiment", "keywords": "experiment behavior neuroscience psychoacoustics", "license": "BSD", "maintainer": "", "maintainer_email": "", "name": "PsiExperiment", "package_url": "https://pypi.org/project/PsiExperiment/", "platform": "", "project_url": "https://pypi.org/project/PsiExperiment/", "project_urls": { "Homepage": "http://github.com/bburan/psiexperiment", "bug reports": "https://github.com/bburan/psiexperiment/issues" }, "release_url": "https://pypi.org/project/PsiExperiment/0.1.2/", "requires_dist": [ "atom", "bcolz", "enaml", "numpy", "scipy", "joblib", "palettable", "pandas", "pyqtgraph", "pyyaml", "pydaqmx; extra == 'nidaqmx'", "pytest; extra == 'test'" ], "requires_python": "", "summary": "Module for running trial-based experiments.", "version": "0.1.2" }, "last_serial": 4125613, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "b6fad288956814839878fdf4331750be", "sha256": "cb7b3dd85addc91301738fadb05ad5e1ca1e2cf7622129db78d10c81f8d6b525" }, "downloads": -1, "filename": "PsiExperiment-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "b6fad288956814839878fdf4331750be", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 121818, "upload_time": "2018-08-01T17:08:42", "url": "https://files.pythonhosted.org/packages/8b/41/bd0b5ae0c716937f7f5bbc8d6a18a8566b8f6ec2b1121b7db4e90dc92d83/PsiExperiment-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6d34d620a9b245c55a3afbc6b8a5d25d", "sha256": "71291bc3dac3c7455bb072fbb2c5bf8f862bb9a992f3d2f8a04066bd5085f42f" }, "downloads": -1, "filename": "PsiExperiment-0.1.1.tar.gz", "has_sig": false, "md5_digest": "6d34d620a9b245c55a3afbc6b8a5d25d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 97366, "upload_time": "2018-08-01T17:10:06", "url": "https://files.pythonhosted.org/packages/04/dd/0c21e005429427d67818d9a40de7869f14e7048d4227dc8ea3689d55fa3c/PsiExperiment-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "7fcc407316e732c0f10e25bbb0387e5d", "sha256": "541ea044355addbb0b20d49da852ebdfc79e402ac96264f1e68cc7aa4279e087" }, "downloads": -1, "filename": "PsiExperiment-0.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7fcc407316e732c0f10e25bbb0387e5d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 189690, "upload_time": "2018-08-01T17:39:31", "url": "https://files.pythonhosted.org/packages/86/68/fd1fb4a785645269e172fa07e2b673e9b7958a8800ca329cd714977b5d70/PsiExperiment-0.1.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c0e18dabc5b1313cb983393532e6f5c5", "sha256": "f0ce14f644463b93fd5ac7292744fb46f308b0ec495d5d87856421df9b7cab10" }, "downloads": -1, "filename": "PsiExperiment-0.1.2.tar.gz", "has_sig": false, "md5_digest": "c0e18dabc5b1313cb983393532e6f5c5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 140673, "upload_time": "2018-08-01T17:39:33", "url": "https://files.pythonhosted.org/packages/64/32/faefa8b19dd08246d10904171c4706c0b79947e7a3f6f5137d670d48edf2/PsiExperiment-0.1.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7fcc407316e732c0f10e25bbb0387e5d", "sha256": "541ea044355addbb0b20d49da852ebdfc79e402ac96264f1e68cc7aa4279e087" }, "downloads": -1, "filename": "PsiExperiment-0.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7fcc407316e732c0f10e25bbb0387e5d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 189690, "upload_time": "2018-08-01T17:39:31", "url": "https://files.pythonhosted.org/packages/86/68/fd1fb4a785645269e172fa07e2b673e9b7958a8800ca329cd714977b5d70/PsiExperiment-0.1.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c0e18dabc5b1313cb983393532e6f5c5", "sha256": "f0ce14f644463b93fd5ac7292744fb46f308b0ec495d5d87856421df9b7cab10" }, "downloads": -1, "filename": "PsiExperiment-0.1.2.tar.gz", "has_sig": false, "md5_digest": "c0e18dabc5b1313cb983393532e6f5c5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 140673, "upload_time": "2018-08-01T17:39:33", "url": "https://files.pythonhosted.org/packages/64/32/faefa8b19dd08246d10904171c4706c0b79947e7a3f6f5137d670d48edf2/PsiExperiment-0.1.2.tar.gz" } ] }