HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Sync eye movements with external events

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
eventswitheyemovementsexternalsync

Problem

This solution was used to synchronize events between two applications: An eye tracking software, Python, and a stimulus control software, object Free Pascal/Delphi. It avoided a rewrite of the stimulus control app from ObjFPC/Delphi to Python.

Pupil Eye Tracker sends some info via ZeroMQ to somewhere (I get used to call "net stack" to this place, please do not laugh). Indeed, a lot of info is sent, so they provided a way to filter this events; I have done this in a way that only timestamps should be sent.

This is a dummy stand alone broadcast:

"""

Broadcast dummy Pupil timestamps

"""

import zmq
#from ctypes import create_string_buffer
from time import sleep

def test_msg():
test_msg = "Pupil\ntimestamp:1389761135.56\n"
return test_msg

def main():
context = zmq.Context()
socket = context.socket(zmq.PUB)
#address = create_string_buffer("tcp://127.0.0.1:5020",512)
address = "tcp://127.0.0.1:5020"
try:
#socket.bind(address.value)
socket.bind(address)
except zmq.ZMQError:
print "Could not set Socket."

for i in range(120):
socket.send( test_msg() )
sleep(0.5)

context.destroy()

if __name__ == '__main__':
main()


A timestamp is the basic unit to synchronize frames, eye-gaze, events, basically all the stuff. Initially, they used the Python now() function to generate them. The current version can use the hardware of some cameras as well: "Hardware time-stamping: Running Pupil Pro with Linux now uses hardware timestamps taken by the camera hardware at the start of exposure".

Since they are using a Publisher-Subscriber protocol, Pupil Team suggested a way to receive this info. So, all I have done was to call the 'receive' client from inside the stimulus control app using a single thread for each call. Of course, it requires a broadcast server, in my case, during the real time Pupil Capture Server broadcast.

`unit client;

{$mode objfpc}{$H+}

interface

uses
Classes
,

Solution

I don't know any Pascal or Delphi, so I can't be any help here, but I can make a few suggestions to improve your general Python style.

Standalone broadcast

-
The Python style guide has some rules for formatting module imports. In particular, you're supposed to group your imports into standard library, third-party and project-specific imports. Within each group, they should be listed alphabetically.

Assuming that the ZeroMQ module is third-party, this means your module imports should be organised like this:

# from ctypes import create_string_buffer
from time import sleep

import zmq


-
Within test_msg(), making a variable of the same name seems bound to cause confusion and/or errors. Either rename it, or return the string directly.

Either

def test_msg():
    return_str = "Pupil\ntimestamp:1389761135.56\n"
    return return_str


or

def test_msg():
    return "Pupil\ntimestamp:1389761135.56\n"


although it's not clear why this needs to be a function, if all it returns is a string. Could you not just make the return string a global variable?

-
Your script needs more comments. I can follow what the program is doing, but I can't see why it's doing this. For example, the string in test_msg(). What are those numbers? Where do they come from? Or in main(), why are you connecting to port 5020?

It's good to provide some background information to make it easier for future readers.

-
You use i as the index variable in your for loop in main(), but never access the value of i. It's common practice to use _ as your index variable in such a situation: this is generally understood to mean that the value of the index variable is unimportant.

Post-factor analysis

-
Again, comments

-
It's fairly unusual to put module imports within a function body. Move them to the top of the script.

-
For constructing the timestamps_by_trial_path, rather than doing string concatenation with os.sep, use os.path.join():

timestamps_by_trial_path = os.path.join(current_path, 'timestamps')


-
When you do with open(...), you need to specify the mode with which to open the file. The common values are 'r' (read-only) and 'w' (write), but others are available. It should be:

with open(timestamps_by_trial_path, 'r') as f:


-
It's clear the components of temp have some meaning – it's expressed by your comment. So rather than giving them opaque names like i or temp[1], actually use their names. You can use tuple unpacking to do this incredibly easily:

(trial_index, timestamp, a_code) = temp


You can now use those names, which makes it easier to see the purpose behind your code.

-
If you use the with open(...) as f: construction, you don't need an f.close at the end. The file object f is closed automatically when you finish the with block. That's the advantage of using with open instead of f.open(); ...; f.close() – it's handled for you.

Code Snippets

# from ctypes import create_string_buffer
from time import sleep

import zmq
def test_msg():
    return_str = "Pupil\ntimestamp:1389761135.56\n"
    return return_str
def test_msg():
    return "Pupil\ntimestamp:1389761135.56\n"
timestamps_by_trial_path = os.path.join(current_path, 'timestamps')
with open(timestamps_by_trial_path, 'r') as f:

Context

StackExchange Code Review Q#75653, answer score: 6

Revisions (0)

No revisions yet.