napari-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/cache/lru.d.ts +19 -0
- package/dist/camera/camera.d.ts +25 -0
- package/dist/camera/camera3d.d.ts +28 -0
- package/dist/camera/controls.d.ts +7 -0
- package/dist/camera/controls3d.d.ts +6 -0
- package/dist/color/checkerboard.d.ts +5 -0
- package/dist/color/colormap.d.ts +26 -0
- package/dist/color/display-pipeline.d.ts +22 -0
- package/dist/color/histogram.d.ts +14 -0
- package/dist/color/label-colormap.d.ts +7 -0
- package/dist/color/lut.d.ts +9 -0
- package/dist/engine/canvas.d.ts +20 -0
- package/dist/engine/device.d.ts +23 -0
- package/dist/engine/readback.d.ts +13 -0
- package/dist/engine/renderer.d.ts +42 -0
- package/dist/engine/viewport.d.ts +12 -0
- package/dist/index.d.ts +34 -0
- package/dist/io/pyramid.d.ts +41 -0
- package/dist/io/texture-source.d.ts +68 -0
- package/dist/layers/image-layer.d.ts +47 -0
- package/dist/layers/labels-layer.d.ts +32 -0
- package/dist/layers/layer.d.ts +32 -0
- package/dist/layers/points-layer.d.ts +59 -0
- package/dist/layers/volume-layer.d.ts +46 -0
- package/dist/math/mat4.d.ts +22 -0
- package/dist/napari-js.js +1986 -0
- package/dist/napari-js.js.map +1 -0
- package/dist/picking/pick.d.ts +6 -0
- package/dist/scene/dims.d.ts +20 -0
- package/dist/scene/events.d.ts +9 -0
- package/dist/scene/layer-list.d.ts +16 -0
- package/dist/scene/viewer-model.d.ts +20 -0
- package/dist/version.d.ts +1 -0
- package/dist/viewer.d.ts +76 -0
- package/dist/visuals/blend.d.ts +6 -0
- package/dist/visuals/format-plan.d.ts +19 -0
- package/dist/visuals/image-colormap-shader.d.ts +1 -0
- package/dist/visuals/image-visual.d.ts +45 -0
- package/dist/visuals/labels-shader.d.ts +1 -0
- package/dist/visuals/labels-visual.d.ts +23 -0
- package/dist/visuals/layer-visual.d.ts +23 -0
- package/dist/visuals/points-shader.d.ts +1 -0
- package/dist/visuals/points-visual.d.ts +22 -0
- package/dist/visuals/tiled-image-visual.d.ts +46 -0
- package/dist/visuals/volume-shader.d.ts +1 -0
- package/dist/visuals/volume-visual.d.ts +32 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Baha Elkassaby
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# napari-js
|
|
2
|
+
|
|
3
|
+
A browser-native, **WebGPU** rendering engine that ports the visualization model of
|
|
4
|
+
[napari](https://napari.org) (the Python multi-dimensional image viewer) to TypeScript.
|
|
5
|
+
|
|
6
|
+
> **Status:** **NJ-5+ complete — Phase B roadmap done.** Added volume rendering:
|
|
7
|
+
> `viewer.addVolume()` fragment-raymarches a 3D texture (MIP / translucent DVR / iso-surface
|
|
8
|
+
> with gradient shading) via a fullscreen quad + ray-box intersection in volume space; a
|
|
9
|
+
> `Camera3D` orbit camera and `dims.ndisplay = 3` mode drive it (visuals are routed by
|
|
10
|
+
> dimensionality). Full 3D matrix math (`perspective`/`lookAt`/`invert`) is unit-tested.
|
|
11
|
+
> napari-js now spans NJ-0…NJ-5+: images (single/multi-channel/16-bit), tiled+z-stacks,
|
|
12
|
+
> points, labels, volume, plus readback/screenshot/histogram. All gates green (typecheck /
|
|
13
|
+
> lint / 83 unit tests / build). **Remaining:** `npm publish` (registry auth) and — when you
|
|
14
|
+
> choose — Phase C, the `jit-ui` `IVisualizer` adapter ([docs/06](./docs/06-jit-ui-integration.md)).
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev # open the printed URL in a WebGPU browser → see the demo quad
|
|
21
|
+
npm test # GPU-free unit tests (Vitest)
|
|
22
|
+
npm run typecheck # tsc --noEmit
|
|
23
|
+
npm run lint # eslint
|
|
24
|
+
npm run build # library bundle + types → dist/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`npm run dev` serves `index.html` → `playground/main.ts`, which creates a `Viewer`, awaits
|
|
28
|
+
its WebGPU device, and renders the NJ-0 demo. If WebGPU is unavailable the page shows the
|
|
29
|
+
reason instead of crashing.
|
|
30
|
+
|
|
31
|
+
## What this is
|
|
32
|
+
|
|
33
|
+
napari-js is a **standalone, framework-agnostic** library. It renders large
|
|
34
|
+
multi-dimensional / multi-channel scientific images in the browser on the GPU, with
|
|
35
|
+
napari's model: a `Viewer` holding a list of `Layer`s (Image, later Points / Labels /
|
|
36
|
+
Volume), each with its own colormap (LUT), contrast limits, gamma, opacity, and blending.
|
|
37
|
+
|
|
38
|
+
It is **not** a port of napari's Python code or its Qt GUI. It is a faithful port of
|
|
39
|
+
napari's _rendering concepts_ — the layer→visual model, per-layer GPU colormapping,
|
|
40
|
+
serializable transforms and camera — onto WebGPU and WGSL. See
|
|
41
|
+
[`docs/07-napari-concept-mapping.md`](./docs/07-napari-concept-mapping.md).
|
|
42
|
+
|
|
43
|
+
## Why
|
|
44
|
+
|
|
45
|
+
WebGPU now ships in all major browsers (late 2025). napari's strengths — GPU
|
|
46
|
+
multi-channel fluorescence compositing, live scalar colormapping, and volume rendering —
|
|
47
|
+
are exactly the things current browser image viewers do poorly or on the CPU. napari-js
|
|
48
|
+
brings those strengths to the web as a reusable npm package.
|
|
49
|
+
|
|
50
|
+
## Where it fits
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
~/git/napari Reference: the Python renderer being ported (napari/_vispy, layers, components)
|
|
54
|
+
~/git/napari-js THIS repo: the standalone TS + WebGPU port, published to npm
|
|
55
|
+
~/git/jit-ui Eventual consumer: jax-image-visualization adds a napari-js IVisualizer backend
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The first downstream consumer will be the `jax-image-visualization` library in the
|
|
59
|
+
`jit-ui` monorepo, which will add napari-js as a new `IVisualizer` backend alongside its
|
|
60
|
+
OpenSeadragon and Plotly backends. **That integration is a later phase** (see
|
|
61
|
+
[`docs/06-jit-ui-integration.md`](./docs/06-jit-ui-integration.md)); napari-js is built
|
|
62
|
+
and published independently first.
|
|
63
|
+
|
|
64
|
+
## Docs
|
|
65
|
+
|
|
66
|
+
| Doc | Contents |
|
|
67
|
+
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
|
68
|
+
| [00 — Feasibility](./docs/00-feasibility.md) | Why this is feasible: napari architecture findings, what ports cleanly, what's hard |
|
|
69
|
+
| [01 — Architecture](./docs/01-architecture.md) | Engine module layout, render loop, design principles |
|
|
70
|
+
| [02 — Public API](./docs/02-public-api.md) | The `Viewer` / `Layer` / `Colormap` / `TextureSource` API surface |
|
|
71
|
+
| [03 — RenderState IR](./docs/03-render-state-ir.md) | The serializable intermediate representation between model and GPU |
|
|
72
|
+
| [04 — WGSL rendering plan](./docs/04-wgsl-rendering-plan.md) | Shader pipelines: image+colormap, multi-channel compositing, future raycasting |
|
|
73
|
+
| [05 — Roadmap](./docs/05-roadmap.md) | Milestones NJ-0 … NJ-5+ |
|
|
74
|
+
| [06 — jit-ui integration](./docs/06-jit-ui-integration.md) | Phase C: the `IVisualizer` adapter in jax-image-visualization (deferred) |
|
|
75
|
+
| [07 — napari concept mapping](./docs/07-napari-concept-mapping.md) | How each napari concept maps to napari-js |
|
|
76
|
+
|
|
77
|
+
## Acknowledgments
|
|
78
|
+
|
|
79
|
+
Built with the help of Claude Opus 4.8 (Anthropic).
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
[MIT](./LICENSE) © Baha Elkassaby
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded least-recently-used cache. Insertion/`get` mark an entry most-recently-used; when
|
|
3
|
+
* size exceeds `capacity` the oldest entries are evicted via `onEvict` (used to destroy GPU
|
|
4
|
+
* textures). Relies on `Map` preserving insertion order.
|
|
5
|
+
*/
|
|
6
|
+
export declare class LruCache<V> {
|
|
7
|
+
private readonly capacity;
|
|
8
|
+
private readonly onEvict?;
|
|
9
|
+
private readonly map;
|
|
10
|
+
constructor(capacity: number, onEvict?: ((value: V, key: string) => void) | undefined);
|
|
11
|
+
get size(): number;
|
|
12
|
+
has(key: string): boolean;
|
|
13
|
+
/** Get a value and mark it most-recently-used. */
|
|
14
|
+
get(key: string): V | undefined;
|
|
15
|
+
/** Insert/update a value (most-recently-used), evicting the oldest beyond capacity. */
|
|
16
|
+
set(key: string, value: V): void;
|
|
17
|
+
delete(key: string): boolean;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Emitter } from '../scene/events';
|
|
2
|
+
import { Mat4 } from '../math/mat4';
|
|
3
|
+
/**
|
|
4
|
+
* 2D orthographic pan/zoom camera. `center` is in world coordinates; `zoom` is canvas pixels
|
|
5
|
+
* per world unit. Emits {@link changed} on any mutation so the viewer can schedule a redraw.
|
|
6
|
+
* The 3D arcball camera arrives in NJ-5+.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Camera {
|
|
9
|
+
readonly changed: Emitter<Camera>;
|
|
10
|
+
private _center;
|
|
11
|
+
private _zoom;
|
|
12
|
+
get center(): [number, number];
|
|
13
|
+
set center(value: readonly [number, number]);
|
|
14
|
+
get zoom(): number;
|
|
15
|
+
set zoom(value: number);
|
|
16
|
+
/** Set center + zoom in one mutation (single change event). */
|
|
17
|
+
set(center: readonly [number, number], zoom: number): void;
|
|
18
|
+
/** World→clip view-projection matrix for a `vw`×`vh` viewport. */
|
|
19
|
+
viewProjection(vw: number, vh: number): Mat4;
|
|
20
|
+
/**
|
|
21
|
+
* Frame a `width`×`height` region centered in a `vw`×`vh` viewport (with a little margin),
|
|
22
|
+
* e.g. to fit a freshly loaded image. No-op for degenerate inputs.
|
|
23
|
+
*/
|
|
24
|
+
fit(width: number, height: number, vw: number, vh: number, margin?: number): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Emitter } from '../scene/events';
|
|
2
|
+
import { Mat4, Vec3 } from '../math/mat4';
|
|
3
|
+
/**
|
|
4
|
+
* Orbit camera for 3D (volume) rendering: spins around `target` at `distance`, parameterized
|
|
5
|
+
* by `azimuth`/`elevation`. Produces a perspective view-projection. Emits {@link changed} on
|
|
6
|
+
* mutation. Used when `dims.ndisplay === 3`.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Camera3D {
|
|
9
|
+
readonly changed: Emitter<Camera3D>;
|
|
10
|
+
azimuth: number;
|
|
11
|
+
elevation: number;
|
|
12
|
+
fov: number;
|
|
13
|
+
private _distance;
|
|
14
|
+
private _target;
|
|
15
|
+
get distance(): number;
|
|
16
|
+
set distance(value: number);
|
|
17
|
+
get target(): [number, number, number];
|
|
18
|
+
set target(value: Vec3);
|
|
19
|
+
/** Rotate the orbit by deltas (radians); elevation is clamped to avoid gimbal flip. */
|
|
20
|
+
orbit(dAzimuth: number, dElevation: number): void;
|
|
21
|
+
zoomBy(factor: number): void;
|
|
22
|
+
/** Eye position in world space. */
|
|
23
|
+
eye(): [number, number, number];
|
|
24
|
+
/** Perspective view-projection for a `vw`×`vh` viewport. */
|
|
25
|
+
viewProjection(vw: number, vh: number): Mat4;
|
|
26
|
+
/** Frame a `[w,h,d]` volume centered at the origin. */
|
|
27
|
+
frame(w: number, h: number, d: number): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Camera } from './camera';
|
|
2
|
+
/**
|
|
3
|
+
* Attach pointer-drag panning and wheel zoom (zoom about the cursor) to a canvas. Returns a
|
|
4
|
+
* detach function. World/screen conversion uses the camera's current center+zoom and the
|
|
5
|
+
* canvas's CSS size (Y flipped to match {@link ortho2d}).
|
|
6
|
+
*/
|
|
7
|
+
export declare function attachCameraControls(canvas: HTMLCanvasElement, camera: Camera): () => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Camera3D } from './camera3d';
|
|
2
|
+
/**
|
|
3
|
+
* Attach orbit controls to a canvas: drag rotates (azimuth/elevation), wheel dollies in/out.
|
|
4
|
+
* Returns a detach function.
|
|
5
|
+
*/
|
|
6
|
+
export declare function attachOrbitControls(canvas: HTMLCanvasElement, camera: Camera3D): () => void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate an `size`×`size` RGBA8 checkerboard with `cells` squares per side. Used by the
|
|
3
|
+
* NJ-0 demo to prove texture upload + sampling. Pure and GPU-free (unit-tested).
|
|
4
|
+
*/
|
|
5
|
+
export declare function makeCheckerboard(size: number, cells?: number): Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type RGB = [number, number, number];
|
|
2
|
+
/** A control point in a colormap: normalized position `t` (0..1) → linear RGB (0..1). */
|
|
3
|
+
export interface ColorStop {
|
|
4
|
+
t: number;
|
|
5
|
+
color: RGB;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A colormap defined by sorted control points, linearly interpolated. Mirrors napari's
|
|
9
|
+
* `Colormap` concept; sampled into a LUT texture for the GPU (see ./lut.ts).
|
|
10
|
+
*/
|
|
11
|
+
export declare class Colormap {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly stops: ColorStop[];
|
|
14
|
+
constructor(name: string, stops: ColorStop[]);
|
|
15
|
+
/** Sample the colormap at `t` (clamped to 0..1), returning linear RGB. */
|
|
16
|
+
sample(t: number): RGB;
|
|
17
|
+
}
|
|
18
|
+
export declare const GRAY: Colormap;
|
|
19
|
+
export declare const RED: Colormap;
|
|
20
|
+
export declare const GREEN: Colormap;
|
|
21
|
+
export declare const BLUE: Colormap;
|
|
22
|
+
export declare const VIRIDIS: Colormap;
|
|
23
|
+
export declare const MAGMA: Colormap;
|
|
24
|
+
export declare const NAMED_COLORMAPS: Record<string, Colormap>;
|
|
25
|
+
/** Resolve a colormap name or pass through a `Colormap`. Throws on an unknown name. */
|
|
26
|
+
export declare function resolveColormap(cmap: Colormap | string): Colormap;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Colormap, RGB } from './colormap';
|
|
2
|
+
/**
|
|
3
|
+
* CPU reference for the scalar display math the `image-colormap` WGSL shader performs:
|
|
4
|
+
* window → invert → gamma. Returns the normalized LUT coordinate `t` in 0..1. Kept pure and
|
|
5
|
+
* tested so the shader has a ground truth (see docs/04) and so histograms/readback can reuse
|
|
6
|
+
* the exact same math. `value` and the clim are in the same units.
|
|
7
|
+
*/
|
|
8
|
+
export declare function windowGamma(value: number, climLo: number, climHi: number, gamma: number, invert: boolean): number;
|
|
9
|
+
/** Map a scalar value through window/gamma and a colormap to linear RGB. */
|
|
10
|
+
export declare function mapScalar(value: number, opts: {
|
|
11
|
+
climLo: number;
|
|
12
|
+
climHi: number;
|
|
13
|
+
gamma: number;
|
|
14
|
+
invert: boolean;
|
|
15
|
+
colormap: Colormap;
|
|
16
|
+
}): RGB;
|
|
17
|
+
/**
|
|
18
|
+
* Additive composite of premultiplied RGB contributions (channels with `blending: 'additive'`
|
|
19
|
+
* over a black background), clamped to 1 — the CPU reference for multi-channel fluorescence.
|
|
20
|
+
*/
|
|
21
|
+
export declare function additiveComposite(colors: readonly RGB[]): RGB;
|
|
22
|
+
export declare function clamp01(x: number): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A binned intensity histogram over a value range. */
|
|
2
|
+
export interface Histogram {
|
|
3
|
+
counts: Uint32Array;
|
|
4
|
+
bins: number;
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
}
|
|
8
|
+
/** Rec.601 luma of an 8-bit RGB triple (0..255). */
|
|
9
|
+
export declare function luminance8(r: number, g: number, b: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* Histogram of per-pixel luminance over RGBA8 data (alpha ignored), `bins` bins across the
|
|
12
|
+
* 0..255 range. Pure and GPU-free (unit-tested); the viewer feeds it readback pixels.
|
|
13
|
+
*/
|
|
14
|
+
export declare function histogramRGBA(data: Uint8ClampedArray | Uint8Array, bins: number): Histogram;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a cyclic label color LUT: an `size`×1 RGBA8 table indexed by `labelId % size`. Entry
|
|
3
|
+
* 0 is fully transparent (background); the rest are distinct hues spaced by the golden ratio
|
|
4
|
+
* so adjacent ids contrast. Pure and GPU-free (unit-tested). Mirrors napari's cyclic label
|
|
5
|
+
* colormap.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildLabelLut(size?: number): Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Colormap } from './colormap';
|
|
2
|
+
/** Default LUT resolution: 256 entries, the napari/OpenGL convention. */
|
|
3
|
+
export declare const LUT_SIZE = 256;
|
|
4
|
+
/**
|
|
5
|
+
* Build an `size`×1 RGBA8 lookup table from a colormap by sampling at evenly spaced
|
|
6
|
+
* positions. Suitable for upload as a `rgba8unorm` texture sampled by the fragment shader.
|
|
7
|
+
* Pure and GPU-free (unit-tested).
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildLut(colormap: Colormap, size?: number): Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns a canvas's WebGPU context: format negotiation, configuration, and DPR-aware sizing.
|
|
3
|
+
* The render loop reads {@link view} each frame for the current swapchain texture.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CanvasTarget {
|
|
6
|
+
readonly canvas: HTMLCanvasElement;
|
|
7
|
+
readonly device: GPUDevice;
|
|
8
|
+
readonly context: GPUCanvasContext;
|
|
9
|
+
readonly format: GPUTextureFormat;
|
|
10
|
+
constructor(canvas: HTMLCanvasElement, device: GPUDevice, format?: GPUTextureFormat);
|
|
11
|
+
/** (Re)configure the swapchain for the current device/format. */
|
|
12
|
+
configure(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Resize the backing buffer to match the canvas's CSS size × DPR, clamped to the device's
|
|
15
|
+
* max texture dimension. Returns `true` when the size actually changed.
|
|
16
|
+
*/
|
|
17
|
+
syncSize(dpr?: number): boolean;
|
|
18
|
+
/** The current swapchain texture view for this frame. */
|
|
19
|
+
get view(): GPUTextureView;
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** GPU features napari-js opts into when the adapter supports them. */
|
|
2
|
+
export interface DeviceFeatures {
|
|
3
|
+
/** Linear filtering of `r32float` textures (else 16-bit/float layers fall back to nearest). */
|
|
4
|
+
float32Filterable: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** A live WebGPU adapter + device pair plus the negotiated optional features. */
|
|
7
|
+
export interface DeviceContext {
|
|
8
|
+
adapter: GPUAdapter;
|
|
9
|
+
device: GPUDevice;
|
|
10
|
+
features: DeviceFeatures;
|
|
11
|
+
}
|
|
12
|
+
/** Thrown when WebGPU is unavailable or no adapter/device can be obtained. */
|
|
13
|
+
export declare class WebGPUUnsupportedError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Acquire a WebGPU device, opting into `float32-filterable` when available. Throws
|
|
18
|
+
* {@link WebGPUUnsupportedError} with an actionable message when the environment lacks
|
|
19
|
+
* `navigator.gpu`, has no suitable adapter, or device creation fails.
|
|
20
|
+
*/
|
|
21
|
+
export declare function acquireDevice(options?: {
|
|
22
|
+
powerPreference?: GPUPowerPreference;
|
|
23
|
+
}): Promise<DeviceContext>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Composited pixels read back from the GPU (RGBA8, top row first). */
|
|
2
|
+
export interface PixelData {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
channels: number;
|
|
6
|
+
data: Uint8ClampedArray;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Copy an `rgba8unorm` texture to the CPU as tightly-packed RGBA bytes. Handles WebGPU's
|
|
10
|
+
* 256-byte `bytesPerRow` alignment by unpadding each row. The texture must have been created
|
|
11
|
+
* with `COPY_SRC` usage.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readTextureToRGBA(device: GPUDevice, texture: GPUTexture, width: number, height: number): Promise<Uint8ClampedArray>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CanvasTarget } from './canvas';
|
|
2
|
+
import { Camera } from '../camera/camera';
|
|
3
|
+
import { Camera3D } from '../camera/camera3d';
|
|
4
|
+
import { Layer } from '../layers/layer';
|
|
5
|
+
/** Camera/dims inputs the viewer hands to a render call (viewport size is filled internally). */
|
|
6
|
+
export interface RenderInputs {
|
|
7
|
+
camera2d: Camera;
|
|
8
|
+
camera3d: Camera3D;
|
|
9
|
+
ndisplay: 2 | 3;
|
|
10
|
+
z: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RendererOptions {
|
|
13
|
+
float32Filterable: boolean;
|
|
14
|
+
/** Called when an async tile upload completes, so the host can schedule a redraw. */
|
|
15
|
+
onNeedsRedraw: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Scene renderer: owns one {@link LayerVisual} per layer (a single-image or tiled visual,
|
|
19
|
+
* chosen by source kind) and draws them in order each frame. Layer lifecycle is driven by
|
|
20
|
+
* the {@link Viewer}; this class only uploads, syncs, and draws.
|
|
21
|
+
*/
|
|
22
|
+
export declare class Renderer {
|
|
23
|
+
private readonly device;
|
|
24
|
+
private readonly target;
|
|
25
|
+
private readonly options;
|
|
26
|
+
private readonly visuals;
|
|
27
|
+
constructor(device: GPUDevice, target: CanvasTarget, options?: RendererOptions);
|
|
28
|
+
addLayer(layer: Layer): void;
|
|
29
|
+
private createVisual;
|
|
30
|
+
removeLayer(id: string): void;
|
|
31
|
+
has(id: string): boolean;
|
|
32
|
+
/** Draw the given layers (in order) into the swapchain for the current cameras/dims. */
|
|
33
|
+
render(inputs: RenderInputs, layers: readonly Layer[], background?: GPUColor): void;
|
|
34
|
+
/**
|
|
35
|
+
* Draw into an arbitrary color-attachment view. `vw`/`vh` are the CSS-pixel projection size
|
|
36
|
+
* (resolution-independent); the attachment may be a different device-pixel size. Only
|
|
37
|
+
* visuals whose `ndisplay` matches `inputs.ndisplay` are drawn. Used for both the swapchain
|
|
38
|
+
* and offscreen readback.
|
|
39
|
+
*/
|
|
40
|
+
renderInto(view: GPUTextureView, inputs: RenderInputs, layers: readonly Layer[], vw: number, vh: number, background?: GPUColor): void;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the backing drawing-buffer size from a canvas's CSS size and the device pixel
|
|
3
|
+
* ratio, clamped to the GPU's maximum 2D texture dimension. Pure and GPU-free so it can be
|
|
4
|
+
* unit-tested directly.
|
|
5
|
+
*
|
|
6
|
+
* - Non-positive `devicePixelRatio` falls back to 1.
|
|
7
|
+
* - Each dimension is clamped to `[1, maxDimension]` and rounded to an integer.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveDrawingBufferSize(cssWidth: number, cssHeight: number, devicePixelRatio: number, maxDimension: number): {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { Viewer } from './viewer';
|
|
2
|
+
export type { ViewerOptions } from './viewer';
|
|
3
|
+
export { acquireDevice, WebGPUUnsupportedError } from './engine/device';
|
|
4
|
+
export type { DeviceContext, DeviceFeatures } from './engine/device';
|
|
5
|
+
export type { PixelData } from './engine/readback';
|
|
6
|
+
export { histogramRGBA, luminance8 } from './color/histogram';
|
|
7
|
+
export type { Histogram } from './color/histogram';
|
|
8
|
+
export { ViewerModel } from './scene/viewer-model';
|
|
9
|
+
export { LayerList } from './scene/layer-list';
|
|
10
|
+
export { Dims } from './scene/dims';
|
|
11
|
+
export { Camera } from './camera/camera';
|
|
12
|
+
export { Camera3D } from './camera/camera3d';
|
|
13
|
+
export { Layer } from './layers/layer';
|
|
14
|
+
export type { BlendMode } from './layers/layer';
|
|
15
|
+
export { ImageLayer } from './layers/image-layer';
|
|
16
|
+
export type { ImageLayerOptions, Interpolation } from './layers/image-layer';
|
|
17
|
+
export { PointsLayer } from './layers/points-layer';
|
|
18
|
+
export type { PointsLayerOptions, PointSymbol, RGBA } from './layers/points-layer';
|
|
19
|
+
export { LabelsLayer } from './layers/labels-layer';
|
|
20
|
+
export type { LabelsLayerOptions } from './layers/labels-layer';
|
|
21
|
+
export { VolumeLayer } from './layers/volume-layer';
|
|
22
|
+
export type { VolumeLayerOptions, VolumeRendering } from './layers/volume-layer';
|
|
23
|
+
export { nearestPointIndex } from './picking/pick';
|
|
24
|
+
export { Colormap, resolveColormap, NAMED_COLORMAPS, GRAY, RED, GREEN, BLUE, VIRIDIS, MAGMA, } from './color/colormap';
|
|
25
|
+
export type { RGB, ColorStop } from './color/colormap';
|
|
26
|
+
export { buildLut, LUT_SIZE } from './color/lut';
|
|
27
|
+
export { buildLabelLut } from './color/label-colormap';
|
|
28
|
+
export { windowGamma, mapScalar, additiveComposite } from './color/display-pipeline';
|
|
29
|
+
export { toTextureSource, defaultContrastLimits, isGrayscale, channelsOf, depthOf, } from './io/texture-source';
|
|
30
|
+
export type { TextureSource, TypedImageSource, ExternalImageSource, TiledSource, TileKey, PixelChunk, ImageInput, PixelDtype, } from './io/texture-source';
|
|
31
|
+
export { selectLevel, levelScale, levelDims, tileGrid, visibleTiles, worldViewport, } from './io/pyramid';
|
|
32
|
+
export type { Rect, VisibleTile } from './io/pyramid';
|
|
33
|
+
export { LruCache } from './cache/lru';
|
|
34
|
+
export { VERSION } from './version';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** An axis-aligned rectangle in level-0 (full-resolution) data coordinates. */
|
|
2
|
+
export interface Rect {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
/** A visible tile plus its rect in level-0 data coordinates (so the camera is uniform). */
|
|
9
|
+
export interface VisibleTile {
|
|
10
|
+
col: number;
|
|
11
|
+
row: number;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
w: number;
|
|
15
|
+
h: number;
|
|
16
|
+
}
|
|
17
|
+
/** Downsample factor of a pyramid level (level 0 = 1, level 1 = 2, …). */
|
|
18
|
+
export declare function levelScale(level: number): number;
|
|
19
|
+
/** Pixel dimensions of a pyramid level. */
|
|
20
|
+
export declare function levelDims(width: number, height: number, level: number): {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
/** Tile grid (cols × rows) at a level. */
|
|
25
|
+
export declare function tileGrid(width: number, height: number, level: number, tileSize: number): {
|
|
26
|
+
cols: number;
|
|
27
|
+
rows: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Choose the pyramid level whose texels are ≈1 screen pixel for `zoom` (CSS px per level-0
|
|
31
|
+
* unit). Zoomed in (zoom ≥ 1) → level 0; each halving of zoom steps one level coarser.
|
|
32
|
+
* Clamped to `[0, levels-1]`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function selectLevel(zoom: number, levels: number): number;
|
|
35
|
+
/** The level-0 data rectangle currently visible for a camera and CSS viewport size. */
|
|
36
|
+
export declare function worldViewport(centerX: number, centerY: number, zoom: number, vw: number, vh: number): Rect;
|
|
37
|
+
/**
|
|
38
|
+
* Tiles of `level` that overlap `view` (level-0 coords), with each tile's rect in level-0
|
|
39
|
+
* coords (edge tiles clipped to the image bounds). Empty when the view misses the image.
|
|
40
|
+
*/
|
|
41
|
+
export declare function visibleTiles(view: Rect, width: number, height: number, level: number, tileSize: number): VisibleTile[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel ingestion for NJ-1: a single full-resolution image (no tiling — that arrives in
|
|
3
|
+
* NJ-3 as a pyramidal `TiledSource`). A source is either raw typed-array pixels or an
|
|
4
|
+
* external decoded image (ImageBitmap/canvas) uploaded via `copyExternalImageToTexture`.
|
|
5
|
+
*/
|
|
6
|
+
export type PixelDtype = 'uint8' | 'uint16' | 'float32';
|
|
7
|
+
/**
|
|
8
|
+
* Raw scalar/RGBA pixels in a typed array. `uint8` → `r8unorm`/`rgba8unorm`;
|
|
9
|
+
* `uint16`/`float32` scalar → `r32float` (native-precision windowing). RGBA is `uint8` only.
|
|
10
|
+
*/
|
|
11
|
+
export interface TypedImageSource {
|
|
12
|
+
readonly kind: 'typed';
|
|
13
|
+
readonly width: number;
|
|
14
|
+
readonly height: number;
|
|
15
|
+
readonly channels: 1 | 4;
|
|
16
|
+
readonly dtype: PixelDtype;
|
|
17
|
+
readonly data: Uint8Array | Uint16Array | Float32Array;
|
|
18
|
+
}
|
|
19
|
+
/** A decoded image to upload directly to an RGBA8 texture. */
|
|
20
|
+
export interface ExternalImageSource {
|
|
21
|
+
readonly kind: 'external';
|
|
22
|
+
readonly width: number;
|
|
23
|
+
readonly height: number;
|
|
24
|
+
readonly image: ImageBitmap | HTMLCanvasElement | OffscreenCanvas | HTMLImageElement;
|
|
25
|
+
}
|
|
26
|
+
/** Identifies one tile within a pyramidal/tiled source. */
|
|
27
|
+
export interface TileKey {
|
|
28
|
+
/** Pyramid level (0 = full resolution; each level halves resolution). */
|
|
29
|
+
level: number;
|
|
30
|
+
col: number;
|
|
31
|
+
row: number;
|
|
32
|
+
/** Z-slice (0 when not a stack). */
|
|
33
|
+
z: number;
|
|
34
|
+
}
|
|
35
|
+
/** Pixels for one tile. Edge tiles may be smaller than the nominal `tileSize`. */
|
|
36
|
+
export interface PixelChunk {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
data: Uint8Array | Uint16Array | Float32Array;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A pyramidal, tiled, optionally z-stacked image — the general large-image case (whole-slide
|
|
43
|
+
* microscopy etc.). `width`/`height` are full-resolution (level 0). Tiles are fetched lazily
|
|
44
|
+
* via {@link fetchTile} and cached on the GPU. The host supplies `fetchTile` (e.g. a server
|
|
45
|
+
* `/tile` request); napari-js stays ignorant of any backend.
|
|
46
|
+
*/
|
|
47
|
+
export interface TiledSource {
|
|
48
|
+
readonly kind: 'tiled';
|
|
49
|
+
readonly width: number;
|
|
50
|
+
readonly height: number;
|
|
51
|
+
readonly tileSize: number;
|
|
52
|
+
readonly levels: number;
|
|
53
|
+
readonly depth: number;
|
|
54
|
+
readonly channels: 1 | 4;
|
|
55
|
+
readonly dtype: PixelDtype;
|
|
56
|
+
fetchTile(key: TileKey): Promise<PixelChunk>;
|
|
57
|
+
}
|
|
58
|
+
export type TextureSource = TypedImageSource | ExternalImageSource | TiledSource;
|
|
59
|
+
/** Anything `Viewer.addImage` accepts. */
|
|
60
|
+
export type ImageInput = TypedImageSource | TiledSource | ImageBitmap | HTMLCanvasElement | HTMLImageElement;
|
|
61
|
+
export declare function channelsOf(source: TextureSource): 1 | 4;
|
|
62
|
+
/** Number of z-slices in a source (1 unless it's a z-stacked tiled source). */
|
|
63
|
+
export declare function depthOf(source: TextureSource): number;
|
|
64
|
+
export declare function isGrayscale(source: TextureSource): boolean;
|
|
65
|
+
/** Default contrast-limit window for a source, in source-data units. */
|
|
66
|
+
export declare function defaultContrastLimits(source: TextureSource): [number, number];
|
|
67
|
+
/** Normalize a user input into a {@link TextureSource}. */
|
|
68
|
+
export declare function toTextureSource(input: ImageInput): TextureSource;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Layer, BlendMode } from './layer';
|
|
2
|
+
import { Colormap } from '../color/colormap';
|
|
3
|
+
import { TextureSource } from '../io/texture-source';
|
|
4
|
+
export type Interpolation = 'nearest' | 'linear';
|
|
5
|
+
export interface ImageLayerOptions {
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Scalar colormap; ignored for RGB(A) sources (rendered directly). Defaults to gray. */
|
|
8
|
+
colormap?: Colormap | string;
|
|
9
|
+
/** Normalization window in source-data units. Defaults to the source's dtype range. */
|
|
10
|
+
contrastLimits?: [number, number];
|
|
11
|
+
gamma?: number;
|
|
12
|
+
invert?: boolean;
|
|
13
|
+
opacity?: number;
|
|
14
|
+
blending?: BlendMode;
|
|
15
|
+
visible?: boolean;
|
|
16
|
+
interpolation?: Interpolation;
|
|
17
|
+
scale?: [number, number];
|
|
18
|
+
translate?: [number, number];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A 2D image layer. Carries the source pixels plus the scalar display pipeline
|
|
22
|
+
* (contrast/gamma/invert/colormap). Display setters are reactive; changing them updates GPU
|
|
23
|
+
* uniforms/LUT — never re-uploads the texture (tracked via {@link colormapVersion}).
|
|
24
|
+
*/
|
|
25
|
+
export declare class ImageLayer extends Layer {
|
|
26
|
+
readonly kind = "image";
|
|
27
|
+
readonly source: TextureSource;
|
|
28
|
+
readonly grayscale: boolean;
|
|
29
|
+
/** Bumped whenever the colormap changes so the visual knows to rebuild its LUT. */
|
|
30
|
+
colormapVersion: number;
|
|
31
|
+
private _colormap;
|
|
32
|
+
private _contrastLimits;
|
|
33
|
+
private _gamma;
|
|
34
|
+
private _invert;
|
|
35
|
+
private _interpolation;
|
|
36
|
+
constructor(source: TextureSource, opts?: ImageLayerOptions);
|
|
37
|
+
get colormap(): Colormap | null;
|
|
38
|
+
set colormap(value: Colormap | string | null);
|
|
39
|
+
get contrastLimits(): [number, number];
|
|
40
|
+
set contrastLimits(value: readonly [number, number]);
|
|
41
|
+
get gamma(): number;
|
|
42
|
+
set gamma(value: number);
|
|
43
|
+
get invert(): boolean;
|
|
44
|
+
set invert(value: boolean);
|
|
45
|
+
get interpolation(): Interpolation;
|
|
46
|
+
set interpolation(value: Interpolation);
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Layer, BlendMode } from './layer';
|
|
2
|
+
export interface LabelsLayerOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
/** Highlight this label id when {@link showSelectedOnly} is set. */
|
|
5
|
+
selectedLabel?: number;
|
|
6
|
+
showSelectedOnly?: boolean;
|
|
7
|
+
opacity?: number;
|
|
8
|
+
blending?: BlendMode;
|
|
9
|
+
visible?: boolean;
|
|
10
|
+
scale?: [number, number];
|
|
11
|
+
translate?: [number, number];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A segmentation/label layer (the napari Labels analog). `data` is an 8-bit integer label
|
|
15
|
+
* image (ids 0..255; 0 = background/transparent), colored by a cyclic LUT. Sampled with
|
|
16
|
+
* nearest filtering so label edges stay crisp. uint16/uint32 label support is a follow-up.
|
|
17
|
+
*/
|
|
18
|
+
export declare class LabelsLayer extends Layer {
|
|
19
|
+
readonly kind = "labels";
|
|
20
|
+
readonly width: number;
|
|
21
|
+
readonly height: number;
|
|
22
|
+
readonly data: Uint8Array;
|
|
23
|
+
private _selectedLabel;
|
|
24
|
+
private _showSelectedOnly;
|
|
25
|
+
constructor(data: Uint8Array, width: number, height: number, opts?: LabelsLayerOptions);
|
|
26
|
+
get selectedLabel(): number;
|
|
27
|
+
set selectedLabel(value: number);
|
|
28
|
+
get showSelectedOnly(): boolean;
|
|
29
|
+
set showSelectedOnly(value: boolean);
|
|
30
|
+
/** Label id at data pixel `(x, y)`, or 0 (background) if out of bounds. */
|
|
31
|
+
labelAt(x: number, y: number): number;
|
|
32
|
+
}
|