@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niivue/niivue",
3
- "version": "0.44.1",
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.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
  }
@@ -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) => {
@@ -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
- for (let i = 0; i < str.length; i++) {
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, hdr.littleEndian)
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], hdr.littleEndian)
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, hdr.littleEndian)
408
- view.setFloat32(60, hdr.intent_p2, hdr.littleEndian)
409
- view.setFloat32(64, hdr.intent_p3, hdr.littleEndian)
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, hdr.littleEndian)
418
+ view.setInt16(68, hdr.intent_code, isLittleEndian)
412
419
  if (isDrawing8) {
413
- view.setInt16(70, 2, hdr.littleEndian) // 2 = DT_UINT8
414
- view.setInt16(72, 8, hdr.littleEndian)
420
+ view.setInt16(70, 2, isLittleEndian) // 2 = DT_UINT8
421
+ view.setInt16(72, 8, isLittleEndian)
415
422
  } else {
416
- view.setInt16(70, hdr.datatypeCode, hdr.littleEndian)
417
- view.setInt16(72, hdr.numBitsPerVoxel, hdr.littleEndian)
423
+ view.setInt16(70, hdr.datatypeCode, isLittleEndian)
424
+ view.setInt16(72, hdr.numBitsPerVoxel, isLittleEndian)
418
425
  }
419
- view.setInt16(74, hdr.slice_start, hdr.littleEndian)
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], hdr.littleEndian)
430
+ view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i], isLittleEndian)
424
431
  }
425
432
  if (isDrawing8) {
426
- view.setFloat32(108, 352, hdr.littleEndian)
427
- view.setFloat32(112, 1.0, hdr.littleEndian)
428
- view.setFloat32(116, 0.0, hdr.littleEndian)
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, hdr.littleEndian)
431
- view.setFloat32(112, hdr.scl_slope, hdr.littleEndian)
432
- view.setFloat32(116, hdr.scl_inter, hdr.littleEndian)
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, hdr.littleEndian)
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
- view.setUint8(123, hdr.xyzt_units)
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, hdr.littleEndian)
444
- view.setFloat32(128, 0, hdr.littleEndian)
455
+ view.setFloat32(124, 0, isLittleEndian)
456
+ view.setFloat32(128, 0, isLittleEndian)
445
457
  } else {
446
- view.setFloat32(124, hdr.cal_max, hdr.littleEndian)
447
- view.setFloat32(128, hdr.cal_min, hdr.littleEndian)
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, hdr.littleEndian)
450
- view.setFloat32(136, hdr.toffset, hdr.littleEndian)
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, hdr.littleEndian)
461
- view.setInt16(254, hdr.sform_code, hdr.littleEndian)
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, hdr.littleEndian)
465
- view.setFloat32(260, hdr.quatern_c, hdr.littleEndian)
466
- view.setFloat32(264, hdr.quatern_d, hdr.littleEndian)
467
- view.setFloat32(268, hdr.qoffset_x, hdr.littleEndian)
468
- view.setFloat32(272, hdr.qoffset_y, hdr.littleEndian)
469
- view.setFloat32(276, hdr.qoffset_z, hdr.littleEndian)
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], hdr.littleEndian)
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
  }
@@ -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 vP;
1210
+ out vec4 vPc;
1211
1211
  void main(void) {
1212
1212
  vec3 lightPosition = vec3(0.0, 0.0, -10.0);
1213
- vP = vec4(pos, 1.0);
1214
- gl_Position = mvpMtx * vP;
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;