分割对象的体素统计以及长短径计算优化、分割组添加状态
continuous-integration/drone/push Build is passing Details

uat_us
wangxiaoshuang 2026-03-31 13:47:06 +08:00
parent 72d0494baa
commit 307c02725f
5 changed files with 250 additions and 112 deletions

View File

@ -399,4 +399,12 @@ export function getReadingQuestionTrialById(params) {
method: 'post',
params
})
}
// 修改分割组保存状态
export function changeSegmentationSavedStatus(data) {
return request({
url: `/Segmentation/changeSegmentationSavedStatus`,
method: 'post',
data
})
}

View File

@ -276,7 +276,6 @@ export default {
let imageId = imageIds[0]
let volume = cache.getVolume(this.volumeId)
let spacing = volume ? volume.spacing : []
console.log(spacing, 'spacing')
// if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId()
if (imageId && volume) {
this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices })
@ -501,7 +500,6 @@ export default {
DicomEvent.$emit("isloaded", {})
}
}]).then(r => {
console.log(this.imageInfo.zoom, 'this.imageInfo.zoom')
if (data.isLocation || !this.imageInfo.zoom) {
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
}

View File

@ -2041,14 +2041,8 @@ export default {
},
setCrosshairsToolLineColor(viewportId) {
let colors = [
'#0ca8df',
'#ffd10a',
'#b6d634',
'#3fbe95',
'#785db0',
'#5070dd',
'#505372',
'#ff994d',
'#fb628b',
]
let index = viewportId.split("-").pop()
@ -2534,6 +2528,14 @@ export default {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resetOrientationMarkers()
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
viewport.resetProperties()
if (this.isMPR) {
let volume = cache.getVolume(this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
const voi = metaData.get('voiLutModule', volume._imageIds[Math.ceil((volume._imageIds.length - 1) / 2)])
const lower = voi.windowCenter[0] - voi.windowWidth[0] / 2
const upper = voi.windowCenter[0] + voi.windowWidth[0] / 2 - 1
console.log(lower, upper)
viewport.setProperties({ voiRange: { upper: upper, lower: lower } })
}
viewport.render()
renderingEngine.render()
if (this.readingTool === 3) {

View File

@ -1,5 +1,5 @@
<template>
<el-form ref="segmentForm" :model="form" label-width="120px" label-position="left">
<el-form ref="segmentForm" :model="form" label-width="120px" label-position="left" :rules="rules">
<!-- 检查名称 -->
<el-form-item :label="$t('segment:form:label:studyName')" prop="taskBlindName">
<el-select v-model="form.studyId" clearable @change="(e) => handleChange(e, 'study')">
@ -15,7 +15,7 @@
</el-select>
</el-form-item>
<!-- 分割组名称 -->
<el-form-item :label="$t('segment:form:label:segmentGroupName')" prop="taskBlindName">
<el-form-item :label="$t('segment:form:label:segmentGroupName')" prop="segmentGroupId">
<el-select v-model="form.segmentGroupId" clearable @change="(e) => handleChange(e, 'segmentGroup')">
<el-option v-for="item in segmentGroupList" :key="item.Id" :label="item.SegmentationName"
:value="item.Id" />
@ -31,7 +31,7 @@
<!-- 取消 -->
<el-button size="mini" @click="handleCancel">{{ $t('common:button:cancel') }}</el-button>
<!-- 确认 -->
<el-button type="primary" size="mini" @click="handleSave">
<el-button type="primary" size="mini" @click="handleSave" :disabled="!form.segmentId">
{{ $t('common:button:confirm') }}</el-button>
</el-form-item>
</el-form>
@ -65,7 +65,26 @@ export default {
segmentGroupList: [],
segmentList: [],
segmentionList: [],
series: {}
series: {},
rules: {
segmentGroupId: [
{
validator: (rule, value, callback) => {
if (value) {
// callback(new Error(''));
let segmentGroup = this.segmentGroupList.find(item => item.Id === value)
if (!segmentGroup.IsSaved) {
callback(new Error(this.$t('segment:confirm:message:segmentGroupIsNotSave')));
} else {
callback();
}
} else {
callback();
}
}, trigger: ['blur', 'change']
}
]
}
}
},
// mounted() {
@ -126,7 +145,9 @@ export default {
handleCancel() {
this.$emit("update:visible", false)
},
handleSave() {
async handleSave() {
let validate = await this.$refs.segmentForm.validate()
if (!validate) return false
let segment = this.segmentList.find(item => item.Id === this.form.segmentId)
if (segment.SegmentJson) {
let obj = JSON.parse(segment.SegmentJson)

View File

@ -120,7 +120,7 @@
</el-switch>
<span style="margin-left: 5px;">{{
$t('trials:reading:Segmentations:title:InactiveSegmentationsShow')
}}</span>
}}</span>
</div>
<!-- <div class="SegmentConfig" v-if="SegmentConfig.InactiveSegmentations.show">
<span>{{ $t('trials:reading:Segmentations:title:Opacity') }}</span>
@ -130,28 +130,38 @@
</div>
<template v-if="segmentList.length > 0">
<div class="SegmentGroupBox">
<el-popover placement="left" width="40" trigger="click">
<div class="SegmentGroupBtnBox">
<div class="SegmentGroupBtn" @click.stop="addSegmentGroup">
{{ $t('trials:reading:Segmentations:button:addSegmentGroup') }}
<div style="display: flex;align-items: center;">
<el-popover placement="left" width="40" trigger="click">
<div class="SegmentGroupBtnBox">
<div class="SegmentGroupBtn" @click.stop="addSegmentGroup">
{{ $t('trials:reading:Segmentations:button:addSegmentGroup') }}
</div>
<div class="SegmentGroupBtn" @click.stop="rename('segmentGroup')">
{{ $t('trials:reading:Segmentations:button:renameSegmentGroup') }}
</div>
<div class="SegmentGroupBtn" @click.stop="exportSegmentGroup">
{{ $t('trials:reading:Segmentations:button:exportSegmentGroup') }}
</div>
<div class="SegmentGroupBtn" @click.stop="delSegmentGroup">
{{ $t('trials:reading:Segmentations:button:deleteSegmentGroup') }}
</div>
</div>
<div class="SegmentGroupBtn" @click.stop="rename('segmentGroup')">
{{ $t('trials:reading:Segmentations:button:renameSegmentGroup') }}
</div>
<div class="SegmentGroupBtn" @click.stop="exportSegmentGroup">
{{ $t('trials:reading:Segmentations:button:exportSegmentGroup') }}
</div>
<div class="SegmentGroupBtn" @click.stop="delSegmentGroup">
{{ $t('trials:reading:Segmentations:button:deleteSegmentGroup') }}
</div>
</div>
<i slot="reference" class="el-icon-more" style="cursor: pointer;color:#fff" />
</el-popover>
<el-select v-model="segmentationId" placeholder="" @change="selectSegmentGroup">
<el-option v-for="item in segmentList" :key="`${item.segmentationId}`" :label="item.name"
:value="item.segmentationId">
</el-option>
</el-select>
<i slot="reference" class="el-icon-more" style="cursor: pointer;color:#fff" />
</el-popover>
<el-select v-model="segmentationId" placeholder="" @change="selectSegmentGroup">
<el-option v-for="item in segmentList" :key="`${item.segmentationId}`"
:label="item.name" :value="item.segmentationId">
</el-option>
</el-select>
</div>
<div style="float: right;">
<i class="el-icon-warning-outline" style="color:red;margin-right: 5px;"
:title="$t('trials:reading:Segmentations:tip:segmentationIsNotSave')"
v-if="!curSegmentGroup.isSaved"></i>
<el-button type="success" size="small" @click="saveSegmentGroup([curSegmentGroup])">
{{ $t("common:button:save") }}
</el-button>
</div>
</div>
<div class="addSegmentBox" @click.stop="addSegment"
style="display: flex;align-items: center;justify-content: space-between;">
@ -191,7 +201,7 @@
<div v-for="k in statsKey" :key="k" class="statsBox">
<span>{{ k }}</span>
<span v-if="item.stats[k]">{{ Number(item.stats[k].value).toFixed(2)
}}<i>{{ item.stats[k].unit }}</i></span>
}}<i>{{ item.stats[k].unit }}</i></span>
</div>
</template>
<div class="serialNum" slot="reference">{{ index + 1 }}</div>
@ -234,12 +244,12 @@
</el-collapse>
<div class="saveBtnBox">
<el-button type="success" size="small" @click="saveSegmentGroup()">{{ $t("common:button:save")
}}</el-button>
}}</el-button>
</div>
</div>
</template>
<script>
import { getSegmentationList, addOrUpdateSegmentation, deleteSegmentation, getSegmentList, addOrUpdateSegment, deleteSegment, getSegmentBindingList, saveSegmentBindingAndAnswer, getReadingTableQuestionTrialById, getReadingQuestionTrialById } from '@/api/reading'
import { changeSegmentationSavedStatus, getSegmentationList, addOrUpdateSegmentation, deleteSegmentation, getSegmentList, addOrUpdateSegment, deleteSegment, getSegmentBindingList, saveSegmentBindingAndAnswer, getReadingTableQuestionTrialById, getReadingQuestionTrialById } from '@/api/reading'
import * as cornerstoneTools from '@cornerstonejs/tools';
import * as cornerstone from "@cornerstonejs/core";
import dcmjs from '@/utils/dcmUpload/dcmjs'
@ -352,7 +362,7 @@ export default {
viewprotIds: ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3', 'viewport-MPR-0', 'viewport-MPR-1', 'viewport-MPR-2'], //
statsKey: [],
drawing: false, //
isDel: false,
// isDel: false,
digitPlaces: 2,
isloaded: false,
popoverId: null
@ -462,7 +472,9 @@ export default {
this.actionConfiguration.contourBidirectional.data.segmentationId = segmentationId
this.actionConfiguration.contourBidirectional.data.segmentIndex = segmentIndex
},
addTip(item) {
async addTip(item) {
let segmentGroup = this.segmentList.filter(item => i.segmentationId === i.segmentationId)
await this.saveSegmentGroup(segmentGroup, false)
this.calculateStatistics([item.segmentIndex], item.segmentationId, 'individual');
this.getBidirectional([item])
@ -633,6 +645,7 @@ export default {
lockSegment(item, lock) {
segmentation.segmentLocking.setSegmentIndexLocked(item.segmentationId, item.segmentIndex, lock)
item.lock = lock
if (!lock) this.changeSegmentationSavedStatus(item.segmentationId, lock)
},
selectSegment(item) {
this.segmentationId = item.segmentationId;
@ -659,6 +672,7 @@ export default {
name: this.$t('trials:reading:Segmentations:name:SegmentGroup') + (this.segmentList.length + 1),
view: true,
segmentationId: null,
isSaved: false,
segments: []
}
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
@ -693,6 +707,7 @@ export default {
let obj = {
name: this.$t('trials:reading:Segmentations:name:SegmentGroup') + 1,
view: true,
isSaved: false,
segments: []
}
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
@ -820,7 +835,7 @@ export default {
this.selectSegment(this.segmentList[groupIndex].segments[0])
}
this.$emit('resetQuestion')
this.saveSegmentGroup([this.segmentList[groupIndex]])
this.saveSegmentGroup([this.segmentList[groupIndex]], false)
},
resetViewport(passive = true) {
@ -995,7 +1010,7 @@ export default {
}
};
if (segment.bidirectional && segment.stats) labelmap3D.metadata[segmentIndex] = segmentMetadata;
if (segment.stats) labelmap3D.metadata[segmentIndex] = segmentMetadata;
}
});
if (labelmap3D.metadata.length <= 0) {
@ -1207,12 +1222,6 @@ export default {
},
segmentationModifiedCallback(evt) {
const { detail } = evt;
// console.log(detail)
if (detail.segmentIndex === 0) {
this.drawing = true
this.isDel = true
}
// || !detail.segmentIndex
if (!detail || detail.segmentIndex === 255) {
return;
}
@ -1271,31 +1280,24 @@ export default {
this.timeoutId = setTimeout(() => {
this.timeoutId = null;
this.drawing = false;
if (!this.isDel) {
this.calculateStatistics([this.segmentIndex], this.segmentationId, 'individual');
let segmentGroup = this.segmentList.find(item => item.segmentationId === this.segmentationId)
let segment = segmentGroup ? segmentGroup.segments.find(item => item.segmentIndex === this.segmentIndex) : null
if (segment) this.getBidirectional([segment])
} else {
let segmentGroup = this.segmentList.find(item => item.segmentationId === this.segmentationId)
if (segmentGroup && segmentGroup.segments && segmentGroup.segments.length > 0) {
let segmentIndexs = []
segmentGroup.segments.forEach(item => {
segmentIndexs.push(item.segmentIndex)
})
annotation.state.getAllAnnotations().forEach(i => {
if (i.metadata.segmentationId === this.segmentationId && i.metadata.toolName === "SegmentBidirectional") {
annotation.state.removeAnnotation(i.annotationUID)
}
})
this.calculateStatistics(segmentIndexs, this.segmentationId, 'individual');
this.getBidirectional(segmentGroup.segments)
}
let segmentGroup = this.segmentList.find(item => item.segmentationId === this.segmentationId)
if (segmentGroup && segmentGroup.segments && segmentGroup.segments.length > 0) {
let segmentIndexs = []
segmentGroup.segments.forEach(item => {
segmentIndexs.push(item.segmentIndex)
item.bidirectional = null
item.stats = null
})
annotation.state.getAllAnnotations().forEach(i => {
if (i.metadata.segmentationId === this.segmentationId && i.metadata.toolName === "SegmentBidirectional") {
annotation.state.removeAnnotation(i.annotationUID)
}
})
this.calculateStatistics(segmentIndexs, this.segmentationId, 'individual');
this.resetViewport(false)
// this.getBidirectional(segmentGroup.segments)
}
this.isDel = false
}, 500);
},
//
@ -1331,51 +1333,28 @@ export default {
}
},
//
async saveSegmentGroup(list = null) {
async saveSegmentGroup(list = null, saveSegment = true) {
try {
let segmentList = list ? list : this.segmentList
if (segmentList.length <= 0) return false
this.$emit("setToolsPassive")
let questionNeedChange = false;
let bindingList = []
for (let i = 0; i < segmentList.length; i++) {
let segmentGroup = segmentList[i]
let data = {
SegmentationId: segmentGroup.segmentationId
}
let res = await this.getSegmentBindingList(data)
if (res && res.length > 0) questionNeedChange = true
for (let j = 0; j < res.length; j++) {
let item = res[j]
let segment = segmentGroup.segments.find(d => d.id === item.SegmentId)
let question = await this.getQuestionConfig(item.TableQuestionId ? item.TableQuestionId : item.QuestionId, !!item.TableQuestionId);
let imageToolAttribute = question.ImageToolAttribute
let answer = ''
if (imageToolAttribute === 'length' || imageToolAttribute === 'width') {
let s = {
length: "maxMajor",
width: 'maxMinor'
}
answer = segment.bidirectional && segment.bidirectional[s[imageToolAttribute]] ? Number(segment.bidirectional[s[imageToolAttribute]]).toFixed(this.digitPlaces) : ''
} else {
answer = segment.stats && segment.stats[imageToolAttribute] ? Number((segment.stats[imageToolAttribute]).value).toFixed(this.digitPlaces) : ''
if (saveSegment) {
for (let i = 0; i < segmentList.length; i++) {
let segmentGroup = segmentList[i]
let data = {
SegmentationId: segmentGroup.segmentationId
}
let o = {
Answer: answer,
QuestionId: item.QuestionId,
RowId: item.RowId,
SegmentId: item.SegmentId,
SegmentationId: item.SegmentationId,
TableQuestionId: item.TableQuestionId,
VisitTaskId: this.visitInfo.VisitTaskId,
let res = await this.getSegmentBindingList(data)
if (res && res.length > 0) {
questionNeedChange = true
break
}
bindingList.push(o)
}
}
if (questionNeedChange) {
let confirm = await this.$confirm(this.$t("segment:confirm:questionNeedChange"))
if (!confirm) return false
if (questionNeedChange) {
let confirm = await this.$confirm(this.$t("segment:confirm:questionNeedChange"))
if (!confirm) return false
}
}
this.$emit("update:globalLoading", true)
this.$emit("update:loadingText", this.$t("segment:loadingText:saveSegmentation"))
@ -1387,6 +1366,7 @@ export default {
})
//
let blob = this.exportSegmentation(segmentGroup.segmentationId, segmentGroup)
console.log(blob, 'blob')
// if (!blob) return false
if (blob) {
let path = `/${this.$route.query.trialId}/Segment/${this.visitInfo.SubjectId
@ -1397,17 +1377,126 @@ export default {
}
this.addOrUpdateSegmentation({ name: segmentGroup.name, id: segmentGroup.segmentationId, url: segmentGroup.segUrl })
segmentGroup.segments.forEach(s => {
this.addOrUpdateSegment({ name: s.SegmentLabel, color: s.color, segmentIndex: s.segmentIndex, segmentationId: s.segmentationId, segmentJson: JSON.stringify({ stats: s.stats, bidirectional: s.bidirectional }), id: s.id })
})
this.changeSegmentationSavedStatus(segmentGroup.segmentationId, saveSegment)
if (saveSegment) {
await this.getBidirectionalSaveSegment(list)
this.syncBindingAnswer(list)
}
}
if (bindingList.length > 0) this.saveSegmentBindingAndAnswer(bindingList)
this.$emit("update:globalLoading", false)
} catch (err) {
this.loading = false
console.log(err)
}
},
//
async getBidirectionalSaveSegment(list) {
for (let i = 0; i < list.length; i++) {
let segmentGroup = list[i]
if (segmentGroup.segments && segmentGroup.segments.length > 0) {
for (let j = 0; j < segmentGroup.segments.length; j++) {
let segment = segmentGroup.segments[j]
let res = await Promise.race([this.getBidirectionalToSegment([segment]), this.setDelay(300000)])
if (res) {
this.addOrUpdateSegment({ name: segment.SegmentLabel, color: segment.color, segmentIndex: segment.segmentIndex, segmentationId: segment.segmentationId, segmentJson: JSON.stringify({ stats: segment.stats, bidirectional: segment.bidirectional }), id: segment.id })
}
}
}
}
},
getBidirectionalToSegment(list) {
return new Promise(async (reslove, reject) => {
list.forEach(item => {
this.createSegmentConfiguration(item.segmentIndex, item.segmentationId);
})
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId);
[viewport.element].forEach(async (element) => {
const bidirectionalData =
await CStUtils.segmentation.getSegmentLargestBidirectional({
segmentationId: list[0].segmentationId,
segmentIndices: list.map(item => item.segmentIndex),
});
console.log(bidirectionalData)
if (bidirectionalData.length <= 0) {
list.forEach(item => {
let annotations = annotation.state.getAllAnnotations().filter(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex);
annotations.forEach(i => {
annotation.state.removeAnnotation(i.annotationUID)
})
item.bidirectional = null
})
this.resetViewport(false)
return reslove(true)
}
let bidirectional = bidirectionalData[0]
const { segmentIndex } = bidirectional;
const { majorAxis, minorAxis, maxMajor, maxMinor } = bidirectional;
let item = list.find(i => i.segmentIndex === segmentIndex)
SegmentBidirectionalTool.hydrate(viewportId, [majorAxis, minorAxis], {
segmentIndex,
segmentationId: item.segmentationId,
});
let an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === bidirectional.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
if (an) {
annotation.locking.setAnnotationLocked(an.annotationUID, true)
annotation.visibility.setAnnotationVisibility(an.annotationUID, item.bidirectionalView)
}
item.bidirectional = bidirectional
reslove(true)
});
})
},
setDelay(time) {
return new Promise(async (reslove, reject) => {
setTimeout(() => { reslove(true) }, time)
})
},
async syncBindingAnswer(segmentList) {
let bindingList = []
for (let i = 0; i < segmentList.length; i++) {
let segmentGroup = segmentList[i]
let data = {
SegmentationId: segmentGroup.segmentationId
}
let res = await this.getSegmentBindingList(data)
if (res && res.length > 0) questionNeedChange = true
for (let j = 0; j < res.length; j++) {
let item = res[j]
let segment = segmentGroup.segments.find(d => d.id === item.SegmentId)
let question = await this.getQuestionConfig(item.TableQuestionId ? item.TableQuestionId : item.QuestionId, !!item.TableQuestionId);
let imageToolAttribute = question.ImageToolAttribute
let answer = ''
if (imageToolAttribute === 'length' || imageToolAttribute === 'width') {
let s = {
length: "maxMajor",
width: 'maxMinor'
}
answer = segment.bidirectional && segment.bidirectional[s[imageToolAttribute]] ? Number(segment.bidirectional[s[imageToolAttribute]]).toFixed(this.digitPlaces) : ''
} else {
answer = segment.stats && segment.stats[imageToolAttribute] ? Number((segment.stats[imageToolAttribute]).value).toFixed(this.digitPlaces) : ''
}
let o = {
Answer: answer,
QuestionId: item.QuestionId,
RowId: item.RowId,
SegmentId: item.SegmentId,
SegmentationId: item.SegmentationId,
TableQuestionId: item.TableQuestionId,
VisitTaskId: this.visitInfo.VisitTaskId,
}
bindingList.push(o)
}
}
if (bindingList.length > 0) this.saveSegmentBindingAndAnswer(bindingList)
},
//
async addOrUpdateSegmentation(item) {
let { name, id, url } = item
@ -1458,6 +1547,7 @@ export default {
name: item.SegmentationName,
view: true,
segUrl: item.SEGUrl,
isSaved: item.IsSaved,
segments: []
}
this.segmentList.push(obj)
@ -1593,6 +1683,23 @@ export default {
console.log(err)
}
},
//
async changeSegmentationSavedStatus(Id, IsSaved) {
try {
let params = {
IsSaved,
Id
}
let res = await changeSegmentationSavedStatus(params)
if (res.IsSuccess) {
let segmentGroup = this.segmentList.find(item => item.segmentationId === Id)
if (segmentGroup) segmentGroup.isSaved = IsSaved
}
} catch (err) {
console.log(err)
}
},
hex2Rgb(hexValue, alpha = 1) {
const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b);
@ -1712,6 +1819,7 @@ export default {
.BidirectionalBtn {
width: 100%;
cursor: pointer;
}
.num {
@ -1836,6 +1944,7 @@ export default {
border-radius: 3px;
display: flex;
align-items: center;
justify-content: space-between;
i {
font-size: 20px;