diff --git a/.env.tpl b/.env.tpl index 324526cf..017b8709 100644 --- a/.env.tpl +++ b/.env.tpl @@ -1,3 +1,4 @@ export PUPIL_PATH=../pupil #export PUPIL_PATH=/home/basile/data/src/pupil export GI_TYPELIB_PATH=/usr/local/lib/x86_64-linux-gnu/girepository-1.0/ +OUTPUT_PATH=/scratch/neuromod/data/ diff --git a/main.py b/main.py index 3df511b3..559aa2c2 100755 --- a/main.py +++ b/main.py @@ -2,7 +2,6 @@ from subprocess import Popen import os, sys, importlib -import itertools from collections.abc import Iterable, Iterator from src.shared import config @@ -21,13 +20,9 @@ def run(parsed): suggestion = suggest_session_tasks(parsed.tasks) raise(ValueError('session tasks file cannot be found for %s. Did you mean %s ?'%(parsed.tasks, suggestion))) from src.shared import cli - if parsed.skip_n_tasks: - if isinstance(tasks, Iterator): - tasks = itertools.islice(tasks, parsed.skip_n_tasks, None) - else: - tasks = tasks[parsed.skip_n_tasks:] + try: - cli.main_loop( + """cli.main_loop( tasks, parsed.subject, parsed.session, @@ -42,7 +37,12 @@ def run(parsed): parsed.skip_soundcheck, parsed.target_ETcalibration, parsed.validate_ET, - ) + )""" + + cli.main_loop( + ses_mod, + parsed + ) finally: if not parsed.no_force_resolution: screen.reset_exp_screen() diff --git a/src/sessions/ses-liris.py b/src/sessions/ses-liris.py deleted file mode 100644 index 3044398e..00000000 --- a/src/sessions/ses-liris.py +++ /dev/null @@ -1,33 +0,0 @@ -from ..tasks import video, task_base -import numpy as np - - -def get_videos(subject, session): - video_idx = np.loadtxt( - "data/liris/order_fmri_neuromod.csv", delimiter=",", skiprows=1, dtype=np.int - ) - selected_idx = video_idx[video_idx[:, 0] == session, subject + 1] - return selected_idx - - -def get_tasks(parsed): - - tasks = [] - - video_indices = get_videos(int(parsed.subject), int(parsed.session)) - - for idx in video_indices: - tasks.append( - video.SingleVideo( - f"data/liris/videos/{idx:03d}.mp4", name=f"task-liris{idx:03d}" - ) - ) - continue - tasks.append( - task_base.Pause( - """The video is finished. -The scanner might run for a few seconds to acquire more images. -Please remain still.""" - ) - ) - return tasks diff --git a/src/sessions/ses-mario.py b/src/sessions/ses-mario.py index 88ae714e..c91cb098 100644 --- a/src/sessions/ses-mario.py +++ b/src/sessions/ses-mario.py @@ -73,3 +73,11 @@ def get_tasks(parsed): import importlib phase2 = importlib.import_module('src.sessions.ses-mario-phase2') yield from phase2.get_tasks(parsed) + + +def get_config(parsed): + return { + 'eyetracking_calibration_version': 1, + 'eyetracking_validation': True, + 'output_dataset': 'mario', + } diff --git a/src/sessions/ses-mario3.py b/src/sessions/ses-mario3.py index 92ff1f4c..aa37cba9 100644 --- a/src/sessions/ses-mario3.py +++ b/src/sessions/ses-mario3.py @@ -100,9 +100,6 @@ def smb3_completion_fn(env): for run in range(10): next_levels = [f"1Player.World{world}.{level}" for idx,(world,level) in design[savestate['index']:savestate['index']+20].iterrows()] - if len(next_levels) < 5: - print('Stable phase completed, no more levels to play') - return [] task = videogame.VideoGameMultiLevel( game_name='SuperMarioBros3-Nes', @@ -125,11 +122,15 @@ def smb3_completion_fn(env): #only increment if the task was not interrupted, if interrupted, it needs to be rescan if task._task_completed: + print('saving savestate') 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', - ) +def get_config(parsed): + return { + 'eyetracking_calibration_version': 2, + 'eyetracking_validation': True, + 'add_pauses': True, + 'output_dataset': 'mario3', + } diff --git a/src/sessions/ses-mariomeg.py b/src/sessions/ses-mariomeg.py index 26ac00ce..69d41bda 100644 --- a/src/sessions/ses-mariomeg.py +++ b/src/sessions/ses-mariomeg.py @@ -79,3 +79,11 @@ def get_tasks(parsed): text="You can take a short break.\n Press A when ready to continue", wait_key='a', ) + +def get_config(parsed): + return { + 'eyetracking_calibration_version': 1, + 'eyetracking_validation': True, + 'output_dataset': 'mariomeg', + 'meg': True, + } diff --git a/src/sessions/ses-mariostars.py b/src/sessions/ses-mariostars.py index eeb624e4..45e74c62 100644 --- a/src/sessions/ses-mariostars.py +++ b/src/sessions/ses-mariostars.py @@ -115,3 +115,11 @@ def get_tasks(parsed): text="You can take a short break.\n Press A when ready to continue", wait_key='a', ) + + +def get_config(parsed): + return { + 'eyetracking_calibration_version': 1, + 'eyetracking_validation': True, + 'output_dataset': 'mariostars', + } diff --git a/src/sessions/ses-motion.py b/src/sessions/ses-motion.py deleted file mode 100644 index b47c9090..00000000 --- a/src/sessions/ses-motion.py +++ /dev/null @@ -1,19 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - task_base.Pause( - """Hi! We are about to start the MRI session. -Make yourself comfortable. -Ensure that you can see the full screen and that the image is sharp. -Please keep your eyes open.""" - ), - speech.Speech("data/speech/motion_study_speech_words.csv", name="Speech"), - video.SingleVideo("data/videos/Inscapes-67962604.mp4", name="Inscapes"), - # source: https://drive.google.com/file/d/1prOM1QuPEAcqe_D-3rLYNu937A8VzbsB/view - video.SingleVideo("data/videos/tammy/Oceans_1.mp4", name="Oceans_chunk1"), - videogame.VideoGame( - state_name="Level1", name="ShinobiIIIReturnOfTheNinjaMaster-test" - ), -] - -# TODO: randomize the order of the tasks? diff --git a/src/sessions/ses-shinobi.py b/src/sessions/ses-shinobi.py deleted file mode 100644 index a97d9a65..00000000 --- a/src/sessions/ses-shinobi.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..tasks import images, videogame, memory, task_base - -TASKS = [ - videogame.VideoGame( - state_name="Level1", - scenario="scenario_repeat1", # this scenario repeats the same level - max_duration=10 - * 60, # if when level completed or dead we exceed that time in secs, stop the task - name="ShinobiIIIReturnOfTheNinjaMaster-level1", - ), - # videogame.VideoGameReplay('output/sub-test/ses-test_refactor/sub-test_ses-test_refactor_20190109_154007_ShinobiIIIReturnOfTheNinjaMaster-Genesis_Level4_000.bk2',name='ShinobiIIIReturnOfTheNinjaMaster-replay',), -] * 3 ###!!!!!!!!!!! REPEAT !!!!!!!!!!!### diff --git a/src/sessions/ses-shinobi_3levels.py b/src/sessions/ses-shinobi_3levels.py deleted file mode 100644 index b674f3b6..00000000 --- a/src/sessions/ses-shinobi_3levels.py +++ /dev/null @@ -1,26 +0,0 @@ -from ..tasks import images, videogame, memory, task_base - -TASKS = [ - videogame.VideoGame( - state_name="Level1", - scenario="scenario_repeat1", # this scenario repeats the same level - max_duration=10 - * 60, # if when level completed or dead we exceed that time in secs, stop the task - name="ShinobiIIIReturnOfTheNinjaMaster-level1", - ), - # videogame.VideoGameReplay('output/sub-test/ses-test_refactor/sub-test_ses-test_refactor_20190109_154007_ShinobiIIIReturnOfTheNinjaMaster-Genesis_Level4_000.bk2',name='ShinobiIIIReturnOfTheNinjaMaster-replay',), - videogame.VideoGame( - state_name="Level4-1", - scenario="scenario_Level4-1", # this scenario repeats the same level - max_duration=10 - * 60, # if when level completed or dead we exceed that time in secs, stop the task - name="ShinobiIIIReturnOfTheNinjaMaster-level4-1", - ), - videogame.VideoGame( - state_name="Level5-0", - scenario="scenario_Level5-0", # this scenario repeats the same level - max_duration=10 - * 60, # if when level completed or dead we exceed that time in secs, stop the task - name="ShinobiIIIReturnOfTheNinjaMaster-level5-0", - ), -] * 3 ###!!!!!!!!!!! REPEAT !!!!!!!!!!!### diff --git a/src/sessions/ses-test64.py b/src/sessions/ses-test64.py deleted file mode 100644 index a1b0ce3a..00000000 --- a/src/sessions/ses-test64.py +++ /dev/null @@ -1,11 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_1_filt.mp4", - name="Oceans_fs_10m_1", - ), -] diff --git a/src/sessions/ses-triplet.py b/src/sessions/ses-triplet.py index 17e73bc1..6a69c21d 100644 --- a/src/sessions/ses-triplet.py +++ b/src/sessions/ses-triplet.py @@ -140,3 +140,11 @@ def generate_design_file(subject, all_triplets, pilot=False): else: all_triplets = pandas.read_csv(os.path.join(TRIPLET_DATA_PATH, 'fMRI_triplets.csv')) generate_design_file(parsed.subject, all_triplets, parsed.pilot) + + +def get_config(parsed): + return { + 'eyetracking_calibration_version': 1, + 'eyetracking_validation': True, + 'output_dataset': 'triplets', + } diff --git a/src/sessions/ses-triplet_test.py b/src/sessions/ses-triplet_test.py deleted file mode 100644 index 05132333..00000000 --- a/src/sessions/ses-triplet_test.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..tasks import language, task_base - -TASKS = [ - language.Triplet( - "data/language/triplet/first1000triples.csv", wait_key=True, name="triplet_test" - ), - task_base.Pause(text="Take a break. Press any key to continue...", wait_key=None), - language.Triplet( - "data/language/triplet/first1000triples.csv", wait_key=True, name="triplet_test" - ), - task_base.Pause(text="Take a break. Press to continue...", wait_key=["a"]), -] diff --git a/src/sessions/ses-video1.py b/src/sessions/ses-video1.py deleted file mode 100644 index d465aaf6..00000000 --- a/src/sessions/ses-video1.py +++ /dev/null @@ -1,23 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - task_base.Pause(), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_1_filt.mp4", - name="Oceans_fs_10m_1", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_2_filt.mp4", - name="Oceans_fs_10m_2", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_3_filt.mp4", - name="Oceans_fs_10m_3", - ), -] diff --git a/src/sessions/ses-video2.py b/src/sessions/ses-video2.py deleted file mode 100644 index 556c7a72..00000000 --- a/src/sessions/ses-video2.py +++ /dev/null @@ -1,27 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - task_base.Pause(), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_4_filt.mp4", - name="Oceans_fs_10m_4", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_5_filt.mp4", - name="Oceans_fs_10m_5", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_6_filt.mp4", - name="Oceans_fs_10m_6", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_7_filt.mp4", - name="Oceans_fs_10m_7", - ), -] diff --git a/src/sessions/ses-video3.py b/src/sessions/ses-video3.py deleted file mode 100644 index c88b8a40..00000000 --- a/src/sessions/ses-video3.py +++ /dev/null @@ -1,27 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - task_base.Pause(), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_8_filt.mp4", - name="Oceans_fs_10m_8", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_9_filt.mp4", - name="Oceans_fs_10m_9", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_10_filt.mp4", - name="Oceans_fs_10m_10", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_11_filt.mp4", - name="Oceans_fs_10m_11", - ), -] diff --git a/src/sessions/ses-video3b.py b/src/sessions/ses-video3b.py deleted file mode 100644 index 8052865f..00000000 --- a/src/sessions/ses-video3b.py +++ /dev/null @@ -1,27 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Inscapes_sound_normed_filt.mp4", name="Inscapes" - ), - task_base.Pause(), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_7_filt.mp4", - name="Oceans_fs_10m_7", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_8_filt.mp4", - name="Oceans_fs_10m_8", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_9_filt.mp4", - name="Oceans_fs_10m_9", - ), - video.SingleVideo( - "data/videos/Oceans_fs_10m_filt/Oceans_fs_10m_10_filt.mp4", - name="Oceans_fs_10m_10", - ), -] diff --git a/src/sessions/ses-video_images_pilot.py b/src/sessions/ses-video_images_pilot.py deleted file mode 100644 index 80ae4065..00000000 --- a/src/sessions/ses-video_images_pilot.py +++ /dev/null @@ -1,25 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - # memory.ImagePosition('data/memory/stimuli.csv', use_fmri=parsed.fmri, use_eyetracking=True), - video.SingleVideo( - "data/videos/Inscapes-67962604.mp4", - name="Inscapes", - use_fmri=parsed.fmri, - use_eyetracking=True, - ), - task_base.Pause(), - video.SingleVideo( - "data/videos/skateboard_fails.mp4", - name="skateboard_fails", - use_fmri=parsed.fmri, - use_eyetracking=True, - ), - images.Images( - "data/images/test_conditions.csv", - "/home/basile/data/projects/task_stimuli/data/images/bold5000/Scene_Stimuli/Presented_Stimuli/ImageNet", - name="bold5000", - use_fmri=parsed.fmri, - use_eyetracking=True, - ), -] diff --git a/src/sessions/ses-video_pilot.py b/src/sessions/ses-video_pilot.py deleted file mode 100644 index 3267f2bb..00000000 --- a/src/sessions/ses-video_pilot.py +++ /dev/null @@ -1,9 +0,0 @@ -from ..tasks import images, video, memory, task_base - -TASKS = [ - video.SingleVideo( - "data/videos/movies_for_montreal/03_Inscaped_NoScannerSound_h264.mov", - name="Inscapes", - ), - video.SingleVideo("data/videos/tammy/Oceans_1.mp4", name="Oceans_chunk1"), -] diff --git a/src/sessions/ses-videogame_test.py b/src/sessions/ses-videogame_test.py deleted file mode 100644 index 8528b4d6..00000000 --- a/src/sessions/ses-videogame_test.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..tasks import images, videogame, memory, task_base - -TASKS = [ - videogame.VideoGame( - state_name="Level1", - scenario="scenario_repeat1", # this scenario repeats the same level - max_duration=10 - * 60, # if when level completed or dead we exceed that time in secs, stop the task - name="ShinobiIIIReturnOfTheNinjaMaster-test", - ), - # videogame.VideoGameReplay('output/sub-test/ses-test_refactor/sub-test_ses-test_refactor_20190109_154007_ShinobiIIIReturnOfTheNinjaMaster-Genesis_Level4_000.bk2',name='ShinobiIIIReturnOfTheNinjaMaster-replay',), -] * 3 ###!!!!!!!!!!! REPEAT !!!!!!!!!!!### diff --git a/src/sessions/ses-videoshorttest.py b/src/sessions/ses-videoshorttest.py deleted file mode 100644 index 25cf7e13..00000000 --- a/src/sessions/ses-videoshorttest.py +++ /dev/null @@ -1,5 +0,0 @@ -from ..tasks import video, task_base - -TASKS = [ - video.SingleVideo("data/videos/bourne_test.mkv", name="testshort"), -] diff --git a/src/shared/cli.py b/src/shared/cli.py index 5de953fc..69e72a2e 100644 --- a/src/shared/cli.py +++ b/src/shared/cli.py @@ -1,6 +1,8 @@ # CLI: command line interface options and main loop import os, datetime, traceback, glob, time +import itertools +from termcolor import colored, cprint from collections.abc import Iterable, Iterator from psychopy import core, visual, logging, event import itertools @@ -20,7 +22,7 @@ def listen_shortcuts(): if any([k[1] & event.MOD_CTRL for k in event._keyBuffer]): - allKeys = event.getKeys(["n", "c", "q"], modifiers=True) + allKeys = event.getKeys(["n", "k", "q"], modifiers=True) ctrl_pressed = any([k[1]["ctrl"] for k in allKeys]) all_keys_only = [k[0] for k in allKeys] if len(allKeys) and ctrl_pressed: @@ -97,113 +99,129 @@ def run_task( def main_loop( - all_tasks, - subject, - session, - output_ds, - enable_eyetracker=False, - use_fmri=False, - use_meg=False, - show_ctl_win=False, - allow_run_on_battery=False, - enable_ptt=False, - record_movie=False, - skip_soundcheck=False, - calibration_targets=False, - validate_eyetrack=False, + session_module, + parsed, ): - - # force screen resolution to solve issues with video splitter at scanner - """xrandr = Popen([ - 'xrandr', - '--output', 'eDP-1', - '--mode', '%dx%d'%config.EXP_WINDOW['size'], - '--rate', str(config.FRAME_RATE)]) - time.sleep(5)""" - + """ subject, + output_ds, + eyetracker=False, + use_fmri=False, + use_meg=False, + show_ctl_win=False, + allow_run_on_battery=False, + enable_ptt=False, + record_movie=False, + skip_soundcheck=False, + calibration_targets=False, + validate_eyetrack=False, + **kwargs + ):""" + + # check power source, for laptop setup if not utils.check_power_plugged(): - print("*" * 25 + "WARNING: the power cord is not connected" + "*" * 25) - if not allow_run_on_battery: + cprint("*" * 25 + "WARNING: the power cord is not connected" + "*" * 25, 'red', attrs=['blink']) + if not parsed.run_on_battery: return - bids_sub_ses = ("sub-%s" % subject, "ses-%s" % session) - log_path = os.path.abspath(os.path.join(output_ds, "sourcedata", *bids_sub_ses)) + # get session specific config + if hasattr(session_module, 'get_config'): + session_config = session_module.get_config(parsed) + #override some config parameters from the command line + session_config.update({k:v for k,v in vars(parsed).items() if v}) + print(session_config) + + + # get tasks subset + all_tasks = session_module.get_tasks(parsed) + + if parsed.skip_n_tasks: + if isinstance(all_tasks, Iterator): + all_tasks = itertools.islice(all_tasks, parsed.skip_n_tasks, None) + else: + all_tasks = tasks[parsed.skip_n_tasks:] + + # setup output and filename templates + if not parsed.output: + parsed.output = os.path.join(os.environ["OUTPUT_PATH"], session_config['output_dataset']) + + print(f'outputs will be saved in {parsed.output}') + bids_sub_ses = ("sub-%s" % parsed.subject, "ses-%s" % parsed.session) + log_path = os.path.abspath(os.path.join(parsed.output, "sourcedata", *bids_sub_ses)) if not os.path.exists(log_path): os.makedirs(log_path, exist_ok=True) log_name_prefix = "sub-%s_ses-%s_%s" % ( - subject, - session, + parsed.subject, + parsed.session, datetime.datetime.now().strftime("%Y%m%d-%H%M%S"), ) logfile_path = os.path.join(log_path, log_name_prefix + ".log") log_file = logging.LogFile(logfile_path, level=logging.INFO, filemode="w") + # create windows for stimuli exp_win = visual.Window(**config.EXP_WINDOW, monitor=config.EXP_MONITOR) exp_win.mouseVisible = False - - if show_ctl_win: + if parsed.control_window: ctl_win = visual.Window(**config.CTL_WINDOW) ctl_win.name = "Stimuli" else: ctl_win = None + # Create push-to-talk "controller" ptt = None - if enable_ptt: + if parsed.ptt: from .ptt import PushToTalk - ptt = PushToTalk() + if parsed.fmri: + setup_video_path = utils.get_subject_soundcheck_video(parsed.subject) + + setup_tasks = [ + video.VideoAudioCheckLoop( + setup_video_path, + name="setup_soundcheck_video",) if not parsed.skip_soundcheck else None, + task_base.Pause( + """We are completing the setup and initializing the scanner. +We will start the tasks in a few minutes. +Please remain still.""" + )] + + all_tasks = itertools.chain( + filter(bool,setup_tasks), + all_tasks, + [task_base.Pause( + """We are done for today. +The scanner might run for a few seconds to acquire reference images. +Please remain still. +We are coming to get you out of the scanner shortly.""" + )], + ) + + # Initiliaze eyetracker client and start thread. eyetracker_client = None gaze_drawer = None - if enable_eyetracker: - print("creating et client") + if parsed.eyetracking: eyetracker_client = eyetracking.EyeTrackerClient( output_path=log_path, output_fname_base=log_name_prefix, profile=False, debug=False, - use_targets = calibration_targets, - validate_calib = validate_eyetrack, ) - print("starting et client") eyetracker_client.start() - print("done") + # append the eyetracker setup and all calibration/validations all_tasks = itertools.chain( [eyetracking.EyetrackerSetup(eyetracker=eyetracker_client, name='eyetracker_setup'),], all_tasks ) - all_tasks = eyetracker_client.interleave_calibration(all_tasks) - - if show_ctl_win: - gaze_drawer = eyetracking.GazeDrawer(ctl_win) - if use_fmri: - - all_tasks = itertools.chain( - [task_base.Pause( - """We are completing the setup and initializing the scanner. -We will start the tasks in a few minutes. -Please remain still.""" - )], + all_tasks = eyetracker_client.interleave_calibration( all_tasks, - [task_base.Pause( - """We are done for today. -The scanner might run for a few seconds to acquire reference images. -Please remain still. -We are coming to get you out of the scanner shortly.""" - )], - ) - - if not skip_soundcheck: - setup_video_path = utils.get_subject_soundcheck_video(subject) - all_tasks = itertools.chain([ - task_base.Pause( - """Setup: we will soon run a soundcheck to check that the sensimetrics is adequately setup.""" - ), - video.VideoAudioCheckLoop(setup_video_path, name="setup_soundcheck_video",)], - all_tasks, + calibration_version = session_config.get('eyetracking_calibration_version', 1), + validation = session_config.get('eyetracking_validation',False), + add_pauses = session_config.get('add_pauses', False), ) + if parsed.control_window: + gaze_drawer = eyetracking.GazeDrawer(ctl_win) else: all_tasks = itertools.chain( @@ -230,7 +248,7 @@ def main_loop( event.clearEvents() use_eyetracking = False - if enable_eyetracker and task.use_eyetracking: + if parsed.eyetracking and task.use_eyetracking: use_eyetracking = True # setup task files (eg. video) @@ -238,8 +256,8 @@ def main_loop( exp_win, log_path, log_name_prefix, - use_fmri=use_fmri, - use_meg=use_meg, + use_fmri=parsed.fmri, + use_meg=parsed.meg, ) print("READY") @@ -254,7 +272,7 @@ def main_loop( ctl_win, eyetracker_client, gaze_drawer, - record_movie=record_movie, + record_movie=parsed.record_movie, ) if shortcut_evt == "n": @@ -272,7 +290,7 @@ def main_loop( break logging.flush() - if record_movie: + if parsed.record_movie: out_fname = os.path.join( task.output_path, "%s_%s.mp4" % (task.output_fname_base, task.name) ) @@ -299,5 +317,5 @@ def main_loop( logging.exp(msg="user killing the program") print("you killing me!") finally: - if enable_eyetracker: + if parsed.eyetracking: eyetracker_client.join(TIMEOUT) diff --git a/src/shared/eyetracking.py b/src/shared/eyetracking.py index 8c16146e..d3105623 100644 --- a/src/shared/eyetracking.py +++ b/src/shared/eyetracking.py @@ -9,7 +9,7 @@ from psychopy import visual, core, data, logging, event from .ellipse import Ellipse -from ..tasks.task_base import Task +from ..tasks.task_base import Task, Pause from . import config INSTRUCTION_DURATION = 4 @@ -56,8 +56,8 @@ CAPTURE_SETTINGS = { "frame_size": [640, 480], "frame_rate": 250, - "exposure_time": 1500, - #"exposure_time": 4000, + # "exposure_time": 1500, + "exposure_time": 4000, "global_gain": 1, "gev_packet_size": 1400, "uid": "Aravis-Fake-GV01", # for test purposes @@ -549,7 +549,7 @@ def _run(self, exp_win, ctl_win): con_text.text = con_text_str % 'failed, retrying' con_text.draw(exp_win) yield True - time.sleep(3) + time.sleep(2) self.eyetracker.pause() from subprocess import Popen @@ -572,7 +572,7 @@ class EyeTrackerClient(threading.Thread): EYE = "eye0" def __init__(self, output_path, output_fname_base, profile=False, - debug=False, use_targets=False, validate_calib=False): + debug=False): super(EyeTrackerClient, self).__init__() self.stoprequest = threading.Event() self.paused = True @@ -589,11 +589,6 @@ def __init__(self, output_path, output_fname_base, profile=False, self.unset_gaze_cb() self.unset_fix_cb() - self.use_targets = use_targets - self.validate_calib = validate_calib - if not self.use_targets: - CAPTURE_SETTINGS["exposure_time"] = 4000 - self.output_path = output_path self.output_fname_base = output_fname_base self.record_dir = os.path.join( @@ -950,12 +945,18 @@ def fix_to_marker_distances(self, markers_dict): return markers_dict, val_qc - def interleave_calibration(self, tasks): + def interleave_calibration(self, tasks, calibration_version=2, validation=False, add_pauses=False): calibration_index=0 for task in tasks: if task.use_eyetracking: + if calibration_index > 0 and add_pauses: + yield Pause( + text="You can take a short break.\n Press A when ready to continue", + wait_key='a', + ) + calibration_index += 1 - if self.use_targets: + if calibration_version == 2: yield EyetrackerCalibration_targets( self, name=f"eyeTrackercalibration-{calibration_index}" @@ -965,7 +966,7 @@ def interleave_calibration(self, tasks): self, name=f"eyeTrackercalibration-{calibration_index}" ) - if self.validate_calib: + if validation: yield EyetrackerCalibration_targets( self, name=f"eyeTrackercalib-validate-{calibration_index}", diff --git a/src/shared/parser.py b/src/shared/parser.py index 7dfba265..9e19eeee 100644 --- a/src/shared/parser.py +++ b/src/shared/parser.py @@ -10,7 +10,7 @@ def parse_args(): parser.add_argument("--subject", "-s", required=True, help="Subject ID") parser.add_argument("--session", "-ss", required=True, help="Session") parser.add_argument("--tasks", "-t", required=True, help="tasks set") - parser.add_argument("--output", "-o", required=True, help="output dataset") + parser.add_argument("--output", "-o", help="output dataset, overrides default session config (eg. for testing)") parser.add_argument( "--fmri", "-f", help="Wait for fmri TTL to start each task", action="store_true" ) @@ -23,12 +23,6 @@ def parse_args(): parser.add_argument( "--eyetracking", "-e", help="Enable eyetracking", action="store_true", ) - parser.add_argument( - "--target_ETcalibration", help="Use concentric circles for eyetracking calibration", action="store_true", - ) - parser.add_argument( - "--validate_ET", "-v", help="validate eyetracking calibration", action="store_true" - ) parser.add_argument( "--skip-soundcheck", help="Disable soundcheck", action="store_true" ) @@ -36,16 +30,16 @@ def parse_args(): "--force", help="Force arguments, including savestate override", action="store_true" ) parser.add_argument( - "--skip_n_tasks", help="skip n of the tasks", default=0, type=int + "--skip-n-tasks", help="skip n of the tasks", default=0, type=int ) - parser.add_argument("--ctl_win", help="show control window", action="store_true") + parser.add_argument("--control-window", help="show control window", action="store_true") parser.add_argument( "--no-force-resolution", help="do not run xrandr to force screen resolution", action="store_true") parser.add_argument( - "--run_on_battery", + "--run-on-battery", help="allow the script to run on battery", action="store_true", ) diff --git a/src/shared/utils.py b/src/shared/utils.py index 64b5be00..76529c84 100644 --- a/src/shared/utils.py +++ b/src/shared/utils.py @@ -50,6 +50,6 @@ def get_subject_soundcheck_video(subject): "data", "videos", "subject_setup_videos", - "sub-default_setup_video.mp4", + "sub-default_setup_video.mkv", ) return setup_video_path[0]