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

1"""Standard task protocol extractor dynamic pipeline tasks.""" 

2import logging 

3import traceback 

4 

5from packaging import version 

6import one.alf.io as alfio 

7from one.alf.path import session_path_parts 

8from one.api import ONE 

9 

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 

22 

23_logger = logging.getLogger(__name__) 

24 

25 

26class HabituationRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask): 

27 priority = 100 

28 job_size = 'small' 

29 

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

46 

47 

48class HabituationTrialsBpod(base_tasks.BehaviourTask): 

49 priority = 90 

50 job_size = 'small' 

51 

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

75 

76 def _run(self, update=True, save=True): 

77 """Extracts an iblrig training session.""" 

78 trials, output_files = self.extract_behaviour(save=save) 1ns

79 

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

84 

85 # Run the task QC 

86 self.run_qc(trials, update=update) 

87 return output_files 

88 

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

97 

98 def run_qc(self, trials_data=None, update=True): 

99 trials_data = self._assert_trials_data(trials_data) # validate trials data 1b

100 

101 # Compile task data for QC 

102 qc = HabituationQC(self.session_path, one=self.one) 1b

103 qc.extractor = TaskQCExtractor(self.session_path) 1b

104 

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

110 

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

114 

115 

116class HabituationTrialsNidq(HabituationTrialsBpod): 

117 priority = 90 

118 job_size = 'small' 

119 

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 

132 

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

137 

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) 

142 

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

148 

149 def run_qc(self, trials_data=None, update=True, **_): 

150 """Run and update QC. 

151 

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

158 

159 

160class TrialRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask): 

161 priority = 100 

162 job_size = 'small' 

163 

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

180 

181 

182class PassiveRegisterRaw(base_tasks.RegisterRawDataTask, base_tasks.BehaviourTask): 

183 priority = 100 

184 job_size = 'small' 

185 

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

199 

200 

201class PassiveTaskNidq(base_tasks.BehaviourTask): 

202 priority = 90 

203 job_size = 'small' 

204 

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

221 

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) 

227 

228 if any(x is None for x in paths): 1yzA

229 self.status = -1 

230 

231 return paths 1yzA

232 

233 

234class PassiveTaskTimeline(base_tasks.BehaviourTask, base_tasks.MesoscopeTask): 

235 """TODO should be mesoscope invariant, using wiring file""" 

236 priority = 90 

237 job_size = 'small' 

238 

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 

253 

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 

263 

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) 

269 

270 if any(x is None for x in paths): 

271 self.status = -1 

272 

273 return paths 

274 

275 

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.""" 

281 

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

302 

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

310 

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

322 

323 return output_files 1c

324 

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

331 

332 def run_qc(self, trials_data=None, update=True, QC=None): 

333 """ 

334 Run the task QC. 

335 

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. 

344 

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

351 

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

365 

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

370 

371 

372class ChoiceWorldTrialsNidq(ChoiceWorldTrialsBpod): 

373 priority = 90 

374 job_size = 'small' 

375 

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

418 

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 

424 

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 ) 

438 

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 ) 

444 

445 def extract_behaviour(self, save=True, **kwargs): 

446 # Extract Bpod trials 

447 bpod_trials, _ = super().extract_behaviour(save=False, **kwargs) 1fbetuvwx

448 

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

456 

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

459 

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

478 

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

482 

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

496 

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) 

501 

502 return output_files 1fd

503 

504 

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

525 

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

530 

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

537 

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) 

541 

542 return dsets, out_files 1d

543 

544 

545class TrainingStatus(base_tasks.BehaviourTask): 

546 priority = 90 

547 job_size = 'small' 

548 

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

559 

560 def _run(self, upload=True): 

561 """ 

562 Extracts training status for subject. 

563 """ 

564 

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

571 

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