EEG-Network-Viewer / src /metrics.py
stardust-coder's picture
[add] directed graph
e648c90
import numpy as np
from scipy import stats
def chatterjee_phase_to_amp(phi, amp, agg="max"):
"""
phi: phase in radians (1D)
amp: amplitude (1D)
agg: 'max' | 'mean' | 'rss'
"""
s = np.sin(phi)
c = np.cos(phi)
xi_s = stats.chatterjeexi(s, amp).statistic
xi_c = stats.chatterjeexi(c, amp).statistic
if agg == "max":
xi = np.nanmax([xi_s, xi_c])
elif agg == "mean":
xi = np.nanmean([xi_s, xi_c])
elif agg == "rss":
xi = np.sqrt(xi_s**2 + xi_c**2)
xi = float(np.clip(xi, 0.0, 1.0))
else:
raise ValueError("agg must be 'max', 'mean', or 'rss'")
return xi #, {"xi_sin": xi_s, "xi_cos": xi_c}
def circular_correlation(rho, theta, mu=None, tau=None):
rho = np.asarray(rho)
theta = np.asarray(theta)
if mu is None:
mu = np.angle(np.mean(np.exp(1j * rho)))
if tau is None:
tau = np.angle(np.mean(np.exp(1j * theta)))
x = np.sin(rho - mu)
y = np.sin(theta - tau)
return np.mean(x * y) / np.sqrt(np.var(x) * np.var(y))
def modulation_index(phase, amp, n_bins=18, eps=1e-12):
"""
Tort et al. (2010) Modulation Index
phase : radians (-pi, pi]
amp : amplitude envelope (>=0)
"""
phase = np.asarray(phase).ravel()
amp = np.asarray(amp).ravel()
mask = np.isfinite(phase) & np.isfinite(amp)
phase = phase[mask]
amp = amp[mask]
# phase bins
edges = np.linspace(-np.pi, np.pi, n_bins + 1)
bins = np.digitize(phase, edges) - 1
bins = np.clip(bins, 0, n_bins - 1)
mean_amp = np.zeros(n_bins)
for k in range(n_bins):
if np.any(bins == k):
mean_amp[k] = amp[bins == k].mean()
if mean_amp.sum() == 0:
return np.nan
p = mean_amp / mean_amp.sum()
uniform = 1.0 / n_bins
kl = np.sum(p * np.log((p + eps) / uniform))
mi = kl / np.log(n_bins)
return mi