Skip to content

Commit

Permalink
Add absent skeleton elements automatically in cvat-core (#7302)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsekachev committed Jan 3, 2024
1 parent 56f37ac commit 397d006
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 190 deletions.
8 changes: 8 additions & 0 deletions changelog.d/20240103_112843_boris_skeletons_autofill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Fixed

- Error message `Edge's nodeFrom ${dataNodeFrom} or nodeTo ${dataNodeTo} do not to refer to any node`
when upload a file with some abscent skeleton nodes (<https://github.com/opencv/cvat/pull/7302>)
- Wrong context menu position in skeleton configurator (Firefox only)
(<https://github.com/opencv/cvat/pull/7302>)
- Fixed console error `(Error: <rect> attribute width: A negative value is not valid`
appearing when skeleton with all outside elements is created (<https://github.com/opencv/cvat/pull/7302>)
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.19.0",
"version": "2.19.1",
"type": "module",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
Expand Down
21 changes: 12 additions & 9 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3070,11 +3070,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

const SVGElement = makeSVGFromTemplate(state.label.structure.svg);

let xtl = Number.MAX_SAFE_INTEGER;
let ytl = Number.MAX_SAFE_INTEGER;
let xbr = Number.MIN_SAFE_INTEGER;
let ybr = Number.MIN_SAFE_INTEGER;

let [xtl, ytl, xbr, ybr] = [null, null, null, null];
const svgElements: Record<number, SVG.Element> = {};
const templateElements = Array.from(SVGElement.children()).filter((el: SVG.Element) => el.type === 'circle');
for (let i = 0; i < state.elements.length; i++) {
Expand All @@ -3084,10 +3080,10 @@ export class CanvasViewImpl implements CanvasView, Listener {
const [cx, cy] = this.translateToCanvas(points);

if (!element.outside) {
xtl = Math.min(xtl, cx);
ytl = Math.min(ytl, cy);
xbr = Math.max(xbr, cx);
ybr = Math.max(ybr, cy);
xtl = xtl === null ? cx : Math.min(xtl, cx);
ytl = ytl === null ? cy : Math.min(ytl, cy);
xbr = xbr === null ? cx : Math.max(xbr, cx);
ybr = ybr === null ? cy : Math.max(ybr, cy);
}

const templateElement = templateElements.find((el: SVG.Circle) => el.attr('data-label-id') === element.label.id);
Expand Down Expand Up @@ -3182,6 +3178,13 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}

// if all elements were outside, set coordinates to zeros
xtl = xtl || 0;
ytl = ytl || 0;
xbr = xbr || 0;
ybr = ybr || 0;

// apply bounding box margin
xtl -= consts.SKELETON_RECT_MARGIN;
ytl -= consts.SKELETON_RECT_MARGIN;
xbr += consts.SKELETON_RECT_MARGIN;
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "14.0.2",
"version": "14.0.3",
"type": "module",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "src/api.ts",
Expand Down
100 changes: 78 additions & 22 deletions cvat-core/src/annotations-objects.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (C) 2019-2022 Intel Corporation
// Copyright (C) 2022-2023 CVAT.ai Corporation
// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -1856,22 +1856,44 @@ export class SkeletonShape extends Shape {
this.pinned = false;
this.rotation = 0;
this.occluded = false;
this.points = undefined;
this.points = [];
this.readOnlyFields = ['points', 'label', 'occluded'];

/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
this.elements = data.elements.map((element) => shapeFactory({
...element,
group: this.group,
z_order: this.zOrder,
source: this.source,
rotation: 0,
frame: data.frame,
}, injection.nextClientID(), {
...injection,
parentID: this.clientID,
readOnlyFields: ['group', 'zOrder', 'source', 'rotation'],
})) as any as Shape[];
const [cx, cy] = data.elements.reduce((acc, element, idx) => {
const result = [acc[0] + element.points[0], acc[1] + element.points[1]];
if (idx === data.elements.length - 1) {
// length can not be 0 because we are inside reduce
result[0] /= data.elements.length;
result[1] /= data.elements.length;
}
return result;
}, [0, 0]);

this.elements = this.label.structure.sublabels.map((sublabel: Label) => {
const element = data.elements.find((_element) => _element.label_id === sublabel.id);
const elementData = element || {
label_id: sublabel.id,
attributes: [],
occluded: false,
outside: true,
points: [cx, cy],
type: sublabel.type as unknown as ShapeType,
};

/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
return shapeFactory({
...elementData,
group: this.group,
z_order: this.zOrder,
source: this.source,
rotation: 0,
frame: data.frame,
}, injection.nextClientID(), {
...injection,
parentID: this.clientID,
readOnlyFields: ['group', 'zOrder', 'source', 'rotation'],
});
});
}

static distance(points: number[], x: number, y: number): number {
Expand Down Expand Up @@ -2766,20 +2788,54 @@ export class SkeletonTrack extends Track {
this.shapeType = ShapeType.SKELETON;
this.readOnlyFields = ['points', 'label', 'occluded', 'outside'];
this.pinned = false;
this.elements = data.elements.map((element: SerializedTrack['elements'][0]) => (

const [cx, cy] = data.elements.reduce((acc, element, idx) => {
const shape = element.shapes[0];
if (!shape || shape.frame !== this.frame) {
return acc;
}

const result = [acc[0] + shape.points[0], acc[1] + shape.points[1], acc[2] + 1];
if (idx === data.elements.length - 1) {
// avoid division by 0, additionally
return [result[0] / (result[2] || 1), result[1] / (result[2] || 1)];
}

return result;
}, [0, 0, 0]);

this.elements = this.label.structure.sublabels.map((sublabel) => {
const element = data.elements.find((_element) => _element.label_id === sublabel.id);
const elementData = element || {
label_id: sublabel.id,
frame: this.frame,
attributes: [],
shapes: [{
attributes: [],
points: [cx, cy],
frame: this.frame,
occluded: false,
outside: true,
rotation: 0,
type: sublabel.type as unknown as ShapeType,
}],
};

/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
trackFactory({
...element,
return trackFactory({
...elementData,
group: this.group,
source: this.source,
shapes: elementData.shapes.map((shape) => ({
...shape,
z_order: this.shapes[shape.frame]?.zOrder || 0,
})),
}, injection.nextClientID(), {
...injection,
parentID: this.clientID,
readOnlyFields: ['group', 'zOrder', 'source', 'rotation'],
})

// todo z_order: this.zOrder,
)).sort((a: Annotation, b: Annotation) => a.label.id - b.label.id) as any as Track[];
});
}).sort((a: Annotation, b: Annotation) => a.label.id - b.label.id);
}

public updateServerID(body: SerializedTrack): void {
Expand Down
6 changes: 3 additions & 3 deletions cvat-core/src/labels.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright (C) 2019-2022 Intel Corporation
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import {
AttrInputType, LabelType, SerializedAttribute, SerializedLabel,
AttrInputType, SerializedAttribute, SerializedLabel,
} from './server-response-types';
import { ShapeType, AttributeType } from './enums';
import { ShapeType, AttributeType, LabelType } from './enums';
import { ArgumentError } from './exceptions';

export class Attribute {
Expand Down
5 changes: 2 additions & 3 deletions cvat-core/src/server-response-types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import {
ChunkType,
DimensionType, JobStage, JobState, JobType, ProjectStatus,
ShapeType, StorageLocation,
ShapeType, StorageLocation, LabelType,
ShareFileType, Source, TaskMode, TaskStatus,
CloudStorageCredentialsType, CloudStorageProviderType,
} from './enums';
Expand Down Expand Up @@ -150,7 +150,6 @@ export interface SerializedAttribute {
id?: number;
}

export type LabelType = 'rectangle' | 'polygon' | 'polyline' | 'points' | 'ellipse' | 'cuboid' | 'skeleton' | 'mask' | 'tag' | 'any';
export interface SerializedLabel {
id?: number;
name: string;
Expand Down
50 changes: 26 additions & 24 deletions cvat-ui/src/components/job-item/job-actions-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -46,29 +46,31 @@ function JobActionsMenu(props: Props): JSX.Element {
}, [job]);

return (
<Menu onClick={(action: MenuInfo) => {
if (action.key === 'task') {
history.push(`/tasks/${job.taskId}`);
} else if (action.key === 'project') {
history.push(`/projects/${job.projectId}`);
} else if (action.key === 'bug_tracker') {
if (job.bugTracker) window.open(job.bugTracker, '_blank', 'noopener noreferrer');
} else if (action.key === 'import_job') {
dispatch(importActions.openImportDatasetModal(job));
} else if (action.key === 'export_job') {
dispatch(exportActions.openExportDatasetModal(job));
} else if (action.key === 'view_analytics') {
history.push(`/tasks/${job.taskId}/jobs/${job.id}/analytics`);
} else if (action.key === 'renew_job') {
job.state = core.enums.JobState.NEW;
job.stage = JobStage.ANNOTATION;
onJobUpdate(job);
} else if (action.key === 'finish_job') {
job.stage = JobStage.ACCEPTANCE;
job.state = core.enums.JobState.COMPLETED;
onJobUpdate(job);
}
}}
<Menu
className='cvat-job-item-menu'
onClick={(action: MenuInfo) => {
if (action.key === 'task') {
history.push(`/tasks/${job.taskId}`);
} else if (action.key === 'project') {
history.push(`/projects/${job.projectId}`);
} else if (action.key === 'bug_tracker') {
if (job.bugTracker) window.open(job.bugTracker, '_blank', 'noopener noreferrer');
} else if (action.key === 'import_job') {
dispatch(importActions.openImportDatasetModal(job));
} else if (action.key === 'export_job') {
dispatch(exportActions.openExportDatasetModal(job));
} else if (action.key === 'view_analytics') {
history.push(`/tasks/${job.taskId}/jobs/${job.id}/analytics`);
} else if (action.key === 'renew_job') {
job.state = core.enums.JobState.NEW;
job.stage = JobStage.ANNOTATION;
onJobUpdate(job);
} else if (action.key === 'finish_job') {
job.stage = JobStage.ACCEPTANCE;
job.state = core.enums.JobState.COMPLETED;
onJobUpdate(job);
}
}}
>
<Menu.Item key='task' disabled={job.taskId === null}>Go to the task</Menu.Item>
<Menu.Item key='project' disabled={job.projectId === null}>Go to the project</Menu.Item>
Expand Down
12 changes: 8 additions & 4 deletions cvat-ui/src/components/job-item/styles.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

@import '../../base.scss';
@import '../../base';

.cvat-job-item {
border: 1px solid $border-color-1;
Expand All @@ -20,7 +20,7 @@
}

.ant-card-body {
padding: $grid-unit-size*2;
padding: $grid-unit-size * 2;
min-height: $grid-unit-size * 9;
}

Expand Down Expand Up @@ -65,8 +65,12 @@
.cvat-job-assignee-selector {
max-width: $grid-unit-size * 16;

@media screen and (max-width: 1620px) {
@media screen and (width <= 1620px) {
max-width: $grid-unit-size * 12;
}
}
}

.ant-menu.cvat-job-item-menu {
box-shadow: $box-shadow-base;
}
4 changes: 2 additions & 2 deletions cvat-ui/src/components/labels-editor/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (C) 2021-2022 Intel Corporation
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -122,7 +122,7 @@ export function toSVGCoord(svg: SVGSVGElement, coord: number[], raiseError = fal

export function fromSVGCoord(svg: SVGSVGElement, coord: number[], raiseError = false): number[] {
const result = [];
const ctm = svg.getCTM();
const ctm = svg.getScreenCTM();
if (!ctm) {
if (raiseError) throw new Error('Inversed screen CTM is null');
return coord;
Expand Down
Loading

0 comments on commit 397d006

Please sign in to comment.