定圆工具配置及功能实现
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": {
|
||||
"@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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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': [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
export default FixedRadiusCircleROITool;
|
||||
|
|
|
|||
|
|
@ -34,13 +34,30 @@
|
|||
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1"
|
||||
:label="$t('trials:readingUnit:readingRules:title:measureTool')">
|
||||
<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">
|
||||
{{ $t(`${tool.i18nKey}`) }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</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)-->
|
||||
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
|
||||
:label="$t('trials:readingUnit:readingRules:title:segmentTool')">
|
||||
|
|
@ -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
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue