From 266698cc799dcc2ed00bd7eeb6275a196088dd90 Mon Sep 17 00:00:00 2001
From: caiyiling <1321909229@qq.com>
Date: Tue, 14 Apr 2026 21:19:00 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E5=9C=86=E5=B7=A5=E5=85=B7=E9=85=8D?=
=?UTF-8?q?=E7=BD=AE=E5=8F=8A=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 4 +-
.../reading/dicoms3D/components/ReadPage.vue | 7 ++-
...nColorMapTransferFunctionForVolumeActor.js | 25 +++++++-
.../reading/dicoms3D/components/toolConfig.js | 9 +++
.../tools/FixedRadiusCircleROITool.js | 43 ++++++++++----
.../reading-unit/components/ReadingRules.vue | 59 ++++++++++++++++++-
6 files changed, 125 insertions(+), 22 deletions(-)
diff --git a/package.json b/package.json
index 8641f081..79ed4a88 100644
--- a/package.json
+++ b/package.json
@@ -16,10 +16,10 @@
"dependencies": {
"@aws-sdk/client-s3": "3.726.1",
"@cornerstonejs/adapters": "^4.19.2",
- "@cornerstonejs/polymorphic-segmentation": "4.19.2",
"@cornerstonejs/calculate-suv": "^1.1.0",
"@cornerstonejs/core": "^4.19.2",
"@cornerstonejs/dicom-image-loader": "^4.19.2",
+ "@cornerstonejs/polymorphic-segmentation": "^4.19.2",
"@cornerstonejs/tools": "^4.19.2",
"@fingerprintjs/fingerprintjs": "^4.6.2",
"@icr/polyseg-wasm": "^0.4.0",
@@ -125,4 +125,4 @@
"not dead",
"not op_mini all"
]
-}
\ No newline at end of file
+}
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 1bc59f41..b40015b1 100644
--- a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue
+++ b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue
@@ -1605,9 +1605,10 @@ export default {
toolGroup.addTool(EllipticalROITool.toolName, {
getTextLines: this.getEllipticalROIToolTextLines
})
- toolGroup.addTool(FixedRadiusCircleROITool.toolName), {
- getTextLines: this.getCircleROIToolTextLines
- }
+ toolGroup.addTool(FixedRadiusCircleROITool.toolName, {
+ radius: Number.isFinite(this.taskInfo.CircleRadius) ? this.taskInfo.CircleRadius : 1,
+ getTextLines: this.getCircleROIToolTextLines
+ })
toolGroup.addTool(AngleTool.toolName, {
getTextLines: this.getAngleToolTextLines
})
diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/helpers/setNmFusionColorMapTransferFunctionForVolumeActor.js b/src/views/trials/trials-panel/reading/dicoms3D/components/helpers/setNmFusionColorMapTransferFunctionForVolumeActor.js
index 757c3ee0..e7fb2773 100644
--- a/src/views/trials/trials-panel/reading/dicoms3D/components/helpers/setNmFusionColorMapTransferFunctionForVolumeActor.js
+++ b/src/views/trials/trials-panel/reading/dicoms3D/components/helpers/setNmFusionColorMapTransferFunctionForVolumeActor.js
@@ -1,5 +1,6 @@
import vtkColorTransferFunction from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction";
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
+import { Scale } from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction/Constants";
import { cache, metaData, utilities } from "@cornerstonejs/core";
const { getColormap } = utilities.colormap;
@@ -14,7 +15,7 @@ function getWindowCenterFromVolumeId(volumeId) {
? voiLutModule.windowCenter[0]
: voiLutModule?.windowCenter;
const center = Number(rawCenter);
- return Number.isFinite(center) ? center : null;
+ return center;
}
export default function setPetColorMapTransferFunctionForVolumeActor({
@@ -33,7 +34,25 @@ export default function setPetColorMapTransferFunctionForVolumeActor({
const center = getWindowCenterFromVolumeId(volumeId);
const upper = center > 1 ? center : 5;
- cfun.setMappingRange(1, upper);
+ const safeUpper = Number.isFinite(upper) && upper > 0 ? upper : 5;
+ const rangeMin = 0;
+ const rangeMax = safeUpper;
+ cfun.setScale(Scale.LOG10);
+ cfun.setMappingRange(rangeMin, rangeMax);
volumeActor.getProperty().setRGBTransferFunction(0, cfun);
-}
+ //低信号更明显,系数可以更小
+ const thresholdValue0 = Math.max(rangeMin, rangeMax * 0.002);
+ const thresholdValue1 = Math.max(thresholdValue0, rangeMax * 0.02);
+ const delta = Math.abs(rangeMax - rangeMin) * 0.001;
+ const threshold0MinusDelta = Math.max(rangeMin, thresholdValue0 - delta);
+
+ const ofun = vtkPiecewiseFunction.newInstance();
+ ofun.addPoint(rangeMin, 0.0);
+ ofun.addPoint(threshold0MinusDelta, 0.0);
+ //低信号更明显,系数可以更小
+ ofun.addPoint(thresholdValue0, 0.08);
+ ofun.addPoint(thresholdValue1, 0.9);
+ ofun.addPoint(rangeMax, 1.0);
+ volumeActor.getProperty().setScalarOpacity(0, ofun);
+}
diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/toolConfig.js b/src/views/trials/trials-panel/reading/dicoms3D/components/toolConfig.js
index 9531a991..f93549c7 100644
--- a/src/views/trials/trials-panel/reading/dicoms3D/components/toolConfig.js
+++ b/src/views/trials/trials-panel/reading/dicoms3D/components/toolConfig.js
@@ -311,6 +311,15 @@ const config = {
'isDisabled': false,
'disabledReason': ''
},
+ {
+ 'name': '定圆工具',
+ 'icon': 'oval',
+ 'toolName': 'FixedRadiusCircleROI',
+ 'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
+ 'i18nKey': 'trials:reading:button:fixedCircle',
+ 'isDisabled': false,
+ 'disabledReason': ''
+ }
],
'customizeStandardsNoneDicom': [
{
diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/tools/FixedRadiusCircleROITool.js b/src/views/trials/trials-panel/reading/dicoms3D/components/tools/FixedRadiusCircleROITool.js
index da073e7f..2aacc73d 100644
--- a/src/views/trials/trials-panel/reading/dicoms3D/components/tools/FixedRadiusCircleROITool.js
+++ b/src/views/trials/trials-panel/reading/dicoms3D/components/tools/FixedRadiusCircleROITool.js
@@ -28,10 +28,12 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
preventHandleOutsideImage: false,
storePointData: false,
centerPointRadius: 0,
+ calculateStats: true,
radius: 10, // Default radius in mm
radiusUnit: 'mm',
statsCalculator: BasicStatsCalculator,
getTextLines: defaultGetTextLines,
+ simplified: true,
},
}
) {
@@ -67,9 +69,12 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
);
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
+ const targetId = this.getTargetId(viewport);
// Calculate end point based on fixed radius
- const radius = this.configuration.radius || 10;
+ const radius = Number.isFinite(this.configuration.radius)
+ ? this.configuration.radius
+ : 10;
// viewUp is a normalized vector.
// We want a point 'radius' distance away from center.
// We can use viewUp or any vector in the plane.
@@ -106,7 +111,20 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
points: [[...worldPos], [...endPos]],
activeHandleIndex: null,
},
- cachedStats: {},
+ cachedStats: {
+ [targetId]: {
+ Modality: null,
+ radius: null,
+ radiusUnit: null,
+ area: null,
+ mean: null,
+ stdDev: null,
+ max: null,
+ isEmptyArea: null,
+ areaUnit: null,
+ modalityUnit: null,
+ },
+ },
},
};
@@ -180,9 +198,11 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
};
}
-export default FixedRadiusCircleROITool;
function defaultGetTextLines(data, targetId) {
- const cachedVolumeStats = data.cachedStats[targetId];
+ const cachedVolumeStats = data?.cachedStats?.[targetId];
+ if (!cachedVolumeStats) {
+ return [];
+ }
const {
radius,
radiusUnit,
@@ -195,32 +215,33 @@ function defaultGetTextLines(data, targetId) {
modalityUnit,
} = cachedVolumeStats;
const textLines = [];
-
- if (radius) {
+ if (csUtils.isNumber(radius)) {
const radiusLine = isEmptyArea
? `Radius: Oblique not supported`
: `Radius: ${csUtils.roundNumber(radius)} ${radiusUnit}`;
textLines.push(radiusLine);
}
- if (area) {
+ if (csUtils.isNumber(area)) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${csUtils.roundNumber(area)} ${areaUnit}`;
textLines.push(areaLine);
}
- if (mean) {
+ if (csUtils.isNumber(mean)) {
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
}
- if (max) {
+ if (csUtils.isNumber(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}
- if (stdDev) {
+ if (csUtils.isNumber(stdDev)) {
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
}
return textLines;
-}
\ No newline at end of file
+}
+
+export default FixedRadiusCircleROITool;
diff --git a/src/views/trials/trials-panel/setting/reading-unit/components/ReadingRules.vue b/src/views/trials/trials-panel/setting/reading-unit/components/ReadingRules.vue
index 2ca38593..0e76f5c2 100644
--- a/src/views/trials/trials-panel/setting/reading-unit/components/ReadingRules.vue
+++ b/src/views/trials/trials-panel/setting/reading-unit/components/ReadingRules.vue
@@ -34,13 +34,30 @@
+ !hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
+ @change="handleReadingToolListChange"
+ >
{{ $t(`${tool.i18nKey}`) }}
+
+
+ handleNumberInput(val, 'CircleRadius')"
+ clearable
+ :disabled="isConfirm ||
+ !hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
+ "
+ >
+ mm
+
+
@@ -460,7 +477,8 @@ export default {
ImageUploadEnum: null,
IsImageFilter: false,
KeyFileListStr: '',
- KeyFileList: []
+ KeyFileList: [],
+ CircleRadius: null
},
rules: {
IsAutoCreate: [
@@ -613,6 +631,10 @@ export default {
trigger: ['blur', 'change'],
},
],
+ CircleRadius: [
+ { required: true, message: this.$t('common:ruleMessage:specify'), trigger: ['blur', 'change'], },
+ { pattern: /^(0|[1-9]\d*)(\.\d{1,2})?$/, message: '请输入正确的数值格式,最多两位小数', trigger: 'blur' }
+ ]
// IsReadingTaskViewInOrder: [
// { required: true, message: this.$t('common:ruleMessage:select'), trigger: ['blur', 'change'] }
// ]
@@ -900,6 +922,37 @@ export default {
})
})
},
+ handleReadingToolListChange(v) {
+ if(!v.includes('FixedRadiusCircleROI')) {
+ this.form.CircleRadius = null
+ }
+ },
+ handleNumberInput(value, field) {
+ if (!value) {
+ this.form[field] = ''
+ return
+ }
+
+ let val = value.toString()
+
+ // 1. 清除“数字”和“.”以外的字符
+ val = val.replace(/[^\d.]/g, '')
+ // 2. 保证第一个字符不能是“.”
+ val = val.replace(/^\./g, '')
+ // 3. 保证不能连续输入多个“.”,只保留第一个
+ val = val.replace(/\.{2,}/g, '.')
+ val = val.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.')
+ // 4. 限制只能输入两位小数
+ val = val.replace(/^(\-)*(\d+)\.(\d{2}).*$/, '$1$2.$3')
+ // 5. 处理前导零(例如输入 01 自动变成 1)
+ if (val.length > 1 && val[0] === '0' && val[1] !== '.') {
+ val = val.replace(/^0+/, '')
+ if (val === '') val = '0'
+ }
+ this.$nextTick(() => {
+ this.ruleForm[field] = val
+ });
+ },
},
}