非DICOM工具支持长度、角度 || 非DICOM工具支持区域测量面积、周长(未完成)
continuous-integration/drone/push Build is passing Details

main
wangxiaoshuang 2025-07-01 14:29:51 +08:00
parent 95428e6f69
commit 505c18b611
7 changed files with 253 additions and 145 deletions

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751350116150" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20392" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M870.4 819.2a51.2 51.2 0 1 1 0 102.4 51.2 51.2 0 0 1 0-102.4zM512 179.2a332.8 332.8 0 1 1 0 665.6 332.8 332.8 0 0 1 0-665.6z m0 51.2a281.6 281.6 0 1 0 0 563.2 281.6 281.6 0 0 0 0-563.2zM153.6 102.4a51.2 51.2 0 1 1 0 102.4 51.2 51.2 0 0 1 0-102.4z" fill="#ADAEB8" p-id="20393"></path></svg>

After

Width:  |  Height:  |  Size: 623 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751349783728" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9505" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M838.4 279.466667zM802.133333 814.933333H279.466667c-38.4 0-70.4-32-70.4-70.4V219.733333c0-38.4 32-70.4 70.4-70.4h123.733333c38.4 0 70.4 32 70.4 70.4v339.2h347.733333c2.133333 0-2.133333 0 0 0 25.6-2.133333 51.2 21.333333 51.2 46.933334v136.533333c0 40.533333-32 72.533333-70.4 72.533333z m25.6-179.2c2.133333-29.866667-34.133333-27.733333-34.133333-27.733333H445.866667c-2.133333 0-2.133333-2.133333-4.266667-2.133333h-2.133333c-8.533333-4.266667-12.8-10.666667-12.8-21.333334v-362.666666c0-14.933333-12.8-27.733333-27.733334-27.733334h-117.333333c-14.933333 0-27.733333 12.8-27.733333 27.733334v64h115.2c12.8 0 23.466667 10.666667 23.466666 23.466666 0 12.8-10.666667 23.466667-23.466666 23.466667h-115.2v78.933333h78.933333c12.8 0 23.466667 10.666667 23.466667 23.466667 0 12.8-10.666667 23.466667-23.466667 23.466667h-78.933333v78.933333h115.2c12.8 0 23.466667 10.666667 23.466666 23.466667 0 12.8-10.666667 23.466667-23.466666 23.466666h-115.2v149.333334c0 14.933333 12.8 27.733333 27.733333 27.733333H426.666667v-85.333333c0-12.8 10.666667-25.6 23.466666-25.6 12.8 0 23.466667 10.666667 23.466667 25.6v85.333333h96v-68.266667c0-12.8 10.666667-25.6 23.466667-25.6 12.8 0 23.466667 10.666667 23.466666 25.6V768h78.933334v-85.333333c0-12.8 10.666667-25.6 23.466666-25.6 12.8 0 23.466667 10.666667 23.466667 25.6v85.333333h51.2c14.933333 0 27.733333-12.8 27.733333-27.733333l6.4-104.533334z" p-id="9506"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,7 +1,7 @@
<template> <template>
<div ref="container" style="width:100%;height:100%" class="dicom-container"> <div ref="container" style="width:100%;height:100%" class="dicom-container">
<!-- 访视阅片 --> <!-- 访视阅片 -->
<div v-if="readingCategory=== 1 && (CriterionType === 7 || ((CriterionType === 1 || CriterionType === 0) && readingVersionEnum === 1)) " class="reading-wrapper"> <div v-if="readingCategory === 1 && (CriterionType === 7 || ((CriterionType === 1 || CriterionType === 0) && readingVersionEnum === 1)) " class="reading-wrapper">
<VisitReview :reading-tool="readingTool" /> <VisitReview :reading-tool="readingTool" />
</div> </div>
<div v-else-if="(isReadingTaskViewInOrder === 1 || ((isReadingTaskViewInOrder !== 1) && isShow)) && readingCategory=== 1 && CriterionType !== 0" class="reading-wrapper"> <div v-else-if="(isReadingTaskViewInOrder === 1 || ((isReadingTaskViewInOrder !== 1) && isShow)) && readingCategory=== 1 && CriterionType !== 0" class="reading-wrapper">

View File

@ -2,7 +2,7 @@
<div v-loading="loading" class="reading-viewer-container"> <div v-loading="loading" class="reading-viewer-container">
<!-- 访视阅片 --> <!-- 访视阅片 -->
<visit-review <visit-review
v-if="taskInfo && taskInfo.ReadingCategory=== 1" v-if="taskInfo && taskInfo.ReadingCategory === 1"
/> />
<!-- 临床数据 --> <!-- 临床数据 -->
<el-dialog <el-dialog

View File

@ -17,51 +17,61 @@
</el-dropdown> </el-dropdown>
</div> </div>
<!-- 缩放 --> <!-- 缩放 -->
<div <div :class="['tool-item', activeTool === 'Zoom' ? 'tool-item-active' : '']"
:class="['tool-item', activeTool === 'Zoom' ? 'tool-item-active' : '']" :title="$t('trials:reading:button:zoom')" @click.prevent="setToolActive('Zoom')">
:title="$t('trials:reading:button:zoom')"
@click.prevent="setToolActive('Zoom')"
>
<svg-icon icon-class="magnifier" class="svg-icon" /> <svg-icon icon-class="magnifier" class="svg-icon" />
</div> </div>
<!-- 移动 --> <!-- 移动 -->
<div <div :class="['tool-item', activeTool === 'Pan' ? 'tool-item-active' : '']"
:class="['tool-item', activeTool === 'Pan' ? 'tool-item-active' : '']" :title="$t('trials:reading:button:move')" @click.prevent="setToolActive('Pan')">
:title="$t('trials:reading:button:move')"
@click.prevent="setToolActive('Pan')"
>
<svg-icon icon-class="move" class="svg-icon" /> <svg-icon icon-class="move" class="svg-icon" />
</div> </div>
<!-- 旋转 --> <!-- 旋转 -->
<div <div :class="['tool-item', activeTool === 'PlanarRotate' ? 'tool-item-active' : '']"
:class="['tool-item', activeTool === 'PlanarRotate' ? 'tool-item-active' : '']" :title="$t('trials:reading:button:rotate')" @click.prevent="setToolActive('PlanarRotate')">
:title="$t('trials:reading:button:rotate')"
@click.prevent="setToolActive('PlanarRotate')"
>
<svg-icon icon-class="rotate" class="svg-icon" /> <svg-icon icon-class="rotate" class="svg-icon" />
</div> </div>
<!--直线工具-->
<div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Length' ? 'tool-item-active' : '']"
:title="$t('trials:nondicom-show:length')" @click.prevent="setAnnotateToolActive('Length')">
<svg-icon icon-class="length" class="svg-icon" />
</div>
<!-- 箭头工具 --> <!-- 箭头工具 -->
<div <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'ArrowAnnotate' ? 'tool-item-active' : '']" :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'ArrowAnnotate' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:arrowAnnotate')" :title="$t('trials:reading:button:arrowAnnotate')" @click.prevent="setAnnotateToolActive('ArrowAnnotate')">
@click.prevent="setAnnotateToolActive('ArrowAnnotate')"
>
<svg-icon icon-class="arrow" class="svg-icon" /> <svg-icon icon-class="arrow" class="svg-icon" />
</div> </div>
<!-- 角度工具 -->
<div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Angle' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:Angle')" @click.prevent="setAnnotateToolActive('Angle')">
<svg-icon icon-class="cobb" class="svg-icon" />
</div>
<!-- 矩形工具 --> <!-- 矩形工具 -->
<div <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'RectangleROI' ? 'tool-item-active' : '']" :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'RectangleROI' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:RectangleRoi')" :title="$t('trials:dicom-show:RectangleRoi')" @click.prevent="setAnnotateToolActive('RectangleROI')">
@click.prevent="setAnnotateToolActive('RectangleROI')"
>
<svg-icon icon-class="rectangle" class="svg-icon" /> <svg-icon icon-class="rectangle" class="svg-icon" />
</div> </div>
<!-- 圆形工具 -->
<div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'CircleROI' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:CircleROI')" @click.prevent="setAnnotateToolActive('CircleROI')">
<svg-icon icon-class="oval" class="svg-icon" />
</div>
<!-- 椭圆工具 -->
<div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'EllipticalROI' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:EllipticalROI')" @click.prevent="setAnnotateToolActive('EllipticalROI')">
<svg-icon icon-class="elliptical" class="svg-icon" />
</div>
<!-- 自由曲线 --> <!-- 自由曲线 -->
<div <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'PlanarFreehandROI' ? 'tool-item-active' : '']" :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'PlanarFreehandROI' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:planarFreehandROI')" :title="$t('trials:reading:button:planarFreehandROI')"
@click.prevent="setAnnotateToolActive('PlanarFreehandROI')" @click.prevent="setAnnotateToolActive('PlanarFreehandROI')">
>
<svg-icon icon-class="polygon" class="svg-icon" /> <svg-icon icon-class="polygon" class="svg-icon" />
</div> </div>
<!-- <div <!-- <div
@ -74,17 +84,14 @@
<div <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Eraser' ? 'tool-item-active' : '']" :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Eraser' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:Eraser')" :title="$t('trials:dicom-show:Eraser')" @click.prevent="setAnnotateToolActive('Eraser')">
@click.prevent="setAnnotateToolActive('Eraser')"
>
<svg-icon icon-class="clear" class="svg-icon" /> <svg-icon icon-class="clear" class="svg-icon" />
</div> </div>
<!--比例尺-->
<div <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Length' ? 'tool-item-active' : '']" :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Lengthscale' ? 'tool-item-active' : '']"
:title="$t('trials:nondicom-show:scale')" :title="$t('trials:nondicom-show:scale')" @click.prevent="setAnnotateToolActive('Lengthscale')">
@click.prevent="setAnnotateToolActive('Length')" <svg-icon icon-class="lengthscale" class="svg-icon" />
>
<svg-icon icon-class="length" class="svg-icon" />
</div> </div>
<!-- 截图 --> <!-- 截图 -->
<!-- <div <!-- <div
@ -95,11 +102,7 @@
<svg-icon icon-class="image" class="svg-icon" /> <svg-icon icon-class="image" class="svg-icon" />
</div> --> </div> -->
<!-- 重置 --> <!-- 重置 -->
<div <div class="tool-item" :title="$t('trials:reading:button:reset')" @click.prevent="resetViewport">
class="tool-item"
:title="$t('trials:reading:button:reset')"
@click.prevent="resetViewport"
>
<svg-icon icon-class="refresh" class="svg-icon" /> <svg-icon icon-class="refresh" class="svg-icon" />
</div> </div>
</div> </div>
@ -111,112 +114,84 @@
<!-- viewports --> <!-- viewports -->
<div class="viewports-wrapper"> <div class="viewports-wrapper">
<div class="grid-container" :style="gridStyle"> <div class="grid-container" :style="gridStyle">
<div <div v-for="(v, index) in viewportInfos" v-show="index < cells.length" :key="index" :style="cellStyle"
v-for="(v, index) in viewportInfos"
v-show="index < cells.length"
:key="index"
:style="cellStyle"
:class="['grid-cell', index === activeCanvasIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']" :class="['grid-cell', index === activeCanvasIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)" @dblclick="toggleFullScreen($event, index)" @click="activeCanvas(index)"
@click="activeCanvas(index)" @mouseup="sliderMouseup($event, index)" @mousemove="sliderMousemove($event, index)"
@mouseup="sliderMouseup($event, index)" @mouseleave="sliderMouseleave($event, index)">
@mousemove="sliderMousemove($event, index)"
@mouseleave="sliderMouseleave($event, index)"
>
<div v-show="imageType.includes(v.fileType)" :ref="`canvas-${index}`" class="content"> <div v-show="imageType.includes(v.fileType)" :ref="`canvas-${index}`" class="content">
<div class="left-top-text"> <div class="left-top-text">
<div <div v-if="v.taskInfo.IsExistsClinicalData" class="cd-info"
v-if="v.taskInfo.IsExistsClinicalData" :title="$t('trials:reading:button:clinicalData')">
class="cd-info" <svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon"
:title="$t('trials:reading:button:clinicalData')" @click.stop="viewCD(v.taskInfo.VisitTaskId)" />
>
<svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon" @click.stop="viewCD(v.taskInfo.VisitTaskId)" />
</div> </div>
<h2 <h2 v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo && v.taskInfo" class="subject-info">
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo && v.taskInfo"
class="subject-info"
>
{{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }} {{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }}
</h2> </h2>
<!-- <div v-if="v.currentFileName">{{ v.currentFileName }}</div> --> <!-- <div v-if="v.currentFileName">{{ v.currentFileName }}</div> -->
</div> </div>
<div <div v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && v.taskInfo" class="top-center-tool">
v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && v.taskInfo"
class="top-center-tool"
>
<div class="toggle-visit-container"> <div class="toggle-visit-container">
<div <div class="arrw_icon"
class="arrw_icon" :style="{ cursor: v.taskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum !== 0 ? '#fff' : '#6b6b6b' }"
:style="{ cursor: v.taskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum !== 0 ? '#fff': '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, -1, v.index)" @click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, -1, v.index)"
@dblclick.stop="preventDefault($event)" @dblclick.stop="preventDefault($event)">
>
<i class="el-icon-caret-left" /> <i class="el-icon-caret-left" />
</div> </div>
<div class="arrow_text"> <div class="arrow_text">
{{ v.taskInfo.TaskBlindName }} {{ v.taskInfo.TaskBlindName }}
</div> </div>
<div <div class="arrw_icon"
class="arrw_icon" :style="{ cursor: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff' : '#6b6b6b' }"
:style="{ cursor: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff': '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, 1, v.index)" @click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, 1, v.index)"
@dblclick.stop="preventDefault($event)" @dblclick.stop="preventDefault($event)">
>
<i class="el-icon-caret-right" /> <i class="el-icon-caret-right" />
</div> </div>
</div> </div>
</div> </div>
<div :ref="`sliderBox-${index}`" class="right-slider-box" @click.stop="clickSlider($event, index)"> <div :ref="`sliderBox-${index}`" class="right-slider-box" @click.stop="clickSlider($event, index)">
<div :style="{top: v.height + '%'}" class="slider" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event, index)" /> <div :style="{ top: v.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
@mousedown.stop="sliderMousedown($event, index)" />
</div> </div>
</div> </div>
<div v-show="v.fileType === 'application/pdf' && fullScreenIndex === null " class="content flex_col"> <div v-show="v.fileType === 'application/pdf' && fullScreenIndex === null" class="content flex_col">
<div class="content-top" style="height: 50px;"> <div class="content-top" style="height: 50px;">
<div class="left-top-text"> <div class="left-top-text">
<div <div v-if="v.taskInfo.IsExistsClinicalData" class="cd-info"
v-if="v.taskInfo.IsExistsClinicalData" :title="$t('trials:reading:button:clinicalData')">
class="cd-info" <svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon"
:title="$t('trials:reading:button:clinicalData')" @click.stop="viewCD(v.taskInfo.VisitTaskId)" />
>
<svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon" @click.stop="viewCD(v.taskInfo.VisitTaskId)" />
</div> </div>
<h2 <h2 v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo && v.taskInfo" class="subject-info">
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo && v.taskInfo"
class="subject-info"
>
{{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }} {{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }}
</h2> </h2>
<!-- <div v-if="v.currentFileName">{{ v.currentFileName }}</div> --> <!-- <div v-if="v.currentFileName">{{ v.currentFileName }}</div> -->
</div> </div>
<div <div v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && v.taskInfo" class="top-center-tool">
v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && v.taskInfo"
class="top-center-tool"
>
<div class="toggle-visit-container"> <div class="toggle-visit-container">
<div <div class="arrw_icon"
class="arrw_icon" :style="{ cursor: v.taskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum !== 0 ? '#fff' : '#6b6b6b' }"
:style="{ cursor: v.taskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum !== 0 ? '#fff': '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, -1, v.index)" @click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, -1, v.index)"
@dblclick.stop="preventDefault($event)" @dblclick.stop="preventDefault($event)">
>
<i class="el-icon-caret-left" /> <i class="el-icon-caret-left" />
</div> </div>
<div class="arrow_text"> <div class="arrow_text">
{{ v.taskInfo.TaskBlindName }} {{ v.taskInfo.TaskBlindName }}
</div> </div>
<div <div class="arrw_icon"
class="arrw_icon" :style="{ cursor: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff' : '#6b6b6b' }"
:style="{ cursor: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff': '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, 1, v.index)" @click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, 1, v.index)"
@dblclick.stop="preventDefault($event)" @dblclick.stop="preventDefault($event)">
>
<i class="el-icon-caret-right" /> <i class="el-icon-caret-right" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="content-main" style="flex: 1;"> <div class="content-main" style="flex: 1;">
<iframe v-if="v.currentFilePath" :ref="`iframe-${index}`" :src="`/static/pdfjs/web/viewer.html?file=${OSSclientConfig.basePath}${v.currentFilePath}?index=${index}`" width="100%" height="100%" frameborder="0" crossorigin="anonymous" /> <iframe v-if="v.currentFilePath" :ref="`iframe-${index}`"
:src="`/static/pdfjs/web/viewer.html?file=${OSSclientConfig.basePath}${v.currentFilePath}?index=${index}`"
width="100%" height="100%" frameborder="0" crossorigin="anonymous" />
</div> </div>
</div> </div>
@ -224,14 +199,8 @@
</div> </div>
</div> </div>
<el-dialog <el-dialog :title="$t('trials:noneDicom:message:msg2')" :visible.sync="dialogVisible" :close-on-click-modal="false"
:title="$t('trials:noneDicom:message:msg2')" :close-on-press-escape="false" :show-close="false" width="400px">
:visible.sync="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
width="400px"
>
<el-form ref="lengthForm" :model="form" :rules="rules"> <el-form ref="lengthForm" :model="form" :rules="rules">
<el-form-item label="" prop="length"> <el-form-item label="" prop="length">
<el-input v-model="form.length" type="number"> <el-input v-model="form.length" type="number">
@ -244,13 +213,8 @@
</span> </span>
</el-dialog> </el-dialog>
<el-dialog <el-dialog v-if="personalConfigDialog.visible" :visible.sync="personalConfigDialog.visible"
v-if="personalConfigDialog.visible" :close-on-click-modal="false" :title="personalConfigDialog.title" width="600px">
:visible.sync="personalConfigDialog.visible"
:close-on-click-modal="false"
:title="personalConfigDialog.title"
width="600px"
>
<Others /> <Others />
</el-dialog> </el-dialog>
</div> </div>
@ -277,6 +241,7 @@ import store from '@/store'
import Others from '@/views/trials/trials-panel/reading/dicoms/components/Others' import Others from '@/views/trials/trials-panel/reading/dicoms/components/Others'
const { ViewportType } = Enums const { ViewportType } = Enums
const renderingEngineId = 'myRenderingEngine' const renderingEngineId = 'myRenderingEngine'
import LengthscaleTool from "../tools/LengthscaleTool"
const { const {
ToolGroupManager, ToolGroupManager,
Enums: csToolsEnums, Enums: csToolsEnums,
@ -289,7 +254,10 @@ const {
PlanarFreehandROITool, PlanarFreehandROITool,
SplineROITool, SplineROITool,
EraserTool, EraserTool,
LengthTool LengthTool,
EllipticalROITool,
CircleROITool,
AngleTool
// cursors // cursors
} = cornerstoneTools } = cornerstoneTools
const { MouseBindings, Events: toolsEvents } = csToolsEnums const { MouseBindings, Events: toolsEvents } = csToolsEnums
@ -480,10 +448,14 @@ export default {
cornerstoneTools.addTool(PlanarRotateTool) cornerstoneTools.addTool(PlanarRotateTool)
cornerstoneTools.addTool(ArrowAnnotateTool) cornerstoneTools.addTool(ArrowAnnotateTool)
cornerstoneTools.addTool(RectangleROITool) cornerstoneTools.addTool(RectangleROITool)
cornerstoneTools.addTool(EllipticalROITool)
cornerstoneTools.addTool(CircleROITool)
cornerstoneTools.addTool(AngleTool)
cornerstoneTools.addTool(PlanarFreehandROITool) cornerstoneTools.addTool(PlanarFreehandROITool)
cornerstoneTools.addTool(SplineROITool) cornerstoneTools.addTool(SplineROITool)
cornerstoneTools.addTool(EraserTool) cornerstoneTools.addTool(EraserTool)
cornerstoneTools.addTool(LengthTool) cornerstoneTools.addTool(LengthTool)
cornerstoneTools.addTool(LengthscaleTool)
viewportIds.forEach((viewportId, i) => { viewportIds.forEach((viewportId, i) => {
const toolGroupId = `canvas-${i}` const toolGroupId = `canvas-${i}`
@ -495,10 +467,10 @@ export default {
toolGroup.addTool(PlanarRotateTool.toolName) toolGroup.addTool(PlanarRotateTool.toolName)
toolGroup.addTool(ArrowAnnotateTool.toolName, { toolGroup.addTool(ArrowAnnotateTool.toolName, {
arrowHeadStyle: 'standard', arrowHeadStyle: 'standard',
changeTextCallback: async(data, eventData, doneChangingTextCallback) => { changeTextCallback: async (data, eventData, doneChangingTextCallback) => {
return doneChangingTextCallback(await this.customPrompt()) return doneChangingTextCallback(await this.customPrompt())
}, },
getTextCallback: async(doneChangingTextCallback) => { getTextCallback: async (doneChangingTextCallback) => {
return doneChangingTextCallback(await this.customPrompt()) return doneChangingTextCallback(await this.customPrompt())
} }
}) })
@ -506,6 +478,15 @@ export default {
cachedStats: false, cachedStats: false,
getTextLines: this.getRectangleROIToolTextLines getTextLines: this.getRectangleROIToolTextLines
}) })
toolGroup.addTool(EllipticalROITool.toolName, {
cachedStats: false,
getTextLines: this.getEllipticalROIToolTextLines
})
toolGroup.addTool(CircleROITool.toolName, {
cachedStats: false,
getTextLines: this.getCircleROIToolTextLines
})
toolGroup.addTool(AngleTool.toolName)
toolGroup.addTool(PlanarFreehandROITool.toolName, { toolGroup.addTool(PlanarFreehandROITool.toolName, {
allowOpenContours: false, allowOpenContours: false,
cachedStats: false, cachedStats: false,
@ -522,6 +503,10 @@ export default {
getTextLines: this.getLengthToolTextLines, getTextLines: this.getLengthToolTextLines,
cachedStats: false cachedStats: false
}) })
toolGroup.addTool(LengthscaleTool.toolName, {
getTextLines: this.getLengthscaleToolTextLines,
cachedStats: false
})
toolGroup.setToolActive(StackScrollTool.toolName, { toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }] bindings: [{ mouseButton: MouseBindings.Wheel }]
@ -532,15 +517,23 @@ export default {
if (this.readingTaskState < 2) { if (this.readingTaskState < 2) {
toolGroup.setToolPassive(ArrowAnnotateTool.toolName) toolGroup.setToolPassive(ArrowAnnotateTool.toolName)
toolGroup.setToolPassive(RectangleROITool.toolName) toolGroup.setToolPassive(RectangleROITool.toolName)
toolGroup.setToolPassive(EllipticalROITool.toolName)
toolGroup.setToolPassive(CircleROITool.toolName)
toolGroup.setToolPassive(AngleTool.toolName)
toolGroup.setToolPassive(PlanarFreehandROITool.toolName) toolGroup.setToolPassive(PlanarFreehandROITool.toolName)
toolGroup.setToolPassive(SplineROITool.toolName) toolGroup.setToolPassive(SplineROITool.toolName)
toolGroup.setToolPassive(LengthTool.toolName) toolGroup.setToolPassive(LengthTool.toolName)
toolGroup.setToolPassive(LengthscaleTool.toolName)
} else { } else {
toolGroup.setToolEnabled(ArrowAnnotateTool.toolName) toolGroup.setToolEnabled(ArrowAnnotateTool.toolName)
toolGroup.setToolEnabled(RectangleROITool.toolName) toolGroup.setToolEnabled(RectangleROITool.toolName)
toolGroup.setToolEnabled(EllipticalROITool.toolName)
toolGroup.setToolEnabled(CircleROITool.toolName)
toolGroup.setToolEnabled(AngleTool.toolName)
toolGroup.setToolEnabled(PlanarFreehandROITool.toolName) toolGroup.setToolEnabled(PlanarFreehandROITool.toolName)
toolGroup.setToolEnabled(SplineROITool.toolName) toolGroup.setToolEnabled(SplineROITool.toolName)
toolGroup.setToolEnabled(LengthTool.toolName) toolGroup.setToolEnabled(LengthTool.toolName)
toolGroup.setToolEnabled(LengthscaleTool.toolName)
} }
toolGroup.setToolPassive(EraserTool.toolName) toolGroup.setToolPassive(EraserTool.toolName)
}) })
@ -804,12 +797,12 @@ export default {
if (this.activeTool) { if (this.activeTool) {
toolGroup.setToolPassive(this.activeTool) toolGroup.setToolPassive(this.activeTool)
} }
if (toolName === 'Length') { if (toolName === 'Lengthscale') {
const renderingEngine = getRenderingEngine(renderingEngineId) const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`) const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`)
const imageId = viewport.csImage.imageId const imageId = viewport.csImage.imageId
const annotations = cornerstoneTools.annotation.state.getAllAnnotations() const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
const idx = annotations.findIndex(i => i.metadata.referencedImageId === imageId && i.metadata.toolName === 'Length') const idx = annotations.findIndex(i => i.metadata.referencedImageId === imageId && i.metadata.toolName === 'Lengthscale')
if (idx > -1) { if (idx > -1) {
this.activeTool = '' this.activeTool = ''
// //
@ -874,7 +867,7 @@ export default {
const path = imageId.split(`web:${this.OSSclientConfig.basePath}`)[1] const path = imageId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const fileList = this.viewportInfos[i].fileList const fileList = this.viewportInfos[i].fileList
const fileIndex = fileList.findIndex(f => f.Path === path) const fileIndex = fileList.findIndex(f => f.Path === path)
if (annotation.metadata.toolName === 'Length') { if (annotation.metadata.toolName === 'Lengthscale') {
this.$emit('setPS', { NoneDicomFileId: fileList[fileIndex].Id, Path: fileList[fileIndex].Path, PS: null }) this.$emit('setPS', { NoneDicomFileId: fileList[fileIndex].Id, Path: fileList[fileIndex].Path, PS: null })
} }
}, },
@ -893,7 +886,7 @@ export default {
const fileList = this.viewportInfos[i].fileList const fileList = this.viewportInfos[i].fileList
const fileIndex = fileList.findIndex(f => f.Path === path) const fileIndex = fileList.findIndex(f => f.Path === path)
if (fileIndex === -1) return if (fileIndex === -1) return
if (annotation.metadata.toolName === 'Length') { if (annotation.metadata.toolName === 'Lengthscale') {
const value = annotation.data.l const value = annotation.data.l
if (value) { if (value) {
const cachedStats = Object.keys(annotation.data.cachedStats) const cachedStats = Object.keys(annotation.data.cachedStats)
@ -929,7 +922,7 @@ export default {
const fileList = this.viewportInfos[i].fileList const fileList = this.viewportInfos[i].fileList
const fileIndex = fileList.findIndex(f => f.Path === path) const fileIndex = fileList.findIndex(f => f.Path === path)
if (fileIndex === -1) return if (fileIndex === -1) return
if (annotation.metadata.toolName === 'Length') { if (annotation.metadata.toolName === 'Lengthscale') {
this.form.annotationObj = { this.form.annotationObj = {
id: '', id: '',
visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId, visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId,
@ -996,7 +989,7 @@ export default {
viewport.render() viewport.render()
this.dialogVisible = false this.dialogVisible = false
}, },
getLengthToolTextLines(data, targetId) { getLengthscaleToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId] const cachedVolumeStats = data.cachedStats[targetId]
const { length, unit } = cachedVolumeStats const { length, unit } = cachedVolumeStats
if (length === undefined || length === null || isNaN(length)) { if (length === undefined || length === null || isNaN(length)) {
@ -1013,6 +1006,27 @@ export default {
} }
return textLines return textLines
}, },
// 线
getLengthToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { length, unit } = cachedVolumeStats
if (length === undefined || length === null || isNaN(length)) {
return
}
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
const textLines = []
if (ps) {
textLines.push(`${this.reRound(csUtils.roundNumber(length * ps), this.digitPlaces)} mm`)
} else {
textLines.push(`${this.reRound(csUtils.roundNumber(length), this.digitPlaces)} ${unit}`)
}
return textLines
},
getPlanarFreehandROIToolTextLines(data, targetId) { getPlanarFreehandROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId] const cachedVolumeStats = data.cachedStats[targetId]
const { const {
@ -1044,7 +1058,7 @@ export default {
if (mean) { if (mean) {
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`) textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
} }
if (max) { if (max) {
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`) textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
} }
@ -1094,6 +1108,60 @@ export default {
return textLines return textLines
}, },
//
getEllipticalROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (ps) {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area * ps * ps), this.digitPlaces)} ${'mm' + '\xb2'}`)
} else {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area), this.digitPlaces)} ${areaUnit}`)
}
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Std Dev: ${this.reRound(csUtils.roundNumber(stdDev), this.digitPlaces)} ${modalityUnit}`)
return textLines
},
//
getCircleROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (ps) {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area * ps * ps), this.digitPlaces)} ${'mm' + '\xb2'}`)
} else {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area), this.digitPlaces)} ${areaUnit}`)
}
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Std Dev: ${this.reRound(csUtils.roundNumber(stdDev), this.digitPlaces)} ${modalityUnit}`)
return textLines
},
reRound(result, finalPrecision) { reRound(result, finalPrecision) {
if (typeof result === 'string' && result.includes(', ')) { if (typeof result === 'string' && result.includes(', ')) {
const numStrs = result.split(', ') const numStrs = result.split(', ')
@ -1105,7 +1173,7 @@ export default {
processSingle(str, precision) { processSingle(str, precision) {
const num = parseFloat(str) const num = parseFloat(str)
if (isNaN(num)) return 'NaN' if (isNaN(num)) return 'NaN'
// //
if (Math.abs(num) < 0.0001) return str if (Math.abs(num) < 0.0001) return str
const factor = 10 ** precision const factor = 10 ** precision
@ -1113,7 +1181,7 @@ export default {
}, },
debounce(callback, delay) { debounce(callback, delay) {
let timerId let timerId
return function() { return function () {
clearTimeout(timerId) clearTimeout(timerId)
timerId = setTimeout(() => { timerId = setTimeout(() => {
callback.apply(this, arguments) callback.apply(this, arguments)
@ -1226,9 +1294,10 @@ export default {
.none-dicom-viewer { .none-dicom-viewer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width:100%; width: 100%;
height: 100%; height: 100%;
user-select: none; user-select: none;
.tools-wrapper { .tools-wrapper {
height: 50px; height: 50px;
display: flex; display: flex;
@ -1236,6 +1305,7 @@ export default {
border-bottom: 1px solid #727272; border-bottom: 1px solid #727272;
color: #ddd; color: #ddd;
padding: 0 5px; padding: 0 5px;
.tools-left { .tools-left {
flex: 1; flex: 1;
display: flex; display: flex;
@ -1243,6 +1313,7 @@ export default {
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
} }
.tool-item { .tool-item {
padding: 5px; padding: 5px;
margin: 0 5px; margin: 0 5px;
@ -1250,15 +1321,19 @@ export default {
font-size: 20px; font-size: 20px;
cursor: pointer; cursor: pointer;
} }
.tool-item-active { .tool-item-active {
background-color: #607d8b; background-color: #607d8b;
} }
.tool-disabled { .tool-disabled {
cursor: not-allowed; cursor: not-allowed;
} }
} }
.viewports-wrapper { .viewports-wrapper {
flex: 1; flex: 1;
.grid-container { .grid-container {
display: grid; display: grid;
height: 100%; height: 100%;
@ -1267,26 +1342,32 @@ export default {
} }
.grid-cell { .grid-cell {
border: 1px dashed #ccc;; border: 1px dashed #ccc;
;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.cell_active { .cell_active {
border-color: #fafa00!important; border-color: #fafa00 !important;
} }
.cell-full-screen { .cell-full-screen {
grid-column: 1 / -1; grid-column: 1 / -1;
grid-row: 1 / -1; grid-row: 1 / -1;
} }
.flex_col { .flex_col {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.content { .content {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
.left-top-text { .left-top-text {
position: absolute; position: absolute;
left: 5px; left: 5px;
@ -1294,26 +1375,31 @@ export default {
color: #ddd; color: #ddd;
z-index: 1; z-index: 1;
font-size: 12px; font-size: 12px;
.cd-info { .cd-info {
color: #ddd; color: #ddd;
font-size: 18px; font-size: 18px;
} }
.subject-info { .subject-info {
color:#f44336; color: #f44336;
padding: 5px 0px; padding: 5px 0px;
margin: 0; margin: 0;
} }
} }
.top-center-tool { .top-center-tool {
position: absolute; position: absolute;
left:50%; left: 50%;
top: 5px; top: 5px;
transform: translateX(-50%); transform: translateX(-50%);
z-index: 1; z-index: 1;
.toggle-visit-container { .toggle-visit-container {
display: flex; display: flex;
} }
.arrw_icon{
.arrw_icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
background-color: #3f3f3f; background-color: #3f3f3f;
@ -1321,15 +1407,17 @@ export default {
line-height: 20px; line-height: 20px;
border-radius: 10%; border-radius: 10%;
} }
.arrow_text{
.arrow_text {
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
background-color: #00000057; background-color: #00000057;
color: #fff; color: #fff;
padding:0 10px; padding: 0 10px;
font-size: 14px; font-size: 14px;
} }
} }
.right-slider-box { .right-slider-box {
position: absolute; position: absolute;
right: 1px; right: 1px;
@ -1341,7 +1429,8 @@ export default {
z-index: 1; z-index: 1;
cursor: pointer; cursor: pointer;
} }
.right-slider-box:after{
.right-slider-box:after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -20px; bottom: -20px;
@ -1350,33 +1439,39 @@ export default {
width: 100%; width: 100%;
background: #333; background: #333;
} }
.slider { .slider {
height: 20px; height: 20px;
width: 100%; width: 100%;
position: absolute; position: absolute;
top: 0; top: 0;
z-index:10; z-index: 10;
background: #9e9e9e; background: #9e9e9e;
cursor: move cursor: move
} }
} }
} }
::v-deep .el-dialog{
::v-deep .el-dialog {
background: #1e1e1e; background: #1e1e1e;
border: 1px solid #ddd; border: 1px solid #ddd;
color: #ddd; color: #ddd;
.el-dialog__title{
color:#fff; .el-dialog__title {
color: #fff;
} }
.el-input .el-input__inner{
.el-input .el-input__inner {
background-color: transparent; background-color: transparent;
color: #ddd; color: #ddd;
border: 1px solid #5e5e5e; border: 1px solid #5e5e5e;
} }
.el-input.is-disabled .el-input__inner{
.el-input.is-disabled .el-input__inner {
background-color: #646464a1; background-color: #646464a1;
} }
.el-form-item__label{
.el-form-item__label {
color: #dfdfdf color: #dfdfdf
} }
} }

View File

@ -264,7 +264,7 @@ export default {
if (typeof i.MeasureData === 'string') { if (typeof i.MeasureData === 'string') {
i.MeasureData = JSON.parse(i.MeasureData) i.MeasureData = JSON.parse(i.MeasureData)
} }
if (i.MeasureData.metadata.toolName === 'Length' && this.psArr.findIndex(p => p.NoneDicomFileId === i.NoneDicomFileId) === -1) { if (i.MeasureData.metadata.toolName === 'Lengthscale' && this.psArr.findIndex(p => p.NoneDicomFileId === i.NoneDicomFileId) === -1) {
this.psArr.push({ NoneDicomFileId: i.NoneDicomFileId, Path: i.Path, PS: i.MeasureData.data.ps }) this.psArr.push({ NoneDicomFileId: i.NoneDicomFileId, Path: i.Path, PS: i.MeasureData.data.ps })
} }
return i return i

View File

@ -0,0 +1,11 @@
import * as cornerstoneTools from '@cornerstonejs/tools'
class LengthscaleTool extends cornerstoneTools.LengthTool {
static { this.toolName = 'Lengthscale'; }
constructor(
toolProps,
defaultToolProps
) {
super(toolProps, defaultToolProps);
}
}
export default LengthscaleTool;