Compare commits
No commits in common. "3a546e6e89084c7a05f14168a054777f408256af" and "a82526e21dc774902024ba1af0e2644729fb9d69" have entirely different histories.
3a546e6e89
...
a82526e21d
|
|
@ -1 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1779246817161" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5348" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M473 71a8 8 0 0 1 8-8h64a8 8 0 0 1 8 8v77.161C722.848 166.622 857.812 301.289 876.729 471H952a8 8 0 0 1 8 8v64a8 8 0 0 1-8 8h-75.053C858.871 721.652 723.515 857.305 553 875.839V951a8 8 0 0 1-8 8h-64a8 8 0 0 1-8-8v-75.161C302.818 857.341 167.659 722.182 149.161 552H72a8 8 0 0 1-8-8v-64a8 8 0 0 1 8-8h77.161C167.659 301.818 302.818 166.659 473 148.161V71z m326 441c0-157.953-128.047-286-286-286S227 354.047 227 512s128.047 286 286 286 286-128.047 286-286z m-286 60c33.137 0 60-26.863 60-60s-26.863-60-60-60-60 26.863-60 60 26.863 60 60 60z" fill="#ffffff" p-id="5349"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 913 B |
File diff suppressed because it is too large
Load Diff
|
|
@ -82,18 +82,6 @@
|
||||||
<div :style="{ top: sliderInfo.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
|
<div :style="{ top: sliderInfo.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
|
||||||
@mousedown.stop="sliderMousedown($event)" />
|
@mousedown.stop="sliderMousedown($event)" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="annotationContextMenu.visible"
|
|
||||||
ref="annotation-context-menu"
|
|
||||||
class="annotation-context-menu"
|
|
||||||
:style="{ left: `${annotationContextMenu.x}px`, top: `${annotationContextMenu.y}px` }"
|
|
||||||
@mousedown.stop
|
|
||||||
@mouseup.stop
|
|
||||||
@click.stop
|
|
||||||
@contextmenu.prevent
|
|
||||||
>
|
|
||||||
<div class="annotation-context-menu__item" @click.stop="copyCircleAnnotation()">复制</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -110,7 +98,7 @@ import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
|
||||||
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
|
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
|
||||||
|
|
||||||
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
|
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
|
||||||
import setCtTransferFunctionForVolumeActor, { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
||||||
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
|
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
|
||||||
import {
|
import {
|
||||||
setMipTransferFunctionForVolumeActor,
|
setMipTransferFunctionForVolumeActor,
|
||||||
|
|
@ -120,15 +108,6 @@ import setNmFusionColorMapTransferFunctionForVolumeActor from './helpers/setNmFu
|
||||||
const { BlendModes, OrientationAxis } = Enums;
|
const { BlendModes, OrientationAxis } = Enums;
|
||||||
const { getColormap } = csUtils.colormap;
|
const { getColormap } = csUtils.colormap;
|
||||||
import { vec3, mat4 } from 'gl-matrix'
|
import { vec3, mat4 } from 'gl-matrix'
|
||||||
const DEFAULT_COLOR_MAP_UPPER = 6
|
|
||||||
|
|
||||||
function getVoiValue(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return Number(value[0])
|
|
||||||
}
|
|
||||||
return Number(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ImageViewport',
|
name: 'ImageViewport',
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -201,13 +180,7 @@ export default {
|
||||||
Colorbar: null,
|
Colorbar: null,
|
||||||
fusionOpacity: 0.95,
|
fusionOpacity: 0.95,
|
||||||
topFusionVolumeActor: null,
|
topFusionVolumeActor: null,
|
||||||
currentVoiUpper: null,
|
currentVoiUpper: null
|
||||||
annotationContextMenu: {
|
|
||||||
visible: false,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
annotation: null,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -227,7 +200,7 @@ export default {
|
||||||
renderingEngine.resize(true, false)
|
renderingEngine.resize(true, false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.element.oncontextmenu = this.handleElementContextMenu
|
this.element.oncontextmenu = (e) => e.preventDefault()
|
||||||
// resizeObserver.observe(this.element)
|
// resizeObserver.observe(this.element)
|
||||||
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
|
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
|
||||||
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
|
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
|
||||||
|
|
@ -240,303 +213,9 @@ export default {
|
||||||
})
|
})
|
||||||
document.addEventListener('mouseup', this.handleDocumentMouseUp)
|
document.addEventListener('mouseup', this.handleDocumentMouseUp)
|
||||||
document.addEventListener('mousemove', this.handleDocumentMouseMove)
|
document.addEventListener('mousemove', this.handleDocumentMouseMove)
|
||||||
document.addEventListener('mousedown', this.handleDocumentMouseDown)
|
|
||||||
// console.log(cornerstoneTools)
|
// console.log(cornerstoneTools)
|
||||||
// element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
|
// element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
|
||||||
},
|
},
|
||||||
getViewportInstance() {
|
|
||||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
||||||
return renderingEngine?.getViewport(this.viewportId)
|
|
||||||
},
|
|
||||||
hideAnnotationContextMenu() {
|
|
||||||
if (!this.annotationContextMenu.visible) return
|
|
||||||
this.annotationContextMenu.visible = false
|
|
||||||
this.annotationContextMenu.annotation = null
|
|
||||||
},
|
|
||||||
handleDocumentMouseDown(e) {
|
|
||||||
const menu = this.$refs['annotation-context-menu']
|
|
||||||
if (menu && menu.contains(e.target)) return
|
|
||||||
this.hideAnnotationContextMenu()
|
|
||||||
},
|
|
||||||
handleElementContextMenu(e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
// const annotation = this.getCircleAnnotationFromContextMenuEvent(e)
|
|
||||||
// if (!annotation) {
|
|
||||||
// this.hideAnnotationContextMenu()
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (cornerstoneTools.annotation.locking.isAnnotationLocked(annotation.annotationUID)) {
|
|
||||||
// this.hideAnnotationContextMenu()
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.$emit('activeViewport', this.viewportIndex)
|
|
||||||
|
|
||||||
// const { x, y } = this.getContextMenuPosition(e)
|
|
||||||
// this.annotationContextMenu.visible = true
|
|
||||||
// this.annotationContextMenu.x = x
|
|
||||||
// this.annotationContextMenu.y = y
|
|
||||||
// this.annotationContextMenu.annotation = annotation
|
|
||||||
// return false
|
|
||||||
},
|
|
||||||
getContextMenuPosition(e) {
|
|
||||||
const rect = this.element?.getBoundingClientRect?.()
|
|
||||||
if (!rect) {
|
|
||||||
return { x: 0, y: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuWidth = 128
|
|
||||||
const menuHeight = 40
|
|
||||||
const padding = 8
|
|
||||||
const maxX = Math.max(padding, rect.width - menuWidth - padding)
|
|
||||||
const maxY = Math.max(padding, rect.height - menuHeight - padding)
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: Math.min(Math.max(e.clientX - rect.left, padding), maxX),
|
|
||||||
y: Math.min(Math.max(e.clientY - rect.top, padding), maxY),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCircleAnnotationFromContextMenuEvent(e) {
|
|
||||||
const viewport = this.getViewportInstance()
|
|
||||||
if (!viewport || !this.element) return null
|
|
||||||
|
|
||||||
const rect = this.element.getBoundingClientRect()
|
|
||||||
const canvasPoint = [e.clientX - rect.left, e.clientY - rect.top]
|
|
||||||
const currentVisitTaskId = this.series?.TaskInfo?.VisitTaskId
|
|
||||||
const toolNames = ['CircleROI'] //FixedRadiusCircleROI看后期是不是要补充
|
|
||||||
const candidates = []
|
|
||||||
|
|
||||||
toolNames.forEach((toolName) => {
|
|
||||||
const annotations = cornerstoneTools.annotation.state.getAnnotations(toolName, this.element) || []
|
|
||||||
annotations.forEach((annotation) => {
|
|
||||||
if (!annotation?.data?.handles?.points?.length) return
|
|
||||||
if (currentVisitTaskId && annotation.visitTaskId && annotation.visitTaskId !== currentVisitTaskId) return
|
|
||||||
|
|
||||||
const hitInfo = this.getCircleAnnotationHitInfo(annotation, canvasPoint, viewport)
|
|
||||||
if (!hitInfo.hit || hitInfo.score === null) return
|
|
||||||
|
|
||||||
candidates.push({
|
|
||||||
annotation,
|
|
||||||
score: hitInfo.score,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!candidates.length) return null
|
|
||||||
|
|
||||||
candidates.sort((a, b) => a.score - b.score)
|
|
||||||
return candidates[0].annotation
|
|
||||||
},
|
|
||||||
getCircleAnnotationHitInfo(annotation, canvasPoint, viewport) {
|
|
||||||
const points = annotation?.data?.handles?.points || []
|
|
||||||
if (points.length < 2) {
|
|
||||||
return { hit: false, score: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
const centerCanvas = viewport.worldToCanvas(points[0]) // 圆心
|
|
||||||
const edgeCanvas = viewport.worldToCanvas(points[1]) // 边缘点
|
|
||||||
const radius = this.distanceToPoint(centerCanvas, edgeCanvas)
|
|
||||||
if (radius <= 0) {
|
|
||||||
return { hit: false, score: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
const dx = canvasPoint[0] - centerCanvas[0]
|
|
||||||
const dy = canvasPoint[1] - centerCanvas[1]
|
|
||||||
const distance = (dx * dx + dy * dy) / (radius * radius)
|
|
||||||
const hit = distance <= 1.2
|
|
||||||
|
|
||||||
return {
|
|
||||||
hit,
|
|
||||||
score: hit ? distance : null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createCopiedCachedStats(sourceCachedStats = {}) {
|
|
||||||
const cloned = JSON.parse(JSON.stringify(sourceCachedStats || {}))
|
|
||||||
const keys = Object.keys(cloned)
|
|
||||||
|
|
||||||
if (!keys.length) {
|
|
||||||
const fallbackKey = this.isFusion ? (this.ptVolumeId || this.volumeId) : this.volumeId
|
|
||||||
if (fallbackKey) {
|
|
||||||
cloned[fallbackKey] = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(cloned).forEach((key) => {
|
|
||||||
cloned[key] = {
|
|
||||||
...cloned[key],
|
|
||||||
area: null,
|
|
||||||
count: null,
|
|
||||||
isEmptyArea: null,
|
|
||||||
max: null,
|
|
||||||
mean: null,
|
|
||||||
min: null,
|
|
||||||
perimeter: null,
|
|
||||||
pointsInShape: null,
|
|
||||||
radius: null,
|
|
||||||
statsArray: [],
|
|
||||||
stdDev: null,
|
|
||||||
total: null,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
},
|
|
||||||
getCopySide(sourceCenterCanvas, radius) {
|
|
||||||
const canvasWidth = this.element.clientWidth || 0
|
|
||||||
const padding = Math.max(radius * 0.5, 8)
|
|
||||||
const requiredSpace = Math.max(radius * 2.1, 16) + padding // 放置新标注所需要的空间
|
|
||||||
const leftSpace = sourceCenterCanvas[0] - padding
|
|
||||||
const rightSpace = canvasWidth - sourceCenterCanvas[0] - padding
|
|
||||||
|
|
||||||
const canPlaceLeft = leftSpace >= requiredSpace
|
|
||||||
const canPlaceRight = rightSpace >= requiredSpace
|
|
||||||
|
|
||||||
if (canPlaceLeft && canPlaceRight) {
|
|
||||||
return rightSpace >= leftSpace ? 'right' : 'left'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canPlaceRight) {
|
|
||||||
return 'right'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canPlaceLeft) {
|
|
||||||
return 'left'
|
|
||||||
}
|
|
||||||
|
|
||||||
return rightSpace >= leftSpace ? 'right' : 'left'
|
|
||||||
},
|
|
||||||
buildCopiedCircleAnnotation(sourceAnnotation, side) {
|
|
||||||
const viewport = this.getViewportInstance()
|
|
||||||
if (!viewport) return null
|
|
||||||
|
|
||||||
const points = sourceAnnotation.data.handles.points || []
|
|
||||||
if (points.length < 2) return null
|
|
||||||
|
|
||||||
const sourceCenterCanvas = viewport.worldToCanvas(points[0])
|
|
||||||
const sourceEdgeCanvas = viewport.worldToCanvas(points[1])
|
|
||||||
|
|
||||||
const radiusVector = [
|
|
||||||
sourceEdgeCanvas[0] - sourceCenterCanvas[0],
|
|
||||||
sourceEdgeCanvas[1] - sourceCenterCanvas[1],
|
|
||||||
]
|
|
||||||
const radius = this.distanceToPoint(sourceCenterCanvas, sourceEdgeCanvas)
|
|
||||||
if (radius <= 0) return null
|
|
||||||
|
|
||||||
const offsetDirection = side === 'left' ? -1 : 1
|
|
||||||
const offsetDistance = Math.max(radius * 2.1, 16)
|
|
||||||
const padding = Math.max(radius * 0.5, 8)
|
|
||||||
const canvasWidth = this.element?.clientWidth || 0
|
|
||||||
const canvasHeight = this.element?.clientHeight || 0
|
|
||||||
const targetCenterCanvas = [
|
|
||||||
sourceCenterCanvas[0] + offsetDirection * offsetDistance,
|
|
||||||
sourceCenterCanvas[1],
|
|
||||||
]
|
|
||||||
|
|
||||||
if (canvasWidth > 0) {
|
|
||||||
targetCenterCanvas[0] = Math.min(Math.max(targetCenterCanvas[0], padding), canvasWidth - padding)
|
|
||||||
}
|
|
||||||
if (canvasHeight > 0) {
|
|
||||||
targetCenterCanvas[1] = Math.min(Math.max(targetCenterCanvas[1], padding), canvasHeight - padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetEdgeCanvas = [
|
|
||||||
targetCenterCanvas[0] + radiusVector[0],
|
|
||||||
targetCenterCanvas[1] + radiusVector[1],
|
|
||||||
]
|
|
||||||
const centerWorld = viewport.canvasToWorld(targetCenterCanvas)
|
|
||||||
const edgeWorld = viewport.canvasToWorld(targetEdgeCanvas)
|
|
||||||
const camera = viewport.getCamera()
|
|
||||||
|
|
||||||
return {
|
|
||||||
highlighted: true,
|
|
||||||
invalidated: true,
|
|
||||||
metadata: {
|
|
||||||
toolName: sourceAnnotation.metadata?.toolName || 'CircleROI',
|
|
||||||
viewPlaneNormal: [...(camera?.viewPlaneNormal || sourceAnnotation.metadata?.viewPlaneNormal || [])],
|
|
||||||
viewUp: [...(camera?.viewUp || sourceAnnotation.metadata?.viewUp || [])],
|
|
||||||
FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
|
|
||||||
referencedImageId: sourceAnnotation.metadata?.referencedImageId || viewport.getCurrentImageId?.(),
|
|
||||||
...viewport.getViewReference({ points: [centerWorld] }),
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
label: '',
|
|
||||||
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],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
points: [[...centerWorld], [...edgeWorld]],
|
|
||||||
activeHandleIndex: null,
|
|
||||||
},
|
|
||||||
cachedStats: this.createCopiedCachedStats(sourceAnnotation.data?.cachedStats),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
copyCircleAnnotation(side) {
|
|
||||||
const sourceAnnotation = this.annotationContextMenu.annotation
|
|
||||||
this.hideAnnotationContextMenu()
|
|
||||||
|
|
||||||
if (!sourceAnnotation || !this.element) return
|
|
||||||
|
|
||||||
let targetSide = side
|
|
||||||
if (!targetSide) {
|
|
||||||
const viewport = this.getViewportInstance()
|
|
||||||
const points = sourceAnnotation.data.handles.points || []
|
|
||||||
if (!viewport || points.length < 2) return
|
|
||||||
|
|
||||||
const sourceCenterCanvas = viewport.worldToCanvas(points[0])
|
|
||||||
const sourceEdgeCanvas = viewport.worldToCanvas(points[1])
|
|
||||||
if (
|
|
||||||
!Array.isArray(sourceCenterCanvas) ||sourceCenterCanvas.length < 2 || !Array.isArray(sourceEdgeCanvas) || sourceEdgeCanvas.length < 2
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const radius = this.distanceToPoint(sourceCenterCanvas, sourceEdgeCanvas)
|
|
||||||
if (radius <= 0) return
|
|
||||||
|
|
||||||
targetSide = this.getCopySide(sourceCenterCanvas, radius)
|
|
||||||
}
|
|
||||||
|
|
||||||
const annotation = this.buildCopiedCircleAnnotation(sourceAnnotation, targetSide)
|
|
||||||
if (!annotation) return
|
|
||||||
|
|
||||||
cornerstoneTools.annotation.state.addAnnotation(annotation, this.element)
|
|
||||||
|
|
||||||
const viewportIdsToRender = cornerstoneTools.utilities.viewportFilters.getViewportIdsWithToolToRender(
|
|
||||||
this.element,
|
|
||||||
annotation.metadata.toolName
|
|
||||||
)
|
|
||||||
cornerstoneTools.utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
cornerstoneTools.annotation.state.triggerAnnotationCompleted(annotation)
|
|
||||||
}, 0)
|
|
||||||
},
|
|
||||||
distanceToPoint(p1, p2) {
|
|
||||||
return Math.sqrt(this.distanceToPointSquared(p1, p2))
|
|
||||||
},
|
|
||||||
distanceToPointSquared(p1, p2) {
|
|
||||||
if (p1.length !== p2.length) {
|
|
||||||
throw Error('Both points should have the same dimensionality')
|
|
||||||
}
|
|
||||||
const [x1, y1, z1 = 0] = p1
|
|
||||||
const [x2, y2, z2 = 0] = p2
|
|
||||||
const dx = x2 - x1
|
|
||||||
const dy = y2 - y1
|
|
||||||
const dz = z2 - z1
|
|
||||||
|
|
||||||
return dx * dx + dy * dy + dz * dz
|
|
||||||
},
|
|
||||||
handleDocumentMouseUp(e) {
|
handleDocumentMouseUp(e) {
|
||||||
this.sliderMouseup(e)
|
this.sliderMouseup(e)
|
||||||
if (this.isMip) {
|
if (this.isMip) {
|
||||||
|
|
@ -909,27 +588,8 @@ export default {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getInitialWindowLevelFromVolumeId(volumeId) {
|
|
||||||
if (!volumeId) return null
|
|
||||||
const volume = cache.getVolume(volumeId)
|
|
||||||
const imageId = volume?.imageIds?.[0]
|
|
||||||
if (!imageId) return null
|
|
||||||
|
|
||||||
const voiLutModule = metaData.get('voiLutModule', imageId)
|
|
||||||
const windowCenter = getVoiValue(voiLutModule?.windowCenter)
|
|
||||||
const windowWidth = getVoiValue(voiLutModule?.windowWidth)
|
|
||||||
|
|
||||||
if (windowWidth <= 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
windowCenter,
|
|
||||||
windowWidth,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFusionVolumes() {
|
getFusionVolumes() {
|
||||||
const ctVolumeId = this.ctSeries.SeriesInstanceUid
|
const ctVolumeId = this.ctSeries?.SeriesInstanceUid
|
||||||
const ptFusionVolumeId = this.ptVolumeId
|
const ptFusionVolumeId = this.ptVolumeId
|
||||||
if (!ctVolumeId || !ptFusionVolumeId) {
|
if (!ctVolumeId || !ptFusionVolumeId) {
|
||||||
return []
|
return []
|
||||||
|
|
@ -938,10 +598,6 @@ export default {
|
||||||
const ctEntry = {
|
const ctEntry = {
|
||||||
volumeId: ctVolumeId,
|
volumeId: ctVolumeId,
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
const ctWindowLevel = this.getInitialWindowLevelFromVolumeId(ctVolumeId)
|
|
||||||
if (ctWindowLevel) {
|
|
||||||
setCtMappingRange(ctWindowLevel.windowWidth, ctWindowLevel.windowCenter)
|
|
||||||
}
|
|
||||||
setCtTransferFunctionForVolumeActor({ ...r, volumeId: ctVolumeId })
|
setCtTransferFunctionForVolumeActor({ ...r, volumeId: ctVolumeId })
|
||||||
if (this.fusionCtOnTop) {
|
if (this.fusionCtOnTop) {
|
||||||
this.topFusionVolumeActor = r.volumeActor
|
this.topFusionVolumeActor = r.volumeActor
|
||||||
|
|
@ -1000,13 +656,13 @@ export default {
|
||||||
async applyFusionRenderOrder() {
|
async applyFusionRenderOrder() {
|
||||||
if (!this.isFusion) return
|
if (!this.isFusion) return
|
||||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
const viewport = renderingEngine?.getViewport?.(this.viewportId)
|
||||||
if (!viewport) return
|
if (!viewport) return
|
||||||
|
|
||||||
const volumes = this.getFusionVolumes()
|
const volumes = this.getFusionVolumes()
|
||||||
if (!volumes.length) return
|
if (!volumes.length) return
|
||||||
|
|
||||||
const camera = viewport.getCamera()
|
const camera = viewport.getCamera?.()
|
||||||
|
|
||||||
const savedVoiUpper = this.currentVoiUpper
|
const savedVoiUpper = this.currentVoiUpper
|
||||||
|
|
||||||
|
|
@ -1038,7 +694,6 @@ export default {
|
||||||
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
|
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
|
||||||
this.volumeId = data.SeriesInstanceUid
|
this.volumeId = data.SeriesInstanceUid
|
||||||
this.ptVolumeId = null
|
this.ptVolumeId = null
|
||||||
this.currentVoiUpper = null
|
|
||||||
this.series = {}
|
this.series = {}
|
||||||
this.topFusionVolumeActor = null
|
this.topFusionVolumeActor = null
|
||||||
let { isFusion, isMip, colorMap } = option
|
let { isFusion, isMip, colorMap } = option
|
||||||
|
|
@ -1050,7 +705,6 @@ export default {
|
||||||
this.renderColorBar(this.presetName)
|
this.renderColorBar(this.presetName)
|
||||||
})
|
})
|
||||||
this.ptVolumeId = `fusion_${this.volumeId}`
|
this.ptVolumeId = `fusion_${this.volumeId}`
|
||||||
this.currentVoiUpper = DEFAULT_COLOR_MAP_UPPER
|
|
||||||
let { ct, data } = obj
|
let { ct, data } = obj
|
||||||
this.series = { ...data }
|
this.series = { ...data }
|
||||||
this.ctSeries = { ...ct }
|
this.ctSeries = { ...ct }
|
||||||
|
|
@ -1061,7 +715,6 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.series = { ...data }
|
this.series = { ...data }
|
||||||
if (this.isMip) {
|
if (this.isMip) {
|
||||||
this.currentVoiUpper = DEFAULT_COLOR_MAP_UPPER
|
|
||||||
let volume = cache.getVolume(this.volumeId)
|
let volume = cache.getVolume(this.volumeId)
|
||||||
const ptVolumeDimensions = volume.dimensions
|
const ptVolumeDimensions = volume.dimensions
|
||||||
const slabThickness = Math.sqrt(
|
const slabThickness = Math.sqrt(
|
||||||
|
|
@ -1094,14 +747,9 @@ export default {
|
||||||
.setVolumes([{
|
.setVolumes([{
|
||||||
volumeId: this.volumeId, callback: (r) => {
|
volumeId: this.volumeId, callback: (r) => {
|
||||||
if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
|
if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
|
||||||
this.currentVoiUpper = DEFAULT_COLOR_MAP_UPPER
|
|
||||||
// setPetColorMapTransferFunctionForVolumeActor(r, true)
|
// setPetColorMapTransferFunctionForVolumeActor(r, true)
|
||||||
setPetTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
setPetTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
||||||
} else {
|
} else {
|
||||||
const ctWindowLevel = this.getInitialWindowLevelFromVolumeId(this.volumeId)
|
|
||||||
if (ctWindowLevel) {
|
|
||||||
setCtMappingRange(ctWindowLevel.windowWidth, ctWindowLevel.windowCenter)
|
|
||||||
}
|
|
||||||
setCtTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
setCtTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1110,9 +758,7 @@ export default {
|
||||||
|
|
||||||
}
|
}
|
||||||
viewport.render()
|
viewport.render()
|
||||||
if (this.currentVoiUpper > 0) {
|
this.voiChange(this.currentVoiUpper)
|
||||||
this.voiChange(this.currentVoiUpper)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
@ -1336,13 +982,8 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.hideAnnotationContextMenu()
|
|
||||||
if (this.element) {
|
|
||||||
this.element.oncontextmenu = null
|
|
||||||
}
|
|
||||||
document.removeEventListener('mouseup', this.handleDocumentMouseUp)
|
document.removeEventListener('mouseup', this.handleDocumentMouseUp)
|
||||||
document.removeEventListener('mousemove', this.handleDocumentMouseMove)
|
document.removeEventListener('mousemove', this.handleDocumentMouseMove)
|
||||||
document.removeEventListener('mousedown', this.handleDocumentMouseDown)
|
|
||||||
this.series = null
|
this.series = null
|
||||||
this.topFusionVolumeActor = null
|
this.topFusionVolumeActor = null
|
||||||
},
|
},
|
||||||
|
|
@ -1360,30 +1001,6 @@ export default {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
|
|
||||||
.annotation-context-menu {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 30;
|
|
||||||
min-width: 120px;
|
|
||||||
padding: 4px 0;
|
|
||||||
background: rgba(24, 24, 24, 0.96);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #ddd;
|
|
||||||
line-height: 1.2;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.opacity-slider-wrapper {
|
.opacity-slider-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
|
|
|
||||||
|
|
@ -186,10 +186,6 @@
|
||||||
@click.prevent="openFusion">
|
@click.prevent="openFusion">
|
||||||
<svg-icon icon-class="fusion" class="svg-icon" />
|
<svg-icon icon-class="fusion" class="svg-icon" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isFusion" :class="['tool-item', activeTool === 'FusionJumpToPointTool' ? 'tool-item-active' : '']"
|
|
||||||
:title="$t('trials:reading:button:crosshairsPosition')" @click.prevent="setToolActive('FusionJumpToPointTool')">
|
|
||||||
<svg-icon icon-class="position" class="svg-icon" />
|
|
||||||
</div>
|
|
||||||
<div v-for="tool in tools" :key="tool.toolName"
|
<div v-for="tool in tools" :key="tool.toolName"
|
||||||
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === tool.toolName ? 'tool-item-active' : '']"
|
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === tool.toolName ? 'tool-item-active' : '']"
|
||||||
:style="{ cursor: tool.isDisabled ? 'not-allowed' : 'pointer' }"
|
:style="{ cursor: tool.isDisabled ? 'not-allowed' : 'pointer' }"
|
||||||
|
|
@ -580,7 +576,6 @@ import FusionForm from './FusionForm.vue'
|
||||||
import SegmentForm from './SegmentForm.vue'
|
import SegmentForm from './SegmentForm.vue'
|
||||||
import colorMap from './colorMap.vue'
|
import colorMap from './colorMap.vue'
|
||||||
import RectangleROITool from './tools/RectangleROITool'
|
import RectangleROITool from './tools/RectangleROITool'
|
||||||
import CircleROITool from './tools/CircleROITool'
|
|
||||||
import ScaleOverlayTool from './tools/ScaleOverlayTool'
|
import ScaleOverlayTool from './tools/ScaleOverlayTool'
|
||||||
import SegmentBidirectionalTool from './tools/SegmentBidirectionalTool'
|
import SegmentBidirectionalTool from './tools/SegmentBidirectionalTool'
|
||||||
import FusionJumpToPointTool from './tools/FusionJumpToPointTool'
|
import FusionJumpToPointTool from './tools/FusionJumpToPointTool'
|
||||||
|
|
@ -595,8 +590,6 @@ import md5 from 'js-md5'
|
||||||
const { visibility } = annotation
|
const { visibility } = annotation
|
||||||
const { ViewportType, Events } = Enums
|
const { ViewportType, Events } = Enums
|
||||||
const renderingEngineId = 'myRenderingEngine'
|
const renderingEngineId = 'myRenderingEngine'
|
||||||
const DEFAULT_FUSION_COLOR_MAP_RANGE = 40
|
|
||||||
const DEFAULT_FUSION_COLOR_MAP_UPPER = 6
|
|
||||||
const {
|
const {
|
||||||
ToolGroupManager,
|
ToolGroupManager,
|
||||||
Enums: csToolsEnums,
|
Enums: csToolsEnums,
|
||||||
|
|
@ -615,6 +608,7 @@ const {
|
||||||
ArrowAnnotateTool,
|
ArrowAnnotateTool,
|
||||||
// RectangleROITool,
|
// RectangleROITool,
|
||||||
PlanarFreehandROITool,
|
PlanarFreehandROITool,
|
||||||
|
CircleROITool,
|
||||||
AngleTool,
|
AngleTool,
|
||||||
CobbAngleTool,
|
CobbAngleTool,
|
||||||
EraserTool,
|
EraserTool,
|
||||||
|
|
@ -2488,14 +2482,12 @@ export default {
|
||||||
getCircleROIToolTextLines(data, targetId) {
|
getCircleROIToolTextLines(data, targetId) {
|
||||||
const cachedVolumeStats = data.cachedStats[targetId]
|
const cachedVolumeStats = data.cachedStats[targetId]
|
||||||
const {
|
const {
|
||||||
Modality,
|
|
||||||
radius,
|
radius,
|
||||||
radiusUnit,
|
radiusUnit,
|
||||||
area,
|
area,
|
||||||
mean,
|
mean,
|
||||||
stdDev,
|
stdDev,
|
||||||
max,
|
max,
|
||||||
total,
|
|
||||||
isEmptyArea,
|
isEmptyArea,
|
||||||
areaUnit,
|
areaUnit,
|
||||||
modalityUnit
|
modalityUnit
|
||||||
|
|
@ -2532,10 +2524,6 @@ export default {
|
||||||
textLines.push(`Std Dev: ${this.reRound(stdDev, this.digitPlaces)} ${modalityUnit}`)
|
textLines.push(`Std Dev: ${this.reRound(stdDev, this.digitPlaces)} ${modalityUnit}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total !== undefined && total !== null ) { //&& Modality === 'NM'
|
|
||||||
textLines.push(`Total: ${this.formatStatSum(total)}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return textLines
|
return textLines
|
||||||
},
|
},
|
||||||
getEllipticalROIToolTextLines(data, targetId) {
|
getEllipticalROIToolTextLines(data, targetId) {
|
||||||
|
|
@ -2627,16 +2615,6 @@ export default {
|
||||||
}
|
}
|
||||||
return this.processSingle(result, finalPrecision)
|
return this.processSingle(result, finalPrecision)
|
||||||
},
|
},
|
||||||
formatStatSum(value) {
|
|
||||||
const num = Number(value)
|
|
||||||
if (!Number.isFinite(num)) return value
|
|
||||||
|
|
||||||
if (Math.abs(num - Math.round(num)) < 1e-6) {
|
|
||||||
return String(Math.round(num))
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.reRound(num, this.digitPlaces)
|
|
||||||
},
|
|
||||||
processSingle(str, precision) {
|
processSingle(str, precision) {
|
||||||
const num = parseFloat(str)
|
const num = parseFloat(str)
|
||||||
if (isNaN(num)) return 'NaN'
|
if (isNaN(num)) return 'NaN'
|
||||||
|
|
@ -2667,9 +2645,8 @@ export default {
|
||||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||||
if (!renderingEngine) return
|
if (!renderingEngine) return
|
||||||
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
|
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
|
||||||
if (!toolGroup) return
|
const instance = toolGroup?.getToolInstance(FusionJumpToPointTool.toolName)
|
||||||
const instance = toolGroup.getToolInstance(FusionJumpToPointTool.toolName)
|
if (!instance?.setPoint) return
|
||||||
if (!instance || !instance.setPoint) return
|
|
||||||
|
|
||||||
instance.setPoint(worldPoint, viewportId, renderingEngine.id, {
|
instance.setPoint(worldPoint, viewportId, renderingEngine.id, {
|
||||||
jumpToTargetViewports: false,
|
jumpToTargetViewports: false,
|
||||||
|
|
@ -2677,53 +2654,28 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setFusionMipJumpEnabled(enabled) {
|
setFusionMipJumpEnabled(enabled) {
|
||||||
|
if (!this.isFusion) return
|
||||||
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
|
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
|
||||||
if (!toolGroup || !toolGroup.hasTool(FusionJumpToPointTool.toolName)) return
|
if (!toolGroup || !toolGroup.hasTool(FusionJumpToPointTool.toolName)) return
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!this.isFusion) return
|
|
||||||
toolGroup.setToolActive(FusionJumpToPointTool.toolName, {
|
toolGroup.setToolActive(FusionJumpToPointTool.toolName, {
|
||||||
bindings: [{ mouseButton: MouseBindings.Primary }]
|
bindings: [{ mouseButton: MouseBindings.Primary }]
|
||||||
})
|
})
|
||||||
this.dispatchFusionCenterPoint()
|
this.dispatchFusionCenterPoint()
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolDisabled(FusionJumpToPointTool.toolName)
|
toolGroup.setToolDisabled(FusionJumpToPointTool.toolName)
|
||||||
this.clearFusionJumpToPointAnnotations()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearFusionJumpToPointAnnotations() {
|
|
||||||
const annotations = cornerstoneTools.annotation.state.getAllAnnotations() || []
|
|
||||||
const removeList = []
|
|
||||||
for (let i = 0; i < annotations.length; i++) {
|
|
||||||
const item = annotations[i]
|
|
||||||
if (!item || !item.metadata) continue
|
|
||||||
if (item.metadata.toolName !== FusionJumpToPointTool.toolName) continue
|
|
||||||
removeList.push(item)
|
|
||||||
}
|
|
||||||
if (!removeList.length) return
|
|
||||||
removeList.forEach(i => {
|
|
||||||
cornerstoneTools.annotation.state.removeAnnotation(i.annotationUID)
|
|
||||||
})
|
|
||||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
|
||||||
if (!renderingEngine) return
|
|
||||||
const viewportIds = ['viewport-fusion-0', 'viewport-fusion-1', 'viewport-fusion-2', 'viewport-fusion-3', 'viewport-fusion-hidden-sag']
|
|
||||||
viewportIds.forEach(viewportId => {
|
|
||||||
const viewport = renderingEngine.getViewport(viewportId)
|
|
||||||
if (viewport && viewport.render) {
|
|
||||||
viewport.render()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
dispatchFusionCenterPoint(retryCount = 0) {
|
dispatchFusionCenterPoint(retryCount = 0) {
|
||||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||||
if (!renderingEngine) return
|
if (!renderingEngine) return
|
||||||
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
|
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
|
||||||
if (!toolGroup || !toolGroup.getToolInstance) return
|
const instance = toolGroup?.getToolInstance?.(FusionJumpToPointTool.toolName)
|
||||||
const instance = toolGroup.getToolInstance(FusionJumpToPointTool.toolName)
|
if (!instance?.setPoint) return
|
||||||
if (!instance || !instance.setPoint) return
|
|
||||||
const candidates = ['viewport-fusion-2', 'viewport-fusion-1', 'viewport-fusion-0']
|
const candidates = ['viewport-fusion-2', 'viewport-fusion-1', 'viewport-fusion-0']
|
||||||
for (const viewportId of candidates) {
|
for (const viewportId of candidates) {
|
||||||
const viewport = renderingEngine.getViewport(viewportId)
|
const viewport = renderingEngine.getViewport(viewportId)
|
||||||
if (!viewport || !viewport.canvasToWorld || !viewport.element) continue
|
if (!viewport?.canvasToWorld || !viewport?.element) continue
|
||||||
const width = viewport.element.clientWidth
|
const width = viewport.element.clientWidth
|
||||||
const height = viewport.element.clientHeight
|
const height = viewport.element.clientHeight
|
||||||
let worldPoint = null
|
let worldPoint = null
|
||||||
|
|
@ -2731,10 +2683,7 @@ export default {
|
||||||
worldPoint = viewport.canvasToWorld([width / 2, height / 2])
|
worldPoint = viewport.canvasToWorld([width / 2, height / 2])
|
||||||
}
|
}
|
||||||
if ((!worldPoint || worldPoint.length < 3) && viewport.getCamera) {
|
if ((!worldPoint || worldPoint.length < 3) && viewport.getCamera) {
|
||||||
const camera = viewport.getCamera()
|
worldPoint = viewport.getCamera()?.focalPoint
|
||||||
if (camera && camera.focalPoint) {
|
|
||||||
worldPoint = camera.focalPoint
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!worldPoint || worldPoint.length < 3) continue
|
if (!worldPoint || worldPoint.length < 3) continue
|
||||||
instance.setPoint(worldPoint, viewportId, renderingEngine.id, {
|
instance.setPoint(worldPoint, viewportId, renderingEngine.id, {
|
||||||
|
|
@ -2775,8 +2724,6 @@ export default {
|
||||||
}
|
}
|
||||||
this.setFusionMipJumpEnabled(true)
|
this.setFusionMipJumpEnabled(true)
|
||||||
// this.setFusionMipRotateEnabled(true)
|
// this.setFusionMipRotateEnabled(true)
|
||||||
} else if (toolName === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
|
|
@ -2789,37 +2736,31 @@ export default {
|
||||||
}
|
}
|
||||||
this.setFusionMipJumpEnabled(true)
|
this.setFusionMipJumpEnabled(true)
|
||||||
// this.setFusionMipRotateEnabled(true)
|
// this.setFusionMipRotateEnabled(true)
|
||||||
} else if (this.activeTool === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toolName === FusionJumpToPointTool.toolName) {
|
toolGroup.setToolActive(toolName, {
|
||||||
this.setFusionMipJumpEnabled(true)
|
bindings: [{ mouseButton: MouseBindings.Primary }]
|
||||||
} else {
|
})
|
||||||
toolGroup.setToolActive(toolName, {
|
if (toolName === CrosshairsTool.toolName) {
|
||||||
bindings: [{ mouseButton: MouseBindings.Primary }]
|
if (this.isFusion) {
|
||||||
})
|
// const instance = toolGroup.getToolInstance?.(CrosshairsTool.toolName)
|
||||||
if (toolName === CrosshairsTool.toolName) {
|
// if (instance && !instance.__fusionSameForPatched) {
|
||||||
if (this.isFusion) {
|
// instance.__fusionSameForPatched = true
|
||||||
// const instance = toolGroup.getToolInstance?.(CrosshairsTool.toolName)
|
// const original = instance._checkIfViewportsRenderingSameScene?.bind(instance)
|
||||||
// if (instance && !instance.__fusionSameForPatched) {
|
// instance._checkIfViewportsRenderingSameScene = (viewport, otherViewport) => {
|
||||||
// instance.__fusionSameForPatched = true
|
// try {
|
||||||
// const original = instance._checkIfViewportsRenderingSameScene?.bind(instance)
|
// const a = viewport?.getFrameOfReferenceUID?.()
|
||||||
// instance._checkIfViewportsRenderingSameScene = (viewport, otherViewport) => {
|
// const b = otherViewport?.getFrameOfReferenceUID?.()
|
||||||
// try {
|
// if (a && b && a === b) return true
|
||||||
// const a = viewport?.getFrameOfReferenceUID?.()
|
// } catch (e) { }
|
||||||
// const b = otherViewport?.getFrameOfReferenceUID?.()
|
// return original ? original(viewport, otherViewport) : true
|
||||||
// if (a && b && a === b) return true
|
// }
|
||||||
// } catch (e) { }
|
// }
|
||||||
// return original ? original(viewport, otherViewport) : true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
// this.setFusionMipRotateEnabled(false)
|
|
||||||
}
|
}
|
||||||
|
this.setFusionMipJumpEnabled(false)
|
||||||
|
// this.setFusionMipRotateEnabled(false)
|
||||||
}
|
}
|
||||||
this.activeTool = toolName
|
this.activeTool = toolName
|
||||||
}
|
}
|
||||||
|
|
@ -2866,8 +2807,6 @@ export default {
|
||||||
if (toolGroup.hasTool(this.activeTool)) {
|
if (toolGroup.hasTool(this.activeTool)) {
|
||||||
toolGroup.setToolDisabled(this.activeTool)
|
toolGroup.setToolDisabled(this.activeTool)
|
||||||
}
|
}
|
||||||
} else if (toolName === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
|
|
@ -2878,8 +2817,6 @@ export default {
|
||||||
if (toolGroup.hasTool(this.activeTool)) {
|
if (toolGroup.hasTool(this.activeTool)) {
|
||||||
toolGroup.setToolDisabled(this.activeTool)
|
toolGroup.setToolDisabled(this.activeTool)
|
||||||
}
|
}
|
||||||
} else if (this.activeTool === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
|
|
@ -2907,8 +2844,6 @@ export default {
|
||||||
if (toolGroup.hasTool(this.activeTool)) {
|
if (toolGroup.hasTool(this.activeTool)) {
|
||||||
toolGroup.setToolDisabled(this.activeTool)
|
toolGroup.setToolDisabled(this.activeTool)
|
||||||
}
|
}
|
||||||
} else if (this.activeTool === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
|
|
@ -2931,8 +2866,6 @@ export default {
|
||||||
if (toolGroup.hasTool(this.activeTool)) {
|
if (toolGroup.hasTool(this.activeTool)) {
|
||||||
toolGroup.setToolDisabled(this.activeTool)
|
toolGroup.setToolDisabled(this.activeTool)
|
||||||
}
|
}
|
||||||
} else if (this.activeTool === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
|
|
@ -2965,8 +2898,6 @@ export default {
|
||||||
if (toolGroup.hasTool(this.activeTool)) {
|
if (toolGroup.hasTool(this.activeTool)) {
|
||||||
toolGroup.setToolDisabled(this.activeTool)
|
toolGroup.setToolDisabled(this.activeTool)
|
||||||
}
|
}
|
||||||
} else if (this.activeTool === FusionJumpToPointTool.toolName) {
|
|
||||||
this.setFusionMipJumpEnabled(false)
|
|
||||||
} else {
|
} else {
|
||||||
toolGroup.setToolPassive(this.activeTool)
|
toolGroup.setToolPassive(this.activeTool)
|
||||||
}
|
}
|
||||||
|
|
@ -3100,7 +3031,7 @@ export default {
|
||||||
this.resetCrosshairsAnnotationsForViewports(fusionAllViewportIds)
|
this.resetCrosshairsAnnotationsForViewports(fusionAllViewportIds)
|
||||||
renderingEngine.render()
|
renderingEngine.render()
|
||||||
this.dispatchFusionCenterPoint()
|
this.dispatchFusionCenterPoint()
|
||||||
if (Number.isFinite(this.fusionOverlayDefaultUpper) && Number.isFinite(this.fusionOverlayDefaultRange)) {
|
if (this.fusionOverlayModality === 'NM' && Number.isFinite(this.fusionOverlayDefaultUpper) && Number.isFinite(this.fusionOverlayDefaultRange)) {
|
||||||
this.lastUpper = null
|
this.lastUpper = null
|
||||||
this.hasFusionUpperInitialized = false
|
this.hasFusionUpperInitialized = false
|
||||||
if (this.$refs.colorMap) {
|
if (this.$refs.colorMap) {
|
||||||
|
|
@ -3691,14 +3622,8 @@ export default {
|
||||||
DicomEvent.$emit('SegmentationLoading', `${this.viewportKey}-${this.activeViewportIndex}`)
|
DicomEvent.$emit('SegmentationLoading', `${this.viewportKey}-${this.activeViewportIndex}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.activeTool && this.activeTool !== CrosshairsTool.toolName && this.activeTool !== FusionJumpToPointTool.toolName) {
|
if (this.activeTool !== CrosshairsTool.toolName) {
|
||||||
const toolGroupId = this.getActiveToolGroupId()
|
this.setToolsPassive()
|
||||||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
|
||||||
if (toolGroup && toolGroup.hasTool(this.activeTool)) {
|
|
||||||
toolGroup.setToolActive(this.activeTool, {
|
|
||||||
bindings: [{ mouseButton: MouseBindings.Primary }]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getRelatedSeries(visitTaskInfo, baselineSeries) {
|
getRelatedSeries(visitTaskInfo, baselineSeries) {
|
||||||
|
|
@ -4410,6 +4335,9 @@ export default {
|
||||||
this.$refs[`viewport-1`][0].setSeriesInfo(pt)
|
this.$refs[`viewport-1`][0].setSeriesInfo(pt)
|
||||||
this.$refs[`viewport-2`][0].setSeriesInfo(pt)
|
this.$refs[`viewport-2`][0].setSeriesInfo(pt)
|
||||||
this.$refs[`viewport-3`][0].setSeriesInfo(pt)
|
this.$refs[`viewport-3`][0].setSeriesInfo(pt)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.setFusionMipJumpEnabled(true)
|
||||||
|
})
|
||||||
// this.resetAnnotation = false
|
// this.resetAnnotation = false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -4446,17 +4374,29 @@ export default {
|
||||||
await this.initFusionHiddenSagViewport(pt)
|
await this.initFusionHiddenSagViewport(pt)
|
||||||
// this.resetAnnotation = false
|
// this.resetAnnotation = false
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const defaultRange = this.getFusionDefaultColorMapRange(pt)
|
|
||||||
const defaultUpper = this.getFusionDefaultColorMapUpper(defaultRange)
|
|
||||||
this.$refs[`colorMap`].init()
|
this.$refs[`colorMap`].init()
|
||||||
this.fusionOverlayDefaultRange = defaultRange
|
if (this.fusionOverlayModality === 'NM') {
|
||||||
this.fusionOverlayDefaultUpper = defaultUpper
|
const imageIds = this.sortImageIdsByImagePositionPatient(pt.ImageIds)
|
||||||
this.lastUpper = null
|
const imageId = imageIds?.[0]
|
||||||
this.hasFusionUpperInitialized = false
|
const voiLutModule = imageId ? metaData.get('voiLutModule', imageId) : null
|
||||||
this.$refs.colorMap.range = defaultRange
|
const rawWidth = Array.isArray(voiLutModule?.windowWidth) ? voiLutModule.windowWidth[0] : voiLutModule?.windowWidth
|
||||||
this.$refs.colorMap.upper = defaultUpper
|
const nmMax = Number(rawWidth)
|
||||||
this.$refs.colorMap.upperRangeChange(defaultRange)
|
if (Number.isFinite(nmMax) && nmMax > 0) {
|
||||||
this.voiChange(defaultUpper)
|
const halfMax = Math.round(nmMax * 0.5)
|
||||||
|
this.fusionOverlayDefaultRange = Math.round(nmMax)
|
||||||
|
this.fusionOverlayDefaultUpper = halfMax
|
||||||
|
this.lastUpper = null
|
||||||
|
this.hasFusionUpperInitialized = false
|
||||||
|
this.$refs.colorMap.range = Math.round(nmMax)
|
||||||
|
this.$refs.colorMap.upper = halfMax
|
||||||
|
this.$refs.colorMap.upperRangeChange(Math.round(nmMax))
|
||||||
|
this.voiChange(halfMax)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fusionOverlayDefaultRange = null
|
||||||
|
this.fusionOverlayDefaultUpper = null
|
||||||
|
}
|
||||||
|
this.setFusionMipJumpEnabled(true)
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
|
@ -4540,41 +4480,6 @@ export default {
|
||||||
pairs.sort((a, b) => b.distance - a.distance);
|
pairs.sort((a, b) => b.distance - a.distance);
|
||||||
return pairs.map((p) => p.imageId);
|
return pairs.map((p) => p.imageId);
|
||||||
},
|
},
|
||||||
getFusionDefaultColorMapRange(series) {
|
|
||||||
if (this.fusionOverlayModality !== 'NM') {
|
|
||||||
return DEFAULT_FUSION_COLOR_MAP_RANGE
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageIds = this.sortImageIdsByImagePositionPatient(series?.ImageIds || [])
|
|
||||||
const imageId = imageIds?.[0]
|
|
||||||
if (!imageId) {
|
|
||||||
return DEFAULT_FUSION_COLOR_MAP_RANGE
|
|
||||||
}
|
|
||||||
|
|
||||||
const voiLutModule = metaData.get('voiLutModule', imageId)
|
|
||||||
const rawWidth = Array.isArray(voiLutModule?.windowWidth)
|
|
||||||
? voiLutModule.windowWidth[0]
|
|
||||||
: voiLutModule?.windowWidth
|
|
||||||
const windowWidth = Math.round(Number(rawWidth))
|
|
||||||
|
|
||||||
if (!Number.isFinite(windowWidth) || windowWidth <= 0) {
|
|
||||||
return DEFAULT_FUSION_COLOR_MAP_RANGE
|
|
||||||
}
|
|
||||||
|
|
||||||
return windowWidth
|
|
||||||
},
|
|
||||||
getFusionDefaultColorMapUpper(range) {
|
|
||||||
if (this.fusionOverlayModality !== 'NM') {
|
|
||||||
return DEFAULT_FUSION_COLOR_MAP_UPPER
|
|
||||||
}
|
|
||||||
|
|
||||||
const safeRange = Number(range)
|
|
||||||
if (!Number.isFinite(safeRange) || safeRange <= 0) {
|
|
||||||
return DEFAULT_FUSION_COLOR_MAP_UPPER
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(safeRange * 0.5)
|
|
||||||
},
|
|
||||||
upperRangeChange(upper) {
|
upperRangeChange(upper) {
|
||||||
if (!this.hasFusionUpperInitialized) {
|
if (!this.hasFusionUpperInitialized) {
|
||||||
if (!upper) return
|
if (!upper) return
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ const config = {
|
||||||
'name': '圆形工具',
|
'name': '圆形工具',
|
||||||
'icon': 'oval',
|
'icon': 'oval',
|
||||||
'toolName': 'CircleROI',
|
'toolName': 'CircleROI',
|
||||||
'props': ['radius', 'area', 'mean', 'max', 'stdDev', 'total'],
|
'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
|
||||||
'i18nKey': 'trials:reading:button:Circle',
|
'i18nKey': 'trials:reading:button:Circle',
|
||||||
'isDisabled': false,
|
'isDisabled': false,
|
||||||
'disabledReason': ''
|
'disabledReason': ''
|
||||||
|
|
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
import { EPSILON, utilities as csUtils } from "@cornerstonejs/core"
|
|
||||||
import * as cornerstoneTools from "@cornerstonejs/tools"
|
|
||||||
const { triggerAnnotationModified } = cornerstoneTools.annotation.state
|
|
||||||
|
|
||||||
const { transformWorldToIndex } = csUtils
|
|
||||||
const { BaseTool, Enums, utilities } = cornerstoneTools
|
|
||||||
const { MeasurementType, ChangeTypes } = Enums
|
|
||||||
const { getCanvasCircleCorners } = utilities.math.circle
|
|
||||||
const { pointInEllipse } = utilities.math.ellipse
|
|
||||||
const { getCalibratedLengthUnitsAndScale } = utilities
|
|
||||||
|
|
||||||
class CircleROITool extends cornerstoneTools.CircleROITool {
|
|
||||||
static toolName = "CircleROI"
|
|
||||||
|
|
||||||
constructor(toolProps = {}, defaultToolProps) {
|
|
||||||
super(toolProps, defaultToolProps)
|
|
||||||
|
|
||||||
this._calculateCachedStats = (
|
|
||||||
annotation,
|
|
||||||
viewport,
|
|
||||||
renderingEngine,
|
|
||||||
enabledElement
|
|
||||||
) => {
|
|
||||||
if (!this.configuration.calculateStats) return
|
|
||||||
|
|
||||||
const { data } = annotation
|
|
||||||
const { element } = viewport
|
|
||||||
const wasInvalidated = annotation.invalidated
|
|
||||||
const { points } = data.handles
|
|
||||||
const canvasCoordinates = points.map((point) =>
|
|
||||||
viewport.worldToCanvas(point)
|
|
||||||
)
|
|
||||||
const canvasCenter = canvasCoordinates[0]
|
|
||||||
const canvasTop = canvasCoordinates[1]
|
|
||||||
const [topLeftCanvas, bottomRightCanvas] = getCanvasCircleCorners([
|
|
||||||
canvasCenter,
|
|
||||||
canvasTop
|
|
||||||
])
|
|
||||||
const topLeftWorld = viewport.canvasToWorld(topLeftCanvas)
|
|
||||||
const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas)
|
|
||||||
const { cachedStats } = data
|
|
||||||
const targetIds = Object.keys(cachedStats)
|
|
||||||
|
|
||||||
for (let i = 0; i < targetIds.length; i++) {
|
|
||||||
const targetId = targetIds[i];
|
|
||||||
const image = this.getTargetImageData(targetId)
|
|
||||||
|
|
||||||
if (!image) {
|
|
||||||
console.warn("image not found for stats:", targetId)
|
|
||||||
delete cachedStats[targetId]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const { dimensions, imageData, metadata, voxelManager } = image
|
|
||||||
const handles = points.map((point) => imageData.worldToIndex(point))
|
|
||||||
const calibrate = getCalibratedLengthUnitsAndScale(image, handles)
|
|
||||||
const radius = CircleROITool.calculateLengthInIndex(
|
|
||||||
calibrate,
|
|
||||||
handles.slice(0, 2)
|
|
||||||
)
|
|
||||||
const area = Math.PI * radius * radius
|
|
||||||
const perimeter = 2 * Math.PI * radius
|
|
||||||
const isEmptyArea = radius === 0
|
|
||||||
const { unit, areaUnit } = calibrate
|
|
||||||
const namedArea = {
|
|
||||||
name: "area",
|
|
||||||
value: area,
|
|
||||||
unit: areaUnit,
|
|
||||||
type: MeasurementType.Area,
|
|
||||||
}
|
|
||||||
const namedCircumference = {
|
|
||||||
name: "circumference",
|
|
||||||
value: perimeter,
|
|
||||||
unit,
|
|
||||||
type: MeasurementType.Linear,
|
|
||||||
}
|
|
||||||
const namedRadius = {
|
|
||||||
name: "radius",
|
|
||||||
value: radius,
|
|
||||||
unit,
|
|
||||||
type: MeasurementType.Linear,
|
|
||||||
}
|
|
||||||
const statsArray = [namedArea, namedRadius, namedCircumference]
|
|
||||||
|
|
||||||
cachedStats[targetId] = {
|
|
||||||
Modality: metadata.Modality,
|
|
||||||
area,
|
|
||||||
isEmptyArea,
|
|
||||||
areaUnit,
|
|
||||||
radius,
|
|
||||||
radiusUnit: unit,
|
|
||||||
perimeter,
|
|
||||||
statsArray,
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos1Index = transformWorldToIndex(imageData, topLeftWorld)
|
|
||||||
const pos2Index = transformWorldToIndex(imageData, bottomRightWorld)
|
|
||||||
|
|
||||||
this.isHandleOutsideImage = !BaseTool.isInsideVolume(dimensions, [
|
|
||||||
pos1Index,
|
|
||||||
pos2Index,
|
|
||||||
])
|
|
||||||
if (!this.isHandleOutsideImage) {
|
|
||||||
const iMin = Math.min(pos1Index[0], pos2Index[0])
|
|
||||||
const iMax = Math.max(pos1Index[0], pos2Index[0])
|
|
||||||
const jMin = Math.min(pos1Index[1], pos2Index[1])
|
|
||||||
const jMax = Math.max(pos1Index[1], pos2Index[1])
|
|
||||||
const kMin = Math.min(pos1Index[2], pos2Index[2])
|
|
||||||
const kMax = Math.max(pos1Index[2], pos2Index[2])
|
|
||||||
const boundsIJK = [
|
|
||||||
[iMin, iMax],
|
|
||||||
[jMin, jMax],
|
|
||||||
[kMin, kMax],
|
|
||||||
]
|
|
||||||
const center = points[0];
|
|
||||||
const xRadius = Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2
|
|
||||||
const yRadius = Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2
|
|
||||||
const zRadius = Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2
|
|
||||||
const ellipseObj = {
|
|
||||||
center,
|
|
||||||
xRadius: xRadius < EPSILON / 2 ? 0 : xRadius,
|
|
||||||
yRadius: yRadius < EPSILON / 2 ? 0 : yRadius,
|
|
||||||
zRadius: zRadius < EPSILON / 2 ? 0 : zRadius,
|
|
||||||
}
|
|
||||||
const pixelUnitsOptions = {
|
|
||||||
isPreScaled: utilities.viewport.isViewportPreScaled(
|
|
||||||
viewport,
|
|
||||||
targetId
|
|
||||||
),
|
|
||||||
isSuvScaled: this.isSuvScaled(
|
|
||||||
viewport,
|
|
||||||
targetId,
|
|
||||||
annotation.metadata.referencedImageId
|
|
||||||
),
|
|
||||||
}
|
|
||||||
const modalityUnit = utilities.getPixelValueUnits(
|
|
||||||
metadata.Modality,
|
|
||||||
annotation.metadata.referencedImageId,
|
|
||||||
pixelUnitsOptions
|
|
||||||
)
|
|
||||||
let pointsInShape
|
|
||||||
if (voxelManager) {
|
|
||||||
pointsInShape = voxelManager.forEach(
|
|
||||||
this.configuration.statsCalculator.statsCallback,
|
|
||||||
{
|
|
||||||
isInObject: (pointLPS) =>
|
|
||||||
pointInEllipse(ellipseObj, pointLPS, { fast: true }),
|
|
||||||
boundsIJK,
|
|
||||||
imageData,
|
|
||||||
returnPoints: this.configuration.storePointData,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const stats = this.configuration.statsCalculator.getStatistics()
|
|
||||||
const mean = stats.mean?.value
|
|
||||||
const count = stats.count?.value
|
|
||||||
const total =
|
|
||||||
Array.isArray(mean) && Number.isFinite(count)
|
|
||||||
? mean.map((value) => value * count)
|
|
||||||
: Number.isFinite(mean) && Number.isFinite(count)
|
|
||||||
? mean * count
|
|
||||||
: undefined;
|
|
||||||
cachedStats[targetId] = {
|
|
||||||
...cachedStats[targetId],
|
|
||||||
Modality: metadata.Modality,
|
|
||||||
mean,
|
|
||||||
max: stats.max?.value,
|
|
||||||
min: stats.min?.value,
|
|
||||||
total,
|
|
||||||
count,
|
|
||||||
pointsInShape,
|
|
||||||
stdDev: stats.stdDev?.value,
|
|
||||||
modalityUnit,
|
|
||||||
statsArray: [...statsArray, ...stats.array],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
annotation.invalidated = false
|
|
||||||
if (wasInvalidated) {
|
|
||||||
triggerAnnotationModified(
|
|
||||||
annotation,
|
|
||||||
element,
|
|
||||||
ChangeTypes.StatsUpdated
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cachedStats
|
|
||||||
}
|
|
||||||
|
|
||||||
this._throttledCalculateCachedStats = utilities.throttle(
|
|
||||||
this._calculateCachedStats,
|
|
||||||
100,
|
|
||||||
{ trailing: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CircleROITool
|
|
||||||
Loading…
Reference in New Issue