@niivue/niivue 0.44.1 → 0.44.2-dev.2
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 +9 -0
- package/build/index.d.ts +31 -6
- package/build/index.js +531 -135
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +106 -18
- package/dist/index.js +26377 -30692
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/package.json +18 -17
- package/src/index.html +46 -7
- package/src/index_segment.html +233 -0
- package/src/index_sync.html +333 -0
- package/src/niivue/index.ts +1054 -147
- package/src/niivue.css +0 -1
- package/src/nvdocument.ts +40 -5
- package/src/nvimage/index.ts +4 -1
- package/src/nvimage/utils.ts +61 -42
- package/src/nvlabel.ts +34 -1
- package/src/nvmesh-loaders.ts +24 -1
- package/src/shader-srcs.ts +84 -3
- package/src/types.ts +13 -0
- package/dist/niivue.umd.js +0 -1792
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@niivue/niivue",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.2-dev.2",
|
|
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.
|
|
76
|
-
"@rollup/plugin-commonjs": "^
|
|
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.
|
|
78
|
+
"@types/node": "^20.14.11",
|
|
79
79
|
"@types/ungap__structured-clone": "^1.2.0",
|
|
80
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
81
|
-
"@typescript-eslint/parser": "^7.
|
|
82
|
-
"@vitest/coverage-v8": "^
|
|
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
|
|
87
|
-
"eslint-plugin-tsdoc": "^0.
|
|
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
|
|
89
|
+
"happy-dom": "^14.12.3",
|
|
90
90
|
"http-server": "^14.1.1",
|
|
91
|
-
"prettier": "^3.
|
|
91
|
+
"prettier": "^3.3.3",
|
|
92
92
|
"regenerator-runtime": "^0.14.1",
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
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.
|
|
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
|
-
<
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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,233 @@
|
|
|
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.drawOpacity = 0.4
|
|
108
|
+
nv1.opts.clickToSegment = true;
|
|
109
|
+
nv1.setPenValue(penColor, false) // red and filled
|
|
110
|
+
// hide the crosshair
|
|
111
|
+
nv1.opts.crosshairWidth = 0;
|
|
112
|
+
// hide the mouse cursor
|
|
113
|
+
nv1.canvas.style.cursor = "none";
|
|
114
|
+
} else {
|
|
115
|
+
nv1.opts.clickToSegment = false;
|
|
116
|
+
// show the crosshair
|
|
117
|
+
nv1.opts.crosshairWidth = 1;
|
|
118
|
+
// show the mouse cursor
|
|
119
|
+
nv1.canvas.style.cursor = "default";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
growBtn.onclick = function () {
|
|
123
|
+
nv1.drawingBinaryDilationWithSeed(
|
|
124
|
+
nv1.frac2vox(nv1.scene.crosshairPos)
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
penColorBtn.onclick = function () {
|
|
128
|
+
penColor = penColor % 4 + 1;
|
|
129
|
+
nv1.setPenValue(penColor, false)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const onDragRelease = (data) => {
|
|
133
|
+
// if there is a div with the id dragReleaseDiv, remove it
|
|
134
|
+
const divToRemove = document.getElementById("dragReleaseDiv");
|
|
135
|
+
if (divToRemove) {
|
|
136
|
+
divToRemove.remove();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log("drag release", data);
|
|
140
|
+
let obj = nv1.getDescriptives({
|
|
141
|
+
layer: 0,
|
|
142
|
+
startVox: data.voxStart,
|
|
143
|
+
endVox: data.voxEnd,
|
|
144
|
+
roiIsMask: true,
|
|
145
|
+
});
|
|
146
|
+
console.log(obj);
|
|
147
|
+
// get dpr
|
|
148
|
+
const dpr = window.devicePixelRatio;
|
|
149
|
+
// get mouse x,y. Divide by dpr to get the correct position
|
|
150
|
+
const x = nv1.uiData.dragEnd[0] / dpr;
|
|
151
|
+
const y = nv1.uiData.dragEnd[1] / dpr;
|
|
152
|
+
console.log(x, y);
|
|
153
|
+
const pad = 5;
|
|
154
|
+
const div = document.createElement("div");
|
|
155
|
+
// set id for removal on the next pass
|
|
156
|
+
div.id = "dragReleaseDiv";
|
|
157
|
+
div.style.color = "red";
|
|
158
|
+
div.style.position = "absolute";
|
|
159
|
+
div.style.backgroundColor = "black";
|
|
160
|
+
div.style.opacity = 0.8;
|
|
161
|
+
div.style.padding = "5px";
|
|
162
|
+
// set z-index to be well above the canvas
|
|
163
|
+
div.style.zIndex = 1000;
|
|
164
|
+
div.innerHTML = `Mean: ${obj.mean}<br>Area: ${obj.area}<br>Stdev: ${obj.stdev}`;
|
|
165
|
+
// set position to be at the mouse x,y relative to the canvas
|
|
166
|
+
div.style.left = x + pad + "px";
|
|
167
|
+
div.style.top = y + pad + "px";
|
|
168
|
+
// get the canvas element reference from niivue
|
|
169
|
+
const canvas = nv1.gl.canvas;
|
|
170
|
+
// niivue should always be in a container, so get the parent of the canvas
|
|
171
|
+
const parentDiv = canvas.parentElement;
|
|
172
|
+
parentDiv.appendChild(div);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const onLocationChange = (data) => {
|
|
176
|
+
// remove the stats div if it exists when the user clicks away
|
|
177
|
+
const divToRemove = document.getElementById("dragReleaseDiv");
|
|
178
|
+
if (divToRemove) {
|
|
179
|
+
divToRemove.remove();
|
|
180
|
+
}
|
|
181
|
+
// console.log(data.string)
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
var volumeList1 = [
|
|
186
|
+
{ url: "../demos/images/FLAIR.nii.gz" },
|
|
187
|
+
];
|
|
188
|
+
let clicks = 0;
|
|
189
|
+
const defaultPercent = 0.02;
|
|
190
|
+
let defaults = {
|
|
191
|
+
backColor: [0, 0, 0, 1],
|
|
192
|
+
show3Dcrosshair: true,
|
|
193
|
+
loglevel: 'debug',
|
|
194
|
+
isRuler: true,
|
|
195
|
+
clickToSegmentRadius: 3, // in mm
|
|
196
|
+
clickToSegmentPercent: defaultPercent,
|
|
197
|
+
clickToSegmentAutoIntensity: true,
|
|
198
|
+
clickToSegmentMaxDistanceMM: 50,
|
|
199
|
+
clickToSegmentIs2D: true,
|
|
200
|
+
floodFillNeighbors: 6,
|
|
201
|
+
penSize: 3, // in voxels, but overridden by clickToSegmentRadius
|
|
202
|
+
maxDrawUndoBitmaps: 100, // large number for undo
|
|
203
|
+
onDragRelease: onDragRelease,
|
|
204
|
+
onLocationChange: onLocationChange,
|
|
205
|
+
selectionBoxColor: [1, 0, 0, 0.5],
|
|
206
|
+
dragMode: DRAG_MODE.roiSelection,
|
|
207
|
+
selectionBoxIsOutline: true,
|
|
208
|
+
scrollRequiresFocus: false
|
|
209
|
+
};
|
|
210
|
+
var nv1 = new Niivue(defaults);
|
|
211
|
+
nv1.attachToCanvas(gl1);
|
|
212
|
+
nv1.opts.multiplanarShowRender = SHOW_RENDER.AUTO
|
|
213
|
+
nv1.setSliceType(0)
|
|
214
|
+
nv1.setInterpolation(false)
|
|
215
|
+
await nv1.loadVolumes(volumeList1);
|
|
216
|
+
equalCheck.onchange()
|
|
217
|
+
|
|
218
|
+
// store mouse position
|
|
219
|
+
const mouse = { x: 0, y: 0 };
|
|
220
|
+
|
|
221
|
+
// when the user presses "t" toggle the overlay opacity between 0 and 1
|
|
222
|
+
document.addEventListener("keydown", (e) => {
|
|
223
|
+
if (e.key === "t") {
|
|
224
|
+
if (nv1.volumes.length < 2) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const currentOpacity = nv1.volumes[1].opacity;
|
|
228
|
+
nv1.setOpacity(1, currentOpacity === 0 ? 0.5 : 0);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
</body>
|
|
233
|
+
</html>
|