The Argus suite's web UI and compose orchestration. The main page demonstrates argus-lens structured image captioning — paste a URL, drag in images, pick a server-side folder, or caption straight from an Immich album — and inspects training-optimised caption variants, raw model outputs, and auto-removed tag analysis. A curator view at /curate talks to argus-curator for dataset ingestion, folder scans, and exports. Upstream acquisition (argus-quarry) runs as a compose profile.
Formerly
argus-vision-demo— the old GitHub URL redirects here.
Designed as a living onboarding document -- every parameter includes an inline explanation of what it does and why.
The suite repos are designed to run together but stay loosely coupled — bring up only what you need with compose profiles. By default the backends run from their published GHCR images, so no sibling checkouts are needed:
git clone https://github.com/smk762/argus-studio # this repo
cd argus-studio
cp .env.example .env # set DATASET_DIR / OUTPUT_DIR, choose UI mode, etc.Then, from this repo root (frontend builds locally; the backends pull GHCR images):
docker compose up # frontend only (demo mode, no backend)
docker compose --profile curator up # frontend + argus-curator
docker compose --profile lens up # frontend + argus-lens
docker compose --profile gallery up # argus-quarry: acquire PD/CC0 images -> DATASET_DIR
ARGUS_CURATOR_UI_MODE=live \
docker compose --profile forge up # frontend + curator + argus-forge (training configs)
docker compose --profile proof up # frontend + argus-proof (post-training LoRA evaluation)
docker compose --profile atlas up # frontend + argus-atlas (fantasy-map bakes from real terrain)
docker compose --profile full up # whole suite (pull + run GHCR images)
docker compose --profile full pull # refresh the images to the current *_TAGPin a specific released version with the *_TAG vars in .env
(CURATOR_TAG / LENS_TAG / QUARRY_TAG / FORGE_TAG / PROOF_TAG / ATLAS_TAG, default latest).
Build from local source instead — clone the sibling repos and layer the build
override, which re-adds each backend's build: context:
git clone https://github.com/smk762/argus-lens ../argus-lens
git clone https://github.com/smk762/argus-curator ../argus-curator
git clone https://github.com/smk762/argus-quarry ../argus-quarry # gallery profile
git clone https://github.com/smk762/argus-forge ../argus-forge # forge profile
git clone https://github.com/smk762/argus-proof ../argus-proof # proof profile
git clone https://github.com/smk762/argus-atlas ../argus-atlas # atlas profile
docker compose -f compose.yaml -f compose.build.yaml --profile full up --build| Profile | Services started | Use it for |
|---|---|---|
| (none) | frontend | Public captioning + read-only /curate demo |
curator |
frontend + argus-curator | Scanning / exporting datasets |
lens |
frontend + argus-lens | Captioning against a running engine |
gallery |
argus-quarry (acquisition job) + argus-quarry-server | Acquiring PD/CC0 images with provenance into DATASET_DIR, browsable at /gallery |
forge |
frontend + argus-curator + argus-forge | Turning /curate exports into ready-to-run LoRA training configs (kohya / OneTrainer / diffusers) at /forge. Set ARGUS_CURATOR_UI_MODE=live for the export flow that hands off to it; /forge also takes an export dir typed by hand |
proof |
frontend + argus-proof | Post-training LoRA evaluation (epic): review scored eval runs at /proof — pass/fail verdict, per-metric scores, and a keyboard-first HITL review (5-star + structured reject reasons, blind mode) against argus-proof on :8104 |
atlas |
frontend + argus-atlas | Baking a georeferenced fantasy-map overlay from a DEM at /atlas: GDAL controls, a ComfyUI restyle (off unless ATLAS_ALLOW_BAKE=1), the alignment gate, and a PMTiles archive, against argus-atlas on :8105 |
full |
frontend + curator + lens + quarry server + forge + proof + atlas | End-to-end acquire → curate → caption → forge → evaluate → bake (set ARGUS_CURATOR_UI_MODE=live) |
argus-quarry (the gallery profile) is the upstream producer: it fetches
public-domain / CC0 images from open archives with full provenance and licence
records, grouped into LoRA-training categories (identity / wardrobe /
setting / concept), and publishes a curator-ready <category>/<subject>/
tree into DATASET_DIR. Run it first, then curate the published images:
docker compose --profile gallery up # fetch -> pool -> publish DATASET_DIR
docker compose --profile curator up # then scan/curate on /curateNVIDIA GPUs (optional, needs the NVIDIA Container Toolkit): layer the override so the base stack still runs on CPU-only machines.
docker compose -f compose.yaml -f compose.gpu.yaml --profile full upThe only cross-service coupling is the curate → caption handoff: curator and lens
share the dataset at /data/images (DATASET_DIR) so lens can read the manifest's
abs_path entries and write .txt sidecars. The curator calls lens
server-to-server at the compose service DNS name (http://argus-lens:8100), not
localhost. Each service still runs perfectly on its own.
You can also run any piece outside Docker. Start the argus-lens server (in a separate terminal):
# In the argus-lens repo (PyPI install)
pip install argus-lens[server,local]
argus-lens serve --cors --port 8100If you are developing argus-lens locally, rebuild the wheel and reinstall into the same environment you use for serve (the demo always talks to whatever is running on ARGUS_LENS_URL). Targets use uv so installs work on PEP 668 (externally managed) system Pythons:
cd ../argus-lens
uv venv # once: create .venv in the repo
source .venv/bin/activate
uv build
uv pip install --force-reinstall "$(ls -t dist/*.whl | head -1)[server,local,cli]"
argus-lens serve --cors --port 8100Or use an editable install while hacking Python: uv pip install -e ".[server,local]" from the argus-lens repo (no wheel step).
The caption page has five input modes (drag-and-drop works anywhere on the page —
images land in Upload, a .jsonl lands in Curate manifest):
-
Single URL — paste an image URL for one structured caption (
POST /caption/url). -
Upload — drag-and-drop (or browse) images from your machine; they stream through
POST /caption/streamwith per-image progress. A single image gets the full variant breakdown; multiple images run as a batch. -
Local folder — batch-caption every image in a server-side folder (
POST /caption/folder), writing a.txtsidecar next to each image. The folder picker browsesGET /folders, which requires a source root:argus-lens serve --cors --port 8100 --source-root /path/to/images # or: LENS_SOURCE_PATH=/path/to/images argus-lens serve --corsIn the suite compose this is preset to
/data/images(DATASET_DIR). You can also type a path manually if browsing is disabled. -
Immich — batch-caption an album on your Immich photo server (
POST /immich/caption/stream), optionally writing each caption back to the asset's description in Immich. RequiresIMMICH_URL/IMMICH_API_KEYon the argus-lens server. -
Curate manifest — upload a
manifest.jsonlproduced by/curate(POST /caption/manifest); each row'starget_profileis applied per image. Images must be reachable at theirabs_path(the shared/data/imagesmount).
What a result contains (argus-lens ≥ 0.6): one caption per category (identity, wardrobe, pose_composition, setting, lighting, action), each leading with its own category, plus training (tag-style, for SDXL/CLIP), training_prose (sentence-style, for Flux/SD3/T5) and zeroshot. Every variant passes a coherence post-check, so dangling fragments, "The image shows…" openers and hedging are repaired before display. metadata.subjects carries a headcount (count, multiple, agree), so multi-person images can go to face identification in argus-curator. metadata.buckets carries the per-category tags behind each caption. When lens runs with ARGUS_REFINE_MODEL, metadata.refine records which variants an LLM rewrote and why any rewrite was rejected.
The caption page shows training_prose directly under training, and the headcount above the variants: a multi-person image, or one where the tags and prose disagree on the count, is flagged for face ID in /curate. Against an older lens server neither appears.
WD14 tags + an Ollama VLM for prose. In the suite compose, set ARGUS_BACKEND=hybrid:wd14+openai-compat with ARGUS_OPENAI_COMPAT_BASE_URL / ARGUS_OPENAI_COMPAT_MODEL (e.g. http://<ollama-host>:11434/v1, qwen3-vl:8b). The URL is reached from inside the lens container, so use the LAN host's IP, or host.docker.internal for an Ollama on the compose host. The other lens settings are in Configuration.
The curator UI calls ARGUS_CURATOR_URL (default http://localhost:8101). Run the FastAPI app from argus-curator in another terminal.
In live mode the page also offers:
- Add images to dataset — drag-and-drop images into a folder under the shared dataset (
POST /uploadon the curator), or pull an Immich album into it via argus-lens (POST /immich/pull); the target folder is pre-filled for scanning. - Recent scans — reopen a persisted scan (
GET /scan/{scan_id}) without rescanning; history is kept in the browser. - Detector badges — what the curator backend can actually do (
GET /detectors: torch / cuda / clip / faces / onnx), so greyed-out options explain themselves. - Forge training config — after export (and captioning), argus-forge turns the export into a ready-to-run LoRA config for kohya sd-scripts, OneTrainer, or diffusers (
POST /configonARGUS_FORGE_URL, defaulthttp://localhost:8103), seeded from the same selection-insight heuristics the panel displays. Demo mode offers a client-side kohya TOML download instead. Two caveats when forge runs in Docker: the generated configs reference container paths (/data/out/…), so substitute yourOUTPUT_DIR(and prefercopy-mode exports oversymlink) when runningtrain.shon the host; and if you run forge outside compose, start it withargus-forge serve --corsso the browser can reach it.
A view over argus-quarry's provenance database (ARGUS_QUARRY_URL, default http://localhost:8102): pool stats, licence/source/category/subject filters, thumbnails, and a per-image provenance card (source page, photographer, attribution, SHA256). Every photo links straight into /curate with its published subject folder preselected. Run the server with the gallery profile, or standalone:
pip install "argus-quarry[server]"
argus-quarry serve --cors --port 8102Sources (the second tab on /gallery, or /gallery?view=sources) browses two large public-domain datasets without downloading them. PD12M has 12.4M CC0 / Public Domain Mark images from 640 institutions. Megalith-10m has 9.58M Flickr photos marked public domain, CC0, Flickr Commons or US Gov. Quarry searches a local index of each dataset's metadata (links, captions, sizes), and thumbnails come straight from where each archive hosts its images. Narrow the matches by caption words, institution or Flickr account, licence, minimum size and orientation, and optionally tick individual images. Then name a category and subject folder and land that slice in the pool through quarry's normal ingest (licence gate, SHA256 dedup, provenance).
- With
QUARRY_ALLOW_FETCH=1on the quarry server, Fetch into the pool does it in place and streams progress. It is a cross-origin POST, so also setQUARRY_CORS_ORIGINSto the studio origin(s) (argus-quarry ≥ 0.3.1). - Otherwise (the default), Download slice.json saves the slice. Land it on the quarry host with
argus-quarry archives fetch slice.json.
Setup, once (this needs argus-quarry with the archives extra, which the image includes):
# in the argus-quarry repo: archive the metadata (~45 GB incl. PD12M's CLIP embeddings; no photos)
HF_TOKEN=hf_... uv run scripts/fetch_public_domain_metadata.py --root /media/photos/public_domain
# in .env here: PUBLIC_DOMAIN_DIR=/media/photos/public_domain (optionally QUARRY_ALLOW_FETCH=1), then
docker compose run --rm argus-quarry-server archives index # builds the search index under QUARRY_HOMEPyPI install:
pip install "argus-curator[server,gpu]"
argus-curator serve --cors --port 8101Local wheel (after uv build or hatch build in the argus-curator repo — you should see something like dist/argus_curator-0.1.1.dev2+g…-py3-none-any.whl):
cd ../argus-curator
source .venv/bin/activate # or: uv venv && source .venv/bin/activate
uv build
WHEEL="$(ls -t dist/*.whl | head -1)"
uv pip install --force-reinstall "${WHEEL}[server,gpu]"
argus-curator serve --cors --port 8101Use "${WHEEL}[server]" instead of [server,gpu] if you only need the HTTP API without optional GPU detectors and embeddings. Editable alternative: uv pip install -e ".[server,gpu]" from the argus-curator repo.
From this repo root (activate the same Python venv you use for argus-curator serve), reinstall the newest wheel without cd by pointing at the sibling dist/ (build in argus-curator first so dist/*.whl exists):
WHEEL="$(ls -t ../argus-curator/dist/*.whl | head -1)"
uv pip install --force-reinstall "${WHEEL}[server,gpu]"Then launch the demo frontend:
# Docker (recommended)
cp .env.example .env
docker compose up # add --build to rebuild the frontend after source changes# Or local dev
cd frontend
npm install
npm run devOpen http://localhost:3000 (captioning) or http://localhost:3000/curate (curation).
Review post-training LoRA evaluation runs from argus-proof (ARGUS_PROOF_URL, default http://localhost:8104). A run browser lists scored runs; the selected run shows its pass/fail verdict, group-collapsed pass-rate, per-metric means (identity / adherence / quality / preference / safety), diversity, and scorer provenance. Below it is the human-in-the-loop review: a keyboard-first grid (1–5 rate, 0 clear, arrows move) with a 5-star rating and a structured, multi-label reject taxonomy (deformation / decoherence / ID-not-applied / …) captured as structured codes, not free text. Blind mode hides each sample's checkpoint/weight/epoch/seed and randomises order to remove expectation bias, revealing provenance after you rate; the borderline (needs-review) band is surfaced first via the automated pre-pass. Saving folds the ratings + reasons + rater id back into the report and recomputes the verdict (POST /report/{id}/hitl). Demo mode reviews a bundled sample report with no backend; run the server with the proof profile, or standalone:
pip install "argus-proof[server]"
argus-proof serve --cors --port 8104Bake one georeferenced fantasy-map overlay from real terrain with argus-atlas (PyPI; ARGUS_ATLAS_URL, default http://localhost:8105). It is the stage after proof: a cartography LoRA that passed evaluation goes in as a file, and atlas does the geodata work with GDAL.
- New canvas picks a DEM and an optional OSM extract from what the server lists under
ATLAS_DEM_DIR(GET /inputs), takes an AOI in degrees, and builds the hillshade, slope, layer masks and composite (POST /controls). No GPU, so this works on a read-only host too. - The job view shows each control on the canvas grid with the painted map over it and an opacity slider. The coastline should not move as you fade: the model only painted pixels inside a frame GDAL had already georeferenced.
- Bake streams
POST /bake/streamwith a style preset (GET /presets), a checkpoint and an optional LoRA. It is off unless the server setsATLAS_ALLOW_BAKE=1; the form then stays visible with the reason. A bake that fails the alignment gate keeps its PNG and writes no GeoTIFF. - Cut PMTiles (
POST /tiles) packs the aligned GeoTIFF up to its native zoom; the archive downloads from the page. Drape it on a basemap with atlas'sexamples/viewer.html. - Provenance lists the DEM hash and every GDAL command, so a bake can be replayed.
In compose, put DEMs (GeoTIFF or .vrt) in DEM_DIR and point MODELS_DIR / COMFYUI_BASE_URL at the ComfyUI the bake uses. Standalone (GDAL must be installed):
pip install "argus-atlas[server,cli,tiles]"
argus-atlas serve --cors # :8105; add ATLAS_ALLOW_BAKE=1 to bakeargus-quarry (gallery profile, run-to-completion)
└─ acquire PD/CC0 + provenance ──▶ /data/images (DATASET_DIR)
browser (:3000) → Next.js frontend
├─ /caption/*, /immich/* → argus-lens (:8100) → captioning
├─ /scan, /upload, … → argus-curator (:8101) → curation API
├─ /photos, /stats, … → argus-quarry-server (:8102) → provenance (read-only)
├─ /config → argus-forge (:8103) → training configs
├─ /reports, /report/* → argus-proof (:8104) → LoRA evaluation + HITL review
└─ /controls, /bake/*, … → argus-atlas (:8105) → DEM → fantasy-map PMTiles
curate → caption → forge handoff ("full" profile):
/curate export ── manifest ──▶ argus-lens /caption/manifest/stream
└───────────── shared /data/images ─────────────┘
then argus-forge /config reads the export (+ collects .txt sidecars)
and writes kohya / OneTrainer / diffusers configs under <export>/forge/
Immich (optional): argus-lens ⇄ IMMICH_URL (album captioning, write-back, pull-to-dataset)
Argus Studio is a thin frontend-only wrapper. It sends JSON requests to the argus-lens and argus-curator HTTP servers and renders results. No backend code lives in this repo — the suite compose.yaml runs the backends from published GHCR images by default (or builds them from their sibling repositories when you layer compose.build.yaml).
- Frontend — Next.js 15 (App Router) + Tailwind CSS v4, dark theme
- Captioning server —
argus-lens[server](see argus-lens) - Curation server —
argus-curator[server](optionalgpuextra; see argus-curator)
| Variable | Default | Description |
|---|---|---|
ARGUS_LENS_URL |
http://localhost:8100 |
URL the browser uses to reach the argus-lens API |
ARGUS_CURATOR_URL |
http://localhost:8101 |
URL the browser uses to reach the argus-curator API (/curate) |
ARGUS_QUARRY_URL |
http://localhost:8102 |
URL the browser uses to reach the argus-quarry provenance API (/gallery) |
PUBLIC_DOMAIN_DIR |
./public_domain |
Host dir holding the archived pd12m/ and megalith/ metadata, mounted read-only into the quarry server for /gallery → Sources |
QUARRY_ALLOW_FETCH |
(unset) | Let the quarry server land a Sources slice in the pool itself; unset, the page hands back slice.json + the CLI command |
QUARRY_CORS_ORIGINS |
(unset = read-only *) |
Comma-separated studio origins the quarry server allows; required for Fetch into the pool (argus-quarry ≥ 0.3.1) |
ARGUS_FORGE_URL |
http://localhost:8103 |
URL the browser uses to reach the argus-forge training bridge (/forge) |
ARGUS_PROOF_URL |
http://localhost:8104 |
URL the browser uses to reach argus-proof (the /proof eval-review view) |
ARGUS_ATLAS_URL |
http://localhost:8105 |
URL the browser uses to reach argus-atlas (the /atlas map-bake view) |
ARGUS_CURATOR_UI_MODE |
demo |
demo (bundled sample, no backend) or live (real scans/exports) |
ARGUS_CURATOR_SOURCE_PATH |
(empty) | Default source path shown in the folder picker (path inside the curator container). compose.yaml sets /data/images |
ARGUS_CURATOR_OUTPUT_PATH |
(empty) | Default export destination (path inside the curator container). compose.yaml sets /data/out |
DATASET_DIR |
./data |
Host dir mounted at /data/images on both curator and lens |
OUTPUT_DIR |
./out |
Host dir mounted at /data/out on curator (exports) |
HF_CACHE_DIR |
~/.cache/huggingface |
Host Hugging Face cache shared with the backends |
ARGUS_BACKEND |
hybrid |
argus-lens captioning backend. hybrid:wd14+openai-compat = WD14 tags locally + prose from an Ollama/vLLM vision model |
WD14_MODEL_REPO |
(unset = SmilingWolf/wd-vit-tagger-v3) |
WD14 v3 tagger for lens; SmilingWolf/wd-eva02-large-tagger-v3 is the most accurate |
WD14_CACHE_DIR |
~/.cache/wd14_tagger |
Host WD14 model cache mounted into lens (RW, so a new WD14_MODEL_REPO can download) |
ARGUS_OPENAI_COMPAT_BASE_URL / ARGUS_OPENAI_COMPAT_MODEL / ARGUS_OPENAI_COMPAT_API_KEY |
(unset) | Vision-model endpoint, model and optional key for lens's openai-compat prose stage, e.g. http://<ollama-host>:11434/v1 + qwen3-vl:8b |
ARGUS_OPENAI_COMPAT_PROMPT |
detailed |
Prompt preset (detailed, straightforward, descriptive, booru, count) or literal text |
ARGUS_REFINE_MODEL / ARGUS_REFINE_BASE_URL / ARGUS_REFINE_API_KEY |
(unset = off) | Chat model that rewrites lens's training captions; every rewrite is validated and falls back to the deterministic caption |
ARGUS_REFINE_VARIANTS |
training,training_prose |
Variants lens refines; add identity,wardrobe,setting,action for cleaner category captions |
ARGUS_IDLE_UNLOAD_S / ARGUS_GPU_COORDINATOR |
(unset) / none |
Lens frees its VRAM after this many idle seconds when it shares a GPU with ComfyUI; lease serialises argus jobs only |
IMMICH_URL / IMMICH_API_KEY |
(unset) | Enable the Immich integration on argus-lens (album captioning + pull-to-dataset) |
DEM_DIR |
./dem |
Host dir of DEMs and vector layers, mounted at /data/dem on argus-atlas |
ATLAS_ALLOW_BAKE / ATLAS_ALLOW_OSM |
(unset) | Let argus-atlas bake on the GPU / fetch OSM from Overpass; both off by default |
LENS_SOURCE_PATH |
/data/images |
Root the caption page's folder picker browses on lens (GET /folders) |
CURATOR_TAG / LENS_TAG / QUARRY_TAG / FORGE_TAG / PROOF_TAG / ATLAS_TAG |
latest |
GHCR image tag per backend on the default pull path; pin an exact release for reproducible deploys |
LENS_EXTRAS |
server,local |
pip extras for a source build only (-f compose.build.yaml); ignored on the default pull path (server,openai,replicate for a cloud-only image) |
PROOF_EXTRAS |
server,cli |
pip extras for a source build only (-f compose.build.yaml); server,cli,score adds the identity/quality/safety scorer stack |
FRONTEND_PORT / LENS_PORT / CURATOR_PORT |
3000 / 8100 / 8101 |
Host ports |
QUARRY_SERVER_PORT / FORGE_PORT / PROOF_PORT / ATLAS_PORT |
8102 / 8103 / 8104 / 8105 |
Host ports |
The ARGUS_* frontend variables are resolved per request from the container's
environment, not baked into the client bundle, so docker compose up -d frontend
applies a change without a rebuild — one published image deploys to any origin.
Behind a reverse proxy, give each service a path prefix on the proxy's origin:
ARGUS_CURATOR_URL=/api/curator # browser calls /api/curator/scan/folder
ARGUS_LENS_URL=/api/lensAn empty string means the origin root (/scan/folder instead of
http://host:8101/scan/folder). That works for at most one backend: all six
services expose /health, curator and lens both expose /folders, and curator
and quarry both expose /thumb, so a shared root is ambiguous. Use prefixes when
more than one service sits behind the proxy.
The pre-existing NEXT_PUBLIC_* names are still honoured as a fallback for older
.env files. The one rename that is not a straight prefix swap:
NEXT_PUBLIC_API_URL is now ARGUS_LENS_URL.
The repo-root .env is read by docker compose. A host npm run dev does
not see it — put local overrides in frontend/.env.local.
All captioning parameters are exposed in the UI with inline documentation:
| Parameter | Default | Description |
|---|---|---|
target_style |
photo |
photo for realism models, anime for booru-tagged models |
target_backend |
sdxl |
Diffusion backend — determines CLIP/T5 token budget (60–200 tokens) |
target_category |
identity |
Which category variant becomes final_caption |
prose_enrichment |
true |
Append novel prose-derived tokens to training variant at lowest priority |
- argus-lens — captioning engine, CLI, and server for the main page. For a whole library at scale (caption once, group by identity/wardrobe/setting/action, flag multi-person images, export SDXL and Flux datasets), see its curate-index workflow
- argus-curator — dataset curation CLI and HTTP server for
/curate - argus-quarry — provenance-first PD/CC0 image acquisition (the
gallerycompose profile) - argus-forge — training bridge: curated exports → ready-to-run LoRA training configs (the
forgeprofile) - argus-proof — post-training LoRA evaluation and optimisation (the
proofprofile; epic) - argus-atlas — georeferenced fantasy-map overlays from real terrain: DEM in, PMTiles out (the
atlasprofile)
MIT


