@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/README.md +9 -0
- package/build/index.d.ts +132 -18
- package/build/index.js +1088 -178
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +132 -18
- package/dist/index.js +26585 -30810
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +111 -32
- package/package.json +18 -17
- package/src/index.html +46 -7
- package/src/index_segment.html +165 -0
- package/src/index_sync.html +289 -0
- package/src/niivue/index.ts +857 -137
- package/src/nvdocument.ts +36 -5
- package/src/nvimage/index.ts +29 -3
- package/src/nvimage/utils.ts +61 -42
- package/src/nvlabel.ts +34 -1
- package/src/nvmesh-loaders.ts +50 -6
- package/src/nvmesh-utilities.ts +139 -0
- package/src/nvmesh.ts +96 -0
- package/src/shader-srcs.ts +84 -3
- package/src/types.ts +13 -0
package/src/nvmesh.ts
CHANGED
|
@@ -1313,6 +1313,102 @@ export class NVMesh {
|
|
|
1313
1313
|
this.updateMesh(gl) // apply the new properties...
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
|
+
hierarchicalOrder(): number {
|
|
1317
|
+
const V0 = 12
|
|
1318
|
+
const F0 = 20
|
|
1319
|
+
const nF = this.tris.length / 3
|
|
1320
|
+
const order = Math.log(nF / F0) / Math.log(4)
|
|
1321
|
+
// Sanity checks
|
|
1322
|
+
if (nF !== Math.pow(4, order) * F0) {
|
|
1323
|
+
return NaN
|
|
1324
|
+
}
|
|
1325
|
+
const nV = this.pts.length / 3
|
|
1326
|
+
if (nV !== Math.pow(4, order) * (V0 - 2) + 2) {
|
|
1327
|
+
return NaN
|
|
1328
|
+
}
|
|
1329
|
+
// next checks are in case FreeSurfer was optimized with more local face indices
|
|
1330
|
+
// for an example see BrainMesh_ICBM152.lh.mz3
|
|
1331
|
+
for (let i = 0; i < 15; i += 3) {
|
|
1332
|
+
if (this.tris[i] !== 0) {
|
|
1333
|
+
return NaN
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
for (let i = 15; i < 24; i += 3) {
|
|
1337
|
+
if (this.tris[i] !== 3) {
|
|
1338
|
+
return NaN
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
for (let i = 24; i < 30; i += 3) {
|
|
1342
|
+
if (this.tris[i] !== 4) {
|
|
1343
|
+
return NaN
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return order
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
decimateFaces(n: number, ntarget: number): void {
|
|
1350
|
+
let fac = this.tris
|
|
1351
|
+
// Constants for the icosahedron
|
|
1352
|
+
const V0 = 12
|
|
1353
|
+
const F0 = 20
|
|
1354
|
+
for (let j = n - 1; j >= ntarget; j--) {
|
|
1355
|
+
const nVjprev = Math.pow(4, j + 1) * (V0 - 2) + 2
|
|
1356
|
+
const nVj = Math.pow(4, j) * (V0 - 2) + 2
|
|
1357
|
+
const nFjprev = fac.length / 3 // = 4^(j+1)*F0
|
|
1358
|
+
const nFj = Math.pow(4, j) * F0
|
|
1359
|
+
|
|
1360
|
+
console.log(`order ${j + 1} -> ${j} vertices ${nVjprev} -> ${nVj} faces ${nFjprev} -> ${nFj}`)
|
|
1361
|
+
|
|
1362
|
+
const remap = Array.from({ length: nVjprev }, (_, i) => i + 1)
|
|
1363
|
+
|
|
1364
|
+
for (let i = 0; i < nFjprev; i++) {
|
|
1365
|
+
const v1 = fac[3 * i]
|
|
1366
|
+
const v2 = fac[3 * i + 1]
|
|
1367
|
+
const v3 = fac[3 * i + 2]
|
|
1368
|
+
remap[v1 - 1] = Math.min(remap[v1 - 1], v2, v3)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const facJ = new Uint32Array(nFj * 3)
|
|
1372
|
+
for (let i = 0; i < nFj; i++) {
|
|
1373
|
+
facJ[3 * i] = remap[fac[3 * i] - 1]
|
|
1374
|
+
facJ[3 * i + 1] = remap[fac[3 * i + 1] - 1]
|
|
1375
|
+
facJ[3 * i + 2] = remap[fac[3 * i + 2] - 1]
|
|
1376
|
+
}
|
|
1377
|
+
fac = facJ
|
|
1378
|
+
}
|
|
1379
|
+
this.tris = new Uint32Array(fac)
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// internal function simplifies FreeSurfer triangulated mesh and overlays
|
|
1383
|
+
decimateHierarchicalMesh(gl: WebGL2RenderingContext, order: number = 4): boolean {
|
|
1384
|
+
const inputOrder = this.hierarchicalOrder()
|
|
1385
|
+
if (isNaN(inputOrder)) {
|
|
1386
|
+
log.warn('Unable to decimate mesh: it does not have a hierarchical structure')
|
|
1387
|
+
return false
|
|
1388
|
+
}
|
|
1389
|
+
if (order >= inputOrder) {
|
|
1390
|
+
log.warn(`Unable to decimate mesh: input order (${inputOrder}) must be larger than downsampled order (${order})`)
|
|
1391
|
+
return false
|
|
1392
|
+
}
|
|
1393
|
+
const inputVLength = this.pts.length / 3
|
|
1394
|
+
const V0 = 12
|
|
1395
|
+
const nV = Math.pow(4, order) * (V0 - 2) + 2
|
|
1396
|
+
this.pts = new Float32Array(this.pts.slice(0, nV * 3))
|
|
1397
|
+
this.decimateFaces(inputOrder, order)
|
|
1398
|
+
if (this.layers && this.layers.length > 0) {
|
|
1399
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
1400
|
+
const layer = this.layers[i]
|
|
1401
|
+
if (layer.values instanceof Float32Array || layer.values.length !== inputVLength) {
|
|
1402
|
+
layer.values = new Float32Array(layer.values.slice(0, nV))
|
|
1403
|
+
} else {
|
|
1404
|
+
log.warn(`decimation logic needs to be updated`)
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
this.updateMesh(gl) // apply the new properties...
|
|
1409
|
+
return true
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1316
1412
|
// adjust attributes of a mesh layer. invoked by niivue.setMeshLayerProperty()
|
|
1317
1413
|
// TODO this method is a bit too generic
|
|
1318
1414
|
setLayerProperty(
|
package/src/shader-srcs.ts
CHANGED
|
@@ -719,6 +719,47 @@ void main() {
|
|
|
719
719
|
color = lineColor;
|
|
720
720
|
}`
|
|
721
721
|
|
|
722
|
+
export const fragRectOutlineShader = `#version 300 es
|
|
723
|
+
#line 723
|
|
724
|
+
precision highp int;
|
|
725
|
+
precision highp float;
|
|
726
|
+
|
|
727
|
+
uniform vec4 lineColor;
|
|
728
|
+
uniform vec4 leftTopWidthHeight;
|
|
729
|
+
uniform float thickness; // line thickness in pixels
|
|
730
|
+
uniform vec2 canvasWidthHeight;
|
|
731
|
+
|
|
732
|
+
out vec4 color;
|
|
733
|
+
|
|
734
|
+
void main() {
|
|
735
|
+
// fragment position in screen coordinates
|
|
736
|
+
vec2 fragCoord = gl_FragCoord.xy;
|
|
737
|
+
|
|
738
|
+
// canvas height
|
|
739
|
+
float canvasHeight = canvasWidthHeight.y;
|
|
740
|
+
|
|
741
|
+
// 'top' and 'bottom' to match gl_FragCoord.y coordinate system
|
|
742
|
+
float top = canvasHeight - leftTopWidthHeight.y;
|
|
743
|
+
float bottom = top - leftTopWidthHeight.w;
|
|
744
|
+
|
|
745
|
+
// left and right edges
|
|
746
|
+
float left = leftTopWidthHeight.x;
|
|
747
|
+
float right = left + leftTopWidthHeight.z;
|
|
748
|
+
|
|
749
|
+
bool withinLeft = fragCoord.x >= left && fragCoord.x <= left + thickness;
|
|
750
|
+
bool withinRight = fragCoord.x <= right && fragCoord.x >= right - thickness;
|
|
751
|
+
bool withinTop = fragCoord.y <= top && fragCoord.y >= top - thickness;
|
|
752
|
+
bool withinBottom = fragCoord.y >= bottom && fragCoord.y <= bottom + thickness;
|
|
753
|
+
|
|
754
|
+
bool isOutline = withinLeft || withinRight || withinTop || withinBottom;
|
|
755
|
+
|
|
756
|
+
if (isOutline) {
|
|
757
|
+
color = lineColor;
|
|
758
|
+
} else {
|
|
759
|
+
discard;
|
|
760
|
+
}
|
|
761
|
+
}`
|
|
762
|
+
|
|
722
763
|
export const vertColorbarShader = `#version 300 es
|
|
723
764
|
#line 490
|
|
724
765
|
layout(location=0) in vec3 pos;
|
|
@@ -1207,11 +1248,11 @@ uniform mat4 mvpMtx;
|
|
|
1207
1248
|
uniform mat4 normMtx;
|
|
1208
1249
|
out vec4 vClr;
|
|
1209
1250
|
out vec3 vN;
|
|
1210
|
-
out vec4
|
|
1251
|
+
out vec4 vPc;
|
|
1211
1252
|
void main(void) {
|
|
1212
1253
|
vec3 lightPosition = vec3(0.0, 0.0, -10.0);
|
|
1213
|
-
|
|
1214
|
-
gl_Position =
|
|
1254
|
+
vPc = mvpMtx * vec4(pos, 1.0);
|
|
1255
|
+
gl_Position = vPc;
|
|
1215
1256
|
vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz);
|
|
1216
1257
|
//vV = -vec3(modelMtx*vec4(pos,1.0));
|
|
1217
1258
|
vClr = clr;
|
|
@@ -1368,6 +1409,46 @@ void main() {
|
|
|
1368
1409
|
color = vec4(d3 + s3 + s, opacity);
|
|
1369
1410
|
}`
|
|
1370
1411
|
|
|
1412
|
+
// https://thorium.rocks/media/curves/curvaceous.html
|
|
1413
|
+
export const fragMeshShaderCrevice = `#version 300 es
|
|
1414
|
+
precision highp int;
|
|
1415
|
+
precision highp float;
|
|
1416
|
+
uniform float opacity;
|
|
1417
|
+
in vec4 vClr;
|
|
1418
|
+
in vec3 vN;
|
|
1419
|
+
in vec4 vPc;
|
|
1420
|
+
out vec4 color;
|
|
1421
|
+
void main() {
|
|
1422
|
+
vec3 n = normalize(vN);
|
|
1423
|
+
// Compute curvature
|
|
1424
|
+
vec3 dx = dFdx(n);
|
|
1425
|
+
vec3 dy = dFdy(n);
|
|
1426
|
+
vec3 xneg = n - dx;
|
|
1427
|
+
vec3 xpos = n + dx;
|
|
1428
|
+
vec3 yneg = n - dy;
|
|
1429
|
+
vec3 ypos = n + dy;
|
|
1430
|
+
float depth = length(vPc.xyz);
|
|
1431
|
+
float curv = (cross(xneg, xpos).y - cross(yneg, ypos).x) / depth;
|
|
1432
|
+
//at this stage 0.5 for flat, with valleys dark and ridges bright
|
|
1433
|
+
curv = 1.0 - (curv + 0.5);
|
|
1434
|
+
//clamp
|
|
1435
|
+
curv = min(max(curv, 0.0), 1.0);
|
|
1436
|
+
// easing function
|
|
1437
|
+
curv = pow(curv, 0.5);
|
|
1438
|
+
//modulate ambient and diffuse with curvature
|
|
1439
|
+
vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix)
|
|
1440
|
+
float ambient = 0.6;
|
|
1441
|
+
float diffuse = 0.6;
|
|
1442
|
+
float specular = 0.2;
|
|
1443
|
+
float shininess = 10.0;
|
|
1444
|
+
vec3 lightPosition = vec3(0.0, 10.0, -2.0);
|
|
1445
|
+
vec3 l = normalize(lightPosition);
|
|
1446
|
+
float lightNormDot = dot(n, l);
|
|
1447
|
+
vec3 a = vClr.rgb * ambient * curv;
|
|
1448
|
+
vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse;
|
|
1449
|
+
float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess);
|
|
1450
|
+
color = vec4(a + d + s, opacity);
|
|
1451
|
+
}`
|
|
1371
1452
|
// Phong: default
|
|
1372
1453
|
export const fragMeshShader = `#version 300 es
|
|
1373
1454
|
precision highp int;
|
package/src/types.ts
CHANGED
|
@@ -152,3 +152,16 @@ export type NiiVueLocation = {
|
|
|
152
152
|
vox: vec3
|
|
153
153
|
xy: [number, number]
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
export type SyncOpts = {
|
|
157
|
+
'3d'?: boolean
|
|
158
|
+
'2d'?: boolean
|
|
159
|
+
zoomPan?: boolean
|
|
160
|
+
cal_min?: boolean
|
|
161
|
+
cal_max?: boolean
|
|
162
|
+
gamma?: boolean
|
|
163
|
+
useSliceOffset?: boolean
|
|
164
|
+
sliceType?: boolean
|
|
165
|
+
crosshair?: boolean
|
|
166
|
+
clipPlane?: boolean
|
|
167
|
+
}
|