File size: 3,283 Bytes
863cb78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import cv2
import numpy as np
from PIL import Image
import zxingcpp
import barcodenumber

class Barcode:
    def __init__(self):
        self._SYM_ALIAS = {
            'EAN13': 'ean13',
            'EAN8':  'ean8',
            'UPCA':  'upc',
            'UPC-A': 'upc',
        }

    def validate_barcode(self, data: str, sym: str) -> bool:
        # Empty strings are always invalid
        if not data:
            return False
            
        # For unknown symbology, try all known formats first
        if sym.upper() not in self._SYM_ALIAS:
            if data.isdigit():
                for known_format in ['ean13', 'ean8', 'upc']:
                    try:
                        if barcodenumber.check_code(known_format, data):
                            return True
                    except (ValueError, KeyError):
                        continue
            # If no known format matches, validate basic structure
            return False
            
        # For known formats, validate normally
        code = self._SYM_ALIAS.get(sym, sym.lower())
        try:
            return barcodenumber.check_code(code, data)
        except (ValueError, KeyError):
            return False

    def scan_and_validate(self, image, show_image: bool = False):
        # 1) normalize to OpenCV BGR numpy array
        if isinstance(image, np.ndarray):
            cv_img = image.copy()
        else:
            # assume PIL
            cv_img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)

        # 2) for zxing we need a PIL, so make one from cv_img
        pil_for_scan = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
        barcodes = zxingcpp.read_barcodes(pil_for_scan)

        results = []
        for i, barcode in enumerate(barcodes):
            pos = barcode.position
            if pos:
                pts = [pos.top_left, pos.top_right, pos.bottom_right, pos.bottom_left]
                xs = [p.x for p in pts]
                ys = [p.y for p in pts]
                x, y = int(min(xs)), int(min(ys))
                w, h = int(max(xs) - x), int(max(ys) - y)
            else:
                x, y, w, h = 0, 0, 100, 50

            raw = barcode.text
            sym = str(barcode.format)
            ok  = self.validate_barcode(raw, sym)

            # Create barcode result with position data
            barcode_result = {
                'id': f'BARCODE_{i+1:03d}',
                'type': sym, 
                'data': raw, 
                'valid': ok,
                'position': {
                    'x': x,
                    'y': y,
                    'width': w,
                    'height': h,
                    'top_left': {'x': x, 'y': y},
                    'top_right': {'x': x + w, 'y': y},
                    'bottom_right': {'x': x + w, 'y': y + h},
                    'bottom_left': {'x': x, 'y': y + h}
                }
            }
            
            results.append(barcode_result)

        return results

    def draw_box(self, img, x, y, w, h, sym, raw, ok):
        color = (0,255,0) if ok else (0,0,255)
        cv2.rectangle(img, (x,y), (x+w, y+h), color, 2)
        cv2.putText(img, f"{sym}:{raw}", (x, y-10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
        return img