From 536fbdd185b30edc71b2c01dbe3a1bc6d6635ec8 Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 6 Aug 2026 12:41:41 -0700 Subject: [PATCH 01/11] figured out issues with mounting and refactored the passive viewing task into a fixation BMI task and the passive co task. Somethign weird is happening with the trial initialization though --- built_in_tasks/bmimultitasks.py | 81 ++++++++++++++++++++++++++++++++- built_in_tasks/passivetasks.py | 39 ++-------------- db/tracker/json_param.py | 2 + features/ecube_features.py | 2 +- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 5051e016a..baa258c8b 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -407,4 +407,83 @@ class BMIControlMultiDirectionConstraint(BMIControlMultiMixin, ScreenReachAngle) ''' Adds an additional constraint that the direction of travel must be within a certain angle ''' - pass \ No newline at end of file + pass + +class FixationBMIControlMulti(BMIControlMultiEyeConstrained): + blink_time_threshold = traits.Float(0.1, desc="The amount of time in seconds that the eyes can be closed before triggering a fixation break, measured by eye_diam=0") + assist_level = (1, 1) + is_bmi_seed = True + + static_states = ['wait', 'reward', 'fixation_penalty', 'delay_penalty', 'hold_penalty', 'timeout_penalty'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.most_recent_open_eye = 0 + + status = dict( + wait = dict(start_trial="target", start_pause="pause"), + target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty"), + hold = dict(leave_target="hold_penalty", hold_complete="delay", fixation_break="fixation_penalty", start_pause="pause"), + delay = dict(leave_target="delay_penalty", delay_complete="targ_transition", fixation_break="fixation_penalty", start_pause="pause"), + targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", start_pause="pause"), + timeout_penalty = dict(timeout_penalty_end="wait", start_pause="pause", end_state=True), + hold_penalty = dict(hold_penalty_end="wait", start_pause="pause", end_state=True), + delay_penalty = dict(delay_penalty_end="wait", start_pause="pause", end_state=True), + fixation_penalty = dict(fixation_penalty_end="wait", start_pause="pause", end_state=True), + reward = dict(reward_end="wait", start_pause="pause", stoppable=False, end_state=True), + pause = dict(end_pause="wait", end_state=True), + ) + def move_effector(self): + pass + + def _start_target(self): + self.plant.set_visibility(True) + #target capture + self.target_index += 1 + + #screen target capture: Modified + # Show target if it is hidden (this is the first target, or previous state was a penalty) + target = self.targets[self.target_index % 2] + if self.target_index == 0: + self.sync_event('TARGET_ON', self.gen_indices[self.target_index]) + self.target_location = self.targs[self.target_index] # save for BMILoop + + def _start_fixation_penalty(self): + self.plant.set_visibility(False) + super()._start_fixation_penalty() + self.decoder.filt.state.mean = self.init_decoder_mean.copy() + + def _start_wait(self): + super()._start_wait() + #self.plant_visible = False + self.plant.set_visibility(True) + + def _end_targ_transition(self): + self.plant.set_visibility(False) + super()._end_targ_transition() + + + def _test_fixation_break(self,time_in_state): + ''' + Triggers the fixation_penalty state when eye positions are outside fixation distance + Only apply this to the first hold and delay period + ''' + + eye_pos = self.calibrated_eye_pos + eye_d = np.linalg.norm(eye_pos - self.targs[self.target_index,[0,2]]) + + #Make flag that tracks the last non-zero eye diameter and check that it has occured in the last 100ms + #First logic check: Check to see if the eye is open. If open, reset flag to 0 + eye_within_fixation_buffer = (eye_d > self.target_radius + self.fixation_radius_buffer) + if self.keyboard_control: + return eye_within_fixation_buffer + elif np.any(self.eye_diam != 0): + self.most_recent_open_eye = 0 + elif self.most_recent_open_eye == 0: #Additionally check if this if the first cycle of 'eyes closed'. If not the first cycle, check how long since the flag was set and trigger a fixatio nfailure if longer than 100ms. + self.most_recent_open_eye=self.get_time() + elif (self.get_time()-self.most_recent_open_eye) > self.blink_time_threshold: + self.most_recent_open_eye = 0 + return True + + #Finally check if the eye location is within the target + buffer + return eye_within_fixation_buffer \ No newline at end of file diff --git a/built_in_tasks/passivetasks.py b/built_in_tasks/passivetasks.py index 03a2d58ed..b213d2e42 100644 --- a/built_in_tasks/passivetasks.py +++ b/built_in_tasks/passivetasks.py @@ -24,7 +24,7 @@ from .target_graphics import * -from .bmimultitasks import BMIControlMultiEyeConstrained +from .bmimultitasks import FixationBMIControlMulti bmi_ssm_options = ['Endpt2D', 'Tentacle', 'Joint2L'] @@ -57,30 +57,6 @@ class TargetCaptureVisualFeedback(EndPostureFeedbackController, BMIControlMulti) def move_effector(self): pass -class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, BMIControlMultiEyeConstrained): - blink_time_threshold = traits.Float(0.1, desc="The amount of time in seconds that the eyes can be closed before triggering a fixation break, measured by eye_diam=0") - assist_level = (1, 1) - is_bmi_seed = True - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.most_recent_open_eye = 0 - - status = dict( - wait = dict(start_trial="target", start_pause="pause"), - target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty"), - hold = dict(leave_target="hold_penalty", hold_complete="delay", fixation_break="fixation_penalty", start_pause="pause"), - delay = dict(leave_target="delay_penalty", delay_complete="targ_transition", fixation_break="fixation_penalty", start_pause="pause"), - targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", start_pause="pause"), - timeout_penalty = dict(timeout_penalty_end="wait", start_pause="pause", end_state=True), - hold_penalty = dict(hold_penalty_end="wait", start_pause="pause", end_state=True), - delay_penalty = dict(delay_penalty_end="wait", start_pause="pause", end_state=True), - fixation_penalty = dict(fixation_penalty_end="wait", start_pause="pause", end_state=True), - reward = dict(reward_end="wait", start_pause="pause", stoppable=False, end_state=True), - pause = dict(end_pause="wait", end_state=True), - ) - def move_effector(self): - pass def _start_target(self): self.plant.set_visibility(True) @@ -161,18 +137,13 @@ def _test_start_trial(self, time_in_state): blink = self.keyboard_control | np.any(self.eye_diam!=0) value = (eye_d < self.target_radius + self.fixation_radius_buffer) & blink - return value#(eye_d > self.target_radius + self.fixation_radius_buffer) - - #def _end_targ_transition(self): - # super()._end_targ_transition() - # if self.reset == 1:# and ((self.target_index == self.chain_length - 1) or (self.target_index == -1)): - - # # Reset on any target transition away from the last target - # self.decoder.filt.state.mean = self.init_decoder_mean.copy() - # self.hdf.sendMsg("reset") + return value +class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, FixationBMIControlMulti): + """Passive viewing version of the fixation BMI task""" + pass class TargetCaptureVFB2DWindow(TargetCaptureVisualFeedback, WindowDispl2D): diff --git a/db/tracker/json_param.py b/db/tracker/json_param.py index a048d4424..1a0e1f780 100644 --- a/db/tracker/json_param.py +++ b/db/tracker/json_param.py @@ -82,6 +82,8 @@ def norm_trait(trait, value): Model = getattr(models, mdl_name) record = Model.objects.get(pk=value) value = record.get() + if value is None: + raise ValueError("Instance not loaded properly, likely a Decoder not found or not properly loaded. Check that /storage/bmi is properly mounted and that the specified decoder is there") # Otherwise, let's hope it's already an instance elif ttype == 'DataFile': # Similar to Instance traits, except we always know to use models.DataFile as the database table to look up the primary key diff --git a/features/ecube_features.py b/features/ecube_features.py index 9029019fe..04122793c 100644 --- a/features/ecube_features.py +++ b/features/ecube_features.py @@ -279,7 +279,7 @@ class EcubeFileBMI(EcubeFileData, CorticalBMI): ''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + self.cortical_channels = [int(ch) for ch in self.decoder.units[:,0]] # These get read by CorticalData when initializing the extractor From 8d9a977ecd7f7c6fbf2c230aa95f0c1e5a4f029f Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 6 Aug 2026 12:50:04 -0700 Subject: [PATCH 02/11] rebased onto master to correct the task --- built_in_tasks/bmimultitasks.py | 33 ++++++++++++- built_in_tasks/passivetasks.py | 82 --------------------------------- 2 files changed, 31 insertions(+), 84 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index baa258c8b..8a1ba5885 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -414,7 +414,7 @@ class FixationBMIControlMulti(BMIControlMultiEyeConstrained): assist_level = (1, 1) is_bmi_seed = True - static_states = ['wait', 'reward', 'fixation_penalty', 'delay_penalty', 'hold_penalty', 'timeout_penalty'] + static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -445,9 +445,16 @@ def _start_target(self): # Show target if it is hidden (this is the first target, or previous state was a penalty) target = self.targets[self.target_index % 2] if self.target_index == 0: + #target.move_to_position(self.targs[self.target_index]) + #target.show() self.sync_event('TARGET_ON', self.gen_indices[self.target_index]) self.target_location = self.targs[self.target_index] # save for BMILoop + + #if self.target_index == 0: + # self.targets_eye[0].move_to_position(self.targs[self.target_index] - self.offset_cube) + # self.targets_eye[0].show() + def _start_fixation_penalty(self): self.plant.set_visibility(False) super()._start_fixation_penalty() @@ -462,6 +469,17 @@ def _end_targ_transition(self): self.plant.set_visibility(False) super()._end_targ_transition() + def _start_pause(self): + super()._start_pause() + self.plant.set_visibility(False) + + def _end_pause(self): + super()._end_pause() + # Reset on any target transition away from the last target + self.decoder.filt.state.mean = self.init_decoder_mean.copy() + self.hdf.sendMsg("reset") + + self.plant.set_visibility(True) def _test_fixation_break(self,time_in_state): ''' @@ -486,4 +504,15 @@ def _test_fixation_break(self,time_in_state): return True #Finally check if the eye location is within the target + buffer - return eye_within_fixation_buffer \ No newline at end of file + return eye_within_fixation_buffer + + def _test_start_trial(self, time_in_state): + #Check that the eye position is on the center target + #return True #super()._test_start_trial + eye_pos = self.calibrated_eye_pos + eye_d = np.linalg.norm(eye_pos - self.targs[0,[0,2]]) #target index is zero, this is only applyied during the wait period before the center target comes on + + blink = self.keyboard_control | np.any(self.eye_diam!=0) + + value = (eye_d < self.target_radius + self.fixation_radius_buffer) & blink + return value#(eye_d > self.target_radius + self.fixation_radius_buffer) \ No newline at end of file diff --git a/built_in_tasks/passivetasks.py b/built_in_tasks/passivetasks.py index b213d2e42..1f82c6efe 100644 --- a/built_in_tasks/passivetasks.py +++ b/built_in_tasks/passivetasks.py @@ -58,88 +58,6 @@ def move_effector(self): pass - def _start_target(self): - self.plant.set_visibility(True) - #target capture - self.target_index += 1 - - #screen target capture: Modified - # Show target if it is hidden (this is the first target, or previous state was a penalty) - target = self.targets[self.target_index % 2] - if self.target_index == 0: - #target.move_to_position(self.targs[self.target_index]) - #target.show() - self.sync_event('TARGET_ON', self.gen_indices[self.target_index]) - self.target_location = self.targs[self.target_index] # save for BMILoop - - - #if self.target_index == 0: - # self.targets_eye[0].move_to_position(self.targs[self.target_index] - self.offset_cube) - # self.targets_eye[0].show() - - def _start_fixation_penalty(self): - self.plant.set_visibility(False) - super()._start_fixation_penalty() - self.decoder.filt.state.mean = self.init_decoder_mean.copy() - - def _start_wait(self): - super()._start_wait() - #self.plant_visible = False - self.plant.set_visibility(True) - - def _end_targ_transition(self): - self.plant.set_visibility(False) - super()._end_targ_transition() - - def _start_pause(self): - super()._start_pause() - self.plant.set_visibility(False) - - def _end_pause(self): - super()._end_pause() - # Reset on any target transition away from the last target - self.decoder.filt.state.mean = self.init_decoder_mean.copy() - self.hdf.sendMsg("reset") - - self.plant.set_visibility(True) - - def _test_fixation_break(self,time_in_state): - ''' - Triggers the fixation_penalty state when eye positions are outside fixation distance - Only apply this to the first hold and delay period - ''' - - eye_pos = self.calibrated_eye_pos - eye_d = np.linalg.norm(eye_pos - self.targs[self.target_index,[0,2]]) - - #Make flag that tracks the last non-zero eye diameter and check that it has occured in the last 100ms - #First logic check: Check to see if the eye is open. If open, reset flag to 0 - eye_within_fixation_buffer = (eye_d > self.target_radius + self.fixation_radius_buffer) - if self.keyboard_control: - return eye_within_fixation_buffer - elif np.any(self.eye_diam != 0): - self.most_recent_open_eye = 0 - elif self.most_recent_open_eye == 0: #Additionally check if this if the first cycle of 'eyes closed'. If not the first cycle, check how long since the flag was set and trigger a fixatio nfailure if longer than 100ms. - self.most_recent_open_eye=self.get_time() - elif (self.get_time()-self.most_recent_open_eye) > self.blink_time_threshold: - self.most_recent_open_eye = 0 - return True - - #Finally check if the eye location is within the target + buffer - return eye_within_fixation_buffer - - def _test_start_trial(self, time_in_state): - #Check that the eye position is on the center target - #return True #super()._test_start_trial - eye_pos = self.calibrated_eye_pos - eye_d = np.linalg.norm(eye_pos - self.targs[0,[0,2]]) #target index is zero, this is only applyied during the wait period before the center target comes on - - blink = self.keyboard_control | np.any(self.eye_diam!=0) - - value = (eye_d < self.target_radius + self.fixation_radius_buffer) & blink - return value - - class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, FixationBMIControlMulti): """Passive viewing version of the fixation BMI task""" From 1250774ab82fa34218027eb58a8f078dbf403d49 Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 6 Aug 2026 13:06:46 -0700 Subject: [PATCH 03/11] brainstorming next steps --- built_in_tasks/bmimultitasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 8a1ba5885..2a53d85e7 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -414,7 +414,7 @@ class FixationBMIControlMulti(BMIControlMultiEyeConstrained): assist_level = (1, 1) is_bmi_seed = True - static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty'] + static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -493,6 +493,7 @@ def _test_fixation_break(self,time_in_state): #Make flag that tracks the last non-zero eye diameter and check that it has occured in the last 100ms #First logic check: Check to see if the eye is open. If open, reset flag to 0 eye_within_fixation_buffer = (eye_d > self.target_radius + self.fixation_radius_buffer) + #eye_within_fixation_buffer_cursor = if self.keyboard_control: return eye_within_fixation_buffer elif np.any(self.eye_diam != 0): From 7fa1fc2dca92a242882223627e1286acfbeb67e3 Mon Sep 17 00:00:00 2001 From: sofie Date: Mon, 10 Aug 2026 17:11:44 -0700 Subject: [PATCH 04/11] updating the task to have the correct behavior on cursor out of bounds. Need to update the penalty states and figure out how to actually trouble shoot it --- built_in_tasks/bmimultitasks.py | 34 ++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 2a53d85e7..46e51655c 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -414,7 +414,7 @@ class FixationBMIControlMulti(BMIControlMultiEyeConstrained): assist_level = (1, 1) is_bmi_seed = True - static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] + static_states = ['wait', 'delay', 'reward', 'cursor_out_of_bounds_penalty', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -422,10 +422,13 @@ def __init__(self, *args, **kwargs): status = dict( wait = dict(start_trial="target", start_pause="pause"), - target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty"), + target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty", + cursor_out_of_bounds='cursor_out_of_bounds_penalty'), hold = dict(leave_target="hold_penalty", hold_complete="delay", fixation_break="fixation_penalty", start_pause="pause"), delay = dict(leave_target="delay_penalty", delay_complete="targ_transition", fixation_break="fixation_penalty", start_pause="pause"), - targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", start_pause="pause"), + targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", + cursor_out_of_bounds='cursor_out_of_bounds_penalty', start_pause="pause"), + cursor_out_of_bounds_penalty = dict(cursor_out_of_bounds_end="wait", start_pause="pause", end_state=True), timeout_penalty = dict(timeout_penalty_end="wait", start_pause="pause", end_state=True), hold_penalty = dict(hold_penalty_end="wait", start_pause="pause", end_state=True), delay_penalty = dict(delay_penalty_end="wait", start_pause="pause", end_state=True), @@ -433,6 +436,31 @@ def __init__(self, *args, **kwargs): reward = dict(reward_end="wait", start_pause="pause", stoppable=False, end_state=True), pause = dict(end_pause="wait", end_state=True), ) + + + def _start_cursor_out_of_bounds_penalty(self): + self.sync_event('TIMEOUT_PENALTY') + super()._start_timeout_penalty() + # Hide targets + for target in self.targets: + target.hide() + target.reset() + + def _end_cursor_out_of_bounds_penalty(self): + super()._end_timeout_penalty() + self.sync_event('TRIAL_END') + + + def _test_cursor_out_of_bounds(self): + cursor_bounds = self.cursor_bounds + #traits.Tuple((-10., 10., -10., 10., -10., 10.), desc='(x min, x max, y min, y max, z min, z max)') + cur_pos = self.plant.get_endpoint_pos() + check_x = (cur_pos[0] => cursor_bounds[0]) | (cur_pos[0] <= cursor_bounds[1]) + check_y = (cur_pos[1] => cursor_bounds[0]) | (cur_pos[1] <= cursor_bounds[1]) + check_z = (cur_pos[2] => cursor_bounds[0]) | (cur_pos[2] <= cursor_bounds[1]) + + return check_x | check_y | check_z + def move_effector(self): pass From fb44ccb433d49db5fbb053adcd19a93d12a1df16 Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 6 Aug 2026 12:41:41 -0700 Subject: [PATCH 05/11] figured out issues with mounting and refactored the passive viewing task into a fixation BMI task and the passive co task. Somethign weird is happening with the trial initialization though --- built_in_tasks/bmimultitasks.py | 81 ++++++++++++++++++++++++++++++++- built_in_tasks/passivetasks.py | 39 ++-------------- db/tracker/json_param.py | 2 + features/ecube_features.py | 2 +- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 5051e016a..baa258c8b 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -407,4 +407,83 @@ class BMIControlMultiDirectionConstraint(BMIControlMultiMixin, ScreenReachAngle) ''' Adds an additional constraint that the direction of travel must be within a certain angle ''' - pass \ No newline at end of file + pass + +class FixationBMIControlMulti(BMIControlMultiEyeConstrained): + blink_time_threshold = traits.Float(0.1, desc="The amount of time in seconds that the eyes can be closed before triggering a fixation break, measured by eye_diam=0") + assist_level = (1, 1) + is_bmi_seed = True + + static_states = ['wait', 'reward', 'fixation_penalty', 'delay_penalty', 'hold_penalty', 'timeout_penalty'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.most_recent_open_eye = 0 + + status = dict( + wait = dict(start_trial="target", start_pause="pause"), + target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty"), + hold = dict(leave_target="hold_penalty", hold_complete="delay", fixation_break="fixation_penalty", start_pause="pause"), + delay = dict(leave_target="delay_penalty", delay_complete="targ_transition", fixation_break="fixation_penalty", start_pause="pause"), + targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", start_pause="pause"), + timeout_penalty = dict(timeout_penalty_end="wait", start_pause="pause", end_state=True), + hold_penalty = dict(hold_penalty_end="wait", start_pause="pause", end_state=True), + delay_penalty = dict(delay_penalty_end="wait", start_pause="pause", end_state=True), + fixation_penalty = dict(fixation_penalty_end="wait", start_pause="pause", end_state=True), + reward = dict(reward_end="wait", start_pause="pause", stoppable=False, end_state=True), + pause = dict(end_pause="wait", end_state=True), + ) + def move_effector(self): + pass + + def _start_target(self): + self.plant.set_visibility(True) + #target capture + self.target_index += 1 + + #screen target capture: Modified + # Show target if it is hidden (this is the first target, or previous state was a penalty) + target = self.targets[self.target_index % 2] + if self.target_index == 0: + self.sync_event('TARGET_ON', self.gen_indices[self.target_index]) + self.target_location = self.targs[self.target_index] # save for BMILoop + + def _start_fixation_penalty(self): + self.plant.set_visibility(False) + super()._start_fixation_penalty() + self.decoder.filt.state.mean = self.init_decoder_mean.copy() + + def _start_wait(self): + super()._start_wait() + #self.plant_visible = False + self.plant.set_visibility(True) + + def _end_targ_transition(self): + self.plant.set_visibility(False) + super()._end_targ_transition() + + + def _test_fixation_break(self,time_in_state): + ''' + Triggers the fixation_penalty state when eye positions are outside fixation distance + Only apply this to the first hold and delay period + ''' + + eye_pos = self.calibrated_eye_pos + eye_d = np.linalg.norm(eye_pos - self.targs[self.target_index,[0,2]]) + + #Make flag that tracks the last non-zero eye diameter and check that it has occured in the last 100ms + #First logic check: Check to see if the eye is open. If open, reset flag to 0 + eye_within_fixation_buffer = (eye_d > self.target_radius + self.fixation_radius_buffer) + if self.keyboard_control: + return eye_within_fixation_buffer + elif np.any(self.eye_diam != 0): + self.most_recent_open_eye = 0 + elif self.most_recent_open_eye == 0: #Additionally check if this if the first cycle of 'eyes closed'. If not the first cycle, check how long since the flag was set and trigger a fixatio nfailure if longer than 100ms. + self.most_recent_open_eye=self.get_time() + elif (self.get_time()-self.most_recent_open_eye) > self.blink_time_threshold: + self.most_recent_open_eye = 0 + return True + + #Finally check if the eye location is within the target + buffer + return eye_within_fixation_buffer \ No newline at end of file diff --git a/built_in_tasks/passivetasks.py b/built_in_tasks/passivetasks.py index 03a2d58ed..b213d2e42 100644 --- a/built_in_tasks/passivetasks.py +++ b/built_in_tasks/passivetasks.py @@ -24,7 +24,7 @@ from .target_graphics import * -from .bmimultitasks import BMIControlMultiEyeConstrained +from .bmimultitasks import FixationBMIControlMulti bmi_ssm_options = ['Endpt2D', 'Tentacle', 'Joint2L'] @@ -57,30 +57,6 @@ class TargetCaptureVisualFeedback(EndPostureFeedbackController, BMIControlMulti) def move_effector(self): pass -class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, BMIControlMultiEyeConstrained): - blink_time_threshold = traits.Float(0.1, desc="The amount of time in seconds that the eyes can be closed before triggering a fixation break, measured by eye_diam=0") - assist_level = (1, 1) - is_bmi_seed = True - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.most_recent_open_eye = 0 - - status = dict( - wait = dict(start_trial="target", start_pause="pause"), - target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty"), - hold = dict(leave_target="hold_penalty", hold_complete="delay", fixation_break="fixation_penalty", start_pause="pause"), - delay = dict(leave_target="delay_penalty", delay_complete="targ_transition", fixation_break="fixation_penalty", start_pause="pause"), - targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", start_pause="pause"), - timeout_penalty = dict(timeout_penalty_end="wait", start_pause="pause", end_state=True), - hold_penalty = dict(hold_penalty_end="wait", start_pause="pause", end_state=True), - delay_penalty = dict(delay_penalty_end="wait", start_pause="pause", end_state=True), - fixation_penalty = dict(fixation_penalty_end="wait", start_pause="pause", end_state=True), - reward = dict(reward_end="wait", start_pause="pause", stoppable=False, end_state=True), - pause = dict(end_pause="wait", end_state=True), - ) - def move_effector(self): - pass def _start_target(self): self.plant.set_visibility(True) @@ -161,18 +137,13 @@ def _test_start_trial(self, time_in_state): blink = self.keyboard_control | np.any(self.eye_diam!=0) value = (eye_d < self.target_radius + self.fixation_radius_buffer) & blink - return value#(eye_d > self.target_radius + self.fixation_radius_buffer) - - #def _end_targ_transition(self): - # super()._end_targ_transition() - # if self.reset == 1:# and ((self.target_index == self.chain_length - 1) or (self.target_index == -1)): - - # # Reset on any target transition away from the last target - # self.decoder.filt.state.mean = self.init_decoder_mean.copy() - # self.hdf.sendMsg("reset") + return value +class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, FixationBMIControlMulti): + """Passive viewing version of the fixation BMI task""" + pass class TargetCaptureVFB2DWindow(TargetCaptureVisualFeedback, WindowDispl2D): diff --git a/db/tracker/json_param.py b/db/tracker/json_param.py index a048d4424..1a0e1f780 100644 --- a/db/tracker/json_param.py +++ b/db/tracker/json_param.py @@ -82,6 +82,8 @@ def norm_trait(trait, value): Model = getattr(models, mdl_name) record = Model.objects.get(pk=value) value = record.get() + if value is None: + raise ValueError("Instance not loaded properly, likely a Decoder not found or not properly loaded. Check that /storage/bmi is properly mounted and that the specified decoder is there") # Otherwise, let's hope it's already an instance elif ttype == 'DataFile': # Similar to Instance traits, except we always know to use models.DataFile as the database table to look up the primary key diff --git a/features/ecube_features.py b/features/ecube_features.py index 9029019fe..04122793c 100644 --- a/features/ecube_features.py +++ b/features/ecube_features.py @@ -279,7 +279,7 @@ class EcubeFileBMI(EcubeFileData, CorticalBMI): ''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + self.cortical_channels = [int(ch) for ch in self.decoder.units[:,0]] # These get read by CorticalData when initializing the extractor From 86d303bfa928be388a0517ce2f62238368350de7 Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 6 Aug 2026 12:50:04 -0700 Subject: [PATCH 06/11] rebased onto master to correct the task --- built_in_tasks/bmimultitasks.py | 33 ++++++++++++- built_in_tasks/passivetasks.py | 82 --------------------------------- 2 files changed, 31 insertions(+), 84 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index baa258c8b..8a1ba5885 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -414,7 +414,7 @@ class FixationBMIControlMulti(BMIControlMultiEyeConstrained): assist_level = (1, 1) is_bmi_seed = True - static_states = ['wait', 'reward', 'fixation_penalty', 'delay_penalty', 'hold_penalty', 'timeout_penalty'] + static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -445,9 +445,16 @@ def _start_target(self): # Show target if it is hidden (this is the first target, or previous state was a penalty) target = self.targets[self.target_index % 2] if self.target_index == 0: + #target.move_to_position(self.targs[self.target_index]) + #target.show() self.sync_event('TARGET_ON', self.gen_indices[self.target_index]) self.target_location = self.targs[self.target_index] # save for BMILoop + + #if self.target_index == 0: + # self.targets_eye[0].move_to_position(self.targs[self.target_index] - self.offset_cube) + # self.targets_eye[0].show() + def _start_fixation_penalty(self): self.plant.set_visibility(False) super()._start_fixation_penalty() @@ -462,6 +469,17 @@ def _end_targ_transition(self): self.plant.set_visibility(False) super()._end_targ_transition() + def _start_pause(self): + super()._start_pause() + self.plant.set_visibility(False) + + def _end_pause(self): + super()._end_pause() + # Reset on any target transition away from the last target + self.decoder.filt.state.mean = self.init_decoder_mean.copy() + self.hdf.sendMsg("reset") + + self.plant.set_visibility(True) def _test_fixation_break(self,time_in_state): ''' @@ -486,4 +504,15 @@ def _test_fixation_break(self,time_in_state): return True #Finally check if the eye location is within the target + buffer - return eye_within_fixation_buffer \ No newline at end of file + return eye_within_fixation_buffer + + def _test_start_trial(self, time_in_state): + #Check that the eye position is on the center target + #return True #super()._test_start_trial + eye_pos = self.calibrated_eye_pos + eye_d = np.linalg.norm(eye_pos - self.targs[0,[0,2]]) #target index is zero, this is only applyied during the wait period before the center target comes on + + blink = self.keyboard_control | np.any(self.eye_diam!=0) + + value = (eye_d < self.target_radius + self.fixation_radius_buffer) & blink + return value#(eye_d > self.target_radius + self.fixation_radius_buffer) \ No newline at end of file diff --git a/built_in_tasks/passivetasks.py b/built_in_tasks/passivetasks.py index b213d2e42..1f82c6efe 100644 --- a/built_in_tasks/passivetasks.py +++ b/built_in_tasks/passivetasks.py @@ -58,88 +58,6 @@ def move_effector(self): pass - def _start_target(self): - self.plant.set_visibility(True) - #target capture - self.target_index += 1 - - #screen target capture: Modified - # Show target if it is hidden (this is the first target, or previous state was a penalty) - target = self.targets[self.target_index % 2] - if self.target_index == 0: - #target.move_to_position(self.targs[self.target_index]) - #target.show() - self.sync_event('TARGET_ON', self.gen_indices[self.target_index]) - self.target_location = self.targs[self.target_index] # save for BMILoop - - - #if self.target_index == 0: - # self.targets_eye[0].move_to_position(self.targs[self.target_index] - self.offset_cube) - # self.targets_eye[0].show() - - def _start_fixation_penalty(self): - self.plant.set_visibility(False) - super()._start_fixation_penalty() - self.decoder.filt.state.mean = self.init_decoder_mean.copy() - - def _start_wait(self): - super()._start_wait() - #self.plant_visible = False - self.plant.set_visibility(True) - - def _end_targ_transition(self): - self.plant.set_visibility(False) - super()._end_targ_transition() - - def _start_pause(self): - super()._start_pause() - self.plant.set_visibility(False) - - def _end_pause(self): - super()._end_pause() - # Reset on any target transition away from the last target - self.decoder.filt.state.mean = self.init_decoder_mean.copy() - self.hdf.sendMsg("reset") - - self.plant.set_visibility(True) - - def _test_fixation_break(self,time_in_state): - ''' - Triggers the fixation_penalty state when eye positions are outside fixation distance - Only apply this to the first hold and delay period - ''' - - eye_pos = self.calibrated_eye_pos - eye_d = np.linalg.norm(eye_pos - self.targs[self.target_index,[0,2]]) - - #Make flag that tracks the last non-zero eye diameter and check that it has occured in the last 100ms - #First logic check: Check to see if the eye is open. If open, reset flag to 0 - eye_within_fixation_buffer = (eye_d > self.target_radius + self.fixation_radius_buffer) - if self.keyboard_control: - return eye_within_fixation_buffer - elif np.any(self.eye_diam != 0): - self.most_recent_open_eye = 0 - elif self.most_recent_open_eye == 0: #Additionally check if this if the first cycle of 'eyes closed'. If not the first cycle, check how long since the flag was set and trigger a fixatio nfailure if longer than 100ms. - self.most_recent_open_eye=self.get_time() - elif (self.get_time()-self.most_recent_open_eye) > self.blink_time_threshold: - self.most_recent_open_eye = 0 - return True - - #Finally check if the eye location is within the target + buffer - return eye_within_fixation_buffer - - def _test_start_trial(self, time_in_state): - #Check that the eye position is on the center target - #return True #super()._test_start_trial - eye_pos = self.calibrated_eye_pos - eye_d = np.linalg.norm(eye_pos - self.targs[0,[0,2]]) #target index is zero, this is only applyied during the wait period before the center target comes on - - blink = self.keyboard_control | np.any(self.eye_diam!=0) - - value = (eye_d < self.target_radius + self.fixation_radius_buffer) & blink - return value - - class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, FixationBMIControlMulti): """Passive viewing version of the fixation BMI task""" From 1c5b668c94325bd2b4f0fa39f7d1a68b8ed482fa Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 6 Aug 2026 13:06:46 -0700 Subject: [PATCH 07/11] brainstorming next steps --- built_in_tasks/bmimultitasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 8a1ba5885..2a53d85e7 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -414,7 +414,7 @@ class FixationBMIControlMulti(BMIControlMultiEyeConstrained): assist_level = (1, 1) is_bmi_seed = True - static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty'] + static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -493,6 +493,7 @@ def _test_fixation_break(self,time_in_state): #Make flag that tracks the last non-zero eye diameter and check that it has occured in the last 100ms #First logic check: Check to see if the eye is open. If open, reset flag to 0 eye_within_fixation_buffer = (eye_d > self.target_radius + self.fixation_radius_buffer) + #eye_within_fixation_buffer_cursor = if self.keyboard_control: return eye_within_fixation_buffer elif np.any(self.eye_diam != 0): From de17d8cd3ed7420a92950f53335001320afdacbb Mon Sep 17 00:00:00 2001 From: sofie Date: Mon, 10 Aug 2026 17:11:44 -0700 Subject: [PATCH 08/11] updating the task to have the correct behavior on cursor out of bounds. Need to update the penalty states and figure out how to actually trouble shoot it --- built_in_tasks/bmimultitasks.py | 34 ++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 2a53d85e7..46e51655c 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -414,7 +414,7 @@ class FixationBMIControlMulti(BMIControlMultiEyeConstrained): assist_level = (1, 1) is_bmi_seed = True - static_states = ['wait', 'reward', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] + static_states = ['wait', 'delay', 'reward', 'cursor_out_of_bounds_penalty', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -422,10 +422,13 @@ def __init__(self, *args, **kwargs): status = dict( wait = dict(start_trial="target", start_pause="pause"), - target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty"), + target = dict(timeout="timeout_penalty", enter_target="hold", start_pause="pause", fixation_break="fixation_penalty", + cursor_out_of_bounds='cursor_out_of_bounds_penalty'), hold = dict(leave_target="hold_penalty", hold_complete="delay", fixation_break="fixation_penalty", start_pause="pause"), delay = dict(leave_target="delay_penalty", delay_complete="targ_transition", fixation_break="fixation_penalty", start_pause="pause"), - targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", start_pause="pause"), + targ_transition = dict(trial_complete="reward", trial_abort="wait", trial_incomplete="target", + cursor_out_of_bounds='cursor_out_of_bounds_penalty', start_pause="pause"), + cursor_out_of_bounds_penalty = dict(cursor_out_of_bounds_end="wait", start_pause="pause", end_state=True), timeout_penalty = dict(timeout_penalty_end="wait", start_pause="pause", end_state=True), hold_penalty = dict(hold_penalty_end="wait", start_pause="pause", end_state=True), delay_penalty = dict(delay_penalty_end="wait", start_pause="pause", end_state=True), @@ -433,6 +436,31 @@ def __init__(self, *args, **kwargs): reward = dict(reward_end="wait", start_pause="pause", stoppable=False, end_state=True), pause = dict(end_pause="wait", end_state=True), ) + + + def _start_cursor_out_of_bounds_penalty(self): + self.sync_event('TIMEOUT_PENALTY') + super()._start_timeout_penalty() + # Hide targets + for target in self.targets: + target.hide() + target.reset() + + def _end_cursor_out_of_bounds_penalty(self): + super()._end_timeout_penalty() + self.sync_event('TRIAL_END') + + + def _test_cursor_out_of_bounds(self): + cursor_bounds = self.cursor_bounds + #traits.Tuple((-10., 10., -10., 10., -10., 10.), desc='(x min, x max, y min, y max, z min, z max)') + cur_pos = self.plant.get_endpoint_pos() + check_x = (cur_pos[0] => cursor_bounds[0]) | (cur_pos[0] <= cursor_bounds[1]) + check_y = (cur_pos[1] => cursor_bounds[0]) | (cur_pos[1] <= cursor_bounds[1]) + check_z = (cur_pos[2] => cursor_bounds[0]) | (cur_pos[2] <= cursor_bounds[1]) + + return check_x | check_y | check_z + def move_effector(self): pass From f0bd1e49cfc6776b67cc7dc42125029a3c90c91d Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 12 Aug 2026 14:17:26 -0700 Subject: [PATCH 09/11] troubleshooting the test_cursor_out_of_bounds method --- built_in_tasks/bmimultitasks.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 46e51655c..22ed5abb1 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -451,15 +451,18 @@ def _end_cursor_out_of_bounds_penalty(self): self.sync_event('TRIAL_END') - def _test_cursor_out_of_bounds(self): - cursor_bounds = self.cursor_bounds + def _test_cursor_out_of_bounds(self, time_in_state): + cursor_bounds = np.array(self.cursor_bounds) #traits.Tuple((-10., 10., -10., 10., -10., 10.), desc='(x min, x max, y min, y max, z min, z max)') cur_pos = self.plant.get_endpoint_pos() - check_x = (cur_pos[0] => cursor_bounds[0]) | (cur_pos[0] <= cursor_bounds[1]) - check_y = (cur_pos[1] => cursor_bounds[0]) | (cur_pos[1] <= cursor_bounds[1]) - check_z = (cur_pos[2] => cursor_bounds[0]) | (cur_pos[2] <= cursor_bounds[1]) + check_x = (cur_pos[0] >= cursor_bounds[0]) or (cur_pos[0] <= cursor_bounds[1]) + check_y = (cur_pos[1] >= cursor_bounds[2]) or (cur_pos[1] <= cursor_bounds[3]) + check_z = (cur_pos[2] >= cursor_bounds[4]) or (cur_pos[2] <= cursor_bounds[5]) - return check_x | check_y | check_z + cursor_out_of_bounds = ~check_x or ~check_y or ~check_z + print(f'Cursor out of bounds: {cursor_out_of_bounds}, pos: {cur_pos}, bounds: {cursor_bounds}') + return cursor_out_of_bounds + def move_effector(self): pass From befc0fed65f1e34907e76f110ba22606d273fc82 Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 12 Aug 2026 15:08:16 -0700 Subject: [PATCH 10/11] Implemented part of the cursor bounds, it is triggering incorrectly and the penalty state is improperly managed --- built_in_tasks/bmimultitasks.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 22ed5abb1..0b8a6a74d 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -452,18 +452,24 @@ def _end_cursor_out_of_bounds_penalty(self): def _test_cursor_out_of_bounds(self, time_in_state): - cursor_bounds = np.array(self.cursor_bounds) + width, height = self.environment_plane_size + x_pos, y_pos, z_pos = self.environment_plane_origin + x_bound = np.array([width/2, -width/2])+x_pos + print(f'The x_bounds are {x_bound}') + y_bound = np.array([height/2, -height/2])+y_pos + print(f'The y_bounds are {y_bound}') + + #traits.Tuple((-10., 10., -10., 10., -10., 10.), desc='(x min, x max, y min, y max, z min, z max)') cur_pos = self.plant.get_endpoint_pos() - check_x = (cur_pos[0] >= cursor_bounds[0]) or (cur_pos[0] <= cursor_bounds[1]) - check_y = (cur_pos[1] >= cursor_bounds[2]) or (cur_pos[1] <= cursor_bounds[3]) - check_z = (cur_pos[2] >= cursor_bounds[4]) or (cur_pos[2] <= cursor_bounds[5]) + check_x = (cur_pos[0] >= x_bound[0]) or (cur_pos[0] <= x_bound[1]) + check_y = (cur_pos[1] >= y_bound[0]) or (cur_pos[1] <= y_bound[1]) + #check_z = (cur_pos[2] >= cursor_bounds[4]) or (cur_pos[2] <= cursor_bounds[5]) - cursor_out_of_bounds = ~check_x or ~check_y or ~check_z - print(f'Cursor out of bounds: {cursor_out_of_bounds}, pos: {cur_pos}, bounds: {cursor_bounds}') + cursor_out_of_bounds = ~check_x or ~check_y# or ~check_z return cursor_out_of_bounds - +/home/pagaiisland/code/bmi3d/built_in_tasks/bmimultitasks.py def move_effector(self): pass From 83306bb1fa9e8d730de6effacfe5402f2a835e1e Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 12 Aug 2026 17:06:15 -0700 Subject: [PATCH 11/11] got the cursor out of bounds timeout workign and added a new task event --- built_in_tasks/bmimultitasks.py | 33 ++++++++++++++++++++++----------- built_in_tasks/passivetasks.py | 2 +- config/rig_defaults.py | 1 + 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/built_in_tasks/bmimultitasks.py b/built_in_tasks/bmimultitasks.py index 0b8a6a74d..434a49d83 100644 --- a/built_in_tasks/bmimultitasks.py +++ b/built_in_tasks/bmimultitasks.py @@ -411,14 +411,19 @@ class BMIControlMultiDirectionConstraint(BMIControlMultiMixin, ScreenReachAngle) class FixationBMIControlMulti(BMIControlMultiEyeConstrained): blink_time_threshold = traits.Float(0.1, desc="The amount of time in seconds that the eyes can be closed before triggering a fixation break, measured by eye_diam=0") + cursor_out_of_bounds_penalty_time = traits.Float(0.5, desc="The length of the penalty for moving the cursor out of bounds, in seconds") assist_level = (1, 1) is_bmi_seed = True - + + exclude_parent_traits = ['cursor_bounds','show_environment_plane', 'show_environment'] static_states = ['wait', 'delay', 'reward', 'cursor_out_of_bounds_penalty', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.most_recent_open_eye = 0 + self.cursor_bounds = (-100., 100., -100., 100., -100., 100.) + self.show_environment_plane = True + self.show_environment = False status = dict( wait = dict(start_trial="target", start_pause="pause"), @@ -437,28 +442,35 @@ def __init__(self, *args, **kwargs): pause = dict(end_pause="wait", end_state=True), ) + def _reset_cursor(self): + self.decoder.filt.state.mean = self.init_decoder_mean.copy() + self.plant.set_visibility(False) + + + def _test_cursor_out_of_bounds_end(self, time_in_state): + return time_in_state > self.cursor_out_of_bounds_penalty_time def _start_cursor_out_of_bounds_penalty(self): - self.sync_event('TIMEOUT_PENALTY') - super()._start_timeout_penalty() + self.sync_event('OUT_OF_BOUNDS_PENALTY') + self._reset_cursor() # Hide targets for target in self.targets: target.hide() target.reset() + self._increment_tries() + self.penalty_index = 1 + def _end_cursor_out_of_bounds_penalty(self): - super()._end_timeout_penalty() self.sync_event('TRIAL_END') + #self.sync_event('TRIAL_END') def _test_cursor_out_of_bounds(self, time_in_state): width, height = self.environment_plane_size x_pos, y_pos, z_pos = self.environment_plane_origin x_bound = np.array([width/2, -width/2])+x_pos - print(f'The x_bounds are {x_bound}') y_bound = np.array([height/2, -height/2])+y_pos - print(f'The y_bounds are {y_bound}') - #traits.Tuple((-10., 10., -10., 10., -10., 10.), desc='(x min, x max, y min, y max, z min, z max)') cur_pos = self.plant.get_endpoint_pos() @@ -466,10 +478,9 @@ def _test_cursor_out_of_bounds(self, time_in_state): check_y = (cur_pos[1] >= y_bound[0]) or (cur_pos[1] <= y_bound[1]) #check_z = (cur_pos[2] >= cursor_bounds[4]) or (cur_pos[2] <= cursor_bounds[5]) - cursor_out_of_bounds = ~check_x or ~check_y# or ~check_z + cursor_out_of_bounds = check_x or check_y# or ~check_z return cursor_out_of_bounds -/home/pagaiisland/code/bmi3d/built_in_tasks/bmimultitasks.py def move_effector(self): pass @@ -492,10 +503,10 @@ def _start_target(self): # self.targets_eye[0].move_to_position(self.targs[self.target_index] - self.offset_cube) # self.targets_eye[0].show() + def _start_fixation_penalty(self): - self.plant.set_visibility(False) + self._reset_cursor() super()._start_fixation_penalty() - self.decoder.filt.state.mean = self.init_decoder_mean.copy() def _start_wait(self): super()._start_wait() diff --git a/built_in_tasks/passivetasks.py b/built_in_tasks/passivetasks.py index 1f82c6efe..e99745ce1 100644 --- a/built_in_tasks/passivetasks.py +++ b/built_in_tasks/passivetasks.py @@ -61,7 +61,7 @@ def move_effector(self): class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, FixationBMIControlMulti): """Passive viewing version of the fixation BMI task""" - pass + static_states = ['wait', 'delay', 'reward', 'cursor_out_of_bounds_penalty', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync'] class TargetCaptureVFB2DWindow(TargetCaptureVisualFeedback, WindowDispl2D): diff --git a/config/rig_defaults.py b/config/rig_defaults.py index 1efa88982..e4f92cba3 100644 --- a/config/rig_defaults.py +++ b/config/rig_defaults.py @@ -26,6 +26,7 @@ TIMEOUT_PENALTY = 0x41, DELAY_PENALTY = 0x42, FIXATION_PENALTY = 0x43, + OUT_OF_BOUNDS_PENALTY = 0x44, OTHER_PENALTY = 0x4f, CURSOR_ENTER_TARGET = 0x50, CURSOR_LEAVE_TARGET = 0x60,