@niivue/nvreact 1.0.0-rc.1 → 1.0.0-rc.5
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/package.json +3 -3
- package/dist/lib.js +0 -793
- package/dist/types/context.d.ts +0 -7
- package/dist/types/hooks.d.ts +0 -10
- package/dist/types/layouts.d.ts +0 -20
- package/dist/types/lib.d.ts +0 -9
- package/dist/types/nvscene-controller.d.ts +0 -138
- package/dist/types/nvscene.d.ts +0 -10
- package/dist/types/nvviewer.d.ts +0 -14
- package/dist/types/types.d.ts +0 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@niivue/nvreact",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/lib.js",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@niivue/niivue": "1.0.0-rc.
|
|
33
|
+
"@niivue/niivue": "1.0.0-rc.4",
|
|
34
34
|
"react": "^19",
|
|
35
35
|
"react-dom": "^19"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@happy-dom/global-registrator": "^20.6.1",
|
|
39
|
-
"@niivue/niivue": "1.0.0-rc.
|
|
39
|
+
"@niivue/niivue": "1.0.0-rc.4",
|
|
40
40
|
"@testing-library/dom": "^10.4.1",
|
|
41
41
|
"@testing-library/jest-dom": "^6.9.1",
|
|
42
42
|
"@testing-library/react": "^16.3.2",
|
package/dist/lib.js
DELETED
|
@@ -1,793 +0,0 @@
|
|
|
1
|
-
// src/context.tsx
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
var NvSceneContext = createContext(null);
|
|
5
|
-
function NvSceneProvider({
|
|
6
|
-
scene,
|
|
7
|
-
children
|
|
8
|
-
}) {
|
|
9
|
-
return /* @__PURE__ */ jsx(NvSceneContext.Provider, {
|
|
10
|
-
value: scene,
|
|
11
|
-
children
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
function useSceneContext() {
|
|
15
|
-
const scene = useContext(NvSceneContext);
|
|
16
|
-
if (!scene) {
|
|
17
|
-
throw new Error("useSceneContext must be used within an NvSceneProvider");
|
|
18
|
-
}
|
|
19
|
-
return scene;
|
|
20
|
-
}
|
|
21
|
-
// src/hooks.ts
|
|
22
|
-
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
23
|
-
|
|
24
|
-
// src/nvscene-controller.ts
|
|
25
|
-
import NiiVueGPU, { DRAG_MODE, SHOW_RENDER, SLICE_TYPE } from "@niivue/niivue";
|
|
26
|
-
|
|
27
|
-
// src/layouts.ts
|
|
28
|
-
var layout1x1 = () => ({
|
|
29
|
-
top: "0",
|
|
30
|
-
left: "0",
|
|
31
|
-
width: "100%",
|
|
32
|
-
height: "100%"
|
|
33
|
-
});
|
|
34
|
-
var layout2x2 = (_containerElement, index) => {
|
|
35
|
-
const row = Math.floor(index / 2);
|
|
36
|
-
const col = index % 2;
|
|
37
|
-
return {
|
|
38
|
-
top: `${row * 50}%`,
|
|
39
|
-
left: `${col * 50}%`,
|
|
40
|
-
width: "50%",
|
|
41
|
-
height: "50%"
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
var layout1x2 = (_containerElement, index) => ({
|
|
45
|
-
top: "0",
|
|
46
|
-
left: `${index * 50}%`,
|
|
47
|
-
width: "50%",
|
|
48
|
-
height: "100%"
|
|
49
|
-
});
|
|
50
|
-
var layout2x1 = (_containerElement, index) => ({
|
|
51
|
-
top: `${index * 50}%`,
|
|
52
|
-
left: "0",
|
|
53
|
-
width: "100%",
|
|
54
|
-
height: "50%"
|
|
55
|
-
});
|
|
56
|
-
var layout1x3 = (_containerElement, index) => ({
|
|
57
|
-
top: "0",
|
|
58
|
-
left: `${index * 33.333}%`,
|
|
59
|
-
width: "33.333%",
|
|
60
|
-
height: "100%"
|
|
61
|
-
});
|
|
62
|
-
var layout3x1 = (_containerElement, index) => ({
|
|
63
|
-
top: `${index * 33.333}%`,
|
|
64
|
-
left: "0",
|
|
65
|
-
width: "100%",
|
|
66
|
-
height: "33.333%"
|
|
67
|
-
});
|
|
68
|
-
var layout3x3 = (_containerElement, index) => {
|
|
69
|
-
const row = Math.floor(index / 3);
|
|
70
|
-
const col = index % 3;
|
|
71
|
-
return {
|
|
72
|
-
top: `${row * 33.333}%`,
|
|
73
|
-
left: `${col * 33.333}%`,
|
|
74
|
-
width: "33.333%",
|
|
75
|
-
height: "33.333%"
|
|
76
|
-
};
|
|
77
|
-
};
|
|
78
|
-
var defaultLayouts = {
|
|
79
|
-
"1x1": {
|
|
80
|
-
slots: 1,
|
|
81
|
-
label: "1x1",
|
|
82
|
-
layoutFunction: layout1x1
|
|
83
|
-
},
|
|
84
|
-
"1x2": {
|
|
85
|
-
slots: 2,
|
|
86
|
-
label: "1x2",
|
|
87
|
-
layoutFunction: layout1x2
|
|
88
|
-
},
|
|
89
|
-
"2x1": {
|
|
90
|
-
slots: 2,
|
|
91
|
-
label: "2x1",
|
|
92
|
-
layoutFunction: layout2x1
|
|
93
|
-
},
|
|
94
|
-
"1x3": {
|
|
95
|
-
slots: 3,
|
|
96
|
-
label: "1x3",
|
|
97
|
-
layoutFunction: layout1x3
|
|
98
|
-
},
|
|
99
|
-
"3x1": {
|
|
100
|
-
slots: 3,
|
|
101
|
-
label: "3x1",
|
|
102
|
-
layoutFunction: layout3x1
|
|
103
|
-
},
|
|
104
|
-
"2x2": {
|
|
105
|
-
slots: 4,
|
|
106
|
-
label: "2x2",
|
|
107
|
-
layoutFunction: layout2x2
|
|
108
|
-
},
|
|
109
|
-
"3x3": {
|
|
110
|
-
slots: 9,
|
|
111
|
-
label: "3x3",
|
|
112
|
-
layoutFunction: layout3x3
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// src/nvscene-controller.ts
|
|
117
|
-
var defaultSliceLayout = [
|
|
118
|
-
{ sliceType: SLICE_TYPE.AXIAL, position: [0, 0, 1, 0.8] },
|
|
119
|
-
{ sliceType: SLICE_TYPE.CORONAL, position: [0, 0.8, 0.5, 0.2] },
|
|
120
|
-
{ sliceType: SLICE_TYPE.SAGITTAL, position: [0.5, 0.8, 0.5, 0.2] }
|
|
121
|
-
];
|
|
122
|
-
var splitSliceLayout = [
|
|
123
|
-
{ sliceType: SLICE_TYPE.SAGITTAL, position: [0, 0, 0.5, 1] },
|
|
124
|
-
{ sliceType: SLICE_TYPE.CORONAL, position: [0.5, 0, 0.5, 0.5] },
|
|
125
|
-
{ sliceType: SLICE_TYPE.AXIAL, position: [0.5, 0.5, 0.5, 0.5] }
|
|
126
|
-
];
|
|
127
|
-
var triSliceLayout = [
|
|
128
|
-
{ sliceType: SLICE_TYPE.AXIAL, position: [0, 0, 0.333, 1] },
|
|
129
|
-
{ sliceType: SLICE_TYPE.CORONAL, position: [0.333, 0, 0.333, 1] },
|
|
130
|
-
{ sliceType: SLICE_TYPE.SAGITTAL, position: [0.666, 0, 0.334, 1] }
|
|
131
|
-
];
|
|
132
|
-
var stackedSliceLayout = [
|
|
133
|
-
{ sliceType: SLICE_TYPE.AXIAL, position: [0, 0, 1, 0.333] },
|
|
134
|
-
{ sliceType: SLICE_TYPE.CORONAL, position: [0, 0.333, 1, 0.333] },
|
|
135
|
-
{ sliceType: SLICE_TYPE.SAGITTAL, position: [0, 0.666, 1, 0.334] }
|
|
136
|
-
];
|
|
137
|
-
var quadSliceLayout = [
|
|
138
|
-
{ sliceType: SLICE_TYPE.AXIAL, position: [0, 0, 0.5, 0.5] },
|
|
139
|
-
{ sliceType: SLICE_TYPE.CORONAL, position: [0.5, 0, 0.5, 0.5] },
|
|
140
|
-
{ sliceType: SLICE_TYPE.SAGITTAL, position: [0, 0.5, 0.5, 0.5] },
|
|
141
|
-
{ sliceType: SLICE_TYPE.RENDER, position: [0.5, 0.5, 0.5, 0.5] }
|
|
142
|
-
];
|
|
143
|
-
var heroRenderSliceLayout = [
|
|
144
|
-
{ sliceType: SLICE_TYPE.RENDER, position: [0, 0, 1, 0.7] },
|
|
145
|
-
{ sliceType: SLICE_TYPE.AXIAL, position: [0, 0.7, 0.333, 0.3] },
|
|
146
|
-
{ sliceType: SLICE_TYPE.CORONAL, position: [0.333, 0.7, 0.333, 0.3] },
|
|
147
|
-
{ sliceType: SLICE_TYPE.SAGITTAL, position: [0.666, 0.7, 0.334, 0.3] }
|
|
148
|
-
];
|
|
149
|
-
var defaultSliceLayouts = {
|
|
150
|
-
"axial-hero": {
|
|
151
|
-
label: "Axial Hero",
|
|
152
|
-
layout: defaultSliceLayout
|
|
153
|
-
},
|
|
154
|
-
"sag-left": {
|
|
155
|
-
label: "Sag Left Split",
|
|
156
|
-
layout: splitSliceLayout
|
|
157
|
-
},
|
|
158
|
-
"tri-h": {
|
|
159
|
-
label: "Tri Horizontal",
|
|
160
|
-
layout: triSliceLayout
|
|
161
|
-
},
|
|
162
|
-
"tri-v": {
|
|
163
|
-
label: "Tri Stacked",
|
|
164
|
-
layout: stackedSliceLayout
|
|
165
|
-
},
|
|
166
|
-
"quad-render": {
|
|
167
|
-
label: "Quad Render",
|
|
168
|
-
layout: quadSliceLayout
|
|
169
|
-
},
|
|
170
|
-
"render-hero": {
|
|
171
|
-
label: "Render Hero",
|
|
172
|
-
layout: heroRenderSliceLayout
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
var defaultViewerOptions = {
|
|
176
|
-
crosshairGap: 5,
|
|
177
|
-
primaryDragMode: DRAG_MODE.crosshair,
|
|
178
|
-
secondaryDragMode: DRAG_MODE.pan
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
class NvSceneController {
|
|
182
|
-
containerElement = null;
|
|
183
|
-
viewers = [];
|
|
184
|
-
currentLayout = "1x1";
|
|
185
|
-
slots;
|
|
186
|
-
layouts;
|
|
187
|
-
onViewerCreated;
|
|
188
|
-
listeners = new Set;
|
|
189
|
-
snapshotCache = null;
|
|
190
|
-
nextId = 0;
|
|
191
|
-
viewersById = new Map;
|
|
192
|
-
broadcasting = false;
|
|
193
|
-
broadcastOptions = { "2d": true, "3d": true };
|
|
194
|
-
viewerSliceLayouts = new Map;
|
|
195
|
-
viewerDefaults;
|
|
196
|
-
eventListeners = new Map;
|
|
197
|
-
loadingCounts = new Map;
|
|
198
|
-
viewerErrors = new Map;
|
|
199
|
-
constructor(layouts = defaultLayouts, viewerDefaults = {}) {
|
|
200
|
-
this.layouts = layouts;
|
|
201
|
-
this.slots = this.layouts[this.currentLayout]?.slots ?? 1;
|
|
202
|
-
this.viewerDefaults = viewerDefaults;
|
|
203
|
-
}
|
|
204
|
-
on(event, cb) {
|
|
205
|
-
if (!this.eventListeners.has(event)) {
|
|
206
|
-
this.eventListeners.set(event, new Set);
|
|
207
|
-
}
|
|
208
|
-
this.eventListeners.get(event)?.add(cb);
|
|
209
|
-
return () => this.off(event, cb);
|
|
210
|
-
}
|
|
211
|
-
off(event, cb) {
|
|
212
|
-
this.eventListeners.get(event)?.delete(cb);
|
|
213
|
-
}
|
|
214
|
-
emit(event, ...args) {
|
|
215
|
-
const listeners = this.eventListeners.get(event);
|
|
216
|
-
if (!listeners)
|
|
217
|
-
return;
|
|
218
|
-
for (const cb of listeners) {
|
|
219
|
-
cb(...args);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
subscribe = (listener) => {
|
|
223
|
-
this.listeners.add(listener);
|
|
224
|
-
return () => this.listeners.delete(listener);
|
|
225
|
-
};
|
|
226
|
-
getSnapshot = () => {
|
|
227
|
-
if (!this.snapshotCache) {
|
|
228
|
-
const viewerStates = this.viewers.map((v) => ({
|
|
229
|
-
id: v.id,
|
|
230
|
-
loading: this.loadingCounts.get(v.id) ?? 0,
|
|
231
|
-
errors: this.viewerErrors.get(v.id) ?? []
|
|
232
|
-
}));
|
|
233
|
-
this.snapshotCache = {
|
|
234
|
-
currentLayout: this.currentLayout,
|
|
235
|
-
viewerCount: this.viewers.length,
|
|
236
|
-
slots: this.slots,
|
|
237
|
-
isBroadcasting: this.broadcasting,
|
|
238
|
-
isLoading: viewerStates.some((s) => s.loading > 0),
|
|
239
|
-
viewerStates
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return this.snapshotCache;
|
|
243
|
-
};
|
|
244
|
-
notify() {
|
|
245
|
-
this.snapshotCache = null;
|
|
246
|
-
this.listeners.forEach((listener) => {
|
|
247
|
-
listener();
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
setContainerElement(element) {
|
|
251
|
-
this.containerElement = element;
|
|
252
|
-
if (element) {
|
|
253
|
-
this.updateLayout();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
setLayout(layoutName) {
|
|
257
|
-
const layoutConfig = this.layouts[layoutName];
|
|
258
|
-
if (!layoutConfig)
|
|
259
|
-
return;
|
|
260
|
-
this.currentLayout = layoutName;
|
|
261
|
-
this.slots = layoutConfig.slots;
|
|
262
|
-
while (this.viewers.length > this.slots) {
|
|
263
|
-
this.removeViewer(this.viewers.length - 1, false);
|
|
264
|
-
}
|
|
265
|
-
if (this.containerElement) {
|
|
266
|
-
while (this.viewers.length < this.slots) {
|
|
267
|
-
this.addViewer();
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
this.updateLayout();
|
|
271
|
-
this.notify();
|
|
272
|
-
}
|
|
273
|
-
updateLayout() {
|
|
274
|
-
if (!this.containerElement)
|
|
275
|
-
return;
|
|
276
|
-
const layoutConfig = this.layouts[this.currentLayout];
|
|
277
|
-
if (!layoutConfig)
|
|
278
|
-
return;
|
|
279
|
-
this.viewers.forEach((viewer, index) => {
|
|
280
|
-
const position = layoutConfig.layoutFunction(this.containerElement, index, this.viewers.length);
|
|
281
|
-
Object.assign(viewer.containerDiv.style, {
|
|
282
|
-
position: "absolute",
|
|
283
|
-
top: position.top,
|
|
284
|
-
left: position.left,
|
|
285
|
-
width: position.width,
|
|
286
|
-
height: position.height
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
canAddViewer() {
|
|
291
|
-
return this.viewers.length < this.slots;
|
|
292
|
-
}
|
|
293
|
-
getNiivue(index) {
|
|
294
|
-
return this.viewers[index]?.niivue;
|
|
295
|
-
}
|
|
296
|
-
getAllNiivue() {
|
|
297
|
-
return this.viewers.map((viewer) => viewer.niivue);
|
|
298
|
-
}
|
|
299
|
-
forEachNiivue(callback) {
|
|
300
|
-
this.viewers.forEach((viewer, i) => {
|
|
301
|
-
callback(viewer.niivue, i);
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
hasSyncableContent(nv) {
|
|
305
|
-
return nv.volumes.length > 0 || nv.meshes.length > 0;
|
|
306
|
-
}
|
|
307
|
-
safeBroadcastTo(index, targets) {
|
|
308
|
-
const viewer = this.viewers[index];
|
|
309
|
-
if (!viewer)
|
|
310
|
-
return;
|
|
311
|
-
try {
|
|
312
|
-
viewer.niivue.broadcastTo(targets, this.broadcastOptions);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
this.addError(viewer.id, err);
|
|
315
|
-
this.emit("error", index, err);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
rewireBroadcasting() {
|
|
319
|
-
if (!this.broadcasting) {
|
|
320
|
-
this.viewers.forEach((_viewer, index) => {
|
|
321
|
-
this.safeBroadcastTo(index, []);
|
|
322
|
-
});
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const syncableViewers = this.viewers.filter((viewer) => this.hasSyncableContent(viewer.niivue));
|
|
326
|
-
this.viewers.forEach((viewer, index) => {
|
|
327
|
-
if (!this.hasSyncableContent(viewer.niivue)) {
|
|
328
|
-
this.safeBroadcastTo(index, []);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
const others = syncableViewers.filter((v) => v.id !== viewer.id).map((v) => v.niivue);
|
|
332
|
-
this.safeBroadcastTo(index, others);
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
setBroadcasting(enabled, options) {
|
|
336
|
-
this.broadcasting = enabled;
|
|
337
|
-
if (options) {
|
|
338
|
-
this.broadcastOptions = { ...this.broadcastOptions, ...options };
|
|
339
|
-
}
|
|
340
|
-
this.rewireBroadcasting();
|
|
341
|
-
this.notify();
|
|
342
|
-
}
|
|
343
|
-
isBroadcasting() {
|
|
344
|
-
return this.broadcasting;
|
|
345
|
-
}
|
|
346
|
-
setViewerSliceLayout(index, layout) {
|
|
347
|
-
const viewer = this.viewers[index];
|
|
348
|
-
if (!viewer)
|
|
349
|
-
return;
|
|
350
|
-
this.viewerSliceLayouts.set(viewer.id, layout);
|
|
351
|
-
this.applySliceLayout(viewer.niivue, layout);
|
|
352
|
-
this.notify();
|
|
353
|
-
}
|
|
354
|
-
getViewerSliceLayout(index) {
|
|
355
|
-
const viewer = this.viewers[index];
|
|
356
|
-
if (!viewer)
|
|
357
|
-
return null;
|
|
358
|
-
return this.viewerSliceLayouts.get(viewer.id) ?? null;
|
|
359
|
-
}
|
|
360
|
-
applySliceLayout(nv, layout) {
|
|
361
|
-
if (layout && layout.length > 0) {
|
|
362
|
-
nv.customLayout = layout;
|
|
363
|
-
} else {
|
|
364
|
-
nv.customLayout = null;
|
|
365
|
-
nv.sliceType = SLICE_TYPE.AXIAL;
|
|
366
|
-
nv.showRender = SHOW_RENDER.NEVER;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
getNiivueById(id) {
|
|
370
|
-
return this.viewersById.get(id)?.niivue;
|
|
371
|
-
}
|
|
372
|
-
getViewerById(id) {
|
|
373
|
-
return this.viewersById.get(id);
|
|
374
|
-
}
|
|
375
|
-
async loadVolume(index, opts) {
|
|
376
|
-
const viewer = this.viewers[index];
|
|
377
|
-
if (!viewer)
|
|
378
|
-
throw new Error(`No viewer at index ${index}`);
|
|
379
|
-
this.incrementLoading(viewer.id);
|
|
380
|
-
try {
|
|
381
|
-
await viewer.niivue.addVolume(opts);
|
|
382
|
-
const vols = viewer.niivue.volumes;
|
|
383
|
-
const image = vols[vols.length - 1];
|
|
384
|
-
if (!image)
|
|
385
|
-
throw new Error("Volume was not added");
|
|
386
|
-
this.emit("volumeAdded", index, opts, image);
|
|
387
|
-
if (this.broadcasting) {
|
|
388
|
-
this.rewireBroadcasting();
|
|
389
|
-
}
|
|
390
|
-
return image;
|
|
391
|
-
} catch (err) {
|
|
392
|
-
this.addError(viewer.id, err);
|
|
393
|
-
this.emit("error", index, err);
|
|
394
|
-
throw err;
|
|
395
|
-
} finally {
|
|
396
|
-
this.decrementLoading(viewer.id);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
async loadVolumes(index, opts) {
|
|
400
|
-
const results = [];
|
|
401
|
-
for (const o of opts) {
|
|
402
|
-
results.push(await this.loadVolume(index, o));
|
|
403
|
-
}
|
|
404
|
-
return results;
|
|
405
|
-
}
|
|
406
|
-
async removeVolume(index, url) {
|
|
407
|
-
const viewer = this.viewers[index];
|
|
408
|
-
if (!viewer)
|
|
409
|
-
return;
|
|
410
|
-
const nv = viewer.niivue;
|
|
411
|
-
const volIdx = nv.volumes.findIndex((v) => v.url === url || v.name === url);
|
|
412
|
-
if (volIdx >= 0) {
|
|
413
|
-
nv.model.removeVolume(volIdx);
|
|
414
|
-
await nv.updateGLVolume();
|
|
415
|
-
this.emit("volumeRemoved", index, url);
|
|
416
|
-
if (this.broadcasting) {
|
|
417
|
-
this.rewireBroadcasting();
|
|
418
|
-
}
|
|
419
|
-
this.notify();
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
findVolume(viewerIndex, volumeIndex) {
|
|
423
|
-
const viewer = this.viewers[viewerIndex];
|
|
424
|
-
if (!viewer)
|
|
425
|
-
return;
|
|
426
|
-
const nv = viewer.niivue;
|
|
427
|
-
const vol = nv.volumes[volumeIndex];
|
|
428
|
-
if (!vol)
|
|
429
|
-
return;
|
|
430
|
-
return { nv, vol, volumeIndex };
|
|
431
|
-
}
|
|
432
|
-
async setColormap(viewerIndex, volumeIndex, colormap) {
|
|
433
|
-
const found = this.findVolume(viewerIndex, volumeIndex);
|
|
434
|
-
if (!found)
|
|
435
|
-
return;
|
|
436
|
-
await found.nv.setVolume(found.volumeIndex, { colormap });
|
|
437
|
-
this.emit("colormapChanged", viewerIndex, volumeIndex, colormap);
|
|
438
|
-
this.notify();
|
|
439
|
-
}
|
|
440
|
-
async setCalMinMax(viewerIndex, volumeIndex, calMin, calMax) {
|
|
441
|
-
const found = this.findVolume(viewerIndex, volumeIndex);
|
|
442
|
-
if (!found)
|
|
443
|
-
return;
|
|
444
|
-
await found.nv.setVolume(found.volumeIndex, { calMin, calMax });
|
|
445
|
-
this.emit("intensityChanged", viewerIndex, volumeIndex, calMin, calMax);
|
|
446
|
-
this.notify();
|
|
447
|
-
}
|
|
448
|
-
async setOpacity(viewerIndex, volumeIndex, opacity) {
|
|
449
|
-
const found = this.findVolume(viewerIndex, volumeIndex);
|
|
450
|
-
if (!found)
|
|
451
|
-
return;
|
|
452
|
-
await found.nv.setVolume(found.volumeIndex, { opacity });
|
|
453
|
-
this.emit("opacityChanged", viewerIndex, volumeIndex, opacity);
|
|
454
|
-
this.notify();
|
|
455
|
-
}
|
|
456
|
-
incrementLoading(id) {
|
|
457
|
-
this.loadingCounts.set(id, (this.loadingCounts.get(id) ?? 0) + 1);
|
|
458
|
-
this.notify();
|
|
459
|
-
}
|
|
460
|
-
decrementLoading(id) {
|
|
461
|
-
const count = (this.loadingCounts.get(id) ?? 1) - 1;
|
|
462
|
-
this.loadingCounts.set(id, Math.max(0, count));
|
|
463
|
-
this.notify();
|
|
464
|
-
}
|
|
465
|
-
addError(id, error) {
|
|
466
|
-
const errors = this.viewerErrors.get(id) ?? [];
|
|
467
|
-
errors.push(error);
|
|
468
|
-
this.viewerErrors.set(id, errors);
|
|
469
|
-
}
|
|
470
|
-
addViewer(options) {
|
|
471
|
-
if (!this.containerElement) {
|
|
472
|
-
throw new Error("Container element not set");
|
|
473
|
-
}
|
|
474
|
-
if (!this.canAddViewer()) {
|
|
475
|
-
throw new Error(`Cannot add viewer: slot limit of ${this.slots} reached`);
|
|
476
|
-
}
|
|
477
|
-
const containerDiv = document.createElement("div");
|
|
478
|
-
containerDiv.className = "niivue-canvas-container";
|
|
479
|
-
containerDiv.style.position = "relative";
|
|
480
|
-
containerDiv.style.overflow = "hidden";
|
|
481
|
-
const canvas = document.createElement("canvas");
|
|
482
|
-
canvas.className = "niivue-canvas";
|
|
483
|
-
canvas.style.position = "absolute";
|
|
484
|
-
canvas.style.top = "0";
|
|
485
|
-
canvas.style.left = "0";
|
|
486
|
-
canvas.style.width = "100%";
|
|
487
|
-
canvas.style.height = "100%";
|
|
488
|
-
containerDiv.appendChild(canvas);
|
|
489
|
-
this.containerElement.appendChild(containerDiv);
|
|
490
|
-
const mergedOptions = {
|
|
491
|
-
...defaultViewerOptions,
|
|
492
|
-
...this.viewerDefaults,
|
|
493
|
-
...options
|
|
494
|
-
};
|
|
495
|
-
const niivue = new NiiVueGPU(mergedOptions);
|
|
496
|
-
const id = `nv-${this.nextId++}`;
|
|
497
|
-
this.viewerSliceLayouts.set(id, null);
|
|
498
|
-
this.loadingCounts.set(id, 0);
|
|
499
|
-
this.viewerErrors.set(id, []);
|
|
500
|
-
const viewer = {
|
|
501
|
-
id,
|
|
502
|
-
niivue,
|
|
503
|
-
canvasElement: canvas,
|
|
504
|
-
containerDiv
|
|
505
|
-
};
|
|
506
|
-
this.viewers.push(viewer);
|
|
507
|
-
this.viewersById.set(id, viewer);
|
|
508
|
-
this.updateLayout();
|
|
509
|
-
const index = this.viewers.length - 1;
|
|
510
|
-
niivue.addEventListener("locationChange", (evt) => {
|
|
511
|
-
this.emit("locationChange", index, evt.detail);
|
|
512
|
-
});
|
|
513
|
-
niivue.addEventListener("volumeLoaded", (evt) => {
|
|
514
|
-
this.emit("imageLoaded", index, evt.detail.volume);
|
|
515
|
-
if (this.broadcasting) {
|
|
516
|
-
this.rewireBroadcasting();
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
niivue.addEventListener("meshLoaded", () => {
|
|
520
|
-
if (this.broadcasting) {
|
|
521
|
-
this.rewireBroadcasting();
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
this.onViewerCreated?.(niivue, index);
|
|
525
|
-
this.emit("viewerCreated", niivue, index);
|
|
526
|
-
this.notify();
|
|
527
|
-
const ready = niivue.attachToCanvas(canvas).then(() => {
|
|
528
|
-
this.applySliceLayout(niivue, null);
|
|
529
|
-
if (this.broadcasting) {
|
|
530
|
-
this.setBroadcasting(true);
|
|
531
|
-
}
|
|
532
|
-
this.notify();
|
|
533
|
-
return viewer;
|
|
534
|
-
});
|
|
535
|
-
return ready;
|
|
536
|
-
}
|
|
537
|
-
removeViewer(index, shouldNotify = true) {
|
|
538
|
-
if (index < 0 || index >= this.viewers.length)
|
|
539
|
-
return;
|
|
540
|
-
const viewer = this.viewers[index];
|
|
541
|
-
if (!viewer)
|
|
542
|
-
return;
|
|
543
|
-
this.viewersById.delete(viewer.id);
|
|
544
|
-
this.viewerSliceLayouts.delete(viewer.id);
|
|
545
|
-
this.loadingCounts.delete(viewer.id);
|
|
546
|
-
this.viewerErrors.delete(viewer.id);
|
|
547
|
-
this.disposeViewer(viewer);
|
|
548
|
-
viewer.containerDiv.remove();
|
|
549
|
-
this.viewers.splice(index, 1);
|
|
550
|
-
this.updateLayout();
|
|
551
|
-
if (this.broadcasting && this.viewers.length > 0) {
|
|
552
|
-
this.setBroadcasting(true);
|
|
553
|
-
}
|
|
554
|
-
this.emit("viewerRemoved", index);
|
|
555
|
-
if (shouldNotify) {
|
|
556
|
-
this.notify();
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
disposeViewer(viewer) {
|
|
560
|
-
viewer.niivue.destroy();
|
|
561
|
-
viewer.canvasElement.width = 0;
|
|
562
|
-
viewer.canvasElement.height = 0;
|
|
563
|
-
}
|
|
564
|
-
clearViewers() {
|
|
565
|
-
this.broadcasting = false;
|
|
566
|
-
this.viewers.forEach((viewer) => {
|
|
567
|
-
this.disposeViewer(viewer);
|
|
568
|
-
viewer.containerDiv.remove();
|
|
569
|
-
});
|
|
570
|
-
this.viewers = [];
|
|
571
|
-
this.viewersById.clear();
|
|
572
|
-
this.loadingCounts.clear();
|
|
573
|
-
this.viewerErrors.clear();
|
|
574
|
-
this.notify();
|
|
575
|
-
}
|
|
576
|
-
reset() {
|
|
577
|
-
this.clearViewers();
|
|
578
|
-
this.currentLayout = "1x1";
|
|
579
|
-
this.slots = this.layouts[this.currentLayout]?.slots ?? 1;
|
|
580
|
-
this.notify();
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// src/hooks.ts
|
|
585
|
-
function useScene(controller, layouts, viewerDefaults) {
|
|
586
|
-
const scene = useMemo(() => controller ?? new NvSceneController(layouts, viewerDefaults), [controller, viewerDefaults, layouts]);
|
|
587
|
-
const snapshot = useSyncExternalStore(scene.subscribe, scene.getSnapshot, scene.getSnapshot);
|
|
588
|
-
return { scene, snapshot };
|
|
589
|
-
}
|
|
590
|
-
function useNiivue(scene, index) {
|
|
591
|
-
const snapshot = useSyncExternalStore(scene.subscribe, scene.getSnapshot, scene.getSnapshot);
|
|
592
|
-
return index < snapshot.viewerCount ? scene.getNiivue(index) : undefined;
|
|
593
|
-
}
|
|
594
|
-
function useSceneEvent(scene, event, callback) {
|
|
595
|
-
const callbackRef = useRef(callback);
|
|
596
|
-
callbackRef.current = callback;
|
|
597
|
-
useEffect(() => {
|
|
598
|
-
const handler = (...args) => {
|
|
599
|
-
callbackRef.current(...args);
|
|
600
|
-
};
|
|
601
|
-
return scene.on(event, handler);
|
|
602
|
-
}, [scene, event]);
|
|
603
|
-
}
|
|
604
|
-
// src/nvscene.tsx
|
|
605
|
-
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
606
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
607
|
-
var NvScene = ({
|
|
608
|
-
scene,
|
|
609
|
-
className,
|
|
610
|
-
style,
|
|
611
|
-
initialLayout = "1x1"
|
|
612
|
-
}) => {
|
|
613
|
-
const containerRef = useRef2(null);
|
|
614
|
-
useEffect2(() => {
|
|
615
|
-
if (containerRef.current) {
|
|
616
|
-
scene.setContainerElement(containerRef.current);
|
|
617
|
-
if (scene.viewers.length === 0) {
|
|
618
|
-
scene.setLayout(initialLayout);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
return () => {
|
|
622
|
-
scene.setContainerElement(null);
|
|
623
|
-
};
|
|
624
|
-
}, [scene, initialLayout]);
|
|
625
|
-
useEffect2(() => {
|
|
626
|
-
const el = containerRef.current;
|
|
627
|
-
if (!el)
|
|
628
|
-
return;
|
|
629
|
-
const ro = new ResizeObserver(() => scene.updateLayout());
|
|
630
|
-
ro.observe(el);
|
|
631
|
-
return () => ro.disconnect();
|
|
632
|
-
}, [scene]);
|
|
633
|
-
return /* @__PURE__ */ jsx2("div", {
|
|
634
|
-
ref: containerRef,
|
|
635
|
-
className,
|
|
636
|
-
style
|
|
637
|
-
});
|
|
638
|
-
};
|
|
639
|
-
// src/nvviewer.tsx
|
|
640
|
-
import NiiVueGPU2, { SLICE_TYPE as SLICE_TYPE2 } from "@niivue/niivue";
|
|
641
|
-
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
642
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
643
|
-
function extractVisualProps(opts) {
|
|
644
|
-
return {
|
|
645
|
-
colormap: opts.colormap,
|
|
646
|
-
calMin: opts.calMin,
|
|
647
|
-
calMax: opts.calMax,
|
|
648
|
-
opacity: opts.opacity
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
var NvViewer = ({
|
|
652
|
-
volumes,
|
|
653
|
-
options,
|
|
654
|
-
sliceType = SLICE_TYPE2.AXIAL,
|
|
655
|
-
className,
|
|
656
|
-
style,
|
|
657
|
-
onLocationChange,
|
|
658
|
-
onImageLoaded,
|
|
659
|
-
onError
|
|
660
|
-
}) => {
|
|
661
|
-
const containerRef = useRef3(null);
|
|
662
|
-
const nvRef = useRef3(null);
|
|
663
|
-
const loadedVolumesRef = useRef3(new Map);
|
|
664
|
-
const onLocationChangeRef = useRef3(onLocationChange);
|
|
665
|
-
onLocationChangeRef.current = onLocationChange;
|
|
666
|
-
const onImageLoadedRef = useRef3(onImageLoaded);
|
|
667
|
-
onImageLoadedRef.current = onImageLoaded;
|
|
668
|
-
const onErrorRef = useRef3(onError);
|
|
669
|
-
onErrorRef.current = onError;
|
|
670
|
-
useEffect3(() => {
|
|
671
|
-
const container = containerRef.current;
|
|
672
|
-
if (!container)
|
|
673
|
-
return;
|
|
674
|
-
const canvas = document.createElement("canvas");
|
|
675
|
-
canvas.className = "niivue-canvas";
|
|
676
|
-
canvas.style.position = "absolute";
|
|
677
|
-
canvas.style.top = "0";
|
|
678
|
-
canvas.style.left = "0";
|
|
679
|
-
canvas.style.width = "100%";
|
|
680
|
-
canvas.style.height = "100%";
|
|
681
|
-
container.appendChild(canvas);
|
|
682
|
-
const mergedOptions = {
|
|
683
|
-
...defaultViewerOptions,
|
|
684
|
-
...options
|
|
685
|
-
};
|
|
686
|
-
const nv = new NiiVueGPU2(mergedOptions);
|
|
687
|
-
nv.addEventListener("locationChange", (evt) => {
|
|
688
|
-
onLocationChangeRef.current?.(evt.detail);
|
|
689
|
-
});
|
|
690
|
-
nv.addEventListener("volumeLoaded", (evt) => {
|
|
691
|
-
onImageLoadedRef.current?.(evt.detail.volume);
|
|
692
|
-
});
|
|
693
|
-
nv.attachToCanvas(canvas).then(() => {
|
|
694
|
-
nv.sliceType = sliceType;
|
|
695
|
-
});
|
|
696
|
-
nvRef.current = nv;
|
|
697
|
-
const ro = new ResizeObserver(() => {
|
|
698
|
-
nv.resize();
|
|
699
|
-
});
|
|
700
|
-
ro.observe(container);
|
|
701
|
-
return () => {
|
|
702
|
-
ro.disconnect();
|
|
703
|
-
nv.destroy();
|
|
704
|
-
canvas.width = 0;
|
|
705
|
-
canvas.height = 0;
|
|
706
|
-
canvas.remove();
|
|
707
|
-
nvRef.current = null;
|
|
708
|
-
loadedVolumesRef.current.clear();
|
|
709
|
-
};
|
|
710
|
-
}, [sliceType, options]);
|
|
711
|
-
useEffect3(() => {
|
|
712
|
-
const nv = nvRef.current;
|
|
713
|
-
if (nv) {
|
|
714
|
-
nv.sliceType = sliceType;
|
|
715
|
-
}
|
|
716
|
-
}, [sliceType]);
|
|
717
|
-
useEffect3(() => {
|
|
718
|
-
const nv = nvRef.current;
|
|
719
|
-
if (!nv)
|
|
720
|
-
return;
|
|
721
|
-
const desiredUrls = new Set((volumes ?? []).map((v) => typeof v.url === "string" ? v.url : v.url.name));
|
|
722
|
-
const currentVolumes = loadedVolumesRef.current;
|
|
723
|
-
for (const url of currentVolumes.keys()) {
|
|
724
|
-
if (!desiredUrls.has(url)) {
|
|
725
|
-
const volIdx = nv.volumes.findIndex((v) => v.url === url || v.name === url);
|
|
726
|
-
if (volIdx >= 0) {
|
|
727
|
-
nv.model.removeVolume(volIdx);
|
|
728
|
-
nv.updateGLVolume();
|
|
729
|
-
}
|
|
730
|
-
currentVolumes.delete(url);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
for (const opts of volumes ?? []) {
|
|
734
|
-
const urlKey = typeof opts.url === "string" ? opts.url : opts.url.name;
|
|
735
|
-
if (!currentVolumes.has(urlKey)) {
|
|
736
|
-
const props = extractVisualProps(opts);
|
|
737
|
-
currentVolumes.set(urlKey, props);
|
|
738
|
-
nv.addVolume(opts).catch((err) => {
|
|
739
|
-
currentVolumes.delete(urlKey);
|
|
740
|
-
onErrorRef.current?.(err);
|
|
741
|
-
});
|
|
742
|
-
} else {
|
|
743
|
-
const prev = currentVolumes.get(urlKey);
|
|
744
|
-
const next = extractVisualProps(opts);
|
|
745
|
-
const volIdx = nv.volumes.findIndex((v) => v.url === urlKey || v.name === urlKey);
|
|
746
|
-
if (volIdx < 0)
|
|
747
|
-
continue;
|
|
748
|
-
const updates = {};
|
|
749
|
-
if (next.colormap !== undefined && next.colormap !== prev.colormap) {
|
|
750
|
-
updates.colormap = next.colormap;
|
|
751
|
-
}
|
|
752
|
-
if (next.calMin !== undefined && next.calMin !== prev.calMin) {
|
|
753
|
-
updates.calMin = next.calMin;
|
|
754
|
-
}
|
|
755
|
-
if (next.calMax !== undefined && next.calMax !== prev.calMax) {
|
|
756
|
-
updates.calMax = next.calMax;
|
|
757
|
-
}
|
|
758
|
-
if (next.opacity !== undefined && next.opacity !== prev.opacity) {
|
|
759
|
-
updates.opacity = next.opacity;
|
|
760
|
-
}
|
|
761
|
-
if (Object.keys(updates).length > 0) {
|
|
762
|
-
nv.setVolume(volIdx, updates);
|
|
763
|
-
}
|
|
764
|
-
currentVolumes.set(urlKey, next);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}, [volumes]);
|
|
768
|
-
return /* @__PURE__ */ jsx3("div", {
|
|
769
|
-
ref: containerRef,
|
|
770
|
-
className,
|
|
771
|
-
style: { position: "relative", ...style }
|
|
772
|
-
});
|
|
773
|
-
};
|
|
774
|
-
export {
|
|
775
|
-
useSceneEvent,
|
|
776
|
-
useSceneContext,
|
|
777
|
-
useScene,
|
|
778
|
-
useNiivue,
|
|
779
|
-
triSliceLayout,
|
|
780
|
-
stackedSliceLayout,
|
|
781
|
-
splitSliceLayout,
|
|
782
|
-
quadSliceLayout,
|
|
783
|
-
heroRenderSliceLayout,
|
|
784
|
-
defaultViewerOptions,
|
|
785
|
-
defaultSliceLayouts,
|
|
786
|
-
defaultSliceLayout,
|
|
787
|
-
defaultLayouts,
|
|
788
|
-
SLICE_TYPE,
|
|
789
|
-
NvViewer,
|
|
790
|
-
NvSceneProvider,
|
|
791
|
-
NvSceneController,
|
|
792
|
-
NvScene
|
|
793
|
-
};
|
package/dist/types/context.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
import type { NvSceneController } from './nvscene-controller';
|
|
3
|
-
export declare function NvSceneProvider({ scene, children, }: {
|
|
4
|
-
scene: NvSceneController;
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export declare function useSceneContext(): NvSceneController;
|
package/dist/types/hooks.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { NiiVueOptions } from '@niivue/niivue';
|
|
2
|
-
import type { LayoutConfig } from './layouts';
|
|
3
|
-
import { NvSceneController } from './nvscene-controller';
|
|
4
|
-
import type { NvSceneEventMap } from './types';
|
|
5
|
-
export declare function useScene(controller?: NvSceneController, layouts?: Record<string, LayoutConfig>, viewerDefaults?: Partial<NiiVueOptions>): {
|
|
6
|
-
scene: NvSceneController;
|
|
7
|
-
snapshot: import("./nvscene-controller").NvSceneControllerSnapshot;
|
|
8
|
-
};
|
|
9
|
-
export declare function useNiivue(scene: NvSceneController, index: number): import("@niivue/niivue").default | undefined;
|
|
10
|
-
export declare function useSceneEvent<E extends keyof NvSceneEventMap>(scene: NvSceneController, event: E, callback: NvSceneEventMap[E]): void;
|
package/dist/types/layouts.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface CanvasPosition {
|
|
2
|
-
top: string;
|
|
3
|
-
left: string;
|
|
4
|
-
width: string;
|
|
5
|
-
height: string;
|
|
6
|
-
}
|
|
7
|
-
export type LayoutFunction = (containerElement: HTMLElement, index: number, total: number) => CanvasPosition;
|
|
8
|
-
export interface LayoutConfig {
|
|
9
|
-
slots: number;
|
|
10
|
-
label: string;
|
|
11
|
-
layoutFunction: LayoutFunction;
|
|
12
|
-
}
|
|
13
|
-
export declare const layout1x1: LayoutFunction;
|
|
14
|
-
export declare const layout2x2: LayoutFunction;
|
|
15
|
-
export declare const layout1x2: LayoutFunction;
|
|
16
|
-
export declare const layout2x1: LayoutFunction;
|
|
17
|
-
export declare const layout1x3: LayoutFunction;
|
|
18
|
-
export declare const layout3x1: LayoutFunction;
|
|
19
|
-
export declare const layout3x3: LayoutFunction;
|
|
20
|
-
export declare const defaultLayouts: Record<string, LayoutConfig>;
|
package/dist/types/lib.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { NvSceneProvider, useSceneContext } from './context';
|
|
2
|
-
export { useNiivue, useScene, useSceneEvent } from './hooks';
|
|
3
|
-
export { defaultLayouts } from './layouts';
|
|
4
|
-
export { NvScene } from './nvscene';
|
|
5
|
-
export type { BroadcastOptions, NiivueCallback, NvSceneControllerSnapshot, SliceLayoutConfig, SliceLayoutTile, ViewerSlot, } from './nvscene-controller';
|
|
6
|
-
export { defaultSliceLayout, defaultSliceLayouts, defaultViewerOptions, heroRenderSliceLayout, NvSceneController, quadSliceLayout, SLICE_TYPE, splitSliceLayout, stackedSliceLayout, triSliceLayout, } from './nvscene-controller';
|
|
7
|
-
export type { NvViewerProps } from './nvviewer';
|
|
8
|
-
export { NvViewer } from './nvviewer';
|
|
9
|
-
export type { ImageFromUrlOptions, NiiVueOptions, NVImage, NvSceneEventMap, ViewerState, } from './types';
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { NiiVueOptions, NVImage } from '@niivue/niivue';
|
|
2
|
-
import NiiVueGPU, { SLICE_TYPE } from '@niivue/niivue';
|
|
3
|
-
import type { LayoutConfig } from './layouts';
|
|
4
|
-
import type { ImageFromUrlOptions, NvSceneEventMap, ViewerState } from './types';
|
|
5
|
-
export { SLICE_TYPE };
|
|
6
|
-
export type NiivueCallback = (nv: NiiVueGPU, index: number) => void;
|
|
7
|
-
export interface ViewerSlot {
|
|
8
|
-
id: string;
|
|
9
|
-
niivue: NiiVueGPU;
|
|
10
|
-
canvasElement: HTMLCanvasElement;
|
|
11
|
-
containerDiv: HTMLDivElement;
|
|
12
|
-
}
|
|
13
|
-
export interface NvSceneControllerSnapshot {
|
|
14
|
-
currentLayout: string;
|
|
15
|
-
viewerCount: number;
|
|
16
|
-
slots: number;
|
|
17
|
-
isBroadcasting: boolean;
|
|
18
|
-
isLoading: boolean;
|
|
19
|
-
viewerStates: ViewerState[];
|
|
20
|
-
}
|
|
21
|
-
export interface BroadcastOptions {
|
|
22
|
-
'2d': boolean;
|
|
23
|
-
'3d': boolean;
|
|
24
|
-
}
|
|
25
|
-
export interface SliceLayoutTile {
|
|
26
|
-
sliceType: number;
|
|
27
|
-
position: [number, number, number, number];
|
|
28
|
-
sliceMM?: number;
|
|
29
|
-
}
|
|
30
|
-
export interface SliceLayoutConfig {
|
|
31
|
-
label: string;
|
|
32
|
-
layout: SliceLayoutTile[];
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Default slice layout: Axial as hero (80% height), coronal and sagittal below (20% height each, side by side)
|
|
36
|
-
*/
|
|
37
|
-
export declare const defaultSliceLayout: SliceLayoutTile[];
|
|
38
|
-
/**
|
|
39
|
-
* Split view: sagittal left, coronal/axial stacked on the right.
|
|
40
|
-
*/
|
|
41
|
-
export declare const splitSliceLayout: SliceLayoutTile[];
|
|
42
|
-
/**
|
|
43
|
-
* Tri-fan: three equal horizontal panels.
|
|
44
|
-
*/
|
|
45
|
-
export declare const triSliceLayout: SliceLayoutTile[];
|
|
46
|
-
/**
|
|
47
|
-
* Stacked: three equal vertical stacks.
|
|
48
|
-
*/
|
|
49
|
-
export declare const stackedSliceLayout: SliceLayoutTile[];
|
|
50
|
-
/**
|
|
51
|
-
* Quad: axial/coronal/sagittal with a render tile.
|
|
52
|
-
*/
|
|
53
|
-
export declare const quadSliceLayout: SliceLayoutTile[];
|
|
54
|
-
/**
|
|
55
|
-
* Hero render with three small orthogonal slices below.
|
|
56
|
-
*/
|
|
57
|
-
export declare const heroRenderSliceLayout: SliceLayoutTile[];
|
|
58
|
-
export declare const defaultSliceLayouts: Record<string, SliceLayoutConfig>;
|
|
59
|
-
type Listener = () => void;
|
|
60
|
-
export declare const defaultViewerOptions: Partial<NiiVueOptions>;
|
|
61
|
-
/**
|
|
62
|
-
* An NvSceneController is a declarative representation of what we want to render with Niivue.
|
|
63
|
-
*
|
|
64
|
-
* A scene can contain multiple Niivue instances. Each Niivue instance has its own
|
|
65
|
-
* canvas element for the WebGL2 context. All canvas elements are wrapped in container
|
|
66
|
-
* divs that are children of the main scene container element.
|
|
67
|
-
*
|
|
68
|
-
* Canvas elements are added/removed on-demand based on the scene definition.
|
|
69
|
-
*
|
|
70
|
-
* The scene layout controls the position of each canvas via layout functions that
|
|
71
|
-
* return absolute positioning styles. The scene container is responsive and maintains
|
|
72
|
-
* the requested layout proportionally.
|
|
73
|
-
*/
|
|
74
|
-
export declare class NvSceneController {
|
|
75
|
-
containerElement: HTMLElement | null;
|
|
76
|
-
viewers: ViewerSlot[];
|
|
77
|
-
currentLayout: string;
|
|
78
|
-
slots: number;
|
|
79
|
-
layouts: Record<string, LayoutConfig>;
|
|
80
|
-
onViewerCreated?: NiivueCallback;
|
|
81
|
-
private listeners;
|
|
82
|
-
private snapshotCache;
|
|
83
|
-
private nextId;
|
|
84
|
-
private viewersById;
|
|
85
|
-
private broadcasting;
|
|
86
|
-
private broadcastOptions;
|
|
87
|
-
private viewerSliceLayouts;
|
|
88
|
-
private viewerDefaults;
|
|
89
|
-
private eventListeners;
|
|
90
|
-
private loadingCounts;
|
|
91
|
-
private viewerErrors;
|
|
92
|
-
constructor(layouts?: Record<string, LayoutConfig>, viewerDefaults?: Partial<NiiVueOptions>);
|
|
93
|
-
on<E extends keyof NvSceneEventMap>(event: E, cb: NvSceneEventMap[E]): () => void;
|
|
94
|
-
off<E extends keyof NvSceneEventMap>(event: E, cb: NvSceneEventMap[E]): void;
|
|
95
|
-
private emit;
|
|
96
|
-
subscribe: (listener: Listener) => (() => void);
|
|
97
|
-
getSnapshot: () => NvSceneControllerSnapshot;
|
|
98
|
-
private notify;
|
|
99
|
-
setContainerElement(element: HTMLElement | null): void;
|
|
100
|
-
setLayout(layoutName: string): void;
|
|
101
|
-
updateLayout(): void;
|
|
102
|
-
canAddViewer(): boolean;
|
|
103
|
-
getNiivue(index: number): NiiVueGPU | undefined;
|
|
104
|
-
getAllNiivue(): NiiVueGPU[];
|
|
105
|
-
forEachNiivue(callback: NiivueCallback): void;
|
|
106
|
-
private hasSyncableContent;
|
|
107
|
-
private safeBroadcastTo;
|
|
108
|
-
private rewireBroadcasting;
|
|
109
|
-
setBroadcasting(enabled: boolean, options?: Partial<BroadcastOptions>): void;
|
|
110
|
-
isBroadcasting(): boolean;
|
|
111
|
-
setViewerSliceLayout(index: number, layout: SliceLayoutTile[] | null): void;
|
|
112
|
-
getViewerSliceLayout(index: number): SliceLayoutTile[] | null;
|
|
113
|
-
private applySliceLayout;
|
|
114
|
-
getNiivueById(id: string): NiiVueGPU | undefined;
|
|
115
|
-
getViewerById(id: string): ViewerSlot | undefined;
|
|
116
|
-
loadVolume(index: number, opts: ImageFromUrlOptions): Promise<NVImage>;
|
|
117
|
-
loadVolumes(index: number, opts: ImageFromUrlOptions[]): Promise<NVImage[]>;
|
|
118
|
-
removeVolume(index: number, url: string): Promise<void>;
|
|
119
|
-
/**
|
|
120
|
-
* Look up the Niivue instance and NVImage at the given viewer and volume indices.
|
|
121
|
-
* Returns undefined if either index is out of bounds.
|
|
122
|
-
*/
|
|
123
|
-
private findVolume;
|
|
124
|
-
/** Set the colormap for a volume at the given viewer and volume index. */
|
|
125
|
-
setColormap(viewerIndex: number, volumeIndex: number, colormap: string): Promise<void>;
|
|
126
|
-
/** Set the intensity range (calMin / calMax) for a volume at the given viewer and volume index. */
|
|
127
|
-
setCalMinMax(viewerIndex: number, volumeIndex: number, calMin: number, calMax: number): Promise<void>;
|
|
128
|
-
/** Set the opacity for a volume at the given viewer and volume index. */
|
|
129
|
-
setOpacity(viewerIndex: number, volumeIndex: number, opacity: number): Promise<void>;
|
|
130
|
-
private incrementLoading;
|
|
131
|
-
private decrementLoading;
|
|
132
|
-
private addError;
|
|
133
|
-
addViewer(options?: Partial<NiiVueOptions>): Promise<ViewerSlot>;
|
|
134
|
-
removeViewer(index: number, shouldNotify?: boolean): void;
|
|
135
|
-
private disposeViewer;
|
|
136
|
-
clearViewers(): void;
|
|
137
|
-
reset(): void;
|
|
138
|
-
}
|
package/dist/types/nvscene.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { CSSProperties } from 'react';
|
|
2
|
-
import type { NvSceneController } from './nvscene-controller';
|
|
3
|
-
interface NiivueControllerProps {
|
|
4
|
-
scene: NvSceneController;
|
|
5
|
-
className?: string;
|
|
6
|
-
style?: CSSProperties;
|
|
7
|
-
initialLayout?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare const NvScene: ({ scene, className, style, initialLayout, }: NiivueControllerProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
export {};
|
package/dist/types/nvviewer.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { NiiVueOptions, NVImage } from '@niivue/niivue';
|
|
2
|
-
import type { CSSProperties } from 'react';
|
|
3
|
-
import type { ImageFromUrlOptions } from './types';
|
|
4
|
-
export interface NvViewerProps {
|
|
5
|
-
volumes?: ImageFromUrlOptions[];
|
|
6
|
-
options?: Partial<NiiVueOptions>;
|
|
7
|
-
sliceType?: number;
|
|
8
|
-
className?: string;
|
|
9
|
-
style?: CSSProperties;
|
|
10
|
-
onLocationChange?: (data: unknown) => void;
|
|
11
|
-
onImageLoaded?: (volume: NVImage) => void;
|
|
12
|
-
onError?: (error: unknown) => void;
|
|
13
|
-
}
|
|
14
|
-
export declare const NvViewer: ({ volumes, options, sliceType, className, style, onLocationChange, onImageLoaded, onError, }: NvViewerProps) => import("react/jsx-runtime").JSX.Element;
|
package/dist/types/types.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type NiiVueGPU from '@niivue/niivue';
|
|
2
|
-
import type { ImageFromUrlOptions, NiiVueOptions, NVImage } from '@niivue/niivue';
|
|
3
|
-
export type { ImageFromUrlOptions, NiiVueOptions, NVImage };
|
|
4
|
-
export interface NvSceneEventMap {
|
|
5
|
-
viewerCreated: (nv: NiiVueGPU, index: number) => void;
|
|
6
|
-
viewerRemoved: (index: number) => void;
|
|
7
|
-
locationChange: (viewerIndex: number, data: unknown) => void;
|
|
8
|
-
imageLoaded: (viewerIndex: number, volume: NVImage) => void;
|
|
9
|
-
error: (viewerIndex: number, error: unknown) => void;
|
|
10
|
-
volumeAdded: (viewerIndex: number, imageOptions: ImageFromUrlOptions, image: NVImage) => void;
|
|
11
|
-
volumeRemoved: (viewerIndex: number, url: string) => void;
|
|
12
|
-
colormapChanged: (viewerIndex: number, volumeIndex: number, colormap: string) => void;
|
|
13
|
-
intensityChanged: (viewerIndex: number, volumeIndex: number, cal_min: number, cal_max: number) => void;
|
|
14
|
-
opacityChanged: (viewerIndex: number, volumeIndex: number, opacity: number) => void;
|
|
15
|
-
}
|
|
16
|
-
export interface ViewerState {
|
|
17
|
-
id: string;
|
|
18
|
-
loading: number;
|
|
19
|
-
errors: unknown[];
|
|
20
|
-
}
|