Initial import: World-UAV prepro

Add dataloaders (v1/v2), analysis scripts, and documentation for working with UAV-GeoLoc (World-UAV).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-09 12:44:49 +03:00
commit 4ff36ce188
72 changed files with 13594 additions and 0 deletions

175
README.md Normal file
View File

@@ -0,0 +1,175 @@
# World-UAV-prepro
Эта папка — **мой слой препроцесса/аналитики** поверх датасета **UAV-GeoLoc (World-UAV)**.
Здесь нет обучения моделей. Основные артефакты:
- `dataloader.py`: компактный PyTorch `Dataset`/`DataLoader` для train/eval по индекс-файлам `Index/*.txt`.
- `dataloader_v2.py`: расширенная версия лоадера с парсингом метаданных (height/rotation), утилитами GPS/локализационной ошибки и scene-based лоадером для кастомных сплитов.
- `analyze/`: оффлайн-скрипты, которые **проверяют структуру датасета**, **схему нарезки спутника**, и **генерируют графики/примерные картинки**.
## Формат данных (ожидаемая структура датасета)
Оба лоадера предполагают, что корень датасета (`root`) выглядит примерно так:
```text
<root>/
Country/...
Terrain/...
Rot/...
Index/
train_query.txt
train_db.txt
val_query.txt
val_db.txt
test_query.txt
test_db.txt
... и варианты *_country.txt, *_all.txt
```
На уровне сцены (примерно):
```text
<root>/Terrain/<TerrainType>/<SceneName>/
positive.json
semi_positive.json
DB/
merge.tif
db_postion.txt
img/crop_X_Y.png
query/
height100_rot0/footage/*.jpeg
...
```
## Index-файлы (ключевой интерфейс)
### DB index (`*_db*.txt`)
По 1 пути на строку, путь **относительно `root`**:
```text
Terrain/Mountain/Andes/DB/img/crop_0_0.png
Terrain/Mountain/Andes/DB/img/crop_0_1.png
...
```
### Query index (`*_query*.txt`)
Формат строки:
```text
<query_path> <scene_label_int> <positive_db_1> [positive_db_2 ...]
```
Пример:
```text
Terrain/Mountain/Andes/query/height100_rot0/footage/height100_rot0_00.jpeg 12 Terrain/Mountain/Andes/DB/img/crop_10_7.png Terrain/Mountain/Andes/DB/img/crop_10_8.png
```
Важно: в путях могут встречаться пробелы (в вариантах/папках). Парсер в обоих лоадерах извлекает DB-пути по паттерну `*/DB/img/crop_*.png`, а `label` берёт как последний числовой токен перед DB-путями.
## `dataloader.py` (базовый)
### Что даёт
- **Train**:
- `UAVGeoLocTrain`: `(query, positive, negative)` triplets (негатив берётся случайно из `train_db.txt`, исключая positives этого scene label).
- `UAVGeoLocPair`: `(query, positive)` пары (под contrastive без explicit negative).
- **Eval**:
- `UAVGeoLocEval(mode="query")`: одиночные query-изображения + `label` + список `positives`.
- `UAVGeoLocEval(mode="db")`: одиночные DB-изображения.
- `eval_collate_fn`: collate, который оставляет `positives` списком (variable-length).
- `build_dataloaders(...)`: собирает набор лоадеров на train/val/test.
### Мини-пример использования
```python
from dataloader import build_dataloaders
root = "/path/to/UAV-GeoLoc"
loaders = build_dataloaders(root, split="terrain", batch_size=32, img_size=512, num_workers=4, mode="triplet")
batch = next(iter(loaders["train"]))
print(batch["query"].shape, batch["positive"].shape, batch["negative"].shape, batch["label"])
```
## `dataloader_v2.py` (расширенный)
### Отличия от базовой версии
- **Метаданные query**:
- парсит `height` и `rotation` из пути вида `height125_rot270/...`.
- возвращает их в батчах train/eval.
- **GPS / локализационная ошибка**:
- `load_db_positions(...)`, `haversine_m(...)`
- `compute_localization_error(...)`: метрики ошибки в метрах по retrieval predictions (top-1 индексы DB).
- **Утилита нарезки спутника**:
- `tile_satellite_image(...)`: генерирует кропы в стиле UAV-GeoLoc (stride по умолчанию `crop_size // 2`).
- **Scene-based loader**:
- `UAVGeoLocScene(scene_dir=...)`: читает сцену напрямую из папки (без `Index/*.txt`), удобно для кастомных выборок/проверок (в т.ч. Rot subset).
- `build_rot_loader(...)`: convenience лоадер для `Rot/SouthernSuburbs`.
### Мини-пример: посчитать error (м) по top-1 предсказаниям
```python
import numpy as np
from dataloader_v2 import build_dataloaders, compute_localization_error
root = "/path/to/UAV-GeoLoc"
loaders = build_dataloaders(root, split="terrain", batch_size=64, img_size=224, num_workers=4, mode="triplet")
q_ds = loaders["test_query"].dataset
d_ds = loaders["test_db"].dataset
# допустим, у вас есть top1 predictions как индексы DB:
pred = np.zeros(len(q_ds), dtype=np.int64)
stats = compute_localization_error(q_ds, d_ds, pred)
print(stats["mean_error_m"], stats["median_error_m"], stats["num_evaluated"])
```
## Какой лоадер использовать
- **Бери `dataloader.py`**, если тебе нужен минимальный, предсказуемый интерфейс:
- train triplets/pairs
- eval query/db
- без метаданных и без геометрии/географии
- **Бери `dataloader_v2.py`**, если:
- в модели/логах важно `height` и `rotation` (например, для condition/аблаций)
- хочешь считать **ошибку локализации в метрах** (по `db_postion.txt`)
- нужно грузить сцены **напрямую из папки** (без `Index/*.txt`) или работать с `Rot` subset
- нужен helper для tiling спутника `tile_satellite_image(...)`
На практике: **для базовых retrieval-экспериментов достаточно `dataloader.py`**, а `v2` — когда переходишь к “исследовательским” метрикам/контролю условий.
## Какие `Index/*.txt` ожидаются для `terrain/country/all`
Оба лоадера используют один и тот же принцип: в `Index/` лежат файлы вида:
- `train_query*.txt`, `train_db*.txt`
- `val_query*.txt`, `val_db*.txt`
- `test_query*.txt`, `test_db*.txt`
Суффиксы:
- **`terrain`**: суффикс пустой (`train_query.txt`, `train_db.txt`, …)
- **`country`**: суффикс `_country` (`train_query_country.txt`, …)
- **`all`**: суффикс `_all` (`train_query_all.txt`, …)
### Fallback-логика в `build_dataloaders`
В `dataloader.py` и `dataloader_v2.py` `build_dataloaders(...)` делает сборку так:
- **train**: берёт строго `Index/train_query{suffix}.txt` и `Index/train_db{suffix}.txt` для выбранного `split`
- **val/test**:
- сначала пытается открыть `Index/{phase}_query{suffix}.txt` / `Index/{phase}_db{suffix}.txt`
- если этих файлов нет, откатывается на **несуффиксные** `Index/{phase}_query.txt` / `Index/{phase}_db.txt`
Это удобно, если у тебя, например, `train_*_country.txt` есть, а `val_*_country.txt` ещё не сгенерен.
## `analyze/` (оффлайн анализ датасета)
Скрипты ориентированы на запуск “как есть”, но почти везде нужно поменять `BASE`/`ROOT` (путь к датасету).
Подробности см. `analyze/README.md`.