@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.
Files changed (141) hide show
  1. package/LICENSE.txt +26 -0
  2. package/README.md +119 -0
  3. package/es/Atlas2DSlice.js +224 -0
  4. package/es/Channel.js +264 -0
  5. package/es/FileSaver.js +31 -0
  6. package/es/FusedChannelData.js +192 -0
  7. package/es/Histogram.js +250 -0
  8. package/es/ImageInfo.js +127 -0
  9. package/es/Light.js +74 -0
  10. package/es/Lut.js +500 -0
  11. package/es/MarchingCubes.js +507 -0
  12. package/es/MeshVolume.js +334 -0
  13. package/es/NaiveSurfaceNets.js +251 -0
  14. package/es/PathTracedVolume.js +482 -0
  15. package/es/RayMarchedAtlasVolume.js +250 -0
  16. package/es/RenderToBuffer.js +31 -0
  17. package/es/ThreeJsPanel.js +633 -0
  18. package/es/Timing.js +28 -0
  19. package/es/TrackballControls.js +538 -0
  20. package/es/View3d.js +848 -0
  21. package/es/Volume.js +352 -0
  22. package/es/VolumeCache.js +161 -0
  23. package/es/VolumeDims.js +16 -0
  24. package/es/VolumeDrawable.js +702 -0
  25. package/es/VolumeMaker.js +101 -0
  26. package/es/VolumeRenderImpl.js +1 -0
  27. package/es/VolumeRenderSettings.js +203 -0
  28. package/es/constants/basicShaders.js +29 -0
  29. package/es/constants/colors.js +59 -0
  30. package/es/constants/denoiseShader.js +43 -0
  31. package/es/constants/lights.js +42 -0
  32. package/es/constants/materials.js +85 -0
  33. package/es/constants/pathtraceOutputShader.js +13 -0
  34. package/es/constants/scaleBarSVG.js +21 -0
  35. package/es/constants/time.js +34 -0
  36. package/es/constants/volumePTshader.js +153 -0
  37. package/es/constants/volumeRayMarchShader.js +123 -0
  38. package/es/constants/volumeSliceShader.js +115 -0
  39. package/es/index.js +21 -0
  40. package/es/loaders/IVolumeLoader.js +131 -0
  41. package/es/loaders/JsonImageInfoLoader.js +255 -0
  42. package/es/loaders/OmeZarrLoader.js +495 -0
  43. package/es/loaders/OpenCellLoader.js +65 -0
  44. package/es/loaders/RawArrayLoader.js +89 -0
  45. package/es/loaders/TiffLoader.js +219 -0
  46. package/es/loaders/VolumeLoadError.js +44 -0
  47. package/es/loaders/VolumeLoaderUtils.js +221 -0
  48. package/es/loaders/index.js +40 -0
  49. package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
  50. package/es/loaders/zarr_utils/WrappedStore.js +51 -0
  51. package/es/loaders/zarr_utils/types.js +24 -0
  52. package/es/loaders/zarr_utils/utils.js +225 -0
  53. package/es/loaders/zarr_utils/validation.js +49 -0
  54. package/es/test/ChunkPrefetchIterator.test.js +208 -0
  55. package/es/test/RequestQueue.test.js +442 -0
  56. package/es/test/SubscribableRequestQueue.test.js +244 -0
  57. package/es/test/VolumeCache.test.js +118 -0
  58. package/es/test/VolumeRenderSettings.test.js +71 -0
  59. package/es/test/lut.test.js +671 -0
  60. package/es/test/num_utils.test.js +140 -0
  61. package/es/test/volume.test.js +98 -0
  62. package/es/test/zarr_utils.test.js +358 -0
  63. package/es/types/Atlas2DSlice.d.ts +41 -0
  64. package/es/types/Channel.d.ts +44 -0
  65. package/es/types/FileSaver.d.ts +6 -0
  66. package/es/types/FusedChannelData.d.ts +26 -0
  67. package/es/types/Histogram.d.ts +57 -0
  68. package/es/types/ImageInfo.d.ts +87 -0
  69. package/es/types/Light.d.ts +27 -0
  70. package/es/types/Lut.d.ts +67 -0
  71. package/es/types/MarchingCubes.d.ts +53 -0
  72. package/es/types/MeshVolume.d.ts +40 -0
  73. package/es/types/NaiveSurfaceNets.d.ts +11 -0
  74. package/es/types/PathTracedVolume.d.ts +65 -0
  75. package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
  76. package/es/types/RenderToBuffer.d.ts +17 -0
  77. package/es/types/ThreeJsPanel.d.ts +107 -0
  78. package/es/types/Timing.d.ts +11 -0
  79. package/es/types/TrackballControls.d.ts +51 -0
  80. package/es/types/View3d.d.ts +357 -0
  81. package/es/types/Volume.d.ts +152 -0
  82. package/es/types/VolumeCache.d.ts +43 -0
  83. package/es/types/VolumeDims.d.ts +28 -0
  84. package/es/types/VolumeDrawable.d.ts +108 -0
  85. package/es/types/VolumeMaker.d.ts +49 -0
  86. package/es/types/VolumeRenderImpl.d.ts +22 -0
  87. package/es/types/VolumeRenderSettings.d.ts +98 -0
  88. package/es/types/constants/basicShaders.d.ts +4 -0
  89. package/es/types/constants/colors.d.ts +2 -0
  90. package/es/types/constants/denoiseShader.d.ts +40 -0
  91. package/es/types/constants/lights.d.ts +38 -0
  92. package/es/types/constants/materials.d.ts +20 -0
  93. package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
  94. package/es/types/constants/scaleBarSVG.d.ts +2 -0
  95. package/es/types/constants/time.d.ts +19 -0
  96. package/es/types/constants/volumePTshader.d.ts +137 -0
  97. package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
  98. package/es/types/constants/volumeSliceShader.d.ts +109 -0
  99. package/es/types/glsl.d.js +0 -0
  100. package/es/types/index.d.ts +28 -0
  101. package/es/types/loaders/IVolumeLoader.d.ts +113 -0
  102. package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
  103. package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
  104. package/es/types/loaders/OpenCellLoader.d.ts +9 -0
  105. package/es/types/loaders/RawArrayLoader.d.ts +33 -0
  106. package/es/types/loaders/TiffLoader.d.ts +45 -0
  107. package/es/types/loaders/VolumeLoadError.d.ts +18 -0
  108. package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
  109. package/es/types/loaders/index.d.ts +22 -0
  110. package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
  111. package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
  112. package/es/types/loaders/zarr_utils/types.d.ts +94 -0
  113. package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
  114. package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
  115. package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
  116. package/es/types/test/RequestQueue.test.d.ts +1 -0
  117. package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
  118. package/es/types/test/VolumeCache.test.d.ts +1 -0
  119. package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
  120. package/es/types/test/lut.test.d.ts +1 -0
  121. package/es/types/test/num_utils.test.d.ts +1 -0
  122. package/es/types/test/volume.test.d.ts +1 -0
  123. package/es/types/test/zarr_utils.test.d.ts +1 -0
  124. package/es/types/types.d.ts +115 -0
  125. package/es/types/utils/RequestQueue.d.ts +112 -0
  126. package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
  127. package/es/types/utils/num_utils.d.ts +43 -0
  128. package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
  129. package/es/types/workers/types.d.ts +101 -0
  130. package/es/types/workers/util.d.ts +3 -0
  131. package/es/types.js +75 -0
  132. package/es/typings.d.js +0 -0
  133. package/es/utils/RequestQueue.js +267 -0
  134. package/es/utils/SubscribableRequestQueue.js +187 -0
  135. package/es/utils/num_utils.js +231 -0
  136. package/es/workers/FetchTiffWorker.js +153 -0
  137. package/es/workers/VolumeLoadWorker.js +129 -0
  138. package/es/workers/VolumeLoaderContext.js +271 -0
  139. package/es/workers/types.js +41 -0
  140. package/es/workers/util.js +8 -0
  141. package/package.json +83 -0
package/es/View3d.js ADDED
@@ -0,0 +1,848 @@
1
+ import { AmbientLight, Vector3, Object3D, SpotLight, DirectionalLight, Euler, Scene, Color } from "three";
2
+ import { Pane } from "tweakpane";
3
+ import { MESH_LAYER, ThreeJsPanel } from "./ThreeJsPanel.js";
4
+ import lightSettings from "./constants/lights.js";
5
+ import VolumeDrawable from "./VolumeDrawable.js";
6
+ import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
7
+ import { isOrthographicCamera, ViewportCorner, RenderMode } from "./types.js";
8
+ import { Axis } from "./VolumeRenderSettings.js";
9
+ // Constants are kept for compatibility reasons.
10
+ export const RENDERMODE_RAYMARCH = RenderMode.RAYMARCH;
11
+ export const RENDERMODE_PATHTRACE = RenderMode.PATHTRACE;
12
+ const allGlobalLoadingOptions = {
13
+ numChunksToPrefetchAhead: 5,
14
+ prefetchAlongNonPlayingAxis: false,
15
+ throttleArrivingChannelData: true
16
+ };
17
+
18
+ /**
19
+ * @class
20
+ */
21
+ export class View3d {
22
+ // TODO because View3d is basically a top level entrypoint for Vol-E,
23
+ // maybe it should create the VolumeLoaderContext with options passed in.
24
+ // (instead of having the loaderContext created externally)
25
+
26
+ /**
27
+ * @param {Object} options Optional options.
28
+ * @param {boolean} options.useWebGL2 Default true
29
+ * @param {HTMLElement} options.parentElement An optional element to which to append the viewer element on creation.
30
+ * The viewer will attempt to fill this element if provided.
31
+ */
32
+ constructor(options) {
33
+ const useWebGL2 = options?.useWebGL2 === undefined ? true : options.useWebGL2;
34
+ this.canvas3d = new ThreeJsPanel(options?.parentElement, useWebGL2);
35
+ this.redraw = this.redraw.bind(this);
36
+ this.scene = new Scene();
37
+ this.backgroundColor = new Color(0x000000);
38
+ this.lights = [];
39
+ this.pixelSamplingRate = 0.75;
40
+ this.exposure = 0.5;
41
+ this.volumeRenderMode = RenderMode.RAYMARCH;
42
+ window.addEventListener("resize", () => this.resize(null));
43
+ this.lightContainer = new Object3D();
44
+ this.ambientLight = new AmbientLight();
45
+ this.spotLight = new SpotLight();
46
+ this.reflectedLight = new DirectionalLight();
47
+ this.fillLight = new DirectionalLight();
48
+ this.buildScene();
49
+ this.tweakpane = null;
50
+ window.addEventListener("keydown", this.handleKeydown);
51
+ }
52
+
53
+ // prerender should be called on every redraw and should be the first thing done.
54
+ preRender() {
55
+ // TODO: if fps just updated and it's too low, do something:
56
+ // if (this.canvas3d.timer.lastFPS < 7 && this.canvas3d.timer.lastFPS > 0 && this.canvas3d.timer.frames === 0) {
57
+ // }
58
+
59
+ const lightContainer = this.scene.getObjectByName("lightContainer");
60
+ if (lightContainer) {
61
+ lightContainer.rotation.setFromRotationMatrix(this.canvas3d.camera.matrixWorld);
62
+ }
63
+ // keep the ortho scale up to date.
64
+ if (this.image && isOrthographicCamera(this.canvas3d.camera)) {
65
+ const {
66
+ top,
67
+ zoom
68
+ } = this.canvas3d.camera;
69
+ this.image.setOrthoScale(Math.abs(top) / zoom);
70
+ this.updateOrthoScaleBar(this.image.volume);
71
+ }
72
+ }
73
+ updateOrthoScaleBar(volume) {
74
+ this.canvas3d.updateOrthoScaleBar(volume.physicalScale, volume.imageInfo.spatialUnit);
75
+ }
76
+ updatePerspectiveScaleBar(volume) {
77
+ this.canvas3d.updatePerspectiveScaleBar(volume.tickMarkPhysicalLength, volume.imageInfo.spatialUnit);
78
+ }
79
+ updateTimestepIndicator(volume) {
80
+ const {
81
+ times,
82
+ timeScale,
83
+ timeUnit
84
+ } = volume.imageInfo;
85
+ const currentTime = volume.loadSpec.time;
86
+ this.canvas3d.updateTimestepIndicator(currentTime * timeScale, times * timeScale, timeUnit);
87
+ }
88
+
89
+ /**
90
+ * Capture the contents of this canvas to a data url
91
+ * @param {Object} dataurlcallback function to call when data url is ready; function accepts dataurl as string arg
92
+ */
93
+ capture(dataurlcallback) {
94
+ return this.canvas3d.requestCapture(dataurlcallback);
95
+ }
96
+ getDOMElement() {
97
+ return this.canvas3d.containerdiv;
98
+ }
99
+ getCameraState() {
100
+ return this.canvas3d.getCameraState();
101
+ }
102
+ setCameraState(transform) {
103
+ this.canvas3d.setCameraState(transform);
104
+ this.redraw();
105
+ }
106
+
107
+ /**
108
+ * Force a redraw.
109
+ */
110
+ redraw() {
111
+ this.canvas3d.redraw();
112
+ }
113
+ unsetImage() {
114
+ if (this.image) {
115
+ this.canvas3d.removeControlHandlers();
116
+ this.canvas3d.animateFuncs = [];
117
+ this.scene.remove(this.image.sceneRoot);
118
+ }
119
+ return this.image;
120
+ }
121
+
122
+ /**
123
+ * 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)
124
+ * @param {Volume} volume
125
+ * @param {VolumeDisplayOptions} options
126
+ */
127
+ addVolume(volume, options) {
128
+ volume.addVolumeDataObserver(this);
129
+ options = options || {};
130
+ options.renderMode = this.volumeRenderMode;
131
+ this.setImage(new VolumeDrawable(volume, options));
132
+ }
133
+
134
+ /**
135
+ * Apply a set of display options to a given channel of a volume
136
+ * @param {Volume} volume
137
+ * @param {number} channelIndex the channel index
138
+ * @param {VolumeChannelDisplayOptions} options
139
+ */
140
+ setVolumeChannelOptions(volume, channelIndex, options) {
141
+ this.image?.setChannelOptions(channelIndex, options);
142
+ this.redraw();
143
+ }
144
+
145
+ /**
146
+ * Apply a set of display options to the given volume
147
+ * @param {Volume} volume
148
+ * @param {VolumeDisplayOptions} options
149
+ */
150
+ setVolumeDisplayOptions(volume, options) {
151
+ this.image?.setOptions(options);
152
+ this.redraw();
153
+ }
154
+
155
+ /**
156
+ * Remove a volume image from the viewer. This will clean up the View3D's resources for the current volume
157
+ * @param {Volume} volume
158
+ */
159
+ removeVolume(volume) {
160
+ const oldImage = this.unsetImage();
161
+ if (oldImage) {
162
+ oldImage.cleanup();
163
+ }
164
+ if (volume) {
165
+ // assert oldImage.volume === volume!
166
+ volume.removeVolumeDataObserver(this);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Remove all volume images from the viewer.
172
+ */
173
+ removeAllVolumes() {
174
+ if (this.image) {
175
+ this.removeVolume(this.image.volume);
176
+ }
177
+ this.image = undefined;
178
+ }
179
+
180
+ /**
181
+ * @param {function} callback a function that will receive the number of render iterations when it changes
182
+ */
183
+ setRenderUpdateListener(callback) {
184
+ this.renderUpdateListener = callback;
185
+ this.image?.setRenderUpdateListener(callback);
186
+ }
187
+
188
+ // channels is an array of channel indices for which new data just arrived.
189
+ onVolumeData(volume, channels) {
190
+ this.image?.updateScale();
191
+ this.image?.onChannelLoaded(channels);
192
+ if (volume.isLoaded() && this.tweakpane) {
193
+ this.tweakpane.refresh();
194
+ }
195
+ }
196
+
197
+ // do fixups for when the volume has had a new empty channel added.
198
+ onVolumeChannelAdded(volume, newChannelIndex) {
199
+ this.image?.onChannelAdded(newChannelIndex);
200
+ }
201
+ onVolumeLoadError(volume, error) {
202
+ this.loadErrorHandler?.(volume, error);
203
+ }
204
+ setLoadErrorHandler(handler) {
205
+ this.loadErrorHandler = handler;
206
+ }
207
+ setTime(volume, time, onChannelLoaded) {
208
+ const timeClamped = Math.max(0, Math.min(time, volume.imageInfo.times - 1));
209
+ volume.updateRequiredData({
210
+ time: timeClamped
211
+ }, onChannelLoaded);
212
+ this.updateTimestepIndicator(volume);
213
+ }
214
+
215
+ /**
216
+ * Nudge the scale level loaded into this volume off the one chosen by the loader.
217
+ * E.g. a bias of `1` will load 1 scale level lower than "ideal."
218
+ */
219
+ setScaleLevelBias(volume, scaleLevelBias) {
220
+ volume.updateRequiredData({
221
+ scaleLevelBias
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Assign a channel index as a mask channel (will multiply its color against the entire visible volume)
227
+ * @param {Object} volume
228
+ * @param {number} maskChannelIndex
229
+ */
230
+ setVolumeChannelAsMask(volume, maskChannelIndex) {
231
+ this.image?.setChannelAsMask(maskChannelIndex);
232
+ this.redraw();
233
+ }
234
+
235
+ /**
236
+ * Set voxel dimensions - controls volume scaling. For example, the physical measurements of the voxels from a biological data set
237
+ * @param {Object} volume
238
+ * @param {number} values Array of x,y,z floating point values for the physical voxel size scaling
239
+ * @param {string} unit The unit of `values`, if different than previous
240
+ */
241
+ setVoxelSize(volume, values, unit) {
242
+ if (this.image) {
243
+ this.image.setVoxelSize(new Vector3().fromArray(values));
244
+ if (unit) {
245
+ this.image.volume.setUnitSymbol(unit);
246
+ }
247
+ this.updatePerspectiveScaleBar(this.image.volume);
248
+ }
249
+ this.redraw();
250
+ }
251
+ setRayStepSizes(volume, primary, secondary) {
252
+ this.image?.setRayStepSizes(primary, secondary);
253
+ this.redraw();
254
+ }
255
+ setShowBoundingBox(volume, showBoundingBox) {
256
+ this.image?.setShowBoundingBox(showBoundingBox);
257
+ this.canvas3d.setShowPerspectiveScaleBar(showBoundingBox && this.canvas3d.showOrthoScaleBar && this.volumeRenderMode !== RenderMode.PATHTRACE);
258
+ this.redraw();
259
+ }
260
+ setBoundingBoxColor(volume, color) {
261
+ this.image?.setBoundingBoxColor(color);
262
+ this.canvas3d.setPerspectiveScaleBarColor(color);
263
+ this.redraw();
264
+ }
265
+ setBackgroundColor(color) {
266
+ const c = new Color().fromArray(color);
267
+ this.backgroundColor = c;
268
+ this.canvas3d.setClearColor(c, 1);
269
+ this.redraw();
270
+ }
271
+
272
+ /**
273
+ * Is an isosurface already created for this channel?
274
+ * @param {Object} volume
275
+ * @param {number} channel
276
+ * @return true if there is currently a mesh isosurface for this channel
277
+ */
278
+ hasIsosurface(volume, channel) {
279
+ return this.image?.hasIsosurface(channel) || false;
280
+ }
281
+
282
+ /**
283
+ * 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.
284
+ * @param {Object} volume
285
+ * @param {number} channelIndex
286
+ * @param {string} type Either 'GLTF' or 'STL'
287
+ */
288
+ saveChannelIsosurface(volume, channelIndex, type) {
289
+ this.image?.saveChannelIsosurface(channelIndex, type);
290
+ }
291
+
292
+ // Add a new volume image to the viewer. The viewer currently only supports a single image at a time, and will return any prior existing image.
293
+ setImage(img) {
294
+ const oldImage = this.unsetImage();
295
+ this.image = img;
296
+ this.scene.add(img.sceneRoot);
297
+
298
+ // new image picks up current settings
299
+ this.image.setResolution(this.canvas3d.getWidth(), this.canvas3d.getHeight());
300
+ this.image.setIsOrtho(isOrthographicCamera(this.canvas3d.camera));
301
+ this.image.setBrightness(this.exposure);
302
+ this.canvas3d.setControlHandlers(this.onStartControls.bind(this), this.onChangeControls.bind(this), this.onEndControls.bind(this));
303
+ this.canvas3d.animateFuncs.push(this.preRender.bind(this));
304
+ this.canvas3d.animateFuncs.push(img.onAnimate.bind(img));
305
+ this.updatePerspectiveScaleBar(img.volume);
306
+ this.updateTimestepIndicator(img.volume);
307
+
308
+ // redraw if not already in draw loop
309
+ this.redraw();
310
+ return oldImage;
311
+ }
312
+ onStartControls() {
313
+ if (this.volumeRenderMode !== RenderMode.PATHTRACE) {
314
+ // TODO: VR display requires a running renderloop
315
+ this.canvas3d.startRenderLoop();
316
+ }
317
+ this.image?.onStartControls();
318
+ }
319
+ onChangeControls() {
320
+ this.image?.onChangeControls();
321
+ }
322
+ onEndControls() {
323
+ this.image?.onEndControls();
324
+ // If we are pathtracing or autorotating, then keep rendering. Otherwise stop now.
325
+ if (this.volumeRenderMode !== RenderMode.PATHTRACE && !this.canvas3d.controls.autoRotate) {
326
+ // TODO: VR display requires a running renderloop
327
+ this.canvas3d.stopRenderLoop();
328
+ }
329
+ // force a redraw. This mainly fixes a bug with the way TrackballControls deals with wheel events.
330
+ this.redraw();
331
+ }
332
+ buildScene() {
333
+ this.scene = this.canvas3d.scene;
334
+
335
+ // background color
336
+ this.canvas3d.setClearColor(this.backgroundColor, 1.0);
337
+ this.lights = [new Light(SKY_LIGHT), new Light(AREA_LIGHT)];
338
+ this.lightContainer = new Object3D();
339
+ this.lightContainer.name = "lightContainer";
340
+ this.ambientLight = new AmbientLight(lightSettings.ambientLightSettings.color, lightSettings.ambientLightSettings.intensity);
341
+ this.ambientLight.layers.enable(MESH_LAYER);
342
+ this.lightContainer.add(this.ambientLight);
343
+
344
+ // key light
345
+ this.spotLight = new SpotLight(lightSettings.spotlightSettings.color, lightSettings.spotlightSettings.intensity);
346
+ this.spotLight.position.set(lightSettings.spotlightSettings.position.x, lightSettings.spotlightSettings.position.y, lightSettings.spotlightSettings.position.z);
347
+ this.spotLight.target = new Object3D(); // this.substrate;
348
+ this.spotLight.angle = lightSettings.spotlightSettings.angle;
349
+ this.spotLight.layers.enable(MESH_LAYER);
350
+ this.lightContainer.add(this.spotLight);
351
+
352
+ // reflect light
353
+ this.reflectedLight = new DirectionalLight(lightSettings.reflectedLightSettings.color);
354
+ this.reflectedLight.position.set(lightSettings.reflectedLightSettings.position.x, lightSettings.reflectedLightSettings.position.y, lightSettings.reflectedLightSettings.position.z);
355
+ this.reflectedLight.castShadow = lightSettings.reflectedLightSettings.castShadow;
356
+ this.reflectedLight.intensity = lightSettings.reflectedLightSettings.intensity;
357
+ this.reflectedLight.layers.enable(MESH_LAYER);
358
+ this.lightContainer.add(this.reflectedLight);
359
+
360
+ // fill light
361
+ this.fillLight = new DirectionalLight(lightSettings.fillLightSettings.color);
362
+ this.fillLight.position.set(lightSettings.fillLightSettings.position.x, lightSettings.fillLightSettings.position.y, lightSettings.fillLightSettings.position.z);
363
+ this.fillLight.castShadow = lightSettings.fillLightSettings.castShadow;
364
+ this.fillLight.intensity = lightSettings.fillLightSettings.intensity;
365
+ this.fillLight.layers.enable(MESH_LAYER);
366
+ this.lightContainer.add(this.fillLight);
367
+ this.scene.add(this.lightContainer);
368
+ }
369
+
370
+ // TODO: Change mode to an enum
371
+ /**
372
+ * Change the camera projection to look along an axis, or to view in a 3d perspective camera.
373
+ * @param {string} mode Mode can be "3D", or "XY" or "Z", or "YZ" or "X", or "XZ" or "Y". 3D is a perspective view, and all the others are orthographic projections
374
+ */
375
+ setCameraMode(mode) {
376
+ this.canvas3d.switchViewMode(mode);
377
+ this.image?.setViewMode(mode, this.volumeRenderMode);
378
+ this.image?.setIsOrtho(mode !== "3D");
379
+ this.canvas3d.redraw();
380
+ }
381
+ setZSlice(volume, slice) {
382
+ if (this.image?.setZSlice(slice)) {
383
+ this.canvas3d.redraw();
384
+ return true;
385
+ }
386
+ return false;
387
+ }
388
+
389
+ /**
390
+ * Enable or disable 3d axis display at lower left.
391
+ * @param {boolean} showAxis
392
+ */
393
+ setShowAxis(showAxis) {
394
+ this.canvas3d.showAxis = showAxis;
395
+ this.canvas3d.redraw();
396
+ }
397
+
398
+ /**
399
+ * Enable or disable scale indicators.
400
+ * @param showScaleBar
401
+ */
402
+ setShowScaleBar(showScaleBar) {
403
+ this.canvas3d.setShowOrthoScaleBar(showScaleBar);
404
+ this.canvas3d.setShowPerspectiveScaleBar(showScaleBar && !!this.image?.showBoundingBox && this.volumeRenderMode !== RenderMode.PATHTRACE);
405
+ }
406
+
407
+ /**
408
+ * Enable or disable time indicator.
409
+ * @param showTimestepIndicator
410
+ */
411
+ setShowTimestepIndicator(showIndicator) {
412
+ const times = this.image?.volume.imageInfo.times;
413
+ const hasTimes = !!times && times > 1;
414
+ this.canvas3d.setShowTimestepIndicator(showIndicator && hasTimes);
415
+ }
416
+
417
+ /**
418
+ * Set the position of the axis indicator, as a corner of the viewport and horizontal and vertical margins from the
419
+ * edge of the viewport.
420
+ * @param {number} marginX
421
+ * @param {number} marginY
422
+ * @param {string} [corner] The corner of the viewport in which the axis appears. Default: `"bottom_left"`.
423
+ * TypeScript users should use the `ViewportCorner` enum. Otherwise, corner is one of: `"top_left"`, `"top_right"`,
424
+ * `"bottom_left"`, `"bottom_right"`.
425
+ */
426
+ setAxisPosition(marginX, marginY, corner = ViewportCorner.BOTTOM_LEFT) {
427
+ this.canvas3d.setAxisPosition(marginX, marginY, corner);
428
+ if (this.canvas3d.showAxis) {
429
+ this.canvas3d.redraw();
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Set the position of the scale bar, as a corner of the viewport and horizontal and vertical margins from the edge
435
+ * of the viewport.
436
+ * @param {number} marginX
437
+ * @param {number} marginY
438
+ * @param {string} [corner] The corner of the viewport in which the scale bar appears. Default: `"bottom_right"`.
439
+ * TypeScript users should use the `ViewportCorner` enum. Otherwise, corner is one of: `"top_left"`, `"top_right"`,
440
+ * `"bottom_left"`, `"bottom_right"`.
441
+ */
442
+ setScaleBarPosition(marginX, marginY, corner = ViewportCorner.BOTTOM_RIGHT) {
443
+ this.canvas3d.setIndicatorPosition(false, marginX, marginY, corner);
444
+ }
445
+
446
+ /**
447
+ * Set the position of the time step indicator, as a corner of the viewport and horizontal and vertical margins from
448
+ * the edge of the viewport.
449
+ * @param {number} marginX
450
+ * @param {number} marginY
451
+ * @param {string} [corner] The corner of the viewport in which the scale bar appears. Default: `"bottom_right"`.
452
+ * TypeScript users should use the `ViewportCorner` enum. Otherwise, corner is one of: `"top_left"`, `"top_right"`,
453
+ * `"bottom_left"`, `"bottom_right"`.
454
+ */
455
+ setTimestepIndicatorPosition(marginX, marginY, corner = ViewportCorner.BOTTOM_RIGHT) {
456
+ this.canvas3d.setIndicatorPosition(true, marginX, marginY, corner);
457
+ }
458
+
459
+ /**
460
+ * Enable or disable a turntable rotation mode. The display will continuously spin about the vertical screen axis.
461
+ * @param {boolean} autorotate
462
+ */
463
+ setAutoRotate(autorotate) {
464
+ this.canvas3d.setAutoRotate(autorotate);
465
+ if (autorotate) {
466
+ this.onStartControls();
467
+ } else {
468
+ this.onEndControls();
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Set the unit symbol for the scale bar (e.g. µm)
474
+ * @param {string} unit
475
+ */
476
+ setScaleUnit(unit) {
477
+ if (this.image) {
478
+ this.image.volume.setUnitSymbol(unit);
479
+ this.updatePerspectiveScaleBar(this.image.volume);
480
+ if (isOrthographicCamera(this.canvas3d.camera)) {
481
+ this.updateOrthoScaleBar(this.image.volume);
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Invert axes of volume. -1 to invert, +1 NOT to invert.
488
+ * @param {Object} volume
489
+ * @param {number} flipX x axis sense
490
+ * @param {number} flipY y axis sense
491
+ * @param {number} flipZ z axis sense
492
+ */
493
+ setFlipVolume(volume, flipX, flipY, flipZ) {
494
+ this.image?.setFlipAxes(flipX, flipY, flipZ);
495
+ this.redraw();
496
+ }
497
+ setInterpolationEnabled(volume, active) {
498
+ this.image?.setInterpolationEnabled(active);
499
+ this.redraw();
500
+ }
501
+
502
+ /**
503
+ * Notify the view that it has been resized. This will automatically be connected to the window when the View3d is created.
504
+ * @param {HTMLElement=} comp Ignored.
505
+ * @param {number=} w Width, or parent element's offsetWidth if not specified.
506
+ * @param {number=} h Height, or parent element's offsetHeight if not specified.
507
+ * @param {number=} ow Ignored.
508
+ * @param {number=} oh Ignored.
509
+ * @param {Object=} eOpts Ignored.
510
+ */
511
+ resize(comp, w, h, ow, oh, eOpts) {
512
+ this.canvas3d.resize(comp, w, h, ow, oh, eOpts);
513
+ this.image?.setResolution(this.canvas3d.getWidth(), this.canvas3d.getHeight());
514
+ this.redraw();
515
+ }
516
+
517
+ /**
518
+ * Set the volume scattering density
519
+ * @param {Object} volume
520
+ * @param {number} density 0..1 UI slider value
521
+ */
522
+ updateDensity(volume, density) {
523
+ this.image?.setDensity(density);
524
+ this.redraw();
525
+ }
526
+
527
+ /**
528
+ * Set the shading method - applies to pathtraced render mode only
529
+ * @param {Object} volume
530
+ * @param {number} isbrdf 0: brdf, 1: isotropic phase function, 2: mixed
531
+ */
532
+ updateShadingMethod(volume, isbrdf) {
533
+ this.image?.updateShadingMethod(isbrdf);
534
+ }
535
+
536
+ /**
537
+ * Set gamma levels: this affects the transparency and brightness of the single pass ray march volume render
538
+ * @param {Object} volume
539
+ * @param {number} gmin
540
+ * @param {number} glevel
541
+ * @param {number} gmax
542
+ */
543
+ setGamma(volume, gmin, glevel, gmax) {
544
+ this.image?.setGamma(gmin, glevel, gmax);
545
+ this.redraw();
546
+ }
547
+
548
+ /**
549
+ * Set max projection on or off - applies to single pass raymarch render mode only
550
+ * @param {Object} volume
551
+ * @param {boolean} isMaxProject true for max project, false for regular volume ray march integration
552
+ */
553
+ setMaxProjectMode(volume, isMaxProject) {
554
+ this.image?.setMaxProjectMode(isMaxProject);
555
+ this.redraw();
556
+ }
557
+
558
+ /**
559
+ * Notify the view that the set of active volume channels has been modified.
560
+ * @param {Object} volume
561
+ */
562
+ updateActiveChannels(_volume) {
563
+ this.image?.fuse();
564
+ this.redraw();
565
+ }
566
+
567
+ /**
568
+ * Notify the view that transfer function lookup table data has been modified.
569
+ * @param {Object} volume
570
+ */
571
+ updateLuts(_volume) {
572
+ this.image?.updateLuts();
573
+ this.redraw();
574
+ }
575
+
576
+ /**
577
+ * Notify the view that color and appearance settings have been modified.
578
+ * @param {Object} volume
579
+ */
580
+ updateMaterial(_volume) {
581
+ this.image?.updateMaterial();
582
+ this.redraw();
583
+ }
584
+
585
+ /**
586
+ * Increase or decrease the overall brightness of the rendered image
587
+ * @param {number} e 0..1
588
+ */
589
+ updateExposure(e) {
590
+ this.exposure = e;
591
+ this.image?.setBrightness(e);
592
+ this.redraw();
593
+ }
594
+
595
+ /**
596
+ * Set camera focus properties.
597
+ * @param {number} fov Vertical field of view in degrees
598
+ * @param {number} focalDistance view-space units for center of focus
599
+ * @param {number} apertureSize view-space units for radius of camera aperture
600
+ */
601
+ updateCamera(fov, focalDistance, apertureSize) {
602
+ this.canvas3d.updateCameraFocus(fov, focalDistance, apertureSize);
603
+ this.image?.onCameraChanged(fov, focalDistance, apertureSize);
604
+ this.redraw();
605
+ }
606
+
607
+ /**
608
+ * Set clipping range (between 0 and 1, relative to bounds) for the current volume.
609
+ * @param {Object} volume
610
+ * @param {number} xmin 0..1, should be less than xmax
611
+ * @param {number} xmax 0..1, should be greater than xmin
612
+ * @param {number} ymin 0..1, should be less than ymax
613
+ * @param {number} ymax 0..1, should be greater than ymin
614
+ * @param {number} zmin 0..1, should be less than zmax
615
+ * @param {number} zmax 0..1, should be greater than zmin
616
+ */
617
+ updateClipRegion(volume, xmin, xmax, ymin, ymax, zmin, zmax) {
618
+ this.image?.updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax);
619
+ this.redraw();
620
+ }
621
+
622
+ /**
623
+ * Set clipping range (between 0 and 1) for a given axis.
624
+ * Calling this allows the rendering to compensate for changes in thickness in orthographic views that affect how bright the volume is.
625
+ * @param {Object} volume
626
+ * @param {string} axis x, y, or z axis
627
+ * @param {number} minval 0..1, should be less than maxval
628
+ * @param {number} maxval 0..1, should be greater than minval
629
+ * @param {boolean} isOrthoAxis is this an orthographic projection or just a clipping of the range for perspective view
630
+ */
631
+ setAxisClip(volume, axis, minval, maxval, isOrthoAxis) {
632
+ this.image?.setAxisClip(axis, minval, maxval, isOrthoAxis);
633
+ this.redraw();
634
+ }
635
+
636
+ /**
637
+ * Update lights
638
+ * @param {Array} state array of Lights
639
+ */
640
+ updateLights(state) {
641
+ // TODO flesh this out
642
+ this.lights = state;
643
+ this.image?.updateLights(state);
644
+ }
645
+
646
+ /**
647
+ * Set a sampling rate to trade performance for quality.
648
+ * @param {number} value (+epsilon..1) 1 is max quality, ~0.1 for lowest quality and highest speed
649
+ */
650
+ updatePixelSamplingRate(value) {
651
+ if (this.pixelSamplingRate === value) {
652
+ return;
653
+ }
654
+ this.pixelSamplingRate = value;
655
+ this.image?.setPixelSamplingRate(value);
656
+ }
657
+
658
+ /**
659
+ * Set the opacity of the mask channel
660
+ * @param {Object} volume
661
+ * @param {number} value (0..1) 0 for full transparent, 1 for fully opaque
662
+ */
663
+ updateMaskAlpha(volume, value) {
664
+ this.image?.setMaskAlpha(value);
665
+ this.redraw();
666
+ }
667
+
668
+ /**
669
+ * Show / hide volume channels
670
+ * @param {Object} volume
671
+ * @param {number} channel
672
+ * @param {boolean} enabled
673
+ */
674
+ setVolumeChannelEnabled(volume, channel, enabled) {
675
+ this.image?.setChannelOptions(channel, {
676
+ enabled
677
+ });
678
+ this.redraw();
679
+ }
680
+
681
+ /**
682
+ * Set the material for a channel
683
+ * @param {Object} volume
684
+ * @param {number} channelIndex
685
+ * @param {Array.<number>} colorrgb [r,g,b]
686
+ * @param {Array.<number>} specularrgb [r,g,b]
687
+ * @param {Array.<number>} emissivergb [r,g,b]
688
+ * @param {number} glossiness
689
+ */
690
+ updateChannelMaterial(volume, channelIndex, colorrgb, specularrgb, emissivergb, glossiness) {
691
+ this.image?.updateChannelMaterial(channelIndex, colorrgb, specularrgb, emissivergb, glossiness);
692
+ }
693
+
694
+ /**
695
+ * Set the color for a channel
696
+ * @param {Object} volume
697
+ * @param {number} channelIndex
698
+ * @param {Array.<number>} colorrgb [r,g,b]
699
+ */
700
+ updateChannelColor(volume, channelIndex, colorrgb) {
701
+ this.image?.updateChannelColor(channelIndex, colorrgb);
702
+ }
703
+
704
+ /**
705
+ * Switch between single pass ray-marched volume rendering and progressive path traced rendering.
706
+ * @param {RenderMode} mode RAYMARCH for single pass ray march, PATHTRACE for progressive path trace
707
+ */
708
+ setVolumeRenderMode(mode) {
709
+ if (mode === this.volumeRenderMode) {
710
+ return;
711
+ }
712
+ this.volumeRenderMode = mode;
713
+ if (this.image) {
714
+ const viewMode = this.image.getViewMode();
715
+ if (viewMode === Axis.Z) {
716
+ // if the camera view is in single-slice view, then we don't want to change
717
+ // anything but still remember the mode for when we switch back to a volumetric view
718
+ return;
719
+ } else if (mode === RenderMode.PATHTRACE && this.canvas3d.hasWebGL2) {
720
+ this.image.setVolumeRendering(RenderMode.PATHTRACE);
721
+ this.image.updateLights(this.lights);
722
+ // pathtrace is a continuous rendering mode
723
+ this.canvas3d.startRenderLoop();
724
+ } else if (mode === RenderMode.RAYMARCH) {
725
+ this.image.setVolumeRendering(RenderMode.RAYMARCH);
726
+ this.canvas3d.redraw();
727
+ }
728
+ this.updatePixelSamplingRate(this.pixelSamplingRate);
729
+ this.image.setIsOrtho(isOrthographicCamera(this.canvas3d.camera));
730
+ this.image.setResolution(this.canvas3d.getWidth(), this.canvas3d.getHeight());
731
+ this.setAutoRotate(this.canvas3d.controls.autoRotate);
732
+ this.image.setRenderUpdateListener(this.renderUpdateListener);
733
+ }
734
+
735
+ // TODO remove when pathtrace supports a bounding box
736
+ this.canvas3d.setShowPerspectiveScaleBar(this.canvas3d.showOrthoScaleBar && !!this.image?.showBoundingBox && mode !== RenderMode.PATHTRACE);
737
+ }
738
+
739
+ /**
740
+ *
741
+ * @param {Object} volume
742
+ * @param {Array.<number>} xyz
743
+ */
744
+ setVolumeTranslation(volume, xyz) {
745
+ this.image?.setTranslation(new Vector3().fromArray(xyz));
746
+ this.redraw();
747
+ }
748
+
749
+ /**
750
+ *
751
+ * @param {Object} volume
752
+ * @param {Array.<number>} eulerXYZ
753
+ */
754
+ setVolumeRotation(volume, eulerXYZ) {
755
+ this.image?.setRotation(new Euler().fromArray(eulerXYZ));
756
+ this.redraw();
757
+ }
758
+ setVolumeScale(volume, xyz) {
759
+ this.image?.setScale(new Vector3().fromArray(xyz));
760
+ this.redraw();
761
+ }
762
+
763
+ /**
764
+ * Reset the camera to its default position
765
+ */
766
+ resetCamera() {
767
+ this.canvas3d.resetCamera();
768
+ this.image?.onResetCamera();
769
+ this.redraw();
770
+ }
771
+ hasWebGL2() {
772
+ return this.canvas3d.hasWebGL2;
773
+ }
774
+ handleKeydown = event => {
775
+ // control-option-1 (mac) or ctrl-alt-1 (windows)
776
+ if (event.code === "Digit1" && event.altKey && event.ctrlKey) {
777
+ if (this.tweakpane) {
778
+ this.tweakpane.dispose();
779
+ this.tweakpane = null;
780
+ } else {
781
+ this.tweakpane = this.setupGui(this.canvas3d.containerdiv);
782
+ }
783
+ }
784
+ };
785
+ removeEventListeners() {
786
+ window.removeEventListener("keydown", this.handleKeydown);
787
+ }
788
+ setupGui(container) {
789
+ const pane = new Pane({
790
+ title: "Advanced Settings",
791
+ container
792
+ });
793
+ const paneStyle = {
794
+ position: "absolute",
795
+ top: "0",
796
+ right: "0"
797
+ };
798
+ Object.assign(pane.element.style, paneStyle);
799
+
800
+ // LIGHTS
801
+ const lights = pane.addFolder({
802
+ title: "Lights (isosurface)"
803
+ });
804
+ const addFolderForLight = (light, title) => {
805
+ const folder = lights.addFolder({
806
+ title,
807
+ expanded: false
808
+ });
809
+ folder.addInput(light, "color", {
810
+ color: {
811
+ type: "float"
812
+ }
813
+ });
814
+ folder.addInput(light, "intensity", {
815
+ min: 0
816
+ });
817
+ if (!light.isAmbientLight) {
818
+ folder.addInput(light, "position");
819
+ }
820
+ };
821
+ addFolderForLight(this.spotLight, "spot light");
822
+ addFolderForLight(this.ambientLight, "ambient light");
823
+ addFolderForLight(this.reflectedLight, "reflected light");
824
+ addFolderForLight(this.fillLight, "fill light");
825
+ this.image?.setupGui(pane);
826
+ const prefetch = pane.addFolder({
827
+ title: "Prefetch"
828
+ });
829
+ // one number will be used for all axis directions
830
+ prefetch.addInput(allGlobalLoadingOptions, "numChunksToPrefetchAhead").on("change", event => {
831
+ this.loaderContext?.getActiveLoader()?.updateFetchOptions({
832
+ maxPrefetchDistance: [event.value, event.value, event.value, event.value]
833
+ });
834
+ this.image?.volume.updateRequiredData({});
835
+ });
836
+ // should we try to prefetch along Z even if we are only playing along T?
837
+ prefetch.addInput(allGlobalLoadingOptions, "prefetchAlongNonPlayingAxis").on("change", event => {
838
+ this.loaderContext?.getActiveLoader()?.updateFetchOptions({
839
+ onlyPriorityDirections: !event.value
840
+ });
841
+ });
842
+ // when multiple prefetch frames arrive at once, should we slow down how quickly we load them?
843
+ prefetch.addInput(allGlobalLoadingOptions, "throttleArrivingChannelData").on("change", event => {
844
+ this.loaderContext?.setThrottleChannelData(event.value);
845
+ });
846
+ return pane;
847
+ }
848
+ }