diff --git a/packages/lb-components/src/assets/annotation/pointCloudTool/highlight.svg b/packages/lb-components/src/assets/annotation/pointCloudTool/highlight.svg new file mode 100644 index 000000000..c03371b4c --- /dev/null +++ b/packages/lb-components/src/assets/annotation/pointCloudTool/highlight.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/lb-components/src/assets/annotation/pointCloudTool/highlight_a.svg b/packages/lb-components/src/assets/annotation/pointCloudTool/highlight_a.svg new file mode 100644 index 000000000..a75a95d41 --- /dev/null +++ b/packages/lb-components/src/assets/annotation/pointCloudTool/highlight_a.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/lb-components/src/components/pointCloudView/PointCloudContext.tsx b/packages/lb-components/src/components/pointCloudView/PointCloudContext.tsx index c6d4e8eaa..222098742 100644 --- a/packages/lb-components/src/components/pointCloudView/PointCloudContext.tsx +++ b/packages/lb-components/src/components/pointCloudView/PointCloudContext.tsx @@ -65,8 +65,11 @@ export interface IPointCloudContext selectedPointCloudBox?: IPointCloudBox; setPointCloudValid: (valid?: boolean) => void; addSelectedID: (selectedID: string) => void; + addHighlightID: (highlightID: number) => void; selectedAllBoxes: () => void; selectedID: string; + highlightIDs: number[]; + setHighlightIDs: (ids: number[]) => void; addPointCloudBox: (boxParams: IPointCloudBox) => IPointCloudBox[]; addPointCloudSphere: (sphereParams: IPointCloudSphere) => IPointCloudSphere[]; @@ -129,6 +132,8 @@ export const PointCloudContext = React.createContext({ lineList: [], selectedID: '', selectedIDs: [], + highlightIDs: [], + setHighlightIDs: () => {}, valid: true, setSelectedIDs: () => {}, setPointCloudResult: () => {}, @@ -139,6 +144,7 @@ export const PointCloudContext = React.createContext({ setBackViewInstance: () => {}, setMainViewInstance: () => {}, addSelectedID: () => {}, + addHighlightID: () => {}, selectedAllBoxes: () => {}, addPointCloudBox: () => { return []; @@ -191,6 +197,7 @@ export const PointCloudProvider: React.FC<{}> = ({ children }) => { const [polygonList, setPolygonList] = useState([]); const [lineList, setLineList] = useState([]); const [selectedIDs, setSelectedIDsState] = useState([]); + const [highlightIDs, setHighlightIDs] = useState([]); const [valid, setValid] = useState(true); const [cuboidBoxIn2DView, setCuboidBoxIn2DView] = useState(true); const [zoom, setZoom] = useState(1); @@ -281,6 +288,14 @@ export const PointCloudProvider: React.FC<{}> = ({ children }) => { } }; + const addHighlightID = (highlightID: number) => { + if (highlightIDs.includes(highlightID)) { + setHighlightIDs([]); + } else { + setHighlightIDs([highlightID]); + } + }; + const selectedAllBoxes = () => { if (pointCloudPattern === EToolName.Rect) { const ids = pointCloudBoxList.map((i) => i.id); @@ -406,6 +421,7 @@ export const PointCloudProvider: React.FC<{}> = ({ children }) => { selectedPointCloudBox, setPointCloudValid, addSelectedID, + addHighlightID, selectedAllBoxes, topViewInstance, setTopViewInstance, @@ -448,6 +464,8 @@ export const PointCloudProvider: React.FC<{}> = ({ children }) => { setCuboidBoxIn2DView, imageSizes, cacheImageNodeSize, + highlightIDs, + setHighlightIDs, }; }, [ valid, @@ -471,6 +489,7 @@ export const PointCloudProvider: React.FC<{}> = ({ children }) => { highlight2DDataList, cuboidBoxIn2DView, imageSizes, + highlightIDs, ]); const updateSelectedIDsAndRenderAfterHide = () => { diff --git a/packages/lb-components/src/utils/audio.ts b/packages/lb-components/src/utils/audio.ts index 51ec7e6ad..d97bd93bd 100644 --- a/packages/lb-components/src/utils/audio.ts +++ b/packages/lb-components/src/utils/audio.ts @@ -6,20 +6,14 @@ import { message as SenseMessage } from 'antd'; import { cStyle, cTool } from '@labelbee/lb-annotation'; import _ from 'lodash'; import { IInputList } from '@/types/main'; -import Decimal from 'decimal.js' +import Decimal from 'decimal.js'; import moment from 'moment'; -import { ITextConfigItem } from '@labelbee/lb-utils' +import { ITextConfigItem } from '@labelbee/lb-utils'; -const { - COLORS_ARRAY, - ICON_ARRAY, - INVALID_ICON, - NULL_COLOR, - NULL_ICON, - WHITE_FONT_COLOR_ARRAY, -} = cStyle +const { COLORS_ARRAY, ICON_ARRAY, INVALID_ICON, NULL_COLOR, NULL_ICON, WHITE_FONT_COLOR_ARRAY } = + cStyle; -const { ETextType } = cTool +const { ETextType } = cTool; export const ATTRIBUTE_COLORS = [NULL_COLOR].concat(COLORS_ARRAY); @@ -42,11 +36,7 @@ export const DEFAULT_TEXT_CONFIG_ITEM: ITextConfigItem = { * @param attribute * @param attributeList */ -export const getAttributeIcon = ( - attribute: string, - attributeList: IInputList[], - valid = true, -) => { +export const getAttributeIcon = (attribute: string, attributeList: IInputList[], valid = true) => { const attributeIndex = attributeList.findIndex((i: any) => i.value === attribute); let src = ICON_ARRAY[attributeIndex % ICON_ARRAY.length] ?? NULL_ICON; if (!valid) { @@ -322,8 +312,8 @@ const generateIsDoubleClick = (interval: number) => { }; return fn; }; -// 间隔300ms点击同一元素视为双击 -export const isDoubleClick = generateIsDoubleClick(300); +// 间隔500ms点击同一元素视为双击 +export const isDoubleClick = generateIsDoubleClick(500); export const formatTime = (time: number) => { const milliseconds = Math.floor(time * 1000); diff --git a/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/annotatedBox/index.module.scss b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/annotatedBox/index.module.scss new file mode 100644 index 000000000..b7ea00918 --- /dev/null +++ b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/annotatedBox/index.module.scss @@ -0,0 +1,21 @@ +.tag { + color: #666; + background-color: #f3f4ff; + border-color: transparent; + cursor: pointer; + position: relative; + &.disabled { + color: #ccc; + background-color: #f5f5f5; + cursor: not-allowed; + } + &.selected { + color: #fff; + background-color: #666fff; + } + .highlight { + position: absolute; + right: -5px; + top: -5px; + } +} diff --git a/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/annotatedBox/index.tsx b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/annotatedBox/index.tsx new file mode 100644 index 000000000..0a1ec263a --- /dev/null +++ b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/annotatedBox/index.tsx @@ -0,0 +1,174 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { Checkbox, Popover, Tag } from 'antd'; +import { PointCloudUtils } from '@labelbee/lb-utils'; +import { PointCloudContext } from '@/components/pointCloudView/PointCloudContext'; +import { IFileItem } from '@/types/data'; +import { useTranslation } from 'react-i18next'; +import styles from './index.module.scss'; +import classNames from 'classnames'; + +import HighlightSvg from '@/assets/annotation/pointCloudTool/highlight.svg'; +import HighlightActiveSvg from '@/assets/annotation/pointCloudTool/highlight_a.svg'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { isDoubleClick } from '@/utils/audio'; + +interface ITrackIDItem { + id: string; + trackID?: number; + disabled: boolean; + selected: boolean; + isHighlight: boolean; +} + +const AnnotatedBox = ({ imgList, imgIndex }: { imgList: IFileItem[]; imgIndex: number }) => { + const { t } = useTranslation(); + + const ptCtx = useContext(PointCloudContext); + const { pointCloudBoxList } = ptCtx; + + const [showIDs, setShowIds] = useState([]); + const [onlyShowCurrentIndex, setOnlyShowCurrentIndex] = useState(false); + + const highlightHandler = (item: ITrackIDItem) => { + ptCtx.addHighlightID(item.trackID as number); + }; + + const selectHandler = (item: ITrackIDItem) => { + ptCtx.addSelectedID(item.id); + }; + + useEffect(() => { + const newImgList = imgList as Array<{ result: string }>; + let trackMap = new Map(); + const selectedTrackIDs = ptCtx.selectedIDs.map( + (v) => ptCtx.pointCloudBoxList.find((box) => box.id === v)?.trackID, + ); + setShowIds( + PointCloudUtils.getAllPointCloudResult({ + imgList: newImgList, + extraBoxList: pointCloudBoxList, + ignoreIndexList: [imgIndex], + }) + .filter((v) => { + if (!v.trackID) { + return false; + } + + if (trackMap.get(v.trackID)) { + return false; + } + trackMap.set(v.trackID, true); + return true; + }) + .sort((a, b) => { + const aTrackID = a?.trackID ?? 0; + const bTrackID = b?.trackID ?? 0; + + return aTrackID - bTrackID; + }) + .map((v) => { + const box = ptCtx.pointCloudBoxList.find((box) => box.trackID === v.trackID); + return { + id: box?.id ?? v.id, + trackID: v.trackID, + disabled: !box, + selected: selectedTrackIDs.includes(v.trackID), + isHighlight: v?.trackID ? ptCtx.highlightIDs.includes(v.trackID) : false, + }; + }), + ); + }, [ptCtx.pointCloudBoxList, imgList, ptCtx.selectedIDs, ptCtx.highlightIDs, imgIndex]); + + useEffect(() => { + const highlightBoxes = ptCtx.pointCloudBoxList.filter( + (box) => box.trackID && ptCtx.highlightIDs.includes(box.trackID), + ); + + if (highlightBoxes?.length) { + const needSetSelectedIDs = highlightBoxes.every((box) => !ptCtx.selectedIDs.includes(box.id)); + if (needSetSelectedIDs) { + const needHighlightSelectedIDs = [...ptCtx.selectedIDs, ...highlightBoxes.map((v) => v.id)]; + ptCtx.setSelectedIDs(needHighlightSelectedIDs); + } + } + }, [imgIndex, ptCtx.highlightIDs, ptCtx.selectedIDs]); + + return ( +
+
+ {t('AllTrackIDs')} + +
{t('ClickOnTheIdToHighlightTheMarkupBox')}
+
{t('DoubleClickOnTheIdToContinuouslyHighlightBoxesAcrossFrames')}
+ + } + > + +
+
+
+ setOnlyShowCurrentIndex(e.target.checked)} + > + {t('OnlyCurrentFrame')} + +
+ +
+ {showIDs.map((item) => { + if (item.disabled && onlyShowCurrentIndex) { + return null; + } + return ( + { + e.preventDefault(); + e.stopPropagation(); + if (isDoubleClick(e as any)) { + highlightHandler(item); + return; + } + if (item.disabled) { + return; + } + selectHandler(item); + }} + > + {item.isHighlight && ( + + )} + + {item.trackID} + + ); + })} +
+
+ ); +}; + +export default AnnotatedBox; diff --git a/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/findTrackIDIndex/index.module.scss b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/findTrackIDIndex/index.module.scss new file mode 100644 index 000000000..796ff6e40 --- /dev/null +++ b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/findTrackIDIndex/index.module.scss @@ -0,0 +1,8 @@ +.container { + padding: 12px 20px; + .content { + display: flex; + justify-content: space-between; + margin-top: 4px; + } +} diff --git a/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/findTrackIDIndex/index.tsx b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/findTrackIDIndex/index.tsx new file mode 100644 index 000000000..fcddafabc --- /dev/null +++ b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/components/findTrackIDIndex/index.tsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from 'react'; +import styles from './index.module.scss'; +import { Input, message } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { PointCloudUtils } from '@labelbee/lb-utils'; +import { IFileItem } from '@/types/data'; +import classNames from 'classnames'; +import { useDispatch } from '@/store/ctx'; +import { PageJump } from '@/store/annotation/actionCreators'; + +interface IProps { + imgList: IFileItem[]; + imgIndex: number; +} + +const FindTrackIDIndex = (props: IProps) => { + const { imgList, imgIndex } = props; + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [trackID, setTrackID] = useState(0); + const [list, setList] = useState([]); + const currentIndex = list.findIndex((item) => item === imgIndex); + + const onPressEnter = (e: any) => { + const inputValue = e.target.value; + const newTrackID = parseInt(inputValue, 10); + if (!(newTrackID > 0)) { + message.error(t('PositiveIntegerCheck')); + return; + } + setTrackID(newTrackID); + }; + + useEffect(() => { + if (trackID) { + const list = PointCloudUtils.getIndexByTrackID(trackID, imgList); + if (list?.length) { + setList(list); + } + } + }, [trackID, imgIndex]); + + const onPrev = () => { + if (currentIndex < 0) { + return; + } + dispatch(PageJump(list[currentIndex - 1])); + }; + + const onNext = () => { + if (currentIndex === list.length - 1) { + return; + } + dispatch(PageJump(list[currentIndex + 1])); + }; + + return ( +
+
查找标注框ID对应帧
+
+ +
+ {currentIndex > -1 && ( + + 帧: {currentIndex + 1} + + )} +
  • + +
  • +
  • + +
  • +
    +
    +
    + ); +}; + +export default FindTrackIDIndex; diff --git a/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/index.tsx b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/index.tsx index 5eff6957a..5a1947c18 100644 --- a/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/index.tsx +++ b/packages/lb-components/src/views/MainView/sidebar/PointCloudToolSidebar/index.tsx @@ -3,7 +3,7 @@ import { EditFilled } from '@ant-design/icons'; import { ToolIcons } from '../ToolIcons'; import { cTool } from '@labelbee/lb-annotation'; import { PointCloudContext } from '@/components/pointCloudView/PointCloudContext'; -import { Select, Tag, message, Input, Divider } from 'antd'; +import { Select, message, Input, Divider } from 'antd'; import { AppState } from '@/store'; import StepUtils from '@/utils/StepUtils'; import { connect } from 'react-redux'; @@ -15,9 +15,10 @@ import { useSingleBox } from '@/components/pointCloudView/hooks/useSingleBox'; import { useTranslation } from 'react-i18next'; import { LabelBeeContext, useDispatch } from '@/store/ctx'; import BatchUpdateModal from './components/batchUpdateModal'; +import AnnotatedBox from './components/annotatedBox'; +import FindTrackIDIndex from './components/findTrackIDIndex'; import { IFileItem } from '@/types/data'; import { - PointCloudUtils, IInputList, IDefaultSize, EPointCloudSegmentStatus, @@ -45,58 +46,6 @@ interface IProps { enableColorPicker?: boolean; } -// Temporarily hidden, this feature does not support the function for the time being. -const AnnotatedBox = ({ imgList, imgIndex }: { imgList: IFileItem[]; imgIndex: number }) => { - const ptCtx = useContext(PointCloudContext); - const [showIDs, setShowIds] = useState([]); - const { t } = useTranslation(); - - useEffect(() => { - const newImgList = imgList as Array<{ result: string }>; - let trackMap = new Map(); - setShowIds( - PointCloudUtils.getAllPointCloudResult({ - imgList: newImgList, - extraBoxList: pointCloudBoxList, - ignoreIndexList: [imgIndex], - }) - .filter((v) => { - if (!v.trackID) { - return false; - } - - if (trackMap.get(v.trackID)) { - return false; - } - trackMap.set(v.trackID, true); - return true; - }) - .sort((a, b) => { - const aTrackID = a?.trackID ?? 0; - const bTrackID = b?.trackID ?? 0; - - return aTrackID - bTrackID; - }) - .map((v) => v?.trackID ?? 0), - ); - }, [ptCtx.pointCloudBoxList, imgList]); - - const { pointCloudBoxList } = ptCtx; - - return ( -
    -
    {t('AllTrackIDs')}
    -
    - {showIDs.map((id) => ( - - {id} - - ))} -
    -
    - ); -}; - const BoxTrackIDInput = () => { const [isEdit, setIsEdit] = useState(false); const ptCtx = useContext(PointCloudContext); @@ -555,6 +504,8 @@ const PointCloudToolSidebar: React.FC = ({ + + )} diff --git a/packages/lb-utils/src/PointCloudUtils.ts b/packages/lb-utils/src/PointCloudUtils.ts index d33aa3d6f..58efa991d 100644 --- a/packages/lb-utils/src/PointCloudUtils.ts +++ b/packages/lb-utils/src/PointCloudUtils.ts @@ -435,6 +435,26 @@ class PointCloudUtils { return boxList; } + public static getIndexByTrackID(id: number, imgList: any) { + const arr: number[] = []; + // 解析字符串 + const resultList = imgList.map((v:any) => this.jsonParser(v.result)); + + const DEFAULT_STEP_NAME = `step_1`; + + resultList.forEach((result:any, index:number) => { + const boxes = result?.[DEFAULT_STEP_NAME]?.['result']; + if (boxes?.length > 0) { + const box = boxes?.find((v:any) => v?.trackID === id); + if (box) { + arr.push(index); + } + } + }); + + return arr; + } + public static getNextTrackID({ imgList, step = 1, diff --git a/packages/lb-utils/src/i18n/resources.json b/packages/lb-utils/src/i18n/resources.json index 736d6d904..f5ed596bc 100644 --- a/packages/lb-utils/src/i18n/resources.json +++ b/packages/lb-utils/src/i18n/resources.json @@ -349,7 +349,11 @@ "Triangle": "Triangle", "Statistics": "Statistics", "Sequence": "Sequence", - "Physics": "Physics" + "Physics": "Physics", + "IndicatorJudgment": "Indicator judgment", + "OnlyCurrentFrame": "Only current frame", + "ClickOnTheIdToHighlightTheMarkupBox": "Click on the ID to highlight the markup box", + "DoubleClickOnTheIdToContinuouslyHighlightBoxesAcrossFrames": "Double-click on the ID to continuously highlight boxes across frames" }, "cn": { "TextInput": "文本输入", @@ -701,6 +705,10 @@ "Triangle": "三角", "Statistics": "统计", "Sequence": "数列", - "Physics": "物理" + "Physics": "物理", + "IndicatorJudgment":"指标判断", + "OnlyCurrentFrame": "只看当前帧", + "ClickOnTheIdToHighlightTheMarkupBox": "点击ID可以高亮标注框", + "DoubleClickOnTheIdToContinuouslyHighlightBoxesAcrossFrames": "双击ID可以跨帧连续高亮标注框" } -} \ No newline at end of file +}