Skip to content

Commit d8913e1

Browse files
authored
Merge pull request #14 from ruuskas/external
Add unit test for dics_coherence_external
2 parents e7881c9 + f6ccc50 commit d8913e1

1 file changed

Lines changed: 134 additions & 43 deletions

File tree

tests/test_connectivity.py

Lines changed: 134 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
one_to_all_connectivity_pairs,
1616
read_connectivity,
1717
restrict_forward_to_vertices,
18+
dics_coherence_external
1819
)
1920
from conpy.connectivity import _BaseConnectivity, _get_vert_ind_from_label
2021
from mne import BiHemiLabel, Label, SourceEstimate
22+
from mne.beamformer import make_dics
2123
from mne.datasets import testing
2224
from mne.time_frequency import csd_morlet
2325
from mne.utils import _TempDir
@@ -60,7 +62,7 @@ def _load_restricted_forward(source_vertno1, source_vertno2):
6062
return fwd_free, fwd_fixed
6163

6264

63-
def _simulate_data(fwd_fixed, source_vertno1, source_vertno2):
65+
def _simulate_data(fwd_fixed, source_vertno1, source_vertno2, external=False):
6466
"""Simulate two oscillators on the cortex."""
6567
sfreq = 50.0 # Hz.
6668
base_freq = 10
@@ -86,18 +88,32 @@ def _simulate_data(fwd_fixed, source_vertno1, source_vertno2):
8688
signal2 += 1e-8 * np.random.randn(len(times))
8789

8890
# Construct a SourceEstimate object
91+
if external:
92+
source_data = signal1[np.newaxis, :]
93+
vertices = [np.array([source_vertno1]), np.array([])]
94+
# Create an info object that holds information about the sensors
95+
info = mne.create_info(
96+
fwd_fixed["info"]["ch_names"] + ["external"],
97+
sfreq,
98+
ch_types=["grad"] * fwd_fixed["info"]["nchan"] + ["misc"],
99+
)
100+
else:
101+
source_data = np.vstack((signal1[np.newaxis, :], signal2[np.newaxis, :]))
102+
vertices = [np.array([source_vertno1]), np.array([source_vertno2])]
103+
# Create an info object that holds information about the sensors
104+
info = mne.create_info(fwd_fixed["info"]["ch_names"], sfreq,
105+
ch_types="grad")
106+
89107
stc = mne.SourceEstimate(
90-
np.vstack((signal1[np.newaxis, :], signal2[np.newaxis, :])),
91-
vertices=[np.array([source_vertno1]), np.array([source_vertno2])],
108+
source_data,
109+
vertices=vertices,
92110
tmin=0,
93111
tstep=1 / sfreq,
94112
subject="sample",
95113
)
96-
97-
# Create an info object that holds information about the sensors
98-
info = mne.create_info(fwd_fixed["info"]["ch_names"], sfreq, ch_types="grad")
99-
with info._unlock():
100-
info.update(fwd_fixed["info"]) # Merge in sensor position information
114+
# Merge in sensor position information
115+
for info_ch, fwd_ch in zip(info["chs"], fwd_fixed["info"]["chs"]):
116+
info_ch.update(fwd_ch)
101117

102118
# Simulated sensor data.
103119
raw = mne.apply_forward_raw(fwd_fixed, stc, info)
@@ -106,6 +122,11 @@ def _simulate_data(fwd_fixed, source_vertno1, source_vertno2):
106122
noise = random.randn(*raw._data.shape) * 1e-14
107123
raw._data += noise
108124

125+
if external:
126+
sensor_data = raw.get_data()
127+
sensor_data = np.vstack((sensor_data, np.atleast_2d(signal2)))
128+
raw = mne.io.RawArray(sensor_data, info)
129+
109130
# Define a single epoch
110131
epochs = mne.Epochs(
111132
raw,
@@ -118,7 +139,7 @@ def _simulate_data(fwd_fixed, source_vertno1, source_vertno2):
118139
)
119140

120141
# Compute the cross-spectral density matrix
121-
csd = csd_morlet(epochs, frequencies=[10, 20])
142+
csd = csd_morlet(epochs, picks=['meg', 'misc'], frequencies=[10, 20])
122143

123144
return csd
124145

@@ -173,43 +194,43 @@ def _generate_labels(vertices, n_labels):
173194
def test_base_connectivity():
174195
"""Test construction of BaseConnectivity."""
175196
# Pairs and data shape don't match
176-
baseCon = _BaseConnectivity([0.5, 0.5, 0.5], [[1, 1, 2], [2, 3, 3]], 4)
177-
assert_array_equal(baseCon.data, [0.5, 0.5, 0.5])
197+
base_con = _BaseConnectivity([0.5, 0.5, 0.5], [[1, 1, 2], [2, 3, 3]], 4)
198+
assert_array_equal(base_con.data, [0.5, 0.5, 0.5])
178199

179200
with pytest.raises(ValueError):
180-
baseCon = _BaseConnectivity([0.5, 0.5, 0.5], [[1, 1], [2, 3]], 3)
201+
base_con = _BaseConnectivity([0.5, 0.5, 0.5], [[1, 1], [2, 3]], 3)
181202

182203
# Not enough sources
183204
with pytest.raises(ValueError):
184-
baseCon = _BaseConnectivity([0.5, 0.5, 0.5], [[1, 1, 2], [2, 3, 3]], 2)
205+
base_con = _BaseConnectivity([0.5, 0.5, 0.5], [[1, 1, 2], [2, 3, 3]], 2)
185206

186207
# Incorrecly shaped source degree
187208
with pytest.raises(ValueError):
188-
baseCon = _BaseConnectivity(
209+
base_con = _BaseConnectivity(
189210
[0.5, 0.5, 0.5],
190211
[[1, 1, 2], [2, 3, 3]],
191212
4,
192213
source_degree=([0, 2, 1], [0, 0, 1]),
193214
)
194215

195-
baseCon = _make_base_connectivity()
216+
base_con = _make_base_connectivity()
196217
assert_array_equal(
197-
baseCon.source_degree,
218+
base_con.source_degree,
198219
np.array([[0, 2, 2, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 2, 2, 0, 0, 0, 0, 0]]),
199220
)
200221

201222
# Test properties
202-
assert baseCon.n_connections == 5
223+
assert base_con.n_connections == 5
203224
state = {
204225
"data": np.array([1, 1, 1]),
205226
"pairs": [[2, 2, 3], [3, 4, 4]],
206227
"n_sources": 5,
207228
"subject": None,
208229
"directed": False,
209230
}
210-
baseCon.__setstate__(state)
211-
assert baseCon.n_sources == 5
212-
assert_array_equal(baseCon.source_degree[0], [0, 0, 2, 1, 0])
231+
base_con.__setstate__(state)
232+
assert base_con.n_sources == 5
233+
assert_array_equal(base_con.source_degree[0], [0, 0, 2, 1, 0])
213234

214235

215236
def test_alltoall_connectivity():
@@ -269,8 +290,8 @@ def test_label_connnectivity():
269290

270291
def test_connectivity_repr():
271292
"""Test string representation of connectivity classes."""
272-
baseCon = _make_base_connectivity()
273-
assert str(baseCon) == (
293+
base_con = _make_base_connectivity()
294+
assert str(base_con) == (
274295
"<_BaseConnectivity | n_sources=10, n_conns=5," " subject=None>"
275296
)
276297

@@ -334,58 +355,58 @@ def test_connectivity_save():
334355

335356
def test_adjacency():
336357
"""Test adjacency matrix."""
337-
baseCon = _make_base_connectivity()
338-
adjmat = baseCon.get_adjacency()
339-
assert adjmat.nnz == 2 * baseCon.n_connections
340-
assert adjmat.shape == (baseCon.n_sources, baseCon.n_sources)
358+
base_con = _make_base_connectivity()
359+
adjmat = base_con.get_adjacency()
360+
assert adjmat.nnz == 2 * base_con.n_connections
361+
assert adjmat.shape == (base_con.n_sources, base_con.n_sources)
341362

342363
# Directed
343-
baseCon.directed = True
344-
adjmat = baseCon.get_adjacency()
345-
assert adjmat.nnz == baseCon.n_connections
346-
assert adjmat.shape == (baseCon.n_sources, baseCon.n_sources)
364+
base_con.directed = True
365+
adjmat = base_con.get_adjacency()
366+
assert adjmat.nnz == base_con.n_connections
367+
assert adjmat.shape == (base_con.n_sources, base_con.n_sources)
347368

348369

349370
def test_connectivity_threshold():
350371
"""Test thresholding function of BaseConnectivity."""
351372
# Criterion = None
352-
baseCon = _make_base_connectivity()
353-
threshCon = baseCon.threshold(2, copy=True)
373+
base_con = _make_base_connectivity()
374+
threshCon = base_con.threshold(2, copy=True)
354375
assert threshCon.n_connections == 3
355-
assert baseCon.n_connections == 5
376+
assert base_con.n_connections == 5
356377
assert_array_equal(threshCon.data, np.array([3, 4, 5]))
357378

358-
threshCon = baseCon.copy()
379+
threshCon = base_con.copy()
359380
threshCon.threshold(2, direction="below", copy=False)
360381
assert threshCon.n_connections == 1
361382
assert_array_equal(threshCon.data, np.array([1]))
362383

363384
# Incorrect direction
364385
with pytest.raises(ValueError):
365-
baseCon.threshold(1, direction="wrong")
386+
base_con.threshold(1, direction="wrong")
366387

367388
# Use criterion
368389
with pytest.raises(ValueError):
369-
baseCon.threshold(1, crit=np.array([0, 0, 1, 2]))
390+
base_con.threshold(1, crit=np.array([0, 0, 1, 2]))
370391

371392
pval = np.array([0.04, 0.7, 0.001, 0.1, 0.06])
372-
threshCon = baseCon.threshold(0.05, crit=pval, copy=True)
393+
threshCon = base_con.threshold(0.05, crit=pval, copy=True)
373394
assert_array_equal(threshCon.data, np.array([2, 4, 5]))
374395

375396

376397
def test_compatibility():
377398
"""Test _iscombatible function."""
378-
baseCon = _make_base_connectivity()
399+
base_con = _make_base_connectivity()
379400
all_con = _make_alltoall_connectivity()
380401
label_con = _make_label_connectivity()
381402

382403
# Test BaseConnectivity
383-
assert not baseCon.is_compatible(label_con)
384-
assert not baseCon.is_compatible(all_con)
385-
baseCon2 = _BaseConnectivity(
386-
np.array([6, 7, 8, 9, 10]), baseCon.pairs, baseCon.n_sources
404+
assert not base_con.is_compatible(label_con)
405+
assert not base_con.is_compatible(all_con)
406+
base_con2 = _BaseConnectivity(
407+
np.array([6, 7, 8, 9, 10]), base_con.pairs, base_con.n_sources
387408
)
388-
assert baseCon.is_compatible(baseCon2)
409+
assert base_con.is_compatible(base_con2)
389410

390411
# Test VertexConnectivity
391412
assert not all_con.is_compatible(label_con)
@@ -655,3 +676,73 @@ def test_dics_connectivity():
655676
fwd_tan = forward_to_tangential(fwd)
656677
con2 = dics_connectivity(pairs, fwd_tan, csd, reg=1)
657678
assert_array_equal(con2.data, con.data)
679+
680+
681+
@testing.requires_testing_data
682+
def test_dics_coherence_external():
683+
"""Test dics_coherence_external function."""
684+
fwd = _load_forward()
685+
fwd = mne.pick_types_forward(fwd, meg="grad", eeg=False)
686+
fwd_fixed = mne.convert_forward_solution(fwd, force_fixed=True)
687+
source_vert = 146374
688+
sfreq = 50.0
689+
csd = _simulate_data(fwd_fixed, source_vert, None, external=True)
690+
print(csd.ch_names)
691+
# Create an info object that holds information about the sensors (their
692+
# location, etc.). Make sure to include the external sensor!
693+
info = mne.create_info(
694+
fwd["info"]["ch_names"] + ["external"],
695+
sfreq,
696+
ch_types=["grad"] * fwd["info"]["nchan"] + ["misc"],
697+
)
698+
# Copy grad positions from the forward solution
699+
for info_ch, fwd_ch in zip(info["chs"], fwd["info"]["chs"]):
700+
info_ch.update(fwd_ch)
701+
702+
dics = make_dics(info.copy(), fwd, csd.copy(), reg=1,
703+
inversion="single",
704+
pick_ori=None)
705+
dics_matrix = make_dics(info.copy(), fwd, csd.copy(), reg=1,
706+
inversion="matrix",
707+
pick_ori=None)
708+
dics_fixed = make_dics(info.copy(), fwd, csd.copy(), reg=1,
709+
inversion="single",
710+
pick_ori='max-power')
711+
# Tangential source space
712+
with pytest.raises(ValueError):
713+
dics_tangential = dics.copy()
714+
dics_tangential["weights"] = np.delete(
715+
dics_tangential["weights"],
716+
np.arange(0, dics["weights"].shape[1], 3), axis=1)
717+
dics_coherence_external(csd, dics_tangential, info, fwd,
718+
external='external',
719+
pick_ori='max-coherence')
720+
# Max-power ori selected but dics has vector ori
721+
with pytest.raises(ValueError):
722+
dics_coherence_external(csd, dics, info, fwd, external='external',
723+
pick_ori='max-power')
724+
# Max-coherence ori selected but dics has fixed ori
725+
with pytest.raises(ValueError):
726+
dics_coherence_external(csd, dics_fixed, info, fwd, external='external',
727+
pick_ori='max-coherence')
728+
# Max-coherence ori selected but dics was computed using matrix inversion
729+
with pytest.raises(ValueError):
730+
dics_coherence_external(csd, dics_matrix, info, fwd,
731+
external='external', pick_ori='max-coherence')
732+
# Incorrect pick_ori
733+
with pytest.raises(ValueError):
734+
dics_coherence_external(csd, dics, info, fwd, external='external',
735+
pick_ori='normal')
736+
# Max power orientation
737+
coh_stc = dics_coherence_external(csd, dics_fixed, info, fwd,
738+
external='external', pick_ori='max-power')
739+
assert isinstance(coh_stc, SourceEstimate)
740+
assert coh_stc.data.shape == (fwd['nsource'], 2) # 2 frequencies
741+
assert np.max(coh_stc.data) <= 1 and np.min(coh_stc.data) >= 0
742+
# Max coherence orientation
743+
coh_stc = dics_coherence_external(csd, dics, info, fwd,
744+
external='external',
745+
pick_ori='max-coherence')
746+
assert isinstance(coh_stc, SourceEstimate)
747+
assert coh_stc.data.shape == (fwd['nsource'], 2) # 2 frequencies
748+
assert np.max(coh_stc.data) <= 1 and np.min(coh_stc.data) >= 0

0 commit comments

Comments
 (0)