Skip to content

Commit

Permalink
Semi-automatic tools enhancements (Stage 0) (#3417)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Boris Sekachev committed Jul 20, 2021
1 parent bab3366 commit 330b8a8
Show file tree
Hide file tree
Showing 31 changed files with 296 additions and 264 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ 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

- Updated manifest format, added meta with related images (<https://github.com/openvinotoolkit/cvat/pull/3122>)
- 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

Expand Down
2 changes: 1 addition & 1 deletion cvat-canvas/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-canvas/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
11 changes: 11 additions & 0 deletions cvat-canvas/src/scss/canvas.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions cvat-canvas/src/typescript/canvasModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 7 additions & 4 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import consts from './consts';
import {
translateToSVG,
translateFromSVG,
translateToCanvas,
pointsToNumberArray,
parsePoints,
displayShapeSize,
Expand Down Expand Up @@ -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[] {
Expand Down Expand Up @@ -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 = '';
Expand Down
88 changes: 76 additions & 12 deletions cvat-canvas/src/typescript/interactionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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 => {
Expand All @@ -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);
Expand All @@ -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');
Expand All @@ -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) {
Expand All @@ -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({
Expand All @@ -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();
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -334,16 +387,27 @@ 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);
}
}
}

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();
Expand Down
6 changes: 6 additions & 0 deletions cvat-canvas/src/typescript/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
3 changes: 2 additions & 1 deletion cvat-core/src/ml-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
}
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/src/assets/fullscreen-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion cvat-ui/src/assets/info-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion cvat-ui/src/assets/main-menu-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion cvat-ui/src/assets/object-filter-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion cvat-ui/src/assets/redo-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 330b8a8

Please sign in to comment.