Local CV pipeline visualization for engineers. Pip-installable. Zero external HTTP calls. No account required.
Spins up a local web dashboard for:
- Dataset analysis (class distribution, duplicates, corrupted images)
- Preprocessing inspection (transform pipeline list, before/after sample gallery)
- Training monitoring (loss/accuracy curves, ETA, system metrics, Grad-CAM)
- Results inspection (per-class metrics, PR curves, prediction browser with pagination)
- Live inference (upload an image, get a prediction from your running model)
- Run comparison (overlaid epoch curves, config diff, class delta table)
| Task | Status |
|---|---|
| Image classification | ✅ |
| Object detection (YOLO / COCO / VOC / xyxy boxes) | ✅ |
Reticus isn't trying to replace these - it's a different tradeoff, built specifically for CV work and for people who don't want their training data touching the network.
| Reticus | TensorBoard | wandb | MLflow | |
|---|---|---|---|---|
| Runs 100% locally, no account | ✅ | ✅ | ❌ (cloud by default) | ✅ (self-hostable) |
| Zero network calls, ever | ✅ | ✅ | ❌ | depends on setup |
| CV-specific (Grad-CAM, box overlays, dataset health checks) | ✅ | ❌ | partial (plugins) | ❌ |
| Rule-based training/data quality flags out of the box | ✅ | ❌ | ❌ | ❌ |
| Self-contained offline HTML report | ✅ | ❌ | ❌ | ❌ |
| Model registry / artifact versioning | ❌ (v1) | ❌ | ✅ | ✅ |
| Hyperparameter sweeps | ❌ (v1) | ❌ | ✅ | partial |
| Team / multi-user collaboration | ❌ (by design) | ❌ | ✅ | ✅ |
If you need a model registry, sweeps, or a shared team dashboard, those tools do that well. If you want a fast, private, CV-aware dashboard that doesn't need an account or a network connection, that's what Reticus is for.
Core (logging + metrics only - tiny, just numpy + Pillow):
pip install reticusWith the local dashboard (FastAPI server):
pip install "reticus[dashboard]"Everything (dashboard + torch Grad-CAM):
pip install "reticus[all]"| Extra | Adds | Use when |
|---|---|---|
| (none) | numpy, Pillow | Headless training - log metrics, no dashboard |
dashboard |
fastapi, uvicorn, psutil | You want the local web dashboard |
torch |
torch, grad-cam | Grad-CAM heatmaps |
all |
all of the above | One-shot full install |
After any run, serve the dashboard on existing data without writing a script:
reticus # auto-pick the only project
reticus my_project # named project
reticus my_project --port 9000 --no-browserReticus(project) with no dashboard extra installed gives a clear install hint.
Use Reticus(project, local=False) to log without starting a server at all.
from reticus import Reticus
r = Reticus("my_project") # opens http://127.0.0.1:8765 in browser
r.start_run(config={
"task": "classification",
"model": "resnet18",
"optimizer": "adam",
"lr": 1e-3,
"class_names": ["cat", "dog", "bird"],
})
for epoch in range(1, 11):
r.log({
"epoch": epoch,
"train_loss": 0.8 - epoch * 0.07,
"val_loss": 0.85 - epoch * 0.065,
"train_acc": 0.5 + epoch * 0.04,
"val_acc": 0.48 + epoch * 0.038,
"lr": 1e-3,
}, total_epochs=10)
# Log predictions (pass images as PIL, numpy array, or torch.Tensor)
r.log_predictions(
images=val_images,
class_names=["cat", "dog", "bird"],
task="classification",
preds=pred_labels,
targets=true_labels,
)
r.end_run()r.start_run(config={
"task": "od",
"class_names": CLASSES, # e.g. ["person", "car", "bike"]
})
r.log({"epoch": 1, "train_loss": 1.8, "val_loss": 1.9, "map50": 0.32})
# pred_boxes / true_boxes: list-of-images, each a list of boxes
# Classification box: [x1, y1, x2, y2, class_id, confidence]
# GT box: [x1, y1, x2, y2, class_id]
r.log_predictions(
images=val_images,
class_names=CLASSES,
task="od",
pred_boxes=preds,
true_boxes=trues,
box_format="xyxy", # or "yolo", "coco", "voc"
)
r.end_run()- Log plain Python numbers, not tensors. Call
loss.item()/float(x)before passing values tor.log(...)- Reticus stores JSON and won't serialize a raw tensor. - Images passed to
log_predictions/log_gradcammay bePIL.Image, a numpy array (HWC or CHW, 0-1 floats or 0-255 ints), atorch.Tensor, orNone(a neutral placeholder is generated if you only care about labels, not pixels). class_namesis an ordered list; a class id is its index in that list. Keep it consistent across the whole run.- Dataset layout for
analyze_dataset: classification expects a subdirectory per class (train/cat/...,train/dog/...); detection expects images plus YOLO-format.txtlabels (flat, orimages/+labels/side by side).
r.analyze_dataset(
data_dir="./data/train",
class_names=["cat", "dog", "bird"],
split_dirs={"train": "./data/train", "val": "./data/val"},
task="classification", # or "od"
)r.log_gradcam(
images=val_images,
targets=true_labels,
class_names=CLASSES,
model=model,
target_layer=model.layer4[-1],
)Register any callable as a predictor and the dashboard's Inference tab lets you drag-and-drop an image and see the model's prediction immediately - no extra server, no separate script.
r.register_predictor(my_predict_fn, class_names=["cat", "dog", "bird"])
# or, for a plain torch.nn.Module:
r.register_torch_classifier(model, class_names=CLASSES, device="cpu")my_predict_fn(pil_image) returns either a 1-D sequence of class
probabilities (aligned to class_names) or a {class_name: prob} dict.
Works with any framework - PyTorch, TensorFlow, ONNX, scikit-learn - since
Reticus only calls the function you give it.
html_path = r.generate_report(run_id="run_1")
# or compare two runs:
html_path = r.generate_report(run_id="run_2", compare_with="run_1")| Param | Default | Description |
|---|---|---|
project |
required | Project name - data stored in .reticus/projects/{name}/ |
local |
True |
Bind server to 127.0.0.1 (never 0.0.0.0) |
port |
8765 |
Dashboard port |
open_browser |
True |
Auto-open browser on first start |
| Method | Description |
|---|---|
start_run(config) |
Begin a new run, write config.json |
log(payload, total_epochs=None) |
Append epoch record, evaluate training flags |
log_predictions(images, class_names, task, ...) |
Save prediction thumbnails + JSONL |
log_gradcam(images, targets, class_names, model, target_layer) |
Compute + save Grad-CAM overlays |
end_run() |
Compute final metrics, merge flags, build prediction index |
analyze_dataset(data_dir, class_names, ...) |
Scan dataset, detect issues, store analysis |
log_preprocessing(steps, samples=None, processed_samples=None) |
Record transform pipeline + optional before/after gallery |
register_predictor(predict_fn, class_names=None) |
Register a callable for the live Inference tab |
register_torch_classifier(model, class_names, device="cpu") |
Convenience wrapper for a torch.nn.Module predictor |
save_model(model, class_names=None, scripted=True) |
Save TorchScript (or pickle fallback) model artifact |
generate_report(run_id, compare_with=None) |
Render standalone HTML report |
- Storage: all writes are atomic (temp-file rename). JSONL pagination uses a byte-offset index for O(chunk_size) seeks instead of O(N) line skipping.
- Metrics: real 11-point interpolated AP (PASCAL VOC method) at 10 IoU thresholds. Not
p x r. - Server: FastAPI on a background thread. Binds
127.0.0.1only. Never blocks training. The thread is intentionally non-daemon, so the dashboard keeps running (and the Inference tab stays live) even after your training script's main work finishes - it exits when your script's process exits. - Frontend: React 18 + Vite + shadcn/ui (Radix UI primitives). Pre-built into
reticus/dashboard/dist/and served as static files. - No network egress: zero external HTTP calls anywhere in the codebase.
Data never leaves the machine. The dashboard server binds to 127.0.0.1 only. No telemetry, no accounts, no external services.
These are scope decisions, not bugs or oversights:
- No hyperparameter sweep / search. Reticus visualizes runs you've already produced; it doesn't launch or schedule them. May reconsider for a future version.
- No model registry or artifact versioning.
save_model()writes one artifact per run - there's no registry, no promotion workflow, no version graph. - No dataset artifact versioning.
analyze_dataset()reads whatever's on disk at the time; it doesn't snapshot or diff dataset versions over time. - No LLM-based suggestions. The rule-based flags are deterministic threshold checks, not model-generated advice - may be worth exploring later, not in v1.
- No multi-user / team collaboration - by design. Reticus is a local, single-user tool. There's no shared server, no permissions model, no concept of "someone else's run."
- No cloud or remote tracking - by design. This is the core of Reticus's privacy positioning, not a missing feature. If you need a shared, remote dashboard, use wandb or MLflow's tracking server instead.
Reticus does not auto-detect class names from folder structure, classes.txt,
data.yaml, or a dataloader's .classes attribute - you always pass class_names
explicitly to start_run, log_predictions, etc. This is deliberate: it keeps Reticus
correct regardless of which of the many dataset conventions your pipeline actually
uses, instead of guessing wrong on an untested one.
MIT