228 lines
6.1 KiB
Vue
228 lines
6.1 KiB
Vue
<template>
|
||
<div class="label-studio-annotator">
|
||
<div id="label-studio" class="annotation-container"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import LabelStudio from 'label-studio';
|
||
import '@/assets/styles/labelStudio.scss';
|
||
import { manualAnnotate } from '../../../../api/dataCenter/annotationTask';
|
||
|
||
export default {
|
||
name: 'LabelStudioAnnotator',
|
||
props: {
|
||
fileUrl: { type: String, required: true },
|
||
taskId: { type: Number, required: true },
|
||
config: { type: String, required: true },
|
||
id: { type: Number, required: true },
|
||
itemIndex: { type: Number, required: true },
|
||
fileAnnotationStatus: { type: String, required: true },
|
||
annotations: {
|
||
type: Array,
|
||
default: () => [] // 默认值为空数组
|
||
}
|
||
},
|
||
computed: {
|
||
index: {
|
||
get() {
|
||
return this.itemIndex;
|
||
},
|
||
set(value) {
|
||
this.$parent.updateItemIndex(value,this.label); // 更新父组件的索引
|
||
}
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
label:null,
|
||
labelStudio: null,
|
||
annotationsList: [] // 用于存储当前标注的结果
|
||
};
|
||
},
|
||
watch: {
|
||
fileUrl: 'resetLabelStudio' // 当文件地址变化时,重置LabelStudio
|
||
},
|
||
mounted() {
|
||
this.initLabelStudio(); // 组件挂载完成后初始化LabelStudio
|
||
},
|
||
methods: {
|
||
// 处理返回的标注数据
|
||
getAnnotations() {
|
||
return [{
|
||
result: this.annotations.map(annotation => ({
|
||
type: "rectanglelabels",
|
||
from_name: "label",
|
||
to_name: "image",
|
||
value: {
|
||
rectanglelabels: [annotation.label],
|
||
x: annotation.x,
|
||
y: annotation.y,
|
||
width: annotation.width,
|
||
height: annotation.height
|
||
}
|
||
}))
|
||
}];
|
||
},
|
||
// 初始化 LabelStudio
|
||
initLabelStudio() {
|
||
this.cleanupLabelStudio(); // 清理之前的实例
|
||
|
||
const task = {
|
||
id: this.id,
|
||
data: { image: this.fileUrl },
|
||
};
|
||
if (this.annotations) {
|
||
task.annotations = this.getAnnotations();
|
||
}
|
||
let interfaces = [];
|
||
if (this.fileAnnotationStatus === '2'){
|
||
interfaces = ["controls"];
|
||
}else {
|
||
interfaces = ["panel","update", "submit", "controls"];
|
||
}
|
||
this.$nextTick(() => {
|
||
const labelStudioElement = document.getElementById('label-studio');
|
||
if (!labelStudioElement) {
|
||
console.error('Label Studio element not found');
|
||
return;
|
||
}
|
||
|
||
this.labelStudio = new LabelStudio('label-studio', {
|
||
config: this.config,
|
||
interfaces: interfaces,
|
||
user: { pk: 1, firstName: '标注者', lastName: '用户' },
|
||
task,
|
||
onLabelStudioLoad: (LS) => {
|
||
if (!LS.annotationStore.selectedAnnotation) {
|
||
const annotation = LS.annotationStore.addAnnotation({ userGenerate: true });
|
||
LS.annotationStore.selectAnnotation(annotation.id);
|
||
}
|
||
this.changeButtonText(LS);
|
||
},
|
||
onSubmitAnnotation: (LS, annotation) => this.handleAnnotationSubmit(LS, annotation, task),
|
||
//onUpdateAnnotation:(LS, annotation) => this.handleAnnotationSubmit(LS, annotation, task),
|
||
});
|
||
});
|
||
},
|
||
// 提交标注结果
|
||
async handleAnnotationSubmit(LS, annotation, task) {
|
||
const results = annotation.serializeAnnotation();
|
||
console.log(results)
|
||
if (results.length === 0) return;
|
||
|
||
const formattedAnnotations = results.map(result => {
|
||
if (result.type === 'rectanglelabels') {
|
||
return {
|
||
label: result.value.rectanglelabels[0],
|
||
x: result.value.x,
|
||
y: result.value.y,
|
||
width: result.value.width,
|
||
height: result.value.height
|
||
};
|
||
}
|
||
return result;
|
||
});
|
||
|
||
const data = {
|
||
annotationResult: JSON.stringify(formattedAnnotations),
|
||
taskId: this.taskId,
|
||
fileId: this.id
|
||
};
|
||
|
||
try {
|
||
await manualAnnotate(data);
|
||
this.annotationsList.push({
|
||
taskId: task.id,
|
||
annotation: JSON.stringify(formattedAnnotations),
|
||
url: this.fileUrl
|
||
});
|
||
this.label = JSON.stringify(formattedAnnotations)
|
||
this.index++; // 更新索引
|
||
} catch (error) {
|
||
console.error('Failed to submit annotation:', error);
|
||
} finally {
|
||
this.cleanupLabelStudio(); // 清理 LabelStudio 实例
|
||
}
|
||
},
|
||
changeButtonText(LS) {
|
||
const buttons = {
|
||
'Undo': '撤销',
|
||
'Redo': '重做',
|
||
'Reset': '重置',
|
||
'Submit': '提交',
|
||
'Update':'更新'
|
||
};
|
||
|
||
const updateButtonText = (element) => {
|
||
const buttonElements = element.querySelectorAll('button');
|
||
buttonElements.forEach((button) => {
|
||
const text = button.textContent.trim();
|
||
if (buttons[text]) {
|
||
button.textContent = buttons[text];
|
||
}
|
||
});
|
||
};
|
||
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.type === 'childList') {
|
||
mutation.addedNodes.forEach((node) => {
|
||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||
updateButtonText(node);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
observer.observe(document.body, { childList: true, subtree: true });
|
||
updateButtonText(document.body);
|
||
},
|
||
|
||
|
||
// 重置 LabelStudio
|
||
resetLabelStudio() {
|
||
this.cleanupLabelStudio();
|
||
this.initLabelStudio(); // 重新初始化
|
||
},
|
||
// 清理 LabelStudio 实例
|
||
cleanupLabelStudio() {
|
||
if (this.labelStudio && this.labelStudio.destroy) {
|
||
this.labelStudio.destroy();
|
||
}
|
||
const labelStudioElement = document.getElementById('label-studio');
|
||
if (labelStudioElement) {
|
||
labelStudioElement.innerHTML = ''; // 清空容器
|
||
}
|
||
this.labelStudio = null; // 置空实例
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.label-studio-annotator{
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.annotation-container{
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
>div{
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 5px;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
</style>
|