Skip to content

Commit

Permalink
Crop polygon properly (#3025)
Browse files Browse the repository at this point in the history
* crop polygon properly

* updated license headers and cvat-canvas version

* updated changelog

* fixed eslint errors

* fixed eslint issues

Co-authored-by: Boris Sekachev <boris.sekachev@intel.com>
  • Loading branch information
Andrey Zhavoronkov and Boris Sekachev authored Mar 30, 2021
1 parent f5277f9 commit 5051249
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed image quality option for tasks created from images (<https://github.com/openvinotoolkit/cvat/pull/2963>)
- Incorrect text on the warning when specifying an incorrect link to the issue tracker (<https://github.com/openvinotoolkit/cvat/pull/2971>)
- Updating label attributes when label contains number attributes (<https://github.com/openvinotoolkit/cvat/pull/2969>)
- Crop a polygon if its points are outside the bounds of the image (<https://github.com/openvinotoolkit/cvat/pull/3025>)

### Security

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.0",
"version": "2.4.1",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {
Expand Down
21 changes: 10 additions & 11 deletions cvat-canvas/src/typescript/cuboid.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import consts from './consts';
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

export interface Point {
x: number;
y: number;
}
import consts from './consts';
import { Point } from './shared';

export enum Orientation {
LEFT = 'left',
Expand All @@ -17,7 +17,7 @@ function line(p1: Point, p2: Point): number[] {
return [a, b, c];
}

function intersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null {
export function intersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null {
const L1 = line(p1, p2);
const L2 = line(p3, p4);

Expand All @@ -27,7 +27,7 @@ function intersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null

let x = null;
let y = null;
if (D !== 0) {
if (Math.abs(D) > Number.EPSILON) {
x = Dx / D;
y = Dy / D;
return { x, y };
Expand Down Expand Up @@ -348,10 +348,9 @@ function setupCuboidPoints(points: Point[]): any[] {
let p3;
let p4;

const height =
Math.abs(points[0].x - points[1].x) < Math.abs(points[1].x - points[2].x)
? Math.abs(points[1].y - points[0].y)
: Math.abs(points[1].y - points[2].y);
const height = Math.abs(points[0].x - points[1].x) < Math.abs(points[1].x - points[2].x)
? Math.abs(points[1].y - points[0].y)
: Math.abs(points[1].y - points[2].y);

// seperate into left and right point
// we pick the first and third point because we know assume they will be on
Expand Down
147 changes: 115 additions & 32 deletions cvat-canvas/src/typescript/drawHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand All @@ -15,12 +15,15 @@ import {
pointsToNumberArray,
BBox,
Box,
Point,
} from './shared';
import Crosshair from './crosshair';
import consts from './consts';
import { DrawData, Geometry, RectDrawingMethod, Configuration, CuboidDrawingMethod } from './canvasModel';
import {
DrawData, Geometry, RectDrawingMethod, Configuration, CuboidDrawingMethod,
} from './canvasModel';

import { cuboidFrom4Points } from './cuboid';
import { cuboidFrom4Points, intersection } from './cuboid';

export interface DrawHandler {
configurate(configuration: Configuration): void;
Expand Down Expand Up @@ -73,11 +76,11 @@ export class DrawHandlerImpl implements DrawHandler {
private getFinalPolyshapeCoordinates(
targetPoints: number[],
): {
points: number[];
box: Box;
} {
points: number[];
box: Box;
} {
const { offset } = this.geometry;
const points = targetPoints.map((coord: number): number => coord - offset);
let points = targetPoints.map((coord: number): number => coord - offset);
const box = {
xtl: Number.MAX_SAFE_INTEGER,
ytl: Number.MAX_SAFE_INTEGER,
Expand All @@ -87,10 +90,91 @@ export class DrawHandlerImpl implements DrawHandler {

const frameWidth = this.geometry.image.width;
const frameHeight = this.geometry.image.height;
for (let i = 0; i < points.length - 1; i += 2) {
points[i] = Math.min(Math.max(points[i], 0), frameWidth);
points[i + 1] = Math.min(Math.max(points[i + 1], 0), frameHeight);

enum Direction {
Horizontal,
Vertical,
}

const isBetween = (x1: number, x2: number, c: number): boolean => (
c >= Math.min(x1, x2) && c <= Math.max(x1, x2)
);

const isInsideFrame = (p: Point, direction: Direction): boolean => {
if (direction === Direction.Horizontal) {
return isBetween(0, frameWidth, p.x);
}
return isBetween(0, frameHeight, p.y);
};

const findInersection = (p1: Point, p2: Point, p3: Point, p4: Point): number[] => {
const intersectionPoint = intersection(p1, p2, p3, p4);
if (
intersectionPoint
&& isBetween(p1.x, p2.x, intersectionPoint.x)
&& isBetween(p1.y, p2.y, intersectionPoint.y)
) {
return [intersectionPoint.x, intersectionPoint.y];
}
return [];
};

const findIntersectionsWithFrameBorders = (p1: Point, p2: Point, direction: Direction): number[] => {
const resultPoints = [];
if (direction === Direction.Horizontal) {
resultPoints.push(...findInersection(p1, p2, { x: 0, y: 0 }, { x: 0, y: frameHeight }));
resultPoints.push(
...findInersection(p1, p2, { x: frameWidth, y: frameHeight }, { x: frameWidth, y: 0 }),
);
} else {
resultPoints.push(
...findInersection(p1, p2, { x: 0, y: frameHeight }, { x: frameWidth, y: frameHeight }),
);
resultPoints.push(...findInersection(p1, p2, { x: frameWidth, y: 0 }, { x: 0, y: 0 }));
}

if (resultPoints.length === 4) {
if (
Math.sign(resultPoints[0] - resultPoints[2]) !== Math.sign(p1.x - p2.x)
&& Math.sign(resultPoints[1] - resultPoints[3]) !== Math.sign(p1.y - p2.y)
) {
[resultPoints[0], resultPoints[2]] = [resultPoints[2], resultPoints[0]];
[resultPoints[1], resultPoints[3]] = [resultPoints[3], resultPoints[1]];
}
}
return resultPoints;
};

const crop = (polygonPoints: number[], direction: Direction): number[] => {
const resultPoints = [];
for (let i = 0; i < polygonPoints.length - 1; i += 2) {
const curPoint = { x: polygonPoints[i], y: polygonPoints[i + 1] };
if (isInsideFrame(curPoint, direction)) {
resultPoints.push(polygonPoints[i], polygonPoints[i + 1]);
}
const isLastPoint = i === polygonPoints.length - 2;
if (
isLastPoint
&& (this.drawData.shapeType === 'polyline'
|| (this.drawData.shapeType === 'polygon' && polygonPoints.length === 4))
) {
break;
}
const nextPoint = isLastPoint
? { x: polygonPoints[0], y: polygonPoints[1] }
: { x: polygonPoints[i + 2], y: polygonPoints[i + 3] };
const intersectionPoints = findIntersectionsWithFrameBorders(curPoint, nextPoint, direction);
if (intersectionPoints.length !== 0) {
resultPoints.push(...intersectionPoints);
}
}
return resultPoints;
};

points = crop(points, Direction.Horizontal);
points = crop(points, Direction.Vertical);

for (let i = 0; i < points.length - 1; i += 2) {
box.xtl = Math.min(box.xtl, points[i]);
box.ytl = Math.min(box.ytl, points[i + 1]);
box.xbr = Math.max(box.xbr, points[i]);
Expand All @@ -106,9 +190,9 @@ export class DrawHandlerImpl implements DrawHandler {
private getFinalCuboidCoordinates(
targetPoints: number[],
): {
points: number[];
box: Box;
} {
points: number[];
box: Box;
} {
const { offset } = this.geometry;
let points = targetPoints;

Expand Down Expand Up @@ -154,8 +238,8 @@ export class DrawHandlerImpl implements DrawHandler {

if (cuboidOffsets.length === points.length / 2) {
cuboidOffsets.forEach((offsetCoords: number[]): void => {
if (Math.sqrt(offsetCoords[0] ** 2 + offsetCoords[1] ** 2) < minCuboidOffset.d) {
minCuboidOffset.d = Math.sqrt(offsetCoords[0] ** 2 + offsetCoords[1] ** 2);
if (Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2)) < minCuboidOffset.d) {
minCuboidOffset.d = Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2));
[minCuboidOffset.dx, minCuboidOffset.dy] = offsetCoords;
}
});
Expand Down Expand Up @@ -213,8 +297,8 @@ export class DrawHandlerImpl implements DrawHandler {
// We check if it is activated with remember function
if (this.drawInstance.remember('_paintHandler')) {
if (
this.drawData.shapeType !== 'rectangle' &&
this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC
this.drawData.shapeType !== 'rectangle'
&& this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC
) {
// Check for unsaved drawn shapes
this.drawInstance.draw('done');
Expand Down Expand Up @@ -365,7 +449,8 @@ export class DrawHandlerImpl implements DrawHandler {
} else {
this.drawInstance.draw('update', e);
const deltaTreshold = 15;
const delta = Math.sqrt((e.clientX - lastDrawnPoint.x) ** 2 + (e.clientY - lastDrawnPoint.y) ** 2);
const delta = Math.sqrt(((e.clientX - lastDrawnPoint.x) ** 2)
+ ((e.clientY - lastDrawnPoint.y) ** 2));
if (delta > deltaTreshold) {
this.drawInstance.draw('point', e);
}
Expand All @@ -386,17 +471,16 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points'));
const { shapeType, redraw: clientID } = this.drawData;
const { points, box } =
shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
const { points, box } = shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
this.release();

if (this.canceled) return;
if (
shapeType === 'polygon' &&
(box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD &&
points.length >= 3 * 2
shapeType === 'polygon'
&& (box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD
&& points.length >= 3 * 2
) {
this.onDrawDone(
{
Expand All @@ -407,9 +491,9 @@ export class DrawHandlerImpl implements DrawHandler {
Date.now() - this.startTimestamp,
);
} else if (
shapeType === 'polyline' &&
(box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD) &&
points.length >= 2 * 2
shapeType === 'polyline'
&& (box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD)
&& points.length >= 2 * 2
) {
this.onDrawDone(
{
Expand Down Expand Up @@ -527,10 +611,9 @@ export class DrawHandlerImpl implements DrawHandler {
.split(/[,\s]/g)
.map((coord: string): number => +coord);

const { points } =
this.drawData.initialState.shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
const { points } = this.drawData.initialState.shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);

if (!e.detail.originalEvent.ctrlKey) {
this.release();
Expand Down
6 changes: 3 additions & 3 deletions cvat-canvas/src/typescript/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand All @@ -25,7 +25,7 @@ export interface BBox {
y: number;
}

interface Point {
export interface Point {
x: number;
y: number;
}
Expand Down Expand Up @@ -176,5 +176,5 @@ export function scalarProduct(a: Vector2D, b: Vector2D): number {
}

export function vectorLength(vector: Vector2D): number {
return Math.sqrt(vector.i ** 2 + vector.j ** 2);
return Math.sqrt((vector.i ** 2) + (vector.j ** 2));
}

0 comments on commit 5051249

Please sign in to comment.