Skip to content

Commit c2f700c

Browse files
authored
docs: overhaul README with architecture, structure, options and integration guide (#25)
1 parent 52cf75d commit c2f700c

1 file changed

Lines changed: 165 additions & 36 deletions

File tree

README.md

Lines changed: 165 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,154 @@
1-
# Viewer interactif — Vol-E with local OME-Zarr `.zip` support
1+
# Interactive Zarr Explorer — Vol-E with local OME-Zarr `.zip` support
22

33
A self-contained monorepo bundling a fork of **Vol-E (Volume Explorer)** — the
44
Allen Institute's browser-based 3D volume viewer — extended to open **local
5-
OME-Zarr files packaged as `.zip`** directly in the browser, with no server and
6-
no URL.
7-
8-
It combines the two halves of Vol-E in one place, with a [pixi](https://pixi.sh)
9-
environment for reproducible setup:
5+
OME-Zarr files packaged as `.zip`** directly in the browser, with no server, no
6+
upload, and no account. Everything runs client-side.
107

118
- **`vole-core/`** — the WebGL engine (forked from
129
[allen-cell-animated/vole-core](https://github.com/allen-cell-animated/vole-core)).
1310
Adds `ZipStore`, a zarrita store that reads OME-Zarr chunks lazily out of a
1411
local `.zip` `Blob`, exposed through the `zipSources` loader option.
1512
- **`vole-app/`** — the React app (forked from
1613
[allen-cell-animated/vole-app](https://github.com/allen-cell-animated/vole-app)).
17-
Its home-page **Load .zip** button opens a drag-and-drop picker for **one or more**
18-
local `.ome.zarr.zip` files, read in-place via the engine's `ZipStore`. With
19-
several files you can either **overlay** their channels into one volume (same
20-
pixel dimensions required) or load them as switchable **scenes**, chosen from a
21-
file-name dropdown. A separate **Load URL** button loads remote OME-Zarr. When an
22-
archive carries a per-object measurement table, the app also adds an interactive
23-
**feature scatter** alongside the 3D view (see below).
14+
Adds local `.zip` loading, multi-zip scenes/overlay, and the analysis panels.
2415

2516
`vole-app` consumes the local `vole-core` through `"@aics/vole-core":
2617
"file:../vole-core"`, so the two always build together.
2718

28-
## Feature scatter (2D)
19+
---
20+
21+
## What's new (fork additions)
22+
23+
Everything below is added on top of upstream Vol-E:
24+
25+
- **Local `.zip` loading** — read an `.ome.zarr.zip` in place, lazily, per chunk,
26+
with no extraction and no server (`ZipStore`).
27+
- **Hardened, secure parsing** — clear errors for unreadable / non-OME-Zarr /
28+
encrypted archives, backslash path normalization for cross-OS zips, and guards
29+
against hostile archives (entry-count and per-entry size caps, metadata CRC,
30+
truncation detection).
31+
- **Multi-zip loading** — load several `.zip` files at once and either **overlay**
32+
their channels into one volume or browse them as switchable **scenes** (with a
33+
file-name scene picker). Channel settings are remembered **per scene**.
34+
- **Feature scatter (2D)** — when a zarr carries a `tables/measurements` table, an
35+
interactive scatter with X/Y/color-by, box/point selection, per-feature gates,
36+
and CSV export.
37+
- **Correlation heatmap** and **manual annotation labels** over the same table.
38+
39+
> **Known limitation:** switching between very different scenes can briefly show a
40+
> _bleedthrough_ of another scene's image while it reloads. Tracked; see the repo's
41+
> task notes.
42+
43+
---
44+
45+
## How it works
46+
47+
### ZIP load path
48+
49+
```
50+
Load .zip / /local route → File Blob
51+
→ vole-core OmeZarrLoader → ZipStore(blob) ← central directory indexed once
52+
.get(key) → Blob.slice per chunk (STORED) | zip.js fallback (DEFLATE)
53+
← auto-detects a nested "<name>.ome.zarr/" root prefix, applied to every key
54+
→ zarrita open(store) → multiscale render pipeline (Three.js / WebGL)
55+
```
56+
57+
`ZipStore` reads only the ZIP central directory up front, then slices each chunk
58+
on demand — so opening a multi-gigabyte archive is instant and memory stays flat.
2959

30-
When a loaded OME-Zarr contains a `tables/measurements` group (an AnnData table
31-
of per-object features, e.g. an ilastik export), the viewer reads it and shows a
32-
**Features** tab in the control panel. The tab only appears once a table is
33-
detected. It holds an interactive scatter plot that reproduces — and extends —
34-
the old Dash viewer, entirely in the browser:
60+
### Measurement-table path (optional)
3561

36-
- pick the **X**, **Y**, and **color-by** feature from dropdowns;
37-
- **click** a point or **drag a box** to select objects;
38-
- set per-feature **min/max gates** to dim out-of-range objects;
39-
- **export CSV** of the current selection, gated population, or full table.
62+
```
63+
loadMeasurements(store) ← reads tables/measurements (AnnData, Zarr v2)
64+
→ { labelIds, features, index }
65+
→ useViewerState.setMeasurements(...) ← shared selection state
66+
→ Features tab: ScatterPanel / CorrelationPanel / AnnotationPanel
67+
```
68+
69+
The analysis panels are **modular**: they only appear when a `tables/measurements`
70+
group is present, so a plain image viewer stays plain.
71+
72+
---
73+
74+
## Repo structure
75+
76+
```
77+
/
78+
├── pixi.toml pixi.lock ← pins Node.js, defines all tasks
79+
├── vole-core/ ← WebGL engine + ZIP/Zarr loaders (library)
80+
│ └── src/loaders/
81+
│ ├── OmeZarrLoader.ts ← multiscale loader entry
82+
│ └── zarr_utils/ZipStore.ts ← lazy per-chunk ZIP store (fork core)
83+
└── vole-app/ ← React app (consumes vole-core via file:../)
84+
└── src/aics-image-viewer/
85+
├── components/
86+
│ ├── App/index.tsx ← viewer root, scenes + panels wiring
87+
│ ├── useVolume.ts ← volume lifecycle, per-scene loading
88+
│ ├── ScatterPanel.tsx ← Plotly scatter + gating + CSV
89+
│ ├── CorrelationPanel.tsx ← correlation heatmap
90+
│ └── AnnotationPanel.tsx ← manual label tagging
91+
├── shared/utils/
92+
│ ├── sceneStore.ts ← one SceneStore per dataset, N scenes
93+
│ └── loadMeasurements.ts ← reads tables/measurements (AnnData)
94+
└── state/
95+
├── store.ts ← Zustand store root (channels, view)
96+
└── selection.ts ← measurements, gates, selection, labels
97+
```
4098

41-
Every object is keyed by its `label_id`, and selection/gates live in shared state
42-
— the groundwork for the planned bidirectional link between the scatter and the
43-
3D view.
99+
Load entry points: `public/LocalZipViewer.tsx` (the `/local` route) and
100+
`website/components/Modals/LoadModal.tsx` (the **Load URL** / **Load .zip** buttons).
44101

45-
The scatter works from any entry point that feeds the viewer a `.zip`: the main
46-
**Load .zip** button, or the minimal standalone picker at the `/local` route.
102+
---
47103

48-
## Quick start
104+
## Installation
49105

50-
Requires [pixi](https://pixi.sh) (it provides the pinned Node.js).
106+
Requires [pixi](https://pixi.sh) (it provides the pinned Node.js — no global Node
107+
needed).
51108

52109
```bash
53110
pixi run setup # install deps + build vole-core + link it into vole-app
54111
pixi run dev # start the dev server at http://localhost:9020
55112
```
56113

57-
Then open http://localhost:9020, click **Load .zip**, and pick a local
58-
`.ome.zarr.zip`. The first three channels are enabled by default.
114+
Then open http://localhost:9020 and click **Load .zip**.
115+
116+
> After editing anything in `vole-core/src`, run `pixi run rebuild-core` (or
117+
> `pixi run build-core`) so the app picks up the rebuilt engine — `vole-app`
118+
> consumes the **built** `vole-core/es`, not its source.
119+
120+
---
121+
122+
## Loading data & options
123+
124+
- **Load .zip** — drag-and-drop **one or more** local `.ome.zarr.zip` files.
125+
- **Load URL** — load a remote OME-Zarr by `https://`, `s3://`, or `gs://` URL.
126+
127+
With **several** files, a toggle appears:
128+
129+
| Mode | Behavior | Use when |
130+
|---|---|---|
131+
| **Separate scenes** (default) | one volume per zip, switchable via the scene picker; each scene keeps its own channel settings | the files are unrelated / different shapes |
132+
| **Overlay channels** | merge every zip's channels into a single volume | the files share identical pixel dimensions |
133+
134+
Overlay requires matching dimensions and fails clearly otherwise, which is why
135+
**Separate scenes** is the default.
136+
137+
### Feature panels
138+
139+
When a loaded zarr contains `tables/measurements`, a **Features** tab appears with:
140+
141+
- an interactive **scatter** — pick X / Y / color-by, click or box-select objects,
142+
set per-feature **min/max gates**, and **export CSV** (selection, gated, or full);
143+
- a **correlation heatmap** across features;
144+
- **manual annotation** labels.
145+
146+
Selection and gates live in shared state keyed by each object's `label_id`.
59147

60148
### Preparing a `.zip`
61149

62150
Package the `.ome.zarr` folder with **no compression** (STORE mode) — Zarr chunks
63-
are already compressed, so zip deflate would only slow reads:
151+
are already compressed, so zip deflate only slows reads:
64152

65153
```python
66154
import zipfile, os
@@ -74,16 +162,57 @@ with zipfile.ZipFile("image.ome.zarr.zip", "w", zipfile.ZIP_STORED) as zf:
74162
zf.write(full, arc)
75163
```
76164

165+
---
166+
167+
## Integrating into your own app
168+
169+
Two levels of reuse:
170+
171+
### 1. The engine (`vole-core`) — read a local zip anywhere
172+
173+
`ZipStore` + `OmeZarrLoader` work outside this app. Point the loader at one or more
174+
zip `Blob`s via the `zipSources` option:
175+
176+
```ts
177+
import { OmeZarrLoader, VolumeFileFormat } from "@aics/vole-core";
178+
179+
const loader = await context.createLoader("local.zip", {
180+
fileType: VolumeFileFormat.ZARR,
181+
zipSources: [{ data: zipBlob /*, rootPath: "name.ome.zarr" */ }],
182+
});
183+
// rootPath is auto-detected when omitted; several zipSources are overlaid as channels.
184+
```
185+
186+
### 2. The viewer (`vole-app`) — mount the `App` component
187+
188+
The React `App` component takes its data source as props, so you can embed it and
189+
feed it a local zip without the landing page:
190+
191+
```tsx
192+
import { App } from "vole-app"; // see vole-app/src/index
193+
194+
// One file, or Blob[] to overlay, or { scenes: Blob[] } for switchable scenes.
195+
<App zipData={myZipBlob} viewerChannelSettings={/* optional defaults */} />
196+
```
197+
198+
`zipData` accepts `Blob | Blob[] | { scenes: (Blob | Blob[])[] }`; pass `zipRootPath`
199+
to skip root auto-detection. For remote data, pass `imageUrl` instead. The minimal
200+
standalone example lives in `public/LocalZipViewer.tsx` (the `/local` route).
201+
202+
---
203+
77204
## Useful pixi tasks
78205

79206
| Command | What it does |
80207
|---|---|
81208
| `pixi run setup` | one-time install + build + link |
82-
| `pixi run dev` | webpack dev server (vole-app) |
83-
| `pixi run rebuild-core` | recompile `vole-core` after editing it |
209+
| `pixi run dev` | webpack dev server (vole-app) at :9020 |
210+
| `pixi run build-core` / `rebuild-core` | recompile `vole-core` after editing it |
84211
| `pixi run typecheck` | typecheck both projects |
85212

86-
## Notes
213+
---
214+
215+
## Notes & license
87216

88217
- The standalone GitHub forks ([vole-app](https://github.com/assadiab/vole-app),
89218
[vole-core](https://github.com/assadiab/vole-core)) remain as snapshots; this

0 commit comments

Comments
 (0)