198 lines
6.0 KiB
Python
198 lines
6.0 KiB
Python
|
|
from pathlib import Path
|
||
|
|
from collections import defaultdict
|
||
|
|
import argparse
|
||
|
|
import math
|
||
|
|
|
||
|
|
IMG_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
|
||
|
|
|
||
|
|
def is_float(s: str) -> bool:
|
||
|
|
try:
|
||
|
|
float(s)
|
||
|
|
return True
|
||
|
|
except:
|
||
|
|
return False
|
||
|
|
|
||
|
|
def parse_label_line(line: str):
|
||
|
|
"""
|
||
|
|
Suporta:
|
||
|
|
- YOLO det: cls xc yc w h
|
||
|
|
- YOLO seg: cls x1 y1 x2 y2 ...
|
||
|
|
Retorna dict com:
|
||
|
|
cls (int), kind ("det"|"seg"|None), n_points (int|None)
|
||
|
|
"""
|
||
|
|
parts = line.strip().split()
|
||
|
|
if len(parts) < 2:
|
||
|
|
return None
|
||
|
|
|
||
|
|
if not is_float(parts[0]):
|
||
|
|
return None
|
||
|
|
cls = int(float(parts[0]))
|
||
|
|
|
||
|
|
nums = []
|
||
|
|
for p in parts[1:]:
|
||
|
|
if not is_float(p):
|
||
|
|
return None
|
||
|
|
nums.append(float(p))
|
||
|
|
|
||
|
|
# det clássico: 4 números
|
||
|
|
if len(nums) == 4:
|
||
|
|
return {"cls": cls, "kind": "det", "n_points": None}
|
||
|
|
|
||
|
|
# seg: pares (x,y)
|
||
|
|
if len(nums) >= 6 and (len(nums) % 2 == 0):
|
||
|
|
return {"cls": cls, "kind": "seg", "n_points": len(nums) // 2}
|
||
|
|
|
||
|
|
# caso estranho
|
||
|
|
return {"cls": cls, "kind": "unknown", "n_points": None}
|
||
|
|
|
||
|
|
def analyze_split(labels_dir: Path):
|
||
|
|
stats = {
|
||
|
|
"images_total": 0,
|
||
|
|
"images_bg": 0,
|
||
|
|
"images_with_obj": 0,
|
||
|
|
|
||
|
|
"images_per_class": defaultdict(int), # quantas imagens têm a classe
|
||
|
|
"instances_per_class": defaultdict(int), # quantas instâncias (linhas) por classe
|
||
|
|
|
||
|
|
"kind_counts": defaultdict(int), # det/seg/unknown
|
||
|
|
"poly_points": [], # lista de n_points (para seg)
|
||
|
|
"weird_lines": 0,
|
||
|
|
"empty_label_files": 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
if not labels_dir.exists():
|
||
|
|
return stats
|
||
|
|
|
||
|
|
label_files = sorted(labels_dir.glob("*.txt"))
|
||
|
|
stats["images_total"] = len(label_files)
|
||
|
|
|
||
|
|
for lf in label_files:
|
||
|
|
text = lf.read_text(encoding="utf-8", errors="ignore").strip()
|
||
|
|
if not text:
|
||
|
|
stats["images_bg"] += 1
|
||
|
|
stats["empty_label_files"] += 1
|
||
|
|
continue
|
||
|
|
|
||
|
|
stats["images_with_obj"] += 1
|
||
|
|
|
||
|
|
classes_in_image = set()
|
||
|
|
|
||
|
|
for line in text.splitlines():
|
||
|
|
parsed = parse_label_line(line)
|
||
|
|
if parsed is None:
|
||
|
|
stats["weird_lines"] += 1
|
||
|
|
continue
|
||
|
|
|
||
|
|
cls = parsed["cls"]
|
||
|
|
kind = parsed["kind"]
|
||
|
|
stats["kind_counts"][kind] += 1
|
||
|
|
|
||
|
|
classes_in_image.add(cls)
|
||
|
|
stats["instances_per_class"][cls] += 1
|
||
|
|
|
||
|
|
if kind == "seg" and parsed["n_points"] is not None:
|
||
|
|
stats["poly_points"].append(parsed["n_points"])
|
||
|
|
|
||
|
|
for cls in classes_in_image:
|
||
|
|
stats["images_per_class"][cls] += 1
|
||
|
|
|
||
|
|
return stats
|
||
|
|
|
||
|
|
def summarize(stats):
|
||
|
|
out = []
|
||
|
|
out.append(f"images_total : {stats['images_total']}")
|
||
|
|
out.append(f"images_bg : {stats['images_bg']} ({pct(stats['images_bg'], stats['images_total'])})")
|
||
|
|
out.append(f"images_with_obj : {stats['images_with_obj']} ({pct(stats['images_with_obj'], stats['images_total'])})")
|
||
|
|
out.append(f"empty_label_files: {stats['empty_label_files']}")
|
||
|
|
out.append(f"weird_lines : {stats['weird_lines']}")
|
||
|
|
|
||
|
|
if stats["kind_counts"]:
|
||
|
|
out.append("label_kinds : " + ", ".join(f"{k}={v}" for k, v in sorted(stats["kind_counts"].items())))
|
||
|
|
|
||
|
|
# imagens por classe
|
||
|
|
if stats["images_per_class"]:
|
||
|
|
out.append("images_per_class : " + ", ".join(f"c{c}={n}" for c, n in sorted(stats["images_per_class"].items())))
|
||
|
|
else:
|
||
|
|
out.append("images_per_class : (nenhuma)")
|
||
|
|
|
||
|
|
# instâncias por classe
|
||
|
|
if stats["instances_per_class"]:
|
||
|
|
out.append("inst_per_class : " + ", ".join(f"c{c}={n}" for c, n in sorted(stats["instances_per_class"].items())))
|
||
|
|
else:
|
||
|
|
out.append("inst_per_class : (nenhuma)")
|
||
|
|
|
||
|
|
# polígonos
|
||
|
|
if stats["poly_points"]:
|
||
|
|
pts = stats["poly_points"]
|
||
|
|
out.append(f"seg_poly_points : count={len(pts)} min={min(pts)} mean={sum(pts)/len(pts):.2f} max={max(pts)}")
|
||
|
|
else:
|
||
|
|
out.append("seg_poly_points : (n/a)")
|
||
|
|
|
||
|
|
return "\n".join(out)
|
||
|
|
|
||
|
|
def pct(a, b):
|
||
|
|
if b == 0:
|
||
|
|
return "n/a"
|
||
|
|
return f"{(100.0*a/b):.1f}%"
|
||
|
|
|
||
|
|
def merge_stats(a, b):
|
||
|
|
"""merge b into a"""
|
||
|
|
a["images_total"] += b["images_total"]
|
||
|
|
a["images_bg"] += b["images_bg"]
|
||
|
|
a["images_with_obj"] += b["images_with_obj"]
|
||
|
|
a["empty_label_files"] += b["empty_label_files"]
|
||
|
|
a["weird_lines"] += b["weird_lines"]
|
||
|
|
|
||
|
|
for k, v in b["images_per_class"].items():
|
||
|
|
a["images_per_class"][k] += v
|
||
|
|
for k, v in b["instances_per_class"].items():
|
||
|
|
a["instances_per_class"][k] += v
|
||
|
|
for k, v in b["kind_counts"].items():
|
||
|
|
a["kind_counts"][k] += v
|
||
|
|
a["poly_points"].extend(b["poly_points"])
|
||
|
|
return a
|
||
|
|
|
||
|
|
def main():
|
||
|
|
ap = argparse.ArgumentParser()
|
||
|
|
ap.add_argument("--base", type=str, required=True,
|
||
|
|
help="Pasta base que contém labels/train labels/val labels/test (ou labels direto)")
|
||
|
|
args = ap.parse_args()
|
||
|
|
|
||
|
|
base = Path(args.base)
|
||
|
|
labels = base / "labels"
|
||
|
|
|
||
|
|
splits = []
|
||
|
|
# padrão YOLO: labels/train, labels/val, labels/test
|
||
|
|
if (labels / "train").exists() or (labels / "val").exists() or (labels / "test").exists():
|
||
|
|
splits = ["train", "val", "test"]
|
||
|
|
split_dirs = {s: labels / s for s in splits}
|
||
|
|
else:
|
||
|
|
# fallback: base/labels direto
|
||
|
|
split_dirs = {"all": labels}
|
||
|
|
|
||
|
|
global_stats = {
|
||
|
|
"images_total": 0,
|
||
|
|
"images_bg": 0,
|
||
|
|
"images_with_obj": 0,
|
||
|
|
"images_per_class": defaultdict(int),
|
||
|
|
"instances_per_class": defaultdict(int),
|
||
|
|
"kind_counts": defaultdict(int),
|
||
|
|
"poly_points": [],
|
||
|
|
"weird_lines": 0,
|
||
|
|
"empty_label_files": 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
for name, d in split_dirs.items():
|
||
|
|
st = analyze_split(d)
|
||
|
|
print("===================================")
|
||
|
|
print(f"SPLIT: {name} ({d})")
|
||
|
|
print(summarize(st))
|
||
|
|
print("===================================")
|
||
|
|
merge_stats(global_stats, st)
|
||
|
|
|
||
|
|
print("\n========== GLOBAL ==========")
|
||
|
|
print(summarize(global_stats))
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|