Ruvadev commited on
Commit
19bc54c
·
verified ·
1 Parent(s): da7432a

Delete raven/manifest.py

Browse files
Files changed (1) hide show
  1. raven/manifest.py +0 -176
raven/manifest.py DELETED
@@ -1,176 +0,0 @@
1
- from __future__ import annotations
2
- from collections import Counter
3
- from pathlib import Path
4
- import numpy as np
5
- from PIL import Image, ImageOps
6
- try:
7
- import imagehash
8
- except Exception:
9
- imagehash = None
10
- from tqdm import tqdm
11
- from .utils import iter_images, stable_unit, write_jsonl
12
- from .parquet_io import discover_parquet_files, materialize_parquet
13
-
14
-
15
- def _phash(path: Path, hash_size: int = 12) -> str | None:
16
- try:
17
- with Image.open(path) as im:
18
- im = ImageOps.exif_transpose(im).convert("RGB")
19
- if imagehash is not None:
20
- return str(imagehash.phash(im, hash_size=hash_size))
21
- g = im.convert("L").resize((32, 32), Image.Resampling.LANCZOS)
22
- a = np.asarray(g, dtype=np.float32) / 255.0
23
- low = np.abs(np.fft.fft2(a))[:hash_size, :hash_size]
24
- flat = low.ravel(); med = float(np.median(flat[1:])) if flat.size > 1 else float(flat[0])
25
- bits = (flat > med).astype(np.uint8)
26
- value = 0
27
- for bit in bits: value = (value << 1) | int(bit)
28
- width = (len(bits) + 3) // 4
29
- return f"{value:0{width}x}"
30
- except Exception:
31
- return None
32
-
33
-
34
- def _hamming_hex(a: str, b: str) -> int:
35
- try: return (int(a, 16) ^ int(b, 16)).bit_count()
36
- except Exception: return 9999
37
-
38
-
39
- class _HammingIndex:
40
-
41
- def __init__(self, max_distance:int):
42
- self.d=max(0,int(max_distance)); self.values=[]; self.buckets={}
43
-
44
- def _segments(self, value:int, bits:int):
45
- parts=max(1,self.d+1); base=bits//parts; rem=bits%parts; shift=0
46
- for i in range(parts):
47
- width=base+(1 if i<rem else 0); mask=(1<<width)-1
48
- yield i,(value>>shift)&mask
49
- shift+=width
50
-
51
- def duplicate_or_add(self, ph:str|None)->bool:
52
- if not ph or self.d<0: return False
53
- try: value=int(ph,16)
54
- except Exception: return False
55
- bits=max(4,len(ph)*4); cand=set(); segs=list(self._segments(value,bits))
56
- for key in segs:
57
- cand.update(self.buckets.get(key,()))
58
- for idx in cand:
59
- if (value^self.values[idx]).bit_count()<=self.d:
60
- return True
61
- idx=len(self.values); self.values.append(value)
62
- for key in segs:
63
- self.buckets.setdefault(key,[]).append(idx)
64
- return False
65
-
66
- def _stats(path: Path) -> dict:
67
- try:
68
- with Image.open(path) as im:
69
- fmt = (im.format or path.suffix.lstrip(".")).upper(); exif = im.getexif(); has_exif = bool(exif)
70
- im = ImageOps.exif_transpose(im).convert("RGB"); w, h = im.size
71
- tiny = im.copy(); tiny.thumbnail((192, 192)); a = np.asarray(tiny, dtype=np.float32) / 255.0
72
- y = a[..., 0] * 0.2126 + a[..., 1] * 0.7152 + a[..., 2] * 0.0722
73
- mean = float(y.mean()); std = float(y.std()); dark_fraction = float((y < 0.12).mean())
74
- if mean < 0.08: bucket = "extreme-dark"
75
- elif mean < 0.20: bucket = "dark"
76
- elif mean < 0.38: bucket = "dim"
77
- else: bucket = "normal"
78
- return {"width":int(w),"height":int(h),"megapixels":float(w*h/1e6),"aspect_ratio":float(w/max(1,h)),"format":fmt,"has_exif":has_exif,"mean_luma":mean,"std_luma":std,"dark_fraction":dark_fraction,"light_bucket":bucket}
79
- except Exception:
80
- return {"width":None,"height":None,"megapixels":None,"aspect_ratio":None,"format":path.suffix.lstrip(".").upper(),"has_exif":False,"mean_luma":None,"std_luma":None,"dark_fraction":None,"light_bucket":"unknown"}
81
-
82
-
83
- def _normal_descriptors(root_spec:str|Path,label:int) -> list[dict]:
84
- root=Path(root_spec).resolve(); out=[]
85
- if not root.exists(): return out
86
-
87
- if root.is_file(): return out
88
- for p in iter_images(root):
89
- try: rel=p.relative_to(root)
90
- except Exception: continue
91
- source_leaf=rel.parts[0] if len(rel.parts)>1 else ("unknown-camera" if label==0 else "unknown-generator")
92
- out.append({"path":str(p.resolve()),"label":int(label),"source_leaf":source_leaf,"group":str(rel.with_suffix("")),"origin_kind":"image","origin_parquet":None,"origin_row":None})
93
- return out
94
-
95
-
96
- def _parquet_descriptors(root_spec:str|Path,label:int,cache_root:Path,image_column:str|None,source_column:str|None) -> list[dict]:
97
- out=[]
98
- for pq in discover_parquet_files(root_spec):
99
- default_source=pq.stem
100
- rows=materialize_parquet(pq,cache_root,label,default_source,image_column,source_column)
101
- out.extend(rows)
102
- return out
103
-
104
-
105
- def build_manifest(
106
- real_dir: str,
107
- ai_dir: str,
108
- out: str,
109
- holdout_sources: list[str] | None = None,
110
- val_fraction: float = 0.15,
111
- real_test_fraction: float = 0.15,
112
- dedupe_hamming_distance: int = 3,
113
- parquet_cache_dir: str | None = None,
114
- real_parquet_image_column: str | None = None,
115
- real_parquet_source_column: str | None = None,
116
- ai_parquet_image_column: str | None = None,
117
- ai_parquet_source_column: str | None = None,
118
- force_real_source: str | None = None,
119
- force_ai_source: str | None = None,
120
- ) -> list[dict]:
121
- holdout = {s.strip().lower() for s in (holdout_sources or []) if s.strip()}
122
- rows=[]; dedupe_index=_HammingIndex(dedupe_hamming_distance)
123
- manifest_parent=Path(out).resolve().parent
124
- pq_cache=Path(parquet_cache_dir).resolve() if parquet_cache_dir else (manifest_parent/"cache"/"parquet_materialized")
125
- pq_cache.mkdir(parents=True,exist_ok=True)
126
-
127
- def duplicate(ph):
128
- if dedupe_hamming_distance < 0:
129
- return False
130
- return dedupe_index.duplicate_or_add(ph)
131
-
132
- specs=[
133
- (0,real_dir,"real",real_parquet_image_column,real_parquet_source_column),
134
- (1,ai_dir,"ai",ai_parquet_image_column,ai_parquet_source_column),
135
- ]
136
- for label,root_spec,kind,img_col,src_col in specs:
137
- descriptors=_normal_descriptors(root_spec,label)
138
- pq_desc=_parquet_descriptors(root_spec,label,pq_cache/kind,img_col,src_col)
139
- descriptors.extend(pq_desc)
140
- print(f"[{kind}] normal_images={len(descriptors)-len(pq_desc)} parquet_images={len(pq_desc)}")
141
- for d in tqdm(descriptors,desc=f"manifest-{kind}"):
142
- p=Path(d["path"]); raw_source_leaf=str(d.get("source_leaf") or ("unknown-camera" if label==0 else "unknown-generator"))
143
- forced = force_real_source if label==0 else force_ai_source
144
- source_leaf = str(forced).strip() if forced and str(forced).strip() else raw_source_leaf
145
- ph=_phash(p)
146
- if duplicate(ph): continue
147
- group=str(d.get("group") or p.stem)
148
- if label==1 and source_leaf.lower() in holdout: split="test"
149
- else:
150
- r=stable_unit(f"{kind}:{group}")
151
- if label==0:
152
- if r<real_test_fraction: split="test"
153
- elif r<real_test_fraction+val_fraction: split="val"
154
- else: split="train"
155
- else: split="val" if r<val_fraction else "train"
156
- rows.append({
157
- "path":str(p.resolve()),"label":int(label),
158
- "source":(f"real:{source_leaf}" if label==0 else source_leaf),
159
- "raw_source":raw_source_leaf,
160
- "generator":source_leaf if label==1 else None,"camera_bucket":source_leaf if label==0 else None,
161
- "group":group,"phash":ph,"split":split,
162
- "origin_kind":d.get("origin_kind","image"),"origin_parquet":d.get("origin_parquet"),"origin_row":d.get("origin_row"),
163
- **_stats(p),
164
- })
165
- write_jsonl(out,rows); return rows
166
-
167
-
168
- def manifest_summary(rows:list[dict])->str:
169
- c=Counter((r.get("split"),int(r.get("label",-1))) for r in rows); src=Counter((r.get("split"),str(r.get("source","unknown"))) for r in rows); origin=Counter((r.get("split"),str(r.get("origin_kind","image"))) for r in rows)
170
- lines=["Counts by split / label (0=REAL, 1=AI):"]
171
- for k,v in sorted(c.items(),key=lambda kv:str(kv[0])): lines.append(f" {k}: {v}")
172
- lines.append("\nCounts by split / source:")
173
- for k,v in sorted(src.items(),key=lambda kv:str(kv[0])): lines.append(f" {k}: {v}")
174
- lines.append("\nCounts by split / storage origin:")
175
- for k,v in sorted(origin.items(),key=lambda kv:str(kv[0])): lines.append(f" {k}: {v}")
176
- return "\n".join(lines)