def _qc_from_path(sess_path, display=True): WHEEL = False sess_path = Path(sess_path) temp_alf_folder = sess_path.joinpath('fpga_test', 'alf') temp_alf_folder.mkdir(parents=True, exist_ok=True) raw_trials = raw_data_loaders.load_data(sess_path) tmax = raw_trials[-1]['behavior_data']['States timestamps']['exit_state'][ 0][-1] + 60 sync, chmap = ephys_fpga._get_main_probe_sync(sess_path, bin_exists=False) _ = ephys_fpga.extract_all(sess_path, output_path=temp_alf_folder, save=True) # check that the output is complete fpga_trials = ephys_fpga.extract_behaviour_sync( sync, output_path=temp_alf_folder, tmax=tmax, chmap=chmap, save=True, display=display) # align with the bpod bpod2fpga = ephys_fpga.align_with_bpod(temp_alf_folder.parent) alf_trials = alf.io.load_object(temp_alf_folder, 'trials') shutil.rmtree(temp_alf_folder) # do the QC qcs, qct = qc_fpga_task(fpga_trials, alf_trials) # do the wheel part if WHEEL: bpod_wheel = training_wheel.get_wheel_data(sess_path, save=False) fpga_wheel = ephys_fpga.extract_wheel_sync(sync, chmap=chmap, save=False) if display: import matplotlib.pyplot as plt t0 = max(np.min(bpod2fpga(bpod_wheel['re_ts'])), np.min(fpga_wheel['re_ts'])) dy = np.interp( t0, fpga_wheel['re_ts'], fpga_wheel['re_pos']) - np.interp( t0, bpod2fpga(bpod_wheel['re_ts']), bpod_wheel['re_pos']) fix, axes = plt.subplots(nrows=2, sharex='all', sharey='all') # axes[0].plot(t, pos), axes[0].title.set_text('Extracted') axes[0].plot(bpod2fpga(bpod_wheel['re_ts']), bpod_wheel['re_pos'] + dy) axes[0].plot(fpga_wheel['re_ts'], fpga_wheel['re_pos']) axes[0].title.set_text('FPGA') axes[1].plot(bpod2fpga(bpod_wheel['re_ts']), bpod_wheel['re_pos'] + dy) axes[1].title.set_text('Bpod') return alf.io.dataframe({**fpga_trials, **alf_trials, **qct})
def validate_ttl_test(ses_path, display=False): """ For a mock session on the Ephys Choice world task, check the sync channels for all device properly connected and perform a synchronization if dual probes to check that all channels are recorded properly :param ses_path: session path :param display: show the probe synchronization plot if several probes :return: True if tests pass, errors otherwise """ def _single_test(assertion, str_ok, str_ko): if assertion: _logger.info(str_ok) return True else: _logger.error(str_ko) return False EXPECTED_RATES_HZ = {'left_camera': 60, 'right_camera': 150, 'body_camera': 30} SYNC_RATE_HZ = 1 MIN_TRIALS_NB = 6 ok = True ses_path = Path(ses_path) if not ses_path.exists(): return False rawsync, sync_map = fpga._get_main_probe_sync(ses_path) last_time = rawsync['times'][-1] # get upgoing fronts for each sync = Bunch({}) for k in sync_map: fronts = fpga._get_sync_fronts(rawsync, sync_map[k]) sync[k] = fronts['times'][fronts['polarities'] == 1] wheel = fpga.extract_wheel_sync(rawsync, chmap=sync_map, save=False) frame_rates = {'right_camera': np.round(1 / np.median(np.diff(sync.right_camera))), 'left_camera': np.round(1 / np.median(np.diff(sync.left_camera))), 'body_camera': np.round(1 / np.median(np.diff(sync.body_camera)))} # check the camera frame rates for lab in frame_rates: expect = EXPECTED_RATES_HZ[lab] ok &= _single_test(assertion=abs((1 - frame_rates[lab] / expect)) < 0.1, str_ok=f'PASS: {lab} frame rate: {frame_rates[lab]} = {expect} Hz', str_ko=f'FAILED: {lab} frame rate: {frame_rates[lab]} != {expect} Hz') # check that the wheel has a minimum rate of activity on both channels re_test = abs(1 - sync.rotary_encoder_1.size / sync.rotary_encoder_0.size) < 0.1 re_test &= len(wheel['re_pos']) / last_time > 5 ok &= _single_test(assertion=re_test, str_ok="PASS: Rotary encoder", str_ko="FAILED: Rotary encoder") # check that the frame 2 ttls has a minimum rate of activity ok &= _single_test(assertion=len(sync.frame2ttl) / last_time > 0.2, str_ok="PASS: Frame2TTL", str_ko="FAILED: Frame2TTL") # the audio has to have at least one event per trial ok &= _single_test(assertion=len(sync.bpod) > len(sync.audio) > MIN_TRIALS_NB, str_ok="PASS: audio", str_ko="FAILED: audio") # the bpod has to have at least twice the amount of min trial pulses ok &= _single_test(assertion=len(sync.bpod) > MIN_TRIALS_NB * 2, str_ok="PASS: Bpod", str_ko="FAILED: Bpod") try: # note: tried to depend as little as possible on the extraction code but for the valve... behaviour = fpga.extract_behaviour_sync(rawsync, save=False, chmap=sync_map) res = behaviour.valve_open.size > 1 except AssertionError: res = False # check that the reward valve is actionned at least once ok &= _single_test(assertion=res, str_ok="PASS: Valve open", str_ko="FAILED: Valve open not detected") _logger.info('ALL CHECKS PASSED !') # the imec sync is for 3B Probes only if sync.get('imec_sync') is not None: ok &= _single_test(assertion=np.all(1 - SYNC_RATE_HZ * np.diff(sync.imec_sync) < 0.1), str_ok="PASS: imec sync", str_ko="FAILED: imec sync") # second step is to test that we can make the sync. Assertions are whithin the synch code if sync.get('imec_sync') is not None: sync_result = sync_probes.version3B(ses_path, display=display) else: sync_result = sync_probes.version3A(ses_path, display=display) ok &= _single_test(assertion=sync_result, str_ok="PASS: synchronisation", str_ko="FAILED: probe synchronizations threshold exceeded") if not ok: raise ValueError('FAILED TTL test') return ok
def _task_extraction_assertions(self, session_path): alf_path = session_path.joinpath('alf') shutil.rmtree(alf_path, ignore_errors=True) # try once without the sync pulses trials, out_files = ephys_fpga.FpgaTrials(session_path).extract( save=False) # then extract for real sync, chmap = ephys_fpga.get_main_probe_sync(session_path, bin_exists=False) trials, out_files = ephys_fpga.FpgaTrials(session_path).extract( save=True, sync=sync, chmap=chmap) # check that the output is complete for f in BPOD_FILES: self.assertTrue(alf_path.joinpath(f).exists()) # check that the output is complete for f in FPGA_FILES: self.assertTrue(alf_path.joinpath(f).exists()) # check dimensions after alf load alf_trials = alf.io.load_object(alf_path, 'trials') self.assertTrue(alf.io.check_dimensions(alf_trials) == 0) # go deeper and check the internal fpga trials structure consistency fpga_trials = ephys_fpga.extract_behaviour_sync(sync, chmap) # check dimensions self.assertEqual(alf.io.check_dimensions(fpga_trials), 0) # check that the stimOn < stimFreeze < stimOff self.assertTrue( np.all(fpga_trials['stimOn_times'][:-1] < fpga_trials['stimOff_times'][:-1])) self.assertTrue( np.all(fpga_trials['stimFreeze_times'][:-1] < fpga_trials['stimOff_times'][:-1])) # a trial is either an error-nogo or a reward self.assertTrue( np.all( np.isnan(fpga_trials['valveOpen_times'][:-1] * fpga_trials['errorCue_times'][:-1]))) self.assertTrue( np.all( np.logical_xor(np.isnan(fpga_trials['valveOpen_times'][:-1]), np.isnan(fpga_trials['errorCue_times'][:-1])))) # do the task qc # tqc_ephys.extractor.settings['PYBPOD_PROTOCOL'] from ibllib.qc.task_extractors import TaskQCExtractor ex = TaskQCExtractor(session_path, lazy=True, one=None, bpod_only=False) ex.data = fpga_trials ex.extract_data() from ibllib.qc.task_metrics import TaskQC # '/mnt/s0/Data/IntegrationTests/ephys/ephys_choice_world_task/CSP004/2019-11-27/001' tqc_ephys = TaskQC(session_path) tqc_ephys.extractor = ex _, res_ephys = tqc_ephys.run(bpod_only=False, download_data=False) tqc_bpod = TaskQC(session_path) _, res_bpod = tqc_bpod.run(bpod_only=True, download_data=False) for k in res_ephys: if k == "_task_response_feedback_delays": continue assert (np.abs(res_bpod[k] - res_ephys[k]) < .2) shutil.rmtree(alf_path, ignore_errors=True)
def _task_extraction_assertions(self, session_path): alf_path = session_path.joinpath('alf') shutil.rmtree(alf_path, ignore_errors=True) # this gets the full output ephys_fpga.extract_all(session_path, save=True, bin_exists=False) # check that the output is complete for f in BPOD_FILES: self.assertTrue(alf_path.joinpath(f).exists()) # check that the output is complete for f in FPGA_FILES: self.assertTrue(alf_path.joinpath(f).exists()) # check dimensions after alf load alf_trials = alf.io.load_object(alf_path, 'trials') self.assertTrue(alf.io.check_dimensions(alf_trials) == 0) # go deeper and check the internal fpga trials structure consistency sync, chmap = ephys_fpga.get_main_probe_sync(session_path, bin_exists=False) fpga_trials = ephys_fpga.extract_behaviour_sync(sync, chmap) # check dimensions self.assertEqual(alf.io.check_dimensions(fpga_trials), 0) # check that the stimOn < stimFreeze < stimOff self.assertTrue( np.all(fpga_trials['stimOn_times'][:-1] < fpga_trials['stimOff_times'][:-1])) self.assertTrue( np.all(fpga_trials['stimFreeze_times'][:-1] < fpga_trials['stimOff_times'][:-1])) # a trial is either an error-nogo or a reward self.assertTrue( np.all( np.isnan(fpga_trials['valveOpen_times'][:-1] * fpga_trials['errorCue_times'][:-1]))) self.assertTrue( np.all( np.logical_xor(np.isnan(fpga_trials['valveOpen_times'][:-1]), np.isnan(fpga_trials['errorCue_times'][:-1])))) # do the task qc # tqc_ephys.extractor.settings['PYBPOD_PROTOCOL'] from ibllib.qc.task_extractors import TaskQCExtractor ex = TaskQCExtractor(session_path, lazy=True, one=None, bpod_only=False) ex.data = fpga_trials ex.extract_data() from ibllib.qc.task_metrics import TaskQC # '/mnt/s0/Data/IntegrationTests/ephys/ephys_choice_world_task/CSP004/2019-11-27/001' tqc_ephys = TaskQC(session_path) tqc_ephys.extractor = ex _, res_ephys = tqc_ephys.run(bpod_only=False, download_data=False) tqc_bpod = TaskQC(session_path) _, res_bpod = tqc_bpod.run(bpod_only=True, download_data=False) # for a swift comparison using variable explorer # import pandas as pd # df = pd.DataFrame([[res_bpod[k], res_ephys[k]] for k in res_ephys], index=res_ephys.keys()) ok = True for k in res_ephys: if k == "_task_response_feedback_delays": continue if (np.abs(res_bpod[k] - res_ephys[k]) > .2): ok = False print(f"{k} bpod: {res_bpod[k]}, ephys: {res_ephys[k]}") assert ok shutil.rmtree(alf_path, ignore_errors=True)