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
2 changes: 1 addition & 1 deletion eegnb/experiments/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .visual_n170.n170 import VisualN170
from .visual_p300.p300 import VisualP300
from .visual_ssvep.ssvep import VisualSSVEP

from .semantic_n400.n400 import TextN400
# PTB does not yet support macOS Apple Silicon,
# this experiment needs to run as i386 if on macOS.
import sys
Expand Down
139 changes: 139 additions & 0 deletions eegnb/experiments/semantic_n400/n400.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
""" eeg-notebooks/eegnb/experiments/semantic_n400/n400.py """

import os
from time import time
from glob import glob
from random import choice, shuffle
from psychopy import visual, core, event

from eegnb.devices.eeg import EEG
from eegnb.stimuli import N400_TEXT
from eegnb.experiments import Experiment
from typing import Optional

import pandas as pd
import numpy as np


class TextN400(Experiment.BaseExperiment):

def __init__(self, duration=120, eeg: Optional[EEG]=None, save_fn=None,
n_trials=200, iti=0.5, soa=0.5, jitter=0.2):


exp_name = "Text N400"

super(TextN400, self).__init__(exp_name, duration, eeg, save_fn, n_trials, iti, soa, jitter)



self.markernames = {
'congruent': 1,
'incongruent': 2
}

def load_stimulus(self):
"""
Loads the text stimuli (congruent and incongruent sentences/words)
from the specified folders.
"""

text_stim_path = N400_TEXT


congruent_files = glob(os.path.join(text_stim_path, "congruent", "*.txt"))
self.congruent_stims = []
for f in congruent_files:
with open(f, 'r') as file:

self.congruent_stims.extend([line.strip() for line in file if line.strip()])


incongruent_files = glob(os.path.join(text_stim_path, "incongruent", "*.txt"))
self.incongruent_stims = []
for f in incongruent_files:
with open(f, 'r') as file:
self.incongruent_stims.extend([line.strip() for line in file if line.strip()])


num_congruent = len(self.congruent_stims)
num_incongruent = len(self.incongruent_stims)


total_stims_available = num_congruent + num_incongruent
if total_stims_available == 0:
raise ValueError("No stimulus text files found in the congruent or incongruent folders!")


ratio_congruent = num_congruent / total_stims_available
ratio_incongruent = num_incongruent / total_stims_available

trials_to_use_congruent = min(num_congruent, int(self.n_trials * ratio_congruent))
trials_to_use_incongruent = min(num_incongruent, int(self.n_trials * ratio_incongruent))


self.n_trials = trials_to_use_congruent + trials_to_use_incongruent


selected_congruent = np.random.choice(self.congruent_stims, size=trials_to_use_congruent, replace=True).tolist()
selected_incongruent = np.random.choice(self.incongruent_stims, size=trials_to_use_incongruent, replace=True).tolist()

trial_data = []
for stim_text in selected_congruent:
trial_data.append({'stim_text': stim_text, 'type': 'congruent'})
for stim_text in selected_incongruent:
trial_data.append({'stim_text': stim_text, 'type': 'incongruent'})


shuffle(trial_data)


self.trials = pd.DataFrame(trial_data)
self.trials['iti'] = self.iti + np.random.rand(len(self.trials)) * self.jitter
self.trials['soa'] = self.soa



self.text_stim = visual.TextStim(win=self.window, text="", color='white', height=0.1)
self.fixation = visual.GratingStim(win=self.window, size=0.2, pos=[0, 0], sf=0, rgb=[1, 0, 0])
self.fixation.setAutoDraw(True)


self.window.flip()

return None

def present_stimulus(self, idx: int):
"""
Presents the current word/sentence stimulus and sends a marker to the EEG.
"""

current_trial = self.trials.iloc[idx]
word_to_display = current_trial['stim_text']
stim_type = current_trial['type']
marker_value = self.markernames[stim_type]


self.text_stim.text = word_to_display
self.text_stim.draw()
self.fixation.draw()


self.window.flip()


if self.eeg:
timestamp = time()

marker = [int(marker_value)] if self.eeg.backend == "muselsl" else int(marker_value)
self.eeg.push_sample(marker=marker, timestamp=timestamp)


core.wait(self.soa)


self.text_stim.text = ''
self.window.flip()


core.wait(current_trial['iti'])
1 change: 1 addition & 0 deletions eegnb/stimuli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

FACE_HOUSE = path.join(path.dirname(__file__), "visual", "face_house")
CAT_DOG = path.join(path.dirname(__file__), "visual", "cats_dogs")
N400_TEXT = path.join(path.dirname(path.abspath(__file__)), "text")
3 changes: 3 additions & 0 deletions eegnb/stimuli/text/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
All the text data were taken from N400Stimset_stimuli_parameters.tsv from the paper given below
Evoking the N400 event-related potential (ERP) component using a publicly available novel set of sentences with semantically incongruent or congruent eggplants
they are Public Domain (CC0).
1 change: 1 addition & 0 deletions eegnb/stimuli/text/congruent/C_text1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The cake is in the oven to bake.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/congruent/C_text2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In the winter the pond will freeze.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/congruent/C_text3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The soccer team scored a goal.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/congruent/C_text4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
My mom puts money in the bank.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/congruent/C_text5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
His mom makes her own pie crust.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/incongruent/IC_text1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
He puts gas in his chest.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/incongruent/IC_text2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
He put on his shoes to go apple.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/incongruent/IC_text3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Babies sleep in shot.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/incongruent/IC_text4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The angry boys got in a mouse.
1 change: 1 addition & 0 deletions eegnb/stimuli/text/incongruent/IC_text5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The cake is in the oven to milk.