From 593f4f2f4fa08facf1a44dc26636e8279629fb60 Mon Sep 17 00:00:00 2001 From: Miles Wells Date: Wed, 6 Mar 2024 17:01:36 -0500 Subject: [PATCH] Support chained protocols in BehaviourPlots task --- ibllib/io/extractors/base.py | 3 ++ ibllib/io/raw_data_loaders.py | 17 ++++++++++ ibllib/pipes/behavior_tasks.py | 7 ++-- ibllib/plots/figures.py | 61 +++++++++++++++++++++++----------- release_notes.md | 1 + 5 files changed, 66 insertions(+), 23 deletions(-) diff --git a/ibllib/io/extractors/base.py b/ibllib/io/extractors/base.py index 1b3717a89..a41a15401 100644 --- a/ibllib/io/extractors/base.py +++ b/ibllib/io/extractors/base.py @@ -161,6 +161,9 @@ def extract(self, bpod_trials=None, settings=None, **kwargs): self.settings = {"IBLRIG_VERSION": "100.0.0"} elif self.settings.get("IBLRIG_VERSION", "") == "": self.settings["IBLRIG_VERSION"] = "100.0.0" + # Get all detected TTLs. These are stored for QC purposes + self.frame2ttl, self.audio = raw.load_bpod_fronts(self.session_path, data=self.bpod_trials) + return super(BaseBpodTrialsExtractor, self).extract(**kwargs) @property diff --git a/ibllib/io/raw_data_loaders.py b/ibllib/io/raw_data_loaders.py index ca9a83cca..36bed4abe 100644 --- a/ibllib/io/raw_data_loaders.py +++ b/ibllib/io/raw_data_loaders.py @@ -926,6 +926,23 @@ def patch_settings(session_path, collection='raw_behavior_data', ------- dict The modified settings. + + Examples + -------- + File is in /data/subject/2020-01-01/002/raw_behavior_data. Patch the file then move to new location. + >>> patch_settings('/data/subject/2020-01-01/002', number='001') + >>> shutil.move('/data/subject/2020-01-01/002/raw_behavior_data/', '/data/subject/2020-01-01/001/raw_behavior_data/') + + File is moved into new collection within the same session, then patched. + >>> shutil.move('./subject/2020-01-01/002/raw_task_data_00/', './subject/2020-01-01/002/raw_task_data_01/') + >>> patch_settings('/data/subject/2020-01-01/002', collection='raw_task_data_01', new_collection='raw_task_data_01') + + Update subject, date and number. + >>> new_session_path = Path('/data/foobar/2024-02-24/002') + >>> old_session_path = Path('/data/baz/2024-02-23/001') + >>> patch_settings(old_session_path, collection='raw_task_data_00', + ... subject=new_session_path.parts[-3], date=new_session_path.parts[-2], number=new_session_path.parts[-1]) + >>> shutil.move(old_session_path, new_session_path) """ settings = load_settings(session_path, collection) if not settings: diff --git a/ibllib/pipes/behavior_tasks.py b/ibllib/pipes/behavior_tasks.py index 001f3bfed..faa3c4423 100644 --- a/ibllib/pipes/behavior_tasks.py +++ b/ibllib/pipes/behavior_tasks.py @@ -311,6 +311,8 @@ def _run(self, update=True, save=True): def extract_behaviour(self, **kwargs): self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) + _logger.info('Bpod trials extractor: %s.%s', + self.extractor.__module__, self.extractor.__class__.__name__) self.extractor.default_path = self.output_collection return self.extractor.extract(task_collection=self.collection, **kwargs) @@ -453,12 +455,11 @@ def run_qc(self, trials_data=None, update=False, plot_qc=False, QC=None): if plot_qc: _logger.info('Creating Trials QC plots') try: - # TODO needs to be adapted for chained protocols session_id = self.one.path2eid(self.session_path) - plot_task = BehaviourPlots(session_id, self.session_path, one=self.one) + plot_task = BehaviourPlots( + session_id, self.session_path, one=self.one, task_collection=self.output_collection) _ = plot_task.run() self.plot_tasks.append(plot_task) - except Exception: _logger.error('Could not create Trials QC Plot') _logger.error(traceback.format_exc()) diff --git a/ibllib/plots/figures.py b/ibllib/plots/figures.py index 369709db1..e1977beb9 100644 --- a/ibllib/plots/figures.py +++ b/ibllib/plots/figures.py @@ -71,35 +71,58 @@ def remove_axis_outline(ax): class BehaviourPlots(ReportSnapshot): - """ - Behavioural plots - """ - - signature = { - 'input_files': [ - ('*trials.table.pqt', 'alf', True), - ], - 'output_files': [ - ('psychometric_curve.png', 'snapshot/behaviour', True), - ('chronometric_curve.png', 'snapshot/behaviour', True), - ('reaction_time_with_trials.png', 'snapshot/behaviour', True) - ] - } + """Behavioural plots.""" + + @property + def signature(self): + signature = { + 'input_files': [ + ('*trials.table.pqt', self.trials_collection, True), + ], + 'output_files': [ + ('psychometric_curve.png', 'snapshot/behaviour', True), + ('chronometric_curve.png', 'snapshot/behaviour', True), + ('reaction_time_with_trials.png', 'snapshot/behaviour', True) + ] + } + return signature def __init__(self, eid, session_path=None, one=None, **kwargs): + """ + Generate and upload behaviour plots. + + Parameters + ---------- + eid : str, uuid.UUID + An experiment UUID. + session_path : pathlib.Path + A session path. + one : one.api.One + An instance of ONE for registration to Alyx. + trials_collection : str + The location of the trials data (default: 'alf'). + kwargs + Arguments for ReportSnapshot constructor. + """ self.one = one self.eid = eid self.session_path = session_path or self.one.eid2path(self.eid) + self.trials_collection = kwargs.pop('trials_collection', 'alf') super(BehaviourPlots, self).__init__(self.session_path, self.eid, one=self.one, **kwargs) - self.output_directory = self.session_path.joinpath('snapshot', 'behaviour') + # Output directory should mirror trials collection, sans 'alf' part + self.output_directory = self.session_path.joinpath( + 'snapshot', 'behaviour', self.trials_collection.removeprefix('alf').strip('/')) self.output_directory.mkdir(exist_ok=True, parents=True) def _run(self): output_files = [] - trials = alfio.load_object(self.session_path.joinpath('alf'), 'trials') - title = '_'.join(list(self.session_path.parts[-3:])) + trials = alfio.load_object(self.session_path.joinpath(self.trials_collection), 'trials') + if self.one: + title = self.one.path2ref(self.session_path, as_dict=False) + else: + title = '_'.join(list(self.session_path.parts[-3:])) fig, ax = training.plot_psychometric(trials, title=title, figsize=(8, 6)) set_axis_label_size(ax) @@ -127,9 +150,7 @@ def _run(self): # TODO put into histology and alignment pipeline class HistologySlices(ReportSnapshotProbe): - """ - Plots coronal and sagittal slice showing electrode locations - """ + """Plots coronal and sagittal slice showing electrode locations.""" def _run(self): diff --git a/release_notes.md b/release_notes.md index 31418e07a..c88940bbf 100644 --- a/release_notes.md +++ b/release_notes.md @@ -8,6 +8,7 @@ #### 2.32.1 - FpgaTrials supports alignment of Bpod datasets not part of trials object +- Support chained protocols in BehaviourPlots task ## Release Notes 2.31