Skip to main content

Data Sets for Alerting Effects Occur in Simple - but not Compound - Visual Search Tasks - Jankovic_E4_compalertREORIremote_Py3.txt

Date created
2021-06-06
Authors/Contributors
restclientutil…
Edited Text
# -*- coding: utf-8 -*-
"""
COMPALERT_REORI_remote
Created on Sun Mar 28 21:38:38 2021

Follow up to COMP_ALERT2_remote: added a reorienting cue (white box outline) between displays
(Testing "simplification of compound task" hypothesis)
@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()
#win.flip()


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
)

self.ReoriBox = visual.Rect(
win = self.Block.Experiment.win,
#win = win,
lineColor = [1,1,1], #white
fillColor = None,
fillColorSpace = 'rgb',
lineColorSpace = 'rgb',
lineWidth = self.Block.Experiment.convlineThicknessPix[0],
height = (self.Block.Experiment.convReoriStimPix[1]), # y-value from list
width = (self.Block.Experiment.convReoriStimPix[0]), # x-value from list
pos = (0,0)
)

self.ReoriDuration = 100/1000.0 # 100 ms
self.ReoriISI = 500 / 1000.0 # 500 ms SOA between reorienting box and alerting flash

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
trial_jitter = random.randint(10,15)/10.0
reoribox_jitter = trial_jitter - (self.ReoriISI + self.ReoriDuration) # time between the start of the trial and onset of the reorientation box
self.FixCross.autoDraw = True
self.FixCross.draw()
self.timer = core.Clock()
self.Block.Experiment.win.flip()
self.timer.reset()
core.wait(reoribox_jitter)
self.ReoriBox.draw()
self.Block.Experiment.win.flip()
core.wait(self.ReoriDuration)
self.Block.Experiment.win.flip()
core.wait(self.ReoriISI)

self.timer.reset()
while self.timer.getTime() < (Flash_Dur): ##screen flashes to white for ~50 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, ReoriStimDeg, 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.ReoriStimDeg = ReoriStimDeg
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 in 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)
self.convReoriStimPix = self.how_many_pixels(deg_h = self.ReoriStimDeg, deg_v = self.ReoriStimDeg)

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("assets","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, ReoriStimDeg = 1)
exp.run()
exp.cleanup()

#print("Exp cleaned up")

Views & downloads - as of June 2023

Views: 2
Downloads: 0