# cf_vision/barcode.py — barcode and QR code scanning # # MIT licensed. Uses pyzbar (libzbar wrapper) — no GPU required. # Requires [barcode] extras: pip install cf-vision[barcode] # # Primary consumer: Kiwi (pantry item lookup by UPC/EAN barcode scan) from __future__ import annotations import logging from typing import Literal from cf_vision.models import BoundingBox, ImageElement, ImageFrame logger = logging.getLogger(__name__) BarcodeFormat = Literal[ "EAN13", "EAN8", "UPCA", "UPCE", "CODE128", "CODE39", "QR_CODE", "PDF417", "DATAMATRIX", "ITF", "CODABAR", ] class BarcodeScanner: """ Lightweight barcode and QR code scanner using pyzbar. No GPU required. Works on CPU with ~5ms per image. Usage ----- scanner = BarcodeScanner() frame = scanner.scan(image_bytes) for b in frame.barcodes(): print(b.text, b.metadata["format"]) Requires: pip install cf-vision[barcode] """ def scan(self, image_bytes: bytes) -> ImageFrame: """ Scan image_bytes for barcodes and QR codes. Returns an ImageFrame with element_type "barcode" or "qr_code" for each detected code. Elements include decoded text and bounding box. """ try: from pyzbar.pyzbar import decode as pyzbar_decode from PIL import Image import io except ImportError as exc: raise ImportError( "pyzbar and Pillow are required for barcode scanning. " "Install with: pip install cf-vision[barcode]" ) from exc img = Image.open(io.BytesIO(image_bytes)).convert("RGB") w, h = img.size decoded = pyzbar_decode(img) elements: list[ImageElement] = [] for symbol in decoded: fmt = symbol.type.upper() el_type = "qr_code" if fmt == "QRCODE" else "barcode" rect = symbol.rect bbox = BoundingBox( x=rect.left / w, y=rect.top / h, width=rect.width / w, height=rect.height / h, ) elements.append(ImageElement( element_type=el_type, text=symbol.data.decode("utf-8", errors="replace"), confidence=1.0, # pyzbar doesn't give confidence scores bbox=bbox, metadata={"format": fmt}, )) return ImageFrame( source="upload", image_bytes=image_bytes, elements=elements, width_px=w, height_px=h, model="pyzbar", )