Skip to content

Repository files navigation

Labello

Status: Current repository overview and setup guide Owner: Labello maintainers Audience: Users, operators, and contributors Last verified: 2026-07-30 at 5f10153

Labello icon

Labello is a browser-based image annotation system written in Rust. It combines an egui WebAssembly client with an Axum API and stores datasets, annotations, reviews, and audit history on the filesystem.

Labello currently supports:

  • bounding-box and skeleton/keypoint annotation;
  • autosave, undo/redo, and browser draft recovery;
  • automatic annotation and approval-review assignments;
  • object-level approval review, full-image checks, and correction workflows;
  • dataset, task, class, text-tutorial, role, and keybinding administration;
  • filesystem image ingestion, duplicate detection, statistics, and snapshots;
  • dataset contributor leaderboards, top-three podiums and selectable user history in Statistics, with labeling/review counts and acceptance percentages;
  • atomic new-dataset import for explicit YOLO detection/pose and COCO instances/keypoints ground-truth profiles;
  • original-image detection and pose dataset export with explicit round-trip guarantees;
  • guided box-to-skeleton migration with audited exclusions, replayed progress, assignment navigation, read-only browsing of resolved objects, and linked box companions for manually discovered objects;
  • loopback-only local administrator login and GitHub OAuth;
  • verified stable GitHub releases and rootless transactional deployment to a Debian 12 guest.

The project is under active development. See Current limitations before using it in production.

Quick Start

Prerequisites

  • Rust 1.98.0, pinned by rust-toolchain.toml, with rustfmt and Clippy
  • the wasm32-unknown-unknown Rust target
  • Trunk

Install the pinned compiler and browser tooling from the repository root:

rustup show
cargo install --locked trunk --version 0.21.14

The root and standalone inspector workspaces both require Rust 1.98. Dependency updates must retain the locked native, inspector, and WASM compatibility matrix.

Start the API from the repository root:

cargo run -p labello-server

On first start, the server creates labello.server.toml and listens on 127.0.0.1:8080.

In another terminal, start the browser client from apps/labello-wasm:

trunk serve --address 127.0.0.1 --port 8081

Open http://127.0.0.1:8081 and select Continue as local admin. The default loopback-only server configuration enables this session login for the admin bootstrap user, which can create the first dataset.

The dedicated login page checks the session before showing the authentication methods enabled by the server. It shows separate loading, failure/retry, and no-enabled-method states. About is available before and after sign-in. Endpoint editing lives under Advanced connection; deployment configuration remains the normal source of the API URL.

If a request indicates lost authentication, the UI rechecks the session before allowing more work. Sign-in recovery retains drafts for their original account; returning as another account cannot apply those drafts. Changing the endpoint immediately clears account and dataset state and rejects pending responses from the previous endpoint.

The client selects its API URL from the api query parameter, then from the public labello.client.json file deployed beside the browser application, and finally from port 8080 on the same hostname used to open the UI. The tracked labello.client.example.json contains every supported field with its default. Copy it before configuring a local client:

cp apps/labello-wasm/labello.client.example.json \
  apps/labello-wasm/labello.client.json

Its default null value selects the hostname-derived port 8080 fallback. For example, replace it with the following value to make port 8090 the deployment default without putting it in every application URL:

{
  "apiBaseUrl": "http://127.0.0.1:8090"
}

apps/labello-wasm/labello.client.json is ignored by Git and copied into a Trunk distribution when it exists. It is public browser configuration and must not contain secrets. A build without the file uses the hostname-derived fallback. Replace the deployed copy for each environment, or edit the source copy before starting a local trunk serve session. The api query parameter remains an explicit temporary override.

The annotation and review workflows prepare two upcoming assignments by default; set queueSize=1 to hold only one upcoming assignment. Values are clamped to 1..=2, so a browser holds at most the current assignment and two prepared assignments.

http://127.0.0.1:8081/?api=http://127.0.0.1:9000&queueSize=1

Server Configuration

The server creates labello.server.toml with local development defaults on its first start. A tracked configuration containing every supported setting is available at labello.server.example.toml.

See the complete server configuration reference for all TOML keys, defaults, validation rules, environment overrides, OAuth setup, and production guidance.

The default configuration is:

bind = "127.0.0.1:8080"
datasetsRoot = "datasets"
bootstrapAdmins = ["admin"]
browserOrigins = [
    "http://127.0.0.1:8081",
    "http://localhost:8081",
]
sessionCookieSecure = false

[developmentAuth]
localAdminLogin = true

browserOrigins must contain exact browser origins without paths. Unknown or missing required configuration fields are rejected.

The server supports these environment variables:

Variable Purpose
LABELLO_CONFIG Configuration file path; defaults to labello.server.toml
LABELLO_DATASETS_ROOT Overrides datasetsRoot
LABELLO_BIND Overrides bind
GITHUB_CLIENT_ID GitHub OAuth client ID
GITHUB_CLIENT_SECRET GitHub OAuth client secret
GITHUB_REDIRECT_URI GitHub OAuth callback URI
RUST_LOG Tracing filter
LABELLO_LOG_FORMAT text or json; defaults to text

All three GITHUB_* variables must be set to enable or override GitHub OAuth.

Authentication

Local Development Login

The local default enables one-click session login as the first configured bootstrap admin. This is accepted only on a loopback bind. Disable it on any internet-facing server:

[developmentAuth]
localAdminLogin = false

Dataset permissions remain role-based. The available roles are annotator, reviewer, adjudicator, and data admin. Only users listed in bootstrapAdmins can create datasets.

GitHub OAuth

Create a GitHub OAuth App and set its callback URL to the API callback route. For the default local setup, use:

Homepage URL:              http://127.0.0.1:8081
Authorization callback:   http://127.0.0.1:8080/auth/github/callback

Start the server with the OAuth credentials:

GITHUB_CLIENT_ID="..." \
GITHUB_CLIENT_SECRET="..." \
GITHUB_REDIRECT_URI="http://127.0.0.1:8080/auth/github/callback" \
cargo run -p labello-server

Keep the hostname consistent throughout the flow. Cookies set for 127.0.0.1 are not available to localhost, or vice versa. Do not commit the client secret to labello.server.toml.

GitHub accounts receive an internal ID such as github_123456. On first login, an account receives the annotator role on each existing dataset where it has no role assignment. A data admin can change those roles through the admin UI. Add the internal ID to bootstrapAdmins if the account should create datasets.

Annotation Controls

Every Annotate workspace action has a configurable keyboard shortcut. Open Settings (Ctrl+, on Windows/Linux or Cmd+, on macOS) to record shortcuts, search actions, resolve contextual conflicts, or restore defaults. Changes are staged until Save changes is selected. The Pan toggle defaults to P. Its primary-button drag gesture has a separately configurable modifier that defaults to Ctrl.

The canvas zooms with the mouse wheel, two-finger touchpad scrolling, pinch, or the configured zoom keys. Settings lists Zoom in and Zoom out with their current bindings and gesture instructions. The context bar keeps Pan, Fit, and the applicable Refocus control; it has no zoom buttons or percentage display. Press the configured Pan key to toggle Pan mode, then left-drag a zoomed image. Press it again or Escape to return to annotation input. The configured modifier plus left-drag (Ctrl+left-drag by default), middle-drag, touch gestures, and double-click-to-fit remain available. Use Refocus in the workspace context bar, or press its configurable shortcut (R by default), to center and zoom to the active review object or guided-migration guide.

Approval review keeps Pan mode active so primary drag moves the focused image without an extra mode switch. Missing-object placement and reviewer correction return primary drag to their respective editing controls; the configured modifier plus left-drag and middle-drag still pan while correcting. Refocus uses the active object's current correction geometry without leaving the assignment. Guided migration review likewise refocuses its active canonical guide or discovered skeleton. Discovered-object focus uses its linked box, positioned-keypoint bounds, or the full image when historical data has no positions.

Review Previous, also available through the configured Previous image shortcut, returns to the immediately previous eligible skipped or completed review. Completed reviews open a decision-only revision on current geometry. Stage the replacement decisions and explicitly commit the full-image result; opening or cancelling preserves the old outcome. Later work can make the previous review ineligible. See assignment rules.

During the full-image check of an ordinary approval review, Mark missing adds locations for missing objects. Drag a marker to move it, select its list entry to refocus, or remove it before submitting. Keyboard users can add at the image center and edit normalized coordinates in the inspector. Approval is disabled while locations exist; Send back records the rejection and locations together. The annotator sees read-only guidance until resubmission. Later reviewers can inspect the history. These markers are evidence, never annotations. Unsent locations are scoped to the current assignment and require confirmation before switching. The browser warns on ordinary page exit, but reloads and crashes can lose drafts. Guided migration uses its separate missing-object flow.

Saving a missing migration object also creates a derived box in its configured box task and reopens that task for correction and ordinary review. Still-derived boxes follow skeleton edits and removal. Independently edited or reviewed boxes require explicit reconciliation. The full-image inspector reports pairing progress and offers per-object repair for historical discoveries; unresolved provenance or coordinate-less objects need action rather than guessed boxes. Migration reviews list each discovered skeleton separately before confirmation. A failed save or companion refresh retains unsaved skeleton input.

While placing or revising a skeleton, drag any already placed keypoint on the selected object to correct its position. This works for ordinary annotation, guided migration drafts, and reviewer correction; read-only review remains non-editable.

Datasets

A bootstrap admin creates a dataset in the setup view. A data admin can then:

  1. Define classes and bounding-box or skeleton tasks.
  2. Configure review requirements and user roles.
  3. Add relative filesystem image roots or upload a browser folder.
  4. Run ingestion to index images and detect duplicate content.
  5. Assign users to annotation, review, adjudication, or administration roles.

A bootstrap administrator can also select Import a dataset in Setup when the server advertises import capability. Import accepts the four explicit profiles ultralytics_yolo_detect_v1, ultralytics_yolo_pose_v1, coco_instances_gt_v1, and coco_keypoints_gt_v1. It creates a new dataset only; it never merges into or replaces an existing dataset.

Server-directory import is preferred for large sources. Browser folder import is resumable within the advertised limits, but selecting the folder again is required after reload when the browser does not preserve a directory handle. Sources are sealed, preflighted, mapped, rebuilt from generated event logs, and published only after verification. YOLO paths must be portable and relative to the sealed source; absolute YAML paths, URLs, and download directives are not followed.

Dataset administrators can use dataset export to select a detection or pose profile, preflight compatible tasks/classes, build a private archive, and download it after verification. Re-import requires local extraction and a new dataset; native workflow history and identities are not restored. See the export contract for completeness rules and pose compatibility.

The server stores each dataset below datasetsRoot:

datasets/
  .labello-server/
    auth.json
    imports/
    exports/<job-id>/
  <dataset-id>/
    labello.dataset.toml
    labello.schema.json
    images-index.json
    images/
    annotations/<image-id>/
      events.jsonl
      state.json
    users/<user-id>/
      keybindings.toml
    .labello/snapshots/
    .labello/imports/<import-id>/
      manifest.json
      source-objects.jsonl

Per-image events.jsonl files are the authoritative audit history; state.json can be rebuilt from them. Snapshots include dataset metadata and annotation history but not image bytes, authentication data, or user keybindings. Import manifests and canonical source-object audit records are included, but imported image bytes remain excluded. Back up image bytes and authentication state separately.

Architecture

labello-domain
|-- labello-storage --+
|-- labello-client ---+-- labello-api -- labello-server
+---------------------+-- labello-ui --- labello-wasm
Package Responsibility
labello-domain Shared domain types, validation, events, and workflow logic
labello-storage Filesystem persistence, ingestion, assignment, statistics, and snapshots
labello-client API contracts plus HTTP and demo implementations
labello-api Axum routes, authentication, authorization, and workflow orchestration
labello-ui Shared egui annotation and administration UI
labello-server Tokio/Axum API executable
labello-wasm Browser entry point and Trunk build target

Inside the crates, ownership follows the same direction. Domain modules own pure replay and workflow policy; storage modules own filesystem mechanics and transaction ordering; the API owns authorization and transport trust boundaries; and the UI owns explicit feature state with closed asynchronous commands and responses. DatasetRepository, ImportService, and LabelloApi are intentional capability facades, not generic abstraction layers.

See the current architecture and ownership map, the HTTP API contract, the persistence and recovery contract, and the detailed import and UI ownership references.

The API server does not serve the browser distribution. Build and deploy apps/labello-wasm/dist separately.

apps/egui-mcp-inspector is a standalone native development tool outside the main workspace. It reuses labello-ui with deterministic demo state by default and has an opt-in live mode for local development servers.

Development

Run the canonical changed-path verification from the repository root:

./scripts/verify.sh changed origin/main

It checks formatting, Clippy, all-feature workspace tests, inspector-preset UI tests, the standalone inspector with its tracked lockfile, and the WASM target. It also runs the locked release browser build when affected paths require it. Use ./scripts/verify.sh all to run every machine check regardless of changed paths. Prerequisites, risk-specific checks, CI equivalence, evidence, and independent acceptance are defined in the contributor guide and verification contract.

To build only the browser distribution from apps/labello-wasm:

trunk build --release --locked

The output is written to apps/labello-wasm/dist. The API health endpoint is GET /health.

GUI Inspection

Install egui_mcp and run the native inspector from the repository root:

cargo install egui_mcp --locked
EGUI_INSPECTION=1 cargo run --manifest-path apps/egui-mcp-inspector/Cargo.toml

To inspect the native UI against a running local server, enable local administrator login and start the inspector in live mode:

EGUI_INSPECTION=1 cargo run --manifest-path apps/egui-mcp-inspector/Cargo.toml -- --live

The repository's opencode.json configures the egui MCP server. Restart OpenCode after changing that configuration. The inspector exposes the shared egui accessibility tree and accepts inspection input after attaching. Live mode can mutate real server data through the local administrator session; use a disposable development dataset. Use Chromium to validate actual WASM startup, browser behavior, cookies, and responsive rendering. See the inspector README for details.

Production Notes

  • Terminate TLS in front of both the UI and API.
  • Set sessionCookieSecure = true when using HTTPS.
  • Disable developmentAuth.localAdminLogin.
  • Store OAuth secrets outside committed configuration.
  • Configure browserOrigins and the OAuth callback with exact public URLs.
  • Run one Labello server process per dataset root; filesystem locks are process-local.
  • Back up the dataset root, image roots, and .labello-server/auth.json.
  • Use the release and deployment guide for immutable artifacts, rootless user services, Caddy, backup, rollback, and recovery.

Current Limitations

Product And Workflow Gaps

  • Session recovery retains account-scoped drafts but does not enable offline work. Browser recovery remains a best-effort local cache; reloading or an OAuth round trip relies on the existing persisted draft and assignment checks.

  • Offline bundle and synchronization APIs exist, but the browser UI cannot download an offline workspace, author against it without a network connection, retain versioned offline mutations, synchronize them, or present merge conflicts. Browser draft recovery is not offline mode.

  • Independent multi-annotator labeling, agreement calculation, automatic acceptance, disagreement routing, and adjudication are not operational. Adjudicator roles and API/domain shapes exist, but there is no reachable production adjudication workflow and the Adjudicate UI is disabled.

  • Prelabel configuration, task association, queued loading, display, acceptance, and discard controls exist, but annotators cannot choose among the available configurations: every configuration associated with the task is requested. No model is executed. The server returns fixed placeholder geometry; browser-local WebGPU and CPU/WASM fallback execution are not implemented. Accepted placeholders currently record a generic model identity rather than the configured model's exact identity.

  • Task tutorials display configured title and text only. Administrators can enter example-image paths, but those images are not loaded or shown to annotators.

  • Approval review supports object decisions through buttons and configurable shortcuts plus a final full-image check. Swipe-to-approve or reject is not implemented.

  • The canvas routes a single pen like a generic pointer, but Labello does not currently claim tested stylus support for a named browser/device combination or guarantee that pen, mouse, and touch interactions do not conflict.

  • Assignment balance can enforce an absolute completion-count window across enabled tasks. It does not separately aggregate and enforce class-level balance when multiple tasks share a class. See Assignment for exact count and boundary semantics.

  • There is no supported native desktop client. The native inspector is a development tool, not an offline or production client.

Persistence And Compatibility Gaps

  • Current dataset configuration and keybindings are versioned TOML, while image indexes, state, events, schemas, snapshots, and import records use JSON or JSONL. This differs from the target design's all-JSON dataset-metadata description.
  • Persisted schema version 3 is current and version 2 is the only supported legacy version. Version 1 artifacts are rejected; no 1 -> 2 migration is available despite the target design saying schema versions start at 1.
  • Snapshots are downloadable annotation/audit packages, not complete backups. They omit image bytes, authentication state, user keybindings, and private import/export control state, and there is no native snapshot-restore operation.

Production And Operational Boundaries

  • There is no browser end-to-end test suite. egui_kittest and the native inspector do not validate WASM networking, cookies, IndexedDB, browser input, or deployed responsive behavior.
  • Ingest jobs and some derived caches are process-local and do not survive restarts as durable jobs.
  • Configured cleanup of retained import jobs is not invoked or scheduled by the production server, and import API control/idempotency records have no complete retention lifecycle.
  • GET /health is liveness only. GET /deployment/readiness checks dataset-root traversal and authentication-store loading for deployment admission, but it does not cover write capacity, free space, representative dataset reads, OAuth, or browser networking.
  • Graceful shutdown is wired to Ctrl-C, but there is no application drain deadline or documented SIGTERM handler.
  • Import format support is tested under configured limits, but official COCO-scale operation remains a separate performance gate.
  • Import does not merge into existing datasets and does not support prediction or prelabel import, segmentation, remote sources, or archive sources. Detection and pose export supports explicit ground-truth round trips into new datasets; it does not restore native identities or history.
  • Import publication, assignment locking, and in-memory caches assume one Labello server process per datasets root. Multi-process coordination is not supported, including on a shared network filesystem.

The broader product requirements and planned behavior are documented in labello.md. That document describes the target product and is not evidence of current support. Planned capabilities, partial behavior, contract disagreements, and implementation defects are tracked in GitHub issues and organized for authorized maintainers in the Labello project.

Working image previews

Annotation, review and migration always use Data saver previews: lossy WebP at quality 80, with a maximum edge of 1280 pixels. Upcoming-image prefetch and retries use the same profile. There is no image-quality setting or original-detail action; previously saved quality preferences are ignored.

Failed loads show Retry image load and never automatically fetch larger previews or original bytes. Annotation coordinates still use the original image dimensions. Preview limits are documented in configuration. Cached images do not provide an offline annotation workflow.

About

Image annotation tooling

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages