Coverage for ibllib/pipes/behavior_tasks.py: 85%
292 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-07 14:26 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-07 14:26 +0100
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, TimelineTrialsHabituation
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 = { 1D
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 1D
48class HabituationTrialsBpod(base_tasks.BehaviourTask):
49 priority = 90
50 job_size = 'small'
52 @property
53 def signature(self):
54 signature = { 1ogt
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 1ogt
76 def _run(self, update=True, save=True):
77 """Extracts an iblrig training session."""
78 trials, output_files = self.extract_behaviour(save=save) 1ot
80 if trials is None: 1ot
81 return None 1t
82 if self.one is None or self.one.offline: 1o
83 return output_files 1o
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) 1ogbt
91 if version.parse(settings['IBLRIG_VERSION'] or '100.0.0') < version.parse('5.0.0'): 1ogbt
92 _logger.error('No extraction of legacy habituation sessions') 1t
93 return None, None 1t
94 self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) 1ogb
95 self.extractor.default_path = self.output_collection 1ogb
96 return self.extractor.extract(task_collection=self.collection, **kwargs) 1ogb
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 1g
123 signature['input_files'] = [ 1g
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 1g
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 HabituationTrialsTimeline(HabituationTrialsNidq):
161 """Behaviour task extractor with DAQdata.raw NPY datasets."""
162 @property
163 def signature(self):
164 signature = super().signature 1g
165 signature['input_files'] = [ 1g
166 ('_iblrig_taskData.raw.*', self.collection, True),
167 ('_iblrig_taskSettings.raw.*', self.collection, True),
168 (f'_{self.sync_namespace}_DAQdata.raw.npy', self.sync_collection, True),
169 (f'_{self.sync_namespace}_DAQdata.timestamps.npy', self.sync_collection, True),
170 (f'_{self.sync_namespace}_DAQdata.meta.json', self.sync_collection, True),
171 ]
172 return signature 1g
174 def extract_behaviour(self, save=True, **kwargs):
175 """Extract the Bpod trials data and Timeline acquired signals."""
176 # First determine the extractor from the task protocol
177 bpod_trials, _ = HabituationTrialsBpod.extract_behaviour(self, save=False, **kwargs) 1g
179 # Sync Bpod trials to DAQ
180 self.extractor = TimelineTrialsHabituation(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1g
181 save_path = self.session_path / self.output_collection 1g
182 if not self._spacer_support(self.extractor.settings): 1g
183 _logger.warning('Protocol spacers not supported; setting protocol_number to None')
184 self.protocol_number = None
186 # NB: The stimOff times are called stimCenter times for habituation choice world
187 dsets, out_files = self.extractor.extract( 1g
188 save=save, path_out=save_path, sync_collection=self.sync_collection,
189 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
191 return dsets, out_files 1g
194class TrialRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
195 priority = 100
196 job_size = 'small'
198 @property
199 def signature(self):
200 signature = { 1aEFGcijklmhn
201 'input_files': [],
202 'output_files': [
203 ('_iblrig_taskData.raw.*', self.collection, True),
204 ('_iblrig_taskSettings.raw.*', self.collection, True),
205 ('_iblrig_encoderEvents.raw*', self.collection, False),
206 ('_iblrig_encoderPositions.raw*', self.collection, False),
207 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
208 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
209 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
210 ('_iblrig_ambientSensorData.raw*', self.collection, False)
211 ]
212 }
213 return signature 1aEFGcijklmhn
216class PassiveRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
217 priority = 100
218 job_size = 'small'
220 @property
221 def signature(self):
222 signature = { 1aHijklmn
223 'input_files': [],
224 'output_files': [('_iblrig_taskSettings.raw.*', self.collection, True),
225 ('_iblrig_encoderEvents.raw*', self.collection, False),
226 ('_iblrig_encoderPositions.raw*', self.collection, False),
227 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
228 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
229 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
230 ('_iblrig_RFMapStim.raw*', self.collection, True)]
231 }
232 return signature 1aHijklmn
235class PassiveTaskNidq(base_tasks.BehaviourTask):
236 priority = 90
237 job_size = 'small'
239 @property
240 def signature(self):
241 signature = { 1azAijklmnB
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, True),
245 (f'_{self.sync_namespace}_sync.polarities.*', self.sync_collection, True),
246 (f'_{self.sync_namespace}_sync.times.*', self.sync_collection, True),
247 ('*.wiring.json', self.sync_collection, False),
248 ('*.meta', self.sync_collection, False)],
249 'output_files': [('_ibl_passiveGabor.table.csv', self.output_collection, False),
250 ('_ibl_passivePeriods.intervalsTable.csv', self.output_collection, True),
251 ('_ibl_passiveRFM.times.npy', self.output_collection, True),
252 ('_ibl_passiveStims.table.csv', self.output_collection, False)]
253 }
254 return signature 1azAijklmnB
256 def _run(self, **kwargs):
257 """returns a list of pathlib.Paths. """
258 data, paths = PassiveChoiceWorld(self.session_path).extract( 1zAB
259 sync_collection=self.sync_collection, task_collection=self.collection, save=True,
260 path_out=self.session_path.joinpath(self.output_collection), protocol_number=self.protocol_number)
262 if any(x is None for x in paths): 1zAB
263 self.status = -1
265 return paths 1zAB
268class PassiveTaskTimeline(base_tasks.BehaviourTask, base_tasks.MesoscopeTask):
269 """TODO should be mesoscope invariant, using wiring file"""
270 priority = 90
271 job_size = 'small'
273 @property
274 def signature(self):
275 signature = {
276 'input_files': [('_iblrig_taskSettings.raw*', self.collection, True),
277 ('_iblrig_RFMapStim.raw*', self.collection, True),
278 (f'_{self.sync_namespace}_sync.channels.*', self.sync_collection, False),
279 (f'_{self.sync_namespace}_sync.polarities.*', self.sync_collection, False),
280 (f'_{self.sync_namespace}_sync.times.*', self.sync_collection, False)],
281 'output_files': [('_ibl_passiveGabor.table.csv', self.output_collection, False),
282 ('_ibl_passivePeriods.intervalsTable.csv', self.output_collection, True),
283 ('_ibl_passiveRFM.times.npy', self.output_collection, True),
284 ('_ibl_passiveStims.table.csv', self.output_collection, False)]
285 }
286 return signature
288 def _run(self, **kwargs):
289 """returns a list of pathlib.Paths.
290 This class exists to load the sync file and set the protocol_number to None
291 """
292 settings = load_settings(self.session_path, self.collection)
293 ver = settings.get('IBLRIG_VERSION') or '100.0.0'
294 if ver == '100.0.0' or version.parse(ver) <= version.parse('7.1.0'):
295 _logger.warning('Protocol spacers not supported; setting protocol_number to None')
296 self.protocol_number = None
298 sync, chmap = self.load_sync()
299 data, paths = PassiveChoiceWorld(self.session_path).extract(
300 sync_collection=self.sync_collection, task_collection=self.collection, save=True,
301 path_out=self.session_path.joinpath(self.output_collection),
302 protocol_number=self.protocol_number, sync=sync, sync_map=chmap)
304 if any(x is None for x in paths):
305 self.status = -1
307 return paths
310class ChoiceWorldTrialsBpod(base_tasks.BehaviourTask):
311 priority = 90
312 job_size = 'small'
313 extractor = None
314 """ibllib.io.extractors.base.BaseBpodTrialsExtractor: An instance of the Bpod trials extractor."""
316 @property
317 def signature(self):
318 signature = { 1IJpqrcs
319 'input_files': [
320 ('_iblrig_taskData.raw.*', self.collection, True),
321 ('_iblrig_taskSettings.raw.*', self.collection, True),
322 ('_iblrig_encoderEvents.raw*', self.collection, True),
323 ('_iblrig_encoderPositions.raw*', self.collection, True)],
324 'output_files': [
325 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
326 ('*trials.stimOffTrigger_times.npy', self.output_collection, False),
327 ('*trials.stimOnTrigger_times.npy', self.output_collection, False),
328 ('*trials.table.pqt', self.output_collection, True),
329 ('*wheel.position.npy', self.output_collection, True),
330 ('*wheel.timestamps.npy', self.output_collection, True),
331 ('*wheelMoves.intervals.npy', self.output_collection, True),
332 ('*wheelMoves.peakAmplitude.npy', self.output_collection, True)
333 ]
334 }
335 return signature 1IJpqrcs
337 def _run(self, update=True, save=True, **kwargs):
338 """Extracts an iblrig training session."""
339 trials, output_files = self.extract_behaviour(save=save) 1pfqrcds
340 if trials is None: 1pfqrcds
341 return None
342 if self.one is None or self.one.offline: 1pfqrcds
343 return output_files 1pfqrds
345 # Run the task QC
346 qc = self.run_qc(trials, update=update, **kwargs) 1c
347 if update and not self.one.offline: 1c
348 on_server = self.location == 'server' and isinstance(self.data_handler, ServerDataHandler) 1c
349 if not on_server: 1c
350 _logger.warning('Updating dataset QC only supported on local servers')
351 else:
352 labs = get_lab(self.session_path, self.one.alyx) 1c
353 # registered_dsets = self.register_datasets(labs=labs)
354 datasets = self.data_handler.uploadData(output_files, self.version, labs=labs) 1c
355 update_dataset_qc(qc, datasets, self.one) 1c
357 return output_files 1c
359 def extract_behaviour(self, **kwargs):
360 self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) 1aCpfqrcdbeuvwsxy
361 _logger.info('Bpod trials extractor: %s.%s', 1aCpfqrcdbeuvwsxy
362 self.extractor.__module__, self.extractor.__class__.__name__)
363 self.extractor.default_path = self.output_collection 1aCpfqrcdbeuvwsxy
364 return self.extractor.extract(task_collection=self.collection, **kwargs) 1aCpfqrcdbeuvwsxy
366 def run_qc(self, trials_data=None, update=True, QC=None):
367 """
368 Run the task QC.
370 Parameters
371 ----------
372 trials_data : dict
373 The complete extracted task data.
374 update : bool
375 If True, updates the session QC fields on Alyx.
376 QC : ibllib.qc.task_metrics.TaskQC
377 An optional QC class to instantiate.
379 Returns
380 -------
381 ibllib.qc.task_metrics.TaskQC
382 The task QC object.
383 """
384 trials_data = self._assert_trials_data(trials_data) # validate trials data 1ac
386 # Compile task data for QC
387 qc_extractor = TaskQCExtractor(self.session_path) 1ac
388 qc_extractor.data = qc_extractor.rename_data(trials_data) 1ac
389 if not QC: 1ac
390 QC = HabituationQC if type(self.extractor).__name__ == 'HabituationTrials' else TaskQC 1ac
391 _logger.debug('Running QC with %s.%s', QC.__module__, QC.__name__) 1ac
392 qc = QC(self.session_path, one=self.one, log=_logger) 1ac
393 if QC is not HabituationQC: 1ac
394 qc_extractor.wheel_encoding = 'X1' 1ac
395 qc_extractor.settings = self.extractor.settings 1ac
396 qc_extractor.frame_ttls, qc_extractor.audio_ttls = load_bpod_fronts( 1ac
397 self.session_path, task_collection=self.collection)
398 qc.extractor = qc_extractor 1ac
400 # Aggregate and update Alyx QC fields
401 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1ac
402 qc.run(update=update, namespace=namespace) 1ac
403 return qc 1ac
406class ChoiceWorldTrialsNidq(ChoiceWorldTrialsBpod):
407 priority = 90
408 job_size = 'small'
410 @property
411 def signature(self):
412 I = ExpectedDataset.input # noqa 1afijklmhnd
413 ns = self.sync_namespace 1afijklmhnd
414 # Neuropixels 3A sync data are kept in individual probe collections
415 v3A = ( 1afijklmhnd
416 I(f'_{ns}_sync.channels.probe??.npy', f'{self.sync_collection}/probe??', True, unique=False) &
417 I(f'_{ns}_sync.polarities.probe??.npy', f'{self.sync_collection}/probe??', True, unique=False) &
418 I(f'_{ns}_sync.times.probe??.npy', f'{self.sync_collection}/probe??', True, unique=False) &
419 I(f'_{ns}_*.ap.meta', f'{self.sync_collection}/probe??', True, unique=False) &
420 I(f'_{ns}_*wiring.json', f'{self.sync_collection}/probe??', False, unique=False)
421 )
422 # Neuropixels 3B sync data are kept in probe-independent datasets
423 v3B = ( 1afijklmhnd
424 I(f'_{ns}_sync.channels.npy', self.sync_collection, True) &
425 I(f'_{ns}_sync.polarities.npy', self.sync_collection, True) &
426 I(f'_{ns}_sync.times.npy', self.sync_collection, True) &
427 I(f'_{ns}_*.meta', self.sync_collection, True) &
428 I(f'_{ns}_*wiring.json', self.sync_collection, False)
429 )
430 signature = { 1afijklmhnd
431 'input_files': [
432 ('_iblrig_taskData.raw.*', self.collection, True),
433 ('_iblrig_taskSettings.raw.*', self.collection, True),
434 ('_iblrig_encoderEvents.raw*', self.collection, True),
435 ('_iblrig_encoderPositions.raw*', self.collection, True),
436 v3B | (~v3B & v3A) # either 3B datasets OR 3A datasets must be present
437 ],
438 'output_files': [
439 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
440 ('*trials.intervals_bpod.npy', self.output_collection, False),
441 ('*trials.stimOff_times.npy', self.output_collection, False),
442 ('*trials.stimOffTrigger_times.npy', self.output_collection, False),
443 ('*trials.stimOnTrigger_times.npy', self.output_collection, False),
444 ('*trials.table.pqt', self.output_collection, True),
445 ('*wheel.position.npy', self.output_collection, True),
446 ('*wheel.timestamps.npy', self.output_collection, True),
447 ('*wheelMoves.intervals.npy', self.output_collection, True),
448 ('*wheelMoves.peakAmplitude.npy', self.output_collection, True)
449 ]
450 }
451 return signature 1afijklmhnd
453 def _behaviour_criterion(self, update=True, truncate_to_pass=True):
454 """
455 Computes and update the behaviour criterion on Alyx
456 """
457 from brainbox.behavior import training
459 trials = alfio.load_object(self.session_path.joinpath(self.output_collection), 'trials').to_df()
460 good_enough, _ = training.criterion_delay(
461 n_trials=trials.shape[0],
462 perf_easy=training.compute_performance_easy(trials),
463 )
464 if truncate_to_pass and not good_enough:
465 n_trials = trials.shape[0]
466 while not good_enough and n_trials > 400:
467 n_trials -= 1
468 good_enough, _ = training.criterion_delay(
469 n_trials=n_trials,
470 perf_easy=training.compute_performance_easy(trials[:n_trials]),
471 )
473 if update:
474 eid = self.one.path2eid(self.session_path, query_type='remote')
475 self.one.alyx.json_field_update(
476 "sessions", eid, "extended_qc", {"behavior": int(good_enough)}
477 )
479 def extract_behaviour(self, save=True, **kwargs):
480 # Extract Bpod trials
481 bpod_trials, _ = super().extract_behaviour(save=False, **kwargs) 1fbeuvwxy
483 # Sync Bpod trials to FPGA
484 sync, chmap = get_sync_and_chn_map(self.session_path, self.sync_collection) 1fbeuvwxy
485 self.extractor = FpgaTrials(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1fbeuvwxy
486 outputs, files = self.extractor.extract( 1fbeuvwxy
487 save=save, sync=sync, chmap=chmap, path_out=self.session_path.joinpath(self.output_collection),
488 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
489 return outputs, files 1fbeuvwxy
491 def run_qc(self, trials_data=None, update=False, plot_qc=False, QC=None):
492 trials_data = self._assert_trials_data(trials_data) # validate trials data 1be
494 # Compile task data for QC
495 qc_extractor = TaskQCExtractor(self.session_path) 1be
496 qc_extractor.data = qc_extractor.rename_data(trials_data.copy()) 1be
497 if not QC: 1be
498 QC = HabituationQC if type(self.extractor).__name__ == 'HabituationTrials' else TaskQC 1be
499 _logger.debug('Running QC with %s.%s', QC.__module__, QC.__name__) 1be
500 qc = QC(self.session_path, one=self.one, log=_logger) 1be
501 if QC is not HabituationQC: 1be
502 # Add Bpod wheel data
503 wheel_ts_bpod = self.extractor.bpod2fpga(self.extractor.bpod_trials['wheel_timestamps']) 1be
504 qc_extractor.data['wheel_timestamps_bpod'] = wheel_ts_bpod 1be
505 qc_extractor.data['wheel_position_bpod'] = self.extractor.bpod_trials['wheel_position'] 1be
506 qc_extractor.wheel_encoding = 'X4' 1be
507 qc_extractor.frame_ttls = self.extractor.frame2ttl 1be
508 qc_extractor.audio_ttls = self.extractor.audio 1be
509 qc_extractor.bpod_ttls = self.extractor.bpod 1be
510 qc_extractor.settings = self.extractor.settings 1be
511 qc.extractor = qc_extractor 1be
513 # Aggregate and update Alyx QC fields
514 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1be
515 qc.run(update=update, namespace=namespace) 1be
517 if plot_qc: 1be
518 _logger.info('Creating Trials QC plots')
519 try:
520 session_id = self.one.path2eid(self.session_path)
521 plot_task = BehaviourPlots(
522 session_id, self.session_path, one=self.one, task_collection=self.output_collection)
523 _ = plot_task.run()
524 self.plot_tasks.append(plot_task)
525 except Exception:
526 _logger.error('Could not create Trials QC Plot')
527 _logger.error(traceback.format_exc())
528 self.status = -1
529 return qc 1be
531 def _run(self, update=True, plot_qc=True, save=True):
532 output_files = super()._run(update=update, save=save, plot_qc=plot_qc) 1fd
533 if update and not self.one.offline: 1fd
534 self._behaviour_criterion(update=update)
536 return output_files 1fd
539class ChoiceWorldTrialsTimeline(ChoiceWorldTrialsNidq):
540 """Behaviour task extractor with DAQdata.raw NPY datasets."""
541 @property
542 def signature(self):
543 signature = super().signature 1hd
544 signature['input_files'] = [ 1hd
545 ('_iblrig_taskData.raw.*', self.collection, True),
546 ('_iblrig_taskSettings.raw.*', self.collection, True),
547 ('_iblrig_encoderEvents.raw*', self.collection, True),
548 ('_iblrig_encoderPositions.raw*', self.collection, True),
549 (f'_{self.sync_namespace}_DAQdata.raw.npy', self.sync_collection, True),
550 (f'_{self.sync_namespace}_DAQdata.timestamps.npy', self.sync_collection, True),
551 (f'_{self.sync_namespace}_DAQdata.meta.json', self.sync_collection, True),
552 ]
553 if self.protocol: 1hd
554 extractor = get_bpod_extractor(self.session_path, protocol=self.protocol) 1hd
555 if extractor.save_names: 1hd
556 signature['output_files'] = [(fn, self.output_collection, True) 1hd
557 for fn in filter(None, extractor.save_names)]
558 return signature 1hd
560 def extract_behaviour(self, save=True, **kwargs):
561 """Extract the Bpod trials data and Timeline acquired signals."""
562 # First determine the extractor from the task protocol
563 bpod_trials, _ = ChoiceWorldTrialsBpod.extract_behaviour(self, save=False, **kwargs) 1d
565 # Sync Bpod trials to DAQ
566 self.extractor = TimelineTrials(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1d
567 save_path = self.session_path / self.output_collection 1d
568 if not self._spacer_support(self.extractor.settings): 1d
569 _logger.warning('Protocol spacers not supported; setting protocol_number to None') 1d
570 self.protocol_number = None 1d
572 dsets, out_files = self.extractor.extract( 1d
573 save=save, path_out=save_path, sync_collection=self.sync_collection,
574 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
576 return dsets, out_files 1d
579class TrainingStatus(base_tasks.BehaviourTask):
580 priority = 90
581 job_size = 'small'
583 @property
584 def signature(self):
585 signature = { 1c
586 'input_files': [
587 ('_iblrig_taskData.raw.*', self.collection, True),
588 ('_iblrig_taskSettings.raw.*', self.collection, True),
589 ('*trials.table.pqt', self.output_collection, True)],
590 'output_files': []
591 }
592 return signature 1c
594 def _run(self, upload=True):
595 """
596 Extracts training status for subject.
597 """
599 lab = get_lab(self.session_path, self.one.alyx) 1c
600 if lab == 'cortexlab' and 'cortexlab' in self.one.alyx.base_url: 1c
601 _logger.info('Switching from cortexlab Alyx to IBL Alyx for training status queries.')
602 one = ONE(base_url='https://alyx.internationalbrainlab.org')
603 else:
604 one = self.one 1c
606 df = training_status.get_latest_training_information(self.session_path, one) 1c
607 if df is not None: 1c
608 training_status.make_plots( 1c
609 self.session_path, self.one, df=df, save=True, upload=upload, task_collection=self.collection)
610 # Update status map in JSON field of subjects endpoint
611 if self.one and not self.one.offline: 1c
612 _logger.debug('Updating JSON field of subjects endpoint') 1c
613 status = (df.set_index('date')[['training_status', 'session_path']].drop_duplicates( 1c
614 subset='training_status', keep='first').to_dict())
615 date, sess = status.items() 1c
616 data = {'trained_criteria': {v.replace(' ', '_'): (k, self.one.path2eid(sess[1][k])) 1c
617 for k, v in date[1].items()}}
618 _, subject, *_ = session_path_parts(self.session_path) 1c
619 self.one.alyx.json_field_update('subjects', subject, data=data) 1c
620 output_files = [] 1c
621 return output_files 1c