Source code for autopilot.tasks.nafc


import datetime
import itertools
import tables
import threading
from copy import copy
import typing

import numpy as np

import autopilot.hardware.gpio
from autopilot.tasks import Task
from autopilot.stim import init_manager
from autopilot.stim.sound import sounds as stim_sounds
from autopilot.hardware import gpio
from collections import OrderedDict as odict
from autopilot.core.networking import Net_Node
from autopilot.core.utils import find_recursive

from autopilot import prefs
import pdb
import pickle

# This declaration allows Subject to identify which class in this file contains the task class. Could also be done with __init__ but yno I didnt for no reason.
# TODO: Move this to __init__
TASK = 'Nafc'

[docs]class Nafc(Task): """ A Two-alternative forced choice task. *(can't have number as first character of class.)* **Stages** * **request** - compute stimulus, set request trigger in center port. * **discrim** - respond to input, set reward/punishment triggers on target/distractor ports * **reinforcement** - deliver reward/punishment, end trial. Attributes: target ("L", "R"): Correct response distractor ("L", "R"): Incorrect response stim : Current stimulus response ("L", "R"): Response to discriminand correct (0, 1): Current trial was correct/incorrect correction_trial (bool): If using correction trials, last trial was a correction trial trial_counter (:class:`itertools.count`): Which trial are we on? discrim_playing (bool): Is the stimulus playing? bailed (0, 1): Subject answered before stimulus was finished playing. current_stage (int): As each stage is reached, update for asynchronous event reference """ STAGE_NAMES = ["request", "discrim", "reinforcement"] # Class attributes # List of needed params, returned data and data format. # Params are [name]={'tag': Human Readable Tag, 'type': 'int', 'float', 'bool', etc.} PARAMS = odict() # TODO: Reward no longer just duration -- fix with parameter structure PARAMS['reward'] = {'tag':'Reward Duration (ms)', 'type':'int'} PARAMS['req_reward'] = {'tag':'Request Rewards', 'type':'bool'} PARAMS['punish_stim'] = {'tag':'White Noise Punishment', 'type':'bool'} PARAMS['punish_dur'] = {'tag':'Punishment Duration (ms)', 'type':'int'} PARAMS['correction'] = {'tag':'Correction Trials', 'type':'bool'} PARAMS['correction_pct'] = {'tag':'% Correction Trials', 'type':'int', 'depends':{'correction':True}} PARAMS['bias_mode'] = {'tag':'Bias Correction Mode', 'type':'list', 'values':{'None':0, 'Proportional':1, 'Thresholded Proportional':2}} PARAMS['bias_threshold'] = {'tag': 'Bias Correction Threshold (%)', 'type':'int', 'depends':{'bias_mode':2}} #PARAMS['timeout'] = {'tag':'Delay Timeout (ms)', # 'type':'int'} PARAMS['stim'] = {'tag':'Sounds', 'type':'sounds'} # Set plot params, which data should be plotted, its default shape, etc. # TODO: Plots should take the default type, but options panel should be able to set - eg. corrects are done by rolling mean as default, but can be made points PLOT = { 'data': { 'target' : 'point', 'response' : 'segment', 'correct' : 'rollmean' }, 'chance_bar' : True, # Draw a red bar at 50% 'roll_window' : 50 # number of trials to roll window over } # PyTables Data descriptor # for numpy data types see http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html#arrays-dtypes-constructing class TrialData(tables.IsDescription): # This class allows the Subject object to make a data table with the correct data types. You must update it for any new data you'd like to store trial_num = tables.Int32Col() target = tables.StringCol(1) response = tables.StringCol(1) correct = tables.Int32Col() correction = tables.Int32Col() RQ_timestamp = tables.StringCol(26) DC_timestamp = tables.StringCol(26) bailed = tables.Int32Col() HARDWARE = { 'POKES':{ 'L': gpio.Digital_In, 'C': gpio.Digital_In, 'R': gpio.Digital_In }, 'LEDS':{ # TODO: use LEDs, RGB vs. white LED option in init 'L': gpio.LED_RGB, 'C': gpio.LED_RGB, 'R': gpio.LED_RGB }, 'PORTS':{ 'L': gpio.Solenoid, 'C': gpio.Solenoid, 'R': gpio.Solenoid } } def __init__(self, stage_block=None, stim=None, reward=50, req_reward=False, punish_stim=False, punish_dur=100, correction=False, correction_pct=50., bias_mode=False, bias_threshold=20, current_trial=0, stim_light=True, **kwargs): """ Args: stage_block (:class:`threading.Event`): Signal when task stages complete. stim (dict): Stimuli like:: "sounds": { "L": [{"type": "Tone", ...}], "R": [{"type": "Tone", ...}] } reward (float): duration of solenoid open in ms req_reward (bool): Whether to give a water reward in the center port for requesting trials punish_stim (bool): Do a white noise punishment stimulus punish_dur (float): Duration of white noise in ms correction (bool): Should we do correction trials? correction_pct (float): (0-1), What proportion of trials should randomly be correction trials? bias_mode (False, "thresholded_linear"): False, or some bias correction type (see :class:`.managers.Bias_Correction` ) bias_threshold (float): If using a bias correction mode, what threshold should bias be corrected for? current_trial (int): If starting at nonzero trial number, which? stim_light (bool): Should the LED be turned blue while the stimulus is playing? **kwargs: """ super(Nafc, self).__init__() # Fixed parameters # Because the current protocol is json.loads from a string, # we should explicitly type everything to be safe. if isinstance(reward, dict): self.reward = reward else: self.reward = {'type':'duration', 'value': float(reward)} self.req_reward = bool(req_reward) self.punish_stim = bool(punish_stim) self.punish_dur = float(punish_dur) self.correction = bool(correction) self.correction_pct = float(correction_pct)/100 self.bias_mode = bias_mode self.bias_threshold = float(bias_threshold)/100 self.stim_light = bool(stim_light) #self.timeout = int(timeout) # Variable Parameters self.target = None self.distractor = None self.stim = None self.response = None self.correct = None self.correction_trial = False self.trial_counter = itertools.count(int(current_trial)) self.current_trial = int(current_trial) #self.discrim_finished = False # Set to true once the discrim stim has finished, used for punishing leaving C early self.discrim_playing = False self.current_stage = None # Keep track of stages so some asynchronous callbacks know when it's their turn self.bailed = 0 # We make a list of the variables that need to be reset each trial so it's easier to do so self.resetting_variables = [self.response, self.bailed] # This allows us to cycle through the task by just repeatedly calling self.stages.next() stage_list = [self.request, self.discrim, self.reinforcement] self.num_stages = len(stage_list) self.stages = itertools.cycle(stage_list) # Initialize hardware self.init_hardware() self.logger.debug('Hardware initialized') # Set reward values for solenoids # TODO: Super inelegant, implement better with reward manager if self.reward['type'] == "volume": self.set_reward(vol=self.reward['value']) else: self.set_reward(duration=self.reward['value']) # Initialize stim manager if not stim: raise RuntimeError("Cant instantiate task without stimuli!") else: self.stim_manager = init_manager(stim) # give the sounds a function to call when they end self.stim_manager.set_triggers(self.stim_end) if self.correction: self.stim_manager.do_correction(self.correction_pct) if bias_mode: self.stim_manager.do_bias(mode=self.bias_mode, thresh=self.bias_threshold) self.logger.debug('Stimulus manager initialized') # If we aren't passed an event handler # (used to signal that a trigger has been tripped), # we should warn whoever called us that things could get a little screwy if not stage_block: raise Warning('No stage_block Event() was passed, youll need to handle stage progression on your own') else: self.stage_block = stage_block # # def center_out(self, pin, level, tick): # """ # # """ # # Called when something leaves the center pin, # # We use this to handle the subject leaving the port early # if self.discrim_playing: # self.bail_trial() ################################################################################## # Stage Functions ##################################################################################
[docs] def request(self,*args,**kwargs): """ Stage 0: compute stimulus, set request trigger in center port. Returns: data (dict): With fields:: { 'target': self.target, 'trial_num' : self.current_trial, 'correction': self.correction_trial, 'type': stimulus type, **stim.PARAMS } """ # Set the event lock self.stage_block.clear() # Reset all the variables that need to be for v in self.resetting_variables: v = None # reset triggers if there are any left self.triggers = {} # get next stim self.target, self.distractor, self.stim = self.stim_manager.next_stim() # buffer it self.stim.buffer() # if we're doing correction trials, check if this is one if self.correction: self.correction_trial = self.stim_manager.correction_trial # Set sound trigger and LEDs self.triggers['C'] = [self.stim.play, self.stim_start] if self.stim_light: change_to_blue = lambda: self.hardware['LEDS']['C'].set([0, 0, 255]) self.triggers['C'].append(change_to_blue) else: # just turn the center light off and side lights on immediately. turn_off = lambda: self.set_leds({ 'L': [0,255,0], 'R': [0,255,0] }) #turn_off = lambda: self.hardware['LEDS']['C'].set([0,0,0]) self.triggers['C'].append(turn_off) if self.req_reward: self.triggers['C'].append(self.hardware['PORTS']['C'].open) self.current_trial = next(self.trial_counter) data = { 'target':self.target, 'trial_num' : self.current_trial, 'correction':self.correction_trial } # get stim info and add to data dict sound_info = {k:getattr(self.stim, k) for k in self.stim.PARAMS} data.update(sound_info) data.update({'type':self.stim.type}) self.current_stage = 0 # wait on punish block # FIXME: Only waiting to test whether this is where the bug that hangs after this stage is self.punish_block.wait(20) # set to green in the meantime self.set_leds({'C': [0, 255, 0]}) return data
[docs] def discrim(self,*args,**kwargs): """ Stage 1: respond to input, set reward/punishment triggers on target/distractor ports Returns: data (dict): With fields:: { 'RQ_timestamp': datetime.datetime.now().isoformat(), 'trial_num': self.current_trial, } """ # moust just poked in center, set response triggers self.stage_block.clear() self.triggers[self.target] = [lambda: self.respond(self.target), self.hardware['PORTS'][self.target].open] self.triggers[self.distractor] = [lambda: self.respond(self.distractor), self.punish] # TODO: Handle timeout # Only data is the timestamp data = {'RQ_timestamp': datetime.datetime.now().isoformat(), 'trial_num': self.current_trial} self.current_stage = 1 return data
[docs] def reinforcement(self,*args,**kwargs): """ Stage 2 - deliver reward/punishment, end trial. Returns: data (dict): With fields:: { 'DC_timestamp': datetime.datetime.now().isoformat(), 'response': self.response, 'correct': self.correct, 'bailed': self.bailed, 'trial_num': self.current_trial, 'TRIAL_END': True } """ # We do NOT clear the task event flag here because we want # the pi to call the next stage immediately # We are just filling in the last data # and performing any calculations we need for the next trial if self.bailed: self.bailed = 0 data = { 'DC_timestamp': datetime.datetime.now().isoformat(), 'bailed':1, 'trial_num': self.current_trial, 'TRIAL_END':True } return data if self.response == self.target: self.correct = 1 else: self.correct = 0 # update stim manager self.stim_manager.update(self.response, self.correct) data = { 'DC_timestamp': datetime.datetime.now().isoformat(), 'response':self.response, 'correct':self.correct, 'bailed':0, 'trial_num': self.current_trial, 'TRIAL_END':True } self.current_stage = 2 return data
[docs] def punish(self): """ Flash lights, play punishment sound if set """ # TODO: If we're not in the last stage (eg. we were timed out after stim presentation), reset stages self.punish_block.clear() if self.punish_stim: self.stim_manager.play_punishment() # self.set_leds() self.flash_leds() threading.Timer(self.punish_dur / 1000., self.punish_block.set).start()
[docs] def respond(self, pin): """ Set self.response Args: pin: Pin to set response to """ self.response = pin
[docs] def stim_start(self): """ mark discrim_playing = true """ self.discrim_playing = True
[docs] def stim_end(self): """ called by stimulus callback set outside lights blue """ # Called by the discrim sound's table trigger when playback is finished # Used in punishing leaving early self.discrim_playing = False #if not self.bailed and self.current_stage == 1: if self.stim_light: self.set_leds({'L':[0,255,0], 'R':[0,255,0]})
# def bail_trial(self): # # If a timer ends or the subject pulls out too soon, we punish and bail # self.bailed = 1 # self.triggers = {} # self.punish() # self.stage_block.set() # def clear_triggers(self): # for pin in self.hardware.values(): # pin.clear_cb()
[docs] def flash_leds(self): """ flash lights for punish_dir """ for k, v in self.hardware['LEDS'].items(): if isinstance(v, gpio.LED_RGB): v.flash(self.punish_dur)
[docs]class Nafc_Gap(Nafc): PARAMS = copy(Nafc.PARAMS) del PARAMS['punish_stim'] PARAMS['noise_amplitude'] = {'tag':'Amplitude of continuous white noise', 'type': 'float'} def __init__(self, noise_amplitude = 0.01, **kwargs): """ A Mild variation of :class:`Nafc` that starts continuous white noise that plays continuously while the task is active. Args: noise_amplitude (float): Multiplier used to scale amplitude of continuous noise **kwargs: passed to :class:`Nafc` """ # Can't really have a white noise punishment when there is continuous noise kwargs['punish_stim'] = False kwargs['stim_light'] = False super(Nafc_Gap, self).__init__(**kwargs) self.logger.debug('starting background sound') self.noise_amplitude = noise_amplitude self.noise_duration = 10*1000 # 10 seconds self.noise = stim_sounds.Noise(duration=self.noise_duration, amplitude=self.noise_amplitude) self.noise.play_continuous() self.logger.debug('background sound started')
[docs] def end(self): """ Stop the task, ending the continuous white noise. """ self.noise.stop_continuous() super(Nafc_Gap, self).end()
[docs]class Nafc_Gap_Laser(Nafc_Gap): PARAMS = copy(Nafc_Gap.PARAMS) PARAMS['laser_probability'] = {'tag': 'Probability (of trials whose targets match laser_mode) of laser being turned on (0-1)', 'type':'float'} PARAMS['laser_mode'] = {'tag':'Laser Mode, laser will be possible when target == ?', 'type':'list', 'values':{ 'L':0, 'R':1, 'Both':2 }} PARAMS['laser_freq'] = {'tag': 'Laser Pulse Frequency (Hz), list-like [20, 30]', 'type': 'str'} PARAMS['laser_duty_cycle'] = {'tag': 'Laser Duty Cycle (0-1), list-like [0.1, 0.2]', 'type': 'str'} PARAMS['laser_durations'] = {'tag': 'Laser durations (ms), list-like [10, 20]. if blank, use durations from stimuli', 'type': 'str'} PARAMS['arena_led_mode'] = {'tag': 'Arena LED Mode: always ON vs. on for longest stim duration during requests', 'type': 'list', 'values':{'ON': 0, 'STIM': 1}} HARDWARE = copy(Nafc_Gap.HARDWARE) HARDWARE['LASERS'] = { 'LR': gpio.Digital_Out } HARDWARE['LEDS']['TOP'] = gpio.Digital_Out TrialData = copy(Nafc_Gap.TrialData) TrialData.laser = tables.Int32Col() TrialData.laser_duration = tables.Float32Col() TrialData.laser_freq = tables.Float32Col() TrialData.laser_duty_cycle = tables.Float32Col() def __init__(self, laser_probability: float, laser_mode: str, laser_freq: typing.Union[str, list], laser_duty_cycle: typing.Union[str, list], laser_durations: typing.Union[str, list], arena_led_mode: str = 'ON', **kwargs): """ Gap detection task with ability to control lasers via TTL logic for optogenetics :attr:`.laser_freq`, :attr:`.laser_duty_cycle`, and :attr:`.laser_durations` can be passed either as an integer (actually typically a string because of the way the value is pulled from the protocol wizard), or as a list -- the product of values for all three are generated and presented equiprobably (eg. if ``laser_freq = 20, laser_duty_cycle=[0.1, 0.2, 0.3], laser_durations = [1, 2, 4, 8]`` were passed, then 1*3*4=12 different laser conditions would be possible. .. note:: Subclasses like these will be made obsolete with the completion of stimulus managers Args: laser_probability (float): if trial satisfies ``laser_mode``, probability that laser will be laser_mode ('L', 'R', or 'Both'): Selects whether the laser is to be presented when :attr:`.target` is ``'L', 'R'`` or Either. laser_freq (str, list): Single value or list of possible laser frequencies in Hz laser_duty_cycle (str, list): Single value or list of possible duty cycles from 0-1 laser_durations (str, list): Single value or list of possible laser durations (total time laser is on) in ms arena_led_mode ('ON', 'STIM'): Whether the overhead LED should always be 'ON', or whether it should be illuminated for the duration of the longest stimulus at every request Attributes: laser_conditions (tuple): tuple of dicts of laser conditions, of format:: { 'freq': laser frequency, 'duty_cycle': laser duty cycle, 'duration': laser duration, 'script_id': script ID for the series used by the laser Digital Out object, } """ self.laser_probability = float(laser_probability) self.laser_mode = laser_mode self.arena_led_mode = arena_led_mode # accept them if we're given a list of values, otherwise they should be strings that are single values, # which are put in lists so they can be iterated over in the product iterator. self.laser_freq = laser_freq if isinstance(laser_freq, list) else [float(laser_freq)] # type: list self.laser_duty_cycle = laser_duty_cycle if isinstance(laser_duty_cycle, list) else [float(laser_duty_cycle)] # type: list self.laser_durations = laser_durations if isinstance(laser_durations, list) else [float(laser_durations)] # type: list self.laser_conditions = tuple() # type: typing.Tuple[typing.Dict] super(Nafc_Gap_Laser, self).__init__(**kwargs) self.init_lasers() # ----------------------------------- # create a pulse for the LED that's equal to the longest stimulus duration # use find_recursive to find all durations # FIXME: implement stimulus managers properly, including API to get attributes of stimuli if self.arena_led_mode == "ON": self.hardware['LEDS']['TOP'].turn(True) elif self.arena_led_mode == "STIM": stim_durations = list(find_recursive('duration', kwargs['stim'])) max_duration = np.max(stim_durations) self.hardware['LEDS']['TOP'].store_series('on', values=1, durations=max_duration ) else: raise ValueError(f'arena_led_mode must be one of ON or STIM, got {self.arena_led_mode}')
[docs] def init_lasers(self): """ Given :attr:`.laser_freq`, :attr:`.laser_duty_cycle`, :attr:`.laser_durations` , create series with :meth:`.Digital_Out.store_series` and populate :attr:`.laser_conditions` """ # TODO: This really should be something that Digital_Out should be capable of doing -- specifying series from these params... # -------------------------------------- # create description of laser pulses # iterate over laser condition lists, # create lists of values (on/off) and durations (ms) # use them to create pigpio scripts using the Digital_Out.store_series() method # -------------------------------------------------- self.logger.debug('Creating laser and LED series') # create iterator condition_iter = itertools.product(self.laser_durations, self.laser_freq, self.laser_duty_cycle) conditions = [] for duration, freq, duty_cycle in condition_iter: # get the durations of on and off for a single cycle cycle_duration = (1/freq)*1000 # convert Hz to ms duty_cycle_on = duty_cycle * cycle_duration duty_cycle_off = cycle_duration - duty_cycle_on # get number of repeats to make n_cycles = int(np.floor(duration/cycle_duration)) durations = [duty_cycle_on, duty_cycle_off]*n_cycles values = [1, 0]*n_cycles # pad any incomplete cycles dur_remaining = duration-(cycle_duration*n_cycles) if dur_remaining < duty_cycle_on: durations.append(dur_remaining) values.append(1) else: durations.extend([duty_cycle_on, dur_remaining-duty_cycle_on]) values.extend([1, 0]) # create ID from params script_id = f"{duration}_{freq}_{duty_cycle}" # store pulses as pigpio scripts self.hardware['LASERS']['LR'].store_series(script_id, values=values, durations=durations) conditions.append({ 'freq':freq, 'duty_cycle': duty_cycle, 'duration': duration, 'script_id': script_id }) self.laser_conditions = tuple(conditions) self.logger.debug(f'Laser series created with {len(self.laser_conditions)} conditions')
[docs] def request(self,*args,**kwargs): """ Call the superclass request method, and then compute laser presentation logic. If :attr:`.target` == :attr:`.laser_mode`, spin for a laser trial depending on :attr:`.laser_probability`. If we present a laser on this trial, we randomly draw from :attr:`.laser_conditions` and call the appropriate script. """ # call the super method data = super(Nafc_Gap_Laser, self).request(*args, **kwargs) # handle laser logic # if the laser_mode is fulfilled, roll for a laser test_laser = False if self.laser_mode == "L" and self.target == "L": test_laser = True elif self.laser_mode == "R" and self.target == "R": test_laser = True elif self.laser_mode == "Both": test_laser = True duration = 0 duty_cycle = 0 frequency = 0 do_laser = False if test_laser: # if we've rolled correctly for a laser... if np.random.rand() <= self.laser_probability: do_laser = True # If we're doing laser, we don't do the stim, so we pop the first two triggers del self.triggers['C'][:2] # pick a random duration condition = np.random.choice(self.laser_conditions) duration = condition['duration'] duty_cycle = condition['duty_cycle'] frequency = condition['freq'] # insert the laser triggers before the rest of the triggers self.triggers['C'].insert(0, lambda: self.hardware['LASERS']['LR'].series(id=condition['script_id'])) # always turn the light on if self.arena_led_mode == "STIM": self.triggers['C'].insert(0, lambda: self.hardware['LEDS']['TOP'].series(id='on')) # store the data about the laser status data['laser'] = do_laser data['laser_duration'] = duration data['laser_duty_cycle'] = duty_cycle data['laser_frequency'] = frequency # return the data created by the original task return data
[docs] def set_leds(self, color_dict=None): """ Set the color of all LEDs at once. Override base method to exclude TOP led Args: color_dict (dict): If None, turn LEDs off, otherwise like: {'pin': [R,G,B], 'pin2: [R,G,B]} """ # We are passed a dict of ['pin']:[R, G, B] to set multiple colors # All others are turned off if not color_dict: color_dict = {} for k, v in self.hardware['LEDS'].items(): if k == "TOP": continue if k in color_dict.keys(): v.set(color_dict[k]) else: v.set(0)
# # class Nafc_Wheel(Nafc): # """ # 2afc using a wheel run on a child pi as the input device # """ # HARDWARE = { # 'POKES': { # 'C': hardware.Beambreak, # }, # 'FLAGS': { # 'L': hardware.Flag, # 'R': hardware.Flag # }, # 'LEDS': { # # TODO: use LEDs, RGB vs. white LED option in init # 'L': hardware.LED_RGB, # 'C': hardware.LED_RGB, # 'R': hardware.LED_RGB # } # } # # PLOT = { # 'data': { # 'x':'shaded', # 'response':'segment' # }, # 'continuous' : True # } # # PARAMS = Nafc.PARAMS # # # # def __init__(self, **kwargs): # self.init_networking(kwargs) # # super(Nafc_Wheel, self).__init__(**kwargs) # # # # TODO: Update PARAMS with wheel params # def init_networking(self, kwargs): # # self.node = Net_Node(id="T_{}".format(prefs.get('NAME')), # upstream=prefs.get('NAME'), # port=prefs.get('MSGPORT'), # listens = {}, # instance=True) # # value = { # 'child': {'parent':prefs.get('NAME'), 'subject':kwargs['subject']}, # 'task_type': 'Wheel Child', # 'subject': kwargs['subject'] # } # # self.node.send(key='CHILD', value=value) # # # class Gap_2AFC(Nafc): # def __init__(self, **kwargs): # # """ # Args: # **kwargs: # """ # super(Gap_2AFC, self).__init__(**kwargs) # # # def load_sounds(self): # # TODO: Definitely put this in a metaclass # # # Iterate through sounds and load them to memory # for k, v in self.soundict.items(): # # If multiple sounds on one side, v will be a list # if isinstance(v, list): # self.sounds[k] = [] # for sound in v: # if float(sound['duration']) == 0: # self.sounds[k].append(None) # continue # # a zero duration gap doesn't change the continuous noise object # # # We send the dict 'sound' to the function specified by 'type' and 'SOUND_LIST' as kwargs # self.sounds[k].append(sounds.SOUND_LIST[sound['type']](**sound)) # # Then give the sound a callback to mark when it's finished # #self.sounds[k][-1].set_trigger(self.stim_end) # # If not a list, a single sound # else: # if v['duration'] == 0: # self.sounds[k] = [None] # continue # # self.sounds[k] = sounds.SOUND_LIST[v['type']](**v) # #self.sounds[k].set_trigger(self.stim_end) # # def blank_trigger(self): # print('blank trig') # sys.stdout.flush() # #pass # # def stim_end(self): # # Called by the discrim sound's table trigger when playback is finished # # Used in punishing leaving early # # self.set_leds({'L':[0,255,0], 'R':[0,255,0]}) # # def request(self,*args,**kwargs): # """ # Args: # args: # kwargs: # """ # # Set the event lock # self.stage_block.clear() # # # Reset all the variables that need to be # for v in self.resetting_variables: # v = None # # self.triggers = {} # # # # # Attempt to identify target sound # #TODO: Implement sound ID's better # #try: # # self.target_sound_id = self.target_sound.id # #except AttributeError: # # warnings.warn("Sound ID not defined! Sounds cannot be uniquely identified!") # # self.target_sound_id = None # # # Set sound trigger and LEDs # # We make two triggers to play the sound and change the light color # # # set triggers # if self.target_sound is None: # sound_trigger = self.blank_trigger # else: # sound_trigger = self.target_sound.play # # # if self.req_reward is True: # self.triggers['C'] = [self.hardware['PORTS']['C'].open, sound_trigger, self.stim_end] # else: # self.triggers['C'] = [sound_trigger, self.stim_end] # self.set_leds({'C': [0, 255, 0]}) # # self.current_trial = self.trial_counter.next() # data = { # 'target':self.target, # #'target_sound_id':self.target_sound_id, # 'RQ_timestamp':datetime.datetime.now().isoformat(), # 'trial_num' : self.current_trial # #'correction':self.correction_trial # } # # get sound info and add to data dict # if self.target_sound is None: # sound_info = {'duration':0, 'amplitude':0.01} # else: # sound_info = {k:getattr(self.target_sound, k) for k in self.target_sound.PARAMS} # data.update({'type': self.target_sound.type}) # # data.update(sound_info) # # # self.current_stage = 0 # return data # # def end(self): # for k, v in self.sounds.items(): # if isinstance(v, list): # for sound in v: # try: # sound.stop() # except: # pass # else: # try: # v.stop() # except: # pass # # for k, v in self.hardware.items(): # for pin, obj in v.items(): # if k == "LEDS": # obj.set_color([0,0,0]) # obj.release()