From 2bf427e898e42f5a6273b30424c7878431b1cb73 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 20 Jul 2021 12:44:08 +0300 Subject: [PATCH 01/17] First stage for points minimizer --- cvat-ui/src/actions/settings-actions.ts | 10 +++++++ .../controls-side-bar/tools-control.tsx | 21 +++++++++++++ .../header/settings-modal/styles.scss | 8 ++++- .../settings-modal/workspace-settings.tsx | 28 ++++++++++++++++- .../settings-modal/workspace-settings.tsx | 8 +++++ cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/settings-reducer.ts | 10 +++++++ .../utils/opencv-wrapper/opencv-wrapper.ts | 30 +++++++++++++++++++ 8 files changed, 114 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index 6fca99c3d33..a799dfcfe92 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -26,6 +26,7 @@ export enum SettingsActionTypes { SWITCH_AUTO_SAVE = 'SWITCH_AUTO_SAVE', CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL', CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN', + CHANGE_DEFAULT_APPROX_POLY_THRESHOLD = 'CHANGE_DEFAULT_APPROX_POLY_THRESHOLD', SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING', SWITCH_INTELLIGENT_POLYGON_CROP = 'SWITCH_INTELLIGENT_POLYGON_CROP', SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', @@ -270,6 +271,15 @@ export function switchSettingsDialog(show?: boolean): AnyAction { }; } +export function changeDefaultApproxPolyThreshold(approxPolyThreshold: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_DEFAULT_APPROX_POLY_THRESHOLD, + payload: { + approxPolyThreshold, + }, + }; +} + export function setSettings(settings: Partial): AnyAction { return { type: SettingsActionTypes.SET_SETTINGS, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index d859c80578e..2413f46230b 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -13,6 +13,7 @@ import Text from 'antd/lib/typography/Text'; import Tabs from 'antd/lib/tabs'; import { Row, Col } from 'antd/lib/grid'; import notification from 'antd/lib/notification'; +import message from 'antd/lib/message'; import Progress from 'antd/lib/progress'; import InputNumber from 'antd/lib/input-number'; @@ -20,6 +21,7 @@ import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; import range from 'utils/range'; import getCore from 'cvat-core-wrapper'; +import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper'; import { CombinedState, ActiveControl, Model, ObjectType, ShapeType, } from 'reducers/interfaces'; @@ -46,6 +48,7 @@ interface StateToProps { trackers: Model[]; curZOrder: number; aiToolsRef: MutableRefObject; + defaultApproxPolyThreshold: number; } interface DispatchToProps { @@ -60,6 +63,7 @@ const CustomPopover = withVisibilityHandling(Popover, 'tools-control'); function mapStateToProps(state: CombinedState): StateToProps { const { annotation } = state; + const { settings } = state; const { number: frame } = annotation.player.frame; const { instance: jobInstance } = annotation.job; const { instance: canvasInstance, activeControl } = annotation.canvas; @@ -79,6 +83,7 @@ function mapStateToProps(state: CombinedState): StateToProps { frame, curZOrder: annotation.annotations.zLayer.cur, aiToolsRef: annotation.aiToolsRef, + defaultApproxPolyThreshold: settings.workspace.defaultApproxPolyThreshold, }; } @@ -182,6 +187,7 @@ export class ToolsControlComponent extends React.PureComponent { isActivated, activeLabelID, canvasInstance, + defaultApproxPolyThreshold, createAnnotations, } = this.props; const { activeInteractor, fetching } = this.state; @@ -209,6 +215,21 @@ export class ToolsControlComponent extends React.PureComponent { this.latestResult = []; return; } + + if (this.latestResult.length > 3 * 2) { + if (!openCVWrapper.isInitialized) { + const hide = message.loading('OpenCV.js initialization..'); + try { + await openCVWrapper.initialize(() => {}); + } finally { + hide(); + } + } + this.latestResult = openCVWrapper.contours.approxPoly( + this.latestResult, + defaultApproxPolyThreshold, + ); + } } finally { this.setState({ fetching: false }); } diff --git a/cvat-ui/src/components/header/settings-modal/styles.scss b/cvat-ui/src/components/header/settings-modal/styles.scss index 06fd21a9d40..a1a0dbc239b 100644 --- a/cvat-ui/src/components/header/settings-modal/styles.scss +++ b/cvat-ui/src/components/header/settings-modal/styles.scss @@ -27,7 +27,9 @@ .cvat-workspace-settings-autoborders, .cvat-workspace-settings-intelligent-polygon-cropping, .cvat-workspace-settings-show-text-always, -.cvat-workspace-settings-show-interpolated { +.cvat-workspace-settings-show-interpolated, +.cvat-workspace-settings-approx-poly-threshold, +.cvat-workspace-settings-aam-zoom-margin { margin-bottom: 25px; > div:first-child { @@ -35,6 +37,10 @@ } } +.cvat-workspace-settings-approx-poly-threshold { + user-select: none; +} + .cvat-player-settings-step, .cvat-player-settings-speed, .cvat-player-settings-reset-zoom, 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 1aef81decda..5be37d6f07b 100644 --- a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -8,6 +8,7 @@ import { Row, Col } from 'antd/lib/grid'; 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 { clamp } from 'utils/math'; @@ -19,16 +20,18 @@ interface Props { showObjectsTextAlways: boolean; automaticBordering: boolean; intelligentPolygonCrop: boolean; + defaultApproxPolyThreshold: number; onSwitchAutoSave(enabled: boolean): void; onChangeAutoSaveInterval(interval: number): void; onChangeAAMZoomMargin(margin: number): void; + onChangeDefaultApproxPolyThreshold(approxPolyThreshold: number): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void; onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void; onSwitchIntelligentPolygonCrop(enabled: boolean): void; } -export default function WorkspaceSettingsComponent(props: Props): JSX.Element { +function WorkspaceSettingsComponent(props: Props): JSX.Element { const { autoSave, autoSaveInterval, @@ -37,6 +40,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, + defaultApproxPolyThreshold, onSwitchAutoSave, onChangeAutoSaveInterval, onChangeAAMZoomMargin, @@ -44,6 +48,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { onSwitchShowingObjectsTextAlways, onSwitchAutomaticBordering, onSwitchIntelligentPolygonCrop, + onChangeDefaultApproxPolyThreshold, } = props; const minAutoSaveInterval = 1; @@ -168,6 +173,27 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { /> + + + Default polygon approximation threshold + + + + + + + The value defines maximum distance. Works for serverless interactors and OpenCV scissors + + + ); } + +export default React.memo(WorkspaceSettingsComponent); 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 2384c1664ff..70705ea12b5 100644 --- a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx @@ -13,6 +13,7 @@ import { switchShowingObjectsTextAlways, switchAutomaticBordering, switchIntelligentPolygonCrop, + changeDefaultApproxPolyThreshold, } from 'actions/settings-actions'; import { CombinedState } from 'reducers/interfaces'; @@ -25,6 +26,7 @@ interface StateToProps { aamZoomMargin: number; showAllInterpolationTracks: boolean; showObjectsTextAlways: boolean; + defaultApproxPolyThreshold: number; automaticBordering: boolean; intelligentPolygonCrop: boolean; } @@ -37,6 +39,7 @@ interface DispatchToProps { onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void; onSwitchIntelligentPolygonCrop(enabled: boolean): void; + onChangeDefaultApproxPolyThreshold(approxPolyThreshold: number): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -49,6 +52,7 @@ function mapStateToProps(state: CombinedState): StateToProps { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, + defaultApproxPolyThreshold, } = workspace; return { @@ -59,6 +63,7 @@ function mapStateToProps(state: CombinedState): StateToProps { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, + defaultApproxPolyThreshold, }; } @@ -85,6 +90,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSwitchIntelligentPolygonCrop(enabled: boolean): void { dispatch(switchIntelligentPolygonCrop(enabled)); }, + onChangeDefaultApproxPolyThreshold(approxPolyThreshold: number): void { + dispatch(changeDefaultApproxPolyThreshold(approxPolyThreshold)); + }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index eaae78a484a..06ca097120b 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -556,6 +556,7 @@ export interface WorkspaceSettingsState { showObjectsTextAlways: boolean; showAllInterpolationTracks: boolean; intelligentPolygonCrop: boolean; + defaultApproxPolyThreshold: number; } export interface ShapesSettingsState { diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 8e5a66e55db..f82b431189d 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -31,6 +31,7 @@ const defaultState: SettingsState = { showObjectsTextAlways: false, showAllInterpolationTracks: false, intelligentPolygonCrop: true, + defaultApproxPolyThreshold: 2, }, player: { canvasBackgroundColor: '#ffffff', @@ -277,6 +278,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.CHANGE_DEFAULT_APPROX_POLY_THRESHOLD: { + return { + ...state, + workspace: { + ...state.workspace, + defaultApproxPolyThreshold: action.payload.approxPolyThreshold, + }, + }; + } case SettingsActionTypes.SWITCH_SETTINGS_DIALOG: { return { ...state, diff --git a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts index 74394a25400..fab5e318363 100644 --- a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts +++ b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts @@ -14,6 +14,10 @@ export interface Segmentation { intelligentScissorsFactory: () => IntelligentScissors; } +export interface Contours { + approxPoly: (points: number[] | any, threshold: number, closed?: boolean) => number[]; +} + export class OpenCVWrapper { private initialized: boolean; private cv: any; @@ -80,6 +84,32 @@ export class OpenCVWrapper { return this.initialized; } + public get contours(): Contours { + if (!this.initialized) { + throw new Error('Need to initialize OpenCV first'); + } + + const { cv } = this; + return { + approxPoly: (points: number[] | any, threshold: number, closed = true): number[] => { + const approx = new cv.Mat(); + if (points instanceof cv.Mat) { + this.cv.approxPolyDP(points, approx, threshold, closed); + } else { + const contour = cv.matFromArray(points.length, 2, cv.CV_32FC1, points.flat()); + cv.approxPolyDP(contour, approx, threshold, closed); + } + + const result = []; + for (let row = 0; row < approx.rows; row++) { + result.push(approx.floatAt(row, 0), approx.floatAt(row, 1)); + } + + return result; + }, + }; + } + public get segmentation(): Segmentation { if (!this.initialized) { throw new Error('Need to initialize OpenCV first'); From 57fbbe29bea5a3dad6bf97003554f1cc5eca5cce Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 20 Jul 2021 13:05:02 +0300 Subject: [PATCH 02/17] Fixed issue with correct opencv initialization status --- .../controls-side-bar/opencv-control.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx index f91233eaecc..9cd1aeeedf5 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx @@ -324,6 +324,7 @@ class OpenCVControlComponent extends React.PureComponent { + if (libraryInitialized !== openCVWrapper.isInitialized) { + this.setState({ + libraryInitialized: openCVWrapper.isInitialized, + }); + } + }} > From dd63985098d549bce0ad1072738667881a2f0c1c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 20 Jul 2021 13:23:37 +0300 Subject: [PATCH 03/17] Displaying points during interaction --- .../src/typescript/interactionHandler.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/cvat-canvas/src/typescript/interactionHandler.ts b/cvat-canvas/src/typescript/interactionHandler.ts index c6526a4c334..2c23f13bdc1 100644 --- a/cvat-canvas/src/typescript/interactionHandler.ts +++ b/cvat-canvas/src/typescript/interactionHandler.ts @@ -225,6 +225,7 @@ export class InteractionHandlerImpl implements InteractionHandler { private release(): void { if (this.drawnIntermediateShape) { + this.selectize(false, this.drawnIntermediateShape); this.drawnIntermediateShape.remove(); this.drawnIntermediateShape = null; } @@ -270,6 +271,7 @@ export class InteractionHandlerImpl implements InteractionHandler { private updateIntermediateShape(): void { const { intermediateShape, geometry } = this; if (this.drawnIntermediateShape) { + this.selectize(false, this.drawnIntermediateShape); this.drawnIntermediateShape.remove(); } @@ -285,6 +287,7 @@ export class InteractionHandlerImpl implements InteractionHandler { fill: 'none', }) .addClass('cvat_canvas_interact_intermediate_shape'); + this.selectize(true, this.drawnIntermediateShape); } else { throw new Error( `Shape type "${shapeType}" was not implemented at interactionHandler::updateIntermediateShape`, @@ -292,6 +295,39 @@ export class InteractionHandlerImpl implements InteractionHandler { } } + private selectize(value: boolean, shape: SVG.Element): void { + const self = this; + + if (value) { + (shape as any).selectize(value, { + deepSelect: true, + pointSize: consts.BASE_POINT_SIZE / self.geometry.scale, + rotationPoint: false, + classPoints: 'cvat_canvas_interact_intermediate_shape_point', + pointType(cx: number, cy: number): SVG.Circle { + return this.nested + .circle(this.options.pointSize) + .stroke('black') + .fill('black') + .center(cx, cy) + .attr({ + 'fill-opacity': 1, + 'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale, + }); + }, + }); + } else { + (shape as any).selectize(false, { + deepSelect: true, + }); + } + + const handler = shape.remember('_selectHandler'); + if (handler && handler.nested) { + handler.nested.fill(shape.attr('fill')); + } + } + public constructor( onInteraction: ( shapes: InteractionResult[] | null, @@ -398,6 +434,15 @@ export class InteractionHandlerImpl implements InteractionHandler { shape.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale); } } + + for (const element of window.document.getElementsByClassName('cvat_canvas_interact_intermediate_shape_point')) { + element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / (2 * this.geometry.scale)}`); + element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`); + } + + if (this.drawnIntermediateShape) { + this.drawnIntermediateShape.stroke({ width: consts.BASE_STROKE_WIDTH / this.geometry.scale }); + } } public interact(interactionData: InteractionData): void { From b6cb02ff9b0e8b3f240c3fa20bf044411de09940 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 20 Jul 2021 13:41:18 +0300 Subject: [PATCH 04/17] Added releasing memory --- .../utils/opencv-wrapper/opencv-wrapper.ts | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts index fab5e318363..9cb7e644a07 100644 --- a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts +++ b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts @@ -93,19 +93,26 @@ export class OpenCVWrapper { return { approxPoly: (points: number[] | any, threshold: number, closed = true): number[] => { const approx = new cv.Mat(); - if (points instanceof cv.Mat) { - this.cv.approxPolyDP(points, approx, threshold, closed); - } else { - const contour = cv.matFromArray(points.length, 2, cv.CV_32FC1, points.flat()); - cv.approxPolyDP(contour, approx, threshold, closed); + try { + if (points instanceof cv.Mat) { + this.cv.approxPolyDP(points, approx, threshold, closed); + } else { + const contour = cv.matFromArray(points.length, 2, cv.CV_32FC1, points.flat()); + try { + cv.approxPolyDP(contour, approx, threshold, closed); + } finally { + contour.delete(); + } + } + + const result = []; + for (let row = 0; row < approx.rows; row++) { + result.push(approx.floatAt(row, 0), approx.floatAt(row, 1)); + } + return result; + } finally { + approx.delete(); } - - const result = []; - for (let row = 0; row < approx.rows; row++) { - result.push(approx.floatAt(row, 0), approx.floatAt(row, 1)); - } - - return result; }, }; } From 367383a9c1dd466c9e9a8b4fc1f55e0ce72e1c71 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 20 Jul 2021 15:39:58 +0300 Subject: [PATCH 05/17] Initial version for on-the-fly optimization --- .../controls-side-bar/tools-control.tsx | 96 +++++++++++++++---- .../objects-side-bar/styles.scss | 12 +++ .../utils/opencv-wrapper/opencv-wrapper.ts | 6 +- 3 files changed, 91 insertions(+), 23 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 2413f46230b..0e2751e8886 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT import React, { MutableRefObject } from 'react'; +import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import Icon, { LoadingOutlined } from '@ant-design/icons'; import Popover from 'antd/lib/popover'; @@ -16,7 +17,9 @@ import notification from 'antd/lib/notification'; import message from 'antd/lib/message'; import Progress from 'antd/lib/progress'; import InputNumber from 'antd/lib/input-number'; +import Slider from 'antd/lib/slider'; +import { Tooltip } from 'antd'; import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; import range from 'utils/range'; @@ -102,6 +105,7 @@ interface State { trackingProgress: number | null; trackingFrames: number; fetching: boolean; + approxPolyThreshold: number; mode: 'detection' | 'interaction' | 'tracking'; } @@ -109,7 +113,8 @@ export class ToolsControlComponent extends React.PureComponent { private interactionIsAborted: boolean; private interactionIsDone: boolean; - private latestResult: number[]; + private latestResponseResult: number[][]; + private latestResult: number[][]; public constructor(props: Props) { super(props); @@ -117,12 +122,14 @@ export class ToolsControlComponent extends React.PureComponent { activeInteractor: props.interactors.length ? props.interactors[0] : null, activeTracker: props.trackers.length ? props.trackers[0] : null, activeLabelID: props.labels.length ? props.labels[0].id : null, + approxPolyThreshold: props.defaultApproxPolyThreshold, trackingProgress: null, trackingFrames: 10, fetching: false, mode: 'interaction', }; + this.latestResponseResult = []; this.latestResult = []; this.interactionIsAborted = false; this.interactionIsDone = false; @@ -135,16 +142,36 @@ export class ToolsControlComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.canceled', this.cancelListener); } - public componentDidUpdate(prevProps: Props): void { - const { isActivated } = this.props; + public componentDidUpdate(prevProps: Props, prevState: State): void { + const { isActivated, defaultApproxPolyThreshold, canvasInstance } = this.props; + const { approxPolyThreshold, activeInteractor } = this.state; + if (prevProps.isActivated && !isActivated) { window.removeEventListener('contextmenu', this.contextmenuDisabler); + this.setState({ + approxPolyThreshold: defaultApproxPolyThreshold, + }); } else if (!prevProps.isActivated && isActivated) { // reset flags when start interaction/tracking this.interactionIsDone = false; this.interactionIsAborted = false; window.addEventListener('contextmenu', this.contextmenuDisabler); } + + if (prevState.approxPolyThreshold !== approxPolyThreshold) { + if (isActivated && activeInteractor !== null && this.latestResponseResult.length) { + this.approximateResponsePoints(this.latestResponseResult).then((points: number[][]) => { + this.latestResult = points; + canvasInstance.interact({ + enabled: true, + intermediateShape: { + shapeType: ShapeType.POLYGON, + points: this.latestResult.flat(), + }, + }); + }); + } + } } public componentWillUnmount(): void { @@ -187,7 +214,6 @@ export class ToolsControlComponent extends React.PureComponent { isActivated, activeLabelID, canvasInstance, - defaultApproxPolyThreshold, createAnnotations, } = this.props; const { activeInteractor, fetching } = this.state; @@ -203,33 +229,22 @@ export class ToolsControlComponent extends React.PureComponent { if ((e as CustomEvent).detail.shapesUpdated) { this.setState({ fetching: true }); try { - this.latestResult = await core.lambda.call(jobInstance.task, interactor, { + this.latestResponseResult = await core.lambda.call(jobInstance.task, interactor, { frame, pos_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 0), neg_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 2), }); + this.latestResult = this.latestResponseResult; if (this.interactionIsAborted) { // while the server request // user has cancelled interaction (for example pressed ESC) this.latestResult = []; + this.latestResponseResult = []; return; } - if (this.latestResult.length > 3 * 2) { - if (!openCVWrapper.isInitialized) { - const hide = message.loading('OpenCV.js initialization..'); - try { - await openCVWrapper.initialize(() => {}); - } finally { - hide(); - } - } - this.latestResult = openCVWrapper.contours.approxPoly( - this.latestResult, - defaultApproxPolyThreshold, - ); - } + this.latestResult = await this.approximateResponsePoints(this.latestResponseResult); } finally { this.setState({ fetching: false }); } @@ -341,6 +356,24 @@ export class ToolsControlComponent extends React.PureComponent { }); }; + private async approximateResponsePoints(points: number[][]): Promise { + const { approxPolyThreshold } = this.state; + if (points.length > 3) { + if (!openCVWrapper.isInitialized) { + const hide = message.loading('OpenCV.js initialization..'); + try { + await openCVWrapper.initialize(() => {}); + } finally { + hide(); + } + } + + return openCVWrapper.contours.approxPoly(points, approxPolyThreshold); + } + + return points; + } + public async trackState(state: any): Promise { const { jobInstance, frame } = this.props; const { activeTracker, trackingFrames } = this.state; @@ -682,7 +715,9 @@ export class ToolsControlComponent extends React.PureComponent { const { interactors, detectors, trackers, isActivated, canvasInstance, labels, } = this.props; - const { fetching, trackingProgress } = this.state; + const { + fetching, trackingProgress, approxPolyThreshold, activeInteractor, + } = this.state; if (![...interactors, ...detectors, ...trackers].length) return null; @@ -722,6 +757,27 @@ export class ToolsControlComponent extends React.PureComponent { )} + {isActivated && + activeInteractor !== null && + window.document.getElementsByClassName('cvat-canvas-container')[0] ? + ReactDOM.createPortal( +
+ Approximation threshold + + { + this.setState({ approxPolyThreshold: value }); + }} + /> + +
, + window.document.getElementsByClassName('cvat-canvas-container')[0] as HTMLDivElement, + ) : + null} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index 667278fd02a..d6580411a5d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -397,3 +397,15 @@ .cvat-objects-sidebar-label-item-disabled { opacity: 0.5; } + +.cvat-approx-poly-threshold-wrapper { + position: absolute; + background: $background-color-2; + top: 8px; + left: 50%; + opacity: 0.5; + border-radius: 6px; + border: 1px solid $border-color-3; + z-index: 100; + padding: 4px 12px; +} diff --git a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts index 9cb7e644a07..6fc7366e50e 100644 --- a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts +++ b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts @@ -15,7 +15,7 @@ export interface Segmentation { } export interface Contours { - approxPoly: (points: number[] | any, threshold: number, closed?: boolean) => number[]; + approxPoly: (points: number[] | any, threshold: number, closed?: boolean) => number[][]; } export class OpenCVWrapper { @@ -91,7 +91,7 @@ export class OpenCVWrapper { const { cv } = this; return { - approxPoly: (points: number[] | any, threshold: number, closed = true): number[] => { + approxPoly: (points: number[] | any, threshold: number, closed = true): number[][] => { const approx = new cv.Mat(); try { if (points instanceof cv.Mat) { @@ -107,7 +107,7 @@ export class OpenCVWrapper { const result = []; for (let row = 0; row < approx.rows; row++) { - result.push(approx.floatAt(row, 0), approx.floatAt(row, 1)); + result.push([approx.floatAt(row, 0), approx.floatAt(row, 1)]); } return result; } finally { From b2d95480002235c2e6a0b77d9d2347e4917fd83e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 22 Jul 2021 12:18:54 +0300 Subject: [PATCH 06/17] Redesigned accuracy --- cvat-ui/src/actions/settings-actions.ts | 4 +- .../controls-side-bar/tools-control.tsx | 59 +++++++++++-------- .../settings-modal/workspace-settings.tsx | 25 ++++---- .../settings-modal/workspace-settings.tsx | 14 ++--- cvat-ui/src/reducers/interfaces.ts | 2 +- cvat-ui/src/reducers/settings-reducer.ts | 4 +- 6 files changed, 57 insertions(+), 51 deletions(-) diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index a799dfcfe92..966743c47ee 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -271,11 +271,11 @@ export function switchSettingsDialog(show?: boolean): AnyAction { }; } -export function changeDefaultApproxPolyThreshold(approxPolyThreshold: number): AnyAction { +export function changeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): AnyAction { return { type: SettingsActionTypes.CHANGE_DEFAULT_APPROX_POLY_THRESHOLD, payload: { - approxPolyThreshold, + approxPolyAccuracy, }, }; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 0e2751e8886..32feeb3af3d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -19,7 +19,6 @@ import Progress from 'antd/lib/progress'; import InputNumber from 'antd/lib/input-number'; import Slider from 'antd/lib/slider'; -import { Tooltip } from 'antd'; import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; import range from 'utils/range'; @@ -51,7 +50,7 @@ interface StateToProps { trackers: Model[]; curZOrder: number; aiToolsRef: MutableRefObject; - defaultApproxPolyThreshold: number; + defaultApproxPolyAccuracy: number; } interface DispatchToProps { @@ -86,7 +85,7 @@ function mapStateToProps(state: CombinedState): StateToProps { frame, curZOrder: annotation.annotations.zLayer.cur, aiToolsRef: annotation.aiToolsRef, - defaultApproxPolyThreshold: settings.workspace.defaultApproxPolyThreshold, + defaultApproxPolyAccuracy: settings.workspace.defaultApproxPolyAccuracy, }; } @@ -105,7 +104,7 @@ interface State { trackingProgress: number | null; trackingFrames: number; fetching: boolean; - approxPolyThreshold: number; + approxPolyAccuracy: number; mode: 'detection' | 'interaction' | 'tracking'; } @@ -122,7 +121,7 @@ export class ToolsControlComponent extends React.PureComponent { activeInteractor: props.interactors.length ? props.interactors[0] : null, activeTracker: props.trackers.length ? props.trackers[0] : null, activeLabelID: props.labels.length ? props.labels[0].id : null, - approxPolyThreshold: props.defaultApproxPolyThreshold, + approxPolyAccuracy: props.defaultApproxPolyAccuracy, trackingProgress: null, trackingFrames: 10, fetching: false, @@ -143,22 +142,22 @@ export class ToolsControlComponent extends React.PureComponent { } public componentDidUpdate(prevProps: Props, prevState: State): void { - const { isActivated, defaultApproxPolyThreshold, canvasInstance } = this.props; - const { approxPolyThreshold, activeInteractor } = this.state; + const { isActivated, defaultApproxPolyAccuracy, canvasInstance } = this.props; + const { approxPolyAccuracy, activeInteractor } = this.state; if (prevProps.isActivated && !isActivated) { window.removeEventListener('contextmenu', this.contextmenuDisabler); - this.setState({ - approxPolyThreshold: defaultApproxPolyThreshold, - }); } else if (!prevProps.isActivated && isActivated) { // reset flags when start interaction/tracking + this.setState({ + approxPolyAccuracy: defaultApproxPolyAccuracy, + }); this.interactionIsDone = false; this.interactionIsAborted = false; window.addEventListener('contextmenu', this.contextmenuDisabler); } - if (prevState.approxPolyThreshold !== approxPolyThreshold) { + if (prevState.approxPolyAccuracy !== approxPolyAccuracy) { if (isActivated && activeInteractor !== null && this.latestResponseResult.length) { this.approximateResponsePoints(this.latestResponseResult).then((points: number[][]) => { this.latestResult = points; @@ -357,7 +356,7 @@ export class ToolsControlComponent extends React.PureComponent { }; private async approximateResponsePoints(points: number[][]): Promise { - const { approxPolyThreshold } = this.state; + const { approxPolyAccuracy } = this.state; if (points.length > 3) { if (!openCVWrapper.isInitialized) { const hide = message.loading('OpenCV.js initialization..'); @@ -368,7 +367,15 @@ export class ToolsControlComponent extends React.PureComponent { } } - return openCVWrapper.contours.approxPoly(points, approxPolyThreshold); + const maxAccuracy = 7; + const approxPolyMaxDistance = maxAccuracy - approxPolyAccuracy; + let threshold = 0; + if (approxPolyMaxDistance > 0) { + // 0.5 for 1, 1 for 2, 2 for 3, 4 for 4, ... + threshold = 2 ** (approxPolyMaxDistance - 2); + } + + return openCVWrapper.contours.approxPoly(points, threshold); } return points; @@ -716,7 +723,7 @@ export class ToolsControlComponent extends React.PureComponent { interactors, detectors, trackers, isActivated, canvasInstance, labels, } = this.props; const { - fetching, trackingProgress, approxPolyThreshold, activeInteractor, + fetching, trackingProgress, approxPolyAccuracy, activeInteractor, } = this.state; if (![...interactors, ...detectors, ...trackers].length) return null; @@ -762,18 +769,18 @@ export class ToolsControlComponent extends React.PureComponent { window.document.getElementsByClassName('cvat-canvas-container')[0] ? ReactDOM.createPortal(
- Approximation threshold - - { - this.setState({ approxPolyThreshold: value }); - }} - /> - + Approximation accuracy + { + this.setState({ approxPolyAccuracy: value }); + }} + />
, window.document.getElementsByClassName('cvat-canvas-container')[0] as HTMLDivElement, ) : 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 5be37d6f07b..dad651b84fb 100644 --- a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -20,11 +20,11 @@ interface Props { showObjectsTextAlways: boolean; automaticBordering: boolean; intelligentPolygonCrop: boolean; - defaultApproxPolyThreshold: number; + defaultApproxPolyAccuracy: number; onSwitchAutoSave(enabled: boolean): void; onChangeAutoSaveInterval(interval: number): void; onChangeAAMZoomMargin(margin: number): void; - onChangeDefaultApproxPolyThreshold(approxPolyThreshold: number): void; + onChangeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void; onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void; @@ -40,7 +40,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, - defaultApproxPolyThreshold, + defaultApproxPolyAccuracy, onSwitchAutoSave, onChangeAutoSaveInterval, onChangeAAMZoomMargin, @@ -48,7 +48,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element { onSwitchShowingObjectsTextAlways, onSwitchAutomaticBordering, onSwitchIntelligentPolygonCrop, - onChangeDefaultApproxPolyThreshold, + onChangeDefaultApproxPolyAccuracy, } = props; const minAutoSaveInterval = 1; @@ -175,21 +175,20 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element { - Default polygon approximation threshold + Default polygon approximation accuracy level - - The value defines maximum distance. Works for serverless interactors and OpenCV scissors - + Works for serverless interactors and OpenCV scissors 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 70705ea12b5..d92f4429785 100644 --- a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx @@ -13,7 +13,7 @@ import { switchShowingObjectsTextAlways, switchAutomaticBordering, switchIntelligentPolygonCrop, - changeDefaultApproxPolyThreshold, + changeDefaultApproxPolyAccuracy, } from 'actions/settings-actions'; import { CombinedState } from 'reducers/interfaces'; @@ -26,7 +26,7 @@ interface StateToProps { aamZoomMargin: number; showAllInterpolationTracks: boolean; showObjectsTextAlways: boolean; - defaultApproxPolyThreshold: number; + defaultApproxPolyAccuracy: number; automaticBordering: boolean; intelligentPolygonCrop: boolean; } @@ -39,7 +39,7 @@ interface DispatchToProps { onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void; onSwitchIntelligentPolygonCrop(enabled: boolean): void; - onChangeDefaultApproxPolyThreshold(approxPolyThreshold: number): void; + onChangeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -52,7 +52,7 @@ function mapStateToProps(state: CombinedState): StateToProps { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, - defaultApproxPolyThreshold, + defaultApproxPolyAccuracy, } = workspace; return { @@ -63,7 +63,7 @@ function mapStateToProps(state: CombinedState): StateToProps { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, - defaultApproxPolyThreshold, + defaultApproxPolyAccuracy, }; } @@ -90,8 +90,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSwitchIntelligentPolygonCrop(enabled: boolean): void { dispatch(switchIntelligentPolygonCrop(enabled)); }, - onChangeDefaultApproxPolyThreshold(approxPolyThreshold: number): void { - dispatch(changeDefaultApproxPolyThreshold(approxPolyThreshold)); + onChangeDefaultApproxPolyAccuracy(threshold: number): void { + dispatch(changeDefaultApproxPolyAccuracy(threshold)); }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 06ca097120b..7da382b85df 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -556,7 +556,7 @@ export interface WorkspaceSettingsState { showObjectsTextAlways: boolean; showAllInterpolationTracks: boolean; intelligentPolygonCrop: boolean; - defaultApproxPolyThreshold: number; + defaultApproxPolyAccuracy: number; } export interface ShapesSettingsState { diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index f82b431189d..d5910bcecab 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -31,7 +31,7 @@ const defaultState: SettingsState = { showObjectsTextAlways: false, showAllInterpolationTracks: false, intelligentPolygonCrop: true, - defaultApproxPolyThreshold: 2, + defaultApproxPolyAccuracy: 4, }, player: { canvasBackgroundColor: '#ffffff', @@ -283,7 +283,7 @@ export default (state = defaultState, action: AnyAction): SettingsState => { ...state, workspace: { ...state.workspace, - defaultApproxPolyThreshold: action.payload.approxPolyThreshold, + defaultApproxPolyAccuracy: action.payload.approxPolyAccuracy, }, }; } From 463654885ff672019796f24cfedc01fc7ac476c5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 22 Jul 2021 12:25:31 +0300 Subject: [PATCH 07/17] Updated version & changelog --- CHANGELOG.md | 1 + cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ef9865a1b..de6d050e616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Filter `is_active` for user list () - Ability to export/import tasks () - Explicit "Done" button when drawing any polyshapes () +- Client-side polyshapes approximation when using semi-automatic interactors & scissors () ### Changed diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index d654c11a2b1..52229d04b57 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.21.0", + "version": "1.21.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 2158072dd07..6e18e7efc8c 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.21.0", + "version": "1.21.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { From 725dafa1c386625e2da53dfa6196d6019b77c06d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 22 Jul 2021 15:56:04 +0300 Subject: [PATCH 08/17] Fixed opencv scissors --- cvat-canvas/src/scss/canvas.scss | 9 +- .../approximation-accuracy.tsx | 50 ++++++++ .../controls-side-bar/opencv-control.tsx | 117 ++++++++++++------ .../controls-side-bar/tools-control.tsx | 43 ++----- .../opencv-wrapper/intelligent-scissors.ts | 7 +- .../utils/opencv-wrapper/opencv-wrapper.ts | 24 ++-- 6 files changed, 160 insertions(+), 90 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index ff0a50fc28b..157d1020128 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -160,12 +160,15 @@ polyline.cvat_canvas_shape_splitting { } .cvat_canvas_removable_interaction_point { - cursor: - url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K') - 10 10, + cursor: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K') + 10 10, auto; } +.cvat_canvas_interact_intermediate_shape_point { + pointer-events: none; +} + .svg_select_boundingRect { opacity: 0; pointer-events: none; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx new file mode 100644 index 00000000000..85fae4587fc --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx @@ -0,0 +1,50 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Text from 'antd/lib/typography/Text'; +import Slider from 'antd/lib/slider'; + +interface Props { + approxPolyAccuracy: number; + onChange(value: number): void; +} + +export function thresholdFromAccuracy(approxPolyAccuracy: number): number { + const maxAccuracy = 7; + const approxPolyMaxDistance = maxAccuracy - approxPolyAccuracy; + let threshold = 0; + if (approxPolyMaxDistance > 0) { + // 0.5 for 1, 1 for 2, 2 for 3, 4 for 4, ... + threshold = 2 ** (approxPolyMaxDistance - 2); + } + + return threshold; +} + +function ApproximationAccuracy(props: Props): React.ReactPortal | null { + const { approxPolyAccuracy, onChange } = props; + const target = window.document.getElementsByClassName('cvat-canvas-container')[0]; + + return target ? + ReactDOM.createPortal( +
+ Approximation accuracy + +
, + target, + ) : + null; +} + +export default React.memo(ApproximationAccuracy); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx index 9cd1aeeedf5..2dce8c899a3 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx @@ -29,6 +29,9 @@ import { } from 'actions/annotation-actions'; import LabelSelector from 'components/label-selector/label-selector'; import CVATTooltip from 'components/common/cvat-tooltip'; +import ApproximationAccuracy, { + thresholdFromAccuracy, +} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; import withVisibilityHandling from './handle-popover-visibility'; interface Props { @@ -39,6 +42,7 @@ interface Props { states: any[]; frame: number; curZOrder: number; + defaultApproxPolyAccuracy: number; } interface DispatchToProps { @@ -53,6 +57,7 @@ interface State { initializationError: boolean; initializationProgress: number; activeLabelID: number; + approxPolyAccuracy: number; } const core = getCore(); @@ -71,11 +76,15 @@ function mapStateToProps(state: CombinedState): Props { frame: { number: frame }, }, }, + settings: { + workspace: { defaultApproxPolyAccuracy }, + }, } = state; return { isActivated: activeControl === ActiveControl.OPENCV_TOOLS, canvasInstance: canvasInstance as Canvas, + defaultApproxPolyAccuracy, jobInstance, curZOrder, labels, @@ -93,15 +102,18 @@ const mapDispatchToProps = { class OpenCVControlComponent extends React.PureComponent { private activeTool: IntelligentScissors | null; + private latestPoints: number[] | null; public constructor(props: Props & DispatchToProps) { super(props); - const { labels } = props; + const { labels, defaultApproxPolyAccuracy } = props; this.activeTool = null; + this.latestPoints = null; this.state = { libraryInitialized: openCVWrapper.isInitialized, initializationError: false, initializationProgress: -1, + approxPolyAccuracy: defaultApproxPolyAccuracy, activeLabelID: labels.length ? labels[0].id : null, }; } @@ -111,14 +123,35 @@ class OpenCVControlComponent extends React.PureComponent => { + const { approxPolyAccuracy } = this.state; const { createAnnotations, isActivated, jobInstance, frame, labels, curZOrder, canvasInstance, } = this.props; @@ -142,24 +176,32 @@ class OpenCVControlComponent extends React.PureComponent label.id === activeLabelID)[0], - // need to recalculate without the latest sliding point - points: await this.runCVAlgorithm(pressedPoints, threshold), + points: openCVWrapper.contours + .approxPoly(finalPoints, thresholdFromAccuracy(approxPolyAccuracy)) + .flat(), occluded: false, zOrder: curZOrder, }); @@ -173,7 +215,7 @@ class OpenCVControlComponent extends React.PureComponent { + private async runCVAlgorithm(pressedPoints: number[], threshold: number): Promise { // Getting image data const canvas: HTMLCanvasElement | undefined = window.document.getElementById('cvat_canvas_background') as | HTMLCanvasElement @@ -195,23 +237,10 @@ class OpenCVControlComponent extends React.PureComponent ) : ( - { - if (libraryInitialized !== openCVWrapper.isInitialized) { - this.setState({ - libraryInitialized: openCVWrapper.isInitialized, - }); - } - }} - > - - + <> + { + if (libraryInitialized !== openCVWrapper.isInitialized) { + this.setState({ + libraryInitialized: openCVWrapper.isInitialized, + }); + } + }} + > + + + {isActivated ? ( + { + this.setState({ approxPolyAccuracy: value }); + }} + /> + ) : null} + ); } } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 32feeb3af3d..101c35626ef 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -3,7 +3,6 @@ // SPDX-License-Identifier: MIT import React, { MutableRefObject } from 'react'; -import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import Icon, { LoadingOutlined } from '@ant-design/icons'; import Popover from 'antd/lib/popover'; @@ -17,7 +16,6 @@ import notification from 'antd/lib/notification'; import message from 'antd/lib/message'; import Progress from 'antd/lib/progress'; import InputNumber from 'antd/lib/input-number'; -import Slider from 'antd/lib/slider'; import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; @@ -35,6 +33,9 @@ import { } from 'actions/annotation-actions'; import DetectorRunner from 'components/model-runner-modal/detector-runner'; import LabelSelector from 'components/label-selector/label-selector'; +import ApproximationAccuracy, { + thresholdFromAccuracy, +} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; import withVisibilityHandling from './handle-popover-visibility'; interface StateToProps { @@ -367,14 +368,7 @@ export class ToolsControlComponent extends React.PureComponent { } } - const maxAccuracy = 7; - const approxPolyMaxDistance = maxAccuracy - approxPolyAccuracy; - let threshold = 0; - if (approxPolyMaxDistance > 0) { - // 0.5 for 1, 1 for 2, 2 for 3, 4 for 4, ... - threshold = 2 ** (approxPolyMaxDistance - 2); - } - + const threshold = thresholdFromAccuracy(approxPolyAccuracy); return openCVWrapper.contours.approxPoly(points, threshold); } @@ -764,27 +758,14 @@ export class ToolsControlComponent extends React.PureComponent { )} - {isActivated && - activeInteractor !== null && - window.document.getElementsByClassName('cvat-canvas-container')[0] ? - ReactDOM.createPortal( -
- Approximation accuracy - { - this.setState({ approxPolyAccuracy: value }); - }} - /> -
, - window.document.getElementsByClassName('cvat-canvas-container')[0] as HTMLDivElement, - ) : - null} + {isActivated && activeInteractor !== null ? ( + { + this.setState({ approxPolyAccuracy: value }); + }} + /> + ) : null} diff --git a/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts b/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts index cd05eec5086..8fca230143f 100644 --- a/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts +++ b/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts @@ -92,7 +92,6 @@ export default class IntelligentScissorsImplementation implements IntelligentSci if (points.length > 1) { let matImage = null; const contour = new cv.Mat(); - const approx = new cv.Mat(); try { const [prev, cur] = points.slice(-2); @@ -123,11 +122,10 @@ export default class IntelligentScissorsImplementation implements IntelligentSci tool.applyImage(matImage); tool.buildMap(new cv.Point(prevX, prevY)); tool.getContour(new cv.Point(curX, curY), contour); - cv.approxPolyDP(contour, approx, 2, false); const pathSegment = []; - for (let row = 0; row < approx.rows; row++) { - pathSegment.push(approx.intAt(row, 0) + offsetX, approx.intAt(row, 1) + offsetY); + for (let row = 0; row < contour.rows; row++) { + pathSegment.push(contour.intAt(row, 0) + offsetX, contour.intAt(row, 1) + offsetY); } state.anchors[points.length - 1] = { point: cur, @@ -140,7 +138,6 @@ export default class IntelligentScissorsImplementation implements IntelligentSci } contour.delete(); - approx.delete(); } } else { state.path.push(...pointsToNumberArray(applyOffset(points.slice(-1), -offsetX, -offsetY))); diff --git a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts index 6fc7366e50e..ff19cb4b543 100644 --- a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts +++ b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts @@ -91,20 +91,19 @@ export class OpenCVWrapper { const { cv } = this; return { - approxPoly: (points: number[] | any, threshold: number, closed = true): number[][] => { + approxPoly: (points: number[] | number[][], threshold: number, closed = true): number[][] => { + const isArrayOfArrays = Array.isArray(points[0]); + if (points.length < 3) { + // one pair of coordinates [x, y], approximation not possible + return (isArrayOfArrays ? points : [points]) as number[][]; + } + const rows = isArrayOfArrays ? points.length : points.length / 2; + const cols = 2; + const approx = new cv.Mat(); + const contour = cv.matFromArray(rows, cols, cv.CV_32FC1, points.flat()); try { - if (points instanceof cv.Mat) { - this.cv.approxPolyDP(points, approx, threshold, closed); - } else { - const contour = cv.matFromArray(points.length, 2, cv.CV_32FC1, points.flat()); - try { - cv.approxPolyDP(contour, approx, threshold, closed); - } finally { - contour.delete(); - } - } - + cv.approxPolyDP(contour, approx, threshold, closed); // approx output type is CV_32F const result = []; for (let row = 0; row < approx.rows; row++) { result.push([approx.floatAt(row, 0), approx.floatAt(row, 1)]); @@ -112,6 +111,7 @@ export class OpenCVWrapper { return result; } finally { approx.delete(); + contour.delete(); } }, }; From 90d69bca86b3d1e9c33edc4bf7f6c8aef2d1e719 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 22 Jul 2021 16:17:08 +0300 Subject: [PATCH 09/17] Clean up some intermediate state --- .../controls-side-bar/opencv-control.tsx | 10 +++++----- .../controls-side-bar/tools-control.tsx | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx index 2dce8c899a3..d0cf65cdad6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx @@ -102,13 +102,13 @@ const mapDispatchToProps = { class OpenCVControlComponent extends React.PureComponent { private activeTool: IntelligentScissors | null; - private latestPoints: number[] | null; + private latestPoints: number[]; public constructor(props: Props & DispatchToProps) { super(props); const { labels, defaultApproxPolyAccuracy } = props; this.activeTool = null; - this.latestPoints = null; + this.latestPoints = []; this.state = { libraryInitialized: openCVWrapper.isInitialized, initializationError: false, @@ -128,7 +128,7 @@ class OpenCVControlComponent extends React.PureComponent { + private async runCVAlgorithm(pressedPoints: number[], threshold: number): Promise { // Getting image data const canvas: HTMLCanvasElement | undefined = window.document.getElementById('cvat_canvas_background') as | HTMLCanvasElement @@ -237,7 +237,7 @@ class OpenCVControlComponent extends React.PureComponent { this.setState({ approxPolyAccuracy: defaultApproxPolyAccuracy, }); + this.latestResult = []; + this.latestResponseResult = []; this.interactionIsDone = false; this.interactionIsAborted = false; window.addEventListener('contextmenu', this.contextmenuDisabler); @@ -239,6 +241,7 @@ export class ToolsControlComponent extends React.PureComponent { if (this.interactionIsAborted) { // while the server request // user has cancelled interaction (for example pressed ESC) + // need to clean variables that have been just set this.latestResult = []; this.latestResponseResult = []; return; From c111668d230e6b09f3fe7598e78c747e592dcaea Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 22 Jul 2021 21:02:33 +0300 Subject: [PATCH 10/17] Fixed scss --- cvat-canvas/src/scss/canvas.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 157d1020128..993745ba4fb 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -160,8 +160,10 @@ polyline.cvat_canvas_shape_splitting { } .cvat_canvas_removable_interaction_point { - cursor: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K') - 10 10, + cursor: + url( + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K' + ) 10 10, auto; } From ca42cb589be89dfd730aff04ca4fbf33594f2455 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 26 Jul 2021 10:56:06 +0300 Subject: [PATCH 11/17] Redesigned slider a bit --- .../approximation-accuracy.tsx | 57 +++++++++++++------ .../objects-side-bar/styles.scss | 12 +++- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx index 85fae4587fc..d0fc9426243 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx @@ -6,19 +6,26 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Text from 'antd/lib/typography/Text'; import Slider from 'antd/lib/slider'; +import { Col, Row } from 'antd/lib/grid'; interface Props { approxPolyAccuracy: number; onChange(value: number): void; } +const MAX_ACCURACY = 13; + export function thresholdFromAccuracy(approxPolyAccuracy: number): number { - const maxAccuracy = 7; - const approxPolyMaxDistance = maxAccuracy - approxPolyAccuracy; + const approxPolyMaxDistance = MAX_ACCURACY - approxPolyAccuracy; let threshold = 0; if (approxPolyMaxDistance > 0) { - // 0.5 for 1, 1 for 2, 2 for 3, 4 for 4, ... - threshold = 2 ** (approxPolyMaxDistance - 2); + if (approxPolyMaxDistance <= 8) { + // −2.75x+7y+1=0 linear made from two points (1; 0.25) and (8; 3) + threshold = (2.75 * approxPolyMaxDistance - 1) / 7; + } else { + // 4 for 9, 8 for 10, 16 for 11, 32 for 12, 64 for 13 + threshold = 2 ** (approxPolyMaxDistance - 7); + } } return threshold; @@ -30,18 +37,36 @@ function ApproximationAccuracy(props: Props): React.ReactPortal | null { return target ? ReactDOM.createPortal( -
- Approximation accuracy - -
, + + + Points: + + + less, + }, + 13: { + style: { + color: '#61c200', + }, + label: more, + }, + }} + /> + + , target, ) : null; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index d6580411a5d..a8a903713d0 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -399,13 +399,21 @@ } .cvat-approx-poly-threshold-wrapper { + width: $grid-unit-size * 32; position: absolute; background: $background-color-2; top: 8px; left: 50%; - opacity: 0.5; border-radius: 6px; border: 1px solid $border-color-3; z-index: 100; - padding: 4px 12px; + padding: $grid-unit-size / 2 $grid-unit-size * 2 $grid-unit-size / 2 $grid-unit-size / 2; + + .ant-slider-track { + background: linear-gradient(90deg, #1890ff 0%, #61c200 100%); + } + + .ant-slider-mark { + position: static; + } } From 1117bf855b06f3296f6e5b735c37d90b3c1869db Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 26 Jul 2021 11:18:52 +0300 Subject: [PATCH 12/17] Added errored shape --- cvat-canvas/src/typescript/interactionHandler.ts | 8 +++++--- .../controls-side-bar/approximation-accuracy.tsx | 2 +- .../header/settings-modal/workspace-settings.tsx | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/src/typescript/interactionHandler.ts b/cvat-canvas/src/typescript/interactionHandler.ts index 2c23f13bdc1..dcb8101ef5e 100644 --- a/cvat-canvas/src/typescript/interactionHandler.ts +++ b/cvat-canvas/src/typescript/interactionHandler.ts @@ -278,16 +278,18 @@ export class InteractionHandlerImpl implements InteractionHandler { if (!intermediateShape) return; const { shapeType, points } = intermediateShape; if (shapeType === 'polygon') { + const erroredShape = shapeType === 'polygon' && points.length < 3 * 2; this.drawnIntermediateShape = this.canvas .polygon(stringifyPoints(translateToCanvas(geometry.offset, points))) .attr({ 'color-rendering': 'optimizeQuality', 'shape-rendering': 'geometricprecision', 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + stroke: erroredShape ? 'red' : 'black', fill: 'none', }) .addClass('cvat_canvas_interact_intermediate_shape'); - this.selectize(true, this.drawnIntermediateShape); + this.selectize(true, this.drawnIntermediateShape, erroredShape); } else { throw new Error( `Shape type "${shapeType}" was not implemented at interactionHandler::updateIntermediateShape`, @@ -295,7 +297,7 @@ export class InteractionHandlerImpl implements InteractionHandler { } } - private selectize(value: boolean, shape: SVG.Element): void { + private selectize(value: boolean, shape: SVG.Element, erroredShape = false): void { const self = this; if (value) { @@ -307,7 +309,7 @@ export class InteractionHandlerImpl implements InteractionHandler { pointType(cx: number, cy: number): SVG.Circle { return this.nested .circle(this.options.pointSize) - .stroke('black') + .stroke(erroredShape ? 'red' : 'black') .fill('black') .center(cx, cy) .attr({ diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx index d0fc9426243..5f2be94b1a2 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx @@ -13,7 +13,7 @@ interface Props { onChange(value: number): void; } -const MAX_ACCURACY = 13; +export const MAX_ACCURACY = 13; export function thresholdFromAccuracy(approxPolyAccuracy: number): number { const approxPolyMaxDistance = MAX_ACCURACY - approxPolyAccuracy; 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 dad651b84fb..c64506aef61 100644 --- a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -10,6 +10,7 @@ import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; import Slider from 'antd/lib/slider'; +import { MAX_ACCURACY } from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; import { clamp } from 'utils/math'; interface Props { @@ -180,7 +181,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element { Date: Mon, 26 Jul 2021 11:30:12 +0300 Subject: [PATCH 13/17] Keep slider hidden while didn't recieve first points --- .../controls-side-bar/tools-control.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 415fa2185d3..6afee27e427 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -105,13 +105,13 @@ interface State { trackingProgress: number | null; trackingFrames: number; fetching: boolean; + pointsRecieved: boolean; approxPolyAccuracy: number; mode: 'detection' | 'interaction' | 'tracking'; } export class ToolsControlComponent extends React.PureComponent { private interactionIsAborted: boolean; - private interactionIsDone: boolean; private latestResponseResult: number[][]; private latestResult: number[][]; @@ -126,6 +126,7 @@ export class ToolsControlComponent extends React.PureComponent { trackingProgress: null, trackingFrames: 10, fetching: false, + pointsRecieved: false, mode: 'interaction', }; @@ -152,6 +153,7 @@ export class ToolsControlComponent extends React.PureComponent { // reset flags when start interaction/tracking this.setState({ approxPolyAccuracy: defaultApproxPolyAccuracy, + pointsRecieved: false, }); this.latestResult = []; this.latestResponseResult = []; @@ -236,6 +238,7 @@ export class ToolsControlComponent extends React.PureComponent { pos_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 0), neg_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 2), }); + this.latestResult = this.latestResponseResult; if (this.interactionIsAborted) { @@ -249,7 +252,7 @@ export class ToolsControlComponent extends React.PureComponent { this.latestResult = await this.approximateResponsePoints(this.latestResponseResult); } finally { - this.setState({ fetching: false }); + this.setState({ fetching: false, pointsRecieved: !!this.latestResult.length }); } } @@ -720,7 +723,7 @@ export class ToolsControlComponent extends React.PureComponent { interactors, detectors, trackers, isActivated, canvasInstance, labels, } = this.props; const { - fetching, trackingProgress, approxPolyAccuracy, activeInteractor, + fetching, trackingProgress, approxPolyAccuracy, activeInteractor, pointsRecieved, } = this.state; if (![...interactors, ...detectors, ...trackers].length) return null; @@ -761,7 +764,7 @@ export class ToolsControlComponent extends React.PureComponent { )} - {isActivated && activeInteractor !== null ? ( + {isActivated && activeInteractor !== null && pointsRecieved ? ( { From b1371767189e95efd426e04b4bfd33c5a4ec5b24 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 26 Jul 2021 12:16:06 +0300 Subject: [PATCH 14/17] Adjusted settings slider --- .../approximation-accuracy.tsx | 31 ++++++++++--------- .../objects-side-bar/styles.scss | 12 ++++--- .../settings-modal/workspace-settings.tsx | 8 +++-- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx index 5f2be94b1a2..2bfbf7fb32c 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { CSSProperties } from 'react'; import ReactDOM from 'react-dom'; import Text from 'antd/lib/typography/Text'; import Slider from 'antd/lib/slider'; @@ -15,6 +15,20 @@ interface Props { export const MAX_ACCURACY = 13; +export const marks: Record = {}; +marks[0] = { + style: { + color: '#1890ff', + }, + label: less, +}; +marks[MAX_ACCURACY] = { + style: { + color: '#61c200', + }, + label: more, +}; + export function thresholdFromAccuracy(approxPolyAccuracy: number): number { const approxPolyMaxDistance = MAX_ACCURACY - approxPolyAccuracy; let threshold = 0; @@ -50,20 +64,7 @@ function ApproximationAccuracy(props: Props): React.ReactPortal | null { dots tooltipVisible={false} onChange={onChange} - marks={{ - 0: { - style: { - color: '#1890ff', - }, - label: less, - }, - 13: { - style: { - color: '#61c200', - }, - label: more, - }, - }} + marks={marks} /> , diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index a8a903713d0..5b56f9d404c 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -398,7 +398,15 @@ opacity: 0.5; } +.cvat-workspace-settings-approx-poly-threshold { + .ant-slider-track { + background: linear-gradient(90deg, #1890ff 0%, #61c200 100%); + } +} + .cvat-approx-poly-threshold-wrapper { + @extend .cvat-workspace-settings-approx-poly-threshold; + width: $grid-unit-size * 32; position: absolute; background: $background-color-2; @@ -409,10 +417,6 @@ z-index: 100; padding: $grid-unit-size / 2 $grid-unit-size * 2 $grid-unit-size / 2 $grid-unit-size / 2; - .ant-slider-track { - background: linear-gradient(90deg, #1890ff 0%, #61c200 100%); - } - .ant-slider-mark { position: static; } 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 c64506aef61..e5d920494cc 100644 --- a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -10,7 +10,10 @@ import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; import Slider from 'antd/lib/slider'; -import { MAX_ACCURACY } from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; +import { + MAX_ACCURACY, + marks, +} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; import { clamp } from 'utils/math'; interface Props { @@ -178,7 +181,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element { Default polygon approximation accuracy level - + From b62d6bff7e012539b19250f059e5c0acd3b36302 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 26 Jul 2021 12:18:30 +0300 Subject: [PATCH 15/17] Updated label --- .../src/components/header/settings-modal/workspace-settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e5d920494cc..48299819d5e 100644 --- a/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -179,7 +179,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element { - Default polygon approximation accuracy level + Default number of points in polygon approximation Date: Tue, 27 Jul 2021 13:36:48 +0300 Subject: [PATCH 16/17] A couple of fixes for trackers & detectors --- .../controls-side-bar/tools-control.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 6afee27e427..9f4ff760b2d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -301,7 +301,8 @@ export class ToolsControlComponent extends React.PureComponent { const { activeLabelID } = this.state; const [label] = jobInstance.task.labels.filter((_label: any): boolean => _label.id === activeLabelID); - if (!(e as CustomEvent).detail.isDone) { + const { isDone, shapesUpdated } = (e as CustomEvent).detail; + if (!isDone || !shapesUpdated) { return; } @@ -382,7 +383,7 @@ export class ToolsControlComponent extends React.PureComponent { } public async trackState(state: any): Promise { - const { jobInstance, frame } = this.props; + const { jobInstance, frame, fetchAnnotations } = this.props; const { activeTracker, trackingFrames } = this.state; const { clientID, points } = state; @@ -434,6 +435,7 @@ export class ToolsControlComponent extends React.PureComponent { } } finally { this.setState({ trackingProgress: null, fetching: false }); + fetchAnnotations(); } } @@ -638,7 +640,7 @@ export class ToolsControlComponent extends React.PureComponent { private renderDetectorBlock(): JSX.Element { const { - jobInstance, detectors, curZOrder, frame, + jobInstance, detectors, curZOrder, frame, createAnnotations, } = this.props; if (!detectors.length) { @@ -677,7 +679,7 @@ export class ToolsControlComponent extends React.PureComponent { }), ); - createAnnotationsAsync(jobInstance, frame, states); + createAnnotations(jobInstance, frame, states); } catch (error) { notification.error({ description: error.toString(), From 1197639d1f870f627eacc69d57bf74e74cd7492b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 27 Jul 2021 17:15:01 +0300 Subject: [PATCH 17/17] Updated default value --- cvat-ui/src/reducers/settings-reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index d5910bcecab..301ff9a7e32 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -31,7 +31,7 @@ const defaultState: SettingsState = { showObjectsTextAlways: false, showAllInterpolationTracks: false, intelligentPolygonCrop: true, - defaultApproxPolyAccuracy: 4, + defaultApproxPolyAccuracy: 9, }, player: { canvasBackgroundColor: '#ffffff',