1+ package main
2+
3+ import (
4+ "bytes"
5+ "encoding/json"
6+ "math"
7+ "os"
8+ "os/exec"
9+ "path/filepath"
10+ "regexp"
11+ "strconv"
12+ "testing"
13+ )
14+
15+ const ocrTestImage = "meter-reader-ocr-test"
16+
17+ func TestIntegrationOCR (t * testing.T ) {
18+ if _ , err := exec .LookPath ("docker" ); err != nil {
19+ t .Skip ("skipping: docker not found" )
20+ }
21+ if entries , _ := filepath .Glob ("testdata/*.jpg" ); len (entries ) == 0 {
22+ t .Skip ("skipping: no testdata/*.jpg files found" )
23+ }
24+
25+ // Build or reuse the OCR test image.
26+ check := exec .Command ("docker" , "image" , "inspect" , ocrTestImage )
27+ if err := check .Run (); err != nil {
28+ t .Log ("building Docker image for OCR integration tests (this may take a few minutes on first run)..." )
29+ cmd := exec .Command ("docker" , "build" , "-f" , "Dockerfile.test" , "-t" , ocrTestImage , "." )
30+ cmd .Stdout = os .Stdout
31+ cmd .Stderr = os .Stderr
32+ if err := cmd .Run (); err != nil {
33+ t .Fatalf ("failed to build test image: %v" , err )
34+ }
35+ } else {
36+ t .Log ("using existing Docker image:" , ocrTestImage )
37+ }
38+
39+ cropCfg := & cropConfig {X0 : 850 , Y0 : 630 , X1 : 1470 , Y1 : 795 }
40+ matchRe := regexp .MustCompile (`^000\d+$` )
41+ fixRules := parseOCRFixRules ("^300=000,^800=000,^900=000" )
42+ masks := parseMaskRegions (
43+ "405,21,451,43,457,27,499,45,507,29,550,47,68,125,136,163" ,
44+ "8a9986,8a9986,8a9986,cef2ce" ,
45+ )
46+ divisor := 1000.0
47+
48+ tests := []struct {
49+ file string
50+ want float64
51+ }{
52+ {"testdata/0.jpg" , 355.797 },
53+ {"testdata/1.jpg" , 355.914 },
54+ {"testdata/2.jpg" , 355.920 },
55+ }
56+
57+ for _ , tt := range tests {
58+ t .Run (filepath .Base (tt .file ), func (t * testing.T ) {
59+ imageData , err := os .ReadFile (tt .file )
60+ if err != nil {
61+ t .Fatalf ("read image: %v" , err )
62+ }
63+
64+ processed , err := cropImage (imageData , cropCfg )
65+ if err != nil {
66+ t .Fatalf ("crop: %v" , err )
67+ }
68+
69+ processed , err = maskImage (processed , masks )
70+ if err != nil {
71+ t .Fatalf ("mask: %v" , err )
72+ }
73+
74+ tmpDir := t .TempDir ()
75+ tmpFile := filepath .Join (tmpDir , "image.jpg" )
76+ if err := os .WriteFile (tmpFile , processed , 0644 ); err != nil {
77+ t .Fatalf ("write temp file: %v" , err )
78+ }
79+
80+ cmd := exec .Command ("docker" , "run" , "--rm" ,
81+ "-v" , tmpDir + ":/data:ro" ,
82+ ocrTestImage ,
83+ "/data/image.jpg" ,
84+ )
85+ var stdout , stderr bytes.Buffer
86+ cmd .Stdout = & stdout
87+ cmd .Stderr = & stderr
88+ if err := cmd .Run (); err != nil {
89+ t .Fatalf ("docker run: %v\n stderr: %s" , err , stderr .String ())
90+ }
91+
92+ var ocrOut ocrOutput
93+ if err := json .Unmarshal (stdout .Bytes (), & ocrOut ); err != nil {
94+ t .Fatalf ("parse OCR output: %v (raw: %s)" , err , stdout .String ())
95+ }
96+
97+ reading := extractReading (ocrOut .Texts , matchRe , fixRules )
98+ if reading == "" {
99+ t .Fatalf ("no reading extracted from texts: %v" , ocrOut .Texts )
100+ }
101+
102+ raw , err := strconv .ParseFloat (reading , 64 )
103+ if err != nil {
104+ t .Fatalf ("parse reading %q: %v" , reading , err )
105+ }
106+
107+ got := raw / divisor
108+ if math .Abs (got - tt .want ) > 0.001 {
109+ t .Errorf ("reading = %.3f, want %.3f (raw=%s, texts=%v)" , got , tt .want , reading , ocrOut .Texts )
110+ }
111+ })
112+ }
113+ }
0 commit comments