提交代码

This commit is contained in:
jiang 2024-12-20 18:12:47 +08:00
parent d9a58838ad
commit 48fba692e7
10 changed files with 232 additions and 178 deletions

View File

@ -64,7 +64,9 @@
style="width: 150px;"
/>
<!-- 显示标注进度 -->
<span>({{ (scope.row.status1Count + scope.row.status2Count) + '/' + scope.row.totalCount }})</span>
<span>({{
(scope.row.status1Count + scope.row.status2Count) + '/' + scope.row.totalCount
}})</span>
</div>
</template>
</el-table-column>

View File

@ -72,7 +72,6 @@ export default {
id: this.id,
data: { image: this.fileUrl },
};
if (this.annotations) {
task.annotations = this.getAnnotations();
}

View File

@ -72,7 +72,9 @@
<ul>
<li v-for="(item, index) in images" :key="item.fileId" class="list-item">
<div @click="setItem(item, index)">
<input type="checkbox" :checked="item.fileAnnotationStatus!=='0'" disabled>
<input type="checkbox" :checked="item.fileAnnotationStatus!=='0' && item.fileAnnotationStatus!=='3'"
disabled
>
<span :class="{'highlighted': itemIndex === index}" style="font-size: 14px; margin-left: 5px;"
>{{ item.fileName }}</span>
</div>
@ -105,7 +107,7 @@ export default {
data() {
return {
auditFailedReason: '',
fileAnnotationStatus: '1',
fileAnnotationStatus: '',
itemIndex: 0,
annotationResult: [],
item: {},
@ -150,9 +152,7 @@ export default {
loadTaskList() {
getMyNoAnnotatedTask().then(res => {
this.taskList = res.data
console.log(this.taskList)
this.taskId = Number(this.$route.params && this.$route.params.taskId)
console.log(this.taskId)
this.selectTask(this.taskId)
})
},
@ -207,7 +207,6 @@ export default {
fetchImages() {
if (!this.taskId) return
getMyAnnotationFiles(this.annotationType, this.taskId).then(response => {
console.log(response)
this.itemIndex = 0
this.images = response.data
this.updateTaskData(this.images[this.itemIndex])

View File

@ -1,8 +1,17 @@
<template>
<div class="label-studio-annotator">
<div class="button-container">
<el-button type="success" plain size="small" :disabled="this.annotations === null" @click="agreement(1)">通过</el-button>
<el-button type="danger" plain size="small" :disabled="this.annotations === null" @click="showDisagreement()">不通过</el-button>
<el-button type="success" plain size="small"
:disabled="this.fileAnnotationStatus === '2' || this.annotations ===null" @click="agreement(1)"
>
通过
</el-button>
<el-button type="danger" plain size="small"
:disabled="this.fileAnnotationStatus === '2' || this.annotations ===null"
@click="showDisagreement()"
>
不通过
</el-button>
</div>
<div id="label-studio" class="annotation-container"></div>
@ -10,9 +19,9 @@
</template>
<script>
import LabelStudio from 'label-studio';
import '@/assets/styles/labelStudio.scss';
import {agreement, manualAnnotate } from '../../../../api/dataCenter/annotationTask';
import LabelStudio from 'label-studio'
import '@/assets/styles/labelStudio.scss'
import { agreement, manualAnnotate } from '../../../../api/dataCenter/annotationTask'
export default {
name: 'LabelStudioAnnotator',
@ -22,6 +31,7 @@ export default {
config: { type: String, required: true },
id: { type: Number, required: true },
itemIndex: { type: Number, required: true },
fileAnnotationStatus: { type: String, required: true },
annotations: {
type: Array,
default: () => [] //
@ -30,28 +40,29 @@ export default {
computed: {
index: {
get() {
return this.itemIndex;
return this.itemIndex
},
set(value) {
this.$parent.updateItemIndex(value); //
this.$parent.updateItemIndex(value, this.annotationStatus) //
}
}
},
data() {
return {
annotationStatus: '',
labelStudio: null,
annotationsList: [] ,
annotationsList: [],
disagreementReason: '' //
};
}
},
watch: {
fileUrl: 'resetLabelStudio' // LabelStudio
},
mounted() {
this.initLabelStudio(); // LabelStudio
this.initLabelStudio() // LabelStudio
},
methods: {
showDisagreement(){
showDisagreement() {
this.$prompt('请输入驳回原因', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -60,24 +71,24 @@ export default {
inputPattern: /^.{0,200}$/, // 200
inputErrorMessage: '描述不能超过200字符',
beforeClose: (action, instance, done) => {
if (action ==='confirm'){
if (action === 'confirm') {
//
if (!instance.inputValue) {
this.$message({
type: 'error',
message: '不通过原因不能为空'
});
return; //
})
return //
}
}
done();
done()
}
})
.then(({ value }) => {
this.sendDisagreement(value);
this.sendDisagreement(value)
})
.catch(() => {
});
})
},
//
sendDisagreement(value) {
@ -86,19 +97,20 @@ export default {
fileId: Number(this.id),
auditFailedReason: value
}).then(response => {
this.$modal.msgSuccess('已提交不通过原因');
this.index++; //
this.$modal.msgSuccess('已提交不通过原因')
this.annotationStatus = '3'
this.index++ //
}).catch(error => {
console.log(error);
});
console.log(error)
})
},
//
getAnnotations() {
return [{
result: this.annotations.map(annotation => ({
type: "rectanglelabels",
from_name: "label",
to_name: "image",
type: 'rectanglelabels',
from_name: 'label',
to_name: 'image',
value: {
rectanglelabels: [annotation.label],
x: annotation.x,
@ -107,41 +119,42 @@ export default {
height: annotation.height
}
}))
}];
}]
},
//
disagreement(){
disagreement() {
},
//
agreement(){
agreement() {
agreement({
taskId : Number(this.taskId),
fileId : Number(this.id)
taskId: Number(this.taskId),
fileId: Number(this.id)
}).then(response => {
this.annotationStatus = '2'
this.$modal.msgSuccess('已同意标注')
this.index++; //
this.index++ //
}).catch(error => {
console.log(error);
});
console.log(error)
})
},
// LabelStudio
initLabelStudio() {
this.cleanupLabelStudio(); //
this.cleanupLabelStudio() //
const task = {
id: this.id,
data: { image: this.fileUrl },
};
data: { image: this.fileUrl }
}
if (this.annotations) {
task.annotations = this.getAnnotations();
task.annotations = this.getAnnotations()
}
this.$nextTick(() => {
const labelStudioElement = document.getElementById('label-studio');
const labelStudioElement = document.getElementById('label-studio')
if (!labelStudioElement) {
console.error('Label Studio element not found');
return;
console.error('Label Studio element not found')
return
}
this.labelStudio = new LabelStudio('label-studio', {
@ -151,50 +164,51 @@ export default {
task,
onLabelStudioLoad: (LS) => {
if (!LS.annotationStore.selectedAnnotation) {
const annotation = LS.annotationStore.addAnnotation({ userGenerate: true });
LS.annotationStore.selectAnnotation(annotation.id);
const annotation = LS.annotationStore.addAnnotation({ userGenerate: true })
LS.annotationStore.selectAnnotation(annotation.id)
}
},
});
});
}
})
})
},
// LabelStudio
resetLabelStudio() {
this.cleanupLabelStudio();
this.initLabelStudio(); //
this.cleanupLabelStudio()
this.initLabelStudio() //
},
// LabelStudio
cleanupLabelStudio() {
if (this.labelStudio && this.labelStudio.destroy) {
this.labelStudio.destroy();
this.labelStudio.destroy()
}
const labelStudioElement = document.getElementById('label-studio');
const labelStudioElement = document.getElementById('label-studio')
if (labelStudioElement) {
labelStudioElement.innerHTML = ''; //
labelStudioElement.innerHTML = '' //
}
this.labelStudio = null; //
this.labelStudio = null //
}
}
};
}
</script>
<style scoped>
.button-container {
margin: 10px;
}
.label-studio-annotator{
.label-studio-annotator {
width: 100%;
height: 100%;
}
.annotation-container{
.annotation-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
>div{
> div {
width: 100%;
height: 100%;
padding: 5px;

View File

@ -85,7 +85,8 @@
</div>
<div class="bottom-content-center">
<div>
<custom-label-studio :annotations="annotationResult" :taskId="taskId || 0" :item-index="itemIndex"
<custom-label-studio :fileAnnotationStatus="fileAnnotationStatus" :annotations="annotationResult"
:taskId="taskId || 0" :item-index="itemIndex"
:config="labelConfig" :id="task.id" :file-url="task.data.image"
@update-itemIndex="updateItemIndex"
></custom-label-studio>
@ -107,6 +108,7 @@ export default {
return {
itemIndex: 0,
annotationResult: [],
fileAnnotationStatus: '',
item: {},
taskList: [],
task: {
@ -147,9 +149,9 @@ export default {
},
methods: {
//
updateItemIndex(val) {
updateItemIndex(val, fileAnnotationStatus) {
const item = this.images[val]
this.images[val - 1].fileAnnotationStatus = '2'
this.images[val - 1].fileAnnotationStatus = fileAnnotationStatus
this.itemIndex = val
this.updateTaskData(item)
},
@ -158,7 +160,7 @@ export default {
loadTaskList() {
getMyNoAnnotatedTask().then(res => {
this.taskList = res.data
this.taskId = Number(this.$route.params && this.$route.params.taskId);
this.taskId = Number(this.$route.params && this.$route.params.taskId)
this.selectTask(this.taskId)
})
},
@ -201,6 +203,7 @@ export default {
setItem(item, index) {
this.annotationResult = JSON.parse(item.annotationResult)
this.itemIndex = index
this.updateTaskData(item)
},
@ -229,6 +232,7 @@ export default {
this.task.data.image = `${url.minioUrl}${item.fileUrl}`
this.task.id = item.fileId
this.annotationResult = JSON.parse(item.annotationResult)
this.fileAnnotationStatus = item.fileAnnotationStatus
}
}

View File

@ -136,6 +136,9 @@ export default {
};
},
methods: {
uploadFileId(res){
console.log("addDataSetDialog.vue",res)
},
//
submitForm() {
this.$refs["form"].validate(valid => {

View File

@ -1,34 +1,40 @@
<template>
<div>
<el-dialog title="导入" :visible.sync="isOpen" width="700px" append-to-body :modal="false" @close="cancel" :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="importRules" label-width="100px">
<el-form-item label="数据来源" prop="dataSource" >
<el-radio-group v-model="form.dataSource">
<el-dialog title="导入" :visible.sync="isOpen" width="700px" append-to-body :modal="false" @close="cancel"
:close-on-click-modal="false"
>
<el-form ref="form" :model="form" :rules="importRules" label-width="100px">
<el-form-item label="数据来源" prop="dataSource">
<el-radio-group v-model="form.dataSource" @change="handleDataSourceChange">
<el-radio :label="0" border>系统选择</el-radio>
<el-radio :label="1" border>本地上传</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="导入路径" prop="inputPath">
<el-input placeholder="请选择导入路径" readonly :disabled="true" v-model="form.inputPath" class="input-with-select">
<el-button slot="append" icon="el-icon-folder-opened" @click="selectFolder(0)"></el-button>
<el-input placeholder="请选择导入路径" readonly :disabled="true" v-model="form.inputPath"
class="input-with-select"
>
<el-button slot="append" icon="el-icon-folder-opened" @click="selectFolder(0)"></el-button>
</el-input>
</el-form-item>
<div v-show="form.dataSource ===1">
<el-form-item label="上传文件">
<uploadFiles :disabled="form.inputId===0" :key="new Date().getTime()" :parent-id="form.inputId.toString()" :fileUrl="form.inputPath || ''"/>
<uploadFiles @file-uploaded="uploadFileId" :disabled="form.inputId===0" :key="new Date().getTime()"
:parent-id="form.inputId.toString()" :fileUrl="form.inputPath || ''"
/>
</el-form-item>
<!-- <el-form-item label="数据标注状态" prop="isAnnotated">
<el-radio-group v-model="form.isAnnotated">
<el-radio :label="0" border>未标注</el-radio>
<el-radio :label="1" border>已标注</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.isAnnotated === 1">
<div style="display: flex;flex-direction: column;">
<span>文件存放方式满足YOLO格式</span>
<img style="width: 50%" :src="ObjectDetection_YOLO" alt="Weibo">
</div>
</el-form-item>-->
<!-- <el-form-item label="数据标注状态" prop="isAnnotated">
<el-radio-group v-model="form.isAnnotated">
<el-radio :label="0" border>未标注</el-radio>
<el-radio :label="1" border>已标注</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.isAnnotated === 1">
<div style="display: flex;flex-direction: column;">
<span>文件存放方式满足YOLO格式</span>
<img style="width: 50%" :src="ObjectDetection_YOLO" alt="Weibo">
</div>
</el-form-item>-->
</div>
</el-form>
<template #footer>
@ -36,7 +42,9 @@
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
<selectFolderDialog :open="folderOpen" :folder-type="folderType" @dialog-cancel="handleCancel" @update-data ="updateData" />
<selectFolderDialog :open="folderOpen" :folder-type="folderType" @dialog-cancel="handleCancel"
@update-data="updateData"
/>
</div>
</template>
@ -48,20 +56,20 @@ import uploadFiles from '../../library/child/uploadFiles.vue'
import { importData } from '@/api/dataCenter/dataSet'
export default {
components: { uploadFiles, selectFolderDialog},
components: { uploadFiles, selectFolderDialog },
props: {
open: {
type: Boolean,
default: false,
default: false
},
dataType:{
dataType: {
type: String,
default: '',
required: true
},
getList: {
type: Function,
required: true,
required: true
},
datasetId: {
type: String,
@ -72,44 +80,59 @@ export default {
computed: {
isOpen: {
get() {
return this.open;
return this.open
},
set(value) {
this.$emit('update:open', value);
},
},
this.$emit('update:open', value)
}
}
},
watch: {
isOpen(newVal, oldVal) {
if (newVal) {
this.fileIds = []
}
}
},
data() {
return {
type: this.dataType, //
ObjectDetection_YOLO:ObjectDetection_YOLO,
ObjectDetection_YOLO: ObjectDetection_YOLO,
folderOpen: false,
folderType:0,
folderType: 0,
fileIds: [],
form: {
dataSource:0,
inputId:0,
inputPath:'',
dataSource: 0,
inputId: 0,
inputPath: ''
}, //
importRules:{
dataSource:[
{ required: true, message: '数据来源不能为空', trigger: 'blur' },
importRules: {
dataSource: [
{ required: true, message: '数据来源不能为空', trigger: 'blur' }
],
inputPath:[
{ required: true, message: '导入路径不能为空', trigger: 'blur' },
inputPath: [
{ required: true, message: '导入路径不能为空', trigger: 'blur' }
]
}
};
}
},
methods: {
handleDataSourceChange() {
this.fileIds = []
},
uploadFileId(res) {
this.fileIds.push(res.fileId)
},
//
submitForm() {
this.$refs["form"].validate(valid => {
this.$refs['form'].validate(valid => {
if (valid) {
this.form.datasetId = this.datasetId
this.form.dataType = this.dataType
this.form.fileIds = this.fileIds
importData(this.form).then(response => {
this.$modal.msgSuccess("导入成功");
this.cancel();
this.$modal.msgSuccess('导入成功')
this.cancel()
})
}
})
@ -117,37 +140,37 @@ export default {
},
//
cancel() {
this.getList();
this.isOpen = false;
this.reset();
this.$emit('dialog-cancel'); //
this.getList()
this.isOpen = false
this.reset()
this.$emit('dialog-cancel') //
},
//
reset() {
this.form = {
dataSource:0,
inputId:0,
outputId:0
}; //
this.$refs.form.resetFields(); // Element UI
dataSource: 0,
inputId: 0,
outputId: 0
} //
this.$refs.form.resetFields() // Element UI
},
//
selectFolder(folderType){
this.folderType = folderType;
this.folderOpen = true;
selectFolder(folderType) {
this.folderType = folderType
this.folderOpen = true
},
handleCancel(){
this.folderOpen = false;
handleCancel() {
this.folderOpen = false
},
updateData(data){
updateData(data) {
if (data.folderType === 0) {
this.form.inputPath = data.folder || ''; // inputPath
this.form.inputId = data.fileId || 0;
this.form.inputPath = data.folder || '' // inputPath
this.form.inputId = data.fileId || 0
}
}
},
};
}
}
</script>
<style scoped lang="scss">
@ -156,7 +179,7 @@ export default {
z-index: 2000 !important; /* Set a lower z-index for dialog */
}
input[aria-hidden=true]{
input[aria-hidden=true] {
display: none !important;
}
</style>

View File

@ -32,7 +32,7 @@
</template>
<script>
import { getDetails, addDetails, updateDetails } from '@/api/dataCenter/evaluateDetails';
import { getDetails, addDetails, updateDetails } from '@/api/dataCenter/evaluateDetails'
export default {
props: {
@ -45,23 +45,16 @@ export default {
computed: {
isOpen: {
get() {
return this.open;
return this.open
},
set(value) {
this.$emit('dialog-cancel');
this.$emit('dialog-cancel')
}
}
},
data() {
return {
form: {
justSqmple: null,
loseSample: null,
tpNum: null,
tnNum: null,
fpNum: null,
fnNum: null
},
form: {},
rules: {
justSqmple: [
{ required: true, message: '请输入正样本数', trigger: 'blur' },
@ -73,59 +66,68 @@ export default {
],
tpNum: [
{ required: true, message: '请输入TP数量', trigger: 'blur' },
{ pattern: /^[1-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ pattern: /^[0-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ validator: this.validatePositiveSamples, trigger: 'blur' }
],
fnNum: [
{ required: true, message: '请输入FN数量', trigger: 'blur' },
{ pattern: /^[1-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ pattern: /^[0-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ validator: this.validatePositiveSamples, trigger: 'blur' }
],
tnNum: [
{ required: true, message: '请输入TN数量', trigger: 'blur' },
{ pattern: /^[1-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ pattern: /^[0-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ validator: this.validateNegativeSamples, trigger: 'blur' }
],
fpNum: [
{ required: true, message: '请输入FP数量', trigger: 'blur' },
{ pattern: /^[1-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ pattern: /^[0-9]\d*$/, message: '只能输入正整数', trigger: 'blur' },
{ validator: this.validateNegativeSamples, trigger: 'blur' }
]
}
};
}
},
watch: {
isOpen(newVal) {
if (newVal && this.evaluateDetailId) {
getDetails(this.evaluateDetailId).then((res) => {
this.form = res.data;
});
this.form = res.data
})
}
}
},
methods: {
validatePositiveSamples(rule, value, callback) {
const { tpNum, fnNum, justSqmple } = this.form;
if (tpNum != null && fnNum != null && justSqmple != null) {
if (tpNum + fnNum !== parseInt(justSqmple, 10)) {
callback(new Error('TP和FN之和必须等于正样本数'));
} else {
callback();
}
console.log(this.form)
const { tpNum, fnNum, justSqmple } = this.form
//
const tp = Number(tpNum)
const fn = Number(fnNum)
const just = Number(justSqmple)
// TP + FN
if (tp + fn !== just) {
callback(new Error('TP和FN之和必须等于正样本数'))
} else {
callback();
callback()
}
},
validateNegativeSamples(rule, value, callback) {
const { tnNum, fpNum, loseSample } = this.form;
if (tnNum != null && fpNum != null && loseSample != null) {
if (tnNum + fpNum !== parseInt(loseSample, 10)) {
callback(new Error('FP和TN之和必须等于负样本数'));
} else {
callback();
}
console.log(this.form)
const { tnNum, fpNum, loseSample } = this.form
//
const tn = Number(tnNum)
const fp = Number(fpNum)
const lose = Number(loseSample)
// TN + FP
if (tn + fp !== lose) {
callback(new Error('FP和TN之和必须等于负样本数'))
} else {
callback();
callback()
}
},
/** 提交按钮 */
@ -134,30 +136,30 @@ export default {
if (valid) {
if (this.form.id != null) {
updateDetails(this.form).then((res) => {
this.$modal.msgSuccess('修改成功');
this.isOpen = false;
this.getList();
});
this.$modal.msgSuccess('修改成功')
this.isOpen = false
this.getList()
})
} else {
this.form.evaluateId = this.evaluateId;
this.form.evaluateId = this.evaluateId
addDetails(this.form).then((response) => {
this.$modal.msgSuccess('新增成功');
this.isOpen = false;
this.getList();
});
this.$modal.msgSuccess('新增成功')
this.isOpen = false
this.getList()
})
}
}
});
})
},
cancel() {
this.isOpen = false;
this.resetForm();
this.isOpen = false
this.resetForm()
},
resetForm() {
this.form = {};
this.form = {}
}
}
};
}
</script>

View File

@ -155,7 +155,7 @@ export default {
formData.append('fileUrl', this.fileUrl)
uploadFiles(formData)
.then(() => {
.then(res => {
currentChunk++
const progress = Math.floor((currentChunk / totalChunks) * 100)
this.$set(this.uploadsNum, file.name, progress)
@ -163,11 +163,17 @@ export default {
if (currentChunk < totalChunks) {
uploadNextChunk()
} else {
console.log(res)
console.log(this.$parent)
// $emit
this.$emit('file-uploaded', res.data);
console.log(res)
this.startNextUpload()
this.checkAllUploadsComplete()
}
})
.catch(() => {
.catch(reason => {
console.log(reason)
this.$set(this.uploadFailed, file.name, true)
this.startNextUpload()
})
@ -187,7 +193,6 @@ export default {
//
startNextUpload() {
if (!this.drawer) return //
Object.keys(this.uploadsNum).forEach((fileName) => {
if (this.uploadsNum[fileName] === 100) {
delete this.uploadsNum[fileName]

View File

@ -339,6 +339,9 @@ export default {
}
},
methods: {
uploadFileId(res){
console.log("file.vue",res)
},
/** 转换菜单数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {