Sample 02: Using units (for PsychoPy)

What does this sample do?

This sample shows how to use 'units' option when using PsychoPy. Codes are the same as those of Sample 01: Controlling SimpleGazeTracker from GazeParser.TrackingTools except units.

Note that units of gaze positions are always 'pix' in the SimpleGazeTracker datafile. In the sample script, calibration target locations are defined as following.

calTargetPos = [[   0,   0],
                [-0.6,-0.6],[-0.6,  0],[-0.6,0.6],
                [   0,-0.6],[   0,  0],[   0,0.6],
                [ 0.6,-0.6],[ 0.6,  0],[ 0.6,0.6]]

tracker.setCalibrationScreen(win)
tracker.setCalibrationTargetPositions(calarea, calTargetPos,units='norm')

setCalibrationTargetPositions(). translates calibation target positions into 'pix' before sending them to the SimpleGazeTracker. As a result, calibration positions are recorded in 'pix' in the SimpleGazeTracker data file as following.:

#CALPOINT,-307.000000,0.000000
#CALPOINT,-307.000000,-230.000000
#CALPOINT,307.000000,230.000000
#CALPOINT,0.000000,-230.000000
#CALPOINT,-307.000000,230.000000
#CALPOINT,307.000000,0.000000
#CALPOINT,307.000000,-230.000000
#CALPOINT,0.000000,0.000000
#CALPOINT,0.000000,230.000000

Comparing these outputs with the script, 0.6 'norm' corresponds to 307 'pix' in horizontal direction and 230 'pix' in vertical direction.

In the SimpleGazeTracker data file, units of recorded gaze positions are also 'pix'.:

2.525,-4.1,-19.3
19.093,-3.9,-18.8
35.692,-3.9,-22.3
52.301,-4.7,-14.0
68.924,0.1,-18.8
85.546,-0.6,-15.0
102.175,2.1,-25.4
118.800,2.1,-17.4
135.421,-0.7,-21.9
152.057,-3.0,-14.7
168.669,-1.9,-22.7
185.294,-4.3,-30.5
201.919,-8.0,-22.7
218.542,-4.9,-23.5
235.170,-5.3,-21.2
251.680,-1.2,-21.8
268.296,-9.0,-15.2

getEyePosition() receives gaze position in 'pix' and converts to desirable units. In this sample, units of gaze positions are converted to 'norm' and are output to the local log file.:

trial1
getSpatialError: 0.1786,0.0234,-0.1771
SentAt,ReceivedAt,Lag,TargetX,TargetY,EyeX,EyeY
0.0,0.6,0.5,0.0000,0.0000,-0.0020,-0.0391
14.0,14.3,0.3,0.0000,0.0000,0.0039,-0.0651
18.3,18.6,0.3,0.0000,0.0000,0.0039,-0.0651
33.6,33.9,0.3,0.0000,0.0000,0.0039,-0.0443
50.3,50.6,0.3,0.0000,0.0000,-0.0020,-0.0573
66.9,67.3,0.3,0.0000,0.0000,-0.0059,-0.0391
83.7,84.0,0.3,0.0000,0.0000,-0.0039,-0.0599
100.3,100.7,0.3,0.0000,0.0000,-0.0078,-0.0781
117.0,117.3,0.3,0.0000,0.0000,-0.0156,-0.0599
133.7,134.0,0.3,0.0000,0.0000,-0.0098,-0.0599
150.4,150.7,0.3,0.0000,0.0000,-0.0098,-0.0547
167.1,167.4,0.3,0.0000,0.0000,-0.0020,-0.0573
183.7,184.1,0.3,0.0000,0.0000,-0.0176,-0.0391
200.4,200.8,0.3,0.0000,0.0000,-0.0176,-0.0521
217.1,217.4,0.3,0.0000,0.0000,0.0000,-0.0469
233.8,234.1,0.3,0.0000,0.0000,-0.0254,-0.0651
250.5,250.8,0.3,0.0000,0.0000,-0.0156,-0.0521

Codes (PsychoPy)

Lines modified from sample01_PsychoPy.py are highlighted.

import psychopy.visual
import psychopy.event
import psychopy.core
import sys
import random

import GazeParser.TrackingTools

import wx

class FileWindow(wx.Frame):
    def __init__(self,parent,id,title):
        wx.Frame.__init__(self,parent,id,title)

        panel = wx.Panel(self,wx.ID_ANY)

        vbox = wx.BoxSizer(wx.VERTICAL)

        filenameBox = wx.BoxSizer(wx.HORIZONTAL)
        filenameBox.Add(wx.StaticText(panel,wx.ID_ANY,'Datafile name',size=(160,30)),0)
        self.filenameEdit = wx.TextCtrl(panel,wx.ID_ANY)
        filenameBox.Add(self.filenameEdit,1)
        filenameBox.Add(wx.StaticText(panel,wx.ID_ANY,'.csv'),0)
        vbox.Add(filenameBox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)

        addressBox = wx.BoxSizer(wx.HORIZONTAL)
        addressBox.Add(wx.StaticText(panel,wx.ID_ANY,'SimpleGazeTracker address',size=(160,30)),0)
        self.addressEdit = wx.TextCtrl(panel,wx.ID_ANY)
        self.addressEdit.SetValue('192.168.1.1')
        addressBox.Add(self.addressEdit,1)
        vbox.Add(addressBox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)

        imgsizeBox = wx.BoxSizer(wx.HORIZONTAL)
        imgsizeBox.Add(wx.StaticText(panel,wx.ID_ANY,'Capture image size',size=(160,30)),0)
        self.imgsizeEdit = wx.TextCtrl(panel,wx.ID_ANY)
        self.imgsizeEdit.SetValue('640,480')
        imgsizeBox.Add(self.imgsizeEdit,1)
        vbox.Add(imgsizeBox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)

        isdummyBox = wx.BoxSizer(wx.HORIZONTAL)
        self.isdummyCheck = wx.CheckBox(panel,wx.ID_ANY,'Use dummy mode (for standalone debug)')
        isdummyBox.Add(self.isdummyCheck)
        vbox.Add(isdummyBox, 0, wx.ALIGN_CENTER | wx.CENTER, 10)

        vbox.Add((-1, 25))

        okBox = wx.BoxSizer(wx.HORIZONTAL)
        okButton = wx.Button(panel,wx.ID_ANY, 'Ok', size=(70, 30))
        self.Bind(wx.EVT_BUTTON, self.quitfunc, okButton)
        okBox.Add(okButton)
        vbox.Add(okBox, 0, wx.ALIGN_CENTER | wx.CENTER, 10)

        panel.SetSizer(vbox)

        self.Show(True)

    def quitfunc(self, event):
        global FileWindowValues
        filename = self.filenameEdit.GetValue()
        address = self.addressEdit.GetValue()
        imgsize = self.imgsizeEdit.GetValue()
        isdummy = self.isdummyCheck.GetValue()

        FileWindowValues = {'filename':filename,'address':address,'imgsize':imgsize,'isdummy':isdummy}
        self.Close(True)

FileWindowValues = {}
application = wx.App(False)
fw = FileWindow(None,wx.ID_ANY,"Sample01_PsychoPy")
application.MainLoop()


dataFileName = FileWindowValues['filename']
fp = open(dataFileName+'_local.csv','w')
xy = FileWindowValues['imgsize'].split(',')
cameraX = int(xy[0])
cameraY = int(xy[1])

tracker = GazeParser.TrackingTools.getController(backend='PsychoPy',dummy=FileWindowValues['isdummy'])
tracker.setReceiveImageSize((cameraX,cameraY))
tracker.connect(FileWindowValues['address'])

win = psychopy.visual.Window(size=(1024,768),units='norm')

tracker.openDataFile(dataFileName+'.csv')
tracker.sendSettings(GazeParser.config.getParametersAsDict())


calarea = [-0.8,-0.8,0.8,0.8]
calTargetPos = [[   0,   0],
                [-0.6,-0.6],[-0.6,  0],[-0.6,0.6],
                [   0,-0.6],[   0,  0],[   0,0.6],
                [ 0.6,-0.6],[ 0.6,  0],[ 0.6,0.6]]

tracker.setCalibrationScreen(win)
tracker.setCalibrationTargetPositions(calarea, calTargetPos,units='norm')

while True:
    res = tracker.calibrationLoop()
    if res=='q':
        sys.exit(0)
    if tracker.isCalibrationFinished():
        break

stim = psychopy.visual.Rect(win, width=0.03, height=0.04, units='norm')
marker = psychopy.visual.Rect(win, width=0.009, height=0.012, units='norm', fillColor=(1,1,0),lineWidth=0.1)

trialClock = psychopy.core.Clock()
for tr in range(2):
    error = tracker.getSpatialError(message='Press space key', units='norm')

    targetPositionList = [(0.1*random.randint(-3,3),0.1*random.randint(-3,3)) for i in range(10)]
    targetPositionList.insert(0,(0,0))
    currentPosition = 0
    previousPosition = 0
    stim.setPos(targetPositionList[currentPosition])
    marker.setPos(targetPositionList[currentPosition])

    waitkeypress = True
    while waitkeypress:
        if 'space' in psychopy.event.getKeys():
            waitkeypress = False

        stim.draw()
        win.flip()

    tracker.startRecording(message='trial'+str(tr+1))
    tracker.sendMessage('STIM %s %s'%targetPositionList[currentPosition])

    data = []
    trialClock.reset()
    while True:
        currentTime = trialClock.getTime()
        currentPosition = int(currentTime)
        if currentPosition>=len(targetPositionList):
            break
        targetPosition = targetPositionList[currentPosition]
        if previousPosition != currentPosition:
            tracker.sendMessage('STIM %s %s'%targetPosition)
            previousPosition = currentPosition

        preGet = trialClock.getTime()
        eyePos= tracker.getEyePosition(units='norm')
        postGet = trialClock.getTime()
        if not eyePos[0] == None:
            data.append((1000*preGet,1000*postGet,1000*(postGet-preGet),
                         targetPosition[0],targetPosition[1],eyePos[0],eyePos[1]))
            marker.setPos((eyePos[0],eyePos[1]))
        else:
            data.append((1000*preGet,1000*postGet,1000*(postGet-preget),
                         targetPosition[0],targetPosition[1],-65536,-65536))

        keyList = psychopy.event.getKeys()
        if 'space' in keyList:
            tracker.sendMessage('press space')

        stim.setPos(targetPosition)
        stim.draw()
        marker.draw()
        win.flip()

    tracker.stopRecording(message='end trial')

    fp.write('trial%d\n' % (tr+1))
    if error[0] != None:
        fp.write('getSpatialError: %.4f,%.4f,%.4f\n' % (error[0],error[-1][0],error[-1][1]))
    else:
        fp.write('getSpatialError: None\n')
    fp.write('SentAt,ReceivedAt,Lag,TargetX,TargetY,EyeX,EyeY\n')
    for d in data:
        fp.write('%.1f,%.1f,%.1f,%.4f,%.4f,%.4f,%.4f\n' % d)
    fp.flush()

tracker.closeDataFile()

fp.close()