irc_web/.svn/pristine/50/50f0bfcd85c152250d9d5cdc924...

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