@aics/vole-core 3.15.2 → 3.15.4
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/es/FusedChannelData.js +5 -1
- package/es/Line3d.js +167 -0
- package/es/ThreeJsPanel.js +12 -2
- package/es/View3d.js +53 -1
- package/es/Volume.js +21 -4
- package/es/VolumeDrawable.js +74 -11
- package/es/index.js +2 -1
- package/es/types/Line3d.d.ts +70 -0
- package/es/types/MeshVolume.d.ts +2 -1
- package/es/types/ThreeJsPanel.d.ts +3 -0
- package/es/types/View3d.d.ts +29 -0
- package/es/types/Volume.d.ts +21 -4
- package/es/types/VolumeDrawable.d.ts +24 -0
- package/es/types/index.d.ts +2 -1
- package/es/types/types.d.ts +22 -1
- package/package.json +2 -2
package/es/FusedChannelData.js
CHANGED
|
@@ -7,7 +7,7 @@ const fuseShaderSrcF = "precision highp float;\nprecision highp int;\nprecision
|
|
|
7
7
|
/* babel-plugin-inline-import './constants/shaders/fuseI.frag' */
|
|
8
8
|
const fuseShaderSrcI = "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\nprecision highp sampler3D;\n\n// the lut texture is a 256x1 rgba texture for each channel\nuniform sampler2D lutSampler;\n\nuniform vec2 lutMinMax;\n\n// src texture is the raw volume intensity data\nuniform isampler2D srcTexture;\n\nvoid main()\n{\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n int intensity = texelFetch(srcTexture, vUv, 0).r;\n float ilookup = float(float(intensity) - lutMinMax.x) / float(lutMinMax.y - lutMinMax.x);\n // apply lut to intensity:\n vec4 pix = texture(lutSampler, vec2(ilookup, 0.5));\n gl_FragColor = vec4(pix.xyz*pix.w, pix.w);\n}\n";
|
|
9
9
|
/* babel-plugin-inline-import './constants/shaders/colorizeUI.frag' */
|
|
10
|
-
const colorizeSrcUI = "precision highp float;\nprecision highp int;\nprecision highp usampler2D;\nprecision highp sampler3D;\n\nuniform sampler2D featureData;\n/** Min and max feature values that define the endpoints of the color map. Values\n * outside the range will be clamped to the nearest endpoint.\n */\nuniform float featureColorRampMin;\nuniform float featureColorRampMax;\nuniform sampler2D colorRamp;\nuniform usampler2D inRangeIds;\nuniform usampler2D outlierData;\n\n/**\n * LUT mapping from the segmentation ID (raw pixel value) to the\n * global ID (index in data buffers like `featureData` and `outlierData`).\n * \n * For a given segmentation ID `segId`, the global ID is given by:\n * `segIdToGlobalId[segId - segIdOffset] - 1`.\n*/\nuniform usampler2D segIdToGlobalId;\nuniform uint segIdOffset;\n\nuniform vec3 outlineColor;\n\n/** MUST be synchronized with the DrawMode enum in ColorizeCanvas! */\nconst uint DRAW_MODE_HIDE = 0u;\nconst uint DRAW_MODE_COLOR = 1u;\nconst uint BACKGROUND_ID = 0u;\nconst uint MISSING_DATA_ID = 0xFFFFFFFFu;\n\nuniform vec3 outlierColor;\nuniform uint outlierDrawMode;\nuniform vec3 outOfRangeColor;\nuniform uint outOfRangeDrawMode;\n\nuniform uint highlightedId;\n\nuniform bool
|
|
10
|
+
const colorizeSrcUI = "precision highp float;\nprecision highp int;\nprecision highp usampler2D;\nprecision highp sampler3D;\n\nuniform sampler2D featureData;\n/** Min and max feature values that define the endpoints of the color map. Values\n * outside the range will be clamped to the nearest endpoint.\n */\nuniform float featureColorRampMin;\nuniform float featureColorRampMax;\nuniform sampler2D colorRamp;\nuniform usampler2D inRangeIds;\nuniform usampler2D outlierData;\n\n/**\n * LUT mapping from the segmentation ID (raw pixel value) to the\n * global ID (index in data buffers like `featureData` and `outlierData`).\n * \n * For a given segmentation ID `segId`, the global ID is given by:\n * `segIdToGlobalId[segId - segIdOffset] - 1`.\n*/\nuniform usampler2D segIdToGlobalId;\nuniform uint segIdOffset;\n\nuniform vec3 outlineColor;\n\n/** MUST be synchronized with the DrawMode enum in ColorizeCanvas! */\nconst uint DRAW_MODE_HIDE = 0u;\nconst uint DRAW_MODE_COLOR = 1u;\nconst uint BACKGROUND_ID = 0u;\nconst uint MISSING_DATA_ID = 0xFFFFFFFFu;\n\nuniform vec3 outlierColor;\nuniform uint outlierDrawMode;\nuniform vec3 outOfRangeColor;\nuniform uint outOfRangeDrawMode;\n\nuniform uint highlightedId;\n\nuniform bool useRepeatingCategoricalColors;\n\n// src texture is the raw volume intensity data\nuniform usampler2D srcTexture;\n\nvec4 getFloatFromTex(sampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return texelFetch(tex, featurePos, 0);\n}\nuvec4 getUintFromTex(usampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return texelFetch(tex, featurePos, 0);\n}\nuint getId(ivec2 uv) {\n uint rawId = texelFetch(srcTexture, uv, 0).r;\n if (rawId == 0u) {\n return BACKGROUND_ID;\n }\n uvec4 c = getUintFromTex(segIdToGlobalId, int(rawId - segIdOffset));\n // Note: IDs are offset by `1` to reserve `0` for segmentations that don't\n // have associated data. `1` MUST be subtracted from the ID when accessing\n // data buffers.\n uint globalId = c.r;\n if (globalId == 0u) {\n return MISSING_DATA_ID;\n }\n return globalId;\n}\n\nvec4 getColorRamp(float val) {\n float width = float(textureSize(colorRamp, 0).x);\n float range = (width - 1.0) / width;\n float adjustedVal = (0.5 / width) + (val * range);\n return texture(colorRamp, vec2(adjustedVal, 0.5));\n}\n\nvec4 getCategoricalColor(float featureValue) {\n float width = float(textureSize(colorRamp, 0).x);\n float modValue = mod(featureValue, width);\n // The categorical texture uses no interpolation, so when sampling, `modValue`\n // is rounded to the nearest integer.\n return getColorRamp(modValue / (width - 1.0));\n}\n\nvec4 getColorFromDrawMode(uint drawMode, vec3 defaultColor) {\n const uint DRAW_MODE_HIDE = 0u;\n vec3 backgroundColor = vec3(0.0, 0.0, 0.0);\n if (drawMode == DRAW_MODE_HIDE) {\n return vec4(backgroundColor, 0.0);\n } else {\n return vec4(defaultColor, 1.0);\n }\n}\n\nfloat getFeatureVal(uint id) {\n // Data buffer starts at 0, non-background segmentation IDs start at 1\n return getFloatFromTex(featureData, int(id) - 1).r;\n}\nuint getOutlierVal(uint id) {\n // Data buffer starts at 0, non-background segmentation IDs start at 1\n return getUintFromTex(outlierData, int(id) - 1).r;\n}\nbool getIsInRange(uint id) {\n return getUintFromTex(inRangeIds, int(id) - 1).r == 1u;\n}\nbool getIsOutlier(float featureVal, uint outlierVal) {\n return isinf(featureVal) || outlierVal != 0u;\n}\n\nvec4 getObjectColor(ivec2 sUv, float opacity) {\n // Get the segmentation id at this pixel\n uint id = getId(sUv);\n\n // A segmentation id of 0 represents background\n if (id == BACKGROUND_ID) {\n return vec4(0, 0, 0, 0);\n }\n\n // color the highlighted object. Note, `highlightedId` is a 0-based index\n // (global ID w/o offset), while `id` is a 1-based index.\n if (id - 1u == highlightedId) {\n return vec4(outlineColor, 1.0);\n }\n\n float featureVal = getFeatureVal(id);\n uint outlierVal = getOutlierVal(id);\n float normFeatureVal = (featureVal - featureColorRampMin) / (featureColorRampMax - featureColorRampMin);\n\n // Use the selected draw mode to handle out of range and outlier values;\n // otherwise color with the color ramp as usual.\n bool isInRange = getIsInRange(id);\n bool isOutlier = getIsOutlier(featureVal, outlierVal);\n bool isMissingData = (id == MISSING_DATA_ID);\n\n // Features outside the filtered/thresholded range will all be treated the same (use `outOfRangeDrawColor`).\n // Features inside the range can either be outliers or standard values, and are colored accordingly.\n vec4 color;\n if (isMissingData) { \n // TODO: Add color controls for missing data\n color = getColorFromDrawMode(outlierDrawMode, outlierColor);\n } else if (isInRange) {\n if (isOutlier) {\n color = getColorFromDrawMode(outlierDrawMode, outlierColor);\n } else if (useRepeatingCategoricalColors) {\n color = getCategoricalColor(featureVal);\n } else {\n color = getColorRamp(normFeatureVal);\n }\n } else {\n color = getColorFromDrawMode(outOfRangeDrawMode, outOfRangeColor);\n }\n color.a *= opacity;\n return color;\n}\n\nvoid main() {\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n gl_FragColor = getObjectColor(vUv, 1.0);\n}\n";
|
|
11
11
|
// This is the owner of the fused RGBA volume texture atlas, and the mask texture atlas.
|
|
12
12
|
// This module is responsible for updating the fused texture, given the read-only volume channel data.
|
|
13
13
|
export default class FusedChannelData {
|
|
@@ -107,6 +107,9 @@ export default class FusedChannelData {
|
|
|
107
107
|
colorRamp: {
|
|
108
108
|
value: null
|
|
109
109
|
},
|
|
110
|
+
useRepeatingCategoricalColors: {
|
|
111
|
+
value: false
|
|
112
|
+
},
|
|
110
113
|
outlineColor: {
|
|
111
114
|
value: new Color(0xffffff)
|
|
112
115
|
},
|
|
@@ -219,6 +222,7 @@ export default class FusedChannelData {
|
|
|
219
222
|
mat.uniforms.featureColorRampMin.value = feature.featureMin;
|
|
220
223
|
mat.uniforms.featureColorRampMax.value = feature.featureMax;
|
|
221
224
|
mat.uniforms.colorRamp.value = feature.featureValueToColor;
|
|
225
|
+
mat.uniforms.useRepeatingCategoricalColors.value = feature.useRepeatingColor;
|
|
222
226
|
mat.uniforms.outlineColor.value = feature.outlineColor;
|
|
223
227
|
mat.uniforms.outlierColor.value = feature.outlierColor;
|
|
224
228
|
mat.uniforms.outOfRangeColor.value = feature.outOfRangeColor;
|
package/es/Line3d.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Group, Vector3 } from "three";
|
|
2
|
+
import { LineMaterial } from "three/addons/lines/LineMaterial";
|
|
3
|
+
import { MESH_LAYER, OVERLAY_LAYER } from "./ThreeJsPanel";
|
|
4
|
+
import { LineSegments2 } from "three/addons/lines/LineSegments2";
|
|
5
|
+
import { LineSegmentsGeometry } from "three/addons/lines/LineSegmentsGeometry";
|
|
6
|
+
const DEFAULT_VERTEX_BUFFER_SIZE = 1020;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
10
|
+
* color, width, and segments visible.
|
|
11
|
+
*/
|
|
12
|
+
export default class Line3d {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.bufferSize = DEFAULT_VERTEX_BUFFER_SIZE;
|
|
15
|
+
const geometry = new LineSegmentsGeometry();
|
|
16
|
+
geometry.setPositions(new Float32Array(this.bufferSize));
|
|
17
|
+
const material = new LineMaterial({
|
|
18
|
+
color: "#f00",
|
|
19
|
+
linewidth: 2,
|
|
20
|
+
worldUnits: false
|
|
21
|
+
});
|
|
22
|
+
this.lineMesh = new LineSegments2(geometry, material);
|
|
23
|
+
this.lineMesh.layers.set(MESH_LAYER);
|
|
24
|
+
this.lineMesh.frustumCulled = false;
|
|
25
|
+
this.meshPivot = new Group();
|
|
26
|
+
this.meshPivot.add(this.lineMesh);
|
|
27
|
+
this.meshPivot.layers.set(MESH_LAYER);
|
|
28
|
+
this.scale = new Vector3(1, 1, 1);
|
|
29
|
+
this.flipAxes = new Vector3(1, 1, 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// IDrawableObject interface methods
|
|
33
|
+
|
|
34
|
+
cleanup() {
|
|
35
|
+
this.lineMesh.geometry.dispose();
|
|
36
|
+
this.lineMesh.material.dispose();
|
|
37
|
+
}
|
|
38
|
+
setVisible(visible) {
|
|
39
|
+
this.lineMesh.visible = visible;
|
|
40
|
+
}
|
|
41
|
+
doRender() {
|
|
42
|
+
// no op
|
|
43
|
+
}
|
|
44
|
+
get3dObject() {
|
|
45
|
+
return this.meshPivot;
|
|
46
|
+
}
|
|
47
|
+
setTranslation(translation) {
|
|
48
|
+
this.meshPivot.position.copy(translation);
|
|
49
|
+
}
|
|
50
|
+
setScale(scale) {
|
|
51
|
+
this.scale.copy(scale);
|
|
52
|
+
this.meshPivot.scale.copy(scale).multiply(this.flipAxes);
|
|
53
|
+
}
|
|
54
|
+
setRotation(eulerXYZ) {
|
|
55
|
+
this.meshPivot.rotation.copy(eulerXYZ);
|
|
56
|
+
}
|
|
57
|
+
setFlipAxes(flipX, flipY, flipZ) {
|
|
58
|
+
this.flipAxes.set(flipX, flipY, flipZ);
|
|
59
|
+
this.meshPivot.scale.copy(this.scale).multiply(this.flipAxes);
|
|
60
|
+
}
|
|
61
|
+
setOrthoThickness(_thickness) {
|
|
62
|
+
// no op
|
|
63
|
+
}
|
|
64
|
+
setResolution(_x, _y) {
|
|
65
|
+
// no op
|
|
66
|
+
}
|
|
67
|
+
setAxisClip(_axis, _minval, _maxval, _isOrthoAxis) {
|
|
68
|
+
// no op
|
|
69
|
+
}
|
|
70
|
+
updateClipRegion(_xmin, _xmax, _ymin, _ymax, _zmin, _zmax) {
|
|
71
|
+
// no op
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Line-specific functions
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sets the color of the line material.
|
|
78
|
+
* @param color Base line color.
|
|
79
|
+
* @param useVertexColors If true, _the line will multiply the base color with
|
|
80
|
+
* the per-vertex colors defined in the geometry (see `setLineVertexData`). Default is false.
|
|
81
|
+
*/
|
|
82
|
+
setColor(color, useVertexColors = false) {
|
|
83
|
+
this.lineMesh.material.color.set(color);
|
|
84
|
+
this.lineMesh.material.vertexColors = useVertexColors;
|
|
85
|
+
this.lineMesh.material.needsUpdate = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Sets the opacity of the line material.
|
|
90
|
+
*
|
|
91
|
+
* Note that transparent lines may have unexpected colors when intersecting
|
|
92
|
+
* with or overlapping the volume. Consider setting them to render as an
|
|
93
|
+
* overlay instead using `setOverlay()`.
|
|
94
|
+
* @param opacity Opacity value in the range `[0, 1]`. 0 is fully transparent,
|
|
95
|
+
* 1 is fully opaque.
|
|
96
|
+
*/
|
|
97
|
+
setOpacity(opacity) {
|
|
98
|
+
const isTransparent = opacity < 1.0;
|
|
99
|
+
this.lineMesh.material.opacity = opacity;
|
|
100
|
+
this.lineMesh.material.transparent = isTransparent;
|
|
101
|
+
this.lineMesh.material.needsUpdate = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets whether a line should be rendered as an overlay (rendered on top of
|
|
106
|
+
* the volume) instead of a mesh (with depth, intersects with volume).
|
|
107
|
+
* @param renderAsOverlay If true, the line will be rendered on top of the
|
|
108
|
+
* volume, ignoring depth.
|
|
109
|
+
*/
|
|
110
|
+
setRenderAsOverlay(renderAsOverlay) {
|
|
111
|
+
this.lineMesh.layers.set(renderAsOverlay ? OVERLAY_LAYER : MESH_LAYER);
|
|
112
|
+
this.lineMesh.material.depthTest = !renderAsOverlay;
|
|
113
|
+
this.lineMesh.material.depthTest = !renderAsOverlay;
|
|
114
|
+
this.lineMesh.material.needsUpdate = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Sets the width of the line in pixels.
|
|
119
|
+
* @param widthPx Width in pixels.
|
|
120
|
+
* @param useWorldUnits Whether to use world units for the line width. By
|
|
121
|
+
* default (false), the width is in screen pixels.
|
|
122
|
+
*/
|
|
123
|
+
setLineWidth(widthPx, useWorldUnits = false) {
|
|
124
|
+
this.lineMesh.material.linewidth = widthPx;
|
|
125
|
+
this.lineMesh.material.worldUnits = useWorldUnits;
|
|
126
|
+
this.lineMesh.material.needsUpdate = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sets the vertex data (position and RGB colors) for the line segments.
|
|
131
|
+
* @param positions A Float32Array of 3D coordinates, where each pair of
|
|
132
|
+
* coordinates is one line segment. Length must be a multiple of 6 (pairs of
|
|
133
|
+
* two 3-dimensional coordinates).
|
|
134
|
+
* @param colors A Float32Array of RGB values in the [0, 1] range, where each
|
|
135
|
+
* triplet corresponds to a vertex color.
|
|
136
|
+
* @throws {Error} If `positions` length is not a multiple of 6 or if `colors`
|
|
137
|
+
* length is not a multiple of 3.
|
|
138
|
+
*/
|
|
139
|
+
setLineVertexData(positions, colors) {
|
|
140
|
+
if (positions.length % 6 !== 0) {
|
|
141
|
+
throw new Error(`positions length of ${positions.length} is not a multiple of 6 (pairs of two 3-dimensional coordinates)`);
|
|
142
|
+
}
|
|
143
|
+
if (colors !== undefined && colors.length % 3 !== 0) {
|
|
144
|
+
throw new Error(`colors length of ${colors.length} is not a multiple of 3 (triplets of RGB values)`);
|
|
145
|
+
}
|
|
146
|
+
const newBufferSize = Math.max(positions.length, colors ? colors.length : 0);
|
|
147
|
+
// If buffer size is too small, dispose of the old geometry and create a new
|
|
148
|
+
// one with the larger size.
|
|
149
|
+
if (newBufferSize > this.bufferSize) {
|
|
150
|
+
this.lineMesh.geometry.dispose();
|
|
151
|
+
this.lineMesh.geometry = new LineSegmentsGeometry();
|
|
152
|
+
this.bufferSize = newBufferSize;
|
|
153
|
+
}
|
|
154
|
+
this.lineMesh.geometry.setPositions(positions);
|
|
155
|
+
if (colors) {
|
|
156
|
+
this.lineMesh.geometry.setColors(colors);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Number of line segments that should be visible. */
|
|
161
|
+
setNumSegmentsVisible(segments) {
|
|
162
|
+
if (this.lineMesh.geometry) {
|
|
163
|
+
const count = segments;
|
|
164
|
+
this.lineMesh.geometry.instanceCount = Math.max(0, count);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
package/es/ThreeJsPanel.js
CHANGED
|
@@ -9,6 +9,7 @@ import RenderToBuffer from "./RenderToBuffer.js";
|
|
|
9
9
|
import { copyImageFragShader } from "./constants/basicShaders.js";
|
|
10
10
|
export const VOLUME_LAYER = 0;
|
|
11
11
|
export const MESH_LAYER = 1;
|
|
12
|
+
export const OVERLAY_LAYER = 2;
|
|
12
13
|
const DEFAULT_PERSPECTIVE_CAMERA_DISTANCE = 5.0;
|
|
13
14
|
const DEFAULT_PERSPECTIVE_CAMERA_NEAR = 0.1;
|
|
14
15
|
const DEFAULT_PERSPECTIVE_CAMERA_FAR = 20.0;
|
|
@@ -554,9 +555,14 @@ export class ThreeJsPanel {
|
|
|
554
555
|
this.meshRenderToBuffer.render(this.renderer);
|
|
555
556
|
|
|
556
557
|
// Step 3: Render volumes, which can now depth test against the meshes.
|
|
558
|
+
this.renderer.autoClear = false;
|
|
557
559
|
this.camera.layers.set(VOLUME_LAYER);
|
|
558
560
|
this.renderer.setRenderTarget(null);
|
|
559
|
-
this.renderer.
|
|
561
|
+
this.renderer.render(this.scene, this.camera);
|
|
562
|
+
|
|
563
|
+
// Step 4: Render lines and other objects that must render over volumes and meshes.
|
|
564
|
+
this.camera.layers.set(OVERLAY_LAYER);
|
|
565
|
+
this.renderer.setRenderTarget(null);
|
|
560
566
|
this.renderer.render(this.scene, this.camera);
|
|
561
567
|
this.renderer.autoClear = true;
|
|
562
568
|
|
|
@@ -588,6 +594,7 @@ export class ThreeJsPanel {
|
|
|
588
594
|
const delta = this.timer.lastFrameMs / 1000.0;
|
|
589
595
|
this.controls.update(delta);
|
|
590
596
|
this.render();
|
|
597
|
+
this.onRenderCallback?.();
|
|
591
598
|
}
|
|
592
599
|
startRenderLoop() {
|
|
593
600
|
this.inRenderLoop = true;
|
|
@@ -604,6 +611,9 @@ export class ThreeJsPanel {
|
|
|
604
611
|
}
|
|
605
612
|
this.timer.end();
|
|
606
613
|
}
|
|
614
|
+
setOnRenderCallback(callback) {
|
|
615
|
+
this.onRenderCallback = callback ?? undefined;
|
|
616
|
+
}
|
|
607
617
|
removeControlHandlers() {
|
|
608
618
|
if (this.controlStartHandler) {
|
|
609
619
|
this.controls.removeEventListener("start", this.controlStartHandler);
|
|
@@ -648,7 +658,7 @@ export class ThreeJsPanel {
|
|
|
648
658
|
// read from the instance buffer
|
|
649
659
|
const pixel = new Float32Array(4).fill(-1);
|
|
650
660
|
this.renderer.readRenderTargetPixels(pickBuffer, sx, sy, 1, 1, pixel);
|
|
651
|
-
// For future reference, Simularium stores the following:
|
|
661
|
+
// For future reference, Simularium stores the following:
|
|
652
662
|
// (typeId), (instanceId), fragViewPos.z, fragPosDepth;
|
|
653
663
|
|
|
654
664
|
if (pixel[3] === -1 || pixel[3] === 0) {
|
package/es/View3d.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AmbientLight, Vector3, Object3D, SpotLight, DirectionalLight, Euler, Scene, Color } from "three";
|
|
1
|
+
import { AmbientLight, Vector3, Object3D, SpotLight, DirectionalLight, Euler, Scene, Color, Matrix4 } from "three";
|
|
2
2
|
import { Pane } from "tweakpane";
|
|
3
3
|
import { MESH_LAYER, ThreeJsPanel } from "./ThreeJsPanel.js";
|
|
4
4
|
import lightSettings from "./constants/lights.js";
|
|
@@ -107,6 +107,18 @@ export class View3d {
|
|
|
107
107
|
this.redraw();
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Returns the view projection matrix, which transforms from world coordinates
|
|
112
|
+
* to clip space coordinates.
|
|
113
|
+
*
|
|
114
|
+
* 3D coordinates within the camera's view frustum will be transformed to a
|
|
115
|
+
* [-1, 1] range in the X and Y axes, and a [0, 1] range in the Z axis.
|
|
116
|
+
*/
|
|
117
|
+
getViewProjectionMatrix() {
|
|
118
|
+
const camera = this.canvas3d.camera;
|
|
119
|
+
return new Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
120
|
+
}
|
|
121
|
+
|
|
110
122
|
/**
|
|
111
123
|
* Force a redraw.
|
|
112
124
|
* @param synchronous If true, the redraw will be done synchronously. If false (default), the
|
|
@@ -120,6 +132,13 @@ export class View3d {
|
|
|
120
132
|
this.canvas3d.redraw();
|
|
121
133
|
}
|
|
122
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets a listener that will be called after the 3D canvas renders.
|
|
138
|
+
*/
|
|
139
|
+
setOnRenderCallback(callback) {
|
|
140
|
+
this.canvas3d.setOnRenderCallback(callback);
|
|
141
|
+
}
|
|
123
142
|
unsetImage() {
|
|
124
143
|
if (this.image) {
|
|
125
144
|
this.canvas3d.removeControlHandlers();
|
|
@@ -824,6 +843,39 @@ export class View3d {
|
|
|
824
843
|
}
|
|
825
844
|
}
|
|
826
845
|
|
|
846
|
+
/**
|
|
847
|
+
* Adds a Line3d object as a child of the Volume, if a volume has been added
|
|
848
|
+
* to the view and the line is not already a child of it. Line positions will
|
|
849
|
+
* be in the normalized coordinate space of the Volume, where the origin
|
|
850
|
+
* (0,0,0) is at the center of the Volume and the extent is from -0.5 to 0.5
|
|
851
|
+
* in each axis.
|
|
852
|
+
*/
|
|
853
|
+
addLineObject(line) {
|
|
854
|
+
if (this.image) {
|
|
855
|
+
this.image.addLineObject(line);
|
|
856
|
+
this.redraw();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/** Returns whether a Line3d object exists as a child of the volume. */
|
|
861
|
+
hasLineObject(line) {
|
|
862
|
+
if (this.image) {
|
|
863
|
+
return this.image.hasLineObject(line);
|
|
864
|
+
}
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
870
|
+
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
871
|
+
*/
|
|
872
|
+
removeLineObject(line) {
|
|
873
|
+
if (this.image) {
|
|
874
|
+
this.image.removeLineObject(line);
|
|
875
|
+
this.redraw();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
827
879
|
/**
|
|
828
880
|
* @description Enable or disable picking on a volume. If enabled, the channelIndex is used to determine which channel to pick.
|
|
829
881
|
* @param volume the image to enable picking on
|
package/es/Volume.js
CHANGED
|
@@ -14,13 +14,30 @@ export default class Volume {
|
|
|
14
14
|
* Used to intelligently issue load requests whenever required by a state change. Modify with `updateRequiredData`.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* The maximum of the measurements of 3 axes in physical units.
|
|
19
|
+
*
|
|
20
|
+
* Equivalent to `Math.max(physicalSize.x, physicalSize.y, physicalSize.z)`.
|
|
21
|
+
*/
|
|
18
22
|
|
|
19
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* The size of a voxel in the original level 0 volume in real-world (physical)
|
|
25
|
+
* units (e.g. μm).
|
|
26
|
+
*/
|
|
20
27
|
|
|
21
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The dimensions of the whole volume (not accounting for subregion) in
|
|
30
|
+
* real-world (physical) units. Equivalent to `imageInfo.originalSize *
|
|
31
|
+
* physicalPixelSize`.
|
|
32
|
+
*/
|
|
22
33
|
|
|
23
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Physical size of the whole volume (not accounting for subregion) normalized
|
|
36
|
+
* along the largest axis, so that all dimensions are `<= 1`.
|
|
37
|
+
*
|
|
38
|
+
* Example: If the physical size is `[100, 50, 25]` μm, then the
|
|
39
|
+
* normPhysicalSize will be `[1, 0.5, 0.25]`.
|
|
40
|
+
*/
|
|
24
41
|
|
|
25
42
|
constructor(imageInfo = defaultImageInfo(), loadSpec = new LoadSpec(), loader) {
|
|
26
43
|
this.loaded = false;
|
package/es/VolumeDrawable.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vector3, Object3D, Euler, Vector2, Box3 } from "three";
|
|
1
|
+
import { Vector3, Object3D, Euler, Vector2, Box3, Group } from "three";
|
|
2
2
|
import MeshVolume from "./MeshVolume.js";
|
|
3
3
|
import RayMarchedAtlasVolume from "./RayMarchedAtlasVolume.js";
|
|
4
4
|
import PathTracedVolume from "./PathTracedVolume.js";
|
|
@@ -23,6 +23,19 @@ export const colorObjectToArray = ({
|
|
|
23
23
|
|
|
24
24
|
// A renderable multichannel volume image with 8-bits per channel intensity values.
|
|
25
25
|
export default class VolumeDrawable {
|
|
26
|
+
// TODO: Move responsibility for drawable objects that are otherwise unrelated
|
|
27
|
+
// to the volume out of VolumeDrawable. Consider making a parent group that
|
|
28
|
+
// can contain all other drawable objects AND the VolumeDrawable and its owned
|
|
29
|
+
// objects (MeshVolume).
|
|
30
|
+
/**
|
|
31
|
+
* Group for all child objects of the volume. The group is scaled and
|
|
32
|
+
* transformed to the normalized volume coordinate space, where the volume's
|
|
33
|
+
* center is at (0, 0, 0) and the bounds have a range of [-0.5, 0.5] along each
|
|
34
|
+
* axis.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** Set of drawable objects that are children of the volume's transform. */
|
|
38
|
+
|
|
26
39
|
constructor(volume, options) {
|
|
27
40
|
// THE VOLUME DATA
|
|
28
41
|
this.volume = volume;
|
|
@@ -49,7 +62,9 @@ export default class VolumeDrawable {
|
|
|
49
62
|
});
|
|
50
63
|
this.sceneRoot = new Object3D(); //create an empty container
|
|
51
64
|
|
|
52
|
-
this.
|
|
65
|
+
this.childObjectsGroup = new Group();
|
|
66
|
+
this.childObjects = new Set();
|
|
67
|
+
this.sceneRoot.add(this.childObjectsGroup);
|
|
53
68
|
options.renderMode = options.renderMode || RenderMode.RAYMARCH;
|
|
54
69
|
switch (options.renderMode) {
|
|
55
70
|
case RenderMode.PATHTRACE:
|
|
@@ -67,8 +82,10 @@ export default class VolumeDrawable {
|
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
// draw meshes first, and volume last, for blending and depth test reasons with raymarch
|
|
85
|
+
this.meshVolume = new MeshVolume(this.volume);
|
|
70
86
|
if (options.renderMode === RenderMode.RAYMARCH || options.renderMode === RenderMode.SLICE) {
|
|
71
|
-
this.
|
|
87
|
+
this.childObjectsGroup.add(this.meshVolume.get3dObject());
|
|
88
|
+
this.childObjects.add(this.meshVolume);
|
|
72
89
|
}
|
|
73
90
|
this.sceneRoot.add(this.volumeRendering.get3dObject());
|
|
74
91
|
// draw meshes last (as overlay) for pathtrace? (or not at all?)
|
|
@@ -216,7 +233,8 @@ export default class VolumeDrawable {
|
|
|
216
233
|
normRegionSize
|
|
217
234
|
} = this.volume;
|
|
218
235
|
const scale = normPhysicalSize.clone().multiply(normRegionSize).multiply(this.settings.scale);
|
|
219
|
-
this.
|
|
236
|
+
this.childObjectsGroup.scale.copy(scale);
|
|
237
|
+
this.childObjectsGroup.position.copy(this.volume.getContentCenter().multiply(this.settings.scale));
|
|
220
238
|
// TODO only `RayMarchedAtlasVolume` handles scale properly. Get the others on board too!
|
|
221
239
|
this.volumeRendering.updateVolumeDimensions();
|
|
222
240
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
@@ -234,7 +252,9 @@ export default class VolumeDrawable {
|
|
|
234
252
|
setResolution(x, y) {
|
|
235
253
|
const resolution = new Vector2(x, y);
|
|
236
254
|
if (!this.settings.resolution.equals(resolution)) {
|
|
237
|
-
this.
|
|
255
|
+
for (const object of this.childObjects) {
|
|
256
|
+
object.setResolution(x, y);
|
|
257
|
+
}
|
|
238
258
|
this.settings.resolution = resolution;
|
|
239
259
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.SAMPLING);
|
|
240
260
|
this.pickRendering?.updateSettings(this.settings, SettingsFlags.SAMPLING);
|
|
@@ -259,7 +279,9 @@ export default class VolumeDrawable {
|
|
|
259
279
|
|
|
260
280
|
// Configure mesh volume when in an orthographic axis alignment
|
|
261
281
|
if (axis !== Axis.NONE && this.renderMode !== RenderMode.PATHTRACE) {
|
|
262
|
-
|
|
282
|
+
for (const object of this.childObjects) {
|
|
283
|
+
object.setAxisClip(axis, minval, maxval, !!isOrthoAxis);
|
|
284
|
+
}
|
|
263
285
|
}
|
|
264
286
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.ROI | SettingsFlags.VIEW);
|
|
265
287
|
this.pickRendering?.updateSettings(this.settings, SettingsFlags.ROI | SettingsFlags.VIEW);
|
|
@@ -323,7 +345,9 @@ export default class VolumeDrawable {
|
|
|
323
345
|
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
324
346
|
return;
|
|
325
347
|
}
|
|
326
|
-
this.
|
|
348
|
+
for (const object of this.childObjects) {
|
|
349
|
+
object.setOrthoThickness(value);
|
|
350
|
+
}
|
|
327
351
|
// No settings update because ortho thickness is calculated in the renderers
|
|
328
352
|
}
|
|
329
353
|
|
|
@@ -345,7 +369,9 @@ export default class VolumeDrawable {
|
|
|
345
369
|
const flipAxes = new Vector3(flipX, flipY, flipZ);
|
|
346
370
|
if (!this.settings.flipAxes.equals(flipAxes)) {
|
|
347
371
|
this.settings.flipAxes = flipAxes;
|
|
348
|
-
|
|
372
|
+
for (const object of this.childObjects) {
|
|
373
|
+
object.setFlipAxes(flipX, flipY, flipZ);
|
|
374
|
+
}
|
|
349
375
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
350
376
|
this.pickRendering?.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
351
377
|
}
|
|
@@ -367,7 +393,9 @@ export default class VolumeDrawable {
|
|
|
367
393
|
// TODO confirm sequence
|
|
368
394
|
this.volumeRendering.doRender(renderer, camera, depthTexture);
|
|
369
395
|
if (this.renderMode !== RenderMode.PATHTRACE) {
|
|
370
|
-
this.
|
|
396
|
+
for (const object of this.childObjects) {
|
|
397
|
+
object.doRender();
|
|
398
|
+
}
|
|
371
399
|
}
|
|
372
400
|
}
|
|
373
401
|
enablePicking(enabled, channelIndex) {
|
|
@@ -441,7 +469,9 @@ export default class VolumeDrawable {
|
|
|
441
469
|
this.updateScale();
|
|
442
470
|
}
|
|
443
471
|
cleanup() {
|
|
444
|
-
this.
|
|
472
|
+
for (const object of this.childObjects) {
|
|
473
|
+
object.cleanup();
|
|
474
|
+
}
|
|
445
475
|
this.volumeRendering.cleanup();
|
|
446
476
|
this.pickRendering?.cleanup();
|
|
447
477
|
}
|
|
@@ -635,7 +665,9 @@ export default class VolumeDrawable {
|
|
|
635
665
|
updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax) {
|
|
636
666
|
this.settings.bounds.bmin = new Vector3(xmin - 0.5, ymin - 0.5, zmin - 0.5);
|
|
637
667
|
this.settings.bounds.bmax = new Vector3(xmax - 0.5, ymax - 0.5, zmax - 0.5);
|
|
638
|
-
|
|
668
|
+
for (const object of this.childObjects) {
|
|
669
|
+
object.updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax);
|
|
670
|
+
}
|
|
639
671
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.ROI);
|
|
640
672
|
this.pickRendering?.updateSettings(this.settings, SettingsFlags.ROI);
|
|
641
673
|
}
|
|
@@ -717,6 +749,37 @@ export default class VolumeDrawable {
|
|
|
717
749
|
this.settings.scale.copy(xyz);
|
|
718
750
|
this.updateScale();
|
|
719
751
|
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Adds a Line3d object as a child of the Volume, if it does not already
|
|
755
|
+
* exist. Line objects will be in the normalized coordinate space of the
|
|
756
|
+
* Volume, where the origin (0,0,0) is at the center of the Volume and the
|
|
757
|
+
* extent is from -0.5 to 0.5 in each axis.
|
|
758
|
+
*/
|
|
759
|
+
addLineObject(line) {
|
|
760
|
+
if (!this.childObjects.has(line)) {
|
|
761
|
+
this.childObjects.add(line);
|
|
762
|
+
this.childObjectsGroup.add(line.get3dObject());
|
|
763
|
+
line.setResolution(this.settings.resolution.x, this.settings.resolution.y);
|
|
764
|
+
line.setFlipAxes(this.settings.flipAxes.x, this.settings.flipAxes.y, this.settings.flipAxes.z);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Returns whether a line object exists as a child of the volume. */
|
|
769
|
+
hasLineObject(line) {
|
|
770
|
+
return this.childObjects.has(line);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
775
|
+
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
776
|
+
*/
|
|
777
|
+
removeLineObject(line) {
|
|
778
|
+
if (this.childObjects.has(line)) {
|
|
779
|
+
this.childObjects.delete(line);
|
|
780
|
+
this.childObjectsGroup.remove(line.get3dObject());
|
|
781
|
+
}
|
|
782
|
+
}
|
|
720
783
|
setupGui(pane) {
|
|
721
784
|
pane.addInput(this.settings, "translation").on("change", ({
|
|
722
785
|
value
|
package/es/index.js
CHANGED
|
@@ -18,4 +18,5 @@ import { TiffLoader } from "./loaders/TiffLoader.js";
|
|
|
18
18
|
import VolumeLoaderContext from "./workers/VolumeLoaderContext.js";
|
|
19
19
|
import { VolumeLoadError, VolumeLoadErrorType } from "./loaders/VolumeLoadError.js";
|
|
20
20
|
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
21
|
-
|
|
21
|
+
import Line3d from "./Line3d.js";
|
|
22
|
+
export { Histogram, Lut, Line3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Color, Euler, Group, Vector3 } from "three";
|
|
2
|
+
import { IDrawableObject } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
5
|
+
* color, width, and segments visible.
|
|
6
|
+
*/
|
|
7
|
+
export default class Line3d implements IDrawableObject {
|
|
8
|
+
private meshPivot;
|
|
9
|
+
private scale;
|
|
10
|
+
private flipAxes;
|
|
11
|
+
private lineMesh;
|
|
12
|
+
private bufferSize;
|
|
13
|
+
constructor();
|
|
14
|
+
cleanup(): void;
|
|
15
|
+
setVisible(visible: boolean): void;
|
|
16
|
+
doRender(): void;
|
|
17
|
+
get3dObject(): Group;
|
|
18
|
+
setTranslation(translation: Vector3): void;
|
|
19
|
+
setScale(scale: Vector3): void;
|
|
20
|
+
setRotation(eulerXYZ: Euler): void;
|
|
21
|
+
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
22
|
+
setOrthoThickness(_thickness: number): void;
|
|
23
|
+
setResolution(_x: number, _y: number): void;
|
|
24
|
+
setAxisClip(_axis: "x" | "y" | "z", _minval: number, _maxval: number, _isOrthoAxis: boolean): void;
|
|
25
|
+
updateClipRegion(_xmin: number, _xmax: number, _ymin: number, _ymax: number, _zmin: number, _zmax: number): void;
|
|
26
|
+
/**
|
|
27
|
+
* Sets the color of the line material.
|
|
28
|
+
* @param color Base line color.
|
|
29
|
+
* @param useVertexColors If true, _the line will multiply the base color with
|
|
30
|
+
* the per-vertex colors defined in the geometry (see `setLineVertexData`). Default is false.
|
|
31
|
+
*/
|
|
32
|
+
setColor(color: Color, useVertexColors?: boolean): void;
|
|
33
|
+
/**
|
|
34
|
+
* Sets the opacity of the line material.
|
|
35
|
+
*
|
|
36
|
+
* Note that transparent lines may have unexpected colors when intersecting
|
|
37
|
+
* with or overlapping the volume. Consider setting them to render as an
|
|
38
|
+
* overlay instead using `setOverlay()`.
|
|
39
|
+
* @param opacity Opacity value in the range `[0, 1]`. 0 is fully transparent,
|
|
40
|
+
* 1 is fully opaque.
|
|
41
|
+
*/
|
|
42
|
+
setOpacity(opacity: number): void;
|
|
43
|
+
/**
|
|
44
|
+
* Sets whether a line should be rendered as an overlay (rendered on top of
|
|
45
|
+
* the volume) instead of a mesh (with depth, intersects with volume).
|
|
46
|
+
* @param renderAsOverlay If true, the line will be rendered on top of the
|
|
47
|
+
* volume, ignoring depth.
|
|
48
|
+
*/
|
|
49
|
+
setRenderAsOverlay(renderAsOverlay: boolean): void;
|
|
50
|
+
/**
|
|
51
|
+
* Sets the width of the line in pixels.
|
|
52
|
+
* @param widthPx Width in pixels.
|
|
53
|
+
* @param useWorldUnits Whether to use world units for the line width. By
|
|
54
|
+
* default (false), the width is in screen pixels.
|
|
55
|
+
*/
|
|
56
|
+
setLineWidth(widthPx: number, useWorldUnits?: boolean): void;
|
|
57
|
+
/**
|
|
58
|
+
* Sets the vertex data (position and RGB colors) for the line segments.
|
|
59
|
+
* @param positions A Float32Array of 3D coordinates, where each pair of
|
|
60
|
+
* coordinates is one line segment. Length must be a multiple of 6 (pairs of
|
|
61
|
+
* two 3-dimensional coordinates).
|
|
62
|
+
* @param colors A Float32Array of RGB values in the [0, 1] range, where each
|
|
63
|
+
* triplet corresponds to a vertex color.
|
|
64
|
+
* @throws {Error} If `positions` length is not a multiple of 6 or if `colors`
|
|
65
|
+
* length is not a multiple of 3.
|
|
66
|
+
*/
|
|
67
|
+
setLineVertexData(positions: Float32Array, colors?: Float32Array): void;
|
|
68
|
+
/** Number of line segments that should be visible. */
|
|
69
|
+
setNumSegmentsVisible(segments: number): void;
|
|
70
|
+
}
|
package/es/types/MeshVolume.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BufferGeometry, Euler, Object3D, Vector3, Group, Material } from "three";
|
|
2
2
|
import Volume from "./Volume.js";
|
|
3
|
-
|
|
3
|
+
import type { IDrawableObject } from "./types.js";
|
|
4
|
+
export default class MeshVolume implements IDrawableObject {
|
|
4
5
|
private volume;
|
|
5
6
|
private meshRoot;
|
|
6
7
|
private meshPivot;
|
|
@@ -3,6 +3,7 @@ import TrackballControls from "./TrackballControls.js";
|
|
|
3
3
|
import { ViewportCorner } from "./types.js";
|
|
4
4
|
export declare const VOLUME_LAYER = 0;
|
|
5
5
|
export declare const MESH_LAYER = 1;
|
|
6
|
+
export declare const OVERLAY_LAYER = 2;
|
|
6
7
|
export type CameraState = {
|
|
7
8
|
position: [number, number, number];
|
|
8
9
|
up: [number, number, number];
|
|
@@ -53,6 +54,7 @@ export declare class ThreeJsPanel {
|
|
|
53
54
|
private timestepIndicatorElement;
|
|
54
55
|
showTimestepIndicator: boolean;
|
|
55
56
|
private dataurlcallback?;
|
|
57
|
+
private onRenderCallback?;
|
|
56
58
|
constructor(parentElement: HTMLElement | undefined, _useWebGL2: boolean);
|
|
57
59
|
updateCameraFocus(fov: number, _focalDistance: number, _apertureSize: number): void;
|
|
58
60
|
resetPerspectiveCamera(): void;
|
|
@@ -102,6 +104,7 @@ export declare class ThreeJsPanel {
|
|
|
102
104
|
onAnimationLoop(): void;
|
|
103
105
|
startRenderLoop(): void;
|
|
104
106
|
stopRenderLoop(): void;
|
|
107
|
+
setOnRenderCallback(callback: (() => void) | null): void;
|
|
105
108
|
removeControlHandlers(): void;
|
|
106
109
|
setControlHandlers(onstart: EventListener<Event, "start", TrackballControls>, onchange: EventListener<Event, "change", TrackballControls>, onend: EventListener<Event, "end", TrackballControls>): void;
|
|
107
110
|
hitTest(offsetX: number, offsetY: number, pickBuffer: WebGLRenderTarget | undefined): number;
|
package/es/types/View3d.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Matrix4 } from "three";
|
|
1
2
|
import { CameraState } from "./ThreeJsPanel.js";
|
|
2
3
|
import VolumeDrawable from "./VolumeDrawable.js";
|
|
3
4
|
import { Light } from "./Light.js";
|
|
@@ -5,6 +6,7 @@ import Volume from "./Volume.js";
|
|
|
5
6
|
import { type ColorizeFeature, type VolumeChannelDisplayOptions, type VolumeDisplayOptions, ViewportCorner, RenderMode } from "./types.js";
|
|
6
7
|
import { PerChannelCallback } from "./loaders/IVolumeLoader.js";
|
|
7
8
|
import VolumeLoaderContext from "./workers/VolumeLoaderContext.js";
|
|
9
|
+
import Line3d from "./Line3d.js";
|
|
8
10
|
export declare const RENDERMODE_RAYMARCH = RenderMode.RAYMARCH;
|
|
9
11
|
export declare const RENDERMODE_PATHTRACE = RenderMode.PATHTRACE;
|
|
10
12
|
export interface View3dOptions {
|
|
@@ -52,6 +54,14 @@ export declare class View3d {
|
|
|
52
54
|
getCanvasDOMElement(): HTMLCanvasElement;
|
|
53
55
|
getCameraState(): CameraState;
|
|
54
56
|
setCameraState(transform: Partial<CameraState>): void;
|
|
57
|
+
/**
|
|
58
|
+
* Returns the view projection matrix, which transforms from world coordinates
|
|
59
|
+
* to clip space coordinates.
|
|
60
|
+
*
|
|
61
|
+
* 3D coordinates within the camera's view frustum will be transformed to a
|
|
62
|
+
* [-1, 1] range in the X and Y axes, and a [0, 1] range in the Z axis.
|
|
63
|
+
*/
|
|
64
|
+
getViewProjectionMatrix(): Matrix4;
|
|
55
65
|
/**
|
|
56
66
|
* Force a redraw.
|
|
57
67
|
* @param synchronous If true, the redraw will be done synchronously. If false (default), the
|
|
@@ -59,6 +69,10 @@ export declare class View3d {
|
|
|
59
69
|
* whenever possible for the best performance.
|
|
60
70
|
*/
|
|
61
71
|
redraw(synchronous?: boolean): void;
|
|
72
|
+
/**
|
|
73
|
+
* Sets a listener that will be called after the 3D canvas renders.
|
|
74
|
+
*/
|
|
75
|
+
setOnRenderCallback(callback: (() => void) | null): void;
|
|
62
76
|
unsetImage(): VolumeDrawable | undefined;
|
|
63
77
|
/**
|
|
64
78
|
* Add a new volume image to the viewer. (The viewer currently only supports a single image at a time - adding repeatedly, without removing in between, is a potential resource leak)
|
|
@@ -371,6 +385,21 @@ export declare class View3d {
|
|
|
371
385
|
* @param id the selected id
|
|
372
386
|
*/
|
|
373
387
|
setSelectedID(volume: Volume, channel: number, id: number): void;
|
|
388
|
+
/**
|
|
389
|
+
* Adds a Line3d object as a child of the Volume, if a volume has been added
|
|
390
|
+
* to the view and the line is not already a child of it. Line positions will
|
|
391
|
+
* be in the normalized coordinate space of the Volume, where the origin
|
|
392
|
+
* (0,0,0) is at the center of the Volume and the extent is from -0.5 to 0.5
|
|
393
|
+
* in each axis.
|
|
394
|
+
*/
|
|
395
|
+
addLineObject(line: Line3d): void;
|
|
396
|
+
/** Returns whether a Line3d object exists as a child of the volume. */
|
|
397
|
+
hasLineObject(line: Line3d): boolean;
|
|
398
|
+
/**
|
|
399
|
+
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
400
|
+
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
401
|
+
*/
|
|
402
|
+
removeLineObject(line: Line3d): void;
|
|
374
403
|
/**
|
|
375
404
|
* @description Enable or disable picking on a volume. If enabled, the channelIndex is used to determine which channel to pick.
|
|
376
405
|
* @param volume the image to enable picking on
|
package/es/types/Volume.d.ts
CHANGED
|
@@ -30,13 +30,30 @@ export default class Volume {
|
|
|
30
30
|
numChannels: number;
|
|
31
31
|
channelNames: string[];
|
|
32
32
|
channelColorsDefault: [number, number, number][];
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* The maximum of the measurements of 3 axes in physical units.
|
|
35
|
+
*
|
|
36
|
+
* Equivalent to `Math.max(physicalSize.x, physicalSize.y, physicalSize.z)`.
|
|
37
|
+
*/
|
|
34
38
|
physicalScale: number;
|
|
35
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The size of a voxel in the original level 0 volume in real-world (physical)
|
|
41
|
+
* units (e.g. μm).
|
|
42
|
+
*/
|
|
36
43
|
physicalPixelSize: Vector3;
|
|
37
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* The dimensions of the whole volume (not accounting for subregion) in
|
|
46
|
+
* real-world (physical) units. Equivalent to `imageInfo.originalSize *
|
|
47
|
+
* physicalPixelSize`.
|
|
48
|
+
*/
|
|
38
49
|
physicalSize: Vector3;
|
|
39
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Physical size of the whole volume (not accounting for subregion) normalized
|
|
52
|
+
* along the largest axis, so that all dimensions are `<= 1`.
|
|
53
|
+
*
|
|
54
|
+
* Example: If the physical size is `[100, 50, 25]` μm, then the
|
|
55
|
+
* normPhysicalSize will be `[1, 0.5, 0.25]`.
|
|
56
|
+
*/
|
|
40
57
|
normPhysicalSize: Vector3;
|
|
41
58
|
normRegionSize: Vector3;
|
|
42
59
|
normRegionOffset: Vector3;
|
|
@@ -6,6 +6,7 @@ import { RenderMode } from "./types.js";
|
|
|
6
6
|
import { Light } from "./Light.js";
|
|
7
7
|
import Channel from "./Channel.js";
|
|
8
8
|
import { Axis } from "./VolumeRenderSettings.js";
|
|
9
|
+
import Line3d from "./Line3d.js";
|
|
9
10
|
type ColorArray = [number, number, number];
|
|
10
11
|
type ColorObject = {
|
|
11
12
|
r: number;
|
|
@@ -27,6 +28,15 @@ export default class VolumeDrawable {
|
|
|
27
28
|
private fusion;
|
|
28
29
|
sceneRoot: Object3D;
|
|
29
30
|
private meshVolume;
|
|
31
|
+
/**
|
|
32
|
+
* Group for all child objects of the volume. The group is scaled and
|
|
33
|
+
* transformed to the normalized volume coordinate space, where the volume's
|
|
34
|
+
* center is at (0, 0, 0) and the bounds have a range of [-0.5, 0.5] along each
|
|
35
|
+
* axis.
|
|
36
|
+
*/
|
|
37
|
+
private childObjectsGroup;
|
|
38
|
+
/** Set of drawable objects that are children of the volume's transform. */
|
|
39
|
+
private childObjects;
|
|
30
40
|
private volumeRendering;
|
|
31
41
|
private pickRendering?;
|
|
32
42
|
private renderMode;
|
|
@@ -107,6 +117,20 @@ export default class VolumeDrawable {
|
|
|
107
117
|
setTranslation(xyz: Vector3): void;
|
|
108
118
|
setRotation(eulerXYZ: Euler): void;
|
|
109
119
|
setScale(xyz: Vector3): void;
|
|
120
|
+
/**
|
|
121
|
+
* Adds a Line3d object as a child of the Volume, if it does not already
|
|
122
|
+
* exist. Line objects will be in the normalized coordinate space of the
|
|
123
|
+
* Volume, where the origin (0,0,0) is at the center of the Volume and the
|
|
124
|
+
* extent is from -0.5 to 0.5 in each axis.
|
|
125
|
+
*/
|
|
126
|
+
addLineObject(line: Line3d): void;
|
|
127
|
+
/** Returns whether a line object exists as a child of the volume. */
|
|
128
|
+
hasLineObject(line: Line3d): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
131
|
+
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
132
|
+
*/
|
|
133
|
+
removeLineObject(line: Line3d): void;
|
|
110
134
|
setupGui(pane: Pane): void;
|
|
111
135
|
setZSlice(slice: number): boolean;
|
|
112
136
|
get showBoundingBox(): boolean;
|
package/es/types/index.d.ts
CHANGED
|
@@ -19,10 +19,11 @@ import VolumeLoaderContext from "./workers/VolumeLoaderContext.js";
|
|
|
19
19
|
import { VolumeLoadError, VolumeLoadErrorType } from "./loaders/VolumeLoadError.js";
|
|
20
20
|
import { type CameraState } from "./ThreeJsPanel.js";
|
|
21
21
|
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
22
|
+
import Line3d from "./Line3d.js";
|
|
22
23
|
export type { ImageInfo } from "./ImageInfo.js";
|
|
23
24
|
export type { ControlPoint } from "./Lut.js";
|
|
24
25
|
export type { CreateLoaderOptions } from "./loaders/index.js";
|
|
25
26
|
export type { IVolumeLoader, PerChannelCallback, ThreadableVolumeLoader } from "./loaders/IVolumeLoader.js";
|
|
26
27
|
export type { ZarrLoaderFetchOptions } from "./loaders/OmeZarrLoader.js";
|
|
27
28
|
export type { WorkerLoader } from "./workers/VolumeLoaderContext.js";
|
|
28
|
-
export { Histogram, Lut, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, type RawArrayData, type RawArrayInfo, type RawArrayLoaderOptions, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT, type CameraState, type ColorizeFeature, };
|
|
29
|
+
export { Histogram, Lut, Line3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, type RawArrayData, type RawArrayInfo, type RawArrayLoaderOptions, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT, type CameraState, type ColorizeFeature, };
|
package/es/types/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Camera, Color, DataTexture, OrthographicCamera, PerspectiveCamera, Vector3 } from "three";
|
|
1
|
+
import { Camera, Color, DataTexture, Euler, Group, OrthographicCamera, PerspectiveCamera, Vector3 } from "three";
|
|
2
2
|
export interface Bounds {
|
|
3
3
|
bmin: Vector3;
|
|
4
4
|
bmax: Vector3;
|
|
@@ -30,6 +30,13 @@ export declare const ARRAY_CONSTRUCTORS: {
|
|
|
30
30
|
export interface ColorizeFeature {
|
|
31
31
|
idsToFeatureValue: DataTexture;
|
|
32
32
|
featureValueToColor: DataTexture;
|
|
33
|
+
/**
|
|
34
|
+
* Ignore the feature min and max, and treat the color ramp texture as a
|
|
35
|
+
* direct lookup for feature values. Feature values that are greater than
|
|
36
|
+
* the length of the color ramp will be wrapped around to the start
|
|
37
|
+
* (e.g. `value % colorRamp.length`).
|
|
38
|
+
*/
|
|
39
|
+
useRepeatingColor: boolean;
|
|
33
40
|
/**
|
|
34
41
|
* Maps from a frame number to an info object used to look up the global ID
|
|
35
42
|
* from a given segmentation ID (raw pixel value) on that frame. The info
|
|
@@ -64,6 +71,20 @@ export interface ColorizeFeature {
|
|
|
64
71
|
outOfRangeDrawMode: number;
|
|
65
72
|
hideOutOfRange: boolean;
|
|
66
73
|
}
|
|
74
|
+
export interface IDrawableObject {
|
|
75
|
+
cleanup(): void;
|
|
76
|
+
setVisible(visible: boolean): void;
|
|
77
|
+
doRender(): void;
|
|
78
|
+
get3dObject(): Group;
|
|
79
|
+
setTranslation(translation: Vector3): void;
|
|
80
|
+
setScale(scale: Vector3): void;
|
|
81
|
+
setRotation(eulerXYZ: Euler): void;
|
|
82
|
+
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
83
|
+
setOrthoThickness(thickness: number): void;
|
|
84
|
+
setResolution(x: number, y: number): void;
|
|
85
|
+
setAxisClip(axis: "x" | "y" | "z", minval: number, maxval: number, _isOrthoAxis: boolean): void;
|
|
86
|
+
updateClipRegion(xmin: number, xmax: number, ymin: number, ymax: number, zmin: number, zmax: number): void;
|
|
87
|
+
}
|
|
67
88
|
export interface FuseChannel {
|
|
68
89
|
chIndex: number;
|
|
69
90
|
lut: Uint8Array;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aics/vole-core",
|
|
3
|
-
"version": "3.15.
|
|
3
|
+
"version": "3.15.4",
|
|
4
4
|
"description": "volume renderer for 3d, 4d, or 5d imaging data with OME-Zarr support",
|
|
5
5
|
"main": "es/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -78,6 +78,6 @@
|
|
|
78
78
|
"vitest": "^3.0.8",
|
|
79
79
|
"webpack": "^5.69.1",
|
|
80
80
|
"webpack-cli": "^4.9.2",
|
|
81
|
-
"webpack-dev-server": "^
|
|
81
|
+
"webpack-dev-server": "^5.2.1"
|
|
82
82
|
}
|
|
83
83
|
}
|