@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.
- package/dist/esm/config.d.ts +4 -0
- package/dist/esm/drawingSvg/drawPath.d.ts +3 -0
- package/dist/esm/drawingSvg/drawPath.js +4 -1
- package/dist/esm/eventListeners/keyboard/keyDownListener.js +2 -2
- package/dist/esm/eventListeners/mouse/getMouseEventPoints.d.ts +1 -1
- package/dist/esm/eventListeners/mouse/getMouseEventPoints.js +19 -1
- package/dist/esm/eventListeners/mouse/mouseDoubleClickListener.js +8 -1
- package/dist/esm/eventListeners/mouse/mouseDownListener.js +37 -5
- package/dist/esm/eventListeners/mouse/mouseMoveListener.js +3 -0
- package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +60 -92
- package/dist/esm/eventListeners/segmentation/labelmap/onLabelmapSegmentationDataModified.js +49 -21
- package/dist/esm/eventListeners/segmentation/labelmap/performStackLabelmapUpdate.js +7 -13
- package/dist/esm/eventListeners/segmentation/labelmap/performVolumeLabelmapUpdate.js +44 -18
- package/dist/esm/eventListeners/touch/getTouchEventPoints.js +27 -4
- package/dist/esm/eventListeners/touch/touchStartListener.js +27 -9
- package/dist/esm/eventListeners/wheel/wheelListener.js +5 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/init.js +2 -0
- package/dist/esm/stateManagement/annotation/FrameOfReferenceSpecificAnnotationManager.js +10 -4
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.js +23 -20
- package/dist/esm/stateManagement/segmentation/SegmentationRepresentationDisplayRegistry.d.ts +12 -0
- package/dist/esm/stateManagement/segmentation/SegmentationRepresentationDisplayRegistry.js +7 -0
- package/dist/esm/stateManagement/segmentation/SegmentationStateManager.d.ts +1 -10
- package/dist/esm/stateManagement/segmentation/SegmentationStateManager.js +28 -149
- package/dist/esm/stateManagement/segmentation/addColorLUT.js +7 -1
- package/dist/esm/stateManagement/segmentation/getCurrentLabelmapImageIdForViewport.js +16 -1
- package/dist/esm/stateManagement/segmentation/helpers/clearSegmentValue.js +9 -7
- package/dist/esm/stateManagement/segmentation/helpers/getSegmentationActor.d.ts +1 -1
- package/dist/esm/stateManagement/segmentation/helpers/getSegmentationActor.js +3 -2
- package/dist/esm/stateManagement/segmentation/helpers/getViewportLabelmapRenderMode.d.ts +5 -0
- package/dist/esm/stateManagement/segmentation/helpers/getViewportLabelmapRenderMode.js +58 -0
- package/dist/esm/stateManagement/segmentation/helpers/labelmapImageMapperSupport.d.ts +52 -0
- package/dist/esm/stateManagement/segmentation/helpers/labelmapImageMapperSupport.js +246 -0
- package/dist/esm/stateManagement/segmentation/helpers/labelmapSegmentationState.d.ts +1 -0
- package/dist/esm/stateManagement/segmentation/helpers/labelmapSegmentationState.js +1 -0
- package/dist/esm/stateManagement/segmentation/helpers/normalizeSegmentationInput.js +12 -1
- package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.js +3 -3
- package/dist/esm/stateManagement/segmentation/labelmapModel/index.d.ts +9 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/index.js +7 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.d.ts +54 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.js +224 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageIdMapping.d.ts +6 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageIdMapping.js +39 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.d.ts +23 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.js +269 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLayerStore.d.ts +15 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLayerStore.js +160 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLegacyAdapter.d.ts +4 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapLegacyAdapter.js +42 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapSegmentBindings.d.ts +11 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapSegmentBindings.js +73 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/normalizeLabelmapSegmentationData.d.ts +17 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/normalizeLabelmapSegmentationData.js +75 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/privateLabelmap.d.ts +5 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/privateLabelmap.js +106 -0
- package/dist/esm/stateManagement/segmentation/models/SegmentModel.d.ts +11 -0
- package/dist/esm/stateManagement/segmentation/models/SegmentModel.js +19 -0
- package/dist/esm/stateManagement/segmentation/models/SegmentationModel.d.ts +12 -0
- package/dist/esm/stateManagement/segmentation/models/SegmentationModel.js +23 -0
- package/dist/esm/stateManagement/segmentation/removeSegmentationRepresentations.js +6 -10
- package/dist/esm/stateManagement/segmentation/segmentIndex.js +24 -0
- package/dist/esm/stateManagement/segmentation/segmentationEventManager.js +2 -9
- package/dist/esm/stateManagement/segmentation/segmentationState.d.ts +2 -1
- package/dist/esm/stateManagement/segmentation/segmentationState.js +4 -1
- package/dist/esm/store/SynchronizerManager/Synchronizer.d.ts +3 -1
- package/dist/esm/store/state.js +2 -1
- package/dist/esm/synchronizers/callbacks/imageSliceSyncCallback.js +12 -3
- package/dist/esm/synchronizers/callbacks/presentationViewSyncCallback.js +5 -2
- package/dist/esm/synchronizers/callbacks/zoomPanSyncCallback.js +51 -3
- package/dist/esm/synchronizers/synchronizers/createPresentationViewSynchronizer.d.ts +1 -1
- package/dist/esm/tools/AdvancedMagnifyTool.js +4 -1
- package/dist/esm/tools/CrosshairsTool.d.ts +4 -0
- package/dist/esm/tools/CrosshairsTool.js +95 -41
- package/dist/esm/tools/MagnifyTool.js +3 -1
- package/dist/esm/tools/OrientationControllerTool.d.ts +45 -0
- package/dist/esm/tools/OrientationControllerTool.js +454 -0
- package/dist/esm/tools/OrientationMarkerTool.js +4 -4
- package/dist/esm/tools/PanTool.js +26 -3
- package/dist/esm/tools/PlanarRotateTool.js +19 -4
- package/dist/esm/tools/ReferenceCursors.js +7 -1
- package/dist/esm/tools/SculptorTool/CircleSculptCursor.js +1 -1
- package/dist/esm/tools/TrackballRotateTool.js +3 -2
- package/dist/esm/tools/VolumeCroppingControlTool.d.ts +10 -35
- package/dist/esm/tools/VolumeCroppingControlTool.js +179 -699
- package/dist/esm/tools/VolumeCroppingTool.d.ts +34 -32
- package/dist/esm/tools/VolumeCroppingTool.js +813 -532
- package/dist/esm/tools/WindowLevelTool.d.ts +2 -1
- package/dist/esm/tools/WindowLevelTool.js +48 -4
- package/dist/esm/tools/ZoomTool.d.ts +8 -0
- package/dist/esm/tools/ZoomTool.js +92 -11
- package/dist/esm/tools/annotation/AngleTool.js +38 -32
- package/dist/esm/tools/annotation/ArrowAnnotateTool.js +30 -28
- package/dist/esm/tools/annotation/BidirectionalTool.js +51 -49
- package/dist/esm/tools/annotation/CircleROITool.d.ts +1 -0
- package/dist/esm/tools/annotation/CircleROITool.js +89 -51
- package/dist/esm/tools/annotation/CobbAngleTool.js +1 -1
- package/dist/esm/tools/annotation/DragProbeTool.js +1 -1
- package/dist/esm/tools/annotation/ETDRSGridTool.js +1 -1
- package/dist/esm/tools/annotation/EllipticalROITool.js +46 -39
- package/dist/esm/tools/annotation/HeightTool.js +1 -1
- package/dist/esm/tools/annotation/KeyImageTool.js +11 -11
- package/dist/esm/tools/annotation/LabelTool.js +37 -35
- package/dist/esm/tools/annotation/LengthTool.js +35 -33
- package/dist/esm/tools/annotation/LivewireContourSegmentationTool.js +6 -4
- package/dist/esm/tools/annotation/LivewireContourTool.js +4 -8
- package/dist/esm/tools/annotation/PlanarFreehandContourSegmentationTool.js +6 -4
- package/dist/esm/tools/annotation/PlanarFreehandROITool.d.ts +2 -1
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js +11 -8
- package/dist/esm/tools/annotation/ProbeTool.js +66 -56
- package/dist/esm/tools/annotation/RectangleROITool.js +48 -37
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +1 -1
- package/dist/esm/tools/annotation/RegionSegmentTool.js +1 -1
- package/dist/esm/tools/annotation/SplineContourSegmentationTool.js +1 -1
- package/dist/esm/tools/annotation/SplineROITool.js +60 -56
- package/dist/esm/tools/annotation/UltrasoundDirectionalTool.js +1 -1
- package/dist/esm/tools/annotation/UltrasoundPleuraBLineTool/UltrasoundPleuraBLineTool.js +57 -55
- package/dist/esm/tools/annotation/VideoRedactionTool.js +1 -1
- package/dist/esm/tools/base/AnnotationDisplayTool.js +9 -6
- package/dist/esm/tools/base/AnnotationTool.js +2 -1
- package/dist/esm/tools/base/BaseTool.js +16 -10
- package/dist/esm/tools/base/ContourSegmentationBaseTool.js +1 -1
- package/dist/esm/tools/base/GrowCutBaseTool.js +2 -2
- package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.d.ts +2 -4
- package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.js +15 -85
- package/dist/esm/tools/displayTools/Labelmap/labelmapActorStyle.d.ts +5 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapActorStyle.js +191 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.d.ts +4 -3
- package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.js +48 -209
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/createLabelmapRenderPlan.d.ts +3 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/createLabelmapRenderPlan.js +51 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/index.d.ts +4 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/index.js +3 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/legacyVolumePlan.d.ts +14 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/legacyVolumePlan.js +143 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/planarGenericVolumeLabelmap.d.ts +40 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/planarGenericVolumeLabelmap.js +79 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/removeLabelmapRepresentationFromViewport.d.ts +3 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/removeLabelmapRepresentationFromViewport.js +18 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/resolveLabelmapRenderPlan.d.ts +9 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/resolveLabelmapRenderPlan.js +56 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/stackImagePlan.d.ts +11 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/stackImagePlan.js +35 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/types.d.ts +48 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/types.js +0 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/volumeSliceImageMapperPlan.d.ts +13 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan/volumeSliceImageMapperPlan.js +34 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan.d.ts +2 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRenderPlan.js +1 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRepresentationUID.d.ts +8 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapRepresentationUID.js +18 -0
- package/dist/esm/tools/displayTools/Labelmap/removeLabelmapFromElement.js +2 -2
- package/dist/esm/tools/displayTools/Labelmap/removeLabelmapRepresentationData.d.ts +3 -0
- package/dist/esm/tools/displayTools/Labelmap/removeLabelmapRepresentationData.js +16 -0
- package/dist/esm/tools/displayTools/Labelmap/syncStackLabelmapActors.d.ts +2 -0
- package/dist/esm/tools/displayTools/Labelmap/syncStackLabelmapActors.js +135 -0
- package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapImageMapper.d.ts +16 -0
- package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapImageMapper.js +267 -0
- package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapSliceData.d.ts +27 -0
- package/dist/esm/tools/displayTools/Labelmap/volumeLabelmapSliceData.js +185 -0
- package/dist/esm/tools/displayTools/registerBuiltInSegmentationRepresentationDisplays.d.ts +1 -0
- package/dist/esm/tools/displayTools/registerBuiltInSegmentationRepresentationDisplays.js +16 -0
- package/dist/esm/tools/index.d.ts +2 -1
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/segmentation/BrushTool.d.ts +9 -2
- package/dist/esm/tools/segmentation/BrushTool.js +123 -26
- package/dist/esm/tools/segmentation/CircleScissorsTool.js +19 -36
- package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +2 -3
- package/dist/esm/tools/segmentation/LabelmapBaseTool.js +77 -46
- package/dist/esm/tools/segmentation/LabelmapEditWithContour.js +3 -3
- package/dist/esm/tools/segmentation/PaintFillTool.js +11 -4
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +2 -0
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +16 -8
- package/dist/esm/tools/segmentation/RectangleScissorsTool.js +13 -6
- package/dist/esm/tools/segmentation/SegmentBidirectionalTool.js +63 -61
- package/dist/esm/tools/segmentation/SegmentSelectTool.js +4 -4
- package/dist/esm/tools/segmentation/SphereScissorsTool.js +11 -31
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.d.ts +7 -0
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +47 -24
- package/dist/esm/tools/segmentation/strategies/compositions/circularCursor.js +49 -21
- package/dist/esm/tools/segmentation/strategies/compositions/determineSegmentIndex.js +2 -2
- package/dist/esm/tools/segmentation/strategies/compositions/dynamicThreshold.js +5 -1
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.js +2 -2
- package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -2
- package/dist/esm/tools/segmentation/strategies/compositions/setValue.js +14 -6
- package/dist/esm/tools/segmentation/strategies/fillCircle.d.ts +3 -1
- package/dist/esm/tools/segmentation/strategies/fillCircle.js +38 -31
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +11 -3
- package/dist/esm/tools/segmentation/strategies/utils/crossLayerErase.d.ts +4 -0
- package/dist/esm/tools/segmentation/strategies/utils/crossLayerErase.js +23 -0
- package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js +1 -1
- package/dist/esm/tools/segmentation/strategies/utils/handleUseSegmentCenterIndex.js +12 -11
- package/dist/esm/tools/segmentation/strategies/utils/labelmapOverlap.d.ts +4 -0
- package/dist/esm/tools/segmentation/strategies/utils/labelmapOverlap.js +41 -0
- package/dist/esm/tools/segmentation/strategies/utils/overwritePolicy.d.ts +3 -0
- package/dist/esm/tools/segmentation/strategies/utils/overwritePolicy.js +31 -0
- package/dist/esm/tools/segmentation/strategies/utils/segmentSeparation.d.ts +3 -0
- package/dist/esm/tools/segmentation/strategies/utils/segmentSeparation.js +38 -0
- package/dist/esm/tools/segmentation/utils/LazyBrushEditController.d.ts +19 -0
- package/dist/esm/tools/segmentation/utils/LazyBrushEditController.js +55 -0
- package/dist/esm/tools/segmentation/utils/lazyBrushPreview.d.ts +3 -0
- package/dist/esm/tools/segmentation/utils/lazyBrushPreview.js +34 -0
- package/dist/esm/tools/segmentation/utils/shouldUseLazyLabelmapEditing.d.ts +3 -0
- package/dist/esm/tools/segmentation/utils/shouldUseLazyLabelmapEditing.js +42 -0
- package/dist/esm/types/ISynchronizerEventHandler.d.ts +2 -1
- package/dist/esm/types/LabelmapToolOperationData.d.ts +5 -0
- package/dist/esm/types/LabelmapTypes.d.ts +29 -6
- package/dist/esm/types/SegmentationStateTypes.d.ts +6 -0
- package/dist/esm/utilities/boundingBox/index.d.ts +2 -1
- package/dist/esm/utilities/boundingBox/index.js +2 -1
- package/dist/esm/utilities/boundingBox/snapIndexBounds.d.ts +3 -0
- package/dist/esm/utilities/boundingBox/snapIndexBounds.js +9 -0
- package/dist/esm/utilities/calibrateImageSpacing.js +17 -2
- package/dist/esm/utilities/contours/AnnotationToPointData.js +1 -1
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +7 -0
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +34 -0
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +6 -0
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +7 -0
- package/dist/esm/utilities/draw3D/index.d.ts +2 -0
- package/dist/esm/utilities/draw3D/index.js +2 -0
- package/dist/esm/utilities/drawing/getTextBoxCoordsCanvas.js +22 -14
- package/dist/esm/utilities/getCenterAndRadiusInCanvas.d.ts +6 -0
- package/dist/esm/utilities/getCenterAndRadiusInCanvas.js +26 -0
- package/dist/esm/utilities/getEllipseWorldCoordinates.d.ts +2 -0
- package/dist/esm/utilities/getEllipseWorldCoordinates.js +26 -0
- package/dist/esm/utilities/getSphereBoundsInfo.js +5 -1
- package/dist/esm/utilities/getViewportICamera.d.ts +4 -0
- package/dist/esm/utilities/getViewportICamera.js +23 -0
- package/dist/esm/utilities/getViewportsForAnnotation.js +5 -1
- package/dist/esm/utilities/index.d.ts +2 -1
- package/dist/esm/utilities/index.js +2 -1
- package/dist/esm/utilities/interactionDragCoordinator.d.ts +5 -0
- package/dist/esm/utilities/interactionDragCoordinator.js +16 -0
- package/dist/esm/utilities/math/basic/BasicStatsCalculator.js +9 -7
- package/dist/esm/utilities/pointInSurroundingSphereCallback.js +8 -1
- package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js +121 -118
- package/dist/esm/utilities/segmentation/SegmentStatsCalculator.js +5 -4
- package/dist/esm/utilities/segmentation/VolumetricCalculator.js +1 -1
- package/dist/esm/utilities/segmentation/createLabelmapVolumeForViewport.js +1 -1
- package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentation.js +1 -1
- package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentationVolume.js +11 -2
- package/dist/esm/utilities/segmentation/getSegmentIndexAtLabelmapBorder.js +36 -17
- package/dist/esm/utilities/segmentation/getSegmentIndexAtWorldPoint.js +42 -25
- package/dist/esm/utilities/segmentation/getUniqueSegmentIndices.js +3 -30
- package/dist/esm/utilities/segmentation/index.d.ts +2 -1
- package/dist/esm/utilities/segmentation/index.js +2 -1
- package/dist/esm/utilities/segmentation/utilsForWorker.js +6 -2
- package/dist/esm/utilities/segmentation/validateLabelmap.js +1 -1
- package/dist/esm/utilities/stackPrefetch/stackPrefetch.js +0 -1
- package/dist/esm/utilities/touch/index.js +3 -2
- package/dist/esm/utilities/viewportCapabilities.d.ts +16 -0
- package/dist/esm/utilities/viewportCapabilities.js +18 -0
- package/dist/esm/utilities/viewportFilters/filterViewportsWithParallelNormals.d.ts +1 -1
- package/dist/esm/utilities/viewportFilters/filterViewportsWithParallelNormals.js +12 -4
- package/dist/esm/utilities/viewportFilters/filterViewportsWithSameOrientation.d.ts +1 -1
- package/dist/esm/utilities/viewportFilters/filterViewportsWithSameOrientation.js +11 -4
- package/dist/esm/utilities/viewportFilters/getViewportIdsWithToolToRender.js +1 -1
- package/dist/esm/utilities/viewportPresentation.d.ts +3 -0
- package/dist/esm/utilities/viewportPresentation.js +26 -0
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +6 -0
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +37 -0
- package/dist/esm/utilities/volumeCropping/constants.d.ts +31 -0
- package/dist/esm/utilities/volumeCropping/constants.js +31 -0
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +2 -0
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +6 -0
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +9 -0
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +9 -0
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +5 -0
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +50 -0
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +1 -0
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +13 -0
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +2 -0
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +19 -0
- package/dist/esm/utilities/volumeCropping/index.d.ts +9 -0
- package/dist/esm/utilities/volumeCropping/index.js +9 -0
- package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +8 -0
- package/dist/esm/utilities/volumeCropping/parseCornerKey.js +11 -0
- package/dist/esm/utilities/volumeCropping/types.d.ts +5 -0
- package/dist/esm/utilities/volumeCropping/types.js +0 -0
- package/dist/esm/utilities/vtkjs/AnnotatedRhombicuboctahedronActor/index.d.ts +31 -0
- package/dist/esm/utilities/vtkjs/AnnotatedRhombicuboctahedronActor/index.js +391 -0
- package/dist/esm/utilities/vtkjs/OrientationControllerWidget/index.d.ts +69 -0
- package/dist/esm/utilities/vtkjs/OrientationControllerWidget/index.js +804 -0
- package/dist/esm/utilities/vtkjs/RhombicuboctahedronSource/index.d.ts +7 -0
- package/dist/esm/utilities/vtkjs/RhombicuboctahedronSource/index.js +144 -0
- package/dist/esm/utilities/vtkjs/index.d.ts +3 -0
- package/dist/esm/utilities/vtkjs/index.js +3 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- 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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
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
|
-
|
|
91
|
-
const 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
|
|
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
|
-
|
|
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.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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.
|
|
263
|
+
this._notifyClippingPlanesChanged();
|
|
198
264
|
return true;
|
|
199
265
|
};
|
|
200
266
|
this._onControlToolChange = (evt) => {
|
|
201
267
|
const viewport = this._getViewport();
|
|
202
|
-
if (
|
|
203
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
279
|
-
return
|
|
290
|
+
const toolGroup = getToolGroup(this.toolGroupId);
|
|
291
|
+
return toolGroup?.viewportsInfo || [];
|
|
280
292
|
};
|
|
281
293
|
this._initialize3DViewports = (viewportsInfo) => {
|
|
282
|
-
if (!viewportsInfo
|
|
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
|
-
|
|
288
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
315
|
-
origin:
|
|
316
|
-
normal: [-
|
|
336
|
+
const planeXMax = vtkPlane.newInstance({
|
|
337
|
+
origin: faceXMax,
|
|
338
|
+
normal: [-xDir[0], -xDir[1], -xDir[2]],
|
|
317
339
|
});
|
|
318
|
-
const
|
|
319
|
-
origin:
|
|
320
|
-
normal:
|
|
340
|
+
const planeYMin = vtkPlane.newInstance({
|
|
341
|
+
origin: faceYMin,
|
|
342
|
+
normal: yDir,
|
|
321
343
|
});
|
|
322
|
-
const
|
|
323
|
-
origin:
|
|
324
|
-
normal: [0, -1,
|
|
344
|
+
const planeYMax = vtkPlane.newInstance({
|
|
345
|
+
origin: faceYMax,
|
|
346
|
+
normal: [-yDir[0], -yDir[1], -yDir[2]],
|
|
325
347
|
});
|
|
326
|
-
const
|
|
327
|
-
origin:
|
|
328
|
-
normal:
|
|
348
|
+
const planeZMin = vtkPlane.newInstance({
|
|
349
|
+
origin: faceZMin,
|
|
350
|
+
normal: zDir,
|
|
329
351
|
});
|
|
330
|
-
const
|
|
331
|
-
origin:
|
|
332
|
-
normal: [0,
|
|
352
|
+
const planeZMax = vtkPlane.newInstance({
|
|
353
|
+
origin: faceZMax,
|
|
354
|
+
normal: [-zDir[0], -zDir[1], -zDir[2]],
|
|
333
355
|
});
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
this._addSphere(viewport,
|
|
357
|
-
this._addSphere(viewport,
|
|
358
|
-
this._addSphere(viewport,
|
|
359
|
-
this._addSphere(viewport,
|
|
360
|
-
this._addSphere(viewport,
|
|
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]
|
|
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 } =
|
|
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.
|
|
408
|
-
mapper.addClippingPlane(
|
|
409
|
-
mapper.addClippingPlane(
|
|
410
|
-
mapper.addClippingPlane(
|
|
411
|
-
mapper.addClippingPlane(
|
|
412
|
-
mapper.addClippingPlane(
|
|
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
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
this.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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 {
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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.
|
|
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.
|
|
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
|
|
742
|
-
|
|
743
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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 =
|
|
893
|
-
|
|
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[
|
|
1055
|
+
this.originalClippingPlanes[PLANEINDEX.XMAX].origin = [
|
|
897
1056
|
...this.sphereStates[SPHEREINDEX.XMAX].point,
|
|
898
1057
|
];
|
|
899
|
-
this.originalClippingPlanes[
|
|
1058
|
+
this.originalClippingPlanes[PLANEINDEX.YMIN].origin = [
|
|
900
1059
|
...this.sphereStates[SPHEREINDEX.YMIN].point,
|
|
901
1060
|
];
|
|
902
|
-
this.originalClippingPlanes[
|
|
1061
|
+
this.originalClippingPlanes[PLANEINDEX.YMAX].origin = [
|
|
903
1062
|
...this.sphereStates[SPHEREINDEX.YMAX].point,
|
|
904
1063
|
];
|
|
905
|
-
this.originalClippingPlanes[
|
|
1064
|
+
this.originalClippingPlanes[PLANEINDEX.ZMIN].origin = [
|
|
906
1065
|
...this.sphereStates[SPHEREINDEX.ZMIN].point,
|
|
907
1066
|
];
|
|
908
|
-
this.originalClippingPlanes[
|
|
1067
|
+
this.originalClippingPlanes[PLANEINDEX.ZMAX].origin = [
|
|
909
1068
|
...this.sphereStates[SPHEREINDEX.ZMAX].point,
|
|
910
1069
|
];
|
|
911
|
-
|
|
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
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
const
|
|
928
|
-
const
|
|
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
|
-
{
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
{
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
|
941
|
-
if (
|
|
942
|
-
|
|
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
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
];
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
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
|
-
{
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
{
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
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
|
-
|
|
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;
|