自定义阅片完成后,后续可继续在阅片页面添加临时标注
parent
8f8c5d0744
commit
f9d81aa726
|
@ -499,7 +499,7 @@ const actions = {
|
|||
|
||||
// var uuid = obj.measureData.data.uuid
|
||||
// var idx = measureData.findIndex(item => item.MeasureData && item.MeasureData.data && item.MeasureData.data.uuid === uuid)
|
||||
console.log(obj, measureData)
|
||||
// console.log(obj, measureData)
|
||||
var idx = measureData.findIndex(item => item.Id === obj.questionInfo.Id)
|
||||
console.log('idx', idx)
|
||||
if (idx > -1) {
|
||||
|
@ -510,7 +510,9 @@ const actions = {
|
|||
measureData.splice(idx, 1)
|
||||
console.log('移除标记成功', idx)
|
||||
}
|
||||
await deleteCustomTag(obj.questionInfo.Id)
|
||||
if (obj.questionInfo.Id) {
|
||||
await deleteCustomTag(obj.questionInfo.Id)
|
||||
}
|
||||
state.visitTaskList[index].MeasureData = measureData
|
||||
} else if (obj.orderMarkName) {
|
||||
const i = measureData.findIndex(item => item.QuestionId === obj.questionId && item.OrderMarkName === obj.orderMarkName)
|
||||
|
@ -523,7 +525,9 @@ const actions = {
|
|||
console.log('移除标记成功', i)
|
||||
}
|
||||
}
|
||||
await deleteCustomTag(obj.questionInfo.Id)
|
||||
if (obj.questionInfo.Id) {
|
||||
await deleteCustomTag(obj.questionInfo.Id)
|
||||
}
|
||||
state.visitTaskList[index].MeasureData = measureData
|
||||
}
|
||||
// sessionStorage.setItem('visitTaskList', state.visitTaskList.length > 0 ? JSON.stringify(state.visitTaskList) : '')
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<!-- 临床数据 -->
|
||||
<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">
|
||||
<svg-icon icon-class="documentation" class="svg-icon"/>
|
||||
<svg-icon icon-class="documentation" class="svg-icon" />
|
||||
</el-tooltip>
|
||||
|
||||
</div>
|
||||
|
@ -146,6 +146,7 @@ import LengthTool from '@/views/trials/trials-panel/reading/dicoms/tools/Length/
|
|||
import BidirectionalTool from '@/views/trials/trials-panel/reading/dicoms/tools/Bidirectional/BidirectionalTool'
|
||||
import ArrowAnnotateTool from '@/views/trials/trials-panel/reading/dicoms/tools/ArrowAnnotate/ArrowAnnotateTool'
|
||||
import RectangleRoiTool from '@/views/trials/trials-panel/reading/dicoms/tools/RectangleRoi/RectangleRoiTool'
|
||||
import CircleRoiTool from '@/views/trials/trials-panel/reading/dicoms/tools/CircleRoi/CircleRoiTool'
|
||||
// import OrientationMarkersTool from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/OrientationMarkersTool'
|
||||
import ScaleOverlayTool from '@/views/trials/trials-panel/reading/dicoms/tools/ScaleOverlay/ScaleOverlayTool'
|
||||
import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/getOrientationString'
|
||||
|
@ -257,7 +258,7 @@ export default {
|
|||
series: '',
|
||||
ToolStateManager: null,
|
||||
renderedMeasured: [],
|
||||
measuredTools: ['Length', 'Bidirectional', 'ArrowAnnotate', 'RectangleRoi'],
|
||||
measuredTools: ['Length', 'Bidirectional', 'ArrowAnnotate', 'RectangleRoi', 'CircleRoi'],
|
||||
measureData: [],
|
||||
selectedLesion: null,
|
||||
activeTool: 0, // 0:enable 1:passive 2:active
|
||||
|
@ -297,8 +298,8 @@ export default {
|
|||
|
||||
],
|
||||
scrollSyncInfo: { offset: 0 },
|
||||
hideMeasureArr: []
|
||||
|
||||
hideMeasureArr: [],
|
||||
enabledElement: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -398,7 +399,8 @@ export default {
|
|||
cornerstone.updateImage(this.canvas, true)
|
||||
})
|
||||
DicomEvent.$on('updateImage', () => {
|
||||
cornerstone.updateImage(this.canvas)
|
||||
if (!this.canvas) return
|
||||
this.updateImage()
|
||||
})
|
||||
// this.canvas.addEventListener('keydown', event => {
|
||||
// event.preventDefault()
|
||||
|
@ -541,7 +543,7 @@ export default {
|
|||
this.mousePosition.y = currentPoints.image.y + 1
|
||||
this.mousePosition.mo = stats.mo
|
||||
this.mousePosition.suv = stats.suv
|
||||
if (this.isFirstChangeTask && this.pointNearTool(e)) {
|
||||
if (this.pointNearTool(e)) {
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
@ -550,18 +552,14 @@ export default {
|
|||
|
||||
mouseUp(e) {
|
||||
console.log('mouseUp')
|
||||
if (this.readingTaskState >= 2) return
|
||||
// if (this.readingTaskState >= 2) return
|
||||
this.image = e.detail.image
|
||||
this.getToolStateInfo(e)
|
||||
},
|
||||
mouseDown(e) {
|
||||
this.image = e.detail.image
|
||||
var pointNearTool = this.pointNearTool(e)
|
||||
if (this.isFirstChangeTask && pointNearTool) {
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
} else if (this.activeToolName === 'Length' || this.activeToolName === 'Bidirectional' && this.readingTaskState < 2) {
|
||||
if (this.activeToolName === 'Length' || this.activeToolName === 'Bidirectional') {
|
||||
if (!e.detail.image.columnPixelSpacing || !e.detail.image.rowPixelSpacing) {
|
||||
// '该影像不具备测量长度所需的必要数据,不能进行长度测量。请选择其他工具进行标注。'
|
||||
this.$confirm(this.$t('trials:reading:warnning:msg56'), '', {
|
||||
|
@ -574,6 +572,10 @@ export default {
|
|||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
} else if (pointNearTool) {
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
pointNearTool(e) {
|
||||
|
@ -600,8 +602,8 @@ export default {
|
|||
getDisabledMarks(measureDatas) {
|
||||
var arr = []
|
||||
measureDatas.map(i => {
|
||||
if ((i.LesionType === 0 || i.LesionType === 1 || i.LesionType === 7) && i.IsFirstChangeTask) {
|
||||
arr.push(i.OrderMarkName)
|
||||
if (i.Id && this.readingTaskState >= 2) {
|
||||
arr.push(i.MeasureData.data.remark)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
|
@ -882,7 +884,9 @@ export default {
|
|||
} else if (this.activeTool === 1 && this.readingTaskState < 2) {
|
||||
cornerstoneTools.setToolPassiveForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
|
||||
} else {
|
||||
cornerstoneTools.setToolEnabledForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
|
||||
// cornerstoneTools.setToolEnabledForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
|
||||
cornerstoneTools.setToolPassiveForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
|
||||
|
||||
}
|
||||
// console.log('renderMeasuredData', this.stack.frame)
|
||||
|
||||
|
@ -986,15 +990,14 @@ export default {
|
|||
if (idx > -1) {
|
||||
console.log('mouseClick')
|
||||
DicomEvent.$emit('setCollapseActive', this.measureData[idx])
|
||||
if (this.readingTaskState < 2) {
|
||||
const measureData = {}
|
||||
var markName = this.measureData[idx].OrderMarkName
|
||||
if (this.activeToolName === 'Eraser') {
|
||||
var questionInfo = this.measureData[idx]
|
||||
if (this.activeToolName === 'Eraser' && this.disabledMarks.indexOf(markName) === -1) {
|
||||
const questionInfo = this.measureData[idx]
|
||||
this.$emit('moveMeasureData', { measureData, questionInfo })
|
||||
}
|
||||
if ((this.disabledMarks.indexOf(markName) === -1 || !this.disabledMarks) && this.activeToolName !== 'Eraser') {
|
||||
var questionInfo = this.measureData[idx]
|
||||
const questionInfo = this.measureData[idx]
|
||||
const canvas = this.canvas.querySelector('canvas')
|
||||
measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
|
||||
measureData.studyId = this.stack.studyId
|
||||
|
@ -1010,7 +1013,7 @@ export default {
|
|||
measureData.data.active = false
|
||||
this.$emit('modifyMeasureData', { measureData, questionInfo })
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -1049,11 +1052,7 @@ export default {
|
|||
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === dicomSeries.visitTaskId)
|
||||
this.stack.visitTaskNum = this.visitTaskList[idx].VisitTaskNum
|
||||
this.isFirstChangeTask = this.visitTaskList[idx].IsFirstChangeTask
|
||||
if (this.isFirstChangeTask) {
|
||||
this.disabledMarks = this.getDisabledMarks(this.visitTaskList[idx].MeasureData)
|
||||
} else {
|
||||
this.disabledMarks = []
|
||||
}
|
||||
this.disabledMarks = this.getDisabledMarks(this.visitTaskList[idx].MeasureData)
|
||||
|
||||
this.maxVistNum = this.visitTaskList[this.visitTaskList.length - 1].VisitTaskNum
|
||||
this.minVistNum = this.visitTaskList[0].VisitTaskNum
|
||||
|
@ -1114,7 +1113,6 @@ export default {
|
|||
if (!this.toolState.initialized) {
|
||||
this.toolState.initialized = true
|
||||
const toolButtons = document.querySelectorAll('[data-tool]')
|
||||
|
||||
// const scope = this
|
||||
Array.from(toolButtons).forEach((toolBtn) => {
|
||||
// Add the tool
|
||||
|
@ -1134,6 +1132,8 @@ export default {
|
|||
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }})
|
||||
} else if (toolName === 'RectangleRoi') {
|
||||
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }})
|
||||
} else if (toolName === 'CircleRoi') {
|
||||
cornerstoneTools.addToolForElement(element, CircleRoiTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true, showMinMax: true }})
|
||||
} else {
|
||||
cornerstoneTools.addToolForElement(element, apiTool)
|
||||
}
|
||||
|
@ -1221,8 +1221,8 @@ export default {
|
|||
if (this.dicomInfo.thick) {
|
||||
this.dicomInfo.thick = this.dicomInfo.thick.toFixed(2)
|
||||
}
|
||||
let newImageIdIndex = this.stack.imageIds.findIndex(i=>i===imageId)
|
||||
if(newImageIdIndex === -1) return
|
||||
const newImageIdIndex = this.stack.imageIds.findIndex(i => i === imageId)
|
||||
if (newImageIdIndex === -1) return
|
||||
this.stack.currentImageIdIndex = newImageIdIndex
|
||||
this.stack.imageIdIndex = newImageIdIndex
|
||||
this.series.imageIdIndex = newImageIdIndex
|
||||
|
@ -1298,7 +1298,7 @@ export default {
|
|||
this.setMarkers()
|
||||
},
|
||||
onMeasurementcompleted(e) {
|
||||
if (this.readingTaskState >= 2) return
|
||||
// if (this.readingTaskState >= 2) return
|
||||
|
||||
var element = cornerstone.getEnabledElement(this.canvas)
|
||||
var viewport = element.viewport
|
||||
|
@ -1311,7 +1311,7 @@ export default {
|
|||
var instanceId = imageInfo.instanceId
|
||||
var frame = imageInfo.frame
|
||||
this.stack.frame = !isNaN(parseInt(frame)) ? parseInt(frame) : 0
|
||||
if (e.detail.toolName === 'Length' || e.detail.toolName === 'ArrowAnnotate' || e.detail.toolName === 'RectangleRoi') {
|
||||
if (e.detail.toolName === 'Length' || e.detail.toolName === 'ArrowAnnotate' || e.detail.toolName === 'RectangleRoi' || e.detail.toolName === 'CircleRoi') {
|
||||
const measureData = {}
|
||||
measureData.studyId = this.stack.studyId
|
||||
measureData.seriesId = this.stack.seriesId
|
||||
|
@ -1326,6 +1326,7 @@ export default {
|
|||
|
||||
const canvas = this.canvas.querySelector('canvas')
|
||||
measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
|
||||
measureData.temporary = this.readingTaskState >= 2
|
||||
this.$emit('setMeasureData', measureData)
|
||||
cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName)
|
||||
} else if (e.detail.toolName === 'Bidirectional') {
|
||||
|
@ -1342,6 +1343,7 @@ export default {
|
|||
measureData.wc = Math.round(viewport.voi.windowCenter)
|
||||
const canvas = this.canvas.querySelector('canvas')
|
||||
measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
|
||||
measureData.temporary = this.readingTaskState >= 2
|
||||
this.$emit('setMeasureData', measureData)
|
||||
cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName)
|
||||
} else if (!e.detail.toolName) {
|
||||
|
@ -1422,7 +1424,7 @@ export default {
|
|||
onMeasurementmodified(e) {
|
||||
// 移动
|
||||
console.log('modified')
|
||||
if (this.readingTaskState >= 2) return
|
||||
// if (this.readingTaskState >= 2) return
|
||||
const { measurementData, toolType } = e.detail
|
||||
var element = cornerstone.getEnabledElement(this.canvas)
|
||||
var viewport = element.viewport
|
||||
|
@ -1466,15 +1468,21 @@ export default {
|
|||
}
|
||||
},
|
||||
updateImage(instanceId) {
|
||||
if (!this.canvas) return
|
||||
var i = this.visitTaskList.findIndex(i => i.VisitTaskId === this.stack.visitTaskId)
|
||||
this.measureData = this.visitTaskList[i].MeasureData
|
||||
|
||||
const ToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager
|
||||
|
||||
var element = cornerstone.getEnabledElement(this.canvas)
|
||||
if (!element) return
|
||||
var { imageId } = element.image
|
||||
ToolStateManager.clearImageIdToolState(imageId)
|
||||
cornerstone.updateImage(element, true)
|
||||
if (imageId) {
|
||||
ToolStateManager.clearImageIdToolState(imageId)
|
||||
let elements = cornerstone.getEnabledElementsByImageId(imageId)
|
||||
elements.map(el=>{
|
||||
cornerstone.updateImage(el.element)
|
||||
})
|
||||
}
|
||||
// cornerstone.updateImage(element)
|
||||
},
|
||||
toggleSeries(evt, type) {
|
||||
evt.stopImmediatePropagation()
|
||||
|
@ -1756,20 +1764,19 @@ export default {
|
|||
this.activeToolName = toolName
|
||||
this.$nextTick(() => {
|
||||
// console.log(cornerstoneTools.isToolActiveForElement(this.canvas, 'Bidirectional'))
|
||||
if (!cornerstoneTools.isToolActiveForElement(this.canvas, toolName)) {
|
||||
cornerstoneTools.setToolActiveForElement(this.canvas, toolName, {
|
||||
mouseButtonMask: 1
|
||||
})
|
||||
}
|
||||
|
||||
if (toolName === 'Zoom') {
|
||||
cornerstoneTools.setToolActiveForElement(this.canvas, 'Zoom', {
|
||||
mouseButtonMask: [1, 2]
|
||||
})
|
||||
}
|
||||
if (toolName === 'Pan') {
|
||||
} else if (toolName === 'Pan') {
|
||||
cornerstoneTools.setToolActiveForElement(this.canvas, 'Pan', {
|
||||
mouseButtonMask: [1, 4]
|
||||
})
|
||||
} else {
|
||||
cornerstoneTools.setToolActiveForElement(this.canvas, toolName, {
|
||||
mouseButtonMask: 1
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -217,19 +217,25 @@ export default {
|
|||
this.measureData = measureData
|
||||
},
|
||||
modifyMeasuredData(measureData) {
|
||||
console.log(measureData)
|
||||
measureData.visitTaskId = this.visitTaskId
|
||||
measureData.measureData.pictureBaseStr = ''
|
||||
let params = JSON.parse(JSON.stringify(measureData))
|
||||
params.measureData = JSON.stringify(measureData.measureData)
|
||||
params.Id = params.questionInfo.Id
|
||||
params.StudyId = params.questionInfo.StudyId
|
||||
params.SeriesId = params.questionInfo.SeriesId
|
||||
params.InstanceId = params.questionInfo.InstanceId
|
||||
submitCustomTag(params).then(() => {
|
||||
if (this.readingTaskState >=2) {
|
||||
this.measuredDataVisible = false
|
||||
DicomEvent.$emit('updateImage')
|
||||
})
|
||||
store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData })
|
||||
}else{
|
||||
let params = JSON.parse(JSON.stringify(measureData))
|
||||
params.measureData = JSON.stringify(measureData.measureData)
|
||||
params.Id = params.questionInfo.Id
|
||||
params.StudyId = params.questionInfo.StudyId
|
||||
params.SeriesId = params.questionInfo.SeriesId
|
||||
params.InstanceId = params.questionInfo.InstanceId
|
||||
submitCustomTag(params).then(() => {
|
||||
this.measuredDataVisible = false
|
||||
DicomEvent.$emit('updateImage')
|
||||
// this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
async moveMeasureData(measureData) {
|
||||
console.log('measureData', measureData)
|
||||
|
@ -244,16 +250,25 @@ export default {
|
|||
if (!valid) return
|
||||
this.measureData.data.remark = this.form.measuredDataName
|
||||
this.measureData.pictureBaseStr = ''
|
||||
let params = JSON.parse(JSON.stringify(this.measureData))
|
||||
params.measureData = JSON.stringify(this.measureData)
|
||||
submitCustomTag(params).then(async (res) => {
|
||||
this.measuredDataVisible = false
|
||||
this.form.measuredDataName = ''
|
||||
this.measureData.Id = res.Result
|
||||
await store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData })
|
||||
await store.dispatch('reading/getCustomizeMeasuredData', this.visitTaskId)
|
||||
DicomEvent.$emit('updateImage')
|
||||
})
|
||||
if (this.readingTaskState >=2) {
|
||||
this.measuredDataVisible = false
|
||||
this.form.measuredDataName = ''
|
||||
store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData })
|
||||
DicomEvent.$emit('updateImage')
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
} else {
|
||||
let params = JSON.parse(JSON.stringify(this.measureData))
|
||||
params.measureData = JSON.stringify(this.measureData)
|
||||
submitCustomTag(params).then(async (res) => {
|
||||
this.measuredDataVisible = false
|
||||
this.form.measuredDataName = ''
|
||||
this.measureData.Id = res.Result
|
||||
await store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData })
|
||||
await store.dispatch('reading/getCustomizeMeasuredData', this.visitTaskId)
|
||||
DicomEvent.$emit('updateImage')
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 关闭签名框
|
||||
|
|
|
@ -0,0 +1,453 @@
|
|||
import * as cornerstoneTools from 'cornerstone-tools'
|
||||
const external = cornerstoneTools.external
|
||||
|
||||
// State
|
||||
const getToolState = cornerstoneTools.getToolState
|
||||
const toolStyle = cornerstoneTools.toolStyle
|
||||
const toolColors = cornerstoneTools.toolColors
|
||||
const getModule = cornerstoneTools.getModule
|
||||
|
||||
// Drawing
|
||||
const getNewContext = cornerstoneTools.import('drawing/getNewContext')
|
||||
const draw = cornerstoneTools.import('drawing/draw')
|
||||
const setShadow = cornerstoneTools.import('drawing/setShadow')
|
||||
const drawCircle = cornerstoneTools.import('drawing/drawCircle')
|
||||
const drawHandles = cornerstoneTools.import('drawing/drawHandles')
|
||||
const drawLinkedTextBox = cornerstoneTools.import('drawing/drawLinkedTextBox')
|
||||
|
||||
// Util
|
||||
// const calculateSUV = cornerstoneTools.import('util/calculateSUV')
|
||||
|
||||
const throttle = cornerstoneTools.import('util/throttle')
|
||||
const getPixelSpacing = cornerstoneTools.import('util/getPixelSpacing')
|
||||
|
||||
const circleRoiCursor = cornerstoneTools.import('tools/cursors')
|
||||
const getROITextBoxCoords = cornerstoneTools.import('util/getROITextBoxCoords')
|
||||
import getCircleCoords from './getCircleCoords'
|
||||
// const getCircleCoords = cornerstoneTools.import('util/getCircleCoords')
|
||||
const numbersWithCommas = cornerstoneTools.import('util/numbersWithCommas')
|
||||
|
||||
// import calculateSUV from './../../util/calculateSUV.js'
|
||||
// import { calculateEllipseStatistics } from './../../util/ellipse/index.js'
|
||||
// import getROITextBoxCoords from '../../util/getROITextBoxCoords.js'
|
||||
// import numbersWithCommas from './../../util/numbersWithCommas.js'
|
||||
// import throttle from './../../util/throttle.js'
|
||||
// import { getLogger } from '../../util/logger.js'
|
||||
// import getPixelSpacing from '../../util/getPixelSpacing'
|
||||
|
||||
// import { circleRoiCursor } from '../cursors/index.js'
|
||||
// import getCircleCoords from '../../util/getCircleCoords'
|
||||
|
||||
// const logger = getLogger('tools:annotation:CircleRoiTool')
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @class CircleRoiTool
|
||||
* @memberof Tools.Annotation
|
||||
* @classdesc Tool for drawing circular regions of interest, and measuring
|
||||
* the statistics of the enclosed pixels.
|
||||
* @extends Tools.Base.BaseAnnotationTool
|
||||
*/
|
||||
export default class CircleRoiTool extends cornerstoneTools.CircleRoiTool {
|
||||
constructor(props = {}) {
|
||||
const defaultProps = {
|
||||
name: 'CircleRoi',
|
||||
supportedInteractionTypes: ['Mouse', 'Touch'],
|
||||
svgCursor: circleRoiCursor,
|
||||
configuration: {
|
||||
centerPointRadius: 0,
|
||||
renderDashed: false,
|
||||
hideHandlesIfMoving: false,
|
||||
digits: 1,
|
||||
showRadius: false,
|
||||
showPerimeter: false
|
||||
}
|
||||
}
|
||||
|
||||
super(props, defaultProps)
|
||||
console.log(props.configuration)
|
||||
// this.digits = isNaN(parseInt(props.configuration.digits)) ? 2 : props.configuration.digits
|
||||
// console.log(this.digits)
|
||||
this.throttledUpdateCachedStats = throttle(this.updateCachedStats, 110)
|
||||
}
|
||||
|
||||
renderToolData(evt) {
|
||||
const toolData = getToolState(evt.currentTarget, this.name)
|
||||
|
||||
if (!toolData) {
|
||||
return
|
||||
}
|
||||
|
||||
const getDistance = external.cornerstoneMath.point.distance
|
||||
const eventData = evt.detail
|
||||
const { image, element, canvasContext } = eventData
|
||||
const lineWidth = toolStyle.getToolWidth()
|
||||
const {
|
||||
handleRadius,
|
||||
drawHandlesOnHover,
|
||||
hideHandlesIfMoving,
|
||||
renderDashed,
|
||||
centerPointRadius
|
||||
} = this.configuration
|
||||
console.log(this.configuration)
|
||||
const newContext = getNewContext(canvasContext.canvas)
|
||||
const { rowPixelSpacing, colPixelSpacing } = getPixelSpacing(image)
|
||||
const lineDash = getModule('globalConfiguration').configuration.lineDash
|
||||
|
||||
// Meta
|
||||
const seriesModule =
|
||||
external.cornerstone.metaData.get('generalSeriesModule', image.imageId) ||
|
||||
{}
|
||||
|
||||
// Pixel Spacing
|
||||
const modality = seriesModule.modality
|
||||
const hasPixelSpacing = rowPixelSpacing && colPixelSpacing
|
||||
|
||||
draw(newContext, context => {
|
||||
// If we have tool data for this element, iterate over each set and draw it
|
||||
for (let i = 0; i < toolData.data.length; i++) {
|
||||
const data = toolData.data[i]
|
||||
|
||||
if (data.visible === false) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Configure
|
||||
const color = toolColors.getColorIfActive(data)
|
||||
const handleOptions = {
|
||||
color,
|
||||
handleRadius,
|
||||
drawHandlesIfActive: drawHandlesOnHover,
|
||||
hideHandlesIfMoving
|
||||
}
|
||||
|
||||
setShadow(context, this.configuration)
|
||||
|
||||
const startCanvas = external.cornerstone.pixelToCanvas(
|
||||
element,
|
||||
data.handles.start
|
||||
)
|
||||
|
||||
const endCanvas = external.cornerstone.pixelToCanvas(
|
||||
element,
|
||||
data.handles.end
|
||||
)
|
||||
|
||||
// Calculating the radius where startCanvas is the center of the circle to be drawn
|
||||
const radius = getDistance(startCanvas, endCanvas)
|
||||
|
||||
const circleOptions = { color }
|
||||
|
||||
if (renderDashed) {
|
||||
circleOptions.lineDash = lineDash
|
||||
}
|
||||
|
||||
// Draw Circle
|
||||
drawCircle(
|
||||
context,
|
||||
element,
|
||||
data.handles.start,
|
||||
radius,
|
||||
circleOptions,
|
||||
'pixel'
|
||||
)
|
||||
|
||||
if (centerPointRadius && radius > 3 * centerPointRadius) {
|
||||
drawCircle(
|
||||
context,
|
||||
element,
|
||||
data.handles.start,
|
||||
centerPointRadius,
|
||||
circleOptions,
|
||||
'pixel'
|
||||
)
|
||||
}
|
||||
|
||||
if (data.handles) {
|
||||
data.handles.start.drawnIndependently = true
|
||||
data.handles.end.drawnIndependently = true
|
||||
}
|
||||
|
||||
drawHandles(context, eventData, data.handles, handleOptions)
|
||||
|
||||
// Update textbox stats
|
||||
if (data.invalidated === true) {
|
||||
if (data.cachedStats) {
|
||||
this.throttledUpdateCachedStats(image, element, data)
|
||||
} else {
|
||||
this.updateCachedStats(image, element, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Default to textbox on right side of ROI
|
||||
if (!data.handles.textBox.hasMoved) {
|
||||
const defaultCoords = getROITextBoxCoords(
|
||||
eventData.viewport,
|
||||
data.handles
|
||||
)
|
||||
|
||||
Object.assign(data.handles.textBox, defaultCoords)
|
||||
}
|
||||
|
||||
const textBoxAnchorPoints = handles =>
|
||||
_findTextBoxAnchorPoints(handles.start, handles.end)
|
||||
|
||||
const textBoxContent = _createTextBoxContent(
|
||||
context,
|
||||
image.color,
|
||||
data.cachedStats,
|
||||
modality,
|
||||
hasPixelSpacing,
|
||||
this.configuration
|
||||
)
|
||||
if (data.remark) {
|
||||
textBoxContent.unshift(data.remark)
|
||||
}
|
||||
data.unit = _getUnit(modality, this.configuration.showHounsfieldUnits)
|
||||
|
||||
drawLinkedTextBox(
|
||||
context,
|
||||
element,
|
||||
data.handles.textBox,
|
||||
textBoxContent,
|
||||
data.handles,
|
||||
textBoxAnchorPoints,
|
||||
color,
|
||||
lineWidth,
|
||||
20,
|
||||
true
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} startHandle
|
||||
* @param {*} endHandle
|
||||
* @returns {Array.<{x: number, y: number}>}
|
||||
*/
|
||||
function _findTextBoxAnchorPoints(startHandle, endHandle) {
|
||||
const { left, top, width, height } = getCircleCoords(startHandle, endHandle)
|
||||
|
||||
return [
|
||||
{
|
||||
// Top middle point of ellipse
|
||||
x: left + width / 2,
|
||||
y: top
|
||||
},
|
||||
{
|
||||
// Left middle point of ellipse
|
||||
x: left,
|
||||
y: top + height / 2
|
||||
},
|
||||
{
|
||||
// Bottom middle point of ellipse
|
||||
x: left + width / 2,
|
||||
y: top + height
|
||||
},
|
||||
{
|
||||
// Right middle point of ellipse
|
||||
x: left + width,
|
||||
y: top + height / 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function _getUnit(modality, showHounsfieldUnits) {
|
||||
return modality === 'CT' && showHounsfieldUnits !== false ? 'HU' : ''
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} context
|
||||
* @param {*} isColorImage
|
||||
* @param {*} { area, mean, stdDev, min, max, meanStdDevSUV }
|
||||
* @param {*} modality
|
||||
* @param {*} hasPixelSpacing
|
||||
* @param {*} [options={}] - { showMinMax, showHounsfieldUnits }
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function _createTextBoxContent(
|
||||
context,
|
||||
isColorImage,
|
||||
{
|
||||
area = 0,
|
||||
radius = 0,
|
||||
perimeter = 0,
|
||||
mean = 0,
|
||||
stdDev = 0,
|
||||
min = 0,
|
||||
max = 0,
|
||||
meanStdDevSUV = 0
|
||||
} = {},
|
||||
modality,
|
||||
hasPixelSpacing,
|
||||
options = {}
|
||||
) {
|
||||
const showMinMax = options.showMinMax || false
|
||||
const digits = options.digits || 1
|
||||
const textLines = []
|
||||
|
||||
// Don't display mean/standardDev for color images
|
||||
const otherLines = []
|
||||
|
||||
if (!isColorImage) {
|
||||
const hasStandardUptakeValues = meanStdDevSUV && meanStdDevSUV.mean !== 0
|
||||
const unit = _getUnit(modality, options.showHounsfieldUnits)
|
||||
|
||||
let meanString = `Mean: ${numbersWithCommas(mean.toFixed(digits))} ${unit}`
|
||||
const stdDevString = `Std Dev: ${numbersWithCommas(
|
||||
stdDev.toFixed(digits)
|
||||
)} ${unit}`
|
||||
|
||||
// If this image has SUV values to display, concatenate them to the text line
|
||||
if (hasStandardUptakeValues) {
|
||||
const SUVtext = ' SUV: '
|
||||
|
||||
const meanSuvString = `${SUVtext}${numbersWithCommas(
|
||||
meanStdDevSUV.mean.toFixed(digits)
|
||||
)}`
|
||||
const stdDevSuvString = `${SUVtext}${numbersWithCommas(
|
||||
meanStdDevSUV.stdDev.toFixed(digits)
|
||||
)}`
|
||||
|
||||
const targetStringLength = Math.floor(
|
||||
context.measureText(`${stdDevString} `).width
|
||||
)
|
||||
|
||||
while (context.measureText(meanString).width < targetStringLength) {
|
||||
meanString += ' '
|
||||
}
|
||||
|
||||
otherLines.push(`${meanString}${meanSuvString}`)
|
||||
otherLines.push(`${stdDevString} ${stdDevSuvString}`)
|
||||
} else {
|
||||
// otherLines.push(`${meanString} ${stdDevString}`)
|
||||
otherLines.push(`${meanString}`)
|
||||
otherLines.push(`${stdDevString}`)
|
||||
}
|
||||
|
||||
if (showMinMax) {
|
||||
let minString = `Min: ${min} ${unit}`
|
||||
const maxString = `Max: ${max} ${unit}`
|
||||
const targetStringLength = hasStandardUptakeValues
|
||||
? Math.floor(context.measureText(`${stdDevString} `).width)
|
||||
: Math.floor(context.measureText(`${meanString} `).width)
|
||||
|
||||
while (context.measureText(minString).width < targetStringLength) {
|
||||
minString += ' '
|
||||
}
|
||||
otherLines.push(`${minString}`)
|
||||
otherLines.push(`${maxString}`)
|
||||
// otherLines.push(`${minString}${maxString}`)
|
||||
}
|
||||
}
|
||||
|
||||
textLines.push(_formatArea(area, hasPixelSpacing, digits))
|
||||
const showRadius = options.showRadius || false
|
||||
if (radius && showRadius) {
|
||||
textLines.push(_formatLength(radius, 'Radius', hasPixelSpacing, digits))
|
||||
}
|
||||
const showPerimeter = options.showPerimeter || false
|
||||
if (perimeter && showPerimeter) {
|
||||
textLines.push(_formatLength(perimeter, 'Perimeter', hasPixelSpacing, digits))
|
||||
}
|
||||
otherLines.forEach(x => textLines.push(x))
|
||||
// console.log(this.digits)
|
||||
return textLines
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} area
|
||||
* @param {*} hasPixelSpacing
|
||||
* @returns {string} The formatted label for showing area
|
||||
*/
|
||||
function _formatArea(area, hasPixelSpacing, digits) {
|
||||
// This uses Char code 178 for a superscript 2
|
||||
const suffix = hasPixelSpacing
|
||||
? ` mm${String.fromCharCode(178)}`
|
||||
: ` px${String.fromCharCode(178)}`
|
||||
|
||||
return `Area: ${numbersWithCommas(area.toFixed(digits))}${suffix}`
|
||||
}
|
||||
|
||||
function _formatLength(value, name, hasPixelSpacing, digits) {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
const suffix = hasPixelSpacing ? ' mm' : ' px'
|
||||
|
||||
return `${name}: ${numbersWithCommas(value.toFixed(digits))}${suffix}`
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} image
|
||||
* @param {*} element
|
||||
* @param {*} handles
|
||||
* @param {*} modality
|
||||
* @param {*} pixelSpacing
|
||||
* @returns {Object} The Stats object
|
||||
*/
|
||||
// function _calculateStats(image, element, handles, modality, pixelSpacing) {
|
||||
// // Retrieve the bounds of the ellipse in image coordinates
|
||||
// const circleCoordinates = getCircleCoords(handles.start, handles.end)
|
||||
|
||||
// // Retrieve the array of pixels that the ellipse bounds cover
|
||||
// const pixels = external.cornerstone.getPixels(
|
||||
// element,
|
||||
// circleCoordinates.left,
|
||||
// circleCoordinates.top,
|
||||
// circleCoordinates.width,
|
||||
// circleCoordinates.height
|
||||
// )
|
||||
|
||||
// // Calculate the mean & standard deviation from the pixels and the ellipse details.
|
||||
// const ellipseMeanStdDev = calculateEllipseStatistics(
|
||||
// pixels,
|
||||
// circleCoordinates
|
||||
// )
|
||||
|
||||
// let meanStdDevSUV
|
||||
|
||||
// if (modality === 'PT') {
|
||||
// meanStdDevSUV = {
|
||||
// mean: calculateSUV(image, ellipseMeanStdDev.mean, true) || 0,
|
||||
// stdDev: calculateSUV(image, ellipseMeanStdDev.stdDev, true) || 0
|
||||
// }
|
||||
// }
|
||||
|
||||
// const radius =
|
||||
// (circleCoordinates.width *
|
||||
// ((pixelSpacing && pixelSpacing.colPixelSpacing) || 1)) /
|
||||
// 2
|
||||
// const perimeter = 2 * Math.PI * radius
|
||||
// const area =
|
||||
// Math.PI *
|
||||
// ((circleCoordinates.width *
|
||||
// ((pixelSpacing && pixelSpacing.colPixelSpacing) || 1)) /
|
||||
// 2) *
|
||||
// ((circleCoordinates.height *
|
||||
// ((pixelSpacing && pixelSpacing.rowPixelSpacing) || 1)) /
|
||||
// 2)
|
||||
|
||||
// return {
|
||||
// area: area || 0,
|
||||
// radius: radius || 0,
|
||||
// perimeter: perimeter || 0,
|
||||
// count: ellipseMeanStdDev.count || 0,
|
||||
// mean: ellipseMeanStdDev.mean || 0,
|
||||
// variance: ellipseMeanStdDev.variance || 0,
|
||||
// stdDev: ellipseMeanStdDev.stdDev || 0,
|
||||
// min: ellipseMeanStdDev.min || 0,
|
||||
// max: ellipseMeanStdDev.max || 0,
|
||||
// meanStdDevSUV
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,21 @@
|
|||
import * as cornerstoneTools from 'cornerstone-tools'
|
||||
const external = cornerstoneTools.external
|
||||
|
||||
/**
|
||||
* Retrieve the bounds of the circle in image coordinates
|
||||
*
|
||||
* @param {*} startHandle
|
||||
* @param {*} endHandle
|
||||
* @returns {{ left: number, top: number, width: number, height: number }}
|
||||
*/
|
||||
export default function getCircleCoords(startHandle, endHandle) {
|
||||
const { distance } = external.cornerstoneMath.point
|
||||
const radius = distance(startHandle, endHandle)
|
||||
|
||||
return {
|
||||
left: Math.floor(Math.min(startHandle.x - radius, endHandle.x)),
|
||||
top: Math.floor(Math.min(startHandle.y - radius, endHandle.y)),
|
||||
width: radius * 2,
|
||||
height: radius * 2
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue