Camera-driven book intake and checkout inventory system for Books in Hand, a nonprofit keeping books out of landfills. Deterministic size-bucketing, barcode/OCR/vision-LLM backfill, FastAPI kiosk backend.
Find a file
pyr0ball 7c0d8479d4 test: add synthetic platform smoke test using real book cover images
No camera hardware is on hand yet. This script composites public
Open Library cover images onto a synthetic platform background at
small/medium/large footprints and drives the real intake pipeline
(IntakeSession, Catalogue, capture strategies) end to end, in place
of solid-color test frames. Not part of the pytest suite — it fetches
over the network and is meant for human-visible verification.
2026-07-13 19:07:20 -07:00
bookmark docs: drop AI/ML terminology in README, document marker ordering 2026-07-13 11:22:22 -07:00
scripts test: add synthetic platform smoke test using real book cover images 2026-07-13 19:07:20 -07:00
tests fix: handle empty-platform captures, no-op fires, and observe races 2026-07-13 11:22:08 -07:00
.gitignore test: add synthetic platform smoke test using real book cover images 2026-07-13 19:07:20 -07:00
AGENTS.md docs: add AGENTS.md project context (CLAUDE.md symlinked for compatibility) 2026-07-13 10:06:17 -07:00
calibrate.py feat: add config loading, calibration script, and production entrypoint 2026-07-13 11:08:48 -07:00
CLAUDE.md docs: add AGENTS.md project context (CLAUDE.md symlinked for compatibility) 2026-07-13 10:06:17 -07:00
config.example.json feat: add config loading, calibration script, and production entrypoint 2026-07-13 11:08:48 -07:00
CONTRIBUTING.md chore: add MIT LICENSE and CONTRIBUTING.md, set SPDX license in pyproject 2026-07-13 10:17:05 -07:00
LICENSE chore: add MIT LICENSE and CONTRIBUTING.md, set SPDX license in pyproject 2026-07-13 10:17:05 -07:00
pyproject.toml chore: add MIT LICENSE and CONTRIBUTING.md, set SPDX license in pyproject 2026-07-13 10:17:05 -07:00
README.md docs: drop AI/ML terminology in README, document marker ordering 2026-07-13 11:22:22 -07:00
run.py feat: add config loading, calibration script, and production entrypoint 2026-07-13 11:08:48 -07:00

Bookmark

A camera-driven book intake and checkout inventory system, built for Books in Hand, a nonprofit that keeps books out of landfills and in people's hands.

Bookmark replaces manual book logging with a guided, camera-based workflow: a volunteer places a book on a marked platform, the system photographs the front and back, sorts it into a size category for tax/reporting purposes, and later backfills the catalogue with title, author, and ISBN — all running on local hardware, no cloud dependency.

This project is fully open source and is being built and donated as volunteer/client work — it isn't part of any commercial product line.

Status

Phase 1 (intake station) is built, unit-tested, and ready for real-hardware validation. Phase 2 (batch OCR/ISBN backfill) and Phase 3 (checkout station) are planned for later.

Phase What it does Status
1 — Intake station Camera capture, guided front/back flow, deterministic size bucketing, CSV catalogue Built, unit-tested — pending real-hardware validation
2 — Batch OCR/ISBN backfill Barcode decode + local vision-LLM fallback to fill in title/author/ISBN Planned
3 — Checkout station Track books leaving inventory, reusing the intake identification pipeline Planned

How it works (Phase 1)

  1. A downward-facing webcam looks at a fixed platform with printed nested size-boundary markers (small / medium / large).
  2. A volunteer centers the book; capture fires automatically or on a button press (configurable).
  3. The system photographs the front, determines size — deterministically, by checking which physical markers are covered, not by any model inference step — then prompts the volunteer to flip the book and photographs the back.
  4. Every book gets a row in a CSV catalogue: images, size bucket, and a status that later pipeline stages (OCR backfill, checkout) update.

Full design rationale lives in AGENTS.md (also readable as CLAUDE.md — same file, symlinked for compatibility with different AI coding tools).

Why this matters for a nonprofit

  • No cloud costs, no vendor lock-in. Everything runs on donated hardware; no per-book or per-scan fees.
  • Deterministic where it counts. Size-bucket counts feed Books in Hand's year-end tax and 501(c)(3) reporting — that number is computed from physical measurements, not inferred by a model, so it can't drift or hallucinate.
  • Volunteer-friendly. The intake flow is designed to be usable by volunteers with no technical background — guided steps, big buttons, no login required.

Development

Requires Python 3.11+.

Setup

  1. Create and activate a virtual environment:

    python3 -m venv .venv
    .venv/bin/pip install -e ".[dev]"
    
  2. Mount the USB webcam over the capture platform. See the design spec (circuitforge-plans/books-in-hand/superpowers/specs/2026-07-13-inventory-system-design.md) for physical setup details: nested size-boundary markers, guide box.

  3. Run calibration once per physical install, with the platform empty:

    .venv/bin/python calibrate.py <camera_device_index> reference.jpg
    

    This captures a reference image of the empty platform for marker detection.

  4. Copy config.example.json to config.json and fill in:

    • markers: pixel coordinates (from the reference image)
    • guide_box: capture region boundaries
    • trigger_strategy: one of manual, motion_stop, or stable_centered
  5. Run the backend:

    .venv/bin/python run.py config.json
    
  6. Open http://<machine-ip>:8000 in a kiosk-mode browser on the station's monitor.

Running tests

.venv/bin/pytest -v

All tests run without requiring real camera hardware — they use synthetic NumPy frames and mocked cv2.VideoCapture.

End-to-end smoke test (manual, requires real hardware)

This checklist is for whoever installs this system on real hardware. It verifies that the intake workflow functions end-to-end.

  1. Boot the station per Setup above with trigger_strategy: "manual".
  2. Place a book on the platform, click "Capture" — confirm the status advances to "Front captured — flip it over" and a new row appears in catalogue.csv with a size_bucket value and populated front_wide_path / front_cropped_path columns pointing at real files under captures/<book_id>/.
  3. Flip the book, click "Capture" again — confirm status advances to "Book complete!" and the same row now has back_wide_path / back_cropped_path populated.
  4. Click "Next Book" — confirm status returns to "Waiting for book..." and the next capture gets a different book_id.
  5. Repeat with a small book and a large book — confirm size_bucket differs correctly between them, matching which physical markers each book actually covers.
  6. Switch trigger_strategy to motion_stop in config.json, restart, and confirm placing a book and holding it still triggers capture without pressing the button.
  7. Switch to stable_centered and repeat, confirming capture fires once the book is centered and steady in the guide box.

License

MIT — see LICENSE.