Exporting to BIDS formats#

The Brain Imaging Data Structure (BIDS) is a comprehensive framework designed to systematically organize and share diverse types of data, including behavioral, physiological, and neuroimaging information. Converting datasets into BIDS format is a widely adopted methodology, particularly in the process of curating datasets that adhere to the principles of FAIR (Findable, Accessible, Interoperable, Reusable).

For datasets encompassing mobile eye-tracking data, it is essential to apply specific BIDS specifications tailored for such data. In this context, Motion-BIDS and Eye-Tracking-BIDS specifications are noteworthy. Motion-BIDS (BEP029) has been successfully integrated into the official BIDS specification, demonstrating its readiness for use in organizing motion-related data. On the other hand, Eye-Tracking-BIDS (BEP020) is still undergoing development, reflecting ongoing efforts to provide a standardized format for eye-tracking data. You can find more information about these specifications in the following references:

Gorgolewski, K., Auer, T., Calhoun, V. et al. The brain imaging data structure, a format for organizing and describing outputs of neuroimaging experiments. Sci Data 3, 160044 (2016). https://doi.org/10.1038/sdata.2016.44

Jeung, S., Cockx, H., Appelhoff, S. et al. Motion-BIDS: an extension to the brain imaging data structure to organize motion data for reproducible research. Sci Data 11, 716 (2024). https://doi.org/10.1038/s41597-024-03559-8

In the ensuing section, we will delve into the procedure for exporting data to Motion-BIDS. This will be accomplished using the export_motion_bids method available within PyNeon’s Recording objects, offering a practical guide for researchers aiming to standardize their motion data in alignment with the BIDS framework.

[1]:
from pyneon import get_sample_data, Recording
import pandas as pd
import json

rec_dir = (
    get_sample_data("screenFlash")
    / "Timeseries Data + Scene Video"
    / "screenflash-54b2f924"
)
rec = Recording(rec_dir)

To use export_motion_bids method, we need to specify the output directory where the BIDS dataset will be saved, and a string prefix to denote this session of data. The prefix follows the following format (Fields in [] are optional):

sub-<label>[_ses-<label>]_task-<label>_tracksys-<label>[_acq-<label>][_run-<index>]

If you have any additional metadata that you would like to include, you can pass it as a dictionary to the extra_metadata argument. This metadata will be saved in the dataset_description.json file.

Let’s see what files will be exported to the BIDS dataset directory:

[2]:
# Create a BIDS directory
motion_dir = rec_dir.parent / "BIDS" / "sub-1" / "motion"
motion_dir.mkdir(exist_ok=True, parents=True)

# Export the motion data to BIDS format
prefix = "sub-1_task-screenFlash_tracksys-NeonIMU_run-1"
extra_metadata = {
    "TaskName": "screenFlash",
    "InstitutionName": "Streeling University",
    "InstitutionAddress": "Trantor, Galactic Empire",
    "InstitutionalDepartmentName": "Department of Psychohistory",
}

rec.export_motion_bids(motion_dir, prefix=prefix, extra_metadata=extra_metadata)

# Print all the conents of motion_dir
for path in motion_dir.iterdir():
    print(path.name)
sub-1_task-screenFlash_tracksys-NeonIMU_run-1_channels.json
sub-1_task-screenFlash_tracksys-NeonIMU_run-1_channels.tsv
sub-1_task-screenFlash_tracksys-NeonIMU_run-1_motion.json
sub-1_task-screenFlash_tracksys-NeonIMU_run-1_motion.tsv

The contents of these files follow the Motion-BIDS specification at: https://bids-specification.readthedocs.io/en/stable/modality-specific-files/motion.html.

For example, the _motion.tsv is a tab-separated values file that contains the (n_samples, n_channels) motion data without a header:

[3]:
motion_tsv_path = motion_dir / f"{prefix}_motion.tsv"
motion_df = pd.read_csv(motion_tsv_path, sep="\t")
print(f"Motion data shape: {motion_df.shape}")
print(motion_df.head())
Motion data shape: (4944, 13)
   -0.032425  1.249313  -1.062393  -0.027344  -0.378906  0.949707  \
0   0.022678  1.190656  -1.119273  -0.019153  -0.377996  0.940606
1   0.291978  1.451646  -1.228879  -0.024460  -0.373712  0.939519
2   0.139505  1.396314  -1.294069  -0.030831  -0.371104  0.944893
3   0.399156  1.061873  -1.306534  -0.030095  -0.371316  0.943091
4   0.307300  1.362720  -1.348332  -0.027290  -0.373589  0.944235

   1.1495382546655957  -22.014470833356658  131.97212848840843  \
0            1.154530           -22.012730          131.961423
1            1.161212           -22.009442          131.950870
2            1.171125           -22.004620          131.941577
3            1.178265           -21.998308          131.931277
4            1.183449           -21.993049          131.918883

   0.4012015163898468  -0.0866925716400146  -0.1703909933567047  \
0            0.401294            -0.086741            -0.170352
1            0.401388            -0.086798            -0.170295
2            0.401478            -0.086873            -0.170215
3            0.401573            -0.086922            -0.170133
4            0.401681            -0.086962            -0.170064

   0.895817220211029
0           0.895779
1           0.895742
2           0.895709
3           0.895678
4           0.895638

Its metadata is stored in the _motion.json file, which contains (note the extra metadata we added):

[4]:
motion_json = motion_dir / f"{prefix}_motion.json"
with open(motion_json, "r") as f:
    motion_json_data = json.load(f)
print(json.dumps(motion_json_data, indent=2))
{
  "TaskName": "screenFlash",
  "TaskDescription": "",
  "Instructions": "",
  "DeviceSerialNumber": "321970",
  "Manufacturer": "TDK InvenSense & Pupil Labs",
  "ManufacturersModelName": "ICM-20948",
  "SoftwareVersions": "App version: 2.9.3-prod; Pipeline version: 2.8.0",
  "InstitutionName": "Streeling University",
  "InstitutionAddress": "Trantor, Galactic Empire",
  "InstitutionalDepartmentName": "Department of Psychohistory",
  "SamplingFrequency": 110,
  "ACCELChannelCount": 3,
  "GYROChannelCount": 3,
  "MissingValues": "n/a",
  "MotionChannelCount": 13,
  "ORNTChannelCount": 7,
  "SubjectArtefactDescription": "",
  "TrackedPointsCount": 0,
  "TrackingSystemName": "IMU included in Neon"
}

The metadata for each channel (each column in the _motion.tsv file) is stored in _channels.tsv file:

[5]:
channels_tsv_path = motion_dir / f"{prefix}_channels.tsv"
channels_df = pd.read_csv(channels_tsv_path, sep="\t")
print(channels_df)
              name component   type tracked_point      units  \
0           gyro x         x   GYRO          Head      deg/s
1           gyro y         y   GYRO          Head      deg/s
2           gyro z         z   GYRO          Head      deg/s
3   acceleration x         x  ACCEL          Head          g
4   acceleration y         y  ACCEL          Head          g
5   acceleration z         z  ACCEL          Head          g
6             roll         x   ORNT          Head        deg
7            pitch         y   ORNT          Head        deg
8              yaw         z   ORNT          Head        deg
9     quaternion w         w   ORNT          Head  arbitrary
10    quaternion x         x   ORNT          Head  arbitrary
11    quaternion y         y   ORNT          Head  arbitrary
12    quaternion z         z   ORNT          Head  arbitrary

    sampling_frequency
0                  110
1                  110
2                  110
3                  110
4                  110
5                  110
6                  110
7                  110
8                  110
9                  110
10                 110
11                 110
12                 110

Finally, the _channels.json file contains the coordinate system:

[6]:
channels_json_path = motion_dir / f"{prefix}_channels.json"
with open(channels_json_path, "r") as f:
    channels_json_data = json.load(f)
print(json.dumps(channels_json_data, indent=2))
{
  "reference_frame": {
    "Levels": {
      "global": {
        "SpatialAxes": "RAS",
        "RotationOrder": "ZXY",
        "RotationRule": "right-hand",
        "Description": "This global reference frame is defined by the IMU axes: X right, Y anterior, Z superior. The scene camera frame differs from this frame by a 102-degree rotation around the X-axis. All motion data are expressed relative to the IMU frame for consistency."
      }
    }
  }
}