Files
labelmain-demo/components/label/components/PaperContainer.tsx
2026-02-03 18:05:47 +08:00

2120 lines
70 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import {
Box,
Button,
Card,
Checkbox,
Group,
Modal,
Radio,
ScrollArea,
Select,
Stack,
Text,
Textarea,
TextInput,
} from "@mantine/core"
import { useForm } from "@mantine/form"
import { notifications } from "@mantine/notifications"
import dayjs from "dayjs"
import msgpack from "msgpack-lite"
import paper from "paper"
import React, {
DispatchWithoutAction,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from "react"
import {
getAuxiliaryAnnotation,
getServerImage,
getTrackingAuxiliaryAnnotation,
} from "../api/label"
import { Project } from "../api/project/typing"
import { LabelState } from "../LabelNossr"
import {
initialDetail,
useImagesStore,
useKeyEventStore,
useLabelStore,
useObjectStore,
} from "../store"
import { usePermissionStore } from "../store/auth"
import { useBottomToolsStore } from "../useBottomToolsStore"
import { useKeyboardStore } from "../useKeyBoardStore"
import { useOtherToolsStore } from "../useOtherToolsStore"
import { clearSupportAnnotationItem, usePaperStore } from "../usePaperStore"
import { renderGroupPath } from "../useRenderGroupPath"
import useRenderTag from "../useRenderTag"
import { useRightToolsStore } from "../useRightToolsStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { checkCommentsIsSame } from "../util"
import { labelTypeMap } from "../utils/constants"
import { adjustPoints } from "../utils/paperjs"
import CrosshairComponent from "./CrosshairComponent"
import { renderOperationIcon } from "./RightObjectTools"
interface PaperComponentProps {
imgSrc: string
forceUpdate: DispatchWithoutAction
projectDetail?: Project.DetailResponse
labelState: LabelState
updateLoadingFlag: (flag: boolean) => void
}
const renderPath = (paperGroup: paper.Group, option: any) => {
return new paper.Path(
Object.assign({ parent: paperGroup, closed: true }, option)
)
}
const renderCompoundPath = (paperGroup: paper.Group, option: any) => {
return new paper.CompoundPath(
Object.assign({ parent: paperGroup, closed: true }, option)
)
}
const renderRectangle = (paperGroup: paper.Group, option: any) => {
return new paper.Path.Rectangle(Object.assign({ parent: paperGroup }, option))
}
const renderCircle = (paperGroup: paper.Group, option: any) => {
return new paper.Path.Circle(Object.assign({ parent: paperGroup }, option))
}
const renderRaster = (paperGroup: paper.Group, option: any) => {
return new paper.Raster(Object.assign({ parent: paperGroup }, option))
}
const PaperContainer = (
props: PaperComponentProps,
ref: React.Ref<unknown> | undefined
) => {
const { forceUpdate, projectDetail, labelState, updateLoadingFlag } = props
const containerRef = useRef<any>(null)
const [imgSize, setImageSize] = useState<{
clientWidth: number
clientHeight: number
}>()
const [firstRender, setFirstRender] = useState(false)
const [confirmModalOpened, setConfirmModalOpened] = useState(false)
const [confirmModalCallback, setConfirmModalCallback] = useState<
((flag?: boolean) => void) | null
>(null)
// 监听画布容器宽高变化
useEffect(() => {
const container = containerRef.current
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.target === container) {
setImageSize({
clientWidth: entry.contentRect.width,
clientHeight: entry.contentRect.height,
})
if (!firstRender) setFirstRender(true)
}
}
})
if (container) {
resizeObserver.observe(container)
}
return () => {
if (container) {
resizeObserver.unobserve(container)
}
}
}, [firstRender])
const canvasElem = useRef<HTMLCanvasElement>(null)
const {
init,
initViewTool,
initPanTool,
initPolygonTool,
initRectangleTool,
initBrushTool,
initPointTool,
initSupportTool,
addPolygon,
addBrush,
addPointLine,
addPoint,
addCompoundPath,
addRectangle,
setRasterPath,
setRasterSize,
setRasterScale,
loadingData,
} = usePaperStore()
const { activeImage } = useBottomToolsStore()
const {
label: storeLabel,
deleteOperation,
setOperation,
getLabelDetailDataByKeys,
} = useLabelStore()
const {
objectOperations,
imageFilter,
crosshairStatus,
showTags,
showTagsConfigs,
} = useTopToolsStore()
const {
pathStatus: paperPathStatus,
selectedPath,
updateSelectedPath,
} = useObjectStore()
const {
showContextMenu,
position,
operationId,
id,
setContextMenuId,
setContextMenuOperationId,
} = useOtherToolsStore()
const { renderTag } = useRenderTag()
const [setup, setSetup] = useState(false)
const paperScope = useRef<paper.PaperScope>(null)
useEffect(() => {
if (firstRender) {
paperScope.current = new paper.PaperScope()
if (canvasElem.current) {
paperScope.current.setup(canvasElem.current)
setSetup(true)
}
}
}, [firstRender])
useEffect(() => {
const pScope = paperScope.current
if (pScope) {
const view = pScope.view
view.bounds.height = imgSize!.clientHeight
view.bounds.width = imgSize!.clientWidth
// 调整可见图层大小
const layer = pScope.project.activeLayer
layer.view.viewSize = new paper.Size(
imgSize!.clientWidth,
imgSize!.clientHeight
)
view.center = new paper.Point(
view.bounds.width / 2,
view.bounds.height / 2
)
}
}, [imgSize])
const renderSupportAnnotation = useCallback(
async (
points: Array<[number, number]>,
tags: number[],
clear_previous_state = false
) => {
try {
notifications.show({
id: "sam2",
message: "模型生成中",
loading: true,
autoClose: false,
})
const operationSchema =
projectDetail?.label_schema_list?.find(
(item) =>
item.category_id === +useTopToolsStore.getState().activeOperation
) || null
const strokeColor = operationSchema
? `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})`
: "rgb(0,0,0)"
const fillColor = operationSchema
? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)`
: "rgba(0,0,0,0.3)"
const blankColor = operationSchema
? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)`
: "rgba(0,0,0,0.01)"
const currentScale =
usePaperStore.getState().rasterScale[activeImage] ?? 1
const draw = () => {
points.forEach((point, index) => {
const drawPoint = new paper.Path.Circle({
center: point,
radius: 2,
fillColor: tags[index] === 1 ? "red" : "blue",
strokeColor: tags[index] === 1 ? "red" : "blue",
data: {
id: "support",
type: "point",
},
})
drawPoint.parent = usePaperStore.getState().group!
const raster = usePaperStore
.getState()
.group!.getItems({ data: { name: "pic" } })[0]
if (!raster.contains(drawPoint.position)) {
throw new Error("点位超出图片范围")
}
})
}
console.log(points, tags)
// 绘制点位
clearSupportAnnotationItem()
draw()
const picSize = usePaperStore.getState().rasterSize[activeImage]
const params = {
project_id: projectDetail?.id || 0,
file_name: activeImage,
image_hw:
picSize && picSize.length ? [picSize[1], picSize[0]] : [0, 0],
points_and_box: points.map(([x, y]) => [
Math.round(x / currentScale),
Math.round(y / currentScale),
]),
tags,
clear_previous_state,
}
console.log("传参", params)
try {
const res = await getAuxiliaryAnnotation(
msgpack.encode(params) as any
)
const data = msgpack.decode(new Uint8Array(res))
console.log(data)
if (data.msg === "success") {
const { contours, hollows } = data
const outerPaths: any[] = []
const innerPaths: any[] = []
contours.forEach((contour: any, index: number) => {
if (contour.length > 3) {
const path = new paper.Path({
segments: contour.map(([x, y]: [number, number]) => [
x * currentScale,
y * currentScale,
]),
fillColor: blankColor,
strokeColor: strokeColor,
closed: true,
data: {
id: "support",
type: "polygon",
isHollowPolygon: false,
fillColor,
strokeColor,
blankColor,
},
parent: usePaperStore.getState().group,
strokeScaling: false,
})
if (
Math.abs(path.area) < useTopToolsStore.getState().checkSize
) {
path.remove()
} else {
// path.scaling = usePaperStore.getState().group!.scaling;
if (hollows[index]) {
// 更新标志位
path.data.isHollowPolygon = true
innerPaths.push(path)
} else {
// 上色
path.fillColor = new paper.Color(fillColor)
outerPaths.push(path)
}
}
}
})
const compoundPath = new paper.CompoundPath({
children: [...outerPaths, ...innerPaths],
data: {
id: "support",
type: "parent",
},
strokeColor: strokeColor,
strokeWidth: 2,
fillColor: fillColor,
})
compoundPath.parent = usePaperStore.getState().group!
notifications.show({
id: "sam2",
message: "模型生成成功",
color: "green",
})
}
} catch (error) {
console.log(error)
}
} catch (error: any) {
console.log(error)
notifications.show({
id: "sam2",
message: error.message ?? "模型生成失败",
color: "red",
})
if (error.message === "点位超出图片范围") {
usePaperStore
.getState()
.supportTool?.emit("keydown", { key: "escape" })
}
}
},
[activeImage, projectDetail]
)
const renderTrackingAnnotation = useCallback(async () => {
updateLoadingFlag(true)
try {
notifications.show({
id: "sam2",
message: "模型生成中",
loading: true,
autoClose: false,
})
const ids =
useObjectStore.getState().selectedPath[
useBottomToolsStore.getState().activeImage
]
const picArr = useBottomToolsStore.getState().selectedItems
const [width, height] = usePaperStore.getState().rasterSize[
activeImage
] ?? [0, 0]
const currentScale =
usePaperStore.getState().rasterScale[activeImage] ?? 1
if (ids.length === 1) {
const id = ids[0]
let categoryId: any = null
let data: any = null
const activeImageData = useLabelStore.getState().label.get(activeImage)!
for (let [category, objArr] of activeImageData.entries()) {
objArr.forEach((item) => {
if (item[0] === id) {
categoryId = category
data = item
}
})
}
console.log(data)
if (categoryId && data) {
const contours: [number, number][] = []
const tags: boolean[] = []
data[1].forEach((item: any) => {
contours.push(
item.map(([x, y]: any) => [
Math.round(x / currentScale),
Math.round(y / currentScale),
])
)
tags.push(false)
})
data[3].forEach((item: any) => {
contours.push(
item.map(([x, y]: any) => [
Math.round(x / currentScale),
Math.round(y / currentScale),
])
)
tags.push(true)
})
const file_names = [...new Set([activeImage, ...picArr])]
const params = {
project_id: projectDetail?.id || 0,
file_names,
image_hw: [height, width],
contours,
hollows: tags,
}
console.log("传参", params)
try {
const res = await getTrackingAuxiliaryAnnotation(
msgpack.encode(params)
)
const resData = msgpack.decode(new Uint8Array(res))
console.log(resData)
if (resData.msg === "success") {
const {
future_contours,
}: {
future_contours: {
contours: [number, number][][]
hollows: boolean[]
}[]
} = resData
const nowTaskData = structuredClone(
useLabelStore.getState().label
)
future_contours.forEach(({ contours, hollows }, index) => {
const fileName = file_names[index + 1]
const imgScale =
usePaperStore.getState().rasterScale[fileName] ?? 1
let newId =
useRightToolsStore
.getState()
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1
useRightToolsStore.getState().pushPathId(newId)
// 处理点位数据
let segmentation: Array<[number, number][]> = []
let hollow_segmentation: Array<[number, number][]> = []
contours.forEach((contour, index) => {
if (!hollows[index])
segmentation.push(
contour.map(([x, y]: any) => [x * imgScale, y * imgScale])
)
else
hollow_segmentation.push(
contour.map(([x, y]: any) => [x * imgScale, y * imgScale])
)
})
// 详情数据
let detail = {
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [...useLabelStore.getState().labelDefaultComments],
create_timestamp: dayjs().unix(),
sub_attributes: {},
image_id: activeImage,
category_id: categoryId,
}
const updateData: any = [
newId,
segmentation,
detail,
hollow_segmentation,
]
if (nowTaskData.has(fileName)) {
const categoryMap = nowTaskData.get(fileName)!
if (categoryMap.has(categoryId)) {
const existArr = categoryMap.get(categoryId)
existArr?.push(updateData)
} else {
categoryMap.set(categoryId, [updateData])
}
} else {
let m = new Map()
m.set(categoryId, [updateData])
nowTaskData.set(fileName, m)
}
})
console.log(nowTaskData)
useLabelStore.getState().setLabel(nowTaskData)
useLabelStore.getState().pushStateStack(nowTaskData)
notifications.show({
id: "sam2",
message: "模型生成成功",
color: "green",
})
}
} catch (error) {
console.log(error)
}
}
} else if (!ids.length) {
notifications.show({
id: "sam2",
message: "请先选中标注对象后再进行模型调用",
color: "red",
})
} else {
notifications.show({
id: "sam2",
message: "请勿选择多个标注对象进行模型调用",
color: "red",
})
}
} catch (error) {
console.log(error)
notifications.show({
id: "sam2",
message: "模型生成失败",
color: "red",
})
} finally {
updateLoadingFlag(false)
}
}, [activeImage, projectDetail?.id, updateLoadingFlag])
// const getBase64 = (file: any): Promise<string> =>
// new Promise((resolve, reject) => {
// const reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = () => resolve(reader.result as string);
// reader.onerror = (error) => reject(error);
// });
useEffect(() => {
if (setup) {
const newGroup = new paper.Group({
applyMatrix: false,
selected: true,
strokeScaling: false,
})
if (canvasElem.current && paper) {
init(canvasElem.current, paperScope.current!, newGroup)
canvasElem.current.oncontextmenu = (e) => {
e.preventDefault()
}
let viewTool = new paper.Tool()
initViewTool(viewTool)
let panTool = new paper.Tool()
initPanTool(panTool, renderRectangle, forceUpdate)
let polygonTool = new paper.Tool()
initPolygonTool(
polygonTool,
renderPath,
renderCompoundPath,
forceUpdate
)
let rectangleTool = new paper.Tool()
initRectangleTool(rectangleTool, renderRectangle, forceUpdate)
let brushTool = new paper.Tool()
initBrushTool(brushTool, renderPath, renderCompoundPath, forceUpdate)
let pointTool = new paper.Tool()
initPointTool(pointTool, renderCircle, renderPath, forceUpdate)
let supportTool = new paper.Tool()
initSupportTool(supportTool, renderSupportAnnotation)
}
return () => {
newGroup.remove()
}
}
}, [
forceUpdate,
init,
initBrushTool,
initPanTool,
initPointTool,
initPolygonTool,
initRectangleTool,
initSupportTool,
initViewTool,
renderSupportAnnotation,
setup,
])
const [paperSelectedPath] = useState<{
// key = imageId
[key: string]: number[]
}>(selectedPath)
const getOperationSchema = useCallback(
(operationId: string) => {
return (
projectDetail?.label_schema_list?.find(
(item) => item.category_id === +operationId
) || null
)
},
[projectDetail?.label_schema_list]
)
const { getItemById, getItemsById } = usePaperStore()
const renderPolygons = useCallback(
(
operationId: string,
imageData: Map<string, [number, any[], any, any[]][]> | undefined
) => {
let operationSchema = getOperationSchema(operationId)
if (operationSchema && imageData) {
let strokeColor = `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})`
let fillColor = `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)`
let blankColor = `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)`
let paths = imageData.get(operationId)
switch (
operationSchema &&
labelTypeMap.get(operationSchema.label_type)
) {
case "多边形":
paths?.forEach((item) => {
let outerPaths =
item[1]?.map((child) => {
return addPolygon(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Point) {
return segment
} else {
return new paper.Point(segment[0], segment[1])
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
}
)
}) || []
let innerPaths =
item[3]?.map((child) => {
return addPolygon(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Point) {
return segment
} else {
return new paper.Point(segment[0], segment[1])
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
fillColor: fillColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
isHollowPolygon: true,
}
)
}) || []
addCompoundPath(
renderCompoundPath,
[...outerPaths, ...innerPaths],
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible: !paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(item[0]),
}
)
// renderCompoundPath(usePaperStore.getState().group!, { children });
// item[1].forEach((child) => {
// addPolygon(
// renderPath,
// child.map((segment: any) => {
// if (segment instanceof paper.Point) {
// return segment;
// } else {
// return new paper.Point(segment[0], segment[1]);
// }
// }),
// {
// strokeColor: strokeColor,
// strokeWidth: 2,
// // fillColor: fillColor,
// fillColor: blankColor,
// visible: !paperPathStatus[activeImage + item[0]]?.["HIDE"],
// selected: paperSelectedPath[activeImage]?.includes(item[0]),
// },
// {
// type: "polygon",
// id: item[0],
// imageId: activeImage,
// operationId: operationId,
// fillColor: fillColor,
// blankColor: blankColor,
// }
// );
// });
})
return
case "关键点":
paths?.forEach((item) => {
item[1]?.forEach((child) => {
// 保存前排好序
child.forEach((segment: any, segmentIndex: number) => {
let point =
segment instanceof paper.Segment
? new paper.Point(segment.point)
: new paper.Point(segment[0], segment[1])
addPoint(
renderCircle,
point,
{
fillColor: null,
strokeColor: strokeColor,
radius: 3,
},
{
imageId: useBottomToolsStore.getState().activeImage,
operationId: useTopToolsStore.getState().activeOperation,
text: segmentIndex + 1,
id: item[0],
attributes: item[2]?.circles?.[segmentIndex]?.attributes,
}
)
})
addPointLine(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Segment) {
return new paper.Point(segment.point)
} else {
return new paper.Point(segment[0], segment[1])
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible: !paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(item[0]),
}
)
})
})
return
case "多线段":
paths?.forEach((item) => {
let outerPaths =
item[1]?.map((child) => {
return addBrush(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Point) {
return segment
} else {
return new paper.Point(segment[0], segment[1])
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "brush",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
}
)
}) || []
addCompoundPath(
renderCompoundPath,
outerPaths,
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible: !paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "brush",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(item[0]),
}
)
})
return
case "2D框":
paths?.forEach((item) => {
item[1].forEach((child) => {
let flag = child[0] instanceof paper.Point
let p = flag
? child[1]
: new paper.Point(child[1][0], child[1][1])
let dp = flag
? child[3]
: new paper.Point(child[3][0], child[3][1])
addRectangle(
renderRectangle,
{
from: p,
to: dp,
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible: !paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "rectangle",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(item[0]),
}
)
})
})
return
default:
return
}
}
},
[
activeImage,
addBrush,
addCompoundPath,
addPoint,
addPointLine,
addPolygon,
addRectangle,
getOperationSchema,
paperPathStatus,
paperSelectedPath,
]
)
// 生成辅助标注结果数据并绘制
const saveSupportAnnotationData = useCallback(() => {
const group = usePaperStore.getState().group
if (!group) return
const compoundPath = group.getItems({
data: { id: "support", type: "parent" },
})?.[0]
if (compoundPath) {
console.log(compoundPath)
const children = compoundPath.children as paper.Path[]
const id =
useRightToolsStore
.getState()
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1
useRightToolsStore.getState().pushPathId(id)
// 处理点位数据
let segmentation: Array<[number, number][]> = []
let hollow_segmentation: Array<[number, number][]> = []
children.forEach((child) => {
if (child.data.isHollowPolygon) {
hollow_segmentation.push(
child.segments.map((seg) => [seg.point.x, seg.point.y])
)
} else {
segmentation.push(
child.segments.map((seg) => [seg.point.x, seg.point.y])
)
}
})
console.log(segmentation, hollow_segmentation)
// 处理详情数据
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() ===
useTopToolsStore.getState().activeOperation
)
let label_class = category?.label_class || ""
let subAttr = Object.assign(
{},
useTopToolsStore.getState().subAttributes[label_class]
)
let detail = {
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [...useLabelStore.getState().labelDefaultComments],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: activeImage,
category_id: Number(useTopToolsStore.getState().activeOperation),
}
// 清空support对象
clearSupportAnnotationItem()
setOperation(activeImage, useTopToolsStore.getState().activeOperation, [
id,
segmentation,
detail,
hollow_segmentation,
])
console.log(usePaperStore.getState().group)
// console.log(first)
// 更新当前页绘制数据
usePaperStore
.getState()
.group!.getItems({})
.filter((item) => item.data.id)
.forEach((item) => {
item.remove()
})
const imgMap = useLabelStore.getState().label.get(activeImage)!
for (const key of imgMap.keys()) {
renderPolygons(key, imgMap)
}
// 模型数据生成完毕 调整绘制模式
usePaperStore.getState().setPaperMode("pan")
}
}, [activeImage, renderPolygons, setOperation])
const copyAnnotationDataToMultiFrame = useCallback(() => {
const picArr = useBottomToolsStore
.getState()
.selectedItems.filter((k) => k !== activeImage)
const copyArr = useKeyboardStore.getState().copyDataIds
if (picArr.length && copyArr.length) {
const data = structuredClone(useLabelStore.getState().label)
// 先判断是否有重复的id
let existedIds: number[] = []
picArr.forEach((imageId) => {
if (data.has(imageId) && data.get(imageId)?.size) {
for (let arr of data.get(imageId)!.values()) {
arr.forEach((i) => existedIds.push(i[0]))
}
}
})
const handleData = (flag = false) => {
try {
const activeData = data.get(activeImage) ?? new Map()
const activeScale =
usePaperStore.getState().rasterScale[activeImage] ?? 1
const needCopyMap = new Map()
for (let [key, value] of activeData.entries()) {
let arr = value.filter(([id]: any) => copyArr.includes(id))
console.log(arr)
if (arr.length)
needCopyMap.set(
key,
arr.map(([id, seg, detail, hollow]: any) => [
id,
seg.map((item: any) =>
item.map((p: any) => {
if (p instanceof paper.Point) {
return [p.x / activeScale, p.y / activeScale]
} else {
return [p[0] / activeScale, p[1] / activeScale]
}
})
),
{
...detail,
uid: usePermissionStore.getState().user_id,
create_timestamp: dayjs().unix(),
first_modified_timestamp: 0,
first_modified_uid: 0,
last_modified_timestamp: 0,
last_modified_uid: 0,
},
hollow.map((item: any) =>
item.map((p: any) => {
if (paper instanceof paper.Segment) {
return [
p.point.x / activeScale,
p.point.y / activeScale,
]
} else {
return [p[0] / activeScale, p[1] / activeScale]
}
})
),
])
)
}
picArr.forEach((imageId) => {
const picScale = usePaperStore.getState().rasterScale[imageId] ?? 1
if (data.has(imageId)) {
const categoryMap = data.get(imageId)
if (categoryMap?.size) {
let needCopyIds: number[] = []
let needCopyCategoryIds: string[] = []
for (let [key, needCopyItemsArr] of needCopyMap.entries()) {
// console.log(key);
needCopyItemsArr.forEach((item: any) => {
needCopyIds.push(item[0])
needCopyCategoryIds.push(key)
})
// let arr = needCopyItemsArr.map((item: any) => [
// item[0],
// item[1].map((path: any) =>
// path.map(([x, y]: any) => [x * picScale, y * picScale])
// ),
// item[2],
// item[3].map((path: any) =>
// path.map(([x, y]: any) => [x * picScale, y * picScale])
// ),
// ]);
// needCopyMap.set(key, arr);
}
let currentImageItemIds: number[] = []
let currentImageCategoryIds: string[] = []
for (let [key, items] of categoryMap.entries()) {
items.forEach((item) => {
currentImageItemIds.push(item[0])
currentImageCategoryIds.push(key)
})
}
let finalArr: number[] = []
let finalCategory: string[] = []
needCopyIds.forEach((id, index) => {
const findIndex = currentImageItemIds.findIndex(
(i) => i === id
)
if (findIndex !== -1) {
if (flag) {
console.log(findIndex)
// 确认覆盖
finalArr.push(id)
finalCategory.push(needCopyCategoryIds[index])
let categoryId = currentImageCategoryIds[findIndex]
let arr = categoryMap.get(categoryId)!
let findIndexInArr = arr.findIndex((v) => v[0] === id)
let oldData = arr.find((a) => a[0] === id)!
let arr2 = needCopyMap.get(needCopyCategoryIds[index])!
let newData = {
...arr2.find((b: any) => b[0] === id),
uid: oldData[2].uid,
create_timestamp: oldData[2].create_timestamp,
first_modified_timestamp:
oldData[2].first_modified_timestamp || dayjs().unix(),
first_modified_uid:
oldData[2].first_modified_uid ||
usePermissionStore.getState().user_id,
last_modified_timestamp: dayjs().unix(),
last_modified_uid:
usePermissionStore.getState().user_id,
}
let updateArr = arr2.map((b: any) => {
return b[0] === id ? [b[0], b[1], newData, b[3]] : b
})
needCopyMap.set(needCopyCategoryIds[index], updateArr)
categoryMap.set(
categoryId,
arr.filter((_v, i) => i !== findIndexInArr)
)
}
} else {
finalArr.push(id)
finalCategory.push(needCopyCategoryIds[index])
}
})
finalCategory.forEach((category, index) => {
const categoryArr = categoryMap.get(category) ?? []
const copyCategoryArr = needCopyMap.get(category)!
let copyId = finalArr[index]
let newData = copyCategoryArr.find(
(copyItem: any) => copyItem[0] === copyId
)
newData = [
newData[0],
newData[1].map((path: any) =>
path.map(([x, y]: any) => [x * picScale, y * picScale])
),
newData[2],
newData[3].map((path: any) =>
path.map(([x, y]: any) => [x * picScale, y * picScale])
),
]
categoryArr.push(newData)
categoryMap.set(category, categoryArr)
})
} else {
// categoryMap实际没有值 需要重新生成
let newCategoryMap = new Map()
for (let [key, needCopyItemsArr] of needCopyMap) {
let arr = needCopyItemsArr.map((item: any) => [
item[0],
item[1].map((path: any) =>
path.map(([x, y]: any) => [x * picScale, y * picScale])
),
item[2],
item[3].map((path: any) =>
path.map(([x, y]: any) => [x * picScale, y * picScale])
),
])
newCategoryMap.set(key, arr)
}
data.set(imageId, newCategoryMap)
}
} else {
data.set(imageId, needCopyMap)
}
})
notifications.show({ message: "复制成功", color: "green" })
useLabelStore.getState().setLabel(data)
useLabelStore.getState().pushStateStack(data)
useBottomToolsStore.getState().setSelectedItems("", false, false)
setConfirmModalOpened(false)
} catch (error) {
console.log(error)
notifications.show({ message: "复制失败", color: "red" })
}
}
let flag = existedIds.find((item) => copyArr.includes(item))
if (flag) {
setConfirmModalCallback(() => handleData)
setConfirmModalOpened(true)
return
} else {
handleData()
}
}
}, [activeImage])
const [rasterInited, setRasterInited] = useState<boolean>(false)
const renderAndScaleRaster = useCallback(
async (paperGroup: paper.Group) => {
// const raster = renderRaster(paperGroup, {
// source: `${process.env.NEXT_PUBLIC_URL}/api/v1/label_server/data/image?data_name=${activeImage}&project_path=${projectDetail?.rpath}`,
// });
if (activeImage && projectDetail?.id) {
console.log(usePaperStore.getState().paperScope)
// console.log(activeImage, projectDetail?.rpath);
notifications.show({
message: "图片加载中...",
loading: true,
autoClose: false,
id: "loadingRaster",
})
try {
let raster
let text
const images = useImagesStore.getState().images
const currentImageBase64Text = images.get(activeImage)
if (currentImageBase64Text) {
text = currentImageBase64Text
} else {
const response = await getServerImage({
data_names: [activeImage],
data_type: 0,
project_id: projectDetail?.id,
})
text = `data:image/jpeg;base64,${response}`
images.set(activeImage, text)
useImagesStore.getState().setImages(images)
}
if (!text) return
raster = renderRaster(paperGroup, {
source: text,
data: {
name: "pic",
},
})
if (!raster) return
raster.onLoad = function () {
// Scale the raster to fit the canvas while maintaining aspect ratio
let canvasWidth = paperScope.current?.view.bounds.width
let canvasHeight = paperScope.current?.view.bounds.height
let scaleX = canvasWidth! / raster.bounds.width
let scaleY = canvasHeight! / raster.bounds.height
setRasterSize(activeImage, [
raster.bounds.width,
raster.bounds.height,
])
let scale = Math.min(scaleX, scaleY) // Maintain aspect ratio
// 将raster以00为中心点进行缩放
raster.scale(scale, new paper.Point(0, 0))
const currentImgScale =
usePaperStore.getState().rasterScale[activeImage]
// 当前图片无缩放比例或缩放比例不等于上次记录比例时
if (!currentImgScale) {
setRasterScale(activeImage, scale)
const newData = adjustPoints(
activeImage,
useLabelStore.getState().label,
scale
)
// 处理hollowPoints
useLabelStore.getState().setLabel(newData)
let stack = useLabelStore.getState().stateStack
if (stack.length) {
useLabelStore
.getState()
.setStateStack(
stack.map((item) => adjustPoints(activeImage, item, scale))
)
}
} else if (currentImgScale !== scale) {
// 先将相关数据恢复到原始比例
const restoreScale =
usePaperStore.getState().reciprocalRasterScale[activeImage]
const restoreData = adjustPoints(
activeImage,
useLabelStore.getState().label,
restoreScale
)
setRasterScale(activeImage, scale)
const newData = adjustPoints(activeImage, restoreData, scale)
useLabelStore.getState().setLabel(newData)
let stack = useLabelStore.getState().stateStack
if (stack.length) {
useLabelStore
.getState()
.setStateStack(
stack.map((item) => adjustPoints(activeImage, item, scale))
)
}
}
// 调整raster的左上角到00
raster.position = new paper.Point(
raster.bounds.width / 2,
raster.bounds.height / 2
)
// if (!useTopToolsStore.getState().saveCurrentScale)
usePaperStore.getState().group!.position = raster.position
setRasterPath(new paper.Path.Rectangle(raster.bounds))
// 将raster置底
raster.sendToBack()
setRasterInited(true)
console.log(usePaperStore.getState().group)
usePaperStore.getState().group?.bringToFront()
}
} catch (error) {
console.log(error)
} finally {
notifications.hide("loadingRaster")
}
}
},
[
activeImage,
projectDetail?.id,
setRasterPath,
setRasterScale,
setRasterSize,
]
)
const loadImageAndPolygons = useCallback(
async (
// imageData: Map<string, [number, any[], any][]> | undefined,
group: paper.Group
) => {
// 清除当前的 raster 和多边形
group.removeChildren()
// 添加新的 raster
setRasterInited(false)
renderAndScaleRaster(group)
// if (imageData) {
// objectOperations?.forEach((item) => {
// renderPolygons(item.category_id.toString(), imageData);
// });
// }
},
[renderAndScaleRaster]
)
useEffect(() => {
if (loadingData) return
const currentGroup = usePaperStore.getState().group
if (currentGroup && projectDetail) {
if (!useTopToolsStore.getState().saveCurrentScale) {
currentGroup.scaling = new paper.Point(1, 1)
useTopToolsStore.getState().setScale(1)
} else {
usePaperStore
.getState()
.setGroupScale(useTopToolsStore.getState().scale)
}
loadImageAndPolygons(
// storeLabel.get(activeImage),
currentGroup
)
}
}, [activeImage, loadImageAndPolygons, loadingData, projectDetail])
useEffect(() => {
if (rasterInited) {
if (storeLabel.get(activeImage)) {
objectOperations?.forEach((item) => {
renderPolygons(
item.category_id.toString(),
storeLabel.get(activeImage)
)
})
renderGroupPath()
renderTag()
}
setRasterInited(false)
}
}, [
activeImage,
objectOperations,
rasterInited,
renderPolygons,
renderTag,
storeLabel,
])
useEffect(() => {
renderGroupPath()
}, [storeLabel])
useEffect(() => {
renderTag()
}, [showTags, showTagsConfigs, storeLabel, renderTag])
const [ids, setIds] = useState<number[]>([])
const [existIds, setExistIds] = useState<number[]>([])
// select use existIds todo
false && console.log(existIds)
const [comments, setComments] = useState<string[]>([])
const form = useForm<any>({
initialValues: {
id: undefined,
operation: undefined,
sub_attributes: {},
check_box_comments: [],
text_comments: "",
},
})
const checkboxComments = form.values.check_box_comments
const textComment = form.values.text_comments
const filterOperations = useMemo(() => {
let currentLabelType = objectOperations.find(
(operation) => operation.category_id.toString() === operationId
)?.label_type
return objectOperations.filter(
(operation) => operation.label_type === currentLabelType
)
}, [objectOperations, operationId])
useEffect(() => {
if (projectDetail?.comment_rule) {
if (projectDetail?.comment_rule?.check_box_rules?.length) {
setComments(
projectDetail?.comment_rule?.check_box_rules.map((item) => item.text)
)
}
}
}, [projectDetail?.comment_rule])
const pathItem = useMemo(() => {
return storeLabel
.get(activeImage)
?.get(operationId)
?.find((item) => item[0] === id)
}, [activeImage, id, operationId, storeLabel])
// 打开弹窗时初始化form数据
useEffect(() => {
if (showContextMenu) {
form.reset()
const labelData = useLabelStore.getState().label
const currentPath = labelData
.get(activeImage)
?.get(operationId)
?.find((item) => item[0] === id)
form.setFieldValue("id", id)
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] = (value as string).split(",")
return
default:
obj[key] = value as string
return
}
})
form.setFieldValue("sub_attributes", obj)
}
if (currentPath?.[2].comment) {
const nowComment =
currentPath?.[2].comment[currentPath?.[2].comment.length - 1]
form.setFieldValue(
"check_box_comments",
nowComment && nowComment.check_box_comments.length
? nowComment.check_box_comments
: []
)
form.setFieldValue(
"text_comments",
nowComment && nowComment.text_comments.length
? nowComment.text_comments[0]
: ""
)
}
// 可选的ids
let images = Array.from(labelState.keys()).filter(
(key) => key !== activeImage
)
let ids: number[] = [id]
images.map((imageId) => {
// 可切换成其他图片同一类型的id
filterOperations.map((operation) => {
labelData
.get(imageId)
?.get(operation.category_id.toString())
?.map((arr) => arr[0])?.length &&
ids.push(
...labelData
.get(imageId)
?.get(operation.category_id.toString())
?.map((arr) => arr[0])!
)
})
})
let existIds: number[] = []
objectOperations.forEach((operation) => {
labelData
.get(activeImage)
?.get(operation.category_id.toString())
?.map((arr) => arr[0])?.length &&
existIds.push(
...labelData
.get(activeImage)
?.get(operation.category_id.toString())
?.map((arr) => arr[0])!
)
})
setExistIds(Array.from(new Set(existIds)))
ids.push(
useRightToolsStore
.getState()
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1
)
let selectIds = ids.filter((id) => !existIds.includes(id))
setIds(Array.from(new Set(selectIds)))
} else {
// console.log(form.getFieldValue("id"), id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
activeImage,
filterOperations,
getOperationSchema,
id,
labelState,
objectOperations,
operationId,
showContextMenu,
])
// 监听当前标注对象批注值变化
useEffect(() => {
if (!showContextMenu) return
if (!id || !operationId || !activeImage) return
if (useTopToolsStore.getState().currentTimeKey === "label") return
const currentDetail = useLabelStore
.getState()
.getLabelDetailDataByKeys(activeImage, operationId, id)
if (!currentDetail) return
const { comment } = currentDetail
let updatedComment: any[] = []
// 当表单当前值跟标注对象detail中comment值相同时不更新detail中comment状态
if (
checkCommentsIsSame(
comment[comment.length - 1].check_box_comments,
comment[comment.length - 1].text_comments,
checkboxComments,
textComment ? [textComment] : []
)
)
return
// 无批注
if ((!checkboxComments || !checkboxComments.length) && !textComment) {
updatedComment = comment.map((item: Comment, index: number) =>
index < comment.length - 1
? item
: {
...item,
comment_type: 1,
date: dayjs().unix(),
check_box_comments: [],
text_comments: [],
}
)
} else {
let latestItem = comment[comment.length - 1]
latestItem = {
...latestItem,
comment_type: 2,
date: dayjs().unix(),
check_box_comments:
checkboxComments && checkboxComments.length ? checkboxComments : [],
text_comments: textComment ? [textComment] : [],
}
updatedComment = comment.map((item: Comment, index: number) =>
index < comment.length - 1 ? item : latestItem
)
}
useLabelStore
.getState()
.updateLabelDetailDataByKeys(activeImage, operationId, id, {
...currentDetail,
comment: updatedComment,
})
}, [
activeImage,
checkboxComments,
id,
operationId,
showContextMenu,
textComment,
])
const newDetail = useMemo(() => {
const currentDetail = getLabelDetailDataByKeys(activeImage, operationId, id)
const detail = currentDetail
? {
...currentDetail,
first_modified_timestamp:
currentDetail.first_modified_timestamp || dayjs().unix(),
first_modified_uid:
currentDetail.first_modified_uid ||
usePermissionStore.getState().user_id,
last_modified_timestamp: dayjs().unix(),
last_modified_uid: usePermissionStore.getState().user_id,
}
: {
...initialDetail,
create_timestamp: dayjs().unix(),
}
return detail
}, [activeImage, getLabelDetailDataByKeys, id, operationId])
const handleChangeContextMenuId = useCallback(
(newId: number) => {
// 设置选中id
updateSelectedPath(activeImage, "DELETE", id)
updateSelectedPath(activeImage, "ADD", newId)
// 修改多边形data.id storelabel中 id detail
if (getItemById(id)) getItemById(id)!.data.id = newId
setOperation(activeImage, operationId, [
newId,
pathItem?.[1] || [],
newDetail,
pathItem?.[3] || [],
])
deleteOperation(activeImage, operationId, id)
// 设置菜单id
setContextMenuId(newId)
useRightToolsStore.getState().pushPathId(newId)
},
[
activeImage,
deleteOperation,
getItemById,
id,
newDetail,
operationId,
pathItem,
setContextMenuId,
setOperation,
updateSelectedPath,
]
)
const handleChangeContextMenuOperation = useCallback(
(newOperation: string) => {
const operationSchema = getOperationSchema(newOperation)
const strokeColor = operationSchema
? `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})`
: "rgb(0,0,0)"
const fillColor = operationSchema
? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)`
: "rgba(0,0,0,0.3)"
const blankColor = operationSchema
? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)`
: "rgba(0,0,0,0.01)"
if (getItemsById(id)) {
getItemsById(id).forEach((item) => {
const data = item.data
if (data.operationId) {
data.operationId = newOperation
if (data.type !== "text")
item.strokeColor = new paper.Color(strokeColor)
if (data.type !== "brush" && data.type !== "point")
item.fillColor = new paper.Color(fillColor)
item.data =
data.type !== "mask" && data.type !== "text"
? Object.assign(data || {}, {
fillColor,
blankColor,
strokeColor,
})
: data
// // 如果是组合类型 则遍历将children的data变更
// if (item instanceof paper.CompoundPath) {
// item.children.forEach((path) => {
// path.strokeColor = new paper.Color(strokeColor);
// path.fillColor = new paper.Color(fillColor);
// path.data = Object.assign(path.data || {}, {
// operationId: newOperation,
// fillColor,
// blankColor,
// strokeColor,
// });
// });
// }
}
})
}
// newOperation的subAttributes
let subAttr = Object.assign(
{},
useTopToolsStore.getState().subAttributes[
operationSchema?.label_class ?? ""
]
)
// 修改labeldata
setOperation(activeImage, newOperation, [
id,
pathItem?.[1] || [],
{ ...newDetail, sub_attributes: subAttr },
pathItem?.[3] || [],
])
deleteOperation(activeImage, operationId, id)
// 修改context的operationId
setContextMenuOperationId(newOperation)
},
[
activeImage,
deleteOperation,
getItemsById,
getOperationSchema,
id,
newDetail,
operationId,
pathItem,
setContextMenuOperationId,
setOperation,
]
)
const handleChangeContextMenuSubAttr = useCallback(
(key: string, value: string) => {
setOperation(activeImage, operationId, [
id,
pathItem?.[1] || [],
{
...newDetail,
...(pathItem?.[2] || {}),
sub_attributes: Object.assign(newDetail.sub_attributes || {}, {
[key]: value,
}),
},
pathItem?.[3] || [],
])
},
[activeImage, id, newDetail, operationId, pathItem, setOperation]
)
const contextMenu = useMemo(() => {
let operationSchema = getOperationSchema(operationId)
const currentType = useTopToolsStore.getState().currentTimeKey
return (
<Card
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
position: "absolute",
zIndex: 1000,
width: 224,
height: "fit-content",
display: !showContextMenu ? "none" : "block",
top: `${position.top}px`,
left: `${position.left}px`,
}}>
<Box p="xs">
<Stack gap="xs">
<Select
label="ID"
size="xs"
placeholder="请选择ID!"
data={ids.map((item) => item.toString())}
searchable
// creatable
// getCreateLabel={(query) => `+ Add ${query}`}
// onCreate={(query) => {
// const id = +query
// if (isNaN(id) || ids.includes(id) || existIds.includes(id))
// return null
// setIds([...ids, id])
// return query
// }}
required
{...form.getInputProps("id")}
onChange={(value) => {
form.setFieldValue("id", value)
if (value && +value !== id) {
handleChangeContextMenuId(+value)
}
}}
/>
<Select
label="类型"
size="xs"
placeholder="请选择类型!"
data={filterOperations.map((operation) => ({
label: operation.label_class,
value: operation.category_id.toString(),
}))}
leftSection={
form.values.operation &&
renderOperationIcon(
getOperationSchema(form.values.operation.toString())
)
}
required
{...form.getInputProps("operation")}
onChange={(value) => {
form.setFieldValue("operation", value)
if (value && value !== operationId) {
handleChangeContextMenuOperation(value)
}
}}
/>
<Box
p="xs"
style={{
borderRadius: "2px",
border: "1px solid var(--mantine-color-blue-light)",
}}>
<Text c="blue" fw={600} mb="xs">
</Text>
<ScrollArea.Autosize mah={48} mb="xs">
<Checkbox.Group {...form.getInputProps("check_box_comments")}>
<Stack gap="xs">
{comments.map((item) => (
<Checkbox
key={item}
value={item}
label={item}
disabled={currentType === "label"}
/>
))}
</Stack>
</Checkbox.Group>
</ScrollArea.Autosize>
<Textarea
autosize
minRows={2}
maxRows={2}
disabled={currentType === "label"}
onFocus={() => useKeyEventStore.getState().setFocusInput(true)}
onBlur={() => useKeyEventStore.getState().setFocusInput(false)}
{...form.getInputProps("text_comments")}
/>
</Box>
<Box
px="xs"
pt="xs"
style={{
borderRadius: "2px",
border: "1px solid var(--mantine-color-blue-light)",
display: !operationSchema?.sub_attributes?.length
? "none"
: "block",
}}>
{operationSchema?.sub_attributes?.length ? (
<Text c="blue" fw={600} mx="xs">
</Text>
) : null}
<ScrollArea.Autosize mah={280}>
{operationSchema?.sub_attributes_describe.map((item) => {
if (item.select_mode === 0) {
return (
<TextInput
key={item.chinese_name}
label={item.chinese_name}
size="xs"
placeholder={`请输入${item.chinese_name}`}
// required={item.isrequired}
{...form.getInputProps(
`sub_attributes.${item.chinese_name}`
)}
onChange={(e) => {
form
.getInputProps(
`sub_attributes.${item.chinese_name}`
)
.onChange(e)
handleChangeContextMenuSubAttr(
item.chinese_name,
e.target.value
)
}}
mb="xs"
/>
)
} else if (item.select_mode === 1) {
return (
<Radio.Group
key={item.chinese_name}
label={item.chinese_name}
size="xs"
// required={item.isrequired}
{...form.getInputProps(
`sub_attributes.${item.chinese_name}`
)}
onChange={(value) => {
form.setFieldValue(
`sub_attributes.${item.chinese_name}`,
value
)
handleChangeContextMenuSubAttr(
item.chinese_name,
value
)
}}
mb="xs">
<Stack gap="xs">
{item.optional_item.map((option) => (
<Radio key={option} value={option} label={option} />
))}
</Stack>
</Radio.Group>
)
} else if (item.select_mode === 2) {
return (
<Checkbox.Group
key={item.chinese_name}
label={item.chinese_name}
size="xs"
// required={item.isrequired}
{...form.getInputProps(
`sub_attributes.${item.chinese_name}`
)}
onChange={(value) => {
form.setFieldValue(
`sub_attributes.${item.chinese_name}`,
value
)
handleChangeContextMenuSubAttr(
item.chinese_name,
value.toString()
)
}}
mb="xs">
<Stack gap="xs">
{item.optional_item.map((option) => (
<Checkbox
key={option}
value={option}
label={option}
/>
))}
</Stack>
</Checkbox.Group>
)
}
return null
})}
</ScrollArea.Autosize>
</Box>
</Stack>
</Box>
</Card>
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
comments,
filterOperations,
getOperationSchema,
handleChangeContextMenuId,
handleChangeContextMenuOperation,
handleChangeContextMenuSubAttr,
id,
ids,
operationId,
position.left,
position.top,
showContextMenu,
])
const imageFilterCss = useMemo(() => {
return {
brightness: +(1 + imageFilter.brightness / 100).toFixed(2),
saturate: +(1 + imageFilter.saturate / 100).toFixed(2),
contrast: +(1 + imageFilter.contrast / 100).toFixed(2),
}
}, [imageFilter.brightness, imageFilter.contrast, imageFilter.saturate])
const handleCreatePathGroup = useCallback(() => {
let selectedPaths =
useObjectStore.getState().selectedPath[activeImage] || []
let groupId =
useRightToolsStore
.getState()
.pathGroupIds.reduce((a, b) => Math.max(a, b), 0) + 1
if (selectedPaths && selectedPaths.length > 1) {
// 修改group Map
useRightToolsStore
.getState()
.addPathGroupInMap(activeImage, groupId, selectedPaths)
// push groupId
useRightToolsStore.getState().pushPathGroupId(groupId)
}
}, [activeImage])
const crosshairComponentRef = useRef<any>(null)
const handleCrosshairMove = (event: { clientX: number; clientY: number }) => {
crosshairComponentRef.current?.updateCrosshair(event)
}
useImperativeHandle(ref, () => ({
handleCreatePathGroup,
renderPolygons,
handleCrosshairMove,
renderTrackingAnnotation,
saveSupportAnnotationData,
copyAnnotationDataToMultiFrame,
}))
return (
<Box
w="100%"
h="100%"
pos="relative"
style={{
flex: 1,
filter: `brightness(${imageFilterCss.brightness}) saturate(${imageFilterCss.saturate}) contrast(${imageFilterCss.contrast})`,
}}
id="paper-container"
ref={containerRef}>
{crosshairStatus !== "hidden" && (
<CrosshairComponent
isCarved={crosshairStatus === "carve"}
ref={crosshairComponentRef}
/>
)}
{imgSize?.clientWidth && imgSize?.clientHeight && (
<canvas
id="paper-canvas"
width={imgSize?.clientWidth}
height={imgSize?.clientHeight}
ref={canvasElem}
style={{
position: "absolute",
top: 0,
left: 0,
zIndex: 40,
minWidth: "fit-content",
maxWidth: "fit-content",
}}
/>
)}
{contextMenu}
<Modal
opened={confirmModalOpened}
onClose={() => setConfirmModalOpened(false)}
title="提示"
centered>
<Text>id的对象</Text>
<Group justify="flex-end" mt="md">
<Button
variant="default"
onClick={() => {
useBottomToolsStore.getState().setSelectedItems("", false, false)
setConfirmModalOpened(false)
}}>
</Button>
<Button
onClick={() => {
if (confirmModalCallback) confirmModalCallback()
setConfirmModalOpened(false)
}}>
</Button>
<Button
onClick={() => {
if (confirmModalCallback) confirmModalCallback(true)
setConfirmModalOpened(false)
}}>
</Button>
</Group>
</Modal>
</Box>
)
}
export default forwardRef(PaperContainer)