.. _sample02:

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 :ref:`sample01` 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.

.. code-block:: python

    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')

:func:`~GazeParser.TrackingTools.ControllerPsychoPyBackend.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

:func:`~GazeParser.TrackingTools.ControllerPsychoPyBackend.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)
------------------

- :download:`Download source code (sample02_PsychoPy.py)<sample02_PsychoPy.py>`

Lines modified from sample01_PsychoPy.py are highlighted.

.. code-block:: python
    :emphasize-lines: 89-93,96,105,106,110,112,143,166,171

    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()