增加在线标注

This commit is contained in:
weiweiw 2024-11-26 12:44:17 +08:00
parent 4ddb442c71
commit 7b6429a58c
7 changed files with 208 additions and 71 deletions

View File

@ -15,7 +15,7 @@ import java.util.Map;
public class TaskParam { public class TaskParam {
private Data data; private Data data;
private int id; private Long id;
private Map<String, String> meta; private Map<String, String> meta;
@JsonProperty("created_at") @JsonProperty("created_at")
private String createdAt; private String createdAt;
@ -40,7 +40,7 @@ public class TaskParam {
private int unresolvedCommentCount; private int unresolvedCommentCount;
@JsonProperty("last_comment_updated_at") @JsonProperty("last_comment_updated_at")
private String lastCommentUpdatedAt; private String lastCommentUpdatedAt;
private int project; private Long project;
@JsonProperty("updated_by") @JsonProperty("updated_by")
private List<UpdatedBy> updatedBy; private List<UpdatedBy> updatedBy;
@JsonProperty("file_upload") @JsonProperty("file_upload")

View File

@ -48,7 +48,7 @@ public interface AnnotationTaskMapper
*/ */
List<AnnotationTaskEntity> selectAnnotationTaskList(AnnotationTaskEntity annotationTask); List<AnnotationTaskEntity> selectAnnotationTaskList(AnnotationTaskEntity annotationTask);
int insertAnnotTaskannotator(List<AnnotationTaskAnnotatorEntity> annotationTaskAnnotatorEntities); int insertAnnotTaskannotator(AnnotationTaskAnnotatorEntity annotationTaskAnnotatorEntities);
int deleteTaskById(Long taskId); int deleteTaskById(Long taskId);

View File

@ -2,6 +2,7 @@ package com.bonus.ai.service.Impl.dataset;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.bonus.ai.client.*; import com.bonus.ai.client.*;
import com.bonus.ai.config.MinioConfig;
import com.bonus.ai.config.OnlineAnnotateConfig; import com.bonus.ai.config.OnlineAnnotateConfig;
import com.bonus.ai.domain.DataSetBasicFileEntity; import com.bonus.ai.domain.DataSetBasicFileEntity;
import com.bonus.ai.domain.dataset.*; import com.bonus.ai.domain.dataset.*;
@ -17,6 +18,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -42,40 +44,25 @@ public class AnnotationTaskServiceImpl implements AnnotationTaskService {
@Resource @Resource
private DatasetFileMapper datasetFileMapper; private DatasetFileMapper datasetFileMapper;
@Autowired @Resource
private AnnotationTaskEntity annotationTaskEntity; private MinioConfig minioConfig;
@Autowired
private AnnotationTaskAnnotatorEntity annotationTaskAnnotatorEntity;
private final ThreadPoolTaskExecutor executorService = SpringUtils.getBean(ThreadPoolTaskExecutor.class); private final ThreadPoolTaskExecutor executorService = SpringUtils.getBean(ThreadPoolTaskExecutor.class);
public void callWithOkHttp() throws Exception {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://127.0.0.1:8080/api/projects")
.addHeader("Authorization", "Token bb0859020874c95809212f7b9616d15bbdbb2422")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
}
/** /**
* 创建标注任务 * 创建标注任务
*/ */
@Override @Override
public int createTask(AnnotationTaskEntity task){ public int createTask(AnnotationTaskEntity task){
int status =0; int status =0;
//调用label studio 接口获取其project id //1.调用label studio 接口创建project将projectid 关联到标注任务里
ProjectInputParam lSProject= new ProjectInputParam(); ProjectInputParam lSProject= new ProjectInputParam();
lSProject.setTitle(task.getTaskName()); lSProject.setTitle(task.getTaskName());
lSProject.setDescription(task.getTaskDesc()); lSProject.setDescription(task.getTaskDesc());
//需要将标签转换为json格式 //需要将标签转换为json格式
String labelConfig = OnlineAnnotateUtil.rectangleImageLabels(task.getLabels()); String labelConfig = OnlineAnnotateUtil.rectangleImageLabels(task.getLabels());
lSProject.setLabelConfig(labelConfig); lSProject.setLabelConfig(labelConfig);
String project = "";
Long projectId = 0L; Long projectId = 0L;
try { try {
ProjectParam projectParams = onlineAnnotationService.createProject(lSProject); ProjectParam projectParams = onlineAnnotationService.createProject(lSProject);
@ -85,32 +72,83 @@ public class AnnotationTaskServiceImpl implements AnnotationTaskService {
} }
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();
return 0;
} }
//需要生成32位uuid //2.插入标注任务表
String uuid = UUID.randomUUID().toString().replace("-", ""); String uuid = UUID.randomUUID().toString().replace("-", "");
task.setTaskUuid(uuid); task.setTaskUuid(uuid);
status= annotationTaskMapper.insertAnnotationTask(task); status= annotationTaskMapper.insertAnnotationTask(task);
AnnotationTaskEntity tempTask = new AnnotationTaskEntity();
tempTask.setTaskUuid(uuid);
List<AnnotationTaskEntity> tempTasks = annotationTaskMapper.selectAnnotationTaskList(task);
Long annotationTaskid;
if (!tempTasks.isEmpty()){
annotationTaskid = tempTasks.get(0).getTaskId();
} else {
annotationTaskid = 0L;
}
//3. 根据标注任务创建标注人等信息对任务里的文件进行分配
//通过数据集id 从数据集和文件关联关系表获取文件信息 //通过数据集id 从数据集和文件关联关系表获取文件信息
List<DataSetBasicFileEntity> datafile = datasetFileMapper.selectFilesByDatasetId(task.getDatasetId()); List<DataSetBasicFileEntity> datafile = datasetFileMapper.selectFilesByDatasetId(task.getDatasetId());
//根据标注任务file list 和标注人信息自动分配 //根据标注任务file list 和标注人信息自动分配
List<AnnotationTaskAnnotatorEntity> annotationTaskAnnotatorEntities = AverageUtil.distributeFiles(datafile, task); List<AnnotationTaskAnnotatorEntity> annotationTaskAnnotatorEntities = AverageUtil.distributeFiles(datafile, task);
// for (AnnotationTaskAnnotatorEntity entity : annotationTaskAnnotatorEntities) {
// //TODO 每个文件都需要创建label studio的 task id 与之唯一对应 for (AnnotationTaskAnnotatorEntity entity : annotationTaskAnnotatorEntities) {
// executorService.execute(()->{ //每个文件都需要创建label studio的 task id 与之唯一对应
// TaskParam taskParam = new TaskParam(); entity.setTaskId(annotationTaskid);
// taskParam.setProject(projectId); }
// try { // 使用 CountDownLatch 来确保所有任务完成后才执行下一步操作
// onlineAnnotationService.createTask(taskParam); CountDownLatch latch = new CountDownLatch(annotationTaskAnnotatorEntities.size());
// }catch(Exception e){
// e.printStackTrace(); //4. 文件分配后需要调用label studio 接口创建task将taskid与 文件和标注人表进行关联因为文件较多启用异步调用
// } for (AnnotationTaskAnnotatorEntity entity : annotationTaskAnnotatorEntities) {
// }); //每个文件都需要创建label studio的 task id 与之唯一对应
//entity.setLabelStudioTaskId(0); entity.setTaskId(annotationTaskid);
// } Long finalProjectId = projectId;
// 获取project id以后调用AnnotationTaskMapper 和AnnotationTaskAnnotatorMapper 插入数据集 executorService.execute(()->{
TaskParam taskParam = new TaskParam();
TaskParam.Data data = new TaskParam.Data();
String imageUrl = minioConfig.getEndpoint() + "/" + minioConfig.getBucketName() + "/"+ entity.getFileUrl();
data.setImage(imageUrl);
data.setText("Hello, world!");
taskParam.setData(data);
taskParam.setProject(finalProjectId);
try {
TaskParam lStask = onlineAnnotationService.createTask(taskParam);
synchronized (entity) {
entity.setLabelStudioTaskId(lStask.getId());
}
}catch(Exception e){
e.printStackTrace();
}
finally {
latch.countDown(); // 每个任务完成后减少计数
}
});
}
// 等待所有线程完成
try {
latch.await(); // 主线程会等待所有子线程完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
// 5. 将文件和标注人等信息 插入文件和标注人关联表
if(status == 1){ if(status == 1){
status = annotationTaskMapper.insertAnnotTaskannotator(annotationTaskAnnotatorEntities); try {
for(AnnotationTaskAnnotatorEntity entity:annotationTaskAnnotatorEntities){
status = annotationTaskMapper.insertAnnotTaskannotator(entity);
}
}catch (Exception e){
log.error("Error updating task: ", e);
throw e; // 抛出异常以触发回滚
}
} }
return status; return status;
} }
@ -135,7 +173,12 @@ public class AnnotationTaskServiceImpl implements AnnotationTaskService {
@Override @Override
public int updateTask(AnnotationTaskEntity task){ public int updateTask(AnnotationTaskEntity task){
//将信息也同步更新到label studio studio 对应的project int result = annotationTaskMapper.updateAnnotationTaskById(task);
if (result != 1){
return 0;
}
//防止labels信息发生变化信息也同步更新到label studio studio 对应的project
AnnotationTaskEntity newTask = new AnnotationTaskEntity(); AnnotationTaskEntity newTask = new AnnotationTaskEntity();
newTask.setTaskId(task.getTaskId()); newTask.setTaskId(task.getTaskId());
//处理与label studio 相关的更新 //处理与label studio 相关的更新

View File

@ -4,6 +4,7 @@ import com.bonus.ai.domain.DataSetBasicFileEntity;
import com.bonus.ai.domain.dataset.AnnotationTaskAnnotatorEntity; import com.bonus.ai.domain.dataset.AnnotationTaskAnnotatorEntity;
import com.bonus.ai.domain.dataset.AnnotationTaskEntity; import com.bonus.ai.domain.dataset.AnnotationTaskEntity;
import com.bonus.ai.domain.dataset.DatasetFile; import com.bonus.ai.domain.dataset.DatasetFile;
import com.bonus.common.security.utils.SecurityUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -22,6 +23,33 @@ public class AverageUtil {
List<Long> annotators = taskEntity.getAnnotators(); // 获取标注者列表 List<Long> annotators = taskEntity.getAnnotators(); // 获取标注者列表
List<Long> reviewers = taskEntity.getReviewers(); // 获取审核者列表 List<Long> reviewers = taskEntity.getReviewers(); // 获取审核者列表
//标注者与审核者都为空时默认为当前用户
if(("1").equals(taskEntity.getIsStartTeam())){
if (annotators == null || annotators.size() == 0 ) {
annotators = new ArrayList<>();
Long userId = SecurityUtils.getUserId();
annotators.add(userId);
}
if (reviewers == null || reviewers.size() == 0) {
reviewers = new ArrayList<>();
Long userId = SecurityUtils.getUserId();
reviewers.add(userId);
}
}else{
if (annotators == null || annotators.size() == 0) {
annotators = new ArrayList<>();
Long userId = SecurityUtils.getUserId();
annotators.add(userId);
}
if (reviewers == null || reviewers.size() == 0) {
reviewers = new ArrayList<>();
Long userId = SecurityUtils.getUserId();
reviewers.add(userId);
}
}
//这里应该是根据标注者列表和审核人列表里的人 与文件相关进行平均分 //这里应该是根据标注者列表和审核人列表里的人 与文件相关进行平均分
// 平分文件给标注者 // 平分文件给标注者
for (int i = 0; i < files.size(); i++) { for (int i = 0; i < files.size(); i++) {
@ -39,10 +67,6 @@ public class AverageUtil {
for (int i = 0; i < annotatorEntities.size(); i++) { for (int i = 0; i < annotatorEntities.size(); i++) {
Long reviewerId = reviewers.get(i % reviewers.size()); // 轮流分配给审核者 Long reviewerId = reviewers.get(i % reviewers.size()); // 轮流分配给审核者
annotatorEntities.get(i).setReviewerId(reviewerId); annotatorEntities.get(i).setReviewerId(reviewerId);
// AnnotationTaskAnnotatorEntity entity = new AnnotationTaskAnnotatorEntity();
// entity.setAnnotatorId(reviewerId); // 设置审核者 ID
// entity.setFileId(files.get(i).getFileId()); // 假设 DatasetFile getId() 方法
// annotatorEntities.add(entity);
} }
return annotatorEntities; return annotatorEntities;

View File

@ -77,7 +77,7 @@
<!-- </select>--> <!-- </select>-->
<!-- 插入标注任务 --> <!-- 插入标注任务 -->
<insert id="insertAnnotationTask" parameterType="com.bonus.ai.domain.dataset.AnnotationTaskEntity"> <insert id="insertAnnotationTask" parameterType="com.bonus.ai.domain.dataset.AnnotationTaskEntity" useGeneratedKeys="true" keyProperty="taskId">
INSERT INTO ai_annotation_task INSERT INTO ai_annotation_task
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="datasetId != null">dataset_id,</if> <if test="datasetId != null">dataset_id,</if>
@ -114,73 +114,141 @@
<if test="updateTime != null">#{updateTime},</if> <if test="updateTime != null">#{updateTime},</if>
</trim> </trim>
</insert> </insert>
<insert id="insertAnnotTaskannotator"> <insert id="insertAnnotTaskannotator" parameterType="com.bonus.ai.domain.dataset.AnnotationTaskAnnotatorEntity">
INSERT INTO ai_annotation_task_annotator_map INSERT INTO ai_annotation_task_annotator_map
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
task_id <if test="taskId != null">
task_id ,
</if>
<if test="fileId != null"> <if test="fileId != null">
, file_id file_id ,
</if> </if>
<if test="fileUrl != null"> <if test="fileUrl != null">
, file_url file_url ,
</if>
<if test="fileUrl != null">
, file_url
</if> </if>
<if test="labelStudioTaskId != null"> <if test="labelStudioTaskId != null">
, label_studio_task_id label_studio_task_id,
</if> </if>
<if test="annotatorId != null"> <if test="annotatorId != null">
, annotator_id annotator_id ,
</if> </if>
<if test="reviewerId != null"> <if test="reviewerId != null">
, reviewer_id reviewer_id ,
</if> </if>
<if test="description != null and description != ''"> <if test="description != null and description != ''">
, description description ,
</if> </if>
<if test="annotationStatus != null"> <if test="annotationStatus != null">
, annotation_status annotation_status ,
</if> </if>
<if test="annotationResult != null"> <if test="annotationResult != null">
, annotation_result annotation_result ,
</if> </if>
<if test="annotationResource != null"> <if test="annotationResource != null">
, annotation_resource annotation_resource ,
</if> </if>
</trim> </trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=","> VALUES
#{taskId} <!-- <foreach collection="list" index="index" item="entity" separator=",">-->
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="taskId != null">
#{taskId},
</if>
<if test="fileId != null"> <if test="fileId != null">
, #{fileId} #{fileId},
</if> </if>
<if test="fileUrl != null"> <if test="fileUrl != null">
, #{fileUrl} #{fileUrl},
</if> </if>
<if test="labelStudioTaskId != null"> <if test="labelStudioTaskId != null">
, #{labelStudioTaskId} #{labelStudioTaskId},
</if> </if>
<if test="annotatorId != null"> <if test="annotatorId != null">
, #{annotatorId} #{annotatorId},
</if> </if>
<if test="reviewerId != null"> <if test="reviewerId != null">
, #{reviewerId} #{reviewerId},
</if> </if>
<if test="description != null and description != ''"> <if test="description != null and entity.description != ''">
, #{description} #{description} ,
</if> </if>
<if test="annotationStatus != null"> <if test="annotationStatus != null">
, #{annotationStatus} #{annotationStatus},
</if> </if>
<if test="annotationResult != null"> <if test="annotationResult != null">
, #{annotationResult} #{annotationResult},
</if> </if>
<if test="annotationResource != null"> <if test="annotationResource != null">
, #{annotationResource} #{annotationResource},
</if> </if>
</trim> </trim>
<!-- </foreach>-->
</insert> </insert>
<!-- <insert id="insertAnnotTaskannotator" parameterType="java.util.List">-->
<!-- INSERT INTO ai_annotation_task_annotator_map-->
<!-- <trim prefix="(" suffix=")" suffixOverrides=",">-->
<!-- task_id-->
<!-- <if test="fileId != null">-->
<!-- , file_id-->
<!-- </if>-->
<!-- <if test="fileUrl != null">-->
<!-- , file_url-->
<!-- </if>-->
<!-- <if test="labelStudioTaskId != null">-->
<!-- , label_studio_task_id-->
<!-- </if>-->
<!-- <if test="annotatorId != null">-->
<!-- , annotator_id-->
<!-- </if>-->
<!-- <if test="reviewerId != null">-->
<!-- , reviewer_id-->
<!-- </if>-->
<!-- <if test="description != null and description != ''">-->
<!-- , description-->
<!-- </if>-->
<!-- <if test="annotationStatus != null">-->
<!-- , annotation_status-->
<!-- </if>-->
<!-- <if test="annotationResult != null">-->
<!-- , annotation_result-->
<!-- </if>-->
<!-- <if test="annotationResource != null">-->
<!-- , annotation_resource-->
<!-- </if>-->
<!-- </trim>-->
<!-- <trim prefix="VALUES (" suffix=")" suffixOverrides=",">-->
<!-- #{taskId}-->
<!-- <if test="fileId != null">-->
<!-- , #{fileId}-->
<!-- </if>-->
<!-- <if test="fileUrl != null">-->
<!-- , #{fileUrl}-->
<!-- </if>-->
<!-- <if test="labelStudioTaskId != null">-->
<!-- , #{labelStudioTaskId}-->
<!-- </if>-->
<!-- <if test="annotatorId != null">-->
<!-- , #{annotatorId}-->
<!-- </if>-->
<!-- <if test="reviewerId != null">-->
<!-- , #{reviewerId}-->
<!-- </if>-->
<!-- <if test="description != null and description != ''">-->
<!-- , #{description}-->
<!-- </if>-->
<!-- <if test="annotationStatus != null">-->
<!-- , #{annotationStatus}-->
<!-- </if>-->
<!-- <if test="annotationResult != null">-->
<!-- , #{annotationResult}-->
<!-- </if>-->
<!-- <if test="annotationResource != null">-->
<!-- , #{annotationResource}-->
<!-- </if>-->
<!-- </trim>-->
<!-- </insert>-->
<!-- 根据任务ID更新标注任务 --> <!-- 根据任务ID更新标注任务 -->
<update id="updateAnnotationTaskById" parameterType="com.bonus.ai.domain.dataset.AnnotationTaskEntity"> <update id="updateAnnotationTaskById" parameterType="com.bonus.ai.domain.dataset.AnnotationTaskEntity">

View File

@ -46,8 +46,8 @@
<select id="selectFilesByDatasetId" parameterType="Long" resultType="com.bonus.ai.domain.DataSetBasicFileEntity"> <select id="selectFilesByDatasetId" parameterType="Long" resultType="com.bonus.ai.domain.DataSetBasicFileEntity">
SELECT bf.file_id AS fileId, bf.file_url AS fileUrl SELECT bf.file_id AS fileId, bf.file_url AS fileUrl
FROM ai_dataset_file_map df FROM ai_dataset_file_map df
WHERE df.dataset_id = #{datasetId}
left join ai_basic_file bf on bf.file_id = df.file_id left join ai_basic_file bf on bf.file_id = df.file_id
WHERE df.dataset_id = #{datasetId}
</select> </select>
<!-- 更新是否已标注状态 --> <!-- 更新是否已标注状态 -->

View File

@ -106,6 +106,8 @@ create table ai_dataset
data_source char(1) comment '数据来源(0:local,1:obs)', data_source char(1) comment '数据来源(0:local,1:obs)',
input_path varchar(200) comment '输入路径', input_path varchar(200) comment '输入路径',
output_path varchar(200) comment '输入路径', output_path varchar(200) comment '输入路径',
input_id bigint(20) comment '输入文件夹id',
output_id bigint(20) comment '输出文件夹id'
annotation_status char(1) comment '标注状态(0no, 1 yes)', annotation_status char(1) comment '标注状态(0no, 1 yes)',
is_public char(1) default '0' comment '0 no, 1 yes', is_public char(1) default '0' comment '0 no, 1 yes',
del_flag char(1) default '0' comment '是否删除(0代表存在,1代表删除)', del_flag char(1) default '0' comment '是否删除(0代表存在,1代表删除)',