非dicom阅片更改

uat
caiyiling 2025-03-18 15:26:51 +08:00
parent bfe2f1dbfb
commit 121bf497bb
3 changed files with 232 additions and 26 deletions

View File

@ -63,6 +63,14 @@
> >
<svg-icon icon-class="polygon" class="svg-icon" /> <svg-icon icon-class="polygon" class="svg-icon" />
</div> </div>
<!-- <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'SplineROITool' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:SplineROITool')"
@click.prevent="setAnnotateToolActive('SplineROITool')"
>
<svg-icon icon-class="polygon" class="svg-icon" />
</div> -->
<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')"
@ -70,13 +78,13 @@
> >
<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 === 'Length' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:length')" :title="$t('trials:dicom-show:length')"
@click.prevent="setAnnotateToolActive('Length')" @click.prevent="setAnnotateToolActive('Length')"
> >
<svg-icon icon-class="length" class="svg-icon" /> <svg-icon icon-class="length" class="svg-icon" />
</div> --> </div>
<!-- 截图 --> <!-- 截图 -->
<!-- <div <!-- <div
class="tool-item" class="tool-item"
@ -242,6 +250,7 @@ const {
ArrowAnnotateTool, ArrowAnnotateTool,
RectangleROITool, RectangleROITool,
PlanarFreehandROITool, PlanarFreehandROITool,
SplineROITool,
EraserTool, EraserTool,
LengthTool LengthTool
// cursors // cursors
@ -255,6 +264,12 @@ export default {
default() { default() {
return {} return {}
} }
},
psArr: {
type: Array,
default() {
return []
}
} }
}, },
data() { data() {
@ -272,7 +287,8 @@ export default {
activeTool: '', activeTool: '',
readingTaskState: 2, readingTaskState: 2,
renderHistoryAnnotationTaskIds: [], renderHistoryAnnotationTaskIds: [],
imageType: ['image/jpeg', 'image/jpg', 'image/bmp', 'image/png'] imageType: ['image/jpeg', 'image/jpg', 'image/bmp', 'image/png'],
digitPlaces: 2
} }
}, },
computed: { computed: {
@ -344,6 +360,8 @@ export default {
isMove: false, isMove: false,
height: 0 height: 0
})) }))
let digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.initLoader() this.initLoader()
window.addEventListener('message', this.handleIframeMessage) window.addEventListener('message', this.handleIframeMessage)
}, },
@ -380,6 +398,7 @@ export default {
element.oncontextmenu = (e) => e.preventDefault() element.oncontextmenu = (e) => e.preventDefault()
resizeObserver.observe(element) resizeObserver.observe(element)
element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage) element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
}) })
const viewportInputArray = [ const viewportInputArray = [
{ {
@ -413,6 +432,7 @@ export default {
cornerstoneTools.addTool(ArrowAnnotateTool) cornerstoneTools.addTool(ArrowAnnotateTool)
cornerstoneTools.addTool(RectangleROITool) cornerstoneTools.addTool(RectangleROITool)
cornerstoneTools.addTool(PlanarFreehandROITool) cornerstoneTools.addTool(PlanarFreehandROITool)
cornerstoneTools.addTool(SplineROITool)
cornerstoneTools.addTool(EraserTool) cornerstoneTools.addTool(EraserTool)
cornerstoneTools.addTool(LengthTool) cornerstoneTools.addTool(LengthTool)
@ -434,14 +454,25 @@ export default {
return doneChangingTextCallback(await this.customPrompt()) return doneChangingTextCallback(await this.customPrompt())
} }
}) })
toolGroup.addTool(RectangleROITool.toolName) toolGroup.addTool(RectangleROITool.toolName, {
cachedStats: false,
getTextLines: this.getRectangleROIToolTextLines
})
toolGroup.addTool(PlanarFreehandROITool.toolName, { toolGroup.addTool(PlanarFreehandROITool.toolName, {
allowOpenContours: false, allowOpenContours: false,
cachedStats: false cachedStats: false,
getTextLines: this.getPlanarFreehandROIToolTextLines
}) })
// const splineConfig = toolGroup.getToolConfiguration(
// splineToolName,
// 'spline'
// )
toolGroup.addTool(SplineROITool.toolName)
toolGroup.addTool(EraserTool.toolName) toolGroup.addTool(EraserTool.toolName)
toolGroup.addTool(LengthTool.toolName, { toolGroup.addTool(LengthTool.toolName, {
getTextLines: this.getLengthToolTextLines getTextLines: this.getLengthToolTextLines,
cachedStats: false
}) })
toolGroup.setToolActive(StackScrollTool.toolName, { toolGroup.setToolActive(StackScrollTool.toolName, {
@ -454,11 +485,13 @@ export default {
toolGroup.setToolPassive(ArrowAnnotateTool.toolName) toolGroup.setToolPassive(ArrowAnnotateTool.toolName)
toolGroup.setToolPassive(RectangleROITool.toolName) toolGroup.setToolPassive(RectangleROITool.toolName)
toolGroup.setToolPassive(PlanarFreehandROITool.toolName) toolGroup.setToolPassive(PlanarFreehandROITool.toolName)
toolGroup.setToolPassive(SplineROITool.toolName)
toolGroup.setToolPassive(LengthTool.toolName) toolGroup.setToolPassive(LengthTool.toolName)
} else { } else {
toolGroup.setToolEnabled(ArrowAnnotateTool.toolName) toolGroup.setToolEnabled(ArrowAnnotateTool.toolName)
toolGroup.setToolEnabled(RectangleROITool.toolName) toolGroup.setToolEnabled(RectangleROITool.toolName)
toolGroup.setToolEnabled(PlanarFreehandROITool.toolName) toolGroup.setToolEnabled(PlanarFreehandROITool.toolName)
toolGroup.setToolEnabled(SplineROITool.toolName)
toolGroup.setToolEnabled(LengthTool.toolName) toolGroup.setToolEnabled(LengthTool.toolName)
} }
toolGroup.setToolPassive(EraserTool.toolName) toolGroup.setToolPassive(EraserTool.toolName)
@ -482,18 +515,38 @@ export default {
renderHistoryAnnotations(obj) { renderHistoryAnnotations(obj) {
if (this.renderHistoryAnnotationTaskIds.includes(obj.VisitTaskId)) return if (this.renderHistoryAnnotationTaskIds.includes(obj.VisitTaskId)) return
this.renderHistoryAnnotationTaskIds.push(obj.VisitTaskId) this.renderHistoryAnnotationTaskIds.push(obj.VisitTaskId)
const arr = [] // let arr = []
obj.Annotations.map(i => { obj.Annotations.map(i => {
if (typeof i.MeasureData === 'string') { // if (typeof i.MeasureData === 'string') {
const annotation = JSON.parse(i.MeasureData) // const annotation = JSON.parse(i.MeasureData)
const annotation = i.MeasureData
annotation.annotationId = i.Id annotation.annotationId = i.Id
arr.push(annotation) // if (annotation.metadata.toolName === 'Length') {
// let annotations = obj.Annotations.filter(item=>{
// const annotation = JSON.parse(item.MeasureData)
// item.metadata.referencedImageId === annotation.metadata.referencedImageId
// })
// annotations.map(i=>{
// return {
// i,
// 'i.data.ps': annotation.data.handles.ps
// }
// })
// console.log(annotations)
// }
cornerstoneTools.annotation.state.addAnnotation(annotation) cornerstoneTools.annotation.state.addAnnotation(annotation)
if (obj.ReadingTaskState === 2) { if (obj.ReadingTaskState === 2) {
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID) cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
} }
}
}) })
// arr.map(i=>{
// cornerstoneTools.annotation.state.addAnnotation(i)
// if (obj.ReadingTaskState === 2) {
// cornerstoneTools.annotation.locking.setAnnotationLocked(i.annotationUID)
// }
// })
}, },
// //
async renderImage(imageIds, canvasIndex, sliceIndex) { async renderImage(imageIds, canvasIndex, sliceIndex) {
@ -726,6 +779,18 @@ export default {
if (this.activeTool) { if (this.activeTool) {
toolGroup.setToolPassive(this.activeTool) toolGroup.setToolPassive(this.activeTool)
} }
if (toolName === 'Length') {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`)
let imageId = viewport.csImage.imageId
let annotations = cornerstoneTools.annotation.state.getAllAnnotations()
let idx = annotations.findIndex(i=>i.metadata.referencedImageId === imageId && i.metadata.toolName === 'Length')
if (idx > -1) {
this.activeTool = ''
this.$message.warning('当前图像已标注比例尺!')
return
}
}
toolGroup.setToolActive(toolName, { toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }] bindings: [{ mouseButton: MouseBindings.Primary }]
}) })
@ -772,11 +837,20 @@ export default {
if (!annotation) return if (!annotation) return
if (annotation.annotationId) { if (annotation.annotationId) {
await deleteTrialFileType(annotation.annotationId) await deleteTrialFileType(annotation.annotationId)
} else {
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
} }
const renderingEngine = getRenderingEngine(renderingEngineId) const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`) const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`)
viewport.render() viewport.render()
// const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
const imageId = annotation.metadata.referencedImageId
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') {
this.$emit('setPS', {NoneDicomFileId: fileList[fileIndex].Id,Path: fileList[fileIndex].Path, PS: null})
}
}, },
async annotationModifiedListener(e) { async annotationModifiedListener(e) {
console.log('Modified') console.log('Modified')
@ -793,6 +867,15 @@ 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') {
const value = annotation.data.l
if (value) {
let cachedStats = Object.keys(annotation.data.cachedStats)
let ps = value / annotation.data.cachedStats[cachedStats[0]].length
annotation.data.ps = ps
this.$emit('setPS', {NoneDicomFileId: fileList[fileIndex].Id,Path: fileList[fileIndex].Path, PS: ps})
}
}
const params = { const params = {
id: annotation.annotationId, id: annotation.annotationId,
visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId, visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId,
@ -820,16 +903,29 @@ export default {
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 === 'Length') {
const { value } = await this.$prompt('请录入录入物理长度mm') const value = await this.$prompt('请录入物理长度mm', '', {
annotation.data.handles.scale = value showClose: false,
if (!value) { showCancelButton: false,
// closeOnClickModal: false,
cornerstoneTools.annotation.state.addAnnotation(annotation.annotationUID) inputPattern: /^\d+$/,
const renderingEngine = getRenderingEngine(renderingEngineId) inputErrorMessage: '请输入数值'
const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`) }).then(({ value }) => {
viewport.render() return value
}).catch(() => {
return null
})
if (value) {
annotation.data.l = parseFloat(value)
let cachedStats = Object.keys(annotation.data.cachedStats)
let ps = parseFloat(value) / annotation.data.cachedStats[cachedStats[0]].length
annotation.data.ps = ps
this.$emit('setPS', {NoneDicomFileId: fileList[fileIndex].Id,Path: fileList[fileIndex].Path, PS: ps})
} else {
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
return
} }
} }
const params = { const params = {
id: '', id: '',
visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId, visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId,
@ -841,8 +937,14 @@ export default {
const res = await addNoneDicomMark(params) const res = await addNoneDicomMark(params)
annotation.annotationId = res.Result annotation.annotationId = res.Result
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`)
viewport.render()
// //
// this.$message.success(this.$t('common:message:savedSuccessfully')) // this.$message.success(this.$t('common:message:savedSuccessfully'))
},
calculateCachedStats(imageId) {
}, },
getLengthToolTextLines(data, targetId) { getLengthToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId] const cachedVolumeStats = data.cachedStats[targetId]
@ -850,12 +952,96 @@ export default {
if (length === undefined || length === null || isNaN(length)) { if (length === undefined || length === null || isNaN(length)) {
return return
} }
if (data.handles.scale === undefined || data.handles.scale === null || isNaN(data.handles.scale)) { if (data.l === undefined || data.l === null || isNaN(data.l)) {
return return
} }
const textLines = [] const textLines = []
textLines.push(`${csUtils.roundNumber(length)} ${unit}`) textLines.push(`P: ${parseFloat(length).toFixed(this.digitPlaces)} ${unit}`)
textLines.push(`${csUtils.roundNumber(data.handles.scale)} mm`) if (data.l) {
textLines.push(`L: ${parseFloat(data.l).toFixed(this.digitPlaces)} mm`)
textLines.push(`PS: ${parseFloat(data.l / length).toFixed(this.digitPlaces)} mm/px`)
}
return textLines;
},
getPlanarFreehandROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId];
const {
area,
mean,
stdDev,
length,
perimeter,
max,
isEmptyArea,
unit,
areaUnit,
modalityUnit,
} = cachedVolumeStats || {};
const textLines = [];
let ps = null
let path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
let i = this.psArr.findIndex(i=>i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${ps ? parseFloat(area * ps).toFixed(this.digitPlaces) : parseFloat(area).toFixed(this.digitPlaces)} ${ps ? 'mm' + '\xb2' : areaUnit}`;
textLines.push(areaLine);
}
if (mean) {
textLines.push(`Mean: ${parseFloat(mean).toFixed(this.digitPlaces)} ${modalityUnit}`);
}
if (Number.isFinite(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}
if (stdDev) {
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
}
if (perimeter) {
if (ps) {
textLines.push(`Perimeter: ${ parseFloat(perimeter * ps).toFixed(this.digitPlaces) } mm`);
} else {
textLines.push(`Perimeter: ${ parseFloat(perimeter).toFixed(this.digitPlaces) } ${unit}`);
}
}
// if (length) {
// // No need to show length prefix as there is just the single value
// textLines.push(`${csUtils.roundNumber(length)} ${unit}`);
// }
return textLines;
},
getRectangleROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId];
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats;
if (mean === undefined) {
return;
}
const textLines = [];
let ps = null
let path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
let 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: ${parseFloat(area * ps).toFixed(this.digitPlaces)} ${'mm' + '\xb2'}`);
} else {
textLines.push(`Area: ${parseFloat(area).toFixed(this.digitPlaces)} ${areaUnit}`);
}
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
return textLines; return textLines;
}, },

View File

@ -305,7 +305,7 @@ export default {
imgVisible: false, imgVisible: false,
imageUrl: '', imageUrl: '',
urls: [], urls: [],
digitPlaces: null digitPlaces: 2
} }
}, },
watch: { watch: {

View File

@ -51,10 +51,12 @@
<file-viewer <file-viewer
ref="fileViewer" ref="fileViewer"
:related-study-info="relatedStudyInfo" :related-study-info="relatedStudyInfo"
:psArr="psArr"
@toggleTaskByViewer="toggleTaskByViewer" @toggleTaskByViewer="toggleTaskByViewer"
@toggleTask="toggleTask" @toggleTask="toggleTask"
@toggleImage="toggleImage" @toggleImage="toggleImage"
@previewCD="previewCD" @previewCD="previewCD"
@setPS="setPS"
/> />
</div> </div>
<!-- 表单 --> <!-- 表单 -->
@ -152,7 +154,8 @@ export default {
clinicalDataVisible: false, clinicalDataVisible: false,
isClinicalDataFullscreen: false, isClinicalDataFullscreen: false,
trialId: '', trialId: '',
cdVisitTaskId: '' cdVisitTaskId: '',
psArr: []
} }
}, },
computed: { computed: {
@ -249,7 +252,16 @@ export default {
visitTaskId: visitTaskId visitTaskId: visitTaskId
} }
const res = await getNoneDicomMarkListOutDto(params) const res = await getNoneDicomMarkListOutDto(params)
this.$set(this.visitTaskList[visitTaskIdx], 'Annotations', res.Result.NoneDicomMarkList) let arr = res.Result.NoneDicomMarkList.map(i=>{
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) {
this.psArr.push({NoneDicomFileId: i.NoneDicomFileId, Path: i.Path, PS: i.MeasureData.data.ps})
}
return i
})
this.$set(this.visitTaskList[visitTaskIdx], 'Annotations', arr)
this.loading = false this.loading = false
resolve() resolve()
} catch (e) { } catch (e) {
@ -259,6 +271,14 @@ export default {
} }
}) })
}, },
setPS(obj) {
let i = this.psArr.findIndex(p=>p.NoneDicomFileId === obj.NoneDicomFileId)
if (i > -1) {
this.psArr[i].PS = obj.PS
} else {
this.psArr.push(obj)
}
},
// //
toggleTask(taskInfo) { toggleTask(taskInfo) {
this.setActiveTaskVisitId(taskInfo.VisitTaskId) this.setActiveTaskVisitId(taskInfo.VisitTaskId)