Improve DeepFashion demo layout composition
Browse files- .hyperview/extensions/fashion-search-readout/panel.js +266 -88
- Dockerfile +1 -1
- demo.py +31 -15
.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.
|
| 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:
|
| 570 |
-
display: "flex",
|
| 571 |
-
flexDirection: "column",
|
| 572 |
-
gap: 14,
|
| 573 |
background: colors.panelBg,
|
| 574 |
},
|
| 575 |
},
|
| 576 |
React.createElement(
|
| 577 |
-
|
| 578 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
React.createElement(
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
}
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
),
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
"Demo examples are not configured.",
|
| 623 |
),
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
),
|
| 656 |
-
React.createElement(
|
| 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-
|
| 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-
|
| 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 |
-
|
| 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 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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", "
|
| 446 |
"minimumWidth": 260,
|
|
|
|
| 447 |
}
|
| 448 |
clip_map = hv.ui.Scatter(
|
| 449 |
id="fashion-map-clip",
|
| 450 |
-
title="
|
| 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="
|
| 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
|
| 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 |
|