fix(label): label result
This commit is contained in:
@@ -14,7 +14,12 @@ import { processVideo } from "@/app/component/label/video2image/processVideo"
|
||||
import { Box, Flex, LoadingOverlay, Stack } from "@mantine/core"
|
||||
import { showNotification } from "@mantine/notifications"
|
||||
import { getLabelResult, getServerImage } from "./api/label"
|
||||
import { Comment, WorkLoad } from "./api/label/typing"
|
||||
import {
|
||||
Comment,
|
||||
ImageObjects,
|
||||
LabelResult,
|
||||
WorkLoad,
|
||||
} from "./api/label/typing"
|
||||
import { getProjectDetail } from "./api/project"
|
||||
import { Project } from "./api/project/typing"
|
||||
import { getTaskList, getTaskWorkFlow } from "./api/task"
|
||||
@@ -91,6 +96,29 @@ const normalizeNamePrefix = (name: string) => {
|
||||
return value || "video"
|
||||
}
|
||||
|
||||
interface ResultImageEntry {
|
||||
frameName: string
|
||||
imageObjects: ImageObjects
|
||||
}
|
||||
|
||||
const expandLabelResultImages = (item: LabelResult): ResultImageEntry[] => {
|
||||
if (item.image_objects) {
|
||||
return [
|
||||
{
|
||||
frameName: item.data_name,
|
||||
imageObjects: item.image_objects,
|
||||
},
|
||||
]
|
||||
}
|
||||
if (!item.video_objects) return []
|
||||
return Object.entries(item.video_objects).map(([frameName, imageObjects]) => {
|
||||
return {
|
||||
frameName,
|
||||
imageObjects,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface LabelProps {
|
||||
headerHeight: number
|
||||
leftWidth: number
|
||||
@@ -237,11 +265,10 @@ const LabelPage = ({
|
||||
|
||||
const normalizeVideoTaskData = useCallback(
|
||||
async (detail: Task.DataProps) => {
|
||||
try {
|
||||
const sourceNames = detail.data_name
|
||||
.map((item) => item?.name?.[0] || "")
|
||||
.filter((name) => !!name)
|
||||
if (!sourceNames.length || !projectId) return detail
|
||||
if (!sourceNames.length || !projectId || !taskId) return detail
|
||||
|
||||
const taskDataType = Number(
|
||||
(detail as { [key: string]: any })?.data_type ??
|
||||
@@ -250,10 +277,12 @@ const LabelPage = ({
|
||||
)
|
||||
const isVideoTask =
|
||||
taskDataType === 2 ||
|
||||
taskDataType === 7 ||
|
||||
sourceNames.some((name) => videoExtPattern.test(name))
|
||||
if (!isVideoTask) return detail
|
||||
|
||||
const imagesMap = new Map(useImagesStore.getState().images)
|
||||
const frameMapUpdates = new Map<string, string[]>()
|
||||
let imageMapUpdated = false
|
||||
const normalizedDataName: Task.DataProps["data_name"] = []
|
||||
|
||||
@@ -270,24 +299,37 @@ const LabelPage = ({
|
||||
})
|
||||
|
||||
if (!hasAllCachedFrames) {
|
||||
let frameData: Awaited<ReturnType<typeof processVideo>> = []
|
||||
try {
|
||||
const base64Video = await getServerImage({
|
||||
data_names: [videoName],
|
||||
data_type: 2,
|
||||
project_id: projectId,
|
||||
})
|
||||
const frameData = await processVideo({
|
||||
frameData = await processVideo({
|
||||
base64Data: String(base64Video || ""),
|
||||
fileName: videoName,
|
||||
namePrefix: normalizeNamePrefix(
|
||||
`${projectId}_${taskId}_${videoName.replace(/\.[^.]+$/, "")}`
|
||||
),
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "未知错误"
|
||||
throw new Error(`视频帧生成失败:${videoName}(${message})`)
|
||||
}
|
||||
frameNames = frameData.map((frame) => frame.name)
|
||||
if (!frameNames.length) {
|
||||
throw new Error(`视频帧生成失败:${videoName}`)
|
||||
}
|
||||
frameData.forEach((frame) => {
|
||||
imagesMap.set(frame.name, frame.dataUrl)
|
||||
})
|
||||
imageMapUpdated = true
|
||||
useVideoFrameStore.getState().setVideoFrames(cacheKey, frameNames)
|
||||
frameMapUpdates.set(cacheKey, frameNames)
|
||||
}
|
||||
|
||||
if (!frameNames.length) {
|
||||
throw new Error(`视频帧映射为空:${videoName}`)
|
||||
}
|
||||
|
||||
frameNames.forEach((frameName) => {
|
||||
@@ -298,24 +340,21 @@ const LabelPage = ({
|
||||
})
|
||||
}
|
||||
|
||||
if (!normalizedDataName.length) {
|
||||
throw new Error("未生成可用视频帧")
|
||||
}
|
||||
|
||||
if (imageMapUpdated) {
|
||||
useImagesStore.getState().setImages(imagesMap)
|
||||
}
|
||||
if (!normalizedDataName.length) return detail
|
||||
frameMapUpdates.forEach((frameNames, cacheKey) => {
|
||||
useVideoFrameStore.getState().setVideoFrames(cacheKey, frameNames)
|
||||
})
|
||||
|
||||
return {
|
||||
...detail,
|
||||
data_name: normalizedDataName,
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
showNotification({
|
||||
color: "red",
|
||||
title: "视频解码失败",
|
||||
message: "视频帧生成失败,已回退到原始数据",
|
||||
})
|
||||
return detail
|
||||
}
|
||||
},
|
||||
[projectId, taskId]
|
||||
)
|
||||
@@ -385,6 +424,9 @@ const LabelPage = ({
|
||||
// 获取并处理当前任务标注结果数据
|
||||
const dataRes = await getLabelResult(taskId)
|
||||
const { results } = dataRes
|
||||
const resultImageEntries = results.flatMap((item) =>
|
||||
expandLabelResultImages(item)
|
||||
)
|
||||
|
||||
let currentWorkTime = 0
|
||||
if (label_status === 2) {
|
||||
@@ -411,13 +453,14 @@ const LabelPage = ({
|
||||
review_key_frame_size: {},
|
||||
question_size: {},
|
||||
}
|
||||
results.forEach((item) => {
|
||||
const { image_objects } = item
|
||||
if (image_objects) {
|
||||
const { annotations, images, key_frame, desc } = image_objects
|
||||
resultImageEntries.forEach(({ frameName, imageObjects }) => {
|
||||
const { annotations, images, key_frame, desc } = imageObjects
|
||||
const rasterName = frameName || images.file_name
|
||||
if (rasterName) {
|
||||
usePaperStore
|
||||
.getState()
|
||||
.setRasterSize(images.file_name, [images.width, images.height])
|
||||
.setRasterSize(rasterName, [images.width, images.height])
|
||||
}
|
||||
annotations.forEach((annotation) => {
|
||||
let reviewed = false
|
||||
const { comment, prelabel, uid } = annotation
|
||||
@@ -440,19 +483,19 @@ const LabelPage = ({
|
||||
})
|
||||
if (key_frame) {
|
||||
if (label_status === 2) {
|
||||
let id = image_objects.label1_uid!
|
||||
let id = imageObjects.label1_uid!
|
||||
if (id)
|
||||
workLoad.key_frame_size[id]
|
||||
? workLoad.key_frame_size[id]++
|
||||
: (workLoad.key_frame_size[id] = 1)
|
||||
} else if (label_status === 4) {
|
||||
let id = image_objects.review1_uid!
|
||||
let id = imageObjects.review1_uid!
|
||||
if (id)
|
||||
workLoad.review_key_frame_size[id]
|
||||
? workLoad.review_key_frame_size[id]++
|
||||
: (workLoad.review_key_frame_size[id] = 1)
|
||||
} else if (label_status === 6) {
|
||||
let id = image_objects.review2_uid!
|
||||
let id = imageObjects.review2_uid!
|
||||
if (id)
|
||||
workLoad.review_key_frame_size[id]
|
||||
? workLoad.review_key_frame_size[id]++
|
||||
@@ -481,7 +524,6 @@ const LabelPage = ({
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log("获取时计算的工作量统计", workLoad)
|
||||
setOldWorkLoad(workLoad)
|
||||
@@ -492,11 +534,11 @@ const LabelPage = ({
|
||||
// 重置 PathGroupIds
|
||||
useRightToolsStore.getState().setPathGroupIds([])
|
||||
// 生成标注结果
|
||||
results.forEach((item) => {
|
||||
const { data_name, image_objects } = item
|
||||
if (image_objects) {
|
||||
resultImageEntries.forEach(({ frameName, imageObjects }) => {
|
||||
const key = frameName
|
||||
if (!key) return
|
||||
const categoryMap = new Map()
|
||||
const { annotations, image_groups, desc } = image_objects
|
||||
const { annotations, image_groups, desc } = imageObjects
|
||||
// 赋值用过的 id
|
||||
annotations.map((annotation) => {
|
||||
useRightToolsStore.getState().pushPathId(annotation.id)
|
||||
@@ -578,17 +620,16 @@ const LabelPage = ({
|
||||
|
||||
categoryMap.set(categoryKey, arr)
|
||||
})
|
||||
const key = data_name
|
||||
taskLabelData.set(key, categoryMap)
|
||||
// 关键帧信息
|
||||
const keyFrame = {
|
||||
key_frame: image_objects.key_frame || false,
|
||||
label1_ts: image_objects.label1_ts || 0,
|
||||
label1_uid: image_objects.label1_uid || 0,
|
||||
review1_ts: image_objects.review1_ts || 0,
|
||||
review1_uid: image_objects.review1_uid || 0,
|
||||
review2_ts: image_objects.review2_ts || 0,
|
||||
review2_uid: image_objects.review2_uid || 0,
|
||||
key_frame: imageObjects.key_frame || false,
|
||||
label1_ts: imageObjects.label1_ts || 0,
|
||||
label1_uid: imageObjects.label1_uid || 0,
|
||||
review1_ts: imageObjects.review1_ts || 0,
|
||||
review1_uid: imageObjects.review1_uid || 0,
|
||||
review2_ts: imageObjects.review2_ts || 0,
|
||||
review2_uid: imageObjects.review2_uid || 0,
|
||||
}
|
||||
useBottomToolsStore.getState().setKeyFrameData(key, keyFrame)
|
||||
// 大语言模型 两种数据情况
|
||||
@@ -665,7 +706,6 @@ const LabelPage = ({
|
||||
])
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
setLabel(taskLabelData)
|
||||
// 获取任务详情时设置状态栈初始值
|
||||
@@ -675,9 +715,18 @@ const LabelPage = ({
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
setOldWorkLoad(null)
|
||||
setTaskDetail(undefined)
|
||||
useBottomToolsStore.getState().setAllItems([])
|
||||
setLabel(new Map())
|
||||
setStateStack([])
|
||||
useRightToolsStore.getState().setPathGroupMap(new Map())
|
||||
if (err instanceof Error && err.message.includes("视频")) {
|
||||
showNotification({
|
||||
color: "red",
|
||||
title: "视频解码失败",
|
||||
message: err.message,
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
usePaperStore.getState().setLoadingData(false)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export interface LabelResult {
|
||||
data_name: string
|
||||
image_objects?: ImageObjects
|
||||
video_objects?: {
|
||||
[key: string]: ImageObjects
|
||||
}
|
||||
pc_box_objects?: { [key: string]: any }[]
|
||||
pc_segment_objects?: { [key: string]: any }[]
|
||||
pc_vegetation_objects?: { [key: string]: any }[]
|
||||
@@ -129,6 +132,7 @@ export interface DataList {
|
||||
data_name: string
|
||||
data_type: string
|
||||
image_data?: string
|
||||
video_data?: string
|
||||
pointcloud_data?: PointcloudDatum[]
|
||||
[property: string]: any
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ export namespace Task {
|
||||
id: number
|
||||
create_date: string
|
||||
data_type?: number
|
||||
task_data_type?: number
|
||||
data_name: {
|
||||
name: [string]
|
||||
related_images: []
|
||||
|
||||
@@ -82,6 +82,7 @@ import {
|
||||
useKeyEventStore,
|
||||
useLabelStore,
|
||||
useObjectStore,
|
||||
useVideoFrameStore,
|
||||
} from "../store"
|
||||
import { useBackUrlStore, usePermissionStore } from "../store/auth"
|
||||
import { useBottomToolsStore } from "../useBottomToolsStore"
|
||||
@@ -450,7 +451,7 @@ const TopTools = (
|
||||
}
|
||||
|
||||
const { label_status, data_name } = taskDetail!
|
||||
let results = data_name.map((item, imageId) => {
|
||||
const frameResults = data_name.map((item, imageId) => {
|
||||
const name = item.name?.[0]
|
||||
let finalData: LabelResult = {
|
||||
data_name: name,
|
||||
@@ -637,6 +638,90 @@ const TopTools = (
|
||||
finalData.image_objects = image_objects
|
||||
return finalData
|
||||
})
|
||||
|
||||
let saveResults: LabelResult[] = frameResults
|
||||
if (projectDetail?.label_type === 7) {
|
||||
const frameNamesFromTask = data_name
|
||||
.map((item) => item.name?.[0] || "")
|
||||
.filter((name) => !!name)
|
||||
const frameNameSet = new Set(frameNamesFromTask)
|
||||
const frameStore = useVideoFrameStore.getState().videoFrames
|
||||
const taskVideoPrefix = `${taskDetail!.project_id}:${taskDetail!.id}:`
|
||||
const videoFrameEntries = Array.from(frameStore.entries()).filter(
|
||||
([key]) => key.startsWith(taskVideoPrefix)
|
||||
)
|
||||
if (!videoFrameEntries.length) {
|
||||
throw new Error("视频帧映射缺失,无法保存,请刷新任务后重试")
|
||||
}
|
||||
|
||||
const videoFrameMap = new Map<string, string[]>()
|
||||
videoFrameEntries.forEach(([key, frameNames]) => {
|
||||
const videoName = key.slice(taskVideoPrefix.length)
|
||||
if (!videoName) return
|
||||
videoFrameMap.set(
|
||||
videoName,
|
||||
(frameNames || []).filter((frameName) =>
|
||||
frameNameSet.has(frameName)
|
||||
)
|
||||
)
|
||||
})
|
||||
if (!videoFrameMap.size) {
|
||||
throw new Error("视频帧映射无效,无法保存,请刷新任务后重试")
|
||||
}
|
||||
|
||||
const videoNames = Array.from(videoFrameMap.keys())
|
||||
const frameToVideoMap = new Map<string, string>()
|
||||
videoFrameMap.forEach((frameNames, videoName) => {
|
||||
frameNames.forEach((frameName) => {
|
||||
frameToVideoMap.set(frameName, videoName)
|
||||
})
|
||||
})
|
||||
|
||||
const unresolvedFrames = frameNamesFromTask.filter(
|
||||
(frameName) => !frameToVideoMap.has(frameName)
|
||||
)
|
||||
if (unresolvedFrames.length) {
|
||||
throw new Error(
|
||||
`视频帧映射缺失,无法保存,请刷新任务后重试: ${unresolvedFrames.slice(0, 5).join(", ")}`
|
||||
)
|
||||
}
|
||||
|
||||
const frameResultMap = new Map<string, ImageObjects>()
|
||||
frameResults.forEach((item) => {
|
||||
if (item.image_objects) {
|
||||
frameResultMap.set(item.data_name, item.image_objects)
|
||||
}
|
||||
})
|
||||
|
||||
const groupedVideoObjects = new Map<
|
||||
string,
|
||||
{ [key: string]: ImageObjects }
|
||||
>()
|
||||
frameResultMap.forEach((imageObjects, frameName) => {
|
||||
const videoName = frameToVideoMap.get(frameName)
|
||||
if (!videoName) return
|
||||
if (!groupedVideoObjects.has(videoName)) {
|
||||
groupedVideoObjects.set(videoName, {})
|
||||
}
|
||||
groupedVideoObjects.get(videoName)![frameName] = imageObjects
|
||||
})
|
||||
|
||||
saveResults = videoNames
|
||||
.map((videoName) => {
|
||||
const videoObjects = groupedVideoObjects.get(videoName)
|
||||
if (!videoObjects || !Object.keys(videoObjects).length) return null
|
||||
return {
|
||||
data_name: videoName,
|
||||
video_objects: videoObjects,
|
||||
} as LabelResult
|
||||
})
|
||||
.filter((item): item is LabelResult => !!item)
|
||||
|
||||
if (!saveResults.length) {
|
||||
throw new Error("视频任务保存结果为空")
|
||||
}
|
||||
}
|
||||
|
||||
const workTime = {
|
||||
label_work_time: labelTime.label,
|
||||
first_review_work_time: labelTime.review1,
|
||||
@@ -653,7 +738,7 @@ const TopTools = (
|
||||
}
|
||||
const workLoad: WorkLoad = JSON.parse(JSON.stringify(initialWorkLoad))
|
||||
workLoad.work_time = time ? time - oldWorkLoad.work_time : 0
|
||||
results.forEach((item) => {
|
||||
frameResults.forEach((item) => {
|
||||
const { image_objects } = item
|
||||
if (image_objects) {
|
||||
const { annotations, key_frame, desc } = image_objects
|
||||
@@ -744,12 +829,9 @@ const TopTools = (
|
||||
let params: RequestLabelResult = {
|
||||
uid: user_id,
|
||||
task_id: taskDetail?.id || 0,
|
||||
results: results,
|
||||
results: saveResults,
|
||||
...defaultObj,
|
||||
}
|
||||
results.forEach((item) => {
|
||||
console.log(item.image_objects)
|
||||
})
|
||||
console.log(params, workLoad)
|
||||
await saveLabelResult(params)
|
||||
notifications.show({
|
||||
@@ -760,6 +842,11 @@ const TopTools = (
|
||||
updateOldWorkLoad(workLoad)
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
notifications.hide("save")
|
||||
notifications.show({
|
||||
color: "red",
|
||||
message: error?.message || "保存失败",
|
||||
})
|
||||
}
|
||||
}, [
|
||||
labelCategories,
|
||||
|
||||
Reference in New Issue
Block a user