Skip to content

1. Backend /scan-frame — endpoint for client-side-warped crops ​

Published on 2026-05-12

First page using the new YYYY-MM-DD-NNN-slug.md + dated/numbered frontmatter convention. Older pages keep their unprefixed names (separate backfill task).

Context ​

The poc-tcg-collector Path #5 « Frame » plan (see Path #5 — alternatives considered) splits card detection (client-side, OpenCV on Android) from embedding (server-side, DINOv2 + pgvector). When the client has successfully isolated the card and warped it into a clean crop via a perspective transform, there is no need for the server to redo the bounds detection — it can embed the crop as-is.

This is what POST /scan-frame does, shipped in tcg-collector-backend#148 (merged 2026-05-12).

Key difference with /scan-lab ​

/scan-lab accepts an opt-in ?crop=1 flag (off by default) that triggers a server-side bounds detection via detectCardBounds before embedding. That detection is useful when the client uploads a raw photo, but it is counter-productive when the client uploads an already-perfect crop: the heuristic eats into the card instead of leaving it intact, and the resulting vector no longer matches what was indexed.

/scan-frame never runs a server-side crop. The contract is explicit: "the client has already warped, just embed it".

/scan-lab/scan-frame
Server-side cropopt-in (?crop=1)never
Embed pipelineDINOv2 → pgvector ANNDINOv2 → pgvector ANN
Mode-TCG re-rankyesyes
?tcg= filteryesyes
JSON responsesame shapesame shape (1:1)
Authpublic (POST allowlisted)public (POST allowlisted)

Why the same response shape ​

The mobile app fan-outs in parallel over 4 cells (Lab / Compat / Tormknd / Combined) since 2026-05-10. poc-tcg-collector PR 4b (upcoming) adds a 5th "Frame" cell that consumes the exact same JSON shape. Keeping a 1:1 contract between /scan-lab and /scan-frame makes A/B-testing trivial: same catalog, same indexing, same DB — the only variable is "client crop" vs "raw photo".

Implementation notes ​

  • New file src/api/src/scan/routeFrame.ts (~150 lines). The full pipeline reuses queryLab, projectCardDetails, toPgVector, LabMatchRow from routeLab.ts — now exported and shared. The pgvector SQL is not duplicated, so any future divergence will be contained to the handler envelope.
  • Wired in src/api/src/index.ts next to scanLabRoutes, gated by the same embeddingsConfig check (the endpoint shows up only when the embedding sidecar is online).
  • /scan-frame added to PUBLIC_POST_PATHS in src/api/src/auth.ts — explicit security review point, same as /scan and /scan-lab.
  • 186/186 existing tests pass, tsc --noEmit clean.

Known limitations ​

  • No dedicated e2e test in this PR — the available local fixtures are unwarped photos (designed for /scan-lab), so feeding them to /scan-frame would yield artificially low similarities. A real e2e test needs already-warped crops, which will fall out of the mobile OpenCV pipeline once poc-tcg-collector PR 4b lands.
  • No CHANGELOG.md update in the PR — per backend repo convention (CLAUDE.md), feature branches do not touch CHANGELOG; the entry lands on a separate docs/… branch.

Further reading ​