Skip to content

Commit 8b66f6d

Browse files
authored
fix: compare the feature vector against medians, not poisoned means (#41)
1 parent 81c3f67 commit 8b66f6d

3 files changed

Lines changed: 28 additions & 16 deletions

File tree

src/application/use-cases/RecognizeSignsUseCase.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class RecognizeSignsUseCase {
6565
private lastRawTop: readonly RawScore[] = [];
6666
private lastVeto: WindowVeto | null = null;
6767
private wordsEmitted = 0;
68+
private lettersEmitted = 0;
6869
private lastSignature: SignatureProfile | null = null;
6970
/** The segmenter outlives a session, so short-window counts are read as a delta. */
7071
private shortWindowsAtStart = 0;
@@ -205,7 +206,10 @@ export class RecognizeSignsUseCase {
205206
}
206207

207208
private append(candidate: SignCandidate, atMs: number): void {
208-
this.wordsEmitted += 1;
209+
// Counted apart: a panel reading "2 words" while the vocabulary was vetoed every time
210+
// says the alphabet spoke, and conflating them hid exactly that.
211+
if (candidate.source === 'alphabet') this.lettersEmitted += 1;
212+
else this.wordsEmitted += 1;
209213
this.transcript = this.transcript.append({
210214
text: candidate.gloss.text,
211215
source: candidate.source,
@@ -255,6 +259,7 @@ export class RecognizeSignsUseCase {
255259
this.lastRawTop = [];
256260
this.lastVeto = null;
257261
this.wordsEmitted = 0;
262+
this.lettersEmitted = 0;
258263
this.lastSignature = null;
259264
this.shortWindowsAtStart = this.segmenter.discardedShortWindows;
260265
}
@@ -275,6 +280,7 @@ export class RecognizeSignsUseCase {
275280
lastRawTop: this.lastRawTop,
276281
lastVeto: this.lastVeto,
277282
wordsEmitted: this.wordsEmitted,
283+
lettersEmitted: this.lettersEmitted,
278284
lastSignature: this.lastSignature,
279285
};
280286
}

src/domain/recognition/value-objects/RecognitionDiagnostics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ export interface RecognitionDiagnostics {
5959
/** Unfiltered best guesses for the last window, below the thresholds included. */
6060
readonly lastRawTop: readonly RawScore[];
6161
readonly lastVeto: WindowVeto | null;
62+
/** Vocabulary words only. Fingerspelled letters are counted separately below. */
6263
readonly wordsEmitted: number;
64+
/** Letters from the alphabet engine, which speaks even while the vocabulary is silent. */
65+
readonly lettersEmitted: number;
6366
/** What the model was actually fed for the last window. Null until one is classified. */
6467
readonly lastSignature: SignatureProfile | null;
6568
}
@@ -77,5 +80,6 @@ export const EMPTY_DIAGNOSTICS: RecognitionDiagnostics = {
7780
lastRawTop: [],
7881
lastVeto: null,
7982
wordsEmitted: 0,
83+
lettersEmitted: 0,
8084
lastSignature: null,
8185
};

src/presentation/components/DiagnosticsPanel.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@ import {
66
} from '@domain/recognition/value-objects/RecognitionDiagnostics';
77

88
/**
9-
* The same statistics measured over SWL-LSE's test split — `tools/train`, 598 recordings.
9+
* The same statistics over SWL-LSE's test split — `tools/train`, 598 recordings.
1010
*
11-
* Shown beside the live numbers because the model scores near-noise in the browser while
12-
* measuring 0.741 offline, and feature parity with the trainer is already verified. So the
13-
* input differs, and the part whose numbers do not match is where.
11+
* Medians, not means. The mean of a hand block is 3.37 and its median 0.73: three of the
12+
* sixty-nine floats are the wrist relative to the torso, and on the 0.01% of frames where
13+
* MediaPipe collapses shoulder width to nearly zero that division reaches six figures. A
14+
* mean reference flagged a perfectly healthy vector as broken by a factor of three.
1415
*/
1516
const EXPECTED: Record<string, { empty: number; magnitude: number }> = {
16-
'Mano derecha': { empty: 0.238, magnitude: 3.37 },
17-
'Mano izquierda': { empty: 0.377, magnitude: 3.61 },
18-
Torso: { empty: 0.0, magnitude: 0.515 },
19-
Cara: { empty: 0.002, magnitude: 0.157 },
17+
'Mano derecha': { empty: 0.238, magnitude: 0.73 },
18+
'Mano izquierda': { empty: 0.377, magnitude: 0.73 },
19+
Torso: { empty: 0.0, magnitude: 0.51 },
20+
Cara: { empty: 0.002, magnitude: 0.15 },
21+
};
22+
23+
const VETO_LABELS: Record<WindowVeto, string> = {
24+
classifier: 'el modelo: ninguna opción llegó a su mínimo',
25+
stabilizer: 'el estabilizador: el modelo respondió pero se quedó corto',
26+
duplicate: 'repetido: mismo signo que el anterior',
2027
};
2128

2229
/**
@@ -26,12 +33,6 @@ const EXPECTED: Record<string, { empty: number; magnitude: number }> = {
2633
* deployed page, so a dev-only panel would never once be looked at while the bug is
2734
* reproducing.
2835
*/
29-
const VETO_LABELS: Record<WindowVeto, string> = {
30-
classifier: 'el modelo: ninguna opción llegó a su mínimo',
31-
stabilizer: 'el estabilizador: el modelo respondió pero se quedó corto',
32-
duplicate: 'repetido: mismo signo que el anterior',
33-
};
34-
3536
export class DiagnosticsPanel {
3637
private open = false;
3738
private latest: RecognitionDiagnostics = EMPTY_DIAGNOSTICS;
@@ -84,7 +85,8 @@ export class DiagnosticsPanel {
8485
['Última ventana', d.lastWindowFrames ? `${d.lastWindowFrames} fotogramas` : '—'],
8586
['Motor cargado', d.vocabularyReady ? 'sí' : 'no'],
8687
['Veces consultado', `${d.vocabularyInvocations}`],
87-
['Palabras escritas', `${d.wordsEmitted}`],
88+
['Palabras del vocabulario', `${d.wordsEmitted}`],
89+
['Letras deletreadas', `${d.lettersEmitted}`],
8890
['Bloqueado por', d.lastVeto ? VETO_LABELS[d.lastVeto] : '—'],
8991
];
9092

0 commit comments

Comments
 (0)