@cornerstonejs/tools 5.0.0-beta.1 → 5.0.1

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 (290) hide show
  1. package/dist/esm/config.d.ts +4 -0
  2. package/dist/esm/drawingSvg/drawPath.d.ts +3 -0
  3. package/dist/esm/drawingSvg/drawPath.js +4 -1
  4. package/dist/esm/eventListeners/keyboard/keyDownListener.js +2 -2
  5. package/dist/esm/eventListeners/mouse/getMouseEventPoints.d.ts +1 -1
  6. package/dist/esm/eventListeners/mouse/getMouseEventPoints.js +19 -1
  7. package/dist/esm/eventListeners/mouse/mouseDoubleClickListener.js +8 -1
  8. package/dist/esm/eventListeners/mouse/mouseDownListener.js +37 -5
  9. package/dist/esm/eventListeners/mouse/mouseMoveListener.js +3 -0
  10. package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +60 -92
  11. package/dist/esm/eventListeners/segmentation/labelmap/onLabelmapSegmentationDataModified.js +49 -21
  12. package/dist/esm/eventListeners/segmentation/labelmap/performStackLabelmapUpdate.js +7 -13
  13. package/dist/esm/eventListeners/segmentation/labelmap/performVolumeLabelmapUpdate.js +44 -18
  14. package/dist/esm/eventListeners/touch/getTouchEventPoints.js +27 -4
  15. package/dist/esm/eventListeners/touch/touchStartListener.js +27 -9
  16. package/dist/esm/eventListeners/wheel/wheelListener.js +5 -1
  17. package/dist/esm/index.d.ts +2 -2
  18. package/dist/esm/index.js +2 -2
  19. package/dist/esm/init.js +2 -0
  20. package/dist/esm/stateManagement/annotation/FrameOfReferenceSpecificAnnotationManager.js +10 -4
  21. package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.js +23 -20
  22. package/dist/esm/stateManagement/segmentation/SegmentationRepresentationDisplayRegistry.d.ts +12 -0
  23. package/dist/esm/stateManagement/segmentation/SegmentationRepresentationDisplayRegistry.js +7 -0
  24. package/dist/esm/stateManagement/segmentation/SegmentationStateManager.d.ts +1 -10
  25. package/dist/esm/stateManagement/segmentation/SegmentationStateManager.js +28 -149
  26. package/dist/esm/stateManagement/segmentation/addColorLUT.js +7 -1
  27. package/dist/esm/stateManagement/segmentation/getCurrentLabelmapImageIdForViewport.js +16 -1
  28. package/dist/esm/stateManagement/segmentation/helpers/clearSegmentValue.js +9 -7
  29. package/dist/esm/stateManagement/segmentation/helpers/getSegmentationActor.d.ts +1 -1
  30. package/dist/esm/stateManagement/segmentation/helpers/getSegmentationActor.js +3 -2
  31. package/dist/esm/stateManagement/segmentation/helpers/getViewportLabelmapRenderMode.d.ts +5 -0
  32. package/dist/esm/stateManagement/segmentation/helpers/getViewportLabelmapRenderMode.js +58 -0
  33. package/dist/esm/stateManagement/segmentation/helpers/labelmapImageMapperSupport.d.ts +52 -0
  34. package/dist/esm/stateManagement/segmentation/helpers/labelmapImageMapperSupport.js +246 -0
  35. package/dist/esm/stateManagement/segmentation/helpers/labelmapSegmentationState.d.ts +1 -0
  36. package/dist/esm/stateManagement/segmentation/helpers/labelmapSegmentationState.js +1 -0
  37. package/dist/esm/stateManagement/segmentation/helpers/normalizeSegmentationInput.js +12 -1
  38. package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.js +3 -3
  39. package/dist/esm/stateManagement/segmentation/labelmapModel/index.d.ts +9 -0
  40. package/dist/esm/stateManagement/segmentation/labelmapModel/index.js +7 -0
  41. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.d.ts +54 -0
  42. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.js +224 -0
  43. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageIdMapping.d.ts +6 -0
  44. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageIdMapping.js +39 -0
  45. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.d.ts +23 -0
  46. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.js +269 -0
  47. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLayerStore.d.ts +15 -0
  48. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLayerStore.js +160 -0
  49. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLegacyAdapter.d.ts +4 -0
  50. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLegacyAdapter.js +42 -0
  51. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapSegmentBindings.d.ts +11 -0
  52. package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapSegmentBindings.js +73 -0
  53. package/dist/esm/stateManagement/segmentation/labelmapModel/normalizeLabelmapSegmentationData.d.ts +17 -0
  54. package/dist/esm/stateManagement/segmentation/labelmapModel/normalizeLabelmapSegmentationData.js +75 -0
  55. package/dist/esm/stateManagement/segmentation/labelmapModel/privateLabelmap.d.ts +5 -0
  56. package/dist/esm/stateManagement/segmentation/labelmapModel/privateLabelmap.js +106 -0
  57. package/dist/esm/stateManagement/segmentation/models/SegmentModel.d.ts +11 -0
  58. package/dist/esm/stateManagement/segmentation/models/SegmentModel.js +19 -0
  59. package/dist/esm/stateManagement/segmentation/models/SegmentationModel.d.ts +12 -0
  60. package/dist/esm/stateManagement/segmentation/models/SegmentationModel.js +23 -0
  61. package/dist/esm/stateManagement/segmentation/removeSegmentationRepresentations.js +6 -10
  62. package/dist/esm/stateManagement/segmentation/segmentIndex.js +24 -0
  63. package/dist/esm/stateManagement/segmentation/segmentationEventManager.js +2 -9
  64. package/dist/esm/stateManagement/segmentation/segmentationState.d.ts +2 -1
  65. package/dist/esm/stateManagement/segmentation/segmentationState.js +4 -1
  66. package/dist/esm/store/SynchronizerManager/Synchronizer.d.ts +3 -1
  67. package/dist/esm/store/state.js +2 -1
  68. package/dist/esm/synchronizers/callbacks/imageSliceSyncCallback.js +12 -3
  69. package/dist/esm/synchronizers/callbacks/presentationViewSyncCallback.js +5 -2
  70. package/dist/esm/synchronizers/callbacks/zoomPanSyncCallback.js +51 -3
  71. package/dist/esm/synchronizers/synchronizers/createPresentationViewSynchronizer.d.ts +1 -1
  72. package/dist/esm/tools/AdvancedMagnifyTool.js +4 -1
  73. package/dist/esm/tools/CrosshairsTool.d.ts +4 -0
  74. package/dist/esm/tools/CrosshairsTool.js +95 -41
  75. package/dist/esm/tools/MagnifyTool.js +3 -1
  76. package/dist/esm/tools/OrientationControllerTool.d.ts +45 -0
  77. package/dist/esm/tools/OrientationControllerTool.js +454 -0
  78. package/dist/esm/tools/OrientationMarkerTool.js +4 -4
  79. package/dist/esm/tools/PanTool.js +26 -3
  80. package/dist/esm/tools/PlanarRotateTool.js +19 -4
  81. package/dist/esm/tools/ReferenceCursors.js +7 -1
  82. package/dist/esm/tools/SculptorTool/CircleSculptCursor.js +1 -1
  83. package/dist/esm/tools/TrackballRotateTool.js +3 -2
  84. package/dist/esm/tools/VolumeCroppingControlTool.d.ts +10 -35
  85. package/dist/esm/tools/VolumeCroppingControlTool.js +179 -699
  86. package/dist/esm/tools/VolumeCroppingTool.d.ts +34 -32
  87. package/dist/esm/tools/VolumeCroppingTool.js +813 -532
  88. package/dist/esm/tools/WindowLevelTool.d.ts +2 -1
  89. package/dist/esm/tools/WindowLevelTool.js +48 -4
  90. package/dist/esm/tools/ZoomTool.d.ts +8 -0
  91. package/dist/esm/tools/ZoomTool.js +92 -11
  92. package/dist/esm/tools/annotation/AngleTool.js +38 -32
  93. package/dist/esm/tools/annotation/ArrowAnnotateTool.js +30 -28
  94. package/dist/esm/tools/annotation/BidirectionalTool.js +51 -49
  95. package/dist/esm/tools/annotation/CircleROITool.d.ts +1 -0
  96. package/dist/esm/tools/annotation/CircleROITool.js +89 -51
  97. package/dist/esm/tools/annotation/CobbAngleTool.js +1 -1
  98. package/dist/esm/tools/annotation/DragProbeTool.js +1 -1
  99. package/dist/esm/tools/annotation/ETDRSGridTool.js +1 -1
  100. package/dist/esm/tools/annotation/EllipticalROITool.js +46 -39
  101. package/dist/esm/tools/annotation/HeightTool.js +1 -1
  102. package/dist/esm/tools/annotation/KeyImageTool.js +11 -11
  103. package/dist/esm/tools/annotation/LabelTool.js +37 -35
  104. package/dist/esm/tools/annotation/LengthTool.js +35 -33
  105. package/dist/esm/tools/annotation/LivewireContourSegmentationTool.js +6 -4
  106. package/dist/esm/tools/annotation/LivewireContourTool.js +4 -8
  107. package/dist/esm/tools/annotation/PlanarFreehandContourSegmentationTool.js +6 -4
  108. package/dist/esm/tools/annotation/PlanarFreehandROITool.d.ts +2 -1
  109. package/dist/esm/tools/annotation/PlanarFreehandROITool.js +11 -8
  110. package/dist/esm/tools/annotation/ProbeTool.js +66 -56
  111. package/dist/esm/tools/annotation/RectangleROITool.js +48 -37
  112. package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +1 -1
  113. package/dist/esm/tools/annotation/RegionSegmentTool.js +1 -1
  114. package/dist/esm/tools/annotation/SplineContourSegmentationTool.js +1 -1
  115. package/dist/esm/tools/annotation/SplineROITool.js +60 -56
  116. package/dist/esm/tools/annotation/UltrasoundDirectionalTool.js +1 -1
  117. package/dist/esm/tools/annotation/UltrasoundPleuraBLineTool/UltrasoundPleuraBLineTool.js +57 -55
  118. package/dist/esm/tools/annotation/VideoRedactionTool.js +1 -1
  119. package/dist/esm/tools/base/AnnotationDisplayTool.js +9 -6
  120. package/dist/esm/tools/base/AnnotationTool.js +2 -1
  121. package/dist/esm/tools/base/BaseTool.js +16 -10
  122. package/dist/esm/tools/base/ContourSegmentationBaseTool.js +1 -1
  123. package/dist/esm/tools/base/GrowCutBaseTool.js +2 -2
  124. package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.d.ts +2 -4
  125. package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.js +15 -85
  126. package/dist/esm/tools/displayTools/Labelmap/labelmapActorStyle.d.ts +5 -0
  127. package/dist/esm/tools/displayTools/Labelmap/labelmapActorStyle.js +191 -0
  128. package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.d.ts +4 -3
  129. package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.js +48 -209
  130. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/createLabelmapRenderPlan.d.ts +3 -0
  131. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/createLabelmapRenderPlan.js +51 -0
  132. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/index.d.ts +4 -0
  133. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/index.js +3 -0
  134. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/legacyVolumePlan.d.ts +14 -0
  135. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/legacyVolumePlan.js +143 -0
  136. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/planarGenericVolumeLabelmap.d.ts +40 -0
  137. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/planarGenericVolumeLabelmap.js +79 -0
  138. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/removeLabelmapRepresentationFromViewport.d.ts +3 -0
  139. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/removeLabelmapRepresentationFromViewport.js +18 -0
  140. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/resolveLabelmapRenderPlan.d.ts +9 -0
  141. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/resolveLabelmapRenderPlan.js +56 -0
  142. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/stackImagePlan.d.ts +11 -0
  143. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/stackImagePlan.js +35 -0
  144. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/types.d.ts +48 -0
  145. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/types.js +0 -0
  146. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/volumeSliceImageMapperPlan.d.ts +13 -0
  147. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/volumeSliceImageMapperPlan.js +34 -0
  148. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan.d.ts +2 -0
  149. package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan.js +1 -0
  150. package/dist/esm/tools/displayTools/Labelmap/labelmapRepresentationUID.d.ts +8 -0
  151. package/dist/esm/tools/displayTools/Labelmap/labelmapRepresentationUID.js +18 -0
  152. package/dist/esm/tools/displayTools/Labelmap/removeLabelmapFromElement.js +2 -2
  153. package/dist/esm/tools/displayTools/Labelmap/removeLabelmapRepresentationData.d.ts +3 -0
  154. package/dist/esm/tools/displayTools/Labelmap/removeLabelmapRepresentationData.js +16 -0
  155. package/dist/esm/tools/displayTools/Labelmap/syncStackLabelmapActors.d.ts +2 -0
  156. package/dist/esm/tools/displayTools/Labelmap/syncStackLabelmapActors.js +135 -0
  157. package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapImageMapper.d.ts +16 -0
  158. package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapImageMapper.js +267 -0
  159. package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapSliceData.d.ts +27 -0
  160. package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapSliceData.js +185 -0
  161. package/dist/esm/tools/displayTools/registerBuiltInSegmentationRepresentationDisplays.d.ts +1 -0
  162. package/dist/esm/tools/displayTools/registerBuiltInSegmentationRepresentationDisplays.js +16 -0
  163. package/dist/esm/tools/index.d.ts +2 -1
  164. package/dist/esm/tools/index.js +2 -1
  165. package/dist/esm/tools/segmentation/BrushTool.d.ts +9 -2
  166. package/dist/esm/tools/segmentation/BrushTool.js +123 -26
  167. package/dist/esm/tools/segmentation/CircleScissorsTool.js +19 -36
  168. package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +2 -3
  169. package/dist/esm/tools/segmentation/LabelmapBaseTool.js +77 -46
  170. package/dist/esm/tools/segmentation/LabelmapEditWithContour.js +3 -3
  171. package/dist/esm/tools/segmentation/PaintFillTool.js +11 -4
  172. package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +2 -0
  173. package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +16 -8
  174. package/dist/esm/tools/segmentation/RectangleScissorsTool.js +13 -6
  175. package/dist/esm/tools/segmentation/SegmentBidirectionalTool.js +63 -61
  176. package/dist/esm/tools/segmentation/SegmentSelectTool.js +4 -4
  177. package/dist/esm/tools/segmentation/SphereScissorsTool.js +11 -31
  178. package/dist/esm/tools/segmentation/strategies/BrushStrategy.d.ts +7 -0
  179. package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +47 -24
  180. package/dist/esm/tools/segmentation/strategies/compositions/circularCursor.js +49 -21
  181. package/dist/esm/tools/segmentation/strategies/compositions/determineSegmentIndex.js +2 -2
  182. package/dist/esm/tools/segmentation/strategies/compositions/dynamicThreshold.js +5 -1
  183. package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.js +2 -2
  184. package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -2
  185. package/dist/esm/tools/segmentation/strategies/compositions/setValue.js +14 -6
  186. package/dist/esm/tools/segmentation/strategies/fillCircle.d.ts +3 -1
  187. package/dist/esm/tools/segmentation/strategies/fillCircle.js +38 -31
  188. package/dist/esm/tools/segmentation/strategies/fillSphere.js +11 -3
  189. package/dist/esm/tools/segmentation/strategies/utils/crossLayerErase.d.ts +4 -0
  190. package/dist/esm/tools/segmentation/strategies/utils/crossLayerErase.js +23 -0
  191. package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js +1 -1
  192. package/dist/esm/tools/segmentation/strategies/utils/handleUseSegmentCenterIndex.js +12 -11
  193. package/dist/esm/tools/segmentation/strategies/utils/labelmapOverlap.d.ts +4 -0
  194. package/dist/esm/tools/segmentation/strategies/utils/labelmapOverlap.js +41 -0
  195. package/dist/esm/tools/segmentation/strategies/utils/overwritePolicy.d.ts +3 -0
  196. package/dist/esm/tools/segmentation/strategies/utils/overwritePolicy.js +31 -0
  197. package/dist/esm/tools/segmentation/strategies/utils/segmentSeparation.d.ts +3 -0
  198. package/dist/esm/tools/segmentation/strategies/utils/segmentSeparation.js +38 -0
  199. package/dist/esm/tools/segmentation/utils/LazyBrushEditController.d.ts +19 -0
  200. package/dist/esm/tools/segmentation/utils/LazyBrushEditController.js +55 -0
  201. package/dist/esm/tools/segmentation/utils/lazyBrushPreview.d.ts +3 -0
  202. package/dist/esm/tools/segmentation/utils/lazyBrushPreview.js +34 -0
  203. package/dist/esm/tools/segmentation/utils/shouldUseLazyLabelmapEditing.d.ts +3 -0
  204. package/dist/esm/tools/segmentation/utils/shouldUseLazyLabelmapEditing.js +42 -0
  205. package/dist/esm/types/ISynchronizerEventHandler.d.ts +2 -1
  206. package/dist/esm/types/LabelmapToolOperationData.d.ts +5 -0
  207. package/dist/esm/types/LabelmapTypes.d.ts +29 -6
  208. package/dist/esm/types/SegmentationStateTypes.d.ts +6 -0
  209. package/dist/esm/utilities/boundingBox/index.d.ts +2 -1
  210. package/dist/esm/utilities/boundingBox/index.js +2 -1
  211. package/dist/esm/utilities/boundingBox/snapIndexBounds.d.ts +3 -0
  212. package/dist/esm/utilities/boundingBox/snapIndexBounds.js +9 -0
  213. package/dist/esm/utilities/calibrateImageSpacing.js +17 -2
  214. package/dist/esm/utilities/contours/AnnotationToPointData.js +1 -1
  215. package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +7 -0
  216. package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +34 -0
  217. package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +6 -0
  218. package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +7 -0
  219. package/dist/esm/utilities/draw3D/index.d.ts +2 -0
  220. package/dist/esm/utilities/draw3D/index.js +2 -0
  221. package/dist/esm/utilities/drawing/getTextBoxCoordsCanvas.js +22 -14
  222. package/dist/esm/utilities/getCenterAndRadiusInCanvas.d.ts +6 -0
  223. package/dist/esm/utilities/getCenterAndRadiusInCanvas.js +26 -0
  224. package/dist/esm/utilities/getEllipseWorldCoordinates.d.ts +2 -0
  225. package/dist/esm/utilities/getEllipseWorldCoordinates.js +26 -0
  226. package/dist/esm/utilities/getSphereBoundsInfo.js +5 -1
  227. package/dist/esm/utilities/getViewportICamera.d.ts +4 -0
  228. package/dist/esm/utilities/getViewportICamera.js +23 -0
  229. package/dist/esm/utilities/getViewportsForAnnotation.js +5 -1
  230. package/dist/esm/utilities/index.d.ts +2 -1
  231. package/dist/esm/utilities/index.js +2 -1
  232. package/dist/esm/utilities/interactionDragCoordinator.d.ts +5 -0
  233. package/dist/esm/utilities/interactionDragCoordinator.js +16 -0
  234. package/dist/esm/utilities/math/basic/BasicStatsCalculator.js +9 -7
  235. package/dist/esm/utilities/pointInSurroundingSphereCallback.js +8 -1
  236. package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js +121 -118
  237. package/dist/esm/utilities/segmentation/SegmentStatsCalculator.js +5 -4
  238. package/dist/esm/utilities/segmentation/VolumetricCalculator.js +1 -1
  239. package/dist/esm/utilities/segmentation/createLabelmapVolumeForViewport.js +1 -1
  240. package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentation.js +1 -1
  241. package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentationVolume.js +11 -2
  242. package/dist/esm/utilities/segmentation/getSegmentIndexAtLabelmapBorder.js +36 -17
  243. package/dist/esm/utilities/segmentation/getSegmentIndexAtWorldPoint.js +42 -25
  244. package/dist/esm/utilities/segmentation/getUniqueSegmentIndices.js +3 -30
  245. package/dist/esm/utilities/segmentation/index.d.ts +2 -1
  246. package/dist/esm/utilities/segmentation/index.js +2 -1
  247. package/dist/esm/utilities/segmentation/utilsForWorker.js +6 -2
  248. package/dist/esm/utilities/segmentation/validateLabelmap.js +1 -1
  249. package/dist/esm/utilities/stackPrefetch/stackPrefetch.js +0 -1
  250. package/dist/esm/utilities/touch/index.js +3 -2
  251. package/dist/esm/utilities/viewportCapabilities.d.ts +16 -0
  252. package/dist/esm/utilities/viewportCapabilities.js +18 -0
  253. package/dist/esm/utilities/viewportFilters/filterViewportsWithParallelNormals.d.ts +1 -1
  254. package/dist/esm/utilities/viewportFilters/filterViewportsWithParallelNormals.js +12 -4
  255. package/dist/esm/utilities/viewportFilters/filterViewportsWithSameOrientation.d.ts +1 -1
  256. package/dist/esm/utilities/viewportFilters/filterViewportsWithSameOrientation.js +11 -4
  257. package/dist/esm/utilities/viewportFilters/getViewportIdsWithToolToRender.js +1 -1
  258. package/dist/esm/utilities/viewportPresentation.d.ts +3 -0
  259. package/dist/esm/utilities/viewportPresentation.js +26 -0
  260. package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +6 -0
  261. package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +37 -0
  262. package/dist/esm/utilities/volumeCropping/constants.d.ts +31 -0
  263. package/dist/esm/utilities/volumeCropping/constants.js +31 -0
  264. package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +2 -0
  265. package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +6 -0
  266. package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +9 -0
  267. package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +9 -0
  268. package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +5 -0
  269. package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +50 -0
  270. package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +1 -0
  271. package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +13 -0
  272. package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +2 -0
  273. package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +19 -0
  274. package/dist/esm/utilities/volumeCropping/index.d.ts +9 -0
  275. package/dist/esm/utilities/volumeCropping/index.js +9 -0
  276. package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +8 -0
  277. package/dist/esm/utilities/volumeCropping/parseCornerKey.js +11 -0
  278. package/dist/esm/utilities/volumeCropping/types.d.ts +5 -0
  279. package/dist/esm/utilities/volumeCropping/types.js +0 -0
  280. package/dist/esm/utilities/vtkjs/AnnotatedRhombicuboctahedronActor/index.d.ts +31 -0
  281. package/dist/esm/utilities/vtkjs/AnnotatedRhombicuboctahedronActor/index.js +391 -0
  282. package/dist/esm/utilities/vtkjs/OrientationControllerWidget/index.d.ts +69 -0
  283. package/dist/esm/utilities/vtkjs/OrientationControllerWidget/index.js +804 -0
  284. package/dist/esm/utilities/vtkjs/RhombicuboctahedronSource/index.d.ts +7 -0
  285. package/dist/esm/utilities/vtkjs/RhombicuboctahedronSource/index.js +144 -0
  286. package/dist/esm/utilities/vtkjs/index.d.ts +3 -0
  287. package/dist/esm/utilities/vtkjs/index.js +3 -0
  288. package/dist/esm/version.d.ts +1 -1
  289. package/dist/esm/version.js +1 -1
  290. package/package.json +10 -9
@@ -1,6 +1,4 @@
1
1
  import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
2
- import vtkPoints from '@kitware/vtk.js/Common/Core/Points';
3
- import vtkCellArray from '@kitware/vtk.js/Common/Core/CellArray';
4
2
  import { mat3, mat4, vec3 } from 'gl-matrix';
5
3
  import vtkMath from '@kitware/vtk.js/Common/Core/Math';
6
4
  import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
@@ -11,30 +9,10 @@ import { BaseTool } from './base';
11
9
  import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, Enums, triggerEvent, eventTarget, } from '@cornerstonejs/core';
12
10
  import { getToolGroup } from '../store/ToolGroupManager';
13
11
  import { Events } from '../enums';
14
- const PLANEINDEX = {
15
- XMIN: 0,
16
- XMAX: 1,
17
- YMIN: 2,
18
- YMAX: 3,
19
- ZMIN: 4,
20
- ZMAX: 5,
21
- };
22
- const SPHEREINDEX = {
23
- XMIN: 0,
24
- XMAX: 1,
25
- YMIN: 2,
26
- YMAX: 3,
27
- ZMIN: 4,
28
- ZMAX: 5,
29
- XMIN_YMIN_ZMIN: 6,
30
- XMIN_YMIN_ZMAX: 7,
31
- XMIN_YMAX_ZMIN: 8,
32
- XMIN_YMAX_ZMAX: 9,
33
- XMAX_YMIN_ZMIN: 10,
34
- XMAX_YMIN_ZMAX: 11,
35
- XMAX_YMAX_ZMIN: 12,
36
- XMAX_YMAX_ZMAX: 13,
37
- };
12
+ import { PLANEINDEX, SPHEREINDEX, NUM_CLIPPING_PLANES, extractVolumeDirectionVectors, parseCornerKey, copyClippingPlanes, } from '../utilities/volumeCropping';
13
+ import { addLine3DBetweenPoints, calculateAdaptiveSphereRadius, } from '../utilities/draw3D';
14
+ import { isDragOwnedBy } from '../utilities/interactionDragCoordinator';
15
+ import { applyViewportPresentation, getViewportPresentation, } from '../utilities/viewportPresentation';
38
16
  class VolumeCroppingTool extends BaseTool {
39
17
  constructor(toolProps = {}, defaultToolProps = {
40
18
  configuration: {
@@ -56,6 +34,7 @@ class VolumeCroppingTool extends BaseTool {
56
34
  grabSpherePixelDistance: 20,
57
35
  rotateIncrementDegrees: 2,
58
36
  rotateSampleDistanceFactor: 2,
37
+ rotateClippingPlanesIncrementDegrees: 5,
59
38
  },
60
39
  }) {
61
40
  super(toolProps, defaultToolProps);
@@ -63,16 +42,16 @@ class VolumeCroppingTool extends BaseTool {
63
42
  this._hasResolutionChanged = false;
64
43
  this.originalClippingPlanes = [];
65
44
  this.draggingSphereIndex = null;
66
- this.toolCenter = [0, 0, 0];
45
+ this.rotatePlanesOnDrag = false;
46
+ this.suppressPlaneRotationForCurrentDrag = false;
67
47
  this.cornerDragOffset = null;
68
48
  this.faceDragOffset = null;
49
+ this.volumeDirectionVectors = null;
69
50
  this.sphereStates = [];
70
51
  this.edgeLines = {};
71
52
  this.onSetToolConfiguration = () => {
72
- console.debug('Setting tool settoolconfiguration : volumeCropping');
73
53
  };
74
54
  this.onSetToolEnabled = () => {
75
- console.debug('Setting tool enabled: volumeCropping');
76
55
  };
77
56
  this.onCameraModified = (evt) => {
78
57
  const { element } = evt.currentTarget
@@ -87,8 +66,11 @@ class VolumeCroppingTool extends BaseTool {
87
66
  const { element } = eventDetail;
88
67
  const enabledElement = getEnabledElement(element);
89
68
  const { viewport } = enabledElement;
90
- const actorEntry = viewport.getDefaultActor();
91
- const actor = actorEntry.actor;
69
+ this.suppressPlaneRotationForCurrentDrag = isDragOwnedBy(viewport.id, 'orientation-controller');
70
+ const actor = this._getVolumeActor(viewport);
71
+ if (!actor) {
72
+ return false;
73
+ }
92
74
  const mapper = actor.getMapper();
93
75
  const mouseCanvas = [
94
76
  evt.detail.currentPoints.canvas[0],
@@ -115,9 +97,16 @@ class VolumeCroppingTool extends BaseTool {
115
97
  this.faceDragOffset = null;
116
98
  }
117
99
  else {
118
- const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
100
+ const directionVector = this._getDirectionVectorForAxis(sphereState.axis);
101
+ const delta = [
102
+ sphereState.point[0] - mouseWorld[0],
103
+ sphereState.point[1] - mouseWorld[1],
104
+ sphereState.point[2] - mouseWorld[2],
105
+ ];
119
106
  this.faceDragOffset =
120
- sphereState.point[axisIdx] - mouseWorld[axisIdx];
107
+ delta[0] * directionVector[0] +
108
+ delta[1] * directionVector[1] +
109
+ delta[2] * directionVector[2];
121
110
  this.cornerDragOffset = null;
122
111
  }
123
112
  return true;
@@ -152,6 +141,7 @@ class VolumeCroppingTool extends BaseTool {
152
141
  this.draggingSphereIndex = null;
153
142
  this.cornerDragOffset = null;
154
143
  this.faceDragOffset = null;
144
+ this.suppressPlaneRotationForCurrentDrag = false;
155
145
  viewport.render();
156
146
  this._hasResolutionChanged = false;
157
147
  };
@@ -172,201 +162,233 @@ class VolumeCroppingTool extends BaseTool {
172
162
  return false;
173
163
  }
174
164
  if (sphereState.isCorner) {
175
- const newCorner = this._calculateNewCornerPosition(world);
176
- this._updateSpherePosition(sphereState, newCorner);
177
- const axisFlags = this._parseCornerKey(sphereState.uid);
178
- this._updateRelatedCorners(sphereState, newCorner, axisFlags);
179
- this._updateFaceSpheresFromCorners();
165
+ const newCorner = this.cornerDragOffset
166
+ ? vec3.add([0, 0, 0], world, this.cornerDragOffset)
167
+ : world;
168
+ const oldCorner = sphereState.point;
169
+ const axisFlags = parseCornerKey(sphereState.uid);
170
+ const { xDir, yDir, zDir } = this._getDirectionVectors();
171
+ if (!xDir || !yDir || !zDir)
172
+ return false;
173
+ const delta = [
174
+ newCorner[0] - oldCorner[0],
175
+ newCorner[1] - oldCorner[1],
176
+ newCorner[2] - oldCorner[2],
177
+ ];
178
+ const deltaX = delta[0] * xDir[0] + delta[1] * xDir[1] + delta[2] * xDir[2];
179
+ const deltaY = delta[0] * yDir[0] + delta[1] * yDir[1] + delta[2] * yDir[2];
180
+ const deltaZ = delta[0] * zDir[0] + delta[1] * zDir[1] + delta[2] * zDir[2];
181
+ if (axisFlags.isXMin) {
182
+ const faceXMin = this.sphereStates[SPHEREINDEX.XMIN];
183
+ const newPoint = [
184
+ faceXMin.point[0] + deltaX * xDir[0],
185
+ faceXMin.point[1] + deltaX * xDir[1],
186
+ faceXMin.point[2] + deltaX * xDir[2],
187
+ ];
188
+ this._updateSpherePosition(SPHEREINDEX.XMIN, newPoint);
189
+ }
190
+ else if (axisFlags.isXMax) {
191
+ const faceXMax = this.sphereStates[SPHEREINDEX.XMAX];
192
+ const newPoint = [
193
+ faceXMax.point[0] + deltaX * xDir[0],
194
+ faceXMax.point[1] + deltaX * xDir[1],
195
+ faceXMax.point[2] + deltaX * xDir[2],
196
+ ];
197
+ this._updateSpherePosition(SPHEREINDEX.XMAX, newPoint);
198
+ }
199
+ if (axisFlags.isYMin) {
200
+ const faceYMin = this.sphereStates[SPHEREINDEX.YMIN];
201
+ const newPoint = [
202
+ faceYMin.point[0] + deltaY * yDir[0],
203
+ faceYMin.point[1] + deltaY * yDir[1],
204
+ faceYMin.point[2] + deltaY * yDir[2],
205
+ ];
206
+ this._updateSpherePosition(SPHEREINDEX.YMIN, newPoint);
207
+ }
208
+ else if (axisFlags.isYMax) {
209
+ const faceYMax = this.sphereStates[SPHEREINDEX.YMAX];
210
+ const newPoint = [
211
+ faceYMax.point[0] + deltaY * yDir[0],
212
+ faceYMax.point[1] + deltaY * yDir[1],
213
+ faceYMax.point[2] + deltaY * yDir[2],
214
+ ];
215
+ this._updateSpherePosition(SPHEREINDEX.YMAX, newPoint);
216
+ }
217
+ if (axisFlags.isZMin) {
218
+ const faceZMin = this.sphereStates[SPHEREINDEX.ZMIN];
219
+ const newPoint = [
220
+ faceZMin.point[0] + deltaZ * zDir[0],
221
+ faceZMin.point[1] + deltaZ * zDir[1],
222
+ faceZMin.point[2] + deltaZ * zDir[2],
223
+ ];
224
+ this._updateSpherePosition(SPHEREINDEX.ZMIN, newPoint);
225
+ }
226
+ else if (axisFlags.isZMax) {
227
+ const faceZMax = this.sphereStates[SPHEREINDEX.ZMAX];
228
+ const newPoint = [
229
+ faceZMax.point[0] + deltaZ * zDir[0],
230
+ faceZMax.point[1] + deltaZ * zDir[1],
231
+ faceZMax.point[2] + deltaZ * zDir[2],
232
+ ];
233
+ this._updateSpherePosition(SPHEREINDEX.ZMAX, newPoint);
234
+ }
180
235
  this._updateCornerSpheres();
236
+ this._updateFaceSpheresFromCorners();
181
237
  }
182
238
  else {
183
- const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
184
- let newValue = world[axisIdx];
185
- if (this.faceDragOffset !== null) {
186
- newValue += this.faceDragOffset;
187
- }
188
- sphereState.point[axisIdx] = newValue;
189
- sphereState.sphereSource.setCenter(...sphereState.point);
190
- sphereState.sphereSource.modified();
239
+ const directionVector = this._getDirectionVectorForAxis(sphereState.axis);
240
+ const delta = [
241
+ world[0] - sphereState.point[0],
242
+ world[1] - sphereState.point[1],
243
+ world[2] - sphereState.point[2],
244
+ ];
245
+ const distanceAlongAxis = delta[0] * directionVector[0] +
246
+ delta[1] * directionVector[1] +
247
+ delta[2] * directionVector[2];
248
+ const adjustedDistance = this.faceDragOffset !== null
249
+ ? distanceAlongAxis + this.faceDragOffset
250
+ : distanceAlongAxis;
251
+ const newPoint = [
252
+ sphereState.point[0] + adjustedDistance * directionVector[0],
253
+ sphereState.point[1] + adjustedDistance * directionVector[1],
254
+ sphereState.point[2] + adjustedDistance * directionVector[2],
255
+ ];
256
+ this._updateSpherePosition(this.draggingSphereIndex, newPoint);
191
257
  this._updateCornerSpheresFromFaces();
192
258
  this._updateFaceSpheresFromCorners();
193
259
  this._updateCornerSpheres();
194
260
  }
195
261
  this._updateClippingPlanesFromFaceSpheres(viewport);
196
262
  viewport.render();
197
- this._triggerToolChangedEvent(sphereState);
263
+ this._notifyClippingPlanesChanged();
198
264
  return true;
199
265
  };
200
266
  this._onControlToolChange = (evt) => {
201
267
  const viewport = this._getViewport();
202
- if (!evt.detail.toolCenter) {
203
- triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
204
- originalClippingPlanes: this.originalClippingPlanes,
205
- viewportId: viewport.id,
206
- renderingEngineId: viewport.renderingEngineId,
207
- seriesInstanceUID: this.seriesInstanceUID,
208
- });
268
+ if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
269
+ return;
209
270
  }
210
- else {
211
- if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
212
- return;
213
- }
214
- const isMin = evt.detail.handleType === 'min';
215
- const toolCenter = isMin
216
- ? evt.detail.toolCenterMin
217
- : evt.detail.toolCenterMax;
218
- const normals = isMin
219
- ? [
220
- [1, 0, 0],
221
- [0, 1, 0],
222
- [0, 0, 1],
223
- ]
224
- : [
225
- [-1, 0, 0],
226
- [0, -1, 0],
227
- [0, 0, -1],
228
- ];
229
- const planeIndices = isMin
230
- ? [PLANEINDEX.XMIN, PLANEINDEX.YMIN, PLANEINDEX.ZMIN]
231
- : [PLANEINDEX.XMAX, PLANEINDEX.YMAX, PLANEINDEX.ZMAX];
232
- const sphereIndices = isMin
233
- ? [SPHEREINDEX.XMIN, SPHEREINDEX.YMIN, SPHEREINDEX.ZMIN]
234
- : [SPHEREINDEX.XMAX, SPHEREINDEX.YMAX, SPHEREINDEX.ZMAX];
235
- const axes = ['x', 'y', 'z'];
236
- const orientationAxes = [
237
- Enums.OrientationAxis.SAGITTAL,
238
- Enums.OrientationAxis.CORONAL,
239
- Enums.OrientationAxis.AXIAL,
240
- ];
241
- for (let i = 0; i < 3; ++i) {
242
- const origin = [0, 0, 0];
243
- origin[i] = toolCenter[i];
244
- const plane = vtkPlane.newInstance({
245
- origin,
246
- normal: normals[i],
247
- });
248
- this.originalClippingPlanes[planeIndices[i]].origin = plane.getOrigin();
249
- this.sphereStates[sphereIndices[i]].point[i] = plane.getOrigin()[i];
250
- this.sphereStates[sphereIndices[i]].sphereSource.setCenter(...this.sphereStates[sphereIndices[i]].point);
251
- this.sphereStates[sphereIndices[i]].sphereSource.modified();
252
- const otherSphere = this.sphereStates.find((s, idx) => s.axis === axes[i] && idx !== sphereIndices[i]);
253
- const newCenter = (otherSphere.point[i] + plane.getOrigin()[i]) / 2;
254
- this.sphereStates.forEach((state) => {
255
- if (!state.isCorner &&
256
- state.axis !== axes[i] &&
257
- !evt.detail.viewportOrientation.includes(orientationAxes[i])) {
258
- state.point[i] = newCenter;
259
- state.sphereSource.setCenter(state.point);
260
- state.sphereActor.getProperty().setColor(state.color);
261
- state.sphereSource.modified();
262
- }
263
- });
264
- const volumeActor = viewport.getDefaultActor()?.actor;
265
- if (volumeActor) {
266
- const mapper = volumeActor.getMapper();
267
- const clippingPlanes = mapper.getClippingPlanes();
268
- if (clippingPlanes) {
269
- clippingPlanes[planeIndices[i]].setOrigin(plane.getOrigin());
270
- }
271
- }
272
- }
271
+ if (evt.detail.clippingPlanes &&
272
+ evt.detail.clippingPlanes.length >= NUM_CLIPPING_PLANES) {
273
+ this.originalClippingPlanes = copyClippingPlanes(evt.detail.clippingPlanes);
274
+ this._updateFaceSpheresFromClippingPlanes();
275
+ this._updateCornerSpheresFromFaces();
276
+ this._updateFaceSpheresFromCorners();
273
277
  this._updateCornerSpheres();
278
+ const mapper = this._getVolumeMapper(viewport);
279
+ if (mapper) {
280
+ this._applyClippingPlanesToMapper(mapper);
281
+ }
274
282
  viewport.render();
283
+ this._notifyClippingPlanesChanged(viewport);
284
+ }
285
+ else {
286
+ this._notifyClippingPlanesChanged(viewport);
275
287
  }
276
288
  };
277
289
  this._getViewportsInfo = () => {
278
- const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
279
- return viewports;
290
+ const toolGroup = getToolGroup(this.toolGroupId);
291
+ return toolGroup?.viewportsInfo || [];
280
292
  };
281
293
  this._initialize3DViewports = (viewportsInfo) => {
282
- if (!viewportsInfo || !viewportsInfo.length || !viewportsInfo[0]) {
294
+ if (!viewportsInfo?.length || !viewportsInfo[0]) {
283
295
  console.warn('VolumeCroppingTool: No viewportsInfo available for initialization of volumecroppingtool.');
284
296
  return;
285
297
  }
286
298
  const viewport = this._getViewport();
287
- const volumeActors = viewport.getActors();
288
- if (!volumeActors || volumeActors.length === 0) {
299
+ if (!viewport) {
300
+ return;
301
+ }
302
+ const volumeActor = this._getVolumeActor(viewport);
303
+ if (!volumeActor) {
289
304
  console.warn('VolumeCroppingTool: No volume actors found in the viewport.');
290
305
  return;
291
306
  }
292
- const imageData = volumeActors[0].actor.getMapper().getInputData();
307
+ const imageData = volumeActor.getMapper().getInputData();
293
308
  if (!imageData) {
294
309
  console.warn('VolumeCroppingTool: No image data found for volume actor.');
295
310
  return;
296
311
  }
297
312
  this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
298
- const worldBounds = imageData.getBounds();
313
+ this.volumeDirectionVectors = extractVolumeDirectionVectors(imageData);
314
+ const { xDir, yDir, zDir } = this.volumeDirectionVectors;
315
+ const dimensions = imageData.getDimensions();
299
316
  const cropFactor = this.configuration.initialCropFactor || 0.1;
300
- const xRange = worldBounds[1] - worldBounds[0];
301
- const yRange = worldBounds[3] - worldBounds[2];
302
- const zRange = worldBounds[5] - worldBounds[4];
303
- const xMin = worldBounds[0] + cropFactor * xRange;
304
- const xMax = worldBounds[1] - cropFactor * xRange;
305
- const yMin = worldBounds[2] + cropFactor * yRange;
306
- const yMax = worldBounds[3] - cropFactor * yRange;
307
- const zMin = worldBounds[4] + cropFactor * zRange;
308
- const zMax = worldBounds[5] - cropFactor * zRange;
309
- const planes = [];
310
- const planeXmin = vtkPlane.newInstance({
311
- origin: [xMin, 0, 0],
312
- normal: [1, 0, 0],
317
+ const xMin = cropFactor * dimensions[0];
318
+ const xMax = (1 - cropFactor) * dimensions[0];
319
+ const yMin = cropFactor * dimensions[1];
320
+ const yMax = (1 - cropFactor) * dimensions[1];
321
+ const zMin = cropFactor * dimensions[2];
322
+ const zMax = (1 - cropFactor) * dimensions[2];
323
+ const xCenter = (xMin + xMax) / 2;
324
+ const yCenter = (yMin + yMax) / 2;
325
+ const zCenter = (zMin + zMax) / 2;
326
+ const faceXMin = imageData.indexToWorld([xMin, yCenter, zCenter]);
327
+ const faceXMax = imageData.indexToWorld([xMax, yCenter, zCenter]);
328
+ const faceYMin = imageData.indexToWorld([xCenter, yMin, zCenter]);
329
+ const faceYMax = imageData.indexToWorld([xCenter, yMax, zCenter]);
330
+ const faceZMin = imageData.indexToWorld([xCenter, yCenter, zMin]);
331
+ const faceZMax = imageData.indexToWorld([xCenter, yCenter, zMax]);
332
+ const planeXMin = vtkPlane.newInstance({
333
+ origin: faceXMin,
334
+ normal: xDir,
313
335
  });
314
- const planeXmax = vtkPlane.newInstance({
315
- origin: [xMax, 0, 0],
316
- normal: [-1, 0, 0],
336
+ const planeXMax = vtkPlane.newInstance({
337
+ origin: faceXMax,
338
+ normal: [-xDir[0], -xDir[1], -xDir[2]],
317
339
  });
318
- const planeYmin = vtkPlane.newInstance({
319
- origin: [0, yMin, 0],
320
- normal: [0, 1, 0],
340
+ const planeYMin = vtkPlane.newInstance({
341
+ origin: faceYMin,
342
+ normal: yDir,
321
343
  });
322
- const planeYmax = vtkPlane.newInstance({
323
- origin: [0, yMax, 0],
324
- normal: [0, -1, 0],
344
+ const planeYMax = vtkPlane.newInstance({
345
+ origin: faceYMax,
346
+ normal: [-yDir[0], -yDir[1], -yDir[2]],
325
347
  });
326
- const planeZmin = vtkPlane.newInstance({
327
- origin: [0, 0, zMin],
328
- normal: [0, 0, 1],
348
+ const planeZMin = vtkPlane.newInstance({
349
+ origin: faceZMin,
350
+ normal: zDir,
329
351
  });
330
- const planeZmax = vtkPlane.newInstance({
331
- origin: [0, 0, zMax],
332
- normal: [0, 0, -1],
352
+ const planeZMax = vtkPlane.newInstance({
353
+ origin: faceZMax,
354
+ normal: [-zDir[0], -zDir[1], -zDir[2]],
333
355
  });
334
- const mapper = viewport
335
- .getDefaultActor()
336
- .actor.getMapper();
337
- planes.push(planeXmin);
338
- planes.push(planeXmax);
339
- planes.push(planeYmin);
340
- planes.push(planeYmax);
341
- planes.push(planeZmin);
342
- planes.push(planeZmax);
356
+ const planes = [
357
+ planeXMin,
358
+ planeXMax,
359
+ planeYMin,
360
+ planeYMax,
361
+ planeZMin,
362
+ planeZMax,
363
+ ];
343
364
  const originalPlanes = planes.map((plane) => ({
344
365
  origin: [...plane.getOrigin()],
345
366
  normal: [...plane.getNormal()],
346
367
  }));
347
368
  this.originalClippingPlanes = originalPlanes;
348
- const sphereXminPoint = [xMin, (yMax + yMin) / 2, (zMax + zMin) / 2];
349
- const sphereXmaxPoint = [xMax, (yMax + yMin) / 2, (zMax + zMin) / 2];
350
- const sphereYminPoint = [(xMax + xMin) / 2, yMin, (zMax + zMin) / 2];
351
- const sphereYmaxPoint = [(xMax + xMin) / 2, yMax, (zMax + zMin) / 2];
352
- const sphereZminPoint = [(xMax + xMin) / 2, (yMax + yMin) / 2, zMin];
353
- const sphereZmaxPoint = [(xMax + xMin) / 2, (yMax + yMin) / 2, zMax];
354
- const adaptiveRadius = this._calculateAdaptiveSphereRadius(Math.sqrt(xRange * xRange + yRange * yRange + zRange * zRange));
355
- this._addSphere(viewport, sphereXminPoint, 'x', 'min', null, adaptiveRadius);
356
- this._addSphere(viewport, sphereXmaxPoint, 'x', 'max', null, adaptiveRadius);
357
- this._addSphere(viewport, sphereYminPoint, 'y', 'min', null, adaptiveRadius);
358
- this._addSphere(viewport, sphereYmaxPoint, 'y', 'max', null, adaptiveRadius);
359
- this._addSphere(viewport, sphereZminPoint, 'z', 'min', null, adaptiveRadius);
360
- this._addSphere(viewport, sphereZmaxPoint, 'z', 'max', null, adaptiveRadius);
369
+ const diag0 = imageData.indexToWorld([0, 0, 0]);
370
+ const diag1 = imageData.indexToWorld([
371
+ dimensions[0],
372
+ dimensions[1],
373
+ dimensions[2],
374
+ ]);
375
+ const diagonal = vec3.distance(diag0, diag1);
376
+ const adaptiveRadius = calculateAdaptiveSphereRadius(diagonal, this.configuration);
377
+ this._addSphere(viewport, faceXMin, 'x', 'min', null, adaptiveRadius);
378
+ this._addSphere(viewport, faceXMax, 'x', 'max', null, adaptiveRadius);
379
+ this._addSphere(viewport, faceYMin, 'y', 'min', null, adaptiveRadius);
380
+ this._addSphere(viewport, faceYMax, 'y', 'max', null, adaptiveRadius);
381
+ this._addSphere(viewport, faceZMin, 'z', 'min', null, adaptiveRadius);
382
+ this._addSphere(viewport, faceZMax, 'z', 'max', null, adaptiveRadius);
361
383
  const corners = [
362
- [xMin, yMin, zMin],
363
- [xMin, yMin, zMax],
364
- [xMin, yMax, zMin],
365
- [xMin, yMax, zMax],
366
- [xMax, yMin, zMin],
367
- [xMax, yMin, zMax],
368
- [xMax, yMax, zMin],
369
- [xMax, yMax, zMax],
384
+ imageData.indexToWorld([xMin, yMin, zMin]),
385
+ imageData.indexToWorld([xMin, yMin, zMax]),
386
+ imageData.indexToWorld([xMin, yMax, zMin]),
387
+ imageData.indexToWorld([xMin, yMax, zMax]),
388
+ imageData.indexToWorld([xMax, yMin, zMin]),
389
+ imageData.indexToWorld([xMax, yMin, zMax]),
390
+ imageData.indexToWorld([xMax, yMax, zMin]),
391
+ imageData.indexToWorld([xMax, yMax, zMax]),
370
392
  ];
371
393
  const cornerKeys = [
372
394
  'XMIN_YMIN_ZMIN',
@@ -395,21 +417,22 @@ class VolumeCroppingTool extends BaseTool {
395
417
  ['XMAX_YMIN_ZMIN', 'XMAX_YMIN_ZMAX'],
396
418
  ['XMAX_YMAX_ZMIN', 'XMAX_YMAX_ZMAX'],
397
419
  ];
398
- edgeCornerPairs.forEach(([key1, key2], i) => {
420
+ edgeCornerPairs.forEach(([key1, key2]) => {
399
421
  const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
400
422
  const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
401
423
  if (state1 && state2) {
402
424
  const uid = `edge_${key1}_${key2}`;
403
- const { actor, source } = this._addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid);
425
+ const { actor, source } = addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid, this.configuration.showHandles);
404
426
  this.edgeLines[uid] = { actor, source, key1, key2 };
405
427
  }
406
428
  });
407
- mapper.addClippingPlane(planeXmin);
408
- mapper.addClippingPlane(planeXmax);
409
- mapper.addClippingPlane(planeYmin);
410
- mapper.addClippingPlane(planeYmax);
411
- mapper.addClippingPlane(planeZmin);
412
- mapper.addClippingPlane(planeZmax);
429
+ const mapper = volumeActor.getMapper();
430
+ mapper.addClippingPlane(planeXMin);
431
+ mapper.addClippingPlane(planeXMax);
432
+ mapper.addClippingPlane(planeYMin);
433
+ mapper.addClippingPlane(planeYMax);
434
+ mapper.addClippingPlane(planeZMin);
435
+ mapper.addClippingPlane(planeZMax);
413
436
  eventTarget.addEventListener(Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, (evt) => {
414
437
  this._onControlToolChange(evt);
415
438
  });
@@ -423,107 +446,14 @@ class VolumeCroppingTool extends BaseTool {
423
446
  return { viewport, world };
424
447
  };
425
448
  this._getViewport = () => {
426
- const [viewport3D] = this._getViewportsInfo();
427
- const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
428
- return renderingEngine.getViewport(viewport3D.viewportId);
429
- };
430
- this._handleCornerSphereMovement = (sphereState, world, viewport) => {
431
- const newCorner = this._calculateNewCornerPosition(world);
432
- this._updateSpherePosition(sphereState, newCorner);
433
- const axisFlags = this._parseCornerKey(sphereState.uid);
434
- this._updateRelatedCorners(sphereState, newCorner, axisFlags);
435
- this._updateAfterCornerMovement(viewport);
436
- };
437
- this._handleFaceSphereMovement = (sphereState, world, viewport) => {
438
- const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
439
- let newValue = world[axisIdx];
440
- if (this.faceDragOffset !== null) {
441
- newValue += this.faceDragOffset;
442
- }
443
- sphereState.point[axisIdx] = newValue;
444
- sphereState.sphereSource.setCenter(...sphereState.point);
445
- sphereState.sphereSource.modified();
446
- this._updateAfterFaceMovement(viewport);
447
- };
448
- this._calculateNewCornerPosition = (world) => {
449
- let newCorner = [world[0], world[1], world[2]];
450
- if (this.cornerDragOffset) {
451
- newCorner = [
452
- world[0] + this.cornerDragOffset[0],
453
- world[1] + this.cornerDragOffset[1],
454
- world[2] + this.cornerDragOffset[2],
455
- ];
456
- }
457
- return newCorner;
458
- };
459
- this._parseCornerKey = (uid) => {
460
- const cornerKey = uid.replace('corner_', '');
461
- return {
462
- isXMin: cornerKey.includes('XMIN'),
463
- isXMax: cornerKey.includes('XMAX'),
464
- isYMin: cornerKey.includes('YMIN'),
465
- isYMax: cornerKey.includes('YMAX'),
466
- isZMin: cornerKey.includes('ZMIN'),
467
- isZMax: cornerKey.includes('ZMAX'),
468
- };
469
- };
470
- this._updateSpherePosition = (sphereState, newPosition) => {
471
- sphereState.point = newPosition;
472
- sphereState.sphereSource.setCenter(...newPosition);
473
- sphereState.sphereSource.modified();
474
- };
475
- this._updateRelatedCorners = (draggedSphere, newCorner, axisFlags) => {
476
- this.sphereStates.forEach((state) => {
477
- if (!state.isCorner || state === draggedSphere) {
478
- return;
479
- }
480
- const key = state.uid.replace('corner_', '');
481
- const shouldUpdate = this._shouldUpdateCorner(key, axisFlags);
482
- if (shouldUpdate) {
483
- this._updateCornerCoordinates(state, newCorner, key, axisFlags);
484
- }
485
- });
486
- };
487
- this._shouldUpdateCorner = (cornerKey, axisFlags) => {
488
- return ((axisFlags.isXMin && cornerKey.includes('XMIN')) ||
489
- (axisFlags.isXMax && cornerKey.includes('XMAX')) ||
490
- (axisFlags.isYMin && cornerKey.includes('YMIN')) ||
491
- (axisFlags.isYMax && cornerKey.includes('YMAX')) ||
492
- (axisFlags.isZMin && cornerKey.includes('ZMIN')) ||
493
- (axisFlags.isZMax && cornerKey.includes('ZMAX')));
494
- };
495
- this._updateCornerCoordinates = (state, newCorner, cornerKey, axisFlags) => {
496
- if ((axisFlags.isXMin && cornerKey.includes('XMIN')) ||
497
- (axisFlags.isXMax && cornerKey.includes('XMAX'))) {
498
- state.point[0] = newCorner[0];
499
- }
500
- if ((axisFlags.isYMin && cornerKey.includes('YMIN')) ||
501
- (axisFlags.isYMax && cornerKey.includes('YMAX'))) {
502
- state.point[1] = newCorner[1];
503
- }
504
- if ((axisFlags.isZMin && cornerKey.includes('ZMIN')) ||
505
- (axisFlags.isZMax && cornerKey.includes('ZMAX'))) {
506
- state.point[2] = newCorner[2];
449
+ const viewportsInfo = this._getViewportsInfo();
450
+ if (!viewportsInfo || viewportsInfo.length === 0) {
451
+ return null;
507
452
  }
508
- state.sphereSource.setCenter(...state.point);
509
- state.sphereSource.modified();
510
- };
511
- this._updateAfterCornerMovement = (viewport) => {
512
- this._updateFaceSpheresFromCorners();
513
- this._updateCornerSpheres();
514
- this._updateClippingPlanesFromFaceSpheres(viewport);
515
- };
516
- this._updateAfterFaceMovement = (viewport) => {
517
- this._updateCornerSpheresFromFaces();
518
- this._updateClippingPlanesFromFaceSpheres(viewport);
519
- };
520
- this._triggerToolChangedEvent = (sphereState) => {
521
- triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
522
- toolCenter: sphereState.point,
523
- axis: sphereState.isCorner ? 'corner' : sphereState.axis,
524
- draggingSphereIndex: this.draggingSphereIndex,
525
- seriesInstanceUID: this.seriesInstanceUID,
526
- });
453
+ const [viewport3D] = viewportsInfo;
454
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
455
+ const viewport = renderingEngine?.getViewport(viewport3D.viewportId);
456
+ return viewport || null;
527
457
  };
528
458
  this._onNewVolume = () => {
529
459
  const viewportsInfo = this._getViewportsInfo();
@@ -532,6 +462,207 @@ class VolumeCroppingTool extends BaseTool {
532
462
  this.edgeLines = {};
533
463
  this._initialize3DViewports(viewportsInfo);
534
464
  };
465
+ this._rotateClippingPlanes = (evt) => {
466
+ const { element, currentPoints, lastPoints } = evt.detail;
467
+ const currentPointsCanvas = currentPoints.canvas;
468
+ const lastPointsCanvas = lastPoints.canvas;
469
+ const rotateIncrementDegrees = this.configuration.rotateClippingPlanesIncrementDegrees ??
470
+ this.configuration.rotateIncrementDegrees ??
471
+ 5;
472
+ const enabledElement = getEnabledElement(element);
473
+ const { viewport } = enabledElement;
474
+ const camera = viewport.getCamera();
475
+ const width = element.clientWidth;
476
+ const height = element.clientHeight;
477
+ const normalizedPosition = [
478
+ currentPointsCanvas[0] / width,
479
+ currentPointsCanvas[1] / height,
480
+ ];
481
+ const normalizedPreviousPosition = [
482
+ lastPointsCanvas[0] / width,
483
+ lastPointsCanvas[1] / height,
484
+ ];
485
+ const normalizedCenter = [0.5, 0.5];
486
+ const radsq = (1.0 + Math.abs(normalizedCenter[0])) ** 2.0;
487
+ const op = [normalizedPreviousPosition[0], 0, 0];
488
+ const oe = [normalizedPosition[0], 0, 0];
489
+ const opsq = op[0] ** 2;
490
+ const oesq = oe[0] ** 2;
491
+ const lop = opsq > radsq ? 0 : Math.sqrt(radsq - opsq);
492
+ const loe = oesq > radsq ? 0 : Math.sqrt(radsq - oesq);
493
+ const nop = [op[0], 0, lop];
494
+ vtkMath.normalize(nop);
495
+ const noe = [oe[0], 0, loe];
496
+ vtkMath.normalize(noe);
497
+ const dot = vtkMath.dot(nop, noe);
498
+ if (Math.abs(dot) > 0.0001) {
499
+ const angleX = 20 *
500
+ Math.acos(vtkMath.clampValue(dot, -1.0, 1.0)) *
501
+ Math.sign(normalizedPosition[0] - normalizedPreviousPosition[0]) *
502
+ rotateIncrementDegrees;
503
+ const upVec = camera.viewUp;
504
+ const atV = camera.viewPlaneNormal;
505
+ const rightV = [0, 0, 0];
506
+ const forwardV = [0, 0, 0];
507
+ vtkMath.cross(upVec, atV, rightV);
508
+ vtkMath.normalize(rightV);
509
+ vtkMath.cross(atV, rightV, forwardV);
510
+ vtkMath.normalize(forwardV);
511
+ const angleY = 20 *
512
+ (normalizedPosition[1] - normalizedPreviousPosition[1]) *
513
+ rotateIncrementDegrees;
514
+ let rotationCenter;
515
+ if (this.sphereStates.length >= NUM_CLIPPING_PLANES) {
516
+ const faces = [
517
+ this.sphereStates[SPHEREINDEX.XMIN],
518
+ this.sphereStates[SPHEREINDEX.XMAX],
519
+ this.sphereStates[SPHEREINDEX.YMIN],
520
+ this.sphereStates[SPHEREINDEX.YMAX],
521
+ this.sphereStates[SPHEREINDEX.ZMIN],
522
+ this.sphereStates[SPHEREINDEX.ZMAX],
523
+ ];
524
+ rotationCenter = [
525
+ (faces[0].point[0] +
526
+ faces[1].point[0] +
527
+ faces[2].point[0] +
528
+ faces[3].point[0] +
529
+ faces[4].point[0] +
530
+ faces[5].point[0]) /
531
+ NUM_CLIPPING_PLANES,
532
+ (faces[0].point[1] +
533
+ faces[1].point[1] +
534
+ faces[2].point[1] +
535
+ faces[3].point[1] +
536
+ faces[4].point[1] +
537
+ faces[5].point[1]) /
538
+ NUM_CLIPPING_PLANES,
539
+ (faces[0].point[2] +
540
+ faces[1].point[2] +
541
+ faces[2].point[2] +
542
+ faces[3].point[2] +
543
+ faces[4].point[2] +
544
+ faces[5].point[2]) /
545
+ NUM_CLIPPING_PLANES,
546
+ ];
547
+ }
548
+ else {
549
+ rotationCenter = [0, 0, 0];
550
+ }
551
+ const transformX = mat4.identity(new Float32Array(16));
552
+ mat4.translate(transformX, transformX, rotationCenter);
553
+ mat4.rotate(transformX, transformX, (angleX * Math.PI) / 180, forwardV);
554
+ mat4.translate(transformX, transformX, [
555
+ -rotationCenter[0],
556
+ -rotationCenter[1],
557
+ -rotationCenter[2],
558
+ ]);
559
+ const transformY = mat4.identity(new Float32Array(16));
560
+ mat4.translate(transformY, transformY, rotationCenter);
561
+ mat4.rotate(transformY, transformY, (angleY * Math.PI) / 180, rightV);
562
+ mat4.translate(transformY, transformY, [
563
+ -rotationCenter[0],
564
+ -rotationCenter[1],
565
+ -rotationCenter[2],
566
+ ]);
567
+ const transform = mat4.create();
568
+ mat4.multiply(transform, transformY, transformX);
569
+ const normalTransformX4 = mat4.identity(new Float32Array(16));
570
+ mat4.rotate(normalTransformX4, normalTransformX4, (angleX * Math.PI) / 180, forwardV);
571
+ const normalTransformX = mat3.create();
572
+ mat3.fromMat4(normalTransformX, normalTransformX4);
573
+ const normalTransformY4 = mat4.identity(new Float32Array(16));
574
+ mat4.rotate(normalTransformY4, normalTransformY4, (angleY * Math.PI) / 180, rightV);
575
+ const normalTransformY = mat3.create();
576
+ mat3.fromMat4(normalTransformY, normalTransformY4);
577
+ const normalTransform = mat3.create();
578
+ mat3.multiply(normalTransform, normalTransformY, normalTransformX);
579
+ for (let i = 0; i < this.originalClippingPlanes.length; ++i) {
580
+ const plane = this.originalClippingPlanes[i];
581
+ const originVec = vec3.fromValues(plane.origin[0], plane.origin[1], plane.origin[2]);
582
+ vec3.transformMat4(originVec, originVec, transform);
583
+ plane.origin = [originVec[0], originVec[1], originVec[2]];
584
+ const normalVec = vec3.fromValues(plane.normal[0], plane.normal[1], plane.normal[2]);
585
+ vec3.transformMat3(normalVec, normalVec, normalTransform);
586
+ vec3.normalize(normalVec, normalVec);
587
+ plane.normal = [normalVec[0], normalVec[1], normalVec[2]];
588
+ }
589
+ if (this.sphereStates.length >= NUM_CLIPPING_PLANES) {
590
+ this.sphereStates[SPHEREINDEX.XMIN].point = [
591
+ ...this.originalClippingPlanes[PLANEINDEX.XMIN].origin,
592
+ ];
593
+ this.sphereStates[SPHEREINDEX.XMAX].point = [
594
+ ...this.originalClippingPlanes[PLANEINDEX.XMAX].origin,
595
+ ];
596
+ this.sphereStates[SPHEREINDEX.YMIN].point = [
597
+ ...this.originalClippingPlanes[PLANEINDEX.YMIN].origin,
598
+ ];
599
+ this.sphereStates[SPHEREINDEX.YMAX].point = [
600
+ ...this.originalClippingPlanes[PLANEINDEX.YMAX].origin,
601
+ ];
602
+ this.sphereStates[SPHEREINDEX.ZMIN].point = [
603
+ ...this.originalClippingPlanes[PLANEINDEX.ZMIN].origin,
604
+ ];
605
+ this.sphereStates[SPHEREINDEX.ZMAX].point = [
606
+ ...this.originalClippingPlanes[PLANEINDEX.ZMAX].origin,
607
+ ];
608
+ [
609
+ SPHEREINDEX.XMIN,
610
+ SPHEREINDEX.XMAX,
611
+ SPHEREINDEX.YMIN,
612
+ SPHEREINDEX.YMAX,
613
+ SPHEREINDEX.ZMIN,
614
+ SPHEREINDEX.ZMAX,
615
+ ].forEach((idx) => {
616
+ const s = this.sphereStates[idx];
617
+ s.sphereSource.setCenter(...s.point);
618
+ s.sphereSource.modified();
619
+ });
620
+ const cornerIndices = [
621
+ SPHEREINDEX.XMIN_YMIN_ZMIN,
622
+ SPHEREINDEX.XMIN_YMIN_ZMAX,
623
+ SPHEREINDEX.XMIN_YMAX_ZMIN,
624
+ SPHEREINDEX.XMIN_YMAX_ZMAX,
625
+ SPHEREINDEX.XMAX_YMIN_ZMIN,
626
+ SPHEREINDEX.XMAX_YMIN_ZMAX,
627
+ SPHEREINDEX.XMAX_YMAX_ZMIN,
628
+ SPHEREINDEX.XMAX_YMAX_ZMAX,
629
+ ];
630
+ cornerIndices.forEach((idx) => {
631
+ const cornerState = this.sphereStates[idx];
632
+ if (cornerState) {
633
+ const cornerVec = vec3.fromValues(cornerState.point[0], cornerState.point[1], cornerState.point[2]);
634
+ vec3.transformMat4(cornerVec, cornerVec, transform);
635
+ cornerState.point = [
636
+ cornerVec[0],
637
+ cornerVec[1],
638
+ cornerVec[2],
639
+ ];
640
+ cornerState.sphereSource.setCenter(...cornerState.point);
641
+ cornerState.sphereSource.modified();
642
+ }
643
+ });
644
+ Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
645
+ const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
646
+ const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
647
+ if (state1 && state2) {
648
+ const points = source.getPoints();
649
+ points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
650
+ points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
651
+ points.modified();
652
+ source.modified();
653
+ }
654
+ });
655
+ }
656
+ this._updateClippingPlanes(viewport);
657
+ viewport.render();
658
+ triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
659
+ originalClippingPlanes: this.originalClippingPlanes,
660
+ viewportId: viewport.id,
661
+ renderingEngineId: viewport.renderingEngineId,
662
+ seriesInstanceUID: this.seriesInstanceUID,
663
+ });
664
+ }
665
+ };
535
666
  this._rotateCamera = (viewport, centerWorld, axis, angle) => {
536
667
  const vtkCamera = viewport.getVtkActiveCamera();
537
668
  const viewUp = vtkCamera.getViewUp();
@@ -563,59 +694,55 @@ class VolumeCroppingTool extends BaseTool {
563
694
  this.mouseDragCallback = this._dragCallback.bind(this);
564
695
  }
565
696
  onSetToolActive() {
566
- if (this.sphereStates && this.sphereStates.length > 0) {
567
- if (this.configuration.showHandles) {
568
- this.setHandlesVisible(false);
569
- this.setClippingPlanesVisible(false);
570
- }
571
- else {
572
- this.setHandlesVisible(true);
573
- this.setClippingPlanesVisible(true);
574
- }
575
- }
576
- else {
577
- const viewportsInfo = this._getViewportsInfo();
578
- const subscribeToElementResize = () => {
579
- viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
580
- if (!this._resizeObservers.has(viewportId)) {
581
- const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId) || { viewport: null };
582
- if (!viewport) {
697
+ const viewportsInfo = this._getViewportsInfo();
698
+ const subscribeToElementResize = () => {
699
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
700
+ if (!this._resizeObservers.has(viewportId)) {
701
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId) || { viewport: null };
702
+ if (!viewport) {
703
+ return;
704
+ }
705
+ const { element } = viewport;
706
+ const resizeObserver = new ResizeObserver(() => {
707
+ const element = getEnabledElementByIds(viewportId, renderingEngineId);
708
+ if (!element) {
583
709
  return;
584
710
  }
585
- const { element } = viewport;
586
- const resizeObserver = new ResizeObserver(() => {
587
- const element = getEnabledElementByIds(viewportId, renderingEngineId);
588
- if (!element) {
589
- return;
590
- }
591
- const { viewport } = element;
592
- const viewPresentation = viewport.getViewPresentation();
593
- viewport.resetCamera();
594
- viewport.setViewPresentation(viewPresentation);
595
- viewport.render();
596
- });
597
- resizeObserver.observe(element);
598
- this._resizeObservers.set(viewportId, resizeObserver);
599
- }
600
- });
601
- };
602
- subscribeToElementResize();
603
- this._viewportAddedListener = (evt) => {
604
- if (evt.detail.toolGroupId === this.toolGroupId) {
605
- subscribeToElementResize();
711
+ const { viewport } = element;
712
+ const viewPresentation = getViewportPresentation(viewport);
713
+ viewport.resetCamera();
714
+ applyViewportPresentation(viewport, viewPresentation);
715
+ viewport.render();
716
+ });
717
+ resizeObserver.observe(element);
718
+ this._resizeObservers.set(viewportId, resizeObserver);
606
719
  }
607
- };
608
- eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
609
- this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
610
- this._subscribeToViewportNewVolumeSet(viewportsInfo);
720
+ });
721
+ };
722
+ subscribeToElementResize();
723
+ this._viewportAddedListener = (evt) => {
724
+ if (evt.detail.toolGroupId === this.toolGroupId) {
725
+ subscribeToElementResize();
726
+ }
727
+ };
728
+ eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
729
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
730
+ this._subscribeToViewportNewVolumeSet(viewportsInfo);
731
+ if (this.sphereStates && this.sphereStates.length === 0) {
732
+ this.originalClippingPlanes = [];
611
733
  this._initialize3DViewports(viewportsInfo);
734
+ }
735
+ this.configuration.showClippingPlanes = false;
736
+ this.configuration.showHandles = false;
737
+ const viewport = this._getViewport();
738
+ if (viewport &&
739
+ this.originalClippingPlanes &&
740
+ this.originalClippingPlanes.length > 0) {
741
+ this._updateClippingPlanes(viewport);
612
742
  if (this.sphereStates && this.sphereStates.length > 0) {
613
- this.setHandlesVisible(true);
614
- }
615
- else {
616
- this.originalClippingPlanes = [];
617
- this._initialize3DViewports(viewportsInfo);
743
+ this._updateHandlesVisibility();
618
744
  }
745
+ viewport.render();
619
746
  }
620
747
  }
621
748
  onSetToolDisabled() {
@@ -633,30 +760,7 @@ class VolumeCroppingTool extends BaseTool {
633
760
  setHandlesVisible(visible) {
634
761
  this.configuration.showHandles = visible;
635
762
  if (visible) {
636
- this.sphereStates[SPHEREINDEX.XMIN].point[0] =
637
- this.originalClippingPlanes[PLANEINDEX.XMIN].origin[0];
638
- this.sphereStates[SPHEREINDEX.XMAX].point[0] =
639
- this.originalClippingPlanes[PLANEINDEX.XMAX].origin[0];
640
- this.sphereStates[SPHEREINDEX.YMIN].point[1] =
641
- this.originalClippingPlanes[PLANEINDEX.YMIN].origin[1];
642
- this.sphereStates[SPHEREINDEX.YMAX].point[1] =
643
- this.originalClippingPlanes[PLANEINDEX.YMAX].origin[1];
644
- this.sphereStates[SPHEREINDEX.ZMIN].point[2] =
645
- this.originalClippingPlanes[PLANEINDEX.ZMIN].origin[2];
646
- this.sphereStates[SPHEREINDEX.ZMAX].point[2] =
647
- this.originalClippingPlanes[PLANEINDEX.ZMAX].origin[2];
648
- [
649
- SPHEREINDEX.XMIN,
650
- SPHEREINDEX.XMAX,
651
- SPHEREINDEX.YMIN,
652
- SPHEREINDEX.YMAX,
653
- SPHEREINDEX.ZMIN,
654
- SPHEREINDEX.ZMAX,
655
- ].forEach((idx) => {
656
- const s = this.sphereStates[idx];
657
- s.sphereSource.setCenter(...s.point);
658
- s.sphereSource.modified();
659
- });
763
+ this._updateFaceSpheresFromClippingPlanes();
660
764
  this._updateCornerSpheres();
661
765
  }
662
766
  this._updateHandlesVisibility();
@@ -669,6 +773,23 @@ class VolumeCroppingTool extends BaseTool {
669
773
  getHandlesVisible() {
670
774
  return this.configuration.showHandles;
671
775
  }
776
+ setHandleRadius(radius) {
777
+ this.configuration.sphereRadius = radius;
778
+ this.sphereStates.forEach((state) => {
779
+ if (state?.sphereSource?.setRadius) {
780
+ state.sphereSource.setRadius(radius);
781
+ state.sphereSource.modified();
782
+ }
783
+ });
784
+ const viewportsInfo = this._getViewportsInfo();
785
+ const [viewport3D] = viewportsInfo;
786
+ if (!viewport3D) {
787
+ return;
788
+ }
789
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
790
+ const viewport = renderingEngine?.getViewport(viewport3D.viewportId);
791
+ viewport?.render();
792
+ }
672
793
  getClippingPlanesVisible() {
673
794
  return this.configuration.showClippingPlanes;
674
795
  }
@@ -676,14 +797,39 @@ class VolumeCroppingTool extends BaseTool {
676
797
  this.configuration.showClippingPlanes = visible;
677
798
  const viewport = this._getViewport();
678
799
  this._updateClippingPlanes(viewport);
800
+ if (this.sphereStates && this.sphereStates.length > 0) {
801
+ this.configuration.showHandles = visible;
802
+ this._updateHandlesVisibility();
803
+ }
804
+ if (visible &&
805
+ viewport &&
806
+ this.originalClippingPlanes?.length >= NUM_CLIPPING_PLANES) {
807
+ this._notifyClippingPlanesChanged(viewport);
808
+ }
679
809
  viewport.render();
680
810
  }
811
+ getRotatePlanesOnDrag() {
812
+ return this.rotatePlanesOnDrag;
813
+ }
814
+ setRotatePlanesOnDrag(enable) {
815
+ this.rotatePlanesOnDrag = enable;
816
+ const viewport = this._getViewport();
817
+ if (viewport) {
818
+ viewport.render();
819
+ }
820
+ }
681
821
  _dragCallback(evt) {
682
822
  const { element, currentPoints, lastPoints } = evt.detail;
683
823
  if (this.draggingSphereIndex !== null) {
684
824
  this._onMouseMoveSphere(evt);
685
825
  }
686
826
  else {
827
+ const shiftKey = evt.detail.event?.shiftKey ?? false;
828
+ if ((this.rotatePlanesOnDrag === true || shiftKey) &&
829
+ !this.suppressPlaneRotationForCurrentDrag) {
830
+ this._rotateClippingPlanes(evt);
831
+ return;
832
+ }
687
833
  const currentPointsCanvas = currentPoints.canvas;
688
834
  const lastPointsCanvas = lastPoints.canvas;
689
835
  const { rotateIncrementDegrees } = this.configuration;
@@ -738,16 +884,11 @@ class VolumeCroppingTool extends BaseTool {
738
884
  }
739
885
  }
740
886
  _updateClippingPlanes(viewport) {
741
- const actorEntry = viewport.getDefaultActor();
742
- if (!actorEntry || !actorEntry.actor) {
743
- if (!viewport._missingActorWarned) {
744
- console.warn('VolumeCroppingTool._updateClippingPlanes: No default actor found in viewport.');
745
- viewport._missingActorWarned = true;
746
- }
887
+ const actor = this._getVolumeActor(viewport);
888
+ const mapper = this._getVolumeMapper(viewport);
889
+ if (!actor || !mapper) {
747
890
  return;
748
891
  }
749
- const actor = actorEntry.actor;
750
- const mapper = actor.getMapper();
751
892
  const matrix = actor.getMatrix();
752
893
  if (!this.configuration.showClippingPlanes) {
753
894
  mapper.removeAllClippingPlanes();
@@ -797,35 +938,6 @@ class VolumeCroppingTool extends BaseTool {
797
938
  }
798
939
  });
799
940
  }
800
- _addLine3DBetweenPoints(viewport, point1, point2, color = [0.7, 0.7, 0.7], uid = '') {
801
- if (point1[0] === point2[0] &&
802
- point1[1] === point2[1] &&
803
- point1[2] === point2[2]) {
804
- return { actor: null, source: null };
805
- }
806
- const points = vtkPoints.newInstance();
807
- points.setNumberOfPoints(2);
808
- points.setPoint(0, point1[0], point1[1], point1[2]);
809
- points.setPoint(1, point2[0], point2[1], point2[2]);
810
- const lines = vtkCellArray.newInstance({ values: [2, 0, 1] });
811
- const polyData = vtkPolyData.newInstance();
812
- polyData.setPoints(points);
813
- polyData.setLines(lines);
814
- const mapper = vtkMapper.newInstance();
815
- mapper.setInputData(polyData);
816
- const actor = vtkActor.newInstance();
817
- actor.setMapper(mapper);
818
- actor.getProperty().setColor(...color);
819
- actor.getProperty().setLineWidth(0.5);
820
- actor.getProperty().setOpacity(1.0);
821
- actor.getProperty().setInterpolationToFlat();
822
- actor.getProperty().setAmbient(1.0);
823
- actor.getProperty().setDiffuse(0.0);
824
- actor.getProperty().setSpecular(0.0);
825
- actor.setVisibility(this.configuration.showHandles);
826
- viewport.addActor({ actor, uid });
827
- return { actor, source: polyData };
828
- }
829
941
  _addSphere(viewport, point, axis, position, cornerKey = null, adaptiveRadius) {
830
942
  const uid = cornerKey ? `corner_${cornerKey}` : `${axis}_${position}`;
831
943
  const sphereState = this.sphereStates.find((s) => s.uid === uid);
@@ -854,9 +966,10 @@ class VolumeCroppingTool extends BaseTool {
854
966
  color = sphereColors.CORONAL || [0.0, 1.0, 0.0];
855
967
  }
856
968
  const idx = this.sphereStates.findIndex((s) => s.uid === uid);
969
+ const pointCopy = [point[0], point[1], point[2]];
857
970
  if (idx === -1) {
858
971
  this.sphereStates.push({
859
- point: point.slice(),
972
+ point: pointCopy,
860
973
  axis,
861
974
  uid,
862
975
  sphereSource,
@@ -866,88 +979,150 @@ class VolumeCroppingTool extends BaseTool {
866
979
  });
867
980
  }
868
981
  else {
869
- this.sphereStates[idx].point = point.slice();
982
+ this.sphereStates[idx].point = pointCopy;
870
983
  this.sphereStates[idx].sphereSource = sphereSource;
871
984
  }
872
- const existingActors = viewport.getActors();
873
- const existing = existingActors.find((a) => a.uid === uid);
874
- if (existing) {
875
- return;
876
- }
877
985
  sphereActor.getProperty().setColor(color);
878
986
  sphereActor.setVisibility(this.configuration.showHandles);
879
987
  viewport.addActor({ actor: sphereActor, uid: uid });
880
988
  }
881
- _calculateAdaptiveSphereRadius(diagonal) {
882
- const baseRadius = this.configuration.sphereRadius !== undefined
883
- ? this.configuration.sphereRadius
884
- : 8;
885
- const scaleFactor = this.configuration.sphereRadiusScale || 0.01;
886
- const adaptiveRadius = diagonal * scaleFactor;
887
- const minRadius = this.configuration.minSphereRadius || 2;
888
- const maxRadius = this.configuration.maxSphereRadius || 50;
889
- return Math.max(minRadius, Math.min(maxRadius, adaptiveRadius));
989
+ _getDirectionVectorForAxis(axis) {
990
+ if (this.originalClippingPlanes &&
991
+ this.originalClippingPlanes.length >= NUM_CLIPPING_PLANES) {
992
+ switch (axis) {
993
+ case 'x':
994
+ return this.originalClippingPlanes[PLANEINDEX.XMIN]
995
+ .normal;
996
+ case 'y':
997
+ return this.originalClippingPlanes[PLANEINDEX.YMIN]
998
+ .normal;
999
+ case 'z':
1000
+ return this.originalClippingPlanes[PLANEINDEX.ZMIN]
1001
+ .normal;
1002
+ default:
1003
+ return [1, 0, 0];
1004
+ }
1005
+ }
1006
+ if (!this.volumeDirectionVectors) {
1007
+ if (axis === 'x')
1008
+ return [1, 0, 0];
1009
+ if (axis === 'y')
1010
+ return [0, 1, 0];
1011
+ if (axis === 'z')
1012
+ return [0, 0, 1];
1013
+ return [1, 0, 0];
1014
+ }
1015
+ switch (axis) {
1016
+ case 'x':
1017
+ return this.volumeDirectionVectors.xDir;
1018
+ case 'y':
1019
+ return this.volumeDirectionVectors.yDir;
1020
+ case 'z':
1021
+ return this.volumeDirectionVectors.zDir;
1022
+ default:
1023
+ return [1, 0, 0];
1024
+ }
1025
+ }
1026
+ _getVolumeActor(viewport) {
1027
+ const vp = viewport || this._getViewport();
1028
+ return vp
1029
+ ?.getActors?.()
1030
+ ?.find((entry) => entry.actor?.getClassName?.() === 'vtkVolume')
1031
+ ?.actor;
1032
+ }
1033
+ _getVolumeMapper(viewport) {
1034
+ const actor = this._getVolumeActor(viewport);
1035
+ return actor?.getMapper();
1036
+ }
1037
+ _applyClippingPlanesToMapper(mapper) {
1038
+ mapper.removeAllClippingPlanes();
1039
+ for (let i = 0; i < NUM_CLIPPING_PLANES; ++i) {
1040
+ const plane = vtkPlane.newInstance({
1041
+ origin: this.originalClippingPlanes[i].origin,
1042
+ normal: this.originalClippingPlanes[i].normal,
1043
+ });
1044
+ mapper.addClippingPlane(plane);
1045
+ }
890
1046
  }
891
1047
  _updateClippingPlanesFromFaceSpheres(viewport) {
892
- const mapper = viewport.getDefaultActor().actor.getMapper();
893
- this.originalClippingPlanes[0].origin = [
1048
+ const mapper = this._getVolumeMapper(viewport);
1049
+ if (!mapper) {
1050
+ return;
1051
+ }
1052
+ this.originalClippingPlanes[PLANEINDEX.XMIN].origin = [
894
1053
  ...this.sphereStates[SPHEREINDEX.XMIN].point,
895
1054
  ];
896
- this.originalClippingPlanes[1].origin = [
1055
+ this.originalClippingPlanes[PLANEINDEX.XMAX].origin = [
897
1056
  ...this.sphereStates[SPHEREINDEX.XMAX].point,
898
1057
  ];
899
- this.originalClippingPlanes[2].origin = [
1058
+ this.originalClippingPlanes[PLANEINDEX.YMIN].origin = [
900
1059
  ...this.sphereStates[SPHEREINDEX.YMIN].point,
901
1060
  ];
902
- this.originalClippingPlanes[3].origin = [
1061
+ this.originalClippingPlanes[PLANEINDEX.YMAX].origin = [
903
1062
  ...this.sphereStates[SPHEREINDEX.YMAX].point,
904
1063
  ];
905
- this.originalClippingPlanes[4].origin = [
1064
+ this.originalClippingPlanes[PLANEINDEX.ZMIN].origin = [
906
1065
  ...this.sphereStates[SPHEREINDEX.ZMIN].point,
907
1066
  ];
908
- this.originalClippingPlanes[5].origin = [
1067
+ this.originalClippingPlanes[PLANEINDEX.ZMAX].origin = [
909
1068
  ...this.sphereStates[SPHEREINDEX.ZMAX].point,
910
1069
  ];
911
- mapper.removeAllClippingPlanes();
912
- for (let i = 0; i < 6; ++i) {
913
- const origin = this.originalClippingPlanes[i].origin;
914
- const normal = this.originalClippingPlanes[i].normal;
915
- const plane = vtkPlane.newInstance({
916
- origin,
917
- normal,
918
- });
919
- mapper.addClippingPlane(plane);
920
- }
1070
+ this._applyClippingPlanesToMapper(mapper);
921
1071
  }
922
1072
  _updateCornerSpheresFromFaces() {
923
- const xMin = this.sphereStates[SPHEREINDEX.XMIN].point[0];
924
- const xMax = this.sphereStates[SPHEREINDEX.XMAX].point[0];
925
- const yMin = this.sphereStates[SPHEREINDEX.YMIN].point[1];
926
- const yMax = this.sphereStates[SPHEREINDEX.YMAX].point[1];
927
- const zMin = this.sphereStates[SPHEREINDEX.ZMIN].point[2];
928
- const zMax = this.sphereStates[SPHEREINDEX.ZMAX].point[2];
1073
+ const { xDir, yDir, zDir } = this._getDirectionVectors();
1074
+ if (!xDir || !yDir || !zDir)
1075
+ return;
1076
+ const faceXMin = this.sphereStates[SPHEREINDEX.XMIN].point;
1077
+ const faceXMax = this.sphereStates[SPHEREINDEX.XMAX].point;
1078
+ const faceYMin = this.sphereStates[SPHEREINDEX.YMIN].point;
1079
+ const faceYMax = this.sphereStates[SPHEREINDEX.YMAX].point;
1080
+ const faceZMin = this.sphereStates[SPHEREINDEX.ZMIN].point;
1081
+ const faceZMax = this.sphereStates[SPHEREINDEX.ZMAX].point;
929
1082
  const corners = [
930
- { key: 'XMIN_YMIN_ZMIN', pos: [xMin, yMin, zMin] },
931
- { key: 'XMIN_YMIN_ZMAX', pos: [xMin, yMin, zMax] },
932
- { key: 'XMIN_YMAX_ZMIN', pos: [xMin, yMax, zMin] },
933
- { key: 'XMIN_YMAX_ZMAX', pos: [xMin, yMax, zMax] },
934
- { key: 'XMAX_YMIN_ZMIN', pos: [xMax, yMin, zMin] },
935
- { key: 'XMAX_YMIN_ZMAX', pos: [xMax, yMin, zMax] },
936
- { key: 'XMAX_YMAX_ZMIN', pos: [xMax, yMax, zMin] },
937
- { key: 'XMAX_YMAX_ZMAX', pos: [xMax, yMax, zMax] },
1083
+ {
1084
+ key: 'XMIN_YMIN_ZMIN',
1085
+ pos: this._calculateCornerFromFaces(faceXMin, faceYMin, faceZMin, xDir, yDir, zDir),
1086
+ },
1087
+ {
1088
+ key: 'XMIN_YMIN_ZMAX',
1089
+ pos: this._calculateCornerFromFaces(faceXMin, faceYMin, faceZMax, xDir, yDir, zDir),
1090
+ },
1091
+ {
1092
+ key: 'XMIN_YMAX_ZMIN',
1093
+ pos: this._calculateCornerFromFaces(faceXMin, faceYMax, faceZMin, xDir, yDir, zDir),
1094
+ },
1095
+ {
1096
+ key: 'XMIN_YMAX_ZMAX',
1097
+ pos: this._calculateCornerFromFaces(faceXMin, faceYMax, faceZMax, xDir, yDir, zDir),
1098
+ },
1099
+ {
1100
+ key: 'XMAX_YMIN_ZMIN',
1101
+ pos: this._calculateCornerFromFaces(faceXMax, faceYMin, faceZMin, xDir, yDir, zDir),
1102
+ },
1103
+ {
1104
+ key: 'XMAX_YMIN_ZMAX',
1105
+ pos: this._calculateCornerFromFaces(faceXMax, faceYMin, faceZMax, xDir, yDir, zDir),
1106
+ },
1107
+ {
1108
+ key: 'XMAX_YMAX_ZMIN',
1109
+ pos: this._calculateCornerFromFaces(faceXMax, faceYMax, faceZMin, xDir, yDir, zDir),
1110
+ },
1111
+ {
1112
+ key: 'XMAX_YMAX_ZMAX',
1113
+ pos: this._calculateCornerFromFaces(faceXMax, faceYMax, faceZMax, xDir, yDir, zDir),
1114
+ },
938
1115
  ];
939
1116
  for (const corner of corners) {
940
- const state = this.sphereStates.find((s) => s.uid === `corner_${corner.key}`);
941
- if (state) {
942
- state.point[0] = corner.pos[0];
943
- state.point[1] = corner.pos[1];
944
- state.point[2] = corner.pos[2];
945
- state.sphereSource.setCenter(...state.point);
946
- state.sphereSource.modified();
1117
+ const stateIndex = this.sphereStates.findIndex((s) => s.uid === `corner_${corner.key}`);
1118
+ if (stateIndex !== -1) {
1119
+ this._updateSpherePosition(stateIndex, corner.pos);
947
1120
  }
948
1121
  }
949
1122
  }
950
1123
  _updateFaceSpheresFromCorners() {
1124
+ if (!this.volumeDirectionVectors)
1125
+ return;
951
1126
  const corners = [
952
1127
  this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMIN].point,
953
1128
  this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMAX].point,
@@ -958,93 +1133,100 @@ class VolumeCroppingTool extends BaseTool {
958
1133
  this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMIN].point,
959
1134
  this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMAX].point,
960
1135
  ];
961
- const xs = corners.map((p) => p[0]);
962
- const ys = corners.map((p) => p[1]);
963
- const zs = corners.map((p) => p[2]);
964
- const xMin = Math.min(...xs), xMax = Math.max(...xs);
965
- const yMin = Math.min(...ys), yMax = Math.max(...ys);
966
- const zMin = Math.min(...zs), zMax = Math.max(...zs);
967
- this.sphereStates[SPHEREINDEX.XMIN].point = [
968
- xMin,
969
- (yMin + yMax) / 2,
970
- (zMin + zMax) / 2,
971
- ];
972
- this.sphereStates[SPHEREINDEX.XMAX].point = [
973
- xMax,
974
- (yMin + yMax) / 2,
975
- (zMin + zMax) / 2,
976
- ];
977
- this.sphereStates[SPHEREINDEX.YMIN].point = [
978
- (xMin + xMax) / 2,
979
- yMin,
980
- (zMin + zMax) / 2,
981
- ];
982
- this.sphereStates[SPHEREINDEX.YMAX].point = [
983
- (xMin + xMax) / 2,
984
- yMax,
985
- (zMin + zMax) / 2,
986
- ];
987
- this.sphereStates[SPHEREINDEX.ZMIN].point = [
988
- (xMin + xMax) / 2,
989
- (yMin + yMax) / 2,
990
- zMin,
991
- ];
992
- this.sphereStates[SPHEREINDEX.ZMAX].point = [
993
- (xMin + xMax) / 2,
994
- (yMin + yMax) / 2,
995
- zMax,
996
- ];
997
- [
998
- SPHEREINDEX.XMIN,
999
- SPHEREINDEX.XMAX,
1000
- SPHEREINDEX.YMIN,
1001
- SPHEREINDEX.YMAX,
1002
- SPHEREINDEX.ZMIN,
1003
- SPHEREINDEX.ZMAX,
1004
- ].forEach((idx) => {
1005
- const s = this.sphereStates[idx];
1006
- s.sphereSource.setCenter(...s.point);
1007
- s.sphereSource.modified();
1008
- });
1136
+ const faceXMin = this._averagePoints([
1137
+ corners[0],
1138
+ corners[1],
1139
+ corners[2],
1140
+ corners[3],
1141
+ ]);
1142
+ const faceXMax = this._averagePoints([
1143
+ corners[4],
1144
+ corners[5],
1145
+ corners[6],
1146
+ corners[7],
1147
+ ]);
1148
+ const faceYMin = this._averagePoints([
1149
+ corners[0],
1150
+ corners[1],
1151
+ corners[4],
1152
+ corners[5],
1153
+ ]);
1154
+ const faceYMax = this._averagePoints([
1155
+ corners[2],
1156
+ corners[3],
1157
+ corners[6],
1158
+ corners[7],
1159
+ ]);
1160
+ const faceZMin = this._averagePoints([
1161
+ corners[0],
1162
+ corners[2],
1163
+ corners[4],
1164
+ corners[6],
1165
+ ]);
1166
+ const faceZMax = this._averagePoints([
1167
+ corners[1],
1168
+ corners[3],
1169
+ corners[5],
1170
+ corners[7],
1171
+ ]);
1172
+ this._updateSpherePosition(SPHEREINDEX.XMIN, faceXMin);
1173
+ this._updateSpherePosition(SPHEREINDEX.XMAX, faceXMax);
1174
+ this._updateSpherePosition(SPHEREINDEX.YMIN, faceYMin);
1175
+ this._updateSpherePosition(SPHEREINDEX.YMAX, faceYMax);
1176
+ this._updateSpherePosition(SPHEREINDEX.ZMIN, faceZMin);
1177
+ this._updateSpherePosition(SPHEREINDEX.ZMAX, faceZMax);
1009
1178
  }
1010
1179
  _updateCornerSpheres() {
1011
- const xMin = this.sphereStates[SPHEREINDEX.XMIN].point[0];
1012
- const xMax = this.sphereStates[SPHEREINDEX.XMAX].point[0];
1013
- const yMin = this.sphereStates[SPHEREINDEX.YMIN].point[1];
1014
- const yMax = this.sphereStates[SPHEREINDEX.YMAX].point[1];
1015
- const zMin = this.sphereStates[SPHEREINDEX.ZMIN].point[2];
1016
- const zMax = this.sphereStates[SPHEREINDEX.ZMAX].point[2];
1180
+ const { xDir, yDir, zDir } = this._getDirectionVectors();
1181
+ if (!xDir || !yDir || !zDir)
1182
+ return;
1183
+ const faceXMin = this.sphereStates[SPHEREINDEX.XMIN].point;
1184
+ const faceXMax = this.sphereStates[SPHEREINDEX.XMAX].point;
1185
+ const faceYMin = this.sphereStates[SPHEREINDEX.YMIN].point;
1186
+ const faceYMax = this.sphereStates[SPHEREINDEX.YMAX].point;
1187
+ const faceZMin = this.sphereStates[SPHEREINDEX.ZMIN].point;
1188
+ const faceZMax = this.sphereStates[SPHEREINDEX.ZMAX].point;
1017
1189
  const corners = [
1018
- { key: 'XMIN_YMIN_ZMIN', pos: [xMin, yMin, zMin] },
1019
- { key: 'XMIN_YMIN_ZMAX', pos: [xMin, yMin, zMax] },
1020
- { key: 'XMIN_YMAX_ZMIN', pos: [xMin, yMax, zMin] },
1021
- { key: 'XMIN_YMAX_ZMAX', pos: [xMin, yMax, zMax] },
1022
- { key: 'XMAX_YMIN_ZMIN', pos: [xMax, yMin, zMin] },
1023
- { key: 'XMAX_YMIN_ZMAX', pos: [xMax, yMin, zMax] },
1024
- { key: 'XMAX_YMAX_ZMIN', pos: [xMax, yMax, zMin] },
1025
- { key: 'XMAX_YMAX_ZMAX', pos: [xMax, yMax, zMax] },
1190
+ {
1191
+ key: 'XMIN_YMIN_ZMIN',
1192
+ pos: this._calculateCornerFromProjection(faceXMin, faceYMin, faceZMin, xDir, yDir, zDir),
1193
+ },
1194
+ {
1195
+ key: 'XMIN_YMIN_ZMAX',
1196
+ pos: this._calculateCornerFromProjection(faceXMin, faceYMin, faceZMax, xDir, yDir, zDir),
1197
+ },
1198
+ {
1199
+ key: 'XMIN_YMAX_ZMIN',
1200
+ pos: this._calculateCornerFromProjection(faceXMin, faceYMax, faceZMin, xDir, yDir, zDir),
1201
+ },
1202
+ {
1203
+ key: 'XMIN_YMAX_ZMAX',
1204
+ pos: this._calculateCornerFromProjection(faceXMin, faceYMax, faceZMax, xDir, yDir, zDir),
1205
+ },
1206
+ {
1207
+ key: 'XMAX_YMIN_ZMIN',
1208
+ pos: this._calculateCornerFromProjection(faceXMax, faceYMin, faceZMin, xDir, yDir, zDir),
1209
+ },
1210
+ {
1211
+ key: 'XMAX_YMIN_ZMAX',
1212
+ pos: this._calculateCornerFromProjection(faceXMax, faceYMin, faceZMax, xDir, yDir, zDir),
1213
+ },
1214
+ {
1215
+ key: 'XMAX_YMAX_ZMIN',
1216
+ pos: this._calculateCornerFromProjection(faceXMax, faceYMax, faceZMin, xDir, yDir, zDir),
1217
+ },
1218
+ {
1219
+ key: 'XMAX_YMAX_ZMAX',
1220
+ pos: this._calculateCornerFromProjection(faceXMax, faceYMax, faceZMax, xDir, yDir, zDir),
1221
+ },
1026
1222
  ];
1027
1223
  for (const corner of corners) {
1028
- const state = this.sphereStates.find((s) => s.uid === `corner_${corner.key}`);
1029
- if (state) {
1030
- state.point[0] = corner.pos[0];
1031
- state.point[1] = corner.pos[1];
1032
- state.point[2] = corner.pos[2];
1033
- state.sphereSource.setCenter(...state.point);
1034
- state.sphereSource.modified();
1224
+ const stateIndex = this.sphereStates.findIndex((s) => s.uid === `corner_${corner.key}`);
1225
+ if (stateIndex !== -1) {
1226
+ this._updateSpherePosition(stateIndex, corner.pos);
1035
1227
  }
1036
1228
  }
1037
- Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
1038
- const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
1039
- const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
1040
- if (state1 && state2) {
1041
- const points = source.getPoints();
1042
- points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
1043
- points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
1044
- points.modified();
1045
- source.modified();
1046
- }
1047
- });
1229
+ this._updateEdgeLines();
1048
1230
  }
1049
1231
  _unsubscribeToViewportNewVolumeSet(viewportsInfo) {
1050
1232
  viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
@@ -1060,6 +1242,105 @@ class VolumeCroppingTool extends BaseTool {
1060
1242
  element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1061
1243
  });
1062
1244
  }
1245
+ _getDirectionVectors() {
1246
+ const hasPlanes = this.originalClippingPlanes?.length >= NUM_CLIPPING_PLANES;
1247
+ return {
1248
+ xDir: hasPlanes
1249
+ ? this.originalClippingPlanes[PLANEINDEX.XMIN].normal
1250
+ : this.volumeDirectionVectors?.xDir || [1, 0, 0],
1251
+ yDir: hasPlanes
1252
+ ? this.originalClippingPlanes[PLANEINDEX.YMIN].normal
1253
+ : this.volumeDirectionVectors?.yDir || [0, 1, 0],
1254
+ zDir: hasPlanes
1255
+ ? this.originalClippingPlanes[PLANEINDEX.ZMIN].normal
1256
+ : this.volumeDirectionVectors?.zDir || [0, 0, 1],
1257
+ };
1258
+ }
1259
+ _updateSpherePosition(sphereIndex, newPoint) {
1260
+ const state = this.sphereStates[sphereIndex];
1261
+ if (state) {
1262
+ state.point = newPoint;
1263
+ state.sphereSource.setCenter(...newPoint);
1264
+ state.sphereSource.modified();
1265
+ }
1266
+ }
1267
+ _updateFaceSpheresFromClippingPlanes() {
1268
+ const faceMappings = [
1269
+ { sphereIdx: SPHEREINDEX.XMIN, planeIdx: PLANEINDEX.XMIN },
1270
+ { sphereIdx: SPHEREINDEX.XMAX, planeIdx: PLANEINDEX.XMAX },
1271
+ { sphereIdx: SPHEREINDEX.YMIN, planeIdx: PLANEINDEX.YMIN },
1272
+ { sphereIdx: SPHEREINDEX.YMAX, planeIdx: PLANEINDEX.YMAX },
1273
+ { sphereIdx: SPHEREINDEX.ZMIN, planeIdx: PLANEINDEX.ZMIN },
1274
+ { sphereIdx: SPHEREINDEX.ZMAX, planeIdx: PLANEINDEX.ZMAX },
1275
+ ];
1276
+ faceMappings.forEach(({ sphereIdx, planeIdx }) => {
1277
+ const newPoint = [
1278
+ ...this.originalClippingPlanes[planeIdx].origin,
1279
+ ];
1280
+ this._updateSpherePosition(sphereIdx, newPoint);
1281
+ });
1282
+ }
1283
+ _averagePoints(points) {
1284
+ const sum = points.reduce((acc, p) => [acc[0] + p[0], acc[1] + p[1], acc[2] + p[2]], [0, 0, 0]);
1285
+ return [
1286
+ sum[0] / points.length,
1287
+ sum[1] / points.length,
1288
+ sum[2] / points.length,
1289
+ ];
1290
+ }
1291
+ _notifyClippingPlanesChanged(viewport) {
1292
+ const eventData = {
1293
+ originalClippingPlanes: this.originalClippingPlanes,
1294
+ seriesInstanceUID: this.seriesInstanceUID,
1295
+ };
1296
+ if (viewport) {
1297
+ eventData.viewportId = viewport.id;
1298
+ eventData.renderingEngineId = viewport.renderingEngineId;
1299
+ }
1300
+ triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, eventData);
1301
+ }
1302
+ _calculateCornerFromFaces(faceX, faceY, faceZ, xDir, yDir, zDir) {
1303
+ const deltaXY = [
1304
+ faceY[0] - faceX[0],
1305
+ faceY[1] - faceX[1],
1306
+ faceY[2] - faceX[2],
1307
+ ];
1308
+ const distY = deltaXY[0] * yDir[0] + deltaXY[1] * yDir[1] + deltaXY[2] * yDir[2];
1309
+ const deltaXZ = [
1310
+ faceZ[0] - faceX[0],
1311
+ faceZ[1] - faceX[1],
1312
+ faceZ[2] - faceX[2],
1313
+ ];
1314
+ const distZ = deltaXZ[0] * zDir[0] + deltaXZ[1] * zDir[1] + deltaXZ[2] * zDir[2];
1315
+ return [
1316
+ faceX[0] + distY * yDir[0] + distZ * zDir[0],
1317
+ faceX[1] + distY * yDir[1] + distZ * zDir[1],
1318
+ faceX[2] + distY * yDir[2] + distZ * zDir[2],
1319
+ ];
1320
+ }
1321
+ _calculateCornerFromProjection(faceX, faceY, faceZ, xDir, yDir, zDir) {
1322
+ const dX = faceX[0] * xDir[0] + faceX[1] * xDir[1] + faceX[2] * xDir[2];
1323
+ const dY = faceY[0] * yDir[0] + faceY[1] * yDir[1] + faceY[2] * yDir[2];
1324
+ const dZ = faceZ[0] * zDir[0] + faceZ[1] * zDir[1] + faceZ[2] * zDir[2];
1325
+ return [
1326
+ dX * xDir[0] + dY * yDir[0] + dZ * zDir[0],
1327
+ dX * xDir[1] + dY * yDir[1] + dZ * zDir[1],
1328
+ dX * xDir[2] + dY * yDir[2] + dZ * zDir[2],
1329
+ ];
1330
+ }
1331
+ _updateEdgeLines() {
1332
+ Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
1333
+ const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
1334
+ const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
1335
+ if (state1 && state2) {
1336
+ const points = source.getPoints();
1337
+ points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
1338
+ points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
1339
+ points.modified();
1340
+ source.modified();
1341
+ }
1342
+ });
1343
+ }
1063
1344
  }
1064
1345
  VolumeCroppingTool.toolName = 'VolumeCropping';
1065
1346
  export default VolumeCroppingTool;