@cornerstonejs/core 0.36.2 → 0.36.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 (233) hide show
  1. package/dist/cjs/loaders/volumeLoader.d.ts +1 -0
  2. package/dist/cjs/loaders/volumeLoader.js +5 -1
  3. package/dist/cjs/loaders/volumeLoader.js.map +1 -1
  4. package/dist/cjs/utilities/getSliceRange.js +3 -1
  5. package/dist/cjs/utilities/getSliceRange.js.map +1 -1
  6. package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js +7 -0
  7. package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
  8. package/dist/esm/loaders/volumeLoader.d.ts +1 -0
  9. package/dist/esm/loaders/volumeLoader.js +3 -0
  10. package/dist/esm/loaders/volumeLoader.js.map +1 -1
  11. package/dist/esm/utilities/getSliceRange.js +3 -1
  12. package/dist/esm/utilities/getSliceRange.js.map +1 -1
  13. package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +7 -0
  14. package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
  15. package/dist/umd/index.js +1 -1
  16. package/dist/umd/index.js.map +1 -1
  17. package/package.json +4 -3
  18. package/src/RenderingEngine/BaseVolumeViewport.ts +847 -0
  19. package/src/RenderingEngine/RenderingEngine.ts +1364 -0
  20. package/src/RenderingEngine/StackViewport.ts +2690 -0
  21. package/src/RenderingEngine/Viewport.ts +1244 -0
  22. package/src/RenderingEngine/VolumeViewport.ts +420 -0
  23. package/src/RenderingEngine/VolumeViewport3D.ts +42 -0
  24. package/src/RenderingEngine/getRenderingEngine.ts +34 -0
  25. package/src/RenderingEngine/helpers/addVolumesToViewports.ts +52 -0
  26. package/src/RenderingEngine/helpers/cpuFallback/colors/colormap.ts +343 -0
  27. package/src/RenderingEngine/helpers/cpuFallback/colors/index.ts +4 -0
  28. package/src/RenderingEngine/helpers/cpuFallback/colors/lookupTable.ts +469 -0
  29. package/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts +58 -0
  30. package/src/RenderingEngine/helpers/cpuFallback/rendering/calculateTransform.ts +136 -0
  31. package/src/RenderingEngine/helpers/cpuFallback/rendering/canvasToPixel.ts +25 -0
  32. package/src/RenderingEngine/helpers/cpuFallback/rendering/computeAutoVoi.ts +47 -0
  33. package/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts +38 -0
  34. package/src/RenderingEngine/helpers/cpuFallback/rendering/createViewport.ts +64 -0
  35. package/src/RenderingEngine/helpers/cpuFallback/rendering/doesImageNeedToBeRendered.ts +36 -0
  36. package/src/RenderingEngine/helpers/cpuFallback/rendering/fitToWindow.ts +22 -0
  37. package/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts +60 -0
  38. package/src/RenderingEngine/helpers/cpuFallback/rendering/generateLut.ts +83 -0
  39. package/src/RenderingEngine/helpers/cpuFallback/rendering/getDefaultViewport.ts +88 -0
  40. package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageFitScale.ts +52 -0
  41. package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageSize.ts +55 -0
  42. package/src/RenderingEngine/helpers/cpuFallback/rendering/getLut.ts +53 -0
  43. package/src/RenderingEngine/helpers/cpuFallback/rendering/getModalityLut.ts +55 -0
  44. package/src/RenderingEngine/helpers/cpuFallback/rendering/getTransform.ts +17 -0
  45. package/src/RenderingEngine/helpers/cpuFallback/rendering/getVOILut.ts +74 -0
  46. package/src/RenderingEngine/helpers/cpuFallback/rendering/initializeRenderCanvas.ts +37 -0
  47. package/src/RenderingEngine/helpers/cpuFallback/rendering/lutMatches.ts +21 -0
  48. package/src/RenderingEngine/helpers/cpuFallback/rendering/now.ts +13 -0
  49. package/src/RenderingEngine/helpers/cpuFallback/rendering/pixelToCanvas.ts +22 -0
  50. package/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts +193 -0
  51. package/src/RenderingEngine/helpers/cpuFallback/rendering/renderGrayscaleImage.ts +166 -0
  52. package/src/RenderingEngine/helpers/cpuFallback/rendering/renderPseudoColorImage.ts +203 -0
  53. package/src/RenderingEngine/helpers/cpuFallback/rendering/resetCamera.ts +32 -0
  54. package/src/RenderingEngine/helpers/cpuFallback/rendering/resize.ts +109 -0
  55. package/src/RenderingEngine/helpers/cpuFallback/rendering/saveLastRendered.ts +36 -0
  56. package/src/RenderingEngine/helpers/cpuFallback/rendering/setDefaultViewport.ts +17 -0
  57. package/src/RenderingEngine/helpers/cpuFallback/rendering/setToPixelCoordinateSystem.ts +32 -0
  58. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedColorPixelDataToCanvasImageData.ts +58 -0
  59. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageData.ts +76 -0
  60. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataColorLUT.ts +60 -0
  61. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPET.ts +50 -0
  62. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUT.ts +66 -0
  63. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUTPET.ts +68 -0
  64. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataRGBA.ts +81 -0
  65. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedRGBAPixelDataToCanvasImageData.ts +56 -0
  66. package/src/RenderingEngine/helpers/cpuFallback/rendering/transform.ts +126 -0
  67. package/src/RenderingEngine/helpers/cpuFallback/rendering/validator.ts +31 -0
  68. package/src/RenderingEngine/helpers/createVolumeActor.ts +103 -0
  69. package/src/RenderingEngine/helpers/createVolumeMapper.ts +37 -0
  70. package/src/RenderingEngine/helpers/getOrCreateCanvas.ts +58 -0
  71. package/src/RenderingEngine/helpers/index.ts +15 -0
  72. package/src/RenderingEngine/helpers/isRgbaSourceRgbDest.ts +1 -0
  73. package/src/RenderingEngine/helpers/setDefaultVolumeVOI.ts +227 -0
  74. package/src/RenderingEngine/helpers/setVolumesForViewports.ts +52 -0
  75. package/src/RenderingEngine/helpers/viewportTypeToViewportClass.ts +14 -0
  76. package/src/RenderingEngine/helpers/viewportTypeUsesCustomRenderingPipeline.ts +7 -0
  77. package/src/RenderingEngine/helpers/volumeNewImageEventDispatcher.ts +75 -0
  78. package/src/RenderingEngine/index.ts +23 -0
  79. package/src/RenderingEngine/renderingEngineCache.ts +43 -0
  80. package/src/RenderingEngine/vtkClasses/index.js +11 -0
  81. package/src/RenderingEngine/vtkClasses/vtkOffscreenMultiRenderWindow.js +149 -0
  82. package/src/RenderingEngine/vtkClasses/vtkSharedVolumeMapper.js +52 -0
  83. package/src/RenderingEngine/vtkClasses/vtkSlabCamera.d.ts +781 -0
  84. package/src/RenderingEngine/vtkClasses/vtkSlabCamera.js +155 -0
  85. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLRenderWindow.js +47 -0
  86. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLTexture.js +272 -0
  87. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLViewNodeFactory.js +159 -0
  88. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +319 -0
  89. package/src/Settings.ts +294 -0
  90. package/src/cache/cache.ts +854 -0
  91. package/src/cache/classes/Contour.ts +70 -0
  92. package/src/cache/classes/ContourSet.ts +151 -0
  93. package/src/cache/classes/ImageVolume.ts +155 -0
  94. package/src/cache/index.ts +5 -0
  95. package/src/constants/cpuColormaps.ts +1537 -0
  96. package/src/constants/epsilon.ts +3 -0
  97. package/src/constants/index.ts +13 -0
  98. package/src/constants/mprCameraValues.ts +20 -0
  99. package/src/constants/rendering.ts +8 -0
  100. package/src/constants/viewportPresets.ts +357 -0
  101. package/src/enums/BlendModes.ts +23 -0
  102. package/src/enums/ContourType.ts +6 -0
  103. package/src/enums/Events.ts +196 -0
  104. package/src/enums/GeometryType.ts +5 -0
  105. package/src/enums/InterpolationType.ts +13 -0
  106. package/src/enums/OrientationAxis.ts +8 -0
  107. package/src/enums/RequestType.ts +13 -0
  108. package/src/enums/SharedArrayBufferModes.ts +11 -0
  109. package/src/enums/VOILUTFunctionType.ts +10 -0
  110. package/src/enums/ViewportType.ts +21 -0
  111. package/src/enums/index.ts +23 -0
  112. package/src/eventTarget.ts +67 -0
  113. package/src/getEnabledElement.ts +105 -0
  114. package/src/global.ts +8 -0
  115. package/src/index.ts +123 -0
  116. package/src/init.ts +247 -0
  117. package/src/loaders/geometryLoader.ts +108 -0
  118. package/src/loaders/imageLoader.ts +298 -0
  119. package/src/loaders/volumeLoader.ts +477 -0
  120. package/src/metaData.ts +84 -0
  121. package/src/requestPool/imageLoadPoolManager.ts +43 -0
  122. package/src/requestPool/imageRetrievalPoolManager.ts +25 -0
  123. package/src/requestPool/requestPoolManager.ts +329 -0
  124. package/src/types/ActorSliceRange.ts +17 -0
  125. package/src/types/CPUFallbackColormap.ts +23 -0
  126. package/src/types/CPUFallbackColormapData.ts +12 -0
  127. package/src/types/CPUFallbackColormapsData.ts +7 -0
  128. package/src/types/CPUFallbackEnabledElement.ts +71 -0
  129. package/src/types/CPUFallbackLUT.ts +5 -0
  130. package/src/types/CPUFallbackLookupTable.ts +17 -0
  131. package/src/types/CPUFallbackRenderingTools.ts +25 -0
  132. package/src/types/CPUFallbackTransform.ts +16 -0
  133. package/src/types/CPUFallbackViewport.ts +29 -0
  134. package/src/types/CPUFallbackViewportDisplayedArea.ts +15 -0
  135. package/src/types/CPUIImageData.ts +47 -0
  136. package/src/types/ContourData.ts +19 -0
  137. package/src/types/Cornerstone3DConfig.ts +31 -0
  138. package/src/types/CustomEventType.ts +14 -0
  139. package/src/types/EventTypes.ts +403 -0
  140. package/src/types/FlipDirection.ts +9 -0
  141. package/src/types/IActor.ts +23 -0
  142. package/src/types/ICache.ts +28 -0
  143. package/src/types/ICachedGeometry.ts +13 -0
  144. package/src/types/ICachedImage.ts +13 -0
  145. package/src/types/ICachedVolume.ts +12 -0
  146. package/src/types/ICamera.ts +36 -0
  147. package/src/types/IContour.ts +18 -0
  148. package/src/types/IContourSet.ts +56 -0
  149. package/src/types/IDynamicImageVolume.ts +18 -0
  150. package/src/types/IEnabledElement.ts +21 -0
  151. package/src/types/IGeometry.ts +12 -0
  152. package/src/types/IImage.ts +113 -0
  153. package/src/types/IImageData.ts +45 -0
  154. package/src/types/IImageVolume.ts +78 -0
  155. package/src/types/ILoadObject.ts +36 -0
  156. package/src/types/IRegisterImageLoader.ts +10 -0
  157. package/src/types/IRenderingEngine.ts +28 -0
  158. package/src/types/IStackViewport.ts +138 -0
  159. package/src/types/IStreamingImageVolume.ts +13 -0
  160. package/src/types/IStreamingVolumeProperties.ts +14 -0
  161. package/src/types/IViewport.ts +149 -0
  162. package/src/types/IViewportId.ts +9 -0
  163. package/src/types/IVolume.ts +45 -0
  164. package/src/types/IVolumeInput.ts +36 -0
  165. package/src/types/IVolumeViewport.ts +141 -0
  166. package/src/types/ImageLoaderFn.ts +16 -0
  167. package/src/types/ImageSliceData.ts +6 -0
  168. package/src/types/Mat3.ts +16 -0
  169. package/src/types/Metadata.ts +39 -0
  170. package/src/types/OrientationVectors.ts +36 -0
  171. package/src/types/Plane.ts +6 -0
  172. package/src/types/Point2.ts +6 -0
  173. package/src/types/Point3.ts +6 -0
  174. package/src/types/Point4.ts +6 -0
  175. package/src/types/ScalingParameters.ts +27 -0
  176. package/src/types/StackViewportProperties.ts +25 -0
  177. package/src/types/TransformMatrix2D.ts +4 -0
  178. package/src/types/ViewportInputOptions.ts +21 -0
  179. package/src/types/ViewportPreset.ts +14 -0
  180. package/src/types/VolumeLoaderFn.ts +18 -0
  181. package/src/types/VolumeViewportProperties.ts +14 -0
  182. package/src/types/index.ts +157 -0
  183. package/src/types/voi.ts +15 -0
  184. package/src/utilities/actorCheck.ts +24 -0
  185. package/src/utilities/applyPreset.ts +132 -0
  186. package/src/utilities/calculateViewportsSpatialRegistration.ts +74 -0
  187. package/src/utilities/calibratedPixelSpacingMetadataProvider.ts +38 -0
  188. package/src/utilities/createFloat32SharedArray.ts +45 -0
  189. package/src/utilities/createInt16SharedArray.ts +43 -0
  190. package/src/utilities/createLinearRGBTransferFunction.ts +22 -0
  191. package/src/utilities/createSigmoidRGBTransferFunction.ts +63 -0
  192. package/src/utilities/createUInt16SharedArray.ts +43 -0
  193. package/src/utilities/createUint8SharedArray.ts +45 -0
  194. package/src/utilities/deepFreeze.ts +19 -0
  195. package/src/utilities/deepMerge.ts +81 -0
  196. package/src/utilities/getClosestImageId.ts +80 -0
  197. package/src/utilities/getClosestStackImageIndexForPoint.ts +116 -0
  198. package/src/utilities/getImageSliceDataForVolumeViewport.ts +61 -0
  199. package/src/utilities/getMinMax.ts +31 -0
  200. package/src/utilities/getRuntimeId.ts +54 -0
  201. package/src/utilities/getScalarDataType.ts +31 -0
  202. package/src/utilities/getScalingParameters.ts +35 -0
  203. package/src/utilities/getSliceRange.ts +86 -0
  204. package/src/utilities/getSpacingInNormalDirection.ts +44 -0
  205. package/src/utilities/getTargetVolumeAndSpacingInNormalDir.ts +126 -0
  206. package/src/utilities/getViewportImageCornersInWorld.ts +102 -0
  207. package/src/utilities/getViewportsWithImageURI.ts +46 -0
  208. package/src/utilities/getViewportsWithVolumeId.ts +38 -0
  209. package/src/utilities/getVoiFromSigmoidRGBTransferFunction.ts +23 -0
  210. package/src/utilities/getVolumeActorCorners.ts +24 -0
  211. package/src/utilities/getVolumeSliceRangeInfo.ts +52 -0
  212. package/src/utilities/getVolumeViewportScrollInfo.ts +32 -0
  213. package/src/utilities/getVolumeViewportsContainingSameVolumes.ts +58 -0
  214. package/src/utilities/hasNaNValues.ts +12 -0
  215. package/src/utilities/imageIdToURI.ts +10 -0
  216. package/src/utilities/imageToWorldCoords.ts +54 -0
  217. package/src/utilities/index.ts +100 -0
  218. package/src/utilities/indexWithinDimensions.ts +27 -0
  219. package/src/utilities/invertRgbTransferFunction.ts +36 -0
  220. package/src/utilities/isEqual.ts +27 -0
  221. package/src/utilities/isOpposite.ts +23 -0
  222. package/src/utilities/isTypedArray.ts +20 -0
  223. package/src/utilities/loadImageToCanvas.ts +80 -0
  224. package/src/utilities/planar.ts +91 -0
  225. package/src/utilities/renderToCanvas.ts +32 -0
  226. package/src/utilities/scaleRgbTransferFunction.ts +37 -0
  227. package/src/utilities/snapFocalPointToSlice.ts +78 -0
  228. package/src/utilities/spatialRegistrationMetadataProvider.ts +50 -0
  229. package/src/utilities/transformWorldToIndex.ts +16 -0
  230. package/src/utilities/triggerEvent.ts +38 -0
  231. package/src/utilities/uuidv4.ts +13 -0
  232. package/src/utilities/windowLevel.ts +39 -0
  233. package/src/utilities/worldToImageCoords.ts +64 -0
@@ -0,0 +1,1244 @@
1
+ import type { vtkCamera } from '@kitware/vtk.js/Rendering/Core/Camera';
2
+ import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
3
+ import vtkMath from '@kitware/vtk.js/Common/Core/Math';
4
+ import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
5
+
6
+ import { vec2, vec3 } from 'gl-matrix';
7
+ import _cloneDeep from 'lodash.clonedeep';
8
+
9
+ import Events from '../enums/Events';
10
+ import ViewportType from '../enums/ViewportType';
11
+ import renderingEngineCache from './renderingEngineCache';
12
+ import { triggerEvent, planar, isImageActor, actorIsA } from '../utilities';
13
+ import hasNaNValues from '../utilities/hasNaNValues';
14
+ import { RENDERING_DEFAULTS } from '../constants';
15
+ import type {
16
+ ICamera,
17
+ ActorEntry,
18
+ IRenderingEngine,
19
+ ViewportInputOptions,
20
+ Point2,
21
+ Point3,
22
+ FlipDirection,
23
+ EventTypes,
24
+ } from '../types';
25
+ import type { ViewportInput, IViewport } from '../types/IViewport';
26
+ import type { vtkSlabCamera } from './vtkClasses/vtkSlabCamera';
27
+ import { getConfiguration } from '../init';
28
+
29
+ /**
30
+ * An object representing a single viewport, which is a camera
31
+ * looking into a viewport, and an associated target output `HTMLDivElement`.
32
+ * Viewport is a base class that can be extended to create a specific
33
+ * viewport type. Both VolumeViewport and StackViewport are subclasses
34
+ * of Viewport. Common logic for all viewports is contained in Viewport class
35
+ * which is camera properties/methods, vtk.js actors, and other common
36
+ * logic.
37
+ */
38
+ class Viewport implements IViewport {
39
+ /** unique identifier for the viewport */
40
+ readonly id: string;
41
+ /** HTML element in DOM that is used for rendering the viewport */
42
+ readonly element: HTMLDivElement;
43
+ /** an internal canvas that is created on the provided HTML element */
44
+ readonly canvas: HTMLCanvasElement;
45
+ /** RenderingEngine id that the viewport belongs to */
46
+ readonly renderingEngineId: string;
47
+ /** Type of viewport */
48
+ readonly type: ViewportType;
49
+ protected flipHorizontal = false;
50
+ protected flipVertical = false;
51
+ public isDisabled: boolean;
52
+
53
+ /** sx of viewport on the offscreen canvas */
54
+ sx: number;
55
+ /** sy of viewport on the offscreen canvas */
56
+ sy: number;
57
+ /** sWidth of viewport on the offscreen canvas */
58
+ sWidth: number;
59
+ /** sHeight of viewport on the offscreen canvas */
60
+ sHeight: number;
61
+ /** a Map containing the actor uid and actors */
62
+ _actors: Map<string, any>;
63
+ /** Default options for the viewport which includes orientation, viewPlaneNormal and backgroundColor */
64
+ readonly defaultOptions: any;
65
+ /** options for the viewport which includes orientation axis and backgroundColor */
66
+ options: ViewportInputOptions;
67
+ private _suppressCameraModifiedEvents = false;
68
+ /** A flag representing if viewport methods should fire events or not */
69
+ readonly suppressEvents: boolean;
70
+ protected hasPixelSpacing = true;
71
+ /** The camera that is initially defined on the reset for
72
+ * the relative pan/zoom
73
+ */
74
+ protected initialCamera: ICamera;
75
+
76
+ constructor(props: ViewportInput) {
77
+ this.id = props.id;
78
+ this.renderingEngineId = props.renderingEngineId;
79
+ this.type = props.type;
80
+ this.element = props.element;
81
+ this.canvas = props.canvas;
82
+ this.sx = props.sx;
83
+ this.sy = props.sy;
84
+ this.sWidth = props.sWidth;
85
+ this.sHeight = props.sHeight;
86
+ this._actors = new Map();
87
+ // Set data attributes for render events
88
+ this.element.setAttribute('data-viewport-uid', this.id);
89
+ this.element.setAttribute(
90
+ 'data-rendering-engine-uid',
91
+ this.renderingEngineId
92
+ );
93
+
94
+ this.defaultOptions = _cloneDeep(props.defaultOptions);
95
+ this.suppressEvents = props.defaultOptions.suppressEvents
96
+ ? props.defaultOptions.suppressEvents
97
+ : false;
98
+ this.options = _cloneDeep(props.defaultOptions);
99
+ this.isDisabled = false;
100
+ }
101
+
102
+ getRotation: () => number;
103
+ getFrameOfReferenceUID: () => string;
104
+ canvasToWorld: (canvasPos: Point2) => Point3;
105
+ worldToCanvas: (worldPos: Point3) => Point2;
106
+ customRenderViewportToCanvas: () => unknown;
107
+ resize: () => void;
108
+ getProperties: () => void;
109
+
110
+ static get useCustomRenderingPipeline(): boolean {
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * Returns the rendering engine driving the `Viewport`.
116
+ *
117
+ * @returns The RenderingEngine instance.
118
+ */
119
+ public getRenderingEngine(): IRenderingEngine {
120
+ return renderingEngineCache.get(this.renderingEngineId);
121
+ }
122
+
123
+ /**
124
+ * Returns the `vtkRenderer` responsible for rendering the `Viewport`.
125
+ *
126
+ * @returns The `vtkRenderer` for the `Viewport`.
127
+ */
128
+ public getRenderer(): any {
129
+ const renderingEngine = this.getRenderingEngine();
130
+
131
+ if (!renderingEngine || renderingEngine.hasBeenDestroyed) {
132
+ throw new Error('Rendering engine has been destroyed');
133
+ }
134
+
135
+ return renderingEngine.offscreenMultiRenderWindow.getRenderer(this.id);
136
+ }
137
+
138
+ /**
139
+ * Renders the `Viewport` using the `RenderingEngine`.
140
+ */
141
+ public render(): void {
142
+ const renderingEngine = this.getRenderingEngine();
143
+
144
+ renderingEngine.renderViewport(this.id);
145
+ }
146
+
147
+ /**
148
+ * Sets new options and (TODO) applies them.
149
+ *
150
+ * @param options - The viewport options to set.
151
+ * @param immediate - If `true`, renders the viewport after the options are set.
152
+ */
153
+ public setOptions(options: ViewportInputOptions, immediate = false): void {
154
+ this.options = <ViewportInputOptions>_cloneDeep(options);
155
+
156
+ // TODO When this is needed we need to move the camera position.
157
+ // We can steal some logic from the tools we build to do this.
158
+
159
+ if (immediate) {
160
+ this.render();
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Resets the options the `Viewport`'s `defaultOptions`
166
+ *
167
+ * @param immediate - If `true`, renders the viewport after the options are reset.
168
+ */
169
+ public reset(immediate = false) {
170
+ this.options = _cloneDeep(this.defaultOptions);
171
+
172
+ // TODO When this is needed we need to move the camera position.
173
+ // We can steal some logic from the tools we build to do this.
174
+
175
+ if (immediate) {
176
+ this.render();
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Flip the viewport on horizontal or vertical axis, this method
182
+ * works with vtk-js backed rendering pipeline.
183
+ *
184
+ * @param flipOptions - Flip options specifying the axis of flip
185
+ * @param flipOptions.flipHorizontal - Flip the viewport on horizontal axis
186
+ * @param flipOptions.flipVertical - Flip the viewport on vertical axis
187
+ */
188
+ protected flip({ flipHorizontal, flipVertical }: FlipDirection): void {
189
+ const imageData = this.getDefaultImageData();
190
+
191
+ if (!imageData) {
192
+ return;
193
+ }
194
+
195
+ const camera = this.getCamera();
196
+ const { viewPlaneNormal, viewUp, focalPoint, position } = camera;
197
+
198
+ const viewRight = vec3.cross(vec3.create(), viewPlaneNormal, viewUp);
199
+ let viewUpToSet = vec3.copy(vec3.create(), viewUp);
200
+ const viewPlaneNormalToSet = vec3.negate(vec3.create(), viewPlaneNormal);
201
+
202
+ // for both flip horizontal and vertical we need to move the camera to the
203
+ // other side of the image
204
+ const distance = vec3.distance(position, focalPoint);
205
+
206
+ // If the pan has been applied, we need to be able
207
+ // apply the pan back
208
+ const dimensions = imageData.getDimensions();
209
+ const middleIJK = dimensions.map((d) => Math.floor(d / 2));
210
+
211
+ const idx = [middleIJK[0], middleIJK[1], middleIJK[2]];
212
+ const centeredFocalPoint = imageData.indexToWorld(idx, vec3.create());
213
+
214
+ const resetFocalPoint = this._getFocalPointForResetCamera(
215
+ centeredFocalPoint as Point3,
216
+ camera,
217
+ { resetPan: true, resetToCenter: false }
218
+ );
219
+
220
+ const panDir = vec3.subtract(vec3.create(), focalPoint, resetFocalPoint);
221
+ const panValue = vec3.length(panDir);
222
+
223
+ const getPanDir = (mirrorVec) => {
224
+ const panDirMirror = vec3.scale(
225
+ vec3.create(),
226
+ mirrorVec,
227
+ 2 * vec3.dot(panDir, mirrorVec)
228
+ );
229
+ vec3.subtract(panDirMirror, panDirMirror, panDir);
230
+ vec3.normalize(panDirMirror, panDirMirror);
231
+
232
+ return panDirMirror;
233
+ };
234
+
235
+ // Flipping horizontal mean that the camera should move
236
+ // to the other side of the image but looking at the
237
+ // same direction and same focal point
238
+ if (flipHorizontal) {
239
+ // we need to apply the pan value to the new focal point but in the direction
240
+ // that is mirrored on the viewUp for the flip horizontal and
241
+ // viewRight for the flip vertical
242
+
243
+ // mirror the pan direction based on the viewUp
244
+ const panDirMirror = getPanDir(viewUpToSet);
245
+
246
+ // move focal point from the resetFocalPoint to the newFocalPoint
247
+ // based on the panDirMirror and panValue
248
+ const newFocalPoint = vec3.scaleAndAdd(
249
+ vec3.create(),
250
+ resetFocalPoint,
251
+ panDirMirror,
252
+ panValue
253
+ );
254
+
255
+ // move the camera position also the same way as the focal point
256
+ const newPosition = vec3.scaleAndAdd(
257
+ vec3.create(),
258
+ newFocalPoint,
259
+ viewPlaneNormalToSet,
260
+ distance
261
+ );
262
+
263
+ this.setCamera({
264
+ viewPlaneNormal: viewPlaneNormalToSet as Point3,
265
+ position: newPosition as Point3,
266
+ focalPoint: newFocalPoint as Point3,
267
+ });
268
+
269
+ this.flipHorizontal = !this.flipHorizontal;
270
+ }
271
+
272
+ // Flipping vertical mean that the camera should negate the view up
273
+ // and also move to the other side of the image but looking at the
274
+ if (flipVertical) {
275
+ viewUpToSet = vec3.negate(viewUpToSet, viewUp);
276
+
277
+ // we need to apply the pan value to the new focal point but in the direction
278
+ const panDirMirror = getPanDir(viewRight);
279
+
280
+ const newFocalPoint = vec3.scaleAndAdd(
281
+ vec3.create(),
282
+ resetFocalPoint,
283
+ panDirMirror,
284
+ panValue
285
+ );
286
+
287
+ const newPosition = vec3.scaleAndAdd(
288
+ vec3.create(),
289
+ newFocalPoint,
290
+ viewPlaneNormalToSet,
291
+ distance
292
+ );
293
+
294
+ this.setCamera({
295
+ focalPoint: newFocalPoint as Point3,
296
+ viewPlaneNormal: viewPlaneNormalToSet as Point3,
297
+ viewUp: viewUpToSet as Point3,
298
+ position: newPosition as Point3,
299
+ });
300
+
301
+ this.flipVertical = !this.flipVertical;
302
+ }
303
+
304
+ this.render();
305
+ }
306
+
307
+ private getDefaultImageData(): any {
308
+ const actorEntry = this.getDefaultActor();
309
+
310
+ if (actorEntry && isImageActor(actorEntry)) {
311
+ return actorEntry.actor.getMapper().getInputData();
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Get the default actor
317
+ * @returns An actor entry.
318
+ */
319
+ public getDefaultActor(): ActorEntry {
320
+ return this.getActors()[0];
321
+ }
322
+
323
+ /**
324
+ * Get all the actors in the viewport
325
+ * @returns An array of ActorEntry objects.
326
+ */
327
+ public getActors(): Array<ActorEntry> {
328
+ return Array.from(this._actors.values());
329
+ }
330
+
331
+ /**
332
+ * Get an actor by its UID
333
+ * @param actorUID - The unique ID of the actor.
334
+ * @returns An ActorEntry object.
335
+ */
336
+ public getActor(actorUID: string): ActorEntry {
337
+ return this._actors.get(actorUID);
338
+ }
339
+
340
+ /**
341
+ * Get an actor UID by its index
342
+ * @param index - array index.
343
+ * @returns actorUID
344
+ */
345
+ public getActorUIDByIndex(index: number): string {
346
+ const actor = this.getActors()[index];
347
+ if (actor) {
348
+ return actor.uid;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Get an actor by its index
354
+ * @param index - array index.
355
+ * @returns actorUID
356
+ */
357
+ public getActorByIndex(index: number): ActorEntry {
358
+ return this.getActors()[index];
359
+ }
360
+
361
+ /**
362
+ * It removes all actors from the viewport and then adds the actors from the array.
363
+ * @param actors - An array of ActorEntry objects.
364
+ */
365
+ public setActors(actors: Array<ActorEntry>): void {
366
+ this.removeAllActors();
367
+ const resetCameraPanAndZoom = true;
368
+ // when we set the actor we need to reset the camera to initialize the
369
+ // camera focal point with the bounds of the actors.
370
+ this.addActors(actors, resetCameraPanAndZoom);
371
+ }
372
+
373
+ /**
374
+ * Remove the actor from the viewport
375
+ * @param actorUID - The unique identifier for the actor.
376
+ */
377
+ _removeActor(actorUID: string): void {
378
+ const actorEntry = this.getActor(actorUID);
379
+ if (!actorEntry) {
380
+ console.warn(`Actor ${actorUID} does not exist for this viewport`);
381
+ return;
382
+ }
383
+ const renderer = this.getRenderer();
384
+ renderer.removeViewProp(actorEntry.actor); // removeActor not implemented in vtk?
385
+ this._actors.delete(actorUID);
386
+ }
387
+
388
+ /**
389
+ * Remove the actors with the given UIDs from the viewport
390
+ * @param actorUIDs - An array of actor UIDs to remove.
391
+ */
392
+ public removeActors(actorUIDs: Array<string>): void {
393
+ actorUIDs.forEach((actorUID) => {
394
+ this._removeActor(actorUID);
395
+ });
396
+ }
397
+
398
+ /**
399
+ * Add a list of actors (actor entries) to the viewport
400
+ * @param resetCameraPanAndZoom - force reset pan and zoom of the camera,
401
+ * default value is false.
402
+ * @param actors - An array of ActorEntry objects.
403
+ */
404
+ public addActors(
405
+ actors: Array<ActorEntry>,
406
+ resetCameraPanAndZoom = false
407
+ ): void {
408
+ const renderingEngine = this.getRenderingEngine();
409
+ if (!renderingEngine || renderingEngine.hasBeenDestroyed) {
410
+ console.warn(
411
+ 'Viewport::addActors::Rendering engine has not been initialized or has been destroyed'
412
+ );
413
+ return;
414
+ }
415
+
416
+ actors.forEach((actor) => this.addActor(actor));
417
+
418
+ // set the clipping planes for the actors
419
+ this.resetCamera(resetCameraPanAndZoom, resetCameraPanAndZoom);
420
+ }
421
+
422
+ /**
423
+ * Add an actor to the viewport including its id, its actor and slabThickness
424
+ * if defined
425
+ * @param actorEntry - ActorEntry
426
+ * @param actorEntry.uid - The unique identifier for the actor.
427
+ * @param actorEntry.actor - The volume actor.
428
+ * @param actorEntry.slabThickness - The slab thickness.
429
+ */
430
+ public addActor(actorEntry: ActorEntry): void {
431
+ const { uid: actorUID, actor } = actorEntry;
432
+ const renderingEngine = this.getRenderingEngine();
433
+
434
+ if (!renderingEngine || renderingEngine.hasBeenDestroyed) {
435
+ console.warn(
436
+ `Cannot add actor UID of ${actorUID} Rendering Engine has been destroyed`
437
+ );
438
+ return;
439
+ }
440
+
441
+ if (!actorUID || !actor) {
442
+ throw new Error('Actors should have uid and vtk Actor properties');
443
+ }
444
+
445
+ if (this.getActor(actorUID)) {
446
+ console.warn(`Actor ${actorUID} already exists for this viewport`);
447
+ return;
448
+ }
449
+
450
+ const renderer = this.getRenderer();
451
+ renderer.addActor(actor);
452
+ this._actors.set(actorUID, Object.assign({}, actorEntry));
453
+ }
454
+
455
+ /**
456
+ * Remove all actors from the renderer
457
+ */
458
+ public removeAllActors(): void {
459
+ this.getRenderer().removeAllViewProps();
460
+ this._actors = new Map();
461
+ return;
462
+ }
463
+
464
+ /**
465
+ * Reset the camera to the default viewport camera without firing events
466
+ */
467
+ protected resetCameraNoEvent(): void {
468
+ this._suppressCameraModifiedEvents = true;
469
+ this.resetCamera();
470
+ this._suppressCameraModifiedEvents = false;
471
+ }
472
+
473
+ /**
474
+ * Sets the camera to the default viewport camera without firing events
475
+ * @param camera - The camera to use for the viewport.
476
+ */
477
+ protected setCameraNoEvent(camera: ICamera): void {
478
+ this._suppressCameraModifiedEvents = true;
479
+ this.setCamera(camera);
480
+ this._suppressCameraModifiedEvents = false;
481
+ }
482
+
483
+ /**
484
+ * Calculates the intersections between the volume's boundaries and the viewplane.
485
+ * 1) Determine the viewplane using the camera's ViewplaneNormal and focalPoint.
486
+ * 2) Using volumeBounds, calculate the line equation for the 3D volume's 12 edges.
487
+ * 3) Intersect each edge to the viewPlane and see whether the intersection point is inside the volume bounds.
488
+ * 4) Return list of intersection points
489
+ * It should be noted that intersection points may range from 3 to 6 points.
490
+ * Orthogonal views have four points of intersection.
491
+ *
492
+ * @param imageData - vtkImageData
493
+ * @param focalPoint - camera focal point
494
+ * @param normal - view plane normal
495
+ * @returns intersections list
496
+ */
497
+ private _getViewImageDataIntersections(imageData, focalPoint, normal) {
498
+ // Viewplane equation: Ax+By+Cz=D
499
+ const A = normal[0];
500
+ const B = normal[1];
501
+ const C = normal[2];
502
+ const D = A * focalPoint[0] + B * focalPoint[1] + C * focalPoint[2];
503
+
504
+ // Computing the edges of the 3D cube
505
+ const bounds = imageData.getBounds();
506
+ const edges = this._getEdges(bounds);
507
+
508
+ const intersections = [];
509
+
510
+ for (const edge of edges) {
511
+ // start point: [x0, y0, z0], end point: [x1, y1, z1]
512
+ const [[x0, y0, z0], [x1, y1, z1]] = edge;
513
+ // Check if the edge is parallel to plane
514
+ if (A * (x1 - x0) + B * (y1 - y0) + C * (z1 - z0) === 0) {
515
+ continue;
516
+ }
517
+ const intersectionPoint = planar.linePlaneIntersection(
518
+ [x0, y0, z0],
519
+ [x1, y1, z1],
520
+ [A, B, C, D]
521
+ );
522
+
523
+ if (this._isInBounds(intersectionPoint, bounds)) {
524
+ intersections.push(intersectionPoint);
525
+ }
526
+ }
527
+
528
+ return intersections;
529
+ }
530
+
531
+ /**
532
+ * Resets the camera based on the rendering volume(s) bounds. If
533
+ * resetPan and resetZoom are true it places the focal point at the center of
534
+ * the volume (or slice); otherwise, only the camera zoom and camera Pan or Zoom
535
+ * is reset for the current view.
536
+ * @param resetPan - If true, the camera focal point is reset to the center of the volume (slice)
537
+ * @param resetZoom - If true, the camera zoom is reset to the default zoom
538
+ * @param storeAsInitialCamera - If true, reset camera is stored as the initial camera (to allow differences to
539
+ * be detected for pan/zoom values)
540
+ * @returns boolean
541
+ */
542
+ protected resetCamera(
543
+ resetPan = true,
544
+ resetZoom = true,
545
+ resetToCenter = true,
546
+ storeAsInitialCamera = true
547
+ ): boolean {
548
+ const renderer = this.getRenderer();
549
+
550
+ // fix the flip right away, since we rely on the viewPlaneNormal and
551
+ // viewUp for later. Basically, we need to flip back if flipHorizontal
552
+ // is true or flipVertical is true
553
+ this.setCamera({
554
+ flipHorizontal: false,
555
+ flipVertical: false,
556
+ });
557
+
558
+ const previousCamera = _cloneDeep(this.getCamera());
559
+
560
+ const bounds = renderer.computeVisiblePropBounds();
561
+ const focalPoint = <Point3>[0, 0, 0];
562
+ const imageData = this.getDefaultImageData();
563
+
564
+ // Todo: remove this, this is just for tests passing
565
+ if (imageData) {
566
+ const spc = imageData.getSpacing();
567
+
568
+ bounds[0] = bounds[0] + spc[0] / 2;
569
+ bounds[1] = bounds[1] - spc[0] / 2;
570
+ bounds[2] = bounds[2] + spc[1] / 2;
571
+ bounds[3] = bounds[3] - spc[1] / 2;
572
+ bounds[4] = bounds[4] + spc[2] / 2;
573
+ bounds[5] = bounds[5] - spc[2] / 2;
574
+ }
575
+
576
+ const activeCamera = this.getVtkActiveCamera();
577
+ const viewPlaneNormal = <Point3>activeCamera.getViewPlaneNormal();
578
+ const viewUp = <Point3>activeCamera.getViewUp();
579
+
580
+ // Reset the perspective zoom factors, otherwise subsequent zooms will cause
581
+ // the view angle to become very small and cause bad depth sorting.
582
+ // todo: parallel projection only
583
+
584
+ focalPoint[0] = (bounds[0] + bounds[1]) / 2.0;
585
+ focalPoint[1] = (bounds[2] + bounds[3]) / 2.0;
586
+ focalPoint[2] = (bounds[4] + bounds[5]) / 2.0;
587
+
588
+ if (imageData) {
589
+ const dimensions = imageData.getDimensions();
590
+ const middleIJK = dimensions.map((d) => Math.floor(d / 2));
591
+
592
+ const idx = [middleIJK[0], middleIJK[1], middleIJK[2]];
593
+ imageData.indexToWorld(idx, focalPoint);
594
+ }
595
+
596
+ const { widthWorld, heightWorld } =
597
+ this._getWorldDistanceViewUpAndViewRight(bounds, viewUp, viewPlaneNormal);
598
+
599
+ const canvasSize = [this.sWidth, this.sHeight];
600
+
601
+ const boundsAspectRatio = widthWorld / heightWorld;
602
+ const canvasAspectRatio = canvasSize[0] / canvasSize[1];
603
+
604
+ let radius;
605
+
606
+ if (boundsAspectRatio < canvasAspectRatio) {
607
+ // can fit full height, so use it.
608
+ radius = heightWorld / 2;
609
+ } else {
610
+ const scaleFactor = boundsAspectRatio / canvasAspectRatio;
611
+
612
+ radius = (heightWorld * scaleFactor) / 2;
613
+ }
614
+
615
+ //const angle = vtkMath.radiansFromDegrees(activeCamera.getViewAngle())
616
+ const parallelScale = 1.1 * radius;
617
+
618
+ let w1 = bounds[1] - bounds[0];
619
+ let w2 = bounds[3] - bounds[2];
620
+ let w3 = bounds[5] - bounds[4];
621
+ w1 *= w1;
622
+ w2 *= w2;
623
+ w3 *= w3;
624
+ radius = w1 + w2 + w3;
625
+
626
+ // If we have just a single point, pick a radius of 1.0
627
+ radius = radius === 0 ? 1.0 : radius;
628
+
629
+ // compute the radius of the enclosing sphere
630
+ radius = Math.sqrt(radius) * 0.5;
631
+
632
+ const distance = 1.1 * radius;
633
+
634
+ const viewUpToSet: Point3 =
635
+ Math.abs(vtkMath.dot(viewUp, viewPlaneNormal)) > 0.999
636
+ ? [-viewUp[2], viewUp[0], viewUp[1]]
637
+ : viewUp;
638
+
639
+ const focalPointToSet = this._getFocalPointForResetCamera(
640
+ focalPoint,
641
+ previousCamera,
642
+ { resetPan, resetToCenter }
643
+ );
644
+
645
+ const positionToSet: Point3 = [
646
+ focalPointToSet[0] + distance * viewPlaneNormal[0],
647
+ focalPointToSet[1] + distance * viewPlaneNormal[1],
648
+ focalPointToSet[2] + distance * viewPlaneNormal[2],
649
+ ];
650
+
651
+ renderer.resetCameraClippingRange(bounds);
652
+
653
+ const clippingRangeToUse: Point2 = [
654
+ -RENDERING_DEFAULTS.MAXIMUM_RAY_DISTANCE,
655
+ RENDERING_DEFAULTS.MAXIMUM_RAY_DISTANCE,
656
+ ];
657
+
658
+ activeCamera.setPhysicalScale(radius);
659
+ activeCamera.setPhysicalTranslation(
660
+ -focalPointToSet[0],
661
+ -focalPointToSet[1],
662
+ -focalPointToSet[2]
663
+ );
664
+
665
+ this.setCamera({
666
+ parallelScale: resetZoom ? parallelScale : previousCamera.parallelScale,
667
+ focalPoint: focalPointToSet,
668
+ position: positionToSet,
669
+ viewAngle: 90,
670
+ viewUp: viewUpToSet,
671
+ clippingRange: clippingRangeToUse,
672
+ });
673
+
674
+ const modifiedCamera = _cloneDeep(this.getCamera());
675
+
676
+ if (storeAsInitialCamera) {
677
+ this.setInitialCamera(modifiedCamera);
678
+ }
679
+
680
+ const RESET_CAMERA_EVENT = {
681
+ type: 'ResetCameraEvent',
682
+ renderer,
683
+ };
684
+
685
+ // Here to let parallel/distributed compositing intercept
686
+ // and do the right thing.
687
+ renderer.invokeEvent(RESET_CAMERA_EVENT);
688
+
689
+ this.triggerCameraModifiedEventIfNecessary(previousCamera, modifiedCamera);
690
+
691
+ return true;
692
+ }
693
+
694
+ /**
695
+ * Sets the provided camera as the initial camera.
696
+ * This allows computing differences applied later as compared to the initial
697
+ * position, for things like zoom and pan.
698
+ * @param camera - to store as the initial value.
699
+ */
700
+ protected setInitialCamera(camera: ICamera): void {
701
+ this.initialCamera = camera;
702
+ }
703
+
704
+ /**
705
+ * Helper function to return the current canvas pan value.
706
+ *
707
+ * @returns a Point2 containing the current pan values
708
+ * on the canvas,
709
+ * computed from the current camera, where the initial pan
710
+ * value is [0,0].
711
+ */
712
+ public getPan(): Point2 {
713
+ const activeCamera = this.getVtkActiveCamera();
714
+ const focalPoint = activeCamera.getFocalPoint() as Point3;
715
+
716
+ const zero3 = this.canvasToWorld([0, 0]);
717
+ const initialCanvasFocal = this.worldToCanvas(
718
+ <Point3>vec3.subtract(vec3.create(), this.initialCamera.focalPoint, zero3)
719
+ );
720
+ const currentCanvasFocal = this.worldToCanvas(
721
+ <Point3>vec3.subtract(vec3.create(), focalPoint, zero3)
722
+ );
723
+ const result = <Point2>(
724
+ vec2.subtract(vec2.create(), initialCanvasFocal, currentCanvasFocal)
725
+ );
726
+ return result;
727
+ }
728
+
729
+ /**
730
+ * Sets the canvas pan value relative to the initial view position of 0,0
731
+ * Modifies the camera to perform the pan.
732
+ */
733
+ public setPan(pan: Point2, storeAsInitialCamera = false): void {
734
+ const previousCamera = this.getCamera();
735
+ const { focalPoint, position } = previousCamera;
736
+ const zero3 = this.canvasToWorld([0, 0]);
737
+ const delta2 = vec2.subtract(vec2.create(), pan, this.getPan());
738
+ if (
739
+ Math.abs(delta2[0]) < 1 &&
740
+ Math.abs(delta2[1]) < 1 &&
741
+ !storeAsInitialCamera
742
+ ) {
743
+ return;
744
+ }
745
+ const delta = vec3.subtract(
746
+ vec3.create(),
747
+ this.canvasToWorld(<Point2>delta2),
748
+ zero3
749
+ );
750
+ const newFocal = vec3.subtract(vec3.create(), focalPoint, delta);
751
+ const newPosition = vec3.subtract(vec3.create(), position, delta);
752
+ this.setCamera(
753
+ {
754
+ ...previousCamera,
755
+ focalPoint: newFocal as Point3,
756
+ position: newPosition as Point3,
757
+ },
758
+ storeAsInitialCamera
759
+ );
760
+ }
761
+
762
+ /**
763
+ * Returns a current zoom level relative to the initial parallel scale
764
+ * originally applied to the image. That is, on initial display,
765
+ * the zoom level is 1. Computed as a function of the camera.
766
+ */
767
+ public getZoom(): number {
768
+ const activeCamera = this.getVtkActiveCamera();
769
+ const { parallelScale: initialParallelScale } = this.initialCamera;
770
+ return initialParallelScale / activeCamera.getParallelScale();
771
+ }
772
+
773
+ /** Zooms the image using parallel scale by updating the camera value.
774
+ * @param value - The relative parallel scale to apply. It is relative
775
+ * to the initial offsets value.
776
+ * @param storeAsInitialCamera - can be set to true to reset the camera
777
+ * after applying this zoom as the initial camera. A subsequent getZoom
778
+ * call will return "1", but the zoom will have been applied.
779
+ */
780
+ public setZoom(value: number, storeAsInitialCamera = false): void {
781
+ const camera = this.getCamera();
782
+ const { parallelScale: initialParallelScale } = this.initialCamera;
783
+ const parallelScale = initialParallelScale / value;
784
+ if (camera.parallelScale === parallelScale && !storeAsInitialCamera) {
785
+ return;
786
+ }
787
+ this.setCamera(
788
+ {
789
+ ...camera,
790
+ parallelScale,
791
+ },
792
+ storeAsInitialCamera
793
+ );
794
+ }
795
+
796
+ /**
797
+ * Because the focalPoint is always in the centre of the viewport,
798
+ * we must do planar computations if the frame (image "slice") is to be preserved.
799
+ * 1. Calculate the intersection of the view plane with the imageData
800
+ * which results in points of intersection (minimum of 3, maximum of 6)
801
+ * 2. Calculate average of the intersection points to get newFocalPoint
802
+ * 3. Set the new focalPoint
803
+ * @param imageData - imageData
804
+ * @returns focalPoint
805
+ */
806
+ private _getFocalPointForViewPlaneReset(imageData) {
807
+ // Todo: move some where else
808
+ const { focalPoint, viewPlaneNormal: normal } = this.getCamera();
809
+ const intersections = this._getViewImageDataIntersections(
810
+ imageData,
811
+ focalPoint,
812
+ normal
813
+ );
814
+
815
+ let x = 0;
816
+ let y = 0;
817
+ let z = 0;
818
+
819
+ intersections.forEach(([point_x, point_y, point_z]) => {
820
+ x += point_x;
821
+ y += point_y;
822
+ z += point_z;
823
+ });
824
+
825
+ const newFocalPoint = <Point3>[
826
+ x / intersections.length,
827
+ y / intersections.length,
828
+ z / intersections.length,
829
+ ];
830
+ // Set the focal point on the average of the intersection points
831
+ return newFocalPoint;
832
+ }
833
+
834
+ /**
835
+ * Gets the target output canvas for the `Viewport`.
836
+ *
837
+ * @returns an HTMLCanvasElement.
838
+ */
839
+ public getCanvas(): HTMLCanvasElement {
840
+ return <HTMLCanvasElement>this.canvas;
841
+ }
842
+ /**
843
+ * Gets the active vtkCamera for the viewport.
844
+ *
845
+ * @returns vtk driven camera
846
+ */
847
+ protected getVtkActiveCamera(): vtkCamera | vtkSlabCamera {
848
+ const renderer = this.getRenderer();
849
+
850
+ return renderer.getActiveCamera();
851
+ }
852
+
853
+ /**
854
+ * Get the camera's current state
855
+ * @returns The camera object.
856
+ */
857
+ public getCamera(): ICamera {
858
+ const vtkCamera = this.getVtkActiveCamera();
859
+
860
+ return {
861
+ viewUp: <Point3>vtkCamera.getViewUp(),
862
+ viewPlaneNormal: <Point3>vtkCamera.getViewPlaneNormal(),
863
+ position: <Point3>vtkCamera.getPosition(),
864
+ focalPoint: <Point3>vtkCamera.getFocalPoint(),
865
+ parallelProjection: vtkCamera.getParallelProjection(),
866
+ parallelScale: vtkCamera.getParallelScale(),
867
+ viewAngle: vtkCamera.getViewAngle(),
868
+ flipHorizontal: this.flipHorizontal,
869
+ flipVertical: this.flipVertical,
870
+ };
871
+ }
872
+
873
+ /**
874
+ * Set the camera parameters
875
+ * @param cameraInterface - ICamera
876
+ * @param storeAsInitialCamera - to set the provided camera as the initial one,
877
+ * used to compute differences for things like pan and zoom.
878
+ */
879
+ public setCamera(
880
+ cameraInterface: ICamera,
881
+ storeAsInitialCamera = false
882
+ ): void {
883
+ const vtkCamera = this.getVtkActiveCamera();
884
+ const previousCamera = _cloneDeep(this.getCamera());
885
+ const updatedCamera = Object.assign({}, previousCamera, cameraInterface);
886
+ const {
887
+ viewUp,
888
+ viewPlaneNormal,
889
+ position,
890
+ focalPoint,
891
+ parallelScale,
892
+ viewAngle,
893
+ flipHorizontal,
894
+ flipVertical,
895
+ clippingRange,
896
+ } = cameraInterface;
897
+
898
+ // Note: Flip camera should be two separate calls since
899
+ // for flip, we need to flip the viewportNormal, and if
900
+ // flipHorizontal, and flipVertical are both true, that would
901
+ // the logic would be incorrect. So instead, we handle flip Horizontal
902
+ // and flipVertical separately.
903
+ if (flipHorizontal !== undefined) {
904
+ // flip if not flipped but requested to flip OR if flipped but requested to
905
+ // not flip
906
+ const flipH =
907
+ (flipHorizontal && !this.flipHorizontal) ||
908
+ (!flipHorizontal && this.flipHorizontal);
909
+
910
+ if (flipH) {
911
+ this.flip({ flipHorizontal: flipH });
912
+ }
913
+ }
914
+
915
+ if (flipVertical !== undefined) {
916
+ const flipV =
917
+ (flipVertical && !this.flipVertical) ||
918
+ (!flipVertical && this.flipVertical);
919
+
920
+ if (flipV) {
921
+ this.flip({ flipVertical: flipV });
922
+ }
923
+ }
924
+
925
+ if (viewUp !== undefined) {
926
+ vtkCamera.setViewUp(viewUp);
927
+ }
928
+
929
+ if (viewPlaneNormal !== undefined) {
930
+ vtkCamera.setDirectionOfProjection(
931
+ -viewPlaneNormal[0],
932
+ -viewPlaneNormal[1],
933
+ -viewPlaneNormal[2]
934
+ );
935
+ }
936
+
937
+ if (position !== undefined) {
938
+ vtkCamera.setPosition(...position);
939
+ }
940
+
941
+ if (focalPoint !== undefined) {
942
+ vtkCamera.setFocalPoint(...focalPoint);
943
+ }
944
+
945
+ if (parallelScale !== undefined) {
946
+ vtkCamera.setParallelScale(parallelScale);
947
+ }
948
+
949
+ if (viewAngle !== undefined) {
950
+ vtkCamera.setViewAngle(viewAngle);
951
+ }
952
+
953
+ if (clippingRange !== undefined) {
954
+ vtkCamera.setClippingRange(clippingRange);
955
+ }
956
+
957
+ // update clippingPlanes if volume viewports
958
+ const actorEntry = this.getDefaultActor();
959
+
960
+ if (!actorEntry || !actorEntry.actor) {
961
+ return;
962
+ }
963
+
964
+ const isImageSlice = actorIsA(actorEntry, 'vtkImageSlice');
965
+
966
+ if (!isImageSlice) {
967
+ this.updateClippingPlanesForActors(updatedCamera);
968
+ } else {
969
+ const renderer = this.getRenderer();
970
+ renderer.resetCameraClippingRange();
971
+ }
972
+
973
+ if (storeAsInitialCamera) {
974
+ this.setInitialCamera(updatedCamera);
975
+ }
976
+
977
+ this.triggerCameraModifiedEventIfNecessary(
978
+ previousCamera,
979
+ this.getCamera()
980
+ );
981
+ }
982
+
983
+ /**
984
+ * Trigger camera modified event
985
+ * @param cameraInterface - ICamera
986
+ * @param cameraInterface - ICamera
987
+ */
988
+ public triggerCameraModifiedEventIfNecessary(
989
+ previousCamera: ICamera,
990
+ updatedCamera: ICamera
991
+ ): void {
992
+ if (!this._suppressCameraModifiedEvents && !this.suppressEvents) {
993
+ const eventDetail: EventTypes.CameraModifiedEventDetail = {
994
+ previousCamera,
995
+ camera: updatedCamera,
996
+ element: this.element,
997
+ viewportId: this.id,
998
+ renderingEngineId: this.renderingEngineId,
999
+ rotation: this.getRotation(),
1000
+ };
1001
+
1002
+ triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail);
1003
+ }
1004
+ }
1005
+
1006
+ /**
1007
+ * Updates the actors clipping planes orientation from the camera properties
1008
+ * @param updatedCamera - ICamera
1009
+ */
1010
+ protected updateClippingPlanesForActors(updatedCamera: ICamera): void {
1011
+ const actorEntries = this.getActors();
1012
+ actorEntries.forEach((actorEntry) => {
1013
+ // we assume that the first two clipping plane of the mapper are always
1014
+ // the 'camera' clipping. Update clipping planes only if the actor is
1015
+ // a vtkVolume
1016
+ if (!actorEntry.actor) {
1017
+ return;
1018
+ }
1019
+
1020
+ const mapper = actorEntry.actor.getMapper();
1021
+ const vtkPlanes = mapper.getClippingPlanes();
1022
+
1023
+ let slabThickness = RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
1024
+ if (actorEntry.slabThickness) {
1025
+ slabThickness = actorEntry.slabThickness;
1026
+ }
1027
+
1028
+ const { viewPlaneNormal, focalPoint } = updatedCamera;
1029
+
1030
+ this.setOrientationOfClippingPlanes(
1031
+ vtkPlanes,
1032
+ slabThickness,
1033
+ viewPlaneNormal,
1034
+ focalPoint
1035
+ );
1036
+ });
1037
+ }
1038
+
1039
+ public setOrientationOfClippingPlanes(
1040
+ vtkPlanes: Array<vtkPlane>,
1041
+ slabThickness: number,
1042
+ viewPlaneNormal: Point3,
1043
+ focalPoint: Point3
1044
+ ): void {
1045
+ if (vtkPlanes.length < 2) {
1046
+ return;
1047
+ }
1048
+
1049
+ const scaledDistance = <Point3>[
1050
+ viewPlaneNormal[0],
1051
+ viewPlaneNormal[1],
1052
+ viewPlaneNormal[2],
1053
+ ];
1054
+ vtkMath.multiplyScalar(scaledDistance, slabThickness);
1055
+
1056
+ vtkPlanes[0].setNormal(viewPlaneNormal);
1057
+ const newOrigin1 = <Point3>[0, 0, 0];
1058
+ vtkMath.subtract(focalPoint, scaledDistance, newOrigin1);
1059
+ vtkPlanes[0].setOrigin(newOrigin1);
1060
+
1061
+ vtkPlanes[1].setNormal(
1062
+ -viewPlaneNormal[0],
1063
+ -viewPlaneNormal[1],
1064
+ -viewPlaneNormal[2]
1065
+ );
1066
+ const newOrigin2 = <Point3>[0, 0, 0];
1067
+ vtkMath.add(focalPoint, scaledDistance, newOrigin2);
1068
+ vtkPlanes[1].setOrigin(newOrigin2);
1069
+ }
1070
+
1071
+ private _getWorldDistanceViewUpAndViewRight(bounds, viewUp, viewPlaneNormal) {
1072
+ const viewUpCorners = this._getCorners(bounds);
1073
+ const viewRightCorners = this._getCorners(bounds);
1074
+
1075
+ const viewRight = vec3.cross(vec3.create(), viewUp, viewPlaneNormal);
1076
+
1077
+ let transform = vtkMatrixBuilder
1078
+ .buildFromDegree()
1079
+ .identity()
1080
+ .rotateFromDirections(viewUp, [1, 0, 0]);
1081
+
1082
+ viewUpCorners.forEach((pt) => transform.apply(pt));
1083
+
1084
+ // range is now maximum X distance
1085
+ let minY = Infinity;
1086
+ let maxY = -Infinity;
1087
+ for (let i = 0; i < 8; i++) {
1088
+ const y = viewUpCorners[i][0];
1089
+ if (y > maxY) {
1090
+ maxY = y;
1091
+ }
1092
+ if (y < minY) {
1093
+ minY = y;
1094
+ }
1095
+ }
1096
+
1097
+ transform = vtkMatrixBuilder
1098
+ .buildFromDegree()
1099
+ .identity()
1100
+ .rotateFromDirections(
1101
+ [viewRight[0], viewRight[1], viewRight[2]],
1102
+ [1, 0, 0]
1103
+ );
1104
+
1105
+ viewRightCorners.forEach((pt) => transform.apply(pt));
1106
+
1107
+ // range is now maximum Y distance
1108
+ let minX = Infinity;
1109
+ let maxX = -Infinity;
1110
+ for (let i = 0; i < 8; i++) {
1111
+ const x = viewRightCorners[i][0];
1112
+ if (x > maxX) {
1113
+ maxX = x;
1114
+ }
1115
+ if (x < minX) {
1116
+ minX = x;
1117
+ }
1118
+ }
1119
+
1120
+ return { widthWorld: maxX - minX, heightWorld: maxY - minY };
1121
+ }
1122
+
1123
+ protected _shouldUse16BitTexture() {
1124
+ const { useNorm16Texture, preferSizeOverAccuracy } =
1125
+ getConfiguration().rendering;
1126
+ return useNorm16Texture || preferSizeOverAccuracy;
1127
+ }
1128
+
1129
+ _getCorners(bounds: Array<number>): Array<number>[] {
1130
+ return [
1131
+ [bounds[0], bounds[2], bounds[4]],
1132
+ [bounds[0], bounds[2], bounds[5]],
1133
+ [bounds[0], bounds[3], bounds[4]],
1134
+ [bounds[0], bounds[3], bounds[5]],
1135
+ [bounds[1], bounds[2], bounds[4]],
1136
+ [bounds[1], bounds[2], bounds[5]],
1137
+ [bounds[1], bounds[3], bounds[4]],
1138
+ [bounds[1], bounds[3], bounds[5]],
1139
+ ];
1140
+ }
1141
+
1142
+ _getFocalPointForResetCamera(
1143
+ centeredFocalPoint: Point3,
1144
+ previousCamera: ICamera,
1145
+ { resetPan = true, resetToCenter = true }
1146
+ ): Point3 {
1147
+ if (resetToCenter && resetPan) {
1148
+ return centeredFocalPoint;
1149
+ }
1150
+
1151
+ if (resetToCenter && !resetPan) {
1152
+ return hasNaNValues(previousCamera.focalPoint)
1153
+ ? centeredFocalPoint
1154
+ : previousCamera.focalPoint;
1155
+ }
1156
+
1157
+ if (!resetToCenter && resetPan) {
1158
+ // this is an interesting case that means the reset camera should not
1159
+ // change the slice (default behavior is to go to the center of the
1160
+ // image), and rather just reset the pan on the slice that is currently
1161
+ // being viewed
1162
+ const oldCamera = previousCamera;
1163
+ const oldFocalPoint = oldCamera.focalPoint;
1164
+ const oldViewPlaneNormal = oldCamera.viewPlaneNormal;
1165
+
1166
+ const vectorFromOldFocalPointToCenteredFocalPoint = vec3.subtract(
1167
+ vec3.create(),
1168
+ centeredFocalPoint,
1169
+ oldFocalPoint
1170
+ );
1171
+
1172
+ const distanceFromOldFocalPointToCenteredFocalPoint = vec3.dot(
1173
+ vectorFromOldFocalPointToCenteredFocalPoint,
1174
+ oldViewPlaneNormal
1175
+ );
1176
+
1177
+ const newFocalPoint = vec3.scaleAndAdd(
1178
+ vec3.create(),
1179
+ centeredFocalPoint,
1180
+ oldViewPlaneNormal,
1181
+ -1 * distanceFromOldFocalPointToCenteredFocalPoint
1182
+ );
1183
+
1184
+ return [newFocalPoint[0], newFocalPoint[1], newFocalPoint[2]];
1185
+ }
1186
+
1187
+ if (!resetPan && !resetToCenter) {
1188
+ // this means the reset camera should not change the slice and should not
1189
+ // touch the pan either.
1190
+ return hasNaNValues(previousCamera.focalPoint)
1191
+ ? centeredFocalPoint
1192
+ : previousCamera.focalPoint;
1193
+ }
1194
+ }
1195
+
1196
+ /**
1197
+ * Determines whether or not the 3D point position is inside the boundaries of the 3D imageData.
1198
+ * @param point - 3D coordinate
1199
+ * @param bounds - Bounds of the image
1200
+ * @returns boolean
1201
+ */
1202
+ _isInBounds(point: Point3, bounds: number[]): boolean {
1203
+ const [xMin, xMax, yMin, yMax, zMin, zMax] = bounds;
1204
+ const [x, y, z] = point;
1205
+ if (x < xMin || x > xMax || y < yMin || y > yMax || z < zMin || z > zMax) {
1206
+ return false;
1207
+ }
1208
+ return true;
1209
+ }
1210
+
1211
+ /**
1212
+ * Returns a list of edges for the imageData bounds, which are
1213
+ * the cube edges in the case of volumeViewport edges.
1214
+ * p1: front, bottom, left
1215
+ * p2: front, top, left
1216
+ * p3: back, bottom, left
1217
+ * p4: back, top, left
1218
+ * p5: front, bottom, right
1219
+ * p6: front, top, right
1220
+ * p7: back, bottom, right
1221
+ * p8: back, top, right
1222
+ * @param bounds - Bounds of the renderer
1223
+ * @returns Edges of the containing bounds
1224
+ */
1225
+ _getEdges(bounds: Array<number>): Array<[number[], number[]]> {
1226
+ const [p1, p2, p3, p4, p5, p6, p7, p8] = this._getCorners(bounds);
1227
+ return [
1228
+ [p1, p2],
1229
+ [p1, p5],
1230
+ [p1, p3],
1231
+ [p2, p4],
1232
+ [p2, p6],
1233
+ [p3, p4],
1234
+ [p3, p7],
1235
+ [p4, p8],
1236
+ [p5, p7],
1237
+ [p5, p6],
1238
+ [p6, p8],
1239
+ [p7, p8],
1240
+ ];
1241
+ }
1242
+ }
1243
+
1244
+ export default Viewport;