Source code for allensdk.brain_observatory.behavior.data_objects.licks

import logging
from typing import Optional, Union

import numpy as np
import pandas as pd
from pynwb import NWBFile, TimeSeries, ProcessingModule

from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.brain_observatory.behavior.data_objects import StimulusTimestamps
from allensdk.core import \
    NwbReadableInterface
from allensdk.brain_observatory.behavior.data_files.stimulus_file import \
    StimulusFileReadableInterface
from allensdk.core import \
    NwbWritableInterface


[docs] class Licks(DataObject, StimulusFileReadableInterface, NwbReadableInterface, NwbWritableInterface): _logger = logging.getLogger(__name__) def __init__(self, licks: pd.DataFrame): """ :param licks dataframe containing the following columns: - timestamps: float stimulus timestamps in which there was a lick - frame: int frame number in which there was a lick """ super().__init__(name='licks', value=licks)
[docs] @classmethod def from_stimulus_file( cls, stimulus_file: BehaviorStimulusFile, stimulus_timestamps: Union[StimulusTimestamps, np.ndarray] ) -> "Licks": """Get lick data from pkl file. This function assumes that the first sensor in the list of lick_sensors is the desired lick sensor. Since licks can occur outside of a trial context, the lick times are extracted from the vsyncs and the frame number in `lick_events`. Since we don't have a timestamp for when in "experiment time" the vsync stream starts (from self.get_stimulus_timestamps), we compute it by fitting a linear regression (frame number x time) for the `start_trial` and `end_trial` events in the `trial_log`, to true up these time streams. Parameters ---------- stimulus_file : BehaviorStimulusFile Input Behavior stims loaded from a pickle file. stimulus_timestamps : StimulusTimestamps or np.ndarray Timestamps containing lick data either in a StimulusTimestamps object or numpy array. Numpy array data must be the SyncFile line named ``lick_times``. Returns ------- `Licks` instance """ data = stimulus_file.data lick_frames = (data["items"]["behavior"]["lick_sensors"][0] ["lick_events"]) if isinstance(stimulus_timestamps, StimulusTimestamps): if not np.isclose(stimulus_timestamps.monitor_delay, 0.0): msg = ("Instantiating licks with monitor_delay = " f"{stimulus_timestamps.monitor_delay: .2e}; " "monitor_delay should be zero for Licks " "data object") raise RuntimeError(msg) lick_times = stimulus_timestamps.value else: lick_times = stimulus_timestamps # there's an occasional bug where the number of logged # frames is one greater than the number of vsync intervals. # If the animal licked on this last frame it will cause an # error here. This fixes the problem. # see: https://github.com/AllenInstitute/visual_behavior_analysis # /issues/572 # noqa: E501 # & https://github.com/AllenInstitute/visual_behavior_analysis # /issues/379 # noqa:E501 # # This bugfix copied from # https://github.com/AllenInstitute/visual_behavior_analysis/blob if len(lick_frames) > 0: if lick_frames[-1] == len(lick_times): lick_frames = lick_frames[:-1] cls._logger.error('removed last lick - ' 'it fell outside of stimulus_timestamps ' 'range') if isinstance(stimulus_timestamps, StimulusTimestamps): lick_times = np.array([lick_times[frame] for frame in lick_frames]) # Make sure licks are the same length as number of frames (mostly for # array input). max_length = min(len(lick_times), len(lick_frames)) lick_frames = lick_frames[0:max_length] lick_times = lick_times[0:max_length] df = pd.DataFrame({"timestamps": lick_times, "frame": lick_frames}) return cls(licks=df)
[docs] @classmethod def from_nwb(cls, nwbfile: NWBFile) -> Optional["Licks"]: if 'licking' in nwbfile.processing: lick_module = nwbfile.processing['licking'] licks = lick_module.get_data_interface('licks') timestamps = licks.timestamps[:] frame = licks.data[:] else: timestamps = [] frame = [] df = pd.DataFrame({ 'timestamps': timestamps, 'frame': frame }) return cls(licks=df)
[docs] def to_nwb(self, nwbfile: NWBFile) -> NWBFile: # If there is no lick data, do not write # anything to the NWB file (this is # expected for passive sessions) if len(self.value['frame']) == 0: return nwbfile lick_timeseries = TimeSeries( name='licks', data=self.value['frame'].values, timestamps=self.value['timestamps'].values, description=('Timestamps and stimulus presentation ' 'frame indices for lick events'), unit='N/A' ) # Add lick interface to nwb file, by way of a processing module: licks_mod = ProcessingModule('licking', 'Licking behavior processing module') licks_mod.add_data_interface(lick_timeseries) nwbfile.add_processing_module(licks_mod) return nwbfile