Skip to content

Metadata Processing

This guide illustrates how to process and synchronize metadata from XDAQ binary files containing video frame information with neural recordings from Intan RHD files. It explains the file format and data structure, lists the software requirements, and provides detailed Python code examples for reading XDAQ metadata into a structured NumPy array, loading Intan neural data, and mapping neural timestamps to video frame timestamps using binary search.


Requirements

Ensure you have the following installed for compatibility:

Note

Although earlier versions might work, these versions are recommended.


Reading XDAQ Metadata

The following example shows how to read XDAQ metadata from a binary file into a structured NumPy array:

import sys
from typing import Optional
import numpy as np

metadata_dtype = np.dtype([
    ('fpga_timestamp', np.uint64),
    ('rhythm_timestamp', np.uint32),
    ('ttl_in', np.uint32),
    ('ttl_out', np.uint32),
    ('spi_perf_counter', np.uint32),
    ('reserved', np.uint64)
])

record_dtype = np.dtype([
    ('video_timestamp', np.uint64),
    ('metadata', metadata_dtype)
])

def read_XDAQFrameData_np(filename: str) -> Optional[np.ndarray]:
    """
    Read XDAQ metadata data file.

    Args:
        filename: Path to the metadata data file.

    Returns:
        Structured numpy array containing video timestamps and metadata.
        Returns None if file reading fails.

    Example:
        >>> data = read_XDAQFrameData_np("data.bin")
        >>> print(data['video_timestamp'][0])  # First video timestamp
        >>> print(data['metadata']['ttl_in'][0])  # First TTL input value
    """
    try:
        with open(filename, 'rb') as f:
            data = f.read()
        return np.frombuffer(data, dtype=record_dtype)
    except Exception as e:
        print(f"Error reading file: {e}")
        return None

Expected output:

array([(1234567890, (987654321, 1000, 1, 0, 100, 0)),
       (1234567891, (987654322, 1001, 1, 0, 101, 0)), ...],
      dtype=record_dtype)

Reading Intan Data

The next code snippet uses the Intan Python reader to load data from an .rhd file. You can download the reader from Intan Technologies.

import sys
from load_intan_rhd_format import read_data

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: python {sys.argv[0]} <file_name>")
        sys.exit(1)
    rhd_path = sys.argv[1]
    intan_data = read_data(rhd_path)
    print(f"Loaded neural data with {len(intan_data['t_amplifier'])} samples")
    print(intan_data['t_amplifier'])
    print(intan_data['amplifier_data'])

Usage:

python XDAQFrameData.py <intan_data_file_name>

Expected output:

Reading Intan Technologies RHD Data File, Version 3.3

Found 128 amplifier channels.
Found 6 auxiliary input channels.
Found 0 supply voltage channels.
Found 0 board ADC channels.
Found 8 board digital input channels.
Found 0 board digital output channels.
Found 0 temperature sensors channels.

File contains 28.915 seconds of data.  Amplifiers were sampled at 30.00 kS/s.

Allocating memory for data...
Reading data from file...
10% done...
20% done...
30% done...
40% done...
50% done...
60% done...
70% done...
80% done...
90% done...
Parsing data...
No missing timestamps in data.
Done!  Elapsed time: 1.2 seconds
Loaded neural data with 867456 samples
[0.00000000e+00 3.33333333e-05 6.66666667e-05 ... 2.89151000e+01
 2.89151333e+01 2.89151667e+01]
[[ 435.63   562.38   618.345 ...  533.91   531.57   527.865]
 [ 475.8    592.995  629.07  ...  633.75   629.85   624.39 ]
 [ 881.595 1027.26  1087.125 ... 1037.01  1029.795 1020.63 ]
 ...
 [  67.665   56.355   43.68  ...  300.495  300.3    292.695]
 [ 181.35   301.665  264.42  ...  187.2    186.225  180.57 ]
 [ 160.68   283.14   240.825 ...  375.375  373.815  362.505]]

Synchronizing XDAQ and Intan Data

This example demonstrates how to map neural timestamps to the corresponding video frame timestamps using a binary search with NumPy.

import numpy as np
import sys
from load_intan_rhd_format import read_data

metadata_dtype = np.dtype([
    ('fpga_timestamp', np.uint64),
    ('rhythm_timestamp', np.uint32),
    ('ttl_in', np.uint32),
    ('ttl_out', np.uint32),
    ('spi_perf_counter', np.uint32),
    ('reserved', np.uint64)
])

record_dtype = np.dtype([
    ('video_timestamp', np.uint64),
    ('metadata', metadata_dtype)
])

def read_XDAQFrameData_np(filename: str) -> np.ndarray:
    with open(filename, 'rb') as f:
        data = f.read()
    return np.frombuffer(data, dtype=record_dtype)

def map_rhd_to_video(neural_timestamps: np.ndarray, video_timestamps: np.ndarray) -> np.ndarray:
    """
    For each neural timestamp, find the video frame index for which the video timestamp
    is the largest value less than or equal to the neural timestamp.
    """
    indices = np.searchsorted(video_timestamps, neural_timestamps, side='right') - 1
    indices[indices < 0] = 0
    return indices

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(f"Usage: python {sys.argv[0]} <rhd_file_name> <xdaq_file_name>")
        sys.exit(1)

    rhd_data = read_data(sys.argv[1])
    xdaq_data = read_XDAQFrameData_np(sys.argv[2])

    rhd_timestamps = rhd_data["t_amplifier"]
    video_timestamps = xdaq_data["video_timestamp"]

    mapped_indices = map_rhd_to_video(rhd_timestamps, video_timestamps)
    target_index_rhd = 0
    target_index_video = mapped_indices[target_index_rhd]
    print(f"index_rhd: {target_index_rhd}, index_video: {target_index_video}")
    print(f"video_timestamp: {video_timestamps[target_index_video]}, rhd_timestamp: {rhd_timestamps[target_index_rhd]}")
    print(f"video_metadata: {xdaq_data['metadata'][target_index_video]}")

Usage:

python XDAQFrameData.py <intan_data_file_name> <xdaq_data_file_name>

Expected output:

Reading Intan Technologies RHD Data File, Version 3.3

Found 128 amplifier channels.
Found 6 auxiliary input channels.
Found 0 supply voltage channels.
Found 0 board ADC channels.
Found 8 board digital input channels.
Found 0 board digital output channels.
Found 0 temperature sensors channels.

File contains 28.915 seconds of data.  Amplifiers were sampled at 30.00 kS/s.

Allocating memory for data...
Reading data from file...
10% done...
20% done...
30% done...
40% done...
50% done...
60% done...
70% done...
80% done...
90% done...
Parsing data...
No missing timestamps in data.
Done!  Elapsed time: 1.1 seconds
index_rhd: 0, index_video: 0
video_timestamp: 26213000000, rhd_timestamp: 0.0
video_metadata: (78118894449, 0, 0, 0, 809483121, 0)