From fdc23361f92df0ca54b805fced885af261f76c7c Mon Sep 17 00:00:00 2001 From: wangxiaoshuang <825034831@qq.com> Date: Wed, 31 Dec 2025 15:50:01 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A3=81=E5=85=B1=E6=8C=AF=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E5=9C=A8=E5=81=9AMPR=E6=97=B6=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E5=BB=BA=E7=9A=84=E4=BD=93=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?=E5=90=84=E4=B8=AA=E8=BD=B4=E5=90=91=E4=B8=8A=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E6=95=B0=E9=87=8F=E4=B8=8D=E6=AD=A3=E7=A1=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/dictionary/template/browser/tip.vue | 3 +- .../reading/dicoms/components/DicomViewer.vue | 3 +- .../reading/dicoms3D/components/ReadPage.vue | 22 +++- .../dicoms3D/components/VolumeViewport.vue | 102 +++++++++++++++--- 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/src/views/dictionary/template/browser/tip.vue b/src/views/dictionary/template/browser/tip.vue index a88e10d1..391ea8ec 100644 --- a/src/views/dictionary/template/browser/tip.vue +++ b/src/views/dictionary/template/browser/tip.vue @@ -68,7 +68,8 @@ export default { let { width, height } = allInfo.screen; // 分辨率 // let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡 // let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存 - if (parseFloat(deviceMemory) < 16 || width < 1920 || height < 1080) { + // parseFloat(deviceMemory) < 16 || + if (width < 1920 || height < 1080) { if (this.tip) { this.tip += `
` } diff --git a/src/views/trials/trials-panel/reading/dicoms/components/DicomViewer.vue b/src/views/trials/trials-panel/reading/dicoms/components/DicomViewer.vue index ec2dbdc5..5957f7b7 100644 --- a/src/views/trials/trials-panel/reading/dicoms/components/DicomViewer.vue +++ b/src/views/trials/trials-panel/reading/dicoms/components/DicomViewer.vue @@ -2491,7 +2491,8 @@ export default { let { width, height } = allInfo.screen; // 分辨率 let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡 let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存 - if (parseFloat(deviceMemory) < 16 || width < 1920 || height < 1080 || !discrete || parseFloat(estimatedMemory) < 2) { + // parseFloat(deviceMemory) < 16 || + if (width < 1920 || height < 1080 || !discrete || parseFloat(estimatedMemory) < 2) { let res = await this.$confirm(this.$t('browser:tip:ReadingConfiguration')) resolve(res) } else { 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 e7adbdeb..1af9c593 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue @@ -317,7 +317,7 @@ @dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)"> @@ -673,7 +673,18 @@ export default { ManualsClose: false, isMPR: false, - volumeToolGroupId: "share-viewport-volume" + volumeToolGroupId: "share-viewport-volume", + MPRInfo: { + AXIAL: { + imageNum: 0 + }, + CORONAL: { + imageNum: 0 + }, + SAGITTAL: { + imageNum: 0 + }, + } } }, computed: { @@ -801,6 +812,10 @@ export default { document.addEventListener("click", this.foo); }, methods: { + setMPRInfo(obj) { + let { type, key, value } = obj + this.$set(this.MPRInfo[type], key, value) + }, handleReadingChart(row) { let { e, data } = row let obj = Object.assign({}, data) @@ -3478,7 +3493,8 @@ export default { let { width, height } = allInfo.screen; // 分辨率 let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡 let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存 - if (parseFloat(deviceMemory) < 16 || width < 1920 || height < 1080 || !discrete || parseFloat(estimatedMemory) < 2) { + // parseFloat(deviceMemory) < 16 || + if (width < 1920 || height < 1080 || !discrete || parseFloat(estimatedMemory) < 2) { let res = await this.$confirm(this.$t('browser:tip:ReadingConfiguration'), this.$t('system:menu:confirm:title:warning'), { type: 'warning' }) diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/VolumeViewport.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/VolumeViewport.vue index dd44c9c7..427f84bb 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/VolumeViewport.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/VolumeViewport.vue @@ -70,6 +70,7 @@ import { metaData, getRenderingEngine, utilities as csUtils, + cache } from '@cornerstonejs/core' import * as cornerstoneTools from '@cornerstonejs/tools' import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData' @@ -91,6 +92,12 @@ export default { type: Number, required: true }, + MPRInfo: { + type: Object, + default: () => { + return {} + } + } }, data() { return { @@ -142,6 +149,25 @@ export default { this.initViewport() }) }, + watch: { + MPRInfo: { + handler() { + if (!this.series.orientation) return false + switch (this.series.orientation) { + case 'AXIAL': + this.imageInfo.size = `${this.MPRInfo.SAGITTAL.imageNum}*${this.MPRInfo.CORONAL.imageNum}` + break; + case 'CORONAL': + this.imageInfo.size = `${this.MPRInfo.SAGITTAL.imageNum}*${this.MPRInfo.AXIAL.imageNum}` + break; + case 'SAGITTAL': + this.imageInfo.size = `${this.MPRInfo.CORONAL.imageNum}*${this.MPRInfo.AXIAL.imageNum}` + break; + } + }, + deep: true + } + }, methods: { initViewport() { this.element = this.$refs['viewport-volume'] @@ -185,6 +211,58 @@ export default { }) }, + + determineImagePlane(imageOrientationPatient) { + // imageOrientationPatient 是 [rowX, rowY, rowZ, colX, colY, colZ] + // 我们只关心行方向向量 (rowX, rowY, rowZ) + const [rowX, rowY, rowZ] = imageOrientationPatient; + + // 计算行方向向量与 X, Y, Z 轴的点积(因为轴向量是单位向量,所以点积就是投影长度) + const dotX = Math.abs(rowX); + const dotY = Math.abs(rowY); + const dotZ = Math.abs(rowZ); + + // 找到最大的点积,确定主方向 + const maxDot = Math.max(dotX, dotY, dotZ); + + // 根据主方向判断解剖面 + if (maxDot === dotX) { + // 行方向接近 X 轴,说明视线方向接近 Y 或 Z 轴。 + // 更准确的判断是看视线方向(由行和列向量叉乘得到)。 + // 但一个简化的、在大多数情况下都成立的规则是: + // 如果行向量主要在 X-Y 平面 (rowZ 很小),则为 Axial。 + // 如果行向量主要在 X-Z 平面 (rowY 很小),则为 Sagittal。 + // 为了简化,我们可以直接根据视线方向(法线)来判断。 + // 法线方向 = 行向量 × 列向量 + const [colX, colY, colZ] = imageOrientationPatient.slice(3); + const normalX = rowY * colZ - rowZ * colY; + const normalY = rowZ * colX - rowX * colZ; + const normalZ = rowX * colY - rowY * colX; + + const absNormalX = Math.abs(normalX); + const absNormalY = Math.abs(normalY); + const absNormalZ = Math.abs(normalZ); + + const maxNormal = Math.max(absNormalX, absNormalY, absNormalZ); + + if (maxNormal === absNormalZ) { + return 'AXIAL'; + } else if (maxNormal === absNormalY) { + return 'SAGITTAL'; + } else if (maxNormal === absNormalX) { + return 'CORONAL'; + } + + } else if (maxDot === dotY) { + // 行方向接近 Y 轴,通常是 Coronal 面。 + return 'SAGITTAL'; + } else if (maxDot === dotZ) { + // 行方向接近 Z 轴,通常是 Sagittal 面。 + return 'CORONAL'; + } + + return 'unknown'; + }, stackNewImage(e) { const { detail } = e this.series.SliceIndex = detail.imageIndex @@ -195,26 +273,16 @@ export default { this.imageInfo.zoom = zoom.toFixed(4) let imageIds = viewport.getImageIds(this.volumeId) let imageId = imageIds[0] + let volume = cache.getVolume(this.volumeId) + let { spacing } = volume // if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId() if (imageId) { + this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices }) const imagePlaneModule = metaData.get('imagePlaneModule', imageId) - if (this.series.orientation === 'AXIAL') { - this.imageInfo.size = `${imagePlaneModule.columns}*${imagePlaneModule.rows}` - this.imageInfo.location = imagePlaneModule.sliceLocation - this.imageInfo.total = imageIds.length - this.imageInfo.sliceThickness = imagePlaneModule.sliceThickness - } - if (this.series.orientation === 'CORONAL') { - this.imageInfo.size = `${imagePlaneModule.columns}*${imageIds.length}` - this.imageInfo.total = imagePlaneModule.rows - this.imageInfo.sliceThickness = imagePlaneModule.rowPixelSpacing - } - if (this.series.orientation === 'SAGITTAL') { - this.imageInfo.size = `${imagePlaneModule.rows}*${imageIds.length}` - this.imageInfo.total = imagePlaneModule.columns - this.imageInfo.sliceThickness = imagePlaneModule.columnPixelSpacing - } - + let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient) + this.imageInfo.location = type === this.series.orientation ? imagePlaneModule.sliceLocation : '' + this.imageInfo.sliceThickness = type === this.series.orientation ? spacing[2] : spacing[0] + this.imageInfo.total = detail.numberOfSlices this.getOrientationMarker() let properties = viewport.getProperties(this.volumeId) if (properties && properties.voiRange) {