|
| 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