Semi-automatic tools enhancements (Stage 0) (#3417)

* Improved removable points, added a button to finish current object
* Code refactoring for ai tools and opencv
* Fixed style for workspace selector
* IOG UX fix
* Updated version & changelog
* Added 'min_neg_points' parameter to serverless interactors
* Return 'min_neg_points' from the server
main
Boris Sekachev 5 years ago committed by GitHub
parent bab3366e18
commit 330b8a832f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support of cloud storage without copying data into CVAT: server part (<https://github.com/openvinotoolkit/cvat/pull/2620>)
- Filter `is_active` for user list (<https://github.com/openvinotoolkit/cvat/pull/3235>)
- Ability to export/import tasks (<https://github.com/openvinotoolkit/cvat/pull/3056>)
- Explicit "Done" button when drawing any polyshapes (<https://github.com/openvinotoolkit/cvat/pull/3417>)
### Changed
@ -20,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update of COCO format documentation (<https://github.com/openvinotoolkit/cvat/pull/3197>)
- Updated Webpack Dev Server config to add proxxy (<https://github.com/openvinotoolkit/cvat/pull/3368>)
- Update to Django 3.1.12 (<https://github.com/openvinotoolkit/cvat/pull/3378>)
- Updated visibility for removable points in AI tools (<https://github.com/openvinotoolkit/cvat/pull/3417>)
- Updated UI handling for IOG serverless function (<https://github.com/openvinotoolkit/cvat/pull/3417>)
### Deprecated

@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
"version": "2.4.5",
"version": "2.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
"version": "2.4.5",
"version": "2.5.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {

@ -155,6 +155,17 @@ polyline.cvat_canvas_shape_splitting {
fill: blueviolet;
}
.cvat_canvas_interact_intermediate_shape {
@extend .cvat_canvas_shape;
}
.cvat_canvas_removable_interaction_point {
cursor:
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K')
10 10,
auto;
}
.svg_select_boundingRect {
opacity: 0;
pointer-events: none;

@ -77,10 +77,14 @@ export interface InteractionData {
crosshair?: boolean;
minPosVertices?: number;
minNegVertices?: number;
enableNegVertices?: boolean;
startWithBox?: boolean;
enableThreshold?: boolean;
enableSliding?: boolean;
allowRemoveOnlyLast?: boolean;
intermediateShape?: {
shapeType: string;
points: number[];
};
}
export interface InteractionResult {
@ -551,7 +555,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
if (interactionData.enabled) {
if (interactionData.enabled && !interactionData.intermediateShape) {
if (this.data.interactionData.enabled) {
throw new Error('Interaction has been already started');
} else if (!interactionData.shapeType) {

@ -23,6 +23,7 @@ import consts from './consts';
import {
translateToSVG,
translateFromSVG,
translateToCanvas,
pointsToNumberArray,
parsePoints,
displayShapeSize,
@ -103,7 +104,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private translateToCanvas(points: number[]): number[] {
const { offset } = this.controller.geometry;
return points.map((coord: number): number => coord + offset);
return translateToCanvas(offset, points);
}
private translateFromCanvas(points: number[]): number[] {
@ -1267,9 +1268,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
} else if (reason === UpdateReasons.INTERACT) {
const data: InteractionData = this.controller.interactionData;
if (data.enabled && this.mode === Mode.IDLE) {
this.canvas.style.cursor = 'crosshair';
this.mode = Mode.INTERACT;
if (data.enabled && (this.mode === Mode.IDLE || data.intermediateShape)) {
if (!data.intermediateShape) {
this.canvas.style.cursor = 'crosshair';
this.mode = Mode.INTERACT;
}
this.interactionHandler.interact(data);
} else {
this.canvas.style.cursor = '';

@ -5,7 +5,9 @@
import * as SVG from 'svg.js';
import consts from './consts';
import Crosshair from './crosshair';
import { translateToSVG } from './shared';
import {
translateToSVG, PropType, stringifyPoints, translateToCanvas,
} from './shared';
import { InteractionData, InteractionResult, Geometry } from './canvasModel';
export interface InteractionHandler {
@ -26,6 +28,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
private crosshair: Crosshair;
private threshold: SVG.Rect | null;
private thresholdRectSize: number;
private intermediateShape: PropType<InteractionData, 'intermediateShape'>;
private drawnIntermediateShape: SVG.Shape;
private prepareResult(): InteractionResult[] {
return this.interactionShapes.map(
@ -65,8 +69,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
return enabled && !ctrlKey && !!interactionShapes.length;
}
const minPosVerticesAchieved = typeof minPosVertices === 'undefined' || minPosVertices <= positiveShapes.length;
const minNegVerticesAchieved = typeof minNegVertices === 'undefined' || minPosVertices <= negativeShapes.length;
const minPosVerticesDefined = Number.isInteger(minPosVertices);
const minNegVerticesDefined = Number.isInteger(minNegVertices) && minNegVertices >= 0;
const minPosVerticesAchieved = !minPosVerticesDefined || minPosVertices <= positiveShapes.length;
const minNegVerticesAchieved = !minNegVerticesDefined || minNegVertices <= negativeShapes.length;
const minimumVerticesAchieved = minPosVerticesAchieved && minNegVerticesAchieved;
return enabled && !ctrlKey && minimumVerticesAchieved && shapesWereUpdated;
}
@ -91,7 +97,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
private interactPoints(): void {
const eventListener = (e: MouseEvent): void => {
if ((e.button === 0 || (e.button === 2 && this.interactionData.enableNegVertices)) && !e.altKey) {
if ((e.button === 0 || (e.button === 2 && this.interactionData.minNegVertices >= 0)) && !e.altKey) {
e.preventDefault();
const [cx, cy] = translateToSVG((this.canvas.node as any) as SVGSVGElement, [e.clientX, e.clientY]);
if (!this.isWithinFrame(cx, cy)) return;
@ -121,8 +127,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
}
}
self.addClass('cvat_canvas_removable_interaction_point');
self.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale,
r: (consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale,
});
self.on('mousedown', (_e: MouseEvent): void => {
@ -132,6 +140,9 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.interactionShapes = this.interactionShapes.filter(
(shape: SVG.Shape): boolean => shape !== self,
);
if (this.interactionData.startWithBox && this.interactionShapes.length === 1) {
this.interactionShapes[0].style({ visibility: '' });
}
this.shapesWereUpdated = true;
if (this.shouldRaiseEvent(_e.ctrlKey)) {
this.onInteraction(this.prepareResult(), true, false);
@ -140,8 +151,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
});
self.on('mouseleave', (): void => {
self.removeClass('cvat_canvas_removable_interaction_point');
self.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / this.geometry.scale,
r: consts.BASE_POINT_SIZE / this.geometry.scale,
});
self.off('mousedown');
@ -153,7 +166,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.canvas.on('mousedown.interaction', eventListener);
}
private interactRectangle(): void {
private interactRectangle(shouldFinish: boolean, onContinue?: () => void): void {
let initialized = false;
const eventListener = (e: MouseEvent): void => {
if (e.button === 0 && !e.altKey) {
@ -170,11 +183,15 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.canvas.on('mousedown.interaction', eventListener);
this.currentInteractionShape
.on('drawstop', (): void => {
this.canvas.off('mousedown.interaction', eventListener);
this.interactionShapes.push(this.currentInteractionShape);
this.shapesWereUpdated = true;
this.canvas.off('mousedown.interaction', eventListener);
this.interact({ enabled: false });
if (shouldFinish) {
this.interact({ enabled: false });
} else if (onContinue) {
onContinue();
}
})
.addClass('cvat_canvas_shape_drawing')
.attr({
@ -194,15 +211,24 @@ export class InteractionHandlerImpl implements InteractionHandler {
private startInteraction(): void {
if (this.interactionData.shapeType === 'rectangle') {
this.interactRectangle();
this.interactRectangle(true);
} else if (this.interactionData.shapeType === 'points') {
this.interactPoints();
if (this.interactionData.startWithBox) {
this.interactRectangle(false, (): void => this.interactPoints());
} else {
this.interactPoints();
}
} else {
throw new Error('Interactor implementation supports only rectangle and points');
}
}
private release(): void {
if (this.drawnIntermediateShape) {
this.drawnIntermediateShape.remove();
this.drawnIntermediateShape = null;
}
if (this.crosshair) {
this.removeCrosshair();
}
@ -241,6 +267,31 @@ export class InteractionHandlerImpl implements InteractionHandler {
return imageX >= 0 && imageX < width && imageY >= 0 && imageY < height;
}
private updateIntermediateShape(): void {
const { intermediateShape, geometry } = this;
if (this.drawnIntermediateShape) {
this.drawnIntermediateShape.remove();
}
if (!intermediateShape) return;
const { shapeType, points } = intermediateShape;
if (shapeType === 'polygon') {
this.drawnIntermediateShape = this.canvas
.polygon(stringifyPoints(translateToCanvas(geometry.offset, points)))
.attr({
'color-rendering': 'optimizeQuality',
'shape-rendering': 'geometricprecision',
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
fill: 'none',
})
.addClass('cvat_canvas_interact_intermediate_shape');
} else {
throw new Error(
`Shape type "${shapeType}" was not implemented at interactionHandler::updateIntermediateShape`,
);
}
}
public constructor(
onInteraction: (
shapes: InteractionResult[] | null,
@ -264,6 +315,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.crosshair = new Crosshair();
this.threshold = null;
this.thresholdRectSize = 300;
this.intermediateShape = null;
this.drawnIntermediateShape = null;
this.cursorPosition = {
x: 0,
y: 0,
@ -334,8 +387,13 @@ export class InteractionHandlerImpl implements InteractionHandler {
: [...this.interactionShapes];
for (const shape of shapesToBeScaled) {
if (shape.type === 'circle') {
(shape as SVG.Circle).radius(consts.BASE_POINT_SIZE / this.geometry.scale);
shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale);
if (shape.hasClass('cvat_canvas_removable_interaction_point')) {
(shape as SVG.Circle).radius((consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale);
shape.attr('stroke-width', consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale);
} else {
(shape as SVG.Circle).radius(consts.BASE_POINT_SIZE / this.geometry.scale);
shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale);
}
} else {
shape.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale);
}
@ -343,7 +401,13 @@ export class InteractionHandlerImpl implements InteractionHandler {
}
public interact(interactionData: InteractionData): void {
if (interactionData.enabled) {
if (interactionData.intermediateShape) {
this.intermediateShape = interactionData.intermediateShape;
this.updateIntermediateShape();
if (this.interactionData.startWithBox) {
this.interactionShapes[0].style({ visibility: 'hidden' });
}
} else if (interactionData.enabled) {
this.interactionData = interactionData;
this.initInteraction();
this.startInteraction();

@ -181,3 +181,9 @@ export function vectorLength(vector: Vector2D): number {
const sqrJ = vector.j ** 2;
return Math.sqrt(sqrI + sqrJ);
}
export function translateToCanvas(offset: number, points: number[]): number[] {
return points.map((coord: number): number => coord + offset);
}
export type PropType<T, Prop extends keyof T> = T[Prop];

@ -17,7 +17,8 @@ class MLModel {
this._params = {
canvas: {
minPosVertices: data.min_pos_points,
enableNegVertices: true,
minNegVertices: data.min_neg_points,
startWithBox: data.startswith_box,
},
};
}

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.20.7",
"version": "1.21.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.20.7",
"version": "1.21.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M5.816 24.607v6.572h6.53V32H5v-7.393h.816zm29.184 0V32h-7.347v-.821h6.53v-6.572H35zM12.347 9v.821h-6.53v6.572H5V9h7.347zM35 9v7.393h-.816V9.82h-6.53V9H35z" stroke="#000" fill="#000" fill-rule="evenodd"/></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg"><path d="M5.816 24.607v6.572h6.53V32H5v-7.393h.816zm29.184 0V32h-7.347v-.821h6.53v-6.572H35zM12.347 9v.821h-6.53v6.572H5V9h7.347zM35 9v7.393h-.816V9.82h-6.53V9H35z" stroke="#000" fill="#000" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 304 B

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M20 2c9.941 0 18 8.059 18 18s-8.059 18-18 18S2 29.941 2 20 10.059 2 20 2zm0 2.4C11.384 4.4 4.4 11.384 4.4 20S11.384 35.6 20 35.6 35.6 28.616 35.6 20 28.616 4.4 20 4.4zm1.27 25.2h-2.642V14.147h2.642V29.6zm-2.856-19.552c0-.429.13-.79.393-1.086.261-.295.65-.443 1.164-.443.514 0 .904.148 1.17.443.267.295.4.657.4 1.086 0 .428-.133.785-.4 1.07-.266.286-.656.43-1.17.43-.515 0-.903-.144-1.164-.43-.262-.285-.393-.642-.393-1.07z" fill="#000" fill-rule="nonzero"/></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg"><path d="M20 2c9.941 0 18 8.059 18 18s-8.059 18-18 18S2 29.941 2 20 10.059 2 20 2zm0 2.4C11.384 4.4 4.4 11.384 4.4 20S11.384 35.6 20 35.6 35.6 28.616 35.6 20 28.616 4.4 20 4.4zm1.27 25.2h-2.642V14.147h2.642V29.6zm-2.856-19.552c0-.429.13-.79.393-1.086.261-.295.65-.443 1.164-.443.514 0 .904.148 1.17.443.267.295.4.657.4 1.086 0 .428-.133.785-.4 1.07-.266.286-.656.43-1.17.43-.515 0-.903-.144-1.164-.43-.262-.285-.393-.642-.393-1.07z" fill="#000" fill-rule="nonzero"/></svg>

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 558 B

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M5 30h30v-3.333H5V30zm0-8.333h30v-3.334H5v3.334zM5 10v3.333h30V10H5z" fill="#000" fill-rule="evenodd"/></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg"><path d="M5 30h30v-3.333H5V30zm0-8.333h30v-3.334H5v3.334zM5 10v3.333h30V10H5z" fill="#000" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 182 B

After

Width:  |  Height:  |  Size: 204 B

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M35.748 2H4.253C2.254 2 1.246 4.425 2.662 5.841L15.5 18.682v13.13c0 .65.28 1.267.768 1.694l4.5 3.936c1.437 1.258 3.732.259 3.732-1.693V18.682L37.339 5.841C38.752 4.428 37.75 2 35.748 2zM22.25 17.75v18l-4.5-3.937V17.75L4.25 4.25h31.5l-13.5 13.5z" fill="#000" fill-rule="nonzero"/></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg"><path d="M35.748 2H4.253C2.254 2 1.246 4.425 2.662 5.841L15.5 18.682v13.13c0 .65.28 1.267.768 1.694l4.5 3.936c1.437 1.258 3.732.259 3.732-1.693V18.682L37.339 5.841C38.752 4.428 37.75 2 35.748 2zM22.25 17.75v18l-4.5-3.937V17.75L4.25 4.25h31.5l-13.5 13.5z" fill="#000" fill-rule="nonzero"/></svg>

Before

Width:  |  Height:  |  Size: 357 B

After

Width:  |  Height:  |  Size: 380 B

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="redoA" d="M0 0h34v24H0z"/></defs><g transform="translate(3 8)" fill="none" fill-rule="evenodd"><mask id="redoB" fill="#fff"><use xlink:href="#redoA"/></mask><path d="M28.262 9.331C20.935.18 8.932-.428 1.308 7.643V2.968H0v6.537c0 .45.293.817.654.817h5.232V8.688H2.35c7.125-7.322 18.2-6.683 24.987 1.8 7.14 8.92 7.14 23.437 0 32.357L28.262 44c7.65-9.557 7.65-25.111 0-34.669z" fill="#000" mask="url(#redoB)" transform="matrix(-1 0 0 1 34 0)"/></g></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="redoA" d="M0 0h34v24H0z"/></defs><g transform="translate(3 8)" fill="none" fill-rule="evenodd"><mask id="redoB" fill="#fff"><use xlink:href="#redoA"/></mask><path d="M28.262 9.331C20.935.18 8.932-.428 1.308 7.643V2.968H0v6.537c0 .45.293.817.654.817h5.232V8.688H2.35c7.125-7.322 18.2-6.683 24.987 1.8 7.14 8.92 7.14 23.437 0 32.357L28.262 44c7.65-9.557 7.65-25.111 0-34.669z" fill="#000" mask="url(#redoB)" transform="matrix(-1 0 0 1 34 0)"/></g></svg>

Before

Width:  |  Height:  |  Size: 573 B

After

Width:  |  Height:  |  Size: 596 B

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M29.131 5l5.257 5.257V35H5V5h24.131zm-.866 17.143H10.51v11.633h17.755V22.143zM10.51 6.224H6.224v27.552h3.062V20.918H29.49v12.858h3.673V10.764l-4.539-4.54h-.97v9.796H10.51V6.224zm8.878 20.205a.612.612 0 0 1 .1 1.216l-.1.008h-6.123a.612.612 0 0 1-.1-1.216l.1-.008h6.123zm2.271.177c.11.116.178.276.178.435 0 .159-.068.318-.178.435a.644.644 0 0 1-.435.177.63.63 0 0 1-.434-.177.64.64 0 0 1-.178-.435.64.64 0 0 1 .178-.435.641.641 0 0 1 .87 0zm-4.108-2.626a.612.612 0 0 1 .1 1.216l-.1.008h-4.286a.612.612 0 0 1-.1-1.216l.1-.008h4.286zm8.878-17.756H11.735v8.572h14.694V6.224zM25.51 7.45v6.122h-3.673V7.45h3.673zm-1.224 1.224H23.06v3.674h1.225V8.673z" fill="#000" fill-rule="nonzero"/></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg"><path d="M29.131 5l5.257 5.257V35H5V5h24.131zm-.866 17.143H10.51v11.633h17.755V22.143zM10.51 6.224H6.224v27.552h3.062V20.918H29.49v12.858h3.673V10.764l-4.539-4.54h-.97v9.796H10.51V6.224zm8.878 20.205a.612.612 0 0 1 .1 1.216l-.1.008h-6.123a.612.612 0 0 1-.1-1.216l.1-.008h6.123zm2.271.177c.11.116.178.276.178.435 0 .159-.068.318-.178.435a.644.644 0 0 1-.435.177.63.63 0 0 1-.434-.177.64.64 0 0 1-.178-.435.64.64 0 0 1 .178-.435.641.641 0 0 1 .87 0zm-4.108-2.626a.612.612 0 0 1 .1 1.216l-.1.008h-4.286a.612.612 0 0 1-.1-1.216l.1-.008h4.286zm8.878-17.756H11.735v8.572h14.694V6.224zM25.51 7.45v6.122h-3.673V7.45h3.673zm-1.224 1.224H23.06v3.674h1.225V8.673z" fill="#000" fill-rule="nonzero"/></svg>

Before

Width:  |  Height:  |  Size: 756 B

After

Width:  |  Height:  |  Size: 779 B

@ -1 +1 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="b" d="M0 0h34v24H0z"/></defs><g transform="translate(3 8)" fill="none" fill-rule="evenodd"><mask id="undoB" fill="#fff"><use xlink:href="#b"/></mask><path d="M28.62 9.331C20.935.18 8.932-.428 1.308 7.643V2.968H0v6.537c0 .45.293.817.654.817h5.232V8.688H2.35c7.125-7.322 18.2-6.683 24.987 1.8 7.14 8.92 7.14 23.437 0 32.357L28.262 44c7.65-9.557 7.65-25.111 0-34.669z" fill="#000" mask="url(#undoB)"/></g></svg>
<svg viewBox="0 0 40 40" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="b" d="M0 0h34v24H0z"/></defs><g transform="translate(3 8)" fill="none" fill-rule="evenodd"><mask id="undoB" fill="#fff"><use xlink:href="#b"/></mask><path d="M28.62 9.331C20.935.18 8.932-.428 1.308 7.643V2.968H0v6.537c0 .45.293.817.654.817h5.232V8.688H2.35c7.125-7.322 18.2-6.683 24.987 1.8 7.14 8.92 7.14 23.437 0 32.357L28.262 44c7.65-9.557 7.65-25.111 0-34.669z" fill="#000" mask="url(#undoB)"/></g></svg>

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 553 B

@ -19,7 +19,7 @@ import getCore from 'cvat-core-wrapper';
import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper';
import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors';
import {
CombinedState, ActiveControl, OpenCVTool, ObjectType,
CombinedState, ActiveControl, OpenCVTool, ObjectType, ShapeType,
} from 'reducers/interfaces';
import {
interactWithCanvas,
@ -93,17 +93,11 @@ const mapDispatchToProps = {
class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps, State> {
private activeTool: IntelligentScissors | null;
private interactiveStateID: number | null;
private interactionIsDone: boolean;
public constructor(props: Props & DispatchToProps) {
super(props);
const { labels } = props;
this.activeTool = null;
this.interactiveStateID = null;
this.interactionIsDone = false;
this.state = {
libraryInitialized: openCVWrapper.isInitialized,
initializationError: false,
@ -115,7 +109,6 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps
public componentDidMount(): void {
const { canvasInstance } = this.props;
canvasInstance.html().addEventListener('canvas.interacted', this.interactionListener);
canvasInstance.html().addEventListener('canvas.canceled', this.cancelListener);
}
public componentDidUpdate(prevProps: Props): void {
@ -125,42 +118,17 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps
if (this.activeTool) {
this.activeTool.reset();
}
this.interactiveStateID = null;
this.interactionIsDone = false;
}
}
public componentWillUnmount(): void {
const { canvasInstance } = this.props;
canvasInstance.html().removeEventListener('canvas.interacted', this.interactionListener);
canvasInstance.html().removeEventListener('canvas.canceled', this.cancelListener);
}
private getInteractiveState(): any | null {
const { states } = this.props;
return states.filter((_state: any): boolean => _state.clientID === this.interactiveStateID)[0] || null;
}
private cancelListener = async (): Promise<void> => {
const {
fetchAnnotations, isActivated, jobInstance, frame,
} = this.props;
if (isActivated) {
if (this.interactiveStateID !== null) {
const state = this.getInteractiveState();
this.interactiveStateID = null;
await state.delete(frame);
fetchAnnotations();
}
await jobInstance.actions.freeze(false);
}
};
private interactionListener = async (e: Event): Promise<void> => {
const {
fetchAnnotations, updateAnnotations, isActivated, jobInstance, frame, labels, curZOrder,
createAnnotations, isActivated, jobInstance, frame, labels, curZOrder, canvasInstance,
} = this.props;
const { activeLabelID } = this.state;
if (!isActivated || !this.activeTool) {
@ -171,64 +139,36 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps
shapesUpdated, isDone, threshold, shapes,
} = (e as CustomEvent).detail;
const pressedPoints = convertShapesForInteractor(shapes, 0).flat();
this.interactionIsDone = isDone;
try {
let points: number[] = [];
if (shapesUpdated) {
points = await this.runCVAlgorithm(pressedPoints, threshold);
const result = await this.runCVAlgorithm(pressedPoints, threshold);
canvasInstance.interact({
enabled: true,
intermediateShape: {
shapeType: ShapeType.POLYGON,
points: result,
},
});
}
if (this.interactiveStateID === null) {
if (!this.interactionIsDone) {
await jobInstance.actions.freeze(true);
}
const object = new core.classes.ObjectState({
...this.activeTool.params.shape,
if (isDone) {
const finalObject = new core.classes.ObjectState({
frame,
objectType: ObjectType.SHAPE,
shapeType: ShapeType.POLYGON,
label: labels.filter((label: any) => label.id === activeLabelID)[0],
points,
// need to recalculate without the latest sliding point
points: await this.runCVAlgorithm(pressedPoints, threshold),
occluded: false,
zOrder: curZOrder,
});
// need a clientID of a created object to interact with it further
// so, we do not use createAnnotationAction
const [clientID] = await jobInstance.annotations.put([object]);
this.interactiveStateID = clientID;
// update annotations on a canvas
fetchAnnotations();
return;
}
const state = this.getInteractiveState();
if ((e as CustomEvent).detail.isDone) {
const finalObject = new core.classes.ObjectState({
frame: state.frame,
objectType: state.objectType,
label: state.label,
shapeType: state.shapeType,
// need to recalculate without the latest sliding point
points: points = await this.runCVAlgorithm(pressedPoints, threshold),
occluded: state.occluded,
zOrder: state.zOrder,
});
this.interactiveStateID = null;
await state.delete(frame);
await jobInstance.actions.freeze(false);
await jobInstance.annotations.put([finalObject]);
fetchAnnotations();
} else {
state.points = points;
updateAnnotations([state]);
fetchAnnotations();
createAnnotations(jobInstance, frame, [finalObject]);
}
} catch (error) {
notification.error({
description: error.toString(),
message: 'Processing error occured',
message: 'OpenCV.js processing error occured',
});
}
};

@ -85,15 +85,14 @@ function mapStateToProps(state: CombinedState): StateToProps {
const mapDispatchToProps = {
onInteractionStart: interactWithCanvas,
updateAnnotations: updateAnnotationsAsync,
fetchAnnotations: fetchAnnotationsAsync,
createAnnotations: createAnnotationsAsync,
fetchAnnotations: fetchAnnotationsAsync,
};
type Props = StateToProps & DispatchToProps;
interface State {
activeInteractor: Model | null;
activeLabelID: number;
interactiveStateID: number | null;
activeTracker: Model | null;
trackingProgress: number | null;
trackingFrames: number;
@ -105,6 +104,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
private interactionIsAborted: boolean;
private interactionIsDone: boolean;
private latestResult: number[];
public constructor(props: Props) {
super(props);
@ -112,13 +112,13 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
activeInteractor: props.interactors.length ? props.interactors[0] : null,
activeTracker: props.trackers.length ? props.trackers[0] : null,
activeLabelID: props.labels.length ? props.labels[0].id : null,
interactiveStateID: null,
trackingProgress: null,
trackingFrames: 10,
fetching: false,
mode: 'interaction',
};
this.latestResult = [];
this.interactionIsAborted = false;
this.interactionIsDone = false;
}
@ -149,12 +149,6 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
canvasInstance.html().removeEventListener('canvas.canceled', this.cancelListener);
}
private getInteractiveState(): any | null {
const { states } = this.props;
const { interactiveStateID } = this.state;
return states.filter((_state: any): boolean => _state.clientID === interactiveStateID)[0] || null;
}
private contextmenuDisabler = (e: MouseEvent): void => {
if (
e.target &&
@ -166,10 +160,9 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
};
private cancelListener = async (): Promise<void> => {
const {
isActivated, jobInstance, frame, fetchAnnotations,
} = this.props;
const { interactiveStateID, fetching } = this.state;
const { isActivated } = this.props;
const { fetching } = this.state;
this.latestResult = [];
if (isActivated) {
if (fetching && !this.interactionIsDone) {
@ -177,15 +170,6 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
this.setState({ fetching: false });
this.interactionIsAborted = true;
}
if (interactiveStateID !== null) {
const state = this.getInteractiveState();
this.setState({ interactiveStateID: null });
await state.delete(frame);
fetchAnnotations();
}
await jobInstance.actions.freeze(false);
}
};
@ -197,28 +181,23 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
jobInstance,
isActivated,
activeLabelID,
fetchAnnotations,
updateAnnotations,
canvasInstance,
createAnnotations,
} = this.props;
const { activeInteractor, interactiveStateID, fetching } = this.state;
const { activeInteractor, fetching } = this.state;
if (!isActivated) {
return;
}
try {
if (fetching) {
this.interactionIsDone = (e as CustomEvent).detail.isDone;
return;
}
this.interactionIsDone = (e as CustomEvent).detail.isDone;
const interactor = activeInteractor as Model;
let result = [];
if ((e as CustomEvent).detail.shapesUpdated) {
this.setState({ fetching: true });
try {
result = await core.lambda.call(jobInstance.task, interactor, {
this.latestResult = await core.lambda.call(jobInstance.task, interactor, {
frame,
pos_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 0),
neg_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 2),
@ -227,6 +206,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
if (this.interactionIsAborted) {
// while the server request
// user has cancelled interaction (for example pressed ESC)
this.latestResult = [];
return;
}
} finally {
@ -234,66 +214,30 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
}
if (this.interactionIsDone) {
// while the server request, user has done interaction (for example pressed N)
if (!this.latestResult.length) {
return;
}
if (this.interactionIsDone && !fetching) {
const object = new core.classes.ObjectState({
frame,
objectType: ObjectType.SHAPE,
label: labels.length ? labels.filter((label: any) => label.id === activeLabelID)[0] : null,
shapeType: ShapeType.POLYGON,
points: result.flat(),
points: this.latestResult.flat(),
occluded: false,
zOrder: curZOrder,
});
await jobInstance.annotations.put([object]);
fetchAnnotations();
createAnnotations(jobInstance, frame, [object]);
} else {
// no shape yet, then create it and save to collection
if (interactiveStateID === null) {
// freeze history for interaction time
// (points updating shouldn't cause adding new actions to history)
await jobInstance.actions.freeze(true);
const object = new core.classes.ObjectState({
frame,
objectType: ObjectType.SHAPE,
label: labels.length ? labels.filter((label: any) => label.id === activeLabelID)[0] : null,
canvasInstance.interact({
enabled: true,
intermediateShape: {
shapeType: ShapeType.POLYGON,
points: result.flat(),
occluded: false,
zOrder: curZOrder,
});
// need a clientID of a created object to interact with it further
// so, we do not use createAnnotationAction
const [clientID] = await jobInstance.annotations.put([object]);
// update annotations on a canvas
fetchAnnotations();
this.setState({ interactiveStateID: clientID });
return;
}
const state = this.getInteractiveState();
if ((e as CustomEvent).detail.isDone) {
const finalObject = new core.classes.ObjectState({
frame: state.frame,
objectType: state.objectType,
label: state.label,
shapeType: state.shapeType,
points: result.length ? result.flat() : state.points,
occluded: state.occluded,
zOrder: state.zOrder,
});
this.setState({ interactiveStateID: null });
await state.delete(frame);
await jobInstance.actions.freeze(false);
await jobInstance.annotations.put([finalObject]);
fetchAnnotations();
} else {
state.points = result.flat();
updateAnnotations([state]);
fetchAnnotations();
}
points: this.latestResult.flat(),
},
});
}
} catch (err) {
notification.error({
@ -633,7 +577,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
private renderDetectorBlock(): JSX.Element {
const {
jobInstance, detectors, curZOrder, frame, fetchAnnotations,
jobInstance, detectors, curZOrder, frame,
} = this.props;
if (!detectors.length) {
@ -672,8 +616,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}),
);
await jobInstance.annotations.put(states);
fetchAnnotations();
createAnnotationsAsync(jobInstance, frame, states);
} catch (error) {
notification.error({
description: error.toString(),

@ -16,7 +16,7 @@
.ant-layout-header.cvat-annotation-header {
background-color: $background-color-2;
border-bottom: 1px solid $border-color-1;
height: 54px;
height: 48px;
padding: 0;
> div:first-child {
@ -39,14 +39,14 @@
.ant-btn.cvat-annotation-header-button {
padding: 0;
width: 54px;
height: 54px;
text-align: center;
width: 48px;
height: 48px;
user-select: none;
color: $text-color;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 3px;
> span:not([role='img']) {
@ -55,21 +55,15 @@
}
> span[role='img'] {
transform: scale(0.8);
padding: 3px;
font-size: 24px;
}
&:hover > span[role='img'] {
transform: scale(0.85);
transform: scale(1.1);
}
&:active > span[role='img'] {
transform: scale(0.8);
}
> * {
display: block;
line-height: 0;
transform: scale(1.05);
}
&.filters-armed {
@ -135,7 +129,7 @@ button.cvat-predictor-button {
}
.cvat-annotation-header-player-group > div {
height: 54px;
height: 48px;
line-height: 0;
flex-wrap: nowrap;
}
@ -212,9 +206,11 @@ button.cvat-predictor-button {
justify-content: flex-end;
> div {
display: block;
height: 54px;
margin-right: 15px;
display: flex;
height: 48px;
align-items: center;
justify-content: center;
padding-right: 8px;
}
}

@ -4,7 +4,7 @@
import React from 'react';
import { Col } from 'antd/lib/grid';
import Icon from '@ant-design/icons';
import Icon, { CheckOutlined } from '@ant-design/icons';
import Modal from 'antd/lib/modal';
import Button from 'antd/lib/button';
import Timeline from 'antd/lib/timeline';
@ -14,6 +14,8 @@ import AnnotationMenuContainer from 'containers/annotation-page/top-bar/annotati
import {
MainMenuIcon, SaveIcon, UndoIcon, RedoIcon,
} from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import CVATTooltip from 'components/common/cvat-tooltip';
interface Props {
saving: boolean;
@ -23,9 +25,12 @@ interface Props {
saveShortcut: string;
undoShortcut: string;
redoShortcut: string;
drawShortcut: string;
activeControl: ActiveControl;
onSaveAnnotation(): void;
onUndoClick(): void;
onRedoClick(): void;
onFinishDraw(): void;
}
function LeftGroup(props: Props): JSX.Element {
@ -37,11 +42,22 @@ function LeftGroup(props: Props): JSX.Element {
saveShortcut,
undoShortcut,
redoShortcut,
drawShortcut,
activeControl,
onSaveAnnotation,
onUndoClick,
onRedoClick,
onFinishDraw,
} = props;
const includesDoneButton = [
ActiveControl.DRAW_POLYGON,
ActiveControl.DRAW_POLYLINE,
ActiveControl.DRAW_POINTS,
ActiveControl.AI_TOOLS,
ActiveControl.OPENCV_TOOLS,
].includes(activeControl);
return (
<Col className='cvat-annotation-header-left-group'>
<Dropdown overlay={<AnnotationMenuContainer />}>
@ -50,44 +66,53 @@ function LeftGroup(props: Props): JSX.Element {
Menu
</Button>
</Dropdown>
<Button
title={`Save current changes ${saveShortcut}`}
onClick={saving ? undefined : onSaveAnnotation}
type='link'
className={saving ? 'cvat-annotation-disabled-header-button' : 'cvat-annotation-header-button'}
>
<Icon component={SaveIcon} />
{saving ? 'Saving...' : 'Save'}
<Modal title='Saving changes on the server' visible={saving} footer={[]} closable={false}>
<Timeline pending={savingStatuses[savingStatuses.length - 1] || 'Pending..'}>
{savingStatuses.slice(0, -1).map((status: string, id: number) => (
<Timeline.Item key={id}>{status}</Timeline.Item>
))}
</Timeline>
</Modal>
</Button>
<Button
title={`Undo: ${undoAction} ${undoShortcut}`}
disabled={!undoAction}
style={{ pointerEvents: undoAction ? 'initial' : 'none', opacity: undoAction ? 1 : 0.5 }}
type='link'
className='cvat-annotation-header-button'
onClick={onUndoClick}
>
<Icon component={UndoIcon} />
<span>Undo</span>
</Button>
<Button
title={`Redo: ${redoAction} ${redoShortcut}`}
disabled={!redoAction}
style={{ pointerEvents: redoAction ? 'initial' : 'none', opacity: redoAction ? 1 : 0.5 }}
type='link'
className='cvat-annotation-header-button'
onClick={onRedoClick}
>
<Icon component={RedoIcon} />
Redo
</Button>
<CVATTooltip overlay={`Save current changes ${saveShortcut}`}>
<Button
onClick={saving ? undefined : onSaveAnnotation}
type='link'
className={saving ? 'cvat-annotation-disabled-header-button' : 'cvat-annotation-header-button'}
>
<Icon component={SaveIcon} />
{saving ? 'Saving...' : 'Save'}
<Modal title='Saving changes on the server' visible={saving} footer={[]} closable={false}>
<Timeline pending={savingStatuses[savingStatuses.length - 1] || 'Pending..'}>
{savingStatuses.slice(0, -1).map((status: string, id: number) => (
<Timeline.Item key={id}>{status}</Timeline.Item>
))}
</Timeline>
</Modal>
</Button>
</CVATTooltip>
<CVATTooltip overlay={`Undo: ${undoAction} ${undoShortcut}`}>
<Button
style={{ pointerEvents: undoAction ? 'initial' : 'none', opacity: undoAction ? 1 : 0.5 }}
type='link'
className='cvat-annotation-header-button'
onClick={onUndoClick}
>
<Icon component={UndoIcon} />
<span>Undo</span>
</Button>
</CVATTooltip>
<CVATTooltip overlay={`Redo: ${redoAction} ${redoShortcut}`}>
<Button
style={{ pointerEvents: redoAction ? 'initial' : 'none', opacity: redoAction ? 1 : 0.5 }}
type='link'
className='cvat-annotation-header-button'
onClick={onRedoClick}
>
<Icon component={RedoIcon} />
Redo
</Button>
</CVATTooltip>
{includesDoneButton ? (
<CVATTooltip overlay={`Press "${drawShortcut}" to finish`}>
<Button type='link' className='cvat-annotation-header-button' onClick={onFinishDraw}>
<CheckOutlined />
Done
</Button>
</CVATTooltip>
) : null}
</Col>
);
}

@ -6,7 +6,7 @@ import React from 'react';
import Input from 'antd/lib/input';
import { Col, Row } from 'antd/lib/grid';
import { PredictorState, Workspace } from 'reducers/interfaces';
import { ActiveControl, PredictorState, Workspace } from 'reducers/interfaces';
import LeftGroup from './left-group';
import PlayerButtons from './player-buttons';
import PlayerNavigation from './player-navigation';
@ -27,6 +27,7 @@ interface Props {
saveShortcut: string;
undoShortcut: string;
redoShortcut: string;
drawShortcut: string;
playPauseShortcut: string;
nextFrameShortcut: string;
previousFrameShortcut: string;
@ -37,6 +38,7 @@ interface Props {
focusFrameInputShortcut: string;
predictor: PredictorState;
isTrainingActive: boolean;
activeControl: ActiveControl;
changeWorkspace(workspace: Workspace): void;
switchPredictor(predictorEnabled: boolean): void;
showStatistics(): void;
@ -56,6 +58,7 @@ interface Props {
onURLIconClick(): void;
onUndoClick(): void;
onRedoClick(): void;
onFinishDraw(): void;
jobInstance: any;
}
@ -75,6 +78,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
saveShortcut,
undoShortcut,
redoShortcut,
drawShortcut,
playPauseShortcut,
nextFrameShortcut,
previousFrameShortcut,
@ -84,6 +88,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
nextButtonType,
predictor,
focusFrameInputShortcut,
activeControl,
showStatistics,
switchPredictor,
showFilters,
@ -103,6 +108,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
onURLIconClick,
onUndoClick,
onRedoClick,
onFinishDraw,
jobInstance,
isTrainingActive,
} = props;
@ -117,9 +123,12 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
saveShortcut={saveShortcut}
undoShortcut={undoShortcut}
redoShortcut={redoShortcut}
activeControl={activeControl}
drawShortcut={drawShortcut}
onSaveAnnotation={onSaveAnnotation}
onUndoClick={onUndoClick}
onRedoClick={onRedoClick}
onFinishDraw={onFinishDraw}
/>
<Col className='cvat-annotation-header-player-group'>
<Row align='middle'>

@ -30,7 +30,7 @@ import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-ba
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import {
CombinedState, FrameSpeed, Workspace, PredictorState, DimensionType,
CombinedState, FrameSpeed, Workspace, PredictorState, DimensionType, ActiveControl,
} from 'reducers/interfaces';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
@ -55,6 +55,7 @@ interface StateToProps {
canvasInstance: Canvas | Canvas3d;
forceExit: boolean;
predictor: PredictorState;
activeControl: ActiveControl;
isTrainingActive: boolean;
}
@ -85,7 +86,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
history,
},
job: { instance: jobInstance },
canvas: { ready: canvasIsReady, instance: canvasInstance },
canvas: { ready: canvasIsReady, instance: canvasInstance, activeControl },
workspace,
predictor,
},
@ -118,6 +119,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
canvasInstance,
forceExit,
predictor,
activeControl,
isTrainingActive: list.PREDICT,
};
}
@ -416,6 +418,19 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
}
};
private onFinishDraw = (): void => {
const { activeControl, canvasInstance } = this.props;
if (
[ActiveControl.AI_TOOLS, ActiveControl.OPENCV_TOOLS].includes(activeControl) &&
canvasInstance instanceof Canvas
) {
canvasInstance.interact({ enabled: false });
return;
}
canvasInstance.draw({ enabled: false });
};
private onURLIconClick = (): void => {
const { frameNumber } = this.props;
const { origin, pathname } = window.location;
@ -526,10 +541,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
normalizedKeyMap,
canvasInstance,
predictor,
isTrainingActive,
activeControl,
searchAnnotations,
changeWorkspace,
switchPredictor,
isTrainingActive,
} = this.props;
const preventDefault = (event: KeyboardEvent | undefined): void => {
@ -655,6 +671,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
saveShortcut={normalizedKeyMap.SAVE_JOB}
undoShortcut={normalizedKeyMap.UNDO}
redoShortcut={normalizedKeyMap.REDO}
drawShortcut={normalizedKeyMap.SWITCH_DRAW_MODE}
playPauseShortcut={normalizedKeyMap.PLAY_PAUSE}
nextFrameShortcut={normalizedKeyMap.NEXT_FRAME}
previousFrameShortcut={normalizedKeyMap.PREV_FRAME}
@ -665,8 +682,10 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
focusFrameInputShortcut={normalizedKeyMap.FOCUS_INPUT_FRAME}
onUndoClick={this.undo}
onRedoClick={this.redo}
onFinishDraw={this.onFinishDraw}
jobInstance={jobInstance}
isTrainingActive={isTrainingActive}
activeControl={activeControl}
/>
</>
);

@ -22,7 +22,7 @@ export function convertShapesForInteractor(shapes: InteractionResult[], button:
};
return shapes
.filter((shape: InteractionResult): boolean => shape.shapeType === 'points' && shape.button === button)
.filter((shape: InteractionResult): boolean => shape.button === button)
.map((shape: InteractionResult): number[] => shape.points)
.flat()
.reduce(reducer, []);

@ -111,6 +111,7 @@ class LambdaFunction:
# display name for the function
self.name = meta_anno.get('name', self.id)
self.min_pos_points = int(meta_anno.get('min_pos_points', 1))
self.min_neg_points = int(meta_anno.get('min_neg_points', -1))
self.startswith_box = bool(meta_anno.get('startswith_box', False))
self.gateway = gateway
@ -127,6 +128,7 @@ class LambdaFunction:
if self.kind is LambdaType.INTERACTOR:
response.update({
'min_pos_points': self.min_pos_points,
'min_neg_points': self.min_neg_points,
'startswith_box': self.startswith_box
})
@ -166,8 +168,9 @@ class LambdaFunction:
elif self.kind == LambdaType.INTERACTOR:
payload.update({
"image": self._get_image(db_task, data["frame"], quality),
"pos_points": data["pos_points"],
"neg_points": data["neg_points"]
"pos_points": data["pos_points"][2:] if self.startswith_box else data["pos_points"],
"neg_points": data["neg_points"],
"obj_bbox": data["pos_points"][0:2] if self.startswith_box else None
})
elif self.kind == LambdaType.REID:
payload.update({

@ -7,6 +7,7 @@ metadata:
spec:
framework: pytorch
min_pos_points: 1
min_neg_points: 0
spec:
description: f-BRS interactive segmentation

@ -7,6 +7,7 @@ metadata:
spec:
framework: pytorch
min_pos_points: 1
min_neg_points: 0
startswith_box: true
spec:

@ -50,10 +50,10 @@ class ModelHandler:
# extract a crop with padding from the image
crop_padding = 30
crop_bbox = [
max(bbox[0] - crop_padding, 0),
max(bbox[1] - crop_padding, 0),
min(bbox[2] + crop_padding, image.width - 1),
min(bbox[3] + crop_padding, image.height - 1)
max(bbox[0][0] - crop_padding, 0),
max(bbox[0][1] - crop_padding, 0),
min(bbox[1][0] + crop_padding, image.width - 1),
min(bbox[1][1] + crop_padding, image.height - 1)
]
crop_shape = (
int(crop_bbox[2] - crop_bbox[0] + 1), # width

@ -135,7 +135,7 @@ context('Object make a copy.', () => {
.rightclick('right'); // When click in the center of polyline: is being covered by another element: <svg xmlns="http://www.w3.org/2000/svg" ...
} else {
cy.get(`#cvat_canvas_shape_${id}`)
.trigger('mousemove')
.trigger('mousemove', 'right')
.should('have.class', 'cvat_canvas_shape_activated')
.rightclick();
}
@ -192,12 +192,15 @@ context('Object make a copy.', () => {
it('Copy a shape with holding "Ctrl".', () => {
const keyCodeC = 67;
const keyCodeV = 86;
cy.get('.cvat_canvas_shape').first().trigger('mousemove').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').type('{ctrl}', {release: false}); // Hold
cy.get('.cvat_canvas_shape')
.first()
.trigger('mousemove')
.should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').type('{ctrl}', { release: false }); // Hold
cy.get('body')
.trigger('keydown', {keyCode: keyCodeC, ctrlKey: true})
.trigger('keydown', { keyCode: keyCodeC, ctrlKey: true })
.trigger('keyup')
.trigger('keydown', {keyCode: keyCodeV, ctrlKey: true})
.trigger('keydown', { keyCode: keyCodeV, ctrlKey: true })
.trigger('keyup');
cy.get('.cvat-canvas-container').click(400, 300);
cy.get('.cvat-canvas-container').click(500, 300);

@ -131,7 +131,7 @@ context('Redraw feature.', () => {
it('Draw and redraw a cuboid.', () => {
cy.createCuboid(createCuboidShape2Points);
cy.get('.cvat-canvas-container').trigger('mousemove', 300, 400);
cy.get('.cvat-canvas-container').trigger('mousemove', 350, 400);
cy.get('#cvat_canvas_shape_5').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the cuboid
cy.get('.cvat-canvas-container')

Loading…
Cancel
Save