433 lines
16 KiB
JavaScript
433 lines
16 KiB
JavaScript
|
|
// Copyright (C) CVAT.ai Corporation
|
||
|
|
//
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
/// <reference types="cypress" />
|
||
|
|
|
||
|
|
context('Manipulations with masks', { scrollBehavior: false }, () => {
|
||
|
|
const taskName = 'Basic actions with masks';
|
||
|
|
const serverFiles = ['images/image_1.jpg', 'images/image_2.jpg', 'images/image_3.jpg'];
|
||
|
|
|
||
|
|
const drawingActions = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[300, 300], [700, 300], [700, 700], [300, 700]],
|
||
|
|
}, {
|
||
|
|
method: 'polygon-plus',
|
||
|
|
coordinates: [[450, 210], [650, 400], [450, 600], [260, 400]],
|
||
|
|
}, {
|
||
|
|
method: 'brush-size',
|
||
|
|
value: 150,
|
||
|
|
}, {
|
||
|
|
method: 'eraser',
|
||
|
|
coordinates: [[500, 500]],
|
||
|
|
}, {
|
||
|
|
method: 'brush-size',
|
||
|
|
value: 10,
|
||
|
|
}, {
|
||
|
|
method: 'polygon-minus',
|
||
|
|
coordinates: [[450, 400], [600, 400], [450, 550], [310, 400]],
|
||
|
|
}];
|
||
|
|
|
||
|
|
const editingActions = [{
|
||
|
|
method: 'polygon-minus',
|
||
|
|
coordinates: [[50, 400], [800, 400], [800, 800], [50, 800]],
|
||
|
|
}];
|
||
|
|
|
||
|
|
let taskID = null;
|
||
|
|
let jobID = null;
|
||
|
|
|
||
|
|
before(() => {
|
||
|
|
cy.visit('/auth/login');
|
||
|
|
cy.login();
|
||
|
|
cy.headlessCreateTask({
|
||
|
|
labels: [{ name: 'mask label', attributes: [], type: 'any' }],
|
||
|
|
name: taskName,
|
||
|
|
project_id: null,
|
||
|
|
source_storage: { location: 'local' },
|
||
|
|
target_storage: { location: 'local' },
|
||
|
|
}, {
|
||
|
|
server_files: serverFiles,
|
||
|
|
image_quality: 70,
|
||
|
|
use_zip_chunks: true,
|
||
|
|
use_cache: true,
|
||
|
|
sorting_method: 'lexicographical',
|
||
|
|
}).then((response) => {
|
||
|
|
taskID = response.taskID;
|
||
|
|
[jobID] = response.jobIDs;
|
||
|
|
}).then(() => {
|
||
|
|
cy.visit(`/tasks/${taskID}/jobs/${jobID}`);
|
||
|
|
cy.get('.cvat-canvas-container').should('exist').and('be.visible');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
after(() => {
|
||
|
|
cy.logout();
|
||
|
|
cy.getAuthKey().then((response) => {
|
||
|
|
const authKey = response.body.key;
|
||
|
|
cy.request({
|
||
|
|
method: 'DELETE',
|
||
|
|
url: `/api/tasks/${taskID}`,
|
||
|
|
headers: {
|
||
|
|
Authorization: `Token ${authKey}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Tests to make sure that basic features work with masks', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
cy.removeAnnotations();
|
||
|
|
cy.goCheckFrameNumber(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Drawing a couple of masks. Save job, reopen job, masks must exist', () => {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(drawingActions);
|
||
|
|
cy.get('.cvat-brush-tools-finish').click();
|
||
|
|
cy.get('.cvat-brush-tools-continue').click();
|
||
|
|
cy.get('.cvat-brush-tools-toolbox').should('exist').and('be.visible');
|
||
|
|
cy.get('#cvat_canvas_shape_1').should('exist').and('be.visible');
|
||
|
|
|
||
|
|
// it is expected, that after clicking "continue", brush tools are still opened
|
||
|
|
cy.drawMask(drawingActions);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
cy.get('.cvat-brush-tools-toolbox').should('not.be.visible');
|
||
|
|
|
||
|
|
cy.saveJob();
|
||
|
|
cy.reload();
|
||
|
|
|
||
|
|
for (const id of [1, 2]) {
|
||
|
|
cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible');
|
||
|
|
}
|
||
|
|
cy.removeAnnotations();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Propagate mask to another frame', () => {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(drawingActions);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Propagate');
|
||
|
|
cy.get('.cvat-propagate-confirm-up-to-input').find('input')
|
||
|
|
.should('have.attr', 'value', serverFiles.length - 1);
|
||
|
|
cy.contains('button', 'Yes').click();
|
||
|
|
for (let i = 1; i < serverFiles.length; i++) {
|
||
|
|
cy.goCheckFrameNumber(i);
|
||
|
|
cy.get('.cvat_canvas_shape').should('exist').and('be.visible');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Copy mask to another frame', () => {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(drawingActions);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Make a copy');
|
||
|
|
cy.goCheckFrameNumber(serverFiles.length - 1);
|
||
|
|
cy.get('.cvat-canvas-container').click();
|
||
|
|
cy.get('#cvat_canvas_shape_2').should('exist').and('be.visible');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Check hidden mask still invisible after changing frame/opacity', () => {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(drawingActions);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.get('#cvat-objects-sidebar-state-item-1').within(() => {
|
||
|
|
cy.get('.cvat-object-item-button-hidden')
|
||
|
|
.should('exist').and('be.visible').click();
|
||
|
|
cy.get('.cvat-object-item-button-hidden')
|
||
|
|
.should('have.class', 'cvat-object-item-button-hidden-enabled');
|
||
|
|
});
|
||
|
|
|
||
|
|
cy.goCheckFrameNumber(serverFiles.length - 1);
|
||
|
|
cy.goCheckFrameNumber(0);
|
||
|
|
|
||
|
|
cy.get('.cvat-appearance-opacity-slider').click('right');
|
||
|
|
cy.get('.cvat-appearance-opacity-slider').click('center');
|
||
|
|
cy.get('#cvat_canvas_shape_1')
|
||
|
|
.should('exist').and('have.class', 'cvat_canvas_hidden').and('not.be.visible');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Editing a drawn mask', () => {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(drawingActions);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Edit');
|
||
|
|
cy.drawMask(editingActions);
|
||
|
|
|
||
|
|
// Check issue fixed in https://github.com/cvat-ai/cvat/pull/8598
|
||
|
|
// Frames navigation should not work during editing
|
||
|
|
cy.get('.cvat-player-next-button').click();
|
||
|
|
cy.checkFrameNum(0);
|
||
|
|
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Underlying pixels are removed on enabling "Remove underlying pixels" tool', () => {
|
||
|
|
const mask1 = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [600, 400]],
|
||
|
|
}];
|
||
|
|
const mask2 = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [525, 325]],
|
||
|
|
}];
|
||
|
|
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(mask1);
|
||
|
|
cy.get('.cvat-brush-tools-continue').click();
|
||
|
|
|
||
|
|
cy.drawMask(mask2);
|
||
|
|
cy.get('.cvat-brush-tools-underlying-pixels').click();
|
||
|
|
cy.get('.cvat-brush-tools-underlying-pixels').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.get('#cvat-objects-sidebar-state-item-2').within(() => {
|
||
|
|
cy.get('.cvat-object-item-button-hidden').click();
|
||
|
|
});
|
||
|
|
|
||
|
|
cy.get('.cvat-canvas-container').then(([$canvas]) => {
|
||
|
|
cy.wrap($canvas).trigger('mousemove', { clientX: 450, clientY: 250 });
|
||
|
|
cy.get('#cvat_canvas_shape_1').should('not.have.class', 'cvat_canvas_shape_activated');
|
||
|
|
|
||
|
|
cy.wrap($canvas).trigger('mousemove', { clientX: 550, clientY: 350 });
|
||
|
|
cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated');
|
||
|
|
});
|
||
|
|
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.get('.cvat-brush-tools-underlying-pixels').click();
|
||
|
|
cy.get('.cvat-brush-tools-underlying-pixels').should('not.have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Check brush tools shortcuts', () => {
|
||
|
|
const mask1 = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [600, 400]],
|
||
|
|
}];
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(mask1);
|
||
|
|
cy.get('.cvat-brush-tools-polygon-minus').click();
|
||
|
|
cy.get('.cvat-brush-tools-polygon-minus').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
|
||
|
|
cy.get('body').type('{shift}{1}');
|
||
|
|
cy.get('.cvat-brush-tools-brush').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.get('body').type('{shift}{2}');
|
||
|
|
cy.get('.cvat-brush-tools-eraser').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.get('body').type('{shift}{3}');
|
||
|
|
cy.get('.cvat-brush-tools-polygon-plus').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.get('body').type('{shift}{4}');
|
||
|
|
cy.get('.cvat-brush-tools-polygon-minus').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
|
||
|
|
cy.get('.cvat-brush-tools-finish').trigger('mouseover');
|
||
|
|
cy.get('.cvat-brush-tools-finish').trigger('mouseout');
|
||
|
|
cy.get('body').type('n');
|
||
|
|
cy.get('.cvat-brush-tools-toolbox').should('not.be.visible');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Check hide mask feature', () => {
|
||
|
|
function checkHideFeature() {
|
||
|
|
cy.get('.cvat-brush-tools-hide').click();
|
||
|
|
cy.get('.cvat-brush-tools-hide').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.get('.cvat_masks_canvas_wrapper').should('not.be.visible');
|
||
|
|
cy.get('.cvat-brush-tools-hide').click();
|
||
|
|
cy.get('.cvat_masks_canvas_wrapper').should('be.visible');
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkHideShortcut() {
|
||
|
|
cy.get('body').type('h');
|
||
|
|
cy.get('.cvat-brush-tools-hide').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.get('.cvat_masks_canvas_wrapper').should('not.be.visible');
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkObjectIsHidden() {
|
||
|
|
cy.get('#cvat-objects-sidebar-state-item-1').within(() => {
|
||
|
|
cy.get('.cvat-object-item-button-hidden-enabled').should('exist');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const mask = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [600, 400]],
|
||
|
|
}];
|
||
|
|
const drawPolygon = [{
|
||
|
|
method: 'polygon-plus',
|
||
|
|
coordinates: [[450, 210], [650, 400], [450, 600], [260, 400]],
|
||
|
|
}];
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(mask);
|
||
|
|
|
||
|
|
checkHideFeature();
|
||
|
|
checkHideShortcut();
|
||
|
|
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
cy.get('#cvat_canvas_shape_1').should('be.visible');
|
||
|
|
|
||
|
|
cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Edit');
|
||
|
|
checkHideFeature();
|
||
|
|
|
||
|
|
cy.drawMask(drawPolygon);
|
||
|
|
checkHideShortcut();
|
||
|
|
cy.get('.cvat_canvas_shape_drawing')
|
||
|
|
.invoke('attr', 'fill-opacity')
|
||
|
|
.then((opacity) => expect(+opacity).to.be.equal(0));
|
||
|
|
checkObjectIsHidden();
|
||
|
|
cy.get('.cvat-brush-tools-brush').click();
|
||
|
|
cy.get('.cvat-brush-tools-brush').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
checkObjectIsHidden();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Tests to make sure that empty masks cannot be created', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
cy.removeAnnotations();
|
||
|
|
cy.saveJob('PUT');
|
||
|
|
});
|
||
|
|
|
||
|
|
function checkEraseTools(baseTool = '.cvat-brush-tools-brush', disabled = true) {
|
||
|
|
cy.get(baseTool).should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
|
||
|
|
const condition = disabled ? 'be.disabled' : 'not.be.disabled';
|
||
|
|
cy.get('.cvat-brush-tools-eraser').should(condition);
|
||
|
|
cy.get('.cvat-brush-tools-polygon-minus').should(condition);
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkMaskNotEmpty(selector) {
|
||
|
|
cy.get(selector).should('exist').and('be.visible');
|
||
|
|
cy.get(selector)
|
||
|
|
.should('have.attr', 'height')
|
||
|
|
.then((height) => {
|
||
|
|
expect(+height).to.be.gt(1);
|
||
|
|
});
|
||
|
|
cy.get(selector)
|
||
|
|
.should('have.attr', 'width')
|
||
|
|
.then((width) => {
|
||
|
|
expect(+width).to.be.gt(1);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
it('Erase tools are locked when nothing to erase', () => {
|
||
|
|
const erasedMask = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [600, 400], [450, 550], [300, 400]],
|
||
|
|
}, {
|
||
|
|
method: 'polygon-minus',
|
||
|
|
coordinates: [[100, 100], [700, 100], [700, 700], [100, 700]],
|
||
|
|
}];
|
||
|
|
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
checkEraseTools();
|
||
|
|
cy.drawMask(erasedMask);
|
||
|
|
|
||
|
|
cy.get('.cvat-brush-tools-brush').click();
|
||
|
|
checkEraseTools();
|
||
|
|
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
cy.get('#cvat_canvas_shape_1').should('not.exist');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Drawing a mask, finish with erasing tool. On new mask drawing tool is reset', () => {
|
||
|
|
const masks = [[{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [600, 400], [450, 550], [300, 400]],
|
||
|
|
}, {
|
||
|
|
method: 'polygon-minus',
|
||
|
|
coordinates: [[100, 100], [400, 100], [400, 400], [100, 400]],
|
||
|
|
}], [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[550, 350], [700, 500], [550, 650], [400, 500]],
|
||
|
|
}, {
|
||
|
|
method: 'eraser',
|
||
|
|
coordinates: [[550, 350]],
|
||
|
|
}]];
|
||
|
|
|
||
|
|
for (const [index, mask] of masks.entries()) {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(mask);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.get(`#cvat_canvas_shape_${index + 1}`).should('exist').and('be.visible');
|
||
|
|
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
checkEraseTools();
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Empty masks are deleted using remove underlying pixels feature', () => {
|
||
|
|
const masks = [[{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[150, 150], [270, 270]],
|
||
|
|
}], [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[350, 350], [370, 370]],
|
||
|
|
}], [{
|
||
|
|
method: 'polygon-plus',
|
||
|
|
coordinates: [[100, 100], [400, 100], [400, 400], [100, 400]],
|
||
|
|
}]];
|
||
|
|
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.get('.cvat-brush-tools-underlying-pixels').click();
|
||
|
|
cy.get('.cvat-brush-tools-underlying-pixels').should('have.class', 'cvat-brush-tools-active-tool');
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
for (const [index, mask] of masks.entries()) {
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(mask);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.get(`#cvat_canvas_shape_${index + 1}`).should('exist').and('be.visible');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fist mask is updated, second mask is removed after third mask is drawn
|
||
|
|
cy.contains('Some objects were deleted').should('exist').and('be.visible');
|
||
|
|
for (const id of [1, 3]) {
|
||
|
|
cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible');
|
||
|
|
}
|
||
|
|
cy.get('#cvat_canvas_shape_2').should('not.exist');
|
||
|
|
cy.saveJob('PATCH', 200, 'removeUnderlyingPixelsUndoRedo');
|
||
|
|
|
||
|
|
// Undo creating mask, second mask is restored
|
||
|
|
cy.contains('.cvat-annotation-header-button', 'Undo').click();
|
||
|
|
for (const id of [1, 2]) {
|
||
|
|
cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible');
|
||
|
|
}
|
||
|
|
cy.saveJob('PATCH', 200, 'removeUnderlyingPixelsUndoRedo');
|
||
|
|
|
||
|
|
// Redo creating mask, second mask is removed
|
||
|
|
cy.contains('.cvat-annotation-header-button', 'Redo').click();
|
||
|
|
for (const id of [1, 3]) {
|
||
|
|
cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible');
|
||
|
|
}
|
||
|
|
cy.get('#cvat_canvas_shape_2').should('not.exist');
|
||
|
|
cy.saveJob('PATCH', 200, 'removeUnderlyingPixelsUndoRedo');
|
||
|
|
|
||
|
|
cy.get('.cvat-notification-notice-save-annotations-failed').should('not.exist');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('Erasing a mask during editing is not allowed', () => {
|
||
|
|
const mask = [{
|
||
|
|
method: 'brush',
|
||
|
|
coordinates: [[450, 250], [600, 400], [450, 550], [300, 400]],
|
||
|
|
}];
|
||
|
|
const eraseAction = [{
|
||
|
|
method: 'polygon-minus',
|
||
|
|
coordinates: [[100, 100], [700, 100], [700, 700], [100, 700]],
|
||
|
|
}];
|
||
|
|
|
||
|
|
cy.startMaskDrawing();
|
||
|
|
cy.drawMask(mask);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Edit');
|
||
|
|
cy.drawMask(eraseAction);
|
||
|
|
cy.finishMaskDrawing();
|
||
|
|
|
||
|
|
checkMaskNotEmpty('#cvat_canvas_shape_1');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|