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()`` Method 1: AVSComposer Approach (Recommended) ---------------------------------------------- The AVSComposer provides a streamlined, robust pipeline for MEG-ET data processing: .. code-block:: python 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: .. code-block:: python # 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. .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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 :doc:`../examples/index` 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 :doc:`../reference/faq` guide.