@niivue/nv-ohif 0.1.0-rc.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/README.md +210 -0
- package/dist/index.js +2952 -0
- package/dist/types/NiivueViewport.d.ts +14 -0
- package/dist/types/classifyDisplaySet.d.ts +7 -0
- package/dist/types/commands.d.ts +202 -0
- package/dist/types/dicomToNiivue.d.ts +21 -0
- package/dist/types/dicomWadoRs.d.ts +22 -0
- package/dist/types/displaySetToNiivue.d.ts +7 -0
- package/dist/types/getNiivueViewportModule.d.ts +4 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/niivueRegistry.d.ts +60 -0
- package/dist/types/ohif-types.d.ts +97 -0
- package/dist/types/reconstructP10.d.ts +35 -0
- package/dist/types/sopClassHandler.d.ts +16 -0
- package/dist/types/toolBridge.d.ts +16 -0
- package/dist/types/toolbar.d.ts +38 -0
- package/dist/types/volumeAnnotationOverlay.d.ts +20 -0
- package/dist/types/wsiSlideView.d.ts +30 -0
- package/dist/types/wsiThumbnail.d.ts +40 -0
- package/dist/types/wsiTileSource.d.ts +68 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @niivue/nv-ohif
|
|
2
|
+
|
|
3
|
+
A [NiiVue](https://github.com/niivue/niivue) viewport extension for the
|
|
4
|
+
[OHIF Viewer](https://ohif.org) (v3.12). Render series with NiiVue inside OHIF —
|
|
5
|
+
bringing 3D volume rendering, mesh/surface overlays, multiplanar with colormapped
|
|
6
|
+
overlays, and voxel drawing / vector annotation to your OHIF app.
|
|
7
|
+
|
|
8
|
+
> **Status: proven in a real OHIF app.** Renders **NIfTI (volume-URL) display sets**
|
|
9
|
+
> and **DICOM series** (fetched + converted to NIfTI with `@niivue/dcm2niix`), with a
|
|
10
|
+
> toolbar surfacing NiiVue's views / clip plane / overlay / window-level. Verified
|
|
11
|
+
> mounting inside a full local OHIF Viewer (registered via `pluginConfig.json` + a
|
|
12
|
+
> mode). See `PLAN.md`.
|
|
13
|
+
|
|
14
|
+
> **Consuming the local build (dev):** install **packed tarballs**, do not symlink.
|
|
15
|
+
> Symlinking the monorepo package into an OHIF app makes its bundler follow the link
|
|
16
|
+
> and bundle a duplicate of shared deps (breaks OHIF's floating-ui). Use
|
|
17
|
+
> `npm pack` + `pnpm add file:<dir>` so each resolves self-contained from the app tree.
|
|
18
|
+
|
|
19
|
+
## Try the proof demo
|
|
20
|
+
|
|
21
|
+
`bun run dev` starts a small OHIF-shaped harness (`demo/`) that drives the real
|
|
22
|
+
extension — it pulls the viewport via `getViewportModule()` and renders it with a mock
|
|
23
|
+
OHIF display set pointing at a public NIfTI, so you can see NiiVue rendering a volume
|
|
24
|
+
without a full OHIF app.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bun add @niivue/nv-ohif
|
|
30
|
+
# peers your OHIF app already provides: @ohif/core, @ohif/extension-default,
|
|
31
|
+
# react@^18.3.1, react-dom@^18.3.1, and @niivue/niivue
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Register the extension
|
|
35
|
+
|
|
36
|
+
Add it to your OHIF app's `pluginConfig.json` (or `addExtension`) and reference the
|
|
37
|
+
viewport from a mode. The extension id is `@niivue/nv-ohif`; the viewport name is
|
|
38
|
+
`niivue`.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// in a mode's viewport config
|
|
42
|
+
{
|
|
43
|
+
namespace: '@niivue/nv-ohif.viewportModule.niivue',
|
|
44
|
+
displaySetsToDisplay: ['<your NIfTI/volume display-set handler>'],
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What it does today
|
|
49
|
+
|
|
50
|
+
- Registers a **React 18 viewport** that owns a `<canvas>` + a NiiVue instance.
|
|
51
|
+
- Loads a display set whose URL is a NiiVue-readable volume (`.nii/.nii.gz`, `.nrrd`,
|
|
52
|
+
`.mgz`, `.mha`, `.mif`, …) via `nv.loadVolumes(...)`, opening in multiplanar.
|
|
53
|
+
- Loads a **DICOM** series by fetching it and converting to NIfTI with
|
|
54
|
+
`@niivue/dcm2niix` (see [DICOM support](#dicom-support) for the dependency caveat).
|
|
55
|
+
- Renders **whole-slide (SM)** series with **NVSlide** (tiled deep-zoom) on its own
|
|
56
|
+
canvas: **JPEG, TILED_FULL** slides are supported; **JPEG 2000** and **TILED_SPARSE**
|
|
57
|
+
are declined with an explanatory note (see [Whole-slide imaging](#whole-slide-imaging)).
|
|
58
|
+
- Mirrors OHIF's active primary tool (Window/Level, Pan) onto NiiVue's left-drag,
|
|
59
|
+
and reflects a manual NiiVue window/level drag onto any sibling OHIF viewport
|
|
60
|
+
showing the same series (`setViewportWindowLevel`).
|
|
61
|
+
- Ships **toolbar buttons + commands**: a views dropdown (axial / coronal /
|
|
62
|
+
sagittal / multiplanar / 3D render), a **clip-plane** dropdown (off / anterior /
|
|
63
|
+
posterior / left / right / superior / inferior), a **window/level** dropdown
|
|
64
|
+
(auto robust window + OHIF's modality presets, applied as NiiVue calMin/calMax),
|
|
65
|
+
a **colormap** dropdown (gray / hot / bone / cool / warm / viridis / plasma /
|
|
66
|
+
inferno / turbo / jet, applied to the base volume), a **colorbar** toggle (the
|
|
67
|
+
colormap legend), a **smoothing** toggle (nearest-neighbor vs linear
|
|
68
|
+
interpolation), a **crosshair** toggle (`NiivueCrosshair`, show/hide the
|
|
69
|
+
crosshair — use in place of cornerstone's Crosshairs button, which no-ops on a
|
|
70
|
+
NiiVue viewport), a **capture** button (`NiivueCapture`, save the viewport as
|
|
71
|
+
PNG — use in place of cornerstone's Capture, which errors on a NiiVue
|
|
72
|
+
viewport), an **overlay** toggle (load the study's next series as a colormapped
|
|
73
|
+
overlay), and a reset-view button — all with active/disabled state tracked per
|
|
74
|
+
viewport.
|
|
75
|
+
- Backs OHIF's full **Measurement** tool group (Length, ellipse / rectangle /
|
|
76
|
+
circle / freehand / spline / livewire ROIs, Bidirectional, ArrowAnnotate) with
|
|
77
|
+
NiiVue annotations, reflected into the Measurements panel with stats and
|
|
78
|
+
editable labels (see [Measurement tools](#measurement-tools)).
|
|
79
|
+
|
|
80
|
+
## Toolbar buttons
|
|
81
|
+
|
|
82
|
+
The extension registers the commands (`niivueSetSliceType`, `niivueResetView`,
|
|
83
|
+
`niivueSetClipPlane`, `niivueToggleOverlay`, `niivueSetWindowLevel`,
|
|
84
|
+
`niivueSetWindowLevelPreset`, `niivueAutoWindowLevel`, `niivueSetColormap`,
|
|
85
|
+
`niivueToggleColorbar`, `niivueToggleInterpolation`, `niivueToggleCrosshair`), the toolbar evaluators, and a customization pack with the
|
|
86
|
+
button definitions. A mode pulls them in by reference and places them in its
|
|
87
|
+
primary bar:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
// in a mode
|
|
91
|
+
toolbarButtons: [
|
|
92
|
+
{ $reference: 'cornerstone.toolbarButtons' },
|
|
93
|
+
{ $reference: 'niivue.toolbarButtons' },
|
|
94
|
+
],
|
|
95
|
+
toolbarSections: [
|
|
96
|
+
{ $reference: 'cornerstone.toolbarSections' },
|
|
97
|
+
{ $reference: 'niivue.toolbarSections' },
|
|
98
|
+
// restate your primary bar with the NiiVue buttons added (sections
|
|
99
|
+
// shallow-merge per key, later wins). Use NiivueCapture / NiivueCrosshair
|
|
100
|
+
// in place of cornerstone's 'Capture' / 'Crosshairs' on NiiVue viewports:
|
|
101
|
+
// ToolbarService.register is first-registration-wins, so a same-id override
|
|
102
|
+
// of a cornerstone button is silently dropped.
|
|
103
|
+
{ primary: [/* ...your button ids, */ 'NiivueViews', 'NiivueClip',
|
|
104
|
+
'NiivueWindowLevel', 'NiivueColormap', 'NiivueColorbar', 'NiivueInterpolation',
|
|
105
|
+
'NiivueRuler', 'NiivueOverlay', 'NiivueReset', 'NiivueCapture',
|
|
106
|
+
'NiivueCrosshair'] },
|
|
107
|
+
],
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Measurement tools
|
|
111
|
+
|
|
112
|
+
Every tool in OHIF's **Measurement** group is backed by NiiVue's vector
|
|
113
|
+
annotation system (one unified path, no separate ruler subsystem). Pick a tool in
|
|
114
|
+
the toolbar and it draws on the NiiVue viewport; a completed shape appears as a
|
|
115
|
+
row in OHIF's **Measurements** panel with its stats.
|
|
116
|
+
|
|
117
|
+
| OHIF tool | NiiVue annotation | Gesture | Reported |
|
|
118
|
+
|-----------|-------------------|---------|----------|
|
|
119
|
+
| Length (Ruler button) | `measureLine` | drag | length (mm), drawn as a graduated ruler |
|
|
120
|
+
| EllipticalROI | `measureEllipse` | drag | area (mm2) + min/mean/max/SD intensity |
|
|
121
|
+
| RectangleROI | `measureRect` | drag | area (mm2) + intensity |
|
|
122
|
+
| CircleROI | `measureCircle` | drag | area (mm2) + intensity |
|
|
123
|
+
| PlanarFreehandROI | `freehand` | drag | contour only (no stats) |
|
|
124
|
+
| SplineROI | `measureSpline` | multi-click | area (mm2) + intensity |
|
|
125
|
+
| LivewireContour | `measureLivewire` | multi-click | area (mm2) + intensity |
|
|
126
|
+
| Bidirectional | `measureBidirectional` | two drags | long + short diameter (mm) |
|
|
127
|
+
| ArrowAnnotate | `arrow` | drag | label only |
|
|
128
|
+
|
|
129
|
+
PlanarFreehandROI panel reflection currently requires exactly one contour without
|
|
130
|
+
holes. Multi-part or holed freehand annotations remain visible in NiiVue but are
|
|
131
|
+
not reflected into OHIF, whose measurement schema exposes only one polyline.
|
|
132
|
+
|
|
133
|
+
**Gestures.** Drag tools press-drag-release on a 2D slice. The multi-click tools
|
|
134
|
+
(SplineROI, LivewireContour) click to place each vertex, double-click to close the
|
|
135
|
+
contour, and Escape to cancel an in-progress contour. Bidirectional takes two
|
|
136
|
+
drags: the long axis first, then the perpendicular short axis.
|
|
137
|
+
|
|
138
|
+
**Intensity stats** are sampled from the base volume in the series' modality unit
|
|
139
|
+
(HU for CT, SUV for PT, unitless otherwise); area is in mm2 and voxel-spacing
|
|
140
|
+
aware. Length and the bidirectional diameters are in-plane mm.
|
|
141
|
+
|
|
142
|
+
**Rendering.** Shapes and their labels are drawn by the `@niivue/uikit` overlay
|
|
143
|
+
(the same widget set the whole-slide ruler uses), not OHIF's cornerstone canvas.
|
|
144
|
+
A measured line renders as a graduated ruler (end caps, mm ticks, numbered majors,
|
|
145
|
+
rotated length label) in the annotation's stroke color.
|
|
146
|
+
|
|
147
|
+
**Free-text labels.** Edit a measurement row's label in OHIF's panel and the text
|
|
148
|
+
is pushed onto the shape on the viewport; the label and the annotation stay in
|
|
149
|
+
sync both directions.
|
|
150
|
+
|
|
151
|
+
**Panel reflection needs a backing DICOM series.** A row is added only when the
|
|
152
|
+
viewport is backed by a loaded DICOM series with instances (so the row carries a
|
|
153
|
+
resolvable `referenceSeriesUID`). A NIfTI-URL display set still draws the shape on
|
|
154
|
+
the canvas, but it is not reflected into the panel.
|
|
155
|
+
|
|
156
|
+
## DICOM support
|
|
157
|
+
|
|
158
|
+
DICOM series are rendered by fetching the instances and converting them to NIfTI
|
|
159
|
+
in-browser with `@niivue/dcm2niix` (a WASM build of dcm2niix). This is verified
|
|
160
|
+
working end-to-end in a real OHIF app for both uncompressed and JPEG-LS studies,
|
|
161
|
+
and it now works for `npm`-install consumers too.
|
|
162
|
+
|
|
163
|
+
> **History (resolved):** older `@niivue/dcm2niix` releases aborted every
|
|
164
|
+
> in-browser conversion. Emscripten's `exit()` *throws* inside a Web Worker (it
|
|
165
|
+
> returns the code under Node), and the worker did `const exitCode =
|
|
166
|
+
> mod.callMain(args)` and let that throw hit its catch, so it never read `/output`.
|
|
167
|
+
> The fix wraps `callMain` in try/catch and reads `err.status`. It is merged upstream
|
|
168
|
+
> (`rordenlab/dcm2niix`, commit `aae72ac`, both `js/src/worker.js` and
|
|
169
|
+
> `worker.jpeg.js`) and **published** in `@niivue/dcm2niix 1.3.20260724` (the
|
|
170
|
+
> calendar-versioned npm `latest`). This package pins `@niivue/dcm2niix` at
|
|
171
|
+
> `^1.3.20260724`, so `npm install @niivue/nv-ohif` pulls a dcm2niix that has the
|
|
172
|
+
> fix and DICOM conversion works out of the box. (Versions `1.2.0` and the
|
|
173
|
+
> `1.3.0-dev.0` prerelease predate the fix and are excluded by the pin.)
|
|
174
|
+
|
|
175
|
+
## Whole-slide imaging
|
|
176
|
+
|
|
177
|
+
A whole-slide (SM) series renders with **NVSlide** (tiled deep-zoom) on its own
|
|
178
|
+
WebGL2 canvas, overlaid on the NiiVue viewport. The manifest is built from the
|
|
179
|
+
DICOM tile pyramid (`buildWsiManifest`) and tiles are fetched on demand
|
|
180
|
+
(`DicomWsiTileSource`).
|
|
181
|
+
|
|
182
|
+
Support boundary:
|
|
183
|
+
|
|
184
|
+
- **Supported:** **JPEG**-encoded tiles with **TILED_FULL** frame organization.
|
|
185
|
+
- **Declined (with an in-viewport note, not a crash):**
|
|
186
|
+
- **JPEG 2000** tiles (the viewer cannot decode them yet).
|
|
187
|
+
- **TILED_SPARSE** frame organization (the tile grid assumes row-major
|
|
188
|
+
TILED_FULL order, so a sparse slide would render scrambled).
|
|
189
|
+
- A slide with no tiled (VOLUME) pyramid levels.
|
|
190
|
+
|
|
191
|
+
The slide viewport supports pan/zoom and the whole-slide ruler. A NVSlide path for
|
|
192
|
+
2-D single-frame images is still a TODO (see `PLAN.md`).
|
|
193
|
+
|
|
194
|
+
## Compatibility
|
|
195
|
+
|
|
196
|
+
- **OHIF**: `^3.12` (developed against 3.12.6; also exercised against OHIF
|
|
197
|
+
`master`/3.13-beta in the dev rig).
|
|
198
|
+
- **React**: `^18.3.1` (OHIF is on React 18 — this does not use `@niivue/nvreact`,
|
|
199
|
+
which targets React 19).
|
|
200
|
+
- **`@niivue/dcm2niix`**: required for DICOM only — see [DICOM support](#dicom-support).
|
|
201
|
+
|
|
202
|
+
## Roadmap
|
|
203
|
+
|
|
204
|
+
See `PLAN.md`. Landed: NIfTI + DICOM rendering, a toolbar for views / clip plane /
|
|
205
|
+
overlay / window-level (both directions) / colormap, the full **Measurement** tool
|
|
206
|
+
group (see [Measurement tools](#measurement-tools)), and **NVSlide whole-slide (SM)
|
|
207
|
+
rendering** for JPEG / TILED_FULL slides (see [Whole-slide imaging](#whole-slide-imaging)).
|
|
208
|
+
Next: segmentation overlays, mesh/surface overlay, JPEG 2000 / TILED_SPARSE slide
|
|
209
|
+
support, and **NVSlide for 2-D** single-frame series (see the
|
|
210
|
+
`## TODO — NVSlide for 2D` section in `PLAN.md`).
|