@niivue/niivue 0.48.0 → 0.49.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/build/index.d.ts +25 -6
- package/build/index.js +380 -240
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/build/metafile-esm.json +1 -0
- package/dist/index.d.ts +25 -6
- package/dist/index.js +2711 -90307
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/metafile-esm.json +1 -0
- package/dist/niivue.umd.js +148 -180
- package/package.json +1 -2
- package/src/colortables.ts +3 -1
- package/src/index.html +167 -244
- package/src/indexOK.html +277 -0
- package/src/niivue/index.ts +212 -35
- package/src/nvdocument.ts +9 -50
- package/src/nvimage/index.ts +140 -131
- package/src/nvmesh-loaders.ts +2 -1
- package/src/nvmesh.ts +32 -8
- package/src/nvutilities.ts +10 -0
- package/src/shader-srcs.ts +7 -0
- package/src/shader.ts +8 -0
package/build/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
10
10
|
import { mat4 as mat46, vec2, vec3 as vec311, vec4 as vec48 } from "gl-matrix";
|
|
11
11
|
|
|
12
12
|
// package.json
|
|
13
|
-
var version = "0.
|
|
13
|
+
var version = "0.49.0";
|
|
14
14
|
|
|
15
15
|
// src/logger.ts
|
|
16
16
|
var _Log = class _Log {
|
|
@@ -83,6 +83,13 @@ var compileShader = function(gl, vert, frag) {
|
|
|
83
83
|
gl.attachShader(program, fs);
|
|
84
84
|
gl.linkProgram(program);
|
|
85
85
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
86
|
+
console.log(gl.getProgramInfoLog(program));
|
|
87
|
+
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
|
|
88
|
+
console.log("Vertex shader compilation error:", gl.getShaderInfoLog(vs));
|
|
89
|
+
}
|
|
90
|
+
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
|
|
91
|
+
console.log("Fragment shader compilation error:", gl.getShaderInfoLog(fs));
|
|
92
|
+
}
|
|
86
93
|
log.error(gl.getProgramInfoLog(program));
|
|
87
94
|
throw new Error("Shader failed to link, see console for log");
|
|
88
95
|
}
|
|
@@ -1114,6 +1121,9 @@ void main(void) {
|
|
|
1114
1121
|
float r = max(0.00001, abs(mx - mn));
|
|
1115
1122
|
mn = min(mn, mx);
|
|
1116
1123
|
float txl = mix(0.0, 1.0, (f - mn) / r);
|
|
1124
|
+
if (f > mn) { //issue1139: survives threshold, so round up to opaque voxel
|
|
1125
|
+
txl = max(txl, 2.0/256.0);
|
|
1126
|
+
}
|
|
1117
1127
|
//https://stackoverflow.com/questions/5879403/opengl-texture-coordinates-in-pixel-space
|
|
1118
1128
|
float nlayer = float(textureSize(colormap, 0).y);
|
|
1119
1129
|
//each volume has two color maps:
|
|
@@ -1130,6 +1140,8 @@ void main(void) {
|
|
|
1130
1140
|
r = max(0.00001, abs(mx - mn));
|
|
1131
1141
|
mn = min(mn, mx);
|
|
1132
1142
|
txl = 1.0 - mix(0.0, 1.0, (f - mn) / r);
|
|
1143
|
+
//issue1139: survives threshold, so round up to opaque voxel
|
|
1144
|
+
txl = max(txl, 2.0/256.0);
|
|
1133
1145
|
y = ((2.0 * layer) + 0.5)/nlayer;
|
|
1134
1146
|
FragColor = texture(colormap, vec2(txl, y));
|
|
1135
1147
|
}
|
|
@@ -1308,9 +1320,11 @@ uniform mat4 mvpMtx;
|
|
|
1308
1320
|
uniform mat4 normMtx;
|
|
1309
1321
|
out vec4 vClr;
|
|
1310
1322
|
out vec3 vN;
|
|
1323
|
+
out vec4 vP;
|
|
1311
1324
|
out vec4 vPc;
|
|
1312
1325
|
void main(void) {
|
|
1313
1326
|
vec3 lightPosition = vec3(0.0, 0.0, -10.0);
|
|
1327
|
+
vP = vec4(pos, 1.0);
|
|
1314
1328
|
vPc = mvpMtx * vec4(pos, 1.0);
|
|
1315
1329
|
gl_Position = vPc;
|
|
1316
1330
|
vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz);
|
|
@@ -11169,7 +11183,9 @@ var ColorTables = class {
|
|
|
11169
11183
|
const nLabels = cm.R.length;
|
|
11170
11184
|
const idxs = cm.I ?? [...Array(nLabels).keys()];
|
|
11171
11185
|
if (nLabels !== cm.G.length || nLabels !== cm.B.length || nLabels !== idxs.length) {
|
|
11172
|
-
throw new Error(
|
|
11186
|
+
throw new Error(
|
|
11187
|
+
`colormap does not make sense: ${cm} Rs ${cm.R.length} Gs ${cm.G.length} Bs ${cm.B.length} Is ${idxs.length}`
|
|
11188
|
+
);
|
|
11173
11189
|
}
|
|
11174
11190
|
let As = new Uint8ClampedArray(nLabels).fill(alphaFill);
|
|
11175
11191
|
As[0] = 0;
|
|
@@ -11656,6 +11672,15 @@ var NVUtilities = class _NVUtilities {
|
|
|
11656
11672
|
return mat;
|
|
11657
11673
|
}
|
|
11658
11674
|
// readMatV4()
|
|
11675
|
+
static b64toUint8(base64) {
|
|
11676
|
+
const binaryString = atob(base64);
|
|
11677
|
+
const length = binaryString.length;
|
|
11678
|
+
const bytes = new Uint8Array(length);
|
|
11679
|
+
for (let i = 0; i < length; i++) {
|
|
11680
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
11681
|
+
}
|
|
11682
|
+
return bytes;
|
|
11683
|
+
}
|
|
11659
11684
|
/*
|
|
11660
11685
|
https://gist.github.com/jonleighton/958841
|
|
11661
11686
|
MIT LICENSE
|
|
@@ -12559,7 +12584,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12559
12584
|
alphaThreshold: false,
|
|
12560
12585
|
isTransparentBelowCalMin: true,
|
|
12561
12586
|
isAdditiveBlend: false,
|
|
12562
|
-
colorbarVisible: true
|
|
12587
|
+
colorbarVisible: true,
|
|
12588
|
+
colormapLabel: null
|
|
12563
12589
|
};
|
|
12564
12590
|
const isReadColortables = true;
|
|
12565
12591
|
const re = /(?:\.([^.]+))?$/;
|
|
@@ -15456,7 +15482,7 @@ var NVMesh2 = class _NVMesh {
|
|
|
15456
15482
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
|
|
15457
15483
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Uint32Array.from(idxs), gl.STATIC_DRAW);
|
|
15458
15484
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
15459
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
15485
|
+
gl.bufferData(gl.ARRAY_BUFFER, u8, gl.STATIC_DRAW);
|
|
15460
15486
|
this.indexCount = nidx;
|
|
15461
15487
|
}
|
|
15462
15488
|
// linesToCylinders
|
|
@@ -16162,7 +16188,7 @@ var NVMesh2 = class _NVMesh {
|
|
|
16162
16188
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
|
|
16163
16189
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Uint32Array.from(this.tris), gl.STATIC_DRAW);
|
|
16164
16190
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
16165
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
16191
|
+
gl.bufferData(gl.ARRAY_BUFFER, u8, gl.STATIC_DRAW);
|
|
16166
16192
|
this.indexCount = this.tris.length;
|
|
16167
16193
|
this.vertexCount = this.pts.length;
|
|
16168
16194
|
}
|
|
@@ -16270,13 +16296,26 @@ var NVMesh2 = class _NVMesh {
|
|
|
16270
16296
|
}
|
|
16271
16297
|
// adjust attributes of a mesh layer. invoked by niivue.setMeshLayerProperty()
|
|
16272
16298
|
// TODO this method is a bit too generic
|
|
16273
|
-
setLayerProperty(id, key, val, gl) {
|
|
16299
|
+
async setLayerProperty(id, key, val, gl) {
|
|
16274
16300
|
const layer = this.layers[id];
|
|
16275
16301
|
if (!layer || !(key in layer)) {
|
|
16276
16302
|
log.warn("mesh does not have property ", key, " for layer ", layer);
|
|
16277
16303
|
return;
|
|
16278
16304
|
}
|
|
16279
|
-
|
|
16305
|
+
if (key === "colormapLabel") {
|
|
16306
|
+
if (typeof val === "object") {
|
|
16307
|
+
layer[key] = cmapper.makeLabelLut(val);
|
|
16308
|
+
} else if (typeof val === "string") {
|
|
16309
|
+
const cmap = await cmapper.makeLabelLutFromUrl(val);
|
|
16310
|
+
layer[key] = cmap;
|
|
16311
|
+
this.updateMesh(gl);
|
|
16312
|
+
return;
|
|
16313
|
+
} else {
|
|
16314
|
+
log.error("colormapLabel requires a string or object");
|
|
16315
|
+
}
|
|
16316
|
+
} else {
|
|
16317
|
+
layer[key] = val;
|
|
16318
|
+
}
|
|
16280
16319
|
this.updateMesh(gl);
|
|
16281
16320
|
}
|
|
16282
16321
|
// adjust mesh attributes. invoked by niivue.setMeshProperty(()
|
|
@@ -16502,8 +16541,13 @@ var NVMesh2 = class _NVMesh {
|
|
|
16502
16541
|
anatomicalStructurePrimary
|
|
16503
16542
|
);
|
|
16504
16543
|
if ("scalars" in obj && obj.scalars.length > 0) {
|
|
16505
|
-
NVMeshLoaders.readLayer(name, buffer, nvm, opacity, "gray");
|
|
16506
|
-
|
|
16544
|
+
const newLayer = NVMeshLoaders.readLayer(name, buffer, nvm, opacity, "gray");
|
|
16545
|
+
if (typeof newLayer === "undefined") {
|
|
16546
|
+
log.warn("readLayer() failed to convert scalars");
|
|
16547
|
+
} else {
|
|
16548
|
+
nvm.layers.push(newLayer);
|
|
16549
|
+
nvm.updateMesh(gl);
|
|
16550
|
+
}
|
|
16507
16551
|
}
|
|
16508
16552
|
return nvm;
|
|
16509
16553
|
}
|
|
@@ -18321,7 +18365,6 @@ import { vec3 as vec38, vec4 as vec46 } from "gl-matrix";
|
|
|
18321
18365
|
|
|
18322
18366
|
// src/nvimage/index.ts
|
|
18323
18367
|
import * as nifti from "nifti-reader-js";
|
|
18324
|
-
import daikon from "daikon";
|
|
18325
18368
|
import { mat3, mat4 as mat44, vec3 as vec37, vec4 as vec45 } from "gl-matrix";
|
|
18326
18369
|
import { Decompress, decompressSync as decompressSync3, gzipSync as gzipSync2 } from "fflate/browser";
|
|
18327
18370
|
import { v4 as uuidv42 } from "@lukeed/uuid";
|
|
@@ -18440,32 +18483,6 @@ var NVImageFromUrlOptions = (url, urlImageData = "", name = "", colormap = "gray
|
|
|
18440
18483
|
colormapLabel
|
|
18441
18484
|
};
|
|
18442
18485
|
};
|
|
18443
|
-
function getBestTransform(imageDirections, voxelDimensions, imagePosition) {
|
|
18444
|
-
const cosines = imageDirections;
|
|
18445
|
-
let m = null;
|
|
18446
|
-
if (cosines) {
|
|
18447
|
-
const vs = {
|
|
18448
|
-
colSize: voxelDimensions[0],
|
|
18449
|
-
rowSize: voxelDimensions[1],
|
|
18450
|
-
sliceSize: voxelDimensions[2]
|
|
18451
|
-
};
|
|
18452
|
-
const coord = imagePosition;
|
|
18453
|
-
const cosx = [cosines[0], cosines[1], cosines[2]];
|
|
18454
|
-
const cosy = [cosines[3], cosines[4], cosines[5]];
|
|
18455
|
-
const cosz = [
|
|
18456
|
-
cosx[1] * cosy[2] - cosx[2] * cosy[1],
|
|
18457
|
-
cosx[2] * cosy[0] - cosx[0] * cosy[2],
|
|
18458
|
-
cosx[0] * cosy[1] - cosx[1] * cosy[0]
|
|
18459
|
-
];
|
|
18460
|
-
m = [
|
|
18461
|
-
[cosx[0] * vs.colSize * -1, cosy[0] * vs.rowSize * -1, cosz[0] * vs.sliceSize * -1, -1 * coord[0]],
|
|
18462
|
-
[cosx[1] * vs.colSize * -1, cosy[1] * vs.rowSize * -1, cosz[1] * vs.sliceSize * -1, -1 * coord[1]],
|
|
18463
|
-
[cosx[2] * vs.colSize, cosy[2] * vs.rowSize, cosz[2] * vs.sliceSize, coord[2]],
|
|
18464
|
-
[0, 0, 0, 1]
|
|
18465
|
-
];
|
|
18466
|
-
}
|
|
18467
|
-
return m;
|
|
18468
|
-
}
|
|
18469
18486
|
function str2Buffer(str, maxLen = 80) {
|
|
18470
18487
|
str = str.replace(/[`$]/g, "");
|
|
18471
18488
|
const bytes = [];
|
|
@@ -18766,8 +18783,7 @@ var NVImage = class _NVImage {
|
|
|
18766
18783
|
case NVIMAGE_TYPE.DCM_FOLDER:
|
|
18767
18784
|
case NVIMAGE_TYPE.DCM_MANIFEST:
|
|
18768
18785
|
case NVIMAGE_TYPE.DCM:
|
|
18769
|
-
|
|
18770
|
-
break;
|
|
18786
|
+
return;
|
|
18771
18787
|
case NVIMAGE_TYPE.FIB:
|
|
18772
18788
|
;
|
|
18773
18789
|
[imgRaw, this.v1] = this.readFIB(dataBuffer);
|
|
@@ -19311,121 +19327,133 @@ var NVImage = class _NVImage {
|
|
|
19311
19327
|
}
|
|
19312
19328
|
// not included in public docs
|
|
19313
19329
|
// read DICOM format image and treat it like a NIfTI
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
//
|
|
19330
|
+
// -----------------
|
|
19331
|
+
// readDICOM(buf: ArrayBuffer | ArrayBuffer[]): ArrayBuffer {
|
|
19332
|
+
// this.series = new daikon.Series()
|
|
19333
|
+
// // parse DICOM file
|
|
19334
|
+
// if (Array.isArray(buf)) {
|
|
19335
|
+
// for (let i = 0; i < buf.length; i++) {
|
|
19336
|
+
// const dataview = new DataView(buf[i])
|
|
19337
|
+
// const image = daikon.Series.parseImage(dataview)
|
|
19338
|
+
// if (image === null) {
|
|
19339
|
+
// log.error(daikon.Series.parserError)
|
|
19340
|
+
// } else if (image.hasPixelData()) {
|
|
19341
|
+
// // if it's part of the same series, add it
|
|
19342
|
+
// if (this.series.images.length === 0 || image.getSeriesId() === this.series.images[0].getSeriesId()) {
|
|
19343
|
+
// this.series.addImage(image)
|
|
19344
|
+
// }
|
|
19345
|
+
// } // if hasPixelData
|
|
19346
|
+
// } // for i
|
|
19347
|
+
// } else {
|
|
19348
|
+
// // not a dicom folder drop
|
|
19349
|
+
// const image = daikon.Series.parseImage(new DataView(buf))
|
|
19350
|
+
// if (image === null) {
|
|
19351
|
+
// log.error(daikon.Series.parserError)
|
|
19352
|
+
// } else if (image.hasPixelData()) {
|
|
19353
|
+
// // if it's part of the same series, add it
|
|
19354
|
+
// if (this.series.images.length === 0 || image.getSeriesId() === this.series.images[0].getSeriesId()) {
|
|
19355
|
+
// this.series.addImage(image)
|
|
19356
|
+
// }
|
|
19357
|
+
// }
|
|
19358
|
+
// }
|
|
19359
|
+
// // order the image files, determines number of frames, etc.
|
|
19360
|
+
// this.series.buildSeries()
|
|
19361
|
+
// // output some header info
|
|
19362
|
+
// this.hdr = new nifti.NIFTI1()
|
|
19363
|
+
// const hdr = this.hdr
|
|
19364
|
+
// hdr.scl_inter = 0
|
|
19365
|
+
// hdr.scl_slope = 1
|
|
19366
|
+
// if (this.series.images[0].getDataScaleIntercept()) {
|
|
19367
|
+
// hdr.scl_inter = this.series.images[0].getDataScaleIntercept()
|
|
19368
|
+
// }
|
|
19369
|
+
// if (this.series.images[0].getDataScaleSlope()) {
|
|
19370
|
+
// hdr.scl_slope = this.series.images[0].getDataScaleSlope()
|
|
19371
|
+
// }
|
|
19372
|
+
// hdr.dims = [3, 1, 1, 1, 0, 0, 0, 0]
|
|
19373
|
+
// hdr.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]
|
|
19374
|
+
// hdr.dims[1] = this.series.images[0].getCols()
|
|
19375
|
+
// hdr.dims[2] = this.series.images[0].getRows()
|
|
19376
|
+
// hdr.dims[3] = this.series.images[0].getNumberOfFrames()
|
|
19377
|
+
// if (this.series.images.length > 1) {
|
|
19378
|
+
// if (hdr.dims[3] > 1) {
|
|
19379
|
+
// log.debug('To Do: multiple slices per file and multiple files (XA30 DWI)')
|
|
19380
|
+
// }
|
|
19381
|
+
// hdr.dims[3] = this.series.images.length
|
|
19382
|
+
// }
|
|
19383
|
+
// const rc = this.series.images[0].getPixelSpacing() // TODO: order?
|
|
19384
|
+
// hdr.pixDims[1] = rc[0]
|
|
19385
|
+
// hdr.pixDims[2] = rc[1]
|
|
19386
|
+
// if (this.series.images.length > 1) {
|
|
19387
|
+
// // Multiple slices. The depth of a pixel is the physical distance between offsets. This is not the same as slice
|
|
19388
|
+
// // spacing for tilted slices (skew).
|
|
19389
|
+
// const p0 = vec3.fromValues(...(this.series.images[0].getImagePosition() as [number, number, number]))
|
|
19390
|
+
// const p1 = vec3.fromValues(...(this.series.images[1].getImagePosition() as [number, number, number]))
|
|
19391
|
+
// const n = vec3.fromValues(0, 0, 0)
|
|
19392
|
+
// vec3.subtract(n, p0, p1)
|
|
19393
|
+
// hdr.pixDims[3] = vec3.length(n)
|
|
19394
|
+
// } else {
|
|
19395
|
+
// // Single slice. Use the slice thickness as pixel depth.
|
|
19396
|
+
// hdr.pixDims[3] = this.series.images[0].getSliceThickness()
|
|
19397
|
+
// }
|
|
19398
|
+
// hdr.pixDims[4] = this.series.images[0].getTR() / 1000.0 // msec -> sec
|
|
19399
|
+
// const dt = this.series.images[0].getDataType() // 2=int,3=uint,4=float,
|
|
19400
|
+
// const bpv = this.series.images[0].getBitsAllocated()
|
|
19401
|
+
// hdr.numBitsPerVoxel = bpv
|
|
19402
|
+
// this.hdr.littleEndian = this.series.images[0].littleEndian
|
|
19403
|
+
// if (bpv === 8 && dt === 2) {
|
|
19404
|
+
// hdr.datatypeCode = NiiDataType.DT_INT8
|
|
19405
|
+
// } else if (bpv === 8 && dt === 3) {
|
|
19406
|
+
// hdr.datatypeCode = NiiDataType.DT_UINT8
|
|
19407
|
+
// } else if (bpv === 16 && dt === 2) {
|
|
19408
|
+
// hdr.datatypeCode = NiiDataType.DT_INT16
|
|
19409
|
+
// } else if (bpv === 16 && dt === 3) {
|
|
19410
|
+
// hdr.datatypeCode = NiiDataType.DT_UINT16
|
|
19411
|
+
// } else if (bpv === 32 && dt === 2) {
|
|
19412
|
+
// hdr.datatypeCode = NiiDataType.DT_INT32
|
|
19413
|
+
// } else if (bpv === 32 && dt === 3) {
|
|
19414
|
+
// hdr.datatypeCode = NiiDataType.DT_UINT32
|
|
19415
|
+
// } else if (bpv === 32 && dt === 4) {
|
|
19416
|
+
// hdr.datatypeCode = NiiDataType.DT_FLOAT32
|
|
19417
|
+
// } else if (bpv === 64 && dt === 4) {
|
|
19418
|
+
// hdr.datatypeCode = NiiDataType.DT_FLOAT64
|
|
19419
|
+
// } else if (bpv === 1) {
|
|
19420
|
+
// hdr.datatypeCode = NiiDataType.DT_BINARY
|
|
19421
|
+
// } else {
|
|
19422
|
+
// log.warn('Unsupported DICOM format: ' + dt + ' ' + bpv)
|
|
19423
|
+
// }
|
|
19424
|
+
// const voxelDimensions = hdr.pixDims.slice(1, 4)
|
|
19425
|
+
// const m = getBestTransform(
|
|
19426
|
+
// this.series.images[0].getImageDirections(),
|
|
19427
|
+
// voxelDimensions,
|
|
19428
|
+
// this.series.images[0].getImagePosition()
|
|
19429
|
+
// )
|
|
19430
|
+
// if (m) {
|
|
19431
|
+
// hdr.sform_code = 1
|
|
19432
|
+
// hdr.affine = [
|
|
19433
|
+
// [m[0][0], m[0][1], m[0][2], m[0][3]],
|
|
19434
|
+
// [m[1][0], m[1][1], m[1][2], m[1][3]],
|
|
19435
|
+
// [m[2][0], m[2][1], m[2][2], m[2][3]],
|
|
19436
|
+
// [0, 0, 0, 1]
|
|
19437
|
+
// ]
|
|
19438
|
+
// }
|
|
19439
|
+
// let data
|
|
19440
|
+
// let length = this.series.validatePixelDataLength(this.series.images[0])
|
|
19441
|
+
// const buffer = new Uint8Array(new ArrayBuffer(length * this.series.images.length))
|
|
19442
|
+
// // implementation copied from:
|
|
19443
|
+
// // https://github.com/rii-mango/Daikon/blob/bbe08bad9758dfbdf31ca22fb79048c7bad85706/src/series.js#L496
|
|
19444
|
+
// for (let i = 0; i < this.series.images.length; i++) {
|
|
19445
|
+
// if (this.series.isMosaic) {
|
|
19446
|
+
// data = this.series.getMosaicData(this.series.images[i], this.series.images[i].getPixelDataBytes())
|
|
19447
|
+
// } else {
|
|
19448
|
+
// data = this.series.images[i].getPixelDataBytes()
|
|
19449
|
+
// }
|
|
19450
|
+
// length = this.series.validatePixelDataLength(this.series.images[i])
|
|
19451
|
+
// this.series.images[i].clearPixelData()
|
|
19452
|
+
// buffer.set(new Uint8Array(data, 0, length), length * i)
|
|
19453
|
+
// } // for images.length
|
|
19454
|
+
// return buffer.buffer
|
|
19455
|
+
// } // readDICOM()
|
|
19456
|
+
// -----------------------
|
|
19429
19457
|
// not included in public docs
|
|
19430
19458
|
// read ECAT7 format image
|
|
19431
19459
|
// https://github.com/openneuropet/PET2BIDS/tree/28aae3fab22309047d36d867c624cd629c921ca6/ecat_validation/ecat_info
|
|
@@ -21325,7 +21353,10 @@ var NVImage = class _NVImage {
|
|
|
21325
21353
|
throw Error(response.statusText);
|
|
21326
21354
|
}
|
|
21327
21355
|
const contents = await response.arrayBuffer();
|
|
21328
|
-
dataBuffer.push(
|
|
21356
|
+
dataBuffer.push({
|
|
21357
|
+
name: line,
|
|
21358
|
+
data: contents
|
|
21359
|
+
});
|
|
21329
21360
|
}
|
|
21330
21361
|
return dataBuffer;
|
|
21331
21362
|
}
|
|
@@ -22459,7 +22490,8 @@ var DEFAULT_OPTIONS = {
|
|
|
22459
22490
|
// red
|
|
22460
22491
|
measureLineColor: [1, 0, 0, 1],
|
|
22461
22492
|
// red
|
|
22462
|
-
measureTextHeight: 0.03
|
|
22493
|
+
measureTextHeight: 0.03,
|
|
22494
|
+
isAlphaClipDark: false
|
|
22463
22495
|
};
|
|
22464
22496
|
var INITIAL_SCENE_DATA = {
|
|
22465
22497
|
gamma: 1,
|
|
@@ -22706,55 +22738,12 @@ var NVDocument = class _NVDocument {
|
|
|
22706
22738
|
}
|
|
22707
22739
|
data.customData = this.customData;
|
|
22708
22740
|
if (this.volumes.length) {
|
|
22709
|
-
let
|
|
22710
|
-
if (!imageOptions) {
|
|
22711
|
-
log.debug("no image options for base image");
|
|
22712
|
-
imageOptions = {
|
|
22713
|
-
name: "",
|
|
22714
|
-
colormap: "gray",
|
|
22715
|
-
opacity: 1,
|
|
22716
|
-
pairedImgData: null,
|
|
22717
|
-
cal_min: NaN,
|
|
22718
|
-
cal_max: NaN,
|
|
22719
|
-
trustCalMinMax: true,
|
|
22720
|
-
percentileFrac: 0.02,
|
|
22721
|
-
ignoreZeroVoxels: false,
|
|
22722
|
-
useQFormNotSForm: false,
|
|
22723
|
-
colormapNegative: "",
|
|
22724
|
-
colormapLabel: null,
|
|
22725
|
-
imageType: NVIMAGE_TYPE.NII,
|
|
22726
|
-
frame4D: 0,
|
|
22727
|
-
limitFrames4D: NaN,
|
|
22728
|
-
// TODO the following fields were previously not included
|
|
22729
|
-
url: "",
|
|
22730
|
-
urlImageData: "",
|
|
22731
|
-
alphaThreshold: false,
|
|
22732
|
-
cal_minNeg: NaN,
|
|
22733
|
-
cal_maxNeg: NaN,
|
|
22734
|
-
colorbarVisible: true
|
|
22735
|
-
};
|
|
22736
|
-
}
|
|
22737
|
-
imageOptions.colormap = this.volumes[0].colormap;
|
|
22738
|
-
imageOptions.opacity = this.volumes[0].opacity;
|
|
22739
|
-
imageOptions.cal_max = this.volumes[0].cal_max || NaN;
|
|
22740
|
-
imageOptions.cal_min = this.volumes[0].cal_min || NaN;
|
|
22741
|
-
if (imageOptions) {
|
|
22742
|
-
imageOptionsArray.push(imageOptions);
|
|
22743
|
-
const encodedImageBlob = NVUtilities.uint8tob64(this.volumes[0].toUint8Array());
|
|
22744
|
-
data.encodedImageBlobs.push(encodedImageBlob);
|
|
22745
|
-
if (this.drawBitmap) {
|
|
22746
|
-
data.encodedDrawingBlob = NVUtilities.uint8tob64(this.volumes[0].toUint8Array(this.drawBitmap));
|
|
22747
|
-
}
|
|
22748
|
-
data.imageOptionsMap.set(this.volumes[0].id, 0);
|
|
22749
|
-
} else {
|
|
22750
|
-
throw new Error("image options for base layer not found");
|
|
22751
|
-
}
|
|
22752
|
-
for (let i = 1; i < this.volumes.length; i++) {
|
|
22741
|
+
for (let i = 0; i < this.volumes.length; i++) {
|
|
22753
22742
|
const volume = this.volumes[i];
|
|
22754
|
-
let
|
|
22755
|
-
if (
|
|
22743
|
+
let imageOptions = this.getImageOptions(volume);
|
|
22744
|
+
if (imageOptions === null) {
|
|
22756
22745
|
log.warn("no options found for image, using default");
|
|
22757
|
-
|
|
22746
|
+
imageOptions = {
|
|
22758
22747
|
name: "",
|
|
22759
22748
|
colormap: "gray",
|
|
22760
22749
|
opacity: 1,
|
|
@@ -22779,16 +22768,16 @@ var NVDocument = class _NVDocument {
|
|
|
22779
22768
|
colorbarVisible: true
|
|
22780
22769
|
};
|
|
22781
22770
|
} else {
|
|
22782
|
-
if (!("imageType" in
|
|
22783
|
-
|
|
22771
|
+
if (!("imageType" in imageOptions)) {
|
|
22772
|
+
imageOptions.imageType = NVIMAGE_TYPE.NII;
|
|
22784
22773
|
}
|
|
22785
22774
|
}
|
|
22786
|
-
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
imageOptionsArray.push(
|
|
22775
|
+
imageOptions.colormap = volume.colormap;
|
|
22776
|
+
imageOptions.colormapLabel = volume.colormapLabel;
|
|
22777
|
+
imageOptions.opacity = volume.opacity;
|
|
22778
|
+
imageOptions.cal_max = volume.cal_max || NaN;
|
|
22779
|
+
imageOptions.cal_min = volume.cal_min || NaN;
|
|
22780
|
+
imageOptionsArray.push(imageOptions);
|
|
22792
22781
|
const encodedImageBlob = NVUtilities.uint8tob64(volume.toUint8Array());
|
|
22793
22782
|
data.encodedImageBlobs.push(encodedImageBlob);
|
|
22794
22783
|
data.imageOptionsMap.set(volume.id, i);
|
|
@@ -22857,6 +22846,9 @@ var NVDocument = class _NVDocument {
|
|
|
22857
22846
|
meshes.push(copyMesh);
|
|
22858
22847
|
}
|
|
22859
22848
|
data.meshesString = JSON.stringify(serialize(meshes));
|
|
22849
|
+
if (this.drawBitmap) {
|
|
22850
|
+
data.encodedDrawingBlob = NVUtilities.uint8tob64(this.drawBitmap);
|
|
22851
|
+
}
|
|
22860
22852
|
return data;
|
|
22861
22853
|
}
|
|
22862
22854
|
/**
|
|
@@ -24206,6 +24198,16 @@ var Niivue = class {
|
|
|
24206
24198
|
*/
|
|
24207
24199
|
constructor(options = DEFAULT_OPTIONS) {
|
|
24208
24200
|
__publicField(this, "loaders", {});
|
|
24201
|
+
// create a dicom loader
|
|
24202
|
+
__publicField(this, "dicomLoader", null);
|
|
24203
|
+
// {
|
|
24204
|
+
// loader: (data: DicomLoaderInput) => {
|
|
24205
|
+
// return new Promise<{name: string; data: ArrayBuffer}[]>((resolve, reject) => {
|
|
24206
|
+
// reject('No DICOM loader provided')
|
|
24207
|
+
// })
|
|
24208
|
+
// },
|
|
24209
|
+
// toExt: 'nii'
|
|
24210
|
+
// }
|
|
24209
24211
|
__publicField(this, "canvas", null);
|
|
24210
24212
|
// the reference to the canvas element on the page
|
|
24211
24213
|
__publicField(this, "_gl", null);
|
|
@@ -24301,7 +24303,6 @@ var Niivue = class {
|
|
|
24301
24303
|
// float, 0 for none
|
|
24302
24304
|
__publicField(this, "overlayAlphaShader", 1);
|
|
24303
24305
|
// float, 1 for opaque
|
|
24304
|
-
__publicField(this, "isAlphaClipDark", false);
|
|
24305
24306
|
__publicField(this, "position");
|
|
24306
24307
|
__publicField(this, "extentsMin");
|
|
24307
24308
|
__publicField(this, "extentsMax");
|
|
@@ -24637,6 +24638,8 @@ var Niivue = class {
|
|
|
24637
24638
|
});
|
|
24638
24639
|
__publicField(this, "onMeshPropertyChanged", () => {
|
|
24639
24640
|
});
|
|
24641
|
+
__publicField(this, "onDicomLoaderFinishedWithImages", () => {
|
|
24642
|
+
});
|
|
24640
24643
|
/**
|
|
24641
24644
|
* callback function to run when the user loads a new NiiVue document
|
|
24642
24645
|
* @example
|
|
@@ -24685,6 +24688,12 @@ var Niivue = class {
|
|
|
24685
24688
|
set sliceMosaicString(newSliceMosaicString) {
|
|
24686
24689
|
this.document.opts.sliceMosaicString = newSliceMosaicString;
|
|
24687
24690
|
}
|
|
24691
|
+
get isAlphaClipDark() {
|
|
24692
|
+
return this.document.opts.isAlphaClipDark;
|
|
24693
|
+
}
|
|
24694
|
+
set isAlphaClipDark(newVal) {
|
|
24695
|
+
this.document.opts.isAlphaClipDark = newVal;
|
|
24696
|
+
}
|
|
24688
24697
|
get volumes() {
|
|
24689
24698
|
return this.document.volumes;
|
|
24690
24699
|
}
|
|
@@ -25661,6 +25670,9 @@ var Niivue = class {
|
|
|
25661
25670
|
async addVolumesFromUrl(imageOptionsArray) {
|
|
25662
25671
|
const promises = imageOptionsArray.map(async (imageItem) => {
|
|
25663
25672
|
const ext = this.getFileExt(imageItem.name || imageItem.url);
|
|
25673
|
+
if (ext === "DCM") {
|
|
25674
|
+
throw new Error("DICOM files must be loaded using useDicomLoader");
|
|
25675
|
+
}
|
|
25664
25676
|
if (this.loaders[ext]) {
|
|
25665
25677
|
let itemToLoad = imageItem.url;
|
|
25666
25678
|
const toExt = this.loaders[ext].toExt;
|
|
@@ -25732,6 +25744,36 @@ var Niivue = class {
|
|
|
25732
25744
|
throw new Error("No volume with URL present");
|
|
25733
25745
|
}
|
|
25734
25746
|
}
|
|
25747
|
+
async traverseFileTree(item, path = "", fileArray) {
|
|
25748
|
+
return new Promise((resolve) => {
|
|
25749
|
+
if (item.isFile) {
|
|
25750
|
+
item.file((file) => {
|
|
25751
|
+
file.fullPath = path + file.name;
|
|
25752
|
+
file._webkitRelativePath = path + file.name;
|
|
25753
|
+
fileArray.push(file);
|
|
25754
|
+
resolve(fileArray);
|
|
25755
|
+
});
|
|
25756
|
+
} else if (item.isDirectory) {
|
|
25757
|
+
const dirReader = item.createReader();
|
|
25758
|
+
const readAllEntries = () => {
|
|
25759
|
+
dirReader.readEntries((entries) => {
|
|
25760
|
+
if (entries.length > 0) {
|
|
25761
|
+
const promises = [];
|
|
25762
|
+
for (const entry of entries) {
|
|
25763
|
+
promises.push(this.traverseFileTree(entry, path + item.name + "/", fileArray));
|
|
25764
|
+
}
|
|
25765
|
+
Promise.all(promises).then(readAllEntries).catch((e) => {
|
|
25766
|
+
throw e;
|
|
25767
|
+
});
|
|
25768
|
+
} else {
|
|
25769
|
+
resolve(fileArray);
|
|
25770
|
+
}
|
|
25771
|
+
});
|
|
25772
|
+
};
|
|
25773
|
+
readAllEntries();
|
|
25774
|
+
}
|
|
25775
|
+
});
|
|
25776
|
+
}
|
|
25735
25777
|
readDirectory(directory) {
|
|
25736
25778
|
const reader = directory.createReader();
|
|
25737
25779
|
let allEntiresInDir = [];
|
|
@@ -25742,6 +25784,7 @@ var Niivue = class {
|
|
|
25742
25784
|
};
|
|
25743
25785
|
for (let i = 0; i < fileSystemEntries.length; i++) {
|
|
25744
25786
|
allFileObects.push(await getFile(fileSystemEntries[i]));
|
|
25787
|
+
console.log(allFileObects);
|
|
25745
25788
|
}
|
|
25746
25789
|
return allFileObects;
|
|
25747
25790
|
};
|
|
@@ -25751,21 +25794,8 @@ var Niivue = class {
|
|
|
25751
25794
|
allEntiresInDir = allEntiresInDir.concat(entries);
|
|
25752
25795
|
readEntries();
|
|
25753
25796
|
} else {
|
|
25754
|
-
getFileObjects(allEntiresInDir).then((allFileObjects) => {
|
|
25755
|
-
|
|
25756
|
-
file: allFileObjects,
|
|
25757
|
-
// an array of file objects
|
|
25758
|
-
name: directory.name,
|
|
25759
|
-
urlImgData: null,
|
|
25760
|
-
// nothing
|
|
25761
|
-
imageType: NVIMAGE_TYPE.DCM_FOLDER
|
|
25762
|
-
// signify that this is a dicom directory
|
|
25763
|
-
}).then((volume) => {
|
|
25764
|
-
this.addVolume(volume);
|
|
25765
|
-
this.setDrawingEnabled(true);
|
|
25766
|
-
}).catch((e) => {
|
|
25767
|
-
throw e;
|
|
25768
|
-
});
|
|
25797
|
+
getFileObjects(allEntiresInDir).then(async (allFileObjects) => {
|
|
25798
|
+
console.log(allFileObjects);
|
|
25769
25799
|
}).catch((e) => {
|
|
25770
25800
|
throw e;
|
|
25771
25801
|
});
|
|
@@ -25846,6 +25876,17 @@ var Niivue = class {
|
|
|
25846
25876
|
})
|
|
25847
25877
|
|
|
25848
25878
|
*/
|
|
25879
|
+
// interface LoaderMap {
|
|
25880
|
+
// [key: string]: {
|
|
25881
|
+
// loader: LoaderFunction;
|
|
25882
|
+
// toExt: string;
|
|
25883
|
+
// };
|
|
25884
|
+
// }
|
|
25885
|
+
// Example usage:
|
|
25886
|
+
// const loaderMap: LoaderMap = {
|
|
25887
|
+
// 'TXT': { loader: (file: File) => file.text(), toExt: 'json' }
|
|
25888
|
+
// };
|
|
25889
|
+
// loader can either be a function that takes a file or ArrayBuffer, or the async version of that.
|
|
25849
25890
|
useLoader(loader, fileExt, toExt) {
|
|
25850
25891
|
this.loaders = {
|
|
25851
25892
|
...this.loaders,
|
|
@@ -25855,6 +25896,24 @@ var Niivue = class {
|
|
|
25855
25896
|
}
|
|
25856
25897
|
};
|
|
25857
25898
|
}
|
|
25899
|
+
useDicomLoader(loader) {
|
|
25900
|
+
this.dicomLoader = loader;
|
|
25901
|
+
}
|
|
25902
|
+
getDicomLoader() {
|
|
25903
|
+
return this.dicomLoader;
|
|
25904
|
+
}
|
|
25905
|
+
// dicom loading is a special case because it can take a list
|
|
25906
|
+
// of files (e.g. from a user supplied DICOM directory) or a single file.
|
|
25907
|
+
// Our preferred DICOM loader is the WASM port of dcm2niix (implemented in a separate niivue loader module).
|
|
25908
|
+
// useDicomLoader(loader: unknown, toExt: string) {
|
|
25909
|
+
// this.loaders = {
|
|
25910
|
+
// ...this.loaders,
|
|
25911
|
+
// ['DCM']: {
|
|
25912
|
+
// loader,
|
|
25913
|
+
// toExt,
|
|
25914
|
+
// },
|
|
25915
|
+
// }
|
|
25916
|
+
// }
|
|
25858
25917
|
// not included in public docs
|
|
25859
25918
|
dropListener(e) {
|
|
25860
25919
|
e.stopPropagation();
|
|
@@ -25863,6 +25922,7 @@ var Niivue = class {
|
|
|
25863
25922
|
return;
|
|
25864
25923
|
}
|
|
25865
25924
|
const urlsToLoad = [];
|
|
25925
|
+
const files = [];
|
|
25866
25926
|
const dt = e.dataTransfer;
|
|
25867
25927
|
if (!dt) {
|
|
25868
25928
|
return;
|
|
@@ -25990,7 +26050,33 @@ var Niivue = class {
|
|
|
25990
26050
|
}
|
|
25991
26051
|
});
|
|
25992
26052
|
} else if (entry.isDirectory) {
|
|
25993
|
-
this.
|
|
26053
|
+
this.traverseFileTree(entry, "", files).then((files2) => {
|
|
26054
|
+
const loader = this.getDicomLoader().loader;
|
|
26055
|
+
if (!loader) {
|
|
26056
|
+
throw new Error("No loader for DICOM files");
|
|
26057
|
+
}
|
|
26058
|
+
loader(files2).then(async (fileArrayBuffers) => {
|
|
26059
|
+
console.log(fileArrayBuffers);
|
|
26060
|
+
const promises = fileArrayBuffers.map(
|
|
26061
|
+
(loaderImage) => NVImage.loadFromUrl({
|
|
26062
|
+
url: loaderImage.data,
|
|
26063
|
+
name: loaderImage.name,
|
|
26064
|
+
limitFrames4D: this.opts.limitFrames4D
|
|
26065
|
+
})
|
|
26066
|
+
);
|
|
26067
|
+
Promise.all(promises).then(async (loadedNvImages) => {
|
|
26068
|
+
console.log("from dicom loader");
|
|
26069
|
+
console.log(loadedNvImages);
|
|
26070
|
+
await this.onDicomLoaderFinishedWithImages(loadedNvImages);
|
|
26071
|
+
}).catch((e2) => {
|
|
26072
|
+
throw e2;
|
|
26073
|
+
});
|
|
26074
|
+
}).catch((error) => {
|
|
26075
|
+
console.error("Error loading DICOM files:", error);
|
|
26076
|
+
});
|
|
26077
|
+
}).catch((e2) => {
|
|
26078
|
+
throw e2;
|
|
26079
|
+
});
|
|
25994
26080
|
}
|
|
25995
26081
|
}
|
|
25996
26082
|
}
|
|
@@ -26725,13 +26811,13 @@ var Niivue = class {
|
|
|
26725
26811
|
* @example niivue.setMeshLayerProperty(niivue.meshes[0].id, 0, 'frame4D', 22)
|
|
26726
26812
|
* @see {@link https://niivue.github.io/niivue/features/mesh.4D.html | live demo usage}
|
|
26727
26813
|
*/
|
|
26728
|
-
setMeshLayerProperty(mesh, layer, key, val) {
|
|
26814
|
+
async setMeshLayerProperty(mesh, layer, key, val) {
|
|
26729
26815
|
const idx = this.getMeshIndexByID(mesh);
|
|
26730
26816
|
if (idx < 0) {
|
|
26731
26817
|
log.warn("setMeshLayerProperty() id not loaded", mesh);
|
|
26732
26818
|
return;
|
|
26733
26819
|
}
|
|
26734
|
-
this.meshes[idx].setLayerProperty(layer, key, val, this.gl);
|
|
26820
|
+
await this.meshes[idx].setLayerProperty(layer, key, val, this.gl);
|
|
26735
26821
|
this.updateGLVolume();
|
|
26736
26822
|
}
|
|
26737
26823
|
/**
|
|
@@ -27304,12 +27390,12 @@ var Niivue = class {
|
|
|
27304
27390
|
const encodedImageBlobs = document2.encodedImageBlobs;
|
|
27305
27391
|
for (let i = 0; i < document2.imageOptionsArray.length; i++) {
|
|
27306
27392
|
const imageOptions = document2.imageOptionsArray[i];
|
|
27307
|
-
const
|
|
27308
|
-
if (
|
|
27393
|
+
const base64 = encodedImageBlobs[i];
|
|
27394
|
+
if (base64) {
|
|
27309
27395
|
if ("colorMap" in imageOptions) {
|
|
27310
27396
|
imageOptions.colormap = imageOptions.colorMap;
|
|
27311
27397
|
}
|
|
27312
|
-
const image = NVImage.loadFromBase64({ base64
|
|
27398
|
+
const image = NVImage.loadFromBase64({ base64, ...imageOptions });
|
|
27313
27399
|
if (image) {
|
|
27314
27400
|
if (image.colormapLabel) {
|
|
27315
27401
|
const length = Object.keys(image.colormapLabel.lut).length;
|
|
@@ -27326,14 +27412,6 @@ var Niivue = class {
|
|
|
27326
27412
|
if (this.volumes.length > 0) {
|
|
27327
27413
|
this.back = this.volumes[0];
|
|
27328
27414
|
}
|
|
27329
|
-
const base64 = document2.encodedDrawingBlob;
|
|
27330
|
-
if (base64) {
|
|
27331
|
-
const imageOptions = document2.imageOptionsArray[0];
|
|
27332
|
-
const drawingBitmap = NVImage.loadFromBase64({ base64, ...imageOptions });
|
|
27333
|
-
if (drawingBitmap) {
|
|
27334
|
-
this.loadDrawing(drawingBitmap);
|
|
27335
|
-
}
|
|
27336
|
-
}
|
|
27337
27415
|
for (const meshDataObject of document2.meshDataObjects ?? []) {
|
|
27338
27416
|
const meshInit = { gl: this.gl, ...meshDataObject };
|
|
27339
27417
|
if (meshDataObject.offsetPt0) {
|
|
@@ -27375,6 +27453,19 @@ var Niivue = class {
|
|
|
27375
27453
|
this.addMesh(meshToAdd);
|
|
27376
27454
|
}
|
|
27377
27455
|
}
|
|
27456
|
+
this.createEmptyDrawing();
|
|
27457
|
+
const drawingBase64 = document2.encodedDrawingBlob;
|
|
27458
|
+
if (drawingBase64) {
|
|
27459
|
+
const drawingBitmap = NVUtilities.b64toUint8(drawingBase64);
|
|
27460
|
+
if (drawingBitmap) {
|
|
27461
|
+
const dims = this.back.dims;
|
|
27462
|
+
if (drawingBitmap.length !== dims[1] * dims[2] * dims[3]) {
|
|
27463
|
+
throw new Error("drawBitmap size does not match the texture dimensions.");
|
|
27464
|
+
}
|
|
27465
|
+
this.drawBitmap = drawingBitmap;
|
|
27466
|
+
this.refreshDrawing();
|
|
27467
|
+
}
|
|
27468
|
+
}
|
|
27378
27469
|
this.updateGLVolume();
|
|
27379
27470
|
this.drawScene();
|
|
27380
27471
|
this.onDocumentLoaded(document2);
|
|
@@ -27571,6 +27662,44 @@ var Niivue = class {
|
|
|
27571
27662
|
}
|
|
27572
27663
|
return this;
|
|
27573
27664
|
}
|
|
27665
|
+
async loadDicoms(dicomList) {
|
|
27666
|
+
if (!this.getDicomLoader()) {
|
|
27667
|
+
throw new Error("No dicom loader set. Please set a dicom loader before loading dicoms");
|
|
27668
|
+
}
|
|
27669
|
+
this.drawScene();
|
|
27670
|
+
this.volumes = [];
|
|
27671
|
+
this.gl.clearColor(0, 0, 0, 1);
|
|
27672
|
+
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
27673
|
+
const promises = dicomList.map(async (dicom) => {
|
|
27674
|
+
let dicomData = null;
|
|
27675
|
+
if (dicom.isManifest) {
|
|
27676
|
+
dicomData = await NVImage.fetchDicomData(dicom.url);
|
|
27677
|
+
} else {
|
|
27678
|
+
const response = await fetch(dicom.url);
|
|
27679
|
+
if (!response.ok) {
|
|
27680
|
+
throw new Error(`Failed to load file: ${response.statusText}`);
|
|
27681
|
+
}
|
|
27682
|
+
const dicomArrayBuffer = await response.arrayBuffer();
|
|
27683
|
+
const basename = dicom.url.split("/").pop();
|
|
27684
|
+
const data2 = dicomArrayBuffer;
|
|
27685
|
+
dicomData = [{ name: basename, data: data2 }];
|
|
27686
|
+
}
|
|
27687
|
+
const dicomLoader = this.getDicomLoader().loader;
|
|
27688
|
+
const convertedArrayBuffer = await dicomLoader(dicomData);
|
|
27689
|
+
console.log(convertedArrayBuffer);
|
|
27690
|
+
const name = convertedArrayBuffer[0].name;
|
|
27691
|
+
const data = convertedArrayBuffer[0].data;
|
|
27692
|
+
const image = NVImage.loadFromUrl({ url: data, name });
|
|
27693
|
+
return image;
|
|
27694
|
+
});
|
|
27695
|
+
const nvImages = await Promise.all(promises);
|
|
27696
|
+
if (nvImages.length === 1) {
|
|
27697
|
+
this.addVolume(nvImages[0]);
|
|
27698
|
+
} else {
|
|
27699
|
+
this.onDicomLoaderFinishedWithImages(nvImages);
|
|
27700
|
+
}
|
|
27701
|
+
return this;
|
|
27702
|
+
}
|
|
27574
27703
|
/**
|
|
27575
27704
|
* load an array of volume objects
|
|
27576
27705
|
* @param volumeList - the array of objects to load. each object must have a resolvable "url" property at a minimum
|
|
@@ -29615,6 +29744,17 @@ var Niivue = class {
|
|
|
29615
29744
|
mxNeg = Math.max(overlayItem.cal_minNeg, overlayItem.cal_maxNeg);
|
|
29616
29745
|
}
|
|
29617
29746
|
}
|
|
29747
|
+
if (layer > 0 && this.overlayOutlineWidth > 0) {
|
|
29748
|
+
const A = overlayItem.cal_min;
|
|
29749
|
+
const B = overlayItem.cal_max;
|
|
29750
|
+
let isZeroCrossing = Math.min(A, B) <= 0 && Math.max(A, B) >= 0;
|
|
29751
|
+
if (!isZeroCrossing && mnNeg < mxNeg) {
|
|
29752
|
+
isZeroCrossing = mnNeg <= 0 && mxNeg >= 0;
|
|
29753
|
+
}
|
|
29754
|
+
if (isZeroCrossing) {
|
|
29755
|
+
log.error("issue1139: do not use overlayOutlineWidth when thresholds cross or touch zero");
|
|
29756
|
+
}
|
|
29757
|
+
}
|
|
29618
29758
|
if (!orientShader) {
|
|
29619
29759
|
throw new Error("orientShader undefined");
|
|
29620
29760
|
}
|