Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-modal acquisition #171

Merged
merged 23 commits into from
Sep 16, 2021
Merged

Multi-modal acquisition #171

merged 23 commits into from
Sep 16, 2021

Conversation

lawhead
Copy link
Collaborator

@lawhead lawhead commented Sep 8, 2021

Overview

Re-architected the data acquisition module in support of multi-modal acquisition.

Ticket

https://www.pivotaltracker.com/story/show/178699590
https://www.pivotaltracker.com/story/show/178875719

Contributions

  • Created a new lightweight data acquisition client for reading from LSL data sources. Multiple clients can be created to read from different sources (ex. EEG and eye tracker).
  • Separated out the responsibilities for recording and querying data.
  • Deprecated MarkerWriter
  • Deprecated support for TCP connections.
  • Renamed supported_device to preconfigured_device.
  • Added a Clock abstraction, which by default uses the pylsl local_clock.
  • Refactored data queries to use timestamps rather than sample number.
  • Added code to demonstrate functionality and usage.

Test

  • Run all unit tests
  • Run Calibration task -> offline_analysis -> Copy Phrase task.
  • Confirm timing data.

…s for DeviceSpec sample_rate and support for an IRREGULAR_RATE for devices such as buttons and switches; renamed supported_devices to preconfigured_devices; demos and other prep work for multi-modal acquisition
… streams; initial implementation of LSL-specific acquisition client
…er integration; context manager support; added the ability to name the raw data file. Simplified interface of marker writer to reflect current usage. Updated the acquisition helper to initialize the LslClient.
… that wraps lsl_local_clock. Updated all code to use the new internal clock.
…ing timestamps rather than sample numbers. Modified acquisition client to record only the configured data, rather than all detected LSL data. Synced the recorded data offset with the client.
@lawhead lawhead requested a review from tab-cmd September 8, 2021 23:53
Copy link
Contributor

@tab-cmd tab-cmd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice change! I added some comments. Running locally, I see the TRG column is still present but has sensor values when it was 0.0 before? I see that TRG_device_stream used to have those values. Is this due to how we mock channels for serving?


if include_meta:
# TODO: different types of metadata depending on the content type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make a ticket for this! I don't know how much we use the metadata, but it seems useful

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

content_type='Gaze')


def eye_tracker_data_generator(display_x=1920, display_y=1080):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move this somewhere... datastream/generator?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a new package for acquisition/datastream/mock and moved the eye_tracker_server.py and switch.py here.

log_to_stdout()

# Generic LSL device with 16 channels.
eeg_device = DeviceSpec(name="LSL_demo",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename?

log = logging.getLogger(__name__)


def custom_generator(device_spec: DeviceSpec, low=-1000, high=1000):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to wherever the other eye tracker generator was moved!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think this one can be removed at this point.

background_color='white',
action=self.switch.click)

# @override
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some cleanup on this file

@@ -0,0 +1,3 @@
class InvalidClockError(Exception):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other modules use the name exceptions.py. It may be good to change for consistency, but errors is descriptive enough

self.max_chunk_size = 1024

# seconds to sleep between data pulls from LSL
self.sleep_seconds = 0.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think these values might change? chunk size and sleep seconds?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a ticket to explore this in more detail (see comment below). The max_chunk_size here is the default value that's used if you call pull_chunk without any parameters. The user can set this to a different value after the LslRecordingThread is instantiated (and before it starts recording). There is definitely a relationship between sleep seconds and max_chunk_size, though, and we may want to do some validation checks before setting.

# and capture a timestamp, then use that timestamp to determine how
# long we slept since receiving the call and set the max_samples
# parameter accordingly.
# TODO: do we need to use max_chunk_size here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pull out to a spike?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inlet.close_stream()
self._cleanup()

# pylint: disable=import-outside-toplevel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved closer to the import

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch. Weird that this was here.

- start_at_zero : If `True` the value returned by `getTime` is
relative to when the clock was started or reset. Default if `False`.
- get_time : optional function called to get the current time. The
default value is the LabStreamingLayer (LSL) local_clock.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might indicate why it's the default...

@lawhead lawhead merged commit 4b53064 into 1.5.1 Sep 16, 2021
@tab-cmd tab-cmd deleted the multi-modal-acq branch January 24, 2022 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants