@cornerstonejs/tools 0.56.2 → 0.56.3

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 (352) hide show
  1. package/package.json +5 -4
  2. package/src/constants/COLOR_LUT.ts +262 -0
  3. package/src/constants/index.ts +3 -0
  4. package/src/cursors/ImageMouseCursor.ts +39 -0
  5. package/src/cursors/MouseCursor.ts +114 -0
  6. package/src/cursors/SVGCursorDescriptor.ts +462 -0
  7. package/src/cursors/SVGMouseCursor.ts +145 -0
  8. package/src/cursors/elementCursor.ts +69 -0
  9. package/src/cursors/index.ts +24 -0
  10. package/src/cursors/setCursorForElement.ts +33 -0
  11. package/src/drawingSvg/_getHash.ts +9 -0
  12. package/src/drawingSvg/_setAttributesIfNecessary.ts +13 -0
  13. package/src/drawingSvg/_setNewAttributesIfValid.ts +10 -0
  14. package/src/drawingSvg/clearByToolType.ts +26 -0
  15. package/src/drawingSvg/draw.ts +16 -0
  16. package/src/drawingSvg/drawArrow.ts +82 -0
  17. package/src/drawingSvg/drawCircle.ts +62 -0
  18. package/src/drawingSvg/drawEllipse.ts +71 -0
  19. package/src/drawingSvg/drawHandles.ts +87 -0
  20. package/src/drawingSvg/drawLine.ts +70 -0
  21. package/src/drawingSvg/drawLink.ts +76 -0
  22. package/src/drawingSvg/drawLinkedTextBox.ts +64 -0
  23. package/src/drawingSvg/drawPolyline.ts +80 -0
  24. package/src/drawingSvg/drawRect.ts +70 -0
  25. package/src/drawingSvg/drawTextBox.ts +213 -0
  26. package/src/drawingSvg/getSvgDrawingHelper.ts +98 -0
  27. package/src/drawingSvg/index.ts +23 -0
  28. package/src/enums/AnnotationStyleStates.ts +22 -0
  29. package/src/enums/Events.ts +242 -0
  30. package/src/enums/SegmentationRepresentations.ts +12 -0
  31. package/src/enums/ToolBindings.ts +37 -0
  32. package/src/enums/ToolModes.ts +31 -0
  33. package/src/enums/Touch.ts +8 -0
  34. package/src/enums/index.js +16 -0
  35. package/src/eventDispatchers/annotationModifiedEventDispatcher.ts +41 -0
  36. package/src/eventDispatchers/cameraModifiedEventDispatcher.ts +41 -0
  37. package/src/eventDispatchers/imageRenderedEventDispatcher.ts +37 -0
  38. package/src/eventDispatchers/imageSpacingCalibratedEventDispatcher.ts +50 -0
  39. package/src/eventDispatchers/index.js +15 -0
  40. package/src/eventDispatchers/keyboardEventHandlers/index.js +4 -0
  41. package/src/eventDispatchers/keyboardEventHandlers/keyDown.ts +29 -0
  42. package/src/eventDispatchers/keyboardEventHandlers/keyUp.ts +33 -0
  43. package/src/eventDispatchers/keyboardToolEventDispatcher.ts +28 -0
  44. package/src/eventDispatchers/mouseEventHandlers/index.js +19 -0
  45. package/src/eventDispatchers/mouseEventHandlers/mouseClick.ts +13 -0
  46. package/src/eventDispatchers/mouseEventHandlers/mouseDoubleClick.ts +13 -0
  47. package/src/eventDispatchers/mouseEventHandlers/mouseDown.ts +196 -0
  48. package/src/eventDispatchers/mouseEventHandlers/mouseDownActivate.ts +35 -0
  49. package/src/eventDispatchers/mouseEventHandlers/mouseDrag.ts +25 -0
  50. package/src/eventDispatchers/mouseEventHandlers/mouseMove.ts +70 -0
  51. package/src/eventDispatchers/mouseEventHandlers/mouseUp.ts +9 -0
  52. package/src/eventDispatchers/mouseEventHandlers/mouseWheel.ts +13 -0
  53. package/src/eventDispatchers/mouseToolEventDispatcher.ts +64 -0
  54. package/src/eventDispatchers/shared/customCallbackHandler.ts +73 -0
  55. package/src/eventDispatchers/shared/getActiveToolForKeyboardEvent.ts +58 -0
  56. package/src/eventDispatchers/shared/getActiveToolForMouseEvent.ts +61 -0
  57. package/src/eventDispatchers/shared/getActiveToolForTouchEvent.ts +64 -0
  58. package/src/eventDispatchers/shared/getMouseModifier.ts +30 -0
  59. package/src/eventDispatchers/shared/getToolsWithModesForMouseEvent.ts +56 -0
  60. package/src/eventDispatchers/shared/getToolsWithModesForTouchEvent.ts +54 -0
  61. package/src/eventDispatchers/touchEventHandlers/index.js +15 -0
  62. package/src/eventDispatchers/touchEventHandlers/touchDrag.ts +23 -0
  63. package/src/eventDispatchers/touchEventHandlers/touchEnd.ts +9 -0
  64. package/src/eventDispatchers/touchEventHandlers/touchPress.ts +13 -0
  65. package/src/eventDispatchers/touchEventHandlers/touchStart.ts +174 -0
  66. package/src/eventDispatchers/touchEventHandlers/touchStartActivate.ts +36 -0
  67. package/src/eventDispatchers/touchEventHandlers/touchTap.ts +9 -0
  68. package/src/eventDispatchers/touchToolEventDispatcher.ts +51 -0
  69. package/src/eventListeners/annotations/annotationModifiedListener.ts +22 -0
  70. package/src/eventListeners/annotations/annotationSelectionListener.ts +29 -0
  71. package/src/eventListeners/annotations/index.ts +4 -0
  72. package/src/eventListeners/index.ts +28 -0
  73. package/src/eventListeners/keyboard/index.ts +16 -0
  74. package/src/eventListeners/keyboard/keyDownListener.ts +99 -0
  75. package/src/eventListeners/mouse/getMouseEventPoints.ts +66 -0
  76. package/src/eventListeners/mouse/index.ts +55 -0
  77. package/src/eventListeners/mouse/mouseDoubleClickListener.ts +55 -0
  78. package/src/eventListeners/mouse/mouseDownListener.ts +519 -0
  79. package/src/eventListeners/mouse/mouseMoveListener.ts +33 -0
  80. package/src/eventListeners/segmentation/index.ts +11 -0
  81. package/src/eventListeners/segmentation/segmentationDataModifiedEventListener.ts +61 -0
  82. package/src/eventListeners/segmentation/segmentationModifiedEventListener.ts +32 -0
  83. package/src/eventListeners/segmentation/segmentationRepresentationModifiedEventListener.ts +15 -0
  84. package/src/eventListeners/segmentation/segmentationRepresentationRemovedEventListener.ts +16 -0
  85. package/src/eventListeners/touch/getTouchEventPoints.ts +75 -0
  86. package/src/eventListeners/touch/index.ts +37 -0
  87. package/src/eventListeners/touch/preventGhostClick.js +72 -0
  88. package/src/eventListeners/touch/touchStartListener.ts +499 -0
  89. package/src/eventListeners/wheel/index.ts +27 -0
  90. package/src/eventListeners/wheel/normalizeWheel.ts +69 -0
  91. package/src/eventListeners/wheel/wheelListener.ts +51 -0
  92. package/src/index.ts +133 -0
  93. package/src/init.ts +187 -0
  94. package/src/stateManagement/annotation/FrameOfReferenceSpecificAnnotationManager.ts +399 -0
  95. package/src/stateManagement/annotation/annotationLocking.ts +178 -0
  96. package/src/stateManagement/annotation/annotationSelection.ts +163 -0
  97. package/src/stateManagement/annotation/annotationState.ts +180 -0
  98. package/src/stateManagement/annotation/annotationVisibility.ts +156 -0
  99. package/src/stateManagement/annotation/config/ToolStyle.ts +265 -0
  100. package/src/stateManagement/annotation/config/getFont.ts +36 -0
  101. package/src/stateManagement/annotation/config/getState.ts +26 -0
  102. package/src/stateManagement/annotation/config/helpers.ts +55 -0
  103. package/src/stateManagement/annotation/config/index.ts +5 -0
  104. package/src/stateManagement/annotation/helpers/state.ts +83 -0
  105. package/src/stateManagement/annotation/index.ts +15 -0
  106. package/src/stateManagement/index.js +40 -0
  107. package/src/stateManagement/segmentation/SegmentationStateManager.ts +491 -0
  108. package/src/stateManagement/segmentation/activeSegmentation.ts +60 -0
  109. package/src/stateManagement/segmentation/addSegmentationRepresentations.ts +77 -0
  110. package/src/stateManagement/segmentation/addSegmentations.ts +27 -0
  111. package/src/stateManagement/segmentation/config/index.ts +29 -0
  112. package/src/stateManagement/segmentation/config/segmentationColor.ts +132 -0
  113. package/src/stateManagement/segmentation/config/segmentationConfig.ts +195 -0
  114. package/src/stateManagement/segmentation/config/segmentationVisibility.ts +171 -0
  115. package/src/stateManagement/segmentation/helpers/index.ts +3 -0
  116. package/src/stateManagement/segmentation/helpers/normalizeSegmentationInput.ts +35 -0
  117. package/src/stateManagement/segmentation/helpers/validateSegmentationInput.ts +41 -0
  118. package/src/stateManagement/segmentation/index.ts +22 -0
  119. package/src/stateManagement/segmentation/removeSegmentationsFromToolGroup.ts +85 -0
  120. package/src/stateManagement/segmentation/segmentIndex.ts +38 -0
  121. package/src/stateManagement/segmentation/segmentLocking.ts +72 -0
  122. package/src/stateManagement/segmentation/segmentationState.ts +429 -0
  123. package/src/stateManagement/segmentation/triggerSegmentationEvents.ts +157 -0
  124. package/src/store/SynchronizerManager/Synchronizer.ts +344 -0
  125. package/src/store/SynchronizerManager/createSynchronizer.ts +41 -0
  126. package/src/store/SynchronizerManager/destroy.ts +14 -0
  127. package/src/store/SynchronizerManager/destroySynchronizer.ts +25 -0
  128. package/src/store/SynchronizerManager/getAllSynchronizers.ts +12 -0
  129. package/src/store/SynchronizerManager/getSynchronizer.ts +13 -0
  130. package/src/store/SynchronizerManager/getSynchronizersForViewport.ts +44 -0
  131. package/src/store/SynchronizerManager/index.js +15 -0
  132. package/src/store/ToolGroupManager/ToolGroup.ts +679 -0
  133. package/src/store/ToolGroupManager/createToolGroup.ts +33 -0
  134. package/src/store/ToolGroupManager/destroy.ts +24 -0
  135. package/src/store/ToolGroupManager/destroyToolGroup.ts +26 -0
  136. package/src/store/ToolGroupManager/getAllToolGroups.ts +12 -0
  137. package/src/store/ToolGroupManager/getToolGroup.ts +14 -0
  138. package/src/store/ToolGroupManager/getToolGroupForViewport.ts +44 -0
  139. package/src/store/ToolGroupManager/getToolGroupsWithToolName.ts +33 -0
  140. package/src/store/ToolGroupManager/index.ts +17 -0
  141. package/src/store/addEnabledElement.ts +137 -0
  142. package/src/store/addTool.ts +56 -0
  143. package/src/store/cancelActiveManipulations.ts +30 -0
  144. package/src/store/filterMoveableAnnotationTools.ts +61 -0
  145. package/src/store/filterToolsWithAnnotationsForElement.ts +51 -0
  146. package/src/store/filterToolsWithMoveableHandles.ts +51 -0
  147. package/src/store/index.ts +29 -0
  148. package/src/store/removeEnabledElement.ts +132 -0
  149. package/src/store/state.ts +57 -0
  150. package/src/store/svgNodeCache.ts +7 -0
  151. package/src/synchronizers/callbacks/areViewportsCoplanar .ts +12 -0
  152. package/src/synchronizers/callbacks/cameraSyncCallback.ts +33 -0
  153. package/src/synchronizers/callbacks/stackImageSyncCallback.ts +157 -0
  154. package/src/synchronizers/callbacks/voiSyncCallback.ts +51 -0
  155. package/src/synchronizers/callbacks/zoomPanSyncCallback.ts +43 -0
  156. package/src/synchronizers/index.ts +11 -0
  157. package/src/synchronizers/synchronizers/createCameraPositionSynchronizer.ts +25 -0
  158. package/src/synchronizers/synchronizers/createStackImageSynchronizer.ts +25 -0
  159. package/src/synchronizers/synchronizers/createVOISynchronizer.ts +24 -0
  160. package/src/synchronizers/synchronizers/createZoomPanSynchronizer.ts +25 -0
  161. package/src/synchronizers/synchronizers/index.ts +11 -0
  162. package/src/tools/CrosshairsTool.ts +2693 -0
  163. package/src/tools/MIPJumpToClickTool.ts +99 -0
  164. package/src/tools/MagnifyTool.ts +319 -0
  165. package/src/tools/PanTool.ts +58 -0
  166. package/src/tools/PlanarRotateTool.ts +77 -0
  167. package/src/tools/ReferenceCursors.ts +466 -0
  168. package/src/tools/ReferenceLinesTool.ts +279 -0
  169. package/src/tools/ScaleOverlayTool.ts +685 -0
  170. package/src/tools/StackScrollTool.ts +97 -0
  171. package/src/tools/StackScrollToolMouseWheelTool.ts +58 -0
  172. package/src/tools/TrackballRotateTool.ts +141 -0
  173. package/src/tools/VolumeRotateMouseWheelTool.ts +86 -0
  174. package/src/tools/WindowLevelTool.ts +260 -0
  175. package/src/tools/ZoomTool.ts +293 -0
  176. package/src/tools/annotation/AngleTool.ts +835 -0
  177. package/src/tools/annotation/ArrowAnnotateTool.ts +820 -0
  178. package/src/tools/annotation/BidirectionalTool.ts +1350 -0
  179. package/src/tools/annotation/CircleROITool.ts +1070 -0
  180. package/src/tools/annotation/CobbAngleTool.ts +815 -0
  181. package/src/tools/annotation/DragProbeTool.ts +213 -0
  182. package/src/tools/annotation/EllipticalROITool.ts +1223 -0
  183. package/src/tools/annotation/LengthTool.ts +861 -0
  184. package/src/tools/annotation/PlanarFreehandROITool.ts +636 -0
  185. package/src/tools/annotation/ProbeTool.ts +681 -0
  186. package/src/tools/annotation/RectangleROITool.ts +1028 -0
  187. package/src/tools/annotation/planarFreehandROITool/closedContourEditLoop.ts +488 -0
  188. package/src/tools/annotation/planarFreehandROITool/drawLoop.ts +462 -0
  189. package/src/tools/annotation/planarFreehandROITool/editLoopCommon.ts +331 -0
  190. package/src/tools/annotation/planarFreehandROITool/findOpenUShapedContourVectorToPeak.ts +74 -0
  191. package/src/tools/annotation/planarFreehandROITool/openContourEditLoop.ts +612 -0
  192. package/src/tools/annotation/planarFreehandROITool/openContourEndEditLoop.ts +74 -0
  193. package/src/tools/annotation/planarFreehandROITool/renderMethods.ts +407 -0
  194. package/src/tools/base/AnnotationDisplayTool.ts +228 -0
  195. package/src/tools/base/AnnotationTool.ts +307 -0
  196. package/src/tools/base/BaseTool.ts +215 -0
  197. package/src/tools/base/index.ts +4 -0
  198. package/src/tools/displayTools/Contour/addContourToElement.ts +135 -0
  199. package/src/tools/displayTools/Contour/contourDisplay.ts +252 -0
  200. package/src/tools/displayTools/Contour/index.ts +3 -0
  201. package/src/tools/displayTools/Contour/removeContourFromElement.ts +35 -0
  202. package/src/tools/displayTools/Labelmap/addLabelmapToElement.ts +57 -0
  203. package/src/tools/displayTools/Labelmap/index.ts +4 -0
  204. package/src/tools/displayTools/Labelmap/labelmapConfig.ts +37 -0
  205. package/src/tools/displayTools/Labelmap/labelmapDisplay.ts +461 -0
  206. package/src/tools/displayTools/Labelmap/removeLabelmapFromElement.ts +27 -0
  207. package/src/tools/displayTools/Labelmap/validateRepresentationData.ts +30 -0
  208. package/src/tools/displayTools/SegmentationDisplayTool.ts +198 -0
  209. package/src/tools/index.ts +84 -0
  210. package/src/tools/segmentation/BrushTool.ts +474 -0
  211. package/src/tools/segmentation/CircleScissorsTool.ts +365 -0
  212. package/src/tools/segmentation/PaintFillTool.ts +370 -0
  213. package/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts +471 -0
  214. package/src/tools/segmentation/RectangleROIThresholdTool.ts +281 -0
  215. package/src/tools/segmentation/RectangleScissorsTool.ts +382 -0
  216. package/src/tools/segmentation/SphereScissorsTool.ts +368 -0
  217. package/src/tools/segmentation/strategies/eraseCircle.ts +30 -0
  218. package/src/tools/segmentation/strategies/eraseRectangle.ts +81 -0
  219. package/src/tools/segmentation/strategies/eraseSphere.ts +27 -0
  220. package/src/tools/segmentation/strategies/fillCircle.ts +185 -0
  221. package/src/tools/segmentation/strategies/fillRectangle.ts +110 -0
  222. package/src/tools/segmentation/strategies/fillSphere.ts +88 -0
  223. package/src/tools/segmentation/strategies/index.ts +9 -0
  224. package/src/types/AnnotationGroupSelector.ts +7 -0
  225. package/src/types/AnnotationStyle.ts +42 -0
  226. package/src/types/AnnotationTypes.ts +109 -0
  227. package/src/types/BoundsIJK.ts +5 -0
  228. package/src/types/CINETypes.ts +32 -0
  229. package/src/types/ContourTypes.ts +26 -0
  230. package/src/types/CursorTypes.ts +12 -0
  231. package/src/types/EventTypes.ts +657 -0
  232. package/src/types/FloodFillTypes.ts +19 -0
  233. package/src/types/IAnnotationManager.ts +89 -0
  234. package/src/types/IDistance.ts +16 -0
  235. package/src/types/IPoints.ts +18 -0
  236. package/src/types/ISetToolModeOptions.ts +29 -0
  237. package/src/types/ISynchronizerEventHandler.ts +11 -0
  238. package/src/types/IToolClassReference.ts +5 -0
  239. package/src/types/IToolGroup.ts +72 -0
  240. package/src/types/ITouchPoints.ts +14 -0
  241. package/src/types/InteractionTypes.ts +6 -0
  242. package/src/types/InternalToolTypes.ts +19 -0
  243. package/src/types/JumpToSliceOptions.ts +7 -0
  244. package/src/types/LabelmapTypes.ts +41 -0
  245. package/src/types/PlanarBoundingBox.ts +8 -0
  246. package/src/types/SVGDrawingHelper.ts +10 -0
  247. package/src/types/ScrollOptions.ts +9 -0
  248. package/src/types/SegmentationStateTypes.ts +248 -0
  249. package/src/types/ToolHandle.ts +26 -0
  250. package/src/types/ToolProps.ts +16 -0
  251. package/src/types/ToolSpecificAnnotationTypes.ts +311 -0
  252. package/src/types/index.ts +115 -0
  253. package/src/utilities/boundingBox/extend2DBoundingBoxInViewAxis.ts +29 -0
  254. package/src/utilities/boundingBox/getBoundingBoxAroundShape.ts +57 -0
  255. package/src/utilities/boundingBox/index.ts +4 -0
  256. package/src/utilities/calibrateImageSpacing.ts +46 -0
  257. package/src/utilities/cine/events.ts +9 -0
  258. package/src/utilities/cine/index.ts +5 -0
  259. package/src/utilities/cine/playClip.ts +435 -0
  260. package/src/utilities/cine/state.ts +18 -0
  261. package/src/utilities/clip.js +30 -0
  262. package/src/utilities/debounce.js +217 -0
  263. package/src/utilities/drawing/getTextBoxCoordsCanvas.ts +45 -0
  264. package/src/utilities/drawing/index.ts +3 -0
  265. package/src/utilities/dynamicVolume/getDataInTime.ts +110 -0
  266. package/src/utilities/dynamicVolume/index.ts +2 -0
  267. package/src/utilities/getAnnotationNearPoint.ts +130 -0
  268. package/src/utilities/getModalityUnit.ts +11 -0
  269. package/src/utilities/getToolsWithModesForElement.ts +52 -0
  270. package/src/utilities/index.ts +68 -0
  271. package/src/utilities/isObject.js +29 -0
  272. package/src/utilities/math/angle/angleBetweenLines.ts +29 -0
  273. package/src/utilities/math/circle/_types.ts +6 -0
  274. package/src/utilities/math/circle/getCanvasCircleCorners.ts +23 -0
  275. package/src/utilities/math/circle/getCanvasCircleRadius.ts +16 -0
  276. package/src/utilities/math/circle/index.ts +4 -0
  277. package/src/utilities/math/ellipse/getCanvasEllipseCorners.ts +26 -0
  278. package/src/utilities/math/ellipse/index.ts +4 -0
  279. package/src/utilities/math/ellipse/pointInEllipse.ts +38 -0
  280. package/src/utilities/math/ellipse/pointInEllipsoidWithConstraint.ts +35 -0
  281. package/src/utilities/math/index.ts +8 -0
  282. package/src/utilities/math/line/distanceToPoint.ts +24 -0
  283. package/src/utilities/math/line/distanceToPointSquared.ts +44 -0
  284. package/src/utilities/math/line/index.ts +5 -0
  285. package/src/utilities/math/line/intersectLine.ts +92 -0
  286. package/src/utilities/math/midPoint.ts +24 -0
  287. package/src/utilities/math/point/distanceToPoint.ts +22 -0
  288. package/src/utilities/math/point/index.ts +3 -0
  289. package/src/utilities/math/polyline/addCanvasPointsToArray.ts +62 -0
  290. package/src/utilities/math/polyline/calculateAreaOfPoints.ts +23 -0
  291. package/src/utilities/math/polyline/getIntersectionWithPolyline.ts +182 -0
  292. package/src/utilities/math/polyline/getSubPixelSpacingAndXYDirections.ts +99 -0
  293. package/src/utilities/math/polyline/index.ts +19 -0
  294. package/src/utilities/math/polyline/planarFreehandROIInternalTypes.ts +36 -0
  295. package/src/utilities/math/polyline/pointCanProjectOnLine.ts +57 -0
  296. package/src/utilities/math/polyline/pointsAreWithinCloseContourProximity.ts +15 -0
  297. package/src/utilities/math/rectangle/distanceToPoint.ts +82 -0
  298. package/src/utilities/math/rectangle/index.ts +3 -0
  299. package/src/utilities/math/sphere/index.ts +3 -0
  300. package/src/utilities/math/sphere/pointInSphere.ts +31 -0
  301. package/src/utilities/math/vec2/findClosestPoint.ts +40 -0
  302. package/src/utilities/math/vec2/index.ts +4 -0
  303. package/src/utilities/math/vec2/liangBarksyClip.ts +84 -0
  304. package/src/utilities/orientation/getOrientationStringLPS.ts +52 -0
  305. package/src/utilities/orientation/index.ts +4 -0
  306. package/src/utilities/orientation/invertOrientationStringLPS.ts +21 -0
  307. package/src/utilities/planar/filterAnnotationsForDisplay.ts +68 -0
  308. package/src/utilities/planar/filterAnnotationsWithinSlice.ts +85 -0
  309. package/src/utilities/planar/getPointInLineOfSightWithCriteria.ts +104 -0
  310. package/src/utilities/planar/getWorldWidthAndHeightFromCorners.ts +51 -0
  311. package/src/utilities/planar/getWorldWidthAndHeightFromTwoPoints.ts +51 -0
  312. package/src/utilities/planar/index.ts +18 -0
  313. package/src/utilities/planarFreehandROITool/index.ts +7 -0
  314. package/src/utilities/planarFreehandROITool/interpolateAnnotation.ts +87 -0
  315. package/src/utilities/planarFreehandROITool/interpolatePoints.ts +214 -0
  316. package/src/utilities/planarFreehandROITool/interpolation/algorithms/bspline.ts +55 -0
  317. package/src/utilities/planarFreehandROITool/interpolation/interpolateSegmentPoints.ts +90 -0
  318. package/src/utilities/pointInShapeCallback.ts +138 -0
  319. package/src/utilities/pointInSurroundingSphereCallback.ts +188 -0
  320. package/src/utilities/rectangleROITool/getBoundsIJKFromRectangleAnnotations.ts +76 -0
  321. package/src/utilities/rectangleROITool/index.ts +3 -0
  322. package/src/utilities/scroll.ts +62 -0
  323. package/src/utilities/segmentation/brushSizeForToolGroup.ts +72 -0
  324. package/src/utilities/segmentation/brushThresholdForToolGroup.ts +65 -0
  325. package/src/utilities/segmentation/createLabelmapVolumeForViewport.ts +74 -0
  326. package/src/utilities/segmentation/createMergedLabelmapForIndex.ts +65 -0
  327. package/src/utilities/segmentation/floodFill.ts +194 -0
  328. package/src/utilities/segmentation/getDefaultRepresentationConfig.ts +20 -0
  329. package/src/utilities/segmentation/index.ts +33 -0
  330. package/src/utilities/segmentation/isValidRepresentationConfig.ts +22 -0
  331. package/src/utilities/segmentation/rectangleROIThresholdVolumeByRange.ts +91 -0
  332. package/src/utilities/segmentation/thresholdSegmentationByRange.ts +129 -0
  333. package/src/utilities/segmentation/thresholdVolumeByRange.ts +150 -0
  334. package/src/utilities/segmentation/triggerSegmentationRender.ts +206 -0
  335. package/src/utilities/segmentation/utilities.ts +116 -0
  336. package/src/utilities/stackPrefetch/index.ts +8 -0
  337. package/src/utilities/stackPrefetch/stackPrefetch.ts +405 -0
  338. package/src/utilities/stackPrefetch/state.ts +17 -0
  339. package/src/utilities/throttle.js +69 -0
  340. package/src/utilities/touch/index.ts +246 -0
  341. package/src/utilities/triggerAnnotationRender.ts +237 -0
  342. package/src/utilities/triggerAnnotationRenderForViewportIds.ts +18 -0
  343. package/src/utilities/viewport/index.ts +5 -0
  344. package/src/utilities/viewport/isViewportPreScaled.ts +24 -0
  345. package/src/utilities/viewport/jumpToSlice.ts +73 -0
  346. package/src/utilities/viewport/jumpToWorld.ts +58 -0
  347. package/src/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.ts +28 -0
  348. package/src/utilities/viewportFilters/filterViewportsWithParallelNormals.ts +26 -0
  349. package/src/utilities/viewportFilters/filterViewportsWithSameOrientation.ts +15 -0
  350. package/src/utilities/viewportFilters/filterViewportsWithToolEnabled.ts +72 -0
  351. package/src/utilities/viewportFilters/getViewportIdsWithToolToRender.ts +45 -0
  352. package/src/utilities/viewportFilters/index.ts +11 -0
@@ -0,0 +1,2693 @@
1
+ import { vec2, vec3 } from 'gl-matrix';
2
+ import vtkMath from '@kitware/vtk.js/Common/Core/Math';
3
+ import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
4
+
5
+ import { AnnotationTool } from './base';
6
+
7
+ import {
8
+ getEnabledElementByIds,
9
+ getEnabledElement,
10
+ utilities as csUtils,
11
+ Enums,
12
+ } from '@cornerstonejs/core';
13
+ import type { Types } from '@cornerstonejs/core';
14
+
15
+ import {
16
+ getToolGroup,
17
+ getToolGroupForViewport,
18
+ } from '../store/ToolGroupManager';
19
+
20
+ import {
21
+ addAnnotation,
22
+ getAnnotations,
23
+ removeAnnotation,
24
+ } from '../stateManagement/annotation/annotationState';
25
+ import {
26
+ drawCircle as drawCircleSvg,
27
+ drawHandles as drawHandlesSvg,
28
+ drawLine as drawLineSvg,
29
+ } from '../drawingSvg';
30
+ import { state } from '../store';
31
+ import { Events } from '../enums';
32
+ import { getViewportIdsWithToolToRender } from '../utilities/viewportFilters';
33
+ import {
34
+ resetElementCursor,
35
+ hideElementCursor,
36
+ } from '../cursors/elementCursor';
37
+ import liangBarksyClip from '../utilities/math/vec2/liangBarksyClip';
38
+
39
+ import * as lineSegment from '../utilities/math/line';
40
+ import {
41
+ Annotation,
42
+ Annotations,
43
+ EventTypes,
44
+ ToolHandle,
45
+ PublicToolProps,
46
+ ToolProps,
47
+ InteractionTypes,
48
+ SVGDrawingHelper,
49
+ } from '../types';
50
+ import { isAnnotationLocked } from '../stateManagement/annotation/annotationLocking';
51
+ import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds';
52
+ import { CONSTANTS } from '@cornerstonejs/core';
53
+
54
+ const { RENDERING_DEFAULTS } = CONSTANTS;
55
+
56
+ // TODO: nested config is weird
57
+ interface ToolConfiguration {
58
+ configuration?: {
59
+ getReferenceLineColor?: (viewportId: string) => string;
60
+ getReferenceLineControllable?: (viewportId: string) => boolean;
61
+ getReferenceLineDraggableRotatable?: (viewportId: string) => boolean;
62
+ getReferenceLineSlabThicknessControlsOn?: (viewportId: string) => boolean;
63
+ shadow?: boolean;
64
+ };
65
+ }
66
+
67
+ interface CrosshairsAnnotation extends Annotation {
68
+ data: {
69
+ handles: {
70
+ rotationPoints: any[]; // rotation handles, used for rotation interactions
71
+ slabThicknessPoints: any[]; // slab thickness handles, used for setting the slab thickness
72
+ activeOperation: number | null; // 0 translation, 1 rotation handles, 2 slab thickness handles
73
+ toolCenter: Types.Point3;
74
+ };
75
+ activeViewportIds: string[]; // a list of the viewport ids connected to the reference lines being translated
76
+ viewportId: string;
77
+ };
78
+ }
79
+
80
+ function defaultReferenceLineColor() {
81
+ return 'rgb(0, 200, 0)';
82
+ }
83
+
84
+ function defaultReferenceLineControllable() {
85
+ return true;
86
+ }
87
+
88
+ function defaultReferenceLineDraggableRotatable() {
89
+ return true;
90
+ }
91
+
92
+ function defaultReferenceLineSlabThicknessControlsOn() {
93
+ return true;
94
+ }
95
+
96
+ const OPERATION = {
97
+ DRAG: 1,
98
+ ROTATE: 2,
99
+ SLAB: 3,
100
+ };
101
+
102
+ const EPSILON = 1e-3;
103
+
104
+ /**
105
+ * CrosshairsTool is a tool that provides reference lines between different viewports
106
+ * of a toolGroup. Using crosshairs, you can jump to a specific location in one
107
+ * viewport and the rest of the viewports in the toolGroup will be aligned to that location.
108
+ * Crosshairs have grababble handles that can be used to rotate and translate the
109
+ * reference lines. They can also be used to set the slab thickness of the viewports
110
+ * by modifying the slab thickness handles.
111
+ *
112
+ */
113
+ class CrosshairsTool extends AnnotationTool {
114
+ static toolName;
115
+
116
+ // define the stroke width for mobile/web screen
117
+ private widthStrokeAnnotation = /Mobi|Android/i.test(navigator.userAgent)
118
+ ? 2.5
119
+ : 1;
120
+
121
+ toolCenter: Types.Point3 = [0, 0, 0]; // NOTE: it is assumed that all the active/linked viewports share the same crosshair center.
122
+ // This because the rotation operation rotates also all the other active/intersecting reference lines of the same angle
123
+ _getReferenceLineColor?: (viewportId: string) => string;
124
+ _getReferenceLineControllable?: (viewportId: string) => boolean;
125
+ _getReferenceLineDraggableRotatable?: (viewportId: string) => boolean;
126
+ _getReferenceLineSlabThicknessControlsOn?: (viewportId: string) => boolean;
127
+ editData: {
128
+ annotation: any;
129
+ } | null;
130
+
131
+ constructor(
132
+ toolProps: PublicToolProps = {},
133
+ defaultToolProps: ToolProps = {
134
+ supportedInteractionTypes: ['Mouse'],
135
+ configuration: {
136
+ shadow: true,
137
+ // renders a colored circle on top right of the viewports whose color
138
+ // matches the color of the reference line
139
+ viewportIndicators: true,
140
+ // Auto pan is a configuration which will update pan
141
+ // other viewports in the toolGroup if the center of the crosshairs
142
+ // is outside of the viewport. This might be useful for the case
143
+ // when the user is scrolling through an image (usually in the zoomed view)
144
+ // and the crosshairs will eventually get outside of the viewport for
145
+ // the other viewports.
146
+ autoPan: {
147
+ enabled: false,
148
+ panSize: 10,
149
+ },
150
+ // actorUIDs for slabThickness application, if not defined, the slab thickness
151
+ // will be applied to all actors of the viewport
152
+ filterActorUIDsToSetSlabThickness: [],
153
+ // blend mode for slabThickness modifications
154
+ slabThicknessBlendMode: Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
155
+ },
156
+ }
157
+ ) {
158
+ super(toolProps, defaultToolProps);
159
+
160
+ this._getReferenceLineColor =
161
+ toolProps.configuration?.getReferenceLineColor ||
162
+ defaultReferenceLineColor;
163
+ this._getReferenceLineControllable =
164
+ toolProps.configuration?.getReferenceLineControllable ||
165
+ defaultReferenceLineControllable;
166
+ this._getReferenceLineDraggableRotatable =
167
+ toolProps.configuration?.getReferenceLineDraggableRotatable ||
168
+ defaultReferenceLineDraggableRotatable;
169
+ this._getReferenceLineSlabThicknessControlsOn =
170
+ toolProps.configuration?.getReferenceLineSlabThicknessControlsOn ||
171
+ defaultReferenceLineSlabThicknessControlsOn;
172
+ }
173
+
174
+ /**
175
+ * Gets the camera from the viewport, and adds crosshairs annotation for the viewport
176
+ * to the annotationManager. If any annotation is found in the annotationManager, it
177
+ * overwrites it.
178
+ * @param viewportInfo - The viewportInfo for the viewport to add the crosshairs
179
+ * @returns viewPlaneNormal and center of viewport canvas in world space
180
+ */
181
+ initializeViewport = ({
182
+ renderingEngineId,
183
+ viewportId,
184
+ }: Types.IViewportId): {
185
+ normal: Types.Point3;
186
+ point: Types.Point3;
187
+ } => {
188
+ const enabledElement = getEnabledElementByIds(
189
+ viewportId,
190
+ renderingEngineId
191
+ );
192
+ const { FrameOfReferenceUID, viewport } = enabledElement;
193
+ const { element } = viewport;
194
+ const { position, focalPoint, viewPlaneNormal } = viewport.getCamera();
195
+
196
+ // Check if there is already annotation for this viewport
197
+ let annotations = this._getAnnotations(enabledElement);
198
+ annotations = this.filterInteractableAnnotationsForElement(
199
+ element,
200
+ annotations
201
+ );
202
+
203
+ if (annotations.length) {
204
+ // If found, it will override it by removing the annotation and adding it later
205
+ removeAnnotation(annotations[0].annotationUID);
206
+ }
207
+
208
+ const annotation = {
209
+ highlighted: false,
210
+ metadata: {
211
+ cameraPosition: <Types.Point3>[...position],
212
+ cameraFocalPoint: <Types.Point3>[...focalPoint],
213
+ FrameOfReferenceUID,
214
+ toolName: this.getToolName(),
215
+ },
216
+ data: {
217
+ handles: {
218
+ rotationPoints: [], // rotation handles, used for rotation interactions
219
+ slabThicknessPoints: [], // slab thickness handles, used for setting the slab thickness
220
+ toolCenter: this.toolCenter,
221
+ },
222
+ activeOperation: null, // 0 translation, 1 rotation handles, 2 slab thickness handles
223
+ activeViewportIds: [], // a list of the viewport ids connected to the reference lines being translated
224
+ viewportId,
225
+ },
226
+ };
227
+
228
+ addAnnotation(annotation, element);
229
+
230
+ return {
231
+ normal: viewPlaneNormal,
232
+ point: viewport.canvasToWorld([
233
+ viewport.canvas.clientWidth / 2,
234
+ viewport.canvas.clientHeight / 2,
235
+ ]),
236
+ };
237
+ };
238
+
239
+ _getViewportsInfo = () => {
240
+ const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
241
+
242
+ return viewports;
243
+ };
244
+
245
+ onSetToolActive() {
246
+ const viewportsInfo = this._getViewportsInfo();
247
+
248
+ // Upon new setVolumes on viewports we need to update the crosshairs
249
+ // reference points in the new space, so we subscribe to the event
250
+ // and update the reference points accordingly.
251
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
252
+ this._subscribeToViewportNewVolumeSet(viewportsInfo);
253
+
254
+ this.computeToolCenter(viewportsInfo);
255
+ }
256
+
257
+ onSetToolPassive() {
258
+ const viewportsInfo = this._getViewportsInfo();
259
+
260
+ this.computeToolCenter(viewportsInfo);
261
+ }
262
+
263
+ onSetToolEnabled() {
264
+ const viewportsInfo = this._getViewportsInfo();
265
+
266
+ this.computeToolCenter(viewportsInfo);
267
+ }
268
+
269
+ onSetToolDisabled() {
270
+ const viewportsInfo = this._getViewportsInfo();
271
+
272
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
273
+
274
+ // Crosshairs annotations in the state
275
+ // has no value when the tool is disabled
276
+ // since viewports can change (zoom, pan, scroll)
277
+ // between disabled and enabled/active states.
278
+ // so we just remove the annotations from the state
279
+ viewportsInfo.forEach(({ renderingEngineId, viewportId }) => {
280
+ const enabledElement = getEnabledElementByIds(
281
+ viewportId,
282
+ renderingEngineId
283
+ );
284
+
285
+ if (!enabledElement) {
286
+ return;
287
+ }
288
+
289
+ const annotations = this._getAnnotations(enabledElement);
290
+
291
+ if (annotations?.length) {
292
+ annotations.forEach((annotation) => {
293
+ removeAnnotation(annotation.annotationUID);
294
+ });
295
+ }
296
+ });
297
+ }
298
+
299
+ /**
300
+ * When activated, it initializes the crosshairs. It begins by computing
301
+ * the intersection of viewports associated with the crosshairs instance.
302
+ * When all three views are accessible, the intersection (e.g., crosshairs tool centre)
303
+ * will be an exact point in space; however, with two viewports, because the
304
+ * intersection of two planes is a line, it assumes the last view is between the centre
305
+ * of the two rendering viewports.
306
+ * @param viewportsInfo Array of viewportInputs which each item containing {viewportId, renderingEngineId}
307
+ */
308
+ computeToolCenter = (viewportsInfo): void => {
309
+ if (!viewportsInfo.length || viewportsInfo.length === 1) {
310
+ throw new Error(
311
+ 'For crosshairs to operate, at least two viewports must be given.'
312
+ );
313
+ }
314
+
315
+ // Todo: handle two same view viewport, or more than 3 viewports
316
+ const [firstViewport, secondViewport, thirdViewport] = viewportsInfo;
317
+
318
+ // Initialize first viewport
319
+ const { normal: normal1, point: point1 } =
320
+ this.initializeViewport(firstViewport);
321
+
322
+ // Initialize second viewport
323
+ const { normal: normal2, point: point2 } =
324
+ this.initializeViewport(secondViewport);
325
+
326
+ let normal3 = <Types.Point3>[0, 0, 0];
327
+ let point3 = vec3.create();
328
+
329
+ // If there are three viewports
330
+ if (thirdViewport) {
331
+ ({ normal: normal3, point: point3 } =
332
+ this.initializeViewport(thirdViewport));
333
+ } else {
334
+ // If there are only two views (viewport) associated with the crosshairs:
335
+ // In this situation, we don't have a third information to find the
336
+ // exact intersection, and we "assume" the third view is looking at
337
+ // a location in between the first and second view centers
338
+ vec3.add(point3, point1, point2);
339
+ vec3.scale(point3, point3, 0.5);
340
+ vec3.cross(normal3, normal1, normal2);
341
+ }
342
+
343
+ // Planes of each viewport
344
+ const firstPlane = csUtils.planar.planeEquation(normal1, point1);
345
+ const secondPlane = csUtils.planar.planeEquation(normal2, point2);
346
+ const thirdPlane = csUtils.planar.planeEquation(normal3, point3);
347
+
348
+ // Calculating the intersection of 3 planes
349
+ // prettier-ignore
350
+ this.toolCenter = csUtils.planar.threePlaneIntersection(firstPlane, secondPlane, thirdPlane)
351
+
352
+ // assuming all viewports are in the same rendering engine
353
+ const { renderingEngine } = getEnabledElementByIds(
354
+ viewportsInfo[0].viewportId,
355
+ viewportsInfo[0].renderingEngineId
356
+ );
357
+
358
+ triggerAnnotationRenderForViewportIds(
359
+ renderingEngine,
360
+ viewportsInfo.map(({ viewportId }) => viewportId)
361
+ );
362
+ };
363
+
364
+ /**
365
+ * addNewAnnotation acts as jump for the crosshairs tool. It is called when
366
+ * the user clicks on the image. It does not store the annotation in the stateManager though.
367
+ *
368
+ * @param evt - The mouse event
369
+ * @param interactionType - The type of interaction (e.g., mouse, touch, etc.)
370
+ * @returns Crosshairs annotation
371
+ */
372
+ addNewAnnotation = (
373
+ evt: EventTypes.InteractionEventType
374
+ ): CrosshairsAnnotation => {
375
+ const eventDetail = evt.detail;
376
+ const { element } = eventDetail;
377
+
378
+ const { currentPoints } = eventDetail;
379
+ const jumpWorld = currentPoints.world;
380
+
381
+ const enabledElement = getEnabledElement(element);
382
+ const { viewport } = enabledElement;
383
+ this._jump(enabledElement, jumpWorld);
384
+
385
+ const annotations = this._getAnnotations(enabledElement);
386
+ const filteredAnnotations = this.filterInteractableAnnotationsForElement(
387
+ viewport.element,
388
+ annotations
389
+ );
390
+
391
+ // viewport Annotation
392
+ const { data } = filteredAnnotations[0];
393
+
394
+ const { rotationPoints } = data.handles;
395
+ const viewportIdArray = [];
396
+ // put all the draggable reference lines in the viewportIdArray
397
+ for (let i = 0; i < rotationPoints.length - 1; ++i) {
398
+ const otherViewport = rotationPoints[i][1];
399
+ const viewportControllable = this._getReferenceLineControllable(
400
+ otherViewport.id
401
+ );
402
+ const viewportDraggableRotatable =
403
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
404
+ if (!viewportControllable || !viewportDraggableRotatable) {
405
+ continue;
406
+ }
407
+ viewportIdArray.push(otherViewport.id);
408
+ // rotation handles are two per viewport
409
+ i++;
410
+ }
411
+
412
+ data.activeViewportIds = [...viewportIdArray];
413
+ // set translation operation
414
+ data.handles.activeOperation = OPERATION.DRAG;
415
+
416
+ evt.preventDefault();
417
+
418
+ hideElementCursor(element);
419
+
420
+ this._activateModify(element);
421
+ return filteredAnnotations[0];
422
+ };
423
+
424
+ cancel = () => {
425
+ console.log('Not implemented yet');
426
+ };
427
+
428
+ /**
429
+ * It checks if the mouse click is near crosshairs handles, if yes
430
+ * it returns the handle location. If the mouse click is not near any
431
+ * of the handles, it does not return anything.
432
+ *
433
+ * @param element - The element that the tool is attached to.
434
+ * @param annotation - The annotation object associated with the annotation
435
+ * @param canvasCoords - The coordinates of the mouse click on canvas
436
+ * @param proximity - The distance from the mouse cursor to the point
437
+ * that is considered "near".
438
+ * @returns The handle that is closest to the cursor, or null if the cursor
439
+ * is not near any of the handles.
440
+ */
441
+ getHandleNearImagePoint(
442
+ element: HTMLDivElement,
443
+ annotation: Annotation,
444
+ canvasCoords: Types.Point2,
445
+ proximity: number
446
+ ): ToolHandle | undefined {
447
+ const enabledElement = getEnabledElement(element);
448
+ const { viewport } = enabledElement;
449
+
450
+ let point = this._getRotationHandleNearImagePoint(
451
+ viewport,
452
+ annotation,
453
+ canvasCoords,
454
+ proximity
455
+ );
456
+
457
+ if (point !== null) {
458
+ return point;
459
+ }
460
+
461
+ point = this._getSlabThicknessHandleNearImagePoint(
462
+ viewport,
463
+ annotation,
464
+ canvasCoords,
465
+ proximity
466
+ );
467
+
468
+ if (point !== null) {
469
+ return point;
470
+ }
471
+ }
472
+
473
+ handleSelectedCallback = (
474
+ evt: EventTypes.InteractionEventType,
475
+ annotation: Annotation
476
+ ): void => {
477
+ const eventDetail = evt.detail;
478
+ const { element } = eventDetail;
479
+
480
+ annotation.highlighted = true;
481
+
482
+ // NOTE: handle index or coordinates are not used when dragging.
483
+ // This because the handle points are actually generated in the renderTool and they are a derivative
484
+ // from the camera variables of the viewports and of the slab thickness variable.
485
+ // Remember that the translation and rotation operations operate on the camera
486
+ // variables and not really on the handles. Similar for the slab thickness.
487
+ this._activateModify(element);
488
+
489
+ hideElementCursor(element);
490
+
491
+ evt.preventDefault();
492
+ };
493
+
494
+ /**
495
+ * It returns if the canvas point is near the provided crosshairs annotation in the
496
+ * provided element or not. A proximity is passed to the function to determine the
497
+ * proximity of the point to the annotation in number of pixels.
498
+ *
499
+ * @param element - HTML Element
500
+ * @param annotation - Annotation
501
+ * @param canvasCoords - Canvas coordinates
502
+ * @param proximity - Proximity to tool to consider
503
+ * @returns Boolean, whether the canvas point is near tool
504
+ */
505
+ isPointNearTool = (
506
+ element: HTMLDivElement,
507
+ annotation: CrosshairsAnnotation,
508
+ canvasCoords: Types.Point2,
509
+ proximity: number
510
+ ): boolean => {
511
+ if (this._pointNearTool(element, annotation, canvasCoords, 6)) {
512
+ return true;
513
+ }
514
+
515
+ return false;
516
+ };
517
+
518
+ toolSelectedCallback = (
519
+ evt: EventTypes.InteractionEventType,
520
+ annotation: Annotation,
521
+ interactionType: InteractionTypes
522
+ ): void => {
523
+ const eventDetail = evt.detail;
524
+ const { element } = eventDetail;
525
+ annotation.highlighted = true;
526
+
527
+ this._activateModify(element);
528
+
529
+ hideElementCursor(element);
530
+
531
+ evt.preventDefault();
532
+ };
533
+
534
+ onCameraModified = (evt) => {
535
+ const eventDetail = evt.detail;
536
+ const { element } = eventDetail;
537
+ const enabledElement = getEnabledElement(element);
538
+ const { renderingEngine } = enabledElement;
539
+ const viewport = enabledElement.viewport as Types.IVolumeViewport;
540
+
541
+ const annotations = this._getAnnotations(enabledElement);
542
+ const filteredToolAnnotations =
543
+ this.filterInteractableAnnotationsForElement(element, annotations);
544
+
545
+ // viewport that the camera modified is originating from
546
+ const viewportAnnotation =
547
+ filteredToolAnnotations[0] as CrosshairsAnnotation;
548
+
549
+ if (!viewportAnnotation) {
550
+ return;
551
+ }
552
+
553
+ // -- Update the camera of other linked viewports containing the same volumeId that
554
+ // have the same camera in case of translation
555
+ // -- Update the crosshair center in world coordinates in annotation.
556
+ // This is necessary because other tools can modify the position of the slices,
557
+ // e.g. stackScroll tool at wheel scroll. So we update the coordinates of the center always here.
558
+ // NOTE: rotation and slab thickness handles are created/updated in renderTool.
559
+ const currentCamera = viewport.getCamera();
560
+ const oldCameraPosition = viewportAnnotation.metadata.cameraPosition;
561
+ const deltaCameraPosition: Types.Point3 = [0, 0, 0];
562
+ vtkMath.subtract(
563
+ currentCamera.position,
564
+ oldCameraPosition,
565
+ deltaCameraPosition
566
+ );
567
+
568
+ const oldCameraFocalPoint = viewportAnnotation.metadata.cameraFocalPoint;
569
+ const deltaCameraFocalPoint: Types.Point3 = [0, 0, 0];
570
+ vtkMath.subtract(
571
+ currentCamera.focalPoint,
572
+ oldCameraFocalPoint,
573
+ deltaCameraFocalPoint
574
+ );
575
+
576
+ // updated cached "previous" camera position and focal point
577
+ viewportAnnotation.metadata.cameraPosition = [...currentCamera.position];
578
+ viewportAnnotation.metadata.cameraFocalPoint = [
579
+ ...currentCamera.focalPoint,
580
+ ];
581
+
582
+ const viewportControllable = this._getReferenceLineControllable(
583
+ viewport.id
584
+ );
585
+ const viewportDraggableRotatable = this._getReferenceLineDraggableRotatable(
586
+ viewport.id
587
+ );
588
+ if (
589
+ !csUtils.isEqual(currentCamera.position, oldCameraPosition, 1e-3) &&
590
+ viewportControllable &&
591
+ viewportDraggableRotatable
592
+ ) {
593
+ // Is camera Modified a TRANSLATION or ROTATION?
594
+ let isRotation = false;
595
+
596
+ // This is guaranteed to be the same diff for both position and focal point
597
+ // if the camera is modified by pan, zoom, or scroll BUT for rotation of
598
+ // crosshairs handles it will be different.
599
+ const cameraModifiedSameForPosAndFocalPoint = csUtils.isEqual(
600
+ deltaCameraPosition,
601
+ deltaCameraFocalPoint,
602
+ 1e-3
603
+ );
604
+
605
+ // NOTE: it is a translation if the the focal point and camera position shifts are the same
606
+ if (!cameraModifiedSameForPosAndFocalPoint) {
607
+ isRotation = true;
608
+ }
609
+
610
+ const cameraModifiedInPlane =
611
+ Math.abs(
612
+ vtkMath.dot(deltaCameraPosition, currentCamera.viewPlaneNormal)
613
+ ) < 1e-2;
614
+
615
+ // TRANSLATION
616
+ // NOTE1: if the camera modified is a result of a pan or zoom don't update the crosshair center
617
+ // NOTE2: rotation handles are updates in renderTool
618
+ if (!isRotation && !cameraModifiedInPlane) {
619
+ this.toolCenter[0] += deltaCameraPosition[0];
620
+ this.toolCenter[1] += deltaCameraPosition[1];
621
+ this.toolCenter[2] += deltaCameraPosition[2];
622
+ }
623
+ }
624
+
625
+ // AutoPan modification
626
+ if (this.configuration.autoPan?.enabled) {
627
+ const toolGroup = getToolGroupForViewport(
628
+ viewport.id,
629
+ renderingEngine.id
630
+ );
631
+
632
+ const otherViewportIds = toolGroup
633
+ .getViewportIds()
634
+ .filter((id) => id !== viewport.id);
635
+
636
+ otherViewportIds.forEach((viewportId) => {
637
+ this._autoPanViewportIfNecessary(viewportId, renderingEngine);
638
+ });
639
+ }
640
+
641
+ const requireSameOrientation = false;
642
+ const viewportIdsToRender = getViewportIdsWithToolToRender(
643
+ element,
644
+ this.getToolName(),
645
+ requireSameOrientation
646
+ );
647
+
648
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
649
+ };
650
+
651
+ mouseMoveCallback = (
652
+ evt: EventTypes.MouseMoveEventType,
653
+ filteredToolAnnotations: Annotations
654
+ ): boolean => {
655
+ const { element, currentPoints } = evt.detail;
656
+ const canvasCoords = currentPoints.canvas;
657
+ let imageNeedsUpdate = false;
658
+
659
+ for (let i = 0; i < filteredToolAnnotations.length; i++) {
660
+ const annotation = filteredToolAnnotations[i] as CrosshairsAnnotation;
661
+
662
+ if (isAnnotationLocked(annotation)) {
663
+ continue;
664
+ }
665
+
666
+ const { data, highlighted } = annotation;
667
+ if (!data.handles) {
668
+ continue;
669
+ }
670
+
671
+ const previousActiveOperation = data.handles.activeOperation;
672
+ const previousActiveViewportIds =
673
+ data.activeViewportIds && data.activeViewportIds.length > 0
674
+ ? [...data.activeViewportIds]
675
+ : [];
676
+
677
+ // This init are necessary, because when we move the mouse they are not cleaned by _endCallback
678
+ data.activeViewportIds = [];
679
+ data.handles.activeOperation = null;
680
+
681
+ const handleNearImagePoint = this.getHandleNearImagePoint(
682
+ element,
683
+ annotation,
684
+ canvasCoords,
685
+ 6
686
+ );
687
+
688
+ let near = false;
689
+ if (handleNearImagePoint) {
690
+ near = true;
691
+ } else {
692
+ near = this._pointNearTool(element, annotation, canvasCoords, 6);
693
+ }
694
+
695
+ const nearToolAndNotMarkedActive = near && !highlighted;
696
+ const notNearToolAndMarkedActive = !near && highlighted;
697
+ if (nearToolAndNotMarkedActive || notNearToolAndMarkedActive) {
698
+ annotation.highlighted = !highlighted;
699
+ imageNeedsUpdate = true;
700
+ } else if (
701
+ data.handles.activeOperation !== previousActiveOperation ||
702
+ !this._areViewportIdArraysEqual(
703
+ data.activeViewportIds,
704
+ previousActiveViewportIds
705
+ )
706
+ ) {
707
+ imageNeedsUpdate = true;
708
+ }
709
+ }
710
+
711
+ return imageNeedsUpdate;
712
+ };
713
+
714
+ filterInteractableAnnotationsForElement = (element, annotations) => {
715
+ if (!annotations || !annotations.length) {
716
+ return [];
717
+ }
718
+
719
+ const enabledElement = getEnabledElement(element);
720
+ const { viewportId } = enabledElement;
721
+
722
+ const viewportUIDSpecificCrosshairs = annotations.filter(
723
+ (annotation) => annotation.data.viewportId === viewportId
724
+ );
725
+
726
+ return viewportUIDSpecificCrosshairs;
727
+ };
728
+
729
+ /**
730
+ * renders the crosshairs lines and handles in the requestAnimationFrame callback
731
+ *
732
+ * @param enabledElement - The Cornerstone's enabledElement.
733
+ * @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
734
+ */
735
+ renderAnnotation = (
736
+ enabledElement: Types.IEnabledElement,
737
+ svgDrawingHelper: SVGDrawingHelper
738
+ ): boolean => {
739
+ let renderStatus = false;
740
+ const { viewport, renderingEngine } = enabledElement;
741
+ const { element } = viewport;
742
+ const annotations = this._getAnnotations(enabledElement);
743
+ const camera = viewport.getCamera();
744
+ const filteredToolAnnotations =
745
+ this.filterInteractableAnnotationsForElement(element, annotations);
746
+
747
+ // viewport Annotation
748
+ const viewportAnnotation = filteredToolAnnotations[0];
749
+ if (!annotations?.length || !viewportAnnotation?.data) {
750
+ // No annotations yet, and didn't just create it as we likely don't have a FrameOfReference/any data loaded yet.
751
+ return renderStatus;
752
+ }
753
+
754
+ const annotationUID = viewportAnnotation.annotationUID;
755
+
756
+ // Get cameras/canvases for each of these.
757
+ // -- Get two world positions for this canvas in this line (e.g. the diagonal)
758
+ // -- Convert these world positions to this canvas.
759
+ // -- Extend/confine this line to fit in this canvas.
760
+ // -- Render this line.
761
+ const { clientWidth, clientHeight } = viewport.canvas;
762
+ const canvasDiagonalLength = Math.sqrt(
763
+ clientWidth * clientWidth + clientHeight * clientHeight
764
+ );
765
+
766
+ const data = viewportAnnotation.data;
767
+ const crosshairCenterCanvas = viewport.worldToCanvas(this.toolCenter);
768
+
769
+ const otherViewportAnnotations =
770
+ this._filterAnnotationsByUniqueViewportOrientations(
771
+ enabledElement,
772
+ annotations
773
+ );
774
+
775
+ const referenceLines = [];
776
+
777
+ otherViewportAnnotations.forEach((annotation) => {
778
+ const { data } = annotation;
779
+
780
+ data.handles.toolCenter = this.toolCenter;
781
+
782
+ const otherViewport = renderingEngine.getViewport(
783
+ data.viewportId
784
+ ) as Types.IVolumeViewport;
785
+
786
+ const otherCamera = otherViewport.getCamera();
787
+
788
+ const otherViewportControllable = this._getReferenceLineControllable(
789
+ otherViewport.id
790
+ );
791
+ const otherViewportDraggableRotatable =
792
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
793
+ const otherViewportSlabThicknessControlsOn =
794
+ this._getReferenceLineSlabThicknessControlsOn(otherViewport.id);
795
+
796
+ // get coordinates for the reference line
797
+ const { clientWidth, clientHeight } = otherViewport.canvas;
798
+ const otherCanvasDiagonalLength = Math.sqrt(
799
+ clientWidth * clientWidth + clientHeight * clientHeight
800
+ );
801
+ const otherCanvasCenter: Types.Point2 = [
802
+ clientWidth * 0.5,
803
+ clientHeight * 0.5,
804
+ ];
805
+ const otherViewportCenterWorld =
806
+ otherViewport.canvasToWorld(otherCanvasCenter);
807
+
808
+ const direction: Types.Point3 = [0, 0, 0];
809
+ vtkMath.cross(
810
+ camera.viewPlaneNormal,
811
+ otherCamera.viewPlaneNormal,
812
+ direction
813
+ );
814
+ vtkMath.normalize(direction);
815
+ vtkMath.multiplyScalar(
816
+ <Types.Point3>direction,
817
+ otherCanvasDiagonalLength
818
+ );
819
+
820
+ const pointWorld0: Types.Point3 = [0, 0, 0];
821
+ vtkMath.add(otherViewportCenterWorld, direction, pointWorld0);
822
+
823
+ const pointWorld1: Types.Point3 = [0, 0, 0];
824
+ vtkMath.subtract(otherViewportCenterWorld, direction, pointWorld1);
825
+
826
+ // get canvas information for points and lines (canvas box, canvas horizontal distances)
827
+ const canvasBox = [0, 0, clientWidth, clientHeight];
828
+
829
+ const pointCanvas0 = viewport.worldToCanvas(pointWorld0);
830
+
831
+ const otherViewportCenterCanvas = viewport.worldToCanvas(
832
+ otherViewportCenterWorld
833
+ );
834
+
835
+ const canvasUnitVectorFromCenter = vec2.create();
836
+ vec2.subtract(
837
+ canvasUnitVectorFromCenter,
838
+ pointCanvas0,
839
+ otherViewportCenterCanvas
840
+ );
841
+ vec2.normalize(canvasUnitVectorFromCenter, canvasUnitVectorFromCenter);
842
+
843
+ // Graphic:
844
+ // Mid -> SlabThickness handle
845
+ // Short -> Rotation handle
846
+ // Long
847
+ // |
848
+ // |
849
+ // |
850
+ // Mid
851
+ // |
852
+ // |
853
+ // |
854
+ // Short
855
+ // |
856
+ // |
857
+ // |
858
+ // Long --- Mid--- Short--- Center --- Short --- Mid --- Long
859
+ // |
860
+ // |
861
+ // |
862
+ // Short
863
+ // |
864
+ // |
865
+ // |
866
+ // Mid
867
+ // |
868
+ // |
869
+ // |
870
+ // Long
871
+ const canvasVectorFromCenterLong = vec2.create();
872
+
873
+ // Todo: configuration should provide constants below (100, 0.25, 0.15, 0.04)
874
+ vec2.scale(
875
+ canvasVectorFromCenterLong,
876
+ canvasUnitVectorFromCenter,
877
+ canvasDiagonalLength * 100
878
+ );
879
+ const canvasVectorFromCenterMid = vec2.create();
880
+ vec2.scale(
881
+ canvasVectorFromCenterMid,
882
+ canvasUnitVectorFromCenter,
883
+ canvasDiagonalLength * 0.25
884
+ );
885
+ const canvasVectorFromCenterShort = vec2.create();
886
+ vec2.scale(
887
+ canvasVectorFromCenterShort,
888
+ canvasUnitVectorFromCenter,
889
+ canvasDiagonalLength * 0.15
890
+ );
891
+ const canvasVectorFromCenterStart = vec2.create();
892
+ vec2.scale(
893
+ canvasVectorFromCenterStart,
894
+ canvasUnitVectorFromCenter,
895
+ // Don't put a gap if the the third view is missing
896
+ otherViewportAnnotations.length === 2 ? canvasDiagonalLength * 0.04 : 0
897
+ );
898
+
899
+ // Computing Reference start and end (4 lines per viewport in case of 3 view MPR)
900
+ const refLinePointOne = vec2.create();
901
+ const refLinePointTwo = vec2.create();
902
+ const refLinePointThree = vec2.create();
903
+ const refLinePointFour = vec2.create();
904
+
905
+ let refLinesCenter = vec2.clone(crosshairCenterCanvas);
906
+ if (!otherViewportDraggableRotatable || !otherViewportControllable) {
907
+ refLinesCenter = vec2.clone(otherViewportCenterCanvas);
908
+ }
909
+
910
+ vec2.add(refLinePointOne, refLinesCenter, canvasVectorFromCenterStart);
911
+ vec2.add(refLinePointTwo, refLinesCenter, canvasVectorFromCenterLong);
912
+ vec2.subtract(
913
+ refLinePointThree,
914
+ refLinesCenter,
915
+ canvasVectorFromCenterStart
916
+ );
917
+ vec2.subtract(
918
+ refLinePointFour,
919
+ refLinesCenter,
920
+ canvasVectorFromCenterLong
921
+ );
922
+
923
+ // Clipping lines to be only included in a box (canvas), we don't want
924
+ // the lines goes beyond canvas
925
+ liangBarksyClip(refLinePointOne, refLinePointTwo, canvasBox);
926
+ liangBarksyClip(refLinePointThree, refLinePointFour, canvasBox);
927
+
928
+ // Computing rotation handle positions
929
+ const rotHandleOne = vec2.create();
930
+ vec2.subtract(
931
+ rotHandleOne,
932
+ crosshairCenterCanvas,
933
+ canvasVectorFromCenterMid
934
+ );
935
+
936
+ const rotHandleTwo = vec2.create();
937
+ vec2.add(rotHandleTwo, crosshairCenterCanvas, canvasVectorFromCenterMid);
938
+
939
+ // Computing SlabThickness (st below) position
940
+
941
+ // SlabThickness center in canvas
942
+ let stHandlesCenterCanvas = vec2.clone(crosshairCenterCanvas);
943
+ if (
944
+ !otherViewportDraggableRotatable &&
945
+ otherViewportSlabThicknessControlsOn
946
+ ) {
947
+ stHandlesCenterCanvas = vec2.clone(otherViewportCenterCanvas);
948
+ }
949
+
950
+ // SlabThickness center in world
951
+ let stHandlesCenterWorld: Types.Point3 = [...this.toolCenter];
952
+ if (
953
+ !otherViewportDraggableRotatable &&
954
+ otherViewportSlabThicknessControlsOn
955
+ ) {
956
+ stHandlesCenterWorld = [...otherViewportCenterWorld];
957
+ }
958
+
959
+ const worldUnitVectorFromCenter: Types.Point3 = [0, 0, 0];
960
+ vtkMath.subtract(pointWorld0, pointWorld1, worldUnitVectorFromCenter);
961
+ vtkMath.normalize(worldUnitVectorFromCenter);
962
+
963
+ const { viewPlaneNormal } = camera;
964
+ // @ts-ignore // Todo: fix after vtk pr merged
965
+ const { matrix } = vtkMatrixBuilder
966
+ .buildFromDegree()
967
+ // @ts-ignore fix after vtk pr merged
968
+ .rotate(90, viewPlaneNormal);
969
+
970
+ const worldUnitOrthoVectorFromCenter: Types.Point3 = [0, 0, 0];
971
+ vec3.transformMat4(
972
+ worldUnitOrthoVectorFromCenter,
973
+ worldUnitVectorFromCenter,
974
+ matrix
975
+ );
976
+
977
+ const slabThicknessValue = otherViewport.getSlabThickness();
978
+ const worldOrthoVectorFromCenter: Types.Point3 = [
979
+ ...worldUnitOrthoVectorFromCenter,
980
+ ];
981
+ vtkMath.multiplyScalar(worldOrthoVectorFromCenter, slabThicknessValue);
982
+
983
+ const worldVerticalRefPoint: Types.Point3 = [0, 0, 0];
984
+ vtkMath.add(
985
+ stHandlesCenterWorld,
986
+ worldOrthoVectorFromCenter,
987
+ worldVerticalRefPoint
988
+ );
989
+
990
+ // convert vertical world distances in canvas coordinates
991
+ const canvasVerticalRefPoint = viewport.worldToCanvas(
992
+ worldVerticalRefPoint
993
+ );
994
+
995
+ // points for slab thickness lines
996
+ const canvasOrthoVectorFromCenter = vec2.create();
997
+ vec2.subtract(
998
+ canvasOrthoVectorFromCenter,
999
+ stHandlesCenterCanvas,
1000
+ canvasVerticalRefPoint
1001
+ );
1002
+
1003
+ const stLinePointOne = vec2.create();
1004
+ vec2.subtract(
1005
+ stLinePointOne,
1006
+ stHandlesCenterCanvas,
1007
+ canvasVectorFromCenterLong
1008
+ );
1009
+ vec2.add(stLinePointOne, stLinePointOne, canvasOrthoVectorFromCenter);
1010
+
1011
+ const stLinePointTwo = vec2.create();
1012
+ vec2.add(
1013
+ stLinePointTwo,
1014
+ stHandlesCenterCanvas,
1015
+ canvasVectorFromCenterLong
1016
+ );
1017
+ vec2.add(stLinePointTwo, stLinePointTwo, canvasOrthoVectorFromCenter);
1018
+
1019
+ liangBarksyClip(stLinePointOne, stLinePointTwo, canvasBox);
1020
+
1021
+ const stLinePointThree = vec2.create();
1022
+ vec2.add(
1023
+ stLinePointThree,
1024
+ stHandlesCenterCanvas,
1025
+ canvasVectorFromCenterLong
1026
+ );
1027
+ vec2.subtract(
1028
+ stLinePointThree,
1029
+ stLinePointThree,
1030
+ canvasOrthoVectorFromCenter
1031
+ );
1032
+
1033
+ const stLinePointFour = vec2.create();
1034
+ vec2.subtract(
1035
+ stLinePointFour,
1036
+ stHandlesCenterCanvas,
1037
+ canvasVectorFromCenterLong
1038
+ );
1039
+ vec2.subtract(
1040
+ stLinePointFour,
1041
+ stLinePointFour,
1042
+ canvasOrthoVectorFromCenter
1043
+ );
1044
+
1045
+ liangBarksyClip(stLinePointThree, stLinePointFour, canvasBox);
1046
+
1047
+ // points for slab thickness handles
1048
+ const stHandleOne = vec2.create();
1049
+ const stHandleTwo = vec2.create();
1050
+ const stHandleThree = vec2.create();
1051
+ const stHandleFour = vec2.create();
1052
+
1053
+ vec2.subtract(
1054
+ stHandleOne,
1055
+ stHandlesCenterCanvas,
1056
+ canvasVectorFromCenterShort
1057
+ );
1058
+ vec2.add(stHandleOne, stHandleOne, canvasOrthoVectorFromCenter);
1059
+ vec2.add(stHandleTwo, stHandlesCenterCanvas, canvasVectorFromCenterShort);
1060
+ vec2.add(stHandleTwo, stHandleTwo, canvasOrthoVectorFromCenter);
1061
+ vec2.subtract(
1062
+ stHandleThree,
1063
+ stHandlesCenterCanvas,
1064
+ canvasVectorFromCenterShort
1065
+ );
1066
+ vec2.subtract(stHandleThree, stHandleThree, canvasOrthoVectorFromCenter);
1067
+ vec2.add(
1068
+ stHandleFour,
1069
+ stHandlesCenterCanvas,
1070
+ canvasVectorFromCenterShort
1071
+ );
1072
+ vec2.subtract(stHandleFour, stHandleFour, canvasOrthoVectorFromCenter);
1073
+
1074
+ referenceLines.push([
1075
+ otherViewport,
1076
+ refLinePointOne,
1077
+ refLinePointTwo,
1078
+ refLinePointThree,
1079
+ refLinePointFour,
1080
+ stLinePointOne,
1081
+ stLinePointTwo,
1082
+ stLinePointThree,
1083
+ stLinePointFour,
1084
+ rotHandleOne,
1085
+ rotHandleTwo,
1086
+ stHandleOne,
1087
+ stHandleTwo,
1088
+ stHandleThree,
1089
+ stHandleFour,
1090
+ ]);
1091
+ });
1092
+
1093
+ const newRtpoints = [];
1094
+ const newStpoints = [];
1095
+ const viewportColor = this._getReferenceLineColor(viewport.id);
1096
+ const color =
1097
+ viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
1098
+
1099
+ referenceLines.forEach((line, lineIndex) => {
1100
+ // get color for the reference line
1101
+ const otherViewport = line[0];
1102
+ const viewportColor = this._getReferenceLineColor(otherViewport.id);
1103
+ const viewportControllable = this._getReferenceLineControllable(
1104
+ otherViewport.id
1105
+ );
1106
+ const viewportDraggableRotatable =
1107
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
1108
+ const viewportSlabThicknessControlsOn =
1109
+ this._getReferenceLineSlabThicknessControlsOn(otherViewport.id);
1110
+ const selectedViewportId = data.activeViewportIds.find(
1111
+ (id) => id === otherViewport.id
1112
+ );
1113
+
1114
+ let color =
1115
+ viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
1116
+
1117
+ let lineWidth = this.widthStrokeAnnotation;
1118
+
1119
+ const lineActive =
1120
+ data.handles.activeOperation !== null &&
1121
+ data.handles.activeOperation === OPERATION.DRAG &&
1122
+ selectedViewportId;
1123
+
1124
+ if (lineActive) {
1125
+ lineWidth = 2.5;
1126
+ }
1127
+
1128
+ let lineUID = `${lineIndex}`;
1129
+ if (viewportControllable && viewportDraggableRotatable) {
1130
+ lineUID = `${lineIndex}One`;
1131
+ drawLineSvg(
1132
+ svgDrawingHelper,
1133
+ annotationUID,
1134
+ lineUID,
1135
+ line[1],
1136
+ line[2],
1137
+ {
1138
+ color,
1139
+ lineWidth,
1140
+ }
1141
+ );
1142
+
1143
+ lineUID = `${lineIndex}Two`;
1144
+ drawLineSvg(
1145
+ svgDrawingHelper,
1146
+ annotationUID,
1147
+ lineUID,
1148
+ line[3],
1149
+ line[4],
1150
+ {
1151
+ color,
1152
+ lineWidth,
1153
+ }
1154
+ );
1155
+ } else {
1156
+ drawLineSvg(
1157
+ svgDrawingHelper,
1158
+ annotationUID,
1159
+ lineUID,
1160
+ line[2],
1161
+ line[4],
1162
+ {
1163
+ color,
1164
+ lineWidth,
1165
+ }
1166
+ );
1167
+ }
1168
+
1169
+ if (viewportControllable) {
1170
+ color =
1171
+ viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
1172
+
1173
+ const rotHandlesActive =
1174
+ data.handles.activeOperation === OPERATION.ROTATE;
1175
+ const rotationHandles = [line[9], line[10]];
1176
+
1177
+ const rotHandleWorldOne = [
1178
+ viewport.canvasToWorld(line[9]),
1179
+ otherViewport,
1180
+ line[1],
1181
+ line[2],
1182
+ ];
1183
+ const rotHandleWorldTwo = [
1184
+ viewport.canvasToWorld(line[10]),
1185
+ otherViewport,
1186
+ line[3],
1187
+ line[4],
1188
+ ];
1189
+ newRtpoints.push(rotHandleWorldOne, rotHandleWorldTwo);
1190
+
1191
+ const slabThicknessHandlesActive =
1192
+ data.handles.activeOperation === OPERATION.SLAB;
1193
+ const slabThicknessHandles = [line[11], line[12], line[13], line[14]];
1194
+
1195
+ const slabThicknessHandleWorldOne = [
1196
+ viewport.canvasToWorld(line[11]),
1197
+ otherViewport,
1198
+ line[5],
1199
+ line[6],
1200
+ ];
1201
+ const slabThicknessHandleWorldTwo = [
1202
+ viewport.canvasToWorld(line[12]),
1203
+ otherViewport,
1204
+ line[5],
1205
+ line[6],
1206
+ ];
1207
+ const slabThicknessHandleWorldThree = [
1208
+ viewport.canvasToWorld(line[13]),
1209
+ otherViewport,
1210
+ line[7],
1211
+ line[8],
1212
+ ];
1213
+ const slabThicknessHandleWorldFour = [
1214
+ viewport.canvasToWorld(line[14]),
1215
+ otherViewport,
1216
+ line[7],
1217
+ line[8],
1218
+ ];
1219
+ newStpoints.push(
1220
+ slabThicknessHandleWorldOne,
1221
+ slabThicknessHandleWorldTwo,
1222
+ slabThicknessHandleWorldThree,
1223
+ slabThicknessHandleWorldFour
1224
+ );
1225
+
1226
+ if (
1227
+ lineActive &&
1228
+ !rotHandlesActive &&
1229
+ !slabThicknessHandlesActive &&
1230
+ viewportDraggableRotatable &&
1231
+ viewportSlabThicknessControlsOn
1232
+ ) {
1233
+ // draw all handles inactive (rotation and slab thickness)
1234
+ let handleUID = `${lineIndex}One`;
1235
+ drawHandlesSvg(
1236
+ svgDrawingHelper,
1237
+ annotationUID,
1238
+ handleUID,
1239
+ rotationHandles,
1240
+ {
1241
+ color,
1242
+ handleRadius: 3,
1243
+ type: 'circle',
1244
+ }
1245
+ );
1246
+ handleUID = `${lineIndex}Two`;
1247
+ drawHandlesSvg(
1248
+ svgDrawingHelper,
1249
+ annotationUID,
1250
+ handleUID,
1251
+ slabThicknessHandles,
1252
+ {
1253
+ color,
1254
+ handleRadius: 3,
1255
+ type: 'rect',
1256
+ }
1257
+ );
1258
+ } else if (
1259
+ lineActive &&
1260
+ !rotHandlesActive &&
1261
+ !slabThicknessHandlesActive &&
1262
+ viewportDraggableRotatable
1263
+ ) {
1264
+ const handleUID = `${lineIndex}`;
1265
+ // draw rotation handles inactive
1266
+ drawHandlesSvg(
1267
+ svgDrawingHelper,
1268
+ annotationUID,
1269
+ handleUID,
1270
+ rotationHandles,
1271
+ {
1272
+ color,
1273
+ handleRadius: 3,
1274
+ type: 'circle',
1275
+ }
1276
+ );
1277
+ } else if (
1278
+ selectedViewportId &&
1279
+ !rotHandlesActive &&
1280
+ !slabThicknessHandlesActive &&
1281
+ viewportSlabThicknessControlsOn
1282
+ ) {
1283
+ const handleUID = `${lineIndex}`;
1284
+ // draw slab thickness handles inactive
1285
+ drawHandlesSvg(
1286
+ svgDrawingHelper,
1287
+ annotationUID,
1288
+ handleUID,
1289
+ slabThicknessHandles,
1290
+ {
1291
+ color,
1292
+ handleRadius: 3,
1293
+ type: 'rect',
1294
+ }
1295
+ );
1296
+ } else if (rotHandlesActive && viewportDraggableRotatable) {
1297
+ const handleUID = `${lineIndex}`;
1298
+ // draw all rotation handles as active
1299
+ drawHandlesSvg(
1300
+ svgDrawingHelper,
1301
+ annotationUID,
1302
+ handleUID,
1303
+ rotationHandles,
1304
+ {
1305
+ color,
1306
+ handleRadius: 2,
1307
+ fill: color,
1308
+ type: 'circle',
1309
+ }
1310
+ );
1311
+ } else if (
1312
+ slabThicknessHandlesActive &&
1313
+ selectedViewportId &&
1314
+ viewportSlabThicknessControlsOn
1315
+ ) {
1316
+ // draw only the slab thickness handles for the active viewport as active
1317
+ drawHandlesSvg(
1318
+ svgDrawingHelper,
1319
+ annotationUID,
1320
+ lineUID,
1321
+ slabThicknessHandles,
1322
+ {
1323
+ color,
1324
+ handleRadius: 2,
1325
+ fill: color,
1326
+ type: 'rect',
1327
+ }
1328
+ );
1329
+ }
1330
+ const slabThicknessValue = otherViewport.getSlabThickness();
1331
+ if (slabThicknessValue > 0.5 && viewportSlabThicknessControlsOn) {
1332
+ // draw slab thickness reference lines
1333
+ lineUID = `${lineIndex}STOne`;
1334
+ drawLineSvg(
1335
+ svgDrawingHelper,
1336
+ annotationUID,
1337
+ lineUID,
1338
+ line[5],
1339
+ line[6],
1340
+ {
1341
+ color,
1342
+ width: 1,
1343
+ lineDash: [2, 3],
1344
+ }
1345
+ );
1346
+
1347
+ lineUID = `${lineIndex}STTwo`;
1348
+ drawLineSvg(
1349
+ svgDrawingHelper,
1350
+ annotationUID,
1351
+ lineUID,
1352
+ line[7],
1353
+ line[8],
1354
+ {
1355
+ color,
1356
+ width: line,
1357
+ lineDash: [2, 3],
1358
+ }
1359
+ );
1360
+ }
1361
+ }
1362
+ });
1363
+
1364
+ renderStatus = true;
1365
+
1366
+ // Save new handles points in annotation
1367
+ data.handles.rotationPoints = newRtpoints;
1368
+ data.handles.slabThicknessPoints = newStpoints;
1369
+
1370
+ if (this.configuration.viewportIndicators) {
1371
+ // render a circle to pin point the viewport color
1372
+ // TODO: This should not be part of the tool, and definitely not part of the renderAnnotation loop
1373
+ const referenceColorCoordinates = [
1374
+ clientWidth * 0.95,
1375
+ clientHeight * 0.05,
1376
+ ] as Types.Point2;
1377
+ const circleRadius = canvasDiagonalLength * 0.01;
1378
+
1379
+ const circleUID = '0';
1380
+ drawCircleSvg(
1381
+ svgDrawingHelper,
1382
+ annotationUID,
1383
+ circleUID,
1384
+ referenceColorCoordinates,
1385
+ circleRadius,
1386
+ { color, fill: color }
1387
+ );
1388
+ }
1389
+
1390
+ return renderStatus;
1391
+ };
1392
+
1393
+ _getAnnotations = (enabledElement: Types.IEnabledElement) => {
1394
+ const { viewport } = enabledElement;
1395
+ return getAnnotations(this.getToolName(), viewport.element);
1396
+ };
1397
+
1398
+ _onNewVolume = (e: any) => {
1399
+ const viewportsInfo = this._getViewportsInfo();
1400
+ this.computeToolCenter(viewportsInfo);
1401
+ };
1402
+
1403
+ _unsubscribeToViewportNewVolumeSet(viewportsInfo) {
1404
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
1405
+ const { viewport } = getEnabledElementByIds(
1406
+ viewportId,
1407
+ renderingEngineId
1408
+ );
1409
+ const { element } = viewport;
1410
+
1411
+ element.removeEventListener(
1412
+ Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
1413
+ this._onNewVolume
1414
+ );
1415
+ });
1416
+ }
1417
+
1418
+ _subscribeToViewportNewVolumeSet(viewports) {
1419
+ viewports.forEach(({ viewportId, renderingEngineId }) => {
1420
+ const { viewport } = getEnabledElementByIds(
1421
+ viewportId,
1422
+ renderingEngineId
1423
+ );
1424
+ const { element } = viewport;
1425
+
1426
+ element.addEventListener(
1427
+ Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
1428
+ this._onNewVolume
1429
+ );
1430
+ });
1431
+ }
1432
+
1433
+ _autoPanViewportIfNecessary(
1434
+ viewportId: string,
1435
+ renderingEngine: Types.IRenderingEngine
1436
+ ): void {
1437
+ // 1. Compute the current world bounding box of the viewport from corner to corner
1438
+ // 2. Check if the toolCenter is outside of the world bounding box
1439
+ // 3. If it is outside, pan the viewport to fit in the toolCenter
1440
+
1441
+ const viewport = renderingEngine.getViewport(viewportId);
1442
+ const { clientWidth, clientHeight } = viewport.canvas;
1443
+ const topLefWorld = viewport.canvasToWorld([0, 0]);
1444
+ const bottomRightWorld = viewport.canvasToWorld([
1445
+ clientWidth,
1446
+ clientHeight,
1447
+ ]);
1448
+ const topRightWorld = viewport.canvasToWorld([clientWidth, 0]);
1449
+ const bottomLeftWorld = viewport.canvasToWorld([0, clientHeight]);
1450
+
1451
+ // find the minimum and maximum world coordinates in each x,y,z
1452
+ const minX = Math.min(
1453
+ topLefWorld[0],
1454
+ bottomRightWorld[0],
1455
+ topRightWorld[0],
1456
+ bottomLeftWorld[0]
1457
+ );
1458
+ const maxX = Math.max(
1459
+ topLefWorld[0],
1460
+ bottomRightWorld[0],
1461
+ topRightWorld[0],
1462
+ bottomLeftWorld[0]
1463
+ );
1464
+ const minY = Math.min(
1465
+ topLefWorld[1],
1466
+ bottomRightWorld[1],
1467
+ topRightWorld[1],
1468
+ bottomLeftWorld[1]
1469
+ );
1470
+ const maxY = Math.max(
1471
+ topLefWorld[1],
1472
+ bottomRightWorld[1],
1473
+ topRightWorld[1],
1474
+ bottomLeftWorld[1]
1475
+ );
1476
+ const minZ = Math.min(
1477
+ topLefWorld[2],
1478
+ bottomRightWorld[2],
1479
+ topRightWorld[2],
1480
+ bottomLeftWorld[2]
1481
+ );
1482
+ const maxZ = Math.max(
1483
+ topLefWorld[2],
1484
+ bottomRightWorld[2],
1485
+ topRightWorld[2],
1486
+ bottomLeftWorld[2]
1487
+ );
1488
+
1489
+ // pan the viewport to fit the toolCenter in the direction
1490
+ // that is out of bounds
1491
+ let deltaPointsWorld;
1492
+ const pan = this.configuration.autoPan.panSize;
1493
+
1494
+ if (this.toolCenter[0] < minX - EPSILON) {
1495
+ deltaPointsWorld = [minX - this.toolCenter[0] + pan, 0, 0];
1496
+ } else if (this.toolCenter[0] > maxX + EPSILON) {
1497
+ deltaPointsWorld = [maxX - this.toolCenter[0] - pan, 0, 0];
1498
+ } else if (this.toolCenter[1] < minY - EPSILON) {
1499
+ deltaPointsWorld = [0, minY - this.toolCenter[1] + pan, 0];
1500
+ } else if (this.toolCenter[1] > maxY + EPSILON) {
1501
+ deltaPointsWorld = [0, maxY - this.toolCenter[1] - pan, 0];
1502
+ } else if (this.toolCenter[2] < minZ - EPSILON) {
1503
+ deltaPointsWorld = [0, 0, minZ - this.toolCenter[2] + pan];
1504
+ } else if (this.toolCenter[2] > maxZ + EPSILON) {
1505
+ deltaPointsWorld = [0, 0, maxZ - this.toolCenter[2] - pan];
1506
+ } else {
1507
+ return;
1508
+ }
1509
+
1510
+ const camera = viewport.getCamera();
1511
+ const { focalPoint, position } = camera;
1512
+
1513
+ const updatedPosition = <Types.Point3>[
1514
+ position[0] - deltaPointsWorld[0],
1515
+ position[1] - deltaPointsWorld[1],
1516
+ position[2] - deltaPointsWorld[2],
1517
+ ];
1518
+
1519
+ const updatedFocalPoint = <Types.Point3>[
1520
+ focalPoint[0] - deltaPointsWorld[0],
1521
+ focalPoint[1] - deltaPointsWorld[1],
1522
+ focalPoint[2] - deltaPointsWorld[2],
1523
+ ];
1524
+
1525
+ viewport.setCamera({
1526
+ focalPoint: updatedFocalPoint,
1527
+ position: updatedPosition,
1528
+ });
1529
+
1530
+ viewport.render();
1531
+ }
1532
+
1533
+ _areViewportIdArraysEqual = (viewportIdArrayOne, viewportIdArrayTwo) => {
1534
+ if (viewportIdArrayOne.length !== viewportIdArrayTwo.length) {
1535
+ return false;
1536
+ }
1537
+
1538
+ viewportIdArrayOne.forEach((id) => {
1539
+ let itemFound = false;
1540
+ for (let i = 0; i < viewportIdArrayTwo.length; ++i) {
1541
+ if (id === viewportIdArrayTwo[i]) {
1542
+ itemFound = true;
1543
+ break;
1544
+ }
1545
+ }
1546
+ if (itemFound === false) {
1547
+ return false;
1548
+ }
1549
+ });
1550
+
1551
+ return true;
1552
+ };
1553
+
1554
+ // It filters the viewports with crosshairs and only return viewports
1555
+ // that have different camera.
1556
+ _getAnnotationsForViewportsWithDifferentCameras = (
1557
+ enabledElement,
1558
+ annotations
1559
+ ) => {
1560
+ const { viewportId, renderingEngine, viewport } = enabledElement;
1561
+
1562
+ const otherViewportAnnotations = annotations.filter(
1563
+ (annotation) => annotation.data.viewportId !== viewportId
1564
+ );
1565
+
1566
+ if (!otherViewportAnnotations || !otherViewportAnnotations.length) {
1567
+ return [];
1568
+ }
1569
+
1570
+ const camera = viewport.getCamera();
1571
+ const { viewPlaneNormal, position } = camera;
1572
+
1573
+ const viewportsWithDifferentCameras = otherViewportAnnotations.filter(
1574
+ (annotation) => {
1575
+ const { viewportId } = annotation.data;
1576
+ const targetViewport = renderingEngine.getViewport(viewportId);
1577
+ const cameraOfTarget = targetViewport.getCamera();
1578
+
1579
+ return !(
1580
+ csUtils.isEqual(
1581
+ cameraOfTarget.viewPlaneNormal,
1582
+ viewPlaneNormal,
1583
+ 1e-2
1584
+ ) && csUtils.isEqual(cameraOfTarget.position, position, 1)
1585
+ );
1586
+ }
1587
+ );
1588
+
1589
+ return viewportsWithDifferentCameras;
1590
+ };
1591
+
1592
+ _filterViewportWithSameOrientation = (
1593
+ enabledElement,
1594
+ referenceAnnotation,
1595
+ annotations
1596
+ ) => {
1597
+ const { renderingEngine } = enabledElement;
1598
+ const { data } = referenceAnnotation;
1599
+ const viewport = renderingEngine.getViewport(data.viewportId);
1600
+
1601
+ const linkedViewportAnnotations = annotations.filter((annotation) => {
1602
+ const { data } = annotation;
1603
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
1604
+ const otherViewportControllable = this._getReferenceLineControllable(
1605
+ otherViewport.id
1606
+ );
1607
+
1608
+ return otherViewportControllable === true;
1609
+ });
1610
+
1611
+ if (!linkedViewportAnnotations || !linkedViewportAnnotations.length) {
1612
+ return [];
1613
+ }
1614
+
1615
+ const camera = viewport.getCamera();
1616
+ const viewPlaneNormal = camera.viewPlaneNormal;
1617
+ vtkMath.normalize(viewPlaneNormal);
1618
+
1619
+ const otherViewportsAnnotationsWithSameCameraDirection =
1620
+ linkedViewportAnnotations.filter((annotation) => {
1621
+ const { viewportId } = annotation.data;
1622
+ const otherViewport = renderingEngine.getViewport(viewportId);
1623
+ const otherCamera = otherViewport.getCamera();
1624
+ const otherViewPlaneNormal = otherCamera.viewPlaneNormal;
1625
+ vtkMath.normalize(otherViewPlaneNormal);
1626
+
1627
+ return (
1628
+ csUtils.isEqual(viewPlaneNormal, otherViewPlaneNormal, 1e-2) &&
1629
+ csUtils.isEqual(camera.viewUp, otherCamera.viewUp, 1e-2)
1630
+ );
1631
+ });
1632
+
1633
+ return otherViewportsAnnotationsWithSameCameraDirection;
1634
+ };
1635
+
1636
+ _filterAnnotationsByUniqueViewportOrientations = (
1637
+ enabledElement,
1638
+ annotations
1639
+ ) => {
1640
+ const { renderingEngine, viewport } = enabledElement;
1641
+ const camera = viewport.getCamera();
1642
+ const viewPlaneNormal = camera.viewPlaneNormal;
1643
+ vtkMath.normalize(viewPlaneNormal);
1644
+
1645
+ const otherLinkedViewportAnnotationsFromSameScene = annotations.filter(
1646
+ (annotation) => {
1647
+ const { data } = annotation;
1648
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
1649
+ const otherViewportControllable = this._getReferenceLineControllable(
1650
+ otherViewport.id
1651
+ );
1652
+
1653
+ return (
1654
+ viewport !== otherViewport &&
1655
+ // scene === otherScene &&
1656
+ otherViewportControllable === true
1657
+ );
1658
+ }
1659
+ );
1660
+
1661
+ const otherViewportsAnnotationsWithUniqueCameras = [];
1662
+ // Iterate first on other viewport from the same scene linked
1663
+ for (
1664
+ let i = 0;
1665
+ i < otherLinkedViewportAnnotationsFromSameScene.length;
1666
+ ++i
1667
+ ) {
1668
+ const annotation = otherLinkedViewportAnnotationsFromSameScene[i];
1669
+ const { viewportId } = annotation.data;
1670
+ const otherViewport = renderingEngine.getViewport(viewportId);
1671
+ const otherCamera = otherViewport.getCamera();
1672
+ const otherViewPlaneNormal = otherCamera.viewPlaneNormal;
1673
+ vtkMath.normalize(otherViewPlaneNormal);
1674
+
1675
+ if (
1676
+ csUtils.isEqual(viewPlaneNormal, otherViewPlaneNormal, 1e-2) ||
1677
+ csUtils.isOpposite(viewPlaneNormal, otherViewPlaneNormal, 1e-2)
1678
+ ) {
1679
+ continue;
1680
+ }
1681
+
1682
+ let cameraFound = false;
1683
+ for (
1684
+ let jj = 0;
1685
+ jj < otherViewportsAnnotationsWithUniqueCameras.length;
1686
+ ++jj
1687
+ ) {
1688
+ const annotation = otherViewportsAnnotationsWithUniqueCameras[jj];
1689
+ const { viewportId } = annotation.data;
1690
+ const stockedViewport = renderingEngine.getViewport(viewportId);
1691
+ const cameraOfStocked = stockedViewport.getCamera();
1692
+
1693
+ if (
1694
+ csUtils.isEqual(
1695
+ cameraOfStocked.viewPlaneNormal,
1696
+ otherCamera.viewPlaneNormal,
1697
+ 1e-2
1698
+ ) &&
1699
+ csUtils.isEqual(cameraOfStocked.position, otherCamera.position, 1)
1700
+ ) {
1701
+ cameraFound = true;
1702
+ }
1703
+ }
1704
+
1705
+ if (!cameraFound) {
1706
+ otherViewportsAnnotationsWithUniqueCameras.push(annotation);
1707
+ }
1708
+ }
1709
+
1710
+ const otherNonLinkedViewportAnnotationsFromSameScene = annotations.filter(
1711
+ (annotation) => {
1712
+ const { data } = annotation;
1713
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
1714
+ const otherViewportControllable = this._getReferenceLineControllable(
1715
+ otherViewport.id
1716
+ );
1717
+
1718
+ return (
1719
+ viewport !== otherViewport &&
1720
+ // scene === otherScene &&
1721
+ otherViewportControllable !== true
1722
+ );
1723
+ }
1724
+ );
1725
+
1726
+ // Iterate second on other viewport from the same scene non linked
1727
+ for (
1728
+ let i = 0;
1729
+ i < otherNonLinkedViewportAnnotationsFromSameScene.length;
1730
+ ++i
1731
+ ) {
1732
+ const annotation = otherNonLinkedViewportAnnotationsFromSameScene[i];
1733
+ const { viewportId } = annotation.data;
1734
+ const otherViewport = renderingEngine.getViewport(viewportId);
1735
+
1736
+ const otherCamera = otherViewport.getCamera();
1737
+ const otherViewPlaneNormal = otherCamera.viewPlaneNormal;
1738
+ vtkMath.normalize(otherViewPlaneNormal);
1739
+
1740
+ if (
1741
+ csUtils.isEqual(viewPlaneNormal, otherViewPlaneNormal, 1e-2) ||
1742
+ csUtils.isOpposite(viewPlaneNormal, otherViewPlaneNormal, 1e-2)
1743
+ ) {
1744
+ continue;
1745
+ }
1746
+
1747
+ let cameraFound = false;
1748
+ for (
1749
+ let jj = 0;
1750
+ jj < otherViewportsAnnotationsWithUniqueCameras.length;
1751
+ ++jj
1752
+ ) {
1753
+ const annotation = otherViewportsAnnotationsWithUniqueCameras[jj];
1754
+ const { viewportId } = annotation.data;
1755
+ const stockedViewport = renderingEngine.getViewport(viewportId);
1756
+ const cameraOfStocked = stockedViewport.getCamera();
1757
+
1758
+ if (
1759
+ csUtils.isEqual(
1760
+ cameraOfStocked.viewPlaneNormal,
1761
+ otherCamera.viewPlaneNormal,
1762
+ 1e-2
1763
+ ) &&
1764
+ csUtils.isEqual(cameraOfStocked.position, otherCamera.position, 1)
1765
+ ) {
1766
+ cameraFound = true;
1767
+ }
1768
+ }
1769
+
1770
+ if (!cameraFound) {
1771
+ otherViewportsAnnotationsWithUniqueCameras.push(annotation);
1772
+ }
1773
+ }
1774
+
1775
+ // Iterate on all the viewport
1776
+ const otherViewportAnnotations =
1777
+ this._getAnnotationsForViewportsWithDifferentCameras(
1778
+ enabledElement,
1779
+ annotations
1780
+ );
1781
+
1782
+ for (let i = 0; i < otherViewportAnnotations.length; ++i) {
1783
+ const annotation = otherViewportAnnotations[i];
1784
+ if (
1785
+ otherViewportsAnnotationsWithUniqueCameras.find(
1786
+ (element) => element === annotation
1787
+ ) === true
1788
+ ) {
1789
+ continue;
1790
+ }
1791
+
1792
+ const { viewportId } = annotation.data;
1793
+ const otherViewport = renderingEngine.getViewport(viewportId);
1794
+ const otherCamera = otherViewport.getCamera();
1795
+ const otherViewPlaneNormal = otherCamera.viewPlaneNormal;
1796
+ vtkMath.normalize(otherViewPlaneNormal);
1797
+
1798
+ if (
1799
+ csUtils.isEqual(viewPlaneNormal, otherViewPlaneNormal, 1e-2) ||
1800
+ csUtils.isOpposite(viewPlaneNormal, otherViewPlaneNormal, 1e-2)
1801
+ ) {
1802
+ continue;
1803
+ }
1804
+
1805
+ let cameraFound = false;
1806
+ for (
1807
+ let jj = 0;
1808
+ jj < otherViewportsAnnotationsWithUniqueCameras.length;
1809
+ ++jj
1810
+ ) {
1811
+ const annotation = otherViewportsAnnotationsWithUniqueCameras[jj];
1812
+ const { viewportId } = annotation.data;
1813
+ const stockedViewport = renderingEngine.getViewport(viewportId);
1814
+ const cameraOfStocked = stockedViewport.getCamera();
1815
+
1816
+ if (
1817
+ csUtils.isEqual(
1818
+ cameraOfStocked.viewPlaneNormal,
1819
+ otherCamera.viewPlaneNormal,
1820
+ 1e-2
1821
+ ) &&
1822
+ csUtils.isEqual(cameraOfStocked.position, otherCamera.position, 1)
1823
+ ) {
1824
+ cameraFound = true;
1825
+ }
1826
+ }
1827
+
1828
+ if (!cameraFound) {
1829
+ otherViewportsAnnotationsWithUniqueCameras.push(annotation);
1830
+ }
1831
+ }
1832
+
1833
+ return otherViewportsAnnotationsWithUniqueCameras;
1834
+ };
1835
+
1836
+ _checkIfViewportsRenderingSameScene = (viewport, otherViewport) => {
1837
+ const actors = viewport.getActors();
1838
+ const otherViewportActors = otherViewport.getActors();
1839
+
1840
+ let sameScene = true;
1841
+
1842
+ actors.forEach((actor) => {
1843
+ if (
1844
+ actors.length !== otherViewportActors.length ||
1845
+ otherViewportActors.find(({ uid }) => uid === actor.uid) === undefined
1846
+ ) {
1847
+ sameScene = false;
1848
+ }
1849
+ });
1850
+
1851
+ return sameScene;
1852
+ };
1853
+
1854
+ _jump = (enabledElement, jumpWorld) => {
1855
+ state.isInteractingWithTool = true;
1856
+ const { viewport, renderingEngine } = enabledElement;
1857
+
1858
+ const annotations = this._getAnnotations(enabledElement);
1859
+
1860
+ const delta: Types.Point3 = [0, 0, 0];
1861
+ vtkMath.subtract(jumpWorld, this.toolCenter, delta);
1862
+
1863
+ // TRANSLATION
1864
+ // get the annotation of the other viewport which are parallel to the delta shift and are of the same scene
1865
+ const otherViewportAnnotations =
1866
+ this._getAnnotationsForViewportsWithDifferentCameras(
1867
+ enabledElement,
1868
+ annotations
1869
+ );
1870
+
1871
+ const viewportsAnnotationsToUpdate = otherViewportAnnotations.filter(
1872
+ (annotation) => {
1873
+ const { data } = annotation;
1874
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
1875
+
1876
+ const sameScene = this._checkIfViewportsRenderingSameScene(
1877
+ viewport,
1878
+ otherViewport
1879
+ );
1880
+
1881
+ return (
1882
+ this._getReferenceLineControllable(otherViewport.id) &&
1883
+ this._getReferenceLineDraggableRotatable(otherViewport.id) &&
1884
+ sameScene
1885
+ );
1886
+ }
1887
+ );
1888
+
1889
+ if (viewportsAnnotationsToUpdate.length === 0) {
1890
+ state.isInteractingWithTool = false;
1891
+ return false;
1892
+ }
1893
+
1894
+ this._applyDeltaShiftToSelectedViewportCameras(
1895
+ renderingEngine,
1896
+ viewportsAnnotationsToUpdate,
1897
+ delta
1898
+ );
1899
+
1900
+ state.isInteractingWithTool = false;
1901
+
1902
+ return true;
1903
+ };
1904
+
1905
+ _activateModify = (element) => {
1906
+ state.isInteractingWithTool = true;
1907
+
1908
+ element.addEventListener(Events.MOUSE_UP, this._endCallback);
1909
+ element.addEventListener(Events.MOUSE_DRAG, this._dragCallback);
1910
+ element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
1911
+
1912
+ element.addEventListener(Events.TOUCH_END, this._endCallback);
1913
+ element.addEventListener(Events.TOUCH_DRAG, this._dragCallback);
1914
+ element.addEventListener(Events.TOUCH_TAP, this._endCallback);
1915
+ };
1916
+
1917
+ _deactivateModify = (element) => {
1918
+ state.isInteractingWithTool = false;
1919
+
1920
+ element.removeEventListener(Events.MOUSE_UP, this._endCallback);
1921
+ element.removeEventListener(Events.MOUSE_DRAG, this._dragCallback);
1922
+ element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
1923
+
1924
+ element.removeEventListener(Events.TOUCH_END, this._endCallback);
1925
+ element.removeEventListener(Events.TOUCH_DRAG, this._dragCallback);
1926
+ element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
1927
+ };
1928
+
1929
+ _endCallback = (evt: EventTypes.InteractionEventType) => {
1930
+ const eventDetail = evt.detail;
1931
+ const { element } = eventDetail;
1932
+
1933
+ this.editData.annotation.data.handles.activeOperation = null;
1934
+ this.editData.annotation.data.activeViewportIds = [];
1935
+
1936
+ this._deactivateModify(element);
1937
+
1938
+ resetElementCursor(element);
1939
+
1940
+ this.editData = null;
1941
+
1942
+ const enabledElement = getEnabledElement(element);
1943
+ const { renderingEngine } = enabledElement;
1944
+
1945
+ const requireSameOrientation = false;
1946
+ const viewportIdsToRender = getViewportIdsWithToolToRender(
1947
+ element,
1948
+ this.getToolName(),
1949
+ requireSameOrientation
1950
+ );
1951
+
1952
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
1953
+ };
1954
+
1955
+ _dragCallback = (evt: EventTypes.InteractionEventType) => {
1956
+ const eventDetail = evt.detail;
1957
+ const delta = eventDetail.deltaPoints.world;
1958
+
1959
+ if (
1960
+ Math.abs(delta[0]) < 1e-3 &&
1961
+ Math.abs(delta[1]) < 1e-3 &&
1962
+ Math.abs(delta[2]) < 1e-3
1963
+ ) {
1964
+ return;
1965
+ }
1966
+
1967
+ const { element } = eventDetail;
1968
+ const enabledElement = getEnabledElement(element);
1969
+ const { renderingEngine, viewport } = enabledElement;
1970
+ const annotations = this._getAnnotations(
1971
+ enabledElement
1972
+ ) as CrosshairsAnnotation[];
1973
+ const filteredToolAnnotations =
1974
+ this.filterInteractableAnnotationsForElement(element, annotations);
1975
+
1976
+ // viewport Annotation
1977
+ const viewportAnnotation = filteredToolAnnotations[0];
1978
+ if (!viewportAnnotation) {
1979
+ return;
1980
+ }
1981
+
1982
+ const { handles } = viewportAnnotation.data;
1983
+ const { currentPoints } = evt.detail;
1984
+ const canvasCoords = currentPoints.canvas;
1985
+
1986
+ if (handles.activeOperation === OPERATION.DRAG) {
1987
+ // TRANSLATION
1988
+ // get the annotation of the other viewport which are parallel to the delta shift and are of the same scene
1989
+ const otherViewportAnnotations =
1990
+ this._getAnnotationsForViewportsWithDifferentCameras(
1991
+ enabledElement,
1992
+ annotations
1993
+ );
1994
+
1995
+ const viewportsAnnotationsToUpdate = otherViewportAnnotations.filter(
1996
+ (annotation) => {
1997
+ const { data } = annotation;
1998
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
1999
+ const otherViewportControllable = this._getReferenceLineControllable(
2000
+ otherViewport.id
2001
+ );
2002
+ const otherViewportDraggableRotatable =
2003
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
2004
+
2005
+ return (
2006
+ otherViewportControllable === true &&
2007
+ otherViewportDraggableRotatable === true &&
2008
+ viewportAnnotation.data.activeViewportIds.find(
2009
+ (id) => id === otherViewport.id
2010
+ )
2011
+ );
2012
+ }
2013
+ );
2014
+
2015
+ this._applyDeltaShiftToSelectedViewportCameras(
2016
+ renderingEngine,
2017
+ viewportsAnnotationsToUpdate,
2018
+ delta
2019
+ );
2020
+ } else if (handles.activeOperation === OPERATION.ROTATE) {
2021
+ // ROTATION
2022
+ const otherViewportAnnotations =
2023
+ this._getAnnotationsForViewportsWithDifferentCameras(
2024
+ enabledElement,
2025
+ annotations
2026
+ );
2027
+
2028
+ const viewportsAnnotationsToUpdate = otherViewportAnnotations.filter(
2029
+ (annotation) => {
2030
+ const { data } = annotation;
2031
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
2032
+ const otherViewportControllable = this._getReferenceLineControllable(
2033
+ otherViewport.id
2034
+ );
2035
+ const otherViewportDraggableRotatable =
2036
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
2037
+
2038
+ return (
2039
+ otherViewportControllable === true &&
2040
+ otherViewportDraggableRotatable === true
2041
+ );
2042
+ }
2043
+ );
2044
+
2045
+ const dir1 = vec2.create();
2046
+ const dir2 = vec2.create();
2047
+
2048
+ const center: Types.Point3 = [
2049
+ this.toolCenter[0],
2050
+ this.toolCenter[1],
2051
+ this.toolCenter[2],
2052
+ ];
2053
+
2054
+ const centerCanvas = viewport.worldToCanvas(center);
2055
+
2056
+ const finalPointCanvas = eventDetail.currentPoints.canvas;
2057
+ const originalPointCanvas = vec2.create();
2058
+ vec2.sub(
2059
+ originalPointCanvas,
2060
+ finalPointCanvas,
2061
+ eventDetail.deltaPoints.canvas
2062
+ );
2063
+ vec2.sub(dir1, originalPointCanvas, <vec2>centerCanvas);
2064
+ vec2.sub(dir2, finalPointCanvas, <vec2>centerCanvas);
2065
+
2066
+ let angle = vec2.angle(dir1, dir2);
2067
+
2068
+ if (
2069
+ this._isClockWise(centerCanvas, originalPointCanvas, finalPointCanvas)
2070
+ ) {
2071
+ angle *= -1;
2072
+ }
2073
+
2074
+ // Rounding the angle to allow rotated handles to be undone
2075
+ // If we don't round and rotate handles clockwise by 0.0131233 radians,
2076
+ // there's no assurance that the counter-clockwise rotation occurs at
2077
+ // precisely -0.0131233, resulting in the drawn annotations being lost.
2078
+ angle = Math.round(angle * 100) / 100;
2079
+
2080
+ const rotationAxis = viewport.getCamera().viewPlaneNormal;
2081
+ // @ts-ignore : vtkjs incorrect typing
2082
+ const { matrix } = vtkMatrixBuilder
2083
+ .buildFromRadian()
2084
+ .translate(center[0], center[1], center[2])
2085
+ // @ts-ignore
2086
+ .rotate(angle, rotationAxis) //todo: why we are passing
2087
+ .translate(-center[0], -center[1], -center[2]);
2088
+
2089
+ const otherViewportsIds = [];
2090
+ // update camera for the other viewports.
2091
+ // NOTE: The lines then are rendered by the onCameraModified
2092
+ viewportsAnnotationsToUpdate.forEach((annotation) => {
2093
+ const { data } = annotation;
2094
+ data.handles.toolCenter = center;
2095
+
2096
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
2097
+ const camera = otherViewport.getCamera();
2098
+ const { viewUp, position, focalPoint } = camera;
2099
+
2100
+ viewUp[0] += position[0];
2101
+ viewUp[1] += position[1];
2102
+ viewUp[2] += position[2];
2103
+
2104
+ vec3.transformMat4(focalPoint, focalPoint, matrix);
2105
+ vec3.transformMat4(position, position, matrix);
2106
+ vec3.transformMat4(viewUp, viewUp, matrix);
2107
+
2108
+ viewUp[0] -= position[0];
2109
+ viewUp[1] -= position[1];
2110
+ viewUp[2] -= position[2];
2111
+
2112
+ otherViewport.setCamera({
2113
+ position,
2114
+ viewUp,
2115
+ focalPoint,
2116
+ });
2117
+ otherViewportsIds.push(otherViewport.id);
2118
+ });
2119
+ renderingEngine.renderViewports(otherViewportsIds);
2120
+ } else if (handles.activeOperation === OPERATION.SLAB) {
2121
+ // SLAB THICKNESS
2122
+ // this should be just the active one under the mouse,
2123
+ const otherViewportAnnotations =
2124
+ this._getAnnotationsForViewportsWithDifferentCameras(
2125
+ enabledElement,
2126
+ annotations
2127
+ );
2128
+
2129
+ const referenceAnnotations = otherViewportAnnotations.filter(
2130
+ (annotation) => {
2131
+ const { data } = annotation;
2132
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
2133
+ const otherViewportControllable = this._getReferenceLineControllable(
2134
+ otherViewport.id
2135
+ );
2136
+ const otherViewportSlabThicknessControlsOn =
2137
+ this._getReferenceLineSlabThicknessControlsOn(otherViewport.id);
2138
+
2139
+ return (
2140
+ otherViewportControllable === true &&
2141
+ otherViewportSlabThicknessControlsOn === true &&
2142
+ viewportAnnotation.data.activeViewportIds.find(
2143
+ (id) => id === otherViewport.id
2144
+ )
2145
+ );
2146
+ }
2147
+ );
2148
+
2149
+ if (referenceAnnotations.length === 0) {
2150
+ return;
2151
+ }
2152
+ const viewportsAnnotationsToUpdate =
2153
+ this._filterViewportWithSameOrientation(
2154
+ enabledElement,
2155
+ referenceAnnotations[0],
2156
+ annotations
2157
+ );
2158
+
2159
+ const viewportsIds = [];
2160
+ viewportsIds.push(viewport.id);
2161
+ viewportsAnnotationsToUpdate.forEach(
2162
+ (annotation: CrosshairsAnnotation) => {
2163
+ const { data } = annotation;
2164
+
2165
+ const otherViewport = renderingEngine.getViewport(
2166
+ data.viewportId
2167
+ ) as Types.IVolumeViewport;
2168
+ const camera = otherViewport.getCamera();
2169
+ const normal = camera.viewPlaneNormal;
2170
+
2171
+ const dotProd = vtkMath.dot(delta, normal);
2172
+ const projectedDelta: Types.Point3 = [...normal];
2173
+ vtkMath.multiplyScalar(projectedDelta, dotProd);
2174
+
2175
+ if (
2176
+ Math.abs(projectedDelta[0]) > 1e-3 ||
2177
+ Math.abs(projectedDelta[1]) > 1e-3 ||
2178
+ Math.abs(projectedDelta[2]) > 1e-3
2179
+ ) {
2180
+ const mod = Math.sqrt(
2181
+ projectedDelta[0] * projectedDelta[0] +
2182
+ projectedDelta[1] * projectedDelta[1] +
2183
+ projectedDelta[2] * projectedDelta[2]
2184
+ );
2185
+
2186
+ const currentPoint = eventDetail.lastPoints.world;
2187
+ const direction: Types.Point3 = [0, 0, 0];
2188
+
2189
+ const currentCenter: Types.Point3 = [
2190
+ this.toolCenter[0],
2191
+ this.toolCenter[1],
2192
+ this.toolCenter[2],
2193
+ ];
2194
+
2195
+ // use this.toolCenter only if viewportDraggableRotatable
2196
+ const viewportDraggableRotatable =
2197
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
2198
+ if (!viewportDraggableRotatable) {
2199
+ const { rotationPoints } = this.editData.annotation.data.handles;
2200
+ // Todo: what is a point uid?
2201
+ const otherViewportRotationPoints = rotationPoints.filter(
2202
+ (point) => point[1].uid === otherViewport.id
2203
+ );
2204
+ if (otherViewportRotationPoints.length === 2) {
2205
+ const point1 = viewport.canvasToWorld(
2206
+ otherViewportRotationPoints[0][3]
2207
+ );
2208
+ const point2 = viewport.canvasToWorld(
2209
+ otherViewportRotationPoints[1][3]
2210
+ );
2211
+ vtkMath.add(point1, point2, currentCenter);
2212
+ vtkMath.multiplyScalar(<Types.Point3>currentCenter, 0.5);
2213
+ }
2214
+ }
2215
+
2216
+ vtkMath.subtract(currentPoint, currentCenter, direction);
2217
+ const dotProdDirection = vtkMath.dot(direction, normal);
2218
+ const projectedDirection: Types.Point3 = [...normal];
2219
+ vtkMath.multiplyScalar(projectedDirection, dotProdDirection);
2220
+ const normalizedProjectedDirection: Types.Point3 = [
2221
+ projectedDirection[0],
2222
+ projectedDirection[1],
2223
+ projectedDirection[2],
2224
+ ];
2225
+ vec3.normalize(
2226
+ normalizedProjectedDirection,
2227
+ normalizedProjectedDirection
2228
+ );
2229
+ const normalizedProjectedDelta: Types.Point3 = [
2230
+ projectedDelta[0],
2231
+ projectedDelta[1],
2232
+ projectedDelta[2],
2233
+ ];
2234
+ vec3.normalize(normalizedProjectedDelta, normalizedProjectedDelta);
2235
+
2236
+ let slabThicknessValue = otherViewport.getSlabThickness();
2237
+ if (
2238
+ csUtils.isOpposite(
2239
+ normalizedProjectedDirection,
2240
+ normalizedProjectedDelta,
2241
+ 1e-3
2242
+ )
2243
+ ) {
2244
+ slabThicknessValue -= mod;
2245
+ } else {
2246
+ slabThicknessValue += mod;
2247
+ }
2248
+
2249
+ slabThicknessValue = Math.abs(slabThicknessValue);
2250
+ slabThicknessValue = Math.max(
2251
+ RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS,
2252
+ slabThicknessValue
2253
+ );
2254
+
2255
+ const near = this._pointNearReferenceLine(
2256
+ viewportAnnotation,
2257
+ canvasCoords,
2258
+ 6,
2259
+ otherViewport
2260
+ );
2261
+
2262
+ if (near) {
2263
+ slabThicknessValue = RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
2264
+ }
2265
+
2266
+ // We want to set the slabThickness for the viewport's actors but
2267
+ // since the crosshairs tool instance has configuration regarding which
2268
+ // actorUIDs (in case of volume -> actorUID = volumeIds) to set the
2269
+ // slabThickness for, we need to delegate the slabThickness setting
2270
+ // to the crosshairs tool instance of the toolGroup since configurations
2271
+ // exist on the toolInstance and each toolGroup has its own crosshairs
2272
+ // tool instance (Otherwise, we would need to set this filterActorUIDsToSetSlabThickness at
2273
+ // the viewport level which makes tool and viewport state convoluted).
2274
+ const toolGroup = getToolGroupForViewport(
2275
+ otherViewport.id,
2276
+ renderingEngine.id
2277
+ );
2278
+ const crosshairsInstance = toolGroup.getToolInstance(
2279
+ this.getToolName()
2280
+ );
2281
+ crosshairsInstance.setSlabThickness(
2282
+ otherViewport,
2283
+ slabThicknessValue
2284
+ );
2285
+
2286
+ viewportsIds.push(otherViewport.id);
2287
+ }
2288
+ }
2289
+ );
2290
+ renderingEngine.renderViewports(viewportsIds);
2291
+ }
2292
+ };
2293
+
2294
+ setSlabThickness(viewport, slabThickness) {
2295
+ let actorUIDs;
2296
+ const { filterActorUIDsToSetSlabThickness } = this.configuration;
2297
+ if (
2298
+ filterActorUIDsToSetSlabThickness &&
2299
+ filterActorUIDsToSetSlabThickness.length > 0
2300
+ ) {
2301
+ actorUIDs = filterActorUIDsToSetSlabThickness;
2302
+ }
2303
+
2304
+ let blendModeToUse = this.configuration.slabThicknessBlendMode;
2305
+ if (slabThickness === RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS) {
2306
+ blendModeToUse = Enums.BlendModes.COMPOSITE;
2307
+ }
2308
+
2309
+ const immediate = false;
2310
+ viewport.setBlendMode(blendModeToUse, actorUIDs, immediate);
2311
+ viewport.setSlabThickness(slabThickness, actorUIDs);
2312
+ }
2313
+
2314
+ _isClockWise(a, b, c) {
2315
+ // return true if the rotation is clockwise
2316
+ return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]) > 0;
2317
+ }
2318
+
2319
+ _applyDeltaShiftToSelectedViewportCameras(
2320
+ renderingEngine,
2321
+ viewportsAnnotationsToUpdate,
2322
+ delta
2323
+ ) {
2324
+ // update camera for the other viewports.
2325
+ // NOTE1: The lines then are rendered by the onCameraModified
2326
+ // NOTE2: crosshair center are automatically updated in the onCameraModified event
2327
+ viewportsAnnotationsToUpdate.forEach((annotation) => {
2328
+ this._applyDeltaShiftToViewportCamera(renderingEngine, annotation, delta);
2329
+ });
2330
+ }
2331
+
2332
+ _applyDeltaShiftToViewportCamera(
2333
+ renderingEngine: Types.IRenderingEngine,
2334
+ annotation,
2335
+ delta
2336
+ ) {
2337
+ // update camera for the other viewports.
2338
+ // NOTE1: The lines then are rendered by the onCameraModified
2339
+ // NOTE2: crosshair center are automatically updated in the onCameraModified event
2340
+ const { data } = annotation;
2341
+
2342
+ const viewport = renderingEngine.getViewport(data.viewportId);
2343
+ const camera = viewport.getCamera();
2344
+ const normal = camera.viewPlaneNormal;
2345
+
2346
+ // Project delta over camera normal
2347
+ // (we don't need to pan, we need only to scroll the camera as in the wheel stack scroll tool)
2348
+ const dotProd = vtkMath.dot(delta, normal);
2349
+ const projectedDelta: Types.Point3 = [...normal];
2350
+ vtkMath.multiplyScalar(projectedDelta, dotProd);
2351
+
2352
+ if (
2353
+ Math.abs(projectedDelta[0]) > 1e-3 ||
2354
+ Math.abs(projectedDelta[1]) > 1e-3 ||
2355
+ Math.abs(projectedDelta[2]) > 1e-3
2356
+ ) {
2357
+ const newFocalPoint: Types.Point3 = [0, 0, 0];
2358
+ const newPosition: Types.Point3 = [0, 0, 0];
2359
+
2360
+ vtkMath.add(camera.focalPoint, projectedDelta, newFocalPoint);
2361
+ vtkMath.add(camera.position, projectedDelta, newPosition);
2362
+
2363
+ viewport.setCamera({
2364
+ focalPoint: newFocalPoint,
2365
+ position: newPosition,
2366
+ });
2367
+ viewport.render();
2368
+ }
2369
+ }
2370
+
2371
+ _pointNearReferenceLine = (
2372
+ annotation,
2373
+ canvasCoords,
2374
+ proximity,
2375
+ lineViewport
2376
+ ) => {
2377
+ const { data } = annotation;
2378
+ const { rotationPoints } = data.handles;
2379
+
2380
+ for (let i = 0; i < rotationPoints.length - 1; ++i) {
2381
+ const otherViewport = rotationPoints[i][1];
2382
+ if (otherViewport.id !== lineViewport.id) {
2383
+ continue;
2384
+ }
2385
+
2386
+ const viewportControllable = this._getReferenceLineControllable(
2387
+ otherViewport.id
2388
+ );
2389
+ if (!viewportControllable) {
2390
+ continue;
2391
+ }
2392
+
2393
+ const lineSegment1 = {
2394
+ start: {
2395
+ x: rotationPoints[i][2][0],
2396
+ y: rotationPoints[i][2][1],
2397
+ },
2398
+ end: {
2399
+ x: rotationPoints[i][3][0],
2400
+ y: rotationPoints[i][3][1],
2401
+ },
2402
+ };
2403
+
2404
+ const distanceToPoint1 = lineSegment.distanceToPoint(
2405
+ [lineSegment1.start.x, lineSegment1.start.y],
2406
+ [lineSegment1.end.x, lineSegment1.end.y],
2407
+ [canvasCoords[0], canvasCoords[1]]
2408
+ );
2409
+
2410
+ const lineSegment2 = {
2411
+ start: {
2412
+ x: rotationPoints[i + 1][2][0],
2413
+ y: rotationPoints[i + 1][2][1],
2414
+ },
2415
+ end: {
2416
+ x: rotationPoints[i + 1][3][0],
2417
+ y: rotationPoints[i + 1][3][1],
2418
+ },
2419
+ };
2420
+
2421
+ const distanceToPoint2 = lineSegment.distanceToPoint(
2422
+ [lineSegment2.start.x, lineSegment2.start.y],
2423
+ [lineSegment2.end.x, lineSegment2.end.y],
2424
+ [canvasCoords[0], canvasCoords[1]]
2425
+ );
2426
+
2427
+ if (distanceToPoint1 <= proximity || distanceToPoint2 <= proximity) {
2428
+ return true;
2429
+ }
2430
+
2431
+ // rotation handles are two for viewport
2432
+ i++;
2433
+ }
2434
+
2435
+ return false;
2436
+ };
2437
+
2438
+ _getRotationHandleNearImagePoint(
2439
+ viewport,
2440
+ annotation,
2441
+ canvasCoords,
2442
+ proximity
2443
+ ) {
2444
+ const { data } = annotation;
2445
+ const { rotationPoints } = data.handles;
2446
+
2447
+ for (let i = 0; i < rotationPoints.length; i++) {
2448
+ const point = rotationPoints[i][0];
2449
+ const otherViewport = rotationPoints[i][1];
2450
+ const viewportControllable = this._getReferenceLineControllable(
2451
+ otherViewport.id
2452
+ );
2453
+ if (!viewportControllable) {
2454
+ continue;
2455
+ }
2456
+
2457
+ const viewportDraggableRotatable =
2458
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
2459
+ if (!viewportDraggableRotatable) {
2460
+ continue;
2461
+ }
2462
+
2463
+ const annotationCanvasCoordinate = viewport.worldToCanvas(point);
2464
+ if (vec2.distance(canvasCoords, annotationCanvasCoordinate) < proximity) {
2465
+ data.handles.activeOperation = OPERATION.ROTATE;
2466
+
2467
+ this.editData = {
2468
+ annotation,
2469
+ };
2470
+
2471
+ return point;
2472
+ }
2473
+ }
2474
+
2475
+ return null;
2476
+ }
2477
+
2478
+ _getSlabThicknessHandleNearImagePoint(
2479
+ viewport,
2480
+ annotation,
2481
+ canvasCoords,
2482
+ proximity
2483
+ ) {
2484
+ const { data } = annotation;
2485
+ const { slabThicknessPoints } = data.handles;
2486
+
2487
+ for (let i = 0; i < slabThicknessPoints.length; i++) {
2488
+ const point = slabThicknessPoints[i][0];
2489
+ const otherViewport = slabThicknessPoints[i][1];
2490
+ const viewportControllable = this._getReferenceLineControllable(
2491
+ otherViewport.id
2492
+ );
2493
+ if (!viewportControllable) {
2494
+ continue;
2495
+ }
2496
+
2497
+ const viewportSlabThicknessControlsOn =
2498
+ this._getReferenceLineSlabThicknessControlsOn(otherViewport.id);
2499
+ if (!viewportSlabThicknessControlsOn) {
2500
+ continue;
2501
+ }
2502
+
2503
+ const annotationCanvasCoordinate = viewport.worldToCanvas(point);
2504
+ if (vec2.distance(canvasCoords, annotationCanvasCoordinate) < proximity) {
2505
+ data.handles.activeOperation = OPERATION.SLAB;
2506
+
2507
+ data.activeViewportIds = [otherViewport.id];
2508
+
2509
+ this.editData = {
2510
+ annotation,
2511
+ };
2512
+
2513
+ return point;
2514
+ }
2515
+ }
2516
+
2517
+ return null;
2518
+ }
2519
+
2520
+ _pointNearTool(element, annotation, canvasCoords, proximity) {
2521
+ const enabledElement = getEnabledElement(element);
2522
+ const { viewport } = enabledElement;
2523
+ const { clientWidth, clientHeight } = viewport.canvas;
2524
+ const canvasDiagonalLength = Math.sqrt(
2525
+ clientWidth * clientWidth + clientHeight * clientHeight
2526
+ );
2527
+ const { data } = annotation;
2528
+
2529
+ const { rotationPoints } = data.handles;
2530
+ const { slabThicknessPoints } = data.handles;
2531
+ const viewportIdArray = [];
2532
+
2533
+ for (let i = 0; i < rotationPoints.length - 1; ++i) {
2534
+ const otherViewport = rotationPoints[i][1];
2535
+ const viewportControllable = this._getReferenceLineControllable(
2536
+ otherViewport.id
2537
+ );
2538
+ const viewportDraggableRotatable =
2539
+ this._getReferenceLineDraggableRotatable(otherViewport.id);
2540
+
2541
+ if (!viewportControllable || !viewportDraggableRotatable) {
2542
+ continue;
2543
+ }
2544
+
2545
+ const lineSegment1 = {
2546
+ start: {
2547
+ x: rotationPoints[i][2][0],
2548
+ y: rotationPoints[i][2][1],
2549
+ },
2550
+ end: {
2551
+ x: rotationPoints[i][3][0],
2552
+ y: rotationPoints[i][3][1],
2553
+ },
2554
+ };
2555
+
2556
+ const distanceToPoint1 = lineSegment.distanceToPoint(
2557
+ [lineSegment1.start.x, lineSegment1.start.y],
2558
+ [lineSegment1.end.x, lineSegment1.end.y],
2559
+ [canvasCoords[0], canvasCoords[1]]
2560
+ );
2561
+
2562
+ const lineSegment2 = {
2563
+ start: {
2564
+ x: rotationPoints[i + 1][2][0],
2565
+ y: rotationPoints[i + 1][2][1],
2566
+ },
2567
+ end: {
2568
+ x: rotationPoints[i + 1][3][0],
2569
+ y: rotationPoints[i + 1][3][1],
2570
+ },
2571
+ };
2572
+
2573
+ const distanceToPoint2 = lineSegment.distanceToPoint(
2574
+ [lineSegment2.start.x, lineSegment2.start.y],
2575
+ [lineSegment2.end.x, lineSegment2.end.y],
2576
+ [canvasCoords[0], canvasCoords[1]]
2577
+ );
2578
+
2579
+ if (distanceToPoint1 <= proximity || distanceToPoint2 <= proximity) {
2580
+ viewportIdArray.push(otherViewport.id);
2581
+ data.handles.activeOperation = OPERATION.DRAG;
2582
+ }
2583
+
2584
+ // rotation handles are two for viewport
2585
+ i++;
2586
+ }
2587
+
2588
+ for (let i = 0; i < slabThicknessPoints.length - 1; ++i) {
2589
+ const otherViewport = slabThicknessPoints[i][1];
2590
+ if (viewportIdArray.find((id) => id === otherViewport.id)) {
2591
+ continue;
2592
+ }
2593
+
2594
+ const viewportControllable = this._getReferenceLineControllable(
2595
+ otherViewport.id
2596
+ );
2597
+ const viewportSlabThicknessControlsOn =
2598
+ this._getReferenceLineSlabThicknessControlsOn(otherViewport.id);
2599
+
2600
+ if (!viewportControllable || !viewportSlabThicknessControlsOn) {
2601
+ continue;
2602
+ }
2603
+
2604
+ const stPointLineCanvas1 = slabThicknessPoints[i][2];
2605
+ const stPointLineCanvas2 = slabThicknessPoints[i][3];
2606
+
2607
+ const centerCanvas = vec2.create();
2608
+ vec2.add(centerCanvas, stPointLineCanvas1, stPointLineCanvas2);
2609
+ vec2.scale(centerCanvas, centerCanvas, 0.5);
2610
+
2611
+ const canvasUnitVectorFromCenter = vec2.create();
2612
+ vec2.subtract(
2613
+ canvasUnitVectorFromCenter,
2614
+ stPointLineCanvas1,
2615
+ centerCanvas
2616
+ );
2617
+ vec2.normalize(canvasUnitVectorFromCenter, canvasUnitVectorFromCenter);
2618
+
2619
+ const canvasVectorFromCenterStart = vec2.create();
2620
+ vec2.scale(
2621
+ canvasVectorFromCenterStart,
2622
+ canvasUnitVectorFromCenter,
2623
+ canvasDiagonalLength * 0.05
2624
+ );
2625
+
2626
+ const stPointLineCanvas1Start = vec2.create();
2627
+ const stPointLineCanvas2Start = vec2.create();
2628
+ vec2.add(
2629
+ stPointLineCanvas1Start,
2630
+ centerCanvas,
2631
+ canvasVectorFromCenterStart
2632
+ );
2633
+ vec2.subtract(
2634
+ stPointLineCanvas2Start,
2635
+ centerCanvas,
2636
+ canvasVectorFromCenterStart
2637
+ );
2638
+
2639
+ const lineSegment1 = {
2640
+ start: {
2641
+ x: stPointLineCanvas1Start[0],
2642
+ y: stPointLineCanvas1Start[1],
2643
+ },
2644
+ end: {
2645
+ x: stPointLineCanvas1[0],
2646
+ y: stPointLineCanvas1[1],
2647
+ },
2648
+ };
2649
+
2650
+ const distanceToPoint1 = lineSegment.distanceToPoint(
2651
+ [lineSegment1.start.x, lineSegment1.start.y],
2652
+ [lineSegment1.end.x, lineSegment1.end.y],
2653
+ [canvasCoords[0], canvasCoords[1]]
2654
+ );
2655
+
2656
+ const lineSegment2 = {
2657
+ start: {
2658
+ x: stPointLineCanvas2Start[0],
2659
+ y: stPointLineCanvas2Start[1],
2660
+ },
2661
+ end: {
2662
+ x: stPointLineCanvas2[0],
2663
+ y: stPointLineCanvas2[1],
2664
+ },
2665
+ };
2666
+
2667
+ const distanceToPoint2 = lineSegment.distanceToPoint(
2668
+ [lineSegment2.start.x, lineSegment2.start.y],
2669
+ [lineSegment2.end.x, lineSegment2.end.y],
2670
+ [canvasCoords[0], canvasCoords[1]]
2671
+ );
2672
+
2673
+ if (distanceToPoint1 <= proximity || distanceToPoint2 <= proximity) {
2674
+ viewportIdArray.push(otherViewport.id); // we still need this to draw inactive slab thickness handles
2675
+ data.handles.activeOperation = null; // no operation
2676
+ }
2677
+
2678
+ // slab thickness handles are in couples
2679
+ i++;
2680
+ }
2681
+
2682
+ data.activeViewportIds = [...viewportIdArray];
2683
+
2684
+ this.editData = {
2685
+ annotation,
2686
+ };
2687
+
2688
+ return data.handles.activeOperation === OPERATION.DRAG ? true : false;
2689
+ }
2690
+ }
2691
+
2692
+ CrosshairsTool.toolName = 'Crosshairs';
2693
+ export default CrosshairsTool;