定圆工具配置及功能实现
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
eac6b25b9e
commit
266698cc79
|
|
@ -16,10 +16,10 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.726.1",
|
"@aws-sdk/client-s3": "3.726.1",
|
||||||
"@cornerstonejs/adapters": "^4.19.2",
|
"@cornerstonejs/adapters": "^4.19.2",
|
||||||
"@cornerstonejs/polymorphic-segmentation": "4.19.2",
|
|
||||||
"@cornerstonejs/calculate-suv": "^1.1.0",
|
"@cornerstonejs/calculate-suv": "^1.1.0",
|
||||||
"@cornerstonejs/core": "^4.19.2",
|
"@cornerstonejs/core": "^4.19.2",
|
||||||
"@cornerstonejs/dicom-image-loader": "^4.19.2",
|
"@cornerstonejs/dicom-image-loader": "^4.19.2",
|
||||||
|
"@cornerstonejs/polymorphic-segmentation": "^4.19.2",
|
||||||
"@cornerstonejs/tools": "^4.19.2",
|
"@cornerstonejs/tools": "^4.19.2",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.6.2",
|
"@fingerprintjs/fingerprintjs": "^4.6.2",
|
||||||
"@icr/polyseg-wasm": "^0.4.0",
|
"@icr/polyseg-wasm": "^0.4.0",
|
||||||
|
|
@ -125,4 +125,4 @@
|
||||||
"not dead",
|
"not dead",
|
||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1605,9 +1605,10 @@ export default {
|
||||||
toolGroup.addTool(EllipticalROITool.toolName, {
|
toolGroup.addTool(EllipticalROITool.toolName, {
|
||||||
getTextLines: this.getEllipticalROIToolTextLines
|
getTextLines: this.getEllipticalROIToolTextLines
|
||||||
})
|
})
|
||||||
toolGroup.addTool(FixedRadiusCircleROITool.toolName), {
|
toolGroup.addTool(FixedRadiusCircleROITool.toolName, {
|
||||||
getTextLines: this.getCircleROIToolTextLines
|
radius: Number.isFinite(this.taskInfo.CircleRadius) ? this.taskInfo.CircleRadius : 1,
|
||||||
}
|
getTextLines: this.getCircleROIToolTextLines
|
||||||
|
})
|
||||||
toolGroup.addTool(AngleTool.toolName, {
|
toolGroup.addTool(AngleTool.toolName, {
|
||||||
getTextLines: this.getAngleToolTextLines
|
getTextLines: this.getAngleToolTextLines
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import vtkColorTransferFunction from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction";
|
import vtkColorTransferFunction from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction";
|
||||||
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
|
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";
|
import { cache, metaData, utilities } from "@cornerstonejs/core";
|
||||||
|
|
||||||
const { getColormap } = utilities.colormap;
|
const { getColormap } = utilities.colormap;
|
||||||
|
|
@ -14,7 +15,7 @@ function getWindowCenterFromVolumeId(volumeId) {
|
||||||
? voiLutModule.windowCenter[0]
|
? voiLutModule.windowCenter[0]
|
||||||
: voiLutModule?.windowCenter;
|
: voiLutModule?.windowCenter;
|
||||||
const center = Number(rawCenter);
|
const center = Number(rawCenter);
|
||||||
return Number.isFinite(center) ? center : null;
|
return center;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function setPetColorMapTransferFunctionForVolumeActor({
|
export default function setPetColorMapTransferFunctionForVolumeActor({
|
||||||
|
|
@ -33,7 +34,25 @@ export default function setPetColorMapTransferFunctionForVolumeActor({
|
||||||
|
|
||||||
const center = getWindowCenterFromVolumeId(volumeId);
|
const center = getWindowCenterFromVolumeId(volumeId);
|
||||||
const upper = center > 1 ? center : 5;
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,15 @@ const config = {
|
||||||
'isDisabled': false,
|
'isDisabled': false,
|
||||||
'disabledReason': ''
|
'disabledReason': ''
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': '定圆工具',
|
||||||
|
'icon': 'oval',
|
||||||
|
'toolName': 'FixedRadiusCircleROI',
|
||||||
|
'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
|
||||||
|
'i18nKey': 'trials:reading:button:fixedCircle',
|
||||||
|
'isDisabled': false,
|
||||||
|
'disabledReason': ''
|
||||||
|
}
|
||||||
],
|
],
|
||||||
'customizeStandardsNoneDicom': [
|
'customizeStandardsNoneDicom': [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,12 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
|
||||||
preventHandleOutsideImage: false,
|
preventHandleOutsideImage: false,
|
||||||
storePointData: false,
|
storePointData: false,
|
||||||
centerPointRadius: 0,
|
centerPointRadius: 0,
|
||||||
|
calculateStats: true,
|
||||||
radius: 10, // Default radius in mm
|
radius: 10, // Default radius in mm
|
||||||
radiusUnit: 'mm',
|
radiusUnit: 'mm',
|
||||||
statsCalculator: BasicStatsCalculator,
|
statsCalculator: BasicStatsCalculator,
|
||||||
getTextLines: defaultGetTextLines,
|
getTextLines: defaultGetTextLines,
|
||||||
|
simplified: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
@ -67,9 +69,12 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
|
||||||
);
|
);
|
||||||
|
|
||||||
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
||||||
|
const targetId = this.getTargetId(viewport);
|
||||||
|
|
||||||
// Calculate end point based on fixed radius
|
// 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.
|
// viewUp is a normalized vector.
|
||||||
// We want a point 'radius' distance away from center.
|
// We want a point 'radius' distance away from center.
|
||||||
// We can use viewUp or any vector in the plane.
|
// We can use viewUp or any vector in the plane.
|
||||||
|
|
@ -106,7 +111,20 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
|
||||||
points: [[...worldPos], [...endPos]],
|
points: [[...worldPos], [...endPos]],
|
||||||
activeHandleIndex: null,
|
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) {
|
function defaultGetTextLines(data, targetId) {
|
||||||
const cachedVolumeStats = data.cachedStats[targetId];
|
const cachedVolumeStats = data?.cachedStats?.[targetId];
|
||||||
|
if (!cachedVolumeStats) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const {
|
const {
|
||||||
radius,
|
radius,
|
||||||
radiusUnit,
|
radiusUnit,
|
||||||
|
|
@ -195,32 +215,33 @@ function defaultGetTextLines(data, targetId) {
|
||||||
modalityUnit,
|
modalityUnit,
|
||||||
} = cachedVolumeStats;
|
} = cachedVolumeStats;
|
||||||
const textLines = [];
|
const textLines = [];
|
||||||
|
if (csUtils.isNumber(radius)) {
|
||||||
if (radius) {
|
|
||||||
const radiusLine = isEmptyArea
|
const radiusLine = isEmptyArea
|
||||||
? `Radius: Oblique not supported`
|
? `Radius: Oblique not supported`
|
||||||
: `Radius: ${csUtils.roundNumber(radius)} ${radiusUnit}`;
|
: `Radius: ${csUtils.roundNumber(radius)} ${radiusUnit}`;
|
||||||
textLines.push(radiusLine);
|
textLines.push(radiusLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (area) {
|
if (csUtils.isNumber(area)) {
|
||||||
const areaLine = isEmptyArea
|
const areaLine = isEmptyArea
|
||||||
? `Area: Oblique not supported`
|
? `Area: Oblique not supported`
|
||||||
: `Area: ${csUtils.roundNumber(area)} ${areaUnit}`;
|
: `Area: ${csUtils.roundNumber(area)} ${areaUnit}`;
|
||||||
textLines.push(areaLine);
|
textLines.push(areaLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mean) {
|
if (csUtils.isNumber(mean)) {
|
||||||
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
|
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max) {
|
if (csUtils.isNumber(max)) {
|
||||||
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
|
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdDev) {
|
if (csUtils.isNumber(stdDev)) {
|
||||||
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
|
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return textLines;
|
return textLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default FixedRadiusCircleROITool;
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,30 @@
|
||||||
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1"
|
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1"
|
||||||
:label="$t('trials:readingUnit:readingRules:title:measureTool')">
|
:label="$t('trials:readingUnit:readingRules:title:measureTool')">
|
||||||
<el-checkbox-group v-model="form.ReadingToolList" :disabled="isConfirm ||
|
<el-checkbox-group v-model="form.ReadingToolList" :disabled="isConfirm ||
|
||||||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
|
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
|
||||||
">
|
@change="handleReadingToolListChange"
|
||||||
|
>
|
||||||
<el-checkbox v-for="tool in tools" :key="tool.toolName" :label="tool.toolName" name="ReadingToolList">
|
<el-checkbox v-for="tool in tools" :key="tool.toolName" :label="tool.toolName" name="ReadingToolList">
|
||||||
{{ $t(`${tool.i18nKey}`) }}
|
{{ $t(`${tool.i18nKey}`) }}
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- 定圆工具半径 -->
|
||||||
|
<el-form-item v-if="form.ReadingToolList.includes('FixedRadiusCircleROI')"
|
||||||
|
:label="$t('trials:readingUnit:readingRules:title:circleRadius')"
|
||||||
|
prop="CircleRadius"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.CircleRadius"
|
||||||
|
@input="(val) => handleNumberInput(val, 'CircleRadius')"
|
||||||
|
clearable
|
||||||
|
:disabled="isConfirm ||
|
||||||
|
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template slot="append">mm</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
<!--分割工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
|
<!--分割工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
|
||||||
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
|
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
|
||||||
:label="$t('trials:readingUnit:readingRules:title:segmentTool')">
|
:label="$t('trials:readingUnit:readingRules:title:segmentTool')">
|
||||||
|
|
@ -460,7 +477,8 @@ export default {
|
||||||
ImageUploadEnum: null,
|
ImageUploadEnum: null,
|
||||||
IsImageFilter: false,
|
IsImageFilter: false,
|
||||||
KeyFileListStr: '',
|
KeyFileListStr: '',
|
||||||
KeyFileList: []
|
KeyFileList: [],
|
||||||
|
CircleRadius: null
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
IsAutoCreate: [
|
IsAutoCreate: [
|
||||||
|
|
@ -613,6 +631,10 @@ export default {
|
||||||
trigger: ['blur', 'change'],
|
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: [
|
// IsReadingTaskViewInOrder: [
|
||||||
// { required: true, message: this.$t('common:ruleMessage:select'), trigger: ['blur', 'change'] }
|
// { 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
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue