Skip to content

Commit

Permalink
Fix mne.io.BaserRAW duration calculation
Browse files Browse the repository at this point in the history
The duration calculation, used in `BaseRAW._html_repr_()` and
`BaseRAW.__repr__()`, was taking the timestamp of the last sample as the
duration of the acquisition, but was not accounting for the length of
the last sample.

Also, added tests for the refactored `BaseRAW.duration` property and
`BaseRAW._get_duration()` method, and used a sfreq value that revealed
the discrepancy in the duration calculation in the `BaseRAW.__repr__()`
method.

Finally, simplified the duration string calculation for the html display
by rounding up all the duration seconds, not just the remainder after
hour and minute calculations, thereby avoiding "00:01:60" calculations,
which should have been "00:02:00", when there are fractions of a second
remaining.

Fixes: #12954
  • Loading branch information
leorochael committed Nov 14, 2024
1 parent cb597f8 commit 58a9d38
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 11 deletions.
1 change: 1 addition & 0 deletions doc/changes/devel/12955.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix duration calculation for the textual (``__repr__``) and html (``_repr_html_``, used by e.g. Jupyter) display of :class:`mne.io.BaseRAW` instances. For ex. a duration of 1h is now displayed as ``00:01:00`` rather than ``00:59:60``. By :newcontrib:`Leonardo Rochael Almeida`.
11 changes: 4 additions & 7 deletions mne/io/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1927,7 +1927,7 @@ def n_times(self):
@property
def duration(self):
"""Duration of the data in seconds."""
return self.times[-1]
return self.n_times / self.info["sfreq"]

def __len__(self):
"""Return the number of time points.
Expand Down Expand Up @@ -2139,14 +2139,11 @@ def _repr_html_(self):
)

def _get_duration_string(self):
duration = timedelta(seconds=self.duration)
# https://stackoverflow.com/a/10981895
hours, remainder = divmod(duration.seconds, 3600)
duration = np.ceil(self.duration) # always take full seconds
hours, remainder = divmod(duration, 3600)
minutes, seconds = divmod(remainder, 60)
seconds += duration.microseconds / 1e6
seconds = np.ceil(seconds) # always take full seconds

return f"{int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}"
return f"{hours:02.0f}:{minutes:02.0f}:{seconds:02.0f}"

def add_events(self, events, stim_channel=None, replace=False):
"""Add events to stim channel.
Expand Down
38 changes: 34 additions & 4 deletions mne/io/tests/test_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,14 +778,44 @@ def raw_factory(meas_date):
assert raw_A.annotations.orig_time == _stamp_to_dt((0, 0))


def test_repr():
def test_duration_property():
"""Test BaseRAW.duration property."""
sfreq = 1000
info = create_info(ch_names=["EEG 001"], sfreq=sfreq)
raw = BaseRaw(info, last_samps=[sfreq * 60 - 1])
assert raw.duration == 60


@pytest.mark.parametrize("sfreq", [1, 10, 100, 1000])
@pytest.mark.parametrize(
"duration, expected",
[
(0.1, "00:00:01"),
(1, "00:00:01"),
(59, "00:00:59"),
(59.1, "00:01:00"),
(60, "00:01:00"),
(60.1, "00:01:01"),
(61, "00:01:01"),
(61.1, "00:01:02"),
],
)
def test_get_duration_string(sfreq, duration, expected):
"""Test BaseRAW_get_duration_string() method."""
info = create_info(ch_names=["EEG 001"], sfreq=sfreq)
raw = BaseRaw(info, last_samps=[sfreq * duration - 1])
assert raw._get_duration_string() == expected


@pytest.mark.parametrize("sfreq", [1, 10, 100, 256, 1000])
def test_repr(sfreq):
"""Test repr of Raw."""
sfreq = 256
info = create_info(3, sfreq)
raw = RawArray(np.zeros((3, 10 * sfreq)), info)
sample_count = 10 * sfreq
raw = RawArray(np.zeros((3, sample_count)), info)
r = repr(raw)
size_str = sizeof_fmt(raw._size)
assert r == f"<RawArray | 3 x 2560 (10.0 s), ~{size_str}, data loaded>"
assert r == f"<RawArray | 3 x {sample_count} (10.0 s), ~{size_str}, data loaded>"
assert raw._repr_html_()


Expand Down

0 comments on commit 58a9d38

Please sign in to comment.