Custom Arcbit Codec
Location-first asset tracking through a patent-pending machine-readable arcbit format — scan any tag with any camera, capture GPS or IP-geolocated events on-device, and build a passive audit trail without active radios, firmware, or any camera data leaving the device.
01 / Encoding
Arcbit is a purpose-built 2D barcode codec — not QR, not Data Matrix. Each cell encodes a 4-bit nibble via quadrant arc geometry. A custom framing protocol provides magic identification, versioning, CRC integrity, and an optional raw bit-payload field.
Concentric ring layout
Data cells arranged in concentric rings around a central bull's-eye anchor ring. The anchor encodes orientation via a notch in the outer ring band — a weighted circular mean gives sub-degree accuracy. Optimised for Waveshare e-Paper displays.
N × N flat layout
Uniform N×N grid with corner bull's-eye anchors and alternating timing rails in the quiet-zone border. SVD-based homography correction handles camera perspective distortion. Default 8×8 grid fits ~21 bytes payload.
Each cell encodes one 4-bit nibble (0–15) via quadrant arc geometry. Two nibbles pack into one byte. bit-1 = filled arc from center · bit-0 = corner circle cutout
02 / Classification
Each decoded cell passes through a trained linear classifier. No template matching, no thresholding heuristics per-cell. The model is pre-trained, embedded in the binary, and runs inside CPython-on-WebAssembly in a background worker thread.
16×16 grayscale patch downsampled via OpenCV INTER_AREA, flattened and normalized: (px − 128) / 128 → [−1, 1]
z = W · x + b —
W: (4, 256)
b: (4,)
Weights pre-trained on synthetic arcbit renders, embedded directly in the WASM Python backend at load time.
Binary threshold (no sigmoid):
bits = (z > 0).astype(int)
Pack: (b[0]<<3)|(b[1]<<2)|(b[2]<<1)|b[3]
Per-frame adaptive binarization using Otsu's between-class variance. Clamped to [70, 210]. Three offsets (0, −12, +12) tried per candidate to handle partial or uneven lighting.
Perspective distortion corrected via SVD-based Direct Linear Transform. Corner detection uses adaptive thresholding + contour analysis with squareness (aspect 0.7–1.3) and convexity (>80%) filters.
Centered crop candidates at 8-px step intervals ensure grid alignment across all scales. Off-center candidates at ±6% handle imperfectly framed arcbits. Magic-header fast-reject eliminates false candidates before full cell decode.
Ring contrast score from three concentric disk means. Angular notch detected via 720-sample polar ring scan. Weighted circular mean gives sub-degree orientation for rotation correction.
03 / Architecture
Decoding is entirely on-device — no image is uploaded, no frame ever leaves the browser. CPython compiled to WebAssembly (Pyodide) runs the full CV and ML stack in a background Web Worker, keeping the camera feed private and at full frame rate. Only the decoded text result is sent to the platform.
getUserMedia() requests the environment-facing camera (up to 1920×1080 @ 30fps). A camera scoring algorithm selects the best lens — back wide/main preferred over telephoto or front. Auto-scan fires every 120ms desktop / 220ms mobile.
Scan-frame overlay maps to video pixel coordinates accounting for object-fit letterboxing. ROI padded 8% and scaled to ≤960px mobile / 900px desktop. BT.601 luma: Y = (R·77 + G·150 + B·29) >> 8
The Uint8Array is transferred via postMessage() Transferable — no copy, no serialization. An RPC-style ID sequence tracks concurrent in-flight requests.
On first decode the worker boots Pyodide v0.27 (CPython 3.11 → WASM), installs numpy and opencv-python, then fetches and executes arcbit_backend.py. Subsequent calls invoke decode_frame() directly.
Otsu threshold → multi-pass crop candidates → magic-byte fast-reject → homography correction → ML cell classify (256→4 linear) → nibble assembly → _parse_framed_payload() → CRC-16-CCITT verify → UTF-8 decode. Normal and inverted frames both tried.
Decoded text posted back to main thread. 800ms cooldown prevents duplicate events. Scan frame flashes emerald on success. Results stored to Django session history.
boot: 1 vis: visible worker: ready camera: ready video: 1920×1080 decode: 900×900 attempts: 142 hits: 3 misses: 139 decode ms: 38.4 / 41.2 result: square: https://katlog.io/… reason: decoded thr: 118 | 106 | 130 gray: μ:127 σ:44 life: boot-complete err: -
04 / Deployment
Arcbits printed or displayed on e-Paper become physical asset tags with zero ongoing power cost. Every scan is a timestamped location event — GPS-precise where available, IP-geolocated as fallback — building a passive movement audit trail without any active radio, battery, or network dependency on the tag itself.
e-Paper displays are bi-stable — they hold the last rendered arcbit indefinitely with zero power draw. Tags on assets, cases, or shelving consume power only during updates, giving months of operational life on a small cell.
Every scan event carries location context. The scanner requests the device's GPS coordinates first; if unavailable or denied, the platform falls back to IP geolocation to resolve a city-level fix. Both coordinates and source are recorded per scan.
No active tracking infrastructure required. Each time an asset tag is scanned — by any camera, any device — the platform logs who scanned it, when, and where. The arcbit payload itself encodes the asset identity.
05 / Platform
Django handles auth, session history, server-side arcbit generation, DuckDB scan caching, and the location event pipeline. The decoder is fully client-side — the backend never receives camera frames, raw images, or any visual data. Privacy by design.