Skip to content
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

Matrix calibration refinements #262

Merged
merged 3 commits into from
Jan 26, 2023
Merged
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
23 changes: 12 additions & 11 deletions bcipy/display/demo/matrix/demo_calibration_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
from bcipy.display import init_display_window

info = InformationProperties(
info_color=['White'],
info_color=['white'],
info_pos=[(-.5, -.75)],
info_height=[0.1],
info_font=['Arial'],
info_text=['Matrix Calibration Demo'],
)
task_display = TaskDisplayProperties(task_color=['White'],
task_display = TaskDisplayProperties(task_color=['white'],
task_pos=(-.8, .85),
task_font='Arial',
task_height=.1,
Expand All @@ -39,36 +39,37 @@
matrix_display = MatrixDisplay(win, experiment_clock, stim_properties,
task_display, info)

time_target = 1
time_target = 2
time_fixation = 2
time_flash = 0.25
timing = [time_target] + [time_fixation] + [time_flash] * 5
colors = ['green', 'lightgray'] + ['white'] * 5
task_buffer = 2

matrix_display.schedule_to(stimuli=['A', '+', 'F', '<', 'A', 'B', 'C'],
timing=timing,
colors=[])
matrix_display.update_task_state(text='1/100', color_list=['White'])
colors=colors)
matrix_display.update_task_state(text='1/100', color_list=['white'])
matrix_display.do_inquiry()
core.wait(task_buffer)

matrix_display.schedule_to(stimuli=['B', '+', 'F', '<', 'A', 'B', 'C'],
timing=timing,
colors=[])
matrix_display.update_task_state(text='2/100', color_list=['White'])
colors=colors)
matrix_display.update_task_state(text='2/100', color_list=['white'])
matrix_display.do_inquiry()
core.wait(task_buffer)

matrix_display.schedule_to(stimuli=['C', '+', 'F', '<', 'A', 'B', 'C'],
timing=timing,
colors=[])
matrix_display.update_task_state(text='3/100', color_list=['White'])
colors=colors)
matrix_display.update_task_state(text='3/100', color_list=['white'])
matrix_display.do_inquiry()
core.wait(task_buffer)

matrix_display.schedule_to(stimuli=['<', '+', 'F', '<', 'A', 'B', 'C'],
timing=timing,
colors=[])
matrix_display.update_task_state(text='4/100', color_list=['White'])
colors=colors)
matrix_display.update_task_state(text='4/100', color_list=['white'])
matrix_display.do_inquiry()
core.wait(task_buffer)
61 changes: 42 additions & 19 deletions bcipy/display/paradigm/matrix/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SymbolDuration(NamedTuple):
"""Represents a symbol and its associated duration to display"""
symbol: str
duration: float
color: str = 'white'


class MatrixDisplay(Display):
Expand Down Expand Up @@ -68,6 +69,7 @@ def __init__(

self.stimuli_inquiry = []
self.stimuli_timing = []
self.stimuli_colors = []
self.stimuli_font = stimuli.stim_font

assert stimuli.is_txt_stim, "Matrix display is a text only display"
Expand All @@ -80,6 +82,7 @@ def __init__(
self.position_increment = self.grid_stimuli_height + .05
self.max_grid_width = 0.7

self.grid_color = 'white'
self.start_opacity = 0.15
self.highlight_opacity = 0.95
self.full_grid_opacity = 0.95
Expand All @@ -106,16 +109,22 @@ def schedule_to(self, stimuli: list, timing: list, colors: list) -> None:
timing(list[float]): list of timings of stimuli
colors(list[string]): list of colors
"""
assert len(stimuli) == len(timing), "each stimuli must have a timing value"
assert len(stimuli) == len(
timing), "each stimuli must have a timing value"
self.stimuli_inquiry = stimuli
self.stimuli_timing = timing
if colors:
assert len(stimuli) == len(colors), "each stimuli must have a color"
self.stimuli_colors = colors
else:
self.stimuli_colors = [self.grid_color] * len(stimuli)

def symbol_durations(self) -> List[SymbolDuration]:
"""Symbols associated with their duration for the currently configured
stimuli_inquiry."""
return [
SymbolDuration(*sti)
for sti in zip(self.stimuli_inquiry, self.stimuli_timing)
for sti in zip(self.stimuli_inquiry, self.stimuli_timing, self.stimuli_colors)
]

def add_timing(self, stimuli: str):
Expand Down Expand Up @@ -157,6 +166,7 @@ def build_grid(self) -> Dict[str, visual.TextStim]:
for sym in self.symbol_set:
grid[sym] = visual.TextStim(win=self.window,
text=sym,
color=self.grid_color,
opacity=self.start_opacity,
pos=pos,
height=self.grid_stimuli_height)
Expand All @@ -172,18 +182,27 @@ def increment_position(self, pos: Tuple[float, float]) -> Tuple[float, float]:
x_coordinate = self.position[0]
return (x_coordinate, y_coordinate)

def draw_grid(self, opacity: float = 1, highlight: Optional[str] = None):
def draw_grid(self,
opacity: float = 1,
color: Optional[str] = 'white',
highlight: Optional[str] = None,
highlight_color: Optional[str] = None):
"""Draw the grid.

Parameters
----------
opacity - opacity for each item in the matrix
color - optional color for each item in the matrix
highlight - optional stim label for the item to be highlighted
(rendered using the highlight_opacity).
highlight_color - optional color to use for rendering the
highlighted stim.
"""
for symbol, stim in self.stim_registry.items():
stim.setOpacity(self.highlight_opacity if highlight ==
symbol else opacity)
stim.setColor(highlight_color if highlight_color and
highlight == symbol else color)
stim.draw()

def prompt_target(self, target: SymbolDuration) -> float:
Expand All @@ -196,31 +215,32 @@ def prompt_target(self, target: SymbolDuration) -> float:
"""
# register any timing and marker callbacks
self.window.callOnFlip(self.add_timing, target.symbol)

target_stim = visual.TextStim(win=self.window,
font=self.stimuli_font,
text=f'Target: {target.symbol}',
height=.25,
color='Green',
wrapWidth=2)
target_stim.draw()
self.draw_static()
self.window.flip()
core.wait(target.duration)
self.draw(grid_opacity=self.start_opacity,
duration=target.duration,
highlight=target.symbol,
highlight_color=target.color)

def draw(self,
grid_opacity: float,
duration: float = None,
highlight: Optional[str] = None):
grid_color: Optional[str] = None,
duration: Optional[float] = None,
highlight: Optional[str] = None,
highlight_color: Optional[str] = None):
"""Draw all screen elements and flip the window.

Parameters
----------
grid_opacity - opacity value to use on all grid symbols
duration - optioanl seconds to wait after flipping the window.
grid_color - optional color to use for all grid symbols
duration - optional seconds to wait after flipping the window.
highlight - optional symbol to highlight in the grid.
highlight_color - optional color to use for rendering the
highlighted stim.
"""
self.draw_grid(opacity=grid_opacity, highlight=highlight)
self.draw_grid(opacity=grid_opacity,
color=grid_color or self.grid_color,
highlight=highlight,
highlight_color=highlight_color)
self.draw_static()
self.window.flip()
if duration:
Expand All @@ -237,6 +257,8 @@ def animate_scp(self, fixation: SymbolDuration,
# Flashing the grid at full opacity is considered fixation.
self.window.callOnFlip(self.add_timing, fixation.symbol)
self.draw(grid_opacity=self.full_grid_opacity,
grid_color=(fixation.color if self.should_prompt_target else
self.grid_color),
duration=fixation.duration / 2)
self.draw(grid_opacity=self.start_opacity,
duration=fixation.duration / 2)
Expand All @@ -245,7 +267,8 @@ def animate_scp(self, fixation: SymbolDuration,
self.window.callOnFlip(self.add_timing, stim.symbol)
self.draw(grid_opacity=self.start_opacity,
duration=stim.duration,
highlight=stim.symbol)
highlight=stim.symbol,
highlight_color=stim.color)
self.draw(self.start_opacity)

def wait_screen(self, message: str, message_color: str) -> None:
Expand Down
2 changes: 2 additions & 0 deletions bcipy/display/tests/paradigm/matrix/test_matrix_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def setUp(self):
when(psychopy.visual).TextStim(
win=self.window,
text=any(),
color=any(),
opacity=any(),
pos=any(),
height=any(),
Expand Down Expand Up @@ -149,6 +150,7 @@ def test_build_grid(self):
win=self.window,
height=any(),
text=any(),
color=any(),
pos=any(),
opacity=any()
).thenReturn(self.text_stim_mock)
Expand Down
5 changes: 4 additions & 1 deletion bcipy/task/paradigm/matrix/calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ def __init__(self, win, daq, parameters, file_save):
parameters['time_prompt'], parameters['time_fixation'],
parameters['time_flash']
]
self.color = [parameters['stim_color']]
self.color = [
parameters['target_color'], parameters['fixation_color'],
parameters['stim_color']
]
self.task_info_color = parameters['task_color']
self.stimuli_height = parameters['stim_height']
self.is_txt_stim = parameters['is_txt_stim']
Expand Down