502 lines
16 KiB
Plaintext
502 lines
16 KiB
Plaintext
<template>
|
|
<div
|
|
class="viewport_container"
|
|
:class="['item',activeIndex === index?'item_active':'']"
|
|
@mousemove="sliderMousemove"
|
|
@mouseup="sliderMouseup"
|
|
>
|
|
<div :id="`viewport${index}`" style="height: 100%;" />
|
|
<!-- 序列信息 -->
|
|
<div
|
|
v-if="seriesInfo.taskBlindName"
|
|
class="seriesInfo_wrapper"
|
|
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
|
>
|
|
<h2 v-if="isReadingShowSubjectInfo" class="taskInfo_container">
|
|
{{ subjectCode }} {{ seriesInfo.taskBlindName }}
|
|
</h2>
|
|
<div v-if="index === 1 || index === 2">
|
|
Series: #{{ seriesInfo.seriesNumber }}
|
|
</div>
|
|
<div v-if="index !== 4">Image: #{{ `${seriesInfo.imageIdIndex + 1} / ${seriesInfo.imageIds.length}` }}</div>
|
|
<div v-if="index === 1 || index === 2">{{ seriesInfo.modality }}</div>
|
|
</div>
|
|
<!-- 描述信息 -->
|
|
<div
|
|
v-if="seriesInfo.description && (index === 1 || index === 2)"
|
|
class="descInfo_wrapper"
|
|
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
|
>
|
|
<div>{{ seriesInfo.description }}</div>
|
|
</div>
|
|
|
|
<!-- 图像信息 -->
|
|
<div
|
|
v-if="seriesInfo.description && (index === 1 || index === 2 || index === 3)"
|
|
class="imageInfo_wrapper_l"
|
|
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
|
>
|
|
<div v-show="mousePosition.index.length > 0">
|
|
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
|
|
</div>
|
|
<div v-if="(seriesInfo.modality === 'CT' || seriesInfo.modality === 'DR' || seriesInfo.modality === 'CR') && mousePosition.value">
|
|
HU: {{ mousePosition.value }}
|
|
</div>
|
|
<div v-else-if="(seriesInfo.modality === 'PT' && mousePosition.value)">
|
|
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.value.toFixed(3) :mousePosition.value.toFixed(digitPlaces) }}
|
|
</div>
|
|
<div v-else-if="mousePosition.value">
|
|
Density: {{ mousePosition.value }}
|
|
</div>
|
|
<div v-if="index === 1 || index === 2">
|
|
W*H: {{ imageInfo.size }}
|
|
</div>
|
|
|
|
<div v-if="index === 1 || index === 2">Zoom: {{ imageInfo.zoom }}</div>
|
|
</div>
|
|
<div
|
|
v-if="seriesInfo.description && (index === 1 || index === 2 || index === 3)"
|
|
class="imageInfo_wrapper_r"
|
|
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
|
>
|
|
<div v-show="imageInfo.location && index !== 3 ">Location: {{ imageInfo.location }}</div>
|
|
<div v-show="seriesInfo.sliceThickness && index !== 3">Slice Thickness: {{ Number(seriesInfo.sliceThickness).toFixed(2) }}mm</div>
|
|
<div v-show="imageInfo.wwwc ">WW/WL: {{ imageInfo.wwwc }}</div>
|
|
</div>
|
|
<div v-if="index!==4" ref="sliderBox" class="slider_box" :style="{background: index === 2?'#ddd':'#333'}" @click.stop="goViewer($event)">
|
|
<div :style="{top: sliderBoxHeight + '%'}" class="box" @mousedown.stop="sliderMousedown($event)" />
|
|
</div>
|
|
|
|
<div v-if="presetName" class="color_bar">
|
|
<canvas id="colorBar_Canvas" />
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import {
|
|
getRenderingEngine,
|
|
metaData,
|
|
utilities,
|
|
// cache,
|
|
// StackViewport,
|
|
// BaseVolumeViewport,
|
|
// getEnabledElement,
|
|
// getEnabledElementByIds,
|
|
// eventTarget,
|
|
Enums } from '@cornerstonejs/core'
|
|
// import * as cornerstonejs from '@cornerstonejs/core'
|
|
// import * as cornerstoneTools from '@cornerstonejs/tools'
|
|
import {
|
|
utilities as toosUtilities,
|
|
annotation
|
|
// cursors
|
|
} from '@cornerstonejs/tools'
|
|
// import FusionEvent from './FusionEvent'
|
|
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
|
|
const {
|
|
VOLUME_NEW_IMAGE
|
|
// VOLUME_LOADED,
|
|
// IMAGE_VOLUME_MODIFIED,
|
|
// VOLUME_VIEWPORT_NEW_VOLUME
|
|
} = Enums.Events
|
|
|
|
var renderingEngine
|
|
var viewport
|
|
export default {
|
|
name: 'Viewport',
|
|
props: {
|
|
activeIndex: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
index: {
|
|
// 1:ct 2:pet 3:fusion 4:mip
|
|
type: Number,
|
|
required: true
|
|
},
|
|
isReadingShowSubjectInfo: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
seriesInfo: {
|
|
type: Object,
|
|
default() {
|
|
return {}
|
|
}
|
|
},
|
|
renderingEngineId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
viewportId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
volume: {
|
|
type: Object,
|
|
default() {
|
|
return {}
|
|
}
|
|
},
|
|
measureDatas: {
|
|
type: Array,
|
|
default() {
|
|
return []
|
|
}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
subjectCode: '',
|
|
mousePosition: { index: [], value: null, modalityUnit: '', world: [], ctVal: null, ptVal: null },
|
|
digitPlaces: -1,
|
|
imageInfo: {
|
|
zoom: null,
|
|
size: null,
|
|
location: null,
|
|
sliceThickness: null,
|
|
wwwc: null
|
|
},
|
|
sliderBoxHeight: 0,
|
|
sliderInfo: {
|
|
oldB: null,
|
|
oldM: null,
|
|
isMove: false
|
|
},
|
|
isFirstRender: true,
|
|
defaultWindowLevel: {},
|
|
presetName: ''
|
|
}
|
|
},
|
|
watch: {
|
|
activeIndex: {
|
|
handler(v) {
|
|
console.log('activeIndex ', v)
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
// console.log(toosUtilities)
|
|
this.subjectCode = this.$route.query.subjectCode
|
|
var element = document.getElementById(`viewport${this.index}`)
|
|
|
|
element.addEventListener(VOLUME_NEW_IMAGE, this.handleVolumeNewImage)
|
|
element.addEventListener(Enums.Events.CAMERA_MODIFIED, this.handleCameraModified)
|
|
element.addEventListener(Enums.Events.VOI_MODIFIED, this.handleVOIModified)
|
|
if (this.index !== 4) {
|
|
element.addEventListener('CORNERSTONE_TOOLS_MOUSE_MOVE', this.handleMouseMove)
|
|
}
|
|
element.addEventListener('mouseleave', this.handleMouseLeave)
|
|
element.addEventListener('mousemove', () => {
|
|
element.style.cursor = 'default'
|
|
})
|
|
document.addEventListener('mouseup', () => {
|
|
this.sliderMouseup()
|
|
})
|
|
// element.addEventListener('CORNERSTONE_TOOLS_MOUSE_UP', (e) => {
|
|
// element.style.cursor = 'default'
|
|
// })
|
|
},
|
|
destroyed() {
|
|
|
|
},
|
|
methods: {
|
|
handleVolumeNewImage(e) {
|
|
const { imageIndex } = e.detail
|
|
this.seriesInfo.imageIdIndex = imageIndex
|
|
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
viewport = renderingEngine.getViewport(this.viewportId)
|
|
if (viewport) {
|
|
var zoom = viewport.getZoom()
|
|
|
|
if (zoom) {
|
|
this.imageInfo.zoom = zoom.toFixed(4)
|
|
}
|
|
var imageId = viewport.getCurrentImageId()
|
|
if (imageId) {
|
|
const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
|
|
this.imageInfo.size = `${imagePlaneModule.columns}*${imagePlaneModule.rows}`
|
|
this.imageInfo.location = imagePlaneModule.sliceLocation
|
|
// this.getOrientationMarker(imagePlaneModule)
|
|
this.sliderBoxHeight = imageIndex * 100 / (this.seriesInfo.imageIds.length - 1)
|
|
}
|
|
var properties = viewport.getProperties()
|
|
if (properties && properties.voiRange) {
|
|
var { lower, upper } = properties.voiRange
|
|
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper)
|
|
this.defaultWindowLevel.windowWidth = windowWidth
|
|
this.defaultWindowLevel.windowCenter = windowCenter
|
|
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
|
}
|
|
if (this.presetName) {
|
|
this.renderColorBar(this.presetName)
|
|
}
|
|
}
|
|
},
|
|
getOrientationMarker(imagePlane) {
|
|
// const enabledElement = cornerstone.getEnabledElement(element)
|
|
// const imagePlane = cornerstone.metaData.get(
|
|
// 'imagePlaneModule',
|
|
// enabledElement.image.imageId
|
|
// )
|
|
|
|
if (!imagePlane || !imagePlane.rowCosines || !imagePlane.columnCosines) {
|
|
return
|
|
}
|
|
console.log(imagePlane.rowCosines)
|
|
const row = toosUtilities.orientation.getOrientationStringLPS(imagePlane.rowCosines)
|
|
const column = toosUtilities.orientation.getOrientationStringLPS(imagePlane.columnCosines)
|
|
const oppositeRow = toosUtilities.orientation.invertOrientationStringLPS(row)
|
|
const oppositeColumn = toosUtilities.orientation.invertOrientationStringLPS(column)
|
|
// const markers = {
|
|
// top: oppositeColumn,
|
|
// bottom: column,
|
|
// left: oppositeRow,
|
|
// right: row
|
|
// }
|
|
// if (!markers) {
|
|
// return
|
|
// }
|
|
// this.orientationMarkers = [oppositeColumn, row, column, oppositeRow]
|
|
// this.originalMarkers = [oppositeColumn, row, column, oppositeRow]
|
|
console.log(oppositeColumn, row, column, oppositeRow)
|
|
// this.setMarkers()
|
|
},
|
|
handleCameraModified(e) {
|
|
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
viewport = renderingEngine.getViewport(this.viewportId)
|
|
if (!viewport) return
|
|
var zoom = viewport.getZoom()
|
|
if (!zoom) return
|
|
this.imageInfo.zoom = zoom.toFixed(4)
|
|
},
|
|
handleVOIModified(e) {
|
|
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
viewport = renderingEngine.getViewport(this.viewportId)
|
|
if (!viewport) return
|
|
var properties = viewport.getProperties()
|
|
if (properties && properties.voiRange) {
|
|
var { lower, upper } = properties.voiRange
|
|
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
|
|
lower,
|
|
upper
|
|
)
|
|
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
|
}
|
|
},
|
|
handleMouseMove(e) {
|
|
const { currentPoints } = e.detail
|
|
const worldPoint = currentPoints.world
|
|
const { imageData } = this.volume
|
|
const index = imageData.worldToIndex(worldPoint)
|
|
|
|
index[0] = Math.floor(index[0])
|
|
index[1] = Math.floor(index[1])
|
|
index[2] = Math.floor(index[2])
|
|
this.mousePosition.index = index
|
|
|
|
this.mousePosition.value = this.getValue(this.volume, worldPoint)
|
|
},
|
|
getValue(volume, worldPos) {
|
|
const { dimensions, scalarData, imageData } = volume
|
|
|
|
const index = imageData.worldToIndex(worldPos)
|
|
|
|
index[0] = Math.floor(index[0])
|
|
index[1] = Math.floor(index[1])
|
|
index[2] = Math.floor(index[2])
|
|
|
|
if (!utilities.indexWithinDimensions(index, dimensions)) {
|
|
return
|
|
}
|
|
|
|
const yMultiple = dimensions[0]
|
|
const zMultiple = dimensions[0] * dimensions[1]
|
|
|
|
const value = scalarData[index[2] * zMultiple + index[1] * yMultiple + index[0]]
|
|
|
|
return value
|
|
},
|
|
handleMouseLeave(e) {
|
|
this.mousePosition.index = []
|
|
this.mousePosition.value = null
|
|
},
|
|
goViewer(e) {
|
|
console.log(e)
|
|
var height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
|
|
this.sliderBoxHeight = height
|
|
var index = Math.trunc(this.seriesInfo.imageIds.length * this.sliderBoxHeight / 100)
|
|
if (this.seriesInfo.imageIdIndex !== index) {
|
|
this.scroll(index)
|
|
}
|
|
},
|
|
sliderMousedown(e) {
|
|
var boxHeight = this.$refs['sliderBox'].clientHeight
|
|
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
|
|
this.sliderInfo.oldM = e.clientY
|
|
this.sliderInfo.isMove = true
|
|
e.stopImmediatePropagation()
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
},
|
|
sliderMousemove(e) {
|
|
if (!this.sliderInfo.isMove) return
|
|
var PX = this.sliderInfo.oldB - (this.sliderInfo.oldM - e.clientY)
|
|
var boxHeight = this.$refs['sliderBox'].clientHeight
|
|
if (PX < 0) return
|
|
if (PX > boxHeight) return
|
|
var height = PX * 100 / boxHeight
|
|
var index = Math.trunc(this.seriesInfo.imageIds.length * this.sliderBoxHeight / 100)
|
|
index = index > this.seriesInfo.imageIds.length ? this.seriesInfo.imageIds.length : index < 0 ? 0 : index
|
|
this.sliderBoxHeight = height
|
|
if (this.seriesInfo.imageIdIndex !== index) {
|
|
this.scroll(index)
|
|
}
|
|
},
|
|
scroll(index) {
|
|
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
viewport = renderingEngine.getViewport(this.viewportId)
|
|
const actorEntries = viewport.getActors()
|
|
|
|
if (!actorEntries) {
|
|
return
|
|
}
|
|
const delta = index - this.seriesInfo.imageIdIndex
|
|
toosUtilities.scroll(viewport, {
|
|
delta,
|
|
volumeId: actorEntries.uid
|
|
})
|
|
renderingEngine.render()
|
|
},
|
|
|
|
sliderMouseup(e) {
|
|
this.sliderInfo.isMove = false
|
|
},
|
|
setAnnotation(imageId, element) {
|
|
this.measureDatas.forEach(item => {
|
|
if (item.OtherMeasureData) {
|
|
var { metadata, annotationUID } = item.OtherMeasureData
|
|
var { referencedImageId } = metadata
|
|
console.log(annotationUID, annotation.state.getAnnotation(annotationUID))
|
|
if (!annotation.state.getAnnotation(annotationUID) && referencedImageId === imageId) {
|
|
annotation.state.addAnnotation(item.OtherMeasureData, element)
|
|
}
|
|
}
|
|
})
|
|
},
|
|
setPreset(presetName) {
|
|
this.presetName = presetName
|
|
},
|
|
renderColorBar(presetName) {
|
|
const colorMap = vtkColorMaps.getPresetByName(presetName)
|
|
const rgbPoints = colorMap.RGBPoints
|
|
const canvas = document.getElementById('colorBar_Canvas')
|
|
const ctx = canvas.getContext('2d')
|
|
const canvasWidth = 160
|
|
const canvasHeight = 5
|
|
const rectWidth = 160
|
|
const rectHeight = canvasHeight
|
|
canvas.width = canvasWidth
|
|
canvas.height = canvasHeight
|
|
const gradient = ctx.createLinearGradient(0, 0, rectWidth, 0)
|
|
for (let i = 0; i < rgbPoints.length; i += 4) {
|
|
let position = 0
|
|
if (rgbPoints[0] === -1) {
|
|
position = (rgbPoints[i] + 1) / 2
|
|
} else {
|
|
position = rgbPoints[i]
|
|
}
|
|
const color = `rgb(${parseInt(rgbPoints[i + 1] * 255)}, ${parseInt(rgbPoints[i + 2] * 255)}, ${parseInt(rgbPoints[i + 3] * 255)})`
|
|
gradient.addColorStop(position, color)
|
|
}
|
|
ctx.fillStyle = gradient
|
|
ctx.fillRect(0, 0, rectWidth, rectHeight)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
.viewport_container{
|
|
height: 100%;
|
|
.seriesInfo_wrapper {
|
|
position: absolute;
|
|
left: 10px;
|
|
top: 10px;
|
|
text-align: left;
|
|
font-size: 12px;
|
|
z-index: 1;
|
|
.taskInfo_container{
|
|
color:#f44336;
|
|
padding: 5px 0px;
|
|
margin: 0;
|
|
}
|
|
}
|
|
.descInfo_wrapper{
|
|
position: absolute;
|
|
right: 10px;
|
|
top: 10px;
|
|
text-align: right;
|
|
font-size: 12px;
|
|
z-index: 1;
|
|
}
|
|
.imageInfo_wrapper_l{
|
|
position: absolute;
|
|
left: 10px;
|
|
bottom: 10px;
|
|
text-align: left;
|
|
font-size: 12px;
|
|
z-index: 1;
|
|
}
|
|
.imageInfo_wrapper_r{
|
|
position: absolute;
|
|
right: 10px;
|
|
bottom: 10px;
|
|
text-align: right;
|
|
font-size: 12px;
|
|
z-index: 1;
|
|
}
|
|
.slider_box{
|
|
position: absolute;
|
|
right: 1px;
|
|
height: calc(100% - 120px);
|
|
transform: translateY(-50%);
|
|
top: calc(50% - 30px);
|
|
width: 10px;
|
|
background: #333;
|
|
cursor: pointer;
|
|
.box{
|
|
z-index:10;
|
|
background: #9e9e9e;
|
|
height: 20px;
|
|
width: 100%;
|
|
position: absolute;
|
|
top: 0;
|
|
cursor: move
|
|
}
|
|
}
|
|
.color_bar{
|
|
|
|
position: absolute;
|
|
|
|
// transform:translateY(-50%);
|
|
transform: rotate(-90deg);
|
|
transform-origin: right;
|
|
left: -150px;
|
|
top: 30%;
|
|
// transform-origin: top left;
|
|
z-index: 1;
|
|
// background: #f44336;
|
|
}
|
|
// .my_slider_box:after{
|
|
// content: '';
|
|
// position: absolute;
|
|
// bottom: -20px;
|
|
// left: 0;
|
|
// height: 20px;
|
|
// width: 100%;
|
|
// background: #333;
|
|
// }
|
|
}
|
|
</style>
|