Stages

The stages of the experiment.

class forced_choice.stages.RootStage(**kwargs)

Bases: cplcom.moa.stages.ConfigStageBase

The stage that creates and initializes all the Barst devices (or simulation devices if ExperimentApp.simulate).

configs

A dict whose keys are names of experiment types and whose values are ExperimentConfig instances configuring the corresponding experiment.

create_daqin_devs(sim, settings)

Creates the daq input device, daq_in_dev.

create_daqout_devs(sim, settings)

Creates the daq output device, daq_out_dev.

create_mfc_devs(sim, settings)

Creates the MFC devices: mfc_air, mfc_a, and mfc_b.

create_odor_devs(sim, settings)

Creates the odor device, odor_dev.

create_sound_devs(sim, settings)

Creates the sound devices: sound_l and sound_r.

daq_in_dev

The Switch and Sense device, DAQInDevice when using actual hardware, or a DAQInDeviceSim when simulate the hardware.

daq_out_dev

The Switch and Sense device, DAQOutDevice when using actual hardware, or a DAQOutDeviceSim when simulate the hardware.

filter_len

The number of previous trials to average when displaying the trial result in the graphs.

ftdi_chan

The FTDI controller device, FTDIDevChannel when using actual hardware, or None when simulate.

log_filename

The pattern that will be used to generate the log filenames for each trial. It is generated as follows:

strftime(log_name_pat.format(**{'animal': animal_id, 'trial': trial,
'block': block}))

Which basically means that all instances of {animal}, {trial}, and {block} in the filename will be replaced by the animal name given in the GUI, the current trial, and block numbers. Then, it’s is passed to strftime that formats any time parameters to get the log name used for that animal.

If the filename matches an existing file, the new data will be appended to that file.

mfc_a

The MFC controlling one odor stream, MFC when using actual hardware, or a NumericPropertyChannel when simulate the hardware.

mfc_air

The MFC controlling the air, MFC when using actual hardware, or a NumericPropertyChannel when simulate the hardware.

mfc_b

The MFC controlling the other odor stream, MFC when using actual hardware, or a NumericPropertyChannel when simulate the hardware.

n_valve_boards

The number of valve boards connected. Each board typically controls 8 valves.

odor_dev

The FTDI valve board, FTDIOdors when using actual hardware, or a FTDIOdorsSim when simulate the hardware.

server

The Barst server instance, Server, or None when simulate.

simulate

Whether the user has chosen to simulate the experiment. When True, no actual hardware is required and all the hardware will be emulated by software and virtual devices.

sound_file_l

The sound file used in training as a cue when the left side is rewarded.

sound_file_r

The sound file used in training as a cue when the right side is rewarded.

sound_l

The cplcom.moa.device.ffplayer.FFPyPlayerAudioDevice that plays the file provided in sound_file_l.

sound_r

The cplcom.moa.device.ffplayer.FFPyPlayerAudioDevice that plays the file provided in sound_file_r.

tracker = None

The ObjectStateTracker instance used to process the device activation and deactivation during startup and shutdown.

use_mfc

Whether a MFC is used for mixing the odor streams (i.e. two odors are presented in a mixed form for each trial).

use_mfc_air

When use_mfc is False, if this is True, a MFC will be used for driving air as a single odor stream. No mixing is performed.

class forced_choice.stages.ExperimentConfig(load=True, **kwargs)

Bases: moa.base.MoaBase

Stores the configuration parameters for a experiment.

NO_valve

A list of, for each block in num_blocks, the normally open (mineral oil) odor valve. I.e. the valve which is normally open and closes during the trial when the odor is released.

air_rate

A list of, for each block in num_blocks, the flow rate for the air stream using the air MFC when RootStage.use_mfc or RootStage.use_mfc_air.

apply_config_ui()

Updates the odor and experiment values of the UI using the provided configuration parameters.

bad_iti

The ITI duration of a failed trial.

beta_trials_max

For each odor, it is the last beta_trials_max trials (of that odor) to take into account when computing the accuracy rate for that odor.

Trials for this odor further back in history than beta_trials_max specific to this odor are dropped.

beta_trials_min

The minimum number of trials for each odor that must have occured before odor_beta bias compensation is activated. If the number of trial that occurred for any odor is less than beta_trials_min, bias compensation is disabled.

do_equal_random(n, m, cond, last_val=None)

Implements odor_equalizer when selected.

do_odor_list(block, block_odors, odor_opts)

Reads the odor selection for each trial from a list when odor_method ‘`list’`.

do_odor_random(block, block_odors, odor_opts, method, n)

When odor_method is not 'list', but is random or constant, this generates the odors list for each trial.

good_iti

The ITI duration of a passed trial.

incomplete_iti

The ITI duration of a trial where the animal did not hold its nose long enough in the nose port and min_nose_poke was not satisfied.

max_decision_duration

A list of, for each block in num_blocks, the maximum duration of the decision stage. After this duration, the stage will terminate and proceed to the ITI stage even if the animal didn’t visit the reward port.

The decision determines whether a reward is dispensed and the duration of the ITI.

If zero, there is no maximum.

max_nose_poke

A list of, for each block in num_blocks, the maximum duration of the nose port stage. After this duration, the stage will terminate and proceed to the decision stage even if the animal is still in the nose port.

If zero, there is no maximum.

mfc_a_rate

A list of, for each block in num_blocks, the flow rate for the odor stream a using the odor a MFC when RootStage.use_mfc.

mfc_b_rate

A list of, for each block in num_blocks, the flow rate for the odor stream b using the odor b MFC when RootStage.use_mfc.

min_nose_poke

A list of, for each block in num_blocks, the minimum duration in the nose port AFTER the odor is released (i.e. odor_delay). A nose port exit less than this duration will result in an incomplete trial. The ITI will then be incomplete_iti.

If zero, there is no minimum.

mix_dur

A list of, for each block in num_blocks, how long to pass the air stream through the odor vials before the trial starts (during the last ITI).

This ensures that when the animal enters the nose port, the odor is stream is already saturated. During this time the odor is directed to teh vaccum.

mix_valve

A list of, for each block in num_blocks, the valve that directs the odor to go to vacuum or to the animal. Before the odor goes to the animal, the odor is mixed and evacuated to vacuum in order to saturate the air stream into a steady state condition.

num_blocks

The number of blocks to run. Each block runs num_trials trials.

All the configuration parameters that are lists, e.g. num_trials can specify a different value for each block.

If the number of elements in these lists are less than the number of blocks, the last value of the list is used for the remaining blocks. E.g. for 10 blocks, if num_trials is [5, 6, 2], then blocks 2 - 9 will have 2 trials.

num_pellets

The number of sugar pellets to deliver upon a successful trial.

num_trials

A list of the number of trials to run for each block of num_blocks.

odor_beta

A list of, for each block in num_blocks, the beta value to use when compensating for unequal side performance. This compensation is applied dynamically during the trials on top of any previous odor computations.

We keep track of the accuracy rate of every odor (i.e. how often the animal chooses the incorrectly for that odor). Then, odors where the animal performed poorly will get presented with a higher probability.

A odor_beta value of zero disables this bias compensation. A value of e.g. 10, will bias very strongly towards changing the next trial odor to be a odor in which the animal performed poorly. The closer to zero, the lower such bias compensation. A value of 2-3 is reasonable.

The trials are accumulated across blocks so a new block does not clear the odor bias history.

odor_delay

A list of, for each block in num_blocks, how long to delay the odor delivery onset from when the animal enters the nose port.

If zero, there’s no delay.

odor_equalizer

A list of, for each block in num_blocks, the number of trials during which all the odors for that block will be presented an equal number of times.

That is, during these exclusively grouped odor_equalizer trials, no odor will be presented more times than any other odor.

The number of odors for each block listed in odor_selection must divide without remainder the odor_equalizer value for that block.

odor_method

A list of, for each block in num_blocks, the method used to determine which odor to use in the trials for the odors listed in odor_selection.

Possible methods are constant, randomx, or list. odor_selection is used to select the odor to be used with this method.

constant:
odor_selection is a 2d list of odors of length num_blocks. Each element in the outer list is a single element list containing the odor that is used for all the trials of that block.
randomx: x is a number or empty

odor_selection is a 2d list of odors of length num_blocks. Each inner list is a list of odors from which the trial odor would be randomly selected for each trial in the block.

If the method is random, the odor is randomly selected from that list. If random is followed by an integer, e.g. random2, then it’s random with the condition that no odor can be repeated more then x (2 in this) times successively.

list:

odor_selection is a 2d list of filenames. The files are read for each block and the odors listed in the file is used for the trials.

The structure of the text file is a line for each block. Each line is a comma separated list, with the first column being the block number and the other column the odors to use for that block.

Each inner list in the 2d list (line) can only have a single filename for that block.

odor_names

A RootStage.n_valve_boards * 8 long list for each valve, indicating the name of the odor for that valve.

odor_opts = None

A list containing, for each block, a list of all the possible odors for this block from which we select a odor for every trial.

Each element is a tuple of 1 or two odors with the same structure as the elements returned by extract_odor().

odor_path

The filename of a file containing the names of odors and which side to reward that odor.

The structure of the file is as follows: each line describes an odor and is a 3 or 4 column comma separated list of (idx, name, side, mfc), where idx is the zero-based valve index. Name is the odor name. And side is the side of the odor to reward (r, l, rl, lr, or -). If using an mfc, the 4th column is either a, or b indicating the mfc to use of that valve.

An example file is:

1, mineral oil, r
4, citric acid, rl
5, limonene, l
...
odor_selection

A list of, for each block in num_blocks, a inner list of odors used to select from trial odors for each block. See odor_method.

odor_side

A RootStage.n_valve_boards * 8 long list for each valve, indicating which side is rewarded for that valve.

sound_cue_delay

A list of, for each block in num_blocks, the random amount of time to delay the sound cue AFTER min_nose_poke elapsed. It’s a value between zero and sound_cue_delay.

If zero or if sound_dur is zero, there is no delay.

sound_dur

A list of, for each block in num_blocks, the duration to play the sound cue after sound_cue_delay. It plays either RootStage.sound_file_r or RootStage.sound_file_l depending on the trial odor.

If zero, no sound is played.

trial_odors = None

A 2d list of the odors for each trial in each block.

Each element is a tuple of 1 or two odors with the same structure as the elements returned by extract_odor().

valve_mfc = None

A RootStage.n_valve_boards * 8 long list for each valve, indicating whether MFC a or b is used for that valve.

wait_for_nose_poke

A list of, for each block in num_blocks, whether to wait for a nose poke or to immediately go to the reward stage.

When False, entering the reward port will dispense reward and end the trial. The ITI will then be good_iti for that block.

class forced_choice.stages.AnimalStage(**kwargs)

Bases: moa.stage.MoaStage

In this stage, each loop iteration runs another animal.

animal_id

The animal id of the current animal.

block

The current block number.

config

The ExperimentConfig instance used to configure the current animal.

do_decision(r, l, timed_out)

Executed after the reward port entry or after waiting for the reward port entry timed out. It decides whether the animal is rewarded.

do_nose_poke()

Executed after the first nose port entry of the trial.

do_nose_poke_exit(timed_out)

Executed after the first nose port exit of the trial.

do_odor_release()

After start_mixing(), it redirects the already mixing odor to the animal.

init_trial(block, trial)

Starts the trial.

initialize_animal()

Executed before the start of a new animal.

initialize_box()

Turns on fans, lights etc at the beginning of the experiment.

iti

The ITI used for this trial.

nose_poke_exit_timed_out = False

Whether the animal was in the nose port longer than ExperimentConfig.max_nose_poke and it timed out.

nose_poke_exit_ts = None

The time of the nose port exit.

nose_poke_ts = None

The time of the nose port entry.

odor = None

The odor to reward for this trial.

odor_outcome = {}

keys are the odor indices, values are a list with 0 or 1 indicating the trial reward outcome for that odor. This combines all the blocks for the animal.

odor_start_ts = None

The time when the odor was released to the animal.

odor_widgets = []

List of forced_choice.graphics.TrialPrediction instances for all the blocks, containing all the trials.

outcome = None

Whether this trial was an incomplete.

outcome_wid = None

The forced_choice.graphics.TrialOutcome widget describing the current trial.

outcomes = []

1 or 0 for each trial indicating the trial reward outcome. Reset at each block.

post_trial()

Executed after each trial.

pre_block()

Executed before each block.

pre_trial()

Executed before each trial.

predict_widget = None

forced_choice.graphics.TrialPrediction instance for the current trial.

reward_entry_timed_out = False

Whether the animal waited longer than ExperimentConfig.max_decision_duration before making a decision by going to either feeder side and it timed out.

reward_entry_ts = None

The time of the reward port entry.

reward_side

The feeder device name of the side on which to reward this trial.

side = None

The side (rl) of odor to reward.

side_went = None

The feeder side the animal visited.

sound

The sound file, from RootStage.sound_r and RootStage.sound_l, to use for this trial.

start_mixing()

Opens the odor valves to start mixing with the air stream, but directs it to the vacuum.

total_fail

Total number of failed trials for this block.

total_incomplete

Total number of incomplete trials for this block.

total_pass

Total number of passed trials for this block.

trial

The current trial number.

trial_start_time = None

The start time of the trial in human readable clock format.

trial_start_ts = None

The start time of the trial in seconds.

update_trial_odor()

Updates the trial odor using ExperimentConfig.beta when non-zero.

forced_choice.stages.extract_odor(odors, block, N)

Takes the list of odors for a block, provided in ExperimentConfig.odor_selection, and parses it and returns the possible odors to choose from for that block.

Parameters:
odors: list

The odors list for a specific block, provided in ExperimentConfig.odor_selection.

block: int

The block associated with the odors.

N: int

The total of number of odor valves available (typically 8 or 16).

Returns:

A list of the parsed odors.

Each element of the list is a 1 or 2-tuple. The elements in this tuple is each a 3-tuple with the valve number controlling this odor, the probability p of this odor being rewarded, even if correctly chosen by the subject when provided, and the MFC rate at which this odor airflow will bubble through (in order to mix them), if RootStage.use_mfc.

forced_choice.stages.select_odor(odors)

Given a element of the list returned by extract_odor(), if the element is a 1-tuple it returns the first item, otherwise, it returns the odor with the higher flow rate of the two.

It is the odor (with the higher flow rate) that is rewarded when the animal picks that side when a odor mixture is presented.