diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b6f8a6c27..93f2e260ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.3.0-alpha] - Unreleased +## [1.4.0] - Unreleased + +### Added +- + +### Changed +- + +### Deprecated +- + +### Removed +- + +### Fixed +- + +### Security +- + + +## [1.3.0] - 3/31/2021 ### Added @@ -16,8 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - Intelligent scissors with OpenCV javascript () - CVAT-3D: Visualize 3D point cloud spaces in 3D View, Top View Side View and Front View () -- [Inside Outside Guidence](https://github.com/shiyinzhang/Inside-Outside-Guidance) serverless - function for interative segmentation +- [Inside Outside Guidance](https://github.com/shiyinzhang/Inside-Outside-Guidance) serverless + function for interactive segmentation - Pre-built [cvat_server](https://hub.docker.com/r/openvino/cvat_server) and [cvat_ui](https://hub.docker.com/r/openvino/cvat_ui) images were published on DockerHub () - Project task subsets () @@ -33,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Annotations filters UI using react-awesome-query-builder (https://github.com/openvinotoolkit/cvat/issues/1418) - Storing settings in local storage to keep them between browser sessions () - [ICDAR](https://rrc.cvc.uab.es/?ch=2) format support () -- Added switcher to maintain poylgon crop behaviour ( +- Added switcher to maintain polygon crop behavior ( - Filters and sorting options for job list, added tooltip for tasks filters () - Documentation on mask annotation () @@ -43,22 +64,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated HTTPS install README section (cleanup and described more robust deploy) - Logstash is improved for using with configurable elasticsearch outputs () - Bumped nuclio version to 1.5.16 () -- All methods for interative segmentation accept negative points as well +- All methods for interactive segmentation accept negative points as well - Persistent queue added to logstash () -- Improved maintanance of popups visibility () +- Improved maintenance of popups visibility () - Image visualizations settings on canvas for faster access () - Better scale management of left panel when screen is too small () - Improved error messages for annotation import () - Using manifest support instead video meta information and dummy chunks () -### Deprecated - -- - -### Removed - -- - ### Fixed - More robust execution of nuclio GPU functions by limiting the GPU memory consumption per worker () @@ -66,12 +79,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The cursor jumps to the end of the line when renaming a task () - SSLCertVerificationError when remote source is used () - Fixed filters select overflow () -- Fixed tasks in project autoannotation () +- Fixed tasks in project auto annotation () - Cuboids are missed in annotations statistics () - The list of files attached to the task is not displayed () - Issue with point region doesn't work in Firefox () -- Fixed cuboid perpective change () +- Fixed cuboid perspective change () - Annotation page popups (ai tools, drawing) reset state after detecting, tracking, drawing () - Polygon editing using trailing point () - Updated the path to python for DL models inside automatic annotation documentation () @@ -88,10 +101,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Crop a polygon if its points are outside the bounds of the image () - Export of instance masks with holes () -### Security - -- - ## [1.2.0] - 2021-01-08 ### Fixed diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 76872ec7d41..c23090f3cb8 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.18.0", + "version": "1.18.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index d885b6129e7..d6bbde46e5c 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.18.0", + "version": "1.18.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/components/annotation-page/top-bar/filters-modal.tsx b/cvat-ui/src/components/annotation-page/top-bar/filters-modal.tsx index 365c4c6043d..dbc43d525e5 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/filters-modal.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/filters-modal.tsx @@ -251,7 +251,7 @@ export default function FiltersModalComponent(props: Props): JSX.Element { return ( { useEffect(() => { try { const newSettings: any = {}; - const loadedSettings = JSON.parse(localStorage.getItem('clientSettings') as string); + const settingsString = localStorage.getItem('clientSettings') as string; + if (!settingsString) return; + const loadedSettings = JSON.parse(settingsString); for (const [sectionKey, section] of Object.entries(settings)) { for (const [key, value] of Object.entries(section)) { let settedValue = value; diff --git a/cvat/__init__.py b/cvat/__init__.py index 7038e6ec195..a2e8889ca0b 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -4,6 +4,6 @@ from cvat.utils.version import get_version -VERSION = (1, 3, 0, 'alpha', 0) +VERSION = (1, 4, 0, 'alpha', 0) __version__ = get_version(VERSION) diff --git a/cvat/apps/documentation/static/documentation/images/image067.jpg b/cvat/apps/documentation/static/documentation/images/image067.jpg index 25161e070d1..8391579c710 100644 Binary files a/cvat/apps/documentation/static/documentation/images/image067.jpg and b/cvat/apps/documentation/static/documentation/images/image067.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image068_mapillary_vistas.jpg b/cvat/apps/documentation/static/documentation/images/image068_mapillary_vistas.jpg index 86178d57cc4..3a08d51a9ab 100644 Binary files a/cvat/apps/documentation/static/documentation/images/image068_mapillary_vistas.jpg and b/cvat/apps/documentation/static/documentation/images/image068_mapillary_vistas.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image155.jpg b/cvat/apps/documentation/static/documentation/images/image155.jpg index 5efa220bcd8..0f3391eacc4 100644 Binary files a/cvat/apps/documentation/static/documentation/images/image155.jpg and b/cvat/apps/documentation/static/documentation/images/image155.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image209.jpg b/cvat/apps/documentation/static/documentation/images/image209.jpg new file mode 100644 index 00000000000..b164eda3884 Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/image209.jpg differ diff --git a/cvat/apps/documentation/user_guide.md b/cvat/apps/documentation/user_guide.md index 59eb6c9364f..75b91000528 100644 --- a/cvat/apps/documentation/user_guide.md +++ b/cvat/apps/documentation/user_guide.md @@ -677,11 +677,31 @@ In addition the workspace also has the following functions: ![](static/documentation/images/image140.jpg) +- `Image settings panel` -  used to set up the grid and set up image brightness contrast saturation. + + - Show `Grid`, change grid size, choose color and transparency: + + ![](static/documentation/images/image068_mapillary_vistas.jpg) + + - Adjust `Brightness`/`Contrast`/`Saturation` of too exposed or too + dark images using `F3` — color settings (changes displaying settings and not the + image itself). + + Shortcuts: + + - `Shift+B+=`/`Shift+B+-` for brightness. + - `Shift+C+=`/`Shift+C+-` for contrast. + - `Shift+S+=`/`Shift+S+-` for saturation. + + ![](static/documentation/images/image164_mapillary_vistas.jpg) + + - `Reset color settings` to default values. + --- ### Settings -To open the settings open the user menu in the header and select the settings item or press `F3`. +To open the settings open the user menu in the header and select the settings item or press `F2`. ![](static/documentation/images/image067.jpg) @@ -692,26 +712,9 @@ In tab `Player` you can: - Control step of `C` and `V` shortcuts. - Control speed of `Space`/`Play` button. - Select canvas background color. You can choose a background color or enter manually (in RGB or HEX format). -- Show `Grid`, change grid size, choose color and transparency: - - ![](static/documentation/images/image068_mapillary_vistas.jpg) - -- Show every image in full size or zoomed out like previous +- `Reset zoom` Show every image in full size or zoomed out like previous (it is enabled by default for interpolation mode and disabled for annotation mode). - `Rotate all images` checkbox — switch the rotation of all frames or an individual frame. -- Adjust `Brightness`/`Contrast`/`Saturation` of too exposed or too - dark images using `F3` — color settings (changes displaying settings and not the - image itself). - -Shortcuts: - -- `Shift+B+=`/`Shift+B+-` for brightness. -- `Shift+C+=`/`Shift+C+-` for contrast. -- `Shift+S+=`/`Shift+S+-` for saturation. - - ![](static/documentation/images/image164_mapillary_vistas.jpg) - -- `Reset color settings` to default values. --- @@ -729,9 +732,12 @@ In tab `Workspace` you can: - `Automatic bordering` - enable automatic bordering for polygons and polylines during drawing/editing. For more information To find out more, go to the section [annotation with polygons](#Annotation-with-polygons). + +- `Intelligent polygon cropping` - activates intelligent cropping when editing the polygon (read more in the section [edit polygon](#edit-polygon) + - `Attribute annotation mode (AAM) zoom margin` input box — defines margins (in px) for shape in the attribute annotation mode. -- Press ` Go back` or `F3` to return to the annotation. +- Click `Save` to save settings (settings will be saved on the server and will not change after the page is refreshed). Click `Cancel` or press `F2` to return to the annotation. --- @@ -1259,10 +1265,18 @@ If you need to annotate small objects, increase `Image Quality` to ### Edit polygon -To edit a polygon you have to click with pressed `Shift`, it will open the polygon editor. +To edit a polygon you have to click on it while holding `Shift`, it will open the polygon editor. + +- In the editor you can create new points or delete part of a polygon by closing the line on another point. +- When `Intelligent polygon cropping` option is activated in the settings, СVAT considers two criteria to decide which part of a polygon should be cut off during automatic editing. + - The first criteria is a number of cut points. + - The second criteria is a length of a cut curve. + + If both criteria recommend to cut the same part, algorithm works automatically, and if not, a user has to make the decision. + If you want to choose manually which part of a polygon should be cut off, disable `Intelligent polygon cropping` in the settings. In this case after closing the polygon, you can select the part of the polygon you want to leave. + + ![](static/documentation/images/image209.jpg) -- There you can create new points or delete part of a polygon closing the line on another point. -- After closing the polygon, you can select the part of the polygon that you want to leave. - You can press `Esc` to cancel editing. ![](static/documentation/images/gif007_mapillary_vistas.gif) diff --git a/cvat/requirements/development.txt b/cvat/requirements/development.txt index 36537b3fa8f..1fd64777823 100644 --- a/cvat/requirements/development.txt +++ b/cvat/requirements/development.txt @@ -1,5 +1,5 @@ -r base.txt -pylint==2.6.0 +pylint==2.7.0 pylint-django==2.3.0 pylint-plugin-utils==0.6 rope==0.17.0 diff --git a/package.json b/package.json index fb5eacf50f1..cb209f6d44e 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "instrument": "nyc instrument cvat-ui cvat-ui_cov && nyc instrument cvat-canvas cvat-canvas_cov && nyc instrument cvat-data cvat-data_cov && nyc instrument cvat-core cvat-core_cov", "cp": "cp -r cvat-ui_cov/* cvat-ui && cp -r cvat-canvas_cov/* cvat-canvas && cp -r cvat-data_cov/* cvat-data && cp -r cvat-core_cov/* cvat-core", "rm": "rm -rf cvat-ui_cov cvat-canvas_cov cvat-data_cov cvat-core_cov", - "prepare": "husky install && npx husky add .husky/pre-commit \"npx lint-staged\"", + "prepare": "husky install && rm .husky/pre-commit; npx husky add .husky/pre-commit \"npx lint-staged\"", "precommit:cvat-data": "cd cvat-ui && eslint --fix", "precommit:cvat-core": "cd cvat-ui && eslint --fix", "precommit:cvat-canvas": "cd cvat-ui && eslint --fix", diff --git a/tests/cypress/integration/actions_tasks_objects/case_18_filters_functionality.js b/tests/cypress/integration/actions_tasks_objects/case_18_filters_functionality.js index 1ab9a34cf43..1e82026cdcc 100644 --- a/tests/cypress/integration/actions_tasks_objects/case_18_filters_functionality.js +++ b/tests/cypress/integration/actions_tasks_objects/case_18_filters_functionality.js @@ -4,182 +4,280 @@ // /// -// import { taskName } from '../../support/const'; - -// TODO: Update with new filters UI - -// context('Filters functionality.', () => { -// const caseId = '18'; -// const labelShape = 'shape 3 points'; -// const additionalAttrsLabelShape = [ -// { additionalAttrName: 'type', additionalValue: 'shape', typeAttribute: 'Text' }, -// { additionalAttrName: 'count points', additionalValue: '3', typeAttribute: 'Text' }, -// { additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' }, -// ]; -// const labelTrack = 'track 4 points'; -// const additionalAttrsLabelTrack = [ -// { additionalAttrName: 'type', additionalValue: 'track', typeAttribute: 'Text' }, -// { additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' }, -// { additionalAttrName: 'count points', additionalValue: '4', typeAttribute: 'Text' }, -// ]; - -// const createPolygonShape = { -// reDraw: false, -// type: 'Shape', -// labelName: labelShape, -// pointsMap: [ -// { x: 200, y: 200 }, -// { x: 250, y: 200 }, -// { x: 250, y: 240 }, -// ], -// complete: true, -// numberOfPoints: null, -// }; -// const createRectangleTrack2Points = { -// points: 'By 2 Points', -// type: 'Track', -// labelName: labelTrack, -// firstX: 260, -// firstY: 200, -// secondX: 360, -// secondY: 250, -// }; -// const createRectangleShape4Points = { -// points: 'By 4 Points', -// type: 'Shape', -// labelName: labelShape, -// firstX: 550, -// firstY: 350, -// secondX: 650, -// secondY: 350, -// thirdX: 650, -// thirdY: 450, -// fourthX: 550, -// fourthY: 450, -// }; -// const createPolygonTrack = { -// reDraw: false, -// type: 'Track', -// labelName: labelTrack, -// pointsMap: [ -// { x: 700, y: 350 }, -// { x: 850, y: 350 }, -// { x: 850, y: 450 }, -// { x: 700, y: 450 }, -// ], -// numberOfPoints: 4, -// }; - -// let cvatCanvasShapeList = []; -// let cvatFiltesList = []; - -// function checkingFilterApplication(ids) { -// for (let i = 0; i < cvatCanvasShapeList.length; i++) { -// if (ids.indexOf(cvatCanvasShapeList[i]) > -1) { -// cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('exist'); -// cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('exist'); -// } else { -// cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('not.exist'); -// cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('not.exist'); -// } -// } -// } - -// before(() => { -// cy.openTask(taskName); -// cy.addNewLabel(labelShape, additionalAttrsLabelShape); -// cy.addNewLabel(labelTrack, additionalAttrsLabelTrack); -// cy.openJob(); -// }); - -// describe(`Testing case "${caseId}"`, () => { -// it('Draw several objects (different shapes, tracks, labels)', () => { -// cy.createPolygon(createPolygonShape); -// cy.createRectangle(createRectangleTrack2Points); -// cy.createRectangle(createRectangleShape4Points); -// cy.createPolygon(createPolygonTrack); -// cy.get('.cvat_canvas_shape').then(($cvatCanvasShapeList) => { -// for (let i = 0; i < $cvatCanvasShapeList.length; i++) { -// cvatCanvasShapeList.push(Number($cvatCanvasShapeList[i].id.match(/\d+$/))); -// } -// }); -// }); -// it('Filter: shape=="polygon". Only the polygon exist.', () => { -// const textFilter = 'shape=="polygon"'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(false, textFilter); // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4 -// checkingFilterApplication([1, 4]); -// }); -// it('Filter: shape=="polygon" | shape=="rectangle". Only the rectangle and polygon exist.', () => { -// const textFilter = 'shape=="polygon" | shape=="rectangle"'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4 -// checkingFilterApplication([1, 2, 3, 4]); -// }); -// it('Filter: type=="shape". Only the objects with shape type exist.', () => { -// const textFilter = 'type=="shape"'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3 -// checkingFilterApplication([1, 3]); -// }); -// it('Filter: label=="track 4 points". Only the polygon exist.', () => { -// const textFilter = `label=="${labelTrack}"`; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 -// checkingFilterApplication([2, 4]); -// }); -// it('Filter: attr["count points"] == "4". Only the objects with same attr exist.', () => { -// const textFilter = 'attr["count points"] == "4"'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 -// checkingFilterApplication([2, 4]); -// }); -// it('Filter: width >= height. All objects exist.', () => { -// const textFilter = 'width >= height'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4 -// checkingFilterApplication([1, 2, 3, 4]); -// }); -// it('Filter: clientID == 4. Only the objects with same id exist (polygon track).', () => { -// const textFilter = 'clientID == 4'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_7, #cvat-objects-sidebar-state-item-4 -// checkingFilterApplication([4]); -// }); -// it('Filter: (label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60). Only the objects polygon and rectangle exist.', () => { -// const textFilter = -// '(label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60)'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 -// checkingFilterApplication([2, 4]); -// }); -// it('Filter: (( label==["shape 3 points"]) | (attr["type"]=="shape" & width > 50)) & (height > 50 & (clientID == serverID))). All objects not exist.', () => { -// const textFilter = -// '(( label==["points shape"]) | (attr["type"]=="shape" & width > 50)) & (height > 50 & (clientID == serverID)))'; -// cvatFiltesList.push(textFilter); -// cy.writeFilterValue(true, textFilter); -// checkingFilterApplication([]); -// }); -// it('Verify to show all filters', () => { -// cvatFiltesList.forEach(function (filterValue) { -// cy.contains('.cvat-annotations-filters-input-history-element', filterValue); -// }); -// }); -// it('Select filter: type=="shape"', () => { -// cy.selectFilterValue(true, 'type=="shape"'); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3 -// checkingFilterApplication([1, 3]); -// }); -// it('Select filter: clientID == 4', () => { -// cy.selectFilterValue(true, 'clientID == 4'); // #cvat_canvas_shape_7, #cvat-objects-sidebar-state-item-4 -// checkingFilterApplication([4]); -// }); -// it('Select two filters', () => { -// const textFirstFilter = -// '(label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60)'; // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 -// const textSecondFilter = 'shape=="polygon"'; // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4 -// cy.selectFilterValue(true, textFirstFilter); -// cy.selectFilterValue(false, textSecondFilter); -// checkingFilterApplication([1, 2, 4]); -// }); -// }); -// }); +import { taskName } from '../../support/const'; + +context('Filters functionality.', () => { + const caseId = '18'; + const labelShape = 'shape 3 points'; + const additionalAttrsLabelShape = [ + { additionalAttrName: 'type', additionalValue: 'shape', typeAttribute: 'Text' }, + { additionalAttrName: 'count points', additionalValue: '3', typeAttribute: 'Text' }, + { additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' }, + ]; + const labelTrack = 'track 4 points'; + const additionalAttrsLabelTrack = [ + { additionalAttrName: 'type', additionalValue: 'track', typeAttribute: 'Text' }, + { additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' }, + { additionalAttrName: 'count points', additionalValue: '4', typeAttribute: 'Text' }, + ]; + + const createPolygonShape = { + reDraw: false, + type: 'Shape', + labelName: labelShape, + pointsMap: [ + { x: 200, y: 200 }, + { x: 250, y: 200 }, + { x: 250, y: 240 }, + ], + complete: true, + numberOfPoints: null, + }; + const createRectangleTrack2Points = { + points: 'By 2 Points', + type: 'Track', + labelName: labelTrack, + firstX: 260, + firstY: 200, + secondX: 360, + secondY: 250, + }; + const createRectangleShape4Points = { + points: 'By 4 Points', + type: 'Shape', + labelName: labelShape, + firstX: 550, + firstY: 350, + secondX: 650, + secondY: 350, + thirdX: 650, + thirdY: 450, + fourthX: 550, + fourthY: 450, + }; + const createPolygonTrack = { + reDraw: false, + type: 'Track', + labelName: labelTrack, + pointsMap: [ + { x: 700, y: 350 }, + { x: 850, y: 350 }, + { x: 850, y: 450 }, + { x: 700, y: 450 }, + ], + numberOfPoints: 4, + }; + + let cvatCanvasShapeList = []; + let cvatFiltesList = []; + + function checkingFilterApplication(ids) { + for (let i = 0; i < cvatCanvasShapeList.length; i++) { + if (ids.indexOf(cvatCanvasShapeList[i]) > -1) { + cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('exist'); + cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('exist'); + } else { + cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('not.exist'); + cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('not.exist'); + } + } + } + + before(() => { + cy.openTask(taskName); + cy.addNewLabel(labelShape, additionalAttrsLabelShape); + cy.addNewLabel(labelTrack, additionalAttrsLabelTrack); + cy.openJob(); + }); + + describe(`Testing case "${caseId}"`, () => { + it('Draw several objects (different shapes, tracks, labels)', () => { + cy.createPolygon(createPolygonShape); + cy.createRectangle(createRectangleTrack2Points); + cy.createRectangle(createRectangleShape4Points); + cy.createPolygon(createPolygonTrack); + cy.get('.cvat_canvas_shape').then(($cvatCanvasShapeList) => { + for (let i = 0; i < $cvatCanvasShapeList.length; i++) { + cvatCanvasShapeList.push(Number($cvatCanvasShapeList[i].id.match(/\d+$/))); + } + }); + }); + + it('Filter: shape == "polygon". Only the polygon exist.', () => { + const textFilter = 'shape == "polygon"'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 0, + field: 'Shape', + operator: '==', + value: 'polygon', + submit: true, + }); + checkingFilterApplication([1, 4]); // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: (shape == "polygon" || shape == "rectangle"). Only the rectangle and polygon exist.', () => { + const textFilter = '(shape == "polygon" || shape == "rectangle")'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ groupIndex: 0, ruleIndex: 0, field: 'Shape', operator: '==', value: 'polygon' }); + cy.addFiltersRule(0); + cy.setGroupCondition(0, 'Or'); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 1, + field: 'Shape', + operator: '==', + value: 'rectangle', + submit: true, + }); + checkingFilterApplication([1, 2, 3, 4]); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: type == "shape". Only the objects with shape type exist.', () => { + const textFilter = 'type == "shape"'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ groupIndex: 0, ruleIndex: 0, field: 'Type', operator: '==', value: 'shape', submit: true }); + checkingFilterApplication([1, 3]); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3 + cy.clearFilters(); // Clear filters + }); + + it('Filter: label == "track 4 points". Only the polygon exist.', () => { + const textFilter = 'label == "track 4 points"'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 0, + field: 'Label', + operator: '==', + value: labelTrack, + submit: true, + }); + checkingFilterApplication([2, 4]); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: Attributes count points 4. Only the objects with same attr exist.', () => { + const textFilter = 'attr.track 4 points.count points == "4"'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 0, + field: 'Attributes', + operator: '==', + value: 4, + label: labelTrack, + labelAttr: 'count points', + submit: true, + }); + checkingFilterApplication([2, 4]); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: width >= height. All objects exist.', () => { + const textFilter = 'width >= height'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 0, + field: 'Width', + operator: '>=', + valueSource: 'Field', + value: 'Height', + submit: true, + }); + checkingFilterApplication([1, 2, 3, 4]); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: objectID == 4. Only the objects with same id exist (polygon track).', () => { + const textFilter = 'objectID == 4'; + cvatFiltesList.push(textFilter); + cy.addFiltersRule(0); + cy.setFilter({ groupIndex: 0, ruleIndex: 0, field: 'ObjectID', operator: '==', value: 4, submit: true }); + checkingFilterApplication([4]); // #cvat_canvas_shape_4, #cvat-objects-sidebar-state-item-4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: ((label == "shape 3 points" && attr.track 4 points.type == "polylines") || (label == "track 4 points" && width > 60)). Only the objects polygon and rectangle exist.', () => { + const textFilter = + '((label == "shape 3 points" && attr.track 4 points.type == "polylines") || (label == "track 4 points" && width > 60))'; + cvatFiltesList.push(textFilter); + cy.addFiltersGroup(0); + cy.addFiltersGroup(0); + cy.setFilter({ groupIndex: 1, ruleIndex: 0, field: 'Label', operator: '==', value: labelShape }); + cy.addFiltersRule(1); + cy.setFilter({ + groupIndex: 1, + ruleIndex: 1, + field: 'Attributes', + operator: '==', + value: 'polylines', + label: labelTrack, + labelAttr: 'type', + }); + cy.setFilter({ groupIndex: 2, ruleIndex: 2, field: 'Label', operator: '==', value: labelTrack }); + cy.addFiltersRule(2); + cy.setGroupCondition(0, 'Or'); + cy.setFilter({ groupIndex: 2, ruleIndex: 3, field: 'Width', operator: '>', value: '60', submit: true }); + checkingFilterApplication([2, 4]); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 + cy.clearFilters(); // Clear filters + }); + + it('Filter: ((label == "shape 3 points" || (attr.shape 3 points.type == "shape" && width > 50)) && height > 50). Only the rectangle shape exist.', () => { + const textFilter = + '((label == "shape 3 points" || (attr.shape 3 points.type == "shape" && width > 50)) && height > 50)'; + cvatFiltesList.push(textFilter); + cy.addFiltersGroup(0); + cy.setFilter({ groupIndex: 1, ruleIndex: 0, field: 'Label', operator: '==', value: labelShape }); + cy.addFiltersGroup(1); + cy.setGroupCondition(1, 'Or'); + cy.setFilter({ + groupIndex: 2, + ruleIndex: 1, + field: 'Attributes', + operator: '==', + value: 'shape', + label: labelShape, + labelAttr: 'type', + }); + cy.addFiltersRule(2); + cy.setFilter({ groupIndex: 2, ruleIndex: 2, field: 'Width', operator: '>', value: 50 }); + cy.addFiltersGroup(0); + cy.setGroupCondition(0, 'And'); + cy.setFilter({ groupIndex: 3, ruleIndex: 3, field: 'Height', operator: '>', value: 50, submit: true }); + checkingFilterApplication([3]); // #cvat_canvas_shape_3, #cvat-objects-sidebar-state-item-3 + cy.clearFilters(); // Clear filters + }); + + it('Verify to show all filters', () => { + cy.сheckFiltersModalOpened(); + cy.get('.recently-used-wrapper').trigger('mouseover'); + cy.get('.ant-dropdown') + .not('.ant-dropdown-hidden') + .within(() => { + cvatFiltesList.forEach(function (filterValue) { + cy.contains('[role="menuitem"]', filterValue); + }); + }); + }); + + it('Select filter: type == "shape"', () => { + cy.selectFilterValue('type == "shape"'); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3 + checkingFilterApplication([1, 3]); + }); + + it('Select filter: objectID == 4', () => { + cy.selectFilterValue('objectID == 4'); // #cvat_canvas_shape_4, #cvat-objects-sidebar-state-item-4 + checkingFilterApplication([4]); + }); + }); +}); diff --git a/tests/cypress/integration/actions_tasks_objects/issue_1444_filter_property_shape.js b/tests/cypress/integration/actions_tasks_objects/issue_1444_filter_property_shape.js index 285981ccb4c..427bc84d6b6 100644 --- a/tests/cypress/integration/actions_tasks_objects/issue_1444_filter_property_shape.js +++ b/tests/cypress/integration/actions_tasks_objects/issue_1444_filter_property_shape.js @@ -39,17 +39,27 @@ context('Filter property "shape" work correctly', () => { cy.createRectangle(createRectangleShape2Points); cy.get('#cvat-objects-sidebar-state-item-1').should('contain', '1').and('contain', 'RECTANGLE SHAPE'); }); + it('Create a polygon', () => { cy.createPolygon(createPolygonShape); cy.get('#cvat-objects-sidebar-state-item-2').should('contain', '2').and('contain', 'POLYGON SHAPE'); }); - // TODO: Update with new filters UI - // it('Input filter "shape == "polygon""', () => { - // cy.get('.cvat-annotations-filters-input').type('shape == "polygon"{Enter}'); - // }); - // it('Only polygon is visible', () => { - // cy.get('#cvat_canvas_shape_2').should('exist'); - // cy.get('#cvat_canvas_shape_1').should('not.exist'); - // }); + + it('Set a filter: "shape == "polygon""', () => { + cy.addFiltersRule(0); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 0, + field: 'Shape', + operator: '==', + value: 'polygon', + submit: true, + }); + }); + + it('Only polygon is visible', () => { + cy.get('#cvat_canvas_shape_2').should('exist'); + cy.get('#cvat_canvas_shape_1').should('not.exist'); + }); }); }); diff --git a/tests/cypress/integration/actions_tasks_objects/issue_2485_navigation_empty_frames.js b/tests/cypress/integration/actions_tasks_objects/issue_2485_navigation_empty_frames.js index 0366764b810..4aa7ab8ed22 100644 --- a/tests/cypress/integration/actions_tasks_objects/issue_2485_navigation_empty_frames.js +++ b/tests/cypress/integration/actions_tasks_objects/issue_2485_navigation_empty_frames.js @@ -33,11 +33,18 @@ context('Navigation to empty frames', () => { cy.createRectangle(createRectangleShape2Points); }); - // TODO: Update with new filters UI - // it('Input a filter to see the created objects.', () => { - // cy.writeFilterValue(false, 'shape=="rectangle"'); - // cy.get('#cvat_canvas_shape_2').should('exist'); - // }); + it('Set a filter to see the created objects.', () => { + cy.addFiltersRule(0); + cy.setFilter({ + groupIndex: 0, + ruleIndex: 0, + field: 'Shape', + operator: '==', + value: 'rectangle', + submit: true, + }); + cy.get('#cvat_canvas_shape_2').should('exist'); + }); it('Go to 3rd frame.', () => { cy.goCheckFrameNumber(3); @@ -51,22 +58,21 @@ context('Navigation to empty frames', () => { } }); - // TODO: Update with new filters UI - // it("Press go previous with a filter. CVAT get 2nd frame. Press again. Frame wasn't changed.", () => { - // for (let i = 1; i <= 2; i++) { - // cy.get('.cvat-player-previous-button-filtered').click({ force: true }); - // cy.checkFrameNum(2); - // cy.get('#cvat_canvas_shape_1').should('exist'); - // } - // }); + it("Press go previous with a filter. CVAT get 2nd frame. Press again. Frame wasn't changed.", () => { + for (let i = 1; i <= 2; i++) { + cy.get('.cvat-player-previous-button-filtered').click({ force: true }); + cy.checkFrameNum(2); + cy.get('#cvat_canvas_shape_1').should('exist'); + } + }); - // it("Press go next with a filter. CVAT get 4th frame. Press again. Frame wasn't changed.", () => { - // for (let i = 1; i <= 2; i++) { - // cy.get('.cvat-player-next-button-filtered').click({ force: true }); - // cy.checkFrameNum(4); - // cy.get('#cvat_canvas_shape_2').should('exist'); - // } - // }); + it("Press go next with a filter. CVAT get 4th frame. Press again. Frame wasn't changed.", () => { + for (let i = 1; i <= 2; i++) { + cy.get('.cvat-player-next-button-filtered').click({ force: true }); + cy.checkFrameNum(4); + cy.get('#cvat_canvas_shape_2').should('exist'); + } + }); it('Change navigation buttons mode to "Go next/previous to an empty frame".', () => { for (const i of ['previous', 'next']) { @@ -75,17 +81,16 @@ context('Navigation to empty frames', () => { } }); - // TODO: Update with new filters UI - // it('Go previous to an empty frame. CVAT get 3rd frame.', () => { - // cy.get('.cvat-player-previous-button-empty').click({ force: true }); - // cy.checkFrameNum(3); - // cy.get('.cvat_canvas_shape').should('not.exist'); - // }); + it('Go previous to an empty frame. CVAT get 3rd frame.', () => { + cy.get('.cvat-player-previous-button-empty').click({ force: true }); + cy.checkFrameNum(3); + cy.get('.cvat_canvas_shape').should('not.exist'); + }); - // it('Go next to an empty frame. CVAT get 5th frame.', () => { - // cy.get('.cvat-player-next-button-empty').click({ force: true }); - // cy.checkFrameNum(5); - // cy.get('.cvat_canvas_shape').should('not.exist'); - // }); + it('Go next to an empty frame. CVAT get 5th frame.', () => { + cy.get('.cvat-player-next-button-empty').click({ force: true }); + cy.checkFrameNum(5); + cy.get('.cvat_canvas_shape').should('not.exist'); + }); }); }); diff --git a/tests/cypress/integration/actions_tasks_objects/issue_2690_filters_help_window.js b/tests/cypress/integration/actions_tasks_objects/issue_2690_filters_help_window.js deleted file mode 100644 index 408ee24b549..00000000000 --- a/tests/cypress/integration/actions_tasks_objects/issue_2690_filters_help_window.js +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -/// - -import { taskName } from '../../support/const'; - -context('Annotation filter help dialog window.', () => { - const issueId = '2690'; - - before(() => { - cy.openTaskJob(taskName); - }); - - describe(`Testing issue "${issueId}"`, () => { - // TODO: Update with new filters UI - // it('Open annotation filters help dialog window. The window is visible.', () => { - // cy.get('.cvat-annotations-filters-input').within(() => { - // // class="ant-select-selection-placeholder" has CSS pointer-events: none - // cy.get('.ant-select-selection-placeholder').invoke('css', 'pointer-events', 'auto'); // Replace CSS "pointer-events" to auto - // cy.get('[aria-label="filter"]').click(); - // }); - // cy.get('.cvat-annotations-filters-help-modal-window').should('exist').and('be.visible'); - // }); - // it('Close annotation filters help dialog window. The window is closed.', () => { - // cy.get('.cvat-annotations-filters-help-modal-window').within(() => { - // cy.contains('button', 'OK').click(); - // }); - // cy.get('.cvat-annotations-filters-help-modal-window').should('not.exist'); - // }); - }); -}); diff --git a/tests/cypress/integration/actions_tasks_objects/issue_2992_crop_polygon_properly.js b/tests/cypress/integration/actions_tasks_objects/issue_2992_crop_polygon_properly.js new file mode 100644 index 00000000000..965bc5285bd --- /dev/null +++ b/tests/cypress/integration/actions_tasks_objects/issue_2992_crop_polygon_properly.js @@ -0,0 +1,36 @@ +// Copyright (C) 2020-2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/// + +import { taskName, labelName } from '../../support/const'; + +context('Crop polygon properly.', () => { + const issueId = '2992'; + + const createPolygonShape = { + reDraw: false, + type: 'Shape', + labelName: labelName, + pointsMap: [ + { x: 200, y: 500 }, + { x: 200, y: 200 }, + { x: 10, y: 250 }, + ], + complete: true, + numberOfPoints: null, + }; + + before(() => { + cy.openTaskJob(taskName); + }); + + describe(`Testing issue "${issueId}"`, () => { + it('Draw a polygon with vertices outside the image', () => { + cy.createPolygon(createPolygonShape); + cy.get('.cvat-canvas-container').click(58, 238); + cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated'); + }); + }); +}); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index fbb896c1043..373fff9b4a8 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -572,34 +572,6 @@ Cypress.Commands.add('getScaleValue', () => { }); }); -Cypress.Commands.add('writeFilterValue', (clear, filterValue) => { - if (clear) { - cy.get('.cvat-annotations-filters-input').within(() => { - cy.get('[aria-label="close-circle"]').click(); - }); - } - cy.get('.cvat-annotations-filters-input') - .type(`${filterValue}{Enter}`) - .within(() => { - cy.get('.ant-select-selection-item-content').should('have.text', filterValue); - }); -}); - -Cypress.Commands.add('selectFilterValue', (clear, filterValue) => { - if (clear) { - cy.get('.cvat-annotations-filters-input').within(() => { - cy.get('[aria-label="close-circle"]').click(); - }); - } - cy.get('body').click(); - cy.get('.cvat-annotations-filters-input').click(); - cy.contains('.cvat-annotations-filters-input-history-element', filterValue).scrollIntoView().click(); - cy.get('body').click(); - cy.get('.cvat-annotations-filters-input').within(() => { - cy.contains('.ant-select-selection-item-content', filterValue); - }); -}); - Cypress.Commands.add('goCheckFrameNumber', (frameNum) => { cy.get('.cvat-player-frame-selector').within(() => { cy.get('input[role="spinbutton"]') diff --git a/tests/cypress/support/commands_filters_feature.js b/tests/cypress/support/commands_filters_feature.js new file mode 100644 index 00000000000..eefd04490e8 --- /dev/null +++ b/tests/cypress/support/commands_filters_feature.js @@ -0,0 +1,139 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/// + +Cypress.Commands.add('сheckFiltersModalOpened', () => { + cy.document().then((doc) => { + const filterModal = Array.from(doc.querySelectorAll('.cvat-filters-modal-visible')); + if (filterModal.length === 0) { + cy.contains('.cvat-annotation-header-button', 'Filters').click(); + } + }); +}); + +Cypress.Commands.add('collectGroupID', () => { + let groupDataID = []; + cy.get('.group').then(($group) => { + for (let i = 0; i < $group.length; i++) { + groupDataID.push($group[i].dataset.id); + } + return groupDataID; + }); +}); + +Cypress.Commands.add('collectRuleID', () => { + let ruleDataID = []; + cy.get('.rule').then(($rule) => { + for (let i = 0; i < $rule.length; i++) { + ruleDataID.push($rule[i].dataset.id); + } + return ruleDataID; + }); +}); + +Cypress.Commands.add('clearFilters', () => { + cy.сheckFiltersModalOpened(); + cy.contains('button', 'Clear filters').click(); +}); + +Cypress.Commands.add('addFiltersGroup', (groupIndex) => { + cy.сheckFiltersModalOpened(); + cy.collectGroupID().then((groupIdIndex) => { + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`).contains('button', 'Add group').first().click(); + }); +}); + +Cypress.Commands.add('addFiltersRule', (groupIndex) => { + cy.сheckFiltersModalOpened(); + cy.collectGroupID().then((groupIdIndex) => { + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`).contains('button', 'Add rule').click(); + }); +}); + +Cypress.Commands.add('setGroupCondition', (groupIndex, condition) => { + cy.сheckFiltersModalOpened(); + cy.collectGroupID().then((groupIdIndex) => { + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`).within(() => { + cy.get('.group--header').first().trigger('mouseover'); + cy.contains('button', condition).click({ force: true }); + }); + }); +}); + +Cypress.Commands.add( + 'setFilter', + ({groupIndex, ruleIndex, field, operator, valueSource, value, label, labelAttr, submit}) => { + cy.сheckFiltersModalOpened(); + cy.collectGroupID().then((groupIdIndex) => { + cy.collectRuleID().then((ruleIdIndex) => { + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`) + .find(`[data-id="${ruleIdIndex[ruleIndex]}"]`) + .within(() => { + cy.contains('button', 'Select field').click(); + }); + if (field === 'Attributes') { + cy.get('.ant-dropdown') + .not('.ant-dropdown-hidden') + .contains('[role="menuitem"]', field) + .trigger('mouseover'); + cy.get('.ant-dropdown-menu-sub').contains(label).trigger('mouseover', { force: true }); + cy.contains('.ant-dropdown-menu-item-only-child', labelAttr).click(); + } else { + cy.get('.ant-dropdown').not('.ant-dropdown-hidden').contains('[role="menuitem"]', field).click(); + } + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`) + .find(`[data-id="${ruleIdIndex[ruleIndex]}"]`) + .within(() => { + cy.get('[type="search"]').first().click({ force: true }); + }); + cy.get(`[label="${operator}"]`).last().click(); + if (valueSource) { + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`) + .find(`[data-id="${ruleIdIndex[ruleIndex]}"]`) + .within(() => { + cy.get('[aria-label="ellipsis"]').trigger('mouseover'); + }); + cy.contains('Select value source').parents('[role="tooltip"]').contains(valueSource).click(); + } + cy.get(`[data-id="${groupIdIndex[groupIndex]}"]`) + .find(`[data-id="${ruleIdIndex[ruleIndex]}"]`) + .within(() => { + if (field === 'Attributes') { + cy.get('[placeholder="Enter string"]').last().type(`${value}{Enter}`); + } else { + if (!valueSource) { + if (field === 'ObjectID' || field === 'Width' || field === 'Height') { + cy.get('[placeholder="Enter number"]').type(`${value}{Enter}`); + } else { + cy.get('[type="search"]').last().type(`${value}{Enter}`); + } + } else { + cy.contains('[type="button"]', 'Select field ').click(); + } + } + }); + if (valueSource) { + cy.get('.ant-dropdown').not('.ant-dropdown-hidden').contains('[role="menuitem"]', value).click(); + } + cy.get('.cvat-filters-modal').within(() => { + submit ? cy.contains('button', 'Submit').click() : null; + }); + }); + }); + }, +); + +Cypress.Commands.add('selectFilterValue', (filterValue) => { + cy.сheckFiltersModalOpened(); + cy.get('.recently-used-wrapper').trigger('mouseover'); + cy.get('.ant-dropdown') + .not('.ant-dropdown-hidden') + .within(() => { + cy.contains('[role="menuitem"]', new RegExp(`^${filterValue}$`)).click(); + }); + cy.get('.cvat-filters-modal').within(() => { + cy.contains('button', 'Submit').click(); + }); +}); diff --git a/tests/cypress/support/index.js b/tests/cypress/support/index.js index 91de8794d9f..2a064b04edd 100644 --- a/tests/cypress/support/index.js +++ b/tests/cypress/support/index.js @@ -6,6 +6,7 @@ require('./commands'); require('./commands_projects'); require('./commands_review_pipeline'); require('./commands_canvas3d'); +require('./commands_filters_feature'); require('@cypress/code-coverage/support'); require('cypress-plugin-tab');