From 2bf427e898e42f5a6273b30424c7878431b1cb73 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 20 Jul 2021 12:44:08 +0300 Subject: [PATCH 01/32] 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 6fca99c3d334..a799dfcfe92d 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 d859c80578ef..2413f46230bf 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 06fd21a9d407..a1a0dbc239b6 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 1aef81decda1..5be37d6f07bb 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 2384c1664ff9..70705ea12b5e 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 eaae78a484a3..06ca097120b9 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 8e5a66e55db3..f82b431189d4 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 74394a254009..fab5e318363c 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/32] 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 f91233eaeccf..9cd1aeeedf56 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/32] 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 c6526a4c334b..2c23f13bdc1f 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/32] 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 fab5e318363c..9cb7e644a074 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/32] 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 2413f46230bf..0e2751e88860 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 667278fd02ad..d6580411a5da 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 9cb7e644a074..6fc7366e50e0 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/32] 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 a799dfcfe92d..966743c47ee4 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 0e2751e88860..32feeb3af3df 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 5be37d6f07bb..dad651b84fb1 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 70705ea12b5e..d92f44297855 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 06ca097120b9..7da382b85df3 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 f82b431189d4..d5910bcecab2 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/32] 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 71ef9865a1b0..de6d050e616b 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 d654c11a2b1e..52229d04b57f 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 2158072dd078..6e18e7efc8c1 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/32] 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 ff0a50fc28b4..157d1020128d 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 000000000000..85fae4587fc2 --- /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 9cd1aeeedf56..2dce8c899a36 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 32feeb3af3df..101c35626ef7 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 cd05eec50867..8fca230143f1 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 6fc7366e50e0..ff19cb4b543f 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/32] 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 2dce8c899a36..d0cf65cdad6a 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/32] 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 157d1020128d..993745ba4fbd 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/32] 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 85fae4587fc2..d0fc94262436 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 d6580411a5da..a8a903713d08 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/32] 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 2c23f13bdc1f..dcb8101ef5e5 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 d0fc94262436..5f2be94b1a26 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 dad651b84fb1..c64506aef614 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/32] 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 415fa2185d3c..6afee27e4272 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/32] 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 5f2be94b1a26..2bfbf7fb32c3 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 a8a903713d08..5b56f9d404cd 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 c64506aef614..e5d920494cc2 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/32] 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 e5d920494cc2..48299819d5e7 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:34:06 +0300 Subject: [PATCH 16/32] tmp --- .../controls-side-bar/tools-control.tsx | 65 +++++++++++++------ 1 file changed, 45 insertions(+), 20 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 6afee27e4272..79cee964b0f8 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 @@ -113,6 +113,7 @@ interface State { export class ToolsControlComponent extends React.PureComponent { private interactionIsAborted: boolean; private interactionIsDone: boolean; + private latestUserRequest: number[]; private latestResponseResult: number[][]; private latestResult: number[][]; @@ -130,6 +131,7 @@ export class ToolsControlComponent extends React.PureComponent { mode: 'interaction', }; + this.latestUserRequest = []; this.latestResponseResult = []; this.latestResult = []; this.interactionIsAborted = false; @@ -232,7 +234,9 @@ export class ToolsControlComponent extends React.PureComponent { if ((e as CustomEvent).detail.shapesUpdated) { this.setState({ fetching: true }); + let hide = null; try { + hide = message.loading(`Waiting a response from ${activeInteractor?.name}..`); this.latestResponseResult = await core.lambda.call(jobInstance.task, interactor, { frame, pos_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 0), @@ -252,6 +256,9 @@ export class ToolsControlComponent extends React.PureComponent { this.latestResult = await this.approximateResponsePoints(this.latestResponseResult); } finally { + if (hide) { + hide(); + } this.setState({ fetching: false, pointsRecieved: !!this.latestResult.length }); } } @@ -301,7 +308,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 +390,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 +442,7 @@ export class ToolsControlComponent extends React.PureComponent { } } finally { this.setState({ trackingProgress: null, fetching: false }); + fetchAnnotations(); } } @@ -638,7 +647,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 +686,7 @@ export class ToolsControlComponent extends React.PureComponent { }), ); - createAnnotationsAsync(jobInstance, frame, states); + createAnnotations(jobInstance, frame, states); } catch (error) { notification.error({ description: error.toString(), @@ -723,7 +732,7 @@ export class ToolsControlComponent extends React.PureComponent { interactors, detectors, trackers, isActivated, canvasInstance, labels, } = this.props; const { - fetching, trackingProgress, approxPolyAccuracy, activeInteractor, pointsRecieved, + fetching, trackingProgress, approxPolyAccuracy, pointsRecieved, mode, } = this.state; if (![...interactors, ...detectors, ...trackers].length) return null; @@ -747,35 +756,51 @@ export class ToolsControlComponent extends React.PureComponent { className: 'cvat-tools-control', }; - return !labels.length ? ( - - ) : ( + const showAnyContent = !!labels.length; + const showInteractionContent = isActivated && mode === 'interaction' && pointsRecieved; + const showDetectionContent = fetching && mode === 'detection'; + const showTrackingContent = fetching && mode === 'tracking' && trackingProgress !== null; + const formattedTrackingProgress = showTrackingContent ? +((trackingProgress as number) * 100).toFixed(0) : null; + + const interactionContent: JSX.Element | null = showInteractionContent ? ( <> + { + this.setState({ approxPolyAccuracy: value }); + }} + /> + + ) : null; + + const trackOrDetectModal: JSX.Element | null = + showDetectionContent || showTrackingContent ? ( Waiting for a server response.. - {trackingProgress !== null && ( - - )} + {showTrackingContent ? ( + + ) : null} - {isActivated && activeInteractor !== null && pointsRecieved ? ( - { - this.setState({ approxPolyAccuracy: value }); - }} - /> - ) : null} + ) : null; + + return showAnyContent ? ( + <> + {interactionContent} + {trackOrDetectModal} + ) : ( + ); } } From a1be09c3522065c6e4095643c7df6618542d27c0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 27 Jul 2021 13:36:48 +0300 Subject: [PATCH 17/32] 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 6afee27e4272..9f4ff760b2df 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 18/32] 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 d5910bcecab2..301ff9a7e324 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', From 7b2eb3bd13c31f8d85806a6c373ebe615f5a2942 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 28 Jul 2021 14:55:51 +0300 Subject: [PATCH 19/32] Initial non blocking UI when interaction --- .../controls-side-bar/tools-control.tsx | 222 +++++++++++------- 1 file changed, 135 insertions(+), 87 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 79cee964b0f8..df8f585a6d57 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 @@ -16,6 +16,7 @@ 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 lodash from 'lodash'; import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; @@ -111,11 +112,21 @@ interface State { } export class ToolsControlComponent extends React.PureComponent { - private interactionIsAborted: boolean; - private interactionIsDone: boolean; - private latestUserRequest: number[]; - private latestResponseResult: number[][]; - private latestResult: number[][]; + private interaction: { + id: string | null; + isAborted: boolean; + latestResponse: number[][]; + latestResult: number[][]; + latestRequest: null | { + interactor: Model; + data: { + frame: number; + neg_points: number[][]; + pos_points: number[][]; + }; + } | null; + hideMessage: (() => void) | null; + }; public constructor(props: Props) { super(props); @@ -131,11 +142,14 @@ export class ToolsControlComponent extends React.PureComponent { mode: 'interaction', }; - this.latestUserRequest = []; - this.latestResponseResult = []; - this.latestResult = []; - this.interactionIsAborted = false; - this.interactionIsDone = false; + this.interaction = { + id: null, + isAborted: false, + latestResponse: [], + latestResult: [], + latestRequest: null, + hideMessage: null, + }; } public componentDidMount(): void { @@ -147,32 +161,43 @@ export class ToolsControlComponent extends React.PureComponent { public componentDidUpdate(prevProps: Props, prevState: State): void { const { isActivated, defaultApproxPolyAccuracy, canvasInstance } = this.props; - const { approxPolyAccuracy, activeInteractor } = this.state; + const { approxPolyAccuracy, mode } = this.state; if (prevProps.isActivated && !isActivated) { window.removeEventListener('contextmenu', this.contextmenuDisabler); + // hide interaction message if exists + if (this.interaction.hideMessage) { + this.interaction.hideMessage(); + this.interaction.hideMessage = null; + } } else if (!prevProps.isActivated && isActivated) { // reset flags when start interaction/tracking + this.interaction = { + id: null, + isAborted: false, + latestResponse: [], + latestResult: [], + latestRequest: null, + hideMessage: null, + }; + this.setState({ approxPolyAccuracy: defaultApproxPolyAccuracy, pointsRecieved: false, }); - this.latestResult = []; - this.latestResponseResult = []; - this.interactionIsDone = false; - this.interactionIsAborted = false; + window.addEventListener('contextmenu', this.contextmenuDisabler); } if (prevState.approxPolyAccuracy !== approxPolyAccuracy) { - if (isActivated && activeInteractor !== null && this.latestResponseResult.length) { - this.approximateResponsePoints(this.latestResponseResult).then((points: number[][]) => { - this.latestResult = points; + if (isActivated && mode === 'interaction' && this.interaction.latestResponse.length) { + this.approximateResponsePoints(this.interaction.latestResponse).then((points: number[][]) => { + this.interaction.latestResult = points; canvasInstance.interact({ enabled: true, intermediateShape: { shapeType: ShapeType.POLYGON, - points: this.latestResult.flat(), + points: this.interaction.latestResult.flat(), }, }); }); @@ -198,96 +223,65 @@ export class ToolsControlComponent extends React.PureComponent { }; private cancelListener = async (): Promise => { - const { isActivated } = this.props; const { fetching } = this.state; - this.latestResult = []; - - if (isActivated) { - if (fetching && !this.interactionIsDone) { - // user pressed ESC - this.setState({ fetching: false }); - this.interactionIsAborted = true; - } + if (fetching) { + // user pressed ESC + this.setState({ fetching: false }); + this.interaction.isAborted = true; } }; - private onInteraction = async (e: Event): Promise => { - const { - frame, - labels, - curZOrder, - jobInstance, - isActivated, - activeLabelID, - canvasInstance, - createAnnotations, - } = this.props; + private runInteractionRequest = async (interactionId: string): Promise => { + const { jobInstance, canvasInstance } = this.props; const { activeInteractor, fetching } = this.state; - if (!isActivated) { + const { id, latestRequest } = this.interaction; + if (id !== interactionId || !latestRequest || fetching) { + // current interaction request is not relevant (new interaction session has started) + // or a user didn't add more points + // or one server request is on processing return; } - try { - this.interactionIsDone = (e as CustomEvent).detail.isDone; - const interactor = activeInteractor as Model; + const { interactor, data } = latestRequest; + this.interaction.latestRequest = null; - if ((e as CustomEvent).detail.shapesUpdated) { + try { + this.interaction.hideMessage = message.loading(`Waiting a response from ${activeInteractor?.name}..`, 0); + try { + // run server request this.setState({ fetching: true }); - let hide = null; - try { - hide = message.loading(`Waiting a response from ${activeInteractor?.name}..`); - 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; + const response = await core.lambda.call(jobInstance.task, interactor, data); - 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; - } + if (this.interaction.id !== interactionId || this.interaction.isAborted) { + // new interaction session or the session is aborted + return; + } - this.latestResult = await this.approximateResponsePoints(this.latestResponseResult); - } finally { - if (hide) { - hide(); - } - this.setState({ fetching: false, pointsRecieved: !!this.latestResult.length }); + this.interaction.latestResponse = response; + // approximation with cv.approxPolyDP + this.interaction.latestResult = await this.approximateResponsePoints(response); + this.setState({ pointsRecieved: !!response.length }); + } finally { + if (this.interaction.id === interactionId && this.interaction.hideMessage) { + this.interaction.hideMessage(); + this.interaction.hideMessage = null; } - } - if (!this.latestResult.length) { - return; + this.setState({ fetching: false }); } - if (this.interactionIsDone && !fetching) { - const object = new core.classes.ObjectState({ - frame, - objectType: ObjectType.SHAPE, - label: labels.length ? labels.filter((label: any) => label.id === activeLabelID)[0] : null, - shapeType: ShapeType.POLYGON, - points: this.latestResult.flat(), - occluded: false, - zOrder: curZOrder, - }); - - createAnnotations(jobInstance, frame, [object]); - } else { + if (this.interaction.latestResult.length) { canvasInstance.interact({ enabled: true, intermediateShape: { shapeType: ShapeType.POLYGON, - points: this.latestResult.flat(), + points: this.interaction.latestResult.flat(), }, }); } + + setTimeout(() => this.runInteractionRequest(interactionId)); } catch (err) { notification.error({ description: err.toString(), @@ -296,6 +290,43 @@ export class ToolsControlComponent extends React.PureComponent { } }; + private onInteraction = (e: Event): void => { + const { frame, isActivated } = this.props; + const { activeInteractor } = this.state; + + if (!isActivated) { + return; + } + + if (!this.interaction.id) { + this.interaction.id = lodash.uniqueId('interaction_'); + } + + const { shapesUpdated, isDone, shapes } = (e as CustomEvent).detail; + if (isDone) { + // make an object from current result + // do not make one more request + // prevent future requests if possible + this.interaction.isAborted = true; + this.interaction.latestRequest = null; + if (this.interaction.latestResult.length) { + this.constructFromPoints(this.interaction.latestResult); + } + } else if (shapesUpdated) { + const interactor = activeInteractor as Model; + this.interaction.latestRequest = { + interactor, + data: { + frame, + pos_points: convertShapesForInteractor(shapes, 0), + neg_points: convertShapesForInteractor(shapes, 2), + }, + }; + + this.runInteractionRequest(this.interaction.id); + } + }; + private onTracking = async (e: Event): Promise => { const { isActivated, jobInstance, frame, curZOrder, fetchAnnotations, @@ -313,7 +344,6 @@ export class ToolsControlComponent extends React.PureComponent { return; } - this.interactionIsDone = true; try { const { points } = (e as CustomEvent).detail.shapes[0]; const state = new core.classes.ObjectState({ @@ -370,11 +400,29 @@ export class ToolsControlComponent extends React.PureComponent { }); }; + private constructFromPoints(points: number[][]): void { + const { + frame, labels, curZOrder, jobInstance, activeLabelID, createAnnotations, + } = this.props; + + const object = new core.classes.ObjectState({ + frame, + objectType: ObjectType.SHAPE, + label: labels.length ? labels.filter((label: any) => label.id === activeLabelID)[0] : null, + shapeType: ShapeType.POLYGON, + points: points.flat(), + occluded: false, + zOrder: curZOrder, + }); + + createAnnotations(jobInstance, frame, [object]); + } + private async approximateResponsePoints(points: number[][]): Promise { const { approxPolyAccuracy } = this.state; if (points.length > 3) { if (!openCVWrapper.isInitialized) { - const hide = message.loading('OpenCV.js initialization..'); + const hide = message.loading('OpenCV.js initialization..', 0); try { await openCVWrapper.initialize(() => {}); } finally { From 9acd86f51dfab8cb2e6f2c17519a6f1a416c0031 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 28 Jul 2021 15:26:13 +0300 Subject: [PATCH 20/32] Minor fix --- .../standard-workspace/controls-side-bar/tools-control.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 df1ee8d6491d..bee76e3a9254 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 @@ -251,6 +251,8 @@ export class ToolsControlComponent extends React.PureComponent { // run server request this.setState({ fetching: true }); const response = await core.lambda.call(jobInstance.task, interactor, data); + // approximation with cv.approxPolyDP + const approximated = await this.approximateResponsePoints(response); if (this.interaction.id !== interactionId || this.interaction.isAborted) { // new interaction session or the session is aborted @@ -258,8 +260,8 @@ export class ToolsControlComponent extends React.PureComponent { } this.interaction.latestResponse = response; - // approximation with cv.approxPolyDP - this.interaction.latestResult = await this.approximateResponsePoints(response); + this.interaction.latestResult = approximated; + this.setState({ pointsRecieved: !!response.length }); } finally { if (this.interaction.id === interactionId && this.interaction.hideMessage) { From 86c1c06f1f7e325724af0cdbc28d6d4e33981e31 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 30 Jul 2021 12:52:26 +0300 Subject: [PATCH 21/32] Initial gifs feature --- .../controls-side-bar/interactor-tooltips.tsx | 53 +++++++++++++++++++ .../controls-side-bar/tools-control.tsx | 13 +++-- .../standard-workspace/styles.scss | 18 +++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx new file mode 100644 index 000000000000..0e19e2354fba --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx @@ -0,0 +1,53 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Image from 'antd/lib/image'; +import Paragraph from 'antd/lib/typography/Paragraph'; +import Text from 'antd/lib/typography/Text'; + +interface Props { + tool?: string; +} + +function InteractorTooltips(props: Props): JSX.Element { + const { tool } = props; + + const DEXTR_GIF = 'https://openvinotoolkit.github.io/cvat/images/gif019_detrac.gif'; + const DEXTR_DESC = + 'The interactor allows to get a mask of an object using its extreme points (more or equal 4). You can add a point left-clicking the image'; + + const UNKNOWN_DESC = 'Selected interactor does not have tips'; + + let gif = null; + let desc = ''; + + switch (tool) { + case undefined: + desc = 'Select an interactor to see description'; + break; + case 'DEXTR': + gif = DEXTR_GIF; + desc = DEXTR_DESC; + break; + default: + desc = UNKNOWN_DESC; + } + + return ( +
+ {desc} + {tool ? ( + + You can prevent server requests holding + {' Ctrl '} + key + + ) : null} + {gif ? Example gif : null} +
+ ); +} + +export default React.memo(InteractorTooltips); 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 bee76e3a9254..61ecd06e3006 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 @@ -4,7 +4,7 @@ import React, { MutableRefObject } from 'react'; import { connect } from 'react-redux'; -import Icon, { LoadingOutlined } from '@ant-design/icons'; +import Icon, { LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import Popover from 'antd/lib/popover'; import Select from 'antd/lib/select'; import Button from 'antd/lib/button'; @@ -16,6 +16,7 @@ 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 Dropdown from 'antd/lib/dropdown'; import lodash from 'lodash'; import { AIToolsIcon } from 'icons'; @@ -38,6 +39,7 @@ import ApproximationAccuracy, { thresholdFromAccuracy, } from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; import withVisibilityHandling from './handle-popover-visibility'; +import ToolsTooltips from './interactor-tooltips'; interface StateToProps { canvasInstance: Canvas; @@ -643,8 +645,8 @@ export class ToolsControlComponent extends React.PureComponent { Interactor
- - + + + + }> + + + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 6897e2f1cb87..1a8784e1acc9 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -192,6 +192,24 @@ margin-top: 10px; } +.cvat-interactors-tips-icon-container { + text-align: center; + font-size: 20px; +} + +.cvat-interactor-tip-container { + background: $background-color-2; + padding: $grid-unit-size; + box-shadow: $box-shadow-base; + width: $grid-unit-size * 40; + text-align: center; + border-radius: 4px; +} + +.cvat-interactor-tip-image { + width: $grid-unit-size * 37; +} + .cvat-draw-shape-popover-points-selector { width: 100%; } From f7416817a3ca6e716dada9680f8faa7c9571dae6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Aug 2021 12:02:14 +0300 Subject: [PATCH 22/32] Added tips from docs --- .../controls-side-bar/interactor-tooltips.tsx | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx index 0e19e2354fba..e941d7f15d75 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx @@ -14,9 +14,14 @@ interface Props { function InteractorTooltips(props: Props): JSX.Element { const { tool } = props; - const DEXTR_GIF = 'https://openvinotoolkit.github.io/cvat/images/gif019_detrac.gif'; + const DEXTR_GIF = 'https://openvinotoolkit.github.io/cvat/images/dextr_example.gif'; + const FBRS_GIF = 'https://openvinotoolkit.github.io/cvat/images/fbrs_example.gif'; + const IOG_GIF = 'https://openvinotoolkit.github.io/cvat/images/iog_example.gif'; const DEXTR_DESC = 'The interactor allows to get a mask of an object using its extreme points (more or equal 4). You can add a point left-clicking the image'; + const FBRS_DESC = 'The interactor allows to get a mask for an object using positive points, and negative points.'; + const IOG_DESC = + 'The interactor allows to get a mask of an object using its wrapping boundig box, positive, and negative points inside it'; const UNKNOWN_DESC = 'Selected interactor does not have tips'; @@ -31,6 +36,14 @@ function InteractorTooltips(props: Props): JSX.Element { gif = DEXTR_GIF; desc = DEXTR_DESC; break; + case 'f-BRS': + gif = FBRS_GIF; + desc = FBRS_DESC; + break; + case 'IOG': + gif = IOG_GIF; + desc = IOG_DESC; + break; default: desc = UNKNOWN_DESC; } @@ -39,13 +52,19 @@ function InteractorTooltips(props: Props): JSX.Element {
{desc} {tool ? ( - - You can prevent server requests holding - {' Ctrl '} - key - + <> + + You can prevent server requests holding + {' Ctrl '} + key + + + Positive points can be added by left-clicking the image. + Negative points can be added by right-clicking the image. + + ) : null} - {gif ? Example gif : null} + {gif ? Example gif : null}
); } From 21f283f69745e87654e90d3bef26a27af09219f6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Aug 2021 12:03:36 +0300 Subject: [PATCH 23/32] Additional optional tip --- .../controls-side-bar/interactor-tooltips.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx index e941d7f15d75..9f2905630aa8 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx @@ -58,10 +58,12 @@ function InteractorTooltips(props: Props): JSX.Element { {' Ctrl '} key - - Positive points can be added by left-clicking the image. - Negative points can be added by right-clicking the image. - + {['f-BRS', 'IOG'].includes(tool) ? ( + + Positive points can be added by left-clicking the image. + Negative points can be added by right-clicking the image. + + ) : null} ) : null} {gif ? Example gif : null} From 4d4abe1f566d66cafeda8f79c5601492894651f8 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Aug 2021 12:30:36 +0300 Subject: [PATCH 24/32] Updated version & changelog --- CHANGELOG.md | 2 ++ cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29bd43929a23..818a0cdb21ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Explicit "Done" button when drawing any polyshapes () - Histogram equalization with OpenCV javascript () - Client-side polyshapes approximation when using semi-automatic interactors & scissors () +- Additional inline tips in interactors with demo gifs () ### Changed @@ -28,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated UI handling for IOG serverless function () - Changed Nginx proxy to Traefik in `docker-compose.yml` () - Simplify the process of deploying CVAT with HTTPS () +- Non-blocking UI when using interactors () ### Deprecated diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index e1a4936e74c5..d9e2cea8eb4b 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.21.1", + "version": "1.22.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index b3bb6360c2a1..45556b008db4 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.21.1", + "version": "1.22.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { From c2a8dbd6a53e50a9f947fcf7c426fda9d681c8af Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Aug 2021 18:47:11 +0300 Subject: [PATCH 25/32] Merged develop --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 528a534af9b1..86213e83ef6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- TDB +- Additional inline tips in interactors with demo gifs () ### Changed -- TDB +- Non-blocking UI when using interactors () ### Deprecated @@ -43,7 +43,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Explicit "Done" button when drawing any polyshapes () - Histogram equalization with OpenCV javascript () - Client-side polyshapes approximation when using semi-automatic interactors & scissors () -- Additional inline tips in interactors with demo gifs () ### Changed @@ -55,7 +54,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated UI handling for IOG serverless function () - Changed Nginx proxy to Traefik in `docker-compose.yml` () - Simplify the process of deploying CVAT with HTTPS () -- Non-blocking UI when using interactors () ### Fixed From e6e743d180c7c198cd0f9a9802cc4711de1a6f00 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 4 Aug 2021 12:37:25 +0300 Subject: [PATCH 26/32] Redesigned condition --- .../controls-side-bar/interactor-tooltips.tsx | 13 +++++++------ .../controls-side-bar/tools-control.tsx | 8 +++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx index 9f2905630aa8..3e8fe450ec4a 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx @@ -9,10 +9,11 @@ import Text from 'antd/lib/typography/Text'; interface Props { tool?: string; + withNegativePoints?: boolean; } function InteractorTooltips(props: Props): JSX.Element { - const { tool } = props; + const { tool, withNegativePoints } = props; const DEXTR_GIF = 'https://openvinotoolkit.github.io/cvat/images/dextr_example.gif'; const FBRS_GIF = 'https://openvinotoolkit.github.io/cvat/images/fbrs_example.gif'; @@ -58,12 +59,12 @@ function InteractorTooltips(props: Props): JSX.Element { {' Ctrl '} key - {['f-BRS', 'IOG'].includes(tool) ? ( - - Positive points can be added by left-clicking the image. + + Positive points can be added by left-clicking the image. + {withNegativePoints ? ( Negative points can be added by right-clicking the image. - - ) : null} + ) : null} + ) : null} {gif ? Example gif : 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 61ecd06e3006..ec285b5a6d3a 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 @@ -638,6 +638,8 @@ export class ToolsControlComponent extends React.PureComponent { ); } + const minNegVertices: number = activeInteractor ? activeInteractor.params.canvas.minNegVertices : -1; + return ( <> @@ -666,7 +668,11 @@ export class ToolsControlComponent extends React.PureComponent { - }> + = 0} tool={activeInteractor?.name} /> + } + > From ce677f3a8008f674fdd513e05de039c84a63f306 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Aug 2021 11:28:09 +0300 Subject: [PATCH 27/32] Keep gif & message within serverless yaml --- cvat-core/src/ml-model.js | 15 +++++ .../controls-side-bar/interactor-tooltips.tsx | 55 +++++-------------- .../controls-side-bar/tools-control.tsx | 12 ++-- cvat-ui/src/reducers/interfaces.ts | 6 +- cvat/apps/lambda_manager/views.py | 6 +- .../openvino/dextr/nuclio/function.yaml | 2 + .../saic-vul/fbrs/nuclio/function.yaml | 2 + .../shiyinzhang/iog/nuclio/function.yaml | 2 + 8 files changed, 53 insertions(+), 47 deletions(-) diff --git a/cvat-core/src/ml-model.js b/cvat-core/src/ml-model.js index e16cf24e27f2..98869c40faaf 100644 --- a/cvat-core/src/ml-model.js +++ b/cvat-core/src/ml-model.js @@ -14,6 +14,10 @@ class MLModel { this._framework = data.framework; this._description = data.description; this._type = data.type; + this._tip = { + message: data.help_message, + gif: data.example_gif_url, + }; this._params = { canvas: { minPosVertices: data.min_pos_points, @@ -84,6 +88,17 @@ class MLModel { canvas: { ...this._params.canvas }, }; } + + /** + * @typedef {Object} MlModelTip + * @property {string} message A short message for a user about the model + * @property {string} gif A gif URL to be shawn to a user as an example + * @returns {MlModelTip} + * @readonly + */ + get tip() { + return { ...this._tip }; + } } module.exports = MLModel; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx index 3e8fe450ec4a..6ee62ed1e6ae 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx @@ -8,52 +8,23 @@ import Paragraph from 'antd/lib/typography/Paragraph'; import Text from 'antd/lib/typography/Text'; interface Props { - tool?: string; + name?: string; + gif?: string; + message?: string; withNegativePoints?: boolean; } function InteractorTooltips(props: Props): JSX.Element { - const { tool, withNegativePoints } = props; - - const DEXTR_GIF = 'https://openvinotoolkit.github.io/cvat/images/dextr_example.gif'; - const FBRS_GIF = 'https://openvinotoolkit.github.io/cvat/images/fbrs_example.gif'; - const IOG_GIF = 'https://openvinotoolkit.github.io/cvat/images/iog_example.gif'; - const DEXTR_DESC = - 'The interactor allows to get a mask of an object using its extreme points (more or equal 4). You can add a point left-clicking the image'; - const FBRS_DESC = 'The interactor allows to get a mask for an object using positive points, and negative points.'; - const IOG_DESC = - 'The interactor allows to get a mask of an object using its wrapping boundig box, positive, and negative points inside it'; - - const UNKNOWN_DESC = 'Selected interactor does not have tips'; - - let gif = null; - let desc = ''; - - switch (tool) { - case undefined: - desc = 'Select an interactor to see description'; - break; - case 'DEXTR': - gif = DEXTR_GIF; - desc = DEXTR_DESC; - break; - case 'f-BRS': - gif = FBRS_GIF; - desc = FBRS_DESC; - break; - case 'IOG': - gif = IOG_GIF; - desc = IOG_DESC; - break; - default: - desc = UNKNOWN_DESC; - } - + const { + name, gif, message, withNegativePoints, + } = props; + const UNKNOWN_MESSAGE = 'Selected interactor does not have a help message'; + const desc = message || UNKNOWN_MESSAGE; return (
- {desc} - {tool ? ( + {name ? ( <> + {desc} You can prevent server requests holding {' Ctrl '} @@ -65,9 +36,11 @@ function InteractorTooltips(props: Props): JSX.Element { Negative points can be added by right-clicking the image. ) : null} + {gif ? Example gif : null} - ) : null} - {gif ? Example gif : null} + ) : ( + Select an interactor to see help message + )}
); } 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 ec285b5a6d3a..52ded26dc771 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 @@ -638,7 +638,7 @@ export class ToolsControlComponent extends React.PureComponent { ); } - const minNegVertices: number = activeInteractor ? activeInteractor.params.canvas.minNegVertices : -1; + const minNegVertices = activeInteractor ? (activeInteractor.params.canvas.minNegVertices as number) : -1; return ( <> @@ -669,9 +669,13 @@ export class ToolsControlComponent extends React.PureComponent { = 0} tool={activeInteractor?.name} /> - } + overlay={( + = 0} + {...(activeInteractor?.tip || {})} + /> + )} > diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 7da382b85df3..adbe7bffcdf5 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -182,8 +182,12 @@ export interface Model { framework: string; description: string; type: string; + tip: { + message: string; + gif: string; + }; params: { - canvas: Record; + canvas: Record; }; } diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 53bb2c143892..ef656a675bd0 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -113,6 +113,8 @@ def __init__(self, gateway, data): self.min_pos_points = int(meta_anno.get('min_pos_points', 1)) self.min_neg_points = int(meta_anno.get('min_neg_points', -1)) self.startswith_box = bool(meta_anno.get('startswith_box', False)) + self.example_gif_url = meta_anno.get('example_gif_url', '') + self.help_message = meta_anno.get('help_message', '') self.gateway = gateway def to_dict(self): @@ -129,7 +131,9 @@ def to_dict(self): response.update({ 'min_pos_points': self.min_pos_points, 'min_neg_points': self.min_neg_points, - 'startswith_box': self.startswith_box + 'startswith_box': self.startswith_box, + 'help_message': self.help_message, + 'example_gif_url': self.example_gif_url }) if self.kind is LambdaType.TRACKER: diff --git a/serverless/openvino/dextr/nuclio/function.yaml b/serverless/openvino/dextr/nuclio/function.yaml index 825599428f62..3121792a42b4 100644 --- a/serverless/openvino/dextr/nuclio/function.yaml +++ b/serverless/openvino/dextr/nuclio/function.yaml @@ -7,6 +7,8 @@ metadata: spec: framework: openvino min_pos_points: 4 + example_gif_url: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/dextr_example.gif + help_message: The interactor allows to get a mask of an object using its extreme points (more or equal than 4). You can add a point left-clicking the image spec: description: Deep Extreme Cut diff --git a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml index 57b78b718de0..ad00d1a5fa51 100644 --- a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml +++ b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml @@ -8,6 +8,8 @@ metadata: framework: pytorch min_pos_points: 1 min_neg_points: 0 + example_gif_url: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/fbrs_example.gif + help_message: The interactor allows to get a mask for an object using positive points, and negative points spec: description: f-BRS interactive segmentation diff --git a/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml b/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml index a210195fcd54..14d031b15462 100644 --- a/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml +++ b/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml @@ -9,6 +9,8 @@ metadata: min_pos_points: 1 min_neg_points: 0 startswith_box: true + example_gif_url: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/iog_example.gif + help_message: The interactor allows to get a mask of an object using its wrapping boundig box, positive, and negative points inside it spec: description: Interactive Object Segmentation with Inside-Outside Guidance From 94de8cdefd4f6a714c55169145d02257864f0518 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Aug 2021 13:09:21 +0300 Subject: [PATCH 28/32] Opacity level for drawhandler --- cvat-canvas/src/scss/canvas.scss | 7 ++-- cvat-canvas/src/typescript/canvasModel.ts | 5 +++ cvat-canvas/src/typescript/canvasView.ts | 4 +++ cvat-canvas/src/typescript/drawHandler.ts | 26 ++++++++++++++- .../src/typescript/interactionHandler.ts | 32 +++++++++++++++++-- cvat-canvas/src/typescript/svg.patch.ts | 8 +++-- .../annotation-page/canvas/canvas-wrapper.tsx | 30 ++++++++--------- .../annotation-page/canvas/canvas-wrapper.tsx | 13 ++++---- 8 files changed, 93 insertions(+), 32 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 993745ba4fbd..549e3b65323b 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -24,7 +24,6 @@ polyline.cvat_shape_action_opacity { } .cvat_shape_drawing_opacity { - fill-opacity: 0.2; stroke-opacity: 1; } @@ -160,10 +159,8 @@ 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; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 0da3bce42a91..21658dfeeff9 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -59,6 +59,7 @@ export interface Configuration { forceDisableEditing?: boolean; intelligentPolygonCrop?: boolean; forceFrameUpdate?: boolean; + creationOpacity?: number; } export interface DrawData { @@ -656,6 +657,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate; } + if (typeof configuration.creationOpacity === 'number') { + this.data.configuration.creationOpacity = configuration.creationOpacity; + } + this.notify(UpdateReasons.CONFIG_UPDATED); } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index f1a9f4662b00..1eceef6daf31 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -998,6 +998,8 @@ export class CanvasViewImpl implements CanvasView, Listener { this.adoptedContent, this.adoptedText, this.autoborderHandler, + this.geometry, + this.configuration, ); this.editHandler = new EditHandlerImpl(this.onEditDone.bind(this), this.adoptedContent, this.autoborderHandler); this.mergeHandler = new MergeHandlerImpl( @@ -1026,6 +1028,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.onInteraction.bind(this), this.adoptedContent, this.geometry, + this.configuration, ); // Setup event handlers @@ -1117,6 +1120,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.activate(activeElement); this.editHandler.configurate(this.configuration); this.drawHandler.configurate(this.configuration); + this.interactionHandler.configurate(this.configuration); // remove if exist and not enabled // this.setupObjects([]); diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 8bf38eee1d45..847375a612ab 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -50,6 +50,7 @@ export class DrawHandlerImpl implements DrawHandler { private crosshair: Crosshair; private drawData: DrawData; private geometry: Geometry; + private configuration: Configuration; private autoborderHandler: AutoborderHandler; private autobordersEnabled: boolean; @@ -371,6 +372,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': this.configuration.creationOpacity, }); } @@ -527,6 +529,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': this.configuration.creationOpacity, }); this.drawPolyshape(); @@ -597,6 +600,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': this.configuration.creationOpacity, }); } @@ -654,6 +658,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': this.configuration.creationOpacity, }); this.pasteShape(); @@ -686,6 +691,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': this.configuration.creationOpacity, }); this.pasteShape(); this.pastePolyshape(); @@ -709,6 +715,7 @@ export class DrawHandlerImpl implements DrawHandler { .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'face-stroke': 'black', + 'fill-opacity': this.configuration.creationOpacity, }); this.pasteShape(); this.pastePolyshape(); @@ -845,6 +852,8 @@ export class DrawHandlerImpl implements DrawHandler { canvas: SVG.Container, text: SVG.Container, autoborderHandler: AutoborderHandler, + geometry: Geometry, + configuration: Configuration, ) { this.autoborderHandler = autoborderHandler; this.autobordersEnabled = false; @@ -855,7 +864,8 @@ export class DrawHandlerImpl implements DrawHandler { this.initialized = false; this.canceled = false; this.drawData = null; - this.geometry = null; + this.geometry = geometry; + this.configuration = configuration; this.crosshair = new Crosshair(); this.drawInstance = null; this.pointsGroup = null; @@ -874,6 +884,20 @@ export class DrawHandlerImpl implements DrawHandler { } public configurate(configuration: Configuration): void { + this.configuration = configuration; + + const isFillableRect = this.drawData + && this.drawData.shapeType === 'rectangle' + && (this.drawData.rectDrawingMethod === RectDrawingMethod.CLASSIC || this.drawData.initialState); + const isFillableCuboid = this.drawData + && this.drawData.shapeType === 'cuboid' + && (this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CLASSIC || this.drawData.initialState); + const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon'; + + if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) { + this.drawInstance.fill({ opacity: configuration.creationOpacity }); + } + if (typeof configuration.autoborders === 'boolean') { this.autobordersEnabled = configuration.autoborders; if (this.drawInstance) { diff --git a/cvat-canvas/src/typescript/interactionHandler.ts b/cvat-canvas/src/typescript/interactionHandler.ts index dcb8101ef5e5..b7ede60495c7 100644 --- a/cvat-canvas/src/typescript/interactionHandler.ts +++ b/cvat-canvas/src/typescript/interactionHandler.ts @@ -8,16 +8,20 @@ import Crosshair from './crosshair'; import { translateToSVG, PropType, stringifyPoints, translateToCanvas, } from './shared'; -import { InteractionData, InteractionResult, Geometry } from './canvasModel'; +import { + InteractionData, InteractionResult, Geometry, Configuration, +} from './canvasModel'; export interface InteractionHandler { transform(geometry: Geometry): void; interact(interactData: InteractionData): void; + configurate(config: Configuration): void; cancel(): void; } export class InteractionHandlerImpl implements InteractionHandler { private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void; + private configuration: Configuration; private geometry: Geometry; private canvas: SVG.Container; private interactionData: InteractionData; @@ -196,7 +200,8 @@ export class InteractionHandlerImpl implements InteractionHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - }); + }) + .fill({ opacity: this.configuration.creationOpacity, color: 'white' }); } private initInteraction(): void { @@ -286,8 +291,8 @@ export class InteractionHandlerImpl implements InteractionHandler { 'shape-rendering': 'geometricprecision', 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, stroke: erroredShape ? 'red' : 'black', - fill: 'none', }) + .fill({ opacity: this.configuration.creationOpacity, color: 'white' }) .addClass('cvat_canvas_interact_intermediate_shape'); this.selectize(true, this.drawnIntermediateShape, erroredShape); } else { @@ -339,12 +344,14 @@ export class InteractionHandlerImpl implements InteractionHandler { ) => void, canvas: SVG.Container, geometry: Geometry, + configuration: Configuration, ) { this.onInteraction = (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean): void => { this.shapesWereUpdated = false; onInteraction(shapes, shapesUpdated, isDone, this.threshold ? this.thresholdRectSize / 2 : null); }; this.canvas = canvas; + this.configuration = configuration; this.geometry = geometry; this.shapesWereUpdated = false; this.interactionShapes = []; @@ -465,6 +472,25 @@ export class InteractionHandlerImpl implements InteractionHandler { } } + public configurate(configuration: Configuration): void { + this.configuration = configuration; + if (this.drawnIntermediateShape) { + this.drawnIntermediateShape.fill({ + opacity: configuration.creationOpacity, + }); + } + + // when interactRectangle + if (this.currentInteractionShape && this.currentInteractionShape.type === 'rect') { + this.currentInteractionShape.fill({ opacity: configuration.creationOpacity }); + } + + // when interactPoints with startwithbbox + if (this.interactionShapes[0] && this.interactionShapes[0].type === 'rect') { + this.interactionShapes[0].fill({ opacity: configuration.creationOpacity }); + } + } + public cancel(): void { this.release(); this.onInteraction(null); diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 060dde62e2a6..debebfd1f901 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -958,8 +958,12 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { }, paintOrientationLines() { - const fillColor = this.attr('fill'); - const strokeColor = this.attr('stroke'); + // style has higher priority than attr, so then try to fetch it if exists + // https://stackoverflow.com/questions/47088409/svg-attributes-beaten-by-cssstyle-in-priority] + // we use getComputedStyle to get actual, not-inlined css property (come from the corresponding css class) + const computedStyles = getComputedStyle(this.node); + const fillColor = computedStyles['fill'] || this.attr('fill'); + const strokeColor = computedStyles['stroke'] || this.attr('stroke'); const selectedColor = this.attr('face-stroke') || '#b0bec5'; this.frontTopEdge.stroke({ color: selectedColor }); this.frontLeftEdge.stroke({ color: selectedColor }); 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 fb21708fd116..1afcc0c89f37 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx @@ -106,6 +106,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { showObjectsTextAlways, workspace, showProjections, + selectedOpacity, } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; @@ -121,6 +122,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE, intelligentPolygonCrop, showProjections, + creationOpacity: selectedOpacity, }); this.initialSetup(); @@ -166,7 +168,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { prevProps.showObjectsTextAlways !== showObjectsTextAlways || prevProps.automaticBordering !== automaticBordering || prevProps.showProjections !== showProjections || - prevProps.intelligentPolygonCrop !== intelligentPolygonCrop + prevProps.intelligentPolygonCrop !== intelligentPolygonCrop || + prevProps.selectedOpacity !== selectedOpacity ) { canvasInstance.configure({ undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, @@ -174,6 +177,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { autoborders: automaticBordering, showProjections, intelligentPolygonCrop, + creationOpacity: selectedOpacity, }); } @@ -198,7 +202,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.activate(null); const el = window.document.getElementById(`cvat_canvas_shape_${prevProps.activatedStateID}`); if (el) { - (el as any).instance.fill({ opacity: opacity / 100 }); + (el as any).instance.fill({ opacity }); } } @@ -214,7 +218,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { } if (gridPattern) { gridPattern.style.stroke = gridColor.toLowerCase(); - gridPattern.style.opacity = `${gridOpacity / 100}`; + gridPattern.style.opacity = `${gridOpacity}`; } } @@ -225,10 +229,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { ) { const backgroundElement = window.document.getElementById('cvat_canvas_background'); if (backgroundElement) { - backgroundElement.style.filter = - `brightness(${brightnessLevel / 100})` + - `contrast(${contrastLevel / 100})` + - `saturate(${saturationLevel / 100})`; + const filter = `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`; + backgroundElement.style.filter = filter; } } @@ -619,7 +621,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { } const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); if (el) { - ((el as any) as SVGElement).setAttribute('fill-opacity', `${selectedOpacity / 100}`); + ((el as any) as SVGElement).setAttribute('fill-opacity', `${selectedOpacity}`); } } } @@ -648,7 +650,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { handler.nested.fill({ color: shapeColor }); } - (shapeView as any).instance.fill({ color: shapeColor, opacity: opacity / 100 }); + (shapeView as any).instance.fill({ color: shapeColor, opacity }); (shapeView as any).instance.stroke({ color: outlined ? outlineColor : shapeColor }); } } @@ -710,17 +712,15 @@ export default class CanvasWrapperComponent extends React.PureComponent { } if (gridPattern) { gridPattern.style.stroke = gridColor.toLowerCase(); - gridPattern.style.opacity = `${gridOpacity / 100}`; + gridPattern.style.opacity = `${gridOpacity}`; } canvasInstance.grid(gridSize, gridSize); // Filters const backgroundElement = window.document.getElementById('cvat_canvas_background'); if (backgroundElement) { - backgroundElement.style.filter = - `brightness(${brightnessLevel / 100})` + - `contrast(${contrastLevel / 100})` + - `saturate(${saturationLevel / 100})`; + const filter = `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`; + backgroundElement.style.filter = filter; } const canvasWrapperElement = window.document @@ -823,7 +823,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { - }> + }> 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 cc92a23131ee..d7d52d9ac7f6 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -90,6 +90,7 @@ interface StateToProps { maxZLayer: number; curZLayer: number; automaticBordering: boolean; + intelligentPolygonCrop: boolean; switchableAutomaticBordering: boolean; keyMap: KeyMap; canvasBackgroundColor: string; @@ -188,9 +189,9 @@ function mapStateToProps(state: CombinedState): StateToProps { activatedAttributeID, selectedStatesID, annotations, - opacity, + opacity: opacity / 100, colorBy, - selectedOpacity, + selectedOpacity: selectedOpacity / 100, outlined, outlineColor, showBitmap, @@ -198,12 +199,12 @@ function mapStateToProps(state: CombinedState): StateToProps { grid, gridSize, gridColor, - gridOpacity, + gridOpacity: gridOpacity / 100, activeLabelID, activeObjectType, - brightnessLevel, - contrastLevel, - saturationLevel, + brightnessLevel: brightnessLevel / 100, + contrastLevel: contrastLevel / 100, + saturationLevel: saturationLevel / 100, resetZoom, aamZoomMargin, showObjectsTextAlways, From d9e3928137c08a0585382bb25ea58c983c8d813e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Aug 2021 13:11:29 +0300 Subject: [PATCH 29/32] Updated version & changelog --- CHANGELOG.md | 1 + cvat-canvas/package-lock.json | 2 +- cvat-canvas/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd87e14b0064..8f95eeab3b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Non-blocking UI when using interactors () +- "Selected opacity" slider now defines opacity level for shapes being drawnSelected opacity () ### Deprecated diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index abaab237db6e..a598d7215b3d 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.5.0", + "version": "2.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 813dab0fdd45..ad5dd2897ff2 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.5.0", + "version": "2.6.0", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { From d3f0f9d1e2e05f4f50d2e783e67c264fdbde5e36 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Aug 2021 13:27:12 +0300 Subject: [PATCH 30/32] Fixed style --- cvat-canvas/src/scss/canvas.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 549e3b65323b..5e21171d2cdf 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -159,8 +159,9 @@ 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 9e6844c82061cebc68643736aa6c8afa901d58c0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Aug 2021 16:03:47 +0300 Subject: [PATCH 31/32] Fixed minor issue when drawing with N --- cvat-canvas/src/typescript/canvasModel.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 21658dfeeff9..7732fea355a2 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -548,6 +548,16 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } } + // install default values for drawing method + if (drawData.enabled) { + if (drawData.shapeType === 'rectangle') { + this.data.drawData.rectDrawingMethod = drawData.rectDrawingMethod || RectDrawingMethod.CLASSIC; + } + if (drawData.shapeType === 'cuboid') { + this.data.drawData.cuboidDrawingMethod = drawData.cuboidDrawingMethod || CuboidDrawingMethod.CLASSIC; + } + } + this.notify(UpdateReasons.DRAW); } From df5329985d1523b457721b62de6d3c3c5a01a923 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Fri, 6 Aug 2021 13:20:31 +0300 Subject: [PATCH 32/32] Renamed example_gif_url to animated_gif --- cvat-core/src/ml-model.js | 2 +- cvat/apps/lambda_manager/views.py | 4 ++-- serverless/openvino/dextr/nuclio/function.yaml | 2 +- serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml | 2 +- serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cvat-core/src/ml-model.js b/cvat-core/src/ml-model.js index 98869c40faaf..010e674626b8 100644 --- a/cvat-core/src/ml-model.js +++ b/cvat-core/src/ml-model.js @@ -16,7 +16,7 @@ class MLModel { this._type = data.type; this._tip = { message: data.help_message, - gif: data.example_gif_url, + gif: data.animated_gif, }; this._params = { canvas: { diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index ef656a675bd0..f5a34217e244 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -113,7 +113,7 @@ def __init__(self, gateway, data): self.min_pos_points = int(meta_anno.get('min_pos_points', 1)) self.min_neg_points = int(meta_anno.get('min_neg_points', -1)) self.startswith_box = bool(meta_anno.get('startswith_box', False)) - self.example_gif_url = meta_anno.get('example_gif_url', '') + self.animated_gif = meta_anno.get('animated_gif', '') self.help_message = meta_anno.get('help_message', '') self.gateway = gateway @@ -133,7 +133,7 @@ def to_dict(self): 'min_neg_points': self.min_neg_points, 'startswith_box': self.startswith_box, 'help_message': self.help_message, - 'example_gif_url': self.example_gif_url + 'animated_gif': self.animated_gif }) if self.kind is LambdaType.TRACKER: diff --git a/serverless/openvino/dextr/nuclio/function.yaml b/serverless/openvino/dextr/nuclio/function.yaml index 3121792a42b4..b996c0d8285e 100644 --- a/serverless/openvino/dextr/nuclio/function.yaml +++ b/serverless/openvino/dextr/nuclio/function.yaml @@ -7,7 +7,7 @@ metadata: spec: framework: openvino min_pos_points: 4 - example_gif_url: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/dextr_example.gif + animated_gif: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/dextr_example.gif help_message: The interactor allows to get a mask of an object using its extreme points (more or equal than 4). You can add a point left-clicking the image spec: diff --git a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml index ad00d1a5fa51..c0c4e6dcf35e 100644 --- a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml +++ b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml @@ -8,7 +8,7 @@ metadata: framework: pytorch min_pos_points: 1 min_neg_points: 0 - example_gif_url: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/fbrs_example.gif + animated_gif: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/fbrs_example.gif help_message: The interactor allows to get a mask for an object using positive points, and negative points spec: diff --git a/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml b/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml index 14d031b15462..6535525a1f92 100644 --- a/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml +++ b/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml @@ -9,7 +9,7 @@ metadata: min_pos_points: 1 min_neg_points: 0 startswith_box: true - example_gif_url: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/iog_example.gif + animated_gif: https://raw.githubusercontent.com/openvinotoolkit/cvat/0fbb19ae3846a017853d52e187f0ce149adced7d/site/content/en/images/iog_example.gif help_message: The interactor allows to get a mask of an object using its wrapping boundig box, positive, and negative points inside it spec: