---
title: "Cross-Modal Coupling Analysis with PhysioCrossModal"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Cross-Modal Coupling Analysis with PhysioCrossModal}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

## Introduction

**PhysioCrossModal** provides a comprehensive toolkit for analyzing coupling
and synchrony between physiological signals from different modalities (e.g.,
EEG, EMG, ECG, EDA, MoCap). It extends the PhysioCore ecosystem with:

- The `MultiPhysioExperiment` container for multi-modal recordings at different
  sampling rates
- Spectral coherence (Welch and multitaper methods)
- Phase synchrony measures (PLV, PLI, wPLI)
- Directed coupling (Granger causality)
- Time-domain coupling (cross-correlation, sliding-window)
- Wavelet time-frequency coherence and PLV
- Statistical significance testing via surrogates and bootstrap

This vignette walks through a complete cross-modal coupling analysis workflow.

## Creating Synthetic Data

PhysioCrossModal includes three data generators for quick prototyping and
testing. We start by creating a simulated EEG-EMG dataset with corticomuscular
coherence (CMC) at 20 Hz.

```{r create-data}
library(PhysioCrossModal)

# Simulate EEG (500 Hz) and EMG (1000 Hz) with CMC at 20 Hz
data <- make_eeg_emg(
  n_sec = 10,
  n_eeg_ch = 3,
  n_emg_ch = 2,
  sr_eeg = 500,
  sr_emg = 1000,
  cmc_freq = 20
)

pe_eeg <- data$eeg
pe_emg <- data$emg
```

## The MultiPhysioExperiment Container

When you have signals from multiple modalities recorded simultaneously, the
`MultiPhysioExperiment` class provides a unified container. Each modality is
stored as a named `PhysioExperiment` with its own sampling rate.

```{r create-mpe}
# Wrap modalities into a MultiPhysioExperiment
mpe <- MultiPhysioExperiment(EEG = pe_eeg, EMG = pe_emg)
mpe

# Inspect
modalities(mpe)         # c("EEG", "EMG")
samplingRates(mpe)      # EEG=500, EMG=1000
nModalities(mpe)        # 2

# Extract a single modality
eeg <- mpe[["EEG"]]
```

### Signal Alignment

When coupling functions receive signals at different sampling rates, they
automatically resample to a common rate. You can also align signals explicitly:

```{r alignment}
# Align both modalities to the lowest sampling rate
mpe_aligned <- alignSignals(EEG = pe_eeg, EMG = pe_emg,
                             method = "lowest_rate")
samplingRates(mpe_aligned)  # Both 500 Hz

# Or resample a single PhysioExperiment
pe_emg_500 <- alignToRate(pe_emg, target_rate = 500)
```

## Spectral Coherence

Coherence measures the linear frequency-domain relationship between two
signals. Values range from 0 (no linear relationship) to 1 (perfect linear
relationship) at each frequency.

### Welch-Method Coherence

```{r coherence}
# Coherence between first EEG and first EMG channels
coh <- coherence(pe_eeg, pe_emg,
                  channels_x = 1, channels_y = 1,
                  nperseg = 256L)

# Peak coherence and its frequency
peak_idx <- which.max(coh$coherence)
cat("Peak coherence:", round(coh$coherence[peak_idx], 3),
    "at", round(coh$frequencies[peak_idx], 1), "Hz\n")
cat("95% confidence limit:", round(coh$confidence_limit, 3), "\n")
```

```{r plot-coherence}
# Visualize the coherence spectrum
if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotCoherenceSpectrum(coh)
}
```

### Multitaper Coherence

For short signals or when frequency resolution is critical, multitaper
estimation provides lower variance than Welch's method:

```{r multitaper}
mt_coh <- multitaperCoherence(pe_eeg, pe_emg,
                               channels_x = 1, channels_y = 1,
                               nw = 4, freq_range = c(1, 50))
```

### Cross-Spectral Density

The cross-spectrum captures both magnitude and phase relationships:

```{r cross-spectrum}
csd <- crossSpectrum(pe_eeg, pe_emg,
                      channels_x = 1, channels_y = 1,
                      nperseg = 256L)
# Phase relationship at 20 Hz
idx_20 <- which.min(abs(csd$frequencies - 20))
cat("Phase at 20 Hz:", round(csd$phase[idx_20], 3), "radians\n")
```

## Phase Synchrony

Phase-based measures quantify the consistency of the phase relationship
between signals, independent of amplitude.

### Phase Locking Value (PLV)

PLV measures phase consistency over time. It requires specifying a frequency
band for bandpass filtering:

```{r plv}
plv_result <- phaseLockingValue(pe_eeg, pe_emg,
                                 channels_x = 1, channels_y = 1,
                                 freq_band = c(18, 22))
cat("PLV:", round(plv_result$plv, 3), "\n")
```

### Phase Lag Index (PLI)

PLI is insensitive to zero-lag (volume conduction) effects:

```{r pli}
pli_result <- phaseLagIndex(pe_eeg, pe_emg,
                             channels_x = 1, channels_y = 1,
                             freq_band = c(18, 22))
cat("PLI:", round(pli_result$pli, 3), "\n")
```

### Weighted PLI (wPLI)

wPLI reduces noise sensitivity by weighting phase differences by their
cross-spectral magnitude:

```{r wpli}
wpli_result <- weightedPLI(pe_eeg, pe_emg,
                            channels_x = 1, channels_y = 1,
                            freq_band = c(18, 22))
cat("wPLI:", round(wpli_result$wpli, 3), "\n")
cat("Debiased wPLI:", round(wpli_result$wpli_debiased, 3), "\n")
```

## Directed Coupling: Granger Causality

Granger causality tests whether one signal's past helps predict another
signal beyond its own history, indicating directional information flow.

```{r granger}
# Create directed signals: x drives y with a 10-sample lag
dir_data <- make_directed_signals(n = 5000, sr = 500,
                                   lag_samples = 10, coupling = 0.7)

gc <- grangerCausality(dir_data$x, dir_data$y, sr = 500, order = 15)
cat("GC x->y:", round(gc$gc_xy, 4), "\n")
cat("GC y->x:", round(gc$gc_yx, 4), "\n")
cat("Net GC:",  round(gc$net_gc, 4), "(positive = x drives y)\n")
```

## Time-Domain Coupling

### Cross-Correlation

Cross-correlation identifies time delays between signals:

```{r cross-correlation}
cc <- crossCorrelation(dir_data$x, dir_data$y, sr = 500)
cat("Peak lag:", cc$peak_lag, "samples",
    "(", round(cc$peak_lag_seconds, 4), "s)\n")
cat("Peak correlation:", round(cc$peak_correlation, 3), "\n")
```

### Sliding-Window Cross-Correlation

Track how coupling varies over time:

```{r sliding-xcorr}
sliding <- slidingCrossCorrelation(dir_data$x, dir_data$y, sr = 500,
                                    window_sec = 2, step_sec = 0.5)

# Visualize time course
if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotCouplingTimecourse(sliding)
}
```

## Wavelet Time-Frequency Analysis

Wavelet methods reveal how coupling varies jointly in time and frequency.

### Wavelet Coherence

```{r wavelet-coherence}
signals <- make_coupled_signals(sr1 = 200, sr2 = 200,
                                 coupling_freq = 10, duration = 5)

wcoh <- waveletCoherence(signals$x, signals$y, sr = 200,
                          frequencies = seq(2, 30),
                          n_cycles = 7)

# Visualize time-frequency coherence
if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotWaveletCoherence(wcoh)
}
```

### Wavelet PLV

```{r wavelet-plv}
wplv <- waveletPLV(signals$x, signals$y, sr = 200,
                    frequencies = seq(2, 30),
                    n_cycles = 7)

if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotWaveletCoherence(wplv)  # auto-detects PLV vs coherence
}
```

## Unified Interface: couplingAnalysis()

The `couplingAnalysis()` wrapper dispatches to any coupling method with a
uniform API:

```{r coupling-analysis}
# Same call structure for any method
res_coh <- couplingAnalysis(pe_eeg, pe_emg,
                             method = "coherence",
                             channels_x = 1, channels_y = 1,
                             nperseg = 256L)

res_plv <- couplingAnalysis(pe_eeg, pe_emg,
                             method = "plv",
                             channels_x = 1, channels_y = 1,
                             freq_band = c(18, 22))

res_gc  <- couplingAnalysis(dir_data$x, dir_data$y,
                             method = "granger",
                             sr = 500, order = 15)

# Also works directly with MultiPhysioExperiment
res_mpe <- couplingAnalysis(mpe,
                             modality_x = "EEG", modality_y = "EMG",
                             method = "coherence",
                             channels_x = 1, channels_y = 1)
```

## Multi-Channel Coupling Matrices

Compute coupling between all pairs of channels across two modalities:

```{r coupling-matrix}
# Coherence matrix
coh_mat <- coherenceMatrix(pe_eeg, pe_emg, nperseg = 128L)
coh_mat$matrix

# Generic coupling matrix (any method)
pli_mat <- couplingMatrix(pe_eeg, pe_emg,
                           method = "coherence",
                           nperseg = 128L)

# Visualize as heatmap
if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotCouplingMatrix(coh_mat$matrix, title = "EEG-EMG Coherence")
}
```

## Statistical Significance Testing

### Surrogate Testing

Test whether observed coupling exceeds what would be expected by chance,
using phase-randomized or time-shifted surrogates:

```{r surrogate-test}
sig_test <- surrogateTest(pe_eeg, pe_emg,
                           method = "coherence",
                           channels_x = 1, channels_y = 1,
                           n_surrogates = 199,
                           surrogate_type = "phase",
                           nperseg = 128L)
cat("Observed statistic:", round(sig_test$statistic, 3), "\n")
cat("p-value:", round(sig_test$p_value, 4), "\n")
cat("95th percentile threshold:", round(sig_test$threshold_95, 3), "\n")
```

### Bootstrap Confidence Intervals

Estimate the precision of the coupling statistic using block bootstrap:

```{r bootstrap-ci}
boot <- bootstrapCI(pe_eeg, pe_emg,
                     method = "coherence",
                     channels_x = 1, channels_y = 1,
                     n_boot = 199,
                     ci = 0.95,
                     nperseg = 128L)
cat("Observed:", round(boot$statistic, 3), "\n")
cat("95% CI: [", round(boot$ci_lower, 3), ",",
    round(boot$ci_upper, 3), "]\n")
```

### Matrix-Level Significance with Multiple Comparison Correction

Test significance for every channel pair simultaneously, with FDR or
Bonferroni correction:

```{r surrogate-matrix}
mat_test <- surrogateMatrixTest(pe_eeg, pe_emg,
                                 method = "coherence",
                                 n_surrogates = 99,
                                 correction = "fdr",
                                 alpha = 0.05,
                                 nperseg = 128L)
mat_test$significant   # Logical matrix: which pairs are significant
mat_test$p_adjusted    # FDR-corrected p-values
```

## Complete Workflow Example

Putting it all together -- a minimal script for analyzing EEG-EMG
corticomuscular coherence:

```{r workflow}
library(PhysioCrossModal)

# 1. Create or load data
data <- make_eeg_emg(n_sec = 10, sr_eeg = 500, sr_emg = 1000)

# 2. Wrap into MultiPhysioExperiment
mpe <- MultiPhysioExperiment(EEG = data$eeg, EMG = data$emg)

# 3. Compute coherence matrix
coh_mat <- coherenceMatrix(mpe,
                            modality_x = "EEG", modality_y = "EMG",
                            nperseg = 256L,
                            freq_range = c(15, 25))

# 4. Test significance with FDR correction
sig <- surrogateMatrixTest(mpe,
                            modality_x = "EEG", modality_y = "EMG",
                            method = "coherence",
                            n_surrogates = 199,
                            correction = "fdr",
                            nperseg = 256L,
                            freq_range = c(15, 25))

# 5. Visualize
if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotCouplingMatrix(sig$matrix, title = "CMC (15-25 Hz)")
}

# 6. Detailed spectrum for the most coupled pair
best <- which(sig$matrix == max(sig$matrix, na.rm = TRUE), arr.ind = TRUE)
coh_detail <- coherence(mpe,
                         modality_x = "EEG", modality_y = "EMG",
                         channels_x = best[1, 1],
                         channels_y = best[1, 2])
if (requireNamespace("ggplot2", quietly = TRUE)) {
  plotCoherenceSpectrum(coh_detail)
}
```

## Session Information

```{r session-info}
sessionInfo()
```
