阅片页面增加患者信息、检查信息
continuous-integration/drone/push Build is passing Details

main
wangxiaoshuang 2025-08-25 14:00:46 +08:00
parent ae2cb2c5fb
commit 64581a3e07
2 changed files with 354 additions and 302 deletions

View File

@ -1,15 +1,7 @@
<template> <template>
<div <div id="canvas" ref="canvas" v-loading="loading" element-loading-text="Loading..."
id="canvas" element-loading-background="rgba(0, 0, 0, 0.8)" style="position:relative;" class="cornerstone-element"
ref="canvas" @mouseup="sliderMouseup" @contextmenu.prevent="onContextmenu">
v-loading="loading"
element-loading-text="Loading..."
element-loading-background="rgba(0, 0, 0, 0.8)"
style="position:relative;"
class="cornerstone-element"
@mouseup="sliderMouseup"
@contextmenu.prevent="onContextmenu"
>
<!-- 临床数据 --> <!-- 临床数据 -->
<div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)"> <div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom"> <el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom">
@ -18,46 +10,40 @@
</div> </div>
<!-- 切换访视 --> <!-- 切换访视 -->
<div <div v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" class="info-visit"
v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" @dblclick.stop="preventDefault($event)">
class="info-visit" <div class="arrw_div_wrapper"
@dblclick.stop="preventDefault($event)" :style="{ cursor: stack.visitTaskNum <= minVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum <= minVistNum ? '#888' : '#fff' }"
> @click.stop.prevent="toggleSeries($event, -1)" @dblclick.stop="preventDefault($event)">
<div
class="arrw_div_wrapper"
:style="{cursor:stack.visitTaskNum <= minVistNum?'not-allowed':'pointer',color:stack.visitTaskNum <= minVistNum?'#888':'#fff'}"
@click.stop.prevent="toggleSeries($event,-1)"
@dblclick.stop="preventDefault($event)"
>
<i class="el-icon-caret-left" /> <i class="el-icon-caret-left" />
</div> </div>
<div class="blind_name_wrapper"> <div class="blind_name_wrapper">
{{ stack.taskBlindName }} {{ stack.taskBlindName }}
</div> </div>
<div <div class="arrw_div_wrapper"
class="arrw_div_wrapper" :style="{ cursor: stack.visitTaskNum >= maxVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum >= maxVistNum ? '#888' : '#fff' }"
:style="{cursor:stack.visitTaskNum >= maxVistNum?'not-allowed':'pointer',color:stack.visitTaskNum >= maxVistNum?'#888':'#fff'}" @click.stop.prevent="toggleSeries($event, 1)" @dblclick.stop="preventDefault($event)">
@click.stop.prevent="toggleSeries($event,1)"
@dblclick.stop="preventDefault($event)"
>
<i class="el-icon-caret-right" /> <i class="el-icon-caret-right" />
</div> </div>
</div> </div>
<div class="info-series"> <div class="info-series">
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{ stack.taskBlindName }}</h2> <h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{
stack.taskBlindName }}</h2>
<div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div> <div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div>
<div>Image: #{{ dicomInfo.frame }}</div> <div>Image: #{{ dicomInfo.frame }}</div>
<div>{{ dicomInfo.modality }}</div> <div>{{ dicomInfo.modality }}</div>
</div> </div>
<div class="info-image"> <div class="info-image">
<div v-show="mousePosition.mo"> <div v-show="mousePosition.mo">
Pos: {{ mousePosition.x?mousePosition.x.toFixed(0):'' }}, {{ mousePosition.y?mousePosition.y.toFixed(0):'' }} Pos: {{ mousePosition.x ? mousePosition.x.toFixed(0) : '' }}, {{ mousePosition.y ? mousePosition.y.toFixed(0) :
'' }}
</div> </div>
<div v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo"> <div
v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
HU: {{ mousePosition.mo }} HU: {{ mousePosition.mo }}
</div> </div>
<div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)"> <div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)">
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.suv.toFixed(3) :mousePosition.suv.toFixed(digitPlaces) }} SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.suv.toFixed(3) : mousePosition.suv.toFixed(digitPlaces) }}
</div> </div>
<div v-else-if="mousePosition.mo"> <div v-else-if="mousePosition.mo">
Density: {{ mousePosition.mo }} Density: {{ mousePosition.mo }}
@ -75,14 +61,20 @@
<div class="info-subject"> <div class="info-subject">
<div>{{ stack.description }}</div> <div>{{ stack.description }}</div>
<!-- <div>{{ dicomInfo.hospital }}</div> --> <!-- <div>{{ dicomInfo.hospital }}</div> -->
<!-- <div v-show="dicomInfo.pid">{{ dicomInfo.pid }}</div> --> <div v-if="dicomInfo.pid">{{ dicomInfo.pid }}</div>
<div v-if="dicomInfo.patientName">{{ dicomInfo.patientName }}</div>
<!-- <div>{{ subjectCode }}</div> --> <!-- <div>{{ subjectCode }}</div> -->
<!-- <div>{{ dicomInfo.sex }} {{ dicomInfo.age }}</div> --> <!-- <div>{{ dicomInfo.sex }} {{ dicomInfo.age }}</div> -->
<!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> --> <!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> -->
<!-- <div>{{ dicomInfo.time }}</div> --> <div v-if="dicomInfo.modality">{{ dicomInfo.modality }}</div>
<div v-if="dicomInfo.time">{{ dicomInfo.time }}</div>
</div> </div>
<div ref="sliderBox" class="my_slider_box" style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer" @click.stop="goViewer($event)"> <div ref="sliderBox" class="my_slider_box"
<div :style="{top: height + '%'}" style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" /> style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer"
@click.stop="goViewer($event)">
<div :style="{ top: height + '%' }"
style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move"
@click.stop.prevent="() => { return }" @mousedown.stop="sliderMousedown($event)" />
</div> </div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);"> <div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }} {{ markers.top }}
@ -117,7 +109,7 @@
</div> </div>
</template> </template>
<style> <style>
.my_slider_box:after{ .my_slider_box:after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -20px; bottom: -20px;
@ -152,6 +144,7 @@ import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/too
import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString' import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString'
// import calculateLongestAndShortestDiameters from '@/views/trials/trials-panel/reading/dicoms/tools/Bidirectional/calculateLongestAndShortestDiameters' // import calculateLongestAndShortestDiameters from '@/views/trials/trials-panel/reading/dicoms/tools/Bidirectional/calculateLongestAndShortestDiameters'
import calculateSUV from '@/views/trials/trials-panel/reading/dicoms/tools/calculateSUV' import calculateSUV from '@/views/trials/trials-panel/reading/dicoms/tools/calculateSUV'
import { convertBytes } from '@/utils/dicom-character-set'
cornerstoneTools.external.cornerstone = cornerstone cornerstoneTools.external.cornerstone = cornerstone
cornerstoneTools.external.Hammer = Hammer cornerstoneTools.external.Hammer = Hammer
cornerstoneTools.external.cornerstoneMath = cornerstoneMath cornerstoneTools.external.cornerstoneMath = cornerstoneMath
@ -568,7 +561,7 @@ export default {
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
}).catch(() => {}) }).catch(() => { })
e.stopImmediatePropagation() e.stopImmediatePropagation()
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
@ -1141,7 +1134,7 @@ export default {
// const scope = this // const scope = this
Array.from(toolButtons).forEach((toolBtn) => { Array.from(toolButtons).forEach((toolBtn) => {
// Add the tool // Add the tool
const toolName = toolBtn.getAttribute('data-tool') const toolName = toolBtn.getAttribute('data-tool')
const apiTool = cornerstoneTools[`${toolName}Tool`] const apiTool = cornerstoneTools[`${toolName}Tool`]
if (apiTool) { if (apiTool) {
@ -1149,15 +1142,15 @@ export default {
if (!toolAlreadyAddedToElement) { if (!toolAlreadyAddedToElement) {
if (toolName === 'Length') { if (toolName === 'Length') {
cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true }}) cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true } })
} else if (toolName === 'Bidirectional') { } else if (toolName === 'Bidirectional') {
// cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces }) // cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces })
// , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true // , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true
cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true }}) cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true } })
} else if (toolName === 'ArrowAnnotate') { } else if (toolName === 'ArrowAnnotate') {
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }}) cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'RectangleRoi') { } else if (toolName === 'RectangleRoi') {
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }}) cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else { } else {
cornerstoneTools.addToolForElement(element, apiTool) cornerstoneTools.addToolForElement(element, apiTool)
} }
@ -1183,10 +1176,10 @@ export default {
mouseButtonMask: 4 mouseButtonMask: 4
}) })
// if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) { // if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) {
// cornerstoneTools.addToolForElement(element, OrientationMarkersTool) // cornerstoneTools.addToolForElement(element, OrientationMarkersTool)
// } // }
// cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { }) // cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { })
} }
// cornerstoneTools.addStackStateManager(this.canvas, ['stack', 'stackPrefetch', 'playClip']) // cornerstoneTools.addStackStateManager(this.canvas, ['stack', 'stackPrefetch', 'playClip'])
@ -1218,9 +1211,24 @@ export default {
ToolStateManager.clearImageIdToolState(imageId) ToolStateManager.clearImageIdToolState(imageId)
e.detail.enabledElement.options = {} e.detail.enabledElement.options = {}
var data = e.detail.image.data var data = e.detail.image.data
const patientNameElement = data.elements.x00100010
const patientNameBytes = new Uint8Array(
data.byteArray.buffer,
patientNameElement ? patientNameElement.dataOffset : 0,
patientNameElement ? patientNameElement.length : 0
)
// dicom 2025.03.04
let SpecificCharacterSet = data.string('x00080005')
? data.string('x00080005').replace('ISO IR', 'ISO_IR')
: ''
const patientNameStr = convertBytes(
SpecificCharacterSet,
patientNameBytes
)
this.dicomInfo.hospital = data.string('x00080080') this.dicomInfo.hospital = data.string('x00080080')
// this.dicomInfo.pid = data.string('x00100020') this.dicomInfo.pid = data.string('x00100020')
this.dicomInfo.pid = data.string('x00120040') this.dicomInfo.patientName = patientNameStr
// this.dicomInfo.pid = data.string('x00120040')
this.dicomInfo.name = data.string('x00100010') this.dicomInfo.name = data.string('x00100010')
this.dicomInfo.age = data.string('x00101010') this.dicomInfo.age = data.string('x00101010')
this.dicomInfo.sex = data.string('x00100040') this.dicomInfo.sex = data.string('x00100040')
@ -1231,9 +1239,8 @@ export default {
data.string('x00080030') data.string('x00080030')
) )
this.dicomInfo.series = data.string('x00200011') this.dicomInfo.series = data.string('x00200011')
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${ this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${this.stack.imageIds.length
this.stack.imageIds.length }`
}`
this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16( this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16(
'x00280010' 'x00280010'
)}` )}`
@ -1413,9 +1420,9 @@ export default {
var element = cornerstone.getEnabledElement(this.canvas) var element = cornerstone.getEnabledElement(this.canvas)
const { rowPixelSpacing, colPixelSpacing } = this.getPixelSpacing(element.image) const { rowPixelSpacing, colPixelSpacing } = this.getPixelSpacing(element.image)
const dx = const dx =
(data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1) (data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1)
const dy = const dy =
(data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1) (data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1)
const length = Math.sqrt(dx * dx + dy * dy) const length = Math.sqrt(dx * dx + dy * dy)
return length.toFixed(this.digitPlaces) return length.toFixed(this.digitPlaces)
@ -1429,9 +1436,9 @@ export default {
if (imagePlane) { if (imagePlane) {
return { return {
rowPixelSpacing: rowPixelSpacing:
imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing, imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing,
colPixelSpacing: colPixelSpacing:
imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing
} }
} }
@ -1482,7 +1489,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)
@ -1963,88 +1970,102 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.context-menu-wrapper{ .context-menu-wrapper {
position: absolute; position: absolute;
ul{
list-style: none; ul {
margin: 0px; list-style: none;
padding: 0px; margin: 0px;
padding: 0px;
background: #343333;
color: #fff;
margin: 0;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width: 80px;
box-sizing: border-box;
}
.menu {
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
}
.submenu {
position: absolute;
left: 77px;
top: -1px;
background: #343333; background: #343333;
color: #fff; color: #fff;
margin: 0; display: none;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width:80px;
box-sizing: border-box;
}
.menu{
li { li {
padding: 2px 5px; padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666; border-bottom: 1px solid #666;
div{
div {
padding: 5px; padding: 5px;
} }
} }
.submenu{
position: absolute;
left: 77px;
top: -1px;
background: #343333;
color: #fff;
display: none;
li {
padding: 2px 5px;
border-bottom: 1px solid #666;
div{
padding: 5px;
}
}
}
}
.menu li:hover{
background-color: #ff5722;
.submenu{
display:block;
}
}
.menu_active{
cursor: pointer;
}
.menu_disabled{
cursor: not-allowed;
} }
} }
.info-visit{
position: absolute; .menu li:hover {
left:50%; background-color: #ff5722;
top: 5px;
transform: translateX(-50%); .submenu {
display: flex; display: block;
flex-direction: row;
.arrw_div_wrapper{
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper{
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding:0 10px;
font-size: 14px;
} }
} }
.info-cd{
.menu_active {
cursor: pointer;
}
.menu_disabled {
cursor: not-allowed;
}
}
.info-visit {
position: absolute;
left: 50%;
top: 5px;
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper {
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper {
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding: 0 10px;
font-size: 14px;
}
}
.info-cd {
position: absolute; position: absolute;
left: 10px; left: 10px;
top: 5px; top: 5px;
@ -2053,6 +2074,7 @@ export default {
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
} }
.info-series { .info-series {
position: absolute; position: absolute;
left: 10px; left: 10px;
@ -2062,6 +2084,7 @@ export default {
font-size: 12px; font-size: 12px;
/* z-index: 1; */ /* z-index: 1; */
} }
.info-image { .info-image {
position: absolute; position: absolute;
left: 10px; left: 10px;
@ -2081,6 +2104,7 @@ export default {
font-size: 12px; font-size: 12px;
/* z-index: 1; */ /* z-index: 1; */
} }
.info-instance { .info-instance {
position: absolute; position: absolute;
right: 15px; right: 15px;
@ -2113,6 +2137,7 @@ export default {
margin: 10px; margin: 10px;
cursor: default; cursor: default;
} }
.menu__item:hover { .menu__item:hover {
color: #ff0000; color: #ff0000;
} }
@ -2132,11 +2157,12 @@ li:hover {
background-color: #e0e0e2; background-color: #e0e0e2;
color: white; color: white;
} }
.msg-div { .msg-div {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
background-color: rgba(255, 255, 255, 0.5); background-color: rgba(255, 255, 255, 0.5);
color: #000; color: #000;
padding: 5px 20px; padding: 5px 20px;
} }
</style> </style>

View File

@ -1,15 +1,7 @@
<template> <template>
<div <div id="canvas" ref="canvas" v-loading="loading" element-loading-text="Loading..."
id="canvas" element-loading-background="rgba(0, 0, 0, 0.8)" style="position:relative;" class="cornerstone-element"
ref="canvas" @mouseup="sliderMouseup" @contextmenu.prevent="onContextmenu">
v-loading="loading"
element-loading-text="Loading..."
element-loading-background="rgba(0, 0, 0, 0.8)"
style="position:relative;"
class="cornerstone-element"
@mouseup="sliderMouseup"
@contextmenu.prevent="onContextmenu"
>
<!-- 临床数据 --> <!-- 临床数据 -->
<div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)"> <div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom"> <el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom">
@ -18,46 +10,40 @@
</div> </div>
<!-- 切换访视 --> <!-- 切换访视 -->
<div <div v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" class="info-visit"
v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" @dblclick.stop="preventDefault($event)">
class="info-visit" <div class="arrw_div_wrapper"
@dblclick.stop="preventDefault($event)" :style="{ cursor: stack.visitTaskNum <= minVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum <= minVistNum ? '#888' : '#fff' }"
> @click.stop.prevent="toggleSeries($event, -1)" @dblclick.stop="preventDefault($event)">
<div
class="arrw_div_wrapper"
:style="{cursor:stack.visitTaskNum <= minVistNum?'not-allowed':'pointer',color:stack.visitTaskNum <= minVistNum?'#888':'#fff'}"
@click.stop.prevent="toggleSeries($event,-1)"
@dblclick.stop="preventDefault($event)"
>
<i class="el-icon-caret-left" /> <i class="el-icon-caret-left" />
</div> </div>
<div class="blind_name_wrapper"> <div class="blind_name_wrapper">
{{ stack.taskBlindName }} {{ stack.taskBlindName }}
</div> </div>
<div <div class="arrw_div_wrapper"
class="arrw_div_wrapper" :style="{ cursor: stack.visitTaskNum >= maxVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum >= maxVistNum ? '#888' : '#fff' }"
:style="{cursor:stack.visitTaskNum >= maxVistNum?'not-allowed':'pointer',color:stack.visitTaskNum >= maxVistNum?'#888':'#fff'}" @click.stop.prevent="toggleSeries($event, 1)" @dblclick.stop="preventDefault($event)">
@click.stop.prevent="toggleSeries($event,1)"
@dblclick.stop="preventDefault($event)"
>
<i class="el-icon-caret-right" /> <i class="el-icon-caret-right" />
</div> </div>
</div> </div>
<div class="info-series"> <div class="info-series">
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{ stack.taskBlindName }}</h2> <h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{
stack.taskBlindName }}</h2>
<div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div> <div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div>
<div>Image: #{{ dicomInfo.frame }}</div> <div>Image: #{{ dicomInfo.frame }}</div>
<div>{{ dicomInfo.modality }}</div> <div>{{ dicomInfo.modality }}</div>
</div> </div>
<div class="info-image"> <div class="info-image">
<div v-show="mousePosition.mo"> <div v-show="mousePosition.mo">
Pos: {{ mousePosition.x?mousePosition.x.toFixed(0):'' }}, {{ mousePosition.y?mousePosition.y.toFixed(0):'' }} Pos: {{ mousePosition.x ? mousePosition.x.toFixed(0) : '' }}, {{ mousePosition.y ? mousePosition.y.toFixed(0) :
'' }}
</div> </div>
<div v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo"> <div
v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
HU: {{ mousePosition.mo }} HU: {{ mousePosition.mo }}
</div> </div>
<div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)"> <div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)">
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.suv.toFixed(3) :mousePosition.suv.toFixed(digitPlaces) }} SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.suv.toFixed(3) : mousePosition.suv.toFixed(digitPlaces) }}
</div> </div>
<div v-else-if="mousePosition.mo"> <div v-else-if="mousePosition.mo">
Density: {{ mousePosition.mo }} Density: {{ mousePosition.mo }}
@ -75,14 +61,20 @@
<div class="info-subject"> <div class="info-subject">
<div>{{ stack.description }}</div> <div>{{ stack.description }}</div>
<!-- <div>{{ dicomInfo.hospital }}</div> --> <!-- <div>{{ dicomInfo.hospital }}</div> -->
<!-- <div v-show="dicomInfo.pid">{{ dicomInfo.pid }}</div> --> <div v-if="dicomInfo.pid">{{ dicomInfo.pid }}</div>
<div v-if="dicomInfo.patientName">{{ dicomInfo.patientName }}</div>
<!-- <div>{{ subjectCode }}</div> --> <!-- <div>{{ subjectCode }}</div> -->
<!-- <div>{{ dicomInfo.sex }} {{ dicomInfo.age }}</div> --> <!-- <div>{{ dicomInfo.sex }} {{ dicomInfo.age }}</div> -->
<!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> --> <!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> -->
<!-- <div>{{ dicomInfo.time }}</div> --> <div v-if="dicomInfo.modality">{{ dicomInfo.modality }}</div>
<div v-if="dicomInfo.time">{{ dicomInfo.time }}</div>
</div> </div>
<div ref="sliderBox" class="my_slider_box" style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer" @click.stop="goViewer($event)"> <div ref="sliderBox" class="my_slider_box"
<div :style="{top: height + '%'}" style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" /> style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer"
@click.stop="goViewer($event)">
<div :style="{ top: height + '%' }"
style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move"
@click.stop.prevent="() => { return }" @mousedown.stop="sliderMousedown($event)" />
</div> </div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);"> <div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }} {{ markers.top }}
@ -117,7 +109,7 @@
</div> </div>
</template> </template>
<style> <style>
.my_slider_box:after{ .my_slider_box:after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -20px; bottom: -20px;
@ -153,6 +145,7 @@ import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/too
import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString' import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString'
// import calculateLongestAndShortestDiameters from '@/views/trials/trials-panel/reading/dicoms/tools/Bidirectional/calculateLongestAndShortestDiameters' // import calculateLongestAndShortestDiameters from '@/views/trials/trials-panel/reading/dicoms/tools/Bidirectional/calculateLongestAndShortestDiameters'
import calculateSUV from '@/views/trials/trials-panel/reading/dicoms/tools/calculateSUV' import calculateSUV from '@/views/trials/trials-panel/reading/dicoms/tools/calculateSUV'
import { convertBytes } from '@/utils/dicom-character-set'
cornerstoneTools.external.cornerstone = cornerstone cornerstoneTools.external.cornerstone = cornerstone
cornerstoneTools.external.Hammer = Hammer cornerstoneTools.external.Hammer = Hammer
cornerstoneTools.external.cornerstoneMath = cornerstoneMath cornerstoneTools.external.cornerstoneMath = cornerstoneMath
@ -560,7 +553,7 @@ export default {
mouseDown(e) { mouseDown(e) {
this.image = e.detail.image this.image = e.detail.image
var pointNearTool = this.pointNearTool(e) var pointNearTool = this.pointNearTool(e)
if (pointNearTool) { if (pointNearTool) {
e.stopImmediatePropagation() e.stopImmediatePropagation()
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
@ -572,7 +565,7 @@ export default {
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
}).catch(() => {}) }).catch(() => { })
e.stopImmediatePropagation() e.stopImmediatePropagation()
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
@ -989,31 +982,31 @@ export default {
var idx = this.measureData.findIndex(item => item.MeasureData && item.MeasureData.data && item.MeasureData.data.uuid === toolState.data[i].uuid) var idx = this.measureData.findIndex(item => item.MeasureData && item.MeasureData.data && item.MeasureData.data.uuid === toolState.data[i].uuid)
if (idx > -1) { if (idx > -1) {
DicomEvent.$emit('setCollapseActive', this.measureData[idx]) DicomEvent.$emit('setCollapseActive', this.measureData[idx])
const measureData = {} const measureData = {}
// var markName = this.measureData[idx].OrderMarkName // var markName = this.measureData[idx].OrderMarkName
var markName = this.measureData[idx].MeasureData.data.remark var markName = this.measureData[idx].MeasureData.data.remark
if (this.activeToolName === 'Eraser' && this.disabledMarks.indexOf(markName) === -1) { if (this.activeToolName === 'Eraser' && this.disabledMarks.indexOf(markName) === -1) {
const questionInfo = this.measureData[idx] const questionInfo = this.measureData[idx]
measureData.orderMarkName = markName measureData.orderMarkName = markName
this.$emit('moveMeasureData', { measureData, questionInfo, orderMarkName: markName}) this.$emit('moveMeasureData', { measureData, questionInfo, orderMarkName: markName })
} }
if ((this.disabledMarks.indexOf(markName) === -1 || !this.disabledMarks) && this.activeToolName !== 'Eraser') { if ((this.disabledMarks.indexOf(markName) === -1 || !this.disabledMarks) && this.activeToolName !== 'Eraser') {
const questionInfo = this.measureData[idx] const questionInfo = this.measureData[idx]
const canvas = this.canvas.querySelector('canvas') const canvas = this.canvas.querySelector('canvas')
measureData.pictureBaseStr = canvas.toDataURL('image/png', 1) measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
measureData.studyId = this.stack.studyId measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId measureData.instanceId = instanceId
measureData.frame = this.stack.frame ? this.stack.frame : 0 measureData.frame = this.stack.frame ? this.stack.frame : 0
measureData.data = toolState.data[i] measureData.data = toolState.data[i]
measureData.type = toolType measureData.type = toolType
measureData.thick = this.dicomInfo.thick measureData.thick = this.dicomInfo.thick
measureData.location = this.dicomInfo.location measureData.location = this.dicomInfo.location
measureData.ww = Math.round(viewport.voi.windowWidth) measureData.ww = Math.round(viewport.voi.windowWidth)
measureData.wc = Math.round(viewport.voi.windowCenter) measureData.wc = Math.round(viewport.voi.windowCenter)
measureData.data.active = false measureData.data.active = false
this.$emit('modifyMeasureData', { measureData, questionInfo }) this.$emit('modifyMeasureData', { measureData, questionInfo })
} }
break break
} }
@ -1115,7 +1108,7 @@ export default {
const toolButtons = document.querySelectorAll('[data-tool]') const toolButtons = document.querySelectorAll('[data-tool]')
// const scope = this // const scope = this
Array.from(toolButtons).forEach((toolBtn) => { Array.from(toolButtons).forEach((toolBtn) => {
// Add the tool // Add the tool
const toolName = toolBtn.getAttribute('data-tool') const toolName = toolBtn.getAttribute('data-tool')
const apiTool = cornerstoneTools[`${toolName}Tool`] const apiTool = cornerstoneTools[`${toolName}Tool`]
if (apiTool) { if (apiTool) {
@ -1123,17 +1116,17 @@ export default {
if (!toolAlreadyAddedToElement) { if (!toolAlreadyAddedToElement) {
if (toolName === 'Length') { if (toolName === 'Length') {
cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true }}) cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true } })
} else if (toolName === 'Bidirectional') { } else if (toolName === 'Bidirectional') {
// cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces }) // cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces })
// , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true // , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true
cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true }}) cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true } })
} else if (toolName === 'ArrowAnnotate') { } else if (toolName === 'ArrowAnnotate') {
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }}) cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'RectangleRoi') { } else if (toolName === 'RectangleRoi') {
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }}) cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'CircleRoi') { } else if (toolName === 'CircleRoi') {
cornerstoneTools.addToolForElement(element, CircleRoiTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true, showMinMax: true }}) cornerstoneTools.addToolForElement(element, CircleRoiTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true, showMinMax: true } })
} else { } else {
cornerstoneTools.addToolForElement(element, apiTool) cornerstoneTools.addToolForElement(element, apiTool)
} }
@ -1159,10 +1152,10 @@ export default {
mouseButtonMask: 4 mouseButtonMask: 4
}) })
// if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) { // if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) {
// cornerstoneTools.addToolForElement(element, OrientationMarkersTool) // cornerstoneTools.addToolForElement(element, OrientationMarkersTool)
// } // }
// cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { }) // cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { })
} }
// cornerstoneTools.addStackStateManager(this.canvas, ['stack', 'stackPrefetch', 'playClip']) // cornerstoneTools.addStackStateManager(this.canvas, ['stack', 'stackPrefetch', 'playClip'])
@ -1194,9 +1187,24 @@ export default {
ToolStateManager.clearImageIdToolState(imageId) ToolStateManager.clearImageIdToolState(imageId)
e.detail.enabledElement.options = {} e.detail.enabledElement.options = {}
var data = e.detail.image.data var data = e.detail.image.data
const patientNameElement = data.elements.x00100010
const patientNameBytes = new Uint8Array(
data.byteArray.buffer,
patientNameElement ? patientNameElement.dataOffset : 0,
patientNameElement ? patientNameElement.length : 0
)
// dicom 2025.03.04
let SpecificCharacterSet = data.string('x00080005')
? data.string('x00080005').replace('ISO IR', 'ISO_IR')
: ''
const patientNameStr = convertBytes(
SpecificCharacterSet,
patientNameBytes
)
this.dicomInfo.hospital = data.string('x00080080') this.dicomInfo.hospital = data.string('x00080080')
// this.dicomInfo.pid = data.string('x00100020') this.dicomInfo.pid = data.string('x00100020')
this.dicomInfo.pid = data.string('x00120040') this.dicomInfo.patientName = patientNameStr
// this.dicomInfo.pid = data.string('x00120040')
this.dicomInfo.name = data.string('x00100010') this.dicomInfo.name = data.string('x00100010')
this.dicomInfo.age = data.string('x00101010') this.dicomInfo.age = data.string('x00101010')
this.dicomInfo.sex = data.string('x00100040') this.dicomInfo.sex = data.string('x00100040')
@ -1207,9 +1215,8 @@ export default {
data.string('x00080030') data.string('x00080030')
) )
this.dicomInfo.series = data.string('x00200011') this.dicomInfo.series = data.string('x00200011')
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${ this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${this.stack.imageIds.length
this.stack.imageIds.length }`
}`
this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16( this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16(
'x00280010' 'x00280010'
)}` )}`
@ -1390,9 +1397,9 @@ export default {
var element = cornerstone.getEnabledElement(this.canvas) var element = cornerstone.getEnabledElement(this.canvas)
const { rowPixelSpacing, colPixelSpacing } = this.getPixelSpacing(element.image) const { rowPixelSpacing, colPixelSpacing } = this.getPixelSpacing(element.image)
const dx = const dx =
(data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1) (data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1)
const dy = const dy =
(data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1) (data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1)
const length = Math.sqrt(dx * dx + dy * dy) const length = Math.sqrt(dx * dx + dy * dy)
return length.toFixed(this.digitPlaces) return length.toFixed(this.digitPlaces)
@ -1406,9 +1413,9 @@ export default {
if (imagePlane) { if (imagePlane) {
return { return {
rowPixelSpacing: rowPixelSpacing:
imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing, imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing,
colPixelSpacing: colPixelSpacing:
imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing
} }
} }
@ -1460,7 +1467,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)
@ -1478,7 +1485,7 @@ export default {
if (imageId) { if (imageId) {
ToolStateManager.clearImageIdToolState(imageId) ToolStateManager.clearImageIdToolState(imageId)
let elements = cornerstone.getEnabledElementsByImageId(imageId) let elements = cornerstone.getEnabledElementsByImageId(imageId)
elements.map(el=>{ elements.map(el => {
cornerstone.updateImage(el.element) cornerstone.updateImage(el.element)
}) })
} }
@ -1918,7 +1925,7 @@ export default {
for (const [key, value] of searchParams.entries()) { for (const [key, value] of searchParams.entries()) {
params[key] = value params[key] = value
} }
if (isNaN(params.frame)){ if (isNaN(params.frame)) {
params.frame = 0 params.frame = 0
} }
return params return params
@ -1940,88 +1947,102 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.context-menu-wrapper{ .context-menu-wrapper {
position: absolute; position: absolute;
ul{
list-style: none; ul {
margin: 0px; list-style: none;
padding: 0px; margin: 0px;
padding: 0px;
background: #343333;
color: #fff;
margin: 0;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width: 80px;
box-sizing: border-box;
}
.menu {
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
}
.submenu {
position: absolute;
left: 77px;
top: -1px;
background: #343333; background: #343333;
color: #fff; color: #fff;
margin: 0; display: none;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width:80px;
box-sizing: border-box;
}
.menu{
li { li {
padding: 2px 5px; padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666; border-bottom: 1px solid #666;
div{
div {
padding: 5px; padding: 5px;
} }
} }
.submenu{
position: absolute;
left: 77px;
top: -1px;
background: #343333;
color: #fff;
display: none;
li {
padding: 2px 5px;
border-bottom: 1px solid #666;
div{
padding: 5px;
}
}
}
}
.menu li:hover{
background-color: #ff5722;
.submenu{
display:block;
}
}
.menu_active{
cursor: pointer;
}
.menu_disabled{
cursor: not-allowed;
} }
} }
.info-visit{
position: absolute; .menu li:hover {
left:50%; background-color: #ff5722;
top: 5px;
transform: translateX(-50%); .submenu {
display: flex; display: block;
flex-direction: row;
.arrw_div_wrapper{
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper{
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding:0 10px;
font-size: 14px;
} }
} }
.info-cd{
.menu_active {
cursor: pointer;
}
.menu_disabled {
cursor: not-allowed;
}
}
.info-visit {
position: absolute;
left: 50%;
top: 5px;
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper {
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper {
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding: 0 10px;
font-size: 14px;
}
}
.info-cd {
position: absolute; position: absolute;
left: 10px; left: 10px;
top: 5px; top: 5px;
@ -2030,6 +2051,7 @@ export default {
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
} }
.info-series { .info-series {
position: absolute; position: absolute;
left: 10px; left: 10px;
@ -2039,6 +2061,7 @@ export default {
font-size: 12px; font-size: 12px;
/* z-index: 1; */ /* z-index: 1; */
} }
.info-image { .info-image {
position: absolute; position: absolute;
left: 10px; left: 10px;
@ -2058,6 +2081,7 @@ export default {
font-size: 12px; font-size: 12px;
/* z-index: 1; */ /* z-index: 1; */
} }
.info-instance { .info-instance {
position: absolute; position: absolute;
right: 15px; right: 15px;
@ -2090,6 +2114,7 @@ export default {
margin: 10px; margin: 10px;
cursor: default; cursor: default;
} }
.menu__item:hover { .menu__item:hover {
color: #ff0000; color: #ff0000;
} }
@ -2109,11 +2134,12 @@ li:hover {
background-color: #e0e0e2; background-color: #e0e0e2;
color: white; color: white;
} }
.msg-div { .msg-div {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
background-color: rgba(255, 255, 255, 0.5); background-color: rgba(255, 255, 255, 0.5);
color: #000; color: #000;
padding: 5px 20px; padding: 5px 20px;
} }
</style> </style>