def test_ibl_sync_maps(self): s = ephys_fpga.get_ibl_sync_map({'ap': 'toto', 'path': self.workdir}, '3A') self.assertEqual(s, ephys_fpga.CHMAPS['3A']['ap']) s = ephys_fpga.get_ibl_sync_map({'nidq': 'toto', 'path': self.workdir}, '3B') self.assertEqual(s, ephys_fpga.CHMAPS['3B']['nidq']) s = ephys_fpga.get_ibl_sync_map({'ap': 'toto', 'path': self.workdir}, '3B') self.assertEqual(s, ephys_fpga.CHMAPS['3B']['ap'])
def version3B(ses_path, display=True, type=None, tol=2.5): """ From a session path with _spikeglx_sync arrays extraccted, locate ephys files for 3A and outputs one sync.timestamps.probeN.npy file per acquired probe. By convention the reference probe is the one with the most synchronisation pulses. Assumes the _spikeglx_sync datasets are already extracted from binary data :param ses_path: :param type: linear, exact or smooth :return: None """ DEFAULT_TYPE = 'smooth' ephys_files = spikeglx.glob_ephys_files(ses_path, bin_exists=False) for ef in ephys_files: ef['sync'] = alf.io.load_object(ef.path, 'sync', namespace='spikeglx', short_keys=True) ef['sync_map'] = get_ibl_sync_map(ef, '3B') nidq_file = [ef for ef in ephys_files if ef.get('nidq')] ephys_files = [ef for ef in ephys_files if not ef.get('nidq')] # should have at least 2 probes and only one nidq assert (len(nidq_file) == 1) nidq_file = nidq_file[0] sync_nidq = _get_sync_fronts(nidq_file.sync, nidq_file.sync_map['imec_sync']) qc_all = True out_files = [] for ef in ephys_files: sync_probe = _get_sync_fronts(ef.sync, ef.sync_map['imec_sync']) sr = _get_sr(ef) try: assert (sync_nidq.times.size == sync_probe.times.size) except AssertionError: raise Neuropixel3BSyncFrontsNonMatching(f"{ses_path}") # if the qc of the diff finds anomalies, do not attempt to smooth the interp function qcdiff = _check_diff_3b(sync_probe) if not qcdiff: qc_all = False type_probe = type or 'exact' else: type_probe = type or DEFAULT_TYPE timestamps, qc = sync_probe_front_times(sync_probe.times, sync_nidq.times, sr, display=display, type=type_probe, tol=tol) qc_all &= qc out_files.extend(_save_timestamps_npy(ef, timestamps, sr)) return qc_all, out_files
def version3A(ses_path, display=True, linear=False, tol=1.5): """ From a session path with _spikeglx_sync arrays extracted, locate ephys files for 3A and outputs one sync.timestamps.probeN.npy file per acquired probe. By convention the reference probe is the one with the most synchronisation pulses. Assumes the _spikeglx_sync datasets are already extracted from binary data :param ses_path: :return: bool True on a a successful sync """ ephys_files = spikeglx.glob_ephys_files(ses_path) nprobes = len(ephys_files) if nprobes <= 1: _logger.warning(f"Skipping single probe session: {ses_path}") return True d = Bunch({'times': [], 'nsync': np.zeros(nprobes, )}) for ind, ephys_file in enumerate(ephys_files): sync = alf.io.load_object(ephys_file.ap.parent, '_spikeglx_sync', short_keys=True) sync_map = get_ibl_sync_map(ephys_file, '3A') isync = np.in1d(sync['channels'], np.array([sync_map['right_camera']])) d.nsync[ind] = len(sync.channels) d['times'].append(sync['times'][isync]) # chop off to the lowest number of sync points nsyncs = [t.size for t in d['times']] if len(set(nsyncs)) > 1: _logger.warning( "Probes don't have the same number of synchronizations pulses") d['times'] = np.r_[[t[:min(nsyncs)] for t in d['times']]].transpose() # the reference probe is the one with the most sync pulses detected iref = np.argmax(d.nsync) # islave = np.setdiff1d(np.arange(nprobes), iref) # get the sampling rate from the reference probe using metadata file sr = _get_sr(ephys_files[iref]) qc_all = True # output timestamps files as per ALF convention for ind, ephys_file in enumerate(ephys_files): if ind == iref: timestamps = np.array([[0, 0], [1, 1]]) else: timestamps, qc = sync_probe_front_times(d.times[:, iref], d.times[:, ind], sr, display=display, linear=linear, tol=tol) qc_all &= qc _save_timestamps_npy(ephys_file, timestamps) return qc_all
def get_sync_fronts(auxiliary_name): d = Bunch({'times': [], 'nsync': np.zeros(nprobes, )}) # auxiliary_name: frame2ttl or right_camera for ind, ephys_file in enumerate(ephys_files): sync = alf.io.load_object(ephys_file.ap.parent, '_spikeglx_sync', short_keys=True) sync_map = get_ibl_sync_map(ephys_file, '3A') # exits if sync label not found for current probe if auxiliary_name not in sync_map: return isync = np.in1d(sync['channels'], np.array([sync_map[auxiliary_name]])) # only returns syncs if we get fronts for all probes if np.all(~isync): return d.nsync[ind] = len(sync.channels) d['times'].append(sync['times'][isync]) return d
def version3B(ses_path, display=True, linear=False, tol=2.5): """ From a session path with _spikeglx_sync arrays extraccted, locate ephys files for 3A and outputs one sync.timestamps.probeN.npy file per acquired probe. By convention the reference probe is the one with the most synchronisation pulses. Assumes the _spikeglx_sync datasets are already extracted from binary data :param ses_path: :return: None """ ephys_files = spikeglx.glob_ephys_files(ses_path) for ef in ephys_files: ef['sync'] = alf.io.load_object(ef.path, '_spikeglx_sync', short_keys=True) ef['sync_map'] = get_ibl_sync_map(ef, '3B') nidq_file = [ef for ef in ephys_files if ef.get('nidq')] ephys_files = [ef for ef in ephys_files if not ef.get('nidq')] nprobes = len(ephys_files) # should have at least 2 probes and only one nidq if nprobes <= 1: return True assert (len(nidq_file) == 1) nidq_file = nidq_file[0] sync_nidq = _get_sync_fronts(nidq_file.sync, nidq_file.sync_map['imec_sync']) qc_all = True for ef in ephys_files: sync_probe = _get_sync_fronts(ef.sync, ef.sync_map['imec_sync']) sr = _get_sr(ef) assert (sync_nidq.times.size == sync_probe.times.size) timestamps, qc = sync_probe_front_times(sync_probe.times, sync_nidq.times, sr, display=display, linear=linear, tol=tol) qc_all &= qc _save_timestamps_npy(ef, timestamps) return qc_all