cf-vision/cf_vision/barcode.py
pyr0ball 353525c1f4 feat: initial cf-vision scaffold — ImageFrame API, stub inference modules
- 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.
2026-04-06 17:59:00 -07:00

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",
)