Guide the enrolment, and take the sample total from the config
Two problems from a real attempt, one mine and one the tool failing to explain itself. A sample is taken on the RISING edge only. Holding the finger down produces no further touch events however long it stays there, so a run with the finger almost permanently down collects one sample: 55 finger frames across 60, three touch events, two samples accepted. The loop now says which state it is in on every line -- press, hold, or LIFT -- shows accepted-of-total as it goes, and calls out a finger that has been held for several frames, because that is the state where nothing is happening and nothing on screen said so. And the total is now read from the config instead of inferred. `rem` is reported after the sample is processed, so the first reading of a healthy enrolment is already 9, and a session that takes the first reading as its total is permanently off by one -- it reported "1 of 9 accepted" when two samples had been accepted out of ten. common.max_enrolling_samples is stated explicitly in the generated config so both sides agree on the number rather than one of them guessing. Also recorded: not every press is accepted. The third touch of that run reported the same count as the second, which is the algorithm rejecting a sample, and is normal.
This commit is contained in:
parent
c785aad653
commit
bdd5de21b3
3 changed files with 48 additions and 18 deletions
|
|
@ -57,6 +57,7 @@ bool g_auth = false;
|
||||||
bool g_enrol = false;
|
bool g_enrol = false;
|
||||||
int g_frames = 40;
|
int g_frames = 40;
|
||||||
int g_frameGapMs = 500;
|
int g_frameGapMs = 500;
|
||||||
|
int g_samples = 10; // common.max_enrolling_samples, as shipped
|
||||||
std::string g_logDir = "/var/log/fingerprintd";
|
std::string g_logDir = "/var/log/fingerprintd";
|
||||||
int g_rescan = -1; // -1 = leave the config's value alone
|
int g_rescan = -1; // -1 = leave the config's value alone
|
||||||
|
|
||||||
|
|
@ -1245,7 +1246,8 @@ int Probe() {
|
||||||
// every held frame instead gives the algorithm near-duplicate images
|
// every held frame instead gives the algorithm near-duplicate images
|
||||||
// from a single press.
|
// from a single press.
|
||||||
en::TouchTracker tracker;
|
en::TouchTracker tracker;
|
||||||
en::EnrolSession enrol;
|
en::EnrolSession enrol(g_samples);
|
||||||
|
int heldFrames = 0;
|
||||||
for (int i = 0; i < g_frames && !enrol.Complete(); i++) {
|
for (int i = 0; i < g_frames && !enrol.Complete(); i++) {
|
||||||
std::vector<std::byte> q(0x10, std::byte{0});
|
std::vector<std::byte> q(0x10, std::byte{0});
|
||||||
SendCommand(app, ta::Cmd::QueryEventStatus, q);
|
SendCommand(app, ta::Cmd::QueryEventStatus, q);
|
||||||
|
|
@ -1255,6 +1257,13 @@ int Probe() {
|
||||||
auto c = SendCommand(app, ta::Cmd::CaptureImage, cap);
|
auto c = SendCommand(app, ta::Cmd::CaptureImage, cap);
|
||||||
bool finger = baseline.IsFinger(c.metric);
|
bool finger = baseline.IsFinger(c.metric);
|
||||||
|
|
||||||
|
// A sample is taken on the RISING edge only. Holding the finger
|
||||||
|
// down produces no further touch events however long it stays, so
|
||||||
|
// a run where the finger is never lifted collects exactly one
|
||||||
|
// sample -- which is what a first attempt at this did, 55 finger
|
||||||
|
// frames and three touches.
|
||||||
|
heldFrames = finger ? heldFrames + 1 : 0;
|
||||||
|
|
||||||
std::string note;
|
std::string note;
|
||||||
for (ta::Event ev : tracker.Observe(finger, en::Mode::Enrol)) {
|
for (ta::Event ev : tracker.Observe(finger, en::Mode::Enrol)) {
|
||||||
std::vector<std::byte> evbuf(ta::EventContextSize);
|
std::vector<std::byte> evbuf(ta::EventContextSize);
|
||||||
|
|
@ -1273,8 +1282,15 @@ int Probe() {
|
||||||
}
|
}
|
||||||
SendCommand(app, ta::Cmd::QueryEventStatus, q);
|
SendCommand(app, ta::Cmd::QueryEventStatus, q);
|
||||||
|
|
||||||
std::println(" frame {:2}: metric={:<4}{}{}", i + 1, c.metric,
|
std::println(" [{:2}/{}] {:<28} metric={:<4}{}{}",
|
||||||
finger ? " FINGER" : " ", note);
|
enrol.Accepted(), enrol.Total(),
|
||||||
|
enrol.Started()
|
||||||
|
? (finger ? "hold... then LIFT" : "LIFT -- now press again")
|
||||||
|
: "press your finger",
|
||||||
|
c.metric, finger ? " FINGER" : " ", note);
|
||||||
|
if (heldFrames == 4)
|
||||||
|
std::println(" *** still held -- LIFT the finger, a sample is only "
|
||||||
|
"taken when you press again ***");
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs));
|
std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1332,6 +1348,7 @@ int main(int argc, char** argv) {
|
||||||
if (a.starts_with("--log-dir=")) g_logDir = a.substr(10);
|
if (a.starts_with("--log-dir=")) g_logDir = a.substr(10);
|
||||||
if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));
|
if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));
|
||||||
if (a.starts_with("--group-path=")) g_groupPath = a.substr(13);
|
if (a.starts_with("--group-path=")) g_groupPath = a.substr(13);
|
||||||
|
if (a.starts_with("--samples=")) g_samples = std::stoi(std::string(a.substr(10)));
|
||||||
if (a.starts_with("--sfs-root=")) g_sfsRoot = a.substr(11);
|
if (a.starts_with("--sfs-root=")) g_sfsRoot = a.substr(11);
|
||||||
if (a.starts_with("--gid=")) g_gid = static_cast<std::uint32_t>(
|
if (a.starts_with("--gid=")) g_gid = static_cast<std::uint32_t>(
|
||||||
std::stoul(std::string(a.substr(6))));
|
std::stoul(std::string(a.substr(6))));
|
||||||
|
|
|
||||||
|
|
@ -132,14 +132,22 @@ export namespace fingerprintd::engine {
|
||||||
// the value.
|
// the value.
|
||||||
class EnrolSession {
|
class EnrolSession {
|
||||||
public:
|
public:
|
||||||
|
// The total is common.max_enrolling_samples and is KNOWN from the
|
||||||
|
// config, not inferred. Inferring it from the first reading is off by
|
||||||
|
// one: `rem` is reported after the sample has been processed, so the
|
||||||
|
// first observation of a healthy enrolment is already 9, not 10, and a
|
||||||
|
// session that takes 9 as the total reports one fewer accepted sample
|
||||||
|
// than actually happened.
|
||||||
|
explicit EnrolSession(std::int32_t total) : total_(total) {}
|
||||||
|
|
||||||
void Observe(std::int32_t remaining, bool fromEnrolPath) {
|
void Observe(std::int32_t remaining, bool fromEnrolPath) {
|
||||||
if (!fromEnrolPath) return; // a release reports nothing
|
if (!fromEnrolPath) return; // a release reports nothing
|
||||||
if (remaining < 0) return; // not populated at all
|
if (remaining < 0) return; // not populated at all
|
||||||
|
if (remaining > total_) return; // nonsense
|
||||||
if (!started_) {
|
if (!started_) {
|
||||||
// A first reading of 0 is an unpopulated field, not a finished
|
// A first reading of 0 is an unpopulated field, not a finished
|
||||||
// enrolment: the count starts at max_enrolling_samples.
|
// enrolment: the count starts at the total.
|
||||||
if (remaining == 0) return;
|
if (remaining == 0) return;
|
||||||
total_ = remaining;
|
|
||||||
remaining_ = remaining;
|
remaining_ = remaining;
|
||||||
started_ = true;
|
started_ = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -161,6 +169,8 @@ export namespace fingerprintd::engine {
|
||||||
bool started_ = false;
|
bool started_ = false;
|
||||||
std::int32_t total_ = 0;
|
std::int32_t total_ = 0;
|
||||||
std::int32_t remaining_ = -1;
|
std::int32_t remaining_ = -1;
|
||||||
|
// Set once the total is known; see the constructor.
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Authentication accounting ----------------------------------------
|
// ---- Authentication accounting ----------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -138,16 +138,18 @@ int main() {
|
||||||
|
|
||||||
// ---- Enrolment progress, read from the response
|
// ---- Enrolment progress, read from the response
|
||||||
{
|
{
|
||||||
EnrolSession e;
|
EnrolSession e(10);
|
||||||
Check(!e.Started(), "not started");
|
Check(!e.Started(), "not started");
|
||||||
e.Observe(-1, true);
|
e.Observe(-1, true);
|
||||||
Check(!e.Started(), "an unpopulated field does not start the session");
|
Check(!e.Started(), "an unpopulated field does not start the session");
|
||||||
e.Observe(10, true);
|
Check(e.Total() == 10 && e.Stages() == 10, "the total comes from the config");
|
||||||
Check(e.Started() && e.Total() == 10 && e.Stages() == 10, "first response sets the total");
|
|
||||||
Check(e.Accepted() == 0 && !e.Complete(), "nothing accepted yet");
|
|
||||||
e.Observe(9, true);
|
e.Observe(9, true);
|
||||||
Check(e.Accepted() == 1, "rem 10 -> 9 is one accepted sample");
|
// rem is reported AFTER the sample is processed, so the first reading
|
||||||
|
// of a healthy enrolment is already 9 and one sample is in.
|
||||||
|
Check(e.Started() && e.Accepted() == 1, "a first reading of 9 means one accepted");
|
||||||
|
Check(!e.Complete(), "not complete");
|
||||||
for (std::int32_t r : {8, 7, 6, 5, 4, 3, 2, 1}) e.Observe(r, true);
|
for (std::int32_t r : {8, 7, 6, 5, 4, 3, 2, 1}) e.Observe(r, true);
|
||||||
|
Check(e.Accepted() == 9, "nine accepted at one remaining");
|
||||||
Check(!e.Complete() && e.Remaining() == 1, "not complete at one remaining");
|
Check(!e.Complete() && e.Remaining() == 1, "not complete at one remaining");
|
||||||
e.Observe(0, true);
|
e.Observe(0, true);
|
||||||
Check(e.Complete() && e.Accepted() == 10, "complete at zero");
|
Check(e.Complete() && e.Accepted() == 10, "complete at zero");
|
||||||
|
|
@ -160,33 +162,34 @@ int main() {
|
||||||
// itself finished after ONE press. SAVE_DATA is then called on an
|
// itself finished after ONE press. SAVE_DATA is then called on an
|
||||||
// algorithm holding no template and answers -1.
|
// algorithm holding no template and answers -1.
|
||||||
{
|
{
|
||||||
EnrolSession e;
|
EnrolSession e(10);
|
||||||
e.Observe(10, true); // touch: ten to go
|
e.Observe(9, true); // touch: one accepted, nine to go
|
||||||
e.Observe(0, false); // release: reads zero, means nothing
|
e.Observe(0, false); // release: reads zero, means nothing
|
||||||
Check(!e.Complete(), "a release reading of 0 does NOT complete the enrolment");
|
Check(!e.Complete(), "a release reading of 0 does NOT complete the enrolment");
|
||||||
Check(e.Remaining() == 10, "and does not move the count");
|
Check(e.Remaining() == 9, "and does not move the count");
|
||||||
Check(e.Accepted() == 0, "nothing was accepted");
|
Check(e.Accepted() == 1, "the touch's one sample still stands");
|
||||||
}
|
}
|
||||||
|
|
||||||
// A first reading of 0 is an unpopulated field, not a finished enrolment.
|
// A first reading of 0 is an unpopulated field, not a finished enrolment.
|
||||||
{
|
{
|
||||||
EnrolSession e;
|
EnrolSession e(10);
|
||||||
e.Observe(0, true);
|
e.Observe(0, true);
|
||||||
Check(!e.Started(), "a leading zero does not start a session");
|
Check(!e.Started(), "a leading zero does not start a session");
|
||||||
Check(!e.Complete(), "and certainly does not finish one");
|
Check(!e.Complete(), "and certainly does not finish one");
|
||||||
e.Observe(10, true);
|
e.Observe(9, true);
|
||||||
Check(e.Started() && e.Total() == 10, "a real count still starts it");
|
Check(e.Started() && e.Total() == 10, "a real count still starts it");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The count only falls; a jump back up is noise, not progress.
|
// The count only falls; a jump back up is noise, not progress.
|
||||||
{
|
{
|
||||||
EnrolSession e;
|
EnrolSession e(10);
|
||||||
e.Observe(10, true);
|
|
||||||
e.Observe(9, true);
|
e.Observe(9, true);
|
||||||
|
e.Observe(8, true);
|
||||||
e.Observe(7, true);
|
e.Observe(7, true);
|
||||||
Check(e.Remaining() == 7, "decreases are taken");
|
Check(e.Remaining() == 7, "decreases are taken");
|
||||||
e.Observe(9, true);
|
e.Observe(9, true);
|
||||||
Check(e.Remaining() == 7, "an increase is ignored");
|
Check(e.Remaining() == 7, "an increase is ignored");
|
||||||
|
Check(e.Accepted() == 3, "three accepted, counted against the real total");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- The three recorded runs, replayed in order
|
// ---- The three recorded runs, replayed in order
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue