irc_web/.svn/pristine/e3/e3f935f60e4782bee066a67b22c...

2106 lines
71 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"
@contextmenu="rightClick"
>
<!-- 工具条 -->
<div class="dicom-tools">
<!-- 窗宽窗位 -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:wwwc')}`" placement="bottom">
<div class="tool-wrapper" @click.stop="showPanel($event)" @mouseleave="handleMouseout">
<div class="dropdown">
<div
class="icon"
:class="[activeTool==='WindowLevel'?'tool_active':'']"
data-tool="WindowLevel"
>
<svg-icon icon-class="reverse" class="svg-icon" @click.prevent="setBasicToolActive('WindowLevel')" />
<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:130px;">
<li v-for="item in defaultWwwc" :key="item.label" style="text-align:left;">
<a href="#" @click.prevent="setDicomCanvasWwwc(item)">
<div v-if="item.wc" style="display:flex;flex-direction: row;justify-content: space-between;">
<div>{{ item.label }}</div>
<div>{{ item.ww }}/{{ item.wc }}</div>
</div>
<div v-else style="text-align:left;">
{{ item.label }}
</div>
</a>
</li>
</ul>
</div>
</div>
</div>
</el-tooltip>
<!-- <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:reverseColor')}`" placement="bottom">
<div class="tool-wrapper">
<div
class="icon"
data-tool="reverse"
>
<svg-icon icon-class="reversecolor" class="svg-icon" @click.prevent="toggleInvert" />
</div>
<!-- 调窗 -->
<div class="text">{{ $t('trials:reading:button:reverseColor') }}</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 && readingTaskState !== 2" :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="伪彩" placement="bottom">
<div class="colorBar" style="display:flex;justify-content: flex-start;align-items: center;position: relative;" @mouseleave="isSlideMoving = false">
<div
class="tool-wrapper"
style="margin-right:0px"
@click.stop="showColorBarPanel($event)"
@mouseleave="handleColorBarMouseout"
>
<div>
<!-- <canvas id="colorBarCanvas" /> -->
<div class="dropdown">
<div
id="colorBar"
class="icon"
style="display: flex;align-items: center;width:160px"
>
<canvas id="colorBarCanvas" />
</div>
<!-- 移动 -->
<div class="text">伪彩</div>
<div class="dropdown-content">
<ul>
<li v-for="(colorMap,index) in colorMaps" :key="colorMap" style="display: flex;align-items: center;margin-bottom:5px;padding:0 5px;" @click="setColorMap(colorMap)">
<canvas :id="`colorBarCanvas${index}`" />
<span style="margin-left:5px;font-size: 10px;">{{ colorMap }}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div style="margin-left:-1px;border: 1px solid #424242;">
<el-input
v-model="range"
size="mini"
style="width:120px"
maxlength="3"
oninput="if(value){value=value.replace(/[^\d]/g,'')} if(value<=0){value=''}"
@change="upperRangeChange"
>
<template slot="append">g/ml</template>
</el-input>
</div>
<div id="slider" style="position: absolute;left: 6px;top:5px;cursor: pointer;">
<div id="sliderBox" class="slider" style="height: 17px;width: 10px;background-color: #909399;" />
<div id="slider-position" style="color:#ddd;font-size: 12px;">0</div>
</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_1_1">
<Viewport
:ref="activeRef"
:index="activeIndex"
:active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:series-info="activeSeries"
:rendering-engine-id="renderingEngineId"
:viewport-id="activeViewportId"
:volume="activeVolume"
:measure-datas="measureDatas"
/>
</div> -->
<!-- @dblclick="reloadViewport" -->
<div ref="dicomContainer" class="dicom-container box box_2_2" style="position: relative;">
<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"
:style="1===activeIndex ? viewportStyle : {}"
/>
<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"
:rgb-preset-name="rgbPresetName"
/>
<Viewport
ref="PET_MIP_CORONAL"
: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>
<TableQuestions ref="tableQuestions" />
<Questions ref="questions" @setNonTargetMeasurementStatus="setNonTargetMeasurementStatus" />
</div>
</div>
</div>
<el-dialog
v-if="customWwc.visible"
:visible.sync="customWwc.visible"
:close-on-click-modal="false"
:title="customWwc.title"
width="400px"
custom-class="base-dialog-wrapper"
>
<CustomWwwcForm @close="customWwc.visible = false" @setWwwc="setWwwc" />
</el-dialog>
</div>
</template>
<script>
import {
RenderingEngine,
Enums,
// CONSTANTS,
setVolumesForViewports,
volumeLoader,
getRenderingEngine,
eventTarget,
// eslint-disable-next-line no-unused-vars
cache,
imageLoader,
utilities as csUtils
} from '@cornerstonejs/core'
import * as cornerstone3D 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 { setCtMappingRange } from './js/setCtTransferFunctionForVolumeActor'
import setPetTransferFunctionForVolumeActor from './js/setPetTransferFunctionForVolumeActor'
// eslint-disable-next-line no-unused-vars
import { setPetColorMapTransferFunctionForVolumeActor, setColorPreset } 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 CustomWwwcForm from './../CustomWwwcForm'
import { getTableAnswerRowInfoList } from '@/api/trials'
import FusionEvent from './FusionEvent'
// import { ColorMaps } from '@kitware/vtk.js/Common/Core/ColorMaps'
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'
import vtkOrientationMarkerWidget from '@kitware/vtk.js/Interaction/Widgets/OrientationMarkerWidget'
// import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'
// import vtkMath from '@kitware/vtk.js/Common/Core/Math'
// import CircleROITool from './tools/CircleROITool'
// import ScaleOverlayTool from './tools/ScaleOverlayTool'
const {
ToolGroupManager,
Enums: csToolsEnums,
WindowLevelTool,
PanTool,
ZoomTool,
StackScrollMouseWheelTool,
synchronizers,
MIPJumpToClickTool,
VolumeRotateMouseWheelTool,
OrientationMarkerTool,
// SynchronizerManager,
// LengthTool,
EllipticalROITool,
CircleROITool,
CrosshairsTool,
TrackballRotateTool,
ProbeTool,
ScaleOverlayTool,
utilities,
// eslint-disable-next-line no-unused-vars
annotation,
// eslint-disable-next-line no-unused-vars
cursors
// 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,
CustomWwwcForm
},
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,
isNonTargetMeasurement: false,
readingTaskState: 2,
isLocate: true,
colorMaps: ['BLUE-WHITE', 'BkBu', 'BkCy', 'BkMa', 'Blues', 'Cool to Warm', 'GBBr', 'Grayscale', 'Greens', 'Haze', 'Haze_green', 'Oranges', 'Purples', 'Warm to Cool', 'X Ray', 'blue2yellow', 'coolwarm', 'hsv', 'jet', 'rainbow', 'magenta', '2hot'],
rgbPresetName: 'hsv',
range: 40,
upper: 6,
isSlideMoving: false,
viewportStyle: {},
defaultWwwc: [
{ label: this.$t('trials:reading:button:wwwcDefault'), val: -1, ww: null, wc: null }, // 默认值
{ label: this.$t('trials:reading:button:wwwcCustom'), val: 0, ww: null, wc: null }, // 自定义
{ label: 'CT Brain', wc: 40, ww: 80 },
{ label: 'CT Lungs', wc: -400, ww: 1500 },
{ label: 'CT Abdomen', wc: 60, ww: 400 },
{ label: 'CT Liver', wc: 40, ww: 400 },
{ label: 'CT Bone', wc: 300, ww: 1500 }
],
customWwc: { visible: false, title: null } // 自定义调窗
}
},
computed: {
...mapGetters(['visitTaskList'])
},
watch: {
// activeTool: {
// deep: true,
// immediate: false,
// handler(v) {
// if (!v) {
// const elements = [element_ct, element_pet, element_fusion]
// elements.map(el => {
// console.log(cursors)
// // cursors.setCursorForElement(el, 'default')
// // cursors.elementCursor.hideElementCursor(el)
// })
// }
// }
// }
},
mounted() {
console.log(vtkColorMaps)
window.addEventListener('message', this.receiveMsg)
console.log(cornerstoneTools)
console.log(cornerstone3D)
this.$i18n.locale = this.$route.query.lang
this.setLanguage(this.$route.query.lang)
this.readingTaskState = parseInt(this.$route.query.readingTaskState)
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'
this.customWwc = { visible: false, title: this.$t('trials:reading:dagTitle:wwwcCustom') }
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.renderColorMaps()
this.handleElementsClick()
this.initPage()
this.upperRangeChange(this.range)
this.initSlider()
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.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['CT_AXIAL'].scroll(0)
this.$refs['PT_AXIAL'].scroll(0)
this.$refs['FUSION_AXIAL'].scroll(0)
await this.getAnnotations()
eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_ADDED, (e) => {
this.onAnnotationAdded(e)
})
const debouncedCallback = this.debounce((e) => {
const { annotation } = e.detail
const { cachedStats } = annotation.data
var isNotValidAnnotationNum = 0
for (const volumeId in cachedStats) {
var statObj = cachedStats[volumeId]
var arr = Object.keys(statObj)
if (arr.length < 2) {
++isNotValidAnnotationNum
}
}
if (isNotValidAnnotationNum === 0) {
this.onAnnotationModified(e)
} else {
// 移除标记
this.removeAnnotation({ otherMeasureData: annotation })
const { remark } = annotation.data
// 清除病灶上的标记信息
if (remark === 'Liver' || remark === 'Mediastinum') {
this.$refs['questions'].clearMeasuredData(remark)
// 激活工具
this.setNonTargetMeasurementStatus({ status: true, toolName: 'CircleROI' })
} else {
this.$refs['tableQuestions'].clearMeasuredData()
// 激活工具
this.setBasicToolActive('CircleROI')
}
}
}, 120)
eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_MODIFIED, (e) => {
debouncedCallback(e)
})
eventTarget.addEventListener(cornerstoneTools.Enums.Events.ANNOTATION_SELECTION_CHANGE, (e) => {
console.log(e)
const { detail } = e
const { selection } = detail
if (selection && selection.length > 0) {
const annotationUID = selection[0]
const i = this.measureDatas.findIndex(item => item.OtherMeasureData.annotationUID === annotationUID)
if (i > -1) {
this.$refs['tableQuestions'].setActiveCollapse(this.measureDatas[i])
}
}
})
this.loading = false
},
getAnnotations() {
return new Promise(resolve => {
const viewport = renderingEngine.getViewport('PT_AXIAL')
if (viewport) {
var annotations = annotation.state.getAnnotations('CircleROI', viewport.element)
if (annotations && annotations.length > 0) {
annotations.map(annotation => {
if (annotations.metadata.toolName === 'CircleROI') {
annotation.state.removeAnnotation(annotations.annotationUID)
}
})
}
}
// 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.invalidated = false
// el.OtherMeasureData.highlighted = false
if (this.readingTaskState === 2) {
el.OtherMeasureData.isLocked = true
}
el.OtherMeasureData.data.remark = el.OrderMarkName
const viewport = renderingEngine.getViewport('PT_AXIAL')
annotation.state.addAnnotation(el.OtherMeasureData, viewport.element)
}
arr.push(el)
})
this.measureDatas = arr
this.isInitMeasureDatas = true
resolve()
})
})
},
addOrUpdateAnnotations(obj) {
var idx = this.measureDatas.findIndex(item => item.QuestionId === obj.data.QuestionId)
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('新增标记成功')
}
},
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.measureDatas.splice(i, 1)
} else {
if (this.measureDatas[i].FristAddTaskId) {
this.measureDatas[i].OtherMeasureData = ''
} else {
this.measureDatas.splice(i, 1)
}
}
annotation.state.removeAnnotation(annotationUID)
renderingEngine.render()
}
},
onAnnotationAdded(e) {
this.isLocate = false
const { detail } = e
const { annotation } = detail
const { metadata } = annotation
const measureData = {}
measureData.frame = 0
measureData.data = annotation
measureData.type = metadata.toolName
if (this.isNonTargetMeasurement || annotation.data.remark === 'Liver' || annotation.data.remark === 'Mediastinum') {
this.$refs['questions'].setMeasuredData(measureData)
} else {
this.$refs['tableQuestions'] && this.$refs['tableQuestions'].setMeasuredData(measureData)
}
this.setToolMode('passive', metadata.toolName)
this.activeTool = ''
},
onAnnotationModified(e) {
if (this.isLocate) {
this.isLocate = false
return
}
const { detail } = e
const { annotation } = detail
const { metadata, data } = annotation
var idx = this.measureDatas.findIndex(item => item.OtherMeasureData && item.OtherMeasureData.data.remark === data.remark)
if (idx > -1) {
var questionInfo = this.measureDatas[idx]
const measureData = {}
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
if (data.remark === 'Liver' || data.remark === 'Mediastinum') {
this.$refs['questions'].setMeasuredData(measureData)
this.isNonTargetMeasurement = false
} else {
this.$refs['tableQuestions'].modifyMeasuredData({ measureData, questionInfo })
}
this.setToolMode('passive', metadata.toolName)
if (this.activeTool) {
this.activeTool = ''
}
}
},
setNonTargetMeasurementStatus(obj) {
this.isNonTargetMeasurement = obj.status
if (obj.toolName) {
// this.setMeasureToolActive(obj.toolName)
this.setBasicToolActive(obj.toolName)
}
},
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)
cornerstoneTools.addTool(OrientationMarkerTool)
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)
toolGroup.addTool(OrientationMarkerTool.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)
fusionToolGroup.addTool(OrientationMarkerTool.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)
if (this.readingTaskState === 2) {
toolGroup.setToolEnabled(CircleROITool.toolName)
} else {
toolGroup.setToolPassive(CircleROITool.toolName)
}
// toolGroup.setToolPassive(ProbeTool.toolName)
// toolGroup.setToolPassive(WindowLevelTool.toolName)
toolGroup.setToolConfiguration(
ScaleOverlayTool.toolName,
{
scaleLocation: 'bottom'
},
true // overwrite
)
toolGroup.setToolEnabled(ScaleOverlayTool.toolName)
toolGroup.setToolConfiguration(OrientationMarkerTool.toolName, {
orientationWidget: {
enabled: true,
viewportCorner: vtkOrientationMarkerWidget.Corners.BOTTOM_RIGHT,
viewportSize: 0.15,
minPixelSize: 15,
maxPixelSize: 45
},
overlayConfiguration: {
[OrientationMarkerTool.OVERLAY_MARKER_TYPES.ANNOTATED_CUBE]: {
faceProperties: {
xPlus: { text: 'R', faceColor: '#ffff00', faceRotation: 90 },
xMinus: { text: 'L', faceColor: '#ffff00', faceRotation: 270 },
yPlus: {
text: 'P',
faceColor: '#00ffff',
fontColor: 'white',
faceRotation: 180
},
yMinus: { text: 'A', faceColor: '#00ffff', fontColor: 'white' },
zPlus: { text: 'S' },
zMinus: { text: 'I' }
},
defaultStyle: {
fontStyle: 'bold',
fontFamily: 'Arial',
fontColor: 'black',
fontSizeScale: (res) => res / 2,
faceColor: '#0000ff',
edgeThickness: 0.1,
edgeColor: 'blue',
resolution: 400
}
}
}
})
toolGroup.setToolActive(OrientationMarkerTool.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.addTool(OrientationMarkerTool.toolName)
// mipToolGroup.setToolActive(OrientationMarkerTool.toolName)
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
var unit = modalityUnit
if (modalityUnit === 'raw') {
unit = 'SUV'
} else {
unit = modalityUnit
}
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)} ${unit}`)
}
if (max) {
textLines.push(`Max: ${this.roundNumber(max)} ${unit}`)
}
if (stdDev) {
textLines.push(`Std Dev: ${this.roundNumber(stdDev)} ${unit}`)
}
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 (toolName === 'CircleROI') {
// toolGroup.setToolConfiguration(CircleROITool.toolName, {
// // centerPointRadius: 4,
// disableCursor: true
// })
// }
}
})
if (this.activeTool === toolName) {
this.activeTool = ''
} else {
this.activeTool = toolName
}
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
setMeasureToolActive(toolName) {
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()
const windowCenter = this.ctVolume.cornerstoneImageMetaData.windowCenter[0]
const windowWidth = this.ctVolume.cornerstoneImageMetaData.windowWidth[0]
setCtMappingRange(windowWidth, windowCenter)
// 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()
})
},
async resetViewport() {
if (this.activeTool) {
this.setToolMode('passive', this.activeTool)
this.activeTool = ''
}
// 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 this.initializeCameraSync(renderingEngine)
renderingEngine.render()
// Render the viewports
const viewports = [
'CT_AXIAL',
'PT_AXIAL',
'FUSION_AXIAL'
]
viewports.map(viewportId => {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(viewportId)
viewport.resetCamera(true, true, false)
viewport.render()
})
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
initSlider() {
var slider = document.getElementById('slider')
var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas')
slider.addEventListener('mousedown', () => {
this.isSlideMoving = true
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
})
document.addEventListener('mousemove', (e) => {
if (this.isSlideMoving) {
var containerWidth = container.clientWidth
var sliderWidth = sliderBox.clientWidth
var maxLeft = containerWidth - sliderWidth
var left = e.clientX - container.getBoundingClientRect().left
var position = null
position = left
if (left < 0) {
left = 6
position = 0
} else if (left > maxLeft) {
left = maxLeft + 6
position = maxLeft
}
slider.style.left = left + 'px'
var positionValue = document.getElementById('slider-position')
var upper = this.range
position = parseInt((position / maxLeft) * upper)
positionValue.textContent = position
this.upper = position
this.voiChange(position)
}
})
document.addEventListener('mouseup', () => {
this.isSlideMoving = false
document.onselectstart = null
document.ondragstart = null
})
},
upperRangeChange(v) {
if (v === 0 || v < this.upper) {
return
}
var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas')
var containerWidth = container.clientWidth
var sliderWidth = sliderBox.clientWidth
var maxLeft = containerWidth - sliderWidth
var left = (this.upper / this.range) * maxLeft
if (left < 0) {
left = 6
} else if (left >= maxLeft) {
left = maxLeft + 6
}
var slider = document.getElementById('slider')
slider.style.left = left + 'px'
var positionValue = document.getElementById('slider-position')
positionValue.textContent = this.upper
},
renderColorMaps() {
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 150, 15)
this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName)
this.colorMaps.forEach((e, index) => {
this.createColorBar(e, `colorBarCanvas${index}`, 110, 15)
})
},
getVOIRange(viewportId, volumeId) {
const defaultImageRange = { lower: -1000, upper: 1000 }
const viewport = (
renderingEngine.getViewport(viewportId)
)
const volumeActor = volumeId
? viewport.getActor(volumeId)
: viewport.getDefaultActor()
if (!volumeActor || !csUtils.isImageActor(volumeActor)) {
return defaultImageRange
}
const voiRange = (volumeActor.actor)
.getProperty()
.getRGBTransferFunction(0)
.getRange()
return voiRange[0] === 0 && voiRange[1] === 0
? defaultImageRange
: { lower: voiRange[0], upper: voiRange[1] }
},
voiChange(v) {
const viewportId = viewportIds.FUSION.AXIAL
const volumeId = ptVolumeId
const voiRange = { lower: 0, upper: v }
const viewport = (
renderingEngine.getViewport(viewportId)
)
if (!viewport) return
const viewportsContainingVolumeUID = csUtils.getViewportsWithVolumeId(
volumeId,
viewport.renderingEngineId
)
viewport.setProperties({ voiRange }, volumeId)
viewportsContainingVolumeUID.forEach((vp) => {
vp.render()
this.$refs[vp.id].setWwWc()
})
},
async setColorMap(rgbPresetName) {
this.rgbPresetName = rgbPresetName
this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName)
this.$refs['FUSION_AXIAL'].renderColorBar(this.rgbPresetName)
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 150, 15)
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = (
renderingEngine.getViewport(viewportIds.FUSION.AXIAL)
)
viewport.setProperties({ colormap: { name: rgbPresetName }}, ptVolumeId)
// viewport.setProperties({ colormap: { name: rgbPresetName }}, ctVolumeId)
viewport.render()
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
setPetColorMap(volumeInfo) {
const { volumeActor } = volumeInfo
const mapper = volumeActor.getMapper()
mapper.setSampleDistance(1.0)
const cfun = vtkColorTransferFunction.newInstance()
const presetToUse = this.rgbPresetName
cfun.applyColorMap(presetToUse)
cfun.setMappingRange(0, 5)
volumeActor.getProperty().setRGBTransferFunction(0, cfun)
// Create scalar opacity function
const ofun = vtkPiecewiseFunction.newInstance()
ofun.addPoint(0, 0.0)
ofun.addPoint(0.1, 0.9)
ofun.addPoint(5, 1.0)
volumeActor.getProperty().setScalarOpacity(0, ofun)
},
getApplicableVolumeActor(volumeId, viewportId) {
const viewport = renderingEngine.getViewport(viewportId)
const actorEntries = viewport.getActors()
console.log(actorEntries)
// const actorEntries = this.getActors();
if (!actorEntries.length) {
return
}
let volumeActor
if (volumeId) {
const i = actorEntries.findIndex(e => e.uid === volumeId)
if (i > -1) {
volumeActor = actorEntries[i].actor
}
// volumeActor = this.getActor(volumeId)?.actor as vtkVolume;
}
// // set it for the first volume (if there are more than one - fusion)
if (!volumeActor) {
volumeActor = actorEntries[0].actor
volumeId = actorEntries[0].uid
}
return { volumeActor, volumeId }
},
createColorBar(rgbPresetName, elId, width, height) {
// const ctf = vtkColorTransferFunction.newInstance()
const colorMap = vtkColorMaps.getPresetByName(rgbPresetName)
const rgbPoints = colorMap.RGBPoints
// const range = ctf.getRange()
const canvas = document.getElementById(elId)
const ctx = canvas.getContext('2d')
const canvasWidth = width
const canvasHeight = height
const rectWidth = width
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)
},
toggleInvert() {
if (this.activeTool) {
this.setToolMode('passive', this.activeTool)
this.activeTool = ''
}
const viewports = [
{ viewportId: 'CT_AXIAL', volumeId: ctVolumeId },
{ viewportId: 'PT_AXIAL', volumeId: ptVolumeId }
// { viewportId: 'FUSION_AXIAL', volumeId: ptVolumeId }
]
viewports.map(v => {
const { viewportId, volumeId } = v
const renderingEngine = getRenderingEngine(renderingEngineId)
// Get the volume viewport
const viewport = (
renderingEngine.getViewport(viewportId)
)
// Get the volume actor from the viewport
const actorEntry = viewport.getActor(volumeId)
const volumeActor = actorEntry.actor
const rgbTransferFunction = volumeActor
.getProperty()
.getRGBTransferFunction(0)
// Todo: implement invert in setProperties
csUtils.invertRgbTransferFunction(rgbTransferFunction)
viewport.render()
})
},
showPanel(e) {
e.currentTarget.firstChild.lastChild.style.display = 'block'
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
handleMouseout(e) {
e.currentTarget.firstChild.lastChild.style.display = 'none'
},
showColorBarPanel(e) {
e.currentTarget.firstChild.firstChild.lastChild.style.display = 'block'
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
handleColorBarMouseout(e) {
e.currentTarget.firstChild.firstChild.lastChild.style.display = 'none'
},
setDicomCanvasRotate(v) {
if (this.activeTool) {
this.setToolMode('passive', this.activeTool)
this.activeTool = ''
}
// 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({ flipVertical: !flipVertical })
} else if (v === 3) {
viewport.setCamera({ flipHorizontal: !flipHorizontal })
} else if (v === 4) {
viewport.setCamera({ viewAngle: -90 })
} else if (v === 5) {
viewport.setCamera({ viewAngle: 90 })
}
viewport.render()
})
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
setDicomCanvasWwwc(v) {
if (v.val === 0) {
// 自定义
this.customWwc.visible = true
} else if (v.val === -1) {
// 默认值
const wc = this.ctVolume.cornerstoneImageMetaData.windowCenter[0]
const ww = this.ctVolume.cornerstoneImageMetaData.windowWidth[0]
this.changeMapperRange(wc, ww)
} else {
this.changeMapperRange(v.wc, v.ww)
}
document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
document.ondragstart = function() { return false }
},
setWwwc(v) {
this.changeMapperRange(v.wc, v.ww)
this.customWwc.visible = false
},
changeMapperRange(wc, ww) {
var lower = wc - ww / 2.0
var upper = wc + ww / 2.0
const volumeId = ctVolumeId
const voiRange = { lower, upper }
const viewport = (
renderingEngine.getViewport(viewportIds.CT.AXIAL)
)
viewport.setProperties({ voiRange }, volumeId)
const viewportsContainingVolumeUID = csUtils.getViewportsWithVolumeId(
volumeId,
viewport.renderingEngineId
)
viewportsContainingVolumeUID.forEach((vp) => {
vp.render()
this.$refs[vp.id].setWwWc()
})
},
handleElementsClick() {
var elements = [element_ct, element_pet, element_fusion, element_mip]
elements.forEach((el, index) => {
el.addEventListener('click', () => {
this.activeIndex = index + 1
})
})
},
imageLocation(obj) {
this.setToolToTarget(obj)
if (!obj.otherMeasureData) return
this.isLocate = true
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
}
},
setToolToTarget(obj) {
if (this.readingTaskState < 2 && obj.markTool && !obj.isMarked) {
this.setBasicToolActive(obj.markTool)
}
},
getTargetIdImage(
targetId,
renderingEngine
) {
if (targetId.startsWith('imageId:')) {
const imageId = targetId.split('imageId:')[1]
const imageURI = utilities.imageIdToURI(imageId)
let viewports = utilities.getViewportsWithImageURI(
imageURI,
renderingEngine.id
)
if (!viewports || !viewports.length) {
return
}
viewports = viewports.filter((viewport) => {
return viewport.getCurrentImageId() === imageId
})
if (!viewports || !viewports.length) {
return
}
return viewports[0].getImageData()
} else if (targetId.startsWith('volumeId:')) {
const volumeId = targetId.split('volumeId:')[1]
const viewports = utilities.getViewportsWithVolumeId(
volumeId,
renderingEngine.id
)
if (!viewports || !viewports.length) {
return
}
return viewports[0].getImageData()
} else {
throw new Error(
'getTargetIdImage: targetId must start with "imageId:" or "volumeId:"'
)
}
},
rightClick(e) {
e.preventDefault()
},
reloadViewport(e) {
const width = this.$refs.dicomContainer.offsetWidth
const height = this.$refs.dicomContainer.offsetHeight
if (this.viewportStyle.position) {
this.viewportStyle = {}
} else {
this.viewportStyle = {
position: 'absolute',
top: 0,
left: 0,
zIndex: 10,
width: width + 'px',
height: height - 1 + 'px'
}
}
if (this.activeIndex === 1) {
const renderingEngine = getRenderingEngine(renderingEngineId)
// renderingEngine.render()
const viewport = (
renderingEngine.getViewport(viewportIds.CT.AXIAL)
)
viewport.resetCamera()
viewport.render()
}
},
receiveMsg(event) {
if (event.data.type === 'readingPageUpdate') {
this.$refs['questions'].initList()
this.$refs['tableQuestions'].initList()
this.isLocate = true
this.getAnnotations()
} else if (event.data.type === 'readingPageStateUpdate') {
this.readingTaskState = event.data.data.readingTaskState
}
},
...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;
cursor: pointer;
}
}
.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%
}
.box_1_1{
grid-template-columns: repeat(1, 100%);
grid-template-rows: repeat(1, 100%);
}
}
.colorBar{
/deep/ .el-input--mini .el-input__inner{
height: 25px;
line-height: 25px;
border: none;
background-color: transparent;
color:#ddd;
}
}
</style>