126 lines
5.3 KiB
Python
126 lines
5.3 KiB
Python
"""Сравнение ПОКРЫТИЯ captions двух наборов (v1 vs v2) — отдельно от обучения.
|
||
|
||
Отвечает на один вопрос: одинаковый ли набор картинок покрыт текстом в v1 и v2?
|
||
Если да — сравнение R@1 между версиями чистое. Если нет — разница R@1 может
|
||
объясняться покрытием, а не качеством текста (см. протокол §5.4, §0.4).
|
||
|
||
Использует ТЕ ЖЕ функции чтения, что и обучение (load_text_descriptions,
|
||
combine_text_levels), поэтому «есть текст» здесь означает ровно то же, что
|
||
видит модель: непустой текст для выбранных text_levels, а не заглушку
|
||
"No description available.".
|
||
|
||
Пример:
|
||
python check_coverage.py \
|
||
--data_root /media/servml/SSD_2_2TB/datasets/cvgl_datasets/GTA-UAV-LR \
|
||
--captions_v1 ".../GTA-UAV-LR-captions_ v1" \
|
||
--captions_v2 ".../GTA-UAV-LR-captions_v2" \
|
||
--text_levels level1 level2
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
from pathlib import Path
|
||
|
||
from src.data.gta_uav import combine_text_levels, load_text_descriptions
|
||
from src.data.gta_uav_eval import collect_gallery_names
|
||
|
||
|
||
def _has_real_text(
|
||
name: str,
|
||
descriptions: dict[str, dict],
|
||
text_levels: list[str],
|
||
) -> bool:
|
||
"""True, если для картинки есть непустой текст на выбранных уровнях.
|
||
|
||
Повторяет семантику обучения: пустой результат combine_text_levels →
|
||
модель получит заглушку, т.е. текста фактически нет.
|
||
"""
|
||
desc = descriptions.get(name)
|
||
if not desc:
|
||
return False
|
||
combined = combine_text_levels(desc, text_levels)
|
||
return combined.strip() != "" and combined != "No description available."
|
||
|
||
|
||
def _covered_set(
|
||
names: list[str],
|
||
descriptions: dict[str, dict],
|
||
text_levels: list[str],
|
||
) -> set[str]:
|
||
"""Подмножество имён, у которых есть реальный текст."""
|
||
return {n for n in names if _has_real_text(n, descriptions, text_levels)}
|
||
|
||
|
||
def _report_side(
|
||
title: str,
|
||
names: list[str],
|
||
desc_v1: dict[str, dict],
|
||
desc_v2: dict[str, dict],
|
||
text_levels: list[str],
|
||
) -> None:
|
||
"""Напечатать покрытие v1/v2 и пересечение для одной стороны."""
|
||
total = len(names)
|
||
cov_v1 = _covered_set(names, desc_v1, text_levels)
|
||
cov_v2 = _covered_set(names, desc_v2, text_levels)
|
||
both = cov_v1 & cov_v2
|
||
only_v1 = cov_v1 - cov_v2
|
||
only_v2 = cov_v2 - cov_v1
|
||
|
||
def pct(n: int) -> str:
|
||
return f"{n:>6d} / {total} ({100.0 * n / max(total, 1):5.1f}%)"
|
||
|
||
print(f"\n── {title} (всего уникальных: {total}) " + "─" * 20)
|
||
print(f" покрытие v1: {pct(len(cov_v1))}")
|
||
print(f" покрытие v2: {pct(len(cov_v2))}")
|
||
print(f" есть в ОБОИХ (пересеч): {pct(len(both))}")
|
||
print(f" только v1: {len(only_v1)}")
|
||
print(f" только v2: {len(only_v2)}")
|
||
|
||
if len(cov_v1) == len(cov_v2) == len(both):
|
||
print(" ✅ наборы совпадают → покрытие не влияет на сравнение")
|
||
else:
|
||
diff = abs(len(cov_v1) - len(cov_v2))
|
||
print(
|
||
f" ⚠️ расхождение: |v1−v2| = {diff} картинок; "
|
||
f"для честного сравнения оценивайте на пересечении ({len(both)})"
|
||
)
|
||
|
||
|
||
def main(args: argparse.Namespace) -> None:
|
||
data_root = Path(args.data_root)
|
||
text_levels = list(args.text_levels)
|
||
|
||
with open(data_root / args.test_meta) as f:
|
||
pairs_meta = json.load(f)
|
||
|
||
# Дрон-запросы: уникальные имена картинок дрона в тесте.
|
||
drone_names = sorted({e["drone_img_name"] for e in pairs_meta})
|
||
# Спутниковая галерея: те же уникальные тайлы, что и в eval.
|
||
gallery_names, _ = collect_gallery_names(pairs_meta)
|
||
|
||
print("=" * 64)
|
||
print(f"COVERAGE CHECK | text_levels={text_levels} | {args.test_meta}")
|
||
print("=" * 64)
|
||
|
||
for view, names in (("drone", drone_names), ("satellite", gallery_names)):
|
||
desc_v1 = load_text_descriptions(args.captions_v1, view_type=view)
|
||
desc_v2 = load_text_descriptions(args.captions_v2, view_type=view)
|
||
_report_side(
|
||
f"{view.upper()} {'queries' if view == 'drone' else 'gallery'}",
|
||
names, desc_v1, desc_v2, text_levels,
|
||
)
|
||
|
||
def parse_args() -> argparse.Namespace:
|
||
p = argparse.ArgumentParser(description="Сравнить покрытие captions v1 vs v2")
|
||
p.add_argument("--data_root", type=str, required=True)
|
||
p.add_argument("--test_meta", type=str, default="cross-area-drone2sate-test.json")
|
||
p.add_argument("--captions_v1", type=str, required=True)
|
||
p.add_argument("--captions_v2", type=str, required=True)
|
||
p.add_argument("--text_levels", nargs="+", default=["level1"])
|
||
return p.parse_args()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main(parse_args())
|