Repository: mne-tools/mne-python

Issue:
BUG: Spectrum should only warn on zero spectrum for non-bads
CircleCI just [hit](https://app.circleci.com/pipelines/github/mne-tools/mne-python/21221/workflows/00fe92c1-8ad8-46c3-b797-d8fda6ab4fce/jobs/60092) this:
```
/home/circleci/project/tutorials/preprocessing/50_artifact_correction_ssp.py failed leaving traceback:
Traceback (most recent call last):
  File "/home/circleci/project/tutorials/preprocessing/50_artifact_correction_ssp.py", line 109, in <module>
    spectrum = empty_room_raw.compute_psd()
  File "<decorator-gen-292>", line 12, in compute_psd
  File "/home/circleci/project/mne/io/base.py", line 2181, in compute_psd
    return Spectrum(
  File "/home/circleci/project/mne/time_frequency/spectrum.py", line 1172, in __init__
    self._check_values()
  File "/home/circleci/project/mne/time_frequency/spectrum.py", line 454, in _check_values
    warn(f'Zero value in spectrum for channel{s} {", ".join(chs)}', UserWarning)
  File "/home/circleci/project/mne/utils/_logging.py", line 414, in warn
    warnings.warn_explicit(
UserWarning: Zero value in spectrum for channel MEG 2443
```
I am pretty sure this channel is marked as bad in the data. It seems like spectrum should perhaps not warn about zero or nan/inf values if a channel is bad.

If we go this route we can undo two `verbose="error"`s introduced in https://github.com/mne-tools/mne-python/pull/12074


Discussion:

Resolving patch:
diff --git a/mne/time_frequency/spectrum.py b/mne/time_frequency/spectrum.py
--- a/mne/time_frequency/spectrum.py
+++ b/mne/time_frequency/spectrum.py
@@ -439,15 +439,17 @@ def _check_values(self):
         """Check PSD results for correct shape and bad values."""
         assert len(self._dims) == self._data.ndim, (self._dims, self._data.ndim)
         assert self._data.shape == self._shape
-        # negative values OK if the spectrum is really fourier coefficients
-        if "taper" in self._dims:
-            return
         # TODO: should this be more fine-grained (report "chan X in epoch Y")?
         ch_dim = self._dims.index("channel")
-        dims = np.arange(self._data.ndim).tolist()
+        dims = list(range(self._data.ndim))
         dims.pop(ch_dim)
         # take min() across all but the channel axis
-        bad_value = self._data.min(axis=tuple(dims)) <= 0
+        # (if the abs becomes memory intensive we could iterate over channels)
+        use_data = self._data
+        if use_data.dtype.kind == "c":
+            use_data = np.abs(use_data)
+        bad_value = use_data.min(axis=tuple(dims)) == 0
+        bad_value &= ~np.isin(self.ch_names, self.info["bads"])
         if bad_value.any():
             chs = np.array(self.ch_names)[bad_value].tolist()
             s = _pl(bad_value.sum())
diff --git a/tutorials/preprocessing/50_artifact_correction_ssp.py b/tutorials/preprocessing/50_artifact_correction_ssp.py
--- a/tutorials/preprocessing/50_artifact_correction_ssp.py
+++ b/tutorials/preprocessing/50_artifact_correction_ssp.py
@@ -111,7 +111,8 @@
 # individual spectrum for each sensor, or an average (with confidence band)
 # across sensors:
 
-spectrum = empty_room_raw.compute_psd(verbose="error")  # ignore zero value warning
+raw.info["bads"] = ["MEG 2443"]
+spectrum = empty_room_raw.compute_psd()
 for average in (False, True):
     spectrum.plot(average=average, dB=False, xscale="log", picks="data", exclude="bads")
 


Test patch:
diff --git a/mne/time_frequency/tests/test_spectrum.py b/mne/time_frequency/tests/test_spectrum.py
--- a/mne/time_frequency/tests/test_spectrum.py
+++ b/mne/time_frequency/tests/test_spectrum.py
@@ -1,4 +1,3 @@
-from contextlib import nullcontext
 from functools import partial
 
 import numpy as np
@@ -359,7 +358,6 @@ def test_spectrum_complex(method, average):
     assert len(epochs) == 5
     assert len(epochs.times) == 2 * sfreq
     kwargs = dict(output="complex", method=method)
-    ctx = pytest.warns(UserWarning, match="Zero value")
     if method == "welch":
         kwargs["n_fft"] = sfreq
         want_dims = ("epoch", "channel", "freq")
@@ -371,11 +369,9 @@ def test_spectrum_complex(method, average):
     else:
         assert method == "multitaper"
         assert not average
-        ctx = nullcontext()
         want_dims = ("epoch", "channel", "taper", "freq")
         want_shape = (5, 1, 7, sfreq + 1)
-    with ctx:
-        spectrum = epochs.compute_psd(**kwargs)
+    spectrum = epochs.compute_psd(**kwargs)
     idx = np.argmin(np.abs(spectrum.freqs - freq))
     assert spectrum.freqs[idx] == freq
     assert spectrum._dims == want_dims
@@ -389,6 +385,13 @@ def test_spectrum_complex(method, average):
         coef = coef.mean(-1)  # over segments
     coef = coef.item()
     assert_allclose(np.angle(coef), phase, rtol=1e-4)
+    # Now test that it warns appropriately
+    epochs._data[0, 0, :] = 0  # actually zero for one epoch and ch
+    with pytest.warns(UserWarning, match="Zero value.*channel 0"):
+        epochs.compute_psd(**kwargs)
+    # But not if we mark that channel as bad
+    epochs.info["bads"] = epochs.ch_names[:1]
+    epochs.compute_psd(**kwargs)
 
 
 def test_spectrum_kwarg_triaging(raw):