StrataGrid — large training-image dataset visualizer
1.2K
A React application for visualizing large image datasets served from nginx. Supports hundreds of classes × thousands of images with lazy loading, class filtering, multi-project merging, Wikipedia taxonomy lookup, and a slide-in detail panel.
generate_collection.py). Search classes, page through hundreds of them, and click a card to jump straight into the full Gallery filtered to that class. Toggle between Summary and Gallery anytime from the top bar.catalog.json and renders a card for every collection; click Add → to load it. No URL paste required.collection.json URLs under one card, loaded together as separate projects so each keeps its own anonymous_gateway_url.ClassSection elements are rendered only when near the viewport (400 px margin), keeping 100k+ image galleries smooth.src is set only when the card enters view (300 px margin); shimmer placeholder while loading; graceful error state on failure.localStorage.anonymous_gateway_url, each image in the detail panel gains an Annotation ↗ link to the Tator localization gallery filtered to that image. The link routes through Tator's anonymous gateway so viewers don't need their own Tator credentials (the project must have the guest anonymous user added with View Only).| Tool | Version | Notes |
|---|---|---|
| Node.js | 18 + | JavaScript runtime and npm |
| Docker | 20 + | Runs the local nginx image server |
| Python | 3.9 + | generate_collection.py and semantic-release CI. Exemplar scoring needs pip install -r requirements-generate.txt (Pillow, numpy) — setup.sh does this for you. |
| just | any | Optional — brew install just |
From a fresh checkout, run the bootstrap script — it handles everything:
bash setup.sh
This installs Node dependencies, unpacks the bundled Cats vs Dogs example dataset, generates the collection manifest, writes images/catalog.json, starts nginx, and launches the Vite dev server.
See DEV.md for manual setup steps, just shortcuts, how to add new datasets, and Docker Compose details.
Place a catalog.json next to your collections (typically at images/catalog.json) to advertise
multiple datasets. Each entry is either a single collection or a group that bundles
several collection.json URLs behind one card. The app fetches the manifests to render cards.
{
"title": "StrataGrid Catalog",
"description": "Optional short blurb about this catalog",
"collections": [
{
"id": "catsdogs",
"collection_url": "catsdogs/collection.json"
},
{
"id": "i2map-combined",
"name": "i2MAP combined",
"description": "Bulk + video crops merged into one card",
"thumbnail": "i2mapbulk/crops/Anchovy/00001.jpg",
"tags": ["mbari", "i2map"],
"members": [
{ "collection_url": "i2mapbulk/crops/collection.json" },
{ "collection_url": "video/crops/collection.json" }
]
}
]
}
| Field | Required | Description |
|---|---|---|
id | ✅ | Stable per-collection ID; used to detect "already added" |
collection_url | ✅ | URL to this dataset's collection.json (relative URLs resolve against the catalog URL) |
A group renders one card that loads multiple collection.json files as separate
projects when added. Each project keeps its own anonymous_gateway_url, color, and pill in the
top bar. Classes shared across members are pooled into one section, exactly as if the user
had loaded each collection individually.
| Field | Required | Description |
|---|---|---|
id | ✅ | Stable group ID (must be unique among catalog entries) |
members | ✅ | Array of { "collection_url": "..." } entries (each loaded as its own project) |
name | optional | Card title (falls back to the first member's collection.json name) |
description | optional | Card description (falls back to the first member's description) |
thumbnail | optional | Card thumbnail URL relative to catalog.json (falls back to first member's) |
tags | optional | Tag chips on the card (falls back to the union of member tags) |
Group cards show:
Nx badge in the thumbnail corner indicating member count.Add 2 collections →, Add 1 remaining →, or Added (2).catalog.jsonURLs are tried in order until one returns valid catalog JSON:
localStorage.catalogUrl — manual override (set via browser DevTools)/images/catalog.json on the current origin (works behind nginx)http://localhost:8080/images/catalog.json (dev convenience when Vite runs on :5173)If none resolve, the config screen lists the URLs it tried and shows a Retry button.
One per dataset directory, generated by generate_collection.py:
{
"id": "catsdogs",
"name": "Cats vs Dogs",
"description": "Two-class subset, ~2,000 224×224 crops.",
"base_url": "https://your-server/images/catsdogs",
"image_extension": "jpg",
"stats_url": "stats.json",
"total_images": 1999,
"thumbnail": "cats/cat.0.jpg",
"tags": ["animals", "binary"],
"anonymous_gateway_url": "https://tator.example.org/anonymous-gateway?redirect=/1/analytics/localizations"
}
| Field | Required | Description |
|---|---|---|
id | ✅ | Stable ID (should match the catalog entry) |
name | ✅ | Project display name (shown in the app top bar) |
description | ✅ | Short blurb on the catalog card |
base_url | ✅ | Public URL root where class folders are served (no trailing slash) |
image_extension | ✅ | Image file extension: jpg, png, webp, etc. |
stats_url | ✅ | URL to stats.json (relative URLs resolve against collection.json) |
total_images | optional | Pre-computed total image count |
thumbnail | optional | Thumbnail path/URL for the catalog card (relative to collection.json) |
tags | optional | Tag chips on the catalog card; used for tag filtering |
anonymous_gateway_url | optional | Tator anonymous-gateway URL with a redirect query param pointing at the project's localization gallery (e.g. https://tator.example.org/anonymous-gateway?redirect=/1/analytics/localizations). Enables Annotation ↗ links that authenticate viewers as the project's guest anonymous user, so links work without per-viewer Tator credentials. See Make a Project Public to grant the anonymous user View Only access. |
exemplars | optional | Per-class { sharpest, sharpest_score, blurriest, blurriest_score } picks used by the Summary view, keyed by class name (see below). Generated by generate_collection.py unless run with --skip-exemplars. Classes missing from this object (or collections predating it) fall back to an arbitrary first/last image pair in the Summary view. |
"exemplars": {
"ClassName A": {
"sharpest": "img_042",
"sharpest_score": 512.4,
"blurriest": "img_007",
"blurriest_score": 6.1
}
}
*_score is the variance of the Laplacian of the grayscale image — higher means sharper/more detailed, near-zero means flat/blurry. Scores aren't normalized across collections; they're only used to rank images within the same class.
{
"total_labels": {
"ClassName A": 71,
"ClassName B": ["img_001", "img_002", "img_003"]
}
}
| Field | Required | Description |
|---|---|---|
total_labels | ✅ | Object mapping class name → image count or array of image IDs |
00001, 00002, …) up to that count._view is excluded.Image URL pattern: {collection.base_url}/{class_name}/{image_id}.{collection.image_extension}
The Summary tab is the default view after loading a collection — a higher-level overview than the full gallery, meant for skimming hundreds of classes without loading every image.
Exemplar picks come from the exemplars field in collection.json (see above), computed by
generate_collection.py using variance-of-Laplacian sharpness scoring. If a collection was
generated without exemplar scoring (or with --skip-exemplars), its classes still render with
two thumbnails in the Summary view — an arbitrary first/last pair rather than a true sharp/blur
pick — so the view degrades gracefully rather than breaking.
Load additional collections from the config screen (the + Add project button in the top bar reopens it). Each project gets a distinct color dot on its thumbnails.
To pre-bundle several collections behind one catalog card (so users add them in a single click
without losing per-collection anonymous_gateway_url), use a group entry in
catalog.json.
Each class section has a collapsible Taxonomy toggle. Expanding it fetches the English Wikipedia taxobox for the class name and displays:
Fetched data is cached in localStorage (prefix wiki-taxonomy-v1:). To clear the cache, run
window.__clearTaxonomyCache() in the browser console.
These shortcuts are active when the detail panel is open:
| Key | Action |
|---|---|
← / → | Navigate to the previous / next image within the same class |
Esc | Close the detail panel |
stratagrid/
├── src/
│ ├── App.jsx # Root — state, project lifecycle, data merging
│ ├── components/
│ │ ├── ConfigScreen.jsx # Catalog browser + collection loader
│ │ ├── TopBar.jsx # Project pills, view toggle, image ID search, stats, refresh
│ │ ├── SummaryView.jsx # Paginated (50/page) class overview with sharp/blur exemplars
│ │ ├── ClassSidebar.jsx # Class search & multi-select with bar charts
│ │ ├── ImageGrid.jsx # Scrollable grid with section virtualization
│ │ ├── ImageCard.jsx # Lazy-loading thumbnail with error state
│ │ ├── DetailPanel.jsx # Slide-in full view, metadata, nav, preview strip
│ │ └── TaxonomyPanel.jsx # Wikipedia taxonomy per class (collapsible)
│ └── lib/
│ └── wikipediaTaxonomy.js # Wikipedia API fetch, taxobox parsing, localStorage cache
├── images/
│ ├── catalog.json # Catalog of available collections
│ └── <collection>/
│ ├── collection.json # Manifest (generate_collection.py)
│ ├── stats.json # Image index
│ └── <class>/<id>.jpg
├── catsdogs.tar.gz # Example dataset archive
├── uav.tar.gz # Example UAV dataset archive
├── generate_collection.py # Build collection.json from stats.json (+ exemplar scoring)
├── requirements-generate.txt # Pillow/numpy for generate_collection.py exemplar scoring
├── example-collection.json # collection.json format reference
├── example-stats.json # stats.json format reference
├── setup.sh # Bootstrap: unpack data, start nginx + Vite
├── justfile # Dev shortcuts (just start / stop / logs)
├── docker-compose.yml # Local nginx for image serving
├── DEV.md # Full developer guide
└── nginx/default.conf # nginx config (CORS, autoindex, healthcheck)
Content type
Image
Digest
sha256:fa80d5864…
Size
59.3 MB
Last updated
2 months ago
docker pull mbari/stratagrid