You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
include estimated heartrate in the ECG projectors section
in the BEMsensor alignment section, make the helmet position fixed and rotate the subject head, instead of the other way around (more reflective of reality) (already exists for Report.add_trans)
Add a module-level _calculate_estimated_heartrate(events, sfreq) helper that computes BPM from R-peaks using median R-R interval, with physiological range filtering (0.3–2.0s) before computing
In _add_projs(), capture the raw instance before it gets stripped to an Info object, detect ECG projectors by checking proj['desc'], call find_ecg_events() if ECG projectors are present, and pass the result as the figure caption
Wrapping detection in try/except so it degrades gracefully if no ECG channel exists
I have prior experience building an ECG denoising pipeline with R-peak detection and BPM estimation on raw noisy signals, so I'm comfortable with the edge cases here.
Could I be assigned on this? Happy to open a draft PR.
Hmmm that sounds a bit complex. Typically when someone adds ECG projectors to a report, they have already done the ECG event detection step. We don't want to re-run it.
We currently allow info : mne.Info | mne.Evoked. If we were to change this to also allow mne.Epochs -- like the kind you'd get from mne.create_ecg_epochs (or created manually after using something like mne.find_ecg_events, we could use the min and max times of the epochs instance, plus the len(epochs.drop_log) and epochs.info["sfreq"] to come up with a decent (not exact but presumably close enough) estimate of the detected heart rate, conditional on the projector name containing something about heart rate.
Incidentally, I also use something like this for blinks, and the same process could apply. So we probably want something like both support for supplying mne.Epochs, but also a add_rate="auto" | bool where True means "estimate and add the event rate", False means don't add it, and "auto" means "True if the projector supplied are blink or ecg projectors, according to standard naming"
Thanks for the guidance, that makes much more sense! Re-running event detection is unnecessary if epochs are already available. I'll rework the approach accordingly.
Updated plan:
Accept mne.Epochs as inst in add_projs
Use len(epochs.drop_log) and epochs.info["sfreq"] along with the min/max of epoch times to estimate the rate
Add an add_rate="auto" | True | False parameter, where "auto" checks the projector name for ECG/EOG and only shows the rate if relevant
Apply the same logic for blinks (EOG) as you mentioned
I'll open a draft PR shortly — happy to get early feedback on the implementation before finalising.
Hi @larsoner, I have a working implementation - Epochs support and add_rate parameter are in place and the report generates successfully.
One thing I noticed while testing: when projectors are calculated via compute_proj_epochs the descriptions are named for the channel type and time window (e.g. planar-999--0.499-0.499-PCA-01) instead of having 'ecg' or 'eog' in them. So checking projector descriptions for those keywords doesn’t work reliably for the 'auto' mode.
Are you looking for another field in the projector dict for the 'auto' detection, or did you have some other way you wanted to detect ECG/EOG projectors?
One thing I noticed while testing: when projectors are calculated via compute_proj_epochs the descriptions are named for the channel type and time window (e.g. planar-999--0.499-0.499-PCA-01) instead of having 'ecg' or 'eog' in them.
Yes when using compute_proj_epochs you have to add an appropriate desc_prefix. If it's ECG or EOG, the user should ideally put that in there. (If we don't do that in a tutorial, we should fix it!) The projectors computed by compute_proj_ecg / compute_proj_eog will have it in there. So I thnik it's okay for "auto" only to work if this has been set properly.
Activity
Master-Zero1 commentedon May 28, 2026
Hi!!
I would like to work on this
My approach:
_calculate_estimated_heartrate(events, sfreq)helper that computes BPM from R-peaks using median R-R interval, with physiological range filtering (0.3–2.0s) before computing_add_projs(), capture the raw instance before it gets stripped to anInfoobject, detect ECG projectors by checkingproj['desc'], callfind_ecg_events()if ECG projectors are present, and pass the result as the figure captionI have prior experience building an ECG denoising pipeline with R-peak detection and BPM estimation on raw noisy signals, so I'm comfortable with the edge cases here.
Could I be assigned on this? Happy to open a draft PR.
larsoner commentedon Jun 1, 2026
Hmmm that sounds a bit complex. Typically when someone adds ECG projectors to a report, they have already done the ECG event detection step. We don't want to re-run it.
Instead, looking at
https://mne.tools/stable/generated/mne.Report.html#mne.Report.add_projs
We currently allow
info : mne.Info | mne.Evoked. If we were to change this to also allowmne.Epochs-- like the kind you'd get frommne.create_ecg_epochs(or created manually after using something likemne.find_ecg_events, we could use the min and max times of theepochsinstance, plus thelen(epochs.drop_log)andepochs.info["sfreq"]to come up with a decent (not exact but presumably close enough) estimate of the detected heart rate, conditional on the projector name containing something about heart rate.Incidentally, I also use something like this for blinks, and the same process could apply. So we probably want something like both support for supplying
mne.Epochs, but also aadd_rate="auto" | boolwhere True means "estimate and add the event rate", False means don't add it, and "auto" means "True if the projector supplied are blink or ecg projectors, according to standard naming"Master-Zero1 commentedon Jun 4, 2026
Sorry for the late reply .
Thanks for the guidance, that makes much more sense! Re-running event detection is unnecessary if epochs are already available. I'll rework the approach accordingly.
Updated plan:
mne.Epochsasinstinadd_projslen(epochs.drop_log)andepochs.info["sfreq"]along with the min/max of epoch times to estimate the rateadd_rate="auto" | True | Falseparameter, where"auto"checks the projector name for ECG/EOG and only shows the rate if relevantI'll open a draft PR shortly — happy to get early feedback on the implementation before finalising.
Master-Zero1 commentedon Jun 5, 2026
Hi @larsoner, I have a working implementation - Epochs support and add_rate parameter are in place and the report generates successfully.
One thing I noticed while testing: when projectors are calculated via
compute_proj_epochsthe descriptions are named for the channel type and time window (e.g.planar-999--0.499-0.499-PCA-01) instead of having'ecg'or'eog'in them. So checking projector descriptions for those keywords doesn’t work reliably for the 'auto' mode.Are you looking for another field in the projector dict for the 'auto' detection, or did you have some other way you wanted to detect ECG/EOG projectors?
larsoner commentedon Jun 5, 2026
Yes when using
compute_proj_epochsyou have to add an appropriatedesc_prefix. If it's ECG or EOG, the user should ideally put that in there. (If we don't do that in a tutorial, we should fix it!) The projectors computed bycompute_proj_ecg/compute_proj_eogwill have it in there. So I thnik it's okay for"auto"only to work if this has been set properly.