File size: 3,855 Bytes
e23cf4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Tái cấu trúc test_data_v2 -> test_data_v3: chia theo METHOD rõ ràng.

  test_data_v3/
    manifest.csv
    real/                      # 1,177 ảnh real (dùng chung cho mọi method)
    <method>/fake/             # 40 method, mỗi method 1 folder chứa fake của nó

Vì ảnh real DÙNG CHUNG giữa các method (cùng identity có fake từ nhiều method),
ta KHÔNG nhân bản real vào từng method (sẽ bị tính 40 lần khi eval) mà để real
ở top-level 1 lần duy nhất — vẫn giữ identity-unique như test_data_v2.

Dùng HARD LINK từ test_data_v2/ (cùng volume APFS -> 0 byte thêm, chạy tức thì).
Không xoá gì ở v2; có thể xoá v2 sau khi đã verify v3.

Chạy:
  .venv/bin/python scripts/restructure_test_data_v3.py
"""
import csv
import os
import sys

SRC = "test_data_v2"
OUT = "test_data_v3"


def main():
    src_manifest = os.path.join(SRC, "manifest.csv")
    assert os.path.exists(src_manifest), f"thiếu {src_manifest}"
    os.makedirs(OUT, exist_ok=True)

    rows = []
    with open(src_manifest) as f:
        reader = csv.DictReader(f)
        cols = reader.fieldnames
        for row in reader:
            rows.append(dict(row))
    print(f"Đọc {len(rows):,} rows từ {src_manifest}")

    hardlink_fail = {"n": 0, "e": None}
    n_real = n_fake = 0
    for row in rows:
        v2_rel = row["path"]
        src_abs = os.path.join(SRC, v2_rel)
        method = row["method"]
        if method == "real":
            new_rel = os.path.join("real", os.path.basename(v2_rel))
        else:
            new_rel = os.path.join(method, "fake", os.path.basename(v2_rel))
        dst_abs = os.path.join(OUT, new_rel)
        os.makedirs(os.path.dirname(dst_abs), exist_ok=True)
        if not os.path.exists(dst_abs):
            try:
                os.link(src_abs, dst_abs)          # hard link — 0 byte thêm
            except OSError as e:
                hardlink_fail["n"] += 1
                hardlink_fail["e"] = e
                import shutil
                shutil.copy2(src_abs, dst_abs)     # fallback copy
        row["path"] = new_rel
        if method == "real":
            n_real += 1
        else:
            n_fake += 1

    with open(os.path.join(OUT, "manifest.csv"), "w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=cols)
        w.writeheader()
        w.writerows(rows)

    # ---------- verify ----------
    import collections
    paths = [r["path"] for r in rows]
    dups = [p for p, c in collections.Counter(paths).items() if c > 1]
    methods = sorted(set(r["method"] for r in rows if r["method"] != "real"))
    missing = [p for p in paths if not os.path.exists(os.path.join(OUT, p))]
    print(f"\nOUT: {OUT}/")
    print(f"  real={n_real} | fake={n_fake} | tổng={len(rows):,}")
    print(f"  method (fake): {len(methods)} — path trùng={len(dups)} — thiếu file={len(missing)}")
    if dups[:5]:
        print("  dups:", dups[:5])
    if hardlink_fail["n"]:
        print(f"  [warn] {hardlink_fail['n']} file copy thay vì hard link ({hardlink_fail['e']})")
    if missing:
        print("  [LỖI] thiếu file:", missing[:5])
        sys.exit(1)
    print("OK — verify 1 ảnh mỗi method (decode)...")
    bad = 0
    from PIL import Image
    for m in methods:
        sample = next(r for r in rows if r["method"] == m)
        p = os.path.join(OUT, sample["path"])
        try:
            Image.open(p).convert("RGB")
        except Exception as ex:
            print(f"  [LỖI] {m}: {ex}")
            bad += 1
    s_real = next(r for r in rows if r["method"] == "real")
    try:
        Image.open(os.path.join(OUT, s_real["path"])).convert("RGB")
    except Exception as ex:
        print(f"  [LỖI] real: {ex}")
        bad += 1
    print(f"  decode lỗi: {bad}")


if __name__ == "__main__":
    main()