非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>
<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" />
</div>
<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">
<!-- 访视阅片 -->
<visit-review
v-if="taskInfo && taskInfo.ReadingCategory=== 1"
v-if="taskInfo && taskInfo.ReadingCategory === 1"
/>
<!-- 临床数据 -->
<el-dialog

View File

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

View File

@ -264,7 +264,7 @@ export default {
if (typeof i.MeasureData === 'string') {
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 })
}
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;