From aa9933a6d9824b3c5aecbf1bf5ac8efe40f04fcd Mon Sep 17 00:00:00 2001 From: Yann Harel Date: Thu, 18 Feb 2021 14:55:24 -0500 Subject: [PATCH 01/10] meg --- src/sessions/ses-shinobi3levels.py | 4 ++-- src/shared/config.py | 6 +++--- src/tasks/videogame.py | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sessions/ses-shinobi3levels.py b/src/sessions/ses-shinobi3levels.py index c31acd64..8c80ad9e 100644 --- a/src/sessions/ses-shinobi3levels.py +++ b/src/sessions/ses-shinobi3levels.py @@ -23,10 +23,10 @@ ] levels_scenario = [ - ("Level1-0", "scenario_repeat1"), + ("Level1-0", "scenario_Level1"), ("Level4-1", "scenario_Level4-1"), ("Level5-0", "scenario_Level5-0")] -random.shuffle(levels_scenario) # randomize order +#random.shuffle(levels_scenario) # randomize order TASKS = sum( [ diff --git a/src/shared/config.py b/src/shared/config.py index d6326c08..e277f886 100644 --- a/src/shared/config.py +++ b/src/shared/config.py @@ -12,7 +12,7 @@ EYETRACKING_ROI = (60, 30, 660, 450) -EXP_SCREEN_XRANDR_NAME = "eDP-1" +EXP_SCREEN_XRANDR_NAME = "DVI-D-0" EXP_MONITOR = Monitor( name='__blank__', @@ -21,9 +21,9 @@ ) EXP_WINDOW = dict( - size=(1280, 1024), + size=(1920, 1080), screen=1, - fullscr=True, + #fullscr=True, gammaErrorPolicy="warn", #waitBlanking=False, ) diff --git a/src/tasks/videogame.py b/src/tasks/videogame.py index da3047d5..f18f993f 100644 --- a/src/tasks/videogame.py +++ b/src/tasks/videogame.py @@ -246,6 +246,7 @@ def _run_emulator(self, exp_win, ctl_win): if not level_step % config.FRAME_RATE: exp_win.logOnFlip(level=logging.EXP, msg="level step: %d" % level_step) yield True + self.game_sound.stop() self.game_sound.flush() From 8c050522effeb2ce3a9bf07e743f19f034fa0010 Mon Sep 17 00:00:00 2001 From: basile Date: Fri, 9 Apr 2021 09:26:32 -0400 Subject: [PATCH 02/10] add option to send MEG markers at each screen flip --- src/shared/cli.py | 9 ++++++--- src/shared/meg.py | 8 +++++++- src/tasks/task_base.py | 3 +++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/shared/cli.py b/src/shared/cli.py index d4100eda..e1110593 100644 --- a/src/shared/cli.py +++ b/src/shared/cli.py @@ -26,7 +26,7 @@ def listen_shortcuts(): return False -def run_task_loop(loop, eyetracker=None, gaze_drawer=None, record_movie=False): +def run_task_loop(task, loop, eyetracker=None, gaze_drawer=None, record_movie=False): for frameN, _ in enumerate(loop): if gaze_drawer: gaze = eyetracker.get_gaze() @@ -47,6 +47,7 @@ def run_task( # show instruction shortcut_evt = run_task_loop( + task, task.instructions(exp_win, ctl_win), eyetracker, gaze_drawer, @@ -64,10 +65,11 @@ def run_task( eyetracker.start_recording(task.name) # send start trigger/marker to MEG + Biopac (or anything else on parallel port) if task.use_meg and not shortcut_evt: - meg.send_signal(meg.MEG_settings["TASK_START_CODE"]) + exp_win.callOnFlip(meg.send_signal, meg.MEG_settings["TASK_START_CODE"]) if not shortcut_evt: shortcut_evt = run_task_loop( + task, task.run(exp_win, ctl_win), eyetracker, gaze_drawer, @@ -76,7 +78,7 @@ def run_task( # send stop trigger/marker to MEG + Biopac (or anything else on parallel port) if task.use_meg and not shortcut_evt: - meg.send_signal(meg.MEG_settings["TASK_STOP_CODE"]) + exp_win.callOnFlip(meg.send_signal, meg.MEG_settings["TASK_STOP_CODE"]) if eyetracker: eyetracker.stop_recording() @@ -84,6 +86,7 @@ def run_task( task.save() run_task_loop( + task, task.stop(exp_win, ctl_win), eyetracker, gaze_drawer, diff --git a/src/shared/meg.py b/src/shared/meg.py index fc69dc42..11085fbc 100644 --- a/src/shared/meg.py +++ b/src/shared/meg.py @@ -2,14 +2,20 @@ from . import config import time +MEG_MARKERS_ON_FLIP = True + MEG_settings = { "TASK_START_CODE": int("00000010", 2), "TASK_START_STOP": int("00000100", 2), + "TASK_FLIP": int("00000101", 2), } +port = None def send_signal(data): - port = parallel.ParallelPort(address=config.PARALLEL_PORT_ADDRESS) + global port + if not port: + port = parallel.ParallelPort(address=config.PARALLEL_PORT_ADDRESS) port.setData(data) time.sleep(0.001) port.setData(0) # reset diff --git a/src/tasks/task_base.py b/src/tasks/task_base.py index 4efd6d49..500c10be 100644 --- a/src/tasks/task_base.py +++ b/src/tasks/task_base.py @@ -44,6 +44,7 @@ def setup( self._progress_bar_refresh_rate = config.FRAME_RATE def _setup(self, exp_win): + self._exp_win = exp_win pass def _generate_unique_filename(self, suffix, ext="tsv"): @@ -87,6 +88,8 @@ def run(self, exp_win, ctl_win): for clearBuffer in self._run(exp_win, ctl_win): # yield first to allow external draw before flip yield + if meg.MEG_MARKERS_ON_FLIP and self.use_meg: + exp_win.callOnFlip(meg.send_signal, meg.MEG_settings["TASK_FLIP"]) self._flip_all_windows(exp_win, ctl_win, clearBuffer) if not hasattr(self, "_exp_win_first_flip_time"): self._exp_win_first_flip_time = self._exp_win_last_flip_time From f8a29b805f2cb986d77a78c23a7de94d6f968c58 Mon Sep 17 00:00:00 2001 From: basile Date: Wed, 21 Apr 2021 14:08:39 -0400 Subject: [PATCH 03/10] hog cpu for accurate timing of the MEG marker --- src/shared/meg.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shared/meg.py b/src/shared/meg.py index 11085fbc..004c96db 100644 --- a/src/shared/meg.py +++ b/src/shared/meg.py @@ -4,6 +4,8 @@ MEG_MARKERS_ON_FLIP = True +MEG_MARKER_DURATION = .001 + MEG_settings = { "TASK_START_CODE": int("00000010", 2), "TASK_START_STOP": int("00000100", 2), @@ -16,6 +18,9 @@ def send_signal(data): global port if not port: port = parallel.ParallelPort(address=config.PARALLEL_PORT_ADDRESS) + start=time.monotonic() port.setData(data) - time.sleep(0.001) + # hog cpu for accurate timing + while time.monotonic() < start + MEG_MARKER_DURATION: + continue port.setData(0) # reset From cafecf1e9a5ea3c7f617976ed9fad0f7a1a981d5 Mon Sep 17 00:00:00 2001 From: Hyruuk Date: Tue, 1 Jun 2021 14:38:32 -0400 Subject: [PATCH 04/10] solved a bug in the STOP trigger name --- src/shared/meg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/meg.py b/src/shared/meg.py index 004c96db..8afd0a14 100644 --- a/src/shared/meg.py +++ b/src/shared/meg.py @@ -8,7 +8,7 @@ MEG_settings = { "TASK_START_CODE": int("00000010", 2), - "TASK_START_STOP": int("00000100", 2), + "TASK_STOP_CODE": int("00000100", 2), "TASK_FLIP": int("00000101", 2), } From b484d2e956a8bccf200bdd607d5281219f41660d Mon Sep 17 00:00:00 2001 From: basile Date: Fri, 14 Jan 2022 10:30:37 -0500 Subject: [PATCH 05/10] script to check stable phase progress duration/level-reps --- utils/check_mario_stable_progress.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 utils/check_mario_stable_progress.py diff --git a/utils/check_mario_stable_progress.py b/utils/check_mario_stable_progress.py new file mode 100644 index 00000000..b301123d --- /dev/null +++ b/utils/check_mario_stable_progress.py @@ -0,0 +1,14 @@ +import glob, pandas +for sub in [1,2,3, 6]: + print(f"sub-{sub:02}" + '#'*50) + ev_fnames = glob.glob(f"sub-{sub:02d}/ses-*/*task-mario_*_events.tsv") + evs = [pandas.read_csv(ev_fname,delimiter='\t') for ev_fname in ev_fnames] + evs_complete = [ev for ev in evs if 'questionnaire-answer' in ev.trial_type.values] + evs_stable = [ev for ev in evs_complete if len(ev.level.unique()) > 1 ] + if len(evs_stable) == 0: + print("not reached stable phase yet?") + continue + quest_onset = [ev[ev.trial_type=='questionnaire-value-change'].onset.values for ev in evs_stable] + levels = pandas.concat([ev[ev.trial_type=='gym-retro_game'].level for ev in evs_stable]) + print("stable phase duration (min)", sum([qo[0] for qo in quest_onset if len(qo)])/60.) + print(levels.value_counts()) From 66b3c6a5e41d59bfd3e978a4bf1cad6eb23da465 Mon Sep 17 00:00:00 2001 From: basile Date: Tue, 8 Feb 2022 14:46:40 -0500 Subject: [PATCH 06/10] sort levels in stable phase log util --- utils/check_mario_stable_progress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/check_mario_stable_progress.py b/utils/check_mario_stable_progress.py index b301123d..89a68df6 100644 --- a/utils/check_mario_stable_progress.py +++ b/utils/check_mario_stable_progress.py @@ -11,4 +11,4 @@ quest_onset = [ev[ev.trial_type=='questionnaire-value-change'].onset.values for ev in evs_stable] levels = pandas.concat([ev[ev.trial_type=='gym-retro_game'].level for ev in evs_stable]) print("stable phase duration (min)", sum([qo[0] for qo in quest_onset if len(qo)])/60.) - print(levels.value_counts()) + #print(levels.value_counts().sort_index()) From 37f1ef1717c95e69d9ab8758cf87123129e3437d Mon Sep 17 00:00:00 2001 From: basile Date: Thu, 10 Feb 2022 13:33:35 -0500 Subject: [PATCH 07/10] fix/improve standalone eyetracking script --- start_eyetracking.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/start_eyetracking.py b/start_eyetracking.py index f9c69160..b34e8244 100644 --- a/start_eyetracking.py +++ b/start_eyetracking.py @@ -25,9 +25,10 @@ def start_eyetracker(args): "args": { "fullscreen": True, "marker_scale": 1.0, - "sample_duration": 60, + "sample_duration": 360, "monitor_name": "eDP-1 [1]", "fixed_screen": True, + "selected_gazer_class_name": "3D" }, } ) @@ -37,7 +38,7 @@ def start_eyetracker(args): "subject": "start_plugin", "name": "Annotation_Capture", "args": { - "annotation_definitions": [['Trigger','T']], + "annotation_definitions": [['Trigger','T'], ['Trigger5','5'], ['Trigger%','%']] }, } ) From fa2519c26b59ae7ba5aa64ca64cb54d2b39c98f8 Mon Sep 17 00:00:00 2001 From: basile Date: Thu, 24 Feb 2022 15:11:53 -0500 Subject: [PATCH 08/10] set fullscreen --- src/shared/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/config.py b/src/shared/config.py index 4b4bd688..cc62607b 100644 --- a/src/shared/config.py +++ b/src/shared/config.py @@ -25,7 +25,7 @@ EXP_WINDOW = dict( size=(1920, 1080), screen=1, - #fullscr=True, + fullscr=True, gammaErrorPolicy="warn", #waitBlanking=False, ) From 00f64cd8ce1847b54cb430a81509a1f15d04d184 Mon Sep 17 00:00:00 2001 From: basile Date: Thu, 24 Feb 2022 16:11:42 -0500 Subject: [PATCH 09/10] add a scaling parameters for MEG --- src/sessions/ses-mariomeg.py | 81 ++++++++++++++++++++++++++++++++++++ src/tasks/videogame.py | 6 ++- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/sessions/ses-mariomeg.py diff --git a/src/sessions/ses-mariomeg.py b/src/sessions/ses-mariomeg.py new file mode 100644 index 00000000..26ac00ce --- /dev/null +++ b/src/sessions/ses-mariomeg.py @@ -0,0 +1,81 @@ +import sys, os +import numpy as np +import random +import hashlib +import pandas + + +worlds = 8 +levels = 3 +exclude_list = [(2,2),(7,2)] +all_levels = [(world, level) + for world in range(1,worlds+1) + for level in range(1,levels+1) + if (world,level) not in exclude_list] +n_repetitions = 50 # very high number, will never reach that point + + +def get_tasks(parsed): + + from ..tasks import videogame, task_base + from .game_questionnaires import flow_ratings, other_ratings + import json + import retro + # point to a copy of the whole gym-retro with custom states and scenarii + retro.data.Integrations.add_custom_path( + os.path.join(os.getcwd(), "data", "videogames", "mario") + ) + + bids_sub = "sub-%s" % parsed.subject + + design_path = os.path.join( + 'data', + 'videogames', + 'mario', + "designs", + f"{bids_sub}_design.tsv", + ) + design = pandas.read_csv(design_path, sep='\t') + scenario = "scenario" + + savestate_path = os.path.abspath(os.path.join(parsed.output, "sourcedata", bids_sub, f"{bids_sub}_phase-stable_task-mario_savestate.json")) + + # check for a "savestate" + if os.path.exists(savestate_path): + with open(savestate_path) as f: + savestate = json.load(f) + else: + savestate = {"index": 0} + + for run in range(10): + + next_levels = [f"Level{world}-{level}" for idx,(world,level) in design[savestate['index']:savestate['index']+20].iterrows()] + if len(next_levels) == 0: + print('Stable phase completed, no more levels to play') + return [] + + task = videogame.VideoGameMultiLevel( + game_name='SuperMarioBros-Nes', + state_names=next_levels, + scenarii=[scenario]*len(next_levels), + repeat_scenario=True, + max_duration=10 * 60, # if when level completed or dead we exceed that time in secs, stop the task + name=f"task-mario_run-{run+1:02d}", + instruction="playing Super Mario Bros {state_name} \n\n Let's-a go!", + post_run_ratings = [(k, q, 7) for k, q in enumerate(other_ratings+flow_ratings)], + use_eyetracking=True, + scaling=.5, + ) + + yield task + + #only increment if the task was not interrupted, if interrupted, it needs to be rescan + if task._task_completed: + savestate['index'] += task._nlevels + with open(savestate_path, 'w') as f: + json.dump(savestate, f) + + yield task_base.Pause( + text="You can take a short break.\n Press A when ready to continue", + wait_key='a', + ) diff --git a/src/tasks/videogame.py b/src/tasks/videogame.py index 80319799..f4801d69 100644 --- a/src/tasks/videogame.py +++ b/src/tasks/videogame.py @@ -74,6 +74,7 @@ def __init__( state_name=None, scenario=None, repeat_scenario=True, + scaling=1, inttype=retro.data.Integrations.CUSTOM_ONLY, *args, **kwargs @@ -85,6 +86,7 @@ def __init__( self.scenario = scenario self.repeat_scenario = repeat_scenario self.inttype = inttype + self._scaling = scaling def _setup(self, exp_win): @@ -102,8 +104,8 @@ def _setup(self, exp_win): exp_win.size[0] / self._first_frame.shape[1], exp_win.size[1] / self._first_frame.shape[0], ) - width = int(min_ratio * self._first_frame.shape[1]) - height = int(min_ratio * self._first_frame.shape[0]) + width = int(min_ratio * self._first_frame.shape[1] * self._scaling) + height = int(min_ratio * self._first_frame.shape[0] * self._scaling) self.game_vis_stim = visual.ImageStim( exp_win, From e35a9be76919825f8134a2ac3583aafdfdc6e2b6 Mon Sep 17 00:00:00 2001 From: Hyruuk Date: Fri, 25 Feb 2022 16:37:32 -0500 Subject: [PATCH 10/10] changed parport adress --- src/shared/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/config.py b/src/shared/config.py index cc62607b..bfcb0e79 100644 --- a/src/shared/config.py +++ b/src/shared/config.py @@ -49,4 +49,4 @@ WRAP_WIDTH = 2 # port for meg setup -PARALLEL_PORT_ADDRESS = "/dev/parport0" +PARALLEL_PORT_ADDRESS = "/dev/parport1"