406 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
	
			
		
		
	
	
			406 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
	
/* eslint no-alert: 0 */
 | 
						|
import * as cornerstoneTools from 'cornerstone-tools'
 | 
						|
const external = cornerstoneTools.external
 | 
						|
const EVENTS = cornerstoneTools.EVENTS
 | 
						|
 | 
						|
const toolStyle = cornerstoneTools.toolStyle
 | 
						|
const textStyle = cornerstoneTools.textStyle
 | 
						|
const toolColors = cornerstoneTools.toolColors
 | 
						|
 | 
						|
const pointInsideBoundingBox = cornerstoneTools.import('util/pointInsideBoundingBox')
 | 
						|
const lineSegDistance = cornerstoneTools.import('util/lineSegDistance')
 | 
						|
const triggerEvent = cornerstoneTools.import('util/triggerEvent')
 | 
						|
const moveNewHandle = cornerstoneTools.import('manipulators/moveNewHandle')
 | 
						|
 | 
						|
const addToolState = cornerstoneTools.addToolState
 | 
						|
const getToolState = cornerstoneTools.getToolState
 | 
						|
const removeToolState = cornerstoneTools.removeToolState
 | 
						|
 | 
						|
// Drawing
 | 
						|
const drawLinkedTextBox = cornerstoneTools.import('drawing/drawLinkedTextBox')
 | 
						|
const getNewContext = cornerstoneTools.import('drawing/getNewContext')
 | 
						|
const draw = cornerstoneTools.import('drawing/draw')
 | 
						|
// import textBoxWidth from './textBoxWidth'
 | 
						|
const setShadow = cornerstoneTools.import('drawing/setShadow')
 | 
						|
const drawArrow = cornerstoneTools.import('drawing/drawArrow')
 | 
						|
const drawHandles = cornerstoneTools.import('drawing/drawHandles')
 | 
						|
const { arrowAnnotateCursor } = cornerstoneTools.import('tools/cursors')
 | 
						|
const getModule = cornerstoneTools.getModule
 | 
						|
 | 
						|
/**
 | 
						|
 * @public
 | 
						|
 * @class ArrowAnnotateTool
 | 
						|
 * @memberof Tools.Annotation
 | 
						|
 * @classdesc Create and position an arrow and label
 | 
						|
 * @extends Tools.Base.BaseAnnotationTool
 | 
						|
 */
 | 
						|
export default class ArrowAnnotateTool extends cornerstoneTools.ArrowAnnotateTool {
 | 
						|
  constructor(props = {}) {
 | 
						|
    const defaultProps = {
 | 
						|
      name: 'ArrowAnnotate',
 | 
						|
      supportedInteractionTypes: ['Mouse', 'Touch'],
 | 
						|
      configuration: {
 | 
						|
        getTextCallback,
 | 
						|
        changeTextCallback,
 | 
						|
        drawHandles: false,
 | 
						|
        drawHandlesOnHover: true,
 | 
						|
        hideHandlesIfMoving: false,
 | 
						|
        arrowFirst: true,
 | 
						|
        renderDashed: false,
 | 
						|
        allowEmptyLabel: false
 | 
						|
      },
 | 
						|
      svgCursor: arrowAnnotateCursor
 | 
						|
    }
 | 
						|
 | 
						|
    super(props, defaultProps)
 | 
						|
    this.preventNewMeasurement = false
 | 
						|
  }
 | 
						|
  textBoxWidth(context, text, padding) {
 | 
						|
    const font = textStyle.getFont()
 | 
						|
    const origFont = context.font
 | 
						|
 | 
						|
    if (font && font !== origFont) {
 | 
						|
      context.font = font
 | 
						|
    }
 | 
						|
    const width = context.measureText(text).width
 | 
						|
 | 
						|
    if (font && font !== origFont) {
 | 
						|
      context.font = origFont
 | 
						|
    }
 | 
						|
 | 
						|
    return width + 2 * padding
 | 
						|
  }
 | 
						|
 | 
						|
  createNewMeasurement(evt) {
 | 
						|
    // Create the measurement data for this tool with the end handle activated
 | 
						|
    return {
 | 
						|
      visible: true,
 | 
						|
      active: true,
 | 
						|
      color: undefined,
 | 
						|
      text: '',
 | 
						|
      handles: {
 | 
						|
        start: {
 | 
						|
          x: evt.detail.currentPoints.image.x,
 | 
						|
          y: evt.detail.currentPoints.image.y,
 | 
						|
          highlight: true,
 | 
						|
          active: false
 | 
						|
        },
 | 
						|
        end: {
 | 
						|
          x: evt.detail.currentPoints.image.x,
 | 
						|
          y: evt.detail.currentPoints.image.y,
 | 
						|
          highlight: true,
 | 
						|
          active: false
 | 
						|
        },
 | 
						|
        textBox: {
 | 
						|
          active: false,
 | 
						|
          hasMoved: false,
 | 
						|
          movesIndependently: false,
 | 
						|
          drawnIndependently: true,
 | 
						|
          allowedOutsideImage: true,
 | 
						|
          hasBoundingBox: true
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  pointNearTool(element, data, coords) {
 | 
						|
    if (data.visible === false) {
 | 
						|
      return false
 | 
						|
    }
 | 
						|
 | 
						|
    return (
 | 
						|
      lineSegDistance(element, data.handles.start, data.handles.end, coords) <
 | 
						|
      25
 | 
						|
    )
 | 
						|
  }
 | 
						|
 | 
						|
  updateCachedStats() {
 | 
						|
    // Implementing to satisfy BaseAnnotationTool
 | 
						|
  }
 | 
						|
 | 
						|
  renderToolData(evt) {
 | 
						|
    const { element, enabledElement } = evt.detail
 | 
						|
    const {
 | 
						|
      handleRadius,
 | 
						|
      drawHandlesOnHover,
 | 
						|
      hideHandlesIfMoving,
 | 
						|
      renderDashed
 | 
						|
    } = this.configuration
 | 
						|
 | 
						|
    // If we have no toolData for this element, return immediately as there is nothing to do
 | 
						|
    const toolData = getToolState(element, this.name)
 | 
						|
 | 
						|
    if (!toolData) {
 | 
						|
      return
 | 
						|
    }
 | 
						|
 | 
						|
    // We have tool data for this element - iterate over each one and draw it
 | 
						|
    const canvas = evt.detail.canvasContext.canvas
 | 
						|
    const context = getNewContext(canvas)
 | 
						|
 | 
						|
    const lineWidth = toolStyle.getToolWidth()
 | 
						|
 | 
						|
    let lineDash
 | 
						|
 | 
						|
    if (renderDashed) {
 | 
						|
      lineDash = getModule('globalConfiguration').configuration.lineDash
 | 
						|
    }
 | 
						|
 | 
						|
    for (let i = 0; i < toolData.data.length; i++) {
 | 
						|
      const data = toolData.data[i]
 | 
						|
 | 
						|
      if (data.visible === false) {
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      draw(context, context => {
 | 
						|
        setShadow(context, this.configuration)
 | 
						|
 | 
						|
        const color = toolColors.getColorIfActive(data)
 | 
						|
 | 
						|
        // Draw the arrow
 | 
						|
        const handleStartCanvas = external.cornerstone.pixelToCanvas(
 | 
						|
          element,
 | 
						|
          data.handles.start
 | 
						|
        )
 | 
						|
        const handleEndCanvas = external.cornerstone.pixelToCanvas(
 | 
						|
          element,
 | 
						|
          data.handles.end
 | 
						|
        )
 | 
						|
 | 
						|
        // Config.arrowFirst = false;
 | 
						|
        if (this.configuration.arrowFirst) {
 | 
						|
          drawArrow(
 | 
						|
            context,
 | 
						|
            handleEndCanvas,
 | 
						|
            handleStartCanvas,
 | 
						|
            color,
 | 
						|
            lineWidth,
 | 
						|
            lineDash
 | 
						|
          )
 | 
						|
        } else {
 | 
						|
          drawArrow(
 | 
						|
            context,
 | 
						|
            handleStartCanvas,
 | 
						|
            handleEndCanvas,
 | 
						|
            color,
 | 
						|
            lineWidth,
 | 
						|
            lineDash
 | 
						|
          )
 | 
						|
        }
 | 
						|
 | 
						|
        const handleOptions = {
 | 
						|
          color,
 | 
						|
          handleRadius,
 | 
						|
          drawHandlesIfActive: drawHandlesOnHover,
 | 
						|
          hideHandlesIfMoving
 | 
						|
        }
 | 
						|
        if (this.configuration.drawHandles) {
 | 
						|
          drawHandles(context, evt.detail, data.handles, handleOptions)
 | 
						|
        }
 | 
						|
        const text = []
 | 
						|
 | 
						|
        if (data.hasOwnProperty('remark')) {
 | 
						|
          text.push(data.remark)
 | 
						|
        }
 | 
						|
        text.push(textBoxText(data))
 | 
						|
 | 
						|
        // Draw the text
 | 
						|
        if (text && text !== '') {
 | 
						|
          // Calculate the text coordinates.
 | 
						|
          const padding = 5
 | 
						|
          const textWidth = this.textBoxWidth(context, text, padding)
 | 
						|
          const textHeight = textStyle.getFontSize() + 10
 | 
						|
 | 
						|
          let distance = Math.max(textWidth, textHeight) / 2 + 5
 | 
						|
 | 
						|
          if (handleEndCanvas.x < handleStartCanvas.x) {
 | 
						|
            distance = -distance
 | 
						|
          }
 | 
						|
 | 
						|
          if (!data.handles.textBox.hasMoved) {
 | 
						|
            let textCoords
 | 
						|
 | 
						|
            if (this.configuration.arrowFirst) {
 | 
						|
              textCoords = {
 | 
						|
                x: handleEndCanvas.x - textWidth / 2 + distance,
 | 
						|
                y: handleEndCanvas.y - textHeight / 2
 | 
						|
              }
 | 
						|
            } else {
 | 
						|
              // If the arrow is at the End position, the text should
 | 
						|
              // Be placed near the Start position
 | 
						|
              textCoords = {
 | 
						|
                x: handleStartCanvas.x - textWidth / 2 - distance,
 | 
						|
                y: handleStartCanvas.y - textHeight / 2
 | 
						|
              }
 | 
						|
            }
 | 
						|
 | 
						|
            const transform = external.cornerstone.internal.getTransform(
 | 
						|
              enabledElement
 | 
						|
            )
 | 
						|
 | 
						|
            transform.invert()
 | 
						|
 | 
						|
            const coords = transform.transformPoint(textCoords.x, textCoords.y)
 | 
						|
 | 
						|
            data.handles.textBox.x = coords.x
 | 
						|
            data.handles.textBox.y = coords.y
 | 
						|
          }
 | 
						|
 | 
						|
          drawLinkedTextBox(
 | 
						|
            context,
 | 
						|
            element,
 | 
						|
            data.handles.textBox,
 | 
						|
            text,
 | 
						|
            data.handles,
 | 
						|
            textBoxAnchorPoints,
 | 
						|
            color,
 | 
						|
            lineWidth,
 | 
						|
            0,
 | 
						|
            false
 | 
						|
          )
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
 | 
						|
    function textBoxText(data) {
 | 
						|
      return data.text
 | 
						|
    }
 | 
						|
 | 
						|
    function textBoxAnchorPoints(handles) {
 | 
						|
      const midpoint = {
 | 
						|
        x: (handles.start.x + handles.end.x) / 2,
 | 
						|
        y: (handles.start.y + handles.end.y) / 2
 | 
						|
      }
 | 
						|
 | 
						|
      return [handles.start, midpoint, handles.end]
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  addNewMeasurement(evt, interactionType) {
 | 
						|
    const element = evt.detail.element
 | 
						|
    const measurementData = this.createNewMeasurement(evt)
 | 
						|
 | 
						|
    const { allowEmptyLabel } = this.configuration
 | 
						|
 | 
						|
    // Associate this data with this imageId so we can render it and manipulate it
 | 
						|
    addToolState(element, this.name, measurementData)
 | 
						|
    external.cornerstone.updateImage(element)
 | 
						|
 | 
						|
    moveNewHandle(
 | 
						|
      evt.detail,
 | 
						|
      this.name,
 | 
						|
      measurementData,
 | 
						|
      measurementData.handles.end,
 | 
						|
      this.options,
 | 
						|
      interactionType,
 | 
						|
      success => {
 | 
						|
        if (success) {
 | 
						|
          if (!allowEmptyLabel && measurementData.text === undefined) {
 | 
						|
            this.configuration.getTextCallback(text => {
 | 
						|
              if (text || allowEmptyLabel) {
 | 
						|
                measurementData.text = text
 | 
						|
                measurementData.active = false
 | 
						|
 | 
						|
                const modifiedEventData = {
 | 
						|
                  toolName: this.name,
 | 
						|
                  toolType: this.name, // Deprecation notice: toolType will be replaced by toolName
 | 
						|
                  element,
 | 
						|
                  measurementData
 | 
						|
                }
 | 
						|
 | 
						|
                external.cornerstone.updateImage(element)
 | 
						|
                triggerEvent(
 | 
						|
                  element,
 | 
						|
                  EVENTS.MEASUREMENT_COMPLETED,
 | 
						|
                  modifiedEventData
 | 
						|
                )
 | 
						|
              } else {
 | 
						|
                removeToolState(element, this.name, measurementData)
 | 
						|
              }
 | 
						|
            }, evt.detail)
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          removeToolState(element, this.name, measurementData)
 | 
						|
        }
 | 
						|
        triggerEvent(element, EVENTS.MEASUREMENT_COMPLETED, {
 | 
						|
          toolName: this.name,
 | 
						|
          toolType: this.name, // Deprecation notice: toolType will be replaced by toolName
 | 
						|
          element,
 | 
						|
          measurementData
 | 
						|
        })
 | 
						|
        external.cornerstone.updateImage(element)
 | 
						|
      }
 | 
						|
    )
 | 
						|
  }
 | 
						|
 | 
						|
  doubleClickCallback(evt) {
 | 
						|
    return this._updateTextForNearbyAnnotation(evt)
 | 
						|
  }
 | 
						|
 | 
						|
  touchPressCallback(evt) {
 | 
						|
    return this._updateTextForNearbyAnnotation(evt)
 | 
						|
  }
 | 
						|
 | 
						|
  _updateTextForNearbyAnnotation(evt) {
 | 
						|
    const element = evt.detail.element
 | 
						|
    const coords = evt.detail.currentPoints.canvas
 | 
						|
    const toolState = getToolState(element, this.name)
 | 
						|
 | 
						|
    if (!toolState) {
 | 
						|
      return false
 | 
						|
    }
 | 
						|
 | 
						|
    for (let i = 0; i < toolState.data.length; i++) {
 | 
						|
      const data = toolState.data[i]
 | 
						|
 | 
						|
      if (
 | 
						|
        this.pointNearTool(element, data, coords) ||
 | 
						|
        pointInsideBoundingBox(data.handles.textBox, coords)
 | 
						|
      ) {
 | 
						|
        data.active = true
 | 
						|
        external.cornerstone.updateImage(element)
 | 
						|
 | 
						|
        // Allow relabelling via a callback
 | 
						|
        this.configuration.changeTextCallback(
 | 
						|
          data,
 | 
						|
          evt.detail,
 | 
						|
          this._doneChangingTextCallback.bind(this, element, data)
 | 
						|
        )
 | 
						|
 | 
						|
        evt.stopImmediatePropagation()
 | 
						|
        evt.preventDefault()
 | 
						|
        evt.stopPropagation()
 | 
						|
 | 
						|
        return true
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _doneChangingTextCallback(element, measurementData, updatedText, deleteTool) {
 | 
						|
    if (deleteTool === true) {
 | 
						|
      removeToolState(element, this.name, measurementData)
 | 
						|
    } else {
 | 
						|
      measurementData.text = updatedText
 | 
						|
    }
 | 
						|
 | 
						|
    measurementData.active = false
 | 
						|
    external.cornerstone.updateImage(element)
 | 
						|
 | 
						|
    triggerEvent(element, EVENTS.MEASUREMENT_MODIFIED, {
 | 
						|
      toolName: this.name,
 | 
						|
      toolType: this.name, // Deprecation notice: toolType will be replaced by toolName
 | 
						|
      element,
 | 
						|
      measurementData
 | 
						|
    })
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function getTextCallback(doneChangingTextCallback) {
 | 
						|
  doneChangingTextCallback(prompt('Enter your annotation:'))
 | 
						|
}
 | 
						|
 | 
						|
function changeTextCallback(data, eventData, doneChangingTextCallback) {
 | 
						|
  doneChangingTextCallback(prompt('Change your annotation:'))
 | 
						|
}
 |