Commit efcc0296 authored by Hao Ting Wang's avatar Hao Ting Wang

integrate experience sampling

Committer: Hao Ting Wang <hw1012@nirem008.ynic.york.ac.uk>

On branch nback_refactor
Your branch is up-to-date with 'origin/nback_refactor'.

Changes to be committed:
	modified:   example_trial_generator.py
	modified:   parameters/ConditionsSpecifications_ES.csv
	modified:   parameters/TrialSpecifications.csv
	modified:   run.py
	modified:   settings.py
	deleted:    src/_ynicmr.py
	modified:   src/datastructure/datastructure.py
	modified:   src/datastructure/stimulus.py
	renamed:    src/datastructure/trialtype.py -> src/datastructure/trial_library.py
	modified:   src/experiment.py
parent cb9da52b
Pipeline #588 canceled with stages
......@@ -9,14 +9,17 @@ H.T. Wang
'''
# example 1: build your own
from src.fileIO import write_csv, create_headers
from src.datastructure.stimulus import stimulus_onefeat
from src.fileIO import load_conditions_dict, write_csv, create_headers
from src.datastructure.stimulus import stimulus_onefeat, stimulus_ExpSample
from src.datastructure.datastructure import *
from src.datastructure import trialtype
from src.datastructure import trial_library
# set the two features we used for making the stimulus
shape = ['square', 'triangle', 'circle']
questions, _ = load_conditions_dict('./stimuli/ES_questions.csv')
# load experience sampling quesitons
# locate path of experiment specification related files
......@@ -25,7 +28,7 @@ shape = ['square', 'triangle', 'circle']
# ConditionsSpecifications_ES.csv:
# 0 back condition,
# the experience sampling part has not been implemented yet
#condition_path = './parameters/ConditionsSpecifications.csv'
# condition_path = './parameters/ConditionsSpecifications.csv'
condition_path = './parameters/ConditionsSpecifications_ES.csv'
trialheader_path = './parameters/TrialHeaders.csv'
......@@ -37,9 +40,11 @@ trialspec_col = 'trial_type'
block = None # accept values: '0', '1', None
# now define the generators
# create experiment parameters
# a 1.5 min block can have 6 catch trials max
# a 1.5 min block can have 6 catch trials max for a pure 0-/1-back task
# a 12 min block can have 8 go-task trials and 8 experience sampling probes max
# runs - minimum 1;
parameters = experiment_parameters(block_length=4.5, block_go_n=18, runs=2)
# please don't change the setting here as there are unresolved bugs
parameters = experiment_parameters(block_length=12, block_go_n=16, runs=1)
parameters.load_conditions(condition_path)
parameters.load_header(trialheader_path)
......@@ -48,11 +53,13 @@ parameters.load_header(trialheader_path)
find_trial = trial_finder(trialspec_path=trialspec_path, trialspec_col=trialspec_col)
# create stimulus generators
stimulus_generator = stimulus_onefeat(feature=shape)
stimulus_generator = stimulus_onefeat(features=shape)
exp_sample_generator = stimulus_ExpSample(questions)
# now build the trials
builder = trial_builder()
# build the trial generator
trial_generator = builder.build(parameters, find_trial, stimulus_generator, block)
trial_generator = builder.build(parameters, find_trial, stimulus_generator, exp_sample_generator, block)
# use it like this - it's a list of dictionaries
# I would just use these to save participant's output
trials = next(trial_generator)
......@@ -79,9 +86,9 @@ for trial in trials:
# the stimulus is saved as a tuple in the dictionar, use tup2str function in module stimulus
# uncomment the following lines to compare theout put
#
write_csv(fileName='example_run1.csv', list_headers=parameters.headers, thisTrial=trial)
write_csv(fileName='example_run1.csv', list_headers=parameters.headers, thisTrial=trial)
# to get run two:
trials = next(trial_generator)
for trial in trials:
write_csv(fileName='example_run2.csv', list_headers=parameters.headers, thisTrial=trial)
# to get run two: (only if the setting is on)
# trials = next(trial_generator)
# for trial in trials:
# write_csv(fileName='example_run2.csv', list_headers=parameters.headers, thisTrial=trial)
Condition,GoTrial1,GoTrial2
ZeroBack,ZeroBackRecog,ZeroBack
ZeroBack,ZeroBack,ExpSample
OneBack,OneBack,ExpSample
trial+AF8-type,fix+AF8-t+AF8-min,fix+AF8-t+AF8-max,stim+AF8-t+AF8-min,stim+AF8-t+AF8-max,trial+AF8-t+AF8-total,trial+AF8-n+AF8-min,trial+AF8-n+AF8-max
trial_type,fix_t_min,fix_t_max,stim_t_min,stim_t_max,trial_t_total,trial_n_min,trial_n_max
OneBack,1,2.5,3.5,5,6,1,1
ZeroBack,1,2.5,3.5,5,6,1,1
Recognition,1,2.5,3.5,5,6,1,1
NoGo,1.5,2.5,0.5,1.5,3,2,5
ExpSample,0.5,0.5,2,2,2.5,13,13
OneBack+AF8-feature,1,2.5,3.5,5,6,1,1
ZeroBack+AF8-feature,1,2.5,3.5,5,6,1,1
Recognition+AF8-feature,1,2.5,3.5,5,6,1,1
ExpSample,0.5,0.5,4,4,4.5,13,13
OneBack_feature,1,2.5,3.5,5,6,1,1
ZeroBack_feature,1,2.5,3.5,5,6,1,1
Recognition_feature,1,2.5,3.5,5,6,1,1
OneBackRecog,1,2.5,3.5,5,6,1,1
ZeroBackRecog,1,2.5,3.5,5,6,1,1
......@@ -17,7 +17,7 @@ INFO = {
'Experiment': 'nback_mpsych', # compulsory
'Subject': 'R0001_001', # compulsory
'Run': '1', # compulsory
'Version': ['A', 'B'], # counterbalance the fixation cross
'Version': ['A', 'B'], # counterbalance the fixation color
'N-back': ['0', '1'], # start the task with 1-back or 0-back
'Environment': ['mri', 'lab']
}
......@@ -74,6 +74,7 @@ if __name__ == "__main__":
# create display screens
fixation = fixation_cross(window=Experiment.window, color='black')
stimulus = responsescreen(window=Experiment.window, version=settings)
question = Question(window=Experiment.window, questions=questions, color='white')
switch = Text(window=Experiment.window, text='Switch', color='black')
endtxt = open('./instructions/end_instr.txt', 'r').read().split('#\n')[0]
end_msg = visual.TextStim(Experiment.window, text=endtxt, color='black',
......@@ -104,8 +105,13 @@ if __name__ == "__main__":
# show fixation
fix_t = fixation.show(timer)
# show stimulus screen and catch response
stim_t, KeyResp, Resp, KeyPressTime, respRT, correct = stim.show(timer)
if trial['TrialType'] == 'ExpSample':
question.set(trial)
start_stim, Resp, rt = question.show(timer)
else:
# show stimulus screen and catch response
stim_t, KeyResp, Resp, KeyPressTime, respRT, correct = stim.show(timer)
# post response fixation
if respRT and trial['stim_duration'] - respRT > 0:
fixation.duration = trial['stim_duration'] - respRT
......
......@@ -4,15 +4,15 @@
Define global and environment-specific settings here.
'''
# there's a bug in datastructure so don't change the next two lines
BLOCK_TIME = 4.5
BLOCK_GO_N = 18
BLOCK_TIME = 12
BLOCK_GO_N = 16
# set the two features we used for making the stimulus
shape = ['square', 'triangle', 'circle']
# texture = ['dot', 'solid', 'stripe']
# locate path of experiment specification related files
condition_path = './parameters/ConditionsSpecifications.csv'
condition_path = './parameters/ConditionsSpecifications_ES.csv'
trialheader_path = './parameters/TrialHeaders.csv'
trialspec_path = './parameters/TrialSpecifications.csv'
stimulus_dir = './stimuli/'
......@@ -125,6 +125,20 @@ VER_B_MRI = {
'loc_keys': ['1', '2']
}
EXP_SAMPLING_A = {
'0_back_color': 'blue',
'1_back_color': 'red',
'keys': ['1', '2'],
'keyans': ['left', 'right']
}
EXP_SAMPLING_B = {
'0_back_color': 'red',
'1_back_color': 'blue',
'keys': ['1', '2'],
'keyans': ['left', 'right']
}
def get_trial_generator(block):
'''
return a trial generator and a list of data log headers
......@@ -162,9 +176,9 @@ def get_settings(env, ver, test=False):
# display and key press counter balancing
if ver == 'A':
settings.update(VER_A)
settings.update(EXP_SAMPLING_A)
elif ver == 'B':
settings.update(VER_B)
settings.update(EXP_SAMPLING_B)
else:
raise ValueError, 'Version "{0}" not supported.'.format(ver)
......@@ -174,12 +188,7 @@ def get_settings(env, ver, test=False):
# settings.update(DEV)
elif env == 'mri':
settings.update(MRI)
if ver == 'A':
settings.update(VER_A_MRI)
elif ver == 'B':
settings.update(VER_B_MRI)
else:
raise ValueError, 'Version "{0}" not supported.'.format(ver)
else:
raise ValueError, 'Environment "{0}" not supported.'.format(env)
......@@ -202,6 +211,6 @@ def parse_stimulus_name(trial):
if type(trial[key]) is tuple:
trial[key] = tup2str(stimulus_dir, trial[key], '.png')
elif 'stimPic' in key and type(trial[key]) is str:
if trial[key] not in ['?', 'SWITCH']:
if trial[key] in shape:
trial[key] = stimulus_dir + trial[key] + '.png'
return trial
# -*- coding: utf-8 -*-
'''
Usful functions for collecting trigger at YNiC
and using serial port to collect participant responses
'''
# YNiC fMRI trigger related information
import sys
import serial
if sys.platform == 'linux2':
# in house script ynicstim location
YNICSTIM = '/groups/stimpc/ynicstim'
SERIAL_PORT = '/dev/ttyS0'
else:
YNICSTIM = 'M:/stimpc/ynicstim'
SERIAL_PORT = 'COM1'
TRIG_PORT = '/dev/parport0'
SLICESPERVOL = 60 # check this information with Charlotte
DUMMY_VOL = 3
sys.path.append(YNICSTIM)
from ynicstim import parallel_compat, trigger
# from src.fileIO import read_only
def get_trig_collector():
p = parallel_compat.getParallelPort(TRIG_PORT)
ts = trigger.ParallelInterruptTriggerSource(port=p)
trig_collector = trigger.TriggerCollector(triggersource=ts,
slicespervol=SLICESPERVOL)
return trig_collector
def save_vol_time(trig_collector, timer, path):
trig_collector.endCollection()
vol_time = trig_collector.getVolumeTimings(timer)
with open(path, 'w+') as f:
f.write('Volume,Time\n')
for i, t in enumerate(vol_time):
f.write('{:d},{:.3f}\n'.format(i, t))
# read_only(path)
resp_device = serial.Serial(SERIAL_PORT, 9600, timeout=0.001)
def get_serial(timer, resp_device):
KeyResp = resp_device.read(1)
KeyPressTime = timer.getTime()
if len(KeyResp) == 0:
KeyResp = None
KeyPressTime = np.nan
else:
# Map button numbers to side
## Blue == 1, Green == 3
if KeyResp in ['1', '3']:
KeyResp = 'left'
elif KeyResp in ['2', '4']:
KeyResp = 'right'
return KeyResp, KeyPressTime
......@@ -8,8 +8,8 @@ import codecs
import csv
from random import randrange, shuffle, randint, choice
from . import trialtype
from .trialtype import *
from . import trial_library
from .trial_library import *
from ..fileIO import *
......@@ -68,8 +68,8 @@ class experiment_parameters(object):
a list of counters for the number of catch trial type 1 to n
'''
time = self.block_length * 60
trialtype_n = len(self.conditions[0]) - 1
go_n = [self.block_go_n / trialtype_n] * trialtype_n
trial_library_n = len(self.conditions[0]) - 1
go_n = [self.block_go_n / trial_library_n] * trial_library_n
return time, go_n
......@@ -98,6 +98,7 @@ class trial_finder(object):
reader = csv.DictReader(f)
#loop through csv list
for row in reader:
# first convert strings to float
for item in row.keys():
row[item] = str2float(row[item])
......@@ -105,8 +106,7 @@ class trial_finder(object):
# if current rows trial type is equal to input, print that row
if trial_type == row[self.trialspec_col]:
trial_spec = row
trial_mod = getattr(trialtype, trial_type)
trial_mod = getattr(trial_library, trial_type)
return trial_mod(trial_spec=trial_spec, lst_header=None)
else:
......@@ -161,8 +161,8 @@ class trial_builder(object):
the csv file entries should not use numbers (i.e.1 and 0)
otherwise the behaviour will not work as expected
block: None, '0', '1'
no sequence assigned, starting from 1 back, starting from 0 back
block: '0', '1'
starting from 1 back, starting from 0 back
return
conditions, shuffled: lst
'''
......@@ -240,7 +240,8 @@ class trial_builder(object):
self.dict_trials.append(cur_trial)
self.last_trial = cur_trial
def build(self, experiment_parameters, trial_finder, stimulus_generator, block):
def build(self, experiment_parameters, trial_finder, \
stimulus_generator, expsampling_generator, block):
'''
This feature doesn't integrate experience sampling for now
......@@ -271,13 +272,13 @@ class trial_builder(object):
init_task_t, init_go_n = experiment_parameters.create_counter()
for block in blocks:
self.initialise(init_task_t, [9, 9]) # HW- hard coding go_n this for now
self.initialise(init_task_t, [8, 8]) # HW- hard coding go_n this for now
# get the specific go trials according to the block you are in
trial_NoGo, trial_Go = self.block_trials(
trial_finder, block, experiment_parameters.headers)
self.trial_index = trial_idx_tmp
print self.trial_index
while self.task_t != 0: # start counting
for i in range(experiment_parameters.block_go_n):
# get no-go trial number
......@@ -299,12 +300,24 @@ class trial_builder(object):
if use_go:
# select a random one from the available ones
idx = choice(use_go)
cur_trial, t = next(trial_Go[idx].generate_trial(
if trial_Go[idx].__class__.__name__=='ExpSample':
# if it's experience sampling
cur_trial, t = next(trial_Go[idx].generate_trial(
stimulus_generator=expsampling_generator,
last_trial=self.last_trial)) # n-back
for trial in cur_trial:
self.task_t -= t[0]
self.save_trial(trial, block['Condition'])
else:
cur_trial, t = next(trial_Go[idx].generate_trial(
stimulus_generator=stimulus_generator,
last_trial=self.last_trial)) # n-back
self.task_t -= t
self.task_t -= t
self.save_trial(cur_trial, block['Condition'])
self.go_n[idx] -= 1
self.save_trial(cur_trial, block['Condition'])
# add 1~ 2 no-go trials and then a switch screen to end this block
for k in range(randrange(1, 3, 1)):
......@@ -325,11 +338,11 @@ class trial_builder(object):
self.save_trial(cur_trial, 'Switch')
if self.task_t != 0:
# if this list of trials is not good for the block, restart
self.initialise(init_task_t, [9, 9])# HW- hard coding go_n this for now
self.initialise(init_task_t, [8, 8])# HW- hard coding go_n this for now
self.trial_index = trial_idx_tmp
else:
# if it's good save this block to the run
print 'save this block'
print('save this block')
run += self.dict_trials
trial_idx_tmp = self.trial_index
yield run
......
......@@ -7,29 +7,24 @@ stimulus feature generator
from random import shuffle, randint
from itertools import product
class stimulus_ExpSample(object):
'''
Incomplete
experience sampling stimulus generator
save features and generate stimuli
feature: list, dictionaries of questions
features: list, dictionaries of questions
'''
def __init__(self, feature):
self.stimuli = feature
# here shuffle the questions but hold the first question in place
def __init__(self, features):
'''split questions into two sets'''
self.q_focus = [features[0]] # the focus question stays at the top
self.q_others = features[1:]
def generate(self):
'''
Incomplete
yield self.stimuli
'''
yield self.stimuli
'''yield self.stimuli'''
shuffle(self.q_others)
yield self.q_focus + self.q_others
class stimulus_twofeat(object):
......@@ -57,6 +52,7 @@ class stimulus_twofeat(object):
yield [item_left, item_right]
class stimulus_twofeat_mix(object):
'''
double feature stimulus generator with mixed congurency
......@@ -83,16 +79,17 @@ class stimulus_twofeat_mix(object):
yield [item_left, item_right]
class stimulus_onefeat(object):
'''
single feature stimulus generator
save features and genenrate stimuli
feature1, feature2 : list, features of stimuli
features: list, features of stimuli
'''
def __init__(self, feature):
self.stimuli = feature
def __init__(self, features):
self.stimuli = features
def generate(self):
'''
......
......@@ -33,6 +33,9 @@ class ExpSample(object):
stimulus_generator: generator
stimulus generator
the output of the generator is a list of dictionaries
the header of the dictionaries are
"Item", "Question", "Scale_low", "Scale_high"
last_trial: dict
the previous trial; some trials need this information
......@@ -41,30 +44,37 @@ class ExpSample(object):
output
dict_row: dict
a trail in dictionary
dict_rows: a list of dict
a list of trials in dictionary
self.trial_spec['trial_t_total']: float
total time of this trial, for counter
trial_time: a list of float
total time of each trial, for counter
'''
dict_row = {key: None for key in self.lst_header}
item = next(stimulus_generator.generate())
items = next(stimulus_generator.generate())
dict_row['TrialIndex'] = None
dict_row['Condition'] = None
dict_rows = []
trial_time = []
for item in items:
dict_row['TrialType'] = self.trial_spec['trial_type']
dict_row['fix_duration'] = uniform(self.trial_spec['fix_t_min'],self.trial_spec['fix_t_max'])
dict_row['stim_duration'] =self.trial_spec['trial_t_total'] - dict_row['fix_duration']
dict_row = {key: None for key in self.lst_header}
dict_row['TrialIndex'] = None
dict_row['Condition'] = None
dict_row['stimPicLeft'] = None
dict_row['stimPicRight'] = None
dict_row['Ans'] = None
dict_row['TrialType'] = self.trial_spec['trial_type']
dict_row['fix_duration'] = uniform(self.trial_spec['fix_t_min'], self.trial_spec['fix_t_max'])
dict_row['stim_duration'] =self.trial_spec['trial_t_total'] - dict_row['fix_duration']
dict_row['stimPicMid'] = item
dict_row['stimPicLeft'] = item['Scale_low']
dict_row['stimPicRight'] = item['Scale_high']
dict_row['Ans'] = None
yield dict_row,self.trial_spec['trial_t_total']
dict_row['stimPicMid'] = item['Item']
dict_rows.append(dict_row)
trial_time.append(self.trial_spec['trial_t_total'])
yield dict_rows, trial_time
class NoGo(object):
'''
......
......@@ -239,8 +239,8 @@ class Question(object):
def set(self, trial):
self.description.setText(trial['Item'])
self.scale_h.setText(trial['Min_Scale'])
self.scale_l.setText(trial['Max_Scale'])
self.scale_h.setText(trial['Scale_low'])
self.scale_l.setText(trial['Scale_high'])
def show(self, clock):
keyState=key.KeyStateHandler()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment