@niivue/niivue 0.54.0 → 0.56.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 +57 -0
- package/build/index.js +140 -7
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/build/metafile-esm.json +1 -1
- package/dist/index.d.ts +57 -0
- package/dist/index.js +140 -7
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/niivue.umd.js +19 -19
- package/package.json +1 -1
- package/src/index.html +38 -3
- package/src/niivue/index.ts +198 -6
- package/src/niivue/types.ts +0 -0
- package/src/nvdocument.ts +2 -0
package/build/index.d.ts
CHANGED
|
@@ -723,6 +723,7 @@ type NVConfigOptions = {
|
|
|
723
723
|
yoke3Dto2DZoom: boolean;
|
|
724
724
|
isDepthPickMesh: boolean;
|
|
725
725
|
isCornerOrientationText: boolean;
|
|
726
|
+
isOrientationTextVisible: boolean;
|
|
726
727
|
heroImageFraction: number;
|
|
727
728
|
heroSliceType: SLICE_TYPE;
|
|
728
729
|
sagittalNoseLeft: boolean;
|
|
@@ -1701,6 +1702,11 @@ declare class Niivue {
|
|
|
1701
1702
|
VOLUME_ID: number;
|
|
1702
1703
|
DISTANCE_FROM_CAMERA: number;
|
|
1703
1704
|
graph: Graph;
|
|
1705
|
+
customLayout: Array<{
|
|
1706
|
+
sliceType: SLICE_TYPE;
|
|
1707
|
+
position: [number, number, number, number];
|
|
1708
|
+
sliceMM?: number;
|
|
1709
|
+
}>;
|
|
1704
1710
|
meshShaders: Array<{
|
|
1705
1711
|
Name: string;
|
|
1706
1712
|
Frag: string;
|
|
@@ -2113,6 +2119,12 @@ declare class Niivue {
|
|
|
2113
2119
|
* @see {@link https://niivue.github.io/niivue/features/worldspace2.html | live demo usage}
|
|
2114
2120
|
*/
|
|
2115
2121
|
setCornerOrientationText(isCornerOrientationText: boolean): void;
|
|
2122
|
+
/**
|
|
2123
|
+
* determine if orientation text appears in 2D slice view.
|
|
2124
|
+
* @param isOrientationTextVisible - controls position of text
|
|
2125
|
+
* @example niivue.setIsOrientationTextVisible(false)
|
|
2126
|
+
*/
|
|
2127
|
+
setIsOrientationTextVisible(isOrientationTextVisible: boolean): void;
|
|
2116
2128
|
/**
|
|
2117
2129
|
* determine proportion of screen real estate devoted to rendering in multiplanar view.
|
|
2118
2130
|
* @param fraction - proportion of screen devoted to primary (hero) image (0 to disable)
|
|
@@ -2120,6 +2132,50 @@ declare class Niivue {
|
|
|
2120
2132
|
* @see {@link https://niivue.github.io/niivue/features/layout.html | live demo usage}
|
|
2121
2133
|
*/
|
|
2122
2134
|
setHeroImage(fraction: number): void;
|
|
2135
|
+
/**
|
|
2136
|
+
* Set a custom slice layout. This overrides the built-in layouts.
|
|
2137
|
+
* @param layout - Array of layout specifications for each slice view
|
|
2138
|
+
* @example
|
|
2139
|
+
* niivue.setCustomLayout([
|
|
2140
|
+
* // Left 50% - Sag
|
|
2141
|
+
* {sliceType: 2, position: [0, 0, 0.5, 1.0]},
|
|
2142
|
+
* // Top right - Cor
|
|
2143
|
+
* {sliceType: 1, position: [0.5, 0, 0.5, 0.5]},
|
|
2144
|
+
* // Bottom right - Ax
|
|
2145
|
+
* {sliceType: 0, position: [0.5, 0.5, 0.5, 0.5]}
|
|
2146
|
+
* ])
|
|
2147
|
+
*
|
|
2148
|
+
* produces:
|
|
2149
|
+
* +----------------+----------------+
|
|
2150
|
+
* | | |
|
|
2151
|
+
* | | coronal |
|
|
2152
|
+
* | | |
|
|
2153
|
+
* | | |
|
|
2154
|
+
* | sagittal +----------------+
|
|
2155
|
+
* | | |
|
|
2156
|
+
* | | axial |
|
|
2157
|
+
* | | |
|
|
2158
|
+
* | | |
|
|
2159
|
+
* +----------------+----------------+
|
|
2160
|
+
*/
|
|
2161
|
+
setCustomLayout(layout: Array<{
|
|
2162
|
+
sliceType: SLICE_TYPE;
|
|
2163
|
+
position: [number, number, number, number];
|
|
2164
|
+
sliceMM?: number;
|
|
2165
|
+
}>): void;
|
|
2166
|
+
/**
|
|
2167
|
+
* Clear custom layout and rely on built-in layouts
|
|
2168
|
+
*/
|
|
2169
|
+
clearCustomLayout(): void;
|
|
2170
|
+
/**
|
|
2171
|
+
* Get the current custom layout if set
|
|
2172
|
+
* @returns The current custom layout or null if using built-in layouts
|
|
2173
|
+
*/
|
|
2174
|
+
getCustomLayout(): Array<{
|
|
2175
|
+
sliceType: SLICE_TYPE;
|
|
2176
|
+
position: [number, number, number, number];
|
|
2177
|
+
sliceMM?: number;
|
|
2178
|
+
}> | null;
|
|
2123
2179
|
/**
|
|
2124
2180
|
* control whether 2D slices use radiological or neurological convention.
|
|
2125
2181
|
* @param isRadiologicalConvention - new display convention
|
|
@@ -3062,6 +3118,7 @@ declare class Niivue {
|
|
|
3062
3118
|
* @see {@link https://niivue.github.io/niivue/features/mosaics.html | live demo usage}
|
|
3063
3119
|
*/
|
|
3064
3120
|
drawMosaic(mosaicStr: string): void;
|
|
3121
|
+
calculateWidthHeight(sliceType: number, volScale: number[], containerWidth: number, containerHeight: number): [number, number];
|
|
3065
3122
|
drawSceneCore(): string | void;
|
|
3066
3123
|
drawScene(): string | void;
|
|
3067
3124
|
get gl(): WebGL2RenderingContext;
|
package/build/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
17
17
|
import { mat4 as mat49, vec2, vec3 as vec314, vec4 as vec49 } from "gl-matrix";
|
|
18
18
|
|
|
19
19
|
// package.json
|
|
20
|
-
var version = "0.
|
|
20
|
+
var version = "0.56.0";
|
|
21
21
|
|
|
22
22
|
// src/logger.ts
|
|
23
23
|
var _Log = class _Log {
|
|
@@ -20431,6 +20431,7 @@ var DEFAULT_OPTIONS = {
|
|
|
20431
20431
|
yoke3Dto2DZoom: false,
|
|
20432
20432
|
isDepthPickMesh: false,
|
|
20433
20433
|
isCornerOrientationText: false,
|
|
20434
|
+
isOrientationTextVisible: true,
|
|
20434
20435
|
heroImageFraction: 0,
|
|
20435
20436
|
heroSliceType: 4 /* RENDER */,
|
|
20436
20437
|
sagittalNoseLeft: false,
|
|
@@ -25165,6 +25166,7 @@ var Niivue = class {
|
|
|
25165
25166
|
normalizeValues: false,
|
|
25166
25167
|
isRangeCalMinMax: false
|
|
25167
25168
|
});
|
|
25169
|
+
__publicField(this, "customLayout", []);
|
|
25168
25170
|
__publicField(this, "meshShaders", [
|
|
25169
25171
|
{
|
|
25170
25172
|
Name: "Phong",
|
|
@@ -27100,6 +27102,15 @@ var Niivue = class {
|
|
|
27100
27102
|
this.opts.isCornerOrientationText = isCornerOrientationText;
|
|
27101
27103
|
this.updateGLVolume();
|
|
27102
27104
|
}
|
|
27105
|
+
/**
|
|
27106
|
+
* determine if orientation text appears in 2D slice view.
|
|
27107
|
+
* @param isOrientationTextVisible - controls position of text
|
|
27108
|
+
* @example niivue.setIsOrientationTextVisible(false)
|
|
27109
|
+
*/
|
|
27110
|
+
setIsOrientationTextVisible(isOrientationTextVisible) {
|
|
27111
|
+
this.opts.isOrientationTextVisible = isOrientationTextVisible;
|
|
27112
|
+
this.drawScene();
|
|
27113
|
+
}
|
|
27103
27114
|
/**
|
|
27104
27115
|
* determine proportion of screen real estate devoted to rendering in multiplanar view.
|
|
27105
27116
|
* @param fraction - proportion of screen devoted to primary (hero) image (0 to disable)
|
|
@@ -27110,6 +27121,65 @@ var Niivue = class {
|
|
|
27110
27121
|
this.opts.heroImageFraction = fraction;
|
|
27111
27122
|
this.drawScene();
|
|
27112
27123
|
}
|
|
27124
|
+
/**
|
|
27125
|
+
* Set a custom slice layout. This overrides the built-in layouts.
|
|
27126
|
+
* @param layout - Array of layout specifications for each slice view
|
|
27127
|
+
* @example
|
|
27128
|
+
* niivue.setCustomLayout([
|
|
27129
|
+
* // Left 50% - Sag
|
|
27130
|
+
* {sliceType: 2, position: [0, 0, 0.5, 1.0]},
|
|
27131
|
+
* // Top right - Cor
|
|
27132
|
+
* {sliceType: 1, position: [0.5, 0, 0.5, 0.5]},
|
|
27133
|
+
* // Bottom right - Ax
|
|
27134
|
+
* {sliceType: 0, position: [0.5, 0.5, 0.5, 0.5]}
|
|
27135
|
+
* ])
|
|
27136
|
+
*
|
|
27137
|
+
* produces:
|
|
27138
|
+
* +----------------+----------------+
|
|
27139
|
+
* | | |
|
|
27140
|
+
* | | coronal |
|
|
27141
|
+
* | | |
|
|
27142
|
+
* | | |
|
|
27143
|
+
* | sagittal +----------------+
|
|
27144
|
+
* | | |
|
|
27145
|
+
* | | axial |
|
|
27146
|
+
* | | |
|
|
27147
|
+
* | | |
|
|
27148
|
+
* +----------------+----------------+
|
|
27149
|
+
*/
|
|
27150
|
+
setCustomLayout(layout) {
|
|
27151
|
+
for (let i = 0; i < layout.length; i++) {
|
|
27152
|
+
const [left1, top1, width1, height1] = layout[i].position;
|
|
27153
|
+
const right1 = left1 + width1;
|
|
27154
|
+
const bottom1 = top1 + height1;
|
|
27155
|
+
for (let j = i + 1; j < layout.length; j++) {
|
|
27156
|
+
const [left2, top2, width2, height2] = layout[j].position;
|
|
27157
|
+
const right2 = left2 + width2;
|
|
27158
|
+
const bottom2 = top2 + height2;
|
|
27159
|
+
const horizontallyOverlaps = left1 < right2 && right1 > left2;
|
|
27160
|
+
const verticallyOverlaps = top1 < bottom2 && bottom1 > top2;
|
|
27161
|
+
if (horizontallyOverlaps && verticallyOverlaps) {
|
|
27162
|
+
throw new Error(`Custom layout is invalid. Tile ${i} overlaps with tile ${j}.`);
|
|
27163
|
+
}
|
|
27164
|
+
}
|
|
27165
|
+
}
|
|
27166
|
+
this.customLayout = layout;
|
|
27167
|
+
this.drawScene();
|
|
27168
|
+
}
|
|
27169
|
+
/**
|
|
27170
|
+
* Clear custom layout and rely on built-in layouts
|
|
27171
|
+
*/
|
|
27172
|
+
clearCustomLayout() {
|
|
27173
|
+
this.customLayout = null;
|
|
27174
|
+
this.drawScene();
|
|
27175
|
+
}
|
|
27176
|
+
/**
|
|
27177
|
+
* Get the current custom layout if set
|
|
27178
|
+
* @returns The current custom layout or null if using built-in layouts
|
|
27179
|
+
*/
|
|
27180
|
+
getCustomLayout() {
|
|
27181
|
+
return this.customLayout;
|
|
27182
|
+
}
|
|
27113
27183
|
/**
|
|
27114
27184
|
* control whether 2D slices use radiological or neurological convention.
|
|
27115
27185
|
* @param isRadiologicalConvention - new display convention
|
|
@@ -33481,7 +33551,7 @@ var Niivue = class {
|
|
|
33481
33551
|
if (isNaN(customMM)) {
|
|
33482
33552
|
this.drawCrosshairs3D(false, 0.15, obj.modelViewProjectionMatrix, true, this.opts.isSliceMM);
|
|
33483
33553
|
}
|
|
33484
|
-
if (isStretchToScreen) {
|
|
33554
|
+
if (isStretchToScreen && this.opts.isOrientationTextVisible) {
|
|
33485
33555
|
this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
|
|
33486
33556
|
}
|
|
33487
33557
|
this.readyForSync = true;
|
|
@@ -33515,7 +33585,7 @@ var Niivue = class {
|
|
|
33515
33585
|
if (customMM === Infinity || customMM === -Infinity || axCorSag === 4 /* RENDER */) {
|
|
33516
33586
|
return;
|
|
33517
33587
|
}
|
|
33518
|
-
if (leftTopWidthHeight[2] !== 0 && leftTopWidthHeight[3] !== 0) {
|
|
33588
|
+
if (leftTopWidthHeight[2] !== 0 && leftTopWidthHeight[3] !== 0 && this.opts.isOrientationTextVisible) {
|
|
33519
33589
|
this.drawSliceOrientationText(leftTopWidthHeight, axCorSag, padLeftTop);
|
|
33520
33590
|
}
|
|
33521
33591
|
}
|
|
@@ -35173,6 +35243,36 @@ var Niivue = class {
|
|
|
35173
35243
|
}
|
|
35174
35244
|
this.opts.textHeight = labelSize;
|
|
35175
35245
|
}
|
|
35246
|
+
calculateWidthHeight(sliceType, volScale, containerWidth, containerHeight) {
|
|
35247
|
+
let xScale, yScale;
|
|
35248
|
+
switch (sliceType) {
|
|
35249
|
+
case 0 /* AXIAL */:
|
|
35250
|
+
xScale = volScale[0];
|
|
35251
|
+
yScale = volScale[1];
|
|
35252
|
+
break;
|
|
35253
|
+
case 1 /* CORONAL */:
|
|
35254
|
+
xScale = volScale[0];
|
|
35255
|
+
yScale = volScale[2];
|
|
35256
|
+
break;
|
|
35257
|
+
case 2 /* SAGITTAL */:
|
|
35258
|
+
xScale = volScale[1];
|
|
35259
|
+
yScale = volScale[2];
|
|
35260
|
+
break;
|
|
35261
|
+
default:
|
|
35262
|
+
return [containerWidth, containerHeight];
|
|
35263
|
+
}
|
|
35264
|
+
const aspectRatio = xScale / yScale;
|
|
35265
|
+
const containerAspect = containerWidth / containerHeight;
|
|
35266
|
+
let actualWidth, actualHeight;
|
|
35267
|
+
if (aspectRatio > containerAspect) {
|
|
35268
|
+
actualWidth = containerWidth;
|
|
35269
|
+
actualHeight = containerWidth / aspectRatio;
|
|
35270
|
+
} else {
|
|
35271
|
+
actualHeight = containerHeight;
|
|
35272
|
+
actualWidth = containerHeight * aspectRatio;
|
|
35273
|
+
}
|
|
35274
|
+
return [actualWidth, actualHeight];
|
|
35275
|
+
}
|
|
35176
35276
|
// not included in public docs
|
|
35177
35277
|
drawSceneCore() {
|
|
35178
35278
|
if (!this.initialized) {
|
|
@@ -35198,9 +35298,6 @@ var Niivue = class {
|
|
|
35198
35298
|
this.drawLoadingText(this.opts.loadingText);
|
|
35199
35299
|
return;
|
|
35200
35300
|
}
|
|
35201
|
-
if (this.back === null) {
|
|
35202
|
-
return;
|
|
35203
|
-
}
|
|
35204
35301
|
if (this.uiData.isDragging && this.scene.clipPlaneDepthAziElev[0] < 1.8 && this.inRenderTile(this.uiData.dragStart[0], this.uiData.dragStart[1]) >= 0) {
|
|
35205
35302
|
const x = this.uiData.dragStart[0] - this.uiData.dragEnd[0];
|
|
35206
35303
|
const y = this.uiData.dragStart[1] - this.uiData.dragEnd[1];
|
|
@@ -35236,7 +35333,43 @@ var Niivue = class {
|
|
|
35236
35333
|
let isHeroImage = false;
|
|
35237
35334
|
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
35238
35335
|
this.screenSlices = [];
|
|
35239
|
-
if (this.
|
|
35336
|
+
if (this.customLayout && this.customLayout.length > 0) {
|
|
35337
|
+
this.screenSlices = [];
|
|
35338
|
+
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
35339
|
+
const { volScale } = this.sliceScale();
|
|
35340
|
+
const canvasWH = [this.effectiveCanvasWidth(), this.effectiveCanvasHeight()];
|
|
35341
|
+
for (const view of this.customLayout) {
|
|
35342
|
+
const { sliceType, position, sliceMM } = view;
|
|
35343
|
+
const leftTopWidthHeight = position.slice();
|
|
35344
|
+
if (position[0] >= 0 && position[0] <= 1 && position[2] <= 1) {
|
|
35345
|
+
leftTopWidthHeight[0] = position[0] * canvasWH[0];
|
|
35346
|
+
leftTopWidthHeight[2] = position[2] * canvasWH[0];
|
|
35347
|
+
}
|
|
35348
|
+
if (position[1] >= 0 && position[1] <= 1 && position[3] <= 1) {
|
|
35349
|
+
leftTopWidthHeight[1] = position[1] * canvasWH[1];
|
|
35350
|
+
leftTopWidthHeight[3] = position[3] * canvasWH[1];
|
|
35351
|
+
}
|
|
35352
|
+
if (leftTopWidthHeight[0] + leftTopWidthHeight[2] > canvasWH[0]) {
|
|
35353
|
+
log.warn("adjusting slice width because it would have been clipped");
|
|
35354
|
+
leftTopWidthHeight[2] = canvasWH[0] - leftTopWidthHeight[0];
|
|
35355
|
+
}
|
|
35356
|
+
if (leftTopWidthHeight[1] + leftTopWidthHeight[3] > canvasWH[1]) {
|
|
35357
|
+
log.warn("adjusting slice height because it would have been clipped");
|
|
35358
|
+
leftTopWidthHeight[3] = canvasWH[1] - leftTopWidthHeight[1];
|
|
35359
|
+
}
|
|
35360
|
+
if (sliceType === 4 /* RENDER */) {
|
|
35361
|
+
this.draw3D(leftTopWidthHeight);
|
|
35362
|
+
} else if (sliceType === 0 /* AXIAL */ || sliceType === 1 /* CORONAL */ || sliceType === 2 /* SAGITTAL */) {
|
|
35363
|
+
const actualDimensions = this.calculateWidthHeight(
|
|
35364
|
+
sliceType,
|
|
35365
|
+
volScale,
|
|
35366
|
+
leftTopWidthHeight[2],
|
|
35367
|
+
leftTopWidthHeight[3]
|
|
35368
|
+
);
|
|
35369
|
+
this.draw2D(leftTopWidthHeight, sliceType, sliceMM ?? NaN, actualDimensions);
|
|
35370
|
+
}
|
|
35371
|
+
}
|
|
35372
|
+
} else if (this.opts.sliceType === 0 /* AXIAL */) {
|
|
35240
35373
|
this.draw2D([0, 0, 0, 0], 0);
|
|
35241
35374
|
} else if (this.opts.sliceType === 1 /* CORONAL */) {
|
|
35242
35375
|
this.draw2D([0, 0, 0, 0], 1);
|