Skip to content

add OTB (EMG) reader #13303

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft

add OTB (EMG) reader #13303

wants to merge 4 commits into from

Conversation

drammock
Copy link
Member

This implements a Raw reader for OTB files (EMG data). There are 2 formats, OTB4 and OTB+; so far only OTB+ implemented. Based in part on a MATLAB reader, with permission to translate and relicense as BSD-3, granted here: OTBioelettronica/OTB-Python#2 (comment)

  • add support for OTB4 format
  • decide whether this belongs in MNE-Python, MNE-BIDS, or a separate package. I don't have strong feelings here; I implemented in MNE-Python first because it was easiest, but I'm happy to migrate the code.
  • add data files from Python file reader (or permission to port MATLAB file reader)? OTBioelettronica/OTB-Python#2 (comment) to mne-testing-data
  • add tests. May need help from someone with a MATLAB license, as I can't get the MATLAB-based reader to work in Octave. Would be great to have .mat files with source-of-truth for the signal values from the test data files.
  • handle annotations (marker files); in my example file they're empty.

cc @OTBioelettronica @klotz-t @pranavm19 @agramfort

from ...utils import _check_fname, fill_doc, logger, verbose, warn
from ..base import BaseRaw

OTB_PLUS_DTYPE = np.int16 # the file I have is int16; TBD if *all* OTB+ are like that
Copy link
Member Author

Choose a reason for hiding this comment

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

@OTBioelettronica there are several TODO items in the code here that maybe you can easily answer. Here's the first: are the data in .sig files always 16-bit signed integer?

Copy link

Choose a reason for hiding this comment

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

I think it is 16-bit when recording EMG and 24-bit when recording EEG. Have a look at https://otbioelettronica.it/download/137/otb-file-structure/2665/otb-structure (page 2, bullet point 'Raw data')

Choose a reason for hiding this comment

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

I confirm what @klotz-t wrote! The .sig file is a binary file containing raw data. Data are received from the device and saved immediately. Representation can be on 16-bit or on 24-bit depending on the device configuration. Also, the sampling frequency can be different depending on the device configuration. Most of the configurations are:

  • EEG : 500 Hz, 24-bit
  • EMG: 2000Hz, 16-bit
    there can be others with different sampling frequencies but the bits resolutions are only 16 or 24 bits

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @klotz-t for pointing out the file format spec document (embarassed I didn't find that before asking 😳). @OTBioelettronica is there a similar document for .otb4 format? I didn't see one on the website.

Choose a reason for hiding this comment

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

I wrote that document some while ago and, unfortunately, I still have to do it for the .otb4 file format :(

Comment on lines 86 to 87
# TODO verify these are the only non-data channel IDs
NON_DATA_CHS = ("Quaternion", "BufferChannel", "RampChannel")
Copy link
Member Author

Choose a reason for hiding this comment

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

@OTBioelettronica we need to distinguish which channels are EMG data from other channel types. Are these 3 the only non-EMG channel IDs, or are there others?

Copy link

Choose a reason for hiding this comment

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

There are also arbitrary auxiliary input channels, e.g., AUX Force, AUX Acceleration, ...

Choose a reason for hiding this comment

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

Thank you @klotz-t for answering this!
To recap there are few additional non-EMG channels:

  • Quartenions (intertial sensor)
  • Buffer and Ramp (these are control channels for debugging purposes)
  • AUX (Volt value - BNC connector)
  • Load Cell channels (used with dynamometer)

fnames = fid.getnames()
# the .sig file is the binary channel data
sig_fname = [_fname for _fname in fnames if _fname.endswith(".sig")]
assert len(sig_fname) == 1 # TODO is this a valid assumption?
Copy link
Member Author

Choose a reason for hiding this comment

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

is there guaranteed to be only 1 .sig file in each OTB+ archive? The MATLAB code suggests "yes" but it's not totally clear.

Copy link

Choose a reason for hiding this comment

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

From a pure user experience, this seems to be true (i.e., using this assumption, our own readers work fine).

Choose a reason for hiding this comment

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

In theory, each acquisition contains only one .sig file. However, there are two exceptions to this rule:

  1. Imported acquisitions: A user can import one or more acquisitions into another session. In that case, the corresponding .sig files are copied into the archive, resulting in multiple .sig files.
  2. Novecento device: this device can have many connected probes. Since every probe can have a different configuration (bits resolution and sampling frequency), the OTBiolab25 software split the data in many .sig files (one for each probe) However, Novecento is only supported by our new software, and this case is relevant only for .otb4 files, not .otb+.

Comment on lines 136 to 137
# TODO better to call these "emg_1" etc? should we left-zeropad ix?
ch_names.append(f"{ch_id}_{ix}" if ch_id in dupl_ids else ch_id)
Copy link
Member Author

Choose a reason for hiding this comment

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

right now we get channel names like "HD10MM0804_0", "HD10MM0804_1", etc. Seems like "EMG_000", "EMG_001" might be more user-friendly.

Copy link

Choose a reason for hiding this comment

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

I agree that channel names such as EMG_001 are more user-friendly (names such as HD10MM0804 refer to specific EMG electrode arrays). Yet, it seems not to be straightforward to automatically distinguish EMG and non-EMG channels as there exist a lot of different grids, and depending on the adapter configuration, the same grid can even be input into multiple adapters (e.g., one can use four 16-channel adapters for one 64-channel grid). Thus, in a self-written reader function, we implemented the number of adapters used for acquiring EMG as a function argument.

Choose a reason for hiding this comment

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

Thank you again @klotz-t for pointing this out!
I know is not so user friendly but as you wrote there a lot of different grid and matrices. I want to clarify the nomenclature to make it easier to read.
For example HD10MM0804:

  • HD is the connector type (GR is another one for example)
  • 10MM is the i.e.d (inter electrodic distance)
  • 0804 is the number of rows and columns

Comment on lines 141 to 142
# TODO verify ch_type for quats, buffer channel, and ramp channel
ch_types.append("misc" if ch_id in NON_DATA_CHS else "emg")
Copy link
Member Author

Choose a reason for hiding this comment

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

@larsoner is misc the right channel type for these?

@larsoner
Copy link
Member

decide whether this belongs in MNE-Python, MNE-BIDS, or a separate package. I don't have strong feelings here; I implemented in MNE-Python first because it was easiest, but I'm happy to migrate the code.

I think MNE-Python is better scope-wise than MNE-BIDS. Might make sense to add a MNE-EMG (or another package?) like we have MNE-NIRS if we expect a lot of EMG-specific functionality. In that case, the reader could live here or there. I think my vote is to start this MNE-EMG package with the hopes that EMG-specific functionality can grow there...

@sappelhoff
Copy link
Member

I think MNE-Python is better scope-wise than MNE-BIDS.

agreed

I think my vote is to start this MNE-EMG package with the hopes that EMG-specific functionality can grow there...

Yes, I think following what we have with mne-nirs would be good.

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.

5 participants