Quick start
Two parts. Part 1 gets the inference API answering. Part 2 gets a first curation result from a public sample. Not sure which you need? Read Choose your path first.
1. Core inference API
Install
curl -fsSL https://raw.githubusercontent.com/davidamacey/OpenProcessor/main/setup-openprocessor.sh | bash
This installs the latest published release into ./openprocessor/: no git
clone, no image build, no host Python. It asks which tiers you want,
picks GPUs, pulls digest-pinned images, exports the TensorRT engines inside
the containers, starts everything and runs a health check. First install
takes 30-60 minutes, mostly image pulls and TensorRT export.
| Tier | What you get |
|---|---|
core | Triton, the API (port 4603), OpenSearch |
curation | curation workers, PE-Core embeddings, evaluator (implies core) |
segmenter | SAM 3 segmenter (gated HuggingFace token) |
vlm | local vLLM serving a catalog model |
trainer | training service + MLflow |
cropwright | the Cropwright web UI |
Monitoring is a flag (--with-monitoring), not a tier. For no prompts:
curl -fsSL https://raw.githubusercontent.com/davidamacey/OpenProcessor/main/setup-openprocessor.sh \
| bash -s -- --tiers core,curation,cropwright --unattended
The installer checks every file against the release's SHA256SUMS (integrity,
not authenticity) and sizes the OpenSearch heap from your RAM (half of it, 2 to
30 GB). Cropwright is reachable on your LAN by default, for homelab or
small-business use. The API itself stays bound to 127.0.0.1. There is no login
on Cropwright — a warning is shown. Pass --local-only to opt out and keep
everything on 127.0.0.1.
Tier sizes, every flag, verifying the download by hand, network access, heap sizing, upgrade, rollback and troubleshooting: One-line installer.
Check that it works
cd openprocessor
./openprocessor health
Expected: the pretty-printed API /health JSON. status is ready and
both triton and opensearch report "ok": true:
{
"status": "ready",
"version": "0.6.0",
"api_version": "v1",
"services": {
"triton": {"ok": true, "detail": "is_server_live OK", ...},
"opensearch": {"ok": true, "detail": "HTTP 200", ...}
},
...
}
Then detect objects in any image:
curl -X POST http://127.0.0.1:4603/detect -F "image=@your-image.jpg"
Expected: JSON with a detections list. Coordinates are normalized to
0.0-1.0; multiply by the image width and height for pixels.
Day-to-day commands, all run from the install directory:
./openprocessor status # containers, health, GPU memory
./openprocessor logs api -f # live logs
./openprocessor sample coco # public COCO sample (200 images), used in part 2
Install from source
For development, or to build the images yourself:
git clone https://github.com/davidamacey/OpenProcessor.git && cd OpenProcessor
./scripts/setup.sh
scripts/setup.sh pulls the pre-built images, detects your GPU and picks a
profile (minimal, standard, full), writes .env (it never overwrites an
existing .env unless you pass --force, and a forced rewrite keeps your
OPENSEARCH_HEAP), downloads the base weights, runs the one-time TensorRT
export, starts the core services and runs smoke tests.
Useful flags (all from ./scripts/setup.sh --help):
| Flag | Effect |
|---|---|
--yes, -y | Accept defaults, no prompts |
--profile=NAME | Force minimal, standard or full |
--gpu=ID | GPU id to use (default 0) |
--skip-download / --skip-export / --skip-start | Skip that step |
--force | Overwrite an existing .env |
--curation | Print the curation next steps at the end |
Manage a checkout with make:
make up # core stack (Triton + API + OpenSearch)
make curation-up # the curation workers (compose profile "curation")
make status # health of all services
make down
./openprocessor in a checkoutIn a checkout the openprocessor CLI uses the compose project openprocessor
when .env does not name one (the .env that scripts/setup.sh writes does
not) and prints a warning; add COMPOSE_PROJECT_NAME=<name> to .env to
choose another. An install directory still refuses to run without it (exit
code 3). When you run docker compose by hand, always pass
-p <project> so you never act on another stack that uses the default name.
Prefer to do it by hand? See Model export and Compose profiles.
2. Curation with public sample data
No dataset ships in the repository and nothing proprietary is bundled. The
sample fetchers download small, license-filtered, deterministic subsets
of public datasets into the gitignored data/samples/ directory, from
pinned manifests under scripts/datasets/manifests/.
| Make target | What it fetches | Output |
|---|---|---|
make sample-coco-readme | 200 COCO 2017 images, 20 per class (about 1-2 minutes) | data/samples/coco_va_readme/ |
make sample-coco | 800 COCO 2017 images (10 classes, 80 per class) plus 12-image upload and 24-image post_promote side sets | data/samples/coco_va/ |
make sample-plates | 300 Open Images V7 "Vehicle registration plate" images with YOLO-format region labels | data/samples/oi_plates/ |
make sample-clean | Removes everything under data/samples/ |
COCO images are kept only under Attribution, Attribution-ShareAlike, "No
known copyright restrictions" or "United States Government Work" Flickr
licenses; Open Images images only under CC BY 2.0. Each fetch writes an
ATTRIBUTION.csv next to the images. Run
python3 scripts/datasets/fetch_coco_subset.py --help or
python3 scripts/datasets/fetch_openimages_plates.py --help for every flag.
Walkthrough
Curation data lives in projects: isolated
workspaces with their own classes, items, settings and jobs. A fresh stack
has one project, default. The walkthrough makes its own so nothing is
shared.
Docker commands below use -p openprocessor, the default compose project
name. If you set another COMPOSE_PROJECT_NAME in .env, use that name.
Step 1: make sure the encoders and workers exist
Curation needs the PE-Core encoders and the curation compose profile.
-
Installed directory. Nothing to do if you installed the
curationtier. Otherwise re-run the installer with--tiers core,curation. -
Source checkout.
scripts/setup.shdoes not build the PE-Core encoders:make export-peExpected:
pe_image_encoderandpe_text_encoderare READY inmake models-list.
Step 2: fetch a sample
make sample-coco-readme # source checkout
# or, from an installed directory:
./openprocessor sample coco
Expected: 200 images and an ATTRIBUTION.csv under
data/samples/coco_va_readme/. Both commands write there.
Step 3: let the containers see the sample
The API and the workers read images from one source root, mounted
read-only at /data/source. The host side is OP_SOURCE_ROOT_HOST in .env.
Three services mount it: api, curation-detection-worker and
curation-auto-label-worker. If one of them lacks the mount, thumbnails 404
and the VLM stage labels nothing.
-
Installed directory. The installer already set
OP_SOURCE_ROOT_HOST=./data, so the sample is at/data/source/samples/coco_va_readme/images. Skip to step 4. -
Source checkout. The default root is
./data/source. Point it at the samples directory, set the ingest detector, and recreate the services:cat >> .env <<'EOF'OP_INGEST_PRIMARY_DETECTOR_MODEL=yolov11_small_trt_end2endOP_SOURCE_ROOT_HOST=./data/samplesEOFmake up && make curation-upExpected:
docker compose -p openprocessor pslistsapi,curation-detection-workerandcuration-auto-label-workeras recently created. The sample is at/data/source/coco_va_readme/images.
restart does not re-read .env, so a plain restart keeps the old mount.
./openprocessor start and make up / make curation-up recreate the services
whose configuration changed.
Step 4: create a project and its classes
API=http://127.0.0.1:4603
P=coco-sample
curl -s -X POST "$API/curation/projects" \
-H 'content-type: application/json' \
-d "{\"slug\": \"$P\", \"display_name\": \"COCO sample\"}"
# Seed the classes from the detector's own labels. The default is a dry run;
# "dry_run": false writes. Or create your own with POST .../classes.
curl -s -X POST "$API/curation/projects/$P/classes/seed_from_detector" \
-H 'content-type: application/json' \
-d '{"dry_run": false}'
Expected: the project record, then a created list with one entry per
detector label (person, bicycle, car, and so on for all 80 COCO
classes).
Step 5: ingest
--folder is a path inside the API container, under /data/source. Use
the path from step 3.
SRC=/data/source/samples/coco_va_readme/images # installed directory
# SRC=/data/source/coco_va_readme/images # source checkout
docker compose -p openprocessor exec api python scripts/curation/bulk_ingest.py \
--project $P --folder $SRC
The script shows live progress and exits 0 when the run completes, 1 when some images failed, 2 when the run failed.
Check the counts:
curl -s "$API/curation/projects/$P/stats"
Expected: counts.images reaches 200 and counts.items is higher (one item
per detection). counts.pending_detection drops to 0 once the detection
worker has finished.
To ingest a large folder from a NAS, see Ingest a large folder.
Step 6 (optional): cluster and label
The curation profile is already up. Start an auto-label run:
curl -s -X POST "$API/curation/projects/$P/pipeline/auto_label/start?run_vlm=true&train_clusters=true"
curl -s "$API/curation/projects/$P/pipeline/auto_label/status"
Expected: a job id, then status moving to completed. auto_label
clusters only by default (run_vlm=false). The VLM labels nothing unless you
pass run_vlm=true and the project has activated a VLM endpoint, or you name one
with vlm=<name> (see VLM selection). A new project has
none, so the request above is refused with 409 vlm_not_configured until you activate
one; drop run_vlm=true to cluster without a VLM.
Things that trip people up
- Every curation data route is under
/curation/projects/{project}/. An unscoped path such as/curation/classesis a404. - The detector stores everything it sees. A stock detector's whole label
space (all 80 COCO classes) becomes unlabeled item proposals, each carrying
the detector's own label (person, dog, pizza and so on, not only vehicles).
To store or embed less, set the per-project ingest policy
(
PUT /curation/projects/{project}/ingest/policy) before a big ingest; the oldOP_INGEST_PRIMARY_CLASS_IDSenv var is retired and ignored. See Ingest policy. - Cost of the full vocabulary. Each stored detection also gets an
embedding by default, so the full COCO vocabulary stores about 3.6x the
data of a vehicle-only run (the "Ingest cost per image" section of
docs/PERFORMANCE.md). - Switching detectors. Set
OP_INGEST_PRIMARY_DETECTOR_MODEL(andOP_INGEST_PRIMARY_LABELS_PATHif the model directory has nolabels.txt) in.env, then recreateapi. The model must be loaded in Triton and serve the end2end four-tensor output. The detector is deployment-wide. - Ingest returns
503untilOP_INGEST_PRIMARY_DETECTOR_MODELis set and that model is loaded in Triton. The installer sets it; a checkout does not. OP_SOURCE_ROOT_HOSTis the host side of a fixed/data/sourcemount. The walker's--foldermust be a container path under it, otherwise every image fails asunservable_path.OP_*variables are read at process start. Recreate the services after changing them (./openprocessor startormake up); a restart is not enough.GET /curation/projects/{project}/healthreportsdegradeduntil at least one class exists and Triton and OpenSearch are reachable.
Bring a labeled dataset instead
If you already have a YOLO or COCO dataset, import it rather than re-labeling. Classes are matched to the project's classes by name, and the import can be resumed or undone:
make sample-coco-import # public COCO fixture, four layouts under data/samples/import_fixture
docker compose -p openprocessor exec api python scripts/curation/import_labeled_dataset.py \
--project $P --dataset /data/source/import_fixture/yolo \
--accept-suggestions --dry-run
The dataset path is a container path under the source root, as in step 3
(/data/source/samples/import_fixture/yolo for an installed directory).
Drop --dry-run to import. See Dataset import.
Regions: the car-to-wheel example
A region is a sub-object found inside each item, such as a wheel inside a car, with its own boxes, states and (optionally) text. The public car-to-wheel example walks through a region profile, a prompt pack, VLM verification and multi-box review on public COCO images; see the wheel example.
The Open Images plates set (make sample-plates) is region ground
truth (whole frames with a single region class), not item classes. Index
its images with scripts/curation/import_labeled_dataset.py --images-only
so the labels never touch the item registry, activate a region profile
(see Region profiles), let the detection
worker's cascade run, then score it with
scripts/curation/eval_regions_vs_gt.py.
Continue with the end-to-end curation workflow.