1039 lines
32 KiB
Plaintext
1039 lines
32 KiB
Plaintext
<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 -->
|
||
<el-tooltip v-if="isCurrentTask" class="item" effect="dark" content="圆形测量" placement="bottom">
|
||
<div class="tool-wrapper">
|
||
<div
|
||
class="icon"
|
||
:class="[activeTool==='CircleROI'?'tool_active':'']"
|
||
data-tool="CircleROI"
|
||
>
|
||
<svg-icon icon-class="oval" class="svg-icon" @click.prevent="setBasicToolActive('CircleROI')" />
|
||
</div>
|
||
<!-- 移动 -->
|
||
<div class="text">圆形测量</div>
|
||
</div>
|
||
</el-tooltip>
|
||
<!-- <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
|
||
class="icon"
|
||
style="margin-left: auto;cursor: pointer;padding: 5px;"
|
||
@click.prevent="closePetct"
|
||
>
|
||
<svg-icon icon-class="close" class="svg-icon" />
|
||
</div>
|
||
</div>
|
||
<div class="dicom-datas">
|
||
<!-- 影像 -->
|
||
<div class="dicom-container box box_2_2">
|
||
<Viewport
|
||
:index="1"
|
||
:active-index="activeIndex"
|
||
:is-reading-show-subject-info="isReadingShowSubjectInfo"
|
||
:series-info="ctSeries"
|
||
:rendering-engine-id="renderingEngineId"
|
||
viewport-id="CT_AXIAL"
|
||
:is-render="isRender"
|
||
:volume="ctVolume"
|
||
/>
|
||
<Viewport
|
||
:index="2"
|
||
:active-index="activeIndex"
|
||
:is-reading-show-subject-info="isReadingShowSubjectInfo"
|
||
:series-info="petSeries"
|
||
:rendering-engine-id="renderingEngineId"
|
||
viewport-id="PT_AXIAL"
|
||
:is-render="isRender"
|
||
:volume="ptVolume"
|
||
/>
|
||
<Viewport
|
||
:index="3"
|
||
:active-index="activeIndex"
|
||
:is-reading-show-subject-info="isReadingShowSubjectInfo"
|
||
:series-info="petSeries"
|
||
:rendering-engine-id="renderingEngineId"
|
||
viewport-id="FUSION_AXIAL"
|
||
:is-render="isRender"
|
||
:volume="ptVolume"
|
||
/>
|
||
<Viewport
|
||
:index="4"
|
||
:active-index="activeIndex"
|
||
:is-reading-show-subject-info="isReadingShowSubjectInfo"
|
||
:series-info="petSeries"
|
||
:rendering-engine-id="renderingEngineId"
|
||
viewport-id="PET_MIP_CORONAL"
|
||
:is-render="isRender"
|
||
/>
|
||
</div>
|
||
<!-- 表单 -->
|
||
<!-- <div class="form-container" style="overflow-y: auto;">
|
||
<div style="color:#fff">
|
||
1
|
||
</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 { changeURLStatic } from '@/utils/history.js'
|
||
import Viewport from './Viewport'
|
||
const {
|
||
ToolGroupManager,
|
||
Enums: csToolsEnums,
|
||
WindowLevelTool,
|
||
PanTool,
|
||
ZoomTool,
|
||
StackScrollMouseWheelTool,
|
||
synchronizers,
|
||
MIPJumpToClickTool,
|
||
VolumeRotateMouseWheelTool,
|
||
// SynchronizerManager,
|
||
// LengthTool,
|
||
EllipticalROITool,
|
||
CircleROITool,
|
||
CrosshairsTool,
|
||
TrackballRotateTool,
|
||
ProbeTool,
|
||
ScaleOverlayTool,
|
||
// eslint-disable-next-line no-unused-vars
|
||
annotation
|
||
|
||
} = 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 },
|
||
props: {
|
||
isReadingShowSubjectInfo: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
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: {},
|
||
isRender: false,
|
||
renderingEngineId: renderingEngineId,
|
||
ctVolumeId: `${volumeLoaderScheme}:${ctVolumeName}`,
|
||
ptVolumeId: `${volumeLoaderScheme}:${ptVolumeName}`,
|
||
ctVolume: null,
|
||
ptVolume: null
|
||
}
|
||
},
|
||
mounted() {
|
||
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.$router.currentRoute.query.TokenKey)
|
||
changeURLStatic('TokenKey', '')
|
||
}
|
||
this.initPage()
|
||
},
|
||
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'))
|
||
this.isCurrentTask = this.ctSeries.isCurrentTask
|
||
await this.getImages()
|
||
|
||
// 设置viewport
|
||
await this.setUpDisplay()
|
||
// 设置viewportTools and synchronizers
|
||
this.setUpToolGroups()
|
||
this.setUpSynchronizers()
|
||
|
||
// const axialCtViewport = renderingEngine.getViewport(viewportIds.CT.AXIAL)
|
||
// const { element } = axialCtViewport
|
||
eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_ADDED, (e) => {
|
||
console.log('ADDED', e.detail.annotation)
|
||
// var annotations = annotation.state.getAnnotations('EllipticalROI', element)
|
||
// console.log(annotations)
|
||
})
|
||
this.isRender = true
|
||
this.loading = false
|
||
},
|
||
async getImages() {
|
||
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)
|
||
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
|
||
})
|
||
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.setToolPassive(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)
|
||
},
|
||
// 设置测量工具启用(不会对输入作出反应)
|
||
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
|
||
}
|
||
},
|
||
// 截屏
|
||
saveImage() {
|
||
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].saveImage()
|
||
},
|
||
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]
|
||
)
|
||
|
||
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)
|
||
},
|
||
initializeCameraSync(renderingEngine) {
|
||
// 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()
|
||
},
|
||
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()
|
||
})
|
||
},
|
||
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'
|
||
},
|
||
closePetct() {
|
||
cornerstoneTools.destroy()
|
||
eventTarget.reset()
|
||
cache.purgeCache()
|
||
|
||
renderingEngine.destroy()
|
||
// metaData.removeProvider(fakeMetaDataProvider)
|
||
imageLoader.unregisterAllImageLoaders()
|
||
ToolGroupManager.destroyToolGroup('stack')
|
||
this.$emit('close')
|
||
},
|
||
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()
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
|
||
.dicom-viewer-container{
|
||
display:flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
// padding: 5px;
|
||
background-color: #000;
|
||
}
|
||
::-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>
|