HayrettinIscan commited on
Commit
fe7f66c
·
verified ·
1 Parent(s): d825ec1

Fix BadZipFile skip + _missing_uids harden

Browse files
Files changed (1) hide show
  1. code/meshai_train/dataset.py +221 -0
code/meshai_train/dataset.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import numpy as np
7
+ import torch
8
+ from PIL import Image
9
+ from torch.utils.data import Dataset
10
+
11
+ HF_PREPROCESSED_DEFAULT = "HayrettinIscan/MeshAI-Preprocessed-4K"
12
+
13
+
14
+ def _uid_hf_prefix(uid: str) -> str:
15
+ uid = str(uid)
16
+ return f"preprocessed/{uid[:2]}/{uid}"
17
+
18
+
19
+ class PreprocessedMeshDataset(Dataset):
20
+ """Loads geometry_latent.npz + render PNGs (local cache or HF)."""
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ token: str | None,
26
+ data_root: Path | None = None,
27
+ hf_repo: str = HF_PREPROCESSED_DEFAULT,
28
+ limit: int | None = None,
29
+ render_size: int = 128,
30
+ max_views: int = 4,
31
+ ) -> None:
32
+ self.token = token
33
+ self.data_root = data_root
34
+ self.hf_repo = hf_repo
35
+ self.render_size = render_size
36
+ self.max_views = max_views
37
+ self._missing_uids: set[str] = set()
38
+ self.objects = self._load_index(limit)
39
+
40
+ def _load_index(self, limit: int | None) -> list[dict]:
41
+ rows: list[dict] = []
42
+ roots = [p for p in (self.data_root, Path("data/preprocessed")) if p and p.exists()]
43
+ for root in roots:
44
+ for latent in root.rglob("geometry_latent.npz"):
45
+ uid = latent.parent.name
46
+ if len(uid) >= 8:
47
+ rows.append({"uid": uid, "object_id": uid, "status": "ready"})
48
+ if rows:
49
+ break
50
+ if not rows:
51
+ local_summary = Path("data/preprocessed/preprocessed_objects.json")
52
+ if local_summary.exists():
53
+ payload = json.loads(local_summary.read_text(encoding="utf-8"))
54
+ rows = [r for r in payload.get("objects", []) if r.get("status", "ready") == "ready"]
55
+ if not rows:
56
+ from huggingface_hub import hf_hub_download
57
+
58
+ path = hf_hub_download(
59
+ repo_id=self.hf_repo,
60
+ filename="preprocessed/preprocessed_objects.json",
61
+ repo_type="dataset",
62
+ token=self.token,
63
+ )
64
+ payload = json.loads(Path(path).read_text(encoding="utf-8"))
65
+ rows = [r for r in payload.get("objects", []) if r.get("status", "ready") == "ready"]
66
+ if limit is not None:
67
+ rows = rows[:limit]
68
+ if not rows:
69
+ raise RuntimeError("Preprocessed object listesi bos.")
70
+ return rows
71
+
72
+ def __len__(self) -> int:
73
+ return len(self.objects)
74
+
75
+ def _resolve_file(self, uid: str, name: str) -> Path:
76
+ candidates = []
77
+ if self.data_root:
78
+ candidates.append(self.data_root / uid[:2] / uid / name)
79
+ candidates.append(self.data_root / uid / name)
80
+ candidates.append(Path("data/preprocessed") / uid[:2] / uid / name)
81
+ candidates.append(Path("data/preprocessed") / uid / name)
82
+ for local in candidates:
83
+ if local.exists():
84
+ return local
85
+ if not self.token:
86
+ raise FileNotFoundError(f"Yerel dosya yok ve HF_TOKEN yok: {uid}/{name}")
87
+ from huggingface_hub import hf_hub_download
88
+
89
+ rel = f"{_uid_hf_prefix(uid)}/{name}"
90
+ try:
91
+ return Path(
92
+ hf_hub_download(
93
+ repo_id=self.hf_repo,
94
+ filename=rel,
95
+ repo_type="dataset",
96
+ token=self.token,
97
+ )
98
+ )
99
+ except Exception as exc:
100
+ # 404 / EntryNotFound / RemoteEntryNotFound — manifestte olup HF'de olmayan UID
101
+ msg = str(exc).lower()
102
+ if "404" in msg or "entrynotfound" in msg.replace(" ", "") or "not found" in msg:
103
+ raise FileNotFoundError(f"HF eksik: {rel}") from exc
104
+ raise
105
+
106
+ def _load_npz(self, uid: str):
107
+ """Incomplete HF cache can yield BadZipFile; delete + force redownload once."""
108
+ import zipfile
109
+
110
+ path = self._resolve_file(uid, "geometry_latent.npz")
111
+ try:
112
+ return np.load(path)
113
+ except (zipfile.BadZipFile, OSError, ValueError) as exc:
114
+ try:
115
+ path.unlink(missing_ok=True)
116
+ except OSError:
117
+ pass
118
+ if not self.token:
119
+ raise FileNotFoundError(f"Bozuk latent (token yok, yeniden indirilemez): {uid}") from exc
120
+ from huggingface_hub import hf_hub_download
121
+
122
+ rel = f"{_uid_hf_prefix(uid)}/geometry_latent.npz"
123
+ path = Path(
124
+ hf_hub_download(
125
+ repo_id=self.hf_repo,
126
+ filename=rel,
127
+ repo_type="dataset",
128
+ token=self.token,
129
+ force_download=True,
130
+ )
131
+ )
132
+ try:
133
+ return np.load(path)
134
+ except (zipfile.BadZipFile, OSError, ValueError) as exc2:
135
+ raise FileNotFoundError(f"Bozuk/eksik latent: {rel}") from exc2
136
+
137
+ def _getitem_one(self, idx: int) -> dict:
138
+ row = self.objects[idx]
139
+ uid = str(row.get("uid") or row.get("object_id"))
140
+ if uid in self._missing_uids:
141
+ raise FileNotFoundError(f"cached missing uid: {uid}")
142
+ try:
143
+ z = self._load_npz(uid)
144
+ except FileNotFoundError:
145
+ self._missing_uids.add(uid)
146
+ raise
147
+ hist = z["vertex_hist"].astype(np.float32)
148
+ stats = z["normal_stats"].astype(np.float32)
149
+ voxel = z["voxel_occ_32"].astype(np.float32).reshape(-1) / 255.0
150
+
151
+ views: list[np.ndarray] = []
152
+ for i in range(self.max_views):
153
+ for suffix in (f"view_{i:02d}_tex.png", f"view_{i:02d}.png"):
154
+ try:
155
+ img_path = self._resolve_file(uid, f"renders/{suffix}")
156
+ img = Image.open(img_path).convert("RGB").resize(
157
+ (self.render_size, self.render_size), Image.BILINEAR
158
+ )
159
+ arr = np.asarray(img, dtype=np.float32) / 255.0
160
+ views.append(arr.transpose(2, 0, 1))
161
+ break
162
+ except Exception:
163
+ continue
164
+ if not views:
165
+ views.append(np.zeros((3, self.render_size, self.render_size), dtype=np.float32))
166
+ # Collate batch_size>1 icin view sayisini sabitle (eksik render pad).
167
+ while len(views) < self.max_views:
168
+ views.append(views[-1].copy())
169
+ views = views[: self.max_views]
170
+
171
+ return {
172
+ "uid": uid,
173
+ "idx": idx,
174
+ "geom_in": np.concatenate([hist, stats]),
175
+ "voxel_tgt": voxel,
176
+ "views": np.stack(views, axis=0),
177
+ "quality_score": float(row.get("quality_score", 0.0)),
178
+ }
179
+
180
+ def __getitem__(self, idx: int) -> dict:
181
+ # Manifest bazen HF'de olmayan / bozuk UID icerir; sonraki ornege kay.
182
+ import zipfile
183
+
184
+ if not hasattr(self, "_missing_uids"):
185
+ self._missing_uids = set()
186
+ n = len(self.objects)
187
+ last_err: Exception | None = None
188
+ for offset in range(n):
189
+ cur = (idx + offset) % n
190
+ try:
191
+ return self._getitem_one(cur)
192
+ except (FileNotFoundError, zipfile.BadZipFile, OSError, ValueError, KeyError) as exc:
193
+ last_err = exc
194
+ try:
195
+ uid = str(self.objects[cur].get("uid") or self.objects[cur].get("object_id") or "")
196
+ if uid:
197
+ self._missing_uids.add(uid)
198
+ except Exception:
199
+ pass
200
+ continue
201
+ raise RuntimeError(f"Erisilebilir preprocessed ornek yok (son: {last_err})")
202
+
203
+
204
+ def collate_preprocessed(batch: list[dict]) -> dict:
205
+ # Savunmaci pad: eski checkpoint/yamali dataset karisik shape getirebilir.
206
+ max_v = max(int(b["views"].shape[0]) for b in batch)
207
+ views_list = []
208
+ for b in batch:
209
+ v = b["views"]
210
+ if v.shape[0] < max_v:
211
+ pad = np.repeat(v[-1:], max_v - v.shape[0], axis=0)
212
+ v = np.concatenate([v, pad], axis=0)
213
+ views_list.append(v[:max_v])
214
+ return {
215
+ "uid": [b["uid"] for b in batch],
216
+ "idx": torch.tensor([b["idx"] for b in batch], dtype=torch.long),
217
+ "geom_in": torch.tensor(np.stack([b["geom_in"] for b in batch]), dtype=torch.float32),
218
+ "voxel_tgt": torch.tensor(np.stack([b["voxel_tgt"] for b in batch]), dtype=torch.float32),
219
+ "views": torch.tensor(np.stack(views_list), dtype=torch.float32),
220
+ "quality_score": torch.tensor([b["quality_score"] for b in batch], dtype=torch.float32),
221
+ }