Готово.
Разница с эталоном rppgHeartRate.js: порт брал только спектр и пик. Перенесено всё остальное: 5 секунд до первого показания, пересчёт раз в секунду с кешем, полосовой фильтр 0.7–3.5 Гц через спектр, выбор из трёх пиков с правилом гармоник ×2 (30% мощности) и ×1.5 (40%), порог уверенности 30/100 перед сглаживанием, ограничение сдвига параболы ±0.5, поправка уверенности √(пикселей/2500) — число проверенных пикселей идёт через Observation::with_skin_pixels.
Сверх эталона: вычитание тренда перед фильтром. Дополнение нулями превращало сигнал не у нуля в ступеньку, её звон неподвижное лицо выдавал за уверенный пульс; у эталона тот же изъян. Опробована и отброшена «проверка скачка» (показание дальше 20 уд/мин ждёт подтверждения): под сильным шумом она удерживала неверное первое значение, худшее отклонение выросло с 67 до 101.
Худшее отклонение до и после (синтетика с дрейфом света и рывками): SNR 2.4 — 1.1/1.0; SNR 1.2 — 72.7/1.0; SNR 1.0 — 113.4/1.0; SNR 0.7 — 116.8/18; вторая гармоника — 60.5/1.0.
Предел: при SNR 0.34 (шум в 7 раз больше, чем даёт усреднение по ~2900 пикселям) ошибается любой 10-секундный спектральный метод: из показаний с уверенностью 0.3–0.4 верны 41%, выше 0.7 — 87%.