-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from pupil-labs/dev
2.0.1 release candidate 1
- Loading branch information
Showing
11 changed files
with
293 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
2.0.1 | ||
##### | ||
- Document minimum Pupil Invisible Companion version required (v1.4.14) | ||
- Add code example demonstrating post-hoc time sync between a Pupil Cloud download and | ||
a LSL recording | ||
- Write debug logs to log file (path defined via ``--log_file_name`` parameter) | ||
|
||
- Requires `click <https://pypi.org/project/click/>`_ instead of `asyncclick | ||
<https://pypi.org/project/asyncclick/>`_ | ||
|
||
2.0.0 | ||
##### | ||
- First release supporting the `Pupil Labs Network API <https://github.com/pupil-labs/realtime-network-api>`_ | ||
- The legacy NDSI-based relay application can be found | ||
`here <https://github.com/labstreaminglayer/App-PupilLabs/tree/legacy-pi-lsl-relay/pupil_invisible_lsl_relay>`_ | ||
|
||
- Pull project skeleton from `<https://github.com/pupil-labs/python-module-skeleton>`_ | ||
- Initial fork from `<https://github.com/labstreaminglayer/App-PupilLabs>`_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
****** | ||
Guides | ||
****** | ||
|
||
High-level overviews and hands-on examples how to use the lsl relay. | ||
|
||
.. toctree:: | ||
:maxdepth: 1 | ||
:glob: | ||
|
||
overview.rst | ||
time_alignment.rst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
:tocdepth: 3 | ||
|
||
************************** | ||
Using gaze data from Cloud | ||
************************** | ||
|
||
The LSL stream will contain gaze data with a resolution of ~66 Hz. | ||
You can get a higher sampling rate when you're using the gaze data downloaded from | ||
Pupil Cloud. In order to do this, you'll need to align the timestamps collected during | ||
the lsl streaming to the timestamps recorded in cloud. | ||
|
||
Setup | ||
===== | ||
#. Start the lsl relay of your Pupil Invisible glasses. | ||
|
||
#. In your lsl recording software, select both the gaze data stream and the event stream. | ||
|
||
#. Start the lsl recording through your software of choice (e.g. LabRecorder). | ||
|
||
#. In your Pupil Invisible Companion App, tap the red "record" button and make sure the recording is running. | ||
|
||
#. Run your experiment/data collection. | ||
|
||
#. Stop the recording in the Pupil Invisible Companion App. | ||
|
||
#. Stop the lsl recording. | ||
|
||
#. Wait till the gaze data was uploaded to Pupil Cloud and the 200 Hz gaze data was computed. | ||
|
||
#. Export the gaze data from Pupil Cloud by right-clicking on the recording and selecting Downloads -> Download Recording. | ||
|
||
#. You will end up with an xdf file, containing all data recorded through lsl (including gaze and event data), | ||
and one csv file for each gaze and event data downloaded from Pupil Cloud. | ||
|
||
From here, you can perform the timestamp alignment. | ||
|
||
.. important:: | ||
If your recording is short (less than 1 minute), you should increase the frequency at which ``lsl.timesync`` events | ||
are being generated. As a rule of thumb you should aim for at least 3 events being sent throughout the recording, including | ||
``recording.begin`` and ``recording.end`` events, which are generated by starting and ending the recording in the Pupil | ||
Invisible Companion App. | ||
|
||
You can change the frequency at which ``lsl.timesync`` events are being sent by setting the ``--time_sync_interval`` | ||
argument. | ||
|
||
To run the pipeline below, you can install the necessary dependencies needed via | ||
|
||
.. code-block:: | ||
pip install pupil-invisible-lsl-relay[pupil_cloud_alignment] | ||
Import dependencies | ||
=================== | ||
Import the installed dependencies before running the example code below. | ||
|
||
.. literalinclude:: ../../examples/linear_time_model.py | ||
:language: python | ||
:lines: 1-5 | ||
:linenos: | ||
|
||
Loading event data from xdf file | ||
================================= | ||
Event streams can be identified and selected by their name. | ||
|
||
.. literalinclude:: ../../examples/linear_time_model.py | ||
:language: python | ||
:lines: 7-25 | ||
:linenos: | ||
|
||
Loading event data from cloud | ||
============================== | ||
The raw data enrichment contains a csv file with event names and timestamps. | ||
We can load this csv file with pandas. | ||
|
||
.. literalinclude:: ../../examples/linear_time_model.py | ||
:language: python | ||
:lines: 27-32 | ||
:linenos: | ||
|
||
Building a linear model to map cloud time to lsl time | ||
====================================================== | ||
After extracting the event names and time stamps from both series as shown above, | ||
you can build a linear model to translate from one time series to the other. | ||
|
||
Once the linear model was fitted, we can apply it to map the cloud timestamps to lsl timestamps. | ||
|
||
.. literalinclude:: ../../examples/linear_time_model.py | ||
:language: python | ||
:lines: 34- | ||
:linenos: | ||
|
||
.. hint:: | ||
If you want to invert the mapping, to transform lsl timestamps to cloud timestamps, | ||
you'll have to change the order of arguments in ``time_mapper.fit`` to | ||
|
||
.. code-block:: diff | ||
time_mapper.fit( | ||
- filtered_cloud_event_data[[event_column_timestamp]], | ||
filtered_lsl_event_data[event_column_timestamp], | ||
+ filtered_cloud_event_data[[event_column_timestamp]], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# imports for the full pipeline | ||
import numpy as np | ||
import pandas as pd | ||
import pyxdf | ||
from sklearn import linear_model | ||
|
||
# import xdf data | ||
# define the name of the stream of interest | ||
stream_name = 'pupil_invisible_Event' | ||
|
||
# load xdf data | ||
path_to_recording = './lsl_recordings/recorded_xdf_file.xdf' | ||
data, header = pyxdf.load_xdf(path_to_recording, select_streams=[{'name': stream_name}]) | ||
|
||
# when recording from one device, there will be only one event stream | ||
# extract this stream from the data | ||
event_stream = data[0] | ||
|
||
# extract event names and lsl time stamps into a pandas data frames | ||
event_column_name = 'name' | ||
event_column_timestamp = 'timestamp [s]' | ||
|
||
lsl_event_data = pd.DataFrame(columns=[event_column_name, event_column_timestamp]) | ||
lsl_event_data[event_column_name] = [name[0] for name in event_stream['time_series']] | ||
lsl_event_data[event_column_timestamp] = event_stream['time_stamps'] | ||
|
||
# import cloud data | ||
path_to_cloud_events = './cloud_recordings/events.csv' | ||
cloud_event_data = pd.read_csv(path_to_cloud_events) | ||
|
||
# transform cloud timestamps to seconds | ||
cloud_event_data[event_column_timestamp] = cloud_event_data['timestamp [ns]'] * 1e-9 | ||
|
||
# filter events that were recorded in the lsl stream and in cloud | ||
name_intersection = np.intersect1d( | ||
cloud_event_data[event_column_name], lsl_event_data[event_column_name] | ||
) | ||
|
||
# filter timestamps by the event intersection | ||
filtered_cloud_event_data = cloud_event_data[ | ||
cloud_event_data[event_column_name].isin(name_intersection) | ||
] | ||
|
||
filtered_lsl_event_data = lsl_event_data[ | ||
lsl_event_data[event_column_name].isin(name_intersection) | ||
] | ||
|
||
# fit a linear model | ||
time_mapper = linear_model.LinearRegression() | ||
time_mapper.fit( | ||
filtered_cloud_event_data[[event_column_timestamp]], | ||
filtered_lsl_event_data[event_column_timestamp], | ||
) | ||
|
||
# use convert gaze time stamps from cloud to lsl time | ||
cloud_gaze_data = pd.read_csv('./cloud_recordings/gaze.csv') | ||
|
||
# map from nanoseconds to seconds | ||
cloud_gaze_data[event_column_timestamp] = cloud_gaze_data['timestamp [ns]'] * 1e-9 | ||
|
||
# predict lsl time in seconds | ||
cloud_gaze_data['lsl_time [s]'] = time_mapper.predict( | ||
cloud_gaze_data[[event_column_timestamp]] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
[metadata] | ||
name = pupil_invisible_lsl_relay | ||
description = Project description | ||
description = Relay Pupil Invisible data to LabStreamingLayer | ||
long_description = file: README.rst | ||
long_description_content_type = text/x-rst | ||
url = https://github.com/pupil-labs/python-module-skeleton | ||
url = https://github.com/pupil-labs/pupil-invisible-lsl-relay/ | ||
author = Pupil Labs GmbH | ||
author_email = [email protected] | ||
license = MIT | ||
|
@@ -18,12 +18,15 @@ classifiers = | |
Programming Language :: Python :: 3.8 | ||
Programming Language :: Python :: 3.9 | ||
Programming Language :: Python :: 3.10 | ||
project_urls = | ||
Documentation=https://pupil-invisible-lsl-relay.readthedocs.io/en/stable/ | ||
History=https://pupil-invisible-lsl-relay.readthedocs.io/en/latest/history.html | ||
Pupil Labs Realtime Network API=https://github.com/pupil-labs/realtime-network-api | ||
LabStreamingLayer=https://labstreaminglayer.readthedocs.io/ | ||
|
||
[options] | ||
packages = find_namespace: | ||
install_requires = | ||
anyio>=2.0 # asyncclick requires anyio | ||
asyncclick>=8.0.3.2 | ||
click>=7.0 | ||
pupil-labs-realtime-api>=1.0.0 | ||
pylsl>=1.12.2 | ||
|
@@ -42,7 +45,7 @@ exclude = | |
|
||
[options.entry_points] | ||
console_scripts = | ||
pupil_invisible_lsl_relay = pupil_labs.invisible_lsl_relay.cli:main_handling_keyboard_interrupt | ||
pupil_invisible_lsl_relay = pupil_labs.invisible_lsl_relay.cli:relay_setup_and_start | ||
|
||
[options.extras_require] | ||
docs = | ||
|
@@ -51,6 +54,11 @@ docs = | |
rst.linker>=1.9 | ||
sphinx<4.4 # 4.4 does not detect TypeVars correctly | ||
importlib-metadata;python_version<"3.8" | ||
pupil_cloud_alignment = | ||
numpy | ||
pandas | ||
pyxdf | ||
scikit-learn | ||
testing = | ||
flake8<4 # workaround https://github.com/tholo/pytest-flake8/issues/81 | ||
pytest>=6 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
from .cli import main_handling_keyboard_interrupt | ||
from .cli import relay_setup_and_start | ||
|
||
if __name__ == "__main__": | ||
main_handling_keyboard_interrupt() | ||
relay_setup_and_start() |
Oops, something went wrong.