Skip to content

Commit eaee8ba

Browse files
authored
Merge pull request #56 from h-mayorquin/add_probe_visualization_library
Add probe viewer web app
2 parents 760856b + 742cf30 commit eaee8ba

29 files changed

Lines changed: 7092 additions & 1 deletion

.github/workflows/deploy.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Deploy probe viewer to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch: # Allow manual trigger
7+
8+
# Sets permissions of the GITHUB_TOKEN
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
# Allow only one concurrent deployment
15+
concurrency:
16+
group: "pages"
17+
cancel-in-progress: true
18+
19+
jobs:
20+
build-and-deploy:
21+
runs-on: ubuntu-latest
22+
environment:
23+
name: github-pages
24+
url: ${{ steps.deployment.outputs.page_url }}
25+
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Setup Python
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: "3.13"
34+
35+
- name: Install uv
36+
uses: astral-sh/setup-uv@v4
37+
38+
- name: Setup Node.js
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: "20"
42+
cache: "npm"
43+
cache-dependency-path: apps/probe-viewer/package-lock.json
44+
45+
- name: Build probe viewer
46+
# Reads probe JSONs from this repo, generates the manifest, and runs vite build.
47+
run: uv run apps/probe-viewer/build.py
48+
49+
- name: Setup Pages
50+
uses: actions/configure-pages@v5
51+
52+
- name: Upload artifact
53+
# The built app is the entire site, so the dist directory is the artifact root.
54+
uses: actions/upload-pages-artifact@v3
55+
with:
56+
path: apps/probe-viewer/dist
57+
58+
- name: Deploy to GitHub Pages
59+
id: deployment
60+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Probe viewer generated files (created at build time)
2+
apps/probe-viewer/public/data/
3+
apps/probe-viewer/public/probes-manifest.json
4+
apps/probe-viewer/node_modules/
5+
apps/probe-viewer/dist/
6+
7+
# Build cache
8+
.cache/
9+
10+
# Python
11+
__pycache__/
12+
*.py[cod]
13+
14+
# OS / editor
15+
.DS_Store

apps/probe-viewer/README.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Probe Viewer
2+
3+
An interactive web-based visualization tool for browsing microelectrode probe designs used in neuroscience research. The probe data comes from this repository, the [probeinterface_library](https://github.com/SpikeInterface/probeinterface_library): the build reads the manufacturer folders directly, so there is no separate data source to clone.
4+
5+
## Local Development
6+
7+
### Prerequisites
8+
9+
- Node.js (v18 or later recommended)
10+
- Python 3.13+ with [uv](https://docs.astral.sh/uv/) package manager
11+
- Git
12+
13+
### Quick Start
14+
15+
1. **Generate the probe manifest and data files:**
16+
17+
From the repository root, run:
18+
19+
```bash
20+
uv run apps/probe-viewer/build.py --dev
21+
```
22+
23+
This will:
24+
- Read the probe JSON files from the manufacturer folders in this repository
25+
- Generate `public/probes-manifest.json` with metadata for all probes
26+
- Copy probe JSON files to `public/data/`
27+
- Start the Vite dev server
28+
29+
2. **Access the app:**
30+
31+
Open http://localhost:5173 in your browser.
32+
33+
### Alternative: Manual Setup
34+
35+
If you prefer to run steps separately:
36+
37+
1. **Generate the manifest only:**
38+
39+
```bash
40+
uv run apps/probe-viewer/build.py
41+
```
42+
43+
This generates the manifest without starting the dev server.
44+
45+
2. **Install npm dependencies:**
46+
47+
```bash
48+
cd apps/probe-viewer
49+
npm install
50+
```
51+
52+
3. **Start the dev server:**
53+
54+
```bash
55+
npm run dev
56+
```
57+
58+
### Available Scripts
59+
60+
From the `apps/probe-viewer` directory:
61+
62+
| Command | Description |
63+
|---------|-------------|
64+
| `npm run dev` | Start development server (requires the manifest to exist; generate it with `build.py` first) |
65+
| `npm run build` | Build the production bundle with Vite (run `build.py` first to generate the manifest and data) |
66+
| `npm run preview` | Preview production build locally |
67+
| `npm run lint` | Run ESLint |
68+
69+
### Project Structure
70+
71+
```
72+
apps/probe-viewer/
73+
├── src/
74+
│ ├── components/ # React components
75+
│ ├── services/ # Data fetching
76+
│ ├── state/ # Zustand store
77+
│ ├── types/ # TypeScript types
78+
│ └── hooks/ # Custom React hooks
79+
├── public/
80+
│ ├── probes-manifest.json # Generated probe catalog
81+
│ └── data/ # Generated probe JSON files
82+
└── index.html
83+
```
84+
85+
## Technology Stack
86+
87+
| Technology | Purpose |
88+
|------------|---------|
89+
| **React 19** | UI component framework |
90+
| **TypeScript** | Type-safe JavaScript |
91+
| **Vite** | Build tool and dev server - fast HMR, optimized production builds |
92+
| **Zustand** | Lightweight state management (probe cache, UI state, selections) |
93+
| **React Router** | Client-side routing for shareable URLs like `/#/probes/imec/NP1000` |
94+
| **HTML5 Canvas** | Rendering probe geometries (see below for why not SVG) |
95+
96+
### Deployment
97+
98+
The app is deployed to GitHub Pages via GitHub Actions (`.github/workflows/deploy.yml`). On every push to `main`:
99+
1. The workflow runs `apps/probe-viewer/build.py`, which reads the probe JSONs from this repository and generates the manifest
100+
2. Vite builds the production bundle into `apps/probe-viewer/dist/`
101+
3. That `dist/` directory is published as the GitHub Pages site
102+
103+
The site is served from `/probeinterface_library/` (the Vite `base` in `vite.config.ts`), matching the project-page URL `https://spikeinterface.github.io/probeinterface_library/`.
104+
105+
### Hash Routing vs Browser Routing
106+
107+
This app uses **hash-based routing** (`/#/probes/imec/NP1000`) instead of browser routing (`/probes/imec/NP1000`). Here's why:
108+
109+
**The problem with browser routing on GitHub Pages:**
110+
111+
GitHub Pages is a static file server. When you request `/probeinterface_library/probes/imec/NP1000`:
112+
1. GitHub looks for a file at that exact path
113+
2. No such file exists (there's only `index.html`)
114+
3. GitHub returns 404
115+
4. React never loads, so React Router never gets a chance to handle the route
116+
117+
This means direct links and page refresh would break.
118+
119+
**How hash routing solves this:**
120+
121+
The `#` fragment is never sent to the server. When you request `/#/probes/imec/NP1000`:
122+
1. Browser requests `/` from GitHub
123+
2. GitHub returns `index.html`
124+
3. React loads, reads the hash (`#/probes/imec/NP1000`)
125+
4. React Router renders the correct page
126+
127+
Direct links and refresh work perfectly.
128+
129+
**Trade-offs:**
130+
131+
| Aspect | Browser Routing | Hash Routing |
132+
|--------|-----------------|--------------|
133+
| URLs | `/probes/imec/NP1000` | `/#/probes/imec/NP1000` |
134+
| GitHub Pages | Needs workarounds | Works natively |
135+
| SEO | Better | Worse (fragments ignored) |
136+
| Server-side rendering | Compatible | Not compatible |
137+
138+
For a client-side visualization tool like this, hash routing is the pragmatic choice - the downsides (SEO, SSR) don't apply.
139+
140+
## Why Canvas Instead of SVG?
141+
142+
This application uses the HTML5 Canvas 2D API rather than SVG for rendering probe geometries. Both technologies could work for many probes in this catalog, but Canvas was chosen with scalability in mind.
143+
144+
### The Trade-off
145+
146+
SVG and Canvas represent two fundamentally different rendering architectures:
147+
148+
- **SVG (Retained Mode)**: Each element exists as a DOM node. The browser maintains a scene graph with positions, styles, event listeners, and relationships. This makes interaction easy but creates overhead that grows with element count.
149+
150+
- **Canvas (Immediate Mode)**: Drawing commands paint pixels to a bitmap buffer and are then forgotten. No state is retained. This requires more developer effort but eliminates DOM overhead entirely.
151+
152+
### The Neuropixels Challenge
153+
154+
While many probes in this catalog have modest contact counts (32-128 electrodes), Neuropixels probes push into territory where SVG performance becomes problematic:
155+
156+
| Probe Type | Electrodes | Recording Sites to Visualize |
157+
|------------|------------|------------------------------|
158+
| Cambridge Neurotech | 32-256 | SVG handles well |
159+
| Neuronexus | 16-128 | SVG handles well |
160+
| **Neuropixels 1.0** | **960** | Borderline for SVG |
161+
| **Neuropixels 2.0 (single shank)** | **1,280** | Problematic for SVG |
162+
| **Neuropixels 2.0 (4-shank)** | **5,120** | SVG would struggle significantly |
163+
164+
A 4-shank Neuropixels 2.0 probe has 5,120 recording sites arranged across a ~1 x 10 mm plane. Rendering this many elements as SVG DOM nodes, especially with pan/zoom interactions triggering redraws, would cause noticeable lag on many devices.
165+
166+
### SVG Performance Thresholds
167+
168+
Based on benchmarks from Khan Academy, Felt, and the D3.js community:
169+
170+
| Element Count | SVG Performance |
171+
|---------------|-----------------|
172+
| < 500 | Excellent |
173+
| 500-1000 | Good on desktop, may stutter on mobile |
174+
| 1000-2000 | Noticeable lag during animations |
175+
| 2000-5000 | Poor experience, especially on tablets |
176+
| 5000+ | Unacceptable without virtualization |
177+
178+
Canvas maintains near-constant performance regardless of element count since it only manipulates pixels in a bitmap buffer.
179+
180+
### Why Canvas Fits This Application
181+
182+
1. **Scales to high-density probes**: Neuropixels 2.0 with 5,120 electrodes renders as smoothly as a 32-channel probe.
183+
184+
2. **Predictable pan/zoom performance**: Every interaction redraws all contacts. Canvas makes this explicit rather than relying on browser SVG transform optimizations (which vary significantly across browsers and devices).
185+
186+
3. **Mobile-friendly**: Tablets and phones are common in lab settings. Canvas avoids the SVG performance cliff on resource-constrained devices.
187+
188+
4. **No per-element interaction needed**: This viewer displays probe geometry without requiring click/hover on individual contacts. SVG's main advantage (built-in DOM events per element) goes unused.
189+
190+
### When SVG Would Be Better
191+
192+
SVG would be preferable if the application needed:
193+
- Per-contact selection, tooltips, or click handlers
194+
- CSS-based hover effects and transitions
195+
- Accessibility through per-element ARIA labels
196+
- Integration with React's declarative component model
197+
198+
For a probe catalog viewer focused on displaying geometry with pan/zoom, Canvas is the pragmatic choice that ensures consistent performance across the full range of probe densities.

0 commit comments

Comments
 (0)