Repository: mne-tools/mne-python

Issue:
Epochs are not concatenated chronologically
#### Describe the bug
Concatenate Epochs currently does not create chronologically ordered events.

#### Steps to reproduce

```python
import mne
import numpy as np

np.random.seed(10)
n_epochs, n_channels, n_timepoints = 2, 4, 21
ch_names = [f"ch_{i}" for i in range(n_channels)]
fake_data = 1e-6*np.random.random((n_epochs, n_channels, n_timepoints))
events = np.array([[1000, 0, 1], [20000, 0, 1]])
events2 = np.array([[1, 0, 1], [20, 0, 1]])

info = mne.create_info(ch_names=ch_names, sfreq=40)
epo = mne.EpochsArray(fake_data, info, events=events)
epo2 = mne.EpochsArray(np.copy(fake_data), info, events=events2)

epo_conc = mne.concatenate_epochs([epo, epo2])
assert all(np.diff(epo_conc.events[:, 0]) >= 0), "Not chronologically sorted events"
```

#### Expected results
Events are ordered.

#### Actual results
Events are not ordered.

#### Additional information
During concatenation, the offset is erroneously calculated on the current epoch to concatenate instead of the previous one. See the linked PR.


Discussion:

Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -3364,8 +3364,8 @@ def _concatenate_epochs(epochs_list, with_data=True, add_offset=True, *,
     selection = out.selection
     # offset is the last epoch + tmax + 10 second
     shift = int((10 + tmax) * out.info['sfreq'])
-    events_offset = 0
-    events_number_overflow = False
+    events_offset = int(np.max(events[0][:, 0])) + shift
+    events_overflow = False
     for ii, epochs in enumerate(epochs_list[1:], 1):
         _ensure_infos_match(epochs.info, info, f'epochs[{ii}]',
                             on_mismatch=on_mismatch)
@@ -3392,16 +3392,17 @@ def _concatenate_epochs(epochs_list, with_data=True, add_offset=True, *,
         if len(epochs.events) == 0:
             warn('One of the Epochs objects to concatenate was empty.')
         elif add_offset:
-            # We need to cast to a native Python int here to prevent an
-            # overflow of a numpy int32 or int64 type.
-            events_offset += int(np.max(evs[:, 0])) + shift
+            # We need to cast to a native Python int here to detect an
+            # overflow of a numpy int32 (which is the default on windows)
+            max_timestamp = int(np.max(evs[:, 0]))
+            evs[:, 0] += events_offset
+            events_offset += max_timestamp + shift
             if events_offset > INT32_MAX:
                 warn(f'Event number greater than {INT32_MAX} created, '
                      'events[:, 0] will be assigned consecutive increasing '
                      'integer values')
-                events_number_overflow = True
-            else:
-                evs[:, 0] += events_offset
+                events_overflow = True
+                add_offset = False  # we no longer need to add offset
         events.append(evs)
         selection = np.concatenate((selection, epochs.selection))
         drop_log = drop_log + epochs.drop_log
@@ -3409,7 +3410,7 @@ def _concatenate_epochs(epochs_list, with_data=True, add_offset=True, *,
         metadata.append(epochs.metadata)
     events = np.concatenate(events, axis=0)
     # check to see if we exceeded our maximum event offset
-    if events_number_overflow:
+    if events_overflow:
         events[:, 0] = np.arange(1, len(events) + 1)
 
     # Create metadata object (or make it None)


Test patch:
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
@@ -2764,6 +2764,14 @@ def test_concatenate_epochs():
     with pytest.warns(RuntimeWarning, match='was empty'):
         concatenate_epochs([epochs, epochs2])
 
+    # check concatenating epochs results are chronologically ordered
+    epochs2 = epochs.copy().load_data()
+    # Ensure first event is at 0
+    epochs2.events[:, 0] -= np.min(epochs2.events[:, 0])
+    with pytest.warns(RuntimeWarning, match='not chronologically ordered'):
+        concatenate_epochs([epochs, epochs2], add_offset=False)
+    concatenate_epochs([epochs, epochs2], add_offset=True)
+
 
 def test_concatenate_epochs_large():
     """Test concatenating epochs on large data."""