Repository: mne-tools/mne-python

Issue:
BUG, VIZ: raw.plot_psd fmax param ignored when plotting
the `fmax` parameter should be used to set the right-hand xlim of the plot, but it is not. In the plots below, the right-hand xlim is determined by the `sfreq` even though `fmax=350` is specified for both plots.

```
import os
import mne
sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = os.path.join(sample_data_folder, 'MEG', 'sample',
                                    'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(sample_data_raw_file)
raw.crop(0, 60).load_data() 

raw_downsampled = raw.copy().resample(sfreq=100)

for data in (raw, raw_downsampled):
    fig = data.plot_psd(fmax=350)
```
![Figure_1](https://user-images.githubusercontent.com/1810515/65967765-045b8c00-e417-11e9-9b8c-e32950f093c4.png)

![Figure_2](https://user-images.githubusercontent.com/1810515/65967773-07ef1300-e417-11e9-9be3-fc1a6aac8b5f.png)





Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mne-tools/mne-python/pull/6879?src=pr&el=h1) Report
> Merging [#6879](https://codecov.io/gh/mne-tools/mne-python/pull/6879?src=pr&el=desc) into [master](https://codecov.io/gh/mne-tools/mne-python/commit/1e9b065b91d70d401b49c1b1a5a16767e00d9592?src=pr&el=desc) will **increase** coverage by `1.37%`.
> The diff coverage is `100%`.


```diff
@@            Coverage Diff             @@
##           master    #6879      +/-   ##
==========================================
+ Coverage   88.26%   89.63%   +1.37%     
==========================================
  Files         426      426              
  Lines       76034    76042       +8     
  Branches    12402    12403       +1     
==========================================
+ Hits        67109    68162    +1053     
+ Misses       6141     5102    -1039     
+ Partials     2784     2778       -6
```




Patch:
diff --git a/doc/glossary.rst b/doc/glossary.rst
index 4de71fe51..de7a7704a 100644
--- a/doc/glossary.rst
+++ b/doc/glossary.rst
@@ -25,7 +25,9 @@ general neuroimaging concepts. If you think a term is missing, please consider
         for a tutorial on how to manipulate such objects.
 
     Beamformer 
-        Beamformer is a popular source estimation approach that uses a set of spatial filters (beamformer weights) to compute time courses of sources which coordinates are predefined. See :class:`mne.beamformer.Beamformer`.
+        Beamformer is a popular source estimation approach that uses a set of
+        spatial filters (beamformer weights) to compute time courses of sources
+        which coordinates are predefined. See :class:`mne.beamformer.Beamformer`.
 
     BEM
         BEM is the acronym for boundary element method or boundary element
@@ -43,12 +45,23 @@ general neuroimaging concepts. If you think a term is missing, please consider
         a type, such as gradiometer, and a unit, such as Tesla/Meter that
         is used in the code base, e.g. for plotting.
 
-    Digitization
-        Digitization is a procedure of recording the headshape of a subject and the fiducial coils and/or eeg electrodes locations on the subject’s head. They are represented as a set of points in a 3D space. See :ref:`reading-dig-montages` and :ref:`dig-formats`.  
+    digitization
+        Digitization is a procedure of recording the headshape of a subject and
+        the fiducial coils (or :term:`HPI`) and/or eeg electrodes locations on
+        the subject’s head. They are represented as a set of points in a 3D space.
+        See :ref:`reading-dig-montages` and :ref:`dig-formats`.  
 
     dipole
         See :term:`equivalent current dipole`.
 
+    eLORETA and sLORETA
+        eLORETA and sLORETA (exact and standardized low resolution brain
+        electromagnetic tomography) are linear source estimation techniques,
+        as are dSPM or MNE (:term:`minimum-norm estimation`). sLORETA outputs
+        standardized values such as dSPM, while eLORETA outputs normalized current 
+        estimates. 
+        See :func:`mne.minimum_norm.apply_inverse` and :ref:`example-sLORETA`.
+
     epochs
         Epochs (sometimes called "trials" in other software packages) are
         equal-length spans of data extracted from raw continuous data. Usually,
@@ -124,7 +137,7 @@ general neuroimaging concepts. If you think a term is missing, please consider
         and can be used to infer the head position. With cHPI, the sinusoidal
         signals are typically set at frequencies above any neural signal of
         interest, and thus can be removed after head position correction via
-        low-pass filtering.
+        low-pass filtering. See :ref:`example-head-pos`.
 
     info
         Also called ``measurement info``, it is a collection of metadata regarding
@@ -139,6 +152,7 @@ general neuroimaging concepts. If you think a term is missing, please consider
         signals, yields estimates of the brain activity that gave rise to the
         observed sensor signals. Inverse operators are available for the linear
         inverse methods MNE, dSPM, sLORETA and eLORETA.
+        See :func:`mne.minimum_norm.apply_inverse`.
 
     label
         A :class:`Label` refers to a region in the cortex, also often called
diff --git a/examples/inverse/plot_compute_mne_inverse_raw_in_label.py b/examples/inverse/plot_compute_mne_inverse_raw_in_label.py
index ccce5682a..a9fa4325d 100644
--- a/examples/inverse/plot_compute_mne_inverse_raw_in_label.py
+++ b/examples/inverse/plot_compute_mne_inverse_raw_in_label.py
@@ -1,4 +1,6 @@
 """
+.. _example-sLORETA:
+
 =============================================
 Compute sLORETA inverse solution on raw data
 =============================================
diff --git a/examples/preprocessing/plot_head_positions.py b/examples/preprocessing/plot_head_positions.py
index 0fea28fc7..bf2e990a2 100644
--- a/examples/preprocessing/plot_head_positions.py
+++ b/examples/preprocessing/plot_head_positions.py
@@ -1,4 +1,6 @@
 """
+.. _example-head-pos:
+
 ===============================
 Visualize subject head movement
 ===============================
diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py
index ad0976688..878515ed1 100644
--- a/mne/viz/epochs.py
+++ b/mne/viz/epochs.py
@@ -31,7 +31,8 @@ from .utils import (tight_layout, figure_nobar, _toggle_proj, _toggle_options,
                     _compute_scalings, DraggableColorbar, _setup_cmap,
                     _handle_decim, _setup_plot_projector, _set_ax_label_style,
                     _set_title_multiple_electrodes, _make_combine_callable,
-                    _get_figsize_from_config, _toggle_scrollbars)
+                    _get_figsize_from_config, _toggle_scrollbars,
+                    _check_psd_fmax)
 from .misc import _handle_event_colors
 
 
@@ -919,6 +920,7 @@ def plot_epochs_psd(epochs, fmin=0, fmax=np.inf, tmin=None, tmax=None,
     fig, picks_list, titles_list, units_list, scalings_list, ax_list, \
         make_label = _set_psd_plot_params(epochs.info, proj, picks, ax,
                                           area_mode)
+    _check_psd_fmax(epochs, fmax)
     del ax
     psd_list = list()
     for picks in picks_list:
diff --git a/mne/viz/raw.py b/mne/viz/raw.py
index 86852fdf5..c87071894 100644
--- a/mne/viz/raw.py
+++ b/mne/viz/raw.py
@@ -27,7 +27,8 @@ from .utils import (_toggle_options, _toggle_proj, _prepare_mne_browse,
                     _radio_clicked, _set_radio_button, _handle_topomap_bads,
                     _change_channel_group, _plot_annotations, _setup_butterfly,
                     _handle_decim, _setup_plot_projector, _check_cov,
-                    _set_ax_label_style, _draw_vert_line, _simplify_float)
+                    _set_ax_label_style, _draw_vert_line, _simplify_float,
+                    _check_psd_fmax)
 
 
 def _plot_update_raw_proj(params, bools):
@@ -618,6 +619,7 @@ def plot_raw_psd(raw, fmin=0, fmax=np.inf, tmin=None, tmax=None, proj=False,
     fig, picks_list, titles_list, units_list, scalings_list, ax_list, \
         make_label = _set_psd_plot_params(
             raw.info, proj, picks, ax, area_mode)
+    _check_psd_fmax(raw, fmax)
     del ax
     psd_list = list()
     if n_fft is None:
diff --git a/mne/viz/utils.py b/mne/viz/utils.py
index 8978df58a..a56878d7e 100644
--- a/mne/viz/utils.py
+++ b/mne/viz/utils.py
@@ -3077,6 +3077,14 @@ def _convert_psds(psds, dB, estimate, scaling, unit, ch_names):
     return ylabel
 
 
+def _check_psd_fmax(inst, fmax):
+    """Make sure requested fmax does not exceed Nyquist frequency."""
+    if np.isfinite(fmax) and (fmax > inst.info['sfreq'] / 2):
+        raise ValueError('Requested fmax ({} Hz) must not exceed one half '
+                         'the sampling frequency of the data ({}).'
+                         .format(fmax, 0.5 * inst.info['sfreq']))
+
+
 def _plot_psd(inst, fig, freqs, psd_list, picks_list, titles_list,
               units_list, scalings_list, ax_list, make_label, color, area_mode,
               area_alpha, dB, estimate, average, spatial_colors, xscale,


Test patch:
diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py
index 3b9da893a..97350a83a 100644
--- a/mne/viz/tests/test_raw.py
+++ b/mne/viz/tests/test_raw.py
@@ -411,6 +411,10 @@ def test_plot_raw_psd():
     raw.plot_psd(reject_by_annotation=True)
     raw.plot_psd(reject_by_annotation=False)
 
+    # test fmax value checking
+    with pytest.raises(ValueError, match='not exceed one half the sampling'):
+        raw.plot_psd(fmax=50000)
+
     # gh-5046
     raw = read_raw_fif(raw_fname, preload=True).crop(0, 1)
     picks = pick_types(raw.info)