Skip to content

Commit

Permalink
feat: 3D point cloud supports custom colors in annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
lixinghua123 committed May 8, 2023
1 parent 5f52b80 commit a4733c7
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 11 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"dependencies": {
"react-colorful": "^5.6.1"
}
}
10 changes: 9 additions & 1 deletion packages/lb-annotation/src/core/pointCloud/annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,15 @@ export class PointCloudAnnotation implements IPointCloudAnnotationOperation {

public updateConfig(config: IPointCloudConfig) {
this.config = config;
this.pointCloud2dOperation.setConfig(JSON.stringify(config));
}

// compose attributeList
public updateAttributeList(attributeList: IInputList[]) {
this.config = {
...this.config,
attributeList,
};
this.toolScheduler.syncAllAttributeListInConfig(attributeList);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions packages/lb-annotation/src/core/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ export class ToolScheduler implements IToolSchedulerOperation {
});
}

public syncAllAttributeListInConfig(attributeList: any[]) {
this.toolOperationList.forEach((toolInstance) => {
const newConfig = {
...toolInstance.config,
attributeList,
};
toolInstance.setConfig(JSON.stringify(newConfig));
});
}

public setSize(size: ISize) {
this.toolOperationList.forEach((toolInstance) => {
toolInstance.setSize(size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ class LineToolOperation extends BasicToolOperation {
}

get minLength() {
return this.config.minLength;
return this.config?.minLength || 1;
}

get upperLimitPointNum() {
Expand Down
58 changes: 55 additions & 3 deletions packages/lb-components/src/components/attributeList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { COLORS_ARRAY, NULL_COLOR } from '@/data/Style';
import { ColorTag } from '@/components/colorTag';
import { Radio } from 'antd/es';
import React from 'react';
import React, { useState } from 'react';
import { Popover } from 'antd';
import ColorPalette from '../colorPalette';
import { CloseOutlined } from '@ant-design/icons';
import { i18n } from '@labelbee/lb-utils';

export const ATTRIBUTE_COLORS = [NULL_COLOR].concat(COLORS_ARRAY);

Expand All @@ -18,13 +22,18 @@ interface IProps {
noHeightLimit?: boolean;
num?: number | string;
style?: React.CSSProperties;
enableColorPicker?: boolean;
updateColorConfig?: (value: string, color: string) => void;
}

const AttributeList = React.forwardRef((props: IProps, ref) => {
const radioRef = React.useRef<any>();

const list = props.list || [];

const [paletteVisible, setPaletteVisible] = useState<boolean>(false);
const [editConfigIndex, setEditConfigIndex] = useState<number | undefined>(undefined);

let NEW_ATTRIBUTE_COLORS = [...ATTRIBUTE_COLORS];

// 去除默认的颜色
Expand All @@ -37,6 +46,10 @@ const AttributeList = React.forwardRef((props: IProps, ref) => {
className = 'sensebee-radio-group-no-limit-height';
}

const changeColor = (value: string, color: string) => {
props.updateColorConfig(value, color);
};

return (
<div className={className} style={props.style}>
<Radio.Group
Expand Down Expand Up @@ -69,9 +82,48 @@ const AttributeList = React.forwardRef((props: IProps, ref) => {
}

return (
<Radio value={i.value} ref={radioRef} key={index}>
<Radio value={i.value} ref={radioRef} key={i.label + index}>
<span className='sensebee-radio-label' title={i.label}>
{!props?.forbidColor && <ColorTag color={color} style={{ marginRight: '8px' }} />}
{!props?.forbidColor && (
<Popover
content={
<ColorPalette
defaultColor={color}
setColor={(color) => changeColor(i.value, color)}
/>
}
title={
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<span>{i18n.t('Palette')}</span>
<CloseOutlined onClick={() => setPaletteVisible(false)} />
</div>
}
visible={paletteVisible && editConfigIndex === index}
onVisibleChange={(visible: any) => {
if (!visible) {
return;
}
setPaletteVisible(visible);
}}
>
<ColorTag
color={color}
style={{ cursor: 'pointer', marginRight: '8px' }}
onClick={() => {
if (props?.enableColorPicker) {
setEditConfigIndex(index);
setPaletteVisible(true);
}
}}
/>
</Popover>
)}
{i.label}
</span>
<span className='sensebee-radio-num'>{hotKey}</span>
Expand Down
28 changes: 28 additions & 0 deletions packages/lb-components/src/components/colorPalette/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @file Color Palette
* @author lixinghua <[email protected]>
* @date 2023.04.24
*/
import React from 'react';
import { RgbaColorPicker } from 'react-colorful';
import { toRGBAObj, toRGBAStr } from '@/utils/colorUtils';

interface IProps {
defaultColor?: string;
setColor: (color: string) => void;
}

const Palette = (props: IProps) => {
const { setColor, defaultColor } = props;

return (
<RgbaColorPicker
color={defaultColor && toRGBAObj(defaultColor)}
onChange={(values) => {
const colorStr = toRGBAStr(values);
setColor(colorStr);
}}
/>
);
};
export default Palette;
3 changes: 2 additions & 1 deletion packages/lb-components/src/components/colorTag/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

export const ColorTag = ({ color, style }: any) => (
export const ColorTag = ({ color, style, onClick }: any) => (
<div
style={{
display: 'inline-block',
Expand All @@ -10,5 +10,6 @@ export const ColorTag = ({ color, style }: any) => (
verticalAlign: 'middle',
...style,
}}
onClick={onClick}
/>
);
22 changes: 22 additions & 0 deletions packages/lb-components/src/utils/colorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* transform rgba(a,g,b), to {r,g,b,a} / [r,g,b,a]
* @param color
*/
export const toRGBAObj = (rgbStr: string, toArray?: boolean) => {
const match = rgbStr.replace(/[rgba()]/g, '').split(',');
if (match) {
const [r, g, b, a] = match;
if (toArray) {
return [r, g, b, a];
}
return { r: Number(r), g: Number(g), b: Number(b), a: Number(a) };
}
return '';
};

/**
* transform {a,g,b,a} to rgba{r,g,b,a}
* @param color
*/
export const toRGBAStr = (color: { r: number; g: number; b: number; a: number }) =>
`rgba(${color.r},${color.g},${color.b},${color.a})`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@ import { ICustomToolInstance } from '@/hooks/annotation';
import { useStatus } from '@/components/pointCloudView/hooks/useStatus';
import { useSingleBox } from '@/components/pointCloudView/hooks/useSingleBox';
import { useTranslation } from 'react-i18next';
import { LabelBeeContext } from '@/store/ctx';
import { LabelBeeContext, useDispatch } from '@/store/ctx';
import BatchUpdateModal from './components/batchUpdateModal';
import { IFileItem } from '@/types/data';
import { PointCloudUtils } from '@labelbee/lb-utils';
import AttributeList from '@/components/attributeList';
import { IInputList } from '@/types/main';
import { useAttribute } from '@/components/pointCloudView/hooks/useAttribute';
import { SetTaskStepList } from '@/store/annotation/actionCreators';

interface IProps {
stepInfo: IStepInfo;
toolInstance: ICustomToolInstance; // Created by useCustomToolInstance.
imgList: IFileItem[];
imgIndex: number;
stepList: IStepInfo[];
}

const dispatch = useDispatch();
// 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);
Expand Down Expand Up @@ -189,10 +191,16 @@ const AttributeUpdater = ({
attributeList,
subAttributeList,
toolInstance,
config,
stepList,
stepInfo,
}: {
toolInstance: ICustomToolInstance;
attributeList: any[]; // TODO
subAttributeList: any[]; // TODO
config: any;
stepList: IStepInfo[];
stepInfo: IStepInfo;
}) => {
const { selectedBox } = useSingleBox();
const ptx = useContext(PointCloudContext);
Expand All @@ -212,6 +220,29 @@ const AttributeUpdater = ({
wordWrap: 'break-word' as any, // WordWrap Type ?
};

const updateColorConfig = (value: string, color: string) => {
const attributeList = config?.attributeList?.map((i: any) => {
if (i.value === value) {
return { ...i, color };
}
return i;
});

const formatConfig = { ...config, attributeList };
const configStr = JSON.stringify(formatConfig);
const formatStepList = stepList?.map((i: IStepInfo) => {
if (i?.step === stepInfo?.step) {
return { ...i, config: configStr };
}
return i;
});
ptx?.topViewInstance?.updateAttributeList(attributeList);
ptx?.sideViewInstance?.updateAttributeList(attributeList);
ptx?.backViewInstance?.updateAttributeList(attributeList);
ptx?.mainViewInstance?.setConfig(formatConfig);
dispatch(SetTaskStepList({ stepList: formatStepList }));
};

const setAttribute = (attribute: string) => {
toolInstance.setDefaultAttribute(attribute);
};
Expand All @@ -234,6 +265,8 @@ const AttributeUpdater = ({
forbidDefault={true}
selectedAttribute={defaultAttribute ?? ''}
attributeChanged={(attribute: string) => setAttribute(attribute)}
updateColorConfig={updateColorConfig}
enableColorPicker={true}
/>
<Divider style={{ margin: 0 }} />
{selectedBox && (
Expand Down Expand Up @@ -285,7 +318,13 @@ const AttributeUpdater = ({
);
};

const PointCloudToolSidebar: React.FC<IProps> = ({ stepInfo, toolInstance, imgList, imgIndex }) => {
const PointCloudToolSidebar: React.FC<IProps> = ({
stepInfo,
toolInstance,
imgList,
imgIndex,
stepList,
}) => {
const { updatePointCloudPattern, pointCloudPattern } = useStatus();

const config = jsonParser(stepInfo.config);
Expand All @@ -311,6 +350,9 @@ const PointCloudToolSidebar: React.FC<IProps> = ({ stepInfo, toolInstance, imgLi
toolInstance={toolInstance}
attributeList={attributeList}
subAttributeList={subAttributeList}
config={config}
stepList={stepList}
stepInfo={stepInfo}
/>
</>
);
Expand All @@ -319,12 +361,14 @@ const PointCloudToolSidebar: React.FC<IProps> = ({ stepInfo, toolInstance, imgLi
const mapStateToProps = (state: AppState) => {
const stepInfo = StepUtils.getCurrentStepInfo(state.annotation?.step, state.annotation?.stepList);
const toolInstance = state.annotation?.toolInstance;
const stepList = state.annotation?.stepList;

return {
stepInfo,
toolInstance,
imgList: state.annotation.imgList,
imgIndex: state.annotation.imgIndex,
stepList,
};
};

Expand Down
6 changes: 4 additions & 2 deletions packages/lb-utils/src/i18n/resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@
"CameraFollowTopView": "Positioned by top view",
"MarkOnlyOne": "Mark only one frame of data, need to mark the first and last frame of data",
"HaveNoNeed": "Have no need to predict the data between two frames, please mark the first and last frame first, and need the same serial number",
"PredictingDataFrom": "Predicting data from page {{startPage}} to page {{endPage}} with box ID {{selectedBoxTrackID}}"
"PredictingDataFrom": "Predicting data from page {{startPage}} to page {{endPage}} with box ID {{selectedBoxTrackID}}",
"Palette": "Palette"
},
"cn": {
"TextInput": "文本输入",
Expand Down Expand Up @@ -415,6 +416,7 @@
"CameraFollowTopView": "按俯视图定位",
"MarkOnlyOne": "仅标注一帧数据,需标出首尾的帧数据",
"HaveNoNeed": "两帧之间暂无需预测的数据,请先标出首尾的框,需同序号",
"PredictingDataFrom": "正在预测第 {{startPage}} 页到第 {{endPage}} 页之间框ID为 {{selectedBoxTrackID}} 的数据"
"PredictingDataFrom": "正在预测第 {{startPage}} 页到第 {{endPage}} 页之间框ID为 {{selectedBoxTrackID}} 的数据",
"Palette": "调色盘"
}
}

0 comments on commit a4733c7

Please sign in to comment.