Processing Pipelines: From ECG to HRV in Python
A few years ago, I built an open-source Python toolbox for Heart Rate Variability (HRV) as part of my master’s thesis.
HRV toolboxes already existed, so I wasn’t really reinventing the field. But what frustrated me was how hard the available tools were to understand, especially when you're just starting in the field. Not in terms of usability, but when looking into the code to learn how these algorithms were built.
The issue here was often the same:
They were built by experts for experts, but not really for beginners.
Now, keep in mind that beginners are not only students in their first semesters but also experienced engineers and physiologists entering the biomedical field.
I, like many others, faced code built for efficiency rather than understandability. The key parameters weren't explained, the processing pipelines looked cryptic, and I ended up frustrated and stuck trying to understand what was happening under the hood.
And so I built pyHRV with a clear goal...
Not to make something more powerful, but more accessible, easier to follow, and easier to understand. Looking back, that was probably my first attempt at simplifying biosignal processing.
And it seems to resonate with researchers, as it's been starred over 300 times on GitHub (https://github.com/PGomes92/pyhrv) and cited in over 200 research publications (yes, there's a bit of bragging going on here ;) ).
Fast forward to today, and not much has changed, but I'm lucky to bring this mindset to the biosignals research community on a larger scale as a product leader at PLUX Biosignals .
Enough humble bragging. Here's a new series
The goal of this (and the coming) newsletter edition is to share valuable tools that help you build a full signal processing pipeline in Python in just a few lines of code.
And today, we'll start with ECG & Python!
From raw ECG to a full set of HRV features
To keep this example practical, I’m using ECG data acquired with a PLUX ECG sensor, but the same pipeline is not limited to PLUX data.
You can use it with other ECG sources as well (e.g., cardioBAN BLE) or other systems that export usable ECG recordings.
The pipeline is simple:
import numpy as np
from biosppy.signals import ecg
from pyhrv import hrv
# Load your ECG signal from a .txt file
myECG = np.loadtxt('myECG.txt')[:, -1]
fs = 1000 # Replace with your actual sampling rate in Hz
# Extract R-peaks
rpeaks = ecg.ecg(signal=myECG, sampling_rate=fs, show=False)[2]
# Compute HRV metrics
results = hrv(rpeaks=rpeaks, show=True)
Here's a more detailed breakdown step-by-step:
First, this pipeline loads the ECG signal, here from a PLUX ECG sample file like this one, where the last column contains the ECG signal. In addition, you need to set the sampling rate used during recording, as both peak detection and HRV computation depend on accurate timing information.
Next, we'll use the open-source BioSPPy toolbox to process the ECG and extract the R-peaks. These peaks are the backbone of HRV analysis because they define the beat-to-beat timing intervals used in all downstream metrics.
Finally, it passes those R-peaks into pyHRV, which computes the HRV metrics and displays the results (see the graphs below). That means the whole pipeline goes from raw ECG to HRV analysis in just a few lines, while still relying on a structured and reproducible workflow.
If you want to view all the results it generates, you can even export the results into a JSON file or PDF file. Here's an example of the JSON output:
{
"Comment": "None",
"Name": "myHRV.json",
"nn50": 194.0,
"nni_counter": 461.0,
"nni_diff_max": 160.0,
"nni_diff_mean": 48.8,
"nni_diff_min": 0.0,
"nni_max": 960.0,
"nni_mean": 862.8134490238612,
"nni_min": 683.0,
"pnn20": 71.52173913043478,
"pnn50": 42.173913043478265,
"rmssd": 60.80914547922083,
"sampen": 1.744691958370069,
"sd1": 42.998523516217034,
"sd2": 66.35653149624171,
"sd_ratio": 1.5432281406410415,
"sdann": 5.649052794932491,
"sdnn": 55.92440465594473,
"sdnn_index": 56.99615268820533,
"sdsd": 36.32051701761713,
"tinn": 218.75,
"tinn_m": 953.125,
"tinn_n": 734.375,
"tri_index": 14.40625
... and many many more :)
}
Choosing only the features you need
In the previous example, I used the general hrv() function to compute a complete HRV analysis in one step. That is useful when you want a broad overview of the signal.
But in practice, you do not always need everything at once: pyHRV provides 70+ parameters and lots of graphs.
Sometimes, you only want to focus on one part of the analysis, such as time-domain features, frequency-domain features, or nonlinear metrics. That is why pyHRV also lets you call these modules separately.
For example, if you only want time-domain HRV features, you can do this:
import pyhrv.time_domain as td
results = td.time_domain(rpeaks=rpeaks, show=True)
If you want to look at frequency-domain features instead, you can call those directly:
import pyhrv.frequency_domain as fd
results = fd.welch_psd(rpeaks=rpeaks, show=True)
And if your focus is on nonlinear analysis, that can also be done independently:
import pyhrv.nonlinear as nl
results = nl.nonlinear(rpeaks=rpeaks, show=True)
That makes the workflow easier to read, easier to adapt, and usually easier to explain later when you come back to the script.
🧩 Biosignals Studio: For when you don't want to code
pyHRV is powerful, but it requires you to code.
That’s where Biosignals Studio comes in with a one-click HRV analysis. It’s a visual software built for researchers using PLUX biosignals sensors, combining recording and processing in one place. Labs and teams that want to standardize their analysis workflows across engineers, students, and clinicians.
Want to see it in action and how it can help you? DM me or book a call with me here.
Or sign up for our free open beta program here (until May 2026).
We’re focusing on pulmonary signals at the moment but this is really helpful!
pyHRV is available directly on GitHub: https://github.com/PGomes92/pyhrv Feel free to drop by and star it, if you find it useful :)