-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Calibration Interrupt #359
base: main
Are you sure you want to change the base?
Changes from 6 commits
9a2547b
76c71a5
1c17e56
387d7f5
ada314d
2d4d6e9
da1a849
8503b5e
1782482
2863c80
d331f27
0a068d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -139,6 +139,7 @@ def __init__(self, exp_name, audio_controller=None, response_device=None, | |
# placeholder for extra actions to do on flip-and-play | ||
self._on_every_flip = [] | ||
self._on_next_flip = [] | ||
self._on_every_wait = [] | ||
self._on_trial_ok = [] | ||
# placeholder for extra actions to run on close | ||
self._extra_cleanup_fun = [] # be aware of order when adding to this | ||
|
@@ -680,6 +681,31 @@ def call_on_every_flip(self, function): | |
else: | ||
self._on_every_flip = [] | ||
|
||
def call_on_every_wait(self, function): | ||
"""Add a function to be executed on every wait. | ||
|
||
Parameters | ||
---------- | ||
function : function | None | ||
The function to call. If ``None``, all the "on every flip" | ||
functions will be cleared. | ||
|
||
See Also | ||
-------- | ||
ExperimentController.call_on_next_flip | ||
|
||
Notes | ||
----- | ||
See `flip_and_play` for order of operations. Can be called multiple | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not look correct / relevant |
||
times to add multiple functions to the queue. If the function must be | ||
called with arguments, use `functools.partial` before passing to | ||
`call_on_every_flip`. | ||
""" | ||
if function is not None: | ||
self._on_every_wait.append(function) | ||
else: | ||
self._on_every_wait = [] | ||
|
||
def _convert_units(self, verts, fro, to): | ||
"""Convert between different screen units""" | ||
check_units(to) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -137,14 +137,17 @@ class EyelinkController(object): | |
Sample rate to use. Must be one of [250, 500, 1000, 2000]. | ||
verbose : bool, str, int, or None | ||
If not None, override default verbose level (see expyfun.verbose). | ||
calbration_keys : list | ||
Keys that will trigger recalibration when check_recalibration. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you expand this docstring line a bit? The reference to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also, you say it should be |
||
|
||
Notes | ||
----- | ||
The data will be saved to the ExperimentController ``output_dir``. | ||
If this was `None`, data will be saved to the current working dir. | ||
""" | ||
@verbose_dec | ||
def __init__(self, ec, link='default', fs=1000, verbose=None): | ||
def __init__(self, ec, link='default', fs=1000, verbose=None, | ||
calibration_key=['c']): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not use mutable default arguments. Tuple is immutable so |
||
if link == 'default': | ||
link = get_config('EXPYFUN_EYELINK', None) | ||
if link is not None and pylink is None: | ||
|
@@ -189,6 +192,7 @@ def __init__(self, ec, link='default', fs=1000, verbose=None): | |
self._current_open_file = None | ||
logger.debug('EyeLink: Setup complete') | ||
self._ec.flush() | ||
self.calibration_key = calibration_key | ||
|
||
def _setup(self, fs=1000): | ||
"""Start up Eyelink | ||
|
@@ -381,6 +385,36 @@ def calibrate(self, beep=False, prompt=True): | |
self._start_recording() | ||
return fname | ||
|
||
def check_recalibrate(self, keys=None, prompt=True): | ||
"""Compare key buffer to recalibration keys and calibrate if matched. | ||
|
||
This function always uses the keyboard, so is part of abstraction. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand ths note. |
||
|
||
Parameters | ||
---------- | ||
keys : list or string | ||
keys to check if prompt recalibration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keys to check against the (set of) key(s) that trigger calibration. |
||
prompt : bool | ||
Whether to show the calibration prompt or not | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whether to show the calibration prompt screen before starting the calibration procedure |
||
""" | ||
calibrate = False | ||
if keys is None: | ||
check = self.calibration_key | ||
keys = self._ec._response_handler._retrieve_keyboard_events(check) | ||
else: | ||
if isinstance(keys, string_types): | ||
keys = [keys] | ||
if isinstance(keys, list): | ||
keys = [k for k in keys if k in self.calibration_key] | ||
else: | ||
raise TypeError('Calibration checking requires a string or ' | ||
' list of strings, not a {}.' | ||
''.format(type(keys))) | ||
if len(keys): | ||
self.calibrate(prompt=prompt) | ||
calibrate = True | ||
return calibrate | ||
|
||
def _stamp_trial_id(self, ids): | ||
"""Send trial id message | ||
|
||
|
@@ -505,7 +539,7 @@ def wait_for_fix(self, fix_pos, fix_time=0., tol=100., max_wait=np.inf, | |
""" | ||
# initialize eye position to be outside of target | ||
fix_success = False | ||
|
||
calibrate = False | ||
# sample eye position for el.fix_hold seconds | ||
time_in = time.time() | ||
time_out = time_in + max_wait | ||
|
@@ -514,7 +548,7 @@ def wait_for_fix(self, fix_pos, fix_time=0., tol=100., max_wait=np.inf, | |
raise ValueError('fix_pos must be a 2-element array-like vector') | ||
fix_pos = self._ec._convert_units(fix_pos[:, np.newaxis], units, 'pix') | ||
fix_pos = fix_pos[:, 0] | ||
while (time.time() < time_out and not | ||
while (time.time() < time_out and not calibrate and not | ||
(fix_success and time.time() - time_in >= fix_time)): | ||
# sample eye position | ||
eye_pos = self.get_eye_position() # in pixels | ||
|
@@ -525,7 +559,10 @@ def wait_for_fix(self, fix_pos, fix_time=0., tol=100., max_wait=np.inf, | |
time_in = time.time() | ||
self._ec._response_handler.check_force_quit() | ||
self._ec.wait_secs(check_interval) | ||
|
||
calibrate = self.check_recalibrate() | ||
# rerun wait_for_fix if recalibrated | ||
if calibrate: | ||
fix_success = False | ||
return fix_success | ||
|
||
def maintain_fix(self, fix_pos, check_duration, tol=100., period=.250, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,8 @@ | |
import warnings | ||
|
||
from expyfun import EyelinkController, ExperimentController | ||
from expyfun._utils import _TempDir, _hide_window, requires_opengl21 | ||
from expyfun._utils import (_TempDir, _hide_window, requires_opengl21, | ||
fake_button_press) | ||
|
||
warnings.simplefilter('always') | ||
|
||
|
@@ -46,6 +47,9 @@ def test_eyelink_methods(): | |
# run much of the calibration code, but don't *actually* do it | ||
el._fake_calibration = True | ||
el.calibrate(beep=False, prompt=False) | ||
fake_button_press(ec, 'c') | ||
el.check_recalibrate(prompt=False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't you assert that this is True ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test gets hung up on the prompt screen. Could put in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand. It currently gets stuck, or will get stuck with my suggested addition of an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Read too quickly, you are correct |
||
el.check_recalibrate('k', prompt=False) | ||
el._fake_calibration = False | ||
# missing el_id | ||
assert_raises(KeyError, ec.identify_trial, ec_id='foo', ttl_id=[0]) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this say
all the "on every wait"
?