diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue index 99b6222d..bc191c69 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue @@ -661,6 +661,7 @@ export default { }, sliderMouseup(e) { this.sliderInfo.isMove = false + this.$emit('contentMouseup', e) }, sliderMousedown(e) { const boxHeight = this.$refs['sliderBox'].clientHeight diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue index c64aa02b..517db540 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue @@ -295,7 +295,7 @@ + @renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup" />
+ @renderAnnotations="renderAnnotations" @upperRangeChange="upperRangeChange" + @contentMouseup="contentMouseup" />
@@ -361,7 +362,8 @@ {{ $t('trials:reading:button:handbooks') }} - @@ -460,6 +462,7 @@ import ClinicalData from '@/views/trials/trials-panel/reading/clinical-data' import FusionForm from './FusionForm.vue' import colorMap from './colorMap.vue' import RectangleROITool from './tools/RectangleROITool' +import ScaleOverlayTool from './tools/ScaleOverlayTool' import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom' import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom' import { getNetWorkSpeed, setNetWorkSpeedSizeAll, workSpeedclose } from "@/utils" @@ -470,7 +473,7 @@ const { ToolGroupManager, Enums: csToolsEnums, StackScrollTool, - ScaleOverlayTool, + // ScaleOverlayTool, PanTool, ZoomTool, WindowLevelTool, @@ -625,7 +628,12 @@ export default { uploadStatus: 'upload', taskId: '', isReadingTaskViewInOrder: null, - trialCriterion: {} + trialCriterion: {}, + + curOperation: { + type: '', + annotation: null + } } }, computed: { @@ -1479,11 +1487,8 @@ export default { if (isBound || this.isNumber(operateStateEnum)) { this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].updateAnnotationToQuestion(annotation) } else { - if (this.saveCustomAnnotationTimer) { - clearTimeout(this.saveCustomAnnotationTimer) - this.saveCustomAnnotationTimer = null - } - this.saveCustomAnnotationTimer = setTimeout(() => { this.saveCustomAnnotation(annotation) }, 1000) + this.curOperation.type = 'Modified' + this.curOperation.annotation = annotation } } this.setToolsPassive() @@ -1553,6 +1558,17 @@ export default { console.log(e) } }, + contentMouseup(e) { + console.log('contentMouseup') + if (this.curOperation.type === 'Modified') { + let annotation = this.curOperation.annotation + this.saveCustomAnnotation(annotation) + } + this.curOperation = { + type: '', + annotation: null + } + }, removeAnnotation(annotation) { cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID) const renderingEngine = getRenderingEngine(renderingEngineId) @@ -3032,21 +3048,25 @@ export default { let volumeId = null; let volume = null const key = isFusion ? `fusion_${serie.SeriesInstanceUid}` : serie.SeriesInstanceUid if (!this.volumeData[key] || !cache.getVolume(this.volumeData[key].volumeId)) { - if (serie.Modality === 'PT' && !isFusion) { - serie.ImageIds.forEach(async id => { - const imageLoadObject = cache.getImage(id) - if (imageLoadObject) { - await new Promise(res => { - cache.removeImageLoadObject(id, { force: true }) // 从缓存中删除 - res() - }) - } - }) + // if (serie.Modality === 'PT' && !isFusion) { + // serie.ImageIds.forEach(async id => { + // const imageLoadObject = cache.getImage(id) + // if (imageLoadObject) { + // await new Promise(res => { + // cache.removeImageLoadObject(id, { force: true }) // 从缓存中删除 + // res() + // }) + // } + // }) + // } + volumeId = `${isFusion ? 'fusion' : serie.Modality}Volume` + ':' + serie.SeriesInstanceUid + if (cache.getVolume(volumeId)) { + volume = cache.getVolume(volumeId) + } else { + await this.$refs[`viewport-fusion-0`][0].createImageIdsAndCacheMetaData(serie) + volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: serie.ImageIds }) + volume.load() } - await this.$refs[`viewport-fusion-0`][0].createImageIdsAndCacheMetaData(serie) - volumeId = `${isFusion ? 'fusion' : serie.Modality}Volume` + ':' + csUtils.uuidv4() - volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: serie.ImageIds }) - volume.load() this.volumeData[key] = {} this.volumeData[key].volumeId = volumeId this.volumeData[key].volume = volume diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/Viewport.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/Viewport.vue index 0a3adcd5..7698146c 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/Viewport.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/Viewport.vue @@ -481,6 +481,7 @@ export default { }, sliderMouseup(e) { this.sliderInfo.isMove = false + this.$emit('contentMouseup', e) }, sliderMousedown(e) { const boxHeight = this.$refs['sliderBox'].clientHeight diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/tools/ScaleOverlayTool.js b/src/views/trials/trials-panel/reading/dicoms3D/components/tools/ScaleOverlayTool.js new file mode 100644 index 00000000..5c1611d9 --- /dev/null +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/tools/ScaleOverlayTool.js @@ -0,0 +1,409 @@ +// import AnnotationDisplayTool from './base/AnnotationDisplayTool'; +import { vec3 } from 'gl-matrix'; +import { getEnabledElementByIds, getRenderingEngines, utilities as csUtils, } from '@cornerstonejs/core'; +// import { addAnnotation, getAnnotations, } from '../stateManagement/annotation/annotationState'; +// import { drawLine as drawLineSvg, drawTextBox as drawTextBoxSvg, } from '../drawingSvg'; +// import { getToolGroup } from '../store/ToolGroupManager'; +import * as cornerstoneTools from '@cornerstonejs/tools' +const { + annotation, + drawing, + AnnotationDisplayTool, + ToolGroupManager, +} = cornerstoneTools +const { getAnnotations, addAnnotation } = annotation.state +const { getToolGroup } = ToolGroupManager +const { drawLine, drawTextBox } = drawing +const drawLineSvg = drawLine +const drawTextBoxSvg = drawTextBox +const viewportsWithAnnotations = []; +class ScaleOverlayTool extends AnnotationDisplayTool { + constructor(toolProps = {}, defaultToolProps = { + configuration: { + viewportId: '', + scaleLocation: 'bottom', + }, + }) { + super(toolProps, defaultToolProps); + this.editData = null; + this._init = () => { + const renderingEngines = getRenderingEngines(); + const renderingEngine = renderingEngines[0]; + if (!renderingEngine) { + return; + } + const viewportIds = getToolGroup(this.toolGroupId).viewportsInfo; + if (!viewportIds) { + return; + } + const enabledElements = viewportIds.map((e) => getEnabledElementByIds(e.viewportId, e.renderingEngineId)); + let { viewport } = enabledElements[0]; + const { FrameOfReferenceUID } = enabledElements[0]; + if (this.configuration.viewportId) { + enabledElements.forEach((element) => { + if (element.viewport.id == this.configuration.viewportId) { + viewport = element.viewport; + } + }); + } + if (!viewport) { + return; + } + const { viewUp, viewPlaneNormal } = viewport.getCamera(); + const viewportCanvasCornersInWorld = csUtils.getViewportImageCornersInWorld(viewport); + let annotation = this.editData?.annotation; + const annotations = getAnnotations(this.getToolName(), viewport.element); + if (annotations.length) { + annotation = annotations.filter((thisAnnotation) => thisAnnotation.data.viewportId == viewport.id)[0]; + } + enabledElements.forEach((element) => { + const { viewport } = element; + if (!viewportsWithAnnotations.includes(viewport.id) || annotations.length === 0) { + const newAnnotation = { + metadata: { + toolName: this.getToolName(), + viewPlaneNormal: [...viewPlaneNormal], + viewUp: [...viewUp], + FrameOfReferenceUID, + referencedImageId: null, + }, + data: { + handles: { + points: csUtils.getViewportImageCornersInWorld(viewport), + }, + viewportId: viewport.id, + }, + }; + viewportsWithAnnotations.push(viewport.id); + addAnnotation(newAnnotation, viewport.element); + annotation = newAnnotation; + } + }); + if (this.editData?.annotation && + this.editData.annotation.data.viewportId == viewport.id) { + this.editData.annotation.data.handles.points = + viewportCanvasCornersInWorld; + this.editData.annotation.data.viewportId = viewport.id; + } + this.editData = { + viewport, + renderingEngine, + annotation, + }; + }; + this.onSetToolEnabled = () => { + this._init(); + }; + this.onCameraModified = (evt) => { + this.configuration.viewportId = evt.detail.viewportId; + this._init(); + }; + this.computeScaleSize = (worldWidthViewport, worldHeightViewport, location) => { + const scaleSizes = [ + 16000, 8000, 4000, 2000, 1000, 500, 250, 100, 50, 25, 10, 5, 2, + ]; + let currentScaleSize; + if (location == 'top' || location == 'bottom') { + currentScaleSize = scaleSizes.filter((scaleSize) => scaleSize < worldWidthViewport * 0.6 && + scaleSize > worldWidthViewport * 0.2); + } + else { + currentScaleSize = scaleSizes.filter((scaleSize) => scaleSize < worldHeightViewport * 0.6 && + scaleSize > worldHeightViewport * 0.2); + } + return currentScaleSize[0]; + }; + this.computeEndScaleTicks = (canvasCoordinates, location) => { + const locationTickOffset = { + bottom: [ + [0, -10], + [0, -10], + ], + top: [ + [0, 10], + [0, 10], + ], + left: [ + [0, 0], + [10, 0], + ], + right: [ + [0, 0], + [-10, 0], + ], + }; + const endTick1 = [ + [ + canvasCoordinates[1][0] + locationTickOffset[location][0][0], + canvasCoordinates[1][1] + locationTickOffset[location][0][0], + ], + [ + canvasCoordinates[1][0] + locationTickOffset[location][1][0], + canvasCoordinates[1][1] + locationTickOffset[location][1][1], + ], + ]; + const endTick2 = [ + [ + canvasCoordinates[0][0] + locationTickOffset[location][0][0], + canvasCoordinates[0][1] + locationTickOffset[location][0][0], + ], + [ + canvasCoordinates[0][0] + locationTickOffset[location][1][0], + canvasCoordinates[0][1] + locationTickOffset[location][1][1], + ], + ]; + return { + endTick1: endTick1, + endTick2: endTick2, + }; + }; + this.computeInnerScaleTicks = (scaleSize, location, annotationUID, leftTick, rightTick) => { + let canvasScaleSize; + if (location == 'bottom' || location == 'top') { + canvasScaleSize = rightTick[0][0] - leftTick[0][0]; + } + else if (location == 'left' || location == 'right') { + canvasScaleSize = rightTick[0][1] - leftTick[0][1]; + } + const tickIds = []; + const tickUIDs = []; + const tickCoordinates = []; + let numberSmallTicks = scaleSize; + if (scaleSize >= 50) { + numberSmallTicks = scaleSize / 10; + } + const tickSpacing = canvasScaleSize / numberSmallTicks; + for (let i = 0; i < numberSmallTicks - 1; i++) { + const locationOffset = { + bottom: [ + [tickSpacing * (i + 1), 0], + [tickSpacing * (i + 1), 5], + ], + top: [ + [tickSpacing * (i + 1), 0], + [tickSpacing * (i + 1), -5], + ], + left: [ + [0, tickSpacing * (i + 1)], + [-5, tickSpacing * (i + 1)], + ], + right: [ + [0, tickSpacing * (i + 1)], + [5, tickSpacing * (i + 1)], + ], + }; + tickIds.push(`${annotationUID}-tick${i}`); + tickUIDs.push(`tick${i}`); + if ((i + 1) % 5 == 0) { + tickCoordinates.push([ + [ + leftTick[0][0] + locationOffset[location][0][0], + leftTick[0][1] + locationOffset[location][0][1], + ], + [ + leftTick[1][0] + locationOffset[location][0][0], + leftTick[1][1] + locationOffset[location][0][1], + ], + ]); + } + else { + tickCoordinates.push([ + [ + leftTick[0][0] + locationOffset[location][0][0], + leftTick[0][1] + locationOffset[location][0][1], + ], + [ + leftTick[1][0] + locationOffset[location][1][0], + leftTick[1][1] + locationOffset[location][1][1], + ], + ]); + } + } + return { tickIds, tickUIDs, tickCoordinates }; + }; + this.computeWorldScaleCoordinates = (scaleSize, location, pointSet) => { + let worldCoordinates; + let topBottomVec = vec3.subtract(vec3.create(), pointSet[0], pointSet[1]); + topBottomVec = vec3.normalize(vec3.create(), topBottomVec); + let topRightVec = vec3.subtract(vec3.create(), pointSet[2], pointSet[0]); + topRightVec = vec3.normalize(vec3.create(), topRightVec); + const midpointLocation = { + bottom: [pointSet[1], pointSet[2]], + top: [pointSet[0], pointSet[3]], + right: [pointSet[2], pointSet[3]], + left: [pointSet[0], pointSet[1]], + }; + const midpoint = vec3 + .add(vec3.create(), midpointLocation[location][0], midpointLocation[location][0]) + .map((i) => i / 2); + const offset = scaleSize / + 2 / + Math.sqrt(Math.pow(topBottomVec[0], 2) + + Math.pow(topBottomVec[1], 2) + + Math.pow(topBottomVec[2], 2)); + if (location == 'top' || location == 'bottom') { + worldCoordinates = [ + vec3.subtract(vec3.create(), midpoint, topRightVec.map((i) => i * offset)), + vec3.add(vec3.create(), midpoint, topRightVec.map((i) => i * offset)), + ]; + } + else if (location == 'left' || location == 'right') { + worldCoordinates = [ + vec3.add(vec3.create(), midpoint, topBottomVec.map((i) => i * offset)), + vec3.subtract(vec3.create(), midpoint, topBottomVec.map((i) => i * offset)), + ]; + } + return worldCoordinates; + }; + this.computeCanvasScaleCoordinates = (canvasSize, canvasCoordinates, vscaleBounds, hscaleBounds, location) => { + let scaleCanvasCoordinates; + if (location == 'top' || location == 'bottom') { + const worldDistanceOnCanvas = canvasCoordinates[0][0] - canvasCoordinates[1][0]; + scaleCanvasCoordinates = [ + [canvasSize.width / 2 - worldDistanceOnCanvas / 2, vscaleBounds.height], + [canvasSize.width / 2 + worldDistanceOnCanvas / 2, vscaleBounds.height], + ]; + } + else if (location == 'left' || location == 'right') { + const worldDistanceOnCanvas = canvasCoordinates[0][1] - canvasCoordinates[1][1]; + scaleCanvasCoordinates = [ + [hscaleBounds.width, canvasSize.height / 2 - worldDistanceOnCanvas / 2], + [hscaleBounds.width, canvasSize.height / 2 + worldDistanceOnCanvas / 2], + ]; + } + return scaleCanvasCoordinates; + }; + this.computeScaleBounds = (canvasSize, horizontalReduction, verticalReduction, location) => { + const hReduction = horizontalReduction * Math.min(1000, canvasSize.width); + const vReduction = verticalReduction * Math.min(1000, canvasSize.height); + const locationBounds = { + bottom: [-vReduction, -hReduction], + top: [vReduction, hReduction], + left: [vReduction, hReduction], + right: [-vReduction, -hReduction], + }; + const canvasBounds = { + bottom: [canvasSize.height, canvasSize.width], + top: [0, canvasSize.width], + left: [canvasSize.height, 0], + right: [canvasSize.height, canvasSize.width], + }; + return { + height: canvasBounds[location][0] + locationBounds[location][0], + width: canvasBounds[location][1] + locationBounds[location][1], + }; + }; + } + renderAnnotation(enabledElement, svgDrawingHelper) { + if (!this.editData || !this.editData.viewport) { + return; + } + const location = this.configuration.scaleLocation; + const { viewport } = enabledElement; + const annotations = getAnnotations(this.getToolName(), viewport.element); + const annotation = annotations.filter((thisAnnotation) => thisAnnotation.data.viewportId == viewport.id)[0]; + const canvas = enabledElement.viewport.canvas; + const renderStatus = false; + if (!viewport) { + return renderStatus; + } + const styleSpecifier = { + toolGroupId: this.toolGroupId, + toolName: this.getToolName(), + viewportId: enabledElement.viewport.id, + }; + const canvasSize = { + width: canvas.width / window.devicePixelRatio || 1, + height: canvas.height / window.devicePixelRatio || 1, + }; + const topLeft = annotation.data.handles.points[0]; + const topRight = annotation.data.handles.points[1]; + const bottomLeft = annotation.data.handles.points[2]; + const bottomRight = annotation.data.handles.points[3]; + const pointSet1 = [topLeft, bottomLeft, topRight, bottomRight]; + const worldWidthViewport = vec3.distance(bottomLeft, bottomRight); + const worldHeightViewport = vec3.distance(topLeft, bottomLeft); + const hscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location); + const vscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location); + const scaleSize = this.computeScaleSize(worldWidthViewport, worldHeightViewport, location); + const canvasCoordinates = this.computeWorldScaleCoordinates(scaleSize, location, pointSet1).map((world) => viewport.worldToCanvas(world)); + const scaleCanvasCoordinates = this.computeCanvasScaleCoordinates(canvasSize, canvasCoordinates, vscaleBounds, hscaleBounds, location); + const scaleTicks = this.computeEndScaleTicks(scaleCanvasCoordinates, location); + const { annotationUID } = annotation; + styleSpecifier.annotationUID = annotationUID; + const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation); + const lineDash = this.getStyle('lineDash', styleSpecifier, annotation); + const color = this.getStyle('color', styleSpecifier, annotation); + const shadow = this.getStyle('shadow', styleSpecifier, annotation); + const scaleId = `${annotationUID}-scaleline`; + const scaleLineUID = '1'; + drawLineSvg(svgDrawingHelper, annotationUID, scaleLineUID, scaleCanvasCoordinates[0], scaleCanvasCoordinates[1], { + color, + width: lineWidth, + lineDash, + shadow, + }, scaleId); + const leftTickId = `${annotationUID}-left`; + const leftTickUID = '2'; + drawLineSvg(svgDrawingHelper, annotationUID, leftTickUID, scaleTicks.endTick1[0], scaleTicks.endTick1[1], { + color, + width: lineWidth, + lineDash, + shadow, + }, leftTickId); + const rightTickId = `${annotationUID}-right`; + const rightTickUID = '3'; + drawLineSvg(svgDrawingHelper, annotationUID, rightTickUID, scaleTicks.endTick2[0], scaleTicks.endTick2[1], { + color, + width: lineWidth, + lineDash, + shadow, + }, rightTickId); + const locationTextOffest = { + bottom: [-10, -42], + top: [-12, -35], + left: [-40, -20], + right: [-50, -20], + }; + const textCanvasCoordinates = [ + scaleCanvasCoordinates[0][0] + locationTextOffest[location][0], + scaleCanvasCoordinates[0][1] + locationTextOffest[location][1], + ]; + const textBoxLines = this._getTextLines(scaleSize); + const { tickIds, tickUIDs, tickCoordinates } = this.computeInnerScaleTicks(scaleSize, location, annotationUID, scaleTicks.endTick1, scaleTicks.endTick2); + for (let i = 0; i < tickUIDs.length; i++) { + drawLineSvg(svgDrawingHelper, annotationUID, tickUIDs[i], tickCoordinates[i][0], tickCoordinates[i][1], { + color, + width: lineWidth, + lineDash, + shadow, + }, tickIds[i]); + } + const textUID = 'text0'; + drawTextBoxSvg(svgDrawingHelper, annotationUID, textUID, textBoxLines, [textCanvasCoordinates[0], textCanvasCoordinates[1]], { + fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif', + fontSize: '14px', + lineDash: '2,3', + lineWidth: '1', + shadow: true, + color: color, + }); + return renderStatus; + } + _getTextLines(scaleSize) { + let scaleSizeDisplayValue; + let scaleSizeUnits; + if (scaleSize >= 50) { + scaleSizeDisplayValue = scaleSize / 10; + scaleSizeUnits = ' cm'; + } + else { + scaleSizeDisplayValue = scaleSize; + scaleSizeUnits = ' mm'; + } + const textLines = [scaleSizeDisplayValue.toString().concat(scaleSizeUnits)]; + return textLines; + } +} +ScaleOverlayTool.toolName = 'ScaleOverlay'; +export default ScaleOverlayTool;