Ruvadev commited on
Commit
06ea438
·
verified ·
1 Parent(s): 926dffd

Delete raven/cache.py

Browse files
Files changed (1) hide show
  1. raven/cache.py +0 -323
raven/cache.py DELETED
@@ -1,323 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from concurrent.futures import ThreadPoolExecutor
5
- from pathlib import Path
6
-
7
- import numpy as np
8
- import torch
9
- import torch.nn.functional as F
10
- from tqdm import tqdm
11
-
12
- from .foundation import FoundationEncoder
13
- from .foundation_runtime import FoundationRuntime
14
- from .forensic_features import load_rgb, extract_handcrafted, spectral_map, camera_residual_map
15
- from .specialists import load_specialists
16
- from .transforms import SymmetricAugment
17
- from .utils import configure_cpu, seed_everything, stable_unit
18
-
19
-
20
- def _specialist_batch_scores(spec, cam, smaps: torch.Tensor, cmaps: torch.Tensor) -> np.ndarray:
21
- with torch.inference_mode():
22
- ps = spec(smaps)
23
- pc = cam(cmaps)
24
-
25
- def met(pred, target):
26
- err = torch.abs(pred - target)
27
- flat = err.flatten(1)
28
- mae = flat.mean(1)
29
- mse = ((pred - target) ** 2).flatten(1).mean(1)
30
- cos = 1.0 - F.cosine_similarity(pred.flatten(1), target.flatten(1), dim=1)
31
- q90 = torch.quantile(flat, .90, dim=1)
32
- return torch.stack([mae, mse, cos, q90], 1)
33
-
34
- return torch.cat([met(ps, smaps), met(pc, cmaps)], 1).cpu().numpy().astype(np.float32)
35
-
36
-
37
- def _prepare_unique_row(item):
38
-
39
- r, augment_copies, transform, feat_cfg, map_size, reuse_semantic = item
40
- im = load_rgb(r["path"])
41
- sem_tensor = transform(im)
42
- aug = SymmetricAugment()
43
- views = []
44
- for v in range(0, max(0, int(augment_copies)) + 1):
45
- if v > 0 and r.get("split") != "train":
46
- break
47
- view = im if v == 0 else aug(im)
48
- h = extract_handcrafted(view, feat_cfg)
49
- views.append({
50
- "view_id": v,
51
- "handcrafted": h,
52
- "spectral_map": spectral_map(view, map_size),
53
- "camera_map": camera_residual_map(view, map_size),
54
-
55
- "semantic_tensor": None if (v == 0 or reuse_semantic) else transform(view),
56
- })
57
- return r, sem_tensor, views
58
-
59
-
60
- def _prepare_unique_row_safe(item):
61
- try:
62
- return _prepare_unique_row(item), None
63
- except Exception as exc:
64
- return None, f"{item[0].get('path')}: {exc}"
65
-
66
-
67
- def _select_augment_paths(rows: list[dict], cap_per_class: int) -> set[str]:
68
-
69
- from collections import defaultdict
70
- if cap_per_class <= 0:
71
- return set()
72
- chosen = set()
73
- for cls in (0, 1):
74
- groups = defaultdict(list)
75
- for r in rows:
76
- if int(r.get("label", -1)) == cls and r.get("split") == "train":
77
- groups[str(r.get("source", "unknown"))].append(r)
78
- if not groups:
79
- continue
80
- for g in groups.values():
81
- g.sort(key=lambda r: stable_unit(str(r.get("path", ""))))
82
- keys = sorted(groups)
83
- pos = {k: 0 for k in keys}
84
- count = 0
85
- while count < cap_per_class:
86
- added = False
87
- for k in keys:
88
- i = pos[k]
89
- if i < len(groups[k]):
90
- chosen.add(str(groups[k][i]["path"]))
91
- pos[k] = i + 1
92
- count += 1
93
- added = True
94
- if count >= cap_per_class:
95
- break
96
- if not added:
97
- break
98
- return chosen
99
-
100
-
101
- def _training_coreset(rows: list[dict], cap_per_class: int) -> list[dict]:
102
-
103
- from collections import defaultdict
104
- if cap_per_class <= 0:
105
- return list(rows)
106
- keep = [r for r in rows if str(r.get("split", "train")) != "train"]
107
- for cls in (0, 1):
108
- pool = [r for r in rows if str(r.get("split", "train")) == "train" and int(r.get("label", -1)) == cls]
109
- if len(pool) <= cap_per_class:
110
- keep.extend(pool)
111
- continue
112
- groups = defaultdict(list)
113
- for r in pool:
114
- parent = Path(str(r.get("path", ""))).parent.name
115
- key = (str(r.get("source", "unknown")), str(r.get("light_bucket", "unknown")), parent)
116
- groups[key].append(r)
117
- for vals in groups.values():
118
- vals.sort(key=lambda r: stable_unit(str(r.get("path", ""))))
119
- keys = sorted(groups, key=lambda k: stable_unit("|".join(k)))
120
- pos = {k: 0 for k in keys}
121
- selected = []
122
- while len(selected) < cap_per_class:
123
- added = False
124
- for k in keys:
125
- i = pos[k]
126
- if i < len(groups[k]):
127
- selected.append(groups[k][i]); pos[k] = i + 1; added = True
128
- if len(selected) >= cap_per_class:
129
- break
130
- if not added:
131
- break
132
- keep.extend(selected)
133
-
134
- keep.sort(key=lambda r: (str(r.get("split", "train")), int(r.get("label", 0)), str(r.get("path", ""))))
135
- return keep
136
-
137
- def cache_features(rows: list[dict], cfg: dict, specialists_path: str, out_path: str, augment_copies: int = 1) -> dict:
138
- hc = cfg["hardware"]
139
- fc = cfg["foundation"]
140
- feat_cfg = cfg["features"]
141
- configure_cpu(hc.get("torch_threads", 6), hc.get("interop_threads", 1))
142
- seed_everything(cfg["training"].get("seed", 1337))
143
-
144
- input_unique = len(rows)
145
- train_cap = int(fc.get("train_cap_per_class", 0))
146
- if bool(fc.get("train_coreset", False)) and train_cap > 0:
147
- rows = _training_coreset(rows, train_cap)
148
- print(f"[cache] diversity core-set: {input_unique} -> {len(rows)} unique rows (train cap={train_cap}/class; all val/test kept)")
149
-
150
- foundation = FoundationEncoder(
151
- fc.get("backend", "dinov2_vits14"),
152
- bool(fc.get("allow_fallback", True)),
153
- fc.get("fallback_backend", "torchvision_convnext_tiny"),
154
- )
155
- foundation.eval().to("cpu")
156
-
157
-
158
- p = Path(out_path)
159
- p.parent.mkdir(parents=True, exist_ok=True)
160
- foundation_state_path = p.with_suffix(p.suffix + ".foundation.pt")
161
- torch.save(
162
- {"backend": foundation.backend, "dim": int(foundation.dim), "state_dict": foundation.model.state_dict()},
163
- foundation_state_path,
164
- )
165
-
166
- runtime = FoundationRuntime(fc, foundation)
167
- spec, cam, sck = load_specialists(specialists_path, "cpu")
168
- map_size = int(sck["map_size"])
169
-
170
-
171
- bsz = max(1, int(runtime.batch_size))
172
- preprocess_workers = max(0, int(hc.get("preprocess_workers", 2)))
173
- reuse_sem = bool(fc.get("reuse_semantic_for_augmented_views", True))
174
- augment_cap = int(fc.get("augment_max_per_class", 4000))
175
- augment_paths = _select_augment_paths(rows, augment_cap) if augment_copies > 0 else set()
176
- print(f"[cache] image-level augmentation selected for {len(augment_paths)} train images "
177
- f"(cap={augment_cap}/class, copies={augment_copies})")
178
-
179
- store = {k: [] for k in [
180
- "semantic", "camera", "spectral", "patch", "quality", "specialist",
181
- "label", "source", "path", "split", "view_id", "light_bucket", "mean_luma",
182
- ]}
183
-
184
- total_unique = len(rows)
185
- executor = ThreadPoolExecutor(max_workers=preprocess_workers) if preprocess_workers > 1 else None
186
-
187
- try:
188
- for start in tqdm(range(0, total_unique, bsz), desc=f"cache-features[{runtime.backend}]"):
189
- chunk_rows = rows[start:start + bsz]
190
- jobs = [
191
- (
192
- r,
193
- augment_copies if str(r.get("path")) in augment_paths else 0,
194
- foundation.transform, feat_cfg, map_size, reuse_sem
195
- )
196
- for r in chunk_rows
197
- ]
198
- if executor is not None:
199
- results = list(executor.map(_prepare_unique_row_safe, jobs))
200
- prepared = []
201
- for item, err in results:
202
- if err:
203
- print(f"[warning] skip {err}")
204
- elif item is not None:
205
- prepared.append(item)
206
- else:
207
- prepared = []
208
- for j in jobs:
209
- try:
210
- prepared.append(_prepare_unique_row(j))
211
- except Exception as exc:
212
- print(f"[warning] skip {j[0].get('path')}: {exc}")
213
-
214
- if not prepared:
215
- continue
216
- if not prepared:
217
- continue
218
-
219
- base_tensors = torch.stack([x[1] for x in prepared]).float()
220
- semantic_base = runtime.infer(base_tensors)
221
-
222
-
223
- semantic_aug = {}
224
- if not reuse_sem:
225
- aug_tensors = []
226
- aug_keys = []
227
- for pi, (_, _, views) in enumerate(prepared):
228
- for vv in views:
229
- if vv["view_id"] > 0 and vv["semantic_tensor"] is not None:
230
- aug_tensors.append(vv["semantic_tensor"])
231
- aug_keys.append((pi, vv["view_id"]))
232
- for astart in range(0, len(aug_tensors), bsz):
233
- aa = torch.stack(aug_tensors[astart:astart + bsz]).float()
234
- yy = runtime.infer(aa)
235
- for off in range(len(yy)):
236
- semantic_aug[aug_keys[astart + off]] = yy[off]
237
-
238
-
239
- flat_views = []
240
- smaps = []
241
- cmaps = []
242
- for pi, (r, _, views) in enumerate(prepared):
243
- for vv in views:
244
- flat_views.append((pi, r, vv))
245
- smaps.append(torch.from_numpy(vv["spectral_map"])[None])
246
- cmaps.append(torch.from_numpy(vv["camera_map"])[None])
247
- ss = _specialist_batch_scores(
248
- spec,
249
- cam,
250
- torch.stack(smaps).float(),
251
- torch.stack(cmaps).float(),
252
- )
253
-
254
- for i, (pi, r, vv) in enumerate(flat_views):
255
- v = int(vv["view_id"])
256
- sem = semantic_base[pi] if (v == 0 or reuse_sem) else semantic_aug[(pi, v)]
257
- h = vv["handcrafted"]
258
- store["semantic"].append(sem)
259
- store["camera"].append(h["camera"])
260
- store["spectral"].append(h["spectral"])
261
- store["patch"].append(h["patch"])
262
- store["quality"].append(h["quality"])
263
- store["specialist"].append(ss[i])
264
- store["label"].append(int(r["label"]))
265
- store["source"].append(str(r.get("source", "unknown")))
266
- store["path"].append(str(r["path"]))
267
- store["split"].append(str(r.get("split", "train")))
268
- store["view_id"].append(v)
269
- store["light_bucket"].append(str(r.get("light_bucket", "unknown")))
270
- store["mean_luma"].append(float(h["quality"][0]))
271
- finally:
272
- if executor is not None:
273
- executor.shutdown(wait=True)
274
- runtime.release_accelerator_memory()
275
-
276
- arrays = {}
277
- for k, v in store.items():
278
- if k in {"semantic", "camera", "spectral", "patch", "quality", "specialist"}:
279
- dtype = np.float16 if (k == "semantic" and bool(fc.get("cache_semantic_float16", True))) else np.float32
280
- arrays[k] = np.stack(v).astype(dtype)
281
- elif k in {"label", "view_id"}:
282
- arrays[k] = np.asarray(v, dtype=np.int64)
283
- elif k == "mean_luma":
284
- arrays[k] = np.asarray(v, dtype=np.float32)
285
- else:
286
- arrays[k] = np.asarray(v, dtype=str)
287
-
288
-
289
- if bool(fc.get("compress_feature_cache", False)):
290
- np.savez_compressed(p, **arrays)
291
- else:
292
- np.savez(p, **arrays)
293
-
294
- meta = {
295
- "version": 5,
296
- "foundation_requested": fc.get("backend"),
297
- "foundation_actual": foundation.backend,
298
- "foundation_runtime": runtime.backend,
299
- "foundation_dim": int(foundation.dim),
300
- "foundation_state": str(foundation_state_path.resolve()),
301
- "specialists": str(Path(specialists_path).resolve()),
302
- "augment_copies": int(augment_copies),
303
- "augment_selected_images": int(len(augment_paths)),
304
- "augment_max_per_class": int(augment_cap),
305
- "reuse_semantic_for_augmented_views": reuse_sem,
306
- "unique_images": int(total_unique),
307
- "input_unique_images": int(input_unique),
308
- "train_cap_per_class": int(train_cap),
309
- "train_coreset": bool(fc.get("train_coreset", False)),
310
- "count": int(len(arrays["label"])),
311
- "dims": {k: int(arrays[k].shape[1]) for k in ["semantic", "camera", "spectral", "patch", "quality", "specialist"]},
312
- }
313
- p.with_suffix(p.suffix + ".meta.json").write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")
314
- print(
315
- f"[saved] {p} ({meta['count']} views from {meta['unique_images']} unique images, "
316
- f"foundation={foundation.backend}, runtime={runtime.backend}, reuse_aug_semantic={reuse_sem})"
317
- )
318
- return meta
319
-
320
-
321
- def load_cache(path: str) -> dict[str, np.ndarray]:
322
- z = np.load(path, allow_pickle=False)
323
- return {k: z[k] for k in z.files}