Coverage for ibllib/pipes/behavior_tasks.py: 84%
269 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-08 17:16 +0100
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-08 17:16 +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.files import session_path_parts
8from one.api import ONE
10from ibllib.oneibl.registration import get_lab
11from ibllib.oneibl.data_handlers import ServerDataHandler
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('ibllib')
26class HabituationRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
27 priority = 100
28 job_size = 'small'
30 @property
31 def signature(self):
32 signature = { 1z
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 1z
48class HabituationTrialsBpod(base_tasks.BehaviourTask):
49 priority = 90
50 job_size = 'small'
52 @property
53 def signature(self):
54 signature = { 1r
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.stimOnTrigger_times.npy', self.output_collection, True),
71 ]
72 }
73 return signature 1r
75 def _run(self, update=True, save=True):
76 """Extracts an iblrig training session."""
77 trials, output_files = self.extract_behaviour(save=save) 1r
79 if trials is None: 1r
80 return None
81 if self.one is None or self.one.offline: 1r
82 return output_files 1r
84 # Run the task QC
85 self.run_qc(trials, update=update)
86 return output_files
88 def extract_behaviour(self, **kwargs):
89 self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) 1rb
90 self.extractor.default_path = self.output_collection 1rb
91 return self.extractor.extract(task_collection=self.collection, **kwargs) 1rb
93 def run_qc(self, trials_data=None, update=True):
94 trials_data = self._assert_trials_data(trials_data) # validate trials data 1b
96 # Compile task data for QC
97 qc = HabituationQC(self.session_path, one=self.one) 1b
98 qc.extractor = TaskQCExtractor(self.session_path, lazy=True, sync_collection=self.sync_collection, 1b
99 one=self.one, sync_type=self.sync, task_collection=self.collection)
101 # Update extractor fields
102 qc.extractor.data = qc.extractor.rename_data(trials_data.copy()) 1b
103 qc.extractor.frame_ttls = self.extractor.frame2ttl # used in iblapps QC viewer 1b
104 qc.extractor.audio_ttls = self.extractor.audio # used in iblapps QC viewer 1b
105 qc.extractor.settings = self.extractor.settings 1b
107 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1b
108 qc.run(update=update, namespace=namespace) 1b
109 return qc 1b
112class HabituationTrialsNidq(HabituationTrialsBpod):
113 priority = 90
114 job_size = 'small'
116 @property
117 def signature(self):
118 signature = super().signature
119 signature['input_files'] = [
120 ('_iblrig_taskData.raw.*', self.collection, True),
121 ('_iblrig_taskSettings.raw.*', self.collection, True),
122 (f'_{self.sync_namespace}_sync.channels.npy', self.sync_collection, True),
123 (f'_{self.sync_namespace}_sync.polarities.npy', self.sync_collection, True),
124 (f'_{self.sync_namespace}_sync.times.npy', self.sync_collection, True),
125 ('*wiring.json', self.sync_collection, False),
126 ('*.meta', self.sync_collection, True)]
127 return signature
129 def extract_behaviour(self, save=True, **kwargs):
130 """Extract the habituationChoiceWorld trial data using NI DAQ clock."""
131 # Extract Bpod trials
132 bpod_trials, _ = super().extract_behaviour(save=False, **kwargs) 1b
134 # Sync Bpod trials to FPGA
135 sync, chmap = get_sync_and_chn_map(self.session_path, self.sync_collection) 1b
136 self.extractor = FpgaTrialsHabituation( 1b
137 self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor)
139 # NB: The stimOff times are called stimCenter times for habituation choice world
140 outputs, files = self.extractor.extract( 1b
141 save=save, sync=sync, chmap=chmap, path_out=self.session_path.joinpath(self.output_collection),
142 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
143 return outputs, files 1b
145 def run_qc(self, trials_data=None, update=True, **_):
146 """Run and update QC.
148 This adds the bpod TTLs to the QC object *after* the QC is run in the super call method.
149 The raw Bpod TTLs are not used by the QC however they are used in the iblapps QC plot.
150 """
151 qc = super().run_qc(trials_data=trials_data, update=update) 1b
152 qc.extractor.bpod_ttls = self.extractor.bpod 1b
153 return qc 1b
156class TrialRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
157 priority = 100
158 job_size = 'small'
160 @property
161 def signature(self):
162 signature = { 1aABCcijklmhnd
163 'input_files': [],
164 'output_files': [
165 ('_iblrig_taskData.raw.*', self.collection, True),
166 ('_iblrig_taskSettings.raw.*', self.collection, True),
167 ('_iblrig_encoderEvents.raw*', self.collection, False),
168 ('_iblrig_encoderPositions.raw*', self.collection, False),
169 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
170 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
171 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
172 ('_iblrig_ambientSensorData.raw*', self.collection, False)
173 ]
174 }
175 return signature 1aABCcijklmhnd
178class PassiveRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask):
179 priority = 100
180 job_size = 'small'
182 @property
183 def signature(self):
184 signature = { 1aDijklmhn
185 'input_files': [],
186 'output_files': [('_iblrig_taskSettings.raw.*', self.collection, True),
187 ('_iblrig_encoderEvents.raw*', self.collection, False),
188 ('_iblrig_encoderPositions.raw*', self.collection, False),
189 ('_iblrig_encoderTrialInfo.raw*', self.collection, False),
190 ('_iblrig_stimPositionScreen.raw*', self.collection, False),
191 ('_iblrig_syncSquareUpdate.raw*', self.collection, False),
192 ('_iblrig_RFMapStim.raw*', self.collection, True)]
193 }
194 return signature 1aDijklmhn
197class PassiveTaskNidq(base_tasks.BehaviourTask):
198 priority = 90
199 job_size = 'small'
201 @property
202 def signature(self):
203 signature = { 1awxijklmny
204 'input_files': [('_iblrig_taskSettings.raw*', self.collection, True),
205 ('_iblrig_RFMapStim.raw*', self.collection, True),
206 (f'_{self.sync_namespace}_sync.channels.*', self.sync_collection, True),
207 (f'_{self.sync_namespace}_sync.polarities.*', self.sync_collection, True),
208 (f'_{self.sync_namespace}_sync.times.*', self.sync_collection, True),
209 ('*.wiring.json', self.sync_collection, False),
210 ('*.meta', self.sync_collection, False)],
211 'output_files': [('_ibl_passiveGabor.table.csv', self.output_collection, False),
212 ('_ibl_passivePeriods.intervalsTable.csv', self.output_collection, True),
213 ('_ibl_passiveRFM.times.npy', self.output_collection, True),
214 ('_ibl_passiveStims.table.csv', self.output_collection, False)]
215 }
216 return signature 1awxijklmny
218 def _run(self, **kwargs):
219 """returns a list of pathlib.Paths. """
220 data, paths = PassiveChoiceWorld(self.session_path).extract( 1wxy
221 sync_collection=self.sync_collection, task_collection=self.collection, save=True,
222 path_out=self.session_path.joinpath(self.output_collection), protocol_number=self.protocol_number)
224 if any(x is None for x in paths): 1wxy
225 self.status = -1
227 return paths 1wxy
230class PassiveTaskTimeline(base_tasks.BehaviourTask, base_tasks.MesoscopeTask):
231 """TODO should be mesoscope invariant, using wiring file"""
232 priority = 90
233 job_size = 'small'
235 @property
236 def signature(self):
237 signature = { 1h
238 'input_files': [('_iblrig_taskSettings.raw*', self.collection, True),
239 ('_iblrig_RFMapStim.raw*', self.collection, True),
240 (f'_{self.sync_namespace}_sync.channels.*', self.sync_collection, False),
241 (f'_{self.sync_namespace}_sync.polarities.*', self.sync_collection, False),
242 (f'_{self.sync_namespace}_sync.times.*', self.sync_collection, False)],
243 'output_files': [('_ibl_passiveGabor.table.csv', self.output_collection, False),
244 ('_ibl_passivePeriods.intervalsTable.csv', self.output_collection, True),
245 ('_ibl_passiveRFM.times.npy', self.output_collection, True),
246 ('_ibl_passiveStims.table.csv', self.output_collection, False)]
247 }
248 return signature 1h
250 def _run(self, **kwargs):
251 """returns a list of pathlib.Paths.
252 This class exists to load the sync file and set the protocol_number to None
253 """
254 settings = load_settings(self.session_path, self.collection)
255 ver = settings.get('IBLRIG_VERSION') or '100.0.0'
256 if ver == '100.0.0' or version.parse(ver) <= version.parse('7.1.0'):
257 _logger.warning('Protocol spacers not supported; setting protocol_number to None')
258 self.protocol_number = None
260 sync, chmap = self.load_sync()
261 data, paths = PassiveChoiceWorld(self.session_path).extract(
262 sync_collection=self.sync_collection, task_collection=self.collection, save=True,
263 path_out=self.session_path.joinpath(self.output_collection),
264 protocol_number=self.protocol_number, sync=sync, sync_map=chmap)
266 if any(x is None for x in paths):
267 self.status = -1
269 return paths
272class ChoiceWorldTrialsBpod(base_tasks.BehaviourTask):
273 priority = 90
274 job_size = 'small'
275 extractor = None
276 """ibllib.io.extractors.base.BaseBpodTrialsExtractor: An instance of the Bpod trials extractor."""
278 @property
279 def signature(self):
280 signature = { 1opqcd
281 'input_files': [
282 ('_iblrig_taskData.raw.*', self.collection, True),
283 ('_iblrig_taskSettings.raw.*', self.collection, True),
284 ('_iblrig_encoderEvents.raw*', self.collection, True),
285 ('_iblrig_encoderPositions.raw*', self.collection, True)],
286 'output_files': [
287 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
288 ('*trials.stimOnTrigger_times.npy', self.output_collection, False),
289 ('*trials.table.pqt', self.output_collection, True),
290 ('*wheel.position.npy', self.output_collection, True),
291 ('*wheel.timestamps.npy', self.output_collection, True),
292 ('*wheelMoves.intervals.npy', self.output_collection, True),
293 ('*wheelMoves.peakAmplitude.npy', self.output_collection, True)
294 ]
295 }
296 return signature 1opqcd
298 def _run(self, update=True, save=True, **kwargs):
299 """Extracts an iblrig training session."""
300 trials, output_files = self.extract_behaviour(save=save) 1ogpqcfd
301 if trials is None: 1ogpqcfd
302 return None
303 if self.one is None or self.one.offline: 1ogpqcfd
304 return output_files 1ogpqf
306 # Run the task QC
307 qc = self.run_qc(trials, update=update, **kwargs) 1cd
308 if update and not self.one.offline: 1cd
309 on_server = self.location == 'server' and isinstance(self.data_handler, ServerDataHandler) 1cd
310 if not on_server: 1cd
311 _logger.warning('Updating dataset QC only supported on local servers')
312 else:
313 labs = get_lab(self.session_path, self.one.alyx) 1cd
314 # registered_dsets = self.register_datasets(labs=labs)
315 datasets = self.data_handler.uploadData(output_files, self.version, labs=labs) 1cd
316 update_dataset_qc(qc, datasets, self.one) 1cd
318 return output_files 1cd
320 def extract_behaviour(self, **kwargs):
321 self.extractor = get_bpod_extractor(self.session_path, task_collection=self.collection) 1ogpqcfbestduv
322 _logger.info('Bpod trials extractor: %s.%s', 1ogpqcfbestduv
323 self.extractor.__module__, self.extractor.__class__.__name__)
324 self.extractor.default_path = self.output_collection 1ogpqcfbestduv
325 return self.extractor.extract(task_collection=self.collection, **kwargs) 1ogpqcfbestduv
327 def run_qc(self, trials_data=None, update=True, QC=None):
328 """
329 Run the task QC.
331 Parameters
332 ----------
333 trials_data : dict
334 The complete extracted task data.
335 update : bool
336 If True, updates the session QC fields on Alyx.
337 QC : ibllib.qc.task_metrics.TaskQC
338 An optional QC class to instantiate.
340 Returns
341 -------
342 ibllib.qc.task_metrics.TaskQC
343 The task QC object.
344 """
345 trials_data = self._assert_trials_data(trials_data) # validate trials data 1cd
347 # Compile task data for QC
348 qc_extractor = TaskQCExtractor(self.session_path, lazy=True, sync_collection=self.sync_collection, one=self.one, 1cd
349 sync_type=self.sync, task_collection=self.collection)
350 qc_extractor.data = qc_extractor.rename_data(trials_data) 1cd
351 if not QC: 1cd
352 QC = HabituationQC if type(self.extractor).__name__ == 'HabituationTrials' else TaskQC 1cd
353 _logger.debug('Running QC with %s.%s', QC.__module__, QC.__name__) 1cd
354 qc = QC(self.session_path, one=self.one, log=_logger) 1cd
355 if QC is not HabituationQC: 1cd
356 qc_extractor.wheel_encoding = 'X1' 1cd
357 qc_extractor.settings = self.extractor.settings 1cd
358 qc_extractor.frame_ttls, qc_extractor.audio_ttls = load_bpod_fronts( 1cd
359 self.session_path, task_collection=self.collection)
360 qc.extractor = qc_extractor 1cd
362 # Aggregate and update Alyx QC fields
363 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1cd
364 qc.run(update=update, namespace=namespace) 1cd
365 return qc 1cd
368class ChoiceWorldTrialsNidq(ChoiceWorldTrialsBpod):
369 priority = 90
370 job_size = 'small'
372 @property
373 def signature(self):
374 signature = { 1agijklmhnf
375 'input_files': [
376 ('_iblrig_taskData.raw.*', self.collection, True),
377 ('_iblrig_taskSettings.raw.*', self.collection, True),
378 ('_iblrig_encoderEvents.raw*', self.collection, True),
379 ('_iblrig_encoderPositions.raw*', self.collection, True),
380 (f'_{self.sync_namespace}_sync.channels.npy', self.sync_collection, True),
381 (f'_{self.sync_namespace}_sync.polarities.npy', self.sync_collection, True),
382 (f'_{self.sync_namespace}_sync.times.npy', self.sync_collection, True),
383 ('*wiring.json', self.sync_collection, False),
384 ('*.meta', self.sync_collection, True)],
385 'output_files': [
386 ('*trials.goCueTrigger_times.npy', self.output_collection, True),
387 ('*trials.intervals_bpod.npy', self.output_collection, False),
388 ('*trials.stimOff_times.npy', self.output_collection, False),
389 ('*trials.table.pqt', self.output_collection, True),
390 ('*wheel.position.npy', self.output_collection, True),
391 ('*wheel.timestamps.npy', self.output_collection, True),
392 ('*wheelMoves.intervals.npy', self.output_collection, True),
393 ('*wheelMoves.peakAmplitude.npy', self.output_collection, True)
394 ]
395 }
396 return signature 1agijklmhnf
398 def _behaviour_criterion(self, update=True, truncate_to_pass=True):
399 """
400 Computes and update the behaviour criterion on Alyx
401 """
402 from brainbox.behavior import training
404 trials = alfio.load_object(self.session_path.joinpath(self.output_collection), 'trials').to_df()
405 good_enough = training.criterion_delay(
406 n_trials=trials.shape[0],
407 perf_easy=training.compute_performance_easy(trials),
408 )
409 if truncate_to_pass and not good_enough:
410 n_trials = trials.shape[0]
411 while not good_enough and n_trials > 400:
412 n_trials -= 1
413 good_enough = training.criterion_delay(
414 n_trials=n_trials,
415 perf_easy=training.compute_performance_easy(trials[:n_trials]),
416 )
418 if update:
419 eid = self.one.path2eid(self.session_path, query_type='remote')
420 self.one.alyx.json_field_update(
421 "sessions", eid, "extended_qc", {"behavior": int(good_enough)}
422 )
424 def extract_behaviour(self, save=True, **kwargs):
425 # Extract Bpod trials
426 bpod_trials, _ = super().extract_behaviour(save=False, **kwargs) 1gbestuv
428 # Sync Bpod trials to FPGA
429 sync, chmap = get_sync_and_chn_map(self.session_path, self.sync_collection) 1gbestuv
430 self.extractor = FpgaTrials(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1gbestuv
431 outputs, files = self.extractor.extract( 1gbestuv
432 save=save, sync=sync, chmap=chmap, path_out=self.session_path.joinpath(self.output_collection),
433 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
434 return outputs, files 1gbestuv
436 def run_qc(self, trials_data=None, update=False, plot_qc=False, QC=None):
437 trials_data = self._assert_trials_data(trials_data) # validate trials data 1be
439 # Compile task data for QC
440 qc_extractor = TaskQCExtractor(self.session_path, lazy=True, sync_collection=self.sync_collection, one=self.one, 1be
441 sync_type=self.sync, task_collection=self.collection)
442 qc_extractor.data = qc_extractor.rename_data(trials_data.copy()) 1be
443 if not QC: 1be
444 QC = HabituationQC if type(self.extractor).__name__ == 'HabituationTrials' else TaskQC 1be
445 _logger.debug('Running QC with %s.%s', QC.__module__, QC.__name__) 1be
446 qc = QC(self.session_path, one=self.one, log=_logger) 1be
447 if QC is not HabituationQC: 1be
448 # Add Bpod wheel data
449 wheel_ts_bpod = self.extractor.bpod2fpga(self.extractor.bpod_trials['wheel_timestamps']) 1be
450 qc_extractor.data['wheel_timestamps_bpod'] = wheel_ts_bpod 1be
451 qc_extractor.data['wheel_position_bpod'] = self.extractor.bpod_trials['wheel_position'] 1be
452 qc_extractor.wheel_encoding = 'X4' 1be
453 qc_extractor.frame_ttls = self.extractor.frame2ttl 1be
454 qc_extractor.audio_ttls = self.extractor.audio 1be
455 qc_extractor.bpod_ttls = self.extractor.bpod 1be
456 qc_extractor.settings = self.extractor.settings 1be
457 qc.extractor = qc_extractor 1be
459 # Aggregate and update Alyx QC fields
460 namespace = 'task' if self.protocol_number is None else f'task_{self.protocol_number:02}' 1be
461 qc.run(update=update, namespace=namespace) 1be
463 if plot_qc: 1be
464 _logger.info('Creating Trials QC plots')
465 try:
466 session_id = self.one.path2eid(self.session_path)
467 plot_task = BehaviourPlots(
468 session_id, self.session_path, one=self.one, task_collection=self.output_collection)
469 _ = plot_task.run()
470 self.plot_tasks.append(plot_task)
471 except Exception:
472 _logger.error('Could not create Trials QC Plot')
473 _logger.error(traceback.format_exc())
474 self.status = -1
475 return qc 1be
477 def _run(self, update=True, plot_qc=True, save=True):
478 output_files = super()._run(update=update, save=save, plot_qc=plot_qc) 1gf
479 if update and not self.one.offline: 1gf
480 self._behaviour_criterion(update=update)
482 return output_files 1gf
485class ChoiceWorldTrialsTimeline(ChoiceWorldTrialsNidq):
486 """Behaviour task extractor with DAQdata.raw NPY datasets."""
487 @property
488 def signature(self):
489 signature = super().signature 1hf
490 signature['input_files'] = [ 1hf
491 ('_iblrig_taskData.raw.*', self.collection, True),
492 ('_iblrig_taskSettings.raw.*', self.collection, True),
493 ('_iblrig_encoderEvents.raw*', self.collection, True),
494 ('_iblrig_encoderPositions.raw*', self.collection, True),
495 (f'_{self.sync_namespace}_DAQdata.raw.npy', self.sync_collection, True),
496 (f'_{self.sync_namespace}_DAQdata.timestamps.npy', self.sync_collection, True),
497 (f'_{self.sync_namespace}_DAQdata.meta.json', self.sync_collection, True),
498 ]
499 if self.protocol: 1hf
500 extractor = get_bpod_extractor(self.session_path, protocol=self.protocol) 1hf
501 if extractor.save_names: 1hf
502 signature['output_files'] = [(fn, self.output_collection, True) 1hf
503 for fn in filter(None, extractor.save_names)]
504 return signature 1hf
506 def extract_behaviour(self, save=True, **kwargs):
507 """Extract the Bpod trials data and Timeline acquired signals."""
508 # First determine the extractor from the task protocol
509 bpod_trials, _ = ChoiceWorldTrialsBpod.extract_behaviour(self, save=False, **kwargs) 1f
511 # Sync Bpod trials to DAQ
512 self.extractor = TimelineTrials(self.session_path, bpod_trials=bpod_trials, bpod_extractor=self.extractor) 1f
513 save_path = self.session_path / self.output_collection 1f
514 if not self._spacer_support(self.extractor.settings): 1f
515 _logger.warning('Protocol spacers not supported; setting protocol_number to None') 1f
516 self.protocol_number = None 1f
518 dsets, out_files = self.extractor.extract( 1f
519 save=save, path_out=save_path, sync_collection=self.sync_collection,
520 task_collection=self.collection, protocol_number=self.protocol_number, **kwargs)
522 return dsets, out_files 1f
525class TrainingStatus(base_tasks.BehaviourTask):
526 priority = 90
527 job_size = 'small'
529 @property
530 def signature(self):
531 signature = { 1acijklmhnd
532 'input_files': [
533 ('_iblrig_taskData.raw.*', self.collection, True),
534 ('_iblrig_taskSettings.raw.*', self.collection, True),
535 ('*trials.table.pqt', self.output_collection, True)],
536 'output_files': []
537 }
538 return signature 1acijklmhnd
540 def _run(self, upload=True):
541 """
542 Extracts training status for subject.
543 """
545 lab = get_lab(self.session_path, self.one.alyx) 1cd
546 if lab == 'cortexlab' and 'cortexlab' in self.one.alyx.base_url: 1cd
547 _logger.info('Switching from cortexlab Alyx to IBL Alyx for training status queries.')
548 one = ONE(base_url='https://alyx.internationalbrainlab.org')
549 else:
550 one = self.one 1cd
552 df = training_status.get_latest_training_information(self.session_path, one) 1cd
553 if df is not None: 1cd
554 training_status.make_plots( 1cd
555 self.session_path, self.one, df=df, save=True, upload=upload, task_collection=self.collection)
556 # Update status map in JSON field of subjects endpoint
557 if self.one and not self.one.offline: 1cd
558 _logger.debug('Updating JSON field of subjects endpoint') 1cd
559 status = (df.set_index('date')[['training_status', 'session_path']].drop_duplicates( 1cd
560 subset='training_status', keep='first').to_dict())
561 date, sess = status.items() 1cd
562 data = {'trained_criteria': {v.replace(' ', '_'): (k, self.one.path2eid(sess[1][k])) 1cd
563 for k, v in date[1].items()}}
564 _, subject, *_ = session_path_parts(self.session_path) 1cd
565 self.one.alyx.json_field_update('subjects', subject, data=data) 1cd
566 output_files = [] 1cd
567 return output_files 1cd