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

4998 lines
196 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div v-loading="loading" :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" :readingTool="readingTool"
@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">
<span @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>
</span>
<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;">
<span @click.prevent="setViewportRotate(rotate.val)">
{{ rotate.label }}
</span>
</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>
<!-- MPR -->
<div class="tool-item" :title="`${$t('trials:reading:button:mpr')}`" @click.prevent="openMPRViewport()"
v-if="((criterionType === 0 && readingTool === 0) || this.readingTool === 3) && !isMPR">
<svg-icon icon-class="mpr" class="svg-icon" style="transform: rotate(180deg);" />
</div>
<!-- 退出MPR -->
<div class="tool-item" :title="`${$t('trials:reading:button:exit_mpr')}`" @click.prevent="openMPRViewport()"
v-if="((criterionType === 0 && readingTool === 0) || this.readingTool === 3) && isMPR">
<svg-icon icon-class="exit_mpr" class="svg-icon" style="transform: rotate(180deg);" />
</div>
<!-- 直方图 -->
<div class="tool-item" :title="`${$t('trials:reading:button:histogram')}`" @click.prevent="openHistogram"
v-if="this.readingTool === 3">
<svg-icon icon-class="histogram" class="svg-icon" />
</div>
<!-- 十字准星 -->
<div :class="['tool-item', activeTool === 'Crosshairs' ? 'tool-item-active' : '']" v-if="isMPR"
:title="$t('trials:reading:button:crosshairs')" @click.prevent="setToolActive('Crosshairs')">
<svg-icon icon-class="crosshairs" 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>
<!-- 更多 :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '']" -->
<div v-if="criterionType === 0" :title="$t('trials:reading:button:more')" :class="['tool-item']"
@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">
<!--v-if="readingTaskState < 2"-->
<ul style="width:140px;">
<li v-for="i in customizeStandards" :key="i.toolName" style="text-align:left;">
<span @click.prevent="setMoreToolActive(i.toolName)">
<svg-icon :icon-class="i.icon" class="svg-icon" style="margin-right: 5px;" />
{{ $t(i.i18nKey) }}
</span>
</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" :modality="fusionOverlayModality" @setColorMap="setColorMap"
@voiChange="voiChange" />
</template>
</div>
<div style="display: flex;">
<!-- 手册 -->
<div class="tool-item" :title="$t('trials:reading:button:handbooks')"
v-if="taskInfo && (taskInfo.ExistsManual || taskInfo.IsHaveKeyFile) && readingTaskState < 2"
@click.prevent="previewManuals()">
<svg-icon icon-class="constitution" class="svg-icon" />
</div>
<!-- 临床数据 -->
<div class="tool-item" :title="$t('trials:reading:button:clinicalData')"
v-if="taskInfo && taskInfo.IsExistsClinicalData" @click.prevent="previewCD(taskInfo.VisitTaskId)">
<svg-icon icon-class="documentation" class="svg-icon" />
</div>
<!-- 个性化配置 -->
<div class="tool-item" :title="$t('trials:reading:button:customCfg')" @click.prevent="previewConfig">
<svg-icon icon-class="individuation" class="svg-icon" />
</div>
</div>
</div>
<div class="content-wrapper">
<!-- viewports -->
<div class="viewports-wrapper">
<div ref="container" class="grid-container">
<div
:class="['viewports-box', isFusion || isMPR ? 'viewports-box-down' : '', fullScreenIndex !== null ? 'viewports-box-full-screen' : '']"
:style="gridStyle">
<div v-for="(v, index) in cellsMax" v-show="index < cells.length" :key="`viewport-${index}`"
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)">
<VolumeViewport :ref="`viewport-${index}`" :data-viewport-uid="`viewport-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-${index}`" :viewport-index="index"
:histogramVisible="histogramVisible" :actionConfiguration="actionConfiguration"
:SegmentConfig="SegmentConfig" :segmentationId.sync="segId" :segmentIndex.sync="segIndex"
:curSegSeries.sync="curSegSeries" @activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup"
@resetViewport="resetViewport" @resetHistogram="resetHistogram" v-if="readingTool === 3"
v-resize="(e) => handleSizeChange(e, `viewport-${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" @contentMouseup="contentMouseup" v-else />
</div>
</div>
<div v-if="criterionType === 0 && readingTool === 0 || readingTool === 3"
:class="['viewports-box', !isMPR ? 'viewports-box-down' : '', fullScreenIndex !== null ? 'viewports-box-full-screen' : '']"
:style="gridStyleMPR">
<div v-for="(v, index) in 3" :key="`viewport-MPR-${index}`" v-show="index < cells.length"
:style="cellStyle"
:class="['grid-cell', index === 0 ? 'grid-cell-3' : '', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)">
<MPRViewport :ref="`viewport-MPR-${index}`" :data-viewport-uid="`viewport-MPR-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-MPR-${index}`"
:viewport-index="index" :histogramVisible="histogramVisible"
:actionConfiguration="actionConfiguration" :SegmentConfig="SegmentConfig"
:segmentationId.sync="segId" :segmentIndex.sync="segIndex" :curSegSeries.sync="curSegSeries"
:MPRInfo="MPRInfo" @activeViewport="activeViewport" @setMPRInfo="setMPRInfo"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup"
@resetHistogram="resetHistogram" v-resize="(e) => handleSizeChange(e, `viewport-MPR-${index}`)" />
</div>
</div>
<div v-if="readingTool === 2"
:class="['viewports-box', !isFusion ? 'viewports-box-down' : '', fullScreenIndex !== null ? 'viewports-box-full-screen' : '']"
:style="gridStyle">
<div v-for="(v, index) in cellsMax" v-show="index < cells.length" :key="`viewport-fusion-${index}`"
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)"
@mouseenter="hoverFusionViewport(index)" @mouseleave="hoverFusionViewport(-1)">
<PetCtViewport :ref="`viewport-fusion-${index}`" :data-viewport-uid="`viewport-fusion-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-fusion-${index}`"
:viewport-index="index" :active-tool="activeTool" @activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @upperRangeChange="upperRangeChange"
@contentMouseup="contentMouseup" />
</div>
</div>
<div v-if="readingTool === 2" class="fusion-hidden-viewports">
<div ref="viewport-fusion-hidden-sag" class="fusion-hidden-viewport" />
</div>
</div>
</div>
<!-- 表单 -->
<div class="form-wrapper">
<el-tabs v-model="formWrapperActiveName" v-if="readingTool === 3" @tab-click="handleClick">
<el-tab-pane :label="$t('trials:reading:dicom3D:tabs:segment')" name="segment">
<Segmentations ref="Segmentations" :visitInfo="taskInfo" :isMPR="isMPR"
:volumeToolGroupId="volumeToolGroupId" :viewportKey="viewportKey" :global-loading.sync="loading"
:trialCriterion="trialCriterion" :loadingText.sync="loadingText"
:rendering-engine-id="renderingEngineId" :SegmentConfig="SegmentConfig" :segId.sync="segId"
:segIndex.sync="segIndex" :curSegSeries.sync="curSegSeries" :activeViewportIndex="activeViewportIndex"
:activeTool.sync="activeTool" :actionConfiguration="actionConfiguration"
:histogramVisible="histogramVisible" @setToolsPassive="setToolsPassive"
@resetQuestion="resetQuestion" />
</el-tab-pane>
<el-tab-pane :label="$t('trials:reading:dicom3D:tabs:ecrf')" name="ecrf">
<div v-for="s in visitTaskList" v-show="lastViewportTaskId === s.VisitTaskId" :key="s.VisitTaskId"
style="height: 100%;">
<customize-question-list
v-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" @handleReadingChart="handleReadingChart"
@openSegmentForm="openSegmentForm" />
</div>
</el-tab-pane>
</el-tabs>
<template v-else>
<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" @handleReadingChart="handleReadingChart"
@setReadingToolPassive="setReadingToolPassive" />
<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" @handleReadingChart="handleReadingChart"
@setReadingToolPassive="setReadingToolPassive" />
<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" @handleReadingChart="handleReadingChart" />
</div>
</template>
</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="manualsDialog.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" :justKeyDoc="manualsDialog.justKeyDoc" />
<div slot="footer" style="text-align:right;" v-if="openManuals && !ManualsClose">
<!-- 确认 -->
<el-button type="primary" size="mini" @click="handleSubmitKeyDoc">
{{ $t('common:button:confirm') }}</el-button>
</div>
</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'" :imageToolType="1" />
</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>
<el-dialog :visible.sync="segmentVisible" :close-on-click-modal="false" :title="$t('trials:segment:title:bind')"
width="550px">
<SegmentForm ref="SegmentForm" v-if="segmentVisible" :visible.sync="segmentVisible" :visitInfo="segmentVisitInfo"
@handleSegmentSave="handleSegmentSave" />
</el-dialog>
<!--直方图-->
<histogram ref="histogram" v-if="readingTool === 3" :visible.sync="histogramVisible" :activeTool.sync="activeTool"
:viewportKey="viewportKey" :rendering-engine-id="renderingEngineId" :activeViewportIndex="activeViewportIndex" />
<!--分割可视化窗口-->
<!-- <SurfaceViewport ref="surfaceViewport" viewportId="surfaceViewport" v-if="readingTool === 3"
:visible.sync="surfaceVisible" :renderingEngineId="renderingEngineId" :visitInfo="taskInfo" />
<ContourViewport ref="contourViewport" viewportId="contourViewport" v-if="readingTool === 3"
:renderingEngineId="renderingEngineId" :visitInfo="taskInfo" /> -->
<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" />
<readingChart ref="readingChart" />
</div>
</template>
<script>
import { getRelatedVisitTask, getReadingVisitStudyList, getTableAnswerRowInfoList, deleteCustomTag, getCriterionReadingInfo, setReadKeyFile } from '@/api/trials'
import { getDoctorShortcutKey, getUserWLTemplateList } from '@/api/user'
import { getCustomTag, submitCustomTag } from '@/api/reading'
import { getToken } from '@/utils/auth'
import {
RenderingEngine,
Enums,
// imageLoader,
// CONSTANTS,
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 MPRViewport from './MPRViewport'
import VolumeViewport from './VolumeViewport'
import Segmentations from './Segmentations'
import histogram from "./histogram"
// import SurfaceViewport from "./SurfaceViewport"
// import ContourViewport from "./ContourViewport"
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 SegmentForm from './SegmentForm.vue'
import colorMap from './colorMap.vue'
import RectangleROITool from './tools/RectangleROITool'
import ScaleOverlayTool from './tools/ScaleOverlayTool'
import SegmentBidirectionalTool from './tools/SegmentBidirectionalTool'
import FusionJumpToPointTool from './tools/FusionJumpToPointTool'
import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache'
import FixedRadiusCircleROITool from './tools/FixedRadiusCircleROITool'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
import { getNetWorkSpeed, setNetWorkSpeedSizeAll, workSpeedclose } from "@/utils"
import readingChart from '@/components/readingChart'
import SystemInfo from "@/utils/systemInfo";
import md5 from 'js-md5'
const { visibility } = annotation
const { ViewportType, Events } = Enums
const renderingEngineId = 'myRenderingEngine'
const {
ToolGroupManager,
Enums: csToolsEnums,
StackScrollTool,
TrackballRotateTool,
PlanarFreehandContourSegmentationTool,
SplineContourSegmentationTool,
// ScaleOverlayTool,
PanTool,
ZoomTool,
WindowLevelTool,
WindowLevelRegionTool,
PlanarRotateTool,
LengthTool,
BidirectionalTool,
ArrowAnnotateTool,
// RectangleROITool,
PlanarFreehandROITool,
CircleROITool,
AngleTool,
CobbAngleTool,
EraserTool,
VolumeRotateTool,
CrosshairsTool,
EllipticalROITool,
synchronizers,
LabelMapEditWithContourTool,
BrushTool,
// SegmentBidirectionalTool,
utilities: CStUtils,
// cursors
} = cornerstoneTools
const { createCameraPositionSynchronizer, createVOISynchronizer, createSlabThicknessSynchronizer } = 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,
MPRViewport,
VolumeViewport,
Segmentations,
histogram,
// SurfaceViewport,
// ContourViewport,
mRecisit,
recisit,
customizeQuestionList,
CustomWwwcForm,
Manuals,
Hotkeys,
WL,
Others,
ClinicalData,
FusionForm,
SegmentForm,
colorMap,
downloadDicomAndNonedicom,
uploadDicomAndNonedicom,
readingChart
},
props: {
readingTool: {
type: Number,
default: 2
}
},
data() {
return {
loading: false,
trialId: '',
visitTaskList: [],
selectArr: [],
taskInfo: {},
activeTaskId: null,
activeTaskIndex: -1,
activeStudyIndex: -1,
activeSeriesIndex: -1,
currentVisitInfo: {},
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 }
],
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, justKeyDoc: false },
hotKeyList: [],
forceFitToWindow: false,
isShowAnnotations: true,
clinicalDataVisible: false,
isClinicalDataFullscreen: false,
cdVisitTaskId: '',
lastViewportTaskId: '',
digitPlaces: 2,
instanceInfo: {},
lastViewportTaskIds: [],
markedSeriesIds: [],
customizeStandards: [],
fusionVisible: false,
isFusion: false,
studyList: [],
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: {},
curOperation: {
type: '',
annotation: null
},
ManualsClose: false,
isMPR: false,
volumeToolGroupId: "share-viewport-volume",
fusionToolGroupId: "share-viewport-fusion",
MPRInfo: {
AXIAL: {
imageNum: 0
},
CORONAL: {
imageNum: 0
},
SAGITTAL: {
imageNum: 0
},
},
segmentVisible: false,
segmentVisitInfo: {},
formWrapperActiveName: 'segment',
actionConfiguration: {
contourBidirectional: {
method: CStUtils.segmentation.segmentContourAction,
bindings: [
{
key: 'c',
},
],
data: {
segmentData: new Map(),
segmentationId: null
},
},
},
SegmentConfig: {
renderOutline: true,
renderFill: true,
fillAlpha: 0.5,
outlineWidth: 1,
InactiveSegmentations: {
show: true,
fillAlpha: 0.3,
}
},
segId: null,
segIndex: null,
curSegSeries: {},
fusionOverlayModality: null,
fusionOverlayDefaultUpper: null,
fusionOverlayDefaultRange: null,
fusionCrosshairStyle: {
lineWidth: 2,
lineLength: 20,
centerHoleSize: 20,
},
lastUpper: null,
hasFusionUpperInitialized: false,
timer: {},
FullTimerOut: null,
isDelay: false,
histogramVisible: false,
// surfaceVisible: false
}
},
computed: {
gridStyle() {
return {
display: 'grid',
gridTemplateRows: `repeat(${this.rows}, 1fr)`,
gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
height: '100%',
width: '100%'
}
},
gridStyleMPR() {
return {
display: 'grid',
gridTemplateRows: `repeat(2, 1fr)`,
gridTemplateColumns: `repeat(2, 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.isMPR ? 'viewport-MPR' : this.isFusion ? 'viewport-fusion' : 'viewport'
},
openManuals() {
return !this.taskInfo.IsReadKeyFile && this.taskInfo.IsHaveKeyFile
},
},
watch: {
openManuals: {
handler() {
if (this.openManuals) this.previewManuals(true)
},
immediate: true
},
// 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 && series.TaskInfo) {
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()
}
}
},
// histogramVisible: {
// handler() {
// if (this.readingTool !== 3) return false
// this.setToolsPassive()
// let viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3', this.volumeToolGroupId]
// // if (this.isMPR) {
// // viewportIds = [this.volumeToolGroupId]
// // }
// viewportIds.forEach(id => {
// const toolGroup = ToolGroupManager.getToolGroup(id)
// if (this.histogramVisible) {
// toolGroup.setToolEnabled(StackScrollTool.toolName)
// } else {
// toolGroup.setToolActive(StackScrollTool.toolName, {
// bindings: [{ mouseButton: MouseBindings.Wheel }]
// })
// let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.toolName.includes('histogram_'));
// annotations.forEach(item => {
// annotation.state.removeAnnotation(item.annotationUID)
// })
// for (let i = 0; i < this.cells.length; i++) {
// const viewportId = `${this.viewportKey}-${i}`
// let renderingEngine = getRenderingEngine(renderingEngineId)
// const viewport = renderingEngine.getViewport(viewportId)
// viewport.render()
// }
// }
// })
// }
// },
},
mounted() {
this.taskInfo = JSON.parse(sessionStorage.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))
console.log(this.customizeStandards, 'this.customizeStandards')
} 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()
this.getHotKeys()
})
this.getTrialCriterion()
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)
})
DicomEvent.$on('activeSeries', (series) => {
this.renderedTaskIds = []
})
document.addEventListener("click", this.foo);
this.getSystemInfoReading();
},
methods: {
resetHistogram() {
if (!this.histogramVisible) return false
if (this.timer['histogram']) {
clearTimeout(this.timer['histogram'])
this.timer['histogram'] = null
}
this.timer['histogram'] = setTimeout(() => {
if (this.$refs.histogram && this.histogramVisible) {
this.$refs.histogram.init()
}
clearTimeout(this.timer['histogram'])
this.timer['histogram'] = null
}, 500)
},
showSurface(obj) {
// this.surfaceVisible = true
// this.$refs.contourViewport.setSeriesInfo(obj)
// this.$refs.surfaceViewport.setSeriesInfo(obj)
},
async openHistogram() {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
let viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
let imageIds = viewport.getImageIds(this.$refs[viewportId][0].volumeId)
let imageId = imageIds[0]
const imagePixelModule = metaData.get('imagePixelModule', imageId);
const photometricInterpretation = imagePixelModule?.photometricInterpretation;
console.log(photometricInterpretation, 'photometricInterpretation')
if (photometricInterpretation && photometricInterpretation !== 'MONOCHROME1' && photometricInterpretation !== 'MONOCHROME2') return this.$confirm(this.$t('trials:histogram:confirm:photometricInterpretationNotSupported'))
this.histogramVisible = true
this.setToolsPassive()
this.$refs.histogram.init()
},
handleSizeChange(e, viewportId) {
let index = this.$refs[viewportId][0].series.SliceIndex
this.resetRenderingEngine(viewportId, index)
},
resetQuestion() {
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].getQuestions(false)
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].initSegmentBinding()
},
handleSegmentSave(obj) {
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].handleSegmentSave(obj)
},
openSegmentForm(obj) {
let { visitInfo } = obj
this.segmentVisitInfo = Object.assign(visitInfo, { ...obj })
this.segmentVisible = true
this.$nextTick(() => {
this.$refs.SegmentForm.setSeries(this.$refs[`viewport-${this.activeViewportIndex}`][0].series)
this.$refs.SegmentForm.init()
})
},
handleClick(tab, event) {
this.formWrapperActiveName = tab.name
this.SegmentConfig.InactiveSegmentations.show = true
},
setMPRInfo(obj) {
let { type, key, value } = obj
this.$set(this.MPRInfo[type], key, value)
},
handleReadingChart(row) {
let { e, data } = row
let obj = Object.assign({}, data)
obj.TrialId = this.$route.query.trialId
obj.VisitTaskId = this.taskInfo.VisitTaskId
let zIndex = 9
if (obj.RowIndex) {
zIndex = 9999
}
this.$refs.readingChart.init(e, obj, zIndex)
},
foo() {
if (!this.$refs.readingChart) return false
this.$refs.readingChart.foo()
},
async handleSubmitKeyDoc() {
try {
let data = {
TrialCriterionId: this.$router.currentRoute.query.TrialReadingCriterionId,
}
let res = await setReadKeyFile(data)
if (res.IsSuccess) {
this.manualsDialog.visible = false
this.ManualsClose = true
}
} catch (err) {
console.log(err)
}
},
// 加载当前任务关联的任务信息
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) => {
// 仅对 PT/PET study 缓存临床参数,供 3D SUV 计算链路覆盖原始 DICOM 元数据
const ptClinicalData = {
PatientSex: study.PatientSex,
PatientWeight: study.PatientWeight,
RadionuclideTotalDose: study.RadionuclideTotalDose,
RadionuclideHalfLife: study.RadionuclideHalfLife,
RadiopharmaceuticalStartTime: study.RadiopharmaceuticalStartTime,
AcquisitionTime: study.AcquisitionTime
}
const isPtStudy = ['PT、CT', 'CT、PT', 'PET-CT'].includes(study.Modalities)
const hasPtClinicalData =
isPtStudy &&
(
ptClinicalData.PatientWeight !== null ||
ptClinicalData.RadionuclideTotalDose !== null ||
ptClinicalData.RadionuclideHalfLife !== null ||
ptClinicalData.RadiopharmaceuticalStartTime !== null ||
ptClinicalData.AcquisitionTime !== null
)
study.SeriesList.forEach((series, seriesIndex) => {
const imageIds = []
const stack = []
series.InstanceInfoList.forEach((instance, instanceIndex) => {
if (hasPtClinicalData && ['PT', 'PET'].includes(String(series.Modality).toUpperCase())) {
setPTClinicalDataForInstance(instance.Id, ptClinicalData)
}
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
i.MeasureData.markId = i.MarkId
}
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 (seriesList.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)
}
let viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
const fusionViewportIds = ['viewport-fusion-0', 'viewport-fusion-1', 'viewport-fusion-2', 'viewport-fusion-3']
const volumeViewportIds = ['viewport-MPR-0', 'viewport-MPR-1', 'viewport-MPR-2']
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
}
]
if (this.readingTool === 3) {
viewportInputArray = [
{
viewportId: 'viewport-0',
type: ViewportType.ORTHOGRAPHIC,
element: element1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
},
{
viewportId: 'viewport-1',
type: ViewportType.ORTHOGRAPHIC,
element: element2,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
},
{
viewportId: 'viewport-2',
type: ViewportType.ORTHOGRAPHIC,
element: element3,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
},
{
viewportId: 'viewport-3',
type: ViewportType.ORTHOGRAPHIC,
element: element4,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
}
]
// let element5 = this.$refs.surfaceViewport.$el
// let element6 = this.$refs.contourViewport.$el
// viewportInputArray.push({
// viewportId: 'surfaceViewport',
// type: ViewportType.VOLUME_3D,
// element: element5,
// defaultOptions: {
// orientation: Enums.OrientationAxis.CORONAL,
// background: [1, 1, 1]
// }
// })
// viewportInputArray.push({
// viewportId: 'contourViewport',
// type: ViewportType.ORTHOGRAPHIC,
// element: element6,
// defaultOptions: {
// orientation: Enums.OrientationAxis.AXIAL
// }
// })
// viewportIds.push('surfaceViewport')
// viewportIds.push('contourViewport')
}
if ((this.criterionType === 0 && this.readingTool === 0) || this.readingTool === 3) {
const volumeElement1 = this.$refs['viewport-MPR-0'][0].$el
const volumeElement2 = this.$refs['viewport-MPR-1'][0].$el
const volumeElement3 = this.$refs['viewport-MPR-2'][0].$el
const arr = [
{
viewportId: 'viewport-MPR-0',
type: ViewportType.ORTHOGRAPHIC,
element: volumeElement1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
},
{
viewportId: 'viewport-MPR-1',
type: ViewportType.ORTHOGRAPHIC,
element: volumeElement2,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL
}
},
{
viewportId: 'viewport-MPR-2',
type: ViewportType.ORTHOGRAPHIC,
element: volumeElement3,
defaultOptions: {
orientation: Enums.OrientationAxis.CORONAL
}
},
]
viewportInputArray = [...viewportInputArray, ...arr]
viewportIds = viewportIds.concat(volumeViewportIds)
}
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 fusionHiddenSag = this.$refs['viewport-fusion-hidden-sag']
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]
}
},
{
viewportId: 'viewport-fusion-hidden-sag',
type: ViewportType.ORTHOGRAPHIC,
element: fusionHiddenSag,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL,
background: [1, 1, 1]
}
}
]
viewportInputArray = [...viewportInputArray, ...arr]
viewportIds = viewportIds.concat(fusionViewportIds, ['viewport-fusion-hidden-sag'])
}
renderingEngine.setViewports(viewportInputArray)
this.addAnnotationListeners()
// cornerstoneTools.addTool(TrackballRotateTool)
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(FixedRadiusCircleROITool)
cornerstoneTools.addTool(EllipticalROITool)
cornerstoneTools.addTool(AngleTool)
cornerstoneTools.addTool(CobbAngleTool)
cornerstoneTools.addTool(FusionJumpToPointTool)
cornerstoneTools.addTool(VolumeRotateTool)
cornerstoneTools.addTool(CrosshairsTool)
cornerstoneTools.addTool(LabelMapEditWithContourTool)
cornerstoneTools.addTool(BrushTool)
cornerstoneTools.addTool(SegmentBidirectionalTool)
// cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool);
// cornerstoneTools.addTool(SplineContourSegmentationTool);
viewportIds.forEach((viewportId, i) => {
// const toolGroupId = `viewport-${i}`
let toolGroupId = viewportId
if (volumeViewportIds.includes(viewportId)) {
toolGroupId = this.volumeToolGroupId
} else if (viewportId.startsWith('viewport-fusion-')) {
toolGroupId = this.fusionToolGroupId
}
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) ? ToolGroupManager.getToolGroup(toolGroupId) : ToolGroupManager.createToolGroup(toolGroupId)
toolGroup.addViewport(viewportId, renderingEngineId)
if (toolGroupId.includes('surface')) {
// toolGroup.addTool(TrackballRotateTool.toolName, {
// rotateSampleDistanceFactor: 0,
// });
// toolGroup.setToolActive(TrackballRotateTool.toolName, {
// bindings: [
// {
// mouseButton: MouseBindings.Primary,
// },
// ],
// });
} else if (toolGroupId.includes('contour')) {
// toolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName);
// toolGroup.addTool(SplineContourSegmentationTool.toolName);
// toolGroup.setToolActive(PlanarFreehandContourSegmentationTool.toolName, {
// bindings: [
// {
// mouseButton: MouseBindings.Primary, // Middle Click
// },
// ],
// });
} else {
toolGroup.addTool(StackScrollTool.toolName, {
loop: true, // 启用循环滚动
})
toolGroup.addTool(ScaleOverlayTool.toolName)
toolGroup.addTool(PanTool.toolName)
toolGroup.addTool(ZoomTool.toolName)
toolGroup.addTool(BrushTool.toolName)
if (this.readingTool === 3 || toolGroupId === this.volumeToolGroupId) {
toolGroup.addToolInstance(
'histogram_RectangleROI',
RectangleROITool.toolName,
);
toolGroup.addToolInstance(
'histogram_CircleROI',
CircleROITool.toolName,
);
toolGroup.addToolInstance(
'histogram_PlanarFreehandROI',
PlanarFreehandROITool.toolName,
);
toolGroup.addToolInstance(
'CircularBrush',
BrushTool.toolName,
{
activeStrategy: 'FILL_INSIDE_CIRCLE',
preview: {
previewColors: {}
}
}
);
toolGroup.addToolInstance(
'CircularEraser',
BrushTool.toolName,
{
activeStrategy: 'ERASE_INSIDE_CIRCLE',
preview: {
previewColors: {}
}
}
)
toolGroup.addToolInstance(
'ThresholdCircle',
BrushTool.toolName,
{
activeStrategy: 'THRESHOLD_INSIDE_CIRCLE',
}
)
toolGroup.addToolInstance(
'ThresholdSphere',
BrushTool.toolName,
{
activeStrategy: 'THRESHOLD_INSIDE_SPHERE',
}
)
toolGroup.addTool(LabelMapEditWithContourTool.toolName);
toolGroup.addTool(SegmentBidirectionalTool.toolName, {
getTextLines: this.getBidirectionalToolTextLines
});
}
if (volumeViewportIds.includes(viewportId)) {
toolGroup.addTool(WindowLevelTool.toolName, {
targetViewportIds: volumeViewportIds
})
toolGroup.addTool(CrosshairsTool.toolName, {
getReferenceLineColor: this.setCrosshairsToolLineColor
});
} else if (toolGroupId === this.fusionToolGroupId) {
// toolGroup.addTool(CrosshairsTool.toolName, {
// getReferenceLineColor: this.setFusionCrosshairsToolLineColor,
// getReferenceLineSlabThicknessControlsOn: () => false,
// minimal: { enabled: true, lineLengthInPx: 10000 }
// });
} else {
toolGroup.addTool(WindowLevelTool.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,
actions: this.actionConfiguration,
getTextLines: this.getBidirectionalToolTextLines
})
toolGroup.addTool(CircleROITool.toolName, {
getTextLines: this.getCircleROIToolTextLines
})
toolGroup.addTool(EllipticalROITool.toolName, {
getTextLines: this.getEllipticalROIToolTextLines
})
toolGroup.addTool(FixedRadiusCircleROITool.toolName, {
radius: Number.isFinite(this.taskInfo.CircleRadius) ? this.taskInfo.CircleRadius : 6,
getTextLines: this.getCircleROIToolTextLines
})
toolGroup.addTool(AngleTool.toolName, {
getTextLines: this.getAngleToolTextLines
})
toolGroup.addTool(CobbAngleTool.toolName, {
getTextLines: this.getCobbAngleToolTextLines
})
if (viewportId === 'viewport-fusion-3') {
toolGroup.addTool(VolumeRotateTool.toolName)
toolGroup.addTool(FusionJumpToPointTool.toolName, {
targetViewportIds: ['viewport-fusion-0', 'viewport-fusion-1', 'viewport-fusion-2', 'viewport-fusion-3', 'viewport-fusion-hidden-sag'],
useBrightestPoint: true,
jumpToTargetViewports: true,
dispatchEventName: 'fusion-mip-point-selected',
getReferenceLineColor: this.setFusionCrosshairsToolLineColor,
style: this.fusionCrosshairStyle,
referenceLinesCenterGapRadius: this.fusionCrosshairStyle.centerHoleSize,
minimal: {
enabled: true,
lineLengthInPx: this.fusionCrosshairStyle.lineLength,
},
mipViewportIds: ['viewport-fusion-3'],
})
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Wheel,
},
]
})
}
toolGroup.setToolConfiguration(
ScaleOverlayTool.toolName,
{
scaleLocation: 'bottom'
},
true // overwrite
)
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.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(EllipticalROITool.toolName)
toolGroup.setToolPassive(FixedRadiusCircleROITool.toolName)
toolGroup.setToolPassive(AngleTool.toolName)
toolGroup.setToolPassive(CobbAngleTool.toolName)
if (this.readingTool === 3) {
toolGroup.setToolPassive(LabelMapEditWithContourTool.toolName)
toolGroup.setToolPassive(SegmentBidirectionalTool.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(EllipticalROITool.toolName)
toolGroup.setToolEnabled(FixedRadiusCircleROITool.toolName)
toolGroup.setToolEnabled(AngleTool.toolName)
toolGroup.setToolEnabled(CobbAngleTool.toolName)
if (this.readingTool === 3) {
toolGroup.setToolEnabled(LabelMapEditWithContourTool.toolName)
toolGroup.setToolEnabled(SegmentBidirectionalTool.toolName);
}
}
toolGroup.setToolPassive(EraserTool.toolName)
}
})
eventTarget.addEventListener('cornerstoneimageloadprogress', this.imageLoadProgress)
// console.log(Events, toolsEvents)
if (this.readingTool === 2) {
this.setUpSynchronizers()
}
if ((this.criterionType === 0 && this.readingTool === 0) || this.readingTool === 3) {
this.setUpSynchronizersMPR()
}
renderingEngine.render();
},
// 影像下载进度回调
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]
if (series && series.length > 0) {
let file = series.find(item => item.ImageId === imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSizeAll(percentComplete, detail.total, imageId)
}
}
if (this.readingTool === 3) {
getNetWorkSpeed()
setNetWorkSpeedSizeAll(percentComplete, detail.total, imageId)
}
if (percentComplete === 100) {
workSpeedclose()
}
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 ? series.TaskInfo.VisitTaskId : null
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
if ((this.readingTool === 3 && annotation.seriesId === series.Id) || this.readingTool < 3) {
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')
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) 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')
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
if (!annotation.highlighted) 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) {
const { annotation } = e.detail
try {
if (!annotation) return
if (annotation.metadata.toolName === CrosshairsTool.toolName) return
if (this.readingTaskState === 2 && !annotation.data.label) return false
if (this.readingTaskState === 2) {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
} 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)
}
},
async customAnnotationCompletedListener(e) {
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
if (annotation.metadata.toolName !== LabelMapEditWithContourTool.toolName) this.setToolsPassive()
return
}
if (annotation.metadata.segmentationId) 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
console.log(annotation, 'annotation')
const params = this.getInstanceInfo(referencedImageId)
annotation.visitTaskId = series.TaskInfo.VisitTaskId
annotation.studyId = series.StudyId
annotation.seriesId = series.Id
annotation.from = this.isMPR ? 'MPR' : this.isFusion ? "Fusion" : ''
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)
}
} else {
// if (this.isNumber(operateStateEnum)) {
// this.removeAnnotation(annotation)
// }
}
}
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
annotation.markId = params.MarkId
})
} catch (e) {
this.loading = false
console.log(e)
}
},
customAnnotationModifiedListener(e) {
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
if (!annotation.highlighted) return
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
if (annotation.metadata.toolName !== LabelMapEditWithContourTool.toolName) this.setToolsPassive()
return
}
if (annotation.metadata.segmentationId) 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 {
this.curOperation.type = 'Modified'
this.curOperation.annotation = annotation
}
}
this.setToolsPassive()
},
async customAnnotationRemovedListener(e) {
const { annotation } = e.detail
try {
// if ( this.resetAnnotation && this.isFusion ) return false
if (!annotation) return false
if (annotation.metadata.toolName === CrosshairsTool.toolName) return false
if (this.readingTaskState === 2 && !annotation.data.label) return false
if (this.readingTaskState === 2) {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
if (this.activeTool !== 'Eraser') return false
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
if (annotation.metadata.toolName === SegmentBidirectionalTool.toolName) {
this.setToolsPassive()
const errorMsg = { message: 'SegmentBidirectionalTool Not remove' }
throw errorMsg
}
// 临时标记
return
}
if (annotation.metadata.segmentationId) 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.markId) {
let res = await deleteCustomTag(annotation.markId)
if (!res.IsSuccess) throw ''
} else if (annotation.id) {
let res = await deleteCustomTag(annotation.id)
if (!res.IsSuccess) throw ''
}
}
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)
}
},
contentMouseup(e) {
console.log('contentMouseup')
if (this.$refs.Segmentations) {
this.$refs.Segmentations.contentMouseup()
}
if (this.curOperation.type === 'Modified') {
let annotation = this.curOperation.annotation
this.saveCustomAnnotation(annotation)
}
this.curOperation = {
type: '',
annotation: null
}
},
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
i.MeasureData.markId = i.MarkId
}
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
i.MeasureData.markId = i.MarkId
}
}
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()
},
setCrosshairsToolLineColor(viewportId) {
let colors = [
'#ffd10a',
'#b6d634',
'#fb628b',
]
let index = viewportId.split("-").pop()
return colors[colors.length - 1 - Number(index)] || colors[0]
},
setFusionCrosshairsToolLineColor(viewportId) {
let colors = [
'#fb628b',
'#fb628b',
'#fb628b',
'#ffd10a',
'#b6d634',
]
if (viewportId === 'viewport-fusion-hidden-sag') {
return colors[colors.length - 1]
} else {
let index = viewportId.split("-").pop()
return colors[Number(index)] || colors[0]
}
},
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).toFixed(2)} ${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
},
getEllipticalROIToolTextLines(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).toFixed(2)} ${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)
},
getActiveToolGroupId() {
if (this.isMPR) return this.volumeToolGroupId
if (this.isFusion) return this.fusionToolGroupId
return `${this.viewportKey}-${this.activeViewportIndex}`
},
getCurrentToolGroupIds() {
if (this.isMPR) return [this.volumeToolGroupId]
if (this.isFusion) return [this.fusionToolGroupId]
return [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
},
setFusionMipJumpEnabled(enabled) {
if (!this.isFusion) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
if (!toolGroup || !toolGroup.hasTool(FusionJumpToPointTool.toolName)) return
if (enabled) {
toolGroup.setToolActive(FusionJumpToPointTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.dispatchFusionCenterPoint()
} else {
toolGroup.setToolDisabled(FusionJumpToPointTool.toolName)
}
},
dispatchFusionCenterPoint() {
const renderingEngine = getRenderingEngine(renderingEngineId)
if (!renderingEngine) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
const instance = toolGroup?.getToolInstance?.(FusionJumpToPointTool.toolName)
if (!instance?.setPoint) return
const candidates = ['viewport-fusion-2', 'viewport-fusion-1', 'viewport-fusion-0']
for (const viewportId of candidates) {
const viewport = renderingEngine.getViewport(viewportId)
if (!viewport?.canvasToWorld || !viewport?.element) continue
const width = viewport.element.clientWidth
const height = viewport.element.clientHeight
let worldPoint = null
if (width && height) {
worldPoint = viewport.canvasToWorld([width / 2, height / 2])
}
if ((!worldPoint || worldPoint.length < 3) && viewport.getCamera) {
worldPoint = viewport.getCamera()?.focalPoint
}
if (!worldPoint || worldPoint.length < 3) continue
instance.setPoint(worldPoint, viewportId, renderingEngine.id, {
jumpToTargetViewports: true,
dispatchEvent: false,
})
return
}
},
setFusionMipRotateEnabled(enabled) {
if (!this.isFusion) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
if (!toolGroup || !toolGroup.hasTool(VolumeRotateTool.toolName)) return
if (enabled) {
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
} else {
toolGroup.setToolDisabled(VolumeRotateTool.toolName)
}
},
// 激活工具
setToolActive(toolName) {
if (this.histogramVisible) return false
if (this.isFusion && toolName === CrosshairsTool.toolName) return false
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === toolName) {
if (toolName === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
this.setFusionMipJumpEnabled(true)
// this.setFusionMipRotateEnabled(true)
} else {
toolGroup.setToolPassive(this.activeTool)
}
this.activeTool = ''
} else {
if (this.activeTool) {
if (this.activeTool === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
this.setFusionMipJumpEnabled(true)
// this.setFusionMipRotateEnabled(true)
} else {
toolGroup.setToolPassive(this.activeTool)
}
}
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
if (toolName === CrosshairsTool.toolName) {
if (this.isFusion) {
// const instance = toolGroup.getToolInstance?.(CrosshairsTool.toolName)
// if (instance && !instance.__fusionSameForPatched) {
// instance.__fusionSameForPatched = true
// const original = instance._checkIfViewportsRenderingSameScene?.bind(instance)
// instance._checkIfViewportsRenderingSameScene = (viewport, otherViewport) => {
// try {
// const a = viewport?.getFrameOfReferenceUID?.()
// const b = otherViewport?.getFrameOfReferenceUID?.()
// if (a && b && a === b) return true
// } catch (e) { }
// return original ? original(viewport, otherViewport) : true
// }
// }
}
this.setFusionMipJumpEnabled(false)
// this.setFusionMipRotateEnabled(false)
}
this.activeTool = toolName
}
},
hoverFusionViewport(index) {
if (!this.isFusion) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
if (!toolGroup) return
const isMip = index === 3
if (isMip) {
if (toolGroup.hasTool(StackScrollTool.toolName)) {
toolGroup.setToolDisabled(StackScrollTool.toolName)
}
if (toolGroup.hasTool(VolumeRotateTool.toolName)) {
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
}
} else {
if (toolGroup.hasTool(VolumeRotateTool.toolName)) {
toolGroup.setToolDisabled(VolumeRotateTool.toolName)
}
if (toolGroup.hasTool(StackScrollTool.toolName)) {
toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
}
}
},
// 激活标注工具
setAnnotateToolActive(toolName) {
// if (this.readingTaskState === 2) return
if (this.histogramVisible) return false
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.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === toolName) {
if (toolName === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
} else {
toolGroup.setToolPassive(this.activeTool)
}
this.activeTool = ''
} else {
if (this.activeTool) {
if (this.activeTool === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
} else {
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.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool) {
if (this.activeTool === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
} else {
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.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
} else {
toolGroup.setToolPassive(this.activeTool)
}
this.activeTool = ''
}
}
},
setMoreToolActive(toolName) {
// if (this.readingTaskState === 2) return
if (this.histogramVisible) return false
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.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
},
setToolsPassive() {
if (!this.activeTool) return
const toolGroupIds = this.getCurrentToolGroupIds()
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === CrosshairsTool.toolName) {
if (toolGroup.hasTool(this.activeTool)) {
toolGroup.setToolDisabled(this.activeTool)
}
} else {
toolGroup.setToolPassive(this.activeTool)
}
})
this.activeTool = ''
},
setToolEnabled() {
if (!this.activeTool) return
const toolGroupIds = this.getCurrentToolGroupIds()
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
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 = false
e.target.style.cursor = 'pointer'
} else 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)
if (this.readingTool === 3 || this.isMPR) this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFilp(true)
// 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)
},
resetCrosshairsAnnotationsForViewports(viewportIds = []) {
if (!viewportIds || viewportIds.length === 0) return
const viewportIdSet = new Set(viewportIds)
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
annotations.forEach((a) => {
if (!a) return
if (a.metadata.toolName !== CrosshairsTool.toolName) return
const vpId = a.data.viewportId
if (!vpId || !viewportIdSet.has(vpId)) return
cornerstoneTools.annotation.state.removeAnnotation(a.annotationUID)
})
},
// 重置视口
async resetViewport() {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
if (this.isFusion) {
const fusionViewportIds = ['viewport-fusion-0', 'viewport-fusion-1', 'viewport-fusion-2', 'viewport-fusion-3']
const fusionAllViewportIds = [...fusionViewportIds, 'viewport-fusion-hidden-sag']
for (const id of fusionAllViewportIds) {
const viewport = renderingEngine.getViewport(id)
if (!viewport) continue
const ref = this.$refs[id]?.[0]
const index = ref?.series?.SliceIndex
if (ref.resetOrientationMarkers) ref.resetOrientationMarkers()
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true, resetRotation: true })
if (viewport.resetSlabThickness) viewport.resetSlabThickness()
viewport.resetProperties()
if (id === 'viewport-fusion-3') {
if (ref.rotateBarLeft || ref.rotateBarLeft === 0) ref.rotateBarLeft = 0
if (ref.rotateAngle || ref.rotateAngle === 0) ref.rotateAngle = 0
const ptSeries = ref?.series
if (ptSeries && ptSeries.SeriesInstanceUid) {
await ref.setSeriesInfo({ data: ptSeries }, false, { isMip: true, colorMap: false })
}
} else {
viewport.render()
}
if ((index || index === 0) && ref.setFullScreen) {
ref.setFullScreen(index)
}
}
this.resetCrosshairsAnnotationsForViewports(fusionAllViewportIds)
renderingEngine.render()
this.dispatchFusionCenterPoint()
if (this.fusionOverlayModality === 'NM' && Number.isFinite(this.fusionOverlayDefaultUpper) && Number.isFinite(this.fusionOverlayDefaultRange)) {
this.lastUpper = null
this.hasFusionUpperInitialized = false
if (this.$refs.colorMap) {
this.$refs.colorMap.range = this.fusionOverlayDefaultRange
this.$refs.colorMap.upper = this.fusionOverlayDefaultUpper
this.$refs.colorMap.upperRangeChange(this.fusionOverlayDefaultRange)
this.$refs.colorMap.changeVoi(this.fusionOverlayDefaultUpper)
}
this.voiChange(this.fusionOverlayDefaultUpper)
}
return
}
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resetOrientationMarkers()
let index = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.SliceIndex
if (this.readingTool !== 3) {
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true, resetRotation: true })
}
viewport.resetProperties()
if (this.isMPR) {
let volume = cache.getVolume(this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
const voi = metaData.get('voiLutModule', volume._imageIds[Math.ceil((volume._imageIds.length - 1) / 2)])
const lower = voi.windowCenter[0] - voi.windowWidth[0] / 2
const upper = voi.windowCenter[0] + voi.windowWidth[0] / 2 - 1
let viewportIds = ['viewport-MPR-0', 'viewport-MPR-1', 'viewport-MPR-2']
viewportIds.forEach(viewportId => {
let viewport = renderingEngine.getViewport(viewportId)
viewport.resetProperties()
viewport.setProperties({ voiRange: { upper: upper, lower: lower } })
viewport.render()
})
return this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
}
if (this.readingTool === 3 && this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.Modality === 'PT') {
viewport.setProperties({ voiRange: { upper: 5, lower: 0 } })
}
viewport.render()
// renderingEngine.render()
if (this.readingTool === 3) {
DicomEvent.$emit('isloaded', { isChange: false, viewportId })
}
if (this.readingTool === 3 || this.isMPR) {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
}
},
// 更改视图布局
async changeLayout(v) {
this.setToolsPassive()
this.fullScreenIndex = null
this.layout = v
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
let index = series.SliceIndex
const seriesArr = []
if (v === 1) {
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
series.curIndex = index
if (typeof series === 'object') {
seriesArr.push(series)
}
} else if (v === 2) {
this.rows = 1
this.cols = 2
this.activeViewportIndex = 1
series.curIndex = index
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
series.curIndex = index
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)
let index = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.SliceIndex
if (this.readingTool === 3 || this.isMPR) this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFilp(true)
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()
}
if (this.readingTool === 3 || this.isMPR)this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
},
setWindowLevelActive(e) {
this.setToolActive('WindowLevel')
this.showPanel(e)
},
// 设置窗宽窗位
setWwwc(v) {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = v.id ? v.id : `${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 || this.readingTool === 3) {
viewport.setProperties({ invert: !invert }, this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
} else {
viewport.setProperties({ invert: !invert })
}
viewport.render()
},
// 翻页
scrollPage(type) {
// if (this.histogramVisible) return false
this.clipPlaying = false
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(type)
},
// 播放
toggleClipPlay(isPlay) {
// if (this.histogramVisible) return false
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: 1 })
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)
let viewportIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
if (this.isMPR) {
viewportIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`]
}
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)
},
// 重置视口
resetRenderingEngine(viewportId = null, i) {
if (this.timer[viewportId]) {
clearInterval(this.timer[viewportId])
this.timer[viewportId] = null
}
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(viewportId)
if (!viewport) return false
if (viewport.volumeIds.size <= 0) return false
let index = null
this.timer[viewportId] = setTimeout(() => {
index = i || i === 0 ? i : this.$refs[viewportId ? viewportId : `${this.viewportKey}-${this.activeViewportIndex}`][0].series.SliceIndex
renderingEngine.resize(true, false)
renderingEngine.render()
this.$refs[viewportId ? viewportId : `${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
clearTimeout(this.timer[viewportId])
this.timer[viewportId] = null
if (this.readingTool === 3) {
DicomEvent.$emit('isloaded', { isChange: false, viewportId })
}
}, 100)
},
setDelay(time) {
if (this.FullTimerOut) return false
this.FullTimerOut = setTimeout(() => {
this.isDelay = false
clearTimeout(this.FullTimerOut)
this.FullTimerOut = null
}, time)
},
// 切换全屏
toggleFullScreen(e, index) {
if (this.isDelay && (this.readingTool === 3 || this.isMPR)) return false
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
this.activeViewportIndex = index
if (this.readingTool === 3 || this.isMPR) {
// this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series)
this.$nextTick(() => {
// this.resetRenderingEngine(`${this.viewportKey}-${index}`)
this.isDelay = true
this.setDelay(2000)
// if (this.readingTool === 3) {
// DicomEvent.$emit('isloaded', { isChange: false })
// }
})
}
if (this.isFusion) {
let 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 || this.isMPR) {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
if (!confirm) return false
this.isFusion = false
this.isMPR = false
this.fullScreenIndex = null
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
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 || this.isMPR) {
if (this.isFusion && this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].verifyAnnotationIsSave()) {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:clearnAnnotation'))
if (!confirm) return false
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].removeAllNoSaveAnnotation()
} else {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
if (!confirm) return false
}
if (this.histogramVisible) this.$refs.histogram.close()
this.isFusion = false
this.setToolsPassive()
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
this.fullScreenIndex = null
this.isMPR = false
obj.isChange = false
return this.$nextTick(() => {
DicomEvent.$emit('activeSeries', obj)
DicomEvent.$emit('changeMPR')
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
const renderingEngine = getRenderingEngine(renderingEngineId)
renderingEngine.resize(true, false)
renderingEngine.render()
// if (this.readingTool === 3) {
// DicomEvent.$emit('isloaded', { isChange: false })
// }
})
}
if (this.histogramVisible) this.$refs.histogram.close()
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)
}
}
if (this.readingTool === 3) {
this.$nextTick(() => {
DicomEvent.$emit('SegmentationLoading', `${this.viewportKey}-${this.activeViewportIndex}`)
})
}
if (this.activeTool !== CrosshairsTool.toolName) {
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, checkFrom = false) {
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
}
if ((annotation.from === 'MPR' || annotation.metadata.volumeId) && checkFrom) {
obj = Object.assign({}, seriesList[seriesIdx])
obj.SliceIndex = annotation?.metadata?.sliceIndex
obj.IsMarked = true
obj.MeasureData = annotation
}
}
}
return obj
},
async viewCustomAnnotationSeries(obj) {
const i = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
if (i === -1) return
const studyList = this.visitTaskList[i].StudyList
let series = null
let curSeriesId = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.Id
if (obj.segment) {
let study = studyList.find(item => item.StudyId === obj.segmentGroup.StudyId)
let Series = study.SeriesList.find(item => item.Id === obj.segmentGroup.SeriesId)
Series.SliceIndex = 0
series = Object.assign(Series, { segment: obj.segment })
} else {
series = this.getMarkedSeries(studyList, obj.annotation, true)
delete series.segment
}
if (series) {
if (this.isFusion && series.SeriesInstanceUid !== this.fusionSerieId.ct && series.SeriesInstanceUid !== this.fusionSerieId.pt) {
return await this.$confirm(this.$t('trials:reading:confirm:fusionPTOrCTInconsistent'))
}
this.activeViewportIndex = this.cells.length - 1
if (obj.annotation && obj.annotation.from === 'MPR') {
let viewPlaneNormal = obj.annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? 0 : i).join(',')
if (viewPlaneNormal === '0,0,-1') {
this.activeViewportIndex = 0
// series = Object.assign({ orientation: 'AXIAL' }, series)
} else if (viewPlaneNormal === '0,-1,0') {
this.activeViewportIndex = 2
// series = Object.assign({ orientation: 'CORONAL' }, series)
} else if (viewPlaneNormal === '1,0,0') {
this.activeViewportIndex = 1
// series = Object.assign({ orientation: 'SAGITTAL' }, series)
}
let res = await this.openMPRViewport(series)
if (!res) return false
} else {
if (this.isMPR) {
this.isMPR = false
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
// return this.activeSeries(series)
}
if (this.isFusion) {
this.activeViewportIndex = 0
if (series.Modality === 'PT' || series.Modality === 'NM') {
this.activeViewportIndex = 2
}
series = {
data: series,
StudyIndex: series.StudyIndex,
SeriesIndex: series.SeriesIndex,
TaskInfo: series.TaskInfo
}
}
}
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, series.segment || curSeriesId !== series.Id ? false : true)
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
let series = this.getMarkedSeries(studyList, obj.annotation, true)
if (series) {
this.activeViewportIndex = this.cells.length - 1
if (obj.annotation.from === 'MPR') {
let viewPlaneNormal = obj.annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? 0 : i).join(',')
if (viewPlaneNormal === '0,0,-1') {
this.activeViewportIndex = 0
// series = Object.assign({ orientation: 'AXIAL' }, series)
} else if (viewPlaneNormal === '0,-1,0') {
this.activeViewportIndex = 2
// series = Object.assign({ orientation: 'CORONAL' }, series)
} else if (viewPlaneNormal === '1,0,0') {
this.activeViewportIndex = 1
// series = Object.assign({ orientation: 'SAGITTAL' }, series)
}
let res = await this.openMPRViewport(series)
if (!res) {
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
setTimeout(async () => {
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)
}, 200)
return false
}
} else {
if (this.isMPR) {
this.isMPR = false
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
// return this.activeSeries(series)
}
if (this.isFusion) {
this.activeViewportIndex = 0
if (series.Modality === 'PT' || series.Modality === 'NM') {
this.activeViewportIndex = 2
}
series = {
data: series,
StudyIndex: series.StudyIndex,
SeriesIndex: series.SeriesIndex,
TaskInfo: series.TaskInfo
}
}
}
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, true)
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
setTimeout(async () => {
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)
}, 200)
}
},
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 && obj.SplitOrMergeType !== '1' && obj.SplitOrMergeType !== '3') {
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 && obj.SplitOrMergeType !== '1' && obj.SplitOrMergeType !== '3') {
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.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
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(justKeyDoc = false) {
this.manualsDialog.isFullscreen = false
this.manualsDialog.visible = true
this.manualsDialog.justKeyDoc = justKeyDoc
},
// 临床数据
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 = {}
if (imageId) {
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
// 请输入标记名称
let message = this.$t('trials:noneDicom:message:msg1')
// if (isShowCancelButton) {
// message = `<div><p>${this.$t('trials:noneDicom:message:msg1')}</p><p style='font-size:12px' class='customPromptTip'>${this.$t('trials:noneDicom:message:saveTip')}</p></div>`
// }
const { value } = await this.$prompt(message, '', {
showClose: false,
cancelButtonText: isShowCancelButton ? this.$t('trials:reading:button:temporarySave') : this.$t('common:button:cancel'),
confirmButtonText: isShowCancelButton ? this.$t('trials:reading:button:enduringSave') : this.$t('trials:reading:button:relevancy'),
// showCancelButton: isShowCancelButton,
// dangerouslyUseHTMLString: isShowCancelButton,
inputValidator: (res) => {
if (!res) {
return isShowCancelButton ? this.$t('trials:noneDicom:message:saveTip') : this.$t('trials:noneDicom:message:saveTipByRelevancy')
}
},
inputErrorMessage: this.$t('trials:noneDicom:message:saveTip'),
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
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 (err) {
console.log(err)
return null
}
},
isNumber(value) {
return typeof value === 'number' && value !== null && !isNaN(value)
},
showPanel(e, toolName) {
if (toolName === 'layout' && (this.isFusion || this.isMPR)) 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
})
})
},
// mpr调窗同步设置
setUpSynchronizersMPR() {
const MPRVoiSynchronizer = createVOISynchronizer('MPR_VOI_SYNCHRONIZER_ID', {
syncInvertState: false,
syncColormap: false
})
let viewportIds = [
`viewport-MPR-0`,
`viewport-MPR-1`,
`viewport-MPR-2`
]
viewportIds.forEach((viewportId) => {
MPRVoiSynchronizer.add({
renderingEngineId: this.renderingEngineId,
viewportId
})
});
const synchronizer = createSlabThicknessSynchronizer('SLAB_THICKNESS_SYNCHRONIZER_ID');
// Add viewports to VOI synchronizers
[
`viewport-MPR-0`,
`viewport-MPR-1`,
`viewport-MPR-2`
].forEach((viewportId) => {
synchronizer.add({
renderingEngineId: this.renderingEngineId,
viewportId,
});
});
synchronizer.setEnabled(false);
},
setColorMap(rgbPresetName) {
const fusionViewportIds = [`viewport-fusion-2`]
// 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 openMPRViewport(data = null) {
return new Promise(async (resolve, reject) => {
this.setToolsPassive()
if (this.isMPR) {
this.activeSeries(this.$refs[`viewport-MPR-0`][0].series)
resolve(false)
if (!data) return resolve(false)
let viewportSeries = this.$refs[`viewport-MPR-0`][0].series
if (data && viewportSeries.SeriesInstanceUid === data.SeriesInstanceUid) return resolve(true)
}
if (!data) {
let { imageOrientationPatient, imagePositionPatient } = this.$refs[`viewport-${this.activeViewportIndex}`][0].imageInfo
if (!imageOrientationPatient || !imagePositionPatient || imagePositionPatient.length <= 0 || imageOrientationPatient.length <= 0) return this.$confirm(this.$t('trials:reading:confirm:imageNotMPR'), this.$t('system:menu:confirm:title:warning'), {
type: 'warning'
})
}
const series = data ? data : this.$refs[`viewport-${this.activeViewportIndex}`][0].series
if (series.ImageIds.length <= 5) return this.$confirm(this.$t('trials:reading:confirm:smallNumberOfimage'), this.$t('system:menu:confirm:title:warning'), {
type: 'warning'
})
series.curIndex = series.SliceIndex
DicomEvent.$emit('changeMPR')
if (series.ImageIds.length > 500) {
let res = await this.getSystemInfo()
if (!res) return false
}
this.isMPR = true
// this.rows = 3
// this.cols = 1
this.loading = true
this.loadingText = this.$t('trials:lugano:message:loadVolumes')
if (!data) this.activeViewportIndex = 0
const visitTaskId = this.visitTaskList[this.activeTaskIndex].VisitTaskId
this.$refs[visitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
await this.getVolume(series)
this.loading = false
this.loadingText = null
delete series.orientation
delete series.isLocation
this.$refs[`viewport-MPR-0`][0].setSeriesInfo(Object.assign({ orientation: 'AXIAL', isLocation: data && this.activeViewportIndex === 0 }, series))
this.$refs[`viewport-MPR-1`][0].setSeriesInfo(Object.assign({ orientation: 'SAGITTAL', isLocation: data && this.activeViewportIndex === 1 }, series))
this.$refs[`viewport-MPR-2`][0].setSeriesInfo(Object.assign({ orientation: 'CORONAL', isLocation: data && this.activeViewportIndex === 2 }, series))
this.setToolActive('Crosshairs')
resolve(false)
})
},
async handleFusion(data) {
try {
if (!this.isFusion && this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].verifyAnnotationIsSave()) {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:clearnAnnotation'), this.$t('system:menu:confirm:title:warning'), {
type: 'warning'
})
if (!confirm) return false
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].removeAllNoSaveAnnotation()
}
const { ct, pt } = data
this.fusionOverlayModality = pt?.Modality || null
if (ct.ImageIds.length > 400) {
let res = await this.getSystemInfo()
if (!res) return false
}
this.fusionVisible = false
this.isFusion = true
this.rows = 2
this.cols = 2
this.loading = true
this.loadingText = this.$t('trials:lugano:message:loadVolumes')
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.$nextTick(() => {
this.setFusionMipJumpEnabled(true)
})
// 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
}
const ptData = {
data: pt
}
const fusionData = {
ct,
data: pt,
}
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, false, { colorMap: false })
this.$refs[`viewport-fusion-2`][0].setSeriesInfo(fusionData, false, { isFusion: true, colorMap: true })
this.$refs[`viewport-fusion-3`][0].setSeriesInfo(ptData, false, { isMip: true, colorMap: false })
await this.initFusionHiddenSagViewport(pt)
// this.resetAnnotation = false
this.$nextTick(() => {
this.$refs[`colorMap`].init()
if (this.fusionOverlayModality === 'NM') {
const imageIds = this.sortImageIdsByImagePositionPatient(pt.ImageIds)
const imageId = imageIds?.[0]
const voiLutModule = imageId ? metaData.get('voiLutModule', imageId) : null
const rawWidth = Array.isArray(voiLutModule?.windowWidth) ? voiLutModule.windowWidth[0] : voiLutModule?.windowWidth
const nmMax = Number(rawWidth)
if (Number.isFinite(nmMax) && nmMax > 0) {
const halfMax = Math.round(nmMax * 0.5)
this.fusionOverlayDefaultRange = Math.round(nmMax)
this.fusionOverlayDefaultUpper = halfMax
this.lastUpper = null
this.hasFusionUpperInitialized = false
this.$refs.colorMap.range = Math.round(nmMax)
this.$refs.colorMap.upper = halfMax
this.$refs.colorMap.upperRangeChange(Math.round(nmMax))
this.voiChange(halfMax)
}
} else {
this.fusionOverlayDefaultRange = null
this.fusionOverlayDefaultUpper = null
}
this.setFusionMipJumpEnabled(true)
})
} 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(ct.SeriesInstanceUid) && cache.getVolume(pt.SeriesInstanceUid) && cache.getVolume(`fusion_${pt.SeriesInstanceUid}`)) {
return true
}
return false
},
async initFusionHiddenSagViewport(pt) {
const ptVolumeId = pt?.SeriesInstanceUid
if (!ptVolumeId || !cache.getVolume(ptVolumeId)) return
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const sagViewport = renderingEngine?.getViewport?.('viewport-fusion-hidden-sag')
if (!sagViewport) return
await sagViewport.setVolumes([{ volumeId: ptVolumeId }])
const midIndex = Math.max(0, Math.floor((pt.ImageIds?.length || 1) / 2))
await csUtils.jumpToSlice(sagViewport.element, { imageIndex: midIndex })
sagViewport.render()
},
async getVolume(serie, isFusion = false) {
return new Promise(async res => {
let volumeId = `${isFusion ? 'fusion_' : ''}` + serie.SeriesInstanceUid;
let volume = null;
if (cache.getVolume(volumeId)) {
volume = cache.getVolume(volumeId)
} else {
let imageIds = await this.$refs[`${this.viewportKey}-0`][0].createImageIdsAndCacheMetaData(serie)
// imageIds = this.sortImageIdsByImagePositionPatient(imageIds)
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: imageIds })
volume.load()
}
res({ volumeId, volume })
})
},
sortImageIdsByImagePositionPatient(imageIds) {
if (!Array.isArray(imageIds) || imageIds.length < 2) {
return imageIds;
}
const firstPlane = metaData.get('imagePlaneModule', imageIds[0]);
if (!firstPlane?.imagePositionPatient || !firstPlane?.imageOrientationPatient) {
return imageIds;
}
const reference = firstPlane.imagePositionPatient.map((v) => Number(v));
const iop = firstPlane.imageOrientationPatient.map((v) => Number(v));
const row = [iop[0], iop[1], iop[2]];
const col = [iop[3], iop[4], iop[5]];
const normal = [
row[1] * col[2] - row[2] * col[1],
row[2] * col[0] - row[0] * col[2],
row[0] * col[1] - row[1] * col[0],
];
const pairs = [];
for (const imageId of imageIds) {
const plane = metaData.get('imagePlaneModule', imageId);
const ipp = plane?.imagePositionPatient;
if (!ipp) {
return imageIds;
}
const pos = ipp.map((v) => Number(v));
const positionVector = [
reference[0] - pos[0],
reference[1] - pos[1],
reference[2] - pos[2],
];
const distance =
positionVector[0] * normal[0] +
positionVector[1] * normal[1] +
positionVector[2] * normal[2];
pairs.push({ imageId, distance });
}
pairs.sort((a, b) => b.distance - a.distance);
return pairs.map((p) => p.imageId);
},
upperRangeChange(upper) {
if (!this.hasFusionUpperInitialized) {
if (!upper) return
this.hasFusionUpperInitialized = true
}
if (this.lastUpper === upper) return;
this.lastUpper = upper;
if (this.$refs.colorMap) {
this.$refs.colorMap.changeVoi(upper)
}
this.voiChange(upper)
},
openFusion() {
this.fusionVisible = true
},
closeFusion() {
this.fusionVisible = false
this.fusionOverlayModality = null
},
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(sessionStorage.getItem('taskInfo'))
const subjectId = taskInfo.SubjectId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${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
},
getSystemInfo() {
return new Promise(async resolve => {
const systemInfo = new SystemInfo();
const allInfo = systemInfo.getAllInfo();
let deviceMemory = allInfo.hardware.deviceMemory; // 设备内存
let { width, height } = allInfo.screen; // 分辨率
let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡
let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存
// parseFloat(deviceMemory) < 16 ||
if (width < 1920 || height < 1080 || !discrete || parseFloat(estimatedMemory) < 2) {
let res = await this.$confirm(this.$t('browser:tip:ReadingConfiguration'), this.$t('system:menu:confirm:title:warning'), {
type: 'warning'
})
resolve(res)
} else {
resolve(true)
}
})
},
async getSystemInfoReading() {
return new Promise(async resolve => {
let whitelisting = localStorage.getItem('whitelisting') ? JSON.parse(localStorage.getItem('whitelisting')) : []
let user = md5(sessionStorage.getItem('identityUserId'))
let r = whitelisting.some(item => item === user)
if (r) return resolve(true)
const systemInfo = new SystemInfo();
const allInfo = systemInfo.getAllInfo();
let deviceMemory = allInfo.hardware.deviceMemory; // 设备内存
let { width, height } = allInfo.screen; // 分辨率
// let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡
// let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存
// parseFloat(deviceMemory) < 16 ||
if (width < 1920 || height < 1080) {
let res = await this.$confirm(this.$t('browser:tip:Configuration'))
whitelisting.push(user)
localStorage.setItem('whitelisting', JSON.stringify(whitelisting))
resolve(res)
} else {
resolve(true)
}
})
},
},
beforeDestroy() {
clearPTClinicalDataCache()
DicomEvent.$off('isCanActiveNoneDicomTool')
DicomEvent.$off('removeNoneDicomMeasureData')
DicomEvent.$off('addNoneDicomMeasureData')
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
if (this.timer) {
Object.keys(this.timer).forEach(key => {
if (this.timer[key]) {
clearInterval(this.timer[key])
clearTimeout(this.timer[key])
this.timer[key] = null
}
})
this.timer = {}
}
if (this.FullTimerOut) {
clearTimeout(this.FullTimerOut)
this.FullTimerOut = null
this.isDelay = false
}
workSpeedclose(true)
}
}
</script>
<style lang="scss" scoped>
.form-wrapper {
::v-deep .el-tabs__nav-scroll {
width: 100%;
display: flex;
justify-content: center;
}
}
.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 {
span {
display: block;
padding: 5px;
}
}
}
ul li span: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;
}
.fusion-hidden-viewports {
position: absolute;
left: -100000px;
top: -100000px;
width: 512px;
height: 512px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
.fusion-hidden-viewport {
width: 512px;
height: 512px;
}
.viewports-box {
display: grid;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 9;
overflow: hidden;
}
.viewports-box-down {
z-index: 1;
opacity: 0;
pointer-events: none;
}
.viewports-box-down {
>.grid-cell {
border-color: transparent;
}
}
.viewports-box-full-screen {
>.grid-cell {
display: none;
}
>.grid-cell.cell-full-screen {
display: flex;
}
}
.grid-cell {
border: 1px dashed #ccc;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
min-width: 0;
min-height: 0;
z-index: 1;
}
.grid-cell-3 {
grid-column: 0 / 3;
grid-row: 1 / 3;
}
.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 .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>
<style lang="scss">
.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;
}
}
.manuals-full-dialog-container {
.el-dialog__body {
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header {
position: relative;
}
}
.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;
}
}
.cd-full-dialog-container {
background: #fff !important;
.el-dialog__body {
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header {
position: relative;
}
}
</style>