Upgrade geospatial demo to HyperView 0.6.2
Browse files- .hyperview/extensions/geospatial-readout/panel.js +54 -7
- Dockerfile +3 -1
- README.md +4 -4
- demo.py +87 -25
.hyperview/extensions/geospatial-readout/panel.js
CHANGED
|
@@ -62,9 +62,9 @@ function Section({ title, children }) {
|
|
| 62 |
|
| 63 |
function Walkthrough() {
|
| 64 |
const steps = [
|
| 65 |
-
["Pick", "
|
| 66 |
-
["
|
| 67 |
-
["
|
| 68 |
];
|
| 69 |
|
| 70 |
return React.createElement(
|
|
@@ -104,6 +104,33 @@ function Walkthrough() {
|
|
| 104 |
);
|
| 105 |
}
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
function BenchmarkTable({ benchmark }) {
|
| 108 |
if (!benchmark || !Array.isArray(benchmark.rows)) return null;
|
| 109 |
return React.createElement(
|
|
@@ -132,7 +159,7 @@ function BenchmarkTable({ benchmark }) {
|
|
| 132 |
React.createElement(
|
| 133 |
"tr",
|
| 134 |
{ style: { background: colors.cardBgSoft } },
|
| 135 |
-
["Metric", "CLIP", "Hyper3", "Delta"].map((heading) =>
|
| 136 |
React.createElement(
|
| 137 |
"th",
|
| 138 |
{
|
|
@@ -428,6 +455,21 @@ function ExampleCard({
|
|
| 428 |
),
|
| 429 |
deltaLine(item),
|
| 430 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
React.createElement(ModelCompareTable, { item, models, loadingKey, onSelectQuery }),
|
| 432 |
);
|
| 433 |
}
|
|
@@ -512,7 +554,12 @@ export default function GeospatialComparisonPanel() {
|
|
| 512 |
},
|
| 513 |
React.createElement(
|
| 514 |
Section,
|
| 515 |
-
{ title: "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
React.createElement(Walkthrough, null),
|
| 517 |
),
|
| 518 |
warnings.length
|
|
@@ -534,7 +581,7 @@ export default function GeospatialComparisonPanel() {
|
|
| 534 |
: null,
|
| 535 |
React.createElement(
|
| 536 |
Section,
|
| 537 |
-
{ title: "
|
| 538 |
examples.length && models.length
|
| 539 |
? React.createElement(
|
| 540 |
"div",
|
|
@@ -564,7 +611,7 @@ export default function GeospatialComparisonPanel() {
|
|
| 564 |
: null,
|
| 565 |
React.createElement(
|
| 566 |
Section,
|
| 567 |
-
{ title: "Benchmark
|
| 568 |
React.createElement(BenchmarkTable, { benchmark: panelProps.benchmark }),
|
| 569 |
),
|
| 570 |
),
|
|
|
|
| 62 |
|
| 63 |
function Walkthrough() {
|
| 64 |
const steps = [
|
| 65 |
+
["Pick", "Start from a failing scene query."],
|
| 66 |
+
["Compare", "Show CLIP, then Hyper3-CLIP."],
|
| 67 |
+
["Audit", "Check exact scene and parent group."],
|
| 68 |
];
|
| 69 |
|
| 70 |
return React.createElement(
|
|
|
|
| 104 |
);
|
| 105 |
}
|
| 106 |
|
| 107 |
+
function BusinessCase() {
|
| 108 |
+
return React.createElement(
|
| 109 |
+
"div",
|
| 110 |
+
{
|
| 111 |
+
style: {
|
| 112 |
+
border: `1px solid ${colors.border}`,
|
| 113 |
+
borderRadius: 4,
|
| 114 |
+
background: colors.cardBg,
|
| 115 |
+
padding: 10,
|
| 116 |
+
display: "flex",
|
| 117 |
+
flexDirection: "column",
|
| 118 |
+
gap: 6,
|
| 119 |
+
},
|
| 120 |
+
},
|
| 121 |
+
React.createElement(
|
| 122 |
+
"div",
|
| 123 |
+
{ style: { color: colors.strongText, fontSize: 13, fontWeight: 800, lineHeight: 1.25 } },
|
| 124 |
+
"Retrieval audit for aerial image libraries",
|
| 125 |
+
),
|
| 126 |
+
React.createElement(
|
| 127 |
+
"div",
|
| 128 |
+
{ style: { color: colors.bodyText, fontSize: 11, lineHeight: 1.35 } },
|
| 129 |
+
"When an analyst asks for more scenes like this, the top results should stay in the right scene class and operational group. This demo shows where CLIP drifts and where Hyper3-CLIP keeps the neighborhood cleaner.",
|
| 130 |
+
),
|
| 131 |
+
);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
function BenchmarkTable({ benchmark }) {
|
| 135 |
if (!benchmark || !Array.isArray(benchmark.rows)) return null;
|
| 136 |
return React.createElement(
|
|
|
|
| 159 |
React.createElement(
|
| 160 |
"tr",
|
| 161 |
{ style: { background: colors.cardBgSoft } },
|
| 162 |
+
["Metric", "CLIP", "Hyper3-CLIP", "Delta"].map((heading) =>
|
| 163 |
React.createElement(
|
| 164 |
"th",
|
| 165 |
{
|
|
|
|
| 455 |
),
|
| 456 |
deltaLine(item),
|
| 457 |
),
|
| 458 |
+
item.insight
|
| 459 |
+
? React.createElement(
|
| 460 |
+
"div",
|
| 461 |
+
{
|
| 462 |
+
style: {
|
| 463 |
+
color: colors.bodyText,
|
| 464 |
+
fontSize: 11,
|
| 465 |
+
lineHeight: 1.35,
|
| 466 |
+
borderLeft: `2px solid ${colors.accent}`,
|
| 467 |
+
paddingLeft: 8,
|
| 468 |
+
},
|
| 469 |
+
},
|
| 470 |
+
item.insight,
|
| 471 |
+
)
|
| 472 |
+
: null,
|
| 473 |
React.createElement(ModelCompareTable, { item, models, loadingKey, onSelectQuery }),
|
| 474 |
);
|
| 475 |
}
|
|
|
|
| 554 |
},
|
| 555 |
React.createElement(
|
| 556 |
Section,
|
| 557 |
+
{ title: "Business case" },
|
| 558 |
+
React.createElement(BusinessCase, null),
|
| 559 |
+
),
|
| 560 |
+
React.createElement(
|
| 561 |
+
Section,
|
| 562 |
+
{ title: "What to do" },
|
| 563 |
React.createElement(Walkthrough, null),
|
| 564 |
),
|
| 565 |
warnings.length
|
|
|
|
| 581 |
: null,
|
| 582 |
React.createElement(
|
| 583 |
Section,
|
| 584 |
+
{ title: "Queries to inspect" },
|
| 585 |
examples.length && models.length
|
| 586 |
? React.createElement(
|
| 587 |
"div",
|
|
|
|
| 611 |
: null,
|
| 612 |
React.createElement(
|
| 613 |
Section,
|
| 614 |
+
{ title: "Benchmark context" },
|
| 615 |
React.createElement(BenchmarkTable, { benchmark: panelProps.benchmark }),
|
| 616 |
),
|
| 617 |
),
|
Dockerfile
CHANGED
|
@@ -20,7 +20,8 @@ WORKDIR $HOME/app
|
|
| 20 |
|
| 21 |
RUN pip install --upgrade pip
|
| 22 |
|
| 23 |
-
ARG HYPERVIEW_VERSION=0.6.
|
|
|
|
| 24 |
|
| 25 |
# Install CPU-only PyTorch first so the Space does not pull the default CUDA bundle.
|
| 26 |
RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
|
@@ -33,6 +34,7 @@ import hyperview as hv
|
|
| 33 |
print("hyperview", hv.__version__, inspect.signature(hv.launch))
|
| 34 |
PY
|
| 35 |
RUN pip install \
|
|
|
|
| 36 |
"datasets>=4.5.0" \
|
| 37 |
"Pillow>=12.0.0" \
|
| 38 |
"timm>=1.0.0" \
|
|
|
|
| 20 |
|
| 21 |
RUN pip install --upgrade pip
|
| 22 |
|
| 23 |
+
ARG HYPERVIEW_VERSION=0.6.2
|
| 24 |
+
ARG HYPER_MODELS_VERSION=0.3.0
|
| 25 |
|
| 26 |
# Install CPU-only PyTorch first so the Space does not pull the default CUDA bundle.
|
| 27 |
RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
|
|
|
| 34 |
print("hyperview", hv.__version__, inspect.signature(hv.launch))
|
| 35 |
PY
|
| 36 |
RUN pip install \
|
| 37 |
+
"hyper-models[ml]==${HYPER_MODELS_VERSION}" \
|
| 38 |
"datasets>=4.5.0" \
|
| 39 |
"Pillow>=12.0.0" \
|
| 40 |
"timm>=1.0.0" \
|
README.md
CHANGED
|
@@ -24,7 +24,7 @@ This demo builds a balanced NWPU-RESISC45 remote-sensing subset and opens
|
|
| 24 |
HyperView with two pinned scatter panels plus a compact scene-retrieval readout:
|
| 25 |
|
| 26 |
- CLIP ViT-B/32 in a Euclidean 2D layout
|
| 27 |
-
- Hyper3-CLIP `
|
| 28 |
|
| 29 |
The readout panel ranks live query examples by where Hyper3-CLIP improves the
|
| 30 |
top-10 neighborhood over CLIP. The demo is designed around remote-sensing review
|
|
@@ -79,9 +79,9 @@ remote-sensing model.
|
|
| 79 |
This folder is intended to deploy to `hyper3labs/HyperView-EuroSAT-Geospatial`
|
| 80 |
from the `hyperview-spaces` deployment repository.
|
| 81 |
|
| 82 |
-
Hyper3-CLIP weights are loaded
|
| 83 |
-
`hyper3labs/hyper3-clip-v0.5` model repository at runtime. The Space needs
|
| 84 |
-
`HF_TOKEN` secret with access to that model.
|
| 85 |
|
| 86 |
If that secret is missing or lacks gated-model access, the Space starts with a
|
| 87 |
clearly labeled CLIP fallback for the Hyper3-CLIP panel instead of exiting
|
|
|
|
| 24 |
HyperView with two pinned scatter panels plus a compact scene-retrieval readout:
|
| 25 |
|
| 26 |
- CLIP ViT-B/32 in a Euclidean 2D layout
|
| 27 |
+
- Hyper3-CLIP `hyper3-clip-v0.5` from `hyper-models` in a Poincare 2D layout
|
| 28 |
|
| 29 |
The readout panel ranks live query examples by where Hyper3-CLIP improves the
|
| 30 |
top-10 neighborhood over CLIP. The demo is designed around remote-sensing review
|
|
|
|
| 79 |
This folder is intended to deploy to `hyper3labs/HyperView-EuroSAT-Geospatial`
|
| 80 |
from the `hyperview-spaces` deployment repository.
|
| 81 |
|
| 82 |
+
Hyper3-CLIP weights are loaded through the `hyper-models` catalog entry for the
|
| 83 |
+
gated `hyper3labs/hyper3-clip-v0.5` model repository at runtime. The Space needs
|
| 84 |
+
an `HF_TOKEN` secret with access to that model.
|
| 85 |
|
| 86 |
If that secret is missing or lacks gated-model access, the Space starts with a
|
| 87 |
clearly labeled CLIP fallback for the Hyper3-CLIP panel instead of exiting
|
demo.py
CHANGED
|
@@ -208,12 +208,12 @@ MODEL_SPECS = [
|
|
| 208 |
"Inspect Hyper3-CLIP neighbors",
|
| 209 |
),
|
| 210 |
"provider": env_value(
|
| 211 |
-
"GEOSPATIAL_CANDIDATE_PROVIDER", "EUROSAT_CANDIDATE_PROVIDER", "
|
| 212 |
),
|
| 213 |
"model": env_value(
|
| 214 |
"GEOSPATIAL_CANDIDATE_MODEL",
|
| 215 |
"EUROSAT_CANDIDATE_MODEL",
|
| 216 |
-
"
|
| 217 |
),
|
| 218 |
"layout": env_value(
|
| 219 |
"GEOSPATIAL_CANDIDATE_LAYOUT", "EUROSAT_CANDIDATE_LAYOUT", "poincare:2d"
|
|
@@ -267,6 +267,37 @@ def readable_group(label: str) -> str:
|
|
| 267 |
return label.replace("_", " ").replace("-", " ").title()
|
| 268 |
|
| 269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
def save_image(image: Image.Image, destination: Path) -> None:
|
| 271 |
if (
|
| 272 |
destination.exists()
|
|
@@ -559,6 +590,7 @@ def build_examples(
|
|
| 559 |
"score": score,
|
| 560 |
"classDelta": class_delta,
|
| 561 |
"parentDelta": parent_delta,
|
|
|
|
| 562 |
"summaries": {
|
| 563 |
"clip": clip_summary,
|
| 564 |
"candidate": candidate_summary,
|
|
@@ -609,31 +641,58 @@ def aggregate_metrics(
|
|
| 609 |
return metrics
|
| 610 |
|
| 611 |
|
| 612 |
-
def register_hyper3_clip_provider() -> None:
|
| 613 |
-
from hyperview.runtime import ProviderRegistry
|
| 614 |
-
|
| 615 |
-
ProviderRegistry().register_python(
|
| 616 |
-
"hyper3-clip",
|
| 617 |
-
"hyper3_clip_provider:Hyper3ClipEmbeddings",
|
| 618 |
-
description="Hyper3-CLIP v0.5 image embeddings from hyper3labs/hyper3-clip-v0.5",
|
| 619 |
-
overwrite=True,
|
| 620 |
-
)
|
| 621 |
-
|
| 622 |
-
|
| 623 |
def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
|
| 624 |
summaries = collect_neighbor_summaries(dataset)
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
geometry=spec["geometry"],
|
| 631 |
-
layout_dimension=spec["layout_dimension"],
|
| 632 |
-
)
|
| 633 |
-
for spec in MODEL_SPECS
|
| 634 |
-
]
|
| 635 |
return hv.ui.View(
|
| 636 |
-
hv.ui.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
hv.ui.ExtensionPanel(
|
| 638 |
id="geospatial-retrieval-readout",
|
| 639 |
extension="geospatial-readout",
|
|
@@ -649,6 +708,10 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
|
|
| 649 |
"models": model_panel_props(layouts),
|
| 650 |
"examples": build_examples(dataset, summaries),
|
| 651 |
"warnings": RUNTIME_WARNINGS,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
},
|
| 653 |
),
|
| 654 |
)
|
|
@@ -673,7 +736,6 @@ def launch_demo(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.Session:
|
|
| 673 |
|
| 674 |
|
| 675 |
def main() -> None:
|
| 676 |
-
register_hyper3_clip_provider()
|
| 677 |
dataset, layouts = build_dataset()
|
| 678 |
print("Layouts:", flush=True)
|
| 679 |
for spec in MODEL_SPECS:
|
|
|
|
| 208 |
"Inspect Hyper3-CLIP neighbors",
|
| 209 |
),
|
| 210 |
"provider": env_value(
|
| 211 |
+
"GEOSPATIAL_CANDIDATE_PROVIDER", "EUROSAT_CANDIDATE_PROVIDER", "hyper-models"
|
| 212 |
),
|
| 213 |
"model": env_value(
|
| 214 |
"GEOSPATIAL_CANDIDATE_MODEL",
|
| 215 |
"EUROSAT_CANDIDATE_MODEL",
|
| 216 |
+
"hyper3-clip-v0.5",
|
| 217 |
),
|
| 218 |
"layout": env_value(
|
| 219 |
"GEOSPATIAL_CANDIDATE_LAYOUT", "EUROSAT_CANDIDATE_LAYOUT", "poincare:2d"
|
|
|
|
| 267 |
return label.replace("_", " ").replace("-", " ").title()
|
| 268 |
|
| 269 |
|
| 270 |
+
def example_insight(
|
| 271 |
+
label: str,
|
| 272 |
+
clip_summary: dict[str, Any],
|
| 273 |
+
candidate_summary: dict[str, Any],
|
| 274 |
+
) -> str:
|
| 275 |
+
clip_same = clip_summary["sameClassHits"]
|
| 276 |
+
clip_parent = clip_summary["parentHits"]
|
| 277 |
+
candidate_same = candidate_summary["sameClassHits"]
|
| 278 |
+
candidate_parent = candidate_summary["parentHits"]
|
| 279 |
+
total = candidate_summary.get("total", 10)
|
| 280 |
+
if label == "airplane":
|
| 281 |
+
return (
|
| 282 |
+
f"Transport audit: Hyper3-CLIP raises the neighborhood from {clip_parent}/{total} "
|
| 283 |
+
f"to {candidate_parent}/{total} transport scenes and finds {candidate_same}/{total} exact airplanes."
|
| 284 |
+
)
|
| 285 |
+
if label == "meadow":
|
| 286 |
+
return (
|
| 287 |
+
f"Land-cover QA: Hyper3-CLIP keeps {candidate_parent}/{total} neighbors in agriculture/vegetation "
|
| 288 |
+
f"versus CLIP's {clip_parent}/{total}, with {candidate_same}/{total} exact meadows."
|
| 289 |
+
)
|
| 290 |
+
if label == "runway":
|
| 291 |
+
return (
|
| 292 |
+
f"Infrastructure search: Hyper3-CLIP finds {candidate_same}/{total} exact runways versus "
|
| 293 |
+
f"CLIP's {clip_same}/{total}, while doubling the transport-group neighborhood."
|
| 294 |
+
)
|
| 295 |
+
return (
|
| 296 |
+
f"Hyper3-CLIP improves exact scene hits from {clip_same}/{total} to {candidate_same}/{total} "
|
| 297 |
+
f"and parent-group hits from {clip_parent}/{total} to {candidate_parent}/{total}."
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
|
| 301 |
def save_image(image: Image.Image, destination: Path) -> None:
|
| 302 |
if (
|
| 303 |
destination.exists()
|
|
|
|
| 590 |
"score": score,
|
| 591 |
"classDelta": class_delta,
|
| 592 |
"parentDelta": parent_delta,
|
| 593 |
+
"insight": example_insight(label, clip_summary, candidate_summary),
|
| 594 |
"summaries": {
|
| 595 |
"clip": clip_summary,
|
| 596 |
"candidate": candidate_summary,
|
|
|
|
| 641 |
return metrics
|
| 642 |
|
| 643 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
|
| 645 |
summaries = collect_neighbor_summaries(dataset)
|
| 646 |
+
clip_spec = MODEL_SPECS[0]
|
| 647 |
+
candidate_spec = MODEL_SPECS[1]
|
| 648 |
+
samples_panel_id = "grid"
|
| 649 |
+
clip_panel_id = "clip-geospatial-map"
|
| 650 |
+
candidate_panel_id = "candidate-geospatial-map"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
return hv.ui.View(
|
| 652 |
+
hv.ui.Samples(
|
| 653 |
+
id=samples_panel_id,
|
| 654 |
+
title="Samples",
|
| 655 |
+
position="center",
|
| 656 |
+
props={
|
| 657 |
+
"dockview": {
|
| 658 |
+
"minimumHeight": 220,
|
| 659 |
+
"minimumWidth": 360,
|
| 660 |
+
},
|
| 661 |
+
},
|
| 662 |
+
),
|
| 663 |
+
hv.ui.Scatter(
|
| 664 |
+
id=clip_panel_id,
|
| 665 |
+
title=clip_spec["panel_title"],
|
| 666 |
+
layout_key=layouts[clip_spec["key"]],
|
| 667 |
+
position="center",
|
| 668 |
+
reference_panel_id=samples_panel_id,
|
| 669 |
+
direction="below",
|
| 670 |
+
geometry=clip_spec["geometry"],
|
| 671 |
+
layout_dimension=clip_spec["layout_dimension"],
|
| 672 |
+
props={
|
| 673 |
+
"dockview": {
|
| 674 |
+
"initialHeight": 260,
|
| 675 |
+
"minimumHeight": 190,
|
| 676 |
+
"minimumWidth": 280,
|
| 677 |
+
},
|
| 678 |
+
},
|
| 679 |
+
),
|
| 680 |
+
hv.ui.Scatter(
|
| 681 |
+
id=candidate_panel_id,
|
| 682 |
+
title=candidate_spec["panel_title"],
|
| 683 |
+
layout_key=layouts[candidate_spec["key"]],
|
| 684 |
+
position="center",
|
| 685 |
+
reference_panel_id=clip_panel_id,
|
| 686 |
+
direction="right",
|
| 687 |
+
geometry=candidate_spec["geometry"],
|
| 688 |
+
layout_dimension=candidate_spec["layout_dimension"],
|
| 689 |
+
props={
|
| 690 |
+
"dockview": {
|
| 691 |
+
"minimumHeight": 190,
|
| 692 |
+
"minimumWidth": 280,
|
| 693 |
+
},
|
| 694 |
+
},
|
| 695 |
+
),
|
| 696 |
hv.ui.ExtensionPanel(
|
| 697 |
id="geospatial-retrieval-readout",
|
| 698 |
extension="geospatial-readout",
|
|
|
|
| 708 |
"models": model_panel_props(layouts),
|
| 709 |
"examples": build_examples(dataset, summaries),
|
| 710 |
"warnings": RUNTIME_WARNINGS,
|
| 711 |
+
"dockview": {
|
| 712 |
+
"initialWidth": 330,
|
| 713 |
+
"minimumWidth": 290,
|
| 714 |
+
},
|
| 715 |
},
|
| 716 |
),
|
| 717 |
)
|
|
|
|
| 736 |
|
| 737 |
|
| 738 |
def main() -> None:
|
|
|
|
| 739 |
dataset, layouts = build_dataset()
|
| 740 |
print("Layouts:", flush=True)
|
| 741 |
for spec in MODEL_SPECS:
|