Files
cvgl_experiments/check_coverage.py
pikaliov 05355c6a7b Full fix
2026-07-07 18:47:38 +03:00

126 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Сравнение ПОКРЫТИЯ 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" ⚠️ расхождение: |v1v2| = {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())