@cornerstonejs/tools 0.56.1 → 0.56.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/tools/CrosshairsTool.d.ts +1 -0
- package/dist/cjs/tools/CrosshairsTool.js +4 -1
- package/dist/cjs/tools/CrosshairsTool.js.map +1 -1
- package/dist/esm/tools/CrosshairsTool.d.ts +1 -0
- package/dist/esm/tools/CrosshairsTool.js +4 -1
- package/dist/esm/tools/CrosshairsTool.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +5 -4
- package/src/constants/COLOR_LUT.ts +262 -0
- package/src/constants/index.ts +3 -0
- package/src/cursors/ImageMouseCursor.ts +39 -0
- package/src/cursors/MouseCursor.ts +114 -0
- package/src/cursors/SVGCursorDescriptor.ts +462 -0
- package/src/cursors/SVGMouseCursor.ts +145 -0
- package/src/cursors/elementCursor.ts +69 -0
- package/src/cursors/index.ts +24 -0
- package/src/cursors/setCursorForElement.ts +33 -0
- package/src/drawingSvg/_getHash.ts +9 -0
- package/src/drawingSvg/_setAttributesIfNecessary.ts +13 -0
- package/src/drawingSvg/_setNewAttributesIfValid.ts +10 -0
- package/src/drawingSvg/clearByToolType.ts +26 -0
- package/src/drawingSvg/draw.ts +16 -0
- package/src/drawingSvg/drawArrow.ts +82 -0
- package/src/drawingSvg/drawCircle.ts +62 -0
- package/src/drawingSvg/drawEllipse.ts +71 -0
- package/src/drawingSvg/drawHandles.ts +87 -0
- package/src/drawingSvg/drawLine.ts +70 -0
- package/src/drawingSvg/drawLink.ts +76 -0
- package/src/drawingSvg/drawLinkedTextBox.ts +64 -0
- package/src/drawingSvg/drawPolyline.ts +80 -0
- package/src/drawingSvg/drawRect.ts +70 -0
- package/src/drawingSvg/drawTextBox.ts +213 -0
- package/src/drawingSvg/getSvgDrawingHelper.ts +98 -0
- package/src/drawingSvg/index.ts +23 -0
- package/src/enums/AnnotationStyleStates.ts +22 -0
- package/src/enums/Events.ts +242 -0
- package/src/enums/SegmentationRepresentations.ts +12 -0
- package/src/enums/ToolBindings.ts +37 -0
- package/src/enums/ToolModes.ts +31 -0
- package/src/enums/Touch.ts +8 -0
- package/src/enums/index.js +16 -0
- package/src/eventDispatchers/annotationModifiedEventDispatcher.ts +41 -0
- package/src/eventDispatchers/cameraModifiedEventDispatcher.ts +41 -0
- package/src/eventDispatchers/imageRenderedEventDispatcher.ts +37 -0
- package/src/eventDispatchers/imageSpacingCalibratedEventDispatcher.ts +50 -0
- package/src/eventDispatchers/index.js +15 -0
- package/src/eventDispatchers/keyboardEventHandlers/index.js +4 -0
- package/src/eventDispatchers/keyboardEventHandlers/keyDown.ts +29 -0
- package/src/eventDispatchers/keyboardEventHandlers/keyUp.ts +33 -0
- package/src/eventDispatchers/keyboardToolEventDispatcher.ts +28 -0
- package/src/eventDispatchers/mouseEventHandlers/index.js +19 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseClick.ts +13 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseDoubleClick.ts +13 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseDown.ts +196 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseDownActivate.ts +35 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseDrag.ts +25 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseMove.ts +70 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseUp.ts +9 -0
- package/src/eventDispatchers/mouseEventHandlers/mouseWheel.ts +13 -0
- package/src/eventDispatchers/mouseToolEventDispatcher.ts +64 -0
- package/src/eventDispatchers/shared/customCallbackHandler.ts +73 -0
- package/src/eventDispatchers/shared/getActiveToolForKeyboardEvent.ts +58 -0
- package/src/eventDispatchers/shared/getActiveToolForMouseEvent.ts +61 -0
- package/src/eventDispatchers/shared/getActiveToolForTouchEvent.ts +64 -0
- package/src/eventDispatchers/shared/getMouseModifier.ts +30 -0
- package/src/eventDispatchers/shared/getToolsWithModesForMouseEvent.ts +56 -0
- package/src/eventDispatchers/shared/getToolsWithModesForTouchEvent.ts +54 -0
- package/src/eventDispatchers/touchEventHandlers/index.js +15 -0
- package/src/eventDispatchers/touchEventHandlers/touchDrag.ts +23 -0
- package/src/eventDispatchers/touchEventHandlers/touchEnd.ts +9 -0
- package/src/eventDispatchers/touchEventHandlers/touchPress.ts +13 -0
- package/src/eventDispatchers/touchEventHandlers/touchStart.ts +174 -0
- package/src/eventDispatchers/touchEventHandlers/touchStartActivate.ts +36 -0
- package/src/eventDispatchers/touchEventHandlers/touchTap.ts +9 -0
- package/src/eventDispatchers/touchToolEventDispatcher.ts +51 -0
- package/src/eventListeners/annotations/annotationModifiedListener.ts +22 -0
- package/src/eventListeners/annotations/annotationSelectionListener.ts +29 -0
- package/src/eventListeners/annotations/index.ts +4 -0
- package/src/eventListeners/index.ts +28 -0
- package/src/eventListeners/keyboard/index.ts +16 -0
- package/src/eventListeners/keyboard/keyDownListener.ts +99 -0
- package/src/eventListeners/mouse/getMouseEventPoints.ts +66 -0
- package/src/eventListeners/mouse/index.ts +55 -0
- package/src/eventListeners/mouse/mouseDoubleClickListener.ts +55 -0
- package/src/eventListeners/mouse/mouseDownListener.ts +519 -0
- package/src/eventListeners/mouse/mouseMoveListener.ts +33 -0
- package/src/eventListeners/segmentation/index.ts +11 -0
- package/src/eventListeners/segmentation/segmentationDataModifiedEventListener.ts +61 -0
- package/src/eventListeners/segmentation/segmentationModifiedEventListener.ts +32 -0
- package/src/eventListeners/segmentation/segmentationRepresentationModifiedEventListener.ts +15 -0
- package/src/eventListeners/segmentation/segmentationRepresentationRemovedEventListener.ts +16 -0
- package/src/eventListeners/touch/getTouchEventPoints.ts +75 -0
- package/src/eventListeners/touch/index.ts +37 -0
- package/src/eventListeners/touch/preventGhostClick.js +72 -0
- package/src/eventListeners/touch/touchStartListener.ts +499 -0
- package/src/eventListeners/wheel/index.ts +27 -0
- package/src/eventListeners/wheel/normalizeWheel.ts +69 -0
- package/src/eventListeners/wheel/wheelListener.ts +51 -0
- package/src/index.ts +133 -0
- package/src/init.ts +187 -0
- package/src/stateManagement/annotation/FrameOfReferenceSpecificAnnotationManager.ts +399 -0
- package/src/stateManagement/annotation/annotationLocking.ts +178 -0
- package/src/stateManagement/annotation/annotationSelection.ts +163 -0
- package/src/stateManagement/annotation/annotationState.ts +180 -0
- package/src/stateManagement/annotation/annotationVisibility.ts +156 -0
- package/src/stateManagement/annotation/config/ToolStyle.ts +265 -0
- package/src/stateManagement/annotation/config/getFont.ts +36 -0
- package/src/stateManagement/annotation/config/getState.ts +26 -0
- package/src/stateManagement/annotation/config/helpers.ts +55 -0
- package/src/stateManagement/annotation/config/index.ts +5 -0
- package/src/stateManagement/annotation/helpers/state.ts +83 -0
- package/src/stateManagement/annotation/index.ts +15 -0
- package/src/stateManagement/index.js +40 -0
- package/src/stateManagement/segmentation/SegmentationStateManager.ts +491 -0
- package/src/stateManagement/segmentation/activeSegmentation.ts +60 -0
- package/src/stateManagement/segmentation/addSegmentationRepresentations.ts +77 -0
- package/src/stateManagement/segmentation/addSegmentations.ts +27 -0
- package/src/stateManagement/segmentation/config/index.ts +29 -0
- package/src/stateManagement/segmentation/config/segmentationColor.ts +132 -0
- package/src/stateManagement/segmentation/config/segmentationConfig.ts +195 -0
- package/src/stateManagement/segmentation/config/segmentationVisibility.ts +171 -0
- package/src/stateManagement/segmentation/helpers/index.ts +3 -0
- package/src/stateManagement/segmentation/helpers/normalizeSegmentationInput.ts +35 -0
- package/src/stateManagement/segmentation/helpers/validateSegmentationInput.ts +41 -0
- package/src/stateManagement/segmentation/index.ts +22 -0
- package/src/stateManagement/segmentation/removeSegmentationsFromToolGroup.ts +85 -0
- package/src/stateManagement/segmentation/segmentIndex.ts +38 -0
- package/src/stateManagement/segmentation/segmentLocking.ts +72 -0
- package/src/stateManagement/segmentation/segmentationState.ts +429 -0
- package/src/stateManagement/segmentation/triggerSegmentationEvents.ts +157 -0
- package/src/store/SynchronizerManager/Synchronizer.ts +344 -0
- package/src/store/SynchronizerManager/createSynchronizer.ts +41 -0
- package/src/store/SynchronizerManager/destroy.ts +14 -0
- package/src/store/SynchronizerManager/destroySynchronizer.ts +25 -0
- package/src/store/SynchronizerManager/getAllSynchronizers.ts +12 -0
- package/src/store/SynchronizerManager/getSynchronizer.ts +13 -0
- package/src/store/SynchronizerManager/getSynchronizersForViewport.ts +44 -0
- package/src/store/SynchronizerManager/index.js +15 -0
- package/src/store/ToolGroupManager/ToolGroup.ts +679 -0
- package/src/store/ToolGroupManager/createToolGroup.ts +33 -0
- package/src/store/ToolGroupManager/destroy.ts +24 -0
- package/src/store/ToolGroupManager/destroyToolGroup.ts +26 -0
- package/src/store/ToolGroupManager/getAllToolGroups.ts +12 -0
- package/src/store/ToolGroupManager/getToolGroup.ts +14 -0
- package/src/store/ToolGroupManager/getToolGroupForViewport.ts +44 -0
- package/src/store/ToolGroupManager/getToolGroupsWithToolName.ts +33 -0
- package/src/store/ToolGroupManager/index.ts +17 -0
- package/src/store/addEnabledElement.ts +137 -0
- package/src/store/addTool.ts +56 -0
- package/src/store/cancelActiveManipulations.ts +30 -0
- package/src/store/filterMoveableAnnotationTools.ts +61 -0
- package/src/store/filterToolsWithAnnotationsForElement.ts +51 -0
- package/src/store/filterToolsWithMoveableHandles.ts +51 -0
- package/src/store/index.ts +29 -0
- package/src/store/removeEnabledElement.ts +132 -0
- package/src/store/state.ts +57 -0
- package/src/store/svgNodeCache.ts +7 -0
- package/src/synchronizers/callbacks/areViewportsCoplanar .ts +12 -0
- package/src/synchronizers/callbacks/cameraSyncCallback.ts +33 -0
- package/src/synchronizers/callbacks/stackImageSyncCallback.ts +157 -0
- package/src/synchronizers/callbacks/voiSyncCallback.ts +51 -0
- package/src/synchronizers/callbacks/zoomPanSyncCallback.ts +43 -0
- package/src/synchronizers/index.ts +11 -0
- package/src/synchronizers/synchronizers/createCameraPositionSynchronizer.ts +25 -0
- package/src/synchronizers/synchronizers/createStackImageSynchronizer.ts +25 -0
- package/src/synchronizers/synchronizers/createVOISynchronizer.ts +24 -0
- package/src/synchronizers/synchronizers/createZoomPanSynchronizer.ts +25 -0
- package/src/synchronizers/synchronizers/index.ts +11 -0
- package/src/tools/CrosshairsTool.ts +2693 -0
- package/src/tools/MIPJumpToClickTool.ts +99 -0
- package/src/tools/MagnifyTool.ts +319 -0
- package/src/tools/PanTool.ts +58 -0
- package/src/tools/PlanarRotateTool.ts +77 -0
- package/src/tools/ReferenceCursors.ts +466 -0
- package/src/tools/ReferenceLinesTool.ts +279 -0
- package/src/tools/ScaleOverlayTool.ts +685 -0
- package/src/tools/StackScrollTool.ts +97 -0
- package/src/tools/StackScrollToolMouseWheelTool.ts +58 -0
- package/src/tools/TrackballRotateTool.ts +141 -0
- package/src/tools/VolumeRotateMouseWheelTool.ts +86 -0
- package/src/tools/WindowLevelTool.ts +260 -0
- package/src/tools/ZoomTool.ts +293 -0
- package/src/tools/annotation/AngleTool.ts +835 -0
- package/src/tools/annotation/ArrowAnnotateTool.ts +820 -0
- package/src/tools/annotation/BidirectionalTool.ts +1350 -0
- package/src/tools/annotation/CircleROITool.ts +1070 -0
- package/src/tools/annotation/CobbAngleTool.ts +815 -0
- package/src/tools/annotation/DragProbeTool.ts +213 -0
- package/src/tools/annotation/EllipticalROITool.ts +1223 -0
- package/src/tools/annotation/LengthTool.ts +861 -0
- package/src/tools/annotation/PlanarFreehandROITool.ts +636 -0
- package/src/tools/annotation/ProbeTool.ts +681 -0
- package/src/tools/annotation/RectangleROITool.ts +1028 -0
- package/src/tools/annotation/planarFreehandROITool/closedContourEditLoop.ts +488 -0
- package/src/tools/annotation/planarFreehandROITool/drawLoop.ts +462 -0
- package/src/tools/annotation/planarFreehandROITool/editLoopCommon.ts +331 -0
- package/src/tools/annotation/planarFreehandROITool/findOpenUShapedContourVectorToPeak.ts +74 -0
- package/src/tools/annotation/planarFreehandROITool/openContourEditLoop.ts +612 -0
- package/src/tools/annotation/planarFreehandROITool/openContourEndEditLoop.ts +74 -0
- package/src/tools/annotation/planarFreehandROITool/renderMethods.ts +407 -0
- package/src/tools/base/AnnotationDisplayTool.ts +228 -0
- package/src/tools/base/AnnotationTool.ts +307 -0
- package/src/tools/base/BaseTool.ts +215 -0
- package/src/tools/base/index.ts +4 -0
- package/src/tools/displayTools/Contour/addContourToElement.ts +135 -0
- package/src/tools/displayTools/Contour/contourDisplay.ts +252 -0
- package/src/tools/displayTools/Contour/index.ts +3 -0
- package/src/tools/displayTools/Contour/removeContourFromElement.ts +35 -0
- package/src/tools/displayTools/Labelmap/addLabelmapToElement.ts +57 -0
- package/src/tools/displayTools/Labelmap/index.ts +4 -0
- package/src/tools/displayTools/Labelmap/labelmapConfig.ts +37 -0
- package/src/tools/displayTools/Labelmap/labelmapDisplay.ts +461 -0
- package/src/tools/displayTools/Labelmap/removeLabelmapFromElement.ts +27 -0
- package/src/tools/displayTools/Labelmap/validateRepresentationData.ts +30 -0
- package/src/tools/displayTools/SegmentationDisplayTool.ts +198 -0
- package/src/tools/index.ts +84 -0
- package/src/tools/segmentation/BrushTool.ts +474 -0
- package/src/tools/segmentation/CircleScissorsTool.ts +365 -0
- package/src/tools/segmentation/PaintFillTool.ts +370 -0
- package/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts +471 -0
- package/src/tools/segmentation/RectangleROIThresholdTool.ts +281 -0
- package/src/tools/segmentation/RectangleScissorsTool.ts +382 -0
- package/src/tools/segmentation/SphereScissorsTool.ts +368 -0
- package/src/tools/segmentation/strategies/eraseCircle.ts +30 -0
- package/src/tools/segmentation/strategies/eraseRectangle.ts +81 -0
- package/src/tools/segmentation/strategies/eraseSphere.ts +27 -0
- package/src/tools/segmentation/strategies/fillCircle.ts +185 -0
- package/src/tools/segmentation/strategies/fillRectangle.ts +110 -0
- package/src/tools/segmentation/strategies/fillSphere.ts +88 -0
- package/src/tools/segmentation/strategies/index.ts +9 -0
- package/src/types/AnnotationGroupSelector.ts +7 -0
- package/src/types/AnnotationStyle.ts +42 -0
- package/src/types/AnnotationTypes.ts +109 -0
- package/src/types/BoundsIJK.ts +5 -0
- package/src/types/CINETypes.ts +32 -0
- package/src/types/ContourTypes.ts +26 -0
- package/src/types/CursorTypes.ts +12 -0
- package/src/types/EventTypes.ts +657 -0
- package/src/types/FloodFillTypes.ts +19 -0
- package/src/types/IAnnotationManager.ts +89 -0
- package/src/types/IDistance.ts +16 -0
- package/src/types/IPoints.ts +18 -0
- package/src/types/ISetToolModeOptions.ts +29 -0
- package/src/types/ISynchronizerEventHandler.ts +11 -0
- package/src/types/IToolClassReference.ts +5 -0
- package/src/types/IToolGroup.ts +72 -0
- package/src/types/ITouchPoints.ts +14 -0
- package/src/types/InteractionTypes.ts +6 -0
- package/src/types/InternalToolTypes.ts +19 -0
- package/src/types/JumpToSliceOptions.ts +7 -0
- package/src/types/LabelmapTypes.ts +41 -0
- package/src/types/PlanarBoundingBox.ts +8 -0
- package/src/types/SVGDrawingHelper.ts +10 -0
- package/src/types/ScrollOptions.ts +9 -0
- package/src/types/SegmentationStateTypes.ts +248 -0
- package/src/types/ToolHandle.ts +26 -0
- package/src/types/ToolProps.ts +16 -0
- package/src/types/ToolSpecificAnnotationTypes.ts +311 -0
- package/src/types/index.ts +115 -0
- package/src/utilities/boundingBox/extend2DBoundingBoxInViewAxis.ts +29 -0
- package/src/utilities/boundingBox/getBoundingBoxAroundShape.ts +57 -0
- package/src/utilities/boundingBox/index.ts +4 -0
- package/src/utilities/calibrateImageSpacing.ts +46 -0
- package/src/utilities/cine/events.ts +9 -0
- package/src/utilities/cine/index.ts +5 -0
- package/src/utilities/cine/playClip.ts +435 -0
- package/src/utilities/cine/state.ts +18 -0
- package/src/utilities/clip.js +30 -0
- package/src/utilities/debounce.js +217 -0
- package/src/utilities/drawing/getTextBoxCoordsCanvas.ts +45 -0
- package/src/utilities/drawing/index.ts +3 -0
- package/src/utilities/dynamicVolume/getDataInTime.ts +110 -0
- package/src/utilities/dynamicVolume/index.ts +2 -0
- package/src/utilities/getAnnotationNearPoint.ts +130 -0
- package/src/utilities/getModalityUnit.ts +11 -0
- package/src/utilities/getToolsWithModesForElement.ts +52 -0
- package/src/utilities/index.ts +68 -0
- package/src/utilities/isObject.js +29 -0
- package/src/utilities/math/angle/angleBetweenLines.ts +29 -0
- package/src/utilities/math/circle/_types.ts +6 -0
- package/src/utilities/math/circle/getCanvasCircleCorners.ts +23 -0
- package/src/utilities/math/circle/getCanvasCircleRadius.ts +16 -0
- package/src/utilities/math/circle/index.ts +4 -0
- package/src/utilities/math/ellipse/getCanvasEllipseCorners.ts +26 -0
- package/src/utilities/math/ellipse/index.ts +4 -0
- package/src/utilities/math/ellipse/pointInEllipse.ts +38 -0
- package/src/utilities/math/ellipse/pointInEllipsoidWithConstraint.ts +35 -0
- package/src/utilities/math/index.ts +8 -0
- package/src/utilities/math/line/distanceToPoint.ts +24 -0
- package/src/utilities/math/line/distanceToPointSquared.ts +44 -0
- package/src/utilities/math/line/index.ts +5 -0
- package/src/utilities/math/line/intersectLine.ts +92 -0
- package/src/utilities/math/midPoint.ts +24 -0
- package/src/utilities/math/point/distanceToPoint.ts +22 -0
- package/src/utilities/math/point/index.ts +3 -0
- package/src/utilities/math/polyline/addCanvasPointsToArray.ts +62 -0
- package/src/utilities/math/polyline/calculateAreaOfPoints.ts +23 -0
- package/src/utilities/math/polyline/getIntersectionWithPolyline.ts +182 -0
- package/src/utilities/math/polyline/getSubPixelSpacingAndXYDirections.ts +99 -0
- package/src/utilities/math/polyline/index.ts +19 -0
- package/src/utilities/math/polyline/planarFreehandROIInternalTypes.ts +36 -0
- package/src/utilities/math/polyline/pointCanProjectOnLine.ts +57 -0
- package/src/utilities/math/polyline/pointsAreWithinCloseContourProximity.ts +15 -0
- package/src/utilities/math/rectangle/distanceToPoint.ts +82 -0
- package/src/utilities/math/rectangle/index.ts +3 -0
- package/src/utilities/math/sphere/index.ts +3 -0
- package/src/utilities/math/sphere/pointInSphere.ts +31 -0
- package/src/utilities/math/vec2/findClosestPoint.ts +40 -0
- package/src/utilities/math/vec2/index.ts +4 -0
- package/src/utilities/math/vec2/liangBarksyClip.ts +84 -0
- package/src/utilities/orientation/getOrientationStringLPS.ts +52 -0
- package/src/utilities/orientation/index.ts +4 -0
- package/src/utilities/orientation/invertOrientationStringLPS.ts +21 -0
- package/src/utilities/planar/filterAnnotationsForDisplay.ts +68 -0
- package/src/utilities/planar/filterAnnotationsWithinSlice.ts +85 -0
- package/src/utilities/planar/getPointInLineOfSightWithCriteria.ts +104 -0
- package/src/utilities/planar/getWorldWidthAndHeightFromCorners.ts +51 -0
- package/src/utilities/planar/getWorldWidthAndHeightFromTwoPoints.ts +51 -0
- package/src/utilities/planar/index.ts +18 -0
- package/src/utilities/planarFreehandROITool/index.ts +7 -0
- package/src/utilities/planarFreehandROITool/interpolateAnnotation.ts +87 -0
- package/src/utilities/planarFreehandROITool/interpolatePoints.ts +214 -0
- package/src/utilities/planarFreehandROITool/interpolation/algorithms/bspline.ts +55 -0
- package/src/utilities/planarFreehandROITool/interpolation/interpolateSegmentPoints.ts +90 -0
- package/src/utilities/pointInShapeCallback.ts +138 -0
- package/src/utilities/pointInSurroundingSphereCallback.ts +188 -0
- package/src/utilities/rectangleROITool/getBoundsIJKFromRectangleAnnotations.ts +76 -0
- package/src/utilities/rectangleROITool/index.ts +3 -0
- package/src/utilities/scroll.ts +62 -0
- package/src/utilities/segmentation/brushSizeForToolGroup.ts +72 -0
- package/src/utilities/segmentation/brushThresholdForToolGroup.ts +65 -0
- package/src/utilities/segmentation/createLabelmapVolumeForViewport.ts +74 -0
- package/src/utilities/segmentation/createMergedLabelmapForIndex.ts +65 -0
- package/src/utilities/segmentation/floodFill.ts +194 -0
- package/src/utilities/segmentation/getDefaultRepresentationConfig.ts +20 -0
- package/src/utilities/segmentation/index.ts +33 -0
- package/src/utilities/segmentation/isValidRepresentationConfig.ts +22 -0
- package/src/utilities/segmentation/rectangleROIThresholdVolumeByRange.ts +91 -0
- package/src/utilities/segmentation/thresholdSegmentationByRange.ts +129 -0
- package/src/utilities/segmentation/thresholdVolumeByRange.ts +150 -0
- package/src/utilities/segmentation/triggerSegmentationRender.ts +206 -0
- package/src/utilities/segmentation/utilities.ts +116 -0
- package/src/utilities/stackPrefetch/index.ts +8 -0
- package/src/utilities/stackPrefetch/stackPrefetch.ts +405 -0
- package/src/utilities/stackPrefetch/state.ts +17 -0
- package/src/utilities/throttle.js +69 -0
- package/src/utilities/touch/index.ts +246 -0
- package/src/utilities/triggerAnnotationRender.ts +237 -0
- package/src/utilities/triggerAnnotationRenderForViewportIds.ts +18 -0
- package/src/utilities/viewport/index.ts +5 -0
- package/src/utilities/viewport/isViewportPreScaled.ts +24 -0
- package/src/utilities/viewport/jumpToSlice.ts +73 -0
- package/src/utilities/viewport/jumpToWorld.ts +58 -0
- package/src/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.ts +28 -0
- package/src/utilities/viewportFilters/filterViewportsWithParallelNormals.ts +26 -0
- package/src/utilities/viewportFilters/filterViewportsWithSameOrientation.ts +15 -0
- package/src/utilities/viewportFilters/filterViewportsWithToolEnabled.ts +72 -0
- package/src/utilities/viewportFilters/getViewportIdsWithToolToRender.ts +45 -0
- package/src/utilities/viewportFilters/index.ts +11 -0
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
import { AnnotationTool } from '../base';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getEnabledElement,
|
|
5
|
+
VolumeViewport,
|
|
6
|
+
eventTarget,
|
|
7
|
+
triggerEvent,
|
|
8
|
+
utilities as csUtils,
|
|
9
|
+
} from '@cornerstonejs/core';
|
|
10
|
+
import type { Types } from '@cornerstonejs/core';
|
|
11
|
+
|
|
12
|
+
import throttle from '../../utilities/throttle';
|
|
13
|
+
import {
|
|
14
|
+
addAnnotation,
|
|
15
|
+
getAnnotations,
|
|
16
|
+
removeAnnotation,
|
|
17
|
+
} from '../../stateManagement/annotation/annotationState';
|
|
18
|
+
import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
|
|
19
|
+
import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
|
|
20
|
+
import {
|
|
21
|
+
drawCircle as drawCircleSvg,
|
|
22
|
+
drawEllipse as drawEllipseSvg,
|
|
23
|
+
drawHandles as drawHandlesSvg,
|
|
24
|
+
drawLinkedTextBox as drawLinkedTextBoxSvg,
|
|
25
|
+
} from '../../drawingSvg';
|
|
26
|
+
import { state } from '../../store';
|
|
27
|
+
import { Events } from '../../enums';
|
|
28
|
+
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
29
|
+
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
30
|
+
import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorldWidthAndHeightFromTwoPoints';
|
|
31
|
+
import {
|
|
32
|
+
pointInEllipse,
|
|
33
|
+
getCanvasEllipseCorners,
|
|
34
|
+
} from '../../utilities/math/ellipse';
|
|
35
|
+
import {
|
|
36
|
+
resetElementCursor,
|
|
37
|
+
hideElementCursor,
|
|
38
|
+
} from '../../cursors/elementCursor';
|
|
39
|
+
import {
|
|
40
|
+
EventTypes,
|
|
41
|
+
ToolHandle,
|
|
42
|
+
TextBoxHandle,
|
|
43
|
+
PublicToolProps,
|
|
44
|
+
ToolProps,
|
|
45
|
+
InteractionTypes,
|
|
46
|
+
SVGDrawingHelper,
|
|
47
|
+
} from '../../types';
|
|
48
|
+
import { EllipticalROIAnnotation } from '../../types/ToolSpecificAnnotationTypes';
|
|
49
|
+
|
|
50
|
+
import {
|
|
51
|
+
AnnotationCompletedEventDetail,
|
|
52
|
+
AnnotationModifiedEventDetail,
|
|
53
|
+
MouseDragEventType,
|
|
54
|
+
MouseMoveEventType,
|
|
55
|
+
} from '../../types/EventTypes';
|
|
56
|
+
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
57
|
+
import { pointInShapeCallback } from '../../utilities/';
|
|
58
|
+
import { StyleSpecifier } from '../../types/AnnotationStyle';
|
|
59
|
+
import { getModalityUnit } from '../../utilities/getModalityUnit';
|
|
60
|
+
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
61
|
+
|
|
62
|
+
const { transformWorldToIndex } = csUtils;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* EllipticalROITool let you draw annotations that measures the statistics
|
|
66
|
+
* such as area, max, mean and stdDev of an elliptical region of interest.
|
|
67
|
+
* You can use EllipticalROITool in all perpendicular views (axial, sagittal, coronal).
|
|
68
|
+
* Note: annotation tools in cornerstone3DTools exists in the exact location
|
|
69
|
+
* in the physical 3d space, as a result, by default, all annotations that are
|
|
70
|
+
* drawing in the same frameOfReference will get shared between viewports that
|
|
71
|
+
* are in the same frameOfReference. Elliptical tool's text box lines are dynamically
|
|
72
|
+
* generated based on the viewport's underlying Modality. For instance, if
|
|
73
|
+
* the viewport is displaying CT, the text box will shown the statistics in Hounsfield units,
|
|
74
|
+
* and if the viewport is displaying PET, the text box will show the statistics in
|
|
75
|
+
* SUV units.
|
|
76
|
+
*
|
|
77
|
+
* The resulting annotation's data (statistics) and metadata (the
|
|
78
|
+
* state of the viewport while drawing was happening) will get added to the
|
|
79
|
+
* ToolState manager and can be accessed from the ToolState by calling getAnnotations
|
|
80
|
+
* or similar methods.
|
|
81
|
+
*
|
|
82
|
+
* Changing tool configuration (see below) you can make the tool to draw the center
|
|
83
|
+
* point circle with a given radius.
|
|
84
|
+
*
|
|
85
|
+
* ```js
|
|
86
|
+
* cornerstoneTools.addTool(EllipticalROITool)
|
|
87
|
+
*
|
|
88
|
+
* const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
|
|
89
|
+
*
|
|
90
|
+
* toolGroup.addTool(EllipticalROITool.toolName)
|
|
91
|
+
*
|
|
92
|
+
* toolGroup.addViewport('viewportId', 'renderingEngineId')
|
|
93
|
+
*
|
|
94
|
+
* toolGroup.setToolActive(EllipticalROITool.toolName, {
|
|
95
|
+
* bindings: [
|
|
96
|
+
* {
|
|
97
|
+
* mouseButton: MouseBindings.Primary, // Left Click
|
|
98
|
+
* },
|
|
99
|
+
* ],
|
|
100
|
+
* })
|
|
101
|
+
*
|
|
102
|
+
* // draw a circle at the center point with 4px radius.
|
|
103
|
+
* toolGroup.setToolConfiguration(EllipticalROITool.toolName, {
|
|
104
|
+
* centerPointRadius: 4,
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Read more in the Docs section of the website.
|
|
109
|
+
*/
|
|
110
|
+
class EllipticalROITool extends AnnotationTool {
|
|
111
|
+
static toolName;
|
|
112
|
+
touchDragCallback: any;
|
|
113
|
+
mouseDragCallback: any;
|
|
114
|
+
_throttledCalculateCachedStats: any;
|
|
115
|
+
editData: {
|
|
116
|
+
annotation: any;
|
|
117
|
+
viewportIdsToRender: Array<string>;
|
|
118
|
+
handleIndex?: number;
|
|
119
|
+
movingTextBox?: boolean;
|
|
120
|
+
centerCanvas?: Array<number>;
|
|
121
|
+
canvasWidth?: number;
|
|
122
|
+
canvasHeight?: number;
|
|
123
|
+
originalHandleCanvas?: Array<number>;
|
|
124
|
+
newAnnotation?: boolean;
|
|
125
|
+
hasMoved?: boolean;
|
|
126
|
+
} | null;
|
|
127
|
+
isDrawing: boolean;
|
|
128
|
+
isHandleOutsideImage = false;
|
|
129
|
+
|
|
130
|
+
constructor(
|
|
131
|
+
toolProps: PublicToolProps = {},
|
|
132
|
+
defaultToolProps: ToolProps = {
|
|
133
|
+
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
134
|
+
configuration: {
|
|
135
|
+
shadow: true,
|
|
136
|
+
preventHandleOutsideImage: false,
|
|
137
|
+
// Radius of the circle to draw at the center point of the ellipse.
|
|
138
|
+
// Set this zero(0) in order not to draw the circle.
|
|
139
|
+
centerPointRadius: 0,
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
) {
|
|
143
|
+
super(toolProps, defaultToolProps);
|
|
144
|
+
|
|
145
|
+
this._throttledCalculateCachedStats = throttle(
|
|
146
|
+
this._calculateCachedStats,
|
|
147
|
+
100,
|
|
148
|
+
{ trailing: true }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Based on the current position of the mouse and the current imageId to create
|
|
154
|
+
* a EllipticalROI Annotation and stores it in the annotationManager
|
|
155
|
+
*
|
|
156
|
+
* @param evt - EventTypes.NormalizedMouseEventType
|
|
157
|
+
* @returns The annotation object.
|
|
158
|
+
*
|
|
159
|
+
*/
|
|
160
|
+
addNewAnnotation = (
|
|
161
|
+
evt: EventTypes.InteractionEventType
|
|
162
|
+
): EllipticalROIAnnotation => {
|
|
163
|
+
const eventDetail = evt.detail;
|
|
164
|
+
const { currentPoints, element } = eventDetail;
|
|
165
|
+
const worldPos = currentPoints.world;
|
|
166
|
+
const canvasPos = currentPoints.canvas;
|
|
167
|
+
|
|
168
|
+
const enabledElement = getEnabledElement(element);
|
|
169
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
170
|
+
|
|
171
|
+
this.isDrawing = true;
|
|
172
|
+
|
|
173
|
+
const camera = viewport.getCamera();
|
|
174
|
+
const { viewPlaneNormal, viewUp } = camera;
|
|
175
|
+
|
|
176
|
+
const referencedImageId = this.getReferencedImageId(
|
|
177
|
+
viewport,
|
|
178
|
+
worldPos,
|
|
179
|
+
viewPlaneNormal,
|
|
180
|
+
viewUp
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
|
184
|
+
|
|
185
|
+
const annotation = {
|
|
186
|
+
highlighted: true,
|
|
187
|
+
invalidated: true,
|
|
188
|
+
metadata: {
|
|
189
|
+
toolName: this.getToolName(),
|
|
190
|
+
viewPlaneNormal: <Types.Point3>[...viewPlaneNormal],
|
|
191
|
+
viewUp: <Types.Point3>[...viewUp],
|
|
192
|
+
FrameOfReferenceUID,
|
|
193
|
+
referencedImageId,
|
|
194
|
+
},
|
|
195
|
+
data: {
|
|
196
|
+
label: '',
|
|
197
|
+
handles: {
|
|
198
|
+
textBox: {
|
|
199
|
+
hasMoved: false,
|
|
200
|
+
worldPosition: <Types.Point3>[0, 0, 0],
|
|
201
|
+
worldBoundingBox: {
|
|
202
|
+
topLeft: <Types.Point3>[0, 0, 0],
|
|
203
|
+
topRight: <Types.Point3>[0, 0, 0],
|
|
204
|
+
bottomLeft: <Types.Point3>[0, 0, 0],
|
|
205
|
+
bottomRight: <Types.Point3>[0, 0, 0],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
points: [
|
|
209
|
+
[...worldPos],
|
|
210
|
+
[...worldPos],
|
|
211
|
+
[...worldPos],
|
|
212
|
+
[...worldPos],
|
|
213
|
+
] as [Types.Point3, Types.Point3, Types.Point3, Types.Point3],
|
|
214
|
+
activeHandleIndex: null,
|
|
215
|
+
},
|
|
216
|
+
cachedStats: {},
|
|
217
|
+
initialRotation: viewport.getRotation(),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
addAnnotation(annotation, element);
|
|
222
|
+
|
|
223
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
224
|
+
element,
|
|
225
|
+
this.getToolName()
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
this.editData = {
|
|
229
|
+
annotation,
|
|
230
|
+
viewportIdsToRender,
|
|
231
|
+
centerCanvas: canvasPos,
|
|
232
|
+
newAnnotation: true,
|
|
233
|
+
hasMoved: false,
|
|
234
|
+
};
|
|
235
|
+
this._activateDraw(element);
|
|
236
|
+
|
|
237
|
+
hideElementCursor(element);
|
|
238
|
+
|
|
239
|
+
evt.preventDefault();
|
|
240
|
+
|
|
241
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
242
|
+
|
|
243
|
+
return annotation;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* It returns if the canvas point is near the provided annotation in the provided
|
|
248
|
+
* element or not. A proximity is passed to the function to determine the
|
|
249
|
+
* proximity of the point to the annotation in number of pixels.
|
|
250
|
+
*
|
|
251
|
+
* @param element - HTML Element
|
|
252
|
+
* @param annotation - Annotation
|
|
253
|
+
* @param canvasCoords - Canvas coordinates
|
|
254
|
+
* @param proximity - Proximity to tool to consider
|
|
255
|
+
* @returns Boolean, whether the canvas point is near tool
|
|
256
|
+
*/
|
|
257
|
+
isPointNearTool = (
|
|
258
|
+
element: HTMLDivElement,
|
|
259
|
+
annotation: EllipticalROIAnnotation,
|
|
260
|
+
canvasCoords: Types.Point2,
|
|
261
|
+
proximity: number
|
|
262
|
+
): boolean => {
|
|
263
|
+
const enabledElement = getEnabledElement(element);
|
|
264
|
+
const { viewport } = enabledElement;
|
|
265
|
+
|
|
266
|
+
const { data } = annotation;
|
|
267
|
+
const { points } = data.handles;
|
|
268
|
+
|
|
269
|
+
// For some reason Typescript doesn't understand this, so we need to be
|
|
270
|
+
// more specific about the type
|
|
271
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p)) as [
|
|
272
|
+
Types.Point2,
|
|
273
|
+
Types.Point2,
|
|
274
|
+
Types.Point2,
|
|
275
|
+
Types.Point2
|
|
276
|
+
];
|
|
277
|
+
const canvasCorners = getCanvasEllipseCorners(canvasCoordinates);
|
|
278
|
+
|
|
279
|
+
const [canvasPoint1, canvasPoint2] = canvasCorners;
|
|
280
|
+
|
|
281
|
+
const minorEllipse = {
|
|
282
|
+
left: Math.min(canvasPoint1[0], canvasPoint2[0]) + proximity / 2,
|
|
283
|
+
top: Math.min(canvasPoint1[1], canvasPoint2[1]) + proximity / 2,
|
|
284
|
+
width: Math.abs(canvasPoint1[0] - canvasPoint2[0]) - proximity,
|
|
285
|
+
height: Math.abs(canvasPoint1[1] - canvasPoint2[1]) - proximity,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const majorEllipse = {
|
|
289
|
+
left: Math.min(canvasPoint1[0], canvasPoint2[0]) - proximity / 2,
|
|
290
|
+
top: Math.min(canvasPoint1[1], canvasPoint2[1]) - proximity / 2,
|
|
291
|
+
width: Math.abs(canvasPoint1[0] - canvasPoint2[0]) + proximity,
|
|
292
|
+
height: Math.abs(canvasPoint1[1] - canvasPoint2[1]) + proximity,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const pointInMinorEllipse = this._pointInEllipseCanvas(
|
|
296
|
+
minorEllipse,
|
|
297
|
+
canvasCoords
|
|
298
|
+
);
|
|
299
|
+
const pointInMajorEllipse = this._pointInEllipseCanvas(
|
|
300
|
+
majorEllipse,
|
|
301
|
+
canvasCoords
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (pointInMajorEllipse && !pointInMinorEllipse) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return false;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
toolSelectedCallback = (
|
|
312
|
+
evt: EventTypes.InteractionEventType,
|
|
313
|
+
annotation: EllipticalROIAnnotation
|
|
314
|
+
): void => {
|
|
315
|
+
const eventDetail = evt.detail;
|
|
316
|
+
const { element } = eventDetail;
|
|
317
|
+
|
|
318
|
+
annotation.highlighted = true;
|
|
319
|
+
|
|
320
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
321
|
+
element,
|
|
322
|
+
this.getToolName()
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
this.editData = {
|
|
326
|
+
annotation,
|
|
327
|
+
viewportIdsToRender,
|
|
328
|
+
movingTextBox: false,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
hideElementCursor(element);
|
|
332
|
+
|
|
333
|
+
this._activateModify(element);
|
|
334
|
+
|
|
335
|
+
const enabledElement = getEnabledElement(element);
|
|
336
|
+
const { renderingEngine } = enabledElement;
|
|
337
|
+
|
|
338
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
339
|
+
|
|
340
|
+
evt.preventDefault();
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
handleSelectedCallback = (
|
|
344
|
+
evt: EventTypes.InteractionEventType,
|
|
345
|
+
annotation: EllipticalROIAnnotation,
|
|
346
|
+
handle: ToolHandle
|
|
347
|
+
): void => {
|
|
348
|
+
const eventDetail = evt.detail;
|
|
349
|
+
const { element } = eventDetail;
|
|
350
|
+
const { data } = annotation;
|
|
351
|
+
|
|
352
|
+
annotation.highlighted = true;
|
|
353
|
+
|
|
354
|
+
let movingTextBox = false;
|
|
355
|
+
let handleIndex;
|
|
356
|
+
|
|
357
|
+
let centerCanvas;
|
|
358
|
+
let canvasWidth;
|
|
359
|
+
let canvasHeight;
|
|
360
|
+
let originalHandleCanvas;
|
|
361
|
+
|
|
362
|
+
if ((handle as TextBoxHandle).worldPosition) {
|
|
363
|
+
movingTextBox = true;
|
|
364
|
+
} else {
|
|
365
|
+
const { points } = data.handles;
|
|
366
|
+
const enabledElement = getEnabledElement(element);
|
|
367
|
+
const { worldToCanvas } = enabledElement.viewport;
|
|
368
|
+
|
|
369
|
+
handleIndex = points.findIndex((p) => p === handle);
|
|
370
|
+
|
|
371
|
+
const pointsCanvas = points.map(worldToCanvas);
|
|
372
|
+
|
|
373
|
+
originalHandleCanvas = pointsCanvas[handleIndex];
|
|
374
|
+
|
|
375
|
+
canvasWidth = Math.abs(pointsCanvas[2][0] - pointsCanvas[3][0]);
|
|
376
|
+
canvasHeight = Math.abs(pointsCanvas[0][1] - pointsCanvas[1][1]);
|
|
377
|
+
|
|
378
|
+
centerCanvas = [
|
|
379
|
+
(pointsCanvas[2][0] + pointsCanvas[3][0]) / 2,
|
|
380
|
+
(pointsCanvas[0][1] + pointsCanvas[1][1]) / 2,
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Find viewports to render on drag.
|
|
385
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
386
|
+
element,
|
|
387
|
+
this.getToolName()
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
this.editData = {
|
|
391
|
+
annotation,
|
|
392
|
+
viewportIdsToRender,
|
|
393
|
+
handleIndex,
|
|
394
|
+
canvasWidth,
|
|
395
|
+
canvasHeight,
|
|
396
|
+
centerCanvas,
|
|
397
|
+
originalHandleCanvas,
|
|
398
|
+
movingTextBox,
|
|
399
|
+
};
|
|
400
|
+
this._activateModify(element);
|
|
401
|
+
|
|
402
|
+
hideElementCursor(element);
|
|
403
|
+
|
|
404
|
+
const enabledElement = getEnabledElement(element);
|
|
405
|
+
const { renderingEngine } = enabledElement;
|
|
406
|
+
|
|
407
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
408
|
+
|
|
409
|
+
evt.preventDefault();
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
_endCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
413
|
+
const eventDetail = evt.detail;
|
|
414
|
+
const { element } = eventDetail;
|
|
415
|
+
|
|
416
|
+
const { annotation, viewportIdsToRender, newAnnotation, hasMoved } =
|
|
417
|
+
this.editData;
|
|
418
|
+
const { data } = annotation;
|
|
419
|
+
|
|
420
|
+
if (newAnnotation && !hasMoved) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Elliptical ROI tool should reset its highlight to false on mouse up (as opposed
|
|
425
|
+
// to other tools that keep it highlighted until the user moves. The reason
|
|
426
|
+
// is that we use top-left and bottom-right handles to define the ellipse,
|
|
427
|
+
// and they are by definition not in the ellipse on mouse up.
|
|
428
|
+
annotation.highlighted = false;
|
|
429
|
+
data.handles.activeHandleIndex = null;
|
|
430
|
+
|
|
431
|
+
this._deactivateModify(element);
|
|
432
|
+
this._deactivateDraw(element);
|
|
433
|
+
|
|
434
|
+
resetElementCursor(element);
|
|
435
|
+
|
|
436
|
+
const enabledElement = getEnabledElement(element);
|
|
437
|
+
const { renderingEngine } = enabledElement;
|
|
438
|
+
|
|
439
|
+
this.editData = null;
|
|
440
|
+
this.isDrawing = false;
|
|
441
|
+
|
|
442
|
+
if (
|
|
443
|
+
this.isHandleOutsideImage &&
|
|
444
|
+
this.configuration.preventHandleOutsideImage
|
|
445
|
+
) {
|
|
446
|
+
removeAnnotation(annotation.annotationUID);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
450
|
+
|
|
451
|
+
if (newAnnotation) {
|
|
452
|
+
const eventType = Events.ANNOTATION_COMPLETED;
|
|
453
|
+
|
|
454
|
+
const eventDetail: AnnotationCompletedEventDetail = {
|
|
455
|
+
annotation,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
_dragDrawCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
463
|
+
this.isDrawing = true;
|
|
464
|
+
const eventDetail = evt.detail;
|
|
465
|
+
const { element } = eventDetail;
|
|
466
|
+
const { currentPoints } = eventDetail;
|
|
467
|
+
const currentCanvasPoints = currentPoints.canvas;
|
|
468
|
+
const enabledElement = getEnabledElement(element);
|
|
469
|
+
const { renderingEngine, viewport } = enabledElement;
|
|
470
|
+
const { canvasToWorld } = viewport;
|
|
471
|
+
|
|
472
|
+
//////
|
|
473
|
+
const { annotation, viewportIdsToRender, centerCanvas } = this.editData;
|
|
474
|
+
const { data } = annotation;
|
|
475
|
+
|
|
476
|
+
const dX = Math.abs(currentCanvasPoints[0] - centerCanvas[0]);
|
|
477
|
+
const dY = Math.abs(currentCanvasPoints[1] - centerCanvas[1]);
|
|
478
|
+
|
|
479
|
+
// Todo: why bottom is -dY, it should be +dY
|
|
480
|
+
const bottomCanvas = <Types.Point2>[centerCanvas[0], centerCanvas[1] - dY];
|
|
481
|
+
const topCanvas = <Types.Point2>[centerCanvas[0], centerCanvas[1] + dY];
|
|
482
|
+
const leftCanvas = <Types.Point2>[centerCanvas[0] - dX, centerCanvas[1]];
|
|
483
|
+
const rightCanvas = <Types.Point2>[centerCanvas[0] + dX, centerCanvas[1]];
|
|
484
|
+
|
|
485
|
+
data.handles.points = [
|
|
486
|
+
canvasToWorld(bottomCanvas),
|
|
487
|
+
canvasToWorld(topCanvas),
|
|
488
|
+
canvasToWorld(leftCanvas),
|
|
489
|
+
canvasToWorld(rightCanvas),
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
annotation.invalidated = true;
|
|
493
|
+
|
|
494
|
+
this.editData.hasMoved = true;
|
|
495
|
+
|
|
496
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
_dragModifyCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
500
|
+
this.isDrawing = true;
|
|
501
|
+
const eventDetail = evt.detail;
|
|
502
|
+
const { element } = eventDetail;
|
|
503
|
+
|
|
504
|
+
const { annotation, viewportIdsToRender, handleIndex, movingTextBox } =
|
|
505
|
+
this.editData;
|
|
506
|
+
const { data } = annotation;
|
|
507
|
+
|
|
508
|
+
if (movingTextBox) {
|
|
509
|
+
const { deltaPoints } = eventDetail;
|
|
510
|
+
const worldPosDelta = deltaPoints.world;
|
|
511
|
+
|
|
512
|
+
const { textBox } = data.handles;
|
|
513
|
+
const { worldPosition } = textBox;
|
|
514
|
+
|
|
515
|
+
worldPosition[0] += worldPosDelta[0];
|
|
516
|
+
worldPosition[1] += worldPosDelta[1];
|
|
517
|
+
worldPosition[2] += worldPosDelta[2];
|
|
518
|
+
|
|
519
|
+
textBox.hasMoved = true;
|
|
520
|
+
} else if (handleIndex === undefined) {
|
|
521
|
+
// Moving tool
|
|
522
|
+
const { deltaPoints } = eventDetail;
|
|
523
|
+
const worldPosDelta = deltaPoints.world;
|
|
524
|
+
|
|
525
|
+
const points = data.handles.points;
|
|
526
|
+
|
|
527
|
+
points.forEach((point) => {
|
|
528
|
+
point[0] += worldPosDelta[0];
|
|
529
|
+
point[1] += worldPosDelta[1];
|
|
530
|
+
point[2] += worldPosDelta[2];
|
|
531
|
+
});
|
|
532
|
+
annotation.invalidated = true;
|
|
533
|
+
} else {
|
|
534
|
+
this._dragHandle(evt);
|
|
535
|
+
annotation.invalidated = true;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const enabledElement = getEnabledElement(element);
|
|
539
|
+
const { renderingEngine } = enabledElement;
|
|
540
|
+
|
|
541
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
_dragHandle = (evt: EventTypes.InteractionEventType): void => {
|
|
545
|
+
const eventDetail = evt.detail;
|
|
546
|
+
const { element } = eventDetail;
|
|
547
|
+
const enabledElement = getEnabledElement(element);
|
|
548
|
+
const { canvasToWorld } = enabledElement.viewport;
|
|
549
|
+
|
|
550
|
+
const {
|
|
551
|
+
annotation,
|
|
552
|
+
canvasWidth,
|
|
553
|
+
canvasHeight,
|
|
554
|
+
handleIndex,
|
|
555
|
+
centerCanvas,
|
|
556
|
+
originalHandleCanvas,
|
|
557
|
+
} = this.editData;
|
|
558
|
+
const { data } = annotation;
|
|
559
|
+
const { points } = data.handles;
|
|
560
|
+
|
|
561
|
+
// Move current point in that direction.
|
|
562
|
+
// Move other points in opposite direction.
|
|
563
|
+
|
|
564
|
+
const { currentPoints } = eventDetail;
|
|
565
|
+
const currentCanvasPoints = currentPoints.canvas;
|
|
566
|
+
|
|
567
|
+
if (handleIndex === 0 || handleIndex === 1) {
|
|
568
|
+
// Dragging top or bottom point
|
|
569
|
+
const dYCanvas = Math.abs(currentCanvasPoints[1] - centerCanvas[1]);
|
|
570
|
+
const canvasBottom = <Types.Point2>[
|
|
571
|
+
centerCanvas[0],
|
|
572
|
+
centerCanvas[1] - dYCanvas,
|
|
573
|
+
];
|
|
574
|
+
const canvasTop = <Types.Point2>[
|
|
575
|
+
centerCanvas[0],
|
|
576
|
+
centerCanvas[1] + dYCanvas,
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
points[0] = canvasToWorld(canvasBottom);
|
|
580
|
+
points[1] = canvasToWorld(canvasTop);
|
|
581
|
+
|
|
582
|
+
const dXCanvas = currentCanvasPoints[0] - originalHandleCanvas[0];
|
|
583
|
+
const newHalfCanvasWidth = canvasWidth / 2 + dXCanvas;
|
|
584
|
+
const canvasLeft = <Types.Point2>[
|
|
585
|
+
centerCanvas[0] - newHalfCanvasWidth,
|
|
586
|
+
centerCanvas[1],
|
|
587
|
+
];
|
|
588
|
+
const canvasRight = <Types.Point2>[
|
|
589
|
+
centerCanvas[0] + newHalfCanvasWidth,
|
|
590
|
+
centerCanvas[1],
|
|
591
|
+
];
|
|
592
|
+
|
|
593
|
+
points[2] = canvasToWorld(canvasLeft);
|
|
594
|
+
points[3] = canvasToWorld(canvasRight);
|
|
595
|
+
} else {
|
|
596
|
+
// Dragging left or right point
|
|
597
|
+
const dXCanvas = Math.abs(currentCanvasPoints[0] - centerCanvas[0]);
|
|
598
|
+
const canvasLeft = <Types.Point2>[
|
|
599
|
+
centerCanvas[0] - dXCanvas,
|
|
600
|
+
centerCanvas[1],
|
|
601
|
+
];
|
|
602
|
+
const canvasRight = <Types.Point2>[
|
|
603
|
+
centerCanvas[0] + dXCanvas,
|
|
604
|
+
centerCanvas[1],
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
points[2] = canvasToWorld(canvasLeft);
|
|
608
|
+
points[3] = canvasToWorld(canvasRight);
|
|
609
|
+
|
|
610
|
+
const dYCanvas = currentCanvasPoints[1] - originalHandleCanvas[1];
|
|
611
|
+
const newHalfCanvasHeight = canvasHeight / 2 + dYCanvas;
|
|
612
|
+
const canvasBottom = <Types.Point2>[
|
|
613
|
+
centerCanvas[0],
|
|
614
|
+
centerCanvas[1] - newHalfCanvasHeight,
|
|
615
|
+
];
|
|
616
|
+
const canvasTop = <Types.Point2>[
|
|
617
|
+
centerCanvas[0],
|
|
618
|
+
centerCanvas[1] + newHalfCanvasHeight,
|
|
619
|
+
];
|
|
620
|
+
|
|
621
|
+
points[0] = canvasToWorld(canvasBottom);
|
|
622
|
+
points[1] = canvasToWorld(canvasTop);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
cancel = (element: HTMLDivElement) => {
|
|
627
|
+
// If it is mid-draw or mid-modify
|
|
628
|
+
if (this.isDrawing) {
|
|
629
|
+
this.isDrawing = false;
|
|
630
|
+
this._deactivateDraw(element);
|
|
631
|
+
this._deactivateModify(element);
|
|
632
|
+
resetElementCursor(element);
|
|
633
|
+
|
|
634
|
+
const { annotation, viewportIdsToRender, newAnnotation } = this.editData;
|
|
635
|
+
const { data } = annotation;
|
|
636
|
+
|
|
637
|
+
annotation.highlighted = false;
|
|
638
|
+
data.handles.activeHandleIndex = null;
|
|
639
|
+
|
|
640
|
+
const enabledElement = getEnabledElement(element);
|
|
641
|
+
const { renderingEngine } = enabledElement;
|
|
642
|
+
|
|
643
|
+
triggerAnnotationRenderForViewportIds(
|
|
644
|
+
renderingEngine,
|
|
645
|
+
viewportIdsToRender
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
if (newAnnotation) {
|
|
649
|
+
const eventType = Events.ANNOTATION_COMPLETED;
|
|
650
|
+
|
|
651
|
+
const eventDetail: AnnotationCompletedEventDetail = {
|
|
652
|
+
annotation,
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
this.editData = null;
|
|
659
|
+
return annotation.annotationUID;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
_activateModify = (element) => {
|
|
664
|
+
state.isInteractingWithTool = true;
|
|
665
|
+
|
|
666
|
+
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
667
|
+
element.addEventListener(Events.MOUSE_DRAG, this._dragModifyCallback);
|
|
668
|
+
element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
669
|
+
|
|
670
|
+
element.addEventListener(Events.TOUCH_END, this._endCallback);
|
|
671
|
+
element.addEventListener(Events.TOUCH_DRAG, this._dragModifyCallback);
|
|
672
|
+
element.addEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
_deactivateModify = (element) => {
|
|
676
|
+
state.isInteractingWithTool = false;
|
|
677
|
+
|
|
678
|
+
element.removeEventListener(Events.MOUSE_UP, this._endCallback);
|
|
679
|
+
element.removeEventListener(Events.MOUSE_DRAG, this._dragModifyCallback);
|
|
680
|
+
element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
681
|
+
|
|
682
|
+
element.removeEventListener(Events.TOUCH_END, this._endCallback);
|
|
683
|
+
element.removeEventListener(Events.TOUCH_DRAG, this._dragModifyCallback);
|
|
684
|
+
element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
_activateDraw = (element) => {
|
|
688
|
+
state.isInteractingWithTool = true;
|
|
689
|
+
|
|
690
|
+
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
691
|
+
element.addEventListener(Events.MOUSE_DRAG, this._dragDrawCallback);
|
|
692
|
+
element.addEventListener(Events.MOUSE_MOVE, this._dragDrawCallback);
|
|
693
|
+
element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
694
|
+
|
|
695
|
+
element.addEventListener(Events.TOUCH_END, this._endCallback);
|
|
696
|
+
element.addEventListener(Events.TOUCH_DRAG, this._dragDrawCallback);
|
|
697
|
+
element.addEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
_deactivateDraw = (element) => {
|
|
701
|
+
state.isInteractingWithTool = false;
|
|
702
|
+
|
|
703
|
+
element.removeEventListener(Events.MOUSE_UP, this._endCallback);
|
|
704
|
+
element.removeEventListener(Events.MOUSE_DRAG, this._dragDrawCallback);
|
|
705
|
+
element.removeEventListener(Events.MOUSE_MOVE, this._dragDrawCallback);
|
|
706
|
+
element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
707
|
+
|
|
708
|
+
element.removeEventListener(Events.TOUCH_END, this._endCallback);
|
|
709
|
+
element.removeEventListener(Events.TOUCH_DRAG, this._dragDrawCallback);
|
|
710
|
+
element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* it is used to draw the ellipticalROI annotation in each
|
|
715
|
+
* request animation frame. It calculates the updated cached statistics if
|
|
716
|
+
* data is invalidated and cache it.
|
|
717
|
+
*
|
|
718
|
+
* @param enabledElement - The Cornerstone's enabledElement.
|
|
719
|
+
* @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
|
|
720
|
+
*/
|
|
721
|
+
renderAnnotation = (
|
|
722
|
+
enabledElement: Types.IEnabledElement,
|
|
723
|
+
svgDrawingHelper: SVGDrawingHelper
|
|
724
|
+
): boolean => {
|
|
725
|
+
let renderStatus = false;
|
|
726
|
+
const { viewport } = enabledElement;
|
|
727
|
+
const { element } = viewport;
|
|
728
|
+
|
|
729
|
+
let annotations = getAnnotations(this.getToolName(), element);
|
|
730
|
+
|
|
731
|
+
if (!annotations?.length) {
|
|
732
|
+
return renderStatus;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
annotations = this.filterInteractableAnnotationsForElement(
|
|
736
|
+
element,
|
|
737
|
+
annotations
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
if (!annotations?.length) {
|
|
741
|
+
return renderStatus;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const targetId = this.getTargetId(viewport);
|
|
745
|
+
|
|
746
|
+
const renderingEngine = viewport.getRenderingEngine();
|
|
747
|
+
|
|
748
|
+
const styleSpecifier: StyleSpecifier = {
|
|
749
|
+
toolGroupId: this.toolGroupId,
|
|
750
|
+
toolName: this.getToolName(),
|
|
751
|
+
viewportId: enabledElement.viewport.id,
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
755
|
+
const annotation = annotations[i] as EllipticalROIAnnotation;
|
|
756
|
+
const { annotationUID, data } = annotation;
|
|
757
|
+
const { handles } = data;
|
|
758
|
+
const { points, activeHandleIndex } = handles;
|
|
759
|
+
|
|
760
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
761
|
+
|
|
762
|
+
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
|
763
|
+
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
|
764
|
+
const color = this.getStyle('color', styleSpecifier, annotation);
|
|
765
|
+
|
|
766
|
+
const canvasCoordinates = points.map((p) =>
|
|
767
|
+
viewport.worldToCanvas(p)
|
|
768
|
+
) as [Types.Point2, Types.Point2, Types.Point2, Types.Point2];
|
|
769
|
+
|
|
770
|
+
const rotation = Math.abs(
|
|
771
|
+
viewport.getRotation() - (data.initialRotation || 0)
|
|
772
|
+
);
|
|
773
|
+
let canvasCorners;
|
|
774
|
+
|
|
775
|
+
if (rotation == 90 || rotation == 270) {
|
|
776
|
+
canvasCorners = <Array<Types.Point2>>getCanvasEllipseCorners([
|
|
777
|
+
canvasCoordinates[2], // bottom
|
|
778
|
+
canvasCoordinates[3], // top
|
|
779
|
+
canvasCoordinates[0], // left
|
|
780
|
+
canvasCoordinates[1], // right
|
|
781
|
+
]);
|
|
782
|
+
} else {
|
|
783
|
+
canvasCorners = <Array<Types.Point2>>(
|
|
784
|
+
getCanvasEllipseCorners(canvasCoordinates) // bottom, top, left, right, keep as is
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const { centerPointRadius } = this.configuration;
|
|
789
|
+
|
|
790
|
+
// If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
|
|
791
|
+
// force to recalculate the stats from the points
|
|
792
|
+
if (
|
|
793
|
+
!data.cachedStats[targetId] ||
|
|
794
|
+
data.cachedStats[targetId].areaUnit === undefined
|
|
795
|
+
) {
|
|
796
|
+
data.cachedStats[targetId] = {
|
|
797
|
+
Modality: null,
|
|
798
|
+
area: null,
|
|
799
|
+
max: null,
|
|
800
|
+
mean: null,
|
|
801
|
+
stdDev: null,
|
|
802
|
+
areaUnit: null,
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
this._calculateCachedStats(
|
|
806
|
+
annotation,
|
|
807
|
+
viewport,
|
|
808
|
+
renderingEngine,
|
|
809
|
+
enabledElement
|
|
810
|
+
);
|
|
811
|
+
} else if (annotation.invalidated) {
|
|
812
|
+
this._throttledCalculateCachedStats(
|
|
813
|
+
annotation,
|
|
814
|
+
viewport,
|
|
815
|
+
renderingEngine,
|
|
816
|
+
enabledElement
|
|
817
|
+
);
|
|
818
|
+
// If the invalidated data is as a result of volumeViewport manipulation
|
|
819
|
+
// of the tools, we need to invalidate the related viewports data, so that
|
|
820
|
+
// when scrolling to the related slice in which the tool were manipulated
|
|
821
|
+
// we re-render the correct tool position. This is due to stackViewport
|
|
822
|
+
// which doesn't have the full volume at each time, and we are only working
|
|
823
|
+
// on one slice at a time.
|
|
824
|
+
if (viewport instanceof VolumeViewport) {
|
|
825
|
+
const { referencedImageId } = annotation.metadata;
|
|
826
|
+
|
|
827
|
+
// invalidate all the relevant stackViewports if they are not
|
|
828
|
+
// at the referencedImageId
|
|
829
|
+
for (const targetId in data.cachedStats) {
|
|
830
|
+
if (targetId.startsWith('imageId')) {
|
|
831
|
+
const viewports = renderingEngine.getStackViewports();
|
|
832
|
+
|
|
833
|
+
const invalidatedStack = viewports.find((vp) => {
|
|
834
|
+
// The stack viewport that contains the imageId but is not
|
|
835
|
+
// showing it currently
|
|
836
|
+
const referencedImageURI =
|
|
837
|
+
csUtils.imageIdToURI(referencedImageId);
|
|
838
|
+
const hasImageURI = vp.hasImageURI(referencedImageURI);
|
|
839
|
+
const currentImageURI = csUtils.imageIdToURI(
|
|
840
|
+
vp.getCurrentImageId()
|
|
841
|
+
);
|
|
842
|
+
return hasImageURI && currentImageURI !== referencedImageURI;
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
if (invalidatedStack) {
|
|
846
|
+
delete data.cachedStats[targetId];
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// If rendering engine has been destroyed while rendering
|
|
854
|
+
if (!viewport.getRenderingEngine()) {
|
|
855
|
+
console.warn('Rendering Engine has been destroyed');
|
|
856
|
+
return renderStatus;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
let activeHandleCanvasCoords;
|
|
860
|
+
|
|
861
|
+
if (!isAnnotationVisible(annotationUID)) {
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (
|
|
866
|
+
!isAnnotationLocked(annotation) &&
|
|
867
|
+
!this.editData &&
|
|
868
|
+
activeHandleIndex !== null
|
|
869
|
+
) {
|
|
870
|
+
// Not locked or creating and hovering over handle, so render handle.
|
|
871
|
+
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (activeHandleCanvasCoords) {
|
|
875
|
+
const handleGroupUID = '0';
|
|
876
|
+
drawHandlesSvg(
|
|
877
|
+
svgDrawingHelper,
|
|
878
|
+
annotationUID,
|
|
879
|
+
handleGroupUID,
|
|
880
|
+
activeHandleCanvasCoords,
|
|
881
|
+
{
|
|
882
|
+
color,
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const dataId = `${annotationUID}-ellipse`;
|
|
888
|
+
const ellipseUID = '0';
|
|
889
|
+
drawEllipseSvg(
|
|
890
|
+
svgDrawingHelper,
|
|
891
|
+
annotationUID,
|
|
892
|
+
ellipseUID,
|
|
893
|
+
canvasCorners[0],
|
|
894
|
+
canvasCorners[1],
|
|
895
|
+
{
|
|
896
|
+
color,
|
|
897
|
+
lineDash,
|
|
898
|
+
lineWidth,
|
|
899
|
+
},
|
|
900
|
+
dataId
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
// draw center point, if "centerPointRadius" configuration is valid.
|
|
904
|
+
if (centerPointRadius > 0) {
|
|
905
|
+
const minRadius = Math.min(
|
|
906
|
+
Math.abs(canvasCorners[0][0] - canvasCorners[1][0]) / 2, // horizontal radius
|
|
907
|
+
Math.abs(canvasCorners[0][1] - canvasCorners[1][1]) / 2 // vertical radius
|
|
908
|
+
);
|
|
909
|
+
if (minRadius > 3 * centerPointRadius) {
|
|
910
|
+
const centerPoint = this._getCanvasEllipseCenter(canvasCoordinates);
|
|
911
|
+
drawCircleSvg(
|
|
912
|
+
svgDrawingHelper,
|
|
913
|
+
annotationUID,
|
|
914
|
+
`${ellipseUID}-center`,
|
|
915
|
+
centerPoint,
|
|
916
|
+
centerPointRadius,
|
|
917
|
+
{
|
|
918
|
+
color,
|
|
919
|
+
lineDash,
|
|
920
|
+
lineWidth,
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
renderStatus = true;
|
|
927
|
+
|
|
928
|
+
const isPreScaled = isViewportPreScaled(viewport, targetId);
|
|
929
|
+
|
|
930
|
+
const textLines = this._getTextLines(data, targetId, isPreScaled);
|
|
931
|
+
if (!textLines || textLines.length === 0) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Poor man's cached?
|
|
936
|
+
let canvasTextBoxCoords;
|
|
937
|
+
|
|
938
|
+
if (!data.handles.textBox.hasMoved) {
|
|
939
|
+
canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCorners);
|
|
940
|
+
|
|
941
|
+
data.handles.textBox.worldPosition =
|
|
942
|
+
viewport.canvasToWorld(canvasTextBoxCoords);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const textBoxPosition = viewport.worldToCanvas(
|
|
946
|
+
data.handles.textBox.worldPosition
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
const textBoxUID = '1';
|
|
950
|
+
const boundingBox = drawLinkedTextBoxSvg(
|
|
951
|
+
svgDrawingHelper,
|
|
952
|
+
annotationUID,
|
|
953
|
+
textBoxUID,
|
|
954
|
+
textLines,
|
|
955
|
+
textBoxPosition,
|
|
956
|
+
canvasCoordinates,
|
|
957
|
+
{},
|
|
958
|
+
this.getLinkedTextBoxStyle(styleSpecifier, annotation)
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
const { x: left, y: top, width, height } = boundingBox;
|
|
962
|
+
|
|
963
|
+
data.handles.textBox.worldBoundingBox = {
|
|
964
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
965
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
966
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
967
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return renderStatus;
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
_getTextLines = (data, targetId: string, isPreScaled: boolean): string[] => {
|
|
975
|
+
const cachedVolumeStats = data.cachedStats[targetId];
|
|
976
|
+
const { area, mean, stdDev, max, isEmptyArea, Modality, areaUnit } =
|
|
977
|
+
cachedVolumeStats;
|
|
978
|
+
|
|
979
|
+
const textLines: string[] = [];
|
|
980
|
+
const unit = getModalityUnit(Modality, isPreScaled);
|
|
981
|
+
|
|
982
|
+
if (area) {
|
|
983
|
+
const areaLine = isEmptyArea
|
|
984
|
+
? `Area: Oblique not supported`
|
|
985
|
+
: `Area: ${area.toFixed(2)} ${areaUnit}\xb2`;
|
|
986
|
+
textLines.push(areaLine);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (mean) {
|
|
990
|
+
textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (max) {
|
|
994
|
+
textLines.push(`Max: ${max.toFixed(2)} ${unit}`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (stdDev) {
|
|
998
|
+
textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return textLines;
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
_calculateCachedStats = (
|
|
1005
|
+
annotation,
|
|
1006
|
+
viewport,
|
|
1007
|
+
renderingEngine,
|
|
1008
|
+
enabledElement
|
|
1009
|
+
) => {
|
|
1010
|
+
const data = annotation.data;
|
|
1011
|
+
const { viewportId, renderingEngineId } = enabledElement;
|
|
1012
|
+
|
|
1013
|
+
const { points } = data.handles;
|
|
1014
|
+
|
|
1015
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
1016
|
+
const { viewPlaneNormal, viewUp } = viewport.getCamera();
|
|
1017
|
+
|
|
1018
|
+
const [topLeftCanvas, bottomRightCanvas] = <Array<Types.Point2>>(
|
|
1019
|
+
getCanvasEllipseCorners(canvasCoordinates)
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
|
|
1023
|
+
const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
|
|
1024
|
+
const { cachedStats } = data;
|
|
1025
|
+
|
|
1026
|
+
const targetIds = Object.keys(cachedStats);
|
|
1027
|
+
const worldPos1 = topLeftWorld;
|
|
1028
|
+
const worldPos2 = bottomRightWorld;
|
|
1029
|
+
|
|
1030
|
+
for (let i = 0; i < targetIds.length; i++) {
|
|
1031
|
+
const targetId = targetIds[i];
|
|
1032
|
+
|
|
1033
|
+
const image = this.getTargetIdImage(targetId, renderingEngine);
|
|
1034
|
+
|
|
1035
|
+
// If image does not exists for the targetId, skip. This can be due
|
|
1036
|
+
// to various reasons such as if the target was a volumeViewport, and
|
|
1037
|
+
// the volumeViewport has been decached in the meantime.
|
|
1038
|
+
if (!image) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const { dimensions, imageData, metadata, hasPixelSpacing } = image;
|
|
1043
|
+
|
|
1044
|
+
const worldPos1Index = transformWorldToIndex(imageData, worldPos1);
|
|
1045
|
+
|
|
1046
|
+
worldPos1Index[0] = Math.floor(worldPos1Index[0]);
|
|
1047
|
+
worldPos1Index[1] = Math.floor(worldPos1Index[1]);
|
|
1048
|
+
worldPos1Index[2] = Math.floor(worldPos1Index[2]);
|
|
1049
|
+
|
|
1050
|
+
const worldPos2Index = transformWorldToIndex(imageData, worldPos2);
|
|
1051
|
+
|
|
1052
|
+
worldPos2Index[0] = Math.floor(worldPos2Index[0]);
|
|
1053
|
+
worldPos2Index[1] = Math.floor(worldPos2Index[1]);
|
|
1054
|
+
worldPos2Index[2] = Math.floor(worldPos2Index[2]);
|
|
1055
|
+
|
|
1056
|
+
// Check if one of the indexes are inside the volume, this then gives us
|
|
1057
|
+
// Some area to do stats over.
|
|
1058
|
+
|
|
1059
|
+
if (this._isInsideVolume(worldPos1Index, worldPos2Index, dimensions)) {
|
|
1060
|
+
const iMin = Math.min(worldPos1Index[0], worldPos2Index[0]);
|
|
1061
|
+
const iMax = Math.max(worldPos1Index[0], worldPos2Index[0]);
|
|
1062
|
+
|
|
1063
|
+
const jMin = Math.min(worldPos1Index[1], worldPos2Index[1]);
|
|
1064
|
+
const jMax = Math.max(worldPos1Index[1], worldPos2Index[1]);
|
|
1065
|
+
|
|
1066
|
+
const kMin = Math.min(worldPos1Index[2], worldPos2Index[2]);
|
|
1067
|
+
const kMax = Math.max(worldPos1Index[2], worldPos2Index[2]);
|
|
1068
|
+
|
|
1069
|
+
const boundsIJK = [
|
|
1070
|
+
[iMin, iMax],
|
|
1071
|
+
[jMin, jMax],
|
|
1072
|
+
[kMin, kMax],
|
|
1073
|
+
] as [Types.Point2, Types.Point2, Types.Point2];
|
|
1074
|
+
|
|
1075
|
+
const center = [
|
|
1076
|
+
(topLeftWorld[0] + bottomRightWorld[0]) / 2,
|
|
1077
|
+
(topLeftWorld[1] + bottomRightWorld[1]) / 2,
|
|
1078
|
+
(topLeftWorld[2] + bottomRightWorld[2]) / 2,
|
|
1079
|
+
] as Types.Point3;
|
|
1080
|
+
|
|
1081
|
+
const ellipseObj = {
|
|
1082
|
+
center,
|
|
1083
|
+
xRadius: Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2,
|
|
1084
|
+
yRadius: Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2,
|
|
1085
|
+
zRadius: Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2,
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(
|
|
1089
|
+
viewPlaneNormal,
|
|
1090
|
+
viewUp,
|
|
1091
|
+
worldPos1,
|
|
1092
|
+
worldPos2
|
|
1093
|
+
);
|
|
1094
|
+
const isEmptyArea = worldWidth === 0 && worldHeight === 0;
|
|
1095
|
+
const area = Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2));
|
|
1096
|
+
|
|
1097
|
+
let count = 0;
|
|
1098
|
+
let mean = 0;
|
|
1099
|
+
let stdDev = 0;
|
|
1100
|
+
let max = -Infinity;
|
|
1101
|
+
|
|
1102
|
+
const meanMaxCalculator = ({ value: newValue }) => {
|
|
1103
|
+
if (newValue > max) {
|
|
1104
|
+
max = newValue;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
mean += newValue;
|
|
1108
|
+
count += 1;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
pointInShapeCallback(
|
|
1112
|
+
imageData,
|
|
1113
|
+
(pointLPS, pointIJK) => pointInEllipse(ellipseObj, pointLPS),
|
|
1114
|
+
meanMaxCalculator,
|
|
1115
|
+
boundsIJK
|
|
1116
|
+
);
|
|
1117
|
+
|
|
1118
|
+
mean /= count;
|
|
1119
|
+
|
|
1120
|
+
const stdCalculator = ({ value }) => {
|
|
1121
|
+
const valueMinusMean = value - mean;
|
|
1122
|
+
|
|
1123
|
+
stdDev += valueMinusMean * valueMinusMean;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
pointInShapeCallback(
|
|
1127
|
+
imageData,
|
|
1128
|
+
(pointLPS, pointIJK) => pointInEllipse(ellipseObj, pointLPS),
|
|
1129
|
+
stdCalculator,
|
|
1130
|
+
boundsIJK
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
stdDev /= count;
|
|
1134
|
+
stdDev = Math.sqrt(stdDev);
|
|
1135
|
+
|
|
1136
|
+
cachedStats[targetId] = {
|
|
1137
|
+
Modality: metadata.Modality,
|
|
1138
|
+
area,
|
|
1139
|
+
mean,
|
|
1140
|
+
max,
|
|
1141
|
+
stdDev,
|
|
1142
|
+
isEmptyArea,
|
|
1143
|
+
areaUnit: hasPixelSpacing ? 'mm' : 'px',
|
|
1144
|
+
};
|
|
1145
|
+
} else {
|
|
1146
|
+
this.isHandleOutsideImage = true;
|
|
1147
|
+
|
|
1148
|
+
cachedStats[targetId] = {
|
|
1149
|
+
Modality: metadata.Modality,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
annotation.invalidated = false;
|
|
1155
|
+
|
|
1156
|
+
// Dispatching annotation modified
|
|
1157
|
+
const eventType = Events.ANNOTATION_MODIFIED;
|
|
1158
|
+
|
|
1159
|
+
const eventDetail: AnnotationModifiedEventDetail = {
|
|
1160
|
+
annotation,
|
|
1161
|
+
viewportId,
|
|
1162
|
+
renderingEngineId,
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
1166
|
+
|
|
1167
|
+
return cachedStats;
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
_isInsideVolume = (index1, index2, dimensions) => {
|
|
1171
|
+
return (
|
|
1172
|
+
csUtils.indexWithinDimensions(index1, dimensions) &&
|
|
1173
|
+
csUtils.indexWithinDimensions(index2, dimensions)
|
|
1174
|
+
);
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* This is a temporary function to use the old ellipse's canvas-based
|
|
1179
|
+
* calculation for isPointNearTool, we should move the the world-based
|
|
1180
|
+
* calculation to the tool's isPointNearTool function.
|
|
1181
|
+
*
|
|
1182
|
+
* @param ellipse - The ellipse object
|
|
1183
|
+
* @param location - The location to check
|
|
1184
|
+
* @returns True if the point is inside the ellipse
|
|
1185
|
+
*/
|
|
1186
|
+
_pointInEllipseCanvas(ellipse, location: Types.Point2): boolean {
|
|
1187
|
+
const xRadius = ellipse.width / 2;
|
|
1188
|
+
const yRadius = ellipse.height / 2;
|
|
1189
|
+
|
|
1190
|
+
if (xRadius <= 0.0 || yRadius <= 0.0) {
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const center = [ellipse.left + xRadius, ellipse.top + yRadius];
|
|
1195
|
+
const normalized = [location[0] - center[0], location[1] - center[1]];
|
|
1196
|
+
|
|
1197
|
+
const inEllipse =
|
|
1198
|
+
(normalized[0] * normalized[0]) / (xRadius * xRadius) +
|
|
1199
|
+
(normalized[1] * normalized[1]) / (yRadius * yRadius) <=
|
|
1200
|
+
1.0;
|
|
1201
|
+
|
|
1202
|
+
return inEllipse;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* It takes the canvas coordinates of the ellipse corners and returns the center point of it
|
|
1207
|
+
*
|
|
1208
|
+
* @param ellipseCanvasPoints - The coordinates of the ellipse in the canvas.
|
|
1209
|
+
* @returns center point.
|
|
1210
|
+
*/
|
|
1211
|
+
_getCanvasEllipseCenter(ellipseCanvasPoints: Types.Point2[]): Types.Point2 {
|
|
1212
|
+
const [bottom, top, left, right] = ellipseCanvasPoints;
|
|
1213
|
+
const topLeft = [left[0], top[1]];
|
|
1214
|
+
const bottomRight = [right[0], bottom[1]];
|
|
1215
|
+
return [
|
|
1216
|
+
(topLeft[0] + bottomRight[0]) / 2,
|
|
1217
|
+
(topLeft[1] + bottomRight[1]) / 2,
|
|
1218
|
+
] as Types.Point2;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
EllipticalROITool.toolName = 'EllipticalROI';
|
|
1223
|
+
export default EllipticalROITool;
|