2161 lines
73 KiB
Plaintext
2161 lines
73 KiB
Plaintext
<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:266px"
|
||
>
|
||
<canvas id="colorBarCanvas" />
|
||
|
||
</div>
|
||
<!-- 移动 -->
|
||
<div class="text">伪彩</div>
|
||
<div class="dropdown-content" style="width:266px">
|
||
<ul>
|
||
<li v-for="(colorMap,index) in colorMaps" :key="colorMap" style="display: flex;align-items: center;margin-bottom:5px;padding:0 5px;justify-content: space-between;" @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.native="reloadViewport('CT_AXIAL')" -->
|
||
<div ref="dicomContainer" class="dicom-container box box_2_2" style="position: relative;" @dblclick="reloadViewport">
|
||
<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"
|
||
:style="2===activeIndex ? viewportStyle : {}"
|
||
/>
|
||
<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"
|
||
:style="3===activeIndex ? viewportStyle : {}"
|
||
/>
|
||
<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"
|
||
:style="4===activeIndex ? viewportStyle : {}"
|
||
/>
|
||
</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,
|
||
// eslint-disable-next-line no-unused-vars
|
||
triggerEvent,
|
||
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'
|
||
import colormaps from './js/colormaps'
|
||
// 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 { mat4, vec3 } from 'gl-matrix'
|
||
// 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 { registerColormap, getColormapNames, getColormap } = csUtils.colormap
|
||
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'],
|
||
colorMaps: [],
|
||
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 }, // 自定义调窗
|
||
defaultCamera: null,
|
||
baseViewportSize: { width: null, height: 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() {
|
||
document.documentElement.style.userSelect = 'none'
|
||
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', '')
|
||
}
|
||
colormaps.forEach(colormap => {
|
||
registerColormap(colormap)
|
||
})
|
||
this.colorMaps = getColormapNames()
|
||
this.colorMaps.unshift('hsv')
|
||
this.$nextTick(() => {
|
||
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)
|
||
})
|
||
FusionEvent.$on('imageLocation', (obj) => {
|
||
console.log('imageLocation')
|
||
this.imageLocation(obj)
|
||
})
|
||
FusionEvent.$on('getScreenshots', async(callback) => {
|
||
var base64Str = await this.$refs['FUSION_AXIAL'].getScreenshots()
|
||
callback(base64Str)
|
||
})
|
||
},
|
||
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')
|
||
FusionEvent.$off('getScreenshots')
|
||
},
|
||
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, true)
|
||
})
|
||
}
|
||
}
|
||
})
|
||
|
||
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()
|
||
|
||
// console.log(colormaps, registerColormap)
|
||
// colormaps.forEach(registerColormap)
|
||
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)
|
||
const viewport = renderingEngine.getViewport('CT_AXIAL')
|
||
if (viewport) {
|
||
this.defaultCamera = viewport.getCamera()
|
||
}
|
||
this.baseViewportSize.width = document.querySelector('.cornerstone-canvas').clientWidth
|
||
this.baseViewportSize.height = document.querySelector('.cornerstone-canvas').clientHeight
|
||
|
||
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 viewportIds = ['PT_AXIAL', 'CT_AXIAL', 'FUSION_AXIAL']
|
||
viewportIds.map(v => {
|
||
const viewport = renderingEngine.getViewport(v)
|
||
if (viewport) {
|
||
var annotations = annotation.state.getAnnotations('CircleROI', viewport.element)
|
||
|
||
if (annotations && annotations.length > 0) {
|
||
annotations.map(i => {
|
||
if (i.metadata.toolName === 'CircleROI') {
|
||
annotation.state.removeAnnotation(i.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.OrderMarkName === obj.data.OrderMarkName)
|
||
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 { data } = otherMeasureData
|
||
const i = this.measureDatas.findIndex(item => item.OtherMeasureData && item.OtherMeasureData.data.remark === data.remark)
|
||
if (i > -1) {
|
||
annotation.state.removeAnnotation(this.measureDatas[i].OtherMeasureData.annotationUID)
|
||
if (type === 'delete') {
|
||
// this.measureDatas.splice(i, 1)
|
||
this.measureDatas[i].OtherMeasureData = ''
|
||
} else {
|
||
if (this.measureDatas[i].FristAddTaskId) {
|
||
this.measureDatas[i].OtherMeasureData = ''
|
||
} else {
|
||
// this.measureDatas.splice(i, 1)
|
||
this.measureDatas[i].OtherMeasureData = ''
|
||
}
|
||
}
|
||
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 = 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 = ''
|
||
}
|
||
|
||
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)
|
||
if (this.defaultCamera) {
|
||
viewport.setCamera(this.defaultCamera)
|
||
}
|
||
viewport.render()
|
||
this.$refs[viewportId].resetMarks()
|
||
})
|
||
|
||
// 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', 256, 15)
|
||
this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName)
|
||
this.colorMaps.forEach((e, index) => {
|
||
this.createColorBar(e, `colorBarCanvas${index}`, 180, 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', 256, 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()
|
||
var colorMap = null
|
||
if (rgbPresetName === 'hsv') {
|
||
colorMap = vtkColorMaps.getPresetByName(rgbPresetName)
|
||
} else {
|
||
colorMap = getColormap(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 = ''
|
||
}
|
||
|
||
// Get the rendering engine
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
|
||
const viewport = renderingEngine.getViewport('CT_AXIAL')
|
||
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) {
|
||
this.setRotate(viewport, -Math.PI / 2)
|
||
} else if (v === 5) {
|
||
this.setRotate(viewport, Math.PI / 2)
|
||
}
|
||
|
||
viewport.render()
|
||
if (v === 4 || v === 5) {
|
||
this.resetViewportMarks(v)
|
||
}
|
||
|
||
// document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
|
||
// document.ondragstart = function() { return false }
|
||
},
|
||
resetViewportMarks(rotateType) {
|
||
['CT_AXIAL', 'PT_AXIAL', 'FUSION_AXIAL'].map(v => {
|
||
this.$refs[v].rotateMarkers(rotateType)
|
||
})
|
||
},
|
||
setRotate(viewport, rotateIncrementDegrees) {
|
||
const camera = viewport.getCamera()
|
||
const { viewUp, position, focalPoint } = camera
|
||
const [cx, cy, cz] = focalPoint
|
||
const direction = [0, 0, -1]
|
||
const [ax, ay, az] = direction
|
||
|
||
const angle = rotateIncrementDegrees
|
||
const newPosition = [0, 0, 0]
|
||
const newFocalPoint = [0, 0, 0]
|
||
const newViewUp = [0, 0, 0]
|
||
|
||
const transform = mat4.identity(new Float32Array(16))
|
||
mat4.translate(transform, transform, [cx, cy, cz])
|
||
mat4.rotate(transform, transform, angle, [ax, ay, az])
|
||
mat4.translate(transform, transform, [-cx, -cy, -cz])
|
||
vec3.transformMat4(newPosition, position, transform)
|
||
vec3.transformMat4(newFocalPoint, focalPoint, transform)
|
||
|
||
mat4.identity(transform)
|
||
mat4.rotate(transform, transform, angle, [ax, ay, az])
|
||
vec3.transformMat4(newViewUp, viewUp, transform)
|
||
|
||
viewport.setCamera({
|
||
position: newPosition,
|
||
viewUp: newViewUp,
|
||
focalPoint: newFocalPoint
|
||
})
|
||
},
|
||
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()
|
||
},
|
||
async reloadViewport() {
|
||
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: 1000,
|
||
width: width + 'px',
|
||
height: height - 1 + 'px'
|
||
}
|
||
}
|
||
const renderEngine = getRenderingEngine(renderingEngineId)
|
||
renderEngine.resize(true, true)
|
||
},
|
||
|
||
viewportRender() {
|
||
const viewportIds = ['PT_AXIAL', 'CT_AXIAL', 'FUSION_AXIAL']
|
||
viewportIds.map(v => {
|
||
const viewport = renderingEngine.getViewport(v)
|
||
if (viewport) {
|
||
viewport.render()
|
||
}
|
||
})
|
||
},
|
||
async receiveMsg(event) {
|
||
if (event.data.type === 'readingPageUpdate') {
|
||
this.$refs['questions'].initList()
|
||
this.$refs['tableQuestions'].initList()
|
||
this.isLocate = true
|
||
await this.getAnnotations()
|
||
this.viewportRender()
|
||
} 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>
|