forked from Pikaliov/fuze_task
fuse_proj: Initial operational package for 3 researchers (Pavlenko/Blizno/Moroz)
Multimodal fusion research on StripNet+GTA-UAV proxy: - 3 independent fusion tracks: condition-aware (A), token/bottleneck (B), role-aware (C) - Shared interfaces, protocol, dataset audit, baseline benchmarks - Canonical version-chain references to vault (SPEC, ANALYSIS, TRIAGE) - Personalized task plans and decision tables for each researcher - 3 generated DOCX task assignment files with milestones and DoD checklist - Full modality dropout diagnostics and missing-modality robustness requirements - Data contract, benchmark registry, experiment tracking infrastructure Operational documents: - docs/00_project/: MERIDIAN context, protocol, repository reuse guide, experiment specification - docs/01_tasks/: Master assignment + 3 individual researcher tracks + joint integration - docs/02_references/: Core literature, version-chain bases, code maps - docs/03_codebase_guides/: Existing code snapshots from vault - scripts/: gen_task_plans.js (DOCX generation), placeholder infrastructure - vendor_reference/: Snapshots of caption_test, depth_edges_annotate, existing SOFIA/SegModel code - reports/, results/, experiments/: Shared output structure for all 3 researchers 3 DOCX files generated from gen_task_plans.js (Times New Roman 14pt, GOST format): - План_заданий_Павленко_БВ.docx (Condition-Aware track, fusion API owner) - План_заданий_Близно_МВ.docx (Token/Bottleneck track, benchmark owner) - План_заданий_Мороз_ЕС.docx (Role-Aware track, data contract owner) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
140
vendor_reference/caption_test/scripts/filter_segmentation.py
Normal file
140
vendor_reference/caption_test/scripts/filter_segmentation.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""Filter GTA-UAV-LR images by segmentation class coverage.
|
||||
|
||||
Reads palette-mode PNG segmentation masks, computes per-class pixel ratios,
|
||||
and outputs a JSON meta file listing images that pass the filter
|
||||
(i.e. background + water < threshold).
|
||||
|
||||
Classes (from manifest.json):
|
||||
0: background, 1: building, 2: road, 3: vegetation, 4: water, ...
|
||||
|
||||
Usage:
|
||||
python -m scripts.filter_segmentation [--threshold 0.9] [--output meta/seg_filter.json]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import coloredlogs
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from tqdm import tqdm
|
||||
|
||||
LOGGER = logging.getLogger("caption_test.filter_seg")
|
||||
|
||||
SEGM_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR-aug/segm")
|
||||
EXCLUDE_CLASSES = {0, 4} # background, water
|
||||
DEFAULT_THRESHOLD = 0.90
|
||||
|
||||
|
||||
def compute_class_ratios(mask_path: Path) -> dict[int, float]:
|
||||
"""Load a palette-mode PNG mask and return per-class pixel ratios."""
|
||||
with Image.open(mask_path) as img:
|
||||
arr = np.array(img)
|
||||
total = arr.size
|
||||
unique, counts = np.unique(arr, return_counts=True)
|
||||
return {int(cls): float(cnt / total) for cls, cnt in zip(unique, counts)}
|
||||
|
||||
|
||||
def scan_masks(
|
||||
segm_root: Path,
|
||||
exclude_classes: set[int],
|
||||
threshold: float,
|
||||
) -> dict[str, dict]:
|
||||
"""Scan all segmentation masks and classify as pass/fail.
|
||||
|
||||
Returns:
|
||||
Dict keyed by relative image name (e.g. "drone/images/100_0001_0000000000.png")
|
||||
with values: {"ratios": {...}, "excluded_ratio": float, "pass": bool}
|
||||
"""
|
||||
results: dict[str, dict] = {}
|
||||
subdirs = sorted(p for p in segm_root.iterdir() if p.is_dir())
|
||||
|
||||
for subdir in subdirs:
|
||||
mask_files = sorted(subdir.rglob("*.png"))
|
||||
LOGGER.info("🔍 Scanning %s: %d masks", subdir.name, len(mask_files))
|
||||
|
||||
for mask_path in tqdm(mask_files, desc=f" 📂 {subdir.name}", unit="img", leave=False):
|
||||
rel = mask_path.relative_to(segm_root)
|
||||
ratios = compute_class_ratios(mask_path)
|
||||
excluded_ratio = sum(ratios.get(c, 0.0) for c in exclude_classes)
|
||||
results[str(rel)] = {
|
||||
"ratios": ratios,
|
||||
"excluded_ratio": round(excluded_ratio, 6),
|
||||
"pass": excluded_ratio < threshold,
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def build_meta(
|
||||
results: dict[str, dict],
|
||||
) -> dict[str, list[str]]:
|
||||
"""Split results into passed and excluded lists."""
|
||||
passed = sorted(k for k, v in results.items() if v["pass"])
|
||||
excluded = sorted(k for k, v in results.items() if not v["pass"])
|
||||
return {"passed": passed, "excluded": excluded}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Filter GTA-UAV-LR images by segmentation coverage.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--segm-root", type=str, default=str(SEGM_ROOT),
|
||||
help="Root of segmentation masks.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--threshold", type=float, default=DEFAULT_THRESHOLD,
|
||||
help="Exclude images where background+water >= threshold.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", type=str, default="meta/seg_filter.json",
|
||||
help="Output JSON meta file path.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
coloredlogs.install(
|
||||
level="INFO",
|
||||
logger=LOGGER,
|
||||
fmt="%(asctime)s %(name)s %(levelname)s %(message)s",
|
||||
)
|
||||
|
||||
LOGGER.info("🚀 Starting segmentation filter (threshold=%.2f)", args.threshold)
|
||||
|
||||
segm_root = Path(args.segm_root)
|
||||
results = scan_masks(segm_root, EXCLUDE_CLASSES, args.threshold)
|
||||
meta = build_meta(results)
|
||||
|
||||
n_total = len(results)
|
||||
n_pass = len(meta["passed"])
|
||||
n_excl = len(meta["excluded"])
|
||||
LOGGER.info(
|
||||
"📊 total=%d ✅ passed=%d (%.1f%%) ❌ excluded=%d (%.1f%%)",
|
||||
n_total, n_pass, 100 * n_pass / max(n_total, 1),
|
||||
n_excl, 100 * n_excl / max(n_total, 1),
|
||||
)
|
||||
|
||||
output_path = Path(args.output)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
out = {
|
||||
"threshold": args.threshold,
|
||||
"exclude_classes": sorted(EXCLUDE_CLASSES),
|
||||
"total_images": n_total,
|
||||
"passed_count": n_pass,
|
||||
"excluded_count": n_excl,
|
||||
"passed": meta["passed"],
|
||||
"excluded": meta["excluded"],
|
||||
}
|
||||
with output_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(out, f, indent=2)
|
||||
|
||||
LOGGER.info("💾 Meta file saved to %s", output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
87
vendor_reference/caption_test/scripts/make_split.py
Normal file
87
vendor_reference/caption_test/scripts/make_split.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""Create 80/20 train/test split from GTA-UAV-LR pair JSONs.
|
||||
|
||||
Merges cross-area train+test (33,708 pairs), shuffles deterministically,
|
||||
and saves new 80/20 split JSONs.
|
||||
|
||||
Usage:
|
||||
python -m scripts.make_split [--ratio 0.8] [--seed 42]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import coloredlogs
|
||||
|
||||
LOGGER = logging.getLogger("caption_test.make_split")
|
||||
|
||||
_RGB_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Create 80/20 split for GTA-UAV-LR.")
|
||||
parser.add_argument("--ratio", type=float, default=0.8, help="Train ratio (default 0.8).")
|
||||
parser.add_argument("--seed", type=int, default=42, help="Random seed.")
|
||||
parser.add_argument(
|
||||
"--output-dir", type=str, default="meta",
|
||||
help="Output directory for split JSONs.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
coloredlogs.install(
|
||||
level="INFO", logger=LOGGER,
|
||||
fmt="%(asctime)s %(name)s %(levelname)s %(message)s",
|
||||
)
|
||||
|
||||
# Load both original splits.
|
||||
train_path = _RGB_ROOT / "cross-area-drone2sate-train.json"
|
||||
test_path = _RGB_ROOT / "cross-area-drone2sate-test.json"
|
||||
|
||||
LOGGER.info("📂 Loading %s", train_path.name)
|
||||
with open(train_path) as f:
|
||||
part1 = json.load(f)
|
||||
LOGGER.info("📂 Loading %s", test_path.name)
|
||||
with open(test_path) as f:
|
||||
part2 = json.load(f)
|
||||
|
||||
all_pairs = part1 + part2
|
||||
LOGGER.info("📊 Total pairs: %d", len(all_pairs))
|
||||
|
||||
# Shuffle deterministically.
|
||||
rng = random.Random(args.seed)
|
||||
rng.shuffle(all_pairs)
|
||||
|
||||
# Split.
|
||||
n_train = int(len(all_pairs) * args.ratio)
|
||||
train_pairs = all_pairs[:n_train]
|
||||
test_pairs = all_pairs[n_train:]
|
||||
|
||||
LOGGER.info(
|
||||
"✂️ Split %.0f/%.0f: train=%d (%.1f%%) test=%d (%.1f%%)",
|
||||
args.ratio * 100, (1 - args.ratio) * 100,
|
||||
len(train_pairs), 100 * len(train_pairs) / len(all_pairs),
|
||||
len(test_pairs), 100 * len(test_pairs) / len(all_pairs),
|
||||
)
|
||||
|
||||
# Save.
|
||||
out_dir = Path(args.output_dir)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
train_out = out_dir / "train_80.json"
|
||||
test_out = out_dir / "test_20.json"
|
||||
|
||||
with train_out.open("w", encoding="utf-8") as f:
|
||||
json.dump(train_pairs, f)
|
||||
with test_out.open("w", encoding="utf-8") as f:
|
||||
json.dump(test_pairs, f)
|
||||
|
||||
LOGGER.info("💾 Saved: %s (%d pairs)", train_out, len(train_pairs))
|
||||
LOGGER.info("💾 Saved: %s (%d pairs)", test_out, len(test_pairs))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user