Fix DeepFashion demo sample panel flow
Browse files- .hyperview/extensions/fashion-search-readout/panel.js +66 -78
- Dockerfile +1 -1
- demo.py +9 -9
.hyperview/extensions/fashion-search-readout/panel.js
CHANGED
|
@@ -6,7 +6,6 @@ if (!sdk) throw new Error("HyperViewPanelSDK is not available on window.");
|
|
| 6 |
const { React, components, hooks } = sdk;
|
| 7 |
const { Panel, PanelToolbar, PanelToolbarButton } = components;
|
| 8 |
const {
|
| 9 |
-
usePanelClient,
|
| 10 |
usePanelSelection,
|
| 11 |
usePanelSamples,
|
| 12 |
usePanelCommands,
|
|
@@ -607,7 +606,6 @@ function BenchmarkDetails({ typedMetricRows, imageMetricRows }) {
|
|
| 607 |
}
|
| 608 |
|
| 609 |
export default function FashionSearchComparisonPanel() {
|
| 610 |
-
const client = usePanelClient();
|
| 611 |
const selection = usePanelSelection();
|
| 612 |
const samplesState = usePanelSamples();
|
| 613 |
const commands = usePanelCommands();
|
|
@@ -619,8 +617,9 @@ export default function FashionSearchComparisonPanel() {
|
|
| 619 |
);
|
| 620 |
const [contentWidth, setContentWidth] = React.useState(0);
|
| 621 |
const resultsContentRef = React.useRef(null);
|
| 622 |
-
const didApplyDemoLayout = React.useRef(false);
|
| 623 |
const didPrimeEvidence = React.useRef(false);
|
|
|
|
|
|
|
| 624 |
const mode = panelProps.mode === "summary" ? "summary" : panelProps.mode === "results" ? "results" : "full";
|
| 625 |
|
| 626 |
const models = React.useMemo(() => normalizeModels(panelProps.models), [panelProps.models]);
|
|
@@ -646,16 +645,6 @@ export default function FashionSearchComparisonPanel() {
|
|
| 646 |
{ label: "Image-to-image mAP", hyper3: metrics.imageRetrievalMapHyper3, clip: metrics.imageRetrievalMapClip },
|
| 647 |
];
|
| 648 |
|
| 649 |
-
React.useEffect(() => {
|
| 650 |
-
const handleExampleChange = (event) => {
|
| 651 |
-
if (typeof event.detail === "string") {
|
| 652 |
-
setSelectedExampleId(event.detail);
|
| 653 |
-
}
|
| 654 |
-
};
|
| 655 |
-
window.addEventListener("fashion-search-example-change", handleExampleChange);
|
| 656 |
-
return () => window.removeEventListener("fashion-search-example-change", handleExampleChange);
|
| 657 |
-
}, []);
|
| 658 |
-
|
| 659 |
React.useEffect(() => {
|
| 660 |
const node = resultsContentRef.current;
|
| 661 |
if (!node) return undefined;
|
|
@@ -693,13 +682,21 @@ export default function FashionSearchComparisonPanel() {
|
|
| 693 |
[commands],
|
| 694 |
);
|
| 695 |
|
| 696 |
-
const
|
| 697 |
-
async (
|
| 698 |
-
if (!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
try {
|
| 700 |
await commands.setActiveLayout(model.layoutKey, { persist: false });
|
|
|
|
|
|
|
| 701 |
await commands.showSimilar({
|
| 702 |
-
sampleId
|
| 703 |
layoutKey: model.layoutKey,
|
| 704 |
spaceKey: model.spaceKey,
|
| 705 |
k: 10,
|
|
@@ -707,19 +704,43 @@ export default function FashionSearchComparisonPanel() {
|
|
| 707 |
focus: "samples",
|
| 708 |
persist: false,
|
| 709 |
});
|
| 710 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
} catch (error) {
|
| 712 |
const message = error instanceof Error ? error.message : String(error);
|
| 713 |
-
setPanelError(`
|
| 714 |
}
|
| 715 |
},
|
| 716 |
[commands, selectSimilarityAnchor],
|
| 717 |
);
|
| 718 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
const chooseExample = React.useCallback(
|
| 720 |
(exampleId) => {
|
| 721 |
setSelectedExampleId(exampleId);
|
| 722 |
-
window.dispatchEvent(new CustomEvent("fashion-search-example-change", { detail: exampleId }));
|
| 723 |
const item = examples.find((entry) => entry.id === exampleId);
|
| 724 |
const evidenceModel =
|
| 725 |
models.find((model) => model.key === "clip" && model.layoutKey) ||
|
|
@@ -731,28 +752,6 @@ export default function FashionSearchComparisonPanel() {
|
|
| 731 |
[examples, models, showExampleEvidence],
|
| 732 |
);
|
| 733 |
|
| 734 |
-
React.useEffect(() => {
|
| 735 |
-
if (mode === "summary") return;
|
| 736 |
-
if (didApplyDemoLayout.current) return;
|
| 737 |
-
didApplyDemoLayout.current = true;
|
| 738 |
-
|
| 739 |
-
let attempts = 0;
|
| 740 |
-
const timer = window.setInterval(() => {
|
| 741 |
-
attempts += 1;
|
| 742 |
-
["explorer", "labels"].forEach((panelId) => {
|
| 743 |
-
try {
|
| 744 |
-
commands.closePanel?.(panelId);
|
| 745 |
-
} catch (_error) {
|
| 746 |
-
// Older HyperView builds may not have every panel id; ignore missing panes.
|
| 747 |
-
}
|
| 748 |
-
});
|
| 749 |
-
commands.focusPanel?.("fashion-ranked-results");
|
| 750 |
-
if (attempts >= 8) window.clearInterval(timer);
|
| 751 |
-
}, 250);
|
| 752 |
-
|
| 753 |
-
return () => window.clearInterval(timer);
|
| 754 |
-
}, [commands, mode]);
|
| 755 |
-
|
| 756 |
React.useEffect(() => {
|
| 757 |
if (mode === "summary") return;
|
| 758 |
if (didPrimeEvidence.current) return;
|
|
@@ -773,8 +772,7 @@ export default function FashionSearchComparisonPanel() {
|
|
| 773 |
const clearSelection = async () => {
|
| 774 |
if (commands.setLabelFilter) commands.setLabelFilter(null);
|
| 775 |
setPanelError(null);
|
| 776 |
-
await
|
| 777 |
-
await commands.clearSelection();
|
| 778 |
};
|
| 779 |
|
| 780 |
const selectModelQuery = async (item, model) => {
|
|
@@ -787,17 +785,12 @@ export default function FashionSearchComparisonPanel() {
|
|
| 787 |
}
|
| 788 |
setLoadingKey(key);
|
| 789 |
try {
|
| 790 |
-
await
|
| 791 |
-
await commands.showSimilar({
|
| 792 |
sampleId: item.queryId,
|
| 793 |
-
|
| 794 |
-
spaceKey: model.spaceKey,
|
| 795 |
-
k: 10,
|
| 796 |
source: `fashion-demo:${model.key}`,
|
| 797 |
-
|
| 798 |
-
persist: false,
|
| 799 |
});
|
| 800 |
-
await selectSimilarityAnchor(item.queryId);
|
| 801 |
} catch (error) {
|
| 802 |
const message = error instanceof Error ? error.message : String(error);
|
| 803 |
setPanelError(`Could not select target item: ${message}`);
|
|
@@ -819,17 +812,12 @@ export default function FashionSearchComparisonPanel() {
|
|
| 819 |
}
|
| 820 |
setLoadingKey(key);
|
| 821 |
try {
|
| 822 |
-
await
|
| 823 |
-
await commands.showSimilar({
|
| 824 |
sampleId: result.sampleId,
|
| 825 |
-
|
| 826 |
-
spaceKey: model.spaceKey,
|
| 827 |
-
k: 10,
|
| 828 |
source: `fashion-ranked-result:${model.key}`,
|
| 829 |
-
|
| 830 |
-
persist: false,
|
| 831 |
});
|
| 832 |
-
await selectSimilarityAnchor(result.sampleId);
|
| 833 |
} catch (error) {
|
| 834 |
const message = error instanceof Error ? error.message : String(error);
|
| 835 |
setPanelError(`Could not select ranked result: ${message}`);
|
|
@@ -939,6 +927,24 @@ export default function FashionSearchComparisonPanel() {
|
|
| 939 |
{ style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } },
|
| 940 |
"Demo examples are not configured.",
|
| 941 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 942 |
selectedExample && models.length
|
| 943 |
? React.createElement(SearchResultComparison, {
|
| 944 |
item: selectedExample,
|
|
@@ -950,24 +956,6 @@ export default function FashionSearchComparisonPanel() {
|
|
| 950 |
})
|
| 951 |
: null,
|
| 952 |
),
|
| 953 |
-
selectedExample && inspectableModels.length
|
| 954 |
-
? React.createElement(
|
| 955 |
-
Section,
|
| 956 |
-
{ title: "Inspect Exact Target" },
|
| 957 |
-
React.createElement(
|
| 958 |
-
TextBlock,
|
| 959 |
-
null,
|
| 960 |
-
`Exact-target summary for: ${selectedExample?.targetTitle || "target product"}.`,
|
| 961 |
-
),
|
| 962 |
-
React.createElement(ExactTargetActions, {
|
| 963 |
-
item: selectedExample,
|
| 964 |
-
models: inspectableModels,
|
| 965 |
-
loadingKey,
|
| 966 |
-
onSelectQuery: selectModelQuery,
|
| 967 |
-
compact: compactLayout,
|
| 968 |
-
}),
|
| 969 |
-
)
|
| 970 |
-
: null,
|
| 971 |
warnings.length
|
| 972 |
? React.createElement(
|
| 973 |
"div",
|
|
|
|
| 6 |
const { React, components, hooks } = sdk;
|
| 7 |
const { Panel, PanelToolbar, PanelToolbarButton } = components;
|
| 8 |
const {
|
|
|
|
| 9 |
usePanelSelection,
|
| 10 |
usePanelSamples,
|
| 11 |
usePanelCommands,
|
|
|
|
| 606 |
}
|
| 607 |
|
| 608 |
export default function FashionSearchComparisonPanel() {
|
|
|
|
| 609 |
const selection = usePanelSelection();
|
| 610 |
const samplesState = usePanelSamples();
|
| 611 |
const commands = usePanelCommands();
|
|
|
|
| 617 |
);
|
| 618 |
const [contentWidth, setContentWidth] = React.useState(0);
|
| 619 |
const resultsContentRef = React.useRef(null);
|
|
|
|
| 620 |
const didPrimeEvidence = React.useRef(false);
|
| 621 |
+
const evidenceRequestSeq = React.useRef(0);
|
| 622 |
+
const latestEvidenceRequest = React.useRef(null);
|
| 623 |
const mode = panelProps.mode === "summary" ? "summary" : panelProps.mode === "results" ? "results" : "full";
|
| 624 |
|
| 625 |
const models = React.useMemo(() => normalizeModels(panelProps.models), [panelProps.models]);
|
|
|
|
| 645 |
{ label: "Image-to-image mAP", hyper3: metrics.imageRetrievalMapHyper3, clip: metrics.imageRetrievalMapClip },
|
| 646 |
];
|
| 647 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
React.useEffect(() => {
|
| 649 |
const node = resultsContentRef.current;
|
| 650 |
if (!node) return undefined;
|
|
|
|
| 682 |
[commands],
|
| 683 |
);
|
| 684 |
|
| 685 |
+
const loadEvidenceForSample = React.useCallback(
|
| 686 |
+
async ({ sampleId, model, source = "fashion-demo:evidence", errorPrefix = "Could not load the sample evidence panel" }) => {
|
| 687 |
+
if (!sampleId || !model?.layoutKey) return;
|
| 688 |
+
const requestKey = `${sampleId}:${model.layoutKey}:${model.spaceKey || ""}`;
|
| 689 |
+
const request = { sampleId, model, source, errorPrefix, requestKey };
|
| 690 |
+
latestEvidenceRequest.current = request;
|
| 691 |
+
const requestSeq = evidenceRequestSeq.current + 1;
|
| 692 |
+
evidenceRequestSeq.current = requestSeq;
|
| 693 |
+
|
| 694 |
try {
|
| 695 |
await commands.setActiveLayout(model.layoutKey, { persist: false });
|
| 696 |
+
if (requestSeq !== evidenceRequestSeq.current) return;
|
| 697 |
+
|
| 698 |
await commands.showSimilar({
|
| 699 |
+
sampleId,
|
| 700 |
layoutKey: model.layoutKey,
|
| 701 |
spaceKey: model.spaceKey,
|
| 702 |
k: 10,
|
|
|
|
| 704 |
focus: "samples",
|
| 705 |
persist: false,
|
| 706 |
});
|
| 707 |
+
if (requestSeq !== evidenceRequestSeq.current) {
|
| 708 |
+
const latest = latestEvidenceRequest.current;
|
| 709 |
+
if (latest && latest.requestKey !== requestKey) {
|
| 710 |
+
window.setTimeout(() => {
|
| 711 |
+
if (latestEvidenceRequest.current?.requestKey === latest.requestKey) {
|
| 712 |
+
void loadEvidenceForSample(latest);
|
| 713 |
+
}
|
| 714 |
+
}, 0);
|
| 715 |
+
}
|
| 716 |
+
return;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
await selectSimilarityAnchor(sampleId);
|
| 720 |
} catch (error) {
|
| 721 |
const message = error instanceof Error ? error.message : String(error);
|
| 722 |
+
setPanelError(`${errorPrefix}: ${message}`);
|
| 723 |
}
|
| 724 |
},
|
| 725 |
[commands, selectSimilarityAnchor],
|
| 726 |
);
|
| 727 |
|
| 728 |
+
const showExampleEvidence = React.useCallback(
|
| 729 |
+
async (item, model, source = "fashion-demo:query") => {
|
| 730 |
+
if (!item || !model?.layoutKey) return;
|
| 731 |
+
await loadEvidenceForSample({
|
| 732 |
+
sampleId: item.queryId,
|
| 733 |
+
model,
|
| 734 |
+
source,
|
| 735 |
+
errorPrefix: "Could not load the sample evidence panel",
|
| 736 |
+
});
|
| 737 |
+
},
|
| 738 |
+
[loadEvidenceForSample],
|
| 739 |
+
);
|
| 740 |
+
|
| 741 |
const chooseExample = React.useCallback(
|
| 742 |
(exampleId) => {
|
| 743 |
setSelectedExampleId(exampleId);
|
|
|
|
| 744 |
const item = examples.find((entry) => entry.id === exampleId);
|
| 745 |
const evidenceModel =
|
| 746 |
models.find((model) => model.key === "clip" && model.layoutKey) ||
|
|
|
|
| 752 |
[examples, models, showExampleEvidence],
|
| 753 |
);
|
| 754 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
React.useEffect(() => {
|
| 756 |
if (mode === "summary") return;
|
| 757 |
if (didPrimeEvidence.current) return;
|
|
|
|
| 772 |
const clearSelection = async () => {
|
| 773 |
if (commands.setLabelFilter) commands.setLabelFilter(null);
|
| 774 |
setPanelError(null);
|
| 775 |
+
await commands.clearQueryContext({ persist: true });
|
|
|
|
| 776 |
};
|
| 777 |
|
| 778 |
const selectModelQuery = async (item, model) => {
|
|
|
|
| 785 |
}
|
| 786 |
setLoadingKey(key);
|
| 787 |
try {
|
| 788 |
+
await loadEvidenceForSample({
|
|
|
|
| 789 |
sampleId: item.queryId,
|
| 790 |
+
model,
|
|
|
|
|
|
|
| 791 |
source: `fashion-demo:${model.key}`,
|
| 792 |
+
errorPrefix: "Could not select target item",
|
|
|
|
| 793 |
});
|
|
|
|
| 794 |
} catch (error) {
|
| 795 |
const message = error instanceof Error ? error.message : String(error);
|
| 796 |
setPanelError(`Could not select target item: ${message}`);
|
|
|
|
| 812 |
}
|
| 813 |
setLoadingKey(key);
|
| 814 |
try {
|
| 815 |
+
await loadEvidenceForSample({
|
|
|
|
| 816 |
sampleId: result.sampleId,
|
| 817 |
+
model,
|
|
|
|
|
|
|
| 818 |
source: `fashion-ranked-result:${model.key}`,
|
| 819 |
+
errorPrefix: "Could not select ranked result",
|
|
|
|
| 820 |
});
|
|
|
|
| 821 |
} catch (error) {
|
| 822 |
const message = error instanceof Error ? error.message : String(error);
|
| 823 |
setPanelError(`Could not select ranked result: ${message}`);
|
|
|
|
| 927 |
{ style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } },
|
| 928 |
"Demo examples are not configured.",
|
| 929 |
),
|
| 930 |
+
selectedExample && inspectableModels.length
|
| 931 |
+
? React.createElement(
|
| 932 |
+
Section,
|
| 933 |
+
{ title: "Inspect Exact Target" },
|
| 934 |
+
React.createElement(
|
| 935 |
+
TextBlock,
|
| 936 |
+
null,
|
| 937 |
+
`Load the same target product in Samples: ${selectedExample?.targetTitle || "target product"}.`,
|
| 938 |
+
),
|
| 939 |
+
React.createElement(ExactTargetActions, {
|
| 940 |
+
item: selectedExample,
|
| 941 |
+
models: inspectableModels,
|
| 942 |
+
loadingKey,
|
| 943 |
+
onSelectQuery: selectModelQuery,
|
| 944 |
+
compact: compactLayout,
|
| 945 |
+
}),
|
| 946 |
+
)
|
| 947 |
+
: null,
|
| 948 |
selectedExample && models.length
|
| 949 |
? React.createElement(SearchResultComparison, {
|
| 950 |
item: selectedExample,
|
|
|
|
| 956 |
})
|
| 957 |
: null,
|
| 958 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 959 |
warnings.length
|
| 960 |
? React.createElement(
|
| 961 |
"div",
|
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-retail-search-
|
| 46 |
HYPERVIEW_DATASETS_DIR=/home/user/app/demo_data/datasets \
|
| 47 |
HYPERVIEW_MEDIA_DIR=/home/user/app/demo_data/media \
|
| 48 |
FASHION_ENABLE_CONTEXT_MAPS=1 \
|
|
|
|
| 42 |
|
| 43 |
ENV HYPERVIEW_HOST=0.0.0.0 \
|
| 44 |
HYPERVIEW_PORT=7860 \
|
| 45 |
+
HYPERVIEW_WORKSPACE_ID=fashion-retail-search-v062-samples-visible \
|
| 46 |
HYPERVIEW_DATASETS_DIR=/home/user/app/demo_data/datasets \
|
| 47 |
HYPERVIEW_MEDIA_DIR=/home/user/app/demo_data/media \
|
| 48 |
FASHION_ENABLE_CONTEXT_MAPS=1 \
|
demo.py
CHANGED
|
@@ -19,7 +19,7 @@ from hyperview.core.sample import Sample
|
|
| 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-retail-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 |
|
|
@@ -458,8 +458,8 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
|
|
| 458 |
panel="fashion-comparison",
|
| 459 |
position="center",
|
| 460 |
layout=hv.ui.PanelLayout(
|
| 461 |
-
width=int(os.environ.get("FASHION_RESULTS_WIDTH", "
|
| 462 |
-
min_width=
|
| 463 |
),
|
| 464 |
props={
|
| 465 |
**shared_props,
|
|
@@ -473,9 +473,9 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
|
|
| 473 |
reference_panel_id="fashion-ranked-results",
|
| 474 |
direction="right",
|
| 475 |
layout=hv.ui.PanelLayout(
|
| 476 |
-
width=int(os.environ.get("FASHION_SAMPLES_WIDTH", "
|
| 477 |
-
min_width=
|
| 478 |
-
min_height=
|
| 479 |
),
|
| 480 |
)
|
| 481 |
|
|
@@ -485,9 +485,9 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
|
|
| 485 |
clip_spec = MODEL_SPECS[0]
|
| 486 |
candidate_spec = MODEL_SPECS[1]
|
| 487 |
map_layout = hv.ui.PanelLayout(
|
| 488 |
-
height=int(os.environ.get("FASHION_MAP_HEIGHT", "
|
| 489 |
-
min_height=
|
| 490 |
-
min_width=
|
| 491 |
)
|
| 492 |
clip_map = hv.ui.Scatter(
|
| 493 |
id="fashion-map-clip",
|
|
|
|
| 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-retail-search-v062-samples-visible")
|
| 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 |
|
|
|
|
| 458 |
panel="fashion-comparison",
|
| 459 |
position="center",
|
| 460 |
layout=hv.ui.PanelLayout(
|
| 461 |
+
width=int(os.environ.get("FASHION_RESULTS_WIDTH", "620")),
|
| 462 |
+
min_width=500,
|
| 463 |
),
|
| 464 |
props={
|
| 465 |
**shared_props,
|
|
|
|
| 473 |
reference_panel_id="fashion-ranked-results",
|
| 474 |
direction="right",
|
| 475 |
layout=hv.ui.PanelLayout(
|
| 476 |
+
width=int(os.environ.get("FASHION_SAMPLES_WIDTH", "660")),
|
| 477 |
+
min_width=420,
|
| 478 |
+
min_height=480,
|
| 479 |
),
|
| 480 |
)
|
| 481 |
|
|
|
|
| 485 |
clip_spec = MODEL_SPECS[0]
|
| 486 |
candidate_spec = MODEL_SPECS[1]
|
| 487 |
map_layout = hv.ui.PanelLayout(
|
| 488 |
+
height=int(os.environ.get("FASHION_MAP_HEIGHT", "180")),
|
| 489 |
+
min_height=150,
|
| 490 |
+
min_width=220,
|
| 491 |
)
|
| 492 |
clip_map = hv.ui.Scatter(
|
| 493 |
id="fashion-map-clip",
|