自定义阅片完成后,后续可继续在阅片页面添加临时标注

uat_us
caiyiling 2024-07-01 16:19:55 +08:00
parent 8f8c5d0744
commit f9d81aa726
7 changed files with 1235 additions and 718 deletions

View File

@ -499,7 +499,7 @@ const actions = {
// var uuid = obj.measureData.data.uuid // var uuid = obj.measureData.data.uuid
// var idx = measureData.findIndex(item => item.MeasureData && item.MeasureData.data && item.MeasureData.data.uuid === 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) var idx = measureData.findIndex(item => item.Id === obj.questionInfo.Id)
console.log('idx', idx) console.log('idx', idx)
if (idx > -1) { if (idx > -1) {
@ -510,7 +510,9 @@ const actions = {
measureData.splice(idx, 1) measureData.splice(idx, 1)
console.log('移除标记成功', idx) console.log('移除标记成功', idx)
} }
if (obj.questionInfo.Id) {
await deleteCustomTag(obj.questionInfo.Id) await deleteCustomTag(obj.questionInfo.Id)
}
state.visitTaskList[index].MeasureData = measureData state.visitTaskList[index].MeasureData = measureData
} else if (obj.orderMarkName) { } else if (obj.orderMarkName) {
const i = measureData.findIndex(item => item.QuestionId === obj.questionId && item.OrderMarkName === obj.orderMarkName) const i = measureData.findIndex(item => item.QuestionId === obj.questionId && item.OrderMarkName === obj.orderMarkName)
@ -523,7 +525,9 @@ const actions = {
console.log('移除标记成功', i) console.log('移除标记成功', i)
} }
} }
if (obj.questionInfo.Id) {
await deleteCustomTag(obj.questionInfo.Id) await deleteCustomTag(obj.questionInfo.Id)
}
state.visitTaskList[index].MeasureData = measureData state.visitTaskList[index].MeasureData = measureData
} }
// sessionStorage.setItem('visitTaskList', state.visitTaskList.length > 0 ? JSON.stringify(state.visitTaskList) : '') // sessionStorage.setItem('visitTaskList', state.visitTaskList.length > 0 ? JSON.stringify(state.visitTaskList) : '')

View File

@ -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 BidirectionalTool from '@/views/trials/trials-panel/reading/dicoms/tools/Bidirectional/BidirectionalTool'
import ArrowAnnotateTool from '@/views/trials/trials-panel/reading/dicoms/tools/ArrowAnnotate/ArrowAnnotateTool' 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 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 OrientationMarkersTool from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/OrientationMarkersTool'
import ScaleOverlayTool from '@/views/trials/trials-panel/reading/dicoms/tools/ScaleOverlay/ScaleOverlayTool' import ScaleOverlayTool from '@/views/trials/trials-panel/reading/dicoms/tools/ScaleOverlay/ScaleOverlayTool'
import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/getOrientationString' import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/getOrientationString'
@ -257,7 +258,7 @@ export default {
series: '', series: '',
ToolStateManager: null, ToolStateManager: null,
renderedMeasured: [], renderedMeasured: [],
measuredTools: ['Length', 'Bidirectional', 'ArrowAnnotate', 'RectangleRoi'], measuredTools: ['Length', 'Bidirectional', 'ArrowAnnotate', 'RectangleRoi', 'CircleRoi'],
measureData: [], measureData: [],
selectedLesion: null, selectedLesion: null,
activeTool: 0, // 0:enable 1:passive 2:active activeTool: 0, // 0:enable 1:passive 2:active
@ -297,8 +298,8 @@ export default {
], ],
scrollSyncInfo: { offset: 0 }, scrollSyncInfo: { offset: 0 },
hideMeasureArr: [] hideMeasureArr: [],
enabledElement: null
} }
}, },
computed: { computed: {
@ -398,7 +399,8 @@ export default {
cornerstone.updateImage(this.canvas, true) cornerstone.updateImage(this.canvas, true)
}) })
DicomEvent.$on('updateImage', () => { DicomEvent.$on('updateImage', () => {
cornerstone.updateImage(this.canvas) if (!this.canvas) return
this.updateImage()
}) })
// this.canvas.addEventListener('keydown', event => { // this.canvas.addEventListener('keydown', event => {
// event.preventDefault() // event.preventDefault()
@ -541,7 +543,7 @@ export default {
this.mousePosition.y = currentPoints.image.y + 1 this.mousePosition.y = currentPoints.image.y + 1
this.mousePosition.mo = stats.mo this.mousePosition.mo = stats.mo
this.mousePosition.suv = stats.suv this.mousePosition.suv = stats.suv
if (this.isFirstChangeTask && this.pointNearTool(e)) { if (this.pointNearTool(e)) {
e.stopImmediatePropagation() e.stopImmediatePropagation()
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
@ -550,18 +552,14 @@ export default {
mouseUp(e) { mouseUp(e) {
console.log('mouseUp') console.log('mouseUp')
if (this.readingTaskState >= 2) return // if (this.readingTaskState >= 2) return
this.image = e.detail.image this.image = e.detail.image
this.getToolStateInfo(e) this.getToolStateInfo(e)
}, },
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 (this.isFirstChangeTask && pointNearTool) { if (this.activeToolName === 'Length' || this.activeToolName === 'Bidirectional') {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
} else if (this.activeToolName === 'Length' || this.activeToolName === 'Bidirectional' && this.readingTaskState < 2) {
if (!e.detail.image.columnPixelSpacing || !e.detail.image.rowPixelSpacing) { if (!e.detail.image.columnPixelSpacing || !e.detail.image.rowPixelSpacing) {
// '' // ''
this.$confirm(this.$t('trials:reading:warnning:msg56'), '', { this.$confirm(this.$t('trials:reading:warnning:msg56'), '', {
@ -574,6 +572,10 @@ export default {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
} }
} else if (pointNearTool) {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
} }
}, },
pointNearTool(e) { pointNearTool(e) {
@ -600,8 +602,8 @@ export default {
getDisabledMarks(measureDatas) { getDisabledMarks(measureDatas) {
var arr = [] var arr = []
measureDatas.map(i => { measureDatas.map(i => {
if ((i.LesionType === 0 || i.LesionType === 1 || i.LesionType === 7) && i.IsFirstChangeTask) { if (i.Id && this.readingTaskState >= 2) {
arr.push(i.OrderMarkName) arr.push(i.MeasureData.data.remark)
} }
}) })
return arr return arr
@ -882,7 +884,9 @@ export default {
} else if (this.activeTool === 1 && this.readingTaskState < 2) { } else if (this.activeTool === 1 && this.readingTaskState < 2) {
cornerstoneTools.setToolPassiveForElement(element, data.MeasureData.type, { mouseButtonMask: 1 }) cornerstoneTools.setToolPassiveForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
} else { } 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) // console.log('renderMeasuredData', this.stack.frame)
@ -986,15 +990,14 @@ export default {
if (idx > -1) { if (idx > -1) {
console.log('mouseClick') console.log('mouseClick')
DicomEvent.$emit('setCollapseActive', this.measureData[idx]) DicomEvent.$emit('setCollapseActive', this.measureData[idx])
if (this.readingTaskState < 2) {
const measureData = {} const measureData = {}
var markName = this.measureData[idx].OrderMarkName var markName = this.measureData[idx].OrderMarkName
if (this.activeToolName === 'Eraser') { if (this.activeToolName === 'Eraser' && this.disabledMarks.indexOf(markName) === -1) {
var questionInfo = this.measureData[idx] const questionInfo = this.measureData[idx]
this.$emit('moveMeasureData', { measureData, questionInfo }) this.$emit('moveMeasureData', { measureData, questionInfo })
} }
if ((this.disabledMarks.indexOf(markName) === -1 || !this.disabledMarks) && this.activeToolName !== 'Eraser') { 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') 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
@ -1010,7 +1013,7 @@ export default {
measureData.data.active = false measureData.data.active = false
this.$emit('modifyMeasureData', { measureData, questionInfo }) this.$emit('modifyMeasureData', { measureData, questionInfo })
} }
}
break break
} }
} }
@ -1049,11 +1052,7 @@ export default {
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === dicomSeries.visitTaskId) var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === dicomSeries.visitTaskId)
this.stack.visitTaskNum = this.visitTaskList[idx].VisitTaskNum this.stack.visitTaskNum = this.visitTaskList[idx].VisitTaskNum
this.isFirstChangeTask = this.visitTaskList[idx].IsFirstChangeTask this.isFirstChangeTask = this.visitTaskList[idx].IsFirstChangeTask
if (this.isFirstChangeTask) {
this.disabledMarks = this.getDisabledMarks(this.visitTaskList[idx].MeasureData) this.disabledMarks = this.getDisabledMarks(this.visitTaskList[idx].MeasureData)
} else {
this.disabledMarks = []
}
this.maxVistNum = this.visitTaskList[this.visitTaskList.length - 1].VisitTaskNum this.maxVistNum = this.visitTaskList[this.visitTaskList.length - 1].VisitTaskNum
this.minVistNum = this.visitTaskList[0].VisitTaskNum this.minVistNum = this.visitTaskList[0].VisitTaskNum
@ -1114,7 +1113,6 @@ export default {
if (!this.toolState.initialized) { if (!this.toolState.initialized) {
this.toolState.initialized = true this.toolState.initialized = true
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
@ -1134,6 +1132,8 @@ export default {
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') {
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)
} }
@ -1221,7 +1221,7 @@ export default {
if (this.dicomInfo.thick) { if (this.dicomInfo.thick) {
this.dicomInfo.thick = this.dicomInfo.thick.toFixed(2) this.dicomInfo.thick = this.dicomInfo.thick.toFixed(2)
} }
let newImageIdIndex = this.stack.imageIds.findIndex(i=>i===imageId) const newImageIdIndex = this.stack.imageIds.findIndex(i => i === imageId)
if (newImageIdIndex === -1) return if (newImageIdIndex === -1) return
this.stack.currentImageIdIndex = newImageIdIndex this.stack.currentImageIdIndex = newImageIdIndex
this.stack.imageIdIndex = newImageIdIndex this.stack.imageIdIndex = newImageIdIndex
@ -1298,7 +1298,7 @@ export default {
this.setMarkers() this.setMarkers()
}, },
onMeasurementcompleted(e) { onMeasurementcompleted(e) {
if (this.readingTaskState >= 2) return // if (this.readingTaskState >= 2) return
var element = cornerstone.getEnabledElement(this.canvas) var element = cornerstone.getEnabledElement(this.canvas)
var viewport = element.viewport var viewport = element.viewport
@ -1311,7 +1311,7 @@ export default {
var instanceId = imageInfo.instanceId var instanceId = imageInfo.instanceId
var frame = imageInfo.frame var frame = imageInfo.frame
this.stack.frame = !isNaN(parseInt(frame)) ? parseInt(frame) : 0 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 = {} const measureData = {}
measureData.studyId = this.stack.studyId measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId measureData.seriesId = this.stack.seriesId
@ -1326,6 +1326,7 @@ export default {
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.temporary = this.readingTaskState >= 2
this.$emit('setMeasureData', measureData) this.$emit('setMeasureData', measureData)
cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName) cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName)
} else if (e.detail.toolName === 'Bidirectional') { } else if (e.detail.toolName === 'Bidirectional') {
@ -1342,6 +1343,7 @@ export default {
measureData.wc = Math.round(viewport.voi.windowCenter) measureData.wc = Math.round(viewport.voi.windowCenter)
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.temporary = this.readingTaskState >= 2
this.$emit('setMeasureData', measureData) this.$emit('setMeasureData', measureData)
cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName) cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName)
} else if (!e.detail.toolName) { } else if (!e.detail.toolName) {
@ -1422,7 +1424,7 @@ export default {
onMeasurementmodified(e) { onMeasurementmodified(e) {
// //
console.log('modified') console.log('modified')
if (this.readingTaskState >= 2) return // if (this.readingTaskState >= 2) return
const { measurementData, toolType } = e.detail const { measurementData, toolType } = e.detail
var element = cornerstone.getEnabledElement(this.canvas) var element = cornerstone.getEnabledElement(this.canvas)
var viewport = element.viewport var viewport = element.viewport
@ -1466,15 +1468,21 @@ export default {
} }
}, },
updateImage(instanceId) { updateImage(instanceId) {
if (!this.canvas) return
var i = this.visitTaskList.findIndex(i => i.VisitTaskId === this.stack.visitTaskId) var i = this.visitTaskList.findIndex(i => i.VisitTaskId === this.stack.visitTaskId)
this.measureData = this.visitTaskList[i].MeasureData this.measureData = this.visitTaskList[i].MeasureData
const ToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager const ToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager
var element = cornerstone.getEnabledElement(this.canvas) var element = cornerstone.getEnabledElement(this.canvas)
if (!element) return
var { imageId } = element.image var { imageId } = element.image
if (imageId) {
ToolStateManager.clearImageIdToolState(imageId) ToolStateManager.clearImageIdToolState(imageId)
cornerstone.updateImage(element, true) let elements = cornerstone.getEnabledElementsByImageId(imageId)
elements.map(el=>{
cornerstone.updateImage(el.element)
})
}
// cornerstone.updateImage(element)
}, },
toggleSeries(evt, type) { toggleSeries(evt, type) {
evt.stopImmediatePropagation() evt.stopImmediatePropagation()
@ -1756,20 +1764,19 @@ export default {
this.activeToolName = toolName this.activeToolName = toolName
this.$nextTick(() => { this.$nextTick(() => {
// console.log(cornerstoneTools.isToolActiveForElement(this.canvas, 'Bidirectional')) // console.log(cornerstoneTools.isToolActiveForElement(this.canvas, 'Bidirectional'))
if (!cornerstoneTools.isToolActiveForElement(this.canvas, toolName)) {
cornerstoneTools.setToolActiveForElement(this.canvas, toolName, {
mouseButtonMask: 1
})
}
if (toolName === 'Zoom') { if (toolName === 'Zoom') {
cornerstoneTools.setToolActiveForElement(this.canvas, 'Zoom', { cornerstoneTools.setToolActiveForElement(this.canvas, 'Zoom', {
mouseButtonMask: [1, 2] mouseButtonMask: [1, 2]
}) })
} } else if (toolName === 'Pan') {
if (toolName === 'Pan') {
cornerstoneTools.setToolActiveForElement(this.canvas, 'Pan', { cornerstoneTools.setToolActiveForElement(this.canvas, 'Pan', {
mouseButtonMask: [1, 4] mouseButtonMask: [1, 4]
}) })
} else {
cornerstoneTools.setToolActiveForElement(this.canvas, toolName, {
mouseButtonMask: 1
})
} }
}) })
}, },

View File

@ -217,9 +217,12 @@ export default {
this.measureData = measureData this.measureData = measureData
}, },
modifyMeasuredData(measureData) { modifyMeasuredData(measureData) {
console.log(measureData)
measureData.visitTaskId = this.visitTaskId measureData.visitTaskId = this.visitTaskId
measureData.measureData.pictureBaseStr = '' measureData.measureData.pictureBaseStr = ''
if (this.readingTaskState >=2) {
this.measuredDataVisible = false
store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData })
}else{
let params = JSON.parse(JSON.stringify(measureData)) let params = JSON.parse(JSON.stringify(measureData))
params.measureData = JSON.stringify(measureData.measureData) params.measureData = JSON.stringify(measureData.measureData)
params.Id = params.questionInfo.Id params.Id = params.questionInfo.Id
@ -229,7 +232,10 @@ export default {
submitCustomTag(params).then(() => { submitCustomTag(params).then(() => {
this.measuredDataVisible = false this.measuredDataVisible = false
DicomEvent.$emit('updateImage') DicomEvent.$emit('updateImage')
// this.$message.success(this.$t('common:message:savedSuccessfully'))
}) })
}
}, },
async moveMeasureData(measureData) { async moveMeasureData(measureData) {
console.log('measureData', measureData) console.log('measureData', measureData)
@ -244,6 +250,13 @@ export default {
if (!valid) return if (!valid) return
this.measureData.data.remark = this.form.measuredDataName this.measureData.data.remark = this.form.measuredDataName
this.measureData.pictureBaseStr = '' this.measureData.pictureBaseStr = ''
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)) let params = JSON.parse(JSON.stringify(this.measureData))
params.measureData = JSON.stringify(this.measureData) params.measureData = JSON.stringify(this.measureData)
submitCustomTag(params).then(async (res) => { submitCustomTag(params).then(async (res) => {
@ -253,7 +266,9 @@ export default {
await store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData }) await store.dispatch('reading/addCustomizeMeasuredData', { visitTaskId: this.visitTaskId, ...this.measureData })
await store.dispatch('reading/getCustomizeMeasuredData', this.visitTaskId) await store.dispatch('reading/getCustomizeMeasuredData', this.visitTaskId)
DicomEvent.$emit('updateImage') DicomEvent.$emit('updateImage')
this.$message.success(this.$t('common:message:savedSuccessfully'))
}) })
}
}) })
}, },
// //

View File

@ -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
// }
// }

View File

@ -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
}
}