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:
2026-07-30 08:56:26 +02:00
co-authored by Claude Opus 5
parent f0705a010c
commit d84c15e0d0
7 changed files with 509 additions and 68 deletions
+80
View File
@@ -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');
});