2024-11-28 12:27:07 +08:00
|
|
|
|
<template>
|
2024-12-01 15:08:48 +08:00
|
|
|
|
<div class="label-studio-annotator">
|
|
|
|
|
|
<div id="label-studio" class="annotation-container"></div>
|
2024-11-28 12:27:07 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2024-12-01 15:08:48 +08:00
|
|
|
|
import LabelStudio from 'label-studio';
|
2024-12-03 16:19:33 +08:00
|
|
|
|
import '@/assets/styles/labelStudio.scss';
|
2024-12-01 15:08:48 +08:00
|
|
|
|
import { manualAnnotate } from '../../../../api/dataCenter/annotationTask';
|
2024-11-28 12:27:07 +08:00
|
|
|
|
|
|
|
|
|
|
export default {
|
2024-12-01 15:08:48 +08:00
|
|
|
|
name: 'LabelStudioAnnotator',
|
2024-11-28 12:27:07 +08:00
|
|
|
|
props: {
|
2024-12-01 15:08:48 +08:00
|
|
|
|
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 },
|
2024-12-03 16:35:45 +08:00
|
|
|
|
fileAnnotationStatus: { type: String, required: true },
|
2024-12-01 15:08:48 +08:00
|
|
|
|
annotations: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => [] // 默认值为空数组
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
index: {
|
|
|
|
|
|
get() {
|
|
|
|
|
|
return this.itemIndex;
|
|
|
|
|
|
},
|
|
|
|
|
|
set(value) {
|
2024-12-02 10:46:26 +08:00
|
|
|
|
this.$parent.updateItemIndex(value,this.label); // 更新父组件的索引
|
2024-12-01 15:08:48 +08:00
|
|
|
|
}
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2024-12-02 10:46:26 +08:00
|
|
|
|
label:null,
|
2024-11-28 12:27:07 +08:00
|
|
|
|
labelStudio: null,
|
2024-12-01 15:08:48 +08:00
|
|
|
|
annotationsList: [] // 用于存储当前标注的结果
|
|
|
|
|
|
};
|
2024-11-28 12:27:07 +08:00
|
|
|
|
},
|
2024-12-01 15:08:48 +08:00
|
|
|
|
watch: {
|
|
|
|
|
|
fileUrl: 'resetLabelStudio' // 当文件地址变化时,重置LabelStudio
|
2024-12-01 09:48:41 +08:00
|
|
|
|
},
|
2024-11-28 12:27:07 +08:00
|
|
|
|
mounted() {
|
2024-12-01 15:08:48 +08:00
|
|
|
|
this.initLabelStudio(); // 组件挂载完成后初始化LabelStudio
|
2024-11-28 12:27:07 +08:00
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2024-12-01 15:08:48 +08:00
|
|
|
|
// 处理返回的标注数据
|
|
|
|
|
|
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
|
2024-11-28 12:27:07 +08:00
|
|
|
|
initLabelStudio() {
|
2024-12-01 15:08:48 +08:00
|
|
|
|
this.cleanupLabelStudio(); // 清理之前的实例
|
|
|
|
|
|
|
|
|
|
|
|
const task = {
|
|
|
|
|
|
id: this.id,
|
|
|
|
|
|
data: { image: this.fileUrl },
|
|
|
|
|
|
};
|
2024-12-02 01:44:31 +08:00
|
|
|
|
if (this.annotations) {
|
2024-12-01 15:08:48 +08:00
|
|
|
|
task.annotations = this.getAnnotations();
|
|
|
|
|
|
}
|
2024-12-03 16:35:45 +08:00
|
|
|
|
let interfaces = [];
|
|
|
|
|
|
if (this.fileAnnotationStatus === '2'){
|
|
|
|
|
|
interfaces = ["controls"];
|
|
|
|
|
|
}else {
|
|
|
|
|
|
interfaces = ["panel","update", "submit", "controls"];
|
|
|
|
|
|
}
|
2024-12-01 15:08:48 +08:00
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
const labelStudioElement = document.getElementById('label-studio');
|
|
|
|
|
|
if (!labelStudioElement) {
|
|
|
|
|
|
console.error('Label Studio element not found');
|
|
|
|
|
|
return;
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
2024-12-01 15:08:48 +08:00
|
|
|
|
|
|
|
|
|
|
this.labelStudio = new LabelStudio('label-studio', {
|
|
|
|
|
|
config: this.config,
|
2024-12-03 16:35:45 +08:00
|
|
|
|
interfaces: interfaces,
|
2024-12-01 15:08:48 +08:00
|
|
|
|
user: { pk: 1, firstName: '标注者', lastName: '用户' },
|
|
|
|
|
|
task,
|
|
|
|
|
|
onLabelStudioLoad: (LS) => {
|
|
|
|
|
|
if (!LS.annotationStore.selectedAnnotation) {
|
|
|
|
|
|
const annotation = LS.annotationStore.addAnnotation({ userGenerate: true });
|
|
|
|
|
|
LS.annotationStore.selectAnnotation(annotation.id);
|
|
|
|
|
|
}
|
2024-12-02 14:58:29 +08:00
|
|
|
|
this.changeButtonText(LS);
|
2024-12-01 15:08:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
onSubmitAnnotation: (LS, annotation) => this.handleAnnotationSubmit(LS, annotation, task),
|
2025-01-12 16:39:32 +08:00
|
|
|
|
//onUpdateAnnotation:(LS, annotation) => this.handleAnnotationSubmit(LS, annotation, task),
|
2024-12-01 15:08:48 +08:00
|
|
|
|
});
|
2024-11-28 12:27:07 +08:00
|
|
|
|
});
|
|
|
|
|
|
},
|
2024-12-01 15:08:48 +08:00
|
|
|
|
// 提交标注结果
|
|
|
|
|
|
async handleAnnotationSubmit(LS, annotation, task) {
|
|
|
|
|
|
const results = annotation.serializeAnnotation();
|
2024-12-02 11:19:17 +08:00
|
|
|
|
console.log(results)
|
|
|
|
|
|
if (results.length === 0) return;
|
2024-12-01 15:08:48 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
});
|
2024-11-28 12:27:07 +08:00
|
|
|
|
|
2024-12-01 15:08:48 +08:00
|
|
|
|
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
|
|
|
|
|
|
});
|
2024-12-02 10:46:26 +08:00
|
|
|
|
this.label = JSON.stringify(formattedAnnotations)
|
2024-12-01 15:08:48 +08:00
|
|
|
|
this.index++; // 更新索引
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to submit annotation:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.cleanupLabelStudio(); // 清理 LabelStudio 实例
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2024-12-02 14:58:29 +08:00
|
|
|
|
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);
|
|
|
|
|
|
},
|
2024-12-03 15:17:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
2024-12-01 15:08:48 +08:00
|
|
|
|
// 重置 LabelStudio
|
|
|
|
|
|
resetLabelStudio() {
|
|
|
|
|
|
this.cleanupLabelStudio();
|
|
|
|
|
|
this.initLabelStudio(); // 重新初始化
|
|
|
|
|
|
},
|
|
|
|
|
|
// 清理 LabelStudio 实例
|
|
|
|
|
|
cleanupLabelStudio() {
|
|
|
|
|
|
if (this.labelStudio && this.labelStudio.destroy) {
|
|
|
|
|
|
this.labelStudio.destroy();
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
2024-12-01 15:08:48 +08:00
|
|
|
|
const labelStudioElement = document.getElementById('label-studio');
|
|
|
|
|
|
if (labelStudioElement) {
|
|
|
|
|
|
labelStudioElement.innerHTML = ''; // 清空容器
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
2024-12-01 15:08:48 +08:00
|
|
|
|
this.labelStudio = null; // 置空实例
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-01 15:08:48 +08:00
|
|
|
|
};
|
2024-11-28 12:27:07 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
2024-12-03 15:17:56 +08:00
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
.label-studio-annotator{
|
2024-12-01 15:08:48 +08:00
|
|
|
|
width: 100%;
|
2024-12-03 15:17:56 +08:00
|
|
|
|
height: 100%;
|
2024-12-01 15:08:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-03 15:17:56 +08:00
|
|
|
|
.annotation-container{
|
2024-11-28 12:27:07 +08:00
|
|
|
|
width: 100%;
|
2024-12-01 15:08:48 +08:00
|
|
|
|
height: 100%;
|
2024-12-03 15:17:56 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
|
|
|
|
>div{
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 5px;
|
|
|
|
|
|
}
|
2024-11-28 12:27:07 +08:00
|
|
|
|
}
|
2024-12-03 15:17:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-11-28 12:27:07 +08:00
|
|
|
|
</style>
|