Skip to content

Commit

Permalink
Merge pull request #5 from VU-Cog-Sci/save_data
Browse files Browse the repository at this point in the history
Save data
  • Loading branch information
lukassnoek authored Feb 21, 2019
2 parents 46feaca + cd4238b commit 6cc4ea8
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 24 deletions.
13 changes: 3 additions & 10 deletions demos/eyetracker_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,13 @@ def run(self):
self.start_recording_eyetracker()
self.start_experiment()

for trial_nr in range(self.n_trials):

trial = TestTrial(
session=self,
trial_nr=trial_nr,
phase_durations=(0.5, 0.5),
verbose=True
)

for trial in self.trials:
trial.run()
self.close()


if __name__ == '__main__':

session = TestEyetrackerSession(eyetracker_on=True, n_trials=10)
session = TestEyetrackerSession('sub-01', eyetracker_on=True, n_trials=10)
session.create_trials()
session.run()
2 changes: 1 addition & 1 deletion demos/fmri_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ def run(self):

if __name__ == '__main__':

session = TestFMRISession(n_trials=10)
session = TestFMRISession('sub-01', n_trials=10)
session.create_trials(durations=(15, 15), timing='frames')
session.run()
6 changes: 3 additions & 3 deletions demos/simple_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def draw(self):

class TestSession(Session):
""" Simple session with x trials. """
def __init__(self, settings_file=None, n_trials=10, eyetracker_on=False):
def __init__(self, output_str, settings_file=None, n_trials=10, eyetracker_on=False):
""" Initializes TestSession object. """
self.n_trials = n_trials
super().__init__(settings_file, eyetracker_on)
super().__init__(output_str, settings_file, eyetracker_on)

def create_trials(self, durations=(.5, .5), timing='seconds'):
self.trials = []
Expand All @@ -48,7 +48,7 @@ def run(self):

if __name__ == '__main__':

session = TestSession(n_trials=5)
session = TestSession('sub-01', n_trials=5)
session.create_trials(durations=(.5, .5), timing='seconds')
#session.create_trials(durations=(30, 30), timing='frames')
session.run()
81 changes: 71 additions & 10 deletions exptools2/session.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import yaml
import os.path as op
import numpy as np
Expand All @@ -16,20 +17,25 @@
# - merge default settings with user settings (overwrite default)
# - write function that pickles/joblib dump complete exp


class Session:
""" Base Session class """
def __init__(self, settings_file=None, eyetracker_on=False):
def __init__(self, output_str, settings_file=None, eyetracker_on=False):
""" Initializes base Session class.
parameters
----------
output_str : str
Name (string) for output-files (e.g., 'sub-01_ses-post_run-1')
settings_file : str
Path to settings file. If None, default_settings.yml is used
eyetracker_on : bool
Whether to enable eyetracker
attributes
----------
output_dir : str
Path to output-directory ($PWD/logs)
settings : dict
Dictionary with settings from yaml
clock : psychopy Clock
Expand All @@ -49,6 +55,8 @@ def __init__(self, settings_file=None, eyetracker_on=False):
actual_framerate : float
Estimated framerate of monitor
"""
self.output_str = output_str
self.output_dir = op.join(os.getcwd(), 'logs')
self.settings_file = settings_file
self.eyetracker_on=eyetracker_on
self.clock = core.Clock()
Expand All @@ -57,26 +65,44 @@ def __init__(self, settings_file=None, eyetracker_on=False):
self.exp_stop = None
self.current_trial = None
self.log = dict(trial_nr=[], onset=[], event_type=[], phase=[], response=[], nr_frames=[])
self.logfile = logging.LogFile(f='log.txt', filemode='w', level=logging.EXP)
self.nr_frames = 0 # keeps track of nr of nr of frames per phase

# Initialize
self.settings = self._load_settings()
self.monitor = self._create_monitor()
self.win = self._create_window()
self.mouse = Mouse(**self.settings['mouse'])
self.logfile = self._create_logfile()
self.default_fix = TextStim(self.win, '+')
self.mri_simulator = self._setup_mri_simulator() if self.settings['mri']['simulate'] else None
self.tracker = None

def _load_settings(self):
""" Loads settings and sets preferences. """
if self.settings_file is None:
self.settings_file = op.join(op.dirname(__file__), 'data', 'default_settings.yml')
logging.warn(f"Using default logfile ({self.settings_file})")

with open(self.settings_file, 'r') as f_in:
settings = yaml.load(f_in)
default_settings_path = op.join(op.dirname(__file__), 'data', 'default_settings.yml')
with open(default_settings_path, 'r') as f_in:
default_settings = yaml.load(f_in)

if self.settings_file is None:
settings = default_settings
logging.warn("No settings-file given; using default logfile")
else:
if not op.isfile(self.settings_file):
raise IOError(f"Settings-file ({self.settings_file}) does not exist!")

with open(self.settings_file, 'r') as f_in:
user_settings = yaml.load(f_in)

settings = default_settings.update(user_settings)

# Write settings to sub dir
if not op.isdir(self.output_dir):
os.makedirs(self.output_dir)

settings_out = op.join(self.output_dir, self.output_str + '_expsettings.yml')
with open(settings_out, 'w') as f_out:
yaml.dump(settings, f_out, indent=4, default_flow_style=False)

exp_prefs = settings['preferences'] # set preferences globally
for preftype, these_settings in exp_prefs.items():
Expand All @@ -88,11 +114,13 @@ def _load_settings(self):
return settings

def _create_monitor(self):
""" Creates the monitor based on settings and save to disk. """
monitor = Monitor(**self.settings['monitor'])
monitor.save() # needed for iohub eyetracker
return monitor

def _create_window(self):
""" Creates a window based on the settings and calculates framerate. """
win = Window(monitor=self.monitor, **self.settings['window'])
win.flip(clearBuffer=True)
self.actual_framerate = win.getActualFrameRate()
Expand All @@ -101,7 +129,13 @@ def _create_window(self):
f"(1 frame = {t_per_frame:.5f})")
return win

def _create_logfile(self):
""" Creates a logfile. """
log_path = op.join(self.output_dir, self.output_str + '_log.txt')
return logging.LogFile(f=log_path, filemode='w', level=logging.EXP)

def _setup_mri_simulator(self):
""" Initializes an MRI simulator (if 'mri' in settings). """
args = self.settings['mri'].copy()
args.pop('simulate')
return SyncGenerator(**args)
Expand All @@ -113,6 +147,7 @@ def start_experiment(self):
self.win.flip(clearBuffer=True) # first frame is synchronized to start exp

def _set_exp_start(self):
""" Called upon first win.flip(); timestamps start. """
self.exp_start = self.clock.getTime()
self.clock.reset() # resets global clock
self.timer.reset() # phase-timer
Expand All @@ -121,10 +156,22 @@ def _set_exp_start(self):
self.mri_simulator.start()

def _set_exp_stop(self):
""" Called on last win.flip(); timestamps end. """
self.exp_stop = self.clock.getTime()

def display_text(self, text, keys=['return'], **kwargs):
# TODO: keys should be variable
""" Displays text on the window and waits for a key response.
parameters
----------
text : str
Text to display
keys : str or list[str]
String (or list of strings) of keyname(s) to wait for
kwargs : key-word args
Any (set of) parameter(s) passed to TextStim
"""

stim = TextStim(self.win, text=text, **kwargs)
stim.draw()
self.win.flip()
Expand All @@ -140,6 +187,9 @@ def close(self):

print(f"Duration experiment: {self.exp_stop:.3f}\n")

if not op.isdir(self.output_dir):
os.makedirs(self.output_dir)

self.log = pd.DataFrame(self.log).set_index('trial_nr')
self.log['onset_abs'] = self.log['onset'] + self.exp_start

Expand All @@ -152,22 +202,29 @@ def close(self):
# Same for nr frames
nr_frames = np.append(self.log.loc[nonresp_idx, 'nr_frames'].values[1:], self.nr_frames)
self.log.loc[nonresp_idx, 'nr_frames'] = nr_frames.astype(int)
print(self.log)

# Save to disk
f_out = op.join(self.output_dir, self.output_str + '.tsv')
self.log.to_csv(f_out, sep='\t', index=True)

fig, ax = plt.subplots(figsize=(15, 5))
ax.plot(self.win.frameIntervals)
ax.axhline(1./self.actual_framerate, c='r')
ax.axhline(1./self.actual_framerate + 1./self.actual_framerate, c='r', ls='--')
ax.set(xlim=(0, len(self.win.frameIntervals)), xlabel='Frame nr', ylabel='Interval (sec.)')
fig.savefig('frames.png')
fig.savefig(op.join(self.output_dir, self.output_str + '_frames.png'))

if self.mri_simulator is not None:
self.mri_simulator.stop()

core.quit()

def init_eyetracker(self):
""" Initializes eyetracker.
After initialization, tracker object ("device" in iohub lingo)
can be accessed from self.tracker
"""
if not self.eyetracker_on:
raise ValueError("Cannot initialize eyetracker if eyetracker_on=False!")

Expand All @@ -190,6 +247,10 @@ def stop_recording_eyetracker(self):
self.tracker.setRecordingState(False)

def calibrate_eyetracker(self):

if self.tracker is None:
raise ValueError("Cannot calibrate tracker if it's not initialized yet!")

self.tracker.runSetupProcedure()

def close_tracker(self):
Expand Down

0 comments on commit 6cc4ea8

Please sign in to comment.