Dossier
Reference environment NeuroKit2 0.2.13 · NumPy 2.5.0 · SciPy 1.18.0 · Python 3.14.3. Every value in this dossier is traceable to SHA-256-fingerprinted input and output files (appendix).
Summary for supervisors, ethics committees and procurement
CARLON HRV Research is an iPhone app that records beat-to-beat RR intervals from Polar H10 chest straps and Movesense Flash sensors and exports the original data for analysis in Kubios, Python, R or MATLAB. This dossier documents what we checked, how, and with which result — including the errors we found along the way.
For supervisors
- All 277 metric checks agree with independent references (NeuroKit2, SciPy, Task Force definitions) within predefined tolerances; most to machine precision.
- Artefact detection follows Lipponen & Tarvainen (2019), the method used in Kubios, and flags the same beats as NeuroKit2 in 18 of 18 series.
- Raw RR data are exported unmodified; the analysis of record stays in your software.
- Re-verified on build 1.4 (2026102801): app output byte-identical to the certified run ✓.
For ethics committees
- No account and no server: recordings stay on the researcher's iPhone; vitalkultur receives no study data.
- Participants are managed under pseudonymous IDs; exports can replace participant codes with a stable pseudonym (pseudonymised, not anonymised).
- Every record carries an append-only audit trail (ALCOA+) secured by a SHA-256 hash chain, and every complete export a W3C PROV record of where its data came from.
- Not a medical device; for research and wellness use.
For procurement
- One-time purchase on the App Store; no subscription, no per-participant fees.
- Institutions can buy licences in volume through Apple School Manager or Apple Business Manager (research institutes, clinics) and assign them to devices or users.
- Tested with Polar H10 and Movesense Flash; iPhone with iOS 17 or later.
- One iPhone runs up to four sensors at a time and reads them all out; no extra hardware or software needed for recording.
A typical study workflow
Segment table: duration, beats, heart rate, RMSSD, SDNN and pNN50 for each phase between two events, computed with the same validated code as in this dossier. For a thesis we recommend reporting the values from your analysis software and using the table as a cross-check.
Methods text for a thesis
RR intervals were recorded with a Polar H10 chest strap using the CARLON HRV Research app (version 1.4; vitalkultur, Germany) and exported without filtering for analysis in Kubios HRV Scientific.
If you report values computed by the app itself, the PDF report contains a complete methods paragraph for that recording (classification, threshold floor, correction, metric definitions, software version and citation).
Results
How much margin is left
App against reference
All metrics
| Metric | Reference | Class | Tolerance | Passed | Max. deviation |
|---|---|---|---|---|---|
| Mean RR (ms) | Definition | I | ±0.01 | 18/18 | 9.8e-11 |
| Mean HR (bpm) | Definition | I | ±0.01 | 18/18 | 9.2e-12 |
| SDNN (ms) | NeuroKit2 | I | ±0.01 | 18/18 | 6.0e-12 |
| RMSSD (ms) | NeuroKit2 | I | ±0.01 | 18/18 | 1.1e-12 |
| pNN50 (%) | NeuroKit2 (÷ n−1) | I | ±0.01 | 18/18 | 5.6e-17 |
| SD1 (ms) | NeuroKit2 | I | ±0.01 | 17/17 (+1 n/a) | 7.7e-13 |
| SD2 (ms) | Brennan 2001 (NumPy) | I | ±0.01 | 18/18 | 3.9e-06 |
| SD2/SD1 | Brennan 2001 (NumPy) | I | ±0.01 | 17/17 (+1 n/a) | 5.5e-08 |
| Sample entropy | NeuroKit2 | II | ±0.02 | 15/15 (+3 n/a) | 4.4e-16 |
| DFA α1 | NeuroKit2 (scales 4–16) | II | ±0.02 | 17/17 (+1 n/a) | 4.3e-04 |
| HRV triangular index | Task Force 1996 (NumPy) | II | ±0.5 | 18/18 | 0.0e+00 |
| TINN (ms) | Task Force 1996 (NumPy) | II | ±15 | 17/17 (+1 n/a) | 0.0e+00 |
| LF power (ms²) | SciPy Welch | III | ≤ 0.1 rel. | 18/18 | 4.0e-07 |
| HF power (ms²) | SciPy Welch | III | ≤ 0.1 rel. | 18/18 | 4.9e-07 |
| LF/HF | SciPy Welch | III | ≤ 0.1 rel. | 15/15 (+3 n/a) | 9.9e-08 |
| √Baevsky SI | own reference (NumPy) | own | ≤ 0.01 rel. | 17/17 (+1 n/a) | 1.7e-16 |
Class I: exact definitions (±0.01). Class II: algorithms with discretisation or fitting (DFA, sample entropy, histogram indices). Class III: spectral estimates (≤ 10 % relative). For relative tolerances the deviation is relative. n/a: metric not defined for that series (e.g. sample entropy of a constant series).
What was checked
Artefact detection. The app classifies beats with the Lipponen & Tarvainen (2019) method and was compared beat by beat with NeuroKit2 signal_fixpeaks, including series with inserted missed, extra and ectopic beats.
Threshold floor. Lipponen & Tarvainen scale their thresholds with local variability; during hard exercise beat-to-beat variability shrinks to a few milliseconds and normal beats are flagged. The app therefore bounds both thresholds below at 30 ms. Chosen by the rule “the largest floor that loses no detection in any artefact class” (candidates and rule were set after a first exploration on an external gym recording — disclosed) in an injection study (8 base series × 6, NeuroKit2 0.2.13 as reference): no loss of sensitivity, false alarms 4.13 → 0.09 per 1000 beats. With the floor set to 0 the app flags exactly the beats NeuroKit2 flags (18 of 18 series); with 30 ms it matches NeuroKit2 carrying the same floor in 18 of 18.
Real ectopic beats (MIT-BIH). On the MIT-BIH Arrhythmia Database (44 recordings, every beat annotated by cardiologists; paced records excluded) the detection finds isolated ectopic beats in otherwise regular rhythm — ventricular 85 %, supraventricular 94 % — but not frequent ectopy: in runs with ≥ 5 % ectopic beats around them only 11 % (V) and 6 % (S). The threshold floor changes none of these numbers. Because HRV assumes sinus rhythm (Task Force 1996), the app adds a rhythm check: the share of beats with a premature pattern; from 5 % it shows a data-quality note. On recording level it flags 17 of 19 recordings outside sinus rhythm (≥ 5 % ectopy or atrial fibrillation) and 1 of 25 others. It is a data-quality note, not a rhythm diagnosis.
Movesense ECG → R peaks. For in-sensor Movesense recordings the app detects R peaks in the 256 Hz ECG itself. Against Pan–Tompkins (1985) on 4 real recordings (434 reference beats) it missed 0 and added 0; beats inside motion-disturbed segments, where the reference itself counts artefacts, were judged on the ECG trace and not scored. A beat whose shape is distorted by motion is kept when it falls on the expected beat (±20 % of the recent median interval).
One number everywhere. App screen, PDF report, CSV and both JSON profiles take every headline metric from the same certified analysis; an automated test reads each of them back and compares.
Metrics. Computed on the same artefact-corrected series and compared against an independent implementation with the same definition and parameters: NeuroKit2 where available, otherwise NumPy/SciPy following the original literature.
Movesense ECG → RR. For Movesense in-sensor recordings the app derives RR intervals from the raw ECG (256 Hz) with its own R-peak detector. Against a Polar H10 worn at the same time, all 166 H10 beats have a Movesense counterpart: bias +0.01 ms, SD 0.30 ms. The Movesense recording started 12 beats earlier and ran longer, which is why the two series have different beat counts.
Test series. Eleven synthetic vectors with known properties (constant, alternating, known SD, single and dual tones, white/pink/brown noise, an oracle difference series, time reversal and permutation) and 7 real recordings, including three Polar H10 straps worn simultaneously, a simultaneous Polar H10 / Movesense recording and a 90-min series (6,822 beats) whose last third has twice the variability.
Conventions and comparability with Kubios
- pNN50 divides NN50 by n−1 (differences); NeuroKit divides by n. The NN50 count is checked exactly.
- SD2 uses the Brennan form √(2·SDNN² − ½·SDSD²) as in Kubios.
- DFA α1 uses scales 4–16 beats (Peng 1995; as in the exercise studies of Rogers et al. 2021, doi:10.3389/fphys.2020.596567), non-overlapping windows. Kubios HRV Scientific 4.3 defaults to 4–12 beats (User’s Guide 2026) — set Kubios to 4–16 to compare values.
- Sample entropy: m = 2, r = 0.2 × SD of the series (as NeuroKit2's default). For recordings longer than 3000 beats it is computed on the most recent 3000 beats (quadratic cost); the reference is computed on the same span — the longest test series has 6,822 beats.
- Artefact correction (time-preserving): each run of consecutive flagged intervals is replaced by N equal intervals of the same total duration, N = the sum of whole multiples of the local median where every interval of the run is one (±15 %; missed beats, also when classified ectopic), otherwise run length + missed − extra beats as classified (so a fast heart-rate change neither gains nor loses beats); hence a missed beat is split, an extra beat merged, a premature beat with its compensatory pause averaged. Beats classified as normal are never changed. Only a single flagged interval without compensation is replaced by the mean of its neighbours (the one case that changes the duration; 0.48 s in a 48-min and 2.0 s in a 100-min exercise recording, while the time axis of the export stays the original one). Detection is identical to NeuroKit2; the correction step is the app's own and documented per beat in the export (value × count, correction type). All metrics use this series; the export keeps the uncorrected original.
- Triangular index / TINN use a bin width of 1/128 s anchored at the smallest RR.
- Spectrum: the whole recording (up to 24 h), natural cubic spline, 4 Hz, Welch with a 256-sample Hann window (64 s), 50 % overlap, Task Force bands integrated exactly at the band limits.
- Baevsky stress index: SI = AMo / (2 · Mo · MxDMn), with AMo in %, mode Mo and range MxDMn in s; reported as √SI. Bin width 50 ms for humans (as Kubios). From 10 min on, the median over 5-min windows on a fixed grid (300·k, 300·(k+1)] s by interval end (final window only from 240 s); no detrending.
- Stationarity: augmented Dickey–Fuller test with constant, lag order 12·(n/100)^¼, p-value after MacKinnon (1994); identical to statsmodels adfuller with the same lags (automated test).
Errors found and fixed during validation
A validation is only worth something if it can find errors. These were found and corrected before this dossier was issued:
- Frequency bands for humans. LF/HF for human recordings was computed with the frequency bands of a non-human preset of the shared signal-processing engine. Fixed with a dedicated human profile (Task Force 1996), covered by an automated test.
- Band integration. Spectral bins on a band edge were counted fully in both neighbouring bands (LF of a real 30-min recording +25 %). Fixed by exact integration at the band limits.
- Test expectation. Two older tests required time symmetry of the Lipponen–Tarvainen correction, which is directional (also in NeuroKit2). Replaced by index equality with NeuroKit2.
- Spectrum of long recordings. Resampling stopped after 5000 beats or 2048 s: LF/HF of every recording longer than 34 min came from its first 34 min. The reference had copied the same limits and therefore agreed. Fixed in app and reference; a 90-min test series whose last third has twice the variability now fails the old version (LF −44 %).
- Artefact flags during exercise. An external comparison (gym recording, 5,544 beats) showed 129 flagged beats against 29 corrections of another program; NeuroKit2 also flags 129. Most were normal beats at high heart rate. Fixed with the validated threshold floor above (129 → 55).
- JSON export. The JSON file recomputed its headline metrics itself (SD2 pair variant, sample entropy on the first instead of the last beats). It now carries the certified values; a test compares every field.
Independent external audit (26 Oct 2026)
An external auditor received a blind package — one synthetic recording with injected errors at undisclosed positions and three real recordings — and recomputed every number from the raw RR intervals. It located all injected errors and confirmed hash, audit chain, time axis, time-domain metrics, DFA α1 and segment tables exactly. It also found these defects, all fixed in build 1.4 (2026102801):
- Correction cascade. A first time-preserving correction (moving peaks as in NeuroKit2) also changed the normal beat after a premature beat and its pause (427 → 491 ms), which shifted DFA α1 windows. Replaced by the run-based correction above; a test pins the auditor's case (pair averaged, neighbour unchanged).
- Stationarity test. The ADF test returned t ≈ 0 for every recording (the matrix inverse was not formed), so every report said “not stationary”. Fixed; now identical to statsmodels.
- Provenance. Recordings without stored metadata were labelled live recordings at 1/1024 s. Every export now carries an origin derived from stored metadata and the RR values (live, strap memory, sensor memory, import, synthetic example or unknown), never assumed.
- Quality score. Recordings without a stored end time scored as 0 s long, and the metadata score was fixed at 100. Both now follow the data; the scales are documented in the export.
- Smaller items. Baevsky windows on a fixed grid; DFA α1 windows with a residual artefact or α1 < 0 marked invalid; window assignment on a millisecond axis; TINN and √SI in CSV and JSON; one confidence label per metric; rhythm-check denominator n − 10.
Disclosed post-hoc choices: the Baevsky tolerance (≤ 1 % rel.) was set after the first run, because the first reference implemented a different formula variant; spectral values below 0.01 are treated as zero (rounding noise for constant or alternating series), LF/HF is then not applicable.
Export formats
All formats carry the original RR intervals as recorded — no smoothing, no artefact correction. Each was read back with an independent reader and compared beat by beat with the source (real Polar H10 recording, 2,274 beats).
| Format | Content | Intended for | Independent check |
|---|---|---|---|
| CSV | Time stamp and RR per beat; header with start time and its source, device, discarded impossible values and the exact settings for Kubios' custom text import | Kubios, Python, R, MATLAB, Excel | Export tests on real recordings |
| TXT | One RR interval per line in ms, no header | Kubios RR import, any tool | Export tests |
| FIT | RR as hrv messages (global 78, 1 ms resolution), start time in file_id and record | Kubios (one of the two RR formats Kubios lists) | Garmin FIT SDK 21.218: integrity and CRC valid, 2,274/2,274 RR identical |
| EDF+ (EDF+C) | Every beat as an annotation at its R peak with the exact RR value; a 4 Hz RR trace (step-hold, not interpolated); event markers | EDFbrowser, MNE/Python, sleep-lab software | pyedflib (EDFlib): valid EDF+C, 2,274/2,274 beats exact, trace within 0.05 ms resolution |
| WFDB | Header (.hea) and annotations (.atr, MIT format): every R peak as NORMAL beat, events as notes; 1000 Hz for whole-ms data, 1024 Hz for H10 data (1/1024 s), so every beat lies exactly on the grid | PhysioNet WFDB toolbox (MATLAB), wfdb-python, NeuroKit2 | wfdb-python: 2,274/2,274 RR exact for both resolutions |
| Figures | Every figure of the PDF report as vector PDF and 300-dpi PNG, 85 mm (single column) and 175 mm (full width), drawn at final size; numbered captions as text; file names sort in figure order | Theses, papers, posters | Rendered and checked at both widths |
| JSON | RR, metrics, beat classification, audit trail, W3C PROV; a validation_scope field names the metrics covered by this dossier — all other fields are marked exploratory | Scripts, archives | Probe exports |
Research data handling
| Property | How it is implemented | How it was verified |
|---|---|---|
| Original data | RR intervals exported exactly as recorded, no smoothing or correction; impossible values discarded and counted in the export header. | Export tests on real recordings |
| Integrity | SHA-256 fingerprint of every RR series in every export. | Fingerprint identical in integrity block and PROV record |
| Audit trail (ALCOA+) | Append-only log per record (creation, changes, participant reassignment, deletion, export), each entry SHA-256-chained to the previous one. | Tests: edited, deleted and re-hashed entries all break the chain; chain recomputed independently in Python |
| Provenance | W3C PROV-JSON in every complete export: sensor and firmware, app version, recording period, derivation of the file. | Probe export inspected |
| Time | Start time and its source (live clock, strap trigger or estimate) stored with every recording, plus a derived origin (live, strap memory, sensor memory, import, synthetic example, unknown); for strap-memory recordings also the stop time, and the export header compares the clock span with the sum of RR intervals, so time without detected beats becomes visible. Events carry clock time. | Segment test: every beat in exactly one segment; roster test: start and stop survive the stop command |
| Privacy | No account, no server, no analytics; pseudonymous participant IDs (pseudonymised exports keep only the session ID and a pseudonymous device ID for linking files); recordings are part of the iPhone's regular backup; an optional study folder copies new recordings to a cloud location the researcher chooses. | Code review: no network calls to vitalkultur |
Scope and limits
This dossier verifies the computation: given the same RR series, the app produces the same metrics as independent reference implementations, and it records and exports data in a traceable way. It is not a clinical validation and does not assess the accuracy of the Polar H10 or Movesense sensors themselves; for that, see the published validation literature on these sensors. CARLON HRV Research is not a medical device.
Rhythm. HRV metrics assume sinus rhythm. Artefact correction removes sparse disturbances; frequent ectopy and atrial fibrillation are not corrected (see MIT-BIH above) — the rhythm check flags such recordings, and their HRV values should not be interpreted.
Appendix: reproducibility
| Item | Value |
|---|---|
| Certified run | 2026-10-27T12:51:41+01:00 |
| App commit / shared metric code | fc3f72c / f41c7559 |
| Inputs SHA-256 | 3d9e2d211248f1d27e5721e52161f7a38c280b86694d24c0e10f4cb72c825ae4 |
| App output SHA-256 (certified) | 096344bdaa5812c81b4ea7ed1265c81a2a7adddc61af4ff27ab89aa8a12bc530 |
| App output SHA-256 (build 1.4 (2026102801)) | 096344bdaa5812c81b4ea7ed1265c81a2a7adddc61af4ff27ab89aa8a12bc530 — identical |
| Series | Type | Beats | Artefacts = NeuroKit2 | Source SHA-256 |
|---|---|---|---|---|
| vector_L1_constant | synthetic test vector | 240 | ✓ (0) | bbe1e3164db8… |
| vector_L1_alternating | synthetic test vector | 240 | ✓ (0) | bbe1e3164db8… |
| vector_L1_known_sd | synthetic test vector | 300 | ✓ (0) | bbe1e3164db8… |
| vector_L2_one_tone_hf | synthetic test vector | 376 | ✓ (0) | bbe1e3164db8… |
| vector_L2_two_tone_lf_hf | synthetic test vector | 377 | ✓ (0) | bbe1e3164db8… |
| vector_L3_white_noise | synthetic test vector | 900 | ✓ (23) | bbe1e3164db8… |
| vector_L3_pink_1f | synthetic test vector | 900 | ✓ (14) | bbe1e3164db8… |
| vector_L3_brown_noise | synthetic test vector | 900 | ✓ (3) | bbe1e3164db8… |
| vector_L5_oracle_diff | synthetic test vector | 500 | ✓ (7) | bbe1e3164db8… |
| vector_L6_time_reversal | synthetic test vector | 400 | ✓ (5) | bbe1e3164db8… |
| vector_L6_permutation | synthetic test vector | 400 | ✓ (5) | bbe1e3164db8… |
| h10_30min_strap1 | real · Polar H10 · ~30 min, 3 straps simultaneously | 2274 | ✓ (3) | 6f5b924576f5… |
| h10_30min_strap2 | real · Polar H10 · ~30 min, 3 straps simultaneously | 2274 | ✓ (13) | 6f5b924576f5… |
| h10_30min_strap3 | real · Polar H10 · ~30 min, 3 straps simultaneously | 2274 | ✓ (3) | 6f5b924576f5… |
| h10_alongside_movesense | real · Polar H10, simultaneously with Movesense | 166 | ✓ (0) | 465f30b63225… |
| movesense_internal_ecg | real · Movesense internal ECG 256 Hz → RR (app R-peak detector) | 188 | ✓ (0) | da1f606e7745… |
| h10_with_inserted_artefacts | real · H10 + inserted: missed@150, extra@300, ectopic@500 | 900 | ✓ (7) | 6f5b924576f5… |
| h10_90min_aneinander | real · drei 30-min-H10-Reihen hintereinander (≈ 90 min), letztes Drittel mit doppelter Schwankung | 6822 | ✓ (19) | 6f5b924576f5… |
Series names are shown in English; the machine-readable file keeps the original identifiers.
Changes in dossier 1.4
Found in a field recording by a study partner and fixed: at abrupt heart-rate changes the run correction could add beats (in a 100-min exercise recording 9 beats that did not exist, e.g. 533–801 ms counted as 8 instead of 6 intervals); the number of intervals per run is now set by whole multiples of the local rhythm (missed beats) or else by the classification, with tests built from these sequences; a second field recording (contact loss at the end of a gym session, double intervals classified ectopic) showed why both rules are needed · the DFA α1 time course is now certified window by window (2,854 windows in 18 series against Tarvainen 2002 detrending and NeuroKit2 DFA, largest deviation 7e-11) – NeuroKit2's own tarvainen2002 detrending was found faulty in version 0.2.13 (its second-difference matrix is incomplete) and is not used as reference · the 5 % window limit for DFA α1 is the app’s own conservative rule, informed by Rogers et al. 2021 (Sensors, doi:10.3390/s21030821: small bias at 1–3 %, proportional bias at 6 % missed beats); Schaffarczyk et al. 2022 excluded whole recordings above 5 % artefact · λ = 500 stated as the Kubios default · re-verified on build 1.4 (2026102801).
Changes in dossier 1.3
Time-preserving run-based artefact correction (normal beats never changed) · independent external audit with blind package and its findings (correction cascade, ADF test, provenance, quality score) fixed · ADF identical to statsmodels · origin field in every export · Baevsky windows on a fixed grid, 50 ms bins for humans · figure bundle for publications · up to four sensors at a time · re-verified on build 1.4 (2026102801).
Changes in dossier 1.2
Product name CARLON HRV Research · export formats FIT, EDF+ and WFDB added and independently verified · found by a beta tester and fixed: after a strap-memory recording was stopped, the app discarded the stored start time and later estimated it as sync time minus the sum of RR intervals (in the reported case 10 min late; no data lost); start and stop time are now kept and the clock span is written to the export · a drawing error in the tachogram for recordings above 3,000 beats (curve compressed to half the time axis; data unaffected) fixed · unsourced statements removed from app and exports: artefact grades of 2 %/5 %, age/sex reference percentiles, and interpretive summaries in the complete JSON · re-verified on build 1.4 (2026102801).
Changes in dossier 1.1
Units added to the metric table · sample entropy parameters, artefact interpolation and the Baevsky formula stated · Movesense ECG → RR agreement with the simultaneous Polar H10 reported · Apple Business Manager added · series names in English · re-verified on build 1.4 (2026102801).
A machine-readable file with every single check (app value, reference, deviation, tolerance) is available on request.
References
Task Force of the ESC and NASPE (1996). Circulation 93(5):1043–1065. PMID 8598068 · Lipponen JA, Tarvainen MP (2019). J Med Eng Technol 43(3):173–181. doi:10.1080/03091902.2019.1640306 · Brennan M, Palaniswami M, Kamen P (2001). IEEE Trans Biomed Eng 48(11):1342–1347. doi:10.1109/10.959330 · Peng C-K, Havlin S, Stanley HE, Goldberger AL (1995). Chaos 5:82–87. doi:10.1063/1.166141 · Makowski D et al. (2021). NeuroKit2. Behav Res Methods 53(4):1689–1696. doi:10.3758/s13428-020-01516-y · Baevsky RM, Berseneva AP (2008), Kardivar recommendations — original not consulted; formula after secondary sources.