- cf_vision/models.py: ImageFrame + ImageElement + BoundingBox (MIT) Full Dolphin-v2 element taxonomy (21 types), convenience accessors (text_blocks, barcodes, tables, full_text) - cf_vision/router.py: VisionRouter — mock + real paths, task routing (document, barcode, receipt, general) - cf_vision/barcode.py: BarcodeScanner — pyzbar wrapper, CPU-only, MIT - cf_vision/ocr.py: DolphinOCR — ByteDance/Dolphin-v2 async stub (BSL 1.1) - cf_vision/receipt.py: ReceiptParser stub — Kiwi Phase 2 target (BSL 1.1) - cf_vision/camera.py: CameraCapture — OpenCV single-frame capture (MIT) - pyproject.toml: inference / barcode / camera optional extras - .env.example: HF_TOKEN, CF_VISION_DEVICE, CF_VISION_MOCK - README: module map, ImageFrame API reference, consumer roadmap - tests: 6 passing (ImageFrame accessors, VisionRouter mock/real) Extracted from circuitforge_core.vision per cf-core#36.
85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
# 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",
|
|
)
|