PLUS-WAVE commited on
Commit
e08fcbd
·
verified ·
1 Parent(s): 4507f2a

Sync Hugging Face runtime tests

Browse files
Files changed (1) hide show
  1. tests/test_hf_runtime.py +254 -38
tests/test_hf_runtime.py CHANGED
@@ -7,15 +7,19 @@ import torch
7
  from src.demo import hf_runtime, hf_ui
8
  from src.demo.hf_runtime import (
9
  SPLAT_TRANSFORM_PACKAGE,
10
- ExportedArtifacts,
11
  GaussianArtifact,
 
12
  build_standalone_viewer,
13
  build_splat_transform_command,
 
 
14
  install_shared_viewer_assets,
15
  load_gaussian_artifact,
 
16
  save_gaussian_artifact,
17
  )
18
- from src.demo.hf_ui import build_viewer_iframe
19
  from src.demo.infer_single_image import patch_supersplat_html_viewer_bridge
20
  from src.utils.gaussians import Gaussians3D
21
 
@@ -48,24 +52,92 @@ def test_gaussian_artifact_round_trip(tmp_path) -> None:
48
  assert torch.equal(expected_tensor, actual_tensor)
49
 
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  def test_build_splat_transform_command_is_fixed_and_non_interactive() -> None:
52
  command = build_splat_transform_command(
53
  scene_ply=Path("scene.ply"),
54
- output_html=Path("scene.html"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  viewer_settings=Path("viewer.json"),
56
  )
57
 
58
- assert command[:3] == ["npx", "--yes", SPLAT_TRANSFORM_PACKAGE]
59
- assert command[3:] == [
 
 
 
 
 
60
  "--quiet",
61
  "--overwrite",
62
  "--unbundled",
63
  "--viewer-settings",
64
  "viewer.json",
65
- "scene.ply",
66
  "--filter-harmonics",
67
  "0",
68
- "scene.html",
69
  ]
70
 
71
 
@@ -81,6 +153,15 @@ def test_build_viewer_iframe_uses_gradio_file_route(tmp_path) -> None:
81
  assert "viewer%20output.html" in iframe
82
 
83
 
 
 
 
 
 
 
 
 
 
84
  def test_supersplat_viewer_bridge_reports_first_frame(tmp_path) -> None:
85
  viewer_path = tmp_path / "viewer.html"
86
  viewer_path.write_text(
@@ -130,23 +211,31 @@ def test_select_example_returns_gallery_path() -> None:
130
 
131
 
132
  def test_viewer_statuses_are_explicit() -> None:
 
133
  assert "Reconstructing scene" in hf_ui.show_reconstructing_viewer()
134
- assert "Preparing viewer" in hf_ui.show_exporting_viewer()
 
135
  assert "Reconstruction stopped" in hf_ui.show_failed_viewer()
 
136
 
137
 
138
- def _write_unbundled_viewer(output_dir: Path) -> Path:
139
- viewer_html = output_dir / "viewer.html"
 
 
 
 
 
140
  viewer_html.write_text(
141
  '<html><head><link rel="stylesheet" href="./index.css"></head>'
142
  '<body><script type="module">'
143
  "const settingsUrl = './settings.json';"
144
  'const settings = {settings: fetch(settingsUrl).then(response => response.json())};'
145
- 'const scene = fetch("viewer.sog");'
146
  "import { main } from './index.js';"
147
  "</script></body></html>"
148
  )
149
- (output_dir / "viewer.sog").write_bytes(b"scene")
150
  (output_dir / "index.css").write_text("canvas { display: block; }")
151
  (output_dir / "index.js").write_text("const main = true;")
152
  (output_dir / "settings.json").write_text('{"camera":{"fov":60}}')
@@ -154,10 +243,12 @@ def _write_unbundled_viewer(output_dir: Path) -> Path:
154
 
155
 
156
  def test_build_standalone_viewer_embeds_unbundled_assets(tmp_path) -> None:
157
- viewer_html = _write_unbundled_viewer(tmp_path)
158
- standalone_html = tmp_path / "scene.html"
 
 
159
 
160
- build_standalone_viewer(viewer_html, standalone_html)
161
  source = standalone_html.read_text()
162
 
163
  assert "canvas { display: block; }" in source
@@ -166,6 +257,37 @@ def test_build_standalone_viewer_embeds_unbundled_assets(tmp_path) -> None:
166
  assert "data:application/octet-stream;base64,c2NlbmU=" in source
167
 
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  def test_install_shared_viewer_assets_uses_content_hash(tmp_path) -> None:
170
  request_dir = tmp_path / "request"
171
  request_dir.mkdir()
@@ -180,7 +302,7 @@ def test_install_shared_viewer_assets_uses_content_hash(tmp_path) -> None:
180
  assert f"../_viewer_assets/{shared_dir.name}/index.js" in source
181
 
182
 
183
- def test_export_gaussian_artifact_preserves_all_gaussians(
184
  tmp_path, monkeypatch
185
  ) -> None:
186
  artifact_path = tmp_path / "gaussians.pt"
@@ -194,6 +316,14 @@ def test_export_gaussian_artifact_preserves_all_gaussians(
194
  artifact_path,
195
  )
196
  captured = {}
 
 
 
 
 
 
 
 
197
 
198
  def fake_save_ply(*, gaussians, path, **_) -> None:
199
  captured["gaussians"] = gaussians
@@ -201,20 +331,14 @@ def test_export_gaussian_artifact_preserves_all_gaussians(
201
 
202
  def fake_run(command, **_) -> None:
203
  captured["command"] = command
204
- _write_unbundled_viewer(Path(command[-1]).parent)
205
 
206
  monkeypatch.setattr(hf_runtime, "save_ply", fake_save_ply)
207
  monkeypatch.setattr(hf_runtime.subprocess, "run", fake_run)
208
- monkeypatch.setattr(hf_runtime, "patch_supersplat_html_auto_rotate", lambda _: None)
209
- monkeypatch.setattr(
210
- hf_runtime,
211
- "patch_supersplat_html_viewer_bridge",
212
- lambda *_, **__: None,
213
- )
214
-
215
  exported = hf_runtime.export_gaussian_artifact(
216
  artifact_path=artifact_path,
217
  output_dir=tmp_path / "output",
 
218
  command_prefix=["splat-transform"],
219
  )
220
 
@@ -222,35 +346,127 @@ def test_export_gaussian_artifact_preserves_all_gaussians(
222
  assert actual.mean_vectors.shape == expected.mean_vectors.shape
223
  for expected_tensor, actual_tensor in zip(expected, actual):
224
  assert torch.equal(expected_tensor, actual_tensor)
225
- assert "--unbundled" in captured["command"]
 
 
226
  assert exported.viewer_html.is_file()
 
 
227
  assert exported.standalone_html.is_file()
 
228
 
229
 
230
- def test_export_results_removes_only_internal_artifact(tmp_path, monkeypatch) -> None:
231
  internal_artifact = tmp_path / "gaussians.pt"
232
  scene_ply = tmp_path / "scene.ply"
233
- viewer_html = tmp_path / "viewer.html"
234
- standalone_html = tmp_path / "scene.html"
235
- for path in (internal_artifact, scene_ply, viewer_html, standalone_html):
236
  path.write_bytes(b"test")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  monkeypatch.setattr(
239
  hf_ui,
240
- "export_gaussian_artifact",
241
- lambda **_: ExportedArtifacts(
242
- scene_ply=scene_ply,
243
  viewer_html=viewer_html,
244
- standalone_html=standalone_html,
245
  ),
246
  )
247
 
248
- hf_ui.export_results(str(internal_artifact))
249
 
250
- assert not internal_artifact.exists()
251
- assert scene_ply.exists()
252
- assert viewer_html.exists()
253
- assert standalone_html.exists()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
 
256
  def test_app_imports_spaces_before_model_modules() -> None:
 
7
  from src.demo import hf_runtime, hf_ui
8
  from src.demo.hf_runtime import (
9
  SPLAT_TRANSFORM_PACKAGE,
10
+ BrowserViewerArtifacts,
11
  GaussianArtifact,
12
+ ViewerTemplate,
13
  build_standalone_viewer,
14
  build_splat_transform_command,
15
+ build_viewer_template_command,
16
+ export_filtered_gaussian_ply,
17
  install_shared_viewer_assets,
18
  load_gaussian_artifact,
19
+ prepare_viewer_template,
20
  save_gaussian_artifact,
21
  )
22
+ from src.demo.hf_ui import build_viewer_iframe, build_viewer_preloader
23
  from src.demo.infer_single_image import patch_supersplat_html_viewer_bridge
24
  from src.utils.gaussians import Gaussians3D
25
 
 
52
  assert torch.equal(expected_tensor, actual_tensor)
53
 
54
 
55
+ def test_filtered_ply_export_removes_spatial_outlier(tmp_path, monkeypatch) -> None:
56
+ points = torch.cat(
57
+ [
58
+ torch.randn(100, 3) * 0.01,
59
+ torch.tensor([[10.0, 10.0, 10.0]]),
60
+ ],
61
+ dim=0,
62
+ ).unsqueeze(0)
63
+ count = points.shape[1]
64
+ gaussians = Gaussians3D(
65
+ mean_vectors=points,
66
+ singular_values=torch.ones(1, count, 3),
67
+ quaternions=torch.ones(1, count, 4),
68
+ colors=torch.ones(1, count, 3),
69
+ opacities=torch.ones(1, count),
70
+ )
71
+ artifact_path = tmp_path / "gaussians.pt"
72
+ save_gaussian_artifact(
73
+ GaussianArtifact(
74
+ gaussians=gaussians,
75
+ focal_length_px=1200.0,
76
+ image_shape=(1152, 1536),
77
+ ),
78
+ artifact_path,
79
+ )
80
+ captured = {}
81
+
82
+ def fake_save_ply(*, gaussians, path, **_) -> None:
83
+ captured["gaussians"] = gaussians
84
+ path.write_bytes(b"ply")
85
+
86
+ monkeypatch.setattr(hf_runtime, "save_ply", fake_save_ply)
87
+
88
+ result = export_filtered_gaussian_ply(artifact_path, tmp_path / "output")
89
+
90
+ assert result.read_bytes() == b"ply"
91
+ assert captured["gaussians"].mean_vectors.shape[1] == count - 1
92
+ assert float(captured["gaussians"].mean_vectors.abs().max()) < 1.0
93
+
94
+
95
  def test_build_splat_transform_command_is_fixed_and_non_interactive() -> None:
96
  command = build_splat_transform_command(
97
  scene_ply=Path("scene.ply"),
98
+ output_sog=Path("viewer.sog"),
99
+ )
100
+
101
+ assert command[:4] == [
102
+ "npx",
103
+ "--yes",
104
+ "--prefer-offline",
105
+ SPLAT_TRANSFORM_PACKAGE,
106
+ ]
107
+ assert SPLAT_TRANSFORM_PACKAGE == "@playcanvas/splat-transform@3.1.6"
108
+ assert command[4:] == [
109
+ "--quiet",
110
+ "--overwrite",
111
+ "scene.ply",
112
+ "--filter-harmonics",
113
+ "0",
114
+ "viewer.sog",
115
+ ]
116
+
117
+
118
+ def test_build_viewer_template_command_is_unbundled() -> None:
119
+ command = build_viewer_template_command(
120
+ scene_ply=Path("template.ply"),
121
+ output_html=Path("template.html"),
122
  viewer_settings=Path("viewer.json"),
123
  )
124
 
125
+ assert command[:4] == [
126
+ "npx",
127
+ "--yes",
128
+ "--prefer-offline",
129
+ SPLAT_TRANSFORM_PACKAGE,
130
+ ]
131
+ assert command[4:] == [
132
  "--quiet",
133
  "--overwrite",
134
  "--unbundled",
135
  "--viewer-settings",
136
  "viewer.json",
137
+ "template.ply",
138
  "--filter-harmonics",
139
  "0",
140
+ "template.html",
141
  ]
142
 
143
 
 
153
  assert "viewer%20output.html" in iframe
154
 
155
 
156
+ def test_build_viewer_preloader_warms_template_in_hidden_iframe(tmp_path) -> None:
157
+ preload = build_viewer_preloader(tmp_path / "template.html")
158
+
159
+ assert "Ready" in preload
160
+ assert 'class="viewer-preloader"' in preload
161
+ assert "/gradio_api/file=" in preload
162
+ assert "data-infinisplat-viewer" not in preload
163
+
164
+
165
  def test_supersplat_viewer_bridge_reports_first_frame(tmp_path) -> None:
166
  viewer_path = tmp_path / "viewer.html"
167
  viewer_path.write_text(
 
211
 
212
 
213
  def test_viewer_statuses_are_explicit() -> None:
214
+ assert hf_ui.GPU_DURATION_SECONDS == 6
215
  assert "Reconstructing scene" in hf_ui.show_reconstructing_viewer()
216
+ assert "Filtering floaters" in hf_ui.show_exporting_viewer()
217
+ assert "PLY ready" in hf_ui.show_ply_ready_viewer()
218
  assert "Reconstruction stopped" in hf_ui.show_failed_viewer()
219
+ assert hf_ui.show_failed_html_download()["label"] == "HTML export failed"
220
 
221
 
222
+ def _write_unbundled_viewer(
223
+ output_dir: Path,
224
+ viewer_name: str = "viewer.html",
225
+ ) -> Path:
226
+ output_dir.mkdir(parents=True, exist_ok=True)
227
+ viewer_html = output_dir / viewer_name
228
+ scene_sog = viewer_html.with_suffix(".sog")
229
  viewer_html.write_text(
230
  '<html><head><link rel="stylesheet" href="./index.css"></head>'
231
  '<body><script type="module">'
232
  "const settingsUrl = './settings.json';"
233
  'const settings = {settings: fetch(settingsUrl).then(response => response.json())};'
234
+ f'const scene = fetch("{scene_sog.name}");'
235
  "import { main } from './index.js';"
236
  "</script></body></html>"
237
  )
238
+ scene_sog.write_bytes(b"scene")
239
  (output_dir / "index.css").write_text("canvas { display: block; }")
240
  (output_dir / "index.js").write_text("const main = true;")
241
  (output_dir / "settings.json").write_text('{"camera":{"fov":60}}')
 
243
 
244
 
245
  def test_build_standalone_viewer_embeds_unbundled_assets(tmp_path) -> None:
246
+ request_dir = tmp_path / "request"
247
+ viewer_html = _write_unbundled_viewer(request_dir)
248
+ shared_dir = install_shared_viewer_assets(viewer_html)
249
+ standalone_html = request_dir / "scene.html"
250
 
251
+ build_standalone_viewer(viewer_html, shared_dir, standalone_html)
252
  source = standalone_html.read_text()
253
 
254
  assert "canvas { display: block; }" in source
 
257
  assert "data:application/octet-stream;base64,c2NlbmU=" in source
258
 
259
 
260
+ def test_prepare_viewer_template_builds_assets_once(tmp_path, monkeypatch) -> None:
261
+ captured = {}
262
+
263
+ def fake_save_ply(*, path, **_) -> None:
264
+ path.write_bytes(b"ply")
265
+
266
+ def fake_run(command, **_) -> None:
267
+ captured["command"] = command
268
+ output_html = Path(command[-1])
269
+ _write_unbundled_viewer(output_html.parent, output_html.name)
270
+
271
+ monkeypatch.setattr(hf_runtime, "save_ply", fake_save_ply)
272
+ monkeypatch.setattr(hf_runtime.subprocess, "run", fake_run)
273
+ monkeypatch.setattr(hf_runtime, "patch_supersplat_html_auto_rotate", lambda _: None)
274
+ monkeypatch.setattr(
275
+ hf_runtime,
276
+ "patch_supersplat_html_viewer_bridge",
277
+ lambda *_, **__: None,
278
+ )
279
+
280
+ template = prepare_viewer_template(
281
+ output_root=tmp_path,
282
+ command_prefix=["splat-transform"],
283
+ )
284
+
285
+ assert "--unbundled" in captured["command"]
286
+ assert template.viewer_html.name == "template.html"
287
+ assert template.viewer_assets_dir.parent == tmp_path / "_viewer_assets"
288
+ assert "../_viewer_assets/" in template.viewer_html.read_text()
289
+
290
+
291
  def test_install_shared_viewer_assets_uses_content_hash(tmp_path) -> None:
292
  request_dir = tmp_path / "request"
293
  request_dir.mkdir()
 
302
  assert f"../_viewer_assets/{shared_dir.name}/index.js" in source
303
 
304
 
305
+ def test_export_gaussian_artifact_builds_filtered_downloads(
306
  tmp_path, monkeypatch
307
  ) -> None:
308
  artifact_path = tmp_path / "gaussians.pt"
 
316
  artifact_path,
317
  )
318
  captured = {}
319
+ template_html = _write_unbundled_viewer(
320
+ tmp_path / "_viewer_template",
321
+ "template.html",
322
+ )
323
+ viewer_template = ViewerTemplate(
324
+ viewer_html=template_html,
325
+ viewer_assets_dir=install_shared_viewer_assets(template_html),
326
+ )
327
 
328
  def fake_save_ply(*, gaussians, path, **_) -> None:
329
  captured["gaussians"] = gaussians
 
331
 
332
  def fake_run(command, **_) -> None:
333
  captured["command"] = command
334
+ Path(command[-1]).write_bytes(b"scene")
335
 
336
  monkeypatch.setattr(hf_runtime, "save_ply", fake_save_ply)
337
  monkeypatch.setattr(hf_runtime.subprocess, "run", fake_run)
 
 
 
 
 
 
 
338
  exported = hf_runtime.export_gaussian_artifact(
339
  artifact_path=artifact_path,
340
  output_dir=tmp_path / "output",
341
+ viewer_template=viewer_template,
342
  command_prefix=["splat-transform"],
343
  )
344
 
 
346
  assert actual.mean_vectors.shape == expected.mean_vectors.shape
347
  for expected_tensor, actual_tensor in zip(expected, actual):
348
  assert torch.equal(expected_tensor, actual_tensor)
349
+ assert "--unbundled" not in captured["command"]
350
+ assert captured["command"][-1] == str(exported.scene_sog)
351
+ assert exported.scene_sog.read_bytes() == b"scene"
352
  assert exported.viewer_html.is_file()
353
+ assert 'fetch("viewer.sog")' in exported.viewer_html.read_text()
354
+ assert not (exported.viewer_html.parent / "index.js").exists()
355
  assert exported.standalone_html.is_file()
356
+ assert "data:application/octet-stream;base64,c2NlbmU=" in exported.standalone_html.read_text()
357
 
358
 
359
+ def test_export_ply_result_exposes_ply_before_viewer(tmp_path, monkeypatch) -> None:
360
  internal_artifact = tmp_path / "gaussians.pt"
361
  scene_ply = tmp_path / "scene.ply"
362
+ for path in (internal_artifact, scene_ply):
 
 
363
  path.write_bytes(b"test")
364
+ monkeypatch.setattr(
365
+ hf_ui,
366
+ "export_filtered_gaussian_ply",
367
+ lambda **_: scene_ply,
368
+ )
369
+
370
+ result = hf_ui.export_ply_result(str(internal_artifact))
371
+
372
+ assert not internal_artifact.exists()
373
+ assert scene_ply.exists()
374
+ assert "PLY ready" in result[0]
375
+ assert result[1]["label"] == "Download PLY - Ready"
376
+ assert result[1]["value"] == str(scene_ply)
377
+ assert result[1]["interactive"] is True
378
+ assert result[1]["elem_classes"] == ["artifact-button", "artifact-ready"]
379
+ assert result[2] == str(scene_ply)
380
 
381
+
382
+ def test_export_viewer_result_starts_iframe_before_html(tmp_path, monkeypatch) -> None:
383
+ scene_ply = tmp_path / "scene.ply"
384
+ scene_sog = tmp_path / "viewer.sog"
385
+ viewer_html = tmp_path / "viewer.html"
386
+ scene_sog.write_bytes(b"sog")
387
+ viewer_html.write_text("<html></html>")
388
+ template = ViewerTemplate(
389
+ viewer_html=tmp_path / "template.html",
390
+ viewer_assets_dir=tmp_path / "assets",
391
+ )
392
+ monkeypatch.setattr(hf_ui, "_viewer_template", template)
393
  monkeypatch.setattr(
394
  hf_ui,
395
+ "export_browser_viewer",
396
+ lambda **_: BrowserViewerArtifacts(
397
+ scene_sog=scene_sog,
398
  viewer_html=viewer_html,
 
399
  ),
400
  )
401
 
402
+ result = hf_ui.export_viewer_result(str(scene_ply))
403
 
404
+ assert "Initializing 3D viewer" in result[0]
405
+ assert result[1] == str(viewer_html)
406
+
407
+
408
+ def test_export_html_result_returns_native_download(tmp_path, monkeypatch) -> None:
409
+ viewer_html = tmp_path / "viewer.html"
410
+ standalone_html = tmp_path / "scene.html"
411
+ standalone_html.write_text("<html></html>")
412
+ template = ViewerTemplate(
413
+ viewer_html=tmp_path / "template.html",
414
+ viewer_assets_dir=tmp_path / "assets",
415
+ )
416
+ monkeypatch.setattr(hf_ui, "_viewer_template", template)
417
+ monkeypatch.setattr(
418
+ hf_ui,
419
+ "export_standalone_viewer",
420
+ lambda **_: standalone_html,
421
+ )
422
+
423
+ result = hf_ui.export_html_result(str(viewer_html))
424
+
425
+ assert result["label"] == "Download HTML viewer - Ready"
426
+ assert result["value"] == str(standalone_html)
427
+ assert result["interactive"] is True
428
+ assert result["elem_classes"] == ["artifact-button", "artifact-ready"]
429
+
430
+
431
+ def test_begin_reconstruction_resets_download_states() -> None:
432
+ result = hf_ui.begin_reconstruction()
433
+
434
+ assert "Reconstructing scene" in result[0]
435
+ assert result[1]["label"] == "Preparing PLY..."
436
+ assert result[1]["value"] is None
437
+ assert result[1]["interactive"] is False
438
+ assert result[2] is None
439
+ assert result[3]["label"] == "Preparing HTML viewer..."
440
+ assert result[3]["value"] is None
441
+ assert result[3]["interactive"] is False
442
+ assert result[4] is None
443
+
444
+
445
+ def test_demo_uses_native_staged_downloads(tmp_path, monkeypatch) -> None:
446
+ template = ViewerTemplate(
447
+ viewer_html=tmp_path / "template.html",
448
+ viewer_assets_dir=tmp_path / "assets",
449
+ )
450
+ monkeypatch.setattr(hf_ui, "prepare_viewer_template", lambda _: template)
451
+ monkeypatch.setattr(hf_ui, "configure_runtime", lambda *_: None)
452
+
453
+ demo = hf_ui.create_demo(object())
454
+ config = demo.get_config_file()
455
+ download_labels = {
456
+ component["props"].get("label"): component
457
+ for component in config["components"]
458
+ if component["type"] == "downloadbutton"
459
+ }
460
+ function_names = [
461
+ block.fn.__name__
462
+ for block in demo.fns.values()
463
+ if block.fn is not None
464
+ ]
465
+
466
+ assert "Download PLY" in download_labels
467
+ assert "Download HTML viewer" in download_labels
468
+ assert function_names.index("export_ply_result") < function_names.index("export_viewer_result")
469
+ assert function_names.index("export_viewer_result") < function_names.index("export_html_result")
470
 
471
 
472
  def test_app_imports_spaces_before_model_modules() -> None: