irc_web/src/views/trials/trials-panel/reading/dicoms3D/components/Segmentations.vue

2528 lines
108 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="Segmentations" v-loading="loading">
<h3 style="color: #fff;margin: 0;padding: 15px 10px 5px" v-if="series.TaskInfo">
<span>{{ series.TaskInfo.SubjectCode }} </span>
<span style="margin-left:5px;">{{ series.TaskInfo.TaskBlindName }}</span>
</h3>
<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: isMPR || segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? '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: isMPR || segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? '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: isMPR || segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? '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: isMPR || segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? 'not-allowed' : 'pointer' }"
:title="$t('trials:Segmentations:tools: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" style="margin-right: 10px;"
@click.stop="changeShowSegmentConfig" />
</div>
</template>
<div class="addSegmentBox viewHover" @click.stop="addSegment"
v-if="segmentList.length <= 0 && readingTaskState < 2">
<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>
<template v-if="segmentList.length > 0">
<div class="SegmentGroupBox">
<div style="display: flex;align-items: center;">
<el-popover placement="left" width="200px" trigger="click"
v-if="readingTaskState < 2 && !isMPR" v-model="popoverVisible" :teleported="true">
<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="recoverySegmentGroup">
{{ $t('trials:reading:Segmentations:button:recoverySegmentGroup') }}
</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>
<i class="el-icon-more" style="cursor: not-allowed;color: #fff;" v-if="isMPR"></i>
<el-select v-model="segmentationId" placeholder="" @change="selectSegmentGroup()"
:disabled="saveLoading">
<el-option v-for="item in segmentList" :key="`${item.segmentationId}`"
:label="item.name" :value="item.segmentationId">
</el-option>
</el-select>
</div>
<div style="display: flex;align-items: center;" v-if="readingTaskState < 2">
<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"
:disabled="saveLoading || isMPR || curSegmentGroup.isSaved"
@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 v-if="readingTaskState < 2"><i class="el-icon-plus"></i>
{{ $t('trials:reading:Segmentations:button:addSegment') }}
</span>
<span style="width: 10px;" v-else></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>
<div class="num">
<span>L: {{
item.bidirectional && item.bidirectional.maxMajor
? `${Number(item.bidirectional.maxMajor).toFixed(2)} mm` : ' '
}}</span>
<span>S: {{
item.bidirectional && item.bidirectional.maxMinor
? `${Number(item.bidirectional.maxMinor).toFixed(2)} mm` : ' '
}}</span>
</div>
<div class="btnBox">
<div
:title="!item.bidirectionalView ? $t('trials:reading:Segmentations:button:eye') : $t('trials:reading:Segmentations:button:eye-open')">
<svg-icon :icon-class="!item.bidirectionalView ? 'eye' : 'eye-open'"
style="color:#000;margin-right: 5px;cursor: pointer;"
@click.stop="viewBidirectional([item], !item.bidirectionalView)"
v-if="item.bidirectional" />
</div>
<div :title="$t('trials:reading:Segmentations:button:jumpto')">
<svg-icon icon-class="jumpto" style="color:#000;cursor: pointer;"
@click.stop="jumpBidirectional(item)"
v-if="item.bidirectional" />
</div>
<i class="el-icon-plus" style="color:#000;cursor: pointer;"
v-if="!item.bidirectional"
:title="$t('trials:reading:Segmentations:button:getBidirectional')"
@click.stop="addTip(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]">{{ JSON.stringify(item.stats[k].value) !== 'null'
?
k === 'count' ? Number(item.stats[k].value).toFixed(0) :
Number(item.stats[k].value).toFixed(digitPlaces)
: null
}}<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)" v-if="readingTaskState < 2 && !isMPR">
<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>
<i class="el-icon-more" style="cursor: not-allowed;color: #fff;" v-if="isMPR"></i>
</div>
</div>
</div>
</template>
</el-collapse-item>
</el-collapse>
<div class="saveBtnBox" v-if="readingTaskState < 2 && segmentList && segmentList.length > 1">
<el-button type="success" size="small" :disabled="saveLoading || isMPR || curSegmentGroup.isSaved"
@click="saveSegmentGroup()">
{{ $t("trials:reading:Segmentations:button:saveAll") }}
</el-button>
</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" :label="$t('dictionary:template:globalConfig:order')">
</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="StartTime" :label="$t('trials:reading:Segmentations:table:StartTime')">
</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>
</template>
<script>
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 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'
import * as polySeg from '@cornerstonejs/polymorphic-segmentation'
import Pagination from '@/components/Pagination'
cornerstoneTools.init({ addons: { polySeg } })
const {
ToolGroupManager,
Enums: csToolsEnums,
segmentation,
annotation,
LabelMapEditWithContourTool,
SegmentBidirectionalTool,
CrosshairsTool,
BrushTool,
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;
const searchDataDefault = () => {
return {
SegmentationId: null,
PageIndex: 1,
PageSize: 20,
Asc: false,
SortField: '',
}
}
export default {
name: "Segmentations",
components: {
Pagination,
},
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 {}
}
},
SegmentConfig: {
type: Object,
default: () => {
return {}
}
},
actionConfiguration: {
type: Object,
default: () => {
return {}
}
},
curSegSeries: {
type: Object,
default: () => {
return {}
}
},
trialCriterion: {
type: Object,
default: () => {
return {}
}
},
segId: {
type: String,
default: ''
},
segIndex: {
type: Number,
default: 0
},
renderingEngineId: {
type: String,
required: true
},
loadingText: {
type: String,
default: ''
},
globalLoading: {
type: Boolean,
default: false
},
histogramVisible: {
type: Boolean,
default: false
}
},
data() {
return {
visible: false,
recoveryList: [],
searchData: searchDataDefault(),
total: 0,
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,
segmentList: [],
segmentationId: "",
segmentIndex: null,
SegmentHight: 100,
colors: [
'#0ca8df',
'#ffd10a',
'#b6d634',
'#3fbe95',
'#785db0',
'#5070dd',
'#505372',
'#ff994d',
'#fb628b',
],
viewportIds: [], //
statsKey: [],
drawing: false, // 是否正在分割
// isDel: false,
digitPlaces: 2,
isloaded: false,
popoverId: null,
saveLoading: false,
popoverVisible: false,
timer: 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')
// console.log(cache, 'cache')
eventTarget.addEventListener(
'CORNERSTONE_TOOLS_SEGMENTATION_DATA_MODIFIED',
this.segmentationModifiedCallback
);
DicomEvent.$on('activeSeries', (series) => {
let { TaskInfo = {}, Id } = series
if (this.isMPR) return false
if (Id === this.series.Id && TaskInfo.VisitTaskId === this.visitInfo.VisitTaskId) return false
this.series = series
this.$emit("update:curSegSeries", Object.assign({}, series))
this.popoverId = null
this.segmentIndex = null
this.segmentationId = null
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.getSegmentationList()
})
DicomEvent.$on('isloaded', (data) => {
let { segment, isChange = true, viewportId, series } = data
DicomEvent.$emit('renderSegmentation', viewportId)
})
DicomEvent.$on('SegmentationLoading', (viewportId) => {
if (viewportId !== `${this.viewportKey}-${this.activeViewportIndex}`) return false
this.loading = false
})
DicomEvent.$on('changeMPR', () => {
if (this.loading) return false
// if (viewportId !== `${this.viewportKey}-${this.activeViewportIndex}`) return false
if (this.segmentList && this.segmentList.length > 0) {
this.segmentationId = this.segmentList[0].segmentationId
this.segmentIndex = this.segmentList[0] ? this.segmentList[0].segments[0].segmentIndex : null
}
})
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
},
readingTaskState() {
return this.series.TaskInfo ? this.series.TaskInfo.ReadingTaskState : 0
}
},
watch: {
SegmentConfig: {
handler() {
// this.readingSegmentByConfig()
},
deep: true
},
brushSize: {
handler() {
this.setBrushSize(this.activeTool)
},
deep: true
},
brushThreshold: {
handler() {
this.setBrushThreshold()
},
deep: true
},
segmentIndex() {
this.$emit('update:segIndex', this.segmentIndex)
},
segmentationId() {
this.$emit('update:segId', this.segmentationId)
}
},
methods: {
fileSizeFormatter(size) {
if (!size) return
return (size / Math.pow(1024, 2)).toFixed(3) + 'MB'
},
showSurface(item) {
this.$emit("showSurface", {
segmentationId: item.segmentationId,
segmentIndex: item.segmentIndex,
volumeId: this.series.SeriesInstanceUid,
segmentations: this.curSegmentGroup
})
},
handleClickPopover(item) {
this.popoverId = `popover-${item.segmentationId}_${item.segmentIndex}`
},
initThreshold() {
if (this.isMPR) return false
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),
});
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.SegmentNumber;
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.SegmentNumber;
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
if (this.isMPR) return false
if (this.histogramVisible && !this.ThresholdTools.includes(toolName)) return false
if (['viewport-MPR-1', 'viewport-MPR-2'].includes(`${this.viewportKey}-${this.activeViewportIndex}`)) 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 }]
})
// if (toolName === 'CircularEraser') {
// console.log(toolGroup.getToolInstance(toolName))
// }
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]
item.bidirectionalView = view
}
DicomEvent.$emit('viewBidirectional', arr)
},
async jumpBidirectional(item) {
DicomEvent.$emit('jumpBidirectional', item)
},
viewSegmentGroup(item) {
item.view = !item.view
item.segments.forEach(i => {
i.view = item.view
i.bidirectionalView = item.view
})
DicomEvent.$emit('viewSegmentation', item)
// this.viewBidirectional(item.segments, view)
},
viewSegment(item, view) {
item.bidirectionalView = view
item.view = view
this.$emit('setToolsPassive')
DicomEvent.$emit('viewSegment', item)
},
lockSegment(item, lock) {
if (this.readingTaskState >= 2) return false
if (this.isMPR) return false
this.$emit('setToolsPassive')
segmentation.segmentLocking.setSegmentIndexLocked(item.segmentationId, item.segmentIndex, lock)
item.lock = lock
if (!lock) this.changeSegmentationSavedStatus(item.segmentationId, lock)
this.lockOrUnLockSegment(item.id, 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()
},
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) {
let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.segmentationId && this.segmentationId === item.metadata.segmentationId && item.metadata.toolName === "SegmentBidirectional");
annotations.forEach(item => {
annotation.state.removeAnnotation(item.annotationUID)
})
// 只需要将分割数据清空分割所需的蒙版volume可以复用不需要清除
// 1.清除分割所需的蒙版volume(弃用)
// let volume = cache.getVolume(this.segmentationId)
// // 1. 销毁 Volume 实例
// volume.destroy();
// // 2. 从缓存中移除
// volume.removeFromCache();
// 2.清除分割数据及文件缓存
segmentation.removeSegmentation(this.segmentationId)
let imageId = null
if (this.curSegmentGroup.oldSegUrl) {
imageId = `wadouri:${this.OSSclientConfig.basePath}${this.curSegmentGroup.oldSegUrl}`
this.curSegmentGroup.oldSegUrl = null
} else {
imageId = `wadouri:${this.OSSclientConfig.basePath}${this.curSegmentGroup.segUrl}`
}
if (imageId && cache.getImage(imageId)) {
cache.removeImageLoadObject(imageId)
}
let r = await this.getSegmentation(this.segmentationId)
if (!r) return false
this.visible = false
this.$message.success(this.$t("trials:reading:Segmentations:message:restoreSuccess"))
this.resetViewport()
this.$nextTick(() => {
DicomEvent.$emit('renderSegmentationBychangeSegmention')
})
}
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) {
this.$emit('setToolsPassive')
this.segmentIndex = null;
let segment = s ? s : this.segmentList.find(item => item.segmentationId === this.segmentationId).segments[0]
this.$nextTick(() => {
this.selectSegment(segment)
})
},
getSegmentationName(num = 1) {
let defaultSegmentationName = this.trialCriterion.DefaultSegmentName.SegmentationName
let name = defaultSegmentationName
let has = this.segmentList.find(item => item.name === name)
if (has) {
name = defaultSegmentationName + num
has = this.segmentList.find(item => item.name === name)
num++
if (has) name = this.getSegmentationName(num)
}
return name
},
getSegmentName(arr, num = 1) {
let defaultSegmentName = this.trialCriterion && this.trialCriterion.DefaultSegmentName && this.trialCriterion.DefaultSegmentName.SegmentNameList && this.trialCriterion.DefaultSegmentName.SegmentNameList.length > 0 ? this.trialCriterion.DefaultSegmentName.SegmentNameList[0] : arr[0].SegmentLabel
let name = defaultSegmentName + num
let has = arr.find(item => item.SegmentLabel === name)
num++
if (has) name = this.getSegmentName(arr, num)
return name
},
async addSegmentGroup() {
// let segmentationId = this.$guid();
this.popoverVisible = false
let obj = {
name: this.getSegmentationName(),
view: true,
segmentationId: null,
isSaved: false,
segments: []
}
this.segmentIndex = null
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
obj.segmentationId = segmentationId
await this.createSegmentation(obj.segmentationId)
this.createSegmentationRepresentation(obj.segmentationId)
this.trialCriterion.DefaultSegmentName.SegmentNameList.forEach(async (SegmentName, index) => {
let o = {
segmentIndex: index + 1,
segmentationId,
SegmentLabel: SegmentName,
color: this.colors[index],
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.splice(index, 0, o);
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, index + 1);
this.changeColor(this.colors[index], { segmentationId: obj.segmentationId, segmentIndex: index + 1, color: this.colors[index] })
if (index === this.trialCriterion.DefaultSegmentName.SegmentNameList.length - 1) {
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, 1);
}
})
this.segmentList.push(obj);
this.segmentationId = obj.segmentationId;
this.segmentIndex = 1
},
async addSegment() {
if (this.saveLoading) return false
if (this.isMPR) return false
if (this.segmentList.length <= 0) {
let obj = {
name: this.getSegmentationName(),
view: true,
isSaved: false,
segments: []
}
this.segmentIndex = null
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
obj.segmentationId = segmentationId
await this.createSegmentation(segmentationId)
this.createSegmentationRepresentation(segmentationId)
this.trialCriterion.DefaultSegmentName.SegmentNameList.forEach(async (SegmentName, index) => {
let o = {
segmentIndex: index + 1,
segmentationId,
SegmentLabel: SegmentName,
color: this.colors[index],
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.splice(index, 0, o);
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, index + 1);
this.changeColor(this.colors[index], { segmentationId: obj.segmentationId, segmentIndex: index + 1, color: this.colors[index] })
if (index === this.trialCriterion.DefaultSegmentName.SegmentNameList.length - 1) {
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, 1);
}
})
this.segmentList.push(obj);
this.segmentationId = segmentationId;
this.segmentIndex = 1
// segmentation.segmentIndex.setActiveSegmentIndex(this.segmentList[0].segmentationId, 1);
// this.changeColor(this.colors[0], { segmentationId: this.segmentList[0].segmentationId, segmentIndex: 1, color: this.colors[0] })
// viewportIds.forEach(id => {
// segmentation.config.color.setSegmentIndexColor(id, this.segmentList[0].segmentationId, 1, this.hex2Rgb(this.colors[0]))
// })
// this.readingSegmentByConfig()
} else {
this.changeSegmentationSavedStatus(this.segmentationId, false)
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: this.getSegmentName(item.segments),
color: segmentIndex >= this.colors.length ? this.colors[0] : this.colors[segmentIndex],
stats: null,
bidirectional: null,
bidirectionalView: true,
view: true,
lock: false
}
if (segmentIndex >= this.colors.length) {
let index = segmentIndex % 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)
this.segmentIndex = obj.segmentIndex
// segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, obj.segmentIndex);
this.changeColor(obj.color, { segmentationId: obj.segmentationId, segmentIndex: obj.segmentIndex, color: obj.color })
// viewportIds.forEach(id => {
// segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, obj.segmentIndex, this.hex2Rgb(obj.color))
// })
}
},
changeColor(e, item) {
DicomEvent.$emit('changeColor', item)
},
// 清空所有分割
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() {
this.popoverId = null
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 volume = cache.getVolume(this.segmentationId)
// 1. 销毁 Volume 实例
volume.destroy();
// 2. 从缓存中移除
volume.removeFromCache();
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)
})
let f = this.segmentList.some(item => item.segUrl)
DicomEvent.$emit("IsBeSegment", { StudyId: this.series.StudyId, Id: this.series.Id, IsBeSegment: f })
if (this.segmentList.length > 0) {
this.segmentationId = this.segmentList[0].segmentationId;
this.selectSegmentGroup()
} else {
this.segmentationId = null
}
this.resetViewport()
this.$emit('resetQuestion')
},
// 删除分割片段
async delSegment(data) {
this.popoverId = null;
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 })
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]], true)
},
resetViewport(passive = true) {
DicomEvent.$emit('resetViewport')
if (passive) this.$emit('setToolsPassive')
},
async rename(key, item) {
let value = null
if (key === 'segmentGroup') {
let group = this.segmentList.find(i => i.segmentationId === this.segmentationId)
value = group.name
} else {
value = item.SegmentLabel
}
let name = await this.customPrompt(value)
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, url: group.segUrl, size: group.size })
} 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(name) {
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,
inputValue: name,
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.viewportIds.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() {
this.popoverVisible = false
let group = this.segmentList.find(item => item.segmentationId === this.segmentationId)
this.exportSegmentation(this.segmentationId, group, true)
},
exportSegmentation(segmentationId, group, isFile = false) {
try {
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
};
let segmentIndexs = []
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) {
segmentIndexs.push(segmentIndex)
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
);
segmentIndexs.forEach((item, index) => {
if (generatedSegmentation.dataset.SegmentSequence[index]) {
generatedSegmentation.dataset.SegmentSequence[index].SegmentNumber = item.toString()
}
})
generatedSegmentation.dataset.SegmentSequence.sort((a, b) => a.SegmentNumber - b.SegmentNumber)
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`);
}
} catch (err) {
console.log(err)
}
},
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)
},
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) {
try {
if (!segmentation.state.getSegmentation(segmentationId)) return false
this.saveLoading = true
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';
}
this.saveLoading = false
} catch (err) {
console.log(err)
this.saveLoading = false
}
},
createSegmentationRepresentation(segmentationId) {
DicomEvent.$emit('createSegmentationRepresentation', segmentationId)
this.$emit('setToolsPassive')
},
contentMouseup() {
try {
// 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);
} catch (err) {
console.log(err)
}
},
// 获取当前任务分割标记与问题绑定关系
async getSegmentBindingList(param = {}) {
try {
let data = {
VisitTaskId: this.series.TaskInfo.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.series.TaskInfo.VisitTaskId,
BindingList: list
}
let res = await saveSegmentBindingAndAnswer(data)
if (res.IsSuccess) {
this.$emit("resetQuestion")
}
} catch (err) {
console.log(err)
}
},
checkSaveLoading(callback) {
if (!this.saveLoading) {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.saveSegmentGroup(null, true, callback)
} else {
this.timer = setInterval(() => {
if (!this.saveLoading) {
console.log(this.saveLoading, 'timer')
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.saveSegmentGroup(null, true, callback)
}
}, 100)
}
},
// 保存整个分组
async saveSegmentGroup(list = null, saveSegment = true, callback) {
try {
console.log('saveSegmentGroup')
let segmentList = list ? list : this.segmentList
if (segmentList.length <= 0) return false
this.$emit("setToolsPassive")
this.$emit("update:globalLoading", true)
this.$emit("update:loadingText", this.$t("segment:loadingText:saveSegmentation"))
let IsBeSegment = false
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.oldSegUrl = segmentGroup.segUrl
segmentGroup.segUrl = this.$getObjectName(result.url)
DicomEvent.$emit("IsBeSegment", { StudyId: this.series.StudyId, Id: this.series.Id, IsBeSegment: true })
} else {
blob = { size: 0 }
segmentGroup.segUrl = null
}
this.addOrUpdateSegmentation({ name: segmentGroup.name, id: segmentGroup.segmentationId, url: segmentGroup.segUrl, size: blob.size })
this.changeSegmentationSavedStatus(segmentGroup.segmentationId, saveSegment)
if (saveSegment) {
await this.getBidirectionalSaveSegment(segmentList)
this.syncBindingAnswer(segmentList)
}
}
if (!IsBeSegment) {
let f = this.segmentList.some(item => item.segUrl)
DicomEvent.$emit("IsBeSegment", { StudyId: this.series.StudyId, Id: this.series.Id, IsBeSegment: f })
}
this.$emit("update:globalLoading", false)
if (callback) callback()
} 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 } = 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, true)
}
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 === '-Infinity' || answer === 'NaN' ? null : answer,
QuestionId: item.QuestionId,
RowId: item.RowId,
SegmentId: item.SegmentId,
SegmentationId: item.SegmentationId,
TableQuestionId: item.TableQuestionId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
}
bindingList.push(o)
}
}
if (bindingList.length > 0) this.saveSegmentBindingAndAnswer(bindingList)
},
// 新增或修改分割组
async addOrUpdateSegmentation(item) {
let { name, id, url, size } = 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.series.TaskInfo.VisitTaskId,
}
if (url) data.SegUrl = url;
if (id) data.Id = id;
if (size) data.FileSize = size;
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 getSegmentation(segmentationId) {
return new Promise(async (resolve, reject) => {
try {
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.size = item.FileSize;
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
resolve(true)
} else {
resolve(false)
}
} catch (err) {
console.log(err)
resolve(false)
}
})
},
// 获取分割组
async getSegmentationList() {
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]
let obj = this.segmentList.find(i => i.segmentationId === item.Id)
if (!obj) {
obj = {
segmentationId: item.Id,
name: item.SegmentationName,
view: true,
segUrl: item.SEGUrl,
size: item.FileSize,
isSaved: item.IsSaved,
segments: []
}
this.segmentList.push(obj)
}
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.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)
}
if (!this.segmentIndex) {
this.segmentIndex = s.SegmentNumber
}
})
}
this.isloaded = false
}
} 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,
SegmentNumber: segmentIndex,
SegmentationId: segmentationId,
VisitTaskId: this.series.TaskInfo.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)
}
},
// 修改分割片段锁定状态
async lockOrUnLockSegment(SegmentId, IsLock) {
try {
let data = {
SegmentId,
IsLock
}
let res = await lockOrUnLockSegment(data)
if (res.IsSuccess) return true
return false
} 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);
}
},
destroyed() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}
}
</script>
<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 {
height: 100%;
position: relative;
.saveBtnBox {
position: absolute;
bottom: 5px;
left: 50%;
}
}
.Bidirectionalbox {
padding: 10px;
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 10px;
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 10px;
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;
cursor: not-allowed;
}
.serialNum {
color: #fff;
}
}
.SegmentGroupBox {
padding: 8px 10px;
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 10px;
&:hover {
background-color: rgba($color: #409EFF, $alpha: 0.2);
}
}
.addSegmentBox {
cursor: pointer;
color: #fff;
line-height: 30px;
margin: 5px;
padding: 0 10px;
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 10px;
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 10px;
width: 50px;
}
}
.SegmentConfig {
padding: 5px 10px;
}
.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 10px;
margin-bottom: 10px;
border-bottom: 1px solid #404040;
.icon {
padding: 5px 10px;
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: 10px;
// 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>