Skip to content

Commit

Permalink
Use constrained layout in matplotlib visualization (mne-tools#12050)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
  • Loading branch information
3 people authored and snwnde committed Mar 20, 2024
1 parent 0bb4616 commit 864b3e6
Show file tree
Hide file tree
Showing 71 changed files with 351 additions and 620 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The minimum required dependencies to run MNE-Python are:
- Python >= 3.8
- NumPy >= 1.21.2
- SciPy >= 1.7.1
- Matplotlib >= 3.4.3
- Matplotlib >= 3.5.0
- pooch >= 1.5
- tqdm
- Jinja2
Expand Down
1 change: 1 addition & 0 deletions doc/changes/devel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Enhancements
- Add :class:`~mne.time_frequency.EpochsSpectrumArray` and :class:`~mne.time_frequency.SpectrumArray` to support creating power spectra from :class:`NumPy array <numpy.ndarray>` data (:gh:`11803` by `Alex Rockhill`_)
- Add support for writing forward solutions to HDF5 and convenience function :meth:`mne.Forward.save` (:gh:`12036` by `Eric Larson`_)
- Refactored internals of :func:`mne.read_annotations` (:gh:`11964` by `Paul Roujansky`_)
- By default MNE-Python creates matplotlib figures with ``layout='constrained'`` rather than the default ``layout='tight'`` (:gh:`12050` by `Mathieu Scheltienne`_ and `Eric Larson`_)
- Enhance :func:`~mne.viz.plot_evoked_field` with a GUI that has controls for time, colormap, and contour lines (:gh:`11942` by `Marijn van Vliet`_)
- Add :class:`mne.viz.ui_events.UIEvent` linking for interactive colorbars, allowing users to link figures and change the colormap and limits interactively. This supports :func:`~mne.viz.plot_evoked_topomap`, :func:`~mne.viz.plot_ica_components`, :func:`~mne.viz.plot_tfr_topomap`, :func:`~mne.viz.plot_projs_topomap`, :meth:`~mne.Evoked.plot_image`, and :meth:`~mne.Epochs.plot_image` (:gh:`12057` by `Santeri Ruuskanen`_)

Expand Down
1 change: 0 additions & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,6 @@ def reset_warnings(gallery_conf, fname):
warnings.filterwarnings("default", module="sphinx")
# allow these warnings, but don't show them
for key in (
"The module matplotlib.tight_layout is deprecated", # nilearn
"invalid version and will not be supported", # pyxdf
"distutils Version classes are deprecated", # seaborn and neo
"`np.object` is a deprecated alias for the builtin `object`", # pyxdf
Expand Down
6 changes: 2 additions & 4 deletions examples/decoding/decoding_rsa_sgskip.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
##############################################################################
# Plot
labels = [""] * 5 + ["face"] + [""] * 11 + ["bodypart"] + [""] * 6
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, layout="constrained")
im = ax.matshow(confusion, cmap="RdBu_r", clim=[0.3, 0.7])
ax.set_yticks(range(len(classes)))
ax.set_yticklabels(labels)
Expand All @@ -159,14 +159,13 @@
ax.axhline(11.5, color="k")
ax.axvline(11.5, color="k")
plt.colorbar(im)
plt.tight_layout()
plt.show()

##############################################################################
# Confusion matrix related to mental representations have been historically
# summarized with dimensionality reduction using multi-dimensional scaling [1].
# See how the face samples cluster together.
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, layout="constrained")
mds = MDS(2, random_state=0, dissimilarity="precomputed")
chance = 0.5
summary = mds.fit_transform(chance - confusion)
Expand All @@ -186,7 +185,6 @@
)
ax.axis("off")
ax.legend(loc="lower right", scatterpoints=1, ncol=2)
plt.tight_layout()
plt.show()

##############################################################################
Expand Down
3 changes: 1 addition & 2 deletions examples/decoding/decoding_spoc_CMC.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@
y_preds = cross_val_predict(clf, X, y, cv=cv)

# Plot the True EMG power and the EMG power predicted from MEG data
fig, ax = plt.subplots(1, 1, figsize=[10, 4])
fig, ax = plt.subplots(1, 1, figsize=[10, 4], layout="constrained")
times = raw.times[meg_epochs.events[:, 0] - raw.first_samp]
ax.plot(times, y_preds, color="b", label="Predicted EMG")
ax.plot(times, y, color="r", label="True EMG")
ax.set_xlabel("Time (s)")
ax.set_ylabel("EMG Power")
ax.set_title("SPoC MEG Predictions")
plt.legend()
mne.viz.tight_layout()
plt.show()

##############################################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

# %%
# Plot
fig, ax = plt.subplots(constrained_layout=True)
fig, ax = plt.subplots(layout="constrained")
im = ax.matshow(
scores,
vmin=0,
Expand Down
9 changes: 5 additions & 4 deletions examples/decoding/decoding_xdawn_eeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,24 @@
cm_normalized = cm.astype(float) / cm.sum(axis=1)[:, np.newaxis]

# Plot confusion matrix
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, layout="constrained")
im = ax.imshow(cm_normalized, interpolation="nearest", cmap=plt.cm.Blues)
ax.set(title="Normalized Confusion matrix")
fig.colorbar(im)
tick_marks = np.arange(len(target_names))
plt.xticks(tick_marks, target_names, rotation=45)
plt.yticks(tick_marks, target_names)
fig.tight_layout()
ax.set(ylabel="True label", xlabel="Predicted label")

# %%
# The ``patterns_`` attribute of a fitted Xdawn instance (here from the last
# cross-validation fold) can be used for visualization.

fig, axes = plt.subplots(
nrows=len(event_id), ncols=n_filter, figsize=(n_filter, len(event_id) * 2)
nrows=len(event_id),
ncols=n_filter,
figsize=(n_filter, len(event_id) * 2),
layout="constrained",
)
fitted_xdawn = clf.steps[0][1]
info = create_info(epochs.ch_names, 1, epochs.get_channel_types())
Expand All @@ -131,7 +133,6 @@
show=False,
)
axes[ii, 0].set(ylabel=cur_class)
fig.tight_layout(h_pad=1.0, w_pad=1.0, pad=0.1)

# %%
# References
Expand Down
16 changes: 5 additions & 11 deletions examples/decoding/receptive_field_mtrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,11 @@
n_channels = len(raw.ch_names)

# Plot a sample of brain and stimulus activity
fig, ax = plt.subplots()
fig, ax = plt.subplots(layout="constrained")
lns = ax.plot(scale(raw[:, :800][0].T), color="k", alpha=0.1)
ln1 = ax.plot(scale(speech[0, :800]), color="r", lw=2)
ax.legend([lns[0], ln1[0]], ["EEG", "Speech Envelope"], frameon=False)
ax.set(title="Sample activity", xlabel="Time (s)")
mne.viz.tight_layout()

# %%
# Create and fit a receptive field model
Expand Down Expand Up @@ -117,12 +116,11 @@
mean_scores = scores.mean(axis=0)

# Plot mean prediction scores across all channels
fig, ax = plt.subplots()
fig, ax = plt.subplots(layout="constrained")
ix_chs = np.arange(n_channels)
ax.plot(ix_chs, mean_scores)
ax.axhline(0, ls="--", color="r")
ax.set(title="Mean prediction score", xlabel="Channel", ylabel="Score ($r$)")
mne.viz.tight_layout()

# %%
# Investigate model coefficients
Expand All @@ -134,7 +132,7 @@

# Print mean coefficients across all time delays / channels (see Fig 1)
time_plot = 0.180 # For highlighting a specific time.
fig, ax = plt.subplots(figsize=(4, 8))
fig, ax = plt.subplots(figsize=(4, 8), layout="constrained")
max_coef = mean_coefs.max()
ax.pcolormesh(
times,
Expand All @@ -155,16 +153,14 @@
xticks=np.arange(tmin, tmax + 0.2, 0.2),
)
plt.setp(ax.get_xticklabels(), rotation=45)
mne.viz.tight_layout()

# Make a topographic map of coefficients for a given delay (see Fig 2C)
ix_plot = np.argmin(np.abs(time_plot - times))
fig, ax = plt.subplots()
fig, ax = plt.subplots(layout="constrained")
mne.viz.plot_topomap(
mean_coefs[:, ix_plot], pos=info, axes=ax, show=False, vlim=(-max_coef, max_coef)
)
ax.set(title="Topomap of model coefficients\nfor delay %s" % time_plot)
mne.viz.tight_layout()

# %%
# Create and fit a stimulus reconstruction model
Expand Down Expand Up @@ -240,15 +236,14 @@

y_pred = sr.predict(Y[test])
time = np.linspace(0, 2.0, 5 * int(sfreq))
fig, ax = plt.subplots(figsize=(8, 4))
fig, ax = plt.subplots(figsize=(8, 4), layout="constrained")
ax.plot(
time, speech[test][sr.valid_samples_][: int(5 * sfreq)], color="grey", lw=2, ls="--"
)
ax.plot(time, y_pred[sr.valid_samples_][: int(5 * sfreq)], color="r", lw=2)
ax.legend([lns[0], ln1[0]], ["Envelope", "Reconstruction"], frameon=False)
ax.set(title="Stimulus reconstruction")
ax.set_xlabel("Time (s)")
mne.viz.tight_layout()

# %%
# Investigate model coefficients
Expand Down Expand Up @@ -292,7 +287,6 @@
title="Inverse-transformed coefficients\nbetween delays %s and %s"
% (time_plot[0], time_plot[1])
)
mne.viz.tight_layout()

# %%
# References
Expand Down
6 changes: 2 additions & 4 deletions examples/inverse/label_source_activations.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
# View source activations
# -----------------------

fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, layout="constrained")
t = 1e3 * stc_label.times
ax.plot(t, stc_label.data.T, "k", linewidth=0.5, alpha=0.5)
pe = [
Expand All @@ -81,7 +81,6 @@
xlim=xlim,
ylim=ylim,
)
mne.viz.tight_layout()

# %%
# Using vector solutions
Expand All @@ -92,7 +91,7 @@
pick_ori = "vector"
stc_vec = apply_inverse(evoked, inverse_operator, lambda2, method, pick_ori=pick_ori)
data = stc_vec.extract_label_time_course(label, src)
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, layout="constrained")
stc_vec_label = stc_vec.in_label(label)
colors = ["#EE6677", "#228833", "#4477AA"]
for ii, name in enumerate("XYZ"):
Expand All @@ -117,4 +116,3 @@
xlim=xlim,
ylim=ylim,
)
mne.viz.tight_layout()
3 changes: 1 addition & 2 deletions examples/inverse/mixed_source_space_inverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,8 @@
)

# plot the times series of 2 labels
fig, axes = plt.subplots(1)
fig, axes = plt.subplots(1, layout="constrained")
axes.plot(1e3 * stc.times, label_ts[0][0, :], "k", label="bankssts-lh")
axes.plot(1e3 * stc.times, label_ts[0][-1, :].T, "r", label="Brain-stem")
axes.set(xlabel="Time (ms)", ylabel="MNE current (nAm)")
axes.legend()
mne.viz.tight_layout()
3 changes: 1 addition & 2 deletions examples/inverse/source_space_snr.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@
# Plot an average SNR across source points over time:
ave = np.mean(snr_stc.data, axis=0)

fig, ax = plt.subplots()
fig, ax = plt.subplots(layout="constrained")
ax.plot(evoked.times, ave)
ax.set(xlabel="Time (s)", ylabel="SNR MEG-EEG")
fig.tight_layout()

# Find time point of maximum SNR
maxidx = np.argmax(ave)
Expand Down
7 changes: 2 additions & 5 deletions examples/preprocessing/eeg_bridging.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

bridged_idx, ed_matrix = ed_data[6]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), layout="constrained")
fig.suptitle("Subject 6 Electrical Distance Matrix")

# take median across epochs, only use upper triangular, lower is NaNs
Expand All @@ -110,8 +110,6 @@
ax.set_xlabel("Channel Index")
ax.set_ylabel("Channel Index")

fig.tight_layout()

# %%
# Examine the Distribution of Electrical Distances
# ------------------------------------------------
Expand Down Expand Up @@ -208,7 +206,7 @@
# reflect neural or at least anatomical differences as well (i.e. the
# distance from the sensors to the brain).

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), layout="constrained")
fig.suptitle("Electrical Distance Distribution for EEGBCI Subjects")
for ax in (ax1, ax2):
ax.set_ylabel("Count")
Expand All @@ -229,7 +227,6 @@

ax1.axvspan(0, 30, color="r", alpha=0.5)
ax2.legend(loc=(1.04, 0))
fig.subplots_adjust(right=0.725, bottom=0.15, wspace=0.4)

# %%
# For the group of subjects, let's look at their electrical distances
Expand Down
3 changes: 1 addition & 2 deletions examples/preprocessing/eeg_csd.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@
# CSD has parameters ``stiffness`` and ``lambda2`` affecting smoothing and
# spline flexibility, respectively. Let's see how they affect the solution:

fig, ax = plt.subplots(4, 4)
fig.subplots_adjust(hspace=0.5)
fig, ax = plt.subplots(4, 4, layout="constrained")
fig.set_size_inches(10, 10)
for i, lambda2 in enumerate([0, 1e-7, 1e-5, 1e-3]):
for j, m in enumerate([5, 4, 3, 2]):
Expand Down
3 changes: 1 addition & 2 deletions examples/preprocessing/eog_artifact_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@

# %%
# Plot EOG artifact distribution
fig, ax = plt.subplots()
fig, ax = plt.subplots(layout="constrained")
ax.stem(1e3 * epochs.times, data)
ax.set(xlabel="Times (ms)", ylabel="Blink counts (from %s trials)" % len(epochs))
fig.tight_layout()
7 changes: 3 additions & 4 deletions examples/preprocessing/eog_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@
epochs_after = mne.Epochs(raw_clean, events, event_id, tmin, tmax, baseline=(tmin, 0))
evoked_after = epochs_after.average()

fig, ax = plt.subplots(nrows=3, ncols=2, figsize=(10, 7), sharex=True, sharey="row")
fig, ax = plt.subplots(
nrows=3, ncols=2, figsize=(10, 7), sharex=True, sharey="row", layout="constrained"
)
evoked_before.plot(axes=ax[:, 0], spatial_colors=True)
evoked_after.plot(axes=ax[:, 1], spatial_colors=True)
fig.subplots_adjust(
top=0.905, bottom=0.09, left=0.08, right=0.975, hspace=0.325, wspace=0.145
)
fig.suptitle("Before --> After")
3 changes: 0 additions & 3 deletions examples/preprocessing/shift_evoked.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import matplotlib.pyplot as plt
import mne
from mne.viz import tight_layout
from mne.datasets import sample

print(__doc__)
Expand Down Expand Up @@ -60,5 +59,3 @@
titles=dict(grad="Absolute shift: 500 ms"),
time_unit="s",
)

tight_layout()
6 changes: 3 additions & 3 deletions examples/simulation/plot_stc_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
]

# Plot the results
f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex="col", constrained_layout=True)
f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex="col", layout="constrained")
for ax, (title, results) in zip([ax1, ax2, ax3, ax4], region_results.items()):
ax.plot(thresholds, results, ".-")
ax.set(title=title, ylabel="score", xlabel="Threshold", xticks=thresholds)
Expand All @@ -243,7 +243,7 @@
ax1.ticklabel_format(axis="y", style="sci", scilimits=(0, 1)) # tweak RLE

# Cosine score with respect to time
f, ax1 = plt.subplots(constrained_layout=True)
f, ax1 = plt.subplots(layout="constrained")
ax1.plot(stc_true_region.times, cosine_score(stc_true_region, stc_est_region))
ax1.set(title="Cosine score", xlabel="Time", ylabel="Score")

Expand Down Expand Up @@ -277,6 +277,6 @@

# Plot the results
for name, results in dipole_results.items():
f, ax1 = plt.subplots(constrained_layout=True)
f, ax1 = plt.subplots(layout="constrained")
ax1.plot(thresholds, 100 * np.array(results), ".-")
ax1.set(title=name, ylabel="Error (cm)", xlabel="Threshold", xticks=thresholds)
Loading

0 comments on commit 864b3e6

Please sign in to comment.