-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a lot of documentation plus smooths over most all of the remaining rough edges. It should now be much easier to create new behaviors.
- Loading branch information
Showing
29 changed files
with
4,615 additions
and
486 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,3 +31,4 @@ MANIFEST | |
fabfile.py | ||
|
||
.DS_Store | ||
.idea* |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
#!/usr/bin/env python | ||
import os | ||
import sys | ||
import logging | ||
import csv | ||
import datetime as dt | ||
import random | ||
import numpy as np | ||
from pyoperant.behavior import base | ||
from pyoperant.errors import EndSession | ||
from pyoperant import states, trials, blocks | ||
from pyoperant import components, utils, reinf, queues, configure, stimuli, subjects | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class RewardedCondition(stimuli.StimulusConditionWav): | ||
""" Rewarded stimuli are rewarded if the subject does *not* respond (i.e. | ||
No-Go stimuli). | ||
""" | ||
def __init__(self, file_path="", recursive=False): | ||
super(RewardedCondition, self).__init__(name="Rewarded", | ||
response=False, | ||
is_rewarded=True, | ||
is_punished=False, | ||
file_path=file_path, | ||
recursive=recursive) | ||
|
||
|
||
class UnrewardedCondition(stimuli.StimulusConditionWav): | ||
""" Unrewarded stimuli are not consequated and should be pecked through | ||
(i.e. Go stimuli) | ||
""" | ||
def __init__(self, file_path="", recursive=False): | ||
|
||
super(UnrewardedCondition, self).__init__(name="Unrewarded", | ||
response=True, | ||
is_rewarded=False, | ||
is_punished=False, | ||
file_path=file_path, | ||
recursive=recursive) | ||
|
||
|
||
class GoNoGoInterrupt(base.BaseExp): | ||
"""A go no-go interruption experiment | ||
Additional Parameters | ||
--------------------- | ||
reward_value: int | ||
The value to pass as a reward (e.g. feed duration) | ||
For all other parameters, see pyoperant.behavior.base.BaseExp | ||
Required Panel Attributes | ||
------------------------- | ||
sleep - Puts the panel to sleep | ||
reset - Sets the panel back to a nice initial state | ||
ready - Prepares the panel to run the behavior (e.g. turn on the | ||
response_port light and put the feeder down) | ||
idle - Sets the panel into an idle state for when the experiment is not | ||
running | ||
reward - Method for supplying a reward to the subject. Should take a reward | ||
value as an argument | ||
response_port - The input through which the subject responds | ||
speaker - A speaker for sound output | ||
Fields To Save | ||
-------------- | ||
session - The index of the current session | ||
index - The index of the current trial | ||
time - The start time of the trial | ||
stimulus_name - The filename of the stimulus | ||
condition_name - The condition of the stimulus | ||
response - Whether or not there was a response | ||
correct - Whether the response was correct | ||
rt - If there was a response, the time from sound playback | ||
max_wait - The duration of the sound and thus maximum rt to be counted as a | ||
response. | ||
""" | ||
|
||
req_panel_attr = ["sleep", | ||
"reset", | ||
"ready", | ||
"idle", | ||
"reward", | ||
"response_port", | ||
"speaker"] | ||
|
||
fields_to_save = ['session', | ||
'index', | ||
'time', | ||
'stimulus_name', | ||
'condition_name', | ||
'response', | ||
'correct', | ||
'rt', | ||
'reward', | ||
'max_wait', | ||
] | ||
|
||
def __init__(self, reward_value=12, *args, **kwargs): | ||
|
||
super(GoNoGoInterrupt, self).__init__(*args, **kwargs) | ||
self.start_immediately = False | ||
self.reward_value = reward_value | ||
|
||
def trial_pre(self): | ||
""" Initialize the trial and, if necessary, wait for a peck before | ||
starting stimulus playback. | ||
""" | ||
|
||
logger.debug("Starting trial #%d" % self.this_trial.index) | ||
stimulus = self.this_trial.stimulus | ||
condition = self.this_trial.condition.name | ||
self.this_trial.annotate(stimulus_name=stimulus.file_origin, | ||
condition_name=condition, | ||
max_wait=stimulus.duration) | ||
|
||
if not self.start_immediately: | ||
logger.debug("Begin polling for a response") | ||
self.panel.response_port.poll() | ||
|
||
def stimulus_main(self): | ||
""" Queue the stimulus and play it back """ | ||
|
||
logger.info("Trial %d - %s - %s - %s" % ( | ||
self.this_trial.index, | ||
self.this_trial.time.strftime("%H:%M:%S"), | ||
self.this_trial.condition.name, | ||
self.this_trial.stimulus.name)) | ||
self.panel.speaker.queue(self.this_trial.stimulus.file_origin) | ||
self.this_trial.annotate(stimulus_time=dt.datetime.now()) | ||
self.panel.speaker.play() | ||
|
||
def response_main(self): | ||
""" Poll for an interruption for the duration of the stimulus. """ | ||
|
||
self.this_trial.response_time = self.panel.response_port.poll(self.this_trial.stimulus.duration) | ||
logger.debug("Received peck or timeout. Stopping playback") | ||
|
||
self.panel.speaker.stop() | ||
logger.debug("Playback stopped") | ||
|
||
if self.this_trial.response_time is None: | ||
logger.debug("No peck was received") | ||
self.this_trial.response = False | ||
self.start_immediately = False # Next trial will poll for a response before beginning | ||
self.this_trial.rt = np.nan | ||
else: | ||
logger.debug("Peck was received") | ||
self.this_trial.response = True | ||
self.start_immediately = True # Next trial will begin immediately | ||
self.this_trial.rt = self.this_trial.response_time - \ | ||
self.this_trial.annotations["stimulus_time"] | ||
|
||
def reward_main(self): | ||
""" Reward a correct non-interruption """ | ||
|
||
value = self.parameters.get('reward_value', 12) | ||
logger.info("Supplying reward for %3.2f seconds" % value) | ||
reward_event = self.panel.reward(value=value) | ||
if isinstance(reward_event, dt.datetime): # There was a response during the reward period | ||
self.start_immediately = True | ||
|
||
|
||
if __name__ == "__main__": | ||
|
||
# Load config file | ||
config_file = "/path/to/config" | ||
if config_file.lower().endswith(".json"): | ||
parameters = configure.ConfigureJSON.load(config_file) | ||
elif config_file.lower().endswith(".yaml"): | ||
parameters = configure.ConfigureYAML.load(config_file) | ||
|
||
# Create experiment object | ||
exp = GoNoGoInterrupt(**parameters) | ||
exp.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
--- | ||
# The main format is as follows: | ||
# Each section contains information that will help to configure your | ||
# experiment. Where possible, values that are not supplied will be filled in | ||
# with default values supplied by the given experiment. The author of a | ||
# particular behavior script should also write a config file that will serve as | ||
# a template and possibly be used to provide the default values. If a section | ||
# requires specification of a python object, the formatting should go something | ||
# like this: | ||
# object_name: !!python/object/apply:module.submodule.Class | ||
# args: [list of arguments] | ||
# kwds: | ||
# param1: value1 | ||
# param2: value2 | ||
# ... | ||
# paramn: valuen | ||
# You can refer to a previously defined object using "&some_name" before | ||
# defining the object and "*some_name" when referencing the object | ||
|
||
# Experiment description | ||
name: Go No-Go Interruption | ||
description: > | ||
Runs a Go No-Go Interruption experiment | ||
experimenter: | ||
name: First Last | ||
email: &def_email "[email protected]" | ||
|
||
# File paths | ||
experiment_path: "/path/to/data/directory" | ||
|
||
# Verbose logging | ||
debug: false | ||
|
||
# Number of sessions to run | ||
num_sessions: 1 | ||
|
||
# Behavior properties | ||
reward_value: 12.0 | ||
|
||
# Subject | ||
subject_name: TestSubject | ||
|
||
# Panel properties | ||
panel: !!python/object/apply:pyoperant.panels.BasePanel {} | ||
|
||
# Stimulus conditions | ||
conditions: | ||
- &NoGo !!python/object/apply:pyoperant.stimuli.StimulusConditionWav | ||
kwds: | ||
name: "No-Go" | ||
file_path: "/path/to/nogo/stimuli" | ||
response: false | ||
is_rewarded: true | ||
is_punished: false | ||
- &Go !!python/object:pyoperant.stimuli.StimulusConditionWav | ||
kwds: | ||
name: "Go" | ||
file_path: "/path/to/go/stimuli" | ||
response: true | ||
is_rewarded: false | ||
is_punished: false | ||
|
||
|
||
# Blocks | ||
blocks: | ||
# Block number 1 | ||
- !!python/object/apply:pyoperant.blocks.Block | ||
kwds: | ||
conditions: | ||
# NoGo stimuli | ||
- *NoGo | ||
# Go stimuli | ||
- *Go | ||
queue: !!python/name:pyoperant.queues.random_queue | ||
# Weights for random queue | ||
weights: | ||
- 0.2 | ||
- 0.8 | ||
reinforcement: !!python/object/apply:pyoperant.reinf.ContinuousReinforcement {} | ||
|
||
# Block Handler | ||
block_queue: !!python/name:pyoperant.queues.block_queue | ||
|
||
# Log handler setup | ||
# Possible values are stream, file, email | ||
log_handlers: | ||
# stream's only option is level. Overrides "debug" parameter for logging | ||
stream: | ||
level: !!python/name:logging.INFO | ||
# file takes options of | ||
# filename: a file under experiment_path | ||
# level: a python logging level, written as "!!python/name:logging.LEVEL" | ||
file: | ||
filename: "experiment.log" | ||
level: !!python/name:logging.DEBUG | ||
... |
Oops, something went wrong.