Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 158 additions & 1 deletion built_in_tasks/bmimultitasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,161 @@ class BMIControlMultiDirectionConstraint(BMIControlMultiMixin, ScreenReachAngle)
'''
Adds an additional constraint that the direction of travel must be within a certain angle
'''
pass
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")
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"),
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",
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),
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 _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('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):
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
y_bound = np.array([height/2, -height/2])+y_pos

#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] >= 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
return cursor_out_of_bounds

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._reset_cursor()
super()._start_fixation_penalty()

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)
#eye_within_fixation_buffer_cursor =
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#(eye_d > self.target_radius + self.fixation_radius_buffer)
119 changes: 4 additions & 115 deletions built_in_tasks/passivetasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from .target_graphics import *

from .bmimultitasks import BMIControlMultiEyeConstrained
from .bmimultitasks import FixationBMIControlMulti

bmi_ssm_options = ['Endpt2D', 'Tentacle', 'Joint2L']

Expand Down Expand Up @@ -57,122 +57,11 @@ 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)
#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#(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")



class TargetCaptureVisualFeedbackEyeConstrained(EndPostureFeedbackController, FixationBMIControlMulti):
"""Passive viewing version of the fixation BMI task"""
static_states = ['wait', 'delay', 'reward', 'cursor_out_of_bounds_penalty', 'fixation_penalty', 'pause', 'delay_penalty','timeout_penalty', 'sync']


class TargetCaptureVFB2DWindow(TargetCaptureVisualFeedback, WindowDispl2D):
Expand Down
1 change: 1 addition & 0 deletions config/rig_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions db/tracker/json_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion features/ecube_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down