""" Compound/alerting task with Theeuwes display Original Conditions: Colour Repeat x Flash Presence (2x2); Added Condition: Position repeat (6 possible positions; 1/6 change of Rep, 5/6 chance change) @author: nadja """ from psychopy import visual, event, core, misc, gui import random import AQ from datetime import datetime from ctypes import windll from win32api import EnumDisplayDevices, EnumDisplaySettings import os from resmailer import mailout_results class ExpError(Exception): ##define an error that we can raise (stole from Bert) def __init__(self, value): self.value=value def __str__(self): return self.value class ArrayItem(object): def __init__(self, Display, Trial, LineOrient, Colour, ShapeType, ItemCenter = [0,0]): super(ArrayItem,self).__init__() self.Trial = Trial self.Display = Display self.LineOrient = LineOrient self.Colour = Colour self.ShapeType = ShapeType self.ItemCenter = ItemCenter self.Colour = Colour if self.LineOrient == "Left": self.lineori = 135 elif self.LineOrient =="Right": self.lineori = 45 else: self.lineori = 90 self.Line = visual.ShapeStim( win = self.Display.Trial.Block.Experiment.win, #win = win, lineColor = [1,1,1], fillColor = None, fillColorSpace = 'rgb', lineColorSpace = 'rgb', lineWidth = self.Trial.Block.Experiment.convlineThicknessPix[0], ori = self.lineori, vertices = [(0, (self.Display.Trial.Block.Experiment.convLinePix[1]/2.0)),(0,(-self.Display.Trial.Block.Experiment.convLinePix[1]/2.0))], pos = self.ItemCenter, closeShape = False ) if self.ShapeType == "Circle": self.Shape = visual.Circle( win = self.Display.Trial.Block.Experiment.win, #win = win, lineColor = [0,0,0], fillColor = None, fillColorSpace = 'rgb', lineColorSpace = 'rgb', lineWidth = self.Trial.Block.Experiment.convlineThicknessPix[0], radius = (self.Display.Trial.Block.Experiment.convStimPix[0]/2.0), # grabbing x-value and div by 2 for circle radius pos = self.ItemCenter ) elif self.ShapeType == "Square": self.Shape = visual.Rect( win = self.Display.Trial.Block.Experiment.win, #win = win, lineColor = [0,0,0], fillColor = None, fillColorSpace = 'rgb', lineColorSpace = 'rgb', lineWidth = self.Trial.Block.Experiment.convlineThicknessPix[0], height = (self.Display.Trial.Block.Experiment.convStimPix[1]), # y-value from list width = (self.Display.Trial.Block.Experiment.convStimPix[0]), # x-value from list pos = self.ItemCenter) try: ## Change shape colour based on Colour property. if self.Colour == "Red": self.Shape.lineColor = [1,-1,-1] #self.Shape.fillColor = [1,-1,-1] elif self.Colour == "Green": #self.Shape.fillColor = [-1,-.09,-1] self.Shape.lineColor = [-1,-.09,-1] elif self.Colour == None: pass except: raise ExpError("Error in setting DiamondItem's self.Colour. Acceptable arguments: 'Red', 'Green', None.") def drawItem(self): self.Line.draw() self.Shape.draw() class Display(object): def __init__(self, Trial, SetSize = 6): super(Display,self).__init__() self.Trial = Trial self.SetSize = SetSize if self.Trial.TargColour == "Red": self.DistColour = "Green" elif self.Trial.TargColour == "Green": self.DistColour = "Red" else: self.DistColour = None def createDisplay(self): Num_Positions = int(self.SetSize) ## 6 Positions - 1 target, 5 distractors Position_List = range(Num_Positions) ##generates a list from which we will pick a multiplier for our angles Angles_List = [] ## will populate with list of all our angles that we will use to position the display array items Array_Radius = self.Trial.Block.Experiment.convDispRadPix[0]/1.0 #experiment.how_many_pixels() returns a list [x,y] with x pixels & y pixels - grabbing x for i in range(self.SetSize): angle = (i*360/self.SetSize) + (360/Num_Positions) Angles_List.append(angle) self.Display_Items_List = [] for i in range(len(Angles_List)): ## choose first iteration to be the Target: if i == self.Trial.TargPos: [surr_x, surr_y] = misc.pol2cart( Angles_List[i], Array_Radius ) target = ArrayItem(Display = self, Trial = self.Trial, LineOrient = self.Trial.TargOrient, Colour = self.Trial.TargColour, ShapeType = "Circle", ItemCenter = [surr_x, surr_y]) self.Display_Items_List.append(target) else: ## choose following iterations to be the distractors: [surr_x, surr_y] = misc.pol2cart( Angles_List[i], Array_Radius ) DistOrient = random.choice(["Right","Left"]) ## randomly choose the cut position for the distractor distractor = ArrayItem(Display = self, Trial = self.Trial, LineOrient = DistOrient, Colour = self.DistColour, ShapeType = "Circle", ItemCenter = [surr_x, surr_y]) self.Display_Items_List.append(distractor) def drawDisplay(self): for i in range(len(self.Display_Items_List)): self.Display_Items_List[i].drawItem() class Trial(object): TrialNo = 0 CrashOut = 0 def __init__(self,Experiment,Block,ColourRepeat,PositionRepeat,TargColour,TargOrient,TargPos,Flash): super(Trial, self).__init__() self.Experiment = Experiment self.Block = Block self.ColourRepeat = ColourRepeat self.PositionRepeat = PositionRepeat self.TargColour = TargColour self.TargOrient = TargOrient self.TargPos = TargPos self.Flash = Flash if self.TargColour == "Red": self.DistColour = "Green" elif self.TargColour == "Green": self.DistColour = "Red" self.FixCross = visual.ShapeStim( win = self.Block.Experiment.win, #win = win, vertices=([0,-(self.Block.Experiment.convFixPix[1]/2.0)], [0,(self.Block.Experiment.convFixPix[1]/2.0)], [0,0], [-(self.Block.Experiment.convFixPix[0]/2.0),0], [(self.Block.Experiment.convFixPix[0]/2.0),0]), units="pix", lineWidth = (self.Block.Experiment.convlineThicknessPix[0])/2.0, #want to make fix cross less prominent in relation to other stimuli closeShape=False, lineColor=[-1,-1,-1], ## black: N&M had white fixcross, but I chose black because our screen is flashing white autoDraw=False ) def createTrialDisplay(self): self.TrialDisplay = Display(Trial = self) self.TrialDisplay.createDisplay() def runTrial(self): Flash_Dur = 44/1000.0 ## want to approximate flash lasting 48 msec. To be safe from adding an extra frame, subtract from 48 half of the refresh rate (8.333 ms refresh rate on lab monitors) trial_jitter = random.randint(10,15)/10.0 # inter-trial interval ranges 1-1.5 ms self.FixCross.autoDraw = True self.FixCross.draw() self.timer = core.Clock() self.Block.Experiment.win.flip() self.timer.reset() core.wait(trial_jitter) self.Block.Experiment.win.flip() self.timer.reset() while self.timer.getTime() < (Flash_Dur): ##screen flashes to white for 48 ms if self.Flash == "Flash": self.Block.Experiment.win.color = [1,1,1] elif self.Flash == "NoFlash": self.Block.Experiment.win.color = [0,0,0] self.Block.Experiment.win.flip() self.Block.Experiment.win.color = [0,0,0] self.Block.Experiment.win.flip() self.Block.Experiment.win.flip() ##needed second flip command in code; included this 8.333 ms in timing consideration core.wait(37.5/1000.0) ## Display the stimuli & collect keypress: accepts 'm' or 'z' self.TrialDisplay.drawDisplay() self.Block.Experiment.win.flip() self.timer.reset() self.keys = event.waitKeys( maxWait = 5, keyList = ['m','z','escape'], timeStamped = self.timer ) self.FixCross.autoDraw = False def Response(self): if self.keys: KeyPress = self.keys[0][0] ## to get the actual key pressed: index 0 of self.keys tuple else: KeyPress = "None" return KeyPress def Acc(self): ## Don't risk Simon's effect: correct "left" keypress always z; correct "right" keypress m if self.keys: if self.TargOrient == "Left": if self.keys[0][0] == "z": return 1 else: return 0 elif self.TargOrient == "Right": if self.keys[0][0] == "m": return 1 else: return 0 else: return None def RT(self): if self.keys: ReacTime = self.keys[0][1] else: ReacTime = "None" return ReacTime def __str__(self): ## str function to write to the datafile; Add 1 to both blockNumber and TrialNo, because these start at 0 return str(self.Block.Experiment.screenWidth) +","+\ str(self.Block.Experiment.screenHeight) +","+\ str(self.Block.Experiment.dpi_h) +","+\ str(self.Block.Experiment.dpi_v) +","+\ str(self.Block.Experiment.mm_h) +","+\ str(self.Block.Experiment.mm_v) +","+\ str(self.Block.Experiment.hz) +","+\ str(self.Block.Experiment.refresh) +","+\ str(self.Block.Experiment.Age) +"," +\ str(self.Block.Experiment.Gender)+ ","+\ str(self.Block.Experiment.Hand)+","+\ str(self.Block.Experiment.AQ.AQScore)+","+\ str(self.Block.Experiment.ColourBlindness)+"," +\ str(self.Block.blockNumber()+1)+ ","+\ str(self.TrialNo+1)+","+\ str(self.ColourRepeat) + ","+\ str(self.PositionRepeat)+","+\ str(self.Flash)+","+ \ str(self.TargColour)+","+\ str(self.TargOrient)+","+\ str(self.TargPos)+","+\ str(self.Response()) + ","+ \ str(self.Acc())+","+ \ str(self.RT()) def SpecsHeader(self): ## for first line of participant datafile return "screenWidth,screenHeight,dpi_h,dpi_v,mm_h,mm_v,hz,refresh" def DataHeader(self): ## for first line of participant datafile return "Age,Gender,Hand,AQ_Score,R/G_Colourblindness,BlockNo,TrialNo,ColourRepeat,PositionRepeat,Flash,TargColour,TargetOrient,TargPos,Response,Acc,RT" def saveTrial(self): ## saves the trial's DataHeader() and __str__() if self.Block.blockNumber() == 0 and self.TrialNo == 0: self.Block.Experiment.DataFile.write(self.SpecsHeader() +"," + self.DataHeader()+'\n') ## this is the first trial of the first block: write the dataheader to the outputfile self.Block.Experiment.DataFile.write(self.__str__()+'\n') ## write the trial's data to the file: class Block(object): def __init__(self, Experiment,isPracBlock = False): super(Block, self).__init__() self.Experiment = Experiment self.isPracBlock = isPracBlock self.Trial_Types = [] self.Trials = [] self.Trials_toSave = [] if self.isPracBlock == False: self.TrialsPerBlock = self.Experiment.TrialsPerBlock else: self.TrialsPerBlock = self.Experiment.NumPracTrials ## ---------------------------------------------------------- Conditions Info ----------------------------------------------------- ## ##Trial Type Codes: 2 x 2 x 2 x 6 = 48 ## 1) ColourRepeat = self.Trial_Types[0] ## 2) Flash = self.Trial_Types[1] ## 3) TargOrient = self.Trial_Types[2] ## 4) PositionRepeat = self.Trial_Types[3] ## Of importance to compare with earlier PoPO Experiments is ColourRepeat x Flash (then pos repeat) = 2x2(x6) = 4(24) ## TOTAL in-house conditions: 48 ## TOTAL Exp conditions = 24 (2x2x6) ## ---------------------------------------------------------------- end ----------------------------------------------------------- ## def buildTrialTypes(self): ## Populates self.Trial_Types list ##List of lists: 48 types of trials Types_List = [["NoColRepeat","NoFlash","Left","PosRepeat"],["NoColRepeat","NoFlash","Left","NoPosRepeat"],["NoColRepeat","NoFlash","Left","NoPosRepeat"],["NoColRepeat","NoFlash","Left","NoPosRepeat"],["NoColRepeat","NoFlash","Left","NoPosRepeat"],["NoColRepeat","NoFlash","Left","NoPosRepeat"],\ ["NoColRepeat","NoFlash","Right","PosRepeat"],["NoColRepeat","NoFlash","Right","NoPosRepeat"],["NoColRepeat","NoFlash","Right","NoPosRepeat"],["NoColRepeat","NoFlash","Right","NoPosRepeat"],["NoColRepeat","NoFlash","Right","NoPosRepeat"],["NoColRepeat","NoFlash","Right","NoPosRepeat"],\ ["NoColRepeat","Flash","Left","PosRepeat"],["NoColRepeat","Flash","Left","NoPosRepeat"],["NoColRepeat","Flash","Left","NoPosRepeat"],["NoColRepeat","Flash","Left","NoPosRepeat"],["NoColRepeat","Flash","Left","NoPosRepeat"],["NoColRepeat","Flash","Left","NoPosRepeat"],\ ["NoColRepeat","Flash","Right","PosRepeat"],["NoColRepeat","Flash","Right","NoPosRepeat"],["NoColRepeat","Flash","Right","NoPosRepeat"],["NoColRepeat","Flash","Right","NoPosRepeat"],["NoColRepeat","Flash","Right","NoPosRepeat"],["NoColRepeat","Flash","Right","NoPosRepeat"],\ ["ColRepeat","NoFlash","Left","PosRepeat"],["ColRepeat","NoFlash","Left","NoPosRepeat"],["ColRepeat","NoFlash","Left","NoPosRepeat"],["ColRepeat","NoFlash","Left","NoPosRepeat"],["ColRepeat","NoFlash","Left","NoPosRepeat"],["ColRepeat","NoFlash","Left","NoPosRepeat"],\ ["ColRepeat","NoFlash","Right","PosRepeat"],["ColRepeat","NoFlash","Right","NoPosRepeat"],["ColRepeat","NoFlash","Right","NoPosRepeat"],["ColRepeat","NoFlash","Right","NoPosRepeat"],["ColRepeat","NoFlash","Right","NoPosRepeat"],["ColRepeat","NoFlash","Right","NoPosRepeat"],\ ["ColRepeat","Flash","Left","PosRepeat"],["ColRepeat","Flash","Left","NoPosRepeat"],["ColRepeat","Flash","Left","NoPosRepeat"],["ColRepeat","Flash","Left","NoPosRepeat"],["ColRepeat","Flash","Left","NoPosRepeat"],["ColRepeat","Flash","Left","NoPosRepeat"],\ ["ColRepeat","Flash","Right","PosRepeat"],["ColRepeat","Flash","Right","NoPosRepeat"],["ColRepeat","Flash","Right","NoPosRepeat"],["ColRepeat","Flash","Right","NoPosRepeat"],["ColRepeat","Flash","Right","NoPosRepeat"],["ColRepeat","Flash","Right","NoPosRepeat"]] if self.isPracBlock == False: TrialsPerType = int(self.TrialsPerBlock/(len(Types_List))) ##96 Trials per block / 48 Trial types = 2 trials per typ for i in range(TrialsPerType): for j in range(len(Types_List)): self.Trial_Types.append(Types_List[j]) random.shuffle(self.Trial_Types) def checkColours(self): ## Method to check the levels of colours in each block. Uses self.Trial_Types list checklist = [] for i in range(len(self.Trial_Types)): if i == 0: chk = "red" ## assign first colour as red, just to check ratio of colours (will not necessarily be actual colour assigned to that particular trial) checklist.append(chk) else: try: if self.Trial_Types[i][0] == "ColRepeat": ## if a repeat trial, assign chk colour to chk-1 colour chk = checklist[i-1] checklist.append(chk) elif self.Trial_Types[i][0] == "NoColRepeat": ## if a no-repeat trial, assign chk to opposite of chk-1 if checklist[i-1] == "red": chk = "green" elif checklist[i-1] == "green": chk = "red" checklist.append(chk) except: raise ExpError("Error in Block.checkcolours(). Could not assign colours based on Trial Types.") NumReds = 0 NumGreens = 0 for j in range(len(checklist)): if checklist[j] == "red": NumReds += 1 elif checklist[j] == "green": NumGreens+= 1 Colour_CheckList = [NumReds, NumGreens] return Colour_CheckList def balanceColours(self): ## Method to balance the relative colours obtained from checkColours(). NumLoops = 200 for i in range(NumLoops): colours = self.checkColours() if colours[0] > ((self.TrialsPerBlock)*.46) and colours[0] < ((self.TrialsPerBlock)*.54): #if colours[0] > ((self.TrialsPerBlock)*.4) and colours[0] < ((self.TrialsPerBlock)*.6): ## for testing pass else: random.shuffle(self.Trial_Types) num_colour1 = colours[0] num_colour2 = (self.TrialsPerBlock - colours[0]) Ratio_str = "colour1 = " + str(num_colour1) +"; colour2 = "+ str(num_colour2) ## Will only return the ratio, not which colours (but can be figured out by looking at first colour in a block). Also note: catch trials not included. return Ratio_str def setTrials(self): ## Method to create trial objects based on their trial type. Certain parameters (colour) to be set in setColours() method if self.isPracBlock == True: for n in range(self.TrialsPerBlock): colours = ["Red","Green"] flash = ["Flash","NoFlash"] positions = [0,1,2,3,4,5] oris = ["Left","Right"] random.shuffle(colours) random.shuffle(positions) random.shuffle(flash) random.shuffle(oris) prac = Trial( Experiment = self.Experiment, Block = self, ColourRepeat = None, TargColour = colours[0], TargPos = positions[0], Flash = flash[0], TargOrient = oris[0], PositionRepeat = None) self.Trials.append(prac) else: for i in range(len(self.Trial_Types)): if i == 0: ## First Trial: not a repeat or a no-repeat. Set TargColour randomly. colours = ["Red","Green"] positions = [0,1,2,3,4,5] random.shuffle(colours) random.shuffle(positions) self.Trials.append(Trial( Experiment = self.Experiment, Block = self, ColourRepeat = None, TargColour = colours[0], TargPos = positions[0], Flash = self.Trial_Types[i][1], TargOrient = self.Trial_Types[i][2], PositionRepeat = None)) else: ## Remaining trials: Create trials, leaving the colours to be set in block's setColours() method. self.Trials.append(Trial( Experiment = self.Experiment, Block = self, ColourRepeat = self.Trial_Types[i][0], TargColour = None, TargPos = None, Flash = self.Trial_Types[i][1], TargOrient = self.Trial_Types[i][2], PositionRepeat = self.Trial_Types[i][3])) def setColours(self): for i in range(len(self.Trials)): if i == 0: ## First trial in block, so we pass (we 'flip a coin' to choose the first colour in setTrials() pass else: prevTC = self.Trials[i-1].TargColour if self.Trials[i].ColourRepeat == "ColRepeat": self.Trials[i].TargColour = prevTC elif self.Trials[i].ColourRepeat == "NoColRepeat": if prevTC == "Red": self.Trials[i].TargColour = "Green" elif prevTC == "Green": self.Trials[i].TargColour = "Red" def setPositions(self): ## Gets the position of the Target in the previous Trial, and based on whether it is a Repeat or a NoRepeat trial, it sets the current Target pos accordingly for i in range(len(self.Trials)): if i == 0: pass else: prevPos = self.Trials[i-1].TargPos if self.Trials[i].PositionRepeat == "PosRepeat": self.Trials[i].TargPos = prevPos elif self.Trials[i].PositionRepeat == "NoPosRepeat": poslist = [0,1,2,3,4,5] random.shuffle(poslist) for p in range(len(poslist)): if poslist[p] == prevPos: if p < (len(poslist) - 1): randpos = poslist[p+1] else: randpos = poslist[p-1] elif poslist[p]!= prevPos: randpos = poslist[p] else: randpos = random.choice(poslist) # just to force it self.Trials[i].TargPos = randpos def createDisplays(self): ## Create the display during the Block's init AFTER you have created your trial objects, by calling the trial's createTrialDisplay() method for t in self.Trials: t.createTrialDisplay() def runTrials(self): trialcounter = 0 for t in self.Trials: t.TrialNo = trialcounter t.runTrial() if self.isPracBlock == False: self.Trials_toSave.append(t) trialcounter+=1 if t.keys and t.keys[0][0] == 'escape': t.CrashOut = 1 self.Experiment.drawDebrief() self.Experiment.win.flip() self.saveTrials() event.waitKeys() self.Experiment.cleanup() if self.isPracBlock == False: self.saveTrials() self.Experiment.win.flip() def saveTrials(self): for t in self.Trials_toSave: if t.CrashOut == 0: t.saveTrial() def blockNumber(self): ## find this block in parent experiment's collection: try: idx=self.Experiment.Blocks.index(self) ## built in pythod method: list.index() return idx except: raise ExpError('Block blockNumber() method: unable to index block object') def initBlock(self): if self.isPracBlock == False: self.buildTrialTypes() self.balanceColours() self.setTrials() self.setColours() self.setPositions() self.createDisplays() elif self.isPracBlock == True: self.buildTrialTypes() self.setTrials() self.createDisplays() class Experiment(object): def __init__(self, NumBlocks, TrialsPerBlock, NumPracTrials, StimDeg, LineDeg, DispRadDeg, FixDeg, lineThicknessDeg, saveAQfile = True, screen = 0, datafile = ""): super(Experiment, self).__init__() self.NumBlocks = NumBlocks self.TrialsPerBlock = TrialsPerBlock self.NumPracTrials = NumPracTrials self.StimDeg = StimDeg self.DispRadDeg = DispRadDeg self.LineDeg = LineDeg self.FixDeg = FixDeg self.lineThicknessDeg = lineThicknessDeg self.saveAQfile = saveAQfile # must be true or false (or 1 or 0 I guess) self.Blocks = [] ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CONFIGS for REMOTE RESEARCH ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~## ##get specs ##gets refresh device = EnumDisplayDevices() # returns PyDISPLAY_DEVICE object settings = EnumDisplaySettings(device.DeviceName, -1) # returns PyDEVMODEA object, whatever that is self.hz=getattr(settings, 'DisplayFrequency') # hz = 59 (integer, I think) self.refresh=round(1000)/self.hz # gives duration of refresh cycle; refresh = 16.9491525424 ## gets resolution User32 = windll.user32 User32.SetProcessDPIAware() # improves resolution accuracy, important self.screenWidth = User32.GetSystemMetrics(0) self.screenHeight = User32.GetSystemMetrics(1) dc = windll.user32.GetDC(0) ## should give the display context so that the following code (GetDeviceCaps()) can access specs of a device (in this case, the display monitor) DC: device context. https://stackoverflow.com/questions/2777793/understanding-device-contexts ##-------------------------------------------------------------------------------------------------------------------------------------------------------- ## Regarding GetDeviceCaps(): https://stackoverflow.com/questions/645352/is-the-number-of-pixels-per-inch-standard-on-all-windows-pc-displays-logpixels/645503#645503?newreg=e8065e64a18841d4aaa31bb0c72dbf84 ## "LOGPIXELSX is the parameter you pass to GetDeviceCaps to get the current monitor resolution (technically the horizontal resolution, ## but all modern displays have equal horizontal and vertical resolution). Yes, it is always 88 - if you wanted to get a different value ## from GetDeviceCaps, you'd pass in a different value. For example, to get the number of bits per pixel, you'd pass the BITSPIXEL constant ## which is 12. These magic constants are defined in the Windows API file WINGDI.h." ##-------------------------------------------------------------------------------------------------------------------------------------------------------- ## gets dpi - gives the dots per inch of the display monitor. ## GetDeviceCaps(hdc,Index): Retrieves device-specific parameters and settings. Parameters: hdc (PyHANDLE;Handle to a printer or display device context), Index (int; The capability to return. See MSDN for valid values. Can also be used for Display DCs in addition to printer DCs) LOGPIXELSX = 88 LOGPIXELSY = 90 self.dpi_h = windll.gdi32.GetDeviceCaps(dc, LOGPIXELSX) ## returns 144 on my laptop self.dpi_v = windll.gdi32.GetDeviceCaps(dc, LOGPIXELSY) ## returns 144 on my laptop # note: dpi_h & dpi_v not used in elsewhere code, other than in specsList & writing to file ## gets screensize - gets the screen size (height and width dimensions) in millimeters. ## replace LOGPIXELSX and LOGPIXELSY with the following for mm (size), else all code same as above SCREENSIZEX = 4 SCREENSIZEY = 6 self.mm_h = windll.gdi32.GetDeviceCaps(dc, SCREENSIZEX) ## returns 294 on my laptop self.mm_v = windll.gdi32.GetDeviceCaps(dc, SCREENSIZEY) ## returns 165 on my laptop User32.ReleaseDC(0, dc) self.specsList = [self.screenWidth, self.screenHeight, self.dpi_h, self.dpi_v, self.mm_h, self.mm_v, self.hz, self.refresh] #used for writing to outputfile randPPID = random.randint(9999,99999) #will generate a random PPID for the datafile; will be combined with timestamp for full participant code/datafile name ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ End for REMOTE CONFIGS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~## expGUI = gui.Dlg(title="Experiment", screen = 0) expGUI.addField("ParticipantID:" , randPPID) expGUI.addField("Session:", "1") configinfo = expGUI.show() if expGUI.OK: self.ParticipantID = configinfo[0] self.SessionNo = configinfo[1] else: print ("Experiment.init(): Cancelled by user") core.quit() demoGUI = gui.Dlg(title = "Demographic Information", screen = 0) demoGUI.addField("Age:", "0") demoGUI.addField("Gender:", choices = ["Female", "Male", "Other"]) demoGUI.addField("Dominant Hand:", choices = ["Right", "Left"]) demoGUI.addField("Red/Green Colourblindness?:", choices = ["No", "Yes","Not sure"]) demoinfo = demoGUI.show() if demoGUI.OK: self.Age = demoinfo[0] self.Gender = demoinfo[1] self.Hand = demoinfo[2] self.ColourBlindness = demoinfo[3] self.AQ = AQ.AQ_GUI(Experiment = self,saveDatafile = self.saveAQfile) self.AQPATH = self.AQ.runAQ() #from Jack, added Nov 23 if datafile == "": ## If nothing passed as datafile argument, then a new .csv file will be opened to write to d = datetime.now() filename = str(d.year)+str(d.month)+str(d.day)+str(d.hour)+str(d.minute)+"_"+"s"+str(self.ParticipantID)+"_"+str(self.SessionNo)+".csv" ## fn = 20181112188_s999_1.csv self.RESPATH = filename #from Jack, added Nov 23 self.DataFile = open(filename, 'w') else: ## If filename is passed to datafile argument, then the file will be opened to append to self.DataFile = open(datafile, 'a') self.RESPATH = datafile #not from Jack but for the code he made, added Nov 23 self.win = visual.Window( size = [User32.GetSystemMetrics(0), User32.GetSystemMetrics(1)], ## How to fit to all screens. from win32api import GetSystemMetrics ; width = GetSystemMetics(0), height = GetSystemMetrics(1) units = 'pix', fullscr = True, color = [0,0,0], ## grey allowGUI = False, ## hide mouse waitBlanking = True ) ## obtain all arguments from code above *but* deg_h & deg_v = these are to be specified for the stimuli. Passed as argument to exp object. def how_many_pixels(self, deg_h, deg_v): ## passed args to exp: screenWidth=self.screenWidth, screenHeight= self.screenHeight, mm_h = self.mm_h, mm_v = self.mm_v cm_h=float(self.mm_h)/10 cm_v=float(self.mm_v)/10 x = float(self.screenWidth)/cm_h y = float(self.screenHeight)/cm_v x2 = round(float(x)*deg_h) y2 = round(float(y)*deg_v) pixels = [x2, y2] return pixels def configRemoteStimuli(self): self.convFixPix = self.how_many_pixels(deg_h = self.FixDeg, deg_v = self.FixDeg) self.convStimPix = self.how_many_pixels(deg_h = self.StimDeg, deg_v = self.StimDeg) self.convDispRadPix = self.how_many_pixels(deg_h = self.DispRadDeg, deg_v = self.DispRadDeg) self.convLinePix = self.how_many_pixels(deg_h = self.LineDeg, deg_v = self.LineDeg) self.convlineThicknessPix = self.how_many_pixels(deg_h = self.lineThicknessDeg, deg_v = self.lineThicknessDeg) def drawInstructions_1(self): try:## Create and draw Instructions screen from file. File used to require to be in same folder as program; now adding an assets folder into the path. self.Instructions_1Slide = visual.ImageStim( win = self.win, #image = "Instructions.bmp" image = os.path.join("assets", "Instructions_1.bmp") # from Jack's code, added Nov 23 ) except IOError: ## Display error text instead of 'Instructions.bmp' if instructions file not found self.Instructions_1Slide = visual.TextStim( win = self.win, color = (1,1,1), text = "Oops! Unable to display Instructions. Press space to continue", font = "Calibri", height = 40) self.Instructions_1Slide.draw() def drawInstructions_2(self): try:## Create and draw Instructions screen from file. File used to require to be in same folder as program; now adding an assets folder into the path. self.Instructions_2Slide = visual.ImageStim( win = self.win, #image = "Instructions.bmp" image = os.path.join("assets", "Instructions_2.bmp") # from Jack's code, added Nov 23 ) except IOError: ## Display error text instead of 'Instructions.bmp' if instructions file not found self.Instructions_2Slide = visual.TextStim( win = self.win, color = (1,1,1), text = "Oops! Unable to display Instructions. Press space to continue", font = "Calibri", height = 40) self.Instructions_2Slide.draw() def drawBlockScreen(self, BlockNo): ## Create BlockScreen ("Block # / press space to continue") in between each block. self.BlockScreen = visual.TextStim( win = self.win, color = (1,1,1), pos = [0, 30], text = "Block " + str(BlockNo + 1), font = "Calibri", height = 40) self.BlockScreen.draw() def drawDebrief(self): try: ## Create debrief slide imagestim object self.DebriefSlide = visual.ImageStim( win = self.win, #image = "Debrief.bmp" image = os.path.join("asests","Debrief.bmp") # from Jack's code, added Nov 23 ) except IOError: ## Display textstim error message instead of 'Debrief.bmp' if file not found self.DebriefSlide = visual.TextStim( win = self.win, color = (1,1,1), text = "Oops! Unable to display Debrief. Press spacebar to exit", font = "Calibri", height = 40) self.DebriefSlide.draw() def drawPressSpace(self): self.pressSpace = visual.TextStim( win = self.win, color = (1,1,1), pos = [0, -30], text = "(press the spacebar to continue)", font = "Calibri", height = 25) self.pressSpace.draw() def drawPracStartScreen(self): self.PracStartScreen = visual.TextStim( win = self.win, color = (1,1,1), pos = [0, 30], text = "About to start the practice trials.", font = "Calibri", height = 30) self.pressSpaceBegin = visual.TextStim( win = self.win, color = (1,1,1), pos = [0, -30], text = "(press the spacebar to begin)", font = "Calibri", height = 25) self.PracStartScreen.draw() self.pressSpaceBegin.draw() def drawPracEndScreen(self): self.PracEndScreen = visual.TextStim( win = self.win, color = (1,1,1), pos = [0, 30], text = "That's the end of the practice trials. Ready to begin the experiment?", font = "Calibri", height = 30) self.pressSpaceExp = visual.TextStim( win = self.win, color = (1,1,1), pos = [0, -40], text = "(press the spacebar to start the experiment)", font = "Calibri", height = 25) self.PracEndScreen.draw() self.pressSpaceExp.draw() def createPracBlock(self): self.PracBlock = Block(Experiment = self, isPracBlock = True) def runPracBlock(self): self.PracBlock.initBlock() self.PracBlock.runTrials() def createBlocks(self): for i in range(self.NumBlocks): self.Blocks.append(Block(Experiment = self)) random.shuffle(self.Blocks) def run(self): self.drawInstructions_1() self.win.flip() event.waitKeys(keyList =['space']) self.drawInstructions_2() self.win.flip() self.configRemoteStimuli() self.createPracBlock() self.createBlocks() event.waitKeys(keyList =['space']) self.drawPracStartScreen() self.win.flip() event.waitKeys(keyList =['space']) self.win.flip() self.runPracBlock() self.drawPracEndScreen() self.win.flip() event.waitKeys(keyList =['space']) self.win.flip() self.runAllBlocks() self.drawDebrief() self.win.flip() event.waitKeys() def runAllBlocks(self): for b in range(len(self.Blocks)): self.drawBlockScreen(BlockNo = b) ## draws screen that displays block number & asks for keypress (spacebar) to continue self.win.flip() self.Blocks[b].initBlock() ## initialize the block while BlockScreen is being displayed self.drawBlockScreen(BlockNo = b) self.drawPressSpace() self.win.flip() keypress = event.waitKeys(keyList = ['space','escape']) ## If need to exit out of experiment, press esc key during block break. Will take you to debrief screen. if keypress == ["escape"]: self.drawDebrief() self.win.flip() event.waitKeys() self.cleanup() else: self.win.flip() self.runBlock(BlockNo=b) ##run a single block of trials def runBlock(self, BlockNo=0): ## Run one block of trials self.Blocks[BlockNo].runTrials() def cleanup(self): ## closes the datafile and the window; DO NOT FORGET TO CALL AT END OF EXPERIMENT self.DataFile.close() self.win.close() mailout_results((exp.RESPATH,), dest="njankovi@sfu.ca") # Need to have comma after single "thing" in tuple or else won't be indexable.... but doesn't seem to be working rn # print("datafile mailed out") ## ~~*~~*~~*~~*~~*~~~~*~~*~~*~~*~~**~~*~~*~~*~~*~~*~~*~~*~~*~~* RUN EXPERIMENT: ~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~*~~* ## ## Number of conditions: ColourRepeat x Flash (xPositionRepeat) = 2x2(x6) = 4(24) ## Trials per condition: 192(32) ## TOTAL in-house conditions: 48 ## TOTAL Exp conditions = 4 original 2x2; 24 with position repetition (x6)) ## Stimulus parameters chosen to relfect that of Theeuwes 1992: Theeuwes, J. (1992). Perceptual selectivity for color and form. Perception & psychophysics, 51(6), 599-606. ## Display radius: 3.4 deg of visual angle ## Display elements (i.e. Stim Deg) = 1.4 deg diameter (or 1.4 deg size) - note, diameter, not radius. ## Line segments within display elements: 0.5 degrees ## Fixation cross (had "dot") but their dot was .3 deg exp = Experiment(NumBlocks = 8, TrialsPerBlock = 96, NumPracTrials = 10, saveAQfile = False, StimDeg = 1.4, LineDeg = .5, DispRadDeg = 3.4, FixDeg = .3, lineThicknessDeg = .05) exp.run() exp.cleanup() #print("Exp cleaned up")