Spracherkennung für OCR-Texte und Qualitätsbewertung (Konfidenzwert), um schlechte OCR-Ergebnisse kenntlich zu machen. Letztes Ticket vor QA-03. - ocr/language.go: RecognizeWithLanguageAndConfidence erkennt ein Bild einzeln je Kandidatensprache (deu/eng) im Tesseract-TSV-Modus — die Sprache mit höherem Konfidenzwert gewinnt, derselbe Lauf liefert den Konfidenzwert direkt mit. - search: neue Felder ocr_language/ocr_confidence (Migrationen 0006/0007, gleiches ALTER-Muster wie SRC-05), in Document/Result gespiegelt. Client.AttachmentsBelowConfidence filtert gezielt auf niedrige Konfidenz, schließt Dokumente ohne OCR-Anhang aus. - Regressionsbug gefunden und behoben: reindex.go (SRC-09) kannte die neuen OCR-Spalten nicht, Reindex wäre mit "unknown column" fehlgeschlagen. Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-10-PRUEFPROTOKOLL.md): 1. TestRecognizeWithLanguageAndConfidence_MultilingualCorpus: deutsches und englisches Testbild real korrekt als deu/eng erkannt. 2. TestRecognizeWithLanguageAndConfidence_DegradedImageLowersConfidence: künstliche Verschlechterung senkt Konfidenz real von 91,76 auf 28,21. 3. TestAttachmentsBelowConfidence_QueryReturnsExpectedResults: Abfrage unterhalb Schwelle liefert real genau die erwarteten 2 von 4 Treffern. Kein Umbau: Search/Facets/SearchWithFilters/Index/Delete-Verhalten sonst unverändert, dedup/indexworker/storage/crypto/encstorage/savedsearch unverändert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
102 lines
3.5 KiB
Go
102 lines
3.5 KiB
Go
package ocr
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"testing"
|
|
)
|
|
|
|
// TestRecognizeWithLanguageAndConfidence_MultilingualCorpus ist die
|
|
// geforderte Pflichtprüfung 1: Test mit mehrsprachigem Testkorpus
|
|
// bestätigt korrekte Spracherkennung.
|
|
func TestRecognizeWithLanguageAndConfidence_MultilingualCorpus(t *testing.T) {
|
|
requireTools(t)
|
|
ctx := context.Background()
|
|
|
|
germanImage := knownTextImage(t, ctx, "Rechnung ueber Lieferung Nummer Zwei")
|
|
englishImage := knownTextImage(t, ctx, "Invoice for delivery number two")
|
|
|
|
germanResult, err := RecognizeWithLanguageAndConfidence(ctx, germanImage)
|
|
if err != nil {
|
|
t.Fatalf("deutsches bild: %v", err)
|
|
}
|
|
if germanResult.Language != "deu" {
|
|
t.Fatalf("erwartete sprache 'deu' für deutschen text, habe %q (text: %q)", germanResult.Language, germanResult.Text)
|
|
}
|
|
|
|
englishResult, err := RecognizeWithLanguageAndConfidence(ctx, englishImage)
|
|
if err != nil {
|
|
t.Fatalf("englisches bild: %v", err)
|
|
}
|
|
if englishResult.Language != "eng" {
|
|
t.Fatalf("erwartete sprache 'eng' für englischen text, habe %q (text: %q)", englishResult.Language, englishResult.Text)
|
|
}
|
|
}
|
|
|
|
// TestRecognizeWithLanguageAndConfidence_DegradedImageLowersConfidence ist
|
|
// die geforderte Pflichtprüfung 2: künstlich verschlechtertes Bild erzeugt
|
|
// niedrigeren Konfidenzwert.
|
|
func TestRecognizeWithLanguageAndConfidence_DegradedImageLowersConfidence(t *testing.T) {
|
|
requireTools(t)
|
|
ctx := context.Background()
|
|
|
|
original := knownTextImage(t, ctx, "Qualitaetsvergleich Testbild Konfidenz")
|
|
originalImg, err := png.Decode(bytes.NewReader(original))
|
|
if err != nil {
|
|
t.Fatalf("original dekodieren: %v", err)
|
|
}
|
|
degradedImg := pixelate(originalImg, 12)
|
|
var degradedBuf bytes.Buffer
|
|
if err := png.Encode(°radedBuf, degradedImg); err != nil {
|
|
t.Fatalf("verschlechtertes bild kodieren: %v", err)
|
|
}
|
|
|
|
originalResult, err := RecognizeWithLanguageAndConfidence(ctx, original)
|
|
if err != nil {
|
|
t.Fatalf("original erkennen: %v", err)
|
|
}
|
|
degradedResult, err := RecognizeWithLanguageAndConfidence(ctx, degradedBuf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("verschlechtertes bild erkennen: %v", err)
|
|
}
|
|
|
|
t.Logf("original: conf=%.2f text=%q — verschlechtert: conf=%.2f text=%q",
|
|
originalResult.Confidence, originalResult.Text, degradedResult.Confidence, degradedResult.Text)
|
|
if degradedResult.Confidence >= originalResult.Confidence {
|
|
t.Fatalf("erwartete niedrigeren konfidenzwert für verschlechtertes bild, habe original=%.2f verschlechtert=%.2f",
|
|
originalResult.Confidence, degradedResult.Confidence)
|
|
}
|
|
}
|
|
|
|
// pixelate verschlechtert img künstlich, reproduzierbar und ohne externe
|
|
// Werkzeuge: Downsample um factor per Nearest-Neighbor, dann wieder auf
|
|
// Originalgröße hochskaliert — zerstört Feindetails der Textglyphen.
|
|
func pixelate(img image.Image, factor int) image.Image {
|
|
bounds := img.Bounds()
|
|
w, h := bounds.Dx(), bounds.Dy()
|
|
out := image.NewRGBA(bounds)
|
|
for y := 0; y < h; y++ {
|
|
for x := 0; x < w; x++ {
|
|
blockX := (x / factor) * factor
|
|
blockY := (y / factor) * factor
|
|
c := img.At(bounds.Min.X+blockX, bounds.Min.Y+blockY)
|
|
out.Set(bounds.Min.X+x, bounds.Min.Y+y, colorToGray50(c))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// colorToGray50 mischt zusätzlich mit 50% Grau, um den Kontrast zu
|
|
// verringern — verstärkt die künstliche Qualitätsverschlechterung über
|
|
// reine Pixelierung hinaus.
|
|
func colorToGray50(c color.Color) color.Color {
|
|
r, g, b, a := c.RGBA()
|
|
mix := func(v uint32) uint8 {
|
|
return uint8((v>>8)/2 + 128/2)
|
|
}
|
|
return color.RGBA{R: mix(r), G: mix(g), B: mix(b), A: uint8(a >> 8)}
|
|
}
|