Example #1
0
class OphysTimeAligner(object):
    def __init__(self,
                 sync_file,
                 scanner=None,
                 dff_file=None,
                 stimulus_pkl=None,
                 eye_video=None,
                 behavior_video=None,
                 long_stim_threshold=LONG_STIM_THRESHOLD):
        self.scanner = scanner if scanner is not None else "SCIVIVO"
        self._dataset = Dataset(sync_file)
        self._keys = get_keys(self._dataset)
        self.long_stim_threshold = long_stim_threshold
        if dff_file is not None:
            self.ophys_data_length = get_ophys_data_length(dff_file)
        else:
            self.ophys_data_length = None
        if stimulus_pkl is not None:
            self.stim_data_length = get_stim_data_length(stimulus_pkl)
        else:
            self.stim_data_length = None
        if eye_video is not None:
            self.eye_data_length = get_video_length(eye_video)
        else:
            self.eye_data_length = None
        if behavior_video is not None:
            self.behavior_data_length = get_video_length(behavior_video)
        else:
            self.behavior_data_length = None

    @property
    def dataset(self):
        return self._dataset

    @property
    def ophys_timestamps(self):
        """Get the timestamps for the ophys data."""
        ophys_key = self._keys["2p"]
        if self.scanner == "SCIVIVO":
            # Scientifica data looks different than Nikon.
            # http://confluence.corp.alleninstitute.org/display/IT/Ophys+Time+Sync
            times = self.dataset.get_rising_edges(ophys_key, units="seconds")
        elif self.scanner == "NIKONA1RMP":
            # Nikon has a signal that indicates when it started writing to disk
            acquiring_key = self._keys["acquiring"]
            acquisition_start = self._dataset.get_rising_edges(
                acquiring_key, units="seconds")[0]
            ophys_times = self._dataset.get_falling_edges(ophys_key,
                                                          units="seconds")
            times = ophys_times[ophys_times >= acquisition_start]
        else:
            raise ValueError("Invalid scanner: {}".format(self.scanner))

        return times

    @property
    def corrected_ophys_timestamps(self):
        times = self.ophys_timestamps

        delta = 0
        if self.ophys_data_length is not None:
            if len(times) < self.ophys_data_length:
                raise ValueError(
                    "Got too few timestamps ({}) for ophys data length "
                    "({})".format(len(times), self.ophys_data_length))
            elif len(times) > self.ophys_data_length:
                logging.info(
                    "Ophys data of length %s has timestamps of "
                    "length %s, truncating timestamps", self.ophys_data_length,
                    len(times))
                delta = len(times) - self.ophys_data_length
                times = times[:-delta]
        else:
            logging.info("No data length provided for ophys stream")

        return times, delta

    @property
    def stim_timestamps(self):
        stim_key = self._keys["stimulus"]

        return self.dataset.get_falling_edges(stim_key, units="seconds")

    @property
    def corrected_stim_timestamps(self):
        timestamps = self.stim_timestamps

        delta = 0
        if self.stim_data_length is not None and \
           self.stim_data_length < len(timestamps):
            stim_key = self._keys["stimulus"]
            rising = self.dataset.get_rising_edges(stim_key, units="seconds")

            # Some versions of camstim caused a spike when the DAQ is first
            # initialized. Remove it.
            if rising[1] - rising[0] > self.long_stim_threshold:
                logging.info("Initial DAQ spike detected from stimulus, "
                             "removing it")
                timestamps = timestamps[1:]

            delta = len(timestamps) - self.stim_data_length
            if delta != 0:
                logging.info(
                    "Stim data of length %s has timestamps of "
                    "length %s", self.stim_data_length, len(timestamps))
        elif self.stim_data_length is None:
            logging.info("No data length provided for stim stream")

        photodiode_key = self._keys["photodiode"]
        delay = monitor_delay(self.dataset, timestamps, photodiode_key)

        return timestamps + delay, delta, delay

    @property
    def behavior_video_timestamps(self):
        key = self._keys["behavior_camera"]

        return self.dataset.get_falling_edges(key, units="seconds")

    @property
    def corrected_behavior_video_timestamps(self):
        return corrected_video_timestamps("Behavior video",
                                          self.behavior_video_timestamps,
                                          self.behavior_data_length)

    @property
    def eye_video_timestamps(self):
        key = self._keys["eye_camera"]

        return self.dataset.get_falling_edges(key, units="seconds")

    @property
    def corrected_eye_video_timestamps(self):
        return corrected_video_timestamps("Eye video",
                                          self.eye_video_timestamps,
                                          self.eye_data_length)
Example #2
0
class OphysTimeAligner(object):
    def __init__(self,
                 sync_file,
                 scanner=None,
                 dff_file=None,
                 stimulus_pkl=None,
                 eye_video=None,
                 behavior_video=None,
                 long_stim_threshold=LONG_STIM_THRESHOLD):
        self.scanner = scanner if scanner is not None else "SCIVIVO"
        self._dataset = Dataset(sync_file)
        self._keys = get_keys(self._dataset)
        self.long_stim_threshold = long_stim_threshold

        self._monitor_delay = None
        self._clipped_stim_ts_delta = None
        self._clipped_stim_timestamp_values = None

        if dff_file is not None:
            self.ophys_data_length = get_ophys_data_length(dff_file)
        else:
            self.ophys_data_length = None
        if stimulus_pkl is not None:
            self.stim_data_length = get_stim_data_length(stimulus_pkl)
        else:
            self.stim_data_length = None
        if eye_video is not None:
            self.eye_data_length = get_video_length(eye_video)
        else:
            self.eye_data_length = None
        if behavior_video is not None:
            self.behavior_data_length = get_video_length(behavior_video)
        else:
            self.behavior_data_length = None

    @property
    def dataset(self):
        return self._dataset

    @property
    def ophys_timestamps(self):
        """Get the timestamps for the ophys data."""
        ophys_key = self._keys["2p"]
        if self.scanner == "SCIVIVO":
            # Scientifica data looks different than Nikon.
            # http://confluence.corp.alleninstitute.org/display/IT/Ophys+Time+Sync
            times = self.dataset.get_rising_edges(ophys_key, units="seconds")
        elif self.scanner == "NIKONA1RMP":
            # Nikon has a signal that indicates when it started writing to disk
            acquiring_key = self._keys["acquiring"]
            acquisition_start = self._dataset.get_rising_edges(
                acquiring_key, units="seconds")[0]
            ophys_times = self._dataset.get_falling_edges(ophys_key,
                                                          units="seconds")
            times = ophys_times[ophys_times >= acquisition_start]
        else:
            raise ValueError("Invalid scanner: {}".format(self.scanner))

        return times

    @property
    def corrected_ophys_timestamps(self):
        times = self.ophys_timestamps

        delta = 0
        if self.ophys_data_length is not None:
            if len(times) < self.ophys_data_length:
                raise ValueError(
                    "Got too few timestamps ({}) for ophys data length "
                    "({})".format(len(times), self.ophys_data_length))
            elif len(times) > self.ophys_data_length:
                logging.info(
                    "Ophys data of length %s has timestamps of "
                    "length %s, truncating timestamps", self.ophys_data_length,
                    len(times))
                delta = len(times) - self.ophys_data_length
                times = times[:-delta]
        else:
            logging.info("No data length provided for ophys stream")

        return times, delta

    @property
    def stim_timestamps(self):
        stim_key = self._keys["stimulus"]

        return self.dataset.get_falling_edges(stim_key, units="seconds")

    def _get_clipped_stim_timestamps(self):
        timestamps = self.stim_timestamps

        delta = 0
        if self.stim_data_length is not None and \
           self.stim_data_length < len(timestamps):
            stim_key = self._keys["stimulus"]
            rising = self.dataset.get_rising_edges(stim_key, units="seconds")

            # Some versions of camstim caused a spike when the DAQ is first
            # initialized. Remove it.
            if rising[1] - rising[0] > self.long_stim_threshold:
                logging.info("Initial DAQ spike detected from stimulus, "
                             "removing it")
                timestamps = timestamps[1:]

            delta = len(timestamps) - self.stim_data_length
            if delta != 0:
                logging.info(
                    "Stim data of length %s has timestamps of "
                    "length %s", self.stim_data_length, len(timestamps))
        elif self.stim_data_length is None:
            logging.info("No data length provided for stim stream")

        return timestamps, delta

    @property
    def clipped_stim_timestamps(self):
        """
        Return the stimulus timestamps with the erroneous initial spike
        removed (if relevant)

        Returns
        -------
        timestamps: np.ndarray
            An array of stimulus timestamps in seconds with th emonitor delay
            added

        delta: int
            Difference between the length of timestamps
            and the number of frames reported in the stimulus
            pickle file, i.e.
            len(timestamps) - len(pkl_file['items']['behavior']['intervalsms']
        """
        if self._clipped_stim_ts_delta is None:
            (self._clipped_stim_timestamp_values, self._clipped_stim_ts_delta
             ) = self._get_clipped_stim_timestamps()

        return (self._clipped_stim_timestamp_values,
                self._clipped_stim_ts_delta)

    def _get_monitor_delay(self):
        timestamps, delta = self.clipped_stim_timestamps
        photodiode_key = self._keys["photodiode"]
        delay = calculate_monitor_delay(self.dataset, timestamps,
                                        photodiode_key)
        return delay

    @property
    def monitor_delay(self):
        """
        The monitor delay (in seconds) associated with the session
        """
        if self._monitor_delay is None:
            self._monitor_delay = self._get_monitor_delay()
        return self._monitor_delay

    @property
    def corrected_stim_timestamps(self):
        """
        The stimulus timestamps corrected for monitor delay

        Returns
        -------
        timestamps: np.ndarray
            An array of stimulus timestamps in seconds with th emonitor delay
            added

        delta: int
            Difference between the length of timestamps and
            the number of frames reported in the stimulus
            pickle file, i.e.
            len(timestamps) - len(pkl_file['items']['behavior']['intervalsms']

        delay: float
            The monitor delay in seconds
        """
        timestamps, delta = self.clipped_stim_timestamps
        delay = self.monitor_delay

        return timestamps + delay, delta, delay

    @property
    def behavior_video_timestamps(self):
        key = self._keys["behavior_camera"]

        return self.dataset.get_falling_edges(key, units="seconds")

    @property
    def corrected_behavior_video_timestamps(self):
        return corrected_video_timestamps("Behavior video",
                                          self.behavior_video_timestamps,
                                          self.behavior_data_length)

    @property
    def eye_video_timestamps(self):
        key = self._keys["eye_camera"]

        return self.dataset.get_falling_edges(key, units="seconds")

    @property
    def corrected_eye_video_timestamps(self):
        return corrected_video_timestamps("Eye video",
                                          self.eye_video_timestamps,
                                          self.eye_data_length)
Example #3
0
def get_sync_data(sync_path):

    sync_dataset = SyncDataset(sync_path)
    meta_data = sync_dataset.meta_data
    sample_freq = meta_data['ni_daq']['counter_output_freq']

    # use rising edge for Scientifica, falling edge for Nikon http://confluence.corp.alleninstitute.org/display/IT/Ophys+Time+Sync
    # 2P vsyncs
    vs2p_r = sync_dataset.get_rising_edges('2p_vsync')
    vs2p_f = sync_dataset.get_falling_edges(
        '2p_vsync'
    )  # new sync may be able to do units = 'sec', so conversion can be skipped
    frames_2p = vs2p_r / sample_freq
    vs2p_fsec = vs2p_f / sample_freq

    stimulus_times_no_monitor_delay = sync_dataset.get_falling_edges(
        'stim_vsync') / sample_freq

    if 'lick_times' in meta_data['line_labels']:
        lick_times = sync_dataset.get_rising_edges('lick_1') / sample_freq
    elif 'lick_sensor' in meta_data['line_labels']:
        lick_times = sync_dataset.get_rising_edges('lick_sensor') / sample_freq
    else:
        lick_times = None
    if '2p_trigger' in meta_data['line_labels']:
        trigger = sync_dataset.get_rising_edges('2p_trigger') / sample_freq
    elif 'acq_trigger' in meta_data['line_labels']:
        trigger = sync_dataset.get_rising_edges('acq_trigger') / sample_freq
    if 'stim_photodiode' in meta_data['line_labels']:
        a = sync_dataset.get_rising_edges('stim_photodiode') / sample_freq
        b = sync_dataset.get_falling_edges('stim_photodiode') / sample_freq
        stim_photodiode = sorted(list(a) + list(b))
    elif 'photodiode' in meta_data['line_labels']:
        a = sync_dataset.get_rising_edges('photodiode') / sample_freq
        b = sync_dataset.get_falling_edges('photodiode') / sample_freq
        stim_photodiode = sorted(list(a) + list(b))
    if 'cam1_exposure' in meta_data['line_labels']:
        eye_tracking = sync_dataset.get_rising_edges(
            'cam1_exposure') / sample_freq
    elif 'eye_tracking' in meta_data['line_labels']:
        eye_tracking = sync_dataset.get_rising_edges(
            'eye_tracking') / sample_freq
    if 'cam2_exposure' in meta_data['line_labels']:
        behavior_monitoring = sync_dataset.get_rising_edges(
            'cam2_exposure') / sample_freq
    elif 'behavior_monitoring' in meta_data['line_labels']:
        behavior_monitoring = sync_dataset.get_rising_edges(
            'behavior_monitoring') / sample_freq

    sync_data = {
        'ophys_frames': frames_2p,
        'lick_times': lick_times,
        'ophys_trigger': trigger,
        'eye_tracking': eye_tracking,
        'behavior_monitoring': behavior_monitoring,
        'stim_photodiode': stim_photodiode,
        'stimulus_times_no_delay': stimulus_times_no_monitor_delay,
    }

    return sync_data