diff --git a/cvat-core/.eslintrc.js b/cvat-core/.eslintrc.js
index 8e110cfffafc..a28a5cc1b80a 100644
--- a/cvat-core/.eslintrc.js
+++ b/cvat-core/.eslintrc.js
@@ -3,9 +3,6 @@
// SPDX-License-Identifier: MIT
module.exports = {
- env: {
- 'jest/globals': true,
- },
ignorePatterns: [
'.eslintrc.js',
'webpack.config.js',
@@ -14,17 +11,10 @@ module.exports = {
'src/3rdparty/**',
'node_modules/**',
'dist/**',
+ 'tests/**/*.js',
],
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
- ignorePatterns: ['tests/**/*.js'],
- plugins: ['jest'],
- rules: {
- 'jest/no-disabled-tests': 'warn',
- 'jest/no-focused-tests': 'error',
- 'jest/no-identical-title': 'error',
- 'jest/prefer-to-have-length': 'warn',
- }
};
diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js
index f07d9e184f07..de2b1257c3f0 100644
--- a/cvat-core/tests/api/annotations.js
+++ b/cvat-core/tests/api/annotations.js
@@ -65,6 +65,28 @@ describe('Feature: get annotations', () => {
expect(annotations).toHaveLength(1);
expect(annotations[0].shapeType).toBe('ellipse');
});
+
+ test('get skeletons with a filter', async () => {
+ const job = (await window.cvat.jobs.get({ jobID: 40 }))[0];
+ const annotations = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]'));
+ expect(Array.isArray(annotations)).toBeTruthy();
+ expect(annotations).toHaveLength(2);
+ for (const object of annotations) {
+ expect(object.shapeType).toBe('skeleton');
+ expect(object.elements).toBeInstanceOf(Array);
+ const label = object.label;
+ let points = [];
+ object.elements.forEach((element, idx) => {
+ expect(element).toBeInstanceOf(cvat.classes.ObjectState);
+ expect(element.label.id).toBe(label.structure.sublabels[idx].id);
+ expect(element.shapeType).toBe('points');
+ points = [...points, ...element.points];
+ });
+ expect(points).toEqual(object.points);
+ }
+
+ expect(annotations[0].shapeType).toBe('skeleton');
+ })
});
describe('Feature: get interpolated annotations', () => {
@@ -351,6 +373,68 @@ describe('Feature: put annotations', () => {
zOrder: 0,
})).toThrow(window.cvat.exceptions.ArgumentError);
});
+
+ test('put a skeleton shape to a job', async() => {
+ const job = (await window.cvat.jobs.get({ jobID: 40 }))[0];
+ const label = job.labels[0];
+ await job.annotations.clear(true);
+ await job.annotations.clear();
+ const skeleton = new window.cvat.classes.ObjectState({
+ frame: 0,
+ objectType: window.cvat.enums.ObjectType.SHAPE,
+ shapeType: window.cvat.enums.ShapeType.SKELETON,
+ points: [],
+ label,
+ elements: label.structure.sublabels.map((sublabel, idx) => ({
+ frame: 0,
+ objectType: window.cvat.enums.ObjectType.SHAPE,
+ shapeType: window.cvat.enums.ShapeType.POINTS,
+ points: [idx * 10, idx * 10],
+ label: sublabel,
+ }))
+ });
+
+ await job.annotations.put([skeleton]);
+ const annotations = await job.annotations.get(0);
+ expect(annotations.length).toBe(1);
+ expect(annotations[0].objectType).toBe(window.cvat.enums.ObjectType.SHAPE);
+ expect(annotations[0].shapeType).toBe(window.cvat.enums.ShapeType.SKELETON);
+ for (const element of annotations[0].elements) {
+ expect(element.objectType).toBe(window.cvat.enums.ObjectType.SHAPE);
+ expect(element.shapeType).toBe(window.cvat.enums.ShapeType.POINTS);
+ }
+ });
+
+ test('put a skeleton track to a task', async() => {
+ const task = (await window.cvat.tasks.get({ id: 40 }))[0];
+ const label = task.labels[0];
+ await task.annotations.clear(true);
+ await task.annotations.clear();
+ const skeleton = new window.cvat.classes.ObjectState({
+ frame: 0,
+ objectType: window.cvat.enums.ObjectType.TRACK,
+ shapeType: window.cvat.enums.ShapeType.SKELETON,
+ points: [],
+ label,
+ elements: label.structure.sublabels.map((sublabel, idx) => ({
+ frame: 0,
+ objectType: window.cvat.enums.ObjectType.TRACK,
+ shapeType: window.cvat.enums.ShapeType.POINTS,
+ points: [idx * 10, idx * 10],
+ label: sublabel,
+ }))
+ });
+
+ await task.annotations.put([skeleton]);
+ const annotations = await task.annotations.get(2);
+ expect(annotations.length).toBe(1);
+ expect(annotations[0].objectType).toBe(window.cvat.enums.ObjectType.TRACK);
+ expect(annotations[0].shapeType).toBe(window.cvat.enums.ShapeType.SKELETON);
+ for (const element of annotations[0].elements) {
+ expect(element.objectType).toBe(window.cvat.enums.ObjectType.TRACK);
+ expect(element.shapeType).toBe(window.cvat.enums.ShapeType.POINTS);
+ }
+ });
});
describe('Feature: check unsaved changes', () => {
@@ -772,6 +856,21 @@ describe('Feature: get statistics', () => {
expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics);
expect(statistics.total.total).toBe(1012);
});
+
+ test('get statistics from a job with skeletons', async () => {
+ const job = (await window.cvat.jobs.get({ jobID: 40 }))[0];
+ await job.annotations.clear(true);
+ const statistics = await job.annotations.statistics();
+ expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics);
+ expect(statistics.total.total).toBe(30);
+ const labelName = job.labels[0].name;
+ expect(statistics.label[labelName].skeleton.shape).toBe(1);
+ expect(statistics.label[labelName].skeleton.track).toBe(1);
+ expect(statistics.label[labelName].manually).toBe(2);
+ expect(statistics.label[labelName].interpolated).toBe(3);
+ expect(statistics.label[labelName].total).toBe(5);
+
+ });
});
describe('Feature: select object', () => {
diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js
index 977aaacebde4..bb86279e8555 100644
--- a/cvat-core/tests/api/object-state.js
+++ b/cvat-core/tests/api/object-state.js
@@ -282,3 +282,40 @@ describe('Feature: delete object', () => {
expect(annotationsAfter).toHaveLength(length - 1);
});
});
+
+describe('Feature: skeletons', () => {
+ test('lock, hide, occluded, outside for skeletons', async () => {
+ const job = (await window.cvat.jobs.get({ jobID: 40 }))[0];
+ let [skeleton] = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]'));
+ expect(skeleton.shapeType).toBe('skeleton');
+ skeleton.lock = true;
+ skeleton.outside = true;
+ skeleton.occluded = true;
+ skeleton.hidden = true;
+ skeleton = await skeleton.save();
+ expect(skeleton.lock).toBe(true);
+ expect(skeleton.outside).toBe(true);
+ expect(skeleton.occluded).toBe(true);
+ expect(skeleton.hidden).toBe(true);
+ expect(skeleton.elements).toBeInstanceOf(Array);
+ expect(skeleton.elements.length).toBe(skeleton.label.structure.sublabels.length);
+ for (const element of skeleton.elements) {
+ expect(element.lock).toBe(true);
+ expect(element.outside).toBe(true);
+ expect(element.occluded).toBe(true);
+ expect(element.hidden).toBe(true);
+ }
+
+ skeleton.elements[0].lock = false;
+ skeleton.elements[0].outside = false;
+ skeleton.elements[0].occluded = false;
+ skeleton.elements[0].hidden = false;
+ skeleton.elements[0].save();
+
+ [skeleton] = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]'));
+ expect(skeleton.lock).toBe(false);
+ expect(skeleton.outside).toBe(false);
+ expect(skeleton.occluded).toBe(false);
+ expect(skeleton.hidden).toBe(false);
+ });
+});
\ No newline at end of file
diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js
index ea1299dda4c4..bbb990c81cf8 100644
--- a/cvat-core/tests/api/tasks.js
+++ b/cvat-core/tests/api/tasks.js
@@ -18,7 +18,7 @@ describe('Feature: get a list of tasks', () => {
test('get all tasks', async () => {
const result = await window.cvat.tasks.get();
expect(Array.isArray(result)).toBeTruthy();
- expect(result).toHaveLength(6);
+ expect(result).toHaveLength(7);
for (const el of result) {
expect(el).toBeInstanceOf(Task);
}
@@ -34,6 +34,33 @@ describe('Feature: get a list of tasks', () => {
expect(result[0].id).toBe(3);
});
+ test('get a task with skeletons by an id', async () => {
+ const result = await window.cvat.tasks.get({
+ id: 40,
+ });
+
+ expect(Array.isArray(result)).toBeTruthy();
+ expect(result).toHaveLength(1);
+ expect(result[0]).toBeInstanceOf(Task);
+ expect(result[0].id).toBe(40);
+ expect(result[0].labels).toBeInstanceOf(Array);
+
+ for (const label of result[0].labels) {
+ expect(label).toBeInstanceOf(window.cvat.classes.Label);
+ if (label.type === 'skeleton') {
+ expect(label.hasParent).toBe(false);
+ expect(label.structure.sublabels).toBeInstanceOf(Array);
+ expect(typeof label.structure.svg).toBe('string');
+ expect(label.structure.svg.length).not.toBe(0);
+
+ for (const sublabel of label.structure.sublabels) {
+ expect(sublabel).toBeInstanceOf(window.cvat.classes.Label);
+ expect(sublabel.hasParent).toBe(true);
+ }
+ }
+ }
+ });
+
test('get a task by an unknown id', async () => {
const result = await window.cvat.tasks.get({
id: 50,
@@ -154,12 +181,61 @@ describe('Feature: save a task', () => {
project_id: 2,
bug_tracker: 'bug tracker value',
image_quality: 50,
- z_order: true,
});
const result = await task.save();
expect(result.projectId).toBe(2);
});
+
+ test('create a new task with skeletons', async () => {
+ const svgSpec = `
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ const task = new window.cvat.classes.Task({
+ name: 'task with skeletons',
+ labels: [{
+ name: 'star skeleton',
+ type: 'skeleton',
+ attributes: [],
+ svg: svgSpec,
+ sublabels: [{
+ name: '1',
+ type: 'points',
+ attributes: []
+ }, {
+ name: '2',
+ type: 'points',
+ attributes: []
+ }, {
+ name: '3',
+ type: 'points',
+ attributes: []
+ }, {
+ name: '4',
+ type: 'points',
+ attributes: []
+ }, {
+ name: '5',
+ type: 'points',
+ attributes: []
+ }]
+ }],
+ project_id: null,
+ });
+
+ const result = await task.save();
+ expect(typeof result.id).toBe('number');
+ });
});
describe('Feature: delete a task', () => {
diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js
index 4655f57e7232..2b4edfff1d2d 100644
--- a/cvat-core/tests/mocks/dummy-data.mock.js
+++ b/cvat-core/tests/mocks/dummy-data.mock.js
@@ -783,6 +783,105 @@ const tasksDummyData = {
stop_frame: 5001,
frame_filter: '',
},
+ {
+ url: 'http://localhost:7000/api/tasks/40',
+ id: 40,
+ name: 'test',
+ project_id: null,
+ mode: 'annotation',
+ owner: {
+ url: 'http://localhost:7000/api/users/1',
+ id: 1,
+ username: 'admin',
+ first_name: '',
+ last_name: '',
+ },
+ assignee: null,
+ bug_tracker: '',
+ created_date: '2022-08-25T12:10:45.471663Z',
+ updated_date: '2022-08-25T12:10:45.993989Z',
+ overlap: 0,
+ segment_size: 4,
+ status: 'annotation',
+ labels: [{
+ id: 54,
+ name: 'star skeleton',
+ color: '#9cb75a',
+ attributes: [],
+ type: 'skeleton',
+ sublabels: [{
+ id: 55,
+ name: '1',
+ color: '#d12345',
+ attributes: [],
+ type: 'points',
+ has_parent: true
+ }, {
+ id: 56,
+ name: '2',
+ color: '#350dea',
+ attributes: [],
+ type: 'points',
+ has_parent: true
+ }, {
+ id: 57,
+ name: '3',
+ color: '#479ffe',
+ attributes: [],
+ type: 'points',
+ has_parent: true
+ }, {
+ id: 58,
+ name: '4',
+ color: '#4a649f',
+ attributes: [],
+ type: 'points',
+ has_parent: true
+ }, {
+ id: 59,
+ name: '5',
+ color: '#478144',
+ attributes: [],
+ type: 'points',
+ has_parent: true
+ }],
+ has_parent: false,
+ svg:
+ `
+
+
+
+
+
+
+
+
+ `
+ }],
+ segments: [{
+ start_frame: 0,
+ stop_frame: 3,
+ jobs: [{
+ url: 'http://localhost:7000/api/jobs/40',
+ id: 40,
+ assignee: null,
+ status: 'annotation',
+ stage: 'annotation',
+ state: 'new',
+ }]
+ }],
+ data_chunk_size: 17,
+ data_compressed_chunk_type: 'imageset',
+ data_original_chunk_type: 'imageset',
+ size: 4,
+ image_quality: 70,
+ data: 12,
+ dimension: '2d',
+ subset: '',
+ organization: null,
+ target_storage: null,
+ source_storage: null
+ },
{
url: 'http://localhost:7000/api/tasks/3',
id: 3,
@@ -2532,6 +2631,230 @@ const taskAnnotationsDummyData = {
],
tracks: [],
},
+ 40: {
+ version: 0,
+ tags: [],
+ shapes: [{
+ type: 'skeleton',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [],
+ id: 23,
+ frame: 0,
+ label_id: 54,
+ group: 0,
+ source: 'manual',
+ attributes: [],
+ elements: [{
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 908.0654296875,
+ 768.8268729552019
+ ],
+ id: 24,
+ frame: 0,
+ label_id: 55,
+ group: 0,
+ source: 'manual',
+ attributes: []
+ }, {
+ type: "points",
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 1230.1533057030483,
+ 523.7802734375
+ ],
+ id: 25,
+ frame: 0,
+ label_id: 56,
+ group: 0,
+ source: 'manual',
+ attributes: []
+ }, {
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 1525.9969940892624,
+ 772.6557444966547
+ ],
+ id: 26,
+ frame: 0,
+ label_id: 57,
+ group: 0,
+ source: 'manual',
+ attributes: []
+ }, {
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 1468.7369236136856,
+ 1270.4066429432623
+ ],
+ id: 27,
+ frame: 0,
+ label_id: 58,
+ group: 0,
+ source: 'manual',
+ attributes: []
+ }, {
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 989.1838401839595,
+ 1258.9201156622657
+ ],
+ id: 28,
+ frame: 0,
+ label_id:59,
+ group: 0,
+ source: 'manual',
+ attributes: []
+ }]
+ }],
+ tracks: [{
+ id: 1,
+ frame: 0,
+ label_id: 54,
+ group: 0,
+ source: 'manual',
+ shapes: [{
+ type: 'skeleton',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [],
+ id: 1,
+ frame: 0,
+ attributes: []
+ }],
+ attributes: [],
+ elements: [{
+ id: 2,
+ frame: 0,
+ label_id: 55,
+ group: 0,
+ source: 'manual',
+ shapes: [{
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 88.4140625,
+ 332.85145482411826
+ ],
+ id: 2,
+ frame: 0,
+ attributes: []
+ }],
+ attributes: []
+ }, {
+ id: 3,
+ frame: 0,
+ label_id: 56,
+ group: 0,
+ source: 'manual',
+ shapes: [{
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 437.3386217629577,
+ 96.447265625
+ ],
+ id: 3,
+ frame: 0,
+ attributes: []
+ }],
+ attributes: []
+ }, {
+ id: 4,
+ frame: 0,
+ label_id: 57,
+ group: 0,
+ source: 'manual',
+ shapes: [{
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 757.8323014937105,
+ 336.54528805456357
+ ],
+ id: 4,
+ frame: 0,
+ attributes: []
+ }],
+ attributes: []
+ }, {
+ id: 5,
+ frame: 0,
+ label_id: 58,
+ group: 0,
+ source: 'manual',
+ shapes: [{
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 695.8012648051717,
+ 816.7412907822327
+ ],
+ id: 5,
+ frame: 0,
+ attributes: []
+ }],
+ attributes: []
+ }, {
+ id: 6,
+ frame: 0,
+ label_id: 59,
+ group: 0,
+ source: 'manual',
+ shapes: [{
+ type: 'points',
+ occluded: false,
+ outside: false,
+ z_order: 0,
+ rotation: 0.0,
+ points: [
+ 176.29133990867558,
+ 805.659875353811
+ ],
+ id: 6,
+ frame: 0,
+ attributes: []
+ }],
+ attributes: []
+ }]
+ }]
+ }
};
const jobAnnotationsDummyData = JSON.parse(JSON.stringify(taskAnnotationsDummyData));
@@ -2629,6 +2952,36 @@ const frameMetaDummyData = {
},
],
},
+ 40: {
+ chunk_size: 17,
+ size: 4,
+ image_quality: 70,
+ start_frame: 0,
+ stop_frame: 3,
+ frame_filter: '',
+ frames: [{
+ width: 2560,
+ height: 1703,
+ name: '1598296101_1033667.jpg',
+ has_related_context: false
+ }, {
+ width: 1600,
+ height: 1200,
+ name: '30fdce7f27b9c7b1d50108d7c16d23ef.jpg',
+ has_related_context: false
+ }, {
+ width: 2880,
+ height: 1800,
+ name: '567362-ily-comedy-drama-1finding-3.jpg',
+ has_related_context: false
+ }, {
+ width: 1920,
+ height: 1080,
+ name: '730443-under-the-sea-wallpapers-1920x1080-windows-10.jpg',
+ has_related_context: false
+ }],
+ deleted_frames: []
+ },
100: {
chunk_size: 36,
size: 9,
diff --git a/lint-staged.config.js b/lint-staged.config.js
index de7bc0c4c53d..e2e443718d1c 100644
--- a/lint-staged.config.js
+++ b/lint-staged.config.js
@@ -16,9 +16,9 @@ module.exports = (stagedFiles) => {
const eslintFiles = micromatch(stagedFiles, eslintExtensions);
const scssFiles = micromatch(stagedFiles, scssExtensions);
- const tests = containsInPath('/tests/', eslintFiles);
+ const tests = containsInPath('/tests/cypress', eslintFiles);
const cvatData = containsInPath('/cvat-data/', eslintFiles);
- const cvatCore = containsInPath('/cvat-core/', eslintFiles);
+ const cvatCore = containsInPath('/cvat-core/src', eslintFiles);
const cvatCanvas = containsInPath('/cvat-canvas/', eslintFiles);
const cvatCanvas3d = containsInPath('/cvat-canvas3d/', eslintFiles);
const cvatUI = containsInPath('/cvat-ui/', eslintFiles);