@niivue/niivue 0.44.1 → 0.44.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/build/index.js +102 -49
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.js +25483 -30564
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +47 -9
- package/package.json +18 -17
- package/src/niivue/index.ts +6 -1
- package/src/nvimage/utils.ts +61 -42
- package/src/shader-srcs.ts +43 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@niivue/niivue",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.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/niivue/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
fragMeshMatcapShader,
|
|
42
42
|
fragMeshOutlineShader,
|
|
43
43
|
fragMeshEdgeShader,
|
|
44
|
+
fragMeshShaderCrevice,
|
|
44
45
|
fragMeshDiffuseEdgeShader,
|
|
45
46
|
fragMeshHemiShader,
|
|
46
47
|
fragMeshMatteShader,
|
|
@@ -479,6 +480,10 @@ export class Niivue {
|
|
|
479
480
|
Name: 'Hemispheric',
|
|
480
481
|
Frag: fragMeshHemiShader
|
|
481
482
|
},
|
|
483
|
+
{
|
|
484
|
+
Name: 'Crevice',
|
|
485
|
+
Frag: fragMeshShaderCrevice
|
|
486
|
+
},
|
|
482
487
|
{
|
|
483
488
|
Name: 'Edge',
|
|
484
489
|
Frag: fragMeshEdgeShader
|
|
@@ -2051,7 +2056,7 @@ export class Niivue {
|
|
|
2051
2056
|
this.addVolume(volume)
|
|
2052
2057
|
// set drawing enabled to make sure
|
|
2053
2058
|
// the new drawing bitmap matches the background volume dims
|
|
2054
|
-
this.setDrawingEnabled(true)
|
|
2059
|
+
// this.setDrawingEnabled(true)
|
|
2055
2060
|
}
|
|
2056
2061
|
})
|
|
2057
2062
|
.catch((e) => {
|
package/src/nvimage/utils.ts
CHANGED
|
@@ -373,10 +373,13 @@ following conditions are met:
|
|
|
373
373
|
return m
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
function str2Buffer(str: string): number[] {
|
|
376
|
+
function str2Buffer(str: string, maxLen: number = 80): number[] {
|
|
377
377
|
// emulate node.js Buffer.from
|
|
378
|
+
// remove characters than could be used for shell expansion
|
|
379
|
+
str = str.replace(/[`$]/g, '')
|
|
378
380
|
const bytes = []
|
|
379
|
-
|
|
381
|
+
const len = Math.min(maxLen, str.length)
|
|
382
|
+
for (let i = 0; i < len; i++) {
|
|
380
383
|
const char = str.charCodeAt(i)
|
|
381
384
|
bytes.push(char & 0xff)
|
|
382
385
|
}
|
|
@@ -384,70 +387,79 @@ function str2Buffer(str: string): number[] {
|
|
|
384
387
|
}
|
|
385
388
|
|
|
386
389
|
// save NIfTI header into UINT8 array for saving to disk
|
|
387
|
-
export function hdrToArrayBuffer(hdr: NiftiHeader, isDrawing8 = false): Uint8Array {
|
|
390
|
+
export function hdrToArrayBuffer(hdr: NiftiHeader, isDrawing8 = false, isInputEndian = false): Uint8Array {
|
|
388
391
|
const SHORT_SIZE = 2
|
|
389
392
|
const FLOAT32_SIZE = 4
|
|
390
|
-
|
|
393
|
+
let isLittleEndian = true
|
|
394
|
+
if (isInputEndian) {
|
|
395
|
+
isLittleEndian = hdr.littleEndian
|
|
396
|
+
}
|
|
391
397
|
const byteArray = new Uint8Array(348)
|
|
392
398
|
const view = new DataView(byteArray.buffer)
|
|
393
399
|
// sizeof_hdr
|
|
394
|
-
view.setInt32(0, 348,
|
|
400
|
+
view.setInt32(0, 348, isLittleEndian)
|
|
395
401
|
|
|
396
402
|
// data_type, db_name, extents, session_error, regular are not used
|
|
397
|
-
|
|
403
|
+
// regular set to 'r' (ASCII 114) for Analyze compatibility
|
|
404
|
+
view.setUint8(38, 114)
|
|
398
405
|
// dim_info
|
|
399
406
|
view.setUint8(39, hdr.dim_info)
|
|
400
407
|
|
|
401
408
|
// dims
|
|
402
409
|
for (let i = 0; i < 8; i++) {
|
|
403
|
-
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i],
|
|
410
|
+
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i], isLittleEndian)
|
|
404
411
|
}
|
|
405
412
|
|
|
406
413
|
// intent_p1, intent_p2, intent_p3
|
|
407
|
-
view.setFloat32(56, hdr.intent_p1,
|
|
408
|
-
view.setFloat32(60, hdr.intent_p2,
|
|
409
|
-
view.setFloat32(64, hdr.intent_p3,
|
|
414
|
+
view.setFloat32(56, hdr.intent_p1, isLittleEndian)
|
|
415
|
+
view.setFloat32(60, hdr.intent_p2, isLittleEndian)
|
|
416
|
+
view.setFloat32(64, hdr.intent_p3, isLittleEndian)
|
|
410
417
|
// intent_code, datatype, bitpix, slice_start
|
|
411
|
-
view.setInt16(68, hdr.intent_code,
|
|
418
|
+
view.setInt16(68, hdr.intent_code, isLittleEndian)
|
|
412
419
|
if (isDrawing8) {
|
|
413
|
-
view.setInt16(70, 2,
|
|
414
|
-
view.setInt16(72, 8,
|
|
420
|
+
view.setInt16(70, 2, isLittleEndian) // 2 = DT_UINT8
|
|
421
|
+
view.setInt16(72, 8, isLittleEndian)
|
|
415
422
|
} else {
|
|
416
|
-
view.setInt16(70, hdr.datatypeCode,
|
|
417
|
-
view.setInt16(72, hdr.numBitsPerVoxel,
|
|
423
|
+
view.setInt16(70, hdr.datatypeCode, isLittleEndian)
|
|
424
|
+
view.setInt16(72, hdr.numBitsPerVoxel, isLittleEndian)
|
|
418
425
|
}
|
|
419
|
-
view.setInt16(74, hdr.slice_start,
|
|
426
|
+
view.setInt16(74, hdr.slice_start, isLittleEndian)
|
|
420
427
|
|
|
421
428
|
// pixdim[8], vox_offset, scl_slope, scl_inter
|
|
422
429
|
for (let i = 0; i < 8; i++) {
|
|
423
|
-
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i],
|
|
430
|
+
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i], isLittleEndian)
|
|
424
431
|
}
|
|
425
432
|
if (isDrawing8) {
|
|
426
|
-
view.setFloat32(108, 352,
|
|
427
|
-
view.setFloat32(112, 1.0,
|
|
428
|
-
view.setFloat32(116, 0.0,
|
|
433
|
+
view.setFloat32(108, 352, isLittleEndian)
|
|
434
|
+
view.setFloat32(112, 1.0, isLittleEndian)
|
|
435
|
+
view.setFloat32(116, 0.0, isLittleEndian)
|
|
429
436
|
} else {
|
|
430
|
-
view.setFloat32(108, hdr.vox_offset,
|
|
431
|
-
view.setFloat32(
|
|
432
|
-
view.setFloat32(
|
|
437
|
+
// view.setFloat32(108, hdr.vox_offset, isLittleEndian)
|
|
438
|
+
view.setFloat32(108, 352, isLittleEndian)
|
|
439
|
+
view.setFloat32(112, hdr.scl_slope, isLittleEndian)
|
|
440
|
+
view.setFloat32(116, hdr.scl_inter, isLittleEndian)
|
|
433
441
|
}
|
|
434
442
|
// slice_end
|
|
435
|
-
view.setInt16(120, hdr.slice_end,
|
|
443
|
+
view.setInt16(120, hdr.slice_end, isLittleEndian)
|
|
436
444
|
|
|
437
445
|
// slice_code, xyzt_units
|
|
438
446
|
view.setUint8(122, hdr.slice_code)
|
|
439
|
-
|
|
447
|
+
if (hdr.xyzt_units === 0) {
|
|
448
|
+
view.setUint8(123, 10)
|
|
449
|
+
} else {
|
|
450
|
+
view.setUint8(123, hdr.xyzt_units)
|
|
451
|
+
}
|
|
440
452
|
|
|
441
453
|
// cal_max, cal_min, slice_duration, toffset
|
|
442
454
|
if (isDrawing8) {
|
|
443
|
-
view.setFloat32(124, 0,
|
|
444
|
-
view.setFloat32(128, 0,
|
|
455
|
+
view.setFloat32(124, 0, isLittleEndian)
|
|
456
|
+
view.setFloat32(128, 0, isLittleEndian)
|
|
445
457
|
} else {
|
|
446
|
-
view.setFloat32(124, hdr.cal_max,
|
|
447
|
-
view.setFloat32(128, hdr.cal_min,
|
|
458
|
+
view.setFloat32(124, hdr.cal_max, isLittleEndian)
|
|
459
|
+
view.setFloat32(128, hdr.cal_min, isLittleEndian)
|
|
448
460
|
}
|
|
449
|
-
view.setFloat32(132, hdr.slice_duration,
|
|
450
|
-
view.setFloat32(136, hdr.toffset,
|
|
461
|
+
view.setFloat32(132, hdr.slice_duration, isLittleEndian)
|
|
462
|
+
view.setFloat32(136, hdr.toffset, isLittleEndian)
|
|
451
463
|
|
|
452
464
|
// glmax, glmin are unused
|
|
453
465
|
|
|
@@ -457,27 +469,34 @@ export function hdrToArrayBuffer(hdr: NiftiHeader, isDrawing8 = false): Uint8Arr
|
|
|
457
469
|
// node.js: byteArray.set(Buffer.from(hdr.aux_file), 228);
|
|
458
470
|
byteArray.set(str2Buffer(hdr.aux_file), 228)
|
|
459
471
|
// qform_code, sform_code
|
|
460
|
-
view.setInt16(252, hdr.qform_code,
|
|
461
|
-
|
|
472
|
+
view.setInt16(252, hdr.qform_code, isLittleEndian)
|
|
473
|
+
// if sform unknown, assume NIFTI_XFORM_SCANNER_ANAT
|
|
474
|
+
if (hdr.sform_code < 1 || hdr.sform_code < 1) {
|
|
475
|
+
view.setInt16(254, 1, isLittleEndian)
|
|
476
|
+
} else {
|
|
477
|
+
view.setInt16(254, hdr.sform_code, isLittleEndian)
|
|
478
|
+
}
|
|
462
479
|
|
|
463
480
|
// quatern_b, quatern_c, quatern_d, qoffset_x, qoffset_y, qoffset_z, srow_x[4], srow_y[4], and srow_z[4]
|
|
464
|
-
view.setFloat32(256, hdr.quatern_b,
|
|
465
|
-
view.setFloat32(260, hdr.quatern_c,
|
|
466
|
-
view.setFloat32(264, hdr.quatern_d,
|
|
467
|
-
view.setFloat32(268, hdr.qoffset_x,
|
|
468
|
-
view.setFloat32(272, hdr.qoffset_y,
|
|
469
|
-
view.setFloat32(276, hdr.qoffset_z,
|
|
481
|
+
view.setFloat32(256, hdr.quatern_b, isLittleEndian)
|
|
482
|
+
view.setFloat32(260, hdr.quatern_c, isLittleEndian)
|
|
483
|
+
view.setFloat32(264, hdr.quatern_d, isLittleEndian)
|
|
484
|
+
view.setFloat32(268, hdr.qoffset_x, isLittleEndian)
|
|
485
|
+
view.setFloat32(272, hdr.qoffset_y, isLittleEndian)
|
|
486
|
+
view.setFloat32(276, hdr.qoffset_z, isLittleEndian)
|
|
470
487
|
const flattened = hdr.affine.flat()
|
|
471
488
|
// we only want the first three rows
|
|
472
489
|
for (let i = 0; i < 12; i++) {
|
|
473
|
-
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i],
|
|
490
|
+
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i], isLittleEndian)
|
|
474
491
|
}
|
|
475
492
|
// node.js https://www.w3schools.com/nodejs/met_buffer_from.asp
|
|
476
493
|
// intent_name and magic
|
|
477
494
|
// node.js byteArray.set(Buffer.from(hdr.intent_name), 328);
|
|
478
|
-
byteArray.set(str2Buffer(hdr.intent_name), 328)
|
|
495
|
+
// byteArray.set(str2Buffer(hdr.intent_name), 328)
|
|
479
496
|
// node.js byteArray.set(Buffer.from(hdr.magic), 344);
|
|
480
|
-
byteArray.set(str2Buffer(hdr.magic), 344)
|
|
497
|
+
// byteArray.set(str2Buffer(hdr.magic), 344)
|
|
498
|
+
view.setInt32(344, 3222382, true) // "n+1\0"
|
|
499
|
+
|
|
481
500
|
return byteArray
|
|
482
501
|
// return byteArray.buffer;
|
|
483
502
|
}
|
package/src/shader-srcs.ts
CHANGED
|
@@ -1207,11 +1207,11 @@ uniform mat4 mvpMtx;
|
|
|
1207
1207
|
uniform mat4 normMtx;
|
|
1208
1208
|
out vec4 vClr;
|
|
1209
1209
|
out vec3 vN;
|
|
1210
|
-
out vec4
|
|
1210
|
+
out vec4 vPc;
|
|
1211
1211
|
void main(void) {
|
|
1212
1212
|
vec3 lightPosition = vec3(0.0, 0.0, -10.0);
|
|
1213
|
-
|
|
1214
|
-
gl_Position =
|
|
1213
|
+
vPc = mvpMtx * vec4(pos, 1.0);
|
|
1214
|
+
gl_Position = vPc;
|
|
1215
1215
|
vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz);
|
|
1216
1216
|
//vV = -vec3(modelMtx*vec4(pos,1.0));
|
|
1217
1217
|
vClr = clr;
|
|
@@ -1368,6 +1368,46 @@ void main() {
|
|
|
1368
1368
|
color = vec4(d3 + s3 + s, opacity);
|
|
1369
1369
|
}`
|
|
1370
1370
|
|
|
1371
|
+
// https://thorium.rocks/media/curves/curvaceous.html
|
|
1372
|
+
export const fragMeshShaderCrevice = `#version 300 es
|
|
1373
|
+
precision highp int;
|
|
1374
|
+
precision highp float;
|
|
1375
|
+
uniform float opacity;
|
|
1376
|
+
in vec4 vClr;
|
|
1377
|
+
in vec3 vN;
|
|
1378
|
+
in vec4 vPc;
|
|
1379
|
+
out vec4 color;
|
|
1380
|
+
void main() {
|
|
1381
|
+
vec3 n = normalize(vN);
|
|
1382
|
+
// Compute curvature
|
|
1383
|
+
vec3 dx = dFdx(n);
|
|
1384
|
+
vec3 dy = dFdy(n);
|
|
1385
|
+
vec3 xneg = n - dx;
|
|
1386
|
+
vec3 xpos = n + dx;
|
|
1387
|
+
vec3 yneg = n - dy;
|
|
1388
|
+
vec3 ypos = n + dy;
|
|
1389
|
+
float depth = length(vPc.xyz);
|
|
1390
|
+
float curv = (cross(xneg, xpos).y - cross(yneg, ypos).x) / depth;
|
|
1391
|
+
//at this stage 0.5 for flat, with valleys dark and ridges bright
|
|
1392
|
+
curv = 1.0 - (curv + 0.5);
|
|
1393
|
+
//clamp
|
|
1394
|
+
curv = min(max(curv, 0.0), 1.0);
|
|
1395
|
+
// easing function
|
|
1396
|
+
curv = pow(curv, 0.5);
|
|
1397
|
+
//modulate ambient and diffuse with curvature
|
|
1398
|
+
vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix)
|
|
1399
|
+
float ambient = 0.6;
|
|
1400
|
+
float diffuse = 0.6;
|
|
1401
|
+
float specular = 0.2;
|
|
1402
|
+
float shininess = 10.0;
|
|
1403
|
+
vec3 lightPosition = vec3(0.0, 10.0, -2.0);
|
|
1404
|
+
vec3 l = normalize(lightPosition);
|
|
1405
|
+
float lightNormDot = dot(n, l);
|
|
1406
|
+
vec3 a = vClr.rgb * ambient * curv;
|
|
1407
|
+
vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse;
|
|
1408
|
+
float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess);
|
|
1409
|
+
color = vec4(a + d + s, opacity);
|
|
1410
|
+
}`
|
|
1371
1411
|
// Phong: default
|
|
1372
1412
|
export const fragMeshShader = `#version 300 es
|
|
1373
1413
|
precision highp int;
|