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