@niivue/niivue 0.44.0 → 0.44.2-dev.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niivue/niivue",
3
- "version": "0.44.0",
3
+ "version": "0.44.2-dev.1",
4
4
  "description": "minimal webgl2 nifti image viewer",
5
5
  "types": "./build/index.d.ts",
6
6
  "main": "./build/index.js",
@@ -72,31 +72,32 @@
72
72
  "rxjs": "^7.8.1"
73
73
  },
74
74
  "devDependencies": {
75
- "@playwright/test": "^1.42.1",
76
- "@rollup/plugin-commonjs": "^25.0.7",
75
+ "@playwright/test": "^1.45.2",
76
+ "@rollup/plugin-commonjs": "^26.0.1",
77
77
  "@types/array-equal": "^1.0.2",
78
- "@types/node": "^20.12.2",
78
+ "@types/node": "^20.14.11",
79
79
  "@types/ungap__structured-clone": "^1.2.0",
80
- "@typescript-eslint/eslint-plugin": "^7.5.0",
81
- "@typescript-eslint/parser": "^7.5.0",
82
- "@vitest/coverage-v8": "^1.4.0",
80
+ "@typescript-eslint/eslint-plugin": "^7.16.1",
81
+ "@typescript-eslint/parser": "^7.16.1",
82
+ "@vitest/coverage-v8": "^2.0.3",
83
83
  "eslint": "^8.57.0",
84
84
  "eslint-config-prettier": "^9.1.0",
85
85
  "eslint-config-standard": "^17.1.0",
86
- "eslint-plugin-prettier": "^5.1.3",
87
- "eslint-plugin-tsdoc": "^0.2.17",
86
+ "eslint-plugin-prettier": "^5.2.1",
87
+ "eslint-plugin-tsdoc": "^0.3.0",
88
88
  "express": "^4.19.2",
89
- "happy-dom": "^14.3.10",
89
+ "happy-dom": "^14.12.3",
90
90
  "http-server": "^14.1.1",
91
- "prettier": "^3.2.5",
91
+ "prettier": "^3.3.3",
92
92
  "regenerator-runtime": "^0.14.1",
93
- "tsup": "^8.0.2",
94
- "typedoc": "^0.25.12",
95
- "typescript": "^5.4.3",
96
- "vite": "^5.2.7",
97
- "vitest": "^1.4.0"
93
+ "terser": "^5.31.3",
94
+ "tsup": "^8.1.2",
95
+ "typedoc": "^0.26.4",
96
+ "typescript": "^5.5.3",
97
+ "vite": "^5.3.4",
98
+ "vitest": "^2.0.3"
98
99
  },
99
100
  "optionalDependencies": {
100
- "@rollup/rollup-linux-x64-gnu": "^4.13.2"
101
+ "@rollup/rollup-linux-x64-gnu": "^4.18.1"
101
102
  }
102
103
  }
package/src/index.html CHANGED
@@ -58,7 +58,30 @@
58
58
  class="slider"
59
59
  id="fiberCalMin"
60
60
  />
61
- <button id="about">About</button>
61
+ <label for="layoutSelect">Layout</label>
62
+ <select id="layoutSelect">
63
+ <option value="0" selected>Auto</option>
64
+ <option value="1">Column</option>
65
+ <option value="2">Grid</option>
66
+ <option value="3">Row</option>
67
+ </select>
68
+ <label for="renderingSelect">MultiplanarRendering</label>
69
+ <select id="renderingSelect">
70
+ <option value="0">Never</option>
71
+ <option value="1">Always</option>
72
+ <option value="2" selected>Auto</option>
73
+ </select>
74
+ <select id="sliceType">
75
+ <option value="0">Axial</option>
76
+ <option value="1">Coronal</option>
77
+ <option value="2">Sagittal</option>
78
+ <option value="4">Render</option>
79
+ <option value="3" selected>Multiplanar</option>
80
+ </select>
81
+ <label for="equalCheck">EqualSize</label>
82
+ <input type="checkbox" id="equalCheck" checked />
83
+ <label for="cornerCheck">CornerText</label>
84
+ <input type="checkbox" id="cornerCheck" unchecked />
62
85
  </header>
63
86
  <main id="canvas-container">
64
87
  <canvas id="gl1"></canvas>
@@ -107,16 +130,31 @@
107
130
  nv1.meshes[0].dpv[0].cal_min = this.value * 1; //*1 converts string to number
108
131
  nv1.setMeshProperty(nv1.meshes[0].id, "colormap", fiberColormap.value);
109
132
  };
110
- about.onclick = function () {
111
- window.alert(
112
- "The `Per Vertex` colors visualize a tsf format overlay created with tcksample",
113
- );
114
- };
133
+ layoutSelect.onchange = function () {
134
+ nv1.setMultiplanarLayout(Number(this.value));
135
+ }
136
+ renderingSelect.onchange = function () {
137
+ nv1.opts.multiplanarShowRender = Number(this.value);
138
+ nv1.drawScene();
139
+ }
140
+ equalCheck.onchange = function () {
141
+ nv1.opts.multiplanarEqualSize = this.checked
142
+ nv1.drawScene()
143
+ }
144
+ cornerCheck.onchange = function () {
145
+ //nv1.opts.isCornerOrientationText = this.checked\
146
+ nv1.setCornerOrientationText(this.checked)
147
+ }
148
+ sliceType.onchange = function () {
149
+ let st = parseInt(document.getElementById("sliceType").value);
150
+ nv1.setSliceType(st)
151
+ }
115
152
  var volumeList1 = [{ url: "../demos/images/mni152.nii.gz" }];
116
153
  let defaults = {
117
154
  backColor: [0, 0, 0, 1],
118
155
  show3Dcrosshair: true,
119
- loglevel: 'debug'
156
+ loglevel: 'debug',
157
+ isRuler: true
120
158
  };
121
159
  var nv1 = new Niivue(defaults);
122
160
  nv1.attachToCanvas(gl1);
@@ -152,6 +190,7 @@
152
190
  };
153
191
  cmapEl.appendChild(btn);
154
192
  }
193
+ equalCheck.onchange()
155
194
  </script>
156
195
  </body>
157
196
  </html>
@@ -0,0 +1,165 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
7
+ <title>NiiVue</title>
8
+ <link rel="stylesheet" href="niivue.css" />
9
+ <link rel="icon" href="data:;base64,iVBORw0KGgo=" />
10
+ </head>
11
+ <body>
12
+ <noscript>
13
+ <strong>niivue requires JavaScript.</strong>
14
+ </noscript>
15
+ <header>
16
+ <button id="openBtn">Choose an image or mesh with a dialog</button>
17
+ <label for="layoutSelect">Layout</label>
18
+ <select id="layoutSelect">
19
+ <option value="0" selected>Auto</option>
20
+ <option value="1">Column</option>
21
+ <option value="2">Grid</option>
22
+ <option value="3">Row</option>
23
+ </select>
24
+ <label for="renderingSelect">MultiplanarRendering</label>
25
+ <select id="renderingSelect">
26
+ <option value="0">Never</option>
27
+ <option value="1">Always</option>
28
+ <option value="2" selected>Auto</option>
29
+ </select>
30
+ <select id="sliceType">
31
+ <option value="0">Axial</option>
32
+ <option value="1">Coronal</option>
33
+ <option value="2">Sagittal</option>
34
+ <option value="4">Render</option>
35
+ <option value="3" selected>Multiplanar</option>
36
+ </select>
37
+ <label for="equalCheck">EqualSize</label>
38
+ <input type="checkbox" id="equalCheck" checked />
39
+ <label for="cornerCheck">CornerText</label>
40
+ <input type="checkbox" id="cornerCheck" unchecked />
41
+ <!-- undo button for drawing -->
42
+ <button id="undoBtn">Undo</button>
43
+ <!-- draw toggle button -->
44
+ <button id="drawBtn">Draw</button>
45
+ <!-- grow button -->
46
+ <button id="growBtn">Grow</button>
47
+ <!-- pen color button cycler -->
48
+ <button id="penColorBtn">Pen color</button>
49
+ </header>
50
+ <main id="canvas-container">
51
+ <canvas id="gl1"></canvas>
52
+ </main>
53
+
54
+ <script type="module" async>
55
+ import {
56
+ Niivue,
57
+ NVImage,
58
+ NVMesh,
59
+ NVMeshLoaders,
60
+ SHOW_RENDER,
61
+ DRAG_MODE
62
+ } from "./niivue/index.ts";
63
+ async function addVolumeFromFiles(f) {
64
+ console.log("attempting to open ", f[0].name);
65
+ console.log("details", f[0]);
66
+ nv1.loadFromFile(f[0]);
67
+ }
68
+ openBtn.onclick = function () {
69
+ let input = document.createElement("input");
70
+ input.style.display = "none";
71
+ input.type = "file";
72
+ document.body.appendChild(input);
73
+ input.onchange = function (event) {
74
+ addVolumeFromFiles(event.target.files);
75
+ };
76
+ input.click();
77
+ };
78
+
79
+ // pen colors can be 1, 2, 3, 4
80
+ let penColor = 1;
81
+
82
+ layoutSelect.onchange = function () {
83
+ nv1.setMultiplanarLayout(Number(this.value));
84
+ }
85
+ renderingSelect.onchange = function () {
86
+ nv1.opts.multiplanarShowRender = Number(this.value);
87
+ nv1.drawScene();
88
+ }
89
+ equalCheck.onchange = function () {
90
+ nv1.opts.multiplanarEqualSize = this.checked
91
+ nv1.drawScene()
92
+ }
93
+ cornerCheck.onchange = function () {
94
+ //nv1.opts.isCornerOrientationText = this.checked\
95
+ nv1.setCornerOrientationText(this.checked)
96
+ }
97
+ sliceType.onchange = function () {
98
+ let st = parseInt(document.getElementById("sliceType").value);
99
+ nv1.setSliceType(st)
100
+ }
101
+ undoBtn.onclick = function () {
102
+ nv1.drawUndo()
103
+ }
104
+ drawBtn.onclick = function () {
105
+ nv1.setDrawingEnabled(!nv1.opts.drawingEnabled)
106
+ if (nv1.opts.drawingEnabled) {
107
+ nv1.opts.clickToSegment = true;
108
+ nv1.setPenValue(penColor, false) // red and filled
109
+ } else {
110
+ nv1.opts.clickToSegment = false;
111
+ }
112
+ }
113
+ growBtn.onclick = function () {
114
+ nv1.drawingBinaryDilationWithSeed(
115
+ nv1.frac2vox(nv1.scene.crosshairPos)
116
+ )
117
+ }
118
+ penColorBtn.onclick = function () {
119
+ penColor = penColor % 4 + 1;
120
+ nv1.setPenValue(penColor, false)
121
+ }
122
+
123
+ const onDragRelease = (data) => {
124
+ console.log("drag release", data);
125
+ let obj = nv1.getDescriptives({
126
+ layer: 0,
127
+ startVox: data.voxStart,
128
+ endVox: data.voxEnd,
129
+ roiIsMask: true,
130
+ });
131
+ console.log(obj);
132
+ };
133
+
134
+ var volumeList1 = [{ url: "../demos/images/FLAIR.nii.gz" }];
135
+ let defaults = {
136
+ backColor: [0, 0, 0, 1],
137
+ show3Dcrosshair: true,
138
+ loglevel: 'debug',
139
+ isRuler: true,
140
+ clickToSegmentRadius: 3, // in mm
141
+ clickToSegmentPercent: 0.2,
142
+ clickToSegmentAutoIntensity: true,
143
+ clickToSegmentMaxDistanceMM: 10,
144
+ penSize: 3, // in voxels, but overridden by clickToSegmentRadius
145
+ maxDrawUndoBitmaps: 30,
146
+ onDragRelease: onDragRelease,
147
+ selectionBoxColor: [1, 0, 0, 0.5],
148
+ dragMode: DRAG_MODE.roiSelection,
149
+ selectionBoxIsOutline: true
150
+ };
151
+ var nv1 = new Niivue(defaults);
152
+ nv1.attachToCanvas(gl1);
153
+ // multiplanarShowRender is the preferred option now,
154
+ // but multiplanarForceRender is still available for backwards compatibility
155
+ // previous use was: nv1.opts.multiplanarForceRender = true;
156
+ nv1.opts.multiplanarShowRender = SHOW_RENDER.AUTO
157
+ await nv1.loadVolumes(volumeList1);
158
+ equalCheck.onchange()
159
+
160
+ // set drawing enabled
161
+ // nv1.setDrawingEnabled(true);
162
+ // nv1.setPenValue(1, true) // red and filled
163
+ </script>
164
+ </body>
165
+ </html>
@@ -0,0 +1,289 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
7
+ <title>Bidirectional Sync</title>
8
+ <style>
9
+ html {
10
+ height: auto;
11
+ min-height: 100%;
12
+ margin: 0;
13
+ }
14
+ body {
15
+ display: flex;
16
+ flex-direction: column;
17
+ margin: 0;
18
+ min-height: 100%;
19
+ width: 100%;
20
+ position: absolute;
21
+ font-family: system-ui, Arial, Helvetica, sans-serif;
22
+ user-select: none; /* Standard syntax */
23
+ color: white;
24
+ background: #202020;
25
+ }
26
+ header {
27
+ margin: 10px;
28
+ }
29
+ footer {
30
+ margin: 5px;
31
+ }
32
+ .page-wrapper {
33
+ margin: 2px;
34
+ }
35
+ .row {
36
+ display: flex;
37
+ flex-direction: row;
38
+ flex-wrap: wrap;
39
+ width: 100%;
40
+ min-height: 100%;
41
+ margin: 2px;
42
+ }
43
+ .column {
44
+ display: flex;
45
+ flex-direction: column;
46
+ flex-basis: 100%;
47
+ flex: 1;
48
+ background: #404040;
49
+ }
50
+ canvas:focus {
51
+ outline: red 1px solid;
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <noscript>niivue requires JavaScript.</noscript>
57
+ <header>
58
+ <label for="layout">Layout</label>
59
+ <select id="layout">
60
+ <option value="0" selected>Auto</option>
61
+ <option value="1">Column</option>
62
+ <option value="2">Grid</option>
63
+ <option value="3">Row</option>
64
+ </select>
65
+ <label for="canvasHeight">Height</label>
66
+ <select id="canvasHeight">
67
+ <option value="256">256</option>
68
+ <option value="640" selected>512</option>
69
+ <option value="768">768</option>
70
+ </select>
71
+ <label for="syncDrop"> Broadcast</label>
72
+ <select id="syncDrop">
73
+ <option value="0">Sync Disabled</option>
74
+ <option value="1">Sync 2D</option>
75
+ <option value="2">Sync 3D</option>
76
+ <option value="3" selected>Sync 2D and 3D</option>
77
+ </select>
78
+ <!-- slider for gamma -->
79
+ <label for="gammaSlider">Gamma</label>
80
+ <input
81
+ type="range"
82
+ min="10"
83
+ max="400"
84
+ value="100"
85
+ class="slider"
86
+ id="gammaSlider"
87
+ />
88
+ <!-- sliceType select -->
89
+ <label for="sliceType">Slice Type</label>
90
+ <select id="sliceType">
91
+ <option value="0">Axial</option>
92
+ <option value="1">Coronal</option>
93
+ <option value="2">Sagittal</option>
94
+ <option value="3" selected>Multi</option>
95
+ </select>
96
+
97
+ <!-- slider for cal_min -->
98
+ <label for="calMin">min</label>
99
+ <input
100
+ type="range"
101
+ min="0"
102
+ max="100"
103
+ value="0"
104
+ class="slider"
105
+ id="calMin"
106
+ />
107
+ <!-- slider for cal_max -->
108
+ <label for="calMax">max</label>
109
+ <input
110
+ type="range"
111
+ min="0"
112
+ max="100"
113
+ value="100"
114
+ class="slider"
115
+ id="calMax"
116
+ />
117
+ </header>
118
+ <main>
119
+ <div class='page-wrapper'>
120
+ <div class='row'>
121
+ <div class='column'>
122
+ <div>
123
+ <canvas id="gl1" height="512">
124
+ </div>
125
+ </div>
126
+ <div class='column'>
127
+ <div>
128
+ <canvas id="gl2" height="512">
129
+ </div>
130
+ </div>
131
+ <div class='column'>
132
+ <div>
133
+ <canvas id="gl3" height="512">
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </main>
139
+ <footer>
140
+ <x id="intensity"> &nbsp;</x>
141
+ <x id="intensity2"> &nbsp;</x>
142
+ <x id="intensity3"> &nbsp;</x>
143
+ </footer>
144
+ <script type="module" async>
145
+ import {
146
+ Niivue,
147
+ NVImage,
148
+ NVMesh,
149
+ NVMeshLoaders,
150
+ SHOW_RENDER
151
+ } from "./niivue/index.ts";
152
+ layout.onchange = function () {
153
+ nv1.setMultiplanarLayout(parseInt(this.value));
154
+ nv2.setMultiplanarLayout(parseInt(this.value));
155
+ nv3.setMultiplanarLayout(parseInt(this.value));
156
+ }
157
+ canvasHeight.onchange = function () {
158
+ gl1.height = this.value;
159
+ gl2.height = this.value;
160
+ gl3.height = this.value;
161
+ nv1.resizeListener();
162
+ nv2.resizeListener();
163
+ nv3.resizeListener();
164
+ }
165
+
166
+ gammaSlider.onchange = function() {
167
+ const v = parseFloat(gammaSlider.value) * 0.01
168
+ nv1.setGamma(v)
169
+ nv1.gl.canvas.focus()
170
+ nv1.sync()
171
+ nv1.gl.canvas.blur()
172
+ }
173
+
174
+ syncDrop.onchange = function() {
175
+ const v = parseInt(syncDrop.value)
176
+ let is2D = false
177
+ let is3D = false
178
+ if (v % 2) {
179
+ is2D = true
180
+ }
181
+ if (v > 1) {
182
+ is3D = true
183
+ }
184
+ nv1.broadcastTo([nv2, nv3], { "2d": is2D, "3d": is3D })
185
+ nv2.broadcastTo([nv1, nv3], { "2d": is2D, "3d": is3D })
186
+ nv3.broadcastTo([nv1, nv2], { "2d": is2D, "3d": is3D })
187
+ }
188
+
189
+ calMin.onchange = function() {
190
+ const v = parseFloat(calMin.value)
191
+ nv2.volumes[0].cal_min = v
192
+ // set focus to the canvas
193
+ nv2.gl.canvas.focus()
194
+ nv2.updateGLVolume()
195
+ // remove focus from the canvas
196
+ nv2.gl.canvas.blur()
197
+ }
198
+
199
+ calMax.onchange = function() {
200
+ const v = parseFloat(calMax.value)
201
+ nv2.volumes[0].cal_max = v
202
+ // set focus to the canvas
203
+ nv2.gl.canvas.focus()
204
+ nv2.updateGLVolume()
205
+ // remove focus from the canvas
206
+ nv2.gl.canvas.blur()
207
+ }
208
+
209
+ sliceType.onchange = function() {
210
+ const v = parseInt(sliceType.value)
211
+ nv1.setSliceType(v)
212
+ nv2.setSliceType(v)
213
+ nv3.setSliceType(v)
214
+ }
215
+
216
+ function handleIntensityChange(data) {
217
+ document.getElementById("intensity").innerHTML =
218
+ "&nbsp;&nbsp;" + data.string;
219
+ }
220
+ function handleIntensityChange2(data) {
221
+ document.getElementById("intensity2").innerHTML =
222
+ "&nbsp;&nbsp;" + data.string;
223
+ }
224
+ function handleIntensityChange3(data) {
225
+ document.getElementById("intensity3").innerHTML =
226
+ "&nbsp;&nbsp;" + data.string;
227
+ }
228
+ var nv1 = new Niivue({
229
+ show3Dcrosshair: true,
230
+ onLocationChange: handleIntensityChange,
231
+ backColor: [1, 1, 1, 1],
232
+ // dragMode: 3,
233
+ sliceType: 3,
234
+ multiplanarShowRender: SHOW_RENDER.ALWAYS
235
+ });
236
+ nv1.attachTo("gl1");
237
+ var volumeList1 = [{ url: "../demos/images/pcasl.nii.gz" }];
238
+ await nv1.loadVolumes(volumeList1);
239
+ var nv2 = new Niivue({
240
+ show3Dcrosshair: true,
241
+ onLocationChange: handleIntensityChange2,
242
+ backColor: [1, 1, 1, 1],
243
+ // dragMode: 3,
244
+ sliceType: 3,
245
+ multiplanarShowRender: SHOW_RENDER.ALWAYS
246
+ });
247
+ nv2.attachTo("gl2");
248
+ var nv3 = new Niivue({
249
+ show3Dcrosshair: true,
250
+ onLocationChange: handleIntensityChange3,
251
+ backColor: [1, 1, 1, 1],
252
+ // dragMode: 0,
253
+ sliceType: 3,
254
+ multiplanarShowRender: SHOW_RENDER.ALWAYS
255
+ });
256
+ nv3.attachTo("gl3");
257
+ var volumeList2 = [{ url: "../demos/images/mni152.nii.gz" }];
258
+ var volumeList3 = [{ url: "../demos/images/mni152.nii.gz" }];
259
+ await nv2.loadVolumes(volumeList2);
260
+ // once the volumes are loaded for nv2, set the min and max values for
261
+ // the calMin and calMax sliders to the min and max values of the volume
262
+ calMin.min = nv2.volumes[0].global_min
263
+ calMin.max = nv2.volumes[0].global_max
264
+ calMin.value = nv2.volumes[0].cal_min
265
+ calMax.min = nv2.volumes[0].global_min
266
+ calMax.max = nv2.volumes[0].global_max
267
+ calMax.value = nv2.volumes[0].cal_max
268
+
269
+ await nv3.loadVolumes(volumeList3);
270
+ const syncOpts = {
271
+ // "2d": true, // legacy option
272
+ // "3d": true, // legacy option
273
+ // new options allowing more fine-grained control below.
274
+ crosshair: true,
275
+ zoomPan: true,
276
+ gamma: true,
277
+ sliceType: true,
278
+ clipPlane: true,
279
+ cal_min: false,
280
+ cal_max: false,
281
+ }
282
+ // make sure all views can control each other
283
+ nv1.broadcastTo([nv2, nv3], syncOpts);
284
+ nv2.broadcastTo([nv1, nv3], syncOpts);
285
+ nv3.broadcastTo([nv1, nv2], syncOpts);
286
+
287
+ </script>
288
+ </body>
289
+ </html>