|
11 | 11 | from packaging import version |
12 | 12 |
|
13 | 13 | from ibllib.plots.misc import squares, vertical_lines |
14 | | -from ibllib.io.raw_daq_loaders import (extract_sync_timeline, timeline_get_channel, |
15 | | - correct_counter_discontinuities, load_timeline_sync_and_chmap) |
| 14 | +from ibllib.io.raw_daq_loaders import ( |
| 15 | + extract_sync_timeline, timeline_get_channel, correct_counter_discontinuities, load_timeline_sync_and_chmap) |
16 | 16 | import ibllib.io.extractors.base as extractors_base |
17 | | -from ibllib.io.extractors.ephys_fpga import FpgaTrials, WHEEL_TICKS, WHEEL_RADIUS_CM, _assign_events_to_trial |
| 17 | +from ibllib.io.extractors.ephys_fpga import ( |
| 18 | + FpgaTrials, FpgaTrialsHabituation, WHEEL_TICKS, WHEEL_RADIUS_CM, _assign_events_to_trial) |
18 | 19 | from ibllib.io.extractors.training_wheel import extract_wheel_moves |
19 | 20 | from ibllib.io.extractors.camera import attribute_times |
20 | 21 | from brainbox.behavior.wheel import velocity_filtered |
@@ -157,7 +158,7 @@ def load_sync(self, sync_collection='raw_sync_data', chmap=None, **_): |
157 | 158 | return sync, chmap |
158 | 159 |
|
159 | 160 | def _extract(self, sync=None, chmap=None, sync_collection='raw_sync_data', **kwargs) -> dict: |
160 | | - trials = super()._extract(sync, chmap, sync_collection='raw_sync_data', **kwargs) |
| 161 | + trials = super()._extract(sync, chmap, sync_collection=sync_collection, **kwargs) |
161 | 162 | if kwargs.get('display', False): |
162 | 163 | plot_timeline(self.timeline, channels=chmap.keys(), raw=True) |
163 | 164 | return trials |
@@ -267,7 +268,8 @@ def assign_to_trial(events, take='last', starts=start_times, **kwargs): |
267 | 268 | # Extract valve open times from the DAQ |
268 | 269 | valve_driver_ttls = bpod_event_intervals['valve_open'] |
269 | 270 | correct = self.bpod_trials['feedbackType'] == 1 |
270 | | - # If there is a reward_valve channel, the valve has |
| 271 | + # If there is a reward_valve channel, the voltage across the valve has been recorded and |
| 272 | + # should give a more accurate readout of the valve's activity. |
271 | 273 | if any(ch['name'] == 'reward_valve' for ch in self.timeline['meta']['inputs']): |
272 | 274 | # TODO Let's look at the expected open length based on calibration and reward volume |
273 | 275 | # import scipy.interpolate |
@@ -600,6 +602,63 @@ def _assign_events_audio(self, audio_times, audio_polarities, display=False): |
600 | 602 | return t_ready_tone_in, t_error_tone_in |
601 | 603 |
|
602 | 604 |
|
| 605 | +class TimelineTrialsHabituation(FpgaTrialsHabituation, TimelineTrials): |
| 606 | + """Habituation trials extraction from Timeline DAQ data.""" |
| 607 | + |
| 608 | + sync_field = 'intervals_0' |
| 609 | + |
| 610 | + def build_trials(self, sync=None, chmap=None, **kwargs): |
| 611 | + """ |
| 612 | + Extract task related event times from the sync. |
| 613 | +
|
| 614 | + The valve used at the mesoscope has a way to record the raw voltage across the solenoid, |
| 615 | + giving a more accurate readout of the valve's activity. If the reward_valve channel is |
| 616 | + present on the DAQ, this is used to extract the valve open times. |
| 617 | +
|
| 618 | + Parameters |
| 619 | + ---------- |
| 620 | + sync : dict |
| 621 | + 'polarities' of fronts detected on sync trace for all 16 chans and their 'times' |
| 622 | + chmap : dict |
| 623 | + Map of channel names and their corresponding index. Default to constant. |
| 624 | +
|
| 625 | + Returns |
| 626 | + ------- |
| 627 | + dict |
| 628 | + A map of trial event timestamps. |
| 629 | + """ |
| 630 | + out = super().build_trials(sync, chmap, **kwargs) |
| 631 | + |
| 632 | + start_times = out['intervals'][:, 0] |
| 633 | + last_trial_end = out['intervals'][-1, 1] |
| 634 | + |
| 635 | + # Extract valve open times from the DAQ |
| 636 | + _, bpod_event_intervals = self.get_bpod_event_times(sync, chmap, **kwargs) |
| 637 | + bpod_feedback_times = self.bpod2fpga(self.bpod_trials['feedback_times']) |
| 638 | + valve_driver_ttls = bpod_event_intervals['valve_open'] |
| 639 | + # If there is a reward_valve channel, the voltage across the valve has been recorded and |
| 640 | + # should give a more accurate readout of the valve's activity. |
| 641 | + if any(ch['name'] == 'reward_valve' for ch in self.timeline['meta']['inputs']): |
| 642 | + # Use the driver TTLs to find the valve open times that correspond to the valve opening |
| 643 | + valve_intervals, valve_open_times = self.get_valve_open_times(driver_ttls=valve_driver_ttls) |
| 644 | + if valve_open_times.size != start_times.size: |
| 645 | + _logger.warning( |
| 646 | + 'Number of valve open times does not equal number of correct trials (%i != %i)', |
| 647 | + valve_open_times.size, start_times.size) |
| 648 | + else: |
| 649 | + # Use the valve controller TTLs recorded on the Bpod channel as the reward time |
| 650 | + valve_open_times = valve_driver_ttls[:, 0] |
| 651 | + # there may be an extra last trial that's not in the Bpod intervals as the extractor ignores the last trial |
| 652 | + valve_open_times = valve_open_times[valve_open_times <= last_trial_end] |
| 653 | + out['valveOpen_times'] = _assign_events_to_trial( |
| 654 | + bpod_feedback_times, valve_open_times, take='first', t_trial_end=out['intervals'][:, 1]) |
| 655 | + |
| 656 | + # Feedback times |
| 657 | + out['feedback_times'] = np.copy(out['valveOpen_times']) |
| 658 | + |
| 659 | + return out |
| 660 | + |
| 661 | + |
603 | 662 | class MesoscopeSyncTimeline(extractors_base.BaseExtractor): |
604 | 663 | """Extraction of mesoscope imaging times.""" |
605 | 664 |
|
|
0 commit comments