A fast, open-source annotation tool for video frames and images โ bounding boxes, polygon segmentation, and image classification โ powered by RT-DETR auto-annotation and a built-in web server for multi-user workflows. Ships as a PySide6 desktop app with a one-click installer.
- Features
- Quick Start
- Desktop App
- Web Server
- Performance
- Output Structure
- Project Structure
- Running Tests
- Docker
- License
Desktop app (PySide6)
- Auto-annotate with RT-DETR (HuggingFace transformers + supervision) โ one frame or all at once
- Three annotation types: bounding box, polygon segmentation, image classification
- Click-and-drag box drawing with full resize / move handles
- Polygon draw mode โ click to place vertices, double-click to close, Escape to cancel
- Play/pause video with variable speed (0.5ร / 1ร / 2ร / 4ร) and Space bar toggle
- Adjustable frame step โ load every Nth frame for fast navigation on long videos
- Manual class names + confidence threshold slider
- Live log viewer, class-filter, per-frame undo (clear), label persistence across sessions
Export formats
- YOLO (images/ + labels/ + data.yaml)
- COCO JSON
- Pascal VOC XML
- CSV
- JSON
Web server (FastAPI)
- REST API for projects, frame upload, per-frame annotations, ZIP export
- Dark-theme SPA at
http://localhost:8000โ works in any browser - Canvas annotation (bbox / polygon / classification), frame strip, keyboard navigation
Performance
- Frames saved as JPEG (not PNG) โ 5โ10ร faster writes, 10ร smaller files
- Image folder loading skips unnecessary decoding โ near-instant for large folders
- Label scan does one
os.listdir()at startup instead of per-frame file stats - Background frame extraction โ UI is responsive immediately after opening a video
# 1. Clone
git clone https://github.com/tedo001/tode.git
cd tode
# 2. Virtual environment (Python 3.11 or 3.12)
python3.12 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install desktop dependencies
pip install -r requirements.txt
# 4. Launch the desktop app
python main.py
# 5. (Optional) Install and launch the web server
pip install -r requirements-server.txt
python run_server.py # โ http://localhost:8000Prefer a one-click install? tode also ships as a WEKA-style installer (
tode-setup.exeon Windows, a tarball on Linux) that bundles Python and all dependencies โ no manual setup. Seepackaging/README.md.
| Toolbar button | Action |
|---|---|
๐ Open |
Tabbed dialog โ Video / Image / Image Folder |
๐ฌ Video |
Direct video file picker |
๐ผ Image |
Single image or image folder |
Click the canvas when no source is loaded to open the file picker directly.
Supported video formats: MP4, AVI, MOV, MKV, WEBM, FLV, WMV
Supported image formats: JPG, PNG, BMP, TIFF, WEBP
Frame Step โ the Open dialog includes a "Video frame step" spinbox (1โ30). At step=5 a 30 fps video loads 6ร fewer frames; at step=1 (default) every frame is indexed. Frames not yet extracted from the background thread are decoded on-demand.
Select the annotation type from the mode bar above the canvas.
- Click and drag on the canvas to draw a box
- Click inside a drawn box to select it โ 8 resize handles appear
- Drag a handle to resize; drag the body to move
- Click an empty area or press
V/Escto deselect
- Click to place each vertex
- Double-click to close the polygon and commit it
- Escape cancels the polygon in progress
- Saved in YOLO-seg format (
.seg.txtsidecar)
- Assigns a single class label to the whole frame (no spatial extent)
- Saved as a
.cls.txtsidecar
| Key | Action |
|---|---|
A / โ |
Previous frame |
D / โ |
Next frame |
Home |
Jump to first frame |
End |
Jump to last frame |
W |
Switch to Draw Box mode |
V / Esc |
Switch to View mode / cancel polygon |
Space |
Toggle play / pause |
Y |
Run RT-DETR on the current frame |
Ctrl+S |
Save annotations |
Ctrl+E |
Export dataset |
Ctrl+O |
Open source dialog |
Delete |
Clear all annotations on the current frame |
The navigation bar has five buttons:
โฎ โ โถ/โธ โถ โญ
- โถ (purple) โ click or press
Spaceto start auto-advance - โธ (pink) โ shown while playing; click or press
Spaceto pause - Speed row below:
0.5ร1ร2ร4ร - Any navigation button (โฎ โ โถ โญ) stops playback automatically
The control panel has an RT-DETR model dropdown (HuggingFace ids).
| Model | Backbone | Speed | Accuracy |
|---|---|---|---|
PekingU/rtdetr_r18vd |
ResNet-18 | Fastest | Good |
PekingU/rtdetr_r34vd |
ResNet-34 | Fast | Better |
PekingU/rtdetr_r50vd |
ResNet-50 | Medium | High (default) |
PekingU/rtdetr_r101vd |
ResNet-101 | Slow | Best |
PekingU/rtdetr_v2_r18vd / _r50vd |
RT-DETRv2 | โ | improved |
Weights are auto-downloaded from the HuggingFace Hub on first use and cached
โ no local weight files to manage. Set a different default with the
TODE_RTDETR_MODEL environment variable.
Click ๐ค Export and choose a format and output folder.
| Format | Output |
|---|---|
| YOLO | images/ + labels/ + data.yaml โ ready for yolo train |
| COCO | annotations.json (images, annotations, categories) |
| Pascal VOC | One XML per image |
| CSV | One row per bounding box |
| JSON | Custom JSON with all annotation types |
Only annotated frames are exported. Frame files are renumbered sequentially (img_1, img_2, โฆ) so images and labels always match 1-to-1.
The YOLO/COCO export uses the standard dataset layout, so the result feeds
straight into any detection trainer (RT-DETR fine-tuning via transformers,
or any framework that reads YOLO data.yaml / COCO annotations.json).
The web server is a standalone FastAPI app that does not modify main.py or any desktop code. Run it alongside or instead of the desktop app.
pip install -r requirements-server.txt
python run_server.pyOpen http://localhost:8000 in a browser.
Environment variables:
| Variable | Default | Description |
|---|---|---|
TODE_HOST |
0.0.0.0 |
Bind address |
TODE_PORT |
8000 |
Port |
TODE_RELOAD |
false |
Uvicorn auto-reload (dev mode) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/api/projects |
List all projects |
POST |
/api/projects |
Create a project |
GET |
/api/projects/{id} |
Get project metadata |
DELETE |
/api/projects/{id} |
Delete a project |
PATCH |
/api/projects/{id}/classes |
Update class list |
POST |
/api/projects/{id}/upload |
Upload frame images |
GET |
/api/projects/{id}/export?fmt=yolo |
Download annotations as ZIP |
GET |
/api/projects/{id}/frames |
List frames |
GET |
/api/projects/{id}/frames/{idx}/image |
Get frame image |
GET |
/api/projects/{id}/frames/{idx}/annotations |
Get frame annotations |
POST |
/api/projects/{id}/frames/{idx}/annotations |
Save frame annotations |
All annotation endpoints accept and return JSON with boxes, polygons, and classifications arrays.
| Scenario | Before | After |
|---|---|---|
| Write one 1080p frame | ~40 ms (PNG) | ~6 ms (JPEG) |
| 1000-frame background extraction | ~40 s disk I/O | ~6 s |
| Load 1000-image folder | Decodes all 1000 images | Zero decodes โ path copy only |
| Startup label scan (1000 frames) | ~3000 stat() calls |
1 listdir() call |
10-min 30fps video at step=5 |
18 000 frames | 3 600 frames |
Frames are cached as JPEG on first access. Subsequent navigation reads the JPEG from disk (~5 ms per frame). Old projects with .png frames are supported automatically via a fallback path.
output/
โโโ frames/
โ โโโ <source_name>/
โ โโโ frame_000000.jpg โ JPEG cache (new projects)
โ โโโ frame_000005.jpg
โโโ labels/
โ โโโ <source_name>/
โ โโโ frame_000000.txt โ YOLO bbox (class cx cy w h)
โ โโโ frame_000000.seg.txt โ YOLO-seg polygons
โ โโโ frame_000000.cls.txt โ image-level classification
โ โโโ classes.json โ class id โ name mapping
โโโ server_projects/ โ web server projects
โโโ <project_id>/
โโโ meta.json
โโโ frames/
โโโ labels/
Label format:
| File | Format |
|---|---|
.txt |
<class_id> <cx> <cy> <w> <h> (normalised, YOLO) |
.seg.txt |
<class_id> <x1> <y1> <x2> <y2> โฆ <xN> <yN> (normalised, YOLO-seg) |
.cls.txt |
<class_id> <confidence> |
tode/
โโโ main.py # desktop app entry point
โโโ run_server.py # web server entry point
โโโ requirements.txt # desktop dependencies
โโโ requirements-server.txt # web server dependencies
โโโ requirements-test.txt # test dependencies
โ
โโโ src/
โ โโโ core/
โ โ โโโ annotation_manager.py # orchestrates the full pipeline
โ โ โโโ video_loader.py # OpenCV video I/O
โ โ โโโ frame_extractor.py # sequential JPEG frame extraction
โ โ โโโ image_loader.py # single image / folder loader
โ โ โโโ image_frame_extractor.py
โ โ โโโ auto_annotator.py # RT-DETR detector facade (thread-safe)
โ โ โโโ exporter.py # multi-format dataset export
โ โ โโโ base_detector.py
โ โ โโโ analytics/ # stats, report generator
โ โ โโโ detectors/ # RT-DETR (transformers + supervision) backend
โ โ โโโ exporters/ # YOLO / COCO / Pascal VOC / CSV / JSON
โ โ โโโ importers/ # YOLO / COCO / CSV / JSON importers
โ โ โโโ pipeline/ # QueueManager, BatchProcessor, Scheduler
โ โ
โ โโโ models/
โ โ โโโ annotation_model.py # BoundingBox, PolygonAnnotation,
โ โ โ # ImageClassification, FrameAnnotation
โ โ โโโ project_config.py
โ โ โโโ batch_config.py
โ โ โโโ class_definition.py
โ โ โโโ export_config.py
โ โ โโโ session.py
โ โ
โ โโโ storage/
โ โ โโโ label_storage.py # YOLO .txt / .seg.txt / .cls.txt I/O
โ โ โโโ frame_storage.py
โ โ โโโ project_storage.py
โ โ โโโ session_storage.py
โ โ โโโ formats/ # YOLO / COCO / CSV / Pascal VOC / JSON
โ โ
โ โโโ ui/ # PySide6 desktop UI
โ โโโ qt_main_window.py # main window, toolbar, panel, event wiring
โ โโโ qt_canvas.py # annotation canvas (boxes + polygons)
โ โโโ qt_workers.py # QThread load / detect workers
โ
โโโ server/ # FastAPI web server (standalone)
โ โโโ app.py
โ โโโ config.py
โ โโโ routes/ # health, projects, frames
โ โโโ schemas/ # Pydantic request/response models
โ โโโ services/ # project, annotation, export services
โ โโโ static/ # index.html, app.js, style.css
โ
โโโ packaging/ # PyInstaller spec + Inno Setup wizard + CI
โโโ tests/ # pytest
โโโ output/ # generated files (gitignored)
pip install -r requirements-test.txt
pytest tests/ -q135 tests covering loaders, extractors, exporters, importers, annotation models, storage, pipeline, analytics, and utilities.
Build and run in a reproducible Linux environment with all dependencies pre-installed.
# Build
docker build -t tode .
# Smoke test
docker run --rm tode python -c "from core import YOLOAnnotator; print('OK')"
# GUI on Linux (X11)
xhost +local:docker
docker compose upOutputs persist on the host via volume mounts (./output/).
Install nvidia-container-toolkit then uncomment deploy.resources in docker-compose.yml:
docker compose up # GPU used automatically| Platform | GUI in Docker? | Recommended |
|---|---|---|
| Linux | โ X11 forwarding | Full app |
| Windows | โ Needs WSL2 + VcXsrv | Headless only |
| macOS | โ No native X11 | python main.py in venv |
Licensed under the MIT License โ see LICENSE.
tode is fully permissive: use it privately or commercially, modify it, and ship
it in closed-source products โ just keep the copyright notice. This is possible
because every runtime dependency is permissively licensed: the PySide6 GUI
is LGPL (dynamically linked, no copyleft on your code), RT-DETR /
transformers are Apache-2.0, supervision is MIT, and torch/OpenCV/numpy are
BSD/Apache.
| Action | Allowed? |
|---|---|
| Use locally / privately | โ |
| Modify source code | โ |
| Use in a closed-source or commercial product | โ โ just keep the MIT notice |
| Redistribute / fork | โ |
| Train on your own data and keep the weights | โ โ your data, your weights |
LGPL note (PySide6): MIT covers tode's own code. PySide6 is LGPL, so if you redistribute the bundled Qt libraries, keep them replaceable (the default PyInstaller/installer layout already satisfies this โ Qt ships as separate DLLs, not statically linked).
See THIRD_PARTY_LICENSES.md for the full dependency licence table.
Contributions are welcome! Participation in this project is governed by our Code of Conduct.