2182 lines
91 KiB
Vue
2182 lines
91 KiB
Vue
<template>
|
|
<div class="Segmentations" v-loading="loading">
|
|
<el-collapse v-model="activeNames">
|
|
<el-collapse-item name="tools">
|
|
<template slot="title">
|
|
{{ $t('trials:reading:Segmentations:title:tools') }}
|
|
</template>
|
|
<div class="tool-frame">
|
|
<div :title="$t('trials:Segmentations:tools:contour')"
|
|
:class="['tool-item', activeTool === 'LabelMapEditWithContour' && segmentList.length > 0 ? 'tool-item-active' : '']"
|
|
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
|
|
@click.prevent="setToolActive('LabelMapEditWithContour')">
|
|
<svg-icon icon-class="contour" class="svg-icon" />
|
|
</div>
|
|
<div :title="$t('trials:Segmentations:tools:thresholecircle')"
|
|
:class="['tool-item', ThresholdTools.includes(activeTool) && segmentList.length > 0 ? 'tool-item-active' : '']"
|
|
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
|
|
@click.prevent="initThreshold">
|
|
<svg-icon icon-class="thresholecircle" class="svg-icon" />
|
|
</div>
|
|
<div :title="$t('trials:Segmentations:tools:circularbrush')"
|
|
:class="['tool-item', activeTool === 'CircularBrush' && segmentList.length > 0 ? 'tool-item-active' : '']"
|
|
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
|
|
@click.prevent="setToolActive('CircularBrush')">
|
|
<svg-icon icon-class="circularbrush" class="svg-icon" />
|
|
</div>
|
|
<div :class="['tool-item', activeTool === 'CircularEraser' && segmentList.length > 0 ? 'tool-item-active' : '']"
|
|
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
|
|
:title="$t('trials:dicom-show:Eraser')" @click.prevent="setToolActive('CircularEraser')">
|
|
<svg-icon icon-class="clear" class="svg-icon" />
|
|
</div>
|
|
<!-- <div :class="['tool-item']">
|
|
<input type="file" @change="beginScanFiles($event)">
|
|
</div> -->
|
|
</div>
|
|
<div class="ConfigBox">
|
|
<div class="EraserConfig"
|
|
v-if="activeTool === 'CircularEraser' || activeTool === 'CircularBrush' || ThresholdTools.includes(activeTool)">
|
|
<span>{{ $t('trials:reading:Segmentations:title:EraserConfig') }}</span>
|
|
<el-slider v-model="brushSize" show-input :step="1" :max="100" input-size="mini"
|
|
:show-input-controls="false" />
|
|
</div>
|
|
<div class="EraserConfig" v-if="ThresholdTools.includes(activeTool)">
|
|
<span>{{ $t('trials:reading:Segmentations:title:thresholdType') }}</span>
|
|
<el-radio-group v-model="thresholdType" @input="(key) => setToolActive(key)">
|
|
<el-radio-button :label="tool" v-for="tool of ThresholdTools" :key="tool" size="mini">
|
|
{{ $t(`trials:reading:Segmentations:radioButton:${tool}`) }}
|
|
</el-radio-button>
|
|
</el-radio-group>
|
|
</div>
|
|
<div class="EraserConfig" v-if="ThresholdTools.includes(activeTool)">
|
|
<span>{{ $t('trials:reading:Segmentations:title:isDynamic') }}</span>
|
|
<el-radio-group v-model="brushThreshold.isDynamic">
|
|
<el-radio-button :label="true" size="mini">
|
|
{{ $t('trials:reading:Segmentations:radioButton:Dynamic') }}
|
|
</el-radio-button>
|
|
<el-radio-button :label="false" size="mini">
|
|
{{ $t('trials:reading:Segmentations:radioButton:Rang') }}
|
|
</el-radio-button>
|
|
</el-radio-group>
|
|
</div>
|
|
<div class="EraserConfig" v-if="ThresholdTools.includes(activeTool) && !brushThreshold.isDynamic">
|
|
<span>{{ $t('trials:reading:Segmentations:title:range') }}</span>
|
|
<div style="display: flex;align-items: center;">
|
|
<el-input-number v-model="brushThreshold.range[0]" :controls="false" :step="1"
|
|
:max="brushThreshold.range[1]" size="small" />
|
|
<span style="margin: 0 10px;">--</span>
|
|
<el-input-number v-model="brushThreshold.range[1]" :controls="false" :step="1"
|
|
:min="brushThreshold.range[0]" size="small" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-collapse-item>
|
|
<el-collapse-item name="Segment">
|
|
<template slot="title">
|
|
<div class="SegmentTitle">
|
|
{{ $t('trials:reading:Segmentations:title:Segment') }}
|
|
<svg-icon icon-class="individuation" class="svg-icon" @click.stop="changeShowSegmentConfig" />
|
|
</div>
|
|
</template>
|
|
<div class="addSegmentBox viewHover" @click.stop="addSegment" v-if="segmentList.length <= 0">
|
|
<span><i class="el-icon-plus"></i>
|
|
{{ $t('trials:reading:Segmentations:button:addSegmention') }}
|
|
</span>
|
|
</div>
|
|
<div class="SegmentConfigBox" v-if="showSegmentConfig && segmentList.length > 0">
|
|
<div class="SegmentConfig">
|
|
<span>{{ $t('trials:reading:Segmentations:title:Show:Fill&Outline') }}</span>
|
|
<div style="display: flex;">
|
|
<div :class="['tool-item', SegmentConfig.renderOutline && SegmentConfig.renderFill ? 'tool-item-active' : '']"
|
|
:title="$t('trials:dicom-show:Eraser')" @click.stop="changeSegmentConfig(true, true)">
|
|
<svg-icon icon-class="fill_outline" class="svg-icon" />
|
|
</div>
|
|
<div :class="['tool-item', SegmentConfig.renderOutline && !SegmentConfig.renderFill ? 'tool-item-active' : '']"
|
|
:title="$t('trials:dicom-show:Eraser')" @click.stop="changeSegmentConfig(true, false)">
|
|
<svg-icon icon-class="outline" class="svg-icon" />
|
|
</div>
|
|
<div :class="['tool-item', !SegmentConfig.renderOutline && SegmentConfig.renderFill ? 'tool-item-active' : '']"
|
|
:title="$t('trials:dicom-show:Eraser')" @click.stop="changeSegmentConfig(false, true)">
|
|
<svg-icon icon-class="fill" class="svg-icon" />
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
<div class="SegmentConfig">
|
|
<span>{{ $t('trials:reading:Segmentations:title:Opacity') }}</span>
|
|
<el-slider v-model="SegmentConfig.fillAlpha" show-input :step="0.1" :max="1" input-size="mini"
|
|
:show-input-controls="false" />
|
|
</div>
|
|
<div class="SegmentConfig">
|
|
<span>{{ $t('trials:reading:Segmentations:title:Border') }}</span>
|
|
<el-slider v-model="SegmentConfig.outlineWidth" show-input :step="0.5" :max="10"
|
|
input-size="mini" :show-input-controls="false" />
|
|
</div>
|
|
<span class="line" />
|
|
<div class="SegmentConfig" style="justify-content: flex-start;">
|
|
<el-switch v-model="SegmentConfig.InactiveSegmentations.show" :active-value="true"
|
|
:inactive-value="false">
|
|
</el-switch>
|
|
<span style="margin-left: 5px;">{{
|
|
$t('trials:reading:Segmentations:title:InactiveSegmentationsShow')
|
|
}}</span>
|
|
</div>
|
|
<!-- <div class="SegmentConfig" v-if="SegmentConfig.InactiveSegmentations.show">
|
|
<span>{{ $t('trials:reading:Segmentations:title:Opacity') }}</span>
|
|
<el-slider v-model="SegmentConfig.InactiveSegmentations.fillAlpha" show-input :step="0.1"
|
|
:max="1" input-size="mini" :show-input-controls="false" />
|
|
</div> -->
|
|
</div>
|
|
<template v-if="segmentList.length > 0">
|
|
<div class="SegmentGroupBox">
|
|
<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>
|
|
<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("trials:reading:Segmentations:button:save") }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
<div class="addSegmentBox" @click.stop="addSegment"
|
|
style="display: flex;align-items: center;justify-content: space-between;">
|
|
<span><i class="el-icon-plus"></i>
|
|
{{ $t('trials:reading:Segmentations:button:addSegment') }}
|
|
</span>
|
|
<svg-icon :icon-class="!curSegmentGroup.view ? 'eye' : 'eye-open'"
|
|
@click.stop="viewSegmentGroup(segmentList.find(i => i.segmentationId === segmentationId))" />
|
|
</div>
|
|
<div
|
|
:style="`overflow-y: auto;max-height:${showSegmentConfig ? (SegmentHight - 220) : SegmentHight}px;`">
|
|
<div :class="['SegmentBox', item && item.segmentIndex === segmentIndex ? 'SegmentBox_active' : '']"
|
|
v-for="(item, index) in curSegmentGroup.segments"
|
|
:key="`${item.segmentationId}_${item.segmentIndex}`" @click.stop="selectSegment(item)">
|
|
<div class="messageBox">
|
|
<el-popover placement="left" :title="item.SegmentLabel" width="200" trigger="hover">
|
|
<div class="Bidirectionalbox">
|
|
<div class="BidirectionalBtn" @click.stop="addTip(item)"
|
|
v-if="!item.bidirectional">
|
|
{{ $t('trials:reading:Segmentations:button:getBidirectional') }}
|
|
</div>
|
|
<template v-else>
|
|
<div class="num">
|
|
<span>L:{{ Number(item.bidirectional.maxMajor).toFixed(2) }} mm</span>
|
|
<span>S:{{ Number(item.bidirectional.maxMinor).toFixed(2) }} mm</span>
|
|
</div>
|
|
<div class="btnBox">
|
|
<svg-icon :icon-class="!item.bidirectionalView ? 'eye' : 'eye-open'"
|
|
style="color:#000;margin-right: 5px;cursor: pointer;"
|
|
@click.stop="viewBidirectional([item], !item.bidirectionalView)" />
|
|
<svg-icon icon-class="jumpto" style="color:#000;cursor: pointer;"
|
|
@click.stop="jumpBidirectional(item)" />
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<template v-if="item.stats">
|
|
<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>
|
|
</div>
|
|
</template>
|
|
<div class="serialNum" slot="reference">{{ index + 1 }}</div>
|
|
</el-popover>
|
|
|
|
<el-color-picker v-model="item.color" size="mini"
|
|
@change="(e) => changeColor(e, item)"></el-color-picker>
|
|
<div class="SegmentName">{{ item.SegmentLabel }}</div>
|
|
</div>
|
|
<div class="btnBox">
|
|
<svg-icon :icon-class="item && !item.view ? 'eye' : 'eye-open'"
|
|
@click.stop="viewSegment(item, !item.view)" class="docShow" />
|
|
<i class="el-icon-lock" v-if="item.lock" @click.stop="lockSegment(item, false)"></i>
|
|
<el-popover placement="bottom" width="40" trigger="click" class="docShow"
|
|
:value="popoverId === `popover-${item.segmentationId}_${item.segmentIndex}`"
|
|
@show="handleClickPopover(item)">
|
|
<div class="SegmentGroupBtnBox">
|
|
<div class="SegmentGroupBtn" @click.stop="rename('segment', item)">
|
|
{{ $t('trials:reading:Segmentations:button:renameSegmentGroup') }}
|
|
</div>
|
|
<div class="SegmentGroupBtn" @click.stop="delSegment(item)">
|
|
{{ $t('trials:reading:Segmentations:button:deleteSegmentGroup') }}
|
|
</div>
|
|
<div class="SegmentGroupBtn" v-if="!item.lock"
|
|
@click.stop="lockSegment(item, true)">
|
|
{{ $t('trials:reading:Segmentations:button:lockSegment') }}
|
|
</div>
|
|
<div class="SegmentGroupBtn" v-if="item.lock"
|
|
@click.stop="lockSegment(item, false)">
|
|
{{ $t('trials:reading:Segmentations:button:unlockSegment') }}
|
|
</div>
|
|
</div>
|
|
<i slot="reference" class="el-icon-more" style="cursor: pointer;color:#fff" />
|
|
</el-popover>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-collapse-item>
|
|
</el-collapse>
|
|
<div class="saveBtnBox">
|
|
<el-button type="success" size="small" @click="saveSegmentGroup()">
|
|
{{ $t("trials:reading:Segmentations:button:saveAll") }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
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'
|
|
import * as cornerstoneAdapters from "@cornerstonejs/adapters";
|
|
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
|
|
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
|
|
import { getCustomizeStandardsSegmentDicomTools } from './toolConfig'
|
|
const {
|
|
ToolGroupManager,
|
|
Enums: csToolsEnums,
|
|
segmentation,
|
|
annotation,
|
|
LabelMapEditWithContourTool,
|
|
SegmentBidirectionalTool,
|
|
CrosshairsTool,
|
|
utilities: CStUtils,
|
|
} = cornerstoneTools;
|
|
const { MouseBindings, Events: toolsEvents } = csToolsEnums
|
|
const { segmentation: segmentationUtils } = CStUtils;
|
|
const { cache, getRenderingEngine, imageLoader, eventTarget, metaData, utilities: csUtils, volumeLoader } = cornerstone;
|
|
// const { downloadDICOMData } = cornerstoneAdapters.helpers;
|
|
const { Cornerstone3D } = cornerstoneAdapters.adaptersSEG;
|
|
export default {
|
|
name: "Segmentations",
|
|
props: {
|
|
isMPR: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
volumeToolGroupId: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
viewportKey: {
|
|
type: String,
|
|
default: 'viewport'
|
|
},
|
|
activeViewportIndex: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
activeTool: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
visitInfo: {
|
|
type: Object,
|
|
default: () => {
|
|
return {}
|
|
}
|
|
},
|
|
actionConfiguration: {
|
|
type: Object,
|
|
default: () => {
|
|
return {}
|
|
}
|
|
},
|
|
renderingEngineId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
loadingText: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
globalLoading: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
series: {},
|
|
activeNames: ['tools', 'Segment'],
|
|
brushSize: 10,
|
|
brushThreshold: {
|
|
dynamicRadius: 0,
|
|
isDynamic: false,
|
|
range: [200, 1000]
|
|
},
|
|
tools: ['LabelMapEditWithContour', 'CircularBrush', 'CircularEraser'],
|
|
ThresholdTools: ['ThresholdCircle', 'ThresholdSphere'],
|
|
thresholdType: null,
|
|
showSegmentConfig: false,
|
|
SegmentConfig: {
|
|
renderOutline: true,
|
|
renderFill: true,
|
|
fillAlpha: 0.5,
|
|
outlineWidth: 1,
|
|
InactiveSegmentations: {
|
|
show: true,
|
|
fillAlpha: 0.3,
|
|
}
|
|
},
|
|
segmentList: [],
|
|
segmentationId: "",
|
|
segmentIndex: null,
|
|
SegmentHight: 100,
|
|
colors: [
|
|
'#0ca8df',
|
|
'#ffd10a',
|
|
'#b6d634',
|
|
'#3fbe95',
|
|
'#785db0',
|
|
'#5070dd',
|
|
'#505372',
|
|
'#ff994d',
|
|
'#fb628b',
|
|
],
|
|
viewprotIds: ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3', 'viewport-MPR-0', 'viewport-MPR-1', 'viewport-MPR-2'], //
|
|
statsKey: [],
|
|
drawing: false, // 是否正在分割
|
|
// isDel: false,
|
|
digitPlaces: 2,
|
|
isloaded: false,
|
|
popoverId: null
|
|
}
|
|
},
|
|
mounted() {
|
|
this.SegmentHight = window.innerHeight > 900 ? window.innerHeight * 0.5 : window.innerHeight * 0.4;
|
|
this.statsKey = getCustomizeStandardsSegmentDicomTools('Labelmap')[0].props.filter(item => item !== 'width' && item !== 'length')
|
|
// console.log(segmentation, 'segmentation')
|
|
// console.log(annotation, 'annotation')
|
|
eventTarget.addEventListener(
|
|
'CORNERSTONE_TOOLS_SEGMENTATION_DATA_MODIFIED',
|
|
this.segmentationModifiedCallback
|
|
);
|
|
DicomEvent.$on('activeSeries', (series) => {
|
|
this.series = series
|
|
})
|
|
DicomEvent.$on('isloaded', (data) => {
|
|
if (this.isloaded) return false
|
|
this.isloaded = true
|
|
let { segment, isChange = true } = data
|
|
this.delAllSegment(isChange)
|
|
this.getSegmentationList(segment)
|
|
})
|
|
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
|
|
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
|
|
// document.addEventListener("click", () => {
|
|
// this.popoverId = null
|
|
// });
|
|
},
|
|
computed: {
|
|
curSegmentGroup() {
|
|
let s = this.segmentList.find(item => item.segmentationId === this.segmentationId)
|
|
return s ? s : {}
|
|
},
|
|
curSegment() {
|
|
let s = {}
|
|
if (this.curSegmentGroup.segments && this.curSegmentGroup.segments.length) {
|
|
s = this.curSegmentGroup.segments.find(item => item.segmentIndex === this.segmentIndex)
|
|
}
|
|
return s
|
|
}
|
|
},
|
|
watch: {
|
|
SegmentConfig: {
|
|
handler() {
|
|
this.readingSegmentByConfig()
|
|
},
|
|
deep: true
|
|
},
|
|
brushSize: {
|
|
handler() {
|
|
this.setBrushSize(this.activeTool)
|
|
},
|
|
deep: true
|
|
},
|
|
brushThreshold: {
|
|
handler() {
|
|
this.setBrushThreshold()
|
|
},
|
|
deep: true
|
|
}
|
|
},
|
|
methods: {
|
|
handleClickPopover(item) {
|
|
this.popoverId = `popover-${item.segmentationId}_${item.segmentIndex}`
|
|
},
|
|
initThreshold() {
|
|
if (!this.ThresholdTools.includes(this.activeTool)) {
|
|
this.setToolActive(this.ThresholdTools[0])
|
|
this.thresholdType = this.ThresholdTools[0]
|
|
}
|
|
|
|
},
|
|
createSegmentConfiguration(segmentIndex, segmentationId, otherSegments) {
|
|
const containedSegmentIndices = otherSegments
|
|
? { has: (segmentIndex) => otherSegments.indexOf(segmentIndex) !== -1 }
|
|
: undefined;
|
|
const colorConfig = segmentation.config.color.getSegmentIndexColor(
|
|
`${this.viewportKey}-${this.activeViewportIndex}`,
|
|
segmentationId,
|
|
segmentIndex
|
|
);
|
|
// Allow null style to skip style set
|
|
let color, activeColor;
|
|
if (colorConfig?.length) {
|
|
color = `rgb(${colorConfig.join(',')})`;
|
|
activeColor = color;
|
|
}
|
|
const style = {
|
|
color,
|
|
colorHighlightedActive: activeColor,
|
|
colorActive: activeColor,
|
|
textBoxColor: color,
|
|
textBoxColorActive: activeColor,
|
|
textBoxColorHighlightedActive: activeColor,
|
|
};
|
|
const label = otherSegments
|
|
? `Combined ${segmentIndex} with ${otherSegments.join(', ')}`
|
|
: `Segment ${segmentIndex}`;
|
|
|
|
this.actionConfiguration.contourBidirectional.data.segmentData.set(segmentIndex, {
|
|
containedSegmentIndices,
|
|
label,
|
|
style,
|
|
});
|
|
this.actionConfiguration.contourBidirectional.data.segmentationId = segmentationId
|
|
this.actionConfiguration.contourBidirectional.data.segmentIndex = segmentIndex
|
|
},
|
|
async addTip(item) {
|
|
let segmentGroup = this.segmentList.filter(i => item.segmentationId === i.segmentationId)
|
|
await this.saveSegmentGroup(segmentGroup, false)
|
|
this.calculateStatistics([item.segmentIndex], item.segmentationId, 'individual');
|
|
this.getBidirectional([item])
|
|
|
|
},
|
|
getBidirectional(list, DATA = null, isCompute = true) {
|
|
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);
|
|
if (isCompute) {
|
|
[viewport.element].forEach(async (element) => {
|
|
const bidirectionalData =
|
|
await CStUtils.segmentation.getSegmentLargestBidirectional({
|
|
segmentationId: list[0].segmentationId,
|
|
segmentIndices: list.map(item => item.segmentIndex),
|
|
});
|
|
console.log(bidirectionalData, list[0].segmentationId, '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)
|
|
|
|
if (DATA) {
|
|
this.segmentationId = DATA.SegmentationId;
|
|
this.segmentIndex = DATA.SegmentMumber;
|
|
setTimeout(() => {
|
|
this.selectSegment(DATA)
|
|
})
|
|
}
|
|
}
|
|
bidirectionalData.forEach((bidirectional) => {
|
|
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
|
|
if (DATA) {
|
|
this.segmentationId = DATA.SegmentationId;
|
|
this.segmentIndex = DATA.SegmentMumber;
|
|
setTimeout(() => {
|
|
this.selectSegment(DATA)
|
|
})
|
|
}
|
|
// render the bidirectional tool data
|
|
});
|
|
|
|
});
|
|
} else {
|
|
list.forEach(item => {
|
|
if (item.bidirectional) {
|
|
let { majorAxis, minorAxis } = item.bidirectional
|
|
SegmentBidirectionalTool.hydrate(viewportId, [majorAxis, minorAxis], {
|
|
segmentIndex: item.segmentIndex,
|
|
segmentationId: item.segmentationId,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
},
|
|
setToolActive(toolName) {
|
|
if (this.segmentList.length <= 0) return false
|
|
if (this.curSegment.lock) return false
|
|
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
|
|
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
|
if (this.activeTool === toolName) {
|
|
if (toolName === CrosshairsTool.toolName) {
|
|
toolGroup.setToolDisabled(this.activeTool)
|
|
} else {
|
|
toolGroup.setToolPassive(this.activeTool)
|
|
}
|
|
this.$emit('update:activeTool', '')
|
|
} else {
|
|
if (this.activeTool) {
|
|
if (toolName === CrosshairsTool.toolName) {
|
|
toolGroup.setToolDisabled(this.activeTool)
|
|
} else {
|
|
toolGroup.setToolPassive(this.activeTool)
|
|
}
|
|
}
|
|
toolGroup.setToolActive(toolName, {
|
|
bindings: [{ mouseButton: MouseBindings.Primary }]
|
|
})
|
|
this.$emit('update:activeTool', toolName)
|
|
this.setBrushSize(toolName)
|
|
if (this.ThresholdTools.includes(toolName)) {
|
|
this.setBrushThreshold()
|
|
}
|
|
|
|
}
|
|
},
|
|
changeShowSegmentConfig() {
|
|
if (this.segmentList.length > 0) {
|
|
this.showSegmentConfig = !this.showSegmentConfig
|
|
}
|
|
},
|
|
viewBidirectional(arr, view) {
|
|
for (let j = 0; j < arr.length; j++) {
|
|
let item = arr[j]
|
|
let bidirectional = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
|
|
item.bidirectionalView = view
|
|
if (!bidirectional) continue
|
|
annotation.visibility.setAnnotationVisibility(bidirectional.annotationUID, view)
|
|
}
|
|
this.resetViewport()
|
|
},
|
|
async jumpBidirectional(item) {
|
|
if (item.bidirectional) {
|
|
let an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
|
|
console.log(an, 'an')
|
|
if (!an) return false
|
|
|
|
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
|
|
const viewport = renderingEngine.getViewport(viewportId)
|
|
let key = Object.keys(an.data.cachedStats)[0]; // referencedImageId
|
|
if (key) {
|
|
let sliceIndex = key.split("?")[1].split("&")[0].split("=")[1]
|
|
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIndex });
|
|
} else {
|
|
const points = an.data.handles.points;
|
|
const worldPoint = points[0]; // 取一个点
|
|
let volume = cache.getVolume(this.series.SeriesInstanceUid)
|
|
let { imageData, numFrames } = volume
|
|
const ijk = imageData.worldToIndex(worldPoint);
|
|
const sliceIndex = Math.abs(Math.round(ijk[2]));
|
|
// console.log(sliceIndex, 'sliceIndex')
|
|
csUtils.jumpToSlice(viewport.element, { imageIndex: numFrames - sliceIndex - 1 });
|
|
}
|
|
|
|
}
|
|
},
|
|
viewSegmentGroup(item) {
|
|
let view = !item.view
|
|
this.viewprotIds.forEach(id => {
|
|
segmentation.config.visibility.setSegmentationRepresentationVisibility(
|
|
id,
|
|
{
|
|
segmentationId: item.segmentationId,
|
|
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
|
},
|
|
view
|
|
);
|
|
})
|
|
item.view = view
|
|
|
|
item.segments.forEach(i => {
|
|
i.view = view
|
|
// this.viewBidirectional(i, view)
|
|
})
|
|
this.viewBidirectional(item.segments, view)
|
|
},
|
|
viewSegment(item, view) {
|
|
this.viewprotIds.forEach(id => {
|
|
segmentation.config.visibility.setSegmentIndexVisibility(id, {
|
|
segmentationId: item.segmentationId,
|
|
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
|
}, item.segmentIndex, view)
|
|
})
|
|
item.view = view
|
|
this.viewBidirectional([item], view)
|
|
this.$emit('setToolsPassive')
|
|
},
|
|
lockSegment(item, lock) {
|
|
segmentation.segmentLocking.setSegmentIndexLocked(item.segmentationId, item.segmentIndex, lock)
|
|
item.lock = lock
|
|
if (!lock) this.changeSegmentationSavedStatus(item.segmentationId, lock)
|
|
},
|
|
selectSegment(item, isChange = true) {
|
|
if (isChange) {
|
|
this.segmentationId = item.segmentationId;
|
|
this.segmentIndex = item.segmentIndex;
|
|
}
|
|
segmentation.segmentIndex.setActiveSegmentIndex(item.segmentationId, item.segmentIndex);
|
|
if (isChange) { this.jumpBidirectional(item) }
|
|
|
|
if (item.lock) {
|
|
this.$emit('setToolsPassive')
|
|
}
|
|
// this.resetViewport()
|
|
},
|
|
selectSegmentGroup(s) {
|
|
this.viewprotIds.forEach(id => {
|
|
segmentation.activeSegmentation.setActiveSegmentation(id, this.segmentationId)
|
|
})
|
|
let segment = s ? s : this.segmentList.find(item => item.segmentationId === this.segmentationId).segments[0]
|
|
this.selectSegment(segment, s ? false : true)
|
|
this.readingSegmentByConfig()
|
|
},
|
|
async addSegmentGroup() {
|
|
let viewprotIds = this.viewprotIds
|
|
// let segmentationId = this.$guid();
|
|
let obj = {
|
|
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 })
|
|
obj.segmentationId = segmentationId
|
|
let o = {
|
|
segmentIndex: 1,
|
|
segmentationId,
|
|
SegmentLabel: 'Segment 1',
|
|
color: this.colors[0],
|
|
stats: null,
|
|
bidirectional: null,
|
|
bidirectionalView: true,
|
|
view: true,
|
|
lock: false
|
|
}
|
|
let id = await this.addOrUpdateSegment({ name: o.SegmentLabel, color: o.color, segmentIndex: o.segmentIndex, segmentationId: o.segmentationId })
|
|
o.id = id
|
|
obj.segments.push(o);
|
|
this.segmentList.push(obj);
|
|
this.segmentationId = obj.segmentationId;
|
|
await this.createSegmentation(obj.segmentationId)
|
|
this.createSegmentationRepresentation(obj.segmentationId)
|
|
this.segmentIndex = 1
|
|
viewprotIds.forEach(id => {
|
|
segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, 1, this.hex2Rgb(this.colors[0]))
|
|
})
|
|
this.selectSegmentGroup()
|
|
},
|
|
async addSegment() {
|
|
let viewprotIds = this.viewprotIds;
|
|
if (this.segmentList.length <= 0) {
|
|
let obj = {
|
|
name: this.$t('trials:reading:Segmentations:name:SegmentGroup') + 1,
|
|
view: true,
|
|
isSaved: false,
|
|
segments: []
|
|
}
|
|
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
|
|
obj.segmentationId = segmentationId
|
|
let o = {
|
|
segmentIndex: 1,
|
|
segmentationId,
|
|
SegmentLabel: 'Segment 1',
|
|
color: this.colors[0],
|
|
stats: null,
|
|
bidirectional: null,
|
|
bidirectionalView: true,
|
|
view: true,
|
|
lock: false
|
|
}
|
|
let id = await this.addOrUpdateSegment({ name: o.SegmentLabel, color: o.color, segmentIndex: o.segmentIndex, segmentationId: o.segmentationId })
|
|
o.id = id
|
|
obj.segments.push(o);
|
|
this.segmentList.push(obj);
|
|
this.segmentationId = this.segmentList[0].segmentationId;
|
|
await this.createSegmentation(this.segmentationId)
|
|
this.createSegmentationRepresentation(this.segmentationId)
|
|
this.segmentIndex = 1
|
|
segmentation.segmentIndex.setActiveSegmentIndex(this.segmentList[0].segmentationId, 1);
|
|
viewprotIds.forEach(id => {
|
|
segmentation.config.color.setSegmentIndexColor(id, this.segmentList[0].segmentationId, 1, this.hex2Rgb(this.colors[0]))
|
|
})
|
|
this.readingSegmentByConfig()
|
|
|
|
} else {
|
|
let item = this.segmentList.find(i => i.segmentationId === this.segmentationId)
|
|
let arr = item.segments.sort((a, b) => a.segmentIndex - b.segmentIndex)
|
|
let segmentIndex = arr[item.segments.length - 1].segmentIndex + 1
|
|
let obj = {
|
|
segmentIndex: segmentIndex,
|
|
segmentationId: this.segmentationId,
|
|
SegmentLabel: `Segment ${segmentIndex}`,
|
|
color: item.segments.length >= this.colors.length ? this.colors[0] : this.colors[item.segments.length],
|
|
stats: null,
|
|
bidirectional: null,
|
|
bidirectionalView: true,
|
|
view: true,
|
|
lock: false
|
|
}
|
|
if (item.segments.length >= this.colors.length) {
|
|
let index = item.segments.length % this.colors.length
|
|
let color = this.randomNearColor(this.colors[index], 4)
|
|
obj.color = color
|
|
}
|
|
let id = await this.addOrUpdateSegment({ name: obj.SegmentLabel, color: obj.color, segmentIndex: obj.segmentIndex, segmentationId: obj.segmentationId })
|
|
obj.id = id
|
|
item.segments.push(obj)
|
|
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, obj.segmentIndex);
|
|
viewprotIds.forEach(id => {
|
|
segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, obj.segmentIndex, this.hex2Rgb(obj.color))
|
|
})
|
|
this.segmentIndex = obj.segmentIndex
|
|
}
|
|
|
|
|
|
},
|
|
changeColor(e, item) {
|
|
this.viewprotIds.forEach(id => {
|
|
segmentation.config.color.setSegmentIndexColor(id, item.segmentationId, item.segmentIndex, this.hex2Rgb(e))
|
|
})
|
|
},
|
|
// 清空所有分割
|
|
delAllSegment(isChange) {
|
|
segmentation.removeAllSegmentations()
|
|
segmentation.state.removeAllSegmentations()
|
|
let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.segmentationId && item.metadata.segmentIndex && item.metadata.toolName === "SegmentBidirectional");
|
|
annotations.forEach(item => {
|
|
annotation.state.removeAnnotation(item.annotationUID)
|
|
})
|
|
if (isChange) {
|
|
this.segmentationId = ''
|
|
this.segmentIndex = null
|
|
}
|
|
this.segmentList = []
|
|
this.resetViewport()
|
|
},
|
|
// 删除分割分组
|
|
async delSegmentGroup() {
|
|
let confirm = await this.$confirm(this.$t('trials:reading:Segmentations:confirm:delSegmentions'))
|
|
if (!confirm) return false
|
|
let res = await this.deleteSegmentation(this.segmentationId)
|
|
if (!res) return false
|
|
segmentation.removeSegmentation(this.segmentationId)
|
|
segmentation.state.removeSegmentation(this.segmentationId)
|
|
let groupIndex = this.segmentList.findIndex(item => item.segmentationId === this.segmentationId)
|
|
this.segmentList.splice(groupIndex, 1)
|
|
let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.segmentationId === this.segmentationId);
|
|
annotations.forEach(item => {
|
|
annotation.state.removeAnnotation(item.annotationUID)
|
|
})
|
|
if (this.segmentList.length > 0) {
|
|
this.segmentationId = this.segmentList[0].segmentationId;
|
|
this.selectSegmentGroup()
|
|
} else {
|
|
this.segmentationId = ''
|
|
}
|
|
this.readingSegmentByConfig()
|
|
this.resetViewport()
|
|
this.$emit('resetQuestion')
|
|
},
|
|
// 删除分割片段
|
|
async delSegment(data) {
|
|
let segmentIndex = data.segmentIndex
|
|
let confirm = await this.$confirm(this.$t('trials:reading:Segmentations:confirm:delSegment'))
|
|
if (!confirm) return false
|
|
let groupIndex = this.segmentList.findIndex(item => item.segmentationId === this.segmentationId)
|
|
if (this.segmentList[groupIndex].segments.length <= 1) return this.$confirm(this.$t('trials:reading:Segmentations:confirm:hasOneSegment'))
|
|
let s = this.segmentList[groupIndex].segments.find(item => item.segmentIndex === segmentIndex)
|
|
let res = await this.deleteSegment(s.id)
|
|
if (!res) return false
|
|
segmentation.removeSegment(this.segmentationId, Number(segmentIndex), { setNextSegmentAsActive: false, recordHistory: false })
|
|
segmentation.helpers.clearSegmentValue(this.segmentationId, Number(segmentIndex), { recordHistory: false })
|
|
// segmentation.updateSegmentations({ segmentationId: this.segmentationId })
|
|
let index = this.segmentList[groupIndex].segments.findIndex(item => item.segmentIndex === segmentIndex)
|
|
this.segmentList[groupIndex].segments.splice(index, 1)
|
|
let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.segmentationId === this.segmentationId && item.metadata.segmentIndex === segmentIndex);
|
|
annotations.forEach(item => {
|
|
annotation.state.removeAnnotation(item.annotationUID)
|
|
})
|
|
this.resetViewport()
|
|
if (this.segmentList[groupIndex].segments.length > 0) {
|
|
this.selectSegment(this.segmentList[groupIndex].segments[0])
|
|
}
|
|
this.$emit('resetQuestion')
|
|
this.saveSegmentGroup([this.segmentList[groupIndex]], false)
|
|
|
|
},
|
|
resetViewport(passive = true) {
|
|
let renderingEngine = getRenderingEngine(this.renderingEngineId)
|
|
this.viewprotIds.forEach(id => {
|
|
const viewport = renderingEngine.getViewport(id)
|
|
viewport.render()
|
|
})
|
|
if (passive) this.$emit('setToolsPassive')
|
|
},
|
|
async rename(key, item) {
|
|
let name = await this.customPrompt()
|
|
if (!name) return false
|
|
if (key === 'segmentGroup') {
|
|
let group = this.segmentList.find(i => i.segmentationId === this.segmentationId)
|
|
group.name = name
|
|
this.addOrUpdateSegmentation({ name, id: group.segmentationId })
|
|
} else {
|
|
item.SegmentLabel = name
|
|
this.addOrUpdateSegment({ name: item.SegmentLabel, color: item.color, segmentIndex: item.segmentIndex, segmentationId: item.segmentationId, segmentJson: JSON.stringify({ stats: item.stats, bidirectional: item.bidirectional }), id: item.id })
|
|
}
|
|
},
|
|
async customPrompt() {
|
|
try {
|
|
const that = this
|
|
// 请输入标记名称
|
|
let message = this.$t('trials:reading:Segmentations:message:rename')
|
|
const { value } = await this.$prompt(message, '', {
|
|
showClose: false,
|
|
cancelButtonText: this.$t('common:button:cancel'),
|
|
confirmButtonText: this.$t('common:button:save'),
|
|
showCancelButton: true,
|
|
closeOnClickModal: false,
|
|
closeOnPressEscape: false,
|
|
beforeClose: (action, instance, done) => {
|
|
if (action === 'confirm') {
|
|
// const value = instance.inputValue
|
|
done()
|
|
} else {
|
|
done()
|
|
}
|
|
}
|
|
})
|
|
return value
|
|
} catch (err) {
|
|
console.log(err)
|
|
return null
|
|
}
|
|
},
|
|
// 切换非当前分组分割标记显示
|
|
changeInactiveSegmentShow() {
|
|
let segmentList = this.segmentList.filter(item => item.segmentationId !== this.segmentationId)
|
|
this.viewprotIds.forEach(id => {
|
|
segmentation.config.visibility.setSegmentationRepresentationVisibility(
|
|
id,
|
|
{
|
|
segmentationId: this.segmentationId,
|
|
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
|
},
|
|
true
|
|
);
|
|
segmentList.forEach(segment => {
|
|
segmentation.config.visibility.setSegmentationRepresentationVisibility(
|
|
id,
|
|
{
|
|
segmentationId: segment.segmentationId,
|
|
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
|
},
|
|
this.SegmentConfig.InactiveSegmentations.show
|
|
);
|
|
|
|
})
|
|
|
|
})
|
|
segmentList.forEach(segment => {
|
|
this.viewBidirectional(segment.segments, this.SegmentConfig.InactiveSegmentations.show)
|
|
})
|
|
let segmentGroup = this.segmentList.find(item => item.segmentationId === this.segmentationId)
|
|
if (segmentGroup) {
|
|
let segments = segmentGroup.segments
|
|
this.viewBidirectional(segments, true)
|
|
}
|
|
},
|
|
// 更改分割标记显示模式
|
|
changeSegmentConfig(renderOutline, renderFill) {
|
|
this.SegmentConfig.renderOutline = renderOutline;
|
|
this.SegmentConfig.renderFill = renderFill;
|
|
},
|
|
// 导出SEG
|
|
exportSegmentGroup() {
|
|
let group = this.segmentList.find(item => item.segmentationId === this.segmentationId)
|
|
this.exportSegmentation(this.segmentationId, group, true)
|
|
},
|
|
exportSegmentation(segmentationId, group, isFile = false) {
|
|
const segmentationIds = segmentation.state
|
|
.getSegmentations()
|
|
.map(x => x.segmentationId);
|
|
if (!segmentationIds.length) {
|
|
return;
|
|
}
|
|
const segmentGroup =
|
|
segmentation.state.getSegmentation(segmentationId);
|
|
|
|
let { imageIds } = segmentGroup.representationData.Labelmap;
|
|
|
|
let segImages = imageIds.map(imageId => cache.getImage(imageId));
|
|
segImages = segImages.reverse()
|
|
let referencedImages = segImages.map(image =>
|
|
cache.getImage(image.referencedImageId)
|
|
);
|
|
const labelmaps2D = [];
|
|
|
|
let z = 0;
|
|
|
|
for (const segImage of segImages) {
|
|
const segmentsOnLabelmap = new Set();
|
|
const pixelData = segImage.getPixelData();
|
|
const { rows, columns } = segImage;
|
|
|
|
for (let i = 0; i < pixelData.length; i++) {
|
|
const segment = pixelData[i];
|
|
if (segment !== 0) {
|
|
segmentsOnLabelmap.add(segment);
|
|
}
|
|
}
|
|
|
|
labelmaps2D[z++] = {
|
|
segmentsOnLabelmap: Array.from(segmentsOnLabelmap),
|
|
pixelData,
|
|
rows,
|
|
columns
|
|
};
|
|
}
|
|
|
|
const allSegmentsOnLabelmap = labelmaps2D.map(
|
|
labelmap => labelmap.segmentsOnLabelmap
|
|
);
|
|
|
|
const labelmap3D = {
|
|
segmentsOnLabelmap: Array.from(new Set(allSegmentsOnLabelmap.flat())),
|
|
metadata: [],
|
|
labelmaps2D
|
|
};
|
|
|
|
labelmap3D.segmentsOnLabelmap.forEach(segmentIndex => {
|
|
const color = segmentation.config.color.getSegmentIndexColor(
|
|
`${this.viewportKey}-${this.activeViewportIndex}`,
|
|
segmentationId,
|
|
segmentIndex
|
|
);
|
|
const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
|
|
color.slice(0, 3).map(value => value / 255)
|
|
).map(value => Math.round(value));
|
|
let segment = group.segments.find(item => item.segmentIndex === segmentIndex)
|
|
if (segment) {
|
|
let SegmentLabel = segment.SegmentLabel
|
|
const segmentMetadata = {
|
|
SegmentNumber: segmentIndex.toString(),
|
|
SegmentLabel: SegmentLabel,
|
|
SegmentAlgorithmType: "MANUAL",
|
|
SegmentAlgorithmName: "OHIF Brush",
|
|
RecommendedDisplayCIELabValue,
|
|
SegmentedPropertyCategoryCodeSequence: {
|
|
CodeValue: "T-D0050",
|
|
CodingSchemeDesignator: "SRT",
|
|
CodeMeaning: "Tissue"
|
|
},
|
|
SegmentedPropertyTypeCodeSequence: {
|
|
CodeValue: "T-D0050",
|
|
CodingSchemeDesignator: "SRT",
|
|
CodeMeaning: "Tissue"
|
|
}
|
|
};
|
|
|
|
if (segment.stats) labelmap3D.metadata[segmentIndex] = segmentMetadata;
|
|
}
|
|
});
|
|
if (labelmap3D.metadata.length <= 0) {
|
|
return false
|
|
}
|
|
const generatedSegmentation =
|
|
Cornerstone3D.Segmentation.generateSegmentation(
|
|
referencedImages,
|
|
labelmap3D,
|
|
metaData
|
|
);
|
|
group.segments.forEach((item, index) => {
|
|
if (generatedSegmentation.dataset.SegmentSequence[index]) {
|
|
generatedSegmentation.dataset.SegmentSequence[index].SegmentNumber = item.segmentIndex.toString()
|
|
}
|
|
|
|
})
|
|
if (!isFile) {
|
|
const buffer = Buffer.from(dcmjs.data.datasetToDict(generatedSegmentation.dataset).write());
|
|
let blob = new Blob([buffer], { type: "application/dicom" });
|
|
return blob
|
|
} else {
|
|
this.downloadDICOMData(generatedSegmentation.dataset, `${group.name}.dcm`);
|
|
}
|
|
},
|
|
downloadDICOMData(bufferOrDataset, filename) {
|
|
let blob;
|
|
if (bufferOrDataset instanceof ArrayBuffer) {
|
|
blob = new Blob([bufferOrDataset], { type: 'application/dicom' });
|
|
} else {
|
|
if (!bufferOrDataset._meta) {
|
|
throw new Error('Dataset must have a _meta property');
|
|
}
|
|
blob = dcmjs.data.datasetToBlob(bufferOrDataset);
|
|
}
|
|
|
|
const link = document.createElement('a');
|
|
link.href = window.URL.createObjectURL(blob);
|
|
link.download = filename;
|
|
link.click();
|
|
},
|
|
|
|
// 导入SEG
|
|
beginScanFiles(e) {
|
|
let files = e.target.files
|
|
for (const file of files) {
|
|
file.segmentationId = this.$guid()
|
|
this.readSegmentation(file, true)
|
|
}
|
|
},
|
|
async readSegmentation(obj, isFile = false) {
|
|
let imageId = null
|
|
if (isFile) {
|
|
imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(obj);
|
|
} else {
|
|
const imageIdObj = await cornerstoneDICOMImageLoader.wadouri.loadImage(`wadouri:${this.OSSclientConfig.basePath}${obj.segUrl}`).promise
|
|
imageId = imageIdObj.imageId
|
|
}
|
|
const image = await imageLoader.loadAndCacheImage(imageId);
|
|
|
|
if (!image) {
|
|
return;
|
|
}
|
|
|
|
const instance = metaData.get("instance", imageId);
|
|
|
|
if (instance.Modality !== "SEG") {
|
|
console.error("This is not segmentation: " + file.name);
|
|
return;
|
|
}
|
|
|
|
const arrayBuffer = image.data.byteArray.buffer;
|
|
await this.loadSegmentation(arrayBuffer, obj.segmentationId);
|
|
this.createSegmentationRepresentation(obj.segmentationId);
|
|
},
|
|
async loadSegmentation(arrayBuffer, segmentationId) {
|
|
const generateToolState =
|
|
await Cornerstone3D.Segmentation.generateToolState(
|
|
this.series.ImageIds,
|
|
arrayBuffer,
|
|
metaData,
|
|
);
|
|
if (generateToolState.labelmapBufferArray.length !== 1) {
|
|
alert(
|
|
"Overlapping segments in your segmentation are not supported yet. You can turn on the skipOverlapping option but it will override the overlapping segments."
|
|
);
|
|
return;
|
|
}
|
|
await this.createSegmentation(segmentationId);
|
|
let arr = []
|
|
generateToolState.segMetadata.data.forEach(item => {
|
|
if (item) {
|
|
let Target = JSON.parse(JSON.stringify(item))
|
|
arr.push(Target)
|
|
}
|
|
})
|
|
|
|
let mapping = {}
|
|
|
|
arr.forEach((item, index) => {
|
|
mapping[index + 1] = Number(item.SegmentNumber)
|
|
})
|
|
console.log(mapping, 'mapping')
|
|
const megmentGroup =
|
|
segmentation.state.getSegmentation(segmentationId);
|
|
const { imageIds } = megmentGroup.representationData.Labelmap;
|
|
const derivedSegmentationImages = imageIds.map(imageId =>
|
|
cache.getImage(imageId)
|
|
);
|
|
|
|
const volumeScalarData = new Uint8Array(
|
|
generateToolState.labelmapBufferArray[0]
|
|
);
|
|
const remappedData = new Uint8Array(volumeScalarData.length);
|
|
for (let i = 0; i < volumeScalarData.length; i++) {
|
|
const value = volumeScalarData[i];
|
|
remappedData[i] = value === 0 ? 0 : (mapping[value] ? mapping[value] : value);
|
|
}
|
|
for (let i = 0; i < derivedSegmentationImages.length; i++) {
|
|
const voxelManager = derivedSegmentationImages[i].voxelManager;
|
|
const scalarData = voxelManager.getScalarData();
|
|
scalarData.set(
|
|
remappedData.slice(
|
|
i * scalarData.length,
|
|
(i + 1) * scalarData.length
|
|
)
|
|
);
|
|
voxelManager.setScalarData(scalarData);
|
|
}
|
|
},
|
|
createSegmentGroupObj(name) {
|
|
let flag = this.segmentList.some(item => item.name === name)
|
|
let obj = {
|
|
segmentationId: this.$guid(),
|
|
name: !flag ? name : this.$t('trials:reading:Segmentations:name:SegmentGroup') + (this.segmentList.length + 1),
|
|
view: true,
|
|
segments: []
|
|
}
|
|
return obj
|
|
},
|
|
readingSegmentByConfig() {
|
|
this.changeInactiveSegmentShow()
|
|
segmentation.config.style.setStyle(
|
|
{
|
|
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
|
},
|
|
{
|
|
renderFill: this.SegmentConfig.renderFill,
|
|
renderOutline: this.SegmentConfig.renderOutline,
|
|
outlineWidth: Number(this.SegmentConfig.outlineWidth),
|
|
fillAlpha: Number(this.SegmentConfig.fillAlpha),
|
|
}
|
|
)
|
|
},
|
|
setBrushSize(toolName = null) {
|
|
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
|
|
CStUtils.segmentation.setBrushSizeForToolGroup(toolGroupId, this.brushSize);
|
|
if (toolName === 'ThresholdSphere') {
|
|
this.setDynamicRadius()
|
|
}
|
|
},
|
|
setDynamicRadius() {
|
|
let volume = cache.getVolume(this.series.SeriesInstanceUid);
|
|
let { spacing, numFrames } = volume
|
|
let constant = numFrames * spacing[2] / 100
|
|
this.brushThreshold.dynamicRadius = Math.ceil(this.brushSize * constant)
|
|
// console.log(this.brushThreshold.dynamicRadius)
|
|
},
|
|
setBrushThreshold() {
|
|
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
|
|
let brushThreshold = {
|
|
isDynamic: this.brushThreshold.isDynamic,
|
|
dynamicRadius: this.brushThreshold.dynamicRadius,
|
|
}
|
|
if (!this.brushThreshold.isDynamic) {
|
|
brushThreshold.range = this.brushThreshold.range
|
|
}
|
|
CStUtils.segmentation.setBrushThresholdForToolGroup(toolGroupId, brushThreshold);
|
|
},
|
|
async createSegmentation(segmentationId) {
|
|
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
|
|
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
|
toolGroup.setToolActive(
|
|
LabelMapEditWithContourTool.toolName,
|
|
);
|
|
if (!cache.getVolume(segmentationId)) {
|
|
await volumeLoader.createAndCacheDerivedLabelmapVolume(
|
|
this.series.SeriesInstanceUid,
|
|
{
|
|
volumeId: segmentationId
|
|
}
|
|
)
|
|
}
|
|
if (!segmentation.state.getSegmentation(segmentationId)) {
|
|
segmentation.addSegmentations([
|
|
{
|
|
segmentationId,
|
|
representation: {
|
|
type: cornerstoneTools.Enums.SegmentationRepresentations
|
|
.Labelmap,
|
|
data: {
|
|
volumeId: segmentationId
|
|
}
|
|
}
|
|
}
|
|
]);
|
|
}
|
|
|
|
},
|
|
segmentationModifiedCallback(evt) {
|
|
const { detail } = evt;
|
|
let tools = [...this.ThresholdTools, ...this.tools]
|
|
if (!detail || detail.segmentIndex === 255 || !this.activeTool || !tools.includes(this.activeTool)) {
|
|
return;
|
|
}
|
|
this.drawing = true
|
|
},
|
|
async calculateStatistics(indices, segmentationId, mode) {
|
|
if (!segmentation.state.getSegmentation(segmentationId)) return false
|
|
const stats = await segmentationUtils.getStatistics({
|
|
segmentationId,
|
|
segmentIndices: indices,
|
|
mode,
|
|
});
|
|
console.log(stats)
|
|
if (mode === 'individual') {
|
|
const segmentStats = stats;
|
|
|
|
for (const segmentIndex of indices) {
|
|
if (segmentStats[segmentIndex]) {
|
|
const segmentStat = segmentStats[segmentIndex];
|
|
// console.log(segmentStat, 'segmentStat')
|
|
segmentStat.count.label = 'Voxels';
|
|
let segmentGroup = this.segmentList.find(item => item.segmentationId === segmentationId)
|
|
if (segmentGroup) {
|
|
let segment = segmentGroup.segments.find(item => item.segmentIndex === segmentIndex)
|
|
segment.stats = segmentStat;
|
|
}
|
|
|
|
}
|
|
}
|
|
} else {
|
|
const items = [`Statistics on ${indices.join(', ')}`];
|
|
const namedStats = stats;
|
|
namedStats.count.label = 'Voxels';
|
|
|
|
}
|
|
|
|
},
|
|
createSegmentationRepresentation(segmentationId) {
|
|
this.viewprotIds.forEach(id => {
|
|
segmentation.addSegmentationRepresentations(id, [
|
|
{
|
|
segmentationId,
|
|
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
|
},
|
|
])
|
|
})
|
|
this.$emit('setToolsPassive')
|
|
},
|
|
contentMouseup() {
|
|
// console.log("segment contentMouseup")
|
|
if (!this.drawing) return false
|
|
if (this.timeoutId) {
|
|
clearTimeout(this.timeoutId);
|
|
this.timeoutId = null;
|
|
}
|
|
this.timeoutId = setTimeout(() => {
|
|
this.timeoutId = null;
|
|
this.drawing = false;
|
|
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)
|
|
|
|
}
|
|
}, 500);
|
|
},
|
|
// 获取当前任务分割标记与问题绑定关系
|
|
async getSegmentBindingList(param = {}) {
|
|
try {
|
|
let data = {
|
|
VisitTaskId: this.visitInfo.VisitTaskId,
|
|
PageSize: 9999,
|
|
PageIndex: 1,
|
|
}
|
|
data = Object.assign(data, param)
|
|
let res = await getSegmentBindingList(data)
|
|
if (res.IsSuccess) {
|
|
return res.Result.CurrentPageData
|
|
}
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 添加分割标记与问题绑定关系
|
|
async saveSegmentBindingAndAnswer(list) {
|
|
try {
|
|
let data = {
|
|
VisitTaskId: this.visitInfo.VisitTaskId,
|
|
BindingList: list
|
|
}
|
|
let res = await saveSegmentBindingAndAnswer(data)
|
|
if (res.IsSuccess) {
|
|
this.$emit("resetQuestion")
|
|
}
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 保存整个分组
|
|
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;
|
|
if (saveSegment) {
|
|
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
|
|
break
|
|
}
|
|
}
|
|
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"))
|
|
for (let i = 0; i < segmentList.length; i++) {
|
|
let segmentGroup = segmentList[i]
|
|
// 将所有分割进行锁定
|
|
segmentGroup.segments.forEach(s => {
|
|
this.lockSegment(s, true)
|
|
})
|
|
// 生成文件路径
|
|
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
|
|
}/${this.visitInfo.VisistId}/${this.series.StudyId
|
|
}/${this.series.Id}/${segmentGroup.name}.dcm`
|
|
const result = await this.OSSclient.put(path, blob)
|
|
segmentGroup.segUrl = this.$getObjectName(result.url)
|
|
}
|
|
|
|
this.addOrUpdateSegmentation({ name: segmentGroup.name, id: segmentGroup.segmentationId, url: segmentGroup.segUrl })
|
|
this.changeSegmentationSavedStatus(segmentGroup.segmentationId, saveSegment)
|
|
if (saveSegment) {
|
|
await this.getBidirectionalSaveSegment(segmentList)
|
|
this.syncBindingAnswer(segmentList)
|
|
}
|
|
}
|
|
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
|
|
try {
|
|
let data = {
|
|
SegmentationName: name,
|
|
SeriesId: this.series.Id,
|
|
StudyId: this.series.StudyId,
|
|
SubjectId: this.visitInfo.SubjectId,
|
|
SubjectVisitId: this.visitInfo.VisistId,
|
|
TrialId: this.$route.query.trialId,
|
|
VisitTaskId: this.visitInfo.VisitTaskId,
|
|
}
|
|
if (url) data.SegUrl = url;
|
|
if (id) data.Id = id;
|
|
this.loading = true;
|
|
let res = await addOrUpdateSegmentation(data);
|
|
this.loading = false;
|
|
if (res.IsSuccess) {
|
|
return res.Result
|
|
}
|
|
} catch (err) {
|
|
this.loading = false
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 获取分割组
|
|
async getSegmentationList(SEGMENT = null) {
|
|
try {
|
|
let data = {
|
|
SeriesId: this.series.Id,
|
|
VisitTaskId: this.visitInfo.VisitTaskId,
|
|
PageSize: 9999,
|
|
PageIndex: 1,
|
|
}
|
|
this.loading = true;
|
|
let res = await getSegmentationList(data);
|
|
this.loading = false;
|
|
if (res.IsSuccess) {
|
|
// this.segmentList = []
|
|
let list = res.Result.CurrentPageData;
|
|
for (let i = 0; i < list.length; i++) {
|
|
let item = list[i]
|
|
let obj = this.segmentList.find(i => i.segmentationId === item.Id)
|
|
if (!obj) {
|
|
obj = {
|
|
segmentationId: item.Id,
|
|
name: item.SegmentationName,
|
|
view: true,
|
|
segUrl: item.SEGUrl,
|
|
isSaved: item.IsSaved,
|
|
segments: []
|
|
}
|
|
this.segmentList.push(obj)
|
|
if (item.SEGUrl) {
|
|
await this.readSegmentation(obj)
|
|
} else {
|
|
await this.createSegmentation(obj.segmentationId)
|
|
this.createSegmentationRepresentation(obj.segmentationId)
|
|
}
|
|
}
|
|
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 = obj.segments.find(i => i.id === s.Id)
|
|
if (!o) {
|
|
o = {
|
|
segmentIndex: s.SegmentMumber,
|
|
segmentationId: s.SegmentationId,
|
|
SegmentLabel: s.SegmentName,
|
|
color: s.ColorRgb,
|
|
stats: SegmentJson.stats,
|
|
bidirectional: SegmentJson.bidirectional,
|
|
bidirectionalView: true,
|
|
view: true,
|
|
lock: item.locked,
|
|
id: s.Id
|
|
}
|
|
obj.segments.push(o)
|
|
this.selectSegment(o, false)
|
|
this.changeColor(s.ColorRgb, o)
|
|
this.lockSegment(o, true)
|
|
}
|
|
if (!this.segmentIndex) {
|
|
this.segmentIndex = s.SegmentMumber
|
|
}
|
|
})
|
|
this.$nextTick(() => {
|
|
if (SEGMENT) {
|
|
// console.log(SEGMENT, 'SEGMENT')
|
|
return this.getBidirectional(obj.segments, SEGMENT)
|
|
}
|
|
this.getBidirectional(obj.segments, null, false)
|
|
})
|
|
}
|
|
if (this.segmentationId && this.segmentIndex) {
|
|
let segment = this.segmentList.find(item => item.segmentationId === this.segmentationId).segments.find(item => item.segmentIndex === this.segmentIndex)
|
|
console.log(segment, 'segment')
|
|
this.selectSegmentGroup(segment)
|
|
// this.selectSegment(segment)
|
|
}
|
|
this.isloaded = false
|
|
this.readingSegmentByConfig()
|
|
}
|
|
} catch (err) {
|
|
this.loading = false
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 删除分割组
|
|
async deleteSegmentation(id) {
|
|
try {
|
|
this.loading = true;
|
|
let res = await deleteSegmentation(id)
|
|
this.loading = false;
|
|
if (res.IsSuccess) return true
|
|
return false
|
|
} catch (err) {
|
|
this.loading = false
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 新增或修改分割
|
|
async addOrUpdateSegment(item) {
|
|
try {
|
|
let { name, color, segmentIndex, segmentationId, segmentJson, id } = item
|
|
let data = {
|
|
ColorRgb: color,
|
|
SegmentName: name,
|
|
SegmentMumber: segmentIndex,
|
|
SegmentationId: segmentationId,
|
|
VisitTaskId: this.visitInfo.VisitTaskId,
|
|
SegmentJson: segmentJson
|
|
}
|
|
if (id) data.Id = id
|
|
this.loading = true;
|
|
let res = await addOrUpdateSegment(data)
|
|
this.loading = false;
|
|
if (res.IsSuccess) {
|
|
return res.Result
|
|
}
|
|
} catch (err) {
|
|
this.loading = false
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 获取分割
|
|
async getSegmentList(id) {
|
|
try {
|
|
let data = {
|
|
SegmentationId: id,
|
|
PageSize: 9999,
|
|
PageIndex: 1,
|
|
}
|
|
this.loading = true;
|
|
let res = await getSegmentList(data)
|
|
this.loading = false;
|
|
if (res.IsSuccess) {
|
|
return res.Result.CurrentPageData
|
|
}
|
|
} catch (err) {
|
|
this.loading = false
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 删除分割
|
|
async deleteSegment(id) {
|
|
try {
|
|
this.loading = true;
|
|
let res = await deleteSegment(id)
|
|
this.loading = false;
|
|
if (res.IsSuccess) return true
|
|
return false
|
|
} catch (err) {
|
|
this.loading = false
|
|
console.log(err)
|
|
}
|
|
},
|
|
// 获取问题配置
|
|
async getQuestionConfig(Id, isTable = false) {
|
|
try {
|
|
let params = {
|
|
Id
|
|
}
|
|
let res = null
|
|
if (isTable) {
|
|
res = await getReadingTableQuestionTrialById(params)
|
|
} else {
|
|
res = await getReadingQuestionTrialById(params)
|
|
}
|
|
if (res.IsSuccess) return res.Result
|
|
} catch (err) {
|
|
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);
|
|
const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
if (!rgb) {
|
|
return hexValue;
|
|
}
|
|
const r = parseInt(rgb[1], 16),
|
|
g = parseInt(rgb[2], 16),
|
|
b = parseInt(rgb[3], 16);
|
|
return [r, g, b, alpha * 255];
|
|
},
|
|
randomNearColor(hex, range = 3) {
|
|
if (!/^#([0-9a-fA-F]{6})$/.test(hex)) {
|
|
throw new Error('请输入正确的 6 位十六进制颜色值,例如 #000000');
|
|
}
|
|
|
|
const hexToRgb = (hex) => ({
|
|
r: parseInt(hex.slice(1, 3), 16),
|
|
g: parseInt(hex.slice(3, 5), 16),
|
|
b: parseInt(hex.slice(5, 7), 16),
|
|
});
|
|
|
|
const rgbToHsl = (r, g, b) => {
|
|
r /= 255; g /= 255; b /= 255;
|
|
const max = Math.max(r, g, b);
|
|
const min = Math.min(r, g, b);
|
|
let h = 0, s = 0;
|
|
const l = (max + min) / 2;
|
|
|
|
if (max !== min) {
|
|
const d = max - min;
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
|
|
switch (max) {
|
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
case g: h = (b - r) / d + 2; break;
|
|
case b: h = (r - g) / d + 4; break;
|
|
}
|
|
|
|
h *= 60;
|
|
}
|
|
|
|
return { h, s: s * 100, l: l * 100 };
|
|
};
|
|
|
|
const hslToRgb = (h, s, l) => {
|
|
h /= 360; s /= 100; l /= 100;
|
|
let r, g, b;
|
|
|
|
if (s === 0) {
|
|
r = g = b = l;
|
|
} else {
|
|
const hue2rgb = (p, q, t) => {
|
|
if (t < 0) t += 1;
|
|
if (t > 1) t -= 1;
|
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
if (t < 1 / 2) return q;
|
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
return p;
|
|
};
|
|
|
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
const p = 2 * l - q;
|
|
|
|
r = hue2rgb(p, q, h + 1 / 3);
|
|
g = hue2rgb(p, q, h);
|
|
b = hue2rgb(p, q, h - 1 / 3);
|
|
}
|
|
|
|
return {
|
|
r: Math.round(r * 255),
|
|
g: Math.round(g * 255),
|
|
b: Math.round(b * 255),
|
|
};
|
|
};
|
|
|
|
const rgbToHex = (r, g, b) =>
|
|
`#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')}`;
|
|
|
|
const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
const randomOffset = (n) => (Math.random() * 2 - 1) * n;
|
|
|
|
const { r, g, b } = hexToRgb(hex);
|
|
const { h, s, l } = rgbToHsl(r, g, b);
|
|
|
|
const nh = (h + randomOffset(range) + 360) % 360;
|
|
const ns = clamp(s + randomOffset(range), 0, 100);
|
|
const nl = clamp(l + randomOffset(range), 0, 100);
|
|
|
|
const rgb = hslToRgb(nh, ns, nl);
|
|
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
|
}
|
|
},
|
|
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
.Segmentations {
|
|
height: 100%;
|
|
position: relative;
|
|
|
|
.saveBtnBox {
|
|
position: absolute;
|
|
bottom: 5px;
|
|
left: 50%;
|
|
}
|
|
}
|
|
|
|
.Bidirectionalbox {
|
|
padding: 5px;
|
|
border-bottom: 1px solid #333;
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
.BidirectionalBtn {
|
|
width: 100%;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.num {
|
|
display: flex;
|
|
width: 70%;
|
|
flex-wrap: wrap;
|
|
|
|
span {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
.btnBox {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
.statsBox {
|
|
padding: 0 5px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: #333;
|
|
|
|
i {
|
|
margin-left: 5px;
|
|
color: #000;
|
|
}
|
|
}
|
|
|
|
.SegmentBox {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
color: #fff;
|
|
background-color: #333;
|
|
cursor: pointer;
|
|
margin: 0 5px 10px;
|
|
border-radius: 3px;
|
|
padding: 5px;
|
|
position: relative;
|
|
|
|
.messageBox {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-left: 30px;
|
|
|
|
.serialNum {
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 30px;
|
|
left: 0;
|
|
top: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
line-height: 30px;
|
|
background-color: #222;
|
|
}
|
|
|
|
.color {
|
|
width: 10px;
|
|
height: 10px;
|
|
background-color: #f0f;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.SegmentName {
|
|
margin-left: 5px;
|
|
}
|
|
}
|
|
|
|
.btnBox {
|
|
i {
|
|
font-size: 20px;
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.segmentBtn {
|
|
display: none;
|
|
}
|
|
|
|
.docShow {
|
|
display: inline-block;
|
|
}
|
|
}
|
|
|
|
&:hover {
|
|
background-color: #999;
|
|
|
|
.messageBox {
|
|
color: #333;
|
|
}
|
|
|
|
.serialNum {
|
|
color: #fff;
|
|
}
|
|
|
|
.segmentBtn {
|
|
display: inline-block;
|
|
}
|
|
}
|
|
}
|
|
|
|
.SegmentBox_active {
|
|
background-color: #999;
|
|
|
|
.messageBox {
|
|
color: #333;
|
|
}
|
|
|
|
.serialNum {
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
.SegmentGroupBox {
|
|
padding: 8px 5px;
|
|
background-color: #333;
|
|
margin: 0 5px;
|
|
border-radius: 3px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
|
|
i {
|
|
font-size: 20px;
|
|
}
|
|
|
|
::v-deep .el-input--medium {
|
|
.el-input__inner {
|
|
line-height: 30px;
|
|
height: 30px;
|
|
}
|
|
|
|
.el-input__icon {
|
|
line-height: 30px;
|
|
}
|
|
}
|
|
|
|
::v-deep .el-select {
|
|
margin-left: 10px;
|
|
}
|
|
}
|
|
|
|
.SegmentGroupBtn {
|
|
line-height: 30px;
|
|
cursor: pointer;
|
|
border-radius: 3px;
|
|
padding: 0 5px;
|
|
|
|
&:hover {
|
|
background-color: rgba($color: #409EFF, $alpha: 0.2);
|
|
}
|
|
}
|
|
|
|
.addSegmentBox {
|
|
cursor: pointer;
|
|
color: #fff;
|
|
line-height: 30px;
|
|
margin: 5px;
|
|
padding: 0 5px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.viewHover {
|
|
&:hover {
|
|
background-color: #ddd;
|
|
color: #333;
|
|
|
|
}
|
|
}
|
|
|
|
.line {
|
|
display: block;
|
|
border-bottom: 1px solid #333;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.SegmentTitle {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
|
|
.svg-icon {
|
|
transform: rotate(90deg);
|
|
}
|
|
}
|
|
|
|
.EraserConfig,
|
|
.SegmentConfig {
|
|
width: calc(100% - 10px);
|
|
padding: 0 5px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
span {
|
|
color: #fff;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
::v-deep .el-slider {
|
|
width: 70%;
|
|
}
|
|
|
|
::v-deep .el-slider__input {
|
|
width: 60px;
|
|
}
|
|
|
|
::v-deep .el-slider__runway.show-input {
|
|
margin-right: 70px;
|
|
}
|
|
}
|
|
|
|
.EraserConfig {
|
|
margin: 5px 0;
|
|
|
|
::v-deep .el-input-number {
|
|
width: 50px;
|
|
}
|
|
|
|
::v-deep .el-input-number.is-without-controls .el-input__inner {
|
|
padding: 0 5px;
|
|
width: 50px;
|
|
}
|
|
}
|
|
|
|
.SegmentConfig {
|
|
padding: 5px;
|
|
}
|
|
|
|
.svg-icon {
|
|
color: #fff;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.tool-item {
|
|
padding: 5px;
|
|
margin: 0 5px;
|
|
border: 1px solid #333;
|
|
// font-size: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tool-item-active {
|
|
background-color: #607d8b;
|
|
}
|
|
|
|
.tool-disabled {
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.tool-frame {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
// margin-right: 20px;
|
|
padding: 5px;
|
|
margin-bottom: 10px;
|
|
border-bottom: 1px solid #404040;
|
|
|
|
.icon {
|
|
padding: 5px;
|
|
border-right: 1px solid #404040;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
|
|
.svg-icon {
|
|
font-size: 20px;
|
|
color: #ddd;
|
|
}
|
|
}
|
|
|
|
.select-wrapper {
|
|
width: 60px;
|
|
background-color: black;
|
|
color: #ddd;
|
|
border: none;
|
|
font-size: 13px;
|
|
outline: none;
|
|
}
|
|
|
|
.text {
|
|
position: relative;
|
|
font-size: 12px;
|
|
margin-top: 5px;
|
|
color: #d0d0d0;
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
::v-deep.el-collapse {
|
|
border: none;
|
|
|
|
.el-collapse-item {
|
|
background-color: #000 !important;
|
|
color: #ddd;
|
|
|
|
}
|
|
|
|
.el-collapse-item__content {
|
|
padding-bottom: 0px;
|
|
background-color: #000 !important;
|
|
line-height: 1;
|
|
}
|
|
|
|
.el-collapse-item__header {
|
|
background-color: #000 !important;
|
|
color: #ddd;
|
|
border-bottom-color: #5a5a5a;
|
|
padding-left: 5px;
|
|
// height: 50px;
|
|
line-height: 20px;
|
|
}
|
|
}
|
|
|
|
::v-deep .el-progress-bar__inner {
|
|
transition: width 0s ease;
|
|
}
|
|
|
|
::v-deep .el-collapse-item__wrap {
|
|
background-color: #000;
|
|
}
|
|
</style> |