3604 lines
137 KiB
Vue
3604 lines
137 KiB
Vue
<template>
|
||
<div
|
||
v-loading="loading"
|
||
:element-loading-text="loadingText"
|
||
element-loading-spinner="el-icon-loading"
|
||
element-loading-background="rgba(0, 0, 0, 0.8)"
|
||
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"
|
||
:marked-series-ids="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', isFusion ? 'tool-disabled' : '']"
|
||
:title="$t('trials:reading:button:layout')"
|
||
@click.stop="showPanel($event, 'layout')"
|
||
@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-if="readingTool === 2" class="tool-item" :title="$t('trials:lugano:button:fusion')" @click.prevent="openFusion">
|
||
<svg-icon icon-class="fusion" 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
|
||
v-if="criterionType === 0"
|
||
:class="['tool-item', activeTool === 'Eraser' ? 'tool-item-active' : '']"
|
||
:title="$t('trials:dicom-show:Eraser')"
|
||
@click.prevent="setToolActive('Eraser')"
|
||
>
|
||
<svg-icon icon-class="clear" 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
|
||
v-if="criterionType === 0"
|
||
:title="$t('trials:reading:button:more')"
|
||
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '']"
|
||
@click.stop="showPanel($event)"
|
||
@mouseleave="toolMouseout"
|
||
>
|
||
<div class="dropdown">
|
||
<div
|
||
class="icon"
|
||
data-tool="more"
|
||
>
|
||
<svg-icon icon-class="more" class="svg-icon" />
|
||
<i class="el-icon-arrow-down" style="color:#fff;" />
|
||
</div>
|
||
<div class="dropdown-content">
|
||
<ul v-if="readingTaskState < 2" style="width:100px;">
|
||
<li v-for="i in customizeStandards" :key="i.toolName" style="text-align:left;">
|
||
<a href="#" @click.prevent="setMoreToolActive(i.toolName)">
|
||
<svg-icon :icon-class="i.icon" class="svg-icon" style="margin-right: 5px;" />
|
||
{{ $t(i.i18nKey) }}
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="tool-item"
|
||
:title="$t('trials:reading:button:upload')"
|
||
v-if="trialCriterion.ImageUploadEnum > 0 && readingTaskState < 2"
|
||
v-hasPermi="['role:ir']"
|
||
>
|
||
<div class="tool-wrapper">
|
||
<div class="icon" @click.prevent="openUploadImage('upload')">
|
||
<i class="el-icon-upload2 svg-icon" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-if="trialCriterion.ImageDownloadEnum > 0"
|
||
v-hasPermi="[
|
||
'role:ir',
|
||
'role:mim',
|
||
'role:mc',
|
||
'role:pm',
|
||
'role:apm',
|
||
'role:ea',
|
||
'role:qa',
|
||
]"
|
||
class="tool-item"
|
||
:title="$t('trials:reading:button:download')"
|
||
>
|
||
<div class="tool-wrapper">
|
||
<div class="icon" @click.prevent="openUploadImage('download')">
|
||
<i class="el-icon-download svg-icon" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 伪彩 -->
|
||
<template v-if="readingTool === 2">
|
||
<colorMap v-show="isFusion" ref="colorMap" @setColorMap="setColorMap" @voiChange="voiChange" />
|
||
</template>
|
||
</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>
|
||
<div class="content-wrapper">
|
||
<!-- viewports -->
|
||
<div class="viewports-wrapper">
|
||
<div ref="container" class="grid-container">
|
||
<div :class="[ 'viewports-box', isFusion ? 'viewports-box-down' : '' ]" :style="gridStyle">
|
||
<div
|
||
v-for="(v, index) in cellsMax"
|
||
v-show="index < cells.length"
|
||
:key="`viewport-${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 v-if="readingTool === 2" :class="[ 'viewports-box', !isFusion ? 'viewports-box-down' : '' ]" :style="gridStyle">
|
||
<div
|
||
v-for="(v, index) in cellsMax"
|
||
v-show="index < cells.length"
|
||
:key="`viewport-fusion-${index}`"
|
||
:style="cellStyle"
|
||
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
|
||
@dblclick="toggleFullScreen($event, index)"
|
||
@click="activeViewport(index)"
|
||
>
|
||
<PetCtViewport
|
||
:ref="`viewport-fusion-${index}`"
|
||
:data-viewport-uid="`viewport-fusion-${index}`"
|
||
:rendering-engine-id="renderingEngineId"
|
||
:viewport-id="`viewport-fusion-${index}`"
|
||
:viewport-index="index"
|
||
@activeViewport="activeViewport"
|
||
@toggleTaskByViewport="toggleTaskByViewport"
|
||
@previewCD="previewCD"
|
||
@renderAnnotations="renderAnnotations"
|
||
@upperRangeChange="upperRangeChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 表单 -->
|
||
<div class="form-wrapper">
|
||
<div
|
||
v-for="s in visitTaskList"
|
||
v-show="lastViewportTaskId === s.VisitTaskId"
|
||
:key="s.VisitTaskId"
|
||
style="height: 100%;"
|
||
>
|
||
<mRecisit
|
||
v-if="lastViewportTaskId && criterionType === 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"
|
||
/>
|
||
<recisit
|
||
v-else-if="lastViewportTaskId && criterionType === 1 && 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"
|
||
/>
|
||
<customize-question-list
|
||
v-else-if="lastViewportTaskId && criterionType === 0 && lastViewportTaskIds.includes(s.VisitTaskId)"
|
||
:ref="`ecrf_${s.VisitTaskId}`"
|
||
:reading-task-state="taskInfo && currentVisitInfo.VisitTaskId === taskInfo.VisitTaskId ? readingTaskState : 2"
|
||
:last-viewport-task-id="lastViewportTaskId"
|
||
:visit-info="s"
|
||
@resetAnnotations="resetAnnotations"
|
||
@setReadingTaskState="setReadingTaskState"
|
||
@viewCustomAnnotationSeries="viewCustomAnnotationSeries"
|
||
@getCustomScreenshots="getCustomScreenshots"
|
||
@setReadingToolActive="setReadingToolActive"
|
||
@setReadingToolPassive="setReadingToolPassive"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</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"
|
||
>
|
||
<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>
|
||
<el-dialog
|
||
:visible.sync="fusionVisible"
|
||
:close-on-click-modal="false"
|
||
:title="$t('trials:lugano:button:record')"
|
||
width="850px"
|
||
>
|
||
<FusionForm v-if="fusionVisible" :active-task-index="activeTaskIndex" :task-list="visitTaskList" @close="closeFusion" @fusion="handleFusion" />
|
||
</el-dialog>
|
||
|
||
<upload-dicom-and-nonedicom
|
||
v-if="uploadImageVisible"
|
||
:subject-id="uploadSubjectId"
|
||
:subject-code="uploadSubjectCode"
|
||
:criterion="uploadTrialCriterion"
|
||
:visible.sync="uploadImageVisible"
|
||
:visit-task-id="taskId"
|
||
:is-reading-task-view-in-order="isReadingTaskViewInOrder"
|
||
/>
|
||
<download-dicom-and-nonedicom
|
||
v-if="downloadImageVisible"
|
||
:subject-id="uploadSubjectId"
|
||
:subject-code="uploadSubjectCode"
|
||
:criterion="uploadTrialCriterion"
|
||
:task-id="taskId"
|
||
:visible.sync="downloadImageVisible"
|
||
/>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
import { getRelatedVisitTask, getReadingVisitStudyList, getTableAnswerRowInfoList, deleteCustomTag, getCriterionReadingInfo } from '@/api/trials'
|
||
import { getDoctorShortcutKey, getUserWLTemplateList } from '@/api/user'
|
||
import { getCustomTag, submitCustomTag } from '@/api/reading'
|
||
import { getToken } from '@/utils/auth'
|
||
import {
|
||
RenderingEngine,
|
||
Enums,
|
||
// imageLoader,
|
||
// metaData,
|
||
volumeLoader,
|
||
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, getCustomizeStandardsTools, config } from './toolConfig'
|
||
import StudyList from './StudyList'
|
||
import Viewport from './Viewport'
|
||
import PetCtViewport from './PetCtViewport'
|
||
import mRecisit from './mRecist/QuestionList'
|
||
import recisit from './Recist/QuestionList'
|
||
import customizeQuestionList from './customize/QuestionList'
|
||
import CustomWwwcForm from '@/views/trials/trials-panel/reading/dicoms/components/CustomWwwcForm'
|
||
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
|
||
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'
|
||
import FusionForm from './FusionForm.vue'
|
||
import colorMap from './colorMap.vue'
|
||
import RectangleROITool from './tools/RectangleROITool'
|
||
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
|
||
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
|
||
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,
|
||
CircleROITool,
|
||
AngleTool,
|
||
CobbAngleTool,
|
||
EraserTool,
|
||
MIPJumpToClickTool,
|
||
VolumeRotateTool,
|
||
synchronizers
|
||
// cursors
|
||
} = cornerstoneTools
|
||
const { createCameraPositionSynchronizer, createVOISynchronizer } = synchronizers
|
||
const newStyles = {
|
||
global: {
|
||
color: 'rgb(255, 0, 0)',
|
||
colorHighlighted: 'rgb(0, 255, 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(0, 255, 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,
|
||
PetCtViewport,
|
||
mRecisit,
|
||
recisit,
|
||
customizeQuestionList,
|
||
CustomWwwcForm,
|
||
Manuals,
|
||
Hotkeys,
|
||
WL,
|
||
Others,
|
||
ClinicalData,
|
||
FusionForm,
|
||
colorMap,
|
||
downloadDicomAndNonedicom,
|
||
uploadDicomAndNonedicom,
|
||
},
|
||
props: {
|
||
readingTool: {
|
||
type: Number,
|
||
default: 2
|
||
}
|
||
},
|
||
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: [],
|
||
criterionType: 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: false,
|
||
isShowAnnotations: true,
|
||
clinicalDataVisible: false,
|
||
isClinicalDataFullscreen: false,
|
||
cdVisitTaskId: '',
|
||
lastViewportTaskId: '',
|
||
digitPlaces: 2,
|
||
instanceInfo: {},
|
||
lastViewportTaskIds: [],
|
||
markedSeriesIds: [],
|
||
customizeStandards: [],
|
||
|
||
fusionVisible: false,
|
||
isFusion: false,
|
||
studyList: [],
|
||
volumeData: {},
|
||
fusionSerieId: {},
|
||
loadingText: null,
|
||
toolNames: ['Length', 'Bidirectional', 'RectangleROI', 'ArrowAnnotate', 'CircleROI', 'Eraser'],
|
||
// resetAnnotation: false , // 是否初始化标记 (融合时使用)
|
||
saveCustomAnnotationTimer: null,
|
||
|
||
// 上传
|
||
downloadImageVisible: false,
|
||
uploadImageVisible: false,
|
||
uploadSubjectId: null,
|
||
uploadSubjectCode: null,
|
||
uploadTrialCriterion: {},
|
||
uploadStatus: 'upload',
|
||
taskId: '',
|
||
isReadingTaskViewInOrder: null,
|
||
trialCriterion: {}
|
||
}
|
||
},
|
||
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)
|
||
},
|
||
viewportKey() {
|
||
return this.isFusion ? 'viewport-fusion' : 'viewport'
|
||
},
|
||
},
|
||
watch: {
|
||
currentReadingTaskState(){console.log(this.currentReadingTaskState,'currentReadingTaskState')},
|
||
activeTaskId: {
|
||
immediate: true,
|
||
handler(id) {
|
||
if (!id) return
|
||
// this.addHistoryAnnotations(id)
|
||
}
|
||
},
|
||
activeViewportIndex: {
|
||
immediate: true,
|
||
handler(index) {
|
||
let series = null
|
||
if (this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`] && this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0]) {
|
||
this.clipPlaying = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].playClipState
|
||
series = this.$refs[`${this.viewportKey}-${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)
|
||
}
|
||
}
|
||
},
|
||
readingTaskState: {
|
||
immediate: true,
|
||
handler(state) {
|
||
if (state === 2) {
|
||
// 设置标记锁定
|
||
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
|
||
annotations.map(annotation => {
|
||
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
|
||
})
|
||
this.setToolsPassive()
|
||
}
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
||
this.isReadingTaskViewInOrder = this.taskInfo.IsReadingTaskViewInOrder
|
||
this.criterionType = this.taskInfo.CriterionType
|
||
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
|
||
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
|
||
if (this.criterionType === 0) {
|
||
this.tools = getCustomizeStandardsTools(this.taskInfo.ReadingToolList)
|
||
const toolNames = this.tools.map(i => i.toolName)
|
||
this.customizeStandards = config.customizeStandards.filter(item => !toolNames.includes(item.toolName))
|
||
} else {
|
||
this.tools = getTools(this.criterionType)
|
||
}
|
||
|
||
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()
|
||
})
|
||
|
||
DicomEvent.$on('isCanActiveNoneDicomTool', data => {
|
||
this.open.postMessage({ type: 'isCanActiveNoneDicomTool', data: data }, window.location)
|
||
})
|
||
DicomEvent.$on('removeNoneDicomMeasureData', data => {
|
||
this.open.postMessage({ type: 'removeNoneDicomMeasureData', data: data }, window.location)
|
||
})
|
||
DicomEvent.$on('addNoneDicomMeasureData', data => {
|
||
this.open.postMessage({ type: 'addNoneDicomMeasureData', data: data }, window.location)
|
||
})
|
||
},
|
||
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) {
|
||
return new Promise(async(resolve, reject) => {
|
||
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
|
||
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}`)
|
||
} else {
|
||
// 多帧
|
||
for (let i = 0; i < nFrames; i++) {
|
||
const newImageId = `wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&frame=${i + 1}`
|
||
stack.push(newImageId)
|
||
}
|
||
}
|
||
imageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
|
||
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 = []
|
||
const keyStack = []
|
||
keyImages.forEach(instance => {
|
||
if (instance.KeyFramesList.length > 0) {
|
||
instance.KeyFramesList.map(i => {
|
||
keyStack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&frame=${i}`)
|
||
})
|
||
} else {
|
||
keyStack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
|
||
}
|
||
keyImageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
|
||
})
|
||
res1.Result[keyStudyIndex].SeriesList[keySeriesIndex].ImageIds = keyImageIds
|
||
res1.Result[keyStudyIndex].SeriesList[keySeriesIndex].Stack = keyStack
|
||
}
|
||
// 获取标注信息
|
||
let res2 = null
|
||
if (this.criterionType === 0) {
|
||
res2 = await getCustomTag({ visitTaskId: taskId })
|
||
} else {
|
||
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)
|
||
if (this.criterionType === 0) {
|
||
i.MeasureData.id = i.Id
|
||
}
|
||
annotationUIDs.push(i.MeasureData.annotationUID)
|
||
}
|
||
return i
|
||
})
|
||
// return { studyList: arr, annotations: annotations, annotationUIDs, keyImages }
|
||
resolve({ studyList: arr, annotations: annotations, annotationUIDs, keyImages })
|
||
} catch (e) {
|
||
console.log(e)
|
||
reject(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[`${this.viewportKey}-${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
|
||
|
||
let 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
|
||
}
|
||
]
|
||
let viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
|
||
const fusionViewportIds = ['viewport-fusion-0', 'viewport-fusion-1', 'viewport-fusion-2', 'viewport-fusion-3']
|
||
if (this.readingTool === 2) {
|
||
const fusionElement1 = this.$refs['viewport-fusion-0'][0].$el
|
||
const fusionElement2 = this.$refs['viewport-fusion-1'][0].$el
|
||
const fusionElement3 = this.$refs['viewport-fusion-2'][0].$el
|
||
const fusionElement4 = this.$refs['viewport-fusion-3'][0].$el
|
||
const arr = [
|
||
{
|
||
viewportId: 'viewport-fusion-0',
|
||
type: ViewportType.ORTHOGRAPHIC,
|
||
element: fusionElement1,
|
||
defaultOptions: {
|
||
orientation: Enums.OrientationAxis.AXIAL
|
||
}
|
||
},
|
||
{
|
||
viewportId: 'viewport-fusion-1',
|
||
type: ViewportType.ORTHOGRAPHIC,
|
||
element: fusionElement2,
|
||
defaultOptions: {
|
||
orientation: Enums.OrientationAxis.AXIAL,
|
||
background: [1, 1, 1]
|
||
}
|
||
},
|
||
{
|
||
viewportId: 'viewport-fusion-2',
|
||
type: ViewportType.ORTHOGRAPHIC,
|
||
element: fusionElement3,
|
||
defaultOptions: {
|
||
orientation: Enums.OrientationAxis.AXIAL
|
||
}
|
||
},
|
||
{
|
||
viewportId: 'viewport-fusion-3',
|
||
type: ViewportType.ORTHOGRAPHIC,
|
||
element: fusionElement4,
|
||
defaultOptions: {
|
||
orientation: Enums.OrientationAxis.CORONAL,
|
||
background: [1, 1, 1]
|
||
}
|
||
}
|
||
]
|
||
viewportInputArray = [...viewportInputArray, ...arr]
|
||
viewportIds = viewportIds.concat(fusionViewportIds)
|
||
}
|
||
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)
|
||
cornerstoneTools.addTool(CircleROITool)
|
||
cornerstoneTools.addTool(AngleTool)
|
||
cornerstoneTools.addTool(CobbAngleTool)
|
||
cornerstoneTools.addTool(MIPJumpToClickTool)
|
||
cornerstoneTools.addTool(VolumeRotateTool)
|
||
|
||
viewportIds.forEach((viewportId, i) => {
|
||
// const toolGroupId = `viewport-${i}`
|
||
const toolGroupId = viewportId
|
||
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)
|
||
toolGroup.addTool(ArrowAnnotateTool.toolName, {
|
||
arrowHeadStyle: 'standard',
|
||
changeTextCallback: async(data, eventData, doneChangingTextCallback) => {
|
||
return doneChangingTextCallback(data.text)
|
||
},
|
||
getTextCallback: async(doneChangingTextCallback) => {
|
||
return doneChangingTextCallback('Annotation')
|
||
}
|
||
})
|
||
toolGroup.addTool(RectangleROITool.toolName, {
|
||
cachedStats: false,
|
||
getTextLines: this.criterionType === 0 ? this.getCustomRectangleROIToolTextLines : 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.addTool(CircleROITool.toolName, {
|
||
getTextLines: this.getCircleROIToolTextLines
|
||
})
|
||
toolGroup.addTool(AngleTool.toolName, {
|
||
getTextLines: this.getAngleToolTextLines
|
||
})
|
||
toolGroup.addTool(CobbAngleTool.toolName, {
|
||
getTextLines: this.getCobbAngleToolTextLines
|
||
})
|
||
if (toolGroupId === 'viewport-fusion-3') {
|
||
toolGroup.addTool(VolumeRotateTool.toolName)
|
||
toolGroup.setToolActive(VolumeRotateTool.toolName, {
|
||
bindings: [
|
||
{
|
||
mouseButton: MouseBindings.Wheel // mouse wheel
|
||
}
|
||
]
|
||
})
|
||
toolGroup.addTool(MIPJumpToClickTool.toolName, {
|
||
targetViewportIds: fusionViewportIds
|
||
})
|
||
|
||
// Set the initial state of the tools, here we set one tool active on left click.
|
||
// This means left click will draw that tool.
|
||
toolGroup.setToolActive(MIPJumpToClickTool.toolName, {
|
||
bindings: [
|
||
{
|
||
mouseButton: MouseBindings.Primary // Left Click
|
||
}
|
||
]
|
||
})
|
||
}
|
||
toolGroup.setToolActive(StackScrollTool.toolName, {
|
||
bindings: [{ mouseButton: MouseBindings.Wheel }]
|
||
})
|
||
toolGroup.setToolActive(ZoomTool.toolName, {
|
||
bindings: [{ mouseButton: MouseBindings.Secondary }]
|
||
})
|
||
toolGroup.setToolActive(PanTool.toolName, {
|
||
bindings: [{ mouseButton: MouseBindings.Auxiliary }]
|
||
})
|
||
// 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)
|
||
toolGroup.setToolPassive(CircleROITool.toolName)
|
||
toolGroup.setToolPassive(AngleTool.toolName)
|
||
toolGroup.setToolPassive(CobbAngleTool.toolName)
|
||
} else {
|
||
toolGroup.setToolEnabled(ArrowAnnotateTool.toolName)
|
||
toolGroup.setToolEnabled(RectangleROITool.toolName)
|
||
toolGroup.setToolEnabled(PlanarFreehandROITool.toolName)
|
||
toolGroup.setToolEnabled(LengthTool.toolName)
|
||
toolGroup.setToolEnabled(BidirectionalTool.toolName)
|
||
toolGroup.setToolEnabled(CircleROITool.toolName)
|
||
toolGroup.setToolEnabled(AngleTool.toolName)
|
||
toolGroup.setToolEnabled(CobbAngleTool.toolName)
|
||
}
|
||
toolGroup.setToolPassive(EraserTool.toolName)
|
||
})
|
||
|
||
eventTarget.addEventListener('cornerstoneimageloadprogress', this.imageLoadProgress)
|
||
console.log(Events, toolsEvents)
|
||
if (this.readingTool === 2) {
|
||
this.setUpSynchronizers()
|
||
}
|
||
},
|
||
// 影像下载进度回调
|
||
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 && !Object.hasOwn(i.MeasureData, 'isDicomReading')) {
|
||
const annotation = i.MeasureData
|
||
annotation.highlighted = false
|
||
cornerstoneTools.annotation.state.addAnnotation(annotation)
|
||
if (this.visitTaskList[taskIdx].ReadingTaskState === 2) {
|
||
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
|
||
}
|
||
}
|
||
})
|
||
},
|
||
addAnnotationListeners() {
|
||
const debouncedCallback = this.debounce((evt) => {
|
||
this.criterionType === 0 ? this.customAnnotationModifiedListener(evt) : this.annotationModifiedListener(evt)
|
||
}, 100)
|
||
eventTarget.addEventListener(
|
||
toolsEvents.ANNOTATION_MODIFIED,
|
||
(evt) => {
|
||
debouncedCallback(evt)
|
||
}
|
||
)
|
||
eventTarget.addEventListener(
|
||
toolsEvents.ANNOTATION_COMPLETED,
|
||
this.criterionType === 0 ? this.customAnnotationCompletedListener : this.annotationCompletedListener
|
||
)
|
||
eventTarget.addEventListener(
|
||
toolsEvents.ANNOTATION_REMOVED,
|
||
this.criterionType === 0 ? this.customAnnotationRemovedListener : this.annotationRemovedListener
|
||
)
|
||
eventTarget.addEventListener(
|
||
toolsEvents.ANNOTATION_SELECTION_CHANGE,
|
||
this.criterionType === 0 ? this.customAnnotationSelectionChangeListener : this.annotationSelectionChangeListener
|
||
)
|
||
|
||
// 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[`${this.viewportKey}-${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.highlighted) return
|
||
if (!annotation) return
|
||
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
|
||
const series = this.$refs[`${this.viewportKey}-${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)
|
||
}
|
||
}
|
||
},
|
||
async customAnnotationCompletedListener(e) {
|
||
if (this.readingTaskState === 2) return
|
||
const { annotation } = e.detail
|
||
if (!annotation) return
|
||
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
|
||
if (i === -1) {
|
||
this.setToolsPassive()
|
||
return
|
||
}
|
||
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
|
||
const series = this.$refs[`${this.viewportKey}-${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.markedSeriesIds.push(series.Id)
|
||
const operateStateEnum = this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].operateStateEnum
|
||
const markName = await this.customPrompt(!this.isNumber(operateStateEnum))
|
||
|
||
if (markName) {
|
||
annotation.data.label = markName
|
||
if (annotation.metadata.toolName === 'ArrowAnnotate') {
|
||
annotation.data.text = markName
|
||
}
|
||
if (this.isNumber(operateStateEnum)) {
|
||
this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].bindAnnotationToQuestion(annotation)
|
||
} else {
|
||
if (this.saveCustomAnnotationTimer) {
|
||
clearTimeout(this.saveCustomAnnotationTimer)
|
||
this.saveCustomAnnotationTimer = null
|
||
}
|
||
this.saveCustomAnnotationTimer = setTimeout(()=>{this.saveCustomAnnotation(annotation)},500)
|
||
}
|
||
}
|
||
}
|
||
|
||
this.setToolsPassive()
|
||
},
|
||
validMarkName(markName) {
|
||
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
|
||
const i = annotations.findIndex(i => i.visitTaskId === this.taskInfo.VisitTaskId && i.data.label === markName)
|
||
return i > -1
|
||
},
|
||
async saveCustomAnnotation(annotation) {
|
||
try {
|
||
const measureData = Object.assign({}, annotation)
|
||
const params = {}
|
||
await this.getScreenshots({
|
||
visitTaskId: this.visitTaskId,
|
||
annotation
|
||
},async (base64Str) =>{
|
||
const pictureObj = await this.uploadScreenshots(`${Date.now()}`, base64Str)
|
||
let picturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
|
||
params.PicturePath = picturePath
|
||
params.MarkTool = measureData.markTool
|
||
params.OrderMarkName = measureData.data.label
|
||
params.VisitTaskId = this.taskInfo.VisitTaskId
|
||
params.StudyId = annotation.studyId
|
||
params.SeriesId = annotation.seriesId
|
||
params.InstanceId = annotation.instanceId
|
||
params.Id = annotation.id ? annotation.id : ''
|
||
params.MeasureData = JSON.stringify(measureData)
|
||
params.NumberOfFrames = annotation.numberOfFrames
|
||
params.MarkId = annotation.annotationUID
|
||
const res = await submitCustomTag(params)
|
||
annotation.id = res.Result
|
||
})
|
||
|
||
} catch (e) {
|
||
this.loading = false
|
||
console.log(e)
|
||
}
|
||
},
|
||
customAnnotationModifiedListener(e) {
|
||
if (this.readingTaskState === 2) return
|
||
const { annotation } = e.detail
|
||
if (!annotation.highlighted) return
|
||
if (!annotation) return
|
||
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
|
||
if (i === -1) {
|
||
this.setToolsPassive()
|
||
return
|
||
}
|
||
if (!annotation.data.label) return
|
||
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
|
||
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
|
||
const operateStateEnum = this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].operateStateEnum
|
||
const isBound = this.$refs[`ecrf_${annotation.visitTaskId}`][0].verifyAnnotationIsBound(annotation)
|
||
if (isBound || this.isNumber(operateStateEnum)) {
|
||
this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].updateAnnotationToQuestion(annotation)
|
||
} else {
|
||
if (this.saveCustomAnnotationTimer) {
|
||
clearTimeout(this.saveCustomAnnotationTimer)
|
||
this.saveCustomAnnotationTimer = null
|
||
}
|
||
this.saveCustomAnnotationTimer = setTimeout(()=>{this.saveCustomAnnotation(annotation)},1000)
|
||
}
|
||
}
|
||
this.setToolsPassive()
|
||
},
|
||
async customAnnotationRemovedListener(e) {
|
||
const { annotation } = e.detail
|
||
try {
|
||
// if ( this.resetAnnotation && this.isFusion ) return false
|
||
if (!annotation) return false
|
||
if (this.readingTaskState === 2) {
|
||
const errorMsg = { message: 'annotation Not allowed to operate' }
|
||
throw errorMsg
|
||
}
|
||
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
|
||
if (i === -1) {
|
||
// 临时标记
|
||
return
|
||
}
|
||
if (annotation.visitTaskId === this.taskInfo.VisitTaskId) {
|
||
const isBound = this.$refs[`ecrf_${annotation.visitTaskId}`][0].verifyAnnotationIsBound(annotation)
|
||
if (isBound && this.activeTool === 'Eraser') {
|
||
// '该标记已与问题进行绑定,不允许删除!'
|
||
this.$alert(this.$t('dicom3D:ReadPage:alert:MarkToQuestionNoDel'))
|
||
const errorMsg = { message: 'annotation Not allowed to operate' }
|
||
throw errorMsg
|
||
}
|
||
}
|
||
|
||
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
|
||
if (this.activeTool === 'Eraser') {
|
||
await this.$confirm(
|
||
this.$t('trials:trials-list:table:isDeleted') +
|
||
annotation.data.label +
|
||
'?'
|
||
)
|
||
if (annotation.id) {
|
||
deleteCustomTag(annotation.id)
|
||
}
|
||
}
|
||
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
|
||
if (index !== -1) {
|
||
this.markedSeriesIds.splice(index, 1)
|
||
}
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
for (let i = 0; i < this.cells.length; i++) {
|
||
const viewportId = `${this.viewportKey}-${i}`
|
||
const viewport = renderingEngine.getViewport(viewportId)
|
||
viewport.render()
|
||
}
|
||
} else {
|
||
const errorMsg = { message: 'annotation Not allowed to operate' }
|
||
throw errorMsg
|
||
}
|
||
} catch (e) {
|
||
cornerstoneTools.annotation.state.addAnnotation(annotation)
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
for (let i = 0; i < this.cells.length; i++) {
|
||
const viewportId = `${this.viewportKey}-${i}`
|
||
const viewport = renderingEngine.getViewport(viewportId)
|
||
viewport.render()
|
||
}
|
||
console.log(e)
|
||
}
|
||
},
|
||
removeAnnotation(annotation) {
|
||
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
for (let i = 0; i < this.cells.length; i++) {
|
||
const viewportId = `${this.viewportKey}-${i}`
|
||
const viewport = renderingEngine.getViewport(viewportId)
|
||
viewport.render()
|
||
}
|
||
},
|
||
customAnnotationSelectionChangeListener(e) {
|
||
if (this.readingTaskState === 2) return
|
||
const { detail } = e
|
||
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
|
||
const i = annotations.findIndex(i => i.annotationUID === detail.selection[0])
|
||
if (i > -1) {
|
||
if (annotations[i].visitTaskId !== this.taskInfo.VisitTaskId) {
|
||
return
|
||
} else {
|
||
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].bindAnnotationToQuestion(annotations[i])
|
||
}
|
||
}
|
||
},
|
||
annotationSelectionChangeListener(e) {},
|
||
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
|
||
// 获取标注信息
|
||
let res = null
|
||
if (this.criterionType === 0) {
|
||
res = await getCustomTag({ visitTaskId: visitTaskId })
|
||
} else {
|
||
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)
|
||
if (this.criterionType === 0) {
|
||
i.MeasureData.id = i.Id
|
||
}
|
||
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
|
||
// 获取标注信息
|
||
let res = null
|
||
if (this.criterionType === 0) {
|
||
res = await getCustomTag({ visitTaskId: visitTaskId })
|
||
} else {
|
||
res = await getTableAnswerRowInfoList(visitTaskId)
|
||
}
|
||
const annotations = res.Result.map(i => {
|
||
if (typeof i.MeasureData === 'string' && i.MeasureData) {
|
||
i.MeasureData = JSON.parse(i.MeasureData)
|
||
if (this.criterionType === 0) {
|
||
i.MeasureData.id = i.Id
|
||
}
|
||
}
|
||
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)
|
||
}
|
||
})
|
||
annotations.map(i => {
|
||
if (i.MeasureData) {
|
||
const annotation = i.MeasureData
|
||
cornerstoneTools.annotation.state.addAnnotation(annotation)
|
||
this.markedSeriesIds.push(annotation.seriesId)
|
||
}
|
||
})
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
for (let i = 0; i < this.cells.length; i++) {
|
||
const viewportId = `${this.viewportKey}-${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
|
||
annotations[idx].data.label = obj.name
|
||
} else {
|
||
annotations[idx].data.label = obj.name
|
||
}
|
||
annotations[idx].data.status = obj.status
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
const viewportId = `${this.viewportKey}-${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.status ? `${data.label}(${data.status})` : 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 (data.label) {
|
||
textLines.push(data.label)
|
||
}
|
||
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.status ? `${data.label}(${data.status})` : data.label)
|
||
}
|
||
|
||
return textLines
|
||
},
|
||
getCustomRectangleROIToolTextLines(data, targetId) {
|
||
const cachedVolumeStats = data.cachedStats[targetId]
|
||
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
|
||
|
||
if (mean === undefined) {
|
||
return
|
||
}
|
||
|
||
const textLines = []
|
||
if (data.label) {
|
||
textLines.push(data.label)
|
||
}
|
||
textLines.push(`Area: ${this.reRound(area, this.digitPlaces)} ${areaUnit}`)
|
||
|
||
textLines.push(`Mean: ${this.reRound(mean, this.digitPlaces)} ${modalityUnit}`)
|
||
textLines.push(`Max: ${this.reRound(max, this.digitPlaces)} ${modalityUnit}`)
|
||
textLines.push(`Std Dev: ${this.reRound(stdDev, this.digitPlaces)} ${modalityUnit}`)
|
||
|
||
return textLines
|
||
},
|
||
getBidirectionalToolTextLines(data, targetId) {
|
||
const { cachedStats, label } = data
|
||
const { length, width, unit } = cachedStats[targetId]
|
||
|
||
const textLines = []
|
||
|
||
if (label) {
|
||
textLines.push(data.status ? `${label}(${data.status})` : label)
|
||
}
|
||
if (length === undefined) {
|
||
return textLines
|
||
}
|
||
|
||
textLines.push(
|
||
`L: ${parseFloat(length).toFixed(this.digitPlaces)} ${unit || unit}`,
|
||
`S: ${parseFloat(width).toFixed(this.digitPlaces)} ${unit}`
|
||
)
|
||
|
||
return textLines
|
||
},
|
||
getCircleROIToolTextLines(data, targetId) {
|
||
const cachedVolumeStats = data.cachedStats[targetId]
|
||
const {
|
||
radius,
|
||
radiusUnit,
|
||
area,
|
||
mean,
|
||
stdDev,
|
||
max,
|
||
isEmptyArea,
|
||
areaUnit,
|
||
modalityUnit
|
||
} = cachedVolumeStats
|
||
const textLines = []
|
||
if (data.label) {
|
||
// textLines.push(data.label)
|
||
textLines.push(data.label)
|
||
}
|
||
|
||
if (radius) {
|
||
const radiusLine = isEmptyArea
|
||
? `Radius: Oblique not supported`
|
||
: `Radius: ${this.reRound(radius, this.digitPlaces)} ${radiusUnit}`
|
||
textLines.push(radiusLine)
|
||
}
|
||
|
||
if (area) {
|
||
const areaLine = isEmptyArea
|
||
? `Area: Oblique not supported`
|
||
: `Area: ${parseFloat(area)} ${areaUnit}`
|
||
textLines.push(areaLine)
|
||
}
|
||
|
||
if (mean) {
|
||
textLines.push(`Mean: ${this.reRound(mean, this.digitPlaces)} ${modalityUnit}`)
|
||
}
|
||
|
||
if (max) {
|
||
textLines.push(`Max: ${this.reRound(max, this.digitPlaces)} ${modalityUnit}`)
|
||
}
|
||
|
||
if (stdDev) {
|
||
textLines.push(`Std Dev: ${this.reRound(stdDev, this.digitPlaces)} ${modalityUnit}`)
|
||
}
|
||
|
||
return textLines
|
||
},
|
||
getAngleToolTextLines(data, targetId) {
|
||
const cachedVolumeStats = data.cachedStats[targetId]
|
||
const { label } = data
|
||
const { angle } = cachedVolumeStats
|
||
if (angle === undefined) {
|
||
return
|
||
}
|
||
|
||
if (isNaN(angle)) {
|
||
return [`${angle}`]
|
||
}
|
||
const textLines = []
|
||
if (label) {
|
||
textLines.push(label)
|
||
}
|
||
textLines.push(`${angle.toFixed(this.digitPlaces)} ${String.fromCharCode(176)}`)
|
||
return textLines
|
||
},
|
||
getCobbAngleToolTextLines(data, targetId) {
|
||
const cachedVolumeStats = data.cachedStats[targetId]
|
||
const { angle } = cachedVolumeStats
|
||
const { label } = data
|
||
|
||
if (angle === undefined) {
|
||
return
|
||
}
|
||
const textLines = []
|
||
if (label) {
|
||
textLines.push(label)
|
||
}
|
||
textLines.push(`${angle.toFixed(this.digitPlaces)} ${String.fromCharCode(176)}`)
|
||
|
||
return textLines
|
||
},
|
||
reRound(result, finalPrecision) {
|
||
if (typeof result === 'string' && result.includes(', ')) {
|
||
const numStrs = result.split(', ')
|
||
const processed = numStrs.map(str => this.processSingle(str, finalPrecision))
|
||
return processed.join(', ')
|
||
}
|
||
return this.processSingle(result, finalPrecision)
|
||
},
|
||
processSingle(str, precision) {
|
||
const num = parseFloat(str)
|
||
if (isNaN(num)) return 'NaN'
|
||
|
||
// 保留原极小值处理逻辑
|
||
if (Math.abs(num) < 0.0001) return str
|
||
const factor = 10 ** precision
|
||
return (Math.round(num * factor + 0.0000001) / factor).toFixed(precision)
|
||
},
|
||
// 激活工具
|
||
setToolActive(toolName) {
|
||
const toolGroupId = `${this.viewportKey}-${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[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
|
||
const toolGroupId = `${this.viewportKey}-${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
|
||
}
|
||
}
|
||
if (this.criterionType === 0 && this.readingTaskState < 2) {
|
||
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].resetOperateState()
|
||
}
|
||
},
|
||
setReadingToolActive(toolName) {
|
||
if (this.readingTaskState === 2) return
|
||
if (this.activeTool === toolName) return
|
||
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
|
||
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
|
||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||
if (this.activeTool) {
|
||
toolGroup.setToolPassive(this.activeTool)
|
||
}
|
||
toolGroup.setToolActive(toolName, {
|
||
bindings: [{ mouseButton: MouseBindings.Primary }]
|
||
})
|
||
this.activeTool = toolName
|
||
}
|
||
},
|
||
setReadingToolPassive() {
|
||
if (this.readingTaskState === 2) return
|
||
if (this.activeTool && this.toolNames.includes(this.activeTool)) {
|
||
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
|
||
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
|
||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||
toolGroup.setToolPassive(this.activeTool)
|
||
this.activeTool = ''
|
||
}
|
||
}
|
||
},
|
||
setMoreToolActive(toolName) {
|
||
if (this.readingTaskState === 2) return
|
||
this.setToolsPassive()
|
||
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
|
||
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
|
||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||
toolGroup.setToolActive(toolName, {
|
||
bindings: [{ mouseButton: MouseBindings.Primary }]
|
||
})
|
||
this.activeTool = toolName
|
||
}
|
||
},
|
||
setToolsPassive() {
|
||
if (!this.activeTool) return
|
||
const toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
|
||
toolGroupIds.forEach(toolGroupId => {
|
||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||
toolGroup.setToolPassive(this.activeTool)
|
||
})
|
||
this.activeTool = ''
|
||
},
|
||
setToolEnabled() {
|
||
if (!this.activeTool) return
|
||
const toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-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[`${this.viewportKey}-${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 {
|
||
if (this.criterionType === 0) {
|
||
this.tools[i].isDisabled = false
|
||
e.target.style.cursor = 'pointer'
|
||
return
|
||
}
|
||
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 = `${this.viewportKey}-${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()
|
||
viewport.setViewPresentation({ rotation: 0 })
|
||
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[`${this.viewportKey}-${this.activeViewportIndex}`][0].rotateOrientationMarkers(type)
|
||
},
|
||
// 重置视口
|
||
resetViewport() {
|
||
this.setToolsPassive()
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
|
||
const viewport = renderingEngine.getViewport(viewportId)
|
||
this.$refs[`${this.viewportKey}-${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[`${this.viewportKey}-${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.criterionType === 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[`${this.viewportKey}-${index}`][0].setSeriesInfo(i)
|
||
})
|
||
this.$nextTick(() => {
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
renderingEngine.resize(true, false)
|
||
})
|
||
},
|
||
// 更改窗宽窗位
|
||
changeVoiRange(v) {
|
||
this.setToolsPassive()
|
||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
|
||
const viewport = renderingEngine.getViewport(viewportId)
|
||
if (v.val === -1) {
|
||
// 默认值
|
||
viewport.resetProperties()
|
||
viewport.render()
|
||
} else if (v.val === 0) {
|
||
// 自定义
|
||
const wwwc = this.$refs[`${this.viewportKey}-${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 = `${this.viewportKey}-${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(`${this.viewportKey}-${this.activeViewportIndex}`)
|
||
const { invert } = viewport.getProperties()
|
||
if (this.isFusion) {
|
||
viewport.setProperties({ invert: !invert }, this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
|
||
}
|
||
viewport.setProperties({ invert: !invert })
|
||
viewport.render()
|
||
},
|
||
// 翻页
|
||
scrollPage(type) {
|
||
this.clipPlaying = false
|
||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(type)
|
||
},
|
||
// 播放
|
||
toggleClipPlay(isPlay) {
|
||
this.clipPlaying = !this.clipPlaying
|
||
this.$refs[`${this.viewportKey}-${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 => {
|
||
const 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
|
||
const 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[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
this.$refs[this.activeTaskId][0].getPreviousOrNextSeries(-1, series)
|
||
} else if (shortcutKeyEnum === 4) {
|
||
// 下一个序列
|
||
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
|
||
this.$refs[this.activeTaskId][0].getPreviousOrNextSeries(1, series)
|
||
} else if (shortcutKeyEnum === 5) {
|
||
// 上一张图像
|
||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(-1)
|
||
} else if (shortcutKeyEnum === 6) {
|
||
// 下一张图像
|
||
this.$refs[`${this.viewportKey}-${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[`${this.viewportKey}-${this.activeViewportIndex}`][0].setZoom(1)
|
||
} else if (shortcutKeyEnum === 12) {
|
||
// 缩小
|
||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setZoom(-1)
|
||
} else if (shortcutKeyEnum === 13) {
|
||
// 适应图像
|
||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(false)
|
||
} else if (shortcutKeyEnum === 14) {
|
||
// 适应窗口
|
||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(true)
|
||
} else if (shortcutKeyEnum === 15) {
|
||
// 截图
|
||
} else if (shortcutKeyEnum === 16) {
|
||
// 反色
|
||
this.toggleInvert()
|
||
} else if (shortcutKeyEnum === 17) {
|
||
// 窗宽/窗位
|
||
const wwwcIdx = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].wwwcIdx
|
||
const newWwwcIdx = wwwcIdx === this.wwwcArr.length - 1 ? 3 : wwwcIdx + 1
|
||
const wwwcTpl = this.wwwcArr[newWwwcIdx]
|
||
this.changeVoiRange(wwwcTpl)
|
||
this.$refs[`${this.viewportKey}-${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 = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-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.forceFitToWindow = !forceFitToWindow
|
||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(forceFitToWindow)
|
||
},
|
||
// 切换全屏
|
||
toggleFullScreen(e, index) {
|
||
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
|
||
this.activeViewportIndex = index
|
||
if (this.isFusion) {
|
||
const viewportIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`]
|
||
viewportIds.forEach(id => {
|
||
const index = this.$refs[id][0].series.SliceIndex
|
||
this.$refs[id][0].setFullScreen(index)
|
||
})
|
||
}
|
||
},
|
||
async toggleTask(taskInfo, taskIndex) {
|
||
if (this.isFusion) {
|
||
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
|
||
if (!confirm) return false
|
||
this.isFusion = false
|
||
this.fullScreenIndex = null
|
||
this.setToolsPassive()
|
||
}
|
||
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[`${this.viewportKey}-${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) {
|
||
if (this.isFusion) {
|
||
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
|
||
if (!confirm) return false
|
||
this.isFusion = false
|
||
this.setToolsPassive()
|
||
this.rows = 1
|
||
this.cols = 1
|
||
this.activeViewportIndex = 0
|
||
this.fullScreenIndex = null
|
||
return this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
|
||
}
|
||
if (!obj.IsDicom) {
|
||
return this.previewNoneDicoms(obj)
|
||
}
|
||
this.$refs[`${this.viewportKey}-${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
|
||
}
|
||
if (obj.TaskInfo.VisitTaskId !== this.taskInfo.VisitTaskId) {
|
||
this.setToolsPassive()
|
||
}
|
||
},
|
||
async showMultiFrame(obj) {
|
||
this.$refs[`${this.viewportKey}-${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[`${this.viewportKey}-${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.filter(i => i.IsDicom).map(s => 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].MeasureData)
|
||
}
|
||
}
|
||
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].MeasureData)
|
||
} 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 = `instanceId=${annotation.instanceId}&frame=${annotation.numberOfFrames}`
|
||
} 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
|
||
},
|
||
viewCustomAnnotationSeries(obj) {
|
||
const i = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
|
||
if (i === -1) return
|
||
const studyList = this.visitTaskList[i].StudyList
|
||
const series = this.getMarkedSeries(studyList, obj.annotation)
|
||
if (series) {
|
||
this.$refs[`${this.viewportKey}-${this.cells.length - 1}`][0].setSeriesInfo(series, true)
|
||
this.activeViewportIndex = i
|
||
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
|
||
}
|
||
},
|
||
async getCustomScreenshots(obj, callback) {
|
||
const i = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
|
||
if (i === -1) return
|
||
const studyList = this.visitTaskList[i].StudyList
|
||
const series = this.getMarkedSeries(studyList, obj.annotation)
|
||
if (series) {
|
||
this.$refs[`${this.viewportKey}-${this.cells.length - 1}`][0].setSeriesInfo(series, true)
|
||
this.activeViewportIndex = i
|
||
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
|
||
const divForDownloadViewport = document.querySelector(
|
||
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
|
||
)
|
||
const canvas = await html2canvas(divForDownloadViewport)
|
||
const base64Str = canvas.toDataURL('image/png', 1)
|
||
callback(base64Str)
|
||
}
|
||
},
|
||
async getScreenshots(measureData, callback) {
|
||
if (measureData) {
|
||
await this.imageLocation(measureData)
|
||
const divForDownloadViewport = document.querySelector(
|
||
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
|
||
)
|
||
const canvas = await html2canvas(divForDownloadViewport)
|
||
const base64Str = canvas.toDataURL('image/png', 1)
|
||
callback(base64Str)
|
||
} 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].MeasureData)
|
||
}
|
||
}
|
||
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[`${this.viewportKey}-${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[`${this.viewportKey}-${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[`${this.viewportKey}-${i}`][0].setSeriesInfo(currentAddSeries, true)
|
||
this.activeViewportIndex = i
|
||
this.$refs[currentAddSeries.TaskInfo.VisitTaskId][0].setSeriesActive(currentAddSeries.StudyIndex, currentAddSeries.SeriesIndex)
|
||
} else {
|
||
this.$refs[`${this.viewportKey}-${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 = `${this.viewportKey}-${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(isShowCancelButton = true) {
|
||
try {
|
||
const that = this
|
||
// 请输入标记名称
|
||
const { value } = await this.$prompt(this.$t('trials:noneDicom:message:msg1'), '', {
|
||
showClose: false,
|
||
showCancelButton: isShowCancelButton,
|
||
beforeClose: (action, instance, done) => {
|
||
if (action === 'confirm') {
|
||
const value = instance.inputValue
|
||
if (!value) {
|
||
that.$message.error(this.$t('trials:customReading:error:validMarkName1'))
|
||
} else if (this.validMarkName(value)) {
|
||
that.$message.error(this.$t('trials:customReading:error:validMarkName'))
|
||
} else {
|
||
done()
|
||
}
|
||
} else {
|
||
done()
|
||
}
|
||
}
|
||
})
|
||
return value
|
||
} catch {
|
||
return null
|
||
}
|
||
},
|
||
isNumber(value) {
|
||
return typeof value === 'number' && value !== null && !isNaN(value)
|
||
},
|
||
showPanel(e, toolName) {
|
||
if (toolName === 'layout' && this.isFusion) return false
|
||
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)
|
||
}
|
||
},
|
||
previewNoneDicoms(obj) {
|
||
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.TaskInfo.VisitTaskId)
|
||
var taskBlindName = this.visitTaskList[index].TaskBlindName
|
||
var token = getToken()
|
||
let subjectId = localStorage.getItem("subjectId")
|
||
const routeData = this.$router.resolve({
|
||
path: `/nonedicoms?subjectId=${subjectId}&trialId=${this.trialId}&visitTaskId=${obj.TaskInfo.VisitTaskId}&taskBlindName=${taskBlindName}&readingTaskState=${this.visitTaskList[index].ReadingTaskState}&TokenKey=${token}`
|
||
})
|
||
this.open = window.open(routeData.href, '_blank')
|
||
},
|
||
// 融合视口相机同步
|
||
setUpSynchronizers() {
|
||
const axialCameraPositionSynchronizer = createCameraPositionSynchronizer(
|
||
'AXIAL_CAMERA_SYNCHRONIZER_ID'
|
||
)
|
||
const ctVoiSynchronizer = createVOISynchronizer('CT_VOI_SYNCHRONIZER_ID', {
|
||
syncInvertState: false,
|
||
syncColormap: false
|
||
})
|
||
const ptVoiSynchronizer = createVOISynchronizer('PT_VOI_SYNCHRONIZER_ID', {
|
||
syncInvertState: false,
|
||
syncColormap: false
|
||
})
|
||
const fusionVoiSynchronizer = createVOISynchronizer('FUSION_VOI_SYNCHRONIZER_ID', {
|
||
syncInvertState: false,
|
||
syncColormap: false
|
||
});
|
||
[
|
||
`viewport-fusion-0`,
|
||
`viewport-fusion-1`,
|
||
`viewport-fusion-2`
|
||
].forEach((viewportId) => {
|
||
axialCameraPositionSynchronizer.add({
|
||
renderingEngineId: this.renderingEngineId,
|
||
viewportId
|
||
})
|
||
});
|
||
[
|
||
`viewport-fusion-0`
|
||
].forEach((viewportId) => {
|
||
ctVoiSynchronizer.add({
|
||
renderingEngineId: this.renderingEngineId,
|
||
viewportId
|
||
})
|
||
});
|
||
[
|
||
`viewport-fusion-3`,
|
||
`viewport-fusion-1`
|
||
].forEach((viewportId) => {
|
||
ptVoiSynchronizer.add({
|
||
renderingEngineId: this.renderingEngineId,
|
||
viewportId
|
||
})
|
||
});
|
||
[
|
||
`viewport-fusion-2`
|
||
].forEach((viewportId) => {
|
||
fusionVoiSynchronizer.add({
|
||
renderingEngineId,
|
||
viewportId
|
||
})
|
||
ctVoiSynchronizer.addTarget({
|
||
renderingEngineId,
|
||
viewportId
|
||
})
|
||
ptVoiSynchronizer.addTarget({
|
||
renderingEngineId,
|
||
viewportId
|
||
})
|
||
})
|
||
},
|
||
setColorMap(rgbPresetName) {
|
||
const fusionViewportIds = [`viewport-fusion-1`, `viewport-fusion-2`, `viewport-fusion-3`]
|
||
fusionViewportIds.forEach(id => {
|
||
this.$refs[id][0].setPreset(rgbPresetName)
|
||
this.$refs[id][0].renderColorBar(rgbPresetName)
|
||
this.$refs[id][0].setColorMap(rgbPresetName)
|
||
})
|
||
},
|
||
voiChange(v) {
|
||
const fusionViewportIds = [`viewport-fusion-1`, `viewport-fusion-2`, `viewport-fusion-3`]
|
||
fusionViewportIds.forEach(id => {
|
||
this.$refs[id][0].voiChange(v)
|
||
})
|
||
},
|
||
async handleFusion(data) {
|
||
try {
|
||
this.fusionVisible = false
|
||
this.isFusion = true
|
||
this.rows = 2
|
||
this.cols = 2
|
||
const { ct, pt } = data
|
||
this.loading = true
|
||
this.loadingText = this.$t('trials:lugano:message:loadVolumes')
|
||
// this.resetAnnotation = true
|
||
// console.log(cornerstoneTools.annotation.state.getAllAnnotations(),'cornerstoneTools.annotation.state')
|
||
// cornerstoneTools.annotation.state.removeAllAnnotations()
|
||
this.renderedTaskIds = []
|
||
if (this.verifyFusionData(ct, pt)) {
|
||
this.loading = false
|
||
this.loadingText = null
|
||
this.$refs[`viewport-0`][0].setSeriesInfo(ct)
|
||
this.$refs[`viewport-1`][0].setSeriesInfo(pt)
|
||
this.$refs[`viewport-2`][0].setSeriesInfo(pt)
|
||
this.$refs[`viewport-3`][0].setSeriesInfo(pt)
|
||
// this.resetAnnotation = false
|
||
return true
|
||
}
|
||
if (!this.fusionSerieId.ct || this.fusionSerieId.ct !== ct.SeriesInstanceUid) {
|
||
this.fusionSerieId.ct = ct.SeriesInstanceUid
|
||
}
|
||
if (!this.fusionSerieId.pt || this.fusionSerieId.pt !== pt.SeriesInstanceUid) {
|
||
this.fusionSerieId.pt = pt.SeriesInstanceUid
|
||
}
|
||
await this.getVolume(pt)
|
||
await this.getVolume(ct)
|
||
await this.getVolume(pt, true)
|
||
this.loading = false
|
||
this.loadingText = null
|
||
const ctData = {
|
||
data: ct,
|
||
volumeId: this.volumeData[ct.SeriesInstanceUid].volumeId
|
||
}
|
||
const ptData = {
|
||
data: pt,
|
||
volumeId: this.volumeData[pt.SeriesInstanceUid].volumeId,
|
||
volume: this.volumeData[pt.SeriesInstanceUid].volume
|
||
}
|
||
const fusionData = {
|
||
ct,
|
||
data: pt,
|
||
volumeId: this.volumeData[pt.SeriesInstanceUid].volumeId,
|
||
ctVolumeId: this.volumeData[ct.SeriesInstanceUid].volumeId,
|
||
ptVolumeId: this.volumeData[pt.SeriesInstanceUid].volumeId,
|
||
fusionVolumeId: this.volumeData[`fusion_${pt.SeriesInstanceUid}`].volumeId
|
||
}
|
||
this.$refs[`viewport-0`][0].setSeriesInfo(ct)
|
||
this.$refs[`viewport-1`][0].setSeriesInfo(pt)
|
||
this.$refs[`viewport-2`][0].setSeriesInfo(pt)
|
||
this.$refs[`viewport-3`][0].setSeriesInfo(pt)
|
||
|
||
this.$refs[`viewport-fusion-0`][0].setSeriesInfo(ctData)
|
||
this.$refs[`viewport-fusion-1`][0].setSeriesInfo(ptData, { colorMap: true })
|
||
this.$refs[`viewport-fusion-2`][0].setSeriesInfo(fusionData, { isFusion: true, colorMap: true })
|
||
this.$refs[`viewport-fusion-3`][0].setSeriesInfo(ptData, { isMip: true, colorMap: true })
|
||
// this.resetAnnotation = false
|
||
this.$nextTick(() => {
|
||
this.$refs[`colorMap`].init()
|
||
})
|
||
} catch (err) {
|
||
console.log(err)
|
||
this.loading = false
|
||
this.loadingText = null
|
||
}
|
||
},
|
||
verifyFusionData(ct, pt) {
|
||
if (this.fusionSerieId.ct === ct.SeriesInstanceUid && this.fusionSerieId.pt === pt.SeriesInstanceUid && cache.getVolume(this.volumeData[ct.SeriesInstanceUid].volumeId) && cache.getVolume(this.volumeData[pt.SeriesInstanceUid].volumeId) && cache.getVolume(this.volumeData[`fusion_${pt.SeriesInstanceUid}`].volumeId)) {
|
||
return true
|
||
}
|
||
return false
|
||
},
|
||
async getVolume(serie, isFusion = false) {
|
||
return new Promise(async res => {
|
||
let volumeId = null; let volume = null
|
||
const key = isFusion ? `fusion_${serie.SeriesInstanceUid}` : serie.SeriesInstanceUid
|
||
if (!this.volumeData[key] || !cache.getVolume(this.volumeData[key].volumeId)) {
|
||
if (serie.Modality === 'PT' && !isFusion) {
|
||
serie.ImageIds.forEach(async id => {
|
||
const imageLoadObject = cache.getImage(id)
|
||
if (imageLoadObject) {
|
||
await new Promise(res => {
|
||
cache.removeImageLoadObject(id, { force: true }) // 从缓存中删除
|
||
res()
|
||
})
|
||
}
|
||
})
|
||
}
|
||
await this.$refs[`viewport-fusion-0`][0].createImageIdsAndCacheMetaData(serie)
|
||
volumeId = `${isFusion ? 'fusion' : serie.Modality}Volume` + ':' + csUtils.uuidv4()
|
||
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: serie.ImageIds })
|
||
volume.load()
|
||
this.volumeData[key] = {}
|
||
this.volumeData[key].volumeId = volumeId
|
||
this.volumeData[key].volume = volume
|
||
} else {
|
||
volumeId = this.volumeData[key].volumeId
|
||
volume = this.volumeData[key].volume
|
||
}
|
||
|
||
res({ volumeId, volume })
|
||
})
|
||
},
|
||
upperRangeChange(upper) {
|
||
this.$refs.colorMap.upper = upper
|
||
this.$refs.colorMap.upperRangeChange(upper)
|
||
},
|
||
openFusion() {
|
||
this.fusionVisible = true
|
||
},
|
||
closeFusion() {
|
||
this.fusionVisible = false
|
||
},
|
||
getTrialCriterion() {
|
||
getCriterionReadingInfo({
|
||
TrialId: this.$route.query.trialId,
|
||
TrialReadingCriterionId: this.$route.query.TrialReadingCriterionId
|
||
})
|
||
.then((res) => {
|
||
this.trialCriterion = res.Result
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
async uploadScreenshots(fileName, file) {
|
||
try {
|
||
file = this.convertBase64ToBlob(file)
|
||
const trialId = this.$route.query.trialId
|
||
const taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
||
const subjectId = taskInfo.SubjectId
|
||
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
|
||
return { isSuccess: true, result: result }
|
||
} catch (e) {
|
||
console.log(e)
|
||
return { isSuccess: false, result: e }
|
||
}
|
||
},
|
||
convertBase64ToBlob(imageEditorBase64) {
|
||
const base64Arr = imageEditorBase64.split(',')
|
||
let imgtype = ''
|
||
let base64String = ''
|
||
if (base64Arr.length > 1) {
|
||
base64String = base64Arr[1]
|
||
imgtype = base64Arr[0].substring(
|
||
base64Arr[0].indexOf(':') + 1,
|
||
base64Arr[0].indexOf(';')
|
||
)
|
||
}
|
||
const bytes = atob(base64String)
|
||
const bytesCode = new ArrayBuffer(bytes.length)
|
||
const byteArray = new Uint8Array(bytesCode)
|
||
for (let i = 0; i < bytes.length; i++) {
|
||
byteArray[i] = bytes.charCodeAt(i)
|
||
}
|
||
return new Blob([bytesCode], { type: imgtype })
|
||
},
|
||
openUploadImage(status) {
|
||
const idx = this.visitTaskList.findIndex(i => i.IsCurrentTask)
|
||
if (idx > -1) {
|
||
this.taskId = this.visitTaskList[idx].VisitTaskId
|
||
}
|
||
this.uploadSubjectCode = localStorage.getItem("subjectCode")
|
||
this.uploadSubjectId = localStorage.getItem("subjectId")
|
||
this.uploadTrialCriterion = this.trialCriterion
|
||
this.uploadStatus = status
|
||
this[`${status}ImageVisible`] = true
|
||
},
|
||
},
|
||
beforeDestroy() {
|
||
DicomEvent.$off('isCanActiveNoneDicomTool')
|
||
DicomEvent.$off('removeNoneDicomMeasureData')
|
||
DicomEvent.$off('addNoneDicomMeasureData')
|
||
if (this.saveCustomAnnotationTimer) {
|
||
clearTimeout(this.saveCustomAnnotationTimer)
|
||
this.saveCustomAnnotationTimer = null
|
||
}
|
||
}
|
||
}
|
||
</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;
|
||
}
|
||
}
|
||
.content-wrapper {
|
||
flex: 1;
|
||
height: calc(100% - 50px);
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
.viewports-wrapper {
|
||
flex: 1;
|
||
.grid-container {
|
||
display: grid;
|
||
height: 100%;
|
||
width: 100%;
|
||
position: relative;
|
||
}
|
||
.viewports-box {
|
||
display: grid;
|
||
position: absolute;
|
||
height: 100%;
|
||
width: 100%;
|
||
top: 0;
|
||
left: 0;
|
||
bottom: 0;
|
||
right: 0;
|
||
z-index: 9;
|
||
}
|
||
.viewports-box-down{
|
||
z-index: 1;
|
||
}
|
||
.grid-cell {
|
||
border: 1px dashed #ccc;;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1;
|
||
}
|
||
.cell_active {
|
||
border-color: #fafa00!important;
|
||
}
|
||
.cell-full-screen {
|
||
grid-column: 1 / -1;
|
||
grid-row: 1 / -1;
|
||
z-index: 9;
|
||
}
|
||
}
|
||
.form-wrapper {
|
||
width: 400px;
|
||
height: 100%;
|
||
border: 1px solid #727272;
|
||
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>
|