Skip to content

Commit

Permalink
feat: Optimization of attribute display logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Original-Recipe committed Sep 11, 2024
1 parent c5dd913 commit ebfe6d7
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 34 deletions.
156 changes: 130 additions & 26 deletions packages/lb-annotation/src/core/pointCloud/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PointCloudUtils,
DEFAULT_SPHERE_PARAMS,
ICalib,
IPointCloudBoxList,
} from '@labelbee/lb-utils';
import { BufferAttribute, OrthographicCamera, PerspectiveCamera } from 'three';
import HighlightWorker from 'web-worker:./highlightWorker.js';
Expand Down Expand Up @@ -57,6 +58,8 @@ interface IProps {

isSegment?: boolean;
checkMode?: boolean;

hiddenText?: boolean;
}

interface IPipeTypes {
Expand Down Expand Up @@ -154,6 +157,8 @@ export class PointCloud extends EventListener {

private pipe?: IPipeTypes;

private hiddenText = false;

constructor({
container,
noAppend,
Expand All @@ -163,13 +168,15 @@ export class PointCloud extends EventListener {
config,
isSegment,
checkMode,
hiddenText = false,
}: IProps) {
super();
this.container = container;
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.backgroundColor = backgroundColor;
this.config = config;
this.checkMode = checkMode ?? false;
this.hiddenText = hiddenText;

// TODO: Need to extracted.
if (isOrthographicCamera && orthographicParams) {
Expand Down Expand Up @@ -1373,52 +1380,149 @@ export class PointCloud extends EventListener {
return arrowHelper;
};

public generateBoxTrackID = (boxParams: IPointCloudBox) => {
if (!boxParams.trackID) {
return;
}
/**
* Universal generation of label information
* @param text generation text
* @param scaleFactor scale size
* @returns { sprite, canvasWidth, canvasHeight }
*/
public generateLabel = (text: string, scaleFactor: number) => {
const canvas = this.getTextCanvas(text);
const texture = new THREE.Texture(canvas);

const texture = new THREE.Texture(this.getTextCanvas(boxParams.trackID.toString()));
// Use filters that are more suitable for UI and text
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.needsUpdate = true;
const sprite = new THREE.SpriteMaterial({ map: texture, depthWrite: false });
const boxID = new THREE.Sprite(sprite);
boxID.scale.set(5, 5, 5);
boxID.position.set(-boxParams.width / 2, 0, boxParams.depth / 2 + 0.5);
return boxID;

// Calculate canvas width and height to avoid blurring
const canvasWidth = canvas.width / window.devicePixelRatio;
const canvasHeight = canvas.height / window.devicePixelRatio;

const spriteMaterial = new THREE.SpriteMaterial({ map: texture, depthWrite: false });
const sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(canvasWidth / scaleFactor, canvasHeight / scaleFactor, 1);

return { sprite, canvasWidth, canvasHeight };
};

/**
* Generate label information for ID
* @param boxParams
* @returns sprite
*/
public generateBoxTrackID = (boxParams: IPointCloudBox) => {
if (!boxParams.trackID) return;

const { sprite } = this.generateLabel(boxParams.trackID.toString(), 50);

sprite.position.set(-boxParams.width / 2, 0, boxParams.depth / 2 + 0.5);

return sprite;
};

/**
* Generate label information for secondary attributes
* @param boxParams
* @returns sprite
*/
public generateBoxAttributeLabel = (boxParams: IPointCloudBox) => {
if (!boxParams.attribute) {
return;
}
if (!boxParams.attribute || this.hiddenText) return;

const texture = new THREE.Texture(this.getTextCanvas(boxParams.attribute.toString()));
texture.needsUpdate = true;
const sprite = new THREE.SpriteMaterial({ map: texture, depthWrite: false });
const attributeLabel = new THREE.Sprite(sprite);
attributeLabel.scale.set(5, 5, 5);
const classLabel = this.findSubAttributeLabel(boxParams, this.config);
const subAttributeLabel = classLabel ? `${boxParams.attribute}\n${classLabel}` : `${boxParams.attribute}`;

// 将label放在box的下方
attributeLabel.position.set(-boxParams.width / 2, boxParams.height, -boxParams.depth / 2);
const { sprite, canvasWidth, canvasHeight } = this.generateLabel(subAttributeLabel, 100);

return attributeLabel;
sprite.position.set(
-boxParams.width / 2,
boxParams.height / 2 - canvasWidth / 200,
-boxParams.depth / 2 - canvasHeight / 150,
);

return sprite;
};

/**
* Splicing sub attribute content
* @param boxParams
* @param config
* @returns
*/
public findSubAttributeLabel(boxParams: IPointCloudBox, config: IPointCloudConfig) {
const { inputList } = config;
let resultStr = '';
// Return directly without any secondary attributes
if (Object.keys(boxParams.subAttribute).length === 0) return resultStr;

Object.keys(boxParams.subAttribute).forEach((key) => {
const classInfo = inputList.find((item: { value: string }) => item.value === key);
// If the type of the secondary attribute cannot be found, it will be returned directly
if (!classInfo) return;
resultStr = `${resultStr + classInfo.key}:`;
const { subSelected } = classInfo;
subSelected.forEach((subItem: { value: string; key: string }, index: number) => {
if (subItem.value === boxParams.subAttribute[key]) {
resultStr += subItem.key;
if (index !== subSelected.length - 1) {
resultStr += '、';
}
}
});
});

return resultStr;
}

public getTextCanvas(text: string) {
const canvas = document.createElement('canvas');

const ctx = canvas.getContext('2d');
// Obtain the pixel ratio of the device
const dpr = window.devicePixelRatio || 1;
const fontSize = 50;

if (ctx) {
ctx.font = `${50}px " bold`;
ctx.font = `${fontSize}px bold`;
// Split text into multiple lines using line breaks
const lines = text.split('\n');

// Find the longest row and calculate its width
const maxWidth = Math.max(...lines.map((line) => ctx.measureText(line).width));

// Calculate the logical width and height of the canvas
const canvasWidth = Math.ceil(maxWidth);
const lineHeight = fontSize * 1.5; // 每行的高度
const canvasHeight = lineHeight * lines.length;

// Modify the logical and physical width and height of the canvas
canvas.width = canvasWidth * dpr;
canvas.height = canvasHeight * dpr;

canvas.style.width = `${canvasWidth}px`;
canvas.style.height = `${canvasHeight}px`;

ctx.scale(dpr, dpr);

// Reset font (font size using dpr scaling)
ctx.font = `${fontSize}px bold`;
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
ctx.textAlign = 'left';
ctx.textBaseline = 'top';

// Draw text line by line
lines.forEach((line, index) => {
ctx.fillText(line, 0, index * lineHeight); // The Y coordinate of each line of text is index * line height
});
}

return canvas;
}

public updateHiddenTextAndRender(hiddenText: boolean, pointCloudBoxList: IPointCloudBoxList) {
this.hiddenText = hiddenText;
this.generateBoxes(pointCloudBoxList);
}

/**
* Filter road points and noise in all directions
* 1. The first 5% of the z-axis is used as the road coordinate
Expand Down
40 changes: 40 additions & 0 deletions packages/lb-annotation/src/core/toolOperation/ViewOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
IBasicStyle,
TAnnotationViewCuboid,
ImgPosUtils,
IPointCloudBox,
IPointCloudBoxList,
} from '@labelbee/lb-utils';
import _ from 'lodash';
import rgba from 'color-rgba';
Expand All @@ -36,6 +38,8 @@ interface IViewOperationProps extends IBasicToolOperationProps {
style: IBasicStyle;
staticMode?: boolean;
annotations: TAnnotationViewData[];
pointCloudBoxList: IPointCloudBoxList;
hiddenText: boolean;
}

export interface ISpecificStyle {
Expand Down Expand Up @@ -76,6 +80,10 @@ export default class ViewOperation extends BasicToolOperation {

private convexHullGroup: IConvexHullGroupType = {};

private pointCloudBoxList: IPointCloudBoxList;

private hiddenText: boolean = false;

constructor(props: IViewOperationProps) {
super({ ...props, showDefaultCursor: true });
this.style = props.style ?? { stroke: DEFAULT_STROKE_COLOR, thickness: 3 };
Expand Down Expand Up @@ -290,6 +298,15 @@ export default class ViewOperation extends BasicToolOperation {
}
}

public setPointCloudBoxList(pointCloudBoxList: IPointCloudBoxList) {
this.pointCloudBoxList = pointCloudBoxList;
}

public setHiddenText(hiddenText: boolean) {
this.hiddenText = hiddenText;
this.render();
}

public setConfig(config: { [a: string]: any } | string) {
this.config = config;
}
Expand Down Expand Up @@ -432,6 +449,28 @@ export default class ViewOperation extends BasicToolOperation {
});
}

/**
* Separate rendering of sub attribute content
* The principle is the same as other tools for rendering sub attribute content
*/
public renderAttribute() {
const annotationChunks = _.chunk(this.annotations, 6);
annotationChunks.forEach((annotationList) => {
const annotation = annotationList.find((item) => item.type === 'polygon');
if (!annotation) return;

const { fontStyle } = this.getRenderStyle(annotation);
const polygon = annotation.annotation;
const curPointCloudBox = this.pointCloudBoxList.find((item: IPointCloudBox) => item.id === polygon.id);
const headerText = this.hiddenText ? '' : curPointCloudBox.attribute;
const renderPolygon = AxisUtils.changePointListByZoom(polygon?.pointList ?? [], this.zoom, this.currentPos);

if (headerText) {
DrawUtils.drawText(this.canvas, this.appendOffset(renderPolygon[0]), headerText, fontStyle);
}
});
}

public getRenderStyle(annotation: TAnnotationViewData) {
const style = this.getSpecificStyle(annotation.annotation);
const fontStyle = this.getFontStyle(annotation.annotation, style);
Expand Down Expand Up @@ -938,6 +977,7 @@ export default class ViewOperation extends BasicToolOperation {
});

this.renderConnectionPoints();
this.renderAttribute();
} catch (e) {
console.error('ViewOperation Render Error', e);
}
Expand Down
29 changes: 24 additions & 5 deletions packages/lb-components/src/components/AnnotationView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
* @author laoluo
*/

import React, { useEffect, useCallback, useRef, useImperativeHandle, useState, useContext } from 'react';
import React, {
useEffect,
useCallback,
useRef,
useImperativeHandle,
useState,
useContext,
} from 'react';
import { ViewOperation, ImgUtils } from '@labelbee/lb-annotation';
import { Spin } from 'antd/es';
import useRefCache from '@/hooks/useRefCache';
import { TAnnotationViewData } from '@labelbee/lb-utils';
import { TAnnotationViewData, IPointCloudBoxList } from '@labelbee/lb-utils';
import MeasureCanvas from '../measureCanvas';
import { PointCloudContext } from '@/components/pointCloudView/PointCloudContext';

Expand Down Expand Up @@ -41,7 +48,9 @@ interface IProps {
};
staticMode?: boolean;
measureVisible?: boolean;
onRightClick?: (e: { event: MouseEvent, targetId: string }) => void;
onRightClick?: (e: { event: MouseEvent; targetId: string }) => void;
pointCloudBoxList: IPointCloudBoxList;
hiddenText: boolean;
}

const DEFAULT_SIZE = {
Expand Down Expand Up @@ -88,7 +97,9 @@ const AnnotationView = (props: IProps, ref: any) => {
globalStyle,
afterImgOnLoad,
measureVisible,
onRightClick
onRightClick,
pointCloudBoxList,
hiddenText,
} = props;
const size = sizeInitialized(props.size);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -168,7 +179,7 @@ const AnnotationView = (props: IProps, ref: any) => {
viewOperation.current?.setLoading(false);
setLoading(false);
},
[loadAndSetImage, fallbackSrc]
[loadAndSetImage, fallbackSrc],
);

useEffect(() => {
Expand All @@ -177,6 +188,14 @@ const AnnotationView = (props: IProps, ref: any) => {
}
}, [src, measureVisible, fallbackSrc, loadImage]);

useEffect(() => {
viewOperation.current.setPointCloudBoxList(pointCloudBoxList);
}, [pointCloudBoxList]);

useEffect(() => {
viewOperation.current.setHiddenText(hiddenText);
}, [hiddenText]);

/**
* 基础数据绘制监听
*
Expand Down
Loading

0 comments on commit ebfe6d7

Please sign in to comment.