Analysis
A lead field for a small device
Build the forward model for five electrodes 2 cm apart near Oz, a stand-in for one small device, on a four-shell sphere with the skull at a fifteenth of the brain's conductivity. Reference four channels to the fifth, then find two different source patterns that give the same channels, invert a shallow and a deep source, and see how a source's reach falls with depth.
import numpy as np
import mne
# A spherical head of radius 9 cm in four shells: brain, cerebrospinal fluid, skull and scalp,
# with the skull at a fifteenth of the brain's conductivity.
sphere = mne.make_sphere_model(r0=(0.0, 0.0, 0.0), head_radius=0.09,
relative_radii=(0.90, 0.92, 0.97, 1.0),
sigmas=(0.33, 1.0, 0.33 / 15, 0.33), verbose="error")
def on_scalp(azimuth, elevation, r=0.09):
"""A point at radius r, by degrees around from the back of the head and up from the ears."""
a, e = np.radians(azimuth), np.radians(elevation)
return r * np.array([np.cos(e) * np.sin(a), -np.cos(e) * np.cos(a), np.sin(e)])
# Five electrodes 2 cm apart near Oz: a stand-in for one small device. E5 is the reference.
step = np.degrees(0.02 / 0.09)
spots = [(0, 20 + step), (step, 20), (0, 20 - step), (-step, 20), (0, 20)]
names = ["E1", "E2", "E3", "E4", "E5"]
info = mne.create_info(names, 500.0, "eeg")
info.set_montage(mne.channels.make_dig_montage(
ch_pos={n: on_scalp(*s) for n, s in zip(names, spots)}, coord_frame="head"))
# Candidate sources on a 1 cm grid inside the brain, each free to point any way.
src = mne.setup_volume_source_space(sphere=sphere, pos=10.0, mindist=5.0, exclude=20.0,
verbose="error")
fwd = mne.make_forward_solution(info, trans=None, src=src, bem=sphere, eeg=True, meg=False,
verbose="error")
G = fwd["sol"]["data"] # 5 electrodes x 3 orientations per source, volts per A m
pos = fwd["source_rr"]
D = np.hstack([np.eye(4), -np.ones((4, 1))]) # each channel against E5
L = D @ G
sv = np.linalg.svd(L, compute_uv=False)
print(f"{len(pos)} sources, {L.shape[1]} unknowns, rank {np.linalg.matrix_rank(L)}")
print("singular values, relative to the largest:", np.round(sv / sv[0], 2))
# Two different patterns of sources, the same four channels.
u = on_scalp(0, 20) / 0.09 # the direction from the center to the device
def source_at(depth, moment=10e-9):
"""A source of `moment` A m pointing out of the head, `depth` m under the device."""
k = int(np.argmin(np.linalg.norm(pos - u * (0.09 - depth), axis=1)))
s = np.zeros(L.shape[1])
s[3 * k:3 * k + 3] = moment * pos[k] / np.linalg.norm(pos[k])
return s, k
s, k = source_at(0.02)
null = np.linalg.svd(L)[2][4:] # 5,432 patterns of sources the channels can't see
other = s + 1e-7 * null[0] # add one of them, ten times the source's size
print("one source, microvolts: ", np.round(1e6 * L @ s, 3))
print("a different pattern, same:", np.round(1e6 * L @ other, 3))
# The minimum-norm estimate, for a source 2 cm and 5 cm under the device.
lam = 1e-4 * np.trace(L @ L.T) / 4
W = L.T @ np.linalg.inv(L @ L.T + lam * np.eye(4))
deep = lambda i: 100 * (0.09 - np.linalg.norm(pos[i]))
for depth in (0.02, 0.05):
s, k = source_at(depth)
peak = int(np.argmax(np.linalg.norm((W @ (L @ s)).reshape(-1, 3), axis=1)))
away = 100 * np.linalg.norm(pos[peak] - pos[k])
print(f"a source {deep(k):.1f} cm deep: the estimate peaks {deep(peak):.1f} cm deep, "
f"{away:.1f} cm from it")
# How strongly a source reaches the channels, by depth: against E5, and against infinity.
for depth in (0.02, 0.03, 0.04, 0.05, 0.06):
s, k = source_at(depth)
local, far = 1e6 * np.abs(L @ s).max(), 1e6 * np.abs(G[4] @ s)
print(f"{deep(k):.1f} cm deep: largest channel against E5 {local:.2f} uV, "
f"E5 against infinity {far:.2f} uV")
You should seeRank 4 among 5,436 unknowns. A second pattern, the source plus an invisible one ten times its size, giving identical channels. The estimate of a source 1.7 cm deep in the right place, and of one 4.9 cm deep at 1.7 cm, 3.2 cm away. From 1.7 to 4.9 cm deep, the largest local channel falls from 5.40 to 0.43 microvolts, twelvefold, and the electrode above the source against infinity only fourfold, from 8.74 to 2.15.
Then report: how the depth falloff changes with the spacing of the electrodes, at 1, 2 and 4 cm, and with the skull's ratio at 15 and at 80. Which of your conclusions depend on the head model?