Compare commits
2
Commits
d84c15e0d0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6688efc546 | ||
|
|
4b6bb065b1 |
@@ -9,8 +9,9 @@ QR-Code, Text erkennen oder Zählen (siehe „Funktionsweise" unten). Jede
|
||||
Funktion nutzt ausschließlich ihre eigene Quelle; anders als früher weicht die
|
||||
App nicht mehr automatisch auf Texterkennung aus, wenn kein Code gefunden
|
||||
wird. Die vierte Funktion, „Zählen", gehört nicht zum eigentlichen Sortieren:
|
||||
Sie bucht nichts, sondern zeigt beim Auslegen von Kleinteilen (z. B.
|
||||
Festplattenschrauben) fortlaufend deren Anzahl an.
|
||||
Sie bucht nichts, sondern ermittelt auf Knopfdruck beim Auslegen von
|
||||
Kleinteilen (z. B. Festplattenschrauben) deren Anzahl und zeigt sie an, bis
|
||||
der Nutzer erneut antippt.
|
||||
|
||||
Zwei Module gelten als zusammengehörig, wenn sie denselben Barcode-Inhalt
|
||||
tragen — ein Barcode ist exakt gelesen, das ist der zuverlässigste Maßstab.
|
||||
@@ -65,12 +66,12 @@ keine Daten verlassen das Gerät.
|
||||
ausschließlich in `src/scan-modes.js` — es gibt keine zweite Stelle, an der
|
||||
das steht:
|
||||
|
||||
| Funktion | Liest | Laufende Suche/Zählung | Texterkennung |
|
||||
| Funktion | Liest | Laufende Codesuche | Texterkennung |
|
||||
|---|---|---|---|
|
||||
| **Strichcode** | Code128, Code39, Code93, ITF, EAN-13, EAN-8, UPC-A, UPC-E, Codabar | ja (Codesuche) | nein |
|
||||
| **QR-Code** | QRCode, MicroQRCode, RMQRCode, DataMatrix, Aztec, PDF417 | ja (Codesuche) | nein |
|
||||
| **Strichcode** | Code128, Code39, Code93, ITF, EAN-13, EAN-8, UPC-A, UPC-E, Codabar | ja | nein |
|
||||
| **QR-Code** | QRCode, MicroQRCode, RMQRCode, DataMatrix, Aztec, PDF417 | ja | nein |
|
||||
| **Text erkennen** | keine Codes | nein | ja, auf Knopfdruck |
|
||||
| **Zählen** | keine Codes | ja (Zählung, keine Buchung) | nein |
|
||||
| **Zählen** | keine Codes | nein | nein, dafür Zählung auf Knopfdruck (keine Buchung) |
|
||||
|
||||
„QR-Code" heißt auf Wunsch des Auftraggebers „QR-Code", liest aber bewusst
|
||||
nicht nur QR-Varianten, sondern auch DataMatrix, Aztec und PDF417. Der
|
||||
@@ -80,18 +81,24 @@ keine Daten verlassen das Gerät.
|
||||
`src/scan-modes.js`), und der Name ist Absicht — es ist der Wunsch des
|
||||
Auftraggebers für seinen Sprachgebrauch.
|
||||
|
||||
„Modul scannen" erscheint nur in „Text erkennen" — in „Strichcode" und
|
||||
„QR-Code" sucht die App ohnehin laufend, ein Knopf ohne Texterkennung hätte
|
||||
dort nichts zu tun. Der Zielrahmen hebt sich in „Strichcode" und „QR-Code"
|
||||
hervor, sobald ein passender Code im Bild ist; in „Text erkennen" bleibt er
|
||||
Eine einzige Aufnahme-Schaltfläche bedient beide Knopfdruck-Funktionen —
|
||||
„Modul scannen" in „Text erkennen", „Zählen" bzw. (nach der ersten
|
||||
Aufnahme seit Betreten der Funktion) „Nochmal zählen" in „Zählen" — statt
|
||||
einer zweiten Schaltfläche daneben; nur Wortlaut und Rückruf unterscheiden
|
||||
sich
|
||||
(`setCaptureLabel` in `src/ui/scan-view.js`, verdrahtet in
|
||||
`src/main.js`/`applyMode()`). In „Strichcode" und „QR-Code" bleibt sie
|
||||
verborgen — dort sucht die App ohnehin laufend, ein Knopf hätte nichts zu
|
||||
tun. Der Zielrahmen hebt sich in „Strichcode" und „QR-Code" hervor, sobald
|
||||
ein passender Code im Bild ist; in „Text erkennen" und „Zählen" bleibt er
|
||||
schlicht, weil dort nichts laufend erkannt wird. Trotzdem ist der Rahmen
|
||||
auch dort wirksam: „Modul scannen" liest genau den Bildausschnitt innerhalb
|
||||
des Rahmens, in voller Kameraauflösung, nicht mehr das gesamte,
|
||||
heruntergerechnete Kamerabild (`grabFrameRegion` in `src/camera.js` —
|
||||
derselbe Ausschnitt, den auch die laufende Barcode-Suche benutzt). Ein
|
||||
Wechsel der Funktion während des Sortierens (z. B. bei gemischter Ware)
|
||||
gilt sofort: laufende Suche startet oder stoppt, der Knopf erscheint oder
|
||||
verschwindet.
|
||||
auch dort wirksam: Sowohl „Modul scannen" als auch „Zählen" lesen genau den
|
||||
Bildausschnitt innerhalb des Rahmens, in voller Kameraauflösung, nicht das
|
||||
gesamte, heruntergerechnete Kamerabild (`grabFrameRegion` in
|
||||
`src/camera.js` — derselbe Ausschnitt, den auch die laufende Barcode-Suche
|
||||
benutzt). Ein Wechsel der Funktion während des Sortierens (z. B. bei
|
||||
gemischter Ware) gilt sofort: laufende Suche startet oder stoppt, die
|
||||
Schaltfläche erscheint, verschwindet oder ändert ihren Wortlaut.
|
||||
|
||||
Liegt beim Start eine gesicherte Sitzung vor, erscheint zuerst die Frage
|
||||
nach dem Fortsetzen (siehe Punkt 6 unten) und erst danach die Funktionswahl
|
||||
@@ -172,15 +179,36 @@ keine Daten verlassen das Gerät.
|
||||
`src/ui/result-overlay.js`). Der Rohtext jedes Eintrags lässt sich später
|
||||
auch in der Sitzungsliste nachlesen.
|
||||
4. **Zählen** (ausschließlich in Funktion „Zählen"). Reine Anzeige, ohne
|
||||
jeden Bezug zur Sitzung: Solange die Kamera läuft, wird der Zielrahmen
|
||||
mehrmals pro Sekunde ausgewertet und die Anzahl der darin gefundenen
|
||||
Fundstücke groß im Bild eingeblendet (`countObjects` in
|
||||
`src/count-objects.js`, aufgerufen über denselben Zeitgeber/dieselbe
|
||||
Überlappungssperre wie die Barcode-Dauersuche — siehe
|
||||
`continuousSearchLoop` in `src/main.js`). Es gibt in dieser Funktion
|
||||
keinen „Modul scannen"-Knopf, keine Treffer-Rückmeldung und keine
|
||||
Stapel-Zuweisung; die Zahl wird nirgends gespeichert, nicht einmal
|
||||
flüchtig für die Dauer der Sitzung.
|
||||
jeden Bezug zur Sitzung — und, anders als die drei übrigen Funktionen mit
|
||||
laufender Erkennung, auf **Aufnahme statt Dauerbetrieb**: Eine Zählung ist
|
||||
eine Schätzung, kein eindeutiger Treffer wie ein Barcode; sie mehrmals pro
|
||||
Sekunde neu anzuzeigen macht sie nicht genauer, nur unruhig (am
|
||||
Nutzergerät sprang die Anzeige im Dauerbetrieb zwischen etwa 3 und 60, an
|
||||
einem Standfoto derselben Teile lieferte dasselbe Verfahren stabil 18).
|
||||
Antippen der Schaltfläche „Zählen“/„Nochmal zählen“ nimmt deshalb binnen
|
||||
rund einer Sekunde fünf Bilder aus dem Zielrahmen des laufenden
|
||||
Videobilds auf (`grabFrameRegion` in `src/camera.js` — bewusst nicht die
|
||||
native Fotoaufnahme des Geräts, deren Bildausschnitt von der Vorschau
|
||||
abweichen kann), zählt jedes einzeln (`countObjects` in
|
||||
`src/count-objects.js`, unverändert) und zeigt als Ergebnis deren Median
|
||||
(`combineCaptureCounts` in `src/count-capture.js`) — robust gegen ein
|
||||
einzelnes verwackeltes oder mitten in eine Fokusregelung fallendes Bild,
|
||||
ohne die frühere fortlaufende Glättung über die letzten neun Messungen
|
||||
(`count-history.js`, entfallen: eine Aufnahme mit fünf Bildern leistet
|
||||
dieselbe Robustheit bereits selbst). Während der rund einen Sekunde zeigt
|
||||
die Statuszeile „zähle …“, damit der Nutzer die Kamera ruhig hält und den
|
||||
Knopfdruck nicht für wirkungslos hält (`runCountCapture` in
|
||||
`src/main.js`). Das Ergebnis bleibt danach stehen, bis der Nutzer erneut
|
||||
antippt — verlässt er die Funktion oder wechselt zu einer anderen, werden
|
||||
Ergebnis und Diagnosezeile zurückgesetzt. Es gibt in dieser Funktion keine
|
||||
Treffer-Rückmeldung und keine Stapel-Zuweisung; die Zahl wird nirgends
|
||||
gespeichert, nicht einmal flüchtig für die Dauer der Sitzung. Die
|
||||
Diagnosezeile unter der großen Zahl (Antippen der Zahl blendet sie ein
|
||||
oder aus) zeigt zusätzlich zu den übrigen Zwischenwerten (die sich auf die
|
||||
*letzte* der fünf Einzelmessungen beziehen) die fünf Einzelmessungen
|
||||
dieser Aufnahme selbst — daran erkennen Nutzer und Entwickler sofort, ob
|
||||
das Ergebnis belastbar ist: Fünf Messungen von 17 bis 19 bedeuten etwas
|
||||
anderes als fünf Messungen von 4 bis 50.
|
||||
|
||||
Kein Bilderkennungsmodell: Der **Otsu-Schwellwert** (`threshold.js`),
|
||||
den die Texterkennung weiterhin nutzt, taugt für „Zählen" nicht — er
|
||||
@@ -266,7 +294,7 @@ korrekt funktioniert.
|
||||
npm test
|
||||
```
|
||||
|
||||
165 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich
|
||||
179 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich
|
||||
die reinen Module ohne Browser-Zugriff: Teilenummer-Decoder, Spec-Normalisierung
|
||||
und toleranter Vergleich, OCR-Bildaufbereitung (Otsu-Schwellwert, jetzt in
|
||||
`threshold.js`) und -Feldextraktion, Stapel-Zuweisung, die Erkennungs-Pipeline
|
||||
@@ -282,7 +310,11 @@ nie in „Text erkennen"/„Zählen"). Dazu die Zähl-Berechnung selbst
|
||||
(`count-objects.js` — leeres Bild, einzelne und mehrere getrennte Flächen,
|
||||
verworfenes Rauschen, Hochrechnung berührender Teile ohne ein nur leicht
|
||||
größeres Einzelteil zu verdoppeln, helle wie dunkle Objekte, ein Bild ohne
|
||||
Bildpunkte). Kamera, Barcode-/OCR-Adapter selbst, die Auswahl-Oberfläche und
|
||||
Bildpunkte). Dazu die reine Zusammenfassung der fünf Einzelmessungen einer
|
||||
Zaehl-Aufnahme zu deren Median (`count-capture.js` — auch für den Fall
|
||||
weniger als fünf verwertbarer Bilder, sowie eine leere Messreihe, die
|
||||
regulär wirft statt stillschweigend `NaN` zu liefern). Kamera,
|
||||
Barcode-/OCR-Adapter selbst, die Auswahl-Oberfläche und
|
||||
die Verdrahtung in `main.js` laufen nur im echten Browser und werden dort
|
||||
manuell geprüft (siehe „Am Gerät noch zu prüfen" unten) — `node --test` kennt
|
||||
kein DOM.
|
||||
@@ -350,15 +382,16 @@ worauf du bei der Veröffentlichung achten solltest.
|
||||
| `src/ocr-extract.js` | OCR-Rohtext → Spec-Felder |
|
||||
| `src/session.js` | Stapel halten, vorschlagen, buchen, umsortieren, zurücknehmen — inklusive der aus den Einträgen abgeleiteten, je Stapel bekannten Barcode-Inhalte (`stack.codes`), die vor dem Vergleich der technischen Angaben über die Zuordnung entscheiden, sofern diese Angaben nicht widersprechen |
|
||||
| `src/pipeline.js` | Barcode → Teilenummer → OCR → Ampelfarbe, inkl. Zeitgrenzen, Mehrdeutigkeitsbehandlung und Kurzschluss über einen bereits bekannten Barcode-Inhalt (`deps.isKnownCode`) — kennt keine Scan-Funktionen; liefert zusätzlich (additiv) den ungefilterten OCR-Rohtext (`rawText`), leer, wenn keine Texterkennung lief |
|
||||
| `src/scan-modes.js` | Die vier Scan-Funktionen (Strichcode, QR-Code, Text erkennen, Zählen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Suche ja/nein, Texterkennung ja/nein, laufende Zählung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js` |
|
||||
| `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: in „Strichcode"/„QR-Code" liefert die Texterkennung sofort leeren Text ohne Tesseract anzustoßen, in „Text erkennen" liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen, in „Zählen" liefern beide sofort leer (diese Funktion ruft `recognize()` im normalen Betrieb ohnehin nicht auf) |
|
||||
| `src/scan-modes.js` | Die vier Scan-Funktionen (Strichcode, QR-Code, Text erkennen, Zählen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Codesuche ja/nein, Texterkennung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js`. „Zählen" läuft wie „Text erkennen" auf Knopfdruck (`continuousSearch: false`) und braucht dafür kein eigenes Merkmal mehr |
|
||||
| `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: liest eine Funktion keine Codearten (leeres `barcodeFormats`, bei „Text erkennen" und „Zählen"), liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen; nur bei `useOcr` läuft die echte Texterkennung, sonst liefert sie sofort leeren Text ohne Tesseract anzustoßen (in „Zählen" ruft `recognize()` im normalen Betrieb ohnehin nicht auf — siehe `count-capture.js`) |
|
||||
| `src/count-capture.js` | Reine Berechnung für die Aufnahme in Funktion „Zählen": bildet aus den (bis zu fünf) Einzelmessungen einer Aufnahme (`countObjects()` je Bild) deren Median (`combineCaptureCounts`) — robust gegen ein einzelnes verwackeltes oder unscharfes Bild, bleibt auch bei weniger als fünf verwertbaren Bildern sinnvoll |
|
||||
| `src/threshold.js` | Otsu-Schwellwertbestimmung (Graustufen, Histogramm, Schwellwert) — reines Modul ohne Browser-Zugriff, aus `ocr.js` herausgezogen; gemeinsame Grundlage für `preprocess()` (ocr.js) und `countObjects()` (count-objects.js) |
|
||||
| `src/count-objects.js` | Reine Berechnung für Funktion „Zählen": zählt zusammenhängende Objektflächen in einem Bildausschnitt über örtlichen Kontrast (Normierung auf 800 Bildpunkte Breite, Glättung und örtlicher Hintergrund je über Kastenfilter/Summenbild, Maske aus geglättetem Bild vs. örtlichem Hintergrund ± Marge, beide Polaritäten, Connected-Component-Labeling mit eigener Arbeitsliste, Rauschfilter, Massstab relativ zur größten Fläche, Hochrechnung berührender Teile über den Flächen-Median) — nutzt `threshold.js`/Otsu bewusst **nicht** mehr (siehe „Funktionsweise" oben) |
|
||||
| `src/storage.js` | Absturzschutz der laufenden Sitzung: sichert und lädt aus `localStorage`, verwirft beim Laden jeden in sich unstimmigen Zustand vollständig (siehe unten); `entry.rawText` ist wie `entry.codes` optional und wird bei Fehlen als leerer Rohtext wiederhergestellt |
|
||||
| `src/camera.js` | Kamerastart, Einzelbildaufnahme (herunterskaliert, für die Dateiauswahl), Rahmenausschnitt in voller Auflösung (`grabFrameRegion`, für die laufende Barcode-Suche, „Modul scannen" in „Text erkennen" **und** die laufende Zählung in „Zählen"), Bilddatei-Ersatzweg |
|
||||
| `src/camera.js` | Kamerastart, Einzelbildaufnahme (herunterskaliert, für die Dateiauswahl), Rahmenausschnitt in voller Auflösung (`grabFrameRegion`, für die laufende Barcode-Suche, „Modul scannen" in „Text erkennen" **und** jedes der fünf Bilder einer Aufnahme in „Zählen"), Bilddatei-Ersatzweg |
|
||||
| `src/barcode.js` | Adapter zu `zxing-wasm`; `decodeBarcodes(imageData, formats)` bekommt die Codearten übergeben, ohne Angabe wie bisher (Code128 + DataMatrix) |
|
||||
| `src/ocr.js` | Bildaufbereitung (Otsu, über `threshold.js`) und Adapter zu `tesseract.js`; Zeichen-Whitelist deckt Groß- und Kleinbuchstaben, Ziffern sowie übliche Etiketten-Sonderzeichen ab |
|
||||
| `src/ui/scan-view.js` | Scan-Ansicht: Kamera-Vorschau, antippbare Anzeige der gewählten Funktion (Wechsel), Scan-Knopf (nur in „Text erkennen" sichtbar), große Zählanzeige (nur in „Zählen" sichtbar), Stapel-Leiste, „Zuletzt"-Zeile, Ersatzweg-Schaltfläche bei fehlender Kamera |
|
||||
| `src/ui/scan-view.js` | Scan-Ansicht: Kamera-Vorschau, antippbare Anzeige der gewählten Funktion (Wechsel), eine gemeinsame Aufnahme-Schaltfläche für beide Knopfdruck-Funktionen (sichtbar in „Text erkennen" und „Zählen", Wortlaut je nach Funktion und Zustand über `setCaptureLabel`), große Zählanzeige samt Diagnosezeile (nur in „Zählen" sichtbar), Stapel-Leiste, „Zuletzt"-Zeile, Ersatzweg-Schaltfläche bei fehlender Kamera |
|
||||
| `src/ui/mode-dialog.js` | Vollbild-Auswahl der Scan-Funktion vor dem Start und beim Wechsel: vier große Flächen, eine je Funktion aus `src/scan-modes.js` |
|
||||
| `src/ui/result-overlay.js` | Treffer-Rückmeldung (grün/gelb): blendet sich in „Strichcode"/„QR-Code" nach kurzer Zeit selbst aus; in „Text erkennen" bleibt sie stehen, zeigt den vollen OCR-Rohtext und wartet auf eine Eingabe, bevor sie schließt |
|
||||
| `src/ui/ambiguous-dialog.js` | Rot-Dialog bei roter Konfidenz oder mehrdeutiger Stapelzuordnung |
|
||||
@@ -371,9 +404,10 @@ worauf du bei der Veröffentlichung achten solltest.
|
||||
| `.vch/deploy.yaml` | Bau- und Startbefehl sowie Gesundheitspfad für die Hosting-Umgebung |
|
||||
|
||||
`spec`, `pn-decoder`, `ocr-extract`, `session`, `pipeline`, `storage`,
|
||||
`scan-modes`, `scan-recognition`, `threshold` und `count-objects` sind reine
|
||||
Funktionen ohne Browser-Zugriff (kein `window`, `document` oder
|
||||
`localStorage` direkt) und deshalb vollständig mit `node:test` prüfbar.
|
||||
`scan-modes`, `scan-recognition`, `threshold`, `count-objects` und
|
||||
`count-capture` sind reine Funktionen ohne Browser-Zugriff (kein `window`,
|
||||
`document` oder `localStorage` direkt) und deshalb vollständig mit
|
||||
`node:test` prüfbar.
|
||||
`camera.js`, `barcode.js`, `ocr.js` und `src/ui/` brauchen einen echten
|
||||
Browser und werden nur manuell geprüft.
|
||||
|
||||
@@ -586,18 +620,41 @@ werden:
|
||||
dass die Vorbereitung tatsächlich nur einmal läuft)? In Funktion „Text
|
||||
erkennen" darf dieses Laden dagegen gar nicht erst anlaufen, da dort nie
|
||||
`decodeBarcodes` aufgerufen wird.
|
||||
**Zählen (neu)**
|
||||
**Zählen (Aufnahme statt Dauerbetrieb)**
|
||||
- „Zählen" wählen, Kamera auf ausgelegte Kleinteile richten (z. B. silberne
|
||||
Festplattenschrauben auf dunklem Untergrund): Eine große Zahl erscheint im
|
||||
Bild und aktualisiert sich laufend, ohne dass etwas angetippt werden muss.
|
||||
Kein „Modul scannen"-Knopf, keine Treffer-Rückmeldung, kein roter Dialog,
|
||||
keine Stapel-Zuweisung — der Zielrahmen bleibt sichtbar und zeigt weiterhin,
|
||||
was gezählt wird.
|
||||
- Die angezeigte Zahl aus normalem Bedienabstand (Armlänge) lesbar prüfen.
|
||||
- Ein paar Teile wegnehmen bzw. hinzufügen, während die Kamera läuft: Die
|
||||
Zahl folgt ohne spürbare Verzögerung.
|
||||
Festplattenschrauben auf dunklem Untergrund): Es erscheint **keine**
|
||||
Zahl und **keine** fortlaufende Aktualisierung, sondern eine gut
|
||||
erreichbare Schaltfläche mit der Aufschrift „Zählen". Kein roter Dialog,
|
||||
keine Treffer-Rückmeldung, keine Stapel-Zuweisung — der Zielrahmen bleibt
|
||||
sichtbar und zeigt, was beim nächsten Antippen gezählt wird.
|
||||
- „Zählen" antippen: Für rund eine Sekunde zeigt die Statuszeile „zähle …“,
|
||||
danach erscheint eine große Zahl im Bild und bleibt stehen — sie
|
||||
aktualisiert sich **nicht** von selbst weiter. Die Schaltfläche trägt jetzt
|
||||
die Aufschrift „Nochmal zählen".
|
||||
- Während der rund einen Sekunde Kamera und Teile ruhig halten (leichte
|
||||
Bewegung zwischendurch simulieren) und danach prüfen, dass das Ergebnis
|
||||
plausibel bleibt — die App nimmt in dieser Zeit fünf Bilder auf und zeigt
|
||||
deren Median.
|
||||
- Die Zahl antippen: Diagnosezeile blendet sich ein und zeigt (in dieser
|
||||
Reihenfolge) `Roh: …`, `Messungen: …` (die fünf Einzelmessungen dieser
|
||||
Aufnahme, z. B. `17, 18, 19, 18, 20`), `Bild: …`, `Flächen: …`,
|
||||
`Größte Fläche: …`, `Typische Größe: …`, `Art: …`, `Dauer: …`. Nochmaliges
|
||||
Antippen blendet sie wieder aus. Prüfen, dass die fünf Messungen tatsächlich
|
||||
eng beieinanderliegen, wenn die Teile ruhig lagen, und spürbar streuen,
|
||||
wenn während der Aufnahme bewegt wurde.
|
||||
- „Nochmal zählen" antippen: neue Aufnahme, neues Ergebnis ersetzt das alte
|
||||
vollständig (Zahl **und** Diagnosezeile).
|
||||
- Zur Funktionswahl wechseln und zurück zu „Zählen": Zahl und Diagnosezeile
|
||||
sind zurückgesetzt (nichts angezeigt), Schaltfläche zeigt wieder „Zählen"
|
||||
statt „Nochmal zählen". Dasselbe beim Wechsel zu einer anderen Funktion und
|
||||
zurück.
|
||||
- Zugänge zur Sitzungsliste und zum Funktionswechsel bleiben auch in dieser
|
||||
Funktion erreichbar (Anzeige der Funktion antippen, Stapel-Leiste antippen).
|
||||
Funktion erreichbar (Anzeige der Funktion antippen, Stapel-Leiste antippen)
|
||||
— außer während die rund einssekündige Aufnahme selbst läuft, genau wie bei
|
||||
einem laufenden Scan in „Text erkennen".
|
||||
- Aufnahme antippen, bevor das Kamerabild bereit ist (z. B. gleich nach dem
|
||||
Start): verständliche Meldung „Zählen nicht möglich — Kamerabild noch
|
||||
nicht bereit" statt Absturz oder stiller Nichtreaktion.
|
||||
- Gegenprobe der Grenzen (siehe „Grenzen" oben): Teile eng aneinanderlegen
|
||||
(Hochrechnung), Teile stapeln (nur oberste Schicht zählt), einseitige
|
||||
Beleuchtung, geringer Kontrast zum Untergrund — beobachten, wo die Zahl
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Bildet aus den Einzelmessungen einer Zaehl-Aufnahme (Funktion "Zählen",
|
||||
// siehe main.js) das anzuzeigende Endergebnis. main.js nimmt je Antippen der
|
||||
// Schaltflaeche "Zählen"/"Nochmal zählen" binnen rund einer Sekunde mehrere
|
||||
// Bilder aus dem Zielrahmen auf und zaehlt jedes einzeln (countObjects() in
|
||||
// count-objects.js, unveraendert) - ein einzelnes verwackeltes oder mitten in
|
||||
// eine Fokusregelung fallendes Bild soll das Ergebnis nicht verzerren. Frueher
|
||||
// glaettete ein gleitender Median ueber die letzten neun Durchlaeufe einer
|
||||
// Dauerzaehlung (siehe .superpowers/sdd/counting-capture-report.md); das ist
|
||||
// entfallen, weil eine Aufnahme mit mehreren Bildern dieselbe Robustheit
|
||||
// bereits in sich selbst leistet - zwei Glaettungen uebereinander waeren nur
|
||||
// zusaetzliche Traegheit ohne zusaetzlichen Nutzen.
|
||||
//
|
||||
// Der Median der Einzelmessungen ist robust gegen einen einzelnen groben
|
||||
// Ausreisser (anders als deren Mittelwert) - siehe median() in
|
||||
// count-objects.js fuer dieselbe Ueberlegung bei den dortigen
|
||||
// Flaechengroessen.
|
||||
//
|
||||
// Reines, browserfreies Modul wie count-objects.js - kennt weder Kamera noch
|
||||
// Bilddaten, nur die Zahlenreihe der Einzelmessungen selbst.
|
||||
import { median } from './count-objects.js';
|
||||
|
||||
/**
|
||||
* Bildet den Median der Einzelmessungen einer Aufnahme (siehe Moduldoku
|
||||
* oben). Bleibt auch dann sinnvoll, wenn eine Aufnahme aus weniger als den
|
||||
* vorgesehenen fuenf Bildern besteht (z. B. weil das Kamerabild waehrend
|
||||
* eines Versuchs kurz nicht bereit war, siehe grabFrameRegion() in
|
||||
* camera.js) - nur eine vollstaendig leere Aufnahme (kein einziges
|
||||
* verwertbares Bild) ist kein regulaerer Ausgang und wirft; main.js prueft
|
||||
* diesen Fall vorher selbst und ruft diese Funktion dann gar nicht erst auf.
|
||||
* @param {number[]} counts Einzelmessungen dieser Aufnahme - nicht-leer.
|
||||
* @returns {number}
|
||||
* @throws {Error} wenn counts leer ist.
|
||||
*/
|
||||
export function combineCaptureCounts(counts) {
|
||||
if (counts.length === 0) {
|
||||
throw new Error('combineCaptureCounts: keine Messung vorhanden');
|
||||
}
|
||||
return median(counts);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Verwaltet den gleitenden Verlauf der letzten Zaehlmessungen der Funktion
|
||||
// "Zählen" (siehe count-objects.js, verdrahtet in main.js). Die App zeigt
|
||||
// zusaetzlich zur rohen letzten Messung deren Median ueber die letzten
|
||||
// HISTORY_WINDOW Durchlaeufe als grosse Zahl an: ein einzelner Ausreisser
|
||||
// (Bewegungsunschaerfe, nachregelnder Autofokus, kurzzeitig verdeckte Teile)
|
||||
// verschwindet dadurch, ohne dass die Anzeige traege wird - eine echte,
|
||||
// anhaltende Aenderung (der Nutzer nimmt tatsaechlich Teile weg) schlaegt
|
||||
// nach genuegend neuen Messungen weiterhin durch.
|
||||
//
|
||||
// Reines, browserfreies Modul wie count-objects.js - kennt weder Bilddaten
|
||||
// noch die Oberflaeche, nur die Zahlenreihe selbst. Verwendet denselben
|
||||
// robusten Median wie die Flaechengroessen dort (siehe median() in
|
||||
// count-objects.js).
|
||||
import { median } from './count-objects.js';
|
||||
|
||||
// Fenstergroesse fuer den gleitenden Median (siehe Moduldoku oben) - neun
|
||||
// Messungen bei rund fuenf Durchlaeufen pro Sekunde (siehe
|
||||
// SEARCH_INTERVAL_MS in main.js) sind knapp zwei Sekunden Verlauf: kurz
|
||||
// genug, um eine tatsaechliche Aenderung zuegig zu uebernehmen, lang genug,
|
||||
// um einen einzelnen Ausreisser zuverlaessig zu unterdruecken.
|
||||
export const HISTORY_WINDOW = 9;
|
||||
|
||||
/**
|
||||
* Erstellt einen leeren Verlauf. Wird verwendet, wenn die Funktion "Zählen"
|
||||
* (neu) betreten wird oder der Nutzer die Scan-Funktion wechselt - der
|
||||
* Verlauf einer anderen Situation darf nicht in die neue hinueberwirken
|
||||
* (siehe main.js/applyMode()).
|
||||
* @returns {number[]}
|
||||
*/
|
||||
export function createCountHistory() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Haengt eine neue rohe Messung an den Verlauf an (behaelt hoechstens die
|
||||
* letzten HISTORY_WINDOW Messungen, die aeltesten fallen zuerst heraus) und
|
||||
* bildet daraus den Median. Reine Funktion - veraendert `history` selbst
|
||||
* nicht, sondern liefert einen neuen Verlauf zurueck; der Aufrufer haelt
|
||||
* seinen eigenen Zustand (siehe main.js).
|
||||
* @param {number[]} history bisheriger Verlauf, aelteste Messung zuerst
|
||||
* @param {number} rawCount rohe, gerade gemessene Anzahl
|
||||
* @returns {{history: number[], median: number}}
|
||||
*/
|
||||
export function pushCount(history, rawCount) {
|
||||
const updated = [...history, rawCount];
|
||||
if (updated.length > HISTORY_WINDOW) {
|
||||
updated.shift();
|
||||
}
|
||||
return { history: updated, median: median(updated) };
|
||||
}
|
||||
+179
-103
@@ -4,7 +4,7 @@ import { startCamera, grabFrameRegion, imageDataFromFile } from './camera.js';
|
||||
import { decodeBarcodes } from './barcode.js';
|
||||
import { runOcr, isOcrAvailable } from './ocr.js';
|
||||
import { countObjects } from './count-objects.js';
|
||||
import { createCountHistory, pushCount } from './count-history.js';
|
||||
import { combineCaptureCounts } from './count-capture.js';
|
||||
import { recognize } from './pipeline.js';
|
||||
import { normalizeToken } from './spec.js';
|
||||
import { getScanMode } from './scan-modes.js';
|
||||
@@ -51,23 +51,33 @@ let cameraUnavailableMessage = null;
|
||||
// ohnehin unerreichbar.
|
||||
let currentModeId = null;
|
||||
|
||||
// Gleitender Verlauf der letzten Zaehlmessungen (Funktion "Zählen", siehe
|
||||
// count-history.js) - Grundlage der beruhigten grossen Zahl (Median der
|
||||
// letzten neun Messungen). Wird bei jedem Aufruf von applyMode() neu
|
||||
// angelegt: sowohl ein Wechsel weg von "Zählen" als auch ein (erneuter)
|
||||
// Eintritt in die Funktion soll mit einem leeren Verlauf beginnen, damit die
|
||||
// Anzeige nie Werte einer anderen Situation (anderes Teil, andere Stelle)
|
||||
// zeigt.
|
||||
let countHistory = createCountHistory();
|
||||
// Ob die aktuelle Sitzung in Funktion "Zählen" bereits eine Aufnahme gemacht
|
||||
// hat - traegt die Beschriftung der Aufnahme-Schaltflaeche ("Zählen" vor der
|
||||
// ersten Aufnahme seit Betreten der Funktion, "Nochmal zählen" danach, siehe
|
||||
// updateCountCaptureLabel() unten). Wird bei jedem Aufruf von applyMode()
|
||||
// zurueckgesetzt: sowohl ein Wechsel weg von "Zählen" als auch ein (erneuter)
|
||||
// Eintritt in die Funktion beginnt wieder bei "Zählen" - siehe auch
|
||||
// view.setCountVisible() (loescht das stehende Ergebnis samt Diagnose beim
|
||||
// Verlassen).
|
||||
let hasCountResult = false;
|
||||
|
||||
const view = renderScanView(app, {
|
||||
// "Modul scannen" ist nur in Funktion 3 ("Text erkennen") sichtbar (siehe
|
||||
// applyMode()/setCaptureVisible) - der Zielrahmen suggeriert dem Nutzer dort
|
||||
// einen Ausschnitt, den grabFrame() (Vollbild, herunterskaliert) bisher gar
|
||||
// nicht einloeste. grabFrameRegion() liest genau den Rahmenausschnitt, in
|
||||
// voller Aufloesung - derselbe Ausschnitt, den auch die Barcode-Dauersuche
|
||||
// Eine gemeinsame Schaltflaeche fuer beide Knopfdruck-Funktionen (siehe
|
||||
// scan-view.js/setCaptureLabel()): in Funktion "Text erkennen" loest sie
|
||||
// processCapture() aus (Erkennung + Buchung), in Funktion "Zählen"
|
||||
// stattdessen runCountCapture() (Aufnahme mehrerer Bilder, keine Buchung).
|
||||
// Der Zielrahmen suggeriert dem Nutzer in beiden Faellen einen Ausschnitt,
|
||||
// den grabFrame() (Vollbild, herunterskaliert) nicht einloeste -
|
||||
// grabFrameRegion() liest genau den Rahmenausschnitt, in voller
|
||||
// Aufloesung - derselbe Ausschnitt, den auch die Barcode-Dauersuche
|
||||
// benutzt (siehe runSearchAttempt() weiter unten).
|
||||
onCapture: () => processCapture(() => grabFrameRegion(view.video)),
|
||||
onCapture: () => {
|
||||
if (currentModeId === 'count') {
|
||||
runCountCapture();
|
||||
} else {
|
||||
processCapture(() => grabFrameRegion(view.video));
|
||||
}
|
||||
},
|
||||
onUndo: () => {
|
||||
// Waehrend eine Erkennung laeuft, darf kein Eintrag zurueckgenommen
|
||||
// werden - sonst koennte die Rueckgaengig-Flaeche einen Eintrag treffen,
|
||||
@@ -93,32 +103,38 @@ const view = renderScanView(app, {
|
||||
});
|
||||
|
||||
/**
|
||||
* Uebernimmt eine (neu gewaehlte) Scan-Funktion: Anzeige, Sichtbarkeit von
|
||||
* "Modul scannen" und - bei einem Wechsel weg von laufender Codesuche - der
|
||||
* Zielrahmen wird sofort zurueckgesetzt, statt auf den naechsten Durchlauf
|
||||
* der Dauersuche zu warten.
|
||||
* Uebernimmt eine (neu gewaehlte) Scan-Funktion: Anzeige, Sichtbarkeit und
|
||||
* Beschriftung der Aufnahme-Schaltflaeche und - bei einem Wechsel weg von
|
||||
* laufender Codesuche - der Zielrahmen wird sofort zurueckgesetzt, statt auf
|
||||
* den naechsten Durchlauf der Dauersuche zu warten.
|
||||
* @param {string} modeId
|
||||
*/
|
||||
function applyMode(modeId) {
|
||||
currentModeId = modeId;
|
||||
// Verlauf der Dauerzaehlung zuruecksetzen (siehe Kommentar bei der
|
||||
// Deklaration oben) - unabhaengig davon, ob die neue Funktion "Zählen"
|
||||
// ist: ein Wechsel weg davon soll den Verlauf ebenso wenig ueberleben wie
|
||||
// ein erneuter Eintritt mit alten Werten.
|
||||
countHistory = createCountHistory();
|
||||
const mode = getScanMode(modeId);
|
||||
view.setMode(mode.label);
|
||||
// "Modul scannen" hat nur dort etwas zu tun, wo es weder laufende
|
||||
// Codesuche noch laufende Zaehlung gibt (Funktion 3) - siehe Kommentar in
|
||||
// scan-view.js/setCaptureVisible.
|
||||
view.setCaptureVisible(!mode.continuousSearch && !mode.continuousCount);
|
||||
// Die Aufnahme-Schaltflaeche hat nur dort etwas zu tun, wo es keine
|
||||
// laufende Codesuche gibt (Funktion 3 "Text erkennen" und Funktion 4
|
||||
// "Zählen") - siehe Kommentar in scan-view.js/setCaptureVisible.
|
||||
view.setCaptureVisible(!mode.continuousSearch);
|
||||
if (!mode.continuousSearch) {
|
||||
view.setFrameDetected(false);
|
||||
}
|
||||
// "Zählen" beginnt bei jedem (erneuten) Betreten wieder bei der
|
||||
// Beschriftung "Zählen" statt "Nochmal zählen" - ein Wechsel weg von der
|
||||
// Funktion soll das ebenso wenig ueberleben wie ein erneuter Eintritt mit
|
||||
// altem Zustand (siehe Deklaration von hasCountResult oben).
|
||||
hasCountResult = false;
|
||||
if (mode.id === 'count') {
|
||||
updateCountCaptureLabel();
|
||||
} else {
|
||||
view.setCaptureLabel(mode.useOcr ? 'Modul scannen' : '');
|
||||
}
|
||||
// Die grosse Zaehlanzeige gehoert ausschliesslich zu Funktion "Zählen" -
|
||||
// beim Verlassen sofort ausblenden, statt auf den naechsten (dann gar
|
||||
// nicht mehr laufenden) Durchlauf der Dauerzaehlung zu warten.
|
||||
view.setCountVisible(mode.continuousCount);
|
||||
// beim Verlassen sofort ausblenden (loescht dabei auch ein stehendes
|
||||
// Ergebnis samt Diagnose, siehe scan-view.js/setCountVisible), statt auf
|
||||
// die naechste Aufnahme zu warten.
|
||||
view.setCountVisible(mode.id === 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +159,25 @@ function describeEntry(spec, stackId) {
|
||||
return `${describeSpec(spec)} → Stapel ${stackId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt die Statuszeile nach einem erfolgreich abgeschlossenen Durchlauf
|
||||
* (Erkennung wie Zaehl-Aufnahme) wieder her - von processCapture() und
|
||||
* runCountCapture() gemeinsam genutzt, statt denselben Vorrang zweimal
|
||||
* hinzuschreiben. Der Hinweis auf eine nicht verfuegbare Kamera hat Vorrang
|
||||
* vor der OCR-Verfuegbarkeitsanzeige und bleibt dauerhaft stehen.
|
||||
*/
|
||||
function restoreIdleStatus() {
|
||||
if (cameraUnavailableMessage) {
|
||||
view.setStatus(cameraUnavailableMessage, true);
|
||||
} else {
|
||||
const available = isOcrAvailable();
|
||||
view.setStatus(
|
||||
available ? '' : 'Texterkennung nicht verfuegbar — nur Barcodes werden gelesen',
|
||||
!available,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein Scan-Durchlauf: Bild beschaffen, erkennen, bei unsicherer Erkennung
|
||||
* nachfragen, buchen, kurz rueckmelden. Laeuft ohne Bestaetigung durch,
|
||||
@@ -241,19 +276,8 @@ async function processCapture(getFrame) {
|
||||
} finally {
|
||||
busy = false;
|
||||
// Die OCR-Verfuegbarkeitsanzeige darf eine Fehlermeldung, die der Nutzer
|
||||
// noch lesen muss, nicht ueberschreiben. Der Hinweis auf eine nicht
|
||||
// verfuegbare Kamera hat Vorrang vor beidem und bleibt dauerhaft stehen.
|
||||
if (!failed) {
|
||||
if (cameraUnavailableMessage) {
|
||||
view.setStatus(cameraUnavailableMessage, true);
|
||||
} else {
|
||||
const available = isOcrAvailable();
|
||||
view.setStatus(
|
||||
available ? '' : 'Texterkennung nicht verfuegbar — nur Barcodes werden gelesen',
|
||||
!available,
|
||||
);
|
||||
}
|
||||
}
|
||||
// noch lesen muss, nicht ueberschreiben.
|
||||
if (!failed) restoreIdleStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,95 +408,147 @@ function describePolarity(polarity) {
|
||||
* ui/scan-view.js/setCountDiagnostics()). Kurze, unfachliche Bezeichnungen
|
||||
* in eigenen Zeilen statt einer einzigen langen Zeile - der Nutzer soll sie
|
||||
* bei Bedarf jemandem am Telefon vorlesen koennen, ohne sich zu verhaspeln.
|
||||
* Enthaelt bewusst sowohl die rohe letzte Messung (zeigt die tatsaechliche
|
||||
* Schwankung, die die grosse - geglaettete - Zahl sonst verbergen wuerde)
|
||||
* als auch den Zeitbedarf des letzten Zaehldurchlaufs (zeigt, ob das Geraet
|
||||
* ueberhaupt hinterherkommt).
|
||||
* @param {ReturnType<typeof countObjects>} result
|
||||
* @param {number} durationMs
|
||||
* Enthaelt zusaetzlich zu den schon vorhandenen Werten die fuenf
|
||||
* Einzelmessungen dieser Aufnahme (`counts`) - daran erkennen Nutzer und
|
||||
* Entwickler sofort, ob das Ergebnis belastbar ist (fuenf Messungen von 17
|
||||
* bis 19 bedeuten etwas anderes als fuenf Messungen von 4 bis 50). Die
|
||||
* uebrigen Werte (`last`) beziehen sich weiterhin auf die *letzte* der
|
||||
* fuenf Einzelmessungen - der gleitende Median ueber mehrere Durchlaeufe
|
||||
* (frueher: count-history.js) entfaellt, die Aufnahme mit ihren fuenf
|
||||
* Bildern leistet die noetige Beruhigung bereits selbst (siehe
|
||||
* count-capture.js).
|
||||
* @param {number[]} counts die (bis zu fuenf) Einzelmessungen dieser Aufnahme
|
||||
* @param {ReturnType<typeof countObjects>} last Ergebnis der letzten der
|
||||
* fuenf Einzelmessungen
|
||||
* @param {number} durationMs Zeitbedarf der letzten Einzelmessung
|
||||
*/
|
||||
function describeCountDiagnostics(result, durationMs) {
|
||||
function describeCountDiagnostics(counts, last, durationMs) {
|
||||
return [
|
||||
`Roh: ${result.count}`,
|
||||
`Bild: ${result.width}×${result.height}`,
|
||||
`Flächen: ${result.regionsFound} → ${result.regionsKept}`,
|
||||
`Größte Fläche: ${result.largestArea} Bildpunkte`,
|
||||
`Typische Größe: ${result.typicalArea} Bildpunkte`,
|
||||
`Art: ${describePolarity(result.polarity)}`,
|
||||
`Roh: ${last.count}`,
|
||||
`Messungen: ${counts.join(', ')}`,
|
||||
`Bild: ${last.width}×${last.height}`,
|
||||
`Flächen: ${last.regionsFound} → ${last.regionsKept}`,
|
||||
`Größte Fläche: ${last.largestArea} Bildpunkte`,
|
||||
`Typische Größe: ${last.typicalArea} Bildpunkte`,
|
||||
`Art: ${describePolarity(last.polarity)}`,
|
||||
`Dauer: ${durationMs.toFixed(0)} ms`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein einzelner Durchlauf der Dauerzaehlung (Funktion "Zählen"): Ausschnitt
|
||||
* in nativer Aufloesung holen, Fundstuecke zaehlen (siehe count-objects.js)
|
||||
* und die grosse Anzeige aktualisieren. Anders als runSearchAttempt() wird
|
||||
* hier nie gebucht und nichts gesperrt - reine Anzeige, busy bleibt
|
||||
* unberuehrt, weil kein Ergebnis in die Sitzung geht und daher auch kein
|
||||
* "Modul scannen", keine Treffer-Rueckmeldung und keine Stapel-Zuweisung
|
||||
* dazugehoeren (siehe scan-modes.js).
|
||||
*
|
||||
* Die grosse Zahl selbst zeigt nicht die rohe Messung, sondern den Median
|
||||
* der letzten neun Messungen (siehe count-history.js): ein einzelner
|
||||
* Ausreisser - Bewegungsunschaerfe, nachregelnder Autofokus, ein kurz
|
||||
* verdecktes Teil - verschwindet damit, ohne dass die Anzeige traege wird.
|
||||
* Die rohe Messung selbst geht nicht verloren, sondern steht (zusammen mit
|
||||
* den uebrigen Zwischenwerten) in der Diagnosezeile - siehe
|
||||
* describeCountDiagnostics() oben.
|
||||
*/
|
||||
async function runCountAttempt() {
|
||||
// Nur in Funktion "Zählen" aktiv - siehe scan-modes.js.
|
||||
if (!getScanMode(currentModeId).continuousCount) return;
|
||||
// Anzahl der Bilder je Zaehl-Aufnahme (siehe runCountCapture() unten): ein
|
||||
// einzelnes Videobild kann verwackelt sein oder mitten in eine
|
||||
// Fokusregelung fallen - fuenf einzeln gezaehlte Bilder, zusammengefasst
|
||||
// ueber ihren Median (count-capture.js), sind robuster als eines allein.
|
||||
const CAPTURE_SAMPLE_SIZE = 5;
|
||||
|
||||
// Dieselbe Pausenbedingung wie bei der Barcode-Dauersuche (laufende
|
||||
// Erkennung oder offenes Vollbild-Overlay) - siehe searchPaused() oben.
|
||||
if (searchPaused()) return;
|
||||
// Zeitspanne, ueber die die CAPTURE_SAMPLE_SIZE Bilder einer Aufnahme verteilt
|
||||
// aufgenommen werden - kurz genug, dass der Nutzer die Kamera dafuer ohne
|
||||
// Muehe ruhig haelt, lang genug, dass aufeinanderfolgende Bilder sich
|
||||
// tatsaechlich unterscheiden (statt praktisch identische Wiederholungen
|
||||
// desselben Videobilds zu zaehlen).
|
||||
const CAPTURE_DURATION_MS = 1000;
|
||||
|
||||
/**
|
||||
* Setzt die Beschriftung der Aufnahme-Schaltflaeche in Funktion "Zählen"
|
||||
* anhand von hasCountResult (siehe Deklaration oben): "Zählen" vor der
|
||||
* ersten Aufnahme seit Betreten der Funktion, "Nochmal zählen" danach.
|
||||
*/
|
||||
function updateCountCaptureLabel() {
|
||||
view.setCaptureLabel(hasCountResult ? 'Nochmal zählen' : 'Zählen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine Aufnahme der Funktion "Zählen" (ausgeloest durch die Schaltflaeche
|
||||
* "Zählen"/"Nochmal zählen"): nimmt binnen rund CAPTURE_DURATION_MS
|
||||
* insgesamt CAPTURE_SAMPLE_SIZE Bilder aus dem Zielrahmen des laufenden
|
||||
* Videobilds auf (`grabFrameRegion`, derselbe Ausschnitt wie ueberall sonst
|
||||
* - bewusst *nicht* die native Fotoaufnahme des Geraets, deren
|
||||
* Bildausschnitt von der Vorschau abweichen kann), zaehlt jedes einzeln
|
||||
* (`countObjects`, unveraendert) und zeigt als Ergebnis deren Median
|
||||
* (`combineCaptureCounts`, count-capture.js) - robust gegen ein einzelnes
|
||||
* verwackeltes oder unscharfes Bild. Waehrend der Aufnahme zeigt die
|
||||
* Statuszeile "zähle …" (dasselbe Muster wie "erkenne …" in
|
||||
* processCapture()), damit der Nutzer das Handy ruhig haelt und den
|
||||
* Knopfdruck nicht fuer wirkungslos haelt.
|
||||
*
|
||||
* Anders als frueher (Dauerzaehlung) bleibt das Ergebnis danach stehen, bis
|
||||
* der Nutzer erneut antippt - eine Zaehlung ist eine Schaetzung, die durch
|
||||
* staendiges Neuanzeigen nicht praeziser wird, nur unruhig. Bucht nichts in
|
||||
* die Sitzung, sperrt aber wie processCapture() ueber `busy` waehrend sie
|
||||
* laeuft (Undo, Sitzungsliste, Funktionswechsel bleiben so lange
|
||||
* unerreichbar - dieselbe kurze, unproblematische Sperre wie bei jedem
|
||||
* anderen Scan-Durchlauf).
|
||||
*/
|
||||
async function runCountCapture() {
|
||||
if (busy) return;
|
||||
busy = true;
|
||||
view.setStatus('zähle …');
|
||||
let failed = false;
|
||||
|
||||
try {
|
||||
const counts = [];
|
||||
let last = null;
|
||||
|
||||
for (let i = 0; i < CAPTURE_SAMPLE_SIZE; i += 1) {
|
||||
if (i > 0) {
|
||||
await sleep(CAPTURE_DURATION_MS / (CAPTURE_SAMPLE_SIZE - 1));
|
||||
}
|
||||
|
||||
let region;
|
||||
try {
|
||||
region = grabFrameRegion(view.video);
|
||||
} catch {
|
||||
// Kamera liefert noch keine brauchbare Bildgroesse - naechster Durchlauf
|
||||
// versucht es erneut.
|
||||
return;
|
||||
// Kamerabild in diesem Augenblick nicht bereit - dieses Einzelbild
|
||||
// faellt aus, die Aufnahme laeuft mit den uebrigen weiter (siehe
|
||||
// combineCaptureCounts() fuer den Fall weniger als fuenf Bilder).
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nur der eigentliche Zaehldurchlauf wird gestoppt (nicht das Holen des
|
||||
// Kamera-Ausschnitts) - so sieht man in der Diagnosezeile gezielt, ob das
|
||||
// Zaehlverfahren selbst mit dem Geraet hinterherkommt (siehe
|
||||
// test/count-objects-photo.test.js fuer dieselbe Messweise).
|
||||
const start = performance.now();
|
||||
const result = countObjects(region);
|
||||
const durationMs = performance.now() - start;
|
||||
counts.push(result.count);
|
||||
last = { result, durationMs };
|
||||
}
|
||||
|
||||
const { history, median: smoothedCount } = pushCount(countHistory, result.count);
|
||||
countHistory = history;
|
||||
if (last === null) {
|
||||
// Kein einziges Bild dieser Aufnahme war verwertbar - kein regulaeres
|
||||
// Ergebnis, siehe combineCaptureCounts() (count-capture.js). Bleibt
|
||||
// wie ein Fehlschlag stehen, statt sofort von restoreIdleStatus()
|
||||
// ueberschrieben zu werden.
|
||||
failed = true;
|
||||
view.setStatus('Zählen nicht möglich — Kamerabild noch nicht bereit', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bei einer noch geraden Verlaufslaenge (bis der Verlauf HISTORY_WINDOW
|
||||
// erreicht) liegt der Median genau zwischen zwei ganzen Messungen - die
|
||||
// Zaehlanzeige selbst zeigt trotzdem stets eine ganze Zahl (siehe
|
||||
// Rundung Schritt 10 in count-objects.js, hier fuer die Anzeige ebenso
|
||||
// gehandhabt).
|
||||
view.setCount(Math.round(smoothedCount));
|
||||
view.setCountDiagnostics(describeCountDiagnostics(result, durationMs));
|
||||
const finalCount = combineCaptureCounts(counts);
|
||||
view.setCount(Math.round(finalCount));
|
||||
view.setCountDiagnostics(describeCountDiagnostics(counts, last.result, last.durationMs));
|
||||
hasCountResult = true;
|
||||
updateCountCaptureLabel();
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
view.setStatus(`Fehler: ${error.message}`, true);
|
||||
} finally {
|
||||
busy = false;
|
||||
if (!failed) restoreIdleStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laeuft, solange die Kamera aktiv ist. Ein neuer Versuch beginnt erst, wenn
|
||||
* der vorherige vollstaendig fertig ist - nie ueberlappend. Fuehrt pro
|
||||
* Durchlauf sowohl den Barcode- als auch den Zaehl-Versuch aus; jeder der
|
||||
* beiden bricht selbst sofort ab, wenn die gewaehlte Funktion nicht zu ihm
|
||||
* passt (siehe runSearchAttempt()/runCountAttempt()) - so bleibt es bei
|
||||
* genau einem Zeitgeber und derselben Ueberlappungssperre fuer alle
|
||||
* fortlaufenden Funktionen, statt einen zweiten, parallelen Mechanismus
|
||||
* fuer "Zählen" danebenzustellen.
|
||||
* Durchlauf die Barcode-Dauersuche aus; sie bricht selbst sofort ab, wenn
|
||||
* die gewaehlte Funktion keine laufende Suche vorsieht (siehe
|
||||
* runSearchAttempt()). Funktion "Zählen" laeuft seit dem Umbau auf
|
||||
* Aufnahmen statt Dauerbetrieb (siehe runCountCapture()) und braucht hier
|
||||
* deshalb keinen eigenen Aufruf mehr.
|
||||
*/
|
||||
async function continuousSearchLoop() {
|
||||
for (;;) {
|
||||
const started = performance.now();
|
||||
try {
|
||||
await runSearchAttempt();
|
||||
await runCountAttempt();
|
||||
} catch {
|
||||
// Ein einzelner fehlgeschlagener Versuch darf die Dauersuche nicht
|
||||
// dauerhaft abbrechen.
|
||||
|
||||
+13
-10
@@ -14,9 +14,13 @@
|
||||
//
|
||||
// Funktion 4 ("Zaehlen") ist reine Anzeige: Sie liest weder Codes noch Text
|
||||
// und bucht nichts in die Sitzung - deshalb `continuousSearch: false` und
|
||||
// `useOcr: false`, obwohl sie (wie die Barcode-Dauersuche) fortwaehrend
|
||||
// laeuft, solange die Kamera aktiv ist. Dafuer eigenes Merkmal
|
||||
// `continuousCount`, siehe main.js.
|
||||
// `useOcr: false`. Anders als frueher laeuft sie nicht mehr fortwaehrend,
|
||||
// solange die Kamera aktiv ist, sondern - wie Funktion 3 ("Text erkennen") -
|
||||
// auf Knopfdruck: eine Zaehlung ist eine Schaetzung, die durch staendiges
|
||||
// Neuanzeigen nicht praeziser wird, nur unruhig. Ein eigenes Merkmal dafuer
|
||||
// braucht es nicht mehr - `continuousSearch: false` allein genuegt bereits,
|
||||
// um main.js (siehe applyMode()) und scan-recognition.js dieselbe
|
||||
// Knopfdruck-Behandlung wie bei Funktion 3 zukommen zu lassen.
|
||||
|
||||
/**
|
||||
* @typedef {object} ScanMode
|
||||
@@ -26,12 +30,11 @@
|
||||
* Codearten (zxing-wasm-Formatnamen). Leer, wenn die Funktion keine
|
||||
* Barcode-Dekodierung nutzt.
|
||||
* @property {boolean} continuousSearch Ob waehrend die Kamera laeuft
|
||||
* fortwaehrend nach Codes gesucht wird (statt nur auf Knopfdruck).
|
||||
* fortwaehrend nach Codes gesucht wird (statt nur auf Knopfdruck). Bei
|
||||
* Funktion "Zaehlen" ebenso `false` wie bei "Text erkennen" - siehe
|
||||
* count-objects.js fuer das Zaehlverfahren selbst und main.js
|
||||
* (runCountCapture()) fuer den Knopfdruck-Ablauf.
|
||||
* @property {boolean} useOcr Ob die Texterkennung (Tesseract) genutzt wird.
|
||||
* @property {boolean} continuousCount Ob waehrend die Kamera laeuft
|
||||
* fortwaehrend die Fundstuecke im Zielrahmen gezaehlt werden (siehe
|
||||
* count-objects.js) - reine Anzeige, ohne Buchung. Nur bei Funktion
|
||||
* "Zaehlen" true.
|
||||
*/
|
||||
|
||||
/** @type {ScanMode[]} */
|
||||
@@ -70,11 +73,11 @@ export const SCAN_MODES = [
|
||||
id: 'count',
|
||||
label: 'Zählen',
|
||||
// Keine Codes, keine Texterkennung - reine Bildauswertung (siehe
|
||||
// count-objects.js), die nichts bucht.
|
||||
// count-objects.js), die nichts bucht. Auf Knopfdruck wie Funktion 3,
|
||||
// siehe Moduldoku oben.
|
||||
barcodeFormats: [],
|
||||
continuousSearch: false,
|
||||
useOcr: false,
|
||||
continuousCount: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+10
-4
@@ -12,10 +12,16 @@
|
||||
// aufgerufen wird.
|
||||
// Funktion 4 (Zaehlen): weder Barcode-Dekodierung noch Texterkennung -
|
||||
// recognize() (pipeline.js) wird im normalen Ablauf fuer diese Funktion gar
|
||||
// nicht aufgerufen (siehe main.js: kein Knopf, keine Dauersuche im Sinne
|
||||
// dieses Moduls); dieselbe leere Ruecklieferung wie in Funktion 3 dient hier
|
||||
// nur als Absicherung fuer den seltenen Fall, dass der Ersatzweg ohne Kamera
|
||||
// nicht aufgerufen (siehe main.js: die Schaltflaeche "Zählen"/"Nochmal
|
||||
// zählen" ruft stattdessen runCountCapture(), das direkt countObjects()
|
||||
// verwendet); dieselbe leere Ruecklieferung wie in Funktion 3 dient hier nur
|
||||
// als Absicherung fuer den seltenen Fall, dass der Ersatzweg ohne Kamera
|
||||
// (Dateiauswahl) waehrend gewaehlter Funktion "Zaehlen" ausgeloest wird.
|
||||
//
|
||||
// Sowohl Funktion 3 als auch Funktion 4 haben `barcodeFormats: []` (siehe
|
||||
// scan-modes.js) - eine leere Codeartenliste ist deshalb das gemeinsame,
|
||||
// einzige Merkmal, an dem sich "keine Barcode-Dekodierung noetig" ablesen
|
||||
// laesst, statt je Funktion einzeln nachzufragen.
|
||||
|
||||
import { getScanMode } from './scan-modes.js';
|
||||
|
||||
@@ -30,7 +36,7 @@ export function buildRecognitionAdapters(modeId, adapters) {
|
||||
const mode = getScanMode(modeId);
|
||||
|
||||
return {
|
||||
decodeBarcodes: (mode.useOcr || mode.continuousCount)
|
||||
decodeBarcodes: mode.barcodeFormats.length === 0
|
||||
? async () => []
|
||||
: (imageData) => adapters.decodeBarcodes(imageData, mode.barcodeFormats),
|
||||
runOcr: mode.useOcr
|
||||
|
||||
@@ -5,8 +5,11 @@ import { SCAN_MODES } from '../scan-modes.js';
|
||||
* scan-modes.js) - keine zweite Stelle mit eigenem Text je Funktion.
|
||||
*/
|
||||
function describeMode(mode) {
|
||||
if (mode.useOcr) return 'Auf Knopfdruck';
|
||||
if (mode.continuousCount) return 'Zählt fortlaufend, solange die Kamera läuft';
|
||||
// "Text erkennen" und "Zählen" laufen beide auf Knopfdruck (siehe
|
||||
// scan-modes.js) - `continuousSearch: false` ist bei beiden das
|
||||
// gemeinsame Merkmal dafuer, eine Fallunterscheidung je Funktion braucht
|
||||
// es hier nicht.
|
||||
if (!mode.continuousSearch) return 'Auf Knopfdruck';
|
||||
return 'Läuft von selbst, sobald ein Code im Bild ist';
|
||||
}
|
||||
|
||||
|
||||
+32
-11
@@ -17,7 +17,7 @@ export function renderScanView(root, { onCapture, onUndo, onOpenList, onPickFile
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="status"></div>
|
||||
<button class="action capture" id="capture" hidden>Modul scannen</button>
|
||||
<button class="action capture" id="capture" hidden></button>
|
||||
<button class="action secondary" id="pick-file-button" hidden>Bild auswählen</button>
|
||||
<div class="stacks" id="stacks"></div>
|
||||
<div class="last">
|
||||
@@ -115,15 +115,30 @@ export function renderScanView(root, { onCapture, onUndo, onOpenList, onPickFile
|
||||
},
|
||||
|
||||
/**
|
||||
* "Modul scannen" hat nur in Funktionen mit Texterkennung auf Knopfdruck
|
||||
* etwas zu tun (Funktion 3) - in Funktionen mit laufender Codesuche sucht
|
||||
* die App ohnehin durchgehend, ein Knopf dafuer waere wirkungslos.
|
||||
* Diese eine Schaltflaeche bedient beide Knopfdruck-Funktionen ("Modul
|
||||
* scannen" in Funktion 3 "Text erkennen", "Zählen"/"Nochmal zählen" in
|
||||
* Funktion 4 "Zählen") - nur ihr Wortlaut und ihr Rueckruf unterscheiden
|
||||
* sich, siehe setCaptureLabel() unten und main.js/applyMode(). In
|
||||
* Funktionen mit laufender Codesuche sucht die App ohnehin durchgehend,
|
||||
* ein Knopf dafuer waere wirkungslos.
|
||||
* @param {boolean} visible
|
||||
*/
|
||||
setCaptureVisible(visible) {
|
||||
captureEl.hidden = !visible;
|
||||
},
|
||||
|
||||
/**
|
||||
* Setzt den Wortlaut der Aufnahme-Schaltflaeche - siehe setCaptureVisible()
|
||||
* oben fuer den Grund, warum es dafuer nur eine gemeinsame Flaeche statt
|
||||
* einer zweiten daneben gibt. main.js setzt hier "Modul scannen" (Funktion
|
||||
* "Text erkennen"), "Zählen" (Funktion "Zählen", vor der ersten Aufnahme
|
||||
* seit Betreten der Funktion) bzw. "Nochmal zählen" (danach).
|
||||
* @param {string} text
|
||||
*/
|
||||
setCaptureLabel(text) {
|
||||
captureEl.textContent = text;
|
||||
},
|
||||
|
||||
/** @param {string} text @param {boolean} warn */
|
||||
setStatus(text, warn = false) {
|
||||
statusEl.textContent = text;
|
||||
@@ -162,7 +177,10 @@ export function renderScanView(root, { onCapture, onUndo, onOpenList, onPickFile
|
||||
* Diagnosezeile: sie faellt dabei wieder in ihren Ausgangszustand
|
||||
* (verborgen, leer) zurueck, damit sie nach einem Funktionswechsel nicht
|
||||
* ungefragt (weil sie beim letzten Mal offen war) oder mit veralteten
|
||||
* Werten wieder auftaucht.
|
||||
* Werten wieder auftaucht. Das gilt sowohl beim Verlassen der Funktion
|
||||
* "Zählen" als auch beim erneuten Betreten - ein stehendes Ergebnis
|
||||
* einer frueheren Aufnahme (andere Teile, andere Stelle) darf nicht
|
||||
* hinueberwirken (siehe main.js/applyMode()).
|
||||
* @param {boolean} visible
|
||||
*/
|
||||
setCountVisible(visible) {
|
||||
@@ -176,11 +194,13 @@ export function renderScanView(root, { onCapture, onUndo, onOpenList, onPickFile
|
||||
},
|
||||
|
||||
/**
|
||||
* Zeigt die (bereits ueber die letzten Messungen geglaettete) Anzahl
|
||||
* gefundener Fundstuecke (siehe count-history.js) - gross und aus
|
||||
* Armlaenge lesbar, aktualisiert sich mit jedem Durchlauf der
|
||||
* Dauerzaehlung. Ein Antippen blendet die Diagnosezeile darunter ein
|
||||
* oder aus (siehe Klick-Handler oben).
|
||||
* Zeigt das Ergebnis der zuletzt angetippten Zaehl-Aufnahme (Median der
|
||||
* fuenf Einzelmessungen dieser Aufnahme, siehe count-capture.js und
|
||||
* runCountCapture() in main.js) - gross und aus Armlaenge lesbar. Bleibt
|
||||
* unveraendert stehen, bis der Nutzer erneut auf die Schaltflaeche
|
||||
* "Nochmal zählen" tippt (keine fortlaufende Aktualisierung mehr). Ein
|
||||
* Antippen der Zahl selbst blendet die Diagnosezeile darunter ein oder
|
||||
* aus (siehe Klick-Handler oben).
|
||||
* @param {number} count
|
||||
*/
|
||||
setCount(count) {
|
||||
@@ -189,7 +209,8 @@ export function renderScanView(root, { onCapture, onUndo, onOpenList, onPickFile
|
||||
|
||||
/**
|
||||
* Setzt den Text der kleinen Diagnosezeile unter der grossen Zahl (siehe
|
||||
* main.js fuer den genauen Wortlaut) - unabhaengig davon, ob sie gerade
|
||||
* main.js fuer den genauen Wortlaut, u. a. die fuenf Einzelmessungen der
|
||||
* zuletzt angetippten Aufnahme) - unabhaengig davon, ob sie gerade
|
||||
* eingeblendet ist. Reine Textaktualisierung, die Sichtbarkeit steuert
|
||||
* ausschliesslich der Klick auf die grosse Zahl (siehe oben).
|
||||
* @param {string} text
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Reine Rechenlogik der Zaehl-Aufnahme (Funktion "Zählen", siehe main.js):
|
||||
// aus den (bis zu fuenf) Einzelmessungen einer Aufnahme wird deren Median
|
||||
// gebildet. Siehe count-capture.js fuer die Begruendung (robust gegen einen
|
||||
// einzelnen Ausreisser, ohne wie eine fortlaufende Glaettung traege zu sein).
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { combineCaptureCounts } from '../src/count-capture.js';
|
||||
|
||||
test('fuenf gleiche Messungen liefern diese Zahl', () => {
|
||||
assert.equal(combineCaptureCounts([18, 18, 18, 18, 18]), 18);
|
||||
});
|
||||
|
||||
test('fuenf verschiedene Messungen liefern deren Median (ungerade Anzahl)', () => {
|
||||
assert.equal(combineCaptureCounts([17, 19, 18, 20, 18]), 18);
|
||||
});
|
||||
|
||||
test('ein einzelner grober Ausreisser unter fuenf Messungen schlaegt nicht durch', () => {
|
||||
// Vier stabile Messungen um 18 herum, eine grob daneben (z. B.
|
||||
// Bewegungsunschaerfe oder Autofokus mitten in der Aufnahme) - der Median
|
||||
// bleibt bei 18, ein Mittelwert waere durch den Ausreisser spuerbar verzerrt.
|
||||
assert.equal(combineCaptureCounts([18, 18, 3, 18, 60]), 18);
|
||||
});
|
||||
|
||||
test('weniger als fuenf Messungen (eine Kameraaufnahme scheiterte) liefern trotzdem ein Ergebnis', () => {
|
||||
// Gerade Anzahl (vier statt fuenf) - Median ist der Mittelwert der beiden
|
||||
// mittleren Werte.
|
||||
const result = combineCaptureCounts([17, 18, 18, 19]);
|
||||
assert.equal(result, 18);
|
||||
});
|
||||
|
||||
test('nur eine einzige verwertbare Messung ist ihr eigener Median', () => {
|
||||
assert.equal(combineCaptureCounts([21]), 21);
|
||||
});
|
||||
|
||||
test('eine leere Messreihe wirft, statt stillschweigend ein sinnloses Ergebnis (NaN) zu liefern', () => {
|
||||
assert.throws(() => combineCaptureCounts([]));
|
||||
});
|
||||
|
||||
test('combineCaptureCounts veraendert die uebergebene Liste nicht (reine Funktion)', () => {
|
||||
const counts = [5, 1, 3];
|
||||
const untouched = [...counts];
|
||||
combineCaptureCounts(counts);
|
||||
assert.deepEqual(counts, untouched);
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
// 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');
|
||||
});
|
||||
@@ -43,13 +43,12 @@ test('Funktion 3 (Text erkennen): keine Codearten, keine laufende Suche, Texterk
|
||||
assert.equal(mode.useOcr, true);
|
||||
});
|
||||
|
||||
test('Funktion 4 (Zählen): keine Codearten, keine laufende Codesuche, keine Texterkennung, dafuer laufende Zaehlung', () => {
|
||||
test('Funktion 4 (Zählen): keine Codearten, keine laufende Codesuche, keine Texterkennung - auf Knopfdruck wie Funktion 3', () => {
|
||||
const mode = getScanMode('count');
|
||||
assert.equal(mode.label, 'Zählen');
|
||||
assert.deepEqual(mode.barcodeFormats, []);
|
||||
assert.equal(mode.continuousSearch, false);
|
||||
assert.equal(mode.useOcr, false);
|
||||
assert.equal(mode.continuousCount, true);
|
||||
});
|
||||
|
||||
test('unbekannte Kennung wirft statt undefined zurueckzugeben', () => {
|
||||
|
||||
Reference in New Issue
Block a user