11import logging
2+ import time
23
34import numpy as np
45import pandas as pd
56
67from iblrig import sound
8+ from iblrig .base_choice_world import NTRIALS_INIT
79from iblrig .base_tasks import BaseSession , BpodMixin
810from iblrig .misc import get_task_arguments
911from pybpodapi .state_machine import StateMachine
1012
11- logger = logging .getLogger ('iblrig' )
13+ from iblrig .pydantic_definitions import TrialDataModel
14+
15+ log = logging .getLogger ('iblrig' )
16+
17+
18+ class TonotopicMappingTrialData (TrialDataModel ):
19+ """Pydantic Model for Trial Data."""
20+
21+ frequency_sequence : list [int ]
22+
1223
1324
1425class Session (BpodMixin , BaseSession ):
1526 protocol_name = 'samuel_tonotopicMapping'
27+ TrialDataModel = TonotopicMappingTrialData
1628
1729 frequencies : list [int ] = []
1830 sequence : list [int ] = []
31+ trial_num : int = - 1
1932
2033 def __init__ (self , * args , ** kwargs ):
2134 super ().__init__ (* args , ** kwargs )
35+ self .trials_table = self .TrialDataModel .preallocate_dataframe (NTRIALS_INIT )
2236
2337 assert self .hardware_settings .device_sound .OUTPUT == 'harp' , 'This task requires a Harp sound-card'
2438 assert self .task_params ['n_freqs' ] <= 30 , 'Harp only supports up to 30 individual sounds'
@@ -76,11 +90,11 @@ def n_frequencies(self):
7690 return len (self .frequencies )
7791
7892 @property
79- def n_state_machines (self ):
93+ def n_trials (self ):
8094 return len (self .repetitions )
8195
8296 def start_mixin_sound (self ):
83- logger .info (f'Pushing { len (self .frequencies )} stimuli to Harp soundcard' )
97+ log .info (f'Pushing { len (self .frequencies )} stimuli to Harp soundcard' )
8498 sound .configure_sound_card (sounds = self .stimuli , indexes = self .indices , sample_rate = self .task_params ['fs' ])
8599
86100 module = self .bpod .sound_card
@@ -110,13 +124,13 @@ def softcode_handler(softcode: int) -> None:
110124 frequency_index = Session .sequence [state_index ]
111125 frequency = Session .frequencies [frequency_index ]
112126 n_states = len (Session .sequence )
113- logger .info (f'- { state_index + 1 :03d} /{ n_states } : { frequency :5d} Hz' )
127+ log .info (f'- { state_index + 1 :03d} /{ n_states } : { frequency :5d} Hz' )
114128
115- def get_state_machine (self , sma_idx : int ) -> StateMachine :
116- # generate sequence, optionally shuffled (seeded with state machine number)
117- Session .sequence = np .repeat (np .arange (len (self .frequencies )), self .repetitions [sma_idx ])
129+ def get_state_machine (self , trial_number : int ) -> StateMachine :
130+ # generate sequence, optionally shuffled (seeded with trial number)
131+ Session .sequence = np .repeat (np .arange (len (self .frequencies )), self .repetitions [trial_number ])
118132 if self .task_params ['shuffle' ]:
119- np .random .seed (sma_idx )
133+ np .random .seed (trial_number )
120134 np .random .shuffle (Session .sequence )
121135
122136 # build state machine
@@ -131,15 +145,35 @@ def get_state_machine(self, sma_idx: int) -> StateMachine:
131145 return sma
132146
133147 def _run (self ):
134- logger .info ('Sending spacers to BNC ports' )
148+ log .info ('Sending spacers to BNC ports' )
135149 self .send_spacers ()
136150
137- for sma_idx in range (self .n_state_machines ):
138- logger .info (f'State Machine { sma_idx + 1 } /{ self .n_state_machines } ' )
139- sma = self .get_state_machine (sma_idx )
151+ for trial_number in range (self .n_trials ):
152+ self .trial_num = trial_number
153+
154+ # run state machine
155+ log .info (f'Starting Trial #{ trial_number } ({ trial_number + 1 } /{ self .n_trials } )' )
156+ sma = self .get_state_machine (trial_number )
140157 self .bpod .send_state_machine (sma )
141158 self .bpod .run_state_machine (sma )
142- self .bpod .session .current_trial .export ()
159+
160+ # handle pause event
161+ if self .paused and trial_number < (self .task_params .NTRIALS - 1 ):
162+ log .info (f'Pausing session inbetween trials #{ trial_number } and #{ trial_number + 1 } ' )
163+ while self .paused and not self .stopped :
164+ time .sleep (1 )
165+ if not self .stopped :
166+ log .info ('Resuming session' )
167+
168+ # save trial data
169+ self .trials_table .at [self .trial_num , 'frequency_sequence' ] = self .frequencies [self .sequence ]
170+ bpod_data = self .bpod .session .current_trial .export ()
171+ self .save_trial_data_to_json (bpod_data )
172+
173+ # handle stop event
174+ if self .stopped :
175+ log .info ('Stopping session after trial #%d' , trial_number )
176+ break
143177
144178
145179if __name__ == '__main__' :
0 commit comments