-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathsurface laplacian.py
137 lines (108 loc) · 4.93 KB
/
surface laplacian.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def surface_laplacian(epochs, leg_order, m, smoothing, montage):
"""
This function applies the surface laplacian transform to an mne Epochs object. The
algorithm follows the formulations of Perrin et al. (1989) and it consists for the most part in a
nearly-literal translation of Mike X Cohen's 'Analyzing neural time series data' corresponding MATLAB
code (2014).
INPUTS are:
- epochs: mne Epochs object
- leg_order: maximum order of the Legendre polynomial
- m: smothness parameter for G and H
- smoothing: smothness parameter for the diagonal of G
- montage: montage to reconstruct the transformed Epochs object (same as in raw data import)
OUTPUTS are:
- before: unaffected reconstruction of the original Epochs object
- after: surface laplacian transform of the original Epochs object
References:
- Perrin, F., Pernier, J., Bertrand, O. & Echallier, J.F. (1989). Spherical splines for scalp
potential and current density mapping. Electroencephalography and clinical Neurophysiology, 72,
184-187.
- Cohen, M.X. (2014). Surface Laplacian In Analyzing neural time series data: theory and practice
(pp. 275-290). London, England: The MIT Press.
"""
# import libraries
import numpy as np
from scipy import special
import math
import mne
# get electrodes positions
locs = epochs._get_channel_positions()
x = locs[:,0]
y = locs[:,1]
z = locs[:,2]
# arrange data
data = epochs.get_data() # data
data = np.rollaxis(data, 0, 3)
orig_data_size = np.squeeze(data.shape)
numelectrodes = len(x)
# normalize cartesian coordenates to sphere unit
def cart2sph(x, y, z):
hxy = np.hypot(x, y)
r = np.hypot(hxy, z)
el = np.arctan2(z, hxy)
az = np.arctan2(y, x)
return az, el, r
junk1, junk2, spherical_radii = cart2sph(x,y,z)
maxrad = np.max(spherical_radii)
x = x/maxrad
y = y/maxrad
z = z/maxrad
# compute cousine distance between all pairs of electrodes
cosdist = np.zeros((numelectrodes, numelectrodes))
for i in range(numelectrodes):
for j in range(i+1,numelectrodes):
cosdist[i,j] = 1 - (((x[i] - x[j])**2 + (y[i] - y[j])**2 + (z[i] - z[j])**2)/2)
cosdist = cosdist + cosdist.T + np.identity(numelectrodes)
# get legendre polynomials
legpoly = np.zeros((leg_order, numelectrodes, numelectrodes))
for ni in range(leg_order):
for i in range(numelectrodes):
for j in range(i+1, numelectrodes):
#temp = special.lpn(8,cosdist[0,1])[0][8]
legpoly[ni,i,j] = special.lpn(ni+1,cosdist[i,j])[0][ni+1]
legpoly = legpoly + np.transpose(legpoly,(0,2,1))
for i in range(leg_order):
legpoly[i,:,:] = legpoly[i,:,:] + np.identity(numelectrodes)
# compute G and H matrixes
twoN1 = np.multiply(2, range(1, leg_order+1))+1
gdenom = np.power(np.multiply(range(1, leg_order+1), range(2, leg_order+2)), m, dtype=float)
hdenom = np.power(np.multiply(range(1, leg_order+1), range(2, leg_order+2)), m-1, dtype=float)
G = np.zeros((numelectrodes, numelectrodes))
H = np.zeros((numelectrodes, numelectrodes))
for i in range(numelectrodes):
for j in range(i, numelectrodes):
g = 0
h = 0
for ni in range(leg_order):
g = g + (twoN1[ni] * legpoly[ni,i,j]) / gdenom[ni]
h = h - (twoN1[ni] * legpoly[ni,i,j]) / hdenom[ni]
G[i,j] = g / (4*math.pi)
H[i,j] = -h / (4*math.pi)
G = G + G.T
H = H + H.T
G = G - np.identity(numelectrodes) * G[1,1] / 2
H = H - np.identity(numelectrodes) * H[1,1] / 2
if np.any(orig_data_size==1):
data = data[:]
else:
data = np.reshape(data, (orig_data_size[0], np.prod(orig_data_size[1:3])))
# compute C matrix
Gs = G + np.identity(numelectrodes) * smoothing
GsinvS = np.sum(np.linalg.inv(Gs), 0)
dataGs = np.dot(data.T, np.linalg.inv(Gs))
C = dataGs - np.dot(np.atleast_2d(np.sum(dataGs, 1)/np.sum(GsinvS)).T, np.atleast_2d(GsinvS))
# apply transform
original = np.reshape(data, orig_data_size)
surf_lap = np.reshape(np.transpose(np.dot(C,np.transpose(H))), orig_data_size)
# re-arrange data into mne's Epochs object
events = epochs.events
event_id = epochs.event_id
ch_names = epochs.ch_names
sfreq = epochs.info['sfreq']
tmin = epochs.tmin
info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types='eeg', montage=montage)
original = np.rollaxis(original, 2, 0)
surf_lap = np.rollaxis(surf_lap, 2, 0)
before = mne.EpochsArray(data=original, info=info, events=events, event_id=event_id, tmin=tmin, on_missing='ignore')
after = mne.EpochsArray(data=surf_lap, info=info, events=events, event_id=event_id, tmin=tmin, on_missing='ignore')
return before, after