patternpythonMinor
Sync eye movements with external events
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:
A timestamp is the basic unit to synchronize frames, eye-gaze, events, basically all the stuff. Initially, they used the Python
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
,
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:
-
Within
Either
or
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
It's good to provide some background information to make it easier for future readers.
-
You use
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
-
When you do
-
It's clear the components of
You can now use those names, which makes it easier to see the purpose behind your code.
-
If you use the
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_stror
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) = tempYou 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 zmqdef test_msg():
return_str = "Pupil\ntimestamp:1389761135.56\n"
return return_strdef 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.