Merge branch 'zh' into 'main'

Zh

See merge request prism/lable/labelimage!13
This commit is contained in:
刘耀勇
2026-04-16 20:28:45 +08:00
11 changed files with 392 additions and 142 deletions

View File

@@ -38,6 +38,31 @@ import { useRouter, useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo, useState } from "react"
import TaskStatusTag from "./components/TaskStatusTag"
const EMPTY_TASK_SIZE: Task.SizeProps = Object.freeze({
object_size: 0,
valid_size: 0,
first_comment_size: 0,
second_comment_size: 0,
new_size: 0,
prelabel_size: 0,
prelabel_modify_size: 0,
dynamic_size: 0,
static_size: 0,
review_size: 0,
review_dynamic_size: 0,
review_static_size: 0,
key_frame_size: 0,
question_size: 0,
total_qa_group_size: 0,
single_qa_group_size: 0,
multiple_qa_group_size: 0,
new_qa_group_size: 0,
new_single_qa_group_size: 0,
new_multiple_qa_group_size: 0,
review_question_size: 0,
comment_question_size: 0,
})
function createInitialFilters() {
return {
search_id: "",
@@ -67,6 +92,7 @@ export default function OwnTaskTableContainer(props: {
const user_id = usePermissionStore((s) => s.user_id)
const [total, setTotal] = useState(0)
const [totalSize, setTotalSize] = useState<Task.SizeProps>(EMPTY_TASK_SIZE)
const [pageNumber, setPageNumber] = useState(1)
const [pageSize, setPageSize] = useState(15)
@@ -130,9 +156,11 @@ export default function OwnTaskTableContainer(props: {
const res = await getTaskList(appliedParams)
setRecords(res?.task_list ?? [])
setTotal(res?.total_items ?? 0)
setTotalSize(res?.total_size ?? EMPTY_TASK_SIZE)
} catch (e) {
setRecords([])
setTotal(0)
setTotalSize(EMPTY_TASK_SIZE)
notifications.show({
color: "red",
title: "加载失败",
@@ -201,6 +229,7 @@ export default function OwnTaskTableContainer(props: {
accessor: "id",
title: "任务ID",
width: 100,
footer: "总计",
render: (record) => (
<Button
variant="transparent"
@@ -243,30 +272,35 @@ export default function OwnTaskTableContainer(props: {
accessor: "label_object_size.object_size",
title: "对象总数",
width: 110,
footer: totalSize.object_size,
render: (record) => record.label_object_size?.object_size ?? "-",
},
{
accessor: "label_object_size.valid_size",
title: "有效数",
width: 100,
footer: totalSize.valid_size,
render: (record) => record.label_object_size?.valid_size ?? "-",
},
{
accessor: "label_object_size.new_size",
title: "新增数",
width: 100,
footer: totalSize.new_size,
render: (record) => record.label_object_size?.new_size ?? "-",
},
{
accessor: "label_object_size.prelabel_size",
title: "预标注总数",
width: 120,
footer: totalSize.prelabel_size,
render: (record) => record.label_object_size?.prelabel_size ?? "-",
},
{
accessor: "label_object_size.prelabel_modify_size",
title: "预标注修改数",
width: 130,
footer: totalSize.prelabel_modify_size,
render: (record) =>
record.label_object_size?.prelabel_modify_size ?? "-",
},
@@ -274,18 +308,20 @@ export default function OwnTaskTableContainer(props: {
accessor: "label_object_size.first_comment_size",
title: "批注一轮",
width: 110,
footer: totalSize.first_comment_size,
render: (record) => record.label_object_size?.first_comment_size ?? "-",
},
{
accessor: "label_object_size.second_comment_size",
title: "批注二轮",
width: 110,
footer: totalSize.second_comment_size,
render: (record) =>
record.label_object_size?.second_comment_size ?? "-",
},
]
return cols
}, [info, projectId, router, setBackProps, type])
}, [info, projectId, router, setBackProps, totalSize, type])
return (
<Stack gap="sm" style={{ flex: 1, minHeight: 0 }}>

View File

@@ -122,6 +122,31 @@ const SORTABLE_TASK_SIZE_COLUMNS: Array<{
{ accessor: "second_comment_size", title: "批注数(2)", width: 120 },
]
const EMPTY_TASK_SIZE: Task.SizeProps = Object.freeze({
object_size: 0,
valid_size: 0,
first_comment_size: 0,
second_comment_size: 0,
new_size: 0,
prelabel_size: 0,
prelabel_modify_size: 0,
dynamic_size: 0,
static_size: 0,
review_size: 0,
review_dynamic_size: 0,
review_static_size: 0,
key_frame_size: 0,
question_size: 0,
total_qa_group_size: 0,
single_qa_group_size: 0,
multiple_qa_group_size: 0,
new_qa_group_size: 0,
new_single_qa_group_size: 0,
new_multiple_qa_group_size: 0,
review_question_size: 0,
comment_question_size: 0,
})
export interface selectedDataProps {
status: number
uid: number | null
@@ -195,6 +220,7 @@ export default function TaskTableContainer(props: {
const { setBackProps } = useBackUrlStore()
const [total, setTotal] = useState(0)
const [totalSize, setTotalSize] = useState<Task.SizeProps>(EMPTY_TASK_SIZE)
const [pageNumber, setPageNumber] = useState(1)
const [pageSize, setPageSize] = useState(15)
@@ -324,9 +350,11 @@ export default function TaskTableContainer(props: {
const res = await getTaskList(appliedParams)
setRecords(res?.task_list ?? [])
setTotal(res?.total_items ?? 0)
setTotalSize(res?.total_size ?? EMPTY_TASK_SIZE)
} catch (e) {
setRecords([])
setTotal(0)
setTotalSize(EMPTY_TASK_SIZE)
notifications.show({
color: "red",
title: "加载失败",
@@ -621,6 +649,7 @@ export default function TaskTableContainer(props: {
width,
sortable: true,
render: (record) => record.label_object_size?.[accessor] ?? "-",
footer: totalSize[accessor] ?? 0,
})
const cols: DataTableColumn<Task.DataProps>[] = [
@@ -628,6 +657,7 @@ export default function TaskTableContainer(props: {
accessor: "id",
title: "任务ID",
width: 90,
footer: "总计",
render: (record) => (
<Button
variant="transparent"
@@ -805,7 +835,15 @@ export default function TaskTableContainer(props: {
},
]
return cols
}, [hasRolePermissions, info, projectId, router, setBackProps, type])
}, [
hasRolePermissions,
info,
projectId,
router,
setBackProps,
totalSize,
type,
])
return (
<>

View File

@@ -307,6 +307,7 @@ const LabelPage = ({
activeOperation,
setActiveOperation,
setDrawOption,
setSubAttrPresetShow,
showObjectList,
showGroupList,
showTaskList,
@@ -1433,6 +1434,31 @@ const LabelPage = ({
const { setShift, focusInput } = useKeyEventStore()
const drawAction = useKeyboardStore((state) => state.drawAction)
const initializedDrawActionTaskIdRef = useRef<number | null>(null)
const handleSelectOperation = useCallback(
(operationId: string) => {
const operationSchema = getOperationSchema(operationId)
if (!operationSchema) return false
setActiveOperation(operationId)
setSubAttrPresetShow(!!operationSchema.sub_attributes_describe?.length)
if (isLabelTask) {
setEditMode(true)
}
return true
},
[
getOperationSchema,
isLabelTask,
setActiveOperation,
setEditMode,
setSubAttrPresetShow,
]
)
const drawShortcutSnapshotRef = useRef<{
activeOperation: string
drawOption: "default" | "intersect" | "unite"
@@ -1441,20 +1467,17 @@ const LabelPage = ({
const handleShortcut = useCallback(
(key: string, ctrl: boolean) => {
let checkOperation = getOperationSchema(key)
const mode = usePaperStore.getState().mode
const nextOperationId = ctrl ? (+key + 10).toString() : key
const checkOperation = getOperationSchema(nextOperationId)
// 标注方案里面存在的非文本类型标注才能切换
if (checkOperation) {
// 辅助标注模式下非多边形类型不能切换
if (mode === "support" && checkOperation.label_type !== 102) return
if (ctrl) {
setActiveOperation((+key + 10).toString())
} else {
setActiveOperation(key)
}
handleSelectOperation(nextOperationId)
}
},
[getOperationSchema, setActiveOperation]
[getOperationSchema, handleSelectOperation]
)
const resolveSelectedObjectContext = useCallback(() => {
@@ -1594,6 +1617,50 @@ const LabelPage = ({
})
}, [activeImage, labelData])
const handleToggleHideSelectionOrAll = useCallback(() => {
const selectedIds =
useObjectStore.getState().selectedPath[activeImage] || []
if (!selectedIds.length) {
handleToggleHideAllObjects()
return
}
const activeImageLabel = labelData.get(activeImage)
if (!activeImageLabel?.size) {
return
}
const selectedIdSet = new Set(selectedIds)
const selectedObjects: Array<{
operationId: string
objectId: number
}> = []
activeImageLabel.forEach((paths, operationId) => {
paths.forEach(([objectId]) => {
if (!selectedIdSet.has(objectId)) return
selectedObjects.push({
operationId,
objectId,
})
})
})
if (!selectedObjects.length) {
return
}
const { pathStatus } = useObjectStore.getState()
const areAllSelectedObjectsHidden = selectedObjects.every(
({ objectId }) => pathStatus[activeImage + objectId]?.HIDE ?? false
)
const nextHide = !areAllSelectedObjectsHidden
selectedObjects.forEach(({ operationId, objectId }) => {
toggleObjectHideByShortcut(activeImage, operationId, objectId, nextHide)
})
}, [activeImage, handleToggleHideAllObjects, labelData])
const rerenderActiveImageByLabel = useCallback(
(
imageId: string,
@@ -2185,7 +2252,7 @@ const LabelPage = ({
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") {
e.preventDefault()
handleToggleHideAllObjects()
handleToggleHideSelectionOrAll()
return
}
@@ -2487,6 +2554,7 @@ const LabelPage = ({
focusInput,
getOperationSchema,
handleToggleHideAllObjects,
handleToggleHideSelectionOrAll,
handleMergeSelectedObjects,
handleContinueSelectedPolygon,
handleShortcut,
@@ -2519,6 +2587,14 @@ const LabelPage = ({
drawShortcutSnapshotRef.current = null
}, [activeImage])
useEffect(() => {
if (!taskDetail?.id || !activeImage || !isLabelTask) return
if (initializedDrawActionTaskIdRef.current === taskDetail.id) return
useKeyboardStore.getState().setDrawAction("subtract")
initializedDrawActionTaskIdRef.current = taskDetail.id
}, [activeImage, isLabelTask, taskDetail?.id])
const handleBeforeUnload = (e: any) => {
e.preventDefault()
// 备份
@@ -2708,6 +2784,7 @@ const LabelPage = ({
oldWorkLoad={oldWorkLoad}
updateOldWorkLoad={updateOldWorkLoad}
isLabelTask={isLabelTask}
onOperationChange={handleSelectOperation}
renderPolygons={
paperContainerRef.current
? paperContainerRef.current.renderPolygons

View File

@@ -3,7 +3,6 @@
import { Box } from "@mantine/core"
import { RichTextEditor } from "@mantine/tiptap"
import "@mantine/tiptap/styles.css"
import { IconPhoto } from "@tabler/icons-react"
import Image from "@tiptap/extension-image"
import Link from "@tiptap/extension-link"
import Placeholder from "@tiptap/extension-placeholder"
@@ -148,7 +147,7 @@ const EditorContainer = ({
<RichTextEditor.Hr />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
{/* <RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
@@ -160,7 +159,7 @@ const EditorContainer = ({
title="上传图片">
<IconPhoto stroke={1.5} size="1rem" />
</RichTextEditor.Control>
</RichTextEditor.ControlsGroup>
</RichTextEditor.ControlsGroup> */}
</RichTextEditor.Toolbar>
<RichTextEditor.Content h={144} style={{ overflowY: "auto" }} />

View File

@@ -52,7 +52,7 @@ import { renderGroupPath } from "../useRenderGroupPath"
import useRenderTag from "../useRenderTag"
import { useRightToolsStore } from "../useRightToolsStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { checkCommentsIsSame } from "../util"
import { buildSubAttributeFormValues, checkCommentsIsSame } from "../util"
import { safeClone } from "../utils/clone"
import { labelTypeMap } from "../utils/constants"
import { adjustPoints } from "../utils/paperjs"
@@ -3315,50 +3315,26 @@ const PaperContainer = (
.get(activeImage)
?.get(operationId)
?.find((item) => item[0] === id)
form.setFieldValue("id", id.toString())
form.setFieldValue("operation", operationId)
if (currentPath?.[2].sub_attributes) {
let operationSchema = getOperationSchema(operationId)
let obj: {
[key: string]: string | string[]
} = {}
Object.entries(currentPath?.[2].sub_attributes).map(([key, value]) => {
let select_mode = operationSchema?.sub_attributes_describe?.find(
(item) => item.chinese_name === key
)?.select_mode
switch (select_mode) {
case 2:
obj[key] = Array.isArray(value)
? value
: (value as string)
.split(",")
.map((item) => item.trim())
.filter(Boolean)
return
default:
obj[key] = value as string
return
}
})
form.setFieldValue("sub_attributes", obj)
}
if (currentPath?.[2].comment) {
const operationSchema = getOperationSchema(operationId)
const nowComment =
currentPath?.[2].comment[currentPath?.[2].comment.length - 1]
form.setFieldValue(
"check_box_comments",
currentPath?.[2].comment?.[currentPath?.[2].comment.length - 1]
form.setValues({
id: id.toString(),
operation: operationId,
sub_attributes: buildSubAttributeFormValues(
operationSchema,
currentPath?.[2].sub_attributes || {}
),
check_box_comments:
nowComment && nowComment.check_box_comments.length
? nowComment.check_box_comments
: []
)
form.setFieldValue(
"text_comments",
: [],
text_comments:
nowComment && nowComment.text_comments.length
? nowComment.text_comments[0]
: ""
)
}
: "",
})
// 可选的ids
let images = Array.from(labelState.keys()).filter(
(key) => key !== activeImage
@@ -3580,11 +3556,10 @@ const PaperContainer = (
})
}
// newOperation的subAttributes
let subAttr = Object.assign(
{},
let subAttr = safeClone(
useTopToolsStore.getState().subAttributes[
operationSchema?.label_class ?? ""
]
] || {}
)
// 修改labeldata
setOperation(activeImage, newOperation, [
@@ -3612,16 +3587,21 @@ const PaperContainer = (
)
const handleChangeContextMenuSubAttr = useCallback(
(key: string, value: string) => {
(key: string, value: string | string[]) => {
const currentSubAttributes = safeClone(
(pathItem?.[2]?.sub_attributes ||
newDetail.sub_attributes ||
{}) as Record<string, any>
)
currentSubAttributes[key] = Array.isArray(value) ? value.join(",") : value
setOperation(activeImage, operationId, [
id,
pathItem?.[1] || [],
{
...newDetail,
...(pathItem?.[2] || {}),
sub_attributes: Object.assign(newDetail.sub_attributes || {}, {
[key]: value,
}),
sub_attributes: currentSubAttributes,
},
pathItem?.[3] || [],
])
@@ -3892,7 +3872,7 @@ const PaperContainer = (
)
handleChangeContextMenuSubAttr(
item.chinese_name,
value.toString()
value
)
}}>
<Stack gap={6} mt={6}>

View File

@@ -166,6 +166,24 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
[activeImage, continueObjectId, storeLabel]
)
const getObjectItemsById = useCallback(
(id: number) => {
return getItemsById(id).filter((item) => item.data?.type !== "tag")
},
[getItemsById]
)
const bringTagItemsToFront = useCallback(
(id: number) => {
getItemsById(id).forEach((item) => {
if (item.data?.type === "tag") {
item.bringToFront()
}
})
},
[getItemsById]
)
// 切换选中对象时,清除所有其他对象的当前选中状态
const clearSelectedItems = () => {
usePaperStore
@@ -215,11 +233,16 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
const handleSelect = useCallback(
(id: number, operationId: any) => {
if (pathStatus[activeImage + id]?.HIDE) return
const objectItems = getObjectItemsById(id)
const targetItem = getItemById(id)
if (selectedPath[activeImage]?.length) {
if (!selectedPath[activeImage]?.includes(id)) {
if (pressShift) {
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (objectItems.length && targetItem) {
objectItems.forEach((item) => {
if (item instanceof paper.Path) {
handleSelectedPath(item)
} else if (item instanceof paper.CompoundPath) {
@@ -228,13 +251,14 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
else item.data.selected = true
}
})
bringTagItemsToFront(id)
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === operationId
)
const globalPosition = getItemById(id)!.globalMatrix.transform(
getItemById(id)!.position
const globalPosition = targetItem.globalMatrix.transform(
targetItem.position
)
const { top, left } = getShowContextMenuPosition(
globalPosition,
@@ -251,8 +275,9 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
} else {
clearSelectedItems()
selectedPath[activeImage].forEach((selectedId) => {
if (getItemsById(selectedId)?.length) {
getItemsById(selectedId).forEach((item) => {
const selectedObjectItems = getObjectItemsById(selectedId)
if (selectedObjectItems.length) {
selectedObjectItems.forEach((item) => {
cancelSelectedPathColor(item)
})
useOtherToolsStore.getState().setShowContextMenu({
@@ -267,8 +292,8 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
})
}
})
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (objectItems.length && targetItem) {
objectItems.forEach((item) => {
if (item instanceof paper.Path) {
handleSelectedPath(item)
} else if (item instanceof paper.CompoundPath) {
@@ -277,13 +302,14 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
else item.data.selected = true
}
})
bringTagItemsToFront(id)
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === operationId
)
const globalPosition = getItemById(id)!.globalMatrix.transform(
getItemById(id)!.position
const globalPosition = targetItem.globalMatrix.transform(
targetItem.position
)
const { top, left } = getShowContextMenuPosition(
globalPosition,
@@ -302,8 +328,8 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
} else {
clearSelectedItems()
updateSelectedPath(activeImage, "DELETE", id)
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (objectItems.length) {
objectItems.forEach((item) => {
cancelSelectedPathColor(item)
})
useOtherToolsStore.getState().setShowContextMenu({
@@ -321,8 +347,8 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
} else {
clearSelectedItems()
updateSelectedPath(activeImage, "ADD", id)
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (objectItems.length && targetItem) {
objectItems.forEach((item) => {
if (item instanceof paper.Path) {
handleSelectedPath(item)
} else if (item instanceof paper.CompoundPath) {
@@ -337,14 +363,15 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
// item.fillColor = item.data?.fillColor;
// item.bringToFront();
})
bringTagItemsToFront(id)
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === operationId
)
const globalPosition = getItemById(id)!.globalMatrix.transform(
getItemById(id)!.position
const globalPosition = targetItem.globalMatrix.transform(
targetItem.position
)
const { top, left } = getShowContextMenuPosition(
globalPosition,
@@ -362,8 +389,10 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
},
[
activeImage,
bringTagItemsToFront,
getObjectItemsById,
getItemById,
getItemsById,
pathStatus,
pressShift,
selectedPath,
updateSelectedPath,
@@ -889,10 +918,13 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
: "var(--mantine-color-gray-0)"
}
c={isSelected ? "white" : undefined}
onClick={() =>
onClick={() => {
if (isChildHide) return
handleSelect(child[0], operationId)
}
style={{ cursor: "pointer" }}>
}}
style={{
cursor: isChildHide ? "default" : "pointer",
}}>
<Flex justify="space-between" align="center">
<Group gap="xs">
<ActionIcon

View File

@@ -98,7 +98,7 @@ import {
NODE_SIZE_MIN,
useTopToolsStore,
} from "../useTopToolsStore"
import { getSubAttrStatus } from "../util"
import { buildSubAttributeFormValues, getSubAttrStatus } from "../util"
import { safeClone } from "../utils/clone"
import { labelTypeMap, TaskStatusEnum } from "../utils/constants"
import { adjustAllPoints } from "../utils/paperjs"
@@ -118,6 +118,7 @@ interface TopToolsComponentProps {
oldWorkLoad: WorkLoad | null
updateOldWorkLoad: (data: WorkLoad) => void
isLabelTask: boolean
onOperationChange: (operationId: string) => void
renderPolygons: (
operationId: string,
imageData: Map<string, [number, any[], any, any[]][]> | undefined
@@ -161,6 +162,7 @@ const TopTools = (
oldWorkLoad,
updateOldWorkLoad,
isLabelTask,
onOperationChange,
renderPolygons,
} = props
const { backUrl, basePath } = useBackUrlStore()
@@ -1488,36 +1490,10 @@ const TopTools = (
operationId: string,
values: Record<string, string | string[] | undefined> = {}
) => {
const schema = getOperationSchema(operationId)
if (!schema?.sub_attributes_describe?.length) return {}
const nextValues: Record<string, string | string[]> = {}
schema.sub_attributes_describe.forEach((item) => {
const currentValue = values[item.chinese_name]
if (
currentValue === undefined ||
currentValue === null ||
currentValue === ""
return buildSubAttributeFormValues(
getOperationSchema(operationId),
values
)
return
if (item.select_mode === 2) {
nextValues[item.chinese_name] = Array.isArray(currentValue)
? currentValue
: currentValue
.split(",")
.map((value) => value.trim())
.filter(Boolean)
return
}
nextValues[item.chinese_name] = Array.isArray(currentValue)
? currentValue[0] || ""
: currentValue
})
return nextValues
},
[getOperationSchema]
)
@@ -1627,6 +1603,10 @@ const TopTools = (
size="xs"
onClick={() => {
form.reset()
form.setFieldValue(
"sub_attributes",
getPresetFormValues(activeOperation, {})
)
if (operationSchema?.label_class)
setsubAttributes(operationSchema.label_class, {})
}}>
@@ -2334,7 +2314,7 @@ const TopTools = (
<UnstyledButton
key={item.category_id}
onClick={() => {
setActiveOperation(item.category_id.toString())
onOperationChange(item.category_id.toString())
setOperationPickerOpened(false)
setOperationKeyword("")
}}

View File

@@ -20,6 +20,7 @@ import { usePaperSupportStore } from "./usePaperSupportStore"
import { useRightToolsStore } from "./useRightToolsStore"
import { DEFAULT_NODE_SIZE, useTopToolsStore } from "./useTopToolsStore"
import { safeClone } from "./utils/clone"
import { getObjectTagVisibility } from "./utils/objectVisibility"
interface ContinuePolygonTarget {
imageId: string
@@ -216,6 +217,16 @@ const initialPaperState = {
reciprocalRasterScale: {},
resizeGhostRequestToken: 0,
}
const insertItemBelowTags = (item: paper.Item | null | undefined) => {
if (!item) return
const group = usePaperStore.getState().group
if (!group || item.parent !== group) return
const firstTagItem = group.getItems({ data: { type: "tag" } })[0]
if (!firstTagItem || firstTagItem === item) return
item.insertBelow(firstTagItem)
}
const handleDelete = () => {
const group = usePaperStore.getState().group!
const imageId = useBottomToolsStore.getState().activeImage
@@ -450,12 +461,7 @@ const clearPaperSelectedObjects = () => {
.getState()
.getItemsById(id)
.forEach((item) => {
if (
(item instanceof paper.Path || item instanceof paper.CompoundPath) &&
["rectangle", "polygon"].includes(item.data?.type || "")
) {
item.fillColor = item.data?.blankColor || item.fillColor || null
}
resetPaperShapeFillColor(item)
})
})
@@ -464,6 +470,16 @@ const clearPaperSelectedObjects = () => {
})
}
const resetPaperShapeFillColor = (item: paper.Item) => {
if (!(item instanceof paper.Path || item instanceof paper.CompoundPath)) {
return
}
if (!["rectangle", "polygon"].includes(item.data?.type || "")) {
return
}
item.fillColor = item.data?.blankColor || item.fillColor || null
}
const applyPaperSelectedPath = (item: paper.Path | paper.CompoundPath) => {
if (item.data.type === "rectangle" && item instanceof paper.Path) {
item.data.selected = true
@@ -2011,15 +2027,14 @@ export const usePaperStore = create<PaperState>((set) => ({
type: "tag",
},
})
// 标识位为true时选中路径隐藏tags; 没选中显示tags
const configs = useTopToolsStore.getState().showTagsConfigs
// 仅恢复当前应显示的标签,隐藏对象的标签保持不可见
if (useTopToolsStore.getState().showTags) {
const setTagVisibleByConfig = (tag: any) => {
if (tag.data.tag_category === "mask")
tag.visible = configs.includes("外框")
else if (tag.data.tag_category === "point_text")
tag.visible = configs.includes("点ID")
else tag.visible = true
tag.visible = getObjectTagVisibility(
useBottomToolsStore.getState().activeImage,
Number(tag.data?.id),
tag.data?.tag_category
)
}
lastTags.forEach(setTagVisibleByConfig)
tags.forEach(setTagVisibleByConfig)
@@ -3147,6 +3162,7 @@ export const usePaperStore = create<PaperState>((set) => ({
})
)
polygon.data = Object.assign({ type: "polygon", ...option }, data)
insertItemBelowTags(polygon)
points?.forEach((point) => {
polygon.add(point)
})
@@ -3219,6 +3235,7 @@ export const usePaperStore = create<PaperState>((set) => ({
})
)
compoundPath.data = Object.assign({ type: "polygon", ...option }, data)
insertItemBelowTags(compoundPath)
compoundPath.onMouseDown = (e: {
event: MouseEvent
point: paper.Point
@@ -4606,8 +4623,7 @@ export const usePaperStore = create<PaperState>((set) => ({
.getState()
.group!.getItems({})
.forEach((item) => {
// clear color
item.fillColor = item?.data?.blankColor
resetPaperShapeFillColor(item)
})
}
@@ -4803,6 +4819,7 @@ export const usePaperStore = create<PaperState>((set) => ({
Object.assign(option, { strokeScaling: false })
)
rect.data = Object.assign({ type: "rectangle", ...option }, data)
insertItemBelowTags(rect)
rect.onMouseEnter = (_e: paper.MouseEvent) => {
// if (usePaperStore.getState().mode === "pan") {
// e.target.strokeWidth += 2;
@@ -5045,6 +5062,7 @@ export const usePaperStore = create<PaperState>((set) => ({
)
)
brush.data = Object.assign({ type: "brush", ...option }, data)
insertItemBelowTags(brush)
// points?.forEach((point) => {
// brush.add(point);
// });
@@ -5545,6 +5563,7 @@ export const usePaperStore = create<PaperState>((set) => ({
const newScale = useTopToolsStore.getState().scale / currentScaling
myCircle.scale(newScale / currentScaling)
myCircle.data = Object.assign({ type: "circle", ...option }, data)
insertItemBelowTags(myCircle)
syncPaperCircleDiameter(myCircle)
myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) {
@@ -5614,6 +5633,7 @@ export const usePaperStore = create<PaperState>((set) => ({
)
)
myCircleLine.data = Object.assign({ type: "point", ...option }, data)
insertItemBelowTags(myCircleLine)
return myCircleLine
},

View File

@@ -4,6 +4,7 @@ import { useLabelStore } from "./store"
import { useBottomToolsStore } from "./useBottomToolsStore"
import { usePaperStore } from "./usePaperStore"
import { useTopToolsStore } from "./useTopToolsStore"
import { getObjectTagVisibility } from "./utils/objectVisibility"
const TAG_FONT_SIZE = 12
const TAG_VERTICAL_GAP = 8
@@ -159,13 +160,12 @@ const useRenderTag = () => {
const activeImage = useBottomToolsStore.getState().activeImage
const group = usePaperStore.getState().group
if (!group) return
const visible = useTopToolsStore.getState().showTags
const configs = useTopToolsStore.getState().showTagsConfigs
const groupScale =
Number.isFinite(group.scaling?.x) && group.scaling.x !== 0
? group.scaling.x
: 1
const applyPointTagCompensation = (
const applyScreenSpaceTagCompensation = (
item: paper.Item,
anchor: [number, number]
) => {
@@ -235,7 +235,7 @@ const useRenderTag = () => {
tag_anchor: tagAnchor,
},
parent: group,
visible,
visible: getObjectTagVisibility(activeImage, id, "text"),
})
text.translate(
new paper.Point(
@@ -259,8 +259,10 @@ const useRenderTag = () => {
tag_anchor: tagAnchor,
},
parent: group,
visible,
visible: getObjectTagVisibility(activeImage, id, "text_mask"),
})
applyScreenSpaceTagCompensation(text, tagAnchor)
applyScreenSpaceTagCompensation(textMask, tagAnchor)
text.bringToFront()
}
@@ -276,7 +278,7 @@ const useRenderTag = () => {
tag_category: "mask",
},
parent: group,
visible: visible && configs.includes("外框"),
visible: getObjectTagVisibility(activeImage, id, "mask"),
})
// 标注对象点id
if (path instanceof paper.CompoundPath) {
@@ -301,9 +303,9 @@ const useRenderTag = () => {
tag_anchor: pointTextPosition,
},
parent: group,
visible: visible && configs.includes("点ID"),
visible: getObjectTagVisibility(activeImage, id, "point_text"),
})
applyPointTagCompensation(pointText, pointTextPosition)
applyScreenSpaceTagCompensation(pointText, pointTextPosition)
index++
})
})
@@ -327,9 +329,9 @@ const useRenderTag = () => {
tag_anchor: pointTextPosition,
},
parent: group,
visible: visible && configs.includes("点ID"),
visible: getObjectTagVisibility(activeImage, id, "point_text"),
})
applyPointTagCompensation(pointText, pointTextPosition)
applyScreenSpaceTagCompensation(pointText, pointTextPosition)
})
}
})

View File

@@ -86,6 +86,45 @@ export const getSubAttrStatus = (
return bool
}
export const buildSubAttributeFormValues = (
operationSchema: Project.LabelSchemaList | null,
detail: Record<string, any> = {}
) => {
const nextValues: Record<string, string | string[]> = {}
if (!operationSchema?.sub_attributes_describe?.length) return nextValues
operationSchema.sub_attributes_describe.forEach((item) => {
nextValues[item.chinese_name] = item.select_mode === 2 ? [] : ""
const currentValue = detail?.[item.chinese_name]
if (
currentValue === undefined ||
currentValue === null ||
currentValue === "" ||
(Array.isArray(currentValue) && currentValue.length === 0)
) {
return
}
if (item.select_mode === 2) {
nextValues[item.chinese_name] = Array.isArray(currentValue)
? currentValue
: currentValue
.toString()
.split(",")
.map((value: string) => value.trim())
.filter(Boolean)
return
}
nextValues[item.chinese_name] = Array.isArray(currentValue)
? currentValue[0] || ""
: currentValue.toString()
})
return nextValues
}
//
export const checkCommentsIsSame: (
a: any[] | null,

View File

@@ -1,9 +1,11 @@
import paper from "paper"
import { useLabelStore, useObjectStore } from "../store"
import { useOtherToolsStore } from "../useOtherToolsStore"
import { usePaperStore } from "../usePaperStore"
import { usePaperSupportStore } from "../usePaperSupportStore"
import { useTopToolsStore } from "../useTopToolsStore"
const getTagVisibility = (tagCategory?: string) => {
const getTagVisibilityByConfig = (tagCategory?: string) => {
const { showTags, showTagsConfigs } = useTopToolsStore.getState()
if (!showTags) return false
if (tagCategory === "mask") return showTagsConfigs.includes("外框")
@@ -11,6 +13,16 @@ const getTagVisibility = (tagCategory?: string) => {
return true
}
export const getObjectTagVisibility = (
imageId: string,
objectId: number,
tagCategory?: string
) => {
const { pathStatus } = useObjectStore.getState()
if (pathStatus[imageId + objectId]?.HIDE) return false
return getTagVisibilityByConfig(tagCategory)
}
const setSelectedItemFalse = (id: number) => {
if (!id) return
const sameIdItems = usePaperStore.getState().getItemsById(id)
@@ -22,7 +34,37 @@ const setSelectedItemFalse = (id: number) => {
})
}
const syncObjectItemsVisibility = (objectId: number, hide: boolean) => {
const clearHiddenObjectSelectionState = (imageId: string, objectId: number) => {
const { selectedPath, updateSelectedPath } = useObjectStore.getState()
if (selectedPath[imageId]?.includes(objectId)) {
updateSelectedPath(imageId, "DELETE", objectId)
usePaperSupportStore.getState().clearAll()
}
const otherToolsState = useOtherToolsStore.getState()
if (
otherToolsState.showContextMenu &&
otherToolsState.imageId === imageId &&
otherToolsState.id === objectId
) {
otherToolsState.setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: "",
operationId: "",
id: 0,
})
}
}
const syncObjectItemsVisibility = (
imageId: string,
objectId: number,
hide: boolean
) => {
const sameIdItems = usePaperStore.getState().getItemsById(objectId)
if (!sameIdItems?.length) return
sameIdItems.forEach((item) => {
@@ -31,7 +73,11 @@ const syncObjectItemsVisibility = (objectId: number, hide: boolean) => {
return
}
if (item.data?.type === "tag") {
item.visible = getTagVisibility(item.data?.tag_category)
item.visible = getObjectTagVisibility(
imageId,
objectId,
item.data?.tag_category
)
return
}
item.visible = true
@@ -49,8 +95,9 @@ export const toggleObjectHideByShortcut = (
if (hide) {
updatePathStatus(imageId + objectId, "HIDE", hide)
syncObjectItemsVisibility(objectId, hide)
syncObjectItemsVisibility(imageId, objectId, hide)
setSelectedItemFalse(objectId)
clearHiddenObjectSelectionState(imageId, objectId)
const operationChildren = storeLabel.get(imageId)?.get(operationId) || []
const hasVisibleChild = operationChildren.some((child) => {
@@ -67,5 +114,5 @@ export const toggleObjectHideByShortcut = (
updateOperationStatus(imageId + operationId, "HIDE", hide)
updatePathStatus(imageId + objectId, "HIDE", hide)
syncObjectItemsVisibility(objectId, hide)
syncObjectItemsVisibility(imageId, objectId, hide)
}