From 0cf3978ead40b97799f7cb637aac0e6daee27c56 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 31 Mar 2020 14:04:18 +0300 Subject: [PATCH 01/46] Fixed escape in draw --- cvat-canvas/src/typescript/drawHandler.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 4afe75ec259d..f6c1d947deb8 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -50,6 +50,7 @@ export class DrawHandlerImpl implements DrawHandler { // so, methods like draw() just undefined for SVG.Shape, but nevertheless they exist private drawInstance: any; private initialized: boolean; + private canceled: boolean; private pointsGroup: SVG.G | null; private shapeSizeElement: ShapeSizeElement; @@ -149,6 +150,7 @@ export class DrawHandlerImpl implements DrawHandler { // Clear drawing this.drawInstance.draw('stop'); } + this.drawInstance.off(); this.drawInstance.remove(); this.drawInstance = null; @@ -161,6 +163,7 @@ export class DrawHandlerImpl implements DrawHandler { if (this.crosshair) { this.removeCrosshair(); } + this.onDrawDone(null); } private initDrawing(): void { @@ -175,8 +178,9 @@ export class DrawHandlerImpl implements DrawHandler { const bbox = (e.target as SVGRectElement).getBBox(); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const { shapeType } = this.drawData; - this.cancel(); + this.release(); + if (this.canceled) return; if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { this.onDrawDone({ shapeType, @@ -290,11 +294,11 @@ export class DrawHandlerImpl implements DrawHandler { this.drawInstance.on('drawdone', (e: CustomEvent): void => { const targetPoints = pointsToArray((e.target as SVGElement).getAttribute('points')); - const { points, box } = this.getFinalPolyshapeCoordinates(targetPoints); const { shapeType } = this.drawData; - this.cancel(); + this.release(); + if (this.canceled) return; if (shapeType === 'polygon' && ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD) && points.length >= 3 * 2) { @@ -598,6 +602,7 @@ export class DrawHandlerImpl implements DrawHandler { this.canvas = canvas; this.text = text; this.initialized = false; + this.canceled = false; this.drawData = null; this.geometry = null; this.crosshair = null; @@ -671,17 +676,18 @@ export class DrawHandlerImpl implements DrawHandler { this.geometry = geometry; if (drawData.enabled) { + this.canceled = false; this.drawData = drawData; this.initDrawing(); this.startDraw(); } else { - this.cancel(); + this.release(); this.drawData = drawData; } } public cancel(): void { + this.canceled = true; this.release(); - this.onDrawDone(null); } } From 2e6a92afa4fd52c531678bcb2f4cb9f40d1c244f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 31 Mar 2020 14:09:47 +0300 Subject: [PATCH 02/46] Insert multiple shapes --- cvat-canvas/src/typescript/drawHandler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index f6c1d947deb8..a2c401c5223f 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -163,7 +163,10 @@ export class DrawHandlerImpl implements DrawHandler { if (this.crosshair) { this.removeCrosshair(); } - this.onDrawDone(null); + + if (!this.drawData.initialState) { + this.onDrawDone(null); + } } private initDrawing(): void { From a7f02df7d07842f1cd4f00fa41ac5daa0437f28c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 31 Mar 2020 16:11:35 +0300 Subject: [PATCH 03/46] Added dialog window with some help info about filters --- cvat-core/src/session.js | 5 +- .../annotations-filters-input.tsx | 110 ++++++++++++++++-- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 987b3faed6a6..548dd19121c0 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -441,7 +441,7 @@ * Returns the ranges of cached frames * @method ranges * @memberof Session.frames - * @returns {Array{string}} + * @returns {Array.} * @instance * @async */ @@ -520,7 +520,8 @@ * @returns {HistoryActions} * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ArgumentError} - * @returns {[string, number][]} array of pairs [action name, frame number] + * @returns {Array.>} + * array of pairs [action name, frame number] * @instance * @async */ diff --git a/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx b/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx index 39ff37b6cf76..b6e98198a035 100644 --- a/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx +++ b/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx @@ -2,9 +2,14 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'react-redux'; import Select, { SelectValue, LabeledValue } from 'antd/lib/select'; +import Title from 'antd/lib/typography/Title'; +import Text from 'antd/lib/typography/Text'; +import Paragraph from 'antd/lib/typography/Paragraph'; +import Tooltip from 'antd/lib/tooltip'; +import Modal from 'antd/lib/modal'; import Icon from 'antd/lib/icon'; import { @@ -16,6 +21,8 @@ import { CombinedState } from 'reducers/interfaces'; interface StateToProps { annotationsFilters: string[]; annotationsFiltersHistory: string[]; + searchForwardShortcut: string; + searchBackwardShortcut: string; } interface DispatchToProps { @@ -30,11 +37,16 @@ function mapStateToProps(state: CombinedState): StateToProps { filtersHistory: annotationsFiltersHistory, }, }, + shortcuts: { + normalizedKeyMap, + }, } = state; return { annotationsFilters, annotationsFiltersHistory, + searchForwardShortcut: normalizedKeyMap.SEARCH_FORWARD, + searchBackwardShortcut: normalizedKeyMap.SEARCH_BACKWARD, }; } @@ -56,13 +68,74 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }; } +function filtersHelpModalContent( + searchForwardShortcut: string, + searchBackwardShortcut: string, +): JSX.Element { + return ( + <> + + General + + + You can use filters to display only subset of objects on a frame + or to search objects that satisfy the filters using hotkeys + + {` ${searchForwardShortcut} `} + + and + + {` ${searchBackwardShortcut} `} + + + + Supported properties: + width, height, label, serverID, clientID, type, shape, occluded +
+ Supported operators: + ==, !=, >, >=, <, <=, ~=, (), & and | +
+ + If you have double quotes in your query string, + please escape them using back slash: \" (see the latest example) + +
+ All properties and values are case-sensitive. + CVAT uses json queries to perform search. +
+ + Examples +
    +
  • label=="car" | label==["road sign"]
  • +
  • width >= height
  • +
  • attr["Attribute 1"] == attr["Attribute 2"]
  • +
  • clientID == 50
  • +
  • + (label=="car" & attr["parked"]==true) + | (label=="pedestrian" & width > 150) +
  • +
  • + (( label==["car \"mazda\""]) + & (attr["sunglasses"]==true + | (width > 150 | height > 150 & (clientID == serverID))))) +
  • +
+
+ + ); +} + function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Element { const { annotationsFilters, annotationsFiltersHistory, + searchForwardShortcut, + searchBackwardShortcut, changeAnnotationsFilters, } = props; + const [underCursor, setUnderCursor] = useState(false); + return ( @@ -71,7 +75,9 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { )} > {values.map((value: string): JSX.Element => ( - {value} + + {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + ))} @@ -193,7 +199,9 @@ function renderList(parameters: ListParameters): JSX.Element | null { [key: string]: (keyEvent?: KeyboardEvent) => void; } = {}; - values.slice(0, 10).forEach((value: string, index: number): void => { + const filteredValues = values + .filter((value: string): boolean => value !== consts.UNDEFINED_ATTRIBUTE_VALUE); + filteredValues.slice(0, 10).forEach((value: string, index: number): void => { const key = `SET_${index}_VALUE`; keyMap[key] = { name: `Set value "${value}"`, @@ -214,7 +222,7 @@ function renderList(parameters: ListParameters): JSX.Element | null { return (
- {values.map((value: string, index: number): JSX.Element => ( + {filteredValues.map((value: string, index: number): JSX.Element => (
{`${index}:`} {` ${value}`} diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss index 1f8de8412a37..231cc13b410b 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -48,9 +48,7 @@ margin: 10px 0px 10px 10px; } - .attribute-annotation-sidebar-attr-elem-wrapper { - display: inline-block; width: 60%; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index fcf0fe38eec7..efd81d67595d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -20,7 +20,7 @@ import Text from 'antd/lib/typography/Text'; import Tooltip from 'antd/lib/tooltip'; import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer'; - +import consts from 'consts'; import { ObjectOutsideIcon, FirstIcon, @@ -30,10 +30,10 @@ import { BackgroundIcon, ForegroundIcon, } from 'icons'; - import { ObjectType, ShapeType } from 'reducers/interfaces'; import { clamp } from 'utils/math'; + function ItemMenu( serverID: number | undefined, locked: boolean, @@ -508,7 +508,9 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element }} > { attrValues.map((value: string): JSX.Element => ( - {value} + + {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + )) } @@ -534,7 +536,9 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element className='cvat-object-item-select-attribute' > { attrValues.map((value: string): JSX.Element => ( - {value} + + {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + )) } diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts new file mode 100644 index 000000000000..666dc98dcf86 --- /dev/null +++ b/cvat-ui/src/consts.ts @@ -0,0 +1,9 @@ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__'; + +export default { + UNDEFINED_ATTRIBUTE_VALUE, +}; diff --git a/cvat-ui/src/utils/enviroment.ts b/cvat-ui/src/utils/enviroment.ts index e1f017554c55..137e9dd1a905 100644 --- a/cvat-ui/src/utils/enviroment.ts +++ b/cvat-ui/src/utils/enviroment.ts @@ -2,6 +2,6 @@ // // SPDX-License-Identifier: MIT -export default function isDev() { +export default function isDev(): boolean { return process.env.NODE_ENV === 'development'; } From 5b8ee72d4f32b318c540b6a5d7483d9af5089d31 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 1 Apr 2020 00:27:55 +0300 Subject: [PATCH 08/46] Fixed license year --- cvat-canvas/src/typescript/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index 23a36f11c7dc..84b89940f90b 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2020 Intel Corporation // // SPDX-License-Identifier: MIT From 10c300605f6ef8fc3c786a4bb4bd5423aebbc479 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 1 Apr 2020 00:30:29 +0300 Subject: [PATCH 09/46] No break space const --- .../attribute-annotation-sidebar/attribute-editor.tsx | 6 ++++-- .../standard-workspace/objects-side-bar/object-item.tsx | 6 ++++-- cvat-ui/src/consts.ts | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 50cec85a32a3..6917e42fbf2f 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -56,7 +56,8 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { > {values.map((value: string): JSX.Element => ( - {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + {value === consts.UNDEFINED_ATTRIBUTE_VALUE + ? consts.NO_BREAK_SPACE : value} ))} @@ -76,7 +77,8 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { > {values.map((value: string): JSX.Element => ( - {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + {value === consts.UNDEFINED_ATTRIBUTE_VALUE + ? consts.NO_BREAK_SPACE : value} ))} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index efd81d67595d..2a43e042ff50 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -509,7 +509,8 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element > { attrValues.map((value: string): JSX.Element => ( - {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + {value === consts.UNDEFINED_ATTRIBUTE_VALUE + ? consts.NO_BREAK_SPACE : value} )) } @@ -537,7 +538,8 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element > { attrValues.map((value: string): JSX.Element => ( - {value === consts.UNDEFINED_ATTRIBUTE_VALUE ? '\u00a0' : value} + {value === consts.UNDEFINED_ATTRIBUTE_VALUE + ? consts.NO_BREAK_SPACE : value} )) } diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts index 666dc98dcf86..aceacc0f26b4 100644 --- a/cvat-ui/src/consts.ts +++ b/cvat-ui/src/consts.ts @@ -3,7 +3,9 @@ // SPDX-License-Identifier: MIT const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__'; +const NO_BREAK_SPACE = '\u00a0'; export default { UNDEFINED_ATTRIBUTE_VALUE, + NO_BREAK_SPACE, }; From c91fa277ce2c33e6cb3b59241c1e65e5902aa299 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 1 Apr 2020 00:34:59 +0300 Subject: [PATCH 10/46] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f15c9608770..726e577567d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0-alpha] - Unreleased ### Added -- +- Special behaviour for attribute value ``__undefined__`` (invisibility, no shortcuts to be set in AAM) ### Changed - From 5e092cd932be32bf49ad408e65dc8f8f0f02f85f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 1 Apr 2020 00:36:21 +0300 Subject: [PATCH 11/46] Fixed year in license headers --- cvat-canvas/src/typescript/consts.ts | 2 +- cvat-ui/src/consts.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index 84b89940f90b..23a36f11c7dc 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2019-2020 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts index aceacc0f26b4..b5942c324240 100644 --- a/cvat-ui/src/consts.ts +++ b/cvat-ui/src/consts.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2020 Intel Corporation // // SPDX-License-Identifier: MIT From c842c25e78a55ddf75d13054e63627899514b05b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 13:32:03 +0300 Subject: [PATCH 12/46] Implementation of bitmap in client --- cvat-canvas/README.md | 2 + cvat-canvas/src/scss/canvas.scss | 10 +++ cvat-canvas/src/typescript/canvas.ts | 5 ++ cvat-canvas/src/typescript/canvasModel.ts | 16 ++++- cvat-canvas/src/typescript/canvasView.ts | 65 +++++++++++++++++-- cvat-ui/src/actions/settings-actions.ts | 10 +++ .../standard-workspace/canvas-wrapper.tsx | 8 +++ .../objects-side-bar/appearance-block.tsx | 10 +++ .../objects-side-bar/objects-side-bar.tsx | 6 ++ .../objects-side-bar/styles.scss | 9 +++ .../standard-workspace/canvas-wrapper.tsx | 3 + .../objects-side-bar/objects-side-bar.tsx | 16 +++++ cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/settings-reducer.ts | 10 +++ 14 files changed, 166 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 7fe6049176ee..dbf85b8cd129 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -100,6 +100,7 @@ Canvas itself handles: select(objectState: any): void; fitCanvas(): void; + bitmap(enabled: boolean): void; dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; @@ -189,4 +190,5 @@ Standard JS events are used. | dragCanvas() | + | - | - | - | - | - | + | - | | zoomCanvas() | + | - | - | - | - | - | - | + | | cancel() | - | + | + | + | + | + | + | + | +| bitmap() | + | + | + | + | + | + | + | + | | setZLayer() | + | + | + | + | + | + | + | + | diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index d46edb0e2286..fab01d0bb44b 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -152,6 +152,16 @@ polyline.cvat_canvas_shape_splitting { box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75); } +#cvat_canvas_bitmap { + pointer-events: none; + position: absolute; + z-index: 4; + background: black; + width: 100%; + height: 100%; + box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75); +} + #cvat_canvas_grid { position: absolute; z-index: 2; diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index eef1de40ef31..bb169e388022 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -49,6 +49,7 @@ interface Canvas { select(objectState: any): void; fitCanvas(): void; + bitmap(enable: boolean): void; dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; @@ -86,6 +87,10 @@ class CanvasImpl implements Canvas { ); } + public bitmap(enable: boolean): void { + this.model.bitmap(enable); + } + public dragCanvas(enable: boolean): void { this.model.dragCanvas(enable); } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 66a2717b0562..87e560927e4e 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -98,8 +98,9 @@ export enum UpdateReasons { GROUP = 'group', SELECT = 'select', CANCEL = 'cancel', + BITMAP = 'bitmap', DRAG_CANVAS = 'drag_canvas', - ZOOM_CANVAS = 'ZOOM_CANVAS', + ZOOM_CANVAS = 'zoom_canvas', } export enum Mode { @@ -116,6 +117,7 @@ export enum Mode { } export interface CanvasModel { + readonly imageBitmap: boolean; readonly image: Image | null; readonly objects: any[]; readonly zLayer: number | null; @@ -148,6 +150,7 @@ export interface CanvasModel { select(objectState: any): void; fitCanvas(width: number, height: number): void; + bitmap(enabled: boolean): void; dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; @@ -159,6 +162,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { activeElement: ActiveElement; angle: number; canvasSize: Size; + imageBitmap: boolean; image: Image | null; imageID: number | null; imageOffset: number; @@ -191,6 +195,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { height: 0, width: 0, }, + imageBitmap: false, image: null, imageID: null, imageOffset: 0, @@ -277,6 +282,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.OBJECTS_UPDATED); } + public bitmap(enabled: boolean): void { + this.data.imageBitmap = enabled; + this.notify(UpdateReasons.BITMAP); + } + public dragCanvas(enable: boolean): void { if (enable && this.data.mode !== Mode.IDLE) { throw Error(`Canvas is busy. Action: ${this.data.mode}`); @@ -522,6 +532,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return this.data.zLayer; } + public get imageBitmap(): boolean { + return this.data.imageBitmap; + } + public get image(): Image | null { return this.data.image; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 5d6149482281..487136a7115d 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -47,6 +47,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private text: SVGSVGElement; private adoptedText: SVG.Container; private background: HTMLCanvasElement; + private bitmap: HTMLCanvasElement; private grid: SVGSVGElement; private content: SVGSVGElement; private adoptedContent: SVG.Container; @@ -285,7 +286,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } private moveCanvas(): void { - for (const obj of [this.background, this.grid]) { + for (const obj of [this.background, this.grid, this.bitmap]) { obj.style.top = `${this.geometry.top}px`; obj.style.left = `${this.geometry.left}px`; } @@ -303,7 +304,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private transformCanvas(): void { // Transform canvas - for (const obj of [this.background, this.grid, this.content]) { + for (const obj of [this.background, this.grid, this.content, this.bitmap]) { obj.style.transform = `scale(${this.geometry.scale}) rotate(${this.geometry.angle}deg)`; } @@ -358,7 +359,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } private resizeCanvas(): void { - for (const obj of [this.background, this.grid]) { + for (const obj of [this.background, this.grid, this.bitmap]) { obj.style.width = `${this.geometry.image.width}px`; obj.style.height = `${this.geometry.image.height}px`; } @@ -546,6 +547,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.adoptedText = (SVG.adopt((this.text as any as HTMLElement)) as SVG.Container); this.background = window.document.createElement('canvas'); + this.bitmap = window.document.createElement('canvas'); // window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.grid = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); @@ -590,6 +592,8 @@ export class CanvasViewImpl implements CanvasView, Listener { this.text.setAttribute('id', 'cvat_canvas_text_content'); this.background.setAttribute('id', 'cvat_canvas_background'); this.content.setAttribute('id', 'cvat_canvas_content'); + this.bitmap.setAttribute('id', 'cvat_canvas_bitmap'); + this.bitmap.style.display = 'none'; // Setup wrappers this.canvas.setAttribute('id', 'cvat_canvas_wrapper'); @@ -605,6 +609,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.appendChild(this.loadingAnimation); this.canvas.appendChild(this.text); this.canvas.appendChild(this.background); + this.canvas.appendChild(this.bitmap); this.canvas.appendChild(this.grid); this.canvas.appendChild(this.content); @@ -702,7 +707,16 @@ export class CanvasViewImpl implements CanvasView, Listener { public notify(model: CanvasModel & Master, reason: UpdateReasons): void { this.geometry = this.controller.geometry; - if (reason === UpdateReasons.IMAGE_CHANGED) { + + if (reason === UpdateReasons.BITMAP) { + const { imageBitmap } = model; + if (imageBitmap) { + this.bitmap.style.display = ''; + this.redrawBitmap(); + } else { + this.bitmap.style.display = 'none'; + } + } else if (reason === UpdateReasons.IMAGE_CHANGED) { const { image } = model; if (!image) { this.loadingAnimation.classList.remove('cvat_canvas_hidden'); @@ -875,12 +889,55 @@ export class CanvasViewImpl implements CanvasView, Listener { this.mode = Mode.IDLE; this.canvas.style.cursor = ''; } + + if (model.imageBitmap + && [UpdateReasons.IMAGE_CHANGED, UpdateReasons.OBJECTS_UPDATED].includes(reason) + ) { + this.redrawBitmap(); + } } public html(): HTMLDivElement { return this.canvas; } + private redrawBitmap(): void { + const width = +this.background.style.width.slice(0, -2); + const height = +this.background.style.height.slice(0, -2); + this.bitmap.setAttribute('width', `${width}px`); + this.bitmap.setAttribute('height', `${height}px`); + const states = this.controller.objects; + + const ctx = this.bitmap.getContext('2d'); + if (ctx) { + ctx.fillStyle = 'black'; + ctx.fillRect(0, 0, width, height); + for (const state of states) { + ctx.fillStyle = 'white'; + if (['rectangle', 'polygon'].includes(state.shapeType)) { + const points = state.shapeType === 'rectangle' ? [ + state.points[0], // xtl + state.points[1], // ytl + state.points[2], // xbr + state.points[1], // ytl + state.points[2], // xbr + state.points[3], // ybr + state.points[0], // xtl + state.points[3], // ybr + ] : state.points; + ctx.beginPath(); + ctx.moveTo(points[0], points[1]); + for (let i = 0; i < points.length; i += 2) { + ctx.lineTo(points[i], points[i + 1]); + } + ctx.closePath(); + } + + ctx.fill(); + } + } + } + private saveState(state: any): void { this.drawnStates[state.clientID] = { clientID: state.clientID, diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index c14c2db21381..3cb18f56f614 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -18,6 +18,7 @@ export enum SettingsActionTypes { CHANGE_SELECTED_SHAPES_OPACITY = 'CHANGE_SELECTED_SHAPES_OPACITY', CHANGE_SHAPES_COLOR_BY = 'CHANGE_SHAPES_COLOR_BY', CHANGE_SHAPES_BLACK_BORDERS = 'CHANGE_SHAPES_BLACK_BORDERS', + CHANGE_SHOW_UNLABELED_REGIONS = 'CHANGE_SHOW_UNLABELED_REGIONS', CHANGE_FRAME_STEP = 'CHANGE_FRAME_STEP', CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED', SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM', @@ -66,6 +67,15 @@ export function changeShapesBlackBorders(blackBorders: boolean): AnyAction { }; } +export function changeShowBitmap(showBitmap: boolean): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SHOW_UNLABELED_REGIONS, + payload: { + showBitmap, + }, + }; +} + export function switchRotateAll(rotateAll: boolean): AnyAction { return { type: SettingsActionTypes.SWITCH_ROTATE_ALL, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 038157e2dd24..dbd24fee090e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -42,6 +42,7 @@ interface Props { colorBy: ColorBy; selectedOpacity: number; blackBorders: boolean; + showBitmap: boolean; grid: boolean; gridSize: number; gridColor: GridColor; @@ -112,6 +113,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { colorBy, selectedOpacity, blackBorders, + showBitmap, frameData, frameAngle, annotations, @@ -198,6 +200,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateShapesView(); } + if (prevProps.showBitmap !== showBitmap) { + canvasInstance.bitmap(showBitmap); + } + if (prevProps.frameAngle !== frameAngle) { canvasInstance.rotate(frameAngle); } @@ -557,6 +563,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { for (const state of annotations) { let shapeColor = ''; + if (colorBy === ColorBy.INSTANCE) { shapeColor = state.color; } else if (colorBy === ColorBy.GROUP) { @@ -572,6 +579,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { if (handler && handler.nested) { handler.nested.fill({ color: shapeColor }); } + (shapeView as any).instance.fill({ color: shapeColor, opacity: opacity / 100 }); (shapeView as any).instance.stroke({ color: blackBorders ? 'black' : shapeColor }); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx index 56c5b5ac5fae..d3ed0f03d4d3 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx @@ -24,12 +24,14 @@ interface Props { opacity: number; selectedOpacity: number; blackBorders: boolean; + showBitmap: boolean; collapseAppearance(): void; changeShapesColorBy(event: RadioChangeEvent): void; changeShapesOpacity(event: SliderValue): void; changeSelectedShapesOpacity(event: SliderValue): void; changeShapesBlackBorders(event: CheckboxChangeEvent): void; + changeShowBitmap(event: CheckboxChangeEvent): void; } function AppearanceBlock(props: Props): JSX.Element { @@ -39,11 +41,13 @@ function AppearanceBlock(props: Props): JSX.Element { opacity, selectedOpacity, blackBorders, + showBitmap, collapseAppearance, changeShapesColorBy, changeShapesOpacity, changeSelectedShapesOpacity, changeShapesBlackBorders, + changeShowBitmap, } = props; return ( @@ -85,6 +89,12 @@ function AppearanceBlock(props: Props): JSX.Element { > Black borders + + Show bitmap +
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index 1a8cd5bd0550..b173d870e9e1 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -29,6 +29,7 @@ interface Props { opacity: number; selectedOpacity: number; blackBorders: boolean; + showBitmap: boolean; collapseSidebar(): void; collapseAppearance(): void; @@ -37,6 +38,7 @@ interface Props { changeShapesOpacity(event: SliderValue): void; changeSelectedShapesOpacity(event: SliderValue): void; changeShapesBlackBorders(event: CheckboxChangeEvent): void; + changeShowBitmap(event: CheckboxChangeEvent): void; } function ObjectsSideBar(props: Props): JSX.Element { @@ -47,12 +49,14 @@ function ObjectsSideBar(props: Props): JSX.Element { opacity, selectedOpacity, blackBorders, + showBitmap, collapseSidebar, collapseAppearance, changeShapesColorBy, changeShapesOpacity, changeSelectedShapesOpacity, changeShapesBlackBorders, + changeShowBitmap, } = props; const appearanceProps = { @@ -62,11 +66,13 @@ function ObjectsSideBar(props: Props): JSX.Element { opacity, selectedOpacity, blackBorders, + showBitmap, changeShapesColorBy, changeShapesOpacity, changeSelectedShapesOpacity, changeShapesBlackBorders, + changeShowBitmap, }; return ( 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 07f3c0f6eaf2..51ca31fd4c84 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 @@ -23,6 +23,11 @@ background: $background-color-2; border-bottom: none; height: 230px; + + > .ant-collapse-content-box { + padding: 10px; + } + } } } @@ -254,6 +259,10 @@ width: 33%; } } + + .ant-checkbox-wrapper { + margin-left: 0px; + } } .cvat-object-item-menu { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index e5b74d9bbedd..4d68d9c4f51c 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -62,6 +62,7 @@ interface StateToProps { colorBy: ColorBy; selectedOpacity: number; blackBorders: boolean; + showBitmap: boolean; grid: boolean; gridSize: number; gridColor: GridColor; @@ -169,6 +170,7 @@ function mapStateToProps(state: CombinedState): StateToProps { colorBy, selectedOpacity, blackBorders, + showBitmap, }, }, shortcuts: { @@ -192,6 +194,7 @@ function mapStateToProps(state: CombinedState): StateToProps { colorBy, selectedOpacity, blackBorders, + showBitmap, grid, gridSize, gridColor, diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index bb6162e9cb7a..309700cc715f 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -27,6 +27,7 @@ import { changeShapesOpacity as changeShapesOpacityAction, changeSelectedShapesOpacity as changeSelectedShapesOpacityAction, changeShapesBlackBorders as changeShapesBlackBordersAction, + changeShowBitmap as changeShowUnlabeledRegionsAction, } from 'actions/settings-actions'; @@ -37,6 +38,7 @@ interface StateToProps { opacity: number; selectedOpacity: number; blackBorders: boolean; + showBitmap: boolean; } interface DispatchToProps { @@ -47,6 +49,7 @@ interface DispatchToProps { changeShapesOpacity(shapesOpacity: number): void; changeSelectedShapesOpacity(selectedShapesOpacity: number): void; changeShapesBlackBorders(blackBorders: boolean): void; + changeShowBitmap(showBitmap: boolean): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -61,6 +64,7 @@ function mapStateToProps(state: CombinedState): StateToProps { opacity, selectedOpacity, blackBorders, + showBitmap, }, }, } = state; @@ -72,6 +76,7 @@ function mapStateToProps(state: CombinedState): StateToProps { opacity, selectedOpacity, blackBorders, + showBitmap, }; } @@ -132,6 +137,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { changeShapesBlackBorders(blackBorders: boolean): void { dispatch(changeShapesBlackBordersAction(blackBorders)); }, + changeShowBitmap(showBitmap: boolean) { + dispatch(changeShowUnlabeledRegionsAction(showBitmap)); + }, }; } @@ -177,6 +185,11 @@ class ObjectsSideBarContainer extends React.PureComponent { changeShapesBlackBorders(event.target.checked); }; + private changeShowBitmap = (event: CheckboxChangeEvent): void => { + const { changeShowBitmap } = this.props; + changeShowBitmap(event.target.checked); + }; + public render(): JSX.Element { const { sidebarCollapsed, @@ -185,6 +198,7 @@ class ObjectsSideBarContainer extends React.PureComponent { opacity, selectedOpacity, blackBorders, + showBitmap, collapseSidebar, collapseAppearance, } = this.props; @@ -197,12 +211,14 @@ class ObjectsSideBarContainer extends React.PureComponent { opacity={opacity} selectedOpacity={selectedOpacity} blackBorders={blackBorders} + showBitmap={showBitmap} collapseSidebar={collapseSidebar} collapseAppearance={collapseAppearance} changeShapesColorBy={this.changeShapesColorBy} changeShapesOpacity={this.changeShapesOpacity} changeSelectedShapesOpacity={this.changeSelectedShapesOpacity} changeShapesBlackBorders={this.changeShapesBlackBorders} + changeShowBitmap={this.changeShowBitmap} /> ); } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index c99be219f70b..5df7c6dbc507 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -434,6 +434,7 @@ export interface ShapesSettingsState { opacity: number; selectedOpacity: number; blackBorders: boolean; + showBitmap: boolean; } export interface SettingsState { diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 0a53aca4ff0b..1f1491f498b8 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -22,6 +22,7 @@ const defaultState: SettingsState = { opacity: 3, selectedOpacity: 30, blackBorders: false, + showBitmap: false, }, workspace: { autoSave: false, @@ -127,6 +128,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.CHANGE_SHOW_UNLABELED_REGIONS: { + return { + ...state, + shapes: { + ...state.shapes, + showBitmap: action.payload.showBitmap, + }, + }; + } case SettingsActionTypes.CHANGE_FRAME_STEP: { return { ...state, From ec38159e15f755a9d7b3c53611ef6a35372ad2df Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 13:33:21 +0300 Subject: [PATCH 13/46] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f15c9608770..f6e206847762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0-alpha] - Unreleased ### Added -- +- Ability to display a bitmap in the new UI ### Changed - From 7303ae209483ec73c631e613f969aea46677e2fc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 13:34:57 +0300 Subject: [PATCH 14/46] Z-layer support --- cvat-canvas/src/typescript/canvasView.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 487136a7115d..280aac8333bc 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -891,7 +891,10 @@ export class CanvasViewImpl implements CanvasView, Listener { } if (model.imageBitmap - && [UpdateReasons.IMAGE_CHANGED, UpdateReasons.OBJECTS_UPDATED].includes(reason) + && [UpdateReasons.IMAGE_CHANGED, + UpdateReasons.OBJECTS_UPDATED, + UpdateReasons.SET_Z_LAYER, + ].includes(reason) ) { this.redrawBitmap(); } From 4da951a812be829f7e14fdc60107a14f4dd41698 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 13:42:22 +0300 Subject: [PATCH 15/46] Fixed settings after reopen a job --- cvat-ui/src/reducers/settings-reducer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 1f1491f498b8..1b38bb896677 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -227,18 +227,18 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case BoundariesActionTypes.RESET_AFTER_ERROR: case AnnotationActionTypes.GET_JOB_SUCCESS: { const { job } = action.payload; return { - ...state, + ...defaultState, player: { - ...state.player, + ...defaultState.player, resetZoom: job && job.task.mode === 'annotation', }, }; } - case BoundariesActionTypes.RESET_AFTER_ERROR: case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState }; } From 80a07e593e10dd91087df02e6aceb39adf2f6208 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 13:45:03 +0300 Subject: [PATCH 16/46] Do not show invisible objects on bitmap --- cvat-canvas/src/typescript/canvasView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 280aac8333bc..57370e5037f6 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -916,6 +916,7 @@ export class CanvasViewImpl implements CanvasView, Listener { ctx.fillStyle = 'black'; ctx.fillRect(0, 0, width, height); for (const state of states) { + if (state.hidden || state.outside) continue; ctx.fillStyle = 'white'; if (['rectangle', 'polygon'].includes(state.shapeType)) { const points = state.shapeType === 'rectangle' ? [ From 66c6e7e9195507627e697eaa86ef5020784cc754 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Thu, 2 Apr 2020 14:08:22 +0300 Subject: [PATCH 17/46] Fix point interpolation (#1344) * Extend formats tests with different track types * Add unordered list comparison * Skip empty list comparison * fix * fix * Reproduce problem * Fix point interpolation for single point * undo rest api refactor --- cvat/apps/engine/data_manager.py | 5 ++- cvat/apps/engine/tests/test_data_manager.py | 39 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 cvat/apps/engine/tests/test_data_manager.py diff --git a/cvat/apps/engine/data_manager.py b/cvat/apps/engine/data_manager.py index d7adc8b8b4a5..b39c6783f616 100644 --- a/cvat/apps/engine/data_manager.py +++ b/cvat/apps/engine/data_manager.py @@ -293,7 +293,10 @@ def _modify_unmached_object(obj, end_frame): @staticmethod def normalize_shape(shape): - points = np.asarray(shape["points"]).reshape(-1, 2) + points = list(shape["points"]) + if len(points) == 2: + points.extend(points) # duplicate points for single point case + points = np.asarray(points).reshape(-1, 2) broken_line = geometry.LineString(points) points = [] for off in range(0, 100, 1): diff --git a/cvat/apps/engine/tests/test_data_manager.py b/cvat/apps/engine/tests/test_data_manager.py new file mode 100644 index 000000000000..968b57525f6b --- /dev/null +++ b/cvat/apps/engine/tests/test_data_manager.py @@ -0,0 +1,39 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from cvat.apps.engine.data_manager import TrackManager + +from unittest import TestCase + + +class TrackManagerTest(TestCase): + def test_single_point_interpolation(self): + track = { + "frame": 0, + "label_id": 0, + "group": None, + "attributes": [], + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.0], + "type": "points", + "occluded": False, + "outside": False, + "attributes": [] + }, + { + "frame": 2, + "attributes": [], + "points": [3.0, 4.0, 5.0, 6.0], + "type": "points", + "occluded": False, + "outside": True + }, + ] + } + + interpolated = TrackManager.get_interpolated_shapes(track, 0, 2) + + self.assertEqual(len(interpolated), 3) \ No newline at end of file From 3d44c0616f276243dae056ff19bd2c37821b2fe4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 15:36:07 +0300 Subject: [PATCH 18/46] Added button to reset color settings --- .../components/settings-page/player-settings.tsx | 15 +++++++++++++++ cvat-ui/src/components/settings-page/styles.scss | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/settings-page/player-settings.tsx index 779a03b01fe2..6b834122ccb1 100644 --- a/cvat-ui/src/components/settings-page/player-settings.tsx +++ b/cvat-ui/src/components/settings-page/player-settings.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { Row, Col } from 'antd/lib/grid'; import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import Button from 'antd/lib/button'; import Slider from 'antd/lib/slider'; import Select from 'antd/lib/select'; import InputNumber from 'antd/lib/input-number'; @@ -16,6 +17,7 @@ import { clamp } from 'utils/math'; import { BackJumpIcon, ForwardJumpIcon } from 'icons'; import { FrameSpeed, GridColor } from 'reducers/interfaces'; + interface Props { frameStep: number; frameSpeed: FrameSpeed; @@ -263,6 +265,19 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { /> + + + + +
); } diff --git a/cvat-ui/src/components/settings-page/styles.scss b/cvat-ui/src/components/settings-page/styles.scss index ef4b005a5d03..926d60293104 100644 --- a/cvat-ui/src/components/settings-page/styles.scss +++ b/cvat-ui/src/components/settings-page/styles.scss @@ -73,12 +73,19 @@ width: 90px; } +.cvat-player-reset-color-settings, .cvat-player-settings-brightness, .cvat-player-settings-contrast, .cvat-player-settings-saturation { width: 40%; } +.cvat-player-reset-color-settings { + > .ant-col { + text-align: center; + } +} + .cvat-settings-page-back-button { width: 100px; margin-top: 15px; From 02d6a0c0fba0c754431c691d78076f78ca98097a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 15:38:29 +0300 Subject: [PATCH 19/46] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af02b0bee0dd..09c965d0dcf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Special behaviour for attribute value ``__undefined__`` (invisibility, no shortcuts to be set in AAM) - Dialog window with some helpful information about using filters +- Button to reset colors settings (brightness, saturation, contrast) in the new UI ### Changed - From 7d986d599eb3e85fd06e55717678b4f19ae22df5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 22:14:27 +0300 Subject: [PATCH 20/46] Added option to display shape text always --- cvat-canvas/README.md | 9 ++++- cvat-canvas/src/typescript/canvas.ts | 7 ++++ .../src/typescript/canvasController.ts | 1 - cvat-canvas/src/typescript/canvasModel.ts | 33 +++++++++++++++++++ cvat-canvas/src/typescript/canvasView.ts | 25 ++++++++++++-- cvat-ui/src/actions/settings-actions.ts | 10 ++++++ .../standard-workspace/canvas-wrapper.tsx | 16 +++++++++ .../src/components/settings-page/styles.scss | 3 ++ .../settings-page/workspace-settings.tsx | 20 +++++++++++ .../standard-workspace/canvas-wrapper.tsx | 3 ++ .../settings-page/workspace-settings.tsx | 12 +++++-- cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/settings-reducer.ts | 10 ++++++ 13 files changed, 142 insertions(+), 8 deletions(-) diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 7fe6049176ee..ea6bfa0eabc6 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -50,6 +50,11 @@ Canvas itself handles: ZOOM_CANVAS = 'zoom_canvas', } + interface Configuration { + displayAllText?: boolean; + undefinedAttrValue?: string; + } + interface DrawData { enabled: boolean; shapeType?: string; @@ -83,7 +88,6 @@ Canvas itself handles: } interface Canvas { - mode(): Mode; html(): HTMLDivElement; setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; @@ -103,7 +107,9 @@ Canvas itself handles: dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; + mode(): void; cancel(): void; + configure(configuration: Configuration): void; } ``` @@ -189,4 +195,5 @@ Standard JS events are used. | dragCanvas() | + | - | - | - | - | - | + | - | | zoomCanvas() | + | - | - | - | - | - | - | + | | cancel() | - | + | + | + | + | + | + | + | +| configure() | + | - | - | - | - | - | - | - | | setZLayer() | + | + | + | + | + | + | + | + | diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index eef1de40ef31..c601034f5ca2 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -11,6 +11,7 @@ import { CanvasModel, CanvasModelImpl, RectDrawingMethod, + Configuration, } from './canvasModel'; import { @@ -54,6 +55,7 @@ interface Canvas { mode(): void; cancel(): void; + configure(configuration: Configuration): void; } class CanvasImpl implements Canvas { @@ -141,11 +143,16 @@ class CanvasImpl implements Canvas { public cancel(): void { this.model.cancel(); } + + public configure(configuration: Configuration): void { + this.model.configure(configuration); + } } export { CanvasImpl as Canvas, CanvasVersion, + Configuration, RectDrawingMethod, Mode as CanvasMode, }; diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index 7fc555c64690..179f9b32e869 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -36,7 +36,6 @@ export interface CanvasController { enableDrag(x: number, y: number): void; drag(x: number, y: number): void; disableDrag(): void; - fit(): void; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 66a2717b0562..888b5499c78d 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -46,6 +46,11 @@ export enum RectDrawingMethod { EXTREME_POINTS = 'By 4 points' } +export interface Configuration { + displayAllText?: boolean; + undefinedAttrValue?: string; +} + export interface DrawData { enabled: boolean; shapeType?: string; @@ -100,6 +105,7 @@ export enum UpdateReasons { CANCEL = 'cancel', DRAG_CANVAS = 'drag_canvas', ZOOM_CANVAS = 'ZOOM_CANVAS', + CONFIG_UPDATED = 'config_updated', } export enum Mode { @@ -126,6 +132,7 @@ export interface CanvasModel { readonly mergeData: MergeData; readonly splitData: SplitData; readonly groupData: GroupData; + readonly configuration: Configuration; readonly selected: any; geometry: Geometry; mode: Mode; @@ -151,6 +158,7 @@ export interface CanvasModel { dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; + configure(configuration: Configuration): void; cancel(): void; } @@ -159,6 +167,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { activeElement: ActiveElement; angle: number; canvasSize: Size; + configuration: Configuration; image: Image | null; imageID: number | null; imageOffset: number; @@ -191,6 +200,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { height: 0, width: 0, }, + configuration: { + displayAllText: false, + undefinedAttrValue: '', + }, image: null, imageID: null, imageOffset: 0, @@ -485,10 +498,30 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.selected = null; } + public configure(configuration: Configuration): void { + if (this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (typeof (configuration.displayAllText) !== 'undefined') { + this.data.configuration.displayAllText = configuration.displayAllText; + } + + if (typeof (configuration.undefinedAttrValue) !== 'undefined') { + this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue; + } + + this.notify(UpdateReasons.CONFIG_UPDATED); + } + public cancel(): void { this.notify(UpdateReasons.CANCEL); } + public get configuration(): Configuration { + return { ...this.data.configuration }; + } + public get geometry(): Geometry { return { angle: this.data.angle, diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 75963144a384..bd460be68df6 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -36,6 +36,7 @@ import { GroupData, Mode, Size, + Configuration, } from './canvasModel'; export interface CanvasView { @@ -65,6 +66,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private groupHandler: GroupHandler; private zoomHandler: ZoomHandler; private activeElement: ActiveElement; + private configuration: Configuration; private set mode(value: Mode) { this.controller.mode = value; @@ -538,6 +540,7 @@ export class CanvasViewImpl implements CanvasView, Listener { clientID: null, attributeID: null, }; + this.configuration = model.configuration; this.mode = Mode.IDLE; // Create HTML elements @@ -702,7 +705,11 @@ export class CanvasViewImpl implements CanvasView, Listener { public notify(model: CanvasModel & Master, reason: UpdateReasons): void { this.geometry = this.controller.geometry; - if (reason === UpdateReasons.IMAGE_CHANGED) { + if (reason === UpdateReasons.CONFIG_UPDATED) { + this.configuration = model.configuration; + this.setupObjects([]); + this.setupObjects(model.objects); + } else if (reason === UpdateReasons.IMAGE_CHANGED) { const { image } = model; if (!image) { this.loadingAnimation.classList.remove('cvat_canvas_hidden'); @@ -987,6 +994,8 @@ export class CanvasViewImpl implements CanvasView, Listener { } private addObjects(states: any[], translate: (points: number[]) => number[]): void { + const { displayAllText } = this.configuration; + for (const state of states) { if (state.objectType === 'tag') { this.addTag(state); @@ -1030,6 +1039,14 @@ export class CanvasViewImpl implements CanvasView, Listener { }, })); }); + + if (displayAllText) { + this.svgTexts[state.clientID] = this.addText(state); + this.updateTextPosition( + this.svgTexts[state.clientID], + this.svgShapes[state.clientID], + ); + } } this.saveState(state); @@ -1078,6 +1095,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private deactivateShape(): void { if (this.activeElement.clientID !== null) { + const { displayAllText } = this.configuration; const { clientID } = this.activeElement; const drawnState = this.drawnStates[clientID]; const shape = this.svgShapes[clientID]; @@ -1101,7 +1119,7 @@ export class CanvasViewImpl implements CanvasView, Listener { // TODO: Hide text only if it is hidden by settings const text = this.svgTexts[clientID]; - if (text) { + if (text && !displayAllText) { text.remove(); delete this.svgTexts[clientID]; } @@ -1347,6 +1365,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } private addText(state: any): SVG.Text { + const { undefinedAttrValue } = this.configuration; const { label, clientID, attributes } = state; const attrNames = label.attributes.reduce((acc: any, val: any): void => { acc[val.id] = val.name; @@ -1356,7 +1375,7 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.adoptedText.text((block): void => { block.tspan(`${label.name} ${clientID}`).style('text-transform', 'uppercase'); for (const attrID of Object.keys(attributes)) { - const value = attributes[attrID] === consts.UNDEFINED_ATTRIBUTE_VALUE + const value = attributes[attrID] === undefinedAttrValue ? '' : attributes[attrID]; block.tspan(`${attrNames[attrID]}: ${value}`).attr({ attrID, diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index c14c2db21381..03b56cc2f828 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -28,6 +28,7 @@ export enum SettingsActionTypes { CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL', CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN', SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', + SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS = 'SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS', } export function changeShapesOpacity(opacity: number): AnyAction { @@ -200,3 +201,12 @@ export function switchShowingInterpolatedTracks(showAllInterpolationTracks: bool }, }; } + +export function switchShowingObjectsTextAlways(showObjectsTextAlways: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS, + payload: { + showObjectsTextAlways, + }, + }; +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 038157e2dd24..4f38f3db48e8 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -21,6 +21,7 @@ import { import { LogType } from 'cvat-logger'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; +import consts from 'consts'; const cvat = getCore(); @@ -58,6 +59,7 @@ interface Props { contextVisible: boolean; contextType: ContextMenuType; aamZoomMargin: number; + showObjectsTextAlways: boolean; workspace: Workspace; keyMap: Record; onSetupCanvas: () => void; @@ -91,6 +93,7 @@ interface Props { export default class CanvasWrapperComponent extends React.PureComponent { public componentDidMount(): void { const { + showObjectsTextAlways, canvasInstance, curZLayer, } = this.props; @@ -101,7 +104,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { .getElementsByClassName('cvat-canvas-container'); wrapper.appendChild(canvasInstance.html()); + canvasInstance.configure({ + undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, + displayAllText: showObjectsTextAlways, + }); canvasInstance.setZLayer(curZLayer); + this.initialSetup(); this.updateCanvas(); } @@ -128,8 +136,16 @@ export default class CanvasWrapperComponent extends React.PureComponent { saturationLevel, workspace, frameFetching, + showObjectsTextAlways, } = this.props; + if (prevProps.showObjectsTextAlways !== showObjectsTextAlways) { + canvasInstance.configure({ + undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, + displayAllText: showObjectsTextAlways, + }); + } + if (prevProps.sidebarCollapsed !== sidebarCollapsed) { const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar'); if (sidebar) { diff --git a/cvat-ui/src/components/settings-page/styles.scss b/cvat-ui/src/components/settings-page/styles.scss index ef4b005a5d03..d54c7206722d 100644 --- a/cvat-ui/src/components/settings-page/styles.scss +++ b/cvat-ui/src/components/settings-page/styles.scss @@ -20,6 +20,8 @@ .cvat-player-settings-grid, .cvat-workspace-settings-auto-save, +.cvat-workspace-settings-show-text-always, +.cvat-workspace-settings-show-text-always-checkbox, .cvat-workspace-settings-show-interpolated-checkbox { margin-bottom: 10px; } @@ -31,6 +33,7 @@ .cvat-player-settings-speed, .cvat-player-settings-reset-zoom, .cvat-player-settings-rotate-all, +.cvat-workspace-settings-show-text-always, .cvat-workspace-settings-show-interpolated, .cvat-workspace-settings-aam-zoom-margin, .cvat-workspace-settings-auto-save-interval { diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx index 52ba4a5062c7..0ee068aedbe6 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -16,10 +16,12 @@ interface Props { autoSaveInterval: number; aamZoomMargin: number; showAllInterpolationTracks: boolean; + showObjectsTextAlways: boolean; onSwitchAutoSave(enabled: boolean): void; onChangeAutoSaveInterval(interval: number): void; onChangeAAMZoomMargin(margin: number): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void; + onSwitchShowingObjectsTextAlways(enabled: boolean): void; } export default function WorkspaceSettingsComponent(props: Props): JSX.Element { @@ -28,10 +30,12 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { autoSaveInterval, aamZoomMargin, showAllInterpolationTracks, + showObjectsTextAlways, onSwitchAutoSave, onChangeAutoSaveInterval, onChangeAAMZoomMargin, onSwitchShowingInterpolatedTracks, + onSwitchShowingObjectsTextAlways, } = props; const minAutoSaveInterval = 5; @@ -93,6 +97,22 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { Show hidden interpolated objects in the side panel + + + { + onSwitchShowingObjectsTextAlways(event.target.checked); + }} + > + Always show object details + + + + Show text for an object on the canvas not only when the object is activated + + Attribute annotation mode (AAM) zoom margin diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index e5b74d9bbedd..fc4d62e59ba5 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -73,6 +73,7 @@ interface StateToProps { saturationLevel: number; resetZoom: boolean; aamZoomMargin: number; + showObjectsTextAlways: boolean; workspace: Workspace; minZLayer: number; maxZLayer: number; @@ -163,6 +164,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, workspace: { aamZoomMargin, + showObjectsTextAlways, }, shapes: { opacity, @@ -203,6 +205,7 @@ function mapStateToProps(state: CombinedState): StateToProps { saturationLevel, resetZoom, aamZoomMargin, + showObjectsTextAlways, curZLayer, minZLayer, maxZLayer, diff --git a/cvat-ui/src/containers/settings-page/workspace-settings.tsx b/cvat-ui/src/containers/settings-page/workspace-settings.tsx index 4ab527b3b1a9..db74456435a3 100644 --- a/cvat-ui/src/containers/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/containers/settings-page/workspace-settings.tsx @@ -10,11 +10,10 @@ import { changeAutoSaveInterval, changeAAMZoomMargin, switchShowingInterpolatedTracks, + switchShowingObjectsTextAlways, } from 'actions/settings-actions'; -import { - CombinedState, -} from 'reducers/interfaces'; +import { CombinedState } from 'reducers/interfaces'; import WorkspaceSettingsComponent from 'components/settings-page/workspace-settings'; @@ -23,6 +22,7 @@ interface StateToProps { autoSaveInterval: number; aamZoomMargin: number; showAllInterpolationTracks: boolean; + showObjectsTextAlways: boolean; } interface DispatchToProps { @@ -30,6 +30,7 @@ interface DispatchToProps { onChangeAutoSaveInterval(interval: number): void; onChangeAAMZoomMargin(margin: number): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void; + onSwitchShowingObjectsTextAlways(enabled: boolean): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -39,6 +40,7 @@ function mapStateToProps(state: CombinedState): StateToProps { autoSaveInterval, aamZoomMargin, showAllInterpolationTracks, + showObjectsTextAlways, } = workspace; return { @@ -46,6 +48,7 @@ function mapStateToProps(state: CombinedState): StateToProps { autoSaveInterval, aamZoomMargin, showAllInterpolationTracks, + showObjectsTextAlways, }; } @@ -63,6 +66,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSwitchShowingInterpolatedTracks(enabled: boolean): void { dispatch(switchShowingInterpolatedTracks(enabled)); }, + onSwitchShowingObjectsTextAlways(enabled: boolean): void { + dispatch(switchShowingObjectsTextAlways(enabled)); + }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index c99be219f70b..2ab26f7c8b17 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -426,6 +426,7 @@ export interface WorkspaceSettingsState { autoSave: boolean; autoSaveInterval: number; // in ms aamZoomMargin: number; + showObjectsTextAlways: boolean; showAllInterpolationTracks: boolean; } diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 0a53aca4ff0b..632ed5b91c10 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -27,6 +27,7 @@ const defaultState: SettingsState = { autoSave: false, autoSaveInterval: 15 * 60 * 1000, aamZoomMargin: 100, + showObjectsTextAlways: false, showAllInterpolationTracks: false, }, player: { @@ -217,6 +218,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS: { + return { + ...state, + workspace: { + ...state.workspace, + showObjectsTextAlways: action.payload.showObjectsTextAlways, + }, + }; + } case AnnotationActionTypes.GET_JOB_SUCCESS: { const { job } = action.payload; From 5f53302512bc01cd78adf42d6f8feb67ce05ae9d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 2 Apr 2020 22:20:33 +0300 Subject: [PATCH 21/46] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af02b0bee0dd..80ff9f40990a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Special behaviour for attribute value ``__undefined__`` (invisibility, no shortcuts to be set in AAM) - Dialog window with some helpful information about using filters +- Added option to display shape text always ### Changed - From 96c9427bed1456bea8d9d42a22ba4437da139b31 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 3 Apr 2020 10:25:04 +0300 Subject: [PATCH 22/46] Hidden/outside fix --- cvat-canvas/src/typescript/canvasView.ts | 45 +++++++++++++++--------- cvat-ui/src/reducers/settings-reducer.ts | 2 +- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index da226cf4d5dd..27160436bf5d 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -967,31 +967,44 @@ export class CanvasViewImpl implements CanvasView, Listener { for (const state of states) { const { clientID } = state; const drawnState = this.drawnStates[clientID]; + const shape = this.svgShapes[state.clientID]; + const text = this.svgTexts[state.clientID]; if (drawnState.hidden !== state.hidden || drawnState.outside !== state.outside) { - const none = state.hidden || state.outside; - if (state.shapeType === 'points') { - this.svgShapes[clientID].remember('_selectHandler').nested - .style('display', none ? 'none' : ''); + const isInvisible = state.hidden || state.outside; + if (isInvisible) { + (state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape) + .style('display', 'none'); + if (text) { + text.addClass('cvat_canvas_hidden'); + } } else { - this.svgShapes[clientID].style('display', none ? 'none' : ''); + (state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape) + .style('display', ''); + if (text) { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition( + text, + shape, + ); + } } } if (drawnState.zOrder !== state.zOrder) { if (state.shapeType === 'points') { - this.svgShapes[clientID].remember('_selectHandler').nested + shape.remember('_selectHandler').nested .attr('data-z-order', state.zOrder); } else { - this.svgShapes[clientID].attr('data-z-order', state.zOrder); + shape.attr('data-z-order', state.zOrder); } } if (drawnState.occluded !== state.occluded) { if (state.occluded) { - this.svgShapes[clientID].addClass('cvat_canvas_shape_occluded'); + shape.addClass('cvat_canvas_shape_occluded'); } else { - this.svgShapes[clientID].removeClass('cvat_canvas_shape_occluded'); + shape.removeClass('cvat_canvas_shape_occluded'); } } @@ -1009,7 +1022,7 @@ export class CanvasViewImpl implements CanvasView, Listener { if (state.shapeType === 'rectangle') { const [xtl, ytl, xbr, ybr] = translatedPoints; - this.svgShapes[clientID].attr({ + shape.attr({ x: xtl, y: ytl, width: xbr - xtl, @@ -1025,21 +1038,20 @@ export class CanvasViewImpl implements CanvasView, Listener { return `${acc}${val},`; }, '', ); - (this.svgShapes[clientID] as any).clear(); - this.svgShapes[clientID].attr('points', stringified); + (shape as any).clear(); + shape.attr('points', stringified); if (state.shapeType === 'points') { - this.selectize(false, this.svgShapes[clientID]); - this.setupPoints(this.svgShapes[clientID] as SVG.PolyLine, state); + this.selectize(false, shape); + this.setupPoints(shape as SVG.PolyLine, state); } } } for (const attrID of Object.keys(state.attributes)) { if (state.attributes[attrID] !== drawnState.attributes[attrID]) { - const text = this.svgTexts[state.clientID]; if (text) { - const [span] = this.svgTexts[state.clientID].node + const [span] = text.node .querySelectorAll(`[attrID="${attrID}"]`) as any as SVGTSpanElement[]; if (span && span.textContent) { const prefix = span.textContent.split(':').slice(0, -1).join(':'); @@ -1387,6 +1399,7 @@ export class CanvasViewImpl implements CanvasView, Listener { // Update text position after corresponding box has been moved, resized, etc. private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void { + if (text.node.style.display === 'none') return; // wrong transformation matrix let box = (shape.node as any).getBBox(); // Translate the whole box to the client coordinate system diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 00cc552767cf..87396429efe3 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -28,7 +28,7 @@ const defaultState: SettingsState = { autoSave: false, autoSaveInterval: 15 * 60 * 1000, aamZoomMargin: 100, - showObjectsTextAlways: false, + showObjectsTextAlways: true, showAllInterpolationTracks: false, }, player: { From abbc635101f790e1c2a18021bc9d866ca9ca1286 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 3 Apr 2020 10:51:49 +0300 Subject: [PATCH 23/46] Fixed screen scaling --- cvat-ui/src/components/settings-page/styles.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cvat-ui/src/components/settings-page/styles.scss b/cvat-ui/src/components/settings-page/styles.scss index 926d60293104..82046ae1c8be 100644 --- a/cvat-ui/src/components/settings-page/styles.scss +++ b/cvat-ui/src/components/settings-page/styles.scss @@ -5,6 +5,9 @@ @import '../../base.scss'; .cvat-settings-page { + height: 90%; + overflow-y: auto; + > div:nth-child(1) { margin-top: 30px; margin-bottom: 10px; From be2ec3ad7276774f34f50dee9716f41a025a4c44 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov <41117609+azhavoro@users.noreply.github.com> Date: Fri, 3 Apr 2020 12:32:20 +0300 Subject: [PATCH 24/46] fixed dump error after moving format files (#1342) * fixed dump error after moving format files * updated changelog --- CHANGELOG.md | 1 + cvat/apps/annotation/serializers.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c862c851416..a331eaf9be92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - New shape is added when press ``esc`` when drawing instead of cancellation +- Fixed `FileNotFoundError` during dump after moving format files ### Security - diff --git a/cvat/apps/annotation/serializers.py b/cvat/apps/annotation/serializers.py index 8fa8b3455303..7284c0414a00 100644 --- a/cvat/apps/annotation/serializers.py +++ b/cvat/apps/annotation/serializers.py @@ -1,8 +1,10 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2020 Intel Corporation # # SPDX-License-Identifier: MIT +from django.utils import timezone from rest_framework import serializers + from cvat.apps.annotation import models class AnnotationDumperSerializer(serializers.ModelSerializer): @@ -57,6 +59,9 @@ def create(self, validated_data): def update(self, instance, validated_data): dumper_names = [handler["display_name"] for handler in validated_data["annotationdumper_set"]] loader_names = [handler["display_name"] for handler in validated_data["annotationloader_set"]] + instance.handler_file = validated_data.get('handler_file', instance.handler_file) + instance.owner = validated_data.get('owner', instance.owner) + instance.updated_date = timezone.localtime(timezone.now()) handlers_to_delete = [d for d in instance.annotationdumper_set.all() if d.display_name not in dumper_names] + \ [l for l in instance.annotationloader_set.all() if l.display_name not in loader_names] From 7bdf9bb481d3ce7efe91407b1662a23866d39815 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov <41117609+azhavoro@users.noreply.github.com> Date: Fri, 3 Apr 2020 18:01:13 +0300 Subject: [PATCH 25/46] Az/fix dextr (#1348) * Fixed dextr_segmentation app * updated changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> --- CHANGELOG.md | 1 + cvat/apps/dextr_segmentation/dextr.py | 7 +++++-- cvat/apps/dextr_segmentation/views.py | 10 ++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a331eaf9be92..3f04b1d2fa39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - New shape is added when press ``esc`` when drawing instead of cancellation +- Fixed dextr segmentation. - Fixed `FileNotFoundError` during dump after moving format files ### Security diff --git a/cvat/apps/dextr_segmentation/dextr.py b/cvat/apps/dextr_segmentation/dextr.py index 628961ff5762..db8c71e727d4 100644 --- a/cvat/apps/dextr_segmentation/dextr.py +++ b/cvat/apps/dextr_segmentation/dextr.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: MIT from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network +from cvat.apps.engine.frame_provider import FrameProvider import os import cv2 @@ -29,7 +30,7 @@ def __init__(self): raise Exception("DEXTR_MODEL_DIR is not defined") - def handle(self, im_path, points): + def handle(self, db_data, frame, points): # Lazy initialization if not self._plugin: self._plugin = make_plugin_or_core() @@ -42,7 +43,9 @@ def handle(self, im_path, points): else: self._exec_network = self._plugin.load(network=self._network) - image = PIL.Image.open(im_path) + frame_provider = FrameProvider(db_data) + image = frame_provider.get_frame(frame, frame_provider.Quality.ORIGINAL) + image = PIL.Image.open(image[0]) numpy_image = np.array(image) points = np.asarray([[int(p["x"]), int(p["y"])] for p in points], dtype=int) bounding_box = ( diff --git a/cvat/apps/dextr_segmentation/views.py b/cvat/apps/dextr_segmentation/views.py index 0a837b3abb49..a4827e99afcc 100644 --- a/cvat/apps/dextr_segmentation/views.py +++ b/cvat/apps/dextr_segmentation/views.py @@ -12,15 +12,14 @@ import django_rq import json -import os import rq __RQ_QUEUE_NAME = "default" __DEXTR_HANDLER = DEXTR_HANDLER() -def _dextr_thread(im_path, points): +def _dextr_thread(db_data, frame, points): job = rq.get_current_job() - job.meta["result"] = __DEXTR_HANDLER.handle(im_path, points) + job.meta["result"] = __DEXTR_HANDLER.handle(db_data, frame, points) job.save_meta() @@ -38,8 +37,7 @@ def create(request, jid): slogger.job[jid].info("create dextr request for the JOB: {} ".format(jid) + "by the USER: {} on the FRAME: {}".format(username, frame)) - db_task = Job.objects.select_related("segment__task").get(id=jid).segment.task - im_path = os.path.realpath(db_task.get_frame_path(frame)) + db_data = Job.objects.select_related("segment__task__data").get(id=jid).segment.task.data queue = django_rq.get_queue(__RQ_QUEUE_NAME) rq_id = "dextr.create/{}/{}".format(jid, username) @@ -53,7 +51,7 @@ def create(request, jid): job.delete() queue.enqueue_call(func=_dextr_thread, - args=(im_path, points), + args=(db_data, frame, points), job_id=rq_id, timeout=15, ttl=30) From e0db8f43210c54e30146df3207d9d43e2434a77e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 3 Apr 2020 19:07:17 +0300 Subject: [PATCH 26/46] Disabled option by default --- 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 87396429efe3..00cc552767cf 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -28,7 +28,7 @@ const defaultState: SettingsState = { autoSave: false, autoSaveInterval: 15 * 60 * 1000, aamZoomMargin: 100, - showObjectsTextAlways: true, + showObjectsTextAlways: false, showAllInterpolationTracks: false, }, player: { From 364db2cb168ae4d9cef08dc61c18d4b1f9722aac Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 3 Apr 2020 19:39:40 +0300 Subject: [PATCH 27/46] Fixed typos in interface --- cvat-canvas/README.md | 2 +- cvat-canvas/src/typescript/canvas.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index c3e7b640841c..59806746eb59 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -108,7 +108,7 @@ Canvas itself handles: dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; - mode(): void; + mode(): Mode; cancel(): void; configure(configuration: Configuration): void; } diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 1df3efe256c6..04a99d5b1925 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -54,7 +54,7 @@ interface Canvas { dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; - mode(): void; + mode(): Mode; cancel(): void; configure(configuration: Configuration): void; } From 87784fa97ed104f9d77f95e4f0668bf0c8890d64 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 4 Apr 2020 06:25:00 +0300 Subject: [PATCH 28/46] Increase preview size till 256, 256. Previous preview size was not optimal and led to a blurred image due to too small size. --- cvat/apps/engine/media_extractors.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 778aec9a589f..c32d7abda456 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -52,11 +52,12 @@ def get_progress(self, pos): @staticmethod def _get_preview(obj): + PREVIEW_SIZE = (256, 256) if isinstance(obj, io.IOBase): preview = Image.open(obj) else: preview = obj - preview.thumbnail((128, 128)) + preview.thumbnail(PREVIEW_SIZE) return preview.convert('RGB') From de52a734905ff941041a2c1c71c29162239a4009 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 4 Apr 2020 06:31:19 +0300 Subject: [PATCH 29/46] Add a line into CHANGELOG.md about the change. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e32ad1bb70bf..d8a3778c9f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added option to display shape text always ### Changed -- +- Increase preview size of a task till 256, 256 on the server ### Deprecated - From 457454821f7c1214c776e42c95aa6f27a80a0856 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Mon, 6 Apr 2020 11:54:44 +0300 Subject: [PATCH 30/46] Refactor frame provider (#1355) * Refactor frame provider * fix --- cvat/apps/engine/frame_provider.py | 145 ++++++++++++----------------- 1 file changed, 58 insertions(+), 87 deletions(-) diff --git a/cvat/apps/engine/frame_provider.py b/cvat/apps/engine/frame_provider.py index 9228ccc610bd..7bd60a8a9fbb 100644 --- a/cvat/apps/engine/frame_provider.py +++ b/cvat/apps/engine/frame_provider.py @@ -1,21 +1,21 @@ -# Copyright (C) 2019 Intel Corporation +# Copyright (C) 2020 Intel Corporation # # SPDX-License-Identifier: MIT +import itertools import math -from io import BytesIO from enum import Enum -import itertools +from io import BytesIO import numpy as np from PIL import Image from cvat.apps.engine.media_extractors import VideoReader, ZipReader -from cvat.apps.engine.models import DataChoice from cvat.apps.engine.mime_types import mimetypes +from cvat.apps.engine.models import DataChoice -class FrameProvider(): +class FrameProvider: class Quality(Enum): COMPRESSED = 0 ORIGINAL = 100 @@ -25,26 +25,33 @@ class Type(Enum): PIL = 1 NUMPY_ARRAY = 2 - def __init__(self, db_data): - self._db_data = db_data - if db_data.compressed_chunk_type == DataChoice.IMAGESET: - self._compressed_chunk_reader_class = ZipReader - elif db_data.compressed_chunk_type == DataChoice.VIDEO: - self._compressed_chunk_reader_class = VideoReader - else: - raise Exception('Unsupported chunk type') + class ChunkLoader: + def __init__(self, reader_class, path_getter): + self.chunk_id = None + self.chunk_reader = None + self.reader_class = reader_class + self.get_chunk_path = path_getter - if db_data.original_chunk_type == DataChoice.IMAGESET: - self._original_chunk_reader_class = ZipReader - elif db_data.original_chunk_type == DataChoice.VIDEO: - self._original_chunk_reader_class = VideoReader - else: - raise Exception('Unsupported chunk type') + def load(self, chunk_id): + if self.chunk_id != chunk_id: + self.chunk_id = chunk_id + self.chunk_reader = self.reader_class([self.get_chunk_path(chunk_id)]) + return self.chunk_reader - self._extracted_compressed_chunk = None - self._compressed_chunk_reader = None - self._extracted_original_chunk = None - self._original_chunk_reader = None + def __init__(self, db_data): + self._db_data = db_data + self._loaders = {} + + reader_class = { + DataChoice.IMAGESET: ZipReader, + DataChoice.VIDEO: VideoReader, + } + self._loaders[self.Quality.COMPRESSED] = self.ChunkLoader( + reader_class[db_data.compressed_chunk_type], + db_data.get_compressed_chunk_path) + self._loaders[self.Quality.ORIGINAL] = self.ChunkLoader( + reader_class[db_data.original_chunk_type], + db_data.get_original_chunk_path) def __len__(self): return self._db_data.size @@ -74,77 +81,41 @@ def _av_frame_to_png_bytes(av_frame): buf.seek(0) return buf - def _get_frame(self, frame_number, chunk_path_getter, extracted_chunk, chunk_reader, reader_class): - _, chunk_number, frame_offset = self._validate_frame_number(frame_number) - chunk_path = chunk_path_getter(chunk_number) - if chunk_number != extracted_chunk: - extracted_chunk = chunk_number - chunk_reader = reader_class([chunk_path]) - - frame, frame_name, _ = next(itertools.islice(chunk_reader, frame_offset, None)) - if reader_class is VideoReader: - return (self._av_frame_to_png_bytes(frame), 'image/png') - - return (frame, mimetypes.guess_type(frame_name)) - - def _get_frames(self, chunk_path_getter, reader_class, out_type): - for chunk_idx in range(math.ceil(self._db_data.size / self._db_data.chunk_size)): - chunk_path = chunk_path_getter(chunk_idx) - chunk_reader = reader_class([chunk_path]) - for frame, _, _ in chunk_reader: - if out_type == self.Type.BUFFER: - yield self._av_frame_to_png_bytes(frame) if reader_class is VideoReader else frame - elif out_type == self.Type.PIL: - yield frame.to_image() if reader_class is VideoReader else Image.open(frame) - elif out_type == self.Type.NUMPY_ARRAY: - if reader_class is VideoReader: - image = np.array(frame.to_image()) - else: - image = np.array(Image.open(frame)) - if len(image.shape) == 3 and image.shape[2] in {3, 4}: - image[:, :, :3] = image[:, :, 2::-1] # RGB to BGR - yield image - else: - raise Exception('unsupported output type') + def _convert_frame(self, frame, reader_class, out_type): + if out_type == self.Type.BUFFER: + return self._av_frame_to_png_bytes(frame) if reader_class is VideoReader else frame + elif out_type == self.Type.PIL: + return frame.to_image() if reader_class is VideoReader else Image.open(frame) + elif out_type == self.Type.NUMPY_ARRAY: + if reader_class is VideoReader: + image = np.array(frame.to_image()) + else: + image = np.array(Image.open(frame)) + if len(image.shape) == 3 and image.shape[2] in {3, 4}: + image[:, :, :3] = image[:, :, 2::-1] # RGB to BGR + return image + else: + raise Exception('unsupported output type') def get_preview(self): return self._db_data.get_preview_path() def get_chunk(self, chunk_number, quality=Quality.ORIGINAL): chunk_number = self._validate_chunk_number(chunk_number) - if quality == self.Quality.ORIGINAL: - return self._db_data.get_original_chunk_path(chunk_number) - elif quality == self.Quality.COMPRESSED: - return self._db_data.get_compressed_chunk_path(chunk_number) + return self._loaders[quality].get_chunk_path(chunk_number) def get_frame(self, frame_number, quality=Quality.ORIGINAL): - if quality == self.Quality.ORIGINAL: - return self._get_frame( - frame_number=frame_number, - chunk_path_getter=self._db_data.get_original_chunk_path, - extracted_chunk=self._extracted_original_chunk, - chunk_reader=self._original_chunk_reader, - reader_class=self._original_chunk_reader_class, - ) - elif quality == self.Quality.COMPRESSED: - return self._get_frame( - frame_number=frame_number, - chunk_path_getter=self._db_data.get_compressed_chunk_path, - extracted_chunk=self._extracted_compressed_chunk, - chunk_reader=self._compressed_chunk_reader, - reader_class=self._compressed_chunk_reader_class, - ) + _, chunk_number, frame_offset = self._validate_frame_number(frame_number) + + chunk_reader = self._loaders[quality].load(chunk_number) + + frame, frame_name, _ = next(itertools.islice(chunk_reader, frame_offset, None)) + if self._loaders[quality].reader_class is VideoReader: + return (self._av_frame_to_png_bytes(frame), 'image/png') + return (frame, mimetypes.guess_type(frame_name)) def get_frames(self, quality=Quality.ORIGINAL, out_type=Type.BUFFER): - if quality == self.Quality.ORIGINAL: - return self._get_frames( - chunk_path_getter=self._db_data.get_original_chunk_path, - reader_class=self._original_chunk_reader_class, - out_type=out_type, - ) - elif quality == self.Quality.COMPRESSED: - return self._get_frames( - chunk_path_getter=self._db_data.get_compressed_chunk_path, - reader_class=self._compressed_chunk_reader_class, - out_type=out_type, - ) + loader = self._loaders[quality] + for chunk_idx in range(math.ceil(self._db_data.size / self._db_data.chunk_size)): + for frame, _, _ in loader.load(chunk_idx): + yield self._convert_frame(frame, loader.reader_class, out_type) From 411a3df57ae2ce7021bdb5c07eaee142b2ff7dff Mon Sep 17 00:00:00 2001 From: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> Date: Mon, 6 Apr 2020 11:55:16 +0300 Subject: [PATCH 31/46] Add CODEOWNERS file (#1360) * Add CODEOWNERS file * Removed the outdated file. See https://github.com/opencv/cvat/graphs/contributors * Change codeowners --- .github/CODEOWNERS | 40 ++++++++++++++++++++++++++++++++++++++++ CONTRIBUTORS.md | 38 -------------------------------------- 2 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 CONTRIBUTORS.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000000..2ae576780dd1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,40 @@ +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, they will +# be requested for review when someone opens a pull request. +* @nmanovic + +# Order is important; the last matching pattern takes the most +# precedence. When someone opens a pull request that only +# modifies components below, only the list of owners and not +# the global owner(s) will be requested for a review. + +# Component: Server +/cvat/ @nmanovic @azhavoro + +# Component: CVAT UI +/cvat-ui/ @bsekachev @ActiveChooN +/cvat-data/ @azhavoro @bsekachev +/cvat-canvas/ @bsekachev @ActiveChooN +/cvat-core/ @bsekachev @ActiveChooN + +# Component: Datumaro +/datumaro/ @zhiltsov-max @nmanovic +/cvat/apps/dataset_manager/ @zhiltsov-max @nmanovic + +# Advanced components (e.g. OpenVINO) +/components/ @azhavoro + +# Infrastructure +Dockerfile* @azhavoro +docker-compose* @azhavoro +.* @azhavoro +*.conf @azhavoro +*.sh @azhavoro +/cvat_proxy/ @azhavoro +/tests/ @azhavoro +/utils/ @azhavoro +/*.md @nmanovic +/LICENSE @nmanovic +/.github/ @nmanovic \ No newline at end of file diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index f21998f19904..000000000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,38 +0,0 @@ -# Core support team -- **[Nikita Manovich](https://github.com/nmanovic)** - - * Project lead - * Developer - * Author and maintainer - -- **[Boris Sekachev](https://github.com/bsekachev)** - - * Primary developer - * Author and maintainer - -- **[Andrey Zhavoronkov](https://github.com/azhavoro)** - - * Developer - * Author and maintainer - -# Contributors - -- **[Victor Salimonov](https://github.com/VikTorSalimonov)** - - * Documentation, screencasts - -- **[Dmitry Sidnev](https://github.com/DmitriySidnev)** - - * [convert_to_coco.py](utils/coco) - an utility for converting annotation from CVAT to COCO data annotation format - -- **[Sebastián Yonekura](https://github.com/syonekura)** - - * [convert_to_voc.py](utils/voc) - an utility for converting CVAT XML to PASCAL VOC data annotation format. - -- **[ITLab Team](https://github.com/itlab-vision/cvat):** - **[Vasily Danilin](https://github.com/DanVev)**, - **[Eugene Shashkin](https://github.com/EvgenyShashkin)**, - **[Dmitry Silenko](https://github.com/DimaSilenko)**, - **[Alina Bykovskaya](https://github.com/alinaut)**, - **[Yanina Koltushkina](https://github.com/YaniKolt)** - * Integrating CI tools as Travis CI, Codacy and Coveralls.io From 76fc8e442d74ae8a733fa722c894d33bc037ddb6 Mon Sep 17 00:00:00 2001 From: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> Date: Mon, 6 Apr 2020 15:58:55 +0300 Subject: [PATCH 32/46] Add pull request and issue templates (#1359) * Add initial version of pull request template * Fix links * Fix codacy issues * Slightly improve titles of sections * Add a note about strikethough for the checklist. * Fix progress of a pull request (each checkbox is an issue) * Add the license header, checkboxes about the license. * Updated the license * Update the license to met https://github.com/licensee/licensee/blob/master/vendor/choosealicense.com/_licenses/mit.txt restrictions. * Fix the pull request template name * Make explaination text as comments (it will be visible when you edit the PR message) * Add initial version of the issue template. --- .github/ISSUE_TEMPLATE.md | 53 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 46 +++++++++++++++++++++++++++ LICENSE | 5 +-- 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000000..d9506af88420 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,53 @@ + + +### My actions before raising this issue +- [ ] Read/searched [the docs](https://github.com/opencv/cvat/tree/master#documentation) +- [ ] Searched [past issues](/issues) + + + +### Expected Behaviour + + +### Current Behaviour + + +### Possible Solution + + +### Steps to Reproduce (for bugs) + +1. +1. +1. +1. + +### Context + + +### Your Environment + +- Git hash commit (`git log -1`): +- Docker version `docker version` (e.g. Docker 17.0.05): +- Are you using Docker Swarm or Kubernetes? +- Operating System and version (e.g. Linux, Windows, MacOS): +- Code example or link to GitHub repo or gist to reproduce problem: +- Other diagnostic information / logs: +
+ Logs from `cvat` container +
+ +### Next steps +You may [join our Gitter](https://gitter.im/opencv-cvat/public) channel for community support. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000000..23b9bde82ca7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,46 @@ + + + + +### Motivation and context + + +### How has this been tested? + + +### Checklist + + +- [ ] I have raised an issue to propose this change ([required](https://github.com/opencv/cvat/issues)) +- [ ] My issue has received approval from the maintainers +- [ ] I've read the [CONTRIBUTION](https://github.com/opencv/cvat/blob/develop/CONTRIBUTING.md) guide +- [ ] I have added description of my changes into [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file +- [ ] I have updated the [documentation]( + https://github.com/opencv/cvat/blob/develop/README.md#documentation) accordingly +- [ ] I have added tests to cover my changes +- [ ] I have linked related issues ([read github docs]( + https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) + +### License + +- [ ] I submit _my code changes_ under the same [MIT License]( + https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. + Feel free to contact the maintainers if that's a concern. +- [ ] I have updated the license header for each file (see an example below) + +```python +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT +``` diff --git a/LICENSE b/LICENSE index aae0a08ec7db..46056e4fd379 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (C) 2018 Intel Corporation +MIT License + +Copyright (C) 2018-2020 Intel Corporation   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -18,4 +20,3 @@ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.   -SPDX-License-Identifier: MIT From 1d78c540297fad3cf20abf371269bb34121ed529 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Tue, 7 Apr 2020 15:21:33 +0300 Subject: [PATCH 33/46] Batch of fixes (#1370) * Some margins were change to paddings * Removed extra selected * Fix: added outside shapes when merge polyshapes * Fixed double scroll bars * Updated canvas table * Fixed setup methodf * Disabled change frame during drag, resize and editing * Fixed: hidden points are visible * Fixed: Merge is allowed for points, but clicks on points conflict with frame dragging logic * Fixed: do not filter removed objects * Updated CHANGELOG.md * Couple of headers updated --- CHANGELOG.md | 11 ++- cvat-canvas/README.md | 40 +++++----- cvat-canvas/src/typescript/canvasModel.ts | 4 + cvat-canvas/src/typescript/canvasView.ts | 6 +- cvat-core/src/annotations-collection.js | 4 +- .../annotation-page/annotation-page.tsx | 12 ++- .../components/create-task-page/styles.scss | 2 +- cvat-ui/src/components/tasks-page/styles.scss | 6 +- .../objects-side-bar/object-item.tsx | 60 ++++++--------- .../objects-side-bar/objects-list.tsx | 11 ++- .../annotation-page/top-bar/top-bar.tsx | 73 +++++++++++-------- cvat-ui/src/cvat-canvas.ts | 6 ++ cvat-ui/src/styles.scss | 1 - .../engine/static/engine/js/shapeMerger.js | 4 +- .../engine/templates/engine/annotation.html | 4 +- 15 files changed, 139 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a3778c9f99..4678c31ded5d 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 ### Changed - Increase preview size of a task till 256, 256 on the server +- Minor style updates ### Deprecated - @@ -23,8 +24,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - New shape is added when press ``esc`` when drawing instead of cancellation -- Fixed dextr segmentation. -- Fixed `FileNotFoundError` during dump after moving format files +- Dextr segmentation doesn't work. +- `FileNotFoundError` during dump after moving format files +- CVAT doesn't append outside shapes when merge polyshapes in old UI +- Layout sometimes shows double scroll bars on create task, dashboard and settings pages +- UI fails after trying to change frame during resizing, dragging, editing +- Hidden points (or outsided) are visible after changing a frame +- Merge is allowed for points, but clicks on points conflict with frame dragging logic +- Removed objects are visible for search ### Security - diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 59806746eb59..d9cabfa94a8c 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -179,23 +179,23 @@ Standard JS events are used. ## API Reaction -| | IDLE | GROUPING | SPLITTING | DRAWING | MERGING | EDITING | DRAG | ZOOM | -|--------------|------|----------|-----------|---------|---------|---------|------|------| -| html() | + | + | + | + | + | + | + | + | -| setup() | + | + | + | + | + | - | + | + | -| activate() | + | - | - | - | - | - | - | - | -| rotate() | + | + | + | + | + | + | + | + | -| focus() | + | + | + | + | + | + | + | + | -| fit() | + | + | + | + | + | + | + | + | -| grid() | + | + | + | + | + | + | + | + | -| draw() | + | - | - | - | - | - | - | - | -| split() | + | - | + | - | - | - | - | - | -| group() | + | + | - | - | - | - | - | - | -| merge() | + | - | - | - | + | - | - | - | -| fitCanvas() | + | + | + | + | + | + | + | + | -| dragCanvas() | + | - | - | - | - | - | + | - | -| zoomCanvas() | + | - | - | - | - | - | - | + | -| cancel() | - | + | + | + | + | + | + | + | -| configure() | + | - | - | - | - | - | - | - | -| bitmap() | + | + | + | + | + | + | + | + | -| setZLayer() | + | + | + | + | + | + | + | + | +| | IDLE | GROUP | SPLIT | DRAW | MERGE | EDIT | DRAG | RESIZE | ZOOM_CANVAS | DRAG_CANVAS | +|--------------|------|-------|-------|------|-------|------|------|--------|-------------|-------------| +| html() | + | + | + | + | + | + | + | + | + | + | +| setup() | + | + | + | + | + | - | - | - | + | + | +| activate() | + | - | - | - | - | - | - | - | - | - | +| rotate() | + | + | + | + | + | + | + | + | + | + | +| focus() | + | + | + | + | + | + | + | + | + | + | +| fit() | + | + | + | + | + | + | + | + | + | + | +| grid() | + | + | + | + | + | + | + | + | + | + | +| draw() | + | - | - | - | - | - | - | - | - | - | +| split() | + | - | + | - | - | - | - | - | - | - | +| group() | + | + | - | - | - | - | - | - | - | - | +| merge() | + | - | - | - | + | - | - | - | - | - | +| fitCanvas() | + | + | + | + | + | + | + | + | + | + | +| dragCanvas() | + | - | - | - | - | - | + | - | - | + | +| zoomCanvas() | + | - | - | - | - | - | - | + | + | - | +| cancel() | - | + | + | + | + | + | + | + | + | + | +| configure() | + | - | - | - | - | - | - | - | - | - | +| bitmap() | + | + | + | + | + | + | + | + | + | + | +| setZLayer() | + | + | + | + | + | + | + | + | + | + | diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 13f99be1b08a..da7d112ad061 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -327,6 +327,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } public setup(frameData: any, objectStates: any[]): void { + if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + if (frameData.number === this.data.imageID) { this.data.objects = objectStates; this.notify(UpdateReasons.OBJECTS_UPDATED); diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 27160436bf5d..1de8fa524a37 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1041,7 +1041,7 @@ export class CanvasViewImpl implements CanvasView, Listener { (shape as any).clear(); shape.attr('points', stringified); - if (state.shapeType === 'points') { + if (state.shapeType === 'points' && !state.hidden) { this.selectize(false, shape); this.setupPoints(shape as SVG.PolyLine, state); } @@ -1187,7 +1187,7 @@ export class CanvasViewImpl implements CanvasView, Listener { (shape as any).off('resizestart'); (shape as any).off('resizing'); (shape as any).off('resizedone'); - (shape as any).resize(false); + (shape as any).resize('stop'); // TODO: Hide text only if it is hidden by settings const text = this.svgTexts[clientID]; @@ -1543,6 +1543,8 @@ export class CanvasViewImpl implements CanvasView, Listener { group.on('click.canvas', (event: MouseEvent): void => { // Need to redispatch the event on another element basicPolyline.fire(new MouseEvent('click', event)); + // redispatch event to canvas to be able merge points clicking them + this.content.dispatchEvent(new MouseEvent('click', event)); }); group.bbox = basicPolyline.bbox.bind(basicPolyline); diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index e3ba2735bc96..e06e60f49bad 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -871,8 +871,10 @@ // In particular consider first and last frame as keyframes for all frames const statesData = [].concat( (frame in this.shapes ? this.shapes[frame] : []) + .filter((shape) => !shape.removed) .map((shape) => shape.get(frame)), (frame in this.tags ? this.tags[frame] : []) + .filter((tag) => !tag.removed) .map((tag) => tag.get(frame)), ); const tracks = Object.values(this.tracks) @@ -880,7 +882,7 @@ frame in track.shapes || frame === frameFrom || frame === frameTo - )); + )).filter((track) => !track.removed); statesData.push( ...tracks.map((track) => track.get(frame)) .filter((state) => !state.outside), diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index db5b6500e9b6..529ea77e218a 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -36,7 +36,17 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { useEffect(() => { saveLogs(); - return saveLogs; + const root = window.document.getElementById('root'); + if (root) { + root.style.minHeight = '768px'; + } + + return () => { + saveLogs(); + if (root) { + root.style.minHeight = ''; + } + }; }, []); if (job === null) { diff --git a/cvat-ui/src/components/create-task-page/styles.scss b/cvat-ui/src/components/create-task-page/styles.scss index 3419d8608342..df2bcc459cca 100644 --- a/cvat-ui/src/components/create-task-page/styles.scss +++ b/cvat-ui/src/components/create-task-page/styles.scss @@ -6,7 +6,7 @@ .cvat-create-task-form-wrapper { text-align: center; - margin-top: 40px; + padding-top: 40px; overflow-y: auto; height: 90%; diff --git a/cvat-ui/src/components/tasks-page/styles.scss b/cvat-ui/src/components/tasks-page/styles.scss index b2fbc9be79c2..66cd8d4c3349 100644 --- a/cvat-ui/src/components/tasks-page/styles.scss +++ b/cvat-ui/src/components/tasks-page/styles.scss @@ -9,7 +9,7 @@ height: 100%; > div:nth-child(1) { - margin-bottom: 10px; + padding-bottom: 10px; div > { span { @@ -36,11 +36,11 @@ > div:nth-child(3) { height: 83%; - margin-top: 10px; + padding-top: 10px; } > div:nth-child(4) { - margin-top: 10px; + padding-top: 10px; } } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index b80fcbbca61c..d7855e5d0446 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -7,6 +7,7 @@ import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; import { LogType } from 'cvat-logger'; +import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; import { ActiveControl, CombinedState, ColorBy } from 'reducers/interfaces'; import { collapseObjectItems, @@ -24,7 +25,6 @@ import { import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; - interface OwnProps { clientID: number; } @@ -44,6 +44,7 @@ interface StateToProps { minZLayer: number; maxZLayer: number; normalizedKeyMap: Record; + canvasInstance: Canvas; } interface DispatchToProps { @@ -84,6 +85,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { canvas: { ready, activeControl, + instance: canvasInstance, }, colors, }, @@ -119,6 +121,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { minZLayer, maxZLayer, normalizedKeyMap, + canvasInstance, }; } @@ -166,72 +169,44 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { type Props = StateToProps & DispatchToProps; class ObjectItemContainer extends React.PureComponent { private navigateFirstKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; - + const { objectState, frameNumber } = this.props; const { first } = objectState.keyframes; if (first !== frameNumber) { - changeFrame(first); + this.changeFrame(first); } }; private navigatePrevKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; - + const { objectState, frameNumber } = this.props; const { prev } = objectState.keyframes; if (prev !== null && prev !== frameNumber) { - changeFrame(prev); + this.changeFrame(prev); } }; private navigateNextKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; - + const { objectState, frameNumber } = this.props; const { next } = objectState.keyframes; if (next !== null && next !== frameNumber) { - changeFrame(next); + this.changeFrame(next); } }; private navigateLastKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; - + const { objectState, frameNumber } = this.props; const { last } = objectState.keyframes; if (last !== frameNumber) { - changeFrame(last); + this.changeFrame(last); } }; private copy = (): void => { - const { - objectState, - copyShape, - } = this.props; - + const { objectState, copyShape } = this.props; copyShape(objectState); }; private propagate = (): void => { - const { - objectState, - propagateObject, - } = this.props; - + const { objectState, propagateObject } = this.props; propagateObject(objectState); }; @@ -422,6 +397,13 @@ class ObjectItemContainer extends React.PureComponent { this.commit(); }; + private changeFrame(frame: number): void { + const { changeFrame, canvasInstance } = this.props; + if (isAbleToChangeFrame(canvasInstance)) { + changeFrame(frame); + } + } + private commit(): void { const { objectState, diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 49e61a27041e..df533b87e7e3 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -15,6 +15,7 @@ import { copyShape as copyShapeAction, propagateObject as propagateObjectAction, } from 'actions/annotation-actions'; +import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; import { CombinedState, StatesOrdering, ObjectType } from 'reducers/interfaces'; interface StateToProps { @@ -32,6 +33,7 @@ interface StateToProps { annotationsFiltersHistory: string[]; keyMap: Record; normalizedKeyMap: Record; + canvasInstance: Canvas; } interface DispatchToProps { @@ -65,6 +67,9 @@ function mapStateToProps(state: CombinedState): StateToProps { number: frameNumber, }, }, + canvas: { + instance: canvasInstance, + }, tabContentHeight: listHeight, }, shortcuts: { @@ -104,6 +109,7 @@ function mapStateToProps(state: CombinedState): StateToProps { annotationsFiltersHistory, keyMap, normalizedKeyMap, + canvasInstance, }; } @@ -254,6 +260,7 @@ class ObjectsListContainer extends React.PureComponent { minZLayer, keyMap, normalizedKeyMap, + canvasInstance, } = this.props; const { sortedStatesID, @@ -388,7 +395,7 @@ class ObjectsListContainer extends React.PureComponent { if (state && state.objectType === ObjectType.TRACK) { const frame = typeof (state.keyframes.next) === 'number' ? state.keyframes.next : null; - if (frame !== null) { + if (frame !== null && isAbleToChangeFrame(canvasInstance)) { changeFrame(frame); } } @@ -399,7 +406,7 @@ class ObjectsListContainer extends React.PureComponent { if (state && state.objectType === ObjectType.TRACK) { const frame = typeof (state.keyframes.prev) === 'number' ? state.keyframes.prev : null; - if (frame !== null) { + if (frame !== null && isAbleToChangeFrame(canvasInstance)) { changeFrame(frame); } } diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 21226ce97089..fcfe943b9a8a 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -23,6 +23,7 @@ import { changeWorkspace as changeWorkspaceAction, activateObject, } from 'actions/annotation-actions'; +import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; import { CombinedState, FrameSpeed, Workspace } from 'reducers/interfaces'; @@ -45,6 +46,7 @@ interface StateToProps { workspace: Workspace; keyMap: Record; normalizedKeyMap: Record; + canvasInstance: Canvas; } interface DispatchToProps { @@ -81,6 +83,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, canvas: { ready: canvasIsReady, + instance: canvasInstance, }, workspace, }, @@ -118,6 +121,7 @@ function mapStateToProps(state: CombinedState): StateToProps { workspace, keyMap, normalizedKeyMap, + canvasInstance, }; } @@ -197,6 +201,7 @@ class AnnotationTopBarContainer extends React.PureComponent { frameDelay, playing, canvasIsReady, + canvasInstance, onSwitchPlay, onChangeFrame, } = this.props; @@ -217,10 +222,14 @@ class AnnotationTopBarContainer extends React.PureComponent { setTimeout(() => { const { playing: stillPlaying } = this.props; if (stillPlaying) { - onChangeFrame( - frameNumber + 1 + framesSkiped, - stillPlaying, framesSkiped + 1, - ); + if (isAbleToChangeFrame(canvasInstance)) { + onChangeFrame( + frameNumber + 1 + framesSkiped, + stillPlaying, framesSkiped + 1, + ); + } else { + onSwitchPlay(false); + } } }, frameDelay); } else { @@ -240,9 +249,12 @@ class AnnotationTopBarContainer extends React.PureComponent { undo, jobInstance, frameNumber, + canvasInstance, } = this.props; - undo(jobInstance, frameNumber); + if (isAbleToChangeFrame(canvasInstance)) { + undo(jobInstance, frameNumber); + } }; private redo = (): void => { @@ -250,9 +262,12 @@ class AnnotationTopBarContainer extends React.PureComponent { redo, jobInstance, frameNumber, + canvasInstance, } = this.props; - redo(jobInstance, frameNumber); + if (isAbleToChangeFrame(canvasInstance)) { + redo(jobInstance, frameNumber); + } }; private showStatistics = (): void => { @@ -285,7 +300,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = jobInstance.startFrame; @@ -293,7 +307,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -304,7 +318,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -313,7 +326,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -323,7 +336,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -332,7 +344,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -342,7 +354,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -351,7 +362,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -362,7 +373,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -371,7 +381,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -381,7 +391,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = jobInstance.stopFrame; @@ -389,7 +398,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -403,22 +412,16 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private onChangePlayerSliderValue = (value: SliderValue): void => { - const { - playing, - onSwitchPlay, - onChangeFrame, - } = this.props; - + const { playing, onSwitchPlay } = this.props; if (playing) { onSwitchPlay(false); } - onChangeFrame(value as number); + this.changeFrame(value as number); }; private onChangePlayerInputValue = (value: number): void => { const { onSwitchPlay, - onChangeFrame, playing, frameNumber, } = this.props; @@ -427,7 +430,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(value); + this.changeFrame(value); } }; @@ -441,6 +444,13 @@ class AnnotationTopBarContainer extends React.PureComponent { copy(url); }; + private changeFrame(frame: number): void { + const { onChangeFrame, canvasInstance } = this.props; + if (isAbleToChangeFrame(canvasInstance)) { + onChangeFrame(frame); + } + } + private beforeUnloadCallback(event: BeforeUnloadEvent): any { const { jobInstance } = this.props; if (jobInstance.annotations.hasUnsavedChanges()) { @@ -472,6 +482,7 @@ class AnnotationTopBarContainer extends React.PureComponent { changeWorkspace, keyMap, normalizedKeyMap, + canvasInstance, } = this.props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -537,13 +548,17 @@ class AnnotationTopBarContainer extends React.PureComponent { }, SEARCH_FORWARD: (event: KeyboardEvent | undefined) => { preventDefault(event); - if (frameNumber + 1 <= stopFrame && canvasIsReady) { + if (frameNumber + 1 <= stopFrame && canvasIsReady + && isAbleToChangeFrame(canvasInstance) + ) { searchAnnotations(jobInstance, frameNumber + 1, stopFrame); } }, SEARCH_BACKWARD: (event: KeyboardEvent | undefined) => { preventDefault(event); - if (frameNumber - 1 >= startFrame && canvasIsReady) { + if (frameNumber - 1 >= startFrame && canvasIsReady + && isAbleToChangeFrame(canvasInstance) + ) { searchAnnotations(jobInstance, frameNumber - 1, startFrame); } }, diff --git a/cvat-ui/src/cvat-canvas.ts b/cvat-ui/src/cvat-canvas.ts index 6317c43567e7..1a21be5a3718 100644 --- a/cvat-ui/src/cvat-canvas.ts +++ b/cvat-ui/src/cvat-canvas.ts @@ -9,9 +9,15 @@ import { RectDrawingMethod, } from '../../cvat-canvas/src/typescript/canvas'; +function isAbleToChangeFrame(canvas: Canvas): boolean { + return ![CanvasMode.DRAG, CanvasMode.EDIT, CanvasMode.RESIZE] + .includes(canvas.mode()); +} + export { Canvas, CanvasMode, CanvasVersion, RectDrawingMethod, + isAbleToChangeFrame, }; diff --git a/cvat-ui/src/styles.scss b/cvat-ui/src/styles.scss index 965ea7567361..cbd6a4c2e763 100644 --- a/cvat-ui/src/styles.scss +++ b/cvat-ui/src/styles.scss @@ -48,5 +48,4 @@ hr { height: 100%; display: grid; min-width: 1280px; - min-height: 768px; } diff --git a/cvat/apps/engine/static/engine/js/shapeMerger.js b/cvat/apps/engine/static/engine/js/shapeMerger.js index c7fc478c540b..f04853296bc0 100644 --- a/cvat/apps/engine/static/engine/js/shapeMerger.js +++ b/cvat/apps/engine/static/engine/js/shapeMerger.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018-2020 Intel Corporation * * SPDX-License-Identifier: MIT */ @@ -145,7 +145,7 @@ class ShapeMergerModel extends Listener { let nextFrame = frame + 1; let stopFrame = window.cvat.player.frames.stop; let type = shapeDict[frame].shape.type; - if (type === 'annotation_box' && !(nextFrame in shapeDict) && nextFrame <= stopFrame) { + if (type.startsWith('annotation_') && !(nextFrame in shapeDict) && nextFrame <= stopFrame) { let copy = Object.assign({}, object.shapes[object.shapes.length - 1]); copy.outside = true; copy.frame += 1; diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html index 66cf69fad65a..d78b4ecf93e6 100644 --- a/cvat/apps/engine/templates/engine/annotation.html +++ b/cvat/apps/engine/templates/engine/annotation.html @@ -1,5 +1,5 @@ @@ -451,7 +451,7 @@