def _run(self): dsets, out_files = ephys_fpga.extract_all(self.session_path, save=True) self._behaviour_criterion() # Run the task QC qc = TaskQC(self.session_path, one=self.one, log=_logger) qc.extractor = TaskQCExtractor(self.session_path, lazy=True, one=qc.one) # Extract extra datasets required for QC qc.extractor.data = dsets qc.extractor.extract_data() # Aggregate and update Alyx QC fields qc.run(update=True) return out_files
def test_frame2ttl_flicker(self): from ibllib.io.extractors import ephys_fpga from ibllib.qc.task_metrics import TaskQC from ibllib.qc.task_extractors import TaskQCExtractor init_path = self.data_path.joinpath("ephys", "ephys_choice_world_task") session_path = init_path.joinpath("ibl_witten_27/2021-01-21/001") dsets, out_files = ephys_fpga.extract_all(session_path, save=True) # Run the task QC qc = TaskQC(session_path, one=None) qc.extractor = TaskQCExtractor(session_path, lazy=True, one=None) # Extr+act extra datasets required for QC qc.extractor.data = dsets qc.extractor.extract_data() # Aggregate and update Alyx QC fields _, myqc = qc.run(update=False) # from ibllib.misc import pprint # pprint(myqc) assert myqc['_task_stimOn_delays'] > 0.9 # 0.6176 assert myqc["_task_stimFreeze_delays"] > 0.9 assert myqc["_task_stimOff_delays"] > 0.9 assert myqc["_task_stimOff_itiIn_delays"] > 0.9 assert myqc["_task_stimOn_delays"] > 0.9 assert myqc["_task_stimOn_goCue_delays"] > 0.9
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)
class TestTaskQCObject(base.IntegrationTest): def setUp(self): self.one = one self.eid = "b1c968ad-4874-468d-b2e4-5ffa9b9964e9" # Make sure the data exists locally download_taskqc_raw_data(self.eid, one=one) self.session_path = self.one.path_from_eid(self.eid) self.qc = TaskQC(self.eid, one=self.one) self.qc.load_data(bpod_only=True) # Test session has no raw FPGA data def test_compute(self): # Compute metrics self.assertTrue(self.qc.metrics is None) self.assertTrue(self.qc.passed is None) self.qc.compute() self.assertTrue(self.qc.metrics is not None) self.assertTrue(self.qc.passed is not None) def test_run(self): # Reset Alyx fields before test reset = self.qc.update('NOT_SET', override=True) assert reset == 'NOT_SET', 'failed to reset QC field for test' extended = self.one.alyx.json_field_write('sessions', field_name='extended_qc', uuid=self.eid, data={}) assert not extended, 'failed to reset extended QC field for test' # Test update as False outcome, _ = self.qc.run(update=False) self.assertEqual('FAIL', outcome) extended = self.one.alyx.rest('sessions', 'read', id=self.eid)['extended_qc'] self.assertDictEqual({}, extended, 'unexpected update to extended qc') outcome = self.one.alyx.rest('sessions', 'read', id=self.eid)['qc'] self.assertEqual('NOT_SET', outcome, 'unexpected update to qc') # Test update as True outcome, results = self.qc.run(update=True) self.assertEqual('FAIL', outcome) extended = self.one.alyx.rest('sessions', 'read', id=self.eid)['extended_qc'] expected = list(results.keys()) + ['task'] self.assertCountEqual(expected, extended.keys(), 'unexpected update to extended qc') qc_field = self.one.alyx.rest('sessions', 'read', id=self.eid)['qc'] self.assertEqual(outcome, qc_field, 'unexpected update to qc') def test_compute_session_status(self): with self.assertRaises(AttributeError): self.qc.compute_session_status() self.qc.compute() outcome, results, outcomes = self.qc.compute_session_status() self.assertEqual('FAIL', outcome) # Check each outcome matches... # NOT_SET not_set = [k for k, v in results.items() if np.isnan(v)] self.assertTrue(all(outcomes[k] == 'NOT_SET' for k in not_set)) # PASS passed = [ k for k, v in results.items() if v >= self.qc.criteria['PASS'] ] self.assertTrue(all(outcomes[k] == 'PASS' for k in passed)) # WARNING wrn = [ k for k, v in results.items() if self.qc.criteria['WARNING'] <= v <= self.qc.criteria['PASS'] ] self.assertTrue(all(outcomes[k] == 'WARNING' for k in wrn)) # FAIL fail = [k for k, v in results.items() if v <= self.qc.criteria['FAIL']] self.assertTrue(all(outcomes[k] == 'FAIL' for k in fail))