{"id":23661405,"url":"https://github.com/rfonod/geo-trax","last_synced_at":"2026-07-04T08:00:53.478Z","repository":{"id":245013828,"uuid":"817002220","full_name":"rfonod/geo-trax","owner":"rfonod","description":"🚀 Extract and analyze high-accuracy georeferenced vehicle trajectories from bird's-eye-view aerial video using computer vision and deep learning, for scalable urban traffic analysis.","archived":false,"fork":false,"pushed_at":"2026-07-04T06:22:28.000Z","size":122587,"stargazers_count":29,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-07-04T07:30:08.223Z","etag":null,"topics":["aerial-imagery","computer-vision","cuda","georeferencing","object-detection","object-tracking","orthophoto","traffic-analysis","trajectories","vehicle","video-analytics","yolo"],"latest_commit_sha":null,"homepage":"https://arxiv.org/abs/2411.02136","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rfonod.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":".zenodo.json","notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-06-18T20:20:16.000Z","updated_at":"2026-07-04T06:30:07.000Z","dependencies_parsed_at":"2025-05-21T21:38:34.121Z","dependency_job_id":null,"html_url":"https://github.com/rfonod/geo-trax","commit_stats":null,"previous_names":["rfonod/geo-trax"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/rfonod/geo-trax","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfonod%2Fgeo-trax","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfonod%2Fgeo-trax/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfonod%2Fgeo-trax/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfonod%2Fgeo-trax/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rfonod","download_url":"https://codeload.github.com/rfonod/geo-trax/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfonod%2Fgeo-trax/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35114174,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aerial-imagery","computer-vision","cuda","georeferencing","object-detection","object-tracking","orthophoto","traffic-analysis","trajectories","vehicle","video-analytics","yolo"],"created_at":"2024-12-29T04:57:42.980Z","updated_at":"2026-07-04T08:00:53.439Z","avatar_url":"https://github.com/rfonod.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Geo-trax\n\n[![GitHub Release](https://img.shields.io/github/v/release/rfonod/geo-trax?include_prereleases)](https://github.com/rfonod/geo-trax/releases) [![PyPI - Version](https://img.shields.io/pypi/v/geo-trax)](https://pypi.org/project/geo-trax/) [![PyPI - Total Downloads](https://img.shields.io/pepy/dt/geo-trax?label=total%20downloads)](https://pepy.tech/project/geo-trax) [![PyPI - Downloads per Month](https://img.shields.io/pypi/dm/geo-trax?color=%234c1)](https://pypi.org/project/geo-trax/) [![CI](https://github.com/rfonod/geo-trax/actions/workflows/ci.yml/badge.svg)](https://github.com/rfonod/geo-trax/actions/workflows/ci.yml) [![Python](https://img.shields.io/badge/python-3.9--3.13-blue)](https://www.python.org/) [![License](https://img.shields.io/github/license/rfonod/geo-trax)](https://github.com/rfonod/geo-trax/blob/main/LICENSE) [![GitHub Issues](https://img.shields.io/github/issues/rfonod/geo-trax)](https://github.com/rfonod/geo-trax/issues) [![Open Access](https://img.shields.io/badge/Journal-10.1016%2Fj.trc.2025.105205-blue)](https://doi.org/10.1016/j.trc.2025.105205) [![arXiv](https://img.shields.io/badge/arXiv-2411.02136-b31b1b.svg)](https://arxiv.org/abs/2411.02136) [![Archived Code](https://img.shields.io/badge/Zenodo-Software%20Archive-blue)](https://zenodo.org/doi/10.5281/zenodo.12119542) [![Hugging Face](https://img.shields.io/badge/🤗%20Model-rfonod%2Fgeo--trax-yellow)](https://huggingface.co/rfonod/geo-trax) [![Hugging Face Space](https://img.shields.io/badge/🤗%20Space-Live%20Demo-yellow)](https://huggingface.co/spaces/rfonod/geo-trax) [![Project Website](https://img.shields.io/badge/REAL%20Lab-Geo--trax-informational)](https://www.real-lab.ch/geo-trax) [![YouTube](https://img.shields.io/badge/YouTube-Video-red?logo=youtube\u0026logoColor=red)](https://youtu.be/gOGivL9FFLk)\n\n**Geo-trax** (GEO-referenced TRAjectory eXtraction) is a comprehensive pipeline that extracts high-accuracy, georeferenced vehicle trajectories from high-altitude drone imagery. Built for quasi-stationary aerial monitoring of urban traffic, it turns raw bird's-eye view (BEV) drone footage into precise, real-world vehicle trajectories. The framework combines YOLO detection, multi-object tracking, and video stabilization with a robust orthophoto-based georeferencing stage, producing GNSS-tagged, lane-resolved trajectories that are spatially and temporally consistent and ready for large-scale traffic analysis and simulation. It is optimized for urban intersections and arterial corridors, where high-fidelity, vehicle-level insights drive intelligent transportation systems and digital twin applications.\n\n![Geo-trax Output Visualization](https://raw.githubusercontent.com/rfonod/geo-trax/main/assets/geo-trax_visualization.webp)\n\n🎬 An accelerated preview of Geo-trax's capabilities. Watch the full ~4 min 4K demo on [YouTube](https://youtu.be/gOGivL9FFLk).\n\n\u003e [!TIP]\n\u003e **Just want to see it work?** Try the [interactive demo on 🤗 Hugging Face Spaces](https://huggingface.co/spaces/rfonod/geo-trax): run the vehicle detector on your own aerial image or short clip right in the browser, no install required.\n\n### Why Geo-trax\n\n- 🛰️ **Real-world output**: georeferenced, lane-resolved trajectories (WGS84 + local CRS) with per-vehicle speed, acceleration, and estimated dimensions, straight from raw BEV drone video.\n- 🎯 **Accurate detection**: [YOLOv8s vehicle detector](#detection-model) reaching **0.951 mAP@50**, trained on more than 19,000 annotated aerial images.\n- 🚗 **Flexible tracking**: four vehicle classes and [six selectable multi-object trackers](#tracking) (BoT-SORT, ByteTrack, OC-SORT, and more).\n- 🌀 **Drone-motion robust**: homography-based stabilization ([Stabilo](https://github.com/rfonod/stabilo)) plus orthophoto image registration for consistent, cross-flight coordinates; both optionally CUDA-accelerated.\n- 📊 **Proven at scale**: powered the [Songdo Traffic](https://doi.org/10.5281/zenodo.13828383) dataset (roughly **700,000 trajectories** across **20 intersections**, fleet of **10 drones**; see [Real-World Deployment](#real-world-deployment-the-songdo-experiment)).\n- ⚙️ **One command, one config**: `geotrax batch` runs the whole pipeline; a single YAML drives every stage, with [four tuned presets](#configuration) included.\n\n## Pipeline\n\n![Geo-trax pipeline diagram: raw drone video → detection → tracking → stabilization → georeferencing → dataset](https://raw.githubusercontent.com/rfonod/geo-trax/main/assets/geo-trax_pipeline.svg)\n\n🔍 The core pipeline (solid box) produces stabilized, pixel-coordinate vehicle trajectories. Optional extensions add georeferencing via orthophoto image registration, vision dataset creation through frame (pre-)annotation for custom detector fine-tuning, and visualization, analysis, and probe vehicle validation tools, all applicable to both pixel-coordinate and georeferenced outputs.\n\n## Install\n\n```bash\npython3.11 -m venv .venv \u0026\u0026 source .venv/bin/activate  # Windows: .venv\\Scripts\\activate\npython -m pip install geo-trax\n```\n\nPython 3.9 to 3.13. Also works with [uv](https://docs.astral.sh/uv/) (`uv pip install geo-trax`) and [conda](https://www.anaconda.com/docs/getting-started/miniconda/install). For development:\n\n```bash\ngit clone --depth 1 https://github.com/rfonod/geo-trax.git\ncd geo-trax \u0026\u0026 python -m pip install -e '.[dev]'\n```\n\n\u003e [!NOTE]\n\u003e The default model auto-downloads from [🤗 Hugging Face](https://huggingface.co/rfonod/geo-trax) on first use (cached in `~/.cache/huggingface/hub`, overridable via `HF_HOME`). To use your own weights, set `--model` or `extraction.model` in the config to a local `.pt` path or `hf://\u003corg\u003e/\u003crepo\u003e/\u003cpath/to/file\u003e.pt`.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAlternative Environments \u0026 Advanced Dev Install\u003c/b\u003e\u003c/summary\u003e\n\n**Create and activate a virtual environment** (any of the following):\n\n```bash\n# venv (standard library)\npython3.11 -m venv .venv\nsource .venv/bin/activate          # Windows: .venv\\Scripts\\activate\n\n# uv (fastest drop-in for venv + pip)\nuv venv --python 3.11\nsource .venv/bin/activate          # Windows: .venv\\Scripts\\activate\n\n# Miniconda\nconda create -n geo-trax python=3.11 -y\nconda activate geo-trax\n```\n\n**Install from PyPI** (runtime use). This installs the `geotrax` command-line interface together with the bundled configuration tree (`geotrax/cfg/`):\n\n```bash\npython -m pip install geo-trax    # pip\nuv pip install geo-trax           # uv (faster)\n```\n\n**Install from local source** (recommended for development or model training). Clone (or fork) the repository, then install in editable mode (`-e`), which reflects code changes without reinstalling:\n\n```bash\ngit clone https://github.com/rfonod/geo-trax.git   # add --depth 1 for the latest snapshot only\ncd geo-trax\npython -m pip install -e .         # pip\n# uv pip install -e .              # uv (faster; requires the uv venv above)\n# poetry install                   # Poetry (auto-manages its own virtualenv; skip the venv step)\n```\n\n**Optional dependency groups** (development/testing tools, ONNX export):\n\n```bash\npython -m pip install -e '.[dev]'      # development + test tooling\npython -m pip install -e '.[export]'   # ONNX export dependencies\n# uv pip install -e '.[dev]'           # uv equivalents\n# poetry install --extras dev          # Poetry equivalents\n# poetry install --extras export\n```\n\n**Optional CUDA for image matching.** The stabilization (`--stab-gpu`) and georeferencing (`--geo-gpu`) steps can be CUDA-accelerated on top of a source-built OpenCV. Installing into such an environment needs care so the CPU OpenCV wheels do not overwrite your build; see [GPU acceleration](#gpu-acceleration) for the full setup, install-without-clobbering recipes, and a benchmark. (Object *detection* already uses CUDA automatically when available, via `ultralytics.device`.)\n\n\u003c/details\u003e\n\n## Quick Start\n\n`data/U_video_cut.mp4` is a 5-second sample clip included for immediate testing. See [data/README.md](data/README.md) for matching orthophotos.\n\n```bash\n# Pixel-coordinate trajectories (no orthophoto required)\ngeotrax batch data/U_video_cut.mp4 --no-geo\n\n# Full pipeline: extract, georeference, and analyze (orthophotos required; see data/README.md)\ngeotrax batch data/U_video_cut.mp4 -orf data/orthophotos -mf data/master_frames --show-lanes\n\n# Scale up: process a whole project tree, then merge multi-drone results into one dataset\ngeotrax batch path/to/PROCESSED/\ngeotrax aggregate path/to/PROCESSED/\n```\n\nRun `geotrax -h` or `geotrax batch -h` for all options. The scale-up commands above run flag-free with the [recommended project structure](#real-world-deployment-the-songdo-experiment); any other layout works with explicit path flags.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e📋 Full Feature Overview\u003c/b\u003e\u003c/summary\u003e\n\n- **Detection**: YOLOv8s on aerial BEV imagery; detects car (incl. vans), bus, truck, and motorcycle.\n- **Tracking**: six multi-object trackers (BoT-SORT default); see [Tracking](#tracking) for a comparison; optional per-track frame-gap interpolation.\n- **Stabilization**: homography-based trajectory correction via [Stabilo](https://github.com/rfonod/stabilo) 🌀, tuned with [Stabilo-Optimize](https://github.com/rfonod/stabilo-optimize) 🎯; optional CUDA acceleration (`--stab-gpu`).\n- **Georeferencing**: frame-to-orthophoto registration; outputs lat/lon, local CRS, speed, acceleration, and lane assignment per vehicle; optional CUDA acceleration (`--geo-gpu`).\n- **Visualization**: track overlays on original, stabilized, or static-reference video, in five rendering modes (incl. oriented bounding boxes).\n- **Analysis**: trajectory maps, kinematic distributions, and class/dimension charts, per-video or aggregated across drones and sessions.\n- **Scaling \u0026 tooling**: batch-processes directory trees and aggregates multi-drone data; includes standalone utilities for end-to-end data preparation, training, evaluation, and validation.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e🚀 Planned Enhancements\u003c/b\u003e\u003c/summary\u003e\n\n- Comprehensive documentation in a dedicated `docs/` folder. A [`tools/README.md`](tools/README.md) index already covers the auxiliary scripts.\n- Modularized, OOP-based pipeline with custom reference frame support and georeferencing leveraging Stabilo's image-matching backend.\n- Per-class confidence thresholds.\n- SAHI-based small-object detection.\n- Batch inference and multi-thread processing.\n- Real-world map visualization (e.g., MovingPandas, contextily) and interactive web app.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e🔗 Related Projects\u003c/b\u003e\u003c/summary\u003e\n\nGeo-trax integrates with and complements several specialized tools:\n\n- **[Stabilo](https://github.com/rfonod/stabilo) 🌀**: Python library for video and trajectory stabilization using robust homography transformations. Supports various feature detectors, RANSAC algorithms, and user-defined masks. Used as Geo-trax's core stabilization engine.\n\n- **[Stabilo-Optimize](https://github.com/rfonod/stabilo-optimize) 🎯**: benchmarking and hyperparameter optimization framework for Stabilo. Evaluates stabilization performance through ground truth-free assessment using random perturbations. Used to fine-tune Geo-trax stabilization parameters.\n\n- **[HBB2OBB](https://github.com/rfonod/hbb2obb) 📦**: converts horizontal bounding boxes to oriented bounding boxes using SAM segmentation models. Can enhance Geo-trax outputs when object orientation is needed for downstream analysis.\n\n\u003c/details\u003e\n\n## Configuration\n\nThe entire pipeline is driven by a **single, self-contained YAML config**: one file for detection, tracking, stabilization, georeferencing, visualization, and plotting. Four presets ship with the package:\n\n| Preset | Focus |\n|--------|-------|\n| [`default`](geotrax/cfg/default.yaml) | Balanced baseline |\n| [`confident`](geotrax/cfg/confident.yaml) | Precision (fewer false positives) |\n| [`lenient`](geotrax/cfg/lenient.yaml) | Recall (catches more vehicles) |\n| [`stable`](geotrax/cfg/stable.yaml) | Stabilization quality |\n\n```bash\ngeotrax batch video.mp4 -c confident   # use a bundled preset by name\ngeotrax batch video.mp4 -c ./my.yaml   # use a custom config file\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e⚙️ Inspect, copy, and customize configs\u003c/b\u003e\u003c/summary\u003e\n\nManage the bundled configs with the `geotrax config` command:\n\n```bash\ngeotrax config show              # list bundled presets and their location\ngeotrax config show default      # print a preset's full contents\ngeotrax config copy              # copy presets into the current directory as \u003cname\u003e_copy.yaml\ngeotrax config copy -o ~/myproj  # copy into a specific directory\n```\n\nCopy a preset, edit it, then pass it with `-c`:\n\n```bash\ngeotrax config copy\n# edit default_copy.yaml ...\ngeotrax extract video.mp4 -c default_copy.yaml\n```\n\nTo switch the tracking algorithm, set `tracker.active` in the config (see [Tracking](#tracking)).\n\n\u003c/details\u003e\n\n## GPU acceleration\n\nObject **detection** already runs on CUDA automatically whenever a compatible GPU and PyTorch build are present (via the `ultralytics.device` config key, auto by default). The **stabilization** (`--stab-gpu`) and **georeferencing** (`--geo-gpu`) image-matching steps can *optionally* be CUDA-accelerated too, through [Stabilo](https://github.com/rfonod/stabilo) 1.3.0+. This needs a CUDA-enabled OpenCV build and is Linux/Windows only. Stabilo accelerates the **ORB** detector only, so `--geo-gpu` additionally requires `georef.matching.detector_name: orb`; there is no CPU fallback, so requesting GPU without a working CUDA device raises an error.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e⚡ Full CUDA setup \u0026 benchmarking guide\u003c/b\u003e\u003c/summary\u003e\n\nThroughout this guide, dotted names like `ultralytics.device` or `georef.matching.detector_name` are **keys in the pipeline config**, not CLI flags. To change them, copy the bundled config once and pass your copy with `-c`:\n\n```bash\ngeotrax config copy                 # writes default_copy.yaml in the current directory\n# edit default_copy.yaml, then pass it to any command:\ngeotrax batch \u003cvideo\u003e ... -c default_copy.yaml\n```\n\n### 1. Build OpenCV with CUDA\n\nThe PyPI OpenCV wheels are CPU-only, so you must build `opencv-contrib-python` from source with CUDA. Follow Stabilo's [`docs/cuda.md`](https://github.com/rfonod/stabilo/blob/main/docs/cuda.md), then confirm it works:\n\n```bash\npython -c \"import cv2; print(cv2.__version__, cv2.cuda.getCudaEnabledDeviceCount())\"   # expect: \u003cversion\u003e 1\n```\n\n\u003e **geo-trax needs one OpenCV module beyond the minimal stabilo build: add `video` to the `BUILD_LIST`.** The minimal list in stabilo's guide omits it, but the `sparseOptFlow` global-motion-compensation (GMC) method uses `cv2.calcOpticalFlowPyrLK` from that module. It is the default for the **BoT-SORT** (active default) and **TrackTrack** trackers, and an option for **DeepOCSORT** (`tracker.\u003cname\u003e.gmc_method`). GMC runs during **track association on the original, pre-stabilization frames**, so Stabilo does not make it redundant. Without `video` you get a `cv2 has no attribute 'calcOpticalFlowPyrLK'` warning and GMC silently falls back to identity. If you would rather not rebuild, set `gmc_method: none` for your active tracker (step 3), but that disables camera-motion compensation in tracking; for near-nadir BEV / quasi-stationary drone footage that is often acceptable, but check your tracking quality before relying on it.\n\n### 2. Install geo-trax without clobbering your CUDA OpenCV\n\n`ultralytics[extra]` transitively requires `opencv-python` and `opencv-python-headless`, both CPU wheels. A plain install drops them on top of your compiled `cv2/` and disables CUDA. A transitive dependency cannot be excluded in `pyproject.toml`, so use one of these:\n\n**A. Install, then restore your wheel (recommended).** Let pip install everything, then overwrite the CPU OpenCV by reinstalling the CUDA `opencv_contrib_python-*.whl` you built in step 1:\n\n```bash\npip install -e .          # installs torch/cuda/ultralytics/..., plus (temporarily) CPU opencv\n# reinstall the wheel produced by your CUDA OpenCV build in step 1 (path is wherever you built it):\npip install --force-reinstall --no-deps /path/to/opencv_contrib_python-*.whl\npython -c \"import cv2; print(cv2.cuda.getCudaEnabledDeviceCount())\"   # expect: 1\n```\n\n\u003e If the CUDA check above reports `0` devices (or a `cv2`/CUDA error such as `module 'cv2' has no attribute 'cuda'`) immediately after the reinstall, re-activate the CUDA venv (`source .venv-cuda/bin/activate`, matching stabilo's `docs/cuda.md`) and run it again; if it persists, confirm `cv2.__file__` points into that venv's `site-packages` (see the troubleshooting in stabilo's `docs/cuda.md`). It should then report `1`.\n\u003e\n\u003e ⚠️ Afterwards `opencv-python` / `opencv-python-headless` stay registered but their recorded files now belong to your build. **Never** `pip uninstall opencv-python` / `opencv-python-headless`, or you will delete the shared `cv2/`.\n\n**B. Stub the CPU wheels first (no download, cleaner metadata).** Install two metadata-only packages so pip treats the requirements as already satisfied and never fetches a CPU wheel:\n\n```bash\nCVVER=$(python -c \"import cv2; print(cv2.__version__)\")\nfor pkg in opencv-python opencv-python-headless; do\n  d=$(mktemp -d)\n  cat \u003e \"$d/pyproject.toml\" \u003c\u003cEOF\n[build-system]\nrequires = [\"setuptools\"]\nbuild-backend = \"setuptools.build_meta\"\n[project]\nname = \"$pkg\"\nversion = \"$CVVER\"\n[tool.setuptools]\npy-modules = []\nEOF\n  pip install --no-deps \"$d\" \u0026\u0026 rm -rf \"$d\"\ndone\npip install -e .          # opencv is satisfied by the stubs; your CUDA build is untouched\n```\n\n### 3. Enable GPU per stage\n\n| Stage | How | Notes |\n|-------|-----|-------|\n| Detection | automatic | `ultralytics.device` (auto = CUDA when available) |\n| Stabilization | `--stab-gpu` | works out of the box (default detector is ORB) |\n| Georeferencing | `--geo-gpu` + `detector_name: orb` | set via a copied config (below) |\n\nOnly ORB is CUDA-accelerated, and the georeferencing detector is not a CLI flag, so enable georef GPU through a copied config:\n\n```bash\ngeotrax config copy                       # writes default_copy.yaml\n# edit default_copy.yaml:\n#   georef.matching.detector_name: orb    # required for --geo-gpu\n#   tracker.botsort.gmc_method: none      # ONLY if you did not add 'video' to BUILD_LIST (step 1); disables tracker GMC, verify tracking quality\n```\n\nThese are the GPU-accelerated analogues of the two reproduce commands in [`data/README.md`](data/README.md). Their outputs are *equivalent, not identical* to the committed `data/results-pixel/` and `data/results-full/`: the CUDA path georeferences with ORB (RootSIFT is not GPU-accelerated), GPU feature extraction is not bit-identical to CPU, and with `gmc_method: none` the tracker runs without motion compensation.\n\n```bash\n# Pixel-coordinate results: GPU stabilization, no georeferencing\ngeotrax batch data/U_video_cut.mp4 --no-geo --show-class-names --show-conf --stab-gpu -c default_copy.yaml\n\n# Full pipeline: GPU stabilization + georeferencing (needs the orthophoto/segmentation/master-frame data; see data/README.md)\ngeotrax batch data/U_video_cut.mp4 -orf data/orthophotos -osf data/segmentations -mf data/master_frames \\\n  --show-lanes --plot-segmentations -vm 0 3 --stab-gpu --geo-gpu -c default_copy.yaml\n```\n\n### 4. Benchmark: detection-only CUDA vs fully CUDA\n\nTo isolate the GPU effect, disable the CPU-bound visualization and plotting (`--no-save --no-show --no-plot-save --no-plot-show`) and wipe a scratch output folder before each run (a clean, non-skipped run that leaves your real results untouched). Do **not** pass `--recompute`: the master→orthophoto homography is a one-time, expensive registration normally cached in the master-frames folder, so leaving it cached keeps the benchmark on the per-video work (detection, stabilization, reference→master registration).\n\nThree configurations are compared:\n- **geo-trax default (CPU)**: the shipped defaults with RootSIFT georeferencing; what a CPU-only user actually runs.\n- **ORB (CPU)**: the GPU-tuned config on CPU; a controlled, same-config baseline for the GPU run.\n- **fully CUDA**: the same ORB config with `--stab-gpu --geo-gpu`.\n\nThe default run needs no config file: with no `-c`, geo-trax uses its shipped defaults (RootSIFT georeferencing, CPU for both stabilization and georeferencing). The ORB runs use your `default_copy.yaml` (`georef.matching.detector_name: orb`, `tracker.botsort.gmc_method: none`).\n\nWith [hyperfine](https://github.com/sharkdp/hyperfine):\n\n```bash\nOUT=/tmp/geotrax_bench\nCOMMON=\"geotrax batch data/U_video_cut.mp4 -orf data/orthophotos -osf data/segmentations -mf data/master_frames --no-save --no-show --no-plot-save --no-plot-show -of $OUT\"\nhyperfine --warmup 1 --runs 5 --prepare \"rm -rf $OUT\" \\\n  -n \"geo-trax default (CPU, RootSIFT)\" \"$COMMON\" \\\n  -n \"ORB (CPU)\"                        \"$COMMON -c default_copy.yaml\" \\\n  -n \"fully CUDA (ORB)\"                 \"$COMMON -c default_copy.yaml --stab-gpu --geo-gpu\"\n```\n\nOr without extra tools:\n\n```bash\nbench () { local label=\"$1\"; shift; rm -rf /tmp/geotrax_bench\n  local t0=$(date +%s.%N)\n  geotrax batch data/U_video_cut.mp4 -orf data/orthophotos -osf data/segmentations -mf data/master_frames \\\n    --no-save --no-show --no-plot-save --no-plot-show -y -o -of /tmp/geotrax_bench \"$@\" \u003e/tmp/bench.log 2\u003e\u00261 \\\n    \u0026\u0026 awk -v a=\"$t0\" -v b=\"$(date +%s.%N)\" -v l=\"$label\" 'BEGIN{printf \"%-28s %.1f s\\n\", l, b-a}' \\\n    || echo \"$label FAILED (see /tmp/bench.log)\"; }\nbench \"geo-trax default (CPU)\"\nbench \"ORB (CPU)\"        -c default_copy.yaml\nbench \"fully CUDA (ORB)\" -c default_copy.yaml --stab-gpu --geo-gpu\n```\n\nExample on an **NVIDIA RTX 4090** (5-second sample clip, 150 frames; hyperfine mean ± σ over 5 runs):\n\n\u003cdetails\u003e\n\u003csummary\u003eBenchmark system\u003c/summary\u003e\n\n- **OS**: Ubuntu 24.04.4 LTS (Linux 6.8, x86_64)\n- **CPU**: 13th Gen Intel Core i9-13900KF (24 cores / 32 threads)\n- **RAM**: 62 GiB\n- **GPU**: NVIDIA GeForce RTX 4090 (24 GB, driver 580.159.03)\n- **Software**: Python 3.11.5, torch 2.12.1 (CUDA 13.0), OpenCV 4.13.0, stabilo 1.3.0\n\n\u003c/details\u003e\n\n| Pipeline | Wall time | vs default |\n|----------|-----------|------------|\n| CPU stabilization and georeferencing (geo-trax defaults) | 273.7 ± 0.9 s | 1× |\n| CPU stabilization and georeferencing (GPU-matched config) | 348.4 ± 0.4 s | 0.8× |\n| Fully CUDA (GPU-matched config) | 16.4 ± 2.0 s | **16.7×** |\n\n\u003e Fully CUDA is **16.7× faster than the geo-trax default** and **21.3× faster than the same ORB config on CPU** (row 2). Row 1 is the shipped defaults (RootSIFT georeferencing); rows 2–3 use the GPU-matched config, which switches georeferencing to ORB so the CPU and GPU runs do identical work (only ORB is CUDA-accelerated). Row 2 is slower than row 1 because ORB at a 250k feature ceiling with brute-force matching is costlier on CPU than RootSIFT. Treat these as a **relative** comparison, not absolute throughput: geo-trax's defaults favor maximum accuracy and reliability, with detection at **1920×1920**, stabilization at only a **0.5 downscale** (roughly 2K per frame on this 4K clip) with a high `max_features` ceiling, and RootSIFT georeferencing with a very high `max_features` and conservative MAGSAC++ matcher/projection settings, all against an **8000×8000** orthophoto. Lighter settings would cut absolute times across the board; the point is the CPU→GPU ratio.\n\n\u003c/details\u003e\n\n## Detection Model\n\nThe default detector is **YOLOv8s** (HBB, 1920 × 1920 px, ~11 M parameters), trained on more than 19,000 annotated aerial images (~679k labeled vehicle instances) and fine-tuned on a curated, high-quality subset. It is hosted on [🤗 Hugging Face](https://huggingface.co/rfonod/geo-trax) and **downloads automatically on first use**. Results on the Songdo Vision test split (1,084 images; full results in [Table 3](https://doi.org/10.1016/j.trc.2025.105205)):\n\n| ID | Label | Precision | Recall | mAP@50 | mAP@50-95 |\n|---|---|---|---|---|---|\n| 0 | Car (incl. vans) | 0.979 | 0.981 | 0.992 | 0.835 |\n| 1 | Bus | 0.952 | 0.977 | 0.988 | 0.826 |\n| 2 | Truck | 0.887 | 0.916 | 0.935 | 0.722 |\n| 3 | Motorcycle | 0.827 | 0.866 | 0.888 | 0.463 |\n| **All** | | **0.911** | **0.935** | **0.951** | **0.711** |\n\n\u003e Pedestrian and bicycle classes exist in the weights but are underrepresented, unevaluated, and filtered by default. See the [model card](https://huggingface.co/rfonod/geo-trax) for full details.\n\nTo use a different model, point `--model` (CLI) or `extraction.model` (config) to a local `.pt` path or `hf://\u003corg\u003e/\u003crepo\u003e/\u003cfile\u003e.pt`; any [Ultralytics](https://github.com/ultralytics/ultralytics)-compatible model works.\n\n### Custom Model Training\n\nTraining and export scripts for custom YOLO detectors live in `train/`, with a SLURM wrapper for HPC clusters. See [train/README.md](train/README.md).\n\n## Tracking\n\nSix multi-object trackers ship with [Ultralytics](https://github.com/ultralytics/ultralytics) `\u003e=8.4.63`. Selection is config-driven: set `tracker.active`, no code changes needed. Default: **BoT-SORT**.\n\n| Tracker | `tracker.active` | ReID | GMC¹ | Pros | Cons |\n|---------|------------------|:----:|:----:|------|------|\n| **BoT-SORT** (default) | `botsort` | opt | ✅ | Strong accuracy; motion + optional appearance | Slower; ReID adds compute |\n| **ByteTrack** | `bytetrack` | ❌ | ❌ | Fastest; two-stage association | More ID switches under occlusion |\n| **OC-SORT** | `ocsort` | ❌ | ❌ | Robust to non-linear motion; lightweight | Weaker on long occlusions |\n| **Deep OC-SORT** | `deepocsort` | opt | opt | OC-SORT + appearance; dense scenes | Heaviest variant with ReID |\n| **FastTracker** | `fasttrack` | ❌ | ❌ | Occlusion-aware ByteTrack variant | Newer; several knobs to tune |\n| **TrackTrack** | `tracktrack` | opt | ✅ | Multi-cue cost; best ID retention | Most parameters; highest compute |\n\n¹ GMC (in-tracker camera-motion compensation) runs during tracking and is independent of Stabilo's post-hoc trajectory stabilization stage.\n\n\u003e 💡 Run `geotrax config show default` to print the full `tracker:` block, with every parameter for all six trackers documented inline. Run `geotrax config copy` to get an editable local copy. For a head-to-head comparison on your own data, see [`tools/compare_tracking.py`](tools/compare_tracking.py).\n\n## Usage\n\nThe `geotrax` CLI provides one subcommand per stage: `batch` (primary entry point), `extract`, `georeference`, `visualize`, `plot`, `aggregate`, and `config`. Run `geotrax -h` or `geotrax \u003csubcommand\u003e -h` for the full reference (`python -m geotrax` works identically).\n\n```bash\n# Recursively process a directory (or a single video) without georeferencing\ngeotrax batch path/to/videos/ --no-geo\n\n# Run an individual stage on its own\ngeotrax extract video.mp4                  # detect, track, and stabilize\ngeotrax visualize video.mp4 --save         # render an annotated video from existing results\ngeotrax plot video.mp4                     # trajectory and distribution plots\n```\n\n\u003e [!TIP]\n\u003e See [data/README.md](data/README.md) for sample data and testing examples.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e💡 More Examples \u0026 Advanced Usage\u003c/b\u003e\u003c/summary\u003e\n\n```bash\n# Use a custom config (bundled preset by name, or a path to your own file)\ngeotrax batch video.mp4 -c confident\ngeotrax batch video.mp4 -c path/to/custom_config.yaml\n\n# Fill per-track detection gaps with linear interpolation (adds is_interpolated column to .txt output)\ngeotrax batch video.mp4 --no-geo --interpolate\n\n# Regenerate visualization without re-running extraction\ngeotrax batch video.mp4 --viz-only --save\n\n# Show lane IDs, hide the speed overlay (requires georeferencing)\ngeotrax batch video.mp4 --viz-only --save --show-lanes --hide-speed\n\n# Georeference an already-extracted video against orthophotos\ngeotrax georeference video.mp4 -orf path/to/orthophotos -mf path/to/master_frames\n\n# Aggregated trajectory plots, excluding buses and trucks\ngeotrax batch path/to/PROCESSED/ --plot-only --plot-aggregate --plot-class-filter 1 2\n\n# Merge multi-drone results for the same locations into a unified dataset\ngeotrax aggregate path/to/PROCESSED/\n\n# Rotated box modes (3/4): boxes oriented to vehicle heading, on original (3) or stabilized (4) frame\ngeotrax visualize video.mp4 --save --viz-mode 3 4\n```\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/rfonod/geo-trax/main/assets/geo-trax_visualization_obb_zoom.jpg\" alt=\"Geo-trax mode 3 rotated bounding boxes, zoomed detail\" width=\"70%\"\u003e\u003cbr\u003e\u003cem\u003eMode 3 rotated bounding boxes, zoomed detail from the same scene as the animation above.\u003c/em\u003e\u003c/p\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e⚠️ Rotated box modes (3 and 4): known limitations\u003c/b\u003e\u003c/summary\u003e\n\nModes 3 and 4 replace the standard axis-aligned YOLO detections with **rotated bounding boxes**: each box is sized to the vehicle's estimated physical length and width and rotated to align with its travel direction (heading). The heading is derived from the camera-motion-free stabilized trajectory; mode 3 projects the result back onto the original frame, while mode 4 draws directly on the stabilized frame. They are the most informative rendering modes but also the most sensitive to data quality:\n\n- **Size estimation may fail.** Estimates are computed over frames where the vehicle moves in a nearly straight path with sufficient displacement. Short tracks, near-stationary vehicles, or detections close to the frame edges can yield no usable estimate. In those cases the box dimensions fall back to a per-vehicle Q25 aggregate of the raw YOLO bounding box extents (rotated to the heading), which tends to be inflated during turns since axis-aligned detections expand as the vehicle turns. Fallback boxes are rendered with a **dashed outline** so they are easy to identify.\n- **Heading may be unreliable.** For very slow or stationary vehicles the motion direction cannot be determined; the box is then aligned with the longer axis of the raw bounding box instead.\n- **Back-projection distortion (mode 3 only).** Oriented boxes are computed in stabilized space and projected back onto the original frame via the inverse stabilization homography. Under strong camera motion this can produce visibly skewed boxes.\n- **Edge clipping is approximate.** When a vehicle is entering or exiting the frame, the detection only covers its visible part, so the oriented box is clipped to that footprint instead of being drawn at full size (the clip is triggered once the detection reaches within `edge_clip_margin` pixels of the border, since the YOLO box may stop a few pixels short of the true edge). That footprint, however, is only known as an axis-aligned (HBB) detection box, whereas the rendered box is rotated to the heading — so the clip boundary only approximates where the rotated vehicle actually leaves the frame. The clip rectangle is temporally smoothed (`edge_clip_smoothing`) so the box shrinks steadily as the vehicle exits rather than jumping with per-frame detection noise.\n- Both modes require that the extraction stage was run with stabilization enabled (`stabilize: true` in the config).\n\n\u003c/details\u003e\n\n\u003e [!NOTE]\n\u003e **Why use master frames?** When georeferencing, geo-trax can route each video's homography through a shared *master frame* per location ID. A master frame is a high-quality, near-nadir BEV frame chosen once per location (see [`tools/find_master_frames.py`](tools/find_master_frames.py)), used instead of registering every video's reference frame directly to the orthophoto. The mapping is split into two homographies: `reference → master` (recomputed per video) and `master → orthophoto` (computed **once per location ID and cached**, validated by a hash of the master image). This gives two benefits:\n\u003e - **Speed**: the expensive cross-domain `master → orthophoto` registration runs once and is reused across every drone and flight at that location, instead of once per video.\n\u003e - **Consistency \u0026 robustness**: every video is matched against the *same* master frame. This same-modality BEV-to-BEV registration is far more reliable than a direct BEV-to-orthophoto match, so trajectories from different drones, altitudes, and viewpoints resolve into one coherent coordinate system.\n\u003e\n\u003e Master frames are enabled by default. Disable them with `--no-master`, or force re-computation of the cached `master → orthophoto` homography with `--recompute`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e📁 Output file formats\u003c/b\u003e\u003c/summary\u003e\n\nSuppose the input video is `video_file.mp4`. By default, outputs are written to a `results/` sub-folder next to the input; the folder and all filename postfixes are configurable via the `output:` section of the pipeline config (or `--output-folder` / `-of` for the folder).\n\n- **video_file.txt** (`\u003cstem\u003e\u003ctracks_postfix\u003e.txt`): Contains the extracted vehicle trajectories in the following format:\n\n  ```text\n  frame_id, vehicle_id, x_c(unstab), y_c(unstab), w(unstab), h(unstab), x_c(stab), y_c(stab), w(stab), h(stab), class_id, confidence, vehicle_length, vehicle_width\n  ```\n\n    where:\n  - `frame_id`: Frame number (0, 1, ...).\n  - `vehicle_id`: Unique vehicle identifier (1, 2, ...).\n  - `x_c(unstab)`, `y_c(unstab)`: Unstabilized vehicle centroid coordinates.\n  - `w(unstab)`, `h(unstab)`: Unstabilized vehicle bounding box width and height.\n  - `x_c(stab)`, `y_c(stab)`: Stabilized vehicle centroid coordinates.\n  - `w(stab)`, `h(stab)`: Stabilized vehicle bounding box width and height.\n  - `class_id`: Vehicle class identifier (0: car (incl. vans), 1: bus, 2: truck, 3: motorcycle)\n  - `confidence`: Detection confidence score (0-1).\n  - `vehicle_length`, `vehicle_width`: Estimated vehicle dimensions in pixels.\n  - `is_interpolated` *(optional, 15th column)*: Present only when `extraction.interpolate: true` (CLI: `--interpolate`). `0` = real detection, `1` = linearly interpolated to fill a frame gap. Gaps larger than the active tracker's `track_buffer` are left unfilled (the tracker would not persist a lost track's ID across a longer occlusion).\n\n- **video_file_vid_transf.txt** (`\u003cstem\u003e\u003cstab_transform_postfix\u003e.txt`): Contains the transformation matrix for each frame in the format:\n\n  ```text\n  frame_id, h11, h12, h13, h21, h22, h23, h31, h32, h33\n  ```\n\n    where:\n  - `frame_id`: Frame number of the stabilized frame (starts from `cut_frame_left + 1` since the reference frame itself has no transform).\n  - `hij`: Elements of the 3x3 homography matrix that maps each frame (`frame_id`) to the reference frame.\n\n- **video_file.yaml**: Video metadata and the configuration settings used for processing `video_file.mp4`. (This file is saved in the same directory as the input video, not in the output folder.)\n\n- **video_file_mode_X.mp4** (`\u003cstem\u003e\u003cvisualization_postfix\u003e_mode_\u003cX\u003e.mp4`): Annotated video in five rendering modes (X = 0 / 1 / 2 / 3 / 4):\n  - **Mode 0**: overlaid on the original (unstabilized) video\n  - **Mode 1**: overlaid on the stabilized video\n  - **Mode 2**: plotted on the static reference frame\n  - **Mode 3**: rotated bounding boxes on the original video, where each box is sized to the vehicle's estimated physical dimensions and rotated to its per-frame heading (derived from the camera-motion-free stabilized trajectory and projected back onto the original frame). Requires stabilization to have been run.\n  - **Mode 4**: the same rotated bounding boxes as Mode 3, but drawn directly on the stabilized video (no back-projection). Requires stabilization to have been run.\n\n  Each version can display vehicle bounding boxes, IDs, class labels, confidence scores, and short trajectory trails that fade and vary in thickness to indicate the recency of the movement. If an input `video_file.csv` file is available in the same directory as the input video, i.e., the converted flight logs, vehicle speed and lane information can also be displayed.\n\n- **video_file.csv** (`\u003cstem\u003e\u003cgeoreferenced_postfix\u003e.csv`): Contains the georeferenced vehicle trajectories in a tabular format. This file includes both geographic and local coordinates, estimated real-world dimensions, kinematic data, road section, and lane information. The columns are:\n\n  ```text\n  Vehicle_ID, [Timestamp,] Frame_Number, Ortho_X, Ortho_Y, Local_X, Local_Y, Latitude, Longitude, Vehicle_Length, Vehicle_Width, Vehicle_Class, Vehicle_Speed, Vehicle_Acceleration, Road_Section, Lane_Number, Visibility[, Is_Interpolated]\n  ```\n\n    where:\n  - `Vehicle_ID`: Unique vehicle identifier.\n  - `Timestamp`: Timestamp of the frame (YYYY-MM-DD HH:MM:SS.ms). Present only when a flight-log CSV with timestamps is available alongside the video.\n  - `Frame_Number`: Video frame index corresponding to this detection.\n  - `Ortho_X`, `Ortho_Y`: X and Y coordinates of the vehicle centroid in the orthophoto's pixel coordinate system.\n  - `Local_X`, `Local_Y`: X and Y coordinates of the vehicle centroid in a local projected coordinate system (e.g., EPSG:5186 for KGD2002 / Central Belt 2010 used in the Songdo experiment).\n  - `Latitude`, `Longitude`: Geographic coordinates of the vehicle centroid (WGS84).\n  - `Vehicle_Length`, `Vehicle_Width`: Estimated vehicle dimensions in meters.\n  - `Vehicle_Class`: Vehicle class identifier (0: car (incl. vans), 1: bus, 2: truck, 3: motorcycle).\n  - `Vehicle_Speed`: Estimated vehicle speed in km/h.\n  - `Vehicle_Acceleration`: Estimated vehicle acceleration in m/s$^2$.\n  - `Road_Section`: Identifier for the road segment the vehicle is on.\n  - `Lane_Number`: Identifier for the lane the vehicle is in.\n  - `Visibility`: Boolean indicating if the vehicle's bounding box is fully visible within the frame.\n  - `Is_Interpolated` *(optional)*: Present only when extraction was run with `--interpolate` (`extraction.interpolate: true`). `0` = real detection, `1` = row synthesized by linear interpolation at the extraction stage to fill a frame gap; propagated from the `.txt` tracks file.\n\n- **video_file_geo_transf.txt** (`\u003cstem\u003e\u003cgeo_transform_postfix\u003e.txt`): Contains the 3x3 georeferencing transformation matrix (homography) that maps points from the video's reference frame to the orthomap. The format is a comma-separated list of the 9 matrix elements:\n\n  ```text\n  h11, h12, h13, h21, h22, h23, h31, h32, h33\n  ```\n\n**Note:** *All output files (except `video_file.yaml`) are saved in the configured output folder (default: `results/` sub-folder next to the input video). Trajectory and distribution plots are always written to a `plots/` sub-folder inside the output folder.*\n\n\u003c/details\u003e\n\n## Real-World Deployment: The Songdo Experiment\n\nGeo-trax was validated in a large-scale urban traffic monitoring campaign in Songdo, South Korea, where it processed footage from a fleet of 10 drones to produce the [**Songdo Traffic**](https://doi.org/10.5281/zenodo.13828383) dataset. The detection model was trained on the companion [**Songdo Vision**](https://doi.org/10.5281/zenodo.13828407) dataset. Both are described in the [publication](#citation).\n\n| Songdo campaign | |\n|---|---|\n| 📍 Location | Songdo International Business District, South Korea |\n| 📅 Duration | 4 days (October 4 to 7, 2022) |\n| 🚁 Fleet | 10 drones (DJI Mavic 3), 140 to 150 m altitude, 4K at 29.97 fps |\n| 🔭 Coverage | 20 busy intersections |\n| 🚗 Result | ~700,000 georeferenced vehicle trajectories |\n\n🎥 *Demo of Geo-trax applied to the Songdo experiment:* [https://youtu.be/gOGivL9FFLk](https://youtu.be/gOGivL9FFLk)\n\nThe blocks below document the project layout and data-wrangling workflow used in that campaign; they double as the recommended setup for your own multi-drone projects.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e📂 Recommended project folder structure\u003c/b\u003e\u003c/summary\u003e\n\nThe layout below mirrors the Songdo experiment and matches the pipeline's auto-detection defaults, letting `geotrax batch` run with no path flags. Two conventions do the heavy lifting:\n\n- **A `PROCESSED/` folder anchors auto-detection.** When georeferencing or plotting needs orthophotos, master frames, or segmentations and no explicit path is given, Geo-trax walks *up* from the video until it finds `PROCESSED`, then looks for a sibling `ORTHOPHOTOS/` folder.\n- **A location ID ties each video to its assets.** The location ID is the leading letters in the clip filename (`A1.mp4` → `A`), so `A1.mp4` automatically resolves to `ORTHOPHOTOS/A.png`, `ORTHOPHOTOS/master_frames/A.png`, and `ORTHOPHOTOS/segmentations/A.csv`.\n\n### Directory tree\n\n```text\n\u003cproject\u003e/                                 # project root (name arbitrary)\n├── RAW/                                   # untouched drone footage + flight logs (never modified)\n│   └── 2022-10-07/D1/PM1/                 # arbitrary nesting, e.g. date / drone / session\n│       ├── DJI_0001.MP4  DJI_0001.SRT\n│       └── DJI_0002.MP4  DJI_0002.SRT     # drone splits a recording into segments (file-size limit)\n├── PROCESSED/                             # pipeline input (auto-detect anchor)\n│   └── 2022-10-07/D1/PM1/\n│       ├── 0_merged.mp4  0_merged.srt     # merged flight video + log (temporary, deletable)\n│       ├── 0_merged.txt                   # cut list: start/end frames, one cut per line (temporary)\n│       ├── A1.mp4  A1.csv                 # cut clip + flight log; 'A' = location ID, '1' = sequence\n│       ├── A2.mp4  A2.csv                 # next clip at the same location\n│       ├── A1.yaml                        # run metadata, saved next to the clip (not in results/)\n│       └── results/                       # pipeline outputs, written next to each clip\n│           ├── A1.txt                     # pixel-coordinate tracks\n│           ├── A1_vid_transf.txt          # stabilization homographies\n│           ├── A1_geo_transf.txt          # georeferencing homography\n│           ├── A1.csv                     # georeferenced trajectories + kinematics\n│           ├── A1_mode_0.mp4              # video with overlaid boxes \u0026 trajectories (modes 0/1/2/3/4)\n│           └── plots/                     # various trajectory \u0026 distribution plots\n├── ORTHOPHOTOS/                           # auto-detected sibling of PROCESSED / DATASET\n│   ├── A.png                              # orthophoto cut-out, per location\n│   ├── A.txt  (or A.tif)                  # georeferencing parameters (or a georeferenced GeoTIFF)\n│   ├── ortho_parameters.txt               # (alternative) shared params + per-location A_center.txt\n│   ├── master_frames/                     # optional; consistent reference frame per location\n│   │   ├── A.png                          #   reference frame image\n│   │   └── A.txt                          #   cached master-\u003eortho homography\n│   └── segmentations/                     # optional; per-location lane/road geometry\n│       ├── A.csv                          #   lane \u0026 road-section polygons\n│       └── A.png                          #   overlay image (used for plotting only)\n└── DATASET/                               # `geotrax aggregate` output (sibling of PROCESSED)\n    └── 2022-10-07_A/                      # one intersection-day\n        ├── 2022-10-07_A_AM1.csv           # one CSV per flight session (AM1-AM5, PM1-PM5),\n        └── 2022-10-07_A_PM1.csv           #   trajectories merged across drones for that session\n```\n\n`RAW/` is kept immutable; everything downstream lives under `PROCESSED/`. The `master_frames/` and `segmentations/` sub-folders are optional; provide them only when you need cross-flight georeferencing consistency or lane-level analysis. `DATASET/` is created by `geotrax aggregate` and is also a valid auto-detection anchor for `ORTHOPHOTOS/`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e🏷️ Clip naming conventions\u003c/b\u003e\u003c/summary\u003e\n\nOnly the **leading location letters** of a clip filename are required by the code (parsed by `determine_location_id`). The contextual metadata (date, drone, session) normally lives in the **folder path**, so each clip can be named compactly as location ID + sequence number:\n\n```text\n2022-10-07/D10/PM5/U1.mp4\n│          │   │   └── clip: location ID 'U' + sequence number '1'\n│          │   └────── flight session: AM1-AM5 (morning) / PM1-PM5 (afternoon)\n│          └────────── drone ID (D1, D2, ...)\n└───────────────────── capture date (ISO 8601, YYYY-MM-DD)\n```\n\nThese compact names are assigned automatically by the cutting step, not typed by hand: given a location map (a JSON file pairing each label with its `[lat, lon]` center), [`tools/cut_merged_videos_and_logs.py`](tools/cut_merged_videos_and_logs.py) labels every clip with the location nearest to its GPS centroid and appends a per-location sequence number (`U1`, `U2`, ...).\n\nBecause only the leading letters matter, the same context can instead be packed into a single self-contained filename when clips are detached from this tree. This is how the sample videos published on Zenodo are named, e.g. `U_D10_2022-10-07_PM5_60s.mp4` (location `U`, drone `D10`, date `2022-10-07`, session `PM5`). Here the per-location sequence number is replaced by a time marker showing where the clip falls within the session: `60s` denotes the first 60 seconds of that session at the location. Either way, the code still extracts location `U`.\n\n| Clip filename | Location ID | Resolves to |\n|---|---|---|\n| `U1.mp4` | `U` | `ORTHOPHOTOS/U.png`, `master_frames/U.png`, `segmentations/U.csv` |\n| `U2.mp4` | `U` | `ORTHOPHOTOS/U.png`, … |\n| `U_D10_2022-10-07_PM5_60s.mp4` | `U` | `ORTHOPHOTOS/U.png`, … |\n\n`geotrax aggregate` groups results by location (and date/session), merging clips from different drones that cover the same place into a unified dataset.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e🛠️ From raw footage to trajectories\u003c/b\u003e\u003c/summary\u003e\n\nThe `tools/` directory provides the wrangling scripts that take you from raw footage to pipeline-ready clips (see [`tools/README.md`](tools/README.md) for the full index):\n\n1. **Merge** the recorded video segments and their logs into one video + log per flight session → [`tools/merge_videos_and_logs.py`](tools/merge_videos_and_logs.py)\n2. **Cut** each merged flight into per-location clips: list the start/end frames of each stable hover in `0_merged.txt`, then split (converting the DJI SRT log to a per-clip CSV) → [`tools/cut_merged_videos_and_logs.py`](tools/cut_merged_videos_and_logs.py)\n3. **QA / repair** the cut logs → [`tools/find_cut_video_issues.py`](tools/find_cut_video_issues.py), [`tools/fix_timestamp_anomalies.py`](tools/fix_timestamp_anomalies.py), [`tools/interpolate_missing_timestamps.py`](tools/interpolate_missing_timestamps.py)\n4. **Build the georeferencing assets**: orthophoto cut-outs per location → [`tools/subset_orthophoto.py`](tools/subset_orthophoto.py); master frames → [`tools/find_master_frames.py`](tools/find_master_frames.py); lane segmentations are drawn manually, with overlays rendered via [`tools/viz_segmentations.py`](tools/viz_segmentations.py)\n5. **Run the pipeline**: `geotrax batch PROCESSED/ ...`; orthophotos, master frames, and segmentations are auto-detected from the sibling `ORTHOPHOTOS/` folder.\n6. **(Optional) Aggregate** results across drones and flights for the same location → `geotrax aggregate PROCESSED/`, which writes a unified dataset to a sibling `DATASET/` folder.\n\n### Lessons from the Songdo experiment\n\n- Treat `RAW/` as read-only archival storage and derive everything under `PROCESSED/`; the wrangling steps are reproducible from the raw footage.\n- The **master frame** is an intermediary coordinate system per location: aligning every flight to one shared reference frame keeps trajectories from different drones, altitudes, and viewpoints in a single consistent coordinate system.\n- Coordinates were projected to a local CRS (EPSG:5186, KGD2002 / Central Belt 2010) alongside WGS84 lat/lon; set your own CRS in the `georef:` config section.\n- Imagery was captured at ~140–150 m altitude in 4K, giving a ground sampling distance of ≈ 0.027 m/px (the default `extraction.gsd`). Re-tune the GSD for different altitudes or cameras.\n\n\u003c/details\u003e\n\n## Citation\n\nIf you use **Geo-trax** in your research or software, please cite:\n\n1. **Journal article** (preferred for any use of the framework):\n\n    ```bibtex\n    @article{fonod2025advanced,\n      title = {Advanced computer vision for extracting georeferenced vehicle trajectories from drone imagery},\n      author = {Fonod, Robert and Cho, Haechan and Yeo, Hwasoo and Geroliminis, Nikolas},\n      journal = {Transportation Research Part C: Emerging Technologies},\n      volume = {178},\n      pages = {105205},\n      year = {2025},\n      publisher = {Elsevier},\n      doi = {10.1016/j.trc.2025.105205},\n      url = {https://doi.org/10.1016/j.trc.2025.105205}\n    }\n    ```\n\n2. **Software archive** (when referencing or building on the code itself):\n\n    ```bibtex\n    @software{fonod2026geo-trax,\n      author = {Fonod, Robert},\n      title = {Geo-trax: A Comprehensive Framework for Georeferenced Vehicle Trajectory Extraction from Drone Imagery},\n      year = {2026},\n      month = jul,\n      version = {1.2.0},\n      doi = {10.5281/zenodo.12119542},\n      url = {https://github.com/rfonod/geo-trax},\n      license = {MIT}\n    }\n    ```\n\n## Contributions\n\nEarly code received key contributions from [Haechan Cho](https://github.com/cho-96) (georeferencing) and [Sohyeong Kim](https://github.com/shgold) (video/flight-log merging). Community contributions are welcome: open a [GitHub Issue](https://github.com/rfonod/geo-trax/issues) or submit a pull request.\n\n## License\n\nThis project is distributed under the MIT License. See the [LICENSE](LICENSE) for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frfonod%2Fgeo-trax","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frfonod%2Fgeo-trax","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frfonod%2Fgeo-trax/lists"}