Asnly commited on
Commit
15cff7c
·
verified ·
1 Parent(s): 0d2ccd6

Deploy Space app files

Browse files
Files changed (2) hide show
  1. app.py +186 -35
  2. utils.py +3 -3
app.py CHANGED
@@ -36,6 +36,14 @@ DATASET_REPO = os.environ.get("DATASET_REPO", "3DReflecNet/3DReflecNet")
36
  HF_TOKEN = os.environ.get("HF_TOKEN", None)
37
  MAX_RESULTS = 300
38
  BOOL_COLUMNS = ["hasGlass", "isGenerated", "transparent", "near_light"]
 
 
 
 
 
 
 
 
39
 
40
  _GLB_CACHE_DIR = Path(tempfile.mkdtemp(prefix="glb_cache_"))
41
  atexit.register(shutil.rmtree, str(_GLB_CACHE_DIR), True)
@@ -53,6 +61,10 @@ def load_preview_dataframe() -> pd.DataFrame:
53
  "main_category", "sub_category", "model_name",
54
  "material_name", "env_name", "glb_path",
55
  "hasGlass", "isGenerated", "transparent", "near_light",
 
 
 
 
56
  ]
57
  ds = load_dataset(
58
  DATASET_REPO,
@@ -68,10 +80,13 @@ def load_preview_dataframe() -> pd.DataFrame:
68
  [
69
  "instance_id", "split", "main_category", "sub_category",
70
  "model_name", "material_name", "env_name", "glb_path",
 
71
  ],
72
  "preview parquet",
73
  )
74
  require_bool_columns(df, BOOL_COLUMNS, "preview parquet")
 
 
75
  if df["frame_id"].isna().any() or not pd.api.types.is_integer_dtype(df["frame_id"]):
76
  raise TypeError(f"Expected integer dtype for column 'frame_id' in preview parquet, got {df['frame_id'].dtype}.")
77
  for col in ["rgb", "mask", "depth_preview", "normal_preview"]:
@@ -93,7 +108,7 @@ def build_preview_instance_dataframe(preview_df: pd.DataFrame) -> pd.DataFrame:
93
  instance_cols = [
94
  "instance_id", "main_category", "sub_category", "model_name",
95
  "material_name", "env_name", "hasGlass", "isGenerated",
96
- "transparent", "near_light", "glb_path",
97
  ]
98
  require_columns(preview_df, instance_cols, "preview parquet")
99
 
@@ -113,10 +128,15 @@ def build_preview_instance_dataframe(preview_df: pd.DataFrame) -> pd.DataFrame:
113
  [
114
  "instance_id", "main_category", "sub_category",
115
  "model_name", "material_name", "env_name", "glb_path",
 
116
  ],
117
  "preview instance dataframe",
118
  )
119
  require_bool_columns(df, BOOL_COLUMNS, "preview instance dataframe")
 
 
 
 
120
  if df["glb_path"].map(lambda value: not value.strip()).any():
121
  raise ValueError("Preview instance dataframe contains empty GLB paths.")
122
  return df
@@ -144,6 +164,37 @@ def instance_caption(row: dict[str, Any]) -> str:
144
  return f"{row['model_name']} | {row['material_name']} | {row['env_name']}"
145
 
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def build_instance_gallery_items(
148
  rows: list[dict[str, Any]],
149
  preview_df: pd.DataFrame,
@@ -218,60 +269,139 @@ def download_glb(glb_path: str) -> str:
218
  # ---------------------------------------------------------------------------
219
 
220
  def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
221
- model_name_choices = get_distinct_text_choices(instance_df, "model_name")
222
  material_name_choices = get_distinct_text_choices(instance_df, "material_name")
223
  env_name_choices = get_distinct_text_choices(instance_df, "env_name")
224
 
225
- def filtered_instance_rows(
226
- model_name: str,
227
- material_name: str,
228
- env_name: str,
229
- has_glass: str,
230
- is_generated: str,
231
- transparent: str,
232
- near_light: str,
233
- ) -> tuple[pd.DataFrame, list[dict[str, Any]]]:
234
- filtered = filter_dataframe_advanced(
235
  instance_df,
236
- model_name=model_name,
237
- material_name=material_name,
238
- env_name=env_name,
239
- has_glass=has_glass,
240
- is_generated=is_generated,
241
- transparent=transparent,
242
- near_light=near_light,
243
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  shown = filtered.head(MAX_RESULTS).copy()
245
  rows = shown.to_dict(orient="records")
246
  return filtered, rows
247
 
248
  def filter_gallery(
249
- model_name: str,
250
  material_name: str,
251
  env_name: str,
252
  has_glass: str,
253
  is_generated: str,
254
  transparent: str,
255
  near_light: str,
 
256
  ):
257
- filtered, rows = filtered_instance_rows(
258
- model_name=model_name,
259
- material_name=material_name,
260
- env_name=env_name,
261
- has_glass=has_glass,
262
- is_generated=is_generated,
263
- transparent=transparent,
264
- near_light=near_light,
 
 
 
265
  )
 
266
  summary = f"Matched **{len(filtered)}** preview instances, showing **{len(rows)}**."
267
  gallery_items = build_instance_gallery_items(rows, preview_df)
268
  slider_empty = gr.update(minimum=1, maximum=1, step=1, value=1, interactive=False)
269
- return summary, gallery_items, rows, {}, None, None, None, None, None, slider_empty, []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  def on_instance_select(rows: list[dict[str, Any]], evt: gr.SelectData):
272
  if not rows:
273
  slider_empty = gr.update(minimum=1, maximum=1, step=1, value=1, interactive=False)
274
- return {}, None, None, None, None, None, slider_empty, []
275
  idx = evt.index[0] if isinstance(evt.index, tuple) else evt.index
276
  if not isinstance(idx, int) or idx < 0 or idx >= len(rows):
277
  raise IndexError(f"Selected gallery index is out of range: {evt.index!r}")
@@ -289,7 +419,14 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
289
  value=1,
290
  interactive=True,
291
  )
292
- return row, download_glb(row["glb_path"]), *render_frame_images(frame_items, 1), slider_ready, frame_items
 
 
 
 
 
 
 
293
 
294
  def on_frame_change(frame_idx: float, frame_items: list[dict[str, Any]]):
295
  return render_frame_images(frame_items, frame_idx)
@@ -305,7 +442,7 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
305
  )
306
 
307
  with gr.Row():
308
- model_name = gr.Dropdown(label="model_name", choices=model_name_choices, value=FILTER_ALL)
309
  material_name = gr.Dropdown(label="material_name", choices=material_name_choices, value=FILTER_ALL)
310
  env_name = gr.Dropdown(label="env_name", choices=env_name_choices, value=FILTER_ALL)
311
  with gr.Row():
@@ -325,6 +462,7 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
325
 
326
  with gr.Row():
327
  instance_meta = gr.JSON(label="Instance Metadata")
 
328
  model_viewer = gr.Model3D(
329
  label="3D Preview (GLB)",
330
  clear_color=(0.35, 0.35, 0.38, 1.0),
@@ -349,7 +487,7 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
349
  frame_state = gr.State([])
350
 
351
  filter_inputs = [
352
- model_name,
353
  material_name,
354
  env_name,
355
  has_glass,
@@ -358,10 +496,14 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
358
  near_light,
359
  ]
360
  filter_outputs = [
 
 
 
361
  summary,
362
  instance_gallery,
363
  instance_state,
364
  instance_meta,
 
365
  model_viewer,
366
  rgb_image,
367
  mask_image,
@@ -370,9 +512,17 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
370
  frame_slider,
371
  frame_state,
372
  ]
373
- for filter_component in filter_inputs:
 
 
 
 
 
 
 
 
374
  filter_component.change(
375
- fn=filter_gallery,
376
  inputs=filter_inputs,
377
  outputs=filter_outputs,
378
  )
@@ -381,6 +531,7 @@ def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
381
  inputs=[instance_state],
382
  outputs=[
383
  instance_meta,
 
384
  model_viewer,
385
  rgb_image,
386
  mask_image,
 
36
  HF_TOKEN = os.environ.get("HF_TOKEN", None)
37
  MAX_RESULTS = 300
38
  BOOL_COLUMNS = ["hasGlass", "isGenerated", "transparent", "near_light"]
39
+ TAG_TEXT_COLUMNS = [
40
+ "indoor_outdoor", "glossiness", "reflectivity", "texture_scale",
41
+ "anisotropy", "metallic_hint", "light_type", "light_intensity",
42
+ "key_light_direction", "shadow_hardness", "description",
43
+ ]
44
+ TAG_NUMERIC_COLUMNS = ["roughness"]
45
+ TAG_COLUMNS = TAG_TEXT_COLUMNS + TAG_NUMERIC_COLUMNS
46
+ TEXT_FILTER_COLUMNS = ["main_category", "material_name", "env_name"]
47
 
48
  _GLB_CACHE_DIR = Path(tempfile.mkdtemp(prefix="glb_cache_"))
49
  atexit.register(shutil.rmtree, str(_GLB_CACHE_DIR), True)
 
61
  "main_category", "sub_category", "model_name",
62
  "material_name", "env_name", "glb_path",
63
  "hasGlass", "isGenerated", "transparent", "near_light",
64
+ "indoor_outdoor", "glossiness", "roughness", "reflectivity",
65
+ "texture_scale", "anisotropy", "metallic_hint", "light_type",
66
+ "light_intensity", "key_light_direction", "shadow_hardness",
67
+ "description",
68
  ]
69
  ds = load_dataset(
70
  DATASET_REPO,
 
80
  [
81
  "instance_id", "split", "main_category", "sub_category",
82
  "model_name", "material_name", "env_name", "glb_path",
83
+ *TAG_TEXT_COLUMNS,
84
  ],
85
  "preview parquet",
86
  )
87
  require_bool_columns(df, BOOL_COLUMNS, "preview parquet")
88
+ if df["roughness"].isna().any() or not pd.api.types.is_numeric_dtype(df["roughness"]):
89
+ raise TypeError(f"Expected numeric dtype for column 'roughness' in preview parquet, got {df['roughness'].dtype}.")
90
  if df["frame_id"].isna().any() or not pd.api.types.is_integer_dtype(df["frame_id"]):
91
  raise TypeError(f"Expected integer dtype for column 'frame_id' in preview parquet, got {df['frame_id'].dtype}.")
92
  for col in ["rgb", "mask", "depth_preview", "normal_preview"]:
 
108
  instance_cols = [
109
  "instance_id", "main_category", "sub_category", "model_name",
110
  "material_name", "env_name", "hasGlass", "isGenerated",
111
+ "transparent", "near_light", "glb_path", *TAG_COLUMNS,
112
  ]
113
  require_columns(preview_df, instance_cols, "preview parquet")
114
 
 
128
  [
129
  "instance_id", "main_category", "sub_category",
130
  "model_name", "material_name", "env_name", "glb_path",
131
+ *TAG_TEXT_COLUMNS,
132
  ],
133
  "preview instance dataframe",
134
  )
135
  require_bool_columns(df, BOOL_COLUMNS, "preview instance dataframe")
136
+ if df["roughness"].isna().any() or not pd.api.types.is_numeric_dtype(df["roughness"]):
137
+ raise TypeError(
138
+ f"Expected numeric dtype for column 'roughness' in preview instance dataframe, got {df['roughness'].dtype}."
139
+ )
140
  if df["glb_path"].map(lambda value: not value.strip()).any():
141
  raise ValueError("Preview instance dataframe contains empty GLB paths.")
142
  return df
 
164
  return f"{row['model_name']} | {row['material_name']} | {row['env_name']}"
165
 
166
 
167
+ def build_llm_tags(row: dict[str, Any]) -> dict[str, Any]:
168
+ """Reconstruct the tags.json shape from preview parquet columns."""
169
+ return {
170
+ "instance_id": row["instance_id"],
171
+ "category": {
172
+ "main": row["main_category"],
173
+ "sub": row["sub_category"],
174
+ },
175
+ "material_properties": {
176
+ "glossiness": row["glossiness"],
177
+ "roughness": float(row["roughness"]),
178
+ "reflectivity": row["reflectivity"],
179
+ "texture_scale": row["texture_scale"],
180
+ "anisotropy": row["anisotropy"],
181
+ "metallic_hint": row["metallic_hint"],
182
+ },
183
+ "environment": {
184
+ "indoor_outdoor": row["indoor_outdoor"],
185
+ "light_type": row["light_type"],
186
+ "light_intensity": row["light_intensity"],
187
+ "key_light_direction": row["key_light_direction"],
188
+ "shadow_hardness": row["shadow_hardness"],
189
+ },
190
+ "description": row["description"],
191
+ }
192
+
193
+
194
+ def build_instance_metadata(row: dict[str, Any]) -> dict[str, Any]:
195
+ return {key: value for key, value in row.items() if key not in TAG_COLUMNS}
196
+
197
+
198
  def build_instance_gallery_items(
199
  rows: list[dict[str, Any]],
200
  preview_df: pd.DataFrame,
 
269
  # ---------------------------------------------------------------------------
270
 
271
  def build_app(instance_df: pd.DataFrame, preview_df: pd.DataFrame) -> gr.Blocks:
272
+ main_category_choices = get_distinct_text_choices(instance_df, "main_category")
273
  material_name_choices = get_distinct_text_choices(instance_df, "material_name")
274
  env_name_choices = get_distinct_text_choices(instance_df, "env_name")
275
 
276
+ def text_choices_for(df: pd.DataFrame, column: str) -> list[str]:
277
+ if df.empty:
278
+ return [FILTER_ALL]
279
+ return get_distinct_text_choices(df, column)
280
+
281
+ def filter_with_values(values: dict[str, str], skip_text_column: str | None = None) -> pd.DataFrame:
282
+ return filter_dataframe_advanced(
 
 
 
283
  instance_df,
284
+ main_category=FILTER_ALL if skip_text_column == "main_category" else values["main_category"],
285
+ material_name=FILTER_ALL if skip_text_column == "material_name" else values["material_name"],
286
+ env_name=FILTER_ALL if skip_text_column == "env_name" else values["env_name"],
287
+ has_glass=values["has_glass"],
288
+ is_generated=values["is_generated"],
289
+ transparent=values["transparent"],
290
+ near_light=values["near_light"],
291
  )
292
+
293
+ def normalize_filter_values(values: dict[str, str], changed_column: str | None) -> dict[str, str]:
294
+ normalized = values.copy()
295
+ for column in TEXT_FILTER_COLUMNS:
296
+ value = (normalized[column] or "").strip()
297
+ normalized[column] = value if value else FILTER_ALL
298
+ for column in ["has_glass", "is_generated", "transparent", "near_light"]:
299
+ value = (normalized[column] or "").strip()
300
+ normalized[column] = value if value else FILTER_ALL
301
+
302
+ protected = {changed_column} if changed_column in TEXT_FILTER_COLUMNS else set()
303
+ for _ in TEXT_FILTER_COLUMNS:
304
+ changed = False
305
+ for column in TEXT_FILTER_COLUMNS:
306
+ if column in protected:
307
+ continue
308
+ choices = text_choices_for(filter_with_values(normalized, skip_text_column=column), column)
309
+ if normalized[column] not in choices:
310
+ normalized[column] = FILTER_ALL
311
+ changed = True
312
+ if not changed:
313
+ break
314
+
315
+ for column in protected:
316
+ choices = text_choices_for(filter_with_values(normalized, skip_text_column=column), column)
317
+ if normalized[column] not in choices:
318
+ normalized[column] = FILTER_ALL
319
+ return normalized
320
+
321
+ def text_filter_updates(values: dict[str, str]) -> list[Any]:
322
+ return [
323
+ gr.update(
324
+ choices=text_choices_for(filter_with_values(values, skip_text_column=column), column),
325
+ value=values[column],
326
+ )
327
+ for column in TEXT_FILTER_COLUMNS
328
+ ]
329
+
330
+ def filtered_instance_rows(values: dict[str, str]) -> tuple[pd.DataFrame, list[dict[str, Any]]]:
331
+ filtered = filter_with_values(values)
332
  shown = filtered.head(MAX_RESULTS).copy()
333
  rows = shown.to_dict(orient="records")
334
  return filtered, rows
335
 
336
  def filter_gallery(
337
+ main_category: str,
338
  material_name: str,
339
  env_name: str,
340
  has_glass: str,
341
  is_generated: str,
342
  transparent: str,
343
  near_light: str,
344
+ changed_column: str | None = None,
345
  ):
346
+ values = normalize_filter_values(
347
+ {
348
+ "main_category": main_category,
349
+ "material_name": material_name,
350
+ "env_name": env_name,
351
+ "has_glass": has_glass,
352
+ "is_generated": is_generated,
353
+ "transparent": transparent,
354
+ "near_light": near_light,
355
+ },
356
+ changed_column=changed_column,
357
  )
358
+ filtered, rows = filtered_instance_rows(values)
359
  summary = f"Matched **{len(filtered)}** preview instances, showing **{len(rows)}**."
360
  gallery_items = build_instance_gallery_items(rows, preview_df)
361
  slider_empty = gr.update(minimum=1, maximum=1, step=1, value=1, interactive=False)
362
+ return [
363
+ *text_filter_updates(values),
364
+ summary,
365
+ gallery_items,
366
+ rows,
367
+ {},
368
+ {},
369
+ None,
370
+ None,
371
+ None,
372
+ None,
373
+ None,
374
+ slider_empty,
375
+ [],
376
+ ]
377
+
378
+ def make_filter_handler(changed_column: str):
379
+ def handler(
380
+ main_category: str,
381
+ material_name: str,
382
+ env_name: str,
383
+ has_glass: str,
384
+ is_generated: str,
385
+ transparent: str,
386
+ near_light: str,
387
+ ):
388
+ return filter_gallery(
389
+ main_category,
390
+ material_name,
391
+ env_name,
392
+ has_glass,
393
+ is_generated,
394
+ transparent,
395
+ near_light,
396
+ changed_column=changed_column,
397
+ )
398
+
399
+ return handler
400
 
401
  def on_instance_select(rows: list[dict[str, Any]], evt: gr.SelectData):
402
  if not rows:
403
  slider_empty = gr.update(minimum=1, maximum=1, step=1, value=1, interactive=False)
404
+ return {}, {}, None, None, None, None, None, slider_empty, []
405
  idx = evt.index[0] if isinstance(evt.index, tuple) else evt.index
406
  if not isinstance(idx, int) or idx < 0 or idx >= len(rows):
407
  raise IndexError(f"Selected gallery index is out of range: {evt.index!r}")
 
419
  value=1,
420
  interactive=True,
421
  )
422
+ return (
423
+ build_instance_metadata(row),
424
+ build_llm_tags(row),
425
+ download_glb(row["glb_path"]),
426
+ *render_frame_images(frame_items, 1),
427
+ slider_ready,
428
+ frame_items,
429
+ )
430
 
431
  def on_frame_change(frame_idx: float, frame_items: list[dict[str, Any]]):
432
  return render_frame_images(frame_items, frame_idx)
 
442
  )
443
 
444
  with gr.Row():
445
+ main_category = gr.Dropdown(label="main_category", choices=main_category_choices, value=FILTER_ALL)
446
  material_name = gr.Dropdown(label="material_name", choices=material_name_choices, value=FILTER_ALL)
447
  env_name = gr.Dropdown(label="env_name", choices=env_name_choices, value=FILTER_ALL)
448
  with gr.Row():
 
462
 
463
  with gr.Row():
464
  instance_meta = gr.JSON(label="Instance Metadata")
465
+ llm_tags = gr.JSON(label="LLM Tags")
466
  model_viewer = gr.Model3D(
467
  label="3D Preview (GLB)",
468
  clear_color=(0.35, 0.35, 0.38, 1.0),
 
487
  frame_state = gr.State([])
488
 
489
  filter_inputs = [
490
+ main_category,
491
  material_name,
492
  env_name,
493
  has_glass,
 
496
  near_light,
497
  ]
498
  filter_outputs = [
499
+ main_category,
500
+ material_name,
501
+ env_name,
502
  summary,
503
  instance_gallery,
504
  instance_state,
505
  instance_meta,
506
+ llm_tags,
507
  model_viewer,
508
  rgb_image,
509
  mask_image,
 
512
  frame_slider,
513
  frame_state,
514
  ]
515
+ for filter_component, changed_column in [
516
+ (main_category, "main_category"),
517
+ (material_name, "material_name"),
518
+ (env_name, "env_name"),
519
+ (has_glass, "has_glass"),
520
+ (is_generated, "is_generated"),
521
+ (transparent, "transparent"),
522
+ (near_light, "near_light"),
523
+ ]:
524
  filter_component.change(
525
+ fn=make_filter_handler(changed_column),
526
  inputs=filter_inputs,
527
  outputs=filter_outputs,
528
  )
 
531
  inputs=[instance_state],
532
  outputs=[
533
  instance_meta,
534
+ llm_tags,
535
  model_viewer,
536
  rgb_image,
537
  mask_image,
utils.py CHANGED
@@ -93,7 +93,7 @@ def _apply_text_equals(df: pd.DataFrame, column: str, selected_value: str, all_l
93
 
94
  def filter_dataframe_advanced(
95
  df: pd.DataFrame,
96
- model_name: str = FILTER_ALL,
97
  material_name: str = FILTER_ALL,
98
  env_name: str = FILTER_ALL,
99
  has_glass: str = FILTER_ALL,
@@ -101,9 +101,9 @@ def filter_dataframe_advanced(
101
  transparent: str = FILTER_ALL,
102
  near_light: str = FILTER_ALL,
103
  ) -> pd.DataFrame:
104
- """Filter by model/material/environment exact selection and four tri-state bool fields."""
105
  selected = df
106
- selected = _apply_text_equals(selected, "model_name", model_name)
107
  selected = _apply_text_equals(selected, "material_name", material_name)
108
  selected = _apply_text_equals(selected, "env_name", env_name)
109
  selected = apply_bool_filter(selected, "hasGlass", has_glass)
 
93
 
94
  def filter_dataframe_advanced(
95
  df: pd.DataFrame,
96
+ main_category: str = FILTER_ALL,
97
  material_name: str = FILTER_ALL,
98
  env_name: str = FILTER_ALL,
99
  has_glass: str = FILTER_ALL,
 
101
  transparent: str = FILTER_ALL,
102
  near_light: str = FILTER_ALL,
103
  ) -> pd.DataFrame:
104
+ """Filter by category/material/environment exact selection and four tri-state bool fields."""
105
  selected = df
106
+ selected = _apply_text_equals(selected, "main_category", main_category)
107
  selected = _apply_text_equals(selected, "material_name", material_name)
108
  selected = _apply_text_equals(selected, "env_name", env_name)
109
  selected = apply_bool_filter(selected, "hasGlass", has_glass)