From 73e8f24c3d7b382a005b92df3148e1ad2c32f5fc Mon Sep 17 00:00:00 2001 From: caiyiling <1321909229@qq.com> Date: Wed, 29 Apr 2026 11:48:21 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E9=98=85=E7=89=87=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E6=9B=B4=E6=94=B9=EF=BC=9B=E9=80=82=E5=BA=94?= =?UTF-8?q?=E5=9B=BE=E5=83=8F=E9=80=BB=E8=BE=91=E6=9B=B4=E6=94=B9=EF=BC=9B?= =?UTF-8?q?petct=E5=B7=A5=E5=85=B7=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/icons/svg/fitToImage.svg | 2 +- src/icons/svg/rotateHorizontal.svg | 1 + src/icons/svg/rotateTurnLeft.svg | 1 + src/icons/svg/rotateTurnRight.svg | 1 + src/icons/svg/rotateVertical.svg | 1 + .../dicoms/components/Fusion/PetCt.vue | 103 ++++++++++++++++-- .../dicoms3D/components/MPRViewport.vue | 35 ++++-- .../dicoms3D/components/PetCtViewport.vue | 38 ++++++- .../reading/dicoms3D/components/ReadPage.vue | 13 ++- .../reading/dicoms3D/components/Viewport.vue | 32 +++++- .../dicoms3D/components/VolumeViewport.vue | 34 ++++-- 11 files changed, 220 insertions(+), 41 deletions(-) create mode 100644 src/icons/svg/rotateHorizontal.svg create mode 100644 src/icons/svg/rotateTurnLeft.svg create mode 100644 src/icons/svg/rotateTurnRight.svg create mode 100644 src/icons/svg/rotateVertical.svg diff --git a/src/icons/svg/fitToImage.svg b/src/icons/svg/fitToImage.svg index 3bf71f3e..a5703249 100644 --- a/src/icons/svg/fitToImage.svg +++ b/src/icons/svg/fitToImage.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/icons/svg/rotateHorizontal.svg b/src/icons/svg/rotateHorizontal.svg new file mode 100644 index 00000000..59afcb0b --- /dev/null +++ b/src/icons/svg/rotateHorizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/rotateTurnLeft.svg b/src/icons/svg/rotateTurnLeft.svg new file mode 100644 index 00000000..f14d65da --- /dev/null +++ b/src/icons/svg/rotateTurnLeft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/rotateTurnRight.svg b/src/icons/svg/rotateTurnRight.svg new file mode 100644 index 00000000..410b26ef --- /dev/null +++ b/src/icons/svg/rotateTurnRight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/rotateVertical.svg b/src/icons/svg/rotateVertical.svg new file mode 100644 index 00000000..a02e3b6a --- /dev/null +++ b/src/icons/svg/rotateVertical.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt.vue b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt.vue index a0fdf9e8..22010f45 100644 --- a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt.vue +++ b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt.vue @@ -259,6 +259,7 @@ import CustomWwwcForm from './../CustomWwwcForm' import FusionForm from './FusionForm.vue' import { getTableAnswerRowInfoList, getDicomSeriesInfo } from '@/api/trials' import FusionEvent from './FusionEvent' +import FusionJumpToPointTool from '../../../dicoms3D/components/tools/FusionJumpToPointTool' // import { ColorMaps } from '@kitware/vtk.js/Common/Core/ColorMaps' import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps' import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction' @@ -282,7 +283,6 @@ const { // StackScrollMouseWheelTool, StackScrollTool, synchronizers, - MIPJumpToClickTool, // VolumeRotateMouseWheelTool, VolumeRotateTool, OrientationMarkerTool, @@ -425,7 +425,12 @@ export default { fusion: { visible: false }, // 历史记录融合调窗 screenshotWindow: null, hasVoiChanged: false, - lastUpper: null + lastUpper: null, + fusionCrosshairStyle: { + lineWidth: 2, + lineLength: 20, + centerHoleSize: 20, + } // initFirstAnnotation:false } }, @@ -623,6 +628,7 @@ export default { this.setUpToolGroups() this.setUpSynchronizers() + this.dispatchFusionCenterPoint() // this.$refs['CT_AXIAL'].scroll(0) // this.$refs['PT_AXIAL'].scroll(0) // this.$refs['FUSION_AXIAL'].scroll(0) @@ -974,7 +980,7 @@ export default { cornerstoneTools.addTool(WindowLevelTool) cornerstoneTools.addTool(ZoomTool) cornerstoneTools.addTool(StackScrollTool) - cornerstoneTools.addTool(MIPJumpToClickTool) + cornerstoneTools.addTool(FusionJumpToPointTool) cornerstoneTools.addTool(VolumeRotateTool) cornerstoneTools.addTool(EllipticalROITool) cornerstoneTools.addTool(CircleROITool) @@ -1006,6 +1012,7 @@ export default { toolGroup.addTool(ProbeTool.toolName) toolGroup.addTool(ScaleOverlayTool.toolName) toolGroup.addTool(OrientationMarkerTool.toolName) + toolGroup.addTool(FusionJumpToPointTool.toolName, this.getFusionJumpToolConfiguration()) }) fusionToolGroup.addTool(PanTool.toolName) @@ -1021,6 +1028,7 @@ export default { fusionToolGroup.addTool(ProbeTool.toolName) fusionToolGroup.addTool(ScaleOverlayTool.toolName) fusionToolGroup.addTool(OrientationMarkerTool.toolName) + fusionToolGroup.addTool(FusionJumpToPointTool.toolName, this.getFusionJumpToolConfiguration()) // Here is the difference in the toolGroups used, that we need to specify the // volume to use for the WindowLevelTool for the fusion viewports @@ -1072,6 +1080,13 @@ export default { true // overwrite ) toolGroup.setToolEnabled(ScaleOverlayTool.toolName) + toolGroup.setToolActive(FusionJumpToPointTool.toolName, { + bindings: [ + { + mouseButton: MouseBindings.Primary // Left Click + } + ] + }) // toolGroup.setToolConfiguration(OrientationMarkerTool.toolName, { // orientationWidget: { // enabled: true, @@ -1123,14 +1138,11 @@ export default { mipToolGroup.setToolActive(VolumeRotateTool.toolName, { bindings: [{ mouseButton: MouseBindings.Wheel }], }); - mipToolGroup.addTool('MIPJumpToClickTool', { - // - toolGroupId: ptToolGroupId - }) + mipToolGroup.addTool(FusionJumpToPointTool.toolName, this.getFusionJumpToolConfiguration()) // Set the initial state of the tools, here we set one tool active on left click. // This means left click will draw that tool. - mipToolGroup.setToolActive('MIPJumpToClickTool', { + mipToolGroup.setToolActive(FusionJumpToPointTool.toolName, { bindings: [ { mouseButton: MouseBindings.Primary // Left ClickR @@ -1144,6 +1156,81 @@ export default { // mipToolGroup.setToolActive(OrientationMarkerTool.toolName) mipToolGroup.addViewport(viewportIds.PETMIP.CORONAL, renderingEngineId) }, + getFusionJumpToolConfiguration() { + return { + targetViewportIds: [ + viewportIds.CT.AXIAL, + viewportIds.PT.AXIAL, + viewportIds.FUSION.AXIAL, + viewportIds.PETMIP.CORONAL + ], + useBrightestPoint: true, + jumpToTargetViewports: true, + dispatchEventName: 'fusion-mip-point-selected', + getReferenceLineColor: this.setFusionCrosshairsToolLineColor, + style: this.fusionCrosshairStyle, + referenceLinesCenterGapRadius: this.fusionCrosshairStyle.centerHoleSize, + minimal: { + enabled: true, + lineLengthInPx: this.fusionCrosshairStyle.lineLength, + }, + mipViewportIds: [viewportIds.PETMIP.CORONAL] + } + }, + setFusionCrosshairsToolLineColor(viewportId) { + const colors = { + [viewportIds.CT.AXIAL]: '#0000ff', + [viewportIds.PT.AXIAL]: '#0000ff', + [viewportIds.FUSION.AXIAL]: '#0000ff', + [viewportIds.PETMIP.CORONAL]: '#ff0000' + } + return colors[viewportId] || '#0000ff' + }, + dispatchFusionCenterPoint(retryCount = 0) { + const renderEngine = getRenderingEngine(renderingEngineId) + if (!renderEngine) return + const toolGroupCandidates = [fusionToolGroupId, mipToolGroupUID, ptToolGroupId, ctToolGroupId] + let instance = null + for (const toolGroupId of toolGroupCandidates) { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) + instance = toolGroup?.getToolInstance?.(FusionJumpToPointTool.toolName) + if (instance?.setPoint) break + } + if (!instance?.setPoint) { + if (retryCount < 10) { + setTimeout(() => { + this.dispatchFusionCenterPoint(retryCount + 1) + }, 120) + } + return + } + + const candidates = [viewportIds.FUSION.AXIAL, viewportIds.PT.AXIAL, viewportIds.CT.AXIAL] + for (const viewportId of candidates) { + const viewport = renderEngine.getViewport(viewportId) + if (!viewport) continue + const width = viewport.element?.clientWidth + const height = viewport.element?.clientHeight + let worldPoint = null + if (width && height && viewport.canvasToWorld) { + worldPoint = viewport.canvasToWorld([width / 2, height / 2]) + } + if ((!worldPoint || worldPoint.length < 3) && viewport.getCamera) { + worldPoint = viewport.getCamera()?.focalPoint + } + if (!worldPoint || worldPoint.length < 3) continue + instance.setPoint(worldPoint, viewportId, renderEngine.id, { + jumpToTargetViewports: true, + dispatchEvent: false, + }) + return + } + if (retryCount < 10) { + setTimeout(() => { + this.dispatchFusionCenterPoint(retryCount + 1) + }, 120) + } + }, getTextLines(data, targetId) { const cachedVolumeStats = data.cachedStats[targetId] const { diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/MPRViewport.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/MPRViewport.vue index 242f58c0..de837d4f 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/MPRViewport.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/MPRViewport.vue @@ -561,16 +561,37 @@ export default { viewport.render() }, resize(forceFitToWindow) { - console.log('resize: ', forceFitToWindow) const renderingEngine = getRenderingEngine(this.renderingEngineId) const viewport = renderingEngine.getViewport(this.viewportId) - if (!forceFitToWindow) { - viewport.setZoom(0.5) - viewport.render() - } else { - viewport.setZoom(1) + if (!viewport) return + + if (forceFitToWindow) { + viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true }) viewport.render() + return } + + viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true }) + const canvas = viewport.getCanvas() || this.element.querySelector('canvas') + const imageData = viewport.getImageData()?.imageData + const dimensions = imageData?.getDimensions?.() + const imageWidth = dimensions?.[0] + const imageHeight = dimensions?.[1] + const canvasWidth = canvas?.clientWidth + const canvasHeight = canvas?.clientHeight + + if (!imageWidth || !imageHeight || !canvasWidth || !canvasHeight) { + viewport.render() + return + } + + const fitScale = Math.min(canvasWidth / imageWidth, canvasHeight / imageHeight) + if (fitScale > 0) { + // zoom=1 通常是 fit-to-window,这里换算为图像接近 1:1 像素显示 + viewport.setZoom(1 / fitScale) + } + + viewport.render() }, voiChange(v) { const renderingEngine = getRenderingEngine(this.renderingEngineId) @@ -1024,4 +1045,4 @@ export default { cursor: move } } - \ No newline at end of file + 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 2ea372c7..a4aa5c11 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue @@ -415,16 +415,42 @@ export default { viewport.render() }, resize(forceFitToWindow) { - console.log('resize: ', forceFitToWindow) const renderingEngine = getRenderingEngine(this.renderingEngineId) const viewport = renderingEngine.getViewport(this.viewportId) - if (!forceFitToWindow) { - viewport.setZoom(0.5) - viewport.render() - } else { - viewport.setZoom(1) + if (!viewport) return + + if (this.isMip) { + viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true }) viewport.render() + return } + + if (forceFitToWindow) { + viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true }) + viewport.render() + return + } + + viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true }) + const canvas = viewport.getCanvas() || this.element.querySelector('canvas') + const imageData = viewport.getImageData()?.imageData + const dimensions = imageData?.getDimensions?.() + const imageWidth = dimensions?.[0] + const imageHeight = dimensions?.[1] + const canvasWidth = canvas?.clientWidth + const canvasHeight = canvas?.clientHeight + + if (!imageWidth || !imageHeight || !canvasWidth || !canvasHeight) { + viewport.render() + return + } + + const fitScale = Math.min(canvasWidth / imageWidth, canvasHeight / imageHeight) + if (fitScale > 0) { + viewport.setZoom(1 / fitScale) + } + + viewport.render() }, voiChange(v) { const renderingEngine = getRenderingEngine(this.renderingEngineId) 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 7c79d450..a86af6f4 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue @@ -143,9 +143,10 @@ From 877e47ccb197faa30d4a8204b6de843adee8fadb Mon Sep 17 00:00:00 2001 From: caiyiling <1321909229@qq.com> Date: Wed, 29 Apr 2026 13:30:42 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E9=98=85=E7=89=87=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=8D=95=E4=BD=8D=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/reading-unit/components/ArbitrationRules.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue b/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue index ebbe1998..f6d50402 100644 --- a/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue +++ b/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue @@ -194,9 +194,9 @@
- {{ - $fd('ValueUnit', QuestionList[index].Unit) - }} + + {{ `${QuestionList[index].Unit === 4 ? QuestionList[index].CustomUnit : $fd('ValueUnit', QuestionList[index].Unit)}`}} +