MEG + Eye Tracking Workflow

This tutorial demonstrates the complete workflow for analyzing MEG and eye tracking data using pyAVS, focusing primarily on the AVSComposer - the recommended tool for MEG-ET data fusion.

Overview

The pyAVS MEG + eye tracking workflow provides two main approaches:

Recommended: AVSComposer Approach

The AVSComposer class provides a complete, tested pipeline that replicates and improves upon the original AVS-machine-room composer functionality. This is the recommended approach for most users.

Alternative: Functional API Approach

Individual functions for custom workflows requiring fine-grained control.

The complete workflow follows these main steps:

  1. Composer Initialization: Configure MEG-ET processing parameters

  2. MEG Data Loading: Load and preprocess MEG data with Maxwell filtering, ICA

  3. Eye Tracking Integration: Load ET data and create trigger-based alignment

  4. Event-based Epoching: Create MEG epochs around eye tracking events

  5. Source Reconstruction: Transform sensor data to source space (optional)

  6. Analysis: Population codes, encoding models, statistics

Prerequisites

Before starting this workflow, ensure you have:

  • The AVS dataset downloaded and properly structured

  • pyAVS installed with all dependencies

  • Data path configured using pyavs.set_data_path()

Eye Tracking Integration and Epoching with Composer

Now integrate eye tracking data and create epochs for different event types:

# Process different eye tracking event types
event_types = ["fixation", "saccade", "blink"]
epochs_results = {}

for event_type in event_types:
    print(f"Processing {event_type} events...")

    # Get eye tracking annotations for this event type
    composer.get_et_annotations(
        event_type=event_type,
        recording="scene",              # Focus on scene viewing
        exclude_last_fixation=True,     # Exclude incomplete fixations
        add_cross_event_info=True,      # Add contextual information
        preprocessed=True               # Use preprocessed ET data
    )

    print(f"Loaded {len(composer.et_events)} {event_type} events")

    # Create MEG epochs around eye tracking events
    composer.make_et_event_epochs(
        tmin=-0.2,              # 200ms before event
        tmax=0.8,               # 800ms after event
        event_type=event_type,
        recording="scene",
        get_metadata=True,      # Include rich metadata
        baseline=None           # No baseline correction (recommended for AVS)
    )

    # Store results for later analysis
    epochs_results[event_type] = composer.et_epochs.copy()
    print(f"Created {len(composer.et_epochs)} {event_type} epochs")

    # Display some metadata information
    if composer.et_epochs.metadata is not None:
        metadata_cols = list(composer.et_epochs.metadata.columns)[:5]
        print(f"Metadata columns (first 5): {metadata_cols}")

Alternative Method 2: Functional API Approach

For users requiring fine-grained control, pyAVS also provides individual functions. Note that pyavs.MEGETComposer is a backward-compatibility alias for AVSComposer itself (not a separate, simpler class) – it’s shown here only because it appears with this name in older code; new code should just use pyavs.AVSComposer directly, as in Method 1 above.

# This approach gives you more control but requires more setup

# Load MEG data for one run/block
meg_raw = pyavs.load_meg_raw(subject_id=1, session=1, run=1)

# Load eye tracking data
explog, eye_events = pyavs.load_and_enrich_eye_events([1], [1])

# Apply MEG preprocessing (subject_id/session/block identify where cached
# intermediate outputs are read from/written to)
meg_clean = pyavs.preprocess_meg_block(
    meg_raw,
    subject_id=1, session=1, block=1,
    l_freq=0.2, h_freq=200.0,
)

# Create epochs from eye tracking events -- returns (epochs, epochs_dataframe)
epochs, epochs_df = pyavs.create_et_event_epochs(
    meg_clean, eye_events,
    event_type='fixation',
    tmin=-0.2, tmax=0.5
)

print(f"Created {len(epochs)} fixation epochs")

Note: The AVSComposer approach is recommended for most users as it handles edge cases, provides better error handling, and ensures compatibility with the AVS dataset structure.

Source Reconstruction with Composer

Transform sensor data to source space using the composer epochs:

# Continue with the composer epochs from previous steps
# epochs_results contains epochs for different event types

# Use fixation epochs for source reconstruction
fixation_epochs = epochs_results['fixation']

# Load forward model
try:
    forward_model = pyavs.load_forward_model(subject_id=1, session=1)
    print("✓ Forward model loaded")
except FileNotFoundError:
    print("⚠ Forward model not found - creating for demonstration")
    # In practice, you need to create this using FreeSurfer and coregistration
    forward_model = create_demo_forward_model(fixation_epochs.info)

# Apply source reconstruction. method_kwargs (reg, weight_norm, pick_ori, ...)
# are forwarded to compute_beamformer_filters internally -- do not pass a
# pre-computed `filters` object here, apply_source_reconstruction builds its
# own.
print("Computing beamformer filters and reconstructing source activity...")
source_data = pyavs.apply_source_reconstruction(
    fixation_epochs,
    forward_model,
    method='beamformer',
    reg=0.05,                      # Regularization parameter
    weight_norm='unit-noise-gain', # Normalization method
    pick_ori='max-power',          # Orientation selection
)

print(f"✓ Source data: {source_data.shape} (epochs, sources, timepoints)")

ROI Analysis and Population Codes

Extract activity from regions of interest:

# Define regions of interest (Glasser atlas area category, or explicit ROI names)
visual_rois = pyavs.get_glasser_roi_labels(area='early_visual')

# Extract ROI data
print("Extracting ROI data...")
roi_data = pyavs.extract_roi_data(
    source_data,
    forward_model['src'],
    roi_labels=visual_rois,
    method='mean',  # Average within each ROI
    verbose=True
)

print(f"✓ Extracted data from {len(roi_data)} ROIs")
for roi_name, data in roi_data.items():
    print(f"  {roi_name}: {data.shape} (epochs × timepoints)")

# Compute population codes for experimental conditions
print("Computing population codes...")
population_codes = pyavs.compute_population_codes(
    source_data,
    events_metadata=fixation_epochs.metadata,
    conditions=['scene_id'],  # column(s) in metadata defining conditions
    time_window=(0.0, 0.3),   # analysis window: 0-300ms post-fixation
    times=fixation_epochs.times,
)

print(f"✓ Population codes computed")
print(f"Available data: {list(population_codes.keys())}")

# Save population codes for further analysis
h5_path = pyavs.save_population_codes_h5(
    population_codes=population_codes,
    metadata=fixation_epochs.metadata,
    subject_id=1,
    session=1,
    event_type='fixation',
    sampling_rate=int(fixation_epochs.info['sfreq']),
    rois=list(roi_data.keys()),
    times=fixation_epochs.times,
    filter_params={'l_freq': 0.2, 'h_freq': 200.0}
)

print(f"✓ Population codes saved: {h5_path}")

Alternative: Source Reconstruction with Functional API

For comparison, here’s the functional API approach:

# Load MEG and eye tracking data separately
meg_raw = pyavs.load_meg_raw(subject_id=1, session=1, run=1)
explog, eye_events = pyavs.load_and_enrich_eye_events([1], [1])

# Apply MEG preprocessing
meg_clean = pyavs.preprocess_meg_block(
    meg_raw,
    subject_id=1, session=1, block=1,
    l_freq=0.2, h_freq=200.0,
)

# Create epochs from eye tracking events -- returns (epochs, epochs_dataframe)
epochs, epochs_df = pyavs.create_et_event_epochs(
    meg_clean, eye_events,
    event_type='fixation',
    tmin=-0.2, tmax=0.5
)

# Load forward model and apply source reconstruction
forward_model = pyavs.load_forward_model(subject_id=1, session=1)
source_data = pyavs.apply_source_reconstruction(
    epochs, forward_model, method='beamformer'
)

print(f"Functional API: {len(source_data)} epochs of source data")

Next Steps

After completing this workflow, you can:

  • Perform statistical analysis on the population codes

  • Create encoding models relating brain activity to visual features

  • Analyze temporal dynamics of visual processing

  • Compare activity across different experimental conditions

See the Examples for more specific analysis examples.

Troubleshooting

Common issues and solutions:

Synchronization Problems
  • Check trigger channels are properly recorded

  • Verify eye tracker and MEG system clocks

  • Use cross-correlation method if triggers are unreliable

ICA Convergence Issues
  • Reduce number of components (try 15-20)

  • Filter data more aggressively (e.g., 1-30 Hz)

  • Check for bad channels before ICA

Memory Issues
  • Process data in smaller chunks

  • Use lower source space resolution

  • Apply decimation to reduce sampling rate

For more help, see the Frequently Asked Questions guide.