예제 #1
0
파일: task_qc.py 프로젝트: mschart/iblapps
    def __init__(self, session_path, bpod_only=False, local=False):
        """
        Loads and extracts the QC data for a given session path
        :param session_path: A str or Path to a Bpod session
        :param bpod_only: When True all data is extracted from Bpod instead of FPGA for ephys
        """
        super().__init__(session_path, one=one, log=_logger)

        if local:
            dsets, out_files = ephys_fpga.extract_all(session_path, save=True)
            self.extractor = TaskQCExtractor(session_path, lazy=True, one=one)
            # Extract extra datasets required for QC
            self.extractor.data = dsets
            self.extractor.extract_data()
            # Aggregate and update Alyx QC fields
            self.run(update=False)
        else:
            self.load_data(bpod_only=bpod_only)
            self.compute()
        self.n_trials = self.extractor.data['intervals'].shape[0]
        self.wheel_data = {
            're_pos': self.extractor.data.pop('wheel_position'),
            're_ts': self.extractor.data.pop('wheel_timestamps')
        }

        # Print failed
        outcome, results, outcomes = self.compute_session_status()
        map = {k: [] for k in set(outcomes.values())}
        for k, v in outcomes.items():
            map[v].append(k[6:])
        for k, v in map.items():
            if k == 'PASS':
                continue
            print(f'The following checks were labelled {k}:')
            print('\n'.join(v), '\n')

        # Make DataFrame from the trail level metrics
        def get_trial_level_failed(d):
            new_dict = {
                k[6:]: v
                for k, v in d.items()
                if isinstance(v, Sized) and len(v) == self.n_trials
            }
            return pd.DataFrame.from_dict(new_dict)

        self.frame = get_trial_level_failed(self.metrics)
        self.frame['intervals_0'] = self.extractor.data['intervals'][:, 0]
        self.frame['intervals_1'] = self.extractor.data['intervals'][:, 1]
        self.frame.insert(loc=0, column='trial_no', value=self.frame.index)
예제 #2
0
    def load_data(self, bpod_only=False, download_data=True):
        """Extract the data from raw data files
        Extracts all the required task data from the raw data files.

        :param bpod_only: if True no data is extracted from the FPGA for ephys sessions
        :param download_data: if True, any missing raw data is downloaded via ONE.
        """
        self.extractor = TaskQCExtractor(
            self.session_path, one=self.one, download_data=download_data, bpod_only=bpod_only)
    def test_partial_extraction(self):
        ex = TaskQCExtractor(self.session_path,
                             lazy=True,
                             one=self.one,
                             bpod_only=True)
        ex.extract_data()

        expected = [
            'contrastLeft', 'contrastRight', 'phase', 'position',
            'probabilityLeft', 'quiescence', 'stimOn_times'
        ]
        expected_5up = [
            'contrast', 'errorCueTrigger_times', 'itiIn_times',
            'stimFreezeTrigger_times', 'stimFreeze_times',
            'stimOffTrigger_times', 'stimOff_times', 'stimOnTrigger_times'
        ]
        if version.ge(ex.settings['IBLRIG_VERSION_TAG'], '5.0.0'):
            expected += expected_5up
        self.assertTrue(set(expected).issubset(set(ex.data.keys())))
    def test_download_data(self):
        """Test behavior when download_data flag is True
        """
        # path = self.one.path_from_eid(self.eid_incomplete)  # FIXME Returns None
        det = one.get_details(self.eid_incomplete)
        path = (Path(one._par.CACHE_DIR) / det['lab'] / 'Subjects' /
                det['subject'] / det['start_time'][:10] / str(det['number']))
        ex = TaskQCExtractor(path, lazy=True, one=self.one, download_data=True)
        self.assertTrue(ex.lazy, 'Failed to set lazy flag')

        shutil.rmtree(self.session_path)  # Remove downloaded data
        assert self.session_path.exists(
        ) is False, 'Failed to remove session folder'
        TaskQCExtractor(self.session_path,
                        lazy=True,
                        one=self.one,
                        download_data=True)
        files = list(self.session_path.rglob('*.*'))
        expected = 6  # NB This session is missing raw ephys data and missing some datasets
        self.assertEqual(len(files), expected)
예제 #5
0
    def _run(self):
        """
        Extracts an iblrig training session
        """
        trials, wheel, output_files = bpod_trials.extract_all(
            self.session_path, save=True)
        if trials is None:
            return None

        # Run the task QC
        # Compile task data for QC
        type = get_session_extractor_type(self.session_path)
        if type == 'habituation':
            qc = HabituationQC(self.session_path, one=self.one)
            qc.extractor = TaskQCExtractor(self.session_path, one=self.one)
        else:  # Update wheel data
            qc = TaskQC(self.session_path, one=self.one)
            qc.extractor = TaskQCExtractor(self.session_path, one=self.one)
            qc.extractor.wheel_encoding = 'X1'
        # Aggregate and update Alyx QC fields
        qc.run(update=True)
        return output_files
예제 #6
0
    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
예제 #7
0
 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
예제 #8
0
    def test_extraction(self):
        ex = TaskQCExtractor(self.session_path,
                             lazy=True,
                             one=self.one,
                             bpod_only=True,
                             download_data=True)
        self.assertIsNone(ex.raw_data)

        # Test loading raw data
        ex.load_raw_data()
        self.assertIsNotNone(ex.raw_data, 'Failed to load raw data')
        self.assertIsNotNone(ex.settings, 'Failed to load task settings')
        self.assertIsNotNone(ex.frame_ttls, 'Failed to load BNC1')
        self.assertIsNotNone(ex.audio_ttls, 'Failed to load BNC2')

        # Test extraction
        ex.extract_data()
        expected = [
            'choice', 'contrastLeft', 'contrastRight', 'correct',
            'errorCue_times', 'feedbackType', 'feedback_times',
            'firstMovement_times', 'goCueTrigger_times', 'goCue_times',
            'intervals', 'phase', 'position', 'probabilityLeft', 'quiescence',
            'response_times', 'rewardVolume', 'stimOn_times',
            'valveOpen_times', 'wheel_moves_intervals',
            'wheel_moves_peak_amplitude', 'wheel_position', 'wheel_timestamps'
        ]
        expected_5up = [
            'errorCueTrigger_times', 'itiIn_times', 'stimFreezeTrigger_times',
            'stimFreeze_times', 'stimOffTrigger_times', 'stimOff_times',
            'stimOnTrigger_times'
        ]
        expected += expected_5up

        self.assertTrue(
            len(set(expected).difference(set(ex.data.keys()))) == 0)
        self.assertEqual('ephys', ex.type)
        self.assertEqual('X1', ex.wheel_encoding)
예제 #9
0
    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)
예제 #10
0
파일: task_metrics.py 프로젝트: k1o0/ibllib
 def load_data(self, bpod_only=False, download_data=True):
     self.extractor = TaskQCExtractor(
         self.session_path, one=self.one, download_data=download_data, bpod_only=bpod_only)
예제 #11
0
    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)
예제 #12
0
class QcFrame(TaskQC):

    def __init__(self, session, bpod_only=False, local=False):
        """
        Loads and extracts the QC data for a given session path
        :param session: A str or Path to a session, or a session eid
        :param bpod_only: When True all data is extracted from Bpod instead of FPGA for ephys
        """
        super().__init__(session, one=one, log=_logger)

        if local:
            dsets, out_files = ephys_fpga.extract_all(session, save=True)
            self.extractor = TaskQCExtractor(session, lazy=True, one=one)
            # Extract extra datasets required for QC
            self.extractor.data = dsets
            self.extractor.extract_data()
            # Aggregate and update Alyx QC fields
            self.run(update=False)
        else:
            self.load_data(bpod_only=bpod_only)
            self.compute()
        self.n_trials = self.extractor.data['intervals'].shape[0]
        self.wheel_data = {'re_pos': self.extractor.data.pop('wheel_position'),
                           're_ts': self.extractor.data.pop('wheel_timestamps')}

        # Print failed
        outcome, results, outcomes = self.compute_session_status()
        map = {k: [] for k in set(outcomes.values())}
        for k, v in outcomes.items():
            map[v].append(k[6:])
        for k, v in map.items():
            if k == 'PASS':
                continue
            print(f'The following checks were labelled {k}:')
            print('\n'.join(v), '\n')

        # Make DataFrame from the trail level metrics
        def get_trial_level_failed(d):
            new_dict = {k[6:]: v for k, v in d.items() if
                        isinstance(v, Sized) and len(v) == self.n_trials}
            return pd.DataFrame.from_dict(new_dict)

        self.frame = get_trial_level_failed(self.metrics)
        self.frame['intervals_0'] = self.extractor.data['intervals'][:, 0]
        self.frame['intervals_1'] = self.extractor.data['intervals'][:, 1]
        self.frame.insert(loc=0, column='trial_no', value=self.frame.index)

    def create_plots(self, axes,
                     wheel_axes=None, trial_events=None, color_map=None, linestyle=None):
        """
        Plots the data for bnc1 (sound) and bnc2 (frame2ttl)
        :param axes: An axes handle on which to plot the TTL events
        :param wheel_axes: An axes handle on which to plot the wheel trace
        :param trial_events: A list of Bpod trial events to plot, e.g. ['stimFreeze_times'],
        if None, valve, sound and stimulus events are plotted
        :param color_map: A color map to use for the events, default is the tableau color map
        linestyle: A line style map to use for the events, default is random.
        :return: None
        """
        color_map = color_map or TABLEAU_COLORS.keys()
        if trial_events is None:
            # Default trial events to plot as vertical lines
            trial_events = [
                'goCue_times',
                'goCueTrigger_times',
                'feedback_times',
                'stimFreeze_times',
                'stimOff_times',
                'stimOn_times'
            ]

        plot_args = {
            'ymin': 0,
            'ymax': 4,
            'linewidth': 2,
            'ax': axes
        }

        bnc1 = self.extractor.frame_ttls
        bnc2 = self.extractor.audio_ttls
        trial_data = self.extractor.data

        plots.squares(bnc1['times'], bnc1['polarities'] * 0.4 + 1, ax=axes, color='k')
        plots.squares(bnc2['times'], bnc2['polarities'] * 0.4 + 2, ax=axes, color='k')
        linestyle = linestyle or random.choices(('-', '--', '-.', ':'), k=len(trial_events))

        if self.extractor.bpod_ttls is not None:
            bpttls = self.extractor.bpod_ttls
            plots.squares(bpttls['times'], bpttls['polarities'] * 0.4 + 3, ax=axes, color='k')
            plot_args['ymax'] = 4
            ylabels = ['', 'frame2ttl', 'sound', 'bpod', '']
        else:
            plot_args['ymax'] = 3
            ylabels = ['', 'frame2ttl', 'sound', '']

        for event, c, l in zip(trial_events, cycle(color_map), linestyle):
            plots.vertical_lines(trial_data[event], label=event, color=c, linestyle=l, **plot_args)

        axes.legend(loc='upper left', fontsize='xx-small', bbox_to_anchor=(1, 0.5))
        axes.set_yticklabels(ylabels)
        axes.set_yticks(list(range(plot_args['ymax'] + 1)))
        axes.set_ylim([0, plot_args['ymax']])

        if wheel_axes:
            wheel_plot_args = {
                'ax': wheel_axes,
                'ymin': self.wheel_data['re_pos'].min(),
                'ymax': self.wheel_data['re_pos'].max()}
            plot_args = {**plot_args, **wheel_plot_args}

            wheel_axes.plot(self.wheel_data['re_ts'], self.wheel_data['re_pos'], 'k-x')
            for event, c, ln in zip(trial_events, cycle(color_map), linestyle):
                plots.vertical_lines(trial_data[event],
                                     label=event, color=c, linestyle=ln, **plot_args)
 def test_lazy_extract(self):
     ex = TaskQCExtractor(self.session_path, lazy=True, one=self.one)
     self.assertIsNone(ex.data)