Sign inSign up

mbari/stratagrid

By mbari

•Updated 2 months ago

StrataGrid — large training-image dataset visualizer

Image
0

1.2K

mbari/stratagrid repository overview

⁠StrataGrid

MBARI

Built with Cursor Docker Pulls Docker Image Version semantic-release: angular

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.


⁠Features

  • Summary view — the default landing view after loading a collection: up to 50 classes per page, each shown as a card with two exemplar thumbnails — the sharpest and blurriest image on record for that class (scored at build time by 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-driven onboarding — the config screen auto-fetches a catalog.json and renders a card for every collection; click Add → to load it. No URL paste required.
  • Catalog groups — a single catalog entry can bundle multiple collection.json URLs under one card, loaded together as separate projects so each keeps its own anonymous_gateway_url.
  • Multi-project merging — load multiple collections simultaneously; images from the same class name are pooled into one section, each project color-coded.
  • Toggle / remove projects — enable or disable projects with pills in the top bar; the gallery updates instantly.
  • Class sidebar — search by class name, click classes to multi-select and restrict the grid; selected count badge with one-click clear.
  • Image ID search — real-time filter across all visible images by filename/ID.
  • Tag filtering — catalog cards carry optional tags; click tag chips to filter the catalog to matching collections.
  • Section-level virtualization — ClassSection elements are rendered only when near the viewport (400 px margin), keeping 100k+ image galleries smooth.
  • Card-level lazy loading — each thumbnail's src is set only when the card enters view (300 px margin); shimmer placeholder while loading; graceful error state on failure.
  • Wikipedia taxonomy panel — collapsible panel under each class section; fetches and parses the English Wikipedia taxobox (ranks, binomial, authority, summary, thumbnail, link). Results are cached in localStorage.
  • Detail panel — click any image to open a slide-in panel with the full image, metadata (ID, class, project, URL, index), keyboard navigation, and a class preview strip (8 thumbs before/after current).
  • Annotation links — if a collection sets 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).
  • Refresh — top bar refresh button remounts all sections to re-trigger lazy loading.

⁠Prerequisites

ToolVersionNotes
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⁠anyOptional — brew install just

⁠Quick start

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.


⁠catalog.json (optional)

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" }
      ]
    }
  ]
}
⁠Single entry
FieldRequiredDescription
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)
⁠Group entry

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.

FieldRequiredDescription
id✅Stable group ID (must be unique among catalog entries)
members✅Array of { "collection_url": "..." } entries (each loaded as its own project)
nameoptionalCard title (falls back to the first member's collection.json name)
descriptionoptionalCard description (falls back to the first member's description)
thumbnailoptionalCard thumbnail URL relative to catalog.json (falls back to first member's)
tagsoptionalTag chips on the card (falls back to the union of member tags)

Group cards show:

  • An Nx badge in the thumbnail corner indicating member count.
  • A per-member list with a status dot (loaded / error / pending).
  • A CTA reflecting state: Add 2 collections →, Add 1 remaining →, or Added (2).
⁠Where the app looks for catalog.json

URLs are tried in order until one returns valid catalog JSON:

  1. localStorage.catalogUrl — manual override (set via browser DevTools)
  2. /images/catalog.json on the current origin (works behind nginx)
  3. 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.


⁠collection.json

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"
}
FieldRequiredDescription
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_imagesoptionalPre-computed total image count
thumbnailoptionalThumbnail path/URL for the catalog card (relative to collection.json)
tagsoptionalTag chips on the catalog card; used for tag filtering
anonymous_gateway_urloptionalTator 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.
exemplarsoptionalPer-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.


⁠stats.json format

{
  "total_labels": {
    "ClassName A": 71,
    "ClassName B": ["img_001", "img_002", "img_003"]
  }
}
FieldRequiredDescription
total_labels✅Object mapping class name → image count or array of image IDs
  • Number value: the app generates zero-padded 5-digit IDs (00001, 00002, …) up to that count.
  • Array value: the strings are used directly as image IDs.
  • Any ID whose string contains _view is excluded.

Image URL pattern: {collection.base_url}/{class_name}/{image_id}.{collection.image_extension}


⁠Summary view

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.

  • Up to 50 classes per page, with a search box and pager (First / Prev / page indicator / Next / Last).
  • Each class card shows two thumbnails: the sharpest and blurriest image for that class, labeled accordingly. A class with only one image shows a single "Only image" thumbnail.
  • Clicking a card switches to the Gallery tab filtered to just that class.
  • Switch tabs anytime with the Summary / Gallery toggle in the top bar.

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.


⁠Combining multiple projects

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.

  • Classes shared across projects have their images pooled into one section.
  • Toggle projects on/off using the pills in the top bar; the gallery updates instantly.
  • Remove a project with the × on its pill.

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.


⁠Wikipedia taxonomy

Each class section has a collapsible Taxonomy toggle. Expanding it fetches the English Wikipedia taxobox for the class name and displays:

  • Taxonomic rank hierarchy (domain → subspecies)
  • Binomial name and authority
  • Article summary (first 320 characters)
  • Thumbnail linked to the article

Fetched data is cached in localStorage (prefix wiki-taxonomy-v1:). To clear the cache, run window.__clearTaxonomyCache() in the browser console.


⁠Keyboard shortcuts

These shortcuts are active when the detail panel is open:

KeyAction
← / →Navigate to the previous / next image within the same class
EscClose the detail panel

⁠Project structure

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)

Tag summary

Content type

Image

Digest

sha256:fa80d5864…

Size

59.3 MB

Last updated

2 months ago

docker pull mbari/stratagrid