@cornerstonejs/tools 0.56.2 → 0.56.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,1070 @@
|
|
|
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
|
+
drawHandles as drawHandlesSvg,
|
|
23
|
+
drawLinkedTextBox as drawLinkedTextBoxSvg,
|
|
24
|
+
} from '../../drawingSvg';
|
|
25
|
+
import { state } from '../../store';
|
|
26
|
+
import { Events } from '../../enums';
|
|
27
|
+
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
28
|
+
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
29
|
+
import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorldWidthAndHeightFromTwoPoints';
|
|
30
|
+
import {
|
|
31
|
+
resetElementCursor,
|
|
32
|
+
hideElementCursor,
|
|
33
|
+
} from '../../cursors/elementCursor';
|
|
34
|
+
import {
|
|
35
|
+
EventTypes,
|
|
36
|
+
ToolHandle,
|
|
37
|
+
TextBoxHandle,
|
|
38
|
+
PublicToolProps,
|
|
39
|
+
ToolProps,
|
|
40
|
+
InteractionTypes,
|
|
41
|
+
SVGDrawingHelper,
|
|
42
|
+
} from '../../types';
|
|
43
|
+
import { CircleROIAnnotation } from '../../types/ToolSpecificAnnotationTypes';
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
AnnotationCompletedEventDetail,
|
|
47
|
+
AnnotationModifiedEventDetail,
|
|
48
|
+
MouseDragEventType,
|
|
49
|
+
MouseMoveEventType,
|
|
50
|
+
} from '../../types/EventTypes';
|
|
51
|
+
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
52
|
+
import { pointInShapeCallback } from '../../utilities';
|
|
53
|
+
import { StyleSpecifier } from '../../types/AnnotationStyle';
|
|
54
|
+
import { getModalityUnit } from '../../utilities/getModalityUnit';
|
|
55
|
+
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
56
|
+
import {
|
|
57
|
+
getCanvasCircleCorners,
|
|
58
|
+
getCanvasCircleRadius,
|
|
59
|
+
} from '../../utilities/math/circle';
|
|
60
|
+
import { pointInEllipse } from '../../utilities/math/ellipse';
|
|
61
|
+
|
|
62
|
+
const { transformWorldToIndex } = csUtils;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* CircleROITool 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 CircleROITool 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. Circle 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(CircleROITool)
|
|
87
|
+
*
|
|
88
|
+
* const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
|
|
89
|
+
*
|
|
90
|
+
* toolGroup.addTool(CircleROITool.toolName)
|
|
91
|
+
*
|
|
92
|
+
* toolGroup.addViewport('viewportId', 'renderingEngineId')
|
|
93
|
+
*
|
|
94
|
+
* toolGroup.setToolActive(CircleROITool.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(CircleROITool.toolName, {
|
|
104
|
+
* centerPointRadius: 4,
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Read more in the Docs section of the website.
|
|
109
|
+
*/
|
|
110
|
+
class CircleROITool 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
|
+
newAnnotation?: boolean;
|
|
121
|
+
hasMoved?: boolean;
|
|
122
|
+
} | null;
|
|
123
|
+
isDrawing: boolean;
|
|
124
|
+
isHandleOutsideImage = false;
|
|
125
|
+
|
|
126
|
+
constructor(
|
|
127
|
+
toolProps: PublicToolProps = {},
|
|
128
|
+
defaultToolProps: ToolProps = {
|
|
129
|
+
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
130
|
+
configuration: {
|
|
131
|
+
shadow: true,
|
|
132
|
+
preventHandleOutsideImage: false,
|
|
133
|
+
// Radius of the circle to draw at the center point of the circle.
|
|
134
|
+
// Set this zero(0) in order not to draw the circle.
|
|
135
|
+
centerPointRadius: 0,
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
) {
|
|
139
|
+
super(toolProps, defaultToolProps);
|
|
140
|
+
|
|
141
|
+
this._throttledCalculateCachedStats = throttle(
|
|
142
|
+
this._calculateCachedStats,
|
|
143
|
+
100,
|
|
144
|
+
{ trailing: true }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Based on the current position of the mouse and the current imageId to create
|
|
150
|
+
* a CircleROI Annotation and stores it in the annotationManager
|
|
151
|
+
*
|
|
152
|
+
* @param evt - EventTypes.NormalizedMouseEventType
|
|
153
|
+
* @returns The annotation object.
|
|
154
|
+
*
|
|
155
|
+
*/
|
|
156
|
+
addNewAnnotation = (
|
|
157
|
+
evt: EventTypes.InteractionEventType
|
|
158
|
+
): CircleROIAnnotation => {
|
|
159
|
+
const eventDetail = evt.detail;
|
|
160
|
+
const { currentPoints, element } = eventDetail;
|
|
161
|
+
const worldPos = currentPoints.world;
|
|
162
|
+
const canvasPos = currentPoints.canvas;
|
|
163
|
+
|
|
164
|
+
const enabledElement = getEnabledElement(element);
|
|
165
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
166
|
+
|
|
167
|
+
this.isDrawing = true;
|
|
168
|
+
|
|
169
|
+
const camera = viewport.getCamera();
|
|
170
|
+
const { viewPlaneNormal, viewUp } = camera;
|
|
171
|
+
|
|
172
|
+
const referencedImageId = this.getReferencedImageId(
|
|
173
|
+
viewport,
|
|
174
|
+
worldPos,
|
|
175
|
+
viewPlaneNormal,
|
|
176
|
+
viewUp
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
|
180
|
+
|
|
181
|
+
const annotation = {
|
|
182
|
+
highlighted: true,
|
|
183
|
+
invalidated: true,
|
|
184
|
+
metadata: {
|
|
185
|
+
toolName: this.getToolName(),
|
|
186
|
+
viewPlaneNormal: <Types.Point3>[...viewPlaneNormal],
|
|
187
|
+
viewUp: <Types.Point3>[...viewUp],
|
|
188
|
+
FrameOfReferenceUID,
|
|
189
|
+
referencedImageId,
|
|
190
|
+
},
|
|
191
|
+
data: {
|
|
192
|
+
label: '',
|
|
193
|
+
handles: {
|
|
194
|
+
textBox: {
|
|
195
|
+
hasMoved: false,
|
|
196
|
+
worldPosition: <Types.Point3>[0, 0, 0],
|
|
197
|
+
worldBoundingBox: {
|
|
198
|
+
topLeft: <Types.Point3>[0, 0, 0],
|
|
199
|
+
topRight: <Types.Point3>[0, 0, 0],
|
|
200
|
+
bottomLeft: <Types.Point3>[0, 0, 0],
|
|
201
|
+
bottomRight: <Types.Point3>[0, 0, 0],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
points: [[...worldPos], [...worldPos]] as [
|
|
205
|
+
Types.Point3, // center
|
|
206
|
+
Types.Point3 // end
|
|
207
|
+
],
|
|
208
|
+
activeHandleIndex: null,
|
|
209
|
+
},
|
|
210
|
+
cachedStats: {},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
addAnnotation(annotation, element);
|
|
215
|
+
|
|
216
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
217
|
+
element,
|
|
218
|
+
this.getToolName()
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
this.editData = {
|
|
222
|
+
annotation,
|
|
223
|
+
viewportIdsToRender,
|
|
224
|
+
newAnnotation: true,
|
|
225
|
+
hasMoved: false,
|
|
226
|
+
};
|
|
227
|
+
this._activateDraw(element);
|
|
228
|
+
|
|
229
|
+
hideElementCursor(element);
|
|
230
|
+
|
|
231
|
+
evt.preventDefault();
|
|
232
|
+
|
|
233
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
234
|
+
|
|
235
|
+
return annotation;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* It returns if the canvas point is near the provided annotation in the provided
|
|
240
|
+
* element or not. A proximity is passed to the function to determine the
|
|
241
|
+
* proximity of the point to the annotation in number of pixels.
|
|
242
|
+
*
|
|
243
|
+
* @param element - HTML Element
|
|
244
|
+
* @param annotation - Annotation
|
|
245
|
+
* @param canvasCoords - Canvas coordinates
|
|
246
|
+
* @param proximity - Proximity to tool to consider
|
|
247
|
+
* @returns Boolean, whether the canvas point is near tool
|
|
248
|
+
*/
|
|
249
|
+
isPointNearTool = (
|
|
250
|
+
element: HTMLDivElement,
|
|
251
|
+
annotation: CircleROIAnnotation,
|
|
252
|
+
canvasCoords: Types.Point2,
|
|
253
|
+
proximity: number
|
|
254
|
+
): boolean => {
|
|
255
|
+
const enabledElement = getEnabledElement(element);
|
|
256
|
+
const { viewport } = enabledElement;
|
|
257
|
+
|
|
258
|
+
const { data } = annotation;
|
|
259
|
+
const { points } = data.handles;
|
|
260
|
+
|
|
261
|
+
// For some reason Typescript doesn't understand this, so we need to be
|
|
262
|
+
// more specific about the type
|
|
263
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p)) as [
|
|
264
|
+
Types.Point2,
|
|
265
|
+
Types.Point2
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const radius = getCanvasCircleRadius(canvasCoordinates);
|
|
269
|
+
const radiusPoint = getCanvasCircleRadius([
|
|
270
|
+
canvasCoordinates[0],
|
|
271
|
+
canvasCoords,
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
if (Math.abs(radiusPoint - radius) < proximity / 2) return true;
|
|
275
|
+
|
|
276
|
+
return false;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
toolSelectedCallback = (
|
|
280
|
+
evt: EventTypes.InteractionEventType,
|
|
281
|
+
annotation: CircleROIAnnotation
|
|
282
|
+
): void => {
|
|
283
|
+
const eventDetail = evt.detail;
|
|
284
|
+
const { element } = eventDetail;
|
|
285
|
+
|
|
286
|
+
annotation.highlighted = true;
|
|
287
|
+
|
|
288
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
289
|
+
element,
|
|
290
|
+
this.getToolName()
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
this.editData = {
|
|
294
|
+
annotation,
|
|
295
|
+
viewportIdsToRender,
|
|
296
|
+
movingTextBox: false,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
hideElementCursor(element);
|
|
300
|
+
|
|
301
|
+
this._activateModify(element);
|
|
302
|
+
|
|
303
|
+
const enabledElement = getEnabledElement(element);
|
|
304
|
+
const { renderingEngine } = enabledElement;
|
|
305
|
+
|
|
306
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
307
|
+
|
|
308
|
+
evt.preventDefault();
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
handleSelectedCallback = (
|
|
312
|
+
evt: EventTypes.InteractionEventType,
|
|
313
|
+
annotation: CircleROIAnnotation,
|
|
314
|
+
handle: ToolHandle
|
|
315
|
+
): void => {
|
|
316
|
+
const eventDetail = evt.detail;
|
|
317
|
+
const { element } = eventDetail;
|
|
318
|
+
const { data } = annotation;
|
|
319
|
+
|
|
320
|
+
annotation.highlighted = true;
|
|
321
|
+
|
|
322
|
+
let movingTextBox = false;
|
|
323
|
+
let handleIndex;
|
|
324
|
+
|
|
325
|
+
if ((handle as TextBoxHandle).worldPosition) {
|
|
326
|
+
movingTextBox = true;
|
|
327
|
+
} else {
|
|
328
|
+
const { points } = data.handles;
|
|
329
|
+
|
|
330
|
+
handleIndex = points.findIndex((p) => p === handle);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Find viewports to render on drag.
|
|
334
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
335
|
+
element,
|
|
336
|
+
this.getToolName()
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
this.editData = {
|
|
340
|
+
annotation,
|
|
341
|
+
viewportIdsToRender,
|
|
342
|
+
handleIndex,
|
|
343
|
+
movingTextBox,
|
|
344
|
+
};
|
|
345
|
+
this._activateModify(element);
|
|
346
|
+
|
|
347
|
+
hideElementCursor(element);
|
|
348
|
+
|
|
349
|
+
const enabledElement = getEnabledElement(element);
|
|
350
|
+
const { renderingEngine } = enabledElement;
|
|
351
|
+
|
|
352
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
353
|
+
|
|
354
|
+
evt.preventDefault();
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
_endCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
358
|
+
const eventDetail = evt.detail;
|
|
359
|
+
const { element } = eventDetail;
|
|
360
|
+
|
|
361
|
+
const { annotation, viewportIdsToRender, newAnnotation, hasMoved } =
|
|
362
|
+
this.editData;
|
|
363
|
+
const { data } = annotation;
|
|
364
|
+
|
|
365
|
+
if (newAnnotation && !hasMoved) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Circle ROI tool should reset its highlight to false on mouse up (as opposed
|
|
370
|
+
// to other tools that keep it highlighted until the user moves. The reason
|
|
371
|
+
// is that we use top-left and bottom-right handles to define the circle,
|
|
372
|
+
// and they are by definition not in the circle on mouse up.
|
|
373
|
+
annotation.highlighted = false;
|
|
374
|
+
data.handles.activeHandleIndex = null;
|
|
375
|
+
|
|
376
|
+
this._deactivateModify(element);
|
|
377
|
+
this._deactivateDraw(element);
|
|
378
|
+
|
|
379
|
+
resetElementCursor(element);
|
|
380
|
+
|
|
381
|
+
const enabledElement = getEnabledElement(element);
|
|
382
|
+
const { renderingEngine } = enabledElement;
|
|
383
|
+
|
|
384
|
+
this.editData = null;
|
|
385
|
+
this.isDrawing = false;
|
|
386
|
+
|
|
387
|
+
if (
|
|
388
|
+
this.isHandleOutsideImage &&
|
|
389
|
+
this.configuration.preventHandleOutsideImage
|
|
390
|
+
) {
|
|
391
|
+
removeAnnotation(annotation.annotationUID);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
395
|
+
|
|
396
|
+
if (newAnnotation) {
|
|
397
|
+
const eventType = Events.ANNOTATION_COMPLETED;
|
|
398
|
+
|
|
399
|
+
const eventDetail: AnnotationCompletedEventDetail = {
|
|
400
|
+
annotation,
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
_dragDrawCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
408
|
+
this.isDrawing = true;
|
|
409
|
+
const eventDetail = evt.detail;
|
|
410
|
+
const { element } = eventDetail;
|
|
411
|
+
const { currentPoints } = eventDetail;
|
|
412
|
+
const currentCanvasPoints = currentPoints.canvas;
|
|
413
|
+
const enabledElement = getEnabledElement(element);
|
|
414
|
+
const { renderingEngine, viewport } = enabledElement;
|
|
415
|
+
const { canvasToWorld } = viewport;
|
|
416
|
+
|
|
417
|
+
//////
|
|
418
|
+
const { annotation, viewportIdsToRender } = this.editData;
|
|
419
|
+
const { data } = annotation;
|
|
420
|
+
|
|
421
|
+
data.handles.points = [
|
|
422
|
+
data.handles.points[0], // center stays
|
|
423
|
+
canvasToWorld(currentCanvasPoints), // end point moves (changing radius)
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
annotation.invalidated = true;
|
|
427
|
+
|
|
428
|
+
this.editData.hasMoved = true;
|
|
429
|
+
|
|
430
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
_dragModifyCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
434
|
+
this.isDrawing = true;
|
|
435
|
+
const eventDetail = evt.detail;
|
|
436
|
+
const { element } = eventDetail;
|
|
437
|
+
|
|
438
|
+
const { annotation, viewportIdsToRender, handleIndex, movingTextBox } =
|
|
439
|
+
this.editData;
|
|
440
|
+
const { data } = annotation;
|
|
441
|
+
|
|
442
|
+
if (movingTextBox) {
|
|
443
|
+
const { deltaPoints } = eventDetail;
|
|
444
|
+
const worldPosDelta = deltaPoints.world;
|
|
445
|
+
|
|
446
|
+
const { textBox } = data.handles;
|
|
447
|
+
const { worldPosition } = textBox;
|
|
448
|
+
|
|
449
|
+
worldPosition[0] += worldPosDelta[0];
|
|
450
|
+
worldPosition[1] += worldPosDelta[1];
|
|
451
|
+
worldPosition[2] += worldPosDelta[2];
|
|
452
|
+
|
|
453
|
+
textBox.hasMoved = true;
|
|
454
|
+
} else if (handleIndex === undefined) {
|
|
455
|
+
// Moving tool
|
|
456
|
+
const { deltaPoints } = eventDetail;
|
|
457
|
+
const worldPosDelta = deltaPoints.world;
|
|
458
|
+
|
|
459
|
+
const points = data.handles.points;
|
|
460
|
+
|
|
461
|
+
points.forEach((point) => {
|
|
462
|
+
point[0] += worldPosDelta[0];
|
|
463
|
+
point[1] += worldPosDelta[1];
|
|
464
|
+
point[2] += worldPosDelta[2];
|
|
465
|
+
});
|
|
466
|
+
annotation.invalidated = true;
|
|
467
|
+
} else {
|
|
468
|
+
this._dragHandle(evt);
|
|
469
|
+
annotation.invalidated = true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const enabledElement = getEnabledElement(element);
|
|
473
|
+
const { renderingEngine } = enabledElement;
|
|
474
|
+
|
|
475
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
_dragHandle = (evt: EventTypes.InteractionEventType): void => {
|
|
479
|
+
const eventDetail = evt.detail;
|
|
480
|
+
const { element } = eventDetail;
|
|
481
|
+
const enabledElement = getEnabledElement(element);
|
|
482
|
+
const { canvasToWorld, worldToCanvas } = enabledElement.viewport;
|
|
483
|
+
|
|
484
|
+
const { annotation, handleIndex } = this.editData;
|
|
485
|
+
const { data } = annotation;
|
|
486
|
+
const { points } = data.handles;
|
|
487
|
+
|
|
488
|
+
const canvasCoordinates = points.map((p) => worldToCanvas(p));
|
|
489
|
+
|
|
490
|
+
// Move current point in that direction.
|
|
491
|
+
// Move other points in opposite direction.
|
|
492
|
+
|
|
493
|
+
const { currentPoints } = eventDetail;
|
|
494
|
+
const currentCanvasPoints = currentPoints.canvas;
|
|
495
|
+
|
|
496
|
+
if (handleIndex === 0) {
|
|
497
|
+
// Dragging center, move the circle ROI
|
|
498
|
+
const dXCanvas = currentCanvasPoints[0] - canvasCoordinates[0][0];
|
|
499
|
+
const dYCanvas = currentCanvasPoints[1] - canvasCoordinates[0][1];
|
|
500
|
+
|
|
501
|
+
const canvasCenter = currentCanvasPoints as Types.Point2;
|
|
502
|
+
const canvasEnd = <Types.Point2>[
|
|
503
|
+
canvasCoordinates[1][0] + dXCanvas,
|
|
504
|
+
canvasCoordinates[1][1] + dYCanvas,
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
points[0] = canvasToWorld(canvasCenter);
|
|
508
|
+
points[1] = canvasToWorld(canvasEnd);
|
|
509
|
+
} else {
|
|
510
|
+
// Dragging end point, center stays
|
|
511
|
+
points[1] = canvasToWorld(currentCanvasPoints);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
cancel = (element: HTMLDivElement) => {
|
|
516
|
+
// If it is mid-draw or mid-modify
|
|
517
|
+
if (this.isDrawing) {
|
|
518
|
+
this.isDrawing = false;
|
|
519
|
+
this._deactivateDraw(element);
|
|
520
|
+
this._deactivateModify(element);
|
|
521
|
+
resetElementCursor(element);
|
|
522
|
+
|
|
523
|
+
const { annotation, viewportIdsToRender, newAnnotation } = this.editData;
|
|
524
|
+
const { data } = annotation;
|
|
525
|
+
|
|
526
|
+
annotation.highlighted = false;
|
|
527
|
+
data.handles.activeHandleIndex = null;
|
|
528
|
+
|
|
529
|
+
const enabledElement = getEnabledElement(element);
|
|
530
|
+
const { renderingEngine } = enabledElement;
|
|
531
|
+
|
|
532
|
+
triggerAnnotationRenderForViewportIds(
|
|
533
|
+
renderingEngine,
|
|
534
|
+
viewportIdsToRender
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
if (newAnnotation) {
|
|
538
|
+
const eventType = Events.ANNOTATION_COMPLETED;
|
|
539
|
+
|
|
540
|
+
const eventDetail: AnnotationCompletedEventDetail = {
|
|
541
|
+
annotation,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
this.editData = null;
|
|
548
|
+
return annotation.annotationUID;
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
_activateModify = (element) => {
|
|
553
|
+
state.isInteractingWithTool = true;
|
|
554
|
+
|
|
555
|
+
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
556
|
+
element.addEventListener(Events.MOUSE_DRAG, this._dragModifyCallback);
|
|
557
|
+
element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
558
|
+
|
|
559
|
+
element.addEventListener(Events.TOUCH_END, this._endCallback);
|
|
560
|
+
element.addEventListener(Events.TOUCH_DRAG, this._dragModifyCallback);
|
|
561
|
+
element.addEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
_deactivateModify = (element) => {
|
|
565
|
+
state.isInteractingWithTool = false;
|
|
566
|
+
|
|
567
|
+
element.removeEventListener(Events.MOUSE_UP, this._endCallback);
|
|
568
|
+
element.removeEventListener(Events.MOUSE_DRAG, this._dragModifyCallback);
|
|
569
|
+
element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
570
|
+
|
|
571
|
+
element.removeEventListener(Events.TOUCH_END, this._endCallback);
|
|
572
|
+
element.removeEventListener(Events.TOUCH_DRAG, this._dragModifyCallback);
|
|
573
|
+
element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
_activateDraw = (element) => {
|
|
577
|
+
state.isInteractingWithTool = true;
|
|
578
|
+
|
|
579
|
+
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
580
|
+
element.addEventListener(Events.MOUSE_DRAG, this._dragDrawCallback);
|
|
581
|
+
element.addEventListener(Events.MOUSE_MOVE, this._dragDrawCallback);
|
|
582
|
+
element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
583
|
+
|
|
584
|
+
element.addEventListener(Events.TOUCH_END, this._endCallback);
|
|
585
|
+
element.addEventListener(Events.TOUCH_DRAG, this._dragDrawCallback);
|
|
586
|
+
element.addEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
_deactivateDraw = (element) => {
|
|
590
|
+
state.isInteractingWithTool = false;
|
|
591
|
+
|
|
592
|
+
element.removeEventListener(Events.MOUSE_UP, this._endCallback);
|
|
593
|
+
element.removeEventListener(Events.MOUSE_DRAG, this._dragDrawCallback);
|
|
594
|
+
element.removeEventListener(Events.MOUSE_MOVE, this._dragDrawCallback);
|
|
595
|
+
element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
596
|
+
|
|
597
|
+
element.removeEventListener(Events.TOUCH_END, this._endCallback);
|
|
598
|
+
element.removeEventListener(Events.TOUCH_DRAG, this._dragDrawCallback);
|
|
599
|
+
element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* it is used to draw the circleROI annotation in each
|
|
604
|
+
* request animation frame. It calculates the updated cached statistics if
|
|
605
|
+
* data is invalidated and cache it.
|
|
606
|
+
*
|
|
607
|
+
* @param enabledElement - The Cornerstone's enabledElement.
|
|
608
|
+
* @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
|
|
609
|
+
*/
|
|
610
|
+
renderAnnotation = (
|
|
611
|
+
enabledElement: Types.IEnabledElement,
|
|
612
|
+
svgDrawingHelper: SVGDrawingHelper
|
|
613
|
+
): boolean => {
|
|
614
|
+
let renderStatus = false;
|
|
615
|
+
const { viewport } = enabledElement;
|
|
616
|
+
const { element } = viewport;
|
|
617
|
+
|
|
618
|
+
let annotations = getAnnotations(this.getToolName(), element);
|
|
619
|
+
|
|
620
|
+
if (!annotations?.length) {
|
|
621
|
+
return renderStatus;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
annotations = this.filterInteractableAnnotationsForElement(
|
|
625
|
+
element,
|
|
626
|
+
annotations
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
if (!annotations?.length) {
|
|
630
|
+
return renderStatus;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const targetId = this.getTargetId(viewport);
|
|
634
|
+
|
|
635
|
+
const renderingEngine = viewport.getRenderingEngine();
|
|
636
|
+
|
|
637
|
+
const styleSpecifier: StyleSpecifier = {
|
|
638
|
+
toolGroupId: this.toolGroupId,
|
|
639
|
+
toolName: this.getToolName(),
|
|
640
|
+
viewportId: enabledElement.viewport.id,
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
644
|
+
const annotation = annotations[i] as CircleROIAnnotation;
|
|
645
|
+
const { annotationUID, data } = annotation;
|
|
646
|
+
const { handles } = data;
|
|
647
|
+
const { points, activeHandleIndex } = handles;
|
|
648
|
+
|
|
649
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
650
|
+
|
|
651
|
+
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
|
652
|
+
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
|
653
|
+
const color = this.getStyle('color', styleSpecifier, annotation);
|
|
654
|
+
|
|
655
|
+
const canvasCoordinates = points.map((p) =>
|
|
656
|
+
viewport.worldToCanvas(p)
|
|
657
|
+
) as [Types.Point2, Types.Point2];
|
|
658
|
+
const center = canvasCoordinates[0];
|
|
659
|
+
const radius = getCanvasCircleRadius(canvasCoordinates);
|
|
660
|
+
const canvasCorners = getCanvasCircleCorners(canvasCoordinates);
|
|
661
|
+
|
|
662
|
+
const { centerPointRadius } = this.configuration;
|
|
663
|
+
|
|
664
|
+
// If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
|
|
665
|
+
// force to recalculate the stats from the points
|
|
666
|
+
if (
|
|
667
|
+
!data.cachedStats[targetId] ||
|
|
668
|
+
data.cachedStats[targetId].areaUnit === undefined
|
|
669
|
+
) {
|
|
670
|
+
data.cachedStats[targetId] = {
|
|
671
|
+
Modality: null,
|
|
672
|
+
area: null,
|
|
673
|
+
max: null,
|
|
674
|
+
mean: null,
|
|
675
|
+
stdDev: null,
|
|
676
|
+
areaUnit: null,
|
|
677
|
+
radius: null,
|
|
678
|
+
radiusUnit: null,
|
|
679
|
+
perimeter: null,
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
this._calculateCachedStats(
|
|
683
|
+
annotation,
|
|
684
|
+
viewport,
|
|
685
|
+
renderingEngine,
|
|
686
|
+
enabledElement
|
|
687
|
+
);
|
|
688
|
+
} else if (annotation.invalidated) {
|
|
689
|
+
this._throttledCalculateCachedStats(
|
|
690
|
+
annotation,
|
|
691
|
+
viewport,
|
|
692
|
+
renderingEngine,
|
|
693
|
+
enabledElement
|
|
694
|
+
);
|
|
695
|
+
// If the invalidated data is as a result of volumeViewport manipulation
|
|
696
|
+
// of the tools, we need to invalidate the related viewports data, so that
|
|
697
|
+
// when scrolling to the related slice in which the tool were manipulated
|
|
698
|
+
// we re-render the correct tool position. This is due to stackViewport
|
|
699
|
+
// which doesn't have the full volume at each time, and we are only working
|
|
700
|
+
// on one slice at a time.
|
|
701
|
+
if (viewport instanceof VolumeViewport) {
|
|
702
|
+
const { referencedImageId } = annotation.metadata;
|
|
703
|
+
|
|
704
|
+
// invalidate all the relevant stackViewports if they are not
|
|
705
|
+
// at the referencedImageId
|
|
706
|
+
for (const targetId in data.cachedStats) {
|
|
707
|
+
if (targetId.startsWith('imageId')) {
|
|
708
|
+
const viewports = renderingEngine.getStackViewports();
|
|
709
|
+
|
|
710
|
+
const invalidatedStack = viewports.find((vp) => {
|
|
711
|
+
// The stack viewport that contains the imageId but is not
|
|
712
|
+
// showing it currently
|
|
713
|
+
const referencedImageURI =
|
|
714
|
+
csUtils.imageIdToURI(referencedImageId);
|
|
715
|
+
const hasImageURI = vp.hasImageURI(referencedImageURI);
|
|
716
|
+
const currentImageURI = csUtils.imageIdToURI(
|
|
717
|
+
vp.getCurrentImageId()
|
|
718
|
+
);
|
|
719
|
+
return hasImageURI && currentImageURI !== referencedImageURI;
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
if (invalidatedStack) {
|
|
723
|
+
delete data.cachedStats[targetId];
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// If rendering engine has been destroyed while rendering
|
|
731
|
+
if (!viewport.getRenderingEngine()) {
|
|
732
|
+
console.warn('Rendering Engine has been destroyed');
|
|
733
|
+
return renderStatus;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
let activeHandleCanvasCoords;
|
|
737
|
+
|
|
738
|
+
if (!isAnnotationVisible(annotationUID)) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (
|
|
743
|
+
!isAnnotationLocked(annotation) &&
|
|
744
|
+
!this.editData &&
|
|
745
|
+
activeHandleIndex !== null
|
|
746
|
+
) {
|
|
747
|
+
// Not locked or creating and hovering over handle, so render handle.
|
|
748
|
+
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (activeHandleCanvasCoords) {
|
|
752
|
+
const handleGroupUID = '0';
|
|
753
|
+
drawHandlesSvg(
|
|
754
|
+
svgDrawingHelper,
|
|
755
|
+
annotationUID,
|
|
756
|
+
handleGroupUID,
|
|
757
|
+
activeHandleCanvasCoords,
|
|
758
|
+
{
|
|
759
|
+
color,
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const dataId = `${annotationUID}-circle`;
|
|
765
|
+
const circleUID = '0';
|
|
766
|
+
drawCircleSvg(
|
|
767
|
+
svgDrawingHelper,
|
|
768
|
+
annotationUID,
|
|
769
|
+
circleUID,
|
|
770
|
+
center,
|
|
771
|
+
radius,
|
|
772
|
+
{
|
|
773
|
+
color,
|
|
774
|
+
lineDash,
|
|
775
|
+
lineWidth,
|
|
776
|
+
},
|
|
777
|
+
dataId
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
// draw center point, if "centerPointRadius" configuration is valid.
|
|
781
|
+
if (centerPointRadius > 0) {
|
|
782
|
+
if (radius > 3 * centerPointRadius) {
|
|
783
|
+
drawCircleSvg(
|
|
784
|
+
svgDrawingHelper,
|
|
785
|
+
annotationUID,
|
|
786
|
+
`${circleUID}-center`,
|
|
787
|
+
center,
|
|
788
|
+
centerPointRadius,
|
|
789
|
+
{
|
|
790
|
+
color,
|
|
791
|
+
lineDash,
|
|
792
|
+
lineWidth,
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
renderStatus = true;
|
|
799
|
+
|
|
800
|
+
const isPreScaled = isViewportPreScaled(viewport, targetId);
|
|
801
|
+
|
|
802
|
+
const textLines = this._getTextLines(data, targetId, isPreScaled);
|
|
803
|
+
if (!textLines || textLines.length === 0) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Poor man's cached?
|
|
808
|
+
let canvasTextBoxCoords;
|
|
809
|
+
|
|
810
|
+
if (!data.handles.textBox.hasMoved) {
|
|
811
|
+
canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCorners);
|
|
812
|
+
|
|
813
|
+
data.handles.textBox.worldPosition =
|
|
814
|
+
viewport.canvasToWorld(canvasTextBoxCoords);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const textBoxPosition = viewport.worldToCanvas(
|
|
818
|
+
data.handles.textBox.worldPosition
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
const textBoxUID = '1';
|
|
822
|
+
const boundingBox = drawLinkedTextBoxSvg(
|
|
823
|
+
svgDrawingHelper,
|
|
824
|
+
annotationUID,
|
|
825
|
+
textBoxUID,
|
|
826
|
+
textLines,
|
|
827
|
+
textBoxPosition,
|
|
828
|
+
canvasCoordinates,
|
|
829
|
+
{},
|
|
830
|
+
this.getLinkedTextBoxStyle(styleSpecifier, annotation)
|
|
831
|
+
);
|
|
832
|
+
|
|
833
|
+
const { x: left, y: top, width, height } = boundingBox;
|
|
834
|
+
|
|
835
|
+
data.handles.textBox.worldBoundingBox = {
|
|
836
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
837
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
838
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
839
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return renderStatus;
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
_getTextLines = (data, targetId: string, isPreScaled: boolean): string[] => {
|
|
847
|
+
const cachedVolumeStats = data.cachedStats[targetId];
|
|
848
|
+
const {
|
|
849
|
+
radius,
|
|
850
|
+
radiusUnit,
|
|
851
|
+
area,
|
|
852
|
+
mean,
|
|
853
|
+
stdDev,
|
|
854
|
+
max,
|
|
855
|
+
isEmptyArea,
|
|
856
|
+
Modality,
|
|
857
|
+
areaUnit,
|
|
858
|
+
} = cachedVolumeStats;
|
|
859
|
+
|
|
860
|
+
const textLines: string[] = [];
|
|
861
|
+
const unit = getModalityUnit(Modality, isPreScaled);
|
|
862
|
+
|
|
863
|
+
if (radius) {
|
|
864
|
+
const radiusLine = isEmptyArea
|
|
865
|
+
? `Radius: Oblique not supported`
|
|
866
|
+
: `Radius: ${radius.toFixed(2)} ${radiusUnit}`;
|
|
867
|
+
textLines.push(radiusLine);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (area) {
|
|
871
|
+
const areaLine = isEmptyArea
|
|
872
|
+
? `Area: Oblique not supported`
|
|
873
|
+
: `Area: ${area.toFixed(2)} ${areaUnit}\xb2`;
|
|
874
|
+
textLines.push(areaLine);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (mean) {
|
|
878
|
+
textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (max) {
|
|
882
|
+
textLines.push(`Max: ${max.toFixed(2)} ${unit}`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (stdDev) {
|
|
886
|
+
textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return textLines;
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
_calculateCachedStats = (
|
|
893
|
+
annotation,
|
|
894
|
+
viewport,
|
|
895
|
+
renderingEngine,
|
|
896
|
+
enabledElement
|
|
897
|
+
) => {
|
|
898
|
+
const data = annotation.data;
|
|
899
|
+
const { viewportId, renderingEngineId } = enabledElement;
|
|
900
|
+
|
|
901
|
+
const { points } = data.handles;
|
|
902
|
+
|
|
903
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
904
|
+
const { viewPlaneNormal, viewUp } = viewport.getCamera();
|
|
905
|
+
|
|
906
|
+
const [topLeftCanvas, bottomRightCanvas] = <Array<Types.Point2>>(
|
|
907
|
+
getCanvasCircleCorners(canvasCoordinates)
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
|
|
911
|
+
const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
|
|
912
|
+
const { cachedStats } = data;
|
|
913
|
+
|
|
914
|
+
const targetIds = Object.keys(cachedStats);
|
|
915
|
+
const worldPos1 = topLeftWorld;
|
|
916
|
+
const worldPos2 = bottomRightWorld;
|
|
917
|
+
|
|
918
|
+
for (let i = 0; i < targetIds.length; i++) {
|
|
919
|
+
const targetId = targetIds[i];
|
|
920
|
+
|
|
921
|
+
const image = this.getTargetIdImage(targetId, renderingEngine);
|
|
922
|
+
|
|
923
|
+
// If image does not exists for the targetId, skip. This can be due
|
|
924
|
+
// to various reasons such as if the target was a volumeViewport, and
|
|
925
|
+
// the volumeViewport has been decached in the meantime.
|
|
926
|
+
if (!image) {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const { dimensions, imageData, metadata, hasPixelSpacing } = image;
|
|
931
|
+
|
|
932
|
+
const worldPos1Index = transformWorldToIndex(imageData, worldPos1);
|
|
933
|
+
|
|
934
|
+
worldPos1Index[0] = Math.floor(worldPos1Index[0]);
|
|
935
|
+
worldPos1Index[1] = Math.floor(worldPos1Index[1]);
|
|
936
|
+
worldPos1Index[2] = Math.floor(worldPos1Index[2]);
|
|
937
|
+
|
|
938
|
+
const worldPos2Index = transformWorldToIndex(imageData, worldPos2);
|
|
939
|
+
|
|
940
|
+
worldPos2Index[0] = Math.floor(worldPos2Index[0]);
|
|
941
|
+
worldPos2Index[1] = Math.floor(worldPos2Index[1]);
|
|
942
|
+
worldPos2Index[2] = Math.floor(worldPos2Index[2]);
|
|
943
|
+
|
|
944
|
+
// Check if one of the indexes are inside the volume, this then gives us
|
|
945
|
+
// Some area to do stats over.
|
|
946
|
+
|
|
947
|
+
if (this._isInsideVolume(worldPos1Index, worldPos2Index, dimensions)) {
|
|
948
|
+
const iMin = Math.min(worldPos1Index[0], worldPos2Index[0]);
|
|
949
|
+
const iMax = Math.max(worldPos1Index[0], worldPos2Index[0]);
|
|
950
|
+
|
|
951
|
+
const jMin = Math.min(worldPos1Index[1], worldPos2Index[1]);
|
|
952
|
+
const jMax = Math.max(worldPos1Index[1], worldPos2Index[1]);
|
|
953
|
+
|
|
954
|
+
const kMin = Math.min(worldPos1Index[2], worldPos2Index[2]);
|
|
955
|
+
const kMax = Math.max(worldPos1Index[2], worldPos2Index[2]);
|
|
956
|
+
|
|
957
|
+
const boundsIJK = [
|
|
958
|
+
[iMin, iMax],
|
|
959
|
+
[jMin, jMax],
|
|
960
|
+
[kMin, kMax],
|
|
961
|
+
] as [Types.Point2, Types.Point2, Types.Point2];
|
|
962
|
+
|
|
963
|
+
const center = [
|
|
964
|
+
(topLeftWorld[0] + bottomRightWorld[0]) / 2,
|
|
965
|
+
(topLeftWorld[1] + bottomRightWorld[1]) / 2,
|
|
966
|
+
(topLeftWorld[2] + bottomRightWorld[2]) / 2,
|
|
967
|
+
] as Types.Point3;
|
|
968
|
+
|
|
969
|
+
const ellipseObj = {
|
|
970
|
+
center,
|
|
971
|
+
xRadius: Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2,
|
|
972
|
+
yRadius: Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2,
|
|
973
|
+
zRadius: Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2,
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(
|
|
977
|
+
viewPlaneNormal,
|
|
978
|
+
viewUp,
|
|
979
|
+
worldPos1,
|
|
980
|
+
worldPos2
|
|
981
|
+
);
|
|
982
|
+
const isEmptyArea = worldWidth === 0 && worldHeight === 0;
|
|
983
|
+
const area = Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2));
|
|
984
|
+
|
|
985
|
+
let count = 0;
|
|
986
|
+
let mean = 0;
|
|
987
|
+
let stdDev = 0;
|
|
988
|
+
let max = -Infinity;
|
|
989
|
+
|
|
990
|
+
const meanMaxCalculator = ({ value: newValue }) => {
|
|
991
|
+
if (newValue > max) {
|
|
992
|
+
max = newValue;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
mean += newValue;
|
|
996
|
+
count += 1;
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
pointInShapeCallback(
|
|
1000
|
+
imageData,
|
|
1001
|
+
(pointLPS, pointIJK) => pointInEllipse(ellipseObj, pointLPS),
|
|
1002
|
+
meanMaxCalculator,
|
|
1003
|
+
boundsIJK
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
mean /= count;
|
|
1007
|
+
|
|
1008
|
+
const stdCalculator = ({ value }) => {
|
|
1009
|
+
const valueMinusMean = value - mean;
|
|
1010
|
+
|
|
1011
|
+
stdDev += valueMinusMean * valueMinusMean;
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
pointInShapeCallback(
|
|
1015
|
+
imageData,
|
|
1016
|
+
(pointLPS, pointIJK) => pointInEllipse(ellipseObj, pointLPS),
|
|
1017
|
+
stdCalculator,
|
|
1018
|
+
boundsIJK
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
stdDev /= count;
|
|
1022
|
+
stdDev = Math.sqrt(stdDev);
|
|
1023
|
+
|
|
1024
|
+
cachedStats[targetId] = {
|
|
1025
|
+
Modality: metadata.Modality,
|
|
1026
|
+
area,
|
|
1027
|
+
mean,
|
|
1028
|
+
max,
|
|
1029
|
+
stdDev,
|
|
1030
|
+
isEmptyArea,
|
|
1031
|
+
areaUnit: hasPixelSpacing ? 'mm' : 'px',
|
|
1032
|
+
radius: worldWidth / 2,
|
|
1033
|
+
radiusUnit: hasPixelSpacing ? 'mm' : 'px',
|
|
1034
|
+
perimeter: 2 * Math.PI * (worldWidth / 2),
|
|
1035
|
+
};
|
|
1036
|
+
} else {
|
|
1037
|
+
this.isHandleOutsideImage = true;
|
|
1038
|
+
|
|
1039
|
+
cachedStats[targetId] = {
|
|
1040
|
+
Modality: metadata.Modality,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
annotation.invalidated = false;
|
|
1046
|
+
|
|
1047
|
+
// Dispatching annotation modified
|
|
1048
|
+
const eventType = Events.ANNOTATION_MODIFIED;
|
|
1049
|
+
|
|
1050
|
+
const eventDetail: AnnotationModifiedEventDetail = {
|
|
1051
|
+
annotation,
|
|
1052
|
+
viewportId,
|
|
1053
|
+
renderingEngineId,
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
1057
|
+
|
|
1058
|
+
return cachedStats;
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
_isInsideVolume = (index1, index2, dimensions) => {
|
|
1062
|
+
return (
|
|
1063
|
+
csUtils.indexWithinDimensions(index1, dimensions) &&
|
|
1064
|
+
csUtils.indexWithinDimensions(index2, dimensions)
|
|
1065
|
+
);
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
CircleROITool.toolName = 'CircleROI';
|
|
1070
|
+
export default CircleROITool;
|