mnm-matin commited on
Commit
fe0b8e5
·
verified ·
1 Parent(s): c89e8a4

Improve DeepFashion demo layout composition

Browse files
.hyperview/extensions/fashion-search-readout/panel.js CHANGED
@@ -348,7 +348,7 @@ function SearchResultComparison({ item, models, loadingKey, onSelectResult }) {
348
  React.createElement(
349
  "div",
350
  { style: { color: colors.mutedText, fontSize: 10, marginTop: 3 } },
351
- "Green card = exact product. Map context is optional and hidden by default.",
352
  ),
353
  ),
354
  models.map((model) =>
@@ -428,6 +428,173 @@ function ExactTargetActions({ item, models, loadingKey, onSelectQuery }) {
428
  );
429
  }
430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  export default function FashionSearchComparisonPanel() {
432
  const client = usePanelClient();
433
  const selection = usePanelSelection();
@@ -566,102 +733,113 @@ export default function FashionSearchComparisonPanel() {
566
  style: {
567
  height: "100%",
568
  overflow: "auto",
569
- padding: 12,
570
- display: "flex",
571
- flexDirection: "column",
572
- gap: 14,
573
  background: colors.panelBg,
574
  },
575
  },
576
  React.createElement(
577
- Section,
578
- { title: "Pick A Shopper Query" },
 
 
 
 
 
 
 
 
 
 
 
579
  React.createElement(
580
- TextBlock,
581
- null,
582
- "Choose a typed product search. The ranked cards are the demo; map context is secondary.",
583
- ),
584
- examples.length
585
- ? React.createElement(ExampleChooser, {
586
- examples,
587
- selectedId: selectedExample?.id,
588
- onSelect: setSelectedExampleId,
589
- })
590
- : React.createElement(
591
- "div",
592
- { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } },
593
- "Demo examples are not configured.",
 
 
 
 
594
  ),
595
- selectedExample && models.length
596
- ? React.createElement(SearchResultComparison, {
597
- item: selectedExample,
598
- models,
599
- loadingKey,
600
- onSelectResult: selectRankedResult,
601
- })
602
- : null,
603
- ),
604
- React.createElement(
605
- Section,
606
- { title: "Inspect Exact Target" },
607
- React.createElement(
608
- TextBlock,
609
- null,
610
- `Exact-target summary for: ${selectedExample?.targetTitle || "target product"}.`,
611
- ),
612
- selectedExample && models.length
613
- ? React.createElement(ExactTargetActions, {
614
- item: selectedExample,
615
- models,
616
- loadingKey,
617
- onSelectQuery: selectModelQuery,
618
- })
619
- : React.createElement(
620
- "div",
621
- { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } },
622
- "Demo examples are not configured.",
623
  ),
624
- ),
625
- warnings.length
626
- ? React.createElement(
627
- "div",
628
- {
629
- style: {
630
- border: `1px solid ${colors.warningBorder}`,
631
- borderRadius: 4,
632
- background: colors.warningBg,
633
- color: colors.warningText,
634
- padding: 8,
635
- fontSize: 11,
636
- lineHeight: 1.35,
637
- },
638
- },
639
- warnings[0],
640
- )
641
- : null,
642
- React.createElement(
643
- Section,
644
- { title: "Benchmark Details" },
645
- React.createElement(
646
- TextBlock,
647
- null,
648
- "Bottom reference only. The result strips above are the main demo; these numbers keep the probe traceable.",
649
- ),
650
- React.createElement(MetricGrid, { rows: typedMetricRows }),
651
- React.createElement(
652
- TextBlock,
653
- null,
654
- "Separate image-to-image check, shown only for context because it is not the typed shopper search task.",
 
 
 
 
 
 
 
655
  ),
656
- React.createElement(MetricGrid, { rows: imageMetricRows }),
657
  ),
658
- panelError
659
- ? React.createElement(
660
- "div",
661
- { style: { color: colors.error, fontSize: 11, lineHeight: 1.35 } },
662
- panelError,
663
- )
664
- : null,
665
  ),
666
  );
667
  }
 
348
  React.createElement(
349
  "div",
350
  { style: { color: colors.mutedText, fontSize: 10, marginTop: 3 } },
351
+ "Green card = exact product. Ranked cards are the proof.",
352
  ),
353
  ),
354
  models.map((model) =>
 
428
  );
429
  }
430
 
431
+ function RailBlock({ title, children }) {
432
+ return React.createElement(
433
+ "div",
434
+ {
435
+ style: {
436
+ border: `1px solid ${colors.border}`,
437
+ borderRadius: 6,
438
+ background: colors.cardBg,
439
+ padding: 10,
440
+ display: "flex",
441
+ flexDirection: "column",
442
+ gap: 7,
443
+ },
444
+ },
445
+ React.createElement(
446
+ "div",
447
+ { style: { color: colors.strongText, fontSize: 11, fontWeight: 700 } },
448
+ title,
449
+ ),
450
+ children,
451
+ );
452
+ }
453
+
454
+ function MiniStat({ label, value, accent = false }) {
455
+ return React.createElement(
456
+ "div",
457
+ {
458
+ style: {
459
+ border: `1px solid ${accent ? "#3f6f5b" : colors.border}`,
460
+ borderRadius: 5,
461
+ background: accent ? "rgba(134, 239, 172, 0.05)" : "#0f172a",
462
+ padding: 8,
463
+ },
464
+ },
465
+ React.createElement(
466
+ "div",
467
+ { style: { color: colors.mutedText, fontSize: 9, fontWeight: 700, textTransform: "uppercase" } },
468
+ label,
469
+ ),
470
+ React.createElement(
471
+ "div",
472
+ { style: { color: accent ? colors.success : colors.strongText, fontSize: 17, fontWeight: 800, marginTop: 3 } },
473
+ value,
474
+ ),
475
+ );
476
+ }
477
+
478
+ function BusinessRail({ item, metrics }) {
479
+ if (!item) return null;
480
+ const clipSummary = getSummary(item, "clip");
481
+ const candidateSummary = getSummary(item, "candidate");
482
+
483
+ return React.createElement(
484
+ "aside",
485
+ {
486
+ style: {
487
+ flex: "0 1 280px",
488
+ minWidth: 240,
489
+ maxWidth: 300,
490
+ display: "flex",
491
+ flexDirection: "column",
492
+ gap: 10,
493
+ },
494
+ },
495
+ React.createElement(
496
+ RailBlock,
497
+ { title: "Buyer Readout" },
498
+ React.createElement(
499
+ "div",
500
+ { style: { color: colors.bodyText, fontSize: 11, lineHeight: 1.4 } },
501
+ item.targetTitle,
502
+ ),
503
+ React.createElement(
504
+ "div",
505
+ { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 7 } },
506
+ React.createElement(MiniStat, {
507
+ label: "Hyper3 Target",
508
+ value: rankLabel(candidateSummary.rank),
509
+ accent: true,
510
+ }),
511
+ React.createElement(MiniStat, {
512
+ label: "CLIP Target",
513
+ value: rankLabel(clipSummary.rank),
514
+ }),
515
+ ),
516
+ React.createElement(
517
+ "div",
518
+ { style: { color: colors.mutedText, fontSize: 10, lineHeight: 1.4 } },
519
+ candidateSummary.text || "Exact target appears earlier in the ranked product results.",
520
+ ),
521
+ ),
522
+ React.createElement(
523
+ RailBlock,
524
+ { title: "Why This Query Matters" },
525
+ React.createElement(
526
+ "div",
527
+ { style: { color: colors.bodyText, fontSize: 11, lineHeight: 1.45 } },
528
+ "The search combines color, fit, construction, and item type. That is the kind of typed product search where returning the exact SKU early matters.",
529
+ ),
530
+ ),
531
+ React.createElement(
532
+ RailBlock,
533
+ { title: "Probe Signal" },
534
+ React.createElement(
535
+ "div",
536
+ { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 7 } },
537
+ React.createElement(MiniStat, {
538
+ label: "Hit@10",
539
+ value: pct(metrics.typedHit10Hyper3),
540
+ accent: true,
541
+ }),
542
+ React.createElement(MiniStat, {
543
+ label: "CLIP",
544
+ value: pct(metrics.typedHit10Clip),
545
+ }),
546
+ ),
547
+ React.createElement(
548
+ "div",
549
+ { style: { color: colors.mutedText, fontSize: 10, lineHeight: 1.4 } },
550
+ "Benchmark details stay below the demo so the ranked cards remain the main evidence.",
551
+ ),
552
+ ),
553
+ );
554
+ }
555
+
556
+ function BenchmarkDetails({ typedMetricRows, imageMetricRows }) {
557
+ return React.createElement(
558
+ "details",
559
+ {
560
+ style: {
561
+ border: `1px solid ${colors.border}`,
562
+ borderRadius: 6,
563
+ background: colors.cardBg,
564
+ padding: 9,
565
+ },
566
+ },
567
+ React.createElement(
568
+ "summary",
569
+ {
570
+ style: {
571
+ color: colors.strongText,
572
+ fontSize: 12,
573
+ fontWeight: 700,
574
+ cursor: "pointer",
575
+ },
576
+ },
577
+ "Benchmark Details",
578
+ ),
579
+ React.createElement(
580
+ "div",
581
+ { style: { display: "flex", flexDirection: "column", gap: 8, marginTop: 9 } },
582
+ React.createElement(
583
+ TextBlock,
584
+ null,
585
+ "Bottom reference only. The result strips above are the main demo; these numbers keep the probe traceable.",
586
+ ),
587
+ React.createElement(MetricGrid, { rows: typedMetricRows }),
588
+ React.createElement(
589
+ TextBlock,
590
+ null,
591
+ "Separate image-to-image check, shown only for context because it is not the typed shopper search task.",
592
+ ),
593
+ React.createElement(MetricGrid, { rows: imageMetricRows }),
594
+ ),
595
+ );
596
+ }
597
+
598
  export default function FashionSearchComparisonPanel() {
599
  const client = usePanelClient();
600
  const selection = usePanelSelection();
 
733
  style: {
734
  height: "100%",
735
  overflow: "auto",
736
+ padding: 14,
 
 
 
737
  background: colors.panelBg,
738
  },
739
  },
740
  React.createElement(
741
+ "div",
742
+ {
743
+ style: {
744
+ width: "100%",
745
+ maxWidth: 1130,
746
+ margin: "0 auto",
747
+ display: "flex",
748
+ gap: 12,
749
+ alignItems: "flex-start",
750
+ justifyContent: "center",
751
+ flexWrap: "wrap",
752
+ },
753
+ },
754
  React.createElement(
755
+ "main",
756
+ {
757
+ style: {
758
+ flex: "1 1 680px",
759
+ maxWidth: 820,
760
+ minWidth: 0,
761
+ display: "flex",
762
+ flexDirection: "column",
763
+ gap: 14,
764
+ },
765
+ },
766
+ React.createElement(
767
+ Section,
768
+ { title: "Pick A Shopper Query" },
769
+ React.createElement(
770
+ TextBlock,
771
+ null,
772
+ "Pick a typed product search and compare where each model ranks the exact item.",
773
  ),
774
+ examples.length
775
+ ? React.createElement(ExampleChooser, {
776
+ examples,
777
+ selectedId: selectedExample?.id,
778
+ onSelect: setSelectedExampleId,
779
+ })
780
+ : React.createElement(
781
+ "div",
782
+ { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } },
783
+ "Demo examples are not configured.",
784
+ ),
785
+ selectedExample && models.length
786
+ ? React.createElement(SearchResultComparison, {
787
+ item: selectedExample,
788
+ models,
789
+ loadingKey,
790
+ onSelectResult: selectRankedResult,
791
+ })
792
+ : null,
793
+ ),
794
+ React.createElement(
795
+ Section,
796
+ { title: "Inspect Exact Target" },
797
+ React.createElement(
798
+ TextBlock,
799
+ null,
800
+ `Exact-target summary for: ${selectedExample?.targetTitle || "target product"}.`,
 
801
  ),
802
+ selectedExample && models.length
803
+ ? React.createElement(ExactTargetActions, {
804
+ item: selectedExample,
805
+ models,
806
+ loadingKey,
807
+ onSelectQuery: selectModelQuery,
808
+ })
809
+ : React.createElement(
810
+ "div",
811
+ { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } },
812
+ "Demo examples are not configured.",
813
+ ),
814
+ ),
815
+ warnings.length
816
+ ? React.createElement(
817
+ "div",
818
+ {
819
+ style: {
820
+ border: `1px solid ${colors.warningBorder}`,
821
+ borderRadius: 4,
822
+ background: colors.warningBg,
823
+ color: colors.warningText,
824
+ padding: 8,
825
+ fontSize: 11,
826
+ lineHeight: 1.35,
827
+ },
828
+ },
829
+ warnings[0],
830
+ )
831
+ : null,
832
+ React.createElement(BenchmarkDetails, { typedMetricRows, imageMetricRows }),
833
+ panelError
834
+ ? React.createElement(
835
+ "div",
836
+ { style: { color: colors.error, fontSize: 11, lineHeight: 1.35 } },
837
+ panelError,
838
+ )
839
+ : null,
840
  ),
841
+ React.createElement(BusinessRail, { item: selectedExample, metrics }),
842
  ),
 
 
 
 
 
 
 
843
  ),
844
  );
845
  }
Dockerfile CHANGED
@@ -42,7 +42,7 @@ COPY --chown=user . .
42
 
43
  ENV HYPERVIEW_HOST=0.0.0.0 \
44
  HYPERVIEW_PORT=7860 \
45
- HYPERVIEW_WORKSPACE_ID=fashion-deepfashion-text-search \
46
  HYPERVIEW_DATASETS_DIR=/home/user/app/demo_data/datasets \
47
  HYPERVIEW_MEDIA_DIR=/home/user/app/demo_data/media \
48
  DEEPFASHION_SAMPLES_PER_CATEGORY=10 \
 
42
 
43
  ENV HYPERVIEW_HOST=0.0.0.0 \
44
  HYPERVIEW_PORT=7860 \
45
+ HYPERVIEW_WORKSPACE_ID=fashion-retail-search-demo \
46
  HYPERVIEW_DATASETS_DIR=/home/user/app/demo_data/datasets \
47
  HYPERVIEW_MEDIA_DIR=/home/user/app/demo_data/media \
48
  DEEPFASHION_SAMPLES_PER_CATEGORY=10 \
demo.py CHANGED
@@ -15,11 +15,10 @@ from PIL import Image, ImageOps
15
 
16
  import hyperview as hv
17
 
18
-
19
  SPACE_DIR = Path(__file__).resolve().parent
20
  SPACE_HOST = os.environ.get("HYPERVIEW_HOST", "127.0.0.1")
21
  SPACE_PORT = int(os.environ.get("HYPERVIEW_PORT", "6262"))
22
- WORKSPACE_ID = os.environ.get("HYPERVIEW_WORKSPACE_ID", "fashion-deepfashion-text-search")
23
  DATASET_NAME = os.environ.get("HYPERVIEW_DATASET_NAME", "deepfashion_text_search_clip_hyper3clip")
24
  EXTENSION_DIR = SPACE_DIR / ".hyperview" / "extensions" / "fashion-search-readout"
25
 
@@ -222,8 +221,7 @@ def select_deepfashion_records() -> list[dict[str, Any]]:
222
  def add_deepfashion_samples(dataset: hv.Dataset) -> None:
223
  existing_ids = {sample.id for sample in dataset.samples}
224
  media_dir = media_root()
225
- added = 0
226
- updated = 0
227
  records = select_deepfashion_records()
228
 
229
  for record in records:
@@ -245,13 +243,27 @@ def add_deepfashion_samples(dataset: hv.Dataset) -> None:
245
  "source_dataset": HF_DATASET,
246
  "split": HF_SPLIT,
247
  }
248
- existed = sample_id in existing_ids
249
- dataset.add_image(str(destination), label=category, metadata=metadata, sample_id=sample_id)
250
- if existed:
251
- updated += 1
252
- else:
253
- existing_ids.add(sample_id)
254
- added += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  print(f"Prepared DeepFashion samples ({added} added, {updated} updated).", flush=True)
256
 
257
 
@@ -413,7 +425,9 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
413
  "dockview": {
414
  "closeSamplesPanel": True,
415
  "hideExplorerPanel": True,
 
416
  "minimumWidth": 620,
 
417
  },
418
  "models": model_panel_props(layouts),
419
  "examples": build_examples(dataset),
@@ -442,12 +456,13 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
442
  clip_spec = MODEL_SPECS[0]
443
  candidate_spec = MODEL_SPECS[1]
444
  map_dockview = {
445
- "initialWidth": int(os.environ.get("FASHION_MAP_CONTEXT_WIDTH", "340")),
446
  "minimumWidth": 260,
 
447
  }
448
  clip_map = hv.ui.Scatter(
449
  id="fashion-map-clip",
450
- title="Context Map: CLIP",
451
  layout_key=layouts["clip"],
452
  position="center",
453
  reference_panel_id="fashion-text-search-readout",
@@ -458,7 +473,7 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
458
  )
459
  candidate_map = hv.ui.Scatter(
460
  id="fashion-map-hyper3",
461
- title="Context Map: Hyper3",
462
  layout_key=layouts["candidate"],
463
  position="center",
464
  reference_panel_id="fashion-map-clip",
@@ -481,11 +496,12 @@ def launch_demo(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.Session:
481
  )
482
  print("Installing DeepFashion demo extension...", flush=True)
483
  session.ui.add_extension(EXTENSION_DIR, workspace_id=WORKSPACE_ID)
484
- print("Applying DeepFashion side-by-side demo view...", flush=True)
485
  session.ui.apply_view(build_demo_view(dataset, layouts), workspace_id=WORKSPACE_ID)
486
  session.ui.set_active_layout(None, workspace_id=WORKSPACE_ID)
487
  session.ui.set_selection([], workspace_id=WORKSPACE_ID)
488
  print(f"\nHyperView DeepFashion text-search demo is running at {session.url}", flush=True)
 
489
  return session
490
 
491
 
 
15
 
16
  import hyperview as hv
17
 
 
18
  SPACE_DIR = Path(__file__).resolve().parent
19
  SPACE_HOST = os.environ.get("HYPERVIEW_HOST", "127.0.0.1")
20
  SPACE_PORT = int(os.environ.get("HYPERVIEW_PORT", "6262"))
21
+ WORKSPACE_ID = os.environ.get("HYPERVIEW_WORKSPACE_ID", "fashion-retail-search-demo")
22
  DATASET_NAME = os.environ.get("HYPERVIEW_DATASET_NAME", "deepfashion_text_search_clip_hyper3clip")
23
  EXTENSION_DIR = SPACE_DIR / ".hyperview" / "extensions" / "fashion-search-readout"
24
 
 
221
  def add_deepfashion_samples(dataset: hv.Dataset) -> None:
222
  existing_ids = {sample.id for sample in dataset.samples}
223
  media_dir = media_root()
224
+ image_rows: list[dict[str, Any]] = []
 
225
  records = select_deepfashion_records()
226
 
227
  for record in records:
 
243
  "source_dataset": HF_DATASET,
244
  "split": HF_SPLIT,
245
  }
246
+ image_rows.append(
247
+ {
248
+ "filepath": str(destination),
249
+ "label": category,
250
+ "metadata": metadata,
251
+ "sample_id": sample_id,
252
+ }
253
+ )
254
+
255
+ upserted, skipped_existing = dataset.add_images(
256
+ image_rows,
257
+ skip_existing=not FORCE_SAMPLE_REFRESH,
258
+ )
259
+ updated = (
260
+ sum(1 for row in image_rows if str(row["sample_id"]) in existing_ids)
261
+ if FORCE_SAMPLE_REFRESH
262
+ else 0
263
+ )
264
+ added = upserted - updated
265
+ if skipped_existing:
266
+ print(f"Skipped {skipped_existing} existing DeepFashion sample rows.", flush=True)
267
  print(f"Prepared DeepFashion samples ({added} added, {updated} updated).", flush=True)
268
 
269
 
 
425
  "dockview": {
426
  "closeSamplesPanel": True,
427
  "hideExplorerPanel": True,
428
+ "initialWidth": int(os.environ.get("FASHION_READOUT_WIDTH", "760")),
429
  "minimumWidth": 620,
430
+ "maximumWidth": int(os.environ.get("FASHION_READOUT_MAX_WIDTH", "860")),
431
  },
432
  "models": model_panel_props(layouts),
433
  "examples": build_examples(dataset),
 
456
  clip_spec = MODEL_SPECS[0]
457
  candidate_spec = MODEL_SPECS[1]
458
  map_dockview = {
459
+ "initialWidth": int(os.environ.get("FASHION_MAP_CONTEXT_WIDTH", "320")),
460
  "minimumWidth": 260,
461
+ "maximumWidth": int(os.environ.get("FASHION_MAP_CONTEXT_MAX_WIDTH", "380")),
462
  }
463
  clip_map = hv.ui.Scatter(
464
  id="fashion-map-clip",
465
+ title="Optional Map: CLIP",
466
  layout_key=layouts["clip"],
467
  position="center",
468
  reference_panel_id="fashion-text-search-readout",
 
473
  )
474
  candidate_map = hv.ui.Scatter(
475
  id="fashion-map-hyper3",
476
+ title="Optional Map: Hyper3",
477
  layout_key=layouts["candidate"],
478
  position="center",
479
  reference_panel_id="fashion-map-clip",
 
496
  )
497
  print("Installing DeepFashion demo extension...", flush=True)
498
  session.ui.add_extension(EXTENSION_DIR, workspace_id=WORKSPACE_ID)
499
+ print("Applying DeepFashion retail search demo view...", flush=True)
500
  session.ui.apply_view(build_demo_view(dataset, layouts), workspace_id=WORKSPACE_ID)
501
  session.ui.set_active_layout(None, workspace_id=WORKSPACE_ID)
502
  session.ui.set_selection([], workspace_id=WORKSPACE_ID)
503
  print(f"\nHyperView DeepFashion text-search demo is running at {session.url}", flush=True)
504
+ print(" Ranked search results are primary; scatter maps are narrow optional context tabs.", flush=True)
505
  return session
506
 
507