From 97cfd5f847aeb29427ca4aec57bea895c8b1c8f0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 08:05:08 +0000 Subject: [PATCH 01/52] chore(release): 1.4.10 [skip ci] ## @labelu/video-annotator-react [1.4.10](https://github.com/opendatalab/labelU-Kit/compare/@labelu/video-annotator-react@1.4.9...@labelu/video-annotator-react@1.4.10) (2025-04-30) ### Dependencies * **@labelu/audio-annotator-react:** upgraded to 1.8.4 --- packages/video-annotator-react/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/video-annotator-react/package.json b/packages/video-annotator-react/package.json index 568952d14..646c799d0 100644 --- a/packages/video-annotator-react/package.json +++ b/packages/video-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/video-annotator-react", - "version": "1.4.9", + "version": "1.4.10", "description": "video annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -32,7 +32,7 @@ "@labelu/components-react": "1.7.10", "@labelu/interface": "1.3.1", "@labelu/i18n": "1.0.6", - "@labelu/audio-annotator-react": "1.8.3", + "@labelu/audio-annotator-react": "1.8.4", "@labelu/video-react": "1.5.2", "polished": "^4.2.2", "react-hotkeys-hook": "^4.4.1", From 3a70e2aa2be3347b05a29ec4de6cb8f6fe03abc9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 08:05:10 +0000 Subject: [PATCH 02/52] chore(release): 1.8.4 [skip ci] ## [1.8.4](https://github.com/opendatalab/labelU-Kit/compare/@labelu/audio-annotator-react@1.8.3...@labelu/audio-annotator-react@1.8.4) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** label section style ([ab7cd56](https://github.com/opendatalab/labelU-Kit/commit/ab7cd564cf4168c078cf1b7bd603dae4639d4b43)) * **frontend:** pre-annotation is higher priority than default options ([08be836](https://github.com/opendatalab/labelU-Kit/commit/08be83672b8024269925c3a0a7a947058473a5bf)) * **image-annotator-react:** label section style ([4bc0743](https://github.com/opendatalab/labelU-Kit/commit/4bc07439825aa46e1805f1674c12b5277bd33347)) --- packages/audio-annotator-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/audio-annotator-react/package.json b/packages/audio-annotator-react/package.json index 9d89c90a7..45acca29e 100644 --- a/packages/audio-annotator-react/package.json +++ b/packages/audio-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/audio-annotator-react", - "version": "1.8.3", + "version": "1.8.4", "description": "audio annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", From 5494ae0fcbdbcd3ee7a49ff08d7ec286cc4abf48 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 08:05:12 +0000 Subject: [PATCH 03/52] chore(release): 2.4.4 [skip ci] ## [2.4.4](https://github.com/opendatalab/labelU-Kit/compare/@labelu/image-annotator-react@2.4.3...@labelu/image-annotator-react@2.4.4) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** dont open attribute modal if no annotation selected ([ccc127a](https://github.com/opendatalab/labelU-Kit/commit/ccc127aea26333356744f2f576b982181be7d27f)) * **audio-annotator-react:** label section style ([ab7cd56](https://github.com/opendatalab/labelU-Kit/commit/ab7cd564cf4168c078cf1b7bd603dae4639d4b43)) * **frontend:** pre-annotation is higher priority than default options ([08be836](https://github.com/opendatalab/labelU-Kit/commit/08be83672b8024269925c3a0a7a947058473a5bf)) * **image-annotator-react:** label section style ([4bc0743](https://github.com/opendatalab/labelU-Kit/commit/4bc07439825aa46e1805f1674c12b5277bd33347)) --- packages/image-annotator-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image-annotator-react/package.json b/packages/image-annotator-react/package.json index 4aa444de4..871c963b2 100644 --- a/packages/image-annotator-react/package.json +++ b/packages/image-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/image-annotator-react", - "version": "2.4.3", + "version": "2.4.4", "description": "image annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", From 8780135f9b2b7dc4a0ea9f6d9ea66e116ff06225 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 08:07:40 +0000 Subject: [PATCH 04/52] chore(release): 5.8.9 [skip ci] ## [5.8.9](https://github.com/opendatalab/labelU-Kit/compare/v5.8.8...v5.8.9) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** label section style ([ab7cd56](https://github.com/opendatalab/labelU-Kit/commit/ab7cd564cf4168c078cf1b7bd603dae4639d4b43)) * **frontend:** pre-annotation is higher priority than default options ([08be836](https://github.com/opendatalab/labelU-Kit/commit/08be83672b8024269925c3a0a7a947058473a5bf)) * **image-annotator-react:** label section style ([4bc0743](https://github.com/opendatalab/labelU-Kit/commit/4bc07439825aa46e1805f1674c12b5277bd33347)) --- apps/frontend/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index a910a8830..c596643df 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -5,13 +5,13 @@ "dependencies": { "@ant-design/icons": "^4.6.2", "@labelu/i18n": "1.0.6", - "@labelu/audio-annotator-react": "1.8.3", + "@labelu/audio-annotator-react": "1.8.4", "@labelu/components-react": "1.7.10", "@labelu/image": "1.4.0", "@labelu/formatter": "1.0.2", - "@labelu/image-annotator-react": "2.4.3", + "@labelu/image-annotator-react": "2.4.4", "@labelu/interface": "1.3.1", - "@labelu/video-annotator-react": "1.4.9", + "@labelu/video-annotator-react": "1.4.10", "@labelu/video-react": "1.5.2", "@tanstack/react-query": "^5.0.0", "antd": "5.10.1", From c1de2fdf0712e18dd6d22c53a79cc0278b14b396 Mon Sep 17 00:00:00 2001 From: gary Date: Wed, 30 Apr 2025 16:07:43 +0800 Subject: [PATCH 05/52] chore: update frontend package.json version to 5.8.9 [skip ci] --- apps/frontend/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index c596643df..b752cf940 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/frontend", - "version": "5.8.8", + "version": "5.8.9", "private": true, "dependencies": { "@ant-design/icons": "^4.6.2", diff --git a/package.json b/package.json index 0e3e9ad6c..3b0edd037 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "packages/*", "apps/*" ], - "version": "5.8.8", + "version": "5.8.9", "scripts": { "prepare": "husky install", "build": "pnpm --filter @labelu/interface --filter @labelu/i18n --filter @labelu/formatter --filter @labelu/image --filter @labelu/components-react --filter @labelu/image-annotator-react --filter @labelu/audio-react --filter @labelu/video-react --filter @labelu/audio-annotator-react --filter @labelu/video-annotator-react build", From a38b9318adfba13e63e6854795727d9f691f4e83 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:08:00 +0800 Subject: [PATCH 06/52] fix(frontend): pre-annotation is higher priority than default options --- .../src/utils/convertAudioAndVideoSample.ts | 13 ++++--------- apps/frontend/src/utils/convertImageSample.ts | 7 +++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/frontend/src/utils/convertAudioAndVideoSample.ts b/apps/frontend/src/utils/convertAudioAndVideoSample.ts index ddaeacac3..bebe48c53 100644 --- a/apps/frontend/src/utils/convertAudioAndVideoSample.ts +++ b/apps/frontend/src/utils/convertAudioAndVideoSample.ts @@ -2,17 +2,12 @@ import _ from 'lodash'; import type { AnnotationsWithGlobal, MediaAnnotatorConfig, MediaSample } from '@labelu/audio-annotator-react'; import type { ParsedResult, SampleResponse } from '@/api/types'; -import { MediaType, SampleState } from '@/api/types'; +import { MediaType } from '@/api/types'; import { jsonParse } from './index'; import { generateDefaultValues } from './generateGlobalToolDefaultValues'; -export function convertMediaAnnotations( - mediaType: MediaType, - result: ParsedResult, - config: MediaAnnotatorConfig, - state?: SampleState, -) { +export function convertMediaAnnotations(mediaType: MediaType, result: ParsedResult, config: MediaAnnotatorConfig) { // annotation const pool = [ ['segment', MediaType.VIDEO === mediaType ? 'videoSegmentTool' : 'audioSegmentTool'], @@ -25,7 +20,7 @@ export function convertMediaAnnotations( .map(([type, key]) => { const items = _.get(result, [key, 'result'], []); - if (!items.length && (type === 'tag' || type === 'text') && state !== SampleState.NEW) { + if (!items.length && (type === 'tag' || type === 'text')) { // 生成全局工具的默认值 return [type, generateDefaultValues(config?.[type])]; } @@ -65,6 +60,6 @@ export function convertAudioAndVideoSample( url: [MediaType.VIDEO, MediaType.AUDIO].includes(mediaType as MediaType) ? sample.file.url.replace('attachment', 'partial') : sample.file.url, - data: convertMediaAnnotations(mediaType!, resultParsed, config, sample.state), + data: convertMediaAnnotations(mediaType!, resultParsed, config), }; } diff --git a/apps/frontend/src/utils/convertImageSample.ts b/apps/frontend/src/utils/convertImageSample.ts index 6b22c9912..0b71d48ad 100644 --- a/apps/frontend/src/utils/convertImageSample.ts +++ b/apps/frontend/src/utils/convertImageSample.ts @@ -4,7 +4,7 @@ import { omit } from 'lodash/fp'; import type { ToolName } from '@labelu/image'; import { TOOL_NAMES } from '@labelu/image'; -import { SampleState, type ParsedResult, type SampleResponse } from '@/api/types'; +import type { ParsedResult, SampleResponse } from '@/api/types'; import { jsonParse } from './index'; import { generateDefaultValues } from './generateGlobalToolDefaultValues'; @@ -12,7 +12,6 @@ import { generateDefaultValues } from './generateGlobalToolDefaultValues'; export function convertImageAnnotations( result: ParsedResult, config: Pick & GlobalToolConfig, - state?: SampleState, ) { // annotation const pool = [ @@ -32,7 +31,7 @@ export function convertImageAnnotations( } const items = _.get(result, [key, 'result']) || _.get(result, [type, 'result'], []); - if (!items.length && (type === 'tag' || type === 'text') && state !== SampleState.NEW) { + if (!items.length && (type === 'tag' || type === 'text')) { // 生成全局工具的默认值 return [type, generateDefaultValues(config?.[type])]; } @@ -81,7 +80,7 @@ export function convertImageSample( return { id, url, - data: convertImageAnnotations(resultParsed, config, sample.state), + data: convertImageAnnotations(resultParsed, config), meta: _.pick(resultParsed, ['width', 'height', 'rotate']), }; } From b94154cfffd1513ee116d70c8290cad0f30a49a1 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:08:20 +0800 Subject: [PATCH 07/52] fix(audio-annotator-react): pre-annotation is higher priority than default options --- .../src/AttributePanel/index.tsx | 13 +++++++------ .../src/MediaAnnotatorWrapper.tsx | 7 ++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/audio-annotator-react/src/AttributePanel/index.tsx b/packages/audio-annotator-react/src/AttributePanel/index.tsx index c36936e45..bbaad701c 100644 --- a/packages/audio-annotator-react/src/AttributePanel/index.tsx +++ b/packages/audio-annotator-react/src/AttributePanel/index.tsx @@ -223,12 +223,13 @@ export function AttributePanel() { ...(preAnnotationsWithGlobal?.frame ?? []), ] as MediaAnnotationInUI[]; - if (!_globalAnnotations.length) { - [preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text].forEach((values) => { - if (values) { - _globalAnnotationsWithPreAnnotation.push(...(values as GlobalAnnotation[])); - } - }); + // 传入了预标注说明样本没有人工标注内容 + if (preAnnotationsWithGlobal?.tag?.length) { + _globalAnnotationsWithPreAnnotation.push(...(preAnnotationsWithGlobal?.tag as GlobalAnnotation[])); + } + + if (preAnnotationsWithGlobal?.text?.length) { + _globalAnnotationsWithPreAnnotation.push(...(preAnnotationsWithGlobal?.text as GlobalAnnotation[])); } for (const item of sortedMediaAnnotations) { diff --git a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx index c529d0a9c..c66d83a24 100644 --- a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx +++ b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx @@ -272,9 +272,6 @@ function ForwardAnnotator( const annotatorRef = useRef(null); const samples = useMemo(() => propsSamples ?? [], [propsSamples]); const selectedIndexRef = useRef(-1); - const isSampleDataEmpty = useMemo(() => { - return Object.values(currentSample?.data ?? {}).every((item) => item.length === 0); - }, [currentSample]); const labels = useMemo(() => { if (!currentTool) { return []; @@ -372,8 +369,8 @@ function ForwardAnnotator( ); const convertedAnnotations = useMemo(() => { - return convertAnnotationDataToUI(isSampleDataEmpty && preAnnotations ? preAnnotations : annotationsFromSample); - }, [annotationsFromSample, isSampleDataEmpty, preAnnotations]); + return convertAnnotationDataToUI(preAnnotations ? preAnnotations : annotationsFromSample); + }, [annotationsFromSample, preAnnotations]); // ================== sample state ================== const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = From 98672111996436ad541d556d04b5990f669a92c5 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:08:34 +0800 Subject: [PATCH 08/52] fix(image-annotator-react): pre-annotation is higher priority than default options --- .../src/AttributePanel/index.tsx | 24 ++++++++++++------- .../src/ImageAnnotator.tsx | 12 ++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/image-annotator-react/src/AttributePanel/index.tsx b/packages/image-annotator-react/src/AttributePanel/index.tsx index 8610858a4..c2e1b28e3 100644 --- a/packages/image-annotator-react/src/AttributePanel/index.tsx +++ b/packages/image-annotator-react/src/AttributePanel/index.tsx @@ -267,19 +267,25 @@ export function AttributePanel() { return _globals; }, [globalToolConfig.tag, globalToolConfig.text, preLabelMapping?.tag, preLabelMapping?.text]); - const flatGlobalAnnotations = useMemo(() => { + const flatGlobalTagAnnotations = useMemo(() => { const result = globalAnnotations; + const preResult = [] as GlobalAnnotation[]; - if (globalAnnotations.length === 0) { - [preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text].forEach((values) => { - if (values) { - result.push(...(values as GlobalAnnotation[])); - } - }); + // 传入了预标注说明样本没有人工标注内容 + if (preAnnotationsWithGlobal?.tag?.length) { + preResult.push(...(preAnnotationsWithGlobal?.tag as GlobalAnnotation[])); + } + + if (preAnnotationsWithGlobal?.text?.length) { + preResult.push(...(preAnnotationsWithGlobal?.text as GlobalAnnotation[])); + } + + if (preResult.length) { + return preResult; } return result; - }, [globalAnnotations, preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text]); + }, [globalAnnotations, preAnnotationsWithGlobal]); const titles = useMemo(() => { const _titles = []; @@ -450,7 +456,7 @@ export function AttributePanel() { })} - + diff --git a/packages/image-annotator-react/src/ImageAnnotator.tsx b/packages/image-annotator-react/src/ImageAnnotator.tsx index ca6cf8215..4345cc26d 100644 --- a/packages/image-annotator-react/src/ImageAnnotator.tsx +++ b/packages/image-annotator-react/src/ImageAnnotator.tsx @@ -199,10 +199,6 @@ function ForwardAnnotator( setCurrentSample(editingSample || samples?.[0]); }, [editingSample, samples, setCurrentSample]); - const isSampleDataEmpty = useMemo(() => { - return Object.values(currentSample?.data ?? {}).every((item) => item.length === 0); - }, [currentSample]); - // ================== tool ================== const containerRef = useRef(null); const [currentTool, setCurrentTool] = useState(propsSelectedTool); @@ -344,7 +340,7 @@ function ForwardAnnotator( } }); - if (isSampleDataEmpty) { + if (preAnnotations) { Object.keys(preAnnotations ?? {}).forEach((key) => { if (TOOL_NAMES.includes(key as ToolName)) { engine?.loadData( @@ -360,7 +356,7 @@ function ForwardAnnotator( } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [annotationsFromSample, config, currentSample, engine, isSampleDataEmpty, preAnnotations, tools]); + }, [annotationsFromSample, config, currentSample, engine, preAnnotations, tools]); const selectedIndexRef = useRef(-1); @@ -392,7 +388,7 @@ function ForwardAnnotator( const _data = currentSample?.data ?? {}; const _preData = preAnnotations ?? {}; - if (isSampleDataEmpty) { + if (preAnnotations) { Object.keys(_preData).forEach((key) => { _preData[key as AllAnnotationType]?.forEach((item) => { mapping[item.id] = { @@ -413,7 +409,7 @@ function ForwardAnnotator( }); return mapping; - }, [currentSample?.data, isSampleDataEmpty, preAnnotations]); + }, [currentSample?.data, preAnnotations]); const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = useRedoUndo(annotationsMapping, { From 9deba027929b7b2976109ee1e80dc39912da877c Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:19:07 +0800 Subject: [PATCH 09/52] update(audio-annotator-react): update --- .../src/MediaAnnotatorWrapper.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx index c66d83a24..1a22962ae 100644 --- a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx +++ b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx @@ -272,6 +272,13 @@ function ForwardAnnotator( const annotatorRef = useRef(null); const samples = useMemo(() => propsSamples ?? [], [propsSamples]); const selectedIndexRef = useRef(-1); + const isPreAnnotationEmpty = useMemo(() => { + if (typeof preAnnotations === 'undefined') { + return true; + } + + return Object.values(preAnnotations).every((item) => item.length === 0); + }, [preAnnotations]); const labels = useMemo(() => { if (!currentTool) { return []; @@ -369,8 +376,8 @@ function ForwardAnnotator( ); const convertedAnnotations = useMemo(() => { - return convertAnnotationDataToUI(preAnnotations ? preAnnotations : annotationsFromSample); - }, [annotationsFromSample, preAnnotations]); + return convertAnnotationDataToUI(!isPreAnnotationEmpty ? preAnnotations! : annotationsFromSample); + }, [annotationsFromSample, isPreAnnotationEmpty, preAnnotations]); // ================== sample state ================== const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = From 54f337de4379b1359efe5a796ae7526bbf153404 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:22:11 +0800 Subject: [PATCH 10/52] update(image-annotator-react): update --- .../image-annotator-react/src/ImageAnnotator.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/image-annotator-react/src/ImageAnnotator.tsx b/packages/image-annotator-react/src/ImageAnnotator.tsx index 4345cc26d..fc138c93e 100644 --- a/packages/image-annotator-react/src/ImageAnnotator.tsx +++ b/packages/image-annotator-react/src/ImageAnnotator.tsx @@ -194,6 +194,13 @@ function ForwardAnnotator( // remember last tool const memorizeToolLabel = useRef>({} as Record); const [attributeModalOpen, setAttributeModalOpen] = useState(false); + const isPreAnnotationEmpty = useMemo(() => { + if (typeof preAnnotations === 'undefined') { + return true; + } + + return Object.values(preAnnotations).every((item) => item.length === 0); + }, [preAnnotations]); useEffect(() => { setCurrentSample(editingSample || samples?.[0]); @@ -340,7 +347,7 @@ function ForwardAnnotator( } }); - if (preAnnotations) { + if (!isPreAnnotationEmpty) { Object.keys(preAnnotations ?? {}).forEach((key) => { if (TOOL_NAMES.includes(key as ToolName)) { engine?.loadData( @@ -356,7 +363,7 @@ function ForwardAnnotator( } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [annotationsFromSample, config, currentSample, engine, preAnnotations, tools]); + }, [annotationsFromSample, config, currentSample, engine, preAnnotations, tools, isPreAnnotationEmpty]); const selectedIndexRef = useRef(-1); @@ -388,7 +395,7 @@ function ForwardAnnotator( const _data = currentSample?.data ?? {}; const _preData = preAnnotations ?? {}; - if (preAnnotations) { + if (!isPreAnnotationEmpty) { Object.keys(_preData).forEach((key) => { _preData[key as AllAnnotationType]?.forEach((item) => { mapping[item.id] = { @@ -409,7 +416,7 @@ function ForwardAnnotator( }); return mapping; - }, [currentSample?.data, preAnnotations]); + }, [currentSample?.data, isPreAnnotationEmpty, preAnnotations]); const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = useRedoUndo(annotationsMapping, { From bae052fb8ccc4efa05f29b10e01dbfe573e45779 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:08:00 +0800 Subject: [PATCH 11/52] fix(frontend): pre-annotation is higher priority than default options --- .../src/utils/convertAudioAndVideoSample.ts | 13 ++++--------- apps/frontend/src/utils/convertImageSample.ts | 7 +++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/frontend/src/utils/convertAudioAndVideoSample.ts b/apps/frontend/src/utils/convertAudioAndVideoSample.ts index ddaeacac3..bebe48c53 100644 --- a/apps/frontend/src/utils/convertAudioAndVideoSample.ts +++ b/apps/frontend/src/utils/convertAudioAndVideoSample.ts @@ -2,17 +2,12 @@ import _ from 'lodash'; import type { AnnotationsWithGlobal, MediaAnnotatorConfig, MediaSample } from '@labelu/audio-annotator-react'; import type { ParsedResult, SampleResponse } from '@/api/types'; -import { MediaType, SampleState } from '@/api/types'; +import { MediaType } from '@/api/types'; import { jsonParse } from './index'; import { generateDefaultValues } from './generateGlobalToolDefaultValues'; -export function convertMediaAnnotations( - mediaType: MediaType, - result: ParsedResult, - config: MediaAnnotatorConfig, - state?: SampleState, -) { +export function convertMediaAnnotations(mediaType: MediaType, result: ParsedResult, config: MediaAnnotatorConfig) { // annotation const pool = [ ['segment', MediaType.VIDEO === mediaType ? 'videoSegmentTool' : 'audioSegmentTool'], @@ -25,7 +20,7 @@ export function convertMediaAnnotations( .map(([type, key]) => { const items = _.get(result, [key, 'result'], []); - if (!items.length && (type === 'tag' || type === 'text') && state !== SampleState.NEW) { + if (!items.length && (type === 'tag' || type === 'text')) { // 生成全局工具的默认值 return [type, generateDefaultValues(config?.[type])]; } @@ -65,6 +60,6 @@ export function convertAudioAndVideoSample( url: [MediaType.VIDEO, MediaType.AUDIO].includes(mediaType as MediaType) ? sample.file.url.replace('attachment', 'partial') : sample.file.url, - data: convertMediaAnnotations(mediaType!, resultParsed, config, sample.state), + data: convertMediaAnnotations(mediaType!, resultParsed, config), }; } diff --git a/apps/frontend/src/utils/convertImageSample.ts b/apps/frontend/src/utils/convertImageSample.ts index 6b22c9912..0b71d48ad 100644 --- a/apps/frontend/src/utils/convertImageSample.ts +++ b/apps/frontend/src/utils/convertImageSample.ts @@ -4,7 +4,7 @@ import { omit } from 'lodash/fp'; import type { ToolName } from '@labelu/image'; import { TOOL_NAMES } from '@labelu/image'; -import { SampleState, type ParsedResult, type SampleResponse } from '@/api/types'; +import type { ParsedResult, SampleResponse } from '@/api/types'; import { jsonParse } from './index'; import { generateDefaultValues } from './generateGlobalToolDefaultValues'; @@ -12,7 +12,6 @@ import { generateDefaultValues } from './generateGlobalToolDefaultValues'; export function convertImageAnnotations( result: ParsedResult, config: Pick & GlobalToolConfig, - state?: SampleState, ) { // annotation const pool = [ @@ -32,7 +31,7 @@ export function convertImageAnnotations( } const items = _.get(result, [key, 'result']) || _.get(result, [type, 'result'], []); - if (!items.length && (type === 'tag' || type === 'text') && state !== SampleState.NEW) { + if (!items.length && (type === 'tag' || type === 'text')) { // 生成全局工具的默认值 return [type, generateDefaultValues(config?.[type])]; } @@ -81,7 +80,7 @@ export function convertImageSample( return { id, url, - data: convertImageAnnotations(resultParsed, config, sample.state), + data: convertImageAnnotations(resultParsed, config), meta: _.pick(resultParsed, ['width', 'height', 'rotate']), }; } From a1b3f169f10bd50542a92d900afe7dde1caa2cbb Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:08:20 +0800 Subject: [PATCH 12/52] fix(audio-annotator-react): pre-annotation is higher priority than default options --- .../src/AttributePanel/index.tsx | 13 +++++++------ .../src/MediaAnnotatorWrapper.tsx | 7 ++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/audio-annotator-react/src/AttributePanel/index.tsx b/packages/audio-annotator-react/src/AttributePanel/index.tsx index c36936e45..bbaad701c 100644 --- a/packages/audio-annotator-react/src/AttributePanel/index.tsx +++ b/packages/audio-annotator-react/src/AttributePanel/index.tsx @@ -223,12 +223,13 @@ export function AttributePanel() { ...(preAnnotationsWithGlobal?.frame ?? []), ] as MediaAnnotationInUI[]; - if (!_globalAnnotations.length) { - [preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text].forEach((values) => { - if (values) { - _globalAnnotationsWithPreAnnotation.push(...(values as GlobalAnnotation[])); - } - }); + // 传入了预标注说明样本没有人工标注内容 + if (preAnnotationsWithGlobal?.tag?.length) { + _globalAnnotationsWithPreAnnotation.push(...(preAnnotationsWithGlobal?.tag as GlobalAnnotation[])); + } + + if (preAnnotationsWithGlobal?.text?.length) { + _globalAnnotationsWithPreAnnotation.push(...(preAnnotationsWithGlobal?.text as GlobalAnnotation[])); } for (const item of sortedMediaAnnotations) { diff --git a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx index c529d0a9c..c66d83a24 100644 --- a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx +++ b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx @@ -272,9 +272,6 @@ function ForwardAnnotator( const annotatorRef = useRef(null); const samples = useMemo(() => propsSamples ?? [], [propsSamples]); const selectedIndexRef = useRef(-1); - const isSampleDataEmpty = useMemo(() => { - return Object.values(currentSample?.data ?? {}).every((item) => item.length === 0); - }, [currentSample]); const labels = useMemo(() => { if (!currentTool) { return []; @@ -372,8 +369,8 @@ function ForwardAnnotator( ); const convertedAnnotations = useMemo(() => { - return convertAnnotationDataToUI(isSampleDataEmpty && preAnnotations ? preAnnotations : annotationsFromSample); - }, [annotationsFromSample, isSampleDataEmpty, preAnnotations]); + return convertAnnotationDataToUI(preAnnotations ? preAnnotations : annotationsFromSample); + }, [annotationsFromSample, preAnnotations]); // ================== sample state ================== const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = From b325cfa700701437e120fd07177fc950c4051b76 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:08:34 +0800 Subject: [PATCH 13/52] fix(image-annotator-react): pre-annotation is higher priority than default options --- .../src/AttributePanel/index.tsx | 24 ++++++++++++------- .../src/ImageAnnotator.tsx | 12 ++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/image-annotator-react/src/AttributePanel/index.tsx b/packages/image-annotator-react/src/AttributePanel/index.tsx index 8610858a4..c2e1b28e3 100644 --- a/packages/image-annotator-react/src/AttributePanel/index.tsx +++ b/packages/image-annotator-react/src/AttributePanel/index.tsx @@ -267,19 +267,25 @@ export function AttributePanel() { return _globals; }, [globalToolConfig.tag, globalToolConfig.text, preLabelMapping?.tag, preLabelMapping?.text]); - const flatGlobalAnnotations = useMemo(() => { + const flatGlobalTagAnnotations = useMemo(() => { const result = globalAnnotations; + const preResult = [] as GlobalAnnotation[]; - if (globalAnnotations.length === 0) { - [preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text].forEach((values) => { - if (values) { - result.push(...(values as GlobalAnnotation[])); - } - }); + // 传入了预标注说明样本没有人工标注内容 + if (preAnnotationsWithGlobal?.tag?.length) { + preResult.push(...(preAnnotationsWithGlobal?.tag as GlobalAnnotation[])); + } + + if (preAnnotationsWithGlobal?.text?.length) { + preResult.push(...(preAnnotationsWithGlobal?.text as GlobalAnnotation[])); + } + + if (preResult.length) { + return preResult; } return result; - }, [globalAnnotations, preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text]); + }, [globalAnnotations, preAnnotationsWithGlobal]); const titles = useMemo(() => { const _titles = []; @@ -450,7 +456,7 @@ export function AttributePanel() { })} - + diff --git a/packages/image-annotator-react/src/ImageAnnotator.tsx b/packages/image-annotator-react/src/ImageAnnotator.tsx index ca6cf8215..4345cc26d 100644 --- a/packages/image-annotator-react/src/ImageAnnotator.tsx +++ b/packages/image-annotator-react/src/ImageAnnotator.tsx @@ -199,10 +199,6 @@ function ForwardAnnotator( setCurrentSample(editingSample || samples?.[0]); }, [editingSample, samples, setCurrentSample]); - const isSampleDataEmpty = useMemo(() => { - return Object.values(currentSample?.data ?? {}).every((item) => item.length === 0); - }, [currentSample]); - // ================== tool ================== const containerRef = useRef(null); const [currentTool, setCurrentTool] = useState(propsSelectedTool); @@ -344,7 +340,7 @@ function ForwardAnnotator( } }); - if (isSampleDataEmpty) { + if (preAnnotations) { Object.keys(preAnnotations ?? {}).forEach((key) => { if (TOOL_NAMES.includes(key as ToolName)) { engine?.loadData( @@ -360,7 +356,7 @@ function ForwardAnnotator( } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [annotationsFromSample, config, currentSample, engine, isSampleDataEmpty, preAnnotations, tools]); + }, [annotationsFromSample, config, currentSample, engine, preAnnotations, tools]); const selectedIndexRef = useRef(-1); @@ -392,7 +388,7 @@ function ForwardAnnotator( const _data = currentSample?.data ?? {}; const _preData = preAnnotations ?? {}; - if (isSampleDataEmpty) { + if (preAnnotations) { Object.keys(_preData).forEach((key) => { _preData[key as AllAnnotationType]?.forEach((item) => { mapping[item.id] = { @@ -413,7 +409,7 @@ function ForwardAnnotator( }); return mapping; - }, [currentSample?.data, isSampleDataEmpty, preAnnotations]); + }, [currentSample?.data, preAnnotations]); const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = useRedoUndo(annotationsMapping, { From 424a2dbc1ef985be31ec539e7e4ebecfbf39ecba Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:19:07 +0800 Subject: [PATCH 14/52] update(audio-annotator-react): update --- .../src/MediaAnnotatorWrapper.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx index c66d83a24..1a22962ae 100644 --- a/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx +++ b/packages/audio-annotator-react/src/MediaAnnotatorWrapper.tsx @@ -272,6 +272,13 @@ function ForwardAnnotator( const annotatorRef = useRef(null); const samples = useMemo(() => propsSamples ?? [], [propsSamples]); const selectedIndexRef = useRef(-1); + const isPreAnnotationEmpty = useMemo(() => { + if (typeof preAnnotations === 'undefined') { + return true; + } + + return Object.values(preAnnotations).every((item) => item.length === 0); + }, [preAnnotations]); const labels = useMemo(() => { if (!currentTool) { return []; @@ -369,8 +376,8 @@ function ForwardAnnotator( ); const convertedAnnotations = useMemo(() => { - return convertAnnotationDataToUI(preAnnotations ? preAnnotations : annotationsFromSample); - }, [annotationsFromSample, preAnnotations]); + return convertAnnotationDataToUI(!isPreAnnotationEmpty ? preAnnotations! : annotationsFromSample); + }, [annotationsFromSample, isPreAnnotationEmpty, preAnnotations]); // ================== sample state ================== const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = From d00a72c2a1b1b0305d798cb067f5eaca4d524aca Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 17:22:11 +0800 Subject: [PATCH 15/52] update(image-annotator-react): update --- .../image-annotator-react/src/ImageAnnotator.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/image-annotator-react/src/ImageAnnotator.tsx b/packages/image-annotator-react/src/ImageAnnotator.tsx index 4345cc26d..fc138c93e 100644 --- a/packages/image-annotator-react/src/ImageAnnotator.tsx +++ b/packages/image-annotator-react/src/ImageAnnotator.tsx @@ -194,6 +194,13 @@ function ForwardAnnotator( // remember last tool const memorizeToolLabel = useRef>({} as Record); const [attributeModalOpen, setAttributeModalOpen] = useState(false); + const isPreAnnotationEmpty = useMemo(() => { + if (typeof preAnnotations === 'undefined') { + return true; + } + + return Object.values(preAnnotations).every((item) => item.length === 0); + }, [preAnnotations]); useEffect(() => { setCurrentSample(editingSample || samples?.[0]); @@ -340,7 +347,7 @@ function ForwardAnnotator( } }); - if (preAnnotations) { + if (!isPreAnnotationEmpty) { Object.keys(preAnnotations ?? {}).forEach((key) => { if (TOOL_NAMES.includes(key as ToolName)) { engine?.loadData( @@ -356,7 +363,7 @@ function ForwardAnnotator( } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [annotationsFromSample, config, currentSample, engine, preAnnotations, tools]); + }, [annotationsFromSample, config, currentSample, engine, preAnnotations, tools, isPreAnnotationEmpty]); const selectedIndexRef = useRef(-1); @@ -388,7 +395,7 @@ function ForwardAnnotator( const _data = currentSample?.data ?? {}; const _preData = preAnnotations ?? {}; - if (preAnnotations) { + if (!isPreAnnotationEmpty) { Object.keys(_preData).forEach((key) => { _preData[key as AllAnnotationType]?.forEach((item) => { mapping[item.id] = { @@ -409,7 +416,7 @@ function ForwardAnnotator( }); return mapping; - }, [currentSample?.data, preAnnotations]); + }, [currentSample?.data, isPreAnnotationEmpty, preAnnotations]); const [annotationsWithGlobal, updateAnnotationsWithGlobal, redo, undo, pastRef, futureRef, reset] = useRedoUndo(annotationsMapping, { From 6575807a5249d4b1271e2c14546d4c6ed44d41a7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 09:24:50 +0000 Subject: [PATCH 16/52] chore(release): 1.4.11 [skip ci] ## @labelu/video-annotator-react [1.4.11](https://github.com/opendatalab/labelU-Kit/compare/@labelu/video-annotator-react@1.4.10...@labelu/video-annotator-react@1.4.11) (2025-04-30) ### Dependencies * **@labelu/audio-annotator-react:** upgraded to 1.8.5 --- packages/video-annotator-react/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/video-annotator-react/package.json b/packages/video-annotator-react/package.json index 646c799d0..21497a3fa 100644 --- a/packages/video-annotator-react/package.json +++ b/packages/video-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/video-annotator-react", - "version": "1.4.10", + "version": "1.4.11", "description": "video annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -32,7 +32,7 @@ "@labelu/components-react": "1.7.10", "@labelu/interface": "1.3.1", "@labelu/i18n": "1.0.6", - "@labelu/audio-annotator-react": "1.8.4", + "@labelu/audio-annotator-react": "1.8.5", "@labelu/video-react": "1.5.2", "polished": "^4.2.2", "react-hotkeys-hook": "^4.4.1", From 50dc0be05ced272c526bb3389b076fbdf8fb24ee Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 09:24:54 +0000 Subject: [PATCH 17/52] chore(release): 1.8.5 [skip ci] ## [1.8.5](https://github.com/opendatalab/labelU-Kit/compare/@labelu/audio-annotator-react@1.8.4...@labelu/audio-annotator-react@1.8.5) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** pre-annotation is higher priority than default options ([a1b3f16](https://github.com/opendatalab/labelU-Kit/commit/a1b3f169f10bd50542a92d900afe7dde1caa2cbb)) * **frontend:** pre-annotation is higher priority than default options ([bae052f](https://github.com/opendatalab/labelU-Kit/commit/bae052fb8ccc4efa05f29b10e01dbfe573e45779)) * **image-annotator-react:** pre-annotation is higher priority than default options ([b325cfa](https://github.com/opendatalab/labelU-Kit/commit/b325cfa700701437e120fd07177fc950c4051b76)) --- packages/audio-annotator-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/audio-annotator-react/package.json b/packages/audio-annotator-react/package.json index 45acca29e..13188a97f 100644 --- a/packages/audio-annotator-react/package.json +++ b/packages/audio-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/audio-annotator-react", - "version": "1.8.4", + "version": "1.8.5", "description": "audio annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", From ad65d460179a9a2abb9b52464b005da3e8387d54 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 09:24:57 +0000 Subject: [PATCH 18/52] chore(release): 2.4.5 [skip ci] ## [2.4.5](https://github.com/opendatalab/labelU-Kit/compare/@labelu/image-annotator-react@2.4.4...@labelu/image-annotator-react@2.4.5) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** pre-annotation is higher priority than default options ([a1b3f16](https://github.com/opendatalab/labelU-Kit/commit/a1b3f169f10bd50542a92d900afe7dde1caa2cbb)) * **frontend:** pre-annotation is higher priority than default options ([bae052f](https://github.com/opendatalab/labelU-Kit/commit/bae052fb8ccc4efa05f29b10e01dbfe573e45779)) * **image-annotator-react:** pre-annotation is higher priority than default options ([b325cfa](https://github.com/opendatalab/labelU-Kit/commit/b325cfa700701437e120fd07177fc950c4051b76)) --- packages/image-annotator-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image-annotator-react/package.json b/packages/image-annotator-react/package.json index 871c963b2..86955c717 100644 --- a/packages/image-annotator-react/package.json +++ b/packages/image-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/image-annotator-react", - "version": "2.4.4", + "version": "2.4.5", "description": "image annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", From 2e02e98e6dc921627eaf472c7eef5090e348d7c2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 09:27:30 +0000 Subject: [PATCH 19/52] chore(release): 5.8.10 [skip ci] ## [5.8.10](https://github.com/opendatalab/labelU-Kit/compare/v5.8.9...v5.8.10) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** pre-annotation is higher priority than default options ([a1b3f16](https://github.com/opendatalab/labelU-Kit/commit/a1b3f169f10bd50542a92d900afe7dde1caa2cbb)) * **frontend:** pre-annotation is higher priority than default options ([bae052f](https://github.com/opendatalab/labelU-Kit/commit/bae052fb8ccc4efa05f29b10e01dbfe573e45779)) * **image-annotator-react:** pre-annotation is higher priority than default options ([b325cfa](https://github.com/opendatalab/labelU-Kit/commit/b325cfa700701437e120fd07177fc950c4051b76)) --- apps/frontend/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index b752cf940..3fa1cefd0 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -5,13 +5,13 @@ "dependencies": { "@ant-design/icons": "^4.6.2", "@labelu/i18n": "1.0.6", - "@labelu/audio-annotator-react": "1.8.4", + "@labelu/audio-annotator-react": "1.8.5", "@labelu/components-react": "1.7.10", "@labelu/image": "1.4.0", "@labelu/formatter": "1.0.2", - "@labelu/image-annotator-react": "2.4.4", + "@labelu/image-annotator-react": "2.4.5", "@labelu/interface": "1.3.1", - "@labelu/video-annotator-react": "1.4.10", + "@labelu/video-annotator-react": "1.4.11", "@labelu/video-react": "1.5.2", "@tanstack/react-query": "^5.0.0", "antd": "5.10.1", From 5ba904b0f651dd3e911d71398904dda1da2d2700 Mon Sep 17 00:00:00 2001 From: gary Date: Wed, 30 Apr 2025 17:27:34 +0800 Subject: [PATCH 20/52] chore: update frontend package.json version to 5.8.10 [skip ci] --- apps/frontend/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 3fa1cefd0..f57f3e033 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/frontend", - "version": "5.8.9", + "version": "5.8.10", "private": true, "dependencies": { "@ant-design/icons": "^4.6.2", diff --git a/package.json b/package.json index 3b0edd037..72dd6504f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "packages/*", "apps/*" ], - "version": "5.8.9", + "version": "5.8.10", "scripts": { "prepare": "husky install", "build": "pnpm --filter @labelu/interface --filter @labelu/i18n --filter @labelu/formatter --filter @labelu/image --filter @labelu/components-react --filter @labelu/image-annotator-react --filter @labelu/audio-react --filter @labelu/video-react --filter @labelu/audio-annotator-react --filter @labelu/video-annotator-react build", From 83a6c290281d681f2ef208a2b0266e06da42b47c Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 19:04:45 +0800 Subject: [PATCH 21/52] fix(frontend): default value onchange --- .../annotationRightCorner/index.tsx | 20 ++++++++++++++++++- .../pages/tasks.[id].samples.[id]/index.tsx | 12 +++++------ .../src/utils/convertAudioAndVideoSample.ts | 18 ++++------------- apps/frontend/src/utils/convertImageSample.ts | 19 ++++-------------- 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/apps/frontend/src/pages/tasks.[id].samples.[id]/components/annotationRightCorner/index.tsx b/apps/frontend/src/pages/tasks.[id].samples.[id]/components/annotationRightCorner/index.tsx index 0dd362649..e316547e7 100644 --- a/apps/frontend/src/pages/tasks.[id].samples.[id]/components/annotationRightCorner/index.tsx +++ b/apps/frontend/src/pages/tasks.[id].samples.[id]/components/annotationRightCorner/index.tsx @@ -18,6 +18,7 @@ import { updateSampleState, updateSampleAnnotationResult } from '@/api/services/ import { message } from '@/StaticAnt'; import useMe from '@/hooks/useMe'; import { UserAvatar } from '@/components/UserAvatar'; +import { generateDefaultValues } from '@/utils/generateGlobalToolDefaultValues'; import AnnotationContext from '../../annotation.context'; @@ -247,6 +248,23 @@ const AnnotationRightCorner = ({ noSave, fetchNext, totalSize }: AnnotationRight innerSample = await audioAnnotationRef?.current?.getSample(); } + // 全局标注没有值的话,填充默认值 + if (!result.tagTool?.result?.length) { + const tagConfig = task.config.tools.find((tool) => tool.tool === 'tagTool'); + result.tagTool = { + toolName: 'tagTool', + result: generateDefaultValues(tagConfig?.config.attributes), + }; + } + + if (!result.textTool?.result?.length) { + const textConfig = task.config.tools.find((tool) => tool.tool === 'textTool'); + result.textTool = { + toolName: 'textTool', + result: generateDefaultValues(textConfig?.config.attributes), + }; + } + // 防止sampleid保存错乱,使用标注时传入的sampleid const body = set('data.result')(JSON.stringify(result))(currentSample); @@ -255,7 +273,7 @@ const AnnotationRightCorner = ({ noSave, fetchNext, totalSize }: AnnotationRight annotated_count: getAnnotationCount(body.data!.result), state: SampleState.DONE, }); - }, [currentSample, isMeTheCurrentUser, noSave, task.media_type, taskId]); + }, [currentSample, isMeTheCurrentUser, noSave, task?.config.tools, task?.media_type, taskId]); const handleComplete = useCallback(async () => { await saveCurrentSample(); diff --git a/apps/frontend/src/pages/tasks.[id].samples.[id]/index.tsx b/apps/frontend/src/pages/tasks.[id].samples.[id]/index.tsx index 9df3ba9d0..909a11d1b 100644 --- a/apps/frontend/src/pages/tasks.[id].samples.[id]/index.tsx +++ b/apps/frontend/src/pages/tasks.[id].samples.[id]/index.tsx @@ -101,13 +101,13 @@ const AnnotationPage = () => { } if (task?.media_type === MediaType.IMAGE) { - return convertImageAnnotations(_annotations, preAnnotationConfig); + return convertImageAnnotations(_annotations); } else if (task?.media_type === MediaType.VIDEO || task?.media_type === MediaType.AUDIO) { - return convertMediaAnnotations(task.media_type, _annotations, preAnnotationConfig); + return convertMediaAnnotations(task.media_type, _annotations); } return {}; - }, [preAnnotation, preAnnotationConfig, task?.media_type]); + }, [preAnnotation, task?.media_type]); const [searchParams] = useSearchParams(); const taskConfig = _.get(task, 'config'); @@ -204,11 +204,11 @@ const AnnotationPage = () => { const editingSample = useMemo(() => { if (task?.media_type === MediaType.IMAGE) { - return convertImageSample(sample?.data, editorConfig); + return convertImageSample(sample?.data); } else if (task?.media_type === MediaType.VIDEO || task?.media_type === MediaType.AUDIO) { - return convertAudioAndVideoSample(sample?.data, editorConfig, task.media_type); + return convertAudioAndVideoSample(sample?.data, task.media_type); } - }, [editorConfig, sample?.data, task?.media_type]); + }, [sample?.data, task?.media_type]); const renderSidebar = useMemo(() => { return () => leftSiderContent; diff --git a/apps/frontend/src/utils/convertAudioAndVideoSample.ts b/apps/frontend/src/utils/convertAudioAndVideoSample.ts index bebe48c53..1a202e7fc 100644 --- a/apps/frontend/src/utils/convertAudioAndVideoSample.ts +++ b/apps/frontend/src/utils/convertAudioAndVideoSample.ts @@ -1,13 +1,12 @@ import _ from 'lodash'; -import type { AnnotationsWithGlobal, MediaAnnotatorConfig, MediaSample } from '@labelu/audio-annotator-react'; +import type { AnnotationsWithGlobal, MediaSample } from '@labelu/audio-annotator-react'; import type { ParsedResult, SampleResponse } from '@/api/types'; import { MediaType } from '@/api/types'; import { jsonParse } from './index'; -import { generateDefaultValues } from './generateGlobalToolDefaultValues'; -export function convertMediaAnnotations(mediaType: MediaType, result: ParsedResult, config: MediaAnnotatorConfig) { +export function convertMediaAnnotations(mediaType: MediaType, result: ParsedResult) { // annotation const pool = [ ['segment', MediaType.VIDEO === mediaType ? 'videoSegmentTool' : 'audioSegmentTool'], @@ -20,11 +19,6 @@ export function convertMediaAnnotations(mediaType: MediaType, result: ParsedResu .map(([type, key]) => { const items = _.get(result, [key, 'result'], []); - if (!items.length && (type === 'tag' || type === 'text')) { - // 生成全局工具的默认值 - return [type, generateDefaultValues(config?.[type])]; - } - return [ type, _.map(items, (item) => { @@ -39,11 +33,7 @@ export function convertMediaAnnotations(mediaType: MediaType, result: ParsedResu .value() as AnnotationsWithGlobal; } -export function convertAudioAndVideoSample( - sample: SampleResponse, - config: MediaAnnotatorConfig, - mediaType?: MediaType, -): MediaSample | undefined { +export function convertAudioAndVideoSample(sample: SampleResponse, mediaType?: MediaType): MediaSample | undefined { if (!sample) { return; } @@ -60,6 +50,6 @@ export function convertAudioAndVideoSample( url: [MediaType.VIDEO, MediaType.AUDIO].includes(mediaType as MediaType) ? sample.file.url.replace('attachment', 'partial') : sample.file.url, - data: convertMediaAnnotations(mediaType!, resultParsed, config), + data: convertMediaAnnotations(mediaType!, resultParsed), }; } diff --git a/apps/frontend/src/utils/convertImageSample.ts b/apps/frontend/src/utils/convertImageSample.ts index 0b71d48ad..a68ebb49b 100644 --- a/apps/frontend/src/utils/convertImageSample.ts +++ b/apps/frontend/src/utils/convertImageSample.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import type { GlobalToolConfig, ImageAnnotatorOptions, ImageSample } from '@labelu/image-annotator-react'; +import type { ImageSample } from '@labelu/image-annotator-react'; import { omit } from 'lodash/fp'; import type { ToolName } from '@labelu/image'; import { TOOL_NAMES } from '@labelu/image'; @@ -7,12 +7,8 @@ import { TOOL_NAMES } from '@labelu/image'; import type { ParsedResult, SampleResponse } from '@/api/types'; import { jsonParse } from './index'; -import { generateDefaultValues } from './generateGlobalToolDefaultValues'; -export function convertImageAnnotations( - result: ParsedResult, - config: Pick & GlobalToolConfig, -) { +export function convertImageAnnotations(result: ParsedResult) { // annotation const pool = [ ['line', 'lineTool'], @@ -31,10 +27,6 @@ export function convertImageAnnotations( } const items = _.get(result, [key, 'result']) || _.get(result, [type, 'result'], []); - if (!items.length && (type === 'tag' || type === 'text')) { - // 生成全局工具的默认值 - return [type, generateDefaultValues(config?.[type])]; - } return [ type, @@ -61,10 +53,7 @@ export function convertImageAnnotations( .value(); } -export function convertImageSample( - sample: SampleResponse | undefined, - config: Pick & GlobalToolConfig, -): ImageSample | undefined { +export function convertImageSample(sample: SampleResponse | undefined): ImageSample | undefined { if (!sample) { return; } @@ -80,7 +69,7 @@ export function convertImageSample( return { id, url, - data: convertImageAnnotations(resultParsed, config), + data: convertImageAnnotations(resultParsed), meta: _.pick(resultParsed, ['width', 'height', 'rotate']), }; } From a358393f9558964e36ebb051333c6b32bc21d9b5 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 19:05:05 +0800 Subject: [PATCH 22/52] fix(audio-annotator-react): default value onchange --- .../src/AttributePanel/index.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/audio-annotator-react/src/AttributePanel/index.tsx b/packages/audio-annotator-react/src/AttributePanel/index.tsx index bbaad701c..0013d1778 100644 --- a/packages/audio-annotator-react/src/AttributePanel/index.tsx +++ b/packages/audio-annotator-react/src/AttributePanel/index.tsx @@ -209,6 +209,7 @@ export function AttributePanel() { } = useAnnotationCtx(); const [collapsed, setCollapsed] = useState(false); const { t } = useTranslation(); + const [modified, setModified] = useState(false); const { globalAnnotations, globalAnnotationsWithPreAnnotation, mediaAnnotationGroup, defaultActiveKeys } = useMemo(() => { @@ -223,13 +224,12 @@ export function AttributePanel() { ...(preAnnotationsWithGlobal?.frame ?? []), ] as MediaAnnotationInUI[]; - // 传入了预标注说明样本没有人工标注内容 - if (preAnnotationsWithGlobal?.tag?.length) { - _globalAnnotationsWithPreAnnotation.push(...(preAnnotationsWithGlobal?.tag as GlobalAnnotation[])); - } - - if (preAnnotationsWithGlobal?.text?.length) { - _globalAnnotationsWithPreAnnotation.push(...(preAnnotationsWithGlobal?.text as GlobalAnnotation[])); + if (!_globalAnnotations.length && !modified) { + [preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text].forEach((values) => { + if (values) { + _globalAnnotationsWithPreAnnotation.push(...(values as GlobalAnnotation[])); + } + }); } for (const item of sortedMediaAnnotations) { @@ -262,6 +262,7 @@ export function AttributePanel() { preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text, sortedMediaAnnotations, + modified, ]); const globals = useMemo(() => { @@ -400,6 +401,8 @@ export function AttributePanel() { return; } + setModified(true); + onAnnotationClear(); }; From 54436a82d56cac4ebd474995d95a999a1ff3b8db Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 19:05:17 +0800 Subject: [PATCH 23/52] fix(components-react): default value onchange --- packages/components-react/src/AttributeTree/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components-react/src/AttributeTree/index.tsx b/packages/components-react/src/AttributeTree/index.tsx index 8670c07f0..35fc5835b 100644 --- a/packages/components-react/src/AttributeTree/index.tsx +++ b/packages/components-react/src/AttributeTree/index.tsx @@ -194,12 +194,13 @@ export function AttributeTree({ data, config, onChange, className, disabled }: A }); tagConfig?.forEach((item) => { + const defaultValue = item.options.filter((option) => option.isDefault).map((option) => option.value); if (!_tagData[item.value]) { _tagData[item.value] = { id: uid(), type: 'tag', value: { - [item.value]: [], + [item.value]: defaultValue, }, } as TagAnnotationEntity; } @@ -211,7 +212,7 @@ export function AttributeTree({ data, config, onChange, className, disabled }: A id: uid(), type: 'text', value: { - [item.value]: '', + [item.value]: item.defaultValue, }, } as TextAnnotationEntity; } From bdf62e7c971bffbad2269d5ad96e09de90a9a6ad Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 19:05:28 +0800 Subject: [PATCH 24/52] fix(image-annotator-react): default value onchange --- .../src/AttributePanel/index.tsx | 24 +++++++++---------- .../src/ImageAnnotator.tsx | 1 - 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/image-annotator-react/src/AttributePanel/index.tsx b/packages/image-annotator-react/src/AttributePanel/index.tsx index c2e1b28e3..6309a969c 100644 --- a/packages/image-annotator-react/src/AttributePanel/index.tsx +++ b/packages/image-annotator-react/src/AttributePanel/index.tsx @@ -213,11 +213,13 @@ export function AttributePanel() { disabled, } = useAnnotationCtx(); const [collapsed, setCollapsed] = useState(false); + const [modified, setModified] = useState(false); const globalAnnotations = useMemo(() => { return Object.values(annotationsWithGlobal).filter((item) => ['text', 'tag'].includes((item as GlobalAnnotation).type), ) as GlobalAnnotation[]; }, [annotationsWithGlobal]); + // @ts-ignore const { t } = useTranslation(); @@ -269,23 +271,17 @@ export function AttributePanel() { const flatGlobalTagAnnotations = useMemo(() => { const result = globalAnnotations; - const preResult = [] as GlobalAnnotation[]; - - // 传入了预标注说明样本没有人工标注内容 - if (preAnnotationsWithGlobal?.tag?.length) { - preResult.push(...(preAnnotationsWithGlobal?.tag as GlobalAnnotation[])); - } - - if (preAnnotationsWithGlobal?.text?.length) { - preResult.push(...(preAnnotationsWithGlobal?.text as GlobalAnnotation[])); - } - if (preResult.length) { - return preResult; + if (globalAnnotations.length === 0 && !modified) { + [preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text].forEach((values) => { + if (values) { + result.push(...(values as GlobalAnnotation[])); + } + }); } return result; - }, [globalAnnotations, preAnnotationsWithGlobal]); + }, [globalAnnotations, preAnnotationsWithGlobal?.tag, preAnnotationsWithGlobal?.text, modified]); const titles = useMemo(() => { const _titles = []; @@ -392,6 +388,8 @@ export function AttributePanel() { return; } + setModified(true); + onAnnotationClear(); if (activeKey === 'label') { engine?.clearData(); diff --git a/packages/image-annotator-react/src/ImageAnnotator.tsx b/packages/image-annotator-react/src/ImageAnnotator.tsx index fc138c93e..b24af2141 100644 --- a/packages/image-annotator-react/src/ImageAnnotator.tsx +++ b/packages/image-annotator-react/src/ImageAnnotator.tsx @@ -593,7 +593,6 @@ function ForwardAnnotator( // 删除标记 const handleDelete = (annotation: AnnotationData) => { const newAnnotations = omit(annotationsWithGlobal, annotation.id); - console.log('newAnnotations', newAnnotations); updateAnnotationsWithGlobal(newAnnotations); setSelectedAnnotation((pre) => { if (pre?.id === annotation.id) { From fd0295065dd25a155e51af8d4e307ae40b6be0c3 Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Wed, 30 Apr 2025 19:42:32 +0800 Subject: [PATCH 25/52] fix(components-react): types --- .../components-react/src/AttributeTree/index.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/components-react/src/AttributeTree/index.tsx b/packages/components-react/src/AttributeTree/index.tsx index 35fc5835b..6774021c0 100644 --- a/packages/components-react/src/AttributeTree/index.tsx +++ b/packages/components-react/src/AttributeTree/index.tsx @@ -1,4 +1,10 @@ -import type { InnerAttribute, TagAnnotationEntity, TextAnnotationEntity, TextAttribute } from '@labelu/interface'; +import type { + EnumerableAttribute, + InnerAttribute, + TagAnnotationEntity, + TextAnnotationEntity, + TextAttribute, +} from '@labelu/interface'; import type { CollapseProps } from 'rc-collapse'; import Collapse from 'rc-collapse'; import { useEffect, useMemo } from 'react'; @@ -194,7 +200,9 @@ export function AttributeTree({ data, config, onChange, className, disabled }: A }); tagConfig?.forEach((item) => { - const defaultValue = item.options.filter((option) => option.isDefault).map((option) => option.value); + const defaultValue = (item as EnumerableAttribute).options + .filter((option) => option.isDefault) + .map((option) => option.value); if (!_tagData[item.value]) { _tagData[item.value] = { id: uid(), @@ -212,7 +220,7 @@ export function AttributeTree({ data, config, onChange, className, disabled }: A id: uid(), type: 'text', value: { - [item.value]: item.defaultValue, + [item.value]: (item as TextAttribute).defaultValue, }, } as TextAnnotationEntity; } From 4959c35fb1c1faca383be8e6e3f8ec4ce9c914dc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:48:10 +0000 Subject: [PATCH 26/52] chore(release): 1.5.3 [skip ci] ## @labelu/audio-react [1.5.3](https://github.com/opendatalab/labelU-Kit/compare/@labelu/audio-react@1.5.2...@labelu/audio-react@1.5.3) (2025-04-30) ### Dependencies * **@labelu/components-react:** upgraded to 1.7.11 --- packages/audio-react/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/audio-react/package.json b/packages/audio-react/package.json index 8227379a3..585509750 100644 --- a/packages/audio-react/package.json +++ b/packages/audio-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/audio-react", - "version": "1.5.2", + "version": "1.5.3", "description": "labelu audio annotation component for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -40,7 +40,7 @@ "vite-tsconfig-paths": "^3.5.0" }, "dependencies": { - "@labelu/components-react": "1.7.10", + "@labelu/components-react": "1.7.11", "polished": "^4.2.2", "react-hotkeys-hook": "^4.4.1", "styled-components": "^6.1.15", From c51994f8509c436c6d18c95e2a3973e2c67898b0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:48:13 +0000 Subject: [PATCH 27/52] chore(release): 1.8.6 [skip ci] ## [1.8.6](https://github.com/opendatalab/labelU-Kit/compare/@labelu/audio-annotator-react@1.8.5...@labelu/audio-annotator-react@1.8.6) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** default value onchange ([a358393](https://github.com/opendatalab/labelU-Kit/commit/a358393f9558964e36ebb051333c6b32bc21d9b5)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([b94154c](https://github.com/opendatalab/labelU-Kit/commit/b94154cfffd1513ee116d70c8290cad0f30a49a1)) * **components-react:** default value onchange ([54436a8](https://github.com/opendatalab/labelU-Kit/commit/54436a82d56cac4ebd474995d95a999a1ff3b8db)) * **components-react:** types ([fd02950](https://github.com/opendatalab/labelU-Kit/commit/fd0295065dd25a155e51af8d4e307ae40b6be0c3)) * **frontend:** default value onchange ([83a6c29](https://github.com/opendatalab/labelU-Kit/commit/83a6c290281d681f2ef208a2b0266e06da42b47c)) * **frontend:** pre-annotation is higher priority than default options ([a38b931](https://github.com/opendatalab/labelU-Kit/commit/a38b9318adfba13e63e6854795727d9f691f4e83)) * **image-annotator-react:** default value onchange ([bdf62e7](https://github.com/opendatalab/labelU-Kit/commit/bdf62e7c971bffbad2269d5ad96e09de90a9a6ad)) * **image-annotator-react:** pre-annotation is higher priority than default options ([9867211](https://github.com/opendatalab/labelU-Kit/commit/98672111996436ad541d556d04b5990f669a92c5)) --- packages/audio-annotator-react/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/audio-annotator-react/package.json b/packages/audio-annotator-react/package.json index 13188a97f..734bb7af7 100644 --- a/packages/audio-annotator-react/package.json +++ b/packages/audio-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/audio-annotator-react", - "version": "1.8.5", + "version": "1.8.6", "description": "audio annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -29,8 +29,8 @@ ], "dependencies": { "@labelu/i18n": "1.0.6", - "@labelu/audio-react": "1.5.2", - "@labelu/components-react": "1.7.10", + "@labelu/audio-react": "1.5.3", + "@labelu/components-react": "1.7.11", "@labelu/interface": "1.3.1", "lodash.clonedeep": "^4.5.0", "polished": "^4.2.2", From ad2f768bfa1b5c2a3a330fa1604a5ff791be72bf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:48:16 +0000 Subject: [PATCH 28/52] chore(release): 1.7.11 [skip ci] ## [1.7.11](https://github.com/opendatalab/labelU-Kit/compare/@labelu/components-react@1.7.10...@labelu/components-react@1.7.11) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** default value onchange ([a358393](https://github.com/opendatalab/labelU-Kit/commit/a358393f9558964e36ebb051333c6b32bc21d9b5)) * **audio-annotator-react:** dont open attribute modal if no annotation selected ([ccc127a](https://github.com/opendatalab/labelU-Kit/commit/ccc127aea26333356744f2f576b982181be7d27f)) * **audio-annotator-react:** label section style ([ab7cd56](https://github.com/opendatalab/labelU-Kit/commit/ab7cd564cf4168c078cf1b7bd603dae4639d4b43)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([a1b3f16](https://github.com/opendatalab/labelU-Kit/commit/a1b3f169f10bd50542a92d900afe7dde1caa2cbb)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([b94154c](https://github.com/opendatalab/labelU-Kit/commit/b94154cfffd1513ee116d70c8290cad0f30a49a1)) * **components-react:** default value onchange ([54436a8](https://github.com/opendatalab/labelU-Kit/commit/54436a82d56cac4ebd474995d95a999a1ff3b8db)) * **components-react:** types ([fd02950](https://github.com/opendatalab/labelU-Kit/commit/fd0295065dd25a155e51af8d4e307ae40b6be0c3)) * **frontend:** default value onchange ([83a6c29](https://github.com/opendatalab/labelU-Kit/commit/83a6c290281d681f2ef208a2b0266e06da42b47c)) * **frontend:** pre-annotation is higher priority than default options ([bae052f](https://github.com/opendatalab/labelU-Kit/commit/bae052fb8ccc4efa05f29b10e01dbfe573e45779)) * **frontend:** pre-annotation is higher priority than default options ([a38b931](https://github.com/opendatalab/labelU-Kit/commit/a38b9318adfba13e63e6854795727d9f691f4e83)) * **frontend:** pre-annotation is higher priority than default options ([08be836](https://github.com/opendatalab/labelU-Kit/commit/08be83672b8024269925c3a0a7a947058473a5bf)) * **image-annotator-react:** default value onchange ([bdf62e7](https://github.com/opendatalab/labelU-Kit/commit/bdf62e7c971bffbad2269d5ad96e09de90a9a6ad)) * **image-annotator-react:** label section style ([4bc0743](https://github.com/opendatalab/labelU-Kit/commit/4bc07439825aa46e1805f1674c12b5277bd33347)) * **image-annotator-react:** pre-annotation is higher priority than default options ([b325cfa](https://github.com/opendatalab/labelU-Kit/commit/b325cfa700701437e120fd07177fc950c4051b76)) * **image-annotator-react:** pre-annotation is higher priority than default options ([9867211](https://github.com/opendatalab/labelU-Kit/commit/98672111996436ad541d556d04b5990f669a92c5)) --- packages/components-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components-react/package.json b/packages/components-react/package.json index 14028ee7f..008ffe3ba 100644 --- a/packages/components-react/package.json +++ b/packages/components-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/components-react", - "version": "1.7.10", + "version": "1.7.11", "description": "basic react components for labelU", "main": "./dist/index.mjs", "module": "./dist/index.mjs", From f4355f373ec0f9ebed097fe9c99eb0d0705d3a83 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:48:19 +0000 Subject: [PATCH 29/52] chore(release): 2.4.6 [skip ci] ## [2.4.6](https://github.com/opendatalab/labelU-Kit/compare/@labelu/image-annotator-react@2.4.5...@labelu/image-annotator-react@2.4.6) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** default value onchange ([a358393](https://github.com/opendatalab/labelU-Kit/commit/a358393f9558964e36ebb051333c6b32bc21d9b5)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([b94154c](https://github.com/opendatalab/labelU-Kit/commit/b94154cfffd1513ee116d70c8290cad0f30a49a1)) * **components-react:** default value onchange ([54436a8](https://github.com/opendatalab/labelU-Kit/commit/54436a82d56cac4ebd474995d95a999a1ff3b8db)) * **components-react:** types ([fd02950](https://github.com/opendatalab/labelU-Kit/commit/fd0295065dd25a155e51af8d4e307ae40b6be0c3)) * **frontend:** default value onchange ([83a6c29](https://github.com/opendatalab/labelU-Kit/commit/83a6c290281d681f2ef208a2b0266e06da42b47c)) * **frontend:** pre-annotation is higher priority than default options ([a38b931](https://github.com/opendatalab/labelU-Kit/commit/a38b9318adfba13e63e6854795727d9f691f4e83)) * **image-annotator-react:** default value onchange ([bdf62e7](https://github.com/opendatalab/labelU-Kit/commit/bdf62e7c971bffbad2269d5ad96e09de90a9a6ad)) * **image-annotator-react:** pre-annotation is higher priority than default options ([9867211](https://github.com/opendatalab/labelU-Kit/commit/98672111996436ad541d556d04b5990f669a92c5)) --- packages/image-annotator-react/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image-annotator-react/package.json b/packages/image-annotator-react/package.json index 86955c717..2d38eb70b 100644 --- a/packages/image-annotator-react/package.json +++ b/packages/image-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/image-annotator-react", - "version": "2.4.5", + "version": "2.4.6", "description": "image annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -29,7 +29,7 @@ "react" ], "dependencies": { - "@labelu/components-react": "1.7.10", + "@labelu/components-react": "1.7.11", "@labelu/image": "1.4.0", "@labelu/interface": "1.3.1", "@labelu/i18n": "1.0.6", From db669fb33a878e40928008bb8900eebec5b3905c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:48:21 +0000 Subject: [PATCH 30/52] chore(release): 1.4.12 [skip ci] ## [1.4.12](https://github.com/opendatalab/labelU-Kit/compare/@labelu/video-annotator-react@1.4.11...@labelu/video-annotator-react@1.4.12) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** default value onchange ([a358393](https://github.com/opendatalab/labelU-Kit/commit/a358393f9558964e36ebb051333c6b32bc21d9b5)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([b94154c](https://github.com/opendatalab/labelU-Kit/commit/b94154cfffd1513ee116d70c8290cad0f30a49a1)) * **components-react:** default value onchange ([54436a8](https://github.com/opendatalab/labelU-Kit/commit/54436a82d56cac4ebd474995d95a999a1ff3b8db)) * **components-react:** types ([fd02950](https://github.com/opendatalab/labelU-Kit/commit/fd0295065dd25a155e51af8d4e307ae40b6be0c3)) * **frontend:** default value onchange ([83a6c29](https://github.com/opendatalab/labelU-Kit/commit/83a6c290281d681f2ef208a2b0266e06da42b47c)) * **frontend:** pre-annotation is higher priority than default options ([a38b931](https://github.com/opendatalab/labelU-Kit/commit/a38b9318adfba13e63e6854795727d9f691f4e83)) * **image-annotator-react:** default value onchange ([bdf62e7](https://github.com/opendatalab/labelU-Kit/commit/bdf62e7c971bffbad2269d5ad96e09de90a9a6ad)) * **image-annotator-react:** pre-annotation is higher priority than default options ([9867211](https://github.com/opendatalab/labelU-Kit/commit/98672111996436ad541d556d04b5990f669a92c5)) --- packages/video-annotator-react/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/video-annotator-react/package.json b/packages/video-annotator-react/package.json index 21497a3fa..488951306 100644 --- a/packages/video-annotator-react/package.json +++ b/packages/video-annotator-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/video-annotator-react", - "version": "1.4.11", + "version": "1.4.12", "description": "video annotator for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -29,11 +29,11 @@ "react" ], "dependencies": { - "@labelu/components-react": "1.7.10", + "@labelu/components-react": "1.7.11", "@labelu/interface": "1.3.1", "@labelu/i18n": "1.0.6", - "@labelu/audio-annotator-react": "1.8.5", - "@labelu/video-react": "1.5.2", + "@labelu/audio-annotator-react": "1.8.6", + "@labelu/video-react": "1.5.3", "polished": "^4.2.2", "react-hotkeys-hook": "^4.4.1", "styled-components": "^6.1.15" From 851675e0f85c9b2f6f18ca22e0debc693d98239a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:48:24 +0000 Subject: [PATCH 31/52] chore(release): 1.5.3 [skip ci] ## [1.5.3](https://github.com/opendatalab/labelU-Kit/compare/@labelu/video-react@1.5.2...@labelu/video-react@1.5.3) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** default value onchange ([a358393](https://github.com/opendatalab/labelU-Kit/commit/a358393f9558964e36ebb051333c6b32bc21d9b5)) * **audio-annotator-react:** dont open attribute modal if no annotation selected ([ccc127a](https://github.com/opendatalab/labelU-Kit/commit/ccc127aea26333356744f2f576b982181be7d27f)) * **audio-annotator-react:** label section style ([ab7cd56](https://github.com/opendatalab/labelU-Kit/commit/ab7cd564cf4168c078cf1b7bd603dae4639d4b43)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([a1b3f16](https://github.com/opendatalab/labelU-Kit/commit/a1b3f169f10bd50542a92d900afe7dde1caa2cbb)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([b94154c](https://github.com/opendatalab/labelU-Kit/commit/b94154cfffd1513ee116d70c8290cad0f30a49a1)) * **components-react:** default value onchange ([54436a8](https://github.com/opendatalab/labelU-Kit/commit/54436a82d56cac4ebd474995d95a999a1ff3b8db)) * **components-react:** types ([fd02950](https://github.com/opendatalab/labelU-Kit/commit/fd0295065dd25a155e51af8d4e307ae40b6be0c3)) * **frontend:** default value onchange ([83a6c29](https://github.com/opendatalab/labelU-Kit/commit/83a6c290281d681f2ef208a2b0266e06da42b47c)) * **frontend:** pre-annotation is higher priority than default options ([bae052f](https://github.com/opendatalab/labelU-Kit/commit/bae052fb8ccc4efa05f29b10e01dbfe573e45779)) * **frontend:** pre-annotation is higher priority than default options ([a38b931](https://github.com/opendatalab/labelU-Kit/commit/a38b9318adfba13e63e6854795727d9f691f4e83)) * **frontend:** pre-annotation is higher priority than default options ([08be836](https://github.com/opendatalab/labelU-Kit/commit/08be83672b8024269925c3a0a7a947058473a5bf)) * **image-annotator-react:** default value onchange ([bdf62e7](https://github.com/opendatalab/labelU-Kit/commit/bdf62e7c971bffbad2269d5ad96e09de90a9a6ad)) * **image-annotator-react:** label section style ([4bc0743](https://github.com/opendatalab/labelU-Kit/commit/4bc07439825aa46e1805f1674c12b5277bd33347)) * **image-annotator-react:** pre-annotation is higher priority than default options ([b325cfa](https://github.com/opendatalab/labelU-Kit/commit/b325cfa700701437e120fd07177fc950c4051b76)) * **image-annotator-react:** pre-annotation is higher priority than default options ([9867211](https://github.com/opendatalab/labelU-Kit/commit/98672111996436ad541d556d04b5990f669a92c5)) --- packages/video-react/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/video-react/package.json b/packages/video-react/package.json index 64719e72f..d687244ad 100644 --- a/packages/video-react/package.json +++ b/packages/video-react/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/video-react", - "version": "1.5.2", + "version": "1.5.3", "description": "labelu video annotation component for react", "main": "./dist/index.mjs", "module": "./dist/index.mjs", @@ -39,7 +39,7 @@ "styled-components": "^6.1.15" }, "dependencies": { - "@labelu/components-react": "1.7.10", + "@labelu/components-react": "1.7.11", "polished": "^4.2.2", "rc-tooltip": "^6.0.1", "react-hotkeys-hook": "^4.4.1", From 6ab41681e436e7cb07c4105230f278af66fa748d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Apr 2025 11:51:09 +0000 Subject: [PATCH 32/52] chore(release): 5.8.11 [skip ci] ## [5.8.11](https://github.com/opendatalab/labelU-Kit/compare/v5.8.10...v5.8.11) (2025-04-30) ### Bug Fixes * **audio-annotator-react:** default value onchange ([a358393](https://github.com/opendatalab/labelU-Kit/commit/a358393f9558964e36ebb051333c6b32bc21d9b5)) * **audio-annotator-react:** pre-annotation is higher priority than default options ([b94154c](https://github.com/opendatalab/labelU-Kit/commit/b94154cfffd1513ee116d70c8290cad0f30a49a1)) * **components-react:** default value onchange ([54436a8](https://github.com/opendatalab/labelU-Kit/commit/54436a82d56cac4ebd474995d95a999a1ff3b8db)) * **components-react:** types ([fd02950](https://github.com/opendatalab/labelU-Kit/commit/fd0295065dd25a155e51af8d4e307ae40b6be0c3)) * **frontend:** default value onchange ([83a6c29](https://github.com/opendatalab/labelU-Kit/commit/83a6c290281d681f2ef208a2b0266e06da42b47c)) * **frontend:** pre-annotation is higher priority than default options ([a38b931](https://github.com/opendatalab/labelU-Kit/commit/a38b9318adfba13e63e6854795727d9f691f4e83)) * **image-annotator-react:** default value onchange ([bdf62e7](https://github.com/opendatalab/labelU-Kit/commit/bdf62e7c971bffbad2269d5ad96e09de90a9a6ad)) * **image-annotator-react:** pre-annotation is higher priority than default options ([9867211](https://github.com/opendatalab/labelU-Kit/commit/98672111996436ad541d556d04b5990f669a92c5)) --- apps/frontend/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index f57f3e033..7ccea78e8 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -5,14 +5,14 @@ "dependencies": { "@ant-design/icons": "^4.6.2", "@labelu/i18n": "1.0.6", - "@labelu/audio-annotator-react": "1.8.5", - "@labelu/components-react": "1.7.10", + "@labelu/audio-annotator-react": "1.8.6", + "@labelu/components-react": "1.7.11", "@labelu/image": "1.4.0", "@labelu/formatter": "1.0.2", - "@labelu/image-annotator-react": "2.4.5", + "@labelu/image-annotator-react": "2.4.6", "@labelu/interface": "1.3.1", - "@labelu/video-annotator-react": "1.4.11", - "@labelu/video-react": "1.5.2", + "@labelu/video-annotator-react": "1.4.12", + "@labelu/video-react": "1.5.3", "@tanstack/react-query": "^5.0.0", "antd": "5.10.1", "axios": "^1.3.4", From b54eddd53644ec09620eb6052e6ab7d2ca3c7a6b Mon Sep 17 00:00:00 2001 From: gary Date: Wed, 30 Apr 2025 19:51:13 +0800 Subject: [PATCH 33/52] chore: update frontend package.json version to 5.8.11 [skip ci] --- apps/frontend/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 7ccea78e8..40d576573 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/frontend", - "version": "5.8.10", + "version": "5.8.11", "private": true, "dependencies": { "@ant-design/icons": "^4.6.2", diff --git a/package.json b/package.json index 72dd6504f..91e1b03bd 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "packages/*", "apps/*" ], - "version": "5.8.10", + "version": "5.8.11", "scripts": { "prepare": "husky install", "build": "pnpm --filter @labelu/interface --filter @labelu/i18n --filter @labelu/formatter --filter @labelu/image --filter @labelu/components-react --filter @labelu/image-annotator-react --filter @labelu/audio-react --filter @labelu/video-react --filter @labelu/audio-annotator-react --filter @labelu/video-annotator-react build", From 72d796ef57c69b0bf985215efb2072c7e59b21da Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Mon, 19 May 2025 19:44:47 +0800 Subject: [PATCH 34/52] fix(frontend): fix pagesize --- apps/frontend/src/pages/tasks/index.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/frontend/src/pages/tasks/index.tsx b/apps/frontend/src/pages/tasks/index.tsx index 056b18de6..1408be6da 100644 --- a/apps/frontend/src/pages/tasks/index.tsx +++ b/apps/frontend/src/pages/tasks/index.tsx @@ -6,7 +6,6 @@ import { FlexLayout } from '@labelu/components-react'; import { useTranslation } from '@labelu/i18n'; import type { TaskListResponseWithStatics } from '@/api/types'; -import { usePageSize } from '@/hooks/usePageSize'; import { ResponsiveGrid } from '@/components/ResponsiveGrid'; import TaskCard from './components/taskCard'; @@ -44,12 +43,10 @@ const TaskList = () => { const tasks = _.get(tasksResponse, 'data', []); const meta_data = _.get(tasksResponse, 'meta_data'); const labeluVersion = _.get(routerLoaderData, ['headers', 'labelu-version']); - const pageSize = usePageSize(); const { t, i18n } = useTranslation(); - const [searchParams, setSearchParams] = useSearchParams({ - size: String(pageSize), - }); + const [searchParams, setSearchParams] = useSearchParams(); + const finalPageSize = searchParams.get('size') ? +searchParams.get('size')! : 16; const createTask = () => { navigate('/tasks/0/edit?isNew=true'); @@ -122,11 +119,11 @@ const TaskList = () => { labelu@{labeluVersion} - {meta_data && searchParams && meta_data?.total > pageSize && ( + {meta_data && searchParams && meta_data?.total > finalPageSize && ( { searchParams.set('size', String(_pageSize)); searchParams.set('page', String(value)); From 8973699f9e959ea3fa96981e53ad2fd0069ae2ed Mon Sep 17 00:00:00 2001 From: gary Date: Mon, 19 May 2025 19:50:33 +0800 Subject: [PATCH 35/52] chore: update frontend package.json version to 5.8.12 [skip ci] --- apps/frontend/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 40d576573..dbdbcf7ed 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@labelu/frontend", - "version": "5.8.11", + "version": "5.8.12", "private": true, "dependencies": { "@ant-design/icons": "^4.6.2", diff --git a/package.json b/package.json index 91e1b03bd..c0012a65a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "packages/*", "apps/*" ], - "version": "5.8.11", + "version": "5.8.12", "scripts": { "prepare": "husky install", "build": "pnpm --filter @labelu/interface --filter @labelu/i18n --filter @labelu/formatter --filter @labelu/image --filter @labelu/components-react --filter @labelu/image-annotator-react --filter @labelu/audio-react --filter @labelu/video-react --filter @labelu/audio-annotator-react --filter @labelu/video-annotator-react build", From ecc5c74745f56f23945dee46f71e9e455718f35f Mon Sep 17 00:00:00 2001 From: gary-Shen Date: Thu, 3 Jul 2025 15:51:35 +0800 Subject: [PATCH 36/52] feat(image): add relation annotation tool and line arrow type --- packages/image/src/Annotator.ts | 4 +- packages/image/src/AnnotatorBase.ts | 8 + packages/image/src/annotations/Annotation.ts | 19 +- .../image/src/annotations/Line.annotation.ts | 2 +- .../src/annotations/Polygon.annotation.ts | 16 +- .../image/src/annotations/Rect.annotation.ts | 14 +- .../src/annotations/Relation.annotation.ts | 175 +++++++ packages/image/src/annotations/index.ts | 3 + packages/image/src/constant/index.ts | 5 +- packages/image/src/core/AnnotatorConfig.ts | 6 + packages/image/src/core/CustomRBush.ts | 32 +- packages/image/src/core/Monitor.ts | 54 ++- packages/image/src/drafts/Draft.ts | 7 + packages/image/src/drafts/Polygon.draft.ts | 17 +- packages/image/src/drafts/Rect.draft.ts | 13 +- packages/image/src/drafts/Relation.draft.ts | 259 +++++++++++ .../image/src/enums/internalEvent.enum.ts | 18 + packages/image/src/interface.ts | 26 +- packages/image/src/shapes/ClosedSpline.ts | 5 + packages/image/src/shapes/Group.ts | 27 ++ packages/image/src/shapes/Line.shape.ts | 111 ++++- packages/image/src/shapes/Polygon.shape.ts | 5 + packages/image/src/shapes/Spline.shape.ts | 5 + packages/image/src/singletons/eventEmitter.ts | 5 +- packages/image/src/tools/Cuboid.tool.ts | 2 + packages/image/src/tools/Line.tool.ts | 3 + packages/image/src/tools/Point.tool.ts | 2 + packages/image/src/tools/Polygon.tool.ts | 6 +- packages/image/src/tools/Rect.tool.ts | 2 + packages/image/src/tools/Relation.tool.ts | 437 ++++++++++++++++++ packages/image/src/tools/Tool.decorator.ts | 13 +- packages/image/src/tools/Tool.ts | 3 +- packages/image/src/tools/index.ts | 1 + 33 files changed, 1259 insertions(+), 46 deletions(-) create mode 100644 packages/image/src/annotations/Relation.annotation.ts create mode 100644 packages/image/src/drafts/Relation.draft.ts create mode 100644 packages/image/src/tools/Relation.tool.ts diff --git a/packages/image/src/Annotator.ts b/packages/image/src/Annotator.ts index 62de217f8..2574f7193 100644 --- a/packages/image/src/Annotator.ts +++ b/packages/image/src/Annotator.ts @@ -405,7 +405,9 @@ export class Annotator extends AnnotatorBase { const AnnotationClass = AnnotationMapping[activeToolName]; - currentTool.setLabel(value); + if (!currentTool.setLabel(value)) { + return; + } if (this.cursorManager) { this.cursorManager.color = AnnotationClass.labelStatic.getLabelColor(value); diff --git a/packages/image/src/AnnotatorBase.ts b/packages/image/src/AnnotatorBase.ts index 87518048e..1b1740ff9 100644 --- a/packages/image/src/AnnotatorBase.ts +++ b/packages/image/src/AnnotatorBase.ts @@ -16,6 +16,8 @@ import type { CursorManager } from './core/CursorManager'; import { createCursorManager } from './singletons/cursorManager'; import { createConfig } from './singletons/annotationConfig'; import type { AnnotatorOptions } from './core/AnnotatorConfig'; +// import { relationManager } from './singletons/relationManager'; +import { RelationTool } from './tools/Relation.tool'; const ToolMapping = { line: LineTool, @@ -23,6 +25,7 @@ const ToolMapping = { rect: RectTool, polygon: PolygonTool, cuboid: CuboidTool, + relation: RelationTool, } as const; export class AnnotatorBase { @@ -135,6 +138,7 @@ export class AnnotatorBase { ...(config[toolName] as any), requestEdit: typeof config.requestEdit === 'function' ? config.requestEdit : () => true, showOrder: config.showOrder ?? false, + getTools: () => this.tools, }), ); } @@ -190,6 +194,9 @@ export class AnnotatorBase { annotations.forEach((annotation) => { annotation.render(renderer!.ctx!); }); + + // relationManager.render(renderer!.ctx!); + // 草稿在最上层 draft?.render(renderer!.ctx!); }; @@ -233,6 +240,7 @@ export class AnnotatorBase { showOrder: config.showOrder ?? false, requestEdit: typeof config.requestEdit === 'function' ? config.requestEdit : () => true, data: data as AllTypeAnnotationDataGroup, + getTools: () => this.tools, }), ); } else { diff --git a/packages/image/src/annotations/Annotation.ts b/packages/image/src/annotations/Annotation.ts index 950601b5a..73ec870af 100644 --- a/packages/image/src/annotations/Annotation.ts +++ b/packages/image/src/annotations/Annotation.ts @@ -1,4 +1,4 @@ -import type { BasicImageAnnotation } from '../interface'; +import type { BasicImageAnnotation, ToolName } from '../interface'; import { Group } from '../shapes/Group'; import { type Shape } from '../shapes'; import { monitor } from '../singletons'; @@ -8,6 +8,7 @@ import { DEFAULT_LABEL_COLOR } from '../constant'; export interface AnnotationParams { id: string; data: Data; + name: ToolName; style: Style; hoveredStyle?: Style | ((style: Style) => Style); @@ -27,6 +28,8 @@ export class Annotation; @@ -49,13 +52,13 @@ export class Annotation) { + constructor({ id, data, style, hoveredStyle, showOrder, name }: AnnotationParams) { this.id = id; this.data = data; this.style = style; this.hoveredStyle = hoveredStyle; this.showOrder = showOrder; - + this.name = name; this.group = new Group(id, data.order); // 建立order和id的映射关系 @@ -66,6 +69,16 @@ export class Annotation { +export class AnnotationLine extends Annotation { public labelColor: string = LabelBase.DEFAULT_COLOR; public strokeColor: string = LabelBase.DEFAULT_COLOR; diff --git a/packages/image/src/annotations/Polygon.annotation.ts b/packages/image/src/annotations/Polygon.annotation.ts index 3fa7ea394..eb42f98f2 100644 --- a/packages/image/src/annotations/Polygon.annotation.ts +++ b/packages/image/src/annotations/Polygon.annotation.ts @@ -27,7 +27,7 @@ export interface PolygonData extends BasicImageAnnotation { export type PolygonGroup = Group; -export class AnnotationPolygon extends Annotation { +export class AnnotationPolygon extends Annotation { public labelColor: string = LabelBase.DEFAULT_COLOR; public strokeColor: string = LabelBase.DEFAULT_COLOR; @@ -162,6 +162,20 @@ export class AnnotationPolygon extends Annotation item.x)); + const maxY = Math.max(...group.shapes[0].dynamicCoordinate.map((item) => item.y)); + const minX = Math.min(...group.shapes[0].dynamicCoordinate.map((item) => item.x)); + const minY = Math.min(...group.shapes[0].dynamicCoordinate.map((item) => item.y)); + + return { + x: (maxX + minX) / 2, + y: (maxY + minY) / 2, + }; + } + public destroy(): void { super.destroy(); eventEmitter.off(EInternalEvent.NoTarget, this._handleMouseOut); diff --git a/packages/image/src/annotations/Rect.annotation.ts b/packages/image/src/annotations/Rect.annotation.ts index 0d3f3c7df..efc7c410b 100644 --- a/packages/image/src/annotations/Rect.annotation.ts +++ b/packages/image/src/annotations/Rect.annotation.ts @@ -7,7 +7,6 @@ import type { BasicImageAnnotation } from '../interface'; import type { AnnotationParams } from './Annotation'; import { Annotation } from './Annotation'; import { Rect, type RectStyle } from '../shapes/Rect.shape'; -import type { Line } from '../shapes/Line.shape'; import { ShapeText } from '../shapes/Text.shape'; import type { Group } from '../shapes'; import { LabelBase } from './Label.base'; @@ -23,7 +22,7 @@ export interface RectData extends BasicImageAnnotation { export type RectGroup = Group; -export class AnnotationRect extends Annotation { +export class AnnotationRect extends Annotation { public labelColor: string = LabelBase.DEFAULT_COLOR; public strokeColor: string = LabelBase.DEFAULT_COLOR; @@ -131,6 +130,17 @@ export class AnnotationRect extends Annotation + | Annotation + | Annotation; + +export interface RelationAnnotationParams extends AnnotationParams { + getAnnotation: (id: string) => ValidAnnotationType | undefined; +} + +export class AnnotationRelation extends Annotation { + public labelColor: string = LabelBase.DEFAULT_COLOR; + + public strokeColor: string = LabelBase.DEFAULT_COLOR; + + private _getAnnotation: (id: string) => ValidAnnotationType | undefined; + + constructor({ getAnnotation, ...params }: RelationAnnotationParams) { + super(params); + this._getAnnotation = getAnnotation; + this.labelColor = AnnotationRelation.labelStatic.getLabelColor(params.data.label); + this.strokeColor = Color(this.labelColor).alpha(Annotation.strokeOpacity).string(); + this._setupShapes(); + this.group.on(EInternalEvent.MouseOver, this._handleMouseOver); + this.group.on(EInternalEvent.MouseOut, this._handleMouseOut); + eventEmitter.on(EInternalEvent.NoTarget, this._handleMouseOut); + } + + static buildLabelMapping(labels: ILabel[]) { + AnnotationRelation.labelStatic = new LabelBase(labels); + } + + static labelStatic: LabelBase; + + static chunk(arr: any[], size: number) { + const result = []; + + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + + return result; + } + + private _setupShapes() { + const { data, group, style, labelColor, strokeColor } = this; + + const { visible = true } = data; + + const commonStyle = { + ...style, + opacity: visible ? 1 : 0, + }; + + const sourceAnnotation = this._getAnnotation(data.sourceId); + const targetAnnotation = this._getAnnotation(data.targetId); + const sourceCenter = sourceAnnotation?.getCenter(); + const targetCenter = targetAnnotation?.getCenter(); + + if (!sourceCenter || !targetCenter) { + console.error('sourceAnnotation or targetAnnotation is not found'); + return; + } + + const line = new Line({ + id: uid(), + coordinate: [ + { + x: axis!.getOriginalX(sourceCenter.x), + y: axis!.getOriginalY(sourceCenter.y), + }, + { + x: axis!.getOriginalX(targetCenter.x), + y: axis!.getOriginalY(targetCenter.y), + }, + ], + style: { ...commonStyle, stroke: strokeColor, strokeWidth: Annotation.strokeWidth }, + }); + + group.add(line); + + const attributesText = AnnotationRelation.labelStatic.getLabelTextWithAttributes(data.label, data.attributes); + + group.add( + new ShapeText({ + id: uid(), + // 线段的中点 + coordinate: { + x: axis!.getOriginalX((sourceCenter.x + targetCenter.x) / 2), + y: axis!.getOriginalY((sourceCenter.y + targetCenter.y) / 2), + }, + text: `${this.showOrder ? data.order + ' ' : ''}${attributesText}`, + style: { + opacity: visible ? 1 : 0, + fill: labelColor, + }, + }), + ); + } + + private _handleMouseOver = () => { + const { data, group, style, hoveredStyle, strokeColor } = this; + + const { visible = true } = data; + + const commonStyle = { + ...style, + opacity: visible ? 1 : 0, + }; + + if (hoveredStyle) { + group.updateStyle(typeof hoveredStyle === 'function' ? hoveredStyle(style) : hoveredStyle); + } else { + group.each((shape) => { + if (!(shape instanceof ShapeText)) { + shape.updateStyle({ + ...commonStyle, + stroke: strokeColor, + strokeWidth: Annotation.strokeWidth + 2, + }); + } + }); + } + }; + + private _handleMouseOut = () => { + const { data, style, group, strokeColor } = this; + + const { visible = true } = data; + + const commonStyle = { + ...style, + opacity: visible ? 1 : 0, + }; + + group.each((shape) => { + if (!(shape instanceof ShapeText)) { + shape.updateStyle({ + ...commonStyle, + stroke: strokeColor, + strokeWidth: Annotation.strokeWidth, + }); + } + }); + }; + + public destroy(): void { + super.destroy(); + eventEmitter.off(EInternalEvent.NoTarget, this._handleMouseOut); + } +} diff --git a/packages/image/src/annotations/index.ts b/packages/image/src/annotations/index.ts index 11cbb96b4..9ff6ebb46 100644 --- a/packages/image/src/annotations/index.ts +++ b/packages/image/src/annotations/index.ts @@ -3,6 +3,7 @@ import { AnnotationLine } from './Line.annotation'; import { AnnotationPoint } from './Point.annotation'; import { AnnotationPolygon } from './Polygon.annotation'; import { AnnotationRect } from './Rect.annotation'; +import { AnnotationRelation } from './Relation.annotation'; export * from './Cuboid.annotation'; export * from './Polygon.annotation'; @@ -10,6 +11,7 @@ export * from './Rect.annotation'; export * from './Line.annotation'; export * from './Point.annotation'; export * from './Annotation'; +export * from './Relation.annotation'; export const AnnotationMapping = { cuboid: AnnotationCuboid, @@ -17,4 +19,5 @@ export const AnnotationMapping = { rect: AnnotationRect, line: AnnotationLine, point: AnnotationPoint, + relation: AnnotationRelation, }; diff --git a/packages/image/src/constant/index.ts b/packages/image/src/constant/index.ts index ac7ad5e44..388b6a638 100644 --- a/packages/image/src/constant/index.ts +++ b/packages/image/src/constant/index.ts @@ -1,4 +1,5 @@ -export const TOOL_NAMES = ['point', 'line', 'rect', 'polygon', 'cuboid'] as const; +export const TOOL_NAMES = ['point', 'line', 'rect', 'polygon', 'cuboid', 'relation'] as const; + export const DEFAULT_LABEL_TEXT = 'noneLabel'; export const DEFAULT_LABEL_VALUE = 'noneAttribute'; @@ -6,3 +7,5 @@ export const DEFAULT_LABEL_VALUE = 'noneAttribute'; export const DEFAULT_LABEL_COLOR = '#999'; export const DEFAULT_BACKGROUND_COLOR = '#F0F0F0'; + +export const VALID_RELATION_TOOLS = ['rect', 'polygon'] as const; diff --git a/packages/image/src/core/AnnotatorConfig.ts b/packages/image/src/core/AnnotatorConfig.ts index 14dcfe44e..48647f7f0 100644 --- a/packages/image/src/core/AnnotatorConfig.ts +++ b/packages/image/src/core/AnnotatorConfig.ts @@ -5,6 +5,7 @@ import type { PointToolOptions, PolygonToolOptions, RectToolOptions, + RelationToolOptions, } from '@/tools'; export interface AnnotatorOptions { @@ -24,6 +25,8 @@ export interface AnnotatorOptions { cuboid?: CuboidToolOptions; + relation?: RelationToolOptions; + /** * 全局的是否可编辑设置,权重高于requestEdit函数 * @@ -94,6 +97,8 @@ export default class AnnotatorConfig { public cuboid?: CuboidToolOptions; + public relation?: RelationToolOptions; + public editable?: boolean = true; public image: { @@ -123,6 +128,7 @@ export default class AnnotatorConfig { this.height = options.height; this.line = options.line; this.point = options.point; + this.relation = options.relation; this.rect = options.rect; this.polygon = options.polygon; this.cuboid = options.cuboid; diff --git a/packages/image/src/core/CustomRBush.ts b/packages/image/src/core/CustomRBush.ts index 4da829ed0..f74375d33 100644 --- a/packages/image/src/core/CustomRBush.ts +++ b/packages/image/src/core/CustomRBush.ts @@ -4,11 +4,11 @@ import RBush from 'rbush'; import uid from '@/utils/uid'; import type { AxisPoint, Shape } from '../shapes'; -import { Line, Point } from '../shapes'; +import { Line, Point, ShapeText } from '../shapes'; import type { Group } from '../shapes/Group'; import { axis, eventEmitter } from '../singletons'; import { EInternalEvent } from '../enums'; -import { getDistanceToLine, getLatestPointOnLine } from '../shapes/math.util'; +import { getDistanceToLine, getLatestPointOnLine, isBBoxIntersect } from '../shapes/math.util'; export interface RBushItem extends BBox { id: string; @@ -85,6 +85,34 @@ export class CustomRBush extends RBush { }); } + /** + * 判断点是否在画布图形的任一包围盒中(文字除外) + * + * @param coordinate 坐标点 + * @returns 是否在包围盒中 + */ + public getRBushItemsByPointInBBox(coordinate: AxisPoint) { + const rbushItems = this.scanCanvasObject(coordinate, 0); + + return rbushItems.filter((item) => { + const _bbox = item._group?.getBBoxByFilter((shape) => !(shape instanceof ShapeText)); + + if (!_bbox) { + return false; + } + + // 创建一个以coordinate为中心的极小bbox来检测点是否在_bbox内 + const pointBBox: BBox = { + minX: coordinate.x, + minY: coordinate.y, + maxX: coordinate.x, + maxY: coordinate.y, + }; + + return isBBoxIntersect(pointBBox, _bbox); + }); + } + /** * 扫描多边形并设置最近的点 * diff --git a/packages/image/src/core/Monitor.ts b/packages/image/src/core/Monitor.ts index 740198a84..ccbf2bd90 100644 --- a/packages/image/src/core/Monitor.ts +++ b/packages/image/src/core/Monitor.ts @@ -30,9 +30,9 @@ export interface MonitorOption { export class Monitor { private _canvas: HTMLCanvasElement; - public _hoveredGroup: GroupInAnnotation | null = null; + public hoveredGroup: GroupInAnnotation | null = null; - private _hoveredShape: AnnotationShape | null = null; + public hoveredShape: AnnotationShape | null = null; public selectedAnnotationId: string | null = null; @@ -229,11 +229,13 @@ export class Monitor { * @description 用于处理鼠标移动到标注上时,触发标注的 hover 事件;同时,选中标注的逻辑也会依赖此处理函数 */ private _handleMouseOver = (e: MouseEvent) => { - const { _hoveredGroup, _hoveredShape } = this; + const { hoveredGroup, hoveredShape } = this; const rbushItems = rbush.scanCanvasObject({ x: e.offsetX, y: e.offsetY }); const orderIndexedGroup: any[] = []; let topGroup: any | null = null; + const oldHoveredShape = hoveredShape; + for (const rbushItem of rbushItems) { if (rbushItem._group) { const isUnderCursor = rbushItem._group.isShapesUnderCursor({ @@ -256,6 +258,8 @@ export class Monitor { topGroup = rbushItem._group; } } + } else if (rbushItem._shape && rbushItem._shape.isUnderCursor({ x: e.offsetX, y: e.offsetY })) { + this.hoveredShape = rbushItem._shape as any; } } // 最后一个表示order最大的group @@ -271,7 +275,12 @@ export class Monitor { (shape as Group).reverseEach((innerShape: any) => { if (innerShape.isUnderCursor({ x: e.offsetX, y: e.offsetY })) { innerShape.emit(EInternalEvent.ShapeOver, e, innerShape); - this._hoveredShape = innerShape; + + if (hoveredShape && hoveredShape.id !== innerShape.id) { + hoveredShape.emit(EInternalEvent.ShapeOut, e, hoveredShape); + } + + this.hoveredShape = innerShape; // 只给一个shape发送事件,避免多个shape同时hover return false; } @@ -279,22 +288,23 @@ export class Monitor { return false; } else if (shape.isUnderCursor({ x: e.offsetX, y: e.offsetY })) { shape.emit(EInternalEvent.ShapeOver, e, shape); - this._hoveredShape = shape; + + this.hoveredShape = shape; // 只给一个shape发送事件,避免多个shape同时hover return false; } }); - if (_hoveredGroup && _hoveredGroup.id !== lastGroup.id) { + if (hoveredGroup && hoveredGroup.id !== lastGroup.id) { // 向上一次hover的group发送鼠标离开事件,避免多个group同时hover - _hoveredGroup.emit(EInternalEvent.MouseOut, e); + hoveredGroup.emit(EInternalEvent.MouseOut, e); } - if (_hoveredShape && _hoveredShape.id !== this._hoveredShape?.id) { - _hoveredShape.emit(EInternalEvent.ShapeOut, e, _hoveredShape); + if (oldHoveredShape && oldHoveredShape.id !== this.hoveredShape?.id) { + oldHoveredShape.emit(EInternalEvent.ShapeOut, e, oldHoveredShape); } - this._hoveredGroup = lastGroup; + this.hoveredGroup = lastGroup; for (const rbushItem of rbushItems) { if (rbushItem._group && lastGroup.id !== rbushItem._group.id) { @@ -308,14 +318,18 @@ export class Monitor { } else { eventEmitter.emit(EInternalEvent.NoTarget, e); - if (_hoveredShape) { - _hoveredShape.emit(EInternalEvent.ShapeOut, e, _hoveredShape); - this._hoveredShape = null; + if (this.hoveredShape) { + this.hoveredShape.emit(EInternalEvent.ShapeOver, e, this.hoveredShape); + } + + if (oldHoveredShape && !oldHoveredShape.isUnderCursor({ x: e.offsetX, y: e.offsetY })) { + oldHoveredShape.emit(EInternalEvent.ShapeOut, e, oldHoveredShape); + this.hoveredShape = null; } rbush.nearestPoint?.destroy(); rbush.nearestPoint = null; - this._hoveredGroup = null; + this.hoveredGroup = null; } }; @@ -344,18 +358,18 @@ export class Monitor { return; } - const { _hoveredGroup, selectedAnnotationId } = this; + const { hoveredGroup, selectedAnnotationId } = this; - if (_hoveredGroup) { - if (selectedAnnotationId && _hoveredGroup.id !== selectedAnnotationId) { + if (hoveredGroup) { + if (selectedAnnotationId && hoveredGroup.id !== selectedAnnotationId) { this.selectedAnnotationId = null; } - _hoveredGroup.emit(EInternalEvent.Select, e); - this.selectedAnnotationId = _hoveredGroup.id; + hoveredGroup.emit(EInternalEvent.Select, e); + this.selectedAnnotationId = hoveredGroup.id; } - eventEmitter.emit('rightClick', e, _hoveredGroup?.id); + eventEmitter.emit('rightClick', e, hoveredGroup?.id); }; public setSelectedAnnotationId(id: string | null) { diff --git a/packages/image/src/drafts/Draft.ts b/packages/image/src/drafts/Draft.ts index 3ab1e38ed..8f4c74769 100644 --- a/packages/image/src/drafts/Draft.ts +++ b/packages/image/src/drafts/Draft.ts @@ -174,6 +174,8 @@ export class Draft< // 统一在这里移动草稿 this.moveByDistance(); + eventEmitter.emit(EInternalEvent.DraftMove, e, this); + for (const handler of _onMoveHandlers) { handler(e); } @@ -224,6 +226,11 @@ export class Draft< this.emit('setup'); } + public getCenter() { + console.warn('getCenter is not implemented'); + return { x: 0, y: 0 }; + } + public onMove(handler: MouseEventHandler) { this._onMoveHandlers.push(handler); } diff --git a/packages/image/src/drafts/Polygon.draft.ts b/packages/image/src/drafts/Polygon.draft.ts index 7a3db5e51..527e386c5 100644 --- a/packages/image/src/drafts/Polygon.draft.ts +++ b/packages/image/src/drafts/Polygon.draft.ts @@ -412,8 +412,8 @@ export class DraftPolygon extends Draft { @@ -576,6 +577,20 @@ export class DraftPolygon extends Draft item.x)); + const maxY = Math.max(...group.shapes[0].dynamicCoordinate.map((item) => item.y)); + const minX = Math.min(...group.shapes[0].dynamicCoordinate.map((item) => item.x)); + const minY = Math.min(...group.shapes[0].dynamicCoordinate.map((item) => item.y)); + + return { + x: (maxX + minX) / 2, + y: (maxY + minY) / 2, + }; + } + public destroy() { super.destroy(); eventEmitter.off(EInternalEvent.KeyDown, this._onKeyDown); diff --git a/packages/image/src/drafts/Rect.draft.ts b/packages/image/src/drafts/Rect.draft.ts index 31e4e2dff..c35db025f 100644 --- a/packages/image/src/drafts/Rect.draft.ts +++ b/packages/image/src/drafts/Rect.draft.ts @@ -3,12 +3,13 @@ import type { BBox } from 'rbush'; import Color from 'color'; import uid from '@/utils/uid'; +import { EInternalEvent } from '@/enums'; import { Rect, type RectStyle } from '../shapes/Rect.shape'; import { AnnotationRect, type RectData } from '../annotations'; import type { AxisPoint, LineCoordinate, PointStyle } from '../shapes'; import { Point } from '../shapes'; -import { axis } from '../singletons'; +import { axis, eventEmitter } from '../singletons'; import type { AnnotationParams } from '../annotations/Annotation'; import { Annotation } from '../annotations/Annotation'; import { ControllerPoint } from './ControllerPoint'; @@ -420,6 +421,7 @@ export class DraftRect extends Draft { @@ -656,6 +659,14 @@ export class DraftRect extends Draft cloneDeep(shape.dynamicCoordinate)); } + public getCenter() { + const { minX, minY, maxX, maxY } = this._getBBox(); + return { + x: (minX + maxX) / 2, + y: (minY + maxY) / 2, + }; + } + public syncCoordToData() { const { data } = this; diff --git a/packages/image/src/drafts/Relation.draft.ts b/packages/image/src/drafts/Relation.draft.ts new file mode 100644 index 000000000..495b74565 --- /dev/null +++ b/packages/image/src/drafts/Relation.draft.ts @@ -0,0 +1,259 @@ +import cloneDeep from 'lodash.clonedeep'; + +import uid from '@/utils/uid'; +import { VALID_RELATION_TOOLS } from '@/constant'; + +import type { LineStyle } from '../shapes/Line.shape'; +import { Line } from '../shapes/Line.shape'; +import { AnnotationRelation, type RelationData } from '../annotations'; +import type { PointStyle, Point, AxisPoint } from '../shapes'; +import { ShapeText } from '../shapes'; +import { axis, rbush } from '../singletons'; +import type { AnnotationParams } from '../annotations/Annotation'; +import { Annotation } from '../annotations/Annotation'; +import { ControllerPoint } from './ControllerPoint'; +import { Draft } from './Draft'; + +export interface RelationDraftParams extends AnnotationParams { + getAnnotation: (id: string) => Annotation | undefined; + isDuplicatedRelation: (sourceId: string, targetId: string) => boolean; +} + +export class DraftRelation extends Draft { + private _isDuplicatedRelation: (sourceId: string, targetId: string) => boolean; + + private _getAnnotation: (id: string) => Annotation | undefined; + + private _effectedLines: [Line | undefined, Line | undefined] | null = null; + + private _tempPoint: (AxisPoint & { annotationId: string }) | null = null; + + constructor(options: RelationDraftParams) { + const { isDuplicatedRelation, getAnnotation, ...params } = options; + super({ ...params, name: 'relation', labelColor: AnnotationRelation.labelStatic.getLabelColor(params.data.label) }); + + this._isDuplicatedRelation = isDuplicatedRelation; + this._getAnnotation = getAnnotation; + this._setupShapes(); + this.finishSetup(); + } + + /** + * 设置图形 + */ + private _setupShapes() { + const { data, group, style, strokeColor } = this; + + const sourceAnnotation = this._getAnnotation(data.sourceId); + const targetAnnotation = this._getAnnotation(data.targetId); + const sourceCenter = sourceAnnotation?.getCenter(); + const targetCenter = targetAnnotation?.getCenter(); + + if (!sourceCenter || !targetCenter) { + console.error('sourceAnnotation or targetAnnotation is not found'); + return; + } + + const line = new Line({ + id: uid(), + coordinate: [ + { + x: axis!.getOriginalX(sourceCenter.x), + y: axis!.getOriginalY(sourceCenter.y), + }, + { + x: axis!.getOriginalX(targetCenter.x), + y: axis!.getOriginalY(targetCenter.y), + }, + ], + style: { + ...style, + stroke: strokeColor, + strokeWidth: Annotation.strokeWidth, + }, + }); + + group.add(line); + + // 点要覆盖在线上 + for (let i = 0; i < line.coordinate.length; i++) { + const pointItem = line.coordinate[i]; + const point = new ControllerPoint({ + id: uid(), + name: i === 0 ? 'source' : 'target', + disabled: !this.requestEdit('update'), + outOfImage: true, + // 深拷贝,避免出现引用问题 + coordinate: { ...pointItem }, + }); + + point.onMouseDown(this._onControllerPointDown); + point.onMove(this._onControllerPointMove); + point.onMouseUp(this._onControllerPointUp); + + group.add(point); + } + } + + /** + * 按下控制点 + * @param point + * @description 按下控制点时,记录受影响的线段 + */ + private _onControllerPointDown = (point: ControllerPoint) => { + this._effectedLines = [undefined, undefined]; + + this.group.each((shape) => { + if (shape instanceof Line) { + if ( + shape.dynamicCoordinate[0].x === point.dynamicCoordinate[0].x && + shape.dynamicCoordinate[0].y === point.dynamicCoordinate[0].y + ) { + // 线段的起点 + this._effectedLines![0] = shape; + } + if ( + shape.dynamicCoordinate[1].x === point.dynamicCoordinate[0].x && + shape.dynamicCoordinate[1].y === point.dynamicCoordinate[0].y + ) { + // 线段的终点 + this._effectedLines![1] = shape; + } + } + }); + }; + + /** + * 移动控制点 + * @param changedCoordinate + * @description 控制点移动时,更新线段的端点 + */ + private _onControllerPointMove = ({ coordinate, name }: ControllerPoint) => { + const { _effectedLines } = this; + let x = coordinate[0].x; + let y = coordinate[0].y; + + if (!_effectedLines) { + return; + } + + const rbushItems = rbush.getRBushItemsByPointInBBox({ x, y }).filter((item) => { + const annotation = this._getAnnotation(item._group!.id); + + if (!annotation || !VALID_RELATION_TOOLS.includes(annotation.name as any)) { + return false; + } + + if (name === 'source') { + return item._group?.id !== this.group.id && item._group?.id !== this.data.targetId; + } + + if (name === 'target') { + return item._group?.id !== this.group.id && item._group?.id !== this.data.sourceId; + } + }); + + if (rbushItems.length > 0) { + const targetBBox = rbushItems[0]._group?.getBBoxByFilter((shape) => !(shape instanceof ShapeText)); + + if ( + this._isDuplicatedRelation(rbushItems[0]._group!.id, this.data.targetId) || + this._isDuplicatedRelation(this.data.sourceId, rbushItems[0]._group!.id) + ) { + return; + } + + if (targetBBox) { + // 获取中心点 + x = axis!.getOriginalX(targetBBox.minX + (targetBBox.maxX - targetBBox.minX) / 2); + y = axis!.getOriginalY(targetBBox.minY + (targetBBox.maxY - targetBBox.minY) / 2); + } + + this._tempPoint = { + annotationId: rbushItems[0]._group!.id, + x, + y, + }; + } else { + this._tempPoint = null; + } + + // 更新受影响的线段端点 + if (_effectedLines[1] === undefined && _effectedLines[0]) { + _effectedLines[0].coordinate = [{ x, y }, { ..._effectedLines[0].coordinate[1] }]; + } else if (_effectedLines[0] === undefined && _effectedLines[1]) { + _effectedLines[1].coordinate = [{ ..._effectedLines[1].coordinate[0] }, { x, y }]; + } else if (_effectedLines[0] && _effectedLines[1]) { + // 更新下一个线段的起点 + _effectedLines[0].coordinate = [{ x, y }, { ..._effectedLines[0].coordinate[1] }]; + // 更新前一个线段的终点 + _effectedLines[1].coordinate = [{ ..._effectedLines[1].coordinate[0] }, { x, y }]; + } + + // 手动更新组合的包围盒 + this.group.update(); + }; + + /** + * 释放控制点 + */ + private _onControllerPointUp = ({ coordinate }: ControllerPoint) => { + const { _effectedLines, data, _tempPoint } = this; + + if (!_effectedLines || !_tempPoint) { + return; + } + + // 根据受影响的线段确定是更新source还是target + if (_effectedLines[0] && !_effectedLines[1]) { + // 只有起点受影响,说明是source端 + _effectedLines[0].coordinate = [ + { + x: _tempPoint.x, + y: _tempPoint.y, + }, + { ..._effectedLines[0].coordinate[1] }, + ]; + + if (_tempPoint.annotationId !== data.targetId) { + data.sourceId = _tempPoint.annotationId; + } + } else if (!_effectedLines[0] && _effectedLines[1]) { + // 只有终点受影响,说明是target端 + _effectedLines[1].coordinate = [ + { ..._effectedLines[1].coordinate[0] }, + { + x: _tempPoint.x, + y: _tempPoint.y, + }, + ]; + + if (_tempPoint.annotationId !== data.sourceId) { + data.targetId = _tempPoint.annotationId; + } + } + + coordinate[0].x = _tempPoint.x; + coordinate[0].y = _tempPoint.y; + this._tempPoint = null; + axis?.rerender(); + }; + + protected getDynamicCoordinates() { + return this.group.shapes.map((shape) => cloneDeep(shape.dynamicCoordinate)); + } + + public isUnderCursor(mouseCoord: AxisPoint) { + // 坐标在线条上 + return this.group.shapes.some((shape) => shape.isUnderCursor(mouseCoord)); + } + + public destroy() { + super.destroy(); + this._effectedLines = null; + } + + public render(ctx: CanvasRenderingContext2D) { + super.render(ctx); + } +} diff --git a/packages/image/src/enums/internalEvent.enum.ts b/packages/image/src/enums/internalEvent.enum.ts index f9eb43769..a973315de 100644 --- a/packages/image/src/enums/internalEvent.enum.ts +++ b/packages/image/src/enums/internalEvent.enum.ts @@ -68,6 +68,9 @@ export enum EInternalEvent { /** 鼠标经过图形对象 */ ShapeOver = '__shape_over__', + /** 鼠标经过图形对象 */ + GlobalShapeOver = '__global_shape_over__', + /** 鼠标经过图形对象 */ ShapeOut = '__shape_out__', @@ -107,4 +110,19 @@ export enum EInternalEvent { ContactUp = '__contact_up__', ShapeCoordinateChange = '__shape_coordinate_change__', + + /** 关联关系模式开始 */ + RelationModeStart = '__relation_mode_start__', + + /** 关联关系模式结束 */ + RelationModeEnd = '__relation_mode_end__', + + /** 关联关系创建 */ + RelationCreated = '__relation_created__', + + /** 草稿移动 */ + DraftMove = '__draft_move__', + + /** 草稿大小变化 */ + DraftResize = '__draft_resize__', } diff --git a/packages/image/src/interface.ts b/packages/image/src/interface.ts index f3b5e8f8f..c27ae8acc 100644 --- a/packages/image/src/interface.ts +++ b/packages/image/src/interface.ts @@ -8,6 +8,7 @@ import type { PolygonGroup, RectData, RectGroup, + RelationData, } from './annotations'; import type { ClosedSpline, Line, Point, Polygon, Rect, Spline } from './shapes'; import type { @@ -21,6 +22,8 @@ import type { PolygonToolOptions, RectTool, RectToolOptions, + RelationTool, + RelationToolOptions, } from './tools'; export type EditType = 'create' | 'update' | 'delete'; @@ -68,19 +71,30 @@ export interface BasicImageAnnotation { ) => boolean; } -export type ToolOptions = LineToolOptions | PointToolOptions | RectToolOptions | PolygonToolOptions | CuboidToolOptions; +export type ToolOptions = + | LineToolOptions + | PointToolOptions + | RectToolOptions + | PolygonToolOptions + | CuboidToolOptions + | RelationToolOptions; export type { LineData }; -export type AnnotationData = LineData | PointData | RectData | PolygonData | CuboidData; +export type AnnotationData = LineData | PointData | RectData | PolygonData | CuboidData | RelationData; -export type AllTypeAnnotationDataGroup = CuboidData[] & PolygonData[] & RectData[] & PointData[] & LineData[]; +export type AllTypeAnnotationDataGroup = CuboidData[] & + PolygonData[] & + RectData[] & + PointData[] & + LineData[] & + RelationData[]; -export type AnnotationTool = LineTool | PointTool | RectTool | PolygonTool | CuboidTool; +export type AnnotationTool = LineTool | PointTool | RectTool | PolygonTool | CuboidTool | RelationTool; export type AnnotationShape = Line | Point | Rect | Polygon | Spline | ClosedSpline; -export type ToolName = 'line' | 'point' | 'polygon' | 'rect' | 'cuboid'; +export type ToolName = 'line' | 'point' | 'polygon' | 'rect' | 'cuboid' | 'relation'; export type AnnotationToolData = T extends 'line' ? LineData[] @@ -92,4 +106,6 @@ export type AnnotationToolData = T extends 'line' ? PolygonData[] : T extends 'cuboid' ? CuboidData[] + : T extends 'relation' + ? RelationData[] : never; diff --git a/packages/image/src/shapes/ClosedSpline.ts b/packages/image/src/shapes/ClosedSpline.ts index 1d1eef78d..d55472ef6 100644 --- a/packages/image/src/shapes/ClosedSpline.ts +++ b/packages/image/src/shapes/ClosedSpline.ts @@ -17,6 +17,11 @@ export class ClosedSpline extends Shape { strokeWidth: 2, opacity: 1, fill: Color(DEFAULT_LABEL_COLOR).alpha(0.5).toString(), + lineStyle: 'solid', + dashPattern: [], + arrowType: 'none', + headLength: 10, + headAngle: 30, }; private _controlPoints: AxisPoint[]; diff --git a/packages/image/src/shapes/Group.ts b/packages/image/src/shapes/Group.ts index 119897264..a00c5a506 100644 --- a/packages/image/src/shapes/Group.ts +++ b/packages/image/src/shapes/Group.ts @@ -116,6 +116,33 @@ export class Group, Style> { return this; } + public getBBoxByFilter(filter: (shape: Shape