someone-in-the-world Claude Sonnet 4.6 commited on
Commit
8e461ed
·
1 Parent(s): e005d03

Move logging logic into logging_utils.py

Browse files

Extracts log_inference and its _readme_from_features helper out of
app.py into a dedicated module, keeping app.py focused on the Gradio
UI and inference pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +1 -133
  2. logging_utils.py +135 -0
app.py CHANGED
@@ -10,8 +10,8 @@ import base64
10
  import json
11
  import html as html_lib
12
  from io import BytesIO
13
- from datetime import datetime, timezone
14
  from PIL import Image
 
15
 
16
  MAX_SEED = np.iinfo(np.int32).max
17
  LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
@@ -48,9 +48,6 @@ print("Using default attention processor (FA3 skipped for ZeroGPU GPU-arch compa
48
 
49
  print("torch.compile skipped: lazy Triton kernel compilation inside @spaces.GPU always exceeds ZeroGPU's task timeout.")
50
 
51
- HF_TOKEN = os.environ.get("HF_TOKEN")
52
- DATASET_REPO = os.environ.get("LOG_DATASET_REPO")
53
-
54
  EXAMPLES_CONFIG = [
55
  {
56
  "images": ["examples/1.jpg"],
@@ -188,135 +185,6 @@ def update_dimensions_on_upload(image):
188
  return (nw // 8) * 8, (nh // 8) * 8
189
 
190
 
191
- def _readme_from_features(feats):
192
- lines = ["---", "configs:", "- config_name: default",
193
- " data_files:", " - split: train",
194
- " path: data/*.parquet", " features:"]
195
- for name, f in feats.items():
196
- lines.append(f" - name: {name}")
197
- if f.get("_type") == "Image":
198
- lines.append(" dtype: image")
199
- elif f.get("_type") == "Sequence" and f.get("feature", {}).get("_type") == "Image":
200
- lines.append(" sequence: image")
201
- else:
202
- lines.append(f" dtype: {f.get('dtype', 'string')}")
203
- lines.append("---")
204
- return "\n".join(lines) + "\n"
205
-
206
-
207
- def log_inference(pil_inputs, output_pil, prompt, seed, steps, guidance_scale,
208
- input_width, input_height, duration_seconds, success, error_message=""):
209
- if not HF_TOKEN or not DATASET_REPO:
210
- return
211
- try:
212
- import tempfile, json as _json
213
- import pyarrow as pa
214
- import pyarrow.parquet as pq
215
- from huggingface_hub import HfApi, hf_hub_download
216
-
217
- # Image columns need Arrow struct {bytes: binary, path: utf8} plus
218
- # a 'huggingface' schema metadata key for the HF viewer to render them.
219
- img_struct = pa.struct([("bytes", pa.binary()), ("path", pa.string())])
220
- hf_meta = _json.dumps({"info": {"features": {
221
- "timestamp": {"dtype": "string", "_type": "Value"},
222
- "prompt": {"dtype": "string", "_type": "Value"},
223
- "seed": {"dtype": "int32", "_type": "Value"},
224
- "steps": {"dtype": "int32", "_type": "Value"},
225
- "guidance_scale": {"dtype": "float32", "_type": "Value"},
226
- "input_images": {"feature": {"_type": "Image"}, "_type": "Sequence"},
227
- "output_image": {"_type": "Image"},
228
- "duration_seconds": {"dtype": "float32", "_type": "Value"},
229
- "input_width": {"dtype": "int32", "_type": "Value"},
230
- "input_height": {"dtype": "int32", "_type": "Value"},
231
- "success": {"dtype": "bool", "_type": "Value"},
232
- "error_message": {"dtype": "string", "_type": "Value"},
233
- }}}).encode()
234
- schema = pa.schema([
235
- ("timestamp", pa.string()),
236
- ("prompt", pa.string()),
237
- ("seed", pa.int32()),
238
- ("steps", pa.int32()),
239
- ("guidance_scale", pa.float32()),
240
- ("input_images", pa.list_(img_struct)),
241
- ("output_image", img_struct),
242
- ("duration_seconds", pa.float32()),
243
- ("input_width", pa.int32()),
244
- ("input_height", pa.int32()),
245
- ("success", pa.bool_()),
246
- ("error_message", pa.string()),
247
- ], metadata={b"huggingface": hf_meta})
248
-
249
- def _to_jpeg(img, quality=85):
250
- if img is None:
251
- return None
252
- buf = BytesIO()
253
- img.convert("RGB").save(buf, format="JPEG", quality=quality)
254
- return buf.getvalue()
255
-
256
- def _img(b):
257
- return {"bytes": b, "path": None}
258
-
259
- input_jpegs = [_to_jpeg(img) for img in pil_inputs]
260
- output_jpeg = _to_jpeg(output_pil)
261
-
262
- new_table = pa.table({
263
- "timestamp": pa.array([datetime.now(timezone.utc).isoformat()], type=pa.string()),
264
- "prompt": pa.array([prompt], type=pa.string()),
265
- "seed": pa.array([int(seed)], type=pa.int32()),
266
- "steps": pa.array([int(steps)], type=pa.int32()),
267
- "guidance_scale": pa.array([float(guidance_scale)], type=pa.float32()),
268
- "input_images": pa.array([[_img(b) for b in input_jpegs]], type=pa.list_(img_struct)),
269
- "output_image": pa.array([_img(output_jpeg) if output_jpeg else None], type=img_struct),
270
- "duration_seconds": pa.array([float(duration_seconds)], type=pa.float32()),
271
- "input_width": pa.array([int(input_width)], type=pa.int32()),
272
- "input_height": pa.array([int(input_height)], type=pa.int32()),
273
- "success": pa.array([bool(success)], type=pa.bool_()),
274
- "error_message": pa.array([str(error_message)], type=pa.string()),
275
- }, schema=schema)
276
- print(f"[log] built row — success={success}, inputs={len(input_jpegs)}")
277
-
278
- today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
279
- path_in_repo = f"data/{today}.parquet"
280
- api = HfApi(token=HF_TOKEN)
281
- api.create_repo(repo_id=DATASET_REPO, repo_type="dataset", private=True, exist_ok=True)
282
-
283
- # Upload README.md once so the HF viewer knows the column types.
284
- try:
285
- hf_hub_download(repo_id=DATASET_REPO, filename="README.md",
286
- repo_type="dataset", token=HF_TOKEN)
287
- except Exception:
288
- readme = _readme_from_features(_json.loads(hf_meta)["info"]["features"])
289
- api.upload_file(path_or_fileobj=readme.encode(), path_in_repo="README.md",
290
- repo_id=DATASET_REPO, repo_type="dataset")
291
- print("[log] uploaded README.md with feature schema")
292
-
293
- try:
294
- local_path = hf_hub_download(
295
- repo_id=DATASET_REPO, filename=path_in_repo,
296
- repo_type="dataset", token=HF_TOKEN,
297
- )
298
- existing = pq.read_table(local_path)
299
- combined = pa.concat_tables([existing, new_table])
300
- combined = combined.replace_schema_metadata(schema.metadata)
301
- print(f"[log] appending to existing {existing.num_rows} row(s)")
302
- except Exception as dl_err:
303
- print(f"[log] no existing file ({dl_err}), starting fresh")
304
- combined = new_table
305
-
306
- with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as tmp:
307
- tmp_path = tmp.name
308
- pq.write_table(combined, tmp_path)
309
- print(f"[log] uploading {path_in_repo} ({combined.num_rows} row(s), {os.path.getsize(tmp_path)//1024}KB)")
310
- api.upload_file(
311
- path_or_fileobj=tmp_path, path_in_repo=path_in_repo,
312
- repo_id=DATASET_REPO, repo_type="dataset",
313
- )
314
- print(f"[log] upload done — {DATASET_REPO}/{path_in_repo}")
315
- except Exception as log_err:
316
- import traceback as _tb
317
- print(f"[log] WARNING: {log_err}\n{_tb.format_exc()}")
318
-
319
-
320
  @spaces.GPU
321
  def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
322
  import time, traceback
 
10
  import json
11
  import html as html_lib
12
  from io import BytesIO
 
13
  from PIL import Image
14
+ from logging_utils import log_inference
15
 
16
  MAX_SEED = np.iinfo(np.int32).max
17
  LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
 
48
 
49
  print("torch.compile skipped: lazy Triton kernel compilation inside @spaces.GPU always exceeds ZeroGPU's task timeout.")
50
 
 
 
 
51
  EXAMPLES_CONFIG = [
52
  {
53
  "images": ["examples/1.jpg"],
 
185
  return (nw // 8) * 8, (nh // 8) * 8
186
 
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  @spaces.GPU
189
  def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
190
  import time, traceback
logging_utils.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from io import BytesIO
3
+ from datetime import datetime, timezone
4
+
5
+ HF_TOKEN = os.environ.get("HF_TOKEN")
6
+ DATASET_REPO = os.environ.get("LOG_DATASET_REPO")
7
+
8
+
9
+ def _readme_from_features(feats):
10
+ lines = ["---", "configs:", "- config_name: default",
11
+ " data_files:", " - split: train",
12
+ " path: data/*.parquet", " features:"]
13
+ for name, f in feats.items():
14
+ lines.append(f" - name: {name}")
15
+ if f.get("_type") == "Image":
16
+ lines.append(" dtype: image")
17
+ elif f.get("_type") == "Sequence" and f.get("feature", {}).get("_type") == "Image":
18
+ lines.append(" sequence: image")
19
+ else:
20
+ lines.append(f" dtype: {f.get('dtype', 'string')}")
21
+ lines.append("---")
22
+ return "\n".join(lines) + "\n"
23
+
24
+
25
+ def log_inference(pil_inputs, output_pil, prompt, seed, steps, guidance_scale,
26
+ input_width, input_height, duration_seconds, success, error_message=""):
27
+ if not HF_TOKEN or not DATASET_REPO:
28
+ return
29
+ try:
30
+ import tempfile, json as _json
31
+ import pyarrow as pa
32
+ import pyarrow.parquet as pq
33
+ from huggingface_hub import HfApi, hf_hub_download
34
+
35
+ # Image columns need Arrow struct {bytes: binary, path: utf8} plus
36
+ # a 'huggingface' schema metadata key for the HF viewer to render them.
37
+ img_struct = pa.struct([("bytes", pa.binary()), ("path", pa.string())])
38
+ hf_meta = _json.dumps({"info": {"features": {
39
+ "timestamp": {"dtype": "string", "_type": "Value"},
40
+ "prompt": {"dtype": "string", "_type": "Value"},
41
+ "seed": {"dtype": "int32", "_type": "Value"},
42
+ "steps": {"dtype": "int32", "_type": "Value"},
43
+ "guidance_scale": {"dtype": "float32", "_type": "Value"},
44
+ "input_images": {"feature": {"_type": "Image"}, "_type": "Sequence"},
45
+ "output_image": {"_type": "Image"},
46
+ "duration_seconds": {"dtype": "float32", "_type": "Value"},
47
+ "input_width": {"dtype": "int32", "_type": "Value"},
48
+ "input_height": {"dtype": "int32", "_type": "Value"},
49
+ "success": {"dtype": "bool", "_type": "Value"},
50
+ "error_message": {"dtype": "string", "_type": "Value"},
51
+ }}}).encode()
52
+ schema = pa.schema([
53
+ ("timestamp", pa.string()),
54
+ ("prompt", pa.string()),
55
+ ("seed", pa.int32()),
56
+ ("steps", pa.int32()),
57
+ ("guidance_scale", pa.float32()),
58
+ ("input_images", pa.list_(img_struct)),
59
+ ("output_image", img_struct),
60
+ ("duration_seconds", pa.float32()),
61
+ ("input_width", pa.int32()),
62
+ ("input_height", pa.int32()),
63
+ ("success", pa.bool_()),
64
+ ("error_message", pa.string()),
65
+ ], metadata={b"huggingface": hf_meta})
66
+
67
+ def _to_jpeg(img, quality=85):
68
+ if img is None:
69
+ return None
70
+ buf = BytesIO()
71
+ img.convert("RGB").save(buf, format="JPEG", quality=quality)
72
+ return buf.getvalue()
73
+
74
+ def _img(b):
75
+ return {"bytes": b, "path": None}
76
+
77
+ input_jpegs = [_to_jpeg(img) for img in pil_inputs]
78
+ output_jpeg = _to_jpeg(output_pil)
79
+
80
+ new_table = pa.table({
81
+ "timestamp": pa.array([datetime.now(timezone.utc).isoformat()], type=pa.string()),
82
+ "prompt": pa.array([prompt], type=pa.string()),
83
+ "seed": pa.array([int(seed)], type=pa.int32()),
84
+ "steps": pa.array([int(steps)], type=pa.int32()),
85
+ "guidance_scale": pa.array([float(guidance_scale)], type=pa.float32()),
86
+ "input_images": pa.array([[_img(b) for b in input_jpegs]], type=pa.list_(img_struct)),
87
+ "output_image": pa.array([_img(output_jpeg) if output_jpeg else None], type=img_struct),
88
+ "duration_seconds": pa.array([float(duration_seconds)], type=pa.float32()),
89
+ "input_width": pa.array([int(input_width)], type=pa.int32()),
90
+ "input_height": pa.array([int(input_height)], type=pa.int32()),
91
+ "success": pa.array([bool(success)], type=pa.bool_()),
92
+ "error_message": pa.array([str(error_message)], type=pa.string()),
93
+ }, schema=schema)
94
+ print(f"[log] built row — success={success}, inputs={len(input_jpegs)}")
95
+
96
+ today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
97
+ path_in_repo = f"data/{today}.parquet"
98
+ api = HfApi(token=HF_TOKEN)
99
+ api.create_repo(repo_id=DATASET_REPO, repo_type="dataset", private=True, exist_ok=True)
100
+
101
+ # Upload README.md once so the HF viewer knows the column types.
102
+ try:
103
+ hf_hub_download(repo_id=DATASET_REPO, filename="README.md",
104
+ repo_type="dataset", token=HF_TOKEN)
105
+ except Exception:
106
+ readme = _readme_from_features(_json.loads(hf_meta)["info"]["features"])
107
+ api.upload_file(path_or_fileobj=readme.encode(), path_in_repo="README.md",
108
+ repo_id=DATASET_REPO, repo_type="dataset")
109
+ print("[log] uploaded README.md with feature schema")
110
+
111
+ try:
112
+ local_path = hf_hub_download(
113
+ repo_id=DATASET_REPO, filename=path_in_repo,
114
+ repo_type="dataset", token=HF_TOKEN,
115
+ )
116
+ existing = pq.read_table(local_path)
117
+ combined = pa.concat_tables([existing, new_table])
118
+ combined = combined.replace_schema_metadata(schema.metadata)
119
+ print(f"[log] appending to existing {existing.num_rows} row(s)")
120
+ except Exception as dl_err:
121
+ print(f"[log] no existing file ({dl_err}), starting fresh")
122
+ combined = new_table
123
+
124
+ with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as tmp:
125
+ tmp_path = tmp.name
126
+ pq.write_table(combined, tmp_path)
127
+ print(f"[log] uploading {path_in_repo} ({combined.num_rows} row(s), {os.path.getsize(tmp_path)//1024}KB)")
128
+ api.upload_file(
129
+ path_or_fileobj=tmp_path, path_in_repo=path_in_repo,
130
+ repo_id=DATASET_REPO, repo_type="dataset",
131
+ )
132
+ print(f"[log] upload done — {DATASET_REPO}/{path_in_repo}")
133
+ except Exception as log_err:
134
+ import traceback as _tb
135
+ print(f"[log] WARNING: {log_err}\n{_tb.format_exc()}")