From 5ffff7d411c34c9e07c1b0ac848273e5a2cf8d91 Mon Sep 17 00:00:00 2001 From: lawhead Date: Thu, 26 Sep 2024 16:03:48 -0700 Subject: [PATCH 1/3] Added stoppage criteria for max incorrect --- bcipy/helpers/task.py | 19 ++++++++++++++ bcipy/helpers/tests/test_task.py | 26 ++++++++++++++----- bcipy/parameters/parameters.json | 10 ++++++- bcipy/task/paradigm/rsvp/copy_phrase.py | 15 +++++++++-- .../tests/paradigm/rsvp/test_copy_phrase.py | 1 + 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/bcipy/helpers/task.py b/bcipy/helpers/task.py index 1984bd1a4..a4e36c525 100644 --- a/bcipy/helpers/task.py +++ b/bcipy/helpers/task.py @@ -444,3 +444,22 @@ def generate_targets(alp, stim_number): targets = [target for sublist in lists for target in sublist] return targets + + +def consecutive_incorrect(target_text: str, spelled_text: str) -> int: + """Function that computes the number of consecutive symbols that + are incorrectly spelled. + + >>> consecutive_incorrect('WORLD', 'H') + 1 + >>> consecutive_incorrect('WORLD', 'W') + 0 + >>> consecutive_incorrect('WORLD', 'WOHL') + 2 + """ + if not target_text: + return len(spelled_text) + for i, character in enumerate(spelled_text): + if character != target_text[i]: + return len(spelled_text[i:]) + return 0 diff --git a/bcipy/helpers/tests/test_task.py b/bcipy/helpers/tests/test_task.py index cf45d327a..96817e319 100644 --- a/bcipy/helpers/tests/test_task.py +++ b/bcipy/helpers/tests/test_task.py @@ -1,20 +1,18 @@ import unittest - -from typing import List from collections import Counter -from mockito import unstub, mock, when, verify, verifyStubbedInvocationsAreUsed +from typing import List import numpy as np import psychopy +from mockito import mock, unstub, verify, verifyStubbedInvocationsAreUsed, when from bcipy.acquisition import LslAcquisitionClient from bcipy.acquisition.record import Record -from bcipy.task.exceptions import InsufficientDataException - -from bcipy.helpers.task import (_float_val, - calculate_stimulation_freq, construct_triggers, +from bcipy.helpers.task import (_float_val, calculate_stimulation_freq, + consecutive_incorrect, construct_triggers, generate_targets, get_data_for_decision, get_key_press, target_info) +from bcipy.task.exceptions import InsufficientDataException class TestCalculateStimulationFreq(unittest.TestCase): @@ -319,5 +317,19 @@ def test_get_data_for_decision_throws_insufficient_data_error_if_data_query_out_ get_data_for_decision(inquiry_timing, self.daq) +class TestUtils(unittest.TestCase): + """Tests for utility functions""" + + def test_consecutive_incorrect(self): + """Test calculation of consecutive incorrect""" + self.assertEqual( + 0, consecutive_incorrect(target_text='', spelled_text='')) + self.assertEqual(0, consecutive_incorrect('WORLD', '')) + self.assertEqual(0, consecutive_incorrect('WORLD', 'W')) + self.assertEqual(0, consecutive_incorrect('WORLD', 'WORLD')) + self.assertEqual(1, consecutive_incorrect('WORLD', 'H')) + self.assertEqual(2, consecutive_incorrect('WORLD', 'WOHL')) + + if __name__ == '__main__': unittest.main() diff --git a/bcipy/parameters/parameters.json b/bcipy/parameters/parameters.json index e8f7d7b81..7bd8a0860 100755 --- a/bcipy/parameters/parameters.json +++ b/bcipy/parameters/parameters.json @@ -535,7 +535,7 @@ "type": "float" }, "stim_number": { - "value": "100", + "value": "55", "section": "bci_config", "readableName": "Number of Calibration inquiries", "helpTip": "Specifies the number of inquiries to present in a calibration session. Default: 100", @@ -611,6 +611,14 @@ "recommended_values": "", "type": "int" }, + "max_incorrect": { + "value": "3", + "section": "bci_config", + "readableName": "Maximum Number of Incorrect Selections", + "helpTip": "The maximum number of consecutive incorrect selections for copy/spelling tasks. The task will end if this number is reached.", + "recommended_values": "", + "type": "int" + }, "decision_threshold": { "value": "0.8", "section": "bci_config", diff --git a/bcipy/task/paradigm/rsvp/copy_phrase.py b/bcipy/task/paradigm/rsvp/copy_phrase.py index 645490a95..8bfa138dc 100644 --- a/bcipy/task/paradigm/rsvp/copy_phrase.py +++ b/bcipy/task/paradigm/rsvp/copy_phrase.py @@ -22,7 +22,8 @@ from bcipy.helpers.session import session_excel from bcipy.helpers.stimuli import InquirySchedule, StimuliOrder from bcipy.helpers.symbols import BACKSPACE_CHAR, alphabet -from bcipy.helpers.task import (construct_triggers, fake_copy_phrase_decision, +from bcipy.helpers.task import (consecutive_incorrect, construct_triggers, + fake_copy_phrase_decision, get_device_data_for_decision, get_user_input, relative_triggers, target_info, trial_complete_message) @@ -94,7 +95,8 @@ class RSVPCopyPhraseTask(Task): 'font', 'fixation_color', 'trigger_type', 'filter_high', 'filter_low', 'filter_order', 'notch_filter_frequency', 'down_sampling_rate', 'prestim_length', 'is_txt_stim', 'lm_backspace_prob', 'backspace_always_shown', - 'decision_threshold', 'max_inq_len', 'max_inq_per_series', 'max_minutes', 'max_selections', 'min_inq_len', + 'decision_threshold', 'max_inq_len', 'max_inq_per_series', 'max_minutes', 'max_selections', 'max_incorrect', + 'min_inq_len', 'show_feedback', 'feedback_duration', 'show_preview_inquiry', 'preview_inquiry_isi', 'preview_inquiry_error_prob', 'preview_inquiry_key_input', 'preview_inquiry_length', 'preview_inquiry_progress_method', @@ -416,6 +418,15 @@ def check_stop_criteria(self) -> bool: '(configured with the max_selections parameter)') return False + if consecutive_incorrect( + target_text=self.copy_phrase, + spelled_text=self.spelled_text) >= self.parameters.get( + 'max_incorrect', 3): + self.logger.info( + 'Max number of consecutive incorrect selections reached ' + '(configured with the max_incorrect parameter)') + return False + return True def next_target(self) -> str: diff --git a/bcipy/task/tests/paradigm/rsvp/test_copy_phrase.py b/bcipy/task/tests/paradigm/rsvp/test_copy_phrase.py index 657afe28c..48d104aff 100644 --- a/bcipy/task/tests/paradigm/rsvp/test_copy_phrase.py +++ b/bcipy/task/tests/paradigm/rsvp/test_copy_phrase.py @@ -50,6 +50,7 @@ def setUp(self): 'max_minutes': 20, 'min_inq_len': 1, 'max_selections': 50, + 'max_incorrect': 10, 'notch_filter_frequency': 60.0, 'preview_inquiry_isi': 1.0, 'preview_inquiry_key_input': 'space', From a4b4d0168915d76b2141160d9d863e074bf11822 Mon Sep 17 00:00:00 2001 From: lawhead Date: Thu, 26 Sep 2024 16:49:45 -0700 Subject: [PATCH 2/3] Reverted parameter --- bcipy/parameters/parameters.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bcipy/parameters/parameters.json b/bcipy/parameters/parameters.json index 7bd8a0860..3c35e73a2 100755 --- a/bcipy/parameters/parameters.json +++ b/bcipy/parameters/parameters.json @@ -535,7 +535,7 @@ "type": "float" }, "stim_number": { - "value": "55", + "value": "100", "section": "bci_config", "readableName": "Number of Calibration inquiries", "helpTip": "Specifies the number of inquiries to present in a calibration session. Default: 100", From 2d0929f77160e683155932f9f9bb1c5ac45e5a70 Mon Sep 17 00:00:00 2001 From: lawhead Date: Thu, 26 Sep 2024 17:28:53 -0700 Subject: [PATCH 3/3] Set default max_incorrect value to 5 --- bcipy/parameters/parameters.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bcipy/parameters/parameters.json b/bcipy/parameters/parameters.json index 3c35e73a2..19756cefd 100755 --- a/bcipy/parameters/parameters.json +++ b/bcipy/parameters/parameters.json @@ -612,7 +612,7 @@ "type": "int" }, "max_incorrect": { - "value": "3", + "value": "5", "section": "bci_config", "readableName": "Maximum Number of Incorrect Selections", "helpTip": "The maximum number of consecutive incorrect selections for copy/spelling tasks. The task will end if this number is reached.",