No capture button, no per-frame API spam: one paid API call per distinct card, not per frame. All the vision runs on your machine; a single cropped JPEG leaves it each time a genuinely new card appears.
Quick start
uv tool install "cardstream[client,onnx]" # or: pip install "cardstream[client,onnx]"
export XIMILAR_API_KEY=... # the one environment variable anything here reads
cardstream-web # browser UI on your webcam → http://127.0.0.1:8001
Hold a card up. The overlay shows the state machine thinking and the match when it lands.
You will also need the model weights — they are too large to ship in the wheel.
The installer fetches them for you; a source checkout needs them
dropped into model/ (see Model weights).
Headless, for rigs and streams:
cardstream-client --source 0 # webcam, results to the terminal
cardstream-web --source rtsp://user:pw@cam/stream1 # pull a camera
cardstream-web --source rtmp://0.0.0.0:1935/live --listen # OBS pushes here
Why it is cheap
Identification is a paid call of roughly half a second, so cardstream never makes one per frame. A per-card state machine driven by free, entirely local signals decides when a fresh call is actually warranted:
┌─────────────────────── frame ───────────────────────┐
▼ │
1. detection (throttled) card bbox + deskewed crop │
│ no card → EMPTY │
▼ │
2. MotionGate mean frame-diff; low for N frames → SETTLED
│ still moving → MOVING │
▼ │
3. identity gate pHash Hamming / embedding cosine → NEW card?
│ same card → IDENTIFIED (re-emit cached, 0 calls) │
▼ │
4. identify (async/thread, debounced) → IDENTIFIED + result ────┘
Detection itself is throttled in three tiers by the free motion gate (moving / static-with-card / empty heartbeat). The net effect is one identify call per distinct card presentation, regardless of frame rate. Hold a card still and it costs nothing more. Swap cards and it costs exactly one call.
| Stage | Where it runs | Cost |
|---|---|---|
| Frame capture | browser webcam, or a pulled source | free |
| Motion gate | local (mean frame-diff) | free |
| Card location | local: detection (RF-DETR / RT-DETRv2) or segmentation (RF-DETR) | free |
| Same-card identity gate | local (embedding cosine, or pHash) | free |
| Identify lookup | Ximilar collectibles/v2/*_id |
paid, once per distinct card |
Everything above the last row is free and local. Nothing but that one crop leaves the process — there is no service in the path.
Model weights
model/ is gitignored, so a fresh checkout has no weights and the CLI says so
at startup until you supply them. The installer and the Docker image fetch them
for you.
| Weight | Flag | Required | Download |
|---|---|---|---|
| segmentation — finds the card's outline, so the crop is deskewed and cut tight | --segmentor-model |
yes — the default locator | cardstream-segmentation-v1.tar.gz (108 MB) |
| similarity — the embedding the identity gate compares crops with | --embed-model |
yes by default (--gate phash needs none) |
cardstream-similarity-v1.tar.gz (18 MB) |
| detection — finds a bounding box instead | --detector-model |
no — opt-in, replaces the segmentor | not published — export your own (see model/README.md) |
| tracking — carries the box between detections | --tracker-model |
no — opt-in | vitTracker from the OpenCV zoo |
Each tarball unpacks to a flat folder (segmentation_model/,
similarity_model/) with the .onnx inside; a .sha256 file sits next to
each one on the release page.
Full details, download table and each export's I/O contract:
model/README.md and the
model zoo.
Install
The quick way (Linux/macOS — a venv in ~/.cardstream, shims in
~/.local/bin, model weights included):
curl -fsSL https://raw.githubusercontent.com/Ximilar-com/cardstream/main/scripts/install.sh | sh
cardstream-web --version
From PyPI. Extras are independent and ML imports are lazy, so any subset installs — but locating a card always needs a model, so pick a backend:
pip install "cardstream[client,onnx]" # ONNX Runtime — what the shipped weights use
pip install "cardstream[client,torch]" # torch/transformers backends instead
From a checkout:
git clone https://github.com/Ximilar-com/cardstream && cd cardstream
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[client,onnx]' --group dev
or in one step with the smoke checks: ./scripts/build-from-source.sh
(--models also fetches the weights).
Docker. Read the security header in the Dockerfile before publishing any
port:
docker build -t cardstream .
docker run --rm -e XIMILAR_API_KEY -p 127.0.0.1:8001:8001 -v cardstream-models:/models cardstream
Releases carry the wheel, sdist and SHA256SUMS, which install.sh verifies.
Common recipes
cardstream-web --game "One Piece" --alphabet latin # prefill: faster, more precise
cardstream-web --set-code PBL # restrict matching to one set
cardstream-web --price-stats # USD market prices with every match
cardstream-web --camera-width 3840 # more pixels to identify from
cardstream-web --store-images crops/ # keep every crop you paid for
cardstream-web --show-detection --debug # see what was located, and why
cardstream-web --gate phash # zero-ML identity gate
cardstream-client --source card.jpg --loop # a still image, on repeat
Set --alphabet whenever you set --game. Prefilling the game switches
off the endpoint's own writing-system detection, and it then assumes latin — so
a Japanese card comes back as its English print. Leave both unset and the
endpoint works them out itself, which is why neither is defaulted.
The shipped defaults are the tuned configuration: make prod passes no
flags at all and runs the same pipeline make dev spells out flag by flag.
Documentation
| CLI reference | every flag and its default, generated from the parser |
| Tuning | call economics, detection filters, resolution, the settings dialog |
| Locating the card | detection vs segmentation, expansion, visual tracking |
| Frame sources | webcams, files, RTSP/RTMP/SRT, --listen |
| Model weights | the model zoo and each export's contract |
| Architecture | the internals map, for contributors |
Verify the no-spam behaviour
Run with --debug and watch the identify lines: they appear only on a new-card
transition. Hold one card steady → exactly one. Swap to another → one more. Run
at 10–15 fps throughout → still only those. The header's N calls badge counts
the same thing. That is the whole point.
Development
make venv # .venv with the client, onnx and dev dependencies
make check # lint + typecheck + both test suites — what CI runs
make dev # the tuned run plus diagnostics
The suite is entirely offline: no API key, camera, network or model files. See CONTRIBUTING.md.
Limitations
- Your Ximilar API key lives on the machine running the client, and the
local web app is unauthenticated. That is the direct consequence of having no
service in the path.
--hostdefaults to127.0.0.1— keep it there. See SECURITY.md. - Detector boxes are axis-aligned, so an angled card's crop carries
background wedges.
--segmentor-model— the default — is the answer: a mask gives four corners and the crop is deskewed and cut tight. - One card per frame. The locator picks the best-scoring candidate.
- The tracker is presence, not identity. An in-place card swap keeps its score high, so it never replaces the embedding gate's same-vs-new decision.
- A game prefill costs alphabet detection — see the warning above.
--listenand--ffmpegneed the systemffmpegbinary.
Licence
Apache-2.0. Every model architecture and runtime weight here is Apache-2.0 too — see NOTICE. Identification is performed by the Ximilar collectibles API, a paid hosted service.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cardstream-0.3.0.tar.gz.
File metadata
- Download URL: cardstream-0.3.0.tar.gz
- Upload date:
- Size: 111.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74ab2941450bb0909d3d7f778f9e282d8c1e4b37a429c55f7bedea5d2311a3a0
|
|
| MD5 |
4f3c9b63bf2bda16b2e32dfbe088eab7
|
|
| BLAKE2b-256 |
712dbc5df530d7c09e5a66862ac0bf7876d65abf5e97e6d2587d6e09215a9adc
|
Provenance
The following attestation bundles were made for cardstream-0.3.0.tar.gz:
Publisher:
release.yml on Ximilar-com/cardstream
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cardstream-0.3.0.tar.gz -
Subject digest:
74ab2941450bb0909d3d7f778f9e282d8c1e4b37a429c55f7bedea5d2311a3a0 - Sigstore transparency entry: 2699150954
- Sigstore integration time:
-
Permalink:
Ximilar-com/cardstream@e30042aa4d9bfd8a7e41ab583c261a8db42040cb -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/Ximilar-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e30042aa4d9bfd8a7e41ab583c261a8db42040cb -
Trigger Event:
push
-
Statement type:
File details
Details for the file cardstream-0.3.0-py3-none-any.whl.
File metadata
- Download URL: cardstream-0.3.0-py3-none-any.whl
- Upload date:
- Size: 126.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c06acfeafdd36062d15b52d4e50d644e35e0e34ae9e698e29a83331790ca99c
|
|
| MD5 |
efca48ade3b7aaee366f3b37ef10c850
|
|
| BLAKE2b-256 |
62f60540c9bfc42ec371a7eefb5581af8e2214c9969290b0125f95c56ec809de
|
Provenance
The following attestation bundles were made for cardstream-0.3.0-py3-none-any.whl:
Publisher:
release.yml on Ximilar-com/cardstream
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cardstream-0.3.0-py3-none-any.whl -
Subject digest:
6c06acfeafdd36062d15b52d4e50d644e35e0e34ae9e698e29a83331790ca99c - Sigstore transparency entry: 2699151032
- Sigstore integration time:
-
Permalink:
Ximilar-com/cardstream@e30042aa4d9bfd8a7e41ab583c261a8db42040cb -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/Ximilar-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e30042aa4d9bfd8a7e41ab583c261a8db42040cb -
Trigger Event:
push
-
Statement type: