A cross-platform (Windows + Linux + macOS) desktop app that drives a machine which sorts spent brass cartridge casings by headstamp. A camera photographs each case, an image classifier predicts the headstamp stamped on its base, and a serial-connected sorting machine drops the case into the correct bin.
This is the full-parity Python/Qt version of the original Windows-only WinForms application, intended to eventually replace it. It runs fully offline — signing in to the community is optional and only unlocks model sharing/downloads.
⚠️ Scope & safety. This software sorts inert, already-fired brass cases by their stamped markings. It is not a firearm, not a munition, and contains no load data. It also commands real motors and a drop mechanism over a serial link — moving machinery has pinch points and electrical hazards. Run it on hardware at your own risk and keep hands clear during operation. Provided as-is, with no warranty (see LICENSE).

The case sorter is built from a few separate repositories. This repo is just the desktop software.
| Project | What it is | Link |
|---|---|---|
| AI Case Sorter (this repo) | The cross-platform desktop app: capture, classify, route, train, evaluate. | — |
| CS7.2 hardware | 3D-printable models, build kits, assembly guides, and the Arduino-based firmware the app talks to over serial. | AI-Case-Sorter-CS7.2 |
| CaseSorter AI Server | A small local HTTP server that hosts your trained ConvNeXt models behind an OpenAI-compatible API. This is what AI Config mode points at. | AI-Case-Sorter-Server |
| Community backend | Hosted service at reloadingrecipes.com for sign-in, model sharing/downloads, and the feedback loop. A separate hosted service — not part of this open-source release. | reloadingrecipes.com |
You do not need an account to use the app. Everything except community sharing/downloads works locally and offline.
The app can predict a headstamp in one of two modes:
POST /v1/chat/completions). Point it
at a local CaseSorter AI Server
(default http://localhost:8000) to run inference against your own trained
models with no GPU drivers on the client.The window is an activity sidebar down the left, a working area, and movable panels you open when you want them.
F1, and it follows you between screens), and a theme picker. Drag
them where you want; View → Re-dock panels puts them back.bootstrap.py
itself — it doesn’t need to be the app’s own Python. That one is provisioned
separately by uv, which the launch scripts
install automatically on first run if it isn’t already present.No git, no Python, no terminal. Download install-windows.bat and
install-windows.ps1 from installer/ into the same folder
and double-click the .bat.
It installs Python if you don’t have it, puts the app in
%LOCALAPPDATA%\Programs\CaseSorter (per-user — no admin rights), and adds a
Start Menu entry. First launch installs dependencies and takes a few minutes.
Updates happen inside the app. When a new release is out, the status bar
shows Update available; once it has downloaded it changes to Restart to
update, and the update is applied the next time you start. You never need to
re-run the installer. Full details in installer/README.md.
The installer is unsigned, so Windows SmartScreen will warn the first time. Choose More info → Run anyway.
The launch scripts install uv if it isn’t already on your machine, use it to fetch the right Python version and sync dependencies from the committed lockfile, then launch the app — all in one step, every time.
Linux / macOS
git clone https://github.com/sjseth/AI-Case-Sorter-Py.git
cd AI-Case-Sorter-Py
./start.sh
On Linux the script may offer to install a few system libraries via sudo —
the one thing uv genuinely can’t provision, since they’re system libraries
rather than Python packages. Pass --auto (or set AUTO_INSTALL=1) to
confirm automatically; it prints a notice first.
| Library | Needed by | Debian/Ubuntu | Fedora | Arch |
|---|---|---|---|---|
| libGL | OpenCV | libgl1 |
mesa-libGL |
libglvnd |
| glib | OpenCV | libglib2.0-0 |
glib2 |
glib2 |
| libxcb-cursor | Qt’s X11 plugin | libxcb-cursor0 |
xcb-util-cursor |
xcb-util-cursor |
The first two are required — the app can’t start without them. The third is not: without it Qt falls back to Wayland, where a floating panel can’t be moved or resized. The launcher only asks about it when you have a display, so a headless run is never prompted.
Windows
git clone https://github.com/sjseth/AI-Case-Sorter-Py.git
cd AI-Case-Sorter-Py
start.bat
Prefer to drive uv yourself, or need to run under a debugger? See
CONTRIBUTING.md — the flags matter, and
getting them wrong makes the app misreport its own version.
No sorter attached? In Settings → Serial choose the Emulated port. The
emulator mirrors the real board’s protocol so you can exercise the run loop, the
UI, and most workflows without any hardware.
Local training and local inference need PyTorch. The app will offer to install it
for you (the Install PyTorch dialog), or you can install the ml extra
yourself:
uv sync --extra ml # torch + torchvision
uv sync --extra ml instead resolves
from PyPI, whose Linux build is CUDA 13 (driver R580+) and whose
Windows build is CPU-only. For GPU use, prefer the in-app dialog — or
install the exact torch==… / torchvision==… pins from
pyproject.toml’s [ml] extra yourself from the matching index:
uv pip install --index-url https://download.pytorch.org/whl/cu129 on
Linux, …/whl/cu130 on Windows.Everything the app writes — trained models, training images, settings — lives in one folder, outside the app directory:
| Platform | Location |
|---|---|
| Windows | %LOCALAPPDATA%\CaseSorter |
| Linux | ~/.local/share/CaseSorter (or $XDG_DATA_HOME/CaseSorter) |
| macOS | ~/Library/Application Support/CaseSorter |
<data folder>/
├── config/ casesorter.db (settings/models/headstamps) + msal_cache.bin (token cache)
├── models/<id>/ images · run_images · feedback_images · reports · trainedmodel
└── updates/ staged app update, applied on next launch
Keeping it separate is what makes updating safe — the updater replaces the app folder, and nothing of yours is in it. Delete the folder to reset all state.
Upgrading from an older version? If your data is still in data/ in the
app folder, it’s moved to the new location automatically the first time you
run the app. Nothing to do.
Overrides:
CASESORTER_DATA_DIR to put the data anywhere you like.portable.txt next to bootstrap.py to keep data in
<app>/data instead — for USB-stick or fully self-contained installs.git pull as usual, then just launch the
app again — ./start.sh / start.bat always run whatever’s currently on
disk, so a pull that changes bootstrap.py, start.sh/start.bat
themselves, or uv.lock (new/updated dependencies) takes effect on the
very next launch with nothing extra to run. The in-app updater is still
available, but a source checkout is normally managed with git.CASESORTER_UPDATE_DISABLED=1, or the
checkbox in the update dialog.uv run pytest # ~500 tests covering the non-UI logic
uv run syncs dependencies (including the dev group, which is where pytest
lives) from the committed lockfile before running, so there’s no separate
install step. CI (.github/workflows/build.yml) runs the same suite across a
Python version matrix on every push and PR.
Please run pytest before opening a PR. Most of the UI isn’t covered by automated
tests — smoke-test UI changes by running the app. See CONTRIBUTING.md
for setup and guidelines, and SECURITY.md to report a vulnerability.
The community client talks to https://www.reloadingrecipes.com/api and
verifies TLS normally. To develop against a local copy of that backend, copy
.env.example to .env (next to bootstrap.py, or in
data/config/) and set:
| Variable | Purpose |
|---|---|
CASESORTER_API_BASE |
Base URL of the community API, e.g. https://localhost:7043/api. |
CASESORTER_API_CA_BUNDLE |
PEM cert/bundle to trust — the right way to make a local HTTPS dev server verify. |
CASESORTER_API_INSECURE |
1 skips TLS verification. Honoured only when the API base is localhost, so it can’t weaken production traffic. |
Real environment variables take precedence over the .env file, and .env is
gitignored. For an ASP.NET Core dev server, export its certificate with
dotnet dev-certs https --export-path devcert.pem --format PEM --no-password
and point CASESORTER_API_CA_BUNDLE at it.
CONTRIBUTING.md — how to set up, run the tests, and submit
changes.CLAUDE.md — architecture map for contributors and AI coding
assistants (layers, event bus, module reference, UI surfaces, data layout).RELEASING.md — how a release gets cut, for maintainers.A first run, start to finish — every screen referenced here is described in
Features above, and the full application guide is published at
https://sjseth.github.io/AI-Case-Sorter-Py/ (Getting
Started,
the User Guide — also F1 inside the app — and the
whole thing as a PDF).
Launch it. ./start.sh / start.bat / the Windows installer’s Start
Menu entry — see Install & run. First launch takes a
couple of minutes while dependencies sync; every launch after that is
fast.
No hardware yet? Skip straight to the emulator. In Settings →
Serial, set the port to Emulated. It mirrors the real board’s protocol,
so everything below — camera, classification, sorting — works the same
with no sorter or camera attached, aside from what the camera itself
would show.
Connect a camera. Settings → Camera lists detected devices; pick one and confirm you get a live preview. If casings aren’t cropping cleanly, Settings → Image Processing tunes the Hough-circle detection and primer mask against a captured frame, with a before/after preview.
Connect the sorter (skip if using the emulator). Settings → Serial connects to the board, exposes its init settings, and has a sort-arm test to confirm slots move correctly before you feed it real cases.
Assign headstamps to slots. On the Sort screen, each slot card lists the headstamps that route to it — check the ones you want, per slot. Assignments are saved automatically as sorting templates, so you can switch between different bin layouts (e.g. “range brass” vs. “match prep”) for the same model from the template dropdown, without re-checking boxes each time.
Test before you commit hardware to it. Test once on the Sort screen feeds and classifies a single case without moving the sort arm or motors — confirms the whole pipeline (camera → crop → classify) end to end. Manual feed does one real feed-and-sort cycle. Start runs the full continuous loop.
Watch it work. The Sort screen’s live slot grid updates per-headstamp counts as cases are sorted; the Classification History panel shows a running tile grid of recent classifications with a colour trail. Anything below your confidence floor routes to the catch-all slot instead of guessing.
No hardware, no camera, nothing installed yet? Steps 2 and 5 (emulator + AI Config against a friend’s or your own server) are enough to explore the whole app with zero physical setup.
Copyright (C) 2026 SJSeth Solutions
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. The full text is in LICENSE, or see https://www.gnu.org/licenses/.
Part of the SJSeth AI Case Sorter ecosystem. The hardware, firmware, and build guides live in the CS7.2 repository; the local model host lives in AI-Case-Sorter-Server.