Repository: mne-tools/mne-python

Issue:
Gradiometer units on PSD
Units PSDs for gradiometers are off (dB = True or False) -- fT/cm^2/Hz... Should it not be (fT/cm)^2/Hz or fT^2/cm^2/Hz? Is the square on the cm meant to represent the whole fT/cm term?

Thanks in advance for clarification.

Also curious why the estimate = 'auto' default is a PSD for dB = True but an ASD for dB = False?


Discussion:
> Is the square on the cm meant to represent the whole fT/cm term?

Yes it's just poorly formatted

> Also curious why the estimate = 'auto' default is a PSD for dB = True but an ASD for dB = False?

Historical reasons / backward compatibility IIRC

Resolving patch:
diff --git a/mne/viz/utils.py b/mne/viz/utils.py
--- a/mne/viz/utils.py
+++ b/mne/viz/utils.py
@@ -2959,11 +2959,12 @@ def _convert_psds(psds, dB, estimate, scaling, unit, ch_names):
     if estimate == 'amplitude':
         np.sqrt(psds, out=psds)
         psds *= scaling
-        ylabel = r'$\mathrm{%s / \sqrt{Hz}}$' % unit
+        ylabel = r'$\mathrm{%s/\sqrt{Hz}}$' % unit
     else:
         psds *= scaling * scaling
-        ylabel = r'$\mathrm{%s^2/Hz}$' % unit
-
+        if '/' in unit:
+            unit = '(%s)' % unit
+        ylabel = r'$\mathrm{%s²/Hz}$' % unit
     if dB:
         np.log10(np.maximum(psds, np.finfo(float).tiny), out=psds)
         psds *= 10
@@ -2991,15 +2992,12 @@ def _plot_psd(inst, fig, freqs, psd_list, picks_list, titles_list,
     line_alpha = float(line_alpha)
     ylabels = list()
     for ii, (psd, picks, title, ax, scalings, units) in enumerate(zip(
-                                                                  psd_list,
-                                                                  picks_list,
-                                                                  titles_list,
-                                                                  ax_list,
-                                                                  scalings_list, # noqa
-                                                                  units_list)):
+            psd_list, picks_list, titles_list, ax_list,
+            scalings_list, units_list)):
         ylabel = _convert_psds(psd, dB, estimate, scalings, units,
                                [inst.ch_names[pi] for pi in picks])
         ylabels.append(ylabel)
+        del ylabel
 
         if average:
             # mean across channels
@@ -3048,7 +3046,7 @@ def _plot_psd(inst, fig, freqs, psd_list, picks_list, titles_list,
                     ylim=None, times=freqs, bad_ch_idx=[], titles=titles,
                     ch_types_used=ch_types_used, selectable=True, psd=True,
                     line_alpha=line_alpha, nave=None)
-    for ii, (ax, title) in enumerate(zip(ax_list, titles_list)):
+    for ii, ax in enumerate(ax_list):
         ax.grid(True, linestyle=':')
         if xscale == 'log':
             ax.set(xscale='log')
@@ -3059,8 +3057,7 @@ def _plot_psd(inst, fig, freqs, psd_list, picks_list, titles_list,
         if make_label:
             if ii == len(picks_list) - 1:
                 ax.set_xlabel('Frequency (Hz)')
-            ax.set_ylabel(ylabel)
-            ax.set_title(title)
+            ax.set(ylabel=ylabels[ii], title=titles_list[ii])
     if make_label:
         fig.subplots_adjust(left=.1, bottom=.1, right=.9, top=.9, wspace=0.3,
                             hspace=0.5)
diff --git a/tutorials/raw/plot_40_visualize_raw.py b/tutorials/raw/plot_40_visualize_raw.py
--- a/tutorials/raw/plot_40_visualize_raw.py
+++ b/tutorials/raw/plot_40_visualize_raw.py
@@ -7,7 +7,7 @@
 
 This tutorial shows how to plot continuous data as a time series, how to plot
 the spectral density of continuous data, and how to plot the sensor locations
-and projectors stored in Raw objects.
+and projectors stored in :class:`~mne.io.Raw` objects.
 
 .. contents:: Page contents
    :local:


Test patch:
diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py
--- a/mne/viz/tests/test_raw.py
+++ b/mne/viz/tests/test_raw.py
@@ -389,7 +389,23 @@ def test_plot_raw_psd():
     for dB, estimate in itertools.product((True, False),
                                           ('power', 'amplitude')):
         with pytest.warns(UserWarning, match='[Infinite|Zero]'):
-            raw.plot_psd(average=True, dB=dB, estimate=estimate)
+            fig = raw.plot_psd(average=True, dB=dB, estimate=estimate)
+        ylabel = fig.axes[1].get_ylabel()
+        ends_dB = ylabel.endswith('mathrm{(dB)}$')
+        if dB:
+            assert ends_dB, ylabel
+        else:
+            assert not ends_dB, ylabel
+        if estimate == 'amplitude':
+            assert r'fT/cm/\sqrt{Hz}' in ylabel, ylabel
+        else:
+            assert estimate == 'power'
+            assert '(fT/cm)²/Hz' in ylabel, ylabel
+        ylabel = fig.axes[0].get_ylabel()
+        if estimate == 'amplitude':
+            assert r'fT/\sqrt{Hz}' in ylabel
+        else:
+            assert 'fT²/Hz' in ylabel
     # test reject_by_annotation
     raw = _get_raw()
     raw.set_annotations(Annotations([1, 5], [3, 3], ['test', 'test']))