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:
Composer Initialization: Configure MEG-ET processing parameters
MEG Data Loading: Load and preprocess MEG data with Maxwell filtering, ICA
Eye Tracking Integration: Load ET data and create trigger-based alignment
Event-based Epoching: Create MEG epochs around eye tracking events
Source Reconstruction: Transform sensor data to source space (optional)
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()
Method 1: AVSComposer Approach (Recommended)¶
The AVSComposer provides a streamlined, robust pipeline for MEG-ET data processing:
import pyavs
# Set up data path
pyavs.set_data_path('/path/to/avs/dataset')
# Initialize AVS Composer with optimal settings
composer = pyavs.AVSComposer(
subject=1,
session_num=1,
preprocessed=True, # Use preprocessed MEG data
interpolate_bad_channels=True, # Handle bad channels
use_precomputed_ica=True, # Use existing ICA solutions
l_freq=0.2, # High-pass filter
h_freq=200.0, # Low-pass filter
resample_freq=500.0, # Target sampling rate
causal_filter=True, # Preserve temporal order
verbose=True
)
# Load MEG data (automatic preprocessing)
composer.load_meg_data()
print(f"Loaded MEG blocks: {list(composer.raws_dict.keys())}")
# Apply ICA artifact removal
composer.apply_ica_to_blocks()
# Filter data if needed
composer.filter_meg_data() # Uses configured filter parameters
# Concatenate MEG blocks
composer.concatenate_raws_per_session()
# Find MEG trigger events
composer.find_events_in_raw()
print(f"Found {len(composer.meg_trigger_events)} MEG trigger events")
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.