Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ stages:
- bash: |
set -e
python -m pip install --progress-bar off --upgrade pip
python -m pip install --progress-bar off "mne-qt-browser[opengl] @ git+https://github.com/mne-tools/mne-qt-browser.git" "git+https://github.com/python-quantities/python-quantities" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader
python -m pip install --progress-bar off "mne-qt-browser[opengl] @ git+https://github.com/mne-tools/mne-qt-browser.git" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader
python -m pip uninstall -yq mne
python -m pip install --progress-bar off --upgrade -e . --group=test
displayName: 'Install dependencies with pip'
Expand Down Expand Up @@ -173,7 +173,7 @@ stages:
python -m pip install --progress-bar off --upgrade pip
python -m pip install --progress-bar off --upgrade --pre --only-binary=\"numpy,scipy,matplotlib,vtk\" numpy scipy matplotlib vtk
python -c "import vtk"
python -m pip install --progress-bar off --upgrade -ve .[full] --group=test_extra "git+https://github.com/python-quantities/python-quantities"
python -m pip install --progress-bar off --upgrade -ve .[full] --group=test_extra
displayName: 'Install dependencies with pip'
- bash: |
set -e
Expand Down
1 change: 1 addition & 0 deletions doc/changes/dev/13595.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix bug where :func:`mne.viz.plot_evoked_white` did not accept a single "meg" rank value like those returned from :func:`mne.compute_rank`, by `Eric Larson`_.
26 changes: 20 additions & 6 deletions mne/minimum_norm/tests/test_inverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
EvokedArray,
SourceEstimate,
combine_evoked,
compute_rank,
compute_raw_covariance,
convert_forward_solution,
make_ad_hoc_cov,
Expand Down Expand Up @@ -992,21 +993,34 @@ def test_make_inverse_operator_diag(evoked, noise_cov, tmp_path, azure_windows):

def test_inverse_operator_noise_cov_rank(evoked, noise_cov):
"""Test MNE inverse operator with a specified noise cov rank."""
fwd_op = read_forward_solution_meg(fname_fwd, surf_ori=True)
inv = make_inverse_operator(evoked.info, fwd_op, noise_cov, rank=dict(meg=64))
fwd_op_meg = read_forward_solution_meg(fname_fwd, surf_ori=True)
inv = make_inverse_operator(evoked.info, fwd_op_meg, noise_cov, rank=dict(meg=64))
assert compute_rank_inverse(inv) == 64
inv = make_inverse_operator(evoked.info, fwd_op, noise_cov, rank=dict(meg=64))
inv = make_inverse_operator(evoked.info, fwd_op_meg, noise_cov, rank=dict(meg=64))
assert compute_rank_inverse(inv) == 64

bad_cov = noise_cov.copy()
bad_cov["data"][0, 0] *= 1e12
with pytest.warns(RuntimeWarning, match="orders of magnitude"):
make_inverse_operator(evoked.info, fwd_op, bad_cov, rank=dict(meg=64))
make_inverse_operator(evoked.info, fwd_op_meg, bad_cov, rank=dict(meg=64))

fwd_op = read_forward_solution_eeg(fname_fwd, surf_ori=True)
inv = make_inverse_operator(evoked.info, fwd_op, noise_cov, rank=dict(eeg=20))
fwd_op_eeg = read_forward_solution_eeg(fname_fwd, surf_ori=True)
inv = make_inverse_operator(evoked.info, fwd_op_eeg, noise_cov, rank=dict(eeg=20))
assert compute_rank_inverse(inv) == 20

# with and without rank passed explicitly
inv_info = make_inverse_operator(evoked.info, fwd_op_meg, noise_cov, rank="info")
info_rank = 302
assert compute_rank_inverse(inv_info) == info_rank
rank = compute_rank(noise_cov, info=evoked.copy().pick("meg").info, rank="info")
assert "meg" in rank
assert sum(rank.values()) == info_rank
inv_rank = make_inverse_operator(evoked.info, fwd_op_meg, noise_cov, rank=rank)
assert compute_rank_inverse(inv_rank) == info_rank
evoked_info = apply_inverse(evoked, inv_info, lambda2, "MNE")
evoked_rank = apply_inverse(evoked, inv_rank, lambda2, "MNE")
assert_allclose(evoked_rank.data, evoked_info.data)


def test_inverse_operator_volume(evoked, tmp_path):
"""Test MNE inverse computation on volume source space."""
Expand Down
8 changes: 8 additions & 0 deletions mne/tests/test_cov.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ def test_compute_whitener(proj, pca):
assert pca is False
assert_allclose(round_trip, np.eye(n_channels), atol=0.05)

# with and without rank
W_info, _ = compute_whitener(cov, raw.info, pca=pca, rank="info", verbose="error")
assert_allclose(W_info, W)
rank = compute_rank(raw, rank="info", proj=proj)
assert W.shape == (n_reduced, n_channels)
W_rank, _ = compute_whitener(cov, raw.info, pca=pca, rank=rank, verbose="error")
assert_allclose(W_rank, W)

raw.info["bads"] = [raw.ch_names[0]]
picks = pick_types(raw.info, meg=True, eeg=True, exclude=[])
with pytest.warns(RuntimeWarning, match="Too few samples"):
Expand Down
16 changes: 6 additions & 10 deletions mne/viz/evoked.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,13 +1587,9 @@ def plot_evoked_white(
evoked.del_proj(idx)

evoked.pick_types(ref_meg=False, exclude="bads", **_PICK_TYPES_DATA_DICT)
n_ch_used, rank_list, picks_list, has_sss = _triage_rank_sss(
n_ch_used, rank_list, picks_list, meg_combined = _triage_rank_sss(
evoked.info, noise_cov, rank, scalings=None
)
if has_sss:
logger.info(
"SSS has been applied to data. Showing mag and grad whitening jointly."
)

# get one whitened evoked per cov
evokeds_white = [
Expand Down Expand Up @@ -1663,8 +1659,8 @@ def whitened_gfp(x, rank=None):
# hacks to get it to plot all channels in the same axes, namely setting
# the channel unit (most important) and coil type (for consistency) of
# all MEG channels to be the same.
meg_idx = sss_title = None
if has_sss:
meg_idx = combined_title = None
if meg_combined:
titles_["meg"] = "MEG (combined)"
meg_idx = [
pi for pi, (ch_type, _) in enumerate(picks_list) if ch_type == "meg"
Expand All @@ -1675,7 +1671,7 @@ def whitened_gfp(x, rank=None):
use = evokeds_white[0].info["chs"][picks[0]][key]
for pick in picks:
evokeds_white[0].info["chs"][pick][key] = use
sss_title = f"{titles_['meg']} ({len(picks)} channel{_pl(picks)})"
combined_title = f"{titles_['meg']} ({len(picks)} channel{_pl(picks)})"
evokeds_white[0].plot(
unit=False,
axes=axes_evoked,
Expand All @@ -1684,8 +1680,8 @@ def whitened_gfp(x, rank=None):
time_unit=time_unit,
spatial_colors=spatial_colors,
)
if has_sss:
axes_evoked[meg_idx].set(title=sss_title)
if meg_combined:
axes_evoked[meg_idx].set(title=combined_title)

# Now plot the GFP for all covs if indicated.
for evoked_white, noise_cov, rank_, color in iter_gfp:
Expand Down
20 changes: 18 additions & 2 deletions mne/viz/tests/test_evoked.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Epochs,
compute_covariance,
compute_proj_evoked,
compute_rank,
make_fixed_length_events,
read_cov,
read_events,
Expand Down Expand Up @@ -357,6 +358,21 @@ def test_plot_evoked_image():
evoked.plot_image(clim=[-4, 4])


def test_plot_white_rank():
"""Test plot_white with a combined-MEG rank arg."""
cov = read_cov(cov_fname)
cov["method"] = "empirical"
cov["projs"] = [] # avoid warnings
evoked = _get_epochs().average()
evoked.set_eeg_reference("average") # Avoid warnings
rank = compute_rank(evoked, "info")
assert "grad" not in rank
assert "mag" not in rank
assert "meg" in rank
evoked.plot_white(cov)
evoked.plot_white(cov, rank=rank)


def test_plot_white():
"""Test plot_white."""
cov = read_cov(cov_fname)
Expand All @@ -373,9 +389,9 @@ def test_plot_white():
evoked.plot_white(cov, rank={"grad": 8}, time_unit="s", axes=fig.axes[:4])
with pytest.raises(ValueError, match=r"must have shape \(4,\), got \(2,"):
evoked.plot_white(cov, axes=fig.axes[:2])
with pytest.raises(ValueError, match="When not using SSS"):
with pytest.raises(ValueError, match="exceeds the number"):
evoked.plot_white(cov, rank={"meg": 306})
evoked.plot_white([cov, cov], time_unit="s")
evoked.plot_white([cov, cov], rank={"meg": 9}, time_unit="s")
plt.close("all")

fig = plot_evoked_white(evoked, [cov, cov])
Expand Down
34 changes: 20 additions & 14 deletions mne/viz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2046,13 +2046,13 @@ def _setup_plot_projector(info, noise_cov, proj=True, use_noise_cov=True, nave=1
def _check_sss(info):
"""Check SSS history in info."""
ch_used = [ch for ch in _DATA_CH_TYPES_SPLIT if _contains_ch_type(info, ch)]
has_meg = "mag" in ch_used and "grad" in ch_used
has_sss = (
has_meg
has_mag_and_grad = "mag" in ch_used and "grad" in ch_used
needs_meg_combined = (
has_mag_and_grad
and len(info["proc_history"]) > 0
and info["proc_history"][0].get("max_info") is not None
)
return ch_used, has_meg, has_sss
return ch_used, has_mag_and_grad, needs_meg_combined


def _triage_rank_sss(info, covs, rank=None, scalings=None):
Expand All @@ -2062,22 +2062,28 @@ def _triage_rank_sss(info, covs, rank=None, scalings=None):
# Only look at good channels
picks = _pick_data_channels(info, with_ref_meg=False, exclude="bads")
info = pick_info(info, picks)
ch_used, has_meg, has_sss = _check_sss(info)
if has_sss:
ch_used, has_mag_and_grad, needs_meg_combined = _check_sss(info)
if needs_meg_combined:
if "mag" in rank or "grad" in rank:
raise ValueError(
'When using SSS, pass "meg" to set the rank '
'(separate rank values for "mag" or "grad" are '
"meaningless)."
)
meg_combined = True
elif "meg" in rank:
raise ValueError(
"When not using SSS, pass separate rank values "
'for "mag" and "grad" (do not use "meg").'
)
if needs_meg_combined:
start = "SSS has been applied to data"
else:
start = "Got a single MEG rank value"
logger.info("%s. Showing mag and grad whitening jointly.", start)
meg_combined = True
else:
meg_combined = False
del needs_meg_combined

picks_list = _picks_by_type(info, meg_combined=has_sss)
if has_sss:
picks_list = _picks_by_type(info, meg_combined=meg_combined)
if meg_combined:
# reduce ch_used to combined mag grad
ch_used = list(zip(*picks_list))[0]
# order pick list by ch_used (required for compat with plot_evoked)
Expand All @@ -2088,7 +2094,7 @@ def _triage_rank_sss(info, covs, rank=None, scalings=None):

picks_list2 = [k for k in picks_list]
# add meg picks if needed.
if has_meg:
if has_mag_and_grad:
# append ("meg", picks_meg)
picks_list2 += _picks_by_type(info, meg_combined=True)

Expand Down Expand Up @@ -2121,7 +2127,7 @@ def _triage_rank_sss(info, covs, rank=None, scalings=None):
this_rank[ch_type] = rank[ch_type]

rank_list.append(this_rank)
return n_ch_used, rank_list, picks_list, has_sss
return n_ch_used, rank_list, picks_list, meg_combined


def _check_cov(noise_cov, info):
Expand Down
2 changes: 0 additions & 2 deletions tools/github_actions_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ else
EXTRAS=""
fi
echo ""
# until quantities releases...
STD_ARGS="$STD_ARGS git+https://github.com/python-quantities/python-quantities"

echo "::group::Installing test dependencies using pip"
set -x
Expand Down