Skip to content

Commit a9e30cc

Browse files
committed
Add 3D layout support and improve dataset ingestion
1 parent d86a85e commit a9e30cc

34 files changed

Lines changed: 2874 additions & 630 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ AGENTS.md
7777
.specstory/
7878

7979
# Deployment repo (managed as a separate nested git repository)
80-
deploy/
80+
hyper-models/
81+
hyperview-spaces/
82+
eval/
8183

8284
# Generated version file (hatch-vcs)
8385
src/hyperview/_version.py

docs/datasets.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ dataset.add_from_huggingface(
3131
)
3232
```
3333

34+
To target a named Hugging Face subset/configuration, pass `config="default"`
35+
or another config name alongside `split=`.
36+
37+
To avoid materializing the full split before sampling, use `streaming=True`.
38+
This keeps ingestion on Hugging Face's iterable dataset path and stops after the
39+
requested rows:
40+
41+
```python
42+
dataset.add_from_huggingface(
43+
"uoft-cs/cifar100",
44+
split="train",
45+
image_key="img",
46+
label_key="fine_label",
47+
max_samples=500,
48+
streaming=True,
49+
)
50+
```
51+
52+
When `streaming=True` and `shuffle=True`, sampling becomes approximate and
53+
buffer-based. Tune `shuffle_buffer_size=` if you need more mixing and can afford
54+
additional read-ahead.
55+
3456
### From Directory
3557
```python
3658
dataset.add_images_dir("/path/to/images", label_from_folder=True)
@@ -66,8 +88,8 @@ Samples are **never implicitly deleted**. Use `hv.Dataset.delete("name")` for ex
6688
# High-dimensional embeddings (CLIP)
6789
dataset.compute_embeddings(model="openai/clip-vit-base-patch32", show_progress=True)
6890

69-
# 2D projections for visualization
70-
dataset.compute_visualization() # UMAP to Euclidean + Hyperbolic
91+
# 2D projection for visualization
92+
dataset.compute_visualization() # Defaults to euclidean:2d
7193
```
7294

7395
Embeddings are stored per-sample. If a sample already has embeddings, it's skipped.

frontend/package-lock.json

Lines changed: 14 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"clsx": "^2.1.1",
2626
"cmdk": "^1.1.1",
2727
"dockview": "^4.13.1",
28-
"hyper-scatter": "^0.1.0",
28+
"hyper-scatter": "file:../hyper-scatter",
2929
"justified-layout": "^4.1.0",
3030
"lucide-react": "^0.562.0",
3131
"next": "^16.1.6",

frontend/src/app/page.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DockviewWorkspace, DockviewProvider } from "@/components/DockviewWorksp
66
import { useStore } from "@/store/useStore";
77
import type { Sample } from "@/types";
88
import {
9+
ApiError,
910
fetchDataset,
1011
fetchSamples,
1112
fetchSamplesBatch,
@@ -35,12 +36,22 @@ export default function Home() {
3536
lassoTotal,
3637
lassoIsLoading,
3738
setLassoResults,
39+
clearLassoSelection,
3840
labelFilter,
3941
} = useStore();
4042

4143
const [loadingMore, setLoadingMore] = useState(false);
4244
const labelFilterRef = useRef<string | null>(labelFilter ?? null);
4345

46+
const refreshDatasetMetadata = useCallback(async () => {
47+
try {
48+
const dataset = await fetchDataset();
49+
setDatasetInfo(dataset);
50+
} catch (refreshErr) {
51+
console.error("Failed to refresh dataset metadata:", refreshErr);
52+
}
53+
}, [setDatasetInfo]);
54+
4455
// Initial data load - runs once on mount
4556
// Store setters are stable and don't need to be in deps
4657
useEffect(() => {
@@ -74,7 +85,6 @@ export default function Home() {
7485
const fetchSelectedSamples = async () => {
7586
if (isLassoSelection) return;
7687
if (selectedIds.size === 0) return;
77-
if (selectionSource === "label") return;
7888

7989
// Find IDs that are selected but not in our samples array
8090
const loadedIds = new Set(samples.map((s) => s.id));
@@ -131,6 +141,10 @@ export default function Home() {
131141
const res = await fetchLassoSelection({
132142
layoutKey: lassoQuery.layoutKey,
133143
polygon: lassoQuery.polygon,
144+
labelFilter: lassoQuery.labelFilter ?? undefined,
145+
view3d: lassoQuery.view3d,
146+
viewportWidth: lassoQuery.viewportWidth,
147+
viewportHeight: lassoQuery.viewportHeight,
134148
offset: 0,
135149
limit: SAMPLES_PER_PAGE,
136150
signal: abort.signal,
@@ -139,6 +153,18 @@ export default function Home() {
139153
setLassoResults(res.samples, res.total, false);
140154
} catch (err) {
141155
if (err instanceof DOMException && err.name === "AbortError") return;
156+
157+
if (
158+
err instanceof ApiError &&
159+
err.status === 404 &&
160+
typeof err.detail === "string" &&
161+
err.detail.includes("Layout not found")
162+
) {
163+
clearLassoSelection();
164+
void refreshDatasetMetadata();
165+
return;
166+
}
167+
142168
console.error("Failed to fetch lasso selection:", err);
143169
setLassoResults([], 0, false);
144170
}
@@ -147,7 +173,14 @@ export default function Home() {
147173
run();
148174

149175
return () => abort.abort();
150-
}, [isLassoSelection, lassoIsLoading, lassoQuery, setLassoResults]);
176+
}, [
177+
clearLassoSelection,
178+
isLassoSelection,
179+
lassoIsLoading,
180+
lassoQuery,
181+
refreshDatasetMetadata,
182+
setLassoResults,
183+
]);
151184

152185
// Load more samples
153186
const loadMore = useCallback(async () => {
@@ -163,11 +196,26 @@ export default function Home() {
163196
const res = await fetchLassoSelection({
164197
layoutKey: lassoQuery.layoutKey,
165198
polygon: lassoQuery.polygon,
199+
labelFilter: lassoQuery.labelFilter ?? undefined,
200+
view3d: lassoQuery.view3d,
201+
viewportWidth: lassoQuery.viewportWidth,
202+
viewportHeight: lassoQuery.viewportHeight,
166203
offset: lassoSamples.length,
167204
limit: SAMPLES_PER_PAGE,
168205
});
169206
setLassoResults(res.samples, res.total, true);
170207
} catch (err) {
208+
if (
209+
err instanceof ApiError &&
210+
err.status === 404 &&
211+
typeof err.detail === "string" &&
212+
err.detail.includes("Layout not found")
213+
) {
214+
clearLassoSelection();
215+
void refreshDatasetMetadata();
216+
return;
217+
}
218+
171219
console.error("Failed to load more lasso samples:", err);
172220
} finally {
173221
setLoadingMore(false);
@@ -194,8 +242,10 @@ export default function Home() {
194242
lassoQuery,
195243
lassoSamples.length,
196244
lassoTotal,
245+
clearLassoSelection,
197246
samplesLoaded,
198247
totalSamples,
248+
refreshDatasetMetadata,
199249
setLassoResults,
200250
labelFilter,
201251
]);

0 commit comments

Comments
 (0)