Coverage for ibllib/pipes/behavior_tasks.py: 84%
277 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-17 15:25 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-17 15:25 +0000
1"""Standard task protocol extractor dynamic pipeline tasks."""
2import logging
3import traceback
5from packaging import version
6import one.alf.io as alfio
7from one.alf.path import session_path_parts
8from one.api import ONE
10from ibllib.oneibl.registration import get_lab
11from ibllib.oneibl.data_handlers import ServerDataHandler, ExpectedDataset
12from ibllib.pipes import base_tasks
13from ibllib.io.raw_data_loaders import load_settings, load_bpod_fronts
14from ibllib.qc.task_extractors import TaskQCExtractor
15from ibllib.qc.task_metrics import HabituationQC, TaskQC, update_dataset_qc
16from ibllib.io.extractors.ephys_passive import PassiveChoiceWorld
17from ibllib.io.extractors.bpod_trials import get_bpod_extractor
18from ibllib.io.extractors.ephys_fpga import FpgaTrials, FpgaTrialsHabituation, get_sync_and_chn_map
19from ibllib.io.extractors.mesoscope import TimelineTrials
20from ibllib.pipes import training_status
21from ibllib.plots.figures import BehaviourPlots
23_logger = logging.getLogger(__name__)
26class HabituationRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
27 priority = 100
28 job_size = 'small'
30 @property
31 def signature(self):
32 signature = { 1C
33 'input_files': [],
34 'output_files': [
35 ('_iblrig_taskData.raw.*', self.collection, True),
36 ('_iblrig_taskSettings.raw.*', self.collection, True),
37 ('_iblrig_encoderEvents.raw*', self.collection, False),
38 ('_iblrig_encoderPositions.raw*', self.collection, False),
39 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
40 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
41 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
42 ('_iblrig_ambientSensorData.raw*', self.collection, False)
43 ]
44 }
45 return signature 1C
48class HabituationTrialsBpod(base_tasks.BehaviourTask):
49 priority = 90
50 job_size = 'small'
52 @property
53 def signature(self):
54 signature = { 1ns
55 'input_files': [
56 ('_iblrig_taskData.raw.*', self.collection, True),
57 ('_iblrig_taskSettings.raw.*', self.collection, True),
58 ],
59 'output_files': [
60 ('*trials.contrastLeft.npy', self.output_collection, True),
61 ('*trials.contrastRight.npy', self.output_collection, True),
62 ('*trials.feedback_times.npy', self.output_collection, True),
63 ('*trials.feedbackType.npy', self.output_collection, True),
64 ('*trials.goCue_times.npy', self.output_collection, True),
65 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
66 ('*trials.intervals.npy', self.output_collection, True),
67 ('*trials.rewardVolume.npy', self.output_collection, True),
68 ('*trials.stimOff_times.npy', self.output_collection, True),
69 ('*trials.stimOn_times.npy', self.output_collection, True),
70 ('*trials.stimOffTrigger_times.npy', self.output_collection, False),
71 ('*trials.stimOnTrigger_times.npy', self.output_collection, False),
72 ]
73 }
74 return signature 1ns
76 def _run(self, update=True, save=True):
77 """Extracts an iblrig training session."""
78 trials, output_files = self.extract_behaviour(save=save) 1ns
80 if trials is None: 1ns
81 return None 1s
82 if self.one is None or self.one.offline: 1n
83 return output_files 1n
85 # Run the task QC
86 self.run_qc(trials, update=update)
87 return output_files
89 def extract_behaviour(self, **kwargs):
90 settings = load_settings(self.session_path, self.collection) 1nbs
91 if version.parse(settings['IBLRIG_VERSION'] or '100.0.0') < version.parse('5.0.0'): 1nbs
92 _logger.error('No extraction of legacy habituation sessions') 1s
93 return None, None 1s
94 self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) 1nb
95 self.extractor.default_path = self.output_collection 1nb
96 return self.extractor.extract(task_collection=self.collection, **kwargs) 1nb
98 def run_qc(self, trials_data=None, update=True):
99 trials_data = self._assert_trials_data(trials_data) # validate trials data 1b
101 # Compile task data for QC
102 qc = HabituationQC(self.session_path, one=self.one) 1b
103 qc.extractor = TaskQCExtractor(self.session_path) 1b
105 # Update extractor fields
106 qc.extractor.data = qc.extractor.rename_data(trials_data.copy()) 1b
107 qc.extractor.frame_ttls = self.extractor.frame2ttl # used in iblapps QC viewer 1b
108 qc.extractor.audio_ttls = self.extractor.audio # used in iblapps QC viewer 1b
109 qc.extractor.settings = self.extractor.settings 1b
111 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1b
112 qc.run(update=update, namespace=namespace) 1b
113 return qc 1b
116class HabituationTrialsNidq(HabituationTrialsBpod):
117 priority = 90
118 job_size = 'small'
120 @property
121 def signature(self):
122 signature = super().signature
123 signature['input_files'] = [
124 ('_iblrig_taskData.raw.*', self.collection, True),
125 ('_iblrig_taskSettings.raw.*', self.collection, True),
126 (f'_{self.sync_namespace}_sync.channels.npy', self.sync_collection, True),
127 (f'_{self.sync_namespace}_sync.polarities.npy', self.sync_collection, True),
128 (f'_{self.sync_namespace}_sync.times.npy', self.sync_collection, True),
129 ('*wiring.json', self.sync_collection, False),
130 ('*.meta', self.sync_collection, True)]
131 return signature
133 def extract_behaviour(self, save=True, **kwargs):
134 """Extract the habituationChoiceWorld trial data using NI DAQ clock."""
135 # Extract Bpod trials
136 bpod_trials, _ = super().extract_behaviour(save=False, **kwargs) 1b
138 # Sync Bpod trials to FPGA
139 sync, chmap = get_sync_and_chn_map(self.session_path, self.sync_collection) 1b
140 self.extractor = FpgaTrialsHabituation( 1b
141 self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor)
143 # NB: The stimOff times are called stimCenter times for habituation choice world
144 outputs, files = self.extractor.extract( 1b
145 save=save, sync=sync, chmap=chmap, path_out=self.session_path.joinpath(self.output_collection),
146 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
147 return outputs, files 1b
149 def run_qc(self, trials_data=None, update=True, **_):
150 """Run and update QC.
152 This adds the bpod TTLs to the QC object *after* the QC is run in the super call method.
153 The raw Bpod TTLs are not used by the QC however they are used in the iblapps QC plot.
154 """
155 qc = super().run_qc(trials_data=trials_data, update=update) 1b
156 qc.extractor.bpod_ttls = self.extractor.bpod 1b
157 return qc 1b
160class TrialRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
161 priority = 100
162 job_size = 'small'
164 @property
165 def signature(self):
166 signature = { 1aDEFchijklgm
167 'input_files': [],
168 'output_files': [
169 ('_iblrig_taskData.raw.*', self.collection, True),
170 ('_iblrig_taskSettings.raw.*', self.collection, True),
171 ('_iblrig_encoderEvents.raw*', self.collection, False),
172 ('_iblrig_encoderPositions.raw*', self.collection, False),
173 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
174 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
175 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
176 ('_iblrig_ambientSensorData.raw*', self.collection, False)
177 ]
178 }
179 return signature 1aDEFchijklgm
182class PassiveRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
183 priority = 100
184 job_size = 'small'
186 @property
187 def signature(self):
188 signature = { 1aGhijklm
189 'input_files': [],
190 'output_files': [('_iblrig_taskSettings.raw.*', self.collection, True),
191 ('_iblrig_encoderEvents.raw*', self.collection, False),
192 ('_iblrig_encoderPositions.raw*', self.collection, False),
193 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
194 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
195 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
196 ('_iblrig_RFMapStim.raw*', self.collection, True)]
197 }
198 return signature 1aGhijklm
201class PassiveTaskNidq(base_tasks.BehaviourTask):
202 priority = 90
203 job_size = 'small'
205 @property
206 def signature(self):
207 signature = { 1ayzhijklmA
208 'input_files': [('_iblrig_taskSettings.raw*', self.collection, True),
209 ('_iblrig_RFMapStim.raw*', self.collection, True),
210 (f'_{self.sync_namespace}_sync.channels.*', self.sync_collection, True),
211 (f'_{self.sync_namespace}_sync.polarities.*', self.sync_collection, True),
212 (f'_{self.sync_namespace}_sync.times.*', self.sync_collection, True),
213 ('*.wiring.json', self.sync_collection, False),
214 ('*.meta', self.sync_collection, False)],
215 'output_files': [('_ibl_passiveGabor.table.csv', self.output_collection, False),
216 ('_ibl_passivePeriods.intervalsTable.csv', self.output_collection, True),
217 ('_ibl_passiveRFM.times.npy', self.output_collection, True),
218 ('_ibl_passiveStims.table.csv', self.output_collection, False)]
219 }
220 return signature 1ayzhijklmA
222 def _run(self, **kwargs):
223 """returns a list of pathlib.Paths. """
224 data, paths = PassiveChoiceWorld(self.session_path).extract( 1yzA
225 sync_collection=self.sync_collection, task_collection=self.collection, save=True,
226 path_out=self.session_path.joinpath(self.output_collection), protocol_number=self.protocol_number)
228 if any(x is None for x in paths): 1yzA
229 self.status = -1
231 return paths 1yzA
234class PassiveTaskTimeline(base_tasks.BehaviourTask, base_tasks.MesoscopeTask):
235 """TODO should be mesoscope invariant, using wiring file"""
236 priority = 90
237 job_size = 'small'
239 @property
240 def signature(self):
241 signature = {
242 'input_files': [('_iblrig_taskSettings.raw*', self.collection, True),
243 ('_iblrig_RFMapStim.raw*', self.collection, True),
244 (f'_{self.sync_namespace}_sync.channels.*', self.sync_collection, False),
245 (f'_{self.sync_namespace}_sync.polarities.*', self.sync_collection, False),
246 (f'_{self.sync_namespace}_sync.times.*', self.sync_collection, False)],
247 'output_files': [('_ibl_passiveGabor.table.csv', self.output_collection, False),
248 ('_ibl_passivePeriods.intervalsTable.csv', self.output_collection, True),
249 ('_ibl_passiveRFM.times.npy', self.output_collection, True),
250 ('_ibl_passiveStims.table.csv', self.output_collection, False)]
251 }
252 return signature
254 def _run(self, **kwargs):
255 """returns a list of pathlib.Paths.
256 This class exists to load the sync file and set the protocol_number to None
257 """
258 settings = load_settings(self.session_path, self.collection)
259 ver = settings.get('IBLRIG_VERSION') or '100.0.0'
260 if ver == '100.0.0' or version.parse(ver) <= version.parse('7.1.0'):
261 _logger.warning('Protocol spacers not supported; setting protocol_number to None')
262 self.protocol_number = None
264 sync, chmap = self.load_sync()
265 data, paths = PassiveChoiceWorld(self.session_path).extract(
266 sync_collection=self.sync_collection, task_collection=self.collection, save=True,
267 path_out=self.session_path.joinpath(self.output_collection),
268 protocol_number=self.protocol_number, sync=sync, sync_map=chmap)
270 if any(x is None for x in paths):
271 self.status = -1
273 return paths
276class ChoiceWorldTrialsBpod(base_tasks.BehaviourTask):
277 priority = 90
278 job_size = 'small'
279 extractor = None
280 """ibllib.io.extractors.base.BaseBpodTrialsExtractor: An instance of the Bpod trials extractor."""
282 @property
283 def signature(self):
284 signature = { 1HIopqcr
285 'input_files': [
286 ('_iblrig_taskData.raw.*', self.collection, True),
287 ('_iblrig_taskSettings.raw.*', self.collection, True),
288 ('_iblrig_encoderEvents.raw*', self.collection, True),
289 ('_iblrig_encoderPositions.raw*', self.collection, True)],
290 'output_files': [
291 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
292 ('*trials.stimOffTrigger_times.npy', self.output_collection, False),
293 ('*trials.stimOnTrigger_times.npy', self.output_collection, False),
294 ('*trials.table.pqt', self.output_collection, True),
295 ('*wheel.position.npy', self.output_collection, True),
296 ('*wheel.timestamps.npy', self.output_collection, True),
297 ('*wheelMoves.intervals.npy', self.output_collection, True),
298 ('*wheelMoves.peakAmplitude.npy', self.output_collection, True)
299 ]
300 }
301 return signature 1HIopqcr
303 def _run(self, update=True, save=True, **kwargs):
304 """Extracts an iblrig training session."""
305 trials, output_files = self.extract_behaviour(save=save) 1ofpqcdr
306 if trials is None: 1ofpqcdr
307 return None
308 if self.one is None or self.one.offline: 1ofpqcdr
309 return output_files 1ofpqdr
311 # Run the task QC
312 qc = self.run_qc(trials, update=update, **kwargs) 1c
313 if update and not self.one.offline: 1c
314 on_server = self.location == 'server' and isinstance(self.data_handler, ServerDataHandler) 1c
315 if not on_server: 1c
316 _logger.warning('Updating dataset QC only supported on local servers')
317 else:
318 labs = get_lab(self.session_path, self.one.alyx) 1c
319 # registered_dsets = self.register_datasets(labs=labs)
320 datasets = self.data_handler.uploadData(output_files, self.version, labs=labs) 1c
321 update_dataset_qc(qc, datasets, self.one) 1c
323 return output_files 1c
325 def extract_behaviour(self, **kwargs):
326 self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) 1aBofpqcdbetuvrwx
327 _logger.info('Bpod trials extractor: %s.%s', 1aBofpqcdbetuvrwx
328 self.extractor.__module__, self.extractor.__class__.__name__)
329 self.extractor.default_path = self.output_collection 1aBofpqcdbetuvrwx
330 return self.extractor.extract(task_collection=self.collection, **kwargs) 1aBofpqcdbetuvrwx
332 def run_qc(self, trials_data=None, update=True, QC=None):
333 """
334 Run the task QC.
336 Parameters
337 ----------
338 trials_data : dict
339 The complete extracted task data.
340 update : bool
341 If True, updates the session QC fields on Alyx.
342 QC : ibllib.qc.task_metrics.TaskQC
343 An optional QC class to instantiate.
345 Returns
346 -------
347 ibllib.qc.task_metrics.TaskQC
348 The task QC object.
349 """
350 trials_data = self._assert_trials_data(trials_data) # validate trials data 1ac
352 # Compile task data for QC
353 qc_extractor = TaskQCExtractor(self.session_path) 1ac
354 qc_extractor.data = qc_extractor.rename_data(trials_data) 1ac
355 if not QC: 1ac
356 QC = HabituationQC if type(self.extractor).__name__ == 'HabituationTrials' else TaskQC 1ac
357 _logger.debug('Running QC with %s.%s', QC.__module__, QC.__name__) 1ac
358 qc = QC(self.session_path, one=self.one, log=_logger) 1ac
359 if QC is not HabituationQC: 1ac
360 qc_extractor.wheel_encoding = 'X1' 1ac
361 qc_extractor.settings = self.extractor.settings 1ac
362 qc_extractor.frame_ttls, qc_extractor.audio_ttls = load_bpod_fronts( 1ac
363 self.session_path, task_collection=self.collection)
364 qc.extractor = qc_extractor 1ac
366 # Aggregate and update Alyx QC fields
367 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1ac
368 qc.run(update=update, namespace=namespace) 1ac
369 return qc 1ac
372class ChoiceWorldTrialsNidq(ChoiceWorldTrialsBpod):
373 priority = 90
374 job_size = 'small'
376 @property
377 def signature(self):
378 I = ExpectedDataset.input # noqa 1afhijklgmd
379 ns = self.sync_namespace 1afhijklgmd
380 # Neuropixels 3A sync data are kept in individual probe collections
381 v3A = ( 1afhijklgmd
382 I(f'_{ns}_sync.channels.probe??.npy', f'{self.sync_collection}/probe??', True, unique=False) &
383 I(f'_{ns}_sync.polarities.probe??.npy', f'{self.sync_collection}/probe??', True, unique=False) &
384 I(f'_{ns}_sync.times.probe??.npy', f'{self.sync_collection}/probe??', True, unique=False) &
385 I(f'_{ns}_*.ap.meta', f'{self.sync_collection}/probe??', True, unique=False) &
386 I(f'_{ns}_*wiring.json', f'{self.sync_collection}/probe??', False, unique=False)
387 )
388 # Neuropixels 3B sync data are kept in probe-independent datasets
389 v3B = ( 1afhijklgmd
390 I(f'_{ns}_sync.channels.npy', self.sync_collection, True) &
391 I(f'_{ns}_sync.polarities.npy', self.sync_collection, True) &
392 I(f'_{ns}_sync.times.npy', self.sync_collection, True) &
393 I(f'_{ns}_*.meta', self.sync_collection, True) &
394 I(f'_{ns}_*wiring.json', self.sync_collection, False)
395 )
396 signature = { 1afhijklgmd
397 'input_files': [
398 ('_iblrig_taskData.raw.*', self.collection, True),
399 ('_iblrig_taskSettings.raw.*', self.collection, True),
400 ('_iblrig_encoderEvents.raw*', self.collection, True),
401 ('_iblrig_encoderPositions.raw*', self.collection, True),
402 v3B | (~v3B & v3A) # either 3B datasets OR 3A datasets must be present
403 ],
404 'output_files': [
405 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
406 ('*trials.intervals_bpod.npy', self.output_collection, False),
407 ('*trials.stimOff_times.npy', self.output_collection, False),
408 ('*trials.stimOffTrigger_times.npy', self.output_collection, False),
409 ('*trials.stimOnTrigger_times.npy', self.output_collection, False),
410 ('*trials.table.pqt', self.output_collection, True),
411 ('*wheel.position.npy', self.output_collection, True),
412 ('*wheel.timestamps.npy', self.output_collection, True),
413 ('*wheelMoves.intervals.npy', self.output_collection, True),
414 ('*wheelMoves.peakAmplitude.npy', self.output_collection, True)
415 ]
416 }
417 return signature 1afhijklgmd
419 def _behaviour_criterion(self, update=True, truncate_to_pass=True):
420 """
421 Computes and update the behaviour criterion on Alyx
422 """
423 from brainbox.behavior import training
425 trials = alfio.load_object(self.session_path.joinpath(self.output_collection), 'trials').to_df()
426 good_enough, _ = training.criterion_delay(
427 n_trials=trials.shape[0],
428 perf_easy=training.compute_performance_easy(trials),
429 )
430 if truncate_to_pass and not good_enough:
431 n_trials = trials.shape[0]
432 while not good_enough and n_trials > 400:
433 n_trials -= 1
434 good_enough, _ = training.criterion_delay(
435 n_trials=n_trials,
436 perf_easy=training.compute_performance_easy(trials[:n_trials]),
437 )
439 if update:
440 eid = self.one.path2eid(self.session_path, query_type='remote')
441 self.one.alyx.json_field_update(
442 "sessions", eid, "extended_qc", {"behavior": int(good_enough)}
443 )
445 def extract_behaviour(self, save=True, **kwargs):
446 # Extract Bpod trials
447 bpod_trials, _ = super().extract_behaviour(save=False, **kwargs) 1fbetuvwx
449 # Sync Bpod trials to FPGA
450 sync, chmap = get_sync_and_chn_map(self.session_path, self.sync_collection) 1fbetuvwx
451 self.extractor = FpgaTrials(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1fbetuvwx
452 outputs, files = self.extractor.extract( 1fbetuvwx
453 save=save, sync=sync, chmap=chmap, path_out=self.session_path.joinpath(self.output_collection),
454 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
455 return outputs, files 1fbetuvwx
457 def run_qc(self, trials_data=None, update=False, plot_qc=False, QC=None):
458 trials_data = self._assert_trials_data(trials_data) # validate trials data 1be
460 # Compile task data for QC
461 qc_extractor = TaskQCExtractor(self.session_path) 1be
462 qc_extractor.data = qc_extractor.rename_data(trials_data.copy()) 1be
463 if not QC: 1be
464 QC = HabituationQC if type(self.extractor).__name__ == 'HabituationTrials' else TaskQC 1be
465 _logger.debug('Running QC with %s.%s', QC.__module__, QC.__name__) 1be
466 qc = QC(self.session_path, one=self.one, log=_logger) 1be
467 if QC is not HabituationQC: 1be
468 # Add Bpod wheel data
469 wheel_ts_bpod = self.extractor.bpod2fpga(self.extractor.bpod_trials['wheel_timestamps']) 1be
470 qc_extractor.data['wheel_timestamps_bpod'] = wheel_ts_bpod 1be
471 qc_extractor.data['wheel_position_bpod'] = self.extractor.bpod_trials['wheel_position'] 1be
472 qc_extractor.wheel_encoding = 'X4' 1be
473 qc_extractor.frame_ttls = self.extractor.frame2ttl 1be
474 qc_extractor.audio_ttls = self.extractor.audio 1be
475 qc_extractor.bpod_ttls = self.extractor.bpod 1be
476 qc_extractor.settings = self.extractor.settings 1be
477 qc.extractor = qc_extractor 1be
479 # Aggregate and update Alyx QC fields
480 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1be
481 qc.run(update=update, namespace=namespace) 1be
483 if plot_qc: 1be
484 _logger.info('Creating Trials QC plots')
485 try:
486 session_id = self.one.path2eid(self.session_path)
487 plot_task = BehaviourPlots(
488 session_id, self.session_path, one=self.one, task_collection=self.output_collection)
489 _ = plot_task.run()
490 self.plot_tasks.append(plot_task)
491 except Exception:
492 _logger.error('Could not create Trials QC Plot')
493 _logger.error(traceback.format_exc())
494 self.status = -1
495 return qc 1be
497 def _run(self, update=True, plot_qc=True, save=True):
498 output_files = super()._run(update=update, save=save, plot_qc=plot_qc) 1fd
499 if update and not self.one.offline: 1fd
500 self._behaviour_criterion(update=update)
502 return output_files 1fd
505class ChoiceWorldTrialsTimeline(ChoiceWorldTrialsNidq):
506 """Behaviour task extractor with DAQdata.raw NPY datasets."""
507 @property
508 def signature(self):
509 signature = super().signature 1gd
510 signature['input_files'] = [ 1gd
511 ('_iblrig_taskData.raw.*', self.collection, True),
512 ('_iblrig_taskSettings.raw.*', self.collection, True),
513 ('_iblrig_encoderEvents.raw*', self.collection, True),
514 ('_iblrig_encoderPositions.raw*', self.collection, True),
515 (f'_{self.sync_namespace}_DAQdata.raw.npy', self.sync_collection, True),
516 (f'_{self.sync_namespace}_DAQdata.timestamps.npy', self.sync_collection, True),
517 (f'_{self.sync_namespace}_DAQdata.meta.json', self.sync_collection, True),
518 ]
519 if self.protocol: 1gd
520 extractor = get_bpod_extractor(self.session_path, protocol=self.protocol) 1gd
521 if extractor.save_names: 1gd
522 signature['output_files'] = [(fn, self.output_collection, True) 1gd
523 for fn in filter(None, extractor.save_names)]
524 return signature 1gd
526 def extract_behaviour(self, save=True, **kwargs):
527 """Extract the Bpod trials data and Timeline acquired signals."""
528 # First determine the extractor from the task protocol
529 bpod_trials, _ = ChoiceWorldTrialsBpod.extract_behaviour(self, save=False, **kwargs) 1d
531 # Sync Bpod trials to DAQ
532 self.extractor = TimelineTrials(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1d
533 save_path = self.session_path / self.output_collection 1d
534 if not self._spacer_support(self.extractor.settings): 1d
535 _logger.warning('Protocol spacers not supported; setting protocol_number to None') 1d
536 self.protocol_number = None 1d
538 dsets, out_files = self.extractor.extract( 1d
539 save=save, path_out=save_path, sync_collection=self.sync_collection,
540 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
542 return dsets, out_files 1d
545class TrainingStatus(base_tasks.BehaviourTask):
546 priority = 90
547 job_size = 'small'
549 @property
550 def signature(self):
551 signature = { 1c
552 'input_files': [
553 ('_iblrig_taskData.raw.*', self.collection, True),
554 ('_iblrig_taskSettings.raw.*', self.collection, True),
555 ('*trials.table.pqt', self.output_collection, True)],
556 'output_files': []
557 }
558 return signature 1c
560 def _run(self, upload=True):
561 """
562 Extracts training status for subject.
563 """
565 lab = get_lab(self.session_path, self.one.alyx) 1c
566 if lab == 'cortexlab' and 'cortexlab' in self.one.alyx.base_url: 1c
567 _logger.info('Switching from cortexlab Alyx to IBL Alyx for training status queries.')
568 one = ONE(base_url='https://alyx.internationalbrainlab.org')
569 else:
570 one = self.one 1c
572 df = training_status.get_latest_training_information(self.session_path, one) 1c
573 if df is not None: 1c
574 training_status.make_plots( 1c
575 self.session_path, self.one, df=df, save=True, upload=upload, task_collection=self.collection)
576 # Update status map in JSON field of subjects endpoint
577 if self.one and not self.one.offline: 1c
578 _logger.debug('Updating JSON field of subjects endpoint') 1c
579 status = (df.set_index('date')[['training_status', 'session_path']].drop_duplicates( 1c
580 subset='training_status', keep='first').to_dict())
581 date, sess = status.items() 1c
582 data = {'trained_criteria': {v.replace(' ', '_'): (k, self.one.path2eid(sess[1][k])) 1c
583 for k, v in date[1].items()}}
584 _, subject, *_ = session_path_parts(self.session_path) 1c
585 self.one.alyx.json_field_update('subjects', subject, data=data) 1c
586 output_files = [] 1c
587 return output_files 1c