Repository: mne-tools/mne-python

Issue:
Error with load epochs after cropping after baseline correction
#### Describe the bug

I posted this on the mailing list and Alex Gramfort asked me to open this here as an issue:

I did my baseline correction on a period of 1s which is 6s prior to the onset of the event of interest (due to unfortunate experimental design), so I initially epoched my data to -7 to 3s . After epoching the data I then cropped the data to -0.3 to 3s, as I no longer needed the 7 seconds prior to the data after the baseline correction was done. When I then save the epoched data and load it again somewhere else and error occurs because the baseline parameters are outside the epoched data. 
To my understanding the baseline correction is already applied during epoching so I the decided to just set the baseline parameters to (0,0) after baseline correction has been done and data has been cropped. If I do that I can load the epoched data without no problem. 

#### Steps to reproduce
    decision_epochs = mne.Epochs(eeg_data_interp_ref_ica, events, tmin=-7, tmax=3,baseline=(-7,-6),
                        #reject=reject_criteria,  flat=flat_criteria, # commented beause we're using autoreject here
                        event_id = decision_events_of_interest,
                        reject_by_annotation=False, preload=True)

    decision_epochs_cropped = decision_epochs.copy().crop(tmin=-0.3, tmax=3, include_tmax=True)


    # drop epochs after cropping

    ar = AutoReject(verbose='tqdm_notebook', n_jobs=n_jobs, consensus=np.linspace(0, 1, 20), n_interpolate = np.array([1,4,8,16,32]) )
    decision_epochs_cropped_clean_ar = ar.fit_transform(decision_epochs_cropped)

    out_dir='/data/sebastian/EEG/neural_analysis/'
    
    # This version can't be loaded after
    decision_epochs_cropped_clean_ar.save(opj(out_dir,task,sub+'-TEST_'+str(cutoff_l)+'_HP_'+str(cutoff_h)+'_'+reference+'.fif'), overwrite=True)
    
    # This version can't be loaded after
    decision_epochs_cropped_clean_ar.baseline=(0,0)
    decision_epochs_cropped_clean_ar.save(opj(out_dir,task,sub+'-TEST2_'+str(cutoff_l)+'_HP_'+str(cutoff_h)+'_'+reference+'.fif'), overwrite=True)







Discussion:
It would be really nice to get this fixed. For exactly the same reasons, I can only work with the NumPy arrays after extracting the data from the MNE.Epochs object using the `get_data` method and then saving the info parameter somewhere and rebuilding a new MNE.Epochs object later on if I need to do further processing, all because I can't use the `save` method to then re-load, because of the complaints that the (already applied) baseline correction data has been cropped away in an earlier stage of preprocessing. 

Thanks for raising this, Sebastian. I was too busy to, at the time when it was causing me headaches!

Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -1507,6 +1507,12 @@ def crop(self, tmin=None, tmax=None, include_tmax=True):
         self._set_times(self.times[tmask])
         self._raw_times = self._raw_times[tmask]
         self._data = self._data[:, :, tmask]
+        try:
+            _check_baseline(self.baseline, tmin, tmax, self.info['sfreq'])
+        except ValueError:  # in no longer applies, wipe it out
+            warn('Cropping removes baseline period, setting '
+                 'epochs.baseline = None')
+            self.baseline = None
         return self
 
     def copy(self):


Test patch:
diff --git a/mne/beamformer/tests/test_external.py b/mne/beamformer/tests/test_external.py
--- a/mne/beamformer/tests/test_external.py
+++ b/mne/beamformer/tests/test_external.py
@@ -46,9 +46,9 @@ def _get_bf_data(save_fieldtrip=False):
     epochs.pick_types(meg='grad')
     evoked.pick_types(meg='grad')
 
-    # compute covariance matrix
+    # compute covariance matrix (ignore false alarm about no baseline)
     data_cov = mne.compute_covariance(epochs, tmin=0.04, tmax=0.145,
-                                      method='empirical')
+                                      method='empirical', verbose='error')
 
     if save_fieldtrip is True:
         # if the covariance matrix and epochs need resaving:
diff --git a/mne/beamformer/tests/test_lcmv.py b/mne/beamformer/tests/test_lcmv.py
--- a/mne/beamformer/tests/test_lcmv.py
+++ b/mne/beamformer/tests/test_lcmv.py
@@ -91,7 +91,8 @@ def _get_data(tmin=-0.1, tmax=0.15, all_forward=True, epochs=True,
             baseline=(None, 0), preload=epochs_preload, reject=reject)
         if epochs_preload:
             epochs.resample(200, npad=0)
-        epochs.crop(0, None)
+        with pytest.warns(RuntimeWarning, match='baseline = None'):
+            epochs.crop(0, None)
         evoked = epochs.average()
         info = evoked.info
     else:
@@ -104,7 +105,8 @@ def _get_data(tmin=-0.1, tmax=0.15, all_forward=True, epochs=True,
     noise_cov = mne.cov.regularize(noise_cov, info, mag=0.05, grad=0.05,
                                    eeg=0.1, proj=True, rank=None)
     if data_cov:
-        data_cov = mne.compute_covariance(epochs, tmin=0.04, tmax=0.145)
+        data_cov = mne.compute_covariance(
+            epochs, tmin=0.04, tmax=0.145, verbose='error')  # baseline warning
     else:
         data_cov = None
 
diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py
--- a/mne/tests/test_epochs.py
+++ b/mne/tests/test_epochs.py
@@ -677,32 +677,33 @@ def test_rescale():
     assert_allclose(tester(mode='zlogratio'), x / s)
 
 
-def test_epochs_baseline():
+@pytest.mark.parametrize('preload', (True, False))
+def test_epochs_baseline(preload):
     """Test baseline and rescaling modes with and without preloading."""
     data = np.array([[2, 3], [2, 3]], float)
     info = create_info(2, 1000., ('eeg', 'misc'))
     raw = RawArray(data, info)
     events = np.array([[0, 0, 1]])
-    for preload in (False, True):
-        epochs = mne.Epochs(raw, events, None, 0, 1e-3, baseline=None,
-                            preload=preload)
-        epochs.drop_bad()
-        epochs_data = epochs.get_data()
-        assert epochs_data.shape == (1, 2, 2)
-        expected = data.copy()
-        assert_array_equal(epochs_data[0], expected)
-        # the baseline period (1 sample here)
-        epochs.apply_baseline((0, 0))
-        expected[0] = [0, 1]
-        if preload:
-            assert_allclose(epochs_data[0][0], expected[0])
-        else:
-            assert_allclose(epochs_data[0][0], expected[1])
-        assert_allclose(epochs.get_data()[0], expected, atol=1e-7)
-        # entire interval
-        epochs.apply_baseline((None, None))
-        expected[0] = [-0.5, 0.5]
-        assert_allclose(epochs.get_data()[0], expected)
+
+    epochs = mne.Epochs(raw, events, None, 0, 1e-3, baseline=None,
+                        preload=preload)
+    epochs.drop_bad()
+    epochs_data = epochs.get_data()
+    assert epochs_data.shape == (1, 2, 2)
+    expected = data.copy()
+    assert_array_equal(epochs_data[0], expected)
+    # the baseline period (1 sample here)
+    epochs.apply_baseline((0, 0))
+    expected[0] = [0, 1]
+    if preload:
+        assert_allclose(epochs_data[0][0], expected[0])
+    else:
+        assert_allclose(epochs_data[0][0], expected[1])
+    assert_allclose(epochs.get_data()[0], expected, atol=1e-7)
+    # entire interval
+    epochs.apply_baseline((None, None))
+    expected[0] = [-0.5, 0.5]
+    assert_allclose(epochs.get_data()[0], expected)
 
 
 def test_epochs_bad_baseline():
@@ -929,11 +930,26 @@ def test_epochs_io_preload(tmpdir, preload):
 
     epochs.event_id.pop('1')
     epochs.event_id.update({'a:a': 1})  # test allow for ':' in key
-    epochs.save(op.join(tempdir, 'foo-epo.fif'), overwrite=True)
-    epochs_read2 = read_epochs(op.join(tempdir, 'foo-epo.fif'),
-                               preload=preload)
-    assert_equal(epochs_read2.event_id, epochs.event_id)
-    assert_equal(epochs_read2['a:a'].average().comment, 'a:a')
+    fname_temp = op.join(tempdir, 'foo-epo.fif')
+    epochs.save(fname_temp, overwrite=True)
+    epochs_read = read_epochs(fname_temp, preload=preload)
+    assert_equal(epochs_read.event_id, epochs.event_id)
+    assert_equal(epochs_read['a:a'].average().comment, 'a:a')
+
+    # now use a baseline, crop it out, and I/O round trip afterward
+    assert epochs.times[0] < 0
+    assert epochs.times[-1] > 0
+    epochs.apply_baseline((None, 0))
+    with pytest.warns(RuntimeWarning,
+                      match=r'setting epochs\.baseline = None'):
+        epochs.crop(1. / epochs.info['sfreq'], None)
+    assert epochs.baseline is None
+    epochs.save(fname_temp, overwrite=True)
+    epochs_read = read_epochs(fname_temp, preload=preload)
+    assert epochs_read.baseline is None
+    assert_allclose(epochs.get_data(), epochs_read.get_data(),
+                    rtol=6e-4)  # XXX this rtol should be better...?
+    del epochs, epochs_read
 
     # add reject here so some of the epochs get dropped
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
@@ -988,7 +1004,10 @@ def test_epochs_io_preload(tmpdir, preload):
     assert epochs.drop_log == epochs_read.drop_log
 
     # Test that having a single time point works
-    epochs.load_data().crop(0, 0)
+    assert epochs.baseline is not None
+    with pytest.warns(RuntimeWarning, match=r'setting epochs\.baseline'):
+        epochs.load_data().crop(0, 0)
+    assert epochs.baseline is None
     assert_equal(len(epochs.times), 1)
     assert_equal(epochs.get_data().shape[-1], 1)
     epochs.save(temp_fname, overwrite=True)
diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py
--- a/mne/viz/tests/test_ica.py
+++ b/mne/viz/tests/test_ica.py
@@ -169,7 +169,7 @@ def test_plot_ica_properties():
 
     # Test handling of zeros
     raw._data[:] = 0
-    with pytest.warns(UserWarning, match='Infinite value .* for epochs '):
+    with pytest.warns(None):  # Usually UserWarning: Infinite value .* for epo
         ica.plot_properties(raw)
     ica = ICA(random_state=0, max_iter=1)
     epochs.pick_channels(pick_names)