@aics/vole-core 3.12.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/LICENSE.txt +26 -0
- package/README.md +119 -0
- package/es/Atlas2DSlice.js +224 -0
- package/es/Channel.js +264 -0
- package/es/FileSaver.js +31 -0
- package/es/FusedChannelData.js +192 -0
- package/es/Histogram.js +250 -0
- package/es/ImageInfo.js +127 -0
- package/es/Light.js +74 -0
- package/es/Lut.js +500 -0
- package/es/MarchingCubes.js +507 -0
- package/es/MeshVolume.js +334 -0
- package/es/NaiveSurfaceNets.js +251 -0
- package/es/PathTracedVolume.js +482 -0
- package/es/RayMarchedAtlasVolume.js +250 -0
- package/es/RenderToBuffer.js +31 -0
- package/es/ThreeJsPanel.js +633 -0
- package/es/Timing.js +28 -0
- package/es/TrackballControls.js +538 -0
- package/es/View3d.js +848 -0
- package/es/Volume.js +352 -0
- package/es/VolumeCache.js +161 -0
- package/es/VolumeDims.js +16 -0
- package/es/VolumeDrawable.js +702 -0
- package/es/VolumeMaker.js +101 -0
- package/es/VolumeRenderImpl.js +1 -0
- package/es/VolumeRenderSettings.js +203 -0
- package/es/constants/basicShaders.js +29 -0
- package/es/constants/colors.js +59 -0
- package/es/constants/denoiseShader.js +43 -0
- package/es/constants/lights.js +42 -0
- package/es/constants/materials.js +85 -0
- package/es/constants/pathtraceOutputShader.js +13 -0
- package/es/constants/scaleBarSVG.js +21 -0
- package/es/constants/time.js +34 -0
- package/es/constants/volumePTshader.js +153 -0
- package/es/constants/volumeRayMarchShader.js +123 -0
- package/es/constants/volumeSliceShader.js +115 -0
- package/es/index.js +21 -0
- package/es/loaders/IVolumeLoader.js +131 -0
- package/es/loaders/JsonImageInfoLoader.js +255 -0
- package/es/loaders/OmeZarrLoader.js +495 -0
- package/es/loaders/OpenCellLoader.js +65 -0
- package/es/loaders/RawArrayLoader.js +89 -0
- package/es/loaders/TiffLoader.js +219 -0
- package/es/loaders/VolumeLoadError.js +44 -0
- package/es/loaders/VolumeLoaderUtils.js +221 -0
- package/es/loaders/index.js +40 -0
- package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
- package/es/loaders/zarr_utils/WrappedStore.js +51 -0
- package/es/loaders/zarr_utils/types.js +24 -0
- package/es/loaders/zarr_utils/utils.js +225 -0
- package/es/loaders/zarr_utils/validation.js +49 -0
- package/es/test/ChunkPrefetchIterator.test.js +208 -0
- package/es/test/RequestQueue.test.js +442 -0
- package/es/test/SubscribableRequestQueue.test.js +244 -0
- package/es/test/VolumeCache.test.js +118 -0
- package/es/test/VolumeRenderSettings.test.js +71 -0
- package/es/test/lut.test.js +671 -0
- package/es/test/num_utils.test.js +140 -0
- package/es/test/volume.test.js +98 -0
- package/es/test/zarr_utils.test.js +358 -0
- package/es/types/Atlas2DSlice.d.ts +41 -0
- package/es/types/Channel.d.ts +44 -0
- package/es/types/FileSaver.d.ts +6 -0
- package/es/types/FusedChannelData.d.ts +26 -0
- package/es/types/Histogram.d.ts +57 -0
- package/es/types/ImageInfo.d.ts +87 -0
- package/es/types/Light.d.ts +27 -0
- package/es/types/Lut.d.ts +67 -0
- package/es/types/MarchingCubes.d.ts +53 -0
- package/es/types/MeshVolume.d.ts +40 -0
- package/es/types/NaiveSurfaceNets.d.ts +11 -0
- package/es/types/PathTracedVolume.d.ts +65 -0
- package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
- package/es/types/RenderToBuffer.d.ts +17 -0
- package/es/types/ThreeJsPanel.d.ts +107 -0
- package/es/types/Timing.d.ts +11 -0
- package/es/types/TrackballControls.d.ts +51 -0
- package/es/types/View3d.d.ts +357 -0
- package/es/types/Volume.d.ts +152 -0
- package/es/types/VolumeCache.d.ts +43 -0
- package/es/types/VolumeDims.d.ts +28 -0
- package/es/types/VolumeDrawable.d.ts +108 -0
- package/es/types/VolumeMaker.d.ts +49 -0
- package/es/types/VolumeRenderImpl.d.ts +22 -0
- package/es/types/VolumeRenderSettings.d.ts +98 -0
- package/es/types/constants/basicShaders.d.ts +4 -0
- package/es/types/constants/colors.d.ts +2 -0
- package/es/types/constants/denoiseShader.d.ts +40 -0
- package/es/types/constants/lights.d.ts +38 -0
- package/es/types/constants/materials.d.ts +20 -0
- package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
- package/es/types/constants/scaleBarSVG.d.ts +2 -0
- package/es/types/constants/time.d.ts +19 -0
- package/es/types/constants/volumePTshader.d.ts +137 -0
- package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
- package/es/types/constants/volumeSliceShader.d.ts +109 -0
- package/es/types/glsl.d.js +0 -0
- package/es/types/index.d.ts +28 -0
- package/es/types/loaders/IVolumeLoader.d.ts +113 -0
- package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
- package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
- package/es/types/loaders/OpenCellLoader.d.ts +9 -0
- package/es/types/loaders/RawArrayLoader.d.ts +33 -0
- package/es/types/loaders/TiffLoader.d.ts +45 -0
- package/es/types/loaders/VolumeLoadError.d.ts +18 -0
- package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
- package/es/types/loaders/index.d.ts +22 -0
- package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
- package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
- package/es/types/loaders/zarr_utils/types.d.ts +94 -0
- package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
- package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
- package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
- package/es/types/test/RequestQueue.test.d.ts +1 -0
- package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
- package/es/types/test/VolumeCache.test.d.ts +1 -0
- package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
- package/es/types/test/lut.test.d.ts +1 -0
- package/es/types/test/num_utils.test.d.ts +1 -0
- package/es/types/test/volume.test.d.ts +1 -0
- package/es/types/test/zarr_utils.test.d.ts +1 -0
- package/es/types/types.d.ts +115 -0
- package/es/types/utils/RequestQueue.d.ts +112 -0
- package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
- package/es/types/utils/num_utils.d.ts +43 -0
- package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
- package/es/types/workers/types.d.ts +101 -0
- package/es/types/workers/util.d.ts +3 -0
- package/es/types.js +75 -0
- package/es/typings.d.js +0 -0
- package/es/utils/RequestQueue.js +267 -0
- package/es/utils/SubscribableRequestQueue.js +187 -0
- package/es/utils/num_utils.js +231 -0
- package/es/workers/FetchTiffWorker.js +153 -0
- package/es/workers/VolumeLoadWorker.js +129 -0
- package/es/workers/VolumeLoaderContext.js +271 -0
- package/es/workers/types.js +41 -0
- package/es/workers/util.js +8 -0
- package/package.json +83 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { Vector3, Object3D, Euler, Vector2, Box3 } from "three";
|
|
2
|
+
import MeshVolume from "./MeshVolume.js";
|
|
3
|
+
import RayMarchedAtlasVolume from "./RayMarchedAtlasVolume.js";
|
|
4
|
+
import PathTracedVolume from "./PathTracedVolume.js";
|
|
5
|
+
import { LUT_ARRAY_LENGTH } from "./Lut.js";
|
|
6
|
+
import { RenderMode } from "./types.js";
|
|
7
|
+
import Atlas2DSlice from "./Atlas2DSlice.js";
|
|
8
|
+
import { VolumeRenderSettings, SettingsFlags, Axis } from "./VolumeRenderSettings.js";
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
|
|
12
|
+
export const colorArrayToObject = ([r, g, b]) => ({
|
|
13
|
+
r,
|
|
14
|
+
g,
|
|
15
|
+
b
|
|
16
|
+
});
|
|
17
|
+
export const colorObjectToArray = ({
|
|
18
|
+
r,
|
|
19
|
+
g,
|
|
20
|
+
b
|
|
21
|
+
}) => [r, g, b];
|
|
22
|
+
|
|
23
|
+
// A renderable multichannel volume image with 8-bits per channel intensity values.
|
|
24
|
+
export default class VolumeDrawable {
|
|
25
|
+
constructor(volume, options) {
|
|
26
|
+
// THE VOLUME DATA
|
|
27
|
+
this.volume = volume;
|
|
28
|
+
this.settings = new VolumeRenderSettings(volume);
|
|
29
|
+
this.onChannelDataReadyCallback = undefined;
|
|
30
|
+
this.viewMode = Axis.NONE; // 3D mode
|
|
31
|
+
|
|
32
|
+
this.channelColors = this.volume.channelColorsDefault.slice();
|
|
33
|
+
this.channelOptions = new Array(this.volume.imageInfo.numChannels).fill({});
|
|
34
|
+
this.fusion = this.channelColors.map((col, index) => {
|
|
35
|
+
let rgbColor;
|
|
36
|
+
// take copy of original channel color
|
|
37
|
+
if (col[0] === 0 && col[1] === 0 && col[2] === 0) {
|
|
38
|
+
rgbColor = 0;
|
|
39
|
+
} else {
|
|
40
|
+
rgbColor = [col[0], col[1], col[2]];
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
chIndex: index,
|
|
44
|
+
lut: new Uint8Array(LUT_ARRAY_LENGTH),
|
|
45
|
+
rgbColor: rgbColor
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
this.sceneRoot = new Object3D(); //create an empty container
|
|
49
|
+
|
|
50
|
+
this.meshVolume = new MeshVolume(this.volume);
|
|
51
|
+
options.renderMode = options.renderMode || RenderMode.RAYMARCH;
|
|
52
|
+
switch (options.renderMode) {
|
|
53
|
+
case RenderMode.PATHTRACE:
|
|
54
|
+
this.renderMode = RenderMode.PATHTRACE;
|
|
55
|
+
this.volumeRendering = new PathTracedVolume(this.volume, this.settings);
|
|
56
|
+
break;
|
|
57
|
+
case RenderMode.SLICE: // default to raymarch even when slice is selected
|
|
58
|
+
case RenderMode.RAYMARCH:
|
|
59
|
+
default:
|
|
60
|
+
this.renderMode = RenderMode.RAYMARCH;
|
|
61
|
+
this.volumeRendering = new RayMarchedAtlasVolume(this.volume, this.settings);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// draw meshes first, and volume last, for blending and depth test reasons with raymarch
|
|
65
|
+
if (options.renderMode === RenderMode.RAYMARCH || options.renderMode === RenderMode.SLICE) {
|
|
66
|
+
this.sceneRoot.add(this.meshVolume.get3dObject());
|
|
67
|
+
}
|
|
68
|
+
this.sceneRoot.add(this.volumeRendering.get3dObject());
|
|
69
|
+
// draw meshes last (as overlay) for pathtrace? (or not at all?)
|
|
70
|
+
//this.PT && this.sceneRoot.add(this.meshVolume.get3dObject());
|
|
71
|
+
|
|
72
|
+
this.sceneRoot.position.set(0, 0, 0);
|
|
73
|
+
this.updateScale();
|
|
74
|
+
|
|
75
|
+
// apply the volume's default transformation
|
|
76
|
+
this.settings.translation = new Vector3().fromArray(this.volume.getTranslation());
|
|
77
|
+
this.settings.rotation = new Euler().fromArray(this.volume.getRotation());
|
|
78
|
+
this.setOptions(options);
|
|
79
|
+
// this.volumeRendering.setZSlice(this.zSlice);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Updates whether a channel's data must be loaded for rendering,
|
|
84
|
+
* based on if its volume or isosurface is enabled, or whether it is needed for masking.
|
|
85
|
+
* Calls `Volume.updateRequiredData` to update the list of required channels if necessary.
|
|
86
|
+
*/
|
|
87
|
+
updateChannelDataRequired(channelIndex) {
|
|
88
|
+
const {
|
|
89
|
+
enabled,
|
|
90
|
+
isosurfaceEnabled
|
|
91
|
+
} = this.channelOptions[channelIndex];
|
|
92
|
+
const channelIsRequired = enabled || isosurfaceEnabled || channelIndex === this.settings.maskChannelIndex;
|
|
93
|
+
const requiredChannels = this.volume.loadSpecRequired.channels;
|
|
94
|
+
if (requiredChannels.includes(channelIndex)) {
|
|
95
|
+
if (!channelIsRequired) {
|
|
96
|
+
// This channel is currently marked required, but both its volume and isosurface are off, and it's not a mask. Remove it!
|
|
97
|
+
this.volume.updateRequiredData({
|
|
98
|
+
channels: requiredChannels.filter(i => i !== channelIndex)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
if (channelIsRequired) {
|
|
103
|
+
// This channel is not marked required, but either its volume or isosurface is on, or it is a mask. Add it!
|
|
104
|
+
this.volume.updateRequiredData({
|
|
105
|
+
channels: [...requiredChannels, channelIndex]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
setOptions(options) {
|
|
111
|
+
options = options || {};
|
|
112
|
+
if (options.maskChannelIndex !== undefined) {
|
|
113
|
+
this.setChannelAsMask(options.maskChannelIndex);
|
|
114
|
+
}
|
|
115
|
+
if (options.maskAlpha !== undefined) {
|
|
116
|
+
this.setMaskAlpha(options.maskAlpha);
|
|
117
|
+
}
|
|
118
|
+
if (options.clipBounds !== undefined) {
|
|
119
|
+
this.settings.bounds = {
|
|
120
|
+
bmin: new Vector3(options.clipBounds[0], options.clipBounds[2], options.clipBounds[4]),
|
|
121
|
+
bmax: new Vector3(options.clipBounds[1], options.clipBounds[3], options.clipBounds[5])
|
|
122
|
+
};
|
|
123
|
+
// note: dropping isOrthoAxis argument
|
|
124
|
+
this.setAxisClip(Axis.X, options.clipBounds[0], options.clipBounds[1]);
|
|
125
|
+
this.setAxisClip(Axis.Y, options.clipBounds[2], options.clipBounds[3]);
|
|
126
|
+
this.setAxisClip(Axis.Z, options.clipBounds[4], options.clipBounds[5]);
|
|
127
|
+
}
|
|
128
|
+
if (options.translation !== undefined) {
|
|
129
|
+
this.setTranslation(new Vector3().fromArray(options.translation));
|
|
130
|
+
}
|
|
131
|
+
if (options.rotation !== undefined) {
|
|
132
|
+
this.setRotation(new Euler().fromArray(options.rotation));
|
|
133
|
+
}
|
|
134
|
+
if (options.renderMode !== undefined) {
|
|
135
|
+
this.setVolumeRendering(options.renderMode);
|
|
136
|
+
}
|
|
137
|
+
if (options.primaryRayStepSize !== undefined || options.secondaryRayStepSize !== undefined) {
|
|
138
|
+
this.setRayStepSizes(options.primaryRayStepSize, options.secondaryRayStepSize);
|
|
139
|
+
}
|
|
140
|
+
if (options.showBoundingBox !== undefined) {
|
|
141
|
+
this.setShowBoundingBox(options.showBoundingBox);
|
|
142
|
+
}
|
|
143
|
+
if (options.boundingBoxColor !== undefined) {
|
|
144
|
+
this.setBoundingBoxColor(options.boundingBoxColor);
|
|
145
|
+
}
|
|
146
|
+
if (options.channels !== undefined) {
|
|
147
|
+
// store channel options here!
|
|
148
|
+
this.channelOptions = options.channels;
|
|
149
|
+
this.channelOptions.forEach((channelOptions, channelIndex) => {
|
|
150
|
+
this.setChannelOptions(channelIndex, channelOptions);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
setChannelOptions(channelIndex, options) {
|
|
155
|
+
// merge to current channel options
|
|
156
|
+
this.channelOptions[channelIndex] = Object.assign(this.channelOptions[channelIndex], options);
|
|
157
|
+
if (options.enabled !== undefined) {
|
|
158
|
+
this.setVolumeChannelEnabled(channelIndex, options.enabled);
|
|
159
|
+
}
|
|
160
|
+
if (options.color !== undefined) {
|
|
161
|
+
this.updateChannelColor(channelIndex, options.color);
|
|
162
|
+
}
|
|
163
|
+
if (options.isosurfaceEnabled !== undefined) {
|
|
164
|
+
const hasIso = this.hasIsosurface(channelIndex);
|
|
165
|
+
if (hasIso !== options.isosurfaceEnabled) {
|
|
166
|
+
if (hasIso && !options.isosurfaceEnabled) {
|
|
167
|
+
this.meshVolume.destroyIsosurface(channelIndex);
|
|
168
|
+
} else if (!hasIso && options.isosurfaceEnabled && this.volume.channels[channelIndex].loaded) {
|
|
169
|
+
const {
|
|
170
|
+
isovalue,
|
|
171
|
+
isosurfaceOpacity
|
|
172
|
+
} = options;
|
|
173
|
+
this.meshVolume.createIsosurface(channelIndex, this.channelColors[channelIndex], isovalue, isosurfaceOpacity);
|
|
174
|
+
}
|
|
175
|
+
this.updateChannelDataRequired(channelIndex);
|
|
176
|
+
} else if (options.isosurfaceEnabled) {
|
|
177
|
+
if (options.isovalue !== undefined) {
|
|
178
|
+
this.meshVolume.updateIsovalue(channelIndex, options.isovalue);
|
|
179
|
+
}
|
|
180
|
+
if (options.isosurfaceOpacity !== undefined) {
|
|
181
|
+
this.meshVolume.updateOpacity(channelIndex, options.isosurfaceOpacity);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
if (options.isovalue !== undefined) {
|
|
186
|
+
this.meshVolume.updateIsovalue(channelIndex, options.isovalue);
|
|
187
|
+
}
|
|
188
|
+
if (options.isosurfaceOpacity !== undefined) {
|
|
189
|
+
this.meshVolume.updateOpacity(channelIndex, options.isosurfaceOpacity);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
setRayStepSizes(primary, secondary) {
|
|
194
|
+
if (primary === this.settings.primaryRayStepSize && secondary === this.settings.secondaryRayStepSize) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (primary !== undefined) {
|
|
198
|
+
this.settings.primaryRayStepSize = primary;
|
|
199
|
+
}
|
|
200
|
+
if (secondary !== undefined) {
|
|
201
|
+
this.settings.secondaryRayStepSize = secondary;
|
|
202
|
+
}
|
|
203
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.SAMPLING);
|
|
204
|
+
}
|
|
205
|
+
updateScale() {
|
|
206
|
+
const {
|
|
207
|
+
normPhysicalSize,
|
|
208
|
+
normRegionSize
|
|
209
|
+
} = this.volume;
|
|
210
|
+
const scale = normPhysicalSize.clone().multiply(normRegionSize).multiply(this.settings.scale);
|
|
211
|
+
this.meshVolume.setScale(scale, this.volume.getContentCenter().multiply(this.settings.scale));
|
|
212
|
+
// TODO only `RayMarchedAtlasVolume` handles scale properly. Get the others on board too!
|
|
213
|
+
this.volumeRendering.updateVolumeDimensions();
|
|
214
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
215
|
+
}
|
|
216
|
+
setOrthoScale(value) {
|
|
217
|
+
if (this.settings.orthoScale === value) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.settings.orthoScale = value;
|
|
221
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.VIEW);
|
|
222
|
+
}
|
|
223
|
+
setResolution(x, y) {
|
|
224
|
+
const resolution = new Vector2(x, y);
|
|
225
|
+
if (!this.settings.resolution.equals(resolution)) {
|
|
226
|
+
this.meshVolume.setResolution(x, y);
|
|
227
|
+
this.settings.resolution = resolution;
|
|
228
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.SAMPLING);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Set clipping range (between -0.5 and 0.5) for a given axis.
|
|
233
|
+
// Calling this allows the rendering to compensate for changes in thickness in orthographic views that affect how bright the volume is.
|
|
234
|
+
// @param {number} axis 0, 1, or 2 for x, y, or z axis
|
|
235
|
+
// @param {number} minval -0.5..0.5, should be less than maxval
|
|
236
|
+
// @param {number} maxval -0.5..0.5, should be greater than minval
|
|
237
|
+
// @param {boolean} isOrthoAxis is this an orthographic projection or just a clipping of the range for perspective view
|
|
238
|
+
setAxisClip(axis, minval, maxval, isOrthoAxis) {
|
|
239
|
+
// Skip settings update if nothing has changed
|
|
240
|
+
if (this.settings.bounds.bmax[axis] === maxval && this.settings.bounds.bmin[axis] === minval && this.settings.viewAxis === axis && this.settings.isOrtho === (isOrthoAxis || false)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
this.settings.bounds.bmax[axis] = maxval;
|
|
244
|
+
this.settings.bounds.bmin[axis] = minval;
|
|
245
|
+
this.settings.viewAxis = axis;
|
|
246
|
+
this.settings.isOrtho = isOrthoAxis || false;
|
|
247
|
+
|
|
248
|
+
// Configure mesh volume when in an orthographic axis alignment
|
|
249
|
+
if (axis !== Axis.NONE && this.renderMode !== RenderMode.PATHTRACE) {
|
|
250
|
+
this.meshVolume.setAxisClip(axis, minval, maxval, !!isOrthoAxis);
|
|
251
|
+
}
|
|
252
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.ROI | SettingsFlags.VIEW);
|
|
253
|
+
}
|
|
254
|
+
modeStringToAxis(mode) {
|
|
255
|
+
const modeToAxis = {
|
|
256
|
+
X: Axis.X,
|
|
257
|
+
YZ: Axis.X,
|
|
258
|
+
Y: Axis.Y,
|
|
259
|
+
XZ: Axis.Y,
|
|
260
|
+
Z: Axis.Z,
|
|
261
|
+
XY: Axis.Z
|
|
262
|
+
};
|
|
263
|
+
return modeToAxis[mode] || Axis.NONE;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sets the camera mode of the VolumeDrawable.
|
|
267
|
+
* @param mode Mode can be "3D", or "XY" or "Z", or "YZ" or "X", or "XZ" or "Y".
|
|
268
|
+
*/
|
|
269
|
+
setViewMode(mode, volumeRenderModeHint) {
|
|
270
|
+
const axis = this.modeStringToAxis(mode);
|
|
271
|
+
this.viewMode = axis;
|
|
272
|
+
// Force a volume render reset if we have switched to or from Z mode while raymarching is enabled.
|
|
273
|
+
if (axis === Axis.Z) {
|
|
274
|
+
// If currently in 3D raymarch mode, hotswap the 2D slice
|
|
275
|
+
if (this.renderMode === RenderMode.RAYMARCH || this.renderMode === RenderMode.PATHTRACE) {
|
|
276
|
+
this.setVolumeRendering(RenderMode.SLICE);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
// If in 2D slice mode, switch back to 3D raymarch mode
|
|
280
|
+
if (this.renderMode === RenderMode.SLICE) {
|
|
281
|
+
this.setVolumeRendering(volumeRenderModeHint);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (this.settings.viewAxis !== axis) {
|
|
285
|
+
this.settings.viewAxis = axis;
|
|
286
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.VIEW);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Tell this image that it needs to be drawn in an orthographic mode
|
|
291
|
+
// @param {boolean} isOrtho is this an orthographic projection or a perspective view
|
|
292
|
+
setIsOrtho(isOrtho) {
|
|
293
|
+
if (this.settings.isOrtho === isOrtho) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
this.settings.isOrtho = isOrtho;
|
|
297
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.VIEW);
|
|
298
|
+
}
|
|
299
|
+
setInterpolationEnabled(active) {
|
|
300
|
+
if (this.settings.useInterpolation === active) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this.settings.useInterpolation = active;
|
|
304
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.SAMPLING);
|
|
305
|
+
}
|
|
306
|
+
setOrthoThickness(value) {
|
|
307
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
this.meshVolume.setOrthoThickness(value);
|
|
311
|
+
// No settings update because ortho thickness is calculated in the renderers
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Set parameters for gamma curve for volume rendering.
|
|
315
|
+
// @param {number} gmin 0..1
|
|
316
|
+
// @param {number} glevel 0..1
|
|
317
|
+
// @param {number} gmax 0..1, should be > gmin
|
|
318
|
+
setGamma(gmin, glevel, gmax) {
|
|
319
|
+
if (this.settings.gammaMin === gmin && this.settings.gammaLevel === glevel && this.settings.gammaMax === gmax) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
this.settings.gammaMin = gmin;
|
|
323
|
+
this.settings.gammaLevel = glevel;
|
|
324
|
+
this.settings.gammaMax = gmax;
|
|
325
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.CAMERA);
|
|
326
|
+
}
|
|
327
|
+
setFlipAxes(flipX, flipY, flipZ) {
|
|
328
|
+
const flipAxes = new Vector3(flipX, flipY, flipZ);
|
|
329
|
+
if (!this.settings.flipAxes.equals(flipAxes)) {
|
|
330
|
+
this.settings.flipAxes = flipAxes;
|
|
331
|
+
this.meshVolume.setFlipAxes(flipX, flipY, flipZ);
|
|
332
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
setMaxProjectMode(isMaxProject) {
|
|
336
|
+
if (this.settings.maxProjectMode === isMaxProject) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
this.settings.maxProjectMode = isMaxProject;
|
|
340
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.VIEW);
|
|
341
|
+
}
|
|
342
|
+
onAnimate(renderer, camera, depthTexture) {
|
|
343
|
+
// TODO: this is inefficient, as this work is duplicated by threejs.
|
|
344
|
+
// we need camera matrix up to date before giving the 3d objects a chance to use it.
|
|
345
|
+
camera.updateMatrixWorld(true);
|
|
346
|
+
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
|
|
347
|
+
|
|
348
|
+
// TODO confirm sequence
|
|
349
|
+
this.volumeRendering.doRender(renderer, camera, depthTexture);
|
|
350
|
+
if (this.renderMode !== RenderMode.PATHTRACE) {
|
|
351
|
+
this.meshVolume.doRender();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
getViewMode() {
|
|
355
|
+
return this.viewMode;
|
|
356
|
+
}
|
|
357
|
+
getIsovalue(channel) {
|
|
358
|
+
return this.meshVolume.getIsovalue(channel);
|
|
359
|
+
}
|
|
360
|
+
hasIsosurface(channel) {
|
|
361
|
+
return this.meshVolume.hasIsosurface(channel);
|
|
362
|
+
}
|
|
363
|
+
fuse() {
|
|
364
|
+
if (!this.volume) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.volumeRendering.updateActiveChannels(this.fusion, this.volume.channels);
|
|
368
|
+
}
|
|
369
|
+
setRenderUpdateListener(callback) {
|
|
370
|
+
this.renderUpdateListener = callback;
|
|
371
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
372
|
+
this.volumeRendering.setRenderUpdateListener(callback);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
updateShadingMethod(isbrdf) {
|
|
376
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
377
|
+
this.volumeRendering.updateShadingMethod(isbrdf ? 1 : 0);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
updateMaterial() {
|
|
381
|
+
this.volumeRendering.updateActiveChannels(this.fusion, this.volume.channels);
|
|
382
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.MATERIAL);
|
|
383
|
+
}
|
|
384
|
+
updateLuts() {
|
|
385
|
+
this.volumeRendering.updateActiveChannels(this.fusion, this.volume.channels);
|
|
386
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.MATERIAL);
|
|
387
|
+
}
|
|
388
|
+
setVoxelSize(values) {
|
|
389
|
+
this.volume.setVoxelSize(values);
|
|
390
|
+
this.updateScale();
|
|
391
|
+
}
|
|
392
|
+
cleanup() {
|
|
393
|
+
this.meshVolume.cleanup();
|
|
394
|
+
this.volumeRendering.cleanup();
|
|
395
|
+
}
|
|
396
|
+
getChannel(channelIndex) {
|
|
397
|
+
return this.volume.getChannel(channelIndex);
|
|
398
|
+
}
|
|
399
|
+
onChannelLoaded(batch) {
|
|
400
|
+
for (let j = 0; j < batch.length; ++j) {
|
|
401
|
+
const idx = batch[j];
|
|
402
|
+
const channelOptions = this.channelOptions[idx];
|
|
403
|
+
// TODO: this is a relatively crude way to ensure that channel settings are synced up when volume data is loaded.
|
|
404
|
+
// Can we instead audit which settings updated by `setChannelOptions` actually need to be reset on load?
|
|
405
|
+
this.setChannelOptions(idx, channelOptions);
|
|
406
|
+
if (channelOptions.isosurfaceEnabled) {
|
|
407
|
+
this.meshVolume.destroyIsosurface(idx);
|
|
408
|
+
const {
|
|
409
|
+
isovalue,
|
|
410
|
+
isosurfaceOpacity
|
|
411
|
+
} = channelOptions;
|
|
412
|
+
this.meshVolume.createIsosurface(idx, this.channelColors[idx], isovalue, isosurfaceOpacity);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// let the outside world have a chance
|
|
417
|
+
this.onChannelDataReadyCallback?.();
|
|
418
|
+
}
|
|
419
|
+
onChannelAdded(newChannelIndex) {
|
|
420
|
+
this.channelColors[newChannelIndex] = this.volume.channelColorsDefault[newChannelIndex];
|
|
421
|
+
this.fusion[newChannelIndex] = {
|
|
422
|
+
chIndex: newChannelIndex,
|
|
423
|
+
lut: new Uint8Array[LUT_ARRAY_LENGTH](),
|
|
424
|
+
rgbColor: [this.channelColors[newChannelIndex][0], this.channelColors[newChannelIndex][1], this.channelColors[newChannelIndex][2]]
|
|
425
|
+
};
|
|
426
|
+
this.settings.diffuse[newChannelIndex] = [this.channelColors[newChannelIndex][0], this.channelColors[newChannelIndex][1], this.channelColors[newChannelIndex][2]];
|
|
427
|
+
this.settings.specular[newChannelIndex] = [0, 0, 0];
|
|
428
|
+
this.settings.emissive[newChannelIndex] = [0, 0, 0];
|
|
429
|
+
this.settings.glossiness[newChannelIndex] = 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Save a channel's isosurface as a triangle mesh to either STL or GLTF2 format. File will be named automatically, using image name and channel name.
|
|
433
|
+
// @param {string} type Either 'GLTF' or 'STL'
|
|
434
|
+
saveChannelIsosurface(channelIndex, type) {
|
|
435
|
+
this.meshVolume.saveChannelIsosurface(channelIndex, type, this.volume.name);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Hide or display volume data for a channel
|
|
439
|
+
setVolumeChannelEnabled(channelIndex, enabled) {
|
|
440
|
+
// flip the color to the "null" value
|
|
441
|
+
this.fusion[channelIndex].rgbColor = enabled ? this.channelColors[channelIndex] : 0;
|
|
442
|
+
// if all are nulled out, then hide the volume element from the scene.
|
|
443
|
+
this.settings.visible = !this.fusion.every(elem => elem.rgbColor === 0);
|
|
444
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.VIEW);
|
|
445
|
+
|
|
446
|
+
// add or remove this channel from the list of required channels to load
|
|
447
|
+
this.updateChannelDataRequired(channelIndex);
|
|
448
|
+
}
|
|
449
|
+
isVolumeChannelEnabled(channelIndex) {
|
|
450
|
+
// the zero value for the fusion rgbColor is the indicator that a channel is hidden.
|
|
451
|
+
return this.fusion[channelIndex].rgbColor !== 0;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Set the color for a channel
|
|
455
|
+
// @param {Array.<number>} colorrgb [r,g,b]
|
|
456
|
+
updateChannelColor(channelIndex, colorrgb) {
|
|
457
|
+
if (!this.channelColors[channelIndex]) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
this.channelColors[channelIndex] = colorrgb;
|
|
461
|
+
this.settings.diffuse[channelIndex] = colorrgb;
|
|
462
|
+
// if volume channel is zero'ed out, then don't update it until it is switched on again.
|
|
463
|
+
if (this.fusion[channelIndex].rgbColor !== 0) {
|
|
464
|
+
this.fusion[channelIndex].rgbColor = colorrgb;
|
|
465
|
+
}
|
|
466
|
+
this.meshVolume.updateMeshColors(this.channelColors);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// TODO remove this from public interface?
|
|
470
|
+
updateMeshColors() {
|
|
471
|
+
this.meshVolume.updateMeshColors(this.channelColors);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Get the color for a channel
|
|
475
|
+
// @return {Array.<number>} The color as array of [r,g,b]
|
|
476
|
+
getChannelColor(channelIndex) {
|
|
477
|
+
return this.channelColors[channelIndex];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Set the material for a channel
|
|
481
|
+
// @param {number} channelIndex
|
|
482
|
+
// @param {Array.<number>} colorrgb [r,g,b]
|
|
483
|
+
// @param {Array.<number>} specularrgb [r,g,b]
|
|
484
|
+
// @param {Array.<number>} emissivergb [r,g,b]
|
|
485
|
+
// @param {number} glossiness
|
|
486
|
+
updateChannelMaterial(channelIndex, colorrgb, specularrgb, emissivergb, glossiness) {
|
|
487
|
+
if (!this.channelColors[channelIndex]) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
this.updateChannelColor(channelIndex, colorrgb);
|
|
491
|
+
this.settings.diffuse[channelIndex] = colorrgb;
|
|
492
|
+
this.settings.specular[channelIndex] = specularrgb;
|
|
493
|
+
this.settings.emissive[channelIndex] = emissivergb;
|
|
494
|
+
this.settings.glossiness[channelIndex] = glossiness;
|
|
495
|
+
}
|
|
496
|
+
setDensity(density) {
|
|
497
|
+
this.settings.density = density;
|
|
498
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.MATERIAL);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get the global density of the volume data
|
|
503
|
+
*/
|
|
504
|
+
getDensity() {
|
|
505
|
+
return this.settings.density;
|
|
506
|
+
}
|
|
507
|
+
setBrightness(brightness) {
|
|
508
|
+
this.settings.brightness = brightness;
|
|
509
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.CAMERA);
|
|
510
|
+
}
|
|
511
|
+
getBrightness() {
|
|
512
|
+
return this.settings.brightness;
|
|
513
|
+
}
|
|
514
|
+
setChannelAsMask(channelIndex) {
|
|
515
|
+
if (!this.volume.channels[channelIndex] || !this.volume.channels[channelIndex].loaded) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
this.settings.maskChannelIndex = channelIndex;
|
|
519
|
+
this.updateChannelDataRequired(channelIndex);
|
|
520
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.MASK_DATA);
|
|
521
|
+
}
|
|
522
|
+
setMaskAlpha(maskAlpha) {
|
|
523
|
+
this.settings.maskAlpha = maskAlpha;
|
|
524
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.MASK_ALPHA);
|
|
525
|
+
}
|
|
526
|
+
setShowBoundingBox(showBoundingBox) {
|
|
527
|
+
this.settings.showBoundingBox = showBoundingBox;
|
|
528
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.BOUNDING_BOX);
|
|
529
|
+
}
|
|
530
|
+
setBoundingBoxColor(color) {
|
|
531
|
+
this.settings.boundingBoxColor = color;
|
|
532
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.BOUNDING_BOX);
|
|
533
|
+
}
|
|
534
|
+
getIntensity(c, x, y, z) {
|
|
535
|
+
return this.volume.getIntensity(c, x, y, z);
|
|
536
|
+
}
|
|
537
|
+
onStartControls() {
|
|
538
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
539
|
+
this.volumeRendering.onStartControls();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
onChangeControls() {
|
|
543
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
544
|
+
this.volumeRendering.onChangeControls();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
onEndControls() {
|
|
548
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
549
|
+
this.volumeRendering.onEndControls();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
onResetCamera() {
|
|
553
|
+
this.volumeRendering.viewpointMoved();
|
|
554
|
+
}
|
|
555
|
+
onCameraChanged(fov, focalDistance, apertureSize) {
|
|
556
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
557
|
+
this.volumeRendering.updateCamera(fov, focalDistance, apertureSize);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// values are in 0..1 range
|
|
562
|
+
updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax) {
|
|
563
|
+
this.settings.bounds.bmin = new Vector3(xmin - 0.5, ymin - 0.5, zmin - 0.5);
|
|
564
|
+
this.settings.bounds.bmax = new Vector3(xmax - 0.5, ymax - 0.5, zmax - 0.5);
|
|
565
|
+
this.meshVolume.updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax);
|
|
566
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.ROI);
|
|
567
|
+
}
|
|
568
|
+
updateLights(state) {
|
|
569
|
+
if (this.renderMode === RenderMode.PATHTRACE) {
|
|
570
|
+
this.volumeRendering.updateLights(state);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
setPixelSamplingRate(value) {
|
|
574
|
+
this.settings.pixelSamplingRate = value;
|
|
575
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.SAMPLING);
|
|
576
|
+
}
|
|
577
|
+
setVolumeRendering(newRenderMode) {
|
|
578
|
+
// Skip reassignment of Pathtrace renderer if already using
|
|
579
|
+
if (newRenderMode === RenderMode.PATHTRACE && this.renderMode === RenderMode.PATHTRACE) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// remove old 3d object from scene
|
|
584
|
+
if (this.renderMode === RenderMode.SLICE || this.renderMode === RenderMode.RAYMARCH) {
|
|
585
|
+
this.sceneRoot.remove(this.meshVolume.get3dObject());
|
|
586
|
+
}
|
|
587
|
+
this.sceneRoot.remove(this.volumeRendering.get3dObject());
|
|
588
|
+
|
|
589
|
+
// destroy old resources.
|
|
590
|
+
this.volumeRendering.cleanup();
|
|
591
|
+
|
|
592
|
+
// create new
|
|
593
|
+
switch (newRenderMode) {
|
|
594
|
+
case RenderMode.PATHTRACE:
|
|
595
|
+
this.volumeRendering = new PathTracedVolume(this.volume, this.settings);
|
|
596
|
+
this.volume.updateRequiredData({
|
|
597
|
+
subregion: new Box3(new Vector3(0, 0, 0), new Vector3(1, 1, 1))
|
|
598
|
+
});
|
|
599
|
+
this.volumeRendering.setRenderUpdateListener(this.renderUpdateListener);
|
|
600
|
+
break;
|
|
601
|
+
case RenderMode.SLICE:
|
|
602
|
+
this.volumeRendering = new Atlas2DSlice(this.volume, this.settings);
|
|
603
|
+
// `updateRequiredData` called on construction, via `updateSettings`
|
|
604
|
+
break;
|
|
605
|
+
case RenderMode.RAYMARCH:
|
|
606
|
+
default:
|
|
607
|
+
this.volumeRendering = new RayMarchedAtlasVolume(this.volume, this.settings);
|
|
608
|
+
this.volume.updateRequiredData({
|
|
609
|
+
subregion: new Box3(new Vector3(0, 0, 0), new Vector3(1, 1, 1))
|
|
610
|
+
});
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
if (newRenderMode === RenderMode.RAYMARCH || newRenderMode === RenderMode.SLICE) {
|
|
614
|
+
if (this.renderUpdateListener) {
|
|
615
|
+
this.renderUpdateListener(0);
|
|
616
|
+
}
|
|
617
|
+
this.sceneRoot.add(this.meshVolume.get3dObject());
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// add new 3d object to scene
|
|
621
|
+
this.sceneRoot.add(this.volumeRendering.get3dObject());
|
|
622
|
+
this.renderMode = newRenderMode;
|
|
623
|
+
this.fuse();
|
|
624
|
+
}
|
|
625
|
+
setTranslation(xyz) {
|
|
626
|
+
this.settings.translation.copy(xyz);
|
|
627
|
+
this.meshVolume.setTranslation(this.settings.translation);
|
|
628
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
629
|
+
}
|
|
630
|
+
setRotation(eulerXYZ) {
|
|
631
|
+
this.settings.rotation.copy(eulerXYZ);
|
|
632
|
+
this.meshVolume.setRotation(this.settings.rotation);
|
|
633
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
634
|
+
}
|
|
635
|
+
setScale(xyz) {
|
|
636
|
+
this.settings.scale.copy(xyz);
|
|
637
|
+
this.updateScale();
|
|
638
|
+
}
|
|
639
|
+
setupGui(pane) {
|
|
640
|
+
pane.addInput(this.settings, "translation").on("change", ({
|
|
641
|
+
value
|
|
642
|
+
}) => this.setTranslation(value));
|
|
643
|
+
pane.addInput(this.settings, "rotation").on("change", ({
|
|
644
|
+
value
|
|
645
|
+
}) => this.setRotation(value));
|
|
646
|
+
const scaleFolder = pane.addFolder({
|
|
647
|
+
title: "Multiscale loading"
|
|
648
|
+
});
|
|
649
|
+
// global setting to define texture size (aka gpu memory footprint per-channel)
|
|
650
|
+
scaleFolder.addInput(this.volume.loadSpecRequired, "maxAtlasEdge").on("change", ({
|
|
651
|
+
value
|
|
652
|
+
}) => this.volume.updateRequiredData({
|
|
653
|
+
maxAtlasEdge: value
|
|
654
|
+
}));
|
|
655
|
+
// explicit or automatic???
|
|
656
|
+
scaleFolder.addInput(this.volume.loadSpecRequired, "useExplicitLevel").on("change", ({
|
|
657
|
+
value
|
|
658
|
+
}) => this.volume.updateRequiredData({
|
|
659
|
+
useExplicitLevel: value
|
|
660
|
+
}));
|
|
661
|
+
// only relevant when not using explicit level
|
|
662
|
+
scaleFolder.addInput(this.volume.loadSpecRequired, "scaleLevelBias").on("change", ({
|
|
663
|
+
value
|
|
664
|
+
}) => this.volume.updateRequiredData({
|
|
665
|
+
scaleLevelBias: value
|
|
666
|
+
}));
|
|
667
|
+
// which level to load
|
|
668
|
+
scaleFolder.addInput(this.volume.loadSpecRequired, "multiscaleLevel").on("change", ({
|
|
669
|
+
value
|
|
670
|
+
}) => this.volume.updateRequiredData({
|
|
671
|
+
multiscaleLevel: value
|
|
672
|
+
}));
|
|
673
|
+
const pathtrace = pane.addFolder({
|
|
674
|
+
title: "Pathtrace",
|
|
675
|
+
expanded: false
|
|
676
|
+
});
|
|
677
|
+
pathtrace.addInput(this.settings, "primaryRayStepSize", {
|
|
678
|
+
min: 1,
|
|
679
|
+
max: 100
|
|
680
|
+
}).on("change", ({
|
|
681
|
+
value
|
|
682
|
+
}) => this.setRayStepSizes(value));
|
|
683
|
+
pathtrace.addInput(this.settings, "secondaryRayStepSize", {
|
|
684
|
+
min: 1,
|
|
685
|
+
max: 100
|
|
686
|
+
}).on("change", ({
|
|
687
|
+
value
|
|
688
|
+
}) => this.setRayStepSizes(undefined, value));
|
|
689
|
+
}
|
|
690
|
+
setZSlice(slice) {
|
|
691
|
+
const sizez = this.volume.imageInfo.volumeSize.z;
|
|
692
|
+
if (this.settings.zSlice !== slice && slice < sizez && slice >= 0) {
|
|
693
|
+
this.settings.zSlice = slice;
|
|
694
|
+
this.volumeRendering.updateSettings(this.settings, SettingsFlags.ROI);
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
get showBoundingBox() {
|
|
700
|
+
return this.settings.showBoundingBox;
|
|
701
|
+
}
|
|
702
|
+
}
|