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
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)
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)