Merge pull request #1365 from opencv/develop

Merge changes for 1.0.0-beta release
main
Nikita Manovich 6 years ago committed by GitHub
commit 8593d7e658
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0-beta] - Unreleased
### Added
- Special behaviour for attribute value ``__undefined__`` (invisibility, no shortcuts to be set in AAM)
- Dialog window with some helpful information about using filters
- Ability to display a bitmap in the new UI
- Button to reset colors settings (brightness, saturation, contrast) in the new UI
- Added option to display shape text always
### Changed
- Increase preview size of a task till 256, 256 on the server
### Deprecated
-
### Removed
-
### 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
-
## [1.0.0-alpha] - 2020-03-31
### Added
- Data streaming using chunks (https://github.com/opencv/cvat/pull/1007)

@ -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;
@ -100,10 +104,13 @@ Canvas itself handles:
select(objectState: any): void;
fitCanvas(): void;
bitmap(enabled: boolean): void;
dragCanvas(enable: boolean): void;
zoomCanvas(enable: boolean): void;
mode(): Mode;
cancel(): void;
configure(configuration: Configuration): void;
}
```
@ -189,4 +196,6 @@ Standard JS events are used.
| dragCanvas() | + | - | - | - | - | - | + | - |
| zoomCanvas() | + | - | - | - | - | - | - | + |
| cancel() | - | + | + | + | + | + | + | + |
| configure() | + | - | - | - | - | - | - | - |
| bitmap() | + | + | + | + | + | + | + | + |
| setZLayer() | + | + | + | + | + | + | + | + |

@ -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;

@ -11,6 +11,7 @@ import {
CanvasModel,
CanvasModelImpl,
RectDrawingMethod,
Configuration,
} from './canvasModel';
import {
@ -49,11 +50,13 @@ interface Canvas {
select(objectState: any): void;
fitCanvas(): void;
bitmap(enable: boolean): void;
dragCanvas(enable: boolean): void;
zoomCanvas(enable: boolean): void;
mode(): void;
mode(): Mode;
cancel(): void;
configure(configuration: Configuration): void;
}
class CanvasImpl implements Canvas {
@ -86,6 +89,10 @@ class CanvasImpl implements Canvas {
);
}
public bitmap(enable: boolean): void {
this.model.bitmap(enable);
}
public dragCanvas(enable: boolean): void {
this.model.dragCanvas(enable);
}
@ -141,11 +148,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,
};

@ -36,7 +36,6 @@ export interface CanvasController {
enableDrag(x: number, y: number): void;
drag(x: number, y: number): void;
disableDrag(): void;
fit(): void;
}

@ -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;
@ -98,8 +103,10 @@ export enum UpdateReasons {
GROUP = 'group',
SELECT = 'select',
CANCEL = 'cancel',
BITMAP = 'bitmap',
DRAG_CANVAS = 'drag_canvas',
ZOOM_CANVAS = 'ZOOM_CANVAS',
ZOOM_CANVAS = 'zoom_canvas',
CONFIG_UPDATED = 'config_updated',
}
export enum Mode {
@ -116,6 +123,7 @@ export enum Mode {
}
export interface CanvasModel {
readonly imageBitmap: boolean;
readonly image: Image | null;
readonly objects: any[];
readonly zLayer: number | null;
@ -126,6 +134,7 @@ export interface CanvasModel {
readonly mergeData: MergeData;
readonly splitData: SplitData;
readonly groupData: GroupData;
readonly configuration: Configuration;
readonly selected: any;
geometry: Geometry;
mode: Mode;
@ -148,9 +157,11 @@ 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;
configure(configuration: Configuration): void;
cancel(): void;
}
@ -159,6 +170,8 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
activeElement: ActiveElement;
angle: number;
canvasSize: Size;
configuration: Configuration;
imageBitmap: boolean;
image: Image | null;
imageID: number | null;
imageOffset: number;
@ -191,6 +204,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
height: 0,
width: 0,
},
configuration: {
displayAllText: false,
undefinedAttrValue: '',
},
imageBitmap: false,
image: null,
imageID: null,
imageOffset: 0,
@ -277,6 +295,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}`);
@ -485,10 +508,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,
@ -522,6 +565,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;
}

@ -36,6 +36,7 @@ import {
GroupData,
Mode,
Size,
Configuration,
} from './canvasModel';
export interface CanvasView {
@ -47,6 +48,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;
@ -65,6 +67,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;
@ -285,7 +288,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 +306,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 +361,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`;
}
@ -538,6 +541,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
clientID: null,
attributeID: null,
};
this.configuration = model.configuration;
this.mode = Mode.IDLE;
// Create HTML elements
@ -546,6 +550,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 +595,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 +612,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 +710,19 @@ 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.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 +895,59 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.mode = Mode.IDLE;
this.canvas.style.cursor = '';
}
if (model.imageBitmap
&& [UpdateReasons.IMAGE_CHANGED,
UpdateReasons.OBJECTS_UPDATED,
UpdateReasons.SET_Z_LAYER,
].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) {
if (state.hidden || state.outside) continue;
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,
@ -900,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');
}
}
@ -942,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,
@ -958,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(':');
@ -987,6 +1066,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 +1111,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 +1167,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 +1191,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];
}
@ -1309,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
@ -1347,6 +1438,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 +1448,9 @@ 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)) {
block.tspan(`${attrNames[attrID]}: ${attributes[attrID]}`).attr({
const value = attributes[attrID] === undefinedAttrValue
? '' : attributes[attrID];
block.tspan(`${attrNames[attrID]}: ${value}`).attr({
attrID,
dy: '1em',
x: 0,

@ -10,6 +10,7 @@ const AREA_THRESHOLD = 9;
const SIZE_THRESHOLD = 3;
const POINTS_STROKE_WIDTH = 1.5;
const POINTS_SELECTED_STROKE_WIDTH = 4;
const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
export default {
BASE_STROKE_WIDTH,
@ -20,4 +21,5 @@ export default {
SIZE_THRESHOLD,
POINTS_STROKE_WIDTH,
POINTS_SELECTED_STROKE_WIDTH,
UNDEFINED_ATTRIBUTE_VALUE,
};

@ -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,10 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.crosshair) {
this.removeCrosshair();
}
if (!this.drawData.initialState) {
this.onDrawDone(null);
}
}
private initDrawing(): void {
@ -175,8 +181,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 +297,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 +605,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 +679,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);
}
}

@ -441,7 +441,7 @@
* Returns the ranges of cached frames
* @method ranges
* @memberof Session.frames
* @returns {Array{string}}
* @returns {Array.<string>}
* @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.<string|number>>}
* array of pairs [action name, frame number]
* @instance
* @async
*/

@ -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',
@ -28,6 +29,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 {
@ -66,6 +68,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,
@ -200,3 +211,12 @@ export function switchShowingInterpolatedTracks(showAllInterpolationTracks: bool
},
};
}
export function switchShowingObjectsTextAlways(showObjectsTextAlways: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS,
payload: {
showObjectsTextAlways,
},
};
}

@ -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 (
<>
<Paragraph>
<Title level={3}>General</Title>
</Paragraph>
<Paragraph>
You can use filters to display only subset of objects on a frame
or to search objects that satisfy the filters using hotkeys
<Text strong>
{` ${searchForwardShortcut} `}
</Text>
and
<Text strong>
{` ${searchBackwardShortcut} `}
</Text>
</Paragraph>
<Paragraph>
<Text strong>Supported properties: </Text>
width, height, label, serverID, clientID, type, shape, occluded
<br />
<Text strong>Supported operators: </Text>
==, !=, &gt;, &gt;=, &lt;, &lt;=, ~=, (), &amp; and |
<br />
<Text strong>
If you have double quotes in your query string,
please escape them using back slash: \&quot; (see the latest example)
</Text>
<br />
All properties and values are case-sensitive.
CVAT uses json queries to perform search.
</Paragraph>
<Paragraph>
<Title level={3}>Examples</Title>
<ul>
<li>label==&quot;car&quot; | label==[&quot;road sign&quot;]</li>
<li>width &gt;= height</li>
<li>attr[&quot;Attribute 1&quot;] == attr[&quot;Attribute 2&quot;]</li>
<li>clientID == 50</li>
<li>
(label==&quot;car&quot; &amp; attr[&quot;parked&quot;]==true)
| (label==&quot;pedestrian&quot; &amp; width &gt; 150)
</li>
<li>
(( label==[&quot;car \&quot;mazda\&quot;&quot;])
&amp; (attr[&quot;sunglasses&quot;]==true
| (width &gt; 150 | height &gt; 150 &amp; (clientID == serverID)))))
</li>
</ul>
</Paragraph>
</>
);
}
function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Element {
const {
annotationsFilters,
annotationsFiltersHistory,
searchForwardShortcut,
searchBackwardShortcut,
changeAnnotationsFilters,
} = props;
const [underCursor, setUnderCursor] = useState(false);
return (
<Select
className='cvat-annotations-filters-input'
@ -70,13 +143,36 @@ function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Ele
value={annotationsFilters}
mode='tags'
style={{ width: '100%' }}
placeholder={(
<>
<Icon type='filter' />
<span style={{ marginLeft: 5 }}>Annotations filter</span>
</>
)}
placeholder={
underCursor ? (
<>
<Tooltip title='Click to open help'>
<Icon
type='filter'
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
Modal.info({
width: 700,
title: 'How to use filters?',
content: filtersHelpModalContent(
searchForwardShortcut,
searchBackwardShortcut,
),
});
}}
/>
</Tooltip>
</>
) : (
<>
<Icon style={{ transform: 'scale(0.9)' }} type='filter' />
<span style={{ marginLeft: 5 }}>Annotations filters</span>
</>
)
}
onChange={changeAnnotationsFilters}
onMouseEnter={() => setUnderCursor(true)}
onMouseLeave={() => setUnderCursor(false)}
>
{annotationsFiltersHistory.map((element: string): JSX.Element => (
<Select.Option key={element} value={element}>{element}</Select.Option>

@ -10,6 +10,8 @@ import Select, { SelectValue } from 'antd/lib/select';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Input from 'antd/lib/input';
import consts from 'consts';
interface InputElementParameters {
attrID: number;
inputType: string;
@ -53,7 +55,10 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
)}
>
{values.map((value: string): JSX.Element => (
<Select.Option key={value} value={value}>{value}</Select.Option>
<Select.Option key={value} value={value}>
{value === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Select.Option>
))}
</Select>
</div>
@ -71,7 +76,10 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
)}
>
{values.map((value: string): JSX.Element => (
<Radio style={{ display: 'block' }} key={value} value={value}>{value}</Radio>
<Radio style={{ display: 'block' }} key={value} value={value}>
{value === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Radio>
))}
</Radio.Group>
</div>
@ -193,7 +201,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 +224,7 @@ function renderList(parameters: ListParameters): JSX.Element | null {
return (
<div className='attribute-annotation-sidebar-attr-list-wrapper'>
<GlobalHotKeys keyMap={keyMap as KeyMap} handlers={handlers} allowChanges />
{values.map((value: string, index: number): JSX.Element => (
{filteredValues.map((value: string, index: number): JSX.Element => (
<div key={value}>
<Text strong>{`${index}:`}</Text>
<Text>{` ${value}`}</Text>

@ -48,9 +48,7 @@
margin: 10px 0px 10px 10px;
}
.attribute-annotation-sidebar-attr-elem-wrapper {
display: inline-block;
width: 60%;
}

@ -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();
@ -42,6 +43,7 @@ interface Props {
colorBy: ColorBy;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
grid: boolean;
gridSize: number;
gridColor: GridColor;
@ -58,6 +60,7 @@ interface Props {
contextVisible: boolean;
contextType: ContextMenuType;
aamZoomMargin: number;
showObjectsTextAlways: boolean;
workspace: Workspace;
keyMap: Record<string, ExtendedKeyMapOptions>;
onSetupCanvas: () => void;
@ -91,6 +94,7 @@ interface Props {
export default class CanvasWrapperComponent extends React.PureComponent<Props> {
public componentDidMount(): void {
const {
showObjectsTextAlways,
canvasInstance,
curZLayer,
} = this.props;
@ -101,7 +105,12 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
.getElementsByClassName('cvat-canvas-container');
wrapper.appendChild(canvasInstance.html());
canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways,
});
canvasInstance.setZLayer(curZLayer);
this.initialSetup();
this.updateCanvas();
}
@ -112,6 +121,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
colorBy,
selectedOpacity,
blackBorders,
showBitmap,
frameData,
frameAngle,
annotations,
@ -128,8 +138,16 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
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) {
@ -198,6 +216,10 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
this.updateShapesView();
}
if (prevProps.showBitmap !== showBitmap) {
canvasInstance.bitmap(showBitmap);
}
if (prevProps.frameAngle !== frameAngle) {
canvasInstance.rotate(frameAngle);
}
@ -557,6 +579,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
for (const state of annotations) {
let shapeColor = '';
if (colorBy === ColorBy.INSTANCE) {
shapeColor = state.color;
} else if (colorBy === ColorBy.GROUP) {
@ -572,6 +595,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
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 });
}

@ -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
</Checkbox>
<Checkbox
onChange={changeShowBitmap}
checked={showBitmap}
>
Show bitmap
</Checkbox>
</div>
</Collapse.Panel>
</Collapse>

@ -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,10 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element
}}
>
{ attrValues.map((value: string): JSX.Element => (
<Radio key={value} value={value}>{value}</Radio>
<Radio key={value} value={value}>
{value === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Radio>
)) }
</Radio.Group>
</fieldset>
@ -534,7 +537,10 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element
className='cvat-object-item-select-attribute'
>
{ attrValues.map((value: string): JSX.Element => (
<Select.Option key={value} value={value}>{value}</Select.Option>
<Select.Option key={value} value={value}>
{value === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Select.Option>
)) }
</Select>
</Col>

@ -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 (

@ -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 {

@ -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 {
/>
</Col>
</Row>
<Row className='cvat-player-reset-color-settings'>
<Col>
<Button
onClick={() => {
onChangeBrightnessLevel(100);
onChangeContrastLevel(100);
onChangeSaturationLevel(100);
}}
>
Reset color settings
</Button>
</Col>
</Row>
</div>
);
}

@ -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;
@ -20,6 +23,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 +36,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 {
@ -73,12 +79,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;

@ -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 {
<Text type='secondary'> Show hidden interpolated objects in the side panel </Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-show-text-always'>
<Col className='cvat-workspace-settings-show-text-always-checkbox'>
<Checkbox
className='cvat-text-color'
checked={showObjectsTextAlways}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchShowingObjectsTextAlways(event.target.checked);
}}
>
Always show object details
</Checkbox>
</Col>
<Col>
<Text type='secondary'> Show text for an object on the canvas not only when the object is activated </Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-aam-zoom-margin'>
<Col>
<Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text>

@ -0,0 +1,11 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
const NO_BREAK_SPACE = '\u00a0';
export default {
UNDEFINED_ATTRIBUTE_VALUE,
NO_BREAK_SPACE,
};

@ -62,6 +62,7 @@ interface StateToProps {
colorBy: ColorBy;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
grid: boolean;
gridSize: number;
gridColor: GridColor;
@ -73,6 +74,7 @@ interface StateToProps {
saturationLevel: number;
resetZoom: boolean;
aamZoomMargin: number;
showObjectsTextAlways: boolean;
workspace: Workspace;
minZLayer: number;
maxZLayer: number;
@ -163,12 +165,14 @@ function mapStateToProps(state: CombinedState): StateToProps {
},
workspace: {
aamZoomMargin,
showObjectsTextAlways,
},
shapes: {
opacity,
colorBy,
selectedOpacity,
blackBorders,
showBitmap,
},
},
shortcuts: {
@ -192,6 +196,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
colorBy,
selectedOpacity,
blackBorders,
showBitmap,
grid,
gridSize,
gridColor,
@ -203,6 +208,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
saturationLevel,
resetZoom,
aamZoomMargin,
showObjectsTextAlways,
curZLayer,
minZLayer,
maxZLayer,

@ -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<Props> {
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<Props> {
opacity,
selectedOpacity,
blackBorders,
showBitmap,
collapseSidebar,
collapseAppearance,
} = this.props;
@ -197,12 +211,14 @@ class ObjectsSideBarContainer extends React.PureComponent<Props> {
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}
/>
);
}

@ -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));
},
};
}

@ -426,6 +426,7 @@ export interface WorkspaceSettingsState {
autoSave: boolean;
autoSaveInterval: number; // in ms
aamZoomMargin: number;
showObjectsTextAlways: boolean;
showAllInterpolationTracks: boolean;
}
@ -434,6 +435,7 @@ export interface ShapesSettingsState {
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
}
export interface SettingsState {

@ -22,11 +22,13 @@ const defaultState: SettingsState = {
opacity: 3,
selectedOpacity: 30,
blackBorders: false,
showBitmap: false,
},
workspace: {
autoSave: false,
autoSaveInterval: 15 * 60 * 1000,
aamZoomMargin: 100,
showObjectsTextAlways: false,
showAllInterpolationTracks: false,
},
player: {
@ -127,6 +129,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,
@ -217,18 +228,27 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
},
};
}
case SettingsActionTypes.SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS: {
return {
...state,
workspace: {
...state.workspace,
showObjectsTextAlways: action.payload.showObjectsTextAlways,
},
};
}
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 };
}

@ -2,6 +2,6 @@
//
// SPDX-License-Identifier: MIT
export default function isDev() {
export default function isDev(): boolean {
return process.env.NODE_ENV === 'development';
}

@ -4,6 +4,6 @@
from cvat.utils.version import get_version
VERSION = (1, 0, 0, 'alpha', 0)
VERSION = (1, 0, 0, 'beta', 0)
__version__ = get_version(VERSION)

@ -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 @@ class AnnotationFormatSerializer(serializers.ModelSerializer):
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]

@ -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 @@ class DEXTR_HANDLER:
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 @@ class DEXTR_HANDLER:
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 = (

@ -12,15 +12,14 @@ from cvat.apps.dextr_segmentation.dextr import DEXTR_HANDLER
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)

@ -293,7 +293,10 @@ class TrackManager(ObjectManager):
@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):

@ -52,11 +52,12 @@ class IMediaReader(ABC):
@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')

@ -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)
Loading…
Cancel
Save