未验证 提交 6af3be6c 编写于 作者: B Boris Sekachev 提交者: GitHub

Added ability to setup text labels content (#4029)

* Added ability to setup text labels content

* Updated changelog

* Fixed wrong test

* Added minimum font size const
上级 3cf5265b
......@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Data sorting option (<https://github.com/openvinotoolkit/cvat/pull/3937>)
- Options to change font size & position of text labels on the canvas (<https://github.com/openvinotoolkit/cvat/pull/3972>)
- Add "tag" return type for automatic annotation in Nuclio (<https://github.com/openvinotoolkit/cvat/pull/3896>)
- User is able to customize information that text labels show (<https://github.com/openvinotoolkit/cvat/pull/4029>)
### Changed
- TDB
......
{
"name": "cvat-canvas",
"version": "2.10.2",
"version": "2.11.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cvat-canvas",
"version": "2.10.2",
"version": "2.11.0",
"license": "MIT",
"dependencies": {
"@types/polylabel": "^1.0.5",
......
{
"name": "cvat-canvas",
"version": "2.10.2",
"version": "2.11.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {
......
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import consts from './consts';
import { MasterImpl } from './master';
export interface Size {
......@@ -57,6 +58,7 @@ export interface Configuration {
displayAllText?: boolean;
textFontSize?: number;
textPosition?: 'auto' | 'center';
textContent?: string;
undefinedAttrValue?: string;
showProjections?: boolean;
forceDisableEditing?: boolean;
......@@ -263,6 +265,9 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
displayAllText: false,
autoborders: false,
undefinedAttrValue: '',
textContent: 'id,label,attributes,source,descriptions',
textPosition: 'auto',
textFontSize: consts.DEFAULT_SHAPE_TEXT_SIZE,
},
imageBitmap: false,
image: null,
......@@ -649,7 +654,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.displayAllText = configuration.displayAllText;
}
if (typeof configuration.textFontSize === 'number') {
if (typeof configuration.textFontSize === 'number' && configuration.textFontSize >= consts.MINIMUM_TEXT_FONT_SIZE) {
this.data.configuration.textFontSize = configuration.textFontSize;
}
......@@ -657,6 +662,13 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.textPosition = configuration.textPosition;
}
if (typeof configuration.textContent === 'string') {
const splitted = configuration.textContent.split(',').filter((entry: string) => !!entry);
if (splitted.every((entry: string) => ['id', 'label', 'attributes', 'source', 'descriptions'].includes(entry))) {
this.data.configuration.textContent = configuration.textContent;
}
}
if (typeof configuration.showProjections === 'boolean') {
this.data.configuration.showProjections = configuration.showProjections;
}
......
......@@ -1189,9 +1189,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}
const recreateText = configuration.textContent !== this.configuration.textContent;
const updateTextPosition = configuration.displayAllText !== this.configuration.displayAllText ||
configuration.textFontSize !== this.configuration.textFontSize ||
configuration.textPosition !== this.configuration.textPosition;
configuration.textPosition !== this.configuration.textPosition ||
recreateText;
if (configuration.smoothImage === true) {
this.background.classList.remove('cvat_canvas_pixelized');
......@@ -1200,6 +1202,19 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
this.configuration = configuration;
if (recreateText) {
const states = this.controller.objects;
for (const key of Object.keys(this.drawnStates)) {
const clientID = +key;
const [state] = states.filter((_state: any) => _state.clientID === clientID);
if (clientID in this.svgTexts) {
this.svgTexts[clientID].remove();
delete this.svgTexts[clientID];
if (state) this.svgTexts[clientID] = this.addText(state);
}
}
}
if (updateTextPosition) {
for (const i in this.drawnStates) {
if (i in this.svgTexts) {
......@@ -2071,8 +2086,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Update text position after corresponding box has been moved, resized, etc.
private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void {
if (text.node.style.display === 'none') return; // wrong transformation matrix
const textFontSize = this.configuration.textFontSize || consts.DEFAULT_SHAPE_TEXT_SIZE;
const textPosition = this.configuration.textPosition || 'auto';
const { textFontSize, textPosition } = this.configuration;
text.untransform();
text.style({ 'font-size': `${textFontSize}px` });
......@@ -2131,8 +2145,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Translate found coordinates to text SVG
const [x, y, rotX, rotY]: number[] = translateToSVG(this.text, [
clientX + consts.TEXT_MARGIN,
clientY + consts.TEXT_MARGIN,
clientX + (textPosition === 'auto' ? consts.TEXT_MARGIN : 0),
clientY + (textPosition === 'auto' ? consts.TEXT_MARGIN : 0),
clientCX,
clientCY,
]);
......@@ -2156,6 +2170,13 @@ export class CanvasViewImpl implements CanvasView, Listener {
private addText(state: any): SVG.Text {
const { undefinedAttrValue } = this.configuration;
const content = this.configuration.textContent;
const withID = content.includes('id');
const withAttr = content.includes('attributes');
const withLabel = content.includes('label');
const withSource = content.includes('source');
const withDescriptions = content.includes('descriptions');
const textFontSize = this.configuration.textFontSize || 12;
const {
label, clientID, attributes, source, descriptions,
......@@ -2167,28 +2188,32 @@ export class CanvasViewImpl implements CanvasView, Listener {
return this.adoptedText
.text((block): void => {
block.tspan(`${label.name} ${clientID} (${source})`).style({
block.tspan(`${withLabel ? label.name : ''} ${withID ? clientID : ''} ${withSource ? `(${source})` : ''}`).style({
'text-transform': 'uppercase',
});
for (const desc of descriptions) {
block
.tspan(`${desc}`)
.attr({
dy: '1em',
x: 0,
})
.addClass('cvat_canvas_text_description');
if (withDescriptions) {
for (const desc of descriptions) {
block
.tspan(`${desc}`)
.attr({
dy: '1em',
x: 0,
})
.addClass('cvat_canvas_text_description');
}
}
for (const attrID of Object.keys(attributes)) {
const value = attributes[attrID] === undefinedAttrValue ? '' : attributes[attrID];
block
.tspan(`${attrNames[attrID]}: ${value}`)
.attr({
attrID,
dy: '1em',
x: 0,
})
.addClass('cvat_canvas_text_attribute');
if (withAttr) {
for (const attrID of Object.keys(attributes)) {
const value = attributes[attrID] === undefinedAttrValue ? '' : attributes[attrID];
block
.tspan(`${attrNames[attrID]}: ${value}`)
.attr({
attrID,
dy: '1em',
x: 0,
})
.addClass('cvat_canvas_text_attribute');
}
}
})
.move(0, 0)
......
......@@ -20,6 +20,7 @@ const BASE_PATTERN_SIZE = 5;
const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1;
const SNAP_TO_ANGLE_RESIZE_SHIFT = 15;
const DEFAULT_SHAPE_TEXT_SIZE = 12;
const MINIMUM_TEXT_FONT_SIZE = 8;
export default {
BASE_STROKE_WIDTH,
......@@ -39,4 +40,5 @@ export default {
SNAP_TO_ANGLE_RESIZE_DEFAULT,
SNAP_TO_ANGLE_RESIZE_SHIFT,
DEFAULT_SHAPE_TEXT_SIZE,
MINIMUM_TEXT_FONT_SIZE,
};
{
"name": "cvat-ui",
"version": "1.28.2",
"version": "1.29.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cvat-ui",
"version": "1.28.2",
"version": "1.29.0",
"license": "MIT",
"dependencies": {
"@ant-design/icons": "^4.6.3",
......
{
"name": "cvat-ui",
"version": "1.28.2",
"version": "1.29.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
......
......@@ -25,6 +25,7 @@ export enum SettingsActionTypes {
SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE',
SWITCH_TEXT_FONT_SIZE = 'SWITCH_TEXT_FONT_SIZE',
SWITCH_TEXT_POSITION = 'SWITCH_TEXT_POSITION',
SWITCH_TEXT_CONTENT = 'SWITCH_TEXT_CONTENT',
CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL',
CHANGE_CONTRAST_LEVEL = 'CHANGE_CONTRAST_LEVEL',
CHANGE_SATURATION_LEVEL = 'CHANGE_SATURATION_LEVEL',
......@@ -196,6 +197,15 @@ export function switchTextPosition(position: 'auto' | 'center'): AnyAction {
};
}
export function switchTextContent(textContent: string): AnyAction {
return {
type: SettingsActionTypes.SWITCH_TEXT_CONTENT,
payload: {
textContent,
},
};
}
export function changeBrightnessLevel(level: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL,
......
......@@ -62,6 +62,7 @@ interface Props {
showObjectsTextAlways: boolean;
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
showAllInterpolationTracks: boolean;
workspace: Workspace;
automaticBordering: boolean;
......@@ -111,6 +112,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
smoothImage,
textFontSize,
textPosition,
textContent,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
......@@ -130,6 +132,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
creationOpacity: selectedOpacity,
textFontSize,
textPosition,
textContent,
});
this.initialSetup();
......@@ -166,6 +169,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
showObjectsTextAlways,
textFontSize,
textPosition,
textContent,
showAllInterpolationTracks,
automaticBordering,
intelligentPolygonCrop,
......@@ -182,7 +186,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
prevProps.selectedOpacity !== selectedOpacity ||
prevProps.smoothImage !== smoothImage ||
prevProps.textFontSize !== textFontSize ||
prevProps.textPosition !== textPosition
prevProps.textPosition !== textPosition ||
prevProps.textContent !== textContent
) {
canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
......@@ -194,6 +199,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
smoothImage,
textFontSize,
textPosition,
textContent,
});
}
......
......@@ -38,6 +38,10 @@
}
}
.cvat-workspace-settings-text-content {
width: 100%;
}
.cvat-workspace-settings-approx-poly-threshold {
user-select: none;
}
......
......@@ -9,13 +9,13 @@ import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import InputNumber from 'antd/lib/input-number';
import Text from 'antd/lib/typography/Text';
import Slider from 'antd/lib/slider';
import Select from 'antd/lib/select';
import {
MAX_ACCURACY,
marks,
} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy';
import { clamp } from 'utils/math';
import { Select } from 'antd';
interface Props {
autoSave: boolean;
......@@ -28,6 +28,7 @@ interface Props {
defaultApproxPolyAccuracy: number;
textFontSize: number;
textPosition: 'center' | 'auto';
textContent: string;
onSwitchAutoSave(enabled: boolean): void;
onChangeAutoSaveInterval(interval: number): void;
onChangeAAMZoomMargin(margin: number): void;
......@@ -38,6 +39,7 @@ interface Props {
onSwitchIntelligentPolygonCrop(enabled: boolean): void;
onChangeTextFontSize(fontSize: number): void;
onChangeTextPosition(position: 'auto' | 'center'): void;
onChangeTextContent(textContent: string[]): void;
}
function WorkspaceSettingsComponent(props: Props): JSX.Element {
......@@ -52,6 +54,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
defaultApproxPolyAccuracy,
textFontSize,
textPosition,
textContent,
onSwitchAutoSave,
onChangeAutoSaveInterval,
onChangeAAMZoomMargin,
......@@ -62,6 +65,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
onChangeDefaultApproxPolyAccuracy,
onChangeTextFontSize,
onChangeTextPosition,
onChangeTextContent,
} = props;
const minAutoSaveInterval = 1;
......@@ -137,6 +141,25 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
</Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-text-settings'>
<Col span={24}>
<Text>Content of a text</Text>
</Col>
<Col span={16}>
<Select
className='cvat-workspace-settings-text-content'
mode='multiple'
value={textContent.split(',').filter((entry: string) => !!entry)}
onChange={onChangeTextContent}
>
<Select.Option value='id'>ID</Select.Option>
<Select.Option value='label'>Label</Select.Option>
<Select.Option value='attributes'>Attributes</Select.Option>
<Select.Option value='source'>Source</Select.Option>
<Select.Option value='descriptions'>Descriptions</Select.Option>
</Select>
</Col>
</Row>
<Row className='cvat-workspace-settings-text-settings'>
<Col span={12}>
<Text>Position of a text</Text>
......
......@@ -85,6 +85,7 @@ interface StateToProps {
showObjectsTextAlways: boolean;
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
showAllInterpolationTracks: boolean;
workspace: Workspace;
minZLayer: number;
......@@ -168,6 +169,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
intelligentPolygonCrop,
textFontSize,
textPosition,
textContent,
},
shapes: {
opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections,
......@@ -216,6 +218,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
showObjectsTextAlways,
textFontSize,
textPosition,
textContent,
showAllInterpolationTracks,
curZLayer,
minZLayer,
......
......@@ -16,6 +16,7 @@ import {
changeDefaultApproxPolyAccuracy,
switchTextFontSize,
switchTextPosition,
switchTextContent,
} from 'actions/settings-actions';
import { CombinedState } from 'reducers/interfaces';
......@@ -33,6 +34,7 @@ interface StateToProps {
intelligentPolygonCrop: boolean;
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
}
interface DispatchToProps {
......@@ -46,6 +48,7 @@ interface DispatchToProps {
onChangeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): void;
onChangeTextFontSize(fontSize: number): void;
onChangeTextPosition(position: 'auto' | 'center'): void;
onChangeTextContent(textContent: string[]): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
......@@ -61,6 +64,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
defaultApproxPolyAccuracy,
textFontSize,
textPosition,
textContent,
} = workspace;
return {
......@@ -74,6 +78,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
defaultApproxPolyAccuracy,
textFontSize,
textPosition,
textContent,
};
}
......@@ -109,6 +114,10 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
onChangeTextPosition(position: 'auto' | 'center'): void {
dispatch(switchTextPosition(position));
},
onChangeTextContent(textContent: string[]): void {
const serialized = textContent.join(',');
dispatch(switchTextContent(serialized));
},
};
}
......
......@@ -642,6 +642,7 @@ export interface WorkspaceSettingsState {
toolsBlockerState: ToolsBlockerState;
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
}
export interface ShapesSettingsState {
......
......@@ -34,6 +34,7 @@ const defaultState: SettingsState = {
defaultApproxPolyAccuracy: 9,
textFontSize: 14,
textPosition: 'auto',
textContent: 'id,source,label,attributes,descriptions',
toolsBlockerState: {
algorithmsLocked: false,
buttonVisible: false,
......@@ -213,6 +214,16 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
},
};
}
case SettingsActionTypes.SWITCH_TEXT_CONTENT: {
const { textContent } = action.payload;
return {
...state,
workspace: {
...state.workspace,
textContent,
},
};
}
case SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL: {
return {
...state,
......
......@@ -678,12 +678,12 @@ Cypress.Commands.add('checkFrameNum', (frameNum) => {
});
Cypress.Commands.add('goToNextFrame', (expectedFrameNum) => {
cy.get('.cvat-player-next-button').click();
cy.get('.cvat-player-next-button').click().trigger('mouseout');
cy.checkFrameNum(expectedFrameNum);
});
Cypress.Commands.add('goToPreviousFrame', (expectedFrameNum) => {
cy.get('.cvat-player-previous-button').click();
cy.get('.cvat-player-previous-button').click().trigger('mouseout');
cy.checkFrameNum(expectedFrameNum);
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册