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:
175
README.md
Normal file
175
README.md
Normal 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`.
|
||||
|
||||
Reference in New Issue
Block a user