Skip to content

Commit

Permalink
Added support of ellipses (#4062)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Sekachev authored Jan 12, 2022
1 parent 2cd7a38 commit b85a4ad
Show file tree
Hide file tree
Showing 45 changed files with 839 additions and 283 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- User is able to customize information that text labels show (<https://github.com/openvinotoolkit/cvat/pull/4029>)
- Support for uploading manifest with any name (<https://github.com/openvinotoolkit/cvat/pull/4041>)
- Added information about OpenVINO toolkit to login page (<https://github.com/openvinotoolkit/cvat/pull/4077>)
- Support for working with ellipses (<https://github.com/openvinotoolkit/cvat/pull/4062>)

### Changed
- Users don't have access to a task object anymore if they are assigneed only on some jobs of the task (<https://github.com/openvinotoolkit/cvat/pull/3788>)
Expand Down
4 changes: 2 additions & 2 deletions 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.11.1",
"version": "2.12.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions cvat-canvas/src/typescript/autoborderHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
let points = '';
if (shape.tagName === 'polyline' || shape.tagName === 'polygon') {
points = shape.getAttribute('points');
} else if (shape.tagName === 'ellipse') {
const cx = +shape.getAttribute('cx');
const cy = +shape.getAttribute('cy');
points = `${cx},${cy}`;
} else if (shape.tagName === 'rect') {
const x = +shape.getAttribute('x');
const y = +shape.getAttribute('y');
Expand Down
102 changes: 76 additions & 26 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
translateToSVG,
translateFromSVG,
translateToCanvas,
translateFromCanvas,
pointsToNumberArray,
parsePoints,
displayShapeSize,
Expand All @@ -33,6 +34,7 @@ import {
ShapeSizeElement,
DrawnState,
rotate2DPoints,
readPointsFromShape,
} from './shared';
import {
CanvasModel,
Expand Down Expand Up @@ -88,7 +90,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private activeElement: ActiveElement;
private configuration: Configuration;
private snapToAngleResize: number;
private serviceFlags: {
private innerObjectsFlags: {
drawHidden: Record<number, boolean>;
};

Expand All @@ -112,7 +114,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

private translateFromCanvas(points: number[]): number[] {
const { offset } = this.controller.geometry;
return points.map((coord: number): number => coord - offset);
return translateFromCanvas(offset, points);
}

private translatePointsFromRotatedShape(shape: SVG.Shape, points: number[]): number[] {
Expand Down Expand Up @@ -158,12 +160,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
}, '');
}

private isServiceHidden(clientID: number): boolean {
return this.serviceFlags.drawHidden[clientID] || false;
private isInnerHidden(clientID: number): boolean {
return this.innerObjectsFlags.drawHidden[clientID] || false;
}

private setupServiceHidden(clientID: number, value: boolean): void {
this.serviceFlags.drawHidden[clientID] = value;
private setupInnerFlags(clientID: number, path: 'drawHidden', value: boolean): void {
this.innerObjectsFlags[path][clientID] = value;
const shape = this.svgShapes[clientID];
const text = this.svgTexts[clientID];
const state = this.drawnStates[clientID];
Expand All @@ -179,7 +181,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
text.addClass('cvat_canvas_hidden');
}
} else {
delete this.serviceFlags.drawHidden[clientID];
delete this.innerObjectsFlags[path][clientID];

if (state) {
if (!state.outside && !state.hidden) {
Expand Down Expand Up @@ -236,10 +238,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
}

private onDrawDone(data: any | null, duration: number, continueDraw?: boolean): void {
const hiddenBecauseOfDraw = Object.keys(this.serviceFlags.drawHidden).map((_clientID): number => +_clientID);
const hiddenBecauseOfDraw = Object.keys(this.innerObjectsFlags.drawHidden)
.map((_clientID): number => +_clientID);
if (hiddenBecauseOfDraw.length) {
for (const hidden of hiddenBecauseOfDraw) {
this.setupServiceHidden(hidden, false);
this.setupInnerFlags(hidden, 'drawHidden', false);
}
}

Expand Down Expand Up @@ -867,7 +870,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
(shape as any).selectize(value, {
deepSelect: true,
pointSize: (2 * consts.BASE_POINT_SIZE) / this.geometry.scale,
rotationPoint: shape.type === 'rect',
rotationPoint: shape.type === 'rect' || shape.type === 'ellipse',
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
.circle(this.options.pointSize)
Expand Down Expand Up @@ -967,7 +970,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.configuration = model.configuration;
this.mode = Mode.IDLE;
this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_DEFAULT;
this.serviceFlags = {
this.innerObjectsFlags = {
drawHidden: {},
};

Expand Down Expand Up @@ -1367,7 +1370,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.canvas.style.cursor = 'crosshair';
this.mode = Mode.DRAW;
if (typeof data.redraw === 'number') {
this.setupServiceHidden(data.redraw, true);
this.setupInnerFlags(data.redraw, 'drawHidden', true);
}
this.drawHandler.draw(data, this.geometry);
} else {
Expand Down Expand Up @@ -1544,6 +1547,14 @@ export class CanvasViewImpl implements CanvasView, Listener {
ctx.fill();
}

if (state.shapeType === 'ellipse') {
const [cx, cy, rightX, topY] = state.points;
ctx.beginPath();
ctx.ellipse(cx, cy, rightX - cx, cy - topY, (state.rotation * Math.PI) / 180.0, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}

if (state.shapeType === 'cuboid') {
for (let i = 0; i < 5; i++) {
const points = [
Expand Down Expand Up @@ -1596,7 +1607,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
const drawnState = this.drawnStates[clientID];
const shape = this.svgShapes[state.clientID];
const text = this.svgTexts[state.clientID];
const isInvisible = state.hidden || state.outside || this.isServiceHidden(state.clientID);
const isInvisible = state.hidden || state.outside || this.isInnerHidden(state.clientID);

if (drawnState.hidden !== state.hidden || drawnState.outside !== state.outside) {
if (isInvisible) {
Expand Down Expand Up @@ -1659,6 +1670,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
width: xbr - xtl,
height: ybr - ytl,
});
} else if (state.shapeType === 'ellipse') {
const [cx, cy] = translatedPoints;
const [rx, ry] = [translatedPoints[2] - cx, cy - translatedPoints[3]];
shape.attr({
cx, cy, rx, ry,
});
} else {
const stringified = this.stringifyToCanvas(translatedPoints);
if (state.shapeType !== 'cuboid') {
Expand Down Expand Up @@ -1728,6 +1745,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.svgShapes[state.clientID] = this.addPolyline(stringified, state);
} else if (state.shapeType === 'points') {
this.svgShapes[state.clientID] = this.addPoints(stringified, state);
} else if (state.shapeType === 'ellipse') {
this.svgShapes[state.clientID] = this.addEllipse(stringified, state);
} else if (state.shapeType === 'cuboid') {
this.svgShapes[state.clientID] = this.addCuboid(stringified, state);
} else {
Expand Down Expand Up @@ -1933,10 +1952,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (Math.sqrt(dx2 + dy2) >= delta) {
// these points does not take into account possible transformations, applied on the element
// so, if any (like rotation) we need to map them to canvas coordinate space
let points = pointsToNumberArray(
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`,
);
let points = readPointsFromShape(shape);

// let's keep current points, but they could be rewritten in updateObjects
this.drawnStates[clientID].points = this.translateFromCanvas(points);
Expand Down Expand Up @@ -2021,10 +2037,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

// these points does not take into account possible transformations, applied on the element
// so, if any (like rotation) we need to map them to canvas coordinate space
let points = pointsToNumberArray(
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`,
);
let points = readPointsFromShape(shape);

// let's keep current points, but they could be rewritten in updateObjects
this.drawnStates[clientID].points = this.translateFromCanvas(points);
Expand Down Expand Up @@ -2101,6 +2114,10 @@ export class CanvasViewImpl implements CanvasView, Listener {
// for rectangle finding a center is simple
cx = +shape.attr('x') + +shape.attr('width') / 2;
cy = +shape.attr('y') + +shape.attr('height') / 2;
} else if (shape.type === 'ellipse') {
// even simpler for ellipses
cx = +shape.attr('cx');
cy = +shape.attr('cy');
} else {
// for polyshapes we use special algorithm
const points = parsePoints(pointsToNumberArray(shape.attr('points')));
Expand Down Expand Up @@ -2247,7 +2264,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
rect.addClass('cvat_canvas_shape_occluded');
}

if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) {
rect.addClass('cvat_canvas_hidden');
}

Expand All @@ -2273,7 +2290,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
polygon.addClass('cvat_canvas_shape_occluded');
}

if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) {
polygon.addClass('cvat_canvas_hidden');
}

Expand All @@ -2299,7 +2316,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
polyline.addClass('cvat_canvas_shape_occluded');
}

if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) {
polyline.addClass('cvat_canvas_hidden');
}

Expand All @@ -2326,7 +2343,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
cube.addClass('cvat_canvas_shape_occluded');
}

if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) {
cube.addClass('cvat_canvas_hidden');
}

Expand Down Expand Up @@ -2359,6 +2376,39 @@ export class CanvasViewImpl implements CanvasView, Listener {
return group;
}

private addEllipse(points: string, state: any): SVG.Rect {
const [cx, cy, rightX, topY] = points.split(/[/,\s]/g).map((coord) => +coord);
const [rx, ry] = [rightX - cx, cy - topY];
const rect = this.adoptedContent
.ellipse(rx * 2, ry * 2)
.attr({
clientID: state.clientID,
'color-rendering': 'optimizeQuality',
id: `cvat_canvas_shape_${state.clientID}`,
fill: state.color,
'shape-rendering': 'geometricprecision',
stroke: state.color,
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'data-z-order': state.zOrder,
})
.center(cx, cy)
.addClass('cvat_canvas_shape');

if (state.rotation) {
rect.rotate(state.rotation);
}

if (state.occluded) {
rect.addClass('cvat_canvas_shape_occluded');
}

if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) {
rect.addClass('cvat_canvas_hidden');
}

return rect;
}

private addPoints(points: string, state: any): SVG.PolyLine {
const shape = this.adoptedContent
.polyline(points)
Expand All @@ -2375,7 +2425,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

const group = this.setupPoints(shape, state);

if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) {
group.addClass('cvat_canvas_hidden');
}

Expand Down
Loading

0 comments on commit b85a4ad

Please sign in to comment.