diff --git a/Exp.py b/Exp.py index 99095b1..45950ce 100644 --- a/Exp.py +++ b/Exp.py @@ -1,10 +1,14 @@ from psychopy.visual import Window, Circle, ShapeStim, TextStim, ImageStim from psychopy import event, core +from pyglet.window import key from os import path, listdir from json import dump from numpy import sqrt, arctan2, cos, sin, linalg, clip, ndarray, array, diff, mean, arange, pi, dot import csv import math +import random +#import time +import scipy as sp from pandas import concat, DataFrame from random import choice, seed, shuffle from Tkinter import Tk @@ -17,7 +21,7 @@ except: pass -from time import time +from time import time, sleep root = Tk() def addWorkSpaceLimits(screen): @@ -220,6 +224,140 @@ def angle_split(min_angle, max_angle, num_splits): # myWin.flip() # event.waitKeys(keyList=['space']) +####################################PRE AIM STUFF######################################################### + + class myHomeArrow: + + def __init__(self,cfg,ori=0,color='#999999',size=1): + self.ori = ori + self.color = color + self.size = size + self.rightArrow = ShapeStim(win=cfg['win'], + lineWidth=0, + lineColorSpace='rgb', + lineColor=None, + fillColorSpace='rgb', + fillColor=self.color, + closeShape=True, + size=self.size, + ori=self.ori, + vertices=((-.1,0),(.9,0),(-.636,-.636)) + ) + self.leftArrow = ShapeStim(win=cfg['win'], + lineWidth=0, + lineColorSpace='rgb', + lineColor=None, + fillColorSpace='rgb', + fillColor=self.color, + closeShape=True, + size=self.size, + ori=self.ori, + vertices=((-.1,0),(.9,0),(-.636,.636)) + ) + + def draw(self): + self.rightArrow.ori = self.ori + self.leftArrow.ori = self.ori + self.rightArrow.fillColor = self.color + self.leftArrow.fillColor = self.color + self.rightArrow.size = self.size + self.leftArrow.size = self.size + self.rightArrow.draw() + self.leftArrow.draw() + + cfg['home_arrow'] = myHomeArrow(cfg,size=cfg['radius']) + +def doAiming(cfg,isAim): + + #print "IN doAiming FUNCTION" + + cfg['aim'] = sp.NaN + cfg['aimtime_ms'] = sp.NaN + cfg['keyboard'] = key.KeyStateHandler() + cfg['win'].winHandle.push_handlers(cfg['keyboard']) + + cfg['end_circle'].draw() + cfg['aim_arrow'].ori = (-1 * cfg['target_angle']) + random.randint(-10,10) + cfg['aim_arrow'].draw() + cfg['win'].flip() + #print "drew arrow" + + aimDecided = False + + #event.clearEvents() + + #startaiming = time.time() + + while(aimDecided == False): + #print 'entered loop' + if (cfg['keyboard'][key.ENTER]): + cfg['aim'] = -1 * cfg['aim_arrow'].ori + aimDecided = True + #print 'aim decided' + cfg['aim_arrow'].ori = cfg['aim_arrow'].ori % 360 + #print cfg['aim_arrow'].ori --->PRINTS DEGREE + #cfg['win'].flip() + #print "Aim Decided " + str(aimDecided) + break + return cfg['aim_arrow'].ori + #sys.exit('Aim chosen and exited') + #stopaiming = time.time() + + #NOTE: RYAN CHANGED key.NUM_LEFT and key.NUM_RIGHT to key.LEFT and key.RIGHT --> Local change only + elif cfg['keyboard'][key.LEFT]: + cfg['aim_arrow'].ori = cfg['aim_arrow'].ori - 1 + #cfg['aim_arrow'].draw() + #print "LEFT" + #time.sleep(0.5) + #print(cfg['aim_arrow'].ori) + elif cfg['keyboard'][key.RIGHT]: + cfg['aim_arrow'].ori = cfg['aim_arrow'].ori + 1 + #cfg['aim_arrow'].draw() + #print "RIGHT" + #time.sleep(0.5) + #print(cfg['aim_arrow'].ori) + #print(cfg['keyboard']) + cfg['aim_arrow'].ori = cfg['aim_arrow'].ori % 360 + cfg['end_circle'].draw() + cfg['aim_arrow'].draw() + cfg['win'].flip() + + + if cfg['keyboard'][key.ESCAPE]: + sys.exit('escape key pressed') + + #if (cfg['aim'] < 0) or (cfg['aim'] > 360): + #cfg['aim'] = cfg['aim'] % 360 + #cfg['aimtime_ms'] = int((stopaiming - startaiming) * 1000) + #cfg['aimtime_ms'] = int((stopaiming - startaiming) * 1000) + + #print(cfg['tasks'][cfg['taskno']]['target'][cfg['trialno']]) + #print(cfg['aim']) + + + #return(cfg) + #print "we done" + return cfg['aim_arrow'].ori + +def hold_at_home_function(holdHome): + if (holdHome >= 50 and isHold == True): + firstTime = time() + print firstTime + holdTime = firstTime + holdHome + print holdTime + holding = True + while (holding == True): + currentTime = time() + print currentTime + if (currentTime >= holdTime): + holding = False + break + else: + print "still holding" + currentTime = time() + print currentTime + break + print "We held" def trial_runner(cfg={}): try: @@ -329,6 +467,27 @@ def trial_runner(cfg={}): if (cfg['use_score']): myCircle.lineColor = myCircle.fillColor = [0, 0, 0] endCircle.lineColor = endCircle.fillColor = [0, 0, 0] + #doAiming(cfg,isAim) + global aimValue + aimValue = -1 + # if (isAim == True): + # show_home = False + # show_cursor = False + + if (preTrialAction == True and isAim == True): + winSize = cfg['win'].size + PPC = max(winSize)/31. + cfg['NSU'] = PPC * 8 + #print startPos + #print startPos[0] + #print startPos[1] + arrowvertices = ((-.02,-.02),(0.82,-.02),(0.8,-.08),(1,0),(0.8,.08),(0.82,.02),(-.02,.02)) + cfg['aim_arrow'] = ShapeStim(win=cfg['win'], lineWidth=cfg['NSU']*0.005, lineColorSpace='rgb', lineColor='#CC00CC', fillColorSpace='rgb', fillColor=None, vertices=arrowvertices, closeShape=True, size=PPC*7) + cfg['aim_arrow'].pos = startPos + cfg['aim_arrow'].ori = cfg['target_angle'] + #print cfg['aim_arrow'].pos + aimValue = doAiming(cfg,isAim) + #print "AIM VALUE: " + str(aimValue) except Exception as e: print "error in Block 1" # what is block 1? @@ -437,17 +596,22 @@ def trial_runner(cfg={}): pass # SHOW OBJECTS + try: if (pos_buffer == 0): pos_buffer = pos_buffer + 1 if (show_home == True): + #print "PRINT START CIRCLE HERE" startCircle.draw() # home position + #cfg['aim_arrow'].draw() + #doAiming(cfg) if (show_target == True): endCircle.draw() # target position if (show_arrow == True): arrow.draw() arrowFill.draw() if (show_cursor == True): + #print "PRINT MY CURSOR HERE" myCircle.draw() # cursor # Show the score text only in cursor and error-clamp if (cfg['use_score'] and cfg['trial_type'] != 'no_cursor' and cfg['trial_type'] != 'pause'): @@ -456,8 +620,6 @@ def trial_runner(cfg={}): print('Failed to show object: ') print(e) pass - - # phase 1 is getting to the home position (usually over very soon) try: if (phase_1 == False): if (cfg['trial_type'] == 'cursor'): @@ -516,6 +678,7 @@ def trial_runner(cfg={}): if (get_dist(circle_pos, endPos) < dist_criterion and velocity < 35 and cfg['terminal_feedback'] == False): phase_2 = True show_home = True + show_target = False # If we are using end of reach to show feedback, change the colours for @@ -555,6 +718,7 @@ def trial_runner(cfg={}): score_text.draw() # show feedback: + #print "PRINT END CIRCLE HERE 2" endCircle.draw() myCircle.draw() @@ -622,6 +786,7 @@ def trial_runner(cfg={}): while (show_terminal): # show feedback: + #print "PRINTING END CIRCLE HERE 3" endCircle.draw() myCircle.draw() myWin.flip() @@ -689,12 +854,18 @@ def trial_runner(cfg={}): timePos_dict['targetdistance_percmax'] = int(cfg['target_distance_ratio']*100) timePos_dict['accuracy_reward'] = [cfg['score_points']] * len(mouseposXArray) + timePos_dict['pre_reach_aim'] = cfg['pre_reach_aim'] + timePos_dict['aim_value'] = 0 + + if (cfg['use_score']): timePos_dict['accuracy_reward_bool'] = ['True'] * len(mouseposXArray) else: timePos_dict['accuracy_reward_bool'] = ['False'] * len(mouseposXArray) + #print "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + #print timePos_dict['aim_value'] return timePos_dict elif ((cfg['trial_type'] == 'no_cursor' or cfg['trial_type'] == 'error_clamp' or (cfg['trial_type'] == 'cursor' and cfg['terminal_feedback'] == True)) and get_dist(circle_pos, startPos) <= 3*get_dist(startPos, endPos)/20): @@ -743,6 +914,20 @@ def run_experiment(participant, experiment = {}): task_save = DataFrame({}) running = deepcopy(experiment['experiment']) # why copy this? set up a window, a mouse object and add those, plus a task-index to your cfg, then simply loop through the tasks, and throw that to a run-task function? settings = deepcopy(experiment['settings']) # same here... + #print running + #print "---------------------------------------------------------------------------------" + global preTrialAction + global isAim + global holdHome + global isHold + preTrialAction = running[0]['pre_trial_check'] + isAim = running[0]['pre_reach_aim'] + holdHome = running[0]['hold_home'] + isHold = running[0]['hold_on_home'] + #print "Pre Trial: " + str(preTrialAction) + #print "Is Aim: " + str(isAim) + #print "Hold at home run_experiment: " + str(holdHome) + cfg = {} #### Generate seed #### @@ -760,8 +945,22 @@ def run_experiment(participant, experiment = {}): configureWindow(cfg, experiment) + #print cfg.keys() cfg['psyMouse'] = event.Mouse(visible = False, newPos = None, win = cfg['win']) + #winSize = cfg['win'].size + + # PPC = max(winSize)/31. + # + # cfg['NSU'] = PPC * 8 + # + # arrowvertices = ((-.02,-.02),(0.82,-.02),(0.8,-.08),(1,0),(0.8,.08),(0.82,.02),(-.02,.02)) + # + # cfg['aim_arrow'] = ShapeStim(win=cfg['win'], lineWidth=cfg['NSU']*0.005, lineColorSpace='rgb', lineColor='#CC00CC', fillColorSpace='rgb', fillColor=None, vertices=arrowvertices, closeShape=True, size=PPC*7) + + #doAiming(cfg) + + class myMouse: def Pos(self): #print('PsychoPy mouse') @@ -1017,7 +1216,21 @@ def Pos(self): running[i]['target_distance'] = int(running[i]['max_distance']*running[i]['target_distance_ratio']) running[i]['time'] = core.getTime() + + #checking if we must do a pre-trail action, if yes: do it then proceed to do trial + #if (preTrialAction == True): + #if (isAim == True): + #doAiming(cfg,isAim) + #elif (isHold == True): + #hold_at_home_function(holdHome) + exp = trial_runner(running[i]) # but this runs a whole task, not a single trial, right? since we are going from the experiment runner straight to task... is the function misnamed or is the code not organized? + if (isAim == True): + #print cfg + #print "===============================================================" + exp['aim_value'] = aimValue + #print exp['aim_value'] + #timePos_dict['aim_value'] = cfg['aim_arrow'].ori if exp == 'escaped': running[i]['win'].close() @@ -1025,7 +1238,7 @@ def Pos(self): else: # is this where the data is saved? # would make more sense to me to do that in the trial function, as it is the trial data that is being stored? - df_exp = DataFrame(exp, columns=['task_num','task_name', 'trial_type', 'trial_num', 'terminalfeedback_bool','rotation_angle','targetangle_deg','targetdistance_percmax','homex_px','homey_px','targetx_px','targety_px', 'time_s', 'mousex_px', 'mousey_px', 'cursorx_px', 'cursory_px', 'accuracy_reward', 'accuracy_reward_bool']) + df_exp = DataFrame(exp, columns=['task_num','task_name', 'trial_type', 'trial_num', 'terminalfeedback_bool','rotation_angle','targetangle_deg','targetdistance_percmax','homex_px','homey_px','targetx_px','targety_px', 'time_s', 'mousex_px', 'mousey_px', 'cursorx_px', 'cursory_px', 'accuracy_reward', 'accuracy_reward_bool', 'pre_reach_aim', 'aim_value']) df_exp.to_csv(path_or_buf = path.join("data", settings['experiment_folder'], participant, running[i]['task_name'] + "_" + str(trial_num) + ".csv"), index=False) task_save = concat([task_save, df_exp]) end_exp = concat([end_exp, df_exp]) @@ -1340,7 +1553,7 @@ def Pos(self): running[i]['win'].close() return end_exp else: - df_exp = DataFrame(exp, columns=['task_num','task_name', 'trial_type', 'trial_num', 'terminalfeedback_bool','rotation_angle','targetangle_deg','targetdistance_percmax','homex_px','homey_px','targetx_px','targety_px', 'time_s', 'mousex_px', 'mousey_px', 'cursorx_px', 'cursory_px', 'accuracy_reward', 'accuracy_reward_bool']) + df_exp = DataFrame(exp, columns=['task_num','task_name', 'trial_type', 'trial_num', 'terminalfeedback_bool','rotation_angle','targetangle_deg','targetdistance_percmax','homex_px','homey_px','targetx_px','targety_px', 'time_s', 'mousex_px', 'mousey_px', 'cursorx_px', 'cursory_px', 'accuracy_reward', 'accuracy_reward_bool', 'pre_reach_aim']) df_exp.to_csv(path_or_buf = path.join("data", settings['experiment_folder'], participant, running[i]['task_name'] + "_" + str(trial_num) + ".csv"), index=False) end_exp = concat([end_exp, df_exp]) diff --git a/GUI.py b/GUI.py index fc45737..c9b667e 100644 --- a/GUI.py +++ b/GUI.py @@ -172,6 +172,11 @@ def __init__(self, *args, **kwds): self.score_check = wx.CheckBox(self, wx.ID_ANY, ("Accuracy Rewards")) self.score_settings_button = wx.Button(self, wx.ID_ANY, ("Settings")) + #Pre-Trial Action Settings + self.pre_trial= wx.CheckBox(self, wx.ID_ANY, ("Pre-Trial Action")) + self.pre_trial_settings_button = wx.Button(self, wx.ID_ANY, ("Settings")) + self.pre_trial_settings_button.Enable(self.pre_trial.IsChecked()) + # Participant stuff (column 6) self.participants_statictext = wx.StaticText(self, wx.ID_ANY, "Participants") self.participants_staticline = wx.StaticLine(self, wx.ID_ANY, style = wx.EXPAND) @@ -234,6 +239,10 @@ def __init__(self, *args, **kwds): # self.Bind(wx.EVT_LISTBOX, self.group_listbox_click, self.group_listbox) # end wxGlade + # Pre-Trial Action Events + self.Bind(wx.EVT_CHECKBOX, self.pre_trial_press, self.pre_trial) + self.Bind(wx.EVT_BUTTON, self.pre_trial_settings_press, self.pre_trial_settings_button) + def __set_properties(self): # begin wxGlade: MyFrame.__set_properties @@ -405,7 +414,11 @@ def __do_layout(self): sizer_10 = wx.BoxSizer(wx.VERTICAL) sizer_10.Add(self.radio_box_1, 0, wx.EXPAND | wx.ALL, 3) - sizer_10.Add(self.static_line_3, 0, wx.EXPAND | wx.ALL, 5) + #sizer_10.Add(self.static_line_3, 0, wx.EXPAND | wx.ALL, 5) -->EXTRA PART WE DO NOT WANT + #ADD PRE-TRIAL ACTION CHECK BUTTON HERE AND BUTTON NEXT TO IT + sizer_10.Add(self.pre_trial, 0, wx.LEFT, 0) + sizer_10.Add(self.pre_trial_settings_button, 0, 0, 0) + target_sizer = wx.BoxSizer(wx.VERTICAL) @@ -626,11 +639,20 @@ def regular_experiment_show(self): self.score_check.Enable(False) self.score_check.SetValue(False) self.score_settings_button.Enable(False) + + #Pre-Trial Action Settings + self.pre_trial.Enable(False) + self.pre_trial.SetValue(False) + self.pre_trial_settings_button.Disable() else: # Scoring System self.score_check.Enable(True) self.score_settings_button.Enable(self.score_check.GetValue()) + #Pre-Trial Action Settings + self.pre_trial.Enable(True) + self.pre_trial_settings_button.Disable() + #self.pause_static_text.Hide() #self.pause_txt.Hide() #self.PM_static_text.Hide() @@ -718,6 +740,10 @@ def pause_experiment_show(self): self.score_check.Enable(False) self.score_settings_button.Enable(False) + # Pre-Trial Action + self.pre_trial.Enable(False) + self.pre_trial_settings_button.Disable() + def list_box_dclick(self, event): @@ -795,6 +821,10 @@ def task_list_box_click(self, event): self.score_check.SetValue(self.current_experiment[self.highlit_task_num]['use_score']) self.score_settings_button.Enable(self.current_experiment[self.highlit_task_num]['use_score']) + # Pre-Trial Action + self.pre_trial.SetValue(self.current_experiment[self.highlit_task_num]['pre_trial_check']) + self.pre_trial_settings_button.Disable() + if (self.current_experiment[self.highlit_task_num]['rotation_change_type'] == 'gradual'): # self.MIN_TRIAL_BOOL = True self.Rotation_angle_statictext.SetLabel("Initial rotation") @@ -1064,6 +1094,12 @@ def Plus_Press(self, event): # wxGlade: MyFrame. self.current_experiment[self.highlit_task_num]['pause_instruction'] = "" self.current_experiment[self.highlit_task_num]['final_rotation_angle'] = 0 + # Pre-Trial Action + self.current_experiment[self.highlit_task_num]['pre_trial_check'] = False + self.current_experiment[self.highlit_task_num]['pre_reach_aim'] = False + self.current_experiment[self.highlit_task_num]['hold_on_home'] = False + + # Scoring System self.current_experiment[self.highlit_task_num]['use_score'] = False @@ -1102,6 +1138,9 @@ def Plus_Press(self, event): # wxGlade: MyFrame. self.score_check.SetValue(False) self.score_settings_button.Enable(False) + self.pre_trial.SetValue(False) + self.pre_trial_settings_button.Disable() + # with open(self.experiment_folder + self.current_experiment_name + ".json", "wb") as f: # dump(self.experiment_holder, f) # f.close() @@ -1483,6 +1522,19 @@ def score_settings_press(self, event): scoreframe = ScoreFrame(self, wx.ID_ANY, "") scoreframe.Show(True) event.Skip() + + # Pre-Trial Action Events + def pre_trial_press(self, event): + self.current_experiment[self.highlit_task_num]['pre_trial_check'] = event.IsChecked() + self.pre_trial_settings_button.Disable() + if (self.pre_trial.IsChecked()): + self.pre_trial_settings_button.Enable() + event.Skip() + + def pre_trial_settings_press(self, event): + pretrialframe = PreTrialFrame(self, wx.ID_ANY, "") + pretrialframe.Show(True) + event.Skip() # def rotation_angle_direction_press(self, event): # self.current_experiment[self.highlit_task_num]['rotation_angle_direction'] = event.GetString() # event.Skip() @@ -2684,6 +2736,75 @@ def onLowAccTargClr(self, event): event.GetColour().Get(includeAlpha=False) event.Skip() +class PreTrialFrame(wx.Frame): + def __init__(self, *args, **kwds): + + # begin wxGlade: scoreFrame.__init__ + kwds["style"] = kwds.get("style", 0) \ + | wx.CAPTION | wx.CLIP_CHILDREN | wx.MINIMIZE_BOX \ + | wx.CLOSE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU + wx.Frame.__init__(self, *args, **kwds) + self.SetSize((250, 250)) + + self.__set_properties() + self.__do_layout() + + def Option_Press(self, event): # wxGlade: MyFrame. + + chosen_option = event.GetString() + if (chosen_option == "Pre-Reach Aiming"): + print "Pre-Reach Aiming" + self.hold_home_statictext.Disable() + self.hold_home_slider.Disable() + self.Parent.current_experiment[self.Parent.highlit_task_num]['pre_reach_aim'] = True + self.Parent.current_experiment[self.Parent.highlit_task_num]['hold_on_home'] = False + # self.pre_trial_radio_box.SetValue(self.current_experiment[self.highlit_task_num]['pre_reach_aim']) + + + elif (chosen_option == "Hold At Home"): + print "Hold At Home" + self.hold_home_statictext.Enable() + self.hold_home_slider.Enable() + self.Parent.current_experiment[self.Parent.highlit_task_num]['hold_on_home'] = True + self.Parent.current_experiment[self.Parent.highlit_task_num]['pre_reach_aim'] = False + + def hold_home_choose(self, event): # wxGlade: MyFrame. + self.hold_home_chosen = exp.myRounder(event.GetInt(), 100) + if self.hold_home_chosen < self.hold_home_chosen: + self.hold_home_slider.SetValue(self.hold_home_chosen) + else: + self.hold_home_chosen = self.hold_home_chosen + self.hold_home_slider.SetValue(self.hold_home_chosen) + self.hold_home_slider.SetValue(self.hold_home_chosen) + self.Parent.current_experiment[self.Parent.highlit_task_num]['hold_home'] = self.hold_home_chosen + self.Parent.current_experiment[self.Parent.highlit_task_num]['hold_home'] = self.hold_home_chosen + event.Skip() + + def __set_properties(self): + self.SetTitle("Pre-Trial Action Settings") + self.pre_trial_radio_box = wx.RadioBox(self, wx.ID_ANY, ("Options"), choices=[("Pre-Reach Aiming"), ("Hold At Home")], majorDimension=1, style=wx.RA_SPECIFY_COLS) + self.Bind(wx.EVT_RADIOBOX, self.Option_Press, self.pre_trial_radio_box) + + self.hold_home_statictext = wx.StaticText(self, wx.ID_ANY, ("Time Holding Home (in ms)")) + self.hold_home_slider = wx.Slider(self, wx.ID_ANY, minValue = 50, maxValue = 10000, value = 50, style=wx.SL_HORIZONTAL | wx.SL_LABELS) + self.Bind(wx.EVT_SLIDER, self.hold_home_choose, self.hold_home_slider) + + self.hold_home_statictext.SetPosition((5,80)) + self.hold_home_slider.SetPosition((5,100)) + + + def __do_layout(self): + # begin wxGlade: scoreFrame.__do_layout + mainSizer = wx.BoxSizer(wx.HORIZONTAL) + secondSizer = wx.BoxSizer(wx.VERTICAL) + + mainSizer.Add(self.pre_trial_radio_box, 0, wx.EXPAND | wx.ALL, 3) + secondSizer.Add(self.hold_home_statictext, 0, wx.EXPAND | wx.ALL, 3) + secondSizer.Add(self.hold_home_slider, 0, wx.EXPAND, 3) + self.hold_home_statictext.Disable() + self.hold_home_slider.Disable() + + # end of class MyFrame class MyApp(wx.App): def OnInit(self):