diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d136f0cc7e48..5d36ed29cd4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,6 +38,12 @@ jobs: cd ./tests npm ci npx cypress run --headless --browser chrome + - name: Uploading cypress screenshots as an artifact + if: failure() + uses: actions/upload-artifact@v2 + with: + name: cypress_screenshots + path: ${{ github.workspace }}/tests/cypress/screenshots - name: Collect coverage data env: HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} diff --git a/.github/workflows/publish_docker_images.yml b/.github/workflows/publish_docker_images.yml index 44aad7e9137b..4c7353ee2c4f 100644 --- a/.github/workflows/publish_docker_images.yml +++ b/.github/workflows/publish_docker_images.yml @@ -33,7 +33,14 @@ jobs: run: | cd ./tests npm ci - npx cypress run --headless --browser chrome + npm run cypress:run:chrome + - name: Uploading cypress screenshots as an artifact + if: failure() + uses: actions/upload-artifact@v2 + with: + name: cypress_screenshots + path: ${{ github.workspace }}/tests/cypress/screenshots + - name: Login to Docker Hub uses: docker/login-action@v1 with: diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index ecce8547728e..90f53049f409 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -26,3 +26,9 @@ jobs: cd ./tests npm ci npm run cypress:run:firefox + - name: Uploading cypress screenshots as an artifact + if: failure() + uses: actions/upload-artifact@v2 + with: + name: cypress_screenshots + path: ${{ github.workspace }}/tests/cypress/screenshots diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a65504d9f22..3c338dc0ff9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 () +- [WiderFace](http://shuoyang1213.me/WIDERFACE/) format support () +- [VGGFace2](https://github.com/ox-vgg/vgg_face2) format support () - Ability of upload manifest for dataset with images () ### Changed @@ -61,7 +63,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated the path to python for DL models inside automatic annotation documentation () - Fixed of receiving function variable () - Shortcuts with CAPSLOCK enabled and with non-US languages activated () +- Prevented creating several issues for the same object () - Fixed label editor name field validator () +- An error about track shapes outside of the task frames during export () +- Fixed project search field updating () +- Fixed export error when invalid polygons are present in overlapping frames () +- Fixed image quality option for tasks created from images () ### Security diff --git a/README.md b/README.md index dd8b68c6b56d..ca8b1739d6b5 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ For more information about supported formats look at the | [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X | | [ImageNet](http://www.image-net.org) | X | X | | [CamVid](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) | X | X | +| [WIDER Face](http://shuoyang1213.me/WIDERFACE/) | X | X | +| [VGGFace2](https://github.com/ox-vgg/vgg_face2) | X | X | ## Deep learning serverless functions for automatic labeling diff --git a/cvat-data/package-lock.json b/cvat-data/package-lock.json index 75279ba3c41a..cd0c0979e02d 100644 --- a/cvat-data/package-lock.json +++ b/cvat-data/package-lock.json @@ -1208,9 +1208,9 @@ "dev": true }, "async-mutex": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.0.tgz", - "integrity": "sha512-6VIpUM7s37EMXvnO3TvujgaS6gx4yJby13BhxovMYSap7nrbS0gJ1UzGcjD+HElNSdTz/+IlAIqj7H48N0ZlyQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.1.tgz", + "integrity": "sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw==", "requires": { "tslib": "^2.1.0" }, @@ -4709,9 +4709,9 @@ "dev": true }, "jszip": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", - "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -5423,9 +5423,9 @@ } }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parent-module": { "version": "1.0.1", diff --git a/cvat-data/package.json b/cvat-data/package.json index d57e36c99865..2f7517667986 100644 --- a/cvat-data/package.json +++ b/cvat-data/package.json @@ -21,8 +21,8 @@ "worker-loader": "^2.0.0" }, "dependencies": { - "async-mutex": "^0.3.0", - "jszip": "3.5.0" + "async-mutex": "^0.3.1", + "jszip": "3.6.0" }, "scripts": { "patch": "cd src/js && patch --dry-run --forward -p0 < 3rdparty_patch.diff >> /dev/null && patch -p0 < 3rdparty_patch.diff; true", diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 0cd1ddc4cb36..b45037afa037 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,23 +1,15 @@ { "name": "cvat-ui", - "version": "1.15.1", + "version": "1.15.3", "lockfileVersion": 1, "requires": true, "dependencies": { - "@ant-design/colors": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-5.0.0.tgz", - "integrity": "sha512-Pe1rYorgVC1v4f+InDXvIlQH715pO1g7BsOhy/ehX/U6ebPKqojmkYJKU3lF+84Zmvyar7ngZ28hesAa1nWjLg==", - "requires": { - "@ctrl/tinycolor": "^3.1.6" - } - }, "@ant-design/icons": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.4.0.tgz", - "integrity": "sha512-+X44IouK56JbP3r7zM+Zoykv5wQlXBlxY0NTaFXGpiyYSS/Bh6HIo9aTF62QkSuDTqA3UpeNVTRFioKKRmkWDQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.5.0.tgz", + "integrity": "sha512-ZAKJcmr4DBV3NWr8wm2dCxNKN4eFrX+qCaPsuFejP6FRsf+m5OKxvCVi9bSp1lmKWeOI5yECAx5s0uFm4QHuPw==", "requires": { - "@ant-design/colors": "^5.0.0", + "@ant-design/colors": "^6.0.0", "@ant-design/icons-svg": "^4.0.0", "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", @@ -25,14 +17,27 @@ "rc-util": "^5.0.1" }, "dependencies": { + "@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "requires": { + "@ctrl/tinycolor": "^3.4.0" + } + }, "@babel/runtime": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz", - "integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } }, + "@ctrl/tinycolor": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", + "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" + }, "regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", @@ -58,9 +63,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -1076,11 +1081,6 @@ "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", "dev": true }, - "@ctrl/tinycolor": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.2.0.tgz", - "integrity": "sha512-cP1tbXA1qJp/er2CJaO+Pbe38p7RlhV9WytUxUe79xj++Q6s/jKVvzJ9U2dF9f1/lZAdG+j94A38CsNR+uW4gw==" - }, "@eslint/eslintrc": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", @@ -1256,9 +1256,9 @@ "dev": true }, "@types/react": { - "version": "16.14.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.3.tgz", - "integrity": "sha512-zPrXn03hmPYqh9DznqSFQsoRtrQ4aHgnZDO+hMGvsE/PORvDTdJCHQ6XvJV31ic+0LzF73huPFXUb++W6Kri0Q==", + "version": "16.14.4", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.4.tgz", + "integrity": "sha512-ETj7GbkPGjca/A4trkVeGvoIakmLV6ZtX3J8dcmOpzKzWVybbrOxanwaIPG71GZwImoMDY6Fq4wIe34lEqZ0FQ==", "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1274,9 +1274,9 @@ } }, "@types/react-dom": { - "version": "16.9.10", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.10.tgz", - "integrity": "sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw==", + "version": "16.9.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.11.tgz", + "integrity": "sha512-3UuR4MoWf5spNgrG6cwsmT9DdRghcR4IDFOzNZ6+wcmacxkFykcb5ji0nNVm9ckBT4BCxvCrJJbM4+EYsEEVIg==", "requires": { "@types/react": "^16" } @@ -1886,12 +1886,12 @@ } }, "antd": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.12.2.tgz", - "integrity": "sha512-xB7sGg2qM/Sl3azjbc2RbJQ6cTr2Fos0AYZw2gTLLWtKhOyO3FUH7EBsL17GOkVnEDwMmBYtVXLhMgPM+e4gbA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.12.3.tgz", + "integrity": "sha512-opzbxm6jZB+Pc9M0Kuo6+4WmniB59NJ4i/qBr6ExyMtl9hMgsGNH8GuDXsp2xgTzfq5hyobdLci2DAuPMrf0Zg==", "requires": { - "@ant-design/colors": "^5.0.0", - "@ant-design/icons": "^4.4.0", + "@ant-design/colors": "^6.0.0", + "@ant-design/icons": "^4.5.0", "@ant-design/react-slick": "~0.28.1", "@babel/runtime": "^7.12.5", "array-tree-filter": "^2.1.0", @@ -1934,14 +1934,40 @@ "warning": "^4.0.3" }, "dependencies": { + "@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "requires": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "@ant-design/icons": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.5.0.tgz", + "integrity": "sha512-ZAKJcmr4DBV3NWr8wm2dCxNKN4eFrX+qCaPsuFejP6FRsf+m5OKxvCVi9bSp1lmKWeOI5yECAx5s0uFm4QHuPw==", + "requires": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.0.0", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "insert-css": "^2.0.0", + "rc-util": "^5.0.1" + } + }, "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } }, + "@ctrl/tinycolor": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", + "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" + }, "rc-util": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.8.1.tgz", @@ -3975,9 +4001,9 @@ } }, "csstype": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz", - "integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", + "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" }, "currently-unhandled": { "version": "0.4.1", @@ -15397,7 +15423,6 @@ "cvat-data": { "version": "file:../cvat-data", "requires": { - "async-mutex": "^0.3.0", "jszip": "3.5.0" }, "dependencies": { @@ -21332,9 +21357,9 @@ } }, "date-fns": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.17.0.tgz", - "integrity": "sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA==" + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.18.0.tgz", + "integrity": "sha512-NYyAg4wRmGVU4miKq5ivRACOODdZRY3q5WLmOJSq8djyzftYphU7dTHLcEtLqEvfqMKQ0jVv91P4BAwIjsXIcw==" }, "debug": { "version": "4.1.1", @@ -21733,24 +21758,24 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -27898,9 +27923,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -27925,9 +27950,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -27949,9 +27974,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -27976,9 +28001,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28002,9 +28027,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28042,9 +28067,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28082,9 +28107,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28107,9 +28132,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28133,9 +28158,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28158,9 +28183,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28186,9 +28211,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28216,9 +28241,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28256,9 +28281,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28271,9 +28296,9 @@ } }, "rc-notification": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.4.tgz", - "integrity": "sha512-VsN0ouF4uglE5g3C9oDsXLNYX0Sz++ZNUFYCswkxhpImYJ9u6nJOpyA71uOYDVCu6bAF54Y5Hi/b+EcnMzkepg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.5.tgz", + "integrity": "sha512-YIfhTSw+h5GsSdgMnuMx24wqiPlg3FeamuOlkh9RkyHx+SeZVAKzQ0juy2NGvPEF2hDWi5xTqxUqLdo0L2AmGg==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -28282,9 +28307,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28308,9 +28333,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28347,9 +28372,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28376,9 +28401,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28400,9 +28425,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28425,9 +28450,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28451,9 +28476,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28466,9 +28491,9 @@ } }, "rc-select": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.3.tgz", - "integrity": "sha512-pMJ27VQRh5QbyGLSE+by4tORYucNFbZxON+Ywj81qjXAGMjvhMcOOvlv1RZRNdnZxaMwH//3iDPOf80b0AJxZg==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.5.tgz", + "integrity": "sha512-UElTMw0+XvYJmVfsHTWvLR42RKNf5qyN3Ed/JfuZQceIPK1/3ugGRjdEOKBsPmPyNB5389NAROCV4tQd9fmqwg==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -28480,9 +28505,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28507,9 +28532,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28532,9 +28557,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28557,9 +28582,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28584,9 +28609,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28612,9 +28637,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28638,9 +28663,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28677,9 +28702,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28692,9 +28717,9 @@ } }, "rc-tree": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.1.1.tgz", - "integrity": "sha512-ufq7CkWfvTQa+xMPzEWYfOjTfsEALlPr0/IyujEG4+4d8NdaR3e+0dc8LkkVWoe1VCcXV2FQqAsgr2z/ThFUrQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.1.2.tgz", + "integrity": "sha512-9yhhDqHxG8gOZfkZeHYT6oarzarzi37lDe5c2r72tq5dflvoayGqD2bMkL2KC7GQJPLknZrtCwAbewqvD/T6NQ==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -28704,9 +28729,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28731,9 +28756,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28758,9 +28783,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -28783,9 +28808,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz", - "integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", "requires": { "regenerator-runtime": "^0.13.4" } diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 5d8bf3b4b3ee..9330505fd154 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.15.1", + "version": "1.15.3", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { @@ -48,19 +48,19 @@ "worker-loader": "^2.0.0" }, "dependencies": { - "@ant-design/icons": "^4.4.0", + "@ant-design/icons": "^4.5.0", "@types/lodash": "^4.14.168", "@types/platform": "^1.3.3", - "@types/react": "^16.14.3", + "@types/react": "^16.14.4", "@types/react-color": "^3.0.4", - "@types/react-dom": "^16.9.10", + "@types/react-dom": "^16.9.11", "@types/react-redux": "^7.1.16", "@types/react-router": "^5.1.11", "@types/react-router-dom": "^5.1.7", "@types/react-share": "^3.0.3", "@types/redux-logger": "^3.0.8", "@types/resize-observer-browser": "^0.1.5", - "antd": "^4.12.2", + "antd": "^4.12.3", "copy-to-clipboard": "^3.3.1", "cvat-canvas": "file:../cvat-canvas", "cvat-canvas3d": "file:../cvat-canvas3d", diff --git a/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx b/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx index 579bec3129f0..1be37564d951 100644 --- a/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx +++ b/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import React, { ReactPortal, useEffect } from 'react'; +import React, { ReactPortal, useEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; import Tag from 'antd/lib/tag'; import { CheckOutlined, CloseCircleOutlined } from '@ant-design/icons'; @@ -25,6 +25,8 @@ export default function HiddenIssueLabel(props: Props): ReactPortal { id, message, top, left, resolved, onClick, highlight, blur, } = props; + const ref = useRef(null); + useEffect(() => { if (!resolved) { setTimeout(highlight); @@ -37,10 +39,21 @@ export default function HiddenIssueLabel(props: Props): ReactPortal { return ReactDOM.createPortal( { + if (ref.current !== null) { + const selfElement = ref.current; + if (event.deltaX > 0) { + selfElement.parentElement?.appendChild(selfElement); + } else { + selfElement.parentElement?.prepend(selfElement); + } + } + }} style={{ top, left }} className='cvat-hidden-issue-label' > diff --git a/cvat-ui/src/components/create-task-page/project-search-field.tsx b/cvat-ui/src/components/create-task-page/project-search-field.tsx index 450cbf953d14..9bab28e2b4fa 100644 --- a/cvat-ui/src/components/create-task-page/project-search-field.tsx +++ b/cvat-ui/src/components/create-task-page/project-search-field.tsx @@ -59,19 +59,23 @@ export default function ProjectSearchField(props: Props): JSX.Element { }; useEffect(() => { - if (value && !projects.filter((project) => project.id === value).length) { - core.projects.get({ id: value }).then((result: Project[]) => { - const [project] = result; - setProjects([ - ...projects, - { - id: project.id, - name: project.name, - }, - ]); - setSearchPhrase(project.name); - onSelect(project.id); - }); + if (value) { + if (!projects.filter((project) => project.id === value).length) { + core.projects.get({ id: value }).then((result: Project[]) => { + const [project] = result; + setProjects([ + ...projects, + { + id: project.id, + name: project.name, + }, + ]); + setSearchPhrase(project.name); + onSelect(project.id); + }); + } + } else { + setSearchPhrase(''); } }, [value]); diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index b6dbe1c17482..391b26bf588b 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -110,7 +110,7 @@ def filter_track_shapes(shapes): # Track and TrackedShape models don't expect these fields del track['interpolated_shapes'] for shape in segment_shapes: - del shape['keyframe'] + shape.pop('keyframe', None) track['shapes'] = segment_shapes track['frame'] = track['shapes'][0]['frame'] @@ -315,11 +315,14 @@ def _get_cost_threshold(): @staticmethod def _calc_objects_similarity(obj0, obj1, start_frame, overlap): def _calc_polygons_similarity(p0, p1): - overlap_area = p0.intersection(p1).area - if p0.area == 0 or p1.area == 0: # a line with many points - return 0 + if p0.is_valid and p1.is_valid: # check validity of polygons + overlap_area = p0.intersection(p1).area + if p0.area == 0 or p1.area == 0: # a line with many points + return 0 + else: + return overlap_area / (p0.area + p1.area - overlap_area) else: - return overlap_area / (p0.area + p1.area - overlap_area) + return 0 # if there's invalid polygon, assume similarity is 0 has_same_type = obj0["type"] == obj1["type"] has_same_label = obj0.get("label_id") == obj1.get("label_id") @@ -746,6 +749,10 @@ def interpolate(shape0, shape1): curr_frame = shape["frame"] prev_shape = shape + # keep at least 1 shape + if end_frame <= curr_frame: + break + if not prev_shape["outside"]: shape = copy(prev_shape) shape["frame"] = end_frame diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index 38444d4d18bc..a336a00715d5 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -267,6 +267,11 @@ def get_frame(idx): anno_manager = AnnotationManager(self._annotation_ir) for shape in sorted(anno_manager.to_shapes(self._db_task.data.size), key=lambda shape: shape.get("z_order", 0)): + if shape['frame'] not in self._frame_info: + # After interpolation there can be a finishing frame + # outside of the task boundaries. Filter it out to avoid errors. + # https://github.com/openvinotoolkit/cvat/issues/2827 + continue if 'track_id' in shape: if shape['outside']: continue diff --git a/cvat/apps/dataset_manager/formats/README.md b/cvat/apps/dataset_manager/formats/README.md index 38ff06762172..a89cf6e1fb5f 100644 --- a/cvat/apps/dataset_manager/formats/README.md +++ b/cvat/apps/dataset_manager/formats/README.md @@ -20,6 +20,8 @@ - [TF detection API](#tfrecord) - [ImageNet](#imagenet) - [CamVid](#camvid) + - [WIDER Face](#widerface) + - [VGGFace2](#vggface2) ## How to add a new annotation format support @@ -874,3 +876,64 @@ has own color which corresponds to a label. Uploaded file: a zip archive of the structure above - supported annotations: Polygons + +### [WIDER Face](http://shuoyang1213.me/WIDERFACE/) + +#### WIDER Face Dumper + +Downloaded file: a zip archive of the following structure: + +```bash +taskname.zip/ +├── labels.txt # optional +├── wider_face_split/ +│ └── wider_face__bbx_gt.txt +└── WIDER_/ + └── images/ + ├── 0--label0/ + │ └── 0_label0_image1.jpg + └── 1--label1/ + └── 1_label1_image2.jpg +``` + +- supported annotations: Rectangles (with attributes), Labels +- supported attributes: `blur`, `expression`, `illumination`, + `occluded` (both the annotation property & an attribute), `pose`, `invalid` + +#### WIDER Face Loader + +Uploaded file: a zip archive of the structure above + +- supported annotations: Rectangles (with attributes), Labels +- supported attributes: `blur`, `expression`, `illumination`, `occluded`, `pose`, `invalid` + +### [VGGFace2](https://github.com/ox-vgg/vgg_face2) + +#### VGGFace2 Dumper + +Downloaded file: a zip archive of the following structure: + +```bash +taskname.zip/ +├── labels.txt # optional +├── / +| ├── label0/ +| | └── image1.jpg +| └── label1/ +| └── image2.jpg +└── bb_landmark/ + ├── loose_bb_.csv + └── loose_landmark_.csv +# labels.txt +# n000001 car +label0 +label1 +``` + +- supported annotations: Rectangles, Points (landmarks - groups of 5 points) + +#### VGGFace2 Loader + +Uploaded file: a zip archive of the structure above + +- supported annotations: Rectangles, Points (landmarks - groups of 5 points) diff --git a/cvat/apps/dataset_manager/formats/camvid.py b/cvat/apps/dataset_manager/formats/camvid.py index bcd00b7a7bf1..a8fb50592e9a 100644 --- a/cvat/apps/dataset_manager/formats/camvid.py +++ b/cvat/apps/dataset_manager/formats/camvid.py @@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory -from datumaro.components.project import Dataset +from datumaro.components.dataset import Dataset from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, @@ -17,16 +17,15 @@ @exporter(name='CamVid', ext='ZIP', version='1.0') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - envt = dm_env.transforms - extractor = extractor.transform(envt.get('polygons_to_masks')) - extractor = extractor.transform(envt.get('boxes_to_masks')) - extractor = extractor.transform(envt.get('merge_instance_segments')) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) + dataset.transform('polygons_to_masks') + dataset.transform('boxes_to_masks') + dataset.transform('merge_instance_segments') label_map = make_colormap(task_data) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('camvid').convert(extractor, - save_dir=temp_dir, save_images=save_images, apply_colormap=True, + dataset.export(temp_dir, 'camvid', + save_images=save_images, apply_colormap=True, label_map={label: label_map[label][0] for label in label_map}) make_zip_archive(temp_dir, dst_file) @@ -36,7 +35,6 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) - dataset = dm_env.make_importer('camvid')(tmp_dir).make_dataset() - masks_to_polygons = dm_env.transforms.get('masks_to_polygons') - dataset = dataset.transform(masks_to_polygons) + dataset = Dataset.import_from(tmp_dir, 'camvid', env=dm_env) + dataset.transform('masks_to_polygons') import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/coco.py b/cvat/apps/dataset_manager/formats/coco.py index 84472d3aab1a..3ec3ab18e82f 100644 --- a/cvat/apps/dataset_manager/formats/coco.py +++ b/cvat/apps/dataset_manager/formats/coco.py @@ -5,7 +5,8 @@ import zipfile from tempfile import TemporaryDirectory -from datumaro.components.project import Dataset +from datumaro.components.dataset import Dataset + from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, \ import_dm_annotations from cvat.apps.dataset_manager.util import make_zip_archive @@ -15,11 +16,10 @@ @exporter(name='COCO', ext='ZIP', version='1.0') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('coco_instances').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'coco_instances', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -29,8 +29,9 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: zipfile.ZipFile(src_file).extractall(tmp_dir) - dataset = dm_env.make_importer('coco')(tmp_dir).make_dataset() + dataset = Dataset.import_from(tmp_dir, 'coco', env=dm_env) import_dm_annotations(dataset, task_data) else: - dataset = dm_env.make_extractor('coco_instances', src_file.name) + dataset = Dataset.import_from(src_file.name, + 'coco_instances', env=dm_env) import_dm_annotations(dataset, task_data) \ No newline at end of file diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index d825dae0c651..02025afc750a 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -9,10 +9,11 @@ from glob import glob from tempfile import TemporaryDirectory +from datumaro.components.extractor import DatasetItem + from cvat.apps.dataset_manager.bindings import match_dm_item from cvat.apps.dataset_manager.util import make_zip_archive from cvat.apps.engine.frame_provider import FrameProvider -from datumaro.components.extractor import DatasetItem from .registry import exporter, importer diff --git a/cvat/apps/dataset_manager/formats/imagenet.py b/cvat/apps/dataset_manager/formats/imagenet.py index d9847549f9e1..2ed0cb474849 100644 --- a/cvat/apps/dataset_manager/formats/imagenet.py +++ b/cvat/apps/dataset_manager/formats/imagenet.py @@ -3,12 +3,12 @@ # SPDX-License-Identifier: MIT import os.path as osp -from glob import glob - import zipfile +from glob import glob from tempfile import TemporaryDirectory -from datumaro.components.project import Dataset +from datumaro.components.dataset import Dataset + from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, \ import_dm_annotations from cvat.apps.dataset_manager.util import make_zip_archive @@ -18,15 +18,13 @@ @exporter(name='ImageNet', ext='ZIP', version='1.0') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transform + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: if save_images: - dm_env.converters.get('imagenet').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'imagenet', save_images=save_images) else: - dm_env.converters.get('imagenet_txt').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'imagenet_txt', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -35,7 +33,7 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: zipfile.ZipFile(src_file).extractall(tmp_dir) if glob(osp.join(tmp_dir, '*.txt')): - dataset = dm_env.make_importer('imagenet_txt')(tmp_dir).make_dataset() + dataset = Dataset.import_from(tmp_dir, 'imagenet_txt', env=dm_env) else: - dataset = dm_env.make_importer('imagenet')(tmp_dir).make_dataset() + dataset = Dataset.import_from(tmp_dir, 'imagenet', env=dm_env) import_dm_annotations(dataset, task_data) \ No newline at end of file diff --git a/cvat/apps/dataset_manager/formats/labelme.py b/cvat/apps/dataset_manager/formats/labelme.py index d3bd074d4d31..744b11faab0b 100644 --- a/cvat/apps/dataset_manager/formats/labelme.py +++ b/cvat/apps/dataset_manager/formats/labelme.py @@ -4,23 +4,22 @@ from tempfile import TemporaryDirectory +from datumaro.components.dataset import Dataset from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, import_dm_annotations) from cvat.apps.dataset_manager.util import make_zip_archive -from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer @exporter(name='LabelMe', ext='ZIP', version='3.0') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('label_me').convert(extractor, save_dir=temp_dir, - save_images=save_images) + dataset.export(temp_dir, 'label_me', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -29,7 +28,6 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) - dataset = dm_env.make_importer('label_me')(tmp_dir).make_dataset() - masks_to_polygons = dm_env.transforms.get('masks_to_polygons') - dataset = dataset.transform(masks_to_polygons) + dataset = Dataset.import_from(tmp_dir, 'label_me', env=dm_env) + dataset.transform('masks_to_polygons') import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/mask.py b/cvat/apps/dataset_manager/formats/mask.py index b1307a9ceadf..3e3780e8c6a7 100644 --- a/cvat/apps/dataset_manager/formats/mask.py +++ b/cvat/apps/dataset_manager/formats/mask.py @@ -4,12 +4,12 @@ from tempfile import TemporaryDirectory +from datumaro.components.dataset import Dataset from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, import_dm_annotations) from cvat.apps.dataset_manager.util import make_zip_archive -from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer from .utils import make_colormap @@ -17,15 +17,13 @@ @exporter(name='Segmentation mask', ext='ZIP', version='1.1') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - envt = dm_env.transforms - extractor = extractor.transform(envt.get('polygons_to_masks')) - extractor = extractor.transform(envt.get('boxes_to_masks')) - extractor = extractor.transform(envt.get('merge_instance_segments')) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) + dataset.transform('polygons_to_masks') + dataset.transform('boxes_to_masks') + dataset.transform('merge_instance_segments') with TemporaryDirectory() as temp_dir: - dm_env.converters.get('voc_segmentation').convert(extractor, - save_dir=temp_dir, save_images=save_images, + dataset.export(temp_dir, 'voc_segmentation', save_images=save_images, apply_colormap=True, label_map=make_colormap(task_data)) make_zip_archive(temp_dir, dst_file) @@ -35,7 +33,6 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) - dataset = dm_env.make_importer('voc')(tmp_dir).make_dataset() - masks_to_polygons = dm_env.transforms.get('masks_to_polygons') - dataset = dataset.transform(masks_to_polygons) + dataset = Dataset.import_from(tmp_dir, 'voc', env=dm_env) + dataset.transform('masks_to_polygons') import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/mot.py b/cvat/apps/dataset_manager/formats/mot.py index 81131dc15040..29d5182a674e 100644 --- a/cvat/apps/dataset_manager/formats/mot.py +++ b/cvat/apps/dataset_manager/formats/mot.py @@ -4,23 +4,22 @@ from tempfile import TemporaryDirectory +import datumaro.components.extractor as datumaro +from datumaro.components.dataset import Dataset from pyunpack import Archive -import datumaro.components.extractor as datumaro from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor from cvat.apps.dataset_manager.util import make_zip_archive -from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer @exporter(name='MOT', ext='ZIP', version='1.1') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('mot_seq_gt').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'mot_seq_gt', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -29,7 +28,7 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) - dataset = dm_env.make_importer('mot_seq')(tmp_dir).make_dataset() + dataset = Dataset.import_from(tmp_dir, 'mot_seq', env=dm_env) tracks = {} label_cat = dataset.categories()[datumaro.AnnotationType.label] diff --git a/cvat/apps/dataset_manager/formats/mots.py b/cvat/apps/dataset_manager/formats/mots.py index 52bf0fa623ac..22b9dd08c7ea 100644 --- a/cvat/apps/dataset_manager/formats/mots.py +++ b/cvat/apps/dataset_manager/formats/mots.py @@ -4,13 +4,13 @@ from tempfile import TemporaryDirectory +from datumaro.components.dataset import Dataset +from datumaro.components.extractor import AnnotationType, Transform from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, find_dataset_root, match_dm_item) from cvat.apps.dataset_manager.util import make_zip_archive -from datumaro.components.extractor import AnnotationType, Transform -from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer @@ -22,16 +22,14 @@ def transform_item(self, item): @exporter(name='MOTS PNG', ext='ZIP', version='1.0') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - envt = dm_env.transforms - extractor = extractor.transform(KeepTracks) # can only export tracks - extractor = extractor.transform(envt.get('polygons_to_masks')) - extractor = extractor.transform(envt.get('boxes_to_masks')) - extractor = extractor.transform(envt.get('merge_instance_segments')) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) + dataset.transform(KeepTracks) # can only export tracks + dataset.transform('polygons_to_masks') + dataset.transform('boxes_to_masks') + dataset.transform('merge_instance_segments') with TemporaryDirectory() as temp_dir: - dm_env.converters.get('mots_png').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'mots_png', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -40,9 +38,8 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) - dataset = dm_env.make_importer('mots')(tmp_dir).make_dataset() - masks_to_polygons = dm_env.transforms.get('masks_to_polygons') - dataset = dataset.transform(masks_to_polygons) + dataset = Dataset.import_from(tmp_dir, 'mots', env=dm_env) + dataset.transform('masks_to_polygons') tracks = {} label_cat = dataset.categories()[AnnotationType.label] diff --git a/cvat/apps/dataset_manager/formats/pascal_voc.py b/cvat/apps/dataset_manager/formats/pascal_voc.py index ee30564bc1e7..3f10b93aa937 100644 --- a/cvat/apps/dataset_manager/formats/pascal_voc.py +++ b/cvat/apps/dataset_manager/formats/pascal_voc.py @@ -6,26 +6,25 @@ import os.path as osp import shutil from glob import glob - from tempfile import TemporaryDirectory +from datumaro.components.dataset import Dataset from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, import_dm_annotations) from cvat.apps.dataset_manager.util import make_zip_archive -from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer @exporter(name='PASCAL VOC', ext='ZIP', version='1.1') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('voc').convert(extractor, - save_dir=temp_dir, save_images=save_images, label_map='source') + dataset.export(temp_dir, 'voc', save_images=save_images, + label_map='source') make_zip_archive(temp_dir, dst_file) @@ -56,7 +55,6 @@ def _import(src_file, task_data): for f in anno_files: shutil.move(f, anno_dir) - dataset = dm_env.make_importer('voc')(tmp_dir).make_dataset() - masks_to_polygons = dm_env.transforms.get('masks_to_polygons') - dataset = dataset.transform(masks_to_polygons) + dataset = Dataset.import_from(tmp_dir, 'voc', env=dm_env) + dataset.transform('masks_to_polygons') import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/registry.py b/cvat/apps/dataset_manager/formats/registry.py index 46039f25fcdd..c14a732d3c5c 100644 --- a/cvat/apps/dataset_manager/formats/registry.py +++ b/cvat/apps/dataset_manager/formats/registry.py @@ -95,3 +95,5 @@ def make_exporter(name): import cvat.apps.dataset_manager.formats.yolo import cvat.apps.dataset_manager.formats.imagenet import cvat.apps.dataset_manager.formats.camvid +import cvat.apps.dataset_manager.formats.widerface +import cvat.apps.dataset_manager.formats.vggface2 diff --git a/cvat/apps/dataset_manager/formats/tfrecord.py b/cvat/apps/dataset_manager/formats/tfrecord.py index 3b7e123eb630..9847bf61b66e 100644 --- a/cvat/apps/dataset_manager/formats/tfrecord.py +++ b/cvat/apps/dataset_manager/formats/tfrecord.py @@ -24,11 +24,10 @@ @exporter(name='TFRecord', ext='ZIP', version='1.0', enabled=tf_available) def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('tf_detection_api').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'tf_detection_api', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -37,5 +36,5 @@ def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) - dataset = dm_env.make_importer('tf_detection_api')(tmp_dir).make_dataset() + dataset = Dataset.import_from(tmp_dir, 'tf_detection_api', env=dm_env) import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/vggface2.py b/cvat/apps/dataset_manager/formats/vggface2.py new file mode 100644 index 000000000000..528f52c76d7a --- /dev/null +++ b/cvat/apps/dataset_manager/formats/vggface2.py @@ -0,0 +1,32 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import zipfile +from tempfile import TemporaryDirectory + +from datumaro.components.dataset import Dataset + +from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, \ + import_dm_annotations +from cvat.apps.dataset_manager.util import make_zip_archive + +from .registry import dm_env, exporter, importer + + +@exporter(name='VGGFace2', ext='ZIP', version='1.0') +def _export(dst_file, task_data, save_images=False): + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) + with TemporaryDirectory() as temp_dir: + dataset.export(temp_dir, 'vgg_face2', save_images=save_images) + + make_zip_archive(temp_dir, dst_file) + +@importer(name='VGGFace2', ext='ZIP', version='1.0') +def _import(src_file, task_data): + with TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(src_file).extractall(tmp_dir) + + dataset = Dataset.import_from(tmp_dir, 'vgg_face2', env=dm_env) + import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/widerface.py b/cvat/apps/dataset_manager/formats/widerface.py new file mode 100644 index 000000000000..7f120ffe2154 --- /dev/null +++ b/cvat/apps/dataset_manager/formats/widerface.py @@ -0,0 +1,32 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import zipfile +from tempfile import TemporaryDirectory + +from datumaro.components.dataset import Dataset + +from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, \ + import_dm_annotations +from cvat.apps.dataset_manager.util import make_zip_archive + +from .registry import dm_env, exporter, importer + + +@exporter(name='WiderFace', ext='ZIP', version='1.0') +def _export(dst_file, task_data, save_images=False): + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) + with TemporaryDirectory() as temp_dir: + dataset.export(temp_dir, 'wider_face', save_images=save_images) + + make_zip_archive(temp_dir, dst_file) + +@importer(name='WiderFace', ext='ZIP', version='1.0') +def _import(src_file, task_data): + with TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(src_file).extractall(tmp_dir) + + dataset = Dataset.import_from(tmp_dir, 'wider_face', env=dm_env) + import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/yolo.py b/cvat/apps/dataset_manager/formats/yolo.py index bea73b3c3bd2..0df6f5fe27a1 100644 --- a/cvat/apps/dataset_manager/formats/yolo.py +++ b/cvat/apps/dataset_manager/formats/yolo.py @@ -20,11 +20,10 @@ @exporter(name='YOLO', ext='ZIP', version='1.1') def _export(dst_file, task_data, save_images=False): - extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - extractor = Dataset.from_extractors(extractor) # apply lazy transforms + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dm_env.converters.get('yolo').convert(extractor, - save_dir=temp_dir, save_images=save_images) + dataset.export(temp_dir, 'yolo', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -44,11 +43,11 @@ def _import(src_file, task_data): frame_id = match_dm_item(DatasetItem(id=frame), task_data, root_hint=root_hint) frame_info = task_data.frame_info[frame_id] - except Exception: + except Exception: # nosec pass if frame_info is not None: image_info[frame] = (frame_info['height'], frame_info['width']) - dataset = dm_env.make_importer('yolo')(tmp_dir, image_info=image_info) \ - .make_dataset() + dataset = Dataset.import_from(tmp_dir, 'yolo', + env=dm_env, image_info=image_info) import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index a01e60b78ae7..75ff51d01b41 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -71,6 +71,13 @@ def _put_api_v1_task_id_annotations(self, tid, data): return response + def _put_api_v1_job_id_annotations(self, jid, data): + with ForceLogin(self.user, self.client): + response = self.client.put("/api/v1/jobs/%s/annotations" % jid, + data=data, format="json") + + return response + def _create_task(self, data, image_data): with ForceLogin(self.user, self.client): response = self.client.post('/api/v1/tasks', data=data, format="json") @@ -87,6 +94,10 @@ def _create_task(self, data, image_data): return task class TaskExportTest(_DbTestBase): + def _generate_custom_annotations(self, annotations, task): + self._put_api_v1_task_id_annotations(task["id"], annotations) + return annotations + def _generate_annotations(self, task): annotations = { "version": 0, @@ -204,8 +215,7 @@ def _generate_annotations(self, task): }, ] } - self._put_api_v1_task_id_annotations(task["id"], annotations) - return annotations + return self._generate_custom_annotations(annotations, task) def _generate_task_images(self, count): # pylint: disable=no-self-use images = { @@ -215,7 +225,7 @@ def _generate_task_images(self, count): # pylint: disable=no-self-use images["image_quality"] = 75 return images - def _generate_task(self, images): + def _generate_task(self, images, **overrides): task = { "name": "my task #1", "overlap": 0, @@ -242,6 +252,7 @@ def _generate_task(self, images): {"name": "person"}, ] } + task.update(overrides) return self._create_task(task, images) @staticmethod @@ -271,6 +282,8 @@ def test_export_formats_query(self): 'YOLO 1.1', 'ImageNet 1.0', 'CamVid 1.0', + 'WiderFace 1.0', + 'VGGFace2 1.0', }) def test_import_formats_query(self): @@ -289,6 +302,8 @@ def test_import_formats_query(self): 'YOLO 1.1', 'ImageNet 1.0', 'CamVid 1.0', + 'WiderFace 1.0', + 'VGGFace2 1.0', }) def test_exports(self): @@ -301,6 +316,9 @@ def check(file_path): self.skipTest("Format is disabled") format_name = f.DISPLAY_NAME + if format_name == "VGGFace2 1.0": + self.skipTest("Format does not support multiple shapes for one item") + for save_images in { True, False }: images = self._generate_task_images(3) task = self._generate_task(images) @@ -326,6 +344,8 @@ def test_empty_images_are_exported(self): ('YOLO 1.1', 'yolo'), ('ImageNet 1.0', 'imagenet_txt'), ('CamVid 1.0', 'camvid'), + ('WiderFace 1.0', 'wider_face'), + ('VGGFace2 1.0', 'vgg_face2'), ]: with self.subTest(format=format_name): if not dm.formats.registry.EXPORT_FORMATS[format_name].ENABLED: @@ -346,17 +366,18 @@ def load_dataset(src): project.config.remove('sources') return project.make_dataset() - return dm_env.make_importer(importer_name)(src) \ - .make_dataset() + return datumaro.components.dataset. \ + Dataset.import_from(src, importer_name, env=dm_env) if zipfile.is_zipfile(file_path): with tempfile.TemporaryDirectory() as tmp_dir: zipfile.ZipFile(file_path).extractall(tmp_dir) dataset = load_dataset(tmp_dir) + self.assertEqual(len(dataset), task["size"]) else: dataset = load_dataset(file_path) + self.assertEqual(len(dataset), task["size"]) - self.assertEqual(len(dataset), task["size"]) self._test_export(check, task, format_name, save_images=False) def test_can_skip_outside(self): @@ -422,6 +443,47 @@ def test_can_make_abs_frame_id_from_known(self): self.assertEqual(5, task_data.abs_frame_id(2)) + def test_frames_outside_are_not_generated(self): + # https://github.com/openvinotoolkit/cvat/issues/2827 + images = self._generate_task_images(10) + images['start_frame'] = 0 + task = self._generate_task(images, overlap=3, segment_size=6) + annotations = { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 6, + "label_id": task["labels"][0]["id"], + "group": None, + "source": "manual", + "attributes": [], + "shapes": [ + { + "frame": 6, + "points": [1.0, 2.1, 100, 300.222], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [], + }, + ] + }, + ] + } + self._put_api_v1_job_id_annotations( + task["segments"][2]["jobs"][0]["id"], annotations) + + task_ann = TaskAnnotation(task["id"]) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task['id'])) + + i = -1 + for i, frame in enumerate(task_data.group_by_frame()): + self.assertTrue(frame.frame in range(6, 10)) + self.assertEqual(i + 1, 4) + class FrameMatchingTest(_DbTestBase): def _generate_task_images(self, paths): # pylint: disable=no-self-use f = BytesIO() diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 4cf55479c9da..67779f6ba156 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -475,12 +475,29 @@ def save_as_chunk(self, images, chunk_path): return image_sizes class Mpeg4ChunkWriter(IChunkWriter): - def __init__(self, _): - super().__init__(17) + def __init__(self, quality=67): + # translate inversed range [1:100] to [0:51] + quality = round(51 * (100 - quality) / 99) + super().__init__(quality) self._output_fps = 25 - - @staticmethod - def _create_av_container(path, w, h, rate, options, f='mp4'): + try: + codec = av.codec.Codec('libopenh264', 'w') + self._codec_name = codec.name + self._codec_opts = { + 'profile': 'constrained_baseline', + 'qmin': str(self._image_quality), + 'qmax': str(self._image_quality), + 'rc_mode': 'buffer', + } + except av.codec.codec.UnknownCodecError: + codec = av.codec.Codec('libx264', 'w') + self._codec_name = codec.name + self._codec_opts = { + "crf": str(self._image_quality), + "preset": "ultrafast", + } + + def _create_av_container(self, path, w, h, rate, options, f='mp4'): # x264 requires width and height must be divisible by 2 for yuv420p if h % 2: h += 1 @@ -488,7 +505,7 @@ def _create_av_container(path, w, h, rate, options, f='mp4'): w += 1 container = av.open(path, 'w',format=f) - video_stream = container.add_stream('libopenh264', rate=rate) + video_stream = container.add_stream(self._codec_name, rate=rate) video_stream.pix_fmt = "yuv420p" video_stream.width = w video_stream.height = h @@ -508,12 +525,7 @@ def save_as_chunk(self, images, chunk_path): w=input_w, h=input_h, rate=self._output_fps, - options={ - 'profile': 'constrained_baseline', - 'qmin': str(self._image_quality), - 'qmax': str(self._image_quality), - 'rc_mode': 'buffer', - }, + options=self._codec_opts, ) self._encode_images(images, output_container, output_v_stream) @@ -536,10 +548,15 @@ def _encode_images(images, container, stream): class Mpeg4CompressedChunkWriter(Mpeg4ChunkWriter): def __init__(self, quality): - # translate inversed range [1:100] to [0:51] - self._image_quality = round(51 * (100 - quality) / 99) - self._output_fps = 25 - + super().__init__(quality) + if self._codec_name == 'libx264': + self._codec_opts = { + 'profile': 'baseline', + 'coder': '0', + 'crf': str(self._image_quality), + 'wpredp': '0', + 'flags': '-loop', + } def save_as_chunk(self, images, chunk_path): if not images: @@ -560,12 +577,7 @@ def save_as_chunk(self, images, chunk_path): w=output_w, h=output_h, rate=self._output_fps, - options={ - 'profile': 'constrained_baseline', - 'qmin': str(self._image_quality), - 'qmax': str(self._image_quality), - 'rc_mode': 'buffer', - }, + options=self._codec_opts, ) self._encode_images(images, output_container, output_v_stream) diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index 066f3be3fac7..19a187cbac90 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -9,9 +9,9 @@ import rq import shutil from traceback import print_exception -from urllib import error as urlerror from urllib import parse as urlparse from urllib import request as urlrequest +import requests from cvat.apps.engine.media_extractors import get_mime, MEDIA_TYPES, Mpeg4ChunkWriter, ZipChunkWriter, Mpeg4CompressedChunkWriter, ZipCompressedChunkWriter, ValidateDimension from cvat.apps.engine.models import DataChoice, StorageMethodChoice, StorageChoice, RelatedFile @@ -195,20 +195,16 @@ def _download_data(urls, upload_dir): job.meta['status'] = '{} is being downloaded..'.format(url) job.save_meta() - req = urlrequest.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) - try: - with urlrequest.urlopen(req) as fp, open(os.path.join(upload_dir, name), 'wb') as tfp: # nosec - while True: - block = fp.read(8192) - if not block: - break - tfp.write(block) - except urlerror.HTTPError as err: - raise Exception("Failed to download " + url + ". " + str(err.code) + ' - ' + err.reason) - except urlerror.URLError as err: - raise Exception("Invalid URL: " + url + ". " + err.reason) + response = requests.get(url, stream=True) + if response.status_code == 200: + response.raw.decode_content = True + with open(os.path.join(upload_dir, name), 'wb') as output_file: + shutil.copyfileobj(response.raw, output_file) + else: + raise Exception("Failed to download " + url) local_files[name] = True + return list(local_files.keys()) @transaction.atomic @@ -300,13 +296,20 @@ def update_progress(progress): update_progress.call_counter = (update_progress.call_counter + 1) % len(progress_animation) compressed_chunk_writer_class = Mpeg4CompressedChunkWriter if db_data.compressed_chunk_type == DataChoice.VIDEO else ZipCompressedChunkWriter - original_chunk_writer_class = Mpeg4ChunkWriter if db_data.original_chunk_type == DataChoice.VIDEO else ZipChunkWriter + if db_data.original_chunk_type == DataChoice.VIDEO: + original_chunk_writer_class = Mpeg4ChunkWriter + # Let's use QP=17 (that is 67 for 0-100 range) for the original chunks, which should be visually lossless or nearly so. + # A lower value will significantly increase the chunk size with a slight increase of quality. + original_quality = 67 + else: + original_chunk_writer_class = ZipChunkWriter + original_quality = 100 kwargs = {} if validate_dimension.dimension == DimensionType.DIM_3D: kwargs["dimension"] = validate_dimension.dimension compressed_chunk_writer = compressed_chunk_writer_class(db_data.image_quality, **kwargs) - original_chunk_writer = original_chunk_writer_class(100) + original_chunk_writer = original_chunk_writer_class(original_quality) # calculate chunk size if it isn't specified if db_data.chunk_size is None: diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index 704c50e84fc2..64aa0a8a20ee 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -2654,6 +2654,32 @@ def _create_task(self, owner, assignee): ] }, {"name": "person"}, + { + "name": "widerface", + "attributes": [ + { + "name": "blur", + "mutable": False, + "input_type": "select", + "default_value": "0", + "values": ["0", "1", "2"] + }, + { + "name": "expression", + "mutable": False, + "input_type": "select", + "default_value": "0", + "values": ["0", "1"] + }, + { + "name": "illumination", + "mutable": False, + "input_type": "select", + "default_value": "0", + "values": ["0", "1"] + }, + ] + }, ] } @@ -3775,6 +3801,30 @@ def _get_initial_annotation(annotation_format): "occluded": False, }] + rectangle_shapes_with_wider_attrs = [{ + "frame": 0, + "label_id": task["labels"][2]["id"], + "group": 0, + "source": "manual", + "attributes": [ + { + "spec_id": task["labels"][2]["attributes"][0]["id"], + "value": task["labels"][2]["attributes"][0]["default_value"] + }, + { + "spec_id": task["labels"][2]["attributes"][1]["id"], + "value": task["labels"][2]["attributes"][1]["values"][1] + }, + { + "spec_id": task["labels"][2]["attributes"][2]["id"], + "value": task["labels"][2]["attributes"][2]["default_value"] + } + ], + "points": [1.0, 2.1, 10.6, 53.22], + "type": "rectangle", + "occluded": False, + }] + rectangle_shapes_wo_attrs = [{ "frame": 1, "label_id": task["labels"][1]["id"], @@ -3827,6 +3877,17 @@ def _get_initial_annotation(annotation_format): "occluded": False, }] + points_wo_attrs = [{ + "frame": 1, + "label_id": task["labels"][1]["id"], + "group": 0, + "source": "manual", + "attributes": [], + "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2], + "type": "points", + "occluded": False, + }] + tags_wo_attrs = [{ "frame": 2, "label_id": task["labels"][1]["id"], @@ -3912,6 +3973,15 @@ def _get_initial_annotation(annotation_format): annotations["shapes"] = rectangle_shapes_wo_attrs \ + polygon_shapes_wo_attrs + elif annotation_format == "WiderFace 1.0": + annotations["tags"] = tags_wo_attrs + annotations["shapes"] = rectangle_shapes_with_wider_attrs + + elif annotation_format == "VGGFace2 1.0": + annotations["tags"] = tags_wo_attrs + annotations["shapes"] = points_wo_attrs \ + + rectangle_shapes_wo_attrs + else: raise Exception("Unknown format {}".format(annotation_format)) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index c8a6bda47911..ed31e5031e51 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -1,12 +1,12 @@ click==7.1.2 -Django==3.1.1 +Django==3.1.7 django-appconf==1.0.4 django-auth-ldap==2.2.0 django-cacheops==5.0.1 django-compressor==2.4 django-rq==2.3.2 EasyProcess==0.3 -Pillow==7.2.0 +Pillow==8.1.2 numpy==1.19.5 python-ldap==3.3.1 pytz==2020.1 @@ -45,5 +45,9 @@ tensorflow==2.4.1 # Optional requirement of Datumaro patool==1.12 diskcache==5.0.2 open3d==0.11.2 -# workaround for binary incompatibility with numpy when pycocotools is installed by wheel -datumaro==0.1.5.1 --no-binary=datumaro --no-binary=pycocotools +# --no-binary=datumaro: workaround for pip to install +# opencv-headless instead of regular opencv, to actually run setup script +# --no-binary=pycocotools: workaround for binary incompatibility on numpy 1.20 +# of pycocotools and tensorflow 2.4.1 +# when pycocotools is installed by wheel in python 3.8+ +datumaro==0.1.6.1 --no-binary=datumaro --no-binary=pycocotools diff --git a/tests/cypress.json b/tests/cypress.json index fa98e950252c..eb67a4ae14be 100644 --- a/tests/cypress.json +++ b/tests/cypress.json @@ -15,6 +15,7 @@ "actions_tasks_objects/**/*", "actions_users/**/*", "actions_projects/**/*", + "canvas3d_functionality/*", "remove_users_tasks_projects.js" ] } diff --git a/tests/cypress/integration/actions_projects/issue_2625_delete_project_via_actions.js b/tests/cypress/integration/actions_projects/issue_2625_delete_project_via_actions.js index c45c23c7f34b..57c5e38440db 100644 --- a/tests/cypress/integration/actions_projects/issue_2625_delete_project_via_actions.js +++ b/tests/cypress/integration/actions_projects/issue_2625_delete_project_via_actions.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -15,14 +15,7 @@ context('Delete a project via actions.', () => { describe(`Testing "Issue ${issueID}"`, () => { it('Delete a project via actions.', () => { - cy.get('.cvat-project-top-bar-actions').trigger('mouseover'); - cy.get('.cvat-project-actions-menu').within(() => { - cy.contains('[role="menuitem"]', 'Delete').click(); - }); - cy.get('.cvat-modal-confirm-remove-project').within(() => { - cy.contains('button', 'Delete').click(); - }); - cy.contains('.cvat-projects-project-item-title', projectName).should('not.exist'); + cy.deleteProjectViaActions(projectName); }); }); }); diff --git a/tests/cypress/integration/actions_projects/issue_2900_creating_more_one_tasks_from_project_per_time.js b/tests/cypress/integration/actions_projects/issue_2900_creating_more_one_tasks_from_project_per_time.js new file mode 100644 index 000000000000..91d6b8c5de01 --- /dev/null +++ b/tests/cypress/integration/actions_projects/issue_2900_creating_more_one_tasks_from_project_per_time.js @@ -0,0 +1,74 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/// + +import { projectName, labelName } from '../../support/const_project'; + +context('Create more than one task per time when create from project.', () => { + const issueID = 2900; + const taskName = { + firstTask: `First task for ${projectName}`, + secondTask: `Second task for ${projectName}`, + }; + const imagesCount = 1; + const imageFileName = `image_${taskName.firstTask.replace(/\s+/g, '_').toLowerCase()}`; + const width = 800; + const height = 800; + const posX = 10; + const posY = 10; + const color = 'white'; + const archiveName = `${imageFileName}.zip`; + const archivePath = `cypress/fixtures/${archiveName}`; + const imagesFolder = `cypress/fixtures/${imageFileName}`; + const directoryToArchive = imagesFolder; + + function createTask(nameTaskToCreate, repeatCreation) { + let projectSearchField; + if (!repeatCreation) { + projectSearchField = projectName; + } else { + projectSearchField = ''; + } + cy.get('[id="name"]').clear().type(nameTaskToCreate); + cy.get('.cvat-project-search-field').within(() => { + cy.get('[type="search"]').should('have.value', projectSearchField); + }); + if (repeatCreation) { + cy.get('.cvat-project-search-field').click(); + cy.get('.ant-select-dropdown') + .not('.ant-select-dropdown-hidden') + .within(() => { + cy.get(`.ant-select-item-option[title="${projectName}"]`).click(); + }); + } + cy.get('.cvat-constructor-viewer-new-item').should('not.exist'); + cy.get('input[type="file"]').attachFile(archiveName, { subjectType: 'drag-n-drop' }); + cy.contains('button', 'Submit').click(); + cy.get('.cvat-notification-create-task-success').should('exist'); + cy.get('.cvat-notification-create-task-fail').should('not.exist'); + } + + before(() => { + cy.imageGenerator(imagesFolder, imageFileName, width, height, color, posX, posY, labelName, imagesCount); + cy.createZipArchive(directoryToArchive, archivePath); + cy.openProject(projectName); + }); + + describe(`Testing "Issue ${issueID}"`, () => { + it('Create more than one task per time from project.', () => { + cy.get('#cvat-create-task-button').click(); + createTask(taskName.firstTask, false); + createTask(taskName.secondTask, true); + }); + + it('The tasks successfully created. Remove the project.', () => { + cy.goToProjectsList(); + cy.openProject(projectName); + cy.contains('.cvat-item-task-name', taskName.firstTask).should('exist').and('be.visible'); + cy.contains('.cvat-item-task-name', taskName.secondTask).should('exist').and('be.visible'); + cy.deleteProjectViaActions(projectName); + }); + }); +}); diff --git a/tests/cypress/integration/actions_projects/registration_involved/base_actions_project_task_user.js b/tests/cypress/integration/actions_projects/registration_involved/base_actions_project_task_user.js index 847da80d8bf1..6d93a0f6c341 100644 --- a/tests/cypress/integration/actions_projects/registration_involved/base_actions_project_task_user.js +++ b/tests/cypress/integration/actions_projects/registration_involved/base_actions_project_task_user.js @@ -6,15 +6,6 @@ import { projectName } from '../../../support/const_project'; -const randomString = (isPassword) => { - let result = ''; - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - for (let i = 0; i <= 8; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); - } - return isPassword ? `${result}${Math.floor(Math.random() * 10)}` : result; -}; - context('Base actions on the project', () => { const labelName = `Base label for ${projectName}`; const taskName = { @@ -45,11 +36,11 @@ context('Base actions on the project', () => { const newLabelName2 = `Second label ${projectName}`; const newLabelName3 = `Third label ${projectName}`; const newLabelName4 = `Fourth label ${projectName}`; - const firstName = `${randomString()}`; - const lastName = `${randomString()}`; - const userName = `${randomString()}`; + const firstName = 'Seconduser fm'; + const lastName = 'Seconduser lm'; + const userName = 'Seconduser'; const emailAddr = `${userName}@local.local`; - const password = `${randomString(true)}`; + const password = 'GDrb41RguF!'; let projectID = ''; function getProjectID(projectName) { @@ -65,6 +56,10 @@ context('Base actions on the project', () => { cy.openProject(projectName); }); + after(() => { + cy.deletingRegisteredUsers([userName]); + }); + describe(`Testing "Base actions on the project"`, () => { it('Add some labels to project.', () => { cy.addNewLabel(newLabelName1); @@ -125,7 +120,7 @@ context('Base actions on the project', () => { cy.userRegistration(firstName, lastName, userName, emailAddr, password); cy.goToProjectsList(); // tries to create project - const failProjectName = `${randomString()}`; + const failProjectName = 'failProject'; cy.createProjects(failProjectName, labelName, attrName, textDefaultValue, null, 'fail'); cy.closeNotification('.cvat-notification-notice-create-project-failed'); cy.goToProjectsList(); @@ -159,6 +154,7 @@ context('Base actions on the project', () => { cy.goToTaskList(); cy.contains('strong', taskName.firstTask).should('not.exist'); cy.contains('strong', taskName.secondTask).should('not.exist'); + cy.logout(); }); }); }); diff --git a/tests/cypress/integration/actions_tasks_objects/case_15_group_features.js b/tests/cypress/integration/actions_tasks_objects/case_15_group_features.js index 7f94ed15e1fc..1b837ccf2fd0 100644 --- a/tests/cypress/integration/actions_tasks_objects/case_15_group_features.js +++ b/tests/cypress/integration/actions_tasks_objects/case_15_group_features.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -70,12 +70,20 @@ context('Group features', () => { cy.saveLocalStorage(); }); - function groupObjects(objectsArray) { + function testGroupObjects(objectsArray, cancelGrouping) { cy.get('.cvat-group-control').click(); for (const shapeToGroup of objectsArray) { - cy.get(shapeToGroup).click(); + cy.get(shapeToGroup).click().should('have.class', 'cvat_canvas_shape_grouping'); } + cancelGrouping ? cy.get('body').type('{Esc}') : cy.get('.cvat-group-control').click(); + } + + function testUnGroupObjects() { cy.get('.cvat-group-control').click(); + for (const shapeToGroup of shapeArray) { + cy.get(shapeToGroup).click().should('have.class', 'cvat_canvas_shape_grouping'); + } + cy.get('body').type('{Shift}g'); } function changeGroupColor(object, color) { @@ -91,6 +99,34 @@ context('Group features', () => { cy.changeColorViaBadge(color); } + function testShapesFillEquality(equal) { + for (const groupedShape of shapeArray) { + cy.get(groupedShape) + .should('have.css', 'fill') + .then(($shapesGroupColor) => { + if (equal) { + expect($shapesGroupColor).to.be.equal(defaultGroupColorRgb); + } else { + expect($shapesGroupColor).to.not.equal(defaultGroupColorRgb); + shapesGroupColor = $shapesGroupColor; + } + }); + } + } + + function testSidebarItemsBackgroundColorEquality() { + for (const objectSideBarShape of shapeSidebarItemArray) { + cy.get(objectSideBarShape) + .should('have.css', 'background-color') + .then(($bColorobjectSideBarShape) => { + // expected rgba(250, 50, 83, 0.533) to not include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ] + expect($bColorobjectSideBarShape).to.not.contain(defaultGroupColorRgb.match(/\d+, \d+, \d+/)); + // expected rgba(250, 50, 83, 0.533) to include [ 250, 50, 83, index: 4, input: 'rgb(250, 50, 83)', groups: undefined ] + expect($bColorobjectSideBarShape).to.be.contain(shapesGroupColor.match(/\d+, \d+, \d+/)); + }); + } + } + describe(`Testing case "${caseId}"`, () => { it('Create two shapes and two tracks.', () => { cy.createRectangle(createRectangleShape2Points); @@ -123,30 +159,25 @@ context('Group features', () => { }); it('With group button unite two shapes. They have corresponding colors.', () => { - groupObjects(shapeArray); - for (const groupedShape of shapeArray) { - cy.get(groupedShape) - .should('have.css', 'fill') - .then(($shapesGroupColor) => { - // expected rgb(250, 50, 83) to not equal rgb(224, 224, 224) - expect($shapesGroupColor).to.not.equal(defaultGroupColorRgb); - shapesGroupColor = $shapesGroupColor; - }); - } - for (const objectSideBarShape of shapeSidebarItemArray) { - cy.get(objectSideBarShape) - .should('have.css', 'background-color') - .then(($bColorobjectSideBarShape) => { - // expected rgba(250, 50, 83, 0.533) to not include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ] - expect($bColorobjectSideBarShape).to.not.contain(defaultGroupColorRgb.match(/\d+, \d+, \d+/)); - // expected rgba(250, 50, 83, 0.533) to include [ 250, 50, 83, index: 4, input: 'rgb(250, 50, 83)', groups: undefined ] - expect($bColorobjectSideBarShape).to.be.contain(shapesGroupColor.match(/\d+, \d+, \d+/)); - }); - } + testGroupObjects(shapeArray, true); // Reset grouping + testShapesFillEquality(true); + testGroupObjects(shapeArray); // Group + testShapesFillEquality(false); + testSidebarItemsBackgroundColorEquality(); + testUnGroupObjects(); // Ungroup + testShapesFillEquality(true); + // Start grouping. Cancel grouping via click to the same shape. + cy.get('.cvat-group-control').click(); + cy.get(shapeArray[0]) + .click() + .should('have.class', 'cvat_canvas_shape_grouping') + .click() + .should('not.have.class', 'cvat_canvas_shape_grouping'); + cy.get('body').type('{Esc}'); // Cancel grouping }); it('With group button unite two track. They have corresponding colors.', () => { - groupObjects(trackArray); + testGroupObjects(trackArray); for (const groupedTrack of trackArray) { cy.get(groupedTrack) .should('have.css', 'fill') @@ -195,7 +226,7 @@ context('Group features', () => { }); } }); - groupObjects(shapeArray); + testGroupObjects(shapeArray); }); it('Change group color.', () => { diff --git a/tests/cypress/integration/actions_tasks_objects/case_59_edit_handler.js b/tests/cypress/integration/actions_tasks_objects/case_59_edit_handler.js new file mode 100644 index 000000000000..412a5a079ce9 --- /dev/null +++ b/tests/cypress/integration/actions_tasks_objects/case_59_edit_handler.js @@ -0,0 +1,224 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/// + +import { taskName, labelName } from '../../support/const'; + +context('Edit handler.', () => { + const caseId = '59'; + const createPolygonShape = { + redraw: false, + type: 'Shape', + labelName: labelName, + pointsMap: [ + { x: 450, y: 350 }, + { x: 550, y: 350 }, + { x: 550, y: 450 }, + ], + complete: true, + numberOfPoints: null, + }; + const createPolylinesShape = { + type: 'Shape', + labelName: labelName, + pointsMap: [ + { x: 700, y: 350 }, + { x: 800, y: 350 }, + { x: 800, y: 450 }, + ], + complete: true, + numberOfPoints: null, + }; + const createPointsShape = { + type: 'Shape', + labelName: labelName, + pointsMap: [{ x: 200, y: 400 }], + complete: true, + numberOfPoints: null, + }; + + function testActivatingShape(x, y, expectedShape) { + cy.get('.cvat-canvas-container').trigger('mousemove', x, y); + cy.get(expectedShape).should('have.class', 'cvat_canvas_shape_activated'); + } + + before(() => { + cy.openTaskJob(taskName); + }); + + describe(`Testing case "${caseId}"`, () => { + it('Start editing handler and cancel.', () => { + cy.createPolygon(createPolygonShape); + testActivatingShape(520, 400, '#cvat_canvas_shape_1'); + cy.get('.cvat-canvas-container').click(550, 350, { shiftKey: true }); + cy.get('.cvat_canvas_shape_drawing').should('exist'); + cy.get('.cvat-canvas-container').click(650, 300); + cy.get('body').type('{Esc}'); + cy.get('.cvat_canvas_shape_drawing').should('not.exist'); + }); + + it('Edit handler for the polygon. Splitting.', () => { + cy.get('.cvat-canvas-container').trigger('mousemove', 520, 400); + cy.get('#cvat_canvas_shape_1') + .should('have.class', 'cvat_canvas_shape_activated') + .invoke('attr', 'points') + .then(($points) => { + const pointsCountBefore = $points.split(' ').filter(function (el) { + return el.length != 0; + }).length; + cy.get('.cvat-canvas-container') + .click(550, 350, { shiftKey: true }) + .then(() => { + //Click on the second polygon points to start of change + cy.get('.cvat_canvas_shape_drawing') + .should('exist') + .and('have.attr', 'data-origin-client-id', '1'); + }); + cy.get('.cvat-canvas-container').click(650, 300).click(550, 450); // Click on the third polygon points to finish the change + cy.get('.cvat_canvas_shape_drawing').should('not.exist'); + cy.get('#cvat_canvas_shape_1') + .invoke('attr', 'points') + .then(($points) => { + const pointsCountAfter = $points.split(' ').filter(function (el) { + return el.length != 0; + }).length; + expect(pointsCountBefore).not.equal(pointsCountAfter); // expected 3 to not equal 4 + }); + // Splitting polygon + testActivatingShape(520, 400, '#cvat_canvas_shape_1'); + cy.get('.cvat-canvas-container') + .click(650, 300, { shiftKey: true }) + .click(450, 350) + .trigger('mouseenter', 530, 340); + cy.get('.cvat_canvas_shape_splitting').should('exist'); + cy.get('.cvat-canvas-container').trigger('mouseleave', 530, 340); + cy.get('.cvat_canvas_shape_splitting').should('not.exist'); + cy.get('.cvat-canvas-container').click(530, 340); + // Cancel changes, repeat edit handler and select an another shape + cy.get('body').type('{Ctrl}z'); + testActivatingShape(520, 400, '#cvat_canvas_shape_1'); + cy.get('.cvat-canvas-container') + .click(650, 300, { shiftKey: true }) + .click(450, 350) + .click(530, 400); + // Cancel changes again, repeat edit handler dblclick to the last point + cy.get('body').type('{Ctrl}z'); + testActivatingShape(520, 400, '#cvat_canvas_shape_1'); + cy.get('.cvat-canvas-container') + .click(650, 300, { shiftKey: true }) + .click(630, 300) + .click(530, 300) + .click(450, 350) + .click(530, 400) + .click(450, 350) + .dblclick(530, 400); + cy.get('#cvat_canvas_shape_1') + .invoke('attr', 'points') + .then(($points) => { + const pointsCountAfterSplitting = $points.split(' ').filter(function (el) { + return el.length != 0; + }).length; + expect(pointsCountAfterSplitting).to.be.equal(5); // expected 3 to equal 3 + }); + }); + }); + + it('Edit handler for the polyline.', () => { + cy.createPolyline(createPolylinesShape); + cy.get('.cvat-canvas-container').trigger('mousemove', 800, 400); + cy.get('#cvat_canvas_shape_2') + .should('have.class', 'cvat_canvas_shape_activated') + .invoke('attr', 'points') + .then(($pointsCordsBefore) => { + cy.get('.cvat-canvas-container') + .click(800, 450, { shiftKey: true }) + .then(() => { + // Click on the third polyline points to start of change + cy.get('.cvat_canvas_shape_drawing') + .should('exist') + .and('have.attr', 'data-origin-client-id', '2'); + cy.get('body').type('{Ctrl}'); + cy.get('.cvat_canvas_autoborder_point') + .should('exist') + .and('be.visible') + .then(($autoborderPoints) => { + expect($autoborderPoints.length).to.be.equal(5); // Autoborder points on the polygon + }); + }); + cy.get('.cvat-canvas-container').click(750, 500).click(700, 350); // Click on the first polyline points to finish the change + cy.get('.cvat_canvas_shape_drawing').should('not.exist'); + cy.get('#cvat_canvas_shape_2') + .invoke('attr', 'points') + .then(($pointsCordsAfter) => { + // expected '10071.4287109375,9788.5712890625 ...' to not equal '10166.6669921875,9883.8095703125 ...' + expect($pointsCordsBefore).to.not.equal($pointsCordsAfter); + }); + }); + }); + + it('Edit handler for the points.', () => { + cy.createPoint(createPointsShape); + cy.get('.cvat-canvas-container').trigger('mousemove', 200, 400).trigger('mouseenter', 200, 400); + cy.get('.cvat-canvas-container') + .click(200, 400, { shiftKey: true }) + .then(() => { + // Click on the point shape to start of change + cy.get('.cvat_canvas_selected_point').should('exist'); + cy.get('.cvat_canvas_shape_drawing').should('exist').and('have.attr', 'data-origin-client-id', '3'); + }); + cy.get('.cvat-canvas-container') + .click(200, 300) + .find('circle') + .then(($circleEditHanlerProgress) => { + // rightclick() on canvas to check canceling draw a additional point + cy.get('.cvat-canvas-container') + .rightclick() + .find('circle') + .then(($circleEditHanlerProgressCancelDrawPoint) => { + expect($circleEditHanlerProgress.length).not.equal( + $circleEditHanlerProgressCancelDrawPoint.length, + ); // expected 4 to not equal 3 + }); + }); + cy.get('.cvat-canvas-container').click(200, 300).click(200, 400); // Click on the first points shape to finish the change + cy.get('#cvat_canvas_shape_3') + .find('circle') + .then(($circleCountAfterHanlerEditing) => { + expect($circleCountAfterHanlerEditing.length).to.be.equal(2); + }); + }); + + it('Combining polygon and points.', () => { + testActivatingShape(520, 400, '#cvat_canvas_shape_1'); + cy.get('.cvat-canvas-container') // Draw line with shift key held down + .click(550, 450, { shiftKey: true }) + .trigger('mousemove', 530, 450, { shiftKey: true }) + .trigger('mousemove', 500, 450, { shiftKey: true }) + .trigger('mousemove', 200, 400, { shiftKey: true }); + // Coverage "!pointsCriteria && !lengthCriteria" + cy.get('.cvat-canvas-container').click(200, 400).click(200, 300); + cy.get('.cvat_canvas_autoborder_point_direction').should('exist'); + cy.get('.cvat-canvas-container').dblclick(200, 300); + cy.get('.cvat_canvas_autoborder_point_direction').should('not.exist'); + cy.get('.cvat-canvas-container').click(450, 350); + cy.get('#cvat_canvas_shape_1') + .invoke('attr', 'points') + .then(($points) => { + expect( + $points.split(' ').filter(function (el) { + return el.length != 0; + }).length, + ).to.be.equal(11); + }); + testActivatingShape(750, 500, '#cvat_canvas_shape_2'); + // Coverage "circle.on('mousedown', (e: MouseEvent): void => {" + cy.get('.cvat-canvas-container') + .click(750, 500, { shiftKey: true }) + .click(450, 350) + .trigger('mousemove', 450, 370) + .click(450, 350); + }); + }); +}); diff --git a/tests/cypress/integration/actions_tasks_objects/registration_involved/case_39_issue_2572_rename_task.js b/tests/cypress/integration/actions_tasks_objects/registration_involved/case_39_issue_2572_rename_task.js index f88754998c46..289ebf440d92 100644 --- a/tests/cypress/integration/actions_tasks_objects/registration_involved/case_39_issue_2572_rename_task.js +++ b/tests/cypress/integration/actions_tasks_objects/registration_involved/case_39_issue_2572_rename_task.js @@ -47,6 +47,7 @@ context('Rename a task.', () => { }); after(() => { + cy.deletingRegisteredUsers([secondUserName]); cy.login(); cy.deleteTask(newNaskName); }); diff --git a/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js b/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js index 29c6c55416eb..1c2242bdd18d 100644 --- a/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js +++ b/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js @@ -130,6 +130,8 @@ context('Review pipeline feature', () => { after(() => { cy.goToTaskList(); cy.deleteTask(taskName); + cy.logout(); + cy.deletingRegisteredUsers([secondUserName, thirdUserName]); }); describe(`Testing "${labelName}"`, () => { diff --git a/tests/cypress/integration/actions_users/registration_involved/case_2_register_user_change_pass.js b/tests/cypress/integration/actions_users/registration_involved/case_2_register_user_change_pass.js index adf65255fda0..609755a98116 100644 --- a/tests/cypress/integration/actions_users/registration_involved/case_2_register_user_change_pass.js +++ b/tests/cypress/integration/actions_users/registration_involved/case_2_register_user_change_pass.js @@ -1,26 +1,19 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT /// -const randomString = (isPassword) => { - let result = ''; - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - for (let i = 0; i <= 8; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); - } - return isPassword ? `${result}${Math.floor(Math.random() * 10)}` : result; -}; - context('Register user, change password, login with new password', () => { const caseId = '2'; - const firstName = `${randomString()}`; - const lastName = `${randomString()}`; - const userName = `${randomString()}`; + const firstName = 'SecuserfmCaseTwo'; + const lastName = 'SecuserlmCaseTwo'; + const userName = 'SecuserCase2'; const emailAddr = `${userName}@local.local`; - const password = `${randomString(true)}`; - const newPassword = `${randomString(true)}`; + const password = 'GDrb41RguF!'; + const incorrectCurrentPassword = 'gDrb41RguF!'; + const newPassword = 'bYdOk8#eEd'; + const secondNewPassword = 'ndTh48@yVY'; function changePassword(userName, password, newPassword) { cy.get('.cvat-right-header') @@ -41,6 +34,12 @@ context('Register user, change password, login with new password', () => { cy.url().should('include', '/auth/register'); }); + after(() => { + cy.get('.cvat-modal-change-password').find('[aria-label="Close"]').click(); + cy.logout(userName); + cy.deletingRegisteredUsers([userName]); + }); + describe(`Testing "Case ${caseId}"`, () => { it('Register user, change password', () => { cy.userRegistration(firstName, lastName, userName, emailAddr, password); @@ -55,7 +54,7 @@ context('Register user, change password, login with new password', () => { cy.login(userName, newPassword); }); it('Change password with incorrect current password', () => { - changePassword(userName, `${randomString(true)}`, newPassword); + changePassword(userName, incorrectCurrentPassword, secondNewPassword); cy.get('.cvat-notification-notice-change-password-failed').should('exist'); }); }); diff --git a/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js b/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js index e2be6d576f51..be9e5acfb0cd 100644 --- a/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js +++ b/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js @@ -43,6 +43,7 @@ context('Multiple users. Assign task, job.', () => { }); after(() => { + cy.deletingRegisteredUsers([secondUserName, thirdUserName]); cy.login(); cy.deleteTask(taskName); }); diff --git a/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js b/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js index bbcb5db7ff19..37842cee3539 100644 --- a/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js +++ b/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -16,6 +16,11 @@ context('Issue 1599 (Chinese alphabet).', () => { cy.url().should('include', '/auth/register'); }); + after(() => { + cy.logout(userName); + cy.deletingRegisteredUsers([userName]); + }); + describe('User registration using the Chinese alphabet.', () => { it('Filling in the placeholder "First name"', () => { cy.get('[placeholder="First name"]').type(firstName).should('not.have.class', 'has-error'); diff --git a/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js b/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js index 2ee411d9a504..e0605bf974e9 100644 --- a/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js +++ b/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -16,6 +16,11 @@ context('Issue 1599 (Polish alphabet).', () => { cy.url().should('include', '/auth/register'); }); + after(() => { + cy.logout(userName); + cy.deletingRegisteredUsers([userName]); + }); + describe('User registration using the Polish alphabet.', () => { it('Filling in the placeholder "First name"', () => { cy.get('[placeholder="First name"]').type(firstName).should('not.have.class', 'has-error'); diff --git a/tests/cypress/integration/canvas3d_functionality/assets/test_canvas3d.zip b/tests/cypress/integration/canvas3d_functionality/assets/test_canvas3d.zip new file mode 100644 index 000000000000..c6defd2869c6 Binary files /dev/null and b/tests/cypress/integration/canvas3d_functionality/assets/test_canvas3d.zip differ diff --git a/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js b/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js new file mode 100644 index 000000000000..aa6aa212fca0 --- /dev/null +++ b/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js @@ -0,0 +1,179 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/// + +import { taskName } from '../../support/const_canvas3d'; +// Firefox does not yet support WebGL in headless mode: https://bugzilla.mozilla.org/show_bug.cgi?id=1375585 (disabled in the cypress_cron_type.json) +context('Canvas 3D functionality. Basic actions.', () => { + const caseId = '56'; + const screenshotsPath = + 'cypress/screenshots/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js'; + + function compareImages(imgBefore, imgAfter) { + cy.compareImages(`${screenshotsPath}/${imgBefore}`, `${screenshotsPath}/${imgAfter}`).then((diffPercent) => { + expect(diffPercent).to.be.gt(0); + }); + } + + function testPerspectiveChangeOnKeyPress(key, screenshotNameBefore, screenshotNameAfter) { + cy.get('.cvat-canvas3d-perspective').trigger('mouseover').screenshot(screenshotNameBefore); + cy.get('body').type(`{alt}${key}`); + cy.get('.cvat-canvas3d-perspective').screenshot(screenshotNameAfter); + compareImages(`${screenshotNameBefore}.png`, `${screenshotNameAfter}.png`); + } + + function testPerspectiveChangeOnArrowKeyPress(key, screenshotNameBefore, screenshotNameAfter) { + cy.get('.cvat-canvas3d-perspective').trigger('mouseover').screenshot(screenshotNameBefore); + cy.get('body').type(key); + cy.get('.cvat-canvas3d-perspective').screenshot(screenshotNameAfter); + compareImages(`${screenshotNameBefore}.png`, `${screenshotNameAfter}.png`); + } + + function testPerspectiveChangeOnWheel(screenshotNameBefore, screenshotNameAfter) { + cy.get('.cvat-canvas3d-perspective').screenshot(screenshotNameBefore); + for (let i = 0; i < 5; i++) { + cy.get('.cvat-canvas3d-perspective').trigger('wheel', { deltaY: -200 }); + } + cy.get('.cvat-canvas3d-perspective').screenshot(screenshotNameAfter); + compareImages(`${screenshotNameBefore}.png`, `${screenshotNameAfter}.png`); + } + + function testTopSideFrontChangeOnWheel(element, deltaY, screenshotNameBefore, screenshotNameAfter) { + cy.get(element).screenshot(screenshotNameBefore); + for (let i = 0; i < 10; i++) { + cy.get(element).trigger('wheel', { deltaY: deltaY }); + } + cy.get(element).screenshot(screenshotNameAfter); + compareImages(`${screenshotNameBefore}.png`, `${screenshotNameAfter}.png`); + } + + before(() => { + cy.openTaskJob(taskName); + }); + + after(() => { + cy.goToTaskList(); + cy.deleteTask(taskName); + }); + + describe(`Testing case "${caseId}"`, () => { + it('Check existing of elements.', () => { + cy.get('.cvat-canvas3d-perspective') + .should('exist') + .and('be.visible') + .within(() => { + cy.get('.cvat-canvas3d-perspective-arrow-directions') + .should('exist') + .and('be.visible') + .within(() => { + cy.get('[aria-label="arrow-up"]').should('exist').and('be.visible'); + cy.get('[aria-label="arrow-left"]').should('exist').and('be.visible'); + cy.get('[aria-label="arrow-down"]').should('exist').and('be.visible'); + cy.get('[aria-label="arrow-right"]').should('exist').and('be.visible'); + }); + cy.get('.cvat-canvas3d-perspective-directions') + .should('exist') + .and('be.visible') + .within(() => { + cy.contains('button', 'U').should('exist').and('be.visible'); + cy.contains('button', 'I').should('exist').and('be.visible'); + cy.contains('button', 'O').should('exist').and('be.visible'); + cy.contains('button', 'J').should('exist').and('be.visible'); + cy.contains('button', 'K').should('exist').and('be.visible'); + cy.contains('button', 'L').should('exist').and('be.visible'); + }); + }); + cy.get('.cvat-canvas3d-topview').should('exist').and('be.visible'); + cy.get('.cvat-canvas3d-sideview').should('exist').and('be.visible'); + cy.get('.cvat-canvas3d-frontview').should('exist').and('be.visible'); + cy.get('.cvat-canvas-controls-sidebar') + .should('exist') + .and('be.visible') + .within(() => { + cy.get('.cvat-move-control').should('exist').and('be.visible'); + cy.get('.cvat-cursor-control').should('exist').and('be.visible'); + cy.get('.cvat-draw-cuboid-control').should('exist').and('be.visible'); + cy.get('[aria-label="camera"]').should('exist').and('be.visible'); + }); + }); + + it('Check workspace selector.', () => { + // Try to click on the disabled workspace selectors. The value of the selector should not changed. + cy.get('.cvat-workspace-selector').should('contain.text', 'Standard 3D').click(); + for (const dropdownItems of [ + '[title="Attribute annotation"]', + '[title="Tag annotation"]', + '[title="Review"]', + ]) { + cy.get('.cvat-workspace-selector-dropdown') + .not('.ant-select-dropdown-hidden') + .within(() => { + cy.get(dropdownItems).click(); + }); + cy.get('.cvat-workspace-selector').should('contain.text', 'Standard 3D'); + } + }); + + it('Interaction with the frame change buttons.', () => { + cy.get('.cvat-player-last-button').click(); + cy.checkFrameNum(2); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_50000_points.pcd'); + cy.get('.cvat-player-first-button').click(); + cy.checkFrameNum(0); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_100000_points.pcd'); + cy.get('.cvat-player-forward-button').click(); + cy.checkFrameNum(2); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_50000_points.pcd'); + cy.get('.cvat-player-backward-button').click(); + cy.checkFrameNum(0); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_100000_points.pcd'); + cy.get('.cvat-player-next-button').click(); + cy.checkFrameNum(1); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_10000_points.pcd'); + cy.get('.cvat-player-previous-button').click(); + cy.checkFrameNum(0); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_100000_points.pcd'); + cy.get('.cvat-player-play-button').click(); + cy.checkFrameNum(2); + cy.get('.cvat-player-filename-wrapper').should('contain.text', 'generated_pcd_50000_points.pcd'); + cy.get('.cvat-player-first-button').click(); // Return to first frame + }); + + it('Testing perspective visual regressions.', () => { + testPerspectiveChangeOnWheel('perspective_before_wheel', 'perspective_after_wheel'); + testPerspectiveChangeOnKeyPress('u', 'before_press_altU', 'after_press_altU'); + testPerspectiveChangeOnKeyPress('o', 'before_press_altO', 'after_press_altO'); + testPerspectiveChangeOnKeyPress('i', 'before_press_altI', 'after_press_altI'); + testPerspectiveChangeOnKeyPress('k', 'before_press_altK', 'after_press_altK'); + testPerspectiveChangeOnKeyPress('j', 'before_press_altJ', 'after_press_altJ'); + testPerspectiveChangeOnKeyPress('l', 'before_press_altL', 'after_press_altL'); + testPerspectiveChangeOnArrowKeyPress('{uparrow}', 'before_press_uparrow', 'after_press_uparrow'); + testPerspectiveChangeOnArrowKeyPress('{downarrow}', 'before_press_downarrow', 'after_press_downarrow'); + testPerspectiveChangeOnArrowKeyPress('{leftarrow}', 'before_press_leftarrow', 'after_press_leftarrow'); + testPerspectiveChangeOnArrowKeyPress('{rightarrow}', 'before_press_rightarrow', 'after_press_rightarrow'); + }); + + it('Testing top/side/front views visual regressions.', () => { + testTopSideFrontChangeOnWheel( + '.cvat-canvas3d-topview', + -1000, + 'topview_before_wheel', + 'topview_after_wheel', + ); + testTopSideFrontChangeOnWheel( + '.cvat-canvas3d-sideview', + -1000, + 'sideview_before_wheel', + 'sideview_after_wheel', + ); + testTopSideFrontChangeOnWheel( + '.cvat-canvas3d-frontview', + -1000, + 'frontview_before_wheel', + 'frontview_after_wheel', + ); + }); + }); +}); diff --git a/tests/cypress/plugins/compareImages/addPlugin.js b/tests/cypress/plugins/compareImages/addPlugin.js new file mode 100644 index 000000000000..3cb8529dead2 --- /dev/null +++ b/tests/cypress/plugins/compareImages/addPlugin.js @@ -0,0 +1,16 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +// eslint-disable-next-line no-undef +exports.compareImages = compareImages; + +const Jimp = require('jimp'); + +async function compareImages(args) { + const imgBase = await Jimp.read(args.imgBase); + const imgAfterChanges = await Jimp.read(args.imgAfterChanges); + const diff = Jimp.diff(imgBase, imgAfterChanges); + + return diff.percent; +} diff --git a/tests/cypress/plugins/compareImages/compareImagesCommand.js b/tests/cypress/plugins/compareImages/compareImagesCommand.js new file mode 100644 index 000000000000..92c49f14dfd6 --- /dev/null +++ b/tests/cypress/plugins/compareImages/compareImagesCommand.js @@ -0,0 +1,10 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +Cypress.Commands.add('compareImages', function (imgBase, imgAfterChanges) { + return cy.task('compareImages', { + imgBase: imgBase, + imgAfterChanges: imgAfterChanges, + }); +}); diff --git a/tests/cypress/plugins/index.js b/tests/cypress/plugins/index.js index b9748b5b34f6..b529d9b611aa 100644 --- a/tests/cypress/plugins/index.js +++ b/tests/cypress/plugins/index.js @@ -6,12 +6,14 @@ const { imageGenerator } = require('../plugins/imageGenerator/addPlugin'); const { createZipArchive } = require('../plugins/createZipArchive/addPlugin'); +const { compareImages } = require('../plugins/compareImages/addPlugin'); const fs = require('fs'); module.exports = (on, config) => { require('@cypress/code-coverage/task')(on, config); on('task', { imageGenerator }); on('task', { createZipArchive }); + on('task', { compareImages }); on('task', { log(message) { console.log(message); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 63ac25d9b124..d707c8eb018e 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -8,6 +8,7 @@ require('cypress-file-upload'); require('../plugins/imageGenerator/imageGeneratorCommand'); require('../plugins/createZipArchive/createZipArchiveCommand'); require('cypress-localstorage-commands'); +require('../plugins/compareImages/compareImagesCommand'); let selectedValueGlobal = ''; @@ -41,10 +42,47 @@ Cypress.Commands.add('userRegistration', (firstName, lastName, userName, emailAd } }); +Cypress.Commands.add('deletingRegisteredUsers', (accountToDelete) => { + cy.request({ + method: 'POST', + url: '/api/v1/auth/login', + body: { + username: Cypress.env('user'), + email: Cypress.env('email'), + password: Cypress.env('password'), + }, + }).then((responce) => { + const authKey = responce['body']['key']; + cy.request({ + url: '/api/v1/users?page_size=all', + headers: { + Authorization: `Token ${authKey}`, + }, + }).then((responce) => { + const responceResult = responce['body']['results']; + for (const user of responceResult) { + const userId = user['id']; + const userName = user['username']; + for (const account of accountToDelete) { + if (userName === account) { + cy.request({ + method: 'DELETE', + url: `/api/v1/users/${userId}`, + headers: { + Authorization: `Token ${authKey}`, + }, + }); + } + } + } + }); + }); +}); + Cypress.Commands.add( 'createAnnotationTask', ( - taksName = 'New annotation task', + taskName = 'New annotation task', labelName = 'Some label', attrName = 'Some attr name', textDefaultValue = 'Some default value for type Text', @@ -58,7 +96,7 @@ Cypress.Commands.add( ) => { cy.get('#cvat-create-task-button').click({ force: true }); cy.url().should('include', '/tasks/create'); - cy.get('[id="name"]').type(taksName); + cy.get('[id="name"]').type(taskName); if (!forProject) { cy.get('.cvat-constructor-viewer-new-item').click(); cy.get('[placeholder="Label name"]').type(labelName); diff --git a/tests/cypress/support/commands_projects.js b/tests/cypress/support/commands_projects.js index 5d180ac1c370..e4633160fcf5 100644 --- a/tests/cypress/support/commands_projects.js +++ b/tests/cypress/support/commands_projects.js @@ -63,6 +63,17 @@ Cypress.Commands.add('deleteProject', (projectName, projectID, expectedResult = } }); +Cypress.Commands.add('deleteProjectViaActions', (projectName) => { + cy.get('.cvat-project-top-bar-actions').trigger('mouseover'); + cy.get('.cvat-project-actions-menu').within(() => { + cy.contains('[role="menuitem"]', 'Delete').click(); + }); + cy.get('.cvat-modal-confirm-remove-project').within(() => { + cy.contains('button', 'Delete').click(); + }); + cy.contains('.cvat-projects-project-item-title', projectName).should('not.exist'); +}); + Cypress.Commands.add('assignProjectToUser', (user) => { cy.get('.cvat-project-details').within(() => { cy.get('.cvat-user-search-field').click().type(user); diff --git a/tests/cypress/support/const_canvas3d.js b/tests/cypress/support/const_canvas3d.js new file mode 100644 index 000000000000..2228893b09d0 --- /dev/null +++ b/tests/cypress/support/const_canvas3d.js @@ -0,0 +1,40 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/// + +export const labelName = `points cloud`; +export const taskName = `Canvas 3D functionality`; +export const pcdPngZipArr = '../../cypress/integration/canvas3d_functionality/assets/test_canvas3d.zip'; +export const attrName = `Attr for ${labelName}`; +export const textDefaultValue = 'Some default value for type Text'; +export const advancedConfigurationParams = false; +export const multiAttrParams = false; + +it('Prepare to testing', () => { + cy.visit('/'); + cy.login(); + cy.get('.cvat-tasks-page').should('exist'); + let listItems = []; + cy.document().then((doc) => { + const collection = Array.from(doc.querySelectorAll('.cvat-item-task-name')); + for (let i = 0; i < collection.length; i++) { + listItems.push(collection[i].innerText); + } + if (listItems.indexOf(taskName) === -1) { + cy.task('log', "A task doesn't exist. Creating."); + cy.createAnnotationTask( + taskName, + labelName, + attrName, + textDefaultValue, + pcdPngZipArr, + multiAttrParams, + advancedConfigurationParams, + ); + } else { + cy.task('log', 'The task exist. Skipping creation.'); + } + }); +});