From fba33e5d608c94458e0c58568872ecd24d466ef3 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:09:51 +0100 Subject: [PATCH 01/19] Added opm tasks to task blocks --- MultiTaskBattery/task_blocks.py | 595 ++++++++++++++++++++++++-------- 1 file changed, 452 insertions(+), 143 deletions(-) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index 1cd3fc5f..6f0aab0c 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -1,3 +1,4 @@ +#%% # Task Class definitions # March 2021: First version: Ladan Shahshahani - Maedbh King - Suzanne Witt, # Revised 2023: Bassel Arafat, Jorn Diedrichsen, Incé Husain @@ -8,7 +9,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,6 +18,7 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip import gc import math +import json @@ -72,7 +74,7 @@ def display_instructions(self): # instr.size = 0.8 instr_visual.draw() self.window.flip() - + def run(self): """Loop over trials and collects data Data will be stored in self.trial_data @@ -99,37 +101,37 @@ def run(self): if self.feedback_type[-2:] == 'rt': rt = self.trial_data['rt'].mean() return acc,rt - + def show_progress(self, seconds_left, show_last_seconds=5, height=1, width=10, x_pos=-5, y_pos=8): """ Displays a progress bar for the Picture Sequence task Args: - seconds_left (float): - The number of seconds remaining in the current trial. + seconds_left (float): + The number of seconds remaining in the current trial. If this value is greater than `show_last_seconds`, the progress bar is not shown. - show_last_seconds (float, optional): - The duration (in seconds) over which to display the progress bar at the end of a trial. + show_last_seconds (float, optional): + The duration (in seconds) over which to display the progress bar at the end of a trial. Default is 5 seconds. When `seconds_left` is less than this value, the progress bar appears. - height (float, optional): + height (float, optional): The vertical size of the progress bar in PsychoPy window units. Default is 1. - width (float, optional): + width (float, optional): The horizontal size of the progress bar in PsychoPy window units. Default is 10. - x_pos (float, optional): - The horizontal position of the center of the progress bar in window coordinates. + x_pos (float, optional): + The horizontal position of the center of the progress bar in window coordinates. Negative values move it leftward. Default is -5. - y_pos (float, optional): - The vertical position of the center of the progress bar in window coordinates. + y_pos (float, optional): + The vertical position of the center of the progress bar in window coordinates. Positive values move it upward. Default is 8. """ # If we are in the last five seconds of the trial, display the remaining time if seconds_left < show_last_seconds: progress = visual.Progress( - win=self.window, + win=self.window, progress=1-(seconds_left/show_last_seconds), size=(width, height), pos=(x_pos, y_pos), @@ -160,8 +162,8 @@ def wait_response(self, start_time, max_wait_time, show_last_seconds=None, curre current_stimuli.draw() seconds_left = max_wait_time - (self.ttl_clock.get_time() - start_time) self.show_progress(seconds_left, - show_last_seconds=show_last_seconds, - y_pos=6) + show_last_seconds=show_last_seconds, + y_pos=6) self.window.flip() keys=event.getKeys(keyList= self.const.response_keys, timeStamped=self.ttl_clock.clock) if len(keys)>0: @@ -409,19 +411,324 @@ 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 + """ + txt = (f'{self.descriptive_name} Task\n\n' + f'Tap along to the tones using the "{self.corr_key[0]}" key.\n\n' + f'Keep tapping at the same pace when the tones stop.') + instr_visual = visual.TextStim(self.window, text=txt, 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) + + beep = sound.Sound(1000, secs=0.05) + + # --- Play FIRST tone now, then use THIS time as the grid anchor + 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 + + 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: + beep.play() + 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) + + 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.name = 'time_perception' + 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) # 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': + txt = (f"You will hear two pairs of tones.\n\n" + f"Press [{self.corr_key[0]}] if the SECOND interval is shorter.\n" + f"Press [{self.corr_key[1]}] if the SECOND interval is longer.") + else: + txt = (f"You will hear two pairs of tones.\n\n" + f"Press [{self.corr_key[0]}] if the SECOND pair is quieter.\n" + f"Press [{self.corr_key[1]}] if the SECOND pair is louder.\n" + f"The first pair is always the same.") + visual.TextStim(self.window, text=txt, + 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): 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) + + # Apply movement to current level, then clamp/snap + if mod == 'time': + if side == 'shorter': + st['curr'] += (+st['step'] if trial['correct'] else -st['step']) + st['curr'] = max(160.0, min(392.0, float(int(round(st['curr'] / 8.0) * 8)))) + else: + st['curr'] += (-st['step'] if trial['correct'] else +st['step']) + st['curr'] = max(408.0, min(640.0, float(int(round(st['curr'] / 8.0) * 8)))) + else: + # volume (quieter: toward=+; louder: toward=-), clamp to side range + if side == 'quieter': + st['curr'] += (+st['step'] if trial['correct'] else -st['step']) + st['curr'] = max(64.9, min(72.73, st['curr'])) + else: + st['curr'] += (-st['step'] if trial['correct'] else +st['step']) + st['curr'] = max(73.27, min(81.1, st['curr'])) + + # Reversal check + if st['last_dir'] != 0 and move_dir != st['last_dir']: + # reversal -> halve step (floor at min_step), reset same_dir + st['step'] = max(st['min_step'], st['step'] / 2.0) + st['same_dir'] = 0 + else: + # same direction -> count; double after two consecutive moves, then reset the counter + st['same_dir'] += 1 + if st['same_dir'] >= 2: + st['step'] = st['step'] * 2.0 + st['same_dir'] = 0 # require two more same-direction moves for next doubling + + st['last_dir'] = move_dir + self.stair[side] = st + + # feedback (practice flag from TSV) + 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.name = 'sensmotcontrol' + 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': + self.instruction_text = (f"When the circle turns BLUE, press {self.corr_key[0]}.\n" + f"When the circle turns WHITE, do nothing.\n\n") + elif cond == 'red': + self.instruction_text = (f"When the circle turns RED, press {self.corr_key[1]}.\n" + f"When the circle turns WHITE, do nothing.\n\n") + else: + self.instruction_text = (f"When the circle turns BLUE, press {self.corr_key[0]}. \n" + f"When the circle turns RED, press {self.corr_key[1]}. \n" + "When the circle turns WHITE, do nothing.\n\n") + 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'] end_location = self.trial_info.iloc[0]['location_2'] self.instruction_text = (f"{self.descriptive_name} Task \n\n" - f"Imagine walking around your childhood home\n" - f"Start in the {start_location} – end in the {end_location}\n" - f"Focus on the fixation cross") + f"Imagine walking around your childhood home\n" + f"Start in the {start_location} – end in the {end_location}\n" + f"Focus on the fixation cross") instr_visual = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1], wrapWidth=20) instr_visual.draw() self.window.flip() @@ -449,7 +756,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 @@ -471,7 +778,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')) @@ -650,8 +957,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) @@ -726,7 +1033,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'] @@ -769,7 +1076,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 @@ -844,8 +1151,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): """ @@ -892,7 +1199,7 @@ def run_trial(self, trial): return trial - + class FingerSequence(Task): """ Finger sequence task @@ -922,12 +1229,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) @@ -938,7 +1245,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 @@ -960,7 +1267,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' @@ -986,12 +1293,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. @@ -1032,7 +1339,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): @@ -1050,7 +1357,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): """ @@ -1063,11 +1370,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() @@ -1099,7 +1406,7 @@ def run_trial(self, trial): self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) return trial - + class VisualSearch(Task): """ @@ -1117,10 +1424,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 @@ -1128,7 +1435,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 @@ -1140,7 +1447,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') @@ -1151,12 +1458,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 @@ -1165,16 +1472,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): """ @@ -1194,11 +1501,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) @@ -1210,10 +1517,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 @@ -1249,10 +1556,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'] @@ -1266,7 +1573,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 @@ -1280,7 +1587,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: @@ -1289,12 +1596,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 @@ -1306,7 +1613,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']) @@ -1334,7 +1641,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: @@ -1349,17 +1656,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] @@ -1392,7 +1699,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 @@ -1403,7 +1710,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() @@ -1411,7 +1718,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, @@ -1435,7 +1742,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 @@ -1446,7 +1753,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) @@ -1475,7 +1782,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: @@ -1485,33 +1792,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 @@ -1529,17 +1836,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) @@ -1558,7 +1865,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 @@ -1568,12 +1875,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) @@ -1582,7 +1889,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 @@ -1613,17 +1920,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() @@ -1672,7 +1979,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'] @@ -1685,7 +1992,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() @@ -1700,13 +2007,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]] @@ -1733,7 +2040,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'] @@ -1750,10 +2057,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() @@ -1780,7 +2087,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]}" @@ -1789,7 +2096,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 @@ -1806,8 +2113,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() @@ -1821,14 +2128,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): @@ -1842,7 +2149,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 @@ -1867,7 +2174,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() @@ -1899,7 +2206,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): @@ -1931,10 +2238,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 @@ -1943,8 +2250,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() @@ -1988,7 +2295,7 @@ def run_trial(self, trial): gc.collect() # Collect garbarge return trial - + class Liking(Task): @@ -2015,7 +2322,7 @@ def display_instructions(self): 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') + wrapWidth=20, pos=(-3, -3), alignHoriz='left') key_text.draw() self.window.flip() @@ -2023,7 +2330,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 @@ -2040,13 +2347,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() @@ -2071,7 +2378,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() @@ -2096,11 +2403,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']) @@ -2129,7 +2436,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() @@ -2145,8 +2452,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)) @@ -2165,8 +2472,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() @@ -2190,11 +2497,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]) @@ -2248,7 +2555,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 @@ -2292,3 +2599,5 @@ def run_trial(self, trial): return trial + +#%% From 390d7938bc70ddaea9fc2ce7f39f98099e195825 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:09:56 +0100 Subject: [PATCH 02/19] Added opm tasks to task file --- MultiTaskBattery/task_file.py | 561 ++++++++++++++++++++++------------ 1 file changed, 361 insertions(+), 200 deletions(-) diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index f2e8a1e7..071d69fc 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -1,3 +1,4 @@ +#%% # Create TaskFile file for different tasks # March 2021: First version: Ladan Shahshahani - Maedbh King - Suzanne Witt, # Revised 2023: Bassel Arafat, Jorn Diedrichsen, Ince Husain @@ -24,7 +25,7 @@ def shuffle_rows(dataframe, keep_in_middle=None): if keep_in_middle is not None: dataframe = move_edge_tasks_to_middle(dataframe, keep_in_middle) - + return dataframe def move_edge_tasks_to_middle(dataframe, keep_in_middle): @@ -125,13 +126,13 @@ def __init__(self, const): self.name = 'n_back' def make_task_file(self, - hand = 'right', - responses = [1,2], # 1 = match, 2 = no match - task_dur = 30, - trial_dur = 2, - iti_dur = 0.5, - stim = ['9.jpg','11.jpg','18.jpg','28.jpg'], - file_name = None ): + hand = 'right', + responses = [1,2], # 1 = match, 2 = no match + task_dur = 30, + trial_dur = 2, + iti_dur = 0.5, + stim = ['9.jpg','11.jpg','18.jpg','28.jpg'], + file_name = None ): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -147,7 +148,7 @@ def make_task_file(self, trial['key_match'] = responses[0] trial['key_nomatch'] = responses[1] # Determine if this should be N-2 repetition trial - + if n<2: trial['trial_type'] = 0 else: @@ -183,8 +184,8 @@ def __init__(self, const): self.name = 'rest' def make_task_file(self, - task_dur = 30, - file_name = None): + task_dur = 30, + file_name = None): trial = {} trial['trial_num'] = [1] trial['trial_dur'] = [task_dur] @@ -199,14 +200,14 @@ class VerbGeneration(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'verb_generation' - + def make_task_file(self, - task_dur = 30, - trial_dur = 2, - iti_dur = 0.5, - file_name = None, - stim_file = None): + task_dur = 30, + trial_dur = 2, + iti_dur = 0.5, + file_name = None, + stim_file = None): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -214,7 +215,7 @@ def make_task_file(self, stim = pd.read_csv(stim_file) else: stim = pd.read_csv(self.stim_dir / 'verb_generation' / 'verb_generation.csv') - + stim = stim.sample(frac=1).reset_index(drop=True) t = 0 @@ -253,10 +254,10 @@ def __init__(self, const): self.name = 'tongue_movement' def make_task_file(self, - task_dur = 30, - trial_dur = 1, - iti_dur = 0, - file_name = None): + task_dur = 30, + trial_dur = 1, + iti_dur = 0, + file_name = None): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -323,9 +324,9 @@ def __init__(self, const): super().__init__(const) self.name = 'spatial_navigation' self.location_pairs = [('FRONT-DOOR', 'LIVING-ROOM'), ('WASHROOM', 'LIVING-ROOM'), ('BEDROOM', 'LIVING-ROOM'), - ('KITCHEN', 'LIVING-ROOM'), ('FRONT-DOOR', 'WASHROOM'), ('BEDROOM', 'FRONT-DOOR'), - ('KITCHEN', 'FRONT-DOOR'), ('KITCHEN', 'BEDROOM'), ('KITCHEN', 'WASHROOM'), - ('BEDROOM', 'WASHROOM')] + ('KITCHEN', 'LIVING-ROOM'), ('FRONT-DOOR', 'WASHROOM'), ('BEDROOM', 'FRONT-DOOR'), + ('KITCHEN', 'FRONT-DOOR'), ('KITCHEN', 'BEDROOM'), ('KITCHEN', 'WASHROOM'), + ('BEDROOM', 'WASHROOM')] def make_task_file(self, @@ -369,14 +370,14 @@ def __init__(self, const): def make_task_file(self, hand='right', responses = [1,2], # 1 = True, 2 = False run_number=None, - task_dur=30, - trial_dur=14, - iti_dur=1, - story_dur=10, - question_dur=4, - file_name=None, - stim_file=None, - condition=None): + task_dur=300, + trial_dur=14, + iti_dur=1, + story_dur=10, + question_dur=4, + file_name=None, + stim_file=None, + condition=None): # count number of trials n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) @@ -392,7 +393,7 @@ def make_task_file(self, hand='right', 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) @@ -434,11 +435,11 @@ def __init__(self, const): self.name = 'degraded_passage' def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=14.5, - iti_dur=0.5, - file_name=None): + run_number = None, + task_dur=30, + trial_dur=14.5, + iti_dur=0.5, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -472,11 +473,11 @@ def __init__(self, const): self.name = 'intact_passage' def make_task_file(self, - run_number, - task_dur=30, - trial_dur=14.5, - iti_dur=0.5, - file_name=None): + run_number, + task_dur=30, + trial_dur=14.5, + iti_dur=0.5, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -512,27 +513,27 @@ def __init__(self, const): # Medium/bad knot vids # self.knot_names = [ - # 'Ampere', 'Arbor', 'Baron', 'Belfry', 'Bramble', 'Chamois', 'Coffer', - # 'Farthing', 'Fissure', 'Gentry', 'Henchman', 'Magnate', 'Perry', 'Phial', 'Polka', + # 'Ampere', 'Arbor', 'Baron', 'Belfry', 'Bramble', 'Chamois', 'Coffer', + # 'Farthing', 'Fissure', 'Gentry', 'Henchman', 'Magnate', 'Perry', 'Phial', 'Polka', # 'Rosin', 'Shilling', 'Simper', 'Spangle', 'Squire', 'Vestment', 'Wampum', 'Wicket' # ] # good knot vids - # self.knot_names = ['Adage', + # self.knot_names = ['Adage', # 'Brigand', 'Brocade', 'Casement', 'Cornice',\ # 'Flora', 'Frontage', 'Gadfly', 'Garret', \ # 'Mutton','Placard', 'Purser'] def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=14, - iti_dur=1, - file_name=None, - knot_names = ['Adage', - 'Brigand', 'Brocade', 'Casement', 'Cornice',\ - 'Flora', 'Frontage', 'Gadfly', 'Garret', \ - 'Mutton','Placard', 'Purser']): + run_number = None, + task_dur=30, + trial_dur=14, + iti_dur=1, + file_name=None, + knot_names = ['Adage', + 'Brigand', 'Brocade', 'Casement', 'Cornice', \ + 'Flora', 'Frontage', 'Gadfly', 'Garret', \ + 'Mutton','Placard', 'Purser']): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -673,7 +674,7 @@ def modify_sequence(self, sequence, grid_size): if not available_positions: raise ValueError("Not enough valid adjacent positions available for modification.") - + # Choose a random position from the available ones new_pos = random.choice(list(available_positions)) new_step.append(new_pos) @@ -691,19 +692,19 @@ def modify_sequence(self, sequence, grid_size): # If all steps fail, raise an error raise ValueError("No valid step could be modified with the given constraints.") - + def make_task_file(self, - hand='right', - responses=[1, 2], # 1 = Left, 2 = Right - grid_size=(3, 4), - num_steps=3, - num_boxes_lit=2, - task_dur=30, - trial_dur=7, - question_dur=3, - sequence_dur=4, - iti_dur=0.5, - file_name=None): + hand='right', + responses=[1, 2], # 1 = Left, 2 = Right + grid_size=(3, 4), + num_steps=3, + num_boxes_lit=2, + task_dur=30, + trial_dur=7, + question_dur=3, + sequence_dur=4, + iti_dur=0.5, + file_name=None): """ Create a task file with the specified parameters. @@ -732,14 +733,14 @@ def make_task_file(self, try: # Generate the original sequence original_sequence = self.generate_sequence(grid_size, num_steps, num_boxes_lit) - # Attempt to create a modified sequence + # Attempt to create a modified sequence modified_sequence = self.modify_sequence(original_sequence, grid_size=grid_size) break except ValueError: continue correct_side = random.choice(['left', 'right']) - trial_type = 0 if correct_side == 'left' else 1 + trial_type = 0 if correct_side == 'left' else 1 trial = { 'key_left': responses[0], 'key_right': responses[1], @@ -777,12 +778,12 @@ def __init__(self, const): self.name = 'sentence_reading' def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=5.8, - iti_dur=0.2, - file_name=None, - stim_file=None): + run_number = None, + task_dur=30, + trial_dur=5.8, + iti_dur=0.2, + file_name=None, + stim_file=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -820,16 +821,16 @@ def __init__(self, const): self.name = 'nonword_reading' def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=5.8, - iti_dur=0.2, - file_name=None, - stim_file=None): - + run_number = None, + task_dur=30, + trial_dur=5.8, + iti_dur=0.2, + file_name=None, + stim_file=None): + n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] - + if stim_file: stim = pd.read_csv(stim_file) else: @@ -862,15 +863,15 @@ class OddBall(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'oddball' - + def make_task_file(self, - hand = 'right', - responses = [1,2], # 1,2 any press is ok - task_dur=30, - trial_dur=0.15, - iti_dur=0.85, - file_name=None): + hand = 'right', + responses = [1,2], # 1,2 any press is ok + task_dur=30, + trial_dur=0.15, + iti_dur=0.85, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -904,13 +905,13 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class FingerSequence(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'finger_sequence' self.matching_stimuli = False # sequence of numbers are different for easy and hard sequence condition - + def generate_sequence(self): sequence = [random.choice([1, 2, 3, 4])] while len(sequence) < 6: @@ -919,12 +920,12 @@ def generate_sequence(self): return ' '.join(map(str, sequence)) def make_task_file(self, - hand = 'bimanual', - responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - task_dur=30, - trial_dur=3.25, - iti_dur=0.5, - file_name=None): + hand = 'bimanual', + responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four + task_dur= 10, + trial_dur=3.25, + iti_dur=0.5, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -955,17 +956,175 @@ 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=20, # must be even + trial_dur=4.0, # 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 = 'sensmotcontrol' + + def make_task_file(self, + hand='right', + responses=[1, 2], + run_number=None, + task_dur= 40, + 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 + + class FlexionExtension(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'flexion_extension' def make_task_file(self, - task_dur = 30, - trial_dur = 30, - iti_dur = 0, - stim_dur = 2, - file_name = None): + task_dur = 30, + trial_dur = 30, + iti_dur = 0, + stim_dur = 2, + file_name = None): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -1000,11 +1159,11 @@ def __init__(self, const): def make_task_file(self, hand='right', responses = [1,2], # 1 = True, 2 = False run_number=None, - task_dur=30, - trial_dur=15, - sentence_dur=2, - file_name=None, - stim_file=None): + task_dur=300, + trial_dur=15, + sentence_dur=2, + file_name=None, + stim_file=None): # count number of trials n_trials = int(np.floor(task_dur / (trial_dur))) @@ -1020,7 +1179,7 @@ def make_task_file(self, hand='right', 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): + for n in range(n_trials): trial = {} trial['key_true'] = responses[0] trial['key_false'] = responses[1] @@ -1029,12 +1188,12 @@ def make_task_file(self, hand='right', trial['trial_dur'] = trial_dur trial['sentence_dur'] = sentence_dur trial['sentence'] = stim['sentence'][n] - trial['trial_type'] = random.choice([0,1]) + trial['trial_type'] = random.choice([0,1]) last_word = [stim['wrong_word'][n], stim['right_word'][n]] trial['last_word'] = last_word[trial['trial_type']] trial['display_trial_feedback'] = True trial['start_time'] = t - trial['end_time'] = t + trial_dur + trial['end_time'] = t + trial_dur trial_info.append(trial) @@ -1052,16 +1211,16 @@ def __init__(self, const): self.name = 'visual_search' def make_task_file(self, - hand = 'right', #to recode for alternating hands: put left here, and put 3,4 in responses - responses = [1,2], # 1 = match, 2 = no match - task_dur = 30, - trial_dur = 2, - iti_dur = 0.5, - easy_prob=0.5, - file_name = None ): + hand = 'right', #to recode for alternating hands: put left here, and put 3,4 in responses + responses = [1,2], # 1 = match, 2 = no match + task_dur = 30, + trial_dur = 2, + iti_dur = 0.5, + easy_prob=0.5, + file_name = None ): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] - t = 0 + t = 0 for n in range(n_trials): trial = {} @@ -1071,15 +1230,15 @@ def make_task_file(self, trial['hand'] = hand trial['trial_dur'] = trial_dur trial['iti_dur'] = iti_dur - trial['display_trial_feedback'] = True - trial['trial_type'] = random.choice([0,1]) + trial['display_trial_feedback'] = True + trial['trial_type'] = random.choice([0,1]) trial['num_stimuli'] = '4' if random.random() < easy_prob else '8' # Randomly select difficulty trial['display_trial_feedback'] = True trial['feedback_type'] = 'acc' trial['start_time'] = t trial['end_time'] = t + trial_dur + iti_dur - # Determine the number of stimuli to display based on trial difficulty + # Determine the number of stimuli to display based on trial difficulty num_stimuli = 4 if trial['num_stimuli'] == '4' else 8 trial_info.append(trial) @@ -1102,16 +1261,16 @@ def __init__(self, const): self.repeat_stimuli_from_previous_runs = True def make_task_file(self, hand='right', - responses = [1,2,3,4], - run_number=None, - task_dur=30, - trial_dur=6, - iti_dur=1.5, - file_name=None, - stim_file = None, - condition=None, - half=None): - + responses = [1,2,3,4], + run_number=None, + task_dur=30, + trial_dur=6, + iti_dur=1.5, + file_name=None, + stim_file = None, + condition=None, + half=None): + # count number of trials n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) @@ -1135,9 +1294,9 @@ def make_task_file(self, hand='right', stim_age = stim[stim['condition'] == 'age'] # Split each condition into halves first_half = zip(stim_emotion.iloc[:len(stim_emotion) // 2].iterrows(), - stim_age.iloc[len(stim_age) // 2:].iterrows()) + stim_age.iloc[len(stim_age) // 2:].iterrows()) second_half = zip(stim_emotion.iloc[len(stim_emotion) // 2:].iterrows(), - stim_age.iloc[:len(stim_age) // 2].iterrows()) + stim_age.iloc[:len(stim_age) // 2].iterrows()) stim = pd.concat([pd.concat([row1[1], row2[1]], axis=1).T for row1, row2 in itertools.chain(first_half, second_half)], ignore_index=True) if half: # Selects different stimuli for the social and control condition, to enable showing each story only once for each participant @@ -1152,9 +1311,9 @@ def make_task_file(self, hand='right', stim = stim[stim['half'] != half].iloc[new_start_row:new_end_row + 1].reset_index(drop=True) else: raise ValueError('Not enough stimuli for the run') - else: + else: stim = stim.iloc[start_row:end_row + 1].reset_index(drop=True) - + for n in range(n_trials): trial = {} trial['key_one'] = responses[0] @@ -1188,21 +1347,21 @@ def __init__(self, const): super().__init__(const) self.name = 'picture_sequence' self.matching_stimuli = False # sequence of pictures are different for different conditions - + def generate_sequence(self): sequence = random.sample([1, 2, 3, 4], 4) return ' '.join(map(str, sequence)) def make_task_file(self, - hand = 'right', - responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - run_number=None, - task_dur=30, - trial_dur=14, - iti_dur=1, - file_name=None, - stim_file = None, - condition=None): + hand = 'right', + responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four + run_number=None, + task_dur=30, + trial_dur=14, + iti_dur=1, + file_name=None, + stim_file = None, + condition=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] t = 0 @@ -1254,21 +1413,21 @@ def __init__(self, const): super().__init__(const) self.name = 'story_sequence' self.matching_stimuli = False # sequence of sentences are different for different conditions - + def generate_sequence(self): sequence = random.sample([1, 2, 3, 4], 4) return ' '.join(map(str, sequence)) def make_task_file(self, - hand = 'right', - responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - run_number=None, - task_dur=30, - trial_dur=14, - iti_dur=1, - file_name=None, - stim_file = None, - condition=None): + hand = 'right', + responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four + run_number=None, + task_dur=30, + trial_dur=14, + iti_dur=1, + file_name=None, + stim_file = None, + condition=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] t = 0 @@ -1325,16 +1484,16 @@ def __init__(self, const): self.matching_stimuli = False # sequence of pictures are different for different conditions def make_task_file(self, hand='right', - responses = [1,2], - run_number=None, - task_dur=30, - trial_dur=5, - iti_dur=1, - question_dur=4, - file_name=None, - stim_file = None, - condition=None): - + responses = [1,2], + run_number=None, + task_dur=300, + trial_dur=5, + iti_dur=1, + question_dur=4, + file_name=None, + stim_file = None, + condition=None): + # count number of trials n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) @@ -1439,7 +1598,7 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class StrangeStories(TaskFile): def __init__(self, const): @@ -1509,7 +1668,7 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class FauxPas(TaskFile): def __init__(self, const): @@ -1520,17 +1679,17 @@ def __init__(self, const): self.repeat_stimuli_from_previous_runs = True def make_task_file(self, hand='right', - responses = [1,2], # 1 = True, 2 = False - run_number=None, - task_dur=30, - trial_dur=14, - iti_dur=1, - story_dur=10, - question1_dur=4, - file_name=None, - stim_file=None, - condition=None, - half=None): + responses = [1,2], # 1 = True, 2 = False + run_number=None, + task_dur=30, + trial_dur=14, + iti_dur=1, + story_dur=10, + question1_dur=4, + file_name=None, + stim_file=None, + condition=None, + half=None): # count number of trials @@ -1550,7 +1709,7 @@ def make_task_file(self, hand='right', stim = stim[stim['condition'] == condition] else: stim = stim.loc[~stim['condition'].str.contains('practice', na=False)] - + if half: # Selects different stimuli for the social and control condition, to enable showing each story only once for each participant stim_half = stim[stim['half'] == half] if n_trials <= stim_half.iloc[start_row:end_row + 1].shape[0]: # Check if there are enough stimuli for the run @@ -1563,9 +1722,9 @@ def make_task_file(self, hand='right', stim = stim[stim['half'] != half].iloc[new_start_row:new_end_row + 1].reset_index(drop=True) else: raise ValueError('Not enough stimuli for the run') - else: + else: stim = stim.iloc[start_row:end_row + 1].reset_index(drop=True) - + for n in range(n_trials): trial = {} trial['key_yes'] = responses[0] @@ -1596,7 +1755,7 @@ def make_task_file(self, hand='right', if file_name is not None: trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class FrithHappe(TaskFile): @@ -1666,7 +1825,7 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class Liking(TaskFile): @@ -1674,7 +1833,7 @@ def __init__(self, const): super().__init__(const) self.name = 'liking' self.matching_stimuli = False - + def map_to_4point_scale(self, rating): """ Map the liking rating from a 1-to-5 scale to the closest value on a 4-point scale @@ -1700,7 +1859,7 @@ def map_to_4point_scale(self, rating): # Round to the nearest integer return round(mapped_value) - + def make_task_file(self, hand='right', responses = [1,2], @@ -1759,24 +1918,24 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class Pong(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'pong' self.trajectories = [ - (0.3, -0.15), (-0.3, -0.15), (0.1, -0.15), (-0.1, -0.15), - (0.4, -0.15), (-0.4, -0.15), (0.6, -0.15), (-0.6, -0.15) + (0.3, -0.15), (-0.3, -0.15), (0.1, -0.15), (-0.1, -0.15), + (0.4, -0.15), (-0.4, -0.15), (0.6, -0.15), (-0.6, -0.15) ] def make_task_file(self, - hand = 'bimanual', - responses = [3,4], #3 = Key_three, 4 = Key_four - task_dur=30, - trial_dur=3.25, - iti_dur=0.5, - file_name=None, - run_number=None): + hand = 'bimanual', + responses = [3,4], #3 = Key_three, 4 = Key_four + task_dur=30, + trial_dur=3.25, + iti_dur=0.5, + file_name=None, + run_number=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -1818,12 +1977,12 @@ def make_task_file(self, file_name=None, run_number=None, hand='left', - responses=[3, 4]): + responses=[3, 4]): # check how many trials to include n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) n_pleasant = n_trials // 2 - n_unpleasant = n_trials - n_pleasant + n_unpleasant = n_trials - n_pleasant # Randomly sample numbers 1–26 (image name numbers) pleasant_nums = random.sample(range(1, 27), n_pleasant) @@ -1864,3 +2023,5 @@ def make_task_file(self, return trial_info + +#%% From e415c954040b82d85900482982faa0b2adf3b4ab Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:10:46 +0100 Subject: [PATCH 03/19] Update task_table.tsv --- MultiTaskBattery/task_table.tsv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MultiTaskBattery/task_table.tsv b/MultiTaskBattery/task_table.tsv index fa9a2ca5..179837e2 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 pomg pong game affective Affective affective affect 2 choice is the image pleasant or unpleasant +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. From 15184367738ebe95f4f9928fd81eb94296f0c9c3 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:11:00 +0100 Subject: [PATCH 04/19] Added sensmotcontrol stimuli --- stimuli/sensmotcontrol/sensmotcontrol_block1.csv | 11 +++++++++++ stimuli/sensmotcontrol/sensmotcontrol_block2.csv | 11 +++++++++++ stimuli/sensmotcontrol/sensmotcontrol_block3.csv | 11 +++++++++++ 3 files changed, 33 insertions(+) create mode 100644 stimuli/sensmotcontrol/sensmotcontrol_block1.csv create mode 100644 stimuli/sensmotcontrol/sensmotcontrol_block2.csv create mode 100644 stimuli/sensmotcontrol/sensmotcontrol_block3.csv diff --git a/stimuli/sensmotcontrol/sensmotcontrol_block1.csv b/stimuli/sensmotcontrol/sensmotcontrol_block1.csv new file mode 100644 index 00000000..164a8949 --- /dev/null +++ b/stimuli/sensmotcontrol/sensmotcontrol_block1.csv @@ -0,0 +1,11 @@ +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 \ No newline at end of file diff --git a/stimuli/sensmotcontrol/sensmotcontrol_block2.csv b/stimuli/sensmotcontrol/sensmotcontrol_block2.csv new file mode 100644 index 00000000..249ae3b8 --- /dev/null +++ b/stimuli/sensmotcontrol/sensmotcontrol_block2.csv @@ -0,0 +1,11 @@ +color condition corr_resp +red red 2 +red red 2 +red red 0 +red red 2 +white red 2 +red red 2 +white red 2 +red red 0 +red red 2 +red red 2 \ No newline at end of file diff --git a/stimuli/sensmotcontrol/sensmotcontrol_block3.csv b/stimuli/sensmotcontrol/sensmotcontrol_block3.csv new file mode 100644 index 00000000..656432ac --- /dev/null +++ b/stimuli/sensmotcontrol/sensmotcontrol_block3.csv @@ -0,0 +1,11 @@ +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 \ No newline at end of file From 6a18c9eb71d3711e61e12029dbf040db12653ff1 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:43:39 +0100 Subject: [PATCH 05/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index 6f0aab0c..6f9c12c9 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -8,8 +8,6 @@ import pandas as pd import numpy as np import random -from psychopy import prefs -prefs.hardware['audioLib'] = ['sounddevice'] from psychopy import visual, sound, core, event from pyglet.window import key import MultiTaskBattery.utils as ut @@ -21,9 +19,6 @@ import json - - - class Task: """ Task: takes in inputs from run_experiment.py and methods @@ -506,9 +501,8 @@ 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]] - # one tone for both modalities - self.tone = sound.Sound(value=1000, secs=0.050) # 1000 Hz, 50 ms + 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() From e1555998f056a85daf48dd2ddae81881be1ee365 Mon Sep 17 00:00:00 2001 From: SaraMilosevska Date: Fri, 7 Nov 2025 10:26:51 +0000 Subject: [PATCH 06/19] Stimuli changes Small changes to stimuli files --- .idea/MultiTaskBattery_dev.iml | 7 + .idea/inspectionProfiles/Project_Default.xml | 82 ++++++++ .idea/misc.xml | 10 + .idea/vcs.xml | 6 + .idea/workspace.xml | 136 ++++++++++++++ MultiTaskBattery/task_blocks.py | 175 +++++++++++++----- MultiTaskBattery/task_file.py | 24 +-- stimuli/faux_pas/faux_pas.csv | 94 +++++----- stimuli/liking/liking.csv | 54 ------ .../sensmot_control_block1.csv | 26 +++ .../sensmot_control_block2.csv | 26 +++ .../sensmot_control_block3.csv | 26 +++ .../sensmotcontrol/sensmotcontrol_block1.csv | 11 -- .../sensmotcontrol/sensmotcontrol_block2.csv | 11 -- .../sensmotcontrol/sensmotcontrol_block3.csv | 11 -- 15 files changed, 502 insertions(+), 197 deletions(-) create mode 100644 .idea/MultiTaskBattery_dev.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 stimuli/sensmot_control/sensmot_control_block1.csv create mode 100644 stimuli/sensmot_control/sensmot_control_block2.csv create mode 100644 stimuli/sensmot_control/sensmot_control_block3.csv delete mode 100644 stimuli/sensmotcontrol/sensmotcontrol_block1.csv delete mode 100644 stimuli/sensmotcontrol/sensmotcontrol_block2.csv delete mode 100644 stimuli/sensmotcontrol/sensmotcontrol_block3.csv diff --git a/.idea/MultiTaskBattery_dev.iml b/.idea/MultiTaskBattery_dev.iml new file mode 100644 index 00000000..0070e873 --- /dev/null +++ b/.idea/MultiTaskBattery_dev.iml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..000a3649 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,82 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..268ff05f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..c12e6edb --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 0 +} + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "sara__dev", + "last_opened_file_path": "C:/Users/vlatk/OneDrive/Documents/GitHub/MultiTaskBattery_dev/MultiTaskBattery" + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index 82589351..e807505e 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -418,10 +418,11 @@ def display_instructions(self): """ displays the instruction for the task """ - txt = (f'{self.descriptive_name} Task\n\n' - f'Tap along to the tones using the "{self.corr_key[0]}" key.\n\n' - f'Keep tapping at the same pace when the tones stop.') - instr_visual = visual.TextStim(self.window, text=txt, height=self.const.instruction_text_height, color=[-1, -1, -1]) + + 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() @@ -495,7 +496,6 @@ def run_trial(self, trial): class TimePerception(Task): def __init__(self, info, screen, ttl_clock, const, subj_id): super().__init__(info, screen, ttl_clock, const, subj_id) - self.name = 'time_perception' self.feedback_type = 'acc+rt' def init_task(self): @@ -522,16 +522,19 @@ def init_task(self): def display_instructions(self): mod = str(self.trial_info['modality'].iloc[0]).lower() if mod == 'time': - txt = (f"You will hear two pairs of tones.\n\n" - f"Press [{self.corr_key[0]}] if the SECOND interval is shorter.\n" - f"Press [{self.corr_key[1]}] if the SECOND interval is longer.") + 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: - txt = (f"You will hear two pairs of tones.\n\n" - f"Press [{self.corr_key[0]}] if the SECOND pair is quieter.\n" - f"Press [{self.corr_key[1]}] if the SECOND pair is louder.\n" - f"The first pair is always the same.") - visual.TextStim(self.window, text=txt, - height=self.const.instruction_text_height, color=[-1, -1, -1]).draw() + 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): @@ -601,51 +604,96 @@ def run_trial(self, trial): trial['response'], trial['rt'] = self.wait_response(clk.get_time(), float(trial['question_dur'])) trial['correct'] = (trial['response'] == trial['trial_type']) - # --- PEST update (classic): halve on reversal; double after two same-direction moves --- + # --- 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) - # Apply movement to current level, then clamp/snap + # 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']) - st['curr'] = max(160.0, min(392.0, float(int(round(st['curr'] / 8.0) * 8)))) - else: + # 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'] = max(408.0, min(640.0, float(int(round(st['curr'] / 8.0) * 8)))) + 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 (quieter: toward=+; louder: toward=-), clamp to side range + # 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']) - st['curr'] = max(64.9, min(72.73, st['curr'])) - else: + 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']) - st['curr'] = max(73.27, min(81.1, st['curr'])) + 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 - # Reversal check - if st['last_dir'] != 0 and move_dir != st['last_dir']: - # reversal -> halve step (floor at min_step), reset same_dir - st['step'] = max(st['min_step'], st['step'] / 2.0) - st['same_dir'] = 0 else: - # same direction -> count; double after two consecutive moves, then reset the counter - st['same_dir'] += 1 - if st['same_dir'] >= 2: - st['step'] = st['step'] * 2.0 - st['same_dir'] = 0 # require two more same-direction moves for next doubling + # We hit a border and got clamped. Do NOT adapt step. Do NOT change same_dir. Do NOT touch last_dir. + pass - st['last_dir'] = move_dir self.stair[side] = st - - # feedback (practice flag from TSV) 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.name = 'sensmotcontrol' self.feedback_type = 'acc+rt' def init_task(self): @@ -659,15 +707,19 @@ def display_instructions(self): """ cond = str(self.trial_info['condition'].iloc[0]) if cond == 'blue': - self.instruction_text = (f"When the circle turns BLUE, press {self.corr_key[0]}.\n" - f"When the circle turns WHITE, do nothing.\n\n") + 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': - self.instruction_text = (f"When the circle turns RED, press {self.corr_key[1]}.\n" - f"When the circle turns WHITE, do nothing.\n\n") + 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: - self.instruction_text = (f"When the circle turns BLUE, press {self.corr_key[0]}. \n" - f"When the circle turns RED, press {self.corr_key[1]}. \n" - "When the circle turns WHITE, do nothing.\n\n") + 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() @@ -2302,23 +2354,44 @@ 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], + + 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() diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index 071d69fc..4d5c7c8d 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -79,7 +79,7 @@ def make_run_file(task_list, tfiles, offset = 0, instruction_dur = 5, - task_dur = 30, + task_dur = 300, run_time = None, keep_in_middle=None): """ @@ -128,7 +128,7 @@ def __init__(self, const): def make_task_file(self, hand = 'right', responses = [1,2], # 1 = match, 2 = no match - task_dur = 30, + task_dur = 300, trial_dur = 2, iti_dur = 0.5, stim = ['9.jpg','11.jpg','18.jpg','28.jpg'], @@ -699,7 +699,7 @@ def make_task_file(self, grid_size=(3, 4), num_steps=3, num_boxes_lit=2, - task_dur=30, + task_dur=300, trial_dur=7, question_dur=3, sequence_dur=4, @@ -922,7 +922,7 @@ def generate_sequence(self): def make_task_file(self, hand = 'bimanual', responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - task_dur= 10, + task_dur= 300, trial_dur=3.25, iti_dur=0.5, file_name=None): @@ -965,7 +965,7 @@ def make_task_file(self, hand='right', responses=[1], run_number= None, - task_dur = 70, + task_dur = 300, trial_dur=35, # 2 sec trial start text, 27.95 sec tone train, ~5 sec buffer iti_dur=0, file_name=None): @@ -1005,8 +1005,8 @@ def __init__(self, const): def make_task_file(self, modality='time', # 'time' or 'volume' responses=[1, 2], # code 1 = left option, 2 = right option - n_trials=20, # must be even - trial_dur=4.0, # tone + question window duration + n_trials= 30, # must be even + trial_dur=4, # tone + question window duration iti_dur=1.0, question_dur=2.0, display_feedback= True, @@ -1055,13 +1055,13 @@ def make_task_file(self, class SensMotControl(TaskFile): def __init__(self, const): super().__init__(const) - self.name = 'sensmotcontrol' + self.name = 'sensmot_control' def make_task_file(self, hand='right', responses=[1, 2], run_number=None, - task_dur= 40, + task_dur= 300, trial_dur=3, question_dur=2, iti_dur= 1, @@ -1263,7 +1263,7 @@ def __init__(self, const): def make_task_file(self, hand='right', responses = [1,2,3,4], run_number=None, - task_dur=30, + task_dur=300, trial_dur=6, iti_dur=1.5, file_name=None, @@ -1681,7 +1681,7 @@ def __init__(self, const): def make_task_file(self, hand='right', responses = [1,2], # 1 = True, 2 = False run_number=None, - task_dur=30, + task_dur=300, trial_dur=14, iti_dur=1, story_dur=10, @@ -1864,7 +1864,7 @@ def make_task_file(self, hand='right', responses = [1,2], run_number = None, - task_dur=30, + task_dur=300, trial_dur=28, iti_dur=1, question_dur=3, 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 diff --git a/stimuli/sensmotcontrol/sensmotcontrol_block1.csv b/stimuli/sensmotcontrol/sensmotcontrol_block1.csv deleted file mode 100644 index 164a8949..00000000 --- a/stimuli/sensmotcontrol/sensmotcontrol_block1.csv +++ /dev/null @@ -1,11 +0,0 @@ -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 \ No newline at end of file diff --git a/stimuli/sensmotcontrol/sensmotcontrol_block2.csv b/stimuli/sensmotcontrol/sensmotcontrol_block2.csv deleted file mode 100644 index 249ae3b8..00000000 --- a/stimuli/sensmotcontrol/sensmotcontrol_block2.csv +++ /dev/null @@ -1,11 +0,0 @@ -color condition corr_resp -red red 2 -red red 2 -red red 0 -red red 2 -white red 2 -red red 2 -white red 2 -red red 0 -red red 2 -red red 2 \ No newline at end of file diff --git a/stimuli/sensmotcontrol/sensmotcontrol_block3.csv b/stimuli/sensmotcontrol/sensmotcontrol_block3.csv deleted file mode 100644 index 656432ac..00000000 --- a/stimuli/sensmotcontrol/sensmotcontrol_block3.csv +++ /dev/null @@ -1,11 +0,0 @@ -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 \ No newline at end of file From 299300b8efdadceb8c78c968fafdca7dd5880661 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:46:23 +0000 Subject: [PATCH 07/19] Removed .idea folder --- .idea/MultiTaskBattery_dev.iml | 7 - .idea/inspectionProfiles/Project_Default.xml | 82 ----------- .idea/misc.xml | 10 -- .idea/vcs.xml | 6 - .idea/workspace.xml | 136 ------------------- 5 files changed, 241 deletions(-) delete mode 100644 .idea/MultiTaskBattery_dev.iml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/MultiTaskBattery_dev.iml b/.idea/MultiTaskBattery_dev.iml deleted file mode 100644 index 0070e873..00000000 --- a/.idea/MultiTaskBattery_dev.iml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 000a3649..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 268ff05f..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index c12e6edb..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "associatedIndex": 0 -} - - - - { - "keyToString": { - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "git-widget-placeholder": "sara__dev", - "last_opened_file_path": "C:/Users/vlatk/OneDrive/Documents/GitHub/MultiTaskBattery_dev/MultiTaskBattery" - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 95b257d0ea43179a698e2e18a5c87c3b65fb0e51 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:53:10 +0000 Subject: [PATCH 08/19] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) 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/ + From de510076b244404c375aef51a78c208640f51ee3 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:58:15 +0000 Subject: [PATCH 09/19] Update task_file.py --- MultiTaskBattery/task_file.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index 4d5c7c8d..94d1e325 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -79,7 +79,7 @@ def make_run_file(task_list, tfiles, offset = 0, instruction_dur = 5, - task_dur = 300, + task_dur = 30, run_time = None, keep_in_middle=None): """ @@ -128,7 +128,7 @@ def __init__(self, const): def make_task_file(self, hand = 'right', responses = [1,2], # 1 = match, 2 = no match - task_dur = 300, + task_dur = 30, trial_dur = 2, iti_dur = 0.5, stim = ['9.jpg','11.jpg','18.jpg','28.jpg'], @@ -184,7 +184,7 @@ def __init__(self, const): self.name = 'rest' def make_task_file(self, - task_dur = 30, + task_dur = 30, file_name = None): trial = {} trial['trial_num'] = [1] @@ -203,7 +203,7 @@ def __init__(self, const): def make_task_file(self, - task_dur = 30, + task_dur = 30, trial_dur = 2, iti_dur = 0.5, file_name = None, @@ -254,7 +254,7 @@ def __init__(self, const): self.name = 'tongue_movement' def make_task_file(self, - task_dur = 30, + task_dur = 30, trial_dur = 1, iti_dur = 0, file_name = None): @@ -965,7 +965,7 @@ def make_task_file(self, hand='right', responses=[1], run_number= None, - task_dur = 300, + task_dur = 30, trial_dur=35, # 2 sec trial start text, 27.95 sec tone train, ~5 sec buffer iti_dur=0, file_name=None): @@ -1120,7 +1120,7 @@ def __init__(self, const): self.name = 'flexion_extension' def make_task_file(self, - task_dur = 30, + task_dur = 30, trial_dur = 30, iti_dur = 0, stim_dur = 2, @@ -1213,7 +1213,7 @@ def __init__(self, const): def make_task_file(self, hand = 'right', #to recode for alternating hands: put left here, and put 3,4 in responses responses = [1,2], # 1 = match, 2 = no match - task_dur = 30, + task_dur = 30, trial_dur = 2, iti_dur = 0.5, easy_prob=0.5, From c135772235abd9023473172868f00b3b3a23563c Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:59:21 +0000 Subject: [PATCH 10/19] Update task_file.py --- MultiTaskBattery/task_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index 94d1e325..3316a7f1 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -965,7 +965,7 @@ def make_task_file(self, hand='right', responses=[1], run_number= None, - task_dur = 30, + 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): From 689afde72c51607cb04561b3f63b60b4b94564b4 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Sun, 25 Jan 2026 21:01:22 +0100 Subject: [PATCH 11/19] Update task_file.py --- MultiTaskBattery/task_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index cd4e0d3c..8acbbe3d 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -921,7 +921,7 @@ def generate_sequence(self): def make_task_file(self, hand = 'bimanual', responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - task_dur= 300, + task_dur=300, trial_dur=3.25, iti_dur=0.5, file_name=None): @@ -1060,7 +1060,7 @@ def make_task_file(self, hand='right', responses=[1, 2], run_number=None, - task_dur= 300, + task_dur=300, trial_dur=3, question_dur=2, iti_dur= 1, From 6e7ee458a6c71294c29a3e391e4d8a2c49deb1ae Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Sun, 25 Jan 2026 21:07:43 +0100 Subject: [PATCH 12/19] Rebased task file to og version --- MultiTaskBattery/task_file.py | 563 ++++++++++++---------------------- 1 file changed, 201 insertions(+), 362 deletions(-) diff --git a/MultiTaskBattery/task_file.py b/MultiTaskBattery/task_file.py index 8acbbe3d..5e0fca88 100644 --- a/MultiTaskBattery/task_file.py +++ b/MultiTaskBattery/task_file.py @@ -1,4 +1,3 @@ -#%% # Create TaskFile file for different tasks # March 2021: First version: Ladan Shahshahani - Maedbh King - Suzanne Witt, # Revised 2023: Bassel Arafat, Jorn Diedrichsen, Ince Husain @@ -25,7 +24,7 @@ def shuffle_rows(dataframe, keep_in_middle=None): if keep_in_middle is not None: dataframe = move_edge_tasks_to_middle(dataframe, keep_in_middle) - + return dataframe def move_edge_tasks_to_middle(dataframe, keep_in_middle): @@ -126,13 +125,13 @@ def __init__(self, const): self.name = 'n_back' def make_task_file(self, - hand = 'right', - responses = [1,2], # 1 = match, 2 = no match - task_dur = 30, - trial_dur = 2, - iti_dur = 0.5, - stim = ['9.jpg','11.jpg','18.jpg','28.jpg'], - file_name = None ): + hand = 'right', + responses = [1,2], # 1 = match, 2 = no match + task_dur = 30, + trial_dur = 2, + iti_dur = 0.5, + stim = ['9.jpg','11.jpg','18.jpg','28.jpg'], + file_name = None ): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -148,7 +147,7 @@ def make_task_file(self, trial['key_match'] = responses[0] trial['key_nomatch'] = responses[1] # Determine if this should be N-2 repetition trial - + if n<2: trial['trial_type'] = 0 else: @@ -183,8 +182,8 @@ def __init__(self, const): self.name = 'rest' def make_task_file(self, - task_dur = 30, - file_name = None): + task_dur = 30, + file_name = None): trial = {} trial['trial_num'] = [1] trial['trial_dur'] = [task_dur] @@ -199,14 +198,14 @@ class VerbGeneration(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'verb_generation' - + def make_task_file(self, - task_dur = 30, - trial_dur = 2, - iti_dur = 0.5, - file_name = None, - stim_file = None): + task_dur = 30, + trial_dur = 2, + iti_dur = 0.5, + file_name = None, + stim_file = None): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -214,7 +213,7 @@ def make_task_file(self, stim = pd.read_csv(stim_file) else: stim = pd.read_csv(self.stim_dir / 'verb_generation' / 'verb_generation.csv') - + stim = stim.sample(frac=1).reset_index(drop=True) t = 0 @@ -253,10 +252,10 @@ def __init__(self, const): self.name = 'tongue_movement' def make_task_file(self, - task_dur = 30, - trial_dur = 1, - iti_dur = 0, - file_name = None): + task_dur = 30, + trial_dur = 1, + iti_dur = 0, + file_name = None): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -323,9 +322,9 @@ def __init__(self, const): super().__init__(const) self.name = 'spatial_navigation' self.location_pairs = [('FRONT-DOOR', 'LIVING-ROOM'), ('WASHROOM', 'LIVING-ROOM'), ('BEDROOM', 'LIVING-ROOM'), - ('KITCHEN', 'LIVING-ROOM'), ('FRONT-DOOR', 'WASHROOM'), ('BEDROOM', 'FRONT-DOOR'), - ('KITCHEN', 'FRONT-DOOR'), ('KITCHEN', 'BEDROOM'), ('KITCHEN', 'WASHROOM'), - ('BEDROOM', 'WASHROOM')] + ('KITCHEN', 'LIVING-ROOM'), ('FRONT-DOOR', 'WASHROOM'), ('BEDROOM', 'FRONT-DOOR'), + ('KITCHEN', 'FRONT-DOOR'), ('KITCHEN', 'BEDROOM'), ('KITCHEN', 'WASHROOM'), + ('BEDROOM', 'WASHROOM')] def make_task_file(self, @@ -369,14 +368,14 @@ def __init__(self, const): def make_task_file(self, hand='right', responses = [1,2], # 1 = True, 2 = False run_number=None, - task_dur=300, - trial_dur=14, - iti_dur=1, - story_dur=10, - question_dur=4, - file_name=None, - stim_file=None, - condition=None): + task_dur=30, + trial_dur=14, + iti_dur=1, + story_dur=10, + question_dur=4, + file_name=None, + stim_file=None, + condition=None): # count number of trials n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) @@ -392,7 +391,7 @@ def make_task_file(self, hand='right', 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) @@ -434,11 +433,11 @@ def __init__(self, const): self.name = 'degraded_passage' def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=14.5, - iti_dur=0.5, - file_name=None): + run_number = None, + task_dur=30, + trial_dur=14.5, + iti_dur=0.5, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -472,11 +471,11 @@ def __init__(self, const): self.name = 'intact_passage' def make_task_file(self, - run_number, - task_dur=30, - trial_dur=14.5, - iti_dur=0.5, - file_name=None): + run_number, + task_dur=30, + trial_dur=14.5, + iti_dur=0.5, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -512,27 +511,27 @@ def __init__(self, const): # Medium/bad knot vids # self.knot_names = [ - # 'Ampere', 'Arbor', 'Baron', 'Belfry', 'Bramble', 'Chamois', 'Coffer', - # 'Farthing', 'Fissure', 'Gentry', 'Henchman', 'Magnate', 'Perry', 'Phial', 'Polka', + # 'Ampere', 'Arbor', 'Baron', 'Belfry', 'Bramble', 'Chamois', 'Coffer', + # 'Farthing', 'Fissure', 'Gentry', 'Henchman', 'Magnate', 'Perry', 'Phial', 'Polka', # 'Rosin', 'Shilling', 'Simper', 'Spangle', 'Squire', 'Vestment', 'Wampum', 'Wicket' # ] # good knot vids - # self.knot_names = ['Adage', + # self.knot_names = ['Adage', # 'Brigand', 'Brocade', 'Casement', 'Cornice',\ # 'Flora', 'Frontage', 'Gadfly', 'Garret', \ # 'Mutton','Placard', 'Purser'] def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=14, - iti_dur=1, - file_name=None, - knot_names = ['Adage', - 'Brigand', 'Brocade', 'Casement', 'Cornice', \ - 'Flora', 'Frontage', 'Gadfly', 'Garret', \ - 'Mutton','Placard', 'Purser']): + run_number = None, + task_dur=30, + trial_dur=14, + iti_dur=1, + file_name=None, + knot_names = ['Adage', + 'Brigand', 'Brocade', 'Casement', 'Cornice',\ + 'Flora', 'Frontage', 'Gadfly', 'Garret', \ + 'Mutton','Placard', 'Purser']): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -673,7 +672,7 @@ def modify_sequence(self, sequence, grid_size): if not available_positions: raise ValueError("Not enough valid adjacent positions available for modification.") - + # Choose a random position from the available ones new_pos = random.choice(list(available_positions)) new_step.append(new_pos) @@ -691,19 +690,19 @@ def modify_sequence(self, sequence, grid_size): # If all steps fail, raise an error raise ValueError("No valid step could be modified with the given constraints.") - + def make_task_file(self, - hand='right', - responses=[1, 2], # 1 = Left, 2 = Right - grid_size=(3, 4), - num_steps=3, - num_boxes_lit=2, - task_dur=300, - trial_dur=7, - question_dur=3, - sequence_dur=4, - iti_dur=0.5, - file_name=None): + hand='right', + responses=[1, 2], # 1 = Left, 2 = Right + grid_size=(3, 4), + num_steps=3, + num_boxes_lit=2, + task_dur=30, + trial_dur=7, + question_dur=3, + sequence_dur=4, + iti_dur=0.5, + file_name=None): """ Create a task file with the specified parameters. @@ -732,14 +731,14 @@ def make_task_file(self, try: # Generate the original sequence original_sequence = self.generate_sequence(grid_size, num_steps, num_boxes_lit) - # Attempt to create a modified sequence + # Attempt to create a modified sequence modified_sequence = self.modify_sequence(original_sequence, grid_size=grid_size) break except ValueError: continue correct_side = random.choice(['left', 'right']) - trial_type = 0 if correct_side == 'left' else 1 + trial_type = 0 if correct_side == 'left' else 1 trial = { 'key_left': responses[0], 'key_right': responses[1], @@ -777,12 +776,12 @@ def __init__(self, const): self.name = 'sentence_reading' def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=5.8, - iti_dur=0.2, - file_name=None, - stim_file=None): + run_number = None, + task_dur=30, + trial_dur=5.8, + iti_dur=0.2, + file_name=None, + stim_file=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -820,16 +819,16 @@ def __init__(self, const): self.name = 'nonword_reading' def make_task_file(self, - run_number = None, - task_dur=30, - trial_dur=5.8, - iti_dur=0.2, - file_name=None, - stim_file=None): - + run_number = None, + task_dur=30, + trial_dur=5.8, + iti_dur=0.2, + file_name=None, + stim_file=None): + n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] - + if stim_file: stim = pd.read_csv(stim_file) else: @@ -862,15 +861,15 @@ class OddBall(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'oddball' - + def make_task_file(self, - hand = 'right', - responses = [1,2], # 1,2 any press is ok - task_dur=30, - trial_dur=0.15, - iti_dur=0.85, - file_name=None): + hand = 'right', + responses = [1,2], # 1,2 any press is ok + task_dur=30, + trial_dur=0.15, + iti_dur=0.85, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -904,13 +903,13 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class FingerSequence(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'finger_sequence' self.matching_stimuli = False # sequence of numbers are different for easy and hard sequence condition - + def generate_sequence(self): sequence = [random.choice([1, 2, 3, 4])] while len(sequence) < 6: @@ -919,12 +918,12 @@ def generate_sequence(self): return ' '.join(map(str, sequence)) def make_task_file(self, - hand = 'bimanual', - responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - task_dur=300, - trial_dur=3.25, - iti_dur=0.5, - file_name=None): + hand = 'bimanual', + responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four + task_dur=30, + trial_dur=3.25, + iti_dur=0.5, + file_name=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -955,175 +954,17 @@ 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 - - class FlexionExtension(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'flexion_extension' def make_task_file(self, - task_dur = 30, - trial_dur = 30, - iti_dur = 0, - stim_dur = 2, - file_name = None): + task_dur = 30, + trial_dur = 30, + iti_dur = 0, + stim_dur = 2, + file_name = None): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] @@ -1158,11 +999,11 @@ def __init__(self, const): def make_task_file(self, hand='right', responses = [1,2], # 1 = True, 2 = False run_number=None, - task_dur=300, - trial_dur=15, - sentence_dur=2, - file_name=None, - stim_file=None): + task_dur=30, + trial_dur=15, + sentence_dur=2, + file_name=None, + stim_file=None): # count number of trials n_trials = int(np.floor(task_dur / (trial_dur))) @@ -1178,7 +1019,7 @@ def make_task_file(self, hand='right', 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): + for n in range(n_trials): trial = {} trial['key_true'] = responses[0] trial['key_false'] = responses[1] @@ -1187,12 +1028,12 @@ def make_task_file(self, hand='right', trial['trial_dur'] = trial_dur trial['sentence_dur'] = sentence_dur trial['sentence'] = stim['sentence'][n] - trial['trial_type'] = random.choice([0,1]) + trial['trial_type'] = random.choice([0,1]) last_word = [stim['wrong_word'][n], stim['right_word'][n]] trial['last_word'] = last_word[trial['trial_type']] trial['display_trial_feedback'] = True trial['start_time'] = t - trial['end_time'] = t + trial_dur + trial['end_time'] = t + trial_dur trial_info.append(trial) @@ -1210,16 +1051,16 @@ def __init__(self, const): self.name = 'visual_search' def make_task_file(self, - hand = 'right', #to recode for alternating hands: put left here, and put 3,4 in responses - responses = [1,2], # 1 = match, 2 = no match - task_dur = 30, - trial_dur = 2, - iti_dur = 0.5, - easy_prob=0.5, - file_name = None ): + hand = 'right', #to recode for alternating hands: put left here, and put 3,4 in responses + responses = [1,2], # 1 = match, 2 = no match + task_dur = 30, + trial_dur = 2, + iti_dur = 0.5, + easy_prob=0.5, + file_name = None ): n_trials = int(np.floor(task_dur / (trial_dur+iti_dur))) trial_info = [] - t = 0 + t = 0 for n in range(n_trials): trial = {} @@ -1229,15 +1070,15 @@ def make_task_file(self, trial['hand'] = hand trial['trial_dur'] = trial_dur trial['iti_dur'] = iti_dur - trial['display_trial_feedback'] = True - trial['trial_type'] = random.choice([0,1]) + trial['display_trial_feedback'] = True + trial['trial_type'] = random.choice([0,1]) trial['num_stimuli'] = '4' if random.random() < easy_prob else '8' # Randomly select difficulty trial['display_trial_feedback'] = True trial['feedback_type'] = 'acc' trial['start_time'] = t trial['end_time'] = t + trial_dur + iti_dur - # Determine the number of stimuli to display based on trial difficulty + # Determine the number of stimuli to display based on trial difficulty num_stimuli = 4 if trial['num_stimuli'] == '4' else 8 trial_info.append(trial) @@ -1260,16 +1101,16 @@ def __init__(self, const): self.repeat_stimuli_from_previous_runs = True def make_task_file(self, hand='right', - responses = [1,2,3,4], - run_number=None, - task_dur=300, - trial_dur=6, - iti_dur=1.5, - file_name=None, - stim_file = None, - condition=None, - half=None): - + responses = [1,2,3,4], + run_number=None, + task_dur=30, + trial_dur=6, + iti_dur=1.5, + file_name=None, + stim_file = None, + condition=None, + half=None): + # count number of trials n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) @@ -1293,9 +1134,9 @@ def make_task_file(self, hand='right', stim_age = stim[stim['condition'] == 'age'] # Split each condition into halves first_half = zip(stim_emotion.iloc[:len(stim_emotion) // 2].iterrows(), - stim_age.iloc[len(stim_age) // 2:].iterrows()) + stim_age.iloc[len(stim_age) // 2:].iterrows()) second_half = zip(stim_emotion.iloc[len(stim_emotion) // 2:].iterrows(), - stim_age.iloc[:len(stim_age) // 2].iterrows()) + stim_age.iloc[:len(stim_age) // 2].iterrows()) stim = pd.concat([pd.concat([row1[1], row2[1]], axis=1).T for row1, row2 in itertools.chain(first_half, second_half)], ignore_index=True) if half: # Selects different stimuli for the social and control condition, to enable showing each story only once for each participant @@ -1310,9 +1151,9 @@ def make_task_file(self, hand='right', stim = stim[stim['half'] != half].iloc[new_start_row:new_end_row + 1].reset_index(drop=True) else: raise ValueError('Not enough stimuli for the run') - else: + else: stim = stim.iloc[start_row:end_row + 1].reset_index(drop=True) - + for n in range(n_trials): trial = {} trial['key_one'] = responses[0] @@ -1346,21 +1187,21 @@ def __init__(self, const): super().__init__(const) self.name = 'picture_sequence' self.matching_stimuli = False # sequence of pictures are different for different conditions - + def generate_sequence(self): sequence = random.sample([1, 2, 3, 4], 4) return ' '.join(map(str, sequence)) def make_task_file(self, - hand = 'right', - responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - run_number=None, - task_dur=30, - trial_dur=14, - iti_dur=1, - file_name=None, - stim_file = None, - condition=None): + hand = 'right', + responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four + run_number=None, + task_dur=30, + trial_dur=14, + iti_dur=1, + file_name=None, + stim_file = None, + condition=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] t = 0 @@ -1412,21 +1253,21 @@ def __init__(self, const): super().__init__(const) self.name = 'story_sequence' self.matching_stimuli = False # sequence of sentences are different for different conditions - + def generate_sequence(self): sequence = random.sample([1, 2, 3, 4], 4) return ' '.join(map(str, sequence)) def make_task_file(self, - hand = 'right', - responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four - run_number=None, - task_dur=30, - trial_dur=14, - iti_dur=1, - file_name=None, - stim_file = None, - condition=None): + hand = 'right', + responses = [1,2,3,4], # 1 = Key_one, 2 = Key_two, 3 = Key_three, 4 = Key_four + run_number=None, + task_dur=30, + trial_dur=14, + iti_dur=1, + file_name=None, + stim_file = None, + condition=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] t = 0 @@ -1483,16 +1324,16 @@ def __init__(self, const): self.matching_stimuli = False # sequence of pictures are different for different conditions def make_task_file(self, hand='right', - responses = [1,2], - run_number=None, - task_dur=300, - trial_dur=5, - iti_dur=1, - question_dur=4, - file_name=None, - stim_file = None, - condition=None): - + responses = [1,2], + run_number=None, + task_dur=30, + trial_dur=5, + iti_dur=1, + question_dur=4, + file_name=None, + stim_file = None, + condition=None): + # count number of trials n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) @@ -1597,7 +1438,7 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class StrangeStories(TaskFile): def __init__(self, const): @@ -1667,7 +1508,7 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class FauxPas(TaskFile): def __init__(self, const): @@ -1678,17 +1519,17 @@ def __init__(self, const): self.repeat_stimuli_from_previous_runs = True def make_task_file(self, hand='right', - responses = [1,2], # 1 = True, 2 = False - run_number=None, - task_dur=300, - trial_dur=14, - iti_dur=1, - story_dur=10, - question1_dur=4, - file_name=None, - stim_file=None, - condition=None, - half=None): + responses = [1,2], # 1 = True, 2 = False + run_number=None, + task_dur=30, + trial_dur=14, + iti_dur=1, + story_dur=10, + question1_dur=4, + file_name=None, + stim_file=None, + condition=None, + half=None): # count number of trials @@ -1708,7 +1549,7 @@ def make_task_file(self, hand='right', stim = stim[stim['condition'] == condition] else: stim = stim.loc[~stim['condition'].str.contains('practice', na=False)] - + if half: # Selects different stimuli for the social and control condition, to enable showing each story only once for each participant stim_half = stim[stim['half'] == half] if n_trials <= stim_half.iloc[start_row:end_row + 1].shape[0]: # Check if there are enough stimuli for the run @@ -1721,9 +1562,9 @@ def make_task_file(self, hand='right', stim = stim[stim['half'] != half].iloc[new_start_row:new_end_row + 1].reset_index(drop=True) else: raise ValueError('Not enough stimuli for the run') - else: + else: stim = stim.iloc[start_row:end_row + 1].reset_index(drop=True) - + for n in range(n_trials): trial = {} trial['key_yes'] = responses[0] @@ -1754,7 +1595,7 @@ def make_task_file(self, hand='right', if file_name is not None: trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class FrithHappe(TaskFile): @@ -1824,7 +1665,7 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class Liking(TaskFile): @@ -1832,7 +1673,7 @@ def __init__(self, const): super().__init__(const) self.name = 'liking' self.matching_stimuli = False - + def map_to_4point_scale(self, rating): """ Map the liking rating from a 1-to-5 scale to the closest value on a 4-point scale @@ -1858,12 +1699,12 @@ def map_to_4point_scale(self, rating): # Round to the nearest integer return round(mapped_value) - + def make_task_file(self, hand='right', responses = [1,2], run_number = None, - task_dur=300, + task_dur=30, trial_dur=28, iti_dur=1, question_dur=3, @@ -1917,24 +1758,24 @@ def make_task_file(self, trial_info.to_csv(self.task_dir / self.name / file_name, sep='\t', index=False) return trial_info - + class Pong(TaskFile): def __init__(self, const): super().__init__(const) self.name = 'pong' self.trajectories = [ - (0.3, -0.15), (-0.3, -0.15), (0.1, -0.15), (-0.1, -0.15), - (0.4, -0.15), (-0.4, -0.15), (0.6, -0.15), (-0.6, -0.15) + (0.3, -0.15), (-0.3, -0.15), (0.1, -0.15), (-0.1, -0.15), + (0.4, -0.15), (-0.4, -0.15), (0.6, -0.15), (-0.6, -0.15) ] def make_task_file(self, - hand = 'bimanual', - responses = [3,4], #3 = Key_three, 4 = Key_four - task_dur=30, - trial_dur=3.25, - iti_dur=0.5, - file_name=None, - run_number=None): + hand = 'bimanual', + responses = [3,4], #3 = Key_three, 4 = Key_four + task_dur=30, + trial_dur=3.25, + iti_dur=0.5, + file_name=None, + run_number=None): n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) trial_info = [] @@ -1976,12 +1817,12 @@ def make_task_file(self, file_name=None, run_number=None, hand='left', - responses=[3, 4]): + responses=[3, 4]): # check how many trials to include n_trials = int(np.floor(task_dur / (trial_dur + iti_dur))) n_pleasant = n_trials // 2 - n_unpleasant = n_trials - n_pleasant + n_unpleasant = n_trials - n_pleasant # Randomly sample numbers 1–26 (image name numbers) pleasant_nums = random.sample(range(1, 27), n_pleasant) @@ -2020,5 +1861,3 @@ def make_task_file(self, return trial_info - -#%% From b44409c509261ff6187202b500b11dc84a988bd6 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Sun, 25 Jan 2026 21:08:19 +0100 Subject: [PATCH 13/19] Added new tasks back in --- MultiTaskBattery/task_file.py | 157 ++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) 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 From c471e17019eaa5c4d38473b7e7d6cba8ccb5c3ee Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Sun, 25 Jan 2026 21:14:23 +0100 Subject: [PATCH 14/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 43 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index b573169a..4e3e99ec 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -1,4 +1,3 @@ -#%% # Task Class definitions # March 2021: First version: Ladan Shahshahani - Maedbh King - Suzanne Witt, # Revised 2023: Bassel Arafat, Jorn Diedrichsen, Incé Husain @@ -8,6 +7,8 @@ import pandas as pd import numpy as np import random +from psychopy import prefs +prefs.hardware['audioLib'] = ['sounddevice'] from psychopy import visual, sound, core, event from pyglet.window import key import MultiTaskBattery.utils as ut @@ -19,6 +20,7 @@ import json + class Task: """ Task: takes in inputs from run_experiment.py and methods @@ -69,7 +71,7 @@ def display_instructions(self): # instr.size = 0.8 instr_visual.draw() self.window.flip() - + def run(self): """Loop over trials and collects data Data will be stored in self.trial_data @@ -96,37 +98,37 @@ def run(self): if self.feedback_type[-2:] == 'rt': rt = self.trial_data['rt'].mean() return acc,rt - + def show_progress(self, seconds_left, show_last_seconds=5, height=1, width=10, x_pos=-5, y_pos=8): """ Displays a progress bar for the Picture Sequence task Args: - seconds_left (float): - The number of seconds remaining in the current trial. + seconds_left (float): + The number of seconds remaining in the current trial. If this value is greater than `show_last_seconds`, the progress bar is not shown. - show_last_seconds (float, optional): - The duration (in seconds) over which to display the progress bar at the end of a trial. + show_last_seconds (float, optional): + The duration (in seconds) over which to display the progress bar at the end of a trial. Default is 5 seconds. When `seconds_left` is less than this value, the progress bar appears. - height (float, optional): + height (float, optional): The vertical size of the progress bar in PsychoPy window units. Default is 1. - width (float, optional): + width (float, optional): The horizontal size of the progress bar in PsychoPy window units. Default is 10. - x_pos (float, optional): - The horizontal position of the center of the progress bar in window coordinates. + x_pos (float, optional): + The horizontal position of the center of the progress bar in window coordinates. Negative values move it leftward. Default is -5. - y_pos (float, optional): - The vertical position of the center of the progress bar in window coordinates. + y_pos (float, optional): + The vertical position of the center of the progress bar in window coordinates. Positive values move it upward. Default is 8. """ # If we are in the last five seconds of the trial, display the remaining time if seconds_left < show_last_seconds: progress = visual.Progress( - win=self.window, + win=self.window, progress=1-(seconds_left/show_last_seconds), size=(width, height), pos=(x_pos, y_pos), @@ -157,8 +159,8 @@ def wait_response(self, start_time, max_wait_time, show_last_seconds=None, curre current_stimuli.draw() seconds_left = max_wait_time - (self.ttl_clock.get_time() - start_time) self.show_progress(seconds_left, - show_last_seconds=show_last_seconds, - y_pos=6) + show_last_seconds=show_last_seconds, + y_pos=6) self.window.flip() keys=event.getKeys(keyList= self.const.response_keys, timeStamped=self.ttl_clock.clock) if len(keys)>0: @@ -775,9 +777,9 @@ def display_instructions(self): end_location = self.trial_info.iloc[0]['location_2'] self.instruction_text = (f"{self.descriptive_name} Task \n\n" - f"Imagine walking around your childhood home\n" - f"Start in the {start_location} – end in the {end_location}\n" - f"Focus on the fixation cross") + f"Imagine walking around your childhood home\n" + f"Start in the {start_location} – end in the {end_location}\n" + f"Focus on the fixation cross") instr_visual = visual.TextStim(self.window, text=self.instruction_text, height=self.const.instruction_text_height, color=[-1, -1, -1], wrapWidth=20) instr_visual.draw() self.window.flip() @@ -2670,6 +2672,3 @@ def run_trial(self, trial): self.display_trial_feedback(trial['display_trial_feedback'], trial['correct']) return trial - - -#%% From 5927ae74b7dd1b877092463fad38663369250f5e Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:38:50 +0100 Subject: [PATCH 15/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index 4e3e99ec..b876274b 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -1442,6 +1442,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() From 5cbd6fc1ede6b61703fccdf3fb26603ec48dd298 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:56:29 +0100 Subject: [PATCH 16/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index b876274b..b62aaf6a 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -445,7 +445,7 @@ def run_trial(self, trial): clk = self.ttl_clock.clock t0 = clk.getTime() # trial anchor (TTL) - beep = sound.Sound(1000, secs=0.05) + beep = sound.Sound(value=1000, secs=0.05, sampleRate=48000, stereo=True) # --- Play FIRST tone now, then use THIS time as the grid anchor beep.play() From ab87f0cdd74022b81a2ffaa2bdb344bd27d10857 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:02:12 +0100 Subject: [PATCH 17/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index b62aaf6a..0cc81d17 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -445,9 +445,8 @@ def run_trial(self, trial): clk = self.ttl_clock.clock t0 = clk.getTime() # trial anchor (TTL) - beep = sound.Sound(value=1000, secs=0.05, sampleRate=48000, stereo=True) - # --- 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 @@ -461,6 +460,8 @@ def run_trial(self, trial): 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() break res = event.waitKeys(maxWait=deadline - now, From b9a40e1a0a10205c91a7b3309572cc3da1988496 Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:50:38 +0100 Subject: [PATCH 18/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index 0cc81d17..de91de06 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -452,6 +452,9 @@ def run_trial(self, trial): 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 @@ -463,6 +466,8 @@ def run_trial(self, trial): # 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, From 4c8674ffec0251ab32a84f922c69fad1e7c85faf Mon Sep 17 00:00:00 2001 From: Caro Nettekoven <30801389+carobellum@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:53:24 +0100 Subject: [PATCH 19/19] Update task_blocks.py --- MultiTaskBattery/task_blocks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MultiTaskBattery/task_blocks.py b/MultiTaskBattery/task_blocks.py index de91de06..3ebe37a4 100644 --- a/MultiTaskBattery/task_blocks.py +++ b/MultiTaskBattery/task_blocks.py @@ -500,6 +500,9 @@ def run_trial(self, trial): 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