From 8525c75d48fc7acda1b4ff0e8c3c901de838dda8 Mon Sep 17 00:00:00 2001 From: Miles Wells Date: Thu, 14 Dec 2023 14:59:43 +0200 Subject: [PATCH] Correct handling of missing TTLs in FpgaTrialsHabituation --- ibllib/__init__.py | 2 +- ibllib/io/extractors/ephys_fpga.py | 22 ++++++++++------------ ibllib/tests/qc/test_alignment_qc.py | 9 +++------ ibllib/tests/test_oneibl.py | 11 +++++------ release_notes.md | 3 +++ 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/ibllib/__init__.py b/ibllib/__init__.py index 55e4b2b5d..b55499130 100644 --- a/ibllib/__init__.py +++ b/ibllib/__init__.py @@ -2,7 +2,7 @@ import logging import warnings -__version__ = '2.27' +__version__ = '2.27.1' warnings.filterwarnings('always', category=DeprecationWarning, module='ibllib') # if this becomes a full-blown library we should let the logging configuration to the discretion of the dev diff --git a/ibllib/io/extractors/ephys_fpga.py b/ibllib/io/extractors/ephys_fpga.py index 03eb7079c..f19fbdd68 100644 --- a/ibllib/io/extractors/ephys_fpga.py +++ b/ibllib/io/extractors/ephys_fpga.py @@ -1376,7 +1376,6 @@ def build_trials(self, sync, chmap, display=False, **kwargs): 'intervals_1': bpod_event_intervals['trial_iti'][:, 0], 'goCue_times': audio_event_intervals['ready_tone'][:, 0] }) - n_trials = self.bpod_trials['intervals'].shape[0] # Sync the Bpod clock to the DAQ. self.bpod2fpga, drift_ppm, ibpod, ifpga = self.sync_bpod_clock(self.bpod_trials, fpga_events, self.sync_field) @@ -1390,26 +1389,25 @@ def build_trials(self, sync, chmap, display=False, **kwargs): # Assigning each event to a trial ensures exactly one event per trial (missing events are NaN) assign_to_trial = partial(_assign_events_to_trial, fpga_events['intervals_0']) trials = alfio.AlfBunch({ - 'goCue_times': assign_to_trial(fpga_events['goCue_times'], take='first')[:n_trials], - 'feedback_times': assign_to_trial(fpga_events['feedback_times'])[:n_trials], - 'stimCenter_times': assign_to_trial(self.frame2ttl['times'], take=-2)[:n_trials], - 'stimOn_times': assign_to_trial(self.frame2ttl['times'], take='first')[:n_trials], - 'stimOff_times': assign_to_trial(self.frame2ttl['times'])[:n_trials], + 'goCue_times': assign_to_trial(fpga_events['goCue_times'], take='first'), + 'feedback_times': assign_to_trial(fpga_events['feedback_times']), + 'stimCenter_times': assign_to_trial(self.frame2ttl['times'], take=-2), + 'stimOn_times': assign_to_trial(self.frame2ttl['times'], take='first'), + 'stimOff_times': assign_to_trial(self.frame2ttl['times']), }) + out.update({k: trials[k][ifpga] for k in trials.keys()}) # If stim on occurs before trial end, use stim on time. Likewise for trial end and stim off - to_correct = ~np.isnan(trials['stimOn_times']) & (trials['stimOn_times'] < out['intervals'][:, 0]) + to_correct = ~np.isnan(out['stimOn_times']) & (out['stimOn_times'] < out['intervals'][:, 0]) if np.any(to_correct): _logger.warning('%i/%i stim on events occurring outside trial intervals', sum(to_correct), len(to_correct)) - out['intervals'][to_correct, 0] = trials['stimOn_times'][to_correct] - to_correct = ~np.isnan(trials['stimOff_times']) & (trials['stimOff_times'] > out['intervals'][:, 1]) + out['intervals'][to_correct, 0] = out['stimOn_times'][to_correct] + to_correct = ~np.isnan(out['stimOff_times']) & (out['stimOff_times'] > out['intervals'][:, 1]) if np.any(to_correct): _logger.debug( '%i/%i stim off events occurring outside trial intervals; using stim off times as trial end', sum(to_correct), len(to_correct)) - out['intervals'][to_correct, 1] = trials['stimOff_times'][to_correct] - - out.update({k: trials[k][ifpga] for k in trials.keys()}) + out['intervals'][to_correct, 1] = out['stimOff_times'][to_correct] if display: # pragma: no cover width = 0.5 diff --git a/ibllib/tests/qc/test_alignment_qc.py b/ibllib/tests/qc/test_alignment_qc.py index 331850b17..30df45de4 100644 --- a/ibllib/tests/qc/test_alignment_qc.py +++ b/ibllib/tests/qc/test_alignment_qc.py @@ -266,16 +266,13 @@ class TestAlignmentQcManual(unittest.TestCase): @classmethod def setUpClass(cls) -> None: rng = np.random.default_rng() - data = np.load(Path(Path(__file__).parent.parent. - joinpath('fixtures', 'qc', 'data_alignmentqc_manual.npz')), - allow_pickle=True) + fixture_path = Path(__file__).parent.parent.joinpath('fixtures', 'qc') + data = np.load(fixture_path / 'data_alignmentqc_manual.npz', allow_pickle=True) cls.xyz_picks = (data['xyz_picks'] * 1e6).tolist() cls.alignments = data['alignments'].tolist() cls.cluster_chns = data['cluster_chns'] - data = np.load(Path(Path(__file__).parent.parent. - joinpath('fixtures', 'qc', 'data_alignmentqc_existing.npz')), - allow_pickle=True) + data = np.load(fixture_path / 'data_alignmentqc_existing.npz', allow_pickle=True) insertion = data['insertion'].tolist() insertion['name'] = ''.join(random.choices(string.ascii_letters, k=5)) insertion['json'] = {'xyz_picks': cls.xyz_picks} diff --git a/ibllib/tests/test_oneibl.py b/ibllib/tests/test_oneibl.py index a92305268..729b391ab 100644 --- a/ibllib/tests/test_oneibl.py +++ b/ibllib/tests/test_oneibl.py @@ -272,9 +272,6 @@ def setUp(self) -> None: } # makes sure tests start without session created - eid = self.one.search(subject=self.subject, date_range='2018-04-01', query_type='remote') - for ei in eid: - self.one.alyx.rest('sessions', 'delete', id=ei) self.td = tempfile.TemporaryDirectory() self.session_path = Path(self.td.name).joinpath(self.subject, '2018-04-01', '002') self.alf_path = self.session_path.joinpath('alf') @@ -506,12 +503,14 @@ def tearDown(self) -> None: # Delete weighings for w in self.one.alyx.rest('subjects', 'read', id=self.subject)['weighings']: self.one.alyx.rest('weighings', 'delete', id=w['url'].split('/')[-1]) + # Delete sessions + eid = self.one.search(subject=self.subject, date_range='2018-04-01', query_type='remote') + for ei in eid: + self.one.alyx.rest('sessions', 'delete', id=ei) @classmethod def tearDownClass(cls) -> None: - # Note: datasets deleted in cascade - for ses in cls.one.alyx.rest('sessions', 'list', subject=cls.subject, no_cache=True): - cls.one.alyx.rest('sessions', 'delete', id=ses['url'][-36:]) + # Note: sessions and datasets deleted in cascade cls.one.alyx.rest('subjects', 'delete', id=cls.subject) diff --git a/release_notes.md b/release_notes.md index 569aef684..291fd4055 100644 --- a/release_notes.md +++ b/release_notes.md @@ -16,6 +16,9 @@ - trainingPhaseChoiceWorld added to Bpod protocol extractor map fixture - Last trial of FPGA sessions now correctly extracted - Correct dynamic pipeline extraction of passive choice world trials +#### 2.27.1 +- Correct handling of missing TTLs in FpgaTrialsHabituation + ### other - Removed deprecated pyschofit module