Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is running Details

uat_us
wangxiaoshuang 2026-04-15 14:22:59 +08:00
commit 1a42cfd47c
9 changed files with 269 additions and 31 deletions

View File

@ -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"
]
}
}

View File

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

View File

@ -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);
}

View File

@ -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': [
{

View File

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

View File

@ -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')">
@ -510,7 +527,8 @@ export default {
ImageUploadEnum: null,
IsImageFilter: false,
KeyFileListStr: '',
KeyFileList: []
KeyFileList: [],
CircleRadius: null
},
rules: {
IsAutoCreate: [
@ -678,6 +696,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'] }
// ]
@ -1044,6 +1066,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>

View File

@ -21,6 +21,17 @@
</el-option>
</el-select>
</el-form-item>
<!-- 源可用时间 -->
<el-form-item label="源可用时间">
<el-date-picker
v-model="uploadTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleUploadtimeChange"
style="width: 300px"
type="datetimerange">
</el-date-picker>
</el-form-item>
<!-- 目标区域 -->
<el-form-item label="目标区域" prop="TargetRegion">
<el-select v-model="searchData.TargetRegion" style="width: 120px">
@ -32,6 +43,21 @@
</el-option>
</el-select>
</el-form-item>
<!-- 目标可用时间 -->
<el-form-item label="目标可用时间">
<el-date-picker
v-model="SyncTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleSynctimeChange"
style="width: 300px"
type="datetimerange">
</el-date-picker>
</el-form-item>
<!-- 优先级 -->
<el-form-item label="优先级">
<el-input v-model="searchData.Priority" clearable style="width: 120px"></el-input>
</el-form-item>
<!-- 是否同步完成 -->
<el-form-item label="是否同步完成" prop="IsSync">
<el-select v-model="searchData.IsSync" clearable style="width: 120px">
@ -201,6 +227,11 @@ const searchDataDefault = () => {
UploadRegion: null,
TargetRegion: null,
IsSync: null,
UploadStartTime: null,
UploadEndTime: null,
SyncFinishedStartTime: null,
SyncFinishedEndTime: null,
Priority: null,
Asc: false,
SortField: 'UpdateTime',
PageIndex: 1,
@ -227,7 +258,8 @@ export default {
list: [],
total: 0,
loading: false,
datetimerange: [],
uploadTimeRange: null,
SyncTimeRange: null,
regionOptions: [
{
value: 'CN',
@ -363,13 +395,22 @@ export default {
if (!size) return
return (size / Math.pow(1024, 2)).toFixed(3) + 'MB'
},
handleDatetimeChange(val) {
handleUploadtimeChange(val) {
if (val) {
this.searchData.BeginDate = val[0]
this.searchData.EndDate = val[1]
this.searchData.UploadStartTime = val[0]
this.searchData.UploadEndTime = val[1]
} else {
this.searchData.BeginDate = ''
this.searchData.EndDate = ''
this.searchData.UploadStartTime = ''
this.searchData.UploadEndTime = ''
}
},
handleSynctimeChange(val) {
if (val) {
this.searchData.SyncFinishedStartTime = val[0]
this.searchData.SyncFinishedEndTime = val[1]
} else {
this.searchData.SyncFinishedStartTime = ''
this.searchData.SyncFinishedEndTime = ''
}
},
handleSearch() {
@ -378,7 +419,10 @@ export default {
},
//
handleReset() {
this.datetimerange = null
this.uploadTimeRange = null
this.handleUploadtimeChange()
this.SyncTimeRange = null
this.handleSynctimeChange()
this.searchData = searchDataDefault()
this.getList()
},

View File

@ -31,6 +31,17 @@
</el-option>
</el-select>
</el-form-item>
<!-- 源可用时间 -->
<!-- <el-form-item label="源可用时间">
<el-date-picker
v-model="uploadTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleUploadtimeChange"
style="width: 250px"
type="datetimerange">
</el-date-picker>
</el-form-item> -->
<!-- 目标区域 -->
<el-form-item label="目标区域" prop="TargetRegion">
<el-select v-model="searchData.TargetRegion" style="width: 120px">
@ -42,6 +53,17 @@
</el-option>
</el-select>
</el-form-item>
<!-- 目标可用时间 -->
<!-- <el-form-item label="目标可用时间">
<el-date-picker
v-model="SyncTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleSynctimeChange"
style="width: 250px"
type="datetimerange">
</el-date-picker>
</el-form-item> -->
<!-- 是否同步完成 -->
<el-form-item label="是否同步完成" prop="IsSync">
<el-select v-model="searchData.IsSync" clearable style="width: 120px">
@ -146,6 +168,10 @@ const searchDataDefault = () => {
UploadRegion: '',
TargetRegion: '',
IsSync: null,
// UploadStartTime: null,
// UploadEndTime: null,
// SyncFinishedStartTime: null,
// SyncFinishedEndTime: null,
PageIndex: 1,
PageSize: 20,
Asc: true,
@ -179,7 +205,9 @@ export default {
}
],
fileUploadRecordId: '',
path: ''
path: '',
uploadTimeRange: null,
SyncTimeRange: null,
}
},
mounted() {
@ -231,13 +259,34 @@ export default {
console.log(e)
}
},
handleUploadtimeChange(val) {
if (val) {
this.searchData.UploadStartTime = val[0]
this.searchData.UploadEndTime = val[1]
} else {
this.searchData.UploadStartTime = ''
this.searchData.UploadEndTime = ''
}
},
handleSynctimeChange(val) {
if (val) {
this.searchData.SyncFinishedStartTime = val[0]
this.searchData.SyncFinishedEndTime = val[1]
} else {
this.searchData.SyncFinishedStartTime = ''
this.searchData.SyncFinishedEndTime = ''
}
},
handleSearch() {
this.searchData.PageIndex = 1
this.getList()
},
//
handleReset() {
this.datetimerange = null
// this.uploadTimeRange = null
// this.handleUploadtimeChange()
// this.SyncTimeRange = null
// this.handleSynctimeChange()
this.searchData = searchDataDefault()
this.getList()
},

View File

@ -21,6 +21,28 @@
/>
</el-select>
</el-form-item>
<!-- 任务开始日期 -->
<el-form-item label="任务开始日期">
<el-date-picker
v-model="searchData.StartTime"
type="date"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
:picker-options="beginPickerOption"
style="width:140px;"
/>
</el-form-item>
<!-- 任务结束日期 -->
<el-form-item label="任务结束日期">
<el-date-picker
v-model="searchData.EndTime"
type="date"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
:picker-options="endpickerOption"
style="width:140px;"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
@ -91,6 +113,8 @@ const searchDataDefault = () => {
JobState: null,
FileName: '',
Path: '',
StartTime: '',
EndTime: '',
Asc: false,
SortField: '',
PageIndex: 1,
@ -122,6 +146,24 @@ export default {
total: 0,
loading: false,
datetimerange: [],
beginPickerOption: {
disabledDate: time => {
if (this.searchData.EndTime) {
return time.getTime() >= new Date(this.searchData.EndTime).getTime()
} else {
return time.getTime() > Date.now()
}
}
},
endpickerOption: {
disabledDate: time => {
if (this.searchData.StartTime) {
return time.getTime() > Date.now() || time.getTime() <= new Date(this.searchData.StartTime).getTime() - 86400000
} else {
return time.getTime() > Date.now()
}
}
}
}
},
mounted() {