A fast, open-source annotation tool for video frames and images โ bounding boxes, polygon segmentation, and image classification โ powered by YOLO auto-annotation and a built-in web server for multi-user workflows.
- Features
- Quick Start
- Desktop App
- Web Server
- Performance
- Output Structure
- Project Structure
- Running Tests
- Docker
- License
Desktop app (Tkinter)
- Auto-annotate with any YOLO26 / YOLO11 / YOLOv8 model โ 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:8000| 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 YOLO 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 Annotation Panel โ Auto (YOLO) tab has a model dropdown.
| Model | Size | Speed | Accuracy |
|---|---|---|---|
yolo26x |
XLarge | Slow | Best |
yolo26l |
Large | Medium | High |
yolo26m |
Medium | Fast | Good |
yolo26s |
Small | Faster | OK |
yolo26n |
Nano | Fastest | Basic |
Models are auto-downloaded on first use. To use a local .pt or .onnx file, click the ๐ button next to the dropdown.
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.
Train immediately after export (YOLO format):
yolo train data=export_dir/data.yaml model=yolo26x.pt epochs=100The 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
โ โ โโโ yolo_annotator.py # YOLO inference wrapper (thread-safe)
โ โ โโโ exporter.py # multi-format dataset export
โ โ โโโ base_detector.py
โ โ โโโ analytics/ # stats, report generator
โ โ โโโ detectors/ # ONNX + Ultralytics detector backends
โ โ โโโ 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/
โ โโโ main_window.py # root frame, toolbar, event wiring
โ โโโ video_player.py # canvas, navigation, play/pause, polygon draw
โ โโโ annotation_panel.py # right panel (YOLO, box list)
โ โโโ annotation_type_selector.py
โ โโโ segmentation_panel.py
โ โโโ classification_panel.py
โ โโโ source_dialog.py # open-source dialog with step selector
โ โโโ export_dialog.py
โ โโโ log_viewer.py
โ
โโโ 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
โ
โโโ tests/ # pytest โ 135 tests
โโโ weights/ # local .pt / .onnx model files (gitignored)
โโโ 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 GNU AGPL-3.0 โ see LICENSE.
Why AGPL? Ultralytics YOLO is AGPL-3.0 (viral copyleft). Any project that links against ultralytics and is distributed (including over a network) must also be AGPL-3.0. For closed-source or commercial use, obtain the Ultralytics Enterprise Licence or replace the detector with a permissively-licensed alternative.
| Action | Allowed? |
|---|---|
| Use locally / privately | โ |
| Modify source code | โ |
| Share modifications | โ โ must include AGPL-3.0 source |
| Run as a public web service | โ โ must publish modifications under AGPL-3.0 |
| Sell a closed-source fork or make it open source | โ โ needs Ultralytics Enterprise Licence |
| Train on your own data and keep the weights | โ โ your data, your weights |
See THIRD_PARTY_LICENSES.md for the full dependency licence table.