diff --git a/.gitignore b/.gitignore index bae6b1ee..e1cb00f0 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ stimuli/strange_stories/clips stimuli/strange_stories/orig stimuli/liking/clips +.idea/ + diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index 2c20fd28..3ebe37a4 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -8,7 +8,7 @@ import numpy as np import random from psychopy import prefs -prefs.hardware['audioLib'] = ['sounddevice'] +prefs.hardware['audioLib'] = ['sounddevice'] from psychopy import visual, sound, core, event from pyglet.window import key import MultiTaskBattery.utils as ut @@ -17,8 +17,7 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip import gc import math - - +import json @@ -412,10 +411,375 @@ def run_trial(self, trial): return trial +class FingerRhythmic(Task): + def __init__(self, info, screen, ttl_clock, const, subj_id): + super().__init__(info, screen, ttl_clock, const, subj_id) + + def init_task(self): + self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') + self.corr_key = [self.trial_info['key_one'].iloc[0]] + + def display_instructions(self): + """ + displays the instruction for the task + """ + + str1 = f"Tap along to the tones using the {self.corr_key[0]} key." + str2 = f"Keep tapping at the same pace when the tones stop." + self.instruction_text = f"{self.descriptive_name} Task\n\n {str1} \n {str2}" + instr_visual = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1]) + instr_visual.draw() + self.window.flip() + + def run_trial(self, trial): + """ Runs a single trial of the Finger Rhythmic task """ + + event.clearEvents() + txt = (f"New trial starts now") # this text shows when a new trials starts + visual.TextStim(self.window, text=txt,height=self.const.instruction_text_height, color=[-1, -1, -1]).draw() + self.window.flip() + self.ttl_clock.wait_until(self.ttl_clock.get_time() + 2) + + self.screen.fixation_cross() + event.clearEvents() + clk = self.ttl_clock.clock + t0 = clk.getTime() # trial anchor (TTL) + + # --- Play FIRST tone now, then use THIS time as the grid anchor + beep = sound.Sound(value=1000, secs=0.05, sampleRate=48000, stereo=True) + beep.play() + t_first = clk.getTime() # when we triggered the first tone (TTL) + ioi = 0.65 + expected = [(t_first - t0) + i*ioi for i in range(12)] # expected, aligned to first tone + + # Track beep times for timing verification (quiet by default) + beep_times = [t_first] + + taps_rel = [] + + # --- Remaining 11 tones by absolute TTL deadlines; collect keys in-between + for i in range(1, 12): + deadline = t_first + i*ioi + while True: + now = clk.getTime() + if now >= deadline: + # Create a new Sound object for each beep to ensure it plays + beep = sound.Sound(value=1000, secs=0.05, sampleRate=48000, stereo=True) + beep.play() + beep_time = clk.getTime() + beep_times.append(beep_time) + break + res = event.waitKeys(maxWait=deadline - now, + keyList=self.const.response_keys, + timeStamped=clk) + if res: + for _, ts in res: + taps_rel.append(ts - t0) + + # --- Silent phase: collect until absolute end_time + end_abs = float(trial['end_time']) + while True: + now = clk.getTime() + if now >= end_abs: + break + res = event.waitKeys(maxWait=end_abs - now, + keyList=self.const.response_keys, + timeStamped=clk) + if res: + for _, ts in res: + taps_rel.append(ts - t0) + + # --- Self-paced ISIs only (strictly after last tone onset) + last_tone_t = expected[-1] # relative to t0 + self_taps = [t for t in taps_rel if t > last_tone_t] + isis = np.diff(self_taps) if len(self_taps) > 1 else np.array([], float) + isis = isis[(isis >= 0.300) & (isis <= 0.900)] + + trial['iri_ms_mean'] = float(np.mean(isis) * 1000.0) if isis.size else np.nan + trial['iri_ms_sd'] = float(np.std(isis) * 1000.0) if isis.size else np.nan + trial['iris_ms_json'] = json.dumps((isis * 1000.0).tolist()) + trial['expected_rel_s_json'] = json.dumps([e for e in expected]) # seconds rel to t0 + trial['tap_rel_s_json'] = json.dumps(taps_rel) + # Save beep times relative to trial start (t0) + beep_times_rel = [bt - t0 for bt in beep_times] + trial['beep_times_rel_s_json'] = json.dumps(beep_times_rel) + + return trial + + +class TimePerception(Task): + def __init__(self, info, screen, ttl_clock, const, subj_id): + super().__init__(info, screen, ttl_clock, const, subj_id) + self.feedback_type = 'acc+rt' + + def init_task(self): + self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') + self.corr_key = [self.trial_info['key_one'].iloc[0], self.trial_info['key_two'].iloc[0]] + # one tone for both modalities + self.tone = sound.Sound(value=1000, secs=0.050, sampleRate=48000, stereo=True) # 1000 Hz, 50 ms + + # PEST state per side + mod = str(self.trial_info['modality'].iloc[0]).lower() + if mod == 'time': + # start ±120 ms from 400; step 40; min step 8; directions tracked + self.stair = { + 'shorter': {'curr': 280.0, 'step': 40.0, 'min_step': 8.0, 'last_dir': 0, 'same_dir': 0}, + 'longer': {'curr': 520.0, 'step': 40.0, 'min_step': 8.0, 'last_dir': 0, 'same_dir': 0}, + } + else: # 'volume' (quieter/louder) + # start ±1.62 dB from 73; step 1.08; min step 0.27 + self.stair = { + 'quieter': {'curr': 71.38, 'step': 1.08, 'min_step': 0.27, 'last_dir': 0, 'same_dir': 0}, + 'louder': {'curr': 74.62, 'step': 1.08, 'min_step': 0.27, 'last_dir': 0, 'same_dir': 0}, + } + + def display_instructions(self): + mod = str(self.trial_info['modality'].iloc[0]).lower() + if mod == 'time': + str1 = f"You will hear two pairs of tones." + str2 = f"Press [{self.corr_key[0]}] if the SECOND interval is shorter." + str3 = f"Press [{self.corr_key[1]}] if the SECOND interval is longer." + str4 = "The first pair is always the same." + self.instruction_text = f"{self.descriptive_name} Task\n\n {str1} \n {str2} \n {str3} \n {str4}" + + else: + str1 = f"You will hear two pairs of tones." + str2 = f"Press [{self.corr_key[0]}] if the SECOND interval is quieter." + str3 = f"Press [{self.corr_key[1]}] if the SECOND interval is louder." + str4 = "The first pair is always the same." + self.instruction_text = f"{self.descriptive_name} Task\n\n {str1} \n {str2} \n {str3} \n {str4}" + visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1]).draw() + self.window.flip() + + def run_trial(self, trial): + event.clearEvents() + clk = self.ttl_clock + mod = str(trial['modality']).lower() + side = str(trial['side']).lower() + + # fixation + self.screen.fixation_cross() + + # --- Pair 1 (standard @ 0.7 amp, 400 ms gap) --- + self.tone.setVolume(0.7) + t1 = clk.get_time() + self.tone.play() + clk.wait_until(t1 + 0.050) + clk.wait_until(t1 + 0.050 + 0.400) + self.tone.play() + clk.wait_until(t1 + 2*0.050 + 0.400) + + # inter-pair gap + clk.wait_until(clk.get_time() + 1.000) + + # --- Pair 2 (comparison) --- + st = self.stair[side] # PEST state for this side + + if mod == 'time': + # snap to 8 ms grid within side range + if side == 'shorter': + comp_ms = max(160, min(392, int(round(st['curr'] / 8.0) * 8))) + else: + comp_ms = max(408, min(640, int(round(st['curr'] / 8.0) * 8))) + + t2 = clk.get_time() + self.tone.setVolume(0.7) + self.tone.play() + clk.wait_until(t2 + 0.050) + clk.wait_until(t2 + 0.050 + (comp_ms / 1000.0)) + self.tone.play() + clk.wait_until(t2 + 2*0.050 + (comp_ms / 1000.0)) + + trial['comparison_ms'] = float(comp_ms) + trial['stair_step_ms'] = float(st['step']) # log step used this trial + + else: # volume (quieter/louder), grid 0.27 dB + if side == 'quieter': + comp_db = max(64.9, min(72.73, float(st['curr']))) + else: + comp_db = min(81.1, max(73.27, float(st['curr']))) + + comp_amp = float(min(1.0, 0.7 * (10 ** ((comp_db - 73.0) / 20.0)))) + + t2 = clk.get_time() + self.tone.setVolume(comp_amp) + self.tone.play() + clk.wait_until(t2 + 0.050) + clk.wait_until(t2 + 0.050 + 0.400) + self.tone.setVolume(comp_amp) + self.tone.play() + clk.wait_until(t2 + 2*0.050 + 0.400) + self.tone.setVolume(1.0) + + trial['comparison_dba'] = float(comp_db) + trial['stair_step_dba'] = float(st['step']) # log step used this trial + + # --- response window --- + trial['response'], trial['rt'] = self.wait_response(clk.get_time(), float(trial['question_dur'])) + trial['correct'] = (trial['response'] == trial['trial_type']) + + # --- PEST update (classic + border-safe): halve on reversal; double after two same-direction moves --- + # Define movement: toward standard if correct, away if incorrect. + # Use unified sign for direction comparison: toward = -1, away = +1. + move_dir = (-1 if trial['correct'] else +1) + + # Store the old level so we can tell if we actually moved after clamping. + old_curr = st['curr'] + + # 1. Propose + clamp + if mod == 'time': + if side == 'shorter': + # correct => toward standard => make interval longer => +step + # incorrect => away => make interval even shorter => -step + st['curr'] += (+st['step'] if trial['correct'] else -st['step']) + # snap to 8 ms grid and clamp to that side's allowed range + st['curr'] = float(int(round(st['curr'] / 8.0) * 8)) + if st['curr'] < 160.0: + st['curr'] = 160.0 + if st['curr'] > 392.0: + st['curr'] = 392.0 + + else: # side == 'longer' + # correct => toward standard => make interval shorter => -step + # incorrect => away => make interval even longer => +step + st['curr'] += (-st['step'] if trial['correct'] else +st['step']) + st['curr'] = float(int(round(st['curr'] / 8.0) * 8)) + if st['curr'] < 408.0: + st['curr'] = 408.0 + if st['curr'] > 640.0: + st['curr'] = 640.0 + + else: + # volume + if side == 'quieter': + # correct => toward standard (louder) => +step in dB toward 73 + # incorrect => away (quieter) => -step + st['curr'] += (+st['step'] if trial['correct'] else -st['step']) + if st['curr'] < 64.9: + st['curr'] = 64.9 + if st['curr'] > 72.73: + st['curr'] = 72.73 + + else: # side == 'louder' + # correct => toward standard (quieter) => -step + # incorrect => away (louder) => +step + st['curr'] += (-st['step'] if trial['correct'] else +st['step']) + if st['curr'] < 73.27: + st['curr'] = 73.27 + if st['curr'] > 81.1: + st['curr'] = 81.1 + + # 2. Did we actually move? + actually_moved = (st['curr'] != old_curr) + + if actually_moved: + # Normal PESt adaptation only if we escaped the boundary. + + if st['last_dir'] != 0 and move_dir != st['last_dir']: + # reversal -> halve step (but not below min_step), reset consecutive counter + st['step'] = max(st['min_step'], st['step'] / 2.0) + st['same_dir'] = 0 + + else: + # same direction as last (or first informative move) + if st['last_dir'] == 0 or move_dir == st['last_dir']: + st['same_dir'] += 1 + else: + # new direction but last_dir was 0 should already be covered above, + # but keep a safe fallback + st['same_dir'] = 1 + + # after two same-direction moves -> double step + if st['same_dir'] >= 2: + st['step'] = st['step'] * 2.0 + st['same_dir'] = 0 # require two more same-direction moves for next doubling + + # update last_dir ONLY when a real move happened + st['last_dir'] = move_dir + + else: + # We hit a border and got clamped. Do NOT adapt step. Do NOT change same_dir. Do NOT touch last_dir. + pass + + self.stair[side] = st + self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) + return trial + +class SensMotControl(Task): + def __init__(self, info, screen, ttl_clock, const, subj_id): + super().__init__(info, screen, ttl_clock, const, subj_id) + self.feedback_type = 'acc+rt' + + def init_task(self): + trial_info_file = self.const.task_dir / self.name / self.task_file + self.trial_info = pd.read_csv(trial_info_file, sep='\t') + self.corr_key = [self.trial_info['key_one'].iloc[0], self.trial_info['key_two'].iloc[0]] + + def display_instructions(self): + """ + displays the instruction for the task + """ + cond = str(self.trial_info['condition'].iloc[0]) + if cond == 'blue': + str1 = f"When the circle turns BLUE, press {self.corr_key[0]}." + str2 = f"When the circle turns WHITE, do nothing." + self.instruction_text = f"{self.descriptive_name} Task\n\n {str1} \n {str2}" + elif cond == 'red': + str1 = f"When the circle turns RED, press {self.corr_key[1]}." + str2 = f"When the circle turns WHITE, do nothing." + self.instruction_text = f"{self.descriptive_name} Task\n\n {str1} \n {str2}" + else: + str1 = f"When the circle turns BLUE, press {self.corr_key[0]}." + str2 = f"When the circle turns RED, press {self.corr_key[1]}." + str3 = f"When the circle turns WHITE, do nothing." + self.instruction_text = f"{self.descriptive_name} Task\n\n {str1} \n {str2} \n {str3}" + + instr_visual = visual.TextStim(self.window, text=self.instruction_text, + height=self.const.instruction_text_height, color=[-1, -1, -1], wrapWidth=25, pos=(0, 0)) + instr_visual.draw() + self.window.flip() + + def run_trial(self, trial): + + event.clearEvents() + + # --- 1) Fixation circle (1000 ms) --- + visual.Circle(self.window, radius=3, edges=128, lineWidth=4, lineColor='black', fillColor=None).draw() + self.window.flip() + self.ttl_clock.wait_until(self.ttl_clock.get_time() + 1) + self.ttl_clock.update() + + # --- 2) Colored circle (2000 ms) --- + visual.Circle(self.window, radius=3, edges=128,lineWidth=6, fillColor= trial['stim'], lineColor=trial['stim']).draw() + self.window.flip() + + # collect responses 0: no response 1-4: key pressed + trial['response'], trial['rt'] = self.wait_response(self.ttl_clock.get_time(), trial['question_dur']) + trial['correct'] = (trial['response'] == trial['trial_type']) + + # display trial feedback + if trial['display_trial_feedback']: + # show feedback, then let the schedule absorb any remaining time + self.display_trial_feedback(True, trial['correct']) + else: + # no feedback: go BLANK immediately and stay blank until end_time (we don't want the fixation cross here) + self.window.flip(clearBuffer=True) + while self.ttl_clock.get_time() < trial['end_time']: + # flip blank frames so the window stays responsive + self.window.flip() + self.ttl_clock.update() + + return trial + + class SpatialNavigation(Task): def __init__(self, info, screen, ttl_clock, const, subj_id): super().__init__(info, screen, ttl_clock, const, subj_id) + def init_task(self): + self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') + self.corr_key = [self.trial_info['key_false'].iloc[0],self.trial_info['key_true'].iloc[0]] + def display_instructions(self): start_location = self.trial_info.iloc[0]['location_1'] @@ -452,7 +816,7 @@ def init_task(self): self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') self.corr_key = [self.trial_info['key_false'].iloc[0],self.trial_info['key_true'].iloc[0]] - + def display_instructions(self): """ displays the instruction for the task @@ -474,7 +838,7 @@ def run_trial(self, trial): # Set text height according to constants or default value height = getattr(self.const, 'theory_of_mind_text_height', None) or 1.25 - wrapWidth=25 + wrapWidth=25 # Display story story_clean = ' '.join(trial['story'].split('\n')) @@ -653,8 +1017,8 @@ def create_grid(self, sequence=None, position='center',grid_size=(3,4)): fill_color = 'blue' if sequence and (i, j) in sequence else 'white' rect = visual.Rect(self.window, width=self.square_size, height=self.square_size, - pos=(square_x, square_y), lineWidth=3, - lineColor='black', fillColor=fill_color) + pos=(square_x, square_y), lineWidth=3, + lineColor='black', fillColor=fill_color) rect.draw() row.append(rect) grid.append(row) @@ -729,7 +1093,7 @@ def run_trial(self, trial): # Flush any keys in buffer event.clearEvents() - # Determine which side the correct sequence will be displayed + # Determine which side the correct sequence will be displayed correct_side = trial['correct_side'] @@ -772,7 +1136,7 @@ def run_trial(self, trial): word_stim.draw() self.window.flip() self.ttl_clock.wait_until(self.ttl_clock.get_time() + 0.45) - + event.clearEvents() # show press button image @@ -847,8 +1211,8 @@ def init_task(self): Initialize task - default is to read the target information into the trial_info dataframe """ trial_info_file = self.const.task_dir / self.name / self.task_file - self.trial_info = pd.read_csv(trial_info_file, sep='\t') - self.corr_key = [self.trial_info['key_one'].iloc[0],self.trial_info['key_two'].iloc[0]] + self.trial_info = pd.read_csv(trial_info_file, sep='\t') + self.corr_key = [self.trial_info['key_one'].iloc[0],self.trial_info['key_two'].iloc[0]] def display_instructions(self): """ @@ -895,7 +1259,7 @@ def run_trial(self, trial): return trial - + class FingerSequence(Task): """ Finger sequence task @@ -925,12 +1289,12 @@ def run_trial(self, trial): #clear buffer event.clearEvents() - # Display the sequence + # Display the sequence sequence = trial['stim'].split() # Calculate the start position for the sequence and determine the spacing between numbers num_items = len(sequence) - spacing = 2.0 + spacing = 2.0 start_x = -(num_items - 1) * spacing / 2 # Show the numbers in the sequence next to each other ( using the spacing and start_x calculated above) @@ -941,7 +1305,7 @@ def run_trial(self, trial): self.window.flip() - + sequence_start_time = self.ttl_clock.get_time() # Needed for knowing when to stop looking for key presses digit_start_time = sequence_start_time # Updated with each key press for calculating RT @@ -963,7 +1327,7 @@ def run_trial(self, trial): # Check if key pressed is correct correct_list[num_presses] = key == int(sequence[num_presses]) - + # Update color based on correctness digit_colors[num_presses] = 'green' if correct_list[num_presses] else 'red' @@ -989,12 +1353,12 @@ def run_trial(self, trial): else: trial['rt'] = np.nanmean(rt_list) - + # display trial feedback (for whole trial) self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']== 1) return trial - + class FlexionExtension(Task): """ Flexion extension of toes! No particular feedback. @@ -1035,7 +1399,7 @@ def run_trial(self, trial): # No response is expected in this task, so return trial as is return trial - + #semantic prediction runs (slight bug for response feedback: last word is not synced with last word in task_file..) class SemanticPrediction(Task): @@ -1053,7 +1417,7 @@ def init_task(self): Initialize task - default is to read the target information into the trial_info dataframe """ self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') - self.corr_key = [self.trial_info['key_false'].iloc[0],self.trial_info['key_true'].iloc[0]] + self.corr_key = [self.trial_info['key_false'].iloc[0],self.trial_info['key_true'].iloc[0]] def display_instructions(self): """ @@ -1066,11 +1430,11 @@ def display_instructions(self): instr_visual = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1]) instr_visual.draw() self.window.flip() - + def run_trial(self, trial): """ Runs a single trial of the semantic prediction task """ - - height_word = 2 + + height_word = 2 event.clearEvents() @@ -1087,6 +1451,11 @@ def run_trial(self, trial): event.clearEvents() + # Fixation cross + self.screen.fixation_cross() + self.ttl_clock.wait_until(self.ttl_clock.get_time() + 0.5) + event.clearEvents() + # Display last word last_word_stim = visual.TextStim(self.window, text=trial['last_word'], pos=(0.0, 0.0), color=(-1, -1, -1), units='deg', height= height_word, wrapWidth=30) last_word_stim.draw() @@ -1102,7 +1471,7 @@ def run_trial(self, trial): self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) return trial - + class VisualSearch(Task): """ @@ -1120,10 +1489,10 @@ def init_task(self): trial_info_file = self.const.task_dir / self.name / self.task_file self.trial_info = pd.read_csv(trial_info_file, sep='\t') - #counter initialized in order to read whether trial_type is true or not for each trial; used in generate_trial_stimuli - self.trial_counter = 0 - - #width and height determined by trial and error: x extremity of window is 14; y extremity is 10 + #counter initialized in order to read whether trial_type is true or not for each trial; used in generate_trial_stimuli + self.trial_counter = 0 + + #width and height determined by trial and error: x extremity of window is 14; y extremity is 10 screen_width = 28 screen_height = 20 @@ -1131,7 +1500,7 @@ def init_task(self): num_rows = 4 num_cols = 6 - #Define aperture sizes + positions + #Define aperture sizes + positions aperture_width = screen_width / num_cols aperture_height = screen_height / num_rows @@ -1143,7 +1512,7 @@ def init_task(self): for y in positions_y: for x in positions_x: aperture_positions.append((x, y)) - + self.apertures = [] for pos in aperture_positions: apertures = visual.Aperture(self.window, size=40, shape = 'rectangle', pos=pos, units='norm') @@ -1154,12 +1523,12 @@ def generate_trial_stimuli(self, num_stimuli): stim_images = ['90.png','180.png','270.png','360.png'] self.stim = [] - - is_target = True #determines whether target will appear on screen - if self.trial_info['trial_type'][self.trial_counter] == 0: - stim_images.remove('90.png') #sets a display with no target - is_target = False + is_target = True #determines whether target will appear on screen + + if self.trial_info['trial_type'][self.trial_counter] == 0: + stim_images.remove('90.png') #sets a display with no target + is_target = False self.trial_counter+=1 @@ -1167,16 +1536,16 @@ def generate_trial_stimuli(self, num_stimuli): for aperture in randomly_select_apertures: if is_target: - stim_current = stim_images[0] #sets a display with at least one target - is_target = False - else: - stim_random_idx = random.randint(0, len(stim_images)-1) #chooses random stimuli from stim_images list + stim_current = stim_images[0] #sets a display with at least one target + is_target = False + else: + stim_random_idx = random.randint(0, len(stim_images)-1) #chooses random stimuli from stim_images list stim_current = stim_images[stim_random_idx] stim_path = self.const.stim_dir/ self.name / stim_current stimulus = visual.ImageStim(self.window, str(stim_path), size=(0.8,0.8)) - stimulus.setPos([aperture.pos[0], aperture.pos[1]]) #puts stimuli within apertures - self.stim.append(stimulus) #creates list of all randomly selected stimuli + stimulus.setPos([aperture.pos[0], aperture.pos[1]]) #puts stimuli within apertures + self.stim.append(stimulus) #creates list of all randomly selected stimuli def display_instructions(self): """ @@ -1196,11 +1565,11 @@ def display_instructions(self): def run_trial(self,trial): """Runs a single trial of visual search task """ - + # Flush any keys in buffer event.clearEvents() - - num_stimuli = self.trial_info.loc[trial['trial_num'], 'num_stimuli'] #indicates if trial is easy (4 stimuli) or hard (8 stimuli) + + num_stimuli = self.trial_info.loc[trial['trial_num'], 'num_stimuli'] #indicates if trial is easy (4 stimuli) or hard (8 stimuli) self.generate_trial_stimuli(num_stimuli) @@ -1212,10 +1581,10 @@ def run_trial(self,trial): self.window.flip() - # collect responses + # collect responses trial['response'],trial['rt'] = self.wait_response(self.ttl_clock.get_time(), trial['trial_dur']) trial['correct'] = (trial['response'] == self.corr_key[trial['trial_type']]) - + self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) return trial @@ -1251,10 +1620,10 @@ def display_instructions(self): def run_trial(self, trial): """ Runs a single trial of the Reading the Mind in the Eye (RMET) task """ - + # Flush any keys in buffer event.clearEvents() - + # --- Eyes --- # Get the file name picture_file_name = trial['stim'] @@ -1268,7 +1637,7 @@ def run_trial(self, trial): picture_scale = getattr(self.const, 'rmet_picture_scale', None) or 0.7 picture.size = picture.size * picture_scale - + # --- Answers --- # Get the answer options @@ -1282,7 +1651,7 @@ def run_trial(self, trial): # 2 and 3 should be on the left and right of the bottom line (y position -7 and x positions -7 and 7) x = -8 if i % 2 == 0 else 6 y = 5 if i < 2 else -5 - + if len (option) < 3: tabs = 2 elif len(option) < 9: @@ -1291,12 +1660,12 @@ def run_trial(self, trial): tabs = 4 tab_string = ''.join(["\t"] * tabs) answer_stim = visual.TextStim(self.window, text=f'{i+1}.{tab_string}', - pos=(x, y-0.04), color='blue', height=1, alignHoriz='center') + pos=(x, y-0.04), color='blue', height=1, alignHoriz='center') answer_stims.append(answer_stim) tab_string = ''.join(["\t"] * (tabs-1)) answer_stim = visual.TextStim(self.window, text=f'{tab_string}{option}', - pos=(x, y), color=[-1, -1, -1], height=1.4, alignHoriz='center') + pos=(x, y), color=[-1, -1, -1], height=1.4, alignHoriz='center') answer_stims.append(answer_stim) # Display stimuli @@ -1308,7 +1677,7 @@ def run_trial(self, trial): # collect responses 0: no response 1-4: key pressed trial['response'],trial['rt'] = self.wait_response(self.ttl_clock.get_time(), trial['trial_dur']) trial['correct'] = (trial['response'] == answer_options.index(str(trial['answer']))+1) - + # display trial feedback self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) @@ -1336,7 +1705,7 @@ def display_instructions(self): instr_visual = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1], wrapWidth=20, pos=(0, 0)) instr_visual.draw() self.window.flip() - + def show_presses(self, pressed_keys, positions, last_key_press_time, width=1.4, height=7, line_width=10): """ Displays the presses on the screen Args: @@ -1351,17 +1720,17 @@ def show_presses(self, pressed_keys, positions, last_key_press_time, width=1.4, for p, pressed_key in enumerate(pressed_keys): color = 'blue' if p == len(pressed_keys) - 1 and not self.ttl_clock.get_time() - last_key_press_time > 1 else 'black' #Add a green border around the last selected image if the last key press was less than 2 seconds ago visual.Rect(self.window, size=(width, height), pos=positions[pressed_key-1], lineColor=color, lineWidth=line_width).draw() - + def run_trial(self, trial): """ Runs a single trial of the Reading the Mind in the Eye (RMET) task """ - + # Flush any keys in buffer event.clearEvents() - + # Get the file name picture_file_name = trial['stim'] # Construct the picture file path - picture_paths = [str(Path(self.const.stim_dir) / self.name / 'pictures' / f"{picture_file_name} card{n}") for n in range(1,5)] + picture_paths = [str(Path(self.const.stim_dir) / self.name / 'pictures' / f"{picture_file_name} card{n}") for n in range(1,5)] # Sort them in the order they should be displayed sequence = list(map(int, trial['sequence'].split(' '))) picture_paths = [picture_paths[i-1] for i in sequence] @@ -1394,7 +1763,7 @@ def run_trial(self, trial): # Calculate the start position for the sequence and determine the spacing between numbers num_items = len(sequence) - + # collect responses 0: no response 1-4: key pressed sequence_start_time = self.ttl_clock.get_time() # Needed for knowing when to stop looking for key presses digit_start_time = sequence_start_time # Updated with each key press for calculating RT @@ -1405,7 +1774,7 @@ def run_trial(self, trial): num_presses =0 pressed_keys = [] line_width = 15 - + while self.ttl_clock.get_time() - sequence_start_time < trial['trial_dur']: self.ttl_clock.update() @@ -1413,7 +1782,7 @@ def run_trial(self, trial): picture.draw() for answer_stim in answer_stims: answer_stim.draw() - + seconds_left = trial['trial_dur'] - (self.ttl_clock.get_time() - sequence_start_time) self.show_progress(seconds_left, show_last_seconds=5, @@ -1437,7 +1806,7 @@ def run_trial(self, trial): correct_list[num_presses] = key == int(correct_sequence[num_presses]) num_presses += 1 pressed_keys.append(key) - + # if any press is wrong trial['correct'] needs to be false, this is for post trial feedback trial['correct'] = correct_list.sum()/num_items trial['response'] = pressed_keys @@ -1448,7 +1817,7 @@ def run_trial(self, trial): else: trial['rt'] = np.nanmean(rt_list) - + # display trial feedback (for whole trial) self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']==1) @@ -1477,7 +1846,7 @@ def display_instructions(self): instr_visual = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1], wrapWidth=20, pos=(0, 0)) instr_visual.draw() self.window.flip() - + def show_presses(self, sentences, positions, pressed_keys, last_key_press_time, wrapWidth, text_height=1): """ Displays the presses on the screen Args: @@ -1487,33 +1856,33 @@ def show_presses(self, sentences, positions, pressed_keys, last_key_press_time, last_key_press_time (float): The time of the last key press wrapWidth (float): The width of the text """ - - + + for p, pressed_key in enumerate(pressed_keys): color = 'blue' if p == len(pressed_keys) - 1 and not self.ttl_clock.get_time() - last_key_press_time > 1 else 'darkgrey' # Present the stimuli in blue if the last key press was less than 2 seconds ago visual.TextStim(self.window, text=sentences[pressed_key-1], pos=positions[pressed_key-1], color=color, height=text_height, wrapWidth=wrapWidth).draw() - - + + def run_trial(self, trial): """ Runs a single trial of the Reading the Mind in the Eye (RMET) task """ - + # Flush any keys in buffer event.clearEvents() - + wrapWidth = 20 - + # Sort them in the order they should be displayed sequence = list(map(int, trial['sequence'].split(' '))) sentences = [trial[f"stim{i}"] for i in range(1,5)] sentences = [sentences[i-1] for i in sequence] # Order the sentences according to the sequence # Format the sentences for display sentences = [f'{s+1}.\t{sentence}\n\n' for s, sentence in enumerate(sentences)] - sentences_stim = visual.TextStim(self.window, text=''.join(sentences), pos=(0, 0), color=[-1, -1, -1], height=1, wrapWidth=wrapWidth) + sentences_stim = visual.TextStim(self.window, text=''.join(sentences), pos=(0, 0), color=[-1, -1, -1], height=1, wrapWidth=wrapWidth) # Calculate the start position for the sequence and determine the spacing between numbers num_items = len(sequence) - + # collect responses 0: no response 1-4: key pressed sequence_start_time = self.ttl_clock.get_time() # Needed for knowing when to stop looking for key presses digit_start_time = sequence_start_time # Updated with each key press for calculating RT @@ -1531,17 +1900,17 @@ def run_trial(self, trial): # Arrange sentences non-overlapping from top to bottom positions = [(0, 5), (0, 1), (0, -2), (0, -6)] # Present the stimuli in black - + while self.ttl_clock.get_time() - sequence_start_time < trial['trial_dur']: self.ttl_clock.update() - + seconds_left = trial['trial_dur'] - (self.ttl_clock.get_time() - sequence_start_time) self.show_progress(seconds_left, - show_last_seconds=5, - height=1, - width=bar_width, - x_pos=0-bar_width*0.5, - y_pos=y_pos+height*0.5+1) + show_last_seconds=5, + height=1, + width=bar_width, + x_pos=0-bar_width*0.5, + y_pos=y_pos+height*0.5+1) # Display the sentences [visual.TextStim(self.window, text=sentence, pos=positions[s], color='black', height=text_height, wrapWidth=wrapWidth).draw() for s, sentence in enumerate(sentences)] self.show_presses(sentences, positions, pressed_keys, digit_start_time, wrapWidth, text_height) @@ -1560,7 +1929,7 @@ def run_trial(self, trial): correct_list[num_presses] = key == int(sequence[num_presses]) num_presses += 1 pressed_keys.append(key) - + # if any press is wrong trial['correct'] needs to be false, this is for post trial feedback trial['correct'] = correct_list.sum()/num_items @@ -1570,12 +1939,12 @@ def run_trial(self, trial): else: trial['rt'] = np.nanmean(rt_list) - + # display trial feedback (for whole trial) self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']==1) return trial - + class ActionPrediction(Task): def __init__(self, info, screen, ttl_clock, const, subj_id): super().__init__(info, screen, ttl_clock, const, subj_id) @@ -1584,7 +1953,7 @@ def __init__(self, info, screen, ttl_clock, const, subj_id): def init_task(self): self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') self.corr_key = [self.trial_info['key_one'].iloc[0],self.trial_info['key_two'].iloc[0]] - + def display_instructions(self): """ displays the instruction for the task @@ -1615,17 +1984,17 @@ def run_trial(self, trial): movie_scale = getattr(self.const, 'action_prediction_scale', None) or 0.4 stim_width = int(window_width * movie_scale) # Make the video fraction of the window width stim_height = int(stim_width * 476 / 846) # Original size of the video is 640x360 - - - # Display video + + + # Display video movie_path = Path(self.const.stim_dir) / self.name / 'clips' / f"{trial['stim']}.mp4" movie_path_str = str(movie_path) movie_clip = visual.MovieStim(self.window, movie_path_str, loop=False, noAudio=True, size=(stim_width, stim_height), pos=(0, 0)) movie_clip.play() - + self.window.flip() - + while movie_clip.isFinished == False: movie_clip.play() @@ -1674,7 +2043,7 @@ def run_trial(self, trial): movie_scale = getattr(self.const, 'movie_scale', None) or 0.4 stim_width = int(window_width * movie_scale) # Make the video fraction of the window width stim_height = int(stim_width * 360 / 640) # Original size of the video is 640x360 - + # Get the file name movie_file_name = trial['stim'] @@ -1687,7 +2056,7 @@ def run_trial(self, trial): # Create a MovieStim3 object movie_clip = visual.MovieStim(self.window, movie_path_str, loop=False, size=(stim_width, stim_height), pos=(0, 0), noAudio=True) - + movie_clip.play() self.window.flip() @@ -1702,13 +2071,13 @@ def run_trial(self, trial): gc.collect() # Collect garbarge return trial - + class StrangeStories(Task): def __init__(self, info, screen, ttl_clock, const, subj_id): super().__init__(info, screen, ttl_clock, const, subj_id) self.name = 'strange_stories' - + def init_task(self): self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') self.corr_key = [self.trial_info['key_one'].iloc[0],self.trial_info['key_two'].iloc[0], self.trial_info['key_three'].iloc[0]] @@ -1735,7 +2104,7 @@ def run_trial(self, trial): stim_width = int(window_width * strange_stories_scale) # Make the video 40% of the window width stim_height = int(stim_width * 921 / 1638) # 1280x720 is the original size of the video given in width x height wrapWidth = 25 - + # Get the file name movie_file_name = trial['stim'] @@ -1752,10 +2121,10 @@ def run_trial(self, trial): movie_clip = visual.MovieStim(self.window, movie_path_str, loop=False, size=(stim_width, stim_height), pos=(0, 0), noAudio=play_audio_separatly) - + if play_audio_separatly: audio = self.get_audio_from_movie(movie_path, sample_rate=48000) - + movie_clip.draw() if play_audio_separatly: audio.play() @@ -1782,7 +2151,7 @@ def run_trial(self, trial): if 'control' in trial['condition']: # Only the first option is correct (2 points) scores_orig = [2,0,0] elif 'social'in trial['condition']: # First option gets 2 points, second option gets 1 point, third option gets 0 points - scores_orig = [2,1,0] + scores_orig = [2,1,0] scores_shuffled = [scores_orig[options_orig.index(option)] for option in options_shuffled] answers = f"\n\n\n{self.corr_key[0]}. {options_shuffled[0]} \n{self.corr_key[1]}. {options_shuffled[1]} \n{self.corr_key[2]}. {options_shuffled[2]}" @@ -1791,7 +2160,7 @@ def run_trial(self, trial): stim_question = visual.TextStim(self.window, text = question, pos=(0, 4), color=(-1, -1, -1), units='deg', height= 1.5, wrapWidth=wrapWidth) stim_question.draw() self.window.flip() - + # Display the question until X seconds before trial is over (answer_dur), to make the 'buffer' zone for the trial, i.e. the time of variable length, the time where the participant deliberates about their answer self.ttl_clock.wait_until(self.ttl_clock.get_time() + (trial['trial_dur'] - movie_clip.duration - trial['answer_dur'])) # Flush any keys in buffer @@ -1808,8 +2177,8 @@ def run_trial(self, trial): else: left_position = 0 align='center' - - + + stim_answers = visual.TextStim(self.window, text=answers, pos=(left_position, 0), color=(-1, -1, -1), units='deg', height= 1.5, wrapWidth=wrapWidth, alignHoriz=align) stim_question.draw() stim_answers.draw() @@ -1823,14 +2192,14 @@ def run_trial(self, trial): trial['acc'] = 0 # If the participant pressed a key that is not in the list of answers, set the score to 0 else: trial['acc'] = scores_shuffled[trial['response']-1] - + # Flush memory: This is necessary for the script to be able to run more than 1 run. Presenting movies is very memory hungry, so do not remove! movie_clip.unload() gc.collect() # Collect garbarge return trial - + class FauxPas(Task): def __init__(self, info, screen, ttl_clock, const, subj_id): @@ -1844,7 +2213,7 @@ def init_task(self): self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') self.corr_key = [self.trial_info['key_yes'].iloc[0],self.trial_info['key_no'].iloc[0]] - + def display_instructions(self): """ displays the instruction for the task @@ -1869,7 +2238,7 @@ def run_trial(self, trial): height = getattr(self.const, 'faux_pas_text_height', None) or 1.25 # Display story story = trial['story'] - # story = '.\n'.join(story.split('. ')) + # story = '.\n'.join(story.split('. ')) story_stim = visual.TextStim(self.window, text=story, alignHoriz='center', wrapWidth=20, pos=(0.0, 0.0), color=(-1, -1, -1), units='deg', height= height) story_stim.draw() self.window.flip() @@ -1901,7 +2270,7 @@ def run_trial(self, trial): self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) return trial - + class FrithHappe(Task): def __init__(self, info, screen, ttl_clock, const, subj_id): @@ -1933,10 +2302,10 @@ def run_trial(self, trial): window_width, _ = self.window.size frith_happe_scale = getattr(self.const, 'frith_happe_scale', None) or 0.4 - stim_width = int(window_width * frith_happe_scale) + stim_width = int(window_width * frith_happe_scale) stim_height = int(stim_width * 1074 / 1433) wrapWidth = 25 - + # Get the file name movie_file_name = trial['stim'] # Construct the movie file path @@ -1945,8 +2314,8 @@ def run_trial(self, trial): movie_path_str = str(movie_path) # Create a MovieStim object movie_clip = visual.MovieStim(self.window, movie_path_str, loop=False, size=(stim_width, stim_height), pos=(0, 0), noAudio=True) - - + + movie_clip.play() self.window.flip() @@ -1990,7 +2359,7 @@ def run_trial(self, trial): gc.collect() # Collect garbarge return trial - + class Liking(Task): @@ -2003,24 +2372,45 @@ def init_task(self): self.trial_info = pd.read_csv(self.const.task_dir / self.name / self.task_file, sep='\t') self.corr_key = [self.trial_info['key_one'].iloc[0],self.trial_info['key_two'].iloc[0]] + # MINIMAL: fallback if CSV has no key_one/key_two + if {'key_one', 'key_two'}.issubset(self.trial_info.columns): + self.corr_key = [str(self.trial_info['key_one'].iloc[0]), str(self.trial_info['key_two'].iloc[0])] + else: + # use constants (e.g., ['1','2','3','4']) → take first two + fallback = getattr(self.const, 'response_keys', ['1', '2'])[:2] + self.corr_key = [str(fallback[0]), str(fallback[1])] + def display_instructions(self): - task_name = visual.TextStim(self.window, text=f'{self.descriptive_name.capitalize()}', height=self.const.instruction_text_height, color=[-1, -1, -1], bold=True, pos=(0, 3)) + task_name = visual.TextStim(self.window, text=f'{self.descriptive_name.capitalize()}', + height=self.const.instruction_text_height, color=[-1, -1, -1], + bold=True, pos=(0, 3)) task_name.draw() - self.instruction_text = f"You will watch two people meeting for the first time.\n" - if 'like' in self.task_file: + self.instruction_text = "You will watch two people meeting for the first time.\n" + + # MINIMAL: normalize filename and set a safe default for key_text + fname = str(self.task_file).lower().strip() + key_text = f"\n{self.corr_key[0]}. Option 1 \t{self.corr_key[1]}. Option 2" + + if 'like' in fname and 'control' not in fname: self.instruction_text += "Judge if they LIKE each other." key_text = f"\n{self.corr_key[0]}. Yes \t{self.corr_key[1]}. No" - elif 'control' in self.task_file: + elif 'control' in fname and 'like' not in fname: self.instruction_text += "Judge if one person SPEAKS MORE." key_text = f"\n{self.corr_key[0]}. Yes \t{self.corr_key[1]}. No" - elif 'liking' in self.task_file and 'control' in self.task_file: + elif 'like' in fname and 'control' in fname: self.instruction_text += "Judge if they LIKE each other or if one person SPEAKS MORE." key_text = f"\n{self.corr_key[0]}. Yes \t{self.corr_key[1]}. No" - instr_stim = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1], wrapWidth=20, pos=(0, 0)) + # else: keep the default instruction_text + key_text + + instr_stim = visual.TextStim(self.window, text=self.instruction_text, + height=self.const.instruction_text_height, color=[-1, -1, -1], + wrapWidth=20, pos=(0, 0)) instr_stim.draw() - key_text = visual.TextStim(self.window, text=key_text, height=self.const.instruction_text_height, color=[-1, -1, -1], - wrapWidth=20, pos=(-3, -3), alignHoriz='left') + + key_text = visual.TextStim(self.window, text=key_text, + height=self.const.instruction_text_height, color=[-1, -1, -1], + wrapWidth=20, pos=(-3, -3), alignHoriz='left') key_text.draw() self.window.flip() @@ -2028,7 +2418,7 @@ def run_trial(self, trial): window_width, _ = self.window.size liking_scale = getattr(self.const, 'liking_scale', None) or 0.5 stim_width = int(window_width * liking_scale) - stim_height = int(stim_width * 486 / 720) + stim_height = int(stim_width * 486 / 720) wrapWidth = 20 # Get the file name @@ -2045,13 +2435,13 @@ def run_trial(self, trial): # Create a MovieStim object movie_clip = visual.MovieStim(self.window, movie_path_str, loop=False, - size=(stim_width, stim_height), - pos=(0, 0),noAudio=play_audio_separatly) + size=(stim_width, stim_height), + pos=(0, 0),noAudio=play_audio_separatly) # Play through the movie frame by frame max_video_duration = 24 movie_start_time = self.ttl_clock.get_time() - + movie_clip.draw() if play_audio_separatly: audio.play() @@ -2076,7 +2466,7 @@ def run_trial(self, trial): question = "Do they like each other?" elif 'control' in trial['condition']: question = "Did one person talk more?" - + # Display question stim_question = visual.TextStim(self.window, text = question, pos=(0, 1), color=(-1, -1, -1), units='deg', height= 1.5, wrapWidth=wrapWidth) stim_question.draw() @@ -2101,11 +2491,11 @@ def run_trial(self, trial): trial['correct'] = (trial['response'] == 2) else: trial['correct'] = False - + # Record the played video duration trial['video_dur_orig'] = trial['video_dur'] trial['video_dur'] = max_video_duration - + # display trial feedback self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) @@ -2134,7 +2524,7 @@ def init_task(self): self.key_handler = key.KeyStateHandler() self.window.winHandle.push_handlers(self.key_handler) - def display_instructions(self): + def display_instructions(self): self.instruction_text = f"Use the buttons to move the paddle and catch the ball." instr_visual = visual.TextStim(self.window, text=self.instruction_text, color=[-1, -1, -1],pos=(0, 0.3)) instr_visual.draw() @@ -2150,8 +2540,8 @@ def run_trial(self, trial): # Compute the effective screen dimensions (in degrees) based on monitor calibration. # Get monitor width (in cm) and viewing distance (in cm) from your screen object. - monitor_width_cm = self.screen.monitor.getWidth() - distance_cm = self.screen.distance + monitor_width_cm = self.screen.monitor.getWidth() + distance_cm = self.screen.distance # Calculate horizontal visual angle (in degrees) half_width_deg = math.degrees(math.atan((monitor_width_cm / 2) / distance_cm)) @@ -2170,8 +2560,8 @@ def run_trial(self, trial): max_x = half_screen_width - paddle_half_width # Define margins (in degrees) - paddle_margin = 2.0 - ball_margin = 2.0 + paddle_margin = 2.0 + ball_margin = 2.0 # Clear events event.clearEvents() @@ -2195,11 +2585,11 @@ def run_trial(self, trial): key_left = getattr(key, self.const.response_keys[self.corr_key[0]-1].upper(), None) key_right = getattr(key, self.const.response_keys[self.corr_key[1]-1].upper(), None) trial['correct'] = False - - + + ball_stuck = False ball_offset_x = 0 - + while self.ttl_clock.get_time() - start_time < trial_duration: if self.key_handler[key_left]: paddle.pos = (paddle.pos[0] - paddle_speed, paddle.pos[1]) @@ -2253,7 +2643,7 @@ def init_task(self): """ trial_info_file = self.const.task_dir / self.name / self.task_file self.trial_info = pd.read_csv(trial_info_file, sep='\t') - + self.stim = [] for stim_file in self.trial_info['stim']: stim_path = self.const.stim_dir / self.name / stim_file @@ -2296,4 +2686,3 @@ def run_trial(self, trial): self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) return trial - diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index 5e0fca88..15bc0e64 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -1861,3 +1861,160 @@ def make_task_file(self, return trial_info + +class FingerRhythmic(TaskFile): + def __init__(self, const): + super().__init__(const) + self.name = 'finger_rhythmic' + + def make_task_file(self, + hand='right', + responses=[1], + run_number= None, + task_dur = 70, + trial_dur=35, # 2 sec trial start text, 27.95 sec tone train, ~5 sec buffer + iti_dur=0, + file_name=None): + + # count number of trials + n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) + trial_info = [] + t = 0 + + for n in range(n_trials): + trial = {} + trial['key_one'] = responses[0] + trial['trial_num'] = n + trial['hand'] = hand + trial['trial_dur'] = trial_dur + trial['iti_dur'] = iti_dur + trial['stim'] = 'generated' + trial['display_trial_feedback'] = False + trial['start_time'] = t + trial['end_time'] = t + trial_dur + iti_dur + trial_info.append(trial) + # Update for next trial: + t = trial['end_time'] + + trial_info = pd.DataFrame(trial_info) + if file_name is not None: + ut.dircheck(self.task_dir / self.name) + trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) + + return trial_info + +class TimePerception(TaskFile): + def __init__(self, const): + super().__init__(const) + self.name = 'time_perception' # folder: stimuli/perception/, tasks/perception/ + + def make_task_file(self, + modality='time', # 'time' or 'volume' + responses=[1, 2], # code 1 = left option, 2 = right option + n_trials= 30, # must be even + trial_dur=4, # tone + question window duration + iti_dur=1.0, + question_dur=2.0, + display_feedback= True, + run_number=None, + file_name=None, + **unused): + + # sides per modality + if modality == 'time': + left_label, right_label = 'shorter', 'longer' + elif modality == 'volume': + left_label, right_label = 'quieter', 'louder' + + sides = [left_label] * (n_trials // 2) + [right_label] * (n_trials // 2) + np.random.default_rng(run_number).shuffle(sides) + + rows, t = [], 0.0 + for i, side in enumerate(sides): + rows.append(dict( + trial_num=i, + modality=modality, # drives Task branching + side=side, # shorter/longer or softer/louder + key_one=int(responses[0]), # instruction mapping only + key_two=int(responses[1]), + trial_type=1 if side in (left_label,) else 2, # correct code + question_dur=float(question_dur), + trial_dur=float(trial_dur), + iti_dur=float(iti_dur), + display_trial_feedback= display_feedback, + + # runtime logs (filled in Task) + comparison_ms=np.nan, + comparison_dba=np.nan, + + start_time=float(t), + end_time=float(t + trial_dur + iti_dur), + )) + t = rows[-1]['end_time'] + + df = pd.DataFrame(rows) + if file_name: + ut.dircheck(self.task_dir / self.name) + df.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) + return df + +class SensMotControl(TaskFile): + def __init__(self, const): + super().__init__(const) + self.name = 'sensmot_control' + + def make_task_file(self, + hand='right', + responses=[1, 2], + run_number=None, + task_dur=300, + trial_dur=3, + question_dur=2, + iti_dur= 1, + file_name=None, + stim_file = None, + condition=None): + + # count number of trials + n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) + trial_info = [] + t = 0 + + if stim_file: + stim = pd.read_csv(self.stim_dir / self.name / stim_file, sep='\t') + else: + stim = pd.read_csv(self.stim_dir / self.name / f'{self.name}_block1.csv', sep='\t') + + if condition: + stim = stim[stim['condition'] == condition] + else: + stim = stim.loc[~stim['condition'].str.contains('practice', na=False)] + + start_row = (run_number - 1) * n_trials + end_row = run_number * n_trials - 1 + stim = stim.iloc[start_row:end_row + 1].reset_index(drop=True) + + for n in range(n_trials): + trial = {} + trial['key_one'] = responses[0] + trial['key_two'] = responses[1] + trial['trial_num'] = n + trial['hand'] = hand + trial['trial_dur'] = trial_dur + trial['question_dur'] = question_dur + trial['iti_dur'] = iti_dur + trial['trial_type'] = stim['corr_resp'][n] + trial['stim'] = stim['color'][n] + trial['condition'] = stim['condition'][n] + trial['display_trial_feedback'] = True + trial['start_time'] = t + trial['end_time'] = t + trial_dur + iti_dur + trial_info.append(trial) + + # Update for next trial: + t = trial['end_time'] + + trial_info = pd.DataFrame(trial_info) + if file_name is not None: + trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) + return trial_info diff --git a/MultiTaskBattery/task_table.tsv b/MultiTaskBattery/task_table.tsv index 38fe6f88..9d69294b 100644 --- a/MultiTaskBattery/task_table.tsv +++ b/MultiTaskBattery/task_table.tsv @@ -28,3 +28,6 @@ frith_happe FrithHappe triangle frith_happe "video clips of triangle animations, liking Liking meeting liking "video clips of people interacting, participants must determine if the interaction was positive or negative" "Heerey, E.A. and Gilder, T.S.E. (2019) ‘The subjective value of a smile alters social behaviour’, PLoS ONE, 14(12), pp. 1–19. Available at: https://doi.org/10.1371/journal.pone.0225284." "like,dislike" pong Pong pong pong pong game NA affective Affective affective affect 2 choice is the image pleasant or unpleasant NA +finger_rhythmic FingerRhythmic finger_rhythmic fingrhytm Assess the precision of internal timing mechanisms through a rhythmic tapping production task. Participants are instructed to produce a regular series of taps at a prescribed target interval. Ivry, R. B., & Keele, S. W. (1989). Timing Functions of the Cerebellum. Journal of Cognitive Neuroscience, 1(2), 136-152. self-paced tapping +time_perception TimePerception time_perception timeperc Assess the precision of internal timing mechanisms by measuring participants’ perceptual acuity in discriminating short auditory intervals. Participants are presented with sequences of tones and must decide whether a comparison interval is longer or shorter than a standard reference interval. Ivry, R. B., & Keele, S. W. (1989). Timing Functions of the Cerebellum. Journal of Cognitive Neuroscience, 1(2), 136–152. time interval +sensmot_control SensMotControl sensmot_control sensmotcontrol Assess basic sensorimotor control. Measure reaction time and response accuracy to evaluate low-level perceptual and motor processing. Part 1 is a Go/No-Go. Part 2 is a 2AFC : Koechlin, E., & Summerfield, C. (2007). An information theoretical approach to prefrontal executive function. Trends in cognitive sciences, 11(6), 229-235.Koechlin 2007 Go/No-Go 2AFC. diff --git a/stimuli/faux_pas/faux_pas.csv b/stimuli/faux_pas/faux_pas.csv index ca0ed2a2..75838df4 100644 --- a/stimuli/faux_pas/faux_pas.csv +++ b/stimuli/faux_pas/faux_pas.csv @@ -1,46 +1,46 @@ story,stimulus_id,scenario,question1,options1,answer1,question2,options2,answer2,condition,half -"Vicky was at Oliver’s party, talking to him when a neighbor approached. The woman said, “Hello. I’m Maria, what’s your name?” -“I’m Vicky,” she replied. +"Vicky was at Oliver’s party, talking to him when a neighbor approached. The woman said, “Hello. I’m Maria, what’s your name?” +“I’m Vicky,” she replied. “Would anyone like a drink?” Oliver asked.",1,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Vicky, Maria, Oliver, Nobody",Nobody,social,1 "Helen's husband planned a surprise birthday party. He warned Sarah, ""Don't tell Helen."" While with Helen, Sarah spilled coffee on her dress and said, ""Oh! I was wearing this to your party!"" ""What party?"" Said Helen.",2,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Helen, Sarah, Helen's husband, Nobody",Sarah,social,1 -"Jim was shopping for a shirt to match his suit. He found the right color but it didn’t fit. -“It’s too small,” he said. +"Jim was shopping for a shirt to match his suit. He found the right color but it didn’t fit. +“It’s too small,” he said. “Not to worry,” the salesman replied. “We’ll have larger sizes next week.”",3,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Jim, The salesman, Nobody",Nobody,social,1 "Bob went to the barber for a haircut. “Just take about an inch off,” Bob said. The barber cut the front uneven, so he had to go shorter. “It’s shorter than you wanted,” he said. “Oh well,” Bob replied, “it’ll grow out.”",4,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"The barber, Bob, Nobody",Nobody,social,1 "John stopped at the petrol station to fill his car. When the cashier ran his card she said ""The machine won't accept it."" ""That's funny,"" John said. ""I'll pay in cash."" He gave her fifty and said, ""I filled up with unleaded.""",5,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"The cashier, John, Nobody",Nobody,social,1 -"Jill had just moved into a new flat and bought curtains for her bedroom. After decorating, her best friend Lisa visited. Jill gave her a tour and asked, “How do you like my bedroom?” +"Jill had just moved into a new flat and bought curtains for her bedroom. After decorating, her best friend Lisa visited. Jill gave her a tour and asked, “How do you like my bedroom?” “Those curtains are horrible,” Lisa said.",6,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Lisa, Jill, Nobody",Lisa,social,1 "Sally, a three-year-old girl with short blonde hair was at her Aunt Carol's house. A neighbour, Mary, rang the doorbell and Carol answered. Mary looked at Sally and said, “Oh, I don’t think I’ve met this little boy. What’s your name?”",7,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Aunt Carol, Mary, Sally, Nobody",Mary,social,1 -"Joan took her dog, Zack, to the park and threw a stick for him. Pam, a neighbor, passed by and asked, “Want to walk home together?” -“Sure,” said Joan. She called Jack but he was busy chasing pigeons. “Nevermind, we’ll stay” Joan added.",8,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Pam, Joan, Nobody",Nobody,social,1 -"Kim’s cousin Scott was coming to visit, and Kim made an apple pie for him. After dinner, she said, “I made a pie just for you.” +"Joan took her dog, Zack, to the park and threw a stick for him. Pam, a neighbor, passed by and asked, “Want to walk home together?” +“Sure,” said Joan. She called Zack but he was busy chasing pigeons. “Nevermind, we’ll stay” Joan added.",8,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Pam, Joan, Nobody",Nobody,social,1 +"Kim’s cousin Scott was coming to visit, and Kim made an apple pie for him. After dinner, she said, “I made a pie just for you.” “Mmmm,” Scott replied. “It smells great! I love pies—except for apple, of course.”",9,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Scott, Kim, Nobody",Scott,social,1 -"Jeanette gave Anne a crystal bowl as a wedding gift. A year later, at Anne’s for dinner, Jeanette accidentally broke the bowl. -“I’m sorry,” Jeanette said. +"Jeanette gave Anne a crystal bowl as a wedding gift. A year later, at Anne’s for dinner, Jeanette accidentally broke the bowl. +“I’m sorry,” Jeanette said. “Don’t worry,” Anne replied. “I never liked it anyways. Someone gave it to me.”",10,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Anne, Jeanette, Nobody",Anne,social,1 "Joanne, who had a big role in last year’s play, auditioned for the lead this spring. Checking the cast list, she saw she’d gotten a minor role. When she ran into her boyfriend he said “You must be disappointed.” Joan replied “Yes, I am”",11,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Joanne, Her boyfriend, Nobody",Nobody,social,2 "Joe found a sailing book at the library. After checking his wallet he realized he’d left his library card at home. “I’m sorry,” he said to the librarian. She replied, “That’s OK. If you’re in the computer, you can check out with your driving license”",12,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"The librarian, Joe, Nobody",Nobody,social,2 "Jean West, a manager, called a staff meeting. “John Morehouse, our accountant, is very sick with cancer,” she said. The room fell silent. Robert arrived late, making a joke about illness. Jean cut him off, saying, “Let’s start the meeting.”",13,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Robert, Jean, John, Nobody",Robert,social,2 -"Mike started at a new school. While in a bathroom stall he overheard Joe and Peter at the sinks. -“Doesn’t the new guy, Mike, look weird? And he’s so short!” Joe said. -Mike emerged. +"Mike started at a new school. While in a bathroom stall he overheard Joe and Peter at the sinks. +“Doesn’t the new guy, Mike, look weird? And he’s so short!” Joe said. +Mike emerged. “Oh hi, Mike!” Peter said. “Are you going out to play?”",14,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Mike, Joe, Peter, Nobody",Joe,social,2 -"At Fernhaven Elementary, Christine entered a story competition but didn’t win. Her classmate Jake won first prize. -Sitting with him the next day, Jake said, “The other stories were terrible.” +"At Fernhaven Elementary, Christine entered a story competition but didn’t win. Her classmate Jake won first prize. +Sitting with him the next day, Jake said, “The other stories were terrible.” Christine asked, “Where will you put your trophy?”",15,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Christine, Jake, Nobody",Jake,social,2 -"Tim spilled coffee on the floor at a restaurant. The waiter said, “I’ll get you another cup,” and left. +"Tim spilled coffee on the floor at a restaurant. The waiter said, “I’ll get you another cup,” and left. Tim approached Jack, a customer by the cashier, and asked, “I spilled coffee by my table. Can you mop it up?”",16,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Tim, The waiter, Jack, Nobody",Tim,social,2 -"Eleanor, any older lady, tiredly stood waiting 20 minutes for a late bus. When it arrived there were no seats left. -Eleanor greated her neighbour Paul, speaking about the wait. +"Eleanor, any older lady, tiredly stood waiting 20 minutes for a late bus. When it arrived there were no seats left. +Eleanor greated her neighbour Paul, speaking about the wait. A young man stood and said, “Ma’am, would you like my seat?”",17,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Paul, Eleanor, Nobody",Nobody,social,2 -"Roger, new at the office, told Andrew, “My wife’s a lawyer.” Moments later, Claire walked into the coffee room, complaining, “Lawyers are so arrogant and greedy.” -Andrew asked Claire to review reports. +"Roger, new at the office, told Andrew, “My wife’s a lawyer.” Moments later, Claire walked into the coffee room, complaining, “Lawyers are so arrogant and greedy.” +Andrew asked Claire to review reports. “Not now,” she said. “I need coffee.”",18,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,Who said something they shouldn't have said or something awkward?,"Andrew, Roger, Claire, Nobody",Claire,social,2 -"Richard backed his new red Peugeot into his neighbor Ted’s old Volvo, leaving only a scratch above the wheel. He knocked on Ted’s door, saying, “I’m sorry about the scratch.” +"Richard backed his new red Peugeot into his neighbor Ted’s old Volvo, leaving only a scratch above the wheel. He knocked on Ted’s door, saying, “I’m sorry about the scratch.” Ted looked and said, “Don’t worry. It was an accident.”",19,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"Richard, Ted, Nobody",Nobody,social,2 -"Louise went to the butcher and asked, “Do you have any free-range chickens?” -The butcher nodded and began wrapping a roasted chicken. -“Excuse me,” Louise said, “I asked for free-range.” +"Louise went to the butcher and asked, “Do you have any free-range chickens?” +The butcher nodded and began wrapping a roasted chicken. +“Excuse me,” Louise said, “I asked for free-range.” “Oh sorry,” the butcher replied, “we’re all out.”",20,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,Who said something they shouldn't have said or something awkward?,"The butcher, Louise, Nobody",Nobody,social,2 "Emma was leaving the office when she noticed it was raining. She reached for her umbrella but realized she had left it at home. “I’ll just run,” she thought, and hurried to the bus stop.",22,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,,,,practice_social,1 "Sophia baked a chocolate cake for her grandmother’s birthday. When her cousin Alex saw the cake, he said, “Oh great, cake! I love all cake apart from chocolate cake.” Sophia frowned and said, “Oh, it’s grandma’s favorite.”",23,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,,,,practice_social,1 @@ -50,48 +50,48 @@ The butcher nodded and began wrapping a roasted chicken. "Emily was meeting her friend Alice’s boyfriend for the first time. While chatting, she said, “You and your brother don’t look alike at all!” Sam said. “He’s actually my boyfriend,” he said.",27,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,,,,practice_social,2 "Mia got home and reached into her bag, but her keys weren’t there. She checked again and sighed. “I must have left them at the café,” she thought. She called her friend, who found them on the table.",28,control,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",No,,,,practice_social,2 "Laura gave her friend Nina a handmade scarf for her birthday. Nina unwrapped it and said, “Oh… this is nice. My grandma used to make scarves like these, but I never really wore them.” Laura forced a smile.",29,faux pas,Did anyone say something they shouldn't have said or something awkward?,"Yes, No",Yes,,,,practice_social,2 -"Vicky was at Oliver’s party, talking to him when a neighbor approached. The woman said, “Hello. I’m Maria, what’s your name?” -“I’m Vicky,” she replied. +"Vicky was at Oliver’s party, talking to him when a neighbor approached. The woman said, “Hello. I’m Maria, what’s your name?” +“I’m Vicky,” she replied. “Would anyone like a drink?” Oliver asked.",1,control,Was Vicky at home?,"Yes, No",No,Who was hosting the party?,"Vicky, Maria, Oliver, Nobody",Oliver,control,2 "Helen's husband planned a surprise birthday party. He warned Sarah, ""Don't tell Helen."" While with Helen, Sarah spilled coffee on her dress and said, ""Oh! I was wearing this to your party!"" ""What party?"" Said Helen.",2,faux pas,Was the surprise party for Helen?,"Yes, No",Yes,What got spilled on the dress?,"Juice, Coffee, Tea, Nothing",Coffee,control,2 -"Jim was shopping for a shirt to match his suit. He found the right color but it didn’t fit. -“It’s too small,” he said. +"Jim was shopping for a shirt to match his suit. He found the right color but it didn’t fit. +“It’s too small,” he said. “Not to worry,” the salesman replied. “We’ll have larger sizes next week.”",3,control,Did Jim try something on?,"Yes, No",Yes,What did Jim buy?,"Tie, Shirt, Nothing",Nothing,control,2 "Bob went to the barber for a haircut. “Just take about an inch off,” Bob said. The barber cut the front uneven, so he had to go shorter. “It’s shorter than you wanted,” he said. “Oh well,” Bob replied, “it’ll grow out.”",4,control,Did Bob want to keep the same hairstyle?,"Yes, No",Yes,Where did the barber first cut uneven?,"The front, The back, Nowhere",The front,control,2 "John stopped at the petrol station to fill his car. When the cashier ran his card she said ""The machine won't accept it."" ""That's funny,"" John said. ""I'll pay in cash."" He gave her fifty and said, ""I filled up with unleaded.""",5,control,Did the machine accept John's card?,"Yes, No",No,Did John pay thirty or twenty?,"Thirty, Twenty, Neither",Neither,control,2 -"Jill had just moved into a new flat and bought curtains for her bedroom. After decorating, her best friend Lisa visited. Jill gave her a tour and asked, “How do you like my bedroom?” +"Jill had just moved into a new flat and bought curtains for her bedroom. After decorating, her best friend Lisa visited. Jill gave her a tour and asked, “How do you like my bedroom?” “Those curtains are horrible,” Lisa said.""",6,faux pas,Did Jill just move into a new flat?,"Yes, No",Yes,What had Jill just bought? ,"A flat, Curtains, Nothing",Curtains,control,2 "Sally, a three-year-old girl with short blonde hair was at her Aunt Carol's house. A neighbour, Mary, rang the doorbell and Carol answered. Mary looked at Sally and said, “Oh, I don’t think I’ve met this little boy. What’s your name?”",7,faux pas,Did Sally ring the doorbell?,"Yes, No",No,What colour is Sally's hair?,"Black, Blonde, Brown",Blonde,control,2 -"Joan took her dog, Zack, to the park and threw a stick for him. Pam, a neighbor, passed by and asked, “Want to walk home together?” +"Joan took her dog, Zack, to the park and threw a stick for him. Pam, a neighbor, passed by and asked, “Want to walk home together?” “Sure,” said Joan. She called Jack but he was busy chasing pigeons. “Nevermind, we’ll stay” Joan added.",8,control,Did Joan walk home with Pam?,"Yes, No",No,What was Zack chasing?,"Squirrels, Pigeons",Pigeons,control,2 -"Kim’s cousin Scott was coming to visit, and Kim made an apple pie for him. After dinner, she said, “I made a pie just for you.” +"Kim’s cousin Scott was coming to visit, and Kim made an apple pie for him. After dinner, she said, “I made a pie just for you.” “Mmmm,” Scott replied. “It smells great! I love pies—except for apple, of course.”",9,faux pas,Is Scott Kim's brother?,"Yes, No",No,What kind of pie did Kim make?,"Cherry pie, Blueberry pie, None of the above",None of the above,control,2 -"Jeanette gave Anne a crystal bowl as a wedding gift. A year later, at Anne’s for dinner, Jeanette accidentally broke the bowl. -“I’m sorry,” Jeanette said. +"Jeanette gave Anne a crystal bowl as a wedding gift. A year later, at Anne’s for dinner, Jeanette accidentally broke the bowl. +“I’m sorry,” Jeanette said. “Don’t worry,” Anne replied. “I never liked it anyways. Someone gave it to me.”",10,faux pas,Did the bowl break?,"Yes, No",Yes,What did Jeanette give Anne for her wedding?,"Champagne glasses, Stand mixer, Crystal bowl, None of the above",Crystal bowl,control,2 "Joanne, who had a big role in last year’s play, auditioned for the lead this spring. Checking the cast list, she saw she’d gotten a minor role. When she ran into her boyfriend he said “You must be disappointed.” Joan replied “Yes, I am”",11,control,Did Joanne get a minor role?,"Yes, No",Yes,Who did Joanne run into?,"Her best friend, Her mother, Neither",Neither,control,1 "Joe found a sailing book at the library. After checking his wallet he realized he’d left his library card at home. “I’m sorry,” he said to the librarian. She replied, “That’s OK. If you’re in the computer, you can check out with your driving license”",12,control,Did Joe check out the book?,"Yes, No",Yes,What did Joe forget?,"His library card, His wallet, Neither",His library card,control,1 "Jean West, a manager, called a staff meeting. “John Morehouse, our accountant, is very sick with cancer,” she said. The room fell silent. Robert arrived late, making a joke about illness. Jean cut him off, saying, “Let’s start the meeting.”",13,faux pas,Was Robert late to the meeting?,"Yes, No",Yes,Who arrived late to the meeting?,"Robert, Jean, John, Nobody",Robert,control,1 -"Mike started at a new school. While in a bathroom stall he overheard Joe and Peter at the sinks. -“Doesn’t the new guy, Mike, look weird? And he’s so short!” Joe said. -Mike emerged. +"Mike started at a new school. While in a bathroom stall he overheard Joe and Peter at the sinks. +“Doesn’t the new guy, Mike, look weird? And he’s so short!” Joe said. +Mike emerged. “Oh hi, Mike!” Peter said. “Are you going out to play?”",14,faux pas,Is Mike new in the class?,"Yes, No",Yes,What did Mike give to Joe?,"A football, A hand towel, A bag, Nothing",Nothing,control,1 -"At Fernhaven Elementary, Christine entered a story competition but didn’t win. Her classmate Jake won first prize. -Sitting with him the next day, Jake said, “The other stories were terrible.” +"At Fernhaven Elementary, Christine entered a story competition but didn’t win. Her classmate Jake won first prize. +Sitting with him the next day, Jake said, “The other stories were terrible.” Christine asked, “Where will you put your trophy?”",15,faux pas,Did Christine’s story win anything?,"Yes, No",No,What did Jake win?,"A trophy, A medal, A pen, Nothing",A trophy,control,1 -"Tim spilled coffee on the floor at a restaurant. The waiter said, “I’ll get you another cup,” and left. +"Tim spilled coffee on the floor at a restaurant. The waiter said, “I’ll get you another cup,” and left. Tim approached Jack, a customer by the cashier, and asked, “I spilled coffee by my table. Can you mop it up?”",16,faux pas,Did Jack spill the coffee?,"Yes, No",No,Where was Tim?,"In a restaurant, At home, At school, None of the above",In a restaurant,control,1 -"Eleanor, any older lady, tiredly stood waiting 20 minutes for a late bus. When it arrived there were no seats left. -Eleanor greated her neighbour Paul, speaking about the wait. +"Eleanor, any older lady, tiredly stood waiting 20 minutes for a late bus. When it arrived there were no seats left. +Eleanor greated her neighbour Paul, speaking about the wait. A young man stood and said, “Ma’am, would you like my seat?”",17,control,Did Eleanor get on the bus?,"Yes, No",Yes,How long had Eleanor waited?,"50 minutes, 20 minutes, Neither",20 minutes,control,1 -"Roger, new at the office, told Andrew, “My wife’s a lawyer.” Moments later, Claire walked into the coffee room, complaining, “Lawyers are so arrogant and greedy.” -Andrew asked Claire to review reports. +"Roger, new at the office, told Andrew, “My wife’s a lawyer.” Moments later, Claire walked into the coffee room, complaining, “Lawyers are so arrogant and greedy.” +Andrew asked Claire to review reports. “Not now,” she said. “I need coffee.”",18,faux pas,Is Roger married?,"Yes, No",Yes,Who wants coffee?,"Andrew, Roger, Claire, Nobody",Claire,control,1 -"Richard backed his new red Peugeot into his neighbor Ted’s old Volvo, leaving only a scratch above the wheel. He knocked on Ted’s door, saying, “I’m sorry about the scratch.” +"Richard backed his new red Peugeot into his neighbor Ted’s old Volvo, leaving only a scratch above the wheel. He knocked on Ted’s door, saying, “I’m sorry about the scratch.” Ted looked and said, “Don’t worry. It was an accident.”",19,control,Did Ted buy a new car?,"Yes, No",No,What colour is Richard's new car?,"Red, Blue, Neither",Red,control,1 -"Louise went to the butcher and asked, “Do you have any free-range chickens?” -The butcher nodded and began wrapping a roasted chicken. -“Excuse me,” Louise said, “I asked for free-range.” +"Louise went to the butcher and asked, “Do you have any free-range chickens?” +The butcher nodded and began wrapping a roasted chicken. +“Excuse me,” Louise said, “I asked for free-range.” “Oh sorry,” the butcher replied, “we’re all out.”",20,control,Was Louise at school?,"Yes, No",No,What was Louise buying?,"Vegetables, Meat, Neither",Meat,control,1 "Emma was leaving the office when she noticed it was raining. She reached for her umbrella but realized she had left it at home. “I’ll just run,” she thought, and hurried to the bus stop.",22,control,Did Emma bring her umbrella?,"Yes, No",No,,,,practice_control,2 "Sophia baked a chocolate cake for her grandmother’s birthday. When her cousin Alex saw the cake, he said, “Oh great, cake! I love all cake apart from chocolate cake.” Sophia frowned and said, “Oh, it’s grandma’s favorite.”",23,faux pas,Was the cake for Sophia's grandma?,"Yes, No",Yes,,,,practice_control,2 diff --git a/stimuli/liking/liking.csv b/stimuli/liking/liking.csv index 11992c3c..50bafa54 100644 --- a/stimuli/liking/liking.csv +++ b/stimuli/liking/liking.csv @@ -2,64 +2,10 @@ additional_like,1,dislike1,161,160,3,4.111111111,3.166666667,3,24.04,720,486,2.473684211,2.555555556,2.514619883,2.5625,0.12,dislike like,2,dislike2,172,175,2.333333333,2.222222222,2.166666667,1.777777778,26.52,720,486,1.631578947,1.222222223,1.426900585,1.9375,-0.545454545,dislike additional_like,3,dislike3,185,183,3,2.111111111,2.166666667,3,27.52,720,486,2.473684211,1.222222223,1.847953217,2.1875,-0.043478261,dislike -like,4,dislike4,213,215,3,3.111111111,2.666666667,3,26.16,720,486,2.473684211,1.888888889,2.18128655,2.375,0.090909091,dislike -additional_like,5,dislike5,226,221,2.5,2.777777778,2.166666667,3.777777778,26.92,720,486,1.842105263,1.222222223,1.532163743,2,0.666666667,dislike -additional_like,6,dislike6,223,225,2.833333333,3.555555556,2.5,2.777777778,26.04,720,486,2.263157895,1.666666667,1.964912281,2.25,0,dislike -like,7,dislike7,265,259,2,4.222222222,2.666666667,3.777777778,27.12,720,486,1.210526316,1.888888889,1.549707603,2,0.363636364,dislike -like,8,dislike8,263,262,2.333333333,3,2.666666667,3.333333333,26.04,720,486,1.631578947,1.888888889,1.760233918,2.125,-0.12,dislike -additional_like,9,dislike9,303,306,3.5,4.555555556,3,3.777777778,27.88,720,486,3.105263158,2.333333333,2.719298246,2.6875,0.04,dislike -additional_like,10,dislike10,310,308,3.333333333,3.111111111,2.333333333,2.777777778,26.84,720,486,2.894736842,1.444444444,2.169590643,2.375,-0.52,dislike -additional_like,11,dislike11,330,332,3.166666667,3.444444444,2.666666667,4.333333333,27.2,720,486,2.684210527,1.888888889,2.286549708,2.4375,0,dislike -additional_like,12,dislike12,332,331,3.166666667,4.333333333,2.666666667,2.777777778,26.8,720,486,2.684210527,1.888888889,2.286549708,2.4375,-0.44,dislike -additional_like,13,dislike13,350,349,2.5,3.111111111,2,2.333333333,26.52,720,486,1.842105263,1,1.421052632,1.9375,-0.217391304,dislike -additional_like,14,dislike14,389,386,1.833333333,2.222222222,3,3.888888889,26.12,720,486,1,2.333333333,1.666666667,2.0625,-0.6,dislike -additional_like,15,dislike15,395,398,2.833333333,3.888888889,2.5,3.666666667,25.8,720,486,2.263157895,1.666666667,1.964912281,2.25,-0.181818182,dislike -practice_like,32,dislike19,273,275,3.333333333,4,3,3.333333333,26,720,486,1,2,1,2,0.7,dislike additional_like,16,like1,114,113,5,4.555555556,5,4.555555556,27.28,720,486,5,5,5,4,-0.47826087,like additional_like,17,like2,152,150,5,5,5,4.888888889,26.96,720,486,5,5,5,4,-0.703703704,like like,18,like3,168,170,5,4.666666667,5,4.555555556,26.64,720,486,5,5,5,4,-0.2,like -like,19,like4,178,177,5,5,5,4.555555556,26.6,720,486,5,5,5,4,0,like -like,20,like5,202,205,5,4.333333333,5,4.888888889,26.32,720,486,5,5,5,4,-0.846153846,like -additional_like,21,like6,209,206,5,4.555555556,5,5,27.4,720,486,5,5,5,4,-0.130434783,like -additional_like,22,like7,216,214,5,4.777777778,5,4.888888889,27.84,720,486,5,5,5,4,0.548387097,like -like,23,like8,228,231,5,3.666666667,5,5,26.2,720,486,5,5,5,4,0.259259259,like -additional_like,24,like9,260,261,5,4.666666667,5,4.375,27.32,720,486,5,5,5,4,-0.172413793,like -additional_like,25,like10,277,275,5,4.888888889,5,4.75,28.04,720,486,5,5,5,4,-0.75,like -additional_like,26,like11,283,280,5,4.444444444,5,5,28.36,720,486,5,5,5,4,-0.217391304,like -additional_like,27,like12,294,295,5,5,5,4.888888889,25.64,720,486,5,5,5,4,0.333333333,like -additional_like,28,like13,343,344,5,4.111111111,5,5,26.92,720,486,5,5,5,4,-0.391304348,like -additional_like,29,like14,359,355,5,4.888888889,5,5,26.68,720,486,5,5,5,4,0,like -additional_like,30,like15,389,388,5,5,5,5,26.52,720,486,5,5,5,4,0,like -additional_social,31,like19,214,219,5,5,5,4.222222222,25,720,486,5,5,5,4,-0.84,like -additional_control,1,dislike1,161,160,3,4.111111111,3.166666667,3,24.04,720,486,2.473684211,2.555555556,2.514619883,2.5625,0.12,balanced -additional_control,2,dislike2,172,175,2.333333333,2.222222222,2.166666667,1.777777778,26.52,720,486,1.631578947,1.222222223,1.426900585,1.9375,-0.545454545,unbalanced -additional_control,3,dislike3,185,183,3,2.111111111,2.166666667,3,27.52,720,486,2.473684211,1.222222223,1.847953217,2.1875,-0.043478261,balanced additional_control,4,dislike4,213,215,3,3.111111111,2.666666667,3,26.16,720,486,2.473684211,1.888888889,2.18128655,2.375,0.090909091,balanced control,5,dislike5,226,221,2.5,2.777777778,2.166666667,3.777777778,26.92,720,486,1.842105263,1.222222223,1.532163743,2,0.666666667,unbalanced -control,6,dislike6,223,225,2.833333333,3.555555556,2.5,2.777777778,26.04,720,486,2.263157895,1.666666667,1.964912281,2.25,0,balanced -additional_control,7,dislike7,265,259,2,4.222222222,2.666666667,3.777777778,27.12,720,486,1.210526316,1.888888889,1.549707603,2,0.363636364,unbalanced -additional_control,8,dislike8,263,262,2.333333333,3,2.666666667,3.333333333,26.04,720,486,1.631578947,1.888888889,1.760233918,2.125,-0.12,balanced -additional_control,9,dislike9,303,306,3.5,4.555555556,3,3.777777778,27.88,720,486,3.105263158,2.333333333,2.719298246,2.6875,0.04,balanced -control,10,dislike10,310,308,3.333333333,3.111111111,2.333333333,2.777777778,26.84,720,486,2.894736842,1.444444444,2.169590643,2.375,-0.52,unbalanced -control,11,dislike11,330,332,3.166666667,3.444444444,2.666666667,4.333333333,27.2,720,486,2.684210527,1.888888889,2.286549708,2.4375,0,balanced -additional_control,12,dislike12,332,331,3.166666667,4.333333333,2.666666667,2.777777778,26.8,720,486,2.684210527,1.888888889,2.286549708,2.4375,-0.44,unbalanced -additional_control,13,dislike13,350,349,2.5,3.111111111,2,2.333333333,26.52,720,486,1.842105263,1,1.421052632,1.9375,-0.217391304,N/A -additional_control,14,dislike14,389,386,1.833333333,2.222222222,3,3.888888889,26.12,720,486,1,2.333333333,1.666666667,2.0625,-0.6,unbalanced -additional_control,15,dislike15,395,398,2.833333333,3.888888889,2.5,3.666666667,25.8,720,486,2.263157895,1.666666667,1.964912281,2.25,-0.181818182,N/A -additional_like,32,dislike19,273,275,3.333333333,4,3,3.333333333,26,720,486,1,2,1,2,0.7,unbalanced -additional_control,16,like1,114,113,5,4.555555556,5,4.555555556,27.28,720,486,5,5,5,4,-0.47826087,unbalanced -control,17,like2,152,150,5,5,5,4.888888889,26.96,720,486,5,5,5,4,-0.703703704,unbalanced -additional_control,18,like3,168,170,5,4.666666667,5,4.555555556,26.64,720,486,5,5,5,4,-0.2,N/A additional_control,19,like4,178,177,5,5,5,4.555555556,26.6,720,486,5,5,5,4,0,balanced additional_control,20,like5,202,205,5,4.333333333,5,4.888888889,26.32,720,486,5,5,5,4,-0.846153846,unbalanced -control,21,like6,209,206,5,4.555555556,5,5,27.4,720,486,5,5,5,4,-0.130434783,balanced -control,22,like7,216,214,5,4.777777778,5,4.888888889,27.84,720,486,5,5,5,4,0.548387097,unbalanced -additional_control,23,like8,228,231,5,3.666666667,5,5,26.2,720,486,5,5,5,4,0.259259259,N/A -additional_control,24,like9,260,261,5,4.666666667,5,4.375,27.32,720,486,5,5,5,4,-0.172413793,N/A -additional_control,25,like10,277,275,5,4.888888889,5,4.75,28.04,720,486,5,5,5,4,-0.75,unbalanced -additional_control,26,like11,283,280,5,4.444444444,5,5,28.36,720,486,5,5,5,4,-0.217391304,N/A -additional_control,27,like12,294,295,5,5,5,4.888888889,25.64,720,486,5,5,5,4,0.333333333,unbalanced -additional_control,28,like13,343,344,5,4.111111111,5,5,26.92,720,486,5,5,5,4,-0.391304348,unbalanced -control,29,like14,359,355,5,4.888888889,5,5,26.68,720,486,5,5,5,4,0,balanced -additional_control,30,like15,389,388,5,5,5,5,26.52,720,486,5,5,5,4,0,balanced -practice_control,31,like19,214,219,5,5,5,4.222222222,25,720,486,5,5,5,4,-0.84,unbalanced \ No newline at end of file diff --git a/stimuli/sensmot_control/sensmot_control_block1.csv b/stimuli/sensmot_control/sensmot_control_block1.csv new file mode 100644 index 00000000..4c446c11 --- /dev/null +++ b/stimuli/sensmot_control/sensmot_control_block1.csv @@ -0,0 +1,26 @@ +color condition corr_resp +blue blue 1 +blue blue 1 +white blue 0 +blue blue 1 +blue blue 1 +blue blue 1 +blue blue 1 +white blue 0 +blue blue 1 +blue blue 1 +blue blue 1 +blue blue 1 +white blue 0 +blue blue 1 +white blue 0 +blue blue 1 +blue blue 1 +blue blue 1 +blue blue 1 +blue blue 1 +blue blue 1 +white blue 0 +blue blue 1 +blue blue 1 +blue blue 1 \ No newline at end of file diff --git a/stimuli/sensmot_control/sensmot_control_block2.csv b/stimuli/sensmot_control/sensmot_control_block2.csv new file mode 100644 index 00000000..33814a8f --- /dev/null +++ b/stimuli/sensmot_control/sensmot_control_block2.csv @@ -0,0 +1,26 @@ +color condition corr_resp +red red 2 +red red 2 +red red 2 +red red 2 +white red 0 +red red 2 +white red 0 +red red 2 +red red 2 +red red 2 +red red 2 +red red 2 +red red 2 +white red 0 +red red 2 +red red 2 +red red 2 +white red 0 +red red 2 +red red 2 +red red 2 +white red 0 +red red 2 +red red 2 +red red 2 \ No newline at end of file diff --git a/stimuli/sensmot_control/sensmot_control_block3.csv b/stimuli/sensmot_control/sensmot_control_block3.csv new file mode 100644 index 00000000..03217585 --- /dev/null +++ b/stimuli/sensmot_control/sensmot_control_block3.csv @@ -0,0 +1,26 @@ +color condition corr_resp +blue 2AFC 1 +red 2AFC 2 +white 2AFC 0 +blue 2AFC 1 +blue 2AFC 1 +red 2AFC 2 +red 2AFC 2 +white 2AFC 0 +blue 2AFC 1 +blue 2AFC 1 +red 2AFC 2 +red 2AFC 2 +white 2AFC 0 +blue 2AFC 1 +red 2AFC 2 +blue 2AFC 1 +white 2AFC 0 +blue 2AFC 1 +blue 2AFC 1 +red 2AFC 2 +blue 2AFC 1 +red 2AFC 2 +red 2AFC 2 +red 2AFC 2 +white 2AFC 0 \ No newline at end of file