irc_web/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue

2539 lines
94 KiB
Vue
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="read-page-container">
<!-- 检查列表 -->
<div class="left-panel">
<div class="task-container">
<div class="task-info">
<div
v-for="(s, index) in visitTaskList"
:key="s.VisitTaskId"
class="task-item"
:class="{'task-item-active': activeTaskId==s.VisitTaskId}"
@click.prevent="toggleTask(s, index)"
>{{ s.TaskBlindName }}</div>
</div>
</div>
<div v-loading="sLoading" class="study-info">
<div
v-for="s in visitTaskList"
v-show="activeTaskId === s.VisitTaskId"
:key="s.VisitTaskId"
style="height:100%;"
>
<study-list
v-if="selectArr.includes(s.VisitTaskId) && s.StudyList.length > 0"
:ref="s.VisitTaskId"
:visit-task-info="s"
:markedSeriesIds="markedSeriesIds"
@activeSeries="activeSeries"
@showMultiFrame="showMultiFrame"
/>
</div>
</div>
</div>
<!-- 图像 -->
<div class="middle-panel">
<div class="dicom-viewer">
<!-- tools -->
<div class="tools-wrapper">
<div class="tools-left">
<!-- 布局 -->
<div
class="tool-item"
:title="$t('trials:reading:button:layout')"
@click.stop="showPanel($event)"
@mouseleave="toolMouseout"
>
<div class="dropdown">
<div class="icon">
<svg-icon icon-class="layout" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content layout-content">
<ul style="width:50px" class="layout-content-ul">
<li class="layout_flex_row" @click.stop="changeLayout(1)">
<div class="layout_box_1_1">
A
</div>
</li>
<li class="layout_flex_row" @click.stop="changeLayout(2)">
<div class="layout_box_1_1">
A
</div>
<div class="layout_box_1_1">
A
</div>
</li>
<li v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1" class="layout_flex_row" @click.stop="changeLayout(3)">
<div class="layout_box_1_1">
A
</div>
<div class="layout_box_1_1">
B
</div>
</li>
<li class="layout_flex_column" @click.stop="changeLayout(4)">
<div style="flex:1;display: flex;width:100%;">
<div class="layout_box_1_2">
A
</div>
<div class="layout_box_1_2">
A
</div>
</div>
<div style="flex:1;display: flex;width:100%;">
<div class="layout_box_1_2">
A
</div>
<div class="layout_box_1_2">
A
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<!-- 调窗 -->
<div
:class="['tool-item', activeTool === 'WindowLevel' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:wwwc')"
@click.stop="setWindowLevelActive($event)"
@mouseleave="toolMouseout"
>
<div class="dropdown">
<div class="icon">
<svg-icon icon-class="reverse" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<ul style="width:165px;">
<li v-for="item in wwwcArr" :key="item.label">
<a href="#" @click.stop="changeVoiRange(item)">
<div v-if="item.wc !== null" 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>
<el-divider v-if="item.val === 1" class="divider" content-position="center">
{{ ` ${$t('trials:reading:title:preset') }` }}
</el-divider>
</li>
</ul>
</div>
</div>
</div>
<!-- 反色 -->
<div
class="tool-item"
:title="$t('trials:reading:button:reverseColor')"
@click.prevent="toggleInvert"
>
<svg-icon icon-class="reversecolor" class="svg-icon" />
</div>
<!-- 缩放 -->
<div
:class="['tool-item', activeTool === 'Zoom' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:zoom')"
@click.prevent="setToolActive('Zoom')"
>
<svg-icon icon-class="magnifier" class="svg-icon" />
</div>
<!-- 移动 -->
<div
:class="['tool-item', activeTool === 'Pan' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:move')"
@click.prevent="setToolActive('Pan')"
>
<svg-icon icon-class="move" class="svg-icon" />
</div>
<!-- 旋转 -->
<!-- <div
:class="['tool-item', activeTool === 'PlanarRotate' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:rotate')"
@click.prevent="setToolActive('PlanarRotate')"
>
<svg-icon icon-class="rotate" class="svg-icon" />
</div> -->
<div
class="tool-item"
:title="$t('trials:reading:button:rotate')"
@click.stop="showPanel($event)"
@mouseleave="toolMouseout"
>
<div class="dropdown">
<div
class="icon"
data-tool="Rotate"
:class="[activeTool==='Rotate'?'tool_active':'']"
>
<svg-icon icon-class="rotate" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<ul style="width:100px;">
<li v-for="rotate in rotateOptions" :key="rotate.label" style="text-align:left;">
<a href="#" @click.prevent="setViewportRotate(rotate.val)">
{{ rotate.label }}
</a>
</li>
</ul>
</div>
</div>
</div>
<!-- 适应图像或窗口 -->
<!-- <div
class="tool-item"
:title="forceFitToWindow ? `${$t('trials:reading:button:fitWindow')}` : `${$t('trials:reading:button:fitImage')}`"
@click.prevent="fitToType(forceFitToWindow)"
>
<svg-icon v-if="forceFitToWindow" icon-class="fitToWindow" class="svg-icon" />
<svg-icon v-else icon-class="fitToImage" class="svg-icon" />
</div> -->
<div
v-for="tool in tools"
:key="tool.toolName"
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === tool.toolName ? 'tool-item-active' : '']"
:style="{cursor: tool.isDisabled ? 'not-allowed' : 'pointer'}"
:title="tool.disabledReason ? tool.disabledReason : $t(`${tool.i18nKey}`)"
@click.prevent="setAnnotateToolActive(tool.toolName)"
@mouseenter="enter($event,tool.toolName)"
>
<svg-icon :icon-class="tool.icon" class="svg-icon" />
</div>
<div class="tool-frame">
<!-- 第一帧 -->
<div :title="$t('trials:dicom-show:firstframe')" class="icon" @click.prevent="scrollPage(0)">
<svg-icon icon-class="firstframe" class="svg-icon" />
</div>
<!-- 上一帧 -->
<div :title="$t('trials:dicom-show:previousframe')" class="icon" @click.prevent="scrollPage(-1)">
<svg-icon icon-class="previousframe" class="svg-icon" />
</div>
<!-- 播放/暂停 -->
<div
v-if="clipPlaying"
:title="$t('trials:dicom-show:stop')"
class="icon"
@click.prevent="toggleClipPlay(false)"
>
<svg-icon icon-class="stop" class="svg-icon" />
</div>
<div
v-else
:title="$t('trials:dicom-show:play')"
class="icon"
@click.prevent="toggleClipPlay(true)"
>
<svg-icon icon-class="play" class="svg-icon" />
</div>
<!-- 下一帧 -->
<div :title="$t('trials:dicom-show:nextframe')" class="icon" @click.prevent="scrollPage(1)">
<svg-icon icon-class="nextframe" class="svg-icon" />
</div>
<!-- 最后一帧 -->
<div :title="$t('trials:dicom-show:lastframe')" class="icon" @click.prevent="scrollPage(99999)">
<svg-icon icon-class="lastframe" class="svg-icon" />
</div>
<select v-model="fps" :title="$t('trials:dicom-show:speed')" class="select-wrapper" :disabled="clipPlaying">
<!-- 默认值 -->
<option :value="5">5</option>
<option :value="10">10</option>
<option :value="15">15</option>
<option :value="20">20</option>
<option :value="25">25</option>
<option :value="30">30</option>
</select>
</div>
<!-- 重置 -->
<div
class="tool-item"
:title="$t('trials:reading:button:reset')"
@click.prevent="resetViewport"
>
<svg-icon icon-class="refresh" class="svg-icon" />
</div>
</div>
<div>
<!-- 手册 -->
<el-button
v-if="taskInfo && taskInfo.ExistsManual"
type="text"
@click="previewManuals"
>
{{ $t('trials:reading:button:handbooks') }}
</el-button>
<!-- 临床数据 -->
<el-button
v-if="taskInfo && taskInfo.IsExistsClinicalData"
type="text"
@click="previewCD(taskInfo.VisitTaskId)"
>
{{ $t('trials:reading:button:clinicalData') }}
</el-button>
<!-- 个性化配置 -->
<el-button
type="text"
@click="previewConfig"
>
{{ $t('trials:reading:button:customCfg') }}
</el-button>
</div>
</div>
<!-- viewports -->
<div class="viewports-wrapper">
<div ref="container" class="grid-container" :style="gridStyle">
<div
v-for="(v, index) in cellsMax"
v-show="index < cells.length"
:key="index"
:style="cellStyle"
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)"
@click="activeViewport(index)"
>
<Viewport
:ref="`viewport-${index}`"
:data-viewport-uid="`viewport-${index}`"
:rendering-engine-id="renderingEngineId"
:viewport-id="`viewport-${index}`"
:viewport-index="index"
@activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport"
@previewCD="previewCD"
@renderAnnotations="renderAnnotations"
/>
</div>
</div>
</div>
</div>
</div>
<!-- 表单 -->
<div class="right-panel">
<div
v-for="s in visitTaskList"
v-show="lastViewportTaskId === s.VisitTaskId"
:key="s.VisitTaskId"
style="height:100%;"
>
<mRecisit
v-if="lastViewportTaskId && citerionType === 7 && lastViewportTaskIds.includes(s.VisitTaskId)"
:ref="`ecrf_${s.VisitTaskId}`"
:reading-task-state="currentVisitInfo.VisitTaskId === taskInfo.VisitTaskId ? readingTaskState : 2"
:last-viewport-task-id="lastViewportTaskId"
:visit-info="s"
@removeAnnotation="removeAnnotation"
@getScreenshots="getScreenshots"
@setMarkName="setMarkName"
@imageLocation="imageLocation"
@resetAnnotations="resetAnnotations"
@getAnnotations="getAnnotations"
@setToolToTarget="setToolToTarget"
/>
</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"
>
<custom-wwwc-form :ww="activeViewportWW" :wc="activeViewportWC" @close="customWwc.visible = false" @setWwwc="setWwwc" />
</el-dialog>
<!-- 手册 -->
<el-dialog
v-if="manualsDialog.visible"
:visible.sync="manualsDialog.visible"
:custom-class="manualsDialog.isFullscreen?'manuals-full-dialog-container':'manuals-dialog-container'"
:show-close="false"
:close-on-click-modal="false"
:fullscreen="manualsDialog.isFullscreen"
>
<span slot="title" class="dialog-footer">
<!-- 手册 -->
<span>{{ $t('trials:reading:button:handbooks') }}</span>
<span style="position: absolute;right: 20px;font-size: 20px;">
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" style="margin-right:10px;cursor: pointer;" @click="manualsDialog.isFullscreen=!manualsDialog.isFullscreen" />
<svg-icon icon-class="close" style="cursor: pointer;" @click="manualsDialog.visible = false" />
</span>
</span>
<div style="height: 100%;margin:0;">
<Manuals :trial-id="trialId" />
</div>
</el-dialog>
<!-- 个性化配置 -->
<el-dialog
v-if="personalConfigDialog.visible"
:visible.sync="personalConfigDialog.visible"
:close-on-click-modal="false"
:title="personalConfigDialog.title"
width="600px"
>
<el-tabs v-model="personalConfigDialog.activeName" class="personal_config">
<!-- 热键 -->
<el-tab-pane :label="$t('trials:reading:tab:hotkeys')" name="1">
<Hotkeys v-if="personalConfigDialog.activeName === '1'" :reading-tool="0" @reset="resetHotkeyList" />
</el-tab-pane>
<!-- W/L模板 -->
<el-tab-pane :label="$t('trials:reading:tab:wlTemplate')" name="2">
<WL v-if="personalConfigDialog.activeName === '2'" @getWwcTpl="getWwcTpl" />
</el-tab-pane>
<!-- 其他 -->
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
<Others v-if="personalConfigDialog.activeName === '3'" />
</el-tab-pane>
</el-tabs>
</el-dialog>
<!-- 临床数据 -->
<el-dialog
:visible.sync="clinicalDataVisible"
:custom-class="isClinicalDataFullscreen?'cd-full-dialog-container':'cd-dialog-container'"
:show-close="false"
:close-on-click-modal="false"
:fullscreen="isClinicalDataFullscreen"
>
<span slot="title" class="dialog-footer">
<div style="position: absolute;right: 20px;top: 10px;color: #000;">
<svg-icon :icon-class="isClinicalDataFullscreen?'exit-fullscreen':'fullscreen'" style="cursor: pointer;font-size: 20px;" @click="isClinicalDataFullscreen=!isClinicalDataFullscreen" />
<svg-icon icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;" @click="clinicalDataVisible = false" />
</div>
</span>
<div style="height: 100%;margin:0;display: flex;flex-direction: column;">
<clinical-data
v-if="clinicalDataVisible"
style="flex: 1"
:trial-id="trialId"
:subject-id="taskInfo.SubjectId"
:visit-task-id="cdVisitTaskId"
:is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
/>
</div>
</el-dialog>
</div>
</template>
<script>
import { getRelatedVisitTask, getReadingVisitStudyList, getTableAnswerRowInfoList } from '@/api/trials'
import { getDoctorShortcutKey, getUserWLTemplateList } from '@/api/user'
import {
RenderingEngine,
Enums,
// imageLoader,
// metaData,
getRenderingEngine,
eventTarget,
utilities as csUtils,
cache
} from '@cornerstonejs/core'
import {
annotation
} from '@cornerstonejs/tools'
import * as cornerstoneTools from '@cornerstonejs/tools'
import initLibraries from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initLibraries'
import html2canvas from 'html2canvas'
import { getTools } from './toolConfig'
import StudyList from './StudyList'
import Viewport from './Viewport'
import mRecisit from './mRecist/QuestionList'
import CustomWwwcForm from '@/views/trials/trials-panel/reading/dicoms/components/CustomWwwcForm'
import Manuals from '@/views/trials/trials-panel/reading/dicoms/components/Manuals'
import Hotkeys from '@/views/trials/trials-panel/reading/dicoms/components/Hotkeys'
import WL from '@/views/trials/trials-panel/reading/dicoms/components/WL'
import Others from '@/views/trials/trials-panel/reading/dicoms/components/Others'
import ClinicalData from '@/views/trials/trials-panel/reading/clinical-data'
const { visibility } = annotation
const { ViewportType, Events } = Enums
const renderingEngineId = 'myRenderingEngine'
const {
ToolGroupManager,
Enums: csToolsEnums,
StackScrollTool,
ScaleOverlayTool,
PanTool,
ZoomTool,
WindowLevelTool,
WindowLevelRegionTool,
PlanarRotateTool,
LengthTool,
BidirectionalTool,
ArrowAnnotateTool,
RectangleROITool,
PlanarFreehandROITool,
EraserTool
// cursors
} = cornerstoneTools
const newStyles = {
global: {
color: 'rgb(255, 0, 0)',
colorHighlighted: 'rgb(255, 0, 0)',
colorSelected: 'rgb(255, 0, 0)',
colorLocked: 'rgb(255, 0, 0)',
lineWidth: '1',
lineDash: '',
shadow: true,
textBoxVisibility: true,
textBoxFontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
textBoxFontSize: '14px',
textBoxColor: 'rgb(255, 0, 0)',
textBoxColorHighlighted: 'rgb(255, 0, 0)',
textBoxColorSelected: 'rgb(255, 0, 0)',
textBoxColorLocked: 'rgb(255, 0, 0)',
textBoxBackground: '',
textBoxLinkLineWidth: '1',
textBoxLinkLineDash: '2,3',
textBoxShadow: true,
markerSize: '10'
}
}
annotation.config.style.setDefaultToolStyles(newStyles)
const { MouseBindings, Events: toolsEvents } = csToolsEnums
export default {
name: 'ReadPage',
components: {
StudyList,
Viewport,
mRecisit,
CustomWwwcForm,
Manuals,
Hotkeys,
WL,
Others,
ClinicalData
},
data() {
return {
loading: false,
trialId: '',
visitTaskList: [],
selectArr: [],
taskInfo: null,
activeTaskId: null,
activeTaskIndex: -1,
activeStudyIndex: -1,
activeSeriesIndex: -1,
currentVisitInfo: null,
layout: 1,
cellsMax: 4,
rows: 1,
cols: 1,
fullScreenIndex: null,
activeViewportIndex: 0,
activeTool: '',
readingTaskState: 2,
renderingEngineId: renderingEngineId,
sLoading: false,
renderedTaskIds: [],
citerionType: null,
tools: [],
rotateOptions: [
{ 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 },
{ label: this.$t('trials:reading:button:rotateTurnRight'), val: 5 }
],
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: this.$t('trials:reading:button:wwwcRegion'), val: 1, 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 },
{ label: 'CT Bone1', wc: 0, ww: 0 }
],
wwwcArr: [],
customWwc: { visible: false, title: this.$t('trials:reading:dagTitle:wwwcCustom') }, // 自定义调窗
personalConfigDialog: { visible: false, title: this.$t('trials:reading:button:customCfg'), activeName: '1' }, // 个性化配置
activeViewportWW: null,
activeViewportWC: null,
clipPlaying: false,
fps: 15,
manualsDialog: { visible: false, isFullscreen: false },
hotKeyList: [],
forceFitToWindow: true,
isShowAnnotations: true,
clinicalDataVisible: false,
isClinicalDataFullscreen: false,
cdVisitTaskId: '',
lastViewportTaskId: '',
digitPlaces: 2,
instanceInfo: {},
lastViewportTaskIds: [],
markedSeriesIds: []
}
},
computed: {
gridStyle() {
return {
display: 'grid',
gridTemplateRows: `repeat(${this.rows}, 1fr)`,
gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
height: '100%',
width: '100%'
}
},
cellStyle() {
return {
border: '1px dashed #ccc',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
},
cells() {
return Array(this.rows * this.cols).fill(0)
}
},
watch: {
activeTaskId: {
immediate: true,
handler(id) {
if (!id) return
// this.addHistoryAnnotations(id)
}
},
activeViewportIndex: {
immediate: true,
handler(index) {
let series = null
if (this.$refs[`viewport-${this.activeViewportIndex}`] && this.$refs[`viewport-${this.activeViewportIndex}`][0]) {
this.clipPlaying = this.$refs[`viewport-${this.activeViewportIndex}`][0].playClipState
series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
} else {
this.clipPlaying = false
this.fps = 15
}
if (index === this.cells.length - 1 && series) {
this.lastViewportTaskId = series.TaskInfo.VisitTaskId
this.currentVisitInfo = series.TaskInfo
}
}
},
lastViewportTaskId: {
immediate: true,
handler(id) {
if (!id) return
if (!this.lastViewportTaskIds.includes(id)) {
this.lastViewportTaskIds.push(id)
}
// if (this.readingTaskState === 2) return
// if (id !== this.taskInfo.VisitTaskId) {
// const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.taskInfo.VisitTaskId)
// // const annotationUIDs = this.visitTaskList[taskIdx].AnnotationUIDs
// const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
// // const annotations = this.visitTaskList[taskIdx].Annotations
// annotations.forEach(i => {
// if (i.visitTaskId === this.taskInfo.VisitTaskId) {
// cornerstoneTools.annotation.state.removeAnnotation(i.annotationUID)
// let idx = this.visitTaskList[taskIdx].Annotations.findIndex(v=>v.MeasureData && v.MeasureData.annotationUID === i.annotationUID)
// console.log('123',idx)
// if (idx > -1) {
// cornerstoneTools.annotation.state.addAnnotation(this.visitTaskList[taskIdx].Annotations[idx].MeasureData)
// }
// }
// })
// // const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
// // annotations.forEach(i => {
// // if (!(annotationUIDs.includes(i.annotationUID)) && i.visitTaskId === this.taskInfo.VisitTaskId) {
// // cornerstoneTools.annotation.state.removeAnnotation(i.annotationUID)
// // }
// // })
// }
}
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.citerionType = this.taskInfo.CriterionType
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.tools = getTools(this.citerionType)
this.trialId = this.$route.query.trialId
this.readingTaskState = this.taskInfo.ReadingTaskState
if (!this.taskInfo.IsBaseLine && this.taskInfo.IsReadingTaskViewInOrder !== 0) {
this.rows = 1
this.cols = 2
this.activeViewportIndex = 1
}
this.$nextTick(() => {
this.loadRelatedTasks()
this.initLoader()
this.getWwcTpl()
this.getHotKeys()
})
},
methods: {
// 加载当前任务关联的任务信息
async loadRelatedTasks() {
this.loading = true
try {
const params = {
visitTaskId: this.taskInfo.VisitTaskId
}
const res = await getRelatedVisitTask(params)
let currentTaskIndex = 0
let currentTaskId = ''
this.visitTaskList = []
for (let i = 0; i < res.Result.length; i++) {
const item = res.Result[i]
let studyList = []
let annotations = []
let annotationUIDs = []
let keyImages = []
let isInit = false
if (item.IsCurrentTask) {
currentTaskIndex = i
currentTaskId = item.VisitTaskId
}
if (item.IsCurrentTask || (this.taskInfo.IsReadingTaskViewInOrder === 1 && !this.taskInfo.IsBaseLineTask && item.IsBaseLineTask)) {
const res = await this.loadTaskDetails(item, i)
isInit = true
studyList = res ? res.studyList : []
annotations = res ? res.annotations : []
annotationUIDs = res ? res.annotationUIDs : []
keyImages = res ? res.keyImages : []
if (!this.selectArr.includes(item.VisitTaskId)) {
this.selectArr.push(item.VisitTaskId)
}
if (item.IsCurrentTask) {
this.markedSeriesIds = []
annotations.map(i=> {
if (i.MeasureData && i.MeasureData.seriesId) {
this.markedSeriesIds.push(i.MeasureData.seriesId)
}
})
}
}
this.visitTaskList.push({
...item,
StudyList: studyList,
Annotations: annotations,
KeyImages: keyImages,
AnnotationUIDs: annotationUIDs,
IsInit: isInit
})
}
this.activeTaskId = currentTaskId
this.activeTaskIndex = currentTaskIndex
this.$nextTick(() => {
this.setInitSeries()
})
this.loading = false
} catch (e) {
console.log(e)
this.loading = false
}
},
// 加载检查及标注信息
async loadTaskDetails(taskInfo, taskIndex) {
try {
const sujectVisitId = taskInfo.VisitId
const taskId = taskInfo.VisitTaskId
// 获取检查信息
const keyImages = []
const res1 = await getReadingVisitStudyList(this.trialId, sujectVisitId, taskId)
let keyStudyIndex = -1
let keySeriesIndex = -1
// const arr = [res1.Result[0]]
const arr = res1.Result
arr.forEach((study, studyIndex) => {
study.SeriesList.forEach((series, seriesIndex) => {
const imageIds = []
const stack = []
series.InstanceInfoList.forEach((instance, instanceIndex) => {
if (study.IsCriticalSequence) {
keyStudyIndex = studyIndex
keySeriesIndex = seriesIndex
keyImages.push({ Id: instance.Id, Path: instance.Path, KeyFramesList: instance.KeyFramesList, KeyStudyIndex: studyIndex, KeySeriesIndex: seriesIndex })
} else {
const i = keyImages.findIndex(k => k.Id === instance.Id)
if (i > -1) {
keyImages[i].StudyIndex = studyIndex
keyImages[i].SeriesIndex = seriesIndex
}
const nFrames = instance.NumberOfFrames || 0
if (nFrames === 0) {
// 单帧
stack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&visitTaskId=${taskId}`)
} else {
// 多帧
for (let i = 0; i < nFrames; i++) {
const newImageId = `wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&visitTaskId=${taskId}&frame=${i + 1}`
stack.push(newImageId)
}
}
imageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&visitTaskId=${taskId}`)
this.instanceInfo[instance.Id] = { taskIndex, studyIndex, seriesIndex }
}
})
series.Stack = stack
series.ImageIds = imageIds
series.SliceIndex = 0
series.LoadedImageCount = 0
series.LoadedImageProgress = 0
series.TaskInfo = Object.assign({}, taskInfo)
series.StudyIndex = studyIndex
series.SeriesIndex = seriesIndex
})
})
if (keyStudyIndex > -1 && keySeriesIndex > -1 && keyImages.length > 0) {
const keyImageIds = []
keyImages.forEach(instance => {
if (instance.KeyFramesList.length > 0) {
instance.KeyFramesList.map(i => {
keyImageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&visitTaskId=${taskId}&frame=${i}`)
})
} else {
keyImageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&visitTaskId=${taskId}`)
}
})
res1.Result[keyStudyIndex].SeriesList[keySeriesIndex].ImageIds = keyImageIds
}
// 获取标注信息
const res2 = await getTableAnswerRowInfoList(taskId)
const annotationUIDs = []
const annotations = res2.Result.map(i => {
if (typeof i.MeasureData === 'string' && i.MeasureData) {
i.MeasureData = JSON.parse(i.MeasureData)
annotationUIDs.push(i.MeasureData.annotationUID)
}
return i
})
return { studyList: arr, annotations: annotations, annotationUIDs, keyImages }
} catch (e) {
console.log(e)
}
},
// 设置初始化序列信息
setInitSeries() {
const seriesArr = []
let activeStudyIndex = -1
let activeSeriesIndex = -1
const studyList = this.visitTaskList[this.activeTaskIndex].StudyList.filter(i => i.IsDicom)
const seriesList = studyList.map(s => s.SeriesList).flat()
if (this.taskInfo.IsReadingTaskViewInOrder === 0) {
// 完全随机(默认一个视口,展示第一个序列)
const series = this.getRelatedSeries(this.visitTaskList[this.activeTaskIndex])
seriesArr.push(series)
activeStudyIndex = series.StudyIndex
activeSeriesIndex = series.SeriesIndex
} else if (this.taskInfo.IsReadingTaskViewInOrder === 1) {
// 按时间顺序(默认基线时显示一个视口,展示第一个序列;随访时显示基线和当前随访的序列)
if (this.taskInfo.IsBaseLine) {
// 基线默认显示第一个序列
const series = this.getRelatedSeries(this.visitTaskList[this.activeTaskIndex])
seriesArr.push(series)
activeStudyIndex = series.StudyIndex
activeSeriesIndex = series.SeriesIndex
} else {
// 随访
const i = this.visitTaskList.findIndex(i => i.IsBaseLineTask)
const baseSeries = this.getRelatedSeries(this.visitTaskList[i])
seriesArr.push(baseSeries)
const followUpSeries = this.getRelatedSeries(this.visitTaskList[this.activeTaskIndex], baseSeries)
seriesArr.push(followUpSeries)
activeStudyIndex = followUpSeries.StudyIndex
activeSeriesIndex = followUpSeries.SeriesIndex
}
} else if (this.taskInfo.IsReadingTaskViewInOrder === 2) {
// 受试者内随机(默认两个视口,显示第一个序列和第二个序列;如果只有一个序列则两个视口均显示这个序列)
if (seriesList.length === 1) {
seriesArr.push(seriesList[0], seriesList[0])
activeStudyIndex = seriesList[0].StudyIndex
activeSeriesIndex = seriesList[0].SeriesIndex
} else if (seriesArr.length > 1) {
seriesArr.push(seriesList[0], seriesList[1])
activeStudyIndex = seriesList[1].StudyIndex
activeSeriesIndex = seriesList[1].SeriesIndex
}
}
if (activeStudyIndex > -1 && activeSeriesIndex > -1) {
seriesArr.map((i, index) => {
this.$refs[`viewport-${index}`][0].setSeriesInfo(i)
})
const visitTaskId = this.visitTaskList[this.activeTaskIndex].VisitTaskId
this.lastViewportTaskId = visitTaskId
this.currentVisitInfo = this.visitTaskList[this.activeTaskIndex]
this.$refs[visitTaskId][0].setSeriesActive(activeStudyIndex, activeSeriesIndex)
}
},
// 初始化加载器
async initLoader() {
await initLibraries()
cache.setMaxCacheSize(6 * 1024 * 1024 * 1024);
let renderingEngine = getRenderingEngine(renderingEngineId)
if (!renderingEngine) {
renderingEngine = new RenderingEngine(renderingEngineId)
}
const element1 = this.$refs['viewport-0'][0].$el
const element2 = this.$refs['viewport-1'][0].$el
const element3 = this.$refs['viewport-2'][0].$el
const element4 = this.$refs['viewport-3'][0].$el
const viewportInputArray = [
{
viewportId: 'viewport-0',
type: ViewportType.STACK,
element: element1
},
{
viewportId: 'viewport-1',
type: ViewportType.STACK,
element: element2
},
{
viewportId: 'viewport-2',
type: ViewportType.STACK,
element: element3
},
{
viewportId: 'viewport-3',
type: ViewportType.STACK,
element: element4
}
]
const viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
renderingEngine.setViewports(viewportInputArray)
this.addAnnotationListeners()
cornerstoneTools.addTool(StackScrollTool)
cornerstoneTools.addTool(PanTool)
cornerstoneTools.addTool(ZoomTool)
cornerstoneTools.addTool(WindowLevelTool)
cornerstoneTools.addTool(WindowLevelRegionTool)
cornerstoneTools.addTool(PlanarRotateTool)
cornerstoneTools.addTool(ArrowAnnotateTool)
cornerstoneTools.addTool(RectangleROITool)
cornerstoneTools.addTool(PlanarFreehandROITool)
cornerstoneTools.addTool(EraserTool)
cornerstoneTools.addTool(LengthTool)
cornerstoneTools.addTool(BidirectionalTool)
cornerstoneTools.addTool(ScaleOverlayTool)
viewportIds.forEach((viewportId, i) => {
const toolGroupId = `viewport-${i}`
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId)
toolGroup.addViewport(viewportId, renderingEngineId)
toolGroup.addTool(StackScrollTool.toolName)
toolGroup.addTool(ScaleOverlayTool.toolName)
toolGroup.addTool(PanTool.toolName)
toolGroup.addTool(ZoomTool.toolName)
toolGroup.addTool(WindowLevelTool.toolName)
toolGroup.addTool(WindowLevelRegionTool.toolName)
toolGroup.addTool(PlanarRotateTool.toolName)
if (this.citerionType === 0) {
toolGroup.addTool(ArrowAnnotateTool.toolName, {
arrowHeadStyle: 'standard',
changeTextCallback: async(data, eventData, doneChangingTextCallback) => {
return doneChangingTextCallback(await this.customPrompt())
},
getTextCallback: async(doneChangingTextCallback) => {
return doneChangingTextCallback(await this.customPrompt())
}
})
} else {
toolGroup.addTool(ArrowAnnotateTool.toolName, {
arrowHeadStyle: 'standard',
changeTextCallback: async(data, eventData, doneChangingTextCallback) => {
return doneChangingTextCallback(data.text)
},
getTextCallback: async(doneChangingTextCallback) => {
return doneChangingTextCallback('_')
}
})
}
toolGroup.addTool(RectangleROITool.toolName, {
cachedStats: false,
getTextLines: this.getRectangleROIToolTextLines
})
toolGroup.addTool(PlanarFreehandROITool.toolName, {
allowOpenContours: false,
cachedStats: false,
getTextLines: this.getPlanarFreehandROIToolTextLines
})
toolGroup.addTool(EraserTool.toolName)
toolGroup.addTool(LengthTool.toolName, {
getTextLines: this.getLengthToolTextLines
// cachedStats: false
})
toolGroup.addTool(BidirectionalTool.toolName, {
// cachedStats: true
getTextLines: this.getBidirectionalToolTextLines
})
toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
// toolGroup.setToolEnabled(ScaleOverlayTool.toolName);
toolGroup.setToolPassive(PanTool.toolName)
toolGroup.setToolPassive(ZoomTool.toolName)
toolGroup.setToolPassive(WindowLevelTool.toolName)
toolGroup.setToolPassive(WindowLevelRegionTool.toolName)
toolGroup.setToolPassive(PlanarRotateTool.toolName)
if (this.readingTaskState < 2) {
toolGroup.setToolPassive(ArrowAnnotateTool.toolName)
toolGroup.setToolPassive(RectangleROITool.toolName)
toolGroup.setToolPassive(PlanarFreehandROITool.toolName)
toolGroup.setToolPassive(LengthTool.toolName)
toolGroup.setToolPassive(BidirectionalTool.toolName)
} else {
toolGroup.setToolEnabled(ArrowAnnotateTool.toolName)
toolGroup.setToolEnabled(RectangleROITool.toolName)
toolGroup.setToolEnabled(PlanarFreehandROITool.toolName)
toolGroup.setToolEnabled(LengthTool.toolName)
toolGroup.setToolEnabled(BidirectionalTool.toolName)
}
toolGroup.setToolPassive(EraserTool.toolName)
})
eventTarget.addEventListener('cornerstoneimageloadprogress', this.imageLoadProgress)
console.log(Events)
},
// 影像下载进度回调
imageLoadProgress(e) {
const { detail } = e
const percentComplete = detail.percentComplete
const imageId = detail.imageId
const params = this.getInstanceInfo(imageId)
const instanceId = params.instanceId
const idxObj = this.instanceInfo[instanceId]
if (!(idxObj && typeof idxObj === 'object')) return
if (!(this.instanceInfo[instanceId].hasOwnProperty('percentComplete'))) {
this.instanceInfo[instanceId].percentComplete = 0
}
// if (!params.hasOwnProperty('idx')) return
// const indexArr = params.idx.split('|')
// if (indexArr.length < 3) return
const taskIndex = idxObj.taskIndex
const studyIndex = idxObj.studyIndex
const seriesIndex = idxObj.seriesIndex
const isCriticalSequence = this.visitTaskList[taskIndex].StudyList[studyIndex].IsCriticalSequence
const keyImages = this.visitTaskList[taskIndex].KeyImages
const series = this.visitTaskList[taskIndex].StudyList[studyIndex].SeriesList[seriesIndex]
this.setImageLoadedProgress(series, percentComplete, instanceId)
if (!isCriticalSequence && series.IsBeMark && keyImages.length > 0) {
const i = keyImages.findIndex(i => i.Id === params.instanceId)
if (i === -1) return
const keyStudyIndex = parseInt(keyImages[i].KeyStudyIndex)
const keySeriesIndex = parseInt(keyImages[i].KeySeriesIndex)
const keySeries = this.visitTaskList[taskIndex].StudyList[keyStudyIndex].SeriesList[keySeriesIndex]
this.setImageLoadedProgress(keySeries, percentComplete, instanceId)
}
},
// 设置图像下载进度信息
setImageLoadedProgress(series, percentComplete, instanceId) {
let loadedImageCount = series.LoadedImageCount
if (percentComplete === 100) {
++loadedImageCount
this.$set(series, 'LoadedImageCount', loadedImageCount)
}
const newLoadedImageProgress = series.LoadedImageProgress - this.instanceInfo[instanceId].percentComplete + percentComplete
this.instanceInfo[instanceId].percentComplete = percentComplete
this.$set(series, 'LoadedImageProgress', newLoadedImageProgress)
},
addHistoryAnnotations(taskId) {
// if (this.renderedTaskIds.includes(taskId)) return
// this.renderedTaskIds.push(taskId)
// let taskIdx = this.visitTaskList.findIndex(i=>i.VisitTaskId === taskId)
// if (taskIdx=== -1) return
// const annotations = this.visitTaskList[taskIdx].Annotations
// annotations.map(i => {
// if (i.MeasureData) {
// const annotation = i.MeasureData
// cornerstoneTools.annotation.state.addAnnotation(annotation)
// if (this.visitTaskList[taskIdx].ReadingTaskState === 2) {
// cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
// }
// }
// })
},
renderAnnotations(series) {
const taskId = series.TaskInfo.VisitTaskId
if (!taskId || this.renderedTaskIds.includes(taskId)) return
this.renderedTaskIds.push(taskId)
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === taskId)
if (taskIdx === -1) return
const annotations = this.visitTaskList[taskIdx].Annotations
annotations.map(i => {
if (i.MeasureData) {
const annotation = i.MeasureData
cornerstoneTools.annotation.state.addAnnotation(annotation)
if (this.visitTaskList[taskIdx].ReadingTaskState === 2) {
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
}
}
})
},
addAnnotationListeners() {
const debouncedCallback = this.debounce((evt) => {
this.annotationModifiedListener(evt)
}, 100)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_MODIFIED,
(evt) => {
debouncedCallback(evt)
}
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_COMPLETED,
this.annotationCompletedListener
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_REMOVED,
this.annotationRemovedListener
)
// eventTarget.addEventListener(
// toolsEvents.ANNOTATION_ADDED,
// this.annotationAddedListener
// )
// eventTarget.addEventListener(
// toolsEvents.TOOL_MODE_CHANGED,
// this.toolModeChanged
// )
},
toolModeChanged(e) {
console.log(e)
const arr = cornerstoneTools.annotation.state.getAllAnnotations()
// if (arr)
console.log(arr)
},
annotationAddedListener(e) {
},
annotationCompletedListener(e) {
console.log('Completed')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const referencedImageId = annotation.metadata.referencedImageId
const params = this.getInstanceInfo(referencedImageId)
annotation.visitTaskId = series.TaskInfo.VisitTaskId
annotation.studyId = series.StudyId
annotation.seriesId = series.Id
annotation.instanceId = params.instanceId
annotation.sliceThickness = series.SliceThickness
annotation.numberOfFrames = isNaN(parseInt(params.frame)) ? null : parseInt(params.frame)
annotation.markTool = annotation.metadata.toolName
// this.$refs['ecrf'].setAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].setAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.markedSeriesIds.push(series.Id)
}
this.setToolsPassive()
},
annotationModifiedListener(e) {
console.log('Modified')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
// this.$refs['ecrf'].modifyAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].modifyAnnotation({ annotation, toolName: annotation.metadata.toolName })
}
this.setToolsPassive()
},
annotationRemovedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
}
console.log(this.markedSeriesIds)
},
removeAnnotation(annotation) {
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `viewport-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
},
async getAnnotations(visitTaskId) {
if (this.readingTaskState === 2) return
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
if (taskIdx === -1) return
if (!this.visitTaskList[taskIdx].IsCurrentTask) return
// 获取标注信息
const res = await getTableAnswerRowInfoList(visitTaskId)
const annotationUIDs = []
const annotations = res.Result.map(i => {
if (typeof i.MeasureData === 'string' && i.MeasureData) {
i.MeasureData = JSON.parse(i.MeasureData)
annotationUIDs.push(i.MeasureData.annotationUID)
}
return i
})
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[taskIdx], 'AnnotationUIDs', annotationUIDs)
},
async resetAnnotations(visitTaskId) {
if (this.readingTaskState === 2) return
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
if (taskIdx === -1) return
if (!this.visitTaskList[taskIdx].IsCurrentTask) return
// 获取标注信息
const res = await getTableAnswerRowInfoList(visitTaskId)
const annotations = res.Result.map(i => {
if (typeof i.MeasureData === 'string' && i.MeasureData) {
i.MeasureData = JSON.parse(i.MeasureData)
}
return i
})
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
// 移除病灶
const arr = cornerstoneTools.annotation.state.getAllAnnotations()
arr.map(i => {
if (i.visitTaskId === visitTaskId) {
cornerstoneTools.annotation.state.removeAnnotation(i.annotationUID)
}
})
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `viewport-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
},
setMarkName(obj) {
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
const idx = annotations.findIndex(i => i.annotationUID === obj.annotationUID)
if (idx === -1) return
if (annotations[idx].metadata.toolName === 'ArrowAnnotate') {
annotations[idx].data.text = obj.name
} else {
annotations[idx].data.label = obj.name
}
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `viewport-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
},
getLengthToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { length, unit } = cachedVolumeStats
if (length === undefined || length === null || isNaN(length)) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
textLines.push(`${parseFloat(length).toFixed(this.digitPlaces)} ${unit}`)
return textLines
},
getPlanarFreehandROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const {
area,
mean,
stdDev,
// length,
perimeter,
max,
isEmptyArea,
unit,
areaUnit,
modalityUnit
} = cachedVolumeStats || {}
const textLines = []
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${parseFloat(area).toFixed(this.digitPlaces)} ${areaUnit}`
textLines.push(areaLine)
}
if (mean) {
textLines.push(`Mean: ${parseFloat(mean).toFixed(this.digitPlaces)} ${modalityUnit}`)
}
if (Number.isFinite(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`)
}
if (stdDev) {
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`)
}
if (perimeter) {
textLines.push(`Perimeter: ${parseFloat(perimeter).toFixed(this.digitPlaces)} ${unit}`)
}
// if (length) {
// // No need to show length prefix as there is just the single value
// textLines.push(`${csUtils.roundNumber(length)} ${unit}`);
// }
return textLines
},
getRectangleROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
// const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
const { mean } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
// textLines.push(`Area: ${parseFloat(area).toFixed(this.digitPlaces)} ${areaUnit}`)
// textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`)
// textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`)
// textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`)
return textLines
},
getBidirectionalToolTextLines(data, targetId) {
const { cachedStats, label } = data
const { length, width, unit } = cachedStats[targetId]
const textLines = []
// if (label) {
// textLines.push(label);
// }
if (label) {
textLines.push(label)
}
if (length === undefined) {
return textLines
}
// spaceBetweenSlices & pixelSpacing &
// magnitude in each direction? Otherwise, this is "px"?
textLines.push(
`L: ${csUtils.roundNumber(length)} ${unit || unit}`,
`S: ${csUtils.roundNumber(width)} ${unit}`
)
return textLines
},
// 激活工具
setToolActive(toolName) {
const toolGroupId = `viewport-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (this.activeTool === toolName) {
toolGroup.setToolPassive(this.activeTool)
this.activeTool = ''
} else {
if (this.activeTool) {
toolGroup.setToolPassive(this.activeTool)
}
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
},
// 激活标注工具
setAnnotateToolActive(toolName) {
if (this.readingTaskState === 2) return
const toolObj = this.tools.find(i => i.toolName === toolName)
if (!toolObj || toolObj.isDisabled) return
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = `viewport-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (this.activeTool === toolName) {
toolGroup.setToolPassive(this.activeTool)
this.activeTool = ''
} else {
if (this.activeTool) {
toolGroup.setToolPassive(this.activeTool)
}
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
}
},
setToolsPassive() {
if (!this.activeTool) return
const toolGroupIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolPassive(this.activeTool)
})
this.activeTool = ''
},
setToolEnabled() {
if (!this.activeTool) return
const toolGroupIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolEnabled(this.activeTool)
})
this.activeTool = ''
},
// 鼠标移入测量工具时,判断工具是否可激活
enter(e, toolName) {
const i = this.tools.findIndex(i => i.toolName === toolName)
if (i === -1) return
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
const isCurrentTask = series.TaskInfo.IsCurrentTask
const readingTaskState = this.readingTaskState
if (!isCurrentTask || readingTaskState >= 2) {
this.tools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
if (this.activeTool) {
this.setToolEnabled()
}
} else {
// const obj = this.$refs['ecrf'].validTool(toolName, true)
const obj = this.$refs[`ecrf_${this.lastViewportTaskId}`][0].validTool(toolName, true)
this.tools[i].disabledReason = obj.reason
if (!obj.isCanActiveTool) {
if (this.activeTool === toolName) {
this.setToolsPassive()
}
this.tools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
} else {
this.tools[i].isDisabled = false
e.target.style.cursor = 'pointer'
}
}
},
// 旋转视口
setViewportRotate(value) {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `viewport-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
const type = parseInt(value)
// 1默认值2垂直翻转3水平翻转4左转90度5右转90度
if (type === 1) {
viewport.resetCamera()
viewport.resetProperties()
viewport.render()
} else if (type === 2) {
const { flipVertical } = viewport.getCamera()
viewport.setCamera({ flipVertical: !flipVertical })
viewport.render()
} else if (type === 3) {
const { flipHorizontal } = viewport.getCamera()
viewport.setCamera({ flipHorizontal: !flipHorizontal })
viewport.render()
} else if (type === 4) {
const { rotation } = viewport.getViewPresentation()
viewport.setViewPresentation({ rotation: rotation === 0 ? 270 : rotation - 90 })
viewport.render()
} else if (type === 5) {
const { rotation } = viewport.getViewPresentation()
viewport.setViewPresentation({ rotation: rotation + 90 })
viewport.render()
}
this.$refs[`viewport-${this.activeViewportIndex}`][0].rotateOrientationMarkers(type)
},
// 重置视口
resetViewport() {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `viewport-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
this.$refs[`viewport-${this.activeViewportIndex}`][0].resetOrientationMarkers()
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
viewport.resetProperties()
viewport.render()
renderingEngine.render()
},
// 更改视图布局
async changeLayout(v) {
this.setToolsPassive()
this.fullScreenIndex = null
this.layout = v
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
const seriesArr = []
if (v === 1) {
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
if (typeof series === 'object') {
seriesArr.push(series)
}
} else if (v === 2) {
this.rows = 1
this.cols = 2
this.activeViewportIndex = 1
if (typeof series === 'object') {
seriesArr.push(series)
seriesArr.push(series)
}
} else if (v === 3) {
this.rows = 1
this.cols = 2
if (series) {
const idx = this.visitTaskList.findIndex(i => i.VisitTaskId === series.TaskInfo.VisitTaskId)
const visitTaskNum = idx > -1 ? this.visitTaskList[idx].VisitTaskNum : -1
if (visitTaskNum > 0) {
let visitTaskIdx = -1
if (this.citerionType === 10) {
visitTaskIdx = this.visitTaskList.findIndex(i => i.VisitTaskNum === visitTaskNum - 1)
} else {
visitTaskIdx = this.visitTaskList.findIndex(i => i.IsBaseLineTask)
}
if (visitTaskIdx > -1) {
if (!this.visitTaskList[visitTaskIdx].IsInit) {
this.sLoading = true
try {
const res = await this.loadTaskDetails(this.visitTaskList[visitTaskIdx], visitTaskIdx)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const keyImages = res ? res.keyImages : []
const annotationUIDs = res ? res.annotationUIDs : []
this.$set(this.visitTaskList[visitTaskIdx], 'StudyList', studyList)
this.$set(this.visitTaskList[visitTaskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[visitTaskIdx], 'KeyImages', keyImages)
this.$set(this.visitTaskList[visitTaskIdx], 'AnnotationUIDs', annotationUIDs)
this.$set(this.visitTaskList[visitTaskIdx], 'IsInit', true)
this.sLoading = false
} catch (e) {
this.sLoading = false
}
}
const relatedSeries = this.getRelatedSeries(this.visitTaskList[visitTaskIdx], series)
seriesArr.push(relatedSeries)
seriesArr.push(series)
}
} else if (visitTaskNum === 0) {
seriesArr.push(series)
seriesArr.push(series)
}
}
this.activeViewportIndex = 1
} else if (v === 4) {
this.rows = 2
this.cols = 2
if (typeof series === 'object') {
seriesArr.push(series)
seriesArr.push(series)
seriesArr.push(series)
seriesArr.push(series)
}
this.activeViewportIndex = 3
}
seriesArr.map((i, index) => {
this.$refs[`viewport-${index}`][0].setSeriesInfo(i)
})
this.$nextTick(() => {
const renderingEngine = getRenderingEngine(renderingEngineId)
renderingEngine.resize(true, false)
})
},
// 更改窗宽窗位
changeVoiRange(v) {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `viewport-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
if (v.val === -1) {
// 默认值
viewport.resetProperties()
viewport.render()
} else if (v.val === 0) {
// 自定义
const wwwc = this.$refs[`viewport-${this.activeViewportIndex}`][0].imageInfo.wwwc
this.activeViewportWW = wwwc ? parseInt(wwwc.split('/')[0]) : null
this.activeViewportWC = wwwc ? parseInt(wwwc.split('/')[1]) : null
this.customWwc.visible = true
} else if (v.val === 1) {
// 区域窗宽窗位
this.setToolActive('WindowLevelRegion')
} else {
const lower = v.wc - v.ww / 2
const upper = v.wc + v.ww / 2 - 1
viewport.setProperties({ voiRange: { upper: upper, lower: lower }})
viewport.render()
}
},
setWindowLevelActive(e) {
this.setToolActive('WindowLevel')
this.showPanel(e)
},
// 设置窗宽窗位
setWwwc(v) {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `viewport-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
const lower = v.wc - v.ww / 2
const upper = v.wc + v.ww / 2 - 1
viewport.setProperties({ voiRange: { upper: upper, lower: lower }})
viewport.render()
this.customWwc.visible = false
},
// 反色
toggleInvert() {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`viewport-${this.activeViewportIndex}`)
const { invert } = viewport.getProperties()
viewport.setProperties({ invert: !invert })
viewport.render()
},
// 翻页
scrollPage(type) {
this.clipPlaying = false
this.$refs[`viewport-${this.activeViewportIndex}`][0].scrollPage(type)
},
// 播放
toggleClipPlay(isPlay) {
this.clipPlaying = !this.clipPlaying
this.$refs[`viewport-${this.activeViewportIndex}`][0].toggleClipPlay(isPlay, this.fps)
},
// 获取窗宽窗位模板
async getWwcTpl() {
try {
const res = await getUserWLTemplateList()
const customWwcTpl = []
res.Result.map(i => {
customWwcTpl.push({ label: i.TemplateName, wc: i.WL, ww: i.WW })
})
this.wwwcArr = [...this.defaultWwwc, ...customWwcTpl]
} catch (e) {
console.log(e)
}
},
// 获取热键配置信息
async getHotKeys() {
try {
const res = await getDoctorShortcutKey({ imageToolType: 0 })
res.Result.map(item => {
this.hotKeyList.push({ id: item.Id, altKey: item.AltKey, ctrlKey: item.CtrlKey, shiftKey: item.ShiftKey, metaKey: item.MetaKey, key: item.Keyboardkey, code: item.Code, text: item.Text, shortcutKeyEnum: item.ShortcutKeyEnum })
})
this.bindHotKey()
} catch (e) {
console.log(e)
}
},
// 绑定热键事件
bindHotKey() {
const container = this.$refs['container']
container.addEventListener('keydown', event => {
let idx = this.hotKeyList.findIndex(i => i.code === event.code && i.ctrlKey === event.ctrlKey && i.shiftKey === event.shiftKey && i.altKey === event.altKey)
if (idx === -1) return
let shortcutKeyEnum = this.hotKeyList[idx].shortcutKeyEnum
if (shortcutKeyEnum === 1) {
// 前一图像视口
const viewportIndex = this.activeViewportIndex === 0 ? this.activeViewportIndex : this.activeViewportIndex - 1
this.activeViewport(viewportIndex)
} else if (shortcutKeyEnum === 2) {
// 后一图像视口
const viewportIndex = this.activeViewportIndex === this.cells.length - 1 ? this.activeViewportIndex : this.activeViewportIndex + 1
this.activeViewport(viewportIndex)
} else if (shortcutKeyEnum === 3) {
// 上一个序列
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
this.$refs[this.activeTaskId][0].getPreviousOrNextSeries(-1, series)
} else if (shortcutKeyEnum === 4) {
// 下一个序列
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
this.$refs[this.activeTaskId][0].getPreviousOrNextSeries(1, series)
} else if (shortcutKeyEnum === 5) {
// 上一张图像
this.$refs[`viewport-${this.activeViewportIndex}`][0].scrollPage(-1)
} else if (shortcutKeyEnum === 6) {
// 下一张图像
this.$refs[`viewport-${this.activeViewportIndex}`][0].scrollPage(1)
} else if (shortcutKeyEnum === 7) {
// 向左旋转
this.setViewportRotate(4)
} else if (shortcutKeyEnum === 8) {
// 向右旋转
this.setViewportRotate(5)
} else if (shortcutKeyEnum === 9) {
// 水平翻转
this.setViewportRotate(3)
} else if (shortcutKeyEnum === 10) {
// 垂直翻转
this.setViewportRotate(2)
} else if (shortcutKeyEnum === 11) {
// 放大
this.$refs[`viewport-${this.activeViewportIndex}`][0].setZoom(1)
} else if (shortcutKeyEnum === 12) {
// 缩小
this.$refs[`viewport-${this.activeViewportIndex}`][0].setZoom(-1)
} else if (shortcutKeyEnum === 13) {
// 适应图像
this.$refs[`viewport-${this.activeViewportIndex}`][0].resize(false)
} else if (shortcutKeyEnum === 14) {
// 适应窗口
this.$refs[`viewport-${this.activeViewportIndex}`][0].resize(true)
} else if (shortcutKeyEnum === 15) {
// 截图
} else if (shortcutKeyEnum === 16) {
// 反色
this.toggleInvert()
} else if (shortcutKeyEnum === 17) {
// 窗宽/窗位
const wwwcIdx = this.$refs[`viewport-${this.activeViewportIndex}`][0].wwwcIdx
const newWwwcIdx = wwwcIdx === this.wwwcArr.length - 1 ? 3 : wwwcIdx + 1
const wwwcTpl = this.wwwcArr[newWwwcIdx]
this.changeVoiRange(wwwcTpl)
this.$refs[`viewport-${this.activeViewportIndex}`][0].setWwwcIdx(newWwwcIdx)
} else if (shortcutKeyEnum === 18) {
// 重置
this.resetViewport()
} else if (shortcutKeyEnum === 19) {
// 显示或隐藏标记
this.isShowAnnotations = !this.isShowAnnotations
if (this.isShowAnnotations) {
visibility.showAllAnnotations()
} else {
const annotationUIDs = annotation.state.getAllAnnotations().map((a) => a.annotationUID)
annotationUIDs.forEach((annotationUID) => {
visibility.setAnnotationVisibility(annotationUID, false)
})
}
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
renderingEngine.renderViewports(viewportIds)
}
event.stopImmediatePropagation()
event.stopPropagation()
event.preventDefault()
})
},
// 重置热键信息
resetHotkeyList(arr) {
this.hotKeyList = []
arr.map(item => {
this.hotKeyList.push({ id: item.id, altKey: item.keys.controlKey.altKey, ctrlKey: item.keys.controlKey.ctrlKey, shiftKey: item.keys.controlKey.shiftKey, metaKey: item.keys.controlKey.metaKey, key: item.keys.controlKey.key, code: item.keys.controlKey.code, text: item.keys.text, shortcutKeyEnum: item.label })
})
},
fitToType(forceFitToWindow) {
this.$refs[`viewport-${this.activeViewportIndex}`][0].resize(forceFitToWindow)
this.forceFitToWindow = !forceFitToWindow
},
// 切换全屏
toggleFullScreen(e, index) {
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
this.activeViewportIndex = index
},
async toggleTask(taskInfo, taskIndex) {
if (taskIndex === this.activeTaskIndex) return
if (!this.selectArr.includes(taskInfo.VisitTaskId)) {
this.selectArr.push(taskInfo.VisitTaskId)
}
this.activeTaskId = taskInfo.VisitTaskId
this.activeTaskIndex = taskIndex
if (!taskInfo.IsInit) {
this.sLoading = true
try {
const res = await this.loadTaskDetails(taskInfo, taskIndex)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const annotationUIDs = res ? res.annotationUIDs : []
const keyImages = res ? res.keyImages : []
this.$set(taskInfo, 'StudyList', studyList)
this.$set(taskInfo, 'Annotations', annotations)
this.$set(taskInfo, 'KeyImages', keyImages)
this.$set(taskInfo, 'AnnotationUIDs', annotationUIDs)
this.$set(taskInfo, 'IsInit', true)
this.sLoading = false
} catch (e) {
this.sLoading = false
}
}
this.setToolsPassive()
},
async toggleTaskByViewport(obj) {
const i = this.visitTaskList.findIndex(v => v.VisitTaskNum === obj.visitTaskNum)
if (i === -1) return
this.activeTaskId = this.visitTaskList[i].VisitTaskId
this.activeTaskIndex = i
if (!this.selectArr.includes(this.activeTaskId)) {
this.selectArr.push(this.activeTaskId)
}
if (!this.visitTaskList[i].IsInit) {
this.sLoading = true
try {
const res = await this.loadTaskDetails(this.visitTaskList[i], i)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const annotationUIDs = res ? res.annotationUIDs : []
const keyImages = res ? res.keyImages : []
this.$set(this.visitTaskList[i], 'StudyList', studyList)
this.$set(this.visitTaskList[i], 'Annotations', annotations)
this.$set(this.visitTaskList[i], 'KeyImages', keyImages)
this.$set(this.visitTaskList[i], 'AnnotationUIDs', annotationUIDs)
this.$set(this.visitTaskList[i], 'IsInit', true)
this.sLoading = false
} catch (e) {
this.sLoading = false
}
}
const series = this.getRelatedSeries(this.visitTaskList[i], obj.series)
this.$nextTick(() => {
this.$refs[`viewport-${this.activeViewportIndex}`][0].setSeriesInfo(series)
this.$refs[this.activeTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
if (this.activeViewportIndex === this.cells.length - 1) {
this.lastViewportTaskId = series.TaskInfo.VisitTaskId
this.currentVisitInfo = series.TaskInfo
}
})
this.setToolsPassive()
},
async activeSeries(obj) {
this.$refs[`viewport-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
this.clipPlaying = false
this.fps = 15
if (this.activeViewportIndex === this.cells.length - 1) {
this.lastViewportTaskId = obj.TaskInfo.VisitTaskId
this.currentVisitInfo = obj.TaskInfo
}
this.setToolsPassive()
},
async showMultiFrame(obj) {
this.$refs[`viewport-${this.activeViewportIndex}`][0].setSeriesInfo(obj, true)
this.clipPlaying = false
this.fps = 15
if (this.activeViewportIndex === this.cells.length - 1) {
this.lastViewportTaskId = obj.TaskInfo.VisitTaskId
this.currentVisitInfo = obj.TaskInfo
}
this.setToolsPassive()
},
// 激活视口
activeViewport(index) {
if (this.activeViewportIndex === index) return
this.activeViewportIndex = index
// 切换检查列表
const series = this.$refs[`viewport-${this.activeViewportIndex}`][0].series
if (series) {
const i = this.visitTaskList.findIndex(v => v.VisitTaskId === series.TaskInfo.VisitTaskId)
if (i > -1) {
this.activeTaskId = series.TaskInfo.VisitTaskId
this.activeTaskIndex = i
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
}
}
this.setToolsPassive()
},
getRelatedSeries(visitTaskInfo, baselineSeries) {
let obj = {}
const studyList = visitTaskInfo.StudyList
const annotations = visitTaskInfo.Annotations
if (baselineSeries) {
const seriesList = studyList.map(s => s.IsDicom && s.SeriesList).flat()
if (baselineSeries.IsMarked) {
const i = annotations.findIndex(i => Object.keys(i.MeasureData).length > 0 && i.OrderMarkName === baselineSeries.MeasureData.OrderMarkName)
if (i > -1) {
obj = this.getMarkedSeries(studyList, annotations[i])
}
}
if (Object.keys(obj).length === 0) {
// 筛选描述相似
let similarArr = seriesList.map((i, index) => {
return { similar: this.strSimilarity2Percent(i.Description, baselineSeries.Description), index }
})
similarArr = similarArr.sort((a, b) => {
return b.similar - a.similar
})
const idx = similarArr[0] && similarArr[0].similar > 0.85 ? similarArr[0].index : -1
if (idx > -1) {
obj = Object.assign({}, seriesList[idx])
const stack = seriesList[idx].Stack
const sliceIdx = Math.floor(stack.length * ((baselineSeries.SliceIndex + 1) / baselineSeries.Stack.length))
// let sliceIdx = Math.floor(imageIds.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
}
}
if (Object.keys(obj).length === 0) {
// 筛选层厚一致
const idx = seriesList.findIndex(series => series.InstanceCount === baselineSeries.InstanceCount)
if (idx > -1) {
obj = Object.assign({}, seriesList[idx])
const stack = seriesList[idx].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
}
}
if (Object.keys(obj).length === 0) {
// 筛选层厚为5的序列
const idx = seriesList.findIndex(series => series.IsDicom && parseInt(series.SliceThickness) === 5)
if (idx > -1) {
obj = Object.assign({}, seriesList[idx])
const stack = seriesList[idx].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
}
}
} else {
const i = annotations.findIndex(a => Object.keys(a.MeasureData).length > 0)
if (i > -1) {
// 有标记时,显示带标记影像所在序列
obj = this.getMarkedSeries(studyList, annotations[i])
} else {
for (let k = 0; k < studyList.length; k++) {
const seriesIdx = studyList[k].SeriesList.findIndex(s => s.SliceThickness && parseInt(s.SliceThickness) === 5)
if (seriesIdx > -1) {
obj = Object.assign({}, studyList[k].SeriesList[seriesIdx])
const stack = studyList[k].SeriesList[seriesIdx].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
obj.IsMarked = false
break
}
}
}
}
if (Object.keys(obj).length === 0) {
const sIdx = studyList.findIndex(s => s.IsDicom)
if (sIdx > -1) {
obj = Object.assign({}, studyList[sIdx].SeriesList[0])
const stack = studyList[sIdx].SeriesList[0].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
obj.IsMarked = false
}
}
return obj
},
getMarkedSeries(studyList, annotation) {
let obj = {}
const sIdx = studyList.findIndex(s => s.StudyId === annotation.StudyId)
if (sIdx > -1) {
const seriesList = studyList[sIdx].SeriesList
const seriesIdx = seriesList.findIndex(s => s.Id === annotation.SeriesId)
if (seriesIdx > -1) {
const instanceIdx = seriesList[seriesIdx].InstanceInfoList.findIndex(i => i.Id === annotation.InstanceId)
let filterStr = ''
if (instanceIdx > -1 && seriesList[seriesIdx].IsExistMutiFrames) {
if (seriesList[seriesIdx].InstanceInfoList[instanceIdx].NumberOfFrames > 0) {
filterStr = `frame=${annotation.MeasureData.frame}&instanceId=${annotation.InstanceId}`
} else {
filterStr = `instanceId=${annotation.InstanceId}`
}
} else {
filterStr = `instanceId=${annotation.InstanceId}`
}
const stack = seriesList[seriesIdx].Stack
const imageIdIdx = stack.findIndex(is => is.includes(filterStr))
if (imageIdIdx > -1) {
obj = Object.assign({}, seriesList[seriesIdx])
obj.SliceIndex = imageIdIdx
obj.IsMarked = true
obj.MeasureData = annotation
}
}
}
return obj
},
async getScreenshots(measureData, callback) {
if (measureData) {
await this.imageLocation(measureData)
setTimeout(async() => {
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="viewport-${this.activeViewportIndex}"]`
)
const canvas = await html2canvas(divForDownloadViewport)
const base64Str = canvas.toDataURL('image/png', 1)
callback(base64Str)
}, 50)
} else {
callback()
}
},
imageLocation(obj) {
return new Promise(async resolve => {
let loading = null
const index = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
if (index === -1) {
resolve()
return
}
if (obj.annotationUID) {
obj.StudyId = obj.studyId
obj.SeriesId = obj.seriesId
obj.InstanceId = obj.instanceId
obj.isMarked = true
} else {
obj.isMarked = false
}
let firstAddSeries = {}
let currentAddSeries = {}
if (this.taskInfo.IsReadingTaskViewInOrder === 1) {
// 有序
// 获取病灶第一次出现的访视序列
let firstAddVisitTaskId = null
const idx = this.visitTaskList[index].Annotations.findIndex(item => {
return item.OrderMarkName === obj.lesionName
})
if (idx > -1) {
firstAddVisitTaskId = this.visitTaskList[index].Annotations[idx].FristAddTaskId
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === firstAddVisitTaskId)
if (taskIdx > -1 && !this.visitTaskList[taskIdx].IsInit) {
loading = this.$loading({ fullscreen: true })
try {
const res = await this.loadTaskDetails(this.visitTaskList[taskIdx], taskIdx)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const annotationUIDs = res ? res.annotationUIDs : []
const keyImages = res ? res.keyImages : []
this.$set(this.visitTaskList[taskIdx], 'StudyList', studyList)
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[taskIdx], 'KeyImages', keyImages)
this.$set(this.visitTaskList[taskIdx], 'AnnotationUIDs', annotationUIDs)
this.$set(this.visitTaskList[taskIdx], 'IsInit', true)
loading.close()
} catch (e) {
loading.close()
}
}
const annotationIdx = this.visitTaskList[taskIdx].Annotations.findIndex(i => i.OrderMarkName === obj.lesionName)
if (annotationIdx > -1) {
firstAddSeries = this.getMarkedSeries(this.visitTaskList[taskIdx].StudyList, this.visitTaskList[taskIdx].Annotations[annotationIdx])
}
}
if (obj.isMarked) {
currentAddSeries = this.getMarkedSeries(this.visitTaskList[index].StudyList, obj)
}
if (Object.keys(firstAddSeries).length === 0 && Object.keys(currentAddSeries).length !== 0) {
firstAddSeries = currentAddSeries
} else if (Object.keys(firstAddSeries).length === 0 && Object.keys(currentAddSeries).length === 0) {
this.setToolsPassive()
if (obj.isActiveTarget) {
this.setToolToTarget(obj)
}
resolve()
return
} else if (Object.keys(firstAddSeries).length !== 0 && Object.keys(currentAddSeries).length === 0) {
// 当前访视序列与首次出现病灶的序列对齐
// currentAddSeries = this.getRelatedSeries(this.visitTaskList[index], firstAddSeries)
// if (Object.keys(currentAddSeries).length === 0) {
// // 未找到对齐的,则就显示当前最后一个窗口现实的序列信息
// currentAddSeries = this.$refs[`viewport-${this.cells.length - 1}`][0].series
// }
// 显示当前序列
currentAddSeries = this.$refs[`viewport-${this.cells.length - 1}`][0].series
}
} else {
// 无序
currentAddSeries = this.getMarkedSeries(this.visitTaskList[index].StudyList, obj)
if (Object.keys(currentAddSeries).length === 0) {
// 未找到标注序列的则就显示当前最后一个窗口现实的序列信息cells.length - 1
currentAddSeries = this.$refs[`viewport-${this.cells.length - 1}`][0].series
}
firstAddSeries = currentAddSeries
}
for (let i = 0; i < this.cells.length; i++) {
if (i === this.cells.length - 1) {
this.$refs[`viewport-${i}`][0].setSeriesInfo(currentAddSeries, true)
this.activeViewportIndex = i
this.$refs[currentAddSeries.TaskInfo.VisitTaskId][0].setSeriesActive(currentAddSeries.StudyIndex, currentAddSeries.SeriesIndex)
} else {
this.$refs[`viewport-${i}`][0].setSeriesInfo(firstAddSeries, true)
}
}
this.setToolsPassive()
if (obj.isActiveTarget) {
this.setToolToTarget(obj)
}
resolve()
})
},
setToolToTarget(obj) {
console.log('setToolToTarget')
if (obj.visitTaskId === this.taskInfo.VisitTaskId && this.readingTaskState !== 2 && obj.markTool && !obj.isMarked) {
const toolName = obj.markTool
if (this.activeTool) {
this.setToolsPassive()
}
const toolGroupId = `viewport-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
},
setReadingTaskState(state) {
this.readingTaskState = state
},
// 两个字符串的相似程度,并返回相似度百分比
strSimilarity2Percent(s, t) {
var l = s.length > t.length ? s.length : t.length
var d = this.strSimilarity2Number(s, t)
return Number((1 - d / l).toFixed(4))
},
strSimilarity2Number(s, t) {
var n = s.length; var m = t.length; var d = []
var i, j, s_i, t_j, cost
if (n === 0) return m
if (m === 0) return n
for (i = 0; i <= n; i++) {
d[i] = []
d[i][0] = i
}
for (j = 0; j <= m; j++) {
d[0][j] = j
}
for (i = 1; i <= n; i++) {
s_i = s.charAt(i - 1)
for (j = 1; j <= m; j++) {
t_j = t.charAt(j - 1)
if (s_i === t_j) {
cost = 0
} else {
cost = 1
}
d[i][j] = this.Minimum(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost)
}
}
return d[n][m]
},
Minimum(a, b, c) {
return a < b ? (a < c ? a : c) : (b < c ? b : c)
},
// 手册
previewManuals() {
this.manualsDialog.isFullscreen = false
this.manualsDialog.visible = true
},
// 临床数据
previewCD(taskId) {
this.isClinicalDataFullscreen = false
this.dialogVisible = true
this.cdVisitTaskId = taskId
this.clinicalDataVisible = true
},
// 个性化配置
previewConfig() {
this.personalConfigDialog.activeName = '1'
this.personalConfigDialog.visible = true
},
getInstanceInfo(imageId) {
const params = {}
const searchParams = new URLSearchParams(imageId.split('?')[1])
for (const [key, value] of searchParams.entries()) {
params[key] = value
}
if (isNaN(params.frame)) {
params.frame = null
}
return params
},
// 箭头工具输入标记名称自定义弹窗
async customPrompt() {
try {
// 请输入标记名称
const { value } = await this.$prompt(this.$t('trials:noneDicom:message:msg1'))
return value
} catch {
return null
}
},
showPanel(e) {
e.currentTarget.firstChild.lastChild.style.display = 'block'
},
toolMouseout(e) {
e.currentTarget.firstChild.lastChild.style.display = 'none'
},
debounce(callback, delay) {
let timerId
return function() {
clearTimeout(timerId)
timerId = setTimeout(() => {
callback.apply(this, arguments)
}, delay)
}
}
}
}
</script>
<style lang="scss" scoped>
.read-page-container {
box-sizing: border-box;
height: 100%;
display: flex;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.left-panel {
display: flex;
width: 200px;
border: 1px solid #727272;
color: #fff;
user-select: none;
::-webkit-scrollbar {
width: 3px;
height: 3px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.task-container {
position: relative;
width: 25px;
overflow-y: auto;
}
.task-info {
position: absolute;
top: 5px;
right: 20px;
transform-origin: right top;
transform: rotate(-90deg);
display: flex;
.task-item {
margin-left: 10px;
white-space: nowrap;
padding: 0px 4px;
border: 1px solid #999999;
border-bottom:none ;
text-align: center;
background-color: #4e4e4e;
color: #d5d5d5;
cursor: pointer;
}
.task-item-active {
background-color: #607d8b;
border: 1px solid #607d8b;
}
}
.study-info {
width: 170px;
border-left: 1px solid #727272;
}
}
.middle-panel {
flex: 1;
border: 1px solid #727272;
margin: 0 5px;
user-select: none;
.dicom-viewer {
display: flex;
flex-direction: column;
width:100%;
height: 100%;
user-select: none;
.tools-wrapper {
height: 50px;
display: flex;
align-items: center;
border-bottom: 1px solid #727272;
color: #ddd;
padding: 0 5px;
.tools-left {
flex: 1;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.tool-item {
padding: 5px;
margin: 0 5px;
border: 1px solid #333;
font-size: 20px;
cursor: pointer;
.el-dropdown-link {
.svg-icon {
color: #ddd;
font-size: 20px;
}
}
}
.tool-item-active {
background-color: #607d8b;
}
.tool-disabled {
cursor: not-allowed;
}
.tool-frame{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
// margin-right: 20px;
padding: 1px;
margin: 0 5px;
border: 1px solid #404040;
.icon{
padding: 5px;
border-right: 1px solid #404040;
cursor: pointer;
text-align: center;
.svg-icon{
font-size:20px;
color:#ddd;
}
}
.select-wrapper{
width: 60px;
background-color: black;
color: #ddd;
border: none;
font-size: 13px;
outline: none;
}
.text{
position: relative;
font-size: 12px;
margin-top: 5px;
color: #d0d0d0;
display: none;
}
}
// .icon:hover{
// background-color: #607d8b;
// }
.dropdown {
position: relative;
display: inline-block;
.text{
text-align: center;
}
}
.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 a:hover{
background-color: #727272;
}
}
.layout-content-ul li:hover{
background-color: #727272;
}
.layout-content ul li{
border-top:1px solid #ddd;
border-left:1px solid #ddd;
}
.layout_flex_row {
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
text-align: center;
margin-bottom: 2px;
}
.layout_flex_column{
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
margin-bottom: 2px;
}
.layout_box_1_1{
flex:1;
line-height: 30px;
font-size: 12px;
text-align: center;
border-bottom:1px solid #ddd;
border-right:1px solid #ddd;
}
.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;
}
.divider{
display: block;
height: 1px;
width: 100%;
margin: 10px 0;
}
.el-divider__text{
padding: 0 10px;
font-size: 12px;
width: 80px;
background-color: #383838;
color: #fff;
}
}
.viewports-wrapper {
flex: 1;
.grid-container {
display: grid;
height: 100%;
width: 100%;
position: relative;
}
.grid-cell {
border: 1px dashed #ccc;;
display: flex;
align-items: center;
justify-content: center;
}
.cell_active {
border-color: #fafa00!important;
}
.cell-full-screen {
grid-column: 1 / -1;
grid-row: 1 / -1;
}
}
}
}
.right-panel {
width: 400px;
border: 1px solid #727272;
overflow-y: auto;
user-select: none;
}
.personal_config {
::v-deep .el-tabs__content{
height: 450px;
overflow-y: auto;
}
}
::v-deep .manuals-dialog-container{
margin-top: 50px !important;
width:75%;
height:80%;
.el-dialog__body{
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header{
position: relative;
}
}
::v-deep .manuals-full-dialog-container{
.el-dialog__body{
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header{
position: relative;
}
}
::v-deep .cd-dialog-container{
background: #fff !important;
margin-top: 50px !important;
width: 75%;
height: 80%;
.el-dialog__body{
padding: 20px 20px 0 20px;
height: calc(100% - 70px);
}
.el-dialog__header{
position: relative;
}
}
::v-deep .cd-full-dialog-container{
background: #fff !important;
.el-dialog__body{
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header{
position: relative;
}
}
::v-deep .el-dropdown-menu{
.el-dropdown-menu__item {
padding: 0 5px;
}
.layout_flex_row {
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
width: 40px;
align-items: center;
text-align: center;
}
.layout_flex_column{
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
}
.layout_box_1_1{
flex:1;
line-height: 30px;
font-size: 12px;
text-align: center;
// border-bottom:1px solid #ddd;
// border-right:1px solid #ddd;
}
.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;
}
}
}
</style>