Skip to content
Open
1 change: 1 addition & 0 deletions doc/changes/dev/13161.newfeature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:meth:`mne.Report.add_projs` now accepts :class:`~mne.Epochs` as ``info`` and adds an ``add_rate`` parameter to show estimated event rates (e.g. heart rate or blink rate) in the report (:gh:`13161` by `Master-Zero1`_).
Comment thread
Master-Zero1 marked this conversation as resolved.
Outdated
58 changes: 56 additions & 2 deletions mne/report/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,7 @@ def add_projs(
title,
projs=None,
topomap_kwargs=None,
add_rate="auto",
tags=("ssp",),
joint=False,
picks_trace=None,
Expand All @@ -1864,14 +1865,23 @@ def add_projs(

Parameters
----------
info : instance of Info | instance of Evoked | path-like
info : instance of Info | instance of Evoked | instance of Epochs | path-like
An `~mne.Info` structure or the path of a file containing one.
title : str
The title corresponding to the :class:`~mne.Projection` object.
projs : iterable of mne.Projection | path-like | None
The projection vectors to add to the report. Can be the path to a
file that will be loaded via `mne.read_proj`. If ``None``, the
projectors are taken from ``info['projs']``.
add_rate : bool | "auto"
Whether to add an estimated event rate to the figure caption when
``info`` is an instance of :class:`~mne.Epochs`. If ``"auto"``
(default), the rate is shown only when the projector descriptions
suggest ECG or EOG content (i.e. contain ``"ecg"``, ``"eog"``, or
``"blink"``). If ``True``, always show the rate. If ``False``, never
show it.

.. versionadded:: 1.10
Comment thread
larsoner marked this conversation as resolved.
Outdated
%(topomap_kwargs)s
%(tags_report)s
joint : bool
Expand Down Expand Up @@ -1903,6 +1913,7 @@ def add_projs(
section=section,
tags=tags,
topomap_kwargs=topomap_kwargs,
add_rate=add_rate,
replace=replace,
joint=joint,
)
Expand Down Expand Up @@ -3611,6 +3622,30 @@ def _add_raw(
replace=replace,
)

@staticmethod
def _event_estimate(epochs, projs):
rate_caption = None
n_events = len(epochs.drop_log)
event_times = [ev[0] / epochs.info["sfreq"] for ev in epochs.events]
if len(event_times) > 1:
duration_sec = event_times[-1] - event_times[0]
duration_min = duration_sec / 60.0
else:
duration_min = 0.0
return None
if duration_min > 0:
rate = n_events / duration_min
unit = (
"BPM"
if any(
any(kw in p["desc"].lower() for kw in ("ecg", "heart"))
Comment thread
Master-Zero1 marked this conversation as resolved.
Outdated
for p in projs
)
else "events/min"
)
rate_caption = f"Estimated rate: {rate:.1f} {unit}"
return rate_caption

@_use_agg
def _add_projs(
self,
Expand All @@ -3622,16 +3657,20 @@ def _add_projs(
tags,
section,
topomap_kwargs,
add_rate,
replace,
picks_trace=None,
joint=False,
):
evoked = None
epochs = None
if isinstance(info, Info): # no-op
pass
elif isinstance(getattr(info, "info", None), Info): # try to get the file name
if isinstance(info, Evoked):
evoked = info
if isinstance(info, BaseEpochs):
epochs = info
info = info.info
else: # read from a file
info = read_info(info, verbose=False)
Expand All @@ -3645,6 +3684,21 @@ def _add_projs(
elif not isinstance(projs, list):
projs = read_proj(projs)

rate_caption = None

if add_rate is False or epochs is None:
pass
elif add_rate is True:
rate_caption = self._event_estimate(epochs, projs)
Comment thread
Master-Zero1 marked this conversation as resolved.
Outdated
elif add_rate == "auto":
if any(
any(kw in p["desc"].lower() for kw in ("ecg", "eog", "blink"))
for p in projs
):
rate_caption = self._event_estimate(epochs, projs)
else:
Comment thread
Master-Zero1 marked this conversation as resolved.
Outdated
rate_caption = None

if not projs:
raise ValueError("No SSP projectors found")

Expand Down Expand Up @@ -3682,7 +3736,7 @@ def _add_projs(
self._add_figure(
fig=fig,
title=title,
caption=None,
caption=rate_caption,
image_format=image_format,
tags=tags,
section=section,
Expand Down
Loading