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."
}
}
}
}