irc_web/.svn/pristine/dc/dc3dada5b698dfe25168f427ec6...

1418 lines
47 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div v-loading="loading" class="dicom-viewer-container">
<!-- 工具条 -->
<div class="dicom-tools">
<!-- 窗宽窗位 -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:wwwc')}`" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
:class="[activeTool==='WindowLevel'?'tool_active':'']"
data-tool="Zoom"
>
<svg-icon icon-class="reverse" class="svg-icon" @click.prevent="setBasicToolActive('WindowLevel')" />
</div>
<!-- 调窗 -->
<div class="text">{{ $t('trials:reading:button:wwwc') }}</div>
</div>
</el-tooltip>
<!-- 缩放 -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:zoom')}`" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
:class="[activeTool==='Zoom'?'tool_active':'']"
data-tool="Zoom"
>
<svg-icon icon-class="magnifier" class="svg-icon" @click.prevent="setBasicToolActive('Zoom')" />
</div>
<!-- 缩放 -->
<div class="text">{{ $t('trials:reading:button:zoom') }}</div>
</div>
</el-tooltip>
<!-- 移动 -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:move')}`" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
:class="[activeTool==='Pan'?'tool_active':'']"
data-tool="Pan"
>
<svg-icon icon-class="move" class="svg-icon" @click.prevent="setBasicToolActive('Pan')" />
</div>
<!-- 移动 -->
<div class="text">{{ $t('trials:reading:button:move') }}</div>
</div>
</el-tooltip>
<!-- 旋转 -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:rotate')}`" placement="bottom">
<div class="tool-wrapper" @click.stop="showPanel($event)" @mouseleave="handleMouseout">
<div class="dropdown">
<div
class="icon"
:class="[activeTool==='Rotate'?'tool_active':'']"
data-tool="Pan"
>
<svg-icon icon-class="rotate" class="svg-icon" @click.prevent="setBasicToolActive('TrackballRotate')" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<!-- 移动 -->
<div class="text">{{ $t('trials:reading:button:rotate') }}</div>
<div class="dropdown-content">
<ul style="width:100px;">
<li v-for="rotate in rotateArr" :key="rotate.label" style="text-align:left;">
<a href="#" @click.prevent="setDicomCanvasRotate(rotate.val)">{{ rotate.label }}</a>
</li>
</ul>
</div>
</div>
</div>
</el-tooltip>
<!-- 椭圆oval -->
<template v-for="tool in measuredTools">
<el-tooltip v-if="isCurrentTask" :key="tool.toolName" class="item" effect="dark" :content="tool.text" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
:class="[activeTool===tool.toolName?'tool_active':'']"
>
<svg-icon :icon-class="tool.icon" class="svg-icon" @click.prevent="setMeasureToolActive(tool.toolName)" />
</div>
<div class="text">{{ tool.text }}</div>
</div>
</el-tooltip>
</template>
<!-- <el-tooltip v-if="isCurrentTask" class="item" effect="dark" content="圆形测量" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
:class="[activeTool==='Probe'?'tool_active':'']"
data-tool="Probe"
>
<svg-icon icon-class="oval" class="svg-icon" @click.prevent="setBasicToolActive('Probe')" />
</div>
<div class="text">探针</div>
</div>
</el-tooltip> -->
<!-- 重置 -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:reset')}`" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
@click.prevent="resetViewport"
>
<svg-icon icon-class="refresh" class="svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:reset') }}</div>
</div>
</el-tooltip>
<!-- 截屏 -->
<!-- <el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:screenShot')}`" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
@click.prevent="saveImage"
>
<svg-icon icon-class="image" class="svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:screenShot') }}</div>
</div>
</el-tooltip> -->
<!-- 椭圆oval -->
</div>
<div class="dicom-datas">
<!-- 影像 -->
<div class="dicom-container box box_2_2">
<Viewport
ref="CT_AXIAL"
:index="1"
:active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:series-info="ctSeries"
:rendering-engine-id="renderingEngineId"
viewport-id="CT_AXIAL"
:volume="ctVolume"
:measure-datas="measureDatas"
/>
<Viewport
ref="PT_AXIAL"
:index="2"
:active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:series-info="petSeries"
:rendering-engine-id="renderingEngineId"
viewport-id="PT_AXIAL"
:volume="ptVolume"
:measure-datas="measureDatas"
/>
<Viewport
ref="FUSION_AXIAL"
:index="3"
:active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:series-info="petSeries"
:rendering-engine-id="renderingEngineId"
viewport-id="FUSION_AXIAL"
:volume="ptVolume"
:measure-datas="measureDatas"
/>
<Viewport
:index="4"
:active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:series-info="petSeries"
:rendering-engine-id="renderingEngineId"
viewport-id="PET_MIP_CORONAL"
:measure-datas="measureDatas"
/>
</div>
<!-- 表单 -->
<div class="form-container" style="overflow-y: auto;">
<div style="color:#fff;padding: 0 5px;">
<h3 v-if="isReadingShowSubjectInfo" style="color: #ddd;padding: 5px 0px;margin: 0;">
<span v-if="subjectCode">{{ subjectCode }} </span>
<span style="margin-left:5px;">{{ taskBlindName }}</span>
</h3>
<Questions ref="questions" />
<TableQuestions ref="tableQuestions" />
</div>
</div>
</div>
</div>
</template>
<script>
import {
RenderingEngine,
Enums,
setVolumesForViewports,
volumeLoader,
getRenderingEngine,
eventTarget,
cache,
// metaData,
imageLoader
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
import initLibraries from './js/initLibraries'
import { createImageIdsAndCacheMetaData } from './js/createImageIdsAndCacheMetaData'
import setCtTransferFunctionForVolumeActor from './js/setCtTransferFunctionForVolumeActor'
import setPetTransferFunctionForVolumeActor from './js/setPetTransferFunctionForVolumeActor'
import setPetColorMapTransferFunctionForVolumeActor from './js/setPetColorMapTransferFunctionForVolumeActor'
import store from '@/store'
import { mapGetters, mapMutations } from 'vuex'
import { changeURLStatic } from '@/utils/history.js'
import Viewport from './Viewport'
import Questions from './Questions'
import TableQuestions from './TableQuestions'
import { getTableAnswerRowInfoList } from '@/api/trials'
import FusionEvent from './FusionEvent'
// import CircleROITool from './tools/CircleROITool'
const {
ToolGroupManager,
Enums: csToolsEnums,
WindowLevelTool,
PanTool,
ZoomTool,
StackScrollMouseWheelTool,
synchronizers,
MIPJumpToClickTool,
VolumeRotateMouseWheelTool,
// SynchronizerManager,
// LengthTool,
EllipticalROITool,
CircleROITool,
CrosshairsTool,
TrackballRotateTool,
ProbeTool,
ScaleOverlayTool,
// utilities,
// eslint-disable-next-line no-unused-vars
annotation
// stateManagement
} = cornerstoneTools
const { MouseBindings } = csToolsEnums
const { ViewportType, BlendModes } = Enums
const { createCameraPositionSynchronizer, createVOISynchronizer } = synchronizers
let renderingEngine
const renderingEngineId = 'myRenderingEngine'
const volumeLoaderScheme = 'cornerstoneStreamingImageVolume' // Loader id which defines which volume loader to use
const ctVolumeName = 'CT_VOLUME_ID' // Id of the volume less loader prefix
const ctVolumeId = `${volumeLoaderScheme}:${ctVolumeName}` // VolumeId with loader id + volume id
const ptVolumeName = 'PT_VOLUME_ID'
const ptVolumeId = `${volumeLoaderScheme}:${ptVolumeName}`
const ctToolGroupId = 'CT_TOOLGROUP_ID'
const ptToolGroupId = 'PT_TOOLGROUP_ID'
const fusionToolGroupId = 'FUSION_TOOLGROUP_ID'
const mipToolGroupUID = 'MIP_TOOLGROUP_ID'
var axialCameraPositionSynchronizer
var ctVoiSynchronizer
var ptVoiSynchronizer
const viewportIds = {
CT: { AXIAL: 'CT_AXIAL', SAGITTAL: 'CT_SAGITTAL', CORONAL: 'CT_CORONAL' },
PT: { AXIAL: 'PT_AXIAL', SAGITTAL: 'PT_SAGITTAL', CORONAL: 'PT_CORONAL' },
FUSION: {
AXIAL: 'FUSION_AXIAL',
SAGITTAL: 'FUSION_SAGITTAL',
CORONAL: 'FUSION_CORONAL'
},
PETMIP: {
CORONAL: 'PET_MIP_CORONAL'
}
}
var element_ct
var element_pet
var element_fusion
var element_mip
var ctToolGroup
var ptToolGroup
var fusionToolGroup
var mipToolGroup
const axialCameraSynchronizerId = 'AXIAL_CAMERA_SYNCHRONIZER_ID'
const ctVoiSynchronizerId = 'CT_VOI_SYNCHRONIZER_ID'
const ptVoiSynchronizerId = 'PT_VOI_SYNCHRONIZER_ID'
export default {
name: 'Fusion',
components: {
Viewport,
Questions,
TableQuestions
},
data() {
return {
activeIndex: 0,
activeTool: '',
loading: false,
fitType: 0,
rotateArr: [
// { label: this.$t('trials:reading:button:rotateDefault'), val: 1 }, // 默认值
{ label: this.$t('trials:reading:button:rotateVertical'), val: 2 }, // 垂直翻转
{ label: this.$t('trials:reading:button:rotateHorizontal'), val: 3 } // 水平翻转
// { label: this.$t('trials:reading:button:rotateTurnLeft'), val: 4 }, // 左转90度
// { label: this.$t('trials:reading:button:rotateTurnRight'), val: 5 }// 右转90度
],
isCurrentTask: false,
ctSeries: {},
petSeries: {},
renderingEngineId: renderingEngineId,
ctVolumeId: `${volumeLoaderScheme}:${ctVolumeName}`,
ptVolumeId: `${volumeLoaderScheme}:${ptVolumeName}`,
ctVolume: null,
ptVolume: null,
isReadingShowSubjectInfo: false,
subjectCode: '',
taskBlindName: '',
measuredTools: [
// 直径测量
{ toolName: 'CircleROI', text: '圆形测量', icon: 'oval', isDisabled: false,
disabledReason: '' }
],
measureDatas: [],
isInitMeasureDatas: false,
isfirstActiveROI: false
}
},
computed: {
...mapGetters(['visitTaskList'])
},
mounted() {
window.addEventListener('message', this.receiveMsg)
console.log(cornerstoneTools)
console.log(annotation)
this.$i18n.locale = this.$route.query.lang
this.setLanguage(this.$route.query.lang)
this.isReadingShowSubjectInfo = this.$route.query.isReadingShowSubjectInfo === 'true'
this.subjectCode = this.$route.query.subjectCode
this.taskBlindName = this.$route.query.taskBlindName
this.isCurrentTask = this.$route.query.isReadingShowSubjectInfo === 'true'
const digitPlaces = parseInt(this.$route.query.digitPlaces)
this.digitPlaces = digitPlaces === -1 ? 2 : digitPlaces
element_ct = document.getElementById('viewport1')
element_pet = document.getElementById('viewport2')
element_fusion = document.getElementById('viewport3')
element_mip = document.getElementById('viewport4')
if (this.$router.currentRoute.query.TokenKey) {
store.dispatch('user/setToken', this.$route.query.TokenKey)
changeURLStatic('TokenKey', '')
}
this.handleElemnetsClick()
this.initPage()
FusionEvent.$on('getAnnotations', () => {
console.log('getAnnotations')
this.getAnnotations()
})
FusionEvent.$on('addOrUpdateAnnotations', (obj) => {
console.log('addOrUpdateAnnotations')
this.addOrUpdateAnnotations(obj)
})
FusionEvent.$on('removeAnnotation', (obj) => {
console.log('removeAnnotation')
this.removeAnnotation(obj)
})
FusionEvent.$on('imageLocation', (obj) => {
console.log('imageLocation')
this.imageLocation(obj)
})
},
destroyed() {
cornerstoneTools.destroy()
eventTarget.reset()
cache.purgeCache()
renderingEngine.destroy()
imageLoader.unregisterAllImageLoaders()
ToolGroupManager.destroyToolGroup('volume')
FusionEvent.$off('getAnnotations')
FusionEvent.$off('addOrUpdateAnnotations')
FusionEvent.$off('removeAnnotation')
FusionEvent.$off('imageLocation')
},
methods: {
initPage() {
const resizeObserver = new ResizeObserver(() => {
if (element_ct.style.width) {
console.log('Size changed')
renderingEngine = getRenderingEngine(renderingEngineId)
if (renderingEngine) {
this.$nextTick(() => {
renderingEngine.resize(true, false)
})
}
}
})
const elements = [
element_ct,
element_pet,
element_fusion
]
elements.forEach((element) => {
// element.style.width = '100%'
// element.style.height = '300px'
// Disable right click context menu so we can have right click tools
element.oncontextmenu = (e) => e.preventDefault()
resizeObserver.observe(element)
})
element_mip.oncontextmenu = (e) => e.preventDefault()
resizeObserver.observe(element_mip)
this.$nextTick(() => {
this.run()
})
},
async run() {
this.loading = true
// 初始化Cornerstone和相关库
await initLibraries()
renderingEngine = new RenderingEngine(renderingEngineId)
this.ctSeries = JSON.parse(sessionStorage.getItem('ctSeriesInfo'))
this.petSeries = JSON.parse(sessionStorage.getItem('petSeriesInfo'))
await this.getImages()
// 设置viewport
await this.setUpDisplay()
// 设置viewportTools and synchronizers
this.setUpToolGroups()
this.setUpSynchronizers()
// this.$refs['questions'].initList()
// this.$refs['tableQuestions'].initList()
await this.getAnnotations()
eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_ADDED, (e) => {
// if (this.isInitMeasureDatas) {
// // console.log('ANNOTATION_ADDED')
// this.onAnnotationAdded(e)
// }
this.onAnnotationAdded(e)
})
// eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_COMPLETED, (e) => {
// if (this.isInitMeasureDatas) {
// console.log('ANNOTATION_COMPLETED')
// this.onAnnotationAdded(e)
// }
// })
eventTarget.addEventListener(cornerstoneTools.Enums.Events.MOUSE_CLICK, (e) => {
if (this.isInitMeasureDatas) {
console.log('MOUSE_CLICK')
// this.onAnnotationAdded(e)
}
})
const debouncedCallback = this.debounce((e) => {
// if (this.isInitMeasureDatas) {
// this.onAnnotationModified(e)
// }
this.onAnnotationModified(e)
}, 120)
eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_MODIFIED, (e) => {
debouncedCallback(e)
})
this.loading = false
},
getAnnotations() {
return new Promise(resolve => {
annotation.state.removeAllAnnotations()
this.isInitMeasureDatas = false
var visitTaskId = this.$route.query.visitTaskId
getTableAnswerRowInfoList(visitTaskId).then(res => {
var arr = []
res.Result.forEach(el => {
if (el.OtherMeasureData) {
el.OtherMeasureData = JSON.parse(el.OtherMeasureData)
el.OtherMeasureData.data.remark = el.OrderMarkName
if (Object.keys(el.OtherMeasureData.data.cachedStats).length === 0) {
el.isCachedStats = false
} else {
el.isCachedStats = true
}
// const viewport1 = renderingEngine.getViewport('CT_AXIAL')
// annotation.state.addAnnotation(el.OtherMeasureData, viewport1.element)
// const viewport2 = renderingEngine.getViewport('PT_AXIAL')
// annotation.state.addAnnotation(el.OtherMeasureData, viewport2.element)
const viewport3 = renderingEngine.getViewport('FUSION_AXIAL')
annotation.state.addAnnotation(el.OtherMeasureData, viewport3.element)
}
arr.push(el)
})
this.measureDatas = arr
this.isInitMeasureDatas = true
resolve()
})
})
},
addOrUpdateAnnotations(obj) {
console.log(this.measureDatas)
var idx = this.measureDatas.findIndex(item => item.QuestionId === obj.data.QuestionId && item.RowIndex === obj.data.RowIndex)
if (idx > -1) {
for (const k in this.measureDatas[idx]) {
if (obj.data[k]) {
this.measureDatas[idx][k] = obj.data[k]
}
}
console.log('更新标记成功', idx)
} else {
this.measureDatas.push(obj.data)
console.log('新增标记成功')
}
// annotation.state.removeAllAnnotations()
// annotation.state.addAnnotation(item.OtherMeasureData)
},
removeAnnotation(obj) {
const { otherMeasureData, type } = obj
const { annotationUID } = otherMeasureData
const i = this.measureDatas.findIndex(item => item.OtherMeasureData.annotationUID === annotationUID)
if (i > -1) {
if (type === 'delete') {
this.getAnnotations()
} else {
if (this.measureDatas[i].FristAddTaskId) {
this.measureDatas[i].OtherMeasureData = ''
} else {
this.measureDatas.splice(i, 1)
}
annotation.state.removeAnnotation(annotationUID)
renderingEngine.render()
}
}
},
onAnnotationAdded(e) {
const { detail } = e
console.log('onAnnotationAdded', detail.viewportId)
const { annotation } = detail
const { metadata } = annotation
// var imageInfo = this.getImageInfo(detail.viewportId, metadata.referencedImageId)
// if (!imageInfo) return
const measureData = {}
// var imageArr = metadata.referencedImageId.split('/')
// var instanceId = imageArr[imageArr.length - 1]
// measureData.studyId = imageInfo.studyId
// measureData.seriesId = imageInfo.seriesId
// measureData.instanceId = instanceId
measureData.frame = 0
measureData.data = annotation
measureData.type = metadata.toolName
this.$refs['tableQuestions'].setMeasuredData(measureData)
this.setToolMode('passive', metadata.toolName)
this.activeTool = ''
},
onAnnotationModified(e) {
const { detail } = e
console.log(detail.viewportId)
// console.log('onAnnotationModified', detail, this.measureDatas)
const { annotation } = detail
const { metadata, data } = annotation
// var annotationUID = annotation.annotationUID
var idx = this.measureDatas.findIndex(item => item.OtherMeasureData && item.OtherMeasureData.data.remark === data.remark)
if (idx > -1) {
if (!this.measureDatas[idx].isCachedStats) {
this.measureDatas[idx].OtherMeasureData = annotation
this.measureDatas[idx].isCachedStats = true
} else {
var questionInfo = this.measureDatas[idx]
// var imageInfo = this.getImageInfo(detail.viewportId, metadata.referencedImageId)
// if (!imageInfo) return
const measureData = {}
// var imageArr = metadata.referencedImageId.split('/')
// var instanceId = imageArr[imageArr.length - 1]
// measureData.studyId = imageInfo.studyId
// measureData.seriesId = imageInfo.seriesId
// measureData.instanceId = instanceId
// measureData.viewportId = imageInfo.viewportId
measureData.frame = 0
measureData.data = annotation
measureData.type = metadata.toolName
measureData.suvMax = data.cachedStats[`volumeId:${ptVolumeId}`] && data.cachedStats[`volumeId:${ptVolumeId}`].max ? data.cachedStats[`volumeId:${ptVolumeId}`].max.toFixed(this.digitPlaces) : null
measureData.modalityUnit = data.cachedStats[`volumeId:${ptVolumeId}`].modalityUnit
this.$refs['tableQuestions'].modifyMeasuredData({ measureData, questionInfo })
// this.$refs['tableQuestions'].setMeasuredData(measureData)
this.setToolMode('passive', metadata.toolName)
this.activeTool = ''
}
}
},
getImageInfo(viewportId, referencedImageId) {
if (!viewportId || !referencedImageId) return
var imageArr = referencedImageId.split('/')
var instanceId = imageArr[imageArr.length - 1]
if (viewportId === 'CT_AXIAL') {
return { studyId: this.ctSeries.studyId, seriesId: this.ctSeries.seriesId, instanceId, viewportId }
} else if (viewportId === 'PT_AXIAL') {
return { studyId: this.petSeries.studyId, seriesId: this.petSeries.seriesId, instanceId, viewportId }
} else {
return { studyId: this.petSeries.studyId, seriesId: this.petSeries.seriesId, instanceId, viewportId }
}
},
debounce(callback, delay) {
let timerId
return function() {
clearTimeout(timerId)
timerId = setTimeout(() => {
callback.apply(this, arguments)
}, delay)
}
},
async getImages() {
// .splice(0, 10)
const ctImageIds = await createImageIdsAndCacheMetaData({
modality: 'CT',
imageIds: this.ctSeries.imageIds
})
const ptImageIds = await createImageIdsAndCacheMetaData({
modality: 'PT',
imageIds: this.petSeries.imageIds
})
this.ctVolume = await volumeLoader.createAndCacheVolume(ctVolumeId, {
imageIds: ctImageIds
})
this.ptVolume = await volumeLoader.createAndCacheVolume(ptVolumeId, {
imageIds: ptImageIds
})
},
setUpToolGroups() {
cornerstoneTools.addTool(WindowLevelTool)
cornerstoneTools.addTool(ZoomTool)
cornerstoneTools.addTool(StackScrollMouseWheelTool)
cornerstoneTools.addTool(MIPJumpToClickTool)
cornerstoneTools.addTool(VolumeRotateMouseWheelTool)
cornerstoneTools.addTool(EllipticalROITool)
cornerstoneTools.addTool(CircleROITool)
cornerstoneTools.addTool(CrosshairsTool)
cornerstoneTools.addTool(TrackballRotateTool)
cornerstoneTools.addTool(ProbeTool)
cornerstoneTools.addTool(PanTool)
cornerstoneTools.addTool(ScaleOverlayTool)
ctToolGroup = ctToolGroup = ToolGroupManager.createToolGroup(ctToolGroupId)
ctToolGroup.addViewport(viewportIds.CT.AXIAL, renderingEngineId)
ptToolGroup = ToolGroupManager.createToolGroup(ptToolGroupId)
ptToolGroup.addViewport(viewportIds.PT.AXIAL, renderingEngineId)
fusionToolGroup = ToolGroupManager.createToolGroup(fusionToolGroupId)
fusionToolGroup.addViewport(viewportIds.FUSION.AXIAL, renderingEngineId)
const toolGroups = [ctToolGroup, ptToolGroup]
toolGroups.forEach((toolGroup) => {
toolGroup.addTool(PanTool.toolName)
toolGroup.addTool(ZoomTool.toolName)
toolGroup.addTool(StackScrollMouseWheelTool.toolName)
toolGroup.addTool(EllipticalROITool.toolName)
toolGroup.addTool(CircleROITool.toolName, {
getTextLines: this.getTextLines
})
toolGroup.addTool(WindowLevelTool.toolName)
toolGroup.addTool(ProbeTool.toolName)
toolGroup.addTool(ScaleOverlayTool.toolName)
})
fusionToolGroup.addTool(PanTool.toolName)
fusionToolGroup.addTool(ZoomTool.toolName)
fusionToolGroup.addTool(StackScrollMouseWheelTool.toolName)
fusionToolGroup.addTool(EllipticalROITool.toolName, {
volumeId: ptVolumeId
})
fusionToolGroup.addTool(CircleROITool.toolName, {
volumeId: ptVolumeId,
getTextLines: this.getTextLines
})
fusionToolGroup.addTool(ProbeTool.toolName)
fusionToolGroup.addTool(ScaleOverlayTool.toolName)
// Here is the difference in the toolGroups used, that we need to specify the
// volume to use for the WindowLevelTool for the fusion viewports
fusionToolGroup.addTool(WindowLevelTool.toolName, {
volumeId: ptVolumeId
});
[ctToolGroup, ptToolGroup, fusionToolGroup].forEach((toolGroup) => {
// toolGroup.setToolActive(ProbeTool.toolName, {
// bindings: [
// {
// mouseButton: MouseBindings.Primary // Left Click
// }
// ]
// })
toolGroup.setToolActive(PanTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Auxiliary // Middle Click
}
]
})
toolGroup.setToolActive(ZoomTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Secondary // Right Click
}
]
})
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName)
// toolGroup.setToolPassive(EllipticalROITool.toolName)
toolGroup.setToolEnabled(CircleROITool.toolName)
// toolGroup.setToolPassive(ProbeTool.toolName)
// toolGroup.setToolPassive(WindowLevelTool.toolName)
// toolGroup.setToolEnabled(ScaleOverlayTool.toolName)
})
// MIP Tool Groups
mipToolGroup = null
if (!ToolGroupManager.getToolGroup(mipToolGroupUID)) {
mipToolGroup = ToolGroupManager.createToolGroup(mipToolGroupUID)
} else {
mipToolGroup = ToolGroupManager.getToolGroup(mipToolGroupUID)
}
mipToolGroup.addTool('VolumeRotateMouseWheel')
mipToolGroup.addTool('MIPJumpToClickTool', {
//
toolGroupId: ptToolGroupId
})
// Set the initial state of the tools, here we set one tool active on left click.
// This means left click will draw that tool.
mipToolGroup.setToolActive('MIPJumpToClickTool', {
bindings: [
{
mouseButton: MouseBindings.Primary // Left ClickR
}
]
})
// As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
// hook instead of mouse buttons, it does not need to assign any mouse button.
mipToolGroup.setToolActive('VolumeRotateMouseWheel')
mipToolGroup.addViewport(viewportIds.PETMIP.CORONAL, renderingEngineId)
},
getTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const {
radius,
radiusUnit,
area,
mean,
stdDev,
max,
isEmptyArea,
// Modality,
areaUnit,
modalityUnit
} = cachedVolumeStats
const textLines = []
if (data.remark) {
textLines.push(data.remark)
}
if (radius) {
const radiusLine = isEmptyArea
? `Radius: Oblique not supported`
: `Radius: ${this.roundNumber(radius)} ${radiusUnit}`
textLines.push(radiusLine)
}
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${this.roundNumber(area)} ${areaUnit}`
textLines.push(areaLine)
}
if (mean) {
textLines.push(`Mean: ${this.roundNumber(mean)} ${modalityUnit}`)
}
if (max) {
textLines.push(`Max: ${this.roundNumber(max)} ${modalityUnit}`)
}
if (stdDev) {
textLines.push(`Std Dev: ${this.roundNumber(stdDev)} ${modalityUnit}`)
}
return textLines
},
roundNumber(value) {
if (value === undefined || value === null || value === '') {
return 'NaN'
}
value = Number(value)
return value.toFixed(this.digitPlaces)
},
// 设置测量工具启用(不会对输入作出反应)
setBasicToolActive(toolName) {
var toolGroupIds = [ctToolGroupId, ptToolGroupId, fusionToolGroupId]
toolGroupIds.forEach((toolGroupId) => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (this.activeTool === toolName) {
toolGroup.setToolPassive(toolName)
} else {
if (this.activeTool) {
toolGroup.setToolPassive(this.activeTool)
}
var bindings = []
if (toolName === 'Pan') {
bindings = [
{
mouseButton: MouseBindings.Auxiliary // Middle Click
},
{
mouseButton: MouseBindings.Primary // Left Click
}
]
} else if (toolName === 'Zoom') {
bindings = [
{
mouseButton: MouseBindings.Secondary // Right Click
},
{
mouseButton: MouseBindings.Primary // Left Click
}
]
} else {
bindings = [
{
mouseButton: MouseBindings.Primary // Left Click
}
]
}
toolGroup.setToolActive(toolName, {
bindings: bindings
})
}
})
if (this.activeTool === toolName) {
this.activeTool = ''
} else {
this.activeTool = toolName
}
},
setMeasureToolActive(toolName) {
this.isfirstActiveROI = true
var toolObj = this.$refs['tableQuestions'].isCanActiveTool(toolName)
if (!toolObj || toolObj.isDisabled) return
this.setBasicToolActive(toolName)
},
// 鼠标移入测量工具时,判断工具是否可激活
enter(e, toolName) {
var i = this.measuredTools.findIndex(item => item.toolName === toolName)
if (i === -1) return
var isCurrentTask = this.isCurrentTask
var readingTaskState = this.readingTaskState
if (!isCurrentTask || readingTaskState >= 2) {
this.measuredTools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
if (this.activeTool) {
this.setToolMode('enabled', toolName)
this.activeTool = ''
}
} else {
var obj = this.$refs['tableQuestions'].isCanActiveTool(toolName, true)
this.measuredTools[i].disabledReason = obj.reason
if (!obj.isCanActiveTool) {
if (this.activeTool === toolName) {
this.setToolMode('passive', toolName)
this.activeTool = ''
}
this.measuredTools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
} else {
this.measuredTools[i].isDisabled = false
e.target.style.cursor = 'pointer'
}
}
},
setToolMode(mode, toolName) {
var toolGroupIds = [ctToolGroupId, ptToolGroupId, fusionToolGroupId]
toolGroupIds.forEach((toolGroupId) => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolPassive(toolName)
if (mode === 'enabled') {
toolGroup.setToolEnabled(toolName)
} else if (mode === 'passive') {
toolGroup.setToolPassive(toolName)
}
})
},
// 截屏
saveImage() {
// const canvas = this.canvas.querySelector('canvas')
// var pictureBaseStr = canvas.toDataURL('image/png', 1)
// return pictureBaseStr
},
setUpSynchronizers() {
// const axialCameraSynchronizerId = 'AXIAL_CAMERA_SYNCHRONIZER_ID'
// const ctVoiSynchronizerId = 'CT_VOI_SYNCHRONIZER_ID'
// const ptVoiSynchronizerId = 'PT_VOI_SYNCHRONIZER_ID'
axialCameraPositionSynchronizer = createCameraPositionSynchronizer(
axialCameraSynchronizerId
)
ctVoiSynchronizer = createVOISynchronizer(ctVoiSynchronizerId)
ptVoiSynchronizer = createVOISynchronizer(ptVoiSynchronizerId);
// Add viewports to camera synchronizers
[
viewportIds.CT.AXIAL,
viewportIds.PT.AXIAL,
viewportIds.FUSION.AXIAL
].forEach((viewportId) => {
axialCameraPositionSynchronizer.add({
renderingEngineId,
viewportId
})
});
// Add viewports to VOI synchronizers
[
viewportIds.CT.AXIAL
].forEach((viewportId) => {
ctVoiSynchronizer.add({
renderingEngineId,
viewportId
})
});
[
viewportIds.FUSION.AXIAL
].forEach((viewportId) => {
// In this example, the fusion viewports are only targets for CT VOI
// synchronization, not sources
ctVoiSynchronizer.addTarget({
renderingEngineId,
viewportId
})
});
[
viewportIds.PT.AXIAL,
viewportIds.FUSION.AXIAL,
viewportIds.PETMIP.CORONAL
].forEach((viewportId) => {
ptVoiSynchronizer.add({
renderingEngineId,
viewportId
})
})
},
async setUpDisplay() {
// 创建 viewports
const viewportInputArray = [
{
viewportId: viewportIds.CT.AXIAL,
type: ViewportType.ORTHOGRAPHIC,
element: element_ct,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
// background: [0, 0, 0]
}
},
{
viewportId: viewportIds.PT.AXIAL,
type: ViewportType.ORTHOGRAPHIC,
element: element_pet,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
background: [1, 1, 1]
}
},
{
viewportId: viewportIds.FUSION.AXIAL,
type: ViewportType.ORTHOGRAPHIC,
element: element_fusion,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
// background: [0, 0, 0]
}
},
{
viewportId: viewportIds.PETMIP.CORONAL,
type: ViewportType.ORTHOGRAPHIC,
element: element_mip,
defaultOptions: {
orientation: Enums.OrientationAxis.CORONAL,
background: [1, 1, 1]
}
}
]
renderingEngine.setViewports(viewportInputArray)
// Set the volumes to load
this.ptVolume.load()
this.ctVolume.load()
// Set volumes on the viewports
await setVolumesForViewports(
renderingEngine,
[
{
volumeId: ctVolumeId,
callback: setCtTransferFunctionForVolumeActor
}
],
[viewportIds.CT.AXIAL]
)
await setVolumesForViewports(
renderingEngine,
[
{
volumeId: ptVolumeId,
callback: setPetTransferFunctionForVolumeActor
}
],
[viewportIds.PT.AXIAL]
)
await setVolumesForViewports(
renderingEngine,
[
{
volumeId: ctVolumeId,
callback: setCtTransferFunctionForVolumeActor
},
{
volumeId: ptVolumeId,
callback: setPetColorMapTransferFunctionForVolumeActor
}
],
[viewportIds.FUSION.AXIAL]
)
// Calculate size of fullBody pet mip
const ptVolumeDimensions = this.ptVolume.dimensions
// Only make the MIP as large as it needs to be.
const slabThickness = Math.sqrt(
ptVolumeDimensions[0] * ptVolumeDimensions[0] +
ptVolumeDimensions[1] * ptVolumeDimensions[1] +
ptVolumeDimensions[2] * ptVolumeDimensions[2]
)
setVolumesForViewports(
renderingEngine,
[
{
volumeId: ptVolumeId,
callback: setPetTransferFunctionForVolumeActor,
blendMode: BlendModes.MAXIMUM_INTENSITY_BLEND,
slabThickness
}
],
[viewportIds.PETMIP.CORONAL]
)
await this.initializeCameraSync(renderingEngine)
// Render the viewports
renderingEngine.render()
},
initCameraSynchronization(sViewport, tViewport) {
// Initialise the sync as they viewports will have
// Different initial zoom levels for viewports of different sizes.
const camera = sViewport.getCamera()
tViewport.setCamera(camera)
},
async initializeCameraSync(renderingEngine) {
return new Promise(resolve => {
// The fusion scene is the target as it is scaled to both volumes.
// TODO -> We should have a more generic way to do this,
// So that when all data is added we can synchronize zoom/position before interaction.
const axialCtViewport = renderingEngine.getViewport(viewportIds.CT.AXIAL)
const axialPtViewport = renderingEngine.getViewport(viewportIds.PT.AXIAL)
const axialFusionViewport = renderingEngine.getViewport(
viewportIds.FUSION.AXIAL
)
this.initCameraSynchronization(axialFusionViewport, axialCtViewport)
this.initCameraSynchronization(axialFusionViewport, axialPtViewport)
renderingEngine.render()
resolve()
})
},
resetViewport() {
const renderingEngine = getRenderingEngine(renderingEngineId)
// const viewportIds = [
// 'CT_AXIAL',
// 'PT_AXIAL',
// 'FUSION_AXIAL'
// ]
// viewportIds.forEach(viewportId => {
// const viewport = renderingEngine.getViewport(viewportId)
// viewport.resetCamera()
// viewport.render()
// })
this.initializeCameraSync(renderingEngine)
renderingEngine.render()
},
showPanel(e) {
// console.log(e.currentTarget.parentNode.parentNode)
// e.currentTarget.parentNode.parentNode.lastChild.style.display = 'block'
e.currentTarget.firstChild.lastChild.style.display = 'block'
},
handleMouseout(e) {
e.currentTarget.firstChild.lastChild.style.display = 'none'
},
setDicomCanvasRotate(v) {
// rotateArr: [
// { label: this.$t('trials:reading:button:rotateDefault'), val: 1 }, // 默认值
// { label: this.$t('trials:reading:button:rotateVertical'), val: 2 }, // 垂直翻转
// { label: this.$t('trials:reading:button:rotateHorizontal'), val: 3 }, // 水平翻转
// { label: this.$t('trials:reading:button:rotateTurnLeft'), val: 4 }, // 左转90度
// { label: this.$t('trials:reading:button:rotateTurnRight'), val: 5 }// 右转90度
// ],
// Get the rendering engine
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportIds = [
'CT_AXIAL',
'PT_AXIAL',
'FUSION_AXIAL'
]
viewportIds.forEach(viewportId => {
const viewport = renderingEngine.getViewport(viewportId)
console.log(viewport.getCamera())
const { flipHorizontal, flipVertical } = viewport.getCamera()
// Flip the viewport horizontally
if (v === 2) {
viewport.setCamera({ flipHorizontal: !flipHorizontal })
} else if (v === 3) {
viewport.setCamera({ flipVertical: !flipVertical })
} else if (v === 4) {
viewport.setCamera({ viewAngle: -90 })
} else if (v === 5) {
viewport.setCamera({ viewAngle: 90 })
}
viewport.render()
})
},
handleElemnetsClick() {
var elements = [element_ct, element_pet, element_fusion, element_mip]
elements.forEach((el, index) => {
el.addEventListener('click', () => {
this.activeIndex = index + 1
})
})
},
imageLocation(obj) {
if (!obj.otherMeasureData) return
const { metadata } = obj.otherMeasureData
var imageArr = metadata.referencedImageId.split('/')
var instanceId = imageArr[imageArr.length - 1]
var viewportId = null
// var seriesId = data.seriesId
// var instanceId = data.instanceId
var index = -1
index = this.petSeries.instanceList.findIndex(i => i === instanceId)
if (index > -1) {
viewportId = 'PT_AXIAL'
this.$refs[viewportId].scroll(index)
return
}
index = this.ctSeries.instanceList.findIndex(i => i === instanceId)
if (index > -1) {
viewportId = 'CT_AXIAL'
this.$refs[viewportId].scroll(index)
return
}
},
receiveMsg(event) {
console.log(event)
if (event.data.type === 'readingPageUpdate') {
this.$refs['questions'].initList()
this.$refs['tableQuestions'].initList()
this.getAnnotations()
}
},
...mapMutations({ setLanguage: 'lang/setLanguage' })
}
}
</script>
<style lang="scss" scoped>
.dicom-viewer-container{
display:flex;
flex-direction: column;
height: 100%;
background-color: #000;
padding: 5px 2px;
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.dicom-tools{
box-sizing: border-box;
width: 100%;
height: 61px;
padding: 0 5px;
border: 1px solid #727272;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.tool-wrapper{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 30px;
.icon{
padding: 5px;
border: 1px solid #404040;
cursor: pointer;
text-align: center;
.svg-icon{
font-size:20px;
color:#ddd;
}
}
.text{
position: relative;
font-size: 12px;
margin-top: 5px;
color: #d0d0d0;
display: none;
}
}
.tool_active{
background-color: #607d8b;
}
.tool_disabled{
cursor:not-allowed
}
.icon:hover{
background-color: #607d8b;
}
.dropdown {
position: relative;
display: inline-block;
.icon-content{
display: flex;
align-items: center;
border: 1px solid #404040;
}
.text{
text-align: center;
}
.tool-icon{
padding: 5px;
cursor: pointer;
text-align: center;
.svg-icon{
font-size:20px;
color:#ddd;
}
}
.arrow-icon{
cursor: pointer;
padding: 7px 2px 7px 0px;
}
.arrow-icon:hover{
background-color: #607d8b;
}
.icon-content-d:hover{
background-color: #607d8b;
}
.tool-icon-d{
padding: 5px;
.svg-icon{
font-size:20px;
color:#ddd;
}
}
}
.dropdown-content {
display: none;
position: absolute;
background-color: #383838;
color: #fff;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 9999;
font-size: 12px;
ul{
list-style: none;
margin: 0;
padding: 0;
text-align: center;
li{
a{
display: block;
padding: 5px;
}
}
}
ul li:hover{
background-color: #727272;
}
}
.layout-content ul li{
border-top:1px solid #ddd;
border-left:1px solid #ddd;
}
.layout-content ul .flex_row{
// border: 1px solid #ddd;
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
// padding: 2px;
margin-bottom: 2px;
}
.layout-content ul .flex_column{
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
margin-bottom: 2px;
}
.layout_box_1_1{
flex:1;
// border: 1px solid #ddd;
line-height: 30px;
font-size: 12px;
text-align: center;
border-bottom:1px solid #ddd;
border-right:1px solid #ddd;
// padding: 0 5px;
}
.layout_box_1_2{
flex:1;
line-height: 15px;
font-size: 10px;
text-align: center;
border-bottom:1px solid #ddd;
border-right:1px solid #ddd;
}
.layout-content li .layout_box_1_1 :last-child{
color: red;
}
.layout-content li:hover {
cursor: pointer;
background-color: #727272;
}
}
.dicom-datas{
box-sizing: border-box;
flex: 1;
margin-top: 5px;
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
overflow: hidden;
.form-container{
width: 350px;
height: 100%;
border: 1px solid #727272;
}
.dicom-container{
box-sizing: border-box;
flex: 1;
height: 100%;
border: 1px solid #727272;
}
.measurement-container{
overflow-y: auto;
}
.box{
display: grid;
box-sizing: border-box;
height: 100%;
padding: 0;
.item{
box-sizing: border-box;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.21);
position: relative;
&_active{
// border: 2px solid #ffeb3b;fff
border: 1px dashed #428bca;
}
}
}
.box_2_2{
grid-template-columns: repeat(2, 50%); //1列占50%
grid-template-rows: repeat(2, 50%); //1行占50%
}
}
</style>