609 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
			
		
		
	
	
			609 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
import {
 | 
						|
  eventTarget,
 | 
						|
  triggerEvent,
 | 
						|
  VolumeViewport,
 | 
						|
  utilities as csUtils
 | 
						|
} from '@cornerstonejs/core'
 | 
						|
 | 
						|
const { transformWorldToIndex } = csUtils
 | 
						|
import * as cornerstoneTools from '@cornerstonejs/tools'
 | 
						|
const {
 | 
						|
  Enums,
 | 
						|
  utilities,
 | 
						|
  annotation,
 | 
						|
  drawing
 | 
						|
 | 
						|
} = cornerstoneTools
 | 
						|
 | 
						|
// const { getWorldWidthAndHeightFromTwoPoints } = utilities.planar
 | 
						|
// const { roundNumber } = utilities
 | 
						|
 | 
						|
const { getAnnotations } = annotation.state
 | 
						|
const { isAnnotationVisible } = annotation.visibility
 | 
						|
const { isAnnotationLocked } = annotation.locking
 | 
						|
const drawHandlesSvg = drawing.drawHandles
 | 
						|
const drawCircleSvg = drawing.drawCircle
 | 
						|
const drawLinkedTextBoxSvg = drawing.drawLinkedTextBox
 | 
						|
const { getTextBoxCoordsCanvas } = utilities.drawing
 | 
						|
console.log(utilities)
 | 
						|
// const getCanvasCircleRadius
 | 
						|
// const getCanvasCircleCorners
 | 
						|
// const { getCanvasCircleRadius, getCanvasCircleCorners } = utilities.math.circle
 | 
						|
import {
 | 
						|
  getCalibratedLengthUnits,
 | 
						|
  getCalibratedAreaUnits,
 | 
						|
  getCalibratedScale,
 | 
						|
  getCalibratedAspect
 | 
						|
} from './../js/getCalibratedUnits'
 | 
						|
import { getModalityUnit } from './../js/getModalityUnit'
 | 
						|
import { pointInShapeCallback } from './../js/pointInShapeCallback'
 | 
						|
import { vec3 } from 'gl-matrix'
 | 
						|
class CircleROITool extends cornerstoneTools.CircleROITool {
 | 
						|
  static toolName;
 | 
						|
  // touchDragCallback: any;
 | 
						|
  // mouseDragCallback: any;
 | 
						|
  // _throttledCalculateCachedStats: any;
 | 
						|
  // editData: {
 | 
						|
  //   annotation: any;
 | 
						|
  //   viewportIdsToRender: Array<string>;
 | 
						|
  //   handleIndex?: number;
 | 
						|
  //   movingTextBox?: boolean;
 | 
						|
  //   newAnnotation?: boolean;
 | 
						|
  //   hasMoved?: boolean;
 | 
						|
  // } | null;
 | 
						|
  // isDrawing: boolean;
 | 
						|
  // isHandleOutsideImage = false;
 | 
						|
 | 
						|
  constructor(
 | 
						|
    toolProps,
 | 
						|
    defaultToolProps
 | 
						|
  ) {
 | 
						|
    super(toolProps, defaultToolProps)
 | 
						|
 | 
						|
    this._throttledCalculateCachedStats = utilities.throttle(
 | 
						|
      this._calculateCachedStats,
 | 
						|
      100,
 | 
						|
      { trailing: true }
 | 
						|
    )
 | 
						|
 | 
						|
    // this._throttledCalculateCachedStats = utilities.throttle(
 | 
						|
    //   this._calculateCachedStats,
 | 
						|
    //   100,
 | 
						|
    //   { trailing: true }
 | 
						|
    // )
 | 
						|
    // this._getTextLines = this.getTextLines
 | 
						|
  }
 | 
						|
  renderAnnotation = (enabledElement, svgDrawingHelper) => {
 | 
						|
    let renderStatus = false
 | 
						|
    const { viewport } = enabledElement
 | 
						|
    const { element } = viewport
 | 
						|
    // console.log(element)
 | 
						|
    let annotations = getAnnotations(this.getToolName(), element)
 | 
						|
 | 
						|
    if (!annotations || (annotations && annotations.length === 0)) {
 | 
						|
      return renderStatus
 | 
						|
    }
 | 
						|
 | 
						|
    annotations = this.filterInteractableAnnotationsForElement(
 | 
						|
      element,
 | 
						|
      annotations
 | 
						|
    )
 | 
						|
 | 
						|
    if (!annotations || (annotations && annotations.length === 0)) {
 | 
						|
      return renderStatus
 | 
						|
    }
 | 
						|
 | 
						|
    const targetId = this.getTargetId(viewport)
 | 
						|
 | 
						|
    const renderingEngine = viewport.getRenderingEngine()
 | 
						|
 | 
						|
    const styleSpecifier = {
 | 
						|
      toolGroupId: this.toolGroupId,
 | 
						|
      toolName: this.getToolName(),
 | 
						|
      viewportId: enabledElement.viewport.id
 | 
						|
    }
 | 
						|
 | 
						|
    for (let i = 0; i < annotations.length; i++) {
 | 
						|
      const annotation = annotations[i]
 | 
						|
      const { annotationUID, data } = annotation
 | 
						|
      const { handles } = data
 | 
						|
      const { points, activeHandleIndex } = handles
 | 
						|
 | 
						|
      styleSpecifier.annotationUID = annotationUID
 | 
						|
 | 
						|
      const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation)
 | 
						|
      const lineDash = this.getStyle('lineDash', styleSpecifier, annotation)
 | 
						|
      const color = this.getStyle('color', styleSpecifier, annotation)
 | 
						|
 | 
						|
      const canvasCoordinates = points.map((p) =>
 | 
						|
        viewport.worldToCanvas(p)
 | 
						|
      )
 | 
						|
      const center = canvasCoordinates[0]
 | 
						|
      const radius = getCanvasCircleRadius(canvasCoordinates)
 | 
						|
      const canvasCorners = getCanvasCircleCorners(canvasCoordinates)
 | 
						|
 | 
						|
      const { centerPointRadius } = this.configuration
 | 
						|
 | 
						|
      const modalityUnitOptions = {
 | 
						|
        isPreScaled: utilities.viewport.isViewportPreScaled(viewport, targetId),
 | 
						|
 | 
						|
        isSuvScaled: this.isSuvScaled(
 | 
						|
          viewport,
 | 
						|
          targetId,
 | 
						|
          annotation.metadata.referencedImageId
 | 
						|
        )
 | 
						|
      }
 | 
						|
 | 
						|
      // If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
 | 
						|
      // force to recalculate the stats from the points
 | 
						|
      if (
 | 
						|
        !data.cachedStats[targetId] ||
 | 
						|
        data.cachedStats[targetId].areaUnit === undefined
 | 
						|
      ) {
 | 
						|
        data.cachedStats[targetId] = {
 | 
						|
          Modality: null,
 | 
						|
          area: null,
 | 
						|
          max: null,
 | 
						|
          mean: null,
 | 
						|
          stdDev: null,
 | 
						|
          areaUnit: null,
 | 
						|
          radius: null,
 | 
						|
          radiusUnit: null,
 | 
						|
          perimeter: null
 | 
						|
        }
 | 
						|
        console.log(111111)
 | 
						|
        this._calculateCachedStats(
 | 
						|
          annotation,
 | 
						|
          viewport,
 | 
						|
          renderingEngine,
 | 
						|
          enabledElement,
 | 
						|
          modalityUnitOptions
 | 
						|
        )
 | 
						|
      } else if (annotation.invalidated) {
 | 
						|
        console.log(22222)
 | 
						|
 | 
						|
        this._throttledCalculateCachedStats(
 | 
						|
          annotation,
 | 
						|
          viewport,
 | 
						|
          renderingEngine,
 | 
						|
          enabledElement,
 | 
						|
          modalityUnitOptions
 | 
						|
        )
 | 
						|
 | 
						|
        // If the invalidated data is as a result of volumeViewport manipulation
 | 
						|
        // of the tools, we need to invalidate the related viewports data, so that
 | 
						|
        // when scrolling to the related slice in which the tool were manipulated
 | 
						|
        // we re-render the correct tool position. This is due to stackViewport
 | 
						|
        // which doesn't have the full volume at each time, and we are only working
 | 
						|
        // on one slice at a time.
 | 
						|
        if (viewport instanceof VolumeViewport) {
 | 
						|
          const { referencedImageId } = annotation.metadata
 | 
						|
 | 
						|
          // invalidate all the relevant stackViewports if they are not
 | 
						|
          // at the referencedImageId
 | 
						|
          for (const targetId in data.cachedStats) {
 | 
						|
            if (targetId.startsWith('imageId')) {
 | 
						|
              const viewports = renderingEngine.getStackViewports()
 | 
						|
 | 
						|
              const invalidatedStack = viewports.find((vp) => {
 | 
						|
                // The stack viewport that contains the imageId but is not
 | 
						|
                // showing it currently
 | 
						|
                const referencedImageURI =
 | 
						|
                  csUtils.imageIdToURI(referencedImageId)
 | 
						|
                const hasImageURI = vp.hasImageURI(referencedImageURI)
 | 
						|
                const currentImageURI = csUtils.imageIdToURI(
 | 
						|
                  vp.getCurrentImageId()
 | 
						|
                )
 | 
						|
                return hasImageURI && currentImageURI !== referencedImageURI
 | 
						|
              })
 | 
						|
 | 
						|
              if (invalidatedStack) {
 | 
						|
                delete data.cachedStats[targetId]
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // If rendering engine has been destroyed while rendering
 | 
						|
      if (!viewport.getRenderingEngine()) {
 | 
						|
        console.warn('Rendering Engine has been destroyed')
 | 
						|
        return renderStatus
 | 
						|
      }
 | 
						|
 | 
						|
      let activeHandleCanvasCoords
 | 
						|
 | 
						|
      if (!isAnnotationVisible(annotationUID)) {
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      if (
 | 
						|
        !isAnnotationLocked(annotation) &&
 | 
						|
        !this.editData &&
 | 
						|
        activeHandleIndex !== null
 | 
						|
      ) {
 | 
						|
        // Not locked or creating and hovering over handle, so render handle.
 | 
						|
        activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]]
 | 
						|
      }
 | 
						|
 | 
						|
      if (activeHandleCanvasCoords) {
 | 
						|
        const handleGroupUID = '0'
 | 
						|
        drawHandlesSvg(
 | 
						|
          svgDrawingHelper,
 | 
						|
          annotationUID,
 | 
						|
          handleGroupUID,
 | 
						|
          activeHandleCanvasCoords,
 | 
						|
          {
 | 
						|
            color
 | 
						|
          }
 | 
						|
        )
 | 
						|
      }
 | 
						|
 | 
						|
      const dataId = `${annotationUID}-circle`
 | 
						|
      const circleUID = '0'
 | 
						|
      drawCircleSvg(
 | 
						|
        svgDrawingHelper,
 | 
						|
        annotationUID,
 | 
						|
        circleUID,
 | 
						|
        center,
 | 
						|
        radius,
 | 
						|
        {
 | 
						|
          color,
 | 
						|
          lineDash,
 | 
						|
          lineWidth
 | 
						|
        },
 | 
						|
        dataId
 | 
						|
      )
 | 
						|
 | 
						|
      // draw center point, if "centerPointRadius" configuration is valid.
 | 
						|
      if (centerPointRadius > 0) {
 | 
						|
        if (radius > 3 * centerPointRadius) {
 | 
						|
          drawCircleSvg(
 | 
						|
            svgDrawingHelper,
 | 
						|
            annotationUID,
 | 
						|
            `${circleUID}-center`,
 | 
						|
            center,
 | 
						|
            centerPointRadius,
 | 
						|
            {
 | 
						|
              color,
 | 
						|
              lineDash,
 | 
						|
              lineWidth
 | 
						|
            }
 | 
						|
          )
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      renderStatus = true
 | 
						|
 | 
						|
      const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation)
 | 
						|
      if (!options.visibility) {
 | 
						|
        data.handles.textBox = {
 | 
						|
          hasMoved: false,
 | 
						|
          worldPosition: [0, 0, 0],
 | 
						|
          worldBoundingBox: {
 | 
						|
            topLeft: [0, 0, 0],
 | 
						|
            topRight: [0, 0, 0],
 | 
						|
            bottomLeft: [0, 0, 0],
 | 
						|
            bottomRight: [0, 0, 0]
 | 
						|
          }
 | 
						|
        }
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      const textLines = this.configuration.getTextLines(data, targetId)
 | 
						|
      if (!textLines || textLines.length === 0) {
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      // Poor man's cached?
 | 
						|
      let canvasTextBoxCoords
 | 
						|
 | 
						|
      if (!data.handles.textBox.hasMoved) {
 | 
						|
        canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCorners)
 | 
						|
 | 
						|
        data.handles.textBox.worldPosition =
 | 
						|
          viewport.canvasToWorld(canvasTextBoxCoords)
 | 
						|
      }
 | 
						|
 | 
						|
      const textBoxPosition = viewport.worldToCanvas(
 | 
						|
        data.handles.textBox.worldPosition
 | 
						|
      )
 | 
						|
 | 
						|
      const textBoxUID = '1'
 | 
						|
      const boundingBox = drawLinkedTextBoxSvg(
 | 
						|
        svgDrawingHelper,
 | 
						|
        annotationUID,
 | 
						|
        textBoxUID,
 | 
						|
        textLines,
 | 
						|
        textBoxPosition,
 | 
						|
        canvasCoordinates,
 | 
						|
        {},
 | 
						|
        options
 | 
						|
      )
 | 
						|
 | 
						|
      const { x: left, y: top, width, height } = boundingBox
 | 
						|
 | 
						|
      data.handles.textBox.worldBoundingBox = {
 | 
						|
        topLeft: viewport.canvasToWorld([left, top]),
 | 
						|
        topRight: viewport.canvasToWorld([left + width, top]),
 | 
						|
        bottomLeft: viewport.canvasToWorld([left, top + height]),
 | 
						|
        bottomRight: viewport.canvasToWorld([left + width, top + height])
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return renderStatus
 | 
						|
  };
 | 
						|
  _calculateCachedStats1 = (
 | 
						|
    annotation,
 | 
						|
    viewport,
 | 
						|
    renderingEngine,
 | 
						|
    enabledElement,
 | 
						|
    modalityUnitOptions
 | 
						|
  ) => {
 | 
						|
    const data = annotation.data
 | 
						|
    const { viewportId, renderingEngineId } = enabledElement
 | 
						|
 | 
						|
    const { points } = data.handles
 | 
						|
 | 
						|
    const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p))
 | 
						|
    const { viewPlaneNormal, viewUp } = viewport.getCamera()
 | 
						|
 | 
						|
    const [topLeftCanvas, bottomRightCanvas] = (
 | 
						|
      getCanvasCircleCorners(canvasCoordinates)
 | 
						|
    )
 | 
						|
 | 
						|
    const topLeftWorld = viewport.canvasToWorld(topLeftCanvas)
 | 
						|
    const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas)
 | 
						|
    const { cachedStats } = data
 | 
						|
 | 
						|
    const targetIds = Object.keys(cachedStats)
 | 
						|
    const worldPos1 = topLeftWorld
 | 
						|
    const worldPos2 = bottomRightWorld
 | 
						|
    for (let i = 0; i < targetIds.length; i++) {
 | 
						|
      const targetId = targetIds[i]
 | 
						|
 | 
						|
      const image = this.getTargetIdImage(targetId, renderingEngine)
 | 
						|
 | 
						|
      // If image does not exists for the targetId, skip. This can be due
 | 
						|
      // to various reasons such as if the target was a volumeViewport, and
 | 
						|
      // the volumeViewport has been decached in the meantime.
 | 
						|
      if (!image) {
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      const { dimensions, imageData, metadata } = image
 | 
						|
 | 
						|
      const worldPos1Index = transformWorldToIndex(imageData, worldPos1)
 | 
						|
 | 
						|
      worldPos1Index[0] = Math.floor(worldPos1Index[0])
 | 
						|
      worldPos1Index[1] = Math.floor(worldPos1Index[1])
 | 
						|
      worldPos1Index[2] = Math.floor(worldPos1Index[2])
 | 
						|
 | 
						|
      const worldPos2Index = transformWorldToIndex(imageData, worldPos2)
 | 
						|
 | 
						|
      worldPos2Index[0] = Math.floor(worldPos2Index[0])
 | 
						|
      worldPos2Index[1] = Math.floor(worldPos2Index[1])
 | 
						|
      worldPos2Index[2] = Math.floor(worldPos2Index[2])
 | 
						|
 | 
						|
      // Check if one of the indexes are inside the volume, this then gives us
 | 
						|
      // Some area to do stats over.
 | 
						|
 | 
						|
      if (this._isInsideVolume(worldPos1Index, worldPos2Index, dimensions)) {
 | 
						|
        const iMin = Math.min(worldPos1Index[0], worldPos2Index[0])
 | 
						|
        const iMax = Math.max(worldPos1Index[0], worldPos2Index[0])
 | 
						|
 | 
						|
        const jMin = Math.min(worldPos1Index[1], worldPos2Index[1])
 | 
						|
        const jMax = Math.max(worldPos1Index[1], worldPos2Index[1])
 | 
						|
 | 
						|
        const kMin = Math.min(worldPos1Index[2], worldPos2Index[2])
 | 
						|
        const kMax = Math.max(worldPos1Index[2], worldPos2Index[2])
 | 
						|
 | 
						|
        const boundsIJK = [
 | 
						|
          [iMin, iMax],
 | 
						|
          [jMin, jMax],
 | 
						|
          [kMin, kMax]
 | 
						|
        ]
 | 
						|
 | 
						|
        const center = [
 | 
						|
          (topLeftWorld[0] + bottomRightWorld[0]) / 2,
 | 
						|
          (topLeftWorld[1] + bottomRightWorld[1]) / 2,
 | 
						|
          (topLeftWorld[2] + bottomRightWorld[2]) / 2
 | 
						|
        ]
 | 
						|
 | 
						|
        const ellipseObj = {
 | 
						|
          center,
 | 
						|
          xRadius: Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2,
 | 
						|
          yRadius: Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2,
 | 
						|
          zRadius: Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2
 | 
						|
        }
 | 
						|
        const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(
 | 
						|
          viewPlaneNormal,
 | 
						|
          viewUp,
 | 
						|
          worldPos1,
 | 
						|
          worldPos2
 | 
						|
        )
 | 
						|
        const isEmptyArea = worldWidth === 0 && worldHeight === 0
 | 
						|
        const scale = getCalibratedScale(image)
 | 
						|
        const aspect = getCalibratedAspect(image)
 | 
						|
        const area = Math.abs(
 | 
						|
          Math.PI *
 | 
						|
            (worldWidth / scale / 2) *
 | 
						|
            (worldHeight / aspect / scale / 2)
 | 
						|
        )
 | 
						|
 | 
						|
        const modalityUnit = getModalityUnit(
 | 
						|
          metadata.Modality,
 | 
						|
          annotation.metadata.referencedImageId,
 | 
						|
          modalityUnitOptions
 | 
						|
        )
 | 
						|
        const pointsInShape = pointInShapeCallback(
 | 
						|
          imageData,
 | 
						|
          (pointLPS, pointIJK) => pointInEllipse(ellipseObj, pointLPS),
 | 
						|
          this.configuration.statsCalculator.statsCallback,
 | 
						|
          boundsIJK
 | 
						|
        )
 | 
						|
 | 
						|
        const stats = this.configuration.statsCalculator.getStatistics()
 | 
						|
 | 
						|
        cachedStats[targetId] = {
 | 
						|
          Modality: metadata.Modality,
 | 
						|
          area,
 | 
						|
          mean: stats[1] && stats[1].value ? stats[1].value : null,
 | 
						|
          max: stats[0] && stats[0].value ? stats[0].value : null,
 | 
						|
          stdDev: stats[2] && stats[2].value ? stats[2].value : null,
 | 
						|
          statsArray: stats,
 | 
						|
          pointsInShape: pointsInShape,
 | 
						|
          isEmptyArea,
 | 
						|
          areaUnit: getCalibratedAreaUnits(null, image),
 | 
						|
          radius: worldWidth / 2 / scale,
 | 
						|
          radiusUnit: getCalibratedLengthUnits(null, image),
 | 
						|
          perimeter: (2 * Math.PI * (worldWidth / 2)) / scale,
 | 
						|
          modalityUnit
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        this.isHandleOutsideImage = true
 | 
						|
 | 
						|
        cachedStats[targetId] = {
 | 
						|
          Modality: metadata.Modality
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    annotation.invalidated = false
 | 
						|
 | 
						|
    // Dispatching annotation modified
 | 
						|
    const eventType = Enums.Events.ANNOTATION_MODIFIED
 | 
						|
 | 
						|
    const eventDetail = {
 | 
						|
      annotation,
 | 
						|
      viewportId,
 | 
						|
      renderingEngineId
 | 
						|
    }
 | 
						|
    triggerEvent(eventTarget, eventType, eventDetail)
 | 
						|
 | 
						|
    return cachedStats
 | 
						|
  }
 | 
						|
}
 | 
						|
function getCanvasCircleCorners(
 | 
						|
  circleCanvasPoints
 | 
						|
) {
 | 
						|
  const [center, end] = circleCanvasPoints
 | 
						|
  const radius = utilities.math.point.distanceToPoint(center, end)
 | 
						|
 | 
						|
  const topLeft = [center[0] - radius, center[1] - radius]
 | 
						|
  const bottomRight = [center[0] + radius, center[1] + radius]
 | 
						|
 | 
						|
  return [topLeft, bottomRight]
 | 
						|
}
 | 
						|
function getCanvasCircleRadius(
 | 
						|
  circleCanvasPoints
 | 
						|
) {
 | 
						|
  const [center, end] = circleCanvasPoints
 | 
						|
  return utilities.math.point.distanceToPoint(center, end)
 | 
						|
}
 | 
						|
function getWorldWidthAndHeightFromTwoPoints(
 | 
						|
  viewPlaneNormal,
 | 
						|
  viewUp,
 | 
						|
  worldPos1,
 | 
						|
  worldPos2
 | 
						|
) {
 | 
						|
  const viewRight = vec3.create()
 | 
						|
 | 
						|
  vec3.cross(viewRight, viewUp, viewPlaneNormal)
 | 
						|
 | 
						|
  const pos1 = vec3.fromValues(...worldPos1)
 | 
						|
  const pos2 = vec3.fromValues(...worldPos2)
 | 
						|
 | 
						|
  const diagonal = vec3.create()
 | 
						|
  vec3.subtract(diagonal, pos1, pos2)
 | 
						|
 | 
						|
  const diagonalLength = vec3.length(diagonal)
 | 
						|
 | 
						|
  // When the two points are very close to each other return width as 0
 | 
						|
  // to avoid NaN the cosTheta formula calculation
 | 
						|
  if (diagonalLength < 0.0001) {
 | 
						|
    return { worldWidth: 0, worldHeight: 0 }
 | 
						|
  }
 | 
						|
 | 
						|
  const cosTheta =
 | 
						|
    vec3.dot(diagonal, viewRight) / (diagonalLength * vec3.length(viewRight))
 | 
						|
 | 
						|
  const sinTheta = Math.sqrt(1 - cosTheta * cosTheta)
 | 
						|
 | 
						|
  const worldWidth = sinTheta * diagonalLength
 | 
						|
  const worldHeight = cosTheta * diagonalLength
 | 
						|
 | 
						|
  return { worldWidth, worldHeight }
 | 
						|
}
 | 
						|
function pointInEllipse(
 | 
						|
  ellipse,
 | 
						|
  pointLPS
 | 
						|
) {
 | 
						|
  const { center: circleCenterWorld, xRadius, yRadius, zRadius } = ellipse
 | 
						|
  const [x, y, z] = pointLPS
 | 
						|
  const [x0, y0, z0] = circleCenterWorld
 | 
						|
 | 
						|
  let inside = 0
 | 
						|
  if (xRadius !== 0) {
 | 
						|
    inside += ((x - x0) * (x - x0)) / (xRadius * xRadius)
 | 
						|
  }
 | 
						|
 | 
						|
  if (yRadius !== 0) {
 | 
						|
    inside += ((y - y0) * (y - y0)) / (yRadius * yRadius)
 | 
						|
  }
 | 
						|
 | 
						|
  if (zRadius !== 0) {
 | 
						|
    inside += ((z - z0) * (z - z0)) / (zRadius * zRadius)
 | 
						|
  }
 | 
						|
 | 
						|
  return inside <= 1
 | 
						|
}
 | 
						|
// function defaultGetTextLines(data, targetId) {
 | 
						|
//   const cachedVolumeStats = data.cachedStats[targetId]
 | 
						|
//   const {
 | 
						|
//     radius,
 | 
						|
//     radiusUnit,
 | 
						|
//     area,
 | 
						|
//     mean,
 | 
						|
//     stdDev,
 | 
						|
//     max,
 | 
						|
//     isEmptyArea,
 | 
						|
//     areaUnit,
 | 
						|
//     modalityUnit
 | 
						|
//   } = cachedVolumeStats
 | 
						|
 | 
						|
//   const textLines = []
 | 
						|
 | 
						|
//   if (radius) {
 | 
						|
//     const radiusLine = isEmptyArea
 | 
						|
//       ? `Radius: Oblique not supported`
 | 
						|
//       : `Radius: ${roundNumber(radius)} ${radiusUnit}`
 | 
						|
//     textLines.push(radiusLine)
 | 
						|
//   }
 | 
						|
 | 
						|
//   if (area) {
 | 
						|
//     const areaLine = isEmptyArea
 | 
						|
//       ? `Area: Oblique not supported`
 | 
						|
//       : `Area: ${roundNumber(area)} ${areaUnit}`
 | 
						|
//     textLines.push(areaLine)
 | 
						|
//   }
 | 
						|
 | 
						|
//   if (mean) {
 | 
						|
//     textLines.push(`Mean: ${roundNumber(mean)} ${modalityUnit}`)
 | 
						|
//   }
 | 
						|
 | 
						|
//   if (max) {
 | 
						|
//     textLines.push(`Max: ${roundNumber(max)} ${modalityUnit}`)
 | 
						|
//   }
 | 
						|
 | 
						|
//   if (stdDev) {
 | 
						|
//     textLines.push(`Std Dev: ${roundNumber(stdDev)} ${modalityUnit}`)
 | 
						|
//   }
 | 
						|
 | 
						|
//   return textLines
 | 
						|
// }
 | 
						|
 | 
						|
CircleROITool.toolName = 'CircleROI'
 | 
						|
export default CircleROITool
 | 
						|
 |