Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat optimize attribute display #559

Merged
merged 3 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 144 additions & 15 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 @@ -584,6 +591,12 @@ export class PointCloud extends EventListener {
group.add(boxID);
}

// 生成并添加attribute标签
const attributeLabel = this.generateBoxAttributeLabel(boxParams);
if (attributeLabel) {
group.add(attributeLabel);
}

group.add(box);
group.add(arrow);
if (center) {
Expand Down Expand Up @@ -1367,35 +1380,151 @@ 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 || this.hiddenText) return;

const classLabel = this.findSubAttributeLabel(boxParams, this.config);
const subAttributeLabel = classLabel ? `${boxParams.attribute}\n${classLabel}` : `${boxParams.attribute}`;

const { sprite, canvasWidth, canvasHeight } = this.generateLabel(subAttributeLabel, 100);

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) {
if (!boxParams?.subAttribute || typeof boxParams.subAttribute !== 'object' || !config) {
return '';
}

const { inputList } = config;
let resultStr = '';
// Return directly without any secondary attributes
const subAttributeKeys = Object.keys(boxParams.subAttribute);
if (subAttributeKeys.length === 0) return resultStr;

subAttributeKeys.forEach((key) => {
const classInfo = inputList.find((item: { value: string }) => item.value === key);
if (!classInfo || !classInfo.subSelected) return; // If the type of the secondary attribute cannot be found, it will be returned directly

const { key: classKey, subSelected } = classInfo;
const subItem = subSelected.find((item: { value: string }) => item.value === boxParams.subAttribute?.[key]);

if (subItem) {
if (resultStr) resultStr += '、';
resultStr += `${classKey}:${subItem.key}`;
}
});

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
39 changes: 39 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,9 @@ import {
IBasicStyle,
TAnnotationViewCuboid,
ImgPosUtils,
IPointCloudBox,
IPointCloudBoxList,
IBasicPolygon,
} from '@labelbee/lb-utils';
import _ from 'lodash';
import rgba from 'color-rgba';
Expand Down Expand Up @@ -76,6 +79,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 +297,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 +448,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 as IBasicPolygon;
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 +976,7 @@ export default class ViewOperation extends BasicToolOperation {
});

this.renderConnectionPoints();
this.renderAttribute();
} catch (e) {
console.error('ViewOperation Render Error', e);
}
Expand Down
33 changes: 28 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 = false,
} = 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,18 @@ const AnnotationView = (props: IProps, ref: any) => {
}
}, [src, measureVisible, fallbackSrc, loadImage]);

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

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

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