From 9a2547b6f6d9da919cc96d4eaea65bc48719d1a7 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Mon, 9 Jul 2018 11:29:59 -0400 Subject: [PATCH 01/12] init commit --- expyfun/_experiment_controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/expyfun/_experiment_controller.py b/expyfun/_experiment_controller.py index 50a3749c..24360b56 100644 --- a/expyfun/_experiment_controller.py +++ b/expyfun/_experiment_controller.py @@ -1105,6 +1105,9 @@ def _log_presses(self, pressed): def check_force_quit(self): """Check to see if any force quit keys were pressed.""" self._response_handler.check_force_quit() + + def check_calibration(self): + """Check if calibration interrupt keys were pressed.""" def text_input(self, stop_key='return', pos=[0, 0], color='white', font_name='Arial', font_size=24, wrap=True, units='norm', From 76c71a55449f7391df9bebe922b8dcdb23ffdc05 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Thu, 12 Jul 2018 15:01:28 -0400 Subject: [PATCH 02/12] add check function --- expyfun/_eyelink_controller.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index 7cfeafb7..a5a8b595 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -137,6 +137,8 @@ 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. Notes ----- @@ -144,7 +146,7 @@ class EyelinkController(object): 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']): if link == 'default': link = get_config('EXPYFUN_EYELINK', None) if link is not None and pylink is None: @@ -381,6 +383,25 @@ def calibrate(self, beep=False, prompt=True): self._start_recording() return fname + def check_recalibrate(self, keys=None): + """Compare key buffer to recalibration keys and calibrate if matched. + + This function always uses the keyboard, so is part of abstraction. + """ + if keys is None: + keys = self._ec._response_handler._retrieve_keyboard_events(self.calibration_key) + 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=False) + def _stamp_trial_id(self, ids): """Send trial id message From 1c17e56a29c016bc595eacbb90e2fa6aceaef0b6 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Mon, 30 Jul 2018 13:31:42 -0400 Subject: [PATCH 03/12] add call_on_every_wait --- expyfun/_experiment_controller.py | 26 ++++++++++++++++++++++++++ expyfun/_eyelink_controller.py | 3 ++- expyfun/_utils.py | 2 ++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/expyfun/_experiment_controller.py b/expyfun/_experiment_controller.py index 24360b56..83aac51f 100644 --- a/expyfun/_experiment_controller.py +++ b/expyfun/_experiment_controller.py @@ -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 + 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) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index a5a8b595..1df73d8e 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -191,6 +191,7 @@ def __init__(self, ec, link='default', fs=1000, verbose=None, calibration_key=[' 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 @@ -400,7 +401,7 @@ def check_recalibrate(self, keys=None): ' list of strings, not a {}.' ''.format(type(keys))) if len(keys): - self.calibrate(prompt=False) + self.calibrate() def _stamp_trial_id(self, ids): """Send trial id message diff --git a/expyfun/_utils.py b/expyfun/_utils.py index 15544ec0..4aaa2217 100644 --- a/expyfun/_utils.py +++ b/expyfun/_utils.py @@ -749,6 +749,8 @@ def wait_secs(secs, ec=None): win.dispatch_events() if ec is not None: ec.check_force_quit() + for function in ec._on_every_wait: + function() def running_rms(signal, win_length): From 387d7f52cac529ac92173dd36c4c78fd5b468709 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Mon, 30 Jul 2018 13:47:15 -0400 Subject: [PATCH 04/12] PEP8 and extra lines --- expyfun/_experiment_controller.py | 3 --- expyfun/_eyelink_controller.py | 6 ++++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/expyfun/_experiment_controller.py b/expyfun/_experiment_controller.py index 83aac51f..f39c4559 100644 --- a/expyfun/_experiment_controller.py +++ b/expyfun/_experiment_controller.py @@ -1131,9 +1131,6 @@ def _log_presses(self, pressed): def check_force_quit(self): """Check to see if any force quit keys were pressed.""" self._response_handler.check_force_quit() - - def check_calibration(self): - """Check if calibration interrupt keys were pressed.""" def text_input(self, stop_key='return', pos=[0, 0], color='white', font_name='Arial', font_size=24, wrap=True, units='norm', diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index 1df73d8e..90466345 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -146,7 +146,8 @@ class EyelinkController(object): 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, calibration_key=['c']): + def __init__(self, ec, link='default', fs=1000, verbose=None, + calibration_key=['c']): if link == 'default': link = get_config('EXPYFUN_EYELINK', None) if link is not None and pylink is None: @@ -390,7 +391,8 @@ def check_recalibrate(self, keys=None): This function always uses the keyboard, so is part of abstraction. """ if keys is None: - keys = self._ec._response_handler._retrieve_keyboard_events(self.calibration_key) + check = self.calibration_key + keys = self._ec._response_handler._retrieve_keyboard_events(check) else: if isinstance(keys, string_types): keys = [keys] From ada314dd518f4747ed3030ad42725bceda486a4d Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Fri, 3 Aug 2018 13:22:25 -0400 Subject: [PATCH 05/12] add tests --- expyfun/_eyelink_controller.py | 4 ++-- expyfun/tests/test_eyelink_controller.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index 90466345..871340a9 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -385,7 +385,7 @@ def calibrate(self, beep=False, prompt=True): self._start_recording() return fname - def check_recalibrate(self, keys=None): + 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. @@ -403,7 +403,7 @@ def check_recalibrate(self, keys=None): ' list of strings, not a {}.' ''.format(type(keys))) if len(keys): - self.calibrate() + self.calibrate(prompt=prompt) def _stamp_trial_id(self, ids): """Send trial id message diff --git a/expyfun/tests/test_eyelink_controller.py b/expyfun/tests/test_eyelink_controller.py index 87c08660..139a9492 100644 --- a/expyfun/tests/test_eyelink_controller.py +++ b/expyfun/tests/test_eyelink_controller.py @@ -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) + 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]) From 2d4d6e91e2f67962c7bc528ef9444caa95adc4a3 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Fri, 3 Aug 2018 14:00:19 -0400 Subject: [PATCH 06/12] add test to wait_for_fix --- expyfun/_eyelink_controller.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index 871340a9..adc8bb6e 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -389,7 +389,15 @@ 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. + + Parameters + ---------- + keys : list or string + keys to check if prompt recalibration + prompt : bool + Whether to show the calibration prompt or not """ + calibrate = False if keys is None: check = self.calibration_key keys = self._ec._response_handler._retrieve_keyboard_events(check) @@ -404,6 +412,8 @@ def check_recalibrate(self, keys=None, prompt=True): ''.format(type(keys))) if len(keys): self.calibrate(prompt=prompt) + calibrate = True + return calibrate def _stamp_trial_id(self, ids): """Send trial id message @@ -529,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 @@ -538,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 @@ -549,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, From da1a8498d8eab4755eb1bbe8a0f0285059eeb3b1 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Fri, 3 Aug 2018 15:01:49 -0400 Subject: [PATCH 07/12] fix docs --- expyfun/_experiment_controller.py | 11 ----------- expyfun/_eyelink_controller.py | 2 +- expyfun/tests/test_eyelink_controller.py | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/expyfun/_experiment_controller.py b/expyfun/_experiment_controller.py index f39c4559..60224c34 100644 --- a/expyfun/_experiment_controller.py +++ b/expyfun/_experiment_controller.py @@ -689,17 +689,6 @@ def call_on_every_wait(self, function): 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 - 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) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index adc8bb6e..e5cfae5d 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -147,7 +147,7 @@ class EyelinkController(object): """ @verbose_dec def __init__(self, ec, link='default', fs=1000, verbose=None, - calibration_key=['c']): + calibration_key=('c',): if link == 'default': link = get_config('EXPYFUN_EYELINK', None) if link is not None and pylink is None: diff --git a/expyfun/tests/test_eyelink_controller.py b/expyfun/tests/test_eyelink_controller.py index 139a9492..3d830b23 100644 --- a/expyfun/tests/test_eyelink_controller.py +++ b/expyfun/tests/test_eyelink_controller.py @@ -48,7 +48,7 @@ def test_eyelink_methods(): el._fake_calibration = True el.calibrate(beep=False, prompt=False) fake_button_press(ec, 'c') - el.check_recalibrate(prompt=False) + assert el.check_recalibrate(prompt=False) el.check_recalibrate('k', prompt=False) el._fake_calibration = False # missing el_id From 8503b5ee785f6b9c8f4620c13c5cf37c81db5417 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Fri, 3 Aug 2018 15:28:28 -0400 Subject: [PATCH 08/12] very important missing paren --- expyfun/_eyelink_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index e5cfae5d..cda48063 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -147,7 +147,7 @@ class EyelinkController(object): """ @verbose_dec def __init__(self, ec, link='default', fs=1000, verbose=None, - calibration_key=('c',): + calibration_key=('c',)): if link == 'default': link = get_config('EXPYFUN_EYELINK', None) if link is not None and pylink is None: From 1782482db4273473cb4af20ac98bd400930f8359 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Fri, 3 Aug 2018 15:34:20 -0400 Subject: [PATCH 09/12] inevitable whitespace --- expyfun/_eyelink_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index cda48063..85dbc110 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -389,7 +389,7 @@ 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. - + Parameters ---------- keys : list or string From 2863c807c8e7d34393ad828c11318a502aa2d9db Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Sun, 5 Aug 2018 12:00:43 -0400 Subject: [PATCH 10/12] better docs --- expyfun/_experiment_controller.py | 2 +- expyfun/_eyelink_controller.py | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/expyfun/_experiment_controller.py b/expyfun/_experiment_controller.py index 60224c34..78b53553 100644 --- a/expyfun/_experiment_controller.py +++ b/expyfun/_experiment_controller.py @@ -687,7 +687,7 @@ def call_on_every_wait(self, function): Parameters ---------- function : function | None - The function to call. If ``None``, all the "on every flip" + The function to call. If ``None``, all the "on every wait" functions will be cleared. """ if function is not None: diff --git a/expyfun/_eyelink_controller.py b/expyfun/_eyelink_controller.py index 85dbc110..e6b1d20e 100644 --- a/expyfun/_eyelink_controller.py +++ b/expyfun/_eyelink_controller.py @@ -137,8 +137,9 @@ 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. + calbration_key : iterable + Keys that can be pressed to trigger recalibration when + ``EyelinkController.check_recalibrate'' is called. Notes ----- @@ -388,14 +389,20 @@ def calibrate(self, beep=False, prompt=True): 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. + The function takes key presses from the key buffer and compares them + to EyelinkController 'calibration_keys'. If one of the calibration keys + has been pressed prior to calling this function, a new calibration will + start. Instead of using the key buffer, keys can also be manually input + into the function. Parameters ---------- - keys : list or string - keys to check if prompt recalibration + keys : list or string or None + Keys to check against the set of calibration keys that trigger a + calibration. None if using keys from the key buffer. prompt : bool - Whether to show the calibration prompt or not + Whether to show the calibration prompt screen before starting the + calibation procedure, """ calibrate = False if keys is None: From d331f271b9a4091c5cafc6d31277efdd01ddcece Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Tue, 7 Aug 2018 13:51:19 -0400 Subject: [PATCH 11/12] add tests for call_on_every_wait --- expyfun/tests/test_experiment_controller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/expyfun/tests/test_experiment_controller.py b/expyfun/tests/test_experiment_controller.py index bc2df489..5df4fa6e 100644 --- a/expyfun/tests/test_experiment_controller.py +++ b/expyfun/tests/test_experiment_controller.py @@ -332,6 +332,10 @@ def test_ec(ac=None, rd=None): ec.stop() assert_true(ec._playing is False) + ec.call_on_every_wait(ec.write_data_line('waiting')) + ec.wait_secs(0.05) + ec.call_on_every_wait(None) + ec.flip(-np.inf) assert_true(ec._playing is False) ec.estimate_screen_fs() From 0a068d1552cc78821a737268283c8d66ea0f3518 Mon Sep 17 00:00:00 2001 From: Maddy Cappelloni Date: Thu, 6 Sep 2018 11:55:59 -0400 Subject: [PATCH 12/12] fix coverage maybe --- expyfun/tests/test_experiment_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expyfun/tests/test_experiment_controller.py b/expyfun/tests/test_experiment_controller.py index 5df4fa6e..a898b83e 100644 --- a/expyfun/tests/test_experiment_controller.py +++ b/expyfun/tests/test_experiment_controller.py @@ -332,7 +332,7 @@ def test_ec(ac=None, rd=None): ec.stop() assert_true(ec._playing is False) - ec.call_on_every_wait(ec.write_data_line('waiting')) + ec.call_on_every_wait(ec.check_force_quit) ec.wait_secs(0.05) ec.call_on_every_wait(None)