Skip to content

Commit c3db378

Browse files
DOC: document first_samp behavior after crop and RawArray workaround (#13685)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 82cc6f0 commit c3db378

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

doc/changes/dev/13685.other.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document the behavior of :term:`first_samp` after :meth:`~mne.io.Raw.crop` and add ``reset_first_samp`` parameter to reset it to 0 after cropping, by :newcontrib:`Famous077`.

mne/io/base.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,7 +1568,15 @@ def rescale(self, scalings, *, verbose=None):
15681568
return self
15691569

15701570
@verbose
1571-
def crop(self, tmin=0.0, tmax=None, include_tmax=True, *, verbose=None):
1571+
def crop(
1572+
self,
1573+
tmin=0.0,
1574+
tmax=None,
1575+
include_tmax=True,
1576+
*,
1577+
reset_first_samp=False,
1578+
verbose=None,
1579+
):
15721580
"""Crop raw data file.
15731581
15741582
Limit the data from the raw file to go between specific times. Note
@@ -1585,12 +1593,51 @@ def crop(self, tmin=0.0, tmax=None, include_tmax=True, *, verbose=None):
15851593
%(tmin_raw)s
15861594
%(tmax_raw)s
15871595
%(include_tmax)s
1596+
reset_first_samp : bool
1597+
If True, reset :term:`first_samp` to 0 after cropping, treating
1598+
the cropped segment as an independent recording. Note that this
1599+
can break things if you extracted events before cropping and try
1600+
to use them afterward. Default is False.
1601+
1602+
.. versionadded:: 1.12
15881603
%(verbose)s
15891604
15901605
Returns
15911606
-------
15921607
raw : instance of Raw
15931608
The cropped raw object, modified in-place.
1609+
1610+
Notes
1611+
-----
1612+
After cropping, :term:`first_samp` is updated to reflect the new
1613+
start of the data, preserving the original recording timeline.
1614+
This means ``raw.times`` will still start at ``0.0``, but
1615+
``raw.first_samp`` will reflect the offset from the original
1616+
recording. If you want to treat the cropped segment as an
1617+
independent signal with ``first_samp=0``, you can convert it
1618+
to a :class:`~mne.io.RawArray`::
1619+
1620+
raw_array = mne.io.RawArray(raw.get_data(), raw.info)
1621+
1622+
Examples
1623+
--------
1624+
By default, cropping preserves the original recording timeline,
1625+
so :term:`first_samp` remains non-zero after cropping::
1626+
1627+
>>> raw = mne.io.read_raw_fif(fname) # doctest: +SKIP
1628+
>>> print(raw.first_samp) # doctest: +SKIP
1629+
25800
1630+
>>> raw.crop(tmin=10, tmax=20) # doctest: +SKIP
1631+
>>> print(raw.first_samp) # doctest: +SKIP
1632+
27810
1633+
1634+
If you want to treat the cropped segment as an independent
1635+
recording, use ``reset_first_samp=True``::
1636+
1637+
>>> raw2 = raw.copy().crop(tmin=10, tmax=20,
1638+
... reset_first_samp=True) # doctest: +SKIP
1639+
>>> print(raw2.first_samp) # doctest: +SKIP
1640+
0
15941641
"""
15951642
max_time = (self.n_times - 1) / self.info["sfreq"]
15961643
if tmax is None:
@@ -1648,7 +1695,11 @@ def crop(self, tmin=0.0, tmax=None, include_tmax=True, *, verbose=None):
16481695
# set_annotations will put it back.
16491696
annotations.onset -= self.first_time
16501697
self.set_annotations(annotations, False)
1651-
1698+
if reset_first_samp:
1699+
delta = self._first_samps[0]
1700+
self._first_samps -= delta
1701+
self._last_samps -= delta
1702+
self._cropped_samp -= delta
16521703
return self
16531704

16541705
@verbose

mne/io/tests/test_raw.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,3 +1087,23 @@ def test_rescale():
10871087
orig = raw.get_data()
10881088
raw.rescale(4) # a scalar works
10891089
assert_allclose(raw.get_data(), orig * 4)
1090+
1091+
1092+
def test_crop_reset_first_samp():
1093+
"""Regression test for GH-13278.
1094+
1095+
crop(reset_first_samp=True) must reset first_samp to 0.
1096+
"""
1097+
info = create_info(ch_names=["CH1"], sfreq=1000.0, ch_types=["eeg"])
1098+
data = np.zeros((1, 10000))
1099+
raw = RawArray(data, info)
1100+
1101+
# crop and reset first_samp
1102+
raw.crop(tmin=2.0, tmax=5.0, reset_first_samp=True)
1103+
assert raw.first_samp == 0
1104+
assert raw.times[0] == 0.0
1105+
1106+
# crop without reset_first_samp (default behaviour)
1107+
raw2 = RawArray(data, info)
1108+
raw2.crop(tmin=2.0, tmax=5.0)
1109+
assert raw2.first_samp != 0

0 commit comments

Comments
 (0)