分割数据恢复

main
wangxiaoshuang 2026-05-07 15:20:01 +08:00
parent e100d45bc1
commit 763e00a6ca
4 changed files with 206 additions and 7 deletions

View File

@ -431,4 +431,20 @@ export function studyUndoMaskImage(data) {
method: 'post', method: 'post',
data data
}) })
}
// 获取分割组历史版本
export function getSegmentationVersionList(data) {
return request({
url: `/Segmentation/getSegmentationVersionList`,
method: 'post',
data
})
}
// 恢复分割历史版本
export function restoreSegmentationVersion(data) {
return request({
url: `/Segmentation/restoreSegmentationVersion`,
method: 'post',
data
})
} }

View File

@ -145,6 +145,9 @@
<div class="SegmentGroupBtn" @click.stop="exportSegmentGroup"> <div class="SegmentGroupBtn" @click.stop="exportSegmentGroup">
{{ $t('trials:reading:Segmentations:button:exportSegmentGroup') }} {{ $t('trials:reading:Segmentations:button:exportSegmentGroup') }}
</div> </div>
<div class="SegmentGroupBtn" @click.stop="recoverySegmentGroup">
{{ $t('trials:reading:Segmentations:button:recoverySegmentGroup') }}
</div>
<div class="SegmentGroupBtn" @click.stop="delSegmentGroup"> <div class="SegmentGroupBtn" @click.stop="delSegmentGroup">
{{ $t('trials:reading:Segmentations:button:deleteSegmentGroup') }} {{ $t('trials:reading:Segmentations:button:deleteSegmentGroup') }}
</div> </div>
@ -228,9 +231,10 @@
<span>{{ k }}</span> <span>{{ k }}</span>
<span v-if="item.stats[k]">{{ JSON.stringify(item.stats[k].value) !== 'null' <span v-if="item.stats[k]">{{ JSON.stringify(item.stats[k].value) !== 'null'
? ?
k === 'count' ? Number(item.stats[k].value).toFixed(0) : Number(item.stats[k].value).toFixed(digitPlaces) k === 'count' ? Number(item.stats[k].value).toFixed(0) :
Number(item.stats[k].value).toFixed(digitPlaces)
: null : null
}}<i>{{ item.stats[k].unit }}</i></span> }}<i>{{ item.stats[k].unit }}</i></span>
</div> </div>
</template> </template>
<div class="serialNum" slot="reference">{{ index + 1 }}</div> <div class="serialNum" slot="reference">{{ index + 1 }}</div>
@ -278,10 +282,37 @@
{{ $t("trials:reading:Segmentations:button:saveAll") }} {{ $t("trials:reading:Segmentations:button:saveAll") }}
</el-button> </el-button>
</div> </div>
<el-dialog :visible.sync="visible" :close-on-click-modal="false"
:title="$t('trials:reading:Segmentations:recovery')" width="850px">
<el-table :data="recoveryList" style="width: 100%;background-color: #1e1e1e;">
<el-table-column type="index" width="50">
</el-table-column>
<el-table-column property="Version" :label="$t('trials:reading:Segmentations:table:Version')">
</el-table-column>
<el-table-column property="FileSize" :label="$t('trials:reading:Segmentations:table:FileSize')">
<template slot-scope="scope">
{{ fileSizeFormatter(scope.row.FileSize) }}
</template>
</el-table-column>
<el-table-column property="CreateTime" :label="$t('trials:reading:Segmentations:table:CreateTime')">
</el-table-column>
<el-table-column :label="$t('common:action:action')" align="left" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click.stop="restoreSegmentationVersion(scope.row)">{{
$t('trials:reading:Segmentations:button:recovery')
}}</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination style="text-align: right;margin-top: 5px;" class="page" :total="total"
:page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getSegmentationVersionList(segmentationId)" />
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { changeSegmentationSavedStatus, getSegmentationList, addOrUpdateSegmentation, deleteSegmentation, getSegmentList, addOrUpdateSegment, deleteSegment, getSegmentBindingList, saveSegmentBindingAndAnswer, getReadingTableQuestionTrialById, getReadingQuestionTrialById, lockOrUnLockSegment } from '@/api/reading' import { changeSegmentationSavedStatus, getSegmentationList, addOrUpdateSegmentation, deleteSegmentation, getSegmentList, addOrUpdateSegment, deleteSegment, getSegmentBindingList, saveSegmentBindingAndAnswer, getReadingTableQuestionTrialById, getReadingQuestionTrialById, lockOrUnLockSegment, restoreSegmentationVersion, getSegmentationVersionList } from '@/api/reading'
import * as cornerstoneTools from '@cornerstonejs/tools'; import * as cornerstoneTools from '@cornerstonejs/tools';
import * as cornerstone from "@cornerstonejs/core"; import * as cornerstone from "@cornerstonejs/core";
import dcmjs from '@/utils/dcmUpload/dcmjs' import dcmjs from '@/utils/dcmUpload/dcmjs'
@ -290,6 +321,7 @@ import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent' import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
import { getCustomizeStandardsSegmentDicomTools } from './toolConfig' import { getCustomizeStandardsSegmentDicomTools } from './toolConfig'
import * as polySeg from '@cornerstonejs/polymorphic-segmentation' import * as polySeg from '@cornerstonejs/polymorphic-segmentation'
import Pagination from '@/components/Pagination'
cornerstoneTools.init({ addons: { polySeg } }) cornerstoneTools.init({ addons: { polySeg } })
const { const {
ToolGroupManager, ToolGroupManager,
@ -307,8 +339,20 @@ const { segmentation: segmentationUtils } = CStUtils;
const { cache, getRenderingEngine, imageLoader, eventTarget, metaData, utilities: csUtils, volumeLoader } = cornerstone; const { cache, getRenderingEngine, imageLoader, eventTarget, metaData, utilities: csUtils, volumeLoader } = cornerstone;
// const { downloadDICOMData } = cornerstoneAdapters.helpers; // const { downloadDICOMData } = cornerstoneAdapters.helpers;
const { Cornerstone3D } = cornerstoneAdapters.adaptersSEG; const { Cornerstone3D } = cornerstoneAdapters.adaptersSEG;
const searchDataDefault = () => {
return {
SegmentationId: null,
PageIndex: 1,
PageSize: 20,
Asc: false,
SortField: '',
}
}
export default { export default {
name: "Segmentations", name: "Segmentations",
components: {
Pagination,
},
props: { props: {
isMPR: { isMPR: {
type: Boolean, type: Boolean,
@ -387,6 +431,10 @@ export default {
}, },
data() { data() {
return { return {
visible: false,
recoveryList: [],
searchData: searchDataDefault(),
total: 0,
loading: false, loading: false,
series: {}, series: {},
activeNames: ['tools', 'Segment'], activeNames: ['tools', 'Segment'],
@ -518,6 +566,10 @@ export default {
} }
}, },
methods: { methods: {
fileSizeFormatter(size) {
if (!size) return
return (size / Math.pow(1024, 2)).toFixed(3) + 'MB'
},
showSurface(item) { showSurface(item) {
this.$emit("showSurface", { this.$emit("showSurface", {
segmentationId: item.segmentationId, segmentationId: item.segmentationId,
@ -742,6 +794,59 @@ export default {
} }
// this.resetViewport() // this.resetViewport()
}, },
async restoreSegmentationVersion(row) {
try {
let confirm = await this.$confirm(this.$t('trials:reading:Segmentations:confirm:CurrentDataIsLoss'))
if (!confirm) return false
let data = {
SegmentationId: this.segmentationId,
VersionId: row.Id
}
let res = await restoreSegmentationVersion(data)
if (res.IsSuccess) {
segmentation.removeSegmentation(this.segmentationId)
segmentation.state.removeSegmentation(this.segmentationId)
let volume = cache.getVolume(this.segmentationId)
// 1. Volume
volume.destroy();
// 2.
volume.removeFromCache();
cache.removeImageLoadObject(`wadouri:${this.OSSclientConfig.basePath}${this.curSegmentGroup.segUrl}`)
this.getSegmentation(this.segmentationId)
DicomEvent.$emit('renderSegmentationBychangeSegmention')
this.visible = false
}
return false
} catch (err) {
console.log(err)
return false
}
},
async getSegmentationVersionList(segmentationId) {
try {
this.searchData.SegmentationId = segmentationId || this.segmentationId
let res = await getSegmentationVersionList(this.searchData)
if (res.IsSuccess) {
this.recoveryList = res.Result.CurrentPageData
// this.visible = true
this.total = res.Result.TotalCount
return true
}
return false
} catch (err) {
console.log(err)
return false
}
},
async recoverySegmentGroup() {
try {
let res = await this.getSegmentationVersionList(this.segmentationId)
if (!res) return this.$message.warning(this.$t("trials:reading:Segmentations:message:getSegmentationVersionFail"))
this.visible = true
} catch (err) {
console.log(err)
}
},
selectSegmentGroup(s) { selectSegmentGroup(s) {
// segmentation.activeSegmentation.setActiveSegmentation(`${this.viewportKey}-${this.activeViewportIndex}`, this.segmentationId) // segmentation.activeSegmentation.setActiveSegmentation(`${this.viewportKey}-${this.activeViewportIndex}`, this.segmentationId)
this.$emit('setToolsPassive') this.$emit('setToolsPassive')
@ -1558,6 +1663,7 @@ export default {
segmentGroup.segUrl = this.$getObjectName(result.url) segmentGroup.segUrl = this.$getObjectName(result.url)
DicomEvent.$emit("IsBeSegment", { StudyId: this.series.StudyId, Id: this.series.Id, IsBeSegment: true }) DicomEvent.$emit("IsBeSegment", { StudyId: this.series.StudyId, Id: this.series.Id, IsBeSegment: true })
} else { } else {
blob = { size: 0 }
segmentGroup.segUrl = null segmentGroup.segUrl = null
} }
@ -1688,7 +1794,7 @@ export default {
}, },
// //
async addOrUpdateSegmentation(item) { async addOrUpdateSegmentation(item) {
let { name, id, url } = item let { name, id, url, size } = item
try { try {
let data = { let data = {
SegmentationName: name, SegmentationName: name,
@ -1701,6 +1807,7 @@ export default {
} }
if (url) data.SegUrl = url; if (url) data.SegUrl = url;
if (id) data.Id = id; if (id) data.Id = id;
if (size) data.FileSize = size;
this.loading = true; this.loading = true;
let res = await addOrUpdateSegmentation(data); let res = await addOrUpdateSegmentation(data);
this.loading = false; this.loading = false;
@ -1712,8 +1819,64 @@ export default {
console.log(err) console.log(err)
} }
}, },
async getSegmentation(segmentationId) {
try {
this.$emit('setToolsPassive')
let data = {
SeriesId: this.series.Id,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
this.loading = true;
let res = await getSegmentationList(data);
this.loading = false;
if (res.IsSuccess) {
// this.segmentList = []
// this.segmentationId = null;
// this.segmentIndex = null;
let list = res.Result.CurrentPageData;
for (let i = 0; i < list.length; i++) {
let item = list[i]
if (item.Id !== segmentationId) continue;
let obj = this.segmentList.find(i => i.segmentationId === item.Id)
if (!obj) continue;
obj.name = item.SegmentationName;
obj.segUrl = item.SEGUrl;
obj.isSaved = item.IsSaved;
obj.segmentationId = item.Id;
obj.segments = []
if (!this.segmentationId) {
this.segmentationId = obj.segmentationId
}
let segments = await this.getSegmentList(item.Id)
segments.forEach((s, index) => {
let SegmentJson = s.SegmentJson ? JSON.parse(s.SegmentJson) : {};
let o = {
segmentIndex: s.SegmentNumber,
segmentationId: s.SegmentationId,
SegmentLabel: s.SegmentName,
color: s.ColorRgb,
stats: SegmentJson.stats,
bidirectional: SegmentJson.bidirectional,
bidirectionalView: true,
view: true,
lock: s.IsLock,
id: s.Id
}
obj.segments.push(o)
})
this.segmentIndex = obj.segments[0].segmentIndex
}
this.isloaded = false
}
} catch (err) {
console.log(err)
}
},
// //
async getSegmentationList(SEGMENT = null) { async getSegmentationList() {
try { try {
this.$emit('setToolsPassive') this.$emit('setToolsPassive')
let data = { let data = {
@ -1998,6 +2161,21 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-table th.el-table__cell {
background-color: #1e1e1e;
}
::v-deep .el-table tr {
background-color: #1e1e1e;
color: #fff;
}
::v-deep .hover-row>td.el-table__cell {
background-color: #1e1e1e !important;
// opacity: 0.5;
color: #fff;
}
.Segmentations { .Segmentations {
height: 100%; height: 100%;
position: relative; position: relative;

View File

@ -59,10 +59,10 @@
<div v-if="series" class="right-bottom-text"> <div v-if="series" class="right-bottom-text">
<div v-show="imageInfo.location">Location: {{ <div v-show="imageInfo.location">Location: {{
`${Number(imageInfo.location).toFixed(digitPlaces)} mm` `${Number(imageInfo.location).toFixed(digitPlaces)} mm`
}}</div> }}</div>
<div v-show="imageInfo.sliceThickness">Slice Thickness: {{ <div v-show="imageInfo.sliceThickness">Slice Thickness: {{
`${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm` `${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm`
}}</div> }}</div>
<div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div> <div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div>
</div> </div>
<div class="orientation-top"> <div class="orientation-top">
@ -242,6 +242,10 @@ export default {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
resetViewport(this.viewportId) resetViewport(this.viewportId)
}) })
DicomEvent.$on('renderSegmentationBychangeSegmention', async () => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
await renderSegmentation(this.series, this.series.TaskInfo, this.viewportId, this.SegmentConfig, this.renderingEngineId, null, this.actionConfiguration, this.segmentationId, this.segmentIndex)
})
DicomEvent.$on('renderSegmentation', async (viewportId) => { DicomEvent.$on('renderSegmentation', async (viewportId) => {
// if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false // if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (this.viewportId !== viewportId) return false if (this.viewportId !== viewportId) return false

View File

@ -67,6 +67,7 @@ async function readSegmentation(obj, series, segmentationId, isFile = false) {
const imageIdObj = await cornerstoneDICOMImageLoader.wadouri.loadImage(`wadouri:${Vue.prototype.OSSclientConfig.basePath}${obj}`).promise const imageIdObj = await cornerstoneDICOMImageLoader.wadouri.loadImage(`wadouri:${Vue.prototype.OSSclientConfig.basePath}${obj}`).promise
imageId = imageIdObj.imageId imageId = imageIdObj.imageId
} }
console.log(imageId, 'imageId')
const image = await imageLoader.loadAndCacheImage(imageId); const image = await imageLoader.loadAndCacheImage(imageId);
if (!image) { if (!image) {