feat: iterate on SsfStepDetector

* use SSF signal instead of accelerometer signal
* use higher BEAT_CORR_THR_{12} for SSF signal
* add absolute SSF_THRESHOLD to ignore small accelero bumps
* compute ssf_threshold according to detected SSF peaks, not the mean (more robust vs. noise)
This commit is contained in:
2026-03-11 20:47:53 +01:00
parent 95d1fee44d
commit 90f8943930
8 changed files with 103 additions and 27 deletions

View File

@@ -3,6 +3,7 @@
//
#include <gtest/gtest.h>
//#include <utility>
#include <deque>
#include "pd_signal.h"
using namespace pd_signal;
@@ -42,29 +43,42 @@ TEST(SignalTest, ranges) {
*/
class RunningQuality {
protected:
// TODO: make it a filter (output proper samples)
/** template beat is resampled to this #samples */
const int BEAT_LEN = 120 /* 2*FPS for 30 bpm lower end */;
/** threshold for accepting initial beats */
const double BEAT_CORR_THR_1 = 0.8;
const double BEAT_CORR_THR_1 = 0.9;
/** threshold for accepting subsequent beats */
const double BEAT_CORR_THR_2 = 0.6;
const double BEAT_CORR_THR_2 = 0.8;
/** absolute SSF threshold for accepting any beat */
const double SSF_THRESHOLD = 5.0;
/** number of recent beats to use for beat template. must be even (alternating feet have different patterns; make it symmetric) */
const int NUM_BEATS = 4;
std::vector<std::vector<double> > beatTemplates;
std::deque<std::vector<double> > beatTemplates;
std::vector<double> beatTemplate;
//std::vector<std::pair<int, int> > badBeatRanges;
double beatCorrThr2;
bool justLocked;
int idx;
/** for debugging only - disable SSF_THRESHOLD */
bool disableSsf;
void addTemplate(std::vector<double>& x) {
beatTemplates.push_back(x);
beatTemplates.emplace_back(x);
while (beatTemplates.size() > NUM_BEATS) {
// sliding window on 'beat_templates', do not use all history
beatTemplates.pop_front();
}
pd_signal::mean(beatTemplate, beatTemplates);
}
void replaceTemplate(std::vector<double>& x) {
beatTemplates.clear();
beatTemplates.push_back(x);
beatTemplates.emplace_back(x);
// essentially just a copy
pd_signal::mean(beatTemplate, beatTemplates);
}
@@ -73,28 +87,35 @@ protected:
virtual void dispatchBeat(int idx, bool good, double posCorr) { /* implement me, add Listener etc. */ }
public:
RunningQuality(): beatCorrThr2(BEAT_CORR_THR_2), justLocked(false), idx(0) {}
RunningQuality(): beatCorrThr2(BEAT_CORR_THR_2), justLocked(false), idx(0), disableSsf(false) {}
explicit RunningQuality(bool disableSsf): beatCorrThr2(BEAT_CORR_THR_2), justLocked(false), idx(0), disableSsf(disableSsf) {}
virtual ~RunningQuality() {}
// note: arg should be an iterator really, but can do later
/**
* @param beat individual beat accelero signal
*/
void append(std::vector<double> &rawBeat) {
void append(std::vector<double> &rawBeat, std::vector<double> &rawSsf) {
// TODO: should ignore crazy-long and very short beats here. (filter up on beat detector)
std::vector<double> beat;
std::vector<double> ssf;
resample(beat, rawBeat, BEAT_LEN);
resample(ssf, rawSsf, BEAT_LEN);
//std::ranges::copy(rawBeat, std::back_inserter(beat));
// check ssf at sample 2 (mid-slope of 4 window of ssf)
// TODO: param upon SsfFilter.upslope_width/2 instead of hardcoding
double checkedSsf = ssf[(int) (2*((double)beat.size())/((double)rawBeat.size()))];
double corr = std::numeric_limits<double>::quiet_NaN();
double posCorr = std::numeric_limits<double>::quiet_NaN();
bool goodBeat = false;
if (beatTemplates.size() > 0) {
corr = pd_signal::crossCorr(beat, beatTemplate);
corr = pd_signal::crossCorr(ssf, beatTemplate);
posCorr = pd_signal::clip(corr, 0.0, 1.0);
double corrThreshold = (beatTemplates.size() > 2) ? beatCorrThr2 : BEAT_CORR_THR_1;
goodBeat = (corr > corrThreshold);
goodBeat = (corr > corrThreshold) && (checkedSsf > SSF_THRESHOLD || disableSsf);
}
if (beatTemplates.size() == 0) {
@@ -144,6 +165,7 @@ protected:
public:
DebugRunningQuality(): locked(false) {}
explicit DebugRunningQuality(bool disableSsf): RunningQuality(disableSsf), locked(false) {}
virtual ~DebugRunningQuality() {}
bool isLocked() { return locked; }
std::vector<double> getCorrs() { return corrs; }
@@ -172,16 +194,16 @@ TEST(SignalTest, resample_same_len) {
*/
TEST(SignalTest, RunningQuality_t1) {
DebugRunningQuality sqi;
DebugRunningQuality sqi(true);
std::vector a {0.0, 0.3, 0.9, 1.0, 0.7, 0.5, 0.1};
std::vector b {0.0, 0.3, 0.9, 1.0, 0.5, 0.5, 0.1};
std::vector c {0.0, 0.3, 0.9, 1.0, 0.9, 0.5, 0.1};
std::vector d {0.0, 0.3, 0.9, 1.0, 0.7, 0.4, 0.1};
sqi.append(a);
sqi.append(b);
sqi.append(c);
sqi.append(a, a);
sqi.append(b, b);
sqi.append(c, c);
EXPECT_FALSE(sqi.isLocked());
sqi.append(d);
sqi.append(d, d);
EXPECT_TRUE(sqi.isLocked());
ASSERT_EQ(1, sqi.getCorrs().size());
double norm = sqrt((0.3*0.3 + 0.9*0.9 + 1.0 + 0.7*0.7 + 0.5*0.5 + 0.1*0.1) // \sum x_i^2