2528 lines
108 KiB
Vue
2528 lines
108 KiB
Vue
<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> |