Diagnoseanzeige fuer die Funktion "Zählen" ergaenzt
Der Nutzer meldet eine stark schwankende Zaehlanzeige im Livebetrieb (3-60), obwohl dasselbe Verfahren an einem Standfoto stabil 18 liefert - er kann aber keine Bilder hochladen und muss Messwerte daher vom Bildschirm ablesen und durchgeben koennen. - count-objects.js liefert additiv Zwischenwerte (Bildgroesse nach Normierung, Flaechen vor/nach Filterung, groesste Flaeche, typische Groesse, gewinnende Polaritaet) - auch bei count 0 sinnvoll belegt. Die eigentliche Zaehllogik bleibt unveraendert. - count-history.js (neu): gleitender Median der letzten 9 Messungen, unterdrueckt einzelne Ausreisser ohne die Anzeige traege zu machen. - main.js zeigt den Median als grosse Zahl, misst den Zeitbedarf des Zaehldurchlaufs und baut daraus die Diagnosezeile; der Verlauf wird bei jedem Funktionswechsel zurueckgesetzt. - scan-view.js/styles.css: die grosse Zahl ist antippbar und blendet eine anfangs verborgene Diagnosezeile ein/aus, ohne selbst zu verrutschen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// Gleitender Verlauf der letzten Zaehlmessungen (Funktion "Zählen") - die
|
||||
// grosse Anzeige zeigt zusaetzlich zur rohen Messung deren Median ueber die
|
||||
// letzten neun Durchlaeufe, damit ein einzelner Ausreisser (Bewegungs-
|
||||
// unschaerfe, nachregelnder Autofokus) nicht sofort die Anzeige springen
|
||||
// laesst. Reines, browserfreies Modul wie count-objects.js - hier ohne
|
||||
// jeden Bezug zu Bilddaten, nur die Zahlenreihe selbst.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createCountHistory, pushCount, HISTORY_WINDOW } from '../src/count-history.js';
|
||||
|
||||
test('leerer Verlauf: die erste Messung ist zugleich ihr eigener Median', () => {
|
||||
const history = createCountHistory();
|
||||
const result = pushCount(history, 18);
|
||||
assert.deepEqual(result.history, [18]);
|
||||
assert.equal(result.median, 18);
|
||||
});
|
||||
|
||||
test('ein einzelner Ausreisser wird vom Median unterdrueckt', () => {
|
||||
let history = createCountHistory();
|
||||
// Acht stabile Messungen ...
|
||||
for (let i = 0; i < 8; i += 1) {
|
||||
({ history } = pushCount(history, 18));
|
||||
}
|
||||
// ... und ein einzelner Ausreisser (z. B. Bewegungsunschaerfe oder
|
||||
// nachregelnder Autofokus, siehe Modul-Doku).
|
||||
const result = pushCount(history, 60);
|
||||
assert.equal(
|
||||
result.median,
|
||||
18,
|
||||
'ein einzelner Ausreisser darf die geglaettete Anzeige nicht verschieben',
|
||||
);
|
||||
});
|
||||
|
||||
test('nach genuegend gleichbleibenden neuen Messungen folgt der Median (nicht traege)', () => {
|
||||
// Der Median glaettet nur einzelne Ausreisser, nicht eine echte,
|
||||
// anhaltende Aenderung (z. B. weil der Nutzer tatsaechlich Teile
|
||||
// weggenommen hat) - sonst waere die Anzeige "beruhigt", aber nicht mehr
|
||||
// ehrlich.
|
||||
let history = createCountHistory();
|
||||
for (let i = 0; i < 8; i += 1) {
|
||||
({ history } = pushCount(history, 18));
|
||||
}
|
||||
let result;
|
||||
for (let i = 0; i < HISTORY_WINDOW; i += 1) {
|
||||
result = pushCount(history, 5);
|
||||
history = result.history;
|
||||
}
|
||||
assert.equal(
|
||||
result.median,
|
||||
5,
|
||||
'nach HISTORY_WINDOW gleichen neuen Messungen ist der alte Wert vollstaendig aus dem Fenster verdraengt',
|
||||
);
|
||||
});
|
||||
|
||||
test('das Fenster behaelt hoechstens HISTORY_WINDOW Messungen - aeltere fallen heraus', () => {
|
||||
let history = createCountHistory();
|
||||
for (let i = 1; i <= HISTORY_WINDOW + 3; i += 1) {
|
||||
({ history } = pushCount(history, i));
|
||||
}
|
||||
assert.equal(history.length, HISTORY_WINDOW);
|
||||
// Die drei aeltesten (1, 2, 3) sind bereits herausgefallen.
|
||||
assert.deepEqual(history, [4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
||||
});
|
||||
|
||||
test('pushCount veraendert den uebergebenen Verlauf nicht (reine Funktion)', () => {
|
||||
const history = createCountHistory();
|
||||
const before = pushCount(history, 3).history;
|
||||
const untouched = [...history];
|
||||
pushCount(before, 4);
|
||||
assert.deepEqual(history, untouched, 'der urspruengliche Verlauf bleibt unangetastet');
|
||||
});
|
||||
@@ -158,3 +158,83 @@ test('zwei nahe, aber nur diagonal benachbarte Flaechen zaehlen getrennt (4er-Na
|
||||
const result = countObjects(image(rows));
|
||||
assert.equal(result.count, 2, 'nur diagonal benachbarte Flaechen bleiben getrennt');
|
||||
});
|
||||
|
||||
// --- Diagnosewerte (siehe Modul-Doku von countObjects()) ----------------
|
||||
//
|
||||
// Die Diagnoseanzeige (ui/scan-view.js, verdrahtet in main.js) liest diese
|
||||
// Zwischenwerte vom Bildschirm ab, wenn beim Nutzer die Zaehlung schwankt -
|
||||
// sie muessen deshalb auch dann sinnvoll belegt sein, wenn nichts gefunden
|
||||
// wurde (count === 0), sonst zeigt die Anzeige in genau dem Fall nichts, in
|
||||
// dem sie am dringendsten gebraucht wird.
|
||||
|
||||
test('ein Bild ohne Bildpunkte belegt alle Diagnosewerte mit 0/null', () => {
|
||||
const result = countObjects({ width: 0, height: 0, data: new Uint8ClampedArray(0) });
|
||||
assert.equal(result.count, 0);
|
||||
assert.equal(result.width, 0);
|
||||
assert.equal(result.height, 0);
|
||||
assert.equal(result.regionsFound, 0);
|
||||
assert.equal(result.regionsKept, 0);
|
||||
assert.equal(result.largestArea, 0);
|
||||
assert.equal(result.typicalArea, 0);
|
||||
assert.equal(result.polarity, null);
|
||||
});
|
||||
|
||||
test('leeres Bild (kein Objekt) belegt die Diagnosewerte trotzdem sinnvoll', () => {
|
||||
const rows = grid(300, 300, 60);
|
||||
const result = countObjects(image(rows));
|
||||
assert.equal(result.count, 0);
|
||||
// Kleiner als TARGET_WIDTH (800) - bleibt bei der Normierung unveraendert.
|
||||
assert.equal(result.width, 300);
|
||||
assert.equal(result.height, 300);
|
||||
assert.equal(result.regionsFound, 0);
|
||||
assert.equal(result.regionsKept, 0);
|
||||
assert.equal(result.largestArea, 0);
|
||||
assert.equal(result.typicalArea, 0);
|
||||
// Keine Flaeche gefunden - trotzdem muss eine der beiden Polaritaeten als
|
||||
// Diagnosewert benannt sein, nicht null (das bleibt dem Fall "gar kein
|
||||
// Bildpunkt" vorbehalten, siehe Test oben).
|
||||
assert.ok(result.polarity === 'hell' || result.polarity === 'dunkel');
|
||||
});
|
||||
|
||||
test('eine einzelne Flaeche liefert plausible Diagnosewerte und die richtige Polaritaet', () => {
|
||||
const rows = grid(300, 300, 30);
|
||||
paint(rows, 140, 140, 20, 20, 220); // heller als der Untergrund
|
||||
const result = countObjects(image(rows));
|
||||
assert.equal(result.count, 1);
|
||||
assert.equal(result.width, 300);
|
||||
assert.equal(result.height, 300);
|
||||
assert.equal(result.polarity, 'hell', 'helle Flaeche auf dunklerem Grund');
|
||||
assert.equal(result.regionsFound, 1);
|
||||
assert.equal(result.regionsKept, 1);
|
||||
assert.ok(result.largestArea > 0);
|
||||
// Bei genau einer signifikanten Flaeche ist der Median (die typische
|
||||
// Groesse) exakt diese eine Flaeche.
|
||||
assert.equal(result.typicalArea, result.largestArea);
|
||||
});
|
||||
|
||||
test('dunkle Flaeche auf hellem Grund meldet die Polaritaet "dunkel"', () => {
|
||||
const rows = grid(300, 300, 220);
|
||||
paint(rows, 140, 140, 20, 20, 30);
|
||||
const result = countObjects(image(rows));
|
||||
assert.equal(result.count, 1);
|
||||
assert.equal(result.polarity, 'dunkel');
|
||||
});
|
||||
|
||||
test('regionsKept zaehlt Flaechen, nicht die hochgerechneten Fundstuecke', () => {
|
||||
// Wie der Test "eine Flaeche von etwa vierfacher Einzelgroesse wird als
|
||||
// vier gezaehlt" oben: drei Einzelflaechen + eine verschmolzene Flaeche
|
||||
// vierfacher Groesse. count rechnet die verschmolzene Flaeche auf vier
|
||||
// Fundstuecke hoch (macht 7 insgesamt) - regionsKept zaehlt aber die
|
||||
// tatsaechlich gefundenen, zusammenhaengenden Flaechen (4), nicht die
|
||||
// hochgerechneten Fundstuecke.
|
||||
const rows = grid(600, 600, 30);
|
||||
paint(rows, 60, 60, 40, 40, 220);
|
||||
paint(rows, 400, 60, 40, 40, 220);
|
||||
paint(rows, 60, 400, 40, 40, 220);
|
||||
paint(rows, 400, 400, 80, 80, 220);
|
||||
const result = countObjects(image(rows));
|
||||
assert.equal(result.count, 7);
|
||||
assert.equal(result.regionsFound, 4);
|
||||
assert.equal(result.regionsKept, 4);
|
||||
assert.ok(result.largestArea > result.typicalArea, 'die verschmolzene Flaeche ist die groesste');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user