Skip to content

Commit

Permalink
Added ability to setup text labels content (#4029)
Browse files Browse the repository at this point in the history
* Added ability to setup text labels content

* Updated changelog

* Fixed wrong test

* Added minimum font size const
  • Loading branch information
Boris Sekachev authored Dec 16, 2021
1 parent 3cf5265 commit 6af3be6
Show file tree
Hide file tree
Showing 17 changed files with 142 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions cvat-canvas/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-canvas/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"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": {
Expand Down
14 changes: 13 additions & 1 deletion cvat-canvas/src/typescript/canvasModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT

import consts from './consts';
import { MasterImpl } from './master';

export interface Size {
Expand Down Expand Up @@ -57,6 +58,7 @@ export interface Configuration {
displayAllText?: boolean;
textFontSize?: number;
textPosition?: 'auto' | 'center';
textContent?: string;
undefinedAttrValue?: string;
showProjections?: boolean;
forceDisableEditing?: boolean;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -649,14 +654,21 @@ 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;
}

if (['auto', 'center'].includes(configuration.textPosition)) {
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;
}
Expand Down
73 changes: 49 additions & 24 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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) {
Expand Down Expand Up @@ -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` });
Expand Down Expand Up @@ -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,
]);
Expand All @@ -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,
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions cvat-canvas/src/typescript/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -39,4 +40,5 @@ export default {
SNAP_TO_ANGLE_RESIZE_DEFAULT,
SNAP_TO_ANGLE_RESIZE_SHIFT,
DEFAULT_SHAPE_TEXT_SIZE,
MINIMUM_TEXT_FONT_SIZE,
};
4 changes: 2 additions & 2 deletions cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.28.2",
"version": "1.29.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions cvat-ui/src/actions/settings-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ interface Props {
showObjectsTextAlways: boolean;
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
showAllInterpolationTracks: boolean;
workspace: Workspace;
automaticBordering: boolean;
Expand Down Expand Up @@ -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 };

Expand All @@ -130,6 +132,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
creationOpacity: selectedOpacity,
textFontSize,
textPosition,
textContent,
});

this.initialSetup();
Expand Down Expand Up @@ -166,6 +169,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
showObjectsTextAlways,
textFontSize,
textPosition,
textContent,
showAllInterpolationTracks,
automaticBordering,
intelligentPolygonCrop,
Expand All @@ -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,
Expand All @@ -194,6 +199,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
smoothImage,
textFontSize,
textPosition,
textContent,
});
}

Expand Down
4 changes: 4 additions & 0 deletions cvat-ui/src/components/header/settings-modal/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
}
}

.cvat-workspace-settings-text-content {
width: 100%;
}

.cvat-workspace-settings-approx-poly-threshold {
user-select: none;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -52,6 +54,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
defaultApproxPolyAccuracy,
textFontSize,
textPosition,
textContent,
onSwitchAutoSave,
onChangeAutoSaveInterval,
onChangeAAMZoomMargin,
Expand All @@ -62,6 +65,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
onChangeDefaultApproxPolyAccuracy,
onChangeTextFontSize,
onChangeTextPosition,
onChangeTextContent,
} = props;

const minAutoSaveInterval = 1;
Expand Down Expand Up @@ -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>
Expand Down
Loading

0 comments on commit 6af3be6

Please sign in to comment.