From 702d5d03e6108e3229f615e41819d3a0fc25ae89 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 15 Dec 2021 13:42:09 +0300 Subject: [PATCH 1/4] Added ability to setup text labels content --- cvat-canvas/package-lock.json | 4 +- cvat-canvas/package.json | 2 +- cvat-canvas/src/typescript/canvasModel.ts | 14 +++- cvat-canvas/src/typescript/canvasView.ts | 73 +++++++++++++------ cvat-ui/package-lock.json | 4 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/settings-actions.ts | 10 +++ .../annotation-page/canvas/canvas-wrapper.tsx | 8 +- .../header/settings-modal/styles.scss | 4 + .../settings-modal/workspace-settings.tsx | 25 ++++++- .../annotation-page/canvas/canvas-wrapper.tsx | 3 + .../settings-modal/workspace-settings.tsx | 9 +++ cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/settings-reducer.ts | 11 +++ 14 files changed, 137 insertions(+), 33 deletions(-) diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 87500d87bfa..94ea84c0260 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,12 +1,12 @@ { "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", diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 9b18633fb20..1654fabdddf 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -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": { diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index daf8f935b71..196ee738884 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -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 >= 8) { 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; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 4d04bfd7cbe..220a82100ee 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -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) diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index f9bad34c043..ebe48e3d0a2 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,12 +1,12 @@ { "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", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index fa806bb7503..5030d6b13af 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -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": { diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index d7d42097bc5..56430553b14 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -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, diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx index d6e53601f75..4fb018cb107 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx @@ -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 { smoothImage, textFontSize, textPosition, + textContent, } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; @@ -130,6 +132,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { creationOpacity: selectedOpacity, textFontSize, textPosition, + textContent, }); this.initialSetup(); @@ -166,6 +169,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { showObjectsTextAlways, textFontSize, textPosition, + textContent, showAllInterpolationTracks, automaticBordering, intelligentPolygonCrop, @@ -182,7 +186,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { 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 { smoothImage, textFontSize, textPosition, + textContent, }); } diff --git a/cvat-ui/src/components/header/settings-modal/styles.scss b/cvat-ui/src/components/header/settings-modal/styles.scss index 1b898e59289..e8754ed6849 100644 --- a/cvat-ui/src/components/header/settings-modal/styles.scss +++ b/cvat-ui/src/components/header/settings-modal/styles.scss @@ -38,6 +38,10 @@ } } +.cvat-workspace-settings-text-content { + width: 100%; +} + .cvat-workspace-settings-approx-poly-threshold { user-select: none; } diff --git a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx index 553bf0c1c10..65a71431c89 100644 --- a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -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 { + + + Content of a text + + + + + Position of a text diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx index 3b0df0ce576..802bb4eaa11 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -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, diff --git a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx index b78036784ed..dbf26e8b739 100644 --- a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx @@ -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)); + }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index aeea45220fe..ff83618b789 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -642,6 +642,7 @@ export interface WorkspaceSettingsState { toolsBlockerState: ToolsBlockerState; textFontSize: number; textPosition: 'auto' | 'center'; + textContent: string; } export interface ShapesSettingsState { diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index acac204e8aa..83eeb833c33 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -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, From b4bb1cf8dc671d1d42b67c6ec3b41542227740bb Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 15 Dec 2021 13:46:22 +0300 Subject: [PATCH 2/4] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0062678f502..afb997fece2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Data sorting option () - Options to change font size & position of text labels on the canvas () - Add "tag" return type for automatic annotation in Nuclio () +- User is able to customize information that text labels show () ### Changed - TDB From f664a9446117824671a96a1e03b28bdb5b7b58d1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 15 Dec 2021 14:46:11 +0300 Subject: [PATCH 3/4] Fixed wrong test --- tests/cypress/support/commands.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 898b5d1dd68..c642a40c2c1 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -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); }); From a839c680d60156e32e333cda3ba1741fc3264cbc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 16 Dec 2021 09:51:48 +0300 Subject: [PATCH 4/4] Added minimum font size const --- cvat-canvas/src/typescript/canvasModel.ts | 2 +- cvat-canvas/src/typescript/consts.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 196ee738884..bb391f582bd 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -654,7 +654,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.displayAllText = configuration.displayAllText; } - if (typeof configuration.textFontSize === 'number' && configuration.textFontSize >= 8) { + if (typeof configuration.textFontSize === 'number' && configuration.textFontSize >= consts.MINIMUM_TEXT_FONT_SIZE) { this.data.configuration.textFontSize = configuration.textFontSize; } diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index a2b42f17792..e382496f5a4 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -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, };