Add dataloaders (v1/v2), analysis scripts, and documentation for working with UAV-GeoLoc (World-UAV). Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
7.9 KiB
Markdown
176 lines
7.9 KiB
Markdown
# 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`.
|
||
|