提交代码

This commit is contained in:
jiang 2024-12-01 15:08:48 +08:00
parent 2467ebca1d
commit 578b0f7b8d
4 changed files with 240 additions and 190 deletions

View File

@ -43,6 +43,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"echarts": "5.4.0", "echarts": "5.4.0",
"element-ui": "2.15.14", "element-ui": "2.15.14",
"fabric": "^5.3.0",
"file-saver": "2.0.5", "file-saver": "2.0.5",
"fuse.js": "6.4.3", "fuse.js": "6.4.3",
"highlight.js": "9.18.5", "highlight.js": "9.18.5",
@ -51,6 +52,7 @@
"jsencrypt": "3.0.0-rc.1", "jsencrypt": "3.0.0-rc.1",
"label-studio": "^1.0.1", "label-studio": "^1.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"prop-types": "^15.8.1",
"quill": "1.3.7", "quill": "1.3.7",
"screenfull": "5.0.2", "screenfull": "5.0.2",
"sm-crypto": "^0.3.13", "sm-crypto": "^0.3.13",

View File

@ -56,4 +56,13 @@ export function getMyAnnotationFiles(annotationStatus,taskId) {
}) })
} }
export function manualAnnotate(data) {
return request({
url: '/ai/annotationTask/manualAnnotate',
method: 'post',
data:data
})
}

View File

@ -1,144 +1,176 @@
<template> <template>
<div> <div class="label-studio-annotator">
<div ref="labelStudio" id="label-studio-container"></div> <div id="label-studio" class="annotation-container"></div>
</div> </div>
</template> </template>
<script> <script>
import LabelStudio from 'label-studio' import LabelStudio from 'label-studio';
import 'label-studio/build/static/css/main.css' import 'label-studio/build/static/css/main.css';
import { manualAnnotate } from '../../../../api/dataCenter/annotationTask';
export default { export default {
name: 'CustomLabelStudio', name: 'LabelStudioAnnotator',
props: { props: {
task: { fileUrl: { type: String, required: true },
type: String, taskId: { type: Number, required: true },
required: true config: { type: String, required: true },
id: { type: Number, required: true },
itemIndex: { type: Number, required: true },
annotations: {
type: Array,
default: () => [] //
}
},
computed: {
index: {
get() {
return this.itemIndex;
},
set(value) {
this.$parent.updateItemIndex(value); //
}
} }
}, },
data() { data() {
return { return {
labelStudio: null, labelStudio: null,
customButtons: [ annotationsList: [] //
{ };
id: "custom-button",
name: "自定义按钮",
icon: "🔍",
action: () => {
console.log("自定义按钮被点击了!")
}
}
],
chineseLocalization: {
"DONE": "完成",
"SKIP": "跳过",
"SUBMIT": "提交",
"ANNOTATION": "标注",
"ANNOTATIONS": "标注",
"LABEL": "标签",
"LABELS": "标签",
"RELATIONS": "关系",
"REGIONS": "区域",
"RESULTS": "结果",
// ...
},
labelConfig: `
<View>
<Image name="image" value="$image"/>
<RectangleLabels name="label" toName="image">
<Label value="人" background="#ff0000"/>
<Label value="车" background="#00ff00"/>
</RectangleLabels>
</View>
`,
previousAnnotations: [] //
}
}, },
watch:{ watch: {
fileUrl: 'resetLabelStudio' // LabelStudio
}, },
mounted() { mounted() {
this.initLabelStudio(); this.initLabelStudio(); // LabelStudio
},
beforeDestroy() {
if (this.labelStudio) {
this.labelStudio.destroy();
}
}, },
methods: { 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() { initLabelStudio() {
this.labelStudio = new LabelStudio('label-studio-container', { console.log(this.annotations);
config: this.labelConfig, this.cleanupLabelStudio(); //
interfaces: [
"panel",
"update",
"controls",
],
user: {
pk: 1,
firstName: "James",
lastName: "Dean"
},
task:JSON.parse( this.task),
locale: 'zh_CN',
messages: this.chineseLocalization,
onLabelStudioLoad: (LS) => {
console.log("Label Studio 已加载", LS);
// const task = {
setInterval(() => { id: this.id,
this.checkAnnotations(LS); data: { image: this.fileUrl },
}, 1000); // };
// if (this.annotations.length) {
const annotation = LS.annotationStore.addAnnotation({ task.annotations = this.getAnnotations();
userGenerate: true }
});
// this.$nextTick(() => {
const box = annotation.geometry; 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: ["update", "submit", "controls"],
user: { pk: 1, firstName: '标注者', lastName: '用户' },
task,
onLabelStudioLoad: (LS) => {
if (!LS.annotationStore.selectedAnnotation) {
const annotation = LS.annotationStore.addAnnotation({ userGenerate: true });
LS.annotationStore.selectAnnotation(annotation.id);
}
},
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();
if (!results) return;
// const formattedAnnotations = results.map(result => {
checkAnnotations(LS) { if (result.type === 'rectanglelabels') {
const currentAnnotations = LS.annotationStore.data; // return {
// label: result.value.rectanglelabels[0],
if (this.hasAnnotationsChanged(currentAnnotations)) { x: result.value.x,
console.log('标注数据发生变化:', currentAnnotations); y: result.value.y,
this.previousAnnotations = currentAnnotations; // 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.index++; //
} catch (error) {
console.error('Failed to submit annotation:', error);
} finally {
this.cleanupLabelStudio(); // LabelStudio
} }
}, },
// LabelStudio
// resetLabelStudio() {
hasAnnotationsChanged(currentAnnotations) { this.cleanupLabelStudio();
if (currentAnnotations.length !== this.previousAnnotations.length) { this.initLabelStudio(); //
return true; // },
// LabelStudio
cleanupLabelStudio() {
if (this.labelStudio && this.labelStudio.destroy) {
this.labelStudio.destroy();
} }
const labelStudioElement = document.getElementById('label-studio');
// if (labelStudioElement) {
for (let i = 0; i < currentAnnotations.length; i++) { labelStudioElement.innerHTML = ''; //
const current = currentAnnotations[i];
const previous = this.previousAnnotations[i];
// true
if (JSON.stringify(current.geometry) !== JSON.stringify(previous.geometry)) {
return true;
}
} }
this.labelStudio = null; //
return false;
} }
} }
} };
</script> </script>
<style scoped> <style scoped>
.label-studio-container { .label-studio-annotator {
height: 600px;
width: 100%; width: 100%;
border: 1px solid #e0e0e0; height: 100vh;
border-radius: 8px; display: flex;
overflow: hidden; flex-direction: column;
}
.annotation-container {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 500px; /* 设置最小高度 */
} }
</style> </style>

View File

@ -3,15 +3,7 @@
<!-- 顶部区域 --> <!-- 顶部区域 -->
<div class="top-part"> <div class="top-part">
<div class="top-content"> <div class="top-content">
<!-- 左侧按钮组 -->
<div class="top-content-left">
<el-button v-for="(icon, index) in leftIcons"
:key="index"
:icon="icon"
circle
>
</el-button>
</div>
<!-- 右侧选择框和设置按钮 --> <!-- 右侧选择框和设置按钮 -->
<div class="top-content-right"> <div class="top-content-right">
<el-select <el-select
@ -29,9 +21,9 @@
/> />
</el-select> </el-select>
<span class="settings-icon"> <!-- <span class="settings-icon">
<i class="el-icon-setting"></i> <i class="el-icon-setting"></i>
</span> </span>-->
</div> </div>
</div> </div>
</div> </div>
@ -78,10 +70,11 @@
<div class="bottom-content-left-bottom"> <div class="bottom-content-left-bottom">
<div> <div>
<ul> <ul>
<li v-for="item in images" class="list-item"> <li v-for="(item, index) in images" :key="item.fileId" class="list-item">
<div @click="setItem(item)"> <div @click="setItem(item, index)">
<input type="checkbox" :checked="false" disabled> <input type="checkbox" :checked="item.fileAnnotationStatus==='1'" disabled>
<span style="font-size: 14px;margin-left: 5px">{{ item.fileName }}</span> <span :class="{'highlighted': itemIndex === index}" style="font-size: 14px; margin-left: 5px;"
>{{ item.fileName }}</span>
</div> </div>
</li> </li>
</ul> </ul>
@ -90,43 +83,10 @@
</div> </div>
<div class="bottom-content-center"> <div class="bottom-content-center">
<div> <div>
<custom-label-studio :task="JSON.stringify(task)"/> <custom-label-studio :annotations="annotationResult" :taskId="taskId" :item-index="itemIndex"
</div> :config="labelConfig" :id="task.id" :file-url="task.data.image"
</div> @update-itemIndex="updateItemIndex"
<div class="bottom-content-right"> ></custom-label-studio>
<div class="bottom-content-right-center">
<div class="bottom-content-right-top">
<div>
</div>
</div>
<div class="bottom-content-right-middle">
<div>
</div>
</div>
<div class="bottom-content-right-bottom">
<div>
<el-button
type="primary"
plain
size="mini"
>上一张
</el-button>
<el-button
type="primary"
plain
size="mini"
>下一张
</el-button>
<el-button
type="primary"
plain
size="mini"
>保存
</el-button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -136,20 +96,32 @@
<script> <script>
import customLabelStudio from './customLabelStudio.vue' import customLabelStudio from './customLabelStudio.vue'
import { getMyNoAnnotatedTask,getMyAnnotationFiles } from '../../../../api/dataCenter/annotationTask' import { getMyNoAnnotatedTask, getMyAnnotationFiles } from '../../../../api/dataCenter/annotationTask'
export default { export default {
dicts: ['ai_annotate_type'], dicts: ['ai_annotate_type'],
components: { customLabelStudio }, components: { customLabelStudio },
data() { data() {
return { return {
taskList:[], itemIndex: 0,
annotationResult: [],
item: {},
taskList: [],
task: { task: {
id: 1, id: 0,
data: { data: {
image: 'http://192.168.0.14:9090/bonus/1/测试/xigu_wangwang_3787075_37.jpg' image: ''
} }
}, },
labelConfig: `
<View>
<Image name="image" value="$image"/>
<RectangleLabels name="label" toName="image">
<Label value="人" background="#ff0000"/>
<Label value="车" background="#00ff00"/>
</RectangleLabels>
</View>
`,
taskId: undefined, // ID taskId: undefined, // ID
leftIcons: [ leftIcons: [
'el-icon-circle-plus-outline', 'el-icon-circle-plus-outline',
@ -165,38 +137,67 @@ export default {
} }
}, },
created() { created() {
this.listSelection() this.loadTaskList(); //
}, },
methods: { methods: {
listSelection() { //
getMyNoAnnotatedTask({}).then(res => { updateItemIndex(val) {
this.taskList = res.data; const item = this.images[val];
}) this.images[val - 1].fileAnnotationStatus = '1';
this.itemIndex = val;
this.updateTaskData(item);
}, },
selectTask(id){
this.images = []; //
this.annotationType = 0; loadTaskList() {
getMyNoAnnotatedTask().then(res => {
this.taskList = res.data;
});
},
// ID
selectTask(id) {
this.resetImages();
this.taskId = id; this.taskId = id;
getMyAnnotationFiles(this.annotationType,id).then(response => { this.fetchImages();
this.images =response.data;
})
},
load() {
}, },
//
toggleSelected(type) { toggleSelected(type) {
this.images = []; if (this.annotationType === type) return;
if (this.annotationType !== type) { this.annotationType = type;
this.annotationType = type this.resetImages();
} this.fetchImages();
if (!this.taskId){
return
}
getMyAnnotationFiles(this.annotationType,this.taskId).then(response => {
this.images =response.data;
})
}, },
setItem(item) {
console.log(item) //
setItem(item, index) {
this.annotationResult = JSON.parse(item.annotationResult);
this.itemIndex = index;
this.updateTaskData(item);
},
//
resetImages() {
this.images = [];
//this.annotationType = 0;
},
// ID
fetchImages() {
if (!this.taskId) return;
getMyAnnotationFiles(this.annotationType, this.taskId).then(response => {
this.itemIndex = 0;
this.images = response.data;
this.updateTaskData(this.images[this.itemIndex]);
});
},
//
updateTaskData(item) {
this.task.data.image = `http://192.168.0.14:9090/bonus/${item.fileUrl}`;
this.task.id = item.fileId;
this.annotationResult = JSON.parse(item.annotationResult);
} }
} }
} }
@ -383,7 +384,7 @@ export default {
} }
.bottom-content-center { .bottom-content-center {
width: 60%; width: 80%;
padding: 5px; padding: 5px;
div { div {
@ -453,4 +454,10 @@ export default {
} }
} }
} }
.highlighted {
color: rgb(64, 158, 255); /* 选中项文本颜色 */
font-size: 16px; /* 选中项字体大小 */
}
</style> </style>